Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
9 changes: 9 additions & 0 deletions .dockerignore
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
node_modules
dist
.git
.gitignore
Dockerfile*
docker-compose*.yml
npm-debug.log
.env
README.md
7 changes: 7 additions & 0 deletions .env.example
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
# Copy this file to .env locally or to the server as /opt/exdev-api/.env.
# Never commit real credentials.
NODE_ENV=development
PORT=3001
# For local `npm run start:dev`, use 127.0.0.1 or localhost.
# For Docker Compose on the VM, use host.docker.internal and PostgreSQL's VM port.
DATABASE_URL=postgresql://exdev_website:CHANGE_ME@127.0.0.1:5432/exdev_staging_db
1 change: 1 addition & 0 deletions .github/CODEOWNERS
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
* @IanBattistoni @Im-Fran
90 changes: 59 additions & 31 deletions .github/workflows/cicd.yml
Original file line number Diff line number Diff line change
@@ -1,9 +1,9 @@
name: CI/CD – exdev-apply-api
name: CI/CD – exdev-api

on:
push:
branches: ["master"] # cámbialo si despliegas otra rama
workflow_dispatch: {} # permite correrlo manualmente
branches: [develop, master]
workflow_dispatch: {}

permissions:
contents: read
Expand All @@ -14,12 +14,10 @@ concurrency:
cancel-in-progress: true

jobs:
build-and-deploy:
build-and-push:
runs-on: ubuntu-latest

steps:
- name: Checkout
uses: actions/checkout@v4
- uses: actions/checkout@v4

- name: Log in to GHCR
uses: docker/login-action@v3
Expand All @@ -28,33 +26,63 @@ jobs:
username: ${{ github.actor }}
password: ${{ secrets.GITHUB_TOKEN }}

- name: Build & Push image
- name: Build and push image
uses: docker/build-push-action@v6
with:
context: .
push: true
tags: ghcr.io/${{ github.repository }}:latest
tags: ghcr.io/exdevutem/exdev-api:${{ github.ref_name }}

deploy-staging:
if: github.ref_name == 'develop'
needs: build-and-push
runs-on: [self-hosted, staging]
environment: staging
steps:
- uses: actions/checkout@v4

- name: Deploy over SSH
uses: appleboy/ssh-action@v1.2.0
- name: Log in to GHCR
uses: docker/login-action@v3
with:
host: ${{ secrets.SSH_HOST }} # ej: 18.231.166.7
username: ${{ secrets.SSH_USER }} # ej: ubuntu o ec2-user
key: ${{ secrets.SSH_PRIVATE_KEY }} # llave privada que creaste
port: ${{ secrets.SSH_PORT || 22 }}
script: |
set -e
if [ ! -d /opt/exdev-apply-api ]; then
mkdir -p /opt/exdev-apply-api
cd /opt/exdev-apply-api
git clone https://github.com/${{ github.repository }} .
else
cd /opt/exdev-apply-api
git fetch --all
git checkout -B ${{ github.ref_name }} origin/${{ github.ref_name }}
fi

docker login ghcr.io -u ${{ github.actor }} -p ${{ secrets.GITHUB_TOKEN }}
docker compose -f docker-compose.yml pull
docker compose -f docker-compose.yml up -d --remove-orphans
docker image prune -f
registry: ghcr.io
username: ${{ github.actor }}
password: ${{ secrets.GITHUB_TOKEN }}

- name: Deploy staging
shell: bash
run: |
set -euo pipefail
APP_DIR=/opt/exdev-api
mkdir -p "$APP_DIR"
cp docker-compose.yml "$APP_DIR/docker-compose.yml"
export IMAGE_TAG=develop
docker compose -f "$APP_DIR/docker-compose.yml" pull
docker compose -f "$APP_DIR/docker-compose.yml" up -d --remove-orphans
docker image prune -f

deploy-production:
if: github.ref_name == 'master'
needs: build-and-push
runs-on: [self-hosted, production]
environment: production
steps:
- uses: actions/checkout@v4

- name: Log in to GHCR
uses: docker/login-action@v3
with:
registry: ghcr.io
username: ${{ github.actor }}
password: ${{ secrets.GITHUB_TOKEN }}

- name: Deploy production
shell: bash
run: |
set -euo pipefail
APP_DIR=/opt/exdev-api
mkdir -p "$APP_DIR"
cp docker-compose.yml "$APP_DIR/docker-compose.yml"
export IMAGE_TAG=master
docker compose -f "$APP_DIR/docker-compose.yml" pull
docker compose -f "$APP_DIR/docker-compose.yml" up -d --remove-orphans
docker image prune -f
30 changes: 30 additions & 0 deletions Dockerfile
Original file line number Diff line number Diff line change
@@ -0,0 +1,30 @@
# ---------- Dep stage ----------
FROM node:24-alpine AS deps
WORKDIR /app
COPY package*.json ./
RUN npm ci

# ---------- Build stage ----------
FROM node:24-alpine AS build
WORKDIR /app
COPY --from=deps /app/node_modules ./node_modules
COPY . .
# Si usas nest-cli.json/tsconfig*.json, se copian arriba con "COPY . ."
RUN npm run build

# ---------- Prod deps (solo prod) ----------
FROM node:24-alpine AS prod-deps
WORKDIR /app
COPY package*.json ./
RUN npm ci --omit=dev

# ---------- Runner ----------
FROM node:24-alpine AS runner
WORKDIR /app
ENV NODE_ENV=production
COPY --from=prod-deps /app/node_modules ./node_modules
COPY --from=build /app/dist ./dist


EXPOSE 3000
CMD ["node", "dist/main.js"]
14 changes: 14 additions & 0 deletions docker-compose.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
version: "3.9"

services:
exdev-api:
image: ghcr.io/exdevutem/exdev-api:${IMAGE_TAG:-latest}
container_name: exdev-api
restart: unless-stopped
ports:
- "3001:3000"
env_file:
- /opt/exdev-api/.env
extra_hosts:
- "host.docker.internal:host-gateway"

4 changes: 4 additions & 0 deletions src/applications/applications.controller.ts
Original file line number Diff line number Diff line change
Expand Up @@ -9,4 +9,8 @@ export class ApplicationsController {
create(@Body() body: any){
return this.applicationsService.create(body);
}
@Get()
findAll() {
return this.applicationsService.findAll();
}
}
47 changes: 47 additions & 0 deletions src/applications/applications.service.ts
Original file line number Diff line number Diff line change
Expand Up @@ -69,4 +69,51 @@ async create(body: any){
}
}

async findAll() {
const countSql = `SELECT COUNT(*)::int AS total FROM postulaciones;`;

const listSql = `
SELECT
id,
nombre_completo,
rut,
edad,
correo_institucional,
campus,
carrera,
anio_ingreso,
anio_actual,
area_interes1,
area_interes2,
area_interes3,
ayudantias,
horas_disponibles_semanales,
motivo_postulacion,
proyecto_idea,
portafolio,
postulacion_conjunta,
pitch,
apodo,
created_at,
updated_at
FROM postulaciones
ORDER BY created_at DESC;
`;

try {
const [countRes, listRes] = await Promise.all([
this.pool.query(countSql),
this.pool.query(listSql),
]);

return {
totalPostulaciones: countRes.rows[0].total as number,
postulaciones: listRes.rows,
};
} catch (err: any) {
const { status, body } = buildErrorExceptionPayload(err);
throw new HttpException(body, status, { cause: err });
}
}

}
34 changes: 34 additions & 0 deletions src/main.ts
Original file line number Diff line number Diff line change
@@ -1,8 +1,42 @@
import { NestFactory } from '@nestjs/core';
import { AppModule } from './app.module';
import { ValidationPipe } from '@nestjs/common';

async function bootstrap() {
const app = await NestFactory.create(AppModule);

//desactivar cache
const expressApp = app.getHttpAdapter().getInstance();
expressApp.set('etag', false);

const WEB_ORIGINS = [
'https://dev.exdev.cl',
'https://exdev.cl',
'https://www.exdev.cl',
'http://localhost:5000',
'http://localhost:3000',
'https://tomas.exdev.cl',
'https://www.tomas.exdev.cl'
];
app.use((req, res, next) => {
res.setHeader('Cache-Control', 'no-store, no-cache, must-revalidate');
res.setHeader('Pragma', 'no-cache');
res.setHeader('Expires', '0');
next();
});
app.enableCors({
origin: (origin, cb) => {

if (!origin) return cb(null, true);
cb(null, WEB_ORIGINS.includes(origin));
},
methods: ['GET','POST','OPTIONS'],
allowedHeaders: ['Content-Type','Authorization'],
credentials: false,
optionsSuccessStatus: 204,
});

await app.listen(process.env.PORT ? Number(process.env.PORT) : 3000);
}
bootstrap();

Loading