mirror of
https://github.com/hex248/sprint.git
synced 2026-02-08 02:33:01 +00:00
db reset and seed scripts
This commit is contained in:
@@ -3,7 +3,7 @@
|
||||
"version": "0.1.0",
|
||||
"type": "module",
|
||||
"engines": {
|
||||
"bun": "1.3.4"
|
||||
"bun": "1.3.4"
|
||||
},
|
||||
"scripts": {
|
||||
"dev": "bun --watch src/index.ts --dev --PORT=3000",
|
||||
@@ -11,7 +11,9 @@
|
||||
"db:start": "docker compose up -d",
|
||||
"db:stop": "docker compose down",
|
||||
"db:migrate": "npx drizzle-kit generate && npx drizzle-kit migrate",
|
||||
"db:push": "npx drizzle-kit push"
|
||||
"db:push": "npx drizzle-kit push",
|
||||
"db:reset": "bun scripts/db-reset.ts",
|
||||
"db:seed": "bun scripts/db-seed.ts"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@types/bcrypt": "^6.0.0",
|
||||
@@ -31,8 +33,5 @@
|
||||
"drizzle-orm": "^0.45.0",
|
||||
"jsonwebtoken": "^9.0.3",
|
||||
"pg": "^8.16.3"
|
||||
},
|
||||
"engines": {
|
||||
"bun": ">=1.0.0"
|
||||
}
|
||||
}
|
||||
|
||||
48
packages/backend/scripts/db-reset.ts
Normal file
48
packages/backend/scripts/db-reset.ts
Normal file
@@ -0,0 +1,48 @@
|
||||
import "dotenv/config";
|
||||
import { execSync } from "node:child_process";
|
||||
import { sql } from "drizzle-orm";
|
||||
import { drizzle } from "drizzle-orm/node-postgres";
|
||||
|
||||
const DATABASE_URL = process.env.DATABASE_URL;
|
||||
|
||||
if (!DATABASE_URL) {
|
||||
console.error("DATABASE_URL is not set");
|
||||
process.exit(1);
|
||||
}
|
||||
|
||||
const db = drizzle({
|
||||
connection: {
|
||||
connectionString: DATABASE_URL,
|
||||
},
|
||||
});
|
||||
|
||||
async function resetDatabase() {
|
||||
console.log("resetting database...");
|
||||
|
||||
try {
|
||||
// drop all tables in the correct order (respecting foreign key constraints)
|
||||
await db.execute(sql`DROP TABLE IF EXISTS "Issue" CASCADE`);
|
||||
await db.execute(sql`DROP TABLE IF EXISTS "Project" CASCADE`);
|
||||
await db.execute(sql`DROP TABLE IF EXISTS "OrganisationMember" CASCADE`);
|
||||
await db.execute(sql`DROP TABLE IF EXISTS "Organisation" CASCADE`);
|
||||
await db.execute(sql`DROP TABLE IF EXISTS "User" CASCADE`);
|
||||
|
||||
console.log("all tables dropped");
|
||||
|
||||
// push the schema to recreate tables
|
||||
console.log("recreating schema...");
|
||||
execSync("npx drizzle-kit push --force", {
|
||||
stdio: "inherit",
|
||||
cwd: `${import.meta.dir}/..`,
|
||||
});
|
||||
|
||||
console.log("database reset complete");
|
||||
} catch (error) {
|
||||
console.error("failed to reset database:", error);
|
||||
process.exit(1);
|
||||
}
|
||||
|
||||
process.exit(0);
|
||||
}
|
||||
|
||||
resetDatabase();
|
||||
181
packages/backend/scripts/db-seed.ts
Normal file
181
packages/backend/scripts/db-seed.ts
Normal file
@@ -0,0 +1,181 @@
|
||||
import "dotenv/config";
|
||||
import { drizzle } from "drizzle-orm/node-postgres";
|
||||
import { Issue, Organisation, OrganisationMember, Project, User } from "@issue/shared";
|
||||
import bcrypt from "bcrypt";
|
||||
|
||||
const DATABASE_URL = process.env.DATABASE_URL;
|
||||
|
||||
if (!DATABASE_URL) {
|
||||
console.error("DATABASE_URL is not set");
|
||||
process.exit(1);
|
||||
}
|
||||
|
||||
const db = drizzle({
|
||||
connection: {
|
||||
connectionString: DATABASE_URL,
|
||||
},
|
||||
});
|
||||
|
||||
const hashPassword = (password: string) => bcrypt.hash(password, 10);
|
||||
|
||||
const issueTitles = [
|
||||
"Fix login redirect loop",
|
||||
"Add pagination to user list",
|
||||
"Update dependencies to latest versions",
|
||||
"Refactor authentication middleware",
|
||||
"Add unit tests for payment service",
|
||||
"Fix memory leak in websocket handler",
|
||||
"Implement password reset flow",
|
||||
"Add caching for API responses",
|
||||
"Fix date formatting in reports",
|
||||
"Add export to CSV feature",
|
||||
"Improve error messages for form validation",
|
||||
"Fix race condition in queue processor",
|
||||
"Add dark mode support",
|
||||
"Optimize database queries for dashboard",
|
||||
"Fix broken image uploads on Safari",
|
||||
"Add rate limiting to public endpoints",
|
||||
"Implement user activity logging",
|
||||
"Fix timezone handling in scheduler",
|
||||
"Add search functionality to admin panel",
|
||||
"Refactor legacy billing code",
|
||||
"Fix email template rendering",
|
||||
"Add webhook retry mechanism",
|
||||
"Improve loading states across app",
|
||||
"Fix notification preferences not saving",
|
||||
"Add bulk delete for archived items",
|
||||
"Fix SSO integration with Okta",
|
||||
"Add two-factor authentication",
|
||||
"Fix scroll position reset on navigation",
|
||||
"Implement lazy loading for images",
|
||||
"Add audit log for sensitive actions",
|
||||
"Fix PDF generation timeout",
|
||||
"Add keyboard shortcuts for common actions",
|
||||
];
|
||||
|
||||
const issueDescriptions = [
|
||||
"Users are reporting this issue in production. Need to investigate and fix.",
|
||||
"This has been requested by several customers. Should be straightforward to implement.",
|
||||
"Low priority but would improve developer experience.",
|
||||
"Blocking other work. Please prioritize.",
|
||||
"Follow-up from the security audit.",
|
||||
"Performance improvement that could reduce server costs.",
|
||||
"Part of the Q1 roadmap.",
|
||||
"Tech debt that we should address soon.",
|
||||
];
|
||||
|
||||
async function seed() {
|
||||
console.log("seeding database with demo data...");
|
||||
|
||||
try {
|
||||
const passwordHash = await hashPassword("a");
|
||||
|
||||
// create 2 users
|
||||
console.log("creating users...");
|
||||
const users = await db
|
||||
.insert(User)
|
||||
.values([
|
||||
{ name: "user 1", username: "u1", passwordHash, avatarURL: null },
|
||||
{ name: "user 2", username: "u2", passwordHash, avatarURL: null },
|
||||
])
|
||||
.returning();
|
||||
|
||||
const u1 = users[0]!;
|
||||
const u2 = users[1]!;
|
||||
|
||||
console.log(`created ${users.length} users`);
|
||||
|
||||
// create 2 orgs per user (4 total)
|
||||
console.log("creating organisations...");
|
||||
const orgs = await db
|
||||
.insert(Organisation)
|
||||
.values([
|
||||
{ name: "u1o1", slug: "u1o1", description: "User 1 organisation 1" },
|
||||
{ name: "u1o2", slug: "u1o2", description: "User 1 organisation 2" },
|
||||
{ name: "u2o1", slug: "u2o1", description: "User 2 organisation 1" },
|
||||
{ name: "u2o2", slug: "u2o2", description: "User 2 organisation 2" },
|
||||
])
|
||||
.returning();
|
||||
|
||||
const u1o1 = orgs[0]!;
|
||||
const u1o2 = orgs[1]!;
|
||||
const u2o1 = orgs[2]!;
|
||||
const u2o2 = orgs[3]!;
|
||||
|
||||
console.log(`created ${orgs.length} organisations`);
|
||||
|
||||
// add members to organisations
|
||||
console.log("adding organisation members...");
|
||||
await db.insert(OrganisationMember).values([
|
||||
{ organisationId: u1o1.id, userId: u1.id, role: "owner" },
|
||||
{ organisationId: u1o2.id, userId: u1.id, role: "owner" },
|
||||
{ organisationId: u2o1.id, userId: u2.id, role: "owner" },
|
||||
{ organisationId: u2o2.id, userId: u2.id, role: "owner" },
|
||||
]);
|
||||
|
||||
console.log("added organisation members");
|
||||
|
||||
// create 2 projects per org (8 total)
|
||||
console.log("creating projects...");
|
||||
const projects = await db
|
||||
.insert(Project)
|
||||
.values([
|
||||
{ key: "11P1", name: "u1o1p1", organisationId: u1o1.id, creatorId: u1.id },
|
||||
{ key: "11P2", name: "u1o1p2", organisationId: u1o1.id, creatorId: u1.id },
|
||||
{ key: "12P1", name: "u1o2p1", organisationId: u1o2.id, creatorId: u1.id },
|
||||
{ key: "12P2", name: "u1o2p2", organisationId: u1o2.id, creatorId: u1.id },
|
||||
{ key: "21P1", name: "u2o1p1", organisationId: u2o1.id, creatorId: u2.id },
|
||||
{ key: "21P2", name: "u2o1p2", organisationId: u2o1.id, creatorId: u2.id },
|
||||
{ key: "22P1", name: "u2o2p1", organisationId: u2o2.id, creatorId: u2.id },
|
||||
{ key: "22P2", name: "u2o2p2", organisationId: u2o2.id, creatorId: u2.id },
|
||||
])
|
||||
.returning();
|
||||
|
||||
console.log(`created ${projects.length} projects`);
|
||||
|
||||
// create 0-4 issues per project
|
||||
console.log("creating issues...");
|
||||
const allUsers = [u1, u2];
|
||||
const issueValues = [];
|
||||
let issueTitleIndex = 0;
|
||||
|
||||
for (const project of projects) {
|
||||
const numIssues = Math.floor(Math.random() * 5); // 0-4 issues
|
||||
for (let i = 1; i <= numIssues; i++) {
|
||||
const creator = allUsers[Math.floor(Math.random() * allUsers.length)]!;
|
||||
const assignee =
|
||||
Math.random() > 0.3 ? allUsers[Math.floor(Math.random() * allUsers.length)] : null;
|
||||
const title = issueTitles[issueTitleIndex % issueTitles.length]!;
|
||||
const description = issueDescriptions[Math.floor(Math.random() * issueDescriptions.length)]!;
|
||||
issueTitleIndex++;
|
||||
|
||||
issueValues.push({
|
||||
projectId: project.id,
|
||||
number: i,
|
||||
title,
|
||||
description,
|
||||
creatorId: creator.id,
|
||||
assigneeId: assignee?.id ?? null,
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
if (issueValues.length > 0) {
|
||||
await db.insert(Issue).values(issueValues);
|
||||
}
|
||||
|
||||
console.log(`created ${issueValues.length} issues`);
|
||||
|
||||
console.log("database seeding complete");
|
||||
console.log("\ndemo accounts (password: a):");
|
||||
console.log(" - u1");
|
||||
console.log(" - u2");
|
||||
} catch (error) {
|
||||
console.error("failed to seed database:", error);
|
||||
process.exit(1);
|
||||
}
|
||||
|
||||
process.exit(0);
|
||||
}
|
||||
|
||||
seed();
|
||||
@@ -1,8 +1,6 @@
|
||||
import { User } from "@issue/shared";
|
||||
import { withAuth, withCors } from "./auth/middleware";
|
||||
import { db, testDB } from "./db/client";
|
||||
import { testDB } from "./db/client";
|
||||
import { routes } from "./routes";
|
||||
import { createDemoData } from "./utils";
|
||||
|
||||
const DEV = process.argv.find((arg) => ["--dev", "--developer", "-d"].includes(arg.toLowerCase())) != null;
|
||||
const PORT = process.argv.find((arg) => arg.toLowerCase().startsWith("--port="))?.split("=")[1] || 0;
|
||||
@@ -54,15 +52,6 @@ const main = async () => {
|
||||
|
||||
console.log(`eussi (issue server) listening on ${server.url}`);
|
||||
await testDB();
|
||||
|
||||
if (DEV) {
|
||||
const users = await db.select().from(User);
|
||||
if (users.length === 0) {
|
||||
console.log("creating demo data...");
|
||||
await createDemoData();
|
||||
console.log("demo data created");
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
main();
|
||||
|
||||
@@ -1,130 +0,0 @@
|
||||
import { hashPassword } from "./auth/utils";
|
||||
import {
|
||||
createIssue,
|
||||
createOrganisation,
|
||||
createOrganisationMember,
|
||||
createProject,
|
||||
createUser,
|
||||
} from "./db/queries";
|
||||
|
||||
export const createDemoData = async () => {
|
||||
const passwordHash = await hashPassword("a");
|
||||
|
||||
// create two users
|
||||
const user1 = await createUser("test 1", "t1", passwordHash);
|
||||
if (!user1) {
|
||||
throw new Error("failed to create test 1");
|
||||
}
|
||||
|
||||
const user2 = await createUser("test 2", "t2", passwordHash);
|
||||
if (!user2) {
|
||||
throw new Error("failed to create test 2");
|
||||
}
|
||||
|
||||
// create four organisations
|
||||
const organisations = [];
|
||||
for (let i = 1; i <= 4; i++) {
|
||||
const org = await createOrganisation(
|
||||
`Demo Organisation ${i}`,
|
||||
`demo-org-${i}`,
|
||||
`A demo organisation ${i} for testing`,
|
||||
);
|
||||
if (!org) {
|
||||
throw new Error(`failed to create demo organisation ${i}`);
|
||||
}
|
||||
organisations.push(org);
|
||||
}
|
||||
|
||||
// set up organisation memberships
|
||||
// user 1 owns: org 1, org 2; has access to: org 3
|
||||
// user 2 owns: org 3, org 4; has access to: org 2
|
||||
const org1 = organisations[0];
|
||||
const org2 = organisations[1];
|
||||
const org3 = organisations[2];
|
||||
const org4 = organisations[3];
|
||||
if (!org1 || !org2 || !org3 || !org4) {
|
||||
throw new Error("failed to create organisations");
|
||||
}
|
||||
|
||||
// user 1 memberships
|
||||
await createOrganisationMember(org1.id, user1.id, "owner"); // owns org 1
|
||||
await createOrganisationMember(org2.id, user1.id, "owner"); // owns org 2
|
||||
await createOrganisationMember(org3.id, user1.id, "member"); // member of org 3
|
||||
|
||||
// user 2 memberships
|
||||
await createOrganisationMember(org2.id, user2.id, "member"); // member of org 2
|
||||
await createOrganisationMember(org3.id, user2.id, "owner"); // owns org 3
|
||||
await createOrganisationMember(org4.id, user2.id, "owner"); // owns org 4
|
||||
|
||||
// project names: AAAAA, BBBBB, CCCCC, DDDDD, EEEEE, FFFFF, GGGGG, HHHHH, IIIII, JJJJJ, KKKKK, LLLLL
|
||||
const projectNames = [
|
||||
"AAAAA",
|
||||
"BBBBB",
|
||||
"CCCCC",
|
||||
"DDDDD",
|
||||
"EEEEE",
|
||||
"FFFFF",
|
||||
"GGGGG",
|
||||
"HHHHH",
|
||||
"IIIII",
|
||||
"JJJJJ",
|
||||
"KKKKK",
|
||||
"LLLLL",
|
||||
];
|
||||
|
||||
// create 3 projects per organisation
|
||||
let projectIndex = 0;
|
||||
const orgConfigs = [
|
||||
{ org: org1, creator: user1 }, // org 1: user1
|
||||
{ org: org2, creator: user1 }, // org 2: user1
|
||||
{ org: org3, creator: user2 }, // org 3: user2
|
||||
{ org: org4, creator: user2 }, // org 4: user2
|
||||
];
|
||||
|
||||
for (const config of orgConfigs) {
|
||||
for (let projNum = 0; projNum < 3; projNum++) {
|
||||
const projectName = projectNames[projectIndex++];
|
||||
if (!projectName) {
|
||||
throw new Error("ran out of project names");
|
||||
}
|
||||
|
||||
const project = await createProject(
|
||||
projectName.slice(0, 4),
|
||||
projectName,
|
||||
config.creator.id,
|
||||
config.org.id,
|
||||
);
|
||||
if (!project) {
|
||||
throw new Error(`failed to create demo project: ${projectName}`);
|
||||
}
|
||||
|
||||
// create some issues for each project
|
||||
for (let i = 1; i <= 3; i++) {
|
||||
let assignee: number | undefined;
|
||||
|
||||
// for crossover organizations (org2 and org3), randomly assign to either user
|
||||
if (config.org.id === org2.id || config.org.id === org3.id) {
|
||||
// 40% chance to assign to creator, 40% chance to assign to other user, 20% chance unassigned
|
||||
const rand = Math.random();
|
||||
if (rand < 0.4) {
|
||||
assignee = config.creator.id;
|
||||
} else if (rand < 0.8) {
|
||||
assignee = config.org.id === org2.id ? user2.id : user1.id; // other user
|
||||
}
|
||||
// else: undefined (unassigned)
|
||||
} else {
|
||||
// for exclusive organizations (org1 and org4), assign to creator on even issues
|
||||
assignee = i % 2 === 0 ? config.creator.id : undefined;
|
||||
}
|
||||
|
||||
await createIssue(
|
||||
project.id,
|
||||
`Issue ${i} in ${projectName}`,
|
||||
`This is a description for issue ${i} in ${projectName}.`,
|
||||
config.creator.id,
|
||||
assignee,
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
};
|
||||
Reference in New Issue
Block a user