초기 커밋

This commit is contained in:
2026-03-01 07:44:19 +09:00
commit 09359f30be
146 changed files with 6120 additions and 0 deletions

View File

View File

@@ -0,0 +1,44 @@
from __future__ import annotations
from datetime import datetime
from app.models.mongodb.analytics_result import AnalyticsResult
class AnalyticsRepository:
async def create(self, result: AnalyticsResult) -> AnalyticsResult:
return await result.insert()
async def get_by_type(
self,
analysis_type: str,
device_id: str | None = None,
skip: int = 0,
limit: int = 20,
) -> list[AnalyticsResult]:
query: dict = {"analysis_type": analysis_type}
if device_id:
query["device_id"] = device_id
return await (
AnalyticsResult.find(query)
.sort("-created_at")
.skip(skip)
.limit(limit)
.to_list()
)
async def get_by_period(
self,
analysis_type: str,
start: datetime,
end: datetime,
device_id: str | None = None,
) -> list[AnalyticsResult]:
query: dict = {
"analysis_type": analysis_type,
"period_start": {"$gte": start},
"period_end": {"$lte": end},
}
if device_id:
query["device_id"] = device_id
return await AnalyticsResult.find(query).sort("-created_at").to_list()

View File

@@ -0,0 +1,50 @@
from __future__ import annotations
from datetime import datetime
from sqlalchemy import select
from sqlalchemy.ext.asyncio import AsyncSession
from app.models.mariadb.auth import OAuthAccount, RefreshToken
from app.repositories.base import BaseRepository
class AuthRepository(BaseRepository[RefreshToken]):
def __init__(self, session: AsyncSession):
super().__init__(RefreshToken, session)
async def get_by_token(self, token: str) -> RefreshToken | None:
stmt = select(RefreshToken).where(
RefreshToken.token == token,
RefreshToken.is_revoked == False, # noqa: E712
RefreshToken.expires_at > datetime.utcnow(),
)
result = await self.session.execute(stmt)
return result.scalar_one_or_none()
async def revoke_all_for_user(self, user_id: int) -> None:
stmt = select(RefreshToken).where(
RefreshToken.user_id == user_id,
RefreshToken.is_revoked == False, # noqa: E712
)
result = await self.session.execute(stmt)
for token in result.scalars().all():
token.is_revoked = True
self.session.add(token)
await self.session.flush()
async def get_oauth_account(
self, provider: str, provider_user_id: str
) -> OAuthAccount | None:
stmt = select(OAuthAccount).where(
OAuthAccount.provider == provider,
OAuthAccount.provider_user_id == provider_user_id,
)
result = await self.session.execute(stmt)
return result.scalar_one_or_none()
async def create_oauth_account(self, account: OAuthAccount) -> OAuthAccount:
self.session.add(account)
await self.session.flush()
await self.session.refresh(account)
return account

58
app/repositories/base.py Normal file
View File

@@ -0,0 +1,58 @@
from __future__ import annotations
from typing import Generic, Sequence, TypeVar
from sqlalchemy import func, select
from sqlalchemy.ext.asyncio import AsyncSession
from sqlmodel import SQLModel
T = TypeVar("T", bound=SQLModel)
class BaseRepository(Generic[T]):
def __init__(self, model: type[T], session: AsyncSession):
self.model = model
self.session = session
async def get_by_id(self, id: int) -> T | None:
return await self.session.get(self.model, id)
async def get_all(
self, *, skip: int = 0, limit: int = 100, filters: dict | None = None
) -> Sequence[T]:
stmt = select(self.model)
if filters:
for key, value in filters.items():
if hasattr(self.model, key):
stmt = stmt.where(getattr(self.model, key) == value)
stmt = stmt.offset(skip).limit(limit)
result = await self.session.execute(stmt)
return result.scalars().all()
async def count(self, filters: dict | None = None) -> int:
stmt = select(func.count()).select_from(self.model)
if filters:
for key, value in filters.items():
if hasattr(self.model, key):
stmt = stmt.where(getattr(self.model, key) == value)
result = await self.session.execute(stmt)
return result.scalar_one()
async def create(self, obj: T) -> T:
self.session.add(obj)
await self.session.flush()
await self.session.refresh(obj)
return obj
async def update(self, obj: T, data: dict) -> T:
for key, value in data.items():
if value is not None and hasattr(obj, key):
setattr(obj, key, value)
self.session.add(obj)
await self.session.flush()
await self.session.refresh(obj)
return obj
async def delete(self, obj: T) -> None:
await self.session.delete(obj)
await self.session.flush()

View File

@@ -0,0 +1,40 @@
from __future__ import annotations
from datetime import datetime
from app.models.mongodb.device_log import DeviceLog
class DeviceLogRepository:
async def create(self, log: DeviceLog) -> DeviceLog:
return await log.insert()
async def get_by_device(
self,
device_id: str,
event_type: str | None = None,
since: datetime | None = None,
skip: int = 0,
limit: int = 100,
) -> list[DeviceLog]:
query: dict = {"device_id": device_id}
if event_type:
query["event_type"] = event_type
if since:
query["timestamp"] = {"$gte": since}
return await (
DeviceLog.find(query)
.sort("-timestamp")
.skip(skip)
.limit(limit)
.to_list()
)
async def count_by_device(
self, device_id: str, event_type: str | None = None
) -> int:
query: dict = {"device_id": device_id}
if event_type:
query["event_type"] = event_type
return await DeviceLog.find(query).count()

View File

@@ -0,0 +1,47 @@
from __future__ import annotations
from sqlalchemy import select
from sqlalchemy.ext.asyncio import AsyncSession
from app.models.mariadb.device import Device, DeviceGroup
from app.repositories.base import BaseRepository
class DeviceRepository(BaseRepository[Device]):
def __init__(self, session: AsyncSession):
super().__init__(Device, session)
async def get_by_uid(self, device_uid: str) -> Device | None:
stmt = select(Device).where(Device.device_uid == device_uid, Device.is_deleted == False) # noqa: E712
result = await self.session.execute(stmt)
return result.scalar_one_or_none()
async def get_by_owner(self, owner_id: int, skip: int = 0, limit: int = 100) -> list[Device]:
stmt = (
select(Device)
.where(Device.owner_id == owner_id, Device.is_deleted == False) # noqa: E712
.offset(skip)
.limit(limit)
)
result = await self.session.execute(stmt)
return list(result.scalars().all())
async def get_by_group(self, group_id: int, skip: int = 0, limit: int = 100) -> list[Device]:
stmt = (
select(Device)
.where(Device.group_id == group_id, Device.is_deleted == False) # noqa: E712
.offset(skip)
.limit(limit)
)
result = await self.session.execute(stmt)
return list(result.scalars().all())
class DeviceGroupRepository(BaseRepository[DeviceGroup]):
def __init__(self, session: AsyncSession):
super().__init__(DeviceGroup, session)
async def get_by_name(self, name: str) -> DeviceGroup | None:
stmt = select(DeviceGroup).where(DeviceGroup.name == name)
result = await self.session.execute(stmt)
return result.scalar_one_or_none()

View File

@@ -0,0 +1,36 @@
from __future__ import annotations
from sqlalchemy import select
from sqlalchemy.ext.asyncio import AsyncSession
from app.models.mariadb.monitoring import Alert, AlertRule
from app.repositories.base import BaseRepository
class AlertRuleRepository(BaseRepository[AlertRule]):
def __init__(self, session: AsyncSession):
super().__init__(AlertRule, session)
async def get_enabled_rules(self) -> list[AlertRule]:
stmt = select(AlertRule).where(AlertRule.is_enabled == True) # noqa: E712
result = await self.session.execute(stmt)
return list(result.scalars().all())
class AlertRepository(BaseRepository[Alert]):
def __init__(self, session: AsyncSession):
super().__init__(Alert, session)
async def get_unacknowledged(self, skip: int = 0, limit: int = 50) -> list[Alert]:
stmt = (
select(Alert)
.where(Alert.is_acknowledged == False) # noqa: E712
.order_by(Alert.created_at.desc())
.offset(skip)
.limit(limit)
)
result = await self.session.execute(stmt)
return list(result.scalars().all())
async def count_active(self) -> int:
return await self.count(filters={"is_acknowledged": False})

View File

@@ -0,0 +1,44 @@
from __future__ import annotations
from sqlalchemy import select
from sqlalchemy.ext.asyncio import AsyncSession
from sqlalchemy.orm import selectinload
from app.models.mariadb.user import User, UserProfile
from app.repositories.base import BaseRepository
class UserRepository(BaseRepository[User]):
def __init__(self, session: AsyncSession):
super().__init__(User, session)
async def get_by_email(self, email: str) -> User | None:
stmt = select(User).where(User.email == email)
result = await self.session.execute(stmt)
return result.scalar_one_or_none()
async def get_with_profile(self, user_id: int) -> User | None:
stmt = (
select(User)
.options(selectinload(User.profile))
.where(User.id == user_id)
)
result = await self.session.execute(stmt)
return result.scalar_one_or_none()
async def create_with_profile(
self, user: User, full_name: str = "", phone: str = "", organization: str = ""
) -> User:
self.session.add(user)
await self.session.flush()
profile = UserProfile(
user_id=user.id, # type: ignore[arg-type]
full_name=full_name,
phone=phone,
organization=organization,
)
self.session.add(profile)
await self.session.flush()
await self.session.refresh(user)
return user