mirror of
https://github.com/nowcommunity/nowdatabase.git
synced 2026-10-12 19:13:52 +03:00
Refactor updatelogger; fix list item deletion
This commit is contained in:
@@ -35,17 +35,8 @@ router.put(
|
||||
if (validationErrors.length > 0) {
|
||||
return res.status(400).send({ validationErrors })
|
||||
}
|
||||
const result = await writeLocality(editedLocality)
|
||||
const result = await writeLocality(editedLocality, comment, references, req.user!.initials)
|
||||
return res.status(200).send({ id: result })
|
||||
// const result = await write(
|
||||
// editedLocality,
|
||||
// 'now_loc',
|
||||
// req.user!.initials,
|
||||
// comment ?? '',
|
||||
// editedLocality.lid ? 'update' : 'add',
|
||||
// references ?? []
|
||||
// )
|
||||
// return res.status(200).send(result ? { id: result } : { error: 'error' })
|
||||
}
|
||||
)
|
||||
|
||||
|
||||
@@ -20,7 +20,8 @@ router.get('/:id', async (req, res) => {
|
||||
|
||||
router.put('/', async (req: Request<object, object, { species: SpeciesDetailsType & EditMetaData }>, res) => {
|
||||
const editedSpecies = req.body.species
|
||||
const id = await writeSpecies(editedSpecies)
|
||||
const { comment, references } = editedSpecies
|
||||
const id = await writeSpecies(editedSpecies, comment, references, req.user!.initials)
|
||||
return res.status(200).send({ id })
|
||||
})
|
||||
|
||||
|
||||
@@ -1,15 +1,29 @@
|
||||
import { EditDataType, LocalityDetailsType } from '../../../../frontend/src/backendTypes'
|
||||
import { EditDataType, LocalityDetailsType, Reference } from '../../../../frontend/src/backendTypes'
|
||||
import { NOW_DB_NAME } from '../../utils/config'
|
||||
import { WriteHandler } from '../writeOperations/writeHandler'
|
||||
import { getFieldsOfTables } from '../../utils/db'
|
||||
|
||||
export const writeLocality = async (locality: EditDataType<LocalityDetailsType>) => {
|
||||
const writeHandler = new WriteHandler(
|
||||
NOW_DB_NAME,
|
||||
'now_loc',
|
||||
'lid',
|
||||
getFieldsOfTables(['now_loc', 'now_ls', 'com_species', 'now_mus', 'now_ss', 'now_coll_meth', 'now_syn_loc'])
|
||||
)
|
||||
export const writeLocality = async (
|
||||
locality: EditDataType<LocalityDetailsType>,
|
||||
comment: string | undefined,
|
||||
references: Reference[] | undefined,
|
||||
authorizer: string
|
||||
) => {
|
||||
const writeHandler = new WriteHandler({
|
||||
dbName: NOW_DB_NAME,
|
||||
table: 'now_loc',
|
||||
idColumn: 'lid',
|
||||
allowedColumns: getFieldsOfTables([
|
||||
'now_loc',
|
||||
'now_ls',
|
||||
'com_species',
|
||||
'now_mus',
|
||||
'now_ss',
|
||||
'now_coll_meth',
|
||||
'now_syn_loc',
|
||||
]),
|
||||
type: locality.lid ? 'update' : 'add',
|
||||
})
|
||||
await writeHandler.start()
|
||||
|
||||
if (!locality.lid) {
|
||||
@@ -29,12 +43,12 @@ export const writeLocality = async (locality: EditDataType<LocalityDetailsType>)
|
||||
species.species_id = species_id as number
|
||||
}
|
||||
|
||||
await writeHandler.upsertList('now_ls', locality.now_ls, ['lid', 'species_id'])
|
||||
await writeHandler.upsertList('now_mus', locality.now_mus, ['lid', 'museum'])
|
||||
await writeHandler.upsertList('now_ss', locality.now_ss, ['lid', 'sed_struct'])
|
||||
await writeHandler.upsertList('now_coll_meth', locality.now_coll_meth, ['lid', 'coll_meth'])
|
||||
await writeHandler.upsertList('now_syn_loc', locality.now_syn_loc, ['lid', 'syn_id'])
|
||||
await writeHandler.logUpdates()
|
||||
await writeHandler.applyListChanges('now_ls', locality.now_ls, ['lid', 'species_id'])
|
||||
await writeHandler.applyListChanges('now_mus', locality.now_mus, ['lid', 'museum'])
|
||||
await writeHandler.applyListChanges('now_ss', locality.now_ss, ['lid', 'sed_struct'])
|
||||
await writeHandler.applyListChanges('now_coll_meth', locality.now_coll_meth, ['lid', 'coll_meth'])
|
||||
await writeHandler.applyListChanges('now_syn_loc', locality.now_syn_loc, ['lid', 'syn_id'])
|
||||
await writeHandler.logUpdatesAndComplete(comment ?? '', references ?? [], authorizer)
|
||||
await writeHandler.end()
|
||||
return locality.lid
|
||||
}
|
||||
|
||||
@@ -1,15 +1,21 @@
|
||||
import { EditDataType, SpeciesDetailsType } from '../../../../frontend/src/backendTypes'
|
||||
import { EditDataType, Reference, SpeciesDetailsType } from '../../../../frontend/src/backendTypes'
|
||||
import { NOW_DB_NAME } from '../../utils/config'
|
||||
import { WriteHandler } from '../writeOperations/writeHandler'
|
||||
import { getFieldsOfTables } from '../../utils/db'
|
||||
|
||||
export const writeSpecies = async (species: EditDataType<SpeciesDetailsType>) => {
|
||||
const writeHandler = new WriteHandler(
|
||||
NOW_DB_NAME,
|
||||
'com_species',
|
||||
'species_id',
|
||||
getFieldsOfTables(['com_species', 'now_ls'])
|
||||
)
|
||||
export const writeSpecies = async (
|
||||
species: EditDataType<SpeciesDetailsType>,
|
||||
comment: string | undefined,
|
||||
references: Reference[] | undefined,
|
||||
authorizer: string
|
||||
) => {
|
||||
const writeHandler = new WriteHandler({
|
||||
dbName: NOW_DB_NAME,
|
||||
table: 'com_species',
|
||||
idColumn: 'species_id',
|
||||
allowedColumns: getFieldsOfTables(['com_species', 'now_ls']),
|
||||
type: species.species_id ? 'update' : 'add',
|
||||
})
|
||||
|
||||
await writeHandler.start()
|
||||
|
||||
@@ -22,10 +28,10 @@ export const writeSpecies = async (species: EditDataType<SpeciesDetailsType>) =>
|
||||
|
||||
writeHandler.idValue = species.species_id
|
||||
|
||||
await writeHandler.upsertList('now_ls', species.now_ls, ['lid', 'species_id'])
|
||||
await writeHandler.upsertList('com_taxa_synonym', species.com_taxa_synonym, ['synonym_id', 'species_id'])
|
||||
console.log(JSON.stringify(writeHandler.writeList))
|
||||
await writeHandler.logUpdates()
|
||||
await writeHandler.applyListChanges('now_ls', species.now_ls, ['lid', 'species_id'])
|
||||
await writeHandler.applyListChanges('com_taxa_synonym', species.com_taxa_synonym, ['synonym_id', 'species_id'])
|
||||
await writeHandler.logUpdatesAndComplete(comment ?? '', references ?? [], authorizer)
|
||||
await writeHandler.end()
|
||||
|
||||
return species.species_id
|
||||
}
|
||||
|
||||
@@ -1,60 +0,0 @@
|
||||
import { PoolConnection } from 'mariadb'
|
||||
import { LOG_DB_NAME, NOW_DB_NAME } from '../../utils/config'
|
||||
import { ActionType, LogRow, debugLog, printJSON } from './writeUtils'
|
||||
|
||||
const tableToId: Record<string, string> = { now_lau: 'lid', now_sau: 'species_id', now_tau: 'tu_name', now_bau: 'bid' }
|
||||
const getUpdateIdField = (tableName: string) => `${tableName[4]}uid`
|
||||
|
||||
export const createUpdateEntry = async (
|
||||
conn: PoolConnection,
|
||||
prefix: 'sau' | 'tau' | 'lau' | 'bau',
|
||||
coordinator: string,
|
||||
authorizer: string,
|
||||
comment: string,
|
||||
id: string | number
|
||||
) => {
|
||||
const table = `now_${prefix}`
|
||||
const idField = getUpdateIdField(table)
|
||||
const result: object[] = await conn.query(
|
||||
`INSERT INTO ${NOW_DB_NAME}.${table} (${prefix}_coordinator, ${prefix}_authorizer, ${tableToId[table]}, ${prefix}_date, ${prefix}_comment) VALUES (?, ?, ?, ?, ?) RETURNING ${table}.${idField}`,
|
||||
[coordinator, authorizer, id, new Date(), comment]
|
||||
)
|
||||
debugLog(`createUpdateEntry result before returning: ${printJSON(result)}`)
|
||||
return result?.[0]?.[idField as keyof object] as number
|
||||
}
|
||||
|
||||
export const writeLogRows = async (conn: PoolConnection, logRows: LogRow[], authorizer: string) => {
|
||||
const actionTypeToDbFormat: Record<ActionType, number> = {
|
||||
delete: 1,
|
||||
add: 2,
|
||||
update: 3,
|
||||
}
|
||||
const updateRows = logRows.map(logRow => ({
|
||||
event_time: new Date(),
|
||||
user_name: authorizer,
|
||||
server_name: 'sysbiol',
|
||||
table_name: logRow.table,
|
||||
pk_data: logRow.pkData,
|
||||
column_name: logRow.column,
|
||||
// 1 = delete, 2 = add, 3 = update
|
||||
log_action: actionTypeToDbFormat[logRow.type],
|
||||
old_data: logRow.oldValue,
|
||||
new_data: logRow.value,
|
||||
suid: logRow.suid ?? null,
|
||||
luid: logRow.luid ?? null,
|
||||
buid: logRow.buid ?? null,
|
||||
tuid: logRow.tuid ?? null,
|
||||
}))
|
||||
|
||||
debugLog(`updateRows: ${printJSON(updateRows)}`)
|
||||
for (const row of updateRows) {
|
||||
const columnsAndValues = Object.entries(row)
|
||||
const columns = columnsAndValues.map(([column]) => column)
|
||||
const values = columnsAndValues.map(([, values]) => values)
|
||||
await conn.query(
|
||||
`INSERT INTO ${LOG_DB_NAME}.log (${columns.join(', ')}) VALUES (${columns.map(() => '?').join(', ')})`,
|
||||
values
|
||||
)
|
||||
debugLog(`Writing into log-db: ${printJSON(row)}`)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,26 +1,11 @@
|
||||
import { PoolConnection } from 'mariadb'
|
||||
import { EditDataType, Reference, ReferenceDetailsType } from '../../../../frontend/src/backendTypes'
|
||||
import { COORDINATOR, NOW_DB_NAME, RUNNING_ENV } from '../../utils/config'
|
||||
import { NOW_DB_NAME, RUNNING_ENV } from '../../utils/config'
|
||||
import { nowDb } from '../../utils/db'
|
||||
import { logger } from '../../utils/logger'
|
||||
import { createUpdateEntry, writeLogRows } from './updateAndLog'
|
||||
import { writeLogRows } from './updateAndLog'
|
||||
import { WriteContext } from './write'
|
||||
|
||||
const tableNameToPrefix = {
|
||||
now_loc: 'lau',
|
||||
com_species: 'sau',
|
||||
now_time_unit: 'tau',
|
||||
now_tu_bound: 'bau',
|
||||
} as Record<PrimaryTables, 'sau' | 'tau' | 'lau' | 'bau'>
|
||||
|
||||
/* Updates to certain tables have to be logged in a different table
|
||||
than the one the user edited. Sometimes several tables. For example,
|
||||
user editing locality and changing now_ls will require update entry
|
||||
to both now_loc and com_species. If this table doesn't have entry for a
|
||||
table, assume it goes to the "main" update entry e.g. if user edited locality, now_loc. */
|
||||
const tableToUpdateTargets = {
|
||||
now_ls: ['now_loc', 'com_species'],
|
||||
} as Record<AllowedTables, PrimaryTables[] | undefined>
|
||||
import { UpdateEntry } from '../writeOperations/updateLogHandler'
|
||||
|
||||
export type PrimaryTables = 'now_loc' | 'com_species' | 'now_time_unit' | 'now_tu_bound'
|
||||
|
||||
@@ -35,14 +20,6 @@ export type LogRow = Item & {
|
||||
type: ActionType
|
||||
}
|
||||
|
||||
type UpdateEntry = {
|
||||
table: PrimaryTables
|
||||
logRows: LogRow[]
|
||||
type: ActionType
|
||||
id: string | number // Id of the item that was changed
|
||||
entryId?: number // Id of the created update entry
|
||||
}
|
||||
|
||||
export const logAllUpdates = async (
|
||||
writeContext: WriteContext,
|
||||
tableName: PrimaryTables,
|
||||
@@ -127,62 +104,12 @@ export const logAllUpdates = async (
|
||||
if (logRow.type !== 'delete') return logRow
|
||||
return { ...logRow, oldValue: logRow.value, value: null }
|
||||
}
|
||||
|
||||
const logRows = mergeLogRows(updateEntries).map(logRow => fixDeleteRows(logRow))
|
||||
|
||||
await writeLogRows(writeContext.connection, logRows, userInitials)
|
||||
}
|
||||
|
||||
const getUpdateIdField = (tableName: PrimaryTables) => `${tableName[4]}uid` as 'buid' | 'luid' | 'suid' | 'tuid'
|
||||
|
||||
const writeReferences = async (updateEntries: UpdateEntry[], connection: PoolConnection, references: Reference[]) => {
|
||||
const referenceIds = references.map(ref => ref.rid)
|
||||
for (const updateEntry of updateEntries) {
|
||||
const id = updateEntry.entryId
|
||||
const idField = getUpdateIdField(updateEntry.table)
|
||||
const referenceJoinTable = `now_${idField[0]}r`
|
||||
for (const referenceId of referenceIds) {
|
||||
await connection.query(`INSERT INTO ${NOW_DB_NAME}.${referenceJoinTable} (${idField}, rid) VALUES (?, ?)`, [
|
||||
id,
|
||||
referenceId,
|
||||
])
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
const mergeLogRows = (updateEntries: UpdateEntry[]) => {
|
||||
const getLogRowKey = (logRow: LogRow) => {
|
||||
return `${logRow.column}-${logRow.value}-${logRow.oldValue}-${logRow.table}-${logRow.pkData}`
|
||||
}
|
||||
|
||||
const logRowMap: Record<string, LogRow> = {}
|
||||
|
||||
// Iterate through updateEntries. Collect logrows into map. LogRows with identical data may be in multiple
|
||||
// updateEntries, such as now_ls rows in locality and species. These should be merged so that only one is left,
|
||||
// but with both id's (luid and suid) found in the updateEntries.
|
||||
for (const updateEntry of updateEntries) {
|
||||
const idFieldName = getUpdateIdField(updateEntry.table)
|
||||
for (const logRow of updateEntry.logRows) {
|
||||
const key = getLogRowKey(logRow)
|
||||
logRowMap[key] = { ...(logRowMap[key] ? logRowMap[key] : logRow), [idFieldName]: updateEntry.entryId }
|
||||
}
|
||||
}
|
||||
return Object.values(logRowMap)
|
||||
}
|
||||
|
||||
const writeUpdateEntries = async (
|
||||
updateEntries: UpdateEntry[],
|
||||
connection: PoolConnection,
|
||||
authorizer: string,
|
||||
comment: string
|
||||
) => {
|
||||
for (const updateEntry of updateEntries) {
|
||||
const prefix = tableNameToPrefix[updateEntry.table]
|
||||
const createdId = await createUpdateEntry(connection, prefix, COORDINATOR, authorizer, comment, updateEntry.id)
|
||||
updateEntry.entryId = createdId
|
||||
}
|
||||
return updateEntries
|
||||
}
|
||||
|
||||
// Some tables are not logged at all.
|
||||
// TODO dont even use write() for these, make their own services.
|
||||
export const tablesToNotLog = ['com_mlist']
|
||||
|
||||
@@ -10,6 +10,7 @@ const createWhereClause = (idColumns: string[]) => `${idColumns.map(idColumn =>
|
||||
export class DatabaseHandler {
|
||||
connection?: PoolConnection
|
||||
dbName: string
|
||||
|
||||
constructor(dbName: string) {
|
||||
this.dbName = dbName
|
||||
}
|
||||
|
||||
@@ -0,0 +1,221 @@
|
||||
import { Reference } from '../../../../frontend/src/backendTypes'
|
||||
import { COORDINATOR, LOG_DB_NAME } from '../../utils/config'
|
||||
import { ActionType, AllowedTables, Item, LogRow, PrimaryTables, WriteItem } from '../write/writeUtils'
|
||||
import { WriteHandler } from './writeHandler'
|
||||
|
||||
const tableNameToPrefix = {
|
||||
now_loc: 'lau',
|
||||
com_species: 'sau',
|
||||
now_time_unit: 'tau',
|
||||
now_tu_bound: 'bau',
|
||||
} as Record<PrimaryTables, 'sau' | 'tau' | 'lau' | 'bau'>
|
||||
|
||||
/* Updates to certain tables have to be logged in a different table
|
||||
than the one the user edited. Sometimes several tables. For example,
|
||||
user editing locality and changing now_ls will require update entry
|
||||
to both now_loc and com_species. If this table doesn't have entry for a
|
||||
table, assume it goes to the "main" update entry e.g. if user edited locality, now_loc. */
|
||||
const tableToUpdateTargets = {
|
||||
now_ls: ['now_loc', 'com_species'],
|
||||
} as Record<AllowedTables, PrimaryTables[] | undefined>
|
||||
|
||||
export type UpdateEntry = {
|
||||
table: PrimaryTables
|
||||
logRows: LogRow[]
|
||||
type: ActionType
|
||||
id: string | number // Id of the item that was changed
|
||||
entryId?: number // Id of the created update entry
|
||||
}
|
||||
|
||||
export const logAllUpdates = async (
|
||||
writeHandler: WriteHandler,
|
||||
writeList: WriteItem[],
|
||||
tableName: PrimaryTables,
|
||||
id: string | number,
|
||||
userInitials: string,
|
||||
comment: string,
|
||||
references: Reference[],
|
||||
type: ActionType
|
||||
) => {
|
||||
const updateEntries: UpdateEntry[] = []
|
||||
const getIdColumn = (targetTable: AllowedTables) => {
|
||||
if (targetTable === 'com_species') return 'species_id'
|
||||
if (targetTable === 'now_mus') return 'museum'
|
||||
if (targetTable === 'now_ls') return tableName === 'now_loc' ? 'species_id' : 'lid'
|
||||
if (targetTable === 'now_loc') return 'lid'
|
||||
if (targetTable === 'now_ss') return 'sed_struct'
|
||||
if (targetTable === 'com_taxa_synonym') return 'synonym_id'
|
||||
if (targetTable === 'now_coll_meth') return 'coll_meth'
|
||||
throw new Error(`No id column found for ${targetTable}`)
|
||||
}
|
||||
|
||||
const getSecondaryPkData = (table: AllowedTables, items: Item[]) => {
|
||||
if (table === tableName) return ''
|
||||
const secondaryId = items.find(item => item.column === getIdColumn(table))?.value
|
||||
if (!secondaryId) throw new Error(`No id value found for ${table}`)
|
||||
return `${secondaryId.toString().length}.${secondaryId};`
|
||||
}
|
||||
|
||||
const rootUpdateEntry: UpdateEntry = {
|
||||
type,
|
||||
table: tableName,
|
||||
id,
|
||||
logRows: writeList.flatMap(writeItem =>
|
||||
writeItem.items.map(item => ({
|
||||
...item,
|
||||
type: writeItem.type,
|
||||
pkData: `${id.toString().length}.${id};${getSecondaryPkData(writeItem.table, writeItem.items)}`,
|
||||
}))
|
||||
),
|
||||
}
|
||||
|
||||
updateEntries.push(rootUpdateEntry)
|
||||
|
||||
// Create "secondary" update-entries. This map holds by the primary table and id, e.g. secondaryUpdateEntries['species_id'][10001]
|
||||
const secondaryUpdateEntries = {} as Record<PrimaryTables, Record<string | number, UpdateEntry>>
|
||||
|
||||
for (const writeListItem of writeList) {
|
||||
const targetTables = tableToUpdateTargets[writeListItem.table] ?? [tableName]
|
||||
for (const targetTable of targetTables) {
|
||||
if (targetTable === tableName) continue
|
||||
// find id of current entry. it will be the other id than the one of this table, e.g.
|
||||
// if we are editing now_loc and this entry is triggered by now_ls (entry for com_species),
|
||||
// the id will be "species_id", not "lid".
|
||||
const idColumn = getIdColumn(targetTable)
|
||||
const secondaryId = writeListItem.items.find(item => item.column === idColumn)?.value as string | number
|
||||
if (!secondaryId)
|
||||
throw new Error(`Error when creating update entries: id not found for targetTable: ${targetTable}`)
|
||||
if (!secondaryUpdateEntries[targetTable]) secondaryUpdateEntries[targetTable] = {}
|
||||
const pkData = `${id.toString().length}.${id};${secondaryId.toString().length}.${secondaryId};`
|
||||
secondaryUpdateEntries[targetTable][secondaryId] = {
|
||||
id: secondaryId,
|
||||
table: targetTable,
|
||||
type: writeListItem.type,
|
||||
logRows: writeListItem.items.map(item => ({ ...item, pkData, type: writeListItem.type })),
|
||||
}
|
||||
}
|
||||
}
|
||||
for (const tableUpdates of Object.values(secondaryUpdateEntries)) {
|
||||
for (const singleUpdateOfTable of Object.values(tableUpdates)) {
|
||||
updateEntries.push(singleUpdateOfTable)
|
||||
}
|
||||
}
|
||||
|
||||
// Write the update-entries: This inserts the id's that are created into the objects.
|
||||
await writeUpdateEntries(writeHandler, updateEntries, userInitials, comment)
|
||||
|
||||
await writeReferences(writeHandler, updateEntries, references)
|
||||
|
||||
// The write has value and oldValue swapped for 'delete' type logRows due to reasons.
|
||||
// It is simpler to fix it here than changing how write works, because values are used to find id's above.
|
||||
const fixDeleteRows = (logRow: LogRow) => {
|
||||
if (logRow.type !== 'delete') return logRow
|
||||
return { ...logRow, oldValue: logRow.value, value: null }
|
||||
}
|
||||
const logRows = mergeLogRows(updateEntries).map(logRow => fixDeleteRows(logRow))
|
||||
|
||||
await writeLogRows(writeHandler, logRows, userInitials)
|
||||
}
|
||||
|
||||
const mergeLogRows = (updateEntries: UpdateEntry[]) => {
|
||||
const getLogRowKey = (logRow: LogRow) => {
|
||||
return `${logRow.column}-${logRow.value}-${logRow.oldValue}-${logRow.table}-${logRow.pkData}`
|
||||
}
|
||||
|
||||
const logRowMap: Record<string, LogRow> = {}
|
||||
|
||||
// Iterate through updateEntries. Collect logrows into map. LogRows with identical data may be in multiple
|
||||
// updateEntries, such as now_ls rows in locality and species. These should be merged so that only one is left,
|
||||
// but with both id's (luid and suid) found in the updateEntries.
|
||||
for (const updateEntry of updateEntries) {
|
||||
const idFieldName = getUpdateIdField(updateEntry.table)
|
||||
for (const logRow of updateEntry.logRows) {
|
||||
const key = getLogRowKey(logRow)
|
||||
logRowMap[key] = { ...(logRowMap[key] ? logRowMap[key] : logRow), [idFieldName]: updateEntry.entryId }
|
||||
}
|
||||
}
|
||||
return Object.values(logRowMap)
|
||||
}
|
||||
|
||||
const writeUpdateEntries = async (
|
||||
writeHandler: WriteHandler,
|
||||
updateEntries: UpdateEntry[],
|
||||
authorizer: string,
|
||||
comment: string
|
||||
) => {
|
||||
for (const updateEntry of updateEntries) {
|
||||
const prefix = tableNameToPrefix[updateEntry.table]
|
||||
const createdId = await createUpdateEntry(writeHandler, prefix, COORDINATOR, authorizer, comment, updateEntry.id)
|
||||
updateEntry.entryId = createdId
|
||||
}
|
||||
return updateEntries
|
||||
}
|
||||
|
||||
const writeReferences = async (writeHandler: WriteHandler, updateEntries: UpdateEntry[], references: Reference[]) => {
|
||||
const referenceIds = references.map(ref => ref.rid)
|
||||
for (const updateEntry of updateEntries) {
|
||||
const id = updateEntry.entryId
|
||||
const idField = getUpdateIdField(updateEntry.table)
|
||||
const referenceJoinTable = `now_${idField[0]}r` as AllowedTables
|
||||
for (const referenceId of referenceIds) {
|
||||
await writeHandler.createObject(referenceJoinTable, { [idField]: id, rid: referenceId }, [idField, 'rid'])
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
const tableToId: Record<string, string> = { now_lau: 'lid', now_sau: 'species_id', now_tau: 'tu_name', now_bau: 'bid' }
|
||||
const getUpdateIdField = (tableName: string) => `${tableName[4]}uid`
|
||||
|
||||
export const createUpdateEntry = async (
|
||||
writeHandler: WriteHandler,
|
||||
prefix: 'sau' | 'tau' | 'lau' | 'bau',
|
||||
coordinator: string,
|
||||
authorizer: string,
|
||||
comment: string,
|
||||
id: string | number
|
||||
) => {
|
||||
const table = `now_${prefix}` as AllowedTables
|
||||
const idField = getUpdateIdField(table)
|
||||
const result = await writeHandler.createObject(
|
||||
table,
|
||||
{
|
||||
[`${prefix}_coordinator`]: coordinator,
|
||||
[`${prefix}_authorizer`]: authorizer,
|
||||
[`${prefix}_date`]: new Date(),
|
||||
[tableToId[table]]: id,
|
||||
[`${prefix}_comment`]: comment,
|
||||
},
|
||||
[idField]
|
||||
)
|
||||
|
||||
return result[idField as keyof object] as number
|
||||
}
|
||||
|
||||
export const writeLogRows = async (writeHandler: WriteHandler, logRows: LogRow[], authorizer: string) => {
|
||||
const actionTypeToDbFormat: Record<ActionType, number> = {
|
||||
delete: 1,
|
||||
add: 2,
|
||||
update: 3,
|
||||
}
|
||||
const updateRows = logRows.map(logRow => ({
|
||||
event_time: new Date(),
|
||||
user_name: authorizer,
|
||||
server_name: 'sysbiol',
|
||||
table_name: logRow.table,
|
||||
pk_data: logRow.pkData,
|
||||
column_name: logRow.column,
|
||||
// 1 = delete, 2 = add, 3 = update
|
||||
log_action: actionTypeToDbFormat[logRow.type],
|
||||
old_data: logRow.oldValue,
|
||||
new_data: logRow.value,
|
||||
suid: logRow.suid ?? null,
|
||||
luid: logRow.luid ?? null,
|
||||
buid: logRow.buid ?? null,
|
||||
tuid: logRow.tuid ?? null,
|
||||
}))
|
||||
|
||||
writeHandler.dbName = LOG_DB_NAME
|
||||
for (const row of updateRows) {
|
||||
await writeHandler.createObject('log' as AllowedTables, row, ['log_id'])
|
||||
}
|
||||
}
|
||||
@@ -1,8 +1,17 @@
|
||||
import { RowState } from '../../../../frontend/src/backendTypes'
|
||||
import { Reference, RowState } from '../../../../frontend/src/backendTypes'
|
||||
import { logger } from '../../utils/logger'
|
||||
import { AllowedTables, DbValue, Item, logAllUpdates, PrimaryTables, WriteItem } from '../write/writeUtils'
|
||||
import { logAllUpdates } from './updateLogHandler'
|
||||
import { DatabaseHandler, DbWriteItem } from './databaseHandler'
|
||||
import { fixBoolean, getItemList, valueIsDifferent } from './utils'
|
||||
import { ActionType, AllowedTables, DbValue, Item, PrimaryTables, WriteItem } from '../write/writeUtils'
|
||||
|
||||
type WriteHandlerParams = {
|
||||
dbName: string
|
||||
table: PrimaryTables
|
||||
idColumn: string
|
||||
allowedColumns: string[]
|
||||
type: ActionType
|
||||
}
|
||||
|
||||
/* Handles writing logic that are agnostic of datatype, but not directly operations to db. */
|
||||
/* Usage: after constructor, initialize with writeHandler.start. Assign value to idValue whever you have it. */
|
||||
@@ -12,12 +21,14 @@ export class WriteHandler extends DatabaseHandler {
|
||||
idColumn: string
|
||||
idValue?: string | number
|
||||
allowedColumns: string[]
|
||||
type: ActionType
|
||||
|
||||
constructor(dbName: string, table: PrimaryTables, idColumn: string, allowedColumns: string[]) {
|
||||
constructor({ dbName, table, type, idColumn, allowedColumns }: WriteHandlerParams) {
|
||||
super(dbName)
|
||||
this.table = table
|
||||
this.idColumn = idColumn
|
||||
this.allowedColumns = allowedColumns
|
||||
this.type = type
|
||||
}
|
||||
|
||||
async createRow<T extends Record<string, DbValue>>(table: AllowedTables, items: DbWriteItem[], ids: string[]) {
|
||||
@@ -72,6 +83,18 @@ export class WriteHandler extends DatabaseHandler {
|
||||
return await this.updateRow<Record<keyof T, DbValue>>(table, itemsToWrite, ids)
|
||||
}
|
||||
|
||||
async deleteObject<T extends Record<string, unknown>>(table: AllowedTables, object: T, idColumns: string[]) {
|
||||
// Get id's, log as deleted, delete
|
||||
const ids = idColumns.map(idColumn => ({ column: idColumn, value: object[idColumn] as DbValue }))
|
||||
this.writeList.push({
|
||||
table,
|
||||
type: 'delete',
|
||||
// oldValue and value are swapped here, but they are logged correctly in update.
|
||||
items: ids.map(id => ({ value: id.value, oldValue: null, table, column: id.column })),
|
||||
})
|
||||
return await this.delete(table, ids)
|
||||
}
|
||||
|
||||
async upsertObject<T extends Record<string, unknown>>(table: AllowedTables, object: T, idColumns: string[]) {
|
||||
if (idColumns.includes(this.idColumn) && this.idValue && !object[this.idColumn]) {
|
||||
;(object as Record<string, unknown>)[this.idColumn] = this.idValue
|
||||
@@ -80,32 +103,25 @@ export class WriteHandler extends DatabaseHandler {
|
||||
return await this.updateObject<T>(table, object, idColumns)
|
||||
}
|
||||
|
||||
async upsertList<T extends { rowState?: RowState }>(table: AllowedTables, objects: T[], idColumns: string[]) {
|
||||
/* Takes in a list of objects which are of Editable type, meaning they have a rowState field. Either adds or removes a join relation. */
|
||||
async applyListChanges<T extends { rowState?: RowState }>(table: AllowedTables, objects: T[], idColumns: string[]) {
|
||||
for (const object of objects) {
|
||||
const rowState = object.rowState
|
||||
if (rowState === 'new') {
|
||||
await this.createObject(table, object, idColumns)
|
||||
} else if (rowState === 'removed') {
|
||||
await this.delete(
|
||||
table,
|
||||
idColumns.map(idColumn => ({ column: idColumn, value: object[idColumn as keyof T] as DbValue }))
|
||||
)
|
||||
await this.deleteObject(table, object, idColumns)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
async logUpdates() {
|
||||
async logUpdatesAndComplete(comment: string, references: Reference[], authorizer: string) {
|
||||
if (this.writeList.length === 0) {
|
||||
logger.info(`No changes detected, skipping logging.`)
|
||||
return
|
||||
}
|
||||
await logAllUpdates(
|
||||
{ connection: this.connection!, username: 'ArK', writeList: this.writeList, type: 'add' },
|
||||
this.table,
|
||||
'ArK',
|
||||
'comment',
|
||||
this.idValue!,
|
||||
[]
|
||||
)
|
||||
console.log(JSON.stringify(this.writeList))
|
||||
await logAllUpdates(this, this.writeList, this.table, this.idValue!, authorizer, comment, references, this.type)
|
||||
await this.end()
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user