Compare commits
No commits in common. "37e4cdda0b7641cefbe3ab0f568abf97bec9b3b4" and "0e72eb2a62e8a855c0a1d71228d0aadabd6bedf6" have entirely different histories.
37e4cdda0b
...
0e72eb2a62
39 changed files with 980 additions and 1049 deletions
50
package.json
50
package.json
|
@ -17,44 +17,42 @@
|
||||||
"test:e2e": "playwright test"
|
"test:e2e": "playwright test"
|
||||||
},
|
},
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@auth/core": "^0.18.6",
|
"@auth/core": "^0.18.4",
|
||||||
"@auth/sveltekit": "^0.5.3",
|
"@auth/sveltekit": "^0.5.0",
|
||||||
"@mdi/js": "^7.4.47",
|
"@mdi/js": "^7.3.67",
|
||||||
"@prisma/client": "^5.8.1",
|
"@prisma/client": "^5.7.0",
|
||||||
"zod": "^3.22.4"
|
"zod": "^3.22.4"
|
||||||
},
|
},
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
"@faker-js/faker": "^8.4.0",
|
"@faker-js/faker": "^8.3.1",
|
||||||
"@playwright/test": "^1.41.1",
|
"@playwright/test": "^1.40.1",
|
||||||
"@sveltejs/adapter-node": "^2.1.2",
|
"@sveltejs/adapter-node": "^2.0.0",
|
||||||
"@sveltejs/kit": "^2.5.0",
|
"@sveltejs/kit": "^2.0.0",
|
||||||
"@sveltejs/vite-plugin-svelte": "^3.0.1",
|
"@sveltejs/vite-plugin-svelte": "^3.0.0",
|
||||||
"@tailwindcss/typography": "^0.5.10",
|
"@tailwindcss/typography": "^0.5.10",
|
||||||
"@trpc/client": "^10.45.0",
|
"@types/node": "^20.10.5",
|
||||||
"@trpc/server": "^10.45.0",
|
"@typescript-eslint/eslint-plugin": "^6.13.2",
|
||||||
"@types/node": "^20.11.7",
|
"@typescript-eslint/parser": "^6.13.2",
|
||||||
"@typescript-eslint/eslint-plugin": "^6.19.1",
|
"autoprefixer": "^10.4.16",
|
||||||
"@typescript-eslint/parser": "^6.19.1",
|
"daisyui": "^4.4.19",
|
||||||
"autoprefixer": "^10.4.17",
|
"eslint": "^8.55.0",
|
||||||
"daisyui": "^4.6.0",
|
|
||||||
"eslint": "^8.56.0",
|
|
||||||
"eslint-config-prettier": "^9.1.0",
|
"eslint-config-prettier": "^9.1.0",
|
||||||
"eslint-plugin-no-relative-import-paths": "^1.5.3",
|
"eslint-plugin-no-relative-import-paths": "^1.5.3",
|
||||||
"eslint-plugin-svelte": "^2.35.1",
|
"eslint-plugin-svelte": "^2.35.1",
|
||||||
"postcss-import": "^15.1.0",
|
"postcss-import": "^15.1.0",
|
||||||
"postcss-nesting": "^12.0.2",
|
"postcss-nesting": "^12.0.1",
|
||||||
"prettier": "^3.2.4",
|
"prettier": "^3.1.0",
|
||||||
"prettier-plugin-svelte": "^3.1.2",
|
"prettier-plugin-svelte": "^3.1.2",
|
||||||
"prisma": "^5.8.1",
|
"prisma": "^5.7.0",
|
||||||
"svelte": "^4.2.9",
|
"svelte": "^4.2.8",
|
||||||
"svelte-check": "^3.6.3",
|
"svelte-check": "^3.6.2",
|
||||||
"tailwindcss": "^3.4.1",
|
"sveltekit-zero-api": "^0.15.7",
|
||||||
"trpc-sveltekit": "^3.5.22",
|
"tailwindcss": "^3.3.6",
|
||||||
"tslib": "^2.6.2",
|
"tslib": "^2.6.2",
|
||||||
"tsx": "^4.7.0",
|
"tsx": "^4.7.0",
|
||||||
"typescript": "^5.3.3",
|
"typescript": "^5.3.3",
|
||||||
"vite": "^5.0.12",
|
"vite": "^5.0.0",
|
||||||
"vitest": "^1.2.2"
|
"vitest": "^1.0.0"
|
||||||
},
|
},
|
||||||
"type": "module"
|
"type": "module"
|
||||||
}
|
}
|
||||||
|
|
1248
pnpm-lock.yaml
1248
pnpm-lock.yaml
File diff suppressed because it is too large
Load diff
|
@ -1,3 +0,0 @@
|
||||||
-- AlterTable
|
|
||||||
ALTER TABLE "patients"
|
|
||||||
ADD COLUMN "hidden" BOOLEAN NOT NULL DEFAULT FALSE;
|
|
|
@ -14,7 +14,7 @@ datasource db {
|
||||||
|
|
||||||
model Account {
|
model Account {
|
||||||
id Int @id @default(autoincrement())
|
id Int @id @default(autoincrement())
|
||||||
user_id Int @map("userId")
|
user_id Int @map("userId")
|
||||||
type String
|
type String
|
||||||
provider String
|
provider String
|
||||||
providerAccountId String
|
providerAccountId String
|
||||||
|
@ -74,13 +74,9 @@ model Patient {
|
||||||
room Room? @relation(fields: [room_id], references: [id], onDelete: SetNull)
|
room Room? @relation(fields: [room_id], references: [id], onDelete: SetNull)
|
||||||
room_id Int?
|
room_id Int?
|
||||||
Entry Entry[]
|
Entry Entry[]
|
||||||
hidden Boolean @default(false)
|
|
||||||
|
|
||||||
created_at DateTime @default(now())
|
created_at DateTime @default(now())
|
||||||
|
|
||||||
full_name String? @default(dbgenerated("((first_name || ' '::text) || last_name)")) @ignore
|
|
||||||
|
|
||||||
@@index([full_name(ops: raw("gin_trgm_ops"))], map: "patients_full_name", type: Gin)
|
|
||||||
@@map("patients")
|
@@map("patients")
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -105,8 +101,6 @@ model Entry {
|
||||||
|
|
||||||
created_at DateTime @default(now())
|
created_at DateTime @default(now())
|
||||||
|
|
||||||
tsvec Unsupported("tsvector")?
|
|
||||||
|
|
||||||
@@map("entries")
|
@@map("entries")
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
9
src/api.ts
Normal file
9
src/api.ts
Normal file
|
@ -0,0 +1,9 @@
|
||||||
|
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,3 +1,5 @@
|
||||||
|
import { prisma } from "$lib/server/prisma";
|
||||||
|
import { PrismaAdapter } from "$lib/server/authAdapter";
|
||||||
import { SvelteKitAuth } from "@auth/sveltekit";
|
import { SvelteKitAuth } from "@auth/sveltekit";
|
||||||
import Keycloak from "@auth/core/providers/keycloak";
|
import Keycloak from "@auth/core/providers/keycloak";
|
||||||
import {
|
import {
|
||||||
|
@ -7,12 +9,6 @@ import {
|
||||||
} from "$env/static/private";
|
} from "$env/static/private";
|
||||||
import { redirect, type Handle } from "@sveltejs/kit";
|
import { redirect, type Handle } from "@sveltejs/kit";
|
||||||
import { sequence } from "@sveltejs/kit/hooks";
|
import { sequence } from "@sveltejs/kit/hooks";
|
||||||
import { createTRPCHandle } from "trpc-sveltekit";
|
|
||||||
|
|
||||||
import { prisma } from "$lib/server/prisma";
|
|
||||||
import { PrismaAdapter } from "$lib/server/authAdapter";
|
|
||||||
import { createContext } from "$lib/server/trpc/context";
|
|
||||||
import { router } from "$lib/server/trpc/router";
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Protect the application against unauthorized access.
|
* Protect the application against unauthorized access.
|
||||||
|
@ -45,6 +41,5 @@ export const handle = sequence(
|
||||||
strategy: "jwt",
|
strategy: "jwt",
|
||||||
},
|
},
|
||||||
}),
|
}),
|
||||||
authorization,
|
authorization
|
||||||
createTRPCHandle({ router, createContext })
|
|
||||||
);
|
);
|
||||||
|
|
|
@ -25,7 +25,7 @@ function mapAccount(account: Account): AdapterAccount {
|
||||||
refresh_token: account.refresh_token ?? undefined,
|
refresh_token: account.refresh_token ?? undefined,
|
||||||
access_token: account.access_token ?? undefined,
|
access_token: account.access_token ?? undefined,
|
||||||
expires_at: account.expires_at ?? undefined,
|
expires_at: account.expires_at ?? undefined,
|
||||||
token_type: (account.token_type as Lowercase<string>) ?? undefined,
|
token_type: account.token_type ?? undefined,
|
||||||
scope: account.scope ?? undefined,
|
scope: account.scope ?? undefined,
|
||||||
id_token: account.id_token ?? undefined,
|
id_token: account.id_token ?? undefined,
|
||||||
};
|
};
|
||||||
|
|
|
@ -1,4 +1,3 @@
|
||||||
/*
|
|
||||||
import { ErrorConflict, ErrorNotFound } from "$lib/shared/util/error";
|
import { ErrorConflict, ErrorNotFound } from "$lib/shared/util/error";
|
||||||
import { ZodError } from "zod";
|
import { ZodError } from "zod";
|
||||||
import {
|
import {
|
||||||
|
@ -68,4 +67,3 @@ export async function apiWrapUser<T>(
|
||||||
return handleError(error);
|
return handleError(error);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
*/
|
|
||||||
|
|
|
@ -2,7 +2,6 @@ import { prisma } from "$lib/server/prisma";
|
||||||
import type {
|
import type {
|
||||||
EntriesFilter,
|
EntriesFilter,
|
||||||
Entry,
|
Entry,
|
||||||
EntryExecution,
|
|
||||||
EntryExecutionNew,
|
EntryExecutionNew,
|
||||||
EntryNew,
|
EntryNew,
|
||||||
EntryVersion,
|
EntryVersion,
|
||||||
|
@ -11,7 +10,7 @@ import type {
|
||||||
PaginationRequest,
|
PaginationRequest,
|
||||||
} from "$lib/shared/model";
|
} from "$lib/shared/model";
|
||||||
import { ErrorConflict } from "$lib/shared/util/error";
|
import { ErrorConflict } from "$lib/shared/util/error";
|
||||||
import { mapEntry, mapVersion, mapExecution } from "./mapping";
|
import { mapEntry, mapVersion } 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 } };
|
||||||
|
@ -37,22 +36,14 @@ export async function getEntry(id: number): Promise<Entry> {
|
||||||
return mapEntry(entry);
|
return mapEntry(entry);
|
||||||
}
|
}
|
||||||
|
|
||||||
export async function getEntryVersions(id: number): Promise<EntryVersion[]> {
|
export async function getEntryHistory(id: number): Promise<EntryVersion[]> {
|
||||||
const versions = await prisma.entryVersion.findMany({
|
const entries = await prisma.entryVersion.findMany({
|
||||||
where: { entry_id: id },
|
where: { entry_id: id },
|
||||||
include: { author: USER_SELECT, category: true },
|
include: { author: USER_SELECT, category: true },
|
||||||
orderBy: { created_at: "desc" },
|
orderBy: { created_at: "desc" },
|
||||||
});
|
});
|
||||||
return versions.map(mapVersion);
|
|
||||||
}
|
|
||||||
|
|
||||||
export async function getEntryExecutions(id: number): Promise<EntryExecution[]> {
|
return entries.map(mapVersion);
|
||||||
const executions = await prisma.entryExecution.findMany({
|
|
||||||
where: { entry_id: id },
|
|
||||||
include: { author: USER_SELECT },
|
|
||||||
orderBy: { created_at: "desc" },
|
|
||||||
});
|
|
||||||
return executions.map(mapExecution);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
export async function newEntry(author_id: number, entry: EntryNew): Promise<number> {
|
export async function newEntry(author_id: number, entry: EntryNew): Promise<number> {
|
||||||
|
@ -74,8 +65,7 @@ export async function newEntry(author_id: number, entry: EntryNew): Promise<numb
|
||||||
export async function newEntryVersion(
|
export async function newEntryVersion(
|
||||||
author_id: number,
|
author_id: number,
|
||||||
entry_id: number,
|
entry_id: number,
|
||||||
version: EntryVersionNew,
|
version: EntryVersionNew
|
||||||
old_version_id: number | undefined = undefined
|
|
||||||
): Promise<number> {
|
): Promise<number> {
|
||||||
return prisma.$transaction(async (tx) => {
|
return prisma.$transaction(async (tx) => {
|
||||||
const entry = await tx.entry.findUniqueOrThrow({
|
const entry = await tx.entry.findUniqueOrThrow({
|
||||||
|
@ -90,7 +80,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 (old_version_id && (!cver || cver.id !== old_version_id)) {
|
if (version.old_version && (!cver || cver.id !== version.old_version)) {
|
||||||
throw new ErrorConflict("old version id does not match");
|
throw new ErrorConflict("old version id does not match");
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -113,8 +103,7 @@ export async function newEntryVersion(
|
||||||
export async function newEntryExecution(
|
export async function newEntryExecution(
|
||||||
author_id: number,
|
author_id: number,
|
||||||
entry_id: number,
|
entry_id: number,
|
||||||
execution: EntryExecutionNew,
|
execution: EntryExecutionNew
|
||||||
old_execution_id: number | null | undefined = undefined
|
|
||||||
): Promise<number> {
|
): Promise<number> {
|
||||||
return prisma.$transaction(async (tx) => {
|
return prisma.$transaction(async (tx) => {
|
||||||
const entry = await tx.entry.findUniqueOrThrow({
|
const entry = await tx.entry.findUniqueOrThrow({
|
||||||
|
@ -130,8 +119,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 (
|
||||||
(old_execution_id && (!cex || cex.id !== old_execution_id)) ||
|
(execution.old_execution && (!cex || cex.id !== execution.old_execution)) ||
|
||||||
(old_execution_id === null && cex)
|
(execution.old_execution === null && cex)
|
||||||
) {
|
) {
|
||||||
throw new ErrorConflict("old execution id does not match");
|
throw new ErrorConflict("old execution id does not match");
|
||||||
}
|
}
|
||||||
|
@ -175,7 +164,6 @@ export async function getEntries(
|
||||||
p.first_name as patient_first_name,
|
p.first_name as patient_first_name,
|
||||||
p.last_name as patient_last_name,
|
p.last_name as patient_last_name,
|
||||||
p.age as patient_age,
|
p.age as patient_age,
|
||||||
p.hidden as patient_hidden,
|
|
||||||
p.created_at as patient_created_at,
|
p.created_at as patient_created_at,
|
||||||
r.id as room_id,
|
r.id as room_id,
|
||||||
r.name as room_name,
|
r.name as room_name,
|
||||||
|
@ -271,7 +259,6 @@ join stations s on s.id = r.station_id`
|
||||||
patient_first_name: string;
|
patient_first_name: string;
|
||||||
patient_last_name: string;
|
patient_last_name: string;
|
||||||
patient_age: number;
|
patient_age: number;
|
||||||
patient_hidden: boolean;
|
|
||||||
patient_created_at: Date;
|
patient_created_at: Date;
|
||||||
room_id: number;
|
room_id: number;
|
||||||
room_name: string;
|
room_name: string;
|
||||||
|
@ -294,7 +281,6 @@ join stations s on s.id = r.station_id`
|
||||||
last_name: item.patient_last_name,
|
last_name: item.patient_last_name,
|
||||||
created_at: item.patient_created_at,
|
created_at: item.patient_created_at,
|
||||||
age: item.patient_age,
|
age: item.patient_age,
|
||||||
hidden: item.patient_hidden,
|
|
||||||
room: {
|
room: {
|
||||||
id: item.room_id,
|
id: item.room_id,
|
||||||
name: item.room_name,
|
name: item.room_name,
|
||||||
|
|
|
@ -38,7 +38,6 @@ export function mapPatient(patient: DbPatientLn): Patient {
|
||||||
first_name: patient.first_name,
|
first_name: patient.first_name,
|
||||||
last_name: patient.last_name,
|
last_name: patient.last_name,
|
||||||
created_at: patient.created_at,
|
created_at: patient.created_at,
|
||||||
hidden: patient.hidden,
|
|
||||||
age: patient.age,
|
age: patient.age,
|
||||||
room: patient.room
|
room: patient.room
|
||||||
? {
|
? {
|
||||||
|
@ -74,7 +73,7 @@ export function mapVersion(version: DbEntryVersionLn): EntryVersion {
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
export function mapExecution(execution: DbEntryExecutionLn): EntryExecution {
|
function mapExecution(execution: DbEntryExecutionLn): EntryExecution {
|
||||||
return {
|
return {
|
||||||
id: execution.id,
|
id: execution.id,
|
||||||
author: execution.author,
|
author: execution.author,
|
||||||
|
|
|
@ -36,10 +36,7 @@ export async function deletePatient(id: number) {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/** Hide/show a patient */
|
// TODO: Hide a patient
|
||||||
export async function hidePatient(id: number, hidden: boolean) {
|
|
||||||
await prisma.patient.update({ where: { id }, data: { hidden } });
|
|
||||||
}
|
|
||||||
|
|
||||||
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({
|
||||||
|
@ -56,7 +53,7 @@ export async function getPatients(
|
||||||
pagination: PaginationRequest
|
pagination: PaginationRequest
|
||||||
): Promise<Pagination<Patient>> {
|
): Promise<Pagination<Patient>> {
|
||||||
const qb = new QueryBuilder(
|
const qb = new QueryBuilder(
|
||||||
`select p.id, p.first_name, p.last_name, p.created_at, p.age, p.hidden,
|
`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`,
|
r.id as room_id, r.name as room_name, s.id as station_id, s.name as station_name`,
|
||||||
`from patients p
|
`from patients p
|
||||||
join rooms r on r.id = p.room_id
|
join rooms r on r.id = p.room_id
|
||||||
|
@ -69,10 +66,6 @@ export async function getPatients(
|
||||||
qb.addOrderClause(`similarity(p.full_name, ${qvar}) desc`);
|
qb.addOrderClause(`similarity(p.full_name, ${qvar}) desc`);
|
||||||
}
|
}
|
||||||
|
|
||||||
if (filter.hidden !== undefined) {
|
|
||||||
qb.addFilter("p.hidden", filter.hidden);
|
|
||||||
}
|
|
||||||
|
|
||||||
qb.addFilterList("r.id", filter.room);
|
qb.addFilterList("r.id", filter.room);
|
||||||
qb.addFilterList("s.id", filter.station);
|
qb.addFilterList("s.id", filter.station);
|
||||||
qb.addOrderClause("p.created_at desc");
|
qb.addOrderClause("p.created_at desc");
|
||||||
|
@ -84,7 +77,6 @@ export async function getPatients(
|
||||||
last_name: string;
|
last_name: string;
|
||||||
created_at: Date;
|
created_at: Date;
|
||||||
age: number;
|
age: number;
|
||||||
hidden: boolean;
|
|
||||||
room_id: number;
|
room_id: number;
|
||||||
room_name: string;
|
room_name: string;
|
||||||
station_id: number;
|
station_id: number;
|
||||||
|
@ -104,7 +96,6 @@ export async function getPatients(
|
||||||
last_name: patient.last_name,
|
last_name: patient.last_name,
|
||||||
age: patient.age,
|
age: patient.age,
|
||||||
created_at: patient.created_at,
|
created_at: patient.created_at,
|
||||||
hidden: patient.hidden,
|
|
||||||
room: {
|
room: {
|
||||||
id: patient.room_id,
|
id: patient.room_id,
|
||||||
name: patient.room_name,
|
name: patient.room_name,
|
||||||
|
|
|
@ -1,21 +0,0 @@
|
||||||
import { ZUser } from "$lib/shared/model/validation";
|
|
||||||
import type { RequestEvent } from "@sveltejs/kit";
|
|
||||||
import { type inferAsyncReturnType, TRPCError } from "@trpc/server";
|
|
||||||
|
|
||||||
// we're not using the event parameter is this example,
|
|
||||||
// hence the eslint-disable rule
|
|
||||||
// eslint-disable-next-line @typescript-eslint/no-unused-vars
|
|
||||||
export async function createContext(event: RequestEvent) {
|
|
||||||
const session = await event.locals.getSession();
|
|
||||||
|
|
||||||
if (!session?.user) {
|
|
||||||
throw new TRPCError({ code: "UNAUTHORIZED", message: "no session" });
|
|
||||||
}
|
|
||||||
|
|
||||||
const user = ZUser.parse(session?.user);
|
|
||||||
return {
|
|
||||||
user,
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
export type Context = inferAsyncReturnType<typeof createContext>;
|
|
|
@ -1,4 +0,0 @@
|
||||||
import { initTRPC } from "@trpc/server";
|
|
||||||
import type { Context } from "./context";
|
|
||||||
|
|
||||||
export const t = initTRPC.context<Context>().create();
|
|
|
@ -1,14 +0,0 @@
|
||||||
import { t } from ".";
|
|
||||||
|
|
||||||
import { categoryRouter } from "./routes/category";
|
|
||||||
import { entryRouter } from "./routes/entry";
|
|
||||||
|
|
||||||
export const router = t.router({
|
|
||||||
greeting: t.procedure.query(async () => {
|
|
||||||
return `Hello tRPC v10 @ ${new Date().toLocaleTimeString()}`;
|
|
||||||
}),
|
|
||||||
category: categoryRouter,
|
|
||||||
entry: entryRouter,
|
|
||||||
});
|
|
||||||
|
|
||||||
export type Router = typeof router;
|
|
|
@ -1,26 +0,0 @@
|
||||||
import { t } from "..";
|
|
||||||
import { z } from "zod";
|
|
||||||
|
|
||||||
import {
|
|
||||||
deleteCategory,
|
|
||||||
getCategories,
|
|
||||||
newCategory,
|
|
||||||
updateCategory,
|
|
||||||
} from "$lib/server/query";
|
|
||||||
import { ZCategoryNew, ZEntityId } from "$lib/shared/model/validation";
|
|
||||||
|
|
||||||
export const categoryRouter = t.router({
|
|
||||||
list: t.procedure.query(getCategories),
|
|
||||||
create: t.procedure.input(ZCategoryNew).mutation(async (opts) => {
|
|
||||||
const id = await newCategory(opts.input);
|
|
||||||
return { id };
|
|
||||||
}),
|
|
||||||
update: t.procedure
|
|
||||||
.input(z.object({ id: ZEntityId, category: ZCategoryNew.partial() }))
|
|
||||||
.mutation(async (opts) => {
|
|
||||||
await updateCategory(opts.input.id, opts.input.category);
|
|
||||||
}),
|
|
||||||
delete: t.procedure.input(ZEntityId).mutation(async (opts) => {
|
|
||||||
await deleteCategory(opts.input);
|
|
||||||
}),
|
|
||||||
});
|
|
|
@ -1,74 +0,0 @@
|
||||||
import { t } from "..";
|
|
||||||
import { z } from "zod";
|
|
||||||
|
|
||||||
import {
|
|
||||||
ZEntityId,
|
|
||||||
ZEntriesFilter,
|
|
||||||
ZEntryExecutionNew,
|
|
||||||
ZEntryVersionNew,
|
|
||||||
ZPagination,
|
|
||||||
} from "$lib/shared/model/validation";
|
|
||||||
import {
|
|
||||||
getEntries,
|
|
||||||
getEntry,
|
|
||||||
getEntryExecutions,
|
|
||||||
getEntryVersions,
|
|
||||||
newEntryExecution,
|
|
||||||
newEntryVersion,
|
|
||||||
} from "$lib/server/query";
|
|
||||||
|
|
||||||
export const entryRouter = t.router({
|
|
||||||
get: t.procedure.input(ZEntityId).query(async (opts) => {
|
|
||||||
return getEntry(opts.input);
|
|
||||||
}),
|
|
||||||
list: t.procedure
|
|
||||||
.input(
|
|
||||||
z
|
|
||||||
.object({
|
|
||||||
filter: ZEntriesFilter,
|
|
||||||
pagination: ZPagination,
|
|
||||||
})
|
|
||||||
.partial()
|
|
||||||
)
|
|
||||||
.query(async (opts) => {
|
|
||||||
return getEntries(opts.input.filter || {}, opts.input.pagination || {});
|
|
||||||
}),
|
|
||||||
versions: t.procedure.input(ZEntityId).query(async (opts) => {
|
|
||||||
return getEntryVersions(opts.input);
|
|
||||||
}),
|
|
||||||
executions: t.procedure.input(ZEntityId).query(async (opts) => {
|
|
||||||
return getEntryExecutions(opts.input);
|
|
||||||
}),
|
|
||||||
newVersion: t.procedure
|
|
||||||
.input(
|
|
||||||
z.object({
|
|
||||||
id: ZEntityId,
|
|
||||||
version: ZEntryVersionNew,
|
|
||||||
old_version_id: ZEntityId.optional(),
|
|
||||||
})
|
|
||||||
)
|
|
||||||
.query(async (opts) => {
|
|
||||||
return newEntryVersion(
|
|
||||||
opts.ctx.user.id,
|
|
||||||
opts.input.id,
|
|
||||||
opts.input.version,
|
|
||||||
opts.input.old_version_id
|
|
||||||
);
|
|
||||||
}),
|
|
||||||
newExecution: t.procedure
|
|
||||||
.input(
|
|
||||||
z.object({
|
|
||||||
id: ZEntityId,
|
|
||||||
execution: ZEntryExecutionNew,
|
|
||||||
old_execution_id: ZEntityId.nullable().optional(),
|
|
||||||
})
|
|
||||||
)
|
|
||||||
.query(async (opts) => {
|
|
||||||
return newEntryExecution(
|
|
||||||
opts.ctx.user.id,
|
|
||||||
opts.input.id,
|
|
||||||
opts.input.execution,
|
|
||||||
opts.input.old_execution_id
|
|
||||||
);
|
|
||||||
}),
|
|
||||||
});
|
|
|
@ -63,7 +63,6 @@ export type Patient = {
|
||||||
last_name: string;
|
last_name: string;
|
||||||
age: Option<number>;
|
age: Option<number>;
|
||||||
room: Option<Room>;
|
room: Option<Room>;
|
||||||
hidden: boolean;
|
|
||||||
created_at: Date;
|
created_at: Date;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
@ -104,7 +103,10 @@ export type EntryVersionNdata = {
|
||||||
priority: boolean;
|
priority: boolean;
|
||||||
};
|
};
|
||||||
|
|
||||||
export type EntryVersionNew = Partial<EntryVersionNdata>;
|
export type EntryVersionNew = Partial<{
|
||||||
|
old_version: number;
|
||||||
|
}> &
|
||||||
|
Partial<EntryVersionNdata>;
|
||||||
|
|
||||||
export type EntryExecution = {
|
export type EntryExecution = {
|
||||||
id: number;
|
id: number;
|
||||||
|
@ -113,6 +115,8 @@ export type EntryExecution = {
|
||||||
created_at: Date;
|
created_at: Date;
|
||||||
};
|
};
|
||||||
|
|
||||||
export type EntryExecutionNew = {
|
export type EntryExecutionNew = Partial<{
|
||||||
|
old_execution: number | null;
|
||||||
|
}> & {
|
||||||
text: string;
|
text: string;
|
||||||
};
|
};
|
||||||
|
|
|
@ -21,5 +21,4 @@ export type PatientsFilter = Partial<{
|
||||||
search: string;
|
search: string;
|
||||||
station: FilterList<number>;
|
station: FilterList<number>;
|
||||||
room: FilterList<number>;
|
room: FilterList<number>;
|
||||||
hidden: boolean;
|
|
||||||
}>;
|
}>;
|
||||||
|
|
25
src/lib/shared/model/validation.test.ts
Normal file
25
src/lib/shared/model/validation.test.ts
Normal file
|
@ -0,0 +1,25 @@
|
||||||
|
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);
|
||||||
|
});
|
|
@ -10,19 +10,12 @@ import type {
|
||||||
PatientNew,
|
PatientNew,
|
||||||
RoomNew,
|
RoomNew,
|
||||||
StationNew,
|
StationNew,
|
||||||
User,
|
|
||||||
} from ".";
|
} from ".";
|
||||||
|
|
||||||
export const ZEntityId = z.number().int().nonnegative();
|
const ZEntityId = z.number().int().nonnegative();
|
||||||
const ZNameString = z.string().min(1).max(200).trim();
|
const ZNameString = z.string().min(1).max(200).trim();
|
||||||
const ZTextString = z.string().trim();
|
const ZTextString = z.string().trim();
|
||||||
|
|
||||||
export const ZUser = implement<User>().with({
|
|
||||||
id: ZEntityId,
|
|
||||||
name: z.string().nullable(),
|
|
||||||
email: z.string().nullable(),
|
|
||||||
});
|
|
||||||
|
|
||||||
export const ZStationNew = implement<StationNew>().with({ name: ZNameString });
|
export const ZStationNew = implement<StationNew>().with({ name: ZNameString });
|
||||||
|
|
||||||
export const ZRoomNew = implement<RoomNew>().with({
|
export const ZRoomNew = implement<RoomNew>().with({
|
||||||
|
@ -48,6 +41,7 @@ export const ZPatientNew = implement<PatientNew>().with({
|
||||||
});
|
});
|
||||||
|
|
||||||
export const ZEntryVersionNew = implement<EntryVersionNew>().with({
|
export const ZEntryVersionNew = implement<EntryVersionNew>().with({
|
||||||
|
old_version: ZEntityId.optional(),
|
||||||
text: ZTextString.optional(),
|
text: ZTextString.optional(),
|
||||||
date: z.date().optional(),
|
date: z.date().optional(),
|
||||||
category_id: ZEntityId.optional().nullable(),
|
category_id: ZEntityId.optional().nullable(),
|
||||||
|
@ -65,40 +59,41 @@ export const ZEntryNew = implement<EntryNew>().with({
|
||||||
});
|
});
|
||||||
|
|
||||||
export const ZEntryExecutionNew = implement<EntryExecutionNew>().with({
|
export const ZEntryExecutionNew = implement<EntryExecutionNew>().with({
|
||||||
|
old_execution: ZEntityId.optional().nullable(),
|
||||||
text: ZTextString,
|
text: ZTextString,
|
||||||
});
|
});
|
||||||
|
|
||||||
export const ZPagination = implement<PaginationRequest>().with({
|
// From URL
|
||||||
limit: ZEntityId.optional(),
|
const ZFilterListUrl = z
|
||||||
offset: ZEntityId.optional(),
|
.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(),
|
||||||
});
|
});
|
||||||
|
|
||||||
// const ZFilterList = z
|
export const ZEntriesFilterUrl = z.object({
|
||||||
// .string()
|
author: ZFilterListUrl,
|
||||||
// .regex(/^\d+(;\d+)*$/)
|
category: ZFilterListUrl,
|
||||||
// .transform((s) => s.split(";").map(Number))
|
done: BooleanUrl.optional(),
|
||||||
// .optional();
|
executor: ZFilterListUrl,
|
||||||
const ZFilterList = z.array(ZEntityId).or(ZEntityId);
|
patient: ZFilterListUrl,
|
||||||
|
priority: BooleanUrl.optional(),
|
||||||
|
room: ZFilterListUrl,
|
||||||
|
search: z.string().optional(),
|
||||||
|
station: ZFilterListUrl,
|
||||||
|
});
|
||||||
|
|
||||||
export const ZEntriesFilter = z
|
export const ZPatientsFilterUrl = z.object({
|
||||||
.object({
|
room: ZFilterListUrl,
|
||||||
author: ZFilterList,
|
station: ZFilterListUrl,
|
||||||
category: ZFilterList,
|
});
|
||||||
done: z.boolean(),
|
|
||||||
executor: ZFilterList,
|
|
||||||
patient: ZFilterList,
|
|
||||||
priority: z.boolean(),
|
|
||||||
room: ZFilterList,
|
|
||||||
search: z.string(),
|
|
||||||
station: ZFilterList,
|
|
||||||
})
|
|
||||||
.partial();
|
|
||||||
|
|
||||||
export const ZPatientsFilterUrl = z
|
|
||||||
.object({
|
|
||||||
search: z.string(),
|
|
||||||
room: ZFilterList,
|
|
||||||
station: ZFilterList,
|
|
||||||
hidden: z.boolean(),
|
|
||||||
})
|
|
||||||
.partial();
|
|
||||||
|
|
|
@ -1,16 +0,0 @@
|
||||||
import type { Router } from "$lib/server/trpc/router";
|
|
||||||
import { createTRPCClient, type TRPCClientInit } from "trpc-sveltekit";
|
|
||||||
|
|
||||||
let browserClient: ReturnType<typeof createTRPCClient<Router>>;
|
|
||||||
|
|
||||||
/** Get a new tRPC client
|
|
||||||
*
|
|
||||||
* This function switches between calling the tRPC server directly and using the TRPC
|
|
||||||
* HTTP client depending on the side it is called on. */
|
|
||||||
export function trpc(init?: TRPCClientInit) {
|
|
||||||
const isBrowser = typeof window !== "undefined";
|
|
||||||
if (isBrowser && browserClient) return browserClient;
|
|
||||||
const client = createTRPCClient<Router>({ init });
|
|
||||||
if (isBrowser) browserClient = client;
|
|
||||||
return client;
|
|
||||||
}
|
|
|
@ -1,10 +1,10 @@
|
||||||
<script lang="ts">
|
<script lang="ts">
|
||||||
import type { PageData } from "./$types";
|
import api from "$api";
|
||||||
|
|
||||||
export let data: PageData;
|
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>
|
||||||
|
|
||||||
<p>{data.greeting}</p>
|
|
||||||
<p>{JSON.stringify(data.categories)}</p>
|
|
||||||
|
|
|
@ -1,11 +0,0 @@
|
||||||
import { trpc } from "$lib/shared/trpc";
|
|
||||||
import type { PageLoad } from "./$types";
|
|
||||||
|
|
||||||
export const load: PageLoad = async (event) => {
|
|
||||||
const [greeting, categories] = await Promise.all([
|
|
||||||
trpc(event).greeting.query(),
|
|
||||||
trpc(event).category.list.query(),
|
|
||||||
]);
|
|
||||||
|
|
||||||
return { greeting, categories };
|
|
||||||
};
|
|
21
src/routes/api/category/+server.ts
Normal file
21
src/routes/api/category/+server.ts
Normal file
|
@ -0,0 +1,21 @@
|
||||||
|
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 };
|
||||||
|
});
|
||||||
|
};
|
35
src/routes/api/category/[id]/+server.ts
Normal file
35
src/routes/api/category/[id]/+server.ts
Normal file
|
@ -0,0 +1,35 @@
|
||||||
|
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 };
|
||||||
|
});
|
||||||
|
};
|
15
src/routes/api/entry/+server.ts
Normal file
15
src/routes/api/entry/+server.ts
Normal file
|
@ -0,0 +1,15 @@
|
||||||
|
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);
|
||||||
|
});
|
||||||
|
};
|
13
src/routes/api/entry/[id]/+server.ts
Normal file
13
src/routes/api/entry/[id]/+server.ts
Normal file
|
@ -0,0 +1,13 @@
|
||||||
|
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);
|
||||||
|
});
|
||||||
|
};
|
18
src/routes/api/entry/[id]/execution/+server.ts
Normal file
18
src/routes/api/entry/[id]/execution/+server.ts
Normal file
|
@ -0,0 +1,18 @@
|
||||||
|
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 };
|
||||||
|
});
|
||||||
|
};
|
26
src/routes/api/entry/[id]/version/+server.ts
Normal file
26
src/routes/api/entry/[id]/version/+server.ts
Normal file
|
@ -0,0 +1,26 @@
|
||||||
|
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 };
|
||||||
|
});
|
||||||
|
};
|
15
src/routes/api/patient/+server.ts
Normal file
15
src/routes/api/patient/+server.ts
Normal file
|
@ -0,0 +1,15 @@
|
||||||
|
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);
|
||||||
|
});
|
||||||
|
};
|
35
src/routes/api/patient/[id]/+server.ts
Normal file
35
src/routes/api/patient/[id]/+server.ts
Normal file
|
@ -0,0 +1,35 @@
|
||||||
|
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 };
|
||||||
|
});
|
||||||
|
};
|
20
src/routes/api/room/+server.ts
Normal file
20
src/routes/api/room/+server.ts
Normal file
|
@ -0,0 +1,20 @@
|
||||||
|
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 };
|
||||||
|
});
|
||||||
|
};
|
35
src/routes/api/room/[id]/+server.ts
Normal file
35
src/routes/api/room/[id]/+server.ts
Normal file
|
@ -0,0 +1,35 @@
|
||||||
|
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 };
|
||||||
|
});
|
||||||
|
};
|
20
src/routes/api/station/+server.ts
Normal file
20
src/routes/api/station/+server.ts
Normal file
|
@ -0,0 +1,20 @@
|
||||||
|
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 };
|
||||||
|
});
|
||||||
|
};
|
35
src/routes/api/station/[id]/+server.ts
Normal file
35
src/routes/api/station/[id]/+server.ts
Normal file
|
@ -0,0 +1,35 @@
|
||||||
|
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 };
|
||||||
|
});
|
||||||
|
};
|
14
src/routes/api/user/+server.ts
Normal file
14
src/routes/api/user/+server.ts
Normal file
|
@ -0,0 +1,14 @@
|
||||||
|
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);
|
||||||
|
});
|
||||||
|
};
|
|
@ -1,7 +1,7 @@
|
||||||
import {
|
import {
|
||||||
getEntries,
|
getEntries,
|
||||||
getEntry,
|
getEntry,
|
||||||
getEntryVersions,
|
getEntryHistory,
|
||||||
newEntry,
|
newEntry,
|
||||||
newEntryExecution,
|
newEntryExecution,
|
||||||
newEntryVersion,
|
newEntryVersion,
|
||||||
|
@ -59,7 +59,7 @@ test("create entry version", async () => {
|
||||||
};
|
};
|
||||||
expect(entry.current_version).toMatchObject(expectedVersion);
|
expect(entry.current_version).toMatchObject(expectedVersion);
|
||||||
|
|
||||||
const history = await getEntryVersions(eId);
|
const history = await getEntryHistory(eId);
|
||||||
expect(history).length(2);
|
expect(history).length(2);
|
||||||
expect(history[0]).toMatchObject(expectedVersion);
|
expect(history[0]).toMatchObject(expectedVersion);
|
||||||
expect(history[1].text).toBe(TEST_VERSION.text);
|
expect(history[1].text).toBe(TEST_VERSION.text);
|
||||||
|
@ -74,7 +74,10 @@ test("create entry version (partial)", async () => {
|
||||||
const oldEntry = await getEntry(eId);
|
const oldEntry = await getEntry(eId);
|
||||||
const text = "10ml Blut abnehmen\n\nPS: Nadel nicht vergessen";
|
const text = "10ml Blut abnehmen\n\nPS: Nadel nicht vergessen";
|
||||||
|
|
||||||
await newEntryVersion(2, eId, { text }, oldEntry.current_version.id);
|
await newEntryVersion(2, eId, {
|
||||||
|
old_version: oldEntry.current_version.id,
|
||||||
|
text,
|
||||||
|
});
|
||||||
|
|
||||||
const entry = await getEntry(eId);
|
const entry = await getEntry(eId);
|
||||||
expect(entry.current_version).toMatchObject({
|
expect(entry.current_version).toMatchObject({
|
||||||
|
@ -94,12 +97,10 @@ test("create entry version (wrong old vid)", async () => {
|
||||||
const entry = await getEntry(eId);
|
const entry = await getEntry(eId);
|
||||||
|
|
||||||
expect(async () => {
|
expect(async () => {
|
||||||
await newEntryVersion(
|
await newEntryVersion(1, eId, {
|
||||||
1,
|
old_version: entry.current_version.id + 1,
|
||||||
eId,
|
text: "Hello World",
|
||||||
{ text: "Hello World" },
|
});
|
||||||
entry.current_version.id + 1
|
|
||||||
);
|
|
||||||
}).rejects.toThrowError(new ErrorConflict("old version id does not match"));
|
}).rejects.toThrowError(new ErrorConflict("old version id does not match"));
|
||||||
});
|
});
|
||||||
|
|
||||||
|
@ -110,7 +111,7 @@ test("create entry execution", async () => {
|
||||||
});
|
});
|
||||||
const text = "Blutabnahme erledigt.";
|
const text = "Blutabnahme erledigt.";
|
||||||
|
|
||||||
const xId = await newEntryExecution(1, eId, { text }, null);
|
const xId = await newEntryExecution(1, eId, { old_execution: null, text });
|
||||||
|
|
||||||
const entry = await getEntry(eId);
|
const entry = await getEntry(eId);
|
||||||
expect(entry.execution?.id).toBe(xId);
|
expect(entry.execution?.id).toBe(xId);
|
||||||
|
@ -124,8 +125,8 @@ test("create entry execution (update)", async () => {
|
||||||
version: TEST_VERSION,
|
version: TEST_VERSION,
|
||||||
});
|
});
|
||||||
|
|
||||||
const x1 = await newEntryExecution(1, eId, { text: "x1" }, null);
|
const x1 = await newEntryExecution(1, eId, { old_execution: null, text: "x1" });
|
||||||
const x2 = await newEntryExecution(2, eId, { text: "x2" }, x1);
|
const x2 = await newEntryExecution(2, eId, { old_execution: x1, text: "x2" });
|
||||||
|
|
||||||
const entry = await getEntry(eId);
|
const entry = await getEntry(eId);
|
||||||
expect(entry.execution?.id).toBe(x2);
|
expect(entry.execution?.id).toBe(x2);
|
||||||
|
@ -138,10 +139,10 @@ test("create entry execution (wrong old xid)", async () => {
|
||||||
patient_id: 1,
|
patient_id: 1,
|
||||||
version: TEST_VERSION,
|
version: TEST_VERSION,
|
||||||
});
|
});
|
||||||
const x1 = await newEntryExecution(1, eId, { text: "x1" }, null);
|
const x1 = await newEntryExecution(1, eId, { old_execution: null, text: "x1" });
|
||||||
|
|
||||||
expect(
|
expect(
|
||||||
async () => await newEntryExecution(1, eId, { text: "x2" }, x1 + 1)
|
async () => await 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"));
|
||||||
});
|
});
|
||||||
|
|
||||||
|
@ -238,12 +239,6 @@ test("get entries", async () => {
|
||||||
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 not done
|
|
||||||
const entriesNotDone = await getEntries({ done: false }, {});
|
|
||||||
expect(entriesNotDone.items).length(1);
|
|
||||||
expect(entriesNotDone.total).toBe(1);
|
|
||||||
expect(entriesNotDone.items[0].id).toBe(eId1);
|
|
||||||
|
|
||||||
// Filter by priority
|
// Filter by priority
|
||||||
const entriesPrio = await getEntries({ priority: true }, {});
|
const entriesPrio = await getEntries({ priority: true }, {});
|
||||||
expect(entriesPrio.items).length(1);
|
expect(entriesPrio.items).length(1);
|
||||||
|
|
|
@ -5,7 +5,6 @@ import {
|
||||||
updatePatient,
|
updatePatient,
|
||||||
deletePatient,
|
deletePatient,
|
||||||
newEntry,
|
newEntry,
|
||||||
hidePatient,
|
|
||||||
} from "$lib/server/query";
|
} from "$lib/server/query";
|
||||||
import { expect, test } from "vitest";
|
import { expect, test } from "vitest";
|
||||||
import { S1, S2 } from "$tests/helpers/testdata";
|
import { S1, S2 } from "$tests/helpers/testdata";
|
||||||
|
@ -66,16 +65,6 @@ test("delete patient (restricted)", async () => {
|
||||||
);
|
);
|
||||||
});
|
});
|
||||||
|
|
||||||
test("hide patient", async () => {
|
|
||||||
await hidePatient(1, true);
|
|
||||||
let patient = await getPatient(1);
|
|
||||||
expect(patient.hidden).toBe(true);
|
|
||||||
|
|
||||||
await hidePatient(1, false);
|
|
||||||
patient = await getPatient(1);
|
|
||||||
expect(patient.hidden).toBe(false);
|
|
||||||
});
|
|
||||||
|
|
||||||
test("get patients", async () => {
|
test("get patients", async () => {
|
||||||
const patients = await getPatients({}, {});
|
const patients = await getPatients({}, {});
|
||||||
expect(patients).toMatchObject({
|
expect(patients).toMatchObject({
|
||||||
|
@ -129,18 +118,6 @@ test("get patients (by station)", async () => {
|
||||||
expect(patients.items[1].id).toBe(2);
|
expect(patients.items[1].id).toBe(2);
|
||||||
});
|
});
|
||||||
|
|
||||||
test("get patients (hidden)", async () => {
|
|
||||||
await hidePatient(2, true);
|
|
||||||
|
|
||||||
let patients = await getPatients({ hidden: false }, {});
|
|
||||||
expect(patients.items).length(2);
|
|
||||||
expect(patients.items[0].id).toBe(1);
|
|
||||||
|
|
||||||
patients = await getPatients({ hidden: true }, {});
|
|
||||||
expect(patients.items).length(1);
|
|
||||||
expect(patients.items[0].id).toBe(2);
|
|
||||||
});
|
|
||||||
|
|
||||||
test("search patients", async () => {
|
test("search patients", async () => {
|
||||||
const patients = await getPatients({ search: "Schustr" }, {});
|
const patients = await getPatients({ search: "Schustr" }, {});
|
||||||
expect(patients.items).length(1);
|
expect(patients.items).length(1);
|
||||||
|
|
|
@ -1,8 +1,9 @@
|
||||||
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()],
|
plugins: [sveltekit(), zeroAPI()],
|
||||||
test: {
|
test: {
|
||||||
include: ["src/**/*.{test,spec}.{js,ts}"],
|
include: ["src/**/*.{test,spec}.{js,ts}"],
|
||||||
},
|
},
|
||||||
|
|
Loading…
Reference in a new issue