🚀 Initial commit - PDIMaker v1.0.0
Sistema completo de gestão de PDI com: - Autenticação com email/senha e Google OAuth - Workspaces privados isolados - Sistema de convites com código único - Interface profissional com Next.js 14 - Backend NestJS com PostgreSQL - Docker com Nginx e SSL Desenvolvido por Sergio Correa
This commit is contained in:
10
backend/.dockerignore
Normal file
10
backend/.dockerignore
Normal file
@@ -0,0 +1,10 @@
|
||||
node_modules
|
||||
dist
|
||||
.env
|
||||
.env.*
|
||||
*.log
|
||||
.git
|
||||
.gitignore
|
||||
README.md
|
||||
.vscode
|
||||
coverage
|
||||
14
backend/Dockerfile
Normal file
14
backend/Dockerfile
Normal file
@@ -0,0 +1,14 @@
|
||||
FROM node:20-alpine
|
||||
|
||||
WORKDIR /app
|
||||
|
||||
RUN apk add --no-cache openssl curl
|
||||
|
||||
COPY package*.json ./
|
||||
RUN npm install || true
|
||||
|
||||
COPY . .
|
||||
|
||||
EXPOSE 4000
|
||||
|
||||
CMD ["node", "dist/main.js"]
|
||||
8
backend/nest-cli.json
Normal file
8
backend/nest-cli.json
Normal file
@@ -0,0 +1,8 @@
|
||||
{
|
||||
"$schema": "https://json.schemastore.org/nest-cli",
|
||||
"collection": "@nestjs/schematics",
|
||||
"sourceRoot": "src",
|
||||
"compilerOptions": {
|
||||
"deleteOutDir": true
|
||||
}
|
||||
}
|
||||
27
backend/package.json
Normal file
27
backend/package.json
Normal file
@@ -0,0 +1,27 @@
|
||||
{
|
||||
"name": "pdimaker-api",
|
||||
"version": "1.0.0",
|
||||
"description": "PDIMaker Backend API",
|
||||
"scripts": {
|
||||
"build": "nest build",
|
||||
"start": "nest start",
|
||||
"start:dev": "nest start --watch",
|
||||
"start:prod": "node dist/main",
|
||||
"prisma:generate": "prisma generate",
|
||||
"prisma:migrate": "prisma migrate deploy"
|
||||
},
|
||||
"dependencies": {
|
||||
"@nestjs/common": "^10.3.0",
|
||||
"@nestjs/core": "^10.3.0",
|
||||
"@nestjs/platform-express": "^10.3.0",
|
||||
"@prisma/client": "^5.8.0",
|
||||
"reflect-metadata": "^0.2.1",
|
||||
"rxjs": "^7.8.1"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@nestjs/cli": "^10.3.0",
|
||||
"@types/node": "^20.11.0",
|
||||
"prisma": "^5.8.0",
|
||||
"typescript": "^5.3.3"
|
||||
}
|
||||
}
|
||||
380
backend/prisma/schema.prisma
Normal file
380
backend/prisma/schema.prisma
Normal file
@@ -0,0 +1,380 @@
|
||||
generator client {
|
||||
provider = "prisma-client-js"
|
||||
}
|
||||
|
||||
datasource db {
|
||||
provider = "postgresql"
|
||||
url = env("DATABASE_URL")
|
||||
}
|
||||
|
||||
// ═══════════════════════════════════════
|
||||
// USUÁRIOS E AUTENTICAÇÃO
|
||||
// ═══════════════════════════════════════
|
||||
|
||||
enum Role {
|
||||
EMPLOYEE
|
||||
MANAGER
|
||||
HR_ADMIN
|
||||
}
|
||||
|
||||
model User {
|
||||
id String @id @default(cuid())
|
||||
email String @unique
|
||||
name String
|
||||
avatar String?
|
||||
role Role @default(EMPLOYEE)
|
||||
googleId String? @unique
|
||||
|
||||
createdAt DateTime @default(now())
|
||||
updatedAt DateTime @updatedAt
|
||||
lastLoginAt DateTime?
|
||||
|
||||
workspacesAsEmployee Workspace[] @relation("EmployeeWorkspaces")
|
||||
workspacesAsManager Workspace[] @relation("ManagerWorkspaces")
|
||||
workspacesAsHR Workspace[] @relation("HRWorkspaces")
|
||||
|
||||
journalEntries JournalEntry[]
|
||||
comments Comment[]
|
||||
reactions Reaction[]
|
||||
goals Goal[]
|
||||
assessmentResults AssessmentResult[]
|
||||
oneOnOnesAsEmployee OneOnOne[] @relation("EmployeeOneOnOnes")
|
||||
oneOnOnesAsManager OneOnOne[] @relation("ManagerOneOnOnes")
|
||||
|
||||
@@index([email])
|
||||
@@map("users")
|
||||
}
|
||||
|
||||
// ═══════════════════════════════════════
|
||||
// WORKSPACES (Salas 1:1)
|
||||
// ═══════════════════════════════════════
|
||||
|
||||
enum WorkspaceStatus {
|
||||
ACTIVE
|
||||
ARCHIVED
|
||||
PENDING_INVITE
|
||||
}
|
||||
|
||||
model Workspace {
|
||||
id String @id @default(cuid())
|
||||
slug String @unique
|
||||
|
||||
employeeId String
|
||||
employee User @relation("EmployeeWorkspaces", fields: [employeeId], references: [id])
|
||||
|
||||
managerId String
|
||||
manager User @relation("ManagerWorkspaces", fields: [managerId], references: [id])
|
||||
|
||||
hrId String?
|
||||
hr User? @relation("HRWorkspaces", fields: [hrId], references: [id])
|
||||
|
||||
status WorkspaceStatus @default(ACTIVE)
|
||||
config Json?
|
||||
|
||||
createdAt DateTime @default(now())
|
||||
updatedAt DateTime @updatedAt
|
||||
|
||||
journalEntries JournalEntry[]
|
||||
goals Goal[]
|
||||
oneOnOnes OneOnOne[]
|
||||
assessmentResults AssessmentResult[]
|
||||
|
||||
@@unique([employeeId, managerId])
|
||||
@@index([slug])
|
||||
@@index([employeeId])
|
||||
@@index([managerId])
|
||||
@@map("workspaces")
|
||||
}
|
||||
|
||||
// ═══════════════════════════════════════
|
||||
// DIÁRIO DE ATIVIDADES
|
||||
// ═══════════════════════════════════════
|
||||
|
||||
enum JournalVisibility {
|
||||
PUBLIC
|
||||
PRIVATE
|
||||
SUMMARY
|
||||
}
|
||||
|
||||
model JournalEntry {
|
||||
id String @id @default(cuid())
|
||||
|
||||
workspaceId String
|
||||
workspace Workspace @relation(fields: [workspaceId], references: [id], onDelete: Cascade)
|
||||
|
||||
authorId String
|
||||
author User @relation(fields: [authorId], references: [id])
|
||||
|
||||
date DateTime @default(now())
|
||||
whatIDid String @db.Text
|
||||
whatILearned String? @db.Text
|
||||
difficulties String? @db.Text
|
||||
|
||||
tags String[]
|
||||
attachments Json[]
|
||||
|
||||
needsFeedback Boolean @default(false)
|
||||
markedFor1on1 Boolean @default(false)
|
||||
|
||||
visibility JournalVisibility @default(PUBLIC)
|
||||
moodScore Int?
|
||||
|
||||
createdAt DateTime @default(now())
|
||||
updatedAt DateTime @updatedAt
|
||||
|
||||
comments Comment[]
|
||||
reactions Reaction[]
|
||||
linkedGoals Goal[] @relation("JournalGoalLinks")
|
||||
|
||||
@@index([workspaceId, date])
|
||||
@@index([authorId])
|
||||
@@map("journal_entries")
|
||||
}
|
||||
|
||||
model Comment {
|
||||
id String @id @default(cuid())
|
||||
|
||||
journalEntryId String
|
||||
journalEntry JournalEntry @relation(fields: [journalEntryId], references: [id], onDelete: Cascade)
|
||||
|
||||
authorId String
|
||||
author User @relation(fields: [authorId], references: [id])
|
||||
|
||||
content String @db.Text
|
||||
|
||||
createdAt DateTime @default(now())
|
||||
updatedAt DateTime @updatedAt
|
||||
|
||||
@@index([journalEntryId])
|
||||
@@map("comments")
|
||||
}
|
||||
|
||||
model Reaction {
|
||||
id String @id @default(cuid())
|
||||
|
||||
journalEntryId String
|
||||
journalEntry JournalEntry @relation(fields: [journalEntryId], references: [id], onDelete: Cascade)
|
||||
|
||||
userId String
|
||||
user User @relation(fields: [userId], references: [id])
|
||||
|
||||
emoji String
|
||||
|
||||
createdAt DateTime @default(now())
|
||||
|
||||
@@unique([journalEntryId, userId, emoji])
|
||||
@@index([journalEntryId])
|
||||
@@map("reactions")
|
||||
}
|
||||
|
||||
// ═══════════════════════════════════════
|
||||
// PDI - PLANO DE DESENVOLVIMENTO
|
||||
// ═══════════════════════════════════════
|
||||
|
||||
enum GoalType {
|
||||
TECHNICAL
|
||||
SOFT_SKILL
|
||||
LEADERSHIP
|
||||
CAREER
|
||||
}
|
||||
|
||||
enum GoalStatus {
|
||||
NOT_STARTED
|
||||
IN_PROGRESS
|
||||
COMPLETED
|
||||
CANCELLED
|
||||
}
|
||||
|
||||
model Goal {
|
||||
id String @id @default(cuid())
|
||||
|
||||
workspaceId String
|
||||
workspace Workspace @relation(fields: [workspaceId], references: [id], onDelete: Cascade)
|
||||
|
||||
ownerId String
|
||||
owner User @relation(fields: [ownerId], references: [id])
|
||||
|
||||
title String
|
||||
description String @db.Text
|
||||
type GoalType
|
||||
why String? @db.Text
|
||||
|
||||
status GoalStatus @default(NOT_STARTED)
|
||||
progress Int @default(0)
|
||||
|
||||
startDate DateTime?
|
||||
targetDate DateTime?
|
||||
completedDate DateTime?
|
||||
|
||||
successMetrics String? @db.Text
|
||||
|
||||
createdAt DateTime @default(now())
|
||||
updatedAt DateTime @updatedAt
|
||||
|
||||
actions Action[]
|
||||
linkedJournals JournalEntry[] @relation("JournalGoalLinks")
|
||||
|
||||
@@index([workspaceId])
|
||||
@@index([ownerId])
|
||||
@@map("goals")
|
||||
}
|
||||
|
||||
enum ActionStatus {
|
||||
PENDING
|
||||
IN_PROGRESS
|
||||
DONE
|
||||
BLOCKED
|
||||
}
|
||||
|
||||
model Action {
|
||||
id String @id @default(cuid())
|
||||
|
||||
goalId String
|
||||
goal Goal @relation(fields: [goalId], references: [id], onDelete: Cascade)
|
||||
|
||||
title String
|
||||
description String? @db.Text
|
||||
|
||||
status ActionStatus @default(PENDING)
|
||||
dueDate DateTime?
|
||||
completedAt DateTime?
|
||||
|
||||
assignedTo String?
|
||||
|
||||
createdAt DateTime @default(now())
|
||||
updatedAt DateTime @updatedAt
|
||||
|
||||
@@index([goalId])
|
||||
@@map("actions")
|
||||
}
|
||||
|
||||
// ═══════════════════════════════════════
|
||||
// TESTES E PERFIS
|
||||
// ═══════════════════════════════════════
|
||||
|
||||
enum AssessmentType {
|
||||
PERSONALITY
|
||||
BEHAVIORAL
|
||||
MOTIVATIONAL
|
||||
VOCATIONAL
|
||||
CUSTOM
|
||||
}
|
||||
|
||||
model Assessment {
|
||||
id String @id @default(cuid())
|
||||
|
||||
title String
|
||||
description String @db.Text
|
||||
type AssessmentType
|
||||
|
||||
questions Json
|
||||
scoringLogic Json
|
||||
|
||||
isActive Boolean @default(true)
|
||||
|
||||
createdAt DateTime @default(now())
|
||||
updatedAt DateTime @updatedAt
|
||||
|
||||
results AssessmentResult[]
|
||||
|
||||
@@map("assessments")
|
||||
}
|
||||
|
||||
model AssessmentResult {
|
||||
id String @id @default(cuid())
|
||||
|
||||
assessmentId String
|
||||
assessment Assessment @relation(fields: [assessmentId], references: [id])
|
||||
|
||||
workspaceId String
|
||||
workspace Workspace @relation(fields: [workspaceId], references: [id], onDelete: Cascade)
|
||||
|
||||
userId String
|
||||
user User @relation(fields: [userId], references: [id])
|
||||
|
||||
answers Json
|
||||
result Json
|
||||
|
||||
primaryType String?
|
||||
scores Json?
|
||||
|
||||
completedAt DateTime @default(now())
|
||||
managerNotes String? @db.Text
|
||||
|
||||
@@index([workspaceId])
|
||||
@@index([userId])
|
||||
@@map("assessment_results")
|
||||
}
|
||||
|
||||
// ═══════════════════════════════════════
|
||||
// REUNIÕES 1:1
|
||||
// ═══════════════════════════════════════
|
||||
|
||||
enum OneOnOneStatus {
|
||||
SCHEDULED
|
||||
COMPLETED
|
||||
CANCELLED
|
||||
}
|
||||
|
||||
model OneOnOne {
|
||||
id String @id @default(cuid())
|
||||
|
||||
workspaceId String
|
||||
workspace Workspace @relation(fields: [workspaceId], references: [id], onDelete: Cascade)
|
||||
|
||||
employeeId String
|
||||
employee User @relation("EmployeeOneOnOnes", fields: [employeeId], references: [id])
|
||||
|
||||
managerId String
|
||||
manager User @relation("ManagerOneOnOnes", fields: [managerId], references: [id])
|
||||
|
||||
scheduledFor DateTime
|
||||
status OneOnOneStatus @default(SCHEDULED)
|
||||
|
||||
agenda Json[]
|
||||
notes String? @db.Text
|
||||
decisions Json[]
|
||||
nextSteps Json[]
|
||||
|
||||
completedAt DateTime?
|
||||
|
||||
createdAt DateTime @default(now())
|
||||
updatedAt DateTime @updatedAt
|
||||
|
||||
@@index([workspaceId])
|
||||
@@index([scheduledFor])
|
||||
@@map("one_on_ones")
|
||||
}
|
||||
|
||||
// ═══════════════════════════════════════
|
||||
// CONVITES
|
||||
// ═══════════════════════════════════════
|
||||
|
||||
enum InviteStatus {
|
||||
PENDING
|
||||
ACCEPTED
|
||||
EXPIRED
|
||||
CANCELLED
|
||||
}
|
||||
|
||||
model Invite {
|
||||
id String @id @default(cuid())
|
||||
|
||||
email String
|
||||
role Role
|
||||
|
||||
token String @unique
|
||||
invitedBy String
|
||||
workspaceId String?
|
||||
|
||||
status InviteStatus @default(PENDING)
|
||||
|
||||
expiresAt DateTime
|
||||
acceptedAt DateTime?
|
||||
|
||||
createdAt DateTime @default(now())
|
||||
|
||||
@@index([email])
|
||||
@@index([token])
|
||||
@@map("invites")
|
||||
}
|
||||
21
backend/src/app.controller.ts
Normal file
21
backend/src/app.controller.ts
Normal file
@@ -0,0 +1,21 @@
|
||||
import { Controller, Get } from '@nestjs/common';
|
||||
import { AppService } from './app.service';
|
||||
|
||||
@Controller()
|
||||
export class AppController {
|
||||
constructor(private readonly appService: AppService) {}
|
||||
|
||||
@Get()
|
||||
getHello(): string {
|
||||
return this.appService.getHello();
|
||||
}
|
||||
|
||||
@Get('health')
|
||||
getHealth() {
|
||||
return {
|
||||
status: 'ok',
|
||||
timestamp: new Date().toISOString(),
|
||||
service: 'pdimaker-api'
|
||||
};
|
||||
}
|
||||
}
|
||||
10
backend/src/app.module.ts
Normal file
10
backend/src/app.module.ts
Normal file
@@ -0,0 +1,10 @@
|
||||
import { Module } from '@nestjs/common';
|
||||
import { AppController } from './app.controller';
|
||||
import { AppService } from './app.service';
|
||||
|
||||
@Module({
|
||||
imports: [],
|
||||
controllers: [AppController],
|
||||
providers: [AppService],
|
||||
})
|
||||
export class AppModule {}
|
||||
8
backend/src/app.service.ts
Normal file
8
backend/src/app.service.ts
Normal file
@@ -0,0 +1,8 @@
|
||||
import { Injectable } from '@nestjs/common';
|
||||
|
||||
@Injectable()
|
||||
export class AppService {
|
||||
getHello(): string {
|
||||
return 'PDIMaker API is running! 🚀';
|
||||
}
|
||||
}
|
||||
17
backend/src/main.ts
Normal file
17
backend/src/main.ts
Normal file
@@ -0,0 +1,17 @@
|
||||
import { NestFactory } from '@nestjs/core';
|
||||
import { AppModule } from './app.module';
|
||||
|
||||
async function bootstrap() {
|
||||
const app = await NestFactory.create(AppModule);
|
||||
|
||||
app.enableCors({
|
||||
origin: process.env.FRONTEND_URL || 'http://localhost:3000',
|
||||
credentials: true,
|
||||
});
|
||||
|
||||
const port = process.env.PORT || 4000;
|
||||
await app.listen(port);
|
||||
|
||||
console.log(`🚀 API rodando na porta ${port}`);
|
||||
}
|
||||
bootstrap();
|
||||
21
backend/tsconfig.json
Normal file
21
backend/tsconfig.json
Normal file
@@ -0,0 +1,21 @@
|
||||
{
|
||||
"compilerOptions": {
|
||||
"module": "commonjs",
|
||||
"declaration": true,
|
||||
"removeComments": true,
|
||||
"emitDecoratorMetadata": true,
|
||||
"experimentalDecorators": true,
|
||||
"allowSyntheticDefaultImports": true,
|
||||
"target": "ES2021",
|
||||
"sourceMap": true,
|
||||
"outDir": "./dist",
|
||||
"baseUrl": "./",
|
||||
"incremental": true,
|
||||
"skipLibCheck": true,
|
||||
"strictNullChecks": false,
|
||||
"noImplicitAny": false,
|
||||
"strictBindCallApply": false,
|
||||
"forceConsistentCasingInFileNames": false,
|
||||
"noFallthroughCasesInSwitch": false
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user