refactor: revert json schema changes

This commit is contained in:
2023-11-01 14:44:45 +01:00
parent 0de613969e
commit d18a579cb8
22 changed files with 173 additions and 371 deletions

View File

@@ -14,8 +14,9 @@
*/ */
import {Plugin} from '@openstapps/api-plugin'; import {Plugin} from '@openstapps/api-plugin';
import * as express from 'express'; import * as express from 'express';
import {requestSchema, SCMinimalRequest} from './protocol/request.js'; import schema from '../../lib/schema.json';
import {responseSchema, SCMinimalResponse} from './protocol/response.js'; import {SCMinimalRequest} from './protocol/request.js';
import {SCMinimalResponse} from './protocol/response.js';
/** /**
* The Plugin Class * The Plugin Class
@@ -24,9 +25,9 @@ import {responseSchema, SCMinimalResponse} from './protocol/response.js';
* TODO: rename the class * TODO: rename the class
*/ */
export class MinimalPlugin extends Plugin { export class MinimalPlugin extends Plugin {
requestSchema = requestSchema; requestSchema = schema.SCMinimalRequest;
responseSchema = responseSchema; responseSchema = schema.SCMinimalResponse;
/** /**
* Calculates the sum of a list of numbers * Calculates the sum of a list of numbers

View File

@@ -19,6 +19,7 @@
* All incoming requests will look like this, this is being checked by the backend. You need to add the @validatable tag * All incoming requests will look like this, this is being checked by the backend. You need to add the @validatable tag
* like shown below for the plugin to work. The request can have any layout you like. * like shown below for the plugin to work. The request can have any layout you like.
* TODO: remove body of the interface and replace with your own layout * TODO: remove body of the interface and replace with your own layout
* @validatable
*/ */
export interface SCMinimalRequest { export interface SCMinimalRequest {
/** /**

View File

@@ -19,6 +19,7 @@
* All your responses to the backend are required to look like this. You need to add the @validatable tag like shown * All your responses to the backend are required to look like this. You need to add the @validatable tag like shown
* below for the plugin to work. The response can have any layout you like. * below for the plugin to work. The response can have any layout you like.
* TODO: remove body of the interface and replace with your own layout * TODO: remove body of the interface and replace with your own layout
* @validatable
*/ */
export interface SCMinimalResponse { export interface SCMinimalResponse {
/** /**
@@ -27,4 +28,4 @@ export interface SCMinimalResponse {
sum: number; sum: number;
} }
export {default as responseSchema} from 'schema:#SCMinimalResponse'; export {default as requestSchema} from 'schema:#SCMinimalResponse';

View File

@@ -1,5 +0,0 @@
declare module 'schema:*' {
import {JSONSchema7} from 'json-schema';
const schema: JSONSchema7;
export default schema;
}

View File

@@ -1,5 +1,5 @@
import {defineConfig} from 'tsup'; import {defineConfig} from 'tsup';
import {esbuildJsonSchemaPlugin} from '@openstapps/json-schema-generator'; import {jsonSchemaPlugin} from '@openstapps/json-schema-generator';
export default defineConfig({ export default defineConfig({
entry: ['src/app.ts'], entry: ['src/app.ts'],
@@ -7,6 +7,6 @@ export default defineConfig({
clean: true, clean: true,
format: 'esm', format: 'esm',
outDir: 'lib', outDir: 'lib',
noExternal: [/schema:.*/], noExternal: [/.*:schema#.*/],
esbuildPlugins: [esbuildJsonSchemaPlugin], plugins: [jsonSchemaPlugin('schema.json')],
}); });

View File

@@ -1,61 +0,0 @@
import academicEventMapping from 'elasticsearch:./things/academic-event.js#SCAcademicEvent';
import articleMapping from 'elasticsearch:./things/article.js#SCArticle';
import assessmentMapping from 'elasticsearch:./things/assessment.js#SCAssessment';
import bookMapping from 'elasticsearch:./things/book.js#SCBook';
import buildingMapping from 'elasticsearch:./things/building.js#SCBuilding';
import catalogMapping from 'elasticsearch:./things/catalog.js#SCCatalog';
import certificationMapping from 'elasticsearch:./things/certification.js#SCCertification';
import contactPointMapping from 'elasticsearch:./things/contact-point.js#SCContactPoint';
import courseOfStudyMapping from 'elasticsearch:./things/course-of-study.js#SCCourseOfStudy';
import dateSeriesMapping from 'elasticsearch:./things/date-series.js#SCDateSeries';
import dishMapping from 'elasticsearch:./things/dish.js#SCDish';
import floorMapping from 'elasticsearch:./things/floor.js#SCFloor';
import idCardMapping from 'elasticsearch:./things/id-card.js#SCIdCard';
import jopPostingMapping from 'elasticsearch:./things/job-posting.js#SCJobPosting';
import messageMapping from 'elasticsearch:./things/message.js#SCMessage';
import organizationMapping from 'elasticsearch:./things/organization.js#SCOrganization';
import periodicalMapping from 'elasticsearch:./things/periodical.js#SCPeriodical';
import personMapping from 'elasticsearch:./things/person.js#SCPerson';
import pointOfInterestMapping from 'elasticsearch:./things/point-of-interest.js#SCPointOfInterest';
import publicationEventMapping from 'elasticsearch:./things/publication-event.js#SCPublicationEvent';
import roomMapping from 'elasticsearch:./things/room.js#SCRoom';
import semesterMapping from 'elasticsearch:./things/semester.js#SCSemester';
import sportCourseMapping from 'elasticsearch:./things/sport-course.js#SCSportCourse';
import studyModuleMapping from 'elasticsearch:./things/study-module.js#SCStudyModule';
import ticketMapping from 'elasticsearch:./things/ticket.js#SCTicket';
import todoMapping from 'elasticsearch:./things/todo.js#SCToDo';
import tourMapping from 'elasticsearch:./things/tour.js#SCTour';
import videoMapping from 'elasticsearch:./things/video.js#SCVideo';
import {SCIndexableThings} from './meta.js';
export type IndexableThingTypes = SCIndexableThings['type'];
export const elasticsearchMappings: Record<IndexableThingTypes, unknown> = {
'academic event': academicEventMapping,
'article': articleMapping,
'assessment': assessmentMapping,
'book': bookMapping,
'building': buildingMapping,
'catalog': catalogMapping,
'certification': certificationMapping,
'contact point': contactPointMapping,
'course of study': courseOfStudyMapping,
'date series': dateSeriesMapping,
'dish': dishMapping,
'floor': floorMapping,
'id card': idCardMapping,
'job posting': jopPostingMapping,
'message': messageMapping,
'organization': organizationMapping,
'periodical': periodicalMapping,
'person': personMapping,
'point of interest': pointOfInterestMapping,
'publication event': publicationEventMapping,
'room': roomMapping,
'semester': semesterMapping,
'sport course': sportCourseMapping,
'study module': studyModuleMapping,
'ticket': ticketMapping,
'todo': todoMapping,
'tour': tourMapping,
'video': videoMapping,
};

View File

@@ -1,10 +0,0 @@
declare module 'schema:*' {
import {JSONSchema7} from 'json-schema';
const schema: JSONSchema7;
export default schema;
}
declare module 'elasticsearch:*' {
const indexRequest: unknown;
export default indexRequest;
}

View File

@@ -27,7 +27,6 @@ import {SCSearchRequest, SCSearchResponse, SCSearchRoute} from './routes/search.
import {SCMultiSearchRequest, SCMultiSearchResponse, SCMultiSearchRoute} from './routes/search-multi.js'; import {SCMultiSearchRequest, SCMultiSearchResponse, SCMultiSearchRoute} from './routes/search-multi.js';
import {SCThingUpdateRequest, SCThingUpdateResponse, SCThingUpdateRoute} from './routes/thing-update.js'; import {SCThingUpdateRequest, SCThingUpdateResponse, SCThingUpdateRoute} from './routes/thing-update.js';
import {SCRatingRequest, SCRatingResponse, SCRatingRoute} from './routes/rating.js'; import {SCRatingRequest, SCRatingResponse, SCRatingRoute} from './routes/rating.js';
import {JSONSchema7} from 'json-schema';
/** /**
* Possible Verbs for HTTP requests * Possible Verbs for HTTP requests
@@ -51,7 +50,7 @@ export interface SCRoute {
/** /**
* A map of names of possible errors that can be returned by the route with their appropriate status codes * A map of names of possible errors that can be returned by the route with their appropriate status codes
*/ */
errors: ReadonlyArray<SCErrorResponseConstructor>; errorNames: SCErrorResponseConstructor[];
/** /**
* HTTP verb to use to request the route * HTTP verb to use to request the route
@@ -66,12 +65,12 @@ export interface SCRoute {
/** /**
* Name of the type of the request body * Name of the type of the request body
*/ */
requestBodySchema: JSONSchema7; requestBodyName: string;
/** /**
* Name of the type of the response body * Name of the type of the response body
*/ */
responseBodySchema: JSONSchema7; responseBodyName: string;
/** /**
* Status code for success * Status code for success
@@ -91,7 +90,7 @@ export abstract class SCAbstractRoute implements SCRoute {
/** /**
* @see SCRoute.errorNames * @see SCRoute.errorNames
*/ */
errorSchemas: SCErrorResponseConstructor[] = []; errorNames: SCErrorResponseConstructor[] = [];
/** /**
* @see SCRoute.method * @see SCRoute.method
@@ -104,14 +103,14 @@ export abstract class SCAbstractRoute implements SCRoute {
obligatoryParameters?: Record<string, string>; obligatoryParameters?: Record<string, string>;
/** /**
* @see SCRoute.requestBodySchema * @see SCRoute.requestBodyName
*/ */
abstract requestBodySchema: JSONSchema7; requestBodyName = 'any';
/** /**
* @see SCRoute.responseBodySchema * @see SCRoute.responseBodyName
*/ */
abstract responseBodySchema: JSONSchema7; responseBodyName = 'any';
/** /**
* @see SCRoute.statusCodeSuccess * @see SCRoute.statusCodeSuccess

View File

@@ -28,23 +28,17 @@ import {SCAbstractRoute, SCRouteHttpVerbs} from '../route.js';
* @validatable * @validatable
*/ */
export type SCBulkAddRequest = SCThings; export type SCBulkAddRequest = SCThings;
import {default as bulkAddRequestSchema} from 'schema:#SCBulkAddRequest';
/** /**
* Response to a request to add a thing to a bulk * Response to a request to add a thing to a bulk
* @validatable * @validatable
*/ */
export interface SCBulkAddResponse {} export interface SCBulkAddResponse {}
import {default as bulkAddResponseSchema} from 'schema:#SCBulkAddResponse';
/** /**
* Route for indexing SC things in a bulk * Route for indexing SC things in a bulk
*/ */
export class SCBulkAddRoute extends SCAbstractRoute { export class SCBulkAddRoute extends SCAbstractRoute {
responseBodySchema = bulkAddRequestSchema;
requestBodySchema = bulkAddResponseSchema;
constructor() { constructor() {
super(); super();
this.errorNames = [ this.errorNames = [
@@ -60,6 +54,8 @@ export class SCBulkAddRoute extends SCAbstractRoute {
this.obligatoryParameters = { this.obligatoryParameters = {
UID: 'SCUuid', UID: 'SCUuid',
}; };
this.requestBodyName = 'SCBulkAddRequest';
this.responseBodyName = 'SCBulkAddResponse';
this.statusCodeSuccess = StatusCodes.CREATED; this.statusCodeSuccess = StatusCodes.CREATED;
this.urlPath = '/bulk/:UID'; this.urlPath = '/bulk/:UID';
} }

View File

@@ -22,17 +22,17 @@ import {SCRequestBodyTooLargeErrorResponse} from '../errors/request-body-too-lar
import {SCSyntaxErrorResponse} from '../errors/syntax-error.js'; import {SCSyntaxErrorResponse} from '../errors/syntax-error.js';
import {SCUnsupportedMediaTypeErrorResponse} from '../errors/unsupported-media-type.js'; import {SCUnsupportedMediaTypeErrorResponse} from '../errors/unsupported-media-type.js';
import {SCValidationErrorResponse} from '../errors/validation.js'; import {SCValidationErrorResponse} from '../errors/validation.js';
import {SCAbstractRoute, SCRouteHttpVerbs} from '../route.js'; import {SCRoute, SCRouteHttpVerbs} from '../route.js';
import {default as indexRequestSchema} from 'schema:#SCIndexRequest';
import {default as indexResponseSchema} from 'schema:#SCIndexResponse';
/** /**
* Index request * Index request
* @validatable
*/ */
export interface SCIndexRequest {} export interface SCIndexRequest {}
/** /**
* A response to an index request * A response to an index request
* @validatable
*/ */
export interface SCIndexResponse { export interface SCIndexResponse {
/** /**
@@ -54,21 +54,20 @@ export interface SCIndexResponse {
/** /**
* Route to request meta information about the deployment * Route to request meta information about the deployment
*/ */
export class SCIndexRoute extends SCAbstractRoute { export type SCIndexRoute = typeof indexRoute;
constructor() {
super(); export const indexRoute = Object.freeze({
this.errorNames = [ errors: [
SCInternalServerErrorResponse, SCInternalServerErrorResponse,
SCMethodNotAllowedErrorResponse, SCMethodNotAllowedErrorResponse,
SCRequestBodyTooLargeErrorResponse, SCRequestBodyTooLargeErrorResponse,
SCSyntaxErrorResponse, SCSyntaxErrorResponse,
SCUnsupportedMediaTypeErrorResponse, SCUnsupportedMediaTypeErrorResponse,
SCValidationErrorResponse, SCValidationErrorResponse,
]; ] as const,
this.method = SCRouteHttpVerbs.POST; method: SCRouteHttpVerbs.POST,
this.requestBodyName = 'SCIndexRequest'; requestBodySchema: indexRequestSchema,
this.responseBodyName = 'SCIndexResponse'; responseBodySchema: indexResponseSchema,
this.statusCodeSuccess = StatusCodes.OK; statusCodeSuccess: StatusCodes.OK,
this.urlPath = '/'; urlPath: '/',
} }) satisfies Readonly<SCRoute>;
}

View File

@@ -22,11 +22,12 @@ import {SCPluginAlreadyRegisteredErrorResponse} from '../errors/plugin-already-r
import {SCPluginRegisteringFailedErrorResponse} from '../errors/plugin-registering-failed.js'; import {SCPluginRegisteringFailedErrorResponse} from '../errors/plugin-registering-failed.js';
import {SCRequestBodyTooLargeErrorResponse} from '../errors/request-body-too-large.js'; import {SCRequestBodyTooLargeErrorResponse} from '../errors/request-body-too-large.js';
import {SCSyntaxErrorResponse} from '../errors/syntax-error.js'; import {SCSyntaxErrorResponse} from '../errors/syntax-error.js';
import {SCAbstractRoute, SCRouteHttpVerbs} from '../route.js'; import {SCRoute, SCRouteHttpVerbs} from '../route.js';
import {default as pluginRegisterRequestSchema} from 'schema:#SCPluginRegisterRequest';
import {default as pluginRegisterResponseSchema} from 'schema:#SCPluginRegisterResponse';
/** /**
* Plugin register request * Plugin register request
* @validatable
*/ */
export type SCPluginRegisterRequest = SCPluginAdd | SCPluginRemove; export type SCPluginRegisterRequest = SCPluginAdd | SCPluginRemove;
@@ -92,7 +93,6 @@ export interface SCPluginMetaData {
/** /**
* Plugin register response * Plugin register response
* @validatable
*/ */
export interface SCPluginRegisterResponse { export interface SCPluginRegisterResponse {
/** /**
@@ -104,10 +104,10 @@ export interface SCPluginRegisterResponse {
/** /**
* Route to register plugins * Route to register plugins
*/ */
export class SCPluginRegisterRoute extends SCAbstractRoute { export type SCPluginRegisterRoute = typeof pluginRegisterRoute;
constructor() {
super(); export const pluginRegisterRoute = Object.freeze({
this.errorNames = [ errors: [
SCInternalServerErrorResponse, SCInternalServerErrorResponse,
SCMethodNotAllowedErrorResponse, SCMethodNotAllowedErrorResponse,
SCNotFoundErrorResponse, SCNotFoundErrorResponse,
@@ -116,11 +116,10 @@ export class SCPluginRegisterRoute extends SCAbstractRoute {
SCPluginRegisteringFailedErrorResponse, SCPluginRegisteringFailedErrorResponse,
SCRequestBodyTooLargeErrorResponse, SCRequestBodyTooLargeErrorResponse,
SCSyntaxErrorResponse, SCSyntaxErrorResponse,
]; ],
this.method = SCRouteHttpVerbs.POST; method: SCRouteHttpVerbs.POST,
this.requestBodyName = 'SCPluginRegisterRequest'; requestBodySchema: pluginRegisterRequestSchema,
this.responseBodyName = 'SCPluginRegisterResponse'; responseBodySchema: pluginRegisterResponseSchema,
this.statusCodeSuccess = StatusCodes.OK; statusCodeSuccess: StatusCodes.OK,
this.urlPath = '/plugin/register'; urlPath: '/plugin/register',
} }) satisfies Readonly<SCRoute>;
}

View File

@@ -18,17 +18,16 @@ import {SCMethodNotAllowedErrorResponse} from '../errors/method-not-allowed.js';
import {SCRequestBodyTooLargeErrorResponse} from '../errors/request-body-too-large.js'; import {SCRequestBodyTooLargeErrorResponse} from '../errors/request-body-too-large.js';
import {SCSyntaxErrorResponse} from '../errors/syntax-error.js'; import {SCSyntaxErrorResponse} from '../errors/syntax-error.js';
import {SCUnsupportedMediaTypeErrorResponse} from '../errors/unsupported-media-type.js'; import {SCUnsupportedMediaTypeErrorResponse} from '../errors/unsupported-media-type.js';
import {SCRoute, SCRouteHttpVerbs} from '../route.js'; import {SCAbstractRoute, SCRouteHttpVerbs} from '../route.js';
import {SCThing} from '../../things/abstract/thing.js'; import {SCThing} from '../../things/abstract/thing.js';
import {SCUserGroupSetting} from '../../things/setting.js'; import {SCUserGroupSetting} from '../../things/setting.js';
import {SCValidationErrorResponse} from '../errors/validation.js'; import {SCValidationErrorResponse} from '../errors/validation.js';
import {default as ratingRequestSchema} from 'schema:#SCRatingRequest';
import {default as ratingResponseSchema} from 'schema:#SCRatingResponse';
/** /**
* User rating from the app * User rating from the app
* Plugin needs to define its own rating request to hit the target rating system. * Plugin needs to define its own rating request to hit the target rating system.
* That request should extend this one and contain timestamp and other needed data. * That request should extend this one and contain timestamp and other needed data.
* @validatable
*/ */
export interface SCRatingRequest { export interface SCRatingRequest {
/** /**
@@ -49,26 +48,28 @@ export interface SCRatingRequest {
/** /**
* A response to a rating request * A response to a rating request
* @validatable
*/ */
export interface SCRatingResponse {} export interface SCRatingResponse {}
/** /**
* Route for rating submission * Route for rating submission
*/ */
export type SCRatingRoute = typeof ratingRoute; export class SCRatingRoute extends SCAbstractRoute {
constructor() {
export const ratingRoute = Object.freeze({ super();
errors: [ this.errorNames = [
SCInternalServerErrorResponse, SCInternalServerErrorResponse,
SCMethodNotAllowedErrorResponse, SCMethodNotAllowedErrorResponse,
SCRequestBodyTooLargeErrorResponse, SCRequestBodyTooLargeErrorResponse,
SCSyntaxErrorResponse, SCSyntaxErrorResponse,
SCUnsupportedMediaTypeErrorResponse, SCUnsupportedMediaTypeErrorResponse,
SCValidationErrorResponse, SCValidationErrorResponse,
] as const, ];
method: SCRouteHttpVerbs.POST, this.method = SCRouteHttpVerbs.POST;
requestBodySchema: ratingRequestSchema, this.requestBodyName = 'SCRatingRequest';
responseBodySchema: ratingResponseSchema, this.responseBodyName = 'SCRatingResponse';
statusCodeSuccess: StatusCodes.OK, this.statusCodeSuccess = StatusCodes.OK;
urlPath: '/rating', this.urlPath = '/rating';
}) satisfies Readonly<SCRoute>; }
}

View File

@@ -20,11 +20,9 @@ import {SCSyntaxErrorResponse} from '../errors/syntax-error.js';
import {SCTooManyRequestsErrorResponse} from '../errors/too-many-requests.js'; import {SCTooManyRequestsErrorResponse} from '../errors/too-many-requests.js';
import {SCUnsupportedMediaTypeErrorResponse} from '../errors/unsupported-media-type.js'; import {SCUnsupportedMediaTypeErrorResponse} from '../errors/unsupported-media-type.js';
import {SCValidationErrorResponse} from '../errors/validation.js'; import {SCValidationErrorResponse} from '../errors/validation.js';
import {SCRoute, SCRouteHttpVerbs} from '../route.js'; import {SCAbstractRoute, SCRouteHttpVerbs} from '../route.js';
import {SCSearchQuery} from '../search/query.js'; import {SCSearchQuery} from '../search/query.js';
import {SCSearchResult} from '../search/result.js'; import {SCSearchResult} from '../search/result.js';
import {default as multiSearchRequestSchema} from 'schema:#SCMultiSearchRequest';
import {default as multiSearchResponseSchema} from 'schema:#SCMultiSearchResponse';
/** /**
* A multi search request * A multi search request
@@ -32,6 +30,7 @@ import {default as multiSearchResponseSchema} from 'schema:#SCMultiSearchRespons
* This is a map of [[SCSearchRequest]]s indexed by name. * This is a map of [[SCSearchRequest]]s indexed by name.
* *
* **CAUTION: This is limited to an amount of queries. Currently this limit is 5.** * **CAUTION: This is limited to an amount of queries. Currently this limit is 5.**
* @validatable
*/ */
export type SCMultiSearchRequest = Record<string, SCSearchQuery>; export type SCMultiSearchRequest = Record<string, SCSearchQuery>;
@@ -39,16 +38,17 @@ export type SCMultiSearchRequest = Record<string, SCSearchQuery>;
* A multi search response * A multi search response
* *
* This is a map of [[SCSearchResponse]]s indexed by name * This is a map of [[SCSearchResponse]]s indexed by name
* @validatable
*/ */
export type SCMultiSearchResponse = Record<string, SCSearchResult>; export type SCMultiSearchResponse = Record<string, SCSearchResult>;
/** /**
* Route for submission of multiple search requests at once * Route for submission of multiple search requests at once
*/ */
export type SCMultiSearchRoute = typeof multiSearchRoute; export class SCMultiSearchRoute extends SCAbstractRoute {
constructor() {
export const multiSearchRoute = Object.freeze({ super();
errors: [ this.errorNames = [
SCInternalServerErrorResponse, SCInternalServerErrorResponse,
SCMethodNotAllowedErrorResponse, SCMethodNotAllowedErrorResponse,
SCRequestBodyTooLargeErrorResponse, SCRequestBodyTooLargeErrorResponse,
@@ -56,10 +56,11 @@ export const multiSearchRoute = Object.freeze({
SCTooManyRequestsErrorResponse, SCTooManyRequestsErrorResponse,
SCUnsupportedMediaTypeErrorResponse, SCUnsupportedMediaTypeErrorResponse,
SCValidationErrorResponse, SCValidationErrorResponse,
] as const, ];
method: SCRouteHttpVerbs.POST, this.method = SCRouteHttpVerbs.POST;
requestBodySchema: multiSearchRequestSchema, this.requestBodyName = 'SCMultiSearchRequest';
responseBodySchema: multiSearchResponseSchema, this.responseBodyName = 'SCMultiSearchResponse';
statusCodeSuccess: StatusCodes.OK, this.statusCodeSuccess = StatusCodes.OK;
urlPath: '/search/multi', this.urlPath = '/search/multi';
}) satisfies Readonly<SCRoute>; }
}

View File

@@ -19,39 +19,40 @@ import {SCRequestBodyTooLargeErrorResponse} from '../errors/request-body-too-lar
import {SCSyntaxErrorResponse} from '../errors/syntax-error.js'; import {SCSyntaxErrorResponse} from '../errors/syntax-error.js';
import {SCUnsupportedMediaTypeErrorResponse} from '../errors/unsupported-media-type.js'; import {SCUnsupportedMediaTypeErrorResponse} from '../errors/unsupported-media-type.js';
import {SCValidationErrorResponse} from '../errors/validation.js'; import {SCValidationErrorResponse} from '../errors/validation.js';
import {SCRoute, SCRouteHttpVerbs} from '../route.js'; import {SCAbstractRoute, SCRouteHttpVerbs} from '../route.js';
import {SCSearchQuery} from '../search/query.js'; import {SCSearchQuery} from '../search/query.js';
import {SCSearchResult} from '../search/result.js'; import {SCSearchResult} from '../search/result.js';
import {default as searchRequestSchema} from 'schema:#SCSearchRequest';
import {default as searchResponseSchema} from 'schema:#SCSearchResponse';
/** /**
* A search request * A search request
* @validatable
*/ */
export type SCSearchRequest = SCSearchQuery; export type SCSearchRequest = SCSearchQuery;
/** /**
* A search response * A search response
* @validatable
*/ */
export type SCSearchResponse = SCSearchResult; export type SCSearchResponse = SCSearchResult;
/** /**
* Route for searching things * Route for searching things
*/ */
export type SCSearchRoute = typeof searchRoute; export class SCSearchRoute extends SCAbstractRoute {
constructor() {
export const searchRoute = Object.freeze({ super();
errors: [ this.errorNames = [
SCInternalServerErrorResponse, SCInternalServerErrorResponse,
SCMethodNotAllowedErrorResponse, SCMethodNotAllowedErrorResponse,
SCRequestBodyTooLargeErrorResponse, SCRequestBodyTooLargeErrorResponse,
SCSyntaxErrorResponse, SCSyntaxErrorResponse,
SCUnsupportedMediaTypeErrorResponse, SCUnsupportedMediaTypeErrorResponse,
SCValidationErrorResponse, SCValidationErrorResponse,
] as const, ];
method: SCRouteHttpVerbs.POST, this.method = SCRouteHttpVerbs.POST;
requestBodySchema: searchRequestSchema, this.requestBodyName = 'SCSearchRequest';
responseBodySchema: searchResponseSchema, this.responseBodyName = 'SCSearchResponse';
statusCodeSuccess: StatusCodes.OK, this.statusCodeSuccess = StatusCodes.OK;
urlPath: '/search', this.urlPath = '/search';
}) satisfies Readonly<SCRoute>; }
}

View File

@@ -21,27 +21,27 @@ import {SCRequestBodyTooLargeErrorResponse} from '../errors/request-body-too-lar
import {SCSyntaxErrorResponse} from '../errors/syntax-error.js'; import {SCSyntaxErrorResponse} from '../errors/syntax-error.js';
import {SCUnsupportedMediaTypeErrorResponse} from '../errors/unsupported-media-type.js'; import {SCUnsupportedMediaTypeErrorResponse} from '../errors/unsupported-media-type.js';
import {SCValidationErrorResponse} from '../errors/validation.js'; import {SCValidationErrorResponse} from '../errors/validation.js';
import {SCRoute, SCRouteHttpVerbs} from '../route.js'; import {SCAbstractRoute, SCRouteHttpVerbs} from '../route.js';
import {default as thingUpdateRequestSchema} from 'schema:#SCThingUpdateRequest';
import {default as thingUpdateResponseSchema} from 'schema:#SCThingUpdateResponse';
/** /**
* Request to update an existing thing * Request to update an existing thing
* @validatable
*/ */
export type SCThingUpdateRequest = SCThings; export type SCThingUpdateRequest = SCThings;
/** /**
* Response for an entity update request * Response for an entity update request
* @validatable
*/ */
export interface SCThingUpdateResponse {} export interface SCThingUpdateResponse {}
/** /**
* Route for updating existing things * Route for updating existing things
*/ */
export type SCThingUpdateRoute = typeof thingUpdateRoute; export class SCThingUpdateRoute extends SCAbstractRoute {
constructor() {
export const thingUpdateRoute = Object.freeze({ super();
errors: [ this.errorNames = [
SCInternalServerErrorResponse, SCInternalServerErrorResponse,
SCMethodNotAllowedErrorResponse, SCMethodNotAllowedErrorResponse,
SCNotFoundErrorResponse, SCNotFoundErrorResponse,
@@ -49,14 +49,15 @@ export const thingUpdateRoute = Object.freeze({
SCSyntaxErrorResponse, SCSyntaxErrorResponse,
SCUnsupportedMediaTypeErrorResponse, SCUnsupportedMediaTypeErrorResponse,
SCValidationErrorResponse, SCValidationErrorResponse,
] as const, ];
method: SCRouteHttpVerbs.PUT, this.method = SCRouteHttpVerbs.PUT;
obligatoryParameters: { this.obligatoryParameters = {
TYPE: 'SCThingType', TYPE: 'SCThingType',
UID: 'SCUuid', UID: 'SCUuid',
}, };
requestBodySchema: thingUpdateRequestSchema, this.requestBodyName = 'SCThingUpdateRequest';
responseBodySchema: thingUpdateResponseSchema, this.responseBodyName = 'SCThingUpdateResponse';
statusCodeSuccess: StatusCodes.OK, this.statusCodeSuccess = StatusCodes.OK;
urlPath: '/:TYPE/:UID', this.urlPath = '/:TYPE/:UID';
}) satisfies Readonly<SCRoute>; }
}

View File

@@ -1,67 +0,0 @@
import academicEventSchema from 'schema:./things/academic-event.js#SCAcademicEvent';
import articleSchema from 'schema:./things/article.js#SCArticle';
import assessmentSchema from 'schema:./things/assessment.js#SCAssessment';
import bookSchema from 'schema:./things/book.js#SCBook';
import buildingSchema from 'schema:./things/building.js#SCBuilding';
import catalogSchema from 'schema:./things/catalog.js#SCCatalog';
import certificationSchema from 'schema:./things/certification.js#SCCertification';
import contactPointSchema from 'schema:./things/contact-point.js#SCContactPoint';
import courseOfStudySchema from 'schema:./things/course-of-study.js#SCCourseOfStudy';
import dateSeriesSchema from 'schema:./things/date-series.js#SCDateSeries';
import diffSchema from 'schema:./things/diff.js#SCDiff';
import dishSchema from 'schema:./things/dish.js#SCDish';
import favoriteSchema from 'schema:./things/favorite.js#SCFavorite';
import floorSchema from 'schema:./things/floor.js#SCFloor';
import idCardSchema from 'schema:./things/id-card.js#SCIdCard';
import jopPostingSchema from 'schema:./things/job-posting.js#SCJobPosting';
import messageSchema from 'schema:./things/message.js#SCMessage';
import organizationSchema from 'schema:./things/organization.js#SCOrganization';
import periodicalSchema from 'schema:./things/periodical.js#SCPeriodical';
import personSchema from 'schema:./things/person.js#SCPerson';
import pointOfInterestSchema from 'schema:./things/point-of-interest.js#SCPointOfInterest';
import publicationEventSchema from 'schema:./things/publication-event.js#SCPublicationEvent';
import roomSchema from 'schema:./things/room.js#SCRoom';
import semesterSchema from 'schema:./things/semester.js#SCSemester';
import settingSchema from 'schema:./things/setting.js#SCSetting';
import sportCourseSchema from 'schema:./things/sport-course.js#SCSportCourse';
import studyModuleSchema from 'schema:./things/study-module.js#SCStudyModule';
import ticketSchema from 'schema:./things/ticket.js#SCTicket';
import todoSchema from 'schema:./things/todo.js#SCToDo';
import tourSchema from 'schema:./things/tour.js#SCTour';
import videoSchema from 'schema:./things/video.js#SCVideo';
import {SCThingType} from './things/abstract/thing.js';
import {JSONSchema7} from 'json-schema';
export const thingSchemas: Record<SCThingType, JSONSchema7> = {
'academic event': academicEventSchema,
'article': articleSchema,
'assessment': assessmentSchema,
'book': bookSchema,
'building': buildingSchema,
'catalog': catalogSchema,
'certification': certificationSchema,
'contact point': contactPointSchema,
'course of study': courseOfStudySchema,
'date series': dateSeriesSchema,
'diff': diffSchema,
'dish': dishSchema,
'favorite': favoriteSchema,
'floor': floorSchema,
'id card': idCardSchema,
'job posting': jopPostingSchema,
'message': messageSchema,
'organization': organizationSchema,
'periodical': periodicalSchema,
'person': personSchema,
'point of interest': pointOfInterestSchema,
'publication event': publicationEventSchema,
'room': roomSchema,
'semester': semesterSchema,
'setting': settingSchema,
'sport course': sportCourseSchema,
'study module': studyModuleSchema,
'ticket': ticketSchema,
'todo': todoSchema,
'tour': tourSchema,
'video': videoSchema,
};

View File

@@ -55,6 +55,7 @@ export interface SCAcademicEventWithoutReferences
/** /**
* An academic event * An academic event
* @validatable
* @elasticsearch indexable * @elasticsearch indexable
*/ */
export interface SCAcademicEvent export interface SCAcademicEvent
@@ -73,9 +74,6 @@ export interface SCAcademicEvent
type: SCThingType.AcademicEvent; type: SCThingType.AcademicEvent;
} }
// export {default as academicEventSchema} from 'schema:#SCAcademicEvent';
// export {default as academicEventElasticsearchMapping} from 'elasticsearch:#SCAcademicEvent';
/** /**
* Categories of academic events * Categories of academic events
*/ */

View File

@@ -45,6 +45,7 @@ export interface SCTicketWithoutReferences extends SCThingWithoutReferences {
/** /**
* A ticket * A ticket
* @validatable
* @elasticsearch indexable * @elasticsearch indexable
*/ */
export interface SCTicket extends SCTicketWithoutReferences, SCThingInPlace { export interface SCTicket extends SCTicketWithoutReferences, SCThingInPlace {

View File

@@ -1,19 +1,14 @@
import {defineConfig} from 'tsup'; import {defineConfig} from 'tsup';
import {esbuildJsonSchemaPlugin, jsonSchemaPlugin} from '@openstapps/json-schema-generator'; import {jsonSchemaPlugin} from '@openstapps/json-schema-generator';
import {openapiPlugin} from '@openstapps/openapi-generator'; import {openapiPlugin} from '@openstapps/openapi-generator';
import { import {elasticsearchMappingGenerator} from '@openstapps/es-mapping-generator';
elasticsearchMappingGenerator,
esbuildElasticsearchMappingPlugin,
} from '@openstapps/es-mapping-generator';
export default defineConfig({ export default defineConfig({
entry: ['src/index.ts', 'src/schemas.ts', 'src/elasticsearch.ts'], entry: ['src/index.ts'],
sourcemap: true, sourcemap: true,
clean: true, clean: true,
format: 'esm', format: 'esm',
outDir: 'lib', outDir: 'lib',
esbuildPlugins: [esbuildJsonSchemaPlugin, esbuildElasticsearchMappingPlugin],
noExternal: [/schema:*/, /elasticsearch:*/],
plugins: [ plugins: [
jsonSchemaPlugin('schema.json', elasticsearchMappingGenerator('elasticsearch.json')), jsonSchemaPlugin('schema.json', elasticsearchMappingGenerator('elasticsearch.json')),
openapiPlugin('openapi.json', 'schema.json'), openapiPlugin('openapi.json', 'schema.json'),

View File

@@ -40,7 +40,7 @@ export function transformProject(project: JSONSchema7) {
}; };
} }
export const OPTIONS: GeneratorOptions = { const OPTIONS: GeneratorOptions = {
template: { template: {
name: 'template_{_type}', name: 'template_{_type}',
index_patterns: 'stapps_{_type}*', index_patterns: 'stapps_{_type}*',

View File

@@ -1,48 +1,5 @@
import {OPTIONS, transformProject} from './generator/index.js'; import {transformProject} from './generator/index.js';
import {SchemaConsumer} from '@openstapps/json-schema-generator'; import {SchemaConsumer} from '@openstapps/json-schema-generator';
import {Plugin} from 'esbuild';
// eslint-disable-next-line unicorn/import-style
import {dirname} from 'path';
import {MappingGenerator} from './generator/mapping-generator.js';
export const esbuildElasticsearchMappingPlugin: Plugin = {
name: 'elasticsearch-mappings',
setup(build) {
const fileRegex = /^elasticsearch:/;
const namespace = 'elasticsearch-mappings-ns';
const mappings = new Map<string, string>();
build.onResolve({filter: fileRegex}, async ({path, importer}) => {
const [from, name] = path.replace(fileRegex, '').split('#', 2);
return {
path: `${
from
? await build
.resolve(from, {resolveDir: dirname(importer), kind: 'import-statement'})
.then(it => it.path)
: importer
}#${name}`,
namespace,
};
});
build.onLoad({filter: /.*/, namespace}, async ({path}) => {
if (!mappings.has(path)) {
const result = await build.resolve(`schema:${path}`, {kind: 'import-statement'});
console.log(result);
const context = new MappingGenerator(result.pluginData.schema, OPTIONS);
const name = path.split('#', 2)[1];
mappings.set(path, JSON.stringify(context.buildTemplate(name)));
}
return {
contents: mappings.get(path),
loader: 'json',
};
});
},
};
/** /**
* JSON Schema Generator plugin for Elasticsearch Mappings * JSON Schema Generator plugin for Elasticsearch Mappings

View File

@@ -3,41 +3,35 @@ import {generateFiles, Plugin, PluginContext} from '@openstapps/tsup-plugin';
import {JSONSchema7} from 'json-schema'; import {JSONSchema7} from 'json-schema';
import {Plugin as EsbuildPlugin} from 'esbuild'; import {Plugin as EsbuildPlugin} from 'esbuild';
import {createGenerator} from 'ts-json-schema-generator'; import {createGenerator} from 'ts-json-schema-generator';
import {dirname} from 'path';
export type SchemaConsumer = (this: PluginContext, schema: JSONSchema7) => Record<string, string | Buffer>; export type SchemaConsumer = (this: PluginContext, schema: JSONSchema7) => Record<string, string | Buffer>;
export const esbuildJsonSchemaPlugin: EsbuildPlugin = { export const jsonSchema: EsbuildPlugin = {
name: 'json-schema', name: 'json-schema',
setup(build) { setup(build) {
const fileRegex = /^schema:/; const fileRegex = /^schema:/;
const namespace = 'json-schema-ns'; const namespace = 'json-schema-ns';
const schemas = new Map<string, string>(); const schemas = new Map<string, string>();
build.onResolve({filter: fileRegex}, async ({path, importer}) => { build.onResolve({filter: fileRegex}, ({path, importer}) => {
const [from, name] = path.replace(fileRegex, '').split('#', 2); const [from, name] = path.replace(fileRegex, '').split('#', 1);
const outputName = `${name}.schema.json`;
if (!schemas.has(outputName)) {
const generator = createGenerator({
path: from
? await build
.resolve(from, {resolveDir: dirname(importer), kind: 'import-statement'})
.then(it => it.path)
: importer,
extraTags: ['elasticsearch'],
skipTypeCheck: true,
});
schemas.set(outputName, JSON.stringify(generator.createSchema(name)));
}
return { return {
path: outputName, path: `${from === 'file' ? importer : from}#${name}`,
pluginData: {schema: JSON.parse(schemas.get(outputName)!)},
namespace, namespace,
}; };
}); });
build.onLoad({filter: /.*/, namespace}, ({path}) => { build.onLoad({filter: /.*/, namespace}, ({path}) => {
if (!schemas.has(path)) {
const [sourcePath, schemaName] = path.split('#', 1);
const generator = createGenerator({
path: sourcePath,
extraTags: ['elasticsearch'],
skipTypeCheck: true,
});
schemas.set(path, JSON.stringify(generator.createSchema(schemaName)));
}
return { return {
contents: schemas.get(path), contents: schemas.get(path),
loader: 'json', loader: 'json',