Compare commits

...

10 Commits

Author SHA1 Message Date
136b58c44f Add /tasks route to load all tasks.
Currently just a proof of concept. The data is pulled from the database
and a few fields display in a table.
2025-09-27 23:28:57 -05:00
5fddf2f394 Create seeding script.
The script updates task related tables only at this time.

Arguments "seed" and "reset" can be passed. "seed" accepts a flag
"--count" which specifies a custom number of task entries to generate.
"task_types" are currently fixed at TA and PC.

To account for db adapter limitations, the script will automatically
batch databse entries by 500 requests.
2025-09-27 23:24:20 -05:00
b6c6f74576 Add drizzle-seed and faker-js for database seeding. 2025-09-27 23:23:21 -05:00
b68935a11e Update getAll() method to include relations. 2025-09-27 23:21:24 -05:00
0331042b8b Change return value of getAll() failure. 2025-09-27 14:39:57 -05:00
8fe8ba68f8 Fix logger log level for development. 2025-09-27 14:22:38 -05:00
a0d12d4661 Add task service.
Database interactions will be handled by service classes. Service
classes will be called by page load functions as well as by API
endpoints. This will allow one source of truth for authorization and
data validation.
2025-09-27 14:03:18 -05:00
2c9b855d2a Add database type. 2025-09-27 11:02:17 -05:00
22c782c3ec Remove drizzle directory from repo. 2025-09-26 21:52:49 -05:00
450bcf173f Add task related tables.
Pivoting from "records" to a combination of "tasks", "micro-tasks",
"assets", and a few others.
2025-09-26 21:49:36 -05:00
13 changed files with 257 additions and 148 deletions

1
.gitignore vendored
View File

@ -12,6 +12,7 @@ node_modules
*.log *.log
# Databases # Databases
/drizzle
*.db *.db
# OS # OS

View File

@ -6,11 +6,13 @@
"dependencies": { "dependencies": {
"better-sqlite3": "^12.4.1", "better-sqlite3": "^12.4.1",
"drizzle-orm": "^0.44.5", "drizzle-orm": "^0.44.5",
"drizzle-seed": "^0.3.1",
"pino": "^9.11.0", "pino": "^9.11.0",
}, },
"devDependencies": { "devDependencies": {
"@eslint/compat": "^1.2.5", "@eslint/compat": "^1.2.5",
"@eslint/js": "^9.22.0", "@eslint/js": "^9.22.0",
"@faker-js/faker": "^10.0.0",
"@sveltejs/adapter-auto": "^6.0.0", "@sveltejs/adapter-auto": "^6.0.0",
"@sveltejs/kit": "^2.22.0", "@sveltejs/kit": "^2.22.0",
"@sveltejs/vite-plugin-svelte": "^6.0.0", "@sveltejs/vite-plugin-svelte": "^6.0.0",
@ -109,6 +111,8 @@
"@eslint/plugin-kit": ["@eslint/plugin-kit@0.3.5", "", { "dependencies": { "@eslint/core": "^0.15.2", "levn": "^0.4.1" } }, "sha512-Z5kJ+wU3oA7MMIqVR9tyZRtjYPr4OC004Q4Rw7pgOKUOKkJfZ3O24nz3WYfGRpMDNmcOi3TwQOmgm7B7Tpii0w=="], "@eslint/plugin-kit": ["@eslint/plugin-kit@0.3.5", "", { "dependencies": { "@eslint/core": "^0.15.2", "levn": "^0.4.1" } }, "sha512-Z5kJ+wU3oA7MMIqVR9tyZRtjYPr4OC004Q4Rw7pgOKUOKkJfZ3O24nz3WYfGRpMDNmcOi3TwQOmgm7B7Tpii0w=="],
"@faker-js/faker": ["@faker-js/faker@10.0.0", "", {}, "sha512-UollFEUkVXutsaP+Vndjxar40Gs5JL2HeLcl8xO1QAjJgOdhc3OmBFWyEylS+RddWaaBiAzH+5/17PLQJwDiLw=="],
"@humanfs/core": ["@humanfs/core@0.19.1", "", {}, "sha512-5DyQ4+1JEUzejeK1JGICcideyfUbGixgS9jNgex5nqkW+cY7WZhxBigmieN5Qnw9ZosSNVC9KQKyb+GUaGyKUA=="], "@humanfs/core": ["@humanfs/core@0.19.1", "", {}, "sha512-5DyQ4+1JEUzejeK1JGICcideyfUbGixgS9jNgex5nqkW+cY7WZhxBigmieN5Qnw9ZosSNVC9KQKyb+GUaGyKUA=="],
"@humanfs/node": ["@humanfs/node@0.16.7", "", { "dependencies": { "@humanfs/core": "^0.19.1", "@humanwhocodes/retry": "^0.4.0" } }, "sha512-/zUx+yOsIrG4Y43Eh2peDeKCxlRt/gET6aHfaKpuq267qXdYDFViVHfMaLyygZOnl0kGWxFIgsBy8QFuTLUXEQ=="], "@humanfs/node": ["@humanfs/node@0.16.7", "", { "dependencies": { "@humanfs/core": "^0.19.1", "@humanwhocodes/retry": "^0.4.0" } }, "sha512-/zUx+yOsIrG4Y43Eh2peDeKCxlRt/gET6aHfaKpuq267qXdYDFViVHfMaLyygZOnl0kGWxFIgsBy8QFuTLUXEQ=="],
@ -303,6 +307,8 @@
"drizzle-orm": ["drizzle-orm@0.44.5", "", { "peerDependencies": { "@aws-sdk/client-rds-data": ">=3", "@cloudflare/workers-types": ">=4", "@electric-sql/pglite": ">=0.2.0", "@libsql/client": ">=0.10.0", "@libsql/client-wasm": ">=0.10.0", "@neondatabase/serverless": ">=0.10.0", "@op-engineering/op-sqlite": ">=2", "@opentelemetry/api": "^1.4.1", "@planetscale/database": ">=1.13", "@prisma/client": "*", "@tidbcloud/serverless": "*", "@types/better-sqlite3": "*", "@types/pg": "*", "@types/sql.js": "*", "@upstash/redis": ">=1.34.7", "@vercel/postgres": ">=0.8.0", "@xata.io/client": "*", "better-sqlite3": ">=7", "bun-types": "*", "expo-sqlite": ">=14.0.0", "gel": ">=2", "knex": "*", "kysely": "*", "mysql2": ">=2", "pg": ">=8", "postgres": ">=3", "sql.js": ">=1", "sqlite3": ">=5" }, "optionalPeers": ["@aws-sdk/client-rds-data", "@cloudflare/workers-types", "@electric-sql/pglite", "@libsql/client", "@libsql/client-wasm", "@neondatabase/serverless", "@op-engineering/op-sqlite", "@opentelemetry/api", "@planetscale/database", "@prisma/client", "@tidbcloud/serverless", "@types/better-sqlite3", "@types/pg", "@types/sql.js", "@upstash/redis", "@vercel/postgres", "@xata.io/client", "better-sqlite3", "bun-types", "expo-sqlite", "gel", "knex", "kysely", "mysql2", "pg", "postgres", "sql.js", "sqlite3"] }, "sha512-jBe37K7d8ZSKptdKfakQFdeljtu3P2Cbo7tJoJSVZADzIKOBo9IAJPOmMsH2bZl90bZgh8FQlD8BjxXA/zuBkQ=="], "drizzle-orm": ["drizzle-orm@0.44.5", "", { "peerDependencies": { "@aws-sdk/client-rds-data": ">=3", "@cloudflare/workers-types": ">=4", "@electric-sql/pglite": ">=0.2.0", "@libsql/client": ">=0.10.0", "@libsql/client-wasm": ">=0.10.0", "@neondatabase/serverless": ">=0.10.0", "@op-engineering/op-sqlite": ">=2", "@opentelemetry/api": "^1.4.1", "@planetscale/database": ">=1.13", "@prisma/client": "*", "@tidbcloud/serverless": "*", "@types/better-sqlite3": "*", "@types/pg": "*", "@types/sql.js": "*", "@upstash/redis": ">=1.34.7", "@vercel/postgres": ">=0.8.0", "@xata.io/client": "*", "better-sqlite3": ">=7", "bun-types": "*", "expo-sqlite": ">=14.0.0", "gel": ">=2", "knex": "*", "kysely": "*", "mysql2": ">=2", "pg": ">=8", "postgres": ">=3", "sql.js": ">=1", "sqlite3": ">=5" }, "optionalPeers": ["@aws-sdk/client-rds-data", "@cloudflare/workers-types", "@electric-sql/pglite", "@libsql/client", "@libsql/client-wasm", "@neondatabase/serverless", "@op-engineering/op-sqlite", "@opentelemetry/api", "@planetscale/database", "@prisma/client", "@tidbcloud/serverless", "@types/better-sqlite3", "@types/pg", "@types/sql.js", "@upstash/redis", "@vercel/postgres", "@xata.io/client", "better-sqlite3", "bun-types", "expo-sqlite", "gel", "knex", "kysely", "mysql2", "pg", "postgres", "sql.js", "sqlite3"] }, "sha512-jBe37K7d8ZSKptdKfakQFdeljtu3P2Cbo7tJoJSVZADzIKOBo9IAJPOmMsH2bZl90bZgh8FQlD8BjxXA/zuBkQ=="],
"drizzle-seed": ["drizzle-seed@0.3.1", "", { "dependencies": { "pure-rand": "^6.1.0" }, "peerDependencies": { "drizzle-orm": ">=0.36.4" }, "optionalPeers": ["drizzle-orm"] }, "sha512-F/0lgvfOAsqlYoHM/QAGut4xXIOXoE5VoAdv2FIl7DpGYVXlAzKuJO+IphkKUFK3Dz+rFlOsQLnMNrvoQ0cx7g=="],
"end-of-stream": ["end-of-stream@1.4.5", "", { "dependencies": { "once": "^1.4.0" } }, "sha512-ooEGc6HP26xXq/N+GCGOT0JKCLDGrq2bQUZrQ7gyrJiZANJ/8YDTxTpQBXGMn+WbIQXNVpyWymm7KYVICQnyOg=="], "end-of-stream": ["end-of-stream@1.4.5", "", { "dependencies": { "once": "^1.4.0" } }, "sha512-ooEGc6HP26xXq/N+GCGOT0JKCLDGrq2bQUZrQ7gyrJiZANJ/8YDTxTpQBXGMn+WbIQXNVpyWymm7KYVICQnyOg=="],
"esbuild": ["esbuild@0.25.10", "", { "optionalDependencies": { "@esbuild/aix-ppc64": "0.25.10", "@esbuild/android-arm": "0.25.10", "@esbuild/android-arm64": "0.25.10", "@esbuild/android-x64": "0.25.10", "@esbuild/darwin-arm64": "0.25.10", "@esbuild/darwin-x64": "0.25.10", "@esbuild/freebsd-arm64": "0.25.10", "@esbuild/freebsd-x64": "0.25.10", "@esbuild/linux-arm": "0.25.10", "@esbuild/linux-arm64": "0.25.10", "@esbuild/linux-ia32": "0.25.10", "@esbuild/linux-loong64": "0.25.10", "@esbuild/linux-mips64el": "0.25.10", "@esbuild/linux-ppc64": "0.25.10", "@esbuild/linux-riscv64": "0.25.10", "@esbuild/linux-s390x": "0.25.10", "@esbuild/linux-x64": "0.25.10", "@esbuild/netbsd-arm64": "0.25.10", "@esbuild/netbsd-x64": "0.25.10", "@esbuild/openbsd-arm64": "0.25.10", "@esbuild/openbsd-x64": "0.25.10", "@esbuild/openharmony-arm64": "0.25.10", "@esbuild/sunos-x64": "0.25.10", "@esbuild/win32-arm64": "0.25.10", "@esbuild/win32-ia32": "0.25.10", "@esbuild/win32-x64": "0.25.10" }, "bin": { "esbuild": "bin/esbuild" } }, "sha512-9RiGKvCwaqxO2owP61uQ4BgNborAQskMR6QusfWzQqv7AZOg5oGehdY2pRJMTKuwxd1IDBP4rSbI5lHzU7SMsQ=="], "esbuild": ["esbuild@0.25.10", "", { "optionalDependencies": { "@esbuild/aix-ppc64": "0.25.10", "@esbuild/android-arm": "0.25.10", "@esbuild/android-arm64": "0.25.10", "@esbuild/android-x64": "0.25.10", "@esbuild/darwin-arm64": "0.25.10", "@esbuild/darwin-x64": "0.25.10", "@esbuild/freebsd-arm64": "0.25.10", "@esbuild/freebsd-x64": "0.25.10", "@esbuild/linux-arm": "0.25.10", "@esbuild/linux-arm64": "0.25.10", "@esbuild/linux-ia32": "0.25.10", "@esbuild/linux-loong64": "0.25.10", "@esbuild/linux-mips64el": "0.25.10", "@esbuild/linux-ppc64": "0.25.10", "@esbuild/linux-riscv64": "0.25.10", "@esbuild/linux-s390x": "0.25.10", "@esbuild/linux-x64": "0.25.10", "@esbuild/netbsd-arm64": "0.25.10", "@esbuild/netbsd-x64": "0.25.10", "@esbuild/openbsd-arm64": "0.25.10", "@esbuild/openbsd-x64": "0.25.10", "@esbuild/openharmony-arm64": "0.25.10", "@esbuild/sunos-x64": "0.25.10", "@esbuild/win32-arm64": "0.25.10", "@esbuild/win32-ia32": "0.25.10", "@esbuild/win32-x64": "0.25.10" }, "bin": { "esbuild": "bin/esbuild" } }, "sha512-9RiGKvCwaqxO2owP61uQ4BgNborAQskMR6QusfWzQqv7AZOg5oGehdY2pRJMTKuwxd1IDBP4rSbI5lHzU7SMsQ=="],
@ -507,6 +513,8 @@
"punycode": ["punycode@2.3.1", "", {}, "sha512-vYt7UD1U9Wg6138shLtLOvdAu+8DsC/ilFtEVHcH+wydcSpNE20AfSOduf6MkRFahL5FY7X1oU7nKVZFtfq8Fg=="], "punycode": ["punycode@2.3.1", "", {}, "sha512-vYt7UD1U9Wg6138shLtLOvdAu+8DsC/ilFtEVHcH+wydcSpNE20AfSOduf6MkRFahL5FY7X1oU7nKVZFtfq8Fg=="],
"pure-rand": ["pure-rand@6.1.0", "", {}, "sha512-bVWawvoZoBYpp6yIoQtQXHZjmz35RSVHnUOTefl8Vcjr8snTPY1wnpSPMWekcFwbxI6gtmT7rSYPFvz71ldiOA=="],
"queue-microtask": ["queue-microtask@1.2.3", "", {}, "sha512-NuaNSa6flKT5JaSYQzJok04JzTL1CA6aGhv5rfLW3PgqA+M2ChpZQnAC8h8i4ZFkBS8X5RqkDBHA7r4hej3K9A=="], "queue-microtask": ["queue-microtask@1.2.3", "", {}, "sha512-NuaNSa6flKT5JaSYQzJok04JzTL1CA6aGhv5rfLW3PgqA+M2ChpZQnAC8h8i4ZFkBS8X5RqkDBHA7r4hej3K9A=="],
"quick-format-unescaped": ["quick-format-unescaped@4.0.4", "", {}, "sha512-tYC1Q1hgyRuHgloV/YXs2w15unPVh8qfu/qCTfhTYamaw7fyhumKa2yGpdSo87vY32rIclj+4fWYQXUMs9EHvg=="], "quick-format-unescaped": ["quick-format-unescaped@4.0.4", "", {}, "sha512-tYC1Q1hgyRuHgloV/YXs2w15unPVh8qfu/qCTfhTYamaw7fyhumKa2yGpdSo87vY32rIclj+4fWYQXUMs9EHvg=="],

View File

@ -1,15 +0,0 @@
CREATE TABLE `records` (
`id` integer PRIMARY KEY AUTOINCREMENT NOT NULL,
`record_id` text NOT NULL,
`type_id` integer NOT NULL,
FOREIGN KEY (`type_id`) REFERENCES `record_types`(`id`) ON UPDATE no action ON DELETE no action
);
--> statement-breakpoint
CREATE UNIQUE INDEX `records_record_id_unique` ON `records` (`record_id`);--> statement-breakpoint
CREATE INDEX `chores_index` ON `records` (`record_id`) WHERE type_id = 1;--> statement-breakpoint
CREATE INDEX `project_index` ON `records` (`record_id`) WHERE type_id = 2;--> statement-breakpoint
CREATE INDEX `ticket_index` ON `records` (`record_id`) WHERE type_id = 3;--> statement-breakpoint
CREATE TABLE `record_types` (
`id` integer PRIMARY KEY AUTOINCREMENT NOT NULL,
`type` text
);

View File

@ -1,119 +0,0 @@
{
"version": "6",
"dialect": "sqlite",
"id": "feb276b9-6e2b-4204-885b-167701a4a707",
"prevId": "00000000-0000-0000-0000-000000000000",
"tables": {
"records": {
"name": "records",
"columns": {
"id": {
"name": "id",
"type": "integer",
"primaryKey": true,
"notNull": true,
"autoincrement": true
},
"record_id": {
"name": "record_id",
"type": "text",
"primaryKey": false,
"notNull": true,
"autoincrement": false
},
"type_id": {
"name": "type_id",
"type": "integer",
"primaryKey": false,
"notNull": true,
"autoincrement": false
}
},
"indexes": {
"records_record_id_unique": {
"name": "records_record_id_unique",
"columns": [
"record_id"
],
"isUnique": true
},
"chores_index": {
"name": "chores_index",
"columns": [
"record_id"
],
"isUnique": false,
"where": "type_id = 1"
},
"project_index": {
"name": "project_index",
"columns": [
"record_id"
],
"isUnique": false,
"where": "type_id = 2"
},
"ticket_index": {
"name": "ticket_index",
"columns": [
"record_id"
],
"isUnique": false,
"where": "type_id = 3"
}
},
"foreignKeys": {
"records_type_id_record_types_id_fk": {
"name": "records_type_id_record_types_id_fk",
"tableFrom": "records",
"tableTo": "record_types",
"columnsFrom": [
"type_id"
],
"columnsTo": [
"id"
],
"onDelete": "no action",
"onUpdate": "no action"
}
},
"compositePrimaryKeys": {},
"uniqueConstraints": {},
"checkConstraints": {}
},
"record_types": {
"name": "record_types",
"columns": {
"id": {
"name": "id",
"type": "integer",
"primaryKey": true,
"notNull": true,
"autoincrement": true
},
"type": {
"name": "type",
"type": "text",
"primaryKey": false,
"notNull": false,
"autoincrement": false
}
},
"indexes": {},
"foreignKeys": {},
"compositePrimaryKeys": {},
"uniqueConstraints": {},
"checkConstraints": {}
}
},
"views": {},
"enums": {},
"_meta": {
"schemas": {},
"tables": {},
"columns": {}
},
"internal": {
"indexes": {}
}
}

View File

@ -1,13 +0,0 @@
{
"version": "7",
"dialect": "sqlite",
"entries": [
{
"idx": 0,
"version": "6",
"when": 1758833816361,
"tag": "0000_wandering_riptide",
"breakpoints": true
}
]
}

View File

@ -15,6 +15,7 @@
"devDependencies": { "devDependencies": {
"@eslint/compat": "^1.2.5", "@eslint/compat": "^1.2.5",
"@eslint/js": "^9.22.0", "@eslint/js": "^9.22.0",
"@faker-js/faker": "^10.0.0",
"@sveltejs/adapter-auto": "^6.0.0", "@sveltejs/adapter-auto": "^6.0.0",
"@sveltejs/kit": "^2.22.0", "@sveltejs/kit": "^2.22.0",
"@sveltejs/vite-plugin-svelte": "^6.0.0", "@sveltejs/vite-plugin-svelte": "^6.0.0",
@ -35,6 +36,7 @@
"dependencies": { "dependencies": {
"better-sqlite3": "^12.4.1", "better-sqlite3": "^12.4.1",
"drizzle-orm": "^0.44.5", "drizzle-orm": "^0.44.5",
"drizzle-seed": "^0.3.1",
"pino": "^9.11.0" "pino": "^9.11.0"
} }
} }

View File

@ -1,7 +1,9 @@
import * as schema2 from "$lib/server/db/schema/records"; import * as schema2 from "$lib/server/db/schema/records";
import * as schema1 from "$lib/server/db/schema/recordTypes"; import * as schema1 from "$lib/server/db/schema/recordTypes";
import * as schema3 from "$lib/server/db/schema/tasks";
import { Database } from "bun:sqlite"; import { Database } from "bun:sqlite";
import { drizzle } from "drizzle-orm/bun-sqlite"; import { drizzle } from "drizzle-orm/bun-sqlite";
const sqlite = new Database("sqlite.db"); const sqlite = new Database("sqlite.db");
export const db = drizzle({ client: sqlite, schema: { ...schema1, ...schema2 } }); export const db = drizzle({ client: sqlite, schema: { ...schema1, ...schema2, ...schema3 } });
export type DB = typeof db;

View File

@ -0,0 +1,71 @@
import { relations, sql } from "drizzle-orm";
import { type AnySQLiteColumn, check, int, sqliteTable, text } from "drizzle-orm/sqlite-core";
export type TaskIntegrationConfigs = {
source_control: Array<{
repo_id: string;
commits: Array<{
hash: string;
message: string;
description?: string;
}>;
}>;
};
export const taskTypes = sqliteTable("task_types", {
id: int("id").primaryKey({ autoIncrement: true }),
name: text("name").unique(),
prefix: text("prefix").unique(),
}, (table) => [
check("prefix_limit", sql`LENGTH(${table.prefix}) <=2`),
]);
export const taskTypesRelations = relations(taskTypes, ({ many }) => ({
tasks: many(tasks),
}));
export const tasks = sqliteTable("tasks", {
id: int("id").primaryKey({ autoIncrement: true }),
taskId: text("task_id").unique(),
description: text("description"),
type: int("type").references(() => taskTypes.id),
subtype: text("subtype"),
openDate: text("open_date").$type<Date["toISOString"]>(),
closeDate: text("close_date").$type<Date["toISOString"]>(),
status: text("status"),
priority: text("priority"),
parent: int("parent").references((): AnySQLiteColumn => tasks.id),
body: text("body"),
bodyHistory: text("body_history"),
checklist: text("checklist", { mode: "json" }).$type<
{
enabled: boolean;
entries: Array<{
group?: string;
sort_order: number;
checked: boolean;
text: string;
}>;
}
>(),
updateChain: text("udpate_chain", { mode: "json" }).$type<
{
enabled: boolean;
entries: Array<{
updated_on: Date["toISOString"];
updated_by: string;
body: string;
}>;
}
>(),
integrations: text("integrations", { mode: "json" }).$type<
{ type: keyof TaskIntegrationConfigs; config: TaskIntegrationConfigs["source_control"] }
>(),
});
export const tasksRelations = relations(tasks, ({ one }) => ({
type: one(taskTypes, {
fields: [tasks.type],
references: [taskTypes.id],
}),
}));

View File

@ -11,12 +11,14 @@ const loggerConfig: Record<string, pino.LoggerOptions> = {
transport: { transport: {
targets: [ targets: [
{ {
level: "debug",
target: "pino-pretty", target: "pino-pretty",
options: { options: {
colorize: true, colorize: true,
}, },
}, },
{ {
level: "debug",
target: "pino/file", target: "pino/file",
options: { options: {
destination: "./logs/dev.log", destination: "./logs/dev.log",

View File

@ -0,0 +1,29 @@
import { type DB, db } from "$lib/server/db/db";
import logger from "../logger";
class TasksService {
private db: DB;
private caller: "internal" | "api";
constructor(caller: "internal" | "api", dbClient: DB = db) {
this.db = dbClient;
this.caller = caller;
}
public async getAll() {
logger.info("Fetching all task records...");
try {
const result = await this.db.query.tasks.findMany({
with: {
type: true,
},
});
logger.debug(`Found ${result.length} records.`);
return result;
} catch (e) {
logger.error({ msg: "Error querying the database.", error: e });
return false as const;
}
}
}
export default TasksService;

View File

@ -0,0 +1,10 @@
import TasksService from "$lib/server/services/tasks";
import type { PageServerLoad } from "./$types";
export const load: PageServerLoad = async () => {
const tasks = new TasksService("internal");
return {
tasks: await tasks.getAll(),
test: "string",
};
};

View File

@ -0,0 +1,32 @@
<script lang="ts">
import type { PageProps } from "./$types";
let { data }: PageProps = $props();
</script>
{#if data.tasks}
<p>{data.tasks.length} total records.</p>
<table>
<thead>
<tr>
<td><strong>Id</strong></td>
<td><strong>Description</strong></td>
<td><strong>Status</strong></td>
</tr>
</thead>
<tbody>
{#each data.tasks as task (task.id)}
<tr>
<td
>{task.type?.prefix +
task.taskId}</td
>
<td>{task.description}</td>
<td>{task.status}</td>
</tr>
{/each}
</tbody>
</table>
{:else}
<p>There was an error accessing the database.</p>
{/if}

99
utils/seed.ts Normal file
View File

@ -0,0 +1,99 @@
import { db } from "../src/lib/server/db/db";
import { tasks, taskTypes } from "../src/lib/server/db/schema/tasks";
import { faker } from "@faker-js/faker";
import logger from "../src/lib/server/logger";
async function resetDatabase() {
logger.info("Resetting database...");
// Delete in reverse order of creation to respect foreign key constraints
await db.delete(tasks);
await db.delete(taskTypes);
logger.info("Database reset complete.");
}
async function seedDatabase(count: number) {
logger.info(`Seeding database with ${count} tasks...`);
// 1. Seed Task Types
const insertedTaskTypes = await db.insert(taskTypes).values([
{ name: "Product Change", prefix: "PC" },
{ name: "Task", prefix: "TA" },
]).returning();
logger.info(`Seeded ${insertedTaskTypes.length} task types.`);
if (insertedTaskTypes.length === 0) {
throw new Error("Task type seeding failed. Cannot seed tasks.");
}
// 2. Seed Tasks
const newTasks = [];
const usedTaskIds = new Set<number>();
for (let i = 0; i < count; i++) {
let randomId: number;
do {
randomId = Math.floor(100000 + Math.random() * 900000);
} while (usedTaskIds.has(randomId));
usedTaskIds.add(randomId);
newTasks.push({
// task_id with a guaranteed unique 6 digit number
taskId: randomId.toString(),
// a random phrase in each description
description: faker.lorem.sentence(),
// open_date with a valid date
openDate: faker.date.recent({ days: 30 }).toISOString(),
// a random paragraph in body
body: faker.lorem.paragraphs(3),
// other fields as needed
type: faker.helpers.arrayElement(insertedTaskTypes).id,
status: faker.helpers.arrayElement(["Todo", "In Progress", "Done", "Canceled"]),
priority: faker.helpers.arrayElement(["Low", "Medium", "High", "Urgent"]),
});
}
// 3. Insert the new tasks in chunks to avoid exceeding driver limits
logger.info(`Inserting ${newTasks.length} tasks in chunks...`);
const chunkSize = 500;
let totalInserted = 0;
for (let i = 0; i < newTasks.length; i += chunkSize) {
const chunk = newTasks.slice(i, i + chunkSize);
const inserted = await db.insert(tasks).values(chunk).returning();
totalInserted += inserted.length;
logger.info(` ... inserted chunk ${Math.floor(i / chunkSize) + 1}, ${totalInserted}/${newTasks.length} tasks`);
}
logger.info(`Seeding complete. Total tasks inserted: ${totalInserted}.`);
}
async function main() {
const mode = process.argv[2];
// Default count if no flag is provided
let count = 20;
// Find and parse the --count flag
const countArg = process.argv.find((arg) => arg.startsWith("--count="));
if (countArg) {
const countValue = parseInt(countArg.split("=")[1], 10);
if (!isNaN(countValue) && countValue > 0) {
count = countValue;
}
}
if (mode === "reset") {
await resetDatabase();
} else if (mode === "seed") {
// Seeding implies a reset first for a clean slate
await resetDatabase();
await seedDatabase(count);
} else {
logger.error('Invalid mode. Use "seed" or "reset".');
process.exit(1);
}
logger.info("Script finished.");
}
main().catch((e) => {
logger.error(e, "An error occurred during the script execution.");
process.exit(1);
});