Skip to main content

data-model-inventory.md

CODITECT Multi-Tenant SaaS Platform - Complete Model Inventory

Generated: 2025-12-30 Purpose: Comprehensive inventory of all existing Django models Status: Production data model for pilot launch (December 24, 2025)


Table of Contents

  1. Overview
  2. Apps Summary
  3. Detailed Model Inventory
  4. Multi-Tenant Architecture
  5. Database Tables Summary

Overview

The CODITECT backend consists of 11 Django apps with 40 models total covering:

  • User Management - Authentication, roles, OAuth
  • Multi-Tenancy - Organization isolation and licensing
  • Commerce - Products, carts, orders, entitlements
  • Subscriptions - Billing, invoices, usage metrics
  • Permissions - RBAC (role-based access control)
  • Context Sync - Cross-device Claude Code session synchronization
  • Licenses - Floating license management
  • Workstations - Cloud development environments (GCP Workstations)
  • Repositories - Multi-tenant Git hosting (Gitea integration)

Architecture Pattern

  • Multi-Tenant Isolation: Django-multitenant with TenantModel mixin (automatic query filtering)
  • Soft Deletes: deleted_at timestamps (not hard deletes)
  • UUID Primary Keys: All models use UUIDs (except global catalog models)
  • Timestamps: created_at, updated_at on all models
  • Indexes: Strategic indexes for query performance
  • Constraints: Unique constraints for business rules

Apps Summary

AppModels CountPrimary PurposeMulti-Tenant
users1User authentication (email, OAuth, roles)Via FK to Tenant
tenants1Organization-level data (licenses, seats)TenantModel (root)
organizations4Teams, memberships, projectsTenantModel
commerce5Products, carts, orders, entitlementsMixed (Product global, rest tenant-scoped)
subscriptions3Billing, invoices, usage trackingTenantModel
permissions1RBAC permission definitionsGlobal (no tenant)
context3Claude Code session sync (messages, cursors)TenantModel
licenses2Floating licenses, sessionsVia FK to Tenant
workstations6Cloud development environmentsVia tenant_id field
repositories7Gitea multi-tenant Git hostingVia organization FK
core0Base app (no models)N/A
TOTAL40

Detailed Model Inventory

Users App

Location: backend/users/models.py Models: 1

User

Purpose: Custom user model extending Django's AbstractUser with tenant support.

FieldTypeConstraintsDescription
idUUIDFieldPK, default=uuid4Unique user identifier
usernameCharField(150)UniqueDjango AbstractUser field
first_nameCharField(150)Django AbstractUser field
last_nameCharField(150)Django AbstractUser field
emailEmailFieldUniqueGlobally unique email
passwordCharField(128)Django AbstractUser field (hashed)
is_staffBooleanFieldDjango admin access
is_activeBooleanFieldAccount active status
is_superuserBooleanFieldDjango superuser flag
date_joinedDateTimeFieldDjango AbstractUser field
last_loginDateTimeFieldNullableDjango AbstractUser field
tenantForeignKey(Tenant)CASCADETenant this user belongs to
roleCharField(20)'owner', 'admin', 'member', 'guest'
oauth_providerCharField(50)Nullable'google', 'github'
oauth_subCharField(255)NullableOAuth subject identifier
updated_atDateTimeFieldauto_nowLast update timestamp

Indexes:

  • tenant + email
  • oauth_provider + oauth_sub
  • role

Unique Constraints:

  • (tenant, email) - Email unique per tenant

Business Rules:

  • Email globally unique AND unique per tenant
  • role determines permissions (via Permission model)
  • OAuth users have oauth_provider + oauth_sub populated

Methods:

  • is_tenant_owner() - Check if role='owner'
  • is_tenant_admin() - Check if role in ['owner', 'admin']
  • can_manage_users() - Permission check
  • can_manage_licenses() - Permission check

Tenants App

Location: backend/tenants/models.py Models: 1

Tenant

Purpose: Root tenant (organization) model - represents a customer organization.

FieldTypeConstraintsDescription
idUUIDFieldPK, default=uuid4Unique tenant identifier
nameCharField(255)Organization name
slugSlugField(100)UniqueURL-safe identifier (e.g., 'acme-corp')
emailEmailFieldPrimary contact email
license_tierCharField(20)'trial', 'starter', 'professional', 'enterprise'
seats_totalPositiveIntegerFielddefault=1Concurrent seat limit
is_activeBooleanFielddefault=TrueAccount active status
created_atDateTimeFieldauto_now_addCreation timestamp
updated_atDateTimeFieldauto_nowLast update timestamp
stripe_customer_idCharField(255)NullableStripe customer ID
stripe_subscription_idCharField(255)NullableStripe subscription ID

Inherits From: TenantModel (django-multitenant)

Indexes:

  • slug
  • email
  • is_active

Business Rules:

  • slug globally unique
  • seats_total determines concurrent session limit
  • Stripe IDs populated after checkout completion

Methods:

  • get_active_seat_count() - Count active sessions
  • has_available_seats() - Check seat availability
  • get_seat_utilization() - Percentage of seats used

Organizations App

Location: backend/organizations/models.py Models: 4

MembershipRole (Enum)

Purpose: Enum for membership roles.

Values:

  • OWNER - 'owner'
  • ADMIN - 'admin'
  • MEMBER - 'member'
  • VIEWER - 'viewer'

Membership

Purpose: User-to-Organization membership with role.

FieldTypeConstraintsDescription
idUUIDFieldPK, default=uuid4Unique membership identifier
tenantForeignKey(Tenant)CASCADEOrganization
userTenantForeignKey(User)CASCADEUser in this organization
roleCharField(20)MembershipRole choice
joined_atDateTimeFielddefault=nowWhen user joined
revoked_atDateTimeFieldNullableWhen membership was revoked

Inherits From: TenantModel

Indexes:

  • (tenant, user, role) where revoked_at IS NULL

Unique Constraints:

  • (user, tenant) - User can only have one role per org

Methods:

  • is_active() - Check if revoked_at is None

Team

Purpose: Team within an organization.

FieldTypeConstraintsDescription
idUUIDFieldPK, default=uuid4Unique team identifier
tenantForeignKey(Tenant)CASCADEOrganization
nameCharField(255)Team name
descriptionTextFieldTeam description
created_atDateTimeFieldauto_now_addCreation timestamp
updated_atDateTimeFieldauto_nowLast update timestamp

Inherits From: TenantModel

Unique Constraints:

  • (tenant, name) - Team name unique per org

TeamMembership

Purpose: User-to-Team membership.

FieldTypeConstraintsDescription
idUUIDFieldPK, default=uuid4Unique membership identifier
tenantForeignKey(Tenant)CASCADEOrganization
teamTenantForeignKey(Team)CASCADETeam
userTenantForeignKey(User)CASCADEUser
joined_atDateTimeFielddefault=nowWhen user joined team

Inherits From: TenantModel

Unique Constraints:

  • (team, user) - User can only belong to a team once

Project

Purpose: Project within an organization.

FieldTypeConstraintsDescription
idUUIDFieldPK, default=uuid4Unique project identifier
tenantForeignKey(Tenant)CASCADEOrganization
nameCharField(255)Project name
descriptionTextFieldProject description
metadataJSONFielddefault=dictArbitrary metadata
created_atDateTimeFieldauto_now_addCreation timestamp
updated_atDateTimeFieldauto_nowLast update timestamp
deleted_atDateTimeFieldNullableSoft delete timestamp

Inherits From: TenantModel

Indexes:

  • (tenant, -created_at) where deleted_at IS NULL

Unique Constraints:

  • (tenant, name) where deleted_at IS NULL

Methods:

  • soft_delete() - Set deleted_at to now

Commerce App

Location: backend/commerce/models.py Models: 5

Product

Purpose: Global product catalog (NOT tenant-specific).

FieldTypeConstraintsDescription
idUUIDFieldPK, default=uuid4Unique product identifier
slugSlugField(50)UniqueProduct slug ('core', 'dms', 'workflow', 'enterprise')
nameCharField(255)Product name
descriptionTextFieldProduct description
product_typeCharField(20)'base', 'addon', 'bundle'
price_centsIntegerFieldPrice in cents (e.g., 4900 = $49.00)
currencyCharField(3)default='usd'Currency code
billing_intervalCharField(10)'month', 'year'
stripe_price_idCharField(255)Unique, nullableStripe Price ID
featuresJSONFielddefault=listList of feature strings
requiresJSONFielddefault=listList of required product slugs
subdomainCharField(255)NullableProduct subdomain (e.g., 'dms.coditect.ai')
activeBooleanFielddefault=TrueProduct active in catalog
created_atDateTimeFieldauto_now_addCreation timestamp
updated_atDateTimeFieldauto_nowLast update timestamp

Indexes:

  • slug where active=True
  • product_type where active=True

Business Rules:

  • Global catalog (all tenants see same products)
  • requires field enforces dependencies (e.g., DMS requires Core)

Methods:

  • get_dependencies() - Return list of required slugs

CODITECT Products:

  • core - CODITECT Core (base, $49/mo)
  • dms - CODITECT DMS (addon, $29/mo, requires core)
  • workflow - Workflow Analyzer (addon, $19/mo, requires core)
  • enterprise - Enterprise Bundle (bundle, $149/mo, all products)

Cart

Purpose: Shopping cart (guest or authenticated).

FieldTypeConstraintsDescription
idUUIDFieldPK, default=uuid4Unique cart identifier
userForeignKey(User)CASCADE, nullableAuthenticated user
session_idCharField(255)Unique, nullableGuest session ID
itemsJSONFielddefault=listList of {product_id, quantity, price_cents}
total_centsIntegerFielddefault=0Total cart value in cents
currencyCharField(3)default='usd'Currency code
statusCharField(20)'active', 'checkout', 'completed', 'abandoned'
expires_atDateTimeFieldCart expiration (24 hours from last update)
created_atDateTimeFieldauto_now_addCreation timestamp
updated_atDateTimeFieldauto_nowLast update timestamp

Indexes:

  • user where status='active'
  • session_id where status='active'
  • expires_at where status='active'

Business Rules:

  • One active cart per user OR session_id
  • Cart expires after 24 hours of inactivity
  • Must validate dependencies before checkout

Methods:

  • add_item(product, quantity) - Add product to cart
  • remove_item(product_id) - Remove product from cart
  • _recalculate_total() - Recalculate total_cents
  • is_expired() - Check if cart expired
  • validate_dependencies() - Check product requirements

Order

Purpose: Completed purchase order.

FieldTypeConstraintsDescription
idUUIDFieldPK, default=uuid4Unique order identifier
tenantForeignKey(Tenant)SET_NULL, nullableOrganization (may be NULL pre-assignment)
userForeignKey(User)RESTRICTPurchasing user
stripe_payment_intent_idCharField(255)Unique, nullableStripe PaymentIntent ID
stripe_checkout_session_idCharField(255)Unique, nullableStripe Checkout Session ID
payment_methodCharField(50)'stripe_checkout', 'google_pay', 'apple_pay'
total_centsIntegerFieldTotal order value in cents
currencyCharField(3)default='usd'Currency code
statusCharField(20)'pending', 'processing', 'paid', 'failed', 'refunded'
line_itemsJSONFielddefault=listList of {product_id, product_slug, quantity, price_cents}
billing_detailsJSONFieldNullableBilling address, name, etc.
paid_atDateTimeFieldNullablePayment completion timestamp
created_atDateTimeFieldauto_now_addOrder creation timestamp
updated_atDateTimeFieldauto_nowLast update timestamp

Inherits From: TenantModel

Indexes:

  • user
  • tenant
  • status where status IN ('pending', 'processing')
  • stripe_payment_intent_id

Methods:

  • mark_paid() - Set status='paid', paid_at=now

Entitlement

Purpose: Product access entitlement for an organization.

FieldTypeConstraintsDescription
idUUIDFieldPK, default=uuid4Unique entitlement identifier
tenantForeignKey(Tenant)CASCADEOrganization
productForeignKey(Product)RESTRICTProduct granted
orderForeignKey(Order)SET_NULL, nullableOriginating order
subscriptionForeignKey(Subscription)SET_NULL, nullableLinked subscription
statusCharField(20)'active', 'expired', 'suspended', 'cancelled'
starts_atDateTimeFielddefault=nowEntitlement start time
expires_atDateTimeFieldNullableEntitlement expiration
created_atDateTimeFieldauto_now_addCreation timestamp
updated_atDateTimeFieldauto_nowLast update timestamp

Inherits From: TenantModel

Indexes:

  • tenant where status='active'
  • product where status='active'
  • expires_at where expires_at IS NOT NULL AND status='active'

Unique Constraints:

  • (tenant, product) - One entitlement per product per org

Methods:

  • is_valid() - Check if status='active' AND not expired
  • suspend() - Set status='suspended'
  • cancel() - Set status='cancelled'

Subscriptions App

Location: backend/subscriptions/models.py Models: 3

Subscription

Purpose: Stripe subscription management.

FieldTypeConstraintsDescription
idUUIDFieldPK, default=uuid4Unique subscription identifier
tenantForeignKey(Tenant)CASCADEOrganization
stripe_subscription_idCharField(255)UniqueStripe Subscription ID
stripe_customer_idCharField(255)Stripe Customer ID
statusCharField(20)'active', 'past_due', 'canceled', 'incomplete', 'trialing', 'unpaid'
tierCharField(20)'free', 'pro', 'enterprise'
max_workstationsIntegerFielddefault=1Workstation quota
max_usersIntegerFielddefault=1User quota
current_period_startDateTimeFieldBilling period start
current_period_endDateTimeFieldBilling period end
canceled_atDateTimeFieldNullableCancellation timestamp
created_atDateTimeFieldauto_now_addCreation timestamp
updated_atDateTimeFieldauto_nowLast update timestamp

Inherits From: TenantModel

Indexes:

  • current_period_end where status='active'

Methods:

  • is_active() - Check if status='active'
  • get_remaining_workstation_quota(current_count) - Calculate remaining slots
  • get_remaining_user_quota(current_count) - Calculate remaining slots

Invoice

Purpose: Invoice records synced from Stripe.

FieldTypeConstraintsDescription
idUUIDFieldPK, default=uuid4Unique invoice identifier
tenantForeignKey(Tenant)CASCADEOrganization
subscriptionTenantForeignKey(Subscription)CASCADELinked subscription
stripe_invoice_idCharField(255)UniqueStripe Invoice ID
amount_centsIntegerFieldInvoice total in cents
currencyCharField(3)default='usd'Currency code
statusCharField(20)'draft', 'open', 'paid', 'void', 'uncollectible'
descriptionTextFieldInvoice description
invoice_pdf_urlURLFieldNullableStripe-hosted PDF
hosted_invoice_urlURLFieldNullableStripe-hosted invoice page
created_atDateTimeFieldauto_now_addCreation timestamp
paid_atDateTimeFieldNullablePayment timestamp

Inherits From: TenantModel

Indexes:

  • subscription
  • stripe_invoice_id

UsageMetric

Purpose: Resource usage tracking for quota enforcement.

FieldTypeConstraintsDescription
idUUIDFieldPK, default=uuid4Unique metric identifier
tenantForeignKey(Tenant)CASCADEOrganization
metric_typeCharField(50)'workstations', 'users', 'storage_gb', 'api_calls', 'context_messages', 'sync_operations'
current_valueIntegerFielddefault=0Current usage count
limit_valueIntegerFielddefault=0Quota limit
period_startDateTimeFieldTracking period start
period_endDateTimeFieldTracking period end
recorded_atDateTimeFieldauto_nowLast update timestamp
created_atDateTimeFieldauto_now_addCreation timestamp

Inherits From: TenantModel

Indexes:

  • (tenant, metric_type)
  • period_end

Unique Constraints:

  • (tenant, metric_type, period_start)

Methods:

  • get_usage_percentage() - Calculate usage/limit * 100
  • is_at_limit() - Check if current_value >= limit_value
  • increment(amount) - Increment current_value

Permissions App

Location: backend/permissions/models.py Models: 1

Permission

Purpose: RBAC permission definitions (role, resource, action tuples).

FieldTypeConstraintsDescription
idUUIDFieldPK, default=uuid4Unique permission identifier
roleCharField(20)'owner', 'admin', 'member', 'viewer'
resourceCharField(50)Resource type enum (organization, team, project, etc.)
actionCharField(20)Action enum (create, read, update, delete, etc.)
descriptionCharField(255)Human-readable description
created_atDateTimeFieldauto_now_addCreation timestamp

Indexes:

  • role
  • resource

Unique Constraints:

  • (role, resource, action) - One permission per combination

Class Methods:

  • check_permission(role, resource, action) - Check if permission exists
  • get_role_permissions(role) - Get all permissions for a role
  • seed_default_permissions() - Seed default RBAC rules

Resources Enum:

  • organization, team, project, workstation, repository, subscription, user, membership, context, entitlement, order, invoice

Actions Enum:

  • create, read, update, delete, list, manage, invite, remove, start, stop, connect, sync

Context App

Location: backend/context/models.py Models: 3

ContextMessage

Purpose: Claude Code session messages (sync across devices).

FieldTypeConstraintsDescription
idUUIDFieldPK, default=uuid4Unique message identifier
tenantTenantForeignKey(Tenant)CASCADEOrganization
user_idCharField(63)User identifier
session_idCharField(255)Session identifier
project_pathCharField(1000)Project directory path
message_typeCharField(50)'human', 'assistant', 'tool_use', 'tool_result', 'summary'
roleCharField(50)default='user'Message role
contentTextFieldMessage content
content_hashCharField(64)UniqueSHA256 hash (tenant_id:session_id:content)
message_timestampDateTimeFieldOriginal client timestamp
synced_atDateTimeFieldauto_now_addServer sync timestamp
sync_cursorBigIntegerFieldUnique, nullableAuto-increment cursor (PostgreSQL sequence)
client_versionCharField(50)CODITECT client version
hardware_idCharField(255)Device hardware ID

Inherits From: TenantModel

Indexes:

  • (tenant, sync_cursor)
  • (tenant, user_id, synced_at)
  • (session_id, synced_at)

Business Rules:

  • content_hash prevents duplicate messages
  • sync_cursor enables cursor-based polling
  • Immutable once created

SyncCursor

Purpose: Per-device sync cursor tracking.

FieldTypeConstraintsDescription
idUUIDFieldPK, default=uuid4Unique cursor identifier
tenantTenantForeignKey(Tenant)CASCADEOrganization
user_idCharField(63)User identifier
hardware_idCharField(255)Device hardware ID
last_cursorBigIntegerFielddefault=0Last synced cursor value
last_sync_atDateTimeFieldauto_nowLast sync timestamp
client_versionCharField(50)CODITECT client version
device_nameCharField(255)Device name

Inherits From: TenantModel

Unique Together:

  • (tenant, user_id, hardware_id)

SyncStats

Purpose: Aggregated sync statistics for monitoring.

FieldTypeConstraintsDescription
idUUIDFieldPK, default=uuid4Unique stats identifier
tenantTenantForeignKey(Tenant)CASCADEOrganization
user_idCharField(63)User identifier
total_messagesBigIntegerFielddefault=0Total messages synced
total_sessionsIntegerFielddefault=0Total unique sessions
total_devicesIntegerFielddefault=0Total devices registered
total_bytesBigIntegerFielddefault=0Total bytes synced
last_push_atDateTimeFieldNullableLast message push timestamp
last_pull_atDateTimeFieldNullableLast message pull timestamp
period_startDateFieldStats period start
period_endDateFieldStats period end

Inherits From: TenantModel

Unique Together:

  • (tenant, user_id, period_start)

Licenses App

Location: backend/licenses/models.py Models: 2

License

Purpose: License allocation for a tenant (floating concurrent licensing).

FieldTypeConstraintsDescription
idUUIDFieldPK, default=uuid4Unique license identifier
tenantForeignKey(Tenant)CASCADEOrganization
license_keyCharField(255)UniqueSigned license key (Cloud KMS)
license_tierCharField(20)'trial', 'starter', 'professional', 'enterprise'
seats_totalPositiveIntegerFielddefault=1Concurrent seat limit
issued_atDateTimeFieldauto_now_addIssuance timestamp
expires_atDateTimeFieldExpiration timestamp
is_activeBooleanFielddefault=TrueLicense active status
is_suspendedBooleanFielddefault=FalseLicense suspended (payment issues)
notesTextFieldInternal notes
created_atDateTimeFieldauto_now_addCreation timestamp
updated_atDateTimeFieldauto_nowLast update timestamp

Indexes:

  • (tenant, is_active)
  • license_key
  • expires_at

Methods:

  • is_expired() - Check if now > expires_at
  • is_valid() - Check active AND not suspended AND not expired
  • get_active_sessions() - Get sessions with status='active'
  • get_seats_used() - Count active sessions
  • has_available_seats() - Check seats_used < seats_total
  • get_seat_utilization() - Percentage of seats used
  • days_until_expiration() - Days remaining

Session

Purpose: Active CODITECT session consuming a license seat.

FieldTypeConstraintsDescription
idUUIDFieldPK, default=uuid4Unique session identifier
tenantForeignKey(Tenant)CASCADEOrganization
licenseForeignKey(License)CASCADELicense being used
userForeignKey(User)SET_NULL, nullableUser (optional)
hardware_idCharField(255)Unique hardware fingerprint
hostnameCharField(255)Machine hostname
statusCharField(20)'active', 'expired', 'released', 'zombie'
last_heartbeat_atDateTimeFieldauto_now_addLast heartbeat timestamp
heartbeat_countPositiveIntegerFielddefault=0Total heartbeats received
started_atDateTimeFieldauto_now_addSession start timestamp
ended_atDateTimeFieldNullableSession end timestamp
client_versionCharField(50)CODITECT client version
ip_addressGenericIPAddressFieldNullableClient IP address
created_atDateTimeFieldauto_now_addCreation timestamp
updated_atDateTimeFieldauto_nowLast update timestamp

Indexes:

  • (tenant, license, status)
  • hardware_id
  • last_heartbeat_at
  • (status, last_heartbeat_at)

Unique Constraints:

  • (license, hardware_id, status) where status='active' - One active session per hardware per license

Methods:

  • is_active() - Check status='active'
  • is_heartbeat_alive(timeout_seconds=360) - Check heartbeat within TTL
  • mark_zombie() - Set status='zombie'
  • release() - Set status='released', ended_at=now
  • record_heartbeat() - Update last_heartbeat_at, increment count
  • get_session_duration() - Duration in seconds
  • get_session_duration_formatted() - Human-readable (e.g., '2h 15m')

Workstations App

Location: backend/workstations/models.py Models: 6

WorkstationConfig

Purpose: Workstation configuration tier (GCP machine specs).

FieldTypeConstraintsDescription
idUUIDFieldPK, default=uuid4Unique config identifier
nameCharField(63)UniqueGCP config name
display_nameCharField(255)Human-readable name
tierCharField(20)'standard', 'power', 'ai'
machine_typeCharField(50)default='e2-standard-4'GCP machine type
vcpu_countIntegerFielddefault=4vCPU count
memory_gbIntegerFielddefault=16Memory in GB
boot_disk_gbIntegerFielddefault=50Boot disk size
persistent_disk_gbIntegerFielddefault=100Persistent disk size
has_gpuBooleanFielddefault=FalseGPU attached
gpu_typeCharField(50)NullableGPU type (e.g., 'nvidia-tesla-t4')
hourly_cost_usdDecimalField(10,4)default=0.08Estimated hourly cost
gcp_config_idCharField(255)GCP resource ID
gcp_cluster_idCharField(255)GCP cluster ID
regionCharField(50)default='us-central1'GCP region
is_activeBooleanFielddefault=TrueConfig active
created_atDateTimeFieldauto_now_addCreation timestamp
updated_atDateTimeFieldauto_nowLast update timestamp

Workstation

Purpose: Dedicated workstation instance (one per user per tenant).

FieldTypeConstraintsDescription
idUUIDFieldPK, default=uuid4Unique workstation identifier
workstation_idCharField(63)UniqueGCP workstation ID (ws-{tenant_id}-{user_id})
display_nameCharField(255)Human-readable name
tenant_idCharField(63)Tenant identifier
user_idCharField(63)User identifier
user_emailEmailFieldUser email
configForeignKey(WorkstationConfig)PROTECTConfiguration tier
statusCharField(20)'CREATING', 'STARTING', 'RUNNING', 'STOPPING', 'STOPPED', 'DELETING', 'DELETED', 'ERROR'
status_messageTextFieldStatus details
last_status_checkDateTimeFieldNullableLast status poll
gcp_resource_nameCharField(500)GCP resource name
gcp_host_uriURLField(500)Connection URI
total_running_secondsBigIntegerFielddefault=0Cumulative runtime
current_session_startDateTimeFieldNullableCurrent session start
last_activity_atDateTimeFieldNullableLast activity timestamp
created_atDateTimeFieldauto_now_addCreation timestamp
updated_atDateTimeFieldauto_nowLast update timestamp
deleted_atDateTimeFieldNullableSoft delete timestamp

Indexes:

  • (tenant_id, status)
  • (user_id, status)

Unique Together:

  • (tenant_id, user_id) - One workstation per user per tenant

Properties:

  • is_running - Check status='RUNNING'
  • estimated_monthly_cost - Based on config hourly_cost_usd * 80 hours/month

Methods:

  • start_session() - Set status='RUNNING', current_session_start=now
  • end_session() - Accumulate runtime, set status='STOPPED'

SharedWorkstation

Purpose: Multi-user shared workstation (cost optimization).

FieldTypeConstraintsDescription
idUUIDFieldPK, default=uuid4Unique workstation identifier
workstation_idCharField(63)UniqueGCP workstation ID
display_nameCharField(255)Human-readable name
tenant_idCharField(63)Tenant identifier
sharing_modeCharField(20)'dedicated', 'shared', 'pool'
max_concurrent_usersIntegerFielddefault=5Max simultaneous users
current_user_countIntegerFielddefault=0Currently connected users
configForeignKey(WorkstationConfig)PROTECTConfiguration tier
statusCharField(20)'CREATING', 'RUNNING', 'STOPPED', 'ERROR'
gcp_resource_nameCharField(500)GCP resource name
gcp_host_uriURLField(500)Connection URI
created_atDateTimeFieldauto_now_addCreation timestamp
updated_atDateTimeFieldauto_nowLast update timestamp

WorkstationUserAccess

Purpose: Maps users to workstations with access control.

FieldTypeConstraintsDescription
idUUIDFieldPK, default=uuid4Unique access identifier
user_idCharField(63)User identifier
user_emailEmailFieldUser email
tenant_idCharField(63)Tenant identifier
dedicated_workstationForeignKey(Workstation)SET_NULL, nullableDedicated workstation
shared_workstationsManyToManyField(SharedWorkstation)Shared workstations
access_levelCharField(20)'user', 'developer', 'admin'
roleCharField(100)User role (e.g., 'frontend', 'backend')
linux_usernameCharField(32)Linux username in workstation
linux_uidIntegerFieldNullableLinux UID for isolation
ssh_public_keyTextFieldSSH public key
home_directoryCharField(255)Persistent home directory path
home_storage_gbIntegerFielddefault=10Home directory quota
default_workstation_idCharField(63)Default workstation to launch
is_activeBooleanFielddefault=TrueAccess active
last_login_atDateTimeFieldNullableLast login timestamp
last_workstation_idCharField(63)Last workstation used
created_atDateTimeFieldauto_now_addCreation timestamp
updated_atDateTimeFieldauto_nowLast update timestamp

Indexes:

  • user_id
  • (tenant_id, is_active)

Unique Together:

  • (tenant_id, user_id)

Methods:

  • get_available_workstations() - List all accessible workstations

WorkstationSession

Purpose: Tracks individual user sessions on workstations.

FieldTypeConstraintsDescription
idUUIDFieldPK, default=uuid4Unique session identifier
session_idCharField(63)UniqueSession identifier
user_accessForeignKey(WorkstationUserAccess)CASCADEUser access record
workstationForeignKey(Workstation)CASCADE, nullableDedicated workstation
shared_workstationForeignKey(SharedWorkstation)CASCADE, nullableShared workstation
stateCharField(20)'connecting', 'active', 'idle', 'disconnected'
started_atDateTimeFieldauto_now_addSession start
last_activity_atDateTimeFieldauto_nowLast activity
ended_atDateTimeFieldNullableSession end
client_ipGenericIPAddressFieldNullableClient IP
user_agentCharField(500)User agent

Indexes:

  • (state, started_at)

WorkstationUsageRecord

Purpose: Detailed usage records for billing.

FieldTypeConstraintsDescription
idUUIDFieldPK, default=uuid4Unique record identifier
workstationForeignKey(Workstation)CASCADEWorkstation
started_atDateTimeFieldSession start
stopped_atDateTimeFieldNullableSession stop
duration_secondsIntegerFielddefault=0Session duration
config_hourly_rateDecimalField(10,4)Hourly rate at session time
calculated_cost_usdDecimalField(10,4)default=0Calculated cost
tenant_idCharField(63)Tenant for billing
created_atDateTimeFieldauto_now_addRecord creation

Indexes:

  • (tenant_id, started_at)
  • (workstation, started_at)

Methods:

  • calculate_cost() - Calculate cost based on duration and rate

Repositories App

Location: backend/repositories/models.py Models: 7

GiteaOrganization

Purpose: Gitea organization for a tenant (multi-tenant Git hosting).

FieldTypeConstraintsDescription
tenant_idCharField(63)UniqueTenant identifier
gitea_org_nameCharField(63)UniqueGitea org name (org-{tenant_id})
gitea_org_idIntegerFieldNullableGitea internal org ID
display_nameCharField(255)Human-readable name
descriptionTextFieldOrganization description
visibilityCharField(20)'private', 'limited', 'public'
repo_limitIntegerFielddefault=-1Max repositories (-1=unlimited)
member_limitIntegerFielddefault=-1Max members (-1=unlimited)
created_atDateTimeFieldauto_now_addCreation timestamp
updated_atDateTimeFieldauto_nowLast update timestamp
synced_atDateTimeFieldNullableLast Gitea API sync

Repository

Purpose: Git repository within a tenant organization.

FieldTypeConstraintsDescription
organizationForeignKey(GiteaOrganization)CASCADEParent organization
nameCharField(255)Repository name
gitea_repo_idIntegerFieldNullableGitea internal repo ID
full_nameCharField(511)Full path (org/repo)
descriptionTextFieldRepository description
default_branchCharField(255)default='main'Default branch
visibilityCharField(20)'private', 'internal', 'public'
has_issuesBooleanFielddefault=FalseIssues enabled
has_wikiBooleanFielddefault=FalseWiki enabled
has_pull_requestsBooleanFielddefault=TruePRs enabled
size_kbBigIntegerFielddefault=0Repository size
stars_countIntegerFielddefault=0Stars count
forks_countIntegerFielddefault=0Forks count
open_issues_countIntegerFielddefault=0Open issues
open_pr_countIntegerFielddefault=0Open PRs
clone_url_httpsURLFieldHTTPS clone URL
clone_url_sshCharField(511)SSH clone URL
created_atDateTimeFieldauto_now_addCreation timestamp
updated_atDateTimeFieldauto_nowLast update timestamp
synced_atDateTimeFieldNullableLast Gitea API sync
last_activity_atDateTimeFieldNullableLast push/activity
is_archivedBooleanFielddefault=FalseRepository archived
archived_atDateTimeFieldNullableArchive timestamp

Indexes:

  • (organization, name)
  • full_name

Unique Together:

  • (organization, name)

RepositoryAccessToken

Purpose: Short-lived access tokens for repository operations.

FieldTypeConstraintsDescription
token_idCharField(63)UniqueToken identifier
user_idCharField(63)User identifier
repositoryForeignKey(Repository)CASCADE, nullableSpecific repository (or org-wide)
organizationForeignKey(GiteaOrganization)CASCADEParent organization
gitea_token_idIntegerFieldNullableGitea internal token ID
nameCharField(255)Token name in Gitea
scopeCharField(20)'read', 'write', 'admin'
token_hashCharField(255)SHA-256 hash of token
token_prefixCharField(10)First 8 chars (for identification)
expires_atDateTimeFieldToken expiration
is_revokedBooleanFielddefault=FalseToken revoked
revoked_atDateTimeFieldNullableRevocation timestamp
revoked_reasonCharField(255)Revocation reason
created_atDateTimeFieldauto_now_addCreation timestamp
last_used_atDateTimeFieldNullableLast use timestamp
use_countIntegerFielddefault=0Usage count

Indexes:

  • user_id
  • expires_at
  • token_prefix

RepositoryMirror

Purpose: GitHub/GitLab/Bitbucket mirror configuration for bidirectional sync.

FieldTypeConstraintsDescription
repositoryOneToOneField(Repository)CASCADELocal Gitea repository
mirror_typeCharField(20)'github', 'gitlab', 'bitbucket'
remote_urlURLFieldRemote repository URL
remote_ownerCharField(255)Remote owner/org
remote_nameCharField(255)Remote repo name
auth_token_secretCharField(255)Secret Manager path for token
directionCharField(20)'pull', 'push', 'bidirectional'
sync_interval_secondsIntegerFielddefault=300Sync interval (5 min)
is_activeBooleanFielddefault=TrueMirroring active
last_sync_atDateTimeFieldNullableLast sync timestamp
last_sync_statusCharField(20)'pending', 'syncing', 'success', 'failed'
last_sync_errorTextFieldError message
sync_countIntegerFielddefault=0Total sync attempts
failure_countIntegerFielddefault=0Failed sync attempts
created_atDateTimeFieldauto_now_addCreation timestamp
updated_atDateTimeFieldauto_nowLast update timestamp

Methods:

  • sync() - Trigger mirror sync (Celery task)

TenantGitQuota

Purpose: Git storage and repository quota tracking.

FieldTypeConstraintsDescription
organizationOneToOneField(GiteaOrganization)CASCADEGitea organization
tierCharField(20)'free', 'starter', 'professional', 'enterprise'
repo_limitIntegerFielddefault=3Max repositories
storage_limit_gbIntegerFielddefault=1Storage limit in GB
github_mirror_enabledBooleanFielddefault=FalseMirroring enabled
repo_countIntegerFielddefault=0Current repository count
storage_used_mbBigIntegerFielddefault=0Storage usage in MB
mirror_countIntegerFielddefault=0Active mirrors
monthly_cost_usdDecimalField(10,2)default=0Base monthly cost
overage_cost_usdDecimalField(10,2)default=0Overage charges
created_atDateTimeFieldauto_now_addCreation timestamp
updated_atDateTimeFieldauto_nowLast update timestamp
usage_synced_atDateTimeFieldNullableLast usage calculation

Properties:

  • storage_used_gb - storage_used_mb / 1024
  • storage_percent - (storage_used_gb / storage_limit_gb) * 100
  • repo_percent - (repo_count / repo_limit) * 100

Methods:

  • is_over_quota() - Check if over any quota
  • calculate_cost() - Calculate monthly cost + overages

RepositoryWebhook

Purpose: Webhooks for CI/CD and external integrations.

FieldTypeConstraintsDescription
repositoryForeignKey(Repository)CASCADERepository
gitea_hook_idIntegerFieldNullableGitea internal hook ID
nameCharField(255)Webhook name
urlURLFieldWebhook target URL
secret_hashCharField(255)SHA-256 hash of secret
content_typeCharField(20)'json', 'form'
eventsJSONFielddefault=listList of events to trigger
is_activeBooleanFielddefault=TrueWebhook active
created_atDateTimeFieldauto_now_addCreation timestamp
updated_atDateTimeFieldauto_nowLast update timestamp
last_delivery_atDateTimeFieldNullableLast delivery timestamp
last_delivery_statusIntegerFieldNullableLast HTTP status
delivery_countIntegerFielddefault=0Total deliveries
failure_countIntegerFielddefault=0Failed deliveries

Core App

Location: backend/core/models.py Models: 0

Purpose: Base application with no models (shared utilities, base views, etc.)


Multi-Tenant Architecture

django-multitenant Integration

Pattern: TenantModel mixin for automatic query filtering by tenant.

Apps Using TenantModel:

  • tenants (Tenant - root model)
  • organizations (Membership, Team, TeamMembership, Project)
  • commerce (Order, Entitlement)
  • subscriptions (Subscription, Invoice, UsageMetric)
  • context (ContextMessage, SyncCursor, SyncStats)

Apps Using ForeignKey to Tenant:

  • users (User - special case: no TenantModel due to AbstractUser MRO conflicts)
  • licenses (License, Session)

Apps Using tenant_id CharField:

  • workstations (all models)
  • repositories (via GiteaOrganization.tenant_id)

Global Models (No Tenant):

  • commerce.Product (global catalog)
  • permissions.Permission (global RBAC rules)
  • workstations.WorkstationConfig (global config tiers)

Usage Pattern

from django_multitenant.utils import set_current_tenant

# Set tenant context (middleware does this automatically)
set_current_tenant(tenant)

# All subsequent TenantModel queries auto-filtered by tenant
projects = Project.objects.all() # Automatically filtered to current tenant

Database Tables Summary

TableModelAppTenant-Scoped
usersUserusersVia FK
tenantsTenanttenantsRoot
membershipsMembershiporganizationsTenantModel
teamsTeamorganizationsTenantModel
team_membershipsTeamMembershiporganizationsTenantModel
projectsProjectorganizationsTenantModel
productsProductcommerceGlobal
cartsCartcommerceGlobal (user or session)
ordersOrdercommerceTenantModel
entitlementsEntitlementcommerceTenantModel
subscriptionsSubscriptionsubscriptionsTenantModel
invoicesInvoicesubscriptionsTenantModel
usage_metricsUsageMetricsubscriptionsTenantModel
permissionsPermissionpermissionsGlobal
context_messagesContextMessagecontextTenantModel
context_sync_cursorsSyncCursorcontextTenantModel
context_sync_statsSyncStatscontextTenantModel
licensesLicenselicensesVia FK
sessionsSessionlicensesVia FK
workstation_configsWorkstationConfigworkstationsGlobal
workstationsWorkstationworkstationsVia tenant_id
shared_workstationsSharedWorkstationworkstationsVia tenant_id
workstation_user_accessWorkstationUserAccessworkstationsVia tenant_id
workstation_sessionsWorkstationSessionworkstationsVia user_access FK
workstation_usage_recordsWorkstationUsageRecordworkstationsVia tenant_id
gitea_organizationsGiteaOrganizationrepositoriesVia tenant_id
repositoriesRepositoryrepositoriesVia organization FK
repository_access_tokensRepositoryAccessTokenrepositoriesVia organization FK
repository_mirrorsRepositoryMirrorrepositoriesVia repository FK
tenant_git_quotasTenantGitQuotarepositoriesVia organization FK
repository_webhooksRepositoryWebhookrepositoriesVia repository FK

Total Tables: 31


Field Type Summary

Field TypeCountCommon Usage
UUIDField40Primary keys (all models)
CharField200+Names, slugs, identifiers, statuses
TextField30+Descriptions, notes, content
ForeignKey60+Relationships
DateTimeField120+Timestamps (created_at, updated_at, etc.)
BooleanField40+Flags (is_active, is_suspended, etc.)
IntegerField50+Counts, quotas, limits
BigIntegerField10+Large counts (bytes, cursors)
JSONField15+Flexible data (metadata, line_items, features)
EmailField10+User emails
URLField15+Clone URLs, webhooks, hosted invoice URLs
DecimalField10+Money amounts (prices, costs)
GenericIPAddressField3IP addresses

Index Strategy

Common Index Patterns:

  1. Foreign Keys - All FKs indexed by default
  2. Tenant Scoping - (tenant, ...) composite indexes for multi-tenant queries
  3. Status Filtering - Partial indexes with WHERE status IN (...) conditions
  4. Time-Range Queries - Indexes on timestamp fields (created_at, expires_at, etc.)
  5. Unique Business Rules - Unique constraints for data integrity

Examples:

  • (tenant, status) - Active records per tenant
  • expires_at WHERE status='active' - Find expiring records
  • (tenant, user, role) WHERE revoked_at IS NULL - Active memberships
  • stripe_payment_intent_id - Fast Stripe webhook lookups

Validation Summary

Multi-Tenant Compliance: All tenant-scoped models use TenantModel or FK to Tenant ✅ UUID Primary Keys: All models use UUIDs for distributed systems ✅ Timestamps: All models have created_at, updated_at ✅ Soft Deletes: Projects, workstations use deleted_at ✅ Indexes: Strategic indexes for query performance ✅ Constraints: Unique constraints enforce business rules ✅ Choices: Enums for controlled vocabularies (status, tier, role, etc.)


End of data-model-inventory.md