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
115 changes: 115 additions & 0 deletions .github/workflows/ci.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,115 @@
name: CI/CD Pipeline

on:
push:
branches: [ main, develop ]
pull_request:
branches: [ main ]

jobs:
test:
runs-on: ubuntu-latest

services:
postgres:
image: postgres:15
env:
POSTGRES_DB: events_test
POSTGRES_USER: postgres
POSTGRES_PASSWORD: postgres
ports:
- 5432:5432
options: >-
--health-cmd pg_isready
--health-interval 10s
--health-timeout 5s
--health-retries 5

redis:
image: redis:7
ports:
- 6379:6379
options: >-
--health-cmd "redis-cli ping"
--health-interval 10s
--health-timeout 5s
--health-retries 5

steps:
- uses: actions/checkout@v3

- name: Setup Node.js
uses: actions/setup-node@v3
with:
node-version: '18'
cache: 'npm'

- name: Install dependencies
run: npm ci

- name: Run linter
run: npm run lint || echo "Add linting script to package.json"

- name: Run tests
run: npm test
env:
NODE_ENV: test
DB_HOST: localhost
DB_PORT: 5432
DB_NAME: events_test
DB_USER: postgres
DB_PASSWORD: postgres
REDIS_HOST: localhost
REDIS_PORT: 6379

- name: Build Docker image
run: docker build -f docker/Dockerfile -t event-scraper-api .

build:
needs: test
runs-on: ubuntu-latest
if: github.ref == 'refs/heads/main'

steps:
- uses: actions/checkout@v3

- name: Set up Docker Buildx
uses: docker/setup-buildx-action@v2

- name: Login to Docker Hub
uses: docker/login-action@v2
with:
username: ${{ secrets.DOCKER_USERNAME }}
password: ${{ secrets.DOCKER_PASSWORD }}

- name: Build and push API
uses: docker/build-push-action@v4
with:
context: .
file: docker/Dockerfile
push: true
tags: |
${{ secrets.DOCKER_USERNAME }}/event-scraper-api:latest
${{ secrets.DOCKER_USERNAME }}/event-scraper-api:${{ github.sha }}

- name: Build and push Worker
uses: docker/build-push-action@v4
with:
context: .
file: docker/Dockerfile.worker
push: true
tags: |
${{ secrets.DOCKER_USERNAME }}/event-scraper-worker:latest
${{ secrets.DOCKER_USERNAME }}/event-scraper-worker:${{ github.sha }}

deploy:
needs: build
runs-on: ubuntu-latest
if: github.ref == 'refs/heads/main'

steps:
- name: Deploy to production
run: |
echo "Add your deployment steps here"
# Example: SSH into server and pull new images
# ssh user@server 'cd /app && docker-compose pull && docker-compose up -d'
33 changes: 33 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
@@ -0,0 +1,33 @@
# dependencies
node_modules/
package-lock.json

# env variables
.env
.env.local
.env.production

# logs
logs/
*.log
npm-debug.log*

# OS files
.DS_Store
Thumbs.db

# IDE
.vscode/
.idea/
*.swp
*.swo

# test coverage
coverage/

# build
dist/
build/

# setup scripts
create-source-files.sh
17 changes: 17 additions & 0 deletions docker/Dockerfile
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
FROM node:18-alpine

WORKDIR /app

# install dependencis
COPY package*.json ./
RUN npm ci --only=production

# copy app code
COPY . .

# create logs directory
RUN mkdir -p logs

EXPOSE 3000

CMD ["node", "src/app.js"]
12 changes: 12 additions & 0 deletions docker/Dockerfile.worker
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
FROM node:18-alpine

WORKDIR /app

COPY package*.json ./
RUN npm ci --only=production

COPY . .

RUN mkdir -p logs

CMD ["node", "src/jobs/worker.js"]
74 changes: 74 additions & 0 deletions docker/docker-compose.prod.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,74 @@
version: '3.8'

services:
postgres:
image: postgres:15-alpine
environment:
POSTGRES_DB: ${DB_NAME}
POSTGRES_USER: ${DB_USER}
POSTGRES_PASSWORD: ${DB_PASSWORD}
volumes:
- postgres_data:/var/lib/postgresql/data
restart: always
networks:
- app-network

redis:
image: redis:7-alpine
volumes:
- redis_data:/data
restart: always
networks:
- app-network

api:
build:
context: ..
dockerfile: docker/Dockerfile
environment:
NODE_ENV: production
env_file:
- ../.env.production
depends_on:
- postgres
- redis
restart: always
networks:
- app-network

worker:
build:
context: ..
dockerfile: docker/Dockerfile.worker
environment:
NODE_ENV: production
env_file:
- ../.env.production
depends_on:
- postgres
- redis
restart: always
networks:
- app-network

nginx:
image: nginx:alpine
ports:
- "80:80"
- "443:443"
volumes:
- ./nginx.conf:/etc/nginx/nginx.conf:ro
- ./ssl:/etc/nginx/ssl:ro
depends_on:
- api
restart: always
networks:
- app-network

volumes:
postgres_data:
redis_data:

networks:
app-network:
driver: bridge
23 changes: 23 additions & 0 deletions docker/docker-compose.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
version: '3.8'
services:
postgres:
image: postgres:15-alpine
environment:
POSTGRES_DB: events_db
POSTGRES_USER: postgres
POSTGRES_PASSWORD: postgres
ports:
- "5432:5432"
volumes:
- postgres_data:/var/lib/postgresql/data

redis:
image: redis:7-alpine
ports:
- "6379:6379"
volumes:
- redis_data:/data

volumes:
postgres_data:
redis_data:
49 changes: 49 additions & 0 deletions docker/nginx.conf
Original file line number Diff line number Diff line change
@@ -0,0 +1,49 @@
events {
worker_connections 1024;
}

http {
upstream api_backend {
least_conn;
server api:3000;
}

# rate limiting
limit_req_zone $binary_remote_addr zone=api_limit:10m rate=10r/s;

server {
listen 80;
server_name localhost;

# scurity headers
add_header X-Frame-Options "SAMEORIGIN" always;
add_header X-Content-Type-Options "nosniff" always;
add_header X-XSS-Protection "1; mode=block" always;

# logging
access_log /var/log/nginx/access.log;
error_log /var/log/nginx/error.log;

location / {
limit_req zone = api_limit burst=20 nodelay;

proxy_pass http://api_backend;
proxy_http_version 1.1;

proxy_set_header Host $host;
proxy_set_header X-Real-IP $remote_addr;
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
proxy_set_header X-Forwarded-Proto $scheme;

# timeouts
proxy_connect_timeout 60s;
proxy_send_timeout 60s;
proxy_read_timeout 60s;
}

location /health {
proxy_pass http://api_backend/api/v1/health;
access_log off;
}
}
}
25 changes: 25 additions & 0 deletions migrations/001_create_events_table.sql
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
CREATE TABLE IF NOT EXISTS events (
id SERIAL PRIMARY KEY,

external_id VARCHAR(255) UNIQUE,
source VARCHAR(50) NOT NULL,
title VARCHAR(500) NOT NULL,
description TEXT,
start_date TIMESTAMP NOT NULL,
end_date TIMESTAMP,
location JSONB,
category VARCHAR(100),
url VARCHAR(1000),
image_url VARCHAR(1000),
is_free BOOLEAN DEFAULT false,
price DECIMAL(10,2),
organizer VARCHAR(255),
raw_data JSONB,

created_at TIMESTAMP DEFAULT NOW(),
updated_at TIMESTAMP DEFAULT NOW()
);

CREATE INDEX IF NOT EXISTS idx_events_start_date ON events(start_date);
CREATE INDEX IF NOT EXISTS idx_events_category ON events(category);
CREATE INDEX IF NOT EXISTS idx_events_source ON events(source);
Loading
Loading