Commit 69687704 authored by ThinhNC's avatar ThinhNC

chore: remove old migration guide, add readme

parent 70944fb6
# HƯỚNG DẪN KHỞI TẠO SOURCE BACKEND CRAWL DATA
## 1. Mục tiêu
Khởi tạo source backend sử dụng:
```txt
Node.js
Express.js
TypeScript
PostgreSQL
Prisma ORM
Prisma Migrate
Service Layer Pattern
Firecrawl Integration
JWT Authentication
Role-Based Access Control
File Export JSON/CSV/XLSX/Markdown/ZIP
```
Dự án đi theo hướng **Code First Migration**:
```txt
Code schema trong source
→ tạo migration
→ apply migration xuống PostgreSQL
→ database được quản lý bằng code
```
Không sửa bảng trực tiếp trong database bằng tay, trừ trường hợp debug local.
---
## 2. Nguyên tắc database migration
Dự án sử dụng **Prisma Migrate** để quản lý thay đổi database.
Nguồn chuẩn của database là file:
```txt
prisma/schema.prisma
```
Khi muốn tạo bảng hoặc thay đổi bảng:
```txt
1. Sửa prisma/schema.prisma
2. Chạy lệnh tạo migration
3. Prisma sinh folder migration trong prisma/migrations
4. Apply migration xuống PostgreSQL
5. Generate Prisma Client
6. Code service/repository sử dụng Prisma Client
```
---
## 3. Package cần cài
```bash
pnpm add express cors helmet morgan dotenv jsonwebtoken bcryptjs zod
pnpm add @prisma/client
pnpm add @mendable/firecrawl-js
pnpm add archiver exceljs json2csv turndown
pnpm add bullmq ioredis
```
Dev dependencies:
```bash
pnpm add -D typescript ts-node-dev ts-node tsx
pnpm add -D prisma
pnpm add -D @types/node @types/express @types/cors @types/morgan
pnpm add -D @types/jsonwebtoken @types/bcryptjs @types/archiver @types/turndown
pnpm add -D eslint prettier
```
Tạo file `.npmrc` để đảm bảo Prisma Client hoạt động với pnpm:
```txt
shamefully-hoist=true
```
---
## 4. Cấu trúc thư mục đề xuất
```txt
data-crawler-be/
├── prisma/
│ ├── schema.prisma
│ ├── seed.ts
│ └── migrations/
│ └── ...
├── src/
│ ├── app.ts
│ ├── server.ts
│ │
│ ├── config/
│ │ ├── env.config.ts
│ │ ├── database.config.ts
│ │ ├── jwt.config.ts
│ │ ├── firecrawl.config.ts
│ │ └── storage.config.ts
│ │
│ ├── database/
│ │ └── prisma.client.ts
│ │
│ ├── common/
│ │ ├── constants/
│ │ │ ├── role.constant.ts
│ │ │ ├── job-status.constant.ts
│ │ │ └── export-type.constant.ts
│ │ ├── errors/
│ │ │ ├── app-error.ts
│ │ │ └── error-code.ts
│ │ ├── helpers/
│ │ │ ├── url.helper.ts
│ │ │ ├── file.helper.ts
│ │ │ └── slug.helper.ts
│ │ └── types/
│ │ └── express.d.ts
│ │
│ ├── middlewares/
│ │ ├── auth.middleware.ts
│ │ ├── role.middleware.ts
│ │ ├── validate.middleware.ts
│ │ ├── error.middleware.ts
│ │ └── rate-limit.middleware.ts
│ │
│ ├── modules/
│ │ ├── auth/
│ │ │ ├── auth.controller.ts
│ │ │ ├── auth.service.ts
│ │ │ ├── auth.repository.ts
│ │ │ ├── auth.route.ts
│ │ │ ├── auth.dto.ts
│ │ │ └── auth.validation.ts
│ │ │
│ │ ├── users/
│ │ │ ├── user.controller.ts
│ │ │ ├── user.service.ts
│ │ │ ├── user.repository.ts
│ │ │ ├── user.route.ts
│ │ │ ├── user.dto.ts
│ │ │ └── user.validation.ts
│ │ │
│ │ ├── crawl-jobs/
│ │ │ ├── crawl-job.controller.ts
│ │ │ ├── crawl-job.service.ts
│ │ │ ├── crawl-job.repository.ts
│ │ │ ├── crawl-job.route.ts
│ │ │ ├── crawl-job.dto.ts
│ │ │ └── crawl-job.validation.ts
│ │ │
│ │ ├── crawl-pages/
│ │ │ ├── crawl-page.service.ts
│ │ │ ├── crawl-page.repository.ts
│ │ │ └── crawl-page.dto.ts
│ │ │
│ │ ├── crawl-assets/
│ │ │ ├── crawl-asset.service.ts
│ │ │ ├── crawl-asset.repository.ts
│ │ │ └── crawl-asset.dto.ts
│ │ │
│ │ ├── crawl-exports/
│ │ │ ├── crawl-export.controller.ts
│ │ │ ├── crawl-export.service.ts
│ │ │ ├── crawl-export.repository.ts
│ │ │ ├── crawl-export.route.ts
│ │ │ └── crawl-export.dto.ts
│ │ │
│ │ ├── firecrawl/
│ │ │ ├── firecrawl.service.ts
│ │ │ ├── firecrawl.client.ts
│ │ │ └── firecrawl.dto.ts
│ │ │
│ │ └── exports/
│ │ ├── export.service.ts
│ │ ├── json-export.service.ts
│ │ ├── csv-export.service.ts
│ │ ├── xlsx-export.service.ts
│ │ └── markdown-export.service.ts
│ │
│ ├── queues/
│ │ ├── crawl.queue.ts
│ │ └── crawl.worker.ts
│ │
│ └── routes/
│ └── index.ts
├── storage/
│ └── exports/
│ └── .gitkeep
├── .env
├── .env.example
├── .gitignore
├── .npmrc
├── package.json
├── tsconfig.json
├── docker-compose.yml
└── README.md
```
---
## 5. Cấu hình PostgreSQL trong `.env`
Tạo file `.env`:
```env
NODE_ENV=development
PORT=3000
# Database Configuration
DB_HOST=27.74.255.96
DB_PORT=5430
DB_USER=postgres
DB_PASSWORD="your_password_here"
DB_NAME=datacrawler
JWT_ACCESS_SECRET=change_me_access_secret
JWT_REFRESH_SECRET=change_me_refresh_secret
JWT_ACCESS_EXPIRES_IN=1d
JWT_REFRESH_EXPIRES_IN=7d
FIRECRAWL_API_KEY=your_firecrawl_api_key
FIRECRAWL_BASE_URL=https://api.firecrawl.dev
REDIS_HOST=127.0.0.1
REDIS_PORT=6379
STORAGE_DRIVER=local
STORAGE_EXPORT_DIR=storage/exports
MAX_CRAWL_PAGES=100
MAX_CRAWL_DEPTH=3
```
File `.env.example` cũng cần có đủ key nhưng không chứa secret thật.
---
## 6. Cấu hình Prisma
Khởi tạo Prisma:
```bash
pnpm dlx prisma init
```
> **Lưu ý:** Prisma schema dùng `env("DATABASE_URL")`. Vì `.env` chỉ có `DB_*` vars, script `scripts/prisma-run.js` sẽ tự xây dựng `DATABASE_URL` từ `DB_HOST`, `DB_PORT`, `DB_USER`, `DB_PASSWORD`, `DB_NAME` trước khi chạy lệnh Prisma.
Sau đó chỉnh file:
```txt
prisma/schema.prisma
```
Nội dung schema đề xuất:
```prisma
generator client {
provider = "prisma-client-js"
}
datasource db {
provider = "postgresql"
url = env("DATABASE_URL")
}
enum UserRole {
ADMIN
CRAWLER_USER
VIEWER
}
enum CrawlJobStatus {
PENDING
RUNNING
COMPLETED
PARTIAL_COMPLETED
FAILED
CANCELED
BLOCKED
}
enum CrawlMode {
SCRAPE
CRAWL
SITEMAP
URL_LIST
}
enum CrawlPageStatus {
PENDING
SUCCESS
FAILED
BLOCKED
REQUIRES_LOGIN
CAPTCHA_DETECTED
PAYWALL_DETECTED
TIMEOUT
SKIPPED
}
enum ExportType {
JSON
CSV
XLSX
MARKDOWN
MARKDOWN_ZIP
FULL_ZIP
}
enum ExportStatus {
PENDING
PROCESSING
COMPLETED
FAILED
}
enum AssetType {
IMAGE
LINK
PDF
FILE
VIDEO
OTHER
}
model User {
id String @id @default(uuid()) @db.Uuid
email String @unique
passwordHash String @map("password_hash")
fullName String? @map("full_name")
role UserRole @default(CRAWLER_USER)
isActive Boolean @default(true) @map("is_active")
crawlJobs CrawlJob[]
createdAt DateTime @default(now()) @map("created_at")
updatedAt DateTime @updatedAt @map("updated_at")
@@map("users")
}
model CrawlJob {
id String @id @default(uuid()) @db.Uuid
userId String @map("user_id") @db.Uuid
startUrl String @map("start_url")
domain String?
mode CrawlMode @default(SCRAPE)
status CrawlJobStatus @default(PENDING)
maxPages Int @default(20) @map("max_pages")
maxDepth Int @default(1) @map("max_depth")
totalPages Int @default(0) @map("total_pages")
successPages Int @default(0) @map("success_pages")
failedPages Int @default(0) @map("failed_pages")
errorMessage String? @map("error_message")
startedAt DateTime? @map("started_at")
finishedAt DateTime? @map("finished_at")
createdAt DateTime @default(now()) @map("created_at")
updatedAt DateTime @updatedAt @map("updated_at")
user User @relation(fields: [userId], references: [id])
pages CrawlPage[]
exports CrawlExport[]
assets CrawlAsset[]
@@index([userId])
@@index([status])
@@index([createdAt])
@@map("crawl_jobs")
}
model CrawlPage {
id String @id @default(uuid()) @db.Uuid
jobId String @map("job_id") @db.Uuid
url String
title String?
description String?
markdownContent String? @map("markdown_content")
htmlContentPath String? @map("html_content_path")
status CrawlPageStatus @default(PENDING)
statusCode Int? @map("status_code")
errorMessage String? @map("error_message")
crawledAt DateTime? @map("crawled_at")
createdAt DateTime @default(now()) @map("created_at")
updatedAt DateTime @updatedAt @map("updated_at")
job CrawlJob @relation(fields: [jobId], references: [id], onDelete: Cascade)
assets CrawlAsset[]
@@index([jobId])
@@index([status])
@@map("crawl_pages")
}
model CrawlAsset {
id String @id @default(uuid()) @db.Uuid
jobId String @map("job_id") @db.Uuid
pageId String? @map("page_id") @db.Uuid
assetType AssetType @map("asset_type")
url String
sourceUrl String? @map("source_url")
altText String? @map("alt_text")
mimeType String? @map("mime_type")
orderIndex Int? @map("order_index")
cssSelector String? @map("css_selector")
domPath String? @map("dom_path")
createdAt DateTime @default(now()) @map("created_at")
job CrawlJob @relation(fields: [jobId], references: [id], onDelete: Cascade)
page CrawlPage? @relation(fields: [pageId], references: [id], onDelete: SetNull)
@@index([jobId])
@@index([pageId])
@@index([assetType])
@@map("crawl_assets")
}
model CrawlExport {
id String @id @default(uuid()) @db.Uuid
jobId String @map("job_id") @db.Uuid
exportType ExportType @map("export_type")
status ExportStatus @default(PENDING)
fileName String @map("file_name")
filePath String @map("file_path")
fileSize Int? @map("file_size")
mimeType String? @map("mime_type")
errorMessage String? @map("error_message")
createdAt DateTime @default(now()) @map("created_at")
updatedAt DateTime @updatedAt @map("updated_at")
job CrawlJob @relation(fields: [jobId], references: [id], onDelete: Cascade)
@@index([jobId])
@@index([exportType])
@@map("crawl_exports")
}
```
---
## 7. Lệnh migration
### 7.1. Tạo migration lần đầu
```bash
pnpm db:migrate:init
```
Lệnh này sẽ:
```txt
- Đọc prisma/schema.prisma
- Tạo folder migration trong prisma/migrations
- Apply migration xuống PostgreSQL local
- Generate Prisma Client
```
---
### 7.2. Khi thay đổi database
Ví dụ muốn thêm field `sourceType` vào `crawl_jobs`.
Bước 1: sửa `schema.prisma`
```prisma
model CrawlJob {
id String @id @default(uuid()) @db.Uuid
sourceType String? @map("source_type")
}
```
Bước 2: tạo migration:
```bash
pnpm db:migrate -- --name add_source_type_to_crawl_jobs
```
Bước 3: commit code:
```txt
prisma/schema.prisma
prisma/migrations/xxxx_add_source_type_to_crawl_jobs/migration.sql
```
---
### 7.3. Apply migration trên server/staging/production
Không dùng `migrate dev` trên production.
Dùng:
```bash
pnpm db:migrate:deploy
```
Lệnh này apply các migration đã có trong folder `prisma/migrations`.
---
### 7.4. Generate Prisma Client
```bash
pnpm prisma:generate
```
Thường lệnh này chạy tự động sau migrate dev, nhưng vẫn nên có script riêng.
---
### 7.5. Reset database local
Chỉ dùng local/dev:
```bash
pnpm db:migrate:reset
```
Lệnh này sẽ xóa dữ liệu và chạy lại toàn bộ migration.
Không dùng trên production.
---
## 8. Package scripts trong `package.json`
Thêm các scripts sau:
```json
{
"scripts": {
"dev": "ts-node-dev --respawn --transpile-only src/server.ts",
"worker": "ts-node-dev --respawn --transpile-only src/queues/crawl.worker.ts",
"build": "tsc",
"start": "node dist/server.js",
"prisma:generate": "node scripts/prisma-run.js generate",
"prisma:studio": "node scripts/prisma-run.js studio",
"db:migrate": "node scripts/prisma-run.js migrate dev",
"db:migrate:init": "node scripts/prisma-run.js migrate dev --name init",
"db:migrate:deploy": "node scripts/prisma-run.js migrate deploy",
"db:migrate:reset": "node scripts/prisma-run.js migrate reset",
"db:migrate:status": "node scripts/prisma-run.js migrate status",
"db:seed": "tsx prisma/seed.ts",
"lint": "eslint .",
"format": "prettier --write ."
}
}
```
---
## 9. Prisma Client dùng trong source
Tạo file:
```txt
src/database/prisma.client.ts
```
```ts
import { PrismaClient } from '@prisma/client';
export const prisma = new PrismaClient({
log: ['error', 'warn'],
});
```
Nếu cần log query khi development:
```ts
export const prisma = new PrismaClient({
log: process.env.NODE_ENV === 'development'
? ['query', 'error', 'warn']
: ['error', 'warn'],
});
```
---
## 10. Service Layer Pattern
Source phải đi theo flow:
```txt
Route
→ Controller
→ Service
→ Repository
→ Prisma Client
→ PostgreSQL
```
Không gọi Prisma trực tiếp trong controller.
Không viết business logic trong route.
Không để repository xử lý nghiệp vụ.
---
## 11. Ví dụ module Crawl Job
### 11.1. Route
```ts
import { Router } from 'express';
import { CrawlJobController } from './crawl-job.controller';
import { authMiddleware } from '../../middlewares/auth.middleware';
const router = Router();
const controller = new CrawlJobController();
router.post('/', authMiddleware, controller.create);
router.get('/', authMiddleware, controller.findAll);
router.get('/:id', authMiddleware, controller.findById);
router.post('/:id/cancel', authMiddleware, controller.cancel);
export default router;
```
---
### 11.2. Controller
```ts
import { Request, Response, NextFunction } from 'express';
import { CrawlJobService } from './crawl-job.service';
export class CrawlJobController {
private readonly service = new CrawlJobService();
create = async (req: Request, res: Response, next: NextFunction) => {
try {
const userId = req.user.id;
const result = await this.service.create(userId, req.body);
res.status(201).json({
success: true,
data: result,
});
} catch (error) {
next(error);
}
};
findAll = async (req: Request, res: Response, next: NextFunction) => {
try {
const userId = req.user.id;
const result = await this.service.findAllByUser(userId, req.query);
res.json({
success: true,
data: result,
});
} catch (error) {
next(error);
}
};
findById = async (req: Request, res: Response, next: NextFunction) => {
try {
const userId = req.user.id;
const result = await this.service.findById(userId, req.params.id);
res.json({
success: true,
data: result,
});
} catch (error) {
next(error);
}
};
cancel = async (req: Request, res: Response, next: NextFunction) => {
try {
const userId = req.user.id;
const result = await this.service.cancel(userId, req.params.id);
res.json({
success: true,
data: result,
});
} catch (error) {
next(error);
}
};
}
```
---
### 11.3. Service
```ts
import { CrawlJobRepository } from './crawl-job.repository';
import { AppError } from '../../common/errors/app-error';
import { crawlQueue } from '../../queues/crawl.queue';
export class CrawlJobService {
private readonly repository = new CrawlJobRepository();
async create(userId: string, payload: any) {
// 1. Validate URL nghiệp vụ
// 2. Chặn localhost/private IP
// 3. Tạo job PENDING
// 4. Đẩy job vào queue
const job = await this.repository.create({
userId,
startUrl: payload.startUrl,
mode: payload.mode,
maxPages: payload.maxPages,
maxDepth: payload.maxDepth,
});
await crawlQueue.add('crawl-job', {
jobId: job.id,
});
return job;
}
async findAllByUser(userId: string, query: any) {
return this.repository.findAllByUser(userId, query);
}
async findById(userId: string, jobId: string) {
const job = await this.repository.findById(jobId);
if (!job || job.userId !== userId) {
throw new AppError('Crawl job not found', 404);
}
return job;
}
async cancel(userId: string, jobId: string) {
const job = await this.findById(userId, jobId);
if (job.status === 'COMPLETED') {
throw new AppError('Completed job cannot be canceled', 400);
}
return this.repository.updateStatus(jobId, 'CANCELED');
}
}
```
---
### 11.4. Repository
```ts
import { prisma } from '../../database/prisma.client';
import { CrawlJobStatus } from '@prisma/client';
export class CrawlJobRepository {
create(data: {
userId: string;
startUrl: string;
mode: any;
maxPages?: number;
maxDepth?: number;
}) {
return prisma.crawlJob.create({
data: {
userId: data.userId,
startUrl: data.startUrl,
mode: data.mode,
maxPages: data.maxPages ?? 20,
maxDepth: data.maxDepth ?? 1,
},
});
}
findAllByUser(userId: string, query: any) {
return prisma.crawlJob.findMany({
where: { userId },
orderBy: { createdAt: 'desc' },
include: {
exports: true,
},
});
}
findById(id: string) {
return prisma.crawlJob.findUnique({
where: { id },
include: {
pages: true,
exports: true,
assets: true,
},
});
}
updateStatus(id: string, status: CrawlJobStatus) {
return prisma.crawlJob.update({
where: { id },
data: { status },
});
}
}
```
---
## 12. Seed dữ liệu ban đầu
Tạo file:
```txt
prisma/seed.ts
```
```ts
import { PrismaClient, UserRole } from '@prisma/client';
import bcrypt from 'bcryptjs';
const prisma = new PrismaClient();
async function main() {
const passwordHash = await bcrypt.hash('Admin@123456', 10);
await prisma.user.upsert({
where: { email: 'admin@crawl.local' },
update: {},
create: {
email: 'admin@crawl.local',
passwordHash,
fullName: 'System Admin',
role: UserRole.ADMIN,
isActive: true,
},
});
console.log('Seed completed');
}
main()
.catch((error) => {
console.error(error);
process.exit(1);
})
.finally(async () => {
await prisma.$disconnect();
});
```
Chạy seed:
```bash
pnpm db:seed
```
---
## 13. Quy trình làm việc với migration cho team BE
### Khi tạo task có thay đổi DB
```txt
1. Pull code mới nhất từ develop
2. Sửa prisma/schema.prisma
3. Chạy migration local:
pnpm db:migrate -- --name ten_migration
4. Kiểm tra bảng trong Prisma Studio:
pnpm prisma:studio
5. Cập nhật repository/service tương ứng
6. Commit cả schema.prisma và folder migration mới
7. Tạo pull request
```
---
## 14. Quy tắc đặt tên migration
Tên migration dùng tiếng Anh, snake_case, rõ mục đích.
Ví dụ:
```txt
init
add_crawl_assets_table
add_export_status_to_crawl_exports
add_sensitive_flag_to_crawl_pages
create_workspace_tables
```
Lệnh:
```bash
pnpm db:migrate -- --name add_sensitive_flag_to_crawl_pages
```
---
## 15. Quy tắc không được làm
Không được:
```txt
- Sửa database trực tiếp trên production bằng tay
- Xóa migration cũ đã merge
- Chỉnh sửa file migration cũ nếu đã apply lên server chung
- Dùng prisma db push cho staging/production
- Dùng migrate reset trên staging/production
- Commit file .env
```
Được phép dùng `prisma db push` chỉ khi thử nghiệm local rất nhanh, nhưng dự án chính thức phải dùng migration.
---
## 16. Deploy với migration
Khi deploy server:
```bash
pnpm install
pnpm build
pnpm db:migrate:deploy
pnpm start
```
Hoặc CI/CD:
```txt
1. Checkout source
2. Install dependencies
3. Generate Prisma Client
4. Build TypeScript
5. Run prisma migrate deploy
6. Restart API/Worker
```
Lệnh migrate production:
```bash
pnpm db:migrate:deploy
```
---
## 17. Các API chính cần dựng trước
```txt
POST /api/v1/auth/login
GET /api/v1/auth/me
GET /api/v1/users
GET /api/v1/users/:id
POST /api/v1/users
PUT /api/v1/users/:id
POST /api/v1/crawl-jobs
GET /api/v1/crawl-jobs
GET /api/v1/crawl-jobs/:id
POST /api/v1/crawl-jobs/:id/cancel
GET /api/v1/crawl-jobs/:id/pages
GET /api/v1/crawl-jobs/:id/exports
POST /api/v1/crawl-jobs/:id/exports
GET /api/v1/exports/:exportId/download
GET /api/v1/health
```
---
## 18. Thứ tự khởi tạo source
```txt
1. Init Node.js + TypeScript + Express
2. Cài Prisma
3. Cấu hình PostgreSQL DATABASE_URL
4. Tạo schema.prisma
5. Chạy migration init
6. Tạo Prisma Client
7. Tạo Service Layer Pattern
8. Tạo Auth module
9. Tạo Crawl Job module
10. Tạo Firecrawl module
11. Tạo Export module
12. Tạo Queue worker
13. Test API bằng Postman/Swagger
```
---
## 19. Checklist hoàn thành phần database migration
```txt
[ ] Có prisma/schema.prisma
[ ] Có kết nối PostgreSQL bằng DATABASE_URL
[ ] Có folder prisma/migrations
[ ] Chạy được pnpm db:migrate:init
[ ] Chạy được pnpm db:migrate:status
[ ] Chạy được pnpm prisma:studio
[ ] Có seed admin mặc định
[ ] Repository dùng Prisma Client
[ ] Không gọi Prisma trực tiếp trong controller
[ ] Có quy định migrate deploy cho staging/production
```
---
## 20. Ghi chú cho AI khởi tạo source
Khi khởi tạo source, AI cần ưu tiên dựng backend API trước, chưa cần giao diện.
Yêu cầu bắt buộc:
```txt
- Dùng Express.js + TypeScript
- Dùng PostgreSQL
- Dùng Prisma ORM
- Dùng Prisma Migrate để quản lý database bằng code
- Dùng Service Layer Pattern
- Không viết business logic trong controller
- Không gọi Prisma trực tiếp ngoài repository
- Có schema.prisma đầy đủ
- Có script migration trong package.json
- Có seed admin
- Có validate URL và chặn private IP ở service layer
- Có module Firecrawl riêng
- Có module export file riêng
```
# NexCampus BE - Intern Management System
Backend API service for managing interns, supervisors (leaders), tasks, submissions, evaluations, and notifications.
## Tech Stack
- **Node.js** & **Express** with **TypeScript**
- **Prisma ORM** with **PostgreSQL** (Supabase)
- **JWT Authentication** & **Role-Based Access Control**
- **Zod** for schema validation
---
## Database Design & Models
The system is structured around 12 core models:
1. **Role**: Represents user authorizations (`ADMIN`, `LEADER`, `INTERN`).
2. **User**: Credentials, active status, and role reference.
3. **Application**: Registry/application form details. Spawns a User account upon approval.
4. **InternProfile**: Profile details for approved interns, linking them to their corresponding `User` and supervisor (`User` with role `LEADER`).
5. **NotificationSetting**: Communication channels configuration per user.
6. **Task**: Assignments created by administrators or leaders.
7. **TaskAssignment**: Task assignations linking exactly one task to one intern.
8. **TaskSubmission**: Intern submissions containing code links (PR), video demos, notes, and reviewer comments. Supports multiple attempts.
9. **DailyReport**: Log reports written daily by interns.
10. **WeeklyEvaluation**: Evaluative scoring (communication, attitude, learning, coding, total score) performed weekly by leaders.
11. **Notification**: In-app notifications.
12. **NotificationLog**: Delivery logs tracking web, email, and Discord channel notifications.
---
## Getting Started
### Prerequisites
- Node.js (v18+)
- pnpm (recommended) or npm
### Installation
1. Clone the repository
2. Install dependencies:
```bash
pnpm install
```
3. Set up your environment file:
- Copy `.env.example` to `.env`
- Fill in your connection strings and JWT keys:
```env
DATABASE_URL=your_supabase_pooler_url
DIRECT_URL=your_supabase_direct_url
JWT_ACCESS_SECRET=your_access_secret
JWT_REFRESH_SECRET=your_refresh_secret
```
### Database Initialization
Apply migrations and run the seeding script to populate initial roles and create the default admin account:
```bash
# Validate schema
npx prisma validate
# Run migrations
npx prisma migrate dev --name init_intern_mgmt
# Seed initial data (Roles & Default Admin)
npm run db:seed
```
*Default Admin credentials:*
- **Email**: `admin@nexcampus.local`
- **Password**: `Admin@123456`
### Running the App
Start the development server:
```bash
npm run dev
```
The server runs on http://localhost:3000 by default.
---
## API Endpoints
- **Auth Routing (`/api/v1/auth`)**:
- `POST /login`: User authentication.
- `GET /me`: Get current authenticated user details.
- **Users Routing (`/api/v1/users`)** (Admin only):
- `GET /`: Get all users.
- `GET /:id`: Get user details by ID.
- `POST /`: Create a new user account.
- `PUT /:id`: Update user details.
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment