feat(mvp): phase 2 - database schema & services layer

Define full Prisma schema (10 models), run initial migration, build core
services (auth, user, group, app, board, permission), Zod validators,
type definitions, API response envelope, constants, and seed script.
This commit is contained in:
2026-03-24 20:00:21 +03:00
parent cf6bde238c
commit f1b1aa5975
28 changed files with 2936 additions and 28 deletions
+125
View File
@@ -0,0 +1,125 @@
import { prisma } from '../prisma.js';
import type { CreateGroupInput, UpdateGroupInput } from '$lib/types/group.js';
export async function findAll() {
return prisma.group.findMany({
orderBy: { name: 'asc' },
include: {
_count: { select: { users: true } }
}
});
}
export async function findById(id: string) {
const group = await prisma.group.findUnique({
where: { id },
include: {
_count: { select: { users: true } }
}
});
if (!group) {
throw new Error(`Group not found: ${id}`);
}
return group;
}
export async function findByName(name: string) {
return prisma.group.findUnique({
where: { name }
});
}
export async function findDefaultGroups() {
return prisma.group.findMany({
where: { isDefault: true }
});
}
export async function create(input: CreateGroupInput) {
const existing = await prisma.group.findUnique({
where: { name: input.name }
});
if (existing) {
throw new Error(`Group with name "${input.name}" already exists`);
}
return prisma.group.create({
data: {
name: input.name,
description: input.description ?? null,
isDefault: input.isDefault ?? false
}
});
}
export async function update(id: string, input: UpdateGroupInput) {
await findById(id);
if (input.name) {
const existing = await prisma.group.findFirst({
where: { name: input.name, NOT: { id } }
});
if (existing) {
throw new Error(`Group with name "${input.name}" already exists`);
}
}
return prisma.group.update({
where: { id },
data: {
...(input.name !== undefined ? { name: input.name } : {}),
...(input.description !== undefined ? { description: input.description } : {}),
...(input.isDefault !== undefined ? { isDefault: input.isDefault } : {})
}
});
}
export async function remove(id: string) {
await findById(id);
await prisma.group.delete({ where: { id } });
}
export async function addUser(groupId: string, userId: string) {
const existing = await prisma.userGroup.findUnique({
where: { userId_groupId: { userId, groupId } }
});
if (existing) {
return existing;
}
return prisma.userGroup.create({
data: { userId, groupId }
});
}
export async function removeUser(groupId: string, userId: string) {
await prisma.userGroup.deleteMany({
where: { userId, groupId }
});
}
export async function getGroupMembers(groupId: string) {
const memberships = await prisma.userGroup.findMany({
where: { groupId },
include: {
user: {
select: {
id: true,
email: true,
displayName: true,
role: true,
avatarUrl: true
}
}
}
});
return memberships.map((m) => m.user);
}
export async function addUserToDefaultGroups(userId: string) {
const defaultGroups = await findDefaultGroups();
const results = await Promise.all(
defaultGroups.map((group) => addUser(group.id, userId))
);
return results;
}