mirror of
https://github.com/nowcommunity/nowdatabase.git
synced 2026-10-12 19:13:52 +03:00
WIP, made to work with object corresponding to db in frontend too
Seems to work pretty good, but not fully tested, & only locality stuff tested, only prints does not write to db
This commit is contained in:
@@ -102,8 +102,7 @@ export const processLocalityForEdit = async (editedLocality: EditDataType<Locali
|
||||
const oldLocality = await getLocalityDetails(fixedEditedLocality.lid!)
|
||||
const validationErrors = validateEntireLocality(editedLocality)
|
||||
if (validationErrors.length > 0) return { validationErrors }
|
||||
// const testLoc = await getLocalityDetails(fixedEditedLocality.lid!)
|
||||
const result = write(editedLocality, 'now_loc', oldLocality!)
|
||||
const result = await write(editedLocality, 'now_loc', oldLocality!)
|
||||
return { result }
|
||||
}
|
||||
|
||||
|
||||
@@ -13,50 +13,115 @@ import {
|
||||
TimeBoundDetailsType,
|
||||
TimeUnitDetailsType,
|
||||
} from '../../../../frontend/src/backendTypes'
|
||||
import { revertFieldNames } from './writeUtils'
|
||||
import { nowDb } from '../../utils/db'
|
||||
import { logger } from '../../utils/logger'
|
||||
import { isEmptyValue } from './writeUtils'
|
||||
|
||||
const DEBUG_MODE = true
|
||||
|
||||
const debugLog = (msg: string) => {
|
||||
if (DEBUG_MODE) logger.info(msg)
|
||||
}
|
||||
|
||||
type WriteFunction = (
|
||||
data: EditDataType<LocalityDetailsType | SpeciesDetailsType | TimeUnitDetailsType | TimeBoundDetailsType>,
|
||||
tableName: string,
|
||||
oldObject: EditDataType<LocalityDetailsType | SpeciesDetailsType | TimeUnitDetailsType | TimeBoundDetailsType>
|
||||
) => string
|
||||
) => Promise<string>
|
||||
|
||||
const ids = { now_loc: 'lid', now_ls: 'species_id' }
|
||||
const ids = {
|
||||
now_loc: ['lid'],
|
||||
now_ls: ['lid', 'species_id'],
|
||||
now_proj: ['pid'],
|
||||
now_plr: ['lid', 'pid'],
|
||||
now_syn_loc: ['syn_id'],
|
||||
now_mus: ['lid', 'museum'],
|
||||
now_coll_meth: ['lid', 'coll_meth'],
|
||||
com_mlist: ['museum'],
|
||||
com_species: ['species_id'],
|
||||
now_ss: ['lid', 'sed_struct'],
|
||||
}
|
||||
|
||||
type Item = { column: string; value: any }
|
||||
type WriteItem = { table: string; type: 'add' | 'update' | 'delete'; items: Item[] }
|
||||
|
||||
const getFindFunction = (tableName: string, newItem: any) => (item: any) => {
|
||||
const matchFields = (fields: string[]) => {
|
||||
return !fields.find(f => newItem[f] !== item[f])
|
||||
}
|
||||
const matches = { now_ls: ['species_id', 'lid'], now_proj: ['pid'], now_plr: ['lid', 'pid'], now_syn_loc: ['syn_id'] }
|
||||
if (!matches[tableName]) console.log('Alert matcher missing for ', tableName)
|
||||
return matchFields(matches[tableName])
|
||||
if (!ids[tableName]) throw new Error(`Matcher missing for ${tableName}`)
|
||||
return !ids[tableName].find(f => newItem[f] !== item[f])
|
||||
}
|
||||
|
||||
// TODO change ignored fields to allowed fields. form those by taking prisma.model.fields, and adding the accepted relation fields.
|
||||
const ignoredFields = ['now_lau']
|
||||
export const write: WriteFunction = (data, tableName, oldObject) => {
|
||||
console.log('Write')
|
||||
const allowedFields = {}
|
||||
const addFieldsToAllowed = fields =>
|
||||
fields.forEach(f => {
|
||||
allowedFields[f] = true
|
||||
})
|
||||
|
||||
const supportedTables = [
|
||||
'now_loc',
|
||||
'now_ls',
|
||||
'now_proj',
|
||||
'now_plr',
|
||||
'now_syn_loc',
|
||||
'now_mus',
|
||||
'now_coll_meth',
|
||||
'com_mlist',
|
||||
'com_species',
|
||||
'now_ss',
|
||||
]
|
||||
// Adding tables and fields to allowed field values
|
||||
addFieldsToAllowed(supportedTables)
|
||||
supportedTables.forEach(table => addFieldsToAllowed(Object.keys(nowDb[table].fields)))
|
||||
// Remove some fields that should be ignored
|
||||
allowedFields['now_lau'] = false
|
||||
allowedFields['now_sau'] = false
|
||||
allowedFields['now_tau'] = false
|
||||
allowedFields['now_bau'] = false
|
||||
|
||||
export const write: WriteFunction = async (data, tableName, oldObject) => {
|
||||
debugLog('Write')
|
||||
const writeList: WriteItem[] = []
|
||||
const writeTable = (obj: object, tableName: string, oldObj: object | null) => {
|
||||
const allFields = Object.keys(obj).filter(f => !ignoredFields.includes(f))
|
||||
const fields = allFields.filter(f => typeof obj[f as keyof object] !== 'object' || obj[f as keyof object] === null)
|
||||
const relationFields = allFields.filter(
|
||||
f => typeof obj[f as keyof object] === 'object' && obj[f as keyof object] !== null
|
||||
const writeTable = async (obj: object, tableName: string) => {
|
||||
debugLog(`writeTable ${tableName} ${Object.keys(obj)}`)
|
||||
const where = ids[tableName].reduce((acc, cur) => {
|
||||
if (obj[cur] !== undefined) return { ...acc, [cur]: obj[cur] }
|
||||
return acc
|
||||
}, {})
|
||||
let whereObject = { [ids[tableName].join('_')]: where }
|
||||
if (ids[tableName].length === 1) whereObject = { [ids[tableName][0]]: obj[ids[tableName][0]] }
|
||||
debugLog(ids[tableName], JSON.stringify(where))
|
||||
const oldObj =
|
||||
ids[tableName] && Object.keys(where).length === ids[tableName].length
|
||||
? await nowDb[tableName].findUnique({
|
||||
where: whereObject,
|
||||
})
|
||||
: null
|
||||
debugLog(`old obj: ${!!oldObj} ${Object.keys(whereObject).length} ${ids[tableName].length}`)
|
||||
const allFields = Object.keys(obj).filter(f => allowedFields[f])
|
||||
const basicFields = allFields.filter(
|
||||
f => typeof obj[f as keyof object] !== 'object' || obj[f as keyof object] === null
|
||||
)
|
||||
// console.log('fields: ', JSON.stringify(fields))
|
||||
// console.log('relations: ', JSON.stringify(relationFields))
|
||||
const arrayFields = allFields.filter(f => Array.isArray(obj[f as keyof object]))
|
||||
const objectFields = allFields.filter(f => typeof obj[f] === 'object' && obj[f] !== null && !Array.isArray(obj[f]))
|
||||
const fieldsToWrite: Item[] = []
|
||||
const rowsToDelete: Item[] = []
|
||||
for (const field of fields) {
|
||||
const newValue = obj[field as keyof object] as any
|
||||
const relationIds = {}
|
||||
for (const objectField of objectFields) {
|
||||
const newId = await writeTable(obj[objectField], objectField, oldObj?.[objectField])
|
||||
relationIds[ids[objectField][0]] = newId
|
||||
debugLog(`Processed objectField ${objectField} and assigned id ${newId} to ${ids[objectField][0]}`)
|
||||
}
|
||||
basicFields.push(Object.keys(relationIds))
|
||||
for (const field of basicFields) {
|
||||
const newValue = relationIds[field] ?? (obj[field as keyof object] as any)
|
||||
const oldValue = oldObj?.[field as keyof object]
|
||||
if (newValue === oldValue) continue
|
||||
console.log(
|
||||
debugLog(
|
||||
`Field: ${field} Old value: ${oldValue} - ${typeof oldValue} New value: ${newValue} - ${typeof newValue}`
|
||||
)
|
||||
if (newValue === oldValue) continue
|
||||
if (isEmptyValue(newValue) && isEmptyValue(oldValue)) continue
|
||||
if (typeof oldValue === 'bigint' && typeof newValue === 'number' && BigInt(newValue) === oldValue) continue
|
||||
fieldsToWrite.push({ column: field, value: newValue })
|
||||
}
|
||||
|
||||
@@ -65,7 +130,7 @@ export const write: WriteFunction = (data, tableName, oldObject) => {
|
||||
const idFieldName = ids[tableName as keyof object] as string
|
||||
let id = obj[idFieldName as keyof object] as string
|
||||
if (values.length > 0) {
|
||||
if (id) {
|
||||
if (id && !!oldObj) {
|
||||
query(`UPDATE ${tableName} SET ${columns.map(c => `${c} = ?`).join(', ')} RETURNING ${idFieldName}`, [
|
||||
...values,
|
||||
])
|
||||
@@ -73,36 +138,36 @@ export const write: WriteFunction = (data, tableName, oldObject) => {
|
||||
id = query(`INSERT INTO ${tableName} (${columns.join(', ')}) VALUES (?) RETURNING ${idFieldName}`, values)
|
||||
}
|
||||
}
|
||||
for (const relationField of relationFields) {
|
||||
const value = obj[relationField]
|
||||
if (Array.isArray(value)) {
|
||||
const toBeDeleted: Item[] = value
|
||||
.filter(({ rowState }) => rowState === 'removed')
|
||||
.map(item => ({ column: relationField, value: item[idFieldName] }))
|
||||
rowsToDelete.push(...toBeDeleted)
|
||||
for (const item of value.filter(({ rowState }) => rowState !== 'removed')) {
|
||||
const itemWithId = { ...item, [ids[tableName]]: id }
|
||||
// console.log('item', item, JSON.stringify(item))
|
||||
writeTable(itemWithId, relationField, oldObj[relationField].find(getFindFunction(relationField, itemWithId)))
|
||||
}
|
||||
} else {
|
||||
// writing a relation object
|
||||
const itemWithId = { ...value, [idFieldName]: id }
|
||||
writeTable(itemWithId, relationField, oldObj?.[relationField as keyof object] as any)
|
||||
for (const arrayField of arrayFields) {
|
||||
const items = obj[arrayField]
|
||||
|
||||
// The ones with rowState 'removed' will be deleted
|
||||
const toBeDeleted: Item[] = items
|
||||
.filter(({ rowState }) => rowState === 'removed')
|
||||
.map(item => ({ column: arrayField, value: item[idFieldName] }))
|
||||
rowsToDelete.push(...toBeDeleted)
|
||||
|
||||
// Ones without that rowstate will be recursed into
|
||||
for (const item of items.filter(({ rowState }) => rowState !== 'removed')) {
|
||||
const itemWithId = { ...item, [ids[tableName]]: id }
|
||||
await writeTable(itemWithId, arrayField, oldObj?.[arrayField]?.find(getFindFunction(arrayField, itemWithId)))
|
||||
debugLog(`Processed array item ${JSON.stringify(item)}`)
|
||||
}
|
||||
}
|
||||
rowsToDelete.map(item => query(`DELETE FROM ? WHERE ? = ?`, [item.column, ids[item.column], item.value])) // NOTE column here is actually table name
|
||||
//console.log(JSON.stringify(fieldsToWrite, null, 2))
|
||||
rowsToDelete.map(item => query(`DELETE FROM ${tableName} WHERE ? = ?`, [ids[item.column], item.value])) // NOTE column here is actually table name
|
||||
//debugLog(JSON.stringify(fieldsToWrite, null, 2))
|
||||
debugLog(`Returning id: ${id}`)
|
||||
return id
|
||||
}
|
||||
|
||||
// console.log('Old object', JSON.stringify(oldObject, null, 2))
|
||||
// console.log('New object', JSON.stringify(data, null, 2))
|
||||
writeTable(revertFieldNames(data), tableName, revertFieldNames(oldObject))
|
||||
// console.log(JSON.stringify(writeList, null, 2))
|
||||
// debugLog('Old object', JSON.stringify(oldObject, null, 2))
|
||||
// debugLog('New object', JSON.stringify(data, null, 2))
|
||||
await writeTable(data, tableName)
|
||||
// debugLog(JSON.stringify(writeList, null, 2))
|
||||
return 'ok'
|
||||
}
|
||||
|
||||
const query = (queryString: string, values: any[]) => {
|
||||
console.log(`${queryString} \t| values: ${values.join(', ')}`)
|
||||
logger.info(`${queryString} \t| values: ${values.join(', ')}`)
|
||||
return 'some_id'
|
||||
}
|
||||
|
||||
@@ -48,3 +48,9 @@ export const printJSON = (obj: object) =>
|
||||
},
|
||||
2
|
||||
)
|
||||
|
||||
export const isEmptyValue = (value: string | number | null | undefined) => {
|
||||
if (value === null || value === undefined) return true
|
||||
if (typeof value === 'string' && value === '') return true
|
||||
return false
|
||||
}
|
||||
|
||||
Vendored
+5
-4
@@ -25,7 +25,7 @@ export type Editable<T> = T & { rowState?: RowState }
|
||||
export type SedimentaryStructureValues = Prisma.now_ss_values
|
||||
export type CollectingMethod = Prisma.now_coll_meth
|
||||
export type LocalityProject = Prisma.now_plr & { now_proj: Prisma.now_proj }
|
||||
export type LocalitySpecies = Omit<Prisma.now_ls, 'com_species'> & { com_species: SpeciesType }
|
||||
export type LocalitySpecies = Prisma.now_ls & { com_species: SpeciesType }
|
||||
export type SpeciesLocality = Prisma.now_ls & { now_loc: Prisma.now_loc }
|
||||
export type LocalityUpdate = Prisma.now_lau & { now_lr: LocalityReference[] } & { updates: UpdateLog[] }
|
||||
export type SpeciesUpdate = Prisma.now_sau & { now_sr: Prisma.now_sr }
|
||||
@@ -40,12 +40,13 @@ export type RegionCountry = Prisma.now_reg_coord_country
|
||||
export type SedimentaryStructure = Prisma.now_ss
|
||||
export type LocalitySynonym = Prisma.now_syn_loc
|
||||
export type SpeciesSynonym = Prisma.com_taxa_synonym
|
||||
export type LocalityDetailsType = Omit<Prisma.now_loc, 'now_ls' | 'now_mus' | 'now_proj'> & {
|
||||
museums: Array<Museum>
|
||||
export type LocalityMuseum = Prisma.now_mus & { com_mlist: Museum }
|
||||
export type LocalityDetailsType = Prisma.now_loc & {
|
||||
now_mus: Array<LocalityMuseum>
|
||||
} & {
|
||||
projects: Array<Project>
|
||||
} & {
|
||||
species: Array<LocalitySpecies>
|
||||
now_ls: Array<LocalitySpecies>
|
||||
} & { now_plr: Array<LocalityProject> } & { now_syn_loc: Array<LocalitySynonym> } & {
|
||||
now_ss: SedimentaryStructure[]
|
||||
} & {
|
||||
|
||||
@@ -44,3 +44,35 @@ export const referenceTableColumns: MRT_ColumnDef<Reference>[] = [
|
||||
maxSize: 60,
|
||||
},
|
||||
]
|
||||
|
||||
export const emptyLocalitySpeciesFields = {
|
||||
nis: null,
|
||||
pct: null,
|
||||
quad: null,
|
||||
mni: null,
|
||||
qua: null,
|
||||
id_status: null,
|
||||
orig_entry: null,
|
||||
source_name: null,
|
||||
body_mass: null,
|
||||
mesowear: null,
|
||||
mw_or_high: null,
|
||||
mw_or_low: null,
|
||||
mw_cs_sharp: null,
|
||||
mw_cs_round: null,
|
||||
mw_cs_blunt: null,
|
||||
mw_scale_min: null,
|
||||
mw_scale_max: null,
|
||||
mw_value: null,
|
||||
microwear: null,
|
||||
dc13_mean: null,
|
||||
dc13_n: null,
|
||||
dc13_max: null,
|
||||
dc13_min: null,
|
||||
dc13_stdev: null,
|
||||
do18_mean: null,
|
||||
do18_n: null,
|
||||
do18_max: null,
|
||||
do18_min: null,
|
||||
do18_stdev: null,
|
||||
}
|
||||
|
||||
@@ -9,7 +9,7 @@ import { Box, CircularProgress } from '@mui/material'
|
||||
import { MRT_ColumnDef } from 'material-react-table'
|
||||
|
||||
export const MuseumTab = () => {
|
||||
const { mode } = useDetailContext<LocalityDetailsType>()
|
||||
const { mode, editData, setEditData } = useDetailContext<LocalityDetailsType>()
|
||||
const { data: museumData, isError } = useGetAllMuseumsQuery()
|
||||
|
||||
if (isError) return 'Error loading museums.'
|
||||
@@ -51,18 +51,38 @@ export const MuseumTab = () => {
|
||||
<EditingForm<Museum, LocalityDetailsType>
|
||||
buttonText="Add new museum"
|
||||
formFields={formFields}
|
||||
arrayFieldName="museums"
|
||||
editAction={(newMuseum: Museum) => {
|
||||
setEditData({
|
||||
...editData,
|
||||
now_mus: [
|
||||
...editData.now_mus,
|
||||
{ lid: editData.lid, museum: newMuseum.museum, com_mlist: newMuseum, rowState: 'new' },
|
||||
],
|
||||
})
|
||||
}}
|
||||
/>
|
||||
<SelectingTable<Museum, LocalityDetailsType>
|
||||
buttonText="Select Museum"
|
||||
data={museumData}
|
||||
columns={columns}
|
||||
fieldName="museums"
|
||||
fieldName="now_mus"
|
||||
idFieldName="museum"
|
||||
editingAction={(newMuseum: Museum) => {
|
||||
setEditData({
|
||||
...editData,
|
||||
now_mus: [
|
||||
...editData.now_mus,
|
||||
{ lid: editData.lid, museum: newMuseum.museum, com_mlist: newMuseum, rowState: 'new' },
|
||||
],
|
||||
})
|
||||
}}
|
||||
/>
|
||||
</Box>
|
||||
)}
|
||||
<EditableTable<Editable<Museum>, LocalityDetailsType> columns={columns} field="museums" />
|
||||
<EditableTable<Editable<Museum>, LocalityDetailsType>
|
||||
columns={columns.map(col => ({ ...col, accessorKey: `com_mlist.${col.accessorKey}` }))}
|
||||
field="now_mus"
|
||||
/>
|
||||
</Grouped>
|
||||
)
|
||||
}
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
import { LocalityDetailsType, Species } from '@/backendTypes'
|
||||
import { LocalityDetailsType, LocalitySpecies, Species } from '@/backendTypes'
|
||||
import { EditableTable } from '@/components/DetailView/common/EditableTable'
|
||||
import { EditingForm } from '@/components/DetailView/common/EditingForm'
|
||||
import { SelectingTable } from '@/components/DetailView/common/SelectingTable'
|
||||
@@ -49,7 +49,40 @@ export const SpeciesTab = () => {
|
||||
header: 'Taxon status',
|
||||
},
|
||||
]
|
||||
|
||||
const columns2: MRT_ColumnDef<LocalitySpecies>[] = [
|
||||
{
|
||||
accessorKey: 'com_species.order_name',
|
||||
header: 'Order',
|
||||
},
|
||||
{
|
||||
accessorKey: 'com_species.family_name',
|
||||
header: 'Family',
|
||||
},
|
||||
{
|
||||
accessorKey: 'com_species.genus_name',
|
||||
header: 'Genus',
|
||||
},
|
||||
{
|
||||
accessorKey: 'com_species.species_name',
|
||||
header: 'Species',
|
||||
},
|
||||
{
|
||||
accessorKey: 'com_species.subclass_or_superorder_name',
|
||||
header: 'Subclass or Superorder',
|
||||
},
|
||||
{
|
||||
accessorKey: 'com_species.suborder_or_superfamily_name',
|
||||
header: 'Suborder or Superfamily',
|
||||
},
|
||||
{
|
||||
accessorKey: 'com_species.unique_identifier',
|
||||
header: 'Unique Identifier',
|
||||
},
|
||||
{
|
||||
accessorKey: 'com_species.taxonomic_status',
|
||||
header: 'Taxon status',
|
||||
},
|
||||
]
|
||||
const formFields: { name: string; label: string; required?: boolean }[] = [
|
||||
{ name: 'order_name', label: 'Order', required: true },
|
||||
{ name: 'family_name', label: 'Family', required: true },
|
||||
@@ -68,21 +101,45 @@ export const SpeciesTab = () => {
|
||||
<EditingForm<Species, LocalityDetailsType>
|
||||
buttonText="Add new Species"
|
||||
formFields={formFields}
|
||||
arrayFieldName="species"
|
||||
editAction={(newSpecies: Species) => {
|
||||
setEditData({
|
||||
...editData,
|
||||
now_ls: [
|
||||
...editData.now_ls,
|
||||
{
|
||||
lid: editData.lid,
|
||||
species_id: newSpecies.species_id,
|
||||
com_species: { ...newSpecies },
|
||||
rowState: 'new',
|
||||
},
|
||||
],
|
||||
})
|
||||
}}
|
||||
/>
|
||||
<SelectingTable<Species, LocalityDetailsType>
|
||||
buttonText="Select Species"
|
||||
data={speciesData}
|
||||
columns={columns}
|
||||
fieldName="species"
|
||||
fieldName="now_ls"
|
||||
idFieldName="species_id"
|
||||
editingAction={(newSpecies: Species) => {
|
||||
setEditData({ ...editData, species: [...editData.species, { ...newSpecies, rowState: 'new' }] })
|
||||
setEditData({
|
||||
...editData,
|
||||
now_ls: [
|
||||
...editData.now_ls,
|
||||
{
|
||||
lid: editData.lid,
|
||||
species_id: newSpecies.species_id,
|
||||
com_species: { ...newSpecies },
|
||||
rowState: 'new',
|
||||
},
|
||||
],
|
||||
})
|
||||
}}
|
||||
/>
|
||||
</Box>
|
||||
)}
|
||||
<EditableTable<Species, LocalityDetailsType> columns={columns} field="species" />
|
||||
<EditableTable<LocalitySpecies, LocalityDetailsType> columns={columns2} field="now_ls" />
|
||||
</Grouped>
|
||||
)
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user