My agentic slop goes here. Not intended for anyone else!
1(*
2 * Copyright (c) 2014, OCaml.org project
3 * Copyright (c) 2015 KC Sivaramakrishnan <sk826@cl.cam.ac.uk>
4 *
5 * Permission to use, copy, modify, and distribute this software for any
6 * purpose with or without fee is hereby granted, provided that the above
7 * copyright notice and this permission notice appear in all copies.
8 *
9 * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
10 * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
11 * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
12 * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
13 * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
14 * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
15 * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
16 *)
17
18(** State management for sync state and feeds.
19
20 User contact data is read from Sortal on-demand. River only persists
21 sync timestamps and feed data. *)
22
23type t
24(** State handle for managing sync state and feeds on disk. *)
25
26val create :
27 < fs : Eio.Fs.dir_ty Eio.Path.t; .. > ->
28 app_name:string ->
29 t
30(** [create env ~app_name] creates a state handle using XDG directories.
31
32 Data is stored in:
33 - Sync state: $XDG_STATE_HOME/[app_name]/sync_state.json
34 - Feeds: $XDG_STATE_HOME/[app_name]/feeds/[username]/
35
36 User contact data is read from Sortal's XDG location.
37
38 @param env The Eio environment with filesystem access
39 @param app_name Application name for XDG paths *)
40
41(** {2 User Operations} *)
42
43val get_user : t -> username:string -> User.t option
44(** [get_user state ~username] retrieves a user by username.
45
46 This reads contact data from Sortal and combines it with River's sync state.
47 Returns [None] if the username doesn't exist in Sortal or has no feeds. *)
48
49val list_users : t -> string list
50(** [list_users state] returns all usernames with feeds from Sortal. *)
51
52val get_all_users : t -> User.t list
53(** [get_all_users state] returns all users from Sortal with their sync state. *)
54
55val update_sync_state : t -> username:string -> timestamp:string -> (unit, string) result
56(** [update_sync_state state ~username ~timestamp] updates the last sync timestamp.
57
58 @param username The user to update
59 @param timestamp ISO 8601 timestamp of the sync *)
60
61(** {2 Feed Operations} *)
62
63val sync_user :
64 < clock : float Eio.Time.clock_ty Eio.Resource.t;
65 fs : Eio.Fs.dir_ty Eio.Path.t;
66 net : [ `Generic | `Unix ] Eio.Net.ty Eio.Resource.t; .. > ->
67 t ->
68 username:string ->
69 (unit, string) result
70(** [sync_user env state ~username] fetches all feeds for the user and stores merged result.
71
72 Posts are fetched concurrently and merged with existing posts.
73 The result is stored as an Atom feed. *)
74
75val sync_all :
76 < clock : float Eio.Time.clock_ty Eio.Resource.t;
77 fs : Eio.Fs.dir_ty Eio.Path.t;
78 net : [ `Generic | `Unix ] Eio.Net.ty Eio.Resource.t; .. > ->
79 t ->
80 (int * int, string) result
81(** [sync_all env state] syncs all users concurrently.
82
83 Returns [Ok (success_count, fail_count)]. *)
84
85val get_user_posts :
86 t ->
87 username:string ->
88 ?limit:int ->
89 unit ->
90 Syndic.Atom.entry list
91(** [get_user_posts state ~username ()] retrieves stored posts for a user.
92
93 @param limit Optional maximum number of posts to return *)
94
95val get_all_posts :
96 t ->
97 ?limit:int ->
98 unit ->
99 (string * Syndic.Atom.entry) list
100(** [get_all_posts state ()] retrieves posts from all users, sorted by date.
101
102 Returns list of (username, entry) tuples.
103 @param limit Optional maximum number of posts to return *)
104
105(** {2 Export} *)
106
107val export_merged_feed :
108 t ->
109 title:string ->
110 format:[ `Atom | `Jsonfeed ] ->
111 ?limit:int ->
112 unit ->
113 (string, string) result
114(** [export_merged_feed state ~title ~format ()] exports a merged feed of all users.
115
116 @param title Feed title
117 @param format Output format
118 @param limit Optional maximum number of entries *)
119
120val export_html_site :
121 t ->
122 output_dir:Eio.Fs.dir_ty Eio.Path.t ->
123 title:string ->
124 ?posts_per_page:int ->
125 unit ->
126 (unit, string) result
127(** [export_html_site state ~output_dir ~title ()] exports a static HTML site.
128
129 Generates a complete static site with:
130 - Paginated post listings
131 - Author index and individual author pages
132 - Category index and individual category pages
133 - Links page showing all outgoing links from posts
134
135 @param output_dir Directory to write HTML files to
136 @param title Site title
137 @param posts_per_page Number of posts per page (default: 25) *)
138
139(** {2 Category Management} *)
140
141val list_categories : t -> Category.t list
142(** [list_categories state] returns all custom categories. *)
143
144val get_category : t -> id:string -> Category.t option
145(** [get_category state ~id] retrieves a category by ID. *)
146
147val add_category : t -> Category.t -> (unit, string) result
148(** [add_category state category] adds or updates a category.
149
150 @param category The category to add/update *)
151
152val remove_category : t -> id:string -> (unit, string) result
153(** [remove_category state ~id] removes a category.
154
155 This also removes the category from any posts that were tagged with it.
156 @param id The category ID to remove *)
157
158val get_post_categories : t -> post_id:string -> string list
159(** [get_post_categories state ~post_id] returns the list of category IDs
160 assigned to a post. *)
161
162val set_post_categories : t -> post_id:string -> category_ids:string list -> (unit, string) result
163(** [set_post_categories state ~post_id ~category_ids] sets the categories for a post.
164
165 Replaces any existing category assignments for this post.
166 @param post_id The post ID to categorize
167 @param category_ids List of category IDs to assign *)
168
169val add_post_category : t -> post_id:string -> category_id:string -> (unit, string) result
170(** [add_post_category state ~post_id ~category_id] adds a category to a post.
171
172 @param post_id The post ID
173 @param category_id The category ID to add *)
174
175val remove_post_category : t -> post_id:string -> category_id:string -> (unit, string) result
176(** [remove_post_category state ~post_id ~category_id] removes a category from a post.
177
178 @param post_id The post ID
179 @param category_id The category ID to remove *)
180
181val get_posts_by_category : t -> category_id:string -> string list
182(** [get_posts_by_category state ~category_id] returns all post IDs with this category. *)
183
184(** {2 Analysis} *)
185
186val analyze_user_quality :
187 t ->
188 username:string ->
189 (Quality.t, string) result
190(** [analyze_user_quality state ~username] analyzes quality metrics for a user's feed. *)