@hiai-gg/hiai-docs 0.0.1

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 (216) hide show
  1. package/.all-contributorsrc +18 -0
  2. package/.claude/settings.local.json +61 -0
  3. package/.dockerignore +113 -0
  4. package/.env.example +68 -0
  5. package/.github/FUNDING.yml +5 -0
  6. package/.github/ISSUE_TEMPLATE/bug_report.md +74 -0
  7. package/.github/ISSUE_TEMPLATE/feature_request.md +78 -0
  8. package/.github/dependabot.yml +136 -0
  9. package/.github/pull_request_template.md +96 -0
  10. package/.github/workflows/ci.yml +283 -0
  11. package/AGENTS.md +237 -0
  12. package/CODE_OF_CONDUCT.md +134 -0
  13. package/CONTRIBUTING.md +77 -0
  14. package/Caddyfile +50 -0
  15. package/Dockerfile.backend +60 -0
  16. package/LICENSE +21 -0
  17. package/README.md +284 -0
  18. package/RELEASE_CHECKLIST.md +34 -0
  19. package/SECURITY.md +60 -0
  20. package/backend/package.json +43 -0
  21. package/backend/src/__tests__/auth-helpers.test.ts +51 -0
  22. package/backend/src/__tests__/chunker.test.ts +65 -0
  23. package/backend/src/__tests__/config.test.ts +91 -0
  24. package/backend/src/__tests__/csrf.test.ts +91 -0
  25. package/backend/src/__tests__/embedding.test.ts +48 -0
  26. package/backend/src/__tests__/rate-limit.test.ts +46 -0
  27. package/backend/src/__tests__/routes.test.ts +38 -0
  28. package/backend/src/__tests__/schema.test.ts +31 -0
  29. package/backend/src/__tests__/validation.test.ts +556 -0
  30. package/backend/src/api/middleware/auth.ts +56 -0
  31. package/backend/src/api/middleware/csrf.ts +91 -0
  32. package/backend/src/api/middleware/rate-limit.ts +77 -0
  33. package/backend/src/api/middleware/webhook-verify.ts +22 -0
  34. package/backend/src/api/routes/attachments.ts +280 -0
  35. package/backend/src/api/routes/auth.ts +52 -0
  36. package/backend/src/api/routes/collaboration.ts +121 -0
  37. package/backend/src/api/routes/documents.ts +664 -0
  38. package/backend/src/api/routes/folders.ts +226 -0
  39. package/backend/src/api/routes/search.ts +354 -0
  40. package/backend/src/api/routes/share.ts +512 -0
  41. package/backend/src/api/routes/tags.ts +247 -0
  42. package/backend/src/api/routes/versions.ts +99 -0
  43. package/backend/src/api/routes/webhooks.ts +43 -0
  44. package/backend/src/embedding/chunker.ts +74 -0
  45. package/backend/src/embedding/index.ts +117 -0
  46. package/backend/src/embedding/providers/ollama.ts +63 -0
  47. package/backend/src/embedding/providers/openrouter.ts +71 -0
  48. package/backend/src/embedding/utils.ts +13 -0
  49. package/backend/src/embedding/worker.ts +89 -0
  50. package/backend/src/index.ts +147 -0
  51. package/backend/src/lib/auth-helpers.ts +27 -0
  52. package/backend/src/lib/auth.ts +35 -0
  53. package/backend/src/lib/config.ts +73 -0
  54. package/backend/src/lib/db.ts +7 -0
  55. package/backend/src/lib/embedding-queue.ts +12 -0
  56. package/backend/src/lib/logger.ts +18 -0
  57. package/backend/src/lib/markdown-to-doc.ts +45 -0
  58. package/backend/src/lib/minio.ts +46 -0
  59. package/backend/src/lib/redis.ts +19 -0
  60. package/backend/src/lib/yjs-provider.ts +182 -0
  61. package/backend/tests/integration/_harness.ts +754 -0
  62. package/backend/tests/integration/auth.test.ts +296 -0
  63. package/backend/tests/integration/routes.documents.test.ts +459 -0
  64. package/backend/tests/integration/routes.folders.test.ts +337 -0
  65. package/backend/tests/integration/routes.search.test.ts +322 -0
  66. package/backend/tests/integration/routes.share.test.ts +773 -0
  67. package/backend/tests/integration/routes.tags.test.ts +425 -0
  68. package/backend/tests/integration/routes.versions.test.ts +233 -0
  69. package/backend/tsconfig.json +18 -0
  70. package/docker-compose.yml +218 -0
  71. package/docs/API.md +328 -0
  72. package/docs/ARCHITECTURE.md +75 -0
  73. package/docs/DEPLOYMENT.md +113 -0
  74. package/docs/PRODUCTION_STATUS.md +61 -0
  75. package/docs/openapi.json +385 -0
  76. package/frontend/.svelte-kit.old/ambient.d.ts +230 -0
  77. package/frontend/.svelte-kit.old/env.d.ts +1 -0
  78. package/frontend/.svelte-kit.old/generated/client/app.js +46 -0
  79. package/frontend/.svelte-kit.old/generated/client/matchers.js +1 -0
  80. package/frontend/.svelte-kit.old/generated/client/nodes/0.js +3 -0
  81. package/frontend/.svelte-kit.old/generated/client/nodes/1.js +1 -0
  82. package/frontend/.svelte-kit.old/generated/client/nodes/10.js +3 -0
  83. package/frontend/.svelte-kit.old/generated/client/nodes/2.js +1 -0
  84. package/frontend/.svelte-kit.old/generated/client/nodes/3.js +1 -0
  85. package/frontend/.svelte-kit.old/generated/client/nodes/4.js +1 -0
  86. package/frontend/.svelte-kit.old/generated/client/nodes/5.js +3 -0
  87. package/frontend/.svelte-kit.old/generated/client/nodes/6.js +1 -0
  88. package/frontend/.svelte-kit.old/generated/client/nodes/7.js +3 -0
  89. package/frontend/.svelte-kit.old/generated/client/nodes/8.js +1 -0
  90. package/frontend/.svelte-kit.old/generated/client/nodes/9.js +3 -0
  91. package/frontend/.svelte-kit.old/generated/root.js +3 -0
  92. package/frontend/.svelte-kit.old/generated/root.svelte +80 -0
  93. package/frontend/.svelte-kit.old/generated/server/internal.js +55 -0
  94. package/frontend/.svelte-kit.old/non-ambient.d.ts +59 -0
  95. package/frontend/.svelte-kit.old/tsconfig.json +59 -0
  96. package/frontend/.svelte-kit.old/types/route_meta_data.json +40 -0
  97. package/frontend/.svelte-kit.old/types/src/routes/$types.d.ts +21 -0
  98. package/frontend/.svelte-kit.old/types/src/routes/(app)/$types.d.ts +30 -0
  99. package/frontend/.svelte-kit.old/types/src/routes/(app)/docs/[id]/$types.d.ts +27 -0
  100. package/frontend/.svelte-kit.old/types/src/routes/(app)/docs/[id]/proxy+page.ts +25 -0
  101. package/frontend/.svelte-kit.old/types/src/routes/api/[...path]/$types.d.ts +10 -0
  102. package/frontend/.svelte-kit.old/types/src/routes/folders/[id]/$types.d.ts +27 -0
  103. package/frontend/.svelte-kit.old/types/src/routes/folders/[id]/proxy+page.ts +15 -0
  104. package/frontend/.svelte-kit.old/types/src/routes/login/$types.d.ts +17 -0
  105. package/frontend/.svelte-kit.old/types/src/routes/register/$types.d.ts +17 -0
  106. package/frontend/.svelte-kit.old/types/src/routes/s/[token]/$types.d.ts +20 -0
  107. package/frontend/.svelte-kit.old/types/src/routes/s/[token]/proxy+page.ts +6 -0
  108. package/frontend/.svelte-kit.old/types/src/routes/search/$types.d.ts +19 -0
  109. package/frontend/.svelte-kit.old/types/src/routes/search/proxy+page.ts +26 -0
  110. package/frontend/.svelte-kit.old/types/src/routes/settings/$types.d.ts +17 -0
  111. package/frontend/Dockerfile +44 -0
  112. package/frontend/biome.json +40 -0
  113. package/frontend/components.json +18 -0
  114. package/frontend/messages/en.json +434 -0
  115. package/frontend/package.json +70 -0
  116. package/frontend/project.inlang/settings.json +12 -0
  117. package/frontend/src/app.css +6 -0
  118. package/frontend/src/app.d.ts +13 -0
  119. package/frontend/src/app.html +30 -0
  120. package/frontend/src/hooks.server.ts +10 -0
  121. package/frontend/src/hooks.ts +10 -0
  122. package/frontend/src/lib/api/attachments.ts +45 -0
  123. package/frontend/src/lib/api/client.test.ts +15 -0
  124. package/frontend/src/lib/api/client.ts +57 -0
  125. package/frontend/src/lib/api/documents.ts +83 -0
  126. package/frontend/src/lib/api/folders.ts +180 -0
  127. package/frontend/src/lib/api/search.test.ts +52 -0
  128. package/frontend/src/lib/api/search.ts +128 -0
  129. package/frontend/src/lib/api/settings.ts +95 -0
  130. package/frontend/src/lib/api/share.ts +71 -0
  131. package/frontend/src/lib/api/tags.test.ts +91 -0
  132. package/frontend/src/lib/api/tags.ts +87 -0
  133. package/frontend/src/lib/auth-client.ts +10 -0
  134. package/frontend/src/lib/collaboration.ts +63 -0
  135. package/frontend/src/lib/components/AttachmentUpload.svelte +110 -0
  136. package/frontend/src/lib/components/DatePicker.svelte +322 -0
  137. package/frontend/src/lib/components/DocumentCard.svelte +166 -0
  138. package/frontend/src/lib/components/EmptyState.svelte +49 -0
  139. package/frontend/src/lib/components/FolderCard.svelte +93 -0
  140. package/frontend/src/lib/components/ScrollToTop.svelte +72 -0
  141. package/frontend/src/lib/components/SearchBar.svelte +47 -0
  142. package/frontend/src/lib/components/SearchResult.svelte +115 -0
  143. package/frontend/src/lib/components/SettingsDialog.svelte +271 -0
  144. package/frontend/src/lib/components/ShareDialog.svelte +158 -0
  145. package/frontend/src/lib/components/ShareLink.svelte +98 -0
  146. package/frontend/src/lib/components/TagCreateDialog.svelte +284 -0
  147. package/frontend/src/lib/components/VersionDiff.svelte +55 -0
  148. package/frontend/src/lib/components/VersionHistory.svelte +96 -0
  149. package/frontend/src/lib/components/editor/DocumentTitle.svelte +87 -0
  150. package/frontend/src/lib/components/editor/EditorToolbar.svelte +1367 -0
  151. package/frontend/src/lib/components/editor/HiAiEditor.svelte +531 -0
  152. package/frontend/src/lib/components/editor/LinkDialog.svelte +134 -0
  153. package/frontend/src/lib/components/editor/MarkdownToggle.svelte +88 -0
  154. package/frontend/src/lib/components/editor/editorExtensions.ts +53 -0
  155. package/frontend/src/lib/components/editor/markdown.ts +38 -0
  156. package/frontend/src/lib/components/sidebar/FolderTree.svelte +731 -0
  157. package/frontend/src/lib/components/sidebar/RecentDocs.svelte +311 -0
  158. package/frontend/src/lib/components/sidebar/Sidebar.svelte +156 -0
  159. package/frontend/src/lib/components/sidebar/TagList.svelte +200 -0
  160. package/frontend/src/lib/components/ui/confirm-dialog/ConfirmDialog.svelte +76 -0
  161. package/frontend/src/lib/components/ui/confirm-dialog/index.ts +1 -0
  162. package/frontend/src/lib/stores/tag-store.svelte.ts +56 -0
  163. package/frontend/src/lib/stores/theme.svelte.ts +97 -0
  164. package/frontend/src/lib/svelte.d.ts +6 -0
  165. package/frontend/src/lib/types.ts +44 -0
  166. package/frontend/src/lib/utils/clipboard.ts +17 -0
  167. package/frontend/src/lib/utils/strip-markdown.ts +59 -0
  168. package/frontend/src/lib/utils.ts +33 -0
  169. package/frontend/src/routes/(app)/+layout.svelte +17 -0
  170. package/frontend/src/routes/(app)/+page.server.ts +10 -0
  171. package/frontend/src/routes/(app)/+page.svelte +303 -0
  172. package/frontend/src/routes/(app)/docs/[id]/+page.server.ts +10 -0
  173. package/frontend/src/routes/(app)/docs/[id]/+page.svelte +1108 -0
  174. package/frontend/src/routes/(app)/docs/[id]/+page.ts +24 -0
  175. package/frontend/src/routes/(app)/search/+page.svelte +593 -0
  176. package/frontend/src/routes/(app)/search/+page.ts +25 -0
  177. package/frontend/src/routes/+error.svelte +12 -0
  178. package/frontend/src/routes/+layout.svelte +18 -0
  179. package/frontend/src/routes/+layout.ts +2 -0
  180. package/frontend/src/routes/api/[...path]/+server.ts +111 -0
  181. package/frontend/src/routes/folders/[id]/+page.server.ts +10 -0
  182. package/frontend/src/routes/folders/[id]/+page.svelte +319 -0
  183. package/frontend/src/routes/folders/[id]/+page.ts +14 -0
  184. package/frontend/src/routes/login/+page.svelte +90 -0
  185. package/frontend/src/routes/register/+page.svelte +97 -0
  186. package/frontend/src/routes/s/[token]/+page.svelte +496 -0
  187. package/frontend/src/routes/s/[token]/+page.ts +5 -0
  188. package/frontend/src/routes/settings/+page.svelte +175 -0
  189. package/frontend/static/favicon.png +0 -0
  190. package/frontend/static/logo.png +0 -0
  191. package/frontend/svelte.config.js +15 -0
  192. package/frontend/tsconfig.json +15 -0
  193. package/frontend/vite.config.ts +25 -0
  194. package/init.sql +9 -0
  195. package/logo.png +0 -0
  196. package/package.json +39 -0
  197. package/package.public.json +39 -0
  198. package/packages/db/drizzle.config.ts +10 -0
  199. package/packages/db/package.json +30 -0
  200. package/packages/db/src/client.ts +9 -0
  201. package/packages/db/src/index.ts +2 -0
  202. package/packages/db/src/migrations/0000_nice_bedlam.sql +165 -0
  203. package/packages/db/src/migrations/0001_w2_3_test.sql +5 -0
  204. package/packages/db/src/migrations/0002_rename_content_json.sql +2 -0
  205. package/packages/db/src/migrations/meta/0000_snapshot.json +1331 -0
  206. package/packages/db/src/migrations/meta/0001_snapshot.json +1399 -0
  207. package/packages/db/src/migrations/meta/0002_snapshot.json +1399 -0
  208. package/packages/db/src/migrations/meta/_journal.json +27 -0
  209. package/packages/db/src/schema.ts +378 -0
  210. package/packages/db/tsconfig.json +17 -0
  211. package/scripts/export-openapi.ts +37 -0
  212. package/scripts/health-check.sh +75 -0
  213. package/scripts/migrate.sh +135 -0
  214. package/scripts/prework_backup.sh +25 -0
  215. package/scripts/release.sh +83 -0
  216. package/tsconfig.json +25 -0
@@ -0,0 +1,27 @@
1
+ {
2
+ "version": "7",
3
+ "dialect": "postgresql",
4
+ "entries": [
5
+ {
6
+ "idx": 0,
7
+ "version": "7",
8
+ "when": 1780763526911,
9
+ "tag": "0000_nice_bedlam",
10
+ "breakpoints": true
11
+ },
12
+ {
13
+ "idx": 1,
14
+ "version": "7",
15
+ "when": 1781110876591,
16
+ "tag": "0001_w2_3_test",
17
+ "breakpoints": true
18
+ },
19
+ {
20
+ "idx": 2,
21
+ "version": "7",
22
+ "when": 1781197276591,
23
+ "tag": "0002_rename_content_json",
24
+ "breakpoints": true
25
+ }
26
+ ]
27
+ }
@@ -0,0 +1,378 @@
1
+ import { pgTable, uuid, text, timestamp, bigint, jsonb, index, uniqueIndex, customType, boolean, type AnyPgColumn } from "drizzle-orm/pg-core";
2
+
3
+ // pgvector vector type — maps to PostgreSQL vector(n) column
4
+ const vector = customType<{ data: number[] }>({
5
+ dataType() {
6
+ return "vector(1024)";
7
+ },
8
+ toDriver(value: number[]) {
9
+ return JSON.stringify(value);
10
+ },
11
+ fromDriver(value: unknown) {
12
+ if (typeof value === "string") return JSON.parse(value) as number[];
13
+ return value as number[];
14
+ },
15
+ });
16
+
17
+ // PostgreSQL tsvector type — used for documents.search_vector full-text search
18
+ const tsvector = customType<{ data: string }>({
19
+ dataType() {
20
+ return "tsvector";
21
+ },
22
+ });
23
+
24
+ import { relations, sql } from "drizzle-orm";
25
+
26
+ // ============================================
27
+ // users — managed by Better Auth
28
+ // ============================================
29
+ export const users = pgTable("users", {
30
+ id: uuid("id").primaryKey().defaultRandom(),
31
+ email: text("email").notNull().unique(),
32
+ name: text("name"),
33
+ emailVerified: boolean("email_verified").default(false),
34
+ image: text("image"),
35
+ createdAt: timestamp("created_at").defaultNow().notNull(),
36
+ updatedAt: timestamp("updated_at").defaultNow().notNull(),
37
+ });
38
+
39
+ // ============================================
40
+ // sessions — managed by Better Auth
41
+ // ============================================
42
+ export const sessions = pgTable("sessions", {
43
+ id: uuid("id").primaryKey().defaultRandom(),
44
+ userId: uuid("user_id").notNull().references(() => users.id, { onDelete: "cascade" }),
45
+ token: text("token").notNull().unique(),
46
+ expiresAt: timestamp("expires_at").notNull(),
47
+ ipAddress: text("ip_address"),
48
+ userAgent: text("user_agent"),
49
+ createdAt: timestamp("created_at").defaultNow().notNull(),
50
+ updatedAt: timestamp("updated_at").defaultNow().notNull(),
51
+ }, (table) => [
52
+ index("sessions_user_id_idx").on(table.userId),
53
+ ]);
54
+
55
+ // ============================================
56
+ // accounts — managed by Better Auth
57
+ // ============================================
58
+ export const accounts = pgTable("accounts", {
59
+ id: uuid("id").primaryKey().defaultRandom(),
60
+ userId: uuid("user_id").notNull().references(() => users.id, { onDelete: "cascade" }),
61
+ accountId: text("account_id").notNull(),
62
+ providerId: text("provider_id").notNull(),
63
+ accessToken: text("access_token"),
64
+ refreshToken: text("refresh_token"),
65
+ accessTokenExpiresAt: timestamp("access_token_expires_at"),
66
+ refreshTokenExpiresAt: timestamp("refresh_token_expires_at"),
67
+ scope: text("scope"),
68
+ password: text("password"),
69
+ createdAt: timestamp("created_at").defaultNow().notNull(),
70
+ updatedAt: timestamp("updated_at").defaultNow().notNull(),
71
+ }, (table) => [
72
+ index("accounts_user_id_idx").on(table.userId),
73
+ uniqueIndex("accounts_provider_account_idx").on(table.providerId, table.accountId),
74
+ ]);
75
+
76
+ // ============================================
77
+ // verifications — managed by Better Auth
78
+ // ============================================
79
+ export const verifications = pgTable("verifications", {
80
+ id: uuid("id").primaryKey().defaultRandom(),
81
+ identifier: text("identifier").notNull(),
82
+ value: text("value").notNull(),
83
+ expiresAt: timestamp("expires_at").notNull(),
84
+ createdAt: timestamp("created_at").defaultNow().notNull(),
85
+ updatedAt: timestamp("updated_at").defaultNow().notNull(),
86
+ }, (table) => [
87
+ index("verifications_identifier_idx").on(table.identifier),
88
+ ]);
89
+
90
+ // ============================================
91
+ // folders — hierarchical folder structure
92
+ // ============================================
93
+ export const folders = pgTable(
94
+ "folders",
95
+ {
96
+ id: uuid("id").primaryKey().defaultRandom(),
97
+ ownerId: uuid("owner_id")
98
+ .notNull()
99
+ .references(() => users.id, { onDelete: "cascade" }),
100
+ parentId: uuid("parent_id").references((): AnyPgColumn => folders.id, {
101
+ onDelete: "set null",
102
+ }),
103
+ name: text("name").notNull(),
104
+ createdAt: timestamp("created_at").defaultNow().notNull(),
105
+ updatedAt: timestamp("updated_at").defaultNow().notNull(),
106
+ },
107
+ (table) => [
108
+ index("folders_owner_id_idx").on(table.ownerId),
109
+ index("folders_parent_id_idx").on(table.parentId),
110
+ ]
111
+ );
112
+
113
+ // Self-referencing for parent folder
114
+ export const folderRelations = relations(folders, ({ one, many }) => ({
115
+ owner: one(users, { fields: [folders.ownerId], references: [users.id] }),
116
+ parent: one(folders, {
117
+ fields: [folders.parentId],
118
+ references: [folders.id],
119
+ relationName: "folderParent",
120
+ }),
121
+ children: many(folders, { relationName: "folderParent" }),
122
+ documents: many(documents),
123
+ }));
124
+
125
+ // ============================================
126
+ // documents — core content
127
+ // ============================================
128
+ export const documents = pgTable(
129
+ "documents",
130
+ {
131
+ id: uuid("id").primaryKey().defaultRandom(),
132
+ ownerId: uuid("owner_id")
133
+ .notNull()
134
+ .references(() => users.id, { onDelete: "cascade" }),
135
+ folderId: uuid("folder_id").references(() => folders.id, {
136
+ onDelete: "set null",
137
+ }),
138
+ title: text("title").notNull().default("Untitled"),
139
+ content: text("content").default(""),
140
+ contentJson: jsonb("content_json"),
141
+ metadata: jsonb("metadata"),
142
+ searchVector: tsvector("search_vector").generatedAlwaysAs(
143
+ sql`to_tsvector('english', COALESCE(title, '') || ' ' || COALESCE(content, ''))`
144
+ ),
145
+ createdAt: timestamp("created_at").defaultNow().notNull(),
146
+ updatedAt: timestamp("updated_at").defaultNow().notNull(),
147
+ },
148
+ (table) => [
149
+ index("documents_owner_id_idx").on(table.ownerId),
150
+ index("documents_folder_id_idx").on(table.folderId),
151
+ index("documents_created_at_idx").on(table.createdAt),
152
+ index("idx_documents_search_vector").using("gin", table.searchVector),
153
+ index("idx_documents_title_trgm").using(
154
+ "gin",
155
+ sql`${table.title} gin_trgm_ops`
156
+ ),
157
+ ]
158
+ );
159
+
160
+ export const documentRelations = relations(documents, ({ one, many }) => ({
161
+ owner: one(users, { fields: [documents.ownerId], references: [users.id] }),
162
+ folder: one(folders, {
163
+ fields: [documents.folderId],
164
+ references: [folders.id],
165
+ }),
166
+ tags: many(documentTags),
167
+ attachments: many(attachments),
168
+ versions: many(versions),
169
+ }));
170
+
171
+ // ============================================
172
+ // tags — document tags
173
+ // ============================================
174
+ export const tags = pgTable(
175
+ "tags",
176
+ {
177
+ id: uuid("id").primaryKey().defaultRandom(),
178
+ ownerId: uuid("owner_id")
179
+ .notNull()
180
+ .references(() => users.id, { onDelete: "cascade" }),
181
+ name: text("name").notNull(),
182
+ color: text("color"),
183
+ createdAt: timestamp("created_at").defaultNow().notNull(),
184
+ },
185
+ (table) => [
186
+ index("tags_owner_id_idx").on(table.ownerId),
187
+ uniqueIndex("tags_owner_name_idx").on(table.ownerId, table.name),
188
+ ]
189
+ );
190
+
191
+ export const tagRelations = relations(tags, ({ many }) => ({
192
+ documents: many(documentTags),
193
+ }));
194
+
195
+ // ============================================
196
+ // document_tags — many-to-many
197
+ // ============================================
198
+ export const documentTags = pgTable(
199
+ "document_tags",
200
+ {
201
+ documentId: uuid("document_id")
202
+ .notNull()
203
+ .references(() => documents.id, { onDelete: "cascade" }),
204
+ tagId: uuid("tag_id")
205
+ .notNull()
206
+ .references(() => tags.id, { onDelete: "cascade" }),
207
+ },
208
+ (table) => [
209
+ uniqueIndex("document_tags_unique_idx").on(table.documentId, table.tagId),
210
+ ]
211
+ );
212
+
213
+ export const documentTagRelations = relations(documentTags, ({ one }) => ({
214
+ document: one(documents, {
215
+ fields: [documentTags.documentId],
216
+ references: [documents.id],
217
+ }),
218
+ tag: one(tags, { fields: [documentTags.tagId], references: [tags.id] }),
219
+ }));
220
+
221
+ // ============================================
222
+ // share_links — sharing tokens
223
+ // ============================================
224
+ export const shareLinks = pgTable(
225
+ "share_links",
226
+ {
227
+ id: uuid("id").primaryKey().defaultRandom(),
228
+ documentId: uuid("document_id").references(() => documents.id, {
229
+ onDelete: "cascade",
230
+ }),
231
+ folderId: uuid("folder_id").references(() => folders.id, {
232
+ onDelete: "cascade",
233
+ }),
234
+ token: text("token").notNull().unique(),
235
+ passwordHash: text("password_hash"),
236
+ expiresAt: timestamp("expires_at"),
237
+ createdBy: uuid("created_by")
238
+ .notNull()
239
+ .references(() => users.id, { onDelete: "cascade" }),
240
+ createdAt: timestamp("created_at").defaultNow().notNull(),
241
+ },
242
+ (table) => [
243
+ index("share_links_token_idx").on(table.token),
244
+ index("share_links_document_id_idx").on(table.documentId),
245
+ index("share_links_folder_id_idx").on(table.folderId),
246
+ ]
247
+ );
248
+
249
+ export const shareLinkRelations = relations(shareLinks, ({ one, many }) => ({
250
+ document: one(documents, {
251
+ fields: [shareLinks.documentId],
252
+ references: [documents.id],
253
+ }),
254
+ folder: one(folders, {
255
+ fields: [shareLinks.folderId],
256
+ references: [folders.id],
257
+ }),
258
+ creator: one(users, {
259
+ fields: [shareLinks.createdBy],
260
+ references: [users.id],
261
+ }),
262
+ guestAccess: many(guestAccess),
263
+ }));
264
+
265
+ // ============================================
266
+ // guest_access — guest email grants
267
+ // ============================================
268
+ export const guestAccess = pgTable(
269
+ "guest_access",
270
+ {
271
+ id: uuid("id").primaryKey().defaultRandom(),
272
+ shareLinkId: uuid("share_link_id")
273
+ .notNull()
274
+ .references(() => shareLinks.id, { onDelete: "cascade" }),
275
+ guestEmail: text("guest_email").notNull(),
276
+ grantedAt: timestamp("granted_at").defaultNow().notNull(),
277
+ },
278
+ (table) => [index("guest_access_share_link_idx").on(table.shareLinkId)]
279
+ );
280
+
281
+ export const guestAccessRelations = relations(guestAccess, ({ one }) => ({
282
+ shareLink: one(shareLinks, {
283
+ fields: [guestAccess.shareLinkId],
284
+ references: [shareLinks.id],
285
+ }),
286
+ }));
287
+
288
+ // ============================================
289
+ // attachments — file uploads (MinIO)
290
+ // ============================================
291
+ export const attachments = pgTable(
292
+ "attachments",
293
+ {
294
+ id: uuid("id").primaryKey().defaultRandom(),
295
+ documentId: uuid("document_id")
296
+ .notNull()
297
+ .references(() => documents.id, { onDelete: "cascade" }),
298
+ filename: text("filename").notNull(),
299
+ mimeType: text("mime_type").notNull(),
300
+ size: bigint("size", { mode: "number" }).notNull(),
301
+ minioKey: text("minio_key").notNull(),
302
+ createdAt: timestamp("created_at").defaultNow().notNull(),
303
+ },
304
+ (table) => [index("attachments_document_id_idx").on(table.documentId)]
305
+ );
306
+
307
+ export const attachmentRelations = relations(attachments, ({ one }) => ({
308
+ document: one(documents, {
309
+ fields: [attachments.documentId],
310
+ references: [documents.id],
311
+ }),
312
+ }));
313
+
314
+ // ============================================
315
+ // versions — document version history
316
+ // ============================================
317
+ export const versions = pgTable(
318
+ "versions",
319
+ {
320
+ id: uuid("id").primaryKey().defaultRandom(),
321
+ documentId: uuid("document_id")
322
+ .notNull()
323
+ .references(() => documents.id, { onDelete: "cascade" }),
324
+ content: text("content").notNull(),
325
+ contentJson: jsonb("content_json"),
326
+ createdBy: uuid("created_by")
327
+ .notNull()
328
+ .references(() => users.id, { onDelete: "cascade" }),
329
+ createdAt: timestamp("created_at").defaultNow().notNull(),
330
+ },
331
+ (table) => [
332
+ index("versions_document_id_idx").on(table.documentId),
333
+ index("versions_created_at_idx").on(table.createdAt),
334
+ ]
335
+ );
336
+
337
+ // ============================================
338
+ // document_embeddings — multi-chunk pgvector storage
339
+ // ============================================
340
+ export const documentEmbeddings = pgTable(
341
+ "document_embeddings",
342
+ {
343
+ id: uuid("id").primaryKey().defaultRandom(),
344
+ documentId: uuid("document_id")
345
+ .notNull()
346
+ .references(() => documents.id, { onDelete: "cascade" }),
347
+ chunkIndex: bigint("chunk_index", { mode: "number" }).notNull(),
348
+ chunkText: text("chunk_text").notNull(),
349
+ embedding: vector("embedding"),
350
+ createdAt: timestamp("created_at").defaultNow().notNull(),
351
+ },
352
+ (table) => [
353
+ index("document_embeddings_doc_id_idx").on(table.documentId),
354
+ uniqueIndex("document_embeddings_doc_chunk_idx").on(table.documentId, table.chunkIndex),
355
+ index("idx_document_embeddings_hnsw").using(
356
+ "hnsw",
357
+ sql`${table.embedding} vector_cosine_ops`
358
+ ),
359
+ ]
360
+ );
361
+
362
+ export const documentEmbeddingRelations = relations(documentEmbeddings, ({ one }) => ({
363
+ document: one(documents, {
364
+ fields: [documentEmbeddings.documentId],
365
+ references: [documents.id],
366
+ }),
367
+ }));
368
+
369
+ export const versionRelations = relations(versions, ({ one }) => ({
370
+ document: one(documents, {
371
+ fields: [versions.documentId],
372
+ references: [documents.id],
373
+ }),
374
+ creator: one(users, {
375
+ fields: [versions.createdBy],
376
+ references: [users.id],
377
+ }),
378
+ }));
@@ -0,0 +1,17 @@
1
+ {
2
+ "compilerOptions": {
3
+ "target": "ESNext",
4
+ "module": "ESNext",
5
+ "moduleResolution": "bundler",
6
+ "strict": true,
7
+ "noUncheckedIndexedAccess": true,
8
+ "esModuleInterop": true,
9
+ "skipLibCheck": true,
10
+ "declaration": true,
11
+ "outDir": "./dist",
12
+ "rootDir": "./src",
13
+ "types": ["node"]
14
+ },
15
+ "include": ["src"],
16
+ "exclude": ["node_modules", "dist"]
17
+ }
@@ -0,0 +1,37 @@
1
+ #!/usr/bin/env bun
2
+ /**
3
+ * Export OpenAPI spec from the running hiai-docs API.
4
+ * Usage: bun run scripts/export-openapi.ts
5
+ *
6
+ * The API server must be running (default: http://localhost:50700).
7
+ * Output: docs/openapi.json
8
+ */
9
+
10
+ import { writeFileSync, mkdirSync } from "fs";
11
+ import { join } from "path";
12
+
13
+ const API_URL = process.env.API_URL ?? "http://localhost:50700";
14
+ const OUTPUT_PATH = join(import.meta.dir, "..", "docs", "openapi.json");
15
+
16
+ async function exportSpec() {
17
+ console.log(`Fetching OpenAPI spec from ${API_URL}/api/docs/json ...`);
18
+
19
+ const response = await fetch(`${API_URL}/api/docs/json`);
20
+ if (!response.ok) {
21
+ console.error(`Failed to fetch spec: ${response.status} ${response.statusText}`);
22
+ process.exit(1);
23
+ }
24
+
25
+ const spec = await response.json();
26
+
27
+ mkdirSync(join(import.meta.dir, "..", "docs"), { recursive: true });
28
+ writeFileSync(OUTPUT_PATH, JSON.stringify(spec, null, 2) + "\n");
29
+
30
+ console.log(`OpenAPI spec exported to ${OUTPUT_PATH}`);
31
+ console.log(`Endpoints: ${Object.keys((spec as Record<string, unknown>).paths ?? {}).length}`);
32
+ }
33
+
34
+ exportSpec().catch((err) => {
35
+ console.error("Export failed:", err);
36
+ process.exit(1);
37
+ });
@@ -0,0 +1,75 @@
1
+ #!/bin/bash
2
+ # hiai-docs health-check script
3
+ # Usage: ./scripts/health-check.sh
4
+ #
5
+ # Checks liveness/readiness of every service in the hiai-docs stack.
6
+ # Exits 0 only if all checks pass; exits 1 if any check fails.
7
+ #
8
+ # Environment overrides (all optional):
9
+ # API_PORT default 50700
10
+ # DB_PORT default 5433
11
+ # DB_USER default aiuser
12
+ # DB_NAME default hiai_docs
13
+ # DB_HOST default localhost
14
+ # REDIS_PORT default 6380 (matches REDIS_URL in .env.example)
15
+ # OLLAMA_URL default http://localhost:11434
16
+ # MINIO_PORT default 9000
17
+ # MINIO_HEALTH_PATH default /minio/health/live
18
+ #
19
+ # Example:
20
+ # ./scripts/health-check.sh
21
+ # REDIS_PORT=6380 API_PORT=50700 ./scripts/health-check.sh
22
+
23
+ set -euo pipefail
24
+
25
+ API_PORT="${API_PORT:-50700}"
26
+ DB_HOST="${DB_HOST:-localhost}"
27
+ DB_PORT="${DB_PORT:-5433}"
28
+ DB_USER="${DB_USER:-aiuser}"
29
+ DB_NAME="${DB_NAME:-hiai_docs}"
30
+ REDIS_PORT="${REDIS_PORT:-6380}"
31
+ OLLAMA_URL="${OLLAMA_URL:-http://localhost:11434}"
32
+ MINIO_PORT="${MINIO_PORT:-9000}"
33
+ MINIO_HEALTH_PATH="${MINIO_HEALTH_PATH:-/minio/health/live}"
34
+
35
+ fail=0
36
+
37
+ check() {
38
+ local name="$1"
39
+ local cmd="$2"
40
+ if eval "$cmd" >/dev/null 2>&1; then
41
+ echo " ✅ $name"
42
+ else
43
+ echo " ❌ $name"
44
+ fail=1
45
+ fi
46
+ }
47
+
48
+ echo "hiai-docs Health Checks"
49
+ echo "======================="
50
+ echo "API: http://localhost:${API_PORT}/api/health"
51
+ echo "PostgreSQL: ${DB_USER}@${DB_HOST}:${DB_PORT}/${DB_NAME}"
52
+ echo "Redis: localhost:${REDIS_PORT}"
53
+ echo "Ollama: ${OLLAMA_URL}"
54
+ echo "MinIO: http://localhost:${MINIO_PORT}${MINIO_HEALTH_PATH}"
55
+ echo ""
56
+
57
+ check "API" "curl -fsS http://localhost:${API_PORT}/api/health"
58
+ check "PostgreSQL" "psql -h ${DB_HOST} -p ${DB_PORT} -U ${DB_USER} -d ${DB_NAME} -c 'SELECT 1'"
59
+ check "Redis" "redis-cli -p ${REDIS_PORT} ping"
60
+ check "Ollama" "curl -fsS ${OLLAMA_URL}/api/tags"
61
+ check "MinIO" "curl -fsS http://localhost:${MINIO_PORT}${MINIO_HEALTH_PATH}"
62
+
63
+ echo ""
64
+ if [ "$fail" -eq 0 ]; then
65
+ echo "✅ All services healthy"
66
+ exit 0
67
+ else
68
+ echo "❌ One or more services failed"
69
+ echo ""
70
+ echo "Troubleshooting:"
71
+ echo " - Start the stack: docker compose up -d"
72
+ echo " - Tail logs: docker compose logs -f <service>"
73
+ echo " - Per-service healthchecks are defined in docker-compose.yml"
74
+ exit 1
75
+ fi
@@ -0,0 +1,135 @@
1
+ #!/bin/bash
2
+ # hiai-docs database migration script
3
+ # Usage: ./scripts/migrate.sh [--yes] [--generate-only]
4
+ #
5
+ # Runs the Drizzle workflow against the configured database:
6
+ # 1. bun install (if node_modules missing)
7
+ # 2. db:generate (regenerate SQL from packages/db/src/schema.ts)
8
+ # 3. db:push (apply schema to DATABASE_URL)
9
+ #
10
+ # Flags:
11
+ # --yes skip the "are you sure?" confirmation prompt
12
+ # --generate-only run db:generate only, skip the actual db:push
13
+ # --help show this help and exit
14
+ #
15
+ # Environment:
16
+ # DB_HOST / DB_PORT / DB_USER / DB_NAME / DB_PASSWORD override .env
17
+ # DATABASE_URL full connection string (takes precedence if set)
18
+ #
19
+ # Example:
20
+ # ./scripts/migrate.sh
21
+ # ./scripts/migrate.sh --yes
22
+ # ./scripts/migrate.sh --generate-only
23
+
24
+ set -euo pipefail
25
+
26
+ # --- Argument parsing ---
27
+ SKIP_CONFIRM=0
28
+ GENERATE_ONLY=0
29
+ for arg in "$@"; do
30
+ case "$arg" in
31
+ --yes) SKIP_CONFIRM=1 ;;
32
+ --generate-only) GENERATE_ONLY=1 ;;
33
+ --help|-h)
34
+ sed -n '2,18p' "$0" | sed 's/^# \?//'
35
+ exit 0
36
+ ;;
37
+ *)
38
+ echo "❌ Unknown argument: $arg"
39
+ echo " Run with --help for usage."
40
+ exit 2
41
+ ;;
42
+ esac
43
+ done
44
+
45
+ # --- Sanity checks ---
46
+ if [ ! -f "package.json" ] || [ ! -d "packages/db" ]; then
47
+ echo "❌ package.json or packages/db not found."
48
+ echo " Run this script from the hiai-docs project root."
49
+ exit 1
50
+ fi
51
+
52
+ if ! command -v bun >/dev/null 2>&1; then
53
+ echo "❌ bun is required but not installed."
54
+ echo " Install: https://bun.sh/docs/installation"
55
+ exit 1
56
+ fi
57
+
58
+ # Load .env if it exists (so DATABASE_URL / DB_* are available)
59
+ if [ -f ".env" ] && [ -z "${DATABASE_URL:-}" ]; then
60
+ set -a
61
+ # shellcheck disable=SC1091
62
+ . ./.env
63
+ set +a
64
+ fi
65
+
66
+ if [ -z "${DATABASE_URL:-}" ]; then
67
+ echo "❌ DATABASE_URL is not set."
68
+ echo " Either set it in .env, or export DATABASE_URL before running this script."
69
+ exit 1
70
+ fi
71
+
72
+ # --- Pre-flight: confirm the database is reachable ---
73
+ echo "==> hiai-docs Migration"
74
+ echo " DATABASE_URL=${DATABASE_URL}"
75
+ echo ""
76
+
77
+ if command -v psql >/dev/null 2>&1; then
78
+ if ! psql "${DATABASE_URL}" -c "SELECT 1" >/dev/null 2>&1; then
79
+ echo "❌ Cannot connect to database at ${DATABASE_URL}."
80
+ echo " Start the stack with: docker compose up -d postgres"
81
+ exit 1
82
+ fi
83
+ echo " ✅ Database reachable"
84
+ else
85
+ echo " ⚠️ psql not found — skipping connectivity check (db:push will fail loudly if unreachable)"
86
+ fi
87
+
88
+ # --- Confirm destructive intent (db:push drops + recreates tables) ---
89
+ if [ "$GENERATE_ONLY" -eq 0 ] && [ "$SKIP_CONFIRM" -eq 0 ]; then
90
+ echo ""
91
+ echo "⚠️ About to push schema to ${DATABASE_URL}."
92
+ echo " db:push will ALTER tables to match the Drizzle schema (destructive on type changes)."
93
+ printf " Continue? [y/N] "
94
+ read -r reply
95
+ case "$reply" in
96
+ y|Y|yes|YES) ;;
97
+ *)
98
+ echo "Aborted."
99
+ exit 0
100
+ ;;
101
+ esac
102
+ fi
103
+
104
+ echo ""
105
+
106
+ # --- Install deps if needed ---
107
+ if [ ! -d "node_modules" ] || [ ! -d "packages/db/node_modules" ]; then
108
+ echo "==> Installing dependencies..."
109
+ bun install
110
+ else
111
+ echo "==> Dependencies already installed (skipping bun install)"
112
+ fi
113
+
114
+ # --- Regenerate migration SQL from schema ---
115
+ echo ""
116
+ echo "==> Regenerating SQL migrations from packages/db/src/schema.ts..."
117
+ cd packages/db
118
+ bun run db:generate
119
+ cd ../..
120
+
121
+ # --- Push to database (unless --generate-only) ---
122
+ if [ "$GENERATE_ONLY" -eq 1 ]; then
123
+ echo ""
124
+ echo "✅ Migrations regenerated (--generate-only; db:push skipped)"
125
+ exit 0
126
+ fi
127
+
128
+ echo ""
129
+ echo "==> Pushing schema to ${DATABASE_URL}..."
130
+ cd packages/db
131
+ bun run db:push
132
+ cd ../..
133
+
134
+ echo ""
135
+ echo "✅ Migration complete"
@@ -0,0 +1,25 @@
1
+ #!/bin/bash
2
+ # hiai-docs pre-work backup script
3
+ # Usage: ./scripts/prework_backup.sh [project_name]
4
+
5
+ set -euo pipefail
6
+
7
+ PROJECT_NAME="${1:-hiai-docs}"
8
+ BACKUP_DIR="/mnt/ai_data/backup/${PROJECT_NAME}/$(date +%Y-%m-%d_%H%M%S)"
9
+
10
+ mkdir -p "$BACKUP_DIR"
11
+
12
+ echo "Creating backup snapshot for ${PROJECT_NAME}..."
13
+
14
+ # Backup database if running
15
+ if docker compose ps postgres 2>/dev/null | grep -q "Up"; then
16
+ echo "Backing up PostgreSQL..."
17
+ docker compose exec -T postgres pg_dump -U aiuser hiai_docs > "${BACKUP_DIR}/database.sql" 2>/dev/null || echo "DB backup skipped (not running or not accessible)"
18
+ fi
19
+
20
+ # Backup .env
21
+ if [ -f ".env" ]; then
22
+ cp .env "${BACKUP_DIR}/.env.bak"
23
+ fi
24
+
25
+ echo "Backup created at: ${BACKUP_DIR}"