floq 0.0.1 → 0.2.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.
- package/README.ja.md +157 -29
- package/README.md +157 -29
- package/dist/changelog.d.ts +13 -0
- package/dist/changelog.js +95 -0
- package/dist/cli.js +70 -1
- package/dist/commands/add.js +5 -6
- package/dist/commands/comment.d.ts +2 -0
- package/dist/commands/comment.js +67 -0
- package/dist/commands/config.d.ts +7 -0
- package/dist/commands/config.js +163 -14
- package/dist/commands/done.js +4 -6
- package/dist/commands/list.js +9 -13
- package/dist/commands/move.js +4 -6
- package/dist/commands/project.js +18 -26
- package/dist/commands/setup.d.ts +1 -0
- package/dist/commands/setup.js +13 -0
- package/dist/config.d.ts +15 -1
- package/dist/config.js +53 -2
- package/dist/db/index.d.ts +5 -4
- package/dist/db/index.js +127 -32
- package/dist/db/schema.d.ts +83 -0
- package/dist/db/schema.js +6 -0
- package/dist/i18n/en.d.ts +258 -0
- package/dist/i18n/en.js +138 -3
- package/dist/i18n/ja.js +138 -3
- package/dist/index.js +33 -1
- package/dist/paths.d.ts +4 -0
- package/dist/paths.js +63 -5
- package/dist/ui/App.js +384 -136
- package/dist/ui/ModeSelector.d.ts +7 -0
- package/dist/ui/ModeSelector.js +37 -0
- package/dist/ui/SetupWizard.d.ts +6 -0
- package/dist/ui/SetupWizard.js +321 -0
- package/dist/ui/ThemeSelector.d.ts +1 -1
- package/dist/ui/ThemeSelector.js +23 -10
- package/dist/ui/components/FunctionKeyBar.d.ts +5 -4
- package/dist/ui/components/FunctionKeyBar.js +19 -15
- package/dist/ui/components/HelpModal.d.ts +2 -1
- package/dist/ui/components/HelpModal.js +118 -4
- package/dist/ui/components/KanbanBoard.d.ts +6 -0
- package/dist/ui/components/KanbanBoard.js +508 -0
- package/dist/ui/components/KanbanColumn.d.ts +12 -0
- package/dist/ui/components/KanbanColumn.js +11 -0
- package/dist/ui/components/ProgressBar.d.ts +7 -0
- package/dist/ui/components/ProgressBar.js +13 -0
- package/dist/ui/components/SearchBar.d.ts +8 -0
- package/dist/ui/components/SearchBar.js +11 -0
- package/dist/ui/components/SearchResults.d.ts +9 -0
- package/dist/ui/components/SearchResults.js +18 -0
- package/dist/ui/components/TaskItem.d.ts +6 -1
- package/dist/ui/components/TaskItem.js +3 -2
- package/dist/ui/theme/themes.d.ts +12 -0
- package/dist/ui/theme/themes.js +495 -3
- package/dist/ui/theme/types.d.ts +1 -1
- package/package.json +2 -1
package/dist/config.js
CHANGED
|
@@ -1,9 +1,37 @@
|
|
|
1
|
-
import { existsSync, readFileSync, writeFileSync, mkdirSync } from 'fs';
|
|
1
|
+
import { existsSync, readFileSync, writeFileSync, mkdirSync, renameSync } from 'fs';
|
|
2
2
|
import { dirname, join, isAbsolute } from 'path';
|
|
3
3
|
import { CONFIG_FILE, DATA_DIR } from './paths.js';
|
|
4
|
+
// Migrate legacy DB file names (including related metadata files)
|
|
5
|
+
function migrateDbFiles() {
|
|
6
|
+
const legacyDb = join(DATA_DIR, 'gtd.db');
|
|
7
|
+
const newDb = join(DATA_DIR, 'floq.db');
|
|
8
|
+
const legacyTursoDb = join(DATA_DIR, 'gtd-turso.db');
|
|
9
|
+
const newTursoDb = join(DATA_DIR, 'floq-turso.db');
|
|
10
|
+
// Turso/libsql related file suffixes
|
|
11
|
+
const tursoSuffixes = ['', '-info', '-shm', '-wal'];
|
|
12
|
+
try {
|
|
13
|
+
if (existsSync(legacyDb) && !existsSync(newDb)) {
|
|
14
|
+
renameSync(legacyDb, newDb);
|
|
15
|
+
}
|
|
16
|
+
// Migrate Turso DB and all related metadata files
|
|
17
|
+
for (const suffix of tursoSuffixes) {
|
|
18
|
+
const legacyFile = legacyTursoDb + suffix;
|
|
19
|
+
const newFile = newTursoDb + suffix;
|
|
20
|
+
if (existsSync(legacyFile) && !existsSync(newFile)) {
|
|
21
|
+
renameSync(legacyFile, newFile);
|
|
22
|
+
}
|
|
23
|
+
}
|
|
24
|
+
}
|
|
25
|
+
catch {
|
|
26
|
+
// Ignore migration errors
|
|
27
|
+
}
|
|
28
|
+
}
|
|
29
|
+
// Run DB file migration on module load
|
|
30
|
+
migrateDbFiles();
|
|
4
31
|
const DEFAULT_CONFIG = {
|
|
5
32
|
locale: 'en',
|
|
6
33
|
theme: 'modern',
|
|
34
|
+
viewMode: 'gtd',
|
|
7
35
|
};
|
|
8
36
|
let configCache = null;
|
|
9
37
|
export function loadConfig() {
|
|
@@ -39,6 +67,16 @@ export function saveConfig(updates) {
|
|
|
39
67
|
// Ignore errors
|
|
40
68
|
}
|
|
41
69
|
}
|
|
70
|
+
export function getTursoConfig() {
|
|
71
|
+
return loadConfig().turso;
|
|
72
|
+
}
|
|
73
|
+
export function setTursoConfig(config) {
|
|
74
|
+
saveConfig({ turso: config });
|
|
75
|
+
}
|
|
76
|
+
export function isTursoEnabled() {
|
|
77
|
+
const turso = getTursoConfig();
|
|
78
|
+
return turso !== undefined && turso.url !== '' && turso.authToken !== '';
|
|
79
|
+
}
|
|
42
80
|
export function getDbPath() {
|
|
43
81
|
const config = loadConfig();
|
|
44
82
|
if (config.db_path) {
|
|
@@ -48,7 +86,11 @@ export function getDbPath() {
|
|
|
48
86
|
}
|
|
49
87
|
return join(DATA_DIR, config.db_path);
|
|
50
88
|
}
|
|
51
|
-
|
|
89
|
+
// Turso モードでは別のDBファイルを使用(embedded replica 用)
|
|
90
|
+
if (isTursoEnabled()) {
|
|
91
|
+
return join(DATA_DIR, 'floq-turso.db');
|
|
92
|
+
}
|
|
93
|
+
return join(DATA_DIR, 'floq.db');
|
|
52
94
|
}
|
|
53
95
|
export function getLocale() {
|
|
54
96
|
return loadConfig().locale;
|
|
@@ -62,3 +104,12 @@ export function getThemeName() {
|
|
|
62
104
|
export function setThemeName(theme) {
|
|
63
105
|
saveConfig({ theme });
|
|
64
106
|
}
|
|
107
|
+
export function getViewMode() {
|
|
108
|
+
return loadConfig().viewMode || 'gtd';
|
|
109
|
+
}
|
|
110
|
+
export function setViewMode(viewMode) {
|
|
111
|
+
saveConfig({ viewMode });
|
|
112
|
+
}
|
|
113
|
+
export function isFirstRun() {
|
|
114
|
+
return !existsSync(CONFIG_FILE);
|
|
115
|
+
}
|
package/dist/db/index.d.ts
CHANGED
|
@@ -1,6 +1,7 @@
|
|
|
1
|
-
import
|
|
1
|
+
import { drizzle } from 'drizzle-orm/libsql';
|
|
2
2
|
import * as schema from './schema.js';
|
|
3
|
-
export
|
|
4
|
-
|
|
5
|
-
|
|
3
|
+
export type DbInstance = ReturnType<typeof drizzle<typeof schema>>;
|
|
4
|
+
export declare function initDb(): Promise<void>;
|
|
5
|
+
export declare function getDb(): DbInstance;
|
|
6
|
+
export declare function syncDb(): Promise<void>;
|
|
6
7
|
export { schema };
|
package/dist/db/index.js
CHANGED
|
@@ -1,45 +1,93 @@
|
|
|
1
|
-
import
|
|
2
|
-
import {
|
|
1
|
+
import { drizzle } from 'drizzle-orm/libsql';
|
|
2
|
+
import { createClient } from '@libsql/client';
|
|
3
3
|
import { mkdirSync, existsSync } from 'fs';
|
|
4
4
|
import { dirname } from 'path';
|
|
5
5
|
import * as schema from './schema.js';
|
|
6
|
-
import { getDbPath } from '../config.js';
|
|
6
|
+
import { getDbPath, isTursoEnabled, getTursoConfig } from '../config.js';
|
|
7
7
|
function ensureDbDir(dbPath) {
|
|
8
8
|
const dbDir = dirname(dbPath);
|
|
9
9
|
if (!existsSync(dbDir)) {
|
|
10
10
|
mkdirSync(dbDir, { recursive: true });
|
|
11
11
|
}
|
|
12
12
|
}
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
13
|
+
let client = null;
|
|
14
|
+
let dbInstance = null;
|
|
15
|
+
// リモート DB のスキーマを初期化
|
|
16
|
+
async function initializeRemoteSchema(tursoUrl, authToken) {
|
|
17
|
+
// リモート専用クライアントでスキーマを作成
|
|
18
|
+
const remoteClient = createClient({
|
|
19
|
+
url: tursoUrl,
|
|
20
|
+
authToken: authToken,
|
|
21
|
+
});
|
|
22
|
+
try {
|
|
23
|
+
const tableInfoResult = await remoteClient.execute("PRAGMA table_info(tasks)");
|
|
24
|
+
const tableInfo = tableInfoResult.rows;
|
|
25
|
+
const tableExists = tableInfo.length > 0;
|
|
26
|
+
if (!tableExists) {
|
|
27
|
+
// Fresh install: create new schema on remote
|
|
28
|
+
await remoteClient.execute(`
|
|
29
|
+
CREATE TABLE IF NOT EXISTS tasks (
|
|
30
|
+
id TEXT PRIMARY KEY,
|
|
31
|
+
title TEXT NOT NULL,
|
|
32
|
+
description TEXT,
|
|
33
|
+
status TEXT NOT NULL DEFAULT 'inbox' CHECK(status IN ('inbox', 'next', 'waiting', 'someday', 'done')),
|
|
34
|
+
is_project INTEGER NOT NULL DEFAULT 0,
|
|
35
|
+
parent_id TEXT,
|
|
36
|
+
waiting_for TEXT,
|
|
37
|
+
due_date INTEGER,
|
|
38
|
+
created_at INTEGER NOT NULL,
|
|
39
|
+
updated_at INTEGER NOT NULL
|
|
40
|
+
)
|
|
41
|
+
`);
|
|
42
|
+
await remoteClient.execute("CREATE INDEX IF NOT EXISTS idx_tasks_status ON tasks(status)");
|
|
43
|
+
await remoteClient.execute("CREATE INDEX IF NOT EXISTS idx_tasks_parent_id ON tasks(parent_id)");
|
|
44
|
+
await remoteClient.execute("CREATE INDEX IF NOT EXISTS idx_tasks_is_project ON tasks(is_project)");
|
|
45
|
+
}
|
|
46
|
+
// Create comments table
|
|
47
|
+
await remoteClient.execute(`
|
|
48
|
+
CREATE TABLE IF NOT EXISTS comments (
|
|
49
|
+
id TEXT PRIMARY KEY,
|
|
50
|
+
task_id TEXT NOT NULL,
|
|
51
|
+
content TEXT NOT NULL,
|
|
52
|
+
created_at INTEGER NOT NULL
|
|
53
|
+
)
|
|
54
|
+
`);
|
|
55
|
+
await remoteClient.execute("CREATE INDEX IF NOT EXISTS idx_comments_task_id ON comments(task_id)");
|
|
56
|
+
}
|
|
57
|
+
finally {
|
|
58
|
+
remoteClient.close();
|
|
59
|
+
}
|
|
60
|
+
}
|
|
61
|
+
// ローカル DB のスキーマを初期化(ローカルモード用)
|
|
62
|
+
async function initializeLocalSchema() {
|
|
63
|
+
if (!client)
|
|
64
|
+
return;
|
|
65
|
+
const tableInfoResult = await client.execute("PRAGMA table_info(tasks)");
|
|
66
|
+
const tableInfo = tableInfoResult.rows;
|
|
16
67
|
const hasProjectId = tableInfo.some(col => col.name === 'project_id');
|
|
17
68
|
const hasIsProject = tableInfo.some(col => col.name === 'is_project');
|
|
18
69
|
const tableExists = tableInfo.length > 0;
|
|
19
70
|
if (tableExists && hasProjectId && !hasIsProject) {
|
|
20
71
|
// Migration: old schema -> new schema
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
const projects = sqlite.prepare("SELECT * FROM projects").all();
|
|
26
|
-
const insertStmt = sqlite.prepare(`
|
|
27
|
-
INSERT INTO tasks (id, title, description, status, is_project, parent_id, waiting_for, due_date, created_at, updated_at)
|
|
28
|
-
VALUES (?, ?, ?, ?, 1, NULL, NULL, NULL, ?, ?)
|
|
29
|
-
`);
|
|
72
|
+
await client.execute("ALTER TABLE tasks ADD COLUMN is_project INTEGER NOT NULL DEFAULT 0");
|
|
73
|
+
await client.execute("ALTER TABLE tasks ADD COLUMN parent_id TEXT");
|
|
74
|
+
const projectsResult = await client.execute("SELECT * FROM projects");
|
|
75
|
+
const projects = projectsResult.rows;
|
|
30
76
|
for (const p of projects) {
|
|
31
77
|
const newStatus = p.status === 'active' ? 'next' : (p.status === 'completed' ? 'done' : p.status);
|
|
32
|
-
|
|
78
|
+
await client.execute({
|
|
79
|
+
sql: `INSERT INTO tasks (id, title, description, status, is_project, parent_id, waiting_for, due_date, created_at, updated_at)
|
|
80
|
+
VALUES (?, ?, ?, ?, 1, NULL, NULL, NULL, ?, ?)`,
|
|
81
|
+
args: [p.id, p.name, p.description, newStatus, p.created_at, p.updated_at]
|
|
82
|
+
});
|
|
33
83
|
}
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
sqlite.prepare("CREATE INDEX IF NOT EXISTS idx_tasks_parent_id ON tasks(parent_id)").run();
|
|
38
|
-
sqlite.prepare("CREATE INDEX IF NOT EXISTS idx_tasks_is_project ON tasks(is_project)").run();
|
|
84
|
+
await client.execute("UPDATE tasks SET parent_id = project_id WHERE project_id IS NOT NULL");
|
|
85
|
+
await client.execute("CREATE INDEX IF NOT EXISTS idx_tasks_parent_id ON tasks(parent_id)");
|
|
86
|
+
await client.execute("CREATE INDEX IF NOT EXISTS idx_tasks_is_project ON tasks(is_project)");
|
|
39
87
|
}
|
|
40
88
|
else if (!tableExists) {
|
|
41
89
|
// Fresh install: create new schema
|
|
42
|
-
|
|
90
|
+
await client.execute(`
|
|
43
91
|
CREATE TABLE IF NOT EXISTS tasks (
|
|
44
92
|
id TEXT PRIMARY KEY,
|
|
45
93
|
title TEXT NOT NULL,
|
|
@@ -52,21 +100,68 @@ function initializeSchema(sqlite) {
|
|
|
52
100
|
created_at INTEGER NOT NULL,
|
|
53
101
|
updated_at INTEGER NOT NULL
|
|
54
102
|
)
|
|
55
|
-
`)
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
103
|
+
`);
|
|
104
|
+
await client.execute("CREATE INDEX IF NOT EXISTS idx_tasks_status ON tasks(status)");
|
|
105
|
+
await client.execute("CREATE INDEX IF NOT EXISTS idx_tasks_parent_id ON tasks(parent_id)");
|
|
106
|
+
await client.execute("CREATE INDEX IF NOT EXISTS idx_tasks_is_project ON tasks(is_project)");
|
|
59
107
|
}
|
|
108
|
+
// Create comments table
|
|
109
|
+
await client.execute(`
|
|
110
|
+
CREATE TABLE IF NOT EXISTS comments (
|
|
111
|
+
id TEXT PRIMARY KEY,
|
|
112
|
+
task_id TEXT NOT NULL,
|
|
113
|
+
content TEXT NOT NULL,
|
|
114
|
+
created_at INTEGER NOT NULL
|
|
115
|
+
)
|
|
116
|
+
`);
|
|
117
|
+
await client.execute("CREATE INDEX IF NOT EXISTS idx_comments_task_id ON comments(task_id)");
|
|
60
118
|
}
|
|
61
|
-
|
|
119
|
+
// DB 初期化
|
|
120
|
+
export async function initDb() {
|
|
121
|
+
if (dbInstance)
|
|
122
|
+
return;
|
|
123
|
+
const dbPath = getDbPath();
|
|
124
|
+
ensureDbDir(dbPath);
|
|
125
|
+
if (isTursoEnabled()) {
|
|
126
|
+
// Turso embedded replica モード
|
|
127
|
+
const turso = getTursoConfig();
|
|
128
|
+
// 1. まずリモートにスキーマを作成
|
|
129
|
+
await initializeRemoteSchema(turso.url, turso.authToken);
|
|
130
|
+
// 2. embedded replica クライアントを作成
|
|
131
|
+
client = createClient({
|
|
132
|
+
url: `file:${dbPath}`,
|
|
133
|
+
syncUrl: turso.url,
|
|
134
|
+
authToken: turso.authToken,
|
|
135
|
+
syncInterval: 60, // 60秒ごとに自動同期
|
|
136
|
+
});
|
|
137
|
+
// 3. 初回同期(リモートからローカルにデータをプル)
|
|
138
|
+
await client.sync();
|
|
139
|
+
}
|
|
140
|
+
else {
|
|
141
|
+
// ローカルモード(libsql でローカルファイルのみ)
|
|
142
|
+
client = createClient({
|
|
143
|
+
url: `file:${dbPath}`,
|
|
144
|
+
});
|
|
145
|
+
// ローカルモードではスキーマをローカルに作成
|
|
146
|
+
await initializeLocalSchema();
|
|
147
|
+
}
|
|
148
|
+
dbInstance = drizzle(client, { schema });
|
|
149
|
+
}
|
|
150
|
+
// DB インスタンス取得
|
|
62
151
|
export function getDb() {
|
|
63
152
|
if (!dbInstance) {
|
|
64
|
-
|
|
65
|
-
ensureDbDir(dbPath);
|
|
66
|
-
const sqlite = new Database(dbPath);
|
|
67
|
-
initializeSchema(sqlite);
|
|
68
|
-
dbInstance = drizzle(sqlite, { schema });
|
|
153
|
+
throw new Error('Database not initialized. Call initDb() first.');
|
|
69
154
|
}
|
|
70
155
|
return dbInstance;
|
|
71
156
|
}
|
|
157
|
+
// 手動同期(Turso モード時のみ有効)
|
|
158
|
+
export async function syncDb() {
|
|
159
|
+
if (!client) {
|
|
160
|
+
throw new Error('Database not initialized');
|
|
161
|
+
}
|
|
162
|
+
if (!isTursoEnabled()) {
|
|
163
|
+
throw new Error('Turso sync is not enabled');
|
|
164
|
+
}
|
|
165
|
+
await client.sync();
|
|
166
|
+
}
|
|
72
167
|
export { schema };
|
package/dist/db/schema.d.ts
CHANGED
|
@@ -190,3 +190,86 @@ export declare const tasks: import("drizzle-orm/sqlite-core").SQLiteTableWithCol
|
|
|
190
190
|
export type Task = typeof tasks.$inferSelect;
|
|
191
191
|
export type NewTask = typeof tasks.$inferInsert;
|
|
192
192
|
export type TaskStatus = 'inbox' | 'next' | 'waiting' | 'someday' | 'done';
|
|
193
|
+
export declare const comments: import("drizzle-orm/sqlite-core").SQLiteTableWithColumns<{
|
|
194
|
+
name: "comments";
|
|
195
|
+
schema: undefined;
|
|
196
|
+
columns: {
|
|
197
|
+
id: import("drizzle-orm/sqlite-core").SQLiteColumn<{
|
|
198
|
+
name: "id";
|
|
199
|
+
tableName: "comments";
|
|
200
|
+
dataType: "string";
|
|
201
|
+
columnType: "SQLiteText";
|
|
202
|
+
data: string;
|
|
203
|
+
driverParam: string;
|
|
204
|
+
notNull: true;
|
|
205
|
+
hasDefault: false;
|
|
206
|
+
isPrimaryKey: true;
|
|
207
|
+
isAutoincrement: false;
|
|
208
|
+
hasRuntimeDefault: false;
|
|
209
|
+
enumValues: [string, ...string[]];
|
|
210
|
+
baseColumn: never;
|
|
211
|
+
identity: undefined;
|
|
212
|
+
generated: undefined;
|
|
213
|
+
}, {}, {
|
|
214
|
+
length: number | undefined;
|
|
215
|
+
}>;
|
|
216
|
+
taskId: import("drizzle-orm/sqlite-core").SQLiteColumn<{
|
|
217
|
+
name: "task_id";
|
|
218
|
+
tableName: "comments";
|
|
219
|
+
dataType: "string";
|
|
220
|
+
columnType: "SQLiteText";
|
|
221
|
+
data: string;
|
|
222
|
+
driverParam: string;
|
|
223
|
+
notNull: true;
|
|
224
|
+
hasDefault: false;
|
|
225
|
+
isPrimaryKey: false;
|
|
226
|
+
isAutoincrement: false;
|
|
227
|
+
hasRuntimeDefault: false;
|
|
228
|
+
enumValues: [string, ...string[]];
|
|
229
|
+
baseColumn: never;
|
|
230
|
+
identity: undefined;
|
|
231
|
+
generated: undefined;
|
|
232
|
+
}, {}, {
|
|
233
|
+
length: number | undefined;
|
|
234
|
+
}>;
|
|
235
|
+
content: import("drizzle-orm/sqlite-core").SQLiteColumn<{
|
|
236
|
+
name: "content";
|
|
237
|
+
tableName: "comments";
|
|
238
|
+
dataType: "string";
|
|
239
|
+
columnType: "SQLiteText";
|
|
240
|
+
data: string;
|
|
241
|
+
driverParam: string;
|
|
242
|
+
notNull: true;
|
|
243
|
+
hasDefault: false;
|
|
244
|
+
isPrimaryKey: false;
|
|
245
|
+
isAutoincrement: false;
|
|
246
|
+
hasRuntimeDefault: false;
|
|
247
|
+
enumValues: [string, ...string[]];
|
|
248
|
+
baseColumn: never;
|
|
249
|
+
identity: undefined;
|
|
250
|
+
generated: undefined;
|
|
251
|
+
}, {}, {
|
|
252
|
+
length: number | undefined;
|
|
253
|
+
}>;
|
|
254
|
+
createdAt: import("drizzle-orm/sqlite-core").SQLiteColumn<{
|
|
255
|
+
name: "created_at";
|
|
256
|
+
tableName: "comments";
|
|
257
|
+
dataType: "date";
|
|
258
|
+
columnType: "SQLiteTimestamp";
|
|
259
|
+
data: Date;
|
|
260
|
+
driverParam: number;
|
|
261
|
+
notNull: true;
|
|
262
|
+
hasDefault: false;
|
|
263
|
+
isPrimaryKey: false;
|
|
264
|
+
isAutoincrement: false;
|
|
265
|
+
hasRuntimeDefault: false;
|
|
266
|
+
enumValues: undefined;
|
|
267
|
+
baseColumn: never;
|
|
268
|
+
identity: undefined;
|
|
269
|
+
generated: undefined;
|
|
270
|
+
}, {}, {}>;
|
|
271
|
+
};
|
|
272
|
+
dialect: "sqlite";
|
|
273
|
+
}>;
|
|
274
|
+
export type Comment = typeof comments.$inferSelect;
|
|
275
|
+
export type NewComment = typeof comments.$inferInsert;
|
package/dist/db/schema.js
CHANGED
|
@@ -11,3 +11,9 @@ export const tasks = sqliteTable('tasks', {
|
|
|
11
11
|
createdAt: integer('created_at', { mode: 'timestamp' }).notNull(),
|
|
12
12
|
updatedAt: integer('updated_at', { mode: 'timestamp' }).notNull(),
|
|
13
13
|
});
|
|
14
|
+
export const comments = sqliteTable('comments', {
|
|
15
|
+
id: text('id').primaryKey(),
|
|
16
|
+
taskId: text('task_id').notNull(),
|
|
17
|
+
content: text('content').notNull(),
|
|
18
|
+
createdAt: integer('created_at', { mode: 'timestamp' }).notNull(),
|
|
19
|
+
});
|