mirror of
https://github.com/docmost/docmost.git
synced 2025-11-17 19:11:10 +10:00
feat: cloud and ee (#805)
* stripe init git submodules for enterprise modules * * Cloud billing UI - WIP * Proxy websockets in dev mode * Separate workspace login and creation for cloud * Other fixes * feat: billing (cloud) * * add domain service * prepare links from workspace hostname * WIP * Add exchange token generation * Validate JWT token type during verification * domain service * add SkipTransform decorator * * updates (server) * add new packages * new sso migration file * WIP * Fix hostname generation * WIP * WIP * Reduce input error font-size * set max password length * jwt package * license page - WIP * * License management UI * Move license key store to db * add reflector * SSO enforcement * * Add default plan * Add usePlan hook * * Fix auth container margin in mobile * Redirect login and home to select page in cloud * update .gitignore * Default to yearly * * Trial messaging * Handle ended trials * Don't set to readonly on collab disconnect (Cloud) * Refine trial (UI) * Fix bug caused by using jotai optics atom in AppHeader component * configurable database maximum pool * Close SSO form on save * wip * sync * Only show sign-in in cloud * exclude base api part from workspaceId check * close db connection beforeApplicationShutdown * Add health/live endpoint * clear cookie on hostname change * reset currentUser atom * Change text * return 401 if workspace does not match * feat: show user workspace list in cloud login page * sync * Add home path * Prefetch to speed up queries * * Add robots.txt * Disallow login and forgot password routes * wildcard user-agent * Fix space query cache * fix * fix * use space uuid for recent pages * prefetch billing plans * enhance license page * sync
This commit is contained in:
@ -3,7 +3,7 @@ import {
|
||||
Logger,
|
||||
Module,
|
||||
OnApplicationBootstrap,
|
||||
OnModuleDestroy,
|
||||
BeforeApplicationShutdown,
|
||||
} from '@nestjs/common';
|
||||
import { InjectKysely, KyselyModule } from 'nestjs-kysely';
|
||||
import { EnvironmentService } from '../integrations/environment/environment.service';
|
||||
@ -38,6 +38,7 @@ types.setTypeParser(types.builtins.INT8, (val) => Number(val));
|
||||
dialect: new PostgresDialect({
|
||||
pool: new Pool({
|
||||
connectionString: environmentService.getDatabaseURL(),
|
||||
max: environmentService.getDatabaseMaxPool(),
|
||||
}).on('error', (err) => {
|
||||
console.error('Database error:', err.message);
|
||||
}),
|
||||
@ -86,7 +87,9 @@ types.setTypeParser(types.builtins.INT8, (val) => Number(val));
|
||||
BacklinkRepo,
|
||||
],
|
||||
})
|
||||
export class DatabaseModule implements OnModuleDestroy, OnApplicationBootstrap {
|
||||
export class DatabaseModule
|
||||
implements OnApplicationBootstrap, BeforeApplicationShutdown
|
||||
{
|
||||
private readonly logger = new Logger(DatabaseModule.name);
|
||||
|
||||
constructor(
|
||||
@ -103,7 +106,7 @@ export class DatabaseModule implements OnModuleDestroy, OnApplicationBootstrap {
|
||||
}
|
||||
}
|
||||
|
||||
async onModuleDestroy(): Promise<void> {
|
||||
async beforeApplicationShutdown(): Promise<void> {
|
||||
if (this.db) {
|
||||
await this.db.destroy();
|
||||
}
|
||||
|
||||
@ -0,0 +1,83 @@
|
||||
import { type Kysely, sql } from 'kysely';
|
||||
|
||||
export async function up(db: Kysely<any>): Promise<void> {
|
||||
await db.schema
|
||||
.createTable('billing')
|
||||
.addColumn('id', 'uuid', (col) =>
|
||||
col.primaryKey().defaultTo(sql`gen_uuid_v7()`),
|
||||
)
|
||||
.addColumn('stripe_subscription_id', 'varchar', (col) => col.notNull())
|
||||
.addColumn('stripe_customer_id', 'varchar', (col) => col)
|
||||
.addColumn('status', 'varchar', (col) => col.notNull())
|
||||
.addColumn('quantity', 'int8', (col) => col)
|
||||
.addColumn('amount', 'int8', (col) => col)
|
||||
.addColumn('interval', 'varchar', (col) => col)
|
||||
.addColumn('currency', 'varchar', (col) => col)
|
||||
.addColumn('metadata', 'jsonb', (col) => col)
|
||||
|
||||
.addColumn('stripe_price_id', 'varchar', (col) => col)
|
||||
.addColumn('stripe_item_id', 'varchar', (col) => col)
|
||||
.addColumn('stripe_product_id', 'varchar', (col) => col)
|
||||
|
||||
.addColumn('period_start_at', 'timestamptz', (col) => col.notNull())
|
||||
.addColumn('period_end_at', 'timestamptz', (col) => col)
|
||||
|
||||
.addColumn('cancel_at_period_end', 'boolean', (col) => col)
|
||||
.addColumn('cancel_at', 'timestamptz', (col) => col)
|
||||
.addColumn('canceled_at', 'timestamptz', (col) => col)
|
||||
.addColumn('ended_at', 'timestamptz', (col) => col)
|
||||
|
||||
.addColumn('workspace_id', 'uuid', (col) =>
|
||||
col.references('workspaces.id').onDelete('cascade').notNull(),
|
||||
)
|
||||
.addColumn('created_at', 'timestamptz', (col) =>
|
||||
col.notNull().defaultTo(sql`now()`),
|
||||
)
|
||||
.addColumn('updated_at', 'timestamptz', (col) =>
|
||||
col.notNull().defaultTo(sql`now()`),
|
||||
)
|
||||
.addColumn('deleted_at', 'timestamptz', (col) => col)
|
||||
.execute();
|
||||
|
||||
await db.schema
|
||||
.alterTable('billing')
|
||||
.addUniqueConstraint('billing_stripe_subscription_id_unique', [
|
||||
'stripe_subscription_id',
|
||||
])
|
||||
.execute();
|
||||
|
||||
// add new workspace columns
|
||||
await db.schema
|
||||
.alterTable('workspaces')
|
||||
.addColumn('stripe_customer_id', 'varchar', (col) => col)
|
||||
.addColumn('status', 'varchar', (col) => col)
|
||||
.addColumn('plan', 'varchar', (col) => col)
|
||||
.addColumn('billing_email', 'varchar', (col) => col)
|
||||
.addColumn('trial_end_at', 'timestamptz', (col) => col)
|
||||
.execute();
|
||||
|
||||
await db.schema
|
||||
.alterTable('workspaces')
|
||||
.addUniqueConstraint('workspaces_stripe_customer_id_unique', [
|
||||
'stripe_customer_id',
|
||||
])
|
||||
.execute();
|
||||
}
|
||||
|
||||
export async function down(db: Kysely<any>): Promise<void> {
|
||||
await db.schema.dropTable('billing').execute();
|
||||
|
||||
await db.schema
|
||||
.alterTable('workspaces')
|
||||
.dropColumn('stripe_customer_id')
|
||||
.execute();
|
||||
|
||||
await db.schema.alterTable('workspaces').dropColumn('status').execute();
|
||||
|
||||
await db.schema
|
||||
.alterTable('workspaces')
|
||||
.dropColumn('billing_email')
|
||||
.execute();
|
||||
|
||||
await db.schema.alterTable('workspaces').dropColumn('trial_end_at').execute();
|
||||
}
|
||||
@ -0,0 +1,86 @@
|
||||
import { type Kysely, sql } from 'kysely';
|
||||
|
||||
export async function up(db: Kysely<any>): Promise<void> {
|
||||
await db.schema
|
||||
.createType('auth_provider_type')
|
||||
.asEnum(['saml', 'oidc', 'google'])
|
||||
.execute();
|
||||
|
||||
await db.schema
|
||||
.createTable('auth_providers')
|
||||
.addColumn('id', 'uuid', (col) =>
|
||||
col.primaryKey().defaultTo(sql`gen_uuid_v7()`),
|
||||
)
|
||||
.addColumn('name', 'varchar', (col) => col.notNull())
|
||||
.addColumn('type', sql`auth_provider_type`, (col) => col.notNull())
|
||||
|
||||
// SAML
|
||||
.addColumn('saml_url', 'varchar', (col) => col)
|
||||
.addColumn('saml_certificate', 'varchar', (col) => col)
|
||||
|
||||
// OIDC
|
||||
.addColumn('oidc_issuer', 'varchar', (col) => col)
|
||||
.addColumn('oidc_client_id', 'varchar', (col) => col)
|
||||
.addColumn('oidc_client_secret', 'varchar', (col) => col)
|
||||
|
||||
.addColumn('allow_signup', 'boolean', (col) =>
|
||||
col.defaultTo(false).notNull(),
|
||||
)
|
||||
.addColumn('is_enabled', 'boolean', (col) => col.defaultTo(false).notNull())
|
||||
.addColumn('creator_id', 'uuid', (col) =>
|
||||
col.references('users.id').onDelete('set null'),
|
||||
)
|
||||
.addColumn('workspace_id', 'uuid', (col) =>
|
||||
col.references('workspaces.id').onDelete('cascade').notNull(),
|
||||
)
|
||||
.addColumn('created_at', 'timestamptz', (col) =>
|
||||
col.notNull().defaultTo(sql`now()`),
|
||||
)
|
||||
.addColumn('updated_at', 'timestamptz', (col) =>
|
||||
col.notNull().defaultTo(sql`now()`),
|
||||
)
|
||||
.addColumn('deleted_at', 'timestamptz', (col) => col)
|
||||
.execute();
|
||||
|
||||
await db.schema
|
||||
.createTable('auth_accounts')
|
||||
.addColumn('id', 'uuid', (col) =>
|
||||
col.primaryKey().defaultTo(sql`gen_uuid_v7()`),
|
||||
)
|
||||
.addColumn('user_id', 'uuid', (col) =>
|
||||
col.references('users.id').onDelete('cascade').notNull(),
|
||||
)
|
||||
.addColumn('provider_user_id', 'varchar', (col) => col.notNull())
|
||||
.addColumn('auth_provider_id', 'uuid', (col) =>
|
||||
col.references('auth_providers.id').onDelete('cascade'),
|
||||
)
|
||||
.addColumn('workspace_id', 'uuid', (col) =>
|
||||
col.references('workspaces.id').onDelete('cascade').notNull(),
|
||||
)
|
||||
.addColumn('created_at', 'timestamptz', (col) =>
|
||||
col.notNull().defaultTo(sql`now()`),
|
||||
)
|
||||
.addColumn('updated_at', 'timestamptz', (col) =>
|
||||
col.notNull().defaultTo(sql`now()`),
|
||||
)
|
||||
.addColumn('deleted_at', 'timestamptz', (col) => col)
|
||||
.addUniqueConstraint('auth_accounts_user_id_auth_provider_id_unique', [
|
||||
'user_id',
|
||||
'auth_provider_id',
|
||||
])
|
||||
.execute();
|
||||
|
||||
await db.schema
|
||||
.alterTable('workspaces')
|
||||
.addColumn('enforce_sso', 'boolean', (col) =>
|
||||
col.defaultTo(false).notNull(),
|
||||
)
|
||||
.execute();
|
||||
}
|
||||
|
||||
export async function down(db: Kysely<any>): Promise<void> {
|
||||
await db.schema.dropTable('auth_accounts').execute();
|
||||
await db.schema.dropTable('auth_providers').execute();
|
||||
await db.schema.alterTable('workspaces').dropColumn('enforce_sso').execute();
|
||||
await db.schema.dropType('auth_provider_type').execute();
|
||||
}
|
||||
@ -0,0 +1,12 @@
|
||||
import { type Kysely } from 'kysely';
|
||||
|
||||
export async function up(db: Kysely<any>): Promise<void> {
|
||||
await db.schema
|
||||
.alterTable('workspaces')
|
||||
.addColumn('license_key', 'varchar', (col) => col)
|
||||
.execute();
|
||||
}
|
||||
|
||||
export async function down(db: Kysely<any>): Promise<void> {
|
||||
await db.schema.alterTable('workspaces').dropColumn('license_key').execute();
|
||||
}
|
||||
@ -56,12 +56,16 @@ export class UserRepo {
|
||||
async findByEmail(
|
||||
email: string,
|
||||
workspaceId: string,
|
||||
includePassword?: boolean,
|
||||
opts?: {
|
||||
includePassword?: boolean;
|
||||
trx?: KyselyTransaction;
|
||||
},
|
||||
): Promise<User> {
|
||||
return this.db
|
||||
const db = dbOrTx(this.db, opts?.trx);
|
||||
return db
|
||||
.selectFrom('users')
|
||||
.select(this.baseFields)
|
||||
.$if(includePassword, (qb) => qb.select('password'))
|
||||
.$if(opts?.includePassword, (qb) => qb.select('password'))
|
||||
.where(sql`LOWER(email)`, '=', sql`LOWER(${email})`)
|
||||
.where('workspaceId', '=', workspaceId)
|
||||
.executeTakeFirst();
|
||||
@ -112,7 +116,7 @@ export class UserRepo {
|
||||
return db
|
||||
.insertInto('users')
|
||||
.values({ ...insertableUser, ...user })
|
||||
.returningAll()
|
||||
.returning(this.baseFields)
|
||||
.executeTakeFirst();
|
||||
}
|
||||
|
||||
@ -172,31 +176,4 @@ export class UserRepo {
|
||||
.returning(this.baseFields)
|
||||
.executeTakeFirst();
|
||||
}
|
||||
|
||||
/*
|
||||
async getSpaceIds(
|
||||
workspaceId: string,
|
||||
pagination: PaginationOptions,
|
||||
): Promise<PaginationResult<Space>> {
|
||||
const spaces = await this.spaceRepo.getSpacesInWorkspace(
|
||||
workspaceId,
|
||||
pagination,
|
||||
);
|
||||
|
||||
return spaces;
|
||||
}
|
||||
|
||||
async getUserSpaces(
|
||||
workspaceId: string,
|
||||
pagination: PaginationOptions,
|
||||
): Promise<PaginationResult<Space>> {
|
||||
const spaces = await this.spaceRepo.getSpacesInWorkspace(
|
||||
workspaceId,
|
||||
pagination,
|
||||
);
|
||||
|
||||
return spaces;
|
||||
}
|
||||
|
||||
*/
|
||||
}
|
||||
|
||||
@ -7,25 +7,63 @@ import {
|
||||
UpdatableWorkspace,
|
||||
Workspace,
|
||||
} from '@docmost/db/types/entity.types';
|
||||
import { sql } from 'kysely';
|
||||
import { ExpressionBuilder, sql } from 'kysely';
|
||||
import { DB, Workspaces } from '@docmost/db/types/db';
|
||||
|
||||
@Injectable()
|
||||
export class WorkspaceRepo {
|
||||
public baseFields: Array<keyof Workspaces> = [
|
||||
'id',
|
||||
'name',
|
||||
'description',
|
||||
'logo',
|
||||
'hostname',
|
||||
'customDomain',
|
||||
'settings',
|
||||
'defaultRole',
|
||||
'emailDomains',
|
||||
'defaultSpaceId',
|
||||
'createdAt',
|
||||
'updatedAt',
|
||||
'deletedAt',
|
||||
'stripeCustomerId',
|
||||
'status',
|
||||
'billingEmail',
|
||||
'trialEndAt',
|
||||
'enforceSso',
|
||||
'plan',
|
||||
];
|
||||
constructor(@InjectKysely() private readonly db: KyselyDB) {}
|
||||
|
||||
async findById(
|
||||
workspaceId: string,
|
||||
opts?: {
|
||||
withLock?: boolean;
|
||||
withMemberCount?: boolean;
|
||||
withLicenseKey?: boolean;
|
||||
trx?: KyselyTransaction;
|
||||
},
|
||||
): Promise<Workspace> {
|
||||
const db = dbOrTx(this.db, opts?.trx);
|
||||
|
||||
return db
|
||||
let query = db
|
||||
.selectFrom('workspaces')
|
||||
.selectAll()
|
||||
.where('id', '=', workspaceId)
|
||||
.executeTakeFirst();
|
||||
.select(this.baseFields)
|
||||
.where('id', '=', workspaceId);
|
||||
|
||||
if (opts?.withMemberCount) {
|
||||
query = query.select(this.withMemberCount);
|
||||
}
|
||||
|
||||
if (opts?.withLicenseKey) {
|
||||
query = query.select('licenseKey');
|
||||
}
|
||||
|
||||
if (opts?.withLock && opts?.trx) {
|
||||
query = query.forUpdate();
|
||||
}
|
||||
|
||||
return query.executeTakeFirst();
|
||||
}
|
||||
|
||||
async findFirst(): Promise<Workspace> {
|
||||
@ -45,17 +83,34 @@ export class WorkspaceRepo {
|
||||
.executeTakeFirst();
|
||||
}
|
||||
|
||||
async hostnameExists(
|
||||
hostname: string,
|
||||
trx?: KyselyTransaction,
|
||||
): Promise<boolean> {
|
||||
if (hostname?.length < 1) return false;
|
||||
|
||||
const db = dbOrTx(this.db, trx);
|
||||
let { count } = await db
|
||||
.selectFrom('workspaces')
|
||||
.select((eb) => eb.fn.count('id').as('count'))
|
||||
.where(sql`LOWER(hostname)`, '=', sql`LOWER(${hostname})`)
|
||||
.executeTakeFirst();
|
||||
count = count as number;
|
||||
return count != 0;
|
||||
}
|
||||
|
||||
async updateWorkspace(
|
||||
updatableWorkspace: UpdatableWorkspace,
|
||||
workspaceId: string,
|
||||
trx?: KyselyTransaction,
|
||||
) {
|
||||
): Promise<Workspace> {
|
||||
const db = dbOrTx(this.db, trx);
|
||||
return db
|
||||
.updateTable('workspaces')
|
||||
.set({ ...updatableWorkspace, updatedAt: new Date() })
|
||||
.where('id', '=', workspaceId)
|
||||
.execute();
|
||||
.returning(this.baseFields)
|
||||
.executeTakeFirst();
|
||||
}
|
||||
|
||||
async insertWorkspace(
|
||||
@ -66,7 +121,7 @@ export class WorkspaceRepo {
|
||||
return db
|
||||
.insertInto('workspaces')
|
||||
.values(insertableWorkspace)
|
||||
.returningAll()
|
||||
.returning(this.baseFields)
|
||||
.executeTakeFirst();
|
||||
}
|
||||
|
||||
@ -77,4 +132,28 @@ export class WorkspaceRepo {
|
||||
.executeTakeFirst();
|
||||
return count as number;
|
||||
}
|
||||
|
||||
withMemberCount(eb: ExpressionBuilder<DB, 'workspaces'>) {
|
||||
return eb
|
||||
.selectFrom('users')
|
||||
.select((eb) => eb.fn.countAll().as('count'))
|
||||
.where('users.deactivatedAt', 'is', null)
|
||||
.where('users.deletedAt', 'is', null)
|
||||
.whereRef('users.workspaceId', '=', 'workspaces.id')
|
||||
.as('memberCount');
|
||||
}
|
||||
|
||||
async getActiveUserCount(workspaceId: string): Promise<number> {
|
||||
const users = await this.db
|
||||
.selectFrom('users')
|
||||
.select(['id', 'deactivatedAt', 'deletedAt'])
|
||||
.where('workspaceId', '=', workspaceId)
|
||||
.execute();
|
||||
|
||||
const activeUsers = users.filter(
|
||||
(user) => user.deletedAt === null && user.deactivatedAt === null,
|
||||
);
|
||||
|
||||
return activeUsers.length;
|
||||
}
|
||||
}
|
||||
|
||||
66
apps/server/src/database/types/db.d.ts
vendored
66
apps/server/src/database/types/db.d.ts
vendored
@ -5,6 +5,8 @@
|
||||
|
||||
import type { ColumnType } from "kysely";
|
||||
|
||||
export type AuthProviderType = "google" | "oidc" | "saml";
|
||||
|
||||
export type Generated<T> = T extends ColumnType<infer S, infer I, infer U>
|
||||
? ColumnType<S, I | undefined, U>
|
||||
: ColumnType<T, T | undefined, T>;
|
||||
@ -42,6 +44,35 @@ export interface Attachments {
|
||||
workspaceId: string;
|
||||
}
|
||||
|
||||
export interface AuthAccounts {
|
||||
authProviderId: string | null;
|
||||
createdAt: Generated<Timestamp>;
|
||||
deletedAt: Timestamp | null;
|
||||
id: Generated<string>;
|
||||
providerUserId: string;
|
||||
updatedAt: Generated<Timestamp>;
|
||||
userId: string;
|
||||
workspaceId: string;
|
||||
}
|
||||
|
||||
export interface AuthProviders {
|
||||
allowSignup: Generated<boolean>;
|
||||
createdAt: Generated<Timestamp>;
|
||||
creatorId: string | null;
|
||||
deletedAt: Timestamp | null;
|
||||
id: Generated<string>;
|
||||
isEnabled: Generated<boolean>;
|
||||
name: string;
|
||||
oidcClientId: string | null;
|
||||
oidcClientSecret: string | null;
|
||||
oidcIssuer: string | null;
|
||||
samlCertificate: string | null;
|
||||
samlUrl: string | null;
|
||||
type: AuthProviderType;
|
||||
updatedAt: Generated<Timestamp>;
|
||||
workspaceId: string;
|
||||
}
|
||||
|
||||
export interface Backlinks {
|
||||
createdAt: Generated<Timestamp>;
|
||||
id: Generated<string>;
|
||||
@ -51,6 +82,31 @@ export interface Backlinks {
|
||||
workspaceId: string;
|
||||
}
|
||||
|
||||
export interface Billing {
|
||||
amount: Int8 | null;
|
||||
cancelAt: Timestamp | null;
|
||||
cancelAtPeriodEnd: boolean | null;
|
||||
canceledAt: Timestamp | null;
|
||||
createdAt: Generated<Timestamp>;
|
||||
currency: string | null;
|
||||
deletedAt: Timestamp | null;
|
||||
endedAt: Timestamp | null;
|
||||
id: Generated<string>;
|
||||
interval: string | null;
|
||||
metadata: Json | null;
|
||||
periodEndAt: Timestamp | null;
|
||||
periodStartAt: Timestamp;
|
||||
quantity: Int8 | null;
|
||||
status: string;
|
||||
stripeCustomerId: string | null;
|
||||
stripeItemId: string | null;
|
||||
stripePriceId: string | null;
|
||||
stripeProductId: string | null;
|
||||
stripeSubscriptionId: string;
|
||||
updatedAt: Generated<Timestamp>;
|
||||
workspaceId: string;
|
||||
}
|
||||
|
||||
export interface Comments {
|
||||
content: Json | null;
|
||||
createdAt: Generated<Timestamp>;
|
||||
@ -198,6 +254,7 @@ export interface WorkspaceInvitations {
|
||||
}
|
||||
|
||||
export interface Workspaces {
|
||||
billingEmail: string | null;
|
||||
createdAt: Generated<Timestamp>;
|
||||
customDomain: string | null;
|
||||
defaultRole: Generated<string>;
|
||||
@ -205,17 +262,26 @@ export interface Workspaces {
|
||||
deletedAt: Timestamp | null;
|
||||
description: string | null;
|
||||
emailDomains: Generated<string[] | null>;
|
||||
enforceSso: Generated<boolean>;
|
||||
hostname: string | null;
|
||||
id: Generated<string>;
|
||||
licenseKey: string | null;
|
||||
logo: string | null;
|
||||
name: string | null;
|
||||
plan: string | null;
|
||||
settings: Json | null;
|
||||
status: string | null;
|
||||
stripeCustomerId: string | null;
|
||||
trialEndAt: Timestamp | null;
|
||||
updatedAt: Generated<Timestamp>;
|
||||
}
|
||||
|
||||
export interface DB {
|
||||
attachments: Attachments;
|
||||
authAccounts: AuthAccounts;
|
||||
authProviders: AuthProviders;
|
||||
backlinks: Backlinks;
|
||||
billing: Billing;
|
||||
comments: Comments;
|
||||
groups: Groups;
|
||||
groupUsers: GroupUsers;
|
||||
|
||||
@ -13,6 +13,9 @@ import {
|
||||
WorkspaceInvitations,
|
||||
UserTokens,
|
||||
Backlinks,
|
||||
Billing as BillingSubscription,
|
||||
AuthProviders,
|
||||
AuthAccounts,
|
||||
} from './db';
|
||||
|
||||
// Workspace
|
||||
@ -83,3 +86,18 @@ export type UpdatableUserToken = Updateable<Omit<UserTokens, 'id'>>;
|
||||
export type Backlink = Selectable<Backlinks>;
|
||||
export type InsertableBacklink = Insertable<Backlink>;
|
||||
export type UpdatableBacklink = Updateable<Omit<Backlink, 'id'>>;
|
||||
|
||||
// Billing
|
||||
export type Billing = Selectable<BillingSubscription>;
|
||||
export type InsertableBilling = Insertable<BillingSubscription>;
|
||||
export type UpdatableBilling = Updateable<Omit<BillingSubscription, 'id'>>;
|
||||
|
||||
// Auth Provider
|
||||
export type AuthProvider = Selectable<AuthProviders>;
|
||||
export type InsertableAuthProvider = Insertable<AuthProviders>;
|
||||
export type UpdatableAuthProvider = Updateable<Omit<AuthProviders, 'id'>>;
|
||||
|
||||
// Auth Account
|
||||
export type AuthAccount = Selectable<AuthAccounts>;
|
||||
export type InsertableAuthAccount = Insertable<AuthAccounts>;
|
||||
export type UpdatableAuthAccount = Updateable<Omit<AuthAccounts, 'id'>>;
|
||||
|
||||
Reference in New Issue
Block a user