mirror of
https://github.com/docmost/docmost.git
synced 2026-05-18 23:44:24 +08:00
b81c9ee10c
* 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
160 lines
4.0 KiB
TypeScript
160 lines
4.0 KiB
TypeScript
import { Injectable } from '@nestjs/common';
|
|
import { InjectKysely } from 'nestjs-kysely';
|
|
import { KyselyDB, KyselyTransaction } from '../../types/kysely.types';
|
|
import { dbOrTx } from '../../utils';
|
|
import {
|
|
InsertableWorkspace,
|
|
UpdatableWorkspace,
|
|
Workspace,
|
|
} from '@docmost/db/types/entity.types';
|
|
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);
|
|
|
|
let query = db
|
|
.selectFrom('workspaces')
|
|
.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> {
|
|
return await this.db
|
|
.selectFrom('workspaces')
|
|
.selectAll()
|
|
.orderBy('createdAt asc')
|
|
.limit(1)
|
|
.executeTakeFirst();
|
|
}
|
|
|
|
async findByHostname(hostname: string): Promise<Workspace> {
|
|
return await this.db
|
|
.selectFrom('workspaces')
|
|
.selectAll()
|
|
.where(sql`LOWER(hostname)`, '=', sql`LOWER(${hostname})`)
|
|
.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)
|
|
.returning(this.baseFields)
|
|
.executeTakeFirst();
|
|
}
|
|
|
|
async insertWorkspace(
|
|
insertableWorkspace: InsertableWorkspace,
|
|
trx?: KyselyTransaction,
|
|
): Promise<Workspace> {
|
|
const db = dbOrTx(this.db, trx);
|
|
return db
|
|
.insertInto('workspaces')
|
|
.values(insertableWorkspace)
|
|
.returning(this.baseFields)
|
|
.executeTakeFirst();
|
|
}
|
|
|
|
async count(): Promise<number> {
|
|
const { count } = await this.db
|
|
.selectFrom('workspaces')
|
|
.select((eb) => eb.fn.count('id').as('count'))
|
|
.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;
|
|
}
|
|
}
|