Compare commits

...

8 Commits

12 changed files with 329 additions and 44 deletions

12
.env.example Normal file
View File

@@ -0,0 +1,12 @@
# Host-side port for the API container (container port remains 3000)
API_HOST_PORT=3001
DATABASE_URL="postgresql://user:password@localhost:5432/nest_db"
JWT_SECRET="change_me_to_a_long_random_string"
PORT=3000
ADMIN_USERNAME="admin"
ADMIN_EMAIL="admin@example.com"
ADMIN_PASSWORD="change_me"
FRONT_ORIGIN="http://localhost:5173"
INTRA_ORIGIN="http://localhost:5174"

View File

@@ -49,7 +49,7 @@ npm install
### 3. Set Up Environment ### 3. Set Up Environment
Create `.env` files based on the examples in each project. See the main [README.md](./README.md) for required environment variables. Create a root `.env` file from `.env.example` (`cp .env.example .env`) and adjust values as needed. See the main [README.md](./README.md) for required environment variables.
### 4. Create a Branch ### 4. Create a Branch

View File

@@ -23,9 +23,11 @@ nest-intra/ # Staff-only internal portal (React + Vite)
### 1. Backend Setup ### 1. Backend Setup
```bash ```bash
cd nest-backend # from repository root
cp .env.example .env cp .env.example .env
# Edit .env with your database credentials # Edit .env with your credentials
cd nest-backend
npm install npm install
npm run db:push # Initialize database schema npm run db:push # Initialize database schema
npm run db:seed # Populate with sample data npm run db:seed # Populate with sample data
@@ -35,6 +37,7 @@ npm run dev # Start dev server (http://localhost:3000)
#### Backend Environment Variables #### Backend Environment Variables
```env ```env
API_HOST_PORT=3001
DATABASE_URL="postgresql://user:password@localhost:5432/nest_db" DATABASE_URL="postgresql://user:password@localhost:5432/nest_db"
JWT_SECRET="your-secret-key" JWT_SECRET="your-secret-key"
PORT=3000 PORT=3000

47
docker-compose.prod.yml Normal file
View File

@@ -0,0 +1,47 @@
services:
db:
image: postgres:16-alpine
restart: unless-stopped
environment:
POSTGRES_DB: nest_db
POSTGRES_USER: nest_user
POSTGRES_PASSWORD: nest_password
volumes:
- db_data:/var/lib/postgresql/data
healthcheck:
test: ["CMD-SHELL", "pg_isready -U nest_user -d nest_db"]
interval: 5s
timeout: 5s
retries: 5
api:
image: git.crowmate.fr/crowmate/nest-api:latest
restart: unless-stopped
ports:
- "${API_HOST_PORT:-3001}:3000"
environment:
DATABASE_URL: postgresql://nest_user:nest_password@db:5432/nest_db
depends_on:
db:
condition: service_healthy
front:
image: git.crowmate.fr/crowmate/nest-front:latest
restart: unless-stopped
ports:
- "5143:80"
environment:
API_URL: http://api:3000
depends_on:
- api
intra:
image: git.crowmate.fr/crowmate/nest-intra:latest
restart: unless-stopped
ports:
- "5174:5174"
depends_on:
- api
volumes:
db_data:

View File

@@ -18,9 +18,9 @@ services:
build: ./nest-backend build: ./nest-backend
restart: unless-stopped restart: unless-stopped
ports: ports:
- "3000:3000" - "${API_HOST_PORT:-3001}:3000"
env_file: env_file:
- ./nest-backend/.env - ./.env
environment: environment:
DATABASE_URL: postgresql://nest_user:nest_password@db:5432/nest_db DATABASE_URL: postgresql://nest_user:nest_password@db:5432/nest_db
depends_on: depends_on:

View File

@@ -9,9 +9,6 @@ dist/
.env.local .env.local
.env.*.local .env.*.local
# Prisma
prisma/migrations/
# Logs # Logs
*.log *.log
npm-debug.log* npm-debug.log*

View File

@@ -28,4 +28,4 @@ COPY prisma ./prisma
EXPOSE 3000 EXPOSE 3000
CMD ["node", "dist/index.js"] CMD ["sh", "-c", "npx prisma migrate deploy && node dist/index.js"]

View File

@@ -18,7 +18,9 @@ services:
build: . build: .
restart: unless-stopped restart: unless-stopped
ports: ports:
- "3000:3000" - "${API_HOST_PORT:-3001}:3000"
env_file:
- ../.env
environment: environment:
DATABASE_URL: postgresql://nest_user:nest_password@db:5432/nest_db DATABASE_URL: postgresql://nest_user:nest_password@db:5432/nest_db
JWT_SECRET: ${JWT_SECRET:-change_me_in_production} JWT_SECRET: ${JWT_SECRET:-change_me_in_production}

View File

@@ -864,7 +864,6 @@
"version": "2.3.3", "version": "2.3.3",
"resolved": "https://registry.npmjs.org/fsevents/-/fsevents-2.3.3.tgz", "resolved": "https://registry.npmjs.org/fsevents/-/fsevents-2.3.3.tgz",
"integrity": "sha512-5xoDfX+fL7faATnagmWPpbFtwh/R77WmMMqqHGS65C3vvB0YHrgF+B1YmZ3441tMj5n63k0212XNoJwzlhffQw==", "integrity": "sha512-5xoDfX+fL7faATnagmWPpbFtwh/R77WmMMqqHGS65C3vvB0YHrgF+B1YmZ3441tMj5n63k0212XNoJwzlhffQw==",
"dev": true,
"hasInstallScript": true, "hasInstallScript": true,
"license": "MIT", "license": "MIT",
"optional": true, "optional": true,

View File

@@ -0,0 +1,254 @@
-- CreateEnum
CREATE TYPE "UserRole" AS ENUM ('user', 'dev', 'com');
-- CreateEnum
CREATE TYPE "BugSeverity" AS ENUM ('low', 'medium', 'high', 'critical');
-- CreateEnum
CREATE TYPE "BugStatus" AS ENUM ('open', 'in_progress', 'resolved', 'closed');
-- CreateEnum
CREATE TYPE "EventType" AS ENUM ('announcement', 'update', 'milestone', 'poll');
-- CreateTable
CREATE TABLE "User" (
"id" TEXT NOT NULL,
"username" TEXT NOT NULL,
"email" TEXT NOT NULL,
"password" TEXT NOT NULL,
"role" "UserRole" NOT NULL DEFAULT 'user',
"isAdmin" BOOLEAN NOT NULL DEFAULT false,
"isBanned" BOOLEAN NOT NULL DEFAULT false,
"avatarUrl" TEXT,
"createdAt" TIMESTAMP(3) NOT NULL DEFAULT CURRENT_TIMESTAMP,
CONSTRAINT "User_pkey" PRIMARY KEY ("id")
);
-- CreateTable
CREATE TABLE "ForumCategory" (
"id" TEXT NOT NULL,
"name" TEXT NOT NULL,
"description" TEXT NOT NULL,
"icon" TEXT NOT NULL,
"createdAt" TIMESTAMP(3) NOT NULL DEFAULT CURRENT_TIMESTAMP,
CONSTRAINT "ForumCategory_pkey" PRIMARY KEY ("id")
);
-- CreateTable
CREATE TABLE "ForumThread" (
"id" TEXT NOT NULL,
"title" TEXT NOT NULL,
"content" TEXT NOT NULL,
"isPinned" BOOLEAN NOT NULL DEFAULT false,
"isLocked" BOOLEAN NOT NULL DEFAULT false,
"createdAt" TIMESTAMP(3) NOT NULL DEFAULT CURRENT_TIMESTAMP,
"updatedAt" TIMESTAMP(3) NOT NULL,
"authorId" TEXT NOT NULL,
"categoryId" TEXT NOT NULL,
CONSTRAINT "ForumThread_pkey" PRIMARY KEY ("id")
);
-- CreateTable
CREATE TABLE "ForumReply" (
"id" TEXT NOT NULL,
"content" TEXT NOT NULL,
"createdAt" TIMESTAMP(3) NOT NULL DEFAULT CURRENT_TIMESTAMP,
"authorId" TEXT NOT NULL,
"threadId" TEXT NOT NULL,
CONSTRAINT "ForumReply_pkey" PRIMARY KEY ("id")
);
-- CreateTable
CREATE TABLE "BugReport" (
"id" TEXT NOT NULL,
"uniqueCode" TEXT NOT NULL,
"title" TEXT NOT NULL,
"description" TEXT NOT NULL,
"stepsToReproduce" TEXT NOT NULL,
"severity" "BugSeverity" NOT NULL,
"gameVersion" TEXT NOT NULL,
"screenshotUrl" TEXT,
"status" "BugStatus" NOT NULL DEFAULT 'open',
"createdAt" TIMESTAMP(3) NOT NULL DEFAULT CURRENT_TIMESTAMP,
"updatedAt" TIMESTAMP(3) NOT NULL,
"submittedById" TEXT NOT NULL,
"assignedToId" TEXT,
CONSTRAINT "BugReport_pkey" PRIMARY KEY ("id")
);
-- CreateTable
CREATE TABLE "BugComment" (
"id" TEXT NOT NULL,
"content" TEXT NOT NULL,
"createdAt" TIMESTAMP(3) NOT NULL DEFAULT CURRENT_TIMESTAMP,
"bugReportId" TEXT NOT NULL,
"authorId" TEXT NOT NULL,
CONSTRAINT "BugComment_pkey" PRIMARY KEY ("id")
);
-- CreateTable
CREATE TABLE "BugReportNote" (
"id" TEXT NOT NULL,
"content" TEXT NOT NULL,
"createdAt" TIMESTAMP(3) NOT NULL DEFAULT CURRENT_TIMESTAMP,
"bugReportId" TEXT NOT NULL,
"authorId" TEXT NOT NULL,
CONSTRAINT "BugReportNote_pkey" PRIMARY KEY ("id")
);
-- CreateTable
CREATE TABLE "MeTooBug" (
"userId" TEXT NOT NULL,
"bugReportId" TEXT NOT NULL,
CONSTRAINT "MeTooBug_pkey" PRIMARY KEY ("userId","bugReportId")
);
-- CreateTable
CREATE TABLE "StaffPost" (
"id" TEXT NOT NULL,
"content" TEXT NOT NULL,
"createdAt" TIMESTAMP(3) NOT NULL DEFAULT CURRENT_TIMESTAMP,
"authorId" TEXT NOT NULL,
CONSTRAINT "StaffPost_pkey" PRIMARY KEY ("id")
);
-- CreateTable
CREATE TABLE "EventPost" (
"id" TEXT NOT NULL,
"type" "EventType" NOT NULL,
"title" TEXT NOT NULL,
"content" TEXT NOT NULL,
"isPublic" BOOLEAN NOT NULL DEFAULT true,
"createdAt" TIMESTAMP(3) NOT NULL DEFAULT CURRENT_TIMESTAMP,
"updatedAt" TIMESTAMP(3) NOT NULL,
"authorId" TEXT NOT NULL,
CONSTRAINT "EventPost_pkey" PRIMARY KEY ("id")
);
-- CreateTable
CREATE TABLE "Poll" (
"id" TEXT NOT NULL,
"question" TEXT NOT NULL,
"isActive" BOOLEAN NOT NULL DEFAULT true,
"endsAt" TIMESTAMP(3),
"allowMultipleVotes" BOOLEAN NOT NULL DEFAULT false,
"createdAt" TIMESTAMP(3) NOT NULL DEFAULT CURRENT_TIMESTAMP,
"eventId" TEXT NOT NULL,
CONSTRAINT "Poll_pkey" PRIMARY KEY ("id")
);
-- CreateTable
CREATE TABLE "PollOption" (
"id" TEXT NOT NULL,
"text" TEXT NOT NULL,
"pollId" TEXT NOT NULL,
CONSTRAINT "PollOption_pkey" PRIMARY KEY ("id")
);
-- CreateTable
CREATE TABLE "PollVote" (
"userId" TEXT NOT NULL,
"pollOptionId" TEXT NOT NULL,
CONSTRAINT "PollVote_pkey" PRIMARY KEY ("userId","pollOptionId")
);
-- CreateTable
CREATE TABLE "SiteSettings" (
"id" INTEGER NOT NULL DEFAULT 1,
"forumEnabled" BOOLEAN NOT NULL DEFAULT true,
"bugsEnabled" BOOLEAN NOT NULL DEFAULT true,
CONSTRAINT "SiteSettings_pkey" PRIMARY KEY ("id")
);
-- CreateTable
CREATE TABLE "TeamMember" (
"id" TEXT NOT NULL,
"name" TEXT NOT NULL,
"role" TEXT NOT NULL,
"bio" TEXT,
"avatarInitials" TEXT NOT NULL,
"twitterHandle" TEXT,
"githubHandle" TEXT,
CONSTRAINT "TeamMember_pkey" PRIMARY KEY ("id")
);
-- CreateIndex
CREATE UNIQUE INDEX "User_username_key" ON "User"("username");
-- CreateIndex
CREATE UNIQUE INDEX "User_email_key" ON "User"("email");
-- CreateIndex
CREATE UNIQUE INDEX "BugReport_uniqueCode_key" ON "BugReport"("uniqueCode");
-- CreateIndex
CREATE UNIQUE INDEX "Poll_eventId_key" ON "Poll"("eventId");
-- AddForeignKey
ALTER TABLE "ForumThread" ADD CONSTRAINT "ForumThread_authorId_fkey" FOREIGN KEY ("authorId") REFERENCES "User"("id") ON DELETE RESTRICT ON UPDATE CASCADE;
-- AddForeignKey
ALTER TABLE "ForumThread" ADD CONSTRAINT "ForumThread_categoryId_fkey" FOREIGN KEY ("categoryId") REFERENCES "ForumCategory"("id") ON DELETE RESTRICT ON UPDATE CASCADE;
-- AddForeignKey
ALTER TABLE "ForumReply" ADD CONSTRAINT "ForumReply_authorId_fkey" FOREIGN KEY ("authorId") REFERENCES "User"("id") ON DELETE RESTRICT ON UPDATE CASCADE;
-- AddForeignKey
ALTER TABLE "ForumReply" ADD CONSTRAINT "ForumReply_threadId_fkey" FOREIGN KEY ("threadId") REFERENCES "ForumThread"("id") ON DELETE CASCADE ON UPDATE CASCADE;
-- AddForeignKey
ALTER TABLE "BugReport" ADD CONSTRAINT "BugReport_submittedById_fkey" FOREIGN KEY ("submittedById") REFERENCES "User"("id") ON DELETE RESTRICT ON UPDATE CASCADE;
-- AddForeignKey
ALTER TABLE "BugReport" ADD CONSTRAINT "BugReport_assignedToId_fkey" FOREIGN KEY ("assignedToId") REFERENCES "User"("id") ON DELETE SET NULL ON UPDATE CASCADE;
-- AddForeignKey
ALTER TABLE "BugComment" ADD CONSTRAINT "BugComment_bugReportId_fkey" FOREIGN KEY ("bugReportId") REFERENCES "BugReport"("id") ON DELETE CASCADE ON UPDATE CASCADE;
-- AddForeignKey
ALTER TABLE "BugComment" ADD CONSTRAINT "BugComment_authorId_fkey" FOREIGN KEY ("authorId") REFERENCES "User"("id") ON DELETE RESTRICT ON UPDATE CASCADE;
-- AddForeignKey
ALTER TABLE "BugReportNote" ADD CONSTRAINT "BugReportNote_bugReportId_fkey" FOREIGN KEY ("bugReportId") REFERENCES "BugReport"("id") ON DELETE CASCADE ON UPDATE CASCADE;
-- AddForeignKey
ALTER TABLE "BugReportNote" ADD CONSTRAINT "BugReportNote_authorId_fkey" FOREIGN KEY ("authorId") REFERENCES "User"("id") ON DELETE RESTRICT ON UPDATE CASCADE;
-- AddForeignKey
ALTER TABLE "MeTooBug" ADD CONSTRAINT "MeTooBug_userId_fkey" FOREIGN KEY ("userId") REFERENCES "User"("id") ON DELETE RESTRICT ON UPDATE CASCADE;
-- AddForeignKey
ALTER TABLE "MeTooBug" ADD CONSTRAINT "MeTooBug_bugReportId_fkey" FOREIGN KEY ("bugReportId") REFERENCES "BugReport"("id") ON DELETE CASCADE ON UPDATE CASCADE;
-- AddForeignKey
ALTER TABLE "StaffPost" ADD CONSTRAINT "StaffPost_authorId_fkey" FOREIGN KEY ("authorId") REFERENCES "User"("id") ON DELETE RESTRICT ON UPDATE CASCADE;
-- AddForeignKey
ALTER TABLE "EventPost" ADD CONSTRAINT "EventPost_authorId_fkey" FOREIGN KEY ("authorId") REFERENCES "User"("id") ON DELETE RESTRICT ON UPDATE CASCADE;
-- AddForeignKey
ALTER TABLE "Poll" ADD CONSTRAINT "Poll_eventId_fkey" FOREIGN KEY ("eventId") REFERENCES "EventPost"("id") ON DELETE CASCADE ON UPDATE CASCADE;
-- AddForeignKey
ALTER TABLE "PollOption" ADD CONSTRAINT "PollOption_pollId_fkey" FOREIGN KEY ("pollId") REFERENCES "Poll"("id") ON DELETE CASCADE ON UPDATE CASCADE;
-- AddForeignKey
ALTER TABLE "PollVote" ADD CONSTRAINT "PollVote_userId_fkey" FOREIGN KEY ("userId") REFERENCES "User"("id") ON DELETE RESTRICT ON UPDATE CASCADE;
-- AddForeignKey
ALTER TABLE "PollVote" ADD CONSTRAINT "PollVote_pollOptionId_fkey" FOREIGN KEY ("pollOptionId") REFERENCES "PollOption"("id") ON DELETE CASCADE ON UPDATE CASCADE;

View File

@@ -0,0 +1,3 @@
# Please do not edit this file manually
# It should be added in your version-control system (i.e. Git)
provider = "postgresql"

View File

@@ -58,39 +58,7 @@ export default function LoginPage() {
</div> </div>
</div> </div>
<form onSubmit={handleSubmit} noValidate className="crt-box" style={{ padding: '2rem' }}> <form onSubmit={handleSubmit}>
{/* Demo hint */}
<div style={{ background: 'rgba(217,119,6,0.08)', border: '1px solid rgba(217,119,6,0.2)', padding: '0.75rem', marginBottom: '1.5rem', borderRadius: '6px' }}>
<div style={{ fontFamily: 'var(--font-mono)', color: 'var(--color-amber)', fontSize: '0.7rem', letterSpacing: '0.05em', marginBottom: '0.4rem' }}>
[DEMO] Quick login emails:
</div>
{[
{ label: 'Dev/Admin', email: 'kestrel@crowmate.dev' },
{ label: 'Com Staff', email: 'vesper@crowmate.dev' },
{ label: 'User', email: 'glitch@mail.com' },
].map(({ label, email: e }) => (
<button
key={e}
type="button"
onClick={() => { setEmail(e); setPassword('password'); }}
style={{
background: 'transparent',
border: 'none',
color: 'var(--color-text-muted)',
cursor: 'pointer',
fontFamily: 'var(--font-mono)',
fontSize: '0.65rem',
display: 'block',
textAlign: 'left',
padding: '0.1rem 0',
width: '100%',
}}
>
&gt; {label}: {e}
</button>
))}
</div>
{errors.form && ( {errors.form && (
<div style={{ background: 'rgba(220,38,38,0.1)', border: '1px solid rgba(220,38,38,0.3)', color: 'var(--color-red)', fontFamily: 'var(--font-mono)', fontSize: '0.78rem', padding: '0.75rem', marginBottom: '1.25rem', borderRadius: '6px' }}> <div style={{ background: 'rgba(220,38,38,0.1)', border: '1px solid rgba(220,38,38,0.3)', color: 'var(--color-red)', fontFamily: 'var(--font-mono)', fontSize: '0.78rem', padding: '0.75rem', marginBottom: '1.25rem', borderRadius: '6px' }}>
[ERROR] {errors.form} [ERROR] {errors.form}