Skip to content

Commit

Permalink
Separated out Members from clubs microservice
Browse files Browse the repository at this point in the history
  • Loading branch information
bhavberi committed May 16, 2024
0 parents commit d1a7973
Show file tree
Hide file tree
Showing 14 changed files with 1,120 additions and 0 deletions.
44 changes: 44 additions & 0 deletions .github/workflows/formatting.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,44 @@
name: Auto Linting and Formatting

on:
push:
branches:
- master

jobs:
linting:
runs-on: ubuntu-latest
steps:
- name: Checkout repository
uses: actions/checkout@v4

- name: Set up Python
uses: actions/setup-python@v5
with:
python-version: '3.x'

- name: Install dependencies
run: |
python -m pip install --upgrade pip
pip install ruff
- name: Lint with ruff
run: ruff check --fix *.py

- name: Format with ruff
run: ruff format *.py

- name: Remove ruff cache
run: rm -rf .ruff_cache

- uses: stefanzweifel/git-auto-commit-action@v5
with:
commit_message: Apply Linting & Formatting Fixes

# - name: Remove Linting Branch
# run: |
# if git rev-parse --verify linting >/dev/null 2>&1; then
# git push origin --delete linting
# fi


5 changes: 5 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
.venv/
.vscode/
.in
.out
__pycache__/
19 changes: 19 additions & 0 deletions Dockerfile
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
# cache dependencies
FROM python:3.12 AS python_cache
ENV VIRTUAL_ENV=/venv
ENV PATH="$VIRTUAL_ENV/bin:$PATH"
WORKDIR /cache/
COPY requirements.txt .
RUN python -m venv /venv
RUN pip install -r requirements.txt

# build and start
FROM python:3.12-slim AS build
EXPOSE 80
WORKDIR /app
ENV VIRTUAL_ENV=/venv
ENV PATH="$VIRTUAL_ENV/bin:$PATH"
COPY --from=python_cache /venv /venv
COPY . .
RUN strawberry export-schema main > schema.graphql
ENTRYPOINT [ "./entrypoint.sh" ]
17 changes: 17 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
# _MEMBERS_ MICRO-SERVICE

This is a microservice using FastAPI + Strawberry + MongoDB.
Contains code for queries, mutations, types and models for the members data and services specifically.

## How to use
1. Go to [https://github.com/Clubs-Council-IIITH/services](https://github.com/Clubs-Council-IIITH/services)
2. Follow the steps given in the README.md file at that page.

---

## FOR DEVELOPERS
_URL_ -> http://members/graphql (For using in single gateway)

> ### QUERIES
> ### MUTATIONS
Empty file added __init__.py
Empty file.
34 changes: 34 additions & 0 deletions db.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,34 @@
from os import getenv

from pymongo import MongoClient


# get mongodb URI and database name from environment variale
MONGO_URI = "mongodb://{}:{}@mongo:{}/".format(
getenv("MONGO_USERNAME", default="username"),
getenv("MONGO_PASSWORD", default="password"),
getenv("MONGO_PORT", default="27017"),
)
MONGO_DATABASE = getenv("MONGO_DATABASE", default="default")

# instantiate mongo client
client = MongoClient(MONGO_URI)

# get database
db = client[MONGO_DATABASE]
membersdb = db.members

try:
# check if the members index exists
if "unique_members" in membersdb.index_information():
print("The members index exists.")
else:
# create the index
membersdb.create_index(
[("cid", 1), ("uid", 1)], unique=True, name="unique_members"
)
print("The members index was created.")

print(membersdb.index_information())
except Exception:
pass
4 changes: 4 additions & 0 deletions entrypoint.sh
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
#!/bin/bash

cp ./schema.graphql /subgraphs/clubs.graphql
uvicorn main:app --host 0.0.0.0 --port 80
50 changes: 50 additions & 0 deletions main.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,50 @@
import strawberry
from strawberry.tools import create_type
from strawberry.fastapi import GraphQLRouter

from fastapi import FastAPI

from os import getenv

# override PyObjectId and Context scalars
from models import PyObjectId
from otypes import Context, PyObjectIdType

# import all queries and mutations
from queries import queries
from mutations import mutations


# check whether running in debug mode
DEBUG = int(getenv("GLOBAL_DEBUG", 0))

# create query types
Query = create_type("Query", queries)

# create mutation types
Mutation = create_type("Mutation", mutations)


# override context getter
async def get_context() -> Context:
return Context()


# initialize federated schema
schema = strawberry.federation.Schema(
query=Query,
mutation=Mutation,
enable_federation_2=True,
scalar_overrides={PyObjectId: PyObjectIdType},
)

DEBUG = getenv("SERVICES_DEBUG", "False").lower() in ("true", "1", "t")

# serve API with FastAPI router
gql_app = GraphQLRouter(schema, graphiql=True, context_getter=get_context)
app = FastAPI(
debug=DEBUG,
title="CC Clubs Microservice",
desciption="Handles Data of Clubs and Members",
)
app.include_router(gql_app, prefix="/graphql")
102 changes: 102 additions & 0 deletions models.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,102 @@
from bson import ObjectId
from pydantic import (
BaseModel,
ConfigDict,
Field,
field_validator,
ValidationInfo,
)
from pydantic_core import core_schema
from typing import Any, List


# for handling mongo ObjectIds
class PyObjectId(ObjectId):
@classmethod
def __get_pydantic_core_schema__(cls, source_type: Any, handler):
return core_schema.union_schema(
[
# check if it's an instance first before doing any further work
core_schema.is_instance_schema(ObjectId),
core_schema.no_info_plain_validator_function(cls.validate),
],
serialization=core_schema.to_string_ser_schema(),
)

@classmethod
def validate(cls, v):
if not ObjectId.is_valid(v):
raise ValueError("Invalid ObjectId")
return ObjectId(v)

@classmethod
def __get_pydantic_json_schema__(cls, field_schema):
field_schema.update(type="string")


class Roles(BaseModel):
rid: str | None = Field(None, description="Unique Identifier for a role")
name: str = Field(..., min_length=1, max_length=99)
start_year: int = Field(..., ge=2010, le=2050)
end_year: int | None = Field(None, gt=2010, le=2051)
approved: bool = False
rejected: bool = False
deleted: bool = False

# Validators
@field_validator("end_year")
def check_end_year(cls, value, info: ValidationInfo):
if value is not None and value < info.data["start_year"]:
return None
return value

@field_validator("rejected")
def check_status(cls, value, info: ValidationInfo):
if info.data["approved"] is True and value is True:
raise ValueError("Role cannot be both approved and rejected")
return value

model_config = ConfigDict(
arbitrary_types_allowed=True,
str_max_length=100,
validate_assignment=True,
validate_default=True,
validate_return=True,
extra="forbid",
str_strip_whitespace=True,
)


class Member(BaseModel):
id: PyObjectId = Field(default_factory=PyObjectId, alias="_id")
cid: str = Field(..., description="Club ID")
uid: str = Field(..., description="User ID")
roles: List[Roles] = Field(
..., description="List of Roles for that specific person"
)

poc: bool = Field(default_factory=(lambda: 0 == 1), description="Club POC")

@field_validator("uid", mode="before")
@classmethod
def transform_uid(cls, v):
return v.lower()

# TODO[pydantic]: The following keys were removed: `json_encoders`.
# Check https://docs.pydantic.dev/dev-v2/migration/#changes-to-config for more information.
model_config = ConfigDict(
arbitrary_types_allowed=True,
str_strip_whitespace=True,
str_max_length=600,
validate_assignment=True,
validate_default=True,
validate_return=True,
extra="forbid",
json_encoders={ObjectId: str},
populate_by_name=True,
)

# Separate Coordinator & other members roles option in frontend, for better filtering for all_members_query


# TODO: ADD Descriptions for non-direct fields
Loading

0 comments on commit d1a7973

Please sign in to comment.