mirror of
https://github.com/temetro/temetro.git
synced 2026-08-21 07:26:45 +00:00
Add TypeScript + Express + Postgres backend with Better Auth
Implements the first temetro backend: an Express 5 API on Postgres via Drizzle ORM, with authentication and multi-tenant clinics powered by Better Auth. - Auth: email/password with required email verification, password reset, rate limiting, CSRF/trusted-origins, secure cookies, session audit hook. - Organizations (clinics) with RBAC (owner/admin/member/viewer) and an extended `patient` permission set; member invitations by email. - Org-scoped patient records mirroring the frontend Patient shape, with CRUD endpoints gated by permission (read/write/delete). - Email helper logs links to the console when SMTP is unset (zero-setup local dev); Dockerfile + docker-compose (db + backend + frontend) with migrations applied on startup and a configurable Postgres host port. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
@@ -0,0 +1,48 @@
|
||||
import "dotenv/config";
|
||||
import { z } from "zod";
|
||||
|
||||
// Defaults keep this module loadable even with no .env present (e.g. when the
|
||||
// Better Auth / drizzle-kit CLIs introspect the config offline). Real values
|
||||
// come from .env at runtime; production misconfiguration is caught below.
|
||||
const schema = z.object({
|
||||
DATABASE_URL: z
|
||||
.string()
|
||||
.min(1)
|
||||
.default("postgres://temetro:temetro@localhost:5432/temetro"),
|
||||
BETTER_AUTH_SECRET: z.string().min(1).default("dev-insecure-secret-change-me"),
|
||||
BETTER_AUTH_URL: z.string().min(1).default("http://localhost:4000"),
|
||||
FRONTEND_URL: z.string().min(1).default("http://localhost:3000"),
|
||||
PORT: z.coerce.number().int().positive().default(4000),
|
||||
NODE_ENV: z
|
||||
.enum(["development", "production", "test"])
|
||||
.default("development"),
|
||||
SMTP_HOST: z.string().optional(),
|
||||
SMTP_PORT: z.coerce.number().int().positive().optional(),
|
||||
SMTP_USER: z.string().optional(),
|
||||
SMTP_PASS: z.string().optional(),
|
||||
SMTP_FROM: z.string().default("temetro <no-reply@temetro.local>"),
|
||||
});
|
||||
|
||||
const parsed = schema.safeParse(process.env);
|
||||
|
||||
if (!parsed.success) {
|
||||
const lines = parsed.error.issues
|
||||
.map((i) => ` - ${i.path.join(".") || "(root)"}: ${i.message}`)
|
||||
.join("\n");
|
||||
console.error(`❌ Invalid environment variables:\n${lines}`);
|
||||
process.exit(1);
|
||||
}
|
||||
|
||||
export const env = parsed.data;
|
||||
|
||||
// Fail fast on dangerous production misconfiguration.
|
||||
if (env.NODE_ENV === "production") {
|
||||
if (env.BETTER_AUTH_SECRET === "dev-insecure-secret-change-me") {
|
||||
console.error(
|
||||
"❌ BETTER_AUTH_SECRET is unset in production. Generate one: openssl rand -base64 32",
|
||||
);
|
||||
process.exit(1);
|
||||
}
|
||||
}
|
||||
|
||||
export const isProd = env.NODE_ENV === "production";
|
||||
Reference in New Issue
Block a user