mirror of
https://github.com/saltbo/zpan.git
synced 2026-09-21 04:59:47 +08:00
310 lines
12 KiB
TypeScript
310 lines
12 KiB
TypeScript
import { sql } from 'drizzle-orm'
|
|
import { index, integer, sqliteTable, text, uniqueIndex } from 'drizzle-orm/sqlite-core'
|
|
import { organization } from './auth-schema'
|
|
|
|
export const matters = sqliteTable('matters', {
|
|
id: text('id').primaryKey(),
|
|
orgId: text('org_id').notNull(),
|
|
alias: text('alias').notNull().unique(),
|
|
name: text('name').notNull(),
|
|
type: text('type').notNull(),
|
|
size: integer('size').default(0),
|
|
dirtype: integer('dirtype').default(0),
|
|
parent: text('parent').notNull().default(''),
|
|
object: text('object').notNull().default(''),
|
|
storageId: text('storage_id').notNull(),
|
|
status: text('status').notNull().default('draft'), // draft, active, trashed
|
|
trashedAt: integer('trashed_at'),
|
|
createdAt: integer('created_at', { mode: 'timestamp' }).notNull(),
|
|
updatedAt: integer('updated_at', { mode: 'timestamp' }).notNull(),
|
|
})
|
|
|
|
export const storages = sqliteTable('storages', {
|
|
id: text('id').primaryKey(),
|
|
title: text('title').notNull(),
|
|
mode: text('mode').notNull(),
|
|
bucket: text('bucket').notNull(),
|
|
endpoint: text('endpoint').notNull(),
|
|
region: text('region').notNull().default('auto'),
|
|
accessKey: text('access_key').notNull(),
|
|
secretKey: text('secret_key').notNull(),
|
|
filePath: text('file_path').notNull().default(''),
|
|
customHost: text('custom_host').default(''),
|
|
capacity: integer('capacity').notNull().default(0),
|
|
used: integer('used').notNull().default(0),
|
|
status: text('status').notNull().default('active'),
|
|
createdAt: integer('created_at', { mode: 'timestamp' }).notNull(),
|
|
updatedAt: integer('updated_at', { mode: 'timestamp' }).notNull(),
|
|
})
|
|
|
|
export const orgQuotas = sqliteTable('org_quotas', {
|
|
id: text('id').primaryKey(),
|
|
orgId: text('org_id').notNull(),
|
|
quota: integer('quota').notNull().default(0),
|
|
used: integer('used').notNull().default(0),
|
|
trafficQuota: integer('traffic_quota').notNull().default(0),
|
|
trafficUsed: integer('traffic_used').notNull().default(0),
|
|
trafficPeriod: text('traffic_period').notNull().default('1970-01'),
|
|
})
|
|
|
|
export const quotaStoreSettings = sqliteTable('quota_store_settings', {
|
|
id: text('id').primaryKey(),
|
|
enabled: integer('enabled', { mode: 'boolean' }).notNull().default(false),
|
|
createdAt: integer('created_at', { mode: 'timestamp_ms' }).notNull(),
|
|
updatedAt: integer('updated_at', { mode: 'timestamp_ms' }).notNull(),
|
|
})
|
|
|
|
export const quotaGrants = sqliteTable(
|
|
'quota_grants',
|
|
{
|
|
id: text('id').primaryKey(),
|
|
orgId: text('org_id').notNull(),
|
|
source: text('source').notNull(),
|
|
externalEventId: text('external_event_id'),
|
|
cloudOrderId: text('cloud_order_id'),
|
|
cloudRedemptionId: text('cloud_redemption_id'),
|
|
code: text('code'),
|
|
bytes: integer('bytes').notNull(),
|
|
packageSnapshot: text('package_snapshot'),
|
|
grantedBy: text('granted_by'),
|
|
terminalUserId: text('terminal_user_id'),
|
|
terminalUserEmail: text('terminal_user_email'),
|
|
active: integer('active', { mode: 'boolean' }).notNull().default(true),
|
|
createdAt: integer('created_at', { mode: 'timestamp_ms' }).notNull(),
|
|
},
|
|
(t) => [
|
|
index('quota_grants_org_created_idx').on(t.orgId, t.createdAt),
|
|
uniqueIndex('quota_grants_external_event_uniq').on(t.externalEventId),
|
|
uniqueIndex('quota_grants_cloud_order_uniq').on(t.cloudOrderId),
|
|
uniqueIndex('quota_grants_cloud_redemption_uniq').on(t.cloudRedemptionId),
|
|
uniqueIndex('quota_grants_code_uniq').on(t.code),
|
|
],
|
|
)
|
|
|
|
export const quotaDeliveryEvents = sqliteTable(
|
|
'quota_delivery_events',
|
|
{
|
|
id: text('id').primaryKey(),
|
|
eventId: text('event_id').notNull(),
|
|
cloudOrderId: text('cloud_order_id'),
|
|
cloudRedemptionId: text('cloud_redemption_id'),
|
|
code: text('code'),
|
|
payloadHash: text('payload_hash').notNull(),
|
|
rawPayload: text('raw_payload').notNull(),
|
|
status: text('status').notNull(),
|
|
error: text('error'),
|
|
createdAt: integer('created_at', { mode: 'timestamp_ms' }).notNull(),
|
|
processedAt: integer('processed_at', { mode: 'timestamp_ms' }),
|
|
},
|
|
(t) => [
|
|
uniqueIndex('quota_delivery_events_event_uniq').on(t.eventId),
|
|
uniqueIndex('quota_delivery_events_order_uniq').on(t.cloudOrderId),
|
|
uniqueIndex('quota_delivery_events_redemption_uniq').on(t.cloudRedemptionId),
|
|
uniqueIndex('quota_delivery_events_code_uniq').on(t.code),
|
|
],
|
|
)
|
|
|
|
export const inviteCodes = sqliteTable('invite_codes', {
|
|
id: text('id').primaryKey(),
|
|
code: text('code').notNull().unique(),
|
|
createdBy: text('created_by').notNull(),
|
|
usedBy: text('used_by'),
|
|
usedAt: integer('used_at', { mode: 'timestamp' }),
|
|
expiresAt: integer('expires_at', { mode: 'timestamp' }),
|
|
createdAt: integer('created_at', { mode: 'timestamp' }).notNull(),
|
|
})
|
|
|
|
export const siteInvitations = sqliteTable(
|
|
'site_invitations',
|
|
{
|
|
id: text('id').primaryKey(),
|
|
email: text('email').notNull(),
|
|
token: text('token').notNull().unique(),
|
|
invitedBy: text('invited_by').notNull(),
|
|
acceptedBy: text('accepted_by'),
|
|
acceptedAt: integer('accepted_at', { mode: 'timestamp' }),
|
|
revokedBy: text('revoked_by'),
|
|
revokedAt: integer('revoked_at', { mode: 'timestamp' }),
|
|
expiresAt: integer('expires_at', { mode: 'timestamp' }).notNull(),
|
|
createdAt: integer('created_at', { mode: 'timestamp' }).notNull(),
|
|
updatedAt: integer('updated_at', { mode: 'timestamp' }).notNull(),
|
|
},
|
|
(t) => [
|
|
index('site_invitations_email_idx').on(t.email),
|
|
index('site_invitations_created_idx').on(t.createdAt),
|
|
index('site_invitations_expires_idx').on(t.expiresAt),
|
|
],
|
|
)
|
|
|
|
export const systemOptions = sqliteTable('system_options', {
|
|
key: text('key').primaryKey(),
|
|
value: text('value').notNull().default(''),
|
|
public: integer('public', { mode: 'boolean' }).default(false),
|
|
})
|
|
|
|
export const licenseBindings = sqliteTable(
|
|
'license_bindings',
|
|
{
|
|
id: text('id').primaryKey(),
|
|
cloudBindingId: text('cloud_binding_id').notNull(),
|
|
instanceId: text('instance_id').notNull(),
|
|
cloudAccountId: text('cloud_account_id').notNull(),
|
|
cloudAccountEmail: text('cloud_account_email'),
|
|
status: text('status').notNull(),
|
|
refreshToken: text('refresh_token'),
|
|
cachedCertificate: text('cached_certificate'),
|
|
cachedCertificateExpiresAt: integer('cached_certificate_expires_at'),
|
|
boundAt: integer('bound_at').notNull(),
|
|
disconnectedAt: integer('disconnected_at'),
|
|
lastRefreshAt: integer('last_refresh_at'),
|
|
lastRefreshError: text('last_refresh_error'),
|
|
createdAt: integer('created_at').notNull(),
|
|
updatedAt: integer('updated_at').notNull(),
|
|
},
|
|
(t) => [
|
|
uniqueIndex('license_bindings_active_uniq').on(t.status).where(sql`status = 'active'`),
|
|
index('license_bindings_cloud_binding_idx').on(t.cloudBindingId),
|
|
index('license_bindings_instance_idx').on(t.instanceId),
|
|
],
|
|
)
|
|
|
|
export const teamInviteLinks = sqliteTable('team_invite_links', {
|
|
id: text('id').primaryKey(),
|
|
token: text('token').notNull().unique(),
|
|
organizationId: text('organization_id').notNull(),
|
|
role: text('role').notNull().default('member'),
|
|
inviterId: text('inviter_id').notNull(),
|
|
expiresAt: integer('expires_at', { mode: 'timestamp_ms' }),
|
|
createdAt: integer('created_at', { mode: 'timestamp_ms' }).notNull(),
|
|
})
|
|
|
|
export const notifications = sqliteTable(
|
|
'notifications',
|
|
{
|
|
id: text('id').primaryKey(),
|
|
userId: text('user_id').notNull(),
|
|
type: text('type').notNull(), // e.g. 'share_received'
|
|
title: text('title').notNull(),
|
|
body: text('body').notNull().default(''),
|
|
refType: text('ref_type'), // e.g. 'share'
|
|
refId: text('ref_id'),
|
|
metadata: text('metadata'), // JSON string for extra context
|
|
readAt: integer('read_at', { mode: 'timestamp' }),
|
|
createdAt: integer('created_at', { mode: 'timestamp' }).notNull(),
|
|
},
|
|
(t) => [
|
|
index('notifications_user_created_idx').on(t.userId, t.createdAt),
|
|
index('notifications_user_read_idx').on(t.userId, t.readAt),
|
|
],
|
|
)
|
|
|
|
export const announcements = sqliteTable(
|
|
'announcements',
|
|
{
|
|
id: text('id').primaryKey(),
|
|
title: text('title').notNull(),
|
|
body: text('body').notNull().default(''),
|
|
status: text('status').notNull().default('draft'),
|
|
priority: integer('priority').notNull().default(0),
|
|
publishedAt: integer('published_at', { mode: 'timestamp' }),
|
|
expiresAt: integer('expires_at', { mode: 'timestamp' }),
|
|
createdBy: text('created_by').notNull(),
|
|
createdAt: integer('created_at', { mode: 'timestamp' }).notNull(),
|
|
updatedAt: integer('updated_at', { mode: 'timestamp' }).notNull(),
|
|
},
|
|
(t) => [
|
|
index('announcements_status_priority_idx').on(t.status, t.priority),
|
|
index('announcements_published_idx').on(t.publishedAt),
|
|
],
|
|
)
|
|
|
|
export const activityEvents = sqliteTable('activity_events', {
|
|
id: text('id').primaryKey(),
|
|
orgId: text('org_id').notNull(),
|
|
userId: text('user_id').notNull(),
|
|
action: text('action').notNull(), // 'upload', 'create', 'delete', 'rename', 'move', 'restore'
|
|
targetType: text('target_type').notNull(), // 'file', 'folder'
|
|
targetId: text('target_id'),
|
|
targetName: text('target_name').notNull(),
|
|
metadata: text('metadata'), // JSON
|
|
createdAt: integer('created_at', { mode: 'timestamp' }).notNull(),
|
|
})
|
|
|
|
export const shares = sqliteTable(
|
|
'shares',
|
|
{
|
|
id: text('id').primaryKey(),
|
|
token: text('token').notNull().unique(),
|
|
kind: text('kind').notNull(), // 'landing' | 'direct'
|
|
matterId: text('matter_id').notNull(),
|
|
orgId: text('org_id').notNull(),
|
|
creatorId: text('creator_id').notNull(),
|
|
passwordHash: text('password_hash'),
|
|
expiresAt: integer('expires_at', { mode: 'timestamp' }),
|
|
downloadLimit: integer('download_limit'),
|
|
views: integer('views').notNull().default(0),
|
|
downloads: integer('downloads').notNull().default(0),
|
|
status: text('status').notNull().default('active'), // 'active' | 'revoked'
|
|
createdAt: integer('created_at', { mode: 'timestamp' }).notNull(),
|
|
},
|
|
(t) => [index('shares_creator_status_created_idx').on(t.creatorId, t.status, t.createdAt)],
|
|
)
|
|
|
|
export const shareRecipients = sqliteTable(
|
|
'share_recipients',
|
|
{
|
|
id: text('id').primaryKey(),
|
|
shareId: text('share_id').notNull(),
|
|
recipientUserId: text('recipient_user_id'),
|
|
recipientEmail: text('recipient_email'),
|
|
createdAt: integer('created_at', { mode: 'timestamp' }).notNull(),
|
|
},
|
|
(t) => [
|
|
index('share_recipients_share_id_idx').on(t.shareId),
|
|
index('share_recipients_user_id_idx').on(t.recipientUserId),
|
|
],
|
|
)
|
|
|
|
// image_hosting_configs — per-org singleton; row exists => feature enabled
|
|
export const imageHostingConfigs = sqliteTable('image_hosting_configs', {
|
|
orgId: text('org_id')
|
|
.primaryKey()
|
|
.references(() => organization.id, { onDelete: 'cascade' }),
|
|
customDomain: text('custom_domain').unique(),
|
|
cfHostnameId: text('cf_hostname_id'),
|
|
domainVerifiedAt: integer('domain_verified_at', { mode: 'timestamp_ms' }),
|
|
refererAllowlist: text('referer_allowlist'), // JSON array of strings; null/empty => allow all
|
|
createdAt: integer('created_at', { mode: 'timestamp_ms' }).notNull(),
|
|
updatedAt: integer('updated_at', { mode: 'timestamp_ms' }).notNull(),
|
|
})
|
|
|
|
// image_hostings — one row per hosted image
|
|
export const imageHostings = sqliteTable(
|
|
'image_hostings',
|
|
{
|
|
id: text('id').primaryKey(), // nanoid(12)
|
|
orgId: text('org_id')
|
|
.notNull()
|
|
.references(() => organization.id, { onDelete: 'cascade' }),
|
|
token: text('token').notNull().unique(), // "ih_" + nanoid(10)
|
|
path: text('path').notNull(), // virtual path e.g. "blog/2026/04/shot.png"
|
|
storageId: text('storage_id')
|
|
.notNull()
|
|
.references(() => storages.id),
|
|
storageKey: text('storage_key').notNull(), // "ih/<orgId>/<id>.<ext>"
|
|
size: integer('size').notNull(),
|
|
mime: text('mime').notNull(),
|
|
width: integer('width'),
|
|
height: integer('height'),
|
|
status: text('status').notNull().default('draft'), // 'draft' | 'active'
|
|
accessCount: integer('access_count').notNull().default(0),
|
|
lastAccessedAt: integer('last_accessed_at', { mode: 'timestamp_ms' }),
|
|
createdAt: integer('created_at', { mode: 'timestamp_ms' }).notNull(),
|
|
},
|
|
(t) => [
|
|
uniqueIndex('image_hostings_org_path_uniq').on(t.orgId, t.path),
|
|
index('image_hostings_org_created_idx').on(t.orgId, t.createdAt),
|
|
index('image_hostings_token_idx').on(t.token),
|
|
],
|
|
)
|