WIP knex > kysely
This commit is contained in:
parent
3ff6aa7310
commit
8f6591b679
@ -1,7 +1,7 @@
|
|||||||
meta {
|
meta {
|
||||||
name: /api/journals
|
name: /api/journals
|
||||||
type: http
|
type: http
|
||||||
seq: 20
|
seq: 21
|
||||||
}
|
}
|
||||||
|
|
||||||
get {
|
get {
|
||||||
@ -12,4 +12,5 @@ get {
|
|||||||
|
|
||||||
settings {
|
settings {
|
||||||
encodeUrl: true
|
encodeUrl: true
|
||||||
|
timeout: 0
|
||||||
}
|
}
|
||||||
|
|||||||
20
.bruno/API/api-suppliers--id.bru
Normal file
20
.bruno/API/api-suppliers--id.bru
Normal file
@ -0,0 +1,20 @@
|
|||||||
|
meta {
|
||||||
|
name: /api/suppliers/:id
|
||||||
|
type: http
|
||||||
|
seq: 17
|
||||||
|
}
|
||||||
|
|
||||||
|
get {
|
||||||
|
url: {{base_url}}/api/suppliers/:id
|
||||||
|
body: none
|
||||||
|
auth: inherit
|
||||||
|
}
|
||||||
|
|
||||||
|
params:path {
|
||||||
|
id: 105
|
||||||
|
}
|
||||||
|
|
||||||
|
settings {
|
||||||
|
encodeUrl: true
|
||||||
|
timeout: 0
|
||||||
|
}
|
||||||
@ -1,7 +1,7 @@
|
|||||||
meta {
|
meta {
|
||||||
name: /api/suppliers/merge
|
name: /api/suppliers/merge
|
||||||
type: http
|
type: http
|
||||||
seq: 17
|
seq: 18
|
||||||
}
|
}
|
||||||
|
|
||||||
post {
|
post {
|
||||||
|
|||||||
@ -1,7 +1,7 @@
|
|||||||
meta {
|
meta {
|
||||||
name: /api/transactions
|
name: /api/transactions
|
||||||
type: http
|
type: http
|
||||||
seq: 18
|
seq: 19
|
||||||
}
|
}
|
||||||
|
|
||||||
get {
|
get {
|
||||||
|
|||||||
@ -1,7 +1,7 @@
|
|||||||
meta {
|
meta {
|
||||||
name: /api/users
|
name: /api/users
|
||||||
type: http
|
type: http
|
||||||
seq: 19
|
seq: 20
|
||||||
}
|
}
|
||||||
|
|
||||||
get {
|
get {
|
||||||
|
|||||||
@ -1,7 +1,7 @@
|
|||||||
import fs from 'node:fs/promises'
|
import fs from 'node:fs/promises'
|
||||||
import { existsSync } from 'node:fs'
|
import { existsSync } from 'node:fs'
|
||||||
import path from 'node:path'
|
import path from 'node:path'
|
||||||
import knex from '../server/lib/knex.ts'
|
import db from '../server/lib/kysely.ts'
|
||||||
|
|
||||||
const dirs = process.argv.slice(2)
|
const dirs = process.argv.slice(2)
|
||||||
|
|
||||||
@ -12,7 +12,7 @@ for await (const dir of dirs) {
|
|||||||
await readdir(dir)
|
await readdir(dir)
|
||||||
}
|
}
|
||||||
|
|
||||||
knex.destroy()
|
db.destroy()
|
||||||
|
|
||||||
async function readdir(dir: string) {
|
async function readdir(dir: string) {
|
||||||
const files = (await fs.readdir(dir)).toSorted((a: string, b: string) => {
|
const files = (await fs.readdir(dir)).toSorted((a: string, b: string) => {
|
||||||
@ -26,7 +26,7 @@ async function readdir(dir: string) {
|
|||||||
}
|
}
|
||||||
})
|
})
|
||||||
|
|
||||||
const trx = await knex.transaction()
|
const trx = await db.startTransaction().execute()
|
||||||
|
|
||||||
for await (const originalFilename of files) {
|
for await (const originalFilename of files) {
|
||||||
const result = originalFilename.match(rFileName)
|
const result = originalFilename.match(rFileName)
|
||||||
@ -37,15 +37,21 @@ async function readdir(dir: string) {
|
|||||||
|
|
||||||
const [, fiskenNumber, invoiceDate, supplierName] = result
|
const [, fiskenNumber, invoiceDate, supplierName] = result
|
||||||
|
|
||||||
let supplier = await trx('supplier').first('*').where('name', supplierName)
|
let supplier = await trx.selectFrom('supplier').selectAll().where('name', '=', supplierName).executeTakeFirst()
|
||||||
|
|
||||||
if (!supplier) {
|
if (!supplier) {
|
||||||
supplier = (await trx('supplier').insert({ name: supplierName, supplierTypeId: 1 }).returning('*'))[0]
|
supplier = await trx
|
||||||
|
.insertInto('supplier')
|
||||||
|
.values({ name: supplierName, supplierTypeId: 1 })
|
||||||
|
.returningAll()
|
||||||
|
.executeTakeFirstOrThrow()
|
||||||
}
|
}
|
||||||
|
|
||||||
const invoice = (
|
const invoice = await trx
|
||||||
await trx('invoice').insert({ fiskenNumber, invoiceDate, supplierId: supplier.id }).returning('*')
|
.insertInto('invoice')
|
||||||
)[0]
|
.values({ fiskenNumber: parseInt(fiskenNumber), invoiceDate, supplierId: supplier.id })
|
||||||
|
.returningAll()
|
||||||
|
.executeTakeFirstOrThrow()
|
||||||
|
|
||||||
const ext = path.extname(originalFilename)
|
const ext = path.extname(originalFilename)
|
||||||
const filename = `${invoiceDate}_fisken_${fiskenNumber}_${supplierName.split(/[\s/]/).join('_').split(/[']/).join('')}${ext}`
|
const filename = `${invoiceDate}_fisken_${fiskenNumber}_${supplierName.split(/[\s/]/).join('_').split(/[']/).join('')}${ext}`
|
||||||
@ -58,9 +64,9 @@ async function readdir(dir: string) {
|
|||||||
console.info('ALREADY EXISTS: ' + filename)
|
console.info('ALREADY EXISTS: ' + filename)
|
||||||
}
|
}
|
||||||
|
|
||||||
const file = (await trx('file').insert({ filename }).returning('id'))[0]
|
const file = await trx.insertInto('file').values({ filename }).returning('id').executeTakeFirstOrThrow()
|
||||||
await trx('filesToInvoice').insert({ fileId: file.id, invoiceId: invoice.id })
|
await trx.insertInto('filesToInvoice').values({ fileId: file.id, invoiceId: invoice.id }).execute()
|
||||||
}
|
}
|
||||||
|
|
||||||
await trx.commit()
|
await trx.commit().execute()
|
||||||
}
|
}
|
||||||
|
|||||||
@ -1,52 +1,56 @@
|
|||||||
import fs from 'node:fs/promises'
|
import fs from 'node:fs/promises'
|
||||||
import { existsSync } from 'node:fs'
|
import { existsSync } from 'node:fs'
|
||||||
import path from 'node:path'
|
import path from 'node:path'
|
||||||
import knex from '../server/lib/knex.ts'
|
import db from '../server/lib/kysely.ts'
|
||||||
import { csvParseRows } from 'd3-dsv'
|
import { csvParseRows } from 'd3-dsv'
|
||||||
|
|
||||||
const csvFilename = process.argv[2]
|
const csvFilename = process.argv[2]
|
||||||
const csvString = await fs.readFile(csvFilename, { encoding: 'utf8' })
|
const csvString = await fs.readFile(csvFilename, { encoding: 'utf8' })
|
||||||
const rows = csvParseRows(csvString)
|
const rows = csvParseRows(csvString)
|
||||||
|
|
||||||
const trx = await knex.transaction()
|
const trx = await db.startTransaction().execute()
|
||||||
|
|
||||||
for (const row of rows.toReversed()) {
|
for (const row of rows.toReversed()) {
|
||||||
const [
|
const [
|
||||||
phmNumber,
|
phmNumber,
|
||||||
// type,
|
_type,
|
||||||
// supplierId,
|
_supplierId,
|
||||||
supplierName,
|
supplierName,
|
||||||
invoiceDate,
|
invoiceDate,
|
||||||
dueDate,
|
dueDate,
|
||||||
invoiceNumber,
|
invoiceNumber,
|
||||||
ocr,
|
ocr,
|
||||||
amount,
|
amount,
|
||||||
// vat,
|
_vat,
|
||||||
// balance,
|
_balance,
|
||||||
// currency,
|
_currency,
|
||||||
// status,
|
_status,
|
||||||
filesString,
|
filesString,
|
||||||
] = row
|
] = row
|
||||||
|
|
||||||
let supplier = await trx('supplier').first('*').where('name', supplierName)
|
let supplier = await trx.selectFrom('supplier').selectAll().where('name', '=', supplierName).executeTakeFirst()
|
||||||
|
|
||||||
if (!supplier) {
|
if (!supplier) {
|
||||||
supplier = (await trx('supplier').insert({ name: supplierName, supplierTypeId: 1 }).returning('*'))[0]
|
supplier = await trx
|
||||||
|
.insertInto('supplier')
|
||||||
|
.values({ name: supplierName, supplierTypeId: 1 })
|
||||||
|
.returningAll()
|
||||||
|
.executeTakeFirstOrThrow()
|
||||||
}
|
}
|
||||||
|
|
||||||
const invoice = (
|
const invoice = await trx
|
||||||
await trx('invoice')
|
.insertInto('invoice')
|
||||||
.insert({
|
.values({
|
||||||
invoiceDate,
|
invoiceDate,
|
||||||
supplierId: supplier.id,
|
supplierId: supplier.id,
|
||||||
dueDate,
|
dueDate,
|
||||||
ocr,
|
ocr,
|
||||||
invoiceNumber,
|
invoiceNumber,
|
||||||
phmNumber,
|
phmNumber: parseInt(phmNumber),
|
||||||
amount,
|
amount,
|
||||||
})
|
})
|
||||||
.returning('id')
|
.returning('id')
|
||||||
)[0]
|
.executeTakeFirstOrThrow()
|
||||||
|
|
||||||
const filenames = filesString.split(',').map((filename) => filename.trim())
|
const filenames = filesString.split(',').map((filename) => filename.trim())
|
||||||
|
|
||||||
@ -63,11 +67,11 @@ for (const row of rows.toReversed()) {
|
|||||||
console.info('ALREADY EXISTS: ' + filename)
|
console.info('ALREADY EXISTS: ' + filename)
|
||||||
}
|
}
|
||||||
|
|
||||||
const file = (await trx('file').insert({ filename }).returning('id'))[0]
|
const file = await trx.insertInto('file').values({ filename }).returning('id').executeTakeFirstOrThrow()
|
||||||
await trx('filesToInvoice').insert({ fileId: file.id, invoiceId: invoice.id })
|
await trx.insertInto('filesToInvoice').values({ fileId: file.id, invoiceId: invoice.id }).execute()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
trx.commit()
|
await trx.commit().execute()
|
||||||
|
|
||||||
knex.destroy()
|
db.destroy()
|
||||||
|
|||||||
@ -1,6 +1,6 @@
|
|||||||
import fs from 'fs/promises'
|
import fs from 'fs/promises'
|
||||||
import parseStream from '../server/lib/parse_stream.ts'
|
import parseStream from '../server/lib/parse_stream.ts'
|
||||||
import knex from '../server/lib/knex.ts'
|
import db from '../server/lib/kysely.ts'
|
||||||
|
|
||||||
for await (const file of process.argv.slice(2)) {
|
for await (const file of process.argv.slice(2)) {
|
||||||
const fh = await fs.open(file)
|
const fh = await fs.open(file)
|
||||||
@ -12,4 +12,4 @@ for await (const file of process.argv.slice(2)) {
|
|||||||
await fh.close()
|
await fh.close()
|
||||||
}
|
}
|
||||||
|
|
||||||
knex.destroy()
|
db.destroy()
|
||||||
|
|||||||
@ -13,12 +13,14 @@ const format = Format.bind(null, null, 'YYYY.MM.DD')
|
|||||||
const InvoicesPage: FunctionComponent = () => {
|
const InvoicesPage: FunctionComponent = () => {
|
||||||
const route = useRoute()
|
const route = useRoute()
|
||||||
|
|
||||||
const [supplier, invoices, totalAmount] = usePromise<[Supplier, (Invoice & { files?: File[] })[], number]>(() =>
|
const [supplier, { data: invoices }, totalAmount] = usePromise<
|
||||||
|
[Supplier, { data: (Invoice & { files?: File[] })[] }, number]
|
||||||
|
>(() =>
|
||||||
Promise.all([
|
Promise.all([
|
||||||
rek(`/api/suppliers/${route.params.supplier}`),
|
rek(`/api/suppliers/${route.params.supplier}`),
|
||||||
rek(`/api/invoices?supplier=${route.params.supplier}`),
|
rek(`/api/invoices?supplierId=${route.params.supplier}`),
|
||||||
rek(`/api/invoices/total-amount?supplier=${route.params.supplier}`).then(
|
rek(`/api/invoices/total-amount?supplierId=${route.params.supplier}`).then(
|
||||||
(totalAmount: { amount: number }) => totalAmount.amount,
|
(totalAmount: { totalAmount: number }) => totalAmount.totalAmount,
|
||||||
),
|
),
|
||||||
]),
|
]),
|
||||||
)
|
)
|
||||||
|
|||||||
@ -12,7 +12,9 @@ import type { Transaction, FinancialYear } from '../../../shared/types.db.ts'
|
|||||||
|
|
||||||
const TransactionsPage: FunctionComponent = () => {
|
const TransactionsPage: FunctionComponent = () => {
|
||||||
const [financialYears, setFinancialYears] = useState<FinancialYear[]>([])
|
const [financialYears, setFinancialYears] = useState<FinancialYear[]>([])
|
||||||
const [transactions, setTransactions] = useState<(Transaction & { entryDescription: string })[]>([])
|
const [{ data: transactions }, setTransactions] = useState<{ data: (Transaction & { entryDescription: string })[] }>({
|
||||||
|
data: [],
|
||||||
|
})
|
||||||
|
|
||||||
const location = useLocation()
|
const location = useLocation()
|
||||||
|
|
||||||
|
|||||||
@ -53,6 +53,7 @@ export default read(
|
|||||||
'SESSION_SECRET',
|
'SESSION_SECRET',
|
||||||
] as const,
|
] as const,
|
||||||
{
|
{
|
||||||
|
MAILGUN_API_KEY: 'invalid key',
|
||||||
PGPASSWORD: null,
|
PGPASSWORD: null,
|
||||||
PGPORT: null,
|
PGPORT: null,
|
||||||
PGUSER: null,
|
PGUSER: null,
|
||||||
|
|||||||
@ -1,5 +1,6 @@
|
|||||||
import type { ReadableStream } from 'stream/web'
|
import type { ReadableStream } from 'stream/web'
|
||||||
import knex from './knex.ts'
|
import type { Selectable } from 'kysely'
|
||||||
|
import db from './kysely.ts'
|
||||||
|
|
||||||
import split, { type Decoder } from './split.ts'
|
import split, { type Decoder } from './split.ts'
|
||||||
import {
|
import {
|
||||||
@ -37,25 +38,27 @@ const defaultDecoder = {
|
|||||||
},
|
},
|
||||||
}
|
}
|
||||||
|
|
||||||
|
import type { Entry, FinancialYear } from '../../shared/types.db.ts'
|
||||||
|
|
||||||
export default async function parseStream(stream: ReadableStream, decoder: Decoder = defaultDecoder) {
|
export default async function parseStream(stream: ReadableStream, decoder: Decoder = defaultDecoder) {
|
||||||
const journals = new Map()
|
const journals = new Map()
|
||||||
|
|
||||||
let currentEntry: { id: number; description: string }
|
let currentEntry: Pick<Selectable<Entry>, 'id' | 'description'>
|
||||||
let currentInvoiceId: number | null
|
let currentInvoiceId: number | null | undefined
|
||||||
let currentYear = null
|
let currentYear: Selectable<FinancialYear>
|
||||||
const details: Record<string, string> = {}
|
const details: Record<string, string> = {}
|
||||||
|
|
||||||
const trx = await knex.transaction()
|
const trx = await db.startTransaction().execute()
|
||||||
|
|
||||||
async function getJournalId(identifier: string) {
|
async function getJournalId(identifier: string) {
|
||||||
if (journals.has(identifier)) {
|
if (journals.has(identifier)) {
|
||||||
return journals.get(identifier)
|
return journals.get(identifier)
|
||||||
}
|
}
|
||||||
|
|
||||||
let journal = await trx('journal').first('*').where('identifier', identifier)
|
let journal = await trx.selectFrom('journal').selectAll().where('identifier', '=', identifier).executeTakeFirst()
|
||||||
|
|
||||||
if (!journal) {
|
if (!journal) {
|
||||||
journal = (await trx('journal').insert({ identifier }).returning('*'))[0]
|
journal = await trx.insertInto('journal').values({ identifier }).returningAll().executeTakeFirstOrThrow()
|
||||||
}
|
}
|
||||||
|
|
||||||
journals.set(identifier, journal.id)
|
journals.set(identifier, journal.id)
|
||||||
@ -77,12 +80,16 @@ export default async function parseStream(stream: ReadableStream, decoder: Decod
|
|||||||
case '#DIM': {
|
case '#DIM': {
|
||||||
const { number, name } = parseDim(line)
|
const { number, name } = parseDim(line)
|
||||||
|
|
||||||
const existingDimension = await trx('dimension').first('*').where('number', number)
|
const existingDimension = await db
|
||||||
|
.selectFrom('dimension')
|
||||||
|
.selectAll()
|
||||||
|
.where('number', '=', number)
|
||||||
|
.executeTakeFirst()
|
||||||
|
|
||||||
if (!existingDimension) {
|
if (!existingDimension) {
|
||||||
await trx('dimension').insert({ number, name })
|
await trx.insertInto('dimension').values({ number, name }).execute()
|
||||||
} else if (existingDimension.name !== name) {
|
} else if (existingDimension.name !== name) {
|
||||||
await trx('dimension').update({ name }).where('number', number)
|
await trx.updateTable('dimension').set({ name }).where('number', '=', number).execute()
|
||||||
}
|
}
|
||||||
|
|
||||||
break
|
break
|
||||||
@ -92,27 +99,36 @@ export default async function parseStream(stream: ReadableStream, decoder: Decod
|
|||||||
|
|
||||||
if (yearNumber !== 0) continue
|
if (yearNumber !== 0) continue
|
||||||
|
|
||||||
const existingAccountBalance = await trx('accountBalance')
|
const existingAccountBalance = await trx
|
||||||
.first('*')
|
.selectFrom('accountBalance')
|
||||||
.where({ financialYearId: currentYear.id, accountNumber })
|
.selectAll()
|
||||||
|
.where((eb) => eb.and({ financialYearId: currentYear!.id, accountNumber }))
|
||||||
|
.executeTakeFirst()
|
||||||
|
|
||||||
if (!existingAccountBalance) {
|
if (!existingAccountBalance) {
|
||||||
await trx('accountBalance').insert({
|
await trx
|
||||||
financialYearId: currentYear.id,
|
.insertInto('accountBalance')
|
||||||
|
.values({
|
||||||
|
financialYearId: currentYear!.id,
|
||||||
accountNumber,
|
accountNumber,
|
||||||
in: balance,
|
in: balance,
|
||||||
inQuantity: quantity,
|
inQuantity: quantity,
|
||||||
})
|
})
|
||||||
|
.execute()
|
||||||
} else {
|
} else {
|
||||||
await trx('accountBalance')
|
await trx
|
||||||
.update({
|
.updateTable('accountBalance')
|
||||||
|
.set({
|
||||||
in: balance,
|
in: balance,
|
||||||
inQuantity: quantity,
|
inQuantity: quantity,
|
||||||
})
|
})
|
||||||
.where({
|
.where((eb) =>
|
||||||
financialYearId: currentYear.id,
|
eb.and({
|
||||||
|
financialYearId: currentYear!.id,
|
||||||
accountNumber,
|
accountNumber,
|
||||||
})
|
}),
|
||||||
|
)
|
||||||
|
.execute()
|
||||||
}
|
}
|
||||||
|
|
||||||
break
|
break
|
||||||
@ -120,19 +136,24 @@ export default async function parseStream(stream: ReadableStream, decoder: Decod
|
|||||||
case '#KONTO': {
|
case '#KONTO': {
|
||||||
const { number, description } = parseKonto(line)
|
const { number, description } = parseKonto(line)
|
||||||
|
|
||||||
const existingAccount = await trx('account')
|
const existingAccount = await trx
|
||||||
.first('*')
|
.selectFrom('account')
|
||||||
.where('number', number)
|
.selectAll()
|
||||||
|
.where('number', '=', number)
|
||||||
.orderBy('financialYearId', 'desc')
|
.orderBy('financialYearId', 'desc')
|
||||||
|
.executeTakeFirst()
|
||||||
|
|
||||||
if (!existingAccount) {
|
if (!existingAccount) {
|
||||||
await trx('account').insert({
|
await trx
|
||||||
|
.insertInto('account')
|
||||||
|
.values({
|
||||||
financialYearId: currentYear!.id,
|
financialYearId: currentYear!.id,
|
||||||
number,
|
number,
|
||||||
description,
|
description,
|
||||||
})
|
})
|
||||||
|
.execute()
|
||||||
} else if (existingAccount.description !== description) {
|
} else if (existingAccount.description !== description) {
|
||||||
await trx('account').update({ description }).where('id', existingAccount.id)
|
await trx.updateTable('account').set({ description }).where('id', '=', existingAccount.id).execute()
|
||||||
}
|
}
|
||||||
|
|
||||||
break
|
break
|
||||||
@ -140,16 +161,28 @@ export default async function parseStream(stream: ReadableStream, decoder: Decod
|
|||||||
case '#OBJEKT': {
|
case '#OBJEKT': {
|
||||||
const { dimensionNumber, number, name } = parseObjekt(line)
|
const { dimensionNumber, number, name } = parseObjekt(line)
|
||||||
|
|
||||||
const dimension = await trx('dimension').first('*').where('number', dimensionNumber)
|
const dimension = await trx
|
||||||
|
.selectFrom('dimension')
|
||||||
|
.selectAll()
|
||||||
|
.where('number', '=', dimensionNumber)
|
||||||
|
.executeTakeFirst()
|
||||||
|
|
||||||
if (!dimension) throw new Error(`Dimension "${dimensionNumber}" does not exist`)
|
if (!dimension) throw new Error(`Dimension "${dimensionNumber}" does not exist`)
|
||||||
|
|
||||||
const existingObject = await trx('object').first('*').where({ dimensionId: dimension.id, number })
|
const existingObject = await trx
|
||||||
|
.selectFrom('object')
|
||||||
|
.selectAll()
|
||||||
|
.where((eb) => eb.and({ dimensionId: dimension.id, number }))
|
||||||
|
.executeTakeFirst()
|
||||||
|
|
||||||
if (!existingObject) {
|
if (!existingObject) {
|
||||||
await trx('object').insert({ dimensionId: dimension.id, number, name })
|
await trx.insertInto('object').values({ dimensionId: dimension.id, number, name }).execute()
|
||||||
} else if (existingObject.name !== name) {
|
} else if (existingObject.name !== name) {
|
||||||
await trx('object').update({ name }).where({ dimensionId: dimension.id, number })
|
await trx
|
||||||
|
.updateTable('object')
|
||||||
|
.set({ name })
|
||||||
|
.where((eb) => eb.and({ dimensionId: dimension.id, number }))
|
||||||
|
.execute()
|
||||||
}
|
}
|
||||||
|
|
||||||
break
|
break
|
||||||
@ -160,9 +193,11 @@ export default async function parseStream(stream: ReadableStream, decoder: Decod
|
|||||||
if (yearNumber !== 0) continue
|
if (yearNumber !== 0) continue
|
||||||
|
|
||||||
currentYear = (
|
currentYear = (
|
||||||
await trx('financialYear')
|
await trx
|
||||||
.insert({ year: startDate.slice(0, 4), startDate, endDate })
|
.insertInto('financialYear')
|
||||||
.returning('*')
|
.values({ year: parseInt(startDate.slice(0, 4)), startDate, endDate })
|
||||||
|
.returningAll()
|
||||||
|
.execute()
|
||||||
)[0]
|
)[0]
|
||||||
|
|
||||||
break
|
break
|
||||||
@ -170,22 +205,27 @@ export default async function parseStream(stream: ReadableStream, decoder: Decod
|
|||||||
case '#SRU': {
|
case '#SRU': {
|
||||||
const { number, sru } = parseSRU(line)
|
const { number, sru } = parseSRU(line)
|
||||||
|
|
||||||
const existingAccount = await trx('account')
|
const existingAccount = await trx
|
||||||
.first('*')
|
.selectFrom('account')
|
||||||
.where('number', number)
|
.selectAll()
|
||||||
|
.where('number', '=', number)
|
||||||
.orderBy('financialYearId', 'desc')
|
.orderBy('financialYearId', 'desc')
|
||||||
|
.executeTakeFirst()
|
||||||
|
|
||||||
if (existingAccount) {
|
if (existingAccount) {
|
||||||
if (existingAccount.sru !== sru) {
|
if (existingAccount.sru !== sru) {
|
||||||
await trx('account').update({ sru: sru }).where('id', existingAccount.id)
|
await trx.updateTable('account').set({ sru: sru }).where('id', '=', existingAccount.id).execute()
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
await trx('account').insert({
|
await trx
|
||||||
|
.insertInto('account')
|
||||||
|
.values({
|
||||||
financialYearId: currentYear!.id,
|
financialYearId: currentYear!.id,
|
||||||
number,
|
number,
|
||||||
description: existingAccount.description,
|
description: existingAccount!.description,
|
||||||
sru,
|
sru,
|
||||||
})
|
})
|
||||||
|
.execute()
|
||||||
}
|
}
|
||||||
|
|
||||||
break
|
break
|
||||||
@ -198,67 +238,95 @@ export default async function parseStream(stream: ReadableStream, decoder: Decod
|
|||||||
|
|
||||||
const result = transaction.description?.match(rFisken)
|
const result = transaction.description?.match(rFisken)
|
||||||
|
|
||||||
let invoiceId: number
|
let invoiceId: number | null | undefined
|
||||||
|
|
||||||
if (result) {
|
if (result) {
|
||||||
const [, fiskenNumber, supplierName] = result
|
const [, fiskenNumber, supplierName] = result
|
||||||
// invoiceId = (await trx('invoice').first('id').where({ fiskenNumber})).id
|
// invoiceId = (await trx('invoice').first('id').where({ fiskenNumber})).id
|
||||||
invoiceId = (await trx('invoice').first('id').where({ fiskenNumber }))?.id
|
invoiceId = (
|
||||||
|
await trx
|
||||||
|
.selectFrom('invoice')
|
||||||
|
.select('id')
|
||||||
|
.where('fiskenNumber', '=', parseInt(fiskenNumber))
|
||||||
|
.executeTakeFirst()
|
||||||
|
)?.id
|
||||||
|
|
||||||
if (!invoiceId) {
|
if (!invoiceId) {
|
||||||
let supplierId = (await trx('supplier').first('id').where('name', supplierName))?.id
|
let supplierId = (
|
||||||
|
await trx.selectFrom('supplier').select('id').where('name', '=', supplierName).executeTakeFirst()
|
||||||
|
)?.id
|
||||||
|
|
||||||
if (!supplierId) {
|
if (!supplierId) {
|
||||||
supplierId = (await trx('supplier').insert({ name: supplierName, supplierTypeId: 1 }).returning('id'))[0]
|
supplierId = (
|
||||||
?.id
|
await trx
|
||||||
|
.insertInto('supplier')
|
||||||
|
.values({ name: supplierName, supplierTypeId: 1 })
|
||||||
|
.returning('id')
|
||||||
|
.executeTakeFirstOrThrow()
|
||||||
|
).id
|
||||||
}
|
}
|
||||||
|
|
||||||
invoiceId = (
|
invoiceId = (
|
||||||
await trx('invoice')
|
await trx
|
||||||
.insert({ financialYearId: currentYear!.id, fiskenNumber, supplierId })
|
.insertInto('invoice')
|
||||||
|
.values({ financialYearId: currentYear!.id, fiskenNumber: parseInt(fiskenNumber), supplierId })
|
||||||
.returning('id')
|
.returning('id')
|
||||||
)[0]?.id
|
.executeTakeFirstOrThrow()
|
||||||
|
).id
|
||||||
}
|
}
|
||||||
|
|
||||||
if (transaction.accountNumber === 2441 && currentEntry!.description?.includes('Fakturajournal')) {
|
if (transaction.accountNumber === 2441 && currentEntry!.description?.includes('Fakturajournal')) {
|
||||||
await trx('invoice').update('amount', Math.abs(transaction.amount)).where('id', invoiceId)
|
await trx
|
||||||
|
.updateTable('invoice')
|
||||||
|
.set('amount', Math.abs(transaction.amount))
|
||||||
|
.where('id', '=', invoiceId!)
|
||||||
|
.execute()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if (invoiceId! && currentInvoiceId!) {
|
if (invoiceId && currentInvoiceId) {
|
||||||
throw new Error('invoiceId and currentInvoiceId')
|
throw new Error('invoiceId and currentInvoiceId')
|
||||||
}
|
}
|
||||||
|
|
||||||
const transactionId = (
|
const transactionId = (
|
||||||
await trx('transaction')
|
await trx
|
||||||
.insert({
|
.insertInto('transaction')
|
||||||
|
.values({
|
||||||
entryId: currentEntry!.id,
|
entryId: currentEntry!.id,
|
||||||
...transaction,
|
...transaction,
|
||||||
invoiceId: invoiceId! || currentInvoiceId!,
|
invoiceId: invoiceId! || currentInvoiceId!,
|
||||||
})
|
})
|
||||||
.returning('id')
|
.returning('id')
|
||||||
)[0].id
|
.executeTakeFirstOrThrow()
|
||||||
|
).id
|
||||||
|
|
||||||
if (objectList) {
|
if (objectList) {
|
||||||
for (const [dimensionNumber, objectNumber] of objectList) {
|
for (const [dimensionNumber, objectNumber] of objectList) {
|
||||||
const objectId = (
|
const objectId = (
|
||||||
await trx('object')
|
await trx
|
||||||
.first('object.id')
|
.selectFrom('object')
|
||||||
.innerJoin('dimension', 'object.dimensionId', 'dimension.id')
|
.innerJoin('dimension', 'object.dimensionId', 'dimension.id')
|
||||||
.where({
|
.select('object.id')
|
||||||
|
.where((eb) =>
|
||||||
|
eb.and({
|
||||||
'object.number': objectNumber,
|
'object.number': objectNumber,
|
||||||
'dimension.number': dimensionNumber,
|
'dimension.number': dimensionNumber,
|
||||||
})
|
}),
|
||||||
|
)
|
||||||
|
.executeTakeFirst()
|
||||||
)?.id
|
)?.id
|
||||||
|
|
||||||
if (!objectId) {
|
if (!objectId) {
|
||||||
throw new Error(`Object {${dimensionNumber} ${objectNumber}} does not exist!`)
|
throw new Error(`Object {${dimensionNumber} ${objectNumber}} does not exist!`)
|
||||||
}
|
}
|
||||||
|
|
||||||
await trx('transactionsToObjects').insert({
|
await trx
|
||||||
|
.insertInto('transactionsToObjects')
|
||||||
|
.values({
|
||||||
transactionId,
|
transactionId,
|
||||||
objectId,
|
objectId,
|
||||||
})
|
})
|
||||||
|
.execute()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -269,27 +337,36 @@ export default async function parseStream(stream: ReadableStream, decoder: Decod
|
|||||||
|
|
||||||
if (yearNumber !== 0) continue
|
if (yearNumber !== 0) continue
|
||||||
|
|
||||||
const existingAccountBalance = await trx('accountBalance')
|
const existingAccountBalance = await trx
|
||||||
.first('*')
|
.selectFrom('accountBalance')
|
||||||
.where({ financialYearId: currentYear!.id, accountNumber })
|
.selectAll()
|
||||||
|
.where((eb) => eb.and({ financialYearId: currentYear!.id, accountNumber }))
|
||||||
|
.executeTakeFirst()
|
||||||
|
|
||||||
if (!existingAccountBalance) {
|
if (!existingAccountBalance) {
|
||||||
await trx('accountBalance').insert({
|
await trx
|
||||||
|
.insertInto('accountBalance')
|
||||||
|
.values({
|
||||||
financialYearId: currentYear!.id,
|
financialYearId: currentYear!.id,
|
||||||
accountNumber,
|
accountNumber,
|
||||||
out: balance,
|
out: balance,
|
||||||
outQuantity: quantity,
|
outQuantity: quantity,
|
||||||
})
|
})
|
||||||
|
.execute()
|
||||||
} else {
|
} else {
|
||||||
await trx('accountBalance')
|
await trx
|
||||||
.update({
|
.updateTable('accountBalance')
|
||||||
|
.set({
|
||||||
out: balance,
|
out: balance,
|
||||||
outQuantity: quantity,
|
outQuantity: quantity,
|
||||||
})
|
})
|
||||||
.where({
|
.where((eb) =>
|
||||||
|
eb.and({
|
||||||
financialYearId: currentYear!.id,
|
financialYearId: currentYear!.id,
|
||||||
accountNumber,
|
accountNumber,
|
||||||
})
|
}),
|
||||||
|
)
|
||||||
|
.execute()
|
||||||
}
|
}
|
||||||
|
|
||||||
break
|
break
|
||||||
@ -310,24 +387,27 @@ export default async function parseStream(stream: ReadableStream, decoder: Decod
|
|||||||
|
|
||||||
if (result) {
|
if (result) {
|
||||||
currentInvoiceId = (
|
currentInvoiceId = (
|
||||||
await trx('invoice')
|
await trx
|
||||||
.select('invoice.*', 'supplier.name')
|
.selectFrom('invoice')
|
||||||
.innerJoin('supplier', 'invoice.supplierId', 'supplier.id')
|
.innerJoin('supplier', 'invoice.supplierId', 'supplier.id')
|
||||||
.where('phmNumber', result[1])
|
.selectAll('invoice')
|
||||||
)[0]?.id
|
.select('supplier.name')
|
||||||
|
.where('phmNumber', '=', parseInt(result[1]))
|
||||||
|
.executeTakeFirst()
|
||||||
|
)?.id
|
||||||
} else {
|
} else {
|
||||||
currentInvoiceId = null
|
currentInvoiceId = null
|
||||||
}
|
}
|
||||||
|
|
||||||
currentEntry = (
|
currentEntry = await trx
|
||||||
await trx('entry')
|
.insertInto('entry')
|
||||||
.insert({
|
.values({
|
||||||
journalId,
|
journalId,
|
||||||
financialYearId: currentYear!.id,
|
financialYearId: currentYear!.id,
|
||||||
...rest,
|
...rest,
|
||||||
})
|
})
|
||||||
.returning(['id', 'description'])
|
.returning(['id', 'description'])
|
||||||
)[0]
|
.executeTakeFirstOrThrow()
|
||||||
|
|
||||||
break
|
break
|
||||||
}
|
}
|
||||||
@ -336,10 +416,10 @@ export default async function parseStream(stream: ReadableStream, decoder: Decod
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
await trx.commit()
|
await trx.commit().execute()
|
||||||
|
|
||||||
// oxlint-disable-next-line no-console
|
// oxlint-disable-next-line no-console
|
||||||
console.dir(details)
|
console.dir(details)
|
||||||
|
|
||||||
console.info(`DONE!: ${currentYear.startDate} - ${currentYear.endDate}`)
|
console.info(`DONE!: ${currentYear!.startDate} - ${currentYear!.endDate}`)
|
||||||
}
|
}
|
||||||
|
|||||||
@ -1,7 +1,7 @@
|
|||||||
import type { RouteHandler } from 'fastify'
|
import type { RouteHandler } from 'fastify'
|
||||||
import * as z from 'zod'
|
import * as z from 'zod'
|
||||||
|
import { sql } from 'kysely'
|
||||||
import config from '../../../config.ts'
|
import config from '../../../config.ts'
|
||||||
import knex from '../../../lib/knex.ts'
|
|
||||||
import StatusError from '../../../lib/status_error.ts'
|
import StatusError from '../../../lib/status_error.ts'
|
||||||
import { hashPassword } from '../helpers.ts'
|
import { hashPassword } from '../helpers.ts'
|
||||||
|
|
||||||
@ -23,32 +23,38 @@ const changePassword: RouteHandler<{ Body: z.infer<typeof BodySchema> }> = async
|
|||||||
request,
|
request,
|
||||||
reply,
|
reply,
|
||||||
) {
|
) {
|
||||||
|
const { db } = request.server
|
||||||
|
|
||||||
try {
|
try {
|
||||||
if (!request.body.email || !request.body.password || !request.body.token) {
|
if (!request.body.email || !request.body.password || !request.body.token) {
|
||||||
throw new StatusError(...errors.missingParameters)
|
throw new StatusError(...errors.missingParameters)
|
||||||
}
|
}
|
||||||
|
|
||||||
const [token, latestToken] = await Promise.all([
|
const [token, latestToken] = await Promise.all([
|
||||||
knex('passwordToken as pt')
|
db
|
||||||
.first('pt.id', 'u.id as userId', 'u.email', 'pt.token', 'pt.createdAt', 'pt.cancelledAt', 'pt.consumedAt')
|
.selectFrom('passwordToken as pt')
|
||||||
.innerJoin('user as u', 'pt.userId', 'u.id')
|
.innerJoin('user as u', 'pt.userId', 'u.id')
|
||||||
.where({
|
.select(['pt.id', 'u.id as userId', 'u.email', 'pt.token', 'pt.createdAt', 'pt.cancelledAt', 'pt.consumedAt'])
|
||||||
|
.where((eb) =>
|
||||||
|
eb.and({
|
||||||
'u.email': request.body.email,
|
'u.email': request.body.email,
|
||||||
'pt.token': request.body.token,
|
'pt.token': request.body.token,
|
||||||
}),
|
}),
|
||||||
knex('passwordToken as pt')
|
)
|
||||||
.first('pt.id')
|
.executeTakeFirst(),
|
||||||
|
db
|
||||||
|
.selectFrom('passwordToken as pt')
|
||||||
.innerJoin('user as u', 'pt.userId', 'u.id')
|
.innerJoin('user as u', 'pt.userId', 'u.id')
|
||||||
.where({
|
.select('pt.id')
|
||||||
'u.email': request.body.email,
|
.where('u.email', '=', request.body.email)
|
||||||
})
|
|
||||||
.orderBy('pt.createdAt', 'desc')
|
.orderBy('pt.createdAt', 'desc')
|
||||||
.limit(1),
|
.limit(1)
|
||||||
|
.executeTakeFirst(),
|
||||||
])
|
])
|
||||||
|
|
||||||
if (!token) {
|
if (!token) {
|
||||||
throw new StatusError(...errors.tokenNotFound)
|
throw new StatusError(...errors.tokenNotFound)
|
||||||
} else if (token.id !== latestToken.id) {
|
} else if (token.id !== latestToken!.id) {
|
||||||
throw new StatusError(...errors.tokenNotLatest)
|
throw new StatusError(...errors.tokenNotLatest)
|
||||||
} else if (token.consumedAt) {
|
} else if (token.consumedAt) {
|
||||||
throw new StatusError(...errors.tokenConsumed)
|
throw new StatusError(...errors.tokenConsumed)
|
||||||
@ -58,10 +64,14 @@ const changePassword: RouteHandler<{ Body: z.infer<typeof BodySchema> }> = async
|
|||||||
|
|
||||||
const hash = await hashPassword(request.body.password)
|
const hash = await hashPassword(request.body.password)
|
||||||
|
|
||||||
await knex.transaction((trx) =>
|
await db.transaction().execute((trx) =>
|
||||||
Promise.all([
|
Promise.all([
|
||||||
trx('user').update({ password: hash }).where('id', token.userId),
|
trx.updateTable('user').set({ password: hash }).where('id', '=', token.userId).execute(),
|
||||||
trx('password_token').update({ consumedAt: knex.fn.now() }).where('id', token.id),
|
trx
|
||||||
|
.updateTable('passwordToken')
|
||||||
|
.set({ consumedAt: sql`now()` })
|
||||||
|
.where('id', '=', token.id)
|
||||||
|
.execute(),
|
||||||
]),
|
]),
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|||||||
@ -1,15 +1,12 @@
|
|||||||
import _ from 'lodash'
|
import _ from 'lodash'
|
||||||
import * as z from 'zod'
|
|
||||||
import type { RouteHandler } from 'fastify'
|
import type { RouteHandler } from 'fastify'
|
||||||
|
import * as z from 'zod'
|
||||||
|
import { sql } from 'kysely'
|
||||||
import config from '../../../config.ts'
|
import config from '../../../config.ts'
|
||||||
import UserQueries from '../../../services/users/queries.ts'
|
|
||||||
import emitter from '../../../lib/emitter.ts'
|
|
||||||
import knex from '../../../lib/knex.ts'
|
|
||||||
import StatusError from '../../../lib/status_error.ts'
|
import StatusError from '../../../lib/status_error.ts'
|
||||||
import { StatusErrorSchema } from '../../../schemas/status_error.ts'
|
import { StatusErrorSchema } from '../../../schemas/status_error.ts'
|
||||||
import { verifyPassword } from '../helpers.ts'
|
import { verifyPassword } from '../helpers.ts'
|
||||||
|
|
||||||
const userQueries = UserQueries({ knex, emitter })
|
|
||||||
const { errors, maxLoginAttempts, requireVerification } = config.auth
|
const { errors, maxLoginAttempts, requireVerification } = config.auth
|
||||||
|
|
||||||
const BodySchema = z.object({
|
const BodySchema = z.object({
|
||||||
@ -32,21 +29,27 @@ const ResponseSchema = {
|
|||||||
}
|
}
|
||||||
|
|
||||||
const login: RouteHandler<{ Body: z.infer<typeof BodySchema> }> = async function (request, reply) {
|
const login: RouteHandler<{ Body: z.infer<typeof BodySchema> }> = async function (request, reply) {
|
||||||
|
const { db } = request.server
|
||||||
|
|
||||||
try {
|
try {
|
||||||
const user = await userQueries.findOne({ email: request.body.email })
|
const user = await db.selectFrom('user').selectAll().where('email', '=', request.body.email).executeTakeFirst()
|
||||||
|
|
||||||
if (!user) {
|
if (!user) {
|
||||||
throw new StatusError(...errors.noUserFound)
|
throw new StatusError(...errors.noUserFound)
|
||||||
} else if (!user.password) {
|
} else if (!user.password) {
|
||||||
throw new StatusError(...errors.notLocal)
|
throw new StatusError(...errors.notLocal)
|
||||||
} else if (user.loginAttempts >= maxLoginAttempts) {
|
} else if (user.loginAttempts && user.loginAttempts >= maxLoginAttempts) {
|
||||||
throw new StatusError(...errors.tooManyLoginAttempts)
|
throw new StatusError(...errors.tooManyLoginAttempts)
|
||||||
}
|
}
|
||||||
|
|
||||||
const result = await verifyPassword(request.body.password, user.password)
|
const result = await verifyPassword(request.body.password, user.password)
|
||||||
|
|
||||||
if (!result) {
|
if (!result) {
|
||||||
await userQueries.onLoginAttempt(user.id)
|
await db
|
||||||
|
.updateTable('user')
|
||||||
|
.set({ lastLoginAttemptAt: sql`now()`, loginAttempts: sql`"loginAttempts" + 1` })
|
||||||
|
.where('id', '=', user.id)
|
||||||
|
.execute()
|
||||||
|
|
||||||
throw new StatusError(...errors.wrongPassword)
|
throw new StatusError(...errors.wrongPassword)
|
||||||
}
|
}
|
||||||
@ -65,7 +68,11 @@ const login: RouteHandler<{ Body: z.infer<typeof BodySchema> }> = async function
|
|||||||
|
|
||||||
await request.login(user)
|
await request.login(user)
|
||||||
|
|
||||||
await userQueries.onLogin(user.id)
|
await db
|
||||||
|
.updateTable('user')
|
||||||
|
.set({ lastLoginAt: sql`now()`, loginAttempts: 0, lastLoginAttemptAt: null })
|
||||||
|
.where('id', '=', user.id)
|
||||||
|
.execute()
|
||||||
|
|
||||||
return reply.status(200).send(_.omit(user, 'password'))
|
return reply.status(200).send(_.omit(user, 'password'))
|
||||||
} catch (err) {
|
} catch (err) {
|
||||||
|
|||||||
@ -1,7 +1,6 @@
|
|||||||
import type { RouteHandler } from 'fastify'
|
import type { RouteHandler } from 'fastify'
|
||||||
import * as z from 'zod'
|
import * as z from 'zod'
|
||||||
import config from '../../../config.ts'
|
import config from '../../../config.ts'
|
||||||
import knex from '../../../lib/knex.ts'
|
|
||||||
import sendMail from '../../../lib/send_mail.ts'
|
import sendMail from '../../../lib/send_mail.ts'
|
||||||
import StatusError from '../../../lib/status_error.ts'
|
import StatusError from '../../../lib/status_error.ts'
|
||||||
import { StatusErrorSchema } from '../../../schemas/status_error.ts'
|
import { StatusErrorSchema } from '../../../schemas/status_error.ts'
|
||||||
@ -21,16 +20,18 @@ const ResponseSchema = {
|
|||||||
}
|
}
|
||||||
|
|
||||||
const forgotPassword: RouteHandler<{ Body: z.infer<typeof BodySchema> }> = async function (request, reply) {
|
const forgotPassword: RouteHandler<{ Body: z.infer<typeof BodySchema> }> = async function (request, reply) {
|
||||||
|
const { db } = request.server
|
||||||
|
|
||||||
const { email } = request.body
|
const { email } = request.body
|
||||||
|
|
||||||
try {
|
try {
|
||||||
const user = await knex('user').first('id').where({ email })
|
const user = await db.selectFrom('user').select('id').where('email', '=', email).executeTakeFirst()
|
||||||
|
|
||||||
if (!user) throw new StatusError(...errors.noUserFound)
|
if (!user) throw new StatusError(...errors.noUserFound)
|
||||||
|
|
||||||
const token = generateToken()
|
const token = generateToken()
|
||||||
|
|
||||||
await knex('password_token').insert({ userId: user.id, token })
|
await db.insertInto('passwordToken').values({ userId: user.id, token }).execute()
|
||||||
|
|
||||||
const link = `${new URL('/admin/change-password', config.site.url)}?email=${encodeURI(email)}&token=${token}`
|
const link = `${new URL('/admin/change-password', config.site.url)}?email=${encodeURI(email)}&token=${token}`
|
||||||
|
|
||||||
|
|||||||
@ -1,9 +1,9 @@
|
|||||||
import type { RouteHandler } from 'fastify'
|
import type { RouteHandler } from 'fastify'
|
||||||
import * as z from 'zod'
|
import * as z from 'zod'
|
||||||
import config from '../../../config.ts'
|
import config from '../../../config.ts'
|
||||||
import knex from '../../../lib/knex.ts'
|
|
||||||
import StatusError from '../../../lib/status_error.ts'
|
import StatusError from '../../../lib/status_error.ts'
|
||||||
import { StatusErrorSchema } from '../../../schemas/status_error.ts'
|
import { StatusErrorSchema } from '../../../schemas/status_error.ts'
|
||||||
|
import { sql } from 'kysely'
|
||||||
|
|
||||||
const { errors, timeouts } = config.auth
|
const { errors, timeouts } = config.auth
|
||||||
|
|
||||||
@ -18,23 +18,37 @@ const ResponseSchema = {
|
|||||||
}
|
}
|
||||||
|
|
||||||
const verifyEmail: RouteHandler<{ Querystring: z.infer<typeof QuerystringSchema> }> = async function (request, reply) {
|
const verifyEmail: RouteHandler<{ Querystring: z.infer<typeof QuerystringSchema> }> = async function (request, reply) {
|
||||||
|
const { db } = request.server
|
||||||
|
|
||||||
try {
|
try {
|
||||||
const { token, email } = request.query
|
const { token, email } = request.query
|
||||||
|
|
||||||
const emailToken = await knex('email_token').first('*').where({ email, token })
|
const emailToken = await db
|
||||||
|
.selectFrom('emailToken')
|
||||||
|
.selectAll()
|
||||||
|
.where((eb) => eb.and({ email, token }))
|
||||||
|
.executeTakeFirst()
|
||||||
|
|
||||||
if (!emailToken) {
|
if (!emailToken) {
|
||||||
throw new StatusError(...errors.tokenNotFound)
|
throw new StatusError(...errors.tokenNotFound)
|
||||||
} else if (emailToken.consumed_at) {
|
} else if (emailToken.consumedAt) {
|
||||||
throw new StatusError(...errors.tokenConsumed)
|
throw new StatusError(...errors.tokenConsumed)
|
||||||
} else if (Date.now() > emailToken.created_at.getTime() + timeouts.verifyEmail) {
|
} else if (Date.now() > emailToken.createdAt.getTime() + timeouts.verifyEmail) {
|
||||||
throw new StatusError(...errors.tokenExpired)
|
throw new StatusError(...errors.tokenExpired)
|
||||||
}
|
}
|
||||||
|
|
||||||
await knex.transaction((trx) =>
|
await db.transaction().execute((trx) =>
|
||||||
Promise.all([
|
Promise.all([
|
||||||
trx('user').update({ email_verified_at: knex.fn.now() }).where('id', emailToken.user_id),
|
trx
|
||||||
trx('email_token').update({ consumed_at: knex.fn.now() }).where('id', emailToken.id),
|
.updateTable('user')
|
||||||
|
.set({ emailVerifiedAt: sql`now()` })
|
||||||
|
.where('id', '=', emailToken.userId)
|
||||||
|
.execute(),
|
||||||
|
trx
|
||||||
|
.updateTable('emailToken')
|
||||||
|
.set({ consumedAt: sql`now()` })
|
||||||
|
.where('id', '=', emailToken.id)
|
||||||
|
.execute(),
|
||||||
]),
|
]),
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|||||||
@ -69,7 +69,7 @@ const entryRoutes: FastifyPluginCallbackZod = (fastify, _, done) => {
|
|||||||
.groupBy(['e.id', 'j.identifier'])
|
.groupBy(['e.id', 'j.identifier'])
|
||||||
.where('e.id', '=', req.params.id)
|
.where('e.id', '=', req.params.id)
|
||||||
.where('t.amount', '>', 0 as ANY)
|
.where('t.amount', '>', 0 as ANY)
|
||||||
.execute()
|
.executeTakeFirst()
|
||||||
},
|
},
|
||||||
})
|
})
|
||||||
|
|
||||||
|
|||||||
@ -55,7 +55,7 @@ const invoiceRoutes: FastifyPluginCallbackZod = (fastify, _, done) => {
|
|||||||
method: 'GET',
|
method: 'GET',
|
||||||
schema: {
|
schema: {
|
||||||
querystring: z.object({
|
querystring: z.object({
|
||||||
supplierId: z.number().optional(),
|
supplierId: z.coerce.number().optional(),
|
||||||
}),
|
}),
|
||||||
response: {
|
response: {
|
||||||
200: z.object({
|
200: z.object({
|
||||||
|
|||||||
@ -23,7 +23,7 @@ const supplierRoutes: FastifyPluginCallbackZod = (fastify, _, done) => {
|
|||||||
url: '/:id',
|
url: '/:id',
|
||||||
method: 'GET',
|
method: 'GET',
|
||||||
schema: {
|
schema: {
|
||||||
params: z.object({ id: z.number() }),
|
params: z.object({ id: z.coerce.number() }),
|
||||||
response: {
|
response: {
|
||||||
200: SupplierSchema,
|
200: SupplierSchema,
|
||||||
},
|
},
|
||||||
|
|||||||
9
shared/types.db_composite.ts
Normal file
9
shared/types.db_composite.ts
Normal file
@ -0,0 +1,9 @@
|
|||||||
|
export type Balance = {
|
||||||
|
accountNumber: string
|
||||||
|
description: string
|
||||||
|
} & Record<number, number>
|
||||||
|
|
||||||
|
export type Result = {
|
||||||
|
accountNumber: number
|
||||||
|
description?: string
|
||||||
|
} & Record<number, number>
|
||||||
Loading…
Reference in New Issue
Block a user