Skip to content

Commit

Permalink
Merge pull request #145 from codeanker/development
Browse files Browse the repository at this point in the history
1.7.0
  • Loading branch information
danielswiatek authored Apr 11, 2024
2 parents 56d30b8 + 7eb698e commit 32f6ba8
Show file tree
Hide file tree
Showing 7 changed files with 274 additions and 40 deletions.
1 change: 1 addition & 0 deletions api/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -32,6 +32,7 @@
"@trpc/server": "^10.28.1",
"config": "^3.3.9",
"dayjs": "^1.11.10",
"fast-csv": "^5.0.1",
"grant": "^5.4.22",
"koa": "^2.14.2",
"koa-body": "^6.0.1",
Expand Down
189 changes: 181 additions & 8 deletions api/src/middleware/importAnmeldungen.ts
Original file line number Diff line number Diff line change
@@ -1,15 +1,188 @@
import * as fs from 'fs'
import * as path from 'path'

import { Role } from '@prisma/client'
import * as csv from 'fast-csv'
import type { Middleware } from 'koa'

import { logger } from '../logger'
import { getEntityIdFromHeader } from '../authentication'
import prisma from '../prisma'
import { inputSchema as anmeldungCreateSchema } from '../services/anmeldung/anmeldungPublicCreate'
import { getPersonCreateData } from '../services/person/schema/person.schema'
import { customFieldValuesCreateMany } from '../types/defineCustomFieldValues'

import { customParseFormat, dayjs } from '@codeanker/helpers'

export const importAnmeldungen: Middleware = async function (ctx) {
for (const file of Object.keys(ctx.request.files!)) {
// TODO: Process file
logger.info(`processing file ${file}`)
dayjs.extend(customParseFormat)

// eslint-disable-next-line @typescript-eslint/no-unused-vars
export const importAnmeldungen: Middleware = async function (ctx, next) {
let accountId
try {
accountId = await getEntityIdFromHeader(ctx.request.header.authorization)
} catch (e) {
ctx.response.status = 401
ctx.response.message = 'Token not valid'
return
}

ctx.response.status = 200
ctx.response.body = {
ok: true,
if (accountId == null || !ctx.request.files || !ctx.request.body.unterveranstaltungId) {
ctx.response.status = 400
ctx.response.message = 'Es wurden keine Dateien oder Unterveranstaltung übergeben'
return
}

const account = await prisma.account.findUnique({
where: {
id: parseInt(accountId),
},
select: {
role: true,
person: {
select: {
firstname: true,
lastname: true,
},
},
},
})

if (account == null) {
ctx.res.statusCode = 401
ctx.res.end()
return
}

if (account.role !== Role.ADMIN) {
ctx.response.status = 401
ctx.response.message = 'Account not authorized'
return
}

let files: any = []
if (Array.isArray(ctx.request.files.files)) {
files = ctx.request.files.files
} else {
files.push(ctx.request.files.files)
}

files.filter((file: any) => {
if (file.mimetype !== 'text/csv') {
ctx.response.status = 406
ctx.response.message = 'Datei muss vom Typ CSV sein'
return
}
})

const unterveranstaltung = await findUnterveranstaltung(parseInt(ctx.request.body.unterveranstaltungId))
if (!unterveranstaltung) {
ctx.response.status = 400
ctx.response.message = 'Unterveranstaltung nicht gefunden'
return
}

files.forEach((file) => {
fs.createReadStream(path.resolve(file.filepath))
.pipe(csv.parse({ headers: true, delimiter: ';', ignoreEmpty: true }))
.on('error', (error) => console.error(error))
.on('data', (row) => createAnmeldung(row, unterveranstaltung))
// eslint-disable-next-line no-console
.on('end', (rowCount: number) => console.log(`Parsed ${rowCount} rows`))

ctx.response.status = 200
ctx.response.body = {
ok: true,
}
})
}

/**
* Erstelle Anmeldung in der Datenbank
* @param row
* @param unterveranstaltungId
*/
async function createAnmeldung(row: any, unterveranstaltung) {
// console.log(unterveranstaltungId, row)

try {
const mappedRow = {
data: {
gliederungId: unterveranstaltung.gliederungId,
unterveranstaltungId: unterveranstaltung.id,
//Person Schema
firstname: row.vorname,
lastname: row.nachname,
birthday: dayjs(row.geburtstag, 'DD.MM.YY').toDate(),
gender: row.geschlecht,
email: row.email,
telefon: row.telefon,
address: {
street: row.strasse,
number: row.hausnummer,
zip: row.plz,
city: row.ort,
},
essgewohnheit: row.essgewohnheit,
nahrungsmittelIntoleranzen: formatNahrungsmittelIntoleranzen(row.nahrungsmittelIntoleranzen),
weitereIntoleranzen: formatNahrungsmittelIntoleranzen(row.weitereIntoleranzen),
notfallkontaktPersonen: [
{
firstname: row.notfallkontaktVorname,
lastname: row.notfallkontaktNachname,
telefon: row.notfallkontaktTelefon,
istErziehungsberechtigt: row.istErziehungsberechtigt === 'Ja' ? true : false,
},
],
},
customFieldValues: [
{
fieldId: parseInt(row.customField1Id),
value: row.customField1Value,
},
],
}

const validatedData = anmeldungCreateSchema.parse(mappedRow)

const personData = await getPersonCreateData(validatedData.data)

await prisma.person.create({
data: {
...personData,
anmeldungen: {
create: {
unterveranstaltungId: unterveranstaltung.id,
comment: validatedData.data.comment,
createdAt: new Date(),
customFieldValues: {
createMany: customFieldValuesCreateMany(validatedData.customFieldValues),
},
},
},
},
})
} catch (e) {
throw new Error('Anmeldung konnte nicht erstellt werden')
}
}
/**
* Suche nach der passenden Unterveranstaltung anhand der ID
* @param unterveranstaltungId
* @returns
*/
async function findUnterveranstaltung(unterveranstaltungId: number) {
return await prisma.unterveranstaltung.findUnique({
where: {
id: unterveranstaltungId,
},
select: {
id: true,
gliederungId: true,
},
})
}

function formatNahrungsmittelIntoleranzen(nahrungsmittelIntoleranzen: string) {
if (!nahrungsmittelIntoleranzen) return []
return nahrungsmittelIntoleranzen.split(',').map((item) => item.trim())
}
22 changes: 3 additions & 19 deletions api/src/middleware/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,23 +3,7 @@ import type Router from 'koa-router'
import { importAnmeldungen } from './importAnmeldungen'

export default function addMiddlewares(router: Router) {
router.post(
'/upload/anmeldungen',
async (ctx, next) => {
if (!ctx.request.files) {
ctx.response.status = 400
return
}

for (const file of Object.keys(ctx.request.files)) {
if (!file.endsWith('.csv')) {
ctx.response.status = 406
return
}
}

return await next()
},
importAnmeldungen
)
router.post('/upload/anmeldungen', async (ctx, next) => {
return await importAnmeldungen(ctx, next)
})
}
24 changes: 13 additions & 11 deletions api/src/services/anmeldung/anmeldungPublicCreate.ts
Original file line number Diff line number Diff line change
Expand Up @@ -9,21 +9,23 @@ import logActivity from '../../util/activity'
import { sendMail } from '../../util/mail'
import { personSchema, getPersonCreateData } from '../person/schema/person.schema'

export const inputSchema = z.strictObject({
data: personSchema.extend({
unterveranstaltungId: z.number().int(),
mahlzeitenIds: z.array(z.number().int()).optional(),
uebernachtungsTage: z.array(z.date()).optional(),
tshirtBestellt: z.boolean().optional(),
email: z.string().email(),
comment: z.string().optional(),
}),
customFieldValues: defineCustomFieldValues(),
})

export const anmeldungPublicCreateProcedure = defineProcedure({
key: 'publicCreate',
method: 'mutation',
protection: { type: 'public' },
inputSchema: z.strictObject({
data: personSchema.extend({
unterveranstaltungId: z.number().int(),
mahlzeitenIds: z.array(z.number().int()).optional(),
uebernachtungsTage: z.array(z.date()).optional(),
tshirtBestellt: z.boolean().optional(),
email: z.string().email(),
comment: z.string().optional(),
}),
customFieldValues: defineCustomFieldValues(),
}),
inputSchema: inputSchema,
async handler(options) {
const unterveranstaltung = await prisma.unterveranstaltung.findUniqueOrThrow({
where: {
Expand Down
73 changes: 73 additions & 0 deletions package-lock.json

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

Loading

0 comments on commit 32f6ba8

Please sign in to comment.