SAFE Stack アプリのデータ保存 (3. サーバの API 本体の実装)
SAFE Stack の ToDo アプリはデータを保存できるようにする続き。
ファイル操作はサーバでおこない、クライアントに API を提供する形になる。 API のシグネチャを src/Shared/Shared.fs に追加する。
diff --git a/src/Shared/Shared.fs b/src/Shared/Shared.fs
--- a/src/Shared/Shared.fs
+++ b/src/Shared/Shared.fs
@@ -80,4 +80,5 @@ module Route =
type ITodosApi =
{ getTodos: unit -> Async<Todo list>
addTodo: Todo -> Async<Todo>
- moveTodo: int * MoveDstPosition -> Async<Todo List> }
+ moveTodo: int * MoveDstPosition -> Async<Todo List>
+ saveTodos: unit -> Async<Todo list> }
そして、FSharp.Json (link) や準備したユーティリティー (link) を使って目的の機能を実現するように src/Server/Server.fs を変更する。
Storage
クラスに JSON ファイルの読み書きを追加した。
diff --git a/src/Server/Server.fs b/src/Server/Server.fs
index 9a01e94..9c24f82 100644
--- a/src/Server/Server.fs
+++ b/src/Server/Server.fs
@@ -6,10 +6,38 @@ open Saturn
open Serilog
open Giraffe.SerilogExtensions
+open FSharp.Json
open Shared
+open ServerUtil
+
+let dataDirEnvName = "TODO_DATA_DIR"
+let defaultDataSubdir = "TodoApp"
+let dataBasename = "todos.json"
+
+let writeTodoListToJsonFile (filePath: string) (todos: Todo list) =
+ let json = Json.serialize todos
+ System.IO.File.WriteAllText (filePath, json)
+
+let readTodoListFromJsonFile (filePath: string) : Todo list =
+ let text = System.IO.File.ReadAllText filePath
+ Json.deserialize<Todo list> text
+
+let dataDir () =
+ let dataSubdir =
+ match EnvUtil.getEnv dataDirEnvName |> StringUtil.strip with
+ | "" -> StringUtil.strip defaultDataSubdir
+ | x -> x
+ if IoUtil.isAbsPath dataSubdir then
+ dataSubdir
+ else
+ (IoUtil.docRoot ()) + IoUtil.dirSepStr + dataSubdir
+
-type Storage() =
+type Storage (dirPath_arg: string) =
+ let dirPath = dirPath_arg
+ let () = IoUtil.assureDir dirPath
+ let filePath = dirPath + IoUtil.dirSepStr + dataBasename
// ResizeArray is System.Collections.Generic.List
let todos = ResizeArray<_>()
@@ -35,21 +63,39 @@ type Storage() =
TodoResizeArray.move todos srcIdx dstPos
Ok (List.ofSeq todos)
+ member __.SaveTodos () =
+ IoUtil.assureDir dirPath
+ let todoList = List.ofSeq todos
+ writeTodoListToJsonFile filePath todoList
+ Ok todoList
+
+ member __.LoadTodos () =
+ if not (IoUtil.checkFile filePath) then
+ Error $"file dose not exist: {filePath}"
+ else
+ let newTodoList = readTodoListFromJsonFile filePath
+ todos.Clear ()
+ todos.AddRange (newTodoList)
+ Ok newTodoList
+
+ member this.ResumeTodos () =
+ match this.LoadTodos () with
+ | Ok _ -> true
+ | Error msg -> false
+
Log.Logger <- LoggerConfiguration()
.Destructure.FSharpTypes()
.WriteTo.Console()
.CreateLogger()
-let storage = Storage()
+let storage = dataDir () |> Storage
-storage.AddTodo(Todo.create "Create new SAFE project")
-|> ignore
-
-storage.AddTodo(Todo.create "Write your app")
-|> ignore
-
-storage.AddTodo(Todo.create "Ship it !!!")
-|> ignore
+if storage.ResumeTodos () then
+ ()
+else
+ storage.AddTodo (Todo.create "Create new SAFE project") |> ignore
+ storage.AddTodo (Todo.create "Write your app") |> ignore
+ storage.AddTodo (Todo.create "Ship it !!!") |> ignore
let todosApi (logger: ILogger) =
{ getTodos =
@@ -80,7 +126,19 @@ let todosApi (logger: ILogger) =
| Error e -> failwith e
logger.Information "moveTodo: end"
return newTodoList
- } }
+ }
+ saveTodos =
+ fun () ->
+ async {
+ logger.Information "saveTodos: begin"
+ let newTodoList =
+ match storage.SaveTodos () with
+ | Ok newTodoList -> newTodoList
+ | Error e -> failwith e
+ logger.Information "saveTodos: end"
+ return newTodoList
+ }
+ }
open Microsoft.AspNetCore.Http