This commit is contained in:
2026-05-07 23:18:34 +02:00
commit 07c58f23ab
39 changed files with 6044 additions and 0 deletions

5
backend/.gitignore vendored Normal file
View File

@@ -0,0 +1,5 @@
node_modules
# Keep environment variables out of version control
.env
/generated/prisma

8
backend/Dockerfile Normal file
View 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"]

View File

@@ -0,0 +1 @@

38
backend/index.js Normal file
View 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}`)
})

View 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

File diff suppressed because it is too large Load Diff

23
backend/package.json Normal file
View 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
View 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
View File

@@ -0,0 +1,3 @@
import { PrismaClient } from '@prisma/client'
export const prisma = new PrismaClient()

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

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

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