Add 'packages/backend/' from commit 'acce648ee5e7e3a3006451e637c0db654820cc48'

git-subtree-dir: packages/backend
git-subtree-mainline: d0babd62af
git-subtree-split: acce648ee5
This commit is contained in:
Oliver Bryan
2025-12-13 20:21:47 +00:00
33 changed files with 1306 additions and 0 deletions

View File

@@ -0,0 +1,22 @@
import "dotenv/config";
import { drizzle } from "drizzle-orm/node-postgres";
if (!process.env.DATABASE_URL) {
throw new Error("DATABASE_URL is not set in environment variables");
}
export const db = drizzle({
connection: {
connectionString: process.env.DATABASE_URL,
},
});
export const testDB = async () => {
try {
await db.execute("SELECT 1;");
console.log("db connected");
} catch (err) {
console.log("db down");
process.exit();
}
};

View File

@@ -0,0 +1,3 @@
export * from "./users";
export * from "./projects";
export * from "./issues";

View File

@@ -0,0 +1,59 @@
import { eq, sql, and } from "drizzle-orm";
import { db } from "../client";
import { Issue } from "../schema";
export async function createIssue(projectId: number, title: string, description: string) {
// prevents two issues with the same unique number
return await db.transaction(async (tx) => {
// raw sql for speed
// most recent issue from project
const [lastIssue] = await tx
.select({ max: sql<number>`MAX(${Issue.number})` })
.from(Issue)
.where(eq(Issue.projectId, projectId));
const nextNumber = (lastIssue?.max || 0) + 1;
// 2. create new issue
const [newIssue] = await tx
.insert(Issue)
.values({
projectId,
title,
description,
number: nextNumber,
})
.returning();
return newIssue;
});
}
export async function deleteIssue(id: number) {
return await db.delete(Issue).where(eq(Issue.id, id));
}
export async function updateIssue(id: number, updates: { title?: string; description?: string }) {
return await db.update(Issue).set(updates).where(eq(Issue.id, id)).returning();
}
export async function getIssues() {
return await db.select().from(Issue);
}
export async function getIssuesByProject(projectId: number) {
return await db.select().from(Issue).where(eq(Issue.projectId, projectId));
}
export async function getIssueByID(id: number) {
const [issue] = await db.select().from(Issue).where(eq(Issue.id, id));
return issue;
}
export async function getIssueByNumber(projectId: number, number: number) {
const [issue] = await db
.select()
.from(Issue)
.where(and(eq(Issue.projectId, projectId), eq(Issue.number, number)));
return issue;
}

View File

@@ -0,0 +1,40 @@
import { eq } from "drizzle-orm";
import { db } from "../client";
import { Issue, Project, User } from "../schema";
export async function createProject(blob: string, name: string, ownerId: number) {
const [project] = await db
.insert(Project)
.values({
blob,
name,
ownerId,
})
.returning();
return project;
}
export async function updateProject(
projectId: number,
updates: { blob?: string; name?: string; ownerId?: number },
) {
const [project] = await db.update(Project).set(updates).where(eq(Project.id, projectId)).returning();
return project;
}
export async function deleteProject(projectId: number) {
// delete all of the project's issues first
await db.delete(Issue).where(eq(Issue.projectId, projectId));
// delete actual project
await db.delete(Project).where(eq(Project.id, projectId));
}
export async function getProjectByID(projectId: number) {
const [project] = await db.select().from(Project).where(eq(Project.id, projectId));
return project;
}
export async function getProjectByBlob(projectBlob: string) {
const [project] = await db.select().from(Project).where(eq(Project.blob, projectBlob));
return project;
}

View File

@@ -0,0 +1,18 @@
import { eq } from "drizzle-orm";
import { db } from "../client";
import { User } from "../schema";
export async function createUser(name: string, username: string) {
const [user] = await db.insert(User).values({ name, username }).returning();
return user;
}
export async function getUserById(id: number) {
const [user] = await db.select().from(User).where(eq(User.id, id));
return user;
}
export async function getUserByUsername(username: string) {
const [user] = await db.select().from(User).where(eq(User.username, username));
return user;
}

View File

@@ -0,0 +1,36 @@
import { integer, pgTable, uniqueIndex, varchar } from "drizzle-orm/pg-core";
export const User = pgTable("User", {
id: integer().primaryKey().generatedAlwaysAsIdentity(),
name: varchar({ length: 256 }).notNull(),
username: varchar({ length: 32 }).notNull().unique(),
});
export const Project = pgTable("Project", {
id: integer().primaryKey().generatedAlwaysAsIdentity(),
blob: varchar({ length: 4 }).notNull(),
name: varchar({ length: 256 }).notNull(),
ownerId: integer()
.notNull()
.references(() => User.id),
});
export const Issue = pgTable(
"Issue",
{
id: integer().primaryKey().generatedAlwaysAsIdentity(),
projectId: integer()
.notNull()
.references(() => Project.id),
number: integer("number").notNull(),
title: varchar({ length: 256 }).notNull(),
description: varchar({ length: 2048 }).notNull(),
},
(t) => [
// ensures unique numbers per project
// you can have Issue 1 in PROJ and Issue 1 in TEST, but not two Issue 1s in PROJ
uniqueIndex("unique_project_issue_number").on(t.projectId, t.number),
],
);