mirror of
https://github.com/harivansh-afk/url-shortner.git
synced 2026-04-15 03:00:48 +00:00
init db and docker
This commit is contained in:
commit
1a980a7a70
5 changed files with 178 additions and 0 deletions
25
.gitignore
vendored
Normal file
25
.gitignore
vendored
Normal file
|
|
@ -0,0 +1,25 @@
|
|||
# Python
|
||||
__pycache__/
|
||||
*.py[cod]
|
||||
*$py.class
|
||||
.venv/
|
||||
venv/
|
||||
*.egg-info/
|
||||
|
||||
# Environment
|
||||
.env
|
||||
.env.local
|
||||
|
||||
# IDE
|
||||
.idea/
|
||||
.vscode/
|
||||
*.swp
|
||||
*.swo
|
||||
|
||||
# Testing
|
||||
.pytest_cache/
|
||||
.coverage
|
||||
htmlcov/
|
||||
|
||||
# Docker
|
||||
docker-compose.override.yml
|
||||
18
Dockerfile
Normal file
18
Dockerfile
Normal file
|
|
@ -0,0 +1,18 @@
|
|||
FROM python:3.12-slim
|
||||
|
||||
WORKDIR /app
|
||||
|
||||
# Install uv for fast package management
|
||||
RUN pip install uv
|
||||
|
||||
# Copy dependency files
|
||||
COPY pyproject.toml .
|
||||
|
||||
# Install dependencies
|
||||
RUN uv pip install --system -e .
|
||||
|
||||
# Copy application code
|
||||
COPY app/ app/
|
||||
|
||||
# Run with uvicorn
|
||||
CMD ["uvicorn", "app.main:app", "--host", "0.0.0.0", "--port", "8000"]
|
||||
43
db/init.sql
Normal file
43
db/init.sql
Normal file
|
|
@ -0,0 +1,43 @@
|
|||
-- URL Shortener Database Schema
|
||||
|
||||
CREATE TABLE IF NOT EXISTS urls (
|
||||
id BIGSERIAL PRIMARY KEY,
|
||||
short_code VARCHAR(10) UNIQUE NOT NULL,
|
||||
original_url TEXT NOT NULL,
|
||||
created_at TIMESTAMP WITH TIME ZONE DEFAULT NOW(),
|
||||
expires_at TIMESTAMP WITH TIME ZONE,
|
||||
click_count BIGINT DEFAULT 0,
|
||||
|
||||
-- Metadata
|
||||
user_agent TEXT,
|
||||
ip_address INET
|
||||
);
|
||||
|
||||
-- Index for fast lookups by short_code (most common query)
|
||||
CREATE INDEX idx_urls_short_code ON urls(short_code);
|
||||
|
||||
-- Index for cleanup of expired URLs
|
||||
CREATE INDEX idx_urls_expires_at ON urls(expires_at) WHERE expires_at IS NOT NULL;
|
||||
|
||||
-- Analytics table (for click tracking)
|
||||
CREATE TABLE IF NOT EXISTS clicks (
|
||||
id BIGSERIAL PRIMARY KEY,
|
||||
short_code VARCHAR(10) NOT NULL,
|
||||
clicked_at TIMESTAMP WITH TIME ZONE DEFAULT NOW(),
|
||||
ip_address INET,
|
||||
user_agent TEXT,
|
||||
referer TEXT,
|
||||
country_code VARCHAR(2)
|
||||
);
|
||||
|
||||
-- Partition-friendly index (clicks will be high volume)
|
||||
CREATE INDEX idx_clicks_short_code ON clicks(short_code);
|
||||
CREATE INDEX idx_clicks_clicked_at ON clicks(clicked_at);
|
||||
|
||||
-- Function to increment click count (atomic)
|
||||
CREATE OR REPLACE FUNCTION increment_click_count(code VARCHAR(10))
|
||||
RETURNS VOID AS $$
|
||||
BEGIN
|
||||
UPDATE urls SET click_count = click_count + 1 WHERE short_code = code;
|
||||
END;
|
||||
$$ LANGUAGE plpgsql;
|
||||
62
docker-compose.yml
Normal file
62
docker-compose.yml
Normal file
|
|
@ -0,0 +1,62 @@
|
|||
services:
|
||||
# PostgreSQL - Primary database
|
||||
postgres:
|
||||
image: postgres:16-alpine
|
||||
environment:
|
||||
POSTGRES_USER: urlshortner
|
||||
POSTGRES_PASSWORD: localdev
|
||||
POSTGRES_DB: urlshortner
|
||||
ports:
|
||||
- "5432:5432"
|
||||
volumes:
|
||||
- postgres_data:/var/lib/postgresql/data
|
||||
- ./db/init.sql:/docker-entrypoint-initdb.d/init.sql
|
||||
healthcheck:
|
||||
test: ["CMD-SHELL", "pg_isready -U urlshortner"]
|
||||
interval: 5s
|
||||
timeout: 5s
|
||||
retries: 5
|
||||
|
||||
# Redis - Caching layer
|
||||
redis:
|
||||
image: redis:7-alpine
|
||||
ports:
|
||||
- "6379:6379"
|
||||
volumes:
|
||||
- redis_data:/data
|
||||
healthcheck:
|
||||
test: ["CMD", "redis-cli", "ping"]
|
||||
interval: 5s
|
||||
timeout: 5s
|
||||
retries: 5
|
||||
|
||||
# API Server (can scale with --scale api=3)
|
||||
api:
|
||||
build:
|
||||
context: .
|
||||
dockerfile: Dockerfile
|
||||
environment:
|
||||
DATABASE_URL: postgresql://urlshortner:localdev@postgres:5432/urlshortner
|
||||
REDIS_URL: redis://redis:6379
|
||||
MACHINE_ID: 1
|
||||
depends_on:
|
||||
postgres:
|
||||
condition: service_healthy
|
||||
redis:
|
||||
condition: service_healthy
|
||||
ports:
|
||||
- "8000" # Random port mapping for scaling
|
||||
|
||||
# Nginx - Load balancer
|
||||
nginx:
|
||||
image: nginx:alpine
|
||||
ports:
|
||||
- "80:80"
|
||||
volumes:
|
||||
- ./nginx/nginx.conf:/etc/nginx/nginx.conf:ro
|
||||
depends_on:
|
||||
- api
|
||||
|
||||
volumes:
|
||||
postgres_data:
|
||||
redis_data:
|
||||
30
pyproject.toml
Normal file
30
pyproject.toml
Normal file
|
|
@ -0,0 +1,30 @@
|
|||
[project]
|
||||
name = "url-shortner"
|
||||
version = "0.1.0"
|
||||
description = "Distributed URL shortening service for learning system design"
|
||||
requires-python = ">=3.11"
|
||||
dependencies = [
|
||||
"fastapi>=0.109.0",
|
||||
"uvicorn[standard]>=0.27.0",
|
||||
"asyncpg>=0.29.0",
|
||||
"redis>=5.0.0",
|
||||
"pydantic>=2.5.0",
|
||||
]
|
||||
|
||||
[project.optional-dependencies]
|
||||
dev = [
|
||||
"pytest>=8.0.0",
|
||||
"pytest-asyncio>=0.23.0",
|
||||
"httpx>=0.26.0",
|
||||
"ruff>=0.1.0",
|
||||
]
|
||||
|
||||
[tool.ruff]
|
||||
line-length = 100
|
||||
target-version = "py311"
|
||||
|
||||
[tool.ruff.lint]
|
||||
select = ["E", "F", "I", "UP"]
|
||||
|
||||
[tool.pytest.ini_options]
|
||||
asyncio_mode = "auto"
|
||||
Loading…
Add table
Add a link
Reference in a new issue