🚀 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:
2025-11-19 02:09:04 +00:00
commit 0524656198
58 changed files with 6660 additions and 0 deletions

10
backend/.dockerignore Normal file
View File

@@ -0,0 +1,10 @@
node_modules
dist
.env
.env.*
*.log
.git
.gitignore
README.md
.vscode
coverage

14
backend/Dockerfile Normal file
View 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
View 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
View 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"
}
}

View 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")
}

View 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
View 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 {}

View 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
View 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
View 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
}
}