Add models and schemas for users, tenants, subscriptions, plans, and business types

This commit is contained in:
Yoga Pangestu 2026-07-17 21:24:55 +07:00
parent e805f86563
commit 8553971981
14 changed files with 298 additions and 5 deletions

15
app/models/__init__.py Normal file
View File

@ -0,0 +1,15 @@
from app.models.user import User
from app.models.user_profile import UserProfile
from app.models.business_type import BusinessType
from app.models.plan import Plan
from app.models.tenant import Tenant
from app.models.subscription import Subscription
__all__ = [
"User",
"UserProfile",
"BusinessType",
"Plan",
"Tenant",
"Subscription",
]

View File

@ -0,0 +1,21 @@
import uuid
from datetime import datetime
from sqlalchemy import Boolean, DateTime, String, Text
from sqlalchemy.dialects.mysql import CHAR
from sqlalchemy.orm import Mapped, mapped_column
from app.database import Base
class BusinessType(Base):
__tablename__ = "business_types"
id: Mapped[str] = mapped_column(CHAR(36), primary_key=True, default=lambda: str(uuid.uuid4()))
code: Mapped[str] = mapped_column(String(30), unique=True)
name: Mapped[str] = mapped_column(String(100))
description: Mapped[str | None] = mapped_column(Text, nullable=True)
is_active: Mapped[bool] = mapped_column(Boolean, default=True)
created_at: Mapped[datetime] = mapped_column(DateTime, default=datetime.now)
updated_at: Mapped[datetime | None] = mapped_column(DateTime, nullable=True, onupdate=datetime.now)
deleted_at: Mapped[datetime | None] = mapped_column(DateTime, nullable=True)

23
app/models/plan.py Normal file
View File

@ -0,0 +1,23 @@
import uuid
from datetime import datetime
from sqlalchemy import Boolean, DateTime, Integer, String, Text
from sqlalchemy.dialects.mysql import CHAR, JSON
from sqlalchemy.orm import Mapped, mapped_column
from app.database import Base
class Plan(Base):
__tablename__ = "plans"
id: Mapped[str] = mapped_column(CHAR(36), primary_key=True, default=lambda: str(uuid.uuid4()))
code: Mapped[str] = mapped_column(String(20), unique=True)
name: Mapped[str] = mapped_column(String(100))
description: Mapped[str | None] = mapped_column(Text, nullable=True)
price: Mapped[int] = mapped_column(Integer)
limits: Mapped[dict | None] = mapped_column(JSON, nullable=True)
is_active: Mapped[bool] = mapped_column(Boolean, default=True)
created_at: Mapped[datetime] = mapped_column(DateTime, default=datetime.now)
updated_at: Mapped[datetime | None] = mapped_column(DateTime, nullable=True, onupdate=datetime.now)
deleted_at: Mapped[datetime | None] = mapped_column(DateTime, nullable=True)

View File

@ -0,0 +1,25 @@
import uuid
from datetime import datetime
from sqlalchemy import DateTime, ForeignKey, String
from sqlalchemy.dialects.mysql import CHAR, JSON
from sqlalchemy.orm import Mapped, mapped_column, relationship
from app.database import Base
class Subscription(Base):
__tablename__ = "subscriptions"
id: Mapped[str] = mapped_column(CHAR(36), primary_key=True, default=lambda: str(uuid.uuid4()))
tenant_id: Mapped[str] = mapped_column(CHAR(36), ForeignKey("tenants.id"), unique=True)
plan_id: Mapped[str] = mapped_column(CHAR(36), ForeignKey("plans.id"))
limits: Mapped[dict | None] = mapped_column(JSON, nullable=True)
status: Mapped[str] = mapped_column(String(20), default="TRIAL")
started_at: Mapped[datetime] = mapped_column(DateTime, default=datetime.now)
ended_at: Mapped[datetime | None] = mapped_column(DateTime, nullable=True)
created_at: Mapped[datetime] = mapped_column(DateTime, default=datetime.now)
updated_at: Mapped[datetime | None] = mapped_column(DateTime, nullable=True, onupdate=datetime.now)
tenant: Mapped["Tenant"] = relationship(back_populates="subscription")
plan: Mapped["Plan"] = relationship()

27
app/models/tenant.py Normal file
View File

@ -0,0 +1,27 @@
import uuid
from datetime import datetime
from sqlalchemy import DateTime, ForeignKey, String, Text
from sqlalchemy.dialects.mysql import CHAR
from sqlalchemy.orm import Mapped, mapped_column, relationship
from app.database import Base
class Tenant(Base):
__tablename__ = "tenants"
id: Mapped[str] = mapped_column(CHAR(36), primary_key=True, default=lambda: str(uuid.uuid4()))
name: Mapped[str] = mapped_column(String(200))
slug: Mapped[str] = mapped_column(String(100), unique=True)
business_type_id: Mapped[str] = mapped_column(CHAR(36), ForeignKey("business_types.id"))
phone: Mapped[str | None] = mapped_column(String(20), nullable=True)
address: Mapped[str | None] = mapped_column(Text, nullable=True)
email: Mapped[str | None] = mapped_column(String(100), nullable=True)
status: Mapped[str] = mapped_column(String(20), default="ACTIVE")
created_at: Mapped[datetime] = mapped_column(DateTime, default=datetime.now)
updated_at: Mapped[datetime | None] = mapped_column(DateTime, nullable=True, onupdate=datetime.now)
deleted_at: Mapped[datetime | None] = mapped_column(DateTime, nullable=True)
business_type: Mapped["BusinessType"] = relationship()
subscription: Mapped["Subscription"] = relationship(back_populates="tenant", uselist=False)

25
app/models/user.py Normal file
View File

@ -0,0 +1,25 @@
import uuid
from datetime import datetime
from sqlalchemy import DateTime, String
from sqlalchemy.dialects.mysql import CHAR
from sqlalchemy.orm import Mapped, mapped_column, relationship
from app.database import Base
class User(Base):
__tablename__ = "users"
id: Mapped[str] = mapped_column(CHAR(36), primary_key=True, default=lambda: str(uuid.uuid4()))
email: Mapped[str] = mapped_column(String(100), unique=True)
username: Mapped[str] = mapped_column(String(20), unique=True)
password: Mapped[str] = mapped_column(String(255))
email_verified_at: Mapped[datetime | None] = mapped_column(DateTime, nullable=True)
tenant_id: Mapped[str | None] = mapped_column(CHAR(36), nullable=True)
status: Mapped[str] = mapped_column(String(20), default="ONBOARDING")
created_at: Mapped[datetime] = mapped_column(DateTime, default=datetime.now)
updated_at: Mapped[datetime | None] = mapped_column(DateTime, nullable=True, onupdate=datetime.now)
deleted_at: Mapped[datetime | None] = mapped_column(DateTime, nullable=True)
profile: Mapped["UserProfile"] = relationship(back_populates="user", uselist=False)

View File

@ -0,0 +1,23 @@
import uuid
from datetime import datetime
from sqlalchemy import DateTime, ForeignKey, String
from sqlalchemy.dialects.mysql import CHAR
from sqlalchemy.orm import Mapped, mapped_column, relationship
from app.database import Base
class UserProfile(Base):
__tablename__ = "user_profiles"
id: Mapped[str] = mapped_column(CHAR(36), primary_key=True, default=lambda: str(uuid.uuid4()))
user_id: Mapped[str] = mapped_column(CHAR(36), ForeignKey("users.id"), unique=True)
full_name: Mapped[str] = mapped_column(String(200))
phone: Mapped[str | None] = mapped_column(String(20), nullable=True)
timezone: Mapped[str | None] = mapped_column(String(50), nullable=True, default="Asia/Jakarta")
created_at: Mapped[datetime] = mapped_column(DateTime, default=datetime.now)
updated_at: Mapped[datetime | None] = mapped_column(DateTime, nullable=True, onupdate=datetime.now)
deleted_at: Mapped[datetime | None] = mapped_column(DateTime, nullable=True)
user: Mapped["User"] = relationship(back_populates="profile")

17
app/schemas/__init__.py Normal file
View File

@ -0,0 +1,17 @@
from app.schemas.user import UserCreate, UserResponse
from app.schemas.business_type import BusinessTypeCreate, BusinessTypeResponse
from app.schemas.plan import PlanCreate, PlanResponse
from app.schemas.tenant import TenantCreate, TenantResponse
from app.schemas.subscription import SubscriptionResponse
__all__ = [
"UserCreate",
"UserResponse",
"BusinessTypeCreate",
"BusinessTypeResponse",
"PlanCreate",
"PlanResponse",
"TenantCreate",
"TenantResponse",
"SubscriptionResponse",
]

View File

@ -0,0 +1,21 @@
from datetime import datetime
from pydantic import BaseModel
class BusinessTypeCreate(BaseModel):
code: str
name: str
description: str | None = None
class BusinessTypeResponse(BaseModel):
id: str
code: str
name: str
description: str | None
is_active: bool
created_at: datetime
class Config:
from_attributes = True

25
app/schemas/plan.py Normal file
View File

@ -0,0 +1,25 @@
from datetime import datetime
from pydantic import BaseModel
class PlanCreate(BaseModel):
code: str
name: str
description: str | None = None
price: int
limits: dict | None = None
class PlanResponse(BaseModel):
id: str
code: str
name: str
description: str | None
price: int
limits: dict | None
is_active: bool
created_at: datetime
class Config:
from_attributes = True

View File

@ -0,0 +1,16 @@
from datetime import datetime
from pydantic import BaseModel
class SubscriptionResponse(BaseModel):
id: str
tenant_id: str
plan_id: str
limits: dict | None
status: str
started_at: datetime
ended_at: datetime | None
class Config:
from_attributes = True

29
app/schemas/tenant.py Normal file
View File

@ -0,0 +1,29 @@
from datetime import datetime
from pydantic import BaseModel
class TenantCreate(BaseModel):
name: str
slug: str
business_type_id: str
phone: str | None = None
address: str | None = None
email: str | None = None
plan_id: str
limits: dict | None = None
class TenantResponse(BaseModel):
id: str
name: str
slug: str
business_type_id: str
phone: str | None
address: str | None
email: str | None
status: str
created_at: datetime
class Config:
from_attributes = True

24
app/schemas/user.py Normal file
View File

@ -0,0 +1,24 @@
from datetime import datetime
from pydantic import BaseModel, EmailStr
class UserCreate(BaseModel):
email: EmailStr
username: str
password: str
full_name: str
phone: str | None = None
timezone: str = "Asia/Jakarta"
class UserResponse(BaseModel):
id: str
email: str
username: str
tenant_id: str | None
status: str
created_at: datetime
class Config:
from_attributes = True

12
main.py
View File

@ -19,11 +19,13 @@ app.add_middleware(
allow_headers=["*"],
)
app.include_router(auth_router)
app.include_router(business_types_router)
app.include_router(plans_router)
app.include_router(tenants_router)
app.include_router(subscriptions_router)
API_PREFIX = "/v1"
app.include_router(auth_router, prefix=API_PREFIX)
app.include_router(business_types_router, prefix=API_PREFIX)
app.include_router(plans_router, prefix=API_PREFIX)
app.include_router(tenants_router, prefix=API_PREFIX)
app.include_router(subscriptions_router, prefix=API_PREFIX)
@app.get("/")