Skip to content
Merged

Dev #123

Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
29 commits
Select commit Hold shift + click to select a range
2d27dca
feat: Add organizer and organizer type models with initial migration …
pradanaadn Dec 1, 2025
f37941b
feat: Implement initial organizer data insert in CLI
pradanaadn Dec 1, 2025
9d73200
feat: Add organizer type routes, schemas, and implementation
pradanaadn Dec 1, 2025
4d3b580
fix: change response model for status code 500
pradanaadn Dec 1, 2025
4130492
feat: Add organizer routes and implementation for CRUD operations
pradanaadn Dec 1, 2025
acede6f
feat: Enhance timezone handling in helper functions
pradanaadn Dec 1, 2025
2b51515
feat: Refactor security module and add permission checking function
pradanaadn Dec 1, 2025
06f2d1b
feat: Add AuthorizationStatusEnum for handling authorization states
pradanaadn Dec 1, 2025
03e7f3e
feat: Add unique constraint for user and organizer type in organizer …
pradanaadn Dec 1, 2025
5ca4327
feat: Add function to retrieve organizer type by ID
pradanaadn Dec 1, 2025
9b64f3c
feat: Implement organizer management functionality with create and de…
pradanaadn Dec 1, 2025
8f4454c
feat: Add function to fetch organizers by type and update related sch…
pradanaadn Dec 3, 2025
2e0474f
feat: Enhance organizer functionality with new routes, update schemas…
pradanaadn Dec 3, 2025
16625f1
feat: Add unit tests for organizer type retrieval functionality
pradanaadn Dec 3, 2025
781b328
feat: Add response models for organizer routes and update schemas
pradanaadn Dec 3, 2025
940bfce
Merge branch 'dev' into feat/organizer
pradanaadn Dec 3, 2025
2341bbc
chore: ruff format
pradanaadn Dec 3, 2025
76a519f
Merge branch 'feat/organizer' of github.com:pradanaadn/pyconid25-be i…
pradanaadn Dec 3, 2025
efd7fdd
fix: remove unnecessary blank line before exception handler
pradanaadn Dec 3, 2025
cfe63b6
refactor: remove unused imports from migration and test files
pradanaadn Dec 3, 2025
3066046
feat: add migration to merge diverged migrations
pradanaadn Dec 3, 2025
bcf153e
refactor: clean up formatting in migration file
pradanaadn Dec 3, 2025
8564090
fix: correct formatting in migration file
pradanaadn Dec 3, 2025
bb7ca2b
feat: update organizer detail user model to conditionally include pub…
pradanaadn Dec 3, 2025
2b1e706
chore: ruff format
pradanaadn Dec 3, 2025
b663e6c
Merge pull request #115 from pradanaadn/feat/organizer
BimaAdi Dec 5, 2025
b4cd197
refactor: Comment out stream readiness check in playback
nizarfadlan Dec 8, 2025
7da0d9b
Refactor streaming tests imports
nizarfadlan Dec 8, 2025
9ec27d6
Merge pull request #122 from nizarfadlan/refactor/comment-validation-…
BimaAdi Dec 9, 2025
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
9 changes: 9 additions & 0 deletions cli.py
Original file line number Diff line number Diff line change
Expand Up @@ -30,6 +30,15 @@ def create_management_user():
)


@app.command()
def create_initial_organizer_types():
from models import factory_session
from repository.organizer_type import insert_initial_organizer_types

with factory_session() as db:
insert_initial_organizer_types(db=db)


@app.command()
def initial_data():
from seeders.initial_seeders import initial_seeders
Expand Down
17 changes: 17 additions & 0 deletions core/helper.py
Original file line number Diff line number Diff line change
@@ -1,10 +1,27 @@
from datetime import datetime
from fastapi import UploadFile
from typing import Optional
from zoneinfo import ZoneInfo, ZoneInfoNotFoundError


def save_file_and_get_url(url: Optional[UploadFile]) -> Optional[str]:
if url:
# Simulasi penyimpanan file dan mendapatkan URL
return f"{url.filename}-{datetime.now().timestamp()}"
return None


def get_current_time_in_timezone(timezone_str: str = "Asia/Jakarta") -> datetime:
"""Get Current Time in Specified Timezone

Args:
timezone_str (str): Timezone string (e.g., "Asia/Jakarta")

Returns:
datetime: Current datetime in the specified timezone
"""
try:
tz = ZoneInfo(timezone_str)
except ZoneInfoNotFoundError:
tz = ZoneInfo("UTC")
return datetime.now(tz)
33 changes: 26 additions & 7 deletions core/security.py
Original file line number Diff line number Diff line change
@@ -1,27 +1,29 @@
from sqlalchemy.orm import Session
from typing import Optional, Tuple
from datetime import datetime, timedelta
from typing import Optional, Tuple

import bcrypt
import jwt
import pytz
from fastapi import Depends
from fastapi.security import OAuth2PasswordBearer
import bcrypt
from pytz import timezone
import pytz
from sqlalchemy import delete, or_, select
from sqlalchemy.orm import Session
from sqlalchemy.orm import Session as SQLAlchemySession

from models import get_db_sync
from models.RefreshToken import RefreshToken
from models.Token import Token
import jwt
from models.User import User
from schemas.auth import AuthorizationStatusEnum
from settings import (
ACCESS_TOKEN_EXPIRE_MINUTES,
ALGORITHM,
REFRESH_TOKEN_EXPIRE_MINUTES,
SECRET_KEY,
ALGORITHM,
TZ,
)


oauth2_scheme = OAuth2PasswordBearer(tokenUrl="auth/token/", auto_error=False)


Expand Down Expand Up @@ -110,3 +112,20 @@ def invalidate_token(db: SQLAlchemySession, token: str):
stmt = delete(Token).where(or_(Token.expired_at <= now, Token.token == token))
db.execute(stmt)
db.commit()


def check_permissions(
current_user: User | None, required_participant_type: str
) -> AuthorizationStatusEnum:
"""Check if the current user has the required permissions.
Args:
current_user (User | None): The current authenticated user.
required_participant_type (str): The required participant type for access.
Returns:
AuthorizationStatusEnum: The authorization status.
"""
if current_user is None:
return AuthorizationStatusEnum.UNAUTHORIZED
if current_user.participant_type != required_participant_type:
return AuthorizationStatusEnum.FORBIDDEN
return AuthorizationStatusEnum.PASSED
4 changes: 4 additions & 0 deletions main.py
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,8 @@
from routes.streaming import router as streaming_router
from routes.voucher import router as voucher_router
from routes.speaker_type import router as speaker_type_router
from routes.organizer_type import router as organizer_type_router
from routes.organizer import router as organizer_router
from routes.schedule_type import router as schedule_type_router
from routes.volunteer import router as volunteer_router

Expand Down Expand Up @@ -59,6 +61,8 @@
app.include_router(streaming_router)
app.include_router(voucher_router)
app.include_router(speaker_type_router)
app.include_router(organizer_type_router)
app.include_router(organizer_router)
app.include_router(schedule_type_router)
app.include_router(volunteer_router)

Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,59 @@
"""organizer_feature

Revision ID: 6feac25c0745
Revises: b3212d6ebfde
Create Date: 2025-12-01 18:59:00.265790

"""

from typing import Sequence, Union

from alembic import op
import sqlalchemy as sa

# revision identifiers, used by Alembic.
revision: str = "6feac25c0745"
down_revision: Union[str, None] = "b3212d6ebfde"
branch_labels: Union[str, Sequence[str], None] = None
depends_on: Union[str, Sequence[str], None] = None


def upgrade() -> None:
"""Upgrade schema."""
# ### commands auto generated by Alembic - please adjust! ###
op.create_table(
"organizer_type",
sa.Column("id", sa.UUID(), nullable=False),
sa.Column("name", sa.String(), nullable=False),
sa.PrimaryKeyConstraint("id"),
)
op.create_table(
"organizer",
sa.Column("id", sa.UUID(), nullable=False),
sa.Column("user_id", sa.UUID(), nullable=False),
sa.Column("organizer_type_id", sa.UUID(), nullable=True),
sa.Column("created_at", sa.DateTime(timezone=True), nullable=True),
sa.Column("updated_at", sa.DateTime(timezone=True), nullable=True),
sa.Column("deleted_at", sa.DateTime(timezone=True), nullable=True),
sa.ForeignKeyConstraint(
["user_id"],
["user.id"],
),
sa.ForeignKeyConstraint(
["organizer_type_id"],
["organizer_type.id"],
),
sa.UniqueConstraint(
"user_id", "organizer_type_id", name="uq_user_organizer_type"
),
sa.PrimaryKeyConstraint("id"),
)
# ### end Alembic commands ###


def downgrade() -> None:
"""Downgrade schema."""
# ### commands auto generated by Alembic - please adjust! ###
op.drop_table("organizer_type")
op.drop_table("organizer")
# ### end Alembic commands ###
Original file line number Diff line number Diff line change
@@ -0,0 +1,29 @@
"""Merge diverged migrations

Revision ID: 92f9a97e9001
Revises: 6feac25c0745, 83852d14430b
Create Date: 2025-12-03 13:42:39.739118

"""

from typing import Sequence, Union

from alembic import op # noqa: F401
import sqlalchemy as sa # noqa: F401


# revision identifiers, used by Alembic.
revision: str = "92f9a97e9001"
down_revision: Union[str, None] = ("6feac25c0745", "83852d14430b") # pyright: ignore[reportAssignmentType]
branch_labels: Union[str, Sequence[str], None] = None
depends_on: Union[str, Sequence[str], None] = None


def upgrade() -> None:
"""Upgrade schema."""
pass


def downgrade() -> None:
"""Downgrade schema."""
pass
33 changes: 33 additions & 0 deletions models/Organizer.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,33 @@
import datetime
import uuid
from models import Base
from sqlalchemy import UUID, DateTime, ForeignKey
from sqlalchemy.orm import mapped_column, Mapped, relationship


class Organizer(Base):
__tablename__ = "organizer"

id: Mapped[str] = mapped_column(
"id", UUID(as_uuid=True), primary_key=True, index=True, default=uuid.uuid4
)
user_id: Mapped[str] = mapped_column(
"user_id", ForeignKey("user.id"), nullable=False
)
organizer_type_id: Mapped[str] = mapped_column(
"organizer_type_id", ForeignKey("organizer_type.id"), nullable=True
)
created_at = mapped_column(
"created_at",
DateTime(timezone=True),
default=datetime.datetime.now(datetime.timezone.utc),
)
updated_at = mapped_column(
"updated_at",
DateTime(timezone=True),
default=datetime.datetime.now(datetime.timezone.utc),
)
deleted_at = mapped_column("deleted_at", DateTime(timezone=True), nullable=True)
# Relationships
user = relationship("User", backref="organizer_user")
organizer_type = relationship("OrganizerType", backref="organizer_organizer_type")
13 changes: 13 additions & 0 deletions models/OrganizerType.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
import uuid
from sqlalchemy import UUID, String
from models import Base
from sqlalchemy.orm import mapped_column, Mapped


class OrganizerType(Base):
__tablename__ = "organizer_type"

id: Mapped[str] = mapped_column(
"id", UUID(as_uuid=True), primary_key=True, index=True, default=uuid.uuid4
)
name: Mapped[str] = mapped_column("name", String)
Loading
Loading