1
0
mirror of https://github.com/laurent22/joplin.git synced 2024-12-30 10:36:35 +02:00
joplin/packages/server/src/models/UserItemModel.ts
2021-09-25 19:51:44 +01:00

200 lines
6.7 KiB
TypeScript

import { ChangeType, Item, ItemType, UserItem, Uuid } from '../services/database/types';
import BaseModel, { DeleteOptions, LoadOptions, SaveOptions } from './BaseModel';
import { unique } from '../utils/array';
import { ErrorNotFound } from '../utils/errors';
import { Knex } from 'knex';
interface DeleteByShare {
id: Uuid;
owner_id: Uuid;
}
export interface UserItemDeleteOptions extends DeleteOptions {
byItemIds?: string[];
byShareId?: string;
byUserId?: string;
byUserItem?: UserItem;
byUserItemIds?: number[];
byShare?: DeleteByShare;
}
export default class UserItemModel extends BaseModel<UserItem> {
public get tableName(): string {
return 'user_items';
}
protected hasUuid(): boolean {
return false;
}
public async remove(userId: Uuid, itemId: Uuid): Promise<void> {
await this.deleteByUserItem(userId, itemId);
}
public async userIdsByItemIds(itemIds: Uuid[]): Promise<Record<Uuid, Uuid[]>> {
const rows: UserItem[] = await this.db(this.tableName).select('item_id', 'user_id').whereIn('item_id', itemIds);
const output: Record<Uuid, Uuid[]> = {};
for (const row of rows) {
if (!output[row.item_id]) output[row.item_id] = [];
output[row.item_id].push(row.user_id);
}
return output;
}
public async byItemIds(itemIds: Uuid[]): Promise<UserItem[]> {
return this.db(this.tableName).select(this.defaultFields).whereIn('item_id', itemIds);
}
public async byShareId(shareId: Uuid, options: LoadOptions = {}): Promise<UserItem[]> {
return this
.db(this.tableName)
.leftJoin('items', 'user_items.item_id', 'items.id')
.select(this.selectFields(options, this.defaultFields, 'user_items'))
.where('items.jop_share_id', '=', shareId);
}
public async byShareAndUserId(shareId: Uuid, userId: Uuid, options: LoadOptions = {}): Promise<UserItem[]> {
return this
.db(this.tableName)
.leftJoin('items', 'user_items.item_id', 'items.id')
.select(this.selectFields(options, this.defaultFields, 'user_items'))
.where('items.jop_share_id', '=', shareId)
.where('user_items.user_id', '=', userId);
}
public async byUserId(userId: Uuid): Promise<UserItem[]> {
return this.db(this.tableName).where('user_id', '=', userId);
}
public async byUserAndItemId(userId: Uuid, itemId: Uuid): Promise<UserItem> {
return this.db(this.tableName).where('user_id', '=', userId).where('item_id', '=', itemId).first();
}
// Returns any user item that is part of a share
public async itemsInShare(userId: Uuid, options: LoadOptions = {}): Promise<UserItem[]> {
return this
.db(this.tableName)
.leftJoin('items', 'user_items.item_id', 'items.id')
.select(this.selectFields(options, this.defaultFields, 'user_items'))
.where('items.jop_share_id', '!=', '')
.where('user_items.user_id', '=', userId);
}
public async deleteByUserItem(userId: Uuid, itemId: Uuid): Promise<void> {
const userItem = await this.byUserAndItemId(userId, itemId);
if (!userItem) throw new ErrorNotFound(`No such user_item: ${userId} / ${itemId}`);
await this.deleteBy({ byUserItem: userItem });
}
public async deleteByItemIds(itemIds: Uuid[]): Promise<void> {
await this.deleteBy({ byItemIds: itemIds });
}
public async deleteByShareId(shareId: Uuid): Promise<void> {
await this.deleteBy({ byShareId: shareId });
}
public async deleteByShare(share: DeleteByShare): Promise<void> {
await this.deleteBy({ byShare: share });
}
public async deleteByUserId(userId: Uuid): Promise<void> {
await this.deleteBy({ byUserId: userId });
}
public async deleteByUserItemIds(userItemIds: number[]): Promise<void> {
await this.deleteBy({ byUserItemIds: userItemIds });
}
public async deleteByShareAndUserId(shareId: Uuid, userId: Uuid): Promise<void> {
await this.deleteBy({ byShareId: shareId, byUserId: userId });
}
public async add(userId: Uuid, itemId: Uuid, options: SaveOptions = {}): Promise<void> {
const item = await this.models().item().load(itemId, { fields: ['id', 'name'] });
await this.addMulti(userId, [item], options);
}
public async addMulti(userId: Uuid, itemsQuery: Knex.QueryBuilder | Item[], options: SaveOptions = {}): Promise<void> {
const items: Item[] = Array.isArray(itemsQuery) ? itemsQuery : await itemsQuery.whereNotIn('id', this.db('user_items').select('item_id').where('user_id', '=', userId));
if (!items.length) return;
await this.withTransaction(async () => {
for (const item of items) {
if (!('name' in item) || !('id' in item)) throw new Error('item.id and item.name must be set');
await super.save({
user_id: userId,
item_id: item.id,
}, options);
if (this.models().item().shouldRecordChange(item.name)) {
await this.models().change().save({
item_type: ItemType.UserItem,
item_id: item.id,
item_name: item.name,
type: ChangeType.Create,
previous_item: '',
user_id: userId,
});
}
}
}, 'UserItemModel::addMulti');
}
public async save(_userItem: UserItem, _options: SaveOptions = {}): Promise<UserItem> {
throw new Error('Call add() or addMulti()');
}
public async delete(_id: string | string[], _options: DeleteOptions = {}): Promise<void> {
throw new Error('Use one of the deleteBy methods');
}
private async deleteBy(options: UserItemDeleteOptions = {}): Promise<void> {
let userItems: UserItem[] = [];
if (options.byShareId && options.byUserId) {
userItems = await this.byShareAndUserId(options.byShareId, options.byUserId);
} else if (options.byItemIds) {
userItems = await this.byItemIds(options.byItemIds);
} else if (options.byShareId) {
userItems = await this.byShareId(options.byShareId);
} else if (options.byShare) {
userItems = await this.byShareId(options.byShare.id);
userItems = userItems.filter(u => u.user_id !== options.byShare.owner_id);
} else if (options.byUserId) {
userItems = await this.byUserId(options.byUserId);
} else if (options.byUserItem) {
userItems = [options.byUserItem];
} else if (options.byUserItemIds) {
userItems = await this.loadByIds(options.byUserItemIds as any);
} else {
throw new Error('Invalid options');
}
const itemIds = unique(userItems.map(ui => ui.item_id));
const items = await this.models().item().loadByIds(itemIds, { fields: ['id', 'name'] });
await this.withTransaction(async () => {
for (const userItem of userItems) {
const item = items.find(i => i.id === userItem.item_id);
if (this.models().item().shouldRecordChange(item.name)) {
await this.models().change().save({
item_type: ItemType.UserItem,
item_id: userItem.item_id,
item_name: item.name,
type: ChangeType.Delete,
previous_item: '',
user_id: userItem.user_id,
});
}
}
await this.db(this.tableName).whereIn('id', userItems.map(ui => ui.id)).delete();
}, 'ItemModel::delete');
}
}