mirror of
https://github.com/harivansh-afk/url-shortner.git
synced 2026-04-17 08:01:04 +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