diff --git a/.dockerignore b/.dockerignore
deleted file mode 100644
index 2e71340..0000000
--- a/.dockerignore
+++ /dev/null
@@ -1,17 +0,0 @@
-.git
-.jj
-node_modules
-**/node_modules
-.turbo
-.cache
-coverage
-**/dist
-**/build
-**/*.tsbuildinfo
-.env*
-**/.env*
-!.env.example
-!**/.env.example
-logs
-*.log
-docs
diff --git a/.env.example b/.env.example
deleted file mode 100644
index 2e78d98..0000000
--- a/.env.example
+++ /dev/null
@@ -1,15 +0,0 @@
-POSTGRES_DB=minmon
-POSTGRES_USER=postgres
-POSTGRES_PASSWORD=postgres
-POSTGRES_PORT=5432
-
-DATABASE_URL=postgresql://postgres:postgres@postgres:5432/minmon
-
-BETTER_AUTH_SECRET=minmon-compose-secret-change-me-123456
-BETTER_AUTH_URL=http://localhost:3000
-CORS_ORIGIN=http://localhost:3001
-NODE_ENV=development
-
-SERVER_PORT=3000
-WEB_PORT=3001
-VITE_SERVER_URL=http://localhost:3000
diff --git a/README.md b/README.md
index 228bcd9..e06f18a 100644
--- a/README.md
+++ b/README.md
@@ -159,25 +159,6 @@ bun run dev
- Web: `http://localhost:5173`
- API: `http://localhost:3000`
-## Docker Compose Deployment
-
-Run the full stack with PostgreSQL, database initialization, API, and web UI:
-
-```bash
-cp .env.example .env
-docker compose up -d
-```
-
-Default endpoints:
-
-- Web: `http://localhost:3001`
-- API: `http://localhost:3000`
-
-On first startup, the database initializer applies the schema and seeds the sample admin only when the users table is empty.
-
-Docker Compose reads the root `.env` file automatically. Edit it before startup if you need different ports, public URLs, credentials, or secrets.
-
-
## Project Structure
```text
@@ -205,5 +186,5 @@ See:
## Notes
- DNS checking is limited to A record / IPv4 as required.
-- Docker Compose is available for running the app stack; the app itself does not include SSH, reverse proxy, or host orchestration automation.
+- This is not a deployment platform and does not include SSH, Docker orchestration, or reverse proxy automation.
- Full workspace typecheck is still blocked by a pre-existing desktop typing issue for `three`, but the web app and server app are verified.
diff --git a/apps/server/Dockerfile b/apps/server/Dockerfile
deleted file mode 100644
index 0c49b3b..0000000
--- a/apps/server/Dockerfile
+++ /dev/null
@@ -1,33 +0,0 @@
-# syntax=docker/dockerfile:1
-
-FROM oven/bun:1.3.12 AS base
-WORKDIR /app
-ENV CI=1
-
-COPY package.json bun.lock turbo.json tsconfig.json bts.jsonc ./
-COPY apps ./apps
-COPY packages ./packages
-
-FROM base AS deps
-RUN bun install --frozen-lockfile
-
-FROM deps AS tools
-ENV NODE_ENV=development
-RUN touch apps/server/.env
-
-FROM deps AS build
-ENV NODE_ENV=production
-RUN bun run --filter server build
-
-FROM base AS prod-deps
-RUN bun install --frozen-lockfile --production
-
-FROM oven/bun:1.3.12 AS runtime
-WORKDIR /app
-ENV NODE_ENV=production
-
-COPY --from=prod-deps /app/node_modules ./node_modules
-COPY --from=build /app/apps/server/dist ./apps/server/dist
-
-EXPOSE 3000
-CMD ["bun", "apps/server/dist/index.mjs"]
diff --git a/apps/web/Dockerfile b/apps/web/Dockerfile
deleted file mode 100644
index f3e637b..0000000
--- a/apps/web/Dockerfile
+++ /dev/null
@@ -1,21 +0,0 @@
-# syntax=docker/dockerfile:1
-
-FROM oven/bun:1.3.12 AS build
-WORKDIR /app
-ENV CI=1
-
-ARG VITE_SERVER_URL=http://localhost:3000
-ENV VITE_SERVER_URL=${VITE_SERVER_URL}
-
-COPY package.json bun.lock turbo.json tsconfig.json bts.jsonc ./
-COPY apps ./apps
-COPY packages ./packages
-
-RUN bun install --frozen-lockfile
-RUN bun run --filter web build
-
-FROM nginx:1.27-alpine AS runtime
-COPY apps/web/nginx.conf /etc/nginx/conf.d/default.conf
-COPY --from=build /app/apps/web/dist /usr/share/nginx/html
-
-EXPOSE 80
diff --git a/apps/web/nginx.conf b/apps/web/nginx.conf
deleted file mode 100644
index 79fd959..0000000
--- a/apps/web/nginx.conf
+++ /dev/null
@@ -1,11 +0,0 @@
-server {
- listen 80;
- server_name _;
-
- root /usr/share/nginx/html;
- index index.html;
-
- location / {
- try_files $uri $uri/ /index.html;
- }
-}
diff --git a/apps/web/src/components/dashboard-shell.tsx b/apps/web/src/components/dashboard-shell.tsx
index a3ddfee..a0147e4 100644
--- a/apps/web/src/components/dashboard-shell.tsx
+++ b/apps/web/src/components/dashboard-shell.tsx
@@ -15,7 +15,6 @@ import {
LayoutGrid,
MenuIcon,
Server,
- Users,
Wrench,
} from "lucide-react";
import { useEffect, useState } from "react";
@@ -26,42 +25,30 @@ const dashboardLinks = [
label: "Overview",
description: "Health, alerts, and recent checks",
icon: LayoutGrid,
- permission: "dashboard.read",
},
{
to: "/dashboard/servers",
label: "Servers",
description: "Inventory and host details",
icon: Server,
- permission: "servers.read",
},
{
to: "/dashboard/services",
label: "Services",
description: "Processes and uptime targets",
icon: Wrench,
- permission: "services.read",
},
{
to: "/dashboard/domains",
label: "Domains",
description: "DNS, SSL, and expiry status",
icon: Globe,
- permission: "domains.read",
},
{
to: "/dashboard/notes",
label: "Notes",
description: "Runbooks and operational handover",
icon: BookText,
- permission: "notes.read",
- },
- {
- to: "/dashboard/users",
- label: "Users",
- description: "Roles and permission assignments",
- icon: Users,
- permission: "users.read",
},
] as const;
@@ -73,18 +60,14 @@ function isActive(pathname: string, to: string): boolean {
function NavItems({
pathname,
- permissions,
onNavigate,
}: {
pathname: string;
- permissions: string[];
onNavigate?: () => void;
}) {
- const allowedPermissions = new Set(permissions);
-
return (
- {allowedPermissions.has("servers.create") ? (
-
-
-
- ) : null}
- {allowedPermissions.has("notes.create") ? (
-
-
-
- ) : null}
+
+
+
+
+
+
@@ -217,7 +192,7 @@ export function DashboardShell({
Navigation
-
+
diff --git a/apps/web/src/routeTree.gen.ts b/apps/web/src/routeTree.gen.ts
index 7eeb976..af9cab6 100644
--- a/apps/web/src/routeTree.gen.ts
+++ b/apps/web/src/routeTree.gen.ts
@@ -13,7 +13,6 @@ import { Route as LoginRouteImport } from './routes/login'
import { Route as DashboardRouteImport } from './routes/dashboard'
import { Route as IndexRouteImport } from './routes/index'
import { Route as DashboardIndexRouteImport } from './routes/dashboard.index'
-import { Route as DashboardUsersRouteImport } from './routes/dashboard.users'
import { Route as DashboardServicesRouteImport } from './routes/dashboard.services'
import { Route as DashboardServersRouteImport } from './routes/dashboard.servers'
import { Route as DashboardNotesRouteImport } from './routes/dashboard.notes'
@@ -47,11 +46,6 @@ const DashboardIndexRoute = DashboardIndexRouteImport.update({
path: '/',
getParentRoute: () => DashboardRoute,
} as any)
-const DashboardUsersRoute = DashboardUsersRouteImport.update({
- id: '/users',
- path: '/users',
- getParentRoute: () => DashboardRoute,
-} as any)
const DashboardServicesRoute = DashboardServicesRouteImport.update({
id: '/services',
path: '/services',
@@ -124,7 +118,6 @@ export interface FileRoutesByFullPath {
'/dashboard/notes': typeof DashboardNotesRoute
'/dashboard/servers': typeof DashboardServersRoute
'/dashboard/services': typeof DashboardServicesRoute
- '/dashboard/users': typeof DashboardUsersRoute
'/dashboard/': typeof DashboardIndexRoute
'/dashboard/domains/$domainId': typeof DashboardDomainsDomainIdRoute
'/dashboard/domains/new': typeof DashboardDomainsNewRoute
@@ -142,7 +135,6 @@ export interface FileRoutesByTo {
'/dashboard/notes': typeof DashboardNotesRoute
'/dashboard/servers': typeof DashboardServersRoute
'/dashboard/services': typeof DashboardServicesRoute
- '/dashboard/users': typeof DashboardUsersRoute
'/dashboard': typeof DashboardIndexRoute
'/dashboard/domains/$domainId': typeof DashboardDomainsDomainIdRoute
'/dashboard/domains/new': typeof DashboardDomainsNewRoute
@@ -162,7 +154,6 @@ export interface FileRoutesById {
'/dashboard/notes': typeof DashboardNotesRoute
'/dashboard/servers': typeof DashboardServersRoute
'/dashboard/services': typeof DashboardServicesRoute
- '/dashboard/users': typeof DashboardUsersRoute
'/dashboard/': typeof DashboardIndexRoute
'/dashboard/domains_/$domainId': typeof DashboardDomainsDomainIdRoute
'/dashboard/domains_/new': typeof DashboardDomainsNewRoute
@@ -183,7 +174,6 @@ export interface FileRouteTypes {
| '/dashboard/notes'
| '/dashboard/servers'
| '/dashboard/services'
- | '/dashboard/users'
| '/dashboard/'
| '/dashboard/domains/$domainId'
| '/dashboard/domains/new'
@@ -201,7 +191,6 @@ export interface FileRouteTypes {
| '/dashboard/notes'
| '/dashboard/servers'
| '/dashboard/services'
- | '/dashboard/users'
| '/dashboard'
| '/dashboard/domains/$domainId'
| '/dashboard/domains/new'
@@ -220,7 +209,6 @@ export interface FileRouteTypes {
| '/dashboard/notes'
| '/dashboard/servers'
| '/dashboard/services'
- | '/dashboard/users'
| '/dashboard/'
| '/dashboard/domains_/$domainId'
| '/dashboard/domains_/new'
@@ -268,13 +256,6 @@ declare module '@tanstack/react-router' {
preLoaderRoute: typeof DashboardIndexRouteImport
parentRoute: typeof DashboardRoute
}
- '/dashboard/users': {
- id: '/dashboard/users'
- path: '/users'
- fullPath: '/dashboard/users'
- preLoaderRoute: typeof DashboardUsersRouteImport
- parentRoute: typeof DashboardRoute
- }
'/dashboard/services': {
id: '/dashboard/services'
path: '/services'
@@ -367,7 +348,6 @@ interface DashboardRouteChildren {
DashboardNotesRoute: typeof DashboardNotesRoute
DashboardServersRoute: typeof DashboardServersRoute
DashboardServicesRoute: typeof DashboardServicesRoute
- DashboardUsersRoute: typeof DashboardUsersRoute
DashboardIndexRoute: typeof DashboardIndexRoute
DashboardDomainsDomainIdRoute: typeof DashboardDomainsDomainIdRoute
DashboardDomainsNewRoute: typeof DashboardDomainsNewRoute
@@ -384,7 +364,6 @@ const DashboardRouteChildren: DashboardRouteChildren = {
DashboardNotesRoute: DashboardNotesRoute,
DashboardServersRoute: DashboardServersRoute,
DashboardServicesRoute: DashboardServicesRoute,
- DashboardUsersRoute: DashboardUsersRoute,
DashboardIndexRoute: DashboardIndexRoute,
DashboardDomainsDomainIdRoute: DashboardDomainsDomainIdRoute,
DashboardDomainsNewRoute: DashboardDomainsNewRoute,
diff --git a/apps/web/src/routes/dashboard.tsx b/apps/web/src/routes/dashboard.tsx
index 87f5073..ac05bd1 100644
--- a/apps/web/src/routes/dashboard.tsx
+++ b/apps/web/src/routes/dashboard.tsx
@@ -1,9 +1,7 @@
import { createFileRoute } from "@tanstack/react-router";
-import { useQuery } from "@tanstack/react-query";
import { DashboardShell } from "@/components/dashboard-shell";
import { requireSession } from "@/lib/protected-route";
-import { trpc } from "@/utils/trpc";
export const Route = createFileRoute("/dashboard")({
component: RouteComponent,
@@ -12,13 +10,8 @@ export const Route = createFileRoute("/dashboard")({
function RouteComponent() {
const { session } = Route.useRouteContext();
- const me = useQuery(trpc.users.me.queryOptions());
return (
-
+
);
}
diff --git a/apps/web/src/routes/dashboard.users.tsx b/apps/web/src/routes/dashboard.users.tsx
deleted file mode 100644
index 70f789c..0000000
--- a/apps/web/src/routes/dashboard.users.tsx
+++ /dev/null
@@ -1,329 +0,0 @@
-import { Button } from "@minmon/ui/components/button";
-import { Card, CardContent, CardDescription, CardHeader, CardTitle } from "@minmon/ui/components/card";
-import { Checkbox } from "@minmon/ui/components/checkbox";
-import { StatusBadge } from "@minmon/ui/components/status-badge";
-import { useForm } from "@tanstack/react-form";
-import { useMutation, useQuery } from "@tanstack/react-query";
-import { createFileRoute } from "@tanstack/react-router";
-import { toast } from "sonner";
-import z from "zod";
-
-import { ConfirmDeleteIconButton, FormCard, PageSection, QueryStateCard, ResourceListCard, TextField } from "@/components/dashboard-ui";
-import { queryClient, trpc, trpcClient } from "@/utils/trpc";
-
-export const Route = createFileRoute("/dashboard/users")({
- component: RouteComponent,
-});
-
-type RoleOption = {
- id: string;
- name: string;
- description: string | null;
-};
-
-type UserCreateFormValues = {
- name: string;
- email: string;
- password: string;
- roleIds: string[];
-};
-
-const userCreateSchema = z.object({
- name: z.string().min(2, "Name must be at least 2 characters"),
- email: z.email("Invalid email address"),
- password: z.string().min(8, "Password must be at least 8 characters"),
- roleIds: z.array(z.string()).min(1, "Select at least one role"),
-});
-
-function RoleBadges({ roles }: { roles: { id: string; name: string }[] }) {
- if (!roles.length) {
- return No roles;
- }
-
- return (
-
- {roles.map((role) => (
-
- {role.name}
-
- ))}
-
- );
-}
-
-function RoleAssignmentCard({ roles, permissions }: { roles: RoleOption[]; permissions: string[] }) {
- const users = useQuery(trpc.users.list.queryOptions());
- const allowedPermissions = new Set(permissions);
- const updateRoles = useMutation({
- mutationFn: async ({ userId, roleIds }: { userId: string; roleIds: string[] }) =>
- trpcClient.users.updateRoles.mutate({ userId, roleIds }),
- onSuccess: async () => {
- toast.success("User roles updated");
- await queryClient.invalidateQueries();
- },
- onError: (error) => toast.error(error.message),
- });
- const removeUser = useMutation({
- mutationFn: async (id: string) => trpcClient.users.delete.mutate({ id }),
- onSuccess: async () => {
- toast.success("User deleted");
- await queryClient.invalidateQueries();
- },
- onError: (error) => toast.error(error.message),
- });
-
- if (users.isLoading) {
- return ;
- }
-
- if (users.isError || !users.data) {
- return ;
- }
-
- return (
- (
-
- {item.name}
- {item.email}
-
- ),
- },
- {
- key: "roles",
- header: "Current roles",
- render: (item: (typeof users.data)[number]) => ,
- },
- ...(allowedPermissions.has("users.assignRoles")
- ? [
- {
- key: "assign",
- header: "Assign roles",
- render: (item: (typeof users.data)[number]) => {
- const assignedRoleIds = new Set(item.roles.map((role) => role.id));
-
- return (
-
- {roles.map((role) => {
- const checked = assignedRoleIds.has(role.id);
- const nextRoleIds = checked
- ? item.roles.map((assignedRole) => assignedRole.id).filter((roleId) => roleId !== role.id)
- : [...item.roles.map((assignedRole) => assignedRole.id), role.id];
-
- return (
-
- );
- })}
-
- );
- },
- },
- ]
- : []),
- ...(allowedPermissions.has("users.delete")
- ? [
- {
- key: "actions",
- header: "Actions",
- className: "w-20",
- render: (item: (typeof users.data)[number]) => (
- removeUser.mutate(item.id)}
- />
- ),
- },
- ]
- : []),
- ]}
- items={users.data}
- emptyTitle="No users found"
- emptyDescription="Users appear here after they sign up or are seeded."
- />
- );
-}
-
-function CreateUserCard({ roles }: { roles: RoleOption[] }) {
- const createUser = useMutation({
- mutationFn: async (values: UserCreateFormValues) => trpcClient.users.create.mutate(values),
- onSuccess: async () => {
- toast.success("User created");
- form.reset();
- await queryClient.invalidateQueries();
- },
- onError: (error) => toast.error(error.message),
- });
-
- const form = useForm({
- defaultValues: {
- name: "",
- email: "",
- password: "",
- roleIds: [roles.find((role) => role.name === "Viewer")?.id ?? roles[0]?.id ?? ""].filter(Boolean),
- } satisfies UserCreateFormValues,
- onSubmit: async ({ value }) => {
- await createUser.mutateAsync(value);
- },
- validators: {
- onSubmit: userCreateSchema,
- },
- });
-
- return (
-
- );
-}
-
-function RouteComponent() {
- const roles = useQuery(trpc.users.roles.queryOptions());
- const me = useQuery(trpc.users.me.queryOptions());
-
- if (roles.isLoading || me.isLoading) {
- return ;
- }
-
- if (roles.isError || !roles.data || me.isError || !me.data) {
- return ;
- }
-
- const allowedPermissions = new Set(me.data.permissions);
-
- return (
-
-
- {allowedPermissions.has("users.create") ?
: null}
-
-
- Role guide
- Default RBAC roles seeded by the application.
-
-
- {roles.data.map((role) => (
-
-
- {role.name}
-
-
{role.description}
-
- ))}
-
-
-
-
-
- );
-}
diff --git a/docker-compose.yml b/docker-compose.yml
deleted file mode 100644
index c3139d9..0000000
--- a/docker-compose.yml
+++ /dev/null
@@ -1,64 +0,0 @@
-name: minmon
-
-services:
- postgres:
- image: postgres:17-alpine
- environment:
- POSTGRES_DB: ${POSTGRES_DB:?POSTGRES_DB must be set in root .env}
- POSTGRES_USER: ${POSTGRES_USER:?POSTGRES_USER must be set in root .env}
- POSTGRES_PASSWORD: ${POSTGRES_PASSWORD:?POSTGRES_PASSWORD must be set in root .env}
- ports:
- - "${POSTGRES_PORT:?POSTGRES_PORT must be set in root .env}:5432"
- volumes:
- - postgres_data:/var/lib/postgresql/data
- healthcheck:
- test: ["CMD-SHELL", "pg_isready -U $${POSTGRES_USER} -d $${POSTGRES_DB}"]
- interval: 10s
- timeout: 5s
- retries: 5
- start_period: 10s
- restart: unless-stopped
-
- db-init:
- build:
- context: .
- dockerfile: apps/server/Dockerfile
- target: tools
- environment:
- <<: *server-environment
- command: ["sh", "-c", "bun run db:push && bun run --filter @minmon/db db:seed:if-empty"]
- depends_on:
- postgres:
- condition: service_healthy
- restart: "no"
-
- server:
- build:
- context: .
- dockerfile: apps/server/Dockerfile
- environment:
- <<: *server-environment
- ports:
- - "${SERVER_PORT:?SERVER_PORT must be set in root .env}:3000"
- depends_on:
- postgres:
- condition: service_healthy
- db-init:
- condition: service_completed_successfully
- restart: unless-stopped
-
- web:
- build:
- context: .
- dockerfile: apps/web/Dockerfile
- args:
- VITE_SERVER_URL: ${VITE_SERVER_URL:?VITE_SERVER_URL must be set in root .env}
- ports:
- - "${WEB_PORT:?WEB_PORT must be set in root .env}:80"
- depends_on:
- server:
- condition: service_started
- restart: unless-stopped
-
-volumes:
- postgres_data:
diff --git a/docs/run-instructions.md b/docs/run-instructions.md
index ddbd54a..d59428e 100644
--- a/docs/run-instructions.md
+++ b/docs/run-instructions.md
@@ -114,25 +114,6 @@ bun run dev
- Web: `http://localhost:5173`
- API: `http://localhost:3000`
-## Docker Compose Deployment
-
-Run the deployable stack with one command:
-
-```bash
-cp .env.example .env
-docker compose up -d
-```
-
-Default Docker endpoints:
-
-- Web: `http://localhost:3001`
-- API: `http://localhost:3000`
-
-The Compose stack starts PostgreSQL, applies the schema, and seeds the sample admin only if the users table is empty.
-
-Docker Compose reads the root `.env` file automatically. Edit it before startup if you need different ports, public URLs, credentials, or secrets.
-
-
## Current Behavior Notes
### List Ordering
@@ -176,4 +157,4 @@ bun x tsc --noEmit -p packages/api/tsconfig.json
- Full workspace typecheck may still fail because of a pre-existing `apps/desktop` typing issue for `three`.
- The verified implementation path is the web app + server app + database push + database seed.
- DNS checking is limited to A record / IPv4.
-- Docker Compose is available for running the app stack; the app itself does not include SSH, reverse proxy, or host orchestration automation.
+- This project is not a deployment platform and does not include SSH, Docker orchestration, or reverse proxy automation.
diff --git a/packages/api/src/context.ts b/packages/api/src/context.ts
index 5026dd5..d6a1081 100644
--- a/packages/api/src/context.ts
+++ b/packages/api/src/context.ts
@@ -1,42 +1,17 @@
import { auth } from "@minmon/auth";
-import { db, permission, role, rolePermission, userRole } from "@minmon/db";
import type { Context as ElysiaContext } from "elysia";
-import { eq } from "drizzle-orm";
export type CreateContextOptions = {
context: ElysiaContext;
};
-async function getAuthorization(userId: string) {
- const rows = await db
- .select({
- roleId: role.id,
- roleName: role.name,
- permissionName: permission.name,
- })
- .from(userRole)
- .innerJoin(role, eq(role.id, userRole.roleId))
- .innerJoin(rolePermission, eq(rolePermission.roleId, role.id))
- .innerJoin(permission, eq(permission.id, rolePermission.permissionId))
- .where(eq(userRole.userId, userId));
-
- return {
- roles: Array.from(new Map(rows.map((row) => [row.roleId, { id: row.roleId, name: row.roleName }])).values()),
- permissions: Array.from(new Set(rows.map((row) => row.permissionName))),
- };
-}
-
export async function createContext({ context }: CreateContextOptions) {
const session = await auth.api.getSession({
headers: context.request.headers,
});
-
- const authorization = session ? await getAuthorization(session.user.id) : { roles: [], permissions: [] };
-
return {
auth: null,
session,
- authorization,
};
}
diff --git a/packages/api/src/index.ts b/packages/api/src/index.ts
index da992c4..a287def 100644
--- a/packages/api/src/index.ts
+++ b/packages/api/src/index.ts
@@ -1,7 +1,6 @@
import { initTRPC, TRPCError } from "@trpc/server";
import type { Context } from "./context";
-import type { Permission } from "./permissions";
export const t = initTRPC.context().create();
@@ -24,33 +23,3 @@ export const protectedProcedure = t.procedure.use(({ ctx, next }) => {
},
});
});
-
-export function requirePermission(permission: Permission) {
- return t.middleware(({ ctx, next }) => {
- if (!ctx.session) {
- throw new TRPCError({
- code: "UNAUTHORIZED",
- message: "Authentication required",
- cause: "No session",
- });
- }
-
- if (!ctx.authorization.permissions.includes(permission)) {
- throw new TRPCError({
- code: "FORBIDDEN",
- message: `Missing permission: ${permission}`,
- });
- }
-
- return next({
- ctx: {
- ...ctx,
- session: ctx.session,
- },
- });
- });
-}
-
-export function permissionedProcedure(permission: Permission) {
- return t.procedure.use(requirePermission(permission));
-}
diff --git a/packages/api/src/permissions.ts b/packages/api/src/permissions.ts
deleted file mode 100644
index 38b1f2f..0000000
--- a/packages/api/src/permissions.ts
+++ /dev/null
@@ -1 +0,0 @@
-export { permissions, roleDefinitions, type Permission, type RoleName } from "@minmon/db/rbac";
diff --git a/packages/api/src/routers/dashboard.ts b/packages/api/src/routers/dashboard.ts
index da673a9..205972d 100644
--- a/packages/api/src/routers/dashboard.ts
+++ b/packages/api/src/routers/dashboard.ts
@@ -2,10 +2,10 @@ import { db } from "@minmon/db";
import { domain, note, server, service } from "@minmon/db";
import { count, desc, eq, inArray } from "drizzle-orm";
-import { permissionedProcedure, router } from "../index";
+import { protectedProcedure, router } from "../index";
export const dashboardRouter = router({
- summary: permissionedProcedure("dashboard.read").query(async () => {
+ summary: protectedProcedure.query(async () => {
const [
serverCount,
serviceCount,
diff --git a/packages/api/src/routers/domains.ts b/packages/api/src/routers/domains.ts
index 9d0381c..5f04711 100644
--- a/packages/api/src/routers/domains.ts
+++ b/packages/api/src/routers/domains.ts
@@ -3,7 +3,7 @@ import { domain, service } from "@minmon/db";
import { desc, eq } from "drizzle-orm";
import { runAndPersistDomainCheck } from "../lib/domain-check";
-import { permissionedProcedure, router } from "../index";
+import { protectedProcedure, router } from "../index";
import {
byIdSchema,
domainCreateSchema,
@@ -19,7 +19,7 @@ import {
} from "./utils";
export const domainsRouter = router({
- list: permissionedProcedure("domains.read").input(domainListSchema).query(async ({ input }) => {
+ list: protectedProcedure.input(domainListSchema).query(async ({ input }) => {
if (!input?.serviceId) {
return db.select().from(domain).orderBy(desc(domain.createdAt), desc(domain.id));
}
@@ -33,11 +33,11 @@ export const domainsRouter = router({
.orderBy(desc(domain.createdAt), desc(domain.id));
}),
- byId: permissionedProcedure("domains.read").input(byIdSchema).query(async ({ input }) => {
+ byId: protectedProcedure.input(byIdSchema).query(async ({ input }) => {
return requireDomain(input.id);
}),
- create: permissionedProcedure("domains.create").input(domainCreateSchema).mutation(async ({ input }) => {
+ create: protectedProcedure.input(domainCreateSchema).mutation(async ({ input }) => {
const { service: parentService, server: parentServer } = await requireServiceWithServer(input.serviceId);
const expectedServerIp = input.expectedServerIp ?? parentServer.primaryIpAddress;
@@ -56,7 +56,7 @@ export const domainsRouter = router({
return created;
}),
- update: permissionedProcedure("domains.update").input(domainUpdateSchema).mutation(async ({ input }) => {
+ update: protectedProcedure.input(domainUpdateSchema).mutation(async ({ input }) => {
const { id, serviceId, ...changes } = input;
const existing = await requireDomain(id);
@@ -78,13 +78,13 @@ export const domainsRouter = router({
return updated;
}),
- delete: permissionedProcedure("domains.delete").input(byIdSchema).mutation(async ({ input }) => {
+ delete: protectedProcedure.input(byIdSchema).mutation(async ({ input }) => {
await requireDomain(input.id);
await db.delete(domain).where(eq(domain.id, input.id));
return { id: input.id };
}),
- check: permissionedProcedure("domains.check").input(byIdSchema).mutation(async ({ input }) => {
+ check: protectedProcedure.input(byIdSchema).mutation(async ({ input }) => {
const existing = await requireDomain(input.id);
const result = await runAndPersistDomainCheck(db, {
@@ -99,7 +99,7 @@ export const domainsRouter = router({
};
}),
- serviceOptions: permissionedProcedure("services.read").query(async () => {
+ serviceOptions: protectedProcedure.query(async () => {
return db
.select({
id: service.id,
diff --git a/packages/api/src/routers/index.ts b/packages/api/src/routers/index.ts
index 3fd7cfe..c287721 100644
--- a/packages/api/src/routers/index.ts
+++ b/packages/api/src/routers/index.ts
@@ -5,7 +5,6 @@ import { domainsRouter } from "./domains";
import { notesRouter } from "./notes";
import { serversRouter } from "./servers";
import { servicesRouter } from "./services";
-import { usersRouter } from "./users";
export const appRouter = router({
healthCheck: publicProcedure.query(() => {
@@ -16,6 +15,5 @@ export const appRouter = router({
services: servicesRouter,
domains: domainsRouter,
notes: notesRouter,
- users: usersRouter,
});
export type AppRouter = typeof appRouter;
diff --git a/packages/api/src/routers/notes.ts b/packages/api/src/routers/notes.ts
index 645a870..ccbd0a0 100644
--- a/packages/api/src/routers/notes.ts
+++ b/packages/api/src/routers/notes.ts
@@ -2,7 +2,7 @@ import { db } from "@minmon/db";
import { note, server } from "@minmon/db";
import { desc, eq } from "drizzle-orm";
-import { permissionedProcedure, router } from "../index";
+import { protectedProcedure, router } from "../index";
import { byIdSchema, noteCreateSchema, noteListSchema, noteUpdateSchema } from "./schemas";
import {
createEntityId,
@@ -12,7 +12,7 @@ import {
} from "./utils";
export const notesRouter = router({
- list: permissionedProcedure("notes.read").input(noteListSchema).query(async ({ input }) => {
+ list: protectedProcedure.input(noteListSchema).query(async ({ input }) => {
if (!input?.serverId) {
return db.select().from(note).orderBy(desc(note.createdAt), desc(note.id));
}
@@ -26,11 +26,11 @@ export const notesRouter = router({
.orderBy(desc(note.createdAt), desc(note.id));
}),
- byId: permissionedProcedure("notes.read").input(byIdSchema).query(async ({ input }) => {
+ byId: protectedProcedure.input(byIdSchema).query(async ({ input }) => {
return requireNote(input.id);
}),
- create: permissionedProcedure("notes.create").input(noteCreateSchema).mutation(async ({ input }) => {
+ create: protectedProcedure.input(noteCreateSchema).mutation(async ({ input }) => {
await requireServer(input.serverId);
const [created] = await db
@@ -44,7 +44,7 @@ export const notesRouter = router({
return created;
}),
- update: permissionedProcedure("notes.update").input(noteUpdateSchema).mutation(async ({ input }) => {
+ update: protectedProcedure.input(noteUpdateSchema).mutation(async ({ input }) => {
const { id, serverId, ...changes } = input;
await requireNote(id);
@@ -61,13 +61,13 @@ export const notesRouter = router({
return updated;
}),
- delete: permissionedProcedure("notes.delete").input(byIdSchema).mutation(async ({ input }) => {
+ delete: protectedProcedure.input(byIdSchema).mutation(async ({ input }) => {
await requireNote(input.id);
await db.delete(note).where(eq(note.id, input.id));
return { id: input.id };
}),
- serverOptions: permissionedProcedure("servers.read").query(async () => {
+ serverOptions: protectedProcedure.query(async () => {
return db
.select({
id: server.id,
diff --git a/packages/api/src/routers/schemas.ts b/packages/api/src/routers/schemas.ts
index b69a4ec..a7256e8 100644
--- a/packages/api/src/routers/schemas.ts
+++ b/packages/api/src/routers/schemas.ts
@@ -105,15 +105,3 @@ export const noteCreateSchema = z.object({
export const noteUpdateSchema = noteCreateSchema.partial().extend({
id: entityIdSchema,
});
-
-export const updateUserRolesSchema = z.object({
- userId: entityIdSchema,
- roleIds: z.array(entityIdSchema).min(1, "Select at least one role"),
-});
-
-export const userCreateSchema = z.object({
- name: z.string().trim().min(2, "Name must be at least 2 characters").max(120),
- email: z.email("Invalid email address").max(255),
- password: z.string().min(8, "Password must be at least 8 characters").max(255),
- roleIds: z.array(entityIdSchema).min(1, "Select at least one role"),
-});
diff --git a/packages/api/src/routers/servers.ts b/packages/api/src/routers/servers.ts
index bed5574..2e96623 100644
--- a/packages/api/src/routers/servers.ts
+++ b/packages/api/src/routers/servers.ts
@@ -2,16 +2,16 @@ import { db } from "@minmon/db";
import { note, server, service } from "@minmon/db";
import { count, desc, eq } from "drizzle-orm";
-import { permissionedProcedure, router } from "../index";
+import { protectedProcedure, router } from "../index";
import { byIdSchema, serverCreateSchema, serverUpdateSchema } from "./schemas";
import { createEntityId, requireServer, stripUndefined } from "./utils";
export const serversRouter = router({
- list: permissionedProcedure("servers.read").query(async () => {
+ list: protectedProcedure.query(async () => {
return db.select().from(server).orderBy(desc(server.createdAt), desc(server.id));
}),
- byId: permissionedProcedure("servers.read").input(byIdSchema).query(async ({ input }) => {
+ byId: protectedProcedure.input(byIdSchema).query(async ({ input }) => {
const item = await requireServer(input.id);
const [serviceCount, noteCount] = await Promise.all([
@@ -28,7 +28,7 @@ export const serversRouter = router({
};
}),
- create: permissionedProcedure("servers.create").input(serverCreateSchema).mutation(async ({ input }) => {
+ create: protectedProcedure.input(serverCreateSchema).mutation(async ({ input }) => {
const [created] = await db
.insert(server)
.values({
@@ -40,7 +40,7 @@ export const serversRouter = router({
return created;
}),
- update: permissionedProcedure("servers.update").input(serverUpdateSchema).mutation(async ({ input }) => {
+ update: protectedProcedure.input(serverUpdateSchema).mutation(async ({ input }) => {
const { id, ...changes } = input;
await requireServer(id);
@@ -53,7 +53,7 @@ export const serversRouter = router({
return updated;
}),
- delete: permissionedProcedure("servers.delete").input(byIdSchema).mutation(async ({ input }) => {
+ delete: protectedProcedure.input(byIdSchema).mutation(async ({ input }) => {
await requireServer(input.id);
await db.delete(server).where(eq(server.id, input.id));
return { id: input.id };
diff --git a/packages/api/src/routers/services.ts b/packages/api/src/routers/services.ts
index 4a4233d..9e84963 100644
--- a/packages/api/src/routers/services.ts
+++ b/packages/api/src/routers/services.ts
@@ -2,7 +2,7 @@ import { db } from "@minmon/db";
import { domain, service } from "@minmon/db";
import { count, desc, eq } from "drizzle-orm";
-import { permissionedProcedure, router } from "../index";
+import { protectedProcedure, router } from "../index";
import {
byIdSchema,
serviceCreateSchema,
@@ -17,7 +17,7 @@ import {
} from "./utils";
export const servicesRouter = router({
- list: permissionedProcedure("services.read").input(serviceListSchema).query(async ({ input }) => {
+ list: protectedProcedure.input(serviceListSchema).query(async ({ input }) => {
if (!input?.serverId) {
return db.select().from(service).orderBy(desc(service.createdAt), desc(service.id));
}
@@ -31,7 +31,7 @@ export const servicesRouter = router({
.orderBy(desc(service.createdAt), desc(service.id));
}),
- byId: permissionedProcedure("services.read").input(byIdSchema).query(async ({ input }) => {
+ byId: protectedProcedure.input(byIdSchema).query(async ({ input }) => {
const item = await requireService(input.id);
const [domainCount] = await db.select({ count: count() }).from(domain).where(eq(domain.serviceId, input.id));
@@ -43,7 +43,7 @@ export const servicesRouter = router({
};
}),
- create: permissionedProcedure("services.create").input(serviceCreateSchema).mutation(async ({ input }) => {
+ create: protectedProcedure.input(serviceCreateSchema).mutation(async ({ input }) => {
await requireServer(input.serverId);
const [created] = await db
@@ -57,7 +57,7 @@ export const servicesRouter = router({
return created;
}),
- update: permissionedProcedure("services.update").input(serviceUpdateSchema).mutation(async ({ input }) => {
+ update: protectedProcedure.input(serviceUpdateSchema).mutation(async ({ input }) => {
const { id, serverId, ...changes } = input;
await requireService(id);
@@ -74,7 +74,7 @@ export const servicesRouter = router({
return updated;
}),
- delete: permissionedProcedure("services.delete").input(byIdSchema).mutation(async ({ input }) => {
+ delete: protectedProcedure.input(byIdSchema).mutation(async ({ input }) => {
await requireService(input.id);
await db.delete(service).where(eq(service.id, input.id));
return { id: input.id };
diff --git a/packages/api/src/routers/users.ts b/packages/api/src/routers/users.ts
deleted file mode 100644
index 668f59f..0000000
--- a/packages/api/src/routers/users.ts
+++ /dev/null
@@ -1,156 +0,0 @@
-import { db, account, role, user, userRole } from "@minmon/db";
-import { hashCredentialPassword } from "@minmon/db/auth-utils";
-import { asc, eq } from "drizzle-orm";
-
-import { permissionedProcedure, protectedProcedure, router } from "../index";
-import { byIdSchema, updateUserRolesSchema, userCreateSchema } from "./schemas";
-import { badRequest, createEntityId, notFound } from "./utils";
-
-async function getUsersWithRoles() {
- const [users, assignments] = await Promise.all([
- db
- .select({
- id: user.id,
- name: user.name,
- email: user.email,
- emailVerified: user.emailVerified,
- image: user.image,
- createdAt: user.createdAt,
- updatedAt: user.updatedAt,
- })
- .from(user)
- .orderBy(asc(user.name), asc(user.email)),
- db
- .select({
- userId: userRole.userId,
- roleId: role.id,
- roleName: role.name,
- })
- .from(userRole)
- .innerJoin(role, eq(role.id, userRole.roleId))
- .orderBy(asc(role.name)),
- ]);
-
- return users.map((item) => ({
- ...item,
- roles: assignments
- .filter((assignment) => assignment.userId === item.id)
- .map((assignment) => ({ id: assignment.roleId, name: assignment.roleName })),
- }));
-}
-
-async function validateRoleIds(roleIds: string[]) {
- const roles = await db.select({ id: role.id }).from(role);
- const validRoleIds = new Set(roles.map((item) => item.id));
- const uniqueRoleIds = Array.from(new Set(roleIds));
-
- const invalidRoleId = uniqueRoleIds.find((roleId) => !validRoleIds.has(roleId));
- if (invalidRoleId) {
- notFound(`Role not found: ${invalidRoleId}`);
- }
-
- return uniqueRoleIds;
-}
-
-export const usersRouter = router({
- me: protectedProcedure.query(({ ctx }) => ({
- user: ctx.session.user,
- roles: ctx.authorization.roles,
- permissions: ctx.authorization.permissions,
- })),
-
- list: permissionedProcedure("users.read").query(async () => getUsersWithRoles()),
-
- roles: permissionedProcedure("users.read").query(async () =>
- db
- .select({
- id: role.id,
- name: role.name,
- description: role.description,
- })
- .from(role)
- .orderBy(asc(role.name)),
- ),
-
- create: permissionedProcedure("users.create")
- .input(userCreateSchema)
- .mutation(async ({ input }) => {
- const [existingUser] = await db.select({ id: user.id }).from(user).where(eq(user.email, input.email)).limit(1);
-
- if (existingUser) {
- badRequest("A user with this email already exists");
- }
-
- const uniqueRoleIds = await validateRoleIds(input.roleIds);
- const userId = createEntityId();
- const now = new Date();
- const passwordHash = await hashCredentialPassword(input.password);
-
- await db.insert(user).values({
- id: userId,
- name: input.name,
- email: input.email,
- emailVerified: true,
- createdAt: now,
- updatedAt: now,
- });
-
- await db.insert(account).values({
- id: createEntityId(),
- accountId: userId,
- providerId: "credential",
- userId,
- password: passwordHash,
- createdAt: now,
- updatedAt: now,
- });
-
- await db.insert(userRole).values(
- uniqueRoleIds.map((roleId) => ({
- userId,
- roleId,
- })),
- );
-
- const users = await getUsersWithRoles();
- return users.find((item) => item.id === userId);
- }),
-
- updateRoles: permissionedProcedure("users.assignRoles")
- .input(updateUserRolesSchema)
- .mutation(async ({ input }) => {
- const [existingUser] = await db.select({ id: user.id }).from(user).where(eq(user.id, input.userId)).limit(1);
-
- if (!existingUser) {
- notFound("User not found");
- }
-
- const uniqueRoleIds = await validateRoleIds(input.roleIds);
-
- await db.delete(userRole).where(eq(userRole.userId, input.userId));
- await db.insert(userRole).values(
- uniqueRoleIds.map((roleId) => ({
- userId: input.userId,
- roleId,
- })),
- );
-
- const updated = await getUsersWithRoles();
- return updated.find((item) => item.id === input.userId);
- }),
-
- delete: permissionedProcedure("users.delete").input(byIdSchema).mutation(async ({ ctx, input }) => {
- if (ctx.session.user.id === input.id) {
- badRequest("You cannot delete your own user account while signed in");
- }
-
- const [existingUser] = await db.select({ id: user.id }).from(user).where(eq(user.id, input.id)).limit(1);
-
- if (!existingUser) {
- notFound("User not found");
- }
-
- await db.delete(user).where(eq(user.id, input.id));
- return { id: input.id };
- }),
-});
diff --git a/packages/db/package.json b/packages/db/package.json
index 6e42483..2b29b35 100644
--- a/packages/db/package.json
+++ b/packages/db/package.json
@@ -15,7 +15,6 @@
"db:studio": "drizzle-kit studio",
"db:migrate": "drizzle-kit migrate",
"db:seed": "bun --env-file=../../apps/server/.env run ./src/seed.ts",
- "db:seed:if-empty": "bun --env-file=../../apps/server/.env run ./src/seed-if-empty.ts",
"db:start": "docker compose up -d",
"db:watch": "docker compose up",
"db:stop": "docker compose stop",
diff --git a/packages/db/src/auth-utils.ts b/packages/db/src/auth-utils.ts
deleted file mode 100644
index 017b916..0000000
--- a/packages/db/src/auth-utils.ts
+++ /dev/null
@@ -1,5 +0,0 @@
-import { hashPassword } from "better-auth/crypto";
-
-export async function hashCredentialPassword(password: string) {
- return hashPassword(password);
-}
diff --git a/packages/db/src/migrations/0001_add_rbac.sql b/packages/db/src/migrations/0001_add_rbac.sql
deleted file mode 100644
index 553996e..0000000
--- a/packages/db/src/migrations/0001_add_rbac.sql
+++ /dev/null
@@ -1,40 +0,0 @@
-CREATE TABLE IF NOT EXISTS "permission" (
- "id" text PRIMARY KEY NOT NULL,
- "name" text NOT NULL,
- "description" text,
- "created_at" timestamp DEFAULT now() NOT NULL,
- "updated_at" timestamp DEFAULT now() NOT NULL,
- CONSTRAINT "permission_name_unique" UNIQUE("name")
-);
---> statement-breakpoint
-CREATE TABLE IF NOT EXISTS "role" (
- "id" text PRIMARY KEY NOT NULL,
- "name" text NOT NULL,
- "description" text,
- "created_at" timestamp DEFAULT now() NOT NULL,
- "updated_at" timestamp DEFAULT now() NOT NULL,
- CONSTRAINT "role_name_unique" UNIQUE("name")
-);
---> statement-breakpoint
-CREATE TABLE IF NOT EXISTS "role_permission" (
- "role_id" text NOT NULL,
- "permission_id" text NOT NULL,
- "created_at" timestamp DEFAULT now() NOT NULL,
- CONSTRAINT "role_permission_role_id_permission_id_pk" PRIMARY KEY("role_id","permission_id")
-);
---> statement-breakpoint
-CREATE TABLE IF NOT EXISTS "user_role" (
- "user_id" text NOT NULL,
- "role_id" text NOT NULL,
- "created_at" timestamp DEFAULT now() NOT NULL,
- CONSTRAINT "user_role_user_id_role_id_pk" PRIMARY KEY("user_id","role_id")
-);
---> statement-breakpoint
-ALTER TABLE "role_permission" ADD CONSTRAINT "role_permission_role_id_role_id_fk" FOREIGN KEY ("role_id") REFERENCES "public"."role"("id") ON DELETE cascade ON UPDATE no action;--> statement-breakpoint
-ALTER TABLE "role_permission" ADD CONSTRAINT "role_permission_permission_id_permission_id_fk" FOREIGN KEY ("permission_id") REFERENCES "public"."permission"("id") ON DELETE cascade ON UPDATE no action;--> statement-breakpoint
-ALTER TABLE "user_role" ADD CONSTRAINT "user_role_user_id_user_id_fk" FOREIGN KEY ("user_id") REFERENCES "public"."user"("id") ON DELETE cascade ON UPDATE no action;--> statement-breakpoint
-ALTER TABLE "user_role" ADD CONSTRAINT "user_role_role_id_role_id_fk" FOREIGN KEY ("role_id") REFERENCES "public"."role"("id") ON DELETE cascade ON UPDATE no action;--> statement-breakpoint
-CREATE INDEX IF NOT EXISTS "role_permission_role_id_idx" ON "role_permission" USING btree ("role_id");--> statement-breakpoint
-CREATE INDEX IF NOT EXISTS "role_permission_permission_id_idx" ON "role_permission" USING btree ("permission_id");--> statement-breakpoint
-CREATE INDEX IF NOT EXISTS "user_role_user_id_idx" ON "user_role" USING btree ("user_id");--> statement-breakpoint
-CREATE INDEX IF NOT EXISTS "user_role_role_id_idx" ON "user_role" USING btree ("role_id");
diff --git a/packages/db/src/migrations/0002_user_lifecycle_permissions.sql b/packages/db/src/migrations/0002_user_lifecycle_permissions.sql
deleted file mode 100644
index 22d0708..0000000
--- a/packages/db/src/migrations/0002_user_lifecycle_permissions.sql
+++ /dev/null
@@ -1,15 +0,0 @@
-INSERT INTO "permission" ("id", "name", "description")
-VALUES
- ('users.create', 'users.create', 'Allows users create'),
- ('users.delete', 'users.delete', 'Allows users delete')
-ON CONFLICT ("id") DO UPDATE SET
- "name" = EXCLUDED."name",
- "description" = EXCLUDED."description",
- "updated_at" = now();
---> statement-breakpoint
-INSERT INTO "role_permission" ("role_id", "permission_id")
-SELECT 'role_admin', "permission"."id"
-FROM "permission"
-WHERE "permission"."id" IN ('users.create', 'users.delete')
- AND EXISTS (SELECT 1 FROM "role" WHERE "role"."id" = 'role_admin')
-ON CONFLICT DO NOTHING;
diff --git a/packages/db/src/migrations/meta/_journal.json b/packages/db/src/migrations/meta/_journal.json
index f7e79eb..f66548c 100644
--- a/packages/db/src/migrations/meta/_journal.json
+++ b/packages/db/src/migrations/meta/_journal.json
@@ -8,20 +8,6 @@
"when": 1777458791180,
"tag": "0000_thick_molecule_man",
"breakpoints": true
- },
- {
- "idx": 1,
- "version": "7",
- "when": 1782694800000,
- "tag": "0001_add_rbac",
- "breakpoints": true
- },
- {
- "idx": 2,
- "version": "7",
- "when": 1782698400000,
- "tag": "0002_user_lifecycle_permissions",
- "breakpoints": true
}
]
-}
+}
\ No newline at end of file
diff --git a/packages/db/src/rbac.ts b/packages/db/src/rbac.ts
deleted file mode 100644
index 212e869..0000000
--- a/packages/db/src/rbac.ts
+++ /dev/null
@@ -1,51 +0,0 @@
-export const permissions = [
- "dashboard.read",
- "servers.read",
- "servers.create",
- "servers.update",
- "servers.delete",
- "services.read",
- "services.create",
- "services.update",
- "services.delete",
- "domains.read",
- "domains.create",
- "domains.update",
- "domains.delete",
- "domains.check",
- "notes.read",
- "notes.create",
- "notes.update",
- "notes.delete",
- "users.read",
- "users.create",
- "users.delete",
- "users.assignRoles",
-] as const;
-
-export type Permission = (typeof permissions)[number];
-
-export const roleDefinitions = [
- {
- id: "role_admin",
- name: "Admin",
- description: "Full access to monitoring data, users, roles, and permissions.",
- permissions: [...permissions],
- },
- {
- id: "role_editor",
- name: "Editor",
- description: "Can create and update monitoring resources, run checks, and read users.",
- permissions: permissions.filter(
- (permission) => !["users.create", "users.delete", "users.assignRoles"].includes(permission),
- ),
- },
- {
- id: "role_viewer",
- name: "Viewer",
- description: "Read-only access to monitoring resources and users.",
- permissions: permissions.filter((permission) => permission.endsWith(".read")),
- },
-] as const;
-
-export type RoleName = (typeof roleDefinitions)[number]["name"];
diff --git a/packages/db/src/schema/auth.ts b/packages/db/src/schema/auth.ts
index cf4ab35..413a524 100644
--- a/packages/db/src/schema/auth.ts
+++ b/packages/db/src/schema/auth.ts
@@ -1,5 +1,5 @@
import { relations } from "drizzle-orm";
-import { pgTable, text, timestamp, boolean, index, primaryKey } from "drizzle-orm/pg-core";
+import { pgTable, text, timestamp, boolean, index } from "drizzle-orm/pg-core";
export const user = pgTable("user", {
id: text("id").primaryKey(),
@@ -73,68 +73,9 @@ export const verification = pgTable(
(table) => [index("verification_identifier_idx").on(table.identifier)],
);
-export const role = pgTable("role", {
- id: text("id").primaryKey(),
- name: text("name").notNull().unique(),
- description: text("description"),
- createdAt: timestamp("created_at").defaultNow().notNull(),
- updatedAt: timestamp("updated_at")
- .defaultNow()
- .$onUpdate(() => /* @__PURE__ */ new Date())
- .notNull(),
-});
-
-export const permission = pgTable("permission", {
- id: text("id").primaryKey(),
- name: text("name").notNull().unique(),
- description: text("description"),
- createdAt: timestamp("created_at").defaultNow().notNull(),
- updatedAt: timestamp("updated_at")
- .defaultNow()
- .$onUpdate(() => /* @__PURE__ */ new Date())
- .notNull(),
-});
-
-export const rolePermission = pgTable(
- "role_permission",
- {
- roleId: text("role_id")
- .notNull()
- .references(() => role.id, { onDelete: "cascade" }),
- permissionId: text("permission_id")
- .notNull()
- .references(() => permission.id, { onDelete: "cascade" }),
- createdAt: timestamp("created_at").defaultNow().notNull(),
- },
- (table) => [
- primaryKey({ columns: [table.roleId, table.permissionId] }),
- index("role_permission_role_id_idx").on(table.roleId),
- index("role_permission_permission_id_idx").on(table.permissionId),
- ],
-);
-
-export const userRole = pgTable(
- "user_role",
- {
- userId: text("user_id")
- .notNull()
- .references(() => user.id, { onDelete: "cascade" }),
- roleId: text("role_id")
- .notNull()
- .references(() => role.id, { onDelete: "cascade" }),
- createdAt: timestamp("created_at").defaultNow().notNull(),
- },
- (table) => [
- primaryKey({ columns: [table.userId, table.roleId] }),
- index("user_role_user_id_idx").on(table.userId),
- index("user_role_role_id_idx").on(table.roleId),
- ],
-);
-
export const userRelations = relations(user, ({ many }) => ({
sessions: many(session),
accounts: many(account),
- roles: many(userRole),
}));
export const sessionRelations = relations(session, ({ one }) => ({
@@ -150,34 +91,3 @@ export const accountRelations = relations(account, ({ one }) => ({
references: [user.id],
}),
}));
-
-export const roleRelations = relations(role, ({ many }) => ({
- permissions: many(rolePermission),
- users: many(userRole),
-}));
-
-export const permissionRelations = relations(permission, ({ many }) => ({
- roles: many(rolePermission),
-}));
-
-export const rolePermissionRelations = relations(rolePermission, ({ one }) => ({
- role: one(role, {
- fields: [rolePermission.roleId],
- references: [role.id],
- }),
- permission: one(permission, {
- fields: [rolePermission.permissionId],
- references: [permission.id],
- }),
-}));
-
-export const userRoleRelations = relations(userRole, ({ one }) => ({
- user: one(user, {
- fields: [userRole.userId],
- references: [user.id],
- }),
- role: one(role, {
- fields: [userRole.roleId],
- references: [role.id],
- }),
-}));
diff --git a/packages/db/src/seed-if-empty.ts b/packages/db/src/seed-if-empty.ts
deleted file mode 100644
index 9a6112b..0000000
--- a/packages/db/src/seed-if-empty.ts
+++ /dev/null
@@ -1,35 +0,0 @@
-import dotenv from "dotenv";
-
-dotenv.config({
- path: new URL("../../../apps/server/.env", import.meta.url).pathname,
-});
-
-import { db } from "./index";
-import { seed, seedRolesAndPermissions } from "./seed";
-
-async function seedIfEmpty() {
- const existingUser = await db.query.user.findFirst({
- columns: {
- id: true,
- },
- });
-
- if (existingUser) {
- await seedRolesAndPermissions(existingUser.id);
- console.info("Database already has users; ensured default roles and permissions.");
- return;
- }
-
- await seed();
- console.info("Seeded auth admin and monitoring dashboard sample data.");
- console.info("Admin login: admin@minmon.local / admin12345");
-}
-
-seedIfEmpty()
- .then(() => {
- process.exit(0);
- })
- .catch((error) => {
- console.error("Failed to initialize seed data.", error);
- process.exit(1);
- });
diff --git a/packages/db/src/seed.ts b/packages/db/src/seed.ts
index e1fea45..d7aa6d8 100644
--- a/packages/db/src/seed.ts
+++ b/packages/db/src/seed.ts
@@ -1,13 +1,12 @@
import dotenv from "dotenv";
-import { roleDefinitions, permissions } from "./rbac";
-import { hashCredentialPassword } from "./auth-utils";
+import { hashPassword } from "better-auth/crypto";
dotenv.config({
path: new URL("../../../apps/server/.env", import.meta.url).pathname,
});
import { db } from "./index";
-import { account, permission, role, rolePermission, user, userRole } from "./schema/auth";
+import { account, user } from "./schema/auth";
import { domain, note, server, service } from "./schema/monitoring";
const now = new Date();
@@ -196,7 +195,7 @@ async function seedAuthAdmin() {
return existingUser.id;
}
- const passwordHash = await hashCredentialPassword(adminUser.password);
+ const passwordHash = await hashPassword(adminUser.password);
await db.insert(user).values({
id: adminUser.id,
@@ -220,46 +219,6 @@ async function seedAuthAdmin() {
return adminUser.id;
}
-export async function seedRolesAndPermissions(adminUserId?: string) {
- await db.insert(permission).values(
- permissions.map((name) => ({
- id: name,
- name,
- description: `Allows ${name.replace(".", " ")}`,
- createdAt: now,
- updatedAt: now,
- })),
- ).onConflictDoNothing();
-
- await db.insert(role).values(
- roleDefinitions.map((item) => ({
- id: item.id,
- name: item.name,
- description: item.description,
- createdAt: now,
- updatedAt: now,
- })),
- ).onConflictDoNothing();
-
- await db.insert(rolePermission).values(
- roleDefinitions.flatMap((item) =>
- item.permissions.map((permissionName) => ({
- roleId: item.id,
- permissionId: permissionName,
- createdAt: now,
- })),
- ),
- ).onConflictDoNothing();
-
- if (adminUserId) {
- await db.insert(userRole).values({
- userId: adminUserId,
- roleId: "role_admin",
- createdAt: now,
- }).onConflictDoNothing();
- }
-}
-
async function seedMonitoringData() {
await db.delete(note);
await db.delete(domain);
@@ -302,21 +261,18 @@ async function seedMonitoringData() {
);
}
-export async function seed() {
- const adminUserId = await seedAuthAdmin();
- await seedRolesAndPermissions(adminUserId);
+async function seed() {
+ await seedAuthAdmin();
await seedMonitoringData();
}
-if (import.meta.main) {
- seed()
- .then(() => {
- console.info("Seeded auth admin and monitoring dashboard sample data.");
- console.info("Admin login: admin@minmon.local / admin12345");
- process.exit(0);
- })
- .catch((error) => {
- console.error("Failed to seed dashboard sample data.", error);
- process.exit(1);
- });
-}
+seed()
+ .then(() => {
+ console.info("Seeded auth admin and monitoring dashboard sample data.");
+ console.info("Admin login: admin@minmon.local / admin12345");
+ process.exit(0);
+ })
+ .catch((error) => {
+ console.error("Failed to seed dashboard sample data.", error);
+ process.exit(1);
+ });