FHIR, API, datamodel
This commit is contained in:
163
app/api/fhir/Patient/[id]/route.ts
Normal file
163
app/api/fhir/Patient/[id]/route.ts
Normal file
@@ -0,0 +1,163 @@
|
||||
/**
|
||||
* FHIR Patient API - Instance Endpoints
|
||||
* GET /api/fhir/Patient/[id] - Read patient
|
||||
* PUT /api/fhir/Patient/[id] - Update patient
|
||||
*/
|
||||
|
||||
import { NextRequest, NextResponse } from 'next/server';
|
||||
import { supabaseAdmin } from '@/lib/supabase/server';
|
||||
import {
|
||||
dbPatientToFHIR,
|
||||
fhirPatientToDB,
|
||||
createOperationOutcome,
|
||||
validateFHIRResource,
|
||||
} from '@/lib/fhir';
|
||||
import type { FHIRPatient } from '@/lib/fhir';
|
||||
|
||||
/**
|
||||
* GET /api/fhir/Patient/[id]
|
||||
* Returns a single FHIR Patient resource
|
||||
*/
|
||||
export async function GET(
|
||||
request: NextRequest,
|
||||
{ params }: { params: { id: string } }
|
||||
) {
|
||||
try {
|
||||
const { id } = params;
|
||||
|
||||
// Query patient by ID
|
||||
const { data: patient, error } = await supabaseAdmin
|
||||
.from('patients')
|
||||
.select('*')
|
||||
.eq('id', id)
|
||||
.single();
|
||||
|
||||
if (error || !patient) {
|
||||
return NextResponse.json(
|
||||
createOperationOutcome(
|
||||
'error',
|
||||
'not-found',
|
||||
`Patient with id ${id} not found`
|
||||
),
|
||||
{ status: 404 }
|
||||
);
|
||||
}
|
||||
|
||||
// Transform to FHIR resource
|
||||
const fhirPatient = dbPatientToFHIR(patient);
|
||||
|
||||
return NextResponse.json(fhirPatient);
|
||||
} catch (error) {
|
||||
return NextResponse.json(
|
||||
createOperationOutcome(
|
||||
'error',
|
||||
'exception',
|
||||
error instanceof Error ? error.message : 'Unknown error'
|
||||
),
|
||||
{ status: 500 }
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* PUT /api/fhir/Patient/[id]
|
||||
* Updates a patient from FHIR Patient resource
|
||||
*/
|
||||
export async function PUT(
|
||||
request: NextRequest,
|
||||
{ params }: { params: { id: string } }
|
||||
) {
|
||||
try {
|
||||
const { id } = params;
|
||||
const fhirPatient: FHIRPatient = await request.json();
|
||||
|
||||
// Validate required fields
|
||||
const validation = validateFHIRResource(fhirPatient, [
|
||||
'resourceType',
|
||||
'name',
|
||||
'gender',
|
||||
'birthDate',
|
||||
]);
|
||||
|
||||
if (!validation.valid) {
|
||||
return NextResponse.json(
|
||||
createOperationOutcome('error', 'invalid', validation.errors.join(', ')),
|
||||
{ status: 400 }
|
||||
);
|
||||
}
|
||||
|
||||
// Verify resourceType
|
||||
if (fhirPatient.resourceType !== 'Patient') {
|
||||
return NextResponse.json(
|
||||
createOperationOutcome(
|
||||
'error',
|
||||
'invalid',
|
||||
`Expected resourceType "Patient", got "${fhirPatient.resourceType}"`
|
||||
),
|
||||
{ status: 400 }
|
||||
);
|
||||
}
|
||||
|
||||
// Verify ID matches
|
||||
if (fhirPatient.id && fhirPatient.id !== id) {
|
||||
return NextResponse.json(
|
||||
createOperationOutcome(
|
||||
'error',
|
||||
'invalid',
|
||||
`ID in URL (${id}) does not match ID in resource (${fhirPatient.id})`
|
||||
),
|
||||
{ status: 400 }
|
||||
);
|
||||
}
|
||||
|
||||
// Check if patient exists
|
||||
const { data: existingPatient, error: fetchError } = await supabaseAdmin
|
||||
.from('patients')
|
||||
.select('id')
|
||||
.eq('id', id)
|
||||
.single();
|
||||
|
||||
if (fetchError || !existingPatient) {
|
||||
return NextResponse.json(
|
||||
createOperationOutcome(
|
||||
'error',
|
||||
'not-found',
|
||||
`Patient with id ${id} not found`
|
||||
),
|
||||
{ status: 404 }
|
||||
);
|
||||
}
|
||||
|
||||
// Transform to database format
|
||||
const patientUpdate = fhirPatientToDB(fhirPatient);
|
||||
|
||||
// Update in database
|
||||
const { data: updatedPatient, error: updateError } = await supabaseAdmin
|
||||
.from('patients')
|
||||
.update(patientUpdate)
|
||||
.eq('id', id)
|
||||
.select()
|
||||
.single();
|
||||
|
||||
if (updateError) {
|
||||
return NextResponse.json(
|
||||
createOperationOutcome('error', 'processing', updateError.message),
|
||||
{ status: 500 }
|
||||
);
|
||||
}
|
||||
|
||||
// Return updated patient as FHIR resource
|
||||
const fhirResponse = dbPatientToFHIR(updatedPatient);
|
||||
|
||||
return NextResponse.json(fhirResponse);
|
||||
} catch (error) {
|
||||
return NextResponse.json(
|
||||
createOperationOutcome(
|
||||
'error',
|
||||
'exception',
|
||||
error instanceof Error ? error.message : 'Unknown error'
|
||||
),
|
||||
{ status: 500 }
|
||||
);
|
||||
}
|
||||
}
|
||||
148
app/api/fhir/Patient/route.ts
Normal file
148
app/api/fhir/Patient/route.ts
Normal file
@@ -0,0 +1,148 @@
|
||||
/**
|
||||
* FHIR Patient API - Collection Endpoints
|
||||
* GET /api/fhir/Patient - List patients
|
||||
* POST /api/fhir/Patient - Create patient
|
||||
*/
|
||||
|
||||
import { NextRequest, NextResponse } from 'next/server';
|
||||
import { supabaseAdmin } from '@/lib/supabase/server';
|
||||
import {
|
||||
dbPatientToFHIR,
|
||||
fhirPatientToDB,
|
||||
createOperationOutcome,
|
||||
validateFHIRResource,
|
||||
} from '@/lib/fhir';
|
||||
import type { FHIRBundle, FHIRPatient } from '@/lib/fhir';
|
||||
|
||||
/**
|
||||
* GET /api/fhir/Patient
|
||||
* Returns a FHIR Bundle with searchset of patients
|
||||
* Supports query parameters: name, identifier, birthdate
|
||||
*/
|
||||
export async function GET(request: NextRequest) {
|
||||
try {
|
||||
const { searchParams } = new URL(request.url);
|
||||
|
||||
// Build query
|
||||
let query = supabaseAdmin.from('patients').select('*');
|
||||
|
||||
// Search by name (family or given)
|
||||
const name = searchParams.get('name');
|
||||
if (name) {
|
||||
query = query.or(
|
||||
`name_family.ilike.%${name}%,name_given.cs.{${name}}`
|
||||
);
|
||||
}
|
||||
|
||||
// Search by identifier (BSN)
|
||||
const identifier = searchParams.get('identifier');
|
||||
if (identifier) {
|
||||
query = query.eq('identifier_bsn', identifier);
|
||||
}
|
||||
|
||||
// Search by birth date
|
||||
const birthdate = searchParams.get('birthdate');
|
||||
if (birthdate) {
|
||||
query = query.eq('birth_date', birthdate);
|
||||
}
|
||||
|
||||
// Execute query
|
||||
const { data: patients, error } = await query;
|
||||
|
||||
if (error) {
|
||||
return NextResponse.json(
|
||||
createOperationOutcome('error', 'processing', error.message),
|
||||
{ status: 500 }
|
||||
);
|
||||
}
|
||||
|
||||
// Transform to FHIR Bundle
|
||||
const bundle: FHIRBundle<FHIRPatient> = {
|
||||
resourceType: 'Bundle',
|
||||
type: 'searchset',
|
||||
total: patients?.length || 0,
|
||||
entry: patients?.map((patient) => ({
|
||||
resource: dbPatientToFHIR(patient),
|
||||
})) || [],
|
||||
};
|
||||
|
||||
return NextResponse.json(bundle);
|
||||
} catch (error) {
|
||||
return NextResponse.json(
|
||||
createOperationOutcome(
|
||||
'error',
|
||||
'exception',
|
||||
error instanceof Error ? error.message : 'Unknown error'
|
||||
),
|
||||
{ status: 500 }
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* POST /api/fhir/Patient
|
||||
* Creates a new patient from FHIR Patient resource
|
||||
*/
|
||||
export async function POST(request: NextRequest) {
|
||||
try {
|
||||
const fhirPatient: FHIRPatient = await request.json();
|
||||
|
||||
// Validate required fields
|
||||
const validation = validateFHIRResource(fhirPatient, [
|
||||
'resourceType',
|
||||
'name',
|
||||
'gender',
|
||||
'birthDate',
|
||||
]);
|
||||
|
||||
if (!validation.valid) {
|
||||
return NextResponse.json(
|
||||
createOperationOutcome('error', 'invalid', validation.errors.join(', ')),
|
||||
{ status: 400 }
|
||||
);
|
||||
}
|
||||
|
||||
// Verify resourceType
|
||||
if (fhirPatient.resourceType !== 'Patient') {
|
||||
return NextResponse.json(
|
||||
createOperationOutcome(
|
||||
'error',
|
||||
'invalid',
|
||||
`Expected resourceType "Patient", got "${fhirPatient.resourceType}"`
|
||||
),
|
||||
{ status: 400 }
|
||||
);
|
||||
}
|
||||
|
||||
// Transform to database format
|
||||
const patientInsert = fhirPatientToDB(fhirPatient);
|
||||
|
||||
// Insert into database
|
||||
const { data: newPatient, error } = await supabaseAdmin
|
||||
.from('patients')
|
||||
.insert(patientInsert)
|
||||
.select()
|
||||
.single();
|
||||
|
||||
if (error) {
|
||||
return NextResponse.json(
|
||||
createOperationOutcome('error', 'processing', error.message),
|
||||
{ status: 500 }
|
||||
);
|
||||
}
|
||||
|
||||
// Return created patient as FHIR resource
|
||||
const fhirResponse = dbPatientToFHIR(newPatient);
|
||||
|
||||
return NextResponse.json(fhirResponse, { status: 201 });
|
||||
} catch (error) {
|
||||
return NextResponse.json(
|
||||
createOperationOutcome(
|
||||
'error',
|
||||
'exception',
|
||||
error instanceof Error ? error.message : 'Unknown error'
|
||||
),
|
||||
{ status: 500 }
|
||||
);
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user