|
|
|
import { Dictionary } from 'lodash';
|
|
|
|
import { FilterQuery } from 'mongodb';
|
|
|
|
import { BUILTIN_ROLES, PRIV } from './builtin';
|
|
|
|
import { DomainDoc } from '../interface';
|
|
|
|
import { ArgMethod } from '../utils';
|
|
|
|
import { MaybeArray, NumberKeys } from '../typeutils';
|
|
|
|
import * as bus from '../service/bus';
|
|
|
|
import db from '../service/db';
|
|
|
|
|
|
|
|
const coll = db.collection('domain');
|
|
|
|
const collUser = db.collection('domain.user');
|
|
|
|
|
|
|
|
interface DomainUserArg {
|
|
|
|
_id: number,
|
|
|
|
priv: number,
|
|
|
|
}
|
|
|
|
|
|
|
|
class DomainModel {
|
|
|
|
static JOIN_METHOD_NONE = 0;
|
|
|
|
static JOIN_METHOD_ALL = 1;
|
|
|
|
static JOIN_METHOD_CODE = 2;
|
|
|
|
|
|
|
|
static JOIN_METHOD_RANGE = {
|
|
|
|
[DomainModel.JOIN_METHOD_NONE]: 'No user is allowed to join this domain',
|
|
|
|
[DomainModel.JOIN_METHOD_ALL]: 'Any user is allowed to join this domain',
|
|
|
|
[DomainModel.JOIN_METHOD_CODE]: 'Any user is allowed to join this domain with an invitation code',
|
|
|
|
};
|
|
|
|
|
|
|
|
static JOIN_EXPIRATION_KEEP_CURRENT = 0;
|
|
|
|
static JOIN_EXPIRATION_UNLIMITED = -1;
|
|
|
|
|
|
|
|
static JOIN_EXPIRATION_RANGE = {
|
|
|
|
[DomainModel.JOIN_EXPIRATION_KEEP_CURRENT]: 'Keep current expiration',
|
|
|
|
3: 'In 3 hours',
|
|
|
|
24: 'In 1 day',
|
|
|
|
[24 * 3]: 'In 3 days',
|
|
|
|
[24 * 7]: 'In 1 week',
|
|
|
|
[24 * 30]: 'In 1 month',
|
|
|
|
[DomainModel.JOIN_EXPIRATION_UNLIMITED]: 'Never expire',
|
|
|
|
};
|
|
|
|
|
|
|
|
@ArgMethod
|
|
|
|
static async add(domainId: string, owner: number, name: string, bulletin: string) {
|
|
|
|
const ddoc: DomainDoc = {
|
|
|
|
_id: domainId,
|
|
|
|
owner,
|
|
|
|
name,
|
|
|
|
bulletin,
|
|
|
|
roles: {},
|
|
|
|
gravatar: '',
|
|
|
|
pidCounter: 0,
|
|
|
|
};
|
|
|
|
await bus.serial('domain/create', ddoc);
|
|
|
|
await coll.insertOne(ddoc);
|
|
|
|
await DomainModel.setUserRole(domainId, owner, 'root');
|
|
|
|
return domainId;
|
|
|
|
}
|
|
|
|
|
|
|
|
@ArgMethod
|
|
|
|
static async get(domainId: string): Promise<DomainDoc | null> {
|
|
|
|
const query: FilterQuery<DomainDoc> = { _id: domainId };
|
|
|
|
await bus.serial('domain/before-get', query);
|
|
|
|
const result = await coll.findOne(query);
|
|
|
|
await bus.serial('domain/get', result);
|
|
|
|
return result;
|
|
|
|
}
|
|
|
|
|
|
|
|
@ArgMethod
|
|
|
|
static async getByHost(host: string): Promise<DomainDoc | null> {
|
|
|
|
const query: FilterQuery<DomainDoc> = { host };
|
|
|
|
await bus.serial('domain/before-get', query);
|
|
|
|
const result = await coll.findOne(query);
|
|
|
|
await bus.serial('domain/get', result);
|
|
|
|
return result;
|
|
|
|
}
|
|
|
|
|
|
|
|
static getMulti(query: FilterQuery<DomainDoc> = {}) {
|
|
|
|
return coll.find(query);
|
|
|
|
}
|
|
|
|
|
|
|
|
static async edit(domainId: string, $set: Partial<DomainDoc>) {
|
|
|
|
await bus.serial('domain/before-update', domainId, $set);
|
|
|
|
const result = await coll.findOneAndUpdate({ _id: domainId }, { $set }, { returnOriginal: false });
|
|
|
|
await bus.serial('domain/update', domainId, $set, result.value);
|
|
|
|
return result.value;
|
|
|
|
}
|
|
|
|
|
|
|
|
@ArgMethod
|
|
|
|
static async inc(domainId: string, field: NumberKeys<DomainDoc>, n: number): Promise<number | null> {
|
|
|
|
const res = await coll.findOneAndUpdate(
|
|
|
|
{ _id: domainId },
|
|
|
|
// FIXME
|
|
|
|
// @ts-expect-error
|
|
|
|
{ $inc: { [field]: n } },
|
|
|
|
{ returnOriginal: false },
|
|
|
|
);
|
|
|
|
return res.value[field];
|
|
|
|
}
|
|
|
|
|
|
|
|
@ArgMethod
|
|
|
|
static async getList(domainIds: string[]): Promise<Dictionary<DomainDoc>> {
|
|
|
|
const r = {};
|
|
|
|
// eslint-disable-next-line no-await-in-loop
|
|
|
|
for (const domainId of domainIds) r[domainId] = await DomainModel.get(domainId);
|
|
|
|
return r;
|
|
|
|
}
|
|
|
|
|
|
|
|
static async countUser(domainId: string, role?: string) {
|
|
|
|
if (role) return await collUser.find({ domainId, role }).count();
|
|
|
|
return await collUser.find({ domainId }).count();
|
|
|
|
}
|
|
|
|
|
|
|
|
@ArgMethod
|
|
|
|
static setUserRole(domainId: string, uid: MaybeArray<number>, role: string) {
|
|
|
|
if (!(uid instanceof Array)) {
|
|
|
|
return collUser.updateOne({ domainId, uid }, { $set: { role } }, { upsert: true });
|
|
|
|
}
|
|
|
|
return collUser.updateMany({ domainId, uid: { $in: uid } }, { $set: { role } }, { upsert: true });
|
|
|
|
}
|
|
|
|
|
|
|
|
static async getRoles(domainId: string, count?: boolean): Promise<any[]>;
|
|
|
|
static async getRoles(domain: DomainDoc, count?: boolean): Promise<any[]>;
|
|
|
|
@ArgMethod
|
|
|
|
static async getRoles(arg: string | DomainDoc, count: boolean = false) {
|
|
|
|
let ddoc: DomainDoc;
|
|
|
|
if (typeof arg === 'string') ddoc = await DomainModel.get(arg);
|
|
|
|
else ddoc = arg;
|
|
|
|
const roles = [];
|
|
|
|
const r = [];
|
|
|
|
for (const role in ddoc.roles) {
|
|
|
|
roles.push({ _id: role, perm: BigInt(ddoc.roles[role]) });
|
|
|
|
r.push(role);
|
|
|
|
}
|
|
|
|
for (const role in BUILTIN_ROLES) {
|
|
|
|
if (!r.includes(role)) {
|
|
|
|
roles.push({ _id: role, perm: BUILTIN_ROLES[role] });
|
|
|
|
}
|
|
|
|
}
|
|
|
|
if (count) {
|
|
|
|
for (const role of roles) {
|
|
|
|
if (!['default', 'guest'].includes(role._id)) {
|
|
|
|
// eslint-disable-next-line no-await-in-loop
|
|
|
|
role.count = await DomainModel.countUser(ddoc._id, role._id);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
return roles;
|
|
|
|
}
|
|
|
|
|
|
|
|
static async setRoles(domainId: string, roles: Dictionary<bigint>) {
|
|
|
|
const current = await DomainModel.get(domainId);
|
|
|
|
for (const role in roles) {
|
|
|
|
current.roles[role] = roles[role].toString();
|
|
|
|
}
|
|
|
|
return await coll.updateOne({ _id: domainId }, { $set: { roles: current.roles } });
|
|
|
|
}
|
|
|
|
|
|
|
|
static async addRole(domainId: string, name: string, permission: bigint) {
|
|
|
|
const current = await DomainModel.get(domainId);
|
|
|
|
current.roles[name] = permission.toString();
|
|
|
|
return await coll.updateOne({ _id: domainId }, { $set: { roles: current.roles } });
|
|
|
|
}
|
|
|
|
|
|
|
|
static async deleteRoles(domainId: string, roles: string[]) {
|
|
|
|
const current = await DomainModel.get(domainId);
|
|
|
|
for (const role of roles) delete current.roles[role];
|
|
|
|
await Promise.all([
|
|
|
|
coll.updateOne({ _id: domainId }, { $set: current }),
|
|
|
|
collUser.updateMany({ domainId, role: { $in: roles } }, { $set: { role: 'default' } }),
|
|
|
|
]);
|
|
|
|
}
|
|
|
|
|
|
|
|
static async getDomainUser(domainId: string, udoc: DomainUserArg) {
|
|
|
|
let dudoc = await collUser.findOne({ domainId, uid: udoc._id });
|
|
|
|
dudoc = dudoc || {};
|
|
|
|
if (!(udoc.priv & PRIV.PRIV_USER_PROFILE)) dudoc.role = 'guest';
|
|
|
|
if (udoc.priv & PRIV.PRIV_MANAGE_ALL_DOMAIN) dudoc.role = 'root';
|
|
|
|
dudoc.role = dudoc.role || 'default';
|
|
|
|
const ddoc = await DomainModel.get(domainId);
|
|
|
|
dudoc.perm = ddoc.roles[dudoc.role]
|
|
|
|
? BigInt(ddoc.roles[dudoc.role])
|
|
|
|
: BUILTIN_ROLES[dudoc.role];
|
|
|
|
return dudoc;
|
|
|
|
}
|
|
|
|
|
|
|
|
static setMultiUserInDomain(domainId: string, query: any, params: any) {
|
|
|
|
return collUser.updateMany({ domainId, ...query }, { $set: params }, { upsert: true });
|
|
|
|
}
|
|
|
|
|
|
|
|
static getMultiUserInDomain(domainId: string, query: any = {}) {
|
|
|
|
return collUser.find({ domainId, ...query });
|
|
|
|
}
|
|
|
|
|
|
|
|
static setUserInDomain(domainId: string, uid: number, params: any) {
|
|
|
|
return collUser.updateOne({ domainId, uid }, { $set: params }, { upsert: true });
|
|
|
|
}
|
|
|
|
|
|
|
|
@ArgMethod
|
|
|
|
static async incUserInDomain(domainId: string, uid: number, field: string, n: number = 1) {
|
|
|
|
// @ts-ignore
|
|
|
|
const dudoc = await DomainModel.getDomainUser(domainId, { _id: uid });
|
|
|
|
dudoc[field] = dudoc[field] + n || n;
|
|
|
|
await DomainModel.setUserInDomain(domainId, uid, { [field]: dudoc[field] });
|
|
|
|
return dudoc;
|
|
|
|
}
|
|
|
|
|
|
|
|
@ArgMethod
|
|
|
|
static async getDictUserByDomainId(uid: number) {
|
|
|
|
const dudocs = await collUser.find({ uid }).toArray();
|
|
|
|
const dudict = {};
|
|
|
|
for (const dudoc of dudocs) {
|
|
|
|
// eslint-disable-next-line no-await-in-loop
|
|
|
|
dudict[dudoc.domainId] = await DomainModel.get(dudoc.domainId);
|
|
|
|
dudict[dudoc.domainId].role = dudoc.role;
|
|
|
|
}
|
|
|
|
return dudict;
|
|
|
|
}
|
|
|
|
|
|
|
|
static getJoinSettings(ddoc: DomainDoc, roles: string[]) {
|
|
|
|
if (!ddoc.join) return null;
|
|
|
|
const joinSettings = ddoc.join;
|
|
|
|
if (joinSettings.method === DomainModel.JOIN_METHOD_NONE) return null;
|
|
|
|
if (!roles.includes(joinSettings.role)) return null;
|
|
|
|
if (joinSettings.expire && joinSettings.expire < new Date()) return null;
|
|
|
|
return joinSettings;
|
|
|
|
}
|
|
|
|
|
|
|
|
@ArgMethod
|
|
|
|
static async getPrefixSearch(prefix: string, limit: number = 50) {
|
|
|
|
const $regex = new RegExp(prefix, 'mi');
|
|
|
|
const ddocs = await coll.find({
|
|
|
|
$or: [{ _id: { $regex } }, { name: { $regex } }],
|
|
|
|
}).limit(limit).toArray();
|
|
|
|
return ddocs;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
export = DomainModel;
|
|
|
|
global.Hydro.model.domain = DomainModel;
|