Files
docmost_custom/apps/server/src/database/repos/user/user.repo.ts
T
Philip Okugbe b81c9ee10c 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
2025-03-06 13:38:37 +00:00

180 lines
4.6 KiB
TypeScript

import { Injectable } from '@nestjs/common';
import { InjectKysely } from 'nestjs-kysely';
import { KyselyDB, KyselyTransaction } from '@docmost/db/types/kysely.types';
import { Users } from '@docmost/db/types/db';
import { hashPassword } from '../../../common/helpers';
import { dbOrTx } from '@docmost/db/utils';
import {
InsertableUser,
UpdatableUser,
User,
} from '@docmost/db/types/entity.types';
import { PaginationOptions } from '../../pagination/pagination-options';
import { executeWithPagination } from '@docmost/db/pagination/pagination';
import { sql } from 'kysely';
@Injectable()
export class UserRepo {
constructor(@InjectKysely() private readonly db: KyselyDB) {}
public baseFields: Array<keyof Users> = [
'id',
'email',
'name',
'emailVerifiedAt',
'avatarUrl',
'role',
'workspaceId',
'locale',
'timezone',
'settings',
'lastLoginAt',
'deactivatedAt',
'createdAt',
'updatedAt',
'deletedAt',
];
async findById(
userId: string,
workspaceId: string,
opts?: {
includePassword?: boolean;
trx?: KyselyTransaction;
},
): Promise<User> {
const db = dbOrTx(this.db, opts?.trx);
return db
.selectFrom('users')
.select(this.baseFields)
.$if(opts?.includePassword, (qb) => qb.select('password'))
.where('id', '=', userId)
.where('workspaceId', '=', workspaceId)
.executeTakeFirst();
}
async findByEmail(
email: string,
workspaceId: string,
opts?: {
includePassword?: boolean;
trx?: KyselyTransaction;
},
): Promise<User> {
const db = dbOrTx(this.db, opts?.trx);
return db
.selectFrom('users')
.select(this.baseFields)
.$if(opts?.includePassword, (qb) => qb.select('password'))
.where(sql`LOWER(email)`, '=', sql`LOWER(${email})`)
.where('workspaceId', '=', workspaceId)
.executeTakeFirst();
}
async updateUser(
updatableUser: UpdatableUser,
userId: string,
workspaceId: string,
trx?: KyselyTransaction,
) {
const db = dbOrTx(this.db, trx);
return await db
.updateTable('users')
.set({ ...updatableUser, updatedAt: new Date() })
.where('id', '=', userId)
.where('workspaceId', '=', workspaceId)
.execute();
}
async updateLastLogin(userId: string, workspaceId: string) {
return await this.db
.updateTable('users')
.set({
lastLoginAt: new Date(),
})
.where('id', '=', userId)
.where('workspaceId', '=', workspaceId)
.execute();
}
async insertUser(
insertableUser: InsertableUser,
trx?: KyselyTransaction,
): Promise<User> {
const user: InsertableUser = {
name:
insertableUser.name || insertableUser.email.split('@')[0].toLowerCase(),
email: insertableUser.email.toLowerCase(),
password: await hashPassword(insertableUser.password),
locale: 'en-US',
role: insertableUser?.role,
lastLoginAt: new Date(),
};
const db = dbOrTx(this.db, trx);
return db
.insertInto('users')
.values({ ...insertableUser, ...user })
.returning(this.baseFields)
.executeTakeFirst();
}
async roleCountByWorkspaceId(
role: string,
workspaceId: string,
): Promise<number> {
const { count } = await this.db
.selectFrom('users')
.select((eb) => eb.fn.count('role').as('count'))
.where('role', '=', role)
.where('workspaceId', '=', workspaceId)
.executeTakeFirst();
return count as number;
}
async getUsersPaginated(workspaceId: string, pagination: PaginationOptions) {
let query = this.db
.selectFrom('users')
.select(this.baseFields)
.where('workspaceId', '=', workspaceId)
.orderBy('createdAt', 'asc');
if (pagination.query) {
query = query.where((eb) =>
eb('users.name', 'ilike', `%${pagination.query}%`).or(
'users.email',
'ilike',
`%${pagination.query}%`,
),
);
}
const result = executeWithPagination(query, {
page: pagination.page,
perPage: pagination.limit,
});
return result;
}
async updatePreference(
userId: string,
prefKey: string,
prefValue: string | boolean,
) {
return await this.db
.updateTable('users')
.set({
settings: sql`COALESCE(settings, '{}'::jsonb)
|| jsonb_build_object('preferences', COALESCE(settings->'preferences', '{}'::jsonb)
|| jsonb_build_object('${sql.raw(prefKey)}', ${sql.lit(prefValue)}))`,
updatedAt: new Date(),
})
.where('id', '=', userId)
.returning(this.baseFields)
.executeTakeFirst();
}
}