From 935002d9f60fcc5bafe62e691b27b565660d0695 Mon Sep 17 00:00:00 2001 From: Wei Zang Date: Sun, 26 Apr 2026 18:23:23 +0100 Subject: [PATCH 1/5] Bump version to 3.0.2; use linkedin.csv Update package version to 3.0.2 and change the CSV import path in the LinkedIn queue endpoint to reference linkedin.csv instead of linkedin_sample.csv. This switches the endpoint to use the actual data file (and preserves the 404 check) likely preparing the app for a new release or production data import. --- app/__init__.py | 2 +- app/api/queue/csv/linkedin.py | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/app/__init__.py b/app/__init__.py index 3e04420..d47177e 100644 --- a/app/__init__.py +++ b/app/__init__.py @@ -1,4 +1,4 @@ """Python° - FastAPI, Postgres, tsvector""" # Current Version -__version__ = "3.0.1" +__version__ = "3.0.2" diff --git a/app/api/queue/csv/linkedin.py b/app/api/queue/csv/linkedin.py index 5d1c43a..0efc35e 100644 --- a/app/api/queue/csv/linkedin.py +++ b/app/api/queue/csv/linkedin.py @@ -10,7 +10,7 @@ @router.post("/queue/csv/linkedin") def import_linkedin_csv() -> dict: """POST /queue/csv/linkedin: Import data from linkedin.csv into the queue table, robust for large files.""" - csv_path = os.path.join(os.path.dirname(__file__), "../csv/linkedin/linkedin_sample.csv") + csv_path = os.path.join(os.path.dirname(__file__), "../csv/linkedin/linkedin.csv") if not os.path.exists(csv_path): raise HTTPException(status_code=404, detail="linkedin.csv not found") try: From fbf6c511e05d154c9d25eb2486f912679f3fe92e Mon Sep 17 00:00:00 2001 From: Wei Zang Date: Mon, 27 Apr 2026 11:03:18 +0100 Subject: [PATCH 2/5] Bump version to 3.0.3 and add GitHub API README Update package version from 3.0.2 to 3.0.3. Add app/api/github/README.md documenting the planned GitHub route that will fetch data via the GitHub API and store it in Postgres (database tables not created yet). --- app/__init__.py | 2 +- app/api/github/README.md | 4 ++++ 2 files changed, 5 insertions(+), 1 deletion(-) create mode 100644 app/api/github/README.md diff --git a/app/__init__.py b/app/__init__.py index d47177e..9e5f6b2 100644 --- a/app/__init__.py +++ b/app/__init__.py @@ -1,4 +1,4 @@ """Python° - FastAPI, Postgres, tsvector""" # Current Version -__version__ = "3.0.2" +__version__ = "3.0.3" diff --git a/app/api/github/README.md b/app/api/github/README.md new file mode 100644 index 0000000..d163ac0 --- /dev/null +++ b/app/api/github/README.md @@ -0,0 +1,4 @@ +## GitHub + +We are going to use this route to get data from our GitHub account using their API. We'll be storing that info in the Postgres DB, but have not created any tables yet. + From b34ee2e1e862a9c1087012e1c123bfe3d0e924d9 Mon Sep 17 00:00:00 2001 From: Wei Zang Date: Mon, 27 Apr 2026 11:05:51 +0100 Subject: [PATCH 3/5] Standardize meta keys and rename title to message Normalize API meta structure: root() now returns "base" (not "base_url") and no longer emits a redundant timestamp; removed the unused epoch variable. make_meta() signature changed from title->message and its returned dict uses "message" and includes "time", "base", "version", and "severity" to provide a consistent meta payload for responses. --- app/api/root.py | 4 +--- app/utils/make_meta.py | 6 +++--- 2 files changed, 4 insertions(+), 6 deletions(-) diff --git a/app/api/root.py b/app/api/root.py index 5ed493d..129aa50 100644 --- a/app/api/root.py +++ b/app/api/root.py @@ -11,12 +11,10 @@ def root() -> dict: """"Python°""" load_dotenv() base_url = os.getenv("BASE_URL", "http://localhost:8000") - epoch = int(time.time() * 1000) meta = { "title": "Python°", "version": __version__, - "base_url": base_url, - "time": epoch, + "base": base_url, } return {"meta": meta} diff --git a/app/utils/make_meta.py b/app/utils/make_meta.py index 0a5e3bd..3107d87 100644 --- a/app/utils/make_meta.py +++ b/app/utils/make_meta.py @@ -2,14 +2,14 @@ import time from app import __version__ -def make_meta(severity: str, title: str) -> dict: - """Create a standard meta dictionary for API responses.""" +def make_meta(severity: str, message: str) -> dict: + """Creates a standard meta dictionary for API responses.""" base_url = os.getenv("BASE_URL", "http://localhost:8000") epoch = int(time.time() * 1000) return { "version": __version__, "time": epoch, "severity": severity, - "title": title, + "message": message, "base": base_url, } From 1e537211232065e83f9ce512704ac579d56f27a5 Mon Sep 17 00:00:00 2001 From: Wei Zang Date: Mon, 27 Apr 2026 11:14:58 +0100 Subject: [PATCH 4/5] Add GitHub metadata API and router Introduce a new GitHub API endpoint and wire its router into the application routes. Adds app/api/github/github.py implementing GET /github which authenticates via get_api_key, queries the database for record count and column names (information_schema) using get_db_connection_direct, and returns structured meta/data (using make_meta). Adds app/api/github/__init__.py to re-export the router and updates app/api/routes.py to include the github router. Includes basic DB error handling that returns an error meta. --- app/api/github/__init__.py | 3 +++ app/api/github/github.py | 40 ++++++++++++++++++++++++++++++++++++++ app/api/routes.py | 4 +++- 3 files changed, 46 insertions(+), 1 deletion(-) create mode 100644 app/api/github/__init__.py create mode 100644 app/api/github/github.py diff --git a/app/api/github/__init__.py b/app/api/github/__init__.py new file mode 100644 index 0000000..c9f9e34 --- /dev/null +++ b/app/api/github/__init__.py @@ -0,0 +1,3 @@ +"""GitHub Routes""" + +from .github import router as github_router diff --git a/app/api/github/github.py b/app/api/github/github.py new file mode 100644 index 0000000..a07e486 --- /dev/null +++ b/app/api/github/github.py @@ -0,0 +1,40 @@ +import os +import hashlib +from fastapi import APIRouter, HTTPException, Depends +from app.utils.make_meta import make_meta +from app.utils.db import get_db_connection_direct +from app.utils.api_key_auth import get_api_key + +router = APIRouter() + +@router.get("/github") +def get_github(api_key: str = Depends(get_api_key)) -> dict: + """GET /github: Return GitHub data.""" + try: + conn = get_db_connection_direct() + cur = conn.cursor() + cur.execute("SELECT COUNT(*) FROM github;") + count_row = cur.fetchone() + record_count = count_row[0] if count_row and count_row[0] is not None else 0 + cur.execute( + """ + SELECT column_name + FROM information_schema.columns + WHERE table_schema = 'public' AND table_name = 'github' + ORDER BY ordinal_position; + """ + ) + columns = [row[0] for row in cur.fetchall()] + cur.close() + conn.close() + meta = make_meta("success", "GitHub table metadata") + return { + "meta": meta, + "data": { + "record_count": record_count, + "columns": columns, + }, + } + except Exception as e: + meta = make_meta("error", f"DB error: {str(e)}") + return {"meta": meta, "data": {}} diff --git a/app/api/routes.py b/app/api/routes.py index 82234f3..b85cdf8 100644 --- a/app/api/routes.py +++ b/app/api/routes.py @@ -13,6 +13,7 @@ from app.api.prospects.prospects import router as prospects_router from app.api.orders.orders import router as orders_router from app.api.queue import router as queue_router +from app.api.github import github_router router.include_router(root_router) router.include_router(resend_router) @@ -21,4 +22,5 @@ router.include_router(prompts_empty_router) router.include_router(prospects_router) router.include_router(orders_router) -router.include_router(queue_router) \ No newline at end of file +router.include_router(queue_router) +router.include_router(github_router) \ No newline at end of file From 561330a63208bea4a29ac17071c86b339754c55b Mon Sep 17 00:00:00 2001 From: Wei Zang Date: Mon, 27 Apr 2026 11:31:44 +0100 Subject: [PATCH 5/5] Add GitHub table creation, seed, and routes Introduce DB schema and seeding for GitHub ingestion and wire up routes. Adds a POST /api/github/createtable route (app/api/github/sql/create_tables.py) that creates github_accounts, github_repos, github_gists, github_projects, and github_resources tables with indexes. Adds a seed script (app/api/github/sql/seed.py) to insert realistic example rows for accounts, repos, gists, projects, and resources. Updates router initialization (app/api/github/__init__.py) to include the new create-table router. Refactors GET /github (app/api/github/github.py) to return counts and recent rows for each GitHub table, adds a helper to fetch table data, and ensures DB cursors/connections are closed in a finally block. Also updates README to document the create-table route and proposed table design. --- app/api/github/README.md | 34 +++ app/api/github/__init__.py | 8 +- app/api/github/github.py | 64 +++--- app/api/github/sql/create_tables.py | 156 +++++++++++++ app/api/github/sql/seed.py | 332 ++++++++++++++++++++++++++++ 5 files changed, 565 insertions(+), 29 deletions(-) create mode 100644 app/api/github/sql/create_tables.py create mode 100644 app/api/github/sql/seed.py diff --git a/app/api/github/README.md b/app/api/github/README.md index d163ac0..c780d36 100644 --- a/app/api/github/README.md +++ b/app/api/github/README.md @@ -2,3 +2,37 @@ We are going to use this route to get data from our GitHub account using their API. We'll be storing that info in the Postgres DB, but have not created any tables yet. +### Create Table Route + +**POST /api/github/createtable** + +Hits this URL to create the GitHub tables in Postgres. Each table is created with `IF NOT EXISTS`, so it is safe to call multiple times — existing tables and data will not be affected. + +### Proposed Table Design + +1. github_accounts +- One row per GitHub account/user profile. +- Stores account identity fields and full raw payload. + +2. github_repos +- One row per repository. +- Stores common repo analytics fields plus raw JSON payload. + +3. github_gists +- One row per gist. +- Stores gist metadata plus raw JSON payload. + +4. github_projects +- One row per project. +- Stores project metadata plus raw JSON payload. + +5. github_resources +- Generic catch-all for any future GitHub resource type. +- Supports "everything else" from the API without migrations for every new object type. + +### Why this shape works + +- Normalized for common entities you asked for: repos, gists, projects. +- Flexible for all additional GitHub objects through jsonb payload storage. +- Indexes on resource type, account, and payload for filtering and search. + diff --git a/app/api/github/__init__.py b/app/api/github/__init__.py index c9f9e34..ccd498c 100644 --- a/app/api/github/__init__.py +++ b/app/api/github/__init__.py @@ -1,3 +1,9 @@ """GitHub Routes""" -from .github import router as github_router +from fastapi import APIRouter +from .github import router as _github_router +from .sql.create_tables import router as _create_tables_router + +github_router = APIRouter() +github_router.include_router(_github_router) +github_router.include_router(_create_tables_router) diff --git a/app/api/github/github.py b/app/api/github/github.py index a07e486..a30a63e 100644 --- a/app/api/github/github.py +++ b/app/api/github/github.py @@ -1,40 +1,48 @@ -import os -import hashlib -from fastapi import APIRouter, HTTPException, Depends +from fastapi import APIRouter, Depends from app.utils.make_meta import make_meta from app.utils.db import get_db_connection_direct from app.utils.api_key_auth import get_api_key router = APIRouter() +_TABLES = [ + "github_accounts", + "github_repos", + "github_gists", + "github_projects", + "github_resources", +] + + +def _fetch_table(cur, table: str) -> dict: + cur.execute(f"SELECT COUNT(*) FROM {table};") + row = cur.fetchone() + count = row[0] if row and row[0] is not None else 0 + cur.execute(f"SELECT * FROM {table} ORDER BY id DESC LIMIT 100;") + if cur.description: + columns = [desc[0] for desc in cur.description] + rows = [dict(zip(columns, r)) for r in cur.fetchall()] + else: + rows = [] + return {"count": count, "rows": rows} + + @router.get("/github") def get_github(api_key: str = Depends(get_api_key)) -> dict: - """GET /github: Return GitHub data.""" + """GET /github: Return counts and records from all GitHub tables.""" + conn = None + cur = None try: conn = get_db_connection_direct() cur = conn.cursor() - cur.execute("SELECT COUNT(*) FROM github;") - count_row = cur.fetchone() - record_count = count_row[0] if count_row and count_row[0] is not None else 0 - cur.execute( - """ - SELECT column_name - FROM information_schema.columns - WHERE table_schema = 'public' AND table_name = 'github' - ORDER BY ordinal_position; - """ - ) - columns = [row[0] for row in cur.fetchall()] - cur.close() - conn.close() - meta = make_meta("success", "GitHub table metadata") - return { - "meta": meta, - "data": { - "record_count": record_count, - "columns": columns, - }, - } + data = {table: _fetch_table(cur, table) for table in _TABLES} + return {"meta": make_meta("success", "GitHub data"), "data": data} except Exception as e: - meta = make_meta("error", f"DB error: {str(e)}") - return {"meta": meta, "data": {}} + return {"meta": make_meta("error", f"DB error: {str(e)}"), "data": {}} + finally: + if cur is not None: + cur.close() + if conn is not None: + conn.close() + + diff --git a/app/api/github/sql/create_tables.py b/app/api/github/sql/create_tables.py new file mode 100644 index 0000000..522232d --- /dev/null +++ b/app/api/github/sql/create_tables.py @@ -0,0 +1,156 @@ +from fastapi import APIRouter, HTTPException, Depends +from app.utils.make_meta import make_meta +from app.utils.db import get_db_connection_direct +from app.utils.api_key_auth import get_api_key + +router = APIRouter() + +_SQL = [ + """ + CREATE TABLE IF NOT EXISTS github_accounts ( + id SERIAL PRIMARY KEY, + github_user_id BIGINT UNIQUE, + login TEXT UNIQUE, + name TEXT, + email TEXT, + company TEXT, + blog TEXT, + location TEXT, + bio TEXT, + avatar_url TEXT, + html_url TEXT, + payload JSONB NOT NULL DEFAULT '{}'::jsonb, + last_synced_at TIMESTAMPTZ, + created_at TIMESTAMPTZ NOT NULL DEFAULT NOW(), + updated_at TIMESTAMPTZ NOT NULL DEFAULT NOW() + ); + """, + """ + CREATE TABLE IF NOT EXISTS github_repos ( + id BIGSERIAL PRIMARY KEY, + github_repo_id BIGINT UNIQUE NOT NULL, + account_login TEXT, + name TEXT NOT NULL, + full_name TEXT, + private BOOLEAN NOT NULL DEFAULT FALSE, + fork BOOLEAN NOT NULL DEFAULT FALSE, + archived BOOLEAN NOT NULL DEFAULT FALSE, + disabled BOOLEAN NOT NULL DEFAULT FALSE, + default_branch TEXT, + language TEXT, + stargazers_count INTEGER, + watchers_count INTEGER, + forks_count INTEGER, + open_issues_count INTEGER, + size_kb INTEGER, + pushed_at TIMESTAMPTZ, + created_at_github TIMESTAMPTZ, + updated_at_github TIMESTAMPTZ, + html_url TEXT, + api_url TEXT, + payload JSONB NOT NULL DEFAULT '{}'::jsonb, + synced_at TIMESTAMPTZ NOT NULL DEFAULT NOW(), + created_at TIMESTAMPTZ NOT NULL DEFAULT NOW(), + updated_at TIMESTAMPTZ NOT NULL DEFAULT NOW() + ); + """, + """ + CREATE TABLE IF NOT EXISTS github_gists ( + id BIGSERIAL PRIMARY KEY, + gist_id TEXT UNIQUE NOT NULL, + owner_login TEXT, + description TEXT, + public BOOLEAN, + files_count INTEGER, + comments_count INTEGER, + html_url TEXT, + api_url TEXT, + created_at_github TIMESTAMPTZ, + updated_at_github TIMESTAMPTZ, + payload JSONB NOT NULL DEFAULT '{}'::jsonb, + synced_at TIMESTAMPTZ NOT NULL DEFAULT NOW(), + created_at TIMESTAMPTZ NOT NULL DEFAULT NOW(), + updated_at TIMESTAMPTZ NOT NULL DEFAULT NOW() + ); + """, + """ + CREATE TABLE IF NOT EXISTS github_projects ( + id BIGSERIAL PRIMARY KEY, + github_project_id BIGINT UNIQUE NOT NULL, + owner_login TEXT, + owner_type TEXT, + name TEXT NOT NULL, + body TEXT, + state TEXT, + number INTEGER, + html_url TEXT, + api_url TEXT, + created_at_github TIMESTAMPTZ, + updated_at_github TIMESTAMPTZ, + payload JSONB NOT NULL DEFAULT '{}'::jsonb, + synced_at TIMESTAMPTZ NOT NULL DEFAULT NOW(), + created_at TIMESTAMPTZ NOT NULL DEFAULT NOW(), + updated_at TIMESTAMPTZ NOT NULL DEFAULT NOW() + ); + """, + """ + CREATE TABLE IF NOT EXISTS github_resources ( + id BIGSERIAL PRIMARY KEY, + account_login TEXT, + resource_type TEXT NOT NULL, + resource_id TEXT NOT NULL, + resource_name TEXT, + resource_url TEXT, + is_private BOOLEAN, + state TEXT, + created_at_github TIMESTAMPTZ, + updated_at_github TIMESTAMPTZ, + payload JSONB NOT NULL DEFAULT '{}'::jsonb, + synced_at TIMESTAMPTZ NOT NULL DEFAULT NOW(), + created_at TIMESTAMPTZ NOT NULL DEFAULT NOW(), + updated_at TIMESTAMPTZ NOT NULL DEFAULT NOW(), + UNIQUE (resource_type, resource_id) + ); + """, + "CREATE INDEX IF NOT EXISTS idx_github_resources_type ON github_resources (resource_type);", + "CREATE INDEX IF NOT EXISTS idx_github_resources_account ON github_resources (account_login);", + "CREATE INDEX IF NOT EXISTS idx_github_resources_payload ON github_resources USING GIN (payload);", + "CREATE INDEX IF NOT EXISTS idx_github_repos_payload ON github_repos USING GIN (payload);", + "CREATE INDEX IF NOT EXISTS idx_github_gists_payload ON github_gists USING GIN (payload);", + "CREATE INDEX IF NOT EXISTS idx_github_projects_payload ON github_projects USING GIN (payload);", +] + + +@router.post("/api/github/createtable") +def create_github_tables(api_key: str = Depends(get_api_key)) -> dict: + """POST /api/github/createtable: Create GitHub ingestion tables.""" + conn = None + cur = None + try: + conn = get_db_connection_direct() + cur = conn.cursor() + for statement in _SQL: + cur.execute(statement) + conn.commit() + meta = make_meta("success", "GitHub tables created") + return { + "meta": meta, + "data": { + "tables": [ + "github_accounts", + "github_repos", + "github_gists", + "github_projects", + "github_resources", + ] + }, + } + except Exception as e: + if conn is not None: + conn.rollback() + raise HTTPException(status_code=500, detail=str(e)) + finally: + if cur is not None: + cur.close() + if conn is not None: + conn.close() diff --git a/app/api/github/sql/seed.py b/app/api/github/sql/seed.py new file mode 100644 index 0000000..abc9c11 --- /dev/null +++ b/app/api/github/sql/seed.py @@ -0,0 +1,332 @@ +"""Seed GitHub tables with realistic fake data.""" + +from app.utils.db import get_db_connection_direct +import json + +_ACCOUNTS = [ + { + "github_user_id": 1024001, + "login": "milkyway-dev", + "name": "Alex Milky", + "email": "alex@milkyway.dev", + "company": "Milky Way Digital", + "blog": "https://milkyway.dev", + "location": "San Francisco, CA", + "bio": "Full-stack developer. Open source enthusiast.", + "avatar_url": "https://avatars.githubusercontent.com/u/1024001", + "html_url": "https://github.com/milkyway-dev", + "payload": {"public_repos": 42, "followers": 318, "following": 97}, + }, + { + "github_user_id": 1024002, + "login": "nova-studios", + "name": "Nova Studios", + "email": "hello@novastudios.io", + "company": "Nova Studios LLC", + "blog": "https://novastudios.io", + "location": "Austin, TX", + "bio": "We build tools for creative teams.", + "avatar_url": "https://avatars.githubusercontent.com/u/1024002", + "html_url": "https://github.com/nova-studios", + "payload": {"public_repos": 17, "followers": 540, "following": 12}, + }, + { + "github_user_id": 1024003, + "login": "byte-foundry", + "name": "Byte Foundry", + "email": "team@bytefoundry.io", + "company": "Byte Foundry Inc.", + "blog": "https://bytefoundry.io", + "location": "London, UK", + "bio": "Crafting high-performance APIs since 2015.", + "avatar_url": "https://avatars.githubusercontent.com/u/1024003", + "html_url": "https://github.com/byte-foundry", + "payload": {"public_repos": 29, "followers": 210, "following": 45}, + }, +] + +_REPOS = [ + { + "github_repo_id": 5001001, + "account_login": "milkyway-dev", + "name": "fastapi-starter", + "full_name": "milkyway-dev/fastapi-starter", + "private": False, + "fork": False, + "archived": False, + "disabled": False, + "default_branch": "main", + "language": "Python", + "stargazers_count": 284, + "watchers_count": 284, + "forks_count": 61, + "open_issues_count": 4, + "size_kb": 1240, + "pushed_at": "2026-04-20T14:32:00Z", + "created_at_github": "2023-06-01T09:00:00Z", + "updated_at_github": "2026-04-20T14:32:00Z", + "html_url": "https://github.com/milkyway-dev/fastapi-starter", + "api_url": "https://api.github.com/repos/milkyway-dev/fastapi-starter", + "payload": {"topics": ["fastapi", "python", "starter-template"]}, + }, + { + "github_repo_id": 5001002, + "account_login": "nova-studios", + "name": "canvas-kit", + "full_name": "nova-studios/canvas-kit", + "private": False, + "fork": False, + "archived": False, + "disabled": False, + "default_branch": "main", + "language": "TypeScript", + "stargazers_count": 912, + "watchers_count": 912, + "forks_count": 143, + "open_issues_count": 11, + "size_kb": 8800, + "pushed_at": "2026-04-25T10:15:00Z", + "created_at_github": "2022-11-14T08:00:00Z", + "updated_at_github": "2026-04-25T10:15:00Z", + "html_url": "https://github.com/nova-studios/canvas-kit", + "api_url": "https://api.github.com/repos/nova-studios/canvas-kit", + "payload": {"topics": ["canvas", "design-system", "typescript"]}, + }, + { + "github_repo_id": 5001003, + "account_login": "byte-foundry", + "name": "pg-lightning", + "full_name": "byte-foundry/pg-lightning", + "private": False, + "fork": False, + "archived": False, + "disabled": False, + "default_branch": "main", + "language": "Go", + "stargazers_count": 1537, + "watchers_count": 1537, + "forks_count": 220, + "open_issues_count": 7, + "size_kb": 4300, + "pushed_at": "2026-04-22T18:45:00Z", + "created_at_github": "2021-03-08T12:00:00Z", + "updated_at_github": "2026-04-22T18:45:00Z", + "html_url": "https://github.com/byte-foundry/pg-lightning", + "api_url": "https://api.github.com/repos/byte-foundry/pg-lightning", + "payload": {"topics": ["postgres", "performance", "go"]}, + }, +] + +_GISTS = [ + { + "gist_id": "a1b2c3d4e5f6a1b2c3d4e5f6a1b2c3d4", + "owner_login": "milkyway-dev", + "description": "FastAPI dependency injection pattern for Postgres", + "public": True, + "files_count": 2, + "comments_count": 5, + "html_url": "https://gist.github.com/milkyway-dev/a1b2c3d4e5f6a1b2c3d4e5f6a1b2c3d4", + "api_url": "https://api.github.com/gists/a1b2c3d4e5f6a1b2c3d4e5f6a1b2c3d4", + "created_at_github": "2025-09-10T11:00:00Z", + "updated_at_github": "2025-09-12T08:30:00Z", + "payload": {"forks_count": 3, "files": ["db.py", "example.py"]}, + }, + { + "gist_id": "b2c3d4e5f6a1b2c3d4e5f6a1b2c3d4e5", + "owner_login": "nova-studios", + "description": "Tailwind CSS dark mode toggle snippet", + "public": True, + "files_count": 1, + "comments_count": 2, + "html_url": "https://gist.github.com/nova-studios/b2c3d4e5f6a1b2c3d4e5f6a1b2c3d4e5", + "api_url": "https://api.github.com/gists/b2c3d4e5f6a1b2c3d4e5f6a1b2c3d4e5", + "created_at_github": "2025-12-01T15:20:00Z", + "updated_at_github": "2025-12-03T09:00:00Z", + "payload": {"forks_count": 1, "files": ["dark-mode.js"]}, + }, + { + "gist_id": "c3d4e5f6a1b2c3d4e5f6a1b2c3d4e5f6", + "owner_login": "byte-foundry", + "description": "Go connection pool tuning for high-throughput Postgres", + "public": False, + "files_count": 3, + "comments_count": 0, + "html_url": "https://gist.github.com/byte-foundry/c3d4e5f6a1b2c3d4e5f6a1b2c3d4e5f6", + "api_url": "https://api.github.com/gists/c3d4e5f6a1b2c3d4e5f6a1b2c3d4e5f6", + "created_at_github": "2026-01-18T07:45:00Z", + "updated_at_github": "2026-01-18T07:45:00Z", + "payload": {"forks_count": 0, "files": ["pool.go", "config.go", "README.md"]}, + }, +] + +_PROJECTS = [ + { + "github_project_id": 9001001, + "owner_login": "milkyway-dev", + "owner_type": "user", + "name": "API Roadmap 2026", + "body": "Tracking all planned API features and improvements for 2026.", + "state": "open", + "number": 1, + "html_url": "https://github.com/users/milkyway-dev/projects/1", + "api_url": "https://api.github.com/projects/9001001", + "created_at_github": "2025-12-15T10:00:00Z", + "updated_at_github": "2026-04-18T14:00:00Z", + "payload": {"columns_count": 4}, + }, + { + "github_project_id": 9001002, + "owner_login": "nova-studios", + "owner_type": "organization", + "name": "Canvas Kit v3", + "body": "Planning board for the Canvas Kit v3 major release.", + "state": "open", + "number": 3, + "html_url": "https://github.com/orgs/nova-studios/projects/3", + "api_url": "https://api.github.com/projects/9001002", + "created_at_github": "2026-01-05T09:30:00Z", + "updated_at_github": "2026-04-23T11:00:00Z", + "payload": {"columns_count": 5}, + }, + { + "github_project_id": 9001003, + "owner_login": "byte-foundry", + "owner_type": "organization", + "name": "Q2 Infrastructure Sprint", + "body": "DevOps and infrastructure tasks for Q2 2026.", + "state": "closed", + "number": 7, + "html_url": "https://github.com/orgs/byte-foundry/projects/7", + "api_url": "https://api.github.com/projects/9001003", + "created_at_github": "2026-03-01T08:00:00Z", + "updated_at_github": "2026-04-01T17:30:00Z", + "payload": {"columns_count": 3}, + }, +] + +_RESOURCES = [ + { + "account_login": "milkyway-dev", + "resource_type": "release", + "resource_id": "rel-milkyway-001", + "resource_name": "v2.4.0", + "resource_url": "https://github.com/milkyway-dev/fastapi-starter/releases/tag/v2.4.0", + "is_private": False, + "state": "published", + "created_at_github": "2026-03-10T12:00:00Z", + "updated_at_github": "2026-03-10T12:00:00Z", + "payload": {"tag_name": "v2.4.0", "prerelease": False, "assets_count": 2}, + }, + { + "account_login": "nova-studios", + "resource_type": "workflow", + "resource_id": "wf-nova-042", + "resource_name": "CI / Test Suite", + "resource_url": "https://github.com/nova-studios/canvas-kit/actions/workflows/ci.yml", + "is_private": False, + "state": "active", + "created_at_github": "2023-01-20T09:00:00Z", + "updated_at_github": "2026-04-25T10:15:00Z", + "payload": {"path": ".github/workflows/ci.yml", "badge_url": "https://github.com/nova-studios/canvas-kit/actions/workflows/ci.yml/badge.svg"}, + }, + { + "account_login": "byte-foundry", + "resource_type": "discussion", + "resource_id": "disc-byte-019", + "resource_name": "RFC: Connection pool strategy", + "resource_url": "https://github.com/byte-foundry/pg-lightning/discussions/19", + "is_private": False, + "state": "open", + "created_at_github": "2026-02-14T16:00:00Z", + "updated_at_github": "2026-04-10T08:20:00Z", + "payload": {"category": "Ideas", "answer_chosen": False, "comments_count": 12}, + }, +] + + +def seed(): + conn = get_db_connection_direct() + cur = conn.cursor() + + for r in _ACCOUNTS: + cur.execute( + """ + INSERT INTO github_accounts + (github_user_id, login, name, email, company, blog, location, bio, + avatar_url, html_url, payload) + VALUES (%s,%s,%s,%s,%s,%s,%s,%s,%s,%s,%s) + ON CONFLICT (github_user_id) DO NOTHING; + """, + (r["github_user_id"], r["login"], r["name"], r["email"], r["company"], + r["blog"], r["location"], r["bio"], r["avatar_url"], r["html_url"], + json.dumps(r["payload"])), + ) + + for r in _REPOS: + cur.execute( + """ + INSERT INTO github_repos + (github_repo_id, account_login, name, full_name, private, fork, archived, + disabled, default_branch, language, stargazers_count, watchers_count, + forks_count, open_issues_count, size_kb, pushed_at, + created_at_github, updated_at_github, html_url, api_url, payload) + VALUES (%s,%s,%s,%s,%s,%s,%s,%s,%s,%s,%s,%s,%s,%s,%s,%s,%s,%s,%s,%s,%s) + ON CONFLICT (github_repo_id) DO NOTHING; + """, + (r["github_repo_id"], r["account_login"], r["name"], r["full_name"], + r["private"], r["fork"], r["archived"], r["disabled"], r["default_branch"], + r["language"], r["stargazers_count"], r["watchers_count"], r["forks_count"], + r["open_issues_count"], r["size_kb"], r["pushed_at"], r["created_at_github"], + r["updated_at_github"], r["html_url"], r["api_url"], json.dumps(r["payload"])), + ) + + for r in _GISTS: + cur.execute( + """ + INSERT INTO github_gists + (gist_id, owner_login, description, public, files_count, comments_count, + html_url, api_url, created_at_github, updated_at_github, payload) + VALUES (%s,%s,%s,%s,%s,%s,%s,%s,%s,%s,%s) + ON CONFLICT (gist_id) DO NOTHING; + """, + (r["gist_id"], r["owner_login"], r["description"], r["public"], + r["files_count"], r["comments_count"], r["html_url"], r["api_url"], + r["created_at_github"], r["updated_at_github"], json.dumps(r["payload"])), + ) + + for r in _PROJECTS: + cur.execute( + """ + INSERT INTO github_projects + (github_project_id, owner_login, owner_type, name, body, state, number, + html_url, api_url, created_at_github, updated_at_github, payload) + VALUES (%s,%s,%s,%s,%s,%s,%s,%s,%s,%s,%s,%s) + ON CONFLICT (github_project_id) DO NOTHING; + """, + (r["github_project_id"], r["owner_login"], r["owner_type"], r["name"], + r["body"], r["state"], r["number"], r["html_url"], r["api_url"], + r["created_at_github"], r["updated_at_github"], json.dumps(r["payload"])), + ) + + for r in _RESOURCES: + cur.execute( + """ + INSERT INTO github_resources + (account_login, resource_type, resource_id, resource_name, resource_url, + is_private, state, created_at_github, updated_at_github, payload) + VALUES (%s,%s,%s,%s,%s,%s,%s,%s,%s,%s) + ON CONFLICT (resource_type, resource_id) DO NOTHING; + """, + (r["account_login"], r["resource_type"], r["resource_id"], r["resource_name"], + r["resource_url"], r["is_private"], r["state"], r["created_at_github"], + r["updated_at_github"], json.dumps(r["payload"])), + ) + + conn.commit() + cur.close() + conn.close() + print("Seeded 3 rows into each GitHub table.") + + +if __name__ == "__main__": + seed()