From b3213ab86339d2e23aa2ddfd007c2aa07db96d11 Mon Sep 17 00:00:00 2001 From: Jakob Scheid Date: Fri, 19 Dec 2025 16:38:02 +0100 Subject: [PATCH] new file: Dockerfile new file: Makefile new file: database-migration/Dockerfile new file: database-migration/data.db new file: database-migration/db_migration.py new file: database-migration/migration.py new file: database-migration/requirements.txt new file: docker-compose.migration.yml new file: docker-compose.yml new file: main.py new file: requirements.txt --- Dockerfile | 7 +++ Makefile | 25 ++++++++ database-migration/Dockerfile | 6 ++ database-migration/data.db | Bin 0 -> 81920 bytes database-migration/db_migration.py | 38 +++++++++++++ database-migration/migration.py | 1 + database-migration/requirements.txt | 1 + docker-compose.migration.yml | 21 +++++++ docker-compose.yml | 27 +++++++++ main.py | 85 ++++++++++++++++++++++++++++ requirements.txt | 5 ++ 11 files changed, 216 insertions(+) create mode 100644 Dockerfile create mode 100644 Makefile create mode 100644 database-migration/Dockerfile create mode 100644 database-migration/data.db create mode 100644 database-migration/db_migration.py create mode 100644 database-migration/migration.py create mode 100644 database-migration/requirements.txt create mode 100644 docker-compose.migration.yml create mode 100644 docker-compose.yml create mode 100644 main.py create mode 100644 requirements.txt diff --git a/Dockerfile b/Dockerfile new file mode 100644 index 0000000..eaee6d4 --- /dev/null +++ b/Dockerfile @@ -0,0 +1,7 @@ +FROM python:3.13-slim +WORKDIR /app +COPY requirements.txt . +RUN pip install --no-cache-dir -r requirements.txt +COPY . . +EXPOSE 1234 +CMD ["python3", "main.py"] \ No newline at end of file diff --git a/Makefile b/Makefile new file mode 100644 index 0000000..55bc8bc --- /dev/null +++ b/Makefile @@ -0,0 +1,25 @@ +build: + podman-compose build + +reload: + podman rm authentication_app_1 + podman rm authentication_db_1 + podman rm authentication_migration_1 + +up: + podman-compose up --build + +down: + podman-compose down + +restart: + make down up + +migrate: + cp ../../../server/data/users/auth_data/basis_data/data.db database-migration/data.db +# make reload +# podman-compose -f docker-compose.yml -f docker-compose.migration.yml up --build + podman-compose -f docker-compose.migration.yml up --build + +migrate-test: + podman-compose -f docker-compose.migration.yml up --build \ No newline at end of file diff --git a/database-migration/Dockerfile b/database-migration/Dockerfile new file mode 100644 index 0000000..0d7072a --- /dev/null +++ b/database-migration/Dockerfile @@ -0,0 +1,6 @@ +FROM python:3.13-slim +WORKDIR /app/ +COPY requirements.txt . +RUN pip install --no-cache-dir -r requirements.txt +COPY . . +CMD ["python3", "db_migration_test2.py"] \ No newline at end of file diff --git a/database-migration/data.db b/database-migration/data.db new file mode 100644 index 0000000000000000000000000000000000000000..c5ed3911a0209580119fadb85a87f0a4ef99bfae GIT binary patch literal 81920 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 PW-~Ek(hU-a#ESn6+rn;J literal 0 HcmV?d00001 diff --git a/database-migration/db_migration.py b/database-migration/db_migration.py new file mode 100644 index 0000000..142a165 --- /dev/null +++ b/database-migration/db_migration.py @@ -0,0 +1,38 @@ +print('RUNNING!') + +import sqlite3 +import os +import psycopg + +print(os.getcwd()) +print(os.listdir()) + +with open('data.db', 'rb') as f: + data = f.read() + print(f'[DEBUG] Begin SQLite database') + print(data) + print(f'[DEBUG] End SQLite database') + + +conn = psycopg.connect( + host = 'db', + port = 5432, + dbname = os.getenv('POSTGRES_DB'), + user = os.getenv('POSTGRES_USER'), + password = os.getenv('POSTGRES_PASSWORD') +) + +cursor = conn.cursor() + +with sqlite3.connect('data.db') as sqlite3_conn: + sqlite_cursor = sqlite3_conn.cursor() + sqlite_cursor.execute('SELECT COUNT(*) FROM data') + number_of_users = sqlite_cursor.fetchone()[0] + for user in range(1, number_of_users + 1): + print(f'Migrating user {user} ...') + sqlite_cursor.execute('SELECT (id, email, remail, phone, password) FROM data WHERE id = ?', (user,)) + cursor.execute('INSERT INTO users (id, email, remail, phone, password) VALUES (%s, %s, %s, %s, %s)', tuple(sqlite_cursor.fetchone())) + cursor.execute('SELECT setval(pg_get_serial_sequence(\'users\', \'id\'), (SELECT MAX(id) FROM users), true)') + sqlite_cursor.close() + +print('[INFO] Migration completed') \ No newline at end of file diff --git a/database-migration/migration.py b/database-migration/migration.py new file mode 100644 index 0000000..8b13789 --- /dev/null +++ b/database-migration/migration.py @@ -0,0 +1 @@ + diff --git a/database-migration/requirements.txt b/database-migration/requirements.txt new file mode 100644 index 0000000..0308453 --- /dev/null +++ b/database-migration/requirements.txt @@ -0,0 +1 @@ +psycopg \ No newline at end of file diff --git a/docker-compose.migration.yml b/docker-compose.migration.yml new file mode 100644 index 0000000..60c2eab --- /dev/null +++ b/docker-compose.migration.yml @@ -0,0 +1,21 @@ +version: "3.9" + +services: + db: + image: postgres:15 + environment: + - DB_URL=postgresql://${POSTGRES_USER}:${POSTGRES_PASSWORD}@db:5432/${POSTGRES_DB} + env_file: + - .env + volumes: + - /srv/data/db/services/authentication:/var/lib/postgresql/data + migration: + build: database-migration + image: python:3.11-slim + environment: + - DB_URL=postgresql://${POSTGRES_USER}:${POSTGRES_PASSWORD}@db:5432/${POSTGRES_DB} + env_file: + - .env + depends_on: + - db + command: python3 db_migration_test1.py \ No newline at end of file diff --git a/docker-compose.yml b/docker-compose.yml new file mode 100644 index 0000000..3dc542b --- /dev/null +++ b/docker-compose.yml @@ -0,0 +1,27 @@ +version: "3.9" + +services: + app: + build: . + image: python:3.11-slim + working_dir: /app + volumes: + - .:/app + command: python3 main.py + ports: + - "1234:1234" + depends_on: + - db + environment: + - DB_URL=postgresql://${POSTGRES_USER}:${POSTGRES_PASSWORD}@db:5432/${POSTGRES_DB} + env_file: + - .env + + db: + image: postgres:15 + environment: + - DB_URL=postgresql://${POSTGRES_USER}:${POSTGRES_PASSWORD}@db:5432/${POSTGRES_DB} + env_file: + - .env + volumes: + - /srv/data/db/services/authentication:/var/lib/postgresql/data \ No newline at end of file diff --git a/main.py b/main.py new file mode 100644 index 0000000..e9da286 --- /dev/null +++ b/main.py @@ -0,0 +1,85 @@ +from fastapi import FastAPI, Depends +from psycopg_pool import AsyncConnectionPool +import uvicorn +import os +from pydantic import BaseModel + +class Item(BaseModel): + name: str + description: str + reporter: str + priority: int + is_stupid: bool + +app = FastAPI() +pool: AsyncConnectionPool + + +@app.on_event('startup') +async def on_startup(): + global pool + pool = AsyncConnectionPool( + os.getenv('DB_URL'), + min_size=5, + max_size=40 + ) + await pool.open() + print('[INFO] Started connection pool') + + async with pool.connection() as conn: + async with conn.cursor() as cursor: + await cursor.execute('CREATE TABLE IF NOT EXISTS users (id SERIAL PRIMARY KEY, email VARCHAR(64), remail VARCHAR(64), phone VARCHAR(16), password VARCHAR(128))') + print('[INFO] Database initialized') + await conn.commit() + +@app.on_event('shutdown') +async def on_shutdown(): + await pool.close() + print('[INFO] Closed connection pool') + +async def get_conn(): + async with pool.connection() as conn: + yield conn + +@app.get('/') +def read_root(): + return { + 'message': 'microservice is running' + } + +@app.post('/item') +async def create_item(item: Item, conn = Depends(get_conn)): + async with conn.cursor() as cursor: + await cursor.execute('INSERT INTO items (name, description, reporter, priority, is_stupid) VALUES (%s, %s, %s, %s, %s) RETURNING *', (item.name, item.description, item.reporter, item.priority, item.is_stupid)) + i = await cursor.fetchone() + return {'id': i[0], 'description' : i[1], 'reporter': i[2], 'priority': i[3], 'is_stupid': i[4]} + +@app.get('/item/{item_id}') +async def read_item(item_id, conn = Depends(get_conn)): + async with conn.cursor() as cursor: + await cursor.execute('SELECT * FROM items WHERE id = %s', (item_id,)) + i = await cursor.fetchone() + return {'id': i[0], 'name' : i[1], 'description': i[2], 'reporter': i[3], 'priority': i[4], 'is_stupid': i[5]} + +@app.get('/items') +async def read_item(conn = Depends(get_conn)): + async with conn.cursor() as cursor: + await cursor.execute('SELECT * FROM items') + items = await cursor.fetchall() + return [{'id': i[0], 'description' : i[1], 'reporter': i[2], 'priority': i[3], 'is_stupid': i[4]} for i in items] + +@app.get('/datadir') +async def get_datadir(conn = Depends(get_conn)): + async with conn.cursor() as cursor: + await cursor.execute('SHOW data_directory') + return {'res': await cursor.fetchall()} + +if __name__ == '__main__': + uvicorn.run( + 'main:app', + host = '0.0.0.0', + port = 1234, + reload = True, + reload_dirs = ['/app'], + server_header = False + ) \ No newline at end of file diff --git a/requirements.txt b/requirements.txt new file mode 100644 index 0000000..c62b07c --- /dev/null +++ b/requirements.txt @@ -0,0 +1,5 @@ +fastapi +psycopg_pool +uvicorn +psycopg[binary] +pydantic \ No newline at end of file