Compare commits
No commits in common. "0e72eb2a62e8a855c0a1d71228d0aadabd6bedf6" and "34bed279db404085de600af4aa2f610d0b3b4982" have entirely different histories.
0e72eb2a62
...
34bed279db
43 changed files with 170 additions and 853 deletions
2
.gitignore
vendored
2
.gitignore
vendored
|
@ -8,5 +8,3 @@ node_modules
|
||||||
!.env.example
|
!.env.example
|
||||||
vite.config.js.timestamp-*
|
vite.config.js.timestamp-*
|
||||||
vite.config.ts.timestamp-*
|
vite.config.ts.timestamp-*
|
||||||
sveltekit-zero-api.d.ts
|
|
||||||
/.vscode
|
|
||||||
|
|
|
@ -46,7 +46,6 @@
|
||||||
"prisma": "^5.7.0",
|
"prisma": "^5.7.0",
|
||||||
"svelte": "^4.2.8",
|
"svelte": "^4.2.8",
|
||||||
"svelte-check": "^3.6.2",
|
"svelte-check": "^3.6.2",
|
||||||
"sveltekit-zero-api": "^0.15.7",
|
|
||||||
"tailwindcss": "^3.3.6",
|
"tailwindcss": "^3.3.6",
|
||||||
"tslib": "^2.6.2",
|
"tslib": "^2.6.2",
|
||||||
"tsx": "^4.7.0",
|
"tsx": "^4.7.0",
|
||||||
|
|
|
@ -88,9 +88,6 @@ devDependencies:
|
||||||
svelte-check:
|
svelte-check:
|
||||||
specifier: ^3.6.2
|
specifier: ^3.6.2
|
||||||
version: 3.6.2(postcss@8.4.32)(svelte@4.2.8)
|
version: 3.6.2(postcss@8.4.32)(svelte@4.2.8)
|
||||||
sveltekit-zero-api:
|
|
||||||
specifier: ^0.15.7
|
|
||||||
version: 0.15.7(@sveltejs/kit@2.0.4)(svelte@4.2.8)(typescript@5.3.3)
|
|
||||||
tailwindcss:
|
tailwindcss:
|
||||||
specifier: ^3.3.6
|
specifier: ^3.3.6
|
||||||
version: 3.3.6
|
version: 3.3.6
|
||||||
|
@ -2774,18 +2771,6 @@ packages:
|
||||||
magic-string: 0.30.5
|
magic-string: 0.30.5
|
||||||
periscopic: 3.1.0
|
periscopic: 3.1.0
|
||||||
|
|
||||||
/sveltekit-zero-api@0.15.7(@sveltejs/kit@2.0.4)(svelte@4.2.8)(typescript@5.3.3):
|
|
||||||
resolution: {integrity: sha512-yklGBeRfHw4dmMjvzskkuXbeakyKWyRqmiVbuD/a4xXPsU3TerBfNKAy6+7Bmpo2KIE2Q6LTdrs+JUQyYYtltA==}
|
|
||||||
peerDependencies:
|
|
||||||
'@sveltejs/kit': ^1.5.5
|
|
||||||
svelte: ^3.55.1
|
|
||||||
typescript: '>=5.0.0'
|
|
||||||
dependencies:
|
|
||||||
'@sveltejs/kit': 2.0.4(@sveltejs/vite-plugin-svelte@3.0.1)(svelte@4.2.8)(vite@5.0.10)
|
|
||||||
svelte: 4.2.8
|
|
||||||
typescript: 5.3.3
|
|
||||||
dev: true
|
|
||||||
|
|
||||||
/tailwindcss@3.3.6:
|
/tailwindcss@3.3.6:
|
||||||
resolution: {integrity: sha512-AKjF7qbbLvLaPieoKeTjG1+FyNZT6KaJMJPFeQyLfIp7l82ggH1fbHJSsYIvnbTFQOlkh+gBYpyby5GT1LIdLw==}
|
resolution: {integrity: sha512-AKjF7qbbLvLaPieoKeTjG1+FyNZT6KaJMJPFeQyLfIp7l82ggH1fbHJSsYIvnbTFQOlkh+gBYpyby5GT1LIdLw==}
|
||||||
engines: {node: '>=14.0.0'}
|
engines: {node: '>=14.0.0'}
|
||||||
|
|
|
@ -114,7 +114,7 @@ ALTER TABLE "rooms" ADD CONSTRAINT "rooms_station_id_fkey" FOREIGN KEY ("station
|
||||||
ALTER TABLE "patients" ADD CONSTRAINT "patients_room_id_fkey" FOREIGN KEY ("room_id") REFERENCES "rooms"("id") ON DELETE SET NULL ON UPDATE CASCADE;
|
ALTER TABLE "patients" ADD CONSTRAINT "patients_room_id_fkey" FOREIGN KEY ("room_id") REFERENCES "rooms"("id") ON DELETE SET NULL ON UPDATE CASCADE;
|
||||||
|
|
||||||
-- AddForeignKey
|
-- AddForeignKey
|
||||||
ALTER TABLE "entries" ADD CONSTRAINT "entries_patient_id_fkey" FOREIGN KEY ("patient_id") REFERENCES "patients"("id") ON DELETE RESTRICT ON UPDATE CASCADE;
|
ALTER TABLE "entries" ADD CONSTRAINT "entries_patient_id_fkey" FOREIGN KEY ("patient_id") REFERENCES "patients"("id") ON DELETE CASCADE ON UPDATE CASCADE;
|
||||||
|
|
||||||
-- AddForeignKey
|
-- AddForeignKey
|
||||||
ALTER TABLE "entry_versions" ADD CONSTRAINT "entry_versions_entry_id_fkey" FOREIGN KEY ("entry_id") REFERENCES "entries"("id") ON DELETE CASCADE ON UPDATE CASCADE;
|
ALTER TABLE "entry_versions" ADD CONSTRAINT "entry_versions_entry_id_fkey" FOREIGN KEY ("entry_id") REFERENCES "entries"("id") ON DELETE CASCADE ON UPDATE CASCADE;
|
||||||
|
|
|
@ -1,5 +1,3 @@
|
||||||
CREATE EXTENSION pg_trgm;
|
|
||||||
|
|
||||||
ALTER TABLE entries
|
ALTER TABLE entries
|
||||||
ADD COLUMN tsvec tsvector;
|
ADD COLUMN tsvec tsvector;
|
||||||
|
|
||||||
|
@ -29,8 +27,3 @@ AFTER INSERT
|
||||||
OR
|
OR
|
||||||
UPDATE ON entry_executions FOR EACH ROW
|
UPDATE ON entry_executions FOR EACH ROW
|
||||||
EXECUTE PROCEDURE update_entry_tsvec ();
|
EXECUTE PROCEDURE update_entry_tsvec ();
|
||||||
|
|
||||||
ALTER TABLE patients
|
|
||||||
ADD COLUMN full_name TEXT GENERATED ALWAYS AS (first_name || ' ' || last_name) STORED;
|
|
||||||
|
|
||||||
CREATE INDEX patients_full_name ON patients USING gin (full_name gin_trgm_ops);
|
|
||||||
|
|
|
@ -93,7 +93,7 @@ model Category {
|
||||||
|
|
||||||
model Entry {
|
model Entry {
|
||||||
id Int @id @default(autoincrement())
|
id Int @id @default(autoincrement())
|
||||||
patient Patient @relation(fields: [patient_id], references: [id], onDelete: Restrict)
|
patient Patient @relation(fields: [patient_id], references: [id], onDelete: Cascade)
|
||||||
patient_id Int
|
patient_id Int
|
||||||
|
|
||||||
EntryVersion EntryVersion[]
|
EntryVersion EntryVersion[]
|
||||||
|
|
|
@ -9,9 +9,4 @@ echo 'Waiting for database to be ready...'
|
||||||
|
|
||||||
# Create temporary test database
|
# Create temporary test database
|
||||||
docker-compose exec -u 999:999 db sh -e -c 'dropdb -f --if-exists test; createdb test'
|
docker-compose exec -u 999:999 db sh -e -c 'dropdb -f --if-exists test; createdb test'
|
||||||
cd "$DIR/../"
|
cd "$DIR/../" && DATABASE_URL="postgresql://postgres:1234@localhost:5432/test?schema=public" npx prisma migrate reset --force
|
||||||
DATABASE_URL="postgresql://postgres:1234@localhost:5432/test?schema=public" npx prisma migrate reset --force
|
|
||||||
|
|
||||||
# Reset main database
|
|
||||||
npx prisma migrate reset --force
|
|
||||||
npx tsx run/gen-mockdata.ts
|
|
||||||
|
|
|
@ -1,9 +0,0 @@
|
||||||
import { createZeroApi } from "sveltekit-zero-api/api";
|
|
||||||
import type { GeneratedAPI } from "./sveltekit-zero-api";
|
|
||||||
|
|
||||||
const routes = createZeroApi({
|
|
||||||
// eslint-disable-next-line no-console
|
|
||||||
onError: async (err) => console.error("[API]", err),
|
|
||||||
}) as GeneratedAPI;
|
|
||||||
|
|
||||||
export default routes.api;
|
|
|
@ -1,69 +0,0 @@
|
||||||
import { ErrorConflict, ErrorNotFound } from "$lib/shared/util/error";
|
|
||||||
import { ZodError } from "zod";
|
|
||||||
import {
|
|
||||||
Ok,
|
|
||||||
BadRequest,
|
|
||||||
NotFound,
|
|
||||||
Conflict,
|
|
||||||
InternalServerError,
|
|
||||||
} from "sveltekit-zero-api/http";
|
|
||||||
import { PrismaClientKnownRequestError } from "@prisma/client/runtime/library";
|
|
||||||
import { userId } from "./util";
|
|
||||||
import type { RequestEvent } from "@sveltejs/kit";
|
|
||||||
|
|
||||||
function handleError(error: unknown) {
|
|
||||||
if (error instanceof ZodError) {
|
|
||||||
// return { status: 400, msg: "Invalid input", data: error.flatten() };
|
|
||||||
return BadRequest({
|
|
||||||
body: { status: 400, msg: "Invalid input", data: error.flatten() },
|
|
||||||
});
|
|
||||||
} else if (error instanceof PrismaClientKnownRequestError) {
|
|
||||||
if (error.code === "P2025") {
|
|
||||||
return NotFound({
|
|
||||||
body: { status: 404, msg: error.message },
|
|
||||||
});
|
|
||||||
} else {
|
|
||||||
return InternalServerError({
|
|
||||||
body: { status: 500, msg: error.message, prismaCode: error.code },
|
|
||||||
});
|
|
||||||
}
|
|
||||||
} else if (error instanceof ErrorNotFound) {
|
|
||||||
return NotFound({
|
|
||||||
body: { status: 404, msg: error.message },
|
|
||||||
});
|
|
||||||
} else if (error instanceof ErrorConflict) {
|
|
||||||
return Conflict({
|
|
||||||
body: { status: 409, msg: error.message },
|
|
||||||
});
|
|
||||||
} else if (error instanceof Error) {
|
|
||||||
return InternalServerError({
|
|
||||||
body: { status: 500, msg: error.message },
|
|
||||||
});
|
|
||||||
} else {
|
|
||||||
return InternalServerError({
|
|
||||||
body: { status: 500, msg: "unknown error" },
|
|
||||||
});
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
export async function apiWrap<T>(f: () => Promise<T>) {
|
|
||||||
try {
|
|
||||||
const body = await f();
|
|
||||||
return Ok({ body });
|
|
||||||
} catch (error) {
|
|
||||||
return handleError(error);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
export async function apiWrapUser<T>(
|
|
||||||
event: RequestEvent,
|
|
||||||
f: (uId: number) => Promise<T>
|
|
||||||
) {
|
|
||||||
try {
|
|
||||||
const uId = await userId(event);
|
|
||||||
const body = await f(uId);
|
|
||||||
return Ok({ body });
|
|
||||||
} catch (error) {
|
|
||||||
return handleError(error);
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -1,22 +1,13 @@
|
||||||
import type { Category, CategoryNew } from "$lib/shared/model";
|
import type { Category, CategoryNew } from "$lib/shared/model";
|
||||||
|
import { ZCategoryNew } from "$lib/shared/model/validation";
|
||||||
import { prisma } from "$lib/server/prisma";
|
import { prisma } from "$lib/server/prisma";
|
||||||
|
|
||||||
export async function newCategory(category: CategoryNew): Promise<number> {
|
export async function newCategory(category: CategoryNew): Promise<number> {
|
||||||
const created = await prisma.category.create({
|
const data = ZCategoryNew.parse(category);
|
||||||
data: category,
|
const created = await prisma.category.create({ data, select: { id: true } });
|
||||||
select: { id: true },
|
|
||||||
});
|
|
||||||
return created.id;
|
return created.id;
|
||||||
}
|
}
|
||||||
|
|
||||||
export async function updateCategory(id: number, category: Partial<CategoryNew>) {
|
|
||||||
await prisma.category.update({ where: { id }, data: category });
|
|
||||||
}
|
|
||||||
|
|
||||||
export async function deleteCategory(id: number) {
|
|
||||||
await prisma.category.delete({ where: { id } });
|
|
||||||
}
|
|
||||||
|
|
||||||
export async function getCategory(id: number): Promise<Category> {
|
export async function getCategory(id: number): Promise<Category> {
|
||||||
return prisma.category.findUniqueOrThrow({ where: { id } });
|
return prisma.category.findUniqueOrThrow({ where: { id } });
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,16 +1,19 @@
|
||||||
import { prisma } from "$lib/server/prisma";
|
import { prisma } from "$lib/server/prisma";
|
||||||
import type {
|
import type {
|
||||||
EntriesFilter,
|
EntriesRequest,
|
||||||
Entry,
|
Entry,
|
||||||
EntryExecutionNew,
|
EntryExecutionNew,
|
||||||
EntryNew,
|
EntryNew,
|
||||||
EntryVersion,
|
|
||||||
EntryVersionNew,
|
EntryVersionNew,
|
||||||
Pagination,
|
Pagination,
|
||||||
PaginationRequest,
|
|
||||||
} from "$lib/shared/model";
|
} from "$lib/shared/model";
|
||||||
|
import {
|
||||||
|
ZEntryExecutionNew,
|
||||||
|
ZEntryNew,
|
||||||
|
ZEntryVersionNew,
|
||||||
|
} from "$lib/shared/model/validation";
|
||||||
import { ErrorConflict } from "$lib/shared/util/error";
|
import { ErrorConflict } from "$lib/shared/util/error";
|
||||||
import { mapEntry, mapVersion } from "./mapping";
|
import { mapEntry } from "./mapping";
|
||||||
import { QueryBuilder, parseSearchQuery } from "./util";
|
import { QueryBuilder, parseSearchQuery } from "./util";
|
||||||
|
|
||||||
const USER_SELECT = { select: { id: true, name: true } };
|
const USER_SELECT = { select: { id: true, name: true } };
|
||||||
|
@ -36,24 +39,15 @@ export async function getEntry(id: number): Promise<Entry> {
|
||||||
return mapEntry(entry);
|
return mapEntry(entry);
|
||||||
}
|
}
|
||||||
|
|
||||||
export async function getEntryHistory(id: number): Promise<EntryVersion[]> {
|
|
||||||
const entries = await prisma.entryVersion.findMany({
|
|
||||||
where: { entry_id: id },
|
|
||||||
include: { author: USER_SELECT, category: true },
|
|
||||||
orderBy: { created_at: "desc" },
|
|
||||||
});
|
|
||||||
|
|
||||||
return entries.map(mapVersion);
|
|
||||||
}
|
|
||||||
|
|
||||||
export async function newEntry(author_id: number, entry: EntryNew): Promise<number> {
|
export async function newEntry(author_id: number, entry: EntryNew): Promise<number> {
|
||||||
|
const data = ZEntryNew.parse(entry);
|
||||||
const created = await prisma.entry.create({
|
const created = await prisma.entry.create({
|
||||||
data: {
|
data: {
|
||||||
patient_id: entry.patient_id,
|
patient_id: data.patient_id,
|
||||||
EntryVersion: {
|
EntryVersion: {
|
||||||
create: {
|
create: {
|
||||||
author_id,
|
author_id,
|
||||||
...entry.version,
|
...data.version,
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
|
@ -67,6 +61,8 @@ export async function newEntryVersion(
|
||||||
entry_id: number,
|
entry_id: number,
|
||||||
version: EntryVersionNew
|
version: EntryVersionNew
|
||||||
): Promise<number> {
|
): Promise<number> {
|
||||||
|
const data = ZEntryVersionNew.parse(version);
|
||||||
|
|
||||||
return prisma.$transaction(async (tx) => {
|
return prisma.$transaction(async (tx) => {
|
||||||
const entry = await tx.entry.findUniqueOrThrow({
|
const entry = await tx.entry.findUniqueOrThrow({
|
||||||
where: { id: entry_id },
|
where: { id: entry_id },
|
||||||
|
@ -80,7 +76,7 @@ export async function newEntryVersion(
|
||||||
const cver = entry.EntryVersion[0];
|
const cver = entry.EntryVersion[0];
|
||||||
|
|
||||||
// Check if the entry has been updated by someone else
|
// Check if the entry has been updated by someone else
|
||||||
if (version.old_version && (!cver || cver.id !== version.old_version)) {
|
if (data.old_version && (!cver || cver.id !== data.old_version)) {
|
||||||
throw new ErrorConflict("old version id does not match");
|
throw new ErrorConflict("old version id does not match");
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -89,10 +85,10 @@ export async function newEntryVersion(
|
||||||
// Old version
|
// Old version
|
||||||
entry_id,
|
entry_id,
|
||||||
author_id,
|
author_id,
|
||||||
text: version.text || cver.text,
|
text: data.text || cver.text,
|
||||||
date: version.date || cver.date,
|
date: data.date || cver.date,
|
||||||
category_id: version.category_id || cver.category_id,
|
category_id: data.category_id || cver.category_id,
|
||||||
priority: version.priority || cver.priority,
|
priority: data.priority || cver.priority,
|
||||||
},
|
},
|
||||||
select: { id: true },
|
select: { id: true },
|
||||||
});
|
});
|
||||||
|
@ -105,6 +101,8 @@ export async function newEntryExecution(
|
||||||
entry_id: number,
|
entry_id: number,
|
||||||
execution: EntryExecutionNew
|
execution: EntryExecutionNew
|
||||||
): Promise<number> {
|
): Promise<number> {
|
||||||
|
const data = ZEntryExecutionNew.parse(execution);
|
||||||
|
|
||||||
return prisma.$transaction(async (tx) => {
|
return prisma.$transaction(async (tx) => {
|
||||||
const entry = await tx.entry.findUniqueOrThrow({
|
const entry = await tx.entry.findUniqueOrThrow({
|
||||||
where: { id: entry_id },
|
where: { id: entry_id },
|
||||||
|
@ -119,8 +117,8 @@ export async function newEntryExecution(
|
||||||
|
|
||||||
// Check if the execution has been updated by someone else
|
// Check if the execution has been updated by someone else
|
||||||
if (
|
if (
|
||||||
(execution.old_execution && (!cex || cex.id !== execution.old_execution)) ||
|
(data.old_execution && (!cex || cex.id !== data.old_execution)) ||
|
||||||
(execution.old_execution === null && cex)
|
(data.old_execution === null && cex)
|
||||||
) {
|
) {
|
||||||
throw new ErrorConflict("old execution id does not match");
|
throw new ErrorConflict("old execution id does not match");
|
||||||
}
|
}
|
||||||
|
@ -129,7 +127,7 @@ export async function newEntryExecution(
|
||||||
data: {
|
data: {
|
||||||
entry_id,
|
entry_id,
|
||||||
author_id,
|
author_id,
|
||||||
text: execution.text,
|
text: data.text,
|
||||||
},
|
},
|
||||||
select: { id: true },
|
select: { id: true },
|
||||||
});
|
});
|
||||||
|
@ -137,10 +135,7 @@ export async function newEntryExecution(
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
export async function getEntries(
|
export async function getEntries(req: EntriesRequest): Promise<Pagination<Entry>> {
|
||||||
filter: EntriesFilter,
|
|
||||||
pagination: PaginationRequest
|
|
||||||
): Promise<Pagination<Entry>> {
|
|
||||||
const qb = new QueryBuilder(
|
const qb = new QueryBuilder(
|
||||||
`select
|
`select
|
||||||
e.id,
|
e.id,
|
||||||
|
@ -202,29 +197,29 @@ join rooms r on r.id = p.room_id
|
||||||
join stations s on s.id = r.station_id`
|
join stations s on s.id = r.station_id`
|
||||||
);
|
);
|
||||||
|
|
||||||
if (filter?.search && filter.search.length > 0) {
|
if (req.filter?.search && req.filter.search.length > 0) {
|
||||||
const query = parseSearchQuery(filter.search);
|
const query = parseSearchQuery(req.filter.search);
|
||||||
qb.addFilterClause(
|
qb.addFilterClause(
|
||||||
`to_tsquery('german', ${qb.pvar()}) @@ e.tsvec`,
|
`to_tsquery('german', ${qb.pvar()}) @@ e.tsvec`,
|
||||||
query.toTsquery()
|
query.toTsquery()
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
if (filter?.done === true) {
|
if (req.filter?.done === true) {
|
||||||
qb.addFilterClause("ex.id is not null");
|
qb.addFilterClause("ex.id is not null");
|
||||||
} else if (filter?.done === false) {
|
} else if (req.filter?.done === false) {
|
||||||
qb.addFilterClause("ex.id is null");
|
qb.addFilterClause("ex.id is null");
|
||||||
}
|
}
|
||||||
|
|
||||||
qb.addFilterList("xau.id", filter?.executor);
|
qb.addFilterList("xau.id", req.filter?.executor);
|
||||||
qb.addFilterList("c.id", filter?.category);
|
qb.addFilterList("c.id", req.filter?.category);
|
||||||
qb.addFilterList("p.id", filter?.patient);
|
qb.addFilterList("p.id", req.filter?.patient);
|
||||||
qb.addFilterList("s.id", filter?.station);
|
qb.addFilterList("s.id", req.filter?.station);
|
||||||
qb.addFilterList("r.id", filter?.room);
|
qb.addFilterList("r.id", req.filter?.room);
|
||||||
qb.addFilter("ev.priority", filter?.priority);
|
qb.addFilter("ev.priority", req.filter?.priority);
|
||||||
|
|
||||||
if (filter?.author) {
|
if (req.filter?.author) {
|
||||||
let author = filter?.author;
|
let author = req.filter?.author;
|
||||||
if (!Array.isArray(author)) {
|
if (!Array.isArray(author)) {
|
||||||
author = [author];
|
author = [author];
|
||||||
}
|
}
|
||||||
|
@ -234,10 +229,10 @@ join stations s on s.id = r.station_id`
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
qb.addOrderClause("e.created_at desc");
|
qb.setOrderClause(`order by e.created_at desc`);
|
||||||
qb.setPagination(pagination);
|
if (req.pagination) qb.setPagination(req.pagination);
|
||||||
|
|
||||||
type RowItem = {
|
type RequestItem = {
|
||||||
id: number;
|
id: number;
|
||||||
created_at: Date;
|
created_at: Date;
|
||||||
text: string;
|
text: string;
|
||||||
|
@ -269,9 +264,10 @@ join stations s on s.id = r.station_id`
|
||||||
const [res, countRes] = (await Promise.all([
|
const [res, countRes] = (await Promise.all([
|
||||||
prisma.$queryRawUnsafe(qb.getQuery(), ...qb.getParams()),
|
prisma.$queryRawUnsafe(qb.getQuery(), ...qb.getParams()),
|
||||||
prisma.$queryRawUnsafe(qb.getCountQuery(), ...qb.getCountParams()),
|
prisma.$queryRawUnsafe(qb.getCountQuery(), ...qb.getCountParams()),
|
||||||
])) as [RowItem[], { count: bigint }[]];
|
])) as [RequestItem[], { count: bigint }[]];
|
||||||
|
|
||||||
const total = Number(countRes[0].count);
|
const total = Number(countRes[0].count);
|
||||||
|
|
||||||
const items: Entry[] = res.map((item) => {
|
const items: Entry[] = res.map((item) => {
|
||||||
return {
|
return {
|
||||||
id: item.id,
|
id: item.id,
|
||||||
|
|
|
@ -1,12 +1,4 @@
|
||||||
import type {
|
import type { Entry, Patient, User, UserTag, Room } from "$lib/shared/model";
|
||||||
Entry,
|
|
||||||
Patient,
|
|
||||||
User,
|
|
||||||
UserTag,
|
|
||||||
Room,
|
|
||||||
EntryVersion,
|
|
||||||
EntryExecution,
|
|
||||||
} from "$lib/shared/model";
|
|
||||||
import { ErrorNotFound } from "$lib/shared/util/error";
|
import { ErrorNotFound } from "$lib/shared/util/error";
|
||||||
import type {
|
import type {
|
||||||
Patient as DbPatient,
|
Patient as DbPatient,
|
||||||
|
@ -26,11 +18,6 @@ type DbEntryVersionLn = DbEntryVersion & {
|
||||||
author: UserTag;
|
author: UserTag;
|
||||||
};
|
};
|
||||||
type DbEntryExecutionLn = DbEntryExecution & { author: UserTag };
|
type DbEntryExecutionLn = DbEntryExecution & { author: UserTag };
|
||||||
type DbEntryFull = DbEntry & {
|
|
||||||
EntryVersion: DbEntryVersionLn[];
|
|
||||||
EntryExecution: DbEntryExecutionLn[];
|
|
||||||
patient: DbPatientLn;
|
|
||||||
};
|
|
||||||
|
|
||||||
export function mapPatient(patient: DbPatientLn): Patient {
|
export function mapPatient(patient: DbPatientLn): Patient {
|
||||||
return {
|
return {
|
||||||
|
@ -61,28 +48,13 @@ export function mapRoom(room: DbRoomLn): Room {
|
||||||
return { id: room.id, name: room.name, station: room.station };
|
return { id: room.id, name: room.name, station: room.station };
|
||||||
}
|
}
|
||||||
|
|
||||||
export function mapVersion(version: DbEntryVersionLn): EntryVersion {
|
export function mapEntry(
|
||||||
return {
|
entry: DbEntry & {
|
||||||
id: version.id,
|
EntryVersion: DbEntryVersionLn[];
|
||||||
text: version.text,
|
EntryExecution: DbEntryExecutionLn[];
|
||||||
date: version.date,
|
patient: DbPatientLn;
|
||||||
category: version.category,
|
|
||||||
priority: version.priority,
|
|
||||||
author: version.author,
|
|
||||||
created_at: version.created_at,
|
|
||||||
};
|
|
||||||
}
|
}
|
||||||
|
): Entry {
|
||||||
function mapExecution(execution: DbEntryExecutionLn): EntryExecution {
|
|
||||||
return {
|
|
||||||
id: execution.id,
|
|
||||||
author: execution.author,
|
|
||||||
created_at: execution.created_at,
|
|
||||||
text: execution.text,
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
export function mapEntry(entry: DbEntryFull): Entry {
|
|
||||||
const v = entry.EntryVersion[0];
|
const v = entry.EntryVersion[0];
|
||||||
if (!v) throw new ErrorNotFound("no version associated with that entry");
|
if (!v) throw new ErrorNotFound("no version associated with that entry");
|
||||||
const x = entry.EntryExecution[0];
|
const x = entry.EntryExecution[0];
|
||||||
|
@ -91,7 +63,22 @@ export function mapEntry(entry: DbEntryFull): Entry {
|
||||||
id: entry.id,
|
id: entry.id,
|
||||||
patient: mapPatient(entry.patient),
|
patient: mapPatient(entry.patient),
|
||||||
created_at: entry.created_at,
|
created_at: entry.created_at,
|
||||||
current_version: mapVersion(v),
|
current_version: {
|
||||||
execution: x ? mapExecution(x) : null,
|
id: v.id,
|
||||||
|
text: v.text,
|
||||||
|
date: v.date,
|
||||||
|
category: v.category,
|
||||||
|
priority: v.priority,
|
||||||
|
author: v.author,
|
||||||
|
created_at: v.created_at,
|
||||||
|
},
|
||||||
|
execution: x
|
||||||
|
? {
|
||||||
|
id: x.id,
|
||||||
|
author: x.author,
|
||||||
|
created_at: x.created_at,
|
||||||
|
text: x.text,
|
||||||
|
}
|
||||||
|
: null,
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,43 +1,21 @@
|
||||||
import type {
|
import type {
|
||||||
Patient,
|
Patient,
|
||||||
PatientNew,
|
PatientNew,
|
||||||
|
PatientsRequest,
|
||||||
Pagination,
|
Pagination,
|
||||||
PaginationRequest,
|
|
||||||
PatientsFilter,
|
|
||||||
} from "$lib/shared/model";
|
} from "$lib/shared/model";
|
||||||
|
import { ZPatientNew } from "$lib/shared/model/validation";
|
||||||
import { prisma } from "$lib/server/prisma";
|
import { prisma } from "$lib/server/prisma";
|
||||||
import { PrismaClientKnownRequestError } from "@prisma/client/runtime/library";
|
|
||||||
import { mapPatient } from "./mapping";
|
import { mapPatient } from "./mapping";
|
||||||
import { QueryBuilder } from "./util";
|
import { PAGINATION_LIMIT } from "$lib/shared/constants";
|
||||||
import { ErrorConflict } from "$lib/shared/util/error";
|
import { convertFilterList } from "./util";
|
||||||
|
|
||||||
export async function newPatient(patient: PatientNew): Promise<number> {
|
export async function newPatient(patient: PatientNew): Promise<number> {
|
||||||
const created = await prisma.patient.create({ data: patient, select: { id: true } });
|
const data = ZPatientNew.parse(patient);
|
||||||
|
const created = await prisma.patient.create({ data, select: { id: true } });
|
||||||
return created.id;
|
return created.id;
|
||||||
}
|
}
|
||||||
|
|
||||||
/** Update a patient */
|
|
||||||
export async function updatePatient(id: number, patient: Partial<PatientNew>) {
|
|
||||||
await prisma.patient.update({ where: { id }, data: patient });
|
|
||||||
}
|
|
||||||
|
|
||||||
/** Delete a patient (Note: this only works if the patient is not associated with any entries) */
|
|
||||||
export async function deletePatient(id: number) {
|
|
||||||
try {
|
|
||||||
await prisma.patient.delete({ where: { id } });
|
|
||||||
} catch (error) {
|
|
||||||
if (error instanceof PrismaClientKnownRequestError) {
|
|
||||||
// Foreign key constraint failed
|
|
||||||
if (error.code === "P2003") {
|
|
||||||
throw new ErrorConflict("cannot delete patient with entries");
|
|
||||||
}
|
|
||||||
}
|
|
||||||
throw error;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// TODO: Hide a patient
|
|
||||||
|
|
||||||
export async function getPatient(id: number): Promise<Patient> {
|
export async function getPatient(id: number): Promise<Patient> {
|
||||||
const patient = await prisma.patient.findUniqueOrThrow({
|
const patient = await prisma.patient.findUniqueOrThrow({
|
||||||
where: { id },
|
where: { id },
|
||||||
|
@ -48,68 +26,30 @@ export async function getPatient(id: number): Promise<Patient> {
|
||||||
return mapPatient(patient);
|
return mapPatient(patient);
|
||||||
}
|
}
|
||||||
|
|
||||||
export async function getPatients(
|
export async function getPatients(req: PatientsRequest): Promise<Pagination<Patient>> {
|
||||||
filter: PatientsFilter,
|
const offset = req.pagination?.offset || 0;
|
||||||
pagination: PaginationRequest
|
const where = {
|
||||||
): Promise<Pagination<Patient>> {
|
room_id: convertFilterList(req.filter?.room),
|
||||||
const qb = new QueryBuilder(
|
|
||||||
`select p.id, p.first_name, p.last_name, p.created_at, p.age,
|
|
||||||
r.id as room_id, r.name as room_name, s.id as station_id, s.name as station_name`,
|
|
||||||
`from patients p
|
|
||||||
join rooms r on r.id = p.room_id
|
|
||||||
join stations s on s.id = r.station_id`
|
|
||||||
);
|
|
||||||
|
|
||||||
if (filter.search && filter.search.length > 0) {
|
|
||||||
const qvar = qb.pvar();
|
|
||||||
qb.addFilterClause(`p.full_name % ${qvar}`, filter.search);
|
|
||||||
qb.addOrderClause(`similarity(p.full_name, ${qvar}) desc`);
|
|
||||||
}
|
|
||||||
|
|
||||||
qb.addFilterList("r.id", filter.room);
|
|
||||||
qb.addFilterList("s.id", filter.station);
|
|
||||||
qb.addOrderClause("p.created_at desc");
|
|
||||||
qb.setPagination(pagination);
|
|
||||||
|
|
||||||
type RowItem = {
|
|
||||||
id: number;
|
|
||||||
first_name: string;
|
|
||||||
last_name: string;
|
|
||||||
created_at: Date;
|
|
||||||
age: number;
|
|
||||||
room_id: number;
|
|
||||||
room_name: string;
|
|
||||||
station_id: number;
|
|
||||||
station_name: string;
|
|
||||||
};
|
|
||||||
|
|
||||||
const [res, countRes] = (await Promise.all([
|
|
||||||
prisma.$queryRawUnsafe(qb.getQuery(), ...qb.getParams()),
|
|
||||||
prisma.$queryRawUnsafe(qb.getCountQuery(), ...qb.getCountParams()),
|
|
||||||
])) as [RowItem[], { count: bigint }[]];
|
|
||||||
|
|
||||||
const total = Number(countRes[0].count);
|
|
||||||
const items: Patient[] = res.map((patient) => {
|
|
||||||
return {
|
|
||||||
id: patient.id,
|
|
||||||
first_name: patient.first_name,
|
|
||||||
last_name: patient.last_name,
|
|
||||||
age: patient.age,
|
|
||||||
created_at: patient.created_at,
|
|
||||||
room: {
|
room: {
|
||||||
id: patient.room_id,
|
station_id: convertFilterList(req.filter?.station),
|
||||||
name: patient.room_name,
|
|
||||||
station: {
|
|
||||||
id: patient.station_id,
|
|
||||||
name: patient.station_name,
|
|
||||||
},
|
|
||||||
},
|
},
|
||||||
};
|
};
|
||||||
});
|
const [patients, total] = await Promise.all([
|
||||||
|
prisma.patient.findMany({
|
||||||
|
where,
|
||||||
|
include: {
|
||||||
|
room: { include: { station: true } },
|
||||||
|
},
|
||||||
|
orderBy: { created_at: "desc" },
|
||||||
|
skip: offset,
|
||||||
|
take: req.pagination?.limit || PAGINATION_LIMIT,
|
||||||
|
}),
|
||||||
|
prisma.patient.count({ where }),
|
||||||
|
]);
|
||||||
|
|
||||||
return {
|
return {
|
||||||
items,
|
items: patients.map(mapPatient),
|
||||||
offset: qb.getOffset(),
|
offset,
|
||||||
total,
|
total,
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,20 +1,14 @@
|
||||||
import type { RoomNew, Room, Station, StationNew } from "$lib/shared/model";
|
import type { RoomNew, Room, Station, StationNew } from "$lib/shared/model";
|
||||||
|
import { ZRoomNew, ZStationNew } from "$lib/shared/model/validation";
|
||||||
import { prisma } from "$lib/server/prisma";
|
import { prisma } from "$lib/server/prisma";
|
||||||
import { mapRoom } from "./mapping";
|
import { mapRoom } from "./mapping";
|
||||||
|
|
||||||
export async function newStation(station: StationNew): Promise<number> {
|
export async function newStation(station: StationNew): Promise<number> {
|
||||||
const created = await prisma.station.create({ data: station, select: { id: true } });
|
const data = ZStationNew.parse(station);
|
||||||
|
const created = await prisma.station.create({ data, select: { id: true } });
|
||||||
return created.id;
|
return created.id;
|
||||||
}
|
}
|
||||||
|
|
||||||
export async function updateStation(id: number, station: Partial<StationNew>) {
|
|
||||||
await prisma.station.update({ where: { id }, data: station });
|
|
||||||
}
|
|
||||||
|
|
||||||
export async function deleteStation(id: number) {
|
|
||||||
await prisma.station.delete({ where: { id } });
|
|
||||||
}
|
|
||||||
|
|
||||||
export async function getStation(id: number): Promise<Station> {
|
export async function getStation(id: number): Promise<Station> {
|
||||||
return await prisma.station.findUniqueOrThrow({ where: { id } });
|
return await prisma.station.findUniqueOrThrow({ where: { id } });
|
||||||
}
|
}
|
||||||
|
@ -24,18 +18,11 @@ export async function getStations(): Promise<Station[]> {
|
||||||
}
|
}
|
||||||
|
|
||||||
export async function newRoom(room: RoomNew): Promise<number> {
|
export async function newRoom(room: RoomNew): Promise<number> {
|
||||||
const created = await prisma.room.create({ data: room, select: { id: true } });
|
const data = ZRoomNew.parse(room);
|
||||||
|
const created = await prisma.room.create({ data, select: { id: true } });
|
||||||
return created.id;
|
return created.id;
|
||||||
}
|
}
|
||||||
|
|
||||||
export async function updateRoom(id: number, room: Partial<RoomNew>) {
|
|
||||||
await prisma.room.update({ where: { id }, data: room });
|
|
||||||
}
|
|
||||||
|
|
||||||
export async function deleteRoom(id: number) {
|
|
||||||
await prisma.room.delete({ where: { id } });
|
|
||||||
}
|
|
||||||
|
|
||||||
export async function getRoom(id: number): Promise<Room> {
|
export async function getRoom(id: number): Promise<Room> {
|
||||||
const room = await prisma.room.findUniqueOrThrow({
|
const room = await prisma.room.findUniqueOrThrow({
|
||||||
where: { id },
|
where: { id },
|
||||||
|
|
|
@ -1,4 +1,4 @@
|
||||||
import type { Pagination, PaginationRequest, User, UserTag } from "$lib/shared/model";
|
import type { Pagination, User, UserTag, UsersRequest } from "$lib/shared/model";
|
||||||
import { prisma } from "$lib/server/prisma";
|
import { prisma } from "$lib/server/prisma";
|
||||||
import { mapUser, mapUserTag } from "./mapping";
|
import { mapUser, mapUserTag } from "./mapping";
|
||||||
import { PAGINATION_LIMIT } from "$lib/shared/constants";
|
import { PAGINATION_LIMIT } from "$lib/shared/constants";
|
||||||
|
@ -8,15 +8,13 @@ export async function getUser(id: number): Promise<User> {
|
||||||
return mapUser(user);
|
return mapUser(user);
|
||||||
}
|
}
|
||||||
|
|
||||||
export async function getUsers(
|
export async function getUsers(req: UsersRequest): Promise<Pagination<UserTag>> {
|
||||||
pagination: PaginationRequest
|
const offset = req.pagination?.offset || 0;
|
||||||
): Promise<Pagination<UserTag>> {
|
|
||||||
const offset = pagination.offset || 0;
|
|
||||||
const [users, total] = await Promise.all([
|
const [users, total] = await Promise.all([
|
||||||
prisma.user.findMany({
|
prisma.user.findMany({
|
||||||
orderBy: { id: "asc" },
|
orderBy: { id: "asc" },
|
||||||
skip: offset,
|
skip: offset,
|
||||||
take: pagination.limit || PAGINATION_LIMIT,
|
take: req.pagination?.limit || PAGINATION_LIMIT,
|
||||||
}),
|
}),
|
||||||
prisma.user.count(),
|
prisma.user.count(),
|
||||||
]);
|
]);
|
||||||
|
|
|
@ -108,7 +108,7 @@ export class QueryBuilder {
|
||||||
private selectClause;
|
private selectClause;
|
||||||
private fromClause;
|
private fromClause;
|
||||||
private filterClauses: string[] = [];
|
private filterClauses: string[] = [];
|
||||||
private orderClauses: string[] = [];
|
private orderClause = "";
|
||||||
private params: unknown[] = [];
|
private params: unknown[] = [];
|
||||||
private nP = 0;
|
private nP = 0;
|
||||||
private limit = PAGINATION_LIMIT;
|
private limit = PAGINATION_LIMIT;
|
||||||
|
@ -120,12 +120,12 @@ export class QueryBuilder {
|
||||||
}
|
}
|
||||||
|
|
||||||
setPagination(pag: PaginationRequest) {
|
setPagination(pag: PaginationRequest) {
|
||||||
if (pag.limit) this.limit = pag.limit;
|
this.limit = pag.limit;
|
||||||
if (pag.offset) this.offset = pag.offset;
|
this.offset = pag.offset;
|
||||||
}
|
}
|
||||||
|
|
||||||
addOrderClause(orderClause: string) {
|
setOrderClause(orderClause: string) {
|
||||||
this.orderClauses.push(orderClause);
|
this.orderClause = orderClause;
|
||||||
}
|
}
|
||||||
|
|
||||||
/** Get the next parameter variable (e.g. $1) and increment the counter */
|
/** Get the next parameter variable (e.g. $1) and increment the counter */
|
||||||
|
@ -163,10 +163,7 @@ export class QueryBuilder {
|
||||||
queryParts.push("where " + this.filterClauses.join(" and "));
|
queryParts.push("where " + this.filterClauses.join(" and "));
|
||||||
}
|
}
|
||||||
|
|
||||||
if (this.orderClauses.length > 0) {
|
if (this.orderClause.length > 0) queryParts.push(this.orderClause);
|
||||||
queryParts.push(" order by ");
|
|
||||||
queryParts.push(this.orderClauses.join(", "));
|
|
||||||
}
|
|
||||||
queryParts.push(`limit $${this.nP + 1} offset $${this.nP + 2}`);
|
queryParts.push(`limit $${this.nP + 1} offset $${this.nP + 2}`);
|
||||||
|
|
||||||
return queryParts.join(" ");
|
return queryParts.join(" ");
|
||||||
|
|
|
@ -1,12 +0,0 @@
|
||||||
import type { RequestEvent } from "@sveltejs/kit";
|
|
||||||
|
|
||||||
export async function userId(event: RequestEvent): Promise<number> {
|
|
||||||
const sess = await event.locals.getSession();
|
|
||||||
const id = Number(sess?.user?.id);
|
|
||||||
if (id) {
|
|
||||||
return id;
|
|
||||||
} else {
|
|
||||||
// This should never happen, since unauthorized requests are caught by hooks.server.ts
|
|
||||||
throw new Error("no user id");
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -6,19 +6,6 @@ export type Pagination<T> = {
|
||||||
offset: number;
|
offset: number;
|
||||||
};
|
};
|
||||||
|
|
||||||
export type ApiBody<T> = {
|
|
||||||
body: T;
|
|
||||||
};
|
|
||||||
|
|
||||||
export type ApiError = {
|
|
||||||
status: number;
|
|
||||||
msg: string;
|
|
||||||
} & Partial<{ data: unknown }>;
|
|
||||||
|
|
||||||
export type ApiCreated = {
|
|
||||||
id: number;
|
|
||||||
};
|
|
||||||
|
|
||||||
export type User = {
|
export type User = {
|
||||||
id: number;
|
id: number;
|
||||||
name: Option<string>;
|
name: Option<string>;
|
||||||
|
@ -35,7 +22,9 @@ export type Station = {
|
||||||
name: string;
|
name: string;
|
||||||
};
|
};
|
||||||
|
|
||||||
export type StationNew = Omit<Station, "id">;
|
export type StationNew = {
|
||||||
|
name: string;
|
||||||
|
};
|
||||||
|
|
||||||
export type Room = {
|
export type Room = {
|
||||||
id: number;
|
id: number;
|
||||||
|
@ -55,7 +44,11 @@ export type Category = {
|
||||||
description: Option<string>;
|
description: Option<string>;
|
||||||
};
|
};
|
||||||
|
|
||||||
export type CategoryNew = Omit<Category, "id">;
|
export type CategoryNew = {
|
||||||
|
name: string;
|
||||||
|
color: Option<string>;
|
||||||
|
description: Option<string>;
|
||||||
|
};
|
||||||
|
|
||||||
export type Patient = {
|
export type Patient = {
|
||||||
id: number;
|
id: number;
|
||||||
|
|
|
@ -1,7 +1,7 @@
|
||||||
export type PaginationRequest = Partial<{
|
export type PaginationRequest = {
|
||||||
limit: number;
|
limit: number;
|
||||||
offset: number;
|
offset: number;
|
||||||
}>;
|
};
|
||||||
|
|
||||||
export type FilterList<T> = T | T[];
|
export type FilterList<T> = T | T[];
|
||||||
|
|
||||||
|
@ -17,8 +17,19 @@ export type EntriesFilter = Partial<{
|
||||||
priority: boolean;
|
priority: boolean;
|
||||||
}>;
|
}>;
|
||||||
|
|
||||||
|
export type EntriesRequest = Partial<{
|
||||||
|
filter: EntriesFilter;
|
||||||
|
pagination: PaginationRequest;
|
||||||
|
}>;
|
||||||
|
|
||||||
|
export type UsersRequest = Partial<{ pagination: PaginationRequest }>;
|
||||||
|
|
||||||
export type PatientsFilter = Partial<{
|
export type PatientsFilter = Partial<{
|
||||||
search: string;
|
|
||||||
station: FilterList<number>;
|
station: FilterList<number>;
|
||||||
room: FilterList<number>;
|
room: FilterList<number>;
|
||||||
}>;
|
}>;
|
||||||
|
|
||||||
|
export type PatientsRequest = Partial<{
|
||||||
|
filter: PatientsFilter;
|
||||||
|
pagination: PaginationRequest;
|
||||||
|
}>;
|
||||||
|
|
|
@ -1,25 +0,0 @@
|
||||||
import { expect, test } from "vitest";
|
|
||||||
import { ZEntriesFilterUrl, ZPatientsFilterUrl } from "./validation";
|
|
||||||
import type { EntriesFilter, PatientsFilter } from ".";
|
|
||||||
|
|
||||||
test("valid EntriesFilterUrl", () => {
|
|
||||||
const data = ZEntriesFilterUrl.parse({
|
|
||||||
done: "0",
|
|
||||||
});
|
|
||||||
|
|
||||||
expect(data).toStrictEqual({
|
|
||||||
done: false,
|
|
||||||
} satisfies EntriesFilter);
|
|
||||||
});
|
|
||||||
|
|
||||||
test("valid PatientsFilterUrl", () => {
|
|
||||||
const data = ZPatientsFilterUrl.parse({
|
|
||||||
room: "1;2;3;4",
|
|
||||||
station: "5",
|
|
||||||
});
|
|
||||||
|
|
||||||
expect(data).toStrictEqual({
|
|
||||||
room: [1, 2, 3, 4],
|
|
||||||
station: [5],
|
|
||||||
} satisfies PatientsFilter);
|
|
||||||
});
|
|
|
@ -6,7 +6,6 @@ import type {
|
||||||
EntryNew,
|
EntryNew,
|
||||||
EntryVersionNdata,
|
EntryVersionNdata,
|
||||||
EntryVersionNew,
|
EntryVersionNew,
|
||||||
PaginationRequest,
|
|
||||||
PatientNew,
|
PatientNew,
|
||||||
RoomNew,
|
RoomNew,
|
||||||
StationNew,
|
StationNew,
|
||||||
|
@ -62,38 +61,3 @@ export const ZEntryExecutionNew = implement<EntryExecutionNew>().with({
|
||||||
old_execution: ZEntityId.optional().nullable(),
|
old_execution: ZEntityId.optional().nullable(),
|
||||||
text: ZTextString,
|
text: ZTextString,
|
||||||
});
|
});
|
||||||
|
|
||||||
// From URL
|
|
||||||
const ZFilterListUrl = z
|
|
||||||
.string()
|
|
||||||
.regex(/^\d+(;\d+)*$/)
|
|
||||||
.transform((s) => s.split(";").map(Number))
|
|
||||||
.optional();
|
|
||||||
|
|
||||||
export const ZEntityIdUrl = z.coerce.number().int().nonnegative();
|
|
||||||
|
|
||||||
export const BooleanUrl = z
|
|
||||||
.string()
|
|
||||||
.transform((s) => s !== "0" && s.toLowerCase() !== "false");
|
|
||||||
|
|
||||||
export const ZPaginationUrl = implement<PaginationRequest>().with({
|
|
||||||
limit: ZEntityIdUrl.optional(),
|
|
||||||
offset: ZEntityIdUrl.optional(),
|
|
||||||
});
|
|
||||||
|
|
||||||
export const ZEntriesFilterUrl = z.object({
|
|
||||||
author: ZFilterListUrl,
|
|
||||||
category: ZFilterListUrl,
|
|
||||||
done: BooleanUrl.optional(),
|
|
||||||
executor: ZFilterListUrl,
|
|
||||||
patient: ZFilterListUrl,
|
|
||||||
priority: BooleanUrl.optional(),
|
|
||||||
room: ZFilterListUrl,
|
|
||||||
search: z.string().optional(),
|
|
||||||
station: ZFilterListUrl,
|
|
||||||
});
|
|
||||||
|
|
||||||
export const ZPatientsFilterUrl = z.object({
|
|
||||||
room: ZFilterListUrl,
|
|
||||||
station: ZFilterListUrl,
|
|
||||||
});
|
|
||||||
|
|
|
@ -1,10 +1,4 @@
|
||||||
<script lang="ts">
|
<script lang="ts">
|
||||||
import api from "$api";
|
|
||||||
|
|
||||||
api.category
|
|
||||||
.id$(1)
|
|
||||||
.GET()
|
|
||||||
.Ok((resp) => console.log(resp.body));
|
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<h1 class="text-4xl">Planung</h1>
|
<h1 class="text-4xl">Planung</h1>
|
||||||
|
|
5
src/routes/api/+server.ts
Normal file
5
src/routes/api/+server.ts
Normal file
|
@ -0,0 +1,5 @@
|
||||||
|
import { json } from "@sveltejs/kit";
|
||||||
|
|
||||||
|
export const GET = () => {
|
||||||
|
return json({ hello: "world" });
|
||||||
|
};
|
|
@ -1,21 +0,0 @@
|
||||||
import type { KitEvent } from "sveltekit-zero-api";
|
|
||||||
import type { RequestEvent } from "./$types";
|
|
||||||
|
|
||||||
import { getCategories, newCategory } from "$lib/server/query";
|
|
||||||
import type { ApiBody, CategoryNew } from "$lib/shared/model";
|
|
||||||
import { ZCategoryNew } from "$lib/shared/model/validation";
|
|
||||||
import { apiWrap } from "$lib/server/handleError";
|
|
||||||
|
|
||||||
/** Get all categories */
|
|
||||||
export const GET = async (event: KitEvent<object, RequestEvent>) => {
|
|
||||||
return apiWrap(getCategories);
|
|
||||||
};
|
|
||||||
|
|
||||||
/** Create a new category */
|
|
||||||
export const POST = async (event: KitEvent<ApiBody<CategoryNew>, RequestEvent>) => {
|
|
||||||
return apiWrap(async () => {
|
|
||||||
const category = ZCategoryNew.parse(await event.request.json());
|
|
||||||
const id = await newCategory(category);
|
|
||||||
return { id };
|
|
||||||
});
|
|
||||||
};
|
|
|
@ -1,35 +0,0 @@
|
||||||
import { apiWrap } from "$lib/server/handleError";
|
|
||||||
import { deleteCategory, getCategory, updateCategory } from "$lib/server/query";
|
|
||||||
import { ZCategoryNew, ZEntityIdUrl } from "$lib/shared/model/validation";
|
|
||||||
import type { KitEvent } from "sveltekit-zero-api";
|
|
||||||
import type { RequestEvent } from "./$types";
|
|
||||||
import type { CategoryNew, ApiBody } from "$lib/shared/model";
|
|
||||||
|
|
||||||
/** Get a category */
|
|
||||||
export const GET = async (event: KitEvent<object, RequestEvent>) => {
|
|
||||||
return apiWrap(async () => {
|
|
||||||
const id = ZEntityIdUrl.parse(event.params.id);
|
|
||||||
return await getCategory(id);
|
|
||||||
});
|
|
||||||
};
|
|
||||||
|
|
||||||
/** Update a category */
|
|
||||||
export const PATCH = async (
|
|
||||||
event: KitEvent<ApiBody<Partial<CategoryNew>>, RequestEvent>
|
|
||||||
) => {
|
|
||||||
return apiWrap(async () => {
|
|
||||||
const id = ZEntityIdUrl.parse(event.params.id);
|
|
||||||
const category = ZCategoryNew.partial().parse(await event.request.json());
|
|
||||||
await updateCategory(id, category);
|
|
||||||
return { id };
|
|
||||||
});
|
|
||||||
};
|
|
||||||
|
|
||||||
/** Delete a category */
|
|
||||||
export const DELETE = async (event: KitEvent<object, RequestEvent>) => {
|
|
||||||
return apiWrap(async () => {
|
|
||||||
const id = ZEntityIdUrl.parse(event.params.id);
|
|
||||||
await deleteCategory(id);
|
|
||||||
return { id };
|
|
||||||
});
|
|
||||||
};
|
|
|
@ -1,15 +0,0 @@
|
||||||
import { getEntries } from "$lib/server/query";
|
|
||||||
import { apiWrap } from "$lib/server/handleError";
|
|
||||||
import { ZEntriesFilterUrl, ZPaginationUrl } from "$lib/shared/model/validation";
|
|
||||||
import type { KitEvent } from "sveltekit-zero-api";
|
|
||||||
import type { RequestEvent } from "./$types";
|
|
||||||
|
|
||||||
/** Get entries */
|
|
||||||
export const GET = async (event: KitEvent<object, RequestEvent>) => {
|
|
||||||
return apiWrap(async () => {
|
|
||||||
const params = Object.fromEntries(event.url.searchParams);
|
|
||||||
const filter = ZEntriesFilterUrl.parse(params);
|
|
||||||
const pagination = ZPaginationUrl.parse(params);
|
|
||||||
return await getEntries(filter, pagination);
|
|
||||||
});
|
|
||||||
};
|
|
|
@ -1,13 +0,0 @@
|
||||||
import { apiWrap } from "$lib/server/handleError";
|
|
||||||
import { getEntry } from "$lib/server/query";
|
|
||||||
import { ZEntityIdUrl } from "$lib/shared/model/validation";
|
|
||||||
import type { KitEvent } from "sveltekit-zero-api";
|
|
||||||
import type { RequestEvent } from "./$types";
|
|
||||||
|
|
||||||
/** Get a entry */
|
|
||||||
export const GET = async (event: KitEvent<object, RequestEvent>) => {
|
|
||||||
return apiWrap(async () => {
|
|
||||||
const id = ZEntityIdUrl.parse(event.params.id);
|
|
||||||
return getEntry(id);
|
|
||||||
});
|
|
||||||
};
|
|
|
@ -1,18 +0,0 @@
|
||||||
import type { KitEvent } from "sveltekit-zero-api";
|
|
||||||
import type { RequestEvent } from "./$types";
|
|
||||||
import type { ApiBody, EntryExecutionNew } from "$lib/shared/model";
|
|
||||||
import { apiWrapUser } from "$lib/server/handleError";
|
|
||||||
import { ZEntityIdUrl, ZEntryExecutionNew } from "$lib/shared/model/validation";
|
|
||||||
import { newEntryExecution } from "$lib/server/query";
|
|
||||||
|
|
||||||
/** Create an entry execution */
|
|
||||||
export const POST = async (
|
|
||||||
event: KitEvent<ApiBody<EntryExecutionNew>, RequestEvent>
|
|
||||||
) => {
|
|
||||||
return apiWrapUser(event, async (uId) => {
|
|
||||||
const id = ZEntityIdUrl.parse(event.params.id);
|
|
||||||
const execution = ZEntryExecutionNew.parse(await event.request.json());
|
|
||||||
const newExecutionId = await newEntryExecution(uId, id, execution);
|
|
||||||
return { id, newExecutionId };
|
|
||||||
});
|
|
||||||
};
|
|
|
@ -1,26 +0,0 @@
|
||||||
import { apiWrap, apiWrapUser } from "$lib/server/handleError";
|
|
||||||
import { getEntryHistory, newEntryVersion } from "$lib/server/query";
|
|
||||||
import { ZEntityIdUrl, ZEntryVersionNew } from "$lib/shared/model/validation";
|
|
||||||
import type { KitEvent } from "sveltekit-zero-api";
|
|
||||||
import type { RequestEvent } from "./$types";
|
|
||||||
import type { ApiBody, EntryVersionNew } from "$lib/shared/model";
|
|
||||||
|
|
||||||
/** Get all versions of an entry */
|
|
||||||
export const GET = async (event: KitEvent<object, RequestEvent>) => {
|
|
||||||
return apiWrap(async () => {
|
|
||||||
const id = ZEntityIdUrl.parse(event.params.id);
|
|
||||||
return getEntryHistory(id);
|
|
||||||
});
|
|
||||||
};
|
|
||||||
|
|
||||||
/** Create an entry version */
|
|
||||||
export const PATCH = async (
|
|
||||||
event: KitEvent<ApiBody<Partial<EntryVersionNew>>, RequestEvent>
|
|
||||||
) => {
|
|
||||||
return apiWrapUser(event, async (uId) => {
|
|
||||||
const id = ZEntityIdUrl.parse(event.params.id);
|
|
||||||
const version = ZEntryVersionNew.parse(await event.request.json());
|
|
||||||
const newVersionId = await newEntryVersion(uId, id, version);
|
|
||||||
return { id, newVersionId };
|
|
||||||
});
|
|
||||||
};
|
|
|
@ -1,15 +0,0 @@
|
||||||
import { apiWrap } from "$lib/server/handleError";
|
|
||||||
import { getPatients } from "$lib/server/query";
|
|
||||||
import { ZPaginationUrl, ZPatientsFilterUrl } from "$lib/shared/model/validation";
|
|
||||||
import type { KitEvent } from "sveltekit-zero-api";
|
|
||||||
import type { RequestEvent } from "./$types";
|
|
||||||
|
|
||||||
/** Get patients */
|
|
||||||
export const GET = async (event: KitEvent<object, RequestEvent>) => {
|
|
||||||
return apiWrap(async () => {
|
|
||||||
const params = Object.fromEntries(event.url.searchParams);
|
|
||||||
const filter = ZPatientsFilterUrl.parse(params);
|
|
||||||
const pagination = ZPaginationUrl.parse(params);
|
|
||||||
return await getPatients(filter, pagination);
|
|
||||||
});
|
|
||||||
};
|
|
|
@ -1,35 +0,0 @@
|
||||||
import { apiWrap } from "$lib/server/handleError";
|
|
||||||
import { deletePatient, getPatient, updatePatient } from "$lib/server/query";
|
|
||||||
import { ZEntityIdUrl, ZPatientNew } from "$lib/shared/model/validation";
|
|
||||||
import type { KitEvent } from "sveltekit-zero-api";
|
|
||||||
import type { RequestEvent } from "./$types";
|
|
||||||
import type { ApiBody, PatientNew } from "$lib/shared/model";
|
|
||||||
|
|
||||||
/** Get a patient */
|
|
||||||
export const GET = async (event: KitEvent<object, RequestEvent>) => {
|
|
||||||
return apiWrap(async () => {
|
|
||||||
const id = ZEntityIdUrl.parse(event.params.id);
|
|
||||||
return await getPatient(id);
|
|
||||||
});
|
|
||||||
};
|
|
||||||
|
|
||||||
/** Update a patient */
|
|
||||||
export const PATCH = async (
|
|
||||||
event: KitEvent<ApiBody<Partial<PatientNew>>, RequestEvent>
|
|
||||||
) => {
|
|
||||||
return apiWrap(async () => {
|
|
||||||
const id = ZEntityIdUrl.parse(event.params.id);
|
|
||||||
const patient = ZPatientNew.partial().parse(await event.request.json());
|
|
||||||
await updatePatient(id, patient);
|
|
||||||
return { id };
|
|
||||||
});
|
|
||||||
};
|
|
||||||
|
|
||||||
/** Delete a patient */
|
|
||||||
export const DELETE = async (event: KitEvent<object, RequestEvent>) => {
|
|
||||||
return apiWrap(async () => {
|
|
||||||
const id = ZEntityIdUrl.parse(event.params.id);
|
|
||||||
await deletePatient(id);
|
|
||||||
return { id };
|
|
||||||
});
|
|
||||||
};
|
|
|
@ -1,20 +0,0 @@
|
||||||
import { getRooms, newRoom } from "$lib/server/query";
|
|
||||||
import { apiWrap } from "$lib/server/handleError";
|
|
||||||
import type { KitEvent } from "sveltekit-zero-api";
|
|
||||||
import type { RequestEvent } from "./$types";
|
|
||||||
import type { ApiBody, RoomNew } from "$lib/shared/model";
|
|
||||||
import { ZRoomNew } from "$lib/shared/model/validation";
|
|
||||||
|
|
||||||
/** Get all rooms */
|
|
||||||
export const GET = async (event: KitEvent<object, RequestEvent>) => {
|
|
||||||
return apiWrap(getRooms);
|
|
||||||
};
|
|
||||||
|
|
||||||
/** Create a new room */
|
|
||||||
export const POST = async (event: KitEvent<ApiBody<RoomNew>, RequestEvent>) => {
|
|
||||||
return apiWrap(async () => {
|
|
||||||
const room = ZRoomNew.parse(await event.request.json());
|
|
||||||
const id = await newRoom(room);
|
|
||||||
return { id };
|
|
||||||
});
|
|
||||||
};
|
|
|
@ -1,35 +0,0 @@
|
||||||
import { apiWrap } from "$lib/server/handleError";
|
|
||||||
import { getRoom, updateRoom, deleteRoom } from "$lib/server/query";
|
|
||||||
import { ZRoomNew, ZEntityIdUrl } from "$lib/shared/model/validation";
|
|
||||||
import type { KitEvent } from "sveltekit-zero-api";
|
|
||||||
import type { RequestEvent } from "./$types";
|
|
||||||
import type { ApiBody, RoomNew } from "$lib/shared/model";
|
|
||||||
|
|
||||||
/** Get a room */
|
|
||||||
export const GET = async (event: KitEvent<object, RequestEvent>) => {
|
|
||||||
return apiWrap(async () => {
|
|
||||||
const id = ZEntityIdUrl.parse(event.params.id);
|
|
||||||
return await getRoom(id);
|
|
||||||
});
|
|
||||||
};
|
|
||||||
|
|
||||||
/** Update a room */
|
|
||||||
export const PATCH = async (
|
|
||||||
event: KitEvent<ApiBody<Partial<RoomNew>>, RequestEvent>
|
|
||||||
) => {
|
|
||||||
return apiWrap(async () => {
|
|
||||||
const id = ZEntityIdUrl.parse(event.params.id);
|
|
||||||
const room = ZRoomNew.partial().parse(await event.request.json());
|
|
||||||
await updateRoom(id, room);
|
|
||||||
return { id };
|
|
||||||
});
|
|
||||||
};
|
|
||||||
|
|
||||||
/** Delete a room */
|
|
||||||
export const DELETE = async (event: KitEvent<object, RequestEvent>) => {
|
|
||||||
return apiWrap(async () => {
|
|
||||||
const id = ZEntityIdUrl.parse(event.params.id);
|
|
||||||
await deleteRoom(id);
|
|
||||||
return { id };
|
|
||||||
});
|
|
||||||
};
|
|
|
@ -1,20 +0,0 @@
|
||||||
import { getRooms, newRoom } from "$lib/server/query";
|
|
||||||
import { apiWrap } from "$lib/server/handleError";
|
|
||||||
import type { KitEvent } from "sveltekit-zero-api";
|
|
||||||
import type { RequestEvent } from "./$types";
|
|
||||||
import type { ApiBody, RoomNew } from "$lib/shared/model";
|
|
||||||
import { ZRoomNew } from "$lib/shared/model/validation";
|
|
||||||
|
|
||||||
/** Get all rooms */
|
|
||||||
export const GET = async (event: KitEvent<object, RequestEvent>) => {
|
|
||||||
return apiWrap(getRooms);
|
|
||||||
};
|
|
||||||
|
|
||||||
/** Create a new room */
|
|
||||||
export const POST = async (event: KitEvent<ApiBody<RoomNew>, RequestEvent>) => {
|
|
||||||
return apiWrap(async () => {
|
|
||||||
const room = ZRoomNew.parse(await event.request.json());
|
|
||||||
const id = await newRoom(room);
|
|
||||||
return { id };
|
|
||||||
});
|
|
||||||
};
|
|
|
@ -1,35 +0,0 @@
|
||||||
import { apiWrap } from "$lib/server/handleError";
|
|
||||||
import { deleteStation, getStation, updateStation } from "$lib/server/query";
|
|
||||||
import { ZEntityIdUrl, ZStationNew } from "$lib/shared/model/validation";
|
|
||||||
import type { KitEvent } from "sveltekit-zero-api";
|
|
||||||
import type { RequestEvent } from "./$types";
|
|
||||||
import type { ApiBody, StationNew } from "$lib/shared/model";
|
|
||||||
|
|
||||||
/** Get a station */
|
|
||||||
export const GET = async (event: KitEvent<object, RequestEvent>) => {
|
|
||||||
return apiWrap(async () => {
|
|
||||||
const id = ZEntityIdUrl.parse(event.params.id);
|
|
||||||
return await getStation(id);
|
|
||||||
});
|
|
||||||
};
|
|
||||||
|
|
||||||
/** Update a station */
|
|
||||||
export const PATCH = async (
|
|
||||||
event: KitEvent<ApiBody<Partial<StationNew>>, RequestEvent>
|
|
||||||
) => {
|
|
||||||
return apiWrap(async () => {
|
|
||||||
const id = ZEntityIdUrl.parse(event.params.id);
|
|
||||||
const station = ZStationNew.partial().parse(await event.request.json());
|
|
||||||
await updateStation(id, station);
|
|
||||||
return { id };
|
|
||||||
});
|
|
||||||
};
|
|
||||||
|
|
||||||
/** Delete a station */
|
|
||||||
export const DELETE = async (event: KitEvent<object, RequestEvent>) => {
|
|
||||||
return apiWrap(async () => {
|
|
||||||
const id = ZEntityIdUrl.parse(event.params.id);
|
|
||||||
await deleteStation(id);
|
|
||||||
return { id };
|
|
||||||
});
|
|
||||||
};
|
|
|
@ -1,14 +0,0 @@
|
||||||
import { apiWrap } from "$lib/server/handleError";
|
|
||||||
import { getUsers } from "$lib/server/query";
|
|
||||||
import { ZPaginationUrl } from "$lib/shared/model/validation";
|
|
||||||
import type { KitEvent } from "sveltekit-zero-api";
|
|
||||||
import type { RequestEvent } from "./$types";
|
|
||||||
|
|
||||||
/** Get users */
|
|
||||||
export const GET = async (event: KitEvent<object, RequestEvent>) => {
|
|
||||||
return apiWrap(async () => {
|
|
||||||
const params = Object.fromEntries(event.url.searchParams);
|
|
||||||
const pagination = ZPaginationUrl.parse(params);
|
|
||||||
return await getUsers(pagination);
|
|
||||||
});
|
|
||||||
};
|
|
|
@ -12,11 +12,6 @@ const config = {
|
||||||
adapter: adapter({
|
adapter: adapter({
|
||||||
precompress: true,
|
precompress: true,
|
||||||
}),
|
}),
|
||||||
|
|
||||||
alias: {
|
|
||||||
$api: "./src/api",
|
|
||||||
$tests: "./tests",
|
|
||||||
},
|
|
||||||
},
|
},
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
|
@ -21,10 +21,6 @@ const N_USERS = 10;
|
||||||
const N_ROOMS = 20;
|
const N_ROOMS = 20;
|
||||||
const N_PATIENTS = 50;
|
const N_PATIENTS = 50;
|
||||||
|
|
||||||
function randomId(len: number): number {
|
|
||||||
return faker.number.int({ min: 1, max: len - 1 });
|
|
||||||
}
|
|
||||||
|
|
||||||
export default async () => {
|
export default async () => {
|
||||||
// Reset database
|
// Reset database
|
||||||
await prisma.$transaction([
|
await prisma.$transaction([
|
||||||
|
@ -56,6 +52,10 @@ export default async () => {
|
||||||
.split("\n")
|
.split("\n")
|
||||||
.map((l) => JSON.parse(l));
|
.map((l) => JSON.parse(l));
|
||||||
|
|
||||||
|
function randomId(len: number): number {
|
||||||
|
return faker.number.int({ min: 1, max: len - 1 });
|
||||||
|
}
|
||||||
|
|
||||||
for (let i = 1; i <= N_USERS; i++) {
|
for (let i = 1; i <= N_USERS; i++) {
|
||||||
const firstName = faker.person.firstName();
|
const firstName = faker.person.firstName();
|
||||||
const lastName = faker.person.lastName();
|
const lastName = faker.person.lastName();
|
||||||
|
|
|
@ -1,6 +1,7 @@
|
||||||
import { getCategories, getCategory, newCategory } from "$lib/server/query";
|
import { getCategories, getCategory, newCategory } from "$lib/server/query";
|
||||||
import { expect, test } from "vitest";
|
import { expect, test } from "vitest";
|
||||||
import { CATEGORIES } from "$tests/helpers/testdata";
|
// eslint-disable-next-line no-relative-import-paths/no-relative-import-paths
|
||||||
|
import { CATEGORIES } from "../../helpers/testdata";
|
||||||
|
|
||||||
test("create category", async () => {
|
test("create category", async () => {
|
||||||
const data = {
|
const data = {
|
||||||
|
|
|
@ -1,7 +1,6 @@
|
||||||
import {
|
import {
|
||||||
getEntries,
|
getEntries,
|
||||||
getEntry,
|
getEntry,
|
||||||
getEntryHistory,
|
|
||||||
newEntry,
|
newEntry,
|
||||||
newEntryExecution,
|
newEntryExecution,
|
||||||
newEntryVersion,
|
newEntryVersion,
|
||||||
|
@ -50,20 +49,13 @@ test("create entry version", async () => {
|
||||||
});
|
});
|
||||||
|
|
||||||
const entry = await getEntry(eId);
|
const entry = await getEntry(eId);
|
||||||
const expectedVersion = {
|
expect(entry.current_version).toMatchObject({
|
||||||
author: { id: 1 },
|
author: { id: 1 },
|
||||||
date,
|
date,
|
||||||
text,
|
text,
|
||||||
category: { id: 2 },
|
category: { id: 2 },
|
||||||
priority: true,
|
priority: true,
|
||||||
};
|
});
|
||||||
expect(entry.current_version).toMatchObject(expectedVersion);
|
|
||||||
|
|
||||||
const history = await getEntryHistory(eId);
|
|
||||||
expect(history).length(2);
|
|
||||||
expect(history[0]).toMatchObject(expectedVersion);
|
|
||||||
expect(history[1].text).toBe(TEST_VERSION.text);
|
|
||||||
expect(history[0].created_at).greaterThan(history[1].created_at);
|
|
||||||
});
|
});
|
||||||
|
|
||||||
test("create entry version (partial)", async () => {
|
test("create entry version (partial)", async () => {
|
||||||
|
@ -94,14 +86,6 @@ test("create entry version (wrong old vid)", async () => {
|
||||||
patient_id: 1,
|
patient_id: 1,
|
||||||
version: TEST_VERSION,
|
version: TEST_VERSION,
|
||||||
});
|
});
|
||||||
const entry = await getEntry(eId);
|
|
||||||
|
|
||||||
expect(async () => {
|
|
||||||
await newEntryVersion(1, eId, {
|
|
||||||
old_version: entry.current_version.id + 1,
|
|
||||||
text: "Hello World",
|
|
||||||
});
|
|
||||||
}).rejects.toThrowError(new ErrorConflict("old version id does not match"));
|
|
||||||
});
|
});
|
||||||
|
|
||||||
test("create entry execution", async () => {
|
test("create entry execution", async () => {
|
||||||
|
@ -141,8 +125,8 @@ test("create entry execution (wrong old xid)", async () => {
|
||||||
});
|
});
|
||||||
const x1 = await newEntryExecution(1, eId, { old_execution: null, text: "x1" });
|
const x1 = await newEntryExecution(1, eId, { old_execution: null, text: "x1" });
|
||||||
|
|
||||||
expect(
|
expect(async () =>
|
||||||
async () => await newEntryExecution(1, eId, { old_execution: x1 + 1, text: "x2" })
|
newEntryExecution(1, eId, { old_execution: x1 + 1, text: "x2" })
|
||||||
).rejects.toThrowError(new ErrorConflict("old execution id does not match"));
|
).rejects.toThrowError(new ErrorConflict("old execution id does not match"));
|
||||||
});
|
});
|
||||||
|
|
||||||
|
@ -188,65 +172,65 @@ async function insertTestEntries() {
|
||||||
|
|
||||||
test("get entries", async () => {
|
test("get entries", async () => {
|
||||||
const { eId1, eId2, eId3 } = await insertTestEntries();
|
const { eId1, eId2, eId3 } = await insertTestEntries();
|
||||||
const entries = await getEntries({}, {});
|
const entries = await getEntries({});
|
||||||
expect(entries.items).length(3);
|
expect(entries.items).length(3);
|
||||||
expect(entries.total).toBe(3);
|
expect(entries.total).toBe(3);
|
||||||
|
|
||||||
// Pagination
|
// Pagination
|
||||||
|
|
||||||
const entriesLim2 = await getEntries({}, { limit: 2, offset: 0 });
|
const entriesLim2 = await getEntries({ pagination: { limit: 2, offset: 0 } });
|
||||||
expect(entriesLim2.items).length(2);
|
expect(entriesLim2.items).length(2);
|
||||||
expect(entriesLim2.total).toBe(3);
|
expect(entriesLim2.total).toBe(3);
|
||||||
|
|
||||||
const entriesLim2Offset = await getEntries({}, { limit: 2, offset: 2 });
|
const entriesLim2Offset = await getEntries({ pagination: { limit: 2, offset: 2 } });
|
||||||
expect(entriesLim2Offset.items).length(1);
|
expect(entriesLim2Offset.items).length(1);
|
||||||
expect(entriesLim2Offset.offset).toBe(2);
|
expect(entriesLim2Offset.offset).toBe(2);
|
||||||
expect(entriesLim2Offset.total).toBe(3);
|
expect(entriesLim2Offset.total).toBe(3);
|
||||||
|
|
||||||
// Filter by category
|
// Filter by category
|
||||||
const entriesCategory = await getEntries({ category: 3 }, {});
|
const entriesCategory = await getEntries({ filter: { category: 3 } });
|
||||||
expect(entriesCategory.items).length(1);
|
expect(entriesCategory.items).length(1);
|
||||||
expect(entriesCategory.total).toBe(1);
|
expect(entriesCategory.total).toBe(1);
|
||||||
expect(entriesCategory.items[0].id).toBe(eId1);
|
expect(entriesCategory.items[0].id).toBe(eId1);
|
||||||
|
|
||||||
// Filter by author
|
// Filter by author
|
||||||
const entriesAuthor = await getEntries({ author: 2 }, {});
|
const entriesAuthor = await getEntries({ filter: { author: 2 } });
|
||||||
expect(entriesAuthor.items).length(1);
|
expect(entriesAuthor.items).length(1);
|
||||||
expect(entriesAuthor.total).toBe(1);
|
expect(entriesAuthor.total).toBe(1);
|
||||||
expect(entriesAuthor.items[0].id).toBe(eId1);
|
expect(entriesAuthor.items[0].id).toBe(eId1);
|
||||||
|
|
||||||
// Filter by executor
|
// Filter by executor
|
||||||
const entriesExecutor = await getEntries({ executor: 1 }, {});
|
const entriesExecutor = await getEntries({ filter: { executor: 1 } });
|
||||||
expect(entriesExecutor.items).length(1);
|
expect(entriesExecutor.items).length(1);
|
||||||
expect(entriesExecutor.total).toBe(1);
|
expect(entriesExecutor.total).toBe(1);
|
||||||
expect(entriesExecutor.items[0].id).toBe(eId2);
|
expect(entriesExecutor.items[0].id).toBe(eId2);
|
||||||
|
|
||||||
// Filter by patient
|
// Filter by patient
|
||||||
const entriesPatient = await getEntries({ patient: 1 }, {});
|
const entriesPatient = await getEntries({ filter: { patient: 1 } });
|
||||||
expect(entriesPatient.items).length(2);
|
expect(entriesPatient.items).length(2);
|
||||||
expect(entriesPatient.total).toBe(2);
|
expect(entriesPatient.total).toBe(2);
|
||||||
expect(entriesPatient.items[0].id).toBe(eId3);
|
expect(entriesPatient.items[0].id).toBe(eId3);
|
||||||
expect(entriesPatient.items[1].id).toBe(eId1);
|
expect(entriesPatient.items[1].id).toBe(eId1);
|
||||||
|
|
||||||
// Filter by room
|
// Filter by room
|
||||||
const entriesRoom = await getEntries({ room: 1 }, {});
|
const entriesRoom = await getEntries({ filter: { room: 1 } });
|
||||||
expect(entriesRoom).toStrictEqual(entriesPatient);
|
expect(entriesRoom).toStrictEqual(entriesPatient);
|
||||||
|
|
||||||
// Filter done
|
// Filter done
|
||||||
const entriesDone = await getEntries({ done: true }, {});
|
const entriesDone = await getEntries({ filter: { done: true } });
|
||||||
expect(entriesDone.items).length(2);
|
expect(entriesDone.items).length(2);
|
||||||
expect(entriesDone.total).toBe(2);
|
expect(entriesDone.total).toBe(2);
|
||||||
expect(entriesDone.items[0].id).toBe(eId3);
|
expect(entriesDone.items[0].id).toBe(eId3);
|
||||||
expect(entriesDone.items[1].id).toBe(eId2);
|
expect(entriesDone.items[1].id).toBe(eId2);
|
||||||
|
|
||||||
// Filter by priority
|
// Filter by priority
|
||||||
const entriesPrio = await getEntries({ priority: true }, {});
|
const entriesPrio = await getEntries({ filter: { priority: true } });
|
||||||
expect(entriesPrio.items).length(1);
|
expect(entriesPrio.items).length(1);
|
||||||
expect(entriesPrio.total).toBe(1);
|
expect(entriesPrio.total).toBe(1);
|
||||||
expect(entriesPrio.items[0].id).toBe(eId1);
|
expect(entriesPrio.items[0].id).toBe(eId1);
|
||||||
|
|
||||||
// Search
|
// Search
|
||||||
const entriesSearch = await getEntries({ search: "Blu" }, {});
|
const entriesSearch = await getEntries({ filter: { search: "Blu" } });
|
||||||
expect(entriesSearch.items).length(1);
|
expect(entriesSearch.items).length(1);
|
||||||
expect(entriesSearch.total).toBe(1);
|
expect(entriesSearch.total).toBe(1);
|
||||||
expect(entriesSearch.items[0].id).toBe(eId1);
|
expect(entriesSearch.items[0].id).toBe(eId1);
|
||||||
|
|
|
@ -1,13 +1,7 @@
|
||||||
import {
|
import { newPatient, getPatient, getPatients } from "$lib/server/query";
|
||||||
newPatient,
|
|
||||||
getPatient,
|
|
||||||
getPatients,
|
|
||||||
updatePatient,
|
|
||||||
deletePatient,
|
|
||||||
newEntry,
|
|
||||||
} from "$lib/server/query";
|
|
||||||
import { expect, test } from "vitest";
|
import { expect, test } from "vitest";
|
||||||
import { S1, S2 } from "$tests/helpers/testdata";
|
// eslint-disable-next-line no-relative-import-paths/no-relative-import-paths
|
||||||
|
import { S1, S2 } from "../../helpers/testdata";
|
||||||
|
|
||||||
test("create patient", async () => {
|
test("create patient", async () => {
|
||||||
const pId = await newPatient({
|
const pId = await newPatient({
|
||||||
|
@ -26,47 +20,8 @@ test("create patient", async () => {
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
test("update patient", async () => {
|
|
||||||
const data = {
|
|
||||||
first_name: "Max",
|
|
||||||
last_name: "Mustermann",
|
|
||||||
age: 66,
|
|
||||||
room_id: 2,
|
|
||||||
};
|
|
||||||
await updatePatient(1, data);
|
|
||||||
const patient = await getPatient(1);
|
|
||||||
expect(patient).toMatchObject({
|
|
||||||
first_name: "Max",
|
|
||||||
last_name: "Mustermann",
|
|
||||||
age: 66,
|
|
||||||
room: { id: 2 },
|
|
||||||
});
|
|
||||||
});
|
|
||||||
|
|
||||||
test("delete patient", async () => {
|
|
||||||
await deletePatient(1);
|
|
||||||
expect(async () => await getPatient(1)).rejects.toThrowError("No Patient found");
|
|
||||||
});
|
|
||||||
|
|
||||||
test("delete patient (restricted)", async () => {
|
|
||||||
// Patients should not be deleted if they have at least 1 entry
|
|
||||||
const pId = await newEntry(1, {
|
|
||||||
patient_id: 1,
|
|
||||||
version: {
|
|
||||||
category_id: null,
|
|
||||||
date: new Date(2024, 1, 1),
|
|
||||||
priority: false,
|
|
||||||
text: "Hello World",
|
|
||||||
},
|
|
||||||
});
|
|
||||||
|
|
||||||
expect(async () => await deletePatient(pId)).rejects.toThrowError(
|
|
||||||
"cannot delete patient with entries"
|
|
||||||
);
|
|
||||||
});
|
|
||||||
|
|
||||||
test("get patients", async () => {
|
test("get patients", async () => {
|
||||||
const patients = await getPatients({}, {});
|
const patients = await getPatients({});
|
||||||
expect(patients).toMatchObject({
|
expect(patients).toMatchObject({
|
||||||
items: [
|
items: [
|
||||||
{
|
{
|
||||||
|
@ -97,7 +52,7 @@ test("get patients", async () => {
|
||||||
});
|
});
|
||||||
|
|
||||||
test("get patients (pagination)", async () => {
|
test("get patients (pagination)", async () => {
|
||||||
const patients = await getPatients({}, { offset: 1, limit: 100 });
|
const patients = await getPatients({ pagination: { offset: 1, limit: 100 } });
|
||||||
expect(patients.items).length(2);
|
expect(patients.items).length(2);
|
||||||
expect(patients.items[0].id).toBe(2);
|
expect(patients.items[0].id).toBe(2);
|
||||||
expect(patients.items[1].id).toBe(3);
|
expect(patients.items[1].id).toBe(3);
|
||||||
|
@ -106,20 +61,14 @@ test("get patients (pagination)", async () => {
|
||||||
});
|
});
|
||||||
|
|
||||||
test("get patients (by room)", async () => {
|
test("get patients (by room)", async () => {
|
||||||
const patients = await getPatients({ room: 1 }, {});
|
const patients = await getPatients({ filter: { room: 1 } });
|
||||||
expect(patients.items).length(1);
|
expect(patients.items).length(1);
|
||||||
expect(patients.items[0].id).toBe(1);
|
expect(patients.items[0].id).toBe(1);
|
||||||
});
|
});
|
||||||
|
|
||||||
test("get patients (by station)", async () => {
|
test("get patients (by station)", async () => {
|
||||||
const patients = await getPatients({ station: 1 }, {});
|
const patients = await getPatients({ filter: { station: 1 } });
|
||||||
expect(patients.items).length(2);
|
expect(patients.items).length(2);
|
||||||
expect(patients.items[0].id).toBe(1);
|
expect(patients.items[0].id).toBe(1);
|
||||||
expect(patients.items[1].id).toBe(2);
|
expect(patients.items[1].id).toBe(2);
|
||||||
});
|
});
|
||||||
|
|
||||||
test("search patients", async () => {
|
|
||||||
const patients = await getPatients({ search: "Schustr" }, {});
|
|
||||||
expect(patients.items).length(1);
|
|
||||||
expect(patients.items[0].id).toBe(3);
|
|
||||||
});
|
|
||||||
|
|
|
@ -1,16 +1,15 @@
|
||||||
import {
|
import {
|
||||||
deleteStation,
|
|
||||||
getRoom,
|
getRoom,
|
||||||
getRooms,
|
getRooms,
|
||||||
getStation,
|
getStation,
|
||||||
getStations,
|
getStations,
|
||||||
newRoom,
|
newRoom,
|
||||||
newStation,
|
newStation,
|
||||||
updateStation,
|
|
||||||
} from "$lib/server/query";
|
} from "$lib/server/query";
|
||||||
import type { Room, Station } from "$lib/shared/model";
|
import type { Room, Station } from "$lib/shared/model";
|
||||||
import { expect, test } from "vitest";
|
import { expect, test } from "vitest";
|
||||||
import { S1, S2 } from "$tests/helpers/testdata";
|
// eslint-disable-next-line no-relative-import-paths/no-relative-import-paths
|
||||||
|
import { S1, S2 } from "../../helpers/testdata";
|
||||||
|
|
||||||
test("create station", async () => {
|
test("create station", async () => {
|
||||||
const sId = await newStation({ name: "S3" });
|
const sId = await newStation({ name: "S3" });
|
||||||
|
@ -18,19 +17,6 @@ test("create station", async () => {
|
||||||
expect(station).toStrictEqual({ id: sId, name: "S3" } satisfies Station);
|
expect(station).toStrictEqual({ id: sId, name: "S3" } satisfies Station);
|
||||||
});
|
});
|
||||||
|
|
||||||
test("update station", async () => {
|
|
||||||
const name = "HelloStation";
|
|
||||||
await updateStation(S1.id, { name });
|
|
||||||
const station = await getStation(S1.id);
|
|
||||||
expect(station.id).toBe(S1.id);
|
|
||||||
expect(station.name).toBe(name);
|
|
||||||
});
|
|
||||||
|
|
||||||
test("delete station", async () => {
|
|
||||||
await deleteStation(S1.id);
|
|
||||||
expect(async () => await getStation(S1.id)).rejects.toThrowError("No Station found");
|
|
||||||
});
|
|
||||||
|
|
||||||
test("get stations", async () => {
|
test("get stations", async () => {
|
||||||
const stations = await getStations();
|
const stations = await getStations();
|
||||||
expect(stations).toStrictEqual([S1, S2]);
|
expect(stations).toStrictEqual([S1, S2]);
|
||||||
|
@ -46,19 +32,6 @@ test("create room", async () => {
|
||||||
} satisfies Room);
|
} satisfies Room);
|
||||||
});
|
});
|
||||||
|
|
||||||
test("update room", async () => {
|
|
||||||
const name = "HelloStation";
|
|
||||||
await updateStation(S1.id, { name });
|
|
||||||
const station = await getStation(S1.id);
|
|
||||||
expect(station.id).toBe(S1.id);
|
|
||||||
expect(station.name).toBe(name);
|
|
||||||
});
|
|
||||||
|
|
||||||
test("delete room", async () => {
|
|
||||||
await deleteStation(S1.id);
|
|
||||||
expect(async () => await getStation(S1.id)).rejects.toThrowError("No Station found");
|
|
||||||
});
|
|
||||||
|
|
||||||
test("get rooms", async () => {
|
test("get rooms", async () => {
|
||||||
const rooms = await getRooms();
|
const rooms = await getRooms();
|
||||||
expect(rooms).toStrictEqual([
|
expect(rooms).toStrictEqual([
|
||||||
|
|
|
@ -1,9 +1,8 @@
|
||||||
import { sveltekit } from "@sveltejs/kit/vite";
|
import { sveltekit } from "@sveltejs/kit/vite";
|
||||||
import { defineConfig } from "vitest/config";
|
import { defineConfig } from "vitest/config";
|
||||||
import { zeroAPI } from "sveltekit-zero-api";
|
|
||||||
|
|
||||||
export default defineConfig({
|
export default defineConfig({
|
||||||
plugins: [sveltekit(), zeroAPI()],
|
plugins: [sveltekit()],
|
||||||
test: {
|
test: {
|
||||||
include: ["src/**/*.{test,spec}.{js,ts}"],
|
include: ["src/**/*.{test,spec}.{js,ts}"],
|
||||||
},
|
},
|
||||||
|
|
Loading…
Reference in a new issue