My aggregated monorepo of OCaml code, automaintained

Add Immich CLI with authentication and fix daemon fiber handling

- Add immich_auth library with session management (JWT + API key)
- Add CLI commands: auth, server, albums, faces
- Support .well-known/immich endpoint for API URL discovery
- Support multiple profiles for managing multiple servers

Connection pool and H2 improvements:
- Use fork_daemon for connection pool fibers to allow clean shutdown
- Use fork_daemon for H2 reader fiber to prevent switch blocking
- Handle Cancelled exceptions properly during cleanup
- Add await_cancel() for proper fiber lifecycle

OpenAPI code generator fix:
- Check nullable flag on all schema types, not just the fallback case
- Fields with nullable: true are now correctly treated as optional

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>

+1950 -381
+18 -10
ocaml-conpool/lib/conpool.ml
··· 242 242 let conn_cancel_ref = ref (fun (_ : exn) -> ()) in 243 243 let ready_promise, ready_resolver = Eio.Promise.create () in 244 244 245 - Eio.Fiber.fork ~sw:pool.sw (fun () -> 246 - Eio.Switch.run (fun conn_sw -> 247 - conn_sw_ref := Some conn_sw; 248 - conn_cancel_ref := (fun exn -> Eio.Switch.fail conn_sw exn); 249 - (* Signal that the switch is ready *) 250 - Eio.Promise.resolve ready_resolver (); 251 - (* Block until the switch is cancelled *) 252 - let wait_forever, _never_resolved = Eio.Promise.create () in 253 - Eio.Promise.await wait_forever 254 - ) 245 + (* Use fork_daemon so connection fibers don't prevent parent switch from completing. 246 + When the parent switch completes, all connection daemon fibers are cancelled, 247 + which triggers cleanup of their inner switches and connection resources. *) 248 + Eio.Fiber.fork_daemon ~sw:pool.sw (fun () -> 249 + (try 250 + Eio.Switch.run (fun conn_sw -> 251 + conn_sw_ref := Some conn_sw; 252 + conn_cancel_ref := (fun exn -> Eio.Switch.fail conn_sw exn); 253 + (* Signal that the switch is ready *) 254 + Eio.Promise.resolve ready_resolver (); 255 + (* Block until the switch is cancelled *) 256 + Eio.Fiber.await_cancel () 257 + ) 258 + with 259 + | Eio.Cancel.Cancelled _ -> () 260 + | exn -> 261 + Log.warn (fun m -> m "Connection fiber caught exception: %s" (Printexc.to_string exn))); 262 + `Stop_daemon 255 263 ); 256 264 257 265 (* Wait for the switch to be created *)
+65
ocaml-immich/bin/cmd_albums.ml
··· 1 + (*--------------------------------------------------------------------------- 2 + Copyright (c) 2025 Anil Madhavapeddy. All rights reserved. 3 + SPDX-License-Identifier: ISC 4 + ---------------------------------------------------------------------------*) 5 + 6 + open Cmdliner 7 + 8 + (* List albums - using low-level API since get_all_albums returns array but typed as single *) 9 + 10 + let list_action ~requests_config ~profile ~shared env = 11 + Immich_auth.Cmd.with_client ~requests_config ?profile (fun _fs client -> 12 + let api = Immich_auth.Client.client client in 13 + let session = Immich.session api in 14 + let base_url = Immich.base_url api in 15 + let query = match shared with 16 + | None -> "" 17 + | Some true -> "?shared=true" 18 + | Some false -> "?shared=false" 19 + in 20 + let url = base_url ^ "/albums" ^ query in 21 + let response = Requests.get session url in 22 + if Requests.Response.ok response then begin 23 + let json = Requests.Response.json response in 24 + let albums = Openapi.Runtime.Json.decode_json_exn 25 + (Jsont.list Immich.Album.ResponseDto.jsont) json in 26 + if albums = [] then 27 + Fmt.pr "No albums found.@." 28 + else begin 29 + Fmt.pr "Albums:@."; 30 + List.iter (fun album -> 31 + let shared_marker = if Immich.Album.ResponseDto.shared album then " (shared)" else "" in 32 + Fmt.pr " %s - %s (%d assets)%s@." 33 + (Immich.Album.ResponseDto.id album) 34 + (Immich.Album.ResponseDto.album_name album) 35 + (Immich.Album.ResponseDto.asset_count album) 36 + shared_marker 37 + ) albums 38 + end 39 + end else begin 40 + Fmt.epr "Failed to get albums: %d@." (Requests.Response.status_code response); 41 + exit 1 42 + end 43 + ) env 44 + 45 + let shared_arg = 46 + let doc = "Filter by shared status. Use --shared for shared albums, --no-shared for owned only." in 47 + Arg.(value & opt (some bool) None & info ["shared"] ~doc) 48 + 49 + let list_cmd env fs = 50 + let doc = "List all albums." in 51 + let info = Cmd.info "list" ~doc in 52 + let list' (style_renderer, level) requests_config profile shared = 53 + Immich_auth.Cmd.setup_logging_with_config style_renderer level requests_config; 54 + list_action ~requests_config ~profile ~shared env 55 + in 56 + Cmd.v info Term.(const list' $ Immich_auth.Cmd.setup_logging $ Immich_auth.Cmd.requests_config_term fs $ Immich_auth.Cmd.profile_arg $ shared_arg) 57 + 58 + (* Albums command group *) 59 + 60 + let albums_cmd env fs = 61 + let doc = "Album commands." in 62 + let info = Cmd.info "albums" ~doc in 63 + Cmd.group info 64 + [ list_cmd env fs 65 + ]
+146
ocaml-immich/bin/cmd_faces.ml
··· 1 + (*--------------------------------------------------------------------------- 2 + Copyright (c) 2025 Anil Madhavapeddy. All rights reserved. 3 + SPDX-License-Identifier: ISC 4 + ---------------------------------------------------------------------------*) 5 + 6 + open Cmdliner 7 + 8 + (* Search for people by name *) 9 + 10 + let search_action ~requests_config ~profile ~name ~with_hidden env = 11 + Immich_auth.Cmd.with_client ~requests_config ?profile (fun _fs client -> 12 + let api = Immich_auth.Client.client client in 13 + let session = Immich.session api in 14 + let base_url = Immich.base_url api in 15 + (* Person.search_person returns ResponseDto (single) but should be a list *) 16 + (* Use low-level API to get proper list response *) 17 + let query = Printf.sprintf "?name=%s%s" 18 + (Uri.pct_encode name) 19 + (if with_hidden then "&withHidden=true" else "") in 20 + let url = base_url ^ "/people/search" ^ query in 21 + let response = Requests.get session url in 22 + if Requests.Response.ok response then begin 23 + let json = Requests.Response.json response in 24 + let people = Openapi.Runtime.Json.decode_json_exn 25 + (Jsont.list Immich.Person.ResponseDto.jsont) json in 26 + if people = [] then 27 + Fmt.pr "No people found matching '%s'.@." name 28 + else begin 29 + Fmt.pr "People matching '%s':@." name; 30 + List.iter (fun person -> 31 + let name = Immich.Person.ResponseDto.name person in 32 + let id = Immich.Person.ResponseDto.id person in 33 + let hidden = if Immich.Person.ResponseDto.is_hidden person then " (hidden)" else "" in 34 + let display_name = if name = "" then "<unnamed>" else name in 35 + Fmt.pr " %s - %s%s@." id display_name hidden 36 + ) people 37 + end 38 + end else begin 39 + Fmt.epr "Search failed: %d@." (Requests.Response.status_code response); 40 + exit 1 41 + end 42 + ) env 43 + 44 + let name_arg = 45 + let doc = "Name to search for." in 46 + Arg.(required & pos 0 (some string) None & info [] ~docv:"NAME" ~doc) 47 + 48 + let with_hidden_arg = 49 + let doc = "Include hidden people in results." in 50 + Arg.(value & flag & info ["with-hidden"] ~doc) 51 + 52 + let search_cmd env fs = 53 + let doc = "Search for people by name." in 54 + let info = Cmd.info "search" ~doc in 55 + let search' (style_renderer, level) requests_config profile name with_hidden = 56 + Immich_auth.Cmd.setup_logging_with_config style_renderer level requests_config; 57 + search_action ~requests_config ~profile ~name ~with_hidden env 58 + in 59 + Cmd.v info Term.(const search' $ Immich_auth.Cmd.setup_logging $ Immich_auth.Cmd.requests_config_term fs $ Immich_auth.Cmd.profile_arg $ name_arg $ with_hidden_arg) 60 + 61 + (* Get person thumbnail *) 62 + 63 + let output_arg = 64 + let doc = "Output file path. Use '-' for stdout." in 65 + Arg.(value & opt string "-" & info ["output"; "o"] ~docv:"FILE" ~doc) 66 + 67 + let person_id_arg = 68 + let doc = "Person ID." in 69 + Arg.(required & pos 0 (some string) None & info [] ~docv:"PERSON_ID" ~doc) 70 + 71 + let thumbnail_action ~requests_config ~profile ~person_id ~output env = 72 + Immich_auth.Cmd.with_client ~requests_config ?profile (fun _fs client -> 73 + let api = Immich_auth.Client.client client in 74 + let session = Immich.session api in 75 + let base_url = Immich.base_url api in 76 + let url = Printf.sprintf "%s/people/%s/thumbnail" base_url person_id in 77 + let response = Requests.get session url in 78 + if Requests.Response.ok response then begin 79 + let data = Requests.Response.text response in 80 + if output = "-" then 81 + print_string data 82 + else begin 83 + let oc = open_out_bin output in 84 + output_string oc data; 85 + close_out oc; 86 + Fmt.pr "Thumbnail saved to %s@." output 87 + end 88 + end else begin 89 + Fmt.epr "Failed to get thumbnail: %d@." (Requests.Response.status_code response); 90 + exit 1 91 + end 92 + ) env 93 + 94 + let thumbnail_cmd env fs = 95 + let doc = "Download a person's thumbnail image." in 96 + let info = Cmd.info "thumbnail" ~doc in 97 + let thumbnail' (style_renderer, level) requests_config profile person_id output = 98 + Immich_auth.Cmd.setup_logging_with_config style_renderer level requests_config; 99 + thumbnail_action ~requests_config ~profile ~person_id ~output env 100 + in 101 + Cmd.v info Term.(const thumbnail' $ Immich_auth.Cmd.setup_logging $ Immich_auth.Cmd.requests_config_term fs $ Immich_auth.Cmd.profile_arg $ person_id_arg $ output_arg) 102 + 103 + (* List all people *) 104 + 105 + let list_action ~requests_config ~profile ~with_hidden env = 106 + Immich_auth.Cmd.with_client ~requests_config ?profile (fun _fs client -> 107 + let api = Immich_auth.Client.client client in 108 + let with_hidden_param = if with_hidden then Some "true" else None in 109 + let resp = Immich.People.get_all_people api ?with_hidden:with_hidden_param () in 110 + let people = Immich.People.ResponseDto.people resp in 111 + let total = Immich.People.ResponseDto.total resp in 112 + let hidden = Immich.People.ResponseDto.hidden resp in 113 + Fmt.pr "People: %d total, %d hidden@." total hidden; 114 + if people = [] then 115 + Fmt.pr "No people found.@." 116 + else begin 117 + List.iter (fun person -> 118 + let name = Immich.Person.ResponseDto.name person in 119 + let id = Immich.Person.ResponseDto.id person in 120 + let is_hidden = Immich.Person.ResponseDto.is_hidden person in 121 + let hidden_marker = if is_hidden then " (hidden)" else "" in 122 + let display_name = if name = "" then "<unnamed>" else name in 123 + Fmt.pr " %s - %s%s@." id display_name hidden_marker 124 + ) people 125 + end 126 + ) env 127 + 128 + let list_cmd env fs = 129 + let doc = "List all people." in 130 + let info = Cmd.info "list" ~doc in 131 + let list' (style_renderer, level) requests_config profile with_hidden = 132 + Immich_auth.Cmd.setup_logging_with_config style_renderer level requests_config; 133 + list_action ~requests_config ~profile ~with_hidden env 134 + in 135 + Cmd.v info Term.(const list' $ Immich_auth.Cmd.setup_logging $ Immich_auth.Cmd.requests_config_term fs $ Immich_auth.Cmd.profile_arg $ with_hidden_arg) 136 + 137 + (* Faces command group *) 138 + 139 + let faces_cmd env fs = 140 + let doc = "Face and people commands." in 141 + let info = Cmd.info "faces" ~doc in 142 + Cmd.group info 143 + [ list_cmd env fs 144 + ; search_cmd env fs 145 + ; thumbnail_cmd env fs 146 + ]
+90
ocaml-immich/bin/cmd_server.ml
··· 1 + (*--------------------------------------------------------------------------- 2 + Copyright (c) 2025 Anil Madhavapeddy. All rights reserved. 3 + SPDX-License-Identifier: ISC 4 + ---------------------------------------------------------------------------*) 5 + 6 + open Cmdliner 7 + 8 + (* Ping command - can work with or without auth *) 9 + 10 + let ping_action ~requests_config ~server ~profile env = 11 + Eio.Switch.run @@ fun sw -> 12 + let fs = env#fs in 13 + let server_url = 14 + match server with 15 + | Some url -> url 16 + | None -> 17 + (* Try to get from session *) 18 + let profile = match profile with 19 + | Some p -> Some p 20 + | None -> Some (Immich_auth.Session.get_current_profile fs) 21 + in 22 + match Immich_auth.Session.load fs ?profile () with 23 + | Some session -> Immich_auth.Session.server_url session 24 + | None -> 25 + Fmt.epr "No server specified and not logged in.@."; 26 + Fmt.epr "Use --server or login first.@."; 27 + exit 1 28 + in 29 + (* Create session using requests config *) 30 + let session = Requests.Cmd.create requests_config env sw in 31 + (* Resolve the API URL from .well-known/immich if available *) 32 + let server_url = Immich_auth.Client.resolve_api_url ~session server_url in 33 + let client = Immich.create ~session ~sw env ~base_url:server_url in 34 + let resp = Immich.ServerPing.ping_server client () in 35 + Fmt.pr "%s@." (Immich.ServerPing.Response.res resp) 36 + 37 + let ping_cmd env fs = 38 + let doc = "Ping the Immich server." in 39 + let info = Cmd.info "ping" ~doc in 40 + let ping' (style_renderer, level) requests_config server profile = 41 + Immich_auth.Cmd.setup_logging_with_config style_renderer level requests_config; 42 + ping_action ~requests_config ~server ~profile env 43 + in 44 + Cmd.v info Term.(const ping' $ Immich_auth.Cmd.setup_logging $ Immich_auth.Cmd.requests_config_term fs $ Immich_auth.Cmd.server_opt $ Immich_auth.Cmd.profile_arg) 45 + 46 + (* Status command - requires auth *) 47 + 48 + let status_action ~requests_config ~profile env = 49 + Immich_auth.Cmd.with_client ~requests_config ?profile (fun _fs client -> 50 + let api = Immich_auth.Client.client client in 51 + (* Get server version *) 52 + let version = Immich.ServerVersion.get_server_version api () in 53 + Fmt.pr "Server version: %d.%d.%d@." 54 + (Immich.ServerVersion.ResponseDto.major version) 55 + (Immich.ServerVersion.ResponseDto.minor version) 56 + (Immich.ServerVersion.ResponseDto.patch version); 57 + (* Get server about *) 58 + let about = Immich.ServerAbout.get_about_info api () in 59 + let version = Immich.ServerAbout.ResponseDto.version about in 60 + if version <> "" then Fmt.pr "Full version: %s@." version; 61 + (match Immich.ServerAbout.ResponseDto.build about with 62 + | Some b -> Fmt.pr "Build: %s@." b 63 + | None -> ()); 64 + (* Get storage info *) 65 + let storage = Immich.ServerStorage.get_storage api () in 66 + Fmt.pr "Storage:@."; 67 + Fmt.pr " Disk size: %s@." (Immich.ServerStorage.ResponseDto.disk_size storage); 68 + Fmt.pr " Disk used: %s@." (Immich.ServerStorage.ResponseDto.disk_use storage); 69 + Fmt.pr " Disk available: %s@." (Immich.ServerStorage.ResponseDto.disk_available storage); 70 + Fmt.pr " Usage: %.1f%%@." (Immich.ServerStorage.ResponseDto.disk_usage_percentage storage) 71 + ) env 72 + 73 + let status_cmd env fs = 74 + let doc = "Show server status." in 75 + let info = Cmd.info "status" ~doc in 76 + let status' (style_renderer, level) requests_config profile = 77 + Immich_auth.Cmd.setup_logging_with_config style_renderer level requests_config; 78 + status_action ~requests_config ~profile env 79 + in 80 + Cmd.v info Term.(const status' $ Immich_auth.Cmd.setup_logging $ Immich_auth.Cmd.requests_config_term fs $ Immich_auth.Cmd.profile_arg) 81 + 82 + (* Server command group *) 83 + 84 + let server_cmd env fs = 85 + let doc = "Server information commands." in 86 + let info = Cmd.info "server" ~doc in 87 + Cmd.group info 88 + [ ping_cmd env fs 89 + ; status_cmd env fs 90 + ]
+13
ocaml-immich/bin/dune
··· 1 + (executable 2 + (name main) 3 + (libraries 4 + immich 5 + immich_auth 6 + openapi 7 + requests 8 + jsont 9 + uri 10 + eio 11 + eio_main 12 + fmt 13 + cmdliner))
+37
ocaml-immich/bin/main.ml
··· 1 + (*--------------------------------------------------------------------------- 2 + Copyright (c) 2025 Anil Madhavapeddy. All rights reserved. 3 + SPDX-License-Identifier: ISC 4 + ---------------------------------------------------------------------------*) 5 + 6 + open Cmdliner 7 + 8 + let version = "0.1.0" 9 + 10 + let () = 11 + let exit_code = 12 + try 13 + Eio_main.run @@ fun env -> 14 + let fs = env#fs in 15 + let doc = "Immich CLI - A command-line interface for Immich photo server" in 16 + let man = [ 17 + `S Manpage.s_description; 18 + `P "A command-line interface for interacting with Immich photo servers."; 19 + `P "Use $(b,immich auth login) to authenticate with your server."; 20 + `S Manpage.s_commands; 21 + `S Manpage.s_bugs; 22 + `P "Report bugs at https://github.com/avsm/ocaml-immich/issues"; 23 + ] in 24 + let info = Cmd.info "immich" ~version ~doc ~man in 25 + let cmds = 26 + [ Immich_auth.Cmd.auth_cmd env fs 27 + ; Cmd_server.server_cmd env fs 28 + ; Cmd_albums.albums_cmd env fs 29 + ; Cmd_faces.faces_cmd env fs 30 + ] 31 + in 32 + Cmd.eval (Cmd.group info cmds) 33 + with exn -> 34 + Fmt.epr "Eio_main.run raised: %s@." (Printexc.to_string exn); 35 + 125 36 + in 37 + exit exit_code
+285 -200
ocaml-immich/immich.ml
··· 44 44 module WorkflowFilter = struct 45 45 module ResponseDto = struct 46 46 type t = { 47 - filter_config : Jsont.json; 47 + filter_config : Jsont.json option; 48 48 id : string; 49 49 order : float; 50 50 plugin_filter_id : string; 51 51 workflow_id : string; 52 52 } 53 53 54 - let v ~filter_config ~id ~order ~plugin_filter_id ~workflow_id () = { filter_config; id; order; plugin_filter_id; workflow_id } 54 + let v ~id ~order ~plugin_filter_id ~workflow_id ?filter_config () = { filter_config; id; order; plugin_filter_id; workflow_id } 55 55 56 56 let filter_config t = t.filter_config 57 57 let id t = t.id ··· 62 62 let jsont : t Jsont.t = 63 63 Jsont.Object.map ~kind:"WorkflowFilterResponseDto" 64 64 (fun filter_config id order plugin_filter_id workflow_id -> { filter_config; id; order; plugin_filter_id; workflow_id }) 65 - |> Jsont.Object.mem "filterConfig" Jsont.json ~enc:(fun r -> r.filter_config) 65 + |> Jsont.Object.mem "filterConfig" (Jsont.option Jsont.json) 66 + ~dec_absent:None ~enc_omit:Option.is_none ~enc:(fun r -> r.filter_config) 66 67 |> Jsont.Object.mem "id" Jsont.string ~enc:(fun r -> r.id) 67 68 |> Jsont.Object.mem "order" Jsont.number ~enc:(fun r -> r.order) 68 69 |> Jsont.Object.mem "pluginFilterId" Jsont.string ~enc:(fun r -> r.plugin_filter_id) ··· 97 98 module WorkflowAction = struct 98 99 module ResponseDto = struct 99 100 type t = { 100 - action_config : Jsont.json; 101 + action_config : Jsont.json option; 101 102 id : string; 102 103 order : float; 103 104 plugin_action_id : string; 104 105 workflow_id : string; 105 106 } 106 107 107 - let v ~action_config ~id ~order ~plugin_action_id ~workflow_id () = { action_config; id; order; plugin_action_id; workflow_id } 108 + let v ~id ~order ~plugin_action_id ~workflow_id ?action_config () = { action_config; id; order; plugin_action_id; workflow_id } 108 109 109 110 let action_config t = t.action_config 110 111 let id t = t.id ··· 115 116 let jsont : t Jsont.t = 116 117 Jsont.Object.map ~kind:"WorkflowActionResponseDto" 117 118 (fun action_config id order plugin_action_id workflow_id -> { action_config; id; order; plugin_action_id; workflow_id }) 118 - |> Jsont.Object.mem "actionConfig" Jsont.json ~enc:(fun r -> r.action_config) 119 + |> Jsont.Object.mem "actionConfig" (Jsont.option Jsont.json) 120 + ~dec_absent:None ~enc_omit:Option.is_none ~enc:(fun r -> r.action_config) 119 121 |> Jsont.Object.mem "id" Jsont.string ~enc:(fun r -> r.id) 120 122 |> Jsont.Object.mem "order" Jsont.number ~enc:(fun r -> r.order) 121 123 |> Jsont.Object.mem "pluginActionId" Jsont.string ~enc:(fun r -> r.plugin_action_id) ··· 166 168 enabled : bool; 167 169 filters : WorkflowFilter.ResponseDto.t list; 168 170 id : string; 169 - name : string; 171 + name : string option; 170 172 owner_id : string; 171 173 trigger_type : Jsont.json; 172 174 } 173 175 174 - let v ~actions ~created_at ~description ~enabled ~filters ~id ~name ~owner_id ~trigger_type () = { actions; created_at; description; enabled; filters; id; name; owner_id; trigger_type } 176 + let v ~actions ~created_at ~description ~enabled ~filters ~id ~owner_id ~trigger_type ?name () = { actions; created_at; description; enabled; filters; id; name; owner_id; trigger_type } 175 177 176 178 let actions t = t.actions 177 179 let created_at t = t.created_at ··· 192 194 |> Jsont.Object.mem "enabled" Jsont.bool ~enc:(fun r -> r.enabled) 193 195 |> Jsont.Object.mem "filters" (Jsont.list WorkflowFilter.ResponseDto.jsont) ~enc:(fun r -> r.filters) 194 196 |> Jsont.Object.mem "id" Jsont.string ~enc:(fun r -> r.id) 195 - |> Jsont.Object.mem "name" Jsont.string ~enc:(fun r -> r.name) 197 + |> Jsont.Object.mem "name" (Jsont.option Jsont.string) 198 + ~dec_absent:None ~enc_omit:Option.is_none ~enc:(fun r -> r.name) 196 199 |> Jsont.Object.mem "ownerId" Jsont.string ~enc:(fun r -> r.owner_id) 197 200 |> Jsont.Object.mem "triggerType" Jsont.json ~enc:(fun r -> r.trigger_type) 198 201 |> Jsont.Object.skip_unknown ··· 385 388 module VersionCheckState = struct 386 389 module ResponseDto = struct 387 390 type t = { 388 - checked_at : string; 389 - release_version : string; 391 + checked_at : string option; 392 + release_version : string option; 390 393 } 391 394 392 - let v ~checked_at ~release_version () = { checked_at; release_version } 395 + let v ?checked_at ?release_version () = { checked_at; release_version } 393 396 394 397 let checked_at t = t.checked_at 395 398 let release_version t = t.release_version ··· 397 400 let jsont : t Jsont.t = 398 401 Jsont.Object.map ~kind:"VersionCheckStateResponseDto" 399 402 (fun checked_at release_version -> { checked_at; release_version }) 400 - |> Jsont.Object.mem "checkedAt" Jsont.string ~enc:(fun r -> r.checked_at) 401 - |> Jsont.Object.mem "releaseVersion" Jsont.string ~enc:(fun r -> r.release_version) 403 + |> Jsont.Object.mem "checkedAt" (Jsont.option Jsont.string) 404 + ~dec_absent:None ~enc_omit:Option.is_none ~enc:(fun r -> r.checked_at) 405 + |> Jsont.Object.mem "releaseVersion" (Jsont.option Jsont.string) 406 + ~dec_absent:None ~enc_omit:Option.is_none ~enc:(fun r -> r.release_version) 402 407 |> Jsont.Object.skip_unknown 403 408 |> Jsont.Object.finish 404 409 end ··· 901 906 module Activity = struct 902 907 module ResponseDto = struct 903 908 type t = { 904 - asset_id : string; 909 + asset_id : string option; 905 910 comment : string option; 906 911 created_at : Ptime.t; 907 912 id : string; ··· 909 914 user : User.ResponseDto.t; 910 915 } 911 916 912 - let v ~asset_id ~created_at ~id ~type_ ~user ?comment () = { asset_id; comment; created_at; id; type_; user } 917 + let v ~created_at ~id ~type_ ~user ?asset_id ?comment () = { asset_id; comment; created_at; id; type_; user } 913 918 914 919 let asset_id t = t.asset_id 915 920 let comment t = t.comment ··· 921 926 let jsont : t Jsont.t = 922 927 Jsont.Object.map ~kind:"ActivityResponseDto" 923 928 (fun asset_id comment created_at id type_ user -> { asset_id; comment; created_at; id; type_; user }) 924 - |> Jsont.Object.mem "assetId" Jsont.string ~enc:(fun r -> r.asset_id) 929 + |> Jsont.Object.mem "assetId" (Jsont.option Jsont.string) 930 + ~dec_absent:None ~enc_omit:Option.is_none ~enc:(fun r -> r.asset_id) 925 931 |> Jsont.Object.opt_mem "comment" Jsont.string ~enc:(fun r -> r.comment) 926 932 |> Jsont.Object.mem "createdAt" Openapi.Runtime.ptime_jsont ~enc:(fun r -> r.created_at) 927 933 |> Jsont.Object.mem "id" Jsont.string ~enc:(fun r -> r.id) ··· 1012 1018 module Dto = struct 1013 1019 type t = { 1014 1020 photos : int; 1015 - quota_size_in_bytes : int64; 1021 + quota_size_in_bytes : int64 option; 1016 1022 usage : int64; 1017 1023 usage_photos : int64; 1018 1024 usage_videos : int64; ··· 1021 1027 videos : int; 1022 1028 } 1023 1029 1024 - let v ~photos ~quota_size_in_bytes ~usage ~usage_photos ~usage_videos ~user_id ~user_name ~videos () = { photos; quota_size_in_bytes; usage; usage_photos; usage_videos; user_id; user_name; videos } 1030 + let v ~photos ~usage ~usage_photos ~usage_videos ~user_id ~user_name ~videos ?quota_size_in_bytes () = { photos; quota_size_in_bytes; usage; usage_photos; usage_videos; user_id; user_name; videos } 1025 1031 1026 1032 let photos t = t.photos 1027 1033 let quota_size_in_bytes t = t.quota_size_in_bytes ··· 1036 1042 Jsont.Object.map ~kind:"UsageByUserDto" 1037 1043 (fun photos quota_size_in_bytes usage usage_photos usage_videos user_id user_name videos -> { photos; quota_size_in_bytes; usage; usage_photos; usage_videos; user_id; user_name; videos }) 1038 1044 |> Jsont.Object.mem "photos" Jsont.int ~enc:(fun r -> r.photos) 1039 - |> Jsont.Object.mem "quotaSizeInBytes" Jsont.int64 ~enc:(fun r -> r.quota_size_in_bytes) 1045 + |> Jsont.Object.mem "quotaSizeInBytes" (Jsont.option Jsont.int64) 1046 + ~dec_absent:None ~enc_omit:Option.is_none ~enc:(fun r -> r.quota_size_in_bytes) 1040 1047 |> Jsont.Object.mem "usage" Jsont.int64 ~enc:(fun r -> r.usage) 1041 1048 |> Jsont.Object.mem "usagePhotos" Jsont.int64 ~enc:(fun r -> r.usage_photos) 1042 1049 |> Jsont.Object.mem "usageVideos" Jsont.int64 ~enc:(fun r -> r.usage_videos) ··· 2142 2149 button_text : string; 2143 2150 client_id : string; 2144 2151 client_secret : string; 2145 - default_storage_quota : int64; 2152 + default_storage_quota : int64 option; 2146 2153 enabled : bool; 2147 2154 issuer_url : string; 2148 2155 mobile_override_enabled : bool; ··· 2157 2164 token_endpoint_auth_method : Jsont.json; 2158 2165 } 2159 2166 2160 - let v ~auto_launch ~auto_register ~button_text ~client_id ~client_secret ~default_storage_quota ~enabled ~issuer_url ~mobile_override_enabled ~mobile_redirect_uri ~profile_signing_algorithm ~role_claim ~scope ~signing_algorithm ~storage_label_claim ~storage_quota_claim ~timeout ~token_endpoint_auth_method () = { auto_launch; auto_register; button_text; client_id; client_secret; default_storage_quota; enabled; issuer_url; mobile_override_enabled; mobile_redirect_uri; profile_signing_algorithm; role_claim; scope; signing_algorithm; storage_label_claim; storage_quota_claim; timeout; token_endpoint_auth_method } 2167 + let v ~auto_launch ~auto_register ~button_text ~client_id ~client_secret ~enabled ~issuer_url ~mobile_override_enabled ~mobile_redirect_uri ~profile_signing_algorithm ~role_claim ~scope ~signing_algorithm ~storage_label_claim ~storage_quota_claim ~timeout ~token_endpoint_auth_method ?default_storage_quota () = { auto_launch; auto_register; button_text; client_id; client_secret; default_storage_quota; enabled; issuer_url; mobile_override_enabled; mobile_redirect_uri; profile_signing_algorithm; role_claim; scope; signing_algorithm; storage_label_claim; storage_quota_claim; timeout; token_endpoint_auth_method } 2161 2168 2162 2169 let auto_launch t = t.auto_launch 2163 2170 let auto_register t = t.auto_register ··· 2186 2193 |> Jsont.Object.mem "buttonText" Jsont.string ~enc:(fun r -> r.button_text) 2187 2194 |> Jsont.Object.mem "clientId" Jsont.string ~enc:(fun r -> r.client_id) 2188 2195 |> Jsont.Object.mem "clientSecret" Jsont.string ~enc:(fun r -> r.client_secret) 2189 - |> Jsont.Object.mem "defaultStorageQuota" Jsont.int64 ~enc:(fun r -> r.default_storage_quota) 2196 + |> Jsont.Object.mem "defaultStorageQuota" (Jsont.option Jsont.int64) 2197 + ~dec_absent:None ~enc_omit:Option.is_none ~enc:(fun r -> r.default_storage_quota) 2190 2198 |> Jsont.Object.mem "enabled" Jsont.bool ~enc:(fun r -> r.enabled) 2191 2199 |> Jsont.Object.mem "issuerUrl" Jsont.string ~enc:(fun r -> r.issuer_url) 2192 2200 |> Jsont.Object.mem "mobileOverrideEnabled" Jsont.bool ~enc:(fun r -> r.mobile_override_enabled) ··· 2496 2504 module T = struct 2497 2505 type t = { 2498 2506 avatar_color : Jsont.json option; 2499 - deleted_at : Ptime.t; 2507 + deleted_at : Ptime.t option; 2500 2508 email : string; 2501 2509 has_profile_image : bool; 2502 2510 id : string; ··· 2504 2512 profile_changed_at : Ptime.t; 2505 2513 } 2506 2514 2507 - let v ~deleted_at ~email ~has_profile_image ~id ~name ~profile_changed_at ?avatar_color () = { avatar_color; deleted_at; email; has_profile_image; id; name; profile_changed_at } 2515 + let v ~email ~has_profile_image ~id ~name ~profile_changed_at ?avatar_color ?deleted_at () = { avatar_color; deleted_at; email; has_profile_image; id; name; profile_changed_at } 2508 2516 2509 2517 let avatar_color t = t.avatar_color 2510 2518 let deleted_at t = t.deleted_at ··· 2519 2527 (fun avatar_color deleted_at email has_profile_image id name profile_changed_at -> { avatar_color; deleted_at; email; has_profile_image; id; name; profile_changed_at }) 2520 2528 |> Jsont.Object.mem "avatarColor" (Jsont.option Jsont.json) 2521 2529 ~dec_absent:None ~enc_omit:Option.is_none ~enc:(fun r -> r.avatar_color) 2522 - |> Jsont.Object.mem "deletedAt" Openapi.Runtime.ptime_jsont ~enc:(fun r -> r.deleted_at) 2530 + |> Jsont.Object.mem "deletedAt" (Jsont.option Openapi.Runtime.ptime_jsont) 2531 + ~dec_absent:None ~enc_omit:Option.is_none ~enc:(fun r -> r.deleted_at) 2523 2532 |> Jsont.Object.mem "email" Jsont.string ~enc:(fun r -> r.email) 2524 2533 |> Jsont.Object.mem "hasProfileImage" Jsont.bool ~enc:(fun r -> r.has_profile_image) 2525 2534 |> Jsont.Object.mem "id" Jsont.string ~enc:(fun r -> r.id) ··· 2754 2763 module SyncPersonV1 = struct 2755 2764 module T = struct 2756 2765 type t = { 2757 - birth_date : Ptime.t; 2758 - color : string; 2766 + birth_date : Ptime.t option; 2767 + color : string option; 2759 2768 created_at : Ptime.t; 2760 - face_asset_id : string; 2769 + face_asset_id : string option; 2761 2770 id : string; 2762 2771 is_favorite : bool; 2763 2772 is_hidden : bool; ··· 2766 2775 updated_at : Ptime.t; 2767 2776 } 2768 2777 2769 - let v ~birth_date ~color ~created_at ~face_asset_id ~id ~is_favorite ~is_hidden ~name ~owner_id ~updated_at () = { birth_date; color; created_at; face_asset_id; id; is_favorite; is_hidden; name; owner_id; updated_at } 2778 + let v ~created_at ~id ~is_favorite ~is_hidden ~name ~owner_id ~updated_at ?birth_date ?color ?face_asset_id () = { birth_date; color; created_at; face_asset_id; id; is_favorite; is_hidden; name; owner_id; updated_at } 2770 2779 2771 2780 let birth_date t = t.birth_date 2772 2781 let color t = t.color ··· 2782 2791 let jsont : t Jsont.t = 2783 2792 Jsont.Object.map ~kind:"SyncPersonV1" 2784 2793 (fun birth_date color created_at face_asset_id id is_favorite is_hidden name owner_id updated_at -> { birth_date; color; created_at; face_asset_id; id; is_favorite; is_hidden; name; owner_id; updated_at }) 2785 - |> Jsont.Object.mem "birthDate" Openapi.Runtime.ptime_jsont ~enc:(fun r -> r.birth_date) 2786 - |> Jsont.Object.mem "color" Jsont.string ~enc:(fun r -> r.color) 2794 + |> Jsont.Object.mem "birthDate" (Jsont.option Openapi.Runtime.ptime_jsont) 2795 + ~dec_absent:None ~enc_omit:Option.is_none ~enc:(fun r -> r.birth_date) 2796 + |> Jsont.Object.mem "color" (Jsont.option Jsont.string) 2797 + ~dec_absent:None ~enc_omit:Option.is_none ~enc:(fun r -> r.color) 2787 2798 |> Jsont.Object.mem "createdAt" Openapi.Runtime.ptime_jsont ~enc:(fun r -> r.created_at) 2788 - |> Jsont.Object.mem "faceAssetId" Jsont.string ~enc:(fun r -> r.face_asset_id) 2799 + |> Jsont.Object.mem "faceAssetId" (Jsont.option Jsont.string) 2800 + ~dec_absent:None ~enc_omit:Option.is_none ~enc:(fun r -> r.face_asset_id) 2789 2801 |> Jsont.Object.mem "id" Jsont.string ~enc:(fun r -> r.id) 2790 2802 |> Jsont.Object.mem "isFavorite" Jsont.bool ~enc:(fun r -> r.is_favorite) 2791 2803 |> Jsont.Object.mem "isHidden" Jsont.bool ~enc:(fun r -> r.is_hidden) ··· 2868 2880 type t = { 2869 2881 created_at : Ptime.t; 2870 2882 data : Jsont.json; 2871 - deleted_at : Ptime.t; 2872 - hide_at : Ptime.t; 2883 + deleted_at : Ptime.t option; 2884 + hide_at : Ptime.t option; 2873 2885 id : string; 2874 2886 is_saved : bool; 2875 2887 memory_at : Ptime.t; 2876 2888 owner_id : string; 2877 - seen_at : Ptime.t; 2878 - show_at : Ptime.t; 2889 + seen_at : Ptime.t option; 2890 + show_at : Ptime.t option; 2879 2891 type_ : Jsont.json; 2880 2892 updated_at : Ptime.t; 2881 2893 } 2882 2894 2883 - let v ~created_at ~data ~deleted_at ~hide_at ~id ~is_saved ~memory_at ~owner_id ~seen_at ~show_at ~type_ ~updated_at () = { created_at; data; deleted_at; hide_at; id; is_saved; memory_at; owner_id; seen_at; show_at; type_; updated_at } 2895 + let v ~created_at ~data ~id ~is_saved ~memory_at ~owner_id ~type_ ~updated_at ?deleted_at ?hide_at ?seen_at ?show_at () = { created_at; data; deleted_at; hide_at; id; is_saved; memory_at; owner_id; seen_at; show_at; type_; updated_at } 2884 2896 2885 2897 let created_at t = t.created_at 2886 2898 let data t = t.data ··· 2900 2912 (fun created_at data deleted_at hide_at id is_saved memory_at owner_id seen_at show_at type_ updated_at -> { created_at; data; deleted_at; hide_at; id; is_saved; memory_at; owner_id; seen_at; show_at; type_; updated_at }) 2901 2913 |> Jsont.Object.mem "createdAt" Openapi.Runtime.ptime_jsont ~enc:(fun r -> r.created_at) 2902 2914 |> Jsont.Object.mem "data" Jsont.json ~enc:(fun r -> r.data) 2903 - |> Jsont.Object.mem "deletedAt" Openapi.Runtime.ptime_jsont ~enc:(fun r -> r.deleted_at) 2904 - |> Jsont.Object.mem "hideAt" Openapi.Runtime.ptime_jsont ~enc:(fun r -> r.hide_at) 2915 + |> Jsont.Object.mem "deletedAt" (Jsont.option Openapi.Runtime.ptime_jsont) 2916 + ~dec_absent:None ~enc_omit:Option.is_none ~enc:(fun r -> r.deleted_at) 2917 + |> Jsont.Object.mem "hideAt" (Jsont.option Openapi.Runtime.ptime_jsont) 2918 + ~dec_absent:None ~enc_omit:Option.is_none ~enc:(fun r -> r.hide_at) 2905 2919 |> Jsont.Object.mem "id" Jsont.string ~enc:(fun r -> r.id) 2906 2920 |> Jsont.Object.mem "isSaved" Jsont.bool ~enc:(fun r -> r.is_saved) 2907 2921 |> Jsont.Object.mem "memoryAt" Openapi.Runtime.ptime_jsont ~enc:(fun r -> r.memory_at) 2908 2922 |> Jsont.Object.mem "ownerId" Jsont.string ~enc:(fun r -> r.owner_id) 2909 - |> Jsont.Object.mem "seenAt" Openapi.Runtime.ptime_jsont ~enc:(fun r -> r.seen_at) 2910 - |> Jsont.Object.mem "showAt" Openapi.Runtime.ptime_jsont ~enc:(fun r -> r.show_at) 2923 + |> Jsont.Object.mem "seenAt" (Jsont.option Openapi.Runtime.ptime_jsont) 2924 + ~dec_absent:None ~enc_omit:Option.is_none ~enc:(fun r -> r.seen_at) 2925 + |> Jsont.Object.mem "showAt" (Jsont.option Openapi.Runtime.ptime_jsont) 2926 + ~dec_absent:None ~enc_omit:Option.is_none ~enc:(fun r -> r.show_at) 2911 2927 |> Jsont.Object.mem "type" Jsont.json ~enc:(fun r -> r.type_) 2912 2928 |> Jsont.Object.mem "updatedAt" Openapi.Runtime.ptime_jsont ~enc:(fun r -> r.updated_at) 2913 2929 |> Jsont.Object.skip_unknown ··· 3165 3181 module T = struct 3166 3182 type t = { 3167 3183 avatar_color : Jsont.json option; 3168 - deleted_at : Ptime.t; 3184 + deleted_at : Ptime.t option; 3169 3185 email : string; 3170 3186 has_profile_image : bool; 3171 3187 id : string; 3172 3188 is_admin : bool; 3173 3189 name : string; 3174 3190 oauth_id : string; 3175 - pin_code : string; 3191 + pin_code : string option; 3176 3192 profile_changed_at : Ptime.t; 3177 - quota_size_in_bytes : int; 3193 + quota_size_in_bytes : int option; 3178 3194 quota_usage_in_bytes : int; 3179 - storage_label : string; 3195 + storage_label : string option; 3180 3196 } 3181 3197 3182 - let v ~deleted_at ~email ~has_profile_image ~id ~is_admin ~name ~oauth_id ~pin_code ~profile_changed_at ~quota_size_in_bytes ~quota_usage_in_bytes ~storage_label ?avatar_color () = { avatar_color; deleted_at; email; has_profile_image; id; is_admin; name; oauth_id; pin_code; profile_changed_at; quota_size_in_bytes; quota_usage_in_bytes; storage_label } 3198 + let v ~email ~has_profile_image ~id ~is_admin ~name ~oauth_id ~profile_changed_at ~quota_usage_in_bytes ?avatar_color ?deleted_at ?pin_code ?quota_size_in_bytes ?storage_label () = { avatar_color; deleted_at; email; has_profile_image; id; is_admin; name; oauth_id; pin_code; profile_changed_at; quota_size_in_bytes; quota_usage_in_bytes; storage_label } 3183 3199 3184 3200 let avatar_color t = t.avatar_color 3185 3201 let deleted_at t = t.deleted_at ··· 3200 3216 (fun avatar_color deleted_at email has_profile_image id is_admin name oauth_id pin_code profile_changed_at quota_size_in_bytes quota_usage_in_bytes storage_label -> { avatar_color; deleted_at; email; has_profile_image; id; is_admin; name; oauth_id; pin_code; profile_changed_at; quota_size_in_bytes; quota_usage_in_bytes; storage_label }) 3201 3217 |> Jsont.Object.mem "avatarColor" (Jsont.option Jsont.json) 3202 3218 ~dec_absent:None ~enc_omit:Option.is_none ~enc:(fun r -> r.avatar_color) 3203 - |> Jsont.Object.mem "deletedAt" Openapi.Runtime.ptime_jsont ~enc:(fun r -> r.deleted_at) 3219 + |> Jsont.Object.mem "deletedAt" (Jsont.option Openapi.Runtime.ptime_jsont) 3220 + ~dec_absent:None ~enc_omit:Option.is_none ~enc:(fun r -> r.deleted_at) 3204 3221 |> Jsont.Object.mem "email" Jsont.string ~enc:(fun r -> r.email) 3205 3222 |> Jsont.Object.mem "hasProfileImage" Jsont.bool ~enc:(fun r -> r.has_profile_image) 3206 3223 |> Jsont.Object.mem "id" Jsont.string ~enc:(fun r -> r.id) 3207 3224 |> Jsont.Object.mem "isAdmin" Jsont.bool ~enc:(fun r -> r.is_admin) 3208 3225 |> Jsont.Object.mem "name" Jsont.string ~enc:(fun r -> r.name) 3209 3226 |> Jsont.Object.mem "oauthId" Jsont.string ~enc:(fun r -> r.oauth_id) 3210 - |> Jsont.Object.mem "pinCode" Jsont.string ~enc:(fun r -> r.pin_code) 3227 + |> Jsont.Object.mem "pinCode" (Jsont.option Jsont.string) 3228 + ~dec_absent:None ~enc_omit:Option.is_none ~enc:(fun r -> r.pin_code) 3211 3229 |> Jsont.Object.mem "profileChangedAt" Openapi.Runtime.ptime_jsont ~enc:(fun r -> r.profile_changed_at) 3212 - |> Jsont.Object.mem "quotaSizeInBytes" Jsont.int ~enc:(fun r -> r.quota_size_in_bytes) 3230 + |> Jsont.Object.mem "quotaSizeInBytes" (Jsont.option Jsont.int) 3231 + ~dec_absent:None ~enc_omit:Option.is_none ~enc:(fun r -> r.quota_size_in_bytes) 3213 3232 |> Jsont.Object.mem "quotaUsageInBytes" Jsont.int ~enc:(fun r -> r.quota_usage_in_bytes) 3214 - |> Jsont.Object.mem "storageLabel" Jsont.string ~enc:(fun r -> r.storage_label) 3233 + |> Jsont.Object.mem "storageLabel" (Jsont.option Jsont.string) 3234 + ~dec_absent:None ~enc_omit:Option.is_none ~enc:(fun r -> r.storage_label) 3215 3235 |> Jsont.Object.skip_unknown 3216 3236 |> Jsont.Object.finish 3217 3237 end ··· 3221 3241 module T = struct 3222 3242 type t = { 3223 3243 checksum : string; 3224 - deleted_at : Ptime.t; 3225 - duration : string; 3226 - file_created_at : Ptime.t; 3227 - file_modified_at : Ptime.t; 3228 - height : int; 3244 + deleted_at : Ptime.t option; 3245 + duration : string option; 3246 + file_created_at : Ptime.t option; 3247 + file_modified_at : Ptime.t option; 3248 + height : int option; 3229 3249 id : string; 3230 3250 is_edited : bool; 3231 3251 is_favorite : bool; 3232 - library_id : string; 3233 - live_photo_video_id : string; 3234 - local_date_time : Ptime.t; 3252 + library_id : string option; 3253 + live_photo_video_id : string option; 3254 + local_date_time : Ptime.t option; 3235 3255 original_file_name : string; 3236 3256 owner_id : string; 3237 - stack_id : string; 3238 - thumbhash : string; 3257 + stack_id : string option; 3258 + thumbhash : string option; 3239 3259 type_ : Jsont.json; 3240 3260 visibility : Jsont.json; 3241 - width : int; 3261 + width : int option; 3242 3262 } 3243 3263 3244 - let v ~checksum ~deleted_at ~duration ~file_created_at ~file_modified_at ~height ~id ~is_edited ~is_favorite ~library_id ~live_photo_video_id ~local_date_time ~original_file_name ~owner_id ~stack_id ~thumbhash ~type_ ~visibility ~width () = { checksum; deleted_at; duration; file_created_at; file_modified_at; height; id; is_edited; is_favorite; library_id; live_photo_video_id; local_date_time; original_file_name; owner_id; stack_id; thumbhash; type_; visibility; width } 3264 + let v ~checksum ~id ~is_edited ~is_favorite ~original_file_name ~owner_id ~type_ ~visibility ?deleted_at ?duration ?file_created_at ?file_modified_at ?height ?library_id ?live_photo_video_id ?local_date_time ?stack_id ?thumbhash ?width () = { checksum; deleted_at; duration; file_created_at; file_modified_at; height; id; is_edited; is_favorite; library_id; live_photo_video_id; local_date_time; original_file_name; owner_id; stack_id; thumbhash; type_; visibility; width } 3245 3265 3246 3266 let checksum t = t.checksum 3247 3267 let deleted_at t = t.deleted_at ··· 3267 3287 Jsont.Object.map ~kind:"SyncAssetV1" 3268 3288 (fun checksum deleted_at duration file_created_at file_modified_at height id is_edited is_favorite library_id live_photo_video_id local_date_time original_file_name owner_id stack_id thumbhash type_ visibility width -> { checksum; deleted_at; duration; file_created_at; file_modified_at; height; id; is_edited; is_favorite; library_id; live_photo_video_id; local_date_time; original_file_name; owner_id; stack_id; thumbhash; type_; visibility; width }) 3269 3289 |> Jsont.Object.mem "checksum" Jsont.string ~enc:(fun r -> r.checksum) 3270 - |> Jsont.Object.mem "deletedAt" Openapi.Runtime.ptime_jsont ~enc:(fun r -> r.deleted_at) 3271 - |> Jsont.Object.mem "duration" Jsont.string ~enc:(fun r -> r.duration) 3272 - |> Jsont.Object.mem "fileCreatedAt" Openapi.Runtime.ptime_jsont ~enc:(fun r -> r.file_created_at) 3273 - |> Jsont.Object.mem "fileModifiedAt" Openapi.Runtime.ptime_jsont ~enc:(fun r -> r.file_modified_at) 3274 - |> Jsont.Object.mem "height" Jsont.int ~enc:(fun r -> r.height) 3290 + |> Jsont.Object.mem "deletedAt" (Jsont.option Openapi.Runtime.ptime_jsont) 3291 + ~dec_absent:None ~enc_omit:Option.is_none ~enc:(fun r -> r.deleted_at) 3292 + |> Jsont.Object.mem "duration" (Jsont.option Jsont.string) 3293 + ~dec_absent:None ~enc_omit:Option.is_none ~enc:(fun r -> r.duration) 3294 + |> Jsont.Object.mem "fileCreatedAt" (Jsont.option Openapi.Runtime.ptime_jsont) 3295 + ~dec_absent:None ~enc_omit:Option.is_none ~enc:(fun r -> r.file_created_at) 3296 + |> Jsont.Object.mem "fileModifiedAt" (Jsont.option Openapi.Runtime.ptime_jsont) 3297 + ~dec_absent:None ~enc_omit:Option.is_none ~enc:(fun r -> r.file_modified_at) 3298 + |> Jsont.Object.mem "height" (Jsont.option Jsont.int) 3299 + ~dec_absent:None ~enc_omit:Option.is_none ~enc:(fun r -> r.height) 3275 3300 |> Jsont.Object.mem "id" Jsont.string ~enc:(fun r -> r.id) 3276 3301 |> Jsont.Object.mem "isEdited" Jsont.bool ~enc:(fun r -> r.is_edited) 3277 3302 |> Jsont.Object.mem "isFavorite" Jsont.bool ~enc:(fun r -> r.is_favorite) 3278 - |> Jsont.Object.mem "libraryId" Jsont.string ~enc:(fun r -> r.library_id) 3279 - |> Jsont.Object.mem "livePhotoVideoId" Jsont.string ~enc:(fun r -> r.live_photo_video_id) 3280 - |> Jsont.Object.mem "localDateTime" Openapi.Runtime.ptime_jsont ~enc:(fun r -> r.local_date_time) 3303 + |> Jsont.Object.mem "libraryId" (Jsont.option Jsont.string) 3304 + ~dec_absent:None ~enc_omit:Option.is_none ~enc:(fun r -> r.library_id) 3305 + |> Jsont.Object.mem "livePhotoVideoId" (Jsont.option Jsont.string) 3306 + ~dec_absent:None ~enc_omit:Option.is_none ~enc:(fun r -> r.live_photo_video_id) 3307 + |> Jsont.Object.mem "localDateTime" (Jsont.option Openapi.Runtime.ptime_jsont) 3308 + ~dec_absent:None ~enc_omit:Option.is_none ~enc:(fun r -> r.local_date_time) 3281 3309 |> Jsont.Object.mem "originalFileName" Jsont.string ~enc:(fun r -> r.original_file_name) 3282 3310 |> Jsont.Object.mem "ownerId" Jsont.string ~enc:(fun r -> r.owner_id) 3283 - |> Jsont.Object.mem "stackId" Jsont.string ~enc:(fun r -> r.stack_id) 3284 - |> Jsont.Object.mem "thumbhash" Jsont.string ~enc:(fun r -> r.thumbhash) 3311 + |> Jsont.Object.mem "stackId" (Jsont.option Jsont.string) 3312 + ~dec_absent:None ~enc_omit:Option.is_none ~enc:(fun r -> r.stack_id) 3313 + |> Jsont.Object.mem "thumbhash" (Jsont.option Jsont.string) 3314 + ~dec_absent:None ~enc_omit:Option.is_none ~enc:(fun r -> r.thumbhash) 3285 3315 |> Jsont.Object.mem "type" Jsont.json ~enc:(fun r -> r.type_) 3286 3316 |> Jsont.Object.mem "visibility" Jsont.json ~enc:(fun r -> r.visibility) 3287 - |> Jsont.Object.mem "width" Jsont.int ~enc:(fun r -> r.width) 3317 + |> Jsont.Object.mem "width" (Jsont.option Jsont.int) 3318 + ~dec_absent:None ~enc_omit:Option.is_none ~enc:(fun r -> r.width) 3288 3319 |> Jsont.Object.skip_unknown 3289 3320 |> Jsont.Object.finish 3290 3321 end ··· 3348 3379 id : string; 3349 3380 image_height : int; 3350 3381 image_width : int; 3351 - person_id : string; 3382 + person_id : string option; 3352 3383 source_type : string; 3353 3384 } 3354 3385 3355 - let v ~asset_id ~bounding_box_x1 ~bounding_box_x2 ~bounding_box_y1 ~bounding_box_y2 ~id ~image_height ~image_width ~person_id ~source_type () = { asset_id; bounding_box_x1; bounding_box_x2; bounding_box_y1; bounding_box_y2; id; image_height; image_width; person_id; source_type } 3386 + let v ~asset_id ~bounding_box_x1 ~bounding_box_x2 ~bounding_box_y1 ~bounding_box_y2 ~id ~image_height ~image_width ~source_type ?person_id () = { asset_id; bounding_box_x1; bounding_box_x2; bounding_box_y1; bounding_box_y2; id; image_height; image_width; person_id; source_type } 3356 3387 3357 3388 let asset_id t = t.asset_id 3358 3389 let bounding_box_x1 t = t.bounding_box_x1 ··· 3376 3407 |> Jsont.Object.mem "id" Jsont.string ~enc:(fun r -> r.id) 3377 3408 |> Jsont.Object.mem "imageHeight" Jsont.int ~enc:(fun r -> r.image_height) 3378 3409 |> Jsont.Object.mem "imageWidth" Jsont.int ~enc:(fun r -> r.image_width) 3379 - |> Jsont.Object.mem "personId" Jsont.string ~enc:(fun r -> r.person_id) 3410 + |> Jsont.Object.mem "personId" (Jsont.option Jsont.string) 3411 + ~dec_absent:None ~enc_omit:Option.is_none ~enc:(fun r -> r.person_id) 3380 3412 |> Jsont.Object.mem "sourceType" Jsont.string ~enc:(fun r -> r.source_type) 3381 3413 |> Jsont.Object.skip_unknown 3382 3414 |> Jsont.Object.finish ··· 3406 3438 module T = struct 3407 3439 type t = { 3408 3440 asset_id : string; 3409 - city : string; 3410 - country : string; 3411 - date_time_original : Ptime.t; 3412 - description : string; 3413 - exif_image_height : int; 3414 - exif_image_width : int; 3415 - exposure_time : string; 3416 - f_number : float; 3417 - file_size_in_byte : int; 3418 - focal_length : float; 3419 - fps : float; 3420 - iso : int; 3421 - latitude : float; 3422 - lens_model : string; 3423 - longitude : float; 3424 - make : string; 3425 - model : string; 3426 - modify_date : Ptime.t; 3427 - orientation : string; 3428 - profile_description : string; 3429 - projection_type : string; 3430 - rating : int; 3431 - state : string; 3432 - time_zone : string; 3441 + city : string option; 3442 + country : string option; 3443 + date_time_original : Ptime.t option; 3444 + description : string option; 3445 + exif_image_height : int option; 3446 + exif_image_width : int option; 3447 + exposure_time : string option; 3448 + f_number : float option; 3449 + file_size_in_byte : int option; 3450 + focal_length : float option; 3451 + fps : float option; 3452 + iso : int option; 3453 + latitude : float option; 3454 + lens_model : string option; 3455 + longitude : float option; 3456 + make : string option; 3457 + model : string option; 3458 + modify_date : Ptime.t option; 3459 + orientation : string option; 3460 + profile_description : string option; 3461 + projection_type : string option; 3462 + rating : int option; 3463 + state : string option; 3464 + time_zone : string option; 3433 3465 } 3434 3466 3435 - let v ~asset_id ~city ~country ~date_time_original ~description ~exif_image_height ~exif_image_width ~exposure_time ~f_number ~file_size_in_byte ~focal_length ~fps ~iso ~latitude ~lens_model ~longitude ~make ~model ~modify_date ~orientation ~profile_description ~projection_type ~rating ~state ~time_zone () = { asset_id; city; country; date_time_original; description; exif_image_height; exif_image_width; exposure_time; f_number; file_size_in_byte; focal_length; fps; iso; latitude; lens_model; longitude; make; model; modify_date; orientation; profile_description; projection_type; rating; state; time_zone } 3467 + let v ~asset_id ?city ?country ?date_time_original ?description ?exif_image_height ?exif_image_width ?exposure_time ?f_number ?file_size_in_byte ?focal_length ?fps ?iso ?latitude ?lens_model ?longitude ?make ?model ?modify_date ?orientation ?profile_description ?projection_type ?rating ?state ?time_zone () = { asset_id; city; country; date_time_original; description; exif_image_height; exif_image_width; exposure_time; f_number; file_size_in_byte; focal_length; fps; iso; latitude; lens_model; longitude; make; model; modify_date; orientation; profile_description; projection_type; rating; state; time_zone } 3436 3468 3437 3469 let asset_id t = t.asset_id 3438 3470 let city t = t.city ··· 3464 3496 Jsont.Object.map ~kind:"SyncAssetExifV1" 3465 3497 (fun asset_id city country date_time_original description exif_image_height exif_image_width exposure_time f_number file_size_in_byte focal_length fps iso latitude lens_model longitude make model modify_date orientation profile_description projection_type rating state time_zone -> { asset_id; city; country; date_time_original; description; exif_image_height; exif_image_width; exposure_time; f_number; file_size_in_byte; focal_length; fps; iso; latitude; lens_model; longitude; make; model; modify_date; orientation; profile_description; projection_type; rating; state; time_zone }) 3466 3498 |> Jsont.Object.mem "assetId" Jsont.string ~enc:(fun r -> r.asset_id) 3467 - |> Jsont.Object.mem "city" Jsont.string ~enc:(fun r -> r.city) 3468 - |> Jsont.Object.mem "country" Jsont.string ~enc:(fun r -> r.country) 3469 - |> Jsont.Object.mem "dateTimeOriginal" Openapi.Runtime.ptime_jsont ~enc:(fun r -> r.date_time_original) 3470 - |> Jsont.Object.mem "description" Jsont.string ~enc:(fun r -> r.description) 3471 - |> Jsont.Object.mem "exifImageHeight" Jsont.int ~enc:(fun r -> r.exif_image_height) 3472 - |> Jsont.Object.mem "exifImageWidth" Jsont.int ~enc:(fun r -> r.exif_image_width) 3473 - |> Jsont.Object.mem "exposureTime" Jsont.string ~enc:(fun r -> r.exposure_time) 3474 - |> Jsont.Object.mem "fNumber" Jsont.number ~enc:(fun r -> r.f_number) 3475 - |> Jsont.Object.mem "fileSizeInByte" Jsont.int ~enc:(fun r -> r.file_size_in_byte) 3476 - |> Jsont.Object.mem "focalLength" Jsont.number ~enc:(fun r -> r.focal_length) 3477 - |> Jsont.Object.mem "fps" Jsont.number ~enc:(fun r -> r.fps) 3478 - |> Jsont.Object.mem "iso" Jsont.int ~enc:(fun r -> r.iso) 3479 - |> Jsont.Object.mem "latitude" Jsont.number ~enc:(fun r -> r.latitude) 3480 - |> Jsont.Object.mem "lensModel" Jsont.string ~enc:(fun r -> r.lens_model) 3481 - |> Jsont.Object.mem "longitude" Jsont.number ~enc:(fun r -> r.longitude) 3482 - |> Jsont.Object.mem "make" Jsont.string ~enc:(fun r -> r.make) 3483 - |> Jsont.Object.mem "model" Jsont.string ~enc:(fun r -> r.model) 3484 - |> Jsont.Object.mem "modifyDate" Openapi.Runtime.ptime_jsont ~enc:(fun r -> r.modify_date) 3485 - |> Jsont.Object.mem "orientation" Jsont.string ~enc:(fun r -> r.orientation) 3486 - |> Jsont.Object.mem "profileDescription" Jsont.string ~enc:(fun r -> r.profile_description) 3487 - |> Jsont.Object.mem "projectionType" Jsont.string ~enc:(fun r -> r.projection_type) 3488 - |> Jsont.Object.mem "rating" Jsont.int ~enc:(fun r -> r.rating) 3489 - |> Jsont.Object.mem "state" Jsont.string ~enc:(fun r -> r.state) 3490 - |> Jsont.Object.mem "timeZone" Jsont.string ~enc:(fun r -> r.time_zone) 3499 + |> Jsont.Object.mem "city" (Jsont.option Jsont.string) 3500 + ~dec_absent:None ~enc_omit:Option.is_none ~enc:(fun r -> r.city) 3501 + |> Jsont.Object.mem "country" (Jsont.option Jsont.string) 3502 + ~dec_absent:None ~enc_omit:Option.is_none ~enc:(fun r -> r.country) 3503 + |> Jsont.Object.mem "dateTimeOriginal" (Jsont.option Openapi.Runtime.ptime_jsont) 3504 + ~dec_absent:None ~enc_omit:Option.is_none ~enc:(fun r -> r.date_time_original) 3505 + |> Jsont.Object.mem "description" (Jsont.option Jsont.string) 3506 + ~dec_absent:None ~enc_omit:Option.is_none ~enc:(fun r -> r.description) 3507 + |> Jsont.Object.mem "exifImageHeight" (Jsont.option Jsont.int) 3508 + ~dec_absent:None ~enc_omit:Option.is_none ~enc:(fun r -> r.exif_image_height) 3509 + |> Jsont.Object.mem "exifImageWidth" (Jsont.option Jsont.int) 3510 + ~dec_absent:None ~enc_omit:Option.is_none ~enc:(fun r -> r.exif_image_width) 3511 + |> Jsont.Object.mem "exposureTime" (Jsont.option Jsont.string) 3512 + ~dec_absent:None ~enc_omit:Option.is_none ~enc:(fun r -> r.exposure_time) 3513 + |> Jsont.Object.mem "fNumber" (Jsont.option Jsont.number) 3514 + ~dec_absent:None ~enc_omit:Option.is_none ~enc:(fun r -> r.f_number) 3515 + |> Jsont.Object.mem "fileSizeInByte" (Jsont.option Jsont.int) 3516 + ~dec_absent:None ~enc_omit:Option.is_none ~enc:(fun r -> r.file_size_in_byte) 3517 + |> Jsont.Object.mem "focalLength" (Jsont.option Jsont.number) 3518 + ~dec_absent:None ~enc_omit:Option.is_none ~enc:(fun r -> r.focal_length) 3519 + |> Jsont.Object.mem "fps" (Jsont.option Jsont.number) 3520 + ~dec_absent:None ~enc_omit:Option.is_none ~enc:(fun r -> r.fps) 3521 + |> Jsont.Object.mem "iso" (Jsont.option Jsont.int) 3522 + ~dec_absent:None ~enc_omit:Option.is_none ~enc:(fun r -> r.iso) 3523 + |> Jsont.Object.mem "latitude" (Jsont.option Jsont.number) 3524 + ~dec_absent:None ~enc_omit:Option.is_none ~enc:(fun r -> r.latitude) 3525 + |> Jsont.Object.mem "lensModel" (Jsont.option Jsont.string) 3526 + ~dec_absent:None ~enc_omit:Option.is_none ~enc:(fun r -> r.lens_model) 3527 + |> Jsont.Object.mem "longitude" (Jsont.option Jsont.number) 3528 + ~dec_absent:None ~enc_omit:Option.is_none ~enc:(fun r -> r.longitude) 3529 + |> Jsont.Object.mem "make" (Jsont.option Jsont.string) 3530 + ~dec_absent:None ~enc_omit:Option.is_none ~enc:(fun r -> r.make) 3531 + |> Jsont.Object.mem "model" (Jsont.option Jsont.string) 3532 + ~dec_absent:None ~enc_omit:Option.is_none ~enc:(fun r -> r.model) 3533 + |> Jsont.Object.mem "modifyDate" (Jsont.option Openapi.Runtime.ptime_jsont) 3534 + ~dec_absent:None ~enc_omit:Option.is_none ~enc:(fun r -> r.modify_date) 3535 + |> Jsont.Object.mem "orientation" (Jsont.option Jsont.string) 3536 + ~dec_absent:None ~enc_omit:Option.is_none ~enc:(fun r -> r.orientation) 3537 + |> Jsont.Object.mem "profileDescription" (Jsont.option Jsont.string) 3538 + ~dec_absent:None ~enc_omit:Option.is_none ~enc:(fun r -> r.profile_description) 3539 + |> Jsont.Object.mem "projectionType" (Jsont.option Jsont.string) 3540 + ~dec_absent:None ~enc_omit:Option.is_none ~enc:(fun r -> r.projection_type) 3541 + |> Jsont.Object.mem "rating" (Jsont.option Jsont.int) 3542 + ~dec_absent:None ~enc_omit:Option.is_none ~enc:(fun r -> r.rating) 3543 + |> Jsont.Object.mem "state" (Jsont.option Jsont.string) 3544 + ~dec_absent:None ~enc_omit:Option.is_none ~enc:(fun r -> r.state) 3545 + |> Jsont.Object.mem "timeZone" (Jsont.option Jsont.string) 3546 + ~dec_absent:None ~enc_omit:Option.is_none ~enc:(fun r -> r.time_zone) 3491 3547 |> Jsont.Object.skip_unknown 3492 3548 |> Jsont.Object.finish 3493 3549 end ··· 3522 3578 name : string; 3523 3579 order : Jsont.json; 3524 3580 owner_id : string; 3525 - thumbnail_asset_id : string; 3581 + thumbnail_asset_id : string option; 3526 3582 updated_at : Ptime.t; 3527 3583 } 3528 3584 3529 - let v ~created_at ~description ~id ~is_activity_enabled ~name ~order ~owner_id ~thumbnail_asset_id ~updated_at () = { created_at; description; id; is_activity_enabled; name; order; owner_id; thumbnail_asset_id; updated_at } 3585 + let v ~created_at ~description ~id ~is_activity_enabled ~name ~order ~owner_id ~updated_at ?thumbnail_asset_id () = { created_at; description; id; is_activity_enabled; name; order; owner_id; thumbnail_asset_id; updated_at } 3530 3586 3531 3587 let created_at t = t.created_at 3532 3588 let description t = t.description ··· 3548 3604 |> Jsont.Object.mem "name" Jsont.string ~enc:(fun r -> r.name) 3549 3605 |> Jsont.Object.mem "order" Jsont.json ~enc:(fun r -> r.order) 3550 3606 |> Jsont.Object.mem "ownerId" Jsont.string ~enc:(fun r -> r.owner_id) 3551 - |> Jsont.Object.mem "thumbnailAssetId" Jsont.string ~enc:(fun r -> r.thumbnail_asset_id) 3607 + |> Jsont.Object.mem "thumbnailAssetId" (Jsont.option Jsont.string) 3608 + ~dec_absent:None ~enc_omit:Option.is_none ~enc:(fun r -> r.thumbnail_asset_id) 3552 3609 |> Jsont.Object.mem "updatedAt" Openapi.Runtime.ptime_jsont ~enc:(fun r -> r.updated_at) 3553 3610 |> Jsont.Object.skip_unknown 3554 3611 |> Jsont.Object.finish ··· 4235 4292 4236 4293 module ResponseDto = struct 4237 4294 type t = { 4238 - app_version : string; 4295 + app_version : string option; 4239 4296 created_at : string; 4240 4297 current : bool; 4241 4298 device_os : string; ··· 4246 4303 updated_at : string; 4247 4304 } 4248 4305 4249 - let v ~app_version ~created_at ~current ~device_os ~device_type ~id ~is_pending_sync_reset ~updated_at ?expires_at () = { app_version; created_at; current; device_os; device_type; expires_at; id; is_pending_sync_reset; updated_at } 4306 + let v ~created_at ~current ~device_os ~device_type ~id ~is_pending_sync_reset ~updated_at ?app_version ?expires_at () = { app_version; created_at; current; device_os; device_type; expires_at; id; is_pending_sync_reset; updated_at } 4250 4307 4251 4308 let app_version t = t.app_version 4252 4309 let created_at t = t.created_at ··· 4261 4318 let jsont : t Jsont.t = 4262 4319 Jsont.Object.map ~kind:"SessionResponseDto" 4263 4320 (fun app_version created_at current device_os device_type expires_at id is_pending_sync_reset updated_at -> { app_version; created_at; current; device_os; device_type; expires_at; id; is_pending_sync_reset; updated_at }) 4264 - |> Jsont.Object.mem "appVersion" Jsont.string ~enc:(fun r -> r.app_version) 4321 + |> Jsont.Object.mem "appVersion" (Jsont.option Jsont.string) 4322 + ~dec_absent:None ~enc_omit:Option.is_none ~enc:(fun r -> r.app_version) 4265 4323 |> Jsont.Object.mem "createdAt" Jsont.string ~enc:(fun r -> r.created_at) 4266 4324 |> Jsont.Object.mem "current" Jsont.bool ~enc:(fun r -> r.current) 4267 4325 |> Jsont.Object.mem "deviceOS" Jsont.string ~enc:(fun r -> r.device_os) ··· 4376 4434 module SessionCreate = struct 4377 4435 module ResponseDto = struct 4378 4436 type t = { 4379 - app_version : string; 4437 + app_version : string option; 4380 4438 created_at : string; 4381 4439 current : bool; 4382 4440 device_os : string; ··· 4388 4446 updated_at : string; 4389 4447 } 4390 4448 4391 - let v ~app_version ~created_at ~current ~device_os ~device_type ~id ~is_pending_sync_reset ~token ~updated_at ?expires_at () = { app_version; created_at; current; device_os; device_type; expires_at; id; is_pending_sync_reset; token; updated_at } 4449 + let v ~created_at ~current ~device_os ~device_type ~id ~is_pending_sync_reset ~token ~updated_at ?app_version ?expires_at () = { app_version; created_at; current; device_os; device_type; expires_at; id; is_pending_sync_reset; token; updated_at } 4392 4450 4393 4451 let app_version t = t.app_version 4394 4452 let created_at t = t.created_at ··· 4404 4462 let jsont : t Jsont.t = 4405 4463 Jsont.Object.map ~kind:"SessionCreateResponseDto" 4406 4464 (fun app_version created_at current device_os device_type expires_at id is_pending_sync_reset token updated_at -> { app_version; created_at; current; device_os; device_type; expires_at; id; is_pending_sync_reset; token; updated_at }) 4407 - |> Jsont.Object.mem "appVersion" Jsont.string ~enc:(fun r -> r.app_version) 4465 + |> Jsont.Object.mem "appVersion" (Jsont.option Jsont.string) 4466 + ~dec_absent:None ~enc_omit:Option.is_none ~enc:(fun r -> r.app_version) 4408 4467 |> Jsont.Object.mem "createdAt" Jsont.string ~enc:(fun r -> r.created_at) 4409 4468 |> Jsont.Object.mem "current" Jsont.bool ~enc:(fun r -> r.current) 4410 4469 |> Jsont.Object.mem "deviceOS" Jsont.string ~enc:(fun r -> r.device_os) ··· 5180 5239 module ReverseGeocodingState = struct 5181 5240 module ResponseDto = struct 5182 5241 type t = { 5183 - last_import_file_name : string; 5184 - last_update : string; 5242 + last_import_file_name : string option; 5243 + last_update : string option; 5185 5244 } 5186 5245 5187 - let v ~last_import_file_name ~last_update () = { last_import_file_name; last_update } 5246 + let v ?last_import_file_name ?last_update () = { last_import_file_name; last_update } 5188 5247 5189 5248 let last_import_file_name t = t.last_import_file_name 5190 5249 let last_update t = t.last_update ··· 5192 5251 let jsont : t Jsont.t = 5193 5252 Jsont.Object.map ~kind:"ReverseGeocodingStateResponseDto" 5194 5253 (fun last_import_file_name last_update -> { last_import_file_name; last_update }) 5195 - |> Jsont.Object.mem "lastImportFileName" Jsont.string ~enc:(fun r -> r.last_import_file_name) 5196 - |> Jsont.Object.mem "lastUpdate" Jsont.string ~enc:(fun r -> r.last_update) 5254 + |> Jsont.Object.mem "lastImportFileName" (Jsont.option Jsont.string) 5255 + ~dec_absent:None ~enc_omit:Option.is_none ~enc:(fun r -> r.last_import_file_name) 5256 + |> Jsont.Object.mem "lastUpdate" (Jsont.option Jsont.string) 5257 + ~dec_absent:None ~enc_omit:Option.is_none ~enc:(fun r -> r.last_update) 5197 5258 |> Jsont.Object.skip_unknown 5198 5259 |> Jsont.Object.finish 5199 5260 end ··· 6079 6140 id : string; 6080 6141 method_name : string; 6081 6142 plugin_id : string; 6082 - schema : Jsont.json; 6143 + schema : Jsont.json option; 6083 6144 supported_contexts : PluginContext.Type.t list; 6084 6145 title : string; 6085 6146 } 6086 6147 6087 - let v ~description ~id ~method_name ~plugin_id ~schema ~supported_contexts ~title () = { description; id; method_name; plugin_id; schema; supported_contexts; title } 6148 + let v ~description ~id ~method_name ~plugin_id ~supported_contexts ~title ?schema () = { description; id; method_name; plugin_id; schema; supported_contexts; title } 6088 6149 6089 6150 let description t = t.description 6090 6151 let id t = t.id ··· 6101 6162 |> Jsont.Object.mem "id" Jsont.string ~enc:(fun r -> r.id) 6102 6163 |> Jsont.Object.mem "methodName" Jsont.string ~enc:(fun r -> r.method_name) 6103 6164 |> Jsont.Object.mem "pluginId" Jsont.string ~enc:(fun r -> r.plugin_id) 6104 - |> Jsont.Object.mem "schema" Jsont.json ~enc:(fun r -> r.schema) 6165 + |> Jsont.Object.mem "schema" (Jsont.option Jsont.json) 6166 + ~dec_absent:None ~enc_omit:Option.is_none ~enc:(fun r -> r.schema) 6105 6167 |> Jsont.Object.mem "supportedContexts" (Jsont.list PluginContext.Type.jsont) ~enc:(fun r -> r.supported_contexts) 6106 6168 |> Jsont.Object.mem "title" Jsont.string ~enc:(fun r -> r.title) 6107 6169 |> Jsont.Object.skip_unknown ··· 6116 6178 id : string; 6117 6179 method_name : string; 6118 6180 plugin_id : string; 6119 - schema : Jsont.json; 6181 + schema : Jsont.json option; 6120 6182 supported_contexts : PluginContext.Type.t list; 6121 6183 title : string; 6122 6184 } 6123 6185 6124 - let v ~description ~id ~method_name ~plugin_id ~schema ~supported_contexts ~title () = { description; id; method_name; plugin_id; schema; supported_contexts; title } 6186 + let v ~description ~id ~method_name ~plugin_id ~supported_contexts ~title ?schema () = { description; id; method_name; plugin_id; schema; supported_contexts; title } 6125 6187 6126 6188 let description t = t.description 6127 6189 let id t = t.id ··· 6138 6200 |> Jsont.Object.mem "id" Jsont.string ~enc:(fun r -> r.id) 6139 6201 |> Jsont.Object.mem "methodName" Jsont.string ~enc:(fun r -> r.method_name) 6140 6202 |> Jsont.Object.mem "pluginId" Jsont.string ~enc:(fun r -> r.plugin_id) 6141 - |> Jsont.Object.mem "schema" Jsont.json ~enc:(fun r -> r.schema) 6203 + |> Jsont.Object.mem "schema" (Jsont.option Jsont.json) 6204 + ~dec_absent:None ~enc_omit:Option.is_none ~enc:(fun r -> r.schema) 6142 6205 |> Jsont.Object.mem "supportedContexts" (Jsont.list PluginContext.Type.jsont) ~enc:(fun r -> r.supported_contexts) 6143 6206 |> Jsont.Object.mem "title" Jsont.string ~enc:(fun r -> r.title) 6144 6207 |> Jsont.Object.skip_unknown ··· 8211 8274 module MapReverseGeocode = struct 8212 8275 module ResponseDto = struct 8213 8276 type t = { 8214 - city : string; 8215 - country : string; 8216 - state : string; 8277 + city : string option; 8278 + country : string option; 8279 + state : string option; 8217 8280 } 8218 8281 8219 - let v ~city ~country ~state () = { city; country; state } 8282 + let v ?city ?country ?state () = { city; country; state } 8220 8283 8221 8284 let city t = t.city 8222 8285 let country t = t.country ··· 8225 8288 let jsont : t Jsont.t = 8226 8289 Jsont.Object.map ~kind:"MapReverseGeocodeResponseDto" 8227 8290 (fun city country state -> { city; country; state }) 8228 - |> Jsont.Object.mem "city" Jsont.string ~enc:(fun r -> r.city) 8229 - |> Jsont.Object.mem "country" Jsont.string ~enc:(fun r -> r.country) 8230 - |> Jsont.Object.mem "state" Jsont.string ~enc:(fun r -> r.state) 8291 + |> Jsont.Object.mem "city" (Jsont.option Jsont.string) 8292 + ~dec_absent:None ~enc_omit:Option.is_none ~enc:(fun r -> r.city) 8293 + |> Jsont.Object.mem "country" (Jsont.option Jsont.string) 8294 + ~dec_absent:None ~enc_omit:Option.is_none ~enc:(fun r -> r.country) 8295 + |> Jsont.Object.mem "state" (Jsont.option Jsont.string) 8296 + ~dec_absent:None ~enc_omit:Option.is_none ~enc:(fun r -> r.state) 8231 8297 |> Jsont.Object.skip_unknown 8232 8298 |> Jsont.Object.finish 8233 8299 end ··· 8261 8327 module MapMarker = struct 8262 8328 module ResponseDto = struct 8263 8329 type t = { 8264 - city : string; 8265 - country : string; 8330 + city : string option; 8331 + country : string option; 8266 8332 id : string; 8267 8333 lat : float; 8268 8334 lon : float; 8269 - state : string; 8335 + state : string option; 8270 8336 } 8271 8337 8272 - let v ~city ~country ~id ~lat ~lon ~state () = { city; country; id; lat; lon; state } 8338 + let v ~id ~lat ~lon ?city ?country ?state () = { city; country; id; lat; lon; state } 8273 8339 8274 8340 let city t = t.city 8275 8341 let country t = t.country ··· 8281 8347 let jsont : t Jsont.t = 8282 8348 Jsont.Object.map ~kind:"MapMarkerResponseDto" 8283 8349 (fun city country id lat lon state -> { city; country; id; lat; lon; state }) 8284 - |> Jsont.Object.mem "city" Jsont.string ~enc:(fun r -> r.city) 8285 - |> Jsont.Object.mem "country" Jsont.string ~enc:(fun r -> r.country) 8350 + |> Jsont.Object.mem "city" (Jsont.option Jsont.string) 8351 + ~dec_absent:None ~enc_omit:Option.is_none ~enc:(fun r -> r.city) 8352 + |> Jsont.Object.mem "country" (Jsont.option Jsont.string) 8353 + ~dec_absent:None ~enc_omit:Option.is_none ~enc:(fun r -> r.country) 8286 8354 |> Jsont.Object.mem "id" Jsont.string ~enc:(fun r -> r.id) 8287 8355 |> Jsont.Object.mem "lat" Jsont.number ~enc:(fun r -> r.lat) 8288 8356 |> Jsont.Object.mem "lon" Jsont.number ~enc:(fun r -> r.lon) 8289 - |> Jsont.Object.mem "state" Jsont.string ~enc:(fun r -> r.state) 8357 + |> Jsont.Object.mem "state" (Jsont.option Jsont.string) 8358 + ~dec_absent:None ~enc_omit:Option.is_none ~enc:(fun r -> r.state) 8290 8359 |> Jsont.Object.skip_unknown 8291 8360 |> Jsont.Object.finish 8292 8361 end ··· 9957 10026 import_paths : string list; 9958 10027 name : string; 9959 10028 owner_id : string; 9960 - refreshed_at : Ptime.t; 10029 + refreshed_at : Ptime.t option; 9961 10030 updated_at : Ptime.t; 9962 10031 } 9963 10032 9964 - let v ~asset_count ~created_at ~exclusion_patterns ~id ~import_paths ~name ~owner_id ~refreshed_at ~updated_at () = { asset_count; created_at; exclusion_patterns; id; import_paths; name; owner_id; refreshed_at; updated_at } 10033 + let v ~asset_count ~created_at ~exclusion_patterns ~id ~import_paths ~name ~owner_id ~updated_at ?refreshed_at () = { asset_count; created_at; exclusion_patterns; id; import_paths; name; owner_id; refreshed_at; updated_at } 9965 10034 9966 10035 let asset_count t = t.asset_count 9967 10036 let created_at t = t.created_at ··· 9983 10052 |> Jsont.Object.mem "importPaths" (Jsont.list Jsont.string) ~enc:(fun r -> r.import_paths) 9984 10053 |> Jsont.Object.mem "name" Jsont.string ~enc:(fun r -> r.name) 9985 10054 |> Jsont.Object.mem "ownerId" Jsont.string ~enc:(fun r -> r.owner_id) 9986 - |> Jsont.Object.mem "refreshedAt" Openapi.Runtime.ptime_jsont ~enc:(fun r -> r.refreshed_at) 10055 + |> Jsont.Object.mem "refreshedAt" (Jsont.option Openapi.Runtime.ptime_jsont) 10056 + ~dec_absent:None ~enc_omit:Option.is_none ~enc:(fun r -> r.refreshed_at) 9987 10057 |> Jsont.Object.mem "updatedAt" Openapi.Runtime.ptime_jsont ~enc:(fun r -> r.updated_at) 9988 10058 |> Jsont.Object.skip_unknown 9989 10059 |> Jsont.Object.finish ··· 10347 10417 type t = { 10348 10418 avatar_color : Jsont.json; 10349 10419 created_at : Ptime.t; 10350 - deleted_at : Ptime.t; 10420 + deleted_at : Ptime.t option; 10351 10421 email : string; 10352 10422 id : string; 10353 10423 is_admin : bool; ··· 10356 10426 oauth_id : string; 10357 10427 profile_changed_at : Ptime.t; 10358 10428 profile_image_path : string; 10359 - quota_size_in_bytes : int64; 10360 - quota_usage_in_bytes : int64; 10429 + quota_size_in_bytes : int64 option; 10430 + quota_usage_in_bytes : int64 option; 10361 10431 should_change_password : bool; 10362 10432 status : Jsont.json; 10363 - storage_label : string; 10433 + storage_label : string option; 10364 10434 updated_at : Ptime.t; 10365 10435 } 10366 10436 10367 - let v ~avatar_color ~created_at ~deleted_at ~email ~id ~is_admin ~name ~oauth_id ~profile_changed_at ~profile_image_path ~quota_size_in_bytes ~quota_usage_in_bytes ~should_change_password ~status ~storage_label ~updated_at ?license () = { avatar_color; created_at; deleted_at; email; id; is_admin; license; name; oauth_id; profile_changed_at; profile_image_path; quota_size_in_bytes; quota_usage_in_bytes; should_change_password; status; storage_label; updated_at } 10437 + let v ~avatar_color ~created_at ~email ~id ~is_admin ~name ~oauth_id ~profile_changed_at ~profile_image_path ~should_change_password ~status ~updated_at ?deleted_at ?license ?quota_size_in_bytes ?quota_usage_in_bytes ?storage_label () = { avatar_color; created_at; deleted_at; email; id; is_admin; license; name; oauth_id; profile_changed_at; profile_image_path; quota_size_in_bytes; quota_usage_in_bytes; should_change_password; status; storage_label; updated_at } 10368 10438 10369 10439 let avatar_color t = t.avatar_color 10370 10440 let created_at t = t.created_at ··· 10389 10459 (fun avatar_color created_at deleted_at email id is_admin license name oauth_id profile_changed_at profile_image_path quota_size_in_bytes quota_usage_in_bytes should_change_password status storage_label updated_at -> { avatar_color; created_at; deleted_at; email; id; is_admin; license; name; oauth_id; profile_changed_at; profile_image_path; quota_size_in_bytes; quota_usage_in_bytes; should_change_password; status; storage_label; updated_at }) 10390 10460 |> Jsont.Object.mem "avatarColor" Jsont.json ~enc:(fun r -> r.avatar_color) 10391 10461 |> Jsont.Object.mem "createdAt" Openapi.Runtime.ptime_jsont ~enc:(fun r -> r.created_at) 10392 - |> Jsont.Object.mem "deletedAt" Openapi.Runtime.ptime_jsont ~enc:(fun r -> r.deleted_at) 10462 + |> Jsont.Object.mem "deletedAt" (Jsont.option Openapi.Runtime.ptime_jsont) 10463 + ~dec_absent:None ~enc_omit:Option.is_none ~enc:(fun r -> r.deleted_at) 10393 10464 |> Jsont.Object.mem "email" Jsont.string ~enc:(fun r -> r.email) 10394 10465 |> Jsont.Object.mem "id" Jsont.string ~enc:(fun r -> r.id) 10395 10466 |> Jsont.Object.mem "isAdmin" Jsont.bool ~enc:(fun r -> r.is_admin) ··· 10399 10470 |> Jsont.Object.mem "oauthId" Jsont.string ~enc:(fun r -> r.oauth_id) 10400 10471 |> Jsont.Object.mem "profileChangedAt" Openapi.Runtime.ptime_jsont ~enc:(fun r -> r.profile_changed_at) 10401 10472 |> Jsont.Object.mem "profileImagePath" Jsont.string ~enc:(fun r -> r.profile_image_path) 10402 - |> Jsont.Object.mem "quotaSizeInBytes" Jsont.int64 ~enc:(fun r -> r.quota_size_in_bytes) 10403 - |> Jsont.Object.mem "quotaUsageInBytes" Jsont.int64 ~enc:(fun r -> r.quota_usage_in_bytes) 10473 + |> Jsont.Object.mem "quotaSizeInBytes" (Jsont.option Jsont.int64) 10474 + ~dec_absent:None ~enc_omit:Option.is_none ~enc:(fun r -> r.quota_size_in_bytes) 10475 + |> Jsont.Object.mem "quotaUsageInBytes" (Jsont.option Jsont.int64) 10476 + ~dec_absent:None ~enc_omit:Option.is_none ~enc:(fun r -> r.quota_usage_in_bytes) 10404 10477 |> Jsont.Object.mem "shouldChangePassword" Jsont.bool ~enc:(fun r -> r.should_change_password) 10405 10478 |> Jsont.Object.mem "status" Jsont.json ~enc:(fun r -> r.status) 10406 - |> Jsont.Object.mem "storageLabel" Jsont.string ~enc:(fun r -> r.storage_label) 10479 + |> Jsont.Object.mem "storageLabel" (Jsont.option Jsont.string) 10480 + ~dec_absent:None ~enc_omit:Option.is_none ~enc:(fun r -> r.storage_label) 10407 10481 |> Jsont.Object.mem "updatedAt" Openapi.Runtime.ptime_jsont ~enc:(fun r -> r.updated_at) 10408 10482 |> Jsont.Object.skip_unknown 10409 10483 |> Jsont.Object.finish ··· 12307 12381 module PersonWithFaces = struct 12308 12382 module ResponseDto = struct 12309 12383 type t = { 12310 - birth_date : string; 12384 + birth_date : string option; 12311 12385 color : string option; 12312 12386 faces : AssetFaceWithoutPerson.ResponseDto.t list; 12313 12387 id : string; ··· 12318 12392 updated_at : Ptime.t option; 12319 12393 } 12320 12394 12321 - let v ~birth_date ~faces ~id ~is_hidden ~name ~thumbnail_path ?color ?is_favorite ?updated_at () = { birth_date; color; faces; id; is_favorite; is_hidden; name; thumbnail_path; updated_at } 12395 + let v ~faces ~id ~is_hidden ~name ~thumbnail_path ?birth_date ?color ?is_favorite ?updated_at () = { birth_date; color; faces; id; is_favorite; is_hidden; name; thumbnail_path; updated_at } 12322 12396 12323 12397 let birth_date t = t.birth_date 12324 12398 let color t = t.color ··· 12333 12407 let jsont : t Jsont.t = 12334 12408 Jsont.Object.map ~kind:"PersonWithFacesResponseDto" 12335 12409 (fun birth_date color faces id is_favorite is_hidden name thumbnail_path updated_at -> { birth_date; color; faces; id; is_favorite; is_hidden; name; thumbnail_path; updated_at }) 12336 - |> Jsont.Object.mem "birthDate" Jsont.string ~enc:(fun r -> r.birth_date) 12410 + |> Jsont.Object.mem "birthDate" (Jsont.option Jsont.string) 12411 + ~dec_absent:None ~enc_omit:Option.is_none ~enc:(fun r -> r.birth_date) 12337 12412 |> Jsont.Object.opt_mem "color" Jsont.string ~enc:(fun r -> r.color) 12338 12413 |> Jsont.Object.mem "faces" (Jsont.list AssetFaceWithoutPerson.ResponseDto.jsont) ~enc:(fun r -> r.faces) 12339 12414 |> Jsont.Object.mem "id" Jsont.string ~enc:(fun r -> r.id) ··· 12360 12435 file_created_at : Ptime.t; (** The actual UTC timestamp when the file was created/captured, preserving timezone information. This is the authoritative timestamp for chronological sorting within timeline groups. Combined with timezone data, this can be used to determine the exact moment the photo was taken. *) 12361 12436 file_modified_at : Ptime.t; (** The UTC timestamp when the file was last modified on the filesystem. This reflects the last time the physical file was changed, which may be different from when the photo was originally taken. *) 12362 12437 has_metadata : bool; 12363 - height : float; 12438 + height : float option; 12364 12439 id : string; 12365 12440 is_archived : bool; 12366 12441 is_edited : bool; ··· 12379 12454 resized : bool option; 12380 12455 stack : Jsont.json option; 12381 12456 tags : Tag.ResponseDto.t list option; 12382 - thumbhash : string; 12457 + thumbhash : string option; 12383 12458 type_ : Jsont.json; 12384 12459 unassigned_faces : AssetFaceWithoutPerson.ResponseDto.t list option; 12385 12460 updated_at : Ptime.t; (** The UTC timestamp when the asset record was last updated in the database. This is automatically maintained by the database and reflects when any field in the asset was last modified. *) 12386 12461 visibility : Jsont.json; 12387 - width : float; 12462 + width : float option; 12388 12463 } 12389 12464 12390 - let v ~checksum ~created_at ~device_asset_id ~device_id ~duration ~file_created_at ~file_modified_at ~has_metadata ~height ~id ~is_archived ~is_edited ~is_favorite ~is_offline ~is_trashed ~local_date_time ~original_file_name ~original_path ~owner_id ~thumbhash ~type_ ~updated_at ~visibility ~width ?duplicate_id ?exif_info ?library_id ?live_photo_video_id ?original_mime_type ?owner ?people ?resized ?stack ?tags ?unassigned_faces () = { checksum; created_at; device_asset_id; device_id; duplicate_id; duration; exif_info; file_created_at; file_modified_at; has_metadata; height; id; is_archived; is_edited; is_favorite; is_offline; is_trashed; library_id; live_photo_video_id; local_date_time; original_file_name; original_mime_type; original_path; owner; owner_id; people; resized; stack; tags; thumbhash; type_; unassigned_faces; updated_at; visibility; width } 12465 + let v ~checksum ~created_at ~device_asset_id ~device_id ~duration ~file_created_at ~file_modified_at ~has_metadata ~id ~is_archived ~is_edited ~is_favorite ~is_offline ~is_trashed ~local_date_time ~original_file_name ~original_path ~owner_id ~type_ ~updated_at ~visibility ?duplicate_id ?exif_info ?height ?library_id ?live_photo_video_id ?original_mime_type ?owner ?people ?resized ?stack ?tags ?thumbhash ?unassigned_faces ?width () = { checksum; created_at; device_asset_id; device_id; duplicate_id; duration; exif_info; file_created_at; file_modified_at; has_metadata; height; id; is_archived; is_edited; is_favorite; is_offline; is_trashed; library_id; live_photo_video_id; local_date_time; original_file_name; original_mime_type; original_path; owner; owner_id; people; resized; stack; tags; thumbhash; type_; unassigned_faces; updated_at; visibility; width } 12391 12466 12392 12467 let checksum t = t.checksum 12393 12468 let created_at t = t.created_at ··· 12438 12513 |> Jsont.Object.mem "fileCreatedAt" Openapi.Runtime.ptime_jsont ~enc:(fun r -> r.file_created_at) 12439 12514 |> Jsont.Object.mem "fileModifiedAt" Openapi.Runtime.ptime_jsont ~enc:(fun r -> r.file_modified_at) 12440 12515 |> Jsont.Object.mem "hasMetadata" Jsont.bool ~enc:(fun r -> r.has_metadata) 12441 - |> Jsont.Object.mem "height" Jsont.number ~enc:(fun r -> r.height) 12516 + |> Jsont.Object.mem "height" (Jsont.option Jsont.number) 12517 + ~dec_absent:None ~enc_omit:Option.is_none ~enc:(fun r -> r.height) 12442 12518 |> Jsont.Object.mem "id" Jsont.string ~enc:(fun r -> r.id) 12443 12519 |> Jsont.Object.mem "isArchived" Jsont.bool ~enc:(fun r -> r.is_archived) 12444 12520 |> Jsont.Object.mem "isEdited" Jsont.bool ~enc:(fun r -> r.is_edited) ··· 12457 12533 |> Jsont.Object.opt_mem "resized" Jsont.bool ~enc:(fun r -> r.resized) 12458 12534 |> Jsont.Object.opt_mem "stack" Jsont.json ~enc:(fun r -> r.stack) 12459 12535 |> Jsont.Object.opt_mem "tags" (Jsont.list Tag.ResponseDto.jsont) ~enc:(fun r -> r.tags) 12460 - |> Jsont.Object.mem "thumbhash" Jsont.string ~enc:(fun r -> r.thumbhash) 12536 + |> Jsont.Object.mem "thumbhash" (Jsont.option Jsont.string) 12537 + ~dec_absent:None ~enc_omit:Option.is_none ~enc:(fun r -> r.thumbhash) 12461 12538 |> Jsont.Object.mem "type" Jsont.json ~enc:(fun r -> r.type_) 12462 12539 |> Jsont.Object.opt_mem "unassignedFaces" (Jsont.list AssetFaceWithoutPerson.ResponseDto.jsont) ~enc:(fun r -> r.unassigned_faces) 12463 12540 |> Jsont.Object.mem "updatedAt" Openapi.Runtime.ptime_jsont ~enc:(fun r -> r.updated_at) 12464 12541 |> Jsont.Object.mem "visibility" Jsont.json ~enc:(fun r -> r.visibility) 12465 - |> Jsont.Object.mem "width" Jsont.number ~enc:(fun r -> r.width) 12542 + |> Jsont.Object.mem "width" (Jsont.option Jsont.number) 12543 + ~dec_absent:None ~enc_omit:Option.is_none ~enc:(fun r -> r.width) 12466 12544 |> Jsont.Object.skip_unknown 12467 12545 |> Jsont.Object.finish 12468 12546 end ··· 12900 12978 count : int; 12901 12979 facets : SearchFacet.ResponseDto.t list; 12902 12980 items : Asset.ResponseDto.t list; 12903 - next_page : string; 12981 + next_page : string option; 12904 12982 total : int; 12905 12983 } 12906 12984 12907 - let v ~count ~facets ~items ~next_page ~total () = { count; facets; items; next_page; total } 12985 + let v ~count ~facets ~items ~total ?next_page () = { count; facets; items; next_page; total } 12908 12986 12909 12987 let count t = t.count 12910 12988 let facets t = t.facets ··· 12918 12996 |> Jsont.Object.mem "count" Jsont.int ~enc:(fun r -> r.count) 12919 12997 |> Jsont.Object.mem "facets" (Jsont.list SearchFacet.ResponseDto.jsont) ~enc:(fun r -> r.facets) 12920 12998 |> Jsont.Object.mem "items" (Jsont.list Asset.ResponseDto.jsont) ~enc:(fun r -> r.items) 12921 - |> Jsont.Object.mem "nextPage" Jsont.string ~enc:(fun r -> r.next_page) 12999 + |> Jsont.Object.mem "nextPage" (Jsont.option Jsont.string) 13000 + ~dec_absent:None ~enc_omit:Option.is_none ~enc:(fun r -> r.next_page) 12922 13001 |> Jsont.Object.mem "total" Jsont.int ~enc:(fun r -> r.total) 12923 13002 |> Jsont.Object.skip_unknown 12924 13003 |> Jsont.Object.finish ··· 13449 13528 13450 13529 module ResponseDto = struct 13451 13530 type t = { 13452 - birth_date : string; 13531 + birth_date : string option; 13453 13532 color : string option; 13454 13533 id : string; 13455 13534 is_favorite : bool option; ··· 13459 13538 updated_at : Ptime.t option; 13460 13539 } 13461 13540 13462 - let v ~birth_date ~id ~is_hidden ~name ~thumbnail_path ?color ?is_favorite ?updated_at () = { birth_date; color; id; is_favorite; is_hidden; name; thumbnail_path; updated_at } 13541 + let v ~id ~is_hidden ~name ~thumbnail_path ?birth_date ?color ?is_favorite ?updated_at () = { birth_date; color; id; is_favorite; is_hidden; name; thumbnail_path; updated_at } 13463 13542 13464 13543 let birth_date t = t.birth_date 13465 13544 let color t = t.color ··· 13473 13552 let jsont : t Jsont.t = 13474 13553 Jsont.Object.map ~kind:"PersonResponseDto" 13475 13554 (fun birth_date color id is_favorite is_hidden name thumbnail_path updated_at -> { birth_date; color; id; is_favorite; is_hidden; name; thumbnail_path; updated_at }) 13476 - |> Jsont.Object.mem "birthDate" Jsont.string ~enc:(fun r -> r.birth_date) 13555 + |> Jsont.Object.mem "birthDate" (Jsont.option Jsont.string) 13556 + ~dec_absent:None ~enc_omit:Option.is_none ~enc:(fun r -> r.birth_date) 13477 13557 |> Jsont.Object.opt_mem "color" Jsont.string ~enc:(fun r -> r.color) 13478 13558 |> Jsont.Object.mem "id" Jsont.string ~enc:(fun r -> r.id) 13479 13559 |> Jsont.Object.opt_mem "isFavorite" Jsont.bool ~enc:(fun r -> r.is_favorite) ··· 14719 14799 module ResponseDto = struct 14720 14800 type t = { 14721 14801 album_name : string; 14722 - album_thumbnail_asset_id : string; 14802 + album_thumbnail_asset_id : string option; 14723 14803 album_users : AlbumUser.ResponseDto.t list; 14724 14804 asset_count : int; 14725 14805 assets : Asset.ResponseDto.t list; ··· 14739 14819 updated_at : Ptime.t; 14740 14820 } 14741 14821 14742 - let v ~album_name ~album_thumbnail_asset_id ~album_users ~asset_count ~assets ~created_at ~description ~has_shared_link ~id ~is_activity_enabled ~owner ~owner_id ~shared ~updated_at ?contributor_counts ?end_date ?last_modified_asset_timestamp ?order ?start_date () = { album_name; album_thumbnail_asset_id; album_users; asset_count; assets; contributor_counts; created_at; description; end_date; has_shared_link; id; is_activity_enabled; last_modified_asset_timestamp; order; owner; owner_id; shared; start_date; updated_at } 14822 + let v ~album_name ~album_users ~asset_count ~assets ~created_at ~description ~has_shared_link ~id ~is_activity_enabled ~owner ~owner_id ~shared ~updated_at ?album_thumbnail_asset_id ?contributor_counts ?end_date ?last_modified_asset_timestamp ?order ?start_date () = { album_name; album_thumbnail_asset_id; album_users; asset_count; assets; contributor_counts; created_at; description; end_date; has_shared_link; id; is_activity_enabled; last_modified_asset_timestamp; order; owner; owner_id; shared; start_date; updated_at } 14743 14823 14744 14824 let album_name t = t.album_name 14745 14825 let album_thumbnail_asset_id t = t.album_thumbnail_asset_id ··· 14765 14845 Jsont.Object.map ~kind:"AlbumResponseDto" 14766 14846 (fun album_name album_thumbnail_asset_id album_users asset_count assets contributor_counts created_at description end_date has_shared_link id is_activity_enabled last_modified_asset_timestamp order owner owner_id shared start_date updated_at -> { album_name; album_thumbnail_asset_id; album_users; asset_count; assets; contributor_counts; created_at; description; end_date; has_shared_link; id; is_activity_enabled; last_modified_asset_timestamp; order; owner; owner_id; shared; start_date; updated_at }) 14767 14847 |> Jsont.Object.mem "albumName" Jsont.string ~enc:(fun r -> r.album_name) 14768 - |> Jsont.Object.mem "albumThumbnailAssetId" Jsont.string ~enc:(fun r -> r.album_thumbnail_asset_id) 14848 + |> Jsont.Object.mem "albumThumbnailAssetId" (Jsont.option Jsont.string) 14849 + ~dec_absent:None ~enc_omit:Option.is_none ~enc:(fun r -> r.album_thumbnail_asset_id) 14769 14850 |> Jsont.Object.mem "albumUsers" (Jsont.list AlbumUser.ResponseDto.jsont) ~enc:(fun r -> r.album_users) 14770 14851 |> Jsont.Object.mem "assetCount" Jsont.int ~enc:(fun r -> r.asset_count) 14771 14852 |> Jsont.Object.mem "assets" (Jsont.list Asset.ResponseDto.jsont) ~enc:(fun r -> r.assets) ··· 14941 15022 allow_upload : bool; 14942 15023 assets : Asset.ResponseDto.t list; 14943 15024 created_at : Ptime.t; 14944 - description : string; 14945 - expires_at : Ptime.t; 15025 + description : string option; 15026 + expires_at : Ptime.t option; 14946 15027 id : string; 14947 15028 key : string; 14948 - password : string; 15029 + password : string option; 14949 15030 show_metadata : bool; 14950 - slug : string; 15031 + slug : string option; 14951 15032 token : string option; 14952 15033 type_ : Jsont.json; 14953 15034 user_id : string; 14954 15035 } 14955 15036 14956 - let v ~allow_download ~allow_upload ~assets ~created_at ~description ~expires_at ~id ~key ~password ~show_metadata ~slug ~type_ ~user_id ?album ?token () = { album; allow_download; allow_upload; assets; created_at; description; expires_at; id; key; password; show_metadata; slug; token; type_; user_id } 15037 + let v ~allow_download ~allow_upload ~assets ~created_at ~id ~key ~show_metadata ~type_ ~user_id ?album ?description ?expires_at ?password ?slug ?token () = { album; allow_download; allow_upload; assets; created_at; description; expires_at; id; key; password; show_metadata; slug; token; type_; user_id } 14957 15038 14958 15039 let album t = t.album 14959 15040 let allow_download t = t.allow_download ··· 14979 15060 |> Jsont.Object.mem "allowUpload" Jsont.bool ~enc:(fun r -> r.allow_upload) 14980 15061 |> Jsont.Object.mem "assets" (Jsont.list Asset.ResponseDto.jsont) ~enc:(fun r -> r.assets) 14981 15062 |> Jsont.Object.mem "createdAt" Openapi.Runtime.ptime_jsont ~enc:(fun r -> r.created_at) 14982 - |> Jsont.Object.mem "description" Jsont.string ~enc:(fun r -> r.description) 14983 - |> Jsont.Object.mem "expiresAt" Openapi.Runtime.ptime_jsont ~enc:(fun r -> r.expires_at) 15063 + |> Jsont.Object.mem "description" (Jsont.option Jsont.string) 15064 + ~dec_absent:None ~enc_omit:Option.is_none ~enc:(fun r -> r.description) 15065 + |> Jsont.Object.mem "expiresAt" (Jsont.option Openapi.Runtime.ptime_jsont) 15066 + ~dec_absent:None ~enc_omit:Option.is_none ~enc:(fun r -> r.expires_at) 14984 15067 |> Jsont.Object.mem "id" Jsont.string ~enc:(fun r -> r.id) 14985 15068 |> Jsont.Object.mem "key" Jsont.string ~enc:(fun r -> r.key) 14986 - |> Jsont.Object.mem "password" Jsont.string ~enc:(fun r -> r.password) 15069 + |> Jsont.Object.mem "password" (Jsont.option Jsont.string) 15070 + ~dec_absent:None ~enc_omit:Option.is_none ~enc:(fun r -> r.password) 14987 15071 |> Jsont.Object.mem "showMetadata" Jsont.bool ~enc:(fun r -> r.show_metadata) 14988 - |> Jsont.Object.mem "slug" Jsont.string ~enc:(fun r -> r.slug) 15072 + |> Jsont.Object.mem "slug" (Jsont.option Jsont.string) 15073 + ~dec_absent:None ~enc_omit:Option.is_none ~enc:(fun r -> r.slug) 14989 15074 |> Jsont.Object.opt_mem "token" Jsont.string ~enc:(fun r -> r.token) 14990 15075 |> Jsont.Object.mem "type" Jsont.json ~enc:(fun r -> r.type_) 14991 15076 |> Jsont.Object.mem "userId" Jsont.string ~enc:(fun r -> r.user_id)
+115 -115
ocaml-immich/immich.mli
··· 36 36 type t 37 37 38 38 (** Construct a value *) 39 - val v : filter_config:Jsont.json -> id:string -> order:float -> plugin_filter_id:string -> workflow_id:string -> unit -> t 39 + val v : id:string -> order:float -> plugin_filter_id:string -> workflow_id:string -> ?filter_config:Jsont.json -> unit -> t 40 40 41 - val filter_config : t -> Jsont.json 41 + val filter_config : t -> Jsont.json option 42 42 43 43 val id : t -> string 44 44 ··· 72 72 type t 73 73 74 74 (** Construct a value *) 75 - val v : action_config:Jsont.json -> id:string -> order:float -> plugin_action_id:string -> workflow_id:string -> unit -> t 75 + val v : id:string -> order:float -> plugin_action_id:string -> workflow_id:string -> ?action_config:Jsont.json -> unit -> t 76 76 77 - val action_config : t -> Jsont.json 77 + val action_config : t -> Jsont.json option 78 78 79 79 val id : t -> string 80 80 ··· 114 114 type t 115 115 116 116 (** Construct a value *) 117 - val v : actions:WorkflowAction.ResponseDto.t list -> created_at:string -> description:string -> enabled:bool -> filters:WorkflowFilter.ResponseDto.t list -> id:string -> name:string -> owner_id:string -> trigger_type:Jsont.json -> unit -> t 117 + val v : actions:WorkflowAction.ResponseDto.t list -> created_at:string -> description:string -> enabled:bool -> filters:WorkflowFilter.ResponseDto.t list -> id:string -> owner_id:string -> trigger_type:Jsont.json -> ?name:string -> unit -> t 118 118 119 119 val actions : t -> WorkflowAction.ResponseDto.t list 120 120 ··· 128 128 129 129 val id : t -> string 130 130 131 - val name : t -> string 131 + val name : t -> string option 132 132 133 133 val owner_id : t -> string 134 134 ··· 210 210 type t 211 211 212 212 (** Construct a value *) 213 - val v : checked_at:string -> release_version:string -> unit -> t 213 + val v : ?checked_at:string -> ?release_version:string -> unit -> t 214 214 215 - val checked_at : t -> string 215 + val checked_at : t -> string option 216 216 217 - val release_version : t -> string 217 + val release_version : t -> string option 218 218 219 219 val jsont : t Jsont.t 220 220 end ··· 471 471 type t 472 472 473 473 (** Construct a value *) 474 - val v : asset_id:string -> created_at:Ptime.t -> id:string -> type_:Jsont.json -> user:User.ResponseDto.t -> ?comment:string -> unit -> t 474 + val v : created_at:Ptime.t -> id:string -> type_:Jsont.json -> user:User.ResponseDto.t -> ?asset_id:string -> ?comment:string -> unit -> t 475 475 476 - val asset_id : t -> string 476 + val asset_id : t -> string option 477 477 478 478 val comment : t -> string option 479 479 ··· 521 521 type t 522 522 523 523 (** Construct a value *) 524 - val v : photos:int -> quota_size_in_bytes:int64 -> usage:int64 -> usage_photos:int64 -> usage_videos:int64 -> user_id:string -> user_name:string -> videos:int -> unit -> t 524 + val v : photos:int -> usage:int64 -> usage_photos:int64 -> usage_videos:int64 -> user_id:string -> user_name:string -> videos:int -> ?quota_size_in_bytes:int64 -> unit -> t 525 525 526 526 val photos : t -> int 527 527 528 - val quota_size_in_bytes : t -> int64 528 + val quota_size_in_bytes : t -> int64 option 529 529 530 530 val usage : t -> int64 531 531 ··· 1146 1146 type t 1147 1147 1148 1148 (** Construct a value *) 1149 - val v : auto_launch:bool -> auto_register:bool -> button_text:string -> client_id:string -> client_secret:string -> default_storage_quota:int64 -> enabled:bool -> issuer_url:string -> mobile_override_enabled:bool -> mobile_redirect_uri:string -> profile_signing_algorithm:string -> role_claim:string -> scope:string -> signing_algorithm:string -> storage_label_claim:string -> storage_quota_claim:string -> timeout:int -> token_endpoint_auth_method:Jsont.json -> unit -> t 1149 + val v : auto_launch:bool -> auto_register:bool -> button_text:string -> client_id:string -> client_secret:string -> enabled:bool -> issuer_url:string -> mobile_override_enabled:bool -> mobile_redirect_uri:string -> profile_signing_algorithm:string -> role_claim:string -> scope:string -> signing_algorithm:string -> storage_label_claim:string -> storage_quota_claim:string -> timeout:int -> token_endpoint_auth_method:Jsont.json -> ?default_storage_quota:int64 -> unit -> t 1150 1150 1151 1151 val auto_launch : t -> bool 1152 1152 ··· 1158 1158 1159 1159 val client_secret : t -> string 1160 1160 1161 - val default_storage_quota : t -> int64 1161 + val default_storage_quota : t -> int64 option 1162 1162 1163 1163 val enabled : t -> bool 1164 1164 ··· 1389 1389 type t 1390 1390 1391 1391 (** Construct a value *) 1392 - val v : deleted_at:Ptime.t -> email:string -> has_profile_image:bool -> id:string -> name:string -> profile_changed_at:Ptime.t -> ?avatar_color:Jsont.json -> unit -> t 1392 + val v : email:string -> has_profile_image:bool -> id:string -> name:string -> profile_changed_at:Ptime.t -> ?avatar_color:Jsont.json -> ?deleted_at:Ptime.t -> unit -> t 1393 1393 1394 1394 val avatar_color : t -> Jsont.json option 1395 1395 1396 - val deleted_at : t -> Ptime.t 1396 + val deleted_at : t -> Ptime.t option 1397 1397 1398 1398 val email : t -> string 1399 1399 ··· 1547 1547 type t 1548 1548 1549 1549 (** Construct a value *) 1550 - val v : birth_date:Ptime.t -> color:string -> created_at:Ptime.t -> face_asset_id:string -> id:string -> is_favorite:bool -> is_hidden:bool -> name:string -> owner_id:string -> updated_at:Ptime.t -> unit -> t 1550 + val v : created_at:Ptime.t -> id:string -> is_favorite:bool -> is_hidden:bool -> name:string -> owner_id:string -> updated_at:Ptime.t -> ?birth_date:Ptime.t -> ?color:string -> ?face_asset_id:string -> unit -> t 1551 1551 1552 - val birth_date : t -> Ptime.t 1552 + val birth_date : t -> Ptime.t option 1553 1553 1554 - val color : t -> string 1554 + val color : t -> string option 1555 1555 1556 1556 val created_at : t -> Ptime.t 1557 1557 1558 - val face_asset_id : t -> string 1558 + val face_asset_id : t -> string option 1559 1559 1560 1560 val id : t -> string 1561 1561 ··· 1623 1623 type t 1624 1624 1625 1625 (** Construct a value *) 1626 - val v : created_at:Ptime.t -> data:Jsont.json -> deleted_at:Ptime.t -> hide_at:Ptime.t -> id:string -> is_saved:bool -> memory_at:Ptime.t -> owner_id:string -> seen_at:Ptime.t -> show_at:Ptime.t -> type_:Jsont.json -> updated_at:Ptime.t -> unit -> t 1626 + val v : created_at:Ptime.t -> data:Jsont.json -> id:string -> is_saved:bool -> memory_at:Ptime.t -> owner_id:string -> type_:Jsont.json -> updated_at:Ptime.t -> ?deleted_at:Ptime.t -> ?hide_at:Ptime.t -> ?seen_at:Ptime.t -> ?show_at:Ptime.t -> unit -> t 1627 1627 1628 1628 val created_at : t -> Ptime.t 1629 1629 1630 1630 val data : t -> Jsont.json 1631 1631 1632 - val deleted_at : t -> Ptime.t 1632 + val deleted_at : t -> Ptime.t option 1633 1633 1634 - val hide_at : t -> Ptime.t 1634 + val hide_at : t -> Ptime.t option 1635 1635 1636 1636 val id : t -> string 1637 1637 ··· 1641 1641 1642 1642 val owner_id : t -> string 1643 1643 1644 - val seen_at : t -> Ptime.t 1644 + val seen_at : t -> Ptime.t option 1645 1645 1646 - val show_at : t -> Ptime.t 1646 + val show_at : t -> Ptime.t option 1647 1647 1648 1648 val type_ : t -> Jsont.json 1649 1649 ··· 1780 1780 type t 1781 1781 1782 1782 (** Construct a value *) 1783 - val v : deleted_at:Ptime.t -> email:string -> has_profile_image:bool -> id:string -> is_admin:bool -> name:string -> oauth_id:string -> pin_code:string -> profile_changed_at:Ptime.t -> quota_size_in_bytes:int -> quota_usage_in_bytes:int -> storage_label:string -> ?avatar_color:Jsont.json -> unit -> t 1783 + val v : email:string -> has_profile_image:bool -> id:string -> is_admin:bool -> name:string -> oauth_id:string -> profile_changed_at:Ptime.t -> quota_usage_in_bytes:int -> ?avatar_color:Jsont.json -> ?deleted_at:Ptime.t -> ?pin_code:string -> ?quota_size_in_bytes:int -> ?storage_label:string -> unit -> t 1784 1784 1785 1785 val avatar_color : t -> Jsont.json option 1786 1786 1787 - val deleted_at : t -> Ptime.t 1787 + val deleted_at : t -> Ptime.t option 1788 1788 1789 1789 val email : t -> string 1790 1790 ··· 1798 1798 1799 1799 val oauth_id : t -> string 1800 1800 1801 - val pin_code : t -> string 1801 + val pin_code : t -> string option 1802 1802 1803 1803 val profile_changed_at : t -> Ptime.t 1804 1804 1805 - val quota_size_in_bytes : t -> int 1805 + val quota_size_in_bytes : t -> int option 1806 1806 1807 1807 val quota_usage_in_bytes : t -> int 1808 1808 1809 - val storage_label : t -> string 1809 + val storage_label : t -> string option 1810 1810 1811 1811 val jsont : t Jsont.t 1812 1812 end ··· 1817 1817 type t 1818 1818 1819 1819 (** Construct a value *) 1820 - val v : checksum:string -> deleted_at:Ptime.t -> duration:string -> file_created_at:Ptime.t -> file_modified_at:Ptime.t -> height:int -> id:string -> is_edited:bool -> is_favorite:bool -> library_id:string -> live_photo_video_id:string -> local_date_time:Ptime.t -> original_file_name:string -> owner_id:string -> stack_id:string -> thumbhash:string -> type_:Jsont.json -> visibility:Jsont.json -> width:int -> unit -> t 1820 + val v : checksum:string -> id:string -> is_edited:bool -> is_favorite:bool -> original_file_name:string -> owner_id:string -> type_:Jsont.json -> visibility:Jsont.json -> ?deleted_at:Ptime.t -> ?duration:string -> ?file_created_at:Ptime.t -> ?file_modified_at:Ptime.t -> ?height:int -> ?library_id:string -> ?live_photo_video_id:string -> ?local_date_time:Ptime.t -> ?stack_id:string -> ?thumbhash:string -> ?width:int -> unit -> t 1821 1821 1822 1822 val checksum : t -> string 1823 1823 1824 - val deleted_at : t -> Ptime.t 1824 + val deleted_at : t -> Ptime.t option 1825 1825 1826 - val duration : t -> string 1826 + val duration : t -> string option 1827 1827 1828 - val file_created_at : t -> Ptime.t 1828 + val file_created_at : t -> Ptime.t option 1829 1829 1830 - val file_modified_at : t -> Ptime.t 1830 + val file_modified_at : t -> Ptime.t option 1831 1831 1832 - val height : t -> int 1832 + val height : t -> int option 1833 1833 1834 1834 val id : t -> string 1835 1835 ··· 1837 1837 1838 1838 val is_favorite : t -> bool 1839 1839 1840 - val library_id : t -> string 1840 + val library_id : t -> string option 1841 1841 1842 - val live_photo_video_id : t -> string 1842 + val live_photo_video_id : t -> string option 1843 1843 1844 - val local_date_time : t -> Ptime.t 1844 + val local_date_time : t -> Ptime.t option 1845 1845 1846 1846 val original_file_name : t -> string 1847 1847 1848 1848 val owner_id : t -> string 1849 1849 1850 - val stack_id : t -> string 1850 + val stack_id : t -> string option 1851 1851 1852 - val thumbhash : t -> string 1852 + val thumbhash : t -> string option 1853 1853 1854 1854 val type_ : t -> Jsont.json 1855 1855 1856 1856 val visibility : t -> Jsont.json 1857 1857 1858 - val width : t -> int 1858 + val width : t -> int option 1859 1859 1860 1860 val jsont : t Jsont.t 1861 1861 end ··· 1898 1898 type t 1899 1899 1900 1900 (** Construct a value *) 1901 - val v : asset_id:string -> bounding_box_x1:int -> bounding_box_x2:int -> bounding_box_y1:int -> bounding_box_y2:int -> id:string -> image_height:int -> image_width:int -> person_id:string -> source_type:string -> unit -> t 1901 + val v : asset_id:string -> bounding_box_x1:int -> bounding_box_x2:int -> bounding_box_y1:int -> bounding_box_y2:int -> id:string -> image_height:int -> image_width:int -> source_type:string -> ?person_id:string -> unit -> t 1902 1902 1903 1903 val asset_id : t -> string 1904 1904 ··· 1916 1916 1917 1917 val image_width : t -> int 1918 1918 1919 - val person_id : t -> string 1919 + val person_id : t -> string option 1920 1920 1921 1921 val source_type : t -> string 1922 1922 ··· 1942 1942 type t 1943 1943 1944 1944 (** Construct a value *) 1945 - val v : asset_id:string -> city:string -> country:string -> date_time_original:Ptime.t -> description:string -> exif_image_height:int -> exif_image_width:int -> exposure_time:string -> f_number:float -> file_size_in_byte:int -> focal_length:float -> fps:float -> iso:int -> latitude:float -> lens_model:string -> longitude:float -> make:string -> model:string -> modify_date:Ptime.t -> orientation:string -> profile_description:string -> projection_type:string -> rating:int -> state:string -> time_zone:string -> unit -> t 1945 + val v : asset_id:string -> ?city:string -> ?country:string -> ?date_time_original:Ptime.t -> ?description:string -> ?exif_image_height:int -> ?exif_image_width:int -> ?exposure_time:string -> ?f_number:float -> ?file_size_in_byte:int -> ?focal_length:float -> ?fps:float -> ?iso:int -> ?latitude:float -> ?lens_model:string -> ?longitude:float -> ?make:string -> ?model:string -> ?modify_date:Ptime.t -> ?orientation:string -> ?profile_description:string -> ?projection_type:string -> ?rating:int -> ?state:string -> ?time_zone:string -> unit -> t 1946 1946 1947 1947 val asset_id : t -> string 1948 1948 1949 - val city : t -> string 1949 + val city : t -> string option 1950 1950 1951 - val country : t -> string 1951 + val country : t -> string option 1952 1952 1953 - val date_time_original : t -> Ptime.t 1953 + val date_time_original : t -> Ptime.t option 1954 1954 1955 - val description : t -> string 1955 + val description : t -> string option 1956 1956 1957 - val exif_image_height : t -> int 1957 + val exif_image_height : t -> int option 1958 1958 1959 - val exif_image_width : t -> int 1959 + val exif_image_width : t -> int option 1960 1960 1961 - val exposure_time : t -> string 1961 + val exposure_time : t -> string option 1962 1962 1963 - val f_number : t -> float 1963 + val f_number : t -> float option 1964 1964 1965 - val file_size_in_byte : t -> int 1965 + val file_size_in_byte : t -> int option 1966 1966 1967 - val focal_length : t -> float 1967 + val focal_length : t -> float option 1968 1968 1969 - val fps : t -> float 1969 + val fps : t -> float option 1970 1970 1971 - val iso : t -> int 1971 + val iso : t -> int option 1972 1972 1973 - val latitude : t -> float 1973 + val latitude : t -> float option 1974 1974 1975 - val lens_model : t -> string 1975 + val lens_model : t -> string option 1976 1976 1977 - val longitude : t -> float 1977 + val longitude : t -> float option 1978 1978 1979 - val make : t -> string 1979 + val make : t -> string option 1980 1980 1981 - val model : t -> string 1981 + val model : t -> string option 1982 1982 1983 - val modify_date : t -> Ptime.t 1983 + val modify_date : t -> Ptime.t option 1984 1984 1985 - val orientation : t -> string 1985 + val orientation : t -> string option 1986 1986 1987 - val profile_description : t -> string 1987 + val profile_description : t -> string option 1988 1988 1989 - val projection_type : t -> string 1989 + val projection_type : t -> string option 1990 1990 1991 - val rating : t -> int 1991 + val rating : t -> int option 1992 1992 1993 - val state : t -> string 1993 + val state : t -> string option 1994 1994 1995 - val time_zone : t -> string 1995 + val time_zone : t -> string option 1996 1996 1997 1997 val jsont : t Jsont.t 1998 1998 end ··· 2016 2016 type t 2017 2017 2018 2018 (** Construct a value *) 2019 - val v : created_at:Ptime.t -> description:string -> id:string -> is_activity_enabled:bool -> name:string -> order:Jsont.json -> owner_id:string -> thumbnail_asset_id:string -> updated_at:Ptime.t -> unit -> t 2019 + val v : created_at:Ptime.t -> description:string -> id:string -> is_activity_enabled:bool -> name:string -> order:Jsont.json -> owner_id:string -> updated_at:Ptime.t -> ?thumbnail_asset_id:string -> unit -> t 2020 2020 2021 2021 val created_at : t -> Ptime.t 2022 2022 ··· 2032 2032 2033 2033 val owner_id : t -> string 2034 2034 2035 - val thumbnail_asset_id : t -> string 2035 + val thumbnail_asset_id : t -> string option 2036 2036 2037 2037 val updated_at : t -> Ptime.t 2038 2038 ··· 2478 2478 type t 2479 2479 2480 2480 (** Construct a value *) 2481 - val v : app_version:string -> created_at:string -> current:bool -> device_os:string -> device_type:string -> id:string -> is_pending_sync_reset:bool -> updated_at:string -> ?expires_at:string -> unit -> t 2481 + val v : created_at:string -> current:bool -> device_os:string -> device_type:string -> id:string -> is_pending_sync_reset:bool -> updated_at:string -> ?app_version:string -> ?expires_at:string -> unit -> t 2482 2482 2483 - val app_version : t -> string 2483 + val app_version : t -> string option 2484 2484 2485 2485 val created_at : t -> string 2486 2486 ··· 2540 2540 type t 2541 2541 2542 2542 (** Construct a value *) 2543 - val v : app_version:string -> created_at:string -> current:bool -> device_os:string -> device_type:string -> id:string -> is_pending_sync_reset:bool -> token:string -> updated_at:string -> ?expires_at:string -> unit -> t 2543 + val v : created_at:string -> current:bool -> device_os:string -> device_type:string -> id:string -> is_pending_sync_reset:bool -> token:string -> updated_at:string -> ?app_version:string -> ?expires_at:string -> unit -> t 2544 2544 2545 - val app_version : t -> string 2545 + val app_version : t -> string option 2546 2546 2547 2547 val created_at : t -> string 2548 2548 ··· 2950 2950 type t 2951 2951 2952 2952 (** Construct a value *) 2953 - val v : last_import_file_name:string -> last_update:string -> unit -> t 2953 + val v : ?last_import_file_name:string -> ?last_update:string -> unit -> t 2954 2954 2955 - val last_import_file_name : t -> string 2955 + val last_import_file_name : t -> string option 2956 2956 2957 - val last_update : t -> string 2957 + val last_update : t -> string option 2958 2958 2959 2959 val jsont : t Jsont.t 2960 2960 end ··· 3422 3422 type t 3423 3423 3424 3424 (** Construct a value *) 3425 - val v : description:string -> id:string -> method_name:string -> plugin_id:string -> schema:Jsont.json -> supported_contexts:PluginContext.Type.t list -> title:string -> unit -> t 3425 + val v : description:string -> id:string -> method_name:string -> plugin_id:string -> supported_contexts:PluginContext.Type.t list -> title:string -> ?schema:Jsont.json -> unit -> t 3426 3426 3427 3427 val description : t -> string 3428 3428 ··· 3432 3432 3433 3433 val plugin_id : t -> string 3434 3434 3435 - val schema : t -> Jsont.json 3435 + val schema : t -> Jsont.json option 3436 3436 3437 3437 val supported_contexts : t -> PluginContext.Type.t list 3438 3438 ··· 3447 3447 type t 3448 3448 3449 3449 (** Construct a value *) 3450 - val v : description:string -> id:string -> method_name:string -> plugin_id:string -> schema:Jsont.json -> supported_contexts:PluginContext.Type.t list -> title:string -> unit -> t 3450 + val v : description:string -> id:string -> method_name:string -> plugin_id:string -> supported_contexts:PluginContext.Type.t list -> title:string -> ?schema:Jsont.json -> unit -> t 3451 3451 3452 3452 val description : t -> string 3453 3453 ··· 3457 3457 3458 3458 val plugin_id : t -> string 3459 3459 3460 - val schema : t -> Jsont.json 3460 + val schema : t -> Jsont.json option 3461 3461 3462 3462 val supported_contexts : t -> PluginContext.Type.t list 3463 3463 ··· 4450 4450 type t 4451 4451 4452 4452 (** Construct a value *) 4453 - val v : city:string -> country:string -> state:string -> unit -> t 4453 + val v : ?city:string -> ?country:string -> ?state:string -> unit -> t 4454 4454 4455 - val city : t -> string 4455 + val city : t -> string option 4456 4456 4457 - val country : t -> string 4457 + val country : t -> string option 4458 4458 4459 - val state : t -> string 4459 + val state : t -> string option 4460 4460 4461 4461 val jsont : t Jsont.t 4462 4462 end ··· 4472 4472 type t 4473 4473 4474 4474 (** Construct a value *) 4475 - val v : city:string -> country:string -> id:string -> lat:float -> lon:float -> state:string -> unit -> t 4475 + val v : id:string -> lat:float -> lon:float -> ?city:string -> ?country:string -> ?state:string -> unit -> t 4476 4476 4477 - val city : t -> string 4477 + val city : t -> string option 4478 4478 4479 - val country : t -> string 4479 + val country : t -> string option 4480 4480 4481 4481 val id : t -> string 4482 4482 ··· 4484 4484 4485 4485 val lon : t -> float 4486 4486 4487 - val state : t -> string 4487 + val state : t -> string option 4488 4488 4489 4489 val jsont : t Jsont.t 4490 4490 end ··· 5369 5369 type t 5370 5370 5371 5371 (** Construct a value *) 5372 - val v : asset_count:int -> created_at:Ptime.t -> exclusion_patterns:string list -> id:string -> import_paths:string list -> name:string -> owner_id:string -> refreshed_at:Ptime.t -> updated_at:Ptime.t -> unit -> t 5372 + val v : asset_count:int -> created_at:Ptime.t -> exclusion_patterns:string list -> id:string -> import_paths:string list -> name:string -> owner_id:string -> updated_at:Ptime.t -> ?refreshed_at:Ptime.t -> unit -> t 5373 5373 5374 5374 val asset_count : t -> int 5375 5375 ··· 5385 5385 5386 5386 val owner_id : t -> string 5387 5387 5388 - val refreshed_at : t -> Ptime.t 5388 + val refreshed_at : t -> Ptime.t option 5389 5389 5390 5390 val updated_at : t -> Ptime.t 5391 5391 ··· 5571 5571 type t 5572 5572 5573 5573 (** Construct a value *) 5574 - val v : avatar_color:Jsont.json -> created_at:Ptime.t -> deleted_at:Ptime.t -> email:string -> id:string -> is_admin:bool -> name:string -> oauth_id:string -> profile_changed_at:Ptime.t -> profile_image_path:string -> quota_size_in_bytes:int64 -> quota_usage_in_bytes:int64 -> should_change_password:bool -> status:Jsont.json -> storage_label:string -> updated_at:Ptime.t -> ?license:Jsont.json -> unit -> t 5574 + val v : avatar_color:Jsont.json -> created_at:Ptime.t -> email:string -> id:string -> is_admin:bool -> name:string -> oauth_id:string -> profile_changed_at:Ptime.t -> profile_image_path:string -> should_change_password:bool -> status:Jsont.json -> updated_at:Ptime.t -> ?deleted_at:Ptime.t -> ?license:Jsont.json -> ?quota_size_in_bytes:int64 -> ?quota_usage_in_bytes:int64 -> ?storage_label:string -> unit -> t 5575 5575 5576 5576 val avatar_color : t -> Jsont.json 5577 5577 5578 5578 val created_at : t -> Ptime.t 5579 5579 5580 - val deleted_at : t -> Ptime.t 5580 + val deleted_at : t -> Ptime.t option 5581 5581 5582 5582 val email : t -> string 5583 5583 ··· 5595 5595 5596 5596 val profile_image_path : t -> string 5597 5597 5598 - val quota_size_in_bytes : t -> int64 5598 + val quota_size_in_bytes : t -> int64 option 5599 5599 5600 - val quota_usage_in_bytes : t -> int64 5600 + val quota_usage_in_bytes : t -> int64 option 5601 5601 5602 5602 val should_change_password : t -> bool 5603 5603 5604 5604 val status : t -> Jsont.json 5605 5605 5606 - val storage_label : t -> string 5606 + val storage_label : t -> string option 5607 5607 5608 5608 val updated_at : t -> Ptime.t 5609 5609 ··· 6561 6561 type t 6562 6562 6563 6563 (** Construct a value *) 6564 - val v : birth_date:string -> faces:AssetFaceWithoutPerson.ResponseDto.t list -> id:string -> is_hidden:bool -> name:string -> thumbnail_path:string -> ?color:string -> ?is_favorite:bool -> ?updated_at:Ptime.t -> unit -> t 6564 + val v : faces:AssetFaceWithoutPerson.ResponseDto.t list -> id:string -> is_hidden:bool -> name:string -> thumbnail_path:string -> ?birth_date:string -> ?color:string -> ?is_favorite:bool -> ?updated_at:Ptime.t -> unit -> t 6565 6565 6566 - val birth_date : t -> string 6566 + val birth_date : t -> string option 6567 6567 6568 6568 val color : t -> string option 6569 6569 ··· 6597 6597 @param local_date_time The local date and time when the photo/video was taken, derived from EXIF metadata. This represents the photographer's local time regardless of timezone, stored as a timezone-agnostic timestamp. Used for timeline grouping by "local" days and months. 6598 6598 @param updated_at The UTC timestamp when the asset record was last updated in the database. This is automatically maintained by the database and reflects when any field in the asset was last modified. 6599 6599 *) 6600 - val v : checksum:string -> created_at:Ptime.t -> device_asset_id:string -> device_id:string -> duration:string -> file_created_at:Ptime.t -> file_modified_at:Ptime.t -> has_metadata:bool -> height:float -> id:string -> is_archived:bool -> is_edited:bool -> is_favorite:bool -> is_offline:bool -> is_trashed:bool -> local_date_time:Ptime.t -> original_file_name:string -> original_path:string -> owner_id:string -> thumbhash:string -> type_:Jsont.json -> updated_at:Ptime.t -> visibility:Jsont.json -> width:float -> ?duplicate_id:string -> ?exif_info:Exif.ResponseDto.t -> ?library_id:string -> ?live_photo_video_id:string -> ?original_mime_type:string -> ?owner:User.ResponseDto.t -> ?people:PersonWithFaces.ResponseDto.t list -> ?resized:bool -> ?stack:Jsont.json -> ?tags:Tag.ResponseDto.t list -> ?unassigned_faces:AssetFaceWithoutPerson.ResponseDto.t list -> unit -> t 6600 + val v : checksum:string -> created_at:Ptime.t -> device_asset_id:string -> device_id:string -> duration:string -> file_created_at:Ptime.t -> file_modified_at:Ptime.t -> has_metadata:bool -> id:string -> is_archived:bool -> is_edited:bool -> is_favorite:bool -> is_offline:bool -> is_trashed:bool -> local_date_time:Ptime.t -> original_file_name:string -> original_path:string -> owner_id:string -> type_:Jsont.json -> updated_at:Ptime.t -> visibility:Jsont.json -> ?duplicate_id:string -> ?exif_info:Exif.ResponseDto.t -> ?height:float -> ?library_id:string -> ?live_photo_video_id:string -> ?original_mime_type:string -> ?owner:User.ResponseDto.t -> ?people:PersonWithFaces.ResponseDto.t list -> ?resized:bool -> ?stack:Jsont.json -> ?tags:Tag.ResponseDto.t list -> ?thumbhash:string -> ?unassigned_faces:AssetFaceWithoutPerson.ResponseDto.t list -> ?width:float -> unit -> t 6601 6601 6602 6602 (** base64 encoded sha1 hash *) 6603 6603 val checksum : t -> string ··· 6623 6623 6624 6624 val has_metadata : t -> bool 6625 6625 6626 - val height : t -> float 6626 + val height : t -> float option 6627 6627 6628 6628 val id : t -> string 6629 6629 ··· 6662 6662 6663 6663 val tags : t -> Tag.ResponseDto.t list option 6664 6664 6665 - val thumbhash : t -> string 6665 + val thumbhash : t -> string option 6666 6666 6667 6667 val type_ : t -> Jsont.json 6668 6668 ··· 6673 6673 6674 6674 val visibility : t -> Jsont.json 6675 6675 6676 - val width : t -> float 6676 + val width : t -> float option 6677 6677 6678 6678 val jsont : t Jsont.t 6679 6679 end ··· 6819 6819 type t 6820 6820 6821 6821 (** Construct a value *) 6822 - val v : count:int -> facets:SearchFacet.ResponseDto.t list -> items:Asset.ResponseDto.t list -> next_page:string -> total:int -> unit -> t 6822 + val v : count:int -> facets:SearchFacet.ResponseDto.t list -> items:Asset.ResponseDto.t list -> total:int -> ?next_page:string -> unit -> t 6823 6823 6824 6824 val count : t -> int 6825 6825 ··· 6827 6827 6828 6828 val items : t -> Asset.ResponseDto.t list 6829 6829 6830 - val next_page : t -> string 6830 + val next_page : t -> string option 6831 6831 6832 6832 val total : t -> int 6833 6833 ··· 7114 7114 type t 7115 7115 7116 7116 (** Construct a value *) 7117 - val v : birth_date:string -> id:string -> is_hidden:bool -> name:string -> thumbnail_path:string -> ?color:string -> ?is_favorite:bool -> ?updated_at:Ptime.t -> unit -> t 7117 + val v : id:string -> is_hidden:bool -> name:string -> thumbnail_path:string -> ?birth_date:string -> ?color:string -> ?is_favorite:bool -> ?updated_at:Ptime.t -> unit -> t 7118 7118 7119 - val birth_date : t -> string 7119 + val birth_date : t -> string option 7120 7120 7121 7121 val color : t -> string option 7122 7122 ··· 7707 7707 type t 7708 7708 7709 7709 (** Construct a value *) 7710 - val v : album_name:string -> album_thumbnail_asset_id:string -> album_users:AlbumUser.ResponseDto.t list -> asset_count:int -> assets:Asset.ResponseDto.t list -> created_at:Ptime.t -> description:string -> has_shared_link:bool -> id:string -> is_activity_enabled:bool -> owner:User.ResponseDto.t -> owner_id:string -> shared:bool -> updated_at:Ptime.t -> ?contributor_counts:ContributorCount.ResponseDto.t list -> ?end_date:Ptime.t -> ?last_modified_asset_timestamp:Ptime.t -> ?order:Jsont.json -> ?start_date:Ptime.t -> unit -> t 7710 + val v : album_name:string -> album_users:AlbumUser.ResponseDto.t list -> asset_count:int -> assets:Asset.ResponseDto.t list -> created_at:Ptime.t -> description:string -> has_shared_link:bool -> id:string -> is_activity_enabled:bool -> owner:User.ResponseDto.t -> owner_id:string -> shared:bool -> updated_at:Ptime.t -> ?album_thumbnail_asset_id:string -> ?contributor_counts:ContributorCount.ResponseDto.t list -> ?end_date:Ptime.t -> ?last_modified_asset_timestamp:Ptime.t -> ?order:Jsont.json -> ?start_date:Ptime.t -> unit -> t 7711 7711 7712 7712 val album_name : t -> string 7713 7713 7714 - val album_thumbnail_asset_id : t -> string 7714 + val album_thumbnail_asset_id : t -> string option 7715 7715 7716 7716 val album_users : t -> AlbumUser.ResponseDto.t list 7717 7717 ··· 7793 7793 type t 7794 7794 7795 7795 (** Construct a value *) 7796 - val v : allow_download:bool -> allow_upload:bool -> assets:Asset.ResponseDto.t list -> created_at:Ptime.t -> description:string -> expires_at:Ptime.t -> id:string -> key:string -> password:string -> show_metadata:bool -> slug:string -> type_:Jsont.json -> user_id:string -> ?album:Album.ResponseDto.t -> ?token:string -> unit -> t 7796 + val v : allow_download:bool -> allow_upload:bool -> assets:Asset.ResponseDto.t list -> created_at:Ptime.t -> id:string -> key:string -> show_metadata:bool -> type_:Jsont.json -> user_id:string -> ?album:Album.ResponseDto.t -> ?description:string -> ?expires_at:Ptime.t -> ?password:string -> ?slug:string -> ?token:string -> unit -> t 7797 7797 7798 7798 val album : t -> Album.ResponseDto.t option 7799 7799 ··· 7805 7805 7806 7806 val created_at : t -> Ptime.t 7807 7807 7808 - val description : t -> string 7808 + val description : t -> string option 7809 7809 7810 - val expires_at : t -> Ptime.t 7810 + val expires_at : t -> Ptime.t option 7811 7811 7812 7812 val id : t -> string 7813 7813 7814 7814 val key : t -> string 7815 7815 7816 - val password : t -> string 7816 + val password : t -> string option 7817 7817 7818 7818 val show_metadata : t -> bool 7819 7819 7820 - val slug : t -> string 7820 + val slug : t -> string option 7821 7821 7822 7822 val token : t -> string option 7823 7823
+164
ocaml-immich/lib/client.ml
··· 1 + (*--------------------------------------------------------------------------- 2 + Copyright (c) 2025 Anil Madhavapeddy. All rights reserved. 3 + SPDX-License-Identifier: ISC 4 + ---------------------------------------------------------------------------*) 5 + 6 + type t = { 7 + client : Immich.t; 8 + session : Session.t; 9 + fs : Eio.Fs.dir_ty Eio.Path.t; 10 + profile : string option; 11 + } 12 + 13 + (* JSON type for .well-known/immich response *) 14 + let well_known_jsont = 15 + let api_obj = 16 + Jsont.Object.map ~kind:"api" (fun endpoint -> endpoint) 17 + |> Jsont.Object.mem "endpoint" Jsont.string ~enc:Fun.id 18 + |> Jsont.Object.finish 19 + in 20 + Jsont.Object.map ~kind:"well-known" (fun api -> api) 21 + |> Jsont.Object.mem "api" api_obj ~enc:Fun.id 22 + |> Jsont.Object.finish 23 + 24 + (** [resolve_api_url ~session base_url] resolves the actual API URL. 25 + 26 + If [base_url] already ends with [/api], returns it unchanged. 27 + Otherwise, tries to fetch [<base_url>/.well-known/immich] to discover 28 + the API endpoint. Falls back to [<base_url>/api] if discovery fails. *) 29 + let resolve_api_url ~session base_url = 30 + (* Remove trailing slash if present *) 31 + let base_url = 32 + if String.ends_with ~suffix:"/" base_url then 33 + String.sub base_url 0 (String.length base_url - 1) 34 + else base_url 35 + in 36 + (* If already has /api suffix, use as-is *) 37 + if String.ends_with ~suffix:"/api" base_url then 38 + base_url 39 + else begin 40 + (* Try .well-known/immich discovery *) 41 + let well_known_url = base_url ^ "/.well-known/immich" in 42 + try 43 + let response = Requests.get session well_known_url in 44 + if Requests.Response.ok response then begin 45 + let json = Requests.Response.json response in 46 + let endpoint = Openapi.Runtime.Json.decode_json_exn well_known_jsont json in 47 + (* Construct full API URL *) 48 + if String.starts_with ~prefix:"/" endpoint then 49 + base_url ^ endpoint 50 + else 51 + base_url ^ "/" ^ endpoint 52 + end 53 + else 54 + (* Discovery failed, default to /api *) 55 + base_url ^ "/api" 56 + with _ -> 57 + (* Any error, default to /api *) 58 + base_url ^ "/api" 59 + end 60 + 61 + let create_with_session ~sw ~env ?requests_config ?profile ~session () = 62 + let fs = env#fs in 63 + let server_url = Session.server_url session in 64 + (* Create a Requests session, optionally from cmdliner config *) 65 + let requests_session = match requests_config with 66 + | Some config -> Requests.Cmd.create config env sw 67 + | None -> Requests.create ~sw env 68 + in 69 + let requests_session = 70 + match Session.auth session with 71 + | Session.Jwt { access_token; _ } -> 72 + Requests.set_auth requests_session (Requests.Auth.bearer ~token:access_token) 73 + | Session.Api_key { key; _ } -> 74 + Requests.set_default_header requests_session "x-api-key" key 75 + in 76 + let client = Immich.create ~session:requests_session ~sw env ~base_url:server_url in 77 + { client; session; fs; profile } 78 + 79 + let login_api_key ~sw ~env ?requests_config ?profile ~server_url ~api_key ?key_name () = 80 + let fs = env#fs in 81 + (* Create session with API key header *) 82 + let requests_session = match requests_config with 83 + | Some config -> Requests.Cmd.create config env sw 84 + | None -> Requests.create ~sw env 85 + in 86 + let requests_session = Requests.set_default_header requests_session "x-api-key" api_key in 87 + (* Resolve the API URL from .well-known/immich if available *) 88 + let server_url = resolve_api_url ~session:requests_session server_url in 89 + let client = Immich.create ~session:requests_session ~sw env ~base_url:server_url in 90 + (* Validate by calling the validate endpoint *) 91 + let resp = Immich.ValidateAccessToken.validate_access_token client () in 92 + if not (Immich.ValidateAccessToken.ResponseDto.auth_status resp) then 93 + failwith "API key validation failed"; 94 + (* Create and save session *) 95 + let auth = Session.Api_key { key = api_key; name = key_name } in 96 + let session = Session.create ~server_url ~auth () in 97 + Session.save fs ?profile session; 98 + (* Set as current profile if first login or explicitly requested *) 99 + let profiles = Session.list_profiles fs in 100 + let profile_name = Option.value ~default:Session.default_profile profile in 101 + if profiles = [] || Option.is_some profile then 102 + Session.set_current_profile fs profile_name; 103 + { client; session; fs; profile } 104 + 105 + let login_password ~sw ~env ?requests_config ?profile ~server_url ~email ~password () = 106 + let fs = env#fs in 107 + (* Create session without auth first *) 108 + let requests_session = match requests_config with 109 + | Some config -> Requests.Cmd.create config env sw 110 + | None -> Requests.create ~sw env 111 + in 112 + (* Resolve the API URL from .well-known/immich if available *) 113 + let server_url = resolve_api_url ~session:requests_session server_url in 114 + let client = Immich.create ~session:requests_session ~sw env ~base_url:server_url in 115 + (* Login using the API *) 116 + let body = Immich.LoginCredential.Dto.v ~email ~password () in 117 + let resp = Immich.Login.login client ~body () in 118 + let access_token = Immich.Login.ResponseDto.access_token resp in 119 + let user_id = Immich.Login.ResponseDto.user_id resp in 120 + (* Now create a new client with the auth token *) 121 + let requests_session = match requests_config with 122 + | Some config -> Requests.Cmd.create config env sw 123 + | None -> Requests.create ~sw env 124 + in 125 + let requests_session = Requests.set_auth requests_session (Requests.Auth.bearer ~token:access_token) in 126 + let client = Immich.create ~session:requests_session ~sw env ~base_url:server_url in 127 + (* Create and save session *) 128 + let auth = Session.Jwt { access_token; user_id; email } in 129 + let session = Session.create ~server_url ~auth () in 130 + Session.save fs ?profile session; 131 + (* Set as current profile if first login or explicitly requested *) 132 + let profiles = Session.list_profiles fs in 133 + let profile_name = Option.value ~default:email profile in 134 + if profiles = [] || Option.is_some profile then 135 + Session.set_current_profile fs profile_name; 136 + { client; session; fs; profile } 137 + 138 + let resume ~sw ~env ?requests_config ?profile ~session () = 139 + (* Check if JWT is expired and refresh if needed *) 140 + let session = 141 + if Session.is_expired session then begin 142 + match Session.auth session with 143 + | Session.Api_key _ -> session (* API keys don't expire *) 144 + | Session.Jwt _ -> 145 + (* JWT expired - for now just fail, user needs to re-login *) 146 + failwith "Session expired. Please login again." 147 + end 148 + else session 149 + in 150 + create_with_session ~sw ~env ?requests_config ?profile ~session () 151 + 152 + let logout t = 153 + Session.clear t.fs ?profile:t.profile () 154 + 155 + let client t = t.client 156 + let session t = t.session 157 + let profile t = t.profile 158 + let fs t = t.fs 159 + 160 + let is_valid t = 161 + try 162 + let resp = Immich.ValidateAccessToken.validate_access_token t.client () in 163 + Immich.ValidateAccessToken.ResponseDto.auth_status resp 164 + with _ -> false
+142
ocaml-immich/lib/client.mli
··· 1 + (*--------------------------------------------------------------------------- 2 + Copyright (c) 2025 Anil Madhavapeddy. All rights reserved. 3 + SPDX-License-Identifier: ISC 4 + ---------------------------------------------------------------------------*) 5 + 6 + (** Authenticated Immich client with session persistence and profile support. 7 + 8 + This module provides a high-level client that wraps the generated 9 + {!Immich} module with authentication support. Sessions are stored in 10 + profile-specific directories. 11 + 12 + {2 Authentication Methods} 13 + 14 + {b API Key} (recommended for CLI): 15 + {[ 16 + let t = Client.login_api_key ~sw ~env 17 + ~server_url:"https://immich.example.com" 18 + ~api_key:"your-api-key" 19 + ~key_name:(Some "cli") 20 + () in 21 + let albums = Immich.Album.get_all_albums (Client.client t) () 22 + ]} 23 + 24 + {b Password} (for interactive use): 25 + {[ 26 + let t = Client.login_password ~sw ~env 27 + ~server_url:"https://immich.example.com" 28 + ~email:"user@example.com" 29 + ~password:"secret" 30 + () in 31 + ... 32 + ]} 33 + 34 + {2 Resuming Sessions} 35 + 36 + {[ 37 + match Session.load fs () with 38 + | Some session -> 39 + let t = Client.resume ~sw ~env ~session () in 40 + ... 41 + | None -> 42 + Fmt.epr "Not logged in@." 43 + ]} *) 44 + 45 + type t 46 + (** Authenticated client state. *) 47 + 48 + (** {1 Authentication} *) 49 + 50 + val login_api_key : 51 + sw:Eio.Switch.t -> 52 + env:< clock : _ Eio.Time.clock 53 + ; net : _ Eio.Net.t 54 + ; fs : Eio.Fs.dir_ty Eio.Path.t 55 + ; .. > -> 56 + ?requests_config:Requests.Cmd.config -> 57 + ?profile:string -> 58 + server_url:string -> 59 + api_key:string -> 60 + ?key_name:string -> 61 + unit -> 62 + t 63 + (** [login_api_key ~sw ~env ?requests_config ?profile ~server_url ~api_key ?key_name ()] 64 + authenticates using an API key. The API key is sent in the [x-api-key] 65 + header for all requests. 66 + 67 + The [server_url] can be either the full API URL (e.g., [https://example.com/api]) 68 + or the base server URL (e.g., [https://example.com]). If the base URL is 69 + provided, the client automatically discovers the API endpoint via 70 + [.well-known/immich]. 71 + 72 + @param requests_config Optional Requests.Cmd.config for HTTP settings 73 + @param profile Profile name (default: "default") 74 + @param key_name Optional name for the API key (for display purposes) *) 75 + 76 + val login_password : 77 + sw:Eio.Switch.t -> 78 + env:< clock : _ Eio.Time.clock 79 + ; net : _ Eio.Net.t 80 + ; fs : Eio.Fs.dir_ty Eio.Path.t 81 + ; .. > -> 82 + ?requests_config:Requests.Cmd.config -> 83 + ?profile:string -> 84 + server_url:string -> 85 + email:string -> 86 + password:string -> 87 + unit -> 88 + t 89 + (** [login_password ~sw ~env ?requests_config ?profile ~server_url ~email ~password ()] 90 + authenticates using email and password. Returns a JWT access token. 91 + 92 + @param requests_config Optional Requests.Cmd.config for HTTP settings 93 + @param profile Profile name (default: email address) *) 94 + 95 + val resume : 96 + sw:Eio.Switch.t -> 97 + env:< clock : _ Eio.Time.clock 98 + ; net : _ Eio.Net.t 99 + ; fs : Eio.Fs.dir_ty Eio.Path.t 100 + ; .. > -> 101 + ?requests_config:Requests.Cmd.config -> 102 + ?profile:string -> 103 + session:Session.t -> 104 + unit -> 105 + t 106 + (** [resume ~sw ~env ?requests_config ?profile ~session ()] resumes from a saved session. 107 + 108 + @param requests_config Optional Requests.Cmd.config for HTTP settings 109 + @raise Failure if the JWT session is expired *) 110 + 111 + val logout : t -> unit 112 + (** [logout t] clears the session from disk. *) 113 + 114 + (** {1 Client Access} *) 115 + 116 + val client : t -> Immich.t 117 + (** [client t] returns the underlying Immich client for API calls. *) 118 + 119 + val session : t -> Session.t 120 + (** [session t] returns the current session. *) 121 + 122 + val profile : t -> string option 123 + (** [profile t] returns the current profile name, if set. *) 124 + 125 + val fs : t -> Eio.Fs.dir_ty Eio.Path.t 126 + (** [fs t] returns the filesystem capability. *) 127 + 128 + val is_valid : t -> bool 129 + (** [is_valid t] returns [true] if the session is still valid by calling 130 + the validate endpoint. *) 131 + 132 + (** {1 URL Resolution} *) 133 + 134 + val resolve_api_url : session:Requests.t -> string -> string 135 + (** [resolve_api_url ~session base_url] resolves the actual API URL. 136 + 137 + If [base_url] already ends with [/api], returns it unchanged. 138 + Otherwise, tries to fetch [<base_url>/.well-known/immich] to discover 139 + the API endpoint. Falls back to [<base_url>/api] if discovery fails. 140 + 141 + This function is called automatically by {!login_api_key} and 142 + {!login_password}, but is exposed for advanced use cases. *)
+286
ocaml-immich/lib/cmd.ml
··· 1 + (*--------------------------------------------------------------------------- 2 + Copyright (c) 2025 Anil Madhavapeddy. All rights reserved. 3 + SPDX-License-Identifier: ISC 4 + ---------------------------------------------------------------------------*) 5 + 6 + open Cmdliner 7 + 8 + let app_name = "immich" 9 + 10 + (* Common Arguments *) 11 + 12 + let server_arg = 13 + let doc = "Immich server URL." in 14 + let env = Cmd.Env.info "IMMICH_SERVER" ~doc in 15 + Arg.(required & pos 0 (some string) None & info [] ~env ~docv:"SERVER" ~doc) 16 + 17 + let server_opt = 18 + let doc = "Immich server URL." in 19 + let env = Cmd.Env.info "IMMICH_SERVER" ~doc in 20 + Arg.(value & opt (some string) None & info ["server"; "s"] ~env ~docv:"URL" ~doc) 21 + 22 + let api_key_arg = 23 + let doc = "API key (will prompt if not provided and not using password auth)." in 24 + let env = Cmd.Env.info "IMMICH_API_KEY" ~doc in 25 + Arg.(value & opt (some string) None & info ["api-key"; "k"] ~env ~docv:"KEY" ~doc) 26 + 27 + let email_arg = 28 + let doc = "Email for password authentication." in 29 + Arg.(value & opt (some string) None & info ["email"; "e"] ~docv:"EMAIL" ~doc) 30 + 31 + let password_arg = 32 + let doc = "Password (will prompt if using email auth and not provided)." in 33 + Arg.(value & opt (some string) None & info ["password"; "p"] ~docv:"PASSWORD" ~doc) 34 + 35 + let profile_arg = 36 + let doc = "Profile name (default: current profile)." in 37 + Arg.(value & opt (some string) None & info ["profile"; "P"] ~docv:"PROFILE" ~doc) 38 + 39 + let key_name_arg = 40 + let doc = "Name for the API key (for display purposes)." in 41 + Arg.(value & opt (some string) None & info ["name"; "n"] ~docv:"NAME" ~doc) 42 + 43 + (* Logging setup - takes requests_config to extract verbose_http *) 44 + 45 + let setup_logging_with_config style_renderer level (requests_config : Requests.Cmd.config) = 46 + Fmt_tty.setup_std_outputs ?style_renderer (); 47 + Logs.set_level level; 48 + Logs.set_reporter (Logs_fmt.reporter ()); 49 + Requests.Cmd.setup_log_sources ~verbose_http:requests_config.verbose_http.value level 50 + 51 + let setup_logging_simple style_renderer level = 52 + Fmt_tty.setup_std_outputs ?style_renderer (); 53 + Logs.set_level level; 54 + Logs.set_reporter (Logs_fmt.reporter ()) 55 + 56 + let setup_logging = 57 + Term.(const (fun style_renderer level -> (style_renderer, level)) 58 + $ Fmt_cli.style_renderer () 59 + $ Logs_cli.level ()) 60 + 61 + (* Requests config term *) 62 + 63 + let requests_config_term fs = 64 + Requests.Cmd.config_term app_name fs 65 + 66 + (* Session helper *) 67 + 68 + let with_session ?profile f env = 69 + let fs = env#fs in 70 + match Session.load fs ?profile () with 71 + | None -> 72 + let profile_msg = 73 + match profile with 74 + | Some p -> Printf.sprintf " (profile: %s)" p 75 + | None -> 76 + let current = Session.get_current_profile fs in 77 + Printf.sprintf " (profile: %s)" current 78 + in 79 + Fmt.epr "Not logged in%s. Use 'immich auth login' first.@." profile_msg; 80 + exit 1 81 + | Some session -> f fs session 82 + 83 + let with_client ?requests_config ?profile f env = 84 + with_session ?profile (fun fs session -> 85 + Eio.Switch.run @@ fun sw -> 86 + let client = Client.resume ~sw ~env ?requests_config ?profile ~session () in 87 + f fs client 88 + ) env 89 + 90 + (* Login command *) 91 + 92 + let login_action ~requests_config ~server ~api_key ~email ~password ~profile ~key_name env = 93 + match (api_key, email) with 94 + | None, None -> 95 + (* No auth method specified, prompt for API key *) 96 + Fmt.pr "API Key: @?"; 97 + let api_key = read_line () in 98 + Eio.Switch.run @@ fun sw -> 99 + let client = Client.login_api_key ~sw ~env ~requests_config ?profile ~server_url:server ~api_key ?key_name () in 100 + let profile_name = Option.value ~default:Session.default_profile profile in 101 + Fmt.pr "Logged in to %s (profile: %s)@." server profile_name; 102 + ignore client 103 + | Some api_key, None -> 104 + Eio.Switch.run @@ fun sw -> 105 + let client = Client.login_api_key ~sw ~env ~requests_config ?profile ~server_url:server ~api_key ?key_name () in 106 + let profile_name = Option.value ~default:Session.default_profile profile in 107 + Fmt.pr "Logged in to %s (profile: %s)@." server profile_name; 108 + ignore client 109 + | None, Some email -> 110 + let password = 111 + match password with 112 + | Some p -> p 113 + | None -> 114 + Fmt.pr "Password: @?"; 115 + read_line () 116 + in 117 + Eio.Switch.run @@ fun sw -> 118 + let client = Client.login_password ~sw ~env ~requests_config ?profile ~server_url:server ~email ~password () in 119 + let profile_name = Option.value ~default:email profile in 120 + Fmt.pr "Logged in as %s (profile: %s)@." email profile_name; 121 + ignore client 122 + | Some _, Some _ -> 123 + Fmt.epr "Cannot specify both --api-key and --email. Choose one authentication method.@."; 124 + exit 1 125 + 126 + let login_cmd env fs = 127 + let doc = "Login to an Immich server." in 128 + let info = Cmd.info "login" ~doc in 129 + let login' (style_renderer, level) requests_config server api_key email password profile key_name = 130 + setup_logging_with_config style_renderer level requests_config; 131 + login_action ~requests_config ~server ~api_key ~email ~password ~profile ~key_name env 132 + in 133 + Cmd.v info 134 + Term.(const login' $ setup_logging $ requests_config_term fs $ server_arg $ api_key_arg $ email_arg $ password_arg $ profile_arg $ key_name_arg) 135 + 136 + (* Logout command *) 137 + 138 + let logout_action ~profile env = 139 + let fs = env#fs in 140 + match Session.load fs ?profile () with 141 + | None -> Fmt.pr "Not logged in.@." 142 + | Some session -> 143 + Session.clear fs ?profile (); 144 + let profile_name = 145 + match profile with 146 + | Some p -> p 147 + | None -> Session.get_current_profile fs 148 + in 149 + Fmt.pr "Logged out from %s (profile: %s).@." (Session.server_url session) profile_name 150 + 151 + let logout_cmd env = 152 + let doc = "Logout and clear saved session." in 153 + let info = Cmd.info "logout" ~doc in 154 + let logout' (style_renderer, level) profile = 155 + setup_logging_simple style_renderer level; 156 + logout_action ~profile env 157 + in 158 + Cmd.v info Term.(const logout' $ setup_logging $ profile_arg) 159 + 160 + (* Status command *) 161 + 162 + let status_action ~profile env = 163 + let fs = env#fs in 164 + let home = Sys.getenv "HOME" in 165 + Fmt.pr "Config directory: %s/.config/immich@." home; 166 + let current = Session.get_current_profile fs in 167 + Fmt.pr "Current profile: %s@." current; 168 + let profiles = Session.list_profiles fs in 169 + if profiles <> [] then begin 170 + Fmt.pr "Available profiles: %s@." (String.concat ", " profiles) 171 + end; 172 + Fmt.pr "@."; 173 + let profile = Option.value ~default:current profile in 174 + match Session.load fs ~profile () with 175 + | None -> Fmt.pr "Profile '%s': Not logged in.@." profile 176 + | Some session -> 177 + Fmt.pr "Profile '%s':@." profile; 178 + Fmt.pr " %a@." Session.pp session; 179 + if Session.is_expired session then 180 + Fmt.pr " (token expired, please login again)@." 181 + 182 + let auth_status_cmd env = 183 + let doc = "Show authentication status." in 184 + let info = Cmd.info "status" ~doc in 185 + let status' (style_renderer, level) profile = 186 + setup_logging_simple style_renderer level; 187 + status_action ~profile env 188 + in 189 + Cmd.v info Term.(const status' $ setup_logging $ profile_arg) 190 + 191 + (* Profile list command *) 192 + 193 + let profile_list_action env = 194 + let fs = env#fs in 195 + let current = Session.get_current_profile fs in 196 + let profiles = Session.list_profiles fs in 197 + if profiles = [] then 198 + Fmt.pr "No profiles found. Use 'immich auth login' to create one.@." 199 + else begin 200 + Fmt.pr "Profiles:@."; 201 + List.iter 202 + (fun p -> 203 + let marker = if p = current then " (current)" else "" in 204 + match Session.load fs ~profile:p () with 205 + | Some session -> 206 + Fmt.pr " %s%s - %s@." p marker (Session.server_url session) 207 + | None -> Fmt.pr " %s%s@." p marker) 208 + profiles 209 + end 210 + 211 + let profile_list_cmd env = 212 + let doc = "List available profiles." in 213 + let info = Cmd.info "list" ~doc in 214 + let list' (style_renderer, level) () = 215 + setup_logging_simple style_renderer level; 216 + profile_list_action env 217 + in 218 + Cmd.v info Term.(const list' $ setup_logging $ const ()) 219 + 220 + (* Profile switch command *) 221 + 222 + let profile_name_arg = 223 + let doc = "Profile name to switch to." in 224 + Arg.(required & pos 0 (some string) None & info [] ~docv:"PROFILE" ~doc) 225 + 226 + let profile_switch_action ~profile env = 227 + let fs = env#fs in 228 + let profiles = Session.list_profiles fs in 229 + if List.mem profile profiles then begin 230 + Session.set_current_profile fs profile; 231 + Fmt.pr "Switched to profile: %s@." profile 232 + end 233 + else begin 234 + Fmt.epr "Profile '%s' not found.@." profile; 235 + if profiles <> [] then 236 + Fmt.epr "Available profiles: %s@." (String.concat ", " profiles); 237 + exit 1 238 + end 239 + 240 + let profile_switch_cmd env = 241 + let doc = "Switch to a different profile." in 242 + let info = Cmd.info "switch" ~doc in 243 + let switch' (style_renderer, level) profile = 244 + setup_logging_simple style_renderer level; 245 + profile_switch_action ~profile env 246 + in 247 + Cmd.v info Term.(const switch' $ setup_logging $ profile_name_arg) 248 + 249 + (* Profile current command *) 250 + 251 + let profile_current_action env = 252 + let fs = env#fs in 253 + let current = Session.get_current_profile fs in 254 + Fmt.pr "%s@." current 255 + 256 + let profile_current_cmd env = 257 + let doc = "Show current profile name." in 258 + let info = Cmd.info "current" ~doc in 259 + let current' (style_renderer, level) () = 260 + setup_logging_simple style_renderer level; 261 + profile_current_action env 262 + in 263 + Cmd.v info Term.(const current' $ setup_logging $ const ()) 264 + 265 + (* Profile command group *) 266 + 267 + let profile_cmd env = 268 + let doc = "Profile management commands." in 269 + let info = Cmd.info "profile" ~doc in 270 + Cmd.group info 271 + [ profile_list_cmd env 272 + ; profile_switch_cmd env 273 + ; profile_current_cmd env 274 + ] 275 + 276 + (* Auth command group *) 277 + 278 + let auth_cmd env fs = 279 + let doc = "Authentication commands." in 280 + let info = Cmd.info "auth" ~doc in 281 + Cmd.group info 282 + [ login_cmd env fs 283 + ; logout_cmd env 284 + ; auth_status_cmd env 285 + ; profile_cmd env 286 + ]
+117
ocaml-immich/lib/cmd.mli
··· 1 + (*--------------------------------------------------------------------------- 2 + Copyright (c) 2025 Anil Madhavapeddy. All rights reserved. 3 + SPDX-License-Identifier: ISC 4 + ---------------------------------------------------------------------------*) 5 + 6 + (** Cmdliner helpers for Immich CLI authentication. 7 + 8 + This module provides reusable command-line argument definitions and command 9 + builders for authentication workflows. It supports multiple profiles for 10 + managing multiple server connections. 11 + 12 + {2 Usage} 13 + 14 + {[ 15 + (* In your main.ml *) 16 + Eio_main.run @@ fun env -> 17 + let fs = env#fs in 18 + Cmd.group (Cmd.info "immich") 19 + [ Cmd.auth_cmd env fs 20 + ; (* other commands *) 21 + ] 22 + |> Cmd.eval_exn 23 + ]} 24 + 25 + {2 Environment Variables} 26 + 27 + - [IMMICH_SERVER]: Default server URL 28 + - [IMMICH_API_KEY]: Default API key 29 + 30 + {2 Logging} 31 + 32 + Uses Logs_cli for verbosity control: 33 + - [-v] or [--verbose]: Info level logging 34 + - [-v -v] or [--verbosity=debug]: Debug level logging 35 + - [--verbose-http]: Enable verbose HTTP protocol logging *) 36 + 37 + open Cmdliner 38 + 39 + (** {1 Common Arguments} *) 40 + 41 + val server_arg : string Term.t 42 + (** Required positional argument for server URL (also reads [IMMICH_SERVER]). *) 43 + 44 + val server_opt : string option Term.t 45 + (** Optional [--server] argument (also reads [IMMICH_SERVER]). *) 46 + 47 + val api_key_arg : string option Term.t 48 + (** Optional [--api-key] argument (also reads [IMMICH_API_KEY]). *) 49 + 50 + val email_arg : string option Term.t 51 + (** Optional [--email] argument for password authentication. *) 52 + 53 + val password_arg : string option Term.t 54 + (** Optional [--password] argument. *) 55 + 56 + val profile_arg : string option Term.t 57 + (** Optional [--profile] argument for selecting a specific profile. *) 58 + 59 + val key_name_arg : string option Term.t 60 + (** Optional [--name] argument for naming API keys. *) 61 + 62 + (** {1 Logging and Configuration} *) 63 + 64 + val setup_logging : (Fmt.style_renderer option * Logs.level option) Term.t 65 + (** Term that collects logging options ([-v], [--color], etc.). 66 + Use with [setup_logging_with_config] to apply logging after parsing. *) 67 + 68 + val setup_logging_with_config : 69 + Fmt.style_renderer option -> 70 + Logs.level option -> 71 + Requests.Cmd.config -> 72 + unit 73 + (** [setup_logging_with_config style_renderer level config] sets up logging 74 + with the given options. Extracts [--verbose-http] from the requests config. 75 + Call this at the start of command execution. *) 76 + 77 + val requests_config_term : Eio.Fs.dir_ty Eio.Path.t -> Requests.Cmd.config Term.t 78 + (** Term for HTTP request configuration (timeouts, retries, proxy, etc.). *) 79 + 80 + (** {1 Commands} 81 + 82 + Commands take an [env] parameter from the outer [Eio_main.run] context, 83 + and an [fs] path for building cmdliner terms. *) 84 + 85 + val auth_cmd : 86 + < fs : Eio.Fs.dir_ty Eio.Path.t 87 + ; clock : _ Eio.Time.clock 88 + ; net : _ Eio.Net.t 89 + ; .. > -> 90 + Eio.Fs.dir_ty Eio.Path.t -> 91 + unit Cmd.t 92 + (** Complete auth command group combining login, logout, status, and profile. *) 93 + 94 + (** {1 Helper Functions} *) 95 + 96 + val with_session : 97 + ?profile:string -> 98 + (Eio.Fs.dir_ty Eio.Path.t -> Session.t -> 'a) -> 99 + < fs : Eio.Fs.dir_ty Eio.Path.t ; .. > -> 100 + 'a 101 + (** [with_session ?profile f env] loads the session and calls 102 + [f fs session]. Prints an error and exits if not logged in. 103 + @param profile Profile to load (default: current profile) *) 104 + 105 + val with_client : 106 + ?requests_config:Requests.Cmd.config -> 107 + ?profile:string -> 108 + (Eio.Fs.dir_ty Eio.Path.t -> Client.t -> 'a) -> 109 + < fs : Eio.Fs.dir_ty Eio.Path.t 110 + ; clock : _ Eio.Time.clock 111 + ; net : _ Eio.Net.t 112 + ; .. > -> 113 + 'a 114 + (** [with_client ?requests_config ?profile f env] loads the session, creates a client, and calls 115 + [f fs client]. Prints an error and exits if not logged in. 116 + @param requests_config HTTP request configuration 117 + @param profile Profile to load (default: current profile) *)
+19
ocaml-immich/lib/dune
··· 1 + (library 2 + (name immich_auth) 3 + (libraries 4 + immich 5 + requests 6 + jsont 7 + jsont.bytesrw 8 + base64 9 + eio 10 + eio_main 11 + ptime 12 + ptime.clock.os 13 + fmt 14 + fmt.tty 15 + fmt.cli 16 + logs 17 + logs.fmt 18 + logs.cli 19 + cmdliner))
+13
ocaml-immich/lib/immich_auth.ml
··· 1 + (*--------------------------------------------------------------------------- 2 + Copyright (c) 2025 Anil Madhavapeddy. All rights reserved. 3 + SPDX-License-Identifier: ISC 4 + ---------------------------------------------------------------------------*) 5 + 6 + (** Immich authentication library. 7 + 8 + This library provides authentication support for the Immich API client, 9 + supporting both API keys and JWT tokens with profile-based session management. *) 10 + 11 + module Session = Session 12 + module Client = Client 13 + module Cmd = Cmd
+245
ocaml-immich/lib/session.ml
··· 1 + (*--------------------------------------------------------------------------- 2 + Copyright (c) 2025 Anil Madhavapeddy. All rights reserved. 3 + SPDX-License-Identifier: ISC 4 + ---------------------------------------------------------------------------*) 5 + 6 + (** Authentication method for Immich. *) 7 + type auth_method = 8 + | Jwt of { access_token : string; user_id : string; email : string } 9 + | Api_key of { key : string; name : string option } 10 + 11 + type t = { 12 + server_url : string; 13 + auth : auth_method; 14 + created_at : string; 15 + } 16 + 17 + let auth_method_jsont = 18 + let jwt_jsont = 19 + Jsont.Object.map ~kind:"Jwt" 20 + (fun access_token user_id email -> 21 + Jwt { access_token; user_id; email }) 22 + |> Jsont.Object.mem "access_token" Jsont.string ~enc:(function 23 + | Jwt { access_token; _ } -> access_token 24 + | Api_key _ -> "") 25 + |> Jsont.Object.mem "user_id" Jsont.string ~enc:(function 26 + | Jwt { user_id; _ } -> user_id 27 + | Api_key _ -> "") 28 + |> Jsont.Object.mem "email" Jsont.string ~enc:(function 29 + | Jwt { email; _ } -> email 30 + | Api_key _ -> "") 31 + |> Jsont.Object.finish 32 + in 33 + let api_key_jsont = 34 + Jsont.Object.map ~kind:"ApiKey" 35 + (fun key name -> Api_key { key; name }) 36 + |> Jsont.Object.mem "key" Jsont.string ~enc:(function 37 + | Api_key { key; _ } -> key 38 + | Jwt _ -> "") 39 + |> Jsont.Object.opt_mem "name" Jsont.string ~enc:(function 40 + | Api_key { name; _ } -> name 41 + | Jwt _ -> None) 42 + |> Jsont.Object.finish 43 + in 44 + Jsont.Object.map ~kind:"AuthMethod" 45 + (fun type_ jwt api_key -> 46 + match type_ with 47 + | "jwt" -> Option.get jwt 48 + | "api_key" -> Option.get api_key 49 + | _ -> failwith ("Unknown auth type: " ^ type_)) 50 + |> Jsont.Object.mem "type" Jsont.string ~enc:(function 51 + | Jwt _ -> "jwt" 52 + | Api_key _ -> "api_key") 53 + |> Jsont.Object.opt_mem "jwt" jwt_jsont ~enc:(function 54 + | Jwt _ as j -> Some j 55 + | Api_key _ -> None) 56 + |> Jsont.Object.opt_mem "api_key" api_key_jsont ~enc:(function 57 + | Api_key _ as a -> Some a 58 + | Jwt _ -> None) 59 + |> Jsont.Object.finish 60 + 61 + let jsont = 62 + Jsont.Object.map ~kind:"Session" 63 + (fun server_url auth created_at -> { server_url; auth; created_at }) 64 + |> Jsont.Object.mem "server_url" Jsont.string ~enc:(fun s -> s.server_url) 65 + |> Jsont.Object.mem "auth" auth_method_jsont ~enc:(fun s -> s.auth) 66 + |> Jsont.Object.mem "created_at" Jsont.string ~enc:(fun s -> s.created_at) 67 + |> Jsont.Object.finish 68 + 69 + (* App config stores the current profile *) 70 + type app_config = { current_profile : string } 71 + 72 + let app_config_jsont = 73 + Jsont.Object.map ~kind:"AppConfig" (fun current_profile -> 74 + { current_profile }) 75 + |> Jsont.Object.mem "current_profile" Jsont.string ~enc:(fun c -> 76 + c.current_profile) 77 + |> Jsont.Object.finish 78 + 79 + let default_profile = "default" 80 + let app_name = "immich" 81 + 82 + (* Base config directory for the app *) 83 + let base_config_dir fs = 84 + let home = Sys.getenv "HOME" in 85 + let config_path = Eio.Path.(fs / home / ".config" / app_name) in 86 + (try Eio.Path.mkdir ~perm:0o700 config_path 87 + with Eio.Io (Eio.Fs.E (Eio.Fs.Already_exists _), _) -> ()); 88 + config_path 89 + 90 + (* Profiles directory *) 91 + let profiles_dir fs = 92 + let base = base_config_dir fs in 93 + let profiles = Eio.Path.(base / "profiles") in 94 + (try Eio.Path.mkdir ~perm:0o700 profiles 95 + with Eio.Io (Eio.Fs.E (Eio.Fs.Already_exists _), _) -> ()); 96 + profiles 97 + 98 + (* Config directory for a specific profile *) 99 + let config_dir fs ?profile () = 100 + let profile_name = Option.value ~default:default_profile profile in 101 + let profiles = profiles_dir fs in 102 + let profile_dir = Eio.Path.(profiles / profile_name) in 103 + (try Eio.Path.mkdir ~perm:0o700 profile_dir 104 + with Eio.Io (Eio.Fs.E (Eio.Fs.Already_exists _), _) -> ()); 105 + profile_dir 106 + 107 + (* App config file (stores current profile) *) 108 + let app_config_file fs = 109 + Eio.Path.(base_config_dir fs / "config.json") 110 + 111 + let load_app_config fs = 112 + let path = app_config_file fs in 113 + try 114 + Eio.Path.load path 115 + |> Jsont_bytesrw.decode_string app_config_jsont 116 + |> Result.to_option 117 + with Eio.Io (Eio.Fs.E (Eio.Fs.Not_found _), _) -> None 118 + 119 + let save_app_config fs config = 120 + let path = app_config_file fs in 121 + match 122 + Jsont_bytesrw.encode_string ~format:Jsont.Indent app_config_jsont config 123 + with 124 + | Ok content -> Eio.Path.save ~create:(`Or_truncate 0o600) path content 125 + | Error e -> failwith ("Failed to encode app config: " ^ e) 126 + 127 + (* Get the current profile name *) 128 + let get_current_profile fs = 129 + match load_app_config fs with 130 + | Some config -> config.current_profile 131 + | None -> default_profile 132 + 133 + (* Set the current profile *) 134 + let set_current_profile fs profile = 135 + save_app_config fs { current_profile = profile } 136 + 137 + (* List all available profiles *) 138 + let list_profiles fs = 139 + let profiles = profiles_dir fs in 140 + try 141 + Eio.Path.read_dir profiles 142 + |> List.filter (fun name -> 143 + (* Check if it's a directory with a session.json *) 144 + let dir = Eio.Path.(profiles / name) in 145 + let session = Eio.Path.(dir / "session.json") in 146 + try 147 + ignore (Eio.Path.load session); 148 + true 149 + with _ -> false) 150 + |> List.sort String.compare 151 + with Eio.Io (Eio.Fs.E (Eio.Fs.Not_found _), _) -> [] 152 + 153 + (* Session file within a profile directory *) 154 + let session_file fs ?profile () = 155 + Eio.Path.(config_dir fs ?profile () / "session.json") 156 + 157 + let load fs ?profile () = 158 + let profile = 159 + match profile with 160 + | Some p -> Some p 161 + | None -> 162 + (* Use current profile if none specified *) 163 + let current = get_current_profile fs in 164 + Some current 165 + in 166 + let path = session_file fs ?profile () in 167 + try 168 + Eio.Path.load path |> Jsont_bytesrw.decode_string jsont |> Result.to_option 169 + with Eio.Io (Eio.Fs.E (Eio.Fs.Not_found _), _) -> None 170 + 171 + let save fs ?profile session = 172 + let profile = 173 + match profile with 174 + | Some p -> Some p 175 + | None -> Some (get_current_profile fs) 176 + in 177 + let path = session_file fs ?profile () in 178 + match Jsont_bytesrw.encode_string ~format:Jsont.Indent jsont session with 179 + | Ok content -> Eio.Path.save ~create:(`Or_truncate 0o600) path content 180 + | Error e -> failwith ("Failed to encode session: " ^ e) 181 + 182 + let clear fs ?profile () = 183 + let profile = 184 + match profile with 185 + | Some p -> Some p 186 + | None -> Some (get_current_profile fs) 187 + in 188 + let path = session_file fs ?profile () in 189 + try Eio.Path.unlink path 190 + with Eio.Io (Eio.Fs.E (Eio.Fs.Not_found _), _) -> () 191 + 192 + (* JWT payload type for expiration check *) 193 + type jwt_payload = { exp : float option } 194 + 195 + let jwt_payload_jsont = 196 + Jsont.Object.map ~kind:"JwtPayload" (fun exp -> { exp }) 197 + |> Jsont.Object.opt_mem "exp" Jsont.number ~enc:(fun p -> p.exp) 198 + |> Jsont.Object.skip_unknown 199 + |> Jsont.Object.finish 200 + 201 + (* JWT expiration check using base64 decoding *) 202 + let is_jwt_expired ?(leeway = 60) token = 203 + try 204 + let parts = String.split_on_char '.' token in 205 + if List.length parts < 2 then true 206 + else begin 207 + let payload = List.nth parts 1 in 208 + (* Add padding if needed *) 209 + let padding = String.length payload mod 4 in 210 + let padded = 211 + if padding > 0 then payload ^ String.make (4 - padding) '=' 212 + else payload 213 + in 214 + let decoded = Base64.decode_exn ~alphabet:Base64.uri_safe_alphabet padded in 215 + (* Parse the JSON payload to find exp *) 216 + match Jsont_bytesrw.decode_string jwt_payload_jsont decoded with 217 + | Ok { exp = Some exp_time } -> 218 + let now = Ptime.to_float_s (Ptime_clock.now ()) in 219 + now >= exp_time -. (Float.of_int leeway) 220 + | Ok { exp = None } -> true 221 + | Error _ -> true 222 + end 223 + with _ -> true 224 + 225 + let is_expired ?leeway session = 226 + match session.auth with 227 + | Jwt { access_token; _ } -> is_jwt_expired ?leeway access_token 228 + | Api_key _ -> false (* API keys don't expire *) 229 + 230 + let pp ppf session = 231 + match session.auth with 232 + | Jwt { user_id; email; _ } -> 233 + Fmt.pf ppf "@[<v>Email: %s@,User ID: %s@,Server: %s@,Created: %s@,Auth: JWT@]" 234 + email user_id session.server_url session.created_at 235 + | Api_key { name; _ } -> 236 + let name_str = Option.value ~default:"<unnamed>" name in 237 + Fmt.pf ppf "@[<v>API Key: %s@,Server: %s@,Created: %s@,Auth: API Key@]" 238 + name_str session.server_url session.created_at 239 + 240 + let server_url t = t.server_url 241 + let auth t = t.auth 242 + let created_at t = t.created_at 243 + 244 + let create ~server_url ~auth () = 245 + { server_url; auth; created_at = Ptime.to_rfc3339 (Ptime_clock.now ()) }
+128
ocaml-immich/lib/session.mli
··· 1 + (*--------------------------------------------------------------------------- 2 + Copyright (c) 2025 Anil Madhavapeddy. All rights reserved. 3 + SPDX-License-Identifier: ISC 4 + ---------------------------------------------------------------------------*) 5 + 6 + (** Session management for Immich CLI with profile support. 7 + 8 + This module provides session persistence for Immich authentication, 9 + supporting both JWT tokens and API keys. Sessions are stored in 10 + profile-specific directories under [~/.config/immich/profiles/<profile>/session.json]. 11 + 12 + {2 Directory Structure} 13 + 14 + {v 15 + ~/.config/immich/ 16 + config.json # Stores current_profile setting 17 + profiles/ 18 + default/ 19 + session.json # Session for "default" profile 20 + home/ 21 + session.json # Session for "home" profile 22 + work/ 23 + session.json # Session for "work" profile 24 + v} 25 + 26 + {2 Authentication Methods} 27 + 28 + Immich supports two authentication methods: 29 + - {b JWT}: Obtained via email/password login. Tokens expire and need refresh. 30 + - {b API Key}: Created in Immich settings. Never expires, simpler for CLI use. 31 + 32 + {[ 33 + (* Login with API key *) 34 + let session = Session.create 35 + ~server_url:"https://immich.example.com" 36 + ~auth:(Api_key { key = "xxx"; name = Some "cli" }) 37 + () in 38 + Session.save fs ~profile:"home" session 39 + ]} *) 40 + 41 + (** {1 Types} *) 42 + 43 + type auth_method = 44 + | Jwt of { access_token : string; user_id : string; email : string } 45 + | Api_key of { key : string; name : string option } 46 + (** Authentication method. API keys are preferred for CLI use as they 47 + don't expire. *) 48 + 49 + type t 50 + (** Session data. *) 51 + 52 + val jsont : t Jsont.t 53 + (** JSON codec for sessions. *) 54 + 55 + (** {1 Session Construction} *) 56 + 57 + val create : server_url:string -> auth:auth_method -> unit -> t 58 + (** [create ~server_url ~auth ()] creates a new session with the current timestamp. *) 59 + 60 + (** {1 Session Accessors} *) 61 + 62 + val server_url : t -> string 63 + (** [server_url t] returns the server URL. *) 64 + 65 + val auth : t -> auth_method 66 + (** [auth t] returns the authentication method. *) 67 + 68 + val created_at : t -> string 69 + (** [created_at t] returns the creation timestamp (RFC 3339). *) 70 + 71 + (** {1 Profile Management} *) 72 + 73 + val default_profile : string 74 + (** The default profile name (["default"]). *) 75 + 76 + val get_current_profile : Eio.Fs.dir_ty Eio.Path.t -> string 77 + (** [get_current_profile fs] returns the current profile name. Returns 78 + {!default_profile} if no profile has been set. *) 79 + 80 + val set_current_profile : Eio.Fs.dir_ty Eio.Path.t -> string -> unit 81 + (** [set_current_profile fs profile] sets the current profile. *) 82 + 83 + val list_profiles : Eio.Fs.dir_ty Eio.Path.t -> string list 84 + (** [list_profiles fs] returns all profiles that have sessions. 85 + Returns profile names sorted alphabetically. *) 86 + 87 + (** {1 Directory Paths} *) 88 + 89 + val base_config_dir : Eio.Fs.dir_ty Eio.Path.t -> Eio.Fs.dir_ty Eio.Path.t 90 + (** [base_config_dir fs] returns the base config directory 91 + ([~/.config/immich]), creating it if needed. *) 92 + 93 + val config_dir : 94 + Eio.Fs.dir_ty Eio.Path.t -> 95 + ?profile:string -> 96 + unit -> 97 + Eio.Fs.dir_ty Eio.Path.t 98 + (** [config_dir fs ?profile ()] returns the config directory for a 99 + profile, creating it if needed. 100 + @param profile Profile name (default: current profile) *) 101 + 102 + (** {1 Session Persistence} *) 103 + 104 + val save : Eio.Fs.dir_ty Eio.Path.t -> ?profile:string -> t -> unit 105 + (** [save fs ?profile session] saves the session. 106 + @param profile Profile name (default: current profile) *) 107 + 108 + val load : Eio.Fs.dir_ty Eio.Path.t -> ?profile:string -> unit -> t option 109 + (** [load fs ?profile ()] loads a saved session. 110 + @param profile Profile name (default: current profile) *) 111 + 112 + val clear : Eio.Fs.dir_ty Eio.Path.t -> ?profile:string -> unit -> unit 113 + (** [clear fs ?profile ()] removes the saved session. 114 + @param profile Profile name (default: current profile) *) 115 + 116 + (** {1 Session Utilities} *) 117 + 118 + val is_jwt_expired : ?leeway:int -> string -> bool 119 + (** [is_jwt_expired ?leeway token] returns [true] if the JWT token is expired. 120 + @param leeway Extra time buffer in seconds (default: 60) *) 121 + 122 + val is_expired : ?leeway:int -> t -> bool 123 + (** [is_expired ?leeway session] returns [true] if the session is expired. 124 + API key sessions never expire. 125 + @param leeway Extra time buffer in seconds for JWT (default: 60) *) 126 + 127 + val pp : t Fmt.t 128 + (** Pretty-print a session. *)
+18 -17
ocaml-openapi/lib/openapi_codegen.ml
··· 262 262 (** {1 Type Resolution} *) 263 263 264 264 let rec type_of_json_schema (json : Jsont.json) : string * bool = 265 + (* Check if the schema is nullable *) 266 + let is_nullable = match get_member "nullable" json with 267 + | Some (Jsont.Bool (b, _)) -> b 268 + | _ -> false 269 + in 265 270 match get_ref json with 266 271 | Some ref_ -> 267 272 (match schema_name_from_ref ref_ with 268 273 | Some name -> 269 274 let prefix, suffix = Name.split_schema_name name in 270 - (Printf.sprintf "%s.%s.t" (Name.to_module_name prefix) (Name.to_module_name suffix), false) 271 - | None -> ("Jsont.json", false)) 275 + (Printf.sprintf "%s.%s.t" (Name.to_module_name prefix) (Name.to_module_name suffix), is_nullable) 276 + | None -> ("Jsont.json", is_nullable)) 272 277 | None -> 273 278 match get_string_member "type" json with 274 279 | Some "string" -> 275 280 (match get_string_member "format" json with 276 - | Some "date-time" -> ("Ptime.t", false) 277 - | _ -> ("string", false)) 281 + | Some "date-time" -> ("Ptime.t", is_nullable) 282 + | _ -> ("string", is_nullable)) 278 283 | Some "integer" -> 279 284 (match get_string_member "format" json with 280 - | Some "int64" -> ("int64", false) 281 - | Some "int32" -> ("int32", false) 282 - | _ -> ("int", false)) 283 - | Some "number" -> ("float", false) 284 - | Some "boolean" -> ("bool", false) 285 + | Some "int64" -> ("int64", is_nullable) 286 + | Some "int32" -> ("int32", is_nullable) 287 + | _ -> ("int", is_nullable)) 288 + | Some "number" -> ("float", is_nullable) 289 + | Some "boolean" -> ("bool", is_nullable) 285 290 | Some "array" -> 286 291 (match get_member "items" json with 287 292 | Some items -> 288 293 let (elem_type, _) = type_of_json_schema items in 289 - (elem_type ^ " list", false) 290 - | None -> ("Jsont.json list", false)) 291 - | Some "object" -> ("Jsont.json", false) 292 - | _ -> 293 - let nullable = match get_member "nullable" json with 294 - | Some (Jsont.Bool (b, _)) -> b 295 - | _ -> false 296 - in ("Jsont.json", nullable) 294 + (elem_type ^ " list", is_nullable) 295 + | None -> ("Jsont.json list", is_nullable)) 296 + | Some "object" -> ("Jsont.json", is_nullable) 297 + | _ -> ("Jsont.json", is_nullable) 297 298 298 299 let rec jsont_of_base_type = function 299 300 | "string" -> "Jsont.string"
+49 -39
ocaml-requests/lib/h2/h2_client.ml
··· 159 159 with 160 160 | End_of_file -> None 161 161 | Eio.Io _ -> None 162 + | Eio.Cancel.Cancelled _ -> None 162 163 163 164 let send_settings_ack flow = 164 165 let ack_frame = H2_frame.make_settings ~ack:true [] in ··· 374 375 () (* Already running *) 375 376 else begin 376 377 t.reader_running <- true; 377 - Eio.Fiber.fork ~sw (fun () -> 378 - Log.debug (fun m -> m "Frame reader fiber started"); 379 - let rec read_loop () = 380 - match read_frame flow with 381 - | None -> 382 - Log.info (fun m -> m "Frame reader: connection closed"); 383 - Eio.Mutex.use_rw ~protect:true t.connection_error_mutex (fun () -> 384 - if t.connection_error = None then 385 - t.connection_error <- Some "Connection closed" 386 - ); 387 - (* Notify all handlers about connection close *) 388 - Eio.Mutex.use_ro t.handlers_mutex (fun () -> 389 - Hashtbl.iter (fun _id handler -> 390 - Eio.Stream.add handler.events (Connection_error "Connection closed") 391 - ) t.handlers 392 - ) 378 + (* Use fork_daemon so the reader doesn't prevent switch completion. 379 + The reader will be automatically cancelled when the switch completes. *) 380 + Eio.Fiber.fork_daemon ~sw (fun () -> 381 + (try 382 + Log.debug (fun m -> m "Frame reader fiber started"); 383 + let rec read_loop () = 384 + match read_frame flow with 385 + | None -> 386 + Log.info (fun m -> m "Frame reader: connection closed"); 387 + Eio.Mutex.use_rw ~protect:true t.connection_error_mutex (fun () -> 388 + if t.connection_error = None then 389 + t.connection_error <- Some "Connection closed" 390 + ); 391 + (* Notify all handlers about connection close *) 392 + Eio.Mutex.use_ro t.handlers_mutex (fun () -> 393 + Hashtbl.iter (fun _id handler -> 394 + Eio.Stream.add handler.events (Connection_error "Connection closed") 395 + ) t.handlers 396 + ) 393 397 394 - | Some frame -> 395 - match dispatch_frame t flow frame with 396 - | `Continue -> read_loop () 397 - | `Goaway (last_stream_id, error_code, debug) -> 398 - (* Call the GOAWAY callback if provided *) 399 - on_goaway ~last_stream_id ~error_code ~debug; 400 - (* Continue reading to drain any remaining frames *) 401 - read_loop () 402 - | `Error msg -> 403 - Log.err (fun m -> m "Frame reader error: %s" msg); 404 - Eio.Mutex.use_rw ~protect:true t.connection_error_mutex (fun () -> 405 - t.connection_error <- Some msg 406 - ); 407 - (* Notify all handlers *) 408 - Eio.Mutex.use_ro t.handlers_mutex (fun () -> 409 - Hashtbl.iter (fun _id handler -> 410 - Eio.Stream.add handler.events (Connection_error msg) 411 - ) t.handlers 412 - ) 413 - in 414 - read_loop (); 415 - t.reader_running <- false; 416 - Log.debug (fun m -> m "Frame reader fiber stopped") 398 + | Some frame -> 399 + match dispatch_frame t flow frame with 400 + | `Continue -> read_loop () 401 + | `Goaway (last_stream_id, error_code, debug) -> 402 + (* Call the GOAWAY callback if provided *) 403 + on_goaway ~last_stream_id ~error_code ~debug; 404 + (* Continue reading to drain any remaining frames *) 405 + read_loop () 406 + | `Error msg -> 407 + Log.err (fun m -> m "Frame reader error: %s" msg); 408 + Eio.Mutex.use_rw ~protect:true t.connection_error_mutex (fun () -> 409 + t.connection_error <- Some msg 410 + ); 411 + (* Notify all handlers *) 412 + Eio.Mutex.use_ro t.handlers_mutex (fun () -> 413 + Hashtbl.iter (fun _id handler -> 414 + Eio.Stream.add handler.events (Connection_error msg) 415 + ) t.handlers 416 + ) 417 + in 418 + read_loop (); 419 + t.reader_running <- false; 420 + Log.debug (fun m -> m "Frame reader fiber stopped") 421 + with 422 + | Eio.Cancel.Cancelled _ -> 423 + Log.debug (fun m -> m "Frame reader fiber cancelled") 424 + | exn -> 425 + Log.err (fun m -> m "Frame reader fiber error: %s" (Printexc.to_string exn))); 426 + `Stop_daemon 417 427 ) 418 428 end 419 429