@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,241 @@
1
+ /**
2
+ * Messaging/DM tables: activities, inbox, deliveryQueue, deliveryCircuit,
3
+ * notificationArchived, dmTyping, dmReadStatus, dmArchivedConversations, mediaUploads
4
+ */
5
+
6
+ import {
7
+ index,
8
+ integer,
9
+ primaryKey,
10
+ sqliteTable,
11
+ text,
12
+ } from "drizzle-orm/sqlite-core";
13
+ import { nowIso, nowIsoUtc } from "./date-utils.ts";
14
+
15
+ // ---------------------------------------------------------------------------
16
+ // ACTIVITIES
17
+ // ---------------------------------------------------------------------------
18
+
19
+ export const activities = sqliteTable(
20
+ "activities",
21
+ {
22
+ apId: text("ap_id").primaryKey(),
23
+ type: text("type").notNull(),
24
+ actorApId: text("actor_ap_id").notNull(),
25
+ objectApId: text("object_ap_id"),
26
+ objectJson: text("object_json"),
27
+ targetApId: text("target_ap_id"),
28
+ rawJson: text("raw_json").notNull(),
29
+ direction: text("direction"),
30
+ processed: integer("processed").notNull().default(0),
31
+ createdAt: text("created_at").notNull().$defaultFn(nowIso),
32
+ },
33
+ (t) => [
34
+ index("activities_actor_idx").on(t.actorApId),
35
+ index("activities_object_idx").on(t.objectApId),
36
+ index("activities_type_idx").on(t.type),
37
+ index("activities_type_created_idx").on(t.type, t.createdAt),
38
+ index("activities_dir_processed_idx").on(t.direction, t.processed),
39
+ index("activities_dir_proc_created_idx").on(
40
+ t.direction,
41
+ t.processed,
42
+ t.createdAt,
43
+ ),
44
+ ],
45
+ );
46
+
47
+ // ---------------------------------------------------------------------------
48
+ // DELIVERY_QUEUE
49
+ // ---------------------------------------------------------------------------
50
+
51
+ export const deliveryQueue = sqliteTable(
52
+ "delivery_queue",
53
+ {
54
+ id: text("id").primaryKey(),
55
+ activityApId: text("activity_ap_id").notNull(),
56
+ inboxUrl: text("inbox_url").notNull(),
57
+ status: text("status").notNull().default("pending"),
58
+ attempts: integer("attempts").notNull().default(0),
59
+ lastAttemptAt: text("last_attempt_at"),
60
+ processingStartedAt: text("processing_started_at"),
61
+ nextAttemptAt: text("next_attempt_at").notNull().$defaultFn(nowIso),
62
+ deliveredAt: text("delivered_at"),
63
+ error: text("error"),
64
+ createdAt: text("created_at").notNull().$defaultFn(nowIso),
65
+ },
66
+ (t) => [
67
+ index("delivery_queue_activity_idx").on(t.activityApId),
68
+ index("delivery_queue_next_attempt_idx").on(t.nextAttemptAt),
69
+ index("delivery_queue_status_next_idx").on(t.status, t.nextAttemptAt),
70
+ ],
71
+ );
72
+
73
+ // ---------------------------------------------------------------------------
74
+ // DELIVERY_CIRCUIT
75
+ // ---------------------------------------------------------------------------
76
+
77
+ export const deliveryCircuit = sqliteTable(
78
+ "delivery_circuit",
79
+ {
80
+ endpoint: text("endpoint").primaryKey(),
81
+ state: text("state").notNull().default("closed"),
82
+ consecutiveFailures: integer("consecutive_failures").notNull().default(0),
83
+ recentOutcomesJson: text("recent_outcomes_json").notNull().default("[]"),
84
+ openUntil: text("open_until"),
85
+ halfOpenProbeAttempts: integer("half_open_probe_attempts")
86
+ .notNull()
87
+ .default(0),
88
+ halfOpenProbeSuccesses: integer("half_open_probe_successes")
89
+ .notNull()
90
+ .default(0),
91
+ createdAt: text("created_at").notNull().$defaultFn(nowIso),
92
+ updatedAt: text("updated_at")
93
+ .notNull()
94
+ .$defaultFn(nowIso)
95
+ .$onUpdateFn(nowIso),
96
+ },
97
+ (t) => [index("delivery_circuit_state_updated_idx").on(t.state, t.updatedAt)],
98
+ );
99
+
100
+ // ---------------------------------------------------------------------------
101
+ // INBOX
102
+ // ---------------------------------------------------------------------------
103
+
104
+ export const inbox = sqliteTable(
105
+ "inbox",
106
+ {
107
+ actorApId: text("actor_ap_id").notNull(),
108
+ activityApId: text("activity_ap_id").notNull(),
109
+ read: integer("read").notNull().default(0),
110
+ // Canonical UTC (…Z): this column is the notification feed's sort + cursor
111
+ // key (`desc(created_at)`, `lt(created_at, before)`). The legacy
112
+ // space-separated `nowIso` would sort below same-instant `…Z` rows written
113
+ // by the explicit-`toISOString` insert paths (likes/reposts/replies),
114
+ // mis-ordering the feed. See nowIsoUtc.
115
+ createdAt: text("created_at").notNull().$defaultFn(nowIsoUtc),
116
+ },
117
+ (t) => [
118
+ primaryKey({ columns: [t.actorApId, t.activityApId] }),
119
+ index("inbox_actor_read_created_idx").on(t.actorApId, t.read, t.createdAt),
120
+ // The notification-list query (WHERE actor_ap_id = ? ORDER BY created_at,
121
+ // activity_ap_id — no `read` constraint) cannot use the index above (its
122
+ // `read` column sits between the equality prefix and the sort key), so it
123
+ // filesorted the whole inbox. This composite serves the seek + sort directly.
124
+ index("inbox_actor_created_idx").on(
125
+ t.actorApId,
126
+ t.createdAt,
127
+ t.activityApId,
128
+ ),
129
+ index("inbox_activity_idx").on(t.activityApId),
130
+ ],
131
+ );
132
+
133
+ // ---------------------------------------------------------------------------
134
+ // NOTIFICATION_ARCHIVED
135
+ // ---------------------------------------------------------------------------
136
+
137
+ export const notificationArchived = sqliteTable(
138
+ "notification_archived",
139
+ {
140
+ actorApId: text("actor_ap_id").notNull(),
141
+ activityApId: text("activity_ap_id").notNull(),
142
+ archivedAt: text("archived_at").notNull().$defaultFn(nowIso),
143
+ },
144
+ (t) => [
145
+ primaryKey({ columns: [t.actorApId, t.activityApId] }),
146
+ index("notification_archived_actor_idx").on(t.actorApId),
147
+ index("notification_archived_actor_archived_idx").on(
148
+ t.actorApId,
149
+ t.archivedAt,
150
+ ),
151
+ ],
152
+ );
153
+
154
+ // ---------------------------------------------------------------------------
155
+ // DM_TYPING
156
+ // ---------------------------------------------------------------------------
157
+
158
+ export const dmTyping = sqliteTable(
159
+ "dm_typing",
160
+ {
161
+ actorApId: text("actor_ap_id").notNull(),
162
+ recipientApId: text("recipient_ap_id").notNull(),
163
+ lastTypedAt: text("last_typed_at").notNull(),
164
+ },
165
+ (t) => [
166
+ primaryKey({ columns: [t.actorApId, t.recipientApId] }),
167
+ index("dm_typing_recipient_typed_idx").on(t.recipientApId, t.lastTypedAt),
168
+ ],
169
+ );
170
+
171
+ // ---------------------------------------------------------------------------
172
+ // DM_READ_STATUS
173
+ // ---------------------------------------------------------------------------
174
+
175
+ export const dmReadStatus = sqliteTable(
176
+ "dm_read_status",
177
+ {
178
+ actorApId: text("actor_ap_id").notNull(),
179
+ conversationId: text("conversation_id").notNull(),
180
+ lastReadAt: text("last_read_at").notNull().$defaultFn(nowIso),
181
+ },
182
+ (t) => [
183
+ primaryKey({ columns: [t.actorApId, t.conversationId] }),
184
+ index("dm_read_status_actor_idx").on(t.actorApId),
185
+ index("dm_read_status_actor_read_idx").on(t.actorApId, t.lastReadAt),
186
+ ],
187
+ );
188
+
189
+ // ---------------------------------------------------------------------------
190
+ // DM_COMMUNITY_READ_STATUS
191
+ // ---------------------------------------------------------------------------
192
+
193
+ export const dmCommunityReadStatus = sqliteTable(
194
+ "dm_community_read_status",
195
+ {
196
+ actorApId: text("actor_ap_id").notNull(),
197
+ communityApId: text("community_ap_id").notNull(),
198
+ lastReadAt: text("last_read_at").notNull().$defaultFn(nowIso),
199
+ },
200
+ (t) => [
201
+ primaryKey({ columns: [t.actorApId, t.communityApId] }),
202
+ index("dm_community_read_status_actor_idx").on(t.actorApId),
203
+ ],
204
+ );
205
+
206
+ // ---------------------------------------------------------------------------
207
+ // DM_ARCHIVED_CONVERSATIONS
208
+ // ---------------------------------------------------------------------------
209
+
210
+ export const dmArchivedConversations = sqliteTable(
211
+ "dm_archived_conversations",
212
+ {
213
+ actorApId: text("actor_ap_id").notNull(),
214
+ conversationId: text("conversation_id").notNull(),
215
+ archivedAt: text("archived_at").$defaultFn(nowIso),
216
+ },
217
+ (t) => [
218
+ primaryKey({ columns: [t.actorApId, t.conversationId] }),
219
+ index("dm_archived_conversations_actor_idx").on(t.actorApId),
220
+ ],
221
+ );
222
+
223
+ // ---------------------------------------------------------------------------
224
+ // MEDIA_UPLOADS
225
+ // ---------------------------------------------------------------------------
226
+
227
+ export const mediaUploads = sqliteTable(
228
+ "media_uploads",
229
+ {
230
+ id: text("id").primaryKey(),
231
+ r2Key: text("r2_key").notNull().unique(),
232
+ uploaderApId: text("uploader_ap_id").notNull(),
233
+ contentType: text("content_type").notNull(),
234
+ size: integer("size").notNull(),
235
+ createdAt: text("created_at").notNull().$defaultFn(nowIso),
236
+ },
237
+ (t) => [
238
+ index("media_uploads_uploader_idx").on(t.uploaderApId),
239
+ index("media_uploads_r2_key_idx").on(t.r2Key),
240
+ ],
241
+ );
@@ -0,0 +1,37 @@
1
+ /**
2
+ * Mobile client tables.
3
+ */
4
+
5
+ import { index, sqliteTable, text, uniqueIndex } from "drizzle-orm/sqlite-core";
6
+ import { nowIsoUtc } from "./date-utils.ts";
7
+ import { actors } from "./actors.ts";
8
+
9
+ export const mobilePushRegistrations = sqliteTable(
10
+ "mobile_push_registrations",
11
+ {
12
+ id: text("id").primaryKey(),
13
+ actorApId: text("actor_ap_id")
14
+ .notNull()
15
+ .references(() => actors.apId),
16
+ product: text("product").notNull(),
17
+ token: text("token").notNull(),
18
+ tokenHash: text("token_hash").notNull(),
19
+ environment: text("environment").notNull().default("production"),
20
+ hostUrl: text("host_url"),
21
+ createdAt: text("created_at").notNull().$defaultFn(nowIsoUtc),
22
+ updatedAt: text("updated_at")
23
+ .notNull()
24
+ .$defaultFn(nowIsoUtc)
25
+ .$onUpdateFn(nowIsoUtc),
26
+ lastSeenAt: text("last_seen_at").notNull().$defaultFn(nowIsoUtc),
27
+ },
28
+ (t) => [
29
+ uniqueIndex("mobile_push_registrations_actor_product_token_idx").on(
30
+ t.actorApId,
31
+ t.product,
32
+ t.tokenHash,
33
+ ),
34
+ index("mobile_push_registrations_actor_idx").on(t.actorApId),
35
+ index("mobile_push_registrations_last_seen_idx").on(t.lastSeenAt),
36
+ ],
37
+ );
@@ -0,0 +1,150 @@
1
+ /**
2
+ * Post/Object-related tables: objects, likes, announces, bookmarks, objectRecipients
3
+ */
4
+
5
+ import {
6
+ index,
7
+ integer,
8
+ primaryKey,
9
+ sqliteTable,
10
+ text,
11
+ } from "drizzle-orm/sqlite-core";
12
+ import { nowIso } from "./date-utils.ts";
13
+
14
+ // ---------------------------------------------------------------------------
15
+ // OBJECTS
16
+ // ---------------------------------------------------------------------------
17
+
18
+ export const objects = sqliteTable(
19
+ "objects",
20
+ {
21
+ apId: text("ap_id").primaryKey(),
22
+ type: text("type").notNull().default("Note"),
23
+ attributedTo: text("attributed_to").notNull(),
24
+ content: text("content").notNull().default(""),
25
+ summary: text("summary"),
26
+ attachmentsJson: text("attachments_json").notNull().default("[]"),
27
+ inReplyTo: text("in_reply_to"),
28
+ conversation: text("conversation"),
29
+ visibility: text("visibility").notNull().default("public"),
30
+ toJson: text("to_json").notNull().default("[]"),
31
+ ccJson: text("cc_json").notNull().default("[]"),
32
+ audienceJson: text("audience_json").notNull().default("[]"),
33
+ tagsJson: text("tags_json").notNull().default("[]"),
34
+ communityApId: text("community_ap_id"),
35
+ endTime: text("end_time"),
36
+ likeCount: integer("like_count").notNull().default(0),
37
+ replyCount: integer("reply_count").notNull().default(0),
38
+ announceCount: integer("announce_count").notNull().default(0),
39
+ shareCount: integer("share_count").notNull().default(0),
40
+ published: text("published").notNull().$defaultFn(nowIso),
41
+ updated: text("updated"),
42
+ isLocal: integer("is_local").notNull().default(1),
43
+ rawJson: text("raw_json"),
44
+ deletedAt: text("deleted_at"),
45
+ },
46
+ (t) => [
47
+ index("objects_attributed_to_idx").on(t.attributedTo),
48
+ index("objects_in_reply_to_idx").on(t.inReplyTo),
49
+ index("objects_community_ap_id_idx").on(t.communityApId),
50
+ index("objects_published_idx").on(t.published),
51
+ index("objects_visibility_idx").on(t.visibility),
52
+ index("objects_end_time_idx").on(t.endTime),
53
+ index("objects_deleted_at_idx").on(t.deletedAt),
54
+ index("objects_attr_published_idx").on(t.attributedTo, t.published),
55
+ index("objects_vis_published_idx").on(t.visibility, t.published),
56
+ index("objects_type_vis_published_idx").on(
57
+ t.type,
58
+ t.visibility,
59
+ t.published,
60
+ ),
61
+ index("objects_conversation_idx").on(t.conversation),
62
+ // Composite for the DM message reader (where conversation=X order by
63
+ // published desc) so the hot, ~4s-polled thread endpoint reads in published
64
+ // order instead of filesorting the conversation's rows on every poll.
65
+ index("objects_conversation_published_idx").on(t.conversation, t.published),
66
+ index("objects_comm_published_idx").on(t.communityApId, t.published),
67
+ // Supports the B0.3 Story scope read path: active community-scoped stories
68
+ // (type='Story' AND community_ap_id=? AND end_time>?).
69
+ index("objects_type_comm_end_idx").on(t.type, t.communityApId, t.endTime),
70
+ index("objects_is_local_idx").on(t.isLocal),
71
+ ],
72
+ );
73
+
74
+ // ---------------------------------------------------------------------------
75
+ // LIKES
76
+ // ---------------------------------------------------------------------------
77
+
78
+ export const likes = sqliteTable(
79
+ "likes",
80
+ {
81
+ actorApId: text("actor_ap_id").notNull(),
82
+ objectApId: text("object_ap_id").notNull(),
83
+ activityApId: text("activity_ap_id"),
84
+ createdAt: text("created_at").notNull().$defaultFn(nowIso),
85
+ },
86
+ (t) => [
87
+ primaryKey({ columns: [t.actorApId, t.objectApId] }),
88
+ index("likes_object_idx").on(t.objectApId),
89
+ index("likes_actor_idx").on(t.actorApId),
90
+ index("likes_actor_object_idx").on(t.actorApId, t.objectApId),
91
+ ],
92
+ );
93
+
94
+ // ---------------------------------------------------------------------------
95
+ // ANNOUNCES
96
+ // ---------------------------------------------------------------------------
97
+
98
+ export const announces = sqliteTable(
99
+ "announces",
100
+ {
101
+ actorApId: text("actor_ap_id").notNull(),
102
+ objectApId: text("object_ap_id").notNull(),
103
+ activityApId: text("activity_ap_id"),
104
+ createdAt: text("created_at").notNull().$defaultFn(nowIso),
105
+ },
106
+ (t) => [
107
+ primaryKey({ columns: [t.actorApId, t.objectApId] }),
108
+ index("announces_object_idx").on(t.objectApId),
109
+ index("announces_actor_idx").on(t.actorApId),
110
+ index("announces_actor_object_idx").on(t.actorApId, t.objectApId),
111
+ ],
112
+ );
113
+
114
+ // ---------------------------------------------------------------------------
115
+ // BOOKMARKS
116
+ // ---------------------------------------------------------------------------
117
+
118
+ export const bookmarks = sqliteTable(
119
+ "bookmarks",
120
+ {
121
+ actorApId: text("actor_ap_id").notNull(),
122
+ objectApId: text("object_ap_id").notNull(),
123
+ createdAt: text("created_at").notNull().$defaultFn(nowIso),
124
+ },
125
+ (t) => [
126
+ primaryKey({ columns: [t.actorApId, t.objectApId] }),
127
+ index("bookmarks_actor_idx").on(t.actorApId),
128
+ ],
129
+ );
130
+
131
+ // ---------------------------------------------------------------------------
132
+ // OBJECT_RECIPIENTS
133
+ // ---------------------------------------------------------------------------
134
+
135
+ export const objectRecipients = sqliteTable(
136
+ "object_recipients",
137
+ {
138
+ objectApId: text("object_ap_id").notNull(),
139
+ recipientApId: text("recipient_ap_id").notNull(),
140
+ type: text("type").notNull(),
141
+ createdAt: text("created_at").notNull().$defaultFn(nowIso),
142
+ },
143
+ (t) => [
144
+ primaryKey({ columns: [t.objectApId, t.recipientApId] }),
145
+ index("object_recipients_recipient_created_idx").on(
146
+ t.recipientApId,
147
+ t.createdAt,
148
+ ),
149
+ ],
150
+ );
@@ -0,0 +1,266 @@
1
+ /**
2
+ * Drizzle ORM Relations definitions
3
+ */
4
+
5
+ import { relations } from "drizzle-orm";
6
+ import { actorCache, actors, sessions } from "./actors.ts";
7
+ import {
8
+ announces,
9
+ bookmarks,
10
+ likes,
11
+ objectRecipients,
12
+ objects,
13
+ } from "./posts.ts";
14
+ import { blocks, follows, mutes } from "./social.ts";
15
+ import {
16
+ communities,
17
+ communityInvites,
18
+ communityJoinRequests,
19
+ communityMembers,
20
+ } from "./communities.ts";
21
+ import { storyShares, storyViews, storyVotes } from "./stories.ts";
22
+ import { activities, inbox } from "./messaging.ts";
23
+
24
+ // ===========================================================================
25
+ // RELATIONS
26
+ // ===========================================================================
27
+
28
+ export const actorsRelations = relations(actors, ({ many }) => ({
29
+ sessions: many(sessions),
30
+ objectsAuthored: many(objects),
31
+ followsAsFollower: many(follows, { relationName: "followerFollows" }),
32
+ followsAsFollowing: many(follows, { relationName: "followingFollows" }),
33
+ likes: many(likes),
34
+ announces: many(announces),
35
+ bookmarks: many(bookmarks),
36
+ blocksAsBlocker: many(blocks, { relationName: "blockerBlocks" }),
37
+ blocksAsBlocked: many(blocks, { relationName: "blockedBlocks" }),
38
+ mutesAsMuter: many(mutes, { relationName: "muterMutes" }),
39
+ mutesAsMuted: many(mutes, { relationName: "mutedMutes" }),
40
+ activities: many(activities),
41
+ storyViews: many(storyViews),
42
+ storyVotes: many(storyVotes),
43
+ storyShares: many(storyShares),
44
+ communityMemberships: many(communityMembers),
45
+ communityJoinRequests: many(communityJoinRequests),
46
+ communityInvites: many(communityInvites),
47
+ inboxItems: many(inbox),
48
+ objectRecipients: many(objectRecipients),
49
+ }));
50
+
51
+ export const objectsRelations = relations(objects, ({ one, many }) => ({
52
+ author: one(actors, {
53
+ fields: [objects.attributedTo],
54
+ references: [actors.apId],
55
+ }),
56
+ community: one(communities, {
57
+ fields: [objects.communityApId],
58
+ references: [communities.apId],
59
+ }),
60
+ likes: many(likes),
61
+ announces: many(announces),
62
+ bookmarks: many(bookmarks),
63
+ storyViews: many(storyViews),
64
+ storyVotes: many(storyVotes),
65
+ storyShares: many(storyShares),
66
+ recipients: many(objectRecipients),
67
+ activities: many(activities),
68
+ }));
69
+
70
+ export const followsRelations = relations(follows, ({ one }) => ({
71
+ follower: one(actors, {
72
+ fields: [follows.followerApId],
73
+ references: [actors.apId],
74
+ relationName: "followerFollows",
75
+ }),
76
+ following: one(actors, {
77
+ fields: [follows.followingApId],
78
+ references: [actors.apId],
79
+ relationName: "followingFollows",
80
+ }),
81
+ }));
82
+
83
+ export const likesRelations = relations(likes, ({ one }) => ({
84
+ actor: one(actors, {
85
+ fields: [likes.actorApId],
86
+ references: [actors.apId],
87
+ }),
88
+ object: one(objects, {
89
+ fields: [likes.objectApId],
90
+ references: [objects.apId],
91
+ }),
92
+ }));
93
+
94
+ export const announcesRelations = relations(announces, ({ one }) => ({
95
+ actor: one(actors, {
96
+ fields: [announces.actorApId],
97
+ references: [actors.apId],
98
+ }),
99
+ object: one(objects, {
100
+ fields: [announces.objectApId],
101
+ references: [objects.apId],
102
+ }),
103
+ }));
104
+
105
+ export const bookmarksRelations = relations(bookmarks, ({ one }) => ({
106
+ actor: one(actors, {
107
+ fields: [bookmarks.actorApId],
108
+ references: [actors.apId],
109
+ }),
110
+ object: one(objects, {
111
+ fields: [bookmarks.objectApId],
112
+ references: [objects.apId],
113
+ }),
114
+ }));
115
+
116
+ export const blocksRelations = relations(blocks, ({ one }) => ({
117
+ blocker: one(actors, {
118
+ fields: [blocks.blockerApId],
119
+ references: [actors.apId],
120
+ relationName: "blockerBlocks",
121
+ }),
122
+ blocked: one(actors, {
123
+ fields: [blocks.blockedApId],
124
+ references: [actors.apId],
125
+ relationName: "blockedBlocks",
126
+ }),
127
+ }));
128
+
129
+ export const mutesRelations = relations(mutes, ({ one }) => ({
130
+ muter: one(actors, {
131
+ fields: [mutes.muterApId],
132
+ references: [actors.apId],
133
+ relationName: "muterMutes",
134
+ }),
135
+ muted: one(actors, {
136
+ fields: [mutes.mutedApId],
137
+ references: [actors.apId],
138
+ relationName: "mutedMutes",
139
+ }),
140
+ }));
141
+
142
+ export const activitiesRelations = relations(activities, ({ one, many }) => ({
143
+ actor: one(actors, {
144
+ fields: [activities.actorApId],
145
+ references: [actors.apId],
146
+ }),
147
+ object: one(objects, {
148
+ fields: [activities.objectApId],
149
+ references: [objects.apId],
150
+ }),
151
+ inboxItems: many(inbox),
152
+ }));
153
+
154
+ export const communitiesRelations = relations(communities, ({ many }) => ({
155
+ members: many(communityMembers),
156
+ objects: many(objects),
157
+ joinRequests: many(communityJoinRequests),
158
+ invites: many(communityInvites),
159
+ }));
160
+
161
+ export const communityMembersRelations = relations(
162
+ communityMembers,
163
+ ({ one }) => ({
164
+ community: one(communities, {
165
+ fields: [communityMembers.communityApId],
166
+ references: [communities.apId],
167
+ }),
168
+ actor: one(actors, {
169
+ fields: [communityMembers.actorApId],
170
+ references: [actors.apId],
171
+ }),
172
+ }),
173
+ );
174
+
175
+ export const communityJoinRequestsRelations = relations(
176
+ communityJoinRequests,
177
+ ({ one }) => ({
178
+ community: one(communities, {
179
+ fields: [communityJoinRequests.communityApId],
180
+ references: [communities.apId],
181
+ }),
182
+ actor: one(actors, {
183
+ fields: [communityJoinRequests.actorApId],
184
+ references: [actors.apId],
185
+ }),
186
+ }),
187
+ );
188
+
189
+ export const communityInvitesRelations = relations(
190
+ communityInvites,
191
+ ({ one }) => ({
192
+ community: one(communities, {
193
+ fields: [communityInvites.communityApId],
194
+ references: [communities.apId],
195
+ }),
196
+ invitedBy: one(actors, {
197
+ fields: [communityInvites.invitedByApId],
198
+ references: [actors.apId],
199
+ }),
200
+ }),
201
+ );
202
+
203
+ export const objectRecipientsRelations = relations(
204
+ objectRecipients,
205
+ ({ one }) => ({
206
+ object: one(objects, {
207
+ fields: [objectRecipients.objectApId],
208
+ references: [objects.apId],
209
+ }),
210
+ recipient: one(actors, {
211
+ fields: [objectRecipients.recipientApId],
212
+ references: [actors.apId],
213
+ }),
214
+ }),
215
+ );
216
+
217
+ export const inboxRelations = relations(inbox, ({ one }) => ({
218
+ actor: one(actors, {
219
+ fields: [inbox.actorApId],
220
+ references: [actors.apId],
221
+ }),
222
+ activity: one(activities, {
223
+ fields: [inbox.activityApId],
224
+ references: [activities.apId],
225
+ }),
226
+ }));
227
+
228
+ export const sessionsRelations = relations(sessions, ({ one }) => ({
229
+ member: one(actors, {
230
+ fields: [sessions.memberId],
231
+ references: [actors.apId],
232
+ }),
233
+ }));
234
+
235
+ export const storyViewsRelations = relations(storyViews, ({ one }) => ({
236
+ actor: one(actors, {
237
+ fields: [storyViews.actorApId],
238
+ references: [actors.apId],
239
+ }),
240
+ story: one(objects, {
241
+ fields: [storyViews.storyApId],
242
+ references: [objects.apId],
243
+ }),
244
+ }));
245
+
246
+ export const storyVotesRelations = relations(storyVotes, ({ one }) => ({
247
+ actor: one(actors, {
248
+ fields: [storyVotes.actorApId],
249
+ references: [actors.apId],
250
+ }),
251
+ story: one(objects, {
252
+ fields: [storyVotes.storyApId],
253
+ references: [objects.apId],
254
+ }),
255
+ }));
256
+
257
+ export const storySharesRelations = relations(storyShares, ({ one }) => ({
258
+ actor: one(actors, {
259
+ fields: [storyShares.actorApId],
260
+ references: [actors.apId],
261
+ }),
262
+ story: one(objects, {
263
+ fields: [storyShares.storyApId],
264
+ references: [objects.apId],
265
+ }),
266
+ }));