@takosjp/yurucommu-core 3.0.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (185) hide show
  1. package/LICENSE +16 -0
  2. package/README.md +82 -0
  3. package/migrations/0001_init.sql +495 -0
  4. package/migrations/0002_social_remote_actor_edges.sql +92 -0
  5. package/migrations/0003_activity_remote_object_edges.sql +68 -0
  6. package/migrations/0004_blocklist.sql +26 -0
  7. package/migrations/0005_story_community_scope.sql +13 -0
  8. package/migrations/0006_dm_community_read_status.sql +19 -0
  9. package/migrations/0007_moderation_reports.sql +22 -0
  10. package/migrations/0008_actor_fields_aka.sql +18 -0
  11. package/migrations/0009_object_tags.sql +13 -0
  12. package/migrations/0010_object_recipients_drop_actor_fk.sql +34 -0
  13. package/migrations/0011_drop_remote_actor_fks.sql +205 -0
  14. package/migrations/0012_objects_content_fts.sql +39 -0
  15. package/migrations/0013_efficiency_indexes.sql +13 -0
  16. package/migrations/0014_inbox_actor_created_idx.sql +15 -0
  17. package/migrations/0015_community_bans.sql +16 -0
  18. package/migrations/0016_namespace_takos_oidc_subject.sql +19 -0
  19. package/migrations/0017_mobile_push_registrations.sql +22 -0
  20. package/migrations/README.md +122 -0
  21. package/package.json +75 -0
  22. package/packages/api/LICENSE +16 -0
  23. package/packages/api/package.json +30 -0
  24. package/packages/api/src/index.ts +4 -0
  25. package/packages/api/src/lib/api/account.ts +20 -0
  26. package/packages/api/src/lib/api/actors.ts +149 -0
  27. package/packages/api/src/lib/api/auth.ts +46 -0
  28. package/packages/api/src/lib/api/communities.ts +329 -0
  29. package/packages/api/src/lib/api/dm.test.ts +67 -0
  30. package/packages/api/src/lib/api/dm.ts +236 -0
  31. package/packages/api/src/lib/api/fetch.ts +111 -0
  32. package/packages/api/src/lib/api/follow.ts +30 -0
  33. package/packages/api/src/lib/api/media.ts +100 -0
  34. package/packages/api/src/lib/api/moderation.ts +98 -0
  35. package/packages/api/src/lib/api/normalize.ts +71 -0
  36. package/packages/api/src/lib/api/notifications.test.ts +63 -0
  37. package/packages/api/src/lib/api/notifications.ts +61 -0
  38. package/packages/api/src/lib/api/posts.test.ts +110 -0
  39. package/packages/api/src/lib/api/posts.ts +181 -0
  40. package/packages/api/src/lib/api/recommendations.ts +22 -0
  41. package/packages/api/src/lib/api/search.ts +88 -0
  42. package/packages/api/src/lib/api/stories.ts +80 -0
  43. package/packages/api/src/lib/api.ts +15 -0
  44. package/packages/api/src/lib/fetch-with-timeout.ts +42 -0
  45. package/packages/api/src/lib/transport.ts +40 -0
  46. package/packages/api/src/social-server.ts +47 -0
  47. package/packages/api/src/types/index.ts +185 -0
  48. package/scripts/apply-takosumi-migrations.ts +621 -0
  49. package/src/backend/federation-helpers.ts +36 -0
  50. package/src/backend/index.ts +872 -0
  51. package/src/backend/lib/account-migration.ts +106 -0
  52. package/src/backend/lib/activitypub-actor-cache.ts +238 -0
  53. package/src/backend/lib/activitypub-helpers.ts +131 -0
  54. package/src/backend/lib/activitypub-validators.ts +323 -0
  55. package/src/backend/lib/ap-context.ts +16 -0
  56. package/src/backend/lib/ap-ids.ts +101 -0
  57. package/src/backend/lib/ap-response.ts +30 -0
  58. package/src/backend/lib/ap-signing.ts +87 -0
  59. package/src/backend/lib/ap-verify.ts +670 -0
  60. package/src/backend/lib/auth-lockout.ts +230 -0
  61. package/src/backend/lib/backend-paths.ts +34 -0
  62. package/src/backend/lib/base64.ts +30 -0
  63. package/src/backend/lib/blocklist-purge.ts +109 -0
  64. package/src/backend/lib/blocklist.ts +279 -0
  65. package/src/backend/lib/chunk.ts +33 -0
  66. package/src/backend/lib/client-ip.ts +169 -0
  67. package/src/backend/lib/community-visibility.ts +230 -0
  68. package/src/backend/lib/crypto.ts +424 -0
  69. package/src/backend/lib/delivery/circuit.ts +265 -0
  70. package/src/backend/lib/delivery/metrics.ts +30 -0
  71. package/src/backend/lib/delivery/planner.ts +190 -0
  72. package/src/backend/lib/delivery/queue-batching.ts +626 -0
  73. package/src/backend/lib/delivery/queue-delivery.ts +641 -0
  74. package/src/backend/lib/delivery/queue.ts +576 -0
  75. package/src/backend/lib/delivery/transformers.ts +56 -0
  76. package/src/backend/lib/delivery/types.ts +139 -0
  77. package/src/backend/lib/errors.ts +114 -0
  78. package/src/backend/lib/federation-fetch.ts +296 -0
  79. package/src/backend/lib/feed-cursor.ts +57 -0
  80. package/src/backend/lib/feed-exclude.ts +48 -0
  81. package/src/backend/lib/hex.ts +8 -0
  82. package/src/backend/lib/log-mask.ts +213 -0
  83. package/src/backend/lib/logger.ts +285 -0
  84. package/src/backend/lib/mobile-contract.ts +137 -0
  85. package/src/backend/lib/oauth-providers.ts +324 -0
  86. package/src/backend/lib/oauth-utils.ts +148 -0
  87. package/src/backend/lib/oidc-id-token.ts +151 -0
  88. package/src/backend/lib/parse-helpers.ts +31 -0
  89. package/src/backend/lib/post-visibility.ts +190 -0
  90. package/src/backend/lib/session-actor.ts +61 -0
  91. package/src/backend/lib/ssrf.ts +428 -0
  92. package/src/backend/lib/strip-image-metadata.ts +191 -0
  93. package/src/backend/middleware/bearer-auth.ts +70 -0
  94. package/src/backend/middleware/body-limit.ts +212 -0
  95. package/src/backend/middleware/cache.ts +429 -0
  96. package/src/backend/middleware/csrf.ts +130 -0
  97. package/src/backend/middleware/error-handler.ts +77 -0
  98. package/src/backend/middleware/rate-limit.ts +308 -0
  99. package/src/backend/public.ts +21 -0
  100. package/src/backend/routes/account-teardown.ts +430 -0
  101. package/src/backend/routes/activitypub/handlers/actor-inbox-handlers.ts +354 -0
  102. package/src/backend/routes/activitypub/handlers/inbound-timestamp.ts +29 -0
  103. package/src/backend/routes/activitypub/handlers/inbox-content-handlers.ts +1634 -0
  104. package/src/backend/routes/activitypub/handlers/inbox-follow-handlers.ts +547 -0
  105. package/src/backend/routes/activitypub/handlers/inbox-interaction-handlers.ts +497 -0
  106. package/src/backend/routes/activitypub/handlers/inbox-shared-helpers.ts +262 -0
  107. package/src/backend/routes/activitypub/handlers/user-inbox-handlers.ts +35 -0
  108. package/src/backend/routes/activitypub/inbox-types.ts +74 -0
  109. package/src/backend/routes/activitypub/inbox.ts +1191 -0
  110. package/src/backend/routes/activitypub/outbox.ts +0 -0
  111. package/src/backend/routes/activitypub/query-helpers.ts +227 -0
  112. package/src/backend/routes/activitypub.ts +616 -0
  113. package/src/backend/routes/actors-helpers.ts +487 -0
  114. package/src/backend/routes/actors.ts +1311 -0
  115. package/src/backend/routes/apps.ts +313 -0
  116. package/src/backend/routes/auth-helpers.ts +566 -0
  117. package/src/backend/routes/auth.ts +615 -0
  118. package/src/backend/routes/communities/membership-invites.ts +208 -0
  119. package/src/backend/routes/communities/membership-join.ts +335 -0
  120. package/src/backend/routes/communities/membership-members.ts +539 -0
  121. package/src/backend/routes/communities/membership-requests.ts +296 -0
  122. package/src/backend/routes/communities/membership-shared.ts +364 -0
  123. package/src/backend/routes/communities/messages.ts +479 -0
  124. package/src/backend/routes/communities/routes.ts +624 -0
  125. package/src/backend/routes/communities.ts +21 -0
  126. package/src/backend/routes/dm/contacts.ts +525 -0
  127. package/src/backend/routes/dm/conversations-helpers.ts +197 -0
  128. package/src/backend/routes/dm/conversations.ts +25 -0
  129. package/src/backend/routes/dm/messages.ts +658 -0
  130. package/src/backend/routes/dm/query-helpers.ts +85 -0
  131. package/src/backend/routes/dm/read-archive.ts +228 -0
  132. package/src/backend/routes/dm/requests.ts +222 -0
  133. package/src/backend/routes/dm/typing.ts +81 -0
  134. package/src/backend/routes/dm.ts +15 -0
  135. package/src/backend/routes/follow-helpers.ts +370 -0
  136. package/src/backend/routes/follow.ts +588 -0
  137. package/src/backend/routes/media.ts +692 -0
  138. package/src/backend/routes/mobile.ts +159 -0
  139. package/src/backend/routes/moderation.ts +373 -0
  140. package/src/backend/routes/notifications.ts +757 -0
  141. package/src/backend/routes/posts/delete-cascade.ts +330 -0
  142. package/src/backend/routes/posts/interactions.ts +795 -0
  143. package/src/backend/routes/posts/post-helpers.ts +847 -0
  144. package/src/backend/routes/posts/queries.ts +537 -0
  145. package/src/backend/routes/posts/routes.ts +865 -0
  146. package/src/backend/routes/posts/transformers.ts +161 -0
  147. package/src/backend/routes/posts.ts +17 -0
  148. package/src/backend/routes/recommendations.ts +88 -0
  149. package/src/backend/routes/search.ts +730 -0
  150. package/src/backend/routes/stories/interactions.ts +576 -0
  151. package/src/backend/routes/stories/query-helpers.ts +482 -0
  152. package/src/backend/routes/stories/routes.ts +906 -0
  153. package/src/backend/routes/stories.ts +13 -0
  154. package/src/backend/routes/takos-tools/dm.ts +249 -0
  155. package/src/backend/routes/takos-tools/follows.ts +225 -0
  156. package/src/backend/routes/takos-tools/posts.ts +292 -0
  157. package/src/backend/routes/takos-tools/search.ts +228 -0
  158. package/src/backend/routes/takos-tools/timeline.ts +132 -0
  159. package/src/backend/routes/takos-tools/types.ts +10 -0
  160. package/src/backend/routes/takos-tools-response.ts +178 -0
  161. package/src/backend/routes/takos-tools.ts +153 -0
  162. package/src/backend/routes/timeline.ts +755 -0
  163. package/src/backend/runtime/bun.ts +620 -0
  164. package/src/backend/runtime/cloudflare.ts +202 -0
  165. package/src/backend/runtime/compat-bun/types.ts +44 -0
  166. package/src/backend/runtime/memory-kv.ts +104 -0
  167. package/src/backend/runtime/shared.ts +142 -0
  168. package/src/backend/runtime/types.ts +205 -0
  169. package/src/backend/server.ts +636 -0
  170. package/src/backend/types.ts +143 -0
  171. package/src/db/index.ts +97 -0
  172. package/src/db/schema/actors.ts +129 -0
  173. package/src/db/schema/communities.ts +133 -0
  174. package/src/db/schema/date-utils.ts +17 -0
  175. package/src/db/schema/index.ts +17 -0
  176. package/src/db/schema/messaging.ts +241 -0
  177. package/src/db/schema/mobile.ts +37 -0
  178. package/src/db/schema/posts.ts +150 -0
  179. package/src/db/schema/relations.ts +266 -0
  180. package/src/db/schema/reports.ts +33 -0
  181. package/src/db/schema/social.ts +106 -0
  182. package/src/db/schema/stories.ts +70 -0
  183. package/src/db/schema.ts +15 -0
  184. package/src/plugin/public.ts +7 -0
  185. package/src/runtime/site-worker.ts +10 -0
@@ -0,0 +1,122 @@
1
+ # yurucommu migrations
2
+
3
+ SQL migrations for the yurucommu social engine. Deployable product repos
4
+ (`yurucommu` and `yurumeet`) apply these migrations from the published
5
+ `@takosjp/yurucommu-core` package.
6
+
7
+ - **Substrate**: libSQL (single-host) or Cloudflare D1 (managed edge).
8
+ - **Ledger table**: `yurucommu_migrations(name TEXT PRIMARY KEY, applied_at)`.
9
+ This is yurucommu-owned product state; it does **not** carry a checksum
10
+ column.
11
+ - **Runner sources**:
12
+ - `bun run app:activate` in this package for direct migration helper tests
13
+ - `bun run takosumi:release` in a deployable product repo for Takosumi
14
+ activation
15
+ - `wrangler d1 migrations apply` for operator-managed Cloudflare D1 paths
16
+
17
+ ## Naming convention
18
+
19
+ Files use a zero-padded 4-digit prefix:
20
+
21
+ ```
22
+ NNNN_short_description.sql
23
+ ```
24
+
25
+ Order is determined by lexicographic sort on the prefix. The runner records the
26
+ file name (not the numeric version) in `yurucommu_migrations`.
27
+
28
+ ## Operator runbook
29
+
30
+ 1. **Apply** (libSQL / single-host):
31
+
32
+ ```bash
33
+ cd yurucommu
34
+ bun run start
35
+ ```
36
+
37
+ Migrations run lazily on the first DB connect.
38
+
39
+ 2. **Apply** (Cloudflare D1):
40
+
41
+ ```bash
42
+ cd yurucommu
43
+ npx wrangler d1 migrations apply <DB-NAME> --env <env>
44
+ ```
45
+
46
+ 3. **Apply migrations only** (Takosumi-compatible helper):
47
+
48
+ `bun run app:activate` is the core package migration helper. Takosumi does
49
+ not parse migrations, talk to the database, or provide a DB-specific API for
50
+ this path; it only records the activation result when a product repo invokes
51
+ the helper through its release hook. The operator activation environment must
52
+ provide the SQL execution command.
53
+
54
+ Prefix mode appends `<resource> <sql>`:
55
+
56
+ ```bash
57
+ YURUCOMMU_SQL_COMMAND_JSON='["operator-sql-cli","query"]' \
58
+ YURUCOMMU_SQL_RESOURCE=database \
59
+ bun run app:activate
60
+ ```
61
+
62
+ Template mode substitutes `{resource}` and `{sql}`:
63
+
64
+ ```bash
65
+ YURUCOMMU_SQL_COMMAND_TEMPLATE_JSON='["bunx","wrangler","d1","execute","{resource}","--remote","--json","--command={sql}"]' \
66
+ YURUCOMMU_SQL_WRAP_TRANSACTIONS=false \
67
+ YURUCOMMU_SQL_RESOURCE=DB \
68
+ bun run app:activate
69
+ ```
70
+
71
+ Remote Cloudflare D1 rejects explicit `BEGIN` / `SAVEPOINT` statements through
72
+ this API, so the Takosumi release path disables transaction wrappers while
73
+ keeping the local/libSQL path wrapped by default.
74
+
75
+ 4. **Post-apply activation**:
76
+
77
+ In the OpenTofu-managed Worker path, the product repo's
78
+ `takosumi_release.post_apply` runs
79
+ `bun run takosumi:release -- --migrations-only` as an opaque runner command.
80
+ It reads non-secret outputs from `TAKOSUMI_OUTPUTS_JSON`, writes a temporary
81
+ Wrangler config, and applies these core migrations through
82
+ `wrangler d1 execute` without explicit SQL transaction wrappers. Provider
83
+ credentials come from the same reviewed Provider Connection used by the
84
+ OpenTofu run.
85
+
86
+ In the fallback path where the Worker script is not managed by OpenTofu,
87
+ `takosumi_release.post_apply` runs `bun run takosumi:release` as an opaque
88
+ operator release command. It reads non-secret outputs from `TAKOSUMI_OUTPUTS_JSON`,
89
+ writes a temporary Wrangler config, runs `bun install --frozen-lockfile`,
90
+ runs `bun run build:takos-worker`, applies D1 migrations through
91
+ `wrangler d1 execute` without explicit SQL transaction wrappers, and deploys
92
+ with `wrangler deploy`.
93
+ Provider credentials and app-specific secrets come from the selected
94
+ operator release activator boundary through
95
+ `TAKOSUMI_RELEASE_COMMAND_ENV_ALLOWLIST`.
96
+
97
+ Common operator env names:
98
+
99
+ ```bash
100
+ YURUCOMMU_ENCRYPTION_KEY=...
101
+ YURUCOMMU_AUTH_PASSWORD_HASH=...
102
+ TAKOSUMI_ACCOUNTS_ISSUER_URL=https://app.takosumi.com
103
+ TAKOSUMI_ACCOUNTS_CLIENT_ID=...
104
+ ```
105
+
106
+ 5. **Forensics**:
107
+
108
+ ```sql
109
+ SELECT name, applied_at FROM yurucommu_migrations ORDER BY applied_at;
110
+ ```
111
+
112
+ ## Product-local ledger note
113
+
114
+ The `yurucommu_migrations` ledger is yurucommu-family product state. The
115
+ Takosumi-managed path invokes product-owned `takosumi_release.post_apply`
116
+ commands and records activation status/logs; it does not expose a Takosumi DB
117
+ migration API. A future core migration may add `checksum TEXT` and store
118
+ `sha256:<hex>` per applied migration, but that requires coordination with
119
+ production data in both yurucommu and Yurumeet installations.
120
+
121
+ For restore / disaster-recovery procedures, see the operator runbooks under
122
+ [`takosumi/docs/operations/`](../../takosumi/docs/operations/).
package/package.json ADDED
@@ -0,0 +1,75 @@
1
+ {
2
+ "name": "@takosjp/yurucommu-core",
3
+ "version": "3.0.0",
4
+ "license": "AGPL-3.0-only",
5
+ "type": "module",
6
+ "workspaces": [
7
+ "packages/*"
8
+ ],
9
+ "files": [
10
+ "src/backend",
11
+ "src/db",
12
+ "src/plugin",
13
+ "src/runtime",
14
+ "!src/backend/**/__tests__/**",
15
+ "!packages/api/src/**/*.test.ts",
16
+ "scripts/apply-takosumi-migrations.ts",
17
+ "migrations",
18
+ "packages/api/src",
19
+ "packages/api/package.json",
20
+ "packages/api/LICENSE"
21
+ ],
22
+ "exports": {
23
+ ".": "./src/backend/public.ts",
24
+ "./server": "./src/backend/public.ts",
25
+ "./client-api": "./packages/api/src/index.ts",
26
+ "./migrations": "./scripts/apply-takosumi-migrations.ts",
27
+ "./plugins": "./src/plugin/public.ts"
28
+ },
29
+ "imports": {
30
+ "#test/assert": "./shims/std/assert.ts",
31
+ "#test/mock": "./shims/std/mock.ts",
32
+ "#test/time": "./shims/std/time.ts"
33
+ },
34
+ "scripts": {
35
+ "start": "bun src/backend/server.ts",
36
+ "dev": "bun src/backend/server.ts",
37
+ "dev:server": "bun src/backend/server.ts",
38
+ "check": "tsc --noEmit",
39
+ "test": "bun run build:api && bun test test/ src/backend/ packages/api/src/",
40
+ "test:backend": "bun test src/backend/",
41
+ "build": "bun run build:api",
42
+ "build:api": "cd packages/api && bun run build",
43
+ "pack:core": "npm pack --dry-run",
44
+ "pack:api": "cd packages/api && bun run pack:dry",
45
+ "app:activate": "bun scripts/apply-takosumi-migrations.ts",
46
+ "lint": "bun run check",
47
+ "fmt": "bunx prettier --write src test packages/api",
48
+ "fmt:check": "bunx prettier --check src test packages/api",
49
+ "db:generate": "bunx drizzle-kit generate",
50
+ "db:push": "bunx drizzle-kit push",
51
+ "db:migrate": "bunx drizzle-kit migrate",
52
+ "db:studio": "bunx drizzle-kit studio"
53
+ },
54
+ "dependencies": {
55
+ "@aws-sdk/client-s3": "^3.971.0",
56
+ "@libsql/client": "^0.17.0",
57
+ "better-sqlite3": "^12.4.1",
58
+ "drizzle-orm": "^0.45.1",
59
+ "fdir": "^6.5.0",
60
+ "hono": "^4.12.9",
61
+ "picomatch": "^4.0.4",
62
+ "tinyglobby": "^0.2.16"
63
+ },
64
+ "devDependencies": {
65
+ "@cloudflare/workers-types": "^4.20260420.1",
66
+ "@types/better-sqlite3": "^7.6.11",
67
+ "@types/bun": "latest",
68
+ "@types/node": "^22.0.0",
69
+ "drizzle-kit": "^0.31.9",
70
+ "typescript": "^5.7.0"
71
+ },
72
+ "publishConfig": {
73
+ "access": "public"
74
+ }
75
+ }
@@ -0,0 +1,16 @@
1
+ SPDX-License-Identifier: AGPL-3.0-only
2
+
3
+ GNU Affero General Public License v3.0 only
4
+
5
+ Copyright (c) 2026 Takos Contributors
6
+
7
+ This program is free software: you can redistribute it and/or modify it under
8
+ the terms of the GNU Affero General Public License as published by the Free
9
+ Software Foundation, version 3 of the License.
10
+
11
+ This program is distributed in the hope that it will be useful, but WITHOUT ANY
12
+ WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A
13
+ PARTICULAR PURPOSE. See the GNU Affero General Public License for more details.
14
+
15
+ You should have received a copy of the GNU Affero General Public License along
16
+ with this program. If not, see <https://www.gnu.org/licenses/agpl-3.0.html>.
@@ -0,0 +1,30 @@
1
+ {
2
+ "name": "@takosjp/yurucommu-api",
3
+ "version": "3.0.0",
4
+ "description": "Typed client SDK and public API contract for yurucommu-server clients.",
5
+ "license": "AGPL-3.0-only",
6
+ "type": "module",
7
+ "sideEffects": false,
8
+ "files": [
9
+ "dist"
10
+ ],
11
+ "exports": {
12
+ ".": {
13
+ "types": "./dist/index.d.ts",
14
+ "import": "./dist/index.js",
15
+ "default": "./dist/index.js"
16
+ }
17
+ },
18
+ "types": "./dist/index.d.ts",
19
+ "scripts": {
20
+ "check": "tsc --noEmit -p tsconfig.json",
21
+ "build": "rm -rf dist && bun build src/index.ts --target browser --format esm --outfile dist/index.js && bunx tsc -p tsconfig.build.json && bun scripts/rewrite-dts-imports.mjs",
22
+ "pack:dry": "npm pack --dry-run"
23
+ },
24
+ "devDependencies": {
25
+ "typescript": "^5.7.0"
26
+ },
27
+ "publishConfig": {
28
+ "access": "public"
29
+ }
30
+ }
@@ -0,0 +1,4 @@
1
+ export * from "./lib/api.ts";
2
+ export * from "./lib/transport.ts";
3
+ export * from "./social-server.ts";
4
+ export * from "./types/index.ts";
@@ -0,0 +1,20 @@
1
+ import { apiFetch, apiPost, apiPut, assertOk } from "./fetch.ts";
2
+
3
+ export async function fetchAlsoKnownAs(identifier: string): Promise<string[]> {
4
+ const res = await apiFetch(`/api/actors/${encodeURIComponent(identifier)}`);
5
+ if (!res.ok) return [];
6
+ const data = (await res.json()) as { also_known_as?: unknown };
7
+ return Array.isArray(data.also_known_as)
8
+ ? data.also_known_as.filter((a): a is string => typeof a === "string")
9
+ : [];
10
+ }
11
+
12
+ export async function setAlsoKnownAs(aliases: string[]): Promise<void> {
13
+ const res = await apiPut("/api/actors/me", { also_known_as: aliases });
14
+ await assertOk(res, "Failed to update aliases");
15
+ }
16
+
17
+ export async function moveAccount(target: string): Promise<void> {
18
+ const res = await apiPost("/api/actors/me/move", { target });
19
+ await assertOk(res, "Failed to move account");
20
+ }
@@ -0,0 +1,149 @@
1
+ import type { Actor, Post } from "../../types/index.ts";
2
+ import { normalizeActor, normalizePost } from "./normalize.ts";
3
+ import { apiDelete, apiFetch, apiPost, apiPut, assertOk } from "./fetch.ts";
4
+
5
+ export async function fetchActor(identifier: string): Promise<Actor> {
6
+ const res = await apiFetch(`/api/actors/${encodeURIComponent(identifier)}`);
7
+ await assertOk(res, "Actor not found");
8
+ const data = (await res.json()) as { actor: Actor };
9
+ return normalizeActor(data.actor);
10
+ }
11
+
12
+ export async function updateProfile(data: {
13
+ name?: string;
14
+ summary?: string;
15
+ icon_url?: string;
16
+ header_url?: string;
17
+ is_private?: boolean;
18
+ // Structured PropertyValue profile fields. PUT /me replaces the stored set
19
+ // with this array (backend caps it at 4 and sanitizes name/value).
20
+ fields?: { name: string; value: string }[];
21
+ }): Promise<void> {
22
+ const res = await apiPut("/api/actors/me", data);
23
+ await assertOk(res, "Failed to update profile");
24
+ }
25
+
26
+ export interface ActorPostsPage {
27
+ posts: Post[];
28
+ nextCursor: string | null;
29
+ hasMore: boolean;
30
+ }
31
+
32
+ export async function fetchActorPosts(
33
+ identifier: string,
34
+ options?: { limit?: number; before?: string },
35
+ ): Promise<ActorPostsPage> {
36
+ const params = new URLSearchParams();
37
+ if (options?.limit) params.set("limit", String(options.limit));
38
+ if (options?.before) params.set("before", options.before);
39
+ const query = params.toString() ? `?${params}` : "";
40
+ const res = await apiFetch(
41
+ `/api/actors/${encodeURIComponent(identifier)}/posts${query}`,
42
+ );
43
+ await assertOk(res, "Failed to fetch actor posts");
44
+ const data = (await res.json()) as {
45
+ posts?: Post[];
46
+ next_cursor?: string | null;
47
+ has_more?: boolean;
48
+ };
49
+ return {
50
+ posts: (data.posts || []).map(normalizePost),
51
+ nextCursor: data.next_cursor ?? null,
52
+ hasMore: data.has_more ?? false,
53
+ };
54
+ }
55
+
56
+ export interface FollowListPage {
57
+ actors: Actor[];
58
+ hasMore: boolean;
59
+ total: number;
60
+ }
61
+
62
+ function followListQuery(options?: {
63
+ limit?: number;
64
+ offset?: number;
65
+ }): string {
66
+ const params = new URLSearchParams();
67
+ if (options?.limit) params.set("limit", String(options.limit));
68
+ if (options?.offset) params.set("offset", String(options.offset));
69
+ return params.toString() ? `?${params}` : "";
70
+ }
71
+
72
+ export async function fetchFollowers(
73
+ identifier: string,
74
+ options?: { limit?: number; offset?: number },
75
+ ): Promise<FollowListPage> {
76
+ const res = await apiFetch(
77
+ `/api/actors/${encodeURIComponent(identifier)}/followers${followListQuery(options)}`,
78
+ );
79
+ await assertOk(res, "Failed to fetch followers");
80
+ const data = (await res.json()) as {
81
+ followers?: Actor[];
82
+ has_more?: boolean;
83
+ total?: number;
84
+ };
85
+ return {
86
+ actors: (data.followers || []).map(normalizeActor),
87
+ hasMore: data.has_more ?? false,
88
+ total: data.total ?? 0,
89
+ };
90
+ }
91
+
92
+ export async function fetchFollowing(
93
+ identifier: string,
94
+ options?: { limit?: number; offset?: number },
95
+ ): Promise<FollowListPage> {
96
+ const res = await apiFetch(
97
+ `/api/actors/${encodeURIComponent(identifier)}/following${followListQuery(options)}`,
98
+ );
99
+ await assertOk(res, "Failed to fetch following");
100
+ const data = (await res.json()) as {
101
+ following?: Actor[];
102
+ has_more?: boolean;
103
+ total?: number;
104
+ };
105
+ return {
106
+ actors: (data.following || []).map(normalizeActor),
107
+ hasMore: data.has_more ?? false,
108
+ total: data.total ?? 0,
109
+ };
110
+ }
111
+
112
+ export async function fetchBlockedUsers(): Promise<Actor[]> {
113
+ const res = await apiFetch("/api/actors/me/blocked");
114
+ await assertOk(res, "Failed to fetch blocked users");
115
+ const data = (await res.json()) as { blocked?: Actor[] };
116
+ return (data.blocked || []).map(normalizeActor);
117
+ }
118
+
119
+ export async function blockUser(apId: string): Promise<void> {
120
+ const res = await apiPost("/api/actors/me/blocked", { ap_id: apId });
121
+ await assertOk(res, "Failed to block user");
122
+ }
123
+
124
+ export async function unblockUser(apId: string): Promise<void> {
125
+ const res = await apiDelete("/api/actors/me/blocked", { ap_id: apId });
126
+ await assertOk(res, "Failed to unblock user");
127
+ }
128
+
129
+ export async function fetchMutedUsers(): Promise<Actor[]> {
130
+ const res = await apiFetch("/api/actors/me/muted");
131
+ await assertOk(res, "Failed to fetch muted users");
132
+ const data = (await res.json()) as { muted?: Actor[] };
133
+ return (data.muted || []).map(normalizeActor);
134
+ }
135
+
136
+ export async function muteUser(apId: string): Promise<void> {
137
+ const res = await apiPost("/api/actors/me/muted", { ap_id: apId });
138
+ await assertOk(res, "Failed to mute user");
139
+ }
140
+
141
+ export async function unmuteUser(apId: string): Promise<void> {
142
+ const res = await apiDelete("/api/actors/me/muted", { ap_id: apId });
143
+ await assertOk(res, "Failed to unmute user");
144
+ }
145
+
146
+ export async function deleteAccount(): Promise<void> {
147
+ const res = await apiPost("/api/actors/me/delete");
148
+ await assertOk(res, "Failed to delete account");
149
+ }
@@ -0,0 +1,46 @@
1
+ import { apiFetch, apiPost, assertOk } from "./fetch.ts";
2
+
3
+ export async function login(
4
+ password: string,
5
+ ): Promise<{ success: boolean; error?: string }> {
6
+ const res = await apiPost("/api/auth/login", { password });
7
+ return (await res.json()) as { success: boolean; error?: string };
8
+ }
9
+
10
+ export async function logout(): Promise<void> {
11
+ await apiPost("/api/auth/logout");
12
+ }
13
+
14
+ export interface AccountInfo {
15
+ ap_id: string;
16
+ preferred_username: string;
17
+ name: string | null;
18
+ icon_url: string | null;
19
+ }
20
+
21
+ export async function fetchAccounts(): Promise<{
22
+ accounts: AccountInfo[];
23
+ current_ap_id: string;
24
+ }> {
25
+ const res = await apiFetch("/api/auth/accounts");
26
+ await assertOk(res, "Failed to fetch accounts");
27
+ return (await res.json()) as {
28
+ accounts: AccountInfo[];
29
+ current_ap_id: string;
30
+ };
31
+ }
32
+
33
+ export async function switchAccount(apId: string): Promise<void> {
34
+ const res = await apiPost("/api/auth/switch", { ap_id: apId });
35
+ await assertOk(res, "Failed to switch account");
36
+ }
37
+
38
+ export async function createAccount(
39
+ username: string,
40
+ name?: string,
41
+ ): Promise<AccountInfo> {
42
+ const res = await apiPost("/api/auth/accounts", { username, name });
43
+ await assertOk(res, "Failed to create account");
44
+ const data = (await res.json()) as { account: AccountInfo };
45
+ return data.account;
46
+ }