init
This commit is contained in:
5
backend/.gitignore
vendored
Normal file
5
backend/.gitignore
vendored
Normal file
@@ -0,0 +1,5 @@
|
||||
node_modules
|
||||
# Keep environment variables out of version control
|
||||
.env
|
||||
|
||||
/generated/prisma
|
||||
8
backend/Dockerfile
Normal file
8
backend/Dockerfile
Normal file
@@ -0,0 +1,8 @@
|
||||
FROM node:20-alpine
|
||||
WORKDIR /app
|
||||
COPY package*.json .
|
||||
RUN npm install
|
||||
COPY . .
|
||||
RUN npx prisma generate
|
||||
EXPOSE 5000
|
||||
CMD ["sh", "-c", "npx prisma migrate deploy && node index.js"]
|
||||
1
backend/controllers/homeController.js
Normal file
1
backend/controllers/homeController.js
Normal file
@@ -0,0 +1 @@
|
||||
|
||||
38
backend/index.js
Normal file
38
backend/index.js
Normal file
@@ -0,0 +1,38 @@
|
||||
import express from 'express'
|
||||
import dotenv from 'dotenv'
|
||||
import cors from 'cors'
|
||||
|
||||
const app = express()
|
||||
const port = 5000
|
||||
|
||||
app.use(express.json())
|
||||
app.use(cors({ origin: 'http://localhost:5173' }))
|
||||
dotenv.config()
|
||||
|
||||
import { prisma } from './prisma.js'
|
||||
|
||||
import { validateToken } from './middleware/auth.js'
|
||||
|
||||
import { register, login } from './routes/auth.js'
|
||||
import { deleteUser } from './routes/user.js'
|
||||
import { createOffer, deleteOffer } from './routes/offer.js'
|
||||
|
||||
/* Test route so nxckwc can test axios*/
|
||||
app.get('/users', async (req, res) => {
|
||||
const user = await prisma.user.findMany()
|
||||
|
||||
res.json(user)
|
||||
})
|
||||
|
||||
app.post('/login', login)
|
||||
app.post('/register', register)
|
||||
|
||||
app.delete('/user/:userId', deleteUser)
|
||||
|
||||
app.post('/crear-oferta', validateToken, createOffer)
|
||||
app.delete('/oferta/:projectId', validateToken, deleteOffer)
|
||||
|
||||
|
||||
app.listen(port, () => {
|
||||
console.log(`Project API running on port: ${port}`)
|
||||
})
|
||||
18
backend/middleware/auth.js
Normal file
18
backend/middleware/auth.js
Normal file
@@ -0,0 +1,18 @@
|
||||
import jwt from 'jsonwebtoken'
|
||||
|
||||
export const validateToken = async (req, res, next) => {
|
||||
|
||||
const authHeader = req.headers.authorization
|
||||
|
||||
if (!authHeader)
|
||||
return res.status(401).json({ error: "Invalid token" })
|
||||
const token = authHeader.split(' ')[1]
|
||||
try {
|
||||
req.user = jwt.verify(token, process.env.JWT_SECRET)
|
||||
} catch (error) {
|
||||
return res.status(401).json({ error: "Invalid token" })
|
||||
}
|
||||
|
||||
|
||||
next()
|
||||
}
|
||||
1752
backend/package-lock.json
generated
Normal file
1752
backend/package-lock.json
generated
Normal file
File diff suppressed because it is too large
Load Diff
23
backend/package.json
Normal file
23
backend/package.json
Normal file
@@ -0,0 +1,23 @@
|
||||
{
|
||||
"name": "backend",
|
||||
"version": "1.0.0",
|
||||
"description": "",
|
||||
"license": "ISC",
|
||||
"author": "",
|
||||
"type": "module",
|
||||
"main": "index.js",
|
||||
"scripts": {
|
||||
"start": "nodemon ./index.js"
|
||||
},
|
||||
"dependencies": {
|
||||
"@prisma/client": "^6.19.3",
|
||||
"bcrypt": "^6.0.0",
|
||||
"cors": "^2.8.6",
|
||||
"express": "^5.2.1",
|
||||
"jsonwebtoken": "^9.0.3",
|
||||
"prisma": "^6.19.3"
|
||||
},
|
||||
"devDependencies": {
|
||||
"nodemon": "^3.1.14"
|
||||
}
|
||||
}
|
||||
16
backend/prisma.config.ts
Normal file
16
backend/prisma.config.ts
Normal file
@@ -0,0 +1,16 @@
|
||||
|
||||
// This file was generated by Prisma and assumes you have installed the following:
|
||||
// npm install --save-dev prisma dotenv
|
||||
import "dotenv/config";
|
||||
import { defineConfig, env } from "prisma/config";
|
||||
|
||||
export default defineConfig({
|
||||
schema: "prisma/schema.prisma",
|
||||
migrations: {
|
||||
path: "prisma/migrations",
|
||||
},
|
||||
engine: "classic",
|
||||
datasource: {
|
||||
url: env("DATABASE_URL"),
|
||||
},
|
||||
});
|
||||
3
backend/prisma.js
Normal file
3
backend/prisma.js
Normal file
@@ -0,0 +1,3 @@
|
||||
import { PrismaClient } from '@prisma/client'
|
||||
|
||||
export const prisma = new PrismaClient()
|
||||
54
backend/prisma/migrations/20260507211134_init/migration.sql
Normal file
54
backend/prisma/migrations/20260507211134_init/migration.sql
Normal file
@@ -0,0 +1,54 @@
|
||||
-- CreateTable
|
||||
CREATE TABLE "User" (
|
||||
"id" SERIAL NOT NULL,
|
||||
"username" TEXT NOT NULL,
|
||||
"email" TEXT NOT NULL,
|
||||
"password" TEXT NOT NULL,
|
||||
"type" TEXT NOT NULL,
|
||||
"createdAt" TIMESTAMP(3) NOT NULL DEFAULT CURRENT_TIMESTAMP,
|
||||
|
||||
CONSTRAINT "User_pkey" PRIMARY KEY ("id")
|
||||
);
|
||||
|
||||
-- CreateTable
|
||||
CREATE TABLE "UserOferta" (
|
||||
"id" SERIAL NOT NULL,
|
||||
"userId" INTEGER NOT NULL,
|
||||
"ofertaId" INTEGER NOT NULL,
|
||||
"createdAt" TIMESTAMP(3) NOT NULL DEFAULT CURRENT_TIMESTAMP,
|
||||
|
||||
CONSTRAINT "UserOferta_pkey" PRIMARY KEY ("id")
|
||||
);
|
||||
|
||||
-- CreateTable
|
||||
CREATE TABLE "Oferta" (
|
||||
"id" SERIAL NOT NULL,
|
||||
"titulo" TEXT NOT NULL,
|
||||
"empresa" TEXT NOT NULL,
|
||||
"ubicacion" TEXT NOT NULL,
|
||||
"salario" TEXT NOT NULL DEFAULT '0',
|
||||
"contrato" TEXT NOT NULL,
|
||||
"descripcion" TEXT NOT NULL,
|
||||
"url" TEXT NOT NULL,
|
||||
"authorId" INTEGER NOT NULL,
|
||||
|
||||
CONSTRAINT "Oferta_pkey" PRIMARY KEY ("id")
|
||||
);
|
||||
|
||||
-- CreateIndex
|
||||
CREATE UNIQUE INDEX "User_username_key" ON "User"("username");
|
||||
|
||||
-- CreateIndex
|
||||
CREATE UNIQUE INDEX "User_email_key" ON "User"("email");
|
||||
|
||||
-- CreateIndex
|
||||
CREATE UNIQUE INDEX "UserOferta_userId_ofertaId_key" ON "UserOferta"("userId", "ofertaId");
|
||||
|
||||
-- AddForeignKey
|
||||
ALTER TABLE "UserOferta" ADD CONSTRAINT "UserOferta_userId_fkey" FOREIGN KEY ("userId") REFERENCES "User"("id") ON DELETE RESTRICT ON UPDATE CASCADE;
|
||||
|
||||
-- AddForeignKey
|
||||
ALTER TABLE "UserOferta" ADD CONSTRAINT "UserOferta_ofertaId_fkey" FOREIGN KEY ("ofertaId") REFERENCES "Oferta"("id") ON DELETE RESTRICT ON UPDATE CASCADE;
|
||||
|
||||
-- AddForeignKey
|
||||
ALTER TABLE "Oferta" ADD CONSTRAINT "Oferta_authorId_fkey" FOREIGN KEY ("authorId") REFERENCES "User"("id") ON DELETE RESTRICT ON UPDATE CASCADE;
|
||||
3
backend/prisma/migrations/migration_lock.toml
Normal file
3
backend/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"
|
||||
53
backend/prisma/schema.prisma
Normal file
53
backend/prisma/schema.prisma
Normal file
@@ -0,0 +1,53 @@
|
||||
// This is your Prisma schema file,
|
||||
// learn more about it in the docs: https://pris.ly/d/prisma-schema
|
||||
|
||||
// Looking for ways to speed up your queries, or scale easily with your serverless or edge functions?
|
||||
// Try Prisma Accelerate: https://pris.ly/cli/accelerate-init
|
||||
|
||||
generator client {
|
||||
provider = "prisma-client-js"
|
||||
}
|
||||
|
||||
datasource db {
|
||||
provider = "postgresql"
|
||||
url = env("DATABASE_URL")
|
||||
}
|
||||
|
||||
model User {
|
||||
id Int @id @default(autoincrement())
|
||||
username String @unique
|
||||
email String @unique
|
||||
password String
|
||||
type String
|
||||
createdAt DateTime @default(now())
|
||||
|
||||
ofertas Oferta[]
|
||||
postulaciones UserOferta[]
|
||||
}
|
||||
|
||||
model UserOferta {
|
||||
id Int @id @default(autoincrement())
|
||||
userId Int
|
||||
ofertaId Int
|
||||
createdAt DateTime @default(now())
|
||||
|
||||
user User @relation(fields: [userId], references: [id])
|
||||
oferta Oferta @relation(fields: [ofertaId], references: [id])
|
||||
|
||||
@@unique([userId, ofertaId])
|
||||
}
|
||||
|
||||
model Oferta {
|
||||
id Int @id @default(autoincrement())
|
||||
titulo String
|
||||
empresa String
|
||||
ubicacion String
|
||||
salario String @default("0")
|
||||
contrato String
|
||||
descripcion String
|
||||
url String
|
||||
authorId Int
|
||||
|
||||
author User @relation(fields: [authorId], references: [id])
|
||||
candidatos UserOferta[]
|
||||
}
|
||||
79
backend/routes/auth.js
Normal file
79
backend/routes/auth.js
Normal file
@@ -0,0 +1,79 @@
|
||||
import bcrypt from 'bcrypt'
|
||||
import jwt from 'jsonwebtoken'
|
||||
|
||||
import { prisma } from '../prisma.js'
|
||||
|
||||
export const login = async (req, res) => {
|
||||
|
||||
// Get data
|
||||
const { email, password } = req.body;
|
||||
|
||||
if (!email || !password) {
|
||||
return res.status(400).json({ error: 'All fields are required' })
|
||||
}
|
||||
|
||||
// Check data is correct
|
||||
const user = await prisma.user.findFirst({ where: { email } });
|
||||
|
||||
if (!user || !await bcrypt.compare(password, user.password)) {
|
||||
return res.status(400).json({ error: 'Invalid credentials' })
|
||||
}
|
||||
|
||||
// Generate JWT
|
||||
const token = jwt.sign({ id: user.id }, process.env.JWT_SECRET, { expiresIn: '7d' })
|
||||
|
||||
// Return
|
||||
res.status(200).json({
|
||||
id: user.id,
|
||||
username: user.username,
|
||||
email: user.email,
|
||||
token
|
||||
})
|
||||
|
||||
}
|
||||
|
||||
export const register = async (req, res) => {
|
||||
|
||||
// Get data
|
||||
const {username, email, password } = req.body
|
||||
|
||||
// Check is not empty
|
||||
if (!username || !email || !password) {
|
||||
return res.status(400).json({ error: 'All fields are required' })
|
||||
}
|
||||
|
||||
// Validate data
|
||||
const emailRegex = /^[^\s@]+@[^\s@]+\.[^\s@]+$/
|
||||
|
||||
if (username.length < 3 ||username.length > 14) { return res.status(400).json({ error: 'Username must be between 3 and 14 characters' })}
|
||||
if (email.length > 30 || !emailRegex.test(email)) { return res.status(400).json({ error: 'Email is not valid' })}
|
||||
if (password.length < 6 || password.length > 32) { return res.status(400).json({ error: 'Password must be between 6 and 32 characters' })}
|
||||
|
||||
// Check email and username doesnt exists
|
||||
const userExists = await prisma.user.findFirst({
|
||||
where: {
|
||||
OR: [ { email }, { username } ]
|
||||
}
|
||||
});
|
||||
|
||||
// If username or email exists, send error
|
||||
if (userExists) {
|
||||
return res.status(409).json({
|
||||
error: 'User already exists'
|
||||
})
|
||||
}
|
||||
|
||||
// Hash password
|
||||
const hashedPassword = await bcrypt.hash(password, 10)
|
||||
|
||||
// Create user
|
||||
const user = await prisma.user.create({
|
||||
data: { username, email, password: hashedPassword, type: "candidato" }
|
||||
})
|
||||
|
||||
// Generates token
|
||||
const token = jwt.sign({ id: user.id }, process.env.JWT_SECRET, { expiresIn: '7d' })
|
||||
|
||||
// Return user
|
||||
res.status(201).json({ id: user.id, username: user.username, email: user.email, token })
|
||||
}
|
||||
39
backend/routes/offer.js
Normal file
39
backend/routes/offer.js
Normal file
@@ -0,0 +1,39 @@
|
||||
import { prisma } from '../prisma.js'
|
||||
|
||||
export const createOffer = async (req, res) => {
|
||||
const { titulo, empresa, ubicacion, salario, contrato, descripcion } = req.body;
|
||||
const authorId = req.user.id
|
||||
|
||||
if (!titulo || !empresa || !ubicacion || !salario || !contrato || !descripcion) return res.status(400).json({ error: ""})
|
||||
|
||||
try {
|
||||
const offer = await prisma.oferta.create({
|
||||
data: {
|
||||
titulo, empresa, ubicacion, salario, descripcion
|
||||
}
|
||||
})
|
||||
res.status(201).json(offer)
|
||||
} catch (err) {
|
||||
res.status(400).json({ error: "Ha ocurrido un error al crear la oferta de trabajo." })
|
||||
}
|
||||
}
|
||||
|
||||
export const deleteOffer = async (req, res) => {
|
||||
const { offerId } = req.params;
|
||||
const authorId = req.user.id;
|
||||
|
||||
const id = parseInt(offerId)
|
||||
|
||||
try {
|
||||
const offer = await prisma.offer.findFirst({ where: { id }})
|
||||
|
||||
if (!offer) return res.status(404).json({ error: "Invalid project" })
|
||||
if (authorId !== offer.authorId) return res.status(403).json({ error: "Missing permissions" })
|
||||
|
||||
await prisma.offer.delete({ where: { id } })
|
||||
res.status(200).json({ message: "Project has been deleted" })
|
||||
|
||||
} catch (err) {
|
||||
res.status(400).json({ error: "Ha ocurrido un error al borrar el proyecto" })
|
||||
}
|
||||
}
|
||||
20
backend/routes/user.js
Normal file
20
backend/routes/user.js
Normal file
@@ -0,0 +1,20 @@
|
||||
import jwt from 'jsonwebtoken'
|
||||
|
||||
import { prisma } from '../prisma.js'
|
||||
|
||||
export const deleteUser = async (req, res) => {
|
||||
|
||||
// Get user ID from url
|
||||
const { userId } = req.params
|
||||
|
||||
try {
|
||||
// Delete user from database
|
||||
await prisma.user.delete({ where: { id: Number(userId) } })
|
||||
|
||||
// Return status and message
|
||||
res.status(200).json({ message: 'User deleted' })
|
||||
} catch (error) {
|
||||
// If user not found, return error 404
|
||||
res.status(404).json({ message: 'User not found' })
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user