Skip to content
Closed
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
88 changes: 88 additions & 0 deletions .github/workflows/cicd.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,88 @@
name: CI/CD – exdev-api

on:
push:
branches: [develop, master]
workflow_dispatch: {}

permissions:
contents: read
packages: write

concurrency:
group: deploy-${{ github.ref_name }}
cancel-in-progress: true

jobs:
build-and-push:
runs-on: ubuntu-latest
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: Build and push image
uses: docker/build-push-action@v6
with:
context: .
push: true
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: Log in to GHCR
uses: docker/login-action@v3
with:
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