@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.
- package/.all-contributorsrc +18 -0
- package/.claude/settings.local.json +61 -0
- package/.dockerignore +113 -0
- package/.env.example +68 -0
- package/.github/FUNDING.yml +5 -0
- package/.github/ISSUE_TEMPLATE/bug_report.md +74 -0
- package/.github/ISSUE_TEMPLATE/feature_request.md +78 -0
- package/.github/dependabot.yml +136 -0
- package/.github/pull_request_template.md +96 -0
- package/.github/workflows/ci.yml +283 -0
- package/AGENTS.md +237 -0
- package/CODE_OF_CONDUCT.md +134 -0
- package/CONTRIBUTING.md +77 -0
- package/Caddyfile +50 -0
- package/Dockerfile.backend +60 -0
- package/LICENSE +21 -0
- package/README.md +284 -0
- package/RELEASE_CHECKLIST.md +34 -0
- package/SECURITY.md +60 -0
- package/backend/package.json +43 -0
- package/backend/src/__tests__/auth-helpers.test.ts +51 -0
- package/backend/src/__tests__/chunker.test.ts +65 -0
- package/backend/src/__tests__/config.test.ts +91 -0
- package/backend/src/__tests__/csrf.test.ts +91 -0
- package/backend/src/__tests__/embedding.test.ts +48 -0
- package/backend/src/__tests__/rate-limit.test.ts +46 -0
- package/backend/src/__tests__/routes.test.ts +38 -0
- package/backend/src/__tests__/schema.test.ts +31 -0
- package/backend/src/__tests__/validation.test.ts +556 -0
- package/backend/src/api/middleware/auth.ts +56 -0
- package/backend/src/api/middleware/csrf.ts +91 -0
- package/backend/src/api/middleware/rate-limit.ts +77 -0
- package/backend/src/api/middleware/webhook-verify.ts +22 -0
- package/backend/src/api/routes/attachments.ts +280 -0
- package/backend/src/api/routes/auth.ts +52 -0
- package/backend/src/api/routes/collaboration.ts +121 -0
- package/backend/src/api/routes/documents.ts +664 -0
- package/backend/src/api/routes/folders.ts +226 -0
- package/backend/src/api/routes/search.ts +354 -0
- package/backend/src/api/routes/share.ts +512 -0
- package/backend/src/api/routes/tags.ts +247 -0
- package/backend/src/api/routes/versions.ts +99 -0
- package/backend/src/api/routes/webhooks.ts +43 -0
- package/backend/src/embedding/chunker.ts +74 -0
- package/backend/src/embedding/index.ts +117 -0
- package/backend/src/embedding/providers/ollama.ts +63 -0
- package/backend/src/embedding/providers/openrouter.ts +71 -0
- package/backend/src/embedding/utils.ts +13 -0
- package/backend/src/embedding/worker.ts +89 -0
- package/backend/src/index.ts +147 -0
- package/backend/src/lib/auth-helpers.ts +27 -0
- package/backend/src/lib/auth.ts +35 -0
- package/backend/src/lib/config.ts +73 -0
- package/backend/src/lib/db.ts +7 -0
- package/backend/src/lib/embedding-queue.ts +12 -0
- package/backend/src/lib/logger.ts +18 -0
- package/backend/src/lib/markdown-to-doc.ts +45 -0
- package/backend/src/lib/minio.ts +46 -0
- package/backend/src/lib/redis.ts +19 -0
- package/backend/src/lib/yjs-provider.ts +182 -0
- package/backend/tests/integration/_harness.ts +754 -0
- package/backend/tests/integration/auth.test.ts +296 -0
- package/backend/tests/integration/routes.documents.test.ts +459 -0
- package/backend/tests/integration/routes.folders.test.ts +337 -0
- package/backend/tests/integration/routes.search.test.ts +322 -0
- package/backend/tests/integration/routes.share.test.ts +773 -0
- package/backend/tests/integration/routes.tags.test.ts +425 -0
- package/backend/tests/integration/routes.versions.test.ts +233 -0
- package/backend/tsconfig.json +18 -0
- package/docker-compose.yml +218 -0
- package/docs/API.md +328 -0
- package/docs/ARCHITECTURE.md +75 -0
- package/docs/DEPLOYMENT.md +113 -0
- package/docs/PRODUCTION_STATUS.md +61 -0
- package/docs/openapi.json +385 -0
- package/frontend/.svelte-kit.old/ambient.d.ts +230 -0
- package/frontend/.svelte-kit.old/env.d.ts +1 -0
- package/frontend/.svelte-kit.old/generated/client/app.js +46 -0
- package/frontend/.svelte-kit.old/generated/client/matchers.js +1 -0
- package/frontend/.svelte-kit.old/generated/client/nodes/0.js +3 -0
- package/frontend/.svelte-kit.old/generated/client/nodes/1.js +1 -0
- package/frontend/.svelte-kit.old/generated/client/nodes/10.js +3 -0
- package/frontend/.svelte-kit.old/generated/client/nodes/2.js +1 -0
- package/frontend/.svelte-kit.old/generated/client/nodes/3.js +1 -0
- package/frontend/.svelte-kit.old/generated/client/nodes/4.js +1 -0
- package/frontend/.svelte-kit.old/generated/client/nodes/5.js +3 -0
- package/frontend/.svelte-kit.old/generated/client/nodes/6.js +1 -0
- package/frontend/.svelte-kit.old/generated/client/nodes/7.js +3 -0
- package/frontend/.svelte-kit.old/generated/client/nodes/8.js +1 -0
- package/frontend/.svelte-kit.old/generated/client/nodes/9.js +3 -0
- package/frontend/.svelte-kit.old/generated/root.js +3 -0
- package/frontend/.svelte-kit.old/generated/root.svelte +80 -0
- package/frontend/.svelte-kit.old/generated/server/internal.js +55 -0
- package/frontend/.svelte-kit.old/non-ambient.d.ts +59 -0
- package/frontend/.svelte-kit.old/tsconfig.json +59 -0
- package/frontend/.svelte-kit.old/types/route_meta_data.json +40 -0
- package/frontend/.svelte-kit.old/types/src/routes/$types.d.ts +21 -0
- package/frontend/.svelte-kit.old/types/src/routes/(app)/$types.d.ts +30 -0
- package/frontend/.svelte-kit.old/types/src/routes/(app)/docs/[id]/$types.d.ts +27 -0
- package/frontend/.svelte-kit.old/types/src/routes/(app)/docs/[id]/proxy+page.ts +25 -0
- package/frontend/.svelte-kit.old/types/src/routes/api/[...path]/$types.d.ts +10 -0
- package/frontend/.svelte-kit.old/types/src/routes/folders/[id]/$types.d.ts +27 -0
- package/frontend/.svelte-kit.old/types/src/routes/folders/[id]/proxy+page.ts +15 -0
- package/frontend/.svelte-kit.old/types/src/routes/login/$types.d.ts +17 -0
- package/frontend/.svelte-kit.old/types/src/routes/register/$types.d.ts +17 -0
- package/frontend/.svelte-kit.old/types/src/routes/s/[token]/$types.d.ts +20 -0
- package/frontend/.svelte-kit.old/types/src/routes/s/[token]/proxy+page.ts +6 -0
- package/frontend/.svelte-kit.old/types/src/routes/search/$types.d.ts +19 -0
- package/frontend/.svelte-kit.old/types/src/routes/search/proxy+page.ts +26 -0
- package/frontend/.svelte-kit.old/types/src/routes/settings/$types.d.ts +17 -0
- package/frontend/Dockerfile +44 -0
- package/frontend/biome.json +40 -0
- package/frontend/components.json +18 -0
- package/frontend/messages/en.json +434 -0
- package/frontend/package.json +70 -0
- package/frontend/project.inlang/settings.json +12 -0
- package/frontend/src/app.css +6 -0
- package/frontend/src/app.d.ts +13 -0
- package/frontend/src/app.html +30 -0
- package/frontend/src/hooks.server.ts +10 -0
- package/frontend/src/hooks.ts +10 -0
- package/frontend/src/lib/api/attachments.ts +45 -0
- package/frontend/src/lib/api/client.test.ts +15 -0
- package/frontend/src/lib/api/client.ts +57 -0
- package/frontend/src/lib/api/documents.ts +83 -0
- package/frontend/src/lib/api/folders.ts +180 -0
- package/frontend/src/lib/api/search.test.ts +52 -0
- package/frontend/src/lib/api/search.ts +128 -0
- package/frontend/src/lib/api/settings.ts +95 -0
- package/frontend/src/lib/api/share.ts +71 -0
- package/frontend/src/lib/api/tags.test.ts +91 -0
- package/frontend/src/lib/api/tags.ts +87 -0
- package/frontend/src/lib/auth-client.ts +10 -0
- package/frontend/src/lib/collaboration.ts +63 -0
- package/frontend/src/lib/components/AttachmentUpload.svelte +110 -0
- package/frontend/src/lib/components/DatePicker.svelte +322 -0
- package/frontend/src/lib/components/DocumentCard.svelte +166 -0
- package/frontend/src/lib/components/EmptyState.svelte +49 -0
- package/frontend/src/lib/components/FolderCard.svelte +93 -0
- package/frontend/src/lib/components/ScrollToTop.svelte +72 -0
- package/frontend/src/lib/components/SearchBar.svelte +47 -0
- package/frontend/src/lib/components/SearchResult.svelte +115 -0
- package/frontend/src/lib/components/SettingsDialog.svelte +271 -0
- package/frontend/src/lib/components/ShareDialog.svelte +158 -0
- package/frontend/src/lib/components/ShareLink.svelte +98 -0
- package/frontend/src/lib/components/TagCreateDialog.svelte +284 -0
- package/frontend/src/lib/components/VersionDiff.svelte +55 -0
- package/frontend/src/lib/components/VersionHistory.svelte +96 -0
- package/frontend/src/lib/components/editor/DocumentTitle.svelte +87 -0
- package/frontend/src/lib/components/editor/EditorToolbar.svelte +1367 -0
- package/frontend/src/lib/components/editor/HiAiEditor.svelte +531 -0
- package/frontend/src/lib/components/editor/LinkDialog.svelte +134 -0
- package/frontend/src/lib/components/editor/MarkdownToggle.svelte +88 -0
- package/frontend/src/lib/components/editor/editorExtensions.ts +53 -0
- package/frontend/src/lib/components/editor/markdown.ts +38 -0
- package/frontend/src/lib/components/sidebar/FolderTree.svelte +731 -0
- package/frontend/src/lib/components/sidebar/RecentDocs.svelte +311 -0
- package/frontend/src/lib/components/sidebar/Sidebar.svelte +156 -0
- package/frontend/src/lib/components/sidebar/TagList.svelte +200 -0
- package/frontend/src/lib/components/ui/confirm-dialog/ConfirmDialog.svelte +76 -0
- package/frontend/src/lib/components/ui/confirm-dialog/index.ts +1 -0
- package/frontend/src/lib/stores/tag-store.svelte.ts +56 -0
- package/frontend/src/lib/stores/theme.svelte.ts +97 -0
- package/frontend/src/lib/svelte.d.ts +6 -0
- package/frontend/src/lib/types.ts +44 -0
- package/frontend/src/lib/utils/clipboard.ts +17 -0
- package/frontend/src/lib/utils/strip-markdown.ts +59 -0
- package/frontend/src/lib/utils.ts +33 -0
- package/frontend/src/routes/(app)/+layout.svelte +17 -0
- package/frontend/src/routes/(app)/+page.server.ts +10 -0
- package/frontend/src/routes/(app)/+page.svelte +303 -0
- package/frontend/src/routes/(app)/docs/[id]/+page.server.ts +10 -0
- package/frontend/src/routes/(app)/docs/[id]/+page.svelte +1108 -0
- package/frontend/src/routes/(app)/docs/[id]/+page.ts +24 -0
- package/frontend/src/routes/(app)/search/+page.svelte +593 -0
- package/frontend/src/routes/(app)/search/+page.ts +25 -0
- package/frontend/src/routes/+error.svelte +12 -0
- package/frontend/src/routes/+layout.svelte +18 -0
- package/frontend/src/routes/+layout.ts +2 -0
- package/frontend/src/routes/api/[...path]/+server.ts +111 -0
- package/frontend/src/routes/folders/[id]/+page.server.ts +10 -0
- package/frontend/src/routes/folders/[id]/+page.svelte +319 -0
- package/frontend/src/routes/folders/[id]/+page.ts +14 -0
- package/frontend/src/routes/login/+page.svelte +90 -0
- package/frontend/src/routes/register/+page.svelte +97 -0
- package/frontend/src/routes/s/[token]/+page.svelte +496 -0
- package/frontend/src/routes/s/[token]/+page.ts +5 -0
- package/frontend/src/routes/settings/+page.svelte +175 -0
- package/frontend/static/favicon.png +0 -0
- package/frontend/static/logo.png +0 -0
- package/frontend/svelte.config.js +15 -0
- package/frontend/tsconfig.json +15 -0
- package/frontend/vite.config.ts +25 -0
- package/init.sql +9 -0
- package/logo.png +0 -0
- package/package.json +39 -0
- package/package.public.json +39 -0
- package/packages/db/drizzle.config.ts +10 -0
- package/packages/db/package.json +30 -0
- package/packages/db/src/client.ts +9 -0
- package/packages/db/src/index.ts +2 -0
- package/packages/db/src/migrations/0000_nice_bedlam.sql +165 -0
- package/packages/db/src/migrations/0001_w2_3_test.sql +5 -0
- package/packages/db/src/migrations/0002_rename_content_json.sql +2 -0
- package/packages/db/src/migrations/meta/0000_snapshot.json +1331 -0
- package/packages/db/src/migrations/meta/0001_snapshot.json +1399 -0
- package/packages/db/src/migrations/meta/0002_snapshot.json +1399 -0
- package/packages/db/src/migrations/meta/_journal.json +27 -0
- package/packages/db/src/schema.ts +378 -0
- package/packages/db/tsconfig.json +17 -0
- package/scripts/export-openapi.ts +37 -0
- package/scripts/health-check.sh +75 -0
- package/scripts/migrate.sh +135 -0
- package/scripts/prework_backup.sh +25 -0
- package/scripts/release.sh +83 -0
- 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}"
|