-
Notifications
You must be signed in to change notification settings - Fork 4
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Add people domain, person model and APIs
- Loading branch information
shri
committed
Jul 31, 2024
1 parent
de98971
commit 5e5ddfc
Showing
14 changed files
with
515 additions
and
2 deletions.
There are no files selected for viewing
105 changes: 105 additions & 0 deletions
105
src/app/db/migrations/versions/2024-07-31_add_people_model_698ddcfa9900.py
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,105 @@ | ||
# type: ignore | ||
"""Add people model | ||
Revision ID: 698ddcfa9900 | ||
Revises: a05c476c0ae9 | ||
Create Date: 2024-07-31 09:48:08.467309+00:00 | ||
""" | ||
from __future__ import annotations | ||
|
||
import warnings | ||
from typing import TYPE_CHECKING | ||
|
||
import sqlalchemy as sa | ||
from alembic import op | ||
from advanced_alchemy.types import EncryptedString, EncryptedText, GUID, ORA_JSONB, DateTimeUTC | ||
from sqlalchemy import Text # noqa: F401 | ||
from sqlalchemy.dialects import postgresql | ||
from app.db.models.custom_types import LocationType, WorkExperienceType, SocialActivityType | ||
if TYPE_CHECKING: | ||
from collections.abc import Sequence | ||
|
||
__all__ = ["downgrade", "upgrade", "schema_upgrades", "schema_downgrades", "data_upgrades", "data_downgrades"] | ||
|
||
sa.GUID = GUID | ||
sa.DateTimeUTC = DateTimeUTC | ||
sa.ORA_JSONB = ORA_JSONB | ||
sa.EncryptedString = EncryptedString | ||
sa.EncryptedText = EncryptedText | ||
|
||
# revision identifiers, used by Alembic. | ||
revision = '698ddcfa9900' | ||
down_revision = 'a05c476c0ae9' | ||
branch_labels = None | ||
depends_on = None | ||
|
||
|
||
def upgrade() -> None: | ||
with warnings.catch_warnings(): | ||
warnings.filterwarnings("ignore", category=UserWarning) | ||
with op.get_context().autocommit_block(): | ||
schema_upgrades() | ||
data_upgrades() | ||
|
||
def downgrade() -> None: | ||
with warnings.catch_warnings(): | ||
warnings.filterwarnings("ignore", category=UserWarning) | ||
with op.get_context().autocommit_block(): | ||
data_downgrades() | ||
schema_downgrades() | ||
|
||
def schema_upgrades() -> None: | ||
"""schema upgrade migrations go here.""" | ||
# ### commands auto generated by Alembic - please adjust! ### | ||
op.create_table('person', | ||
sa.Column('id', sa.GUID(length=16), nullable=False), | ||
sa.Column('first_name', sa.String(), nullable=True), | ||
sa.Column('last_name', sa.String(), nullable=True), | ||
sa.Column('full_name', sa.String(), nullable=True), | ||
sa.Column('headline', sa.String(length=500), nullable=True), | ||
sa.Column('summary', sa.String(length=2000), nullable=True), | ||
sa.Column('occupation', sa.String(), nullable=True), | ||
sa.Column('industry', sa.String(), nullable=True), | ||
sa.Column('profile_pic_url', sa.String(length=2083), nullable=True), | ||
sa.Column('url', sa.String(length=2083), nullable=True), | ||
sa.Column('linkedin_profile_url', sa.String(length=2083), nullable=True), | ||
sa.Column('twitter_profile_url', sa.String(length=2083), nullable=True), | ||
sa.Column('github_profile_url', sa.String(length=2083), nullable=True), | ||
sa.Column('location', LocationType(), nullable=True), | ||
sa.Column('personal_emails', postgresql.JSONB(astext_type=sa.Text()), nullable=True), | ||
sa.Column('work_emails', postgresql.JSONB(astext_type=sa.Text()), nullable=True), | ||
sa.Column('personal_numbers', postgresql.JSONB(astext_type=sa.Text()), nullable=True), | ||
sa.Column('birth_date', sa.Date(), nullable=True), | ||
sa.Column('gender', sa.String(), nullable=True), | ||
sa.Column('languages', postgresql.JSONB(astext_type=sa.Text()), nullable=True), | ||
sa.Column('work_experiences', WorkExperienceType(), nullable=True), | ||
sa.Column('social_activities', SocialActivityType(), nullable=True), | ||
sa.Column('slug', sa.String(length=100), nullable=False), | ||
sa.Column('sa_orm_sentinel', sa.Integer(), nullable=True), | ||
sa.Column('created_at', sa.DateTimeUTC(timezone=True), nullable=False), | ||
sa.Column('updated_at', sa.DateTimeUTC(timezone=True), nullable=False), | ||
sa.PrimaryKeyConstraint('id', name=op.f('pk_person')), | ||
sa.UniqueConstraint('slug', name='uq_person_slug') | ||
) | ||
with op.batch_alter_table('person', schema=None) as batch_op: | ||
batch_op.create_index(batch_op.f('ix_person_industry'), ['industry'], unique=False) | ||
batch_op.create_index('ix_person_slug_unique', ['slug'], unique=True) | ||
|
||
# ### end Alembic commands ### | ||
|
||
def schema_downgrades() -> None: | ||
"""schema downgrade migrations go here.""" | ||
# ### commands auto generated by Alembic - please adjust! ### | ||
with op.batch_alter_table('person', schema=None) as batch_op: | ||
batch_op.drop_index('ix_person_slug_unique') | ||
batch_op.drop_index(batch_op.f('ix_person_industry')) | ||
|
||
op.drop_table('person') | ||
# ### end Alembic commands ### | ||
|
||
def data_upgrades() -> None: | ||
"""Add any optional data upgrade migrations here!""" | ||
|
||
def data_downgrades() -> None: | ||
"""Add any optional data downgrade migrations here!""" |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,43 @@ | ||
from __future__ import annotations | ||
|
||
from typing import TYPE_CHECKING | ||
from datetime import date | ||
|
||
from advanced_alchemy.base import SlugKey, UUIDAuditBase | ||
from sqlalchemy import String | ||
from sqlalchemy.dialects.postgresql import JSONB | ||
from sqlalchemy.orm import Mapped, mapped_column, relationship | ||
|
||
from app.lib.schema import Location, WorkExperience, SocialActivity | ||
from .custom_types import LocationType, WorkExperienceType, SocialActivityType | ||
|
||
|
||
class Person(UUIDAuditBase, SlugKey): | ||
"""A person.""" | ||
|
||
__tablename__ = "person" | ||
__pii_columns__ = {"first_name", "last_name", "full_name", "linkedin_url", "profile_pic_url", "personal_emails", "work_emails", "personal_numbers", "social_activities"} | ||
first_name: Mapped[str] = mapped_column(nullable=True, default=None) | ||
last_name: Mapped[str] = mapped_column(nullable=True, default=None) | ||
full_name: Mapped[str] = mapped_column(nullable=True, default=None) | ||
headline: Mapped[str | None] = mapped_column(String(length=500), nullable=True, default=None) | ||
summary: Mapped[str | None] = mapped_column(String(length=2000), nullable=True, default=None) | ||
occupation: Mapped[str] = mapped_column(nullable=True, default=None) | ||
industry: Mapped[str | None] = mapped_column(nullable=True, default=None, index=True) | ||
profile_pic_url: Mapped[str | None] = mapped_column(String(length=2083), nullable=True, default=None) | ||
url: Mapped[str | None] = mapped_column(String(length=2083), nullable=True, default=None) | ||
linkedin_profile_url: Mapped[str | None] = mapped_column(String(length=2083), nullable=True, default=None) | ||
twitter_profile_url: Mapped[str | None] = mapped_column(String(length=2083), nullable=True, default=None) | ||
github_profile_url: Mapped[str | None] = mapped_column(String(length=2083), nullable=True, default=None) | ||
location: Mapped[Location | None] = mapped_column(LocationType, nullable=True, default=None) | ||
personal_emails: Mapped[list[str] | None] = mapped_column(JSONB, nullable=True, default=None) | ||
work_emails: Mapped[list[str] | None] = mapped_column(JSONB, nullable=True, default=None) | ||
personal_numbers: Mapped[list[str] | None] = mapped_column(JSONB, nullable=True, default=None) | ||
birth_date: Mapped[date | None] = mapped_column(nullable=True, default=None) | ||
gender: Mapped[str | None] = mapped_column(nullable=True, default=None) | ||
languages: Mapped[list[str] | None] = mapped_column(JSONB, nullable=True, default=None) | ||
work_experiences: Mapped[list[WorkExperience] | None] = mapped_column(WorkExperienceType, nullable=True, default=None) | ||
social_activities: Mapped[list[SocialActivity] | None] = mapped_column(SocialActivityType, nullable=True, default=None) | ||
# ----------- | ||
# ORM Relationships | ||
# ------------ |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,4 @@ | ||
"""People Application Module.""" | ||
from . import controllers, dependencies, schemas, services | ||
|
||
__all__ = ["controllers", "services", "schemas", "dependencies"] |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,3 @@ | ||
from .persons import PersonController | ||
|
||
__all__ = ["PersonController"] |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,129 @@ | ||
"""Person Controllers.""" | ||
|
||
from __future__ import annotations | ||
|
||
from typing import TYPE_CHECKING, Annotated | ||
|
||
from litestar import Controller, delete, get, patch, post | ||
from litestar.di import Provide | ||
|
||
from app.config import constants | ||
from app.domain.accounts.guards import requires_active_user | ||
from app.domain.people import urls | ||
from app.domain.people.dependencies import provide_persons_service | ||
from app.domain.people.schemas import Person, PersonCreate, PersonUpdate | ||
from app.domain.people.services import PersonService | ||
|
||
if TYPE_CHECKING: | ||
from uuid import UUID | ||
|
||
from advanced_alchemy.service.pagination import OffsetPagination | ||
from litestar.params import Dependency, Parameter | ||
|
||
from app.lib.dependencies import FilterTypes | ||
|
||
|
||
class PersonController(Controller): | ||
"""Person operations.""" | ||
|
||
tags = ["Persons"] | ||
dependencies = {"persons_service": Provide(provide_persons_service)} | ||
guards = [requires_active_user] | ||
signature_namespace = { | ||
"PersonService": PersonService, | ||
} | ||
dto = None | ||
return_dto = None | ||
|
||
@get( | ||
operation_id="ListPersons", | ||
name="persons:list", | ||
summary="List Persons", | ||
path=urls.PERSON_LIST, | ||
) | ||
async def list_persons( | ||
self, | ||
persons_service: PersonService, | ||
filters: Annotated[list[FilterTypes], Dependency(skip_validation=True)], | ||
) -> OffsetPagination[Person]: | ||
"""List persons that your account can access..""" | ||
results, total = await persons_service.list_and_count(*filters) | ||
return persons_service.to_schema(data=results, total=total, schema_type=Person, filters=filters) | ||
|
||
@post( | ||
operation_id="CreatePerson", | ||
name="persons:create", | ||
summary="Create a new person.", | ||
path=urls.PERSON_CREATE, | ||
) | ||
async def create_person( | ||
self, | ||
persons_service: PersonService, | ||
data: PersonCreate, | ||
) -> PersonCreate: | ||
"""Create a new person.""" | ||
obj = data.to_dict() | ||
db_obj = await persons_service.create(obj) | ||
return persons_service.to_schema(schema_type=Person, data=db_obj) | ||
|
||
@get( | ||
operation_id="GetPerson", | ||
name="persons:get", | ||
summary="Retrieve the details of a person.", | ||
path=urls.PERSON_DETAIL, | ||
) | ||
async def get_person( | ||
self, | ||
persons_service: PersonService, | ||
person_id: Annotated[ | ||
UUID, | ||
Parameter( | ||
title="Person ID", | ||
description="The person to retrieve.", | ||
), | ||
], | ||
) -> Person: | ||
"""Get details about a comapny.""" | ||
db_obj = await persons_service.get(person_id) | ||
return persons_service.to_schema(schema_type=Person, data=db_obj) | ||
|
||
@patch( | ||
operation_id="UpdatePerson", | ||
name="persons:update", | ||
path=urls.PERSON_UPDATE, | ||
) | ||
async def update_person( | ||
self, | ||
data: PersonUpdate, | ||
persons_service: PersonService, | ||
person_id: Annotated[ | ||
UUID, | ||
Parameter( | ||
title="Person ID", | ||
description="The person to update.", | ||
), | ||
], | ||
) -> Person: | ||
"""Update a person.""" | ||
db_obj = await persons_service.update( | ||
item_id=person_id, | ||
data=data.to_dict(), | ||
) | ||
return persons_service.to_schema(schema_type=Person, data=db_obj) | ||
|
||
@delete( | ||
operation_id="DeletePerson", | ||
name="persons:delete", | ||
summary="Remove Person", | ||
path=urls.PERSON_DELETE, | ||
) | ||
async def delete_person( | ||
self, | ||
persons_service: PersonService, | ||
person_id: Annotated[ | ||
UUID, | ||
Parameter(title="Person ID", description="The person to delete."), | ||
], | ||
) -> None: | ||
"""Delete a person.""" | ||
_ = await persons_service.delete(person_id) |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,27 @@ | ||
"""People Controllers.""" | ||
|
||
from __future__ import annotations | ||
|
||
from typing import TYPE_CHECKING | ||
|
||
from sqlalchemy.orm import joinedload, noload, selectinload | ||
|
||
from app.db.models import Person | ||
from app.domain.people.services import PersonService | ||
|
||
__all__ = ("provide_persons_service", ) | ||
|
||
|
||
if TYPE_CHECKING: | ||
from collections.abc import AsyncGenerator | ||
|
||
from sqlalchemy.ext.asyncio import AsyncSession | ||
|
||
|
||
async def provide_persons_service(db_session: AsyncSession) -> AsyncGenerator[PersonService, None]: | ||
"""Construct repository and service objects for the request.""" | ||
async with PersonService.new( | ||
session=db_session, | ||
load=[], | ||
) as service: | ||
yield service |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,23 @@ | ||
from __future__ import annotations | ||
|
||
from typing import TYPE_CHECKING, Any | ||
from uuid import UUID # noqa: TCH003 | ||
|
||
from advanced_alchemy.repository import SQLAlchemyAsyncRepository, SQLAlchemyAsyncSlugRepository | ||
from sqlalchemy import ColumnElement, select | ||
from sqlalchemy.orm import joinedload, selectinload | ||
|
||
from app.db.models import Person | ||
|
||
if TYPE_CHECKING: | ||
from advanced_alchemy.filters import FilterTypes | ||
|
||
__all__ = ( | ||
"PersonRepository", | ||
) | ||
|
||
|
||
class PersonRepository(SQLAlchemyAsyncSlugRepository[Person]): | ||
"""Person Repository.""" | ||
|
||
model_type = Person |
Oops, something went wrong.