Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
48 commits
Select commit Hold shift + click to select a range
0c2e6a9
Add blocked column migration and sqlc updates
Tyjfre-j Mar 20, 2026
d5bf8dc
Add token blacklist and blocked checks in auth
Tyjfre-j Mar 20, 2026
924da2c
Add admin user CRUD and block/unblock endpoints
Tyjfre-j Mar 20, 2026
40f6a18
Fix staff login crash on missing user
Tyjfre-j Mar 20, 2026
aeb2eac
Add admin and mobile session defaults to settings
Tyjfre-j Mar 20, 2026
a1d054e
Refactor mobile auth endpoints for consistency
Tyjfre-j Mar 20, 2026
5c583b6
Refactor admin users router mappings and defaults
Tyjfre-j Mar 20, 2026
96ff5fd
Use settings and consistent DB error handling in user service
Tyjfre-j Mar 20, 2026
016ea29
Fix mypy exception handling in user service
Tyjfre-j Mar 20, 2026
46dff8e
refactor: move admin user mapping out of router
Tyjfre-j Mar 23, 2026
7d9d215
feat: add admin user schema mapper
Tyjfre-j Mar 23, 2026
0bf1e5b
fix: rely on db for session validity in auth service
Tyjfre-j Mar 23, 2026
0f0059e
chore: deprecate token blacklist helpers
Tyjfre-j Mar 23, 2026
7124ec1
fix: validate sessions via db in token auth
Tyjfre-j Mar 23, 2026
97ca343
chore: remove token blacklist helpers
Tyjfre-j Mar 23, 2026
6a2a858
Remove GH Actions cache from docker publish
Tyjfre-j Mar 23, 2026
ea71e2d
Use explicit image tags in docker publish workflow
Tyjfre-j Mar 23, 2026
0dab21a
fix .gitignore for firebase info
wailbentafat Mar 25, 2026
143cf1d
feat : add compute_event_embedding to FaceEmbeddingService (#31)
maya-ots Mar 25, 2026
b04c929
feat: add storage cleaner worker (#33)
wailbentafat Mar 25, 2026
297a707
Feat/ai jetstream listener (#30)
Tyjfre-j Mar 25, 2026
462ba03
feat:seperate repsonsabilites for single face detection
wailbentafat Mar 25, 2026
3bdcc3a
refactor the schema folder add intenral file
wailbentafat Mar 25, 2026
43d7884
refacor dto and logique of the service detection
wailbentafat Mar 25, 2026
4691baf
rename fucntion to follow convention and add readbailite
wailbentafat Mar 25, 2026
9fac4a0
Feat/staged upload review (#36)
ademboukabes Mar 27, 2026
3a63a50
Refresh expired Google Drive access tokens automatically
ademboukabes Mar 27, 2026
4d63b8c
Merge branch 'develop' of https://github.com/MicroClub-USTHB/multAI-b…
wailbentafat Mar 31, 2026
b62ecc0
Feat/group photo processor worker (#37)
maya-ots Mar 31, 2026
3efd867
refactor: optimize worker imports, update dependency management, and …
wailbentafat Mar 31, 2026
49371c6
Merge branch 'develop' of https://github.com/MicroClub-USTHB/multAI-b…
wailbentafat Mar 31, 2026
c936c87
refactor: update Redis initialization and initialize local variables …
wailbentafat Mar 31, 2026
133477a
feat: implement unified photo worker infrastructure and update datab…
wailbentafat Apr 1, 2026
4c053d3
test: add unit tests for PhotoWorker message processing logic
wailbentafat Apr 1, 2026
b3d6c2f
refactor: consolidate photo processing logic into PhotoWorker and imp…
wailbentafat Apr 1, 2026
c8a167d
feat: trigger photo processing events upon approval and implement aut…
wailbentafat Apr 1, 2026
92a5632
test: add test coverage for cleanup event scheduling in photo worker
wailbentafat Apr 1, 2026
c55001e
feat: implement photo approval workflow with decision service and mob…
wailbentafat Apr 1, 2026
70c74e3
feat: add user ID and signup status to auth response and implement au…
wailbentafat Apr 1, 2026
fcf5545
feat: integrate audit logging into photo approval and upload request …
wailbentafat Apr 1, 2026
304718a
feat: publish audit event upon completion of photo processing
wailbentafat Apr 1, 2026
2c60ce4
feat: add Google Drive folder browsing and file search functionality …
wailbentafat Apr 1, 2026
e097e81
feat: add processing job tracking, user photo listing, and system-wid…
wailbentafat Apr 1, 2026
c8405ba
feat: add photos router and implement ListEventPhotosForUser query fo…
wailbentafat Apr 1, 2026
0748d5e
feat: add endpoint and service method to retrieve paginated photos fo…
wailbentafat Apr 1, 2026
3f4f49d
feat: implement photo auto-approval logic, add visibility updates, an…
wailbentafat Apr 1, 2026
c43d524
feat: update UserPhotoService to verify access via face matches and a…
wailbentafat Apr 1, 2026
ac6e82f
Process Drive folder uploads asynchronously in a worker (#38)
ademboukabes Apr 11, 2026
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
2 changes: 1 addition & 1 deletion .env.example
Original file line number Diff line number Diff line change
Expand Up @@ -41,6 +41,6 @@ totp_issuer=MultiAI

GOOGLE_CLIENT_ID=
GOOGLE_CLIENT_SECRET=
GOOGLE_REDIRECT_URI=http://localhost:8000/staff/drive/callback
GOOGLE_REDIRECT_URI=http://127.0.0.1:8000/stuff/drive/callback
GOOGLE_OAUTH_SCOPES=https://www.googleapis.com/auth/drive.readonly openid email profile
FACE_ENCRYPTION_KEY=hkbribvfirirbvivbibvib
16 changes: 3 additions & 13 deletions .github/workflows/docker-publish.yml
Original file line number Diff line number Diff line change
Expand Up @@ -38,22 +38,12 @@ jobs:
username: ${{ github.actor }}
password: ${{ secrets.GITHUB_TOKEN }}

- name: Extract metadata
id: meta
uses: docker/metadata-action@v5
with:
images: ${{ env.REGISTRY }}/${{ env.IMAGE_NAME }}
tags: |
type=raw,value=latest
type=sha,prefix=

- name: Build and push
uses: docker/build-push-action@v5
with:
context: .
push: true
platforms: linux/amd64,linux/arm64
tags: ${{ steps.meta.outputs.tags }}
labels: ${{ steps.meta.outputs.labels }}
cache-from: type=gha
cache-to: type=gha,mode=max
tags: |
${{ env.IMAGE_NAME }}:latest
${{ env.IMAGE_NAME }}:${{ github.sha }}
2 changes: 2 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -7,3 +7,5 @@ db/schema.sql
multiai-c9380-firebase-adminsdk-fbsvc-cb6e5ce41b.json
db.txt

.venv
multiai-c9380-firebase-adminsdk-fbsvc-cb6e5ce41b.json
38 changes: 32 additions & 6 deletions app/container.py
Original file line number Diff line number Diff line change
Expand Up @@ -13,15 +13,21 @@
from app.service.staff_user import StaffUserService

from app.service.audit import AuditService
from app.service.photo_approval import PhotoApprovalService
from app.service.user_photo import UserPhotoService
from app.service.upload_requests import UploadRequestsService
from app.service.users import AuthService
from app.service.user_notification import UserNotificationService
from db.generated import devices as device_queries
from db.generated import photo_approvals as photo_approval_queries
from db.generated import processing_jobs as processing_job_queries
from db.generated import photo_faces as photo_face_queries
from db.generated import photos as photo_queries
from db.generated import session as session_queries
from db.generated import staff_drive_connections as staff_drive_queries
from db.generated import staff_notifications as staff_notification_queries
from db.generated import stuff_user as staff_user_queries
from db.generated import upload_request_groups as upload_request_group_queries
from db.generated import upload_request_photos as upload_request_photo_queries
from db.generated import upload_requests as upload_request_queries
from db.generated import user as user_queries
Expand Down Expand Up @@ -51,9 +57,13 @@ def __init__(
self.device_querier = device_queries.AsyncQuerier(conn)
self.staff_user_querier = staff_user_queries.AsyncQuerier(conn)
self.staff_drive_querier = staff_drive_queries.AsyncQuerier(conn)
self.upload_request_group_querier = upload_request_group_queries.AsyncQuerier(conn)
self.upload_request_querier = upload_request_queries.AsyncQuerier(conn)
self.upload_request_photo_querier = upload_request_photo_queries.AsyncQuerier(conn)
self.photo_querier = photo_queries.AsyncQuerier(conn)
self.photo_approval_querier = photo_approval_queries.AsyncQuerier(conn)
self.photo_face_querier = photo_face_queries.AsyncQuerier(conn)
self.processing_job_querier = processing_job_queries.AsyncQuerier(conn)
self.staff_notification_querier = staff_notification_queries.AsyncQuerier(conn)
self.notification_querier = notification_queries.AsyncQuerier(conn)
self.audit_querier = audit_queries.AsyncQuerier(conn)
Expand Down Expand Up @@ -93,29 +103,31 @@ def __init__(
)
self.staged_upload_storage_service = StagedUploadStorageService()

self.audit_service = AuditService(
audit_querier=self.audit_querier,
user_querier=self.user_querier,
)

self.upload_requests_service = UploadRequestsService(
upload_request_group_querier=self.upload_request_group_querier,
upload_request_querier=self.upload_request_querier,
upload_request_photo_querier=self.upload_request_photo_querier,
photo_querier=self.photo_querier,
staged_upload_storage=self.staged_upload_storage_service,
staff_drive_service=self.staff_drive_service,
staff_notifications_service=self.staff_notifications_service,
audit_service=self.audit_service,
)

notification_queue = NotificationQueue(settings=NotifSetting)

self.user_notifications_service = UserNotificationService(
notification_querier=self.notification_querier,
notification_queue=notification_queue,
)

self.audit_service = AuditService(
audit_querier=self.audit_querier,
user_querier=self.user_querier,
device_querier=self.device_querier,
)

self.staff_user_service = StaffUserService()

self.staff_user_service.init(
staff_user_querier=self.staff_user_querier,)

Expand All @@ -124,6 +136,20 @@ def __init__(
p_querier=self.participant_querier,
)

self.photo_approval_service = PhotoApprovalService(
photo_approval_querier=self.photo_approval_querier,
photo_querier=self.photo_querier,
storage_service=self.staged_upload_storage_service,
audit_service=self.audit_service,
)

self.user_photo_service = UserPhotoService(
photo_querier=self.photo_querier,
photo_face_querier=self.photo_face_querier,
photo_approval_querier=self.photo_approval_querier,
staff_drive_service=self.staff_drive_service,
)

async def get_container(
conn: sqlalchemy.ext.asyncio.AsyncConnection = Depends(get_db),
) -> Container:
Expand Down
39 changes: 32 additions & 7 deletions app/core/config.py
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
from pydantic_settings import BaseSettings
from pydantic_settings import BaseSettings, SettingsConfigDict
from pydantic import field_validator


class Settings(BaseSettings):
Expand All @@ -16,13 +17,13 @@ class Settings(BaseSettings):
NATS_HOST: str
NATS_PASSWORD: str
NATS_USER: str


# MinIO
MINIO_API_PORT: int
MINIO_ROOT_USER: str
MINIO_ROOT_PASSWORD: str
MINIO_HOST: str
MINIO_RETRY_ATTEMPTS: int = 3
MINIO_RETRY_BASE_SECONDS: float = 0.5

# PostgreSQL
POSTGRES_USER: str
Expand All @@ -35,13 +36,22 @@ class Settings(BaseSettings):
MOBILE_SESSION_LIMIT: int = 3
MOBILE_SESSION_TTL_SECONDS: int = 180
MOBILE_SESSION_DAYS: int = 7

# Admin list defaults
ADMIN_USERS_DEFAULT_LIMIT: int = 20
ADMIN_USERS_MAX_LIMIT: int = 100
# Security
jwt_secret: str
jwt_algorithm: str = "HS256"
encryption_key: str
totp_issuer: str = "multAI"

# Face embedding model
FACE_EMBEDDING_MODEL_NAME: str = "buffalo_l"
FACE_EMBEDDING_PROVIDERS: str = "CPUExecutionProvider"
FACE_EMBEDDING_CTX_ID: int = -1
FACE_EMBEDDING_DET_WIDTH: int = 640
FACE_EMBEDDING_DET_HEIGHT: int = 640

# Google Drive OAuth
GOOGLE_CLIENT_ID: str = ""
GOOGLE_CLIENT_SECRET: str = ""
Expand All @@ -53,9 +63,24 @@ class Settings(BaseSettings):
FACE_ENCRYPTION_KEY: str
FIREBASE_CREDENTIALS_PATH: str = "multiai-c9380-firebase-adminsdk-fbsvc-cb6e5ce41b.json"

class Config:
env_file = ".env"
extra = "ignore"
model_config = SettingsConfigDict(
env_file=".env",
extra="ignore",
)

@field_validator("debug", mode="before")
@classmethod
def _parse_debug(cls, value): # type: ignore[no-untyped-def]
if value is None:
return True
if isinstance(value, str):
lowered = value.strip().lower()
if lowered in {"release", "prod", "production", "false", "0", "no"}:
return False
if lowered in {"true", "1", "yes"}:
return True
return value
return value


settings = Settings() # type: ignore
22 changes: 21 additions & 1 deletion app/core/constant.py
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,12 @@ class RedisKey(str, Enum):

NOTIFICATION_EVENT_SUBJECT = "notification_event"
AUDIT_EVENT_SUBJECT = "audit.event"
FINAL_BUCKET_CLEANUP_SUBJECT = "ai.final_bucket.completed"
FINAL_BUCKET_CLEANUP_STREAM = "ai-final-bucket-cleanup"
FINAL_BUCKET_CLEANUP_DURABLE_NAME = "ai-final-bucket-cleaner"
UPLOAD_GROUP_IMPORT_SUBJECT = "staff.upload_group.import.requested"
UPLOAD_GROUP_IMPORT_STREAM = "staff-upload-group-import"
UPLOAD_GROUP_IMPORT_DURABLE_NAME = "staff-upload-group-import-worker"


class AuditEventType(str, Enum):
Expand All @@ -18,7 +24,8 @@ class AuditEventType(str, Enum):
UPLOAD_REQUEST_CREATED = "upload_request.created"
UPLOAD_REQUEST_APPROVED = "upload_request.approved"
UPLOAD_REQUEST_REJECTED = "upload_request.rejected"

PHOTO_PROCESSED = "photo.processed"
PHOTO_APPROVAL_DECIDED = "photo_approval.decided"


IMAGE_ALLOWED_TYPES = {
Expand All @@ -28,6 +35,19 @@ class AuditEventType(str, Enum):
"image/heif"
}

DEFAULT_CONTENT_TYPE = "application/octet-stream"
DRIVE_ALLOWED_HOSTS = {"drive.google.com", "docs.google.com"}
MINIO_URL_PREFIX = "minio://"

IMAGES_BUCKET_NAME = "images"
DOCUMENTS_BUCKET_NAME = "documents"
WA_SIM_BUCKET_NAME = "wa-sim"

GOOGLE_AUTH_URL = "https://accounts.google.com/o/oauth2/v2/auth"
GOOGLE_TOKEN_URL = "https://oauth2.googleapis.com/token"
GOOGLE_USERINFO_URL = "https://www.googleapis.com/oauth2/v2/userinfo"
GOOGLE_DRIVE_FILES_URL = "https://www.googleapis.com/drive/v3/files/{file_id}"

MAX_IMAGE_SIZE = 5 * 1024 * 1024
MIN_ENROLL_IMAGES = 3
MAX_ENROLL_IMAGES = 5
3 changes: 3 additions & 0 deletions app/core/exceptions.py
Original file line number Diff line number Diff line change
Expand Up @@ -75,6 +75,9 @@ def handle_check_violation(exc: Exception) -> HTTPException:
def handle(exc: Exception) -> HTTPException:
logger.error("Database error: %s", exc)

if isinstance(exc, HTTPException):
return exc

if isinstance(exc, IntegrityError):
orig = getattr(exc, "orig", None)
sqlstate = getattr(orig, "sqlstate", None)
Expand Down
2 changes: 2 additions & 0 deletions app/deps/token_auth.py
Original file line number Diff line number Diff line change
Expand Up @@ -43,6 +43,8 @@ async def get_current_mobile_user(
user = await container.auth_service.user_querier.get_user_by_id(id=session.user_id)
if not user:
raise HTTPException(status_code=401, detail="User not found")
if user.blocked:
raise HTTPException(status_code=403, detail="User is blocked")

return MobileUserSchema(
user_id=user.id,
Expand Down
Loading
Loading