mirror of
https://github.com/docmost/docmost.git
synced 2025-11-19 08:01:09 +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:
@ -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;
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user