mirror of
https://github.com/omnivore-app/omnivore.git
synced 2026-03-11 08:54:26 +00:00
183 lines
4.9 KiB
TypeScript
183 lines
4.9 KiB
TypeScript
/* eslint-disable @typescript-eslint/no-floating-promises */
|
|
/* eslint-disable @typescript-eslint/no-unsafe-assignment */
|
|
import DataLoader from 'dataloader'
|
|
import Knex from 'knex'
|
|
import { ENABLE_DB_REQUEST_LOGGING, globalCounter } from './helpers'
|
|
|
|
export enum DataModelError {
|
|
notFound = 'NOT_FOUND',
|
|
}
|
|
|
|
export const MAX_RECORDS_LIMIT = 1000
|
|
|
|
abstract class DataModel<
|
|
ModelData extends { id: string },
|
|
CreateSet,
|
|
UpdateSet
|
|
> {
|
|
protected loader: DataLoader<string, ModelData>
|
|
public tableName!: string
|
|
protected modelKeys!: readonly (keyof ModelData)[]
|
|
kx: Knex
|
|
get: DataLoader<string, ModelData>['load']
|
|
getMany: DataLoader<string, ModelData>['loadMany']
|
|
|
|
/**
|
|
* @param kx - DB connection
|
|
* @param userId - user id to use when executing data loader queries
|
|
* */
|
|
constructor(kx: Knex, cache = true) {
|
|
this.kx = kx
|
|
this.loader = new DataLoader(
|
|
async (keys) => {
|
|
if (ENABLE_DB_REQUEST_LOGGING) {
|
|
globalCounter.log(this.tableName, 'load', JSON.stringify(keys))
|
|
}
|
|
try {
|
|
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
const rows: ModelData[] = await this.kx(this.tableName)
|
|
.select(this.modelKeys)
|
|
.whereIn('id', keys)
|
|
|
|
const keyMap: Record<string, ModelData> = {}
|
|
for (const row of rows) {
|
|
if (row.id in keyMap) continue
|
|
keyMap[row.id] = row
|
|
}
|
|
const result = keys.map((key) => keyMap[key])
|
|
if (result.length !== keys.length) {
|
|
console.error('DataModel error: count mismatch ', keys, result)
|
|
}
|
|
return result
|
|
} catch (e) {
|
|
console.error('DataModel error: ', e)
|
|
throw e
|
|
}
|
|
},
|
|
{ cache }
|
|
)
|
|
this.get = this.loader.load.bind(this.loader)
|
|
this.getMany = this.loader.loadMany.bind(this.loader)
|
|
}
|
|
|
|
/**
|
|
* Gets entity that accomplish "whereIn" condition
|
|
* @param field - string specifying a field to use in "whereIn" condition
|
|
* @param values - an array of values to compare in "whereIn" condition
|
|
* @param tx - DB transaction
|
|
* @example
|
|
* // Return entity that match id comparison
|
|
* const collaboratorsIds = ['1', '2'];
|
|
* return models.user.getWhereIn('id', collaboratorsIds);
|
|
*/
|
|
async getWhereIn<K extends keyof ModelData>(
|
|
field: K,
|
|
values: ModelData[K][],
|
|
tx = this.kx,
|
|
notNullField: string | null = null
|
|
): Promise<ModelData[]> {
|
|
let queryPromise = tx(this.tableName)
|
|
.select(this.modelKeys)
|
|
.whereIn(field, values)
|
|
.orderBy('created_at', 'desc')
|
|
if (notNullField) {
|
|
queryPromise = queryPromise.whereNotNull(notNullField)
|
|
}
|
|
const rows: ModelData[] = await queryPromise
|
|
for (const row of rows) {
|
|
this.loader.prime(row.id, row)
|
|
}
|
|
return rows
|
|
}
|
|
|
|
async getAll(tx = this.kx): Promise<ModelData[]> {
|
|
const rows: ModelData[] = await tx(this.tableName)
|
|
.select(this.modelKeys)
|
|
.orderBy('created_at', 'desc')
|
|
for (const row of rows) {
|
|
this.loader.prime(row.id, row)
|
|
}
|
|
return rows
|
|
}
|
|
|
|
async create(data: CreateSet, tx = this.kx): Promise<ModelData> {
|
|
const [row]: ModelData[] = await tx(this.tableName)
|
|
.insert(data)
|
|
.returning('*')
|
|
this.loader.prime(row.id, row)
|
|
return row
|
|
}
|
|
|
|
async createMany(
|
|
data: CreateSet[],
|
|
tx: Knex.Transaction
|
|
): Promise<ModelData[]> {
|
|
const rows: ModelData[] = await tx
|
|
.batchInsert(this.tableName, data)
|
|
.returning('*')
|
|
for (const row of rows) {
|
|
this.loader.prime(row.id, row)
|
|
}
|
|
return rows
|
|
}
|
|
|
|
async update(id: string, data: UpdateSet, tx = this.kx): Promise<ModelData> {
|
|
const [row]: ModelData[] = await tx(this.tableName)
|
|
.update(data)
|
|
.where({ id })
|
|
.returning('*')
|
|
this.loader.prime(id, row)
|
|
return row
|
|
}
|
|
|
|
async delete(
|
|
id: string,
|
|
tx: Knex.Transaction
|
|
): Promise<ModelData | { error: DataModelError }> {
|
|
const [row]: ModelData[] = await tx(this.tableName)
|
|
.where({ id })
|
|
.delete()
|
|
.returning('*')
|
|
|
|
if (!row) return { error: DataModelError.notFound }
|
|
|
|
this.loader.clear(id)
|
|
return row
|
|
}
|
|
|
|
async deleteWhere<K extends keyof ModelData>(
|
|
params: Record<K, ModelData[K]>,
|
|
tx: Knex.Transaction
|
|
): Promise<ModelData[]> {
|
|
const rows: ModelData[] = await tx(this.tableName)
|
|
.andWhere(params)
|
|
.delete()
|
|
.returning('*')
|
|
|
|
for (const row of rows) {
|
|
this.loader.clear(row.id)
|
|
}
|
|
return rows
|
|
}
|
|
|
|
async deleteWhereIn<K extends keyof ModelData>(
|
|
params: Record<K, ModelData[K][]>,
|
|
tx: Knex.Transaction
|
|
): Promise<ModelData[]> {
|
|
const rows: ModelData[] = await tx(this.tableName)
|
|
.where((builder) => {
|
|
for (const field in params) {
|
|
builder.whereIn(field, params[field])
|
|
}
|
|
})
|
|
.delete()
|
|
.returning('*')
|
|
|
|
for (const row of rows) {
|
|
this.loader.clear(row.id)
|
|
}
|
|
return rows
|
|
}
|
|
}
|
|
|
|
export default DataModel
|