Initial commit in this repository
This commit is contained in:
3
.gitignore
vendored
Normal file
3
.gitignore
vendored
Normal file
@@ -0,0 +1,3 @@
|
|||||||
|
node_modules
|
||||||
|
.env
|
||||||
|
/src/generated/prisma
|
||||||
8
.prettierrc
Normal file
8
.prettierrc
Normal file
@@ -0,0 +1,8 @@
|
|||||||
|
{
|
||||||
|
"bracketSpacing": true,
|
||||||
|
"singleQuote": true,
|
||||||
|
"trailingComma": "es5",
|
||||||
|
"tabWidth": 2,
|
||||||
|
"semi": true,
|
||||||
|
"useTabs": false
|
||||||
|
}
|
||||||
BIN
docs/design/Final Project ERD.jpeg
Normal file
BIN
docs/design/Final Project ERD.jpeg
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 215 KiB |
BIN
docs/design/Phase_1-Design_Document.docx
Normal file
BIN
docs/design/Phase_1-Design_Document.docx
Normal file
Binary file not shown.
BIN
docs/design/Phase_1-Design_Document.pdf
Normal file
BIN
docs/design/Phase_1-Design_Document.pdf
Normal file
Binary file not shown.
BIN
docs/design/Phase_1-Design_PDF.xps
Normal file
BIN
docs/design/Phase_1-Design_PDF.xps
Normal file
Binary file not shown.
53
docs/design/Roadmap
Normal file
53
docs/design/Roadmap
Normal file
@@ -0,0 +1,53 @@
|
|||||||
|
# IDEA TRACKER API - DEVELOPMENT ROADMAP
|
||||||
|
|
||||||
|
# PHASE 1: MIDDLEWARE & VALIDATION
|
||||||
|
|
||||||
|
[x] Create authMiddleware.js - JWT verification and user authentication
|
||||||
|
[x] Create validateIdeas.js - Request validation for idea endpoints - Validate POST: name (3+ chars), description (required) - Validate PUT: same as POST, but both optional - Validate param id as positive integer
|
||||||
|
[x] Create validateProjects.js - Request validation for project endpoints - Validate POST: name (3+ chars), description (required) - Validate PUT: same as POST, but both optional - Validate param id as positive integer
|
||||||
|
[x] Create validateMaterials.js - Request validation for material endpoints - Validate POST: projectId, name, description, source, author, text (all required) - Validate DELETE: param id as positive integer
|
||||||
|
[x] Update/Review handleValidationErrors.js to ensure proper error responses
|
||||||
|
|
||||||
|
# PHASE 2: REPOSITORY LAYER (DATA ACCESS)
|
||||||
|
|
||||||
|
[x] Create ideasRepo.js - getAll(userId) - fetch all user ideas - getById(ideaId, userId) - fetch single idea with ownership check - create(userId, { name, description }) - update(ideaId, userId, { name, description }) - delete(ideaId, userId) - existsByName(userId, name) - check for duplicates
|
||||||
|
[x] Create projectsRepo.js - getAll(userId) - getById(projectId, userId) - create(userId, { name, description }) - update(projectId, userId, { name, description }) - delete(projectId, userId) - existsByName(userId, name)
|
||||||
|
[x] Create materialsRepo.js - getAll(userId) - all user materials - getByProjectId(projectId, userId) - materials for specific project - getById(materialId, userId) - single material with access check - create(userId, { projectId, name, description, source, author, text }) - delete(materialId, userId) - existsByName(userId, name)
|
||||||
|
|
||||||
|
# PHASE 3: SERVICE LAYER (BUSINESS LOGIC)
|
||||||
|
|
||||||
|
[x] Create ideasService.js - getAllIdeas(userId) - getIdeaById(ideaId, userId) - with 404 handling - createIdea(userId, { name, description }) - with duplicate check (409) - updateIdea(ideaId, userId, { name, description }) - with duplicate check - deleteIdea(ideaId, userId) - with 404 handling
|
||||||
|
[x] Create projectsService.js - Same methods as ideas service
|
||||||
|
[x] Create materialsService.js - getAllMaterials(userId) - getMaterialsByProject(projectId, userId) - createMaterial(userId, { projectId, name, description, source, author, text }) - deleteMaterial(materialId, userId) - Include project existence validation before creating material
|
||||||
|
|
||||||
|
# PHASE 4: CONTROLLER LAYER (REQUEST HANDLERS)
|
||||||
|
|
||||||
|
[x] Create ideasController.js - getAll(req, res) - GET /api/ideas - getById(req, res) - GET /api/ideas/:id - create(req, res) - POST /api/ideas - update(req, res) - PUT /api/ideas/:id - delete(req, res) - DELETE /api/ideas/:id
|
||||||
|
[x] Create projectsController.js - getAll(req, res) - GET /api/projects - getById(req, res) - GET /api/projects/:id - create(req, res) - POST /api/projects - update(req, res) - PUT /api/projects/:id - delete(req, res) - DELETE /api/projects/:id
|
||||||
|
[x] Create materialsController.js - getAll(req, res) - GET /api/materials - getByProject(req, res) - GET /api/materials/:projectId - create(req, res) - POST /api/materials - delete(req, res) - DELETE /api/materials/:id
|
||||||
|
|
||||||
|
# PHASE 5: ROUTES
|
||||||
|
|
||||||
|
[x] Create ideasRoutes.js - Route all endpoints with validation & auth middleware
|
||||||
|
[x] Create projectsRoutes.js - Route all endpoints with validation & auth middleware
|
||||||
|
[x] Create materialsRoutes.js - Route all endpoints with validation & auth middleware
|
||||||
|
|
||||||
|
# PHASE 6: UPDATE SERVER
|
||||||
|
|
||||||
|
[x] Update server.js - Replace /api/posts with /api/ideas - Add /api/projects routes - Add /api/materials routes
|
||||||
|
|
||||||
|
# PHASE 7: TESTING
|
||||||
|
|
||||||
|
[X] Test all endpoints with Swagger
|
||||||
|
[X] Verify JWT auth on protected routes
|
||||||
|
[x] Test 404, 409, 403 error cases
|
||||||
|
[X] Run seed script: npm run seed
|
||||||
|
[X] Verify Prettier formatting: npx prettier --write .
|
||||||
|
|
||||||
|
# PHASE 8: POLISH
|
||||||
|
|
||||||
|
[X] Write swagger for pagination
|
||||||
|
[X] Write code for POST project Files (within projects layers)
|
||||||
|
[X] Write code for DELETE project Files (within projects layers)
|
||||||
|
[X] Edit swagger to show an option for "upload a file to this project"
|
||||||
|
[X] create a path for downloading all files related to a project
|
||||||
BIN
docs/design/Roadmap.docx
Normal file
BIN
docs/design/Roadmap.docx
Normal file
Binary file not shown.
699
docs/openapi.yaml
Normal file
699
docs/openapi.yaml
Normal file
@@ -0,0 +1,699 @@
|
|||||||
|
openapi: 3.1.0
|
||||||
|
info:
|
||||||
|
title: Idea Tracker API
|
||||||
|
version: 1.0.0
|
||||||
|
description: A REST API for managing project ideas, projects, materials, and files
|
||||||
|
servers:
|
||||||
|
- url: http://localhost:8080
|
||||||
|
description: Local development server
|
||||||
|
|
||||||
|
tags:
|
||||||
|
- name: Authentication
|
||||||
|
description: User authentication endpoints
|
||||||
|
- name: Ideas
|
||||||
|
description: Idea management endpoints
|
||||||
|
- name: Projects
|
||||||
|
description: Project management endpoints
|
||||||
|
- name: Materials
|
||||||
|
description: Project materials and resources
|
||||||
|
|
||||||
|
paths:
|
||||||
|
/api/auth/signup:
|
||||||
|
post:
|
||||||
|
summary: Create a new user account
|
||||||
|
tags:
|
||||||
|
- Authentication
|
||||||
|
requestBody:
|
||||||
|
required: true
|
||||||
|
content:
|
||||||
|
application/json:
|
||||||
|
schema:
|
||||||
|
$ref: '#/components/schemas/SignupRequest'
|
||||||
|
responses:
|
||||||
|
'201':
|
||||||
|
description: User created successfully
|
||||||
|
content:
|
||||||
|
application/json:
|
||||||
|
schema:
|
||||||
|
$ref: '#/components/schemas/UserResponse'
|
||||||
|
'400':
|
||||||
|
description: Invalid input or validation error
|
||||||
|
'409':
|
||||||
|
description: Username already exists
|
||||||
|
|
||||||
|
/api/auth/login:
|
||||||
|
post:
|
||||||
|
summary: Login and receive JWT token
|
||||||
|
tags:
|
||||||
|
- Authentication
|
||||||
|
requestBody:
|
||||||
|
required: true
|
||||||
|
content:
|
||||||
|
application/json:
|
||||||
|
schema:
|
||||||
|
$ref: '#/components/schemas/LoginRequest'
|
||||||
|
responses:
|
||||||
|
'200':
|
||||||
|
description: Login successful
|
||||||
|
content:
|
||||||
|
application/json:
|
||||||
|
schema:
|
||||||
|
$ref: '#/components/schemas/LoginResponse'
|
||||||
|
'401':
|
||||||
|
description: Invalid credentials
|
||||||
|
|
||||||
|
/api/ideas:
|
||||||
|
get:
|
||||||
|
summary: Get all ideas for the authenticated user
|
||||||
|
tags:
|
||||||
|
- Ideas
|
||||||
|
security:
|
||||||
|
- BearerAuth: []
|
||||||
|
responses:
|
||||||
|
'200':
|
||||||
|
description: OK
|
||||||
|
content:
|
||||||
|
application/json:
|
||||||
|
schema:
|
||||||
|
type: array
|
||||||
|
items:
|
||||||
|
$ref: '#/components/schemas/Idea'
|
||||||
|
'401':
|
||||||
|
description: Unauthorized - Missing or invalid token
|
||||||
|
post:
|
||||||
|
summary: Create a new idea
|
||||||
|
tags:
|
||||||
|
- Ideas
|
||||||
|
security:
|
||||||
|
- BearerAuth: []
|
||||||
|
requestBody:
|
||||||
|
required: true
|
||||||
|
content:
|
||||||
|
application/json:
|
||||||
|
schema:
|
||||||
|
$ref: '#/components/schemas/IdeaRequest'
|
||||||
|
responses:
|
||||||
|
'201':
|
||||||
|
description: Idea created successfully
|
||||||
|
content:
|
||||||
|
application/json:
|
||||||
|
schema:
|
||||||
|
$ref: '#/components/schemas/Idea'
|
||||||
|
'400':
|
||||||
|
description: Bad Request - Validation error
|
||||||
|
'401':
|
||||||
|
description: Unauthorized - Missing or invalid token
|
||||||
|
'409':
|
||||||
|
description: Conflict - Idea with that name already exists
|
||||||
|
|
||||||
|
/api/ideas/{id}:
|
||||||
|
put:
|
||||||
|
summary: Update an idea
|
||||||
|
tags:
|
||||||
|
- Ideas
|
||||||
|
security:
|
||||||
|
- BearerAuth: []
|
||||||
|
parameters:
|
||||||
|
- name: id
|
||||||
|
in: path
|
||||||
|
required: true
|
||||||
|
schema:
|
||||||
|
type: integer
|
||||||
|
requestBody:
|
||||||
|
required: true
|
||||||
|
content:
|
||||||
|
application/json:
|
||||||
|
schema:
|
||||||
|
$ref: '#/components/schemas/IdeaUpdateRequest'
|
||||||
|
responses:
|
||||||
|
'200':
|
||||||
|
description: Idea updated successfully
|
||||||
|
content:
|
||||||
|
application/json:
|
||||||
|
schema:
|
||||||
|
$ref: '#/components/schemas/Idea'
|
||||||
|
'400':
|
||||||
|
description: Bad Request - Id not a positive integer, or no fields provided
|
||||||
|
'401':
|
||||||
|
description: Unauthorized - Missing or invalid token
|
||||||
|
'404':
|
||||||
|
description: Not Found - No idea with that id belongs to this user
|
||||||
|
'409':
|
||||||
|
description: Conflict - Idea with that name already exists
|
||||||
|
delete:
|
||||||
|
summary: Delete an idea
|
||||||
|
tags:
|
||||||
|
- Ideas
|
||||||
|
security:
|
||||||
|
- BearerAuth: []
|
||||||
|
parameters:
|
||||||
|
- name: id
|
||||||
|
in: path
|
||||||
|
required: true
|
||||||
|
schema:
|
||||||
|
type: integer
|
||||||
|
responses:
|
||||||
|
'204':
|
||||||
|
description: Idea deleted successfully
|
||||||
|
'400':
|
||||||
|
description: Bad Request - Id not a positive integer
|
||||||
|
'401':
|
||||||
|
description: Unauthorized - Missing or invalid token
|
||||||
|
'404':
|
||||||
|
description: Not Found - No idea with that id belongs to this user
|
||||||
|
|
||||||
|
/api/projects:
|
||||||
|
get:
|
||||||
|
summary: Get all projects for the authenticated user
|
||||||
|
tags:
|
||||||
|
- Projects
|
||||||
|
security:
|
||||||
|
- BearerAuth: []
|
||||||
|
responses:
|
||||||
|
'200':
|
||||||
|
description: OK
|
||||||
|
content:
|
||||||
|
application/json:
|
||||||
|
schema:
|
||||||
|
type: array
|
||||||
|
items:
|
||||||
|
$ref: '#/components/schemas/Project'
|
||||||
|
'401':
|
||||||
|
description: Unauthorized - Missing or invalid token
|
||||||
|
post:
|
||||||
|
summary: Create a new project
|
||||||
|
tags:
|
||||||
|
- Projects
|
||||||
|
security:
|
||||||
|
- BearerAuth: []
|
||||||
|
requestBody:
|
||||||
|
required: true
|
||||||
|
content:
|
||||||
|
application/json:
|
||||||
|
schema:
|
||||||
|
$ref: '#/components/schemas/ProjectRequest'
|
||||||
|
responses:
|
||||||
|
'201':
|
||||||
|
description: Project created successfully
|
||||||
|
content:
|
||||||
|
application/json:
|
||||||
|
schema:
|
||||||
|
$ref: '#/components/schemas/Project'
|
||||||
|
'400':
|
||||||
|
description: Bad Request - Validation error
|
||||||
|
'401':
|
||||||
|
description: Unauthorized - Missing or invalid token
|
||||||
|
'409':
|
||||||
|
description: Conflict - Project with that name already exists
|
||||||
|
|
||||||
|
/api/projects/{id}:
|
||||||
|
get:
|
||||||
|
summary: Get a project by ID
|
||||||
|
tags:
|
||||||
|
- Projects
|
||||||
|
security:
|
||||||
|
- BearerAuth: []
|
||||||
|
parameters:
|
||||||
|
- name: id
|
||||||
|
in: path
|
||||||
|
required: true
|
||||||
|
schema:
|
||||||
|
type: integer
|
||||||
|
responses:
|
||||||
|
'200':
|
||||||
|
description: OK
|
||||||
|
content:
|
||||||
|
application/json:
|
||||||
|
schema:
|
||||||
|
$ref: '#/components/schemas/Project'
|
||||||
|
'400':
|
||||||
|
description: Bad Request - Id not a positive integer
|
||||||
|
'401':
|
||||||
|
description: Unauthorized - Missing or invalid token
|
||||||
|
'404':
|
||||||
|
description: Not Found - No project with that id belongs to this user
|
||||||
|
put:
|
||||||
|
summary: Update a project
|
||||||
|
tags:
|
||||||
|
- Projects
|
||||||
|
security:
|
||||||
|
- BearerAuth: []
|
||||||
|
parameters:
|
||||||
|
- name: id
|
||||||
|
in: path
|
||||||
|
required: true
|
||||||
|
schema:
|
||||||
|
type: integer
|
||||||
|
requestBody:
|
||||||
|
required: true
|
||||||
|
content:
|
||||||
|
application/json:
|
||||||
|
schema:
|
||||||
|
$ref: '#/components/schemas/ProjectUpdateRequest'
|
||||||
|
responses:
|
||||||
|
'200':
|
||||||
|
description: Project updated successfully
|
||||||
|
content:
|
||||||
|
application/json:
|
||||||
|
schema:
|
||||||
|
$ref: '#/components/schemas/Project'
|
||||||
|
'400':
|
||||||
|
description: Bad Request - Id not a positive integer, or no fields provided
|
||||||
|
'401':
|
||||||
|
description: Unauthorized - Missing or invalid token
|
||||||
|
'404':
|
||||||
|
description: Not Found - No project with that id belongs to this user
|
||||||
|
'409':
|
||||||
|
description: Conflict - Project with that name already exists
|
||||||
|
delete:
|
||||||
|
summary: Delete a project
|
||||||
|
tags:
|
||||||
|
- Projects
|
||||||
|
security:
|
||||||
|
- BearerAuth: []
|
||||||
|
parameters:
|
||||||
|
- name: id
|
||||||
|
in: path
|
||||||
|
required: true
|
||||||
|
schema:
|
||||||
|
type: integer
|
||||||
|
responses:
|
||||||
|
'204':
|
||||||
|
description: Project deleted successfully
|
||||||
|
'400':
|
||||||
|
description: Bad Request - Id not a positive integer
|
||||||
|
'401':
|
||||||
|
description: Unauthorized - Missing or invalid token
|
||||||
|
'404':
|
||||||
|
description: Not Found - No project with that id belongs to this user
|
||||||
|
|
||||||
|
/api/projects/{id}/files:
|
||||||
|
get:
|
||||||
|
summary: List all files related to a project
|
||||||
|
tags:
|
||||||
|
- Projects
|
||||||
|
security:
|
||||||
|
- BearerAuth: []
|
||||||
|
parameters:
|
||||||
|
- name: id
|
||||||
|
in: path
|
||||||
|
required: true
|
||||||
|
schema:
|
||||||
|
type: integer
|
||||||
|
responses:
|
||||||
|
'200':
|
||||||
|
description: OK
|
||||||
|
content:
|
||||||
|
application/json:
|
||||||
|
schema:
|
||||||
|
type: array
|
||||||
|
items:
|
||||||
|
$ref: '#/components/schemas/File'
|
||||||
|
'400':
|
||||||
|
description: Bad Request - Id not a positive integer
|
||||||
|
'401':
|
||||||
|
description: Unauthorized - Missing or invalid token
|
||||||
|
'404':
|
||||||
|
description: Not Found - No project with that id belongs to this user
|
||||||
|
post:
|
||||||
|
summary: Upload a file (Max 10MB)
|
||||||
|
tags: [Projects]
|
||||||
|
security: [{ BearerAuth: [] }]
|
||||||
|
parameters:
|
||||||
|
- $ref: '#/components/parameters/IdParam'
|
||||||
|
requestBody:
|
||||||
|
content:
|
||||||
|
multipart/form-data:
|
||||||
|
schema:
|
||||||
|
type: object
|
||||||
|
properties:
|
||||||
|
file:
|
||||||
|
type: string
|
||||||
|
format: binary
|
||||||
|
responses:
|
||||||
|
'201':
|
||||||
|
description: File uploaded
|
||||||
|
'400':
|
||||||
|
description: Bad Request - Id not a positive integer
|
||||||
|
'401':
|
||||||
|
description: Unauthorized - Missing or invalid token
|
||||||
|
'404':
|
||||||
|
description: Not Found - No project with that id belongs to this user
|
||||||
|
'413':
|
||||||
|
description: Payload Too Large - File size exceeds 10MB limit
|
||||||
|
|
||||||
|
/api/projects/files/{fileId}:
|
||||||
|
delete:
|
||||||
|
summary: Delete a specific project file
|
||||||
|
tags: [Projects]
|
||||||
|
security: [{ BearerAuth: [] }]
|
||||||
|
parameters:
|
||||||
|
- name: fileId
|
||||||
|
in: path
|
||||||
|
required: true
|
||||||
|
schema:
|
||||||
|
type: integer
|
||||||
|
responses:
|
||||||
|
'204':
|
||||||
|
description: File deleted
|
||||||
|
'404':
|
||||||
|
description: Not Found or Unauthorized
|
||||||
|
|
||||||
|
/api/projects/{id}/files/download:
|
||||||
|
get:
|
||||||
|
summary: Download all project files as a ZIP
|
||||||
|
tags: [Projects]
|
||||||
|
security: [{ BearerAuth: [] }]
|
||||||
|
parameters:
|
||||||
|
- $ref: '#/components/parameters/IdParam'
|
||||||
|
responses:
|
||||||
|
'200':
|
||||||
|
description: A ZIP file containing all project documents
|
||||||
|
content:
|
||||||
|
application/zip:
|
||||||
|
schema:
|
||||||
|
type: string
|
||||||
|
format: binary
|
||||||
|
|
||||||
|
/api/materials:
|
||||||
|
get:
|
||||||
|
summary: Get all materials for the authenticated user
|
||||||
|
tags:
|
||||||
|
- Materials
|
||||||
|
security:
|
||||||
|
- BearerAuth: []
|
||||||
|
responses:
|
||||||
|
'200':
|
||||||
|
description: OK
|
||||||
|
content:
|
||||||
|
application/json:
|
||||||
|
schema:
|
||||||
|
type: array
|
||||||
|
items:
|
||||||
|
$ref: '#/components/schemas/Material'
|
||||||
|
'401':
|
||||||
|
description: Unauthorized - Missing or invalid token
|
||||||
|
post:
|
||||||
|
summary: Create a new material
|
||||||
|
tags:
|
||||||
|
- Materials
|
||||||
|
security:
|
||||||
|
- BearerAuth: []
|
||||||
|
requestBody:
|
||||||
|
required: true
|
||||||
|
content:
|
||||||
|
application/json:
|
||||||
|
schema:
|
||||||
|
$ref: '#/components/schemas/MaterialRequest'
|
||||||
|
responses:
|
||||||
|
'201':
|
||||||
|
description: Material created successfully
|
||||||
|
content:
|
||||||
|
application/json:
|
||||||
|
schema:
|
||||||
|
$ref: '#/components/schemas/Material'
|
||||||
|
'400':
|
||||||
|
description: Bad Request - Validation error
|
||||||
|
'401':
|
||||||
|
description: Unauthorized - Missing or invalid token
|
||||||
|
'409':
|
||||||
|
description: Conflict - Material with that name already exists
|
||||||
|
|
||||||
|
/api/materials/project/{projectId}:
|
||||||
|
get:
|
||||||
|
summary: Get all materials for a specific project
|
||||||
|
tags:
|
||||||
|
- Materials
|
||||||
|
security:
|
||||||
|
- BearerAuth: []
|
||||||
|
parameters:
|
||||||
|
- name: projectId
|
||||||
|
in: path
|
||||||
|
required: true
|
||||||
|
schema:
|
||||||
|
type: integer
|
||||||
|
responses:
|
||||||
|
'200':
|
||||||
|
description: OK
|
||||||
|
content:
|
||||||
|
application/json:
|
||||||
|
schema:
|
||||||
|
type: array
|
||||||
|
items:
|
||||||
|
$ref: '#/components/schemas/Material'
|
||||||
|
'400':
|
||||||
|
description: Bad Request - projectId not a positive integer
|
||||||
|
'401':
|
||||||
|
description: Unauthorized - Missing or invalid token
|
||||||
|
|
||||||
|
/api/materials/{id}:
|
||||||
|
delete:
|
||||||
|
summary: Delete a material by ID
|
||||||
|
tags:
|
||||||
|
- Materials
|
||||||
|
security:
|
||||||
|
- BearerAuth: []
|
||||||
|
parameters:
|
||||||
|
- name: id
|
||||||
|
in: path
|
||||||
|
required: true
|
||||||
|
schema:
|
||||||
|
type: integer
|
||||||
|
responses:
|
||||||
|
'204':
|
||||||
|
description: Material deleted successfully
|
||||||
|
'400':
|
||||||
|
description: Bad Request - Id not a positive integer
|
||||||
|
'401':
|
||||||
|
description: Unauthorized - Missing or invalid token
|
||||||
|
'404':
|
||||||
|
description: Not Found - No material with that id belongs to this user
|
||||||
|
|
||||||
|
components:
|
||||||
|
securitySchemes:
|
||||||
|
BearerAuth:
|
||||||
|
type: http
|
||||||
|
scheme: bearer
|
||||||
|
bearerFormat: JWT
|
||||||
|
|
||||||
|
parameters:
|
||||||
|
IdParam:
|
||||||
|
name: id
|
||||||
|
in: path
|
||||||
|
required: true
|
||||||
|
schema: { type: integer }
|
||||||
|
SearchQuery:
|
||||||
|
name: search
|
||||||
|
in: query
|
||||||
|
schema: { type: string }
|
||||||
|
SortBy:
|
||||||
|
name: sortBy
|
||||||
|
in: query
|
||||||
|
schema: { type: string, enum: [name, date_created, id] }
|
||||||
|
Limit:
|
||||||
|
name: limit
|
||||||
|
in: query
|
||||||
|
schema: { type: integer, default: 10 }
|
||||||
|
Offset:
|
||||||
|
name: offset
|
||||||
|
in: query
|
||||||
|
schema: { type: integer, default: 0 }
|
||||||
|
|
||||||
|
schemas:
|
||||||
|
SignupRequest:
|
||||||
|
type: object
|
||||||
|
required:
|
||||||
|
- username
|
||||||
|
- password
|
||||||
|
properties:
|
||||||
|
username:
|
||||||
|
type: string
|
||||||
|
example: new_user_1
|
||||||
|
password:
|
||||||
|
type: string
|
||||||
|
format: password
|
||||||
|
example: newpassword1234
|
||||||
|
|
||||||
|
LoginRequest:
|
||||||
|
type: object
|
||||||
|
required:
|
||||||
|
- username
|
||||||
|
- password
|
||||||
|
properties:
|
||||||
|
username:
|
||||||
|
type: string
|
||||||
|
example: new_user_1
|
||||||
|
password:
|
||||||
|
type: string
|
||||||
|
format: password
|
||||||
|
example: newpassword1234
|
||||||
|
|
||||||
|
UserResponse:
|
||||||
|
type: object
|
||||||
|
properties:
|
||||||
|
id:
|
||||||
|
type: integer
|
||||||
|
example: 10
|
||||||
|
username:
|
||||||
|
type: string
|
||||||
|
example: new_user_1
|
||||||
|
|
||||||
|
LoginResponse:
|
||||||
|
type: object
|
||||||
|
properties:
|
||||||
|
accessToken:
|
||||||
|
type: string
|
||||||
|
example: eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9...
|
||||||
|
|
||||||
|
Idea:
|
||||||
|
type: object
|
||||||
|
properties:
|
||||||
|
id:
|
||||||
|
type: integer
|
||||||
|
readOnly: true
|
||||||
|
example: 1
|
||||||
|
name:
|
||||||
|
type: string
|
||||||
|
example: AI-Powered Task Manager
|
||||||
|
description:
|
||||||
|
type: string
|
||||||
|
example: A web app that uses AI to prioritize tasks intelligently
|
||||||
|
date_created:
|
||||||
|
type: string
|
||||||
|
format: date-time
|
||||||
|
readOnly: true
|
||||||
|
|
||||||
|
IdeaRequest:
|
||||||
|
type: object
|
||||||
|
required:
|
||||||
|
- name
|
||||||
|
- description
|
||||||
|
properties:
|
||||||
|
name:
|
||||||
|
type: string
|
||||||
|
example: AI-Powered Task Manager
|
||||||
|
description:
|
||||||
|
type: string
|
||||||
|
example: A web app that uses AI to prioritize tasks intelligently
|
||||||
|
|
||||||
|
IdeaUpdateRequest:
|
||||||
|
type: object
|
||||||
|
description: At least one field must be provided
|
||||||
|
properties:
|
||||||
|
name:
|
||||||
|
type: string
|
||||||
|
example: AI-Powered Task Manager
|
||||||
|
description:
|
||||||
|
type: string
|
||||||
|
example: A web app that uses AI to prioritize tasks intelligently
|
||||||
|
|
||||||
|
Project:
|
||||||
|
type: object
|
||||||
|
properties:
|
||||||
|
id:
|
||||||
|
type: integer
|
||||||
|
readOnly: true
|
||||||
|
example: 1
|
||||||
|
name:
|
||||||
|
type: string
|
||||||
|
example: Project 1
|
||||||
|
description:
|
||||||
|
type: string
|
||||||
|
example: Project 1 description
|
||||||
|
date_created:
|
||||||
|
type: string
|
||||||
|
format: date-time
|
||||||
|
readOnly: true
|
||||||
|
files:
|
||||||
|
type: array
|
||||||
|
readOnly: true
|
||||||
|
items:
|
||||||
|
$ref: '#/components/schemas/File'
|
||||||
|
|
||||||
|
ProjectRequest:
|
||||||
|
type: object
|
||||||
|
required:
|
||||||
|
- name
|
||||||
|
- description
|
||||||
|
properties:
|
||||||
|
name:
|
||||||
|
type: string
|
||||||
|
example: Project 1
|
||||||
|
description:
|
||||||
|
type: string
|
||||||
|
example: Project 1 description
|
||||||
|
|
||||||
|
ProjectUpdateRequest:
|
||||||
|
type: object
|
||||||
|
description: At least one field must be provided
|
||||||
|
properties:
|
||||||
|
name:
|
||||||
|
type: string
|
||||||
|
example: Project 1
|
||||||
|
description:
|
||||||
|
type: string
|
||||||
|
example: Project 1 description
|
||||||
|
|
||||||
|
Material:
|
||||||
|
type: object
|
||||||
|
properties:
|
||||||
|
id:
|
||||||
|
type: integer
|
||||||
|
readOnly: true
|
||||||
|
example: 1
|
||||||
|
projectId:
|
||||||
|
type: integer
|
||||||
|
example: 1
|
||||||
|
name:
|
||||||
|
type: string
|
||||||
|
example: Resource 1
|
||||||
|
description:
|
||||||
|
type: string
|
||||||
|
example: Resource 1 description
|
||||||
|
source:
|
||||||
|
type: string
|
||||||
|
example: Source 1
|
||||||
|
author:
|
||||||
|
type: string
|
||||||
|
example: Author 1
|
||||||
|
text:
|
||||||
|
type: string
|
||||||
|
example: Resource 1 text
|
||||||
|
|
||||||
|
MaterialRequest:
|
||||||
|
type: object
|
||||||
|
required:
|
||||||
|
- projectId
|
||||||
|
- name
|
||||||
|
- description
|
||||||
|
- source
|
||||||
|
- author
|
||||||
|
- text
|
||||||
|
properties:
|
||||||
|
projectId:
|
||||||
|
type: integer
|
||||||
|
example: 1
|
||||||
|
name:
|
||||||
|
type: string
|
||||||
|
example: Resource 1
|
||||||
|
description:
|
||||||
|
type: string
|
||||||
|
example: Resource 1 description
|
||||||
|
source:
|
||||||
|
type: string
|
||||||
|
example: Source 1
|
||||||
|
author:
|
||||||
|
type: string
|
||||||
|
example: Author 1
|
||||||
|
text:
|
||||||
|
type: string
|
||||||
|
example: Resource 1 text
|
||||||
|
|
||||||
|
File:
|
||||||
|
type: object
|
||||||
|
properties:
|
||||||
|
id: { type: integer }
|
||||||
|
projectId: { type: integer }
|
||||||
|
name: { type: string }
|
||||||
|
size: { type: integer }
|
||||||
|
mimeType: { type: string }
|
||||||
13
eslint.config.js
Normal file
13
eslint.config.js
Normal file
@@ -0,0 +1,13 @@
|
|||||||
|
import js from '@eslint/js';
|
||||||
|
import globals from 'globals';
|
||||||
|
import { defineConfig } from 'eslint/config';
|
||||||
|
|
||||||
|
export default defineConfig([
|
||||||
|
{
|
||||||
|
files: ['**/*.{js,mjs,cjs}'],
|
||||||
|
plugins: { js },
|
||||||
|
extends: ['js/recommended'],
|
||||||
|
rules: { 'no-unused-vars': 'none' },
|
||||||
|
},
|
||||||
|
{ files: ['**/*.{js,mjs,cjs}'], languageOptions: { globals: globals.node } },
|
||||||
|
]);
|
||||||
4597
package-lock.json
generated
Normal file
4597
package-lock.json
generated
Normal file
File diff suppressed because it is too large
Load Diff
36
package.json
Normal file
36
package.json
Normal file
@@ -0,0 +1,36 @@
|
|||||||
|
{
|
||||||
|
"name": "Idea_Tracker",
|
||||||
|
"version": "1.0.0",
|
||||||
|
"description": "A simple idea tracking API built with Express and Prisma",
|
||||||
|
"main": "index.js",
|
||||||
|
"scripts": {
|
||||||
|
"dev": "node --env-file=.env --watch src/server.js",
|
||||||
|
"seed": "node prisma/seed.js"
|
||||||
|
},
|
||||||
|
"keywords": [],
|
||||||
|
"author": "",
|
||||||
|
"license": "ISC",
|
||||||
|
"type": "module",
|
||||||
|
"devDependencies": {
|
||||||
|
"@eslint/js": "^9.29.0",
|
||||||
|
"eslint": "^9.29.0",
|
||||||
|
"eslint-config-prettier": "^10.1.5",
|
||||||
|
"globals": "^16.2.0",
|
||||||
|
"prettier": "^3.6.1",
|
||||||
|
"prisma": "^7.8.0"
|
||||||
|
},
|
||||||
|
"dependencies": {
|
||||||
|
"@prisma/adapter-pg": "^7.3.0",
|
||||||
|
"@prisma/client": "^7.8.0",
|
||||||
|
"archiver": "^7.0.1",
|
||||||
|
"bcrypt": "^6.0.0",
|
||||||
|
"dotenv": "^17.2.3",
|
||||||
|
"express": "^5.1.0",
|
||||||
|
"express-validator": "^7.2.1",
|
||||||
|
"js-yaml": "^4.1.1",
|
||||||
|
"jsonwebtoken": "^9.0.3",
|
||||||
|
"morgan": "^1.10.1",
|
||||||
|
"multer": "^2.1.1",
|
||||||
|
"swagger-ui-express": "^5.0.1"
|
||||||
|
}
|
||||||
|
}
|
||||||
14
prisma.config.ts
Normal file
14
prisma.config.ts
Normal file
@@ -0,0 +1,14 @@
|
|||||||
|
// This file was generated by Prisma, and assumes you have installed the following:
|
||||||
|
// npm install --save-dev prisma dotenv
|
||||||
|
import 'dotenv/config';
|
||||||
|
import { defineConfig } from 'prisma/config';
|
||||||
|
|
||||||
|
export default defineConfig({
|
||||||
|
schema: 'prisma/schema.prisma',
|
||||||
|
migrations: {
|
||||||
|
path: 'prisma/migrations',
|
||||||
|
},
|
||||||
|
datasource: {
|
||||||
|
url: process.env['DATABASE_URL'],
|
||||||
|
},
|
||||||
|
});
|
||||||
@@ -0,0 +1,80 @@
|
|||||||
|
-- CreateTable
|
||||||
|
CREATE TABLE "users" (
|
||||||
|
"id" SERIAL NOT NULL,
|
||||||
|
"username" TEXT NOT NULL,
|
||||||
|
"password" TEXT NOT NULL,
|
||||||
|
|
||||||
|
CONSTRAINT "users_pkey" PRIMARY KEY ("id")
|
||||||
|
);
|
||||||
|
|
||||||
|
-- CreateTable
|
||||||
|
CREATE TABLE "ideas" (
|
||||||
|
"id" SERIAL NOT NULL,
|
||||||
|
"name" TEXT NOT NULL,
|
||||||
|
"description" TEXT NOT NULL,
|
||||||
|
"date_created" TIMESTAMP(3) NOT NULL DEFAULT CURRENT_TIMESTAMP,
|
||||||
|
"user_id" INTEGER NOT NULL,
|
||||||
|
|
||||||
|
CONSTRAINT "ideas_pkey" PRIMARY KEY ("id")
|
||||||
|
);
|
||||||
|
|
||||||
|
-- CreateTable
|
||||||
|
CREATE TABLE "projects" (
|
||||||
|
"id" SERIAL NOT NULL,
|
||||||
|
"name" TEXT NOT NULL,
|
||||||
|
"description" TEXT NOT NULL,
|
||||||
|
"date_created" TIMESTAMP(3) NOT NULL DEFAULT CURRENT_TIMESTAMP,
|
||||||
|
"user_id" INTEGER NOT NULL,
|
||||||
|
|
||||||
|
CONSTRAINT "projects_pkey" PRIMARY KEY ("id")
|
||||||
|
);
|
||||||
|
|
||||||
|
-- CreateTable
|
||||||
|
CREATE TABLE "materials" (
|
||||||
|
"id" SERIAL NOT NULL,
|
||||||
|
"name" TEXT NOT NULL,
|
||||||
|
"description" TEXT NOT NULL,
|
||||||
|
"source" TEXT NOT NULL,
|
||||||
|
"author" TEXT NOT NULL,
|
||||||
|
"text" TEXT NOT NULL,
|
||||||
|
"project_id" INTEGER NOT NULL,
|
||||||
|
|
||||||
|
CONSTRAINT "materials_pkey" PRIMARY KEY ("id")
|
||||||
|
);
|
||||||
|
|
||||||
|
-- CreateTable
|
||||||
|
CREATE TABLE "files" (
|
||||||
|
"id" SERIAL NOT NULL,
|
||||||
|
"name" TEXT NOT NULL,
|
||||||
|
"file" BYTEA NOT NULL,
|
||||||
|
"project_id" INTEGER NOT NULL,
|
||||||
|
|
||||||
|
CONSTRAINT "files_pkey" PRIMARY KEY ("id")
|
||||||
|
);
|
||||||
|
|
||||||
|
-- CreateIndex
|
||||||
|
CREATE UNIQUE INDEX "users_username_key" ON "users"("username");
|
||||||
|
|
||||||
|
-- CreateIndex
|
||||||
|
CREATE INDEX "ideas_user_id_idx" ON "ideas"("user_id");
|
||||||
|
|
||||||
|
-- CreateIndex
|
||||||
|
CREATE INDEX "projects_user_id_idx" ON "projects"("user_id");
|
||||||
|
|
||||||
|
-- CreateIndex
|
||||||
|
CREATE INDEX "materials_project_id_idx" ON "materials"("project_id");
|
||||||
|
|
||||||
|
-- CreateIndex
|
||||||
|
CREATE INDEX "files_project_id_idx" ON "files"("project_id");
|
||||||
|
|
||||||
|
-- AddForeignKey
|
||||||
|
ALTER TABLE "ideas" ADD CONSTRAINT "ideas_user_id_fkey" FOREIGN KEY ("user_id") REFERENCES "users"("id") ON DELETE CASCADE ON UPDATE CASCADE;
|
||||||
|
|
||||||
|
-- AddForeignKey
|
||||||
|
ALTER TABLE "projects" ADD CONSTRAINT "projects_user_id_fkey" FOREIGN KEY ("user_id") REFERENCES "users"("id") ON DELETE CASCADE ON UPDATE CASCADE;
|
||||||
|
|
||||||
|
-- AddForeignKey
|
||||||
|
ALTER TABLE "materials" ADD CONSTRAINT "materials_project_id_fkey" FOREIGN KEY ("project_id") REFERENCES "projects"("id") ON DELETE CASCADE ON UPDATE CASCADE;
|
||||||
|
|
||||||
|
-- AddForeignKey
|
||||||
|
ALTER TABLE "files" ADD CONSTRAINT "files_project_id_fkey" FOREIGN KEY ("project_id") REFERENCES "projects"("id") ON DELETE CASCADE ON UPDATE CASCADE;
|
||||||
@@ -0,0 +1,10 @@
|
|||||||
|
/*
|
||||||
|
Warnings:
|
||||||
|
|
||||||
|
- Added the required column `mimeType` to the `files` table without a default value. This is not possible if the table is not empty.
|
||||||
|
- Added the required column `size` to the `files` table without a default value. This is not possible if the table is not empty.
|
||||||
|
|
||||||
|
*/
|
||||||
|
-- AlterTable
|
||||||
|
ALTER TABLE "files" ADD COLUMN "mimeType" TEXT NOT NULL,
|
||||||
|
ADD COLUMN "size" INTEGER NOT NULL;
|
||||||
3
prisma/migrations/migration_lock.toml
Normal file
3
prisma/migrations/migration_lock.toml
Normal file
@@ -0,0 +1,3 @@
|
|||||||
|
# Please do not edit this file manually
|
||||||
|
# It should be added in your version-control system (e.g., Git)
|
||||||
|
provider = "postgresql"
|
||||||
71
prisma/schema.prisma
Normal file
71
prisma/schema.prisma
Normal file
@@ -0,0 +1,71 @@
|
|||||||
|
generator client {
|
||||||
|
provider = "prisma-client-js"
|
||||||
|
output = "../src/generated/prisma.js"
|
||||||
|
}
|
||||||
|
|
||||||
|
datasource db {
|
||||||
|
provider = "postgresql"
|
||||||
|
}
|
||||||
|
|
||||||
|
model User {
|
||||||
|
id Int @id @default(autoincrement())
|
||||||
|
username String @unique
|
||||||
|
password String
|
||||||
|
ideas Idea[]
|
||||||
|
projects Project[]
|
||||||
|
|
||||||
|
@@map("users")
|
||||||
|
}
|
||||||
|
|
||||||
|
model Idea {
|
||||||
|
id Int @id @default(autoincrement())
|
||||||
|
name String
|
||||||
|
description String
|
||||||
|
date_created DateTime @default(now()) @map("date_created")
|
||||||
|
user User @relation(fields: [userId], references: [id], onDelete: Cascade)
|
||||||
|
userId Int @map("user_id")
|
||||||
|
|
||||||
|
@@index([userId])
|
||||||
|
@@map("ideas")
|
||||||
|
}
|
||||||
|
|
||||||
|
model Project {
|
||||||
|
id Int @id @default(autoincrement())
|
||||||
|
name String
|
||||||
|
description String
|
||||||
|
date_created DateTime @default(now()) @map("date_created")
|
||||||
|
user User @relation(fields: [userId], references: [id], onDelete: Cascade)
|
||||||
|
userId Int @map("user_id")
|
||||||
|
materials Material[]
|
||||||
|
files File[]
|
||||||
|
|
||||||
|
@@index([userId])
|
||||||
|
@@map("projects")
|
||||||
|
}
|
||||||
|
|
||||||
|
model Material {
|
||||||
|
id Int @id @default(autoincrement())
|
||||||
|
name String
|
||||||
|
description String
|
||||||
|
source String
|
||||||
|
author String
|
||||||
|
text String
|
||||||
|
project Project @relation(fields: [projectId], references: [id], onDelete: Cascade)
|
||||||
|
projectId Int @map("project_id")
|
||||||
|
|
||||||
|
@@index([projectId])
|
||||||
|
@@map("materials")
|
||||||
|
}
|
||||||
|
|
||||||
|
model File {
|
||||||
|
id Int @id @default(autoincrement())
|
||||||
|
name String
|
||||||
|
file Bytes
|
||||||
|
size Int
|
||||||
|
mimeType String
|
||||||
|
project Project @relation(fields: [projectId], references: [id], onDelete: Cascade)
|
||||||
|
projectId Int @map("project_id")
|
||||||
|
|
||||||
|
@@index([projectId])
|
||||||
|
@@map("files")
|
||||||
|
}
|
||||||
102
prisma/seed.js
Normal file
102
prisma/seed.js
Normal file
@@ -0,0 +1,102 @@
|
|||||||
|
import bcrypt from 'bcrypt';
|
||||||
|
import 'dotenv/config';
|
||||||
|
import prisma from '../src/config/db.js';
|
||||||
|
|
||||||
|
try {
|
||||||
|
await prisma.$queryRaw`TRUNCATE users, ideas, projects, materials, files RESTART IDENTITY CASCADE;`;
|
||||||
|
|
||||||
|
// Create users
|
||||||
|
const usersData = [
|
||||||
|
{ username: 'alice_dev', password: 'alice1234' },
|
||||||
|
{ username: 'bob_maker', password: 'bob1234' },
|
||||||
|
{ username: 'charlie_creator', password: 'charlie1234' },
|
||||||
|
];
|
||||||
|
|
||||||
|
const users = [];
|
||||||
|
|
||||||
|
for (const userData of usersData) {
|
||||||
|
const hashedPassword = await bcrypt.hash(userData.password, 10);
|
||||||
|
|
||||||
|
const user = await prisma.user.create({
|
||||||
|
data: {
|
||||||
|
username: userData.username,
|
||||||
|
password: hashedPassword,
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
|
users.push(user);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Create ideas for each user
|
||||||
|
for (const user of users) {
|
||||||
|
await prisma.idea.createMany({
|
||||||
|
data: [
|
||||||
|
{
|
||||||
|
name: 'AI-Powered Task Manager',
|
||||||
|
description:
|
||||||
|
'A web app that uses AI to prioritize tasks intelligently based on user patterns.',
|
||||||
|
userId: user.id,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: 'Community Recipe Hub',
|
||||||
|
description:
|
||||||
|
'A platform where users can share, rate, and discover recipes from around the world.',
|
||||||
|
userId: user.id,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: 'Real-time Collaboration Tool',
|
||||||
|
description:
|
||||||
|
'A tool for teams to brainstorm and collaborate in real-time with visual whiteboarding.',
|
||||||
|
userId: user.id,
|
||||||
|
},
|
||||||
|
],
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
// Create projects with materials for each user
|
||||||
|
for (const user of users) {
|
||||||
|
const project = await prisma.project.create({
|
||||||
|
data: {
|
||||||
|
name: `${user.username}'s Innovation Lab`,
|
||||||
|
description: `Main project workspace for ${user.username} to develop and prototype ideas.`,
|
||||||
|
userId: user.id,
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
|
// Add materials to the project
|
||||||
|
await prisma.material.createMany({
|
||||||
|
data: [
|
||||||
|
{
|
||||||
|
name: 'Research Paper on UX Design',
|
||||||
|
description: 'Key findings on modern UI/UX best practices.',
|
||||||
|
source: 'Nielsen Norman Group',
|
||||||
|
author: 'Don Norman',
|
||||||
|
text: 'User experience encompasses all aspects of the end-users interaction with the company, its services, and its products.',
|
||||||
|
projectId: project.id,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: 'API Documentation Reference',
|
||||||
|
description: 'Technical specifications for third-party integrations.',
|
||||||
|
source: 'OpenAI API Docs',
|
||||||
|
author: 'OpenAI Team',
|
||||||
|
text: 'The API provides access to state-of-the-art language models for various NLP tasks.',
|
||||||
|
projectId: project.id,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: 'Project Budget Template',
|
||||||
|
description: 'Financial planning and resource allocation framework.',
|
||||||
|
source: 'Internal Resources',
|
||||||
|
author: 'Finance Department',
|
||||||
|
text: 'Ensure all project expenses are tracked and approved according to company policy.',
|
||||||
|
projectId: project.id,
|
||||||
|
},
|
||||||
|
],
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
console.log('Seed completed successfully!');
|
||||||
|
} catch (error) {
|
||||||
|
console.error('Seed failed:', error);
|
||||||
|
} finally {
|
||||||
|
await prisma.$disconnect();
|
||||||
|
}
|
||||||
BIN
src/.DS_Store
vendored
Normal file
BIN
src/.DS_Store
vendored
Normal file
Binary file not shown.
7
src/config/db.js
Normal file
7
src/config/db.js
Normal file
@@ -0,0 +1,7 @@
|
|||||||
|
import { PrismaClient } from '../generated/prisma.js/client.js';
|
||||||
|
import { PrismaPg } from '@prisma/adapter-pg';
|
||||||
|
|
||||||
|
const adapter = new PrismaPg({ connectionString: process.env.DATABASE_URL });
|
||||||
|
const prisma = new PrismaClient({ adapter });
|
||||||
|
|
||||||
|
export default prisma;
|
||||||
13
src/controllers/authController.js
Normal file
13
src/controllers/authController.js
Normal file
@@ -0,0 +1,13 @@
|
|||||||
|
import { signUp, logIn } from '../services/authService.js';
|
||||||
|
|
||||||
|
export async function signUpHandler(req, res) {
|
||||||
|
const { username, password } = req.body;
|
||||||
|
const newUser = await signUp(username, password);
|
||||||
|
res.status(201).json(newUser);
|
||||||
|
}
|
||||||
|
|
||||||
|
export async function logInHandler(req, res) {
|
||||||
|
const { username, password } = req.body;
|
||||||
|
const accessToken = await logIn(username, password);
|
||||||
|
res.status(200).json({ accessToken });
|
||||||
|
}
|
||||||
43
src/controllers/ideasController.js
Normal file
43
src/controllers/ideasController.js
Normal file
@@ -0,0 +1,43 @@
|
|||||||
|
import {
|
||||||
|
getAllIdeas,
|
||||||
|
createIdea,
|
||||||
|
updateIdea,
|
||||||
|
deleteIdea,
|
||||||
|
} from '../services/ideasService.js';
|
||||||
|
|
||||||
|
export async function getAllIdeasHandler(req, res) {
|
||||||
|
const {
|
||||||
|
search = '',
|
||||||
|
sortBy = 'date_created',
|
||||||
|
order = 'desc',
|
||||||
|
offset = '0',
|
||||||
|
limit = '10',
|
||||||
|
} = req.query;
|
||||||
|
|
||||||
|
const options = {
|
||||||
|
search,
|
||||||
|
sortBy,
|
||||||
|
order,
|
||||||
|
offset: parseInt(offset),
|
||||||
|
limit: parseInt(limit),
|
||||||
|
};
|
||||||
|
|
||||||
|
const ideas = await getAllIdeas(req.user.id, options);
|
||||||
|
res.status(200).json(ideas);
|
||||||
|
}
|
||||||
|
export async function createIdeaHandler(req, res) {
|
||||||
|
const { name, description } = req.body;
|
||||||
|
const newIdea = await createIdea(req.user.id, { name, description });
|
||||||
|
res.status(201).json(newIdea);
|
||||||
|
}
|
||||||
|
export async function updateIdeaHandler(req, res) {
|
||||||
|
const id = parseInt(req.params.id);
|
||||||
|
const { name, description } = req.body;
|
||||||
|
const updatedIdea = await updateIdea(id, req.user.id, { name, description });
|
||||||
|
res.status(200).json(updatedIdea);
|
||||||
|
}
|
||||||
|
export async function deleteIdeaHandler(req, res) {
|
||||||
|
const id = parseInt(req.params.id);
|
||||||
|
await deleteIdea(id, req.user.id);
|
||||||
|
res.status(204).send();
|
||||||
|
}
|
||||||
79
src/controllers/materialsController.js
Normal file
79
src/controllers/materialsController.js
Normal file
@@ -0,0 +1,79 @@
|
|||||||
|
import {
|
||||||
|
getAllMaterials,
|
||||||
|
getMaterialsByProject,
|
||||||
|
getMaterialById,
|
||||||
|
createMaterial,
|
||||||
|
deleteMaterial,
|
||||||
|
} from '../services/materialsService.js';
|
||||||
|
|
||||||
|
export async function getAllMaterialsHandler(req, res) {
|
||||||
|
const {
|
||||||
|
search = '',
|
||||||
|
sortBy = 'id',
|
||||||
|
order = 'desc',
|
||||||
|
offset = '0',
|
||||||
|
limit = '10',
|
||||||
|
} = req.query;
|
||||||
|
|
||||||
|
const options = {
|
||||||
|
search,
|
||||||
|
sortBy,
|
||||||
|
order,
|
||||||
|
offset: parseInt(offset),
|
||||||
|
limit: parseInt(limit),
|
||||||
|
};
|
||||||
|
|
||||||
|
const materials = await getAllMaterials(req.user.id, options);
|
||||||
|
res.status(200).json(materials);
|
||||||
|
}
|
||||||
|
|
||||||
|
export async function getMaterialsByProjectHandler(req, res) {
|
||||||
|
const projectId = parseInt(req.params.projectId);
|
||||||
|
const {
|
||||||
|
search = '',
|
||||||
|
sortBy = 'id',
|
||||||
|
order = 'desc',
|
||||||
|
offset = '0',
|
||||||
|
limit = '10',
|
||||||
|
} = req.query;
|
||||||
|
|
||||||
|
const options = {
|
||||||
|
search,
|
||||||
|
sortBy,
|
||||||
|
order,
|
||||||
|
offset: parseInt(offset),
|
||||||
|
limit: parseInt(limit),
|
||||||
|
};
|
||||||
|
|
||||||
|
const materials = await getMaterialsByProject(
|
||||||
|
projectId,
|
||||||
|
req.user.id,
|
||||||
|
options
|
||||||
|
);
|
||||||
|
res.status(200).json(materials);
|
||||||
|
}
|
||||||
|
|
||||||
|
export async function getMaterialByIdHandler(req, res) {
|
||||||
|
const id = parseInt(req.params.id);
|
||||||
|
const material = await getMaterialById(id, req.user.id);
|
||||||
|
res.status(200).json(material);
|
||||||
|
}
|
||||||
|
|
||||||
|
export async function createMaterialHandler(req, res) {
|
||||||
|
const { projectId, name, description, source, author, text } = req.body;
|
||||||
|
const newMaterial = await createMaterial(req.user.id, {
|
||||||
|
projectId,
|
||||||
|
name,
|
||||||
|
description,
|
||||||
|
source,
|
||||||
|
author,
|
||||||
|
text,
|
||||||
|
});
|
||||||
|
res.status(201).json(newMaterial);
|
||||||
|
}
|
||||||
|
|
||||||
|
export async function deleteMaterialHandler(req, res) {
|
||||||
|
const id = parseInt(req.params.id);
|
||||||
|
await deleteMaterial(id, req.user.id);
|
||||||
|
res.status(204).send();
|
||||||
|
}
|
||||||
159
src/controllers/projectsController.js
Normal file
159
src/controllers/projectsController.js
Normal file
@@ -0,0 +1,159 @@
|
|||||||
|
import {
|
||||||
|
getAllProjects,
|
||||||
|
getProjectById,
|
||||||
|
createProject,
|
||||||
|
updateProject,
|
||||||
|
deleteProject,
|
||||||
|
getProjectFilesById,
|
||||||
|
addProjectFile,
|
||||||
|
deleteProjectFile,
|
||||||
|
getProjectFilesForDownload,
|
||||||
|
} from '../services/projectsService.js';
|
||||||
|
|
||||||
|
import archiver from 'archiver';
|
||||||
|
import { Buffer } from 'node:buffer';
|
||||||
|
|
||||||
|
export async function getAllProjectsHandler(req, res) {
|
||||||
|
const {
|
||||||
|
search = '',
|
||||||
|
sortBy = 'date_created',
|
||||||
|
order = 'desc',
|
||||||
|
offset = '0',
|
||||||
|
limit = '10',
|
||||||
|
} = req.query;
|
||||||
|
|
||||||
|
const options = {
|
||||||
|
search,
|
||||||
|
sortBy,
|
||||||
|
order,
|
||||||
|
offset: parseInt(offset),
|
||||||
|
limit: parseInt(limit),
|
||||||
|
};
|
||||||
|
|
||||||
|
const projects = await getAllProjects(req.user.id, options);
|
||||||
|
res.status(200).json(projects);
|
||||||
|
}
|
||||||
|
|
||||||
|
export async function getProjectByIdHandler(req, res) {
|
||||||
|
const id = parseInt(req.params.id);
|
||||||
|
const project = await getProjectById(id, req.user.id);
|
||||||
|
res.status(200).json(project);
|
||||||
|
}
|
||||||
|
|
||||||
|
export async function createProjectHandler(req, res) {
|
||||||
|
const { name, description } = req.body;
|
||||||
|
const newProject = await createProject(req.user.id, { name, description });
|
||||||
|
res.status(201).json(newProject);
|
||||||
|
}
|
||||||
|
|
||||||
|
export async function updateProjectHandler(req, res) {
|
||||||
|
const id = parseInt(req.params.id);
|
||||||
|
const { name, description } = req.body;
|
||||||
|
const updatedProject = await updateProject(id, req.user.id, {
|
||||||
|
name,
|
||||||
|
description,
|
||||||
|
});
|
||||||
|
res.status(200).json(updatedProject);
|
||||||
|
}
|
||||||
|
|
||||||
|
export async function deleteProjectHandler(req, res) {
|
||||||
|
const id = parseInt(req.params.id);
|
||||||
|
await deleteProject(id, req.user.id);
|
||||||
|
res.status(204).send();
|
||||||
|
}
|
||||||
|
|
||||||
|
export async function getProjectFilesHandler(req, res) {
|
||||||
|
const id = parseInt(req.params.id);
|
||||||
|
const files = await getProjectFilesById(id, req.user.id);
|
||||||
|
res.status(200).json(files);
|
||||||
|
}
|
||||||
|
|
||||||
|
export async function addProjectFileHandler(req, res) {
|
||||||
|
let id;
|
||||||
|
let fileData;
|
||||||
|
|
||||||
|
try {
|
||||||
|
id = parseInt(req.params.id);
|
||||||
|
if (!req.file) {
|
||||||
|
return res.status(400).json({ error: 'No file provided' });
|
||||||
|
}
|
||||||
|
|
||||||
|
fileData = {
|
||||||
|
name: req.file.originalname,
|
||||||
|
file: req.file.buffer,
|
||||||
|
size: req.file.size,
|
||||||
|
mimeType: req.file.mimetype
|
||||||
|
};
|
||||||
|
} catch (error) {
|
||||||
|
return res.status(400).json({ error: 'Invalid file data' });
|
||||||
|
}
|
||||||
|
|
||||||
|
try {
|
||||||
|
const newFile = await addProjectFile(id, req.user.id, fileData);
|
||||||
|
if (!newFile) {
|
||||||
|
return res.status(404).json({ error: 'Project file error' });
|
||||||
|
}
|
||||||
|
const { file, ...metadata } = newFile;
|
||||||
|
res.status(201).json(metadata);
|
||||||
|
} catch (error) {
|
||||||
|
res.status(error.status || 500).json({ error: error.message });
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export async function deleteProjectFileHandler(req, res) {
|
||||||
|
try {
|
||||||
|
const fileId = parseInt(req.params.id);
|
||||||
|
|
||||||
|
const deletedFile = await deleteProjectFile(fileId, req.user.id);
|
||||||
|
|
||||||
|
if (!deletedFile) {
|
||||||
|
return res.status(404).json({ error: 'File not found or unauthorized' });
|
||||||
|
}
|
||||||
|
|
||||||
|
res.status(200).json({ message: 'File deleted successfully' });
|
||||||
|
} catch (error) {
|
||||||
|
res.status(400).json({ error: 'Invalid file ID' });
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export async function downloadProjectFilesHandler(req, res) {
|
||||||
|
try {
|
||||||
|
const fileId = parseInt(req.params.id);
|
||||||
|
const userId = req.user.id;
|
||||||
|
|
||||||
|
const files = await getProjectFilesForDownload(fileId, userId);
|
||||||
|
|
||||||
|
if (!files || files.length === 0) {
|
||||||
|
return res.status(404).json({ error: 'No files found for this project' });
|
||||||
|
}
|
||||||
|
|
||||||
|
res.set({
|
||||||
|
'Content-Type': 'application/zip',
|
||||||
|
'Content-Disposition': `attachment; filename="project_${fileId}_files.zip"`
|
||||||
|
});
|
||||||
|
|
||||||
|
const archive = archiver('zip', { zlib: { level: 9 } });
|
||||||
|
|
||||||
|
archive.pipe(res);
|
||||||
|
|
||||||
|
files.forEach(f => {
|
||||||
|
|
||||||
|
const binaryData = f.file;
|
||||||
|
|
||||||
|
if (binaryData) {
|
||||||
|
const bufferData = Buffer.from(binaryData);
|
||||||
|
archive.append(bufferData, { name: f.name });
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
console.error(`ERROR: No binary data found for ${f.name}. Object was:`, f);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
await archive.finalize();
|
||||||
|
|
||||||
|
} catch (error) {
|
||||||
|
if (!res.headersSent) {
|
||||||
|
res.status(error.status || 500).json({ error: error.message });
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
1
src/generated/prisma.js/client.d.ts
vendored
Normal file
1
src/generated/prisma.js/client.d.ts
vendored
Normal file
@@ -0,0 +1 @@
|
|||||||
|
export * from "./index"
|
||||||
5
src/generated/prisma.js/client.js
Normal file
5
src/generated/prisma.js/client.js
Normal file
@@ -0,0 +1,5 @@
|
|||||||
|
|
||||||
|
/* !!! This is code generated by Prisma. Do not edit directly. !!!
|
||||||
|
/* eslint-disable */
|
||||||
|
// biome-ignore-all lint: generated file
|
||||||
|
module.exports = { ...require('.') }
|
||||||
1
src/generated/prisma.js/default.d.ts
vendored
Normal file
1
src/generated/prisma.js/default.d.ts
vendored
Normal file
@@ -0,0 +1 @@
|
|||||||
|
export * from "./index"
|
||||||
5
src/generated/prisma.js/default.js
Normal file
5
src/generated/prisma.js/default.js
Normal file
@@ -0,0 +1,5 @@
|
|||||||
|
|
||||||
|
/* !!! This is code generated by Prisma. Do not edit directly. !!!
|
||||||
|
/* eslint-disable */
|
||||||
|
// biome-ignore-all lint: generated file
|
||||||
|
module.exports = { ...require('#main-entry-point') }
|
||||||
1
src/generated/prisma.js/edge.d.ts
vendored
Normal file
1
src/generated/prisma.js/edge.d.ts
vendored
Normal file
@@ -0,0 +1 @@
|
|||||||
|
export * from "./default"
|
||||||
186
src/generated/prisma.js/edge.js
Normal file
186
src/generated/prisma.js/edge.js
Normal file
File diff suppressed because one or more lines are too long
212
src/generated/prisma.js/index-browser.js
Normal file
212
src/generated/prisma.js/index-browser.js
Normal file
@@ -0,0 +1,212 @@
|
|||||||
|
|
||||||
|
/* !!! This is code generated by Prisma. Do not edit directly. !!!
|
||||||
|
/* eslint-disable */
|
||||||
|
// biome-ignore-all lint: generated file
|
||||||
|
|
||||||
|
Object.defineProperty(exports, "__esModule", { value: true });
|
||||||
|
|
||||||
|
const {
|
||||||
|
Decimal,
|
||||||
|
DbNull,
|
||||||
|
JsonNull,
|
||||||
|
AnyNull,
|
||||||
|
NullTypes,
|
||||||
|
makeStrictEnum,
|
||||||
|
Public,
|
||||||
|
getRuntime,
|
||||||
|
skip
|
||||||
|
} = require('./runtime/index-browser.js')
|
||||||
|
|
||||||
|
|
||||||
|
const Prisma = {}
|
||||||
|
|
||||||
|
exports.Prisma = Prisma
|
||||||
|
exports.$Enums = {}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Prisma Client JS version: 7.8.0
|
||||||
|
* Query Engine version: 3c6e192761c0362d496ed980de936e2f3cebcd3a
|
||||||
|
*/
|
||||||
|
Prisma.prismaVersion = {
|
||||||
|
client: "7.8.0",
|
||||||
|
engine: "3c6e192761c0362d496ed980de936e2f3cebcd3a"
|
||||||
|
}
|
||||||
|
|
||||||
|
Prisma.PrismaClientKnownRequestError = () => {
|
||||||
|
const runtimeName = getRuntime().prettyName;
|
||||||
|
throw new Error(`PrismaClientKnownRequestError is unable to run in this browser environment, or has been bundled for the browser (running in ${runtimeName}).
|
||||||
|
In case this error is unexpected for you, please report it in https://pris.ly/prisma-prisma-bug-report`,
|
||||||
|
)};
|
||||||
|
Prisma.PrismaClientUnknownRequestError = () => {
|
||||||
|
const runtimeName = getRuntime().prettyName;
|
||||||
|
throw new Error(`PrismaClientUnknownRequestError is unable to run in this browser environment, or has been bundled for the browser (running in ${runtimeName}).
|
||||||
|
In case this error is unexpected for you, please report it in https://pris.ly/prisma-prisma-bug-report`,
|
||||||
|
)}
|
||||||
|
Prisma.PrismaClientRustPanicError = () => {
|
||||||
|
const runtimeName = getRuntime().prettyName;
|
||||||
|
throw new Error(`PrismaClientRustPanicError is unable to run in this browser environment, or has been bundled for the browser (running in ${runtimeName}).
|
||||||
|
In case this error is unexpected for you, please report it in https://pris.ly/prisma-prisma-bug-report`,
|
||||||
|
)}
|
||||||
|
Prisma.PrismaClientInitializationError = () => {
|
||||||
|
const runtimeName = getRuntime().prettyName;
|
||||||
|
throw new Error(`PrismaClientInitializationError is unable to run in this browser environment, or has been bundled for the browser (running in ${runtimeName}).
|
||||||
|
In case this error is unexpected for you, please report it in https://pris.ly/prisma-prisma-bug-report`,
|
||||||
|
)}
|
||||||
|
Prisma.PrismaClientValidationError = () => {
|
||||||
|
const runtimeName = getRuntime().prettyName;
|
||||||
|
throw new Error(`PrismaClientValidationError is unable to run in this browser environment, or has been bundled for the browser (running in ${runtimeName}).
|
||||||
|
In case this error is unexpected for you, please report it in https://pris.ly/prisma-prisma-bug-report`,
|
||||||
|
)}
|
||||||
|
Prisma.Decimal = Decimal
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Re-export of sql-template-tag
|
||||||
|
*/
|
||||||
|
Prisma.sql = () => {
|
||||||
|
const runtimeName = getRuntime().prettyName;
|
||||||
|
throw new Error(`sqltag is unable to run in this browser environment, or has been bundled for the browser (running in ${runtimeName}).
|
||||||
|
In case this error is unexpected for you, please report it in https://pris.ly/prisma-prisma-bug-report`,
|
||||||
|
)}
|
||||||
|
Prisma.empty = () => {
|
||||||
|
const runtimeName = getRuntime().prettyName;
|
||||||
|
throw new Error(`empty is unable to run in this browser environment, or has been bundled for the browser (running in ${runtimeName}).
|
||||||
|
In case this error is unexpected for you, please report it in https://pris.ly/prisma-prisma-bug-report`,
|
||||||
|
)}
|
||||||
|
Prisma.join = () => {
|
||||||
|
const runtimeName = getRuntime().prettyName;
|
||||||
|
throw new Error(`join is unable to run in this browser environment, or has been bundled for the browser (running in ${runtimeName}).
|
||||||
|
In case this error is unexpected for you, please report it in https://pris.ly/prisma-prisma-bug-report`,
|
||||||
|
)}
|
||||||
|
Prisma.raw = () => {
|
||||||
|
const runtimeName = getRuntime().prettyName;
|
||||||
|
throw new Error(`raw is unable to run in this browser environment, or has been bundled for the browser (running in ${runtimeName}).
|
||||||
|
In case this error is unexpected for you, please report it in https://pris.ly/prisma-prisma-bug-report`,
|
||||||
|
)}
|
||||||
|
Prisma.validator = Public.validator
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Extensions
|
||||||
|
*/
|
||||||
|
Prisma.getExtensionContext = () => {
|
||||||
|
const runtimeName = getRuntime().prettyName;
|
||||||
|
throw new Error(`Extensions.getExtensionContext is unable to run in this browser environment, or has been bundled for the browser (running in ${runtimeName}).
|
||||||
|
In case this error is unexpected for you, please report it in https://pris.ly/prisma-prisma-bug-report`,
|
||||||
|
)}
|
||||||
|
Prisma.defineExtension = () => {
|
||||||
|
const runtimeName = getRuntime().prettyName;
|
||||||
|
throw new Error(`Extensions.defineExtension is unable to run in this browser environment, or has been bundled for the browser (running in ${runtimeName}).
|
||||||
|
In case this error is unexpected for you, please report it in https://pris.ly/prisma-prisma-bug-report`,
|
||||||
|
)}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Shorthand utilities for JSON filtering
|
||||||
|
*/
|
||||||
|
Prisma.DbNull = DbNull
|
||||||
|
Prisma.JsonNull = JsonNull
|
||||||
|
Prisma.AnyNull = AnyNull
|
||||||
|
|
||||||
|
Prisma.NullTypes = NullTypes
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Enums
|
||||||
|
*/
|
||||||
|
|
||||||
|
exports.Prisma.TransactionIsolationLevel = makeStrictEnum({
|
||||||
|
ReadUncommitted: 'ReadUncommitted',
|
||||||
|
ReadCommitted: 'ReadCommitted',
|
||||||
|
RepeatableRead: 'RepeatableRead',
|
||||||
|
Serializable: 'Serializable'
|
||||||
|
});
|
||||||
|
|
||||||
|
exports.Prisma.UserScalarFieldEnum = {
|
||||||
|
id: 'id',
|
||||||
|
username: 'username',
|
||||||
|
password: 'password'
|
||||||
|
};
|
||||||
|
|
||||||
|
exports.Prisma.IdeaScalarFieldEnum = {
|
||||||
|
id: 'id',
|
||||||
|
name: 'name',
|
||||||
|
description: 'description',
|
||||||
|
date_created: 'date_created',
|
||||||
|
userId: 'userId'
|
||||||
|
};
|
||||||
|
|
||||||
|
exports.Prisma.ProjectScalarFieldEnum = {
|
||||||
|
id: 'id',
|
||||||
|
name: 'name',
|
||||||
|
description: 'description',
|
||||||
|
date_created: 'date_created',
|
||||||
|
userId: 'userId'
|
||||||
|
};
|
||||||
|
|
||||||
|
exports.Prisma.MaterialScalarFieldEnum = {
|
||||||
|
id: 'id',
|
||||||
|
name: 'name',
|
||||||
|
description: 'description',
|
||||||
|
source: 'source',
|
||||||
|
author: 'author',
|
||||||
|
text: 'text',
|
||||||
|
projectId: 'projectId'
|
||||||
|
};
|
||||||
|
|
||||||
|
exports.Prisma.FileScalarFieldEnum = {
|
||||||
|
id: 'id',
|
||||||
|
name: 'name',
|
||||||
|
file: 'file',
|
||||||
|
size: 'size',
|
||||||
|
mimeType: 'mimeType',
|
||||||
|
projectId: 'projectId'
|
||||||
|
};
|
||||||
|
|
||||||
|
exports.Prisma.SortOrder = {
|
||||||
|
asc: 'asc',
|
||||||
|
desc: 'desc'
|
||||||
|
};
|
||||||
|
|
||||||
|
exports.Prisma.QueryMode = {
|
||||||
|
default: 'default',
|
||||||
|
insensitive: 'insensitive'
|
||||||
|
};
|
||||||
|
|
||||||
|
|
||||||
|
exports.Prisma.ModelName = {
|
||||||
|
User: 'User',
|
||||||
|
Idea: 'Idea',
|
||||||
|
Project: 'Project',
|
||||||
|
Material: 'Material',
|
||||||
|
File: 'File'
|
||||||
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* This is a stub Prisma Client that will error at runtime if called.
|
||||||
|
*/
|
||||||
|
class PrismaClient {
|
||||||
|
constructor() {
|
||||||
|
return new Proxy(this, {
|
||||||
|
get(target, prop) {
|
||||||
|
let message
|
||||||
|
const runtime = getRuntime()
|
||||||
|
if (runtime.isEdge) {
|
||||||
|
message = `PrismaClient is not configured to run in ${runtime.prettyName}. In order to run Prisma Client on edge runtime, either:
|
||||||
|
- Use Prisma Accelerate: https://pris.ly/d/accelerate
|
||||||
|
- Use Driver Adapters: https://pris.ly/d/driver-adapters
|
||||||
|
`;
|
||||||
|
} else {
|
||||||
|
message = 'PrismaClient is unable to run in this browser environment, or has been bundled for the browser (running in `' + runtime.prettyName + '`).'
|
||||||
|
}
|
||||||
|
|
||||||
|
message += `
|
||||||
|
If this is unexpected, please open an issue: https://pris.ly/prisma-prisma-bug-report`
|
||||||
|
|
||||||
|
throw new Error(message)
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
exports.PrismaClient = PrismaClient
|
||||||
|
|
||||||
|
Object.assign(exports, Prisma)
|
||||||
8890
src/generated/prisma.js/index.d.ts
vendored
Normal file
8890
src/generated/prisma.js/index.d.ts
vendored
Normal file
File diff suppressed because it is too large
Load Diff
186
src/generated/prisma.js/index.js
Normal file
186
src/generated/prisma.js/index.js
Normal file
File diff suppressed because one or more lines are too long
144
src/generated/prisma.js/package.json
Normal file
144
src/generated/prisma.js/package.json
Normal file
@@ -0,0 +1,144 @@
|
|||||||
|
{
|
||||||
|
"name": "prisma-client-8a7d0bd0282d327d88c7d480c03ad5f66befdf72c92e9b6d7469f459b777fd67",
|
||||||
|
"main": "index.js",
|
||||||
|
"types": "index.d.ts",
|
||||||
|
"browser": "default.js",
|
||||||
|
"exports": {
|
||||||
|
"./client": {
|
||||||
|
"require": {
|
||||||
|
"node": "./index.js",
|
||||||
|
"edge-light": "./edge.js",
|
||||||
|
"workerd": "./edge.js",
|
||||||
|
"worker": "./edge.js",
|
||||||
|
"browser": "./index-browser.js",
|
||||||
|
"default": "./index.js"
|
||||||
|
},
|
||||||
|
"import": {
|
||||||
|
"node": "./index.js",
|
||||||
|
"edge-light": "./edge.js",
|
||||||
|
"workerd": "./edge.js",
|
||||||
|
"worker": "./edge.js",
|
||||||
|
"browser": "./index-browser.js",
|
||||||
|
"default": "./index.js"
|
||||||
|
},
|
||||||
|
"default": "./index.js"
|
||||||
|
},
|
||||||
|
"./package.json": "./package.json",
|
||||||
|
".": {
|
||||||
|
"require": {
|
||||||
|
"node": "./index.js",
|
||||||
|
"edge-light": "./edge.js",
|
||||||
|
"workerd": "./edge.js",
|
||||||
|
"worker": "./edge.js",
|
||||||
|
"browser": "./index-browser.js",
|
||||||
|
"default": "./index.js"
|
||||||
|
},
|
||||||
|
"import": {
|
||||||
|
"node": "./index.js",
|
||||||
|
"edge-light": "./edge.js",
|
||||||
|
"workerd": "./edge.js",
|
||||||
|
"worker": "./edge.js",
|
||||||
|
"browser": "./index-browser.js",
|
||||||
|
"default": "./index.js"
|
||||||
|
},
|
||||||
|
"default": "./index.js"
|
||||||
|
},
|
||||||
|
"./extension": {
|
||||||
|
"types": "./extension.d.ts",
|
||||||
|
"require": "./extension.js",
|
||||||
|
"import": "./extension.js",
|
||||||
|
"default": "./extension.js"
|
||||||
|
},
|
||||||
|
"./index-browser": {
|
||||||
|
"types": "./index.d.ts",
|
||||||
|
"require": "./index-browser.js",
|
||||||
|
"import": "./index-browser.js",
|
||||||
|
"default": "./index-browser.js"
|
||||||
|
},
|
||||||
|
"./index": {
|
||||||
|
"types": "./index.d.ts",
|
||||||
|
"require": "./index.js",
|
||||||
|
"import": "./index.js",
|
||||||
|
"default": "./index.js"
|
||||||
|
},
|
||||||
|
"./edge": {
|
||||||
|
"types": "./edge.d.ts",
|
||||||
|
"require": "./edge.js",
|
||||||
|
"import": "./edge.js",
|
||||||
|
"default": "./edge.js"
|
||||||
|
},
|
||||||
|
"./runtime/client": {
|
||||||
|
"types": "./runtime/client.d.ts",
|
||||||
|
"node": {
|
||||||
|
"require": "./runtime/client.js",
|
||||||
|
"default": "./runtime/client.js"
|
||||||
|
},
|
||||||
|
"require": "./runtime/client.js",
|
||||||
|
"import": "./runtime/client.mjs",
|
||||||
|
"default": "./runtime/client.mjs"
|
||||||
|
},
|
||||||
|
"./runtime/wasm-compiler-edge": {
|
||||||
|
"types": "./runtime/wasm-compiler-edge.d.ts",
|
||||||
|
"require": "./runtime/wasm-compiler-edge.js",
|
||||||
|
"import": "./runtime/wasm-compiler-edge.mjs",
|
||||||
|
"default": "./runtime/wasm-compiler-edge.mjs"
|
||||||
|
},
|
||||||
|
"./runtime/index-browser": {
|
||||||
|
"types": "./runtime/index-browser.d.ts",
|
||||||
|
"require": "./runtime/index-browser.js",
|
||||||
|
"import": "./runtime/index-browser.mjs",
|
||||||
|
"default": "./runtime/index-browser.mjs"
|
||||||
|
},
|
||||||
|
"./generator-build": {
|
||||||
|
"require": "./generator-build/index.js",
|
||||||
|
"import": "./generator-build/index.js",
|
||||||
|
"default": "./generator-build/index.js"
|
||||||
|
},
|
||||||
|
"./sql": {
|
||||||
|
"require": {
|
||||||
|
"types": "./sql.d.ts",
|
||||||
|
"node": "./sql.js",
|
||||||
|
"default": "./sql.js"
|
||||||
|
},
|
||||||
|
"import": {
|
||||||
|
"types": "./sql.d.ts",
|
||||||
|
"node": "./sql.mjs",
|
||||||
|
"default": "./sql.mjs"
|
||||||
|
},
|
||||||
|
"default": "./sql.js"
|
||||||
|
},
|
||||||
|
"./*": "./*"
|
||||||
|
},
|
||||||
|
"version": "7.8.0",
|
||||||
|
"sideEffects": false,
|
||||||
|
"dependencies": {
|
||||||
|
"@prisma/client-runtime-utils": "7.8.0"
|
||||||
|
},
|
||||||
|
"imports": {
|
||||||
|
"#wasm-compiler-loader": {
|
||||||
|
"edge-light": "./wasm-edge-light-loader.mjs",
|
||||||
|
"workerd": "./wasm-worker-loader.mjs",
|
||||||
|
"worker": "./wasm-worker-loader.mjs",
|
||||||
|
"default": "./wasm-worker-loader.mjs"
|
||||||
|
},
|
||||||
|
"#main-entry-point": {
|
||||||
|
"require": {
|
||||||
|
"node": "./index.js",
|
||||||
|
"edge-light": "./edge.js",
|
||||||
|
"workerd": "./edge.js",
|
||||||
|
"worker": "./edge.js",
|
||||||
|
"browser": "./index-browser.js",
|
||||||
|
"default": "./index.js"
|
||||||
|
},
|
||||||
|
"import": {
|
||||||
|
"node": "./index.js",
|
||||||
|
"edge-light": "./edge.js",
|
||||||
|
"workerd": "./edge.js",
|
||||||
|
"worker": "./edge.js",
|
||||||
|
"browser": "./index-browser.js",
|
||||||
|
"default": "./index.js"
|
||||||
|
},
|
||||||
|
"default": "./index.js"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
2
src/generated/prisma.js/query_compiler_fast_bg.js
Normal file
2
src/generated/prisma.js/query_compiler_fast_bg.js
Normal file
@@ -0,0 +1,2 @@
|
|||||||
|
"use strict";var h=Object.defineProperty;var T=Object.getOwnPropertyDescriptor;var M=Object.getOwnPropertyNames;var j=Object.prototype.hasOwnProperty;var D=(e,t)=>{for(var n in t)h(e,n,{get:t[n],enumerable:!0})},O=(e,t,n,_)=>{if(t&&typeof t=="object"||typeof t=="function")for(let r of M(t))!j.call(e,r)&&r!==n&&h(e,r,{get:()=>t[r],enumerable:!(_=T(t,r))||_.enumerable});return e};var B=e=>O(h({},"__esModule",{value:!0}),e);var xe={};D(xe,{QueryCompiler:()=>F,__wbg_Error_e83987f665cf5504:()=>q,__wbg_Number_bb48ca12f395cd08:()=>C,__wbg_String_8f0eb39a4a4c2f66:()=>k,__wbg___wbindgen_boolean_get_6d5a1ee65bab5f68:()=>W,__wbg___wbindgen_debug_string_df47ffb5e35e6763:()=>V,__wbg___wbindgen_in_bb933bd9e1b3bc0f:()=>z,__wbg___wbindgen_is_object_c818261d21f283a4:()=>L,__wbg___wbindgen_is_string_fbb76cb2940daafd:()=>P,__wbg___wbindgen_is_undefined_2d472862bd29a478:()=>Q,__wbg___wbindgen_jsval_loose_eq_b664b38a2f582147:()=>Y,__wbg___wbindgen_number_get_a20bf9b85341449d:()=>G,__wbg___wbindgen_string_get_e4f06c90489ad01b:()=>J,__wbg___wbindgen_throw_b855445ff6a94295:()=>X,__wbg_entries_e171b586f8f6bdbf:()=>H,__wbg_getTime_14776bfb48a1bff9:()=>K,__wbg_get_7bed016f185add81:()=>Z,__wbg_get_with_ref_key_1dc361bd10053bfe:()=>v,__wbg_instanceof_ArrayBuffer_70beb1189ca63b38:()=>ee,__wbg_instanceof_Uint8Array_20c8e73002f7af98:()=>te,__wbg_isSafeInteger_d216eda7911dde36:()=>ne,__wbg_length_69bca3cb64fc8748:()=>re,__wbg_length_cdd215e10d9dd507:()=>_e,__wbg_new_0_f9740686d739025c:()=>oe,__wbg_new_1acc0b6eea89d040:()=>ce,__wbg_new_5a79be3ab53b8aa5:()=>ie,__wbg_new_68651c719dcda04e:()=>se,__wbg_new_e17d9f43105b08be:()=>ue,__wbg_prototypesetcall_2a6620b6922694b2:()=>fe,__wbg_set_3f1d0b984ed272ed:()=>be,__wbg_set_907fb406c34a251d:()=>de,__wbg_set_c213c871859d6500:()=>ae,__wbg_set_message_82ae475bb413aa5c:()=>ge,__wbg_set_wasm:()=>N,__wbindgen_cast_2241b6af4c4b2941:()=>le,__wbindgen_cast_4625c577ab2ec9ee:()=>we,__wbindgen_cast_9ae0607507abb057:()=>pe,__wbindgen_cast_d6cd19b81560fd6e:()=>ye,__wbindgen_init_externref_table:()=>me});module.exports=B(xe);var A=()=>{};A.prototype=A;let o;function N(e){o=e}let p=null;function a(){return(p===null||p.byteLength===0)&&(p=new Uint8Array(o.memory.buffer)),p}let y=new TextDecoder("utf-8",{ignoreBOM:!0,fatal:!0});y.decode();const U=2146435072;let S=0;function R(e,t){return S+=t,S>=U&&(y=new TextDecoder("utf-8",{ignoreBOM:!0,fatal:!0}),y.decode(),S=t),y.decode(a().subarray(e,e+t))}function m(e,t){return e=e>>>0,R(e,t)}let f=0;const g=new TextEncoder;"encodeInto"in g||(g.encodeInto=function(e,t){const n=g.encode(e);return t.set(n),{read:e.length,written:n.length}});function l(e,t,n){if(n===void 0){const i=g.encode(e),d=t(i.length,1)>>>0;return a().subarray(d,d+i.length).set(i),f=i.length,d}let _=e.length,r=t(_,1)>>>0;const s=a();let c=0;for(;c<_;c++){const i=e.charCodeAt(c);if(i>127)break;s[r+c]=i}if(c!==_){c!==0&&(e=e.slice(c)),r=n(r,_,_=c+e.length*3,1)>>>0;const i=a().subarray(r+c,r+_),d=g.encodeInto(e,i);c+=d.written,r=n(r,_,c,1)>>>0}return f=c,r}let b=null;function u(){return(b===null||b.buffer.detached===!0||b.buffer.detached===void 0&&b.buffer!==o.memory.buffer)&&(b=new DataView(o.memory.buffer)),b}function x(e){return e==null}function I(e){const t=typeof e;if(t=="number"||t=="boolean"||e==null)return`${e}`;if(t=="string")return`"${e}"`;if(t=="symbol"){const r=e.description;return r==null?"Symbol":`Symbol(${r})`}if(t=="function"){const r=e.name;return typeof r=="string"&&r.length>0?`Function(${r})`:"Function"}if(Array.isArray(e)){const r=e.length;let s="[";r>0&&(s+=I(e[0]));for(let c=1;c<r;c++)s+=", "+I(e[c]);return s+="]",s}const n=/\[object ([^\]]+)\]/.exec(toString.call(e));let _;if(n&&n.length>1)_=n[1];else return toString.call(e);if(_=="Object")try{return"Object("+JSON.stringify(e)+")"}catch{return"Object"}return e instanceof Error?`${e.name}: ${e.message}
|
||||||
|
${e.stack}`:_}function $(e,t){return e=e>>>0,a().subarray(e/1,e/1+t)}function w(e){const t=o.__wbindgen_externrefs.get(e);return o.__externref_table_dealloc(e),t}const E=typeof FinalizationRegistry>"u"?{register:()=>{},unregister:()=>{}}:new FinalizationRegistry(e=>o.__wbg_querycompiler_free(e>>>0,1));class F{__destroy_into_raw(){const t=this.__wbg_ptr;return this.__wbg_ptr=0,E.unregister(this),t}free(){const t=this.__destroy_into_raw();o.__wbg_querycompiler_free(t,0)}compileBatch(t){const n=l(t,o.__wbindgen_malloc,o.__wbindgen_realloc),_=f,r=o.querycompiler_compileBatch(this.__wbg_ptr,n,_);if(r[2])throw w(r[1]);return w(r[0])}constructor(t){const n=o.querycompiler_new(t);if(n[2])throw w(n[1]);return this.__wbg_ptr=n[0]>>>0,E.register(this,this.__wbg_ptr,this),this}compile(t){const n=l(t,o.__wbindgen_malloc,o.__wbindgen_realloc),_=f,r=o.querycompiler_compile(this.__wbg_ptr,n,_);if(r[2])throw w(r[1]);return w(r[0])}}Symbol.dispose&&(F.prototype[Symbol.dispose]=F.prototype.free);function q(e,t){return Error(m(e,t))}function C(e){return Number(e)}function k(e,t){const n=String(t),_=l(n,o.__wbindgen_malloc,o.__wbindgen_realloc),r=f;u().setInt32(e+4*1,r,!0),u().setInt32(e+4*0,_,!0)}function W(e){const t=e,n=typeof t=="boolean"?t:void 0;return x(n)?16777215:n?1:0}function V(e,t){const n=I(t),_=l(n,o.__wbindgen_malloc,o.__wbindgen_realloc),r=f;u().setInt32(e+4*1,r,!0),u().setInt32(e+4*0,_,!0)}function z(e,t){return e in t}function L(e){const t=e;return typeof t=="object"&&t!==null}function P(e){return typeof e=="string"}function Q(e){return e===void 0}function Y(e,t){return e==t}function G(e,t){const n=t,_=typeof n=="number"?n:void 0;u().setFloat64(e+8*1,x(_)?0:_,!0),u().setInt32(e+4*0,!x(_),!0)}function J(e,t){const n=t,_=typeof n=="string"?n:void 0;var r=x(_)?0:l(_,o.__wbindgen_malloc,o.__wbindgen_realloc),s=f;u().setInt32(e+4*1,s,!0),u().setInt32(e+4*0,r,!0)}function X(e,t){throw new Error(m(e,t))}function H(e){return Object.entries(e)}function K(e){return e.getTime()}function Z(e,t){return e[t>>>0]}function v(e,t){return e[t]}function ee(e){let t;try{t=e instanceof ArrayBuffer}catch{t=!1}return t}function te(e){let t;try{t=e instanceof Uint8Array}catch{t=!1}return t}function ne(e){return Number.isSafeInteger(e)}function re(e){return e.length}function _e(e){return e.length}function oe(){return new Date}function ce(){return new Object}function ie(e){return new Uint8Array(e)}function se(){return new Map}function ue(){return new Array}function fe(e,t,n){Uint8Array.prototype.set.call($(e,t),n)}function be(e,t,n){e[t]=n}function de(e,t,n){return e.set(t,n)}function ae(e,t,n){e[t>>>0]=n}function ge(e,t){global.PRISMA_WASM_PANIC_REGISTRY.set_message(m(e,t))}function le(e,t){return m(e,t)}function we(e){return BigInt.asUintN(64,e)}function pe(e){return e}function ye(e){return e}function me(){const e=o.__wbindgen_externrefs,t=e.grow(4);e.set(0,void 0),e.set(t+0,void 0),e.set(t+1,null),e.set(t+2,!0),e.set(t+3,!1)}0&&(module.exports={QueryCompiler,__wbg_Error_e83987f665cf5504,__wbg_Number_bb48ca12f395cd08,__wbg_String_8f0eb39a4a4c2f66,__wbg___wbindgen_boolean_get_6d5a1ee65bab5f68,__wbg___wbindgen_debug_string_df47ffb5e35e6763,__wbg___wbindgen_in_bb933bd9e1b3bc0f,__wbg___wbindgen_is_object_c818261d21f283a4,__wbg___wbindgen_is_string_fbb76cb2940daafd,__wbg___wbindgen_is_undefined_2d472862bd29a478,__wbg___wbindgen_jsval_loose_eq_b664b38a2f582147,__wbg___wbindgen_number_get_a20bf9b85341449d,__wbg___wbindgen_string_get_e4f06c90489ad01b,__wbg___wbindgen_throw_b855445ff6a94295,__wbg_entries_e171b586f8f6bdbf,__wbg_getTime_14776bfb48a1bff9,__wbg_get_7bed016f185add81,__wbg_get_with_ref_key_1dc361bd10053bfe,__wbg_instanceof_ArrayBuffer_70beb1189ca63b38,__wbg_instanceof_Uint8Array_20c8e73002f7af98,__wbg_isSafeInteger_d216eda7911dde36,__wbg_length_69bca3cb64fc8748,__wbg_length_cdd215e10d9dd507,__wbg_new_0_f9740686d739025c,__wbg_new_1acc0b6eea89d040,__wbg_new_5a79be3ab53b8aa5,__wbg_new_68651c719dcda04e,__wbg_new_e17d9f43105b08be,__wbg_prototypesetcall_2a6620b6922694b2,__wbg_set_3f1d0b984ed272ed,__wbg_set_907fb406c34a251d,__wbg_set_c213c871859d6500,__wbg_set_message_82ae475bb413aa5c,__wbg_set_wasm,__wbindgen_cast_2241b6af4c4b2941,__wbindgen_cast_4625c577ab2ec9ee,__wbindgen_cast_9ae0607507abb057,__wbindgen_cast_d6cd19b81560fd6e,__wbindgen_init_externref_table});
|
||||||
BIN
src/generated/prisma.js/query_compiler_fast_bg.wasm
Normal file
BIN
src/generated/prisma.js/query_compiler_fast_bg.wasm
Normal file
Binary file not shown.
File diff suppressed because one or more lines are too long
3386
src/generated/prisma.js/runtime/client.d.ts
vendored
Normal file
3386
src/generated/prisma.js/runtime/client.d.ts
vendored
Normal file
File diff suppressed because it is too large
Load Diff
86
src/generated/prisma.js/runtime/client.js
Normal file
86
src/generated/prisma.js/runtime/client.js
Normal file
File diff suppressed because one or more lines are too long
90
src/generated/prisma.js/runtime/index-browser.d.ts
vendored
Normal file
90
src/generated/prisma.js/runtime/index-browser.d.ts
vendored
Normal file
@@ -0,0 +1,90 @@
|
|||||||
|
import { AnyNull } from '@prisma/client-runtime-utils';
|
||||||
|
import { DbNull } from '@prisma/client-runtime-utils';
|
||||||
|
import { Decimal } from '@prisma/client-runtime-utils';
|
||||||
|
import { isAnyNull } from '@prisma/client-runtime-utils';
|
||||||
|
import { isDbNull } from '@prisma/client-runtime-utils';
|
||||||
|
import { isJsonNull } from '@prisma/client-runtime-utils';
|
||||||
|
import { isObjectEnumValue } from '@prisma/client-runtime-utils';
|
||||||
|
import { JsonNull } from '@prisma/client-runtime-utils';
|
||||||
|
import { NullTypes } from '@prisma/client-runtime-utils';
|
||||||
|
|
||||||
|
export { AnyNull }
|
||||||
|
|
||||||
|
declare type Args<T, F extends Operation> = T extends {
|
||||||
|
[K: symbol]: {
|
||||||
|
types: {
|
||||||
|
operations: {
|
||||||
|
[K in F]: {
|
||||||
|
args: any;
|
||||||
|
};
|
||||||
|
};
|
||||||
|
};
|
||||||
|
};
|
||||||
|
} ? T[symbol]['types']['operations'][F]['args'] : any;
|
||||||
|
|
||||||
|
export { DbNull }
|
||||||
|
|
||||||
|
export { Decimal }
|
||||||
|
|
||||||
|
declare type Exact<A, W> = (A extends unknown ? (W extends A ? {
|
||||||
|
[K in keyof A]: Exact<A[K], W[K]>;
|
||||||
|
} : W) : never) | (A extends Narrowable ? A : never);
|
||||||
|
|
||||||
|
export declare function getRuntime(): GetRuntimeOutput;
|
||||||
|
|
||||||
|
declare type GetRuntimeOutput = {
|
||||||
|
id: RuntimeName;
|
||||||
|
prettyName: string;
|
||||||
|
isEdge: boolean;
|
||||||
|
};
|
||||||
|
|
||||||
|
export { isAnyNull }
|
||||||
|
|
||||||
|
export { isDbNull }
|
||||||
|
|
||||||
|
export { isJsonNull }
|
||||||
|
|
||||||
|
export { isObjectEnumValue }
|
||||||
|
|
||||||
|
export { JsonNull }
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Generates more strict variant of an enum which, unlike regular enum,
|
||||||
|
* throws on non-existing property access. This can be useful in following situations:
|
||||||
|
* - we have an API, that accepts both `undefined` and `SomeEnumType` as an input
|
||||||
|
* - enum values are generated dynamically from DMMF.
|
||||||
|
*
|
||||||
|
* In that case, if using normal enums and no compile-time typechecking, using non-existing property
|
||||||
|
* will result in `undefined` value being used, which will be accepted. Using strict enum
|
||||||
|
* in this case will help to have a runtime exception, telling you that you are probably doing something wrong.
|
||||||
|
*
|
||||||
|
* Note: if you need to check for existence of a value in the enum you can still use either
|
||||||
|
* `in` operator or `hasOwnProperty` function.
|
||||||
|
*
|
||||||
|
* @param definition
|
||||||
|
* @returns
|
||||||
|
*/
|
||||||
|
export declare function makeStrictEnum<T extends Record<PropertyKey, string | number>>(definition: T): T;
|
||||||
|
|
||||||
|
declare type Narrowable = string | number | bigint | boolean | [];
|
||||||
|
|
||||||
|
export { NullTypes }
|
||||||
|
|
||||||
|
declare type Operation = 'findFirst' | 'findFirstOrThrow' | 'findUnique' | 'findUniqueOrThrow' | 'findMany' | 'create' | 'createMany' | 'createManyAndReturn' | 'update' | 'updateMany' | 'updateManyAndReturn' | 'upsert' | 'delete' | 'deleteMany' | 'aggregate' | 'count' | 'groupBy' | '$queryRaw' | '$executeRaw' | '$queryRawUnsafe' | '$executeRawUnsafe' | 'findRaw' | 'aggregateRaw' | '$runCommandRaw';
|
||||||
|
|
||||||
|
declare namespace Public {
|
||||||
|
export {
|
||||||
|
validator
|
||||||
|
}
|
||||||
|
}
|
||||||
|
export { Public }
|
||||||
|
|
||||||
|
declare type RuntimeName = 'workerd' | 'deno' | 'netlify' | 'node' | 'bun' | 'edge-light' | '';
|
||||||
|
|
||||||
|
declare function validator<V>(): <S>(select: Exact<S, V>) => S;
|
||||||
|
|
||||||
|
declare function validator<C, M extends Exclude<keyof C, `$${string}`>, O extends keyof C[M] & Operation>(client: C, model: M, operation: O): <S>(select: Exact<S, Args<C[M], O>>) => S;
|
||||||
|
|
||||||
|
declare function validator<C, M extends Exclude<keyof C, `$${string}`>, O extends keyof C[M] & Operation, P extends keyof Args<C[M], O>>(client: C, model: M, operation: O, prop: P): <S>(select: Exact<S, Args<C[M], O>[P]>) => S;
|
||||||
|
|
||||||
|
export { }
|
||||||
6
src/generated/prisma.js/runtime/index-browser.js
Normal file
6
src/generated/prisma.js/runtime/index-browser.js
Normal file
@@ -0,0 +1,6 @@
|
|||||||
|
|
||||||
|
/* !!! This is code generated by Prisma. Do not edit directly. !!!
|
||||||
|
/* eslint-disable */
|
||||||
|
// biome-ignore-all lint: generated file
|
||||||
|
"use strict";var s=Object.defineProperty;var g=Object.getOwnPropertyDescriptor;var p=Object.getOwnPropertyNames;var f=Object.prototype.hasOwnProperty;var a=(e,t)=>{for(var n in t)s(e,n,{get:t[n],enumerable:!0})},y=(e,t,n,r)=>{if(t&&typeof t=="object"||typeof t=="function")for(let i of p(t))!f.call(e,i)&&i!==n&&s(e,i,{get:()=>t[i],enumerable:!(r=g(t,i))||r.enumerable});return e};var x=e=>y(s({},"__esModule",{value:!0}),e);var M={};a(M,{AnyNull:()=>o.AnyNull,DbNull:()=>o.DbNull,Decimal:()=>m.Decimal,JsonNull:()=>o.JsonNull,NullTypes:()=>o.NullTypes,Public:()=>l,getRuntime:()=>c,isAnyNull:()=>o.isAnyNull,isDbNull:()=>o.isDbNull,isJsonNull:()=>o.isJsonNull,isObjectEnumValue:()=>o.isObjectEnumValue,makeStrictEnum:()=>u});module.exports=x(M);var l={};a(l,{validator:()=>d});function d(...e){return t=>t}var b=new Set(["toJSON","$$typeof","asymmetricMatch",Symbol.iterator,Symbol.toStringTag,Symbol.isConcatSpreadable,Symbol.toPrimitive]);function u(e){return new Proxy(e,{get(t,n){if(n in t)return t[n];if(!b.has(n))throw new TypeError("Invalid enum value: ".concat(String(n)))}})}var N=()=>{var e,t;return((t=(e=globalThis.process)==null?void 0:e.release)==null?void 0:t.name)==="node"},E=()=>{var e,t;return!!globalThis.Bun||!!((t=(e=globalThis.process)==null?void 0:e.versions)!=null&&t.bun)},S=()=>!!globalThis.Deno,R=()=>typeof globalThis.Netlify=="object",h=()=>typeof globalThis.EdgeRuntime=="object",C=()=>{var e;return((e=globalThis.navigator)==null?void 0:e.userAgent)==="Cloudflare-Workers"};function k(){var n;return(n=[[R,"netlify"],[h,"edge-light"],[C,"workerd"],[S,"deno"],[E,"bun"],[N,"node"]].flatMap(r=>r[0]()?[r[1]]:[]).at(0))!=null?n:""}var O={node:"Node.js",workerd:"Cloudflare Workers",deno:"Deno and Deno Deploy",netlify:"Netlify Edge Functions","edge-light":"Edge Runtime (Vercel Edge Functions, Vercel Edge Middleware, Next.js (Pages Router) Edge API Routes, Next.js (App Router) Edge Route Handlers or Next.js Middleware)"};function c(){let e=k();return{id:e,prettyName:O[e]||e,isEdge:["workerd","deno","netlify","edge-light"].includes(e)}}var o=require("@prisma/client-runtime-utils"),m=require("@prisma/client-runtime-utils");0&&(module.exports={AnyNull,DbNull,Decimal,JsonNull,NullTypes,Public,getRuntime,isAnyNull,isDbNull,isJsonNull,isObjectEnumValue,makeStrictEnum});
|
||||||
|
//# sourceMappingURL=index-browser.js.map
|
||||||
76
src/generated/prisma.js/runtime/wasm-compiler-edge.js
Normal file
76
src/generated/prisma.js/runtime/wasm-compiler-edge.js
Normal file
File diff suppressed because one or more lines are too long
71
src/generated/prisma.js/schema.prisma
Normal file
71
src/generated/prisma.js/schema.prisma
Normal file
@@ -0,0 +1,71 @@
|
|||||||
|
generator client {
|
||||||
|
provider = "prisma-client-js"
|
||||||
|
output = "../src/generated/prisma.js"
|
||||||
|
}
|
||||||
|
|
||||||
|
datasource db {
|
||||||
|
provider = "postgresql"
|
||||||
|
}
|
||||||
|
|
||||||
|
model User {
|
||||||
|
id Int @id @default(autoincrement())
|
||||||
|
username String @unique
|
||||||
|
password String
|
||||||
|
ideas Idea[]
|
||||||
|
projects Project[]
|
||||||
|
|
||||||
|
@@map("users")
|
||||||
|
}
|
||||||
|
|
||||||
|
model Idea {
|
||||||
|
id Int @id @default(autoincrement())
|
||||||
|
name String
|
||||||
|
description String
|
||||||
|
date_created DateTime @default(now()) @map("date_created")
|
||||||
|
user User @relation(fields: [userId], references: [id], onDelete: Cascade)
|
||||||
|
userId Int @map("user_id")
|
||||||
|
|
||||||
|
@@index([userId])
|
||||||
|
@@map("ideas")
|
||||||
|
}
|
||||||
|
|
||||||
|
model Project {
|
||||||
|
id Int @id @default(autoincrement())
|
||||||
|
name String
|
||||||
|
description String
|
||||||
|
date_created DateTime @default(now()) @map("date_created")
|
||||||
|
user User @relation(fields: [userId], references: [id], onDelete: Cascade)
|
||||||
|
userId Int @map("user_id")
|
||||||
|
materials Material[]
|
||||||
|
files File[]
|
||||||
|
|
||||||
|
@@index([userId])
|
||||||
|
@@map("projects")
|
||||||
|
}
|
||||||
|
|
||||||
|
model Material {
|
||||||
|
id Int @id @default(autoincrement())
|
||||||
|
name String
|
||||||
|
description String
|
||||||
|
source String
|
||||||
|
author String
|
||||||
|
text String
|
||||||
|
project Project @relation(fields: [projectId], references: [id], onDelete: Cascade)
|
||||||
|
projectId Int @map("project_id")
|
||||||
|
|
||||||
|
@@index([projectId])
|
||||||
|
@@map("materials")
|
||||||
|
}
|
||||||
|
|
||||||
|
model File {
|
||||||
|
id Int @id @default(autoincrement())
|
||||||
|
name String
|
||||||
|
file Bytes
|
||||||
|
size Int
|
||||||
|
mimeType String
|
||||||
|
project Project @relation(fields: [projectId], references: [id], onDelete: Cascade)
|
||||||
|
projectId Int @map("project_id")
|
||||||
|
|
||||||
|
@@index([projectId])
|
||||||
|
@@map("files")
|
||||||
|
}
|
||||||
5
src/generated/prisma.js/wasm-edge-light-loader.mjs
Normal file
5
src/generated/prisma.js/wasm-edge-light-loader.mjs
Normal file
@@ -0,0 +1,5 @@
|
|||||||
|
|
||||||
|
/* !!! This is code generated by Prisma. Do not edit directly. !!!
|
||||||
|
/* eslint-disable */
|
||||||
|
// biome-ignore-all lint: generated file
|
||||||
|
export default import('./query_compiler_fast_bg.wasm?module')
|
||||||
5
src/generated/prisma.js/wasm-worker-loader.mjs
Normal file
5
src/generated/prisma.js/wasm-worker-loader.mjs
Normal file
@@ -0,0 +1,5 @@
|
|||||||
|
|
||||||
|
/* !!! This is code generated by Prisma. Do not edit directly. !!!
|
||||||
|
/* eslint-disable */
|
||||||
|
// biome-ignore-all lint: generated file
|
||||||
|
export default import('./query_compiler_fast_bg.wasm')
|
||||||
38
src/middleware/authenticate.js
Normal file
38
src/middleware/authenticate.js
Normal file
@@ -0,0 +1,38 @@
|
|||||||
|
import jwt from 'jsonwebtoken';
|
||||||
|
|
||||||
|
const JWT_SECRET = process.env.JWT_SECRET;
|
||||||
|
if (!JWT_SECRET) throw new Error('JWT_SECRET environment variable is not set');
|
||||||
|
|
||||||
|
export function authenticate(req, res, next) {
|
||||||
|
if (process.env.NODE_ENV === 'development') {
|
||||||
|
console.log('Auth middleware reached!');
|
||||||
|
}
|
||||||
|
|
||||||
|
const authHeader = req.headers.authorization;
|
||||||
|
|
||||||
|
if (process.env.NODE_ENV === 'development') {
|
||||||
|
console.log('Header found:', authHeader);
|
||||||
|
}
|
||||||
|
|
||||||
|
const err = new Error('Not authenticated. Please provide a valid token.');
|
||||||
|
err.status = 401;
|
||||||
|
|
||||||
|
if (!authHeader || !authHeader.startsWith('Bearer ')) {
|
||||||
|
return next(err);
|
||||||
|
}
|
||||||
|
|
||||||
|
const token = authHeader.split(' ')[1];
|
||||||
|
|
||||||
|
try {
|
||||||
|
const payload = jwt.verify(token, JWT_SECRET);
|
||||||
|
|
||||||
|
if (process.env.NODE_ENV === 'development') {
|
||||||
|
console.log('Decoded JWT Payload:', payload);
|
||||||
|
}
|
||||||
|
|
||||||
|
req.user = { id: payload.id, role: payload.role };
|
||||||
|
next();
|
||||||
|
} catch (error) {
|
||||||
|
return next(err);
|
||||||
|
}
|
||||||
|
}
|
||||||
17
src/middleware/handleValidationErrors.js
Normal file
17
src/middleware/handleValidationErrors.js
Normal file
@@ -0,0 +1,17 @@
|
|||||||
|
import { validationResult } from 'express-validator';
|
||||||
|
|
||||||
|
export function handleValidationErrors(req, res, next) {
|
||||||
|
const errors = validationResult(req);
|
||||||
|
if (!errors.isEmpty()) {
|
||||||
|
if (process.env.NODE_ENV === 'development') {
|
||||||
|
console.log('Validation errors:', errors.array());
|
||||||
|
}
|
||||||
|
return res.status(400).json({
|
||||||
|
errors: errors.array().map((err) => ({
|
||||||
|
field: err.path,
|
||||||
|
message: err.msg,
|
||||||
|
})),
|
||||||
|
});
|
||||||
|
}
|
||||||
|
next();
|
||||||
|
}
|
||||||
34
src/middleware/validateAuth.js
Normal file
34
src/middleware/validateAuth.js
Normal file
@@ -0,0 +1,34 @@
|
|||||||
|
import { body } from 'express-validator';
|
||||||
|
import { handleValidationErrors } from './handleValidationErrors.js';
|
||||||
|
|
||||||
|
export const validateSignUp = [
|
||||||
|
body('username')
|
||||||
|
.exists({ checkFalsy: true })
|
||||||
|
.withMessage('Username is required')
|
||||||
|
.bail()
|
||||||
|
.trim()
|
||||||
|
.escape()
|
||||||
|
.isLength({ min: 3 })
|
||||||
|
.withMessage('Username must be at least 3 characters'),
|
||||||
|
|
||||||
|
body('password')
|
||||||
|
.exists({ checkFalsy: true })
|
||||||
|
.withMessage('Password is required')
|
||||||
|
.bail()
|
||||||
|
.isLength({ min: 8 })
|
||||||
|
.withMessage('Password must be at least 8 characters'),
|
||||||
|
|
||||||
|
handleValidationErrors,
|
||||||
|
];
|
||||||
|
|
||||||
|
export const validateLogIn = [
|
||||||
|
body('username')
|
||||||
|
.exists({ checkFalsy: true })
|
||||||
|
.withMessage('Username is required'),
|
||||||
|
|
||||||
|
body('password')
|
||||||
|
.exists({ checkFalsy: true })
|
||||||
|
.withMessage('Password is required'),
|
||||||
|
|
||||||
|
handleValidationErrors,
|
||||||
|
];
|
||||||
93
src/middleware/validateIdeas.js
Normal file
93
src/middleware/validateIdeas.js
Normal file
@@ -0,0 +1,93 @@
|
|||||||
|
import { param, query, body, oneOf } from 'express-validator';
|
||||||
|
import { handleValidationErrors } from './handleValidationErrors.js';
|
||||||
|
|
||||||
|
export const validateGetAllQuery = [
|
||||||
|
query('search')
|
||||||
|
.optional()
|
||||||
|
.trim()
|
||||||
|
.isString()
|
||||||
|
.withMessage('Search must be a string'),
|
||||||
|
|
||||||
|
query('sortBy')
|
||||||
|
.optional()
|
||||||
|
.isIn(['name', 'date_created'])
|
||||||
|
.withMessage('sortBy must be one of: name, date_created'),
|
||||||
|
|
||||||
|
query('order')
|
||||||
|
.optional()
|
||||||
|
.isIn(['asc', 'desc'])
|
||||||
|
.withMessage('order must be asc or desc'),
|
||||||
|
|
||||||
|
query('offset')
|
||||||
|
.optional()
|
||||||
|
.isInt({ min: 0 })
|
||||||
|
.withMessage('offset must be a non-negative integer'),
|
||||||
|
|
||||||
|
query('limit')
|
||||||
|
.optional()
|
||||||
|
.isInt({ min: 1, max: 100 })
|
||||||
|
.withMessage('limit must be an integer between 1 and 100'),
|
||||||
|
|
||||||
|
handleValidationErrors,
|
||||||
|
];
|
||||||
|
|
||||||
|
export const validateId = [
|
||||||
|
param('id')
|
||||||
|
.trim()
|
||||||
|
.escape()
|
||||||
|
.isInt({ min: 1 })
|
||||||
|
.withMessage('Id must be a positive integer'),
|
||||||
|
|
||||||
|
handleValidationErrors,
|
||||||
|
];
|
||||||
|
|
||||||
|
export const validateCreateIdea = [
|
||||||
|
body('name')
|
||||||
|
.exists({ checkFalsy: true })
|
||||||
|
.withMessage('Name is required')
|
||||||
|
.bail()
|
||||||
|
.trim()
|
||||||
|
.escape()
|
||||||
|
.isLength({ min: 3 })
|
||||||
|
.withMessage('Name must be at least 3 characters'),
|
||||||
|
|
||||||
|
body('description')
|
||||||
|
.exists({ checkFalsy: true })
|
||||||
|
.withMessage('Description is required')
|
||||||
|
.bail()
|
||||||
|
.trim()
|
||||||
|
.escape()
|
||||||
|
.isString()
|
||||||
|
.withMessage('Description must be a string'),
|
||||||
|
|
||||||
|
handleValidationErrors,
|
||||||
|
];
|
||||||
|
|
||||||
|
export const validateUpdateIdea = [
|
||||||
|
oneOf(
|
||||||
|
[
|
||||||
|
body('name').exists({ checkFalsy: true }),
|
||||||
|
body('description').exists({ checkFalsy: true }),
|
||||||
|
],
|
||||||
|
{ message: 'At least one field (name, description) must be provided' }
|
||||||
|
),
|
||||||
|
|
||||||
|
body('name')
|
||||||
|
.optional()
|
||||||
|
.trim()
|
||||||
|
.escape()
|
||||||
|
.isString()
|
||||||
|
.withMessage('Name must be a string')
|
||||||
|
.bail()
|
||||||
|
.isLength({ min: 3 })
|
||||||
|
.withMessage('Name must be at least 3 characters'),
|
||||||
|
|
||||||
|
body('description')
|
||||||
|
.optional()
|
||||||
|
.trim()
|
||||||
|
.escape()
|
||||||
|
.isString()
|
||||||
|
.withMessage('Description must be a string'),
|
||||||
|
|
||||||
|
handleValidationErrors,
|
||||||
|
];
|
||||||
107
src/middleware/validateMaterials.js
Normal file
107
src/middleware/validateMaterials.js
Normal file
@@ -0,0 +1,107 @@
|
|||||||
|
import { param, query, body } from 'express-validator';
|
||||||
|
import { handleValidationErrors } from './handleValidationErrors.js';
|
||||||
|
|
||||||
|
export const validateGetAllQuery = [
|
||||||
|
query('search')
|
||||||
|
.optional()
|
||||||
|
.trim()
|
||||||
|
.isString()
|
||||||
|
.withMessage('Search must be a string'),
|
||||||
|
|
||||||
|
query('sortBy')
|
||||||
|
.optional()
|
||||||
|
.isIn(['name', 'id'])
|
||||||
|
.withMessage('sortBy must be one of: name, id'),
|
||||||
|
|
||||||
|
query('order')
|
||||||
|
.optional()
|
||||||
|
.isIn(['asc', 'desc'])
|
||||||
|
.withMessage('order must be asc or desc'),
|
||||||
|
|
||||||
|
query('offset')
|
||||||
|
.optional()
|
||||||
|
.isInt({ min: 0 })
|
||||||
|
.withMessage('offset must be a non-negative integer'),
|
||||||
|
|
||||||
|
query('limit')
|
||||||
|
.optional()
|
||||||
|
.isInt({ min: 1, max: 100 })
|
||||||
|
.withMessage('limit must be an integer between 1 and 100'),
|
||||||
|
|
||||||
|
handleValidationErrors,
|
||||||
|
];
|
||||||
|
|
||||||
|
export const validateId = [
|
||||||
|
param('id')
|
||||||
|
.trim()
|
||||||
|
.escape()
|
||||||
|
.isInt({ min: 1 })
|
||||||
|
.withMessage('Id must be a positive integer'),
|
||||||
|
handleValidationErrors,
|
||||||
|
];
|
||||||
|
|
||||||
|
export const validateProjectId = [
|
||||||
|
param('projectId')
|
||||||
|
.trim()
|
||||||
|
.escape()
|
||||||
|
.isInt({ min: 1 })
|
||||||
|
.withMessage('ProjectId must be a positive integer'),
|
||||||
|
|
||||||
|
handleValidationErrors,
|
||||||
|
];
|
||||||
|
|
||||||
|
export const validateCreateMaterial = [
|
||||||
|
body('projectId')
|
||||||
|
.exists({ checkFalsy: true })
|
||||||
|
.withMessage('ProjectId is required')
|
||||||
|
.bail()
|
||||||
|
.isInt({ min: 1 })
|
||||||
|
.withMessage('ProjectId must be a positive integer'),
|
||||||
|
|
||||||
|
body('name')
|
||||||
|
.exists({ checkFalsy: true })
|
||||||
|
.withMessage('Name is required')
|
||||||
|
.bail()
|
||||||
|
.trim()
|
||||||
|
.escape()
|
||||||
|
.isLength({ min: 3 })
|
||||||
|
.withMessage('Name must be at least 3 characters'),
|
||||||
|
|
||||||
|
body('description')
|
||||||
|
.exists({ checkFalsy: true })
|
||||||
|
.withMessage('Description is required')
|
||||||
|
.bail()
|
||||||
|
.trim()
|
||||||
|
.escape()
|
||||||
|
.isString()
|
||||||
|
.withMessage('Description must be a string'),
|
||||||
|
|
||||||
|
body('source')
|
||||||
|
.exists({ checkFalsy: true })
|
||||||
|
.withMessage('Source is required')
|
||||||
|
.bail()
|
||||||
|
.trim()
|
||||||
|
.escape()
|
||||||
|
.isString()
|
||||||
|
.withMessage('Source must be a string'),
|
||||||
|
|
||||||
|
body('author')
|
||||||
|
.exists({ checkFalsy: true })
|
||||||
|
.withMessage('Author is required')
|
||||||
|
.bail()
|
||||||
|
.trim()
|
||||||
|
.escape()
|
||||||
|
.isString()
|
||||||
|
.withMessage('Author must be a string'),
|
||||||
|
|
||||||
|
body('text')
|
||||||
|
.exists({ checkFalsy: true })
|
||||||
|
.withMessage('Text is required')
|
||||||
|
.bail()
|
||||||
|
.trim()
|
||||||
|
.escape()
|
||||||
|
.isString()
|
||||||
|
.withMessage('Text must be a string'),
|
||||||
|
|
||||||
|
handleValidationErrors,
|
||||||
|
];
|
||||||
93
src/middleware/validateProjects.js
Normal file
93
src/middleware/validateProjects.js
Normal file
@@ -0,0 +1,93 @@
|
|||||||
|
import { param, query, body, oneOf } from 'express-validator';
|
||||||
|
import { handleValidationErrors } from './handleValidationErrors.js';
|
||||||
|
|
||||||
|
export const validateGetAllQuery = [
|
||||||
|
query('search')
|
||||||
|
.optional()
|
||||||
|
.trim()
|
||||||
|
.isString()
|
||||||
|
.withMessage('Search must be a string'),
|
||||||
|
|
||||||
|
query('sortBy')
|
||||||
|
.optional()
|
||||||
|
.isIn(['name', 'date_created'])
|
||||||
|
.withMessage('sortBy must be one of: name, date_created'),
|
||||||
|
|
||||||
|
query('order')
|
||||||
|
.optional()
|
||||||
|
.isIn(['asc', 'desc'])
|
||||||
|
.withMessage('order must be asc or desc'),
|
||||||
|
|
||||||
|
query('offset')
|
||||||
|
.optional()
|
||||||
|
.isInt({ min: 0 })
|
||||||
|
.withMessage('offset must be a non-negative integer'),
|
||||||
|
|
||||||
|
query('limit')
|
||||||
|
.optional()
|
||||||
|
.isInt({ min: 1, max: 100 })
|
||||||
|
.withMessage('limit must be an integer between 1 and 100'),
|
||||||
|
|
||||||
|
handleValidationErrors,
|
||||||
|
];
|
||||||
|
|
||||||
|
export const validateId = [
|
||||||
|
param('id')
|
||||||
|
.trim()
|
||||||
|
.escape()
|
||||||
|
.isInt({ min: 1 })
|
||||||
|
.withMessage('Id must be a positive integer'),
|
||||||
|
|
||||||
|
handleValidationErrors,
|
||||||
|
];
|
||||||
|
|
||||||
|
export const validateCreateProject = [
|
||||||
|
body('name')
|
||||||
|
.exists({ checkFalsy: true })
|
||||||
|
.withMessage('Name is required')
|
||||||
|
.bail()
|
||||||
|
.trim()
|
||||||
|
.escape()
|
||||||
|
.isLength({ min: 3 })
|
||||||
|
.withMessage('Name must be at least 3 characters'),
|
||||||
|
|
||||||
|
body('description')
|
||||||
|
.exists({ checkFalsy: true })
|
||||||
|
.withMessage('Description is required')
|
||||||
|
.bail()
|
||||||
|
.trim()
|
||||||
|
.escape()
|
||||||
|
.isString()
|
||||||
|
.withMessage('Description must be a string'),
|
||||||
|
|
||||||
|
handleValidationErrors,
|
||||||
|
];
|
||||||
|
|
||||||
|
export const validateUpdateProject = [
|
||||||
|
oneOf(
|
||||||
|
[
|
||||||
|
body('name').exists({ checkFalsy: true }),
|
||||||
|
body('description').exists({ checkFalsy: true }),
|
||||||
|
],
|
||||||
|
{ message: 'At least one field (name, description) must be provided' }
|
||||||
|
),
|
||||||
|
|
||||||
|
body('name')
|
||||||
|
.optional()
|
||||||
|
.trim()
|
||||||
|
.escape()
|
||||||
|
.isString()
|
||||||
|
.withMessage('Name must be a string')
|
||||||
|
.bail()
|
||||||
|
.isLength({ min: 3 })
|
||||||
|
.withMessage('Name must be at least 3 characters'),
|
||||||
|
|
||||||
|
body('description')
|
||||||
|
.optional()
|
||||||
|
.trim()
|
||||||
|
.escape()
|
||||||
|
.isString()
|
||||||
|
.withMessage('Description must be a string'),
|
||||||
|
|
||||||
|
handleValidationErrors,
|
||||||
|
];
|
||||||
139
src/repositories/ideasRepo.js
Normal file
139
src/repositories/ideasRepo.js
Normal file
@@ -0,0 +1,139 @@
|
|||||||
|
import prisma from '../config/db.js';
|
||||||
|
|
||||||
|
const ALLOWED_SORT_FIELDS = ['name', 'date_created'];
|
||||||
|
|
||||||
|
export async function getAll(
|
||||||
|
userId,
|
||||||
|
{
|
||||||
|
search = '',
|
||||||
|
sortBy = 'date_created',
|
||||||
|
order = 'desc',
|
||||||
|
offset = 0,
|
||||||
|
limit = 10,
|
||||||
|
} = {}
|
||||||
|
) {
|
||||||
|
if (process.env.NODE_ENV === 'development') {
|
||||||
|
console.log('ideasRepo.getAll() called for userId:', userId);
|
||||||
|
}
|
||||||
|
|
||||||
|
const safeSortBy = ALLOWED_SORT_FIELDS.includes(sortBy)
|
||||||
|
? sortBy
|
||||||
|
: 'date_created';
|
||||||
|
const safeOrder = order === 'asc' ? 'asc' : 'desc';
|
||||||
|
|
||||||
|
const ideas = await prisma.idea.findMany({
|
||||||
|
where: {
|
||||||
|
userId,
|
||||||
|
...(search ? { name: { contains: search, mode: 'insensitive' } } : {}),
|
||||||
|
},
|
||||||
|
orderBy: { [safeSortBy]: safeOrder },
|
||||||
|
skip: offset,
|
||||||
|
take: limit,
|
||||||
|
});
|
||||||
|
|
||||||
|
return ideas;
|
||||||
|
}
|
||||||
|
|
||||||
|
export async function getById(ideaId, userId) {
|
||||||
|
if (process.env.NODE_ENV === 'development') {
|
||||||
|
console.log(
|
||||||
|
'ideasRepo.getById() called for ideaId:',
|
||||||
|
ideaId,
|
||||||
|
'userId:',
|
||||||
|
userId
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
const idea = await prisma.idea.findUnique({
|
||||||
|
where: { id: ideaId },
|
||||||
|
});
|
||||||
|
|
||||||
|
if (idea && idea.userId !== userId) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
return idea;
|
||||||
|
}
|
||||||
|
|
||||||
|
export async function create(userId, ideaData) {
|
||||||
|
if (process.env.NODE_ENV === 'development') {
|
||||||
|
console.log('ideasRepo.create() called with data:', ideaData);
|
||||||
|
}
|
||||||
|
|
||||||
|
const newIdea = await prisma.idea.create({
|
||||||
|
data: {
|
||||||
|
...ideaData,
|
||||||
|
userId,
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
|
return newIdea;
|
||||||
|
}
|
||||||
|
|
||||||
|
export async function update(ideaId, userId, updatedData) {
|
||||||
|
if (process.env.NODE_ENV === 'development') {
|
||||||
|
console.log(
|
||||||
|
'ideasRepo.update() called for ideaId:',
|
||||||
|
ideaId,
|
||||||
|
'with data:',
|
||||||
|
updatedData
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
try {
|
||||||
|
const idea = await prisma.idea.findUnique({
|
||||||
|
where: { id: ideaId },
|
||||||
|
});
|
||||||
|
|
||||||
|
if (!idea || idea.userId !== userId) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
const updatedIdea = await prisma.idea.update({
|
||||||
|
where: { id: ideaId },
|
||||||
|
data: updatedData,
|
||||||
|
});
|
||||||
|
|
||||||
|
return updatedIdea;
|
||||||
|
} catch (error) {
|
||||||
|
if (error.code === 'P2025') return null;
|
||||||
|
throw error;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export async function remove(ideaId, userId) {
|
||||||
|
if (process.env.NODE_ENV === 'development') {
|
||||||
|
console.log('ideasRepo.remove() called for ideaId:', ideaId);
|
||||||
|
}
|
||||||
|
|
||||||
|
try {
|
||||||
|
const idea = await prisma.idea.findUnique({
|
||||||
|
where: { id: ideaId },
|
||||||
|
});
|
||||||
|
|
||||||
|
if (!idea || idea.userId !== userId) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
const deletedIdea = await prisma.idea.delete({
|
||||||
|
where: { id: ideaId },
|
||||||
|
});
|
||||||
|
|
||||||
|
return deletedIdea;
|
||||||
|
} catch (error) {
|
||||||
|
if (error.code === 'P2025') return null;
|
||||||
|
throw error;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export async function existsByName(userId, name) {
|
||||||
|
if (process.env.NODE_ENV === 'development') {
|
||||||
|
console.log('ideasRepo.existsByName() called for name:', name);
|
||||||
|
}
|
||||||
|
|
||||||
|
const idea = await prisma.idea.findFirst({
|
||||||
|
where: { userId, name },
|
||||||
|
});
|
||||||
|
|
||||||
|
return idea !== null;
|
||||||
|
}
|
||||||
142
src/repositories/materialsRepo.js
Normal file
142
src/repositories/materialsRepo.js
Normal file
@@ -0,0 +1,142 @@
|
|||||||
|
import prisma from '../config/db.js';
|
||||||
|
|
||||||
|
const ALLOWED_SORT_FIELDS = ['name', 'id'];
|
||||||
|
|
||||||
|
export async function getAll(
|
||||||
|
userId,
|
||||||
|
{ search = '', sortBy = 'id', order = 'desc', offset = 0, limit = 10 } = {}
|
||||||
|
) {
|
||||||
|
if (process.env.NODE_ENV === 'development') {
|
||||||
|
console.log('materialsRepo.getAll() called for userId:', userId);
|
||||||
|
}
|
||||||
|
|
||||||
|
const safeSortBy = ALLOWED_SORT_FIELDS.includes(sortBy) ? sortBy : 'id';
|
||||||
|
const safeOrder = order === 'asc' ? 'asc' : 'desc';
|
||||||
|
|
||||||
|
const materials = await prisma.material.findMany({
|
||||||
|
where: {
|
||||||
|
project: { userId },
|
||||||
|
...(search ? { name: { contains: search, mode: 'insensitive' } } : {}),
|
||||||
|
},
|
||||||
|
orderBy: { [safeSortBy]: safeOrder },
|
||||||
|
skip: offset,
|
||||||
|
take: limit,
|
||||||
|
});
|
||||||
|
|
||||||
|
return materials;
|
||||||
|
}
|
||||||
|
|
||||||
|
export async function getByProjectId(
|
||||||
|
projectId,
|
||||||
|
userId,
|
||||||
|
{ search = '', sortBy = 'id', order = 'desc', offset = 0, limit = 10 } = {}
|
||||||
|
) {
|
||||||
|
if (process.env.NODE_ENV === 'development') {
|
||||||
|
console.log(
|
||||||
|
'materialsRepo.getByProjectId() called for projectId:',
|
||||||
|
projectId,
|
||||||
|
'userId:',
|
||||||
|
userId
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
const safeSortBy = ALLOWED_SORT_FIELDS.includes(sortBy) ? sortBy : 'id';
|
||||||
|
const safeOrder = order === 'asc' ? 'asc' : 'desc';
|
||||||
|
|
||||||
|
const materials = await prisma.material.findMany({
|
||||||
|
where: {
|
||||||
|
projectId,
|
||||||
|
project: { userId },
|
||||||
|
...(search ? { name: { contains: search, mode: 'insensitive' } } : {}),
|
||||||
|
},
|
||||||
|
orderBy: { [safeSortBy]: safeOrder },
|
||||||
|
skip: offset,
|
||||||
|
take: limit,
|
||||||
|
});
|
||||||
|
|
||||||
|
return materials;
|
||||||
|
}
|
||||||
|
|
||||||
|
export async function getById(materialId, userId) {
|
||||||
|
if (process.env.NODE_ENV === 'development') {
|
||||||
|
console.log(
|
||||||
|
'materialsRepo.getById() called for materialId:',
|
||||||
|
materialId,
|
||||||
|
'userId:',
|
||||||
|
userId
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
const material = await prisma.material.findUnique({
|
||||||
|
where: { id: materialId },
|
||||||
|
include: { project: true },
|
||||||
|
});
|
||||||
|
|
||||||
|
if (material && material.project.userId !== userId) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
return material;
|
||||||
|
}
|
||||||
|
|
||||||
|
export async function create(userId, materialData) {
|
||||||
|
if (process.env.NODE_ENV === 'development') {
|
||||||
|
console.log('materialsRepo.create() called with data:', materialData);
|
||||||
|
}
|
||||||
|
|
||||||
|
try {
|
||||||
|
const project = await prisma.project.findUnique({
|
||||||
|
where: { id: materialData.projectId },
|
||||||
|
});
|
||||||
|
|
||||||
|
if (!project || project.userId !== userId) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
const newMaterial = await prisma.material.create({
|
||||||
|
data: materialData,
|
||||||
|
});
|
||||||
|
|
||||||
|
return newMaterial;
|
||||||
|
} catch (error) {
|
||||||
|
throw error;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export async function remove(materialId, userId) {
|
||||||
|
if (process.env.NODE_ENV === 'development') {
|
||||||
|
console.log('materialsRepo.remove() called for materialId:', materialId);
|
||||||
|
}
|
||||||
|
|
||||||
|
try {
|
||||||
|
const material = await prisma.material.findUnique({
|
||||||
|
where: { id: materialId },
|
||||||
|
include: { project: true },
|
||||||
|
});
|
||||||
|
|
||||||
|
if (!material || material.project.userId !== userId) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
const deletedMaterial = await prisma.material.delete({
|
||||||
|
where: { id: materialId },
|
||||||
|
});
|
||||||
|
|
||||||
|
return deletedMaterial;
|
||||||
|
} catch (error) {
|
||||||
|
if (error.code === 'P2025') return null;
|
||||||
|
throw error;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export async function existsByName(userId, name) {
|
||||||
|
if (process.env.NODE_ENV === 'development') {
|
||||||
|
console.log('materialsRepo.existsByName() called for name:', name);
|
||||||
|
}
|
||||||
|
|
||||||
|
const material = await prisma.material.findFirst({
|
||||||
|
where: { name, project: { userId } },
|
||||||
|
});
|
||||||
|
|
||||||
|
return material !== null;
|
||||||
|
}
|
||||||
242
src/repositories/projectsRepo.js
Normal file
242
src/repositories/projectsRepo.js
Normal file
@@ -0,0 +1,242 @@
|
|||||||
|
import prisma from '../config/db.js';
|
||||||
|
|
||||||
|
const ALLOWED_SORT_FIELDS = ['name', 'date_created'];
|
||||||
|
|
||||||
|
export async function getAll(
|
||||||
|
userId,
|
||||||
|
{
|
||||||
|
search = '',
|
||||||
|
sortBy = 'date_created',
|
||||||
|
order = 'desc',
|
||||||
|
offset = 0,
|
||||||
|
limit = 10,
|
||||||
|
} = {}
|
||||||
|
) {
|
||||||
|
if (process.env.NODE_ENV === 'development') {
|
||||||
|
console.log('projectsRepo.getAll() called for userId:', userId);
|
||||||
|
}
|
||||||
|
|
||||||
|
const safeSortBy = ALLOWED_SORT_FIELDS.includes(sortBy)
|
||||||
|
? sortBy
|
||||||
|
: 'date_created';
|
||||||
|
const safeOrder = order === 'asc' ? 'asc' : 'desc';
|
||||||
|
|
||||||
|
const projects = await prisma.project.findMany({
|
||||||
|
where: {
|
||||||
|
userId,
|
||||||
|
...(search ? { name: { contains: search, mode: 'insensitive' } } : {}),
|
||||||
|
},
|
||||||
|
orderBy: { [safeSortBy]: safeOrder },
|
||||||
|
skip: offset,
|
||||||
|
take: limit,
|
||||||
|
include: { files: true },
|
||||||
|
});
|
||||||
|
|
||||||
|
return projects;
|
||||||
|
}
|
||||||
|
|
||||||
|
export async function getById(projectId, userId) {
|
||||||
|
if (process.env.NODE_ENV === 'development') {
|
||||||
|
console.log(
|
||||||
|
'projectsRepo.getById() called for projectId:',
|
||||||
|
projectId,
|
||||||
|
'userId:',
|
||||||
|
userId
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
const project = await prisma.project.findUnique({
|
||||||
|
where: { id: projectId },
|
||||||
|
include: { files: true },
|
||||||
|
});
|
||||||
|
|
||||||
|
if (project && project.userId !== userId) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
return project;
|
||||||
|
}
|
||||||
|
|
||||||
|
export async function create(userId, projectData) {
|
||||||
|
if (process.env.NODE_ENV === 'development') {
|
||||||
|
console.log('projectsRepo.create() called with data:', projectData);
|
||||||
|
}
|
||||||
|
|
||||||
|
const newProject = await prisma.project.create({
|
||||||
|
data: { ...projectData, userId },
|
||||||
|
include: { files: true },
|
||||||
|
});
|
||||||
|
|
||||||
|
return newProject;
|
||||||
|
}
|
||||||
|
|
||||||
|
export async function update(projectId, userId, updatedData) {
|
||||||
|
if (process.env.NODE_ENV === 'development') {
|
||||||
|
console.log(
|
||||||
|
'projectsRepo.update() called for projectId:',
|
||||||
|
projectId,
|
||||||
|
'with data:',
|
||||||
|
updatedData
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
try {
|
||||||
|
const project = await prisma.project.findUnique({
|
||||||
|
where: { id: projectId },
|
||||||
|
});
|
||||||
|
|
||||||
|
if (!project || project.userId !== userId) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
const updatedProject = await prisma.project.update({
|
||||||
|
where: { id: projectId },
|
||||||
|
data: updatedData,
|
||||||
|
include: { files: true },
|
||||||
|
});
|
||||||
|
|
||||||
|
return updatedProject;
|
||||||
|
} catch (error) {
|
||||||
|
if (error.code === 'P2025') return null;
|
||||||
|
throw error;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export async function remove(projectId, userId) {
|
||||||
|
if (process.env.NODE_ENV === 'development') {
|
||||||
|
console.log('projectsRepo.remove() called for projectId:', projectId);
|
||||||
|
}
|
||||||
|
|
||||||
|
try {
|
||||||
|
const project = await prisma.project.findUnique({
|
||||||
|
where: { id: projectId },
|
||||||
|
});
|
||||||
|
|
||||||
|
if (!project || project.userId !== userId) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
const deletedProject = await prisma.project.delete({
|
||||||
|
where: { id: projectId },
|
||||||
|
include: { files: true },
|
||||||
|
});
|
||||||
|
|
||||||
|
return deletedProject;
|
||||||
|
} catch (error) {
|
||||||
|
if (error.code === 'P2025') return null;
|
||||||
|
throw error;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export async function existsByName(userId, name) {
|
||||||
|
if (process.env.NODE_ENV === 'development') {
|
||||||
|
console.log('projectsRepo.existsByName() called for name:', name);
|
||||||
|
}
|
||||||
|
|
||||||
|
const project = await prisma.project.findFirst({
|
||||||
|
where: { userId, name },
|
||||||
|
});
|
||||||
|
|
||||||
|
return project !== null;
|
||||||
|
}
|
||||||
|
|
||||||
|
export async function getFilesByProjectId(projectId, userId) {
|
||||||
|
if (process.env.NODE_ENV === 'development') {
|
||||||
|
console.log(
|
||||||
|
'projectsRepo.getFilesByProjectId() called for projectId:',
|
||||||
|
projectId,
|
||||||
|
'userId:',
|
||||||
|
userId
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
try {
|
||||||
|
const project = await prisma.project.findUnique({
|
||||||
|
where: { id: projectId },
|
||||||
|
});
|
||||||
|
|
||||||
|
if (!project || project.userId !== userId) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
const files = await prisma.file.findMany({
|
||||||
|
where: { projectId },
|
||||||
|
});
|
||||||
|
|
||||||
|
return files;
|
||||||
|
} catch (error) {
|
||||||
|
throw error;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export async function addFile(projectId, userId, fileData) {
|
||||||
|
if (process.env.NODE_ENV === 'development') {
|
||||||
|
console.log(
|
||||||
|
'projectsRepo.addFile() called for projectId:',
|
||||||
|
projectId,
|
||||||
|
'userId:',
|
||||||
|
userId,
|
||||||
|
'with data:',
|
||||||
|
fileData
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
const project = await prisma.project.findUnique({
|
||||||
|
where: { id: projectId },
|
||||||
|
});
|
||||||
|
|
||||||
|
if (!project || project.userId !== userId) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
const newFile = await prisma.file.create({
|
||||||
|
data: {
|
||||||
|
name: fileData.name,
|
||||||
|
file: fileData.file,
|
||||||
|
size: fileData.size,
|
||||||
|
mimeType: fileData.mimeType,
|
||||||
|
projectId: projectId
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
return newFile;
|
||||||
|
}
|
||||||
|
|
||||||
|
export async function deleteFile(fileId, userId) {
|
||||||
|
if (process.env.NODE_ENV === 'development') {
|
||||||
|
console.log('projectsRepo.deleteFile() called for fileId:', fileId, 'userId:', userId);
|
||||||
|
}
|
||||||
|
|
||||||
|
const file = await prisma.file.findUnique({
|
||||||
|
where: { id: fileId },
|
||||||
|
include: { project: true }
|
||||||
|
});
|
||||||
|
|
||||||
|
if (!file || file.project.userId !== userId) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
return await prisma.file.delete({
|
||||||
|
where: { id: fileId },
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
export async function getFilesWithContent(projectId, userId) {
|
||||||
|
const project = await prisma.project.findUnique({
|
||||||
|
where: { id: projectId },
|
||||||
|
});
|
||||||
|
|
||||||
|
if (!project || project.userId !== userId) return null;
|
||||||
|
|
||||||
|
return await prisma.file.findMany({
|
||||||
|
where: { projectId },
|
||||||
|
select: {
|
||||||
|
id: true,
|
||||||
|
name: true,
|
||||||
|
file: true,
|
||||||
|
size: true,
|
||||||
|
mimeType: true,
|
||||||
|
projectId: true
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
30
src/repositories/userRepo.js
Normal file
30
src/repositories/userRepo.js
Normal file
@@ -0,0 +1,30 @@
|
|||||||
|
import prisma from '../config/db.js';
|
||||||
|
|
||||||
|
export async function createUser(data) {
|
||||||
|
if (process.env.NODE_ENV === 'development') {
|
||||||
|
console.log('userRepo.createUser() called for username:', data.username);
|
||||||
|
}
|
||||||
|
|
||||||
|
try {
|
||||||
|
const newUser = await prisma.user.create({
|
||||||
|
data,
|
||||||
|
omit: { password: true },
|
||||||
|
});
|
||||||
|
return newUser;
|
||||||
|
} catch (error) {
|
||||||
|
if (error.code === 'P2002') {
|
||||||
|
const err = new Error('Username already exists');
|
||||||
|
err.status = 409;
|
||||||
|
throw err;
|
||||||
|
}
|
||||||
|
throw error;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export async function findUserByUsername(username) {
|
||||||
|
if (process.env.NODE_ENV === 'development') {
|
||||||
|
console.log('userRepo.findUserByUsername() called for username:', username);
|
||||||
|
}
|
||||||
|
|
||||||
|
return await prisma.user.findUnique({ where: { username } });
|
||||||
|
}
|
||||||
10
src/routes/authRoutes.js
Normal file
10
src/routes/authRoutes.js
Normal file
@@ -0,0 +1,10 @@
|
|||||||
|
import express from 'express';
|
||||||
|
import { logInHandler, signUpHandler } from '../controllers/authController.js';
|
||||||
|
import { validateSignUp, validateLogIn } from '../middleware/validateAuth.js';
|
||||||
|
|
||||||
|
const router = express.Router();
|
||||||
|
|
||||||
|
router.post('/signup', validateSignUp, signUpHandler);
|
||||||
|
router.post('/login', validateLogIn, logInHandler);
|
||||||
|
|
||||||
|
export default router;
|
||||||
25
src/routes/ideasRoutes.js
Normal file
25
src/routes/ideasRoutes.js
Normal file
@@ -0,0 +1,25 @@
|
|||||||
|
import express from 'express';
|
||||||
|
import {
|
||||||
|
getAllIdeasHandler,
|
||||||
|
createIdeaHandler,
|
||||||
|
updateIdeaHandler,
|
||||||
|
deleteIdeaHandler,
|
||||||
|
} from '../controllers/ideasController.js';
|
||||||
|
import {
|
||||||
|
validateGetAllQuery,
|
||||||
|
validateId,
|
||||||
|
validateCreateIdea,
|
||||||
|
validateUpdateIdea,
|
||||||
|
} from '../middleware/validateIdeas.js';
|
||||||
|
import { authenticate } from '../middleware/authenticate.js';
|
||||||
|
|
||||||
|
const router = express.Router();
|
||||||
|
|
||||||
|
router.use(authenticate);
|
||||||
|
|
||||||
|
router.get('/', validateGetAllQuery, getAllIdeasHandler);
|
||||||
|
router.post('/', validateCreateIdea, createIdeaHandler);
|
||||||
|
router.put('/:id', validateId, validateUpdateIdea, updateIdeaHandler);
|
||||||
|
router.delete('/:id', validateId, deleteIdeaHandler);
|
||||||
|
|
||||||
|
export default router;
|
||||||
32
src/routes/materialsRoutes.js
Normal file
32
src/routes/materialsRoutes.js
Normal file
@@ -0,0 +1,32 @@
|
|||||||
|
import express from 'express';
|
||||||
|
import {
|
||||||
|
getAllMaterialsHandler,
|
||||||
|
getMaterialsByProjectHandler,
|
||||||
|
getMaterialByIdHandler,
|
||||||
|
createMaterialHandler,
|
||||||
|
deleteMaterialHandler,
|
||||||
|
} from '../controllers/materialsController.js';
|
||||||
|
import {
|
||||||
|
validateGetAllQuery,
|
||||||
|
validateId,
|
||||||
|
validateProjectId,
|
||||||
|
validateCreateMaterial,
|
||||||
|
} from '../middleware/validateMaterials.js';
|
||||||
|
import { authenticate } from '../middleware/authenticate.js';
|
||||||
|
|
||||||
|
const router = express.Router();
|
||||||
|
|
||||||
|
router.use(authenticate);
|
||||||
|
|
||||||
|
router.get('/', validateGetAllQuery, getAllMaterialsHandler);
|
||||||
|
router.get(
|
||||||
|
'/project/:projectId',
|
||||||
|
validateProjectId,
|
||||||
|
validateGetAllQuery,
|
||||||
|
getMaterialsByProjectHandler
|
||||||
|
);
|
||||||
|
router.get('/:id', validateId, getMaterialByIdHandler);
|
||||||
|
router.post('/', validateCreateMaterial, createMaterialHandler);
|
||||||
|
router.delete('/:id', validateId, deleteMaterialHandler);
|
||||||
|
|
||||||
|
export default router;
|
||||||
44
src/routes/projectsRoutes.js
Normal file
44
src/routes/projectsRoutes.js
Normal file
@@ -0,0 +1,44 @@
|
|||||||
|
import express from 'express';
|
||||||
|
import multer from 'multer';
|
||||||
|
import {
|
||||||
|
getAllProjectsHandler,
|
||||||
|
getProjectByIdHandler,
|
||||||
|
createProjectHandler,
|
||||||
|
updateProjectHandler,
|
||||||
|
deleteProjectHandler,
|
||||||
|
getProjectFilesHandler,
|
||||||
|
addProjectFileHandler,
|
||||||
|
deleteProjectFileHandler,
|
||||||
|
downloadProjectFilesHandler
|
||||||
|
} from '../controllers/projectsController.js';
|
||||||
|
import {
|
||||||
|
validateGetAllQuery,
|
||||||
|
validateId,
|
||||||
|
validateCreateProject,
|
||||||
|
validateUpdateProject,
|
||||||
|
} from '../middleware/validateProjects.js';
|
||||||
|
import { authenticate } from '../middleware/authenticate.js';
|
||||||
|
|
||||||
|
const router = express.Router();
|
||||||
|
|
||||||
|
const upload = multer({
|
||||||
|
storage: multer.memoryStorage(),
|
||||||
|
limits: { fileSize: 10 * 1024 * 1024 }
|
||||||
|
});
|
||||||
|
|
||||||
|
router.use(authenticate);
|
||||||
|
|
||||||
|
//Project Routes
|
||||||
|
router.get('/', validateGetAllQuery, getAllProjectsHandler);
|
||||||
|
router.get('/:id', validateId, getProjectByIdHandler);
|
||||||
|
router.post('/', validateCreateProject, createProjectHandler);
|
||||||
|
router.put('/:id', validateId, validateUpdateProject, updateProjectHandler);
|
||||||
|
router.delete('/:id', validateId, deleteProjectHandler);
|
||||||
|
|
||||||
|
// Project File Routes
|
||||||
|
router.get('/:id/files', validateId, getProjectFilesHandler);
|
||||||
|
router.post('/:id/files', validateId, upload.single('file'), addProjectFileHandler);
|
||||||
|
router.delete('/files/:id', validateId, deleteProjectFileHandler);
|
||||||
|
router.get('/:id/files/download', validateId, downloadProjectFilesHandler);
|
||||||
|
|
||||||
|
export default router;
|
||||||
57
src/server.js
Normal file
57
src/server.js
Normal file
@@ -0,0 +1,57 @@
|
|||||||
|
import express from 'express';
|
||||||
|
import morgan from 'morgan';
|
||||||
|
|
||||||
|
import fs from 'fs';
|
||||||
|
import swaggerUI from 'swagger-ui-express';
|
||||||
|
import yaml from 'js-yaml';
|
||||||
|
|
||||||
|
import authRoutes from './routes/authRoutes.js';
|
||||||
|
import ideasRoutes from './routes/ideasRoutes.js';
|
||||||
|
import projectsRoutes from './routes/projectsRoutes.js';
|
||||||
|
import materialsRoutes from './routes/materialsRoutes.js';
|
||||||
|
|
||||||
|
const app = express();
|
||||||
|
const PORT = process.env.PORT || 3000;
|
||||||
|
|
||||||
|
app.use(express.json());
|
||||||
|
if (process.env.NODE_ENV !== 'test') app.use(morgan('tiny'));
|
||||||
|
|
||||||
|
let specs;
|
||||||
|
try {
|
||||||
|
specs = yaml.load(fs.readFileSync('./docs/openapi.yaml', 'utf8'));
|
||||||
|
} catch (error) {
|
||||||
|
console.log('Failed to load OpenAPI specification', error);
|
||||||
|
process.exit(1);
|
||||||
|
}
|
||||||
|
app.use('/api-docs', swaggerUI.serve, swaggerUI.setup(specs));
|
||||||
|
|
||||||
|
app.use('/api/auth', authRoutes);
|
||||||
|
app.use('/api/ideas', ideasRoutes);
|
||||||
|
app.use('/api/projects', projectsRoutes);
|
||||||
|
app.use('/api/materials', materialsRoutes);
|
||||||
|
|
||||||
|
app.use((req, res, next) => {
|
||||||
|
const err = new Error('Not Found');
|
||||||
|
err.status = 404;
|
||||||
|
next(err);
|
||||||
|
});
|
||||||
|
|
||||||
|
app.use((err, req, res, next) => {
|
||||||
|
if (err.code === "LIMIT_FILE_SIZE") {
|
||||||
|
return res.status(413).json({
|
||||||
|
error: 'File size cannot exceed 10MB'
|
||||||
|
});
|
||||||
|
}
|
||||||
|
console.log(err.stack);
|
||||||
|
if (!err.status) {
|
||||||
|
err.status = 500;
|
||||||
|
err.message = 'Internal Server Error';
|
||||||
|
}
|
||||||
|
res.status(err.status).json({ error: err.message });
|
||||||
|
});
|
||||||
|
|
||||||
|
if (process.env.NODE_ENV !== 'test') {
|
||||||
|
app.listen(PORT, () => console.log(`Server is running on port ${PORT}`));
|
||||||
|
}
|
||||||
|
|
||||||
|
export default app;
|
||||||
36
src/services/authService.js
Normal file
36
src/services/authService.js
Normal file
@@ -0,0 +1,36 @@
|
|||||||
|
import bcrypt from 'bcrypt';
|
||||||
|
import jwt from 'jsonwebtoken';
|
||||||
|
import { createUser, findUserByUsername } from '../repositories/userRepo.js';
|
||||||
|
|
||||||
|
export async function signUp(username, password) {
|
||||||
|
if (process.env.NODE_ENV === 'development') {
|
||||||
|
console.log('authService.signUp() called for username:', username);
|
||||||
|
}
|
||||||
|
|
||||||
|
const hashedPassword = await bcrypt.hash(password, 10);
|
||||||
|
const newUser = await createUser({ username, password: hashedPassword });
|
||||||
|
return newUser;
|
||||||
|
}
|
||||||
|
|
||||||
|
export async function logIn(username, password) {
|
||||||
|
if (process.env.NODE_ENV === 'development') {
|
||||||
|
console.log('authService.logIn() called for username:', username);
|
||||||
|
}
|
||||||
|
|
||||||
|
const JWT_SECRET = process.env.JWT_SECRET;
|
||||||
|
const JWT_EXPIRES_IN = process.env.JWT_EXPIRES_IN;
|
||||||
|
const user = await findUserByUsername(username);
|
||||||
|
|
||||||
|
const error = new Error('Invalid credentials');
|
||||||
|
error.status = 401;
|
||||||
|
if (!user) throw error;
|
||||||
|
|
||||||
|
const match = await bcrypt.compare(password, user.password);
|
||||||
|
if (!match) throw error;
|
||||||
|
|
||||||
|
const accessToken = jwt.sign({ id: user.id }, JWT_SECRET, {
|
||||||
|
expiresIn: JWT_EXPIRES_IN,
|
||||||
|
});
|
||||||
|
|
||||||
|
return accessToken;
|
||||||
|
}
|
||||||
88
src/services/ideasService.js
Normal file
88
src/services/ideasService.js
Normal file
@@ -0,0 +1,88 @@
|
|||||||
|
import {
|
||||||
|
getAll,
|
||||||
|
getById,
|
||||||
|
create,
|
||||||
|
update,
|
||||||
|
remove,
|
||||||
|
existsByName,
|
||||||
|
} from '../repositories/ideasRepo.js';
|
||||||
|
|
||||||
|
export async function getAllIdeas(userId, options = {}) {
|
||||||
|
if (process.env.NODE_ENV === 'development') {
|
||||||
|
console.log('ideasService.getAllIdeas() called for userId:', userId);
|
||||||
|
}
|
||||||
|
|
||||||
|
const ideas = await getAll(userId, options);
|
||||||
|
return ideas;
|
||||||
|
}
|
||||||
|
|
||||||
|
export async function getIdeaById(ideaId, userId) {
|
||||||
|
if (process.env.NODE_ENV === 'development') {
|
||||||
|
console.log('ideasService.getIdeaById() called for ideaId:', ideaId);
|
||||||
|
}
|
||||||
|
|
||||||
|
const idea = await getById(ideaId, userId);
|
||||||
|
if (idea) return idea;
|
||||||
|
|
||||||
|
const error = new Error(`Idea ${ideaId} not found`);
|
||||||
|
error.status = 404;
|
||||||
|
throw error;
|
||||||
|
}
|
||||||
|
|
||||||
|
export async function createIdea(userId, ideaData) {
|
||||||
|
if (process.env.NODE_ENV === 'development') {
|
||||||
|
console.log('ideasService.createIdea() called with data:', ideaData);
|
||||||
|
}
|
||||||
|
|
||||||
|
const ideaExists = await existsByName(userId, ideaData.name);
|
||||||
|
if (ideaExists) {
|
||||||
|
const error = new Error(`Idea with name "${ideaData.name}" already exists`);
|
||||||
|
error.status = 409;
|
||||||
|
throw error;
|
||||||
|
}
|
||||||
|
|
||||||
|
const newIdea = await create(userId, ideaData);
|
||||||
|
return newIdea;
|
||||||
|
}
|
||||||
|
|
||||||
|
export async function updateIdea(ideaId, userId, updatedData) {
|
||||||
|
if (process.env.NODE_ENV === 'development') {
|
||||||
|
console.log(
|
||||||
|
'ideasService.updateIdea() called for ideaId:',
|
||||||
|
ideaId,
|
||||||
|
'with data:',
|
||||||
|
updatedData
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (updatedData.name) {
|
||||||
|
const ideaExists = await existsByName(userId, updatedData.name);
|
||||||
|
if (ideaExists) {
|
||||||
|
const error = new Error(
|
||||||
|
`Idea with name "${updatedData.name}" already exists`
|
||||||
|
);
|
||||||
|
error.status = 409;
|
||||||
|
throw error;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
const updatedIdea = await update(ideaId, userId, updatedData);
|
||||||
|
if (updatedIdea) return updatedIdea;
|
||||||
|
|
||||||
|
const error = new Error(`Idea ${ideaId} not found`);
|
||||||
|
error.status = 404;
|
||||||
|
throw error;
|
||||||
|
}
|
||||||
|
|
||||||
|
export async function deleteIdea(ideaId, userId) {
|
||||||
|
if (process.env.NODE_ENV === 'development') {
|
||||||
|
console.log('ideasService.deleteIdea() called for ideaId:', ideaId);
|
||||||
|
}
|
||||||
|
|
||||||
|
const result = await remove(ideaId, userId);
|
||||||
|
if (result) return;
|
||||||
|
|
||||||
|
const error = new Error(`Idea ${ideaId} not found`);
|
||||||
|
error.status = 404;
|
||||||
|
throw error;
|
||||||
|
}
|
||||||
85
src/services/materialsService.js
Normal file
85
src/services/materialsService.js
Normal file
@@ -0,0 +1,85 @@
|
|||||||
|
import {
|
||||||
|
getAll,
|
||||||
|
getByProjectId,
|
||||||
|
getById,
|
||||||
|
create,
|
||||||
|
remove,
|
||||||
|
existsByName,
|
||||||
|
} from '../repositories/materialsRepo.js';
|
||||||
|
|
||||||
|
export async function getAllMaterials(userId, options = {}) {
|
||||||
|
if (process.env.NODE_ENV === 'development') {
|
||||||
|
console.log(
|
||||||
|
'materialsService.getAllMaterials() called for userId:',
|
||||||
|
userId
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
const materials = await getAll(userId, options);
|
||||||
|
return materials;
|
||||||
|
}
|
||||||
|
|
||||||
|
export async function getMaterialsByProject(projectId, userId, options = {}) {
|
||||||
|
if (process.env.NODE_ENV === 'development') {
|
||||||
|
console.log(
|
||||||
|
'materialsService.getMaterialsByProject() called for projectId:',
|
||||||
|
projectId
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
const materials = await getByProjectId(projectId, userId, options);
|
||||||
|
return materials;
|
||||||
|
}
|
||||||
|
|
||||||
|
export async function getMaterialById(materialId, userId) {
|
||||||
|
if (process.env.NODE_ENV === 'development') {
|
||||||
|
console.log(
|
||||||
|
'materialsService.getMaterialById() called for materialId:',
|
||||||
|
materialId
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
const material = await getById(materialId, userId);
|
||||||
|
if (material) return material;
|
||||||
|
|
||||||
|
const error = new Error(`Material ${materialId} not found`);
|
||||||
|
error.status = 404;
|
||||||
|
throw error;
|
||||||
|
}
|
||||||
|
|
||||||
|
export async function createMaterial(userId, materialData) {
|
||||||
|
if (process.env.NODE_ENV === 'development') {
|
||||||
|
console.log(
|
||||||
|
'materialsService.createMaterial() called with data:',
|
||||||
|
materialData
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
const materialExists = await existsByName(userId, materialData.name);
|
||||||
|
if (materialExists) {
|
||||||
|
const error = new Error(
|
||||||
|
`Material with name "${materialData.name}" already exists`
|
||||||
|
);
|
||||||
|
error.status = 409;
|
||||||
|
throw error;
|
||||||
|
}
|
||||||
|
|
||||||
|
const newMaterial = await create(userId, materialData);
|
||||||
|
return newMaterial;
|
||||||
|
}
|
||||||
|
|
||||||
|
export async function deleteMaterial(materialId, userId) {
|
||||||
|
if (process.env.NODE_ENV === 'development') {
|
||||||
|
console.log(
|
||||||
|
'materialsService.deleteMaterial() called for materialId:',
|
||||||
|
materialId
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
const result = await remove(materialId, userId);
|
||||||
|
if (result) return;
|
||||||
|
|
||||||
|
const error = new Error(`Material ${materialId} not found`);
|
||||||
|
error.status = 404;
|
||||||
|
throw error;
|
||||||
|
}
|
||||||
155
src/services/projectsService.js
Normal file
155
src/services/projectsService.js
Normal file
@@ -0,0 +1,155 @@
|
|||||||
|
import {
|
||||||
|
getAll,
|
||||||
|
getById,
|
||||||
|
create,
|
||||||
|
update,
|
||||||
|
remove,
|
||||||
|
existsByName,
|
||||||
|
getFilesByProjectId,
|
||||||
|
getFilesWithContent,
|
||||||
|
addFile,
|
||||||
|
deleteFile
|
||||||
|
} from '../repositories/projectsRepo.js';
|
||||||
|
|
||||||
|
export async function getAllProjects(userId, options = {}) {
|
||||||
|
if (process.env.NODE_ENV === 'development') {
|
||||||
|
console.log('projectsService.getAllProjects() called for userId:', userId);
|
||||||
|
}
|
||||||
|
return await getAll(userId, options);
|
||||||
|
}
|
||||||
|
|
||||||
|
export async function getProjectById(projectId, userId) {
|
||||||
|
if (process.env.NODE_ENV === 'development') {
|
||||||
|
console.log(
|
||||||
|
'projectsService.getProjectById() called for projectId:',
|
||||||
|
projectId
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
const project = await getById(projectId, userId);
|
||||||
|
if (project) return project;
|
||||||
|
|
||||||
|
const error = new Error(`Project ${projectId} not found`);
|
||||||
|
error.status = 404;
|
||||||
|
throw error;
|
||||||
|
}
|
||||||
|
|
||||||
|
export async function createProject(userId, projectData) {
|
||||||
|
if (process.env.NODE_ENV === 'development') {
|
||||||
|
console.log(
|
||||||
|
'projectsService.createProject() called with data:',
|
||||||
|
projectData
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
const projectExists = await existsByName(userId, projectData.name);
|
||||||
|
if (projectExists) {
|
||||||
|
const error = new Error(
|
||||||
|
`Project with name "${projectData.name}" already exists`
|
||||||
|
);
|
||||||
|
error.status = 409;
|
||||||
|
throw error;
|
||||||
|
}
|
||||||
|
|
||||||
|
const newProject = await create(userId, projectData);
|
||||||
|
return newProject;
|
||||||
|
}
|
||||||
|
|
||||||
|
export async function updateProject(projectId, userId, updatedData) {
|
||||||
|
if (process.env.NODE_ENV === 'development') {
|
||||||
|
console.log(
|
||||||
|
'projectsService.updateProject() called for projectId:',
|
||||||
|
projectId,
|
||||||
|
'with data:',
|
||||||
|
updatedData
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (updatedData.name) {
|
||||||
|
const projectExists = await existsByName(userId, updatedData.name);
|
||||||
|
if (projectExists) {
|
||||||
|
const error = new Error(
|
||||||
|
`Project with name "${updatedData.name}" already exists`
|
||||||
|
);
|
||||||
|
error.status = 409;
|
||||||
|
throw error;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
const updatedProject = await update(projectId, userId, updatedData);
|
||||||
|
if (updatedProject) return updatedProject;
|
||||||
|
|
||||||
|
const error = new Error(`Project ${projectId} not found`);
|
||||||
|
error.status = 404;
|
||||||
|
throw error;
|
||||||
|
}
|
||||||
|
|
||||||
|
export async function deleteProject(projectId, userId) {
|
||||||
|
if (process.env.NODE_ENV === 'development') {
|
||||||
|
console.log(
|
||||||
|
'projectsService.deleteProject() called for projectId:',
|
||||||
|
projectId
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
const result = await remove(projectId, userId);
|
||||||
|
if (result) return;
|
||||||
|
|
||||||
|
const error = new Error(`Project ${projectId} not found`);
|
||||||
|
error.status = 404;
|
||||||
|
throw error;
|
||||||
|
}
|
||||||
|
|
||||||
|
export async function getProjectFilesById(projectId, userId) {
|
||||||
|
if (process.env.NODE_ENV === 'development') {
|
||||||
|
console.log(
|
||||||
|
'projectsService.getProjectFilesById() called for projectId:',
|
||||||
|
projectId
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
const files = await getFilesByProjectId(projectId, userId);
|
||||||
|
if (files) return files;
|
||||||
|
|
||||||
|
const error = new Error(`Project ${projectId} not found`);
|
||||||
|
error.status = 404;
|
||||||
|
throw error;
|
||||||
|
}
|
||||||
|
|
||||||
|
export async function getProjectFilesForDownload(projectId, userId) {
|
||||||
|
const files = await getFilesWithContent(projectId, userId);
|
||||||
|
if (!files) {
|
||||||
|
const error = new Error(`Project ${projectId} not found or unauthorized`);
|
||||||
|
error.status = 404;
|
||||||
|
throw error;
|
||||||
|
}
|
||||||
|
return files;
|
||||||
|
}
|
||||||
|
|
||||||
|
export async function addProjectFile(projectId, userId, fileData) {
|
||||||
|
if (process.env.NODE_ENV === 'development') {
|
||||||
|
console.log(
|
||||||
|
'projectsService.addProjectFile() called for projectId:',
|
||||||
|
projectId,
|
||||||
|
'with fileData:',
|
||||||
|
fileData
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
const newFile = await addFile(projectId, userId, fileData);
|
||||||
|
if (!newFile) {
|
||||||
|
const error = new Error(`Project ${projectId} not found or unauthorized`);
|
||||||
|
error.status = 404;
|
||||||
|
throw error;
|
||||||
|
}
|
||||||
|
return newFile;
|
||||||
|
}
|
||||||
|
|
||||||
|
export async function deleteProjectFile(fileId, userId) {
|
||||||
|
if (process.env.NODE_ENV === 'development') {
|
||||||
|
console.log('projectsService.deleteProjectFile() called for fileId:', fileId);
|
||||||
|
}
|
||||||
|
|
||||||
|
const deletedFile = await deleteFile(fileId, userId);
|
||||||
|
if (deletedFile) return deletedFile;
|
||||||
|
}
|
||||||
Reference in New Issue
Block a user