Skip to content

Commit

Permalink
Implement projects endpoints (#33)
Browse files Browse the repository at this point in the history
  • Loading branch information
No767 authored Dec 31, 2024
1 parent cf30345 commit cd9c8ec
Show file tree
Hide file tree
Showing 7 changed files with 728 additions and 1 deletion.
22 changes: 21 additions & 1 deletion server/core.py
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@
from typing import TYPE_CHECKING, Any, Generator, Optional, Self, Union, Unpack

import asyncpg
import orjson
from fastapi import Depends, FastAPI, status
from fastapi.exceptions import HTTPException, RequestValidationError
from fastapi.openapi.utils import get_openapi
Expand Down Expand Up @@ -81,6 +82,23 @@
]


async def init(conn: asyncpg.Connection):
# Refer to https://github.com/MagicStack/asyncpg/issues/140#issuecomment-301477123
def _encode_jsonb(value):
return b"\x01" + orjson.dumps(value)

def _decode_jsonb(value):
return orjson.loads(value[1:].decode("utf-8"))

await conn.set_type_codec(
"jsonb",
schema="pg_catalog",
encoder=_encode_jsonb,
decoder=_decode_jsonb,
format="binary",
)


class Kanae(FastAPI):
pool: asyncpg.Pool

Expand Down Expand Up @@ -383,7 +401,9 @@ async def request_validation_error_handler(

@asynccontextmanager
async def lifespan(self, app: Self):
async with asyncpg.create_pool(dsn=self.config["postgres_uri"]) as app.pool:
async with asyncpg.create_pool(
dsn=self.config["postgres_uri"], init=init
) as app.pool:
yield

def get_db(self) -> Generator[asyncpg.Pool, None, None]:
Expand Down
67 changes: 67 additions & 0 deletions server/migrations/V3__projects.sql
Original file line number Diff line number Diff line change
@@ -0,0 +1,67 @@
-- Revision Version: V3
-- Revises: V2
-- Creation Date: 2024-12-26 09:27:35.701551+00:00 UTC
-- Reason: projects

CREATE TYPE project_type AS ENUM (
'independent',
'sig_ai',
'sig_swe',
'sig_cyber',
'sig_data',
'sig_arch',
'sig_graph'
);

CREATE TYPE project_role AS ENUM (
'unaffiliated',
'member',
'former',
'lead',
'manager'
);

ALTER TABLE IF EXISTS members ADD COLUMN IF NOT EXISTS role project_role DEFAULT 'unaffiliated';

-- Projects by themselves, are basically the same type of relationship compared to events
-- They are many-to-many
-- Ex. A member can be in multiples projects (e.g. Website, UniFoodi, Fishtank, etc), and a project can have multiple members
CREATE TABLE IF NOT EXISTS projects (
id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
name TEXT NOT NULL,
description TEXT,
link TEXT,
type project_type DEFAULT 'independent',
active BOOL DEFAULT TRUE,
founded_at TIMESTAMP WITH TIME ZONE DEFAULT (NOW() AT TIME ZONE 'utc')
);

-- A project also is associated with a set of "tags"
-- Meaning that many projects can have many tags
-- This basically implies that we need bridge tables to overcome the gap.
CREATE TABLE IF NOT EXISTS tags (
id SERIAL PRIMARY KEY,
title TEXT NOT NULL,
description TEXT
);

-- Entirely overkill index for "performance reasons"
-- Realistically, given the scale of the data now, it doesn't matter
CREATE INDEX IF NOT EXISTS tags_title_idx ON tags (title);
CREATE INDEX IF NOT EXISTS tags_title_lower_idx ON tags (LOWER(title));

-- Bridge table for Projects <--> Tags
-- Many need to adjust the cascade for deletions later.
CREATE TABLE IF NOT EXISTS project_tags (
project_id UUID REFERENCES projects (id) ON DELETE CASCADE ON UPDATE NO ACTION,
tag_id INTEGER REFERENCES tags (id) ON DELETE NO ACTION ON UPDATE NO ACTION,
PRIMARY KEY (project_id, tag_id)
);

-- Bridge table for Projects <--> Members
-- Many need to adjust the cascade for deletions later.
CREATE TABLE IF NOT EXISTS project_members (
project_id UUID REFERENCES projects (id) ON DELETE CASCADE ON UPDATE NO ACTION,
member_id UUID REFERENCES members (id) ON DELETE CASCADE ON UPDATE NO ACTION,
PRIMARY KEY (project_id, member_id)
);
Loading

0 comments on commit cd9c8ec

Please sign in to comment.