feat: add licenseBinding table, migration 0013_licensing, and shared licensing types (#339)

- Add `licenseBinding` singleton table (id=1) to server/db/schema.ts
- Correct `0012_image-hosting` journal idx from 11→12 (matches its prefix;
  was left wrong by previous rename-only fix in ef1fab8), then auto-generate
  migration 0013_licensing.sql via drizzle-kit
- New 0013_snapshot.json created; 0012_snapshot.json unchanged
- Add ProFeatures enum to shared/constants.ts
- Add shared/types/licensing.ts with LicenseEntitlement, ProFeature, BindingState
- Export new types from shared/types/index.ts

Agent-Profile: https://agent-kanban.dev/agents/a6bb038c4226a87f

Co-authored-by: Bob <aibob@mails.agent-kanban.dev>
This commit is contained in:
Jasper Van
2026-04-23 22:39:55 -04:00
committed by GitHub
parent 5de2100435
commit 85554512c4
7 changed files with 2088 additions and 1 deletions
+12
View File
@@ -0,0 +1,12 @@
CREATE TABLE `license_binding` (
`id` integer PRIMARY KEY NOT NULL,
`instance_id` text NOT NULL,
`cloud_account_id` text,
`cloud_account_email` text,
`refresh_token` text NOT NULL,
`cached_cert` text,
`cached_expires_at` integer,
`last_refresh_at` integer,
`last_refresh_error` text,
`bound_at` integer
);
File diff suppressed because it is too large Load Diff
+8 -1
View File
@@ -80,11 +80,18 @@
"breakpoints": true
},
{
"idx": 11,
"idx": 12,
"version": "6",
"when": 1776749333797,
"tag": "0012_image-hosting",
"breakpoints": true
},
{
"idx": 13,
"version": "6",
"when": 1776997633827,
"tag": "0013_licensing",
"breakpoints": true
}
]
}
+14
View File
@@ -136,6 +136,20 @@ export const shareRecipients = sqliteTable(
],
)
// licenseBinding — singleton (id=1 always); before binding no row exists
export const licenseBinding = sqliteTable('license_binding', {
id: integer('id').primaryKey(),
instanceId: text('instance_id').notNull(),
cloudAccountId: text('cloud_account_id'),
cloudAccountEmail: text('cloud_account_email'),
refreshToken: text('refresh_token').notNull(),
cachedCert: text('cached_cert'),
cachedExpiresAt: integer('cached_expires_at'),
lastRefreshAt: integer('last_refresh_at'),
lastRefreshError: text('last_refresh_error'),
boundAt: integer('bound_at'),
})
// image_hosting_configs — per-org singleton; row exists => feature enabled
export const imageHostingConfigs = sqliteTable('image_hosting_configs', {
orgId: text('org_id')
+9
View File
@@ -42,3 +42,12 @@ export const SignupMode = {
} as const
export type SignupMode = (typeof SignupMode)[keyof typeof SignupMode]
export const ProFeatures = {
WHITE_LABEL: 'white_label',
OPEN_REGISTRATION: 'open_registration',
TEAMS_UNLIMITED: 'teams_unlimited',
TEAM_QUOTAS: 'team_quotas',
} as const
export type ProFeatures = (typeof ProFeatures)[keyof typeof ProFeatures]
+2
View File
@@ -239,3 +239,5 @@ export interface ImageHosting {
lastAccessedAt: string | null
createdAt: string
}
export type { BindingState, LicenseEntitlement, ProFeature } from './licensing'
+20
View File
@@ -0,0 +1,20 @@
export type ProFeature = 'white_label' | 'open_registration' | 'teams_unlimited' | 'team_quotas'
export interface LicenseEntitlement {
account_id: string
instance_id: string
plan: 'community' | 'pro'
features: ProFeature[]
issued_at: number
expires_at: number
}
export interface BindingState {
bound: boolean
account_email?: string
plan?: 'community' | 'pro'
features?: ProFeature[]
expires_at?: number
last_refresh_at?: number
last_refresh_error?: string
}