Browse Source

Implement delete

Add delete endpoint.
master
TheoryOfNekomata 5 months ago
parent
commit
0806892377
3 changed files with 185 additions and 3 deletions
  1. +21
    -2
      src/core.ts
  2. +4
    -0
      src/data-sources/file-jsonl.ts
  3. +160
    -1
      src/handlers.ts

+ 21
- 2
src/core.ts View File

@@ -4,11 +4,11 @@ import { pluralize } from 'inflection';
import { BaseSchema, ObjectSchema } from 'valibot';
import { SerializerPair } from './serializers';
import {
handleCreateItem,
handleCreateItem, handleDeleteItem,
handleGetCollection,
handleGetItem,
handleGetRoot,
handleHasMethodAndUrl
handleHasMethodAndUrl,
} from './handlers';

export interface DataSource<T = object> {
@@ -34,6 +34,8 @@ export interface Resource {
dataSource: DataSource;
newId(dataSource: DataSource): string | number | unknown;
schema: BaseSchema;
throws404OnDeletingNotFound: boolean;
checksSerializersOnDelete: boolean;
}

interface GenerationStrategy {
@@ -50,9 +52,25 @@ export const resource = <T extends BaseSchema>(schema: T) => {
let theCollectionName: string;
let theRouteName: string;
let idGenerationStrategy: GenerationStrategy;
let throw404OnDeletingNotFound = true;
let checkSerializersOnDelete = false;
const fullTextAttrs = new Set<string>();

return {
shouldCheckSerializersOnDelete(b = true) {
checkSerializersOnDelete = b;
return this;
},
get checksSerializersOnDelete() {
return checkSerializersOnDelete;
},
shouldThrow404OnDeletingNotFound(b = true) {
throw404OnDeletingNotFound = b;
return this;
},
get throws404OnDeletingNotFound() {
return throw404OnDeletingNotFound;
},
id(newIdAttr: string, params: IdParams) {
theIdAttr = newIdAttr;
idGenerationStrategy = params.generationStrategy;
@@ -172,6 +190,7 @@ export const application = (appParams: ApplicationParams) => {
handleGetCollection,
handleGetItem,
handleCreateItem,
handleDeleteItem,
]
.reduce(
async (currentHandlerStatePromise, middleware) => {


+ 4
- 0
src/data-sources/file-jsonl.ts View File

@@ -48,9 +48,13 @@ export class DataSource<T extends Record<string, string>> implements DataSourceI
}

async delete(id: string) {
const oldDataLength = this.data.length;

const newData = this.data.filter((s) => !(s[this.resource.idAttr as string].toString() === id));

await writeFile(this.path, newData.map((d) => JSON.stringify(d)).join('\n'));

return oldDataLength !== newData.length;
}

async emplace(id: string, data: Partial<T>) {


+ 160
- 1
src/handlers.ts View File

@@ -1,7 +1,7 @@
import { constants } from 'http2';
import Negotiator from 'negotiator';
import { ValiError } from 'valibot';
import { Middleware } from './core';
import {Middleware} from './core';
import { getBody, getMethod, getUrl } from './utils';

export const handleHasMethodAndUrl: Middleware = ({}) => (req, res) => {
@@ -220,6 +220,165 @@ export const handleGetItem: Middleware = ({
};
};


export const handleDeleteItem: Middleware = ({
appState,
serverParams
}) => async (req, res) => {
const method = getMethod(req);
if (method !== 'DELETE') {
return {
handled: false
};
}

const baseUrl = serverParams.baseUrl ?? '';
const { url } = getUrl(req, baseUrl);

const [, mainResourceRouteName, mainResourceId = ''] = url.split('/');
if (mainResourceId === '') {
return {
handled: false
}
}

const theResource = Array.from(appState.resources).find((r) => r.routeName === mainResourceRouteName);
if (typeof theResource === 'undefined') {
return {
handled: false
};
}

if (theResource.checksSerializersOnDelete) {
const negotiator = new Negotiator(req);
const availableMediaTypes = Array.from(appState.serializers.keys());
const theMediaType = negotiator.mediaType(availableMediaTypes);
if (typeof theMediaType === 'undefined') {
res.statusCode = constants.HTTP_STATUS_NOT_ACCEPTABLE;
res.end();
return {
handled: true
};
}

const theSerializerPair = appState.serializers.get(theMediaType);
if (typeof theSerializerPair === 'undefined') {
res.statusCode = constants.HTTP_STATUS_NOT_ACCEPTABLE;
res.end();
return {
handled: true
};
}
}

await theResource.dataSource.initialize();
try {
const response = await theResource.dataSource.delete(mainResourceId);
if (typeof response !== 'undefined' && !response && theResource.throws404OnDeletingNotFound) {
res.statusCode = constants.HTTP_STATUS_NOT_FOUND;
res.statusMessage = `${theResource.itemName} Not Found`;
} else {
res.statusCode = constants.HTTP_STATUS_NO_CONTENT;
}
res.end();
return {
handled: true
};
} catch {
// TODO error handling
// what if item is already deleted? Should we hide it by returning no content or throw a 404?
}

res.statusCode = constants.HTTP_STATUS_INTERNAL_SERVER_ERROR;
res.end();
return {
handled: true
};
};

// export const handlePatchItem: Middleware = ({
// appState,
// serverParams,
// }) => async (req, res) => {
// const method = getMethod(req);
// if (method !== 'PATCH') {
// return {
// handled: false
// };
// }
//
// const baseUrl = serverParams.baseUrl ?? '';
// const { url } = getUrl(req, baseUrl);
//
// const [, mainResourceRouteName, mainResourceId = ''] = url.split('/');
// if (mainResourceId === '') {
// return {
// handled: false
// }
// }
//
// const theResource = Array.from(appState.resources).find((r) => r.routeName === mainResourceRouteName);
// if (typeof theResource === 'undefined') {
// return {
// handled: false
// };
// }
//
// const theDeserializer = appState.serializers.get(req.headers['content-type'] ?? 'application/octet-stream');
// if (typeof theDeserializer === 'undefined') {
// res.statusCode = constants.HTTP_STATUS_UNSUPPORTED_MEDIA_TYPE;
// res.end();
// return {
// handled: true
// };
// }
//
// const negotiator = new Negotiator(req);
// const availableMediaTypes = Array.from(appState.serializers.keys());
// const theMediaType = negotiator.mediaType(availableMediaTypes);
// if (typeof theMediaType === 'undefined') {
// res.statusCode = constants.HTTP_STATUS_NOT_ACCEPTABLE;
// res.end();
// return {
// handled: true
// };
// }
//
// const theSerializerPair = appState.serializers.get(theMediaType);
// if (typeof theSerializerPair === 'undefined') {
// res.statusCode = constants.HTTP_STATUS_NOT_ACCEPTABLE;
// res.end();
// return {
// handled: true
// };
// }
//
// await theResource.dataSource.initialize();
// let bodyDeserialized: unknown;
// try {
// bodyDeserialized = await getBody(req, theDeserializer, theResource.schema);
// } catch (errRaw) {
// const err = errRaw as ValiError;
// res.statusCode = constants.HTTP_STATUS_BAD_REQUEST;
// res.statusMessage = `Invalid ${theResource.itemName}`;
//
// if (Array.isArray(err.issues)) {
// // TODO better error reporting, localizable messages
// const theFormatted = theSerializerPair.serialize(
// err.issues.map((i) => (
// `${i.path?.map((p) => p.key)?.join('.') ?? i.reason}: ${i.message}`
// ))
// );
// res.end(theFormatted);
// } else {
// res.end();
// }
// return {
// handled: true
// };
// }
// };

export const handleCreateItem: Middleware = ({
appState,
serverParams


Loading…
Cancel
Save