6 Commits

11 changed files with 1236 additions and 40 deletions

View File

@@ -21,6 +21,12 @@ services:
- "${API_HOST_PORT:-3001}:3000"
environment:
DATABASE_URL: postgresql://nest_user:nest_password@db:5432/nest_db
JWT_SECRET: ${JWT_SECRET}
ADMIN_USERNAME: ${ADMIN_USERNAME}
ADMIN_EMAIL: ${ADMIN_EMAIL}
ADMIN_PASSWORD: ${ADMIN_PASSWORD}
FRONT_ORIGIN: ${FRONT_ORIGIN:-http://localhost:5173}
INTRA_ORIGIN: ${INTRA_ORIGIN:-http://localhost:5174}
depends_on:
db:
condition: service_healthy
@@ -29,7 +35,7 @@ services:
image: git.crowmate.fr/crowmate/nest-front:latest
restart: unless-stopped
ports:
- "5143:80"
- "5173:80"
environment:
API_URL: http://api:3000
depends_on:

View File

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

View File

@@ -10,6 +10,7 @@ RUN npm ci
COPY . .
RUN npx prisma generate
RUN npm run build
RUN npx tsc --module commonjs --target ES2022 --esModuleInterop --skipLibCheck --outDir dist prisma/seed.ts
FROM node:22-slim
@@ -28,4 +29,4 @@ COPY prisma ./prisma
EXPOSE 3000
CMD ["sh", "-c", "npx prisma migrate deploy && node dist/index.js"]
CMD ["sh", "-c", "npx prisma migrate deploy && node dist/seed.js && node dist/index.js"]

View File

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

View File

@@ -13,7 +13,6 @@
},
"dependencies": {
"@prisma/client": "^5.22.0",
"prisma": "^5.22.0",
"bcryptjs": "^2.4.3",
"cors": "^2.8.5",
"dotenv": "^16.4.5",
@@ -30,6 +29,7 @@
"@types/express": "^4.17.25",
"@types/jsonwebtoken": "^9.0.7",
"@types/node": "^22.7.5",
"prisma": "^5.22.0",
"ts-node": "^10.9.2",
"ts-node-dev": "^2.0.0",
"typescript": "^5.6.3"

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>
<form onSubmit={handleSubmit} noValidate className="crt-box" style={{ padding: '2rem' }}>
{/* 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>
<form onSubmit={handleSubmit}>
{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' }}>
[ERROR] {errors.form}

View File

@@ -0,0 +1,897 @@
{
"info": {
"name": "CrowMate API",
"_postman_id": "crowmate-nest-api",
"description": "CrowMate Nest backend API — all endpoints organized by resource. After logging in, the token is automatically saved to the `token` collection variable.",
"schema": "https://schema.getpostman.com/json/collection/v2.1.0/collection.json"
},
"variable": [
{
"key": "token",
"value": "",
"type": "string"
}
],
"item": [
{
"name": "Health",
"item": [
{
"name": "Health Check",
"request": {
"method": "GET",
"header": [],
"url": {
"raw": "{{baseUrl}}/api/health",
"host": ["{{baseUrl}}"],
"path": ["api", "health"]
}
}
}
]
},
{
"name": "Auth",
"item": [
{
"name": "Login",
"event": [
{
"listen": "test",
"script": {
"exec": [
"const res = pm.response.json();",
"if (res.token) {",
" pm.collectionVariables.set('token', res.token);",
" pm.test('Token saved', () => pm.expect(res.token).to.be.a('string'));",
"}"
],
"type": "text/javascript"
}
}
],
"request": {
"method": "POST",
"header": [{ "key": "Content-Type", "value": "application/json" }],
"body": {
"mode": "raw",
"raw": "{\n \"email\": \"{{adminEmail}}\",\n \"password\": \"{{adminPassword}}\"\n}"
},
"url": {
"raw": "{{baseUrl}}/api/auth/login",
"host": ["{{baseUrl}}"],
"path": ["api", "auth", "login"]
}
}
},
{
"name": "Register",
"request": {
"method": "POST",
"header": [{ "key": "Content-Type", "value": "application/json" }],
"body": {
"mode": "raw",
"raw": "{\n \"username\": \"testuser\",\n \"email\": \"testuser@example.com\",\n \"password\": \"password123\"\n}"
},
"url": {
"raw": "{{baseUrl}}/api/auth/register",
"host": ["{{baseUrl}}"],
"path": ["api", "auth", "register"]
}
}
},
{
"name": "Me",
"request": {
"method": "GET",
"header": [{ "key": "Authorization", "value": "Bearer {{token}}" }],
"url": {
"raw": "{{baseUrl}}/api/auth/me",
"host": ["{{baseUrl}}"],
"path": ["api", "auth", "me"]
}
}
}
]
},
{
"name": "Users",
"item": [
{
"name": "List Users (Admin)",
"request": {
"method": "GET",
"header": [{ "key": "Authorization", "value": "Bearer {{token}}" }],
"url": {
"raw": "{{baseUrl}}/api/users",
"host": ["{{baseUrl}}"],
"path": ["api", "users"]
}
}
},
{
"name": "Create Staff Account (Admin)",
"request": {
"method": "POST",
"header": [
{ "key": "Authorization", "value": "Bearer {{token}}" },
{ "key": "Content-Type", "value": "application/json" }
],
"body": {
"mode": "raw",
"raw": "{\n \"username\": \"staffmember\",\n \"email\": \"staff@example.com\",\n \"password\": \"password123\",\n \"role\": \"dev\"\n}"
},
"url": {
"raw": "{{baseUrl}}/api/users",
"host": ["{{baseUrl}}"],
"path": ["api", "users"]
}
}
},
{
"name": "My Profile",
"request": {
"method": "GET",
"header": [{ "key": "Authorization", "value": "Bearer {{token}}" }],
"url": {
"raw": "{{baseUrl}}/api/users/me/profile",
"host": ["{{baseUrl}}"],
"path": ["api", "users", "me", "profile"]
}
}
},
{
"name": "Update My Username",
"request": {
"method": "PATCH",
"header": [
{ "key": "Authorization", "value": "Bearer {{token}}" },
{ "key": "Content-Type", "value": "application/json" }
],
"body": {
"mode": "raw",
"raw": "{\n \"username\": \"newusername\"\n}"
},
"url": {
"raw": "{{baseUrl}}/api/users/me/username",
"host": ["{{baseUrl}}"],
"path": ["api", "users", "me", "username"]
}
}
},
{
"name": "Update My Password",
"request": {
"method": "PATCH",
"header": [
{ "key": "Authorization", "value": "Bearer {{token}}" },
{ "key": "Content-Type", "value": "application/json" }
],
"body": {
"mode": "raw",
"raw": "{\n \"currentPassword\": \"oldpassword\",\n \"newPassword\": \"newpassword123\"\n}"
},
"url": {
"raw": "{{baseUrl}}/api/users/me/password",
"host": ["{{baseUrl}}"],
"path": ["api", "users", "me", "password"]
}
}
},
{
"name": "Get User by ID (Admin)",
"request": {
"method": "GET",
"header": [{ "key": "Authorization", "value": "Bearer {{token}}" }],
"url": {
"raw": "{{baseUrl}}/api/users/:id",
"host": ["{{baseUrl}}"],
"path": ["api", "users", ":id"],
"variable": [{ "key": "id", "value": "USER_ID_HERE" }]
}
}
},
{
"name": "Update User Role (Admin)",
"request": {
"method": "PATCH",
"header": [
{ "key": "Authorization", "value": "Bearer {{token}}" },
{ "key": "Content-Type", "value": "application/json" }
],
"body": {
"mode": "raw",
"raw": "{\n \"role\": \"dev\",\n \"isAdmin\": false\n}"
},
"url": {
"raw": "{{baseUrl}}/api/users/:id",
"host": ["{{baseUrl}}"],
"path": ["api", "users", ":id"],
"variable": [{ "key": "id", "value": "USER_ID_HERE" }]
}
}
},
{
"name": "Ban User (Admin)",
"request": {
"method": "POST",
"header": [{ "key": "Authorization", "value": "Bearer {{token}}" }],
"url": {
"raw": "{{baseUrl}}/api/users/:id/ban",
"host": ["{{baseUrl}}"],
"path": ["api", "users", ":id", "ban"],
"variable": [{ "key": "id", "value": "USER_ID_HERE" }]
}
}
},
{
"name": "Unban User (Admin)",
"request": {
"method": "POST",
"header": [{ "key": "Authorization", "value": "Bearer {{token}}" }],
"url": {
"raw": "{{baseUrl}}/api/users/:id/unban",
"host": ["{{baseUrl}}"],
"path": ["api", "users", ":id", "unban"],
"variable": [{ "key": "id", "value": "USER_ID_HERE" }]
}
}
}
]
},
{
"name": "Forum",
"item": [
{
"name": "Categories",
"item": [
{
"name": "List Categories",
"request": {
"method": "GET",
"header": [],
"url": {
"raw": "{{baseUrl}}/api/forum/categories",
"host": ["{{baseUrl}}"],
"path": ["api", "forum", "categories"]
}
}
},
{
"name": "Create Category (Admin)",
"request": {
"method": "POST",
"header": [
{ "key": "Authorization", "value": "Bearer {{token}}" },
{ "key": "Content-Type", "value": "application/json" }
],
"body": {
"mode": "raw",
"raw": "{\n \"name\": \"General\",\n \"description\": \"General discussion\",\n \"icon\": \"💬\"\n}"
},
"url": {
"raw": "{{baseUrl}}/api/forum/categories",
"host": ["{{baseUrl}}"],
"path": ["api", "forum", "categories"]
}
}
},
{
"name": "Update Category (Admin)",
"request": {
"method": "PATCH",
"header": [
{ "key": "Authorization", "value": "Bearer {{token}}" },
{ "key": "Content-Type", "value": "application/json" }
],
"body": {
"mode": "raw",
"raw": "{\n \"name\": \"General Updated\",\n \"description\": \"Updated description\",\n \"icon\": \"🗨️\"\n}"
},
"url": {
"raw": "{{baseUrl}}/api/forum/categories/:id",
"host": ["{{baseUrl}}"],
"path": ["api", "forum", "categories", ":id"],
"variable": [{ "key": "id", "value": "CATEGORY_ID_HERE" }]
}
}
},
{
"name": "Delete Category (Admin)",
"request": {
"method": "DELETE",
"header": [{ "key": "Authorization", "value": "Bearer {{token}}" }],
"url": {
"raw": "{{baseUrl}}/api/forum/categories/:id",
"host": ["{{baseUrl}}"],
"path": ["api", "forum", "categories", ":id"],
"variable": [{ "key": "id", "value": "CATEGORY_ID_HERE" }]
}
}
}
]
},
{
"name": "Threads",
"item": [
{
"name": "List Threads",
"request": {
"method": "GET",
"header": [],
"url": {
"raw": "{{baseUrl}}/api/forum/threads?categoryId=&page=1&limit=20",
"host": ["{{baseUrl}}"],
"path": ["api", "forum", "threads"],
"query": [
{ "key": "categoryId", "value": "", "description": "Filter by category ID" },
{ "key": "page", "value": "1" },
{ "key": "limit", "value": "20", "description": "Max 50" }
]
}
}
},
{
"name": "Get Thread",
"request": {
"method": "GET",
"header": [],
"url": {
"raw": "{{baseUrl}}/api/forum/threads/:id",
"host": ["{{baseUrl}}"],
"path": ["api", "forum", "threads", ":id"],
"variable": [{ "key": "id", "value": "THREAD_ID_HERE" }]
}
}
},
{
"name": "Create Thread",
"request": {
"method": "POST",
"header": [
{ "key": "Authorization", "value": "Bearer {{token}}" },
{ "key": "Content-Type", "value": "application/json" }
],
"body": {
"mode": "raw",
"raw": "{\n \"title\": \"My thread title\",\n \"content\": \"Thread content here\",\n \"categoryId\": \"CATEGORY_ID_HERE\"\n}"
},
"url": {
"raw": "{{baseUrl}}/api/forum/threads",
"host": ["{{baseUrl}}"],
"path": ["api", "forum", "threads"]
}
}
},
{
"name": "Update Thread",
"request": {
"method": "PATCH",
"header": [
{ "key": "Authorization", "value": "Bearer {{token}}" },
{ "key": "Content-Type", "value": "application/json" }
],
"body": {
"mode": "raw",
"raw": "{\n \"title\": \"Updated title\",\n \"content\": \"Updated content\",\n \"isPinned\": false,\n \"isLocked\": false\n}"
},
"url": {
"raw": "{{baseUrl}}/api/forum/threads/:id",
"host": ["{{baseUrl}}"],
"path": ["api", "forum", "threads", ":id"],
"variable": [{ "key": "id", "value": "THREAD_ID_HERE" }]
}
}
},
{
"name": "Delete Thread (Admin)",
"request": {
"method": "DELETE",
"header": [{ "key": "Authorization", "value": "Bearer {{token}}" }],
"url": {
"raw": "{{baseUrl}}/api/forum/threads/:id",
"host": ["{{baseUrl}}"],
"path": ["api", "forum", "threads", ":id"],
"variable": [{ "key": "id", "value": "THREAD_ID_HERE" }]
}
}
}
]
},
{
"name": "Replies",
"item": [
{
"name": "Post Reply",
"request": {
"method": "POST",
"header": [
{ "key": "Authorization", "value": "Bearer {{token}}" },
{ "key": "Content-Type", "value": "application/json" }
],
"body": {
"mode": "raw",
"raw": "{\n \"content\": \"My reply content\"\n}"
},
"url": {
"raw": "{{baseUrl}}/api/forum/threads/:id/replies",
"host": ["{{baseUrl}}"],
"path": ["api", "forum", "threads", ":id", "replies"],
"variable": [{ "key": "id", "value": "THREAD_ID_HERE" }]
}
}
},
{
"name": "Delete Reply (Admin)",
"request": {
"method": "DELETE",
"header": [{ "key": "Authorization", "value": "Bearer {{token}}" }],
"url": {
"raw": "{{baseUrl}}/api/forum/replies/:id",
"host": ["{{baseUrl}}"],
"path": ["api", "forum", "replies", ":id"],
"variable": [{ "key": "id", "value": "REPLY_ID_HERE" }]
}
}
}
]
}
]
},
{
"name": "Bugs",
"item": [
{
"name": "List Bugs",
"request": {
"method": "GET",
"header": [],
"url": {
"raw": "{{baseUrl}}/api/bugs?status=all&severity=all&page=1&limit=20",
"host": ["{{baseUrl}}"],
"path": ["api", "bugs"],
"query": [
{ "key": "status", "value": "all", "description": "open | in_progress | resolved | closed | all" },
{ "key": "severity", "value": "all", "description": "low | medium | high | critical | all" },
{ "key": "assignedTo", "value": "", "description": "userId | unassigned | all", "disabled": true },
{ "key": "page", "value": "1" },
{ "key": "limit", "value": "20", "description": "Max 50" }
]
}
}
},
{
"name": "Get Bug",
"request": {
"method": "GET",
"header": [],
"url": {
"raw": "{{baseUrl}}/api/bugs/:id",
"host": ["{{baseUrl}}"],
"path": ["api", "bugs", ":id"],
"variable": [{ "key": "id", "value": "BUG_ID_HERE" }]
}
}
},
{
"name": "Submit Bug Report",
"request": {
"method": "POST",
"header": [
{ "key": "Authorization", "value": "Bearer {{token}}" },
{ "key": "Content-Type", "value": "application/json" }
],
"body": {
"mode": "raw",
"raw": "{\n \"title\": \"Bug title\",\n \"description\": \"Detailed description of the bug\",\n \"stepsToReproduce\": \"1. Do this\\n2. Do that\\n3. See error\",\n \"severity\": \"medium\",\n \"gameVersion\": \"1.0.0\",\n \"screenshotUrl\": \"\"\n}"
},
"url": {
"raw": "{{baseUrl}}/api/bugs",
"host": ["{{baseUrl}}"],
"path": ["api", "bugs"]
}
}
},
{
"name": "Update Bug (Staff)",
"request": {
"method": "PATCH",
"header": [
{ "key": "Authorization", "value": "Bearer {{token}}" },
{ "key": "Content-Type", "value": "application/json" }
],
"body": {
"mode": "raw",
"raw": "{\n \"status\": \"in_progress\",\n \"assignedToId\": null,\n \"severity\": \"high\",\n \"title\": \"Updated bug title\"\n}"
},
"url": {
"raw": "{{baseUrl}}/api/bugs/:id",
"host": ["{{baseUrl}}"],
"path": ["api", "bugs", ":id"],
"variable": [{ "key": "id", "value": "BUG_ID_HERE" }]
}
}
},
{
"name": "Toggle Me Too",
"request": {
"method": "POST",
"header": [{ "key": "Authorization", "value": "Bearer {{token}}" }],
"url": {
"raw": "{{baseUrl}}/api/bugs/:id/me-too",
"host": ["{{baseUrl}}"],
"path": ["api", "bugs", ":id", "me-too"],
"variable": [{ "key": "id", "value": "BUG_ID_HERE" }]
}
}
},
{
"name": "Add Comment",
"request": {
"method": "POST",
"header": [
{ "key": "Authorization", "value": "Bearer {{token}}" },
{ "key": "Content-Type", "value": "application/json" }
],
"body": {
"mode": "raw",
"raw": "{\n \"content\": \"This is a public comment on the bug\"\n}"
},
"url": {
"raw": "{{baseUrl}}/api/bugs/:id/comments",
"host": ["{{baseUrl}}"],
"path": ["api", "bugs", ":id", "comments"],
"variable": [{ "key": "id", "value": "BUG_ID_HERE" }]
}
}
},
{
"name": "Delete Comment (Admin)",
"request": {
"method": "DELETE",
"header": [{ "key": "Authorization", "value": "Bearer {{token}}" }],
"url": {
"raw": "{{baseUrl}}/api/bugs/comments/:id",
"host": ["{{baseUrl}}"],
"path": ["api", "bugs", "comments", ":id"],
"variable": [{ "key": "id", "value": "COMMENT_ID_HERE" }]
}
}
},
{
"name": "Add Staff Note (Staff)",
"request": {
"method": "POST",
"header": [
{ "key": "Authorization", "value": "Bearer {{token}}" },
{ "key": "Content-Type", "value": "application/json" }
],
"body": {
"mode": "raw",
"raw": "{\n \"content\": \"Internal staff note — not visible to users\"\n}"
},
"url": {
"raw": "{{baseUrl}}/api/bugs/:id/notes",
"host": ["{{baseUrl}}"],
"path": ["api", "bugs", ":id", "notes"],
"variable": [{ "key": "id", "value": "BUG_ID_HERE" }]
}
}
},
{
"name": "Delete Staff Note (Admin)",
"request": {
"method": "DELETE",
"header": [{ "key": "Authorization", "value": "Bearer {{token}}" }],
"url": {
"raw": "{{baseUrl}}/api/bugs/notes/:id",
"host": ["{{baseUrl}}"],
"path": ["api", "bugs", "notes", ":id"],
"variable": [{ "key": "id", "value": "NOTE_ID_HERE" }]
}
}
}
]
},
{
"name": "Events",
"item": [
{
"name": "List Events",
"request": {
"method": "GET",
"header": [],
"url": {
"raw": "{{baseUrl}}/api/events?public=true&page=1&limit=20",
"host": ["{{baseUrl}}"],
"path": ["api", "events"],
"query": [
{ "key": "public", "value": "true", "description": "Filter to public events only" },
{ "key": "page", "value": "1" },
{ "key": "limit", "value": "20" }
]
}
}
},
{
"name": "Get Event",
"request": {
"method": "GET",
"header": [],
"url": {
"raw": "{{baseUrl}}/api/events/:id",
"host": ["{{baseUrl}}"],
"path": ["api", "events", ":id"],
"variable": [{ "key": "id", "value": "EVENT_ID_HERE" }]
}
}
},
{
"name": "Create Announcement (Staff)",
"request": {
"method": "POST",
"header": [
{ "key": "Authorization", "value": "Bearer {{token}}" },
{ "key": "Content-Type", "value": "application/json" }
],
"body": {
"mode": "raw",
"raw": "{\n \"type\": \"announcement\",\n \"title\": \"Big announcement\",\n \"content\": \"Something important happened!\",\n \"isPublic\": true\n}"
},
"url": {
"raw": "{{baseUrl}}/api/events",
"host": ["{{baseUrl}}"],
"path": ["api", "events"]
}
}
},
{
"name": "Create Poll (Staff)",
"request": {
"method": "POST",
"header": [
{ "key": "Authorization", "value": "Bearer {{token}}" },
{ "key": "Content-Type", "value": "application/json" }
],
"body": {
"mode": "raw",
"raw": "{\n \"type\": \"poll\",\n \"title\": \"Community Poll\",\n \"content\": \"Vote on your favourite feature!\",\n \"isPublic\": true,\n \"poll\": {\n \"question\": \"What should we add next?\",\n \"options\": [\n { \"text\": \"New maps\" },\n { \"text\": \"More characters\" },\n { \"text\": \"Better UI\" }\n ],\n \"isActive\": true,\n \"allowMultipleVotes\": false\n }\n}"
},
"url": {
"raw": "{{baseUrl}}/api/events",
"host": ["{{baseUrl}}"],
"path": ["api", "events"]
}
}
},
{
"name": "Update Event (Staff)",
"request": {
"method": "PATCH",
"header": [
{ "key": "Authorization", "value": "Bearer {{token}}" },
{ "key": "Content-Type", "value": "application/json" }
],
"body": {
"mode": "raw",
"raw": "{\n \"title\": \"Updated title\",\n \"content\": \"Updated content\",\n \"isPublic\": true\n}"
},
"url": {
"raw": "{{baseUrl}}/api/events/:id",
"host": ["{{baseUrl}}"],
"path": ["api", "events", ":id"],
"variable": [{ "key": "id", "value": "EVENT_ID_HERE" }]
}
}
},
{
"name": "Delete Event (Admin)",
"request": {
"method": "DELETE",
"header": [{ "key": "Authorization", "value": "Bearer {{token}}" }],
"url": {
"raw": "{{baseUrl}}/api/events/:id",
"host": ["{{baseUrl}}"],
"path": ["api", "events", ":id"],
"variable": [{ "key": "id", "value": "EVENT_ID_HERE" }]
}
}
},
{
"name": "Vote on Poll",
"request": {
"method": "POST",
"header": [
{ "key": "Authorization", "value": "Bearer {{token}}" },
{ "key": "Content-Type", "value": "application/json" }
],
"body": {
"mode": "raw",
"raw": "{\n \"optionIds\": [\"OPTION_ID_HERE\"]\n}"
},
"url": {
"raw": "{{baseUrl}}/api/events/:id/vote",
"host": ["{{baseUrl}}"],
"path": ["api", "events", ":id", "vote"],
"variable": [{ "key": "id", "value": "EVENT_ID_HERE" }]
}
}
},
{
"name": "Control Poll (Staff)",
"request": {
"method": "PATCH",
"header": [
{ "key": "Authorization", "value": "Bearer {{token}}" },
{ "key": "Content-Type", "value": "application/json" }
],
"body": {
"mode": "raw",
"raw": "{\n \"isActive\": false,\n \"endsAt\": null\n}"
},
"url": {
"raw": "{{baseUrl}}/api/events/:id/poll",
"host": ["{{baseUrl}}"],
"path": ["api", "events", ":id", "poll"],
"variable": [{ "key": "id", "value": "EVENT_ID_HERE" }]
}
}
}
]
},
{
"name": "Feed",
"item": [
{
"name": "List Feed (Staff)",
"request": {
"method": "GET",
"header": [{ "key": "Authorization", "value": "Bearer {{token}}" }],
"url": {
"raw": "{{baseUrl}}/api/feed",
"host": ["{{baseUrl}}"],
"path": ["api", "feed"]
}
}
},
{
"name": "Post to Feed (Staff)",
"request": {
"method": "POST",
"header": [
{ "key": "Authorization", "value": "Bearer {{token}}" },
{ "key": "Content-Type", "value": "application/json" }
],
"body": {
"mode": "raw",
"raw": "{\n \"content\": \"Internal staff feed post content\"\n}"
},
"url": {
"raw": "{{baseUrl}}/api/feed",
"host": ["{{baseUrl}}"],
"path": ["api", "feed"]
}
}
},
{
"name": "Delete Feed Post",
"request": {
"method": "DELETE",
"header": [{ "key": "Authorization", "value": "Bearer {{token}}" }],
"url": {
"raw": "{{baseUrl}}/api/feed/:id",
"host": ["{{baseUrl}}"],
"path": ["api", "feed", ":id"],
"variable": [{ "key": "id", "value": "POST_ID_HERE" }]
}
}
}
]
},
{
"name": "Team",
"item": [
{
"name": "List Team Members",
"request": {
"method": "GET",
"header": [],
"url": {
"raw": "{{baseUrl}}/api/team",
"host": ["{{baseUrl}}"],
"path": ["api", "team"]
}
}
},
{
"name": "Create Team Member (Admin)",
"request": {
"method": "POST",
"header": [
{ "key": "Authorization", "value": "Bearer {{token}}" },
{ "key": "Content-Type", "value": "application/json" }
],
"body": {
"mode": "raw",
"raw": "{\n \"name\": \"John Doe\",\n \"role\": \"Lead Developer\",\n \"bio\": \"A short bio about this team member.\",\n \"avatarInitials\": \"JD\",\n \"social\": {\n \"twitter\": \"johndoe\",\n \"github\": \"johndoe\"\n }\n}"
},
"url": {
"raw": "{{baseUrl}}/api/team",
"host": ["{{baseUrl}}"],
"path": ["api", "team"]
}
}
},
{
"name": "Update Team Member (Admin)",
"request": {
"method": "PATCH",
"header": [
{ "key": "Authorization", "value": "Bearer {{token}}" },
{ "key": "Content-Type", "value": "application/json" }
],
"body": {
"mode": "raw",
"raw": "{\n \"name\": \"John Doe\",\n \"role\": \"Senior Developer\",\n \"bio\": \"Updated bio.\",\n \"avatarInitials\": \"JD\",\n \"social\": {\n \"twitter\": \"johndoe\",\n \"github\": \"johndoe\"\n }\n}"
},
"url": {
"raw": "{{baseUrl}}/api/team/:id",
"host": ["{{baseUrl}}"],
"path": ["api", "team", ":id"],
"variable": [{ "key": "id", "value": "MEMBER_ID_HERE" }]
}
}
},
{
"name": "Delete Team Member (Admin)",
"request": {
"method": "DELETE",
"header": [{ "key": "Authorization", "value": "Bearer {{token}}" }],
"url": {
"raw": "{{baseUrl}}/api/team/:id",
"host": ["{{baseUrl}}"],
"path": ["api", "team", ":id"],
"variable": [{ "key": "id", "value": "MEMBER_ID_HERE" }]
}
}
}
]
},
{
"name": "Settings",
"item": [
{
"name": "Get Settings",
"request": {
"method": "GET",
"header": [],
"url": {
"raw": "{{baseUrl}}/api/settings",
"host": ["{{baseUrl}}"],
"path": ["api", "settings"]
}
}
},
{
"name": "Update Settings (Admin)",
"request": {
"method": "PATCH",
"header": [
{ "key": "Authorization", "value": "Bearer {{token}}" },
{ "key": "Content-Type", "value": "application/json" }
],
"body": {
"mode": "raw",
"raw": "{\n \"forumEnabled\": true,\n \"bugsEnabled\": true\n}"
},
"url": {
"raw": "{{baseUrl}}/api/settings",
"host": ["{{baseUrl}}"],
"path": ["api", "settings"]
}
}
}
]
}
]
}

View File

@@ -0,0 +1,34 @@
{
"id": "crowmate-local-env",
"name": "CrowMate — Local",
"values": [
{
"key": "baseUrl",
"value": "http://localhost:3000",
"type": "default",
"enabled": true
},
{
"key": "adminEmail",
"value": "admin@example.com",
"type": "default",
"enabled": true
},
{
"key": "adminPassword",
"value": "change_me",
"type": "secret",
"enabled": true
},
{
"key": "token",
"value": "",
"type": "secret",
"enabled": true,
"description": "Auto-populated by the Login request"
}
],
"_postman_variable_scope": "environment",
"_postman_exported_at": "2026-03-26T00:00:00.000Z",
"_postman_exported_using": "Claude Code"
}

View File

@@ -0,0 +1,37 @@
{
"id": "crowmate-prod-env",
"name": "CrowMate — Production",
"values": [
{
"key": "baseUrl",
"value": "https://api.your-domain.com",
"type": "default",
"enabled": true,
"description": "Replace with your actual production API URL"
},
{
"key": "adminEmail",
"value": "admin@your-domain.com",
"type": "default",
"enabled": true,
"description": "Replace with your production admin email"
},
{
"key": "adminPassword",
"value": "",
"type": "secret",
"enabled": true,
"description": "Fill in before use — never commit this value"
},
{
"key": "token",
"value": "",
"type": "secret",
"enabled": true,
"description": "Auto-populated by the Login request"
}
],
"_postman_variable_scope": "environment",
"_postman_exported_at": "2026-03-26T00:00:00.000Z",
"_postman_exported_using": "Claude Code"
}