adorn-api 1.0.22 → 1.0.24
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/.eslintignore +3 -0
- package/.eslintrc.cjs +30 -0
- package/README.md +375 -531
- package/dist/core/express-adapter.d.ts +27 -0
- package/dist/core/express-adapter.d.ts.map +1 -0
- package/dist/core/express-adapter.js +146 -0
- package/dist/core/express-adapter.js.map +1 -0
- package/dist/core/http-error.d.ts +7 -0
- package/dist/core/http-error.d.ts.map +1 -0
- package/dist/core/http-error.js +14 -0
- package/dist/core/http-error.js.map +1 -0
- package/dist/decorators/controller.decorator.d.ts +2 -0
- package/dist/decorators/controller.decorator.d.ts.map +1 -0
- package/dist/decorators/controller.decorator.js +26 -0
- package/dist/decorators/controller.decorator.js.map +1 -0
- package/dist/decorators/create.decorator.d.ts +8 -0
- package/dist/decorators/create.decorator.d.ts.map +1 -0
- package/dist/decorators/create.decorator.js +67 -0
- package/dist/decorators/create.decorator.js.map +1 -0
- package/dist/decorators/http-method.decorator.d.ts +16 -0
- package/dist/decorators/http-method.decorator.d.ts.map +1 -0
- package/dist/decorators/http-method.decorator.js +117 -0
- package/dist/decorators/http-method.decorator.js.map +1 -0
- package/dist/decorators/http-params.d.ts +17 -0
- package/dist/decorators/http-params.d.ts.map +1 -0
- package/dist/decorators/http-params.js +26 -0
- package/dist/decorators/http-params.js.map +1 -0
- package/dist/decorators/index.d.ts +10 -5
- package/dist/decorators/index.d.ts.map +1 -1
- package/dist/decorators/index.js +14 -0
- package/dist/decorators/index.js.map +1 -0
- package/dist/decorators/list.decorator.d.ts +18 -0
- package/dist/decorators/list.decorator.d.ts.map +1 -0
- package/dist/decorators/list.decorator.js +99 -0
- package/dist/decorators/list.decorator.js.map +1 -0
- package/dist/decorators/middleware.decorator.d.ts +4 -0
- package/dist/decorators/middleware.decorator.d.ts.map +1 -0
- package/dist/decorators/middleware.decorator.js +34 -0
- package/dist/decorators/middleware.decorator.js.map +1 -0
- package/dist/decorators/response.decorator.d.ts +8 -0
- package/dist/decorators/response.decorator.d.ts.map +1 -0
- package/dist/decorators/response.decorator.js +44 -0
- package/dist/decorators/response.decorator.js.map +1 -0
- package/dist/decorators/route-options.d.ts +14 -0
- package/dist/decorators/route-options.d.ts.map +1 -0
- package/dist/decorators/route-options.js +22 -0
- package/dist/decorators/route-options.js.map +1 -0
- package/dist/decorators/schema.decorator.d.ts +82 -0
- package/dist/decorators/schema.decorator.d.ts.map +1 -0
- package/dist/decorators/schema.decorator.js +123 -0
- package/dist/decorators/schema.decorator.js.map +1 -0
- package/dist/decorators/update.decorator.d.ts +8 -0
- package/dist/decorators/update.decorator.d.ts.map +1 -0
- package/dist/decorators/update.decorator.js +63 -0
- package/dist/decorators/update.decorator.js.map +1 -0
- package/dist/index.d.ts +11 -13
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +18 -637
- package/dist/index.js.map +1 -1
- package/dist/metadata/metadata-storage.d.ts +38 -0
- package/dist/metadata/metadata-storage.d.ts.map +1 -0
- package/dist/metadata/metadata-storage.js +102 -0
- package/dist/metadata/metadata-storage.js.map +1 -0
- package/dist/metal-orm-integration/dto-helper.d.ts +5 -0
- package/dist/metal-orm-integration/dto-helper.d.ts.map +1 -0
- package/dist/metal-orm-integration/dto-helper.js +48 -0
- package/dist/metal-orm-integration/dto-helper.js.map +1 -0
- package/dist/metal-orm-integration/dto-response.decorator.d.ts +4 -0
- package/dist/metal-orm-integration/dto-response.decorator.d.ts.map +1 -0
- package/dist/metal-orm-integration/dto-response.decorator.js +69 -0
- package/dist/metal-orm-integration/dto-response.decorator.js.map +1 -0
- package/dist/metal-orm-integration/entity-schema-builder.d.ts +20 -0
- package/dist/metal-orm-integration/entity-schema-builder.d.ts.map +1 -0
- package/dist/metal-orm-integration/entity-schema-builder.js +356 -0
- package/dist/metal-orm-integration/entity-schema-builder.js.map +1 -0
- package/dist/metal-orm-integration/index.d.ts +5 -0
- package/dist/metal-orm-integration/index.d.ts.map +1 -0
- package/dist/metal-orm-integration/index.js +5 -0
- package/dist/metal-orm-integration/index.js.map +1 -0
- package/dist/metal-orm-integration/schema-modifier.d.ts +11 -0
- package/dist/metal-orm-integration/schema-modifier.d.ts.map +1 -0
- package/dist/metal-orm-integration/schema-modifier.js +62 -0
- package/dist/metal-orm-integration/schema-modifier.js.map +1 -0
- package/dist/openapi/index.d.ts +4 -0
- package/dist/openapi/index.d.ts.map +1 -0
- package/dist/openapi/index.js +4 -0
- package/dist/openapi/index.js.map +1 -0
- package/dist/openapi/openapi-generator.d.ts +22 -0
- package/dist/openapi/openapi-generator.d.ts.map +1 -0
- package/dist/openapi/openapi-generator.js +428 -0
- package/dist/openapi/openapi-generator.js.map +1 -0
- package/dist/openapi/swagger-ui.d.ts +11 -0
- package/dist/openapi/swagger-ui.d.ts.map +1 -0
- package/dist/openapi/swagger-ui.js +20 -0
- package/dist/openapi/swagger-ui.js.map +1 -0
- package/dist/openapi/zod-to-openapi.d.ts +4 -0
- package/dist/openapi/zod-to-openapi.d.ts.map +1 -0
- package/dist/openapi/zod-to-openapi.js +184 -0
- package/dist/openapi/zod-to-openapi.js.map +1 -0
- package/dist/types/common.d.ts +4 -0
- package/dist/types/common.d.ts.map +1 -0
- package/dist/types/common.js +2 -0
- package/dist/types/common.js.map +1 -0
- package/dist/types/controller.d.ts +14 -0
- package/dist/types/controller.d.ts.map +1 -0
- package/dist/types/controller.js +2 -0
- package/dist/types/controller.js.map +1 -0
- package/dist/types/metadata.d.ts +48 -0
- package/dist/types/metadata.d.ts.map +1 -0
- package/dist/types/metadata.js +2 -0
- package/dist/types/metadata.js.map +1 -0
- package/dist/types/openapi.d.ts +30 -0
- package/dist/types/openapi.d.ts.map +1 -0
- package/dist/types/openapi.js +2 -0
- package/dist/types/openapi.js.map +1 -0
- package/dist/validation/zod-adapter.d.ts +15 -0
- package/dist/validation/zod-adapter.d.ts.map +1 -0
- package/dist/validation/zod-adapter.js +61 -0
- package/dist/validation/zod-adapter.js.map +1 -0
- package/examples/basic/app.ts +15 -0
- package/examples/basic/index.ts +6 -0
- package/examples/basic/user.controller.ts +35 -0
- package/examples/basic/user.dtos.ts +23 -0
- package/examples/metal-orm-sqlite/app.ts +18 -0
- package/examples/metal-orm-sqlite/db.ts +90 -0
- package/examples/metal-orm-sqlite/index.ts +6 -0
- package/examples/metal-orm-sqlite/post.controller.ts +209 -0
- package/examples/metal-orm-sqlite/post.dtos.ts +78 -0
- package/examples/metal-orm-sqlite/post.entity.ts +24 -0
- package/examples/metal-orm-sqlite/user.controller.helpers.ts +305 -0
- package/examples/metal-orm-sqlite/user.controller.ts +231 -0
- package/examples/metal-orm-sqlite/user.dtos.ts +88 -0
- package/examples/metal-orm-sqlite/user.entity.ts +21 -0
- package/examples/metal-orm-sqlite-music/album.controller.ts +278 -0
- package/examples/metal-orm-sqlite-music/album.dtos.ts +85 -0
- package/examples/metal-orm-sqlite-music/album.entity.ts +28 -0
- package/examples/metal-orm-sqlite-music/app.ts +19 -0
- package/examples/metal-orm-sqlite-music/artist.controller.ts +272 -0
- package/examples/metal-orm-sqlite-music/artist.dtos.ts +68 -0
- package/examples/metal-orm-sqlite-music/artist.entity.ts +27 -0
- package/examples/metal-orm-sqlite-music/db.ts +148 -0
- package/examples/metal-orm-sqlite-music/index.ts +6 -0
- package/examples/metal-orm-sqlite-music/track.controller.ts +221 -0
- package/examples/metal-orm-sqlite-music/track.dtos.ts +82 -0
- package/examples/metal-orm-sqlite-music/track.entity.ts +27 -0
- package/examples/openapi/health.controller.ts +11 -0
- package/examples/openapi/health.dto.ts +7 -0
- package/examples/openapi/index.ts +12 -0
- package/examples/restful/app.ts +15 -0
- package/examples/restful/index.ts +9 -0
- package/examples/restful/task.controller.ts +118 -0
- package/examples/restful/task.dtos.ts +66 -0
- package/examples/restful/task.store.ts +95 -0
- package/examples/tsconfig.json +8 -0
- package/examples/utils/start-server.ts +56 -0
- package/package.json +33 -97
- package/scripts/run-example.js +29 -0
- package/src/adapter/express.ts +589 -0
- package/src/adapter/metal-orm/convention-overrides.ts +115 -0
- package/src/adapter/metal-orm/crud-dtos.ts +141 -0
- package/src/adapter/metal-orm/dto.ts +20 -0
- package/src/adapter/metal-orm/error-dtos.ts +51 -0
- package/src/adapter/metal-orm/field-builder.ts +185 -0
- package/src/adapter/metal-orm/filters.ts +52 -0
- package/src/adapter/metal-orm/index.ts +66 -0
- package/src/adapter/metal-orm/paged-dtos.ts +94 -0
- package/src/adapter/metal-orm/pagination.ts +28 -0
- package/src/adapter/metal-orm/types.ts +250 -0
- package/src/adapter/metal-orm/utils.ts +36 -0
- package/src/adapter/metal-orm.test.ts +439 -0
- package/src/core/__tests__/coerce.test.ts +39 -0
- package/src/core/__tests__/dto-compose.test.ts +68 -0
- package/src/core/__tests__/schema-builder.test.ts +82 -0
- package/src/core/coerce.ts +190 -0
- package/src/core/decorators.ts +645 -0
- package/src/core/errors.ts +55 -0
- package/src/core/metadata.ts +110 -0
- package/src/core/openapi.ts +282 -0
- package/src/core/schema-builder.ts +287 -0
- package/src/core/schema.ts +400 -0
- package/src/core/types.ts +14 -0
- package/src/e2e/http-error.e2e.test.ts +52 -0
- package/src/e2e/sqlite-metal-orm.e2e.test.ts +174 -0
- package/src/e2e/sqlite.e2e.test.ts +126 -0
- package/src/index.ts +8 -0
- package/tsconfig.eslint.json +7 -0
- package/tsconfig.json +18 -0
- package/vitest.config.ts +8 -0
- package/dist/adapter/express/auth.d.ts +0 -13
- package/dist/adapter/express/auth.d.ts.map +0 -1
- package/dist/adapter/express/bootstrap.d.ts +0 -40
- package/dist/adapter/express/bootstrap.d.ts.map +0 -1
- package/dist/adapter/express/coercion.d.ts +0 -102
- package/dist/adapter/express/coercion.d.ts.map +0 -1
- package/dist/adapter/express/index.d.ts +0 -6
- package/dist/adapter/express/index.d.ts.map +0 -1
- package/dist/adapter/express/merge.d.ts +0 -45
- package/dist/adapter/express/merge.d.ts.map +0 -1
- package/dist/adapter/express/openapi.d.ts +0 -66
- package/dist/adapter/express/openapi.d.ts.map +0 -1
- package/dist/adapter/express/router.d.ts +0 -10
- package/dist/adapter/express/router.d.ts.map +0 -1
- package/dist/adapter/express/swagger.d.ts +0 -18
- package/dist/adapter/express/swagger.d.ts.map +0 -1
- package/dist/adapter/express/types.d.ts +0 -110
- package/dist/adapter/express/types.d.ts.map +0 -1
- package/dist/adapter/express/validation.d.ts +0 -27
- package/dist/adapter/express/validation.d.ts.map +0 -1
- package/dist/cli/progress.d.ts +0 -122
- package/dist/cli/progress.d.ts.map +0 -1
- package/dist/cli.cjs +0 -4390
- package/dist/cli.cjs.map +0 -1
- package/dist/cli.d.ts +0 -3
- package/dist/cli.d.ts.map +0 -1
- package/dist/cli.js +0 -4371
- package/dist/cli.js.map +0 -1
- package/dist/compiler/analyze/index.d.ts +0 -5
- package/dist/compiler/analyze/index.d.ts.map +0 -1
- package/dist/compiler/analyze/scanControllers.d.ts +0 -88
- package/dist/compiler/analyze/scanControllers.d.ts.map +0 -1
- package/dist/compiler/cache/isStale.d.ts +0 -46
- package/dist/compiler/cache/isStale.d.ts.map +0 -1
- package/dist/compiler/cache/loadArtifacts.d.ts +0 -149
- package/dist/compiler/cache/loadArtifacts.d.ts.map +0 -1
- package/dist/compiler/cache/schema.d.ts +0 -32
- package/dist/compiler/cache/schema.d.ts.map +0 -1
- package/dist/compiler/cache/writeCache.d.ts +0 -14
- package/dist/compiler/cache/writeCache.d.ts.map +0 -1
- package/dist/compiler/gems.d.ts +0 -75
- package/dist/compiler/gems.d.ts.map +0 -1
- package/dist/compiler/generator/index.d.ts +0 -7
- package/dist/compiler/generator/index.d.ts.map +0 -1
- package/dist/compiler/generator/manifest.d.ts +0 -23
- package/dist/compiler/generator/manifest.d.ts.map +0 -1
- package/dist/compiler/generator/openapi.d.ts +0 -118
- package/dist/compiler/generator/openapi.d.ts.map +0 -1
- package/dist/compiler/graph/builder.d.ts +0 -24
- package/dist/compiler/graph/builder.d.ts.map +0 -1
- package/dist/compiler/graph/index.d.ts +0 -7
- package/dist/compiler/graph/index.d.ts.map +0 -1
- package/dist/compiler/graph/schemaGraph.d.ts +0 -67
- package/dist/compiler/graph/schemaGraph.d.ts.map +0 -1
- package/dist/compiler/graph/types.d.ts +0 -203
- package/dist/compiler/graph/types.d.ts.map +0 -1
- package/dist/compiler/index.d.ts +0 -12
- package/dist/compiler/index.d.ts.map +0 -1
- package/dist/compiler/ir/index.d.ts +0 -7
- package/dist/compiler/ir/index.d.ts.map +0 -1
- package/dist/compiler/ir/pipeline.d.ts +0 -82
- package/dist/compiler/ir/pipeline.d.ts.map +0 -1
- package/dist/compiler/ir/stages.d.ts +0 -40
- package/dist/compiler/ir/stages.d.ts.map +0 -1
- package/dist/compiler/ir/visitor.d.ts +0 -98
- package/dist/compiler/ir/visitor.d.ts.map +0 -1
- package/dist/compiler/manifest/emit.d.ts +0 -21
- package/dist/compiler/manifest/emit.d.ts.map +0 -1
- package/dist/compiler/manifest/format.d.ts +0 -119
- package/dist/compiler/manifest/format.d.ts.map +0 -1
- package/dist/compiler/manifest/index.d.ts +0 -6
- package/dist/compiler/manifest/index.d.ts.map +0 -1
- package/dist/compiler/runner/createProgram.d.ts +0 -24
- package/dist/compiler/runner/createProgram.d.ts.map +0 -1
- package/dist/compiler/runner/index.d.ts +0 -5
- package/dist/compiler/runner/index.d.ts.map +0 -1
- package/dist/compiler/schema/extractAnnotations.d.ts +0 -57
- package/dist/compiler/schema/extractAnnotations.d.ts.map +0 -1
- package/dist/compiler/schema/index.d.ts +0 -8
- package/dist/compiler/schema/index.d.ts.map +0 -1
- package/dist/compiler/schema/intersectionHandler.d.ts +0 -44
- package/dist/compiler/schema/intersectionHandler.d.ts.map +0 -1
- package/dist/compiler/schema/objectHandler.d.ts +0 -146
- package/dist/compiler/schema/objectHandler.d.ts.map +0 -1
- package/dist/compiler/schema/openapi.d.ts +0 -71
- package/dist/compiler/schema/openapi.d.ts.map +0 -1
- package/dist/compiler/schema/parameters.d.ts +0 -90
- package/dist/compiler/schema/parameters.d.ts.map +0 -1
- package/dist/compiler/schema/partitioner.d.ts +0 -85
- package/dist/compiler/schema/partitioner.d.ts.map +0 -1
- package/dist/compiler/schema/primitives.d.ts +0 -68
- package/dist/compiler/schema/primitives.d.ts.map +0 -1
- package/dist/compiler/schema/queryBuilderAnalyzer.d.ts +0 -76
- package/dist/compiler/schema/queryBuilderAnalyzer.d.ts.map +0 -1
- package/dist/compiler/schema/queryBuilderSchemaBuilder.d.ts +0 -13
- package/dist/compiler/schema/queryBuilderSchemaBuilder.d.ts.map +0 -1
- package/dist/compiler/schema/splitOpenapi.d.ts +0 -46
- package/dist/compiler/schema/splitOpenapi.d.ts.map +0 -1
- package/dist/compiler/schema/typeToJsonSchema.d.ts +0 -26
- package/dist/compiler/schema/typeToJsonSchema.d.ts.map +0 -1
- package/dist/compiler/schema/types.d.ts +0 -70
- package/dist/compiler/schema/types.d.ts.map +0 -1
- package/dist/compiler/schema/unionHandler.d.ts +0 -70
- package/dist/compiler/schema/unionHandler.d.ts.map +0 -1
- package/dist/compiler/transform/dedup.d.ts +0 -35
- package/dist/compiler/transform/dedup.d.ts.map +0 -1
- package/dist/compiler/transform/flatten.d.ts +0 -50
- package/dist/compiler/transform/flatten.d.ts.map +0 -1
- package/dist/compiler/transform/index.d.ts +0 -7
- package/dist/compiler/transform/index.d.ts.map +0 -1
- package/dist/compiler/transform/inline.d.ts +0 -46
- package/dist/compiler/transform/inline.d.ts.map +0 -1
- package/dist/compiler/validation/emitPrecompiledValidators.d.ts +0 -62
- package/dist/compiler/validation/emitPrecompiledValidators.d.ts.map +0 -1
- package/dist/compiler/validation/index.d.ts +0 -5
- package/dist/compiler/validation/index.d.ts.map +0 -1
- package/dist/decorators/Auth.d.ts +0 -22
- package/dist/decorators/Auth.d.ts.map +0 -1
- package/dist/decorators/Controller.d.ts +0 -17
- package/dist/decorators/Controller.d.ts.map +0 -1
- package/dist/decorators/Public.d.ts +0 -15
- package/dist/decorators/Public.d.ts.map +0 -1
- package/dist/decorators/Use.d.ts +0 -23
- package/dist/decorators/Use.d.ts.map +0 -1
- package/dist/decorators/methods.d.ts +0 -26
- package/dist/decorators/methods.d.ts.map +0 -1
- package/dist/express.cjs +0 -1186
- package/dist/express.cjs.map +0 -1
- package/dist/express.d.ts +0 -8
- package/dist/express.d.ts.map +0 -1
- package/dist/express.js +0 -1150
- package/dist/express.js.map +0 -1
- package/dist/http.d.ts +0 -33
- package/dist/http.d.ts.map +0 -1
- package/dist/index.cjs +0 -724
- package/dist/index.cjs.map +0 -1
- package/dist/metal/applyListQuery.d.ts +0 -100
- package/dist/metal/applyListQuery.d.ts.map +0 -1
- package/dist/metal/index.cjs +0 -278
- package/dist/metal/index.cjs.map +0 -1
- package/dist/metal/index.d.ts +0 -15
- package/dist/metal/index.d.ts.map +0 -1
- package/dist/metal/index.js +0 -243
- package/dist/metal/index.js.map +0 -1
- package/dist/metal/listQuery.d.ts +0 -26
- package/dist/metal/listQuery.d.ts.map +0 -1
- package/dist/metal/queryOptions.d.ts +0 -16
- package/dist/metal/queryOptions.d.ts.map +0 -1
- package/dist/metal/readMetalBag.d.ts +0 -69
- package/dist/metal/readMetalBag.d.ts.map +0 -1
- package/dist/metal/registerMetalEntities.d.ts +0 -26
- package/dist/metal/registerMetalEntities.d.ts.map +0 -1
- package/dist/metal/schemaFromEntity.d.ts +0 -41
- package/dist/metal/schemaFromEntity.d.ts.map +0 -1
- package/dist/metal/searchWhere.d.ts +0 -97
- package/dist/metal/searchWhere.d.ts.map +0 -1
- package/dist/metal/symbolMetadata.d.ts +0 -8
- package/dist/metal/symbolMetadata.d.ts.map +0 -1
- package/dist/runtime/auth/runtime.d.ts +0 -183
- package/dist/runtime/auth/runtime.d.ts.map +0 -1
- package/dist/runtime/metadata/bucket.d.ts +0 -2
- package/dist/runtime/metadata/bucket.d.ts.map +0 -1
- package/dist/runtime/metadata/key.d.ts +0 -2
- package/dist/runtime/metadata/key.d.ts.map +0 -1
- package/dist/runtime/metadata/read.d.ts +0 -2
- package/dist/runtime/metadata/read.d.ts.map +0 -1
- package/dist/runtime/metadata/types.d.ts +0 -95
- package/dist/runtime/metadata/types.d.ts.map +0 -1
- package/dist/runtime/polyfill.d.ts +0 -2
- package/dist/runtime/polyfill.d.ts.map +0 -1
- package/dist/runtime/upload.d.ts +0 -44
- package/dist/runtime/upload.d.ts.map +0 -1
- package/dist/runtime/validation/ajv.d.ts +0 -120
- package/dist/runtime/validation/ajv.d.ts.map +0 -1
- package/dist/runtime/validation/index.d.ts +0 -11
- package/dist/runtime/validation/index.d.ts.map +0 -1
- package/dist/schema/decorators.d.ts +0 -37
- package/dist/schema/decorators.d.ts.map +0 -1
- package/dist/schema/index.cjs +0 -214
- package/dist/schema/index.cjs.map +0 -1
- package/dist/schema/index.d.ts +0 -2
- package/dist/schema/index.d.ts.map +0 -1
- package/dist/schema/index.js +0 -163
- package/dist/schema/index.js.map +0 -1
- package/dist/scripts/adorn-example.cjs +0 -404
- package/dist/scripts/adorn-example.cjs.map +0 -1
- package/dist/utils/operationId.d.ts +0 -2
- package/dist/utils/operationId.d.ts.map +0 -1
- package/dist/utils/path.d.ts +0 -2
- package/dist/utils/path.d.ts.map +0 -1
- package/dist/utils/port.d.ts +0 -9
- package/dist/utils/port.d.ts.map +0 -1
|
@@ -0,0 +1,209 @@
|
|
|
1
|
+
import {
|
|
2
|
+
Body,
|
|
3
|
+
Controller,
|
|
4
|
+
Delete,
|
|
5
|
+
Get,
|
|
6
|
+
HttpError,
|
|
7
|
+
Params,
|
|
8
|
+
Patch,
|
|
9
|
+
Post,
|
|
10
|
+
Put,
|
|
11
|
+
Query,
|
|
12
|
+
Returns,
|
|
13
|
+
parsePagination,
|
|
14
|
+
type RequestContext
|
|
15
|
+
} from "../../src";
|
|
16
|
+
import { applyFilter, toPagedResponse } from "metal-orm";
|
|
17
|
+
import type { SimpleWhereInput } from "metal-orm";
|
|
18
|
+
import { entityRef, selectFromEntity } from "metal-orm";
|
|
19
|
+
import { createSession } from "./db";
|
|
20
|
+
import {
|
|
21
|
+
CreatePostDto,
|
|
22
|
+
PostDto,
|
|
23
|
+
PostErrors,
|
|
24
|
+
PostParamsDto,
|
|
25
|
+
PostPagedResponseDto,
|
|
26
|
+
PostQueryDto,
|
|
27
|
+
PostQueryDtoClass,
|
|
28
|
+
ReplacePostDto,
|
|
29
|
+
UpdatePostDto
|
|
30
|
+
} from "./post.dtos";
|
|
31
|
+
import { Post as PostEntity } from "./post.entity";
|
|
32
|
+
import { User } from "./user.entity";
|
|
33
|
+
|
|
34
|
+
type IntegerOptions = {
|
|
35
|
+
min?: number;
|
|
36
|
+
max?: number;
|
|
37
|
+
clamp?: boolean;
|
|
38
|
+
};
|
|
39
|
+
|
|
40
|
+
function parseInteger(value: unknown, options: IntegerOptions = {}): number | undefined {
|
|
41
|
+
if (typeof value !== "number" || !Number.isInteger(value)) {
|
|
42
|
+
return undefined;
|
|
43
|
+
}
|
|
44
|
+
const result = value;
|
|
45
|
+
if (options.min !== undefined && result < options.min) {
|
|
46
|
+
return options.clamp ? options.min : undefined;
|
|
47
|
+
}
|
|
48
|
+
if (options.max !== undefined && result > options.max) {
|
|
49
|
+
return options.clamp ? options.max : undefined;
|
|
50
|
+
}
|
|
51
|
+
return result;
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
function requirePostId(value: unknown): number {
|
|
55
|
+
const id = parseInteger(value, { min: 1 });
|
|
56
|
+
if (id === undefined) {
|
|
57
|
+
throw new HttpError(400, "Invalid post id.");
|
|
58
|
+
}
|
|
59
|
+
return id;
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
const postRef = entityRef(PostEntity);
|
|
63
|
+
|
|
64
|
+
type PostFilter = SimpleWhereInput<typeof PostEntity, "title" | "userId">;
|
|
65
|
+
type OrmSession = ReturnType<typeof createSession>;
|
|
66
|
+
|
|
67
|
+
async function withSession<T>(handler: (session: OrmSession) => Promise<T>) {
|
|
68
|
+
const session = createSession();
|
|
69
|
+
try {
|
|
70
|
+
return await handler(session);
|
|
71
|
+
} finally {
|
|
72
|
+
await session.dispose();
|
|
73
|
+
}
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
async function getPostOrThrow(session: OrmSession, id: number): Promise<PostEntity> {
|
|
77
|
+
const post = await session.find(PostEntity, id);
|
|
78
|
+
if (!post) {
|
|
79
|
+
throw new HttpError(404, "Post not found.");
|
|
80
|
+
}
|
|
81
|
+
return post;
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
async function getUserOrThrow(session: OrmSession, id: number): Promise<User> {
|
|
85
|
+
const user = await session.find(User, id);
|
|
86
|
+
if (!user) {
|
|
87
|
+
throw new HttpError(404, "User not found.");
|
|
88
|
+
}
|
|
89
|
+
return user;
|
|
90
|
+
}
|
|
91
|
+
|
|
92
|
+
function buildPostFilter(query?: PostQueryDto): PostFilter | undefined {
|
|
93
|
+
if (!query) {
|
|
94
|
+
return undefined;
|
|
95
|
+
}
|
|
96
|
+
const filter: PostFilter = {};
|
|
97
|
+
if (query.titleContains) {
|
|
98
|
+
filter.title = { contains: query.titleContains };
|
|
99
|
+
}
|
|
100
|
+
if (query.userId !== undefined) {
|
|
101
|
+
filter.userId = { equals: query.userId };
|
|
102
|
+
}
|
|
103
|
+
return Object.keys(filter).length ? filter : undefined;
|
|
104
|
+
}
|
|
105
|
+
|
|
106
|
+
@Controller("/posts")
|
|
107
|
+
export class PostController {
|
|
108
|
+
@Get("/")
|
|
109
|
+
@Query(PostQueryDtoClass)
|
|
110
|
+
@Returns(PostPagedResponseDto)
|
|
111
|
+
async list(ctx: RequestContext<unknown, PostQueryDto>) {
|
|
112
|
+
const paginationQuery = (ctx.query ?? {}) as Record<string, unknown>;
|
|
113
|
+
const { page, pageSize } = parsePagination(paginationQuery);
|
|
114
|
+
return withSession(async (session) => {
|
|
115
|
+
const filters = buildPostFilter(ctx.query);
|
|
116
|
+
const query = applyFilter(
|
|
117
|
+
selectFromEntity(PostEntity).orderBy(postRef.id, "ASC"),
|
|
118
|
+
PostEntity,
|
|
119
|
+
filters
|
|
120
|
+
);
|
|
121
|
+
const paged = await query.executePaged(session, { page, pageSize });
|
|
122
|
+
return toPagedResponse(paged);
|
|
123
|
+
});
|
|
124
|
+
}
|
|
125
|
+
|
|
126
|
+
@Get("/:id")
|
|
127
|
+
@Params(PostParamsDto)
|
|
128
|
+
@Returns(PostDto)
|
|
129
|
+
@PostErrors
|
|
130
|
+
async getOne(ctx: RequestContext<unknown, undefined, PostParamsDto>) {
|
|
131
|
+
const id = requirePostId(ctx.params.id);
|
|
132
|
+
return withSession(async (session) => {
|
|
133
|
+
const post = await getPostOrThrow(session, id);
|
|
134
|
+
return post as PostDto;
|
|
135
|
+
});
|
|
136
|
+
}
|
|
137
|
+
|
|
138
|
+
@Post("/")
|
|
139
|
+
@Body(CreatePostDto)
|
|
140
|
+
@Returns({ status: 201, schema: PostDto })
|
|
141
|
+
async create(ctx: RequestContext<CreatePostDto>) {
|
|
142
|
+
return withSession(async (session) => {
|
|
143
|
+
await getUserOrThrow(session, ctx.body.userId);
|
|
144
|
+
const post = new PostEntity();
|
|
145
|
+
post.title = ctx.body.title;
|
|
146
|
+
post.body = ctx.body.body ?? null;
|
|
147
|
+
post.userId = ctx.body.userId;
|
|
148
|
+
post.createdAt = new Date().toISOString();
|
|
149
|
+
await session.persist(post);
|
|
150
|
+
await session.commit();
|
|
151
|
+
return post as PostDto;
|
|
152
|
+
});
|
|
153
|
+
}
|
|
154
|
+
|
|
155
|
+
@Put("/:id")
|
|
156
|
+
@Params(PostParamsDto)
|
|
157
|
+
@Body(ReplacePostDto)
|
|
158
|
+
@Returns(PostDto)
|
|
159
|
+
@PostErrors
|
|
160
|
+
async replace(ctx: RequestContext<ReplacePostDto, undefined, PostParamsDto>) {
|
|
161
|
+
const id = requirePostId(ctx.params.id);
|
|
162
|
+
return withSession(async (session) => {
|
|
163
|
+
const entity = await getPostOrThrow(session, id);
|
|
164
|
+
await getUserOrThrow(session, ctx.body.userId);
|
|
165
|
+
entity.title = ctx.body.title;
|
|
166
|
+
entity.body = ctx.body.body ?? null;
|
|
167
|
+
entity.userId = ctx.body.userId;
|
|
168
|
+
await session.commit();
|
|
169
|
+
return entity as PostDto;
|
|
170
|
+
});
|
|
171
|
+
}
|
|
172
|
+
|
|
173
|
+
@Patch("/:id")
|
|
174
|
+
@Params(PostParamsDto)
|
|
175
|
+
@Body(UpdatePostDto)
|
|
176
|
+
@Returns(PostDto)
|
|
177
|
+
@PostErrors
|
|
178
|
+
async update(ctx: RequestContext<UpdatePostDto, undefined, PostParamsDto>) {
|
|
179
|
+
const id = requirePostId(ctx.params.id);
|
|
180
|
+
return withSession(async (session) => {
|
|
181
|
+
const entity = await getPostOrThrow(session, id);
|
|
182
|
+
if (ctx.body.title !== undefined) {
|
|
183
|
+
entity.title = ctx.body.title;
|
|
184
|
+
}
|
|
185
|
+
if (ctx.body.body !== undefined) {
|
|
186
|
+
entity.body = ctx.body.body ?? null;
|
|
187
|
+
}
|
|
188
|
+
if (ctx.body.userId !== undefined) {
|
|
189
|
+
await getUserOrThrow(session, ctx.body.userId);
|
|
190
|
+
entity.userId = ctx.body.userId;
|
|
191
|
+
}
|
|
192
|
+
await session.commit();
|
|
193
|
+
return entity as PostDto;
|
|
194
|
+
});
|
|
195
|
+
}
|
|
196
|
+
|
|
197
|
+
@Delete("/:id")
|
|
198
|
+
@Params(PostParamsDto)
|
|
199
|
+
@Returns({ status: 204 })
|
|
200
|
+
@PostErrors
|
|
201
|
+
async remove(ctx: RequestContext<unknown, undefined, PostParamsDto>) {
|
|
202
|
+
const id = requirePostId(ctx.params.id);
|
|
203
|
+
return withSession(async (session) => {
|
|
204
|
+
const post = await getPostOrThrow(session, id);
|
|
205
|
+
await session.remove(post);
|
|
206
|
+
await session.commit();
|
|
207
|
+
});
|
|
208
|
+
}
|
|
209
|
+
}
|
|
@@ -0,0 +1,78 @@
|
|
|
1
|
+
import {
|
|
2
|
+
Errors,
|
|
3
|
+
createMetalCrudDtoClasses,
|
|
4
|
+
createMetalDtoOverrides,
|
|
5
|
+
createPagedResponseDtoClass,
|
|
6
|
+
createNestedCreateDtoClass,
|
|
7
|
+
createPagedFilterQueryDtoClass,
|
|
8
|
+
SimpleErrorDto,
|
|
9
|
+
t
|
|
10
|
+
} from "../../src";
|
|
11
|
+
import { Post } from "./post.entity";
|
|
12
|
+
|
|
13
|
+
const postOverrides = createMetalDtoOverrides(Post, {
|
|
14
|
+
overrides: {
|
|
15
|
+
email: t.nullable(t.string({ format: "email" }))
|
|
16
|
+
}
|
|
17
|
+
});
|
|
18
|
+
|
|
19
|
+
const postCrud = createMetalCrudDtoClasses(Post, {
|
|
20
|
+
overrides: postOverrides,
|
|
21
|
+
response: { description: "Post returned by API." },
|
|
22
|
+
mutationExclude: ["id", "createdAt"]
|
|
23
|
+
});
|
|
24
|
+
|
|
25
|
+
export const {
|
|
26
|
+
response: PostDto,
|
|
27
|
+
create: CreatePostDto,
|
|
28
|
+
replace: ReplacePostDto,
|
|
29
|
+
update: UpdatePostDto,
|
|
30
|
+
params: PostParamsDto
|
|
31
|
+
} = postCrud;
|
|
32
|
+
|
|
33
|
+
export type PostDto = Omit<Post, "user">;
|
|
34
|
+
type PostMutationDto = Omit<PostDto, "id" | "createdAt">;
|
|
35
|
+
export type CreatePostDto = PostMutationDto;
|
|
36
|
+
export type ReplacePostDto = PostMutationDto;
|
|
37
|
+
export type UpdatePostDto = Partial<PostMutationDto>;
|
|
38
|
+
export type PostParamsDto = InstanceType<typeof PostParamsDto>;
|
|
39
|
+
|
|
40
|
+
export const CreateUserPostDtoClass = createNestedCreateDtoClass(
|
|
41
|
+
Post,
|
|
42
|
+
postOverrides,
|
|
43
|
+
{
|
|
44
|
+
name: "CreateUserPostDto",
|
|
45
|
+
additionalExclude: ["userId"]
|
|
46
|
+
}
|
|
47
|
+
);
|
|
48
|
+
|
|
49
|
+
export interface CreateUserPostDto {
|
|
50
|
+
title: string;
|
|
51
|
+
body?: string | null;
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
export const PostQueryDtoClass = createPagedFilterQueryDtoClass({
|
|
55
|
+
name: "PostQueryDto",
|
|
56
|
+
filters: {
|
|
57
|
+
titleContains: { schema: t.string({ minLength: 1 }), operator: "contains" },
|
|
58
|
+
userId: { schema: t.integer({ minimum: 1 }), operator: "equals" }
|
|
59
|
+
}
|
|
60
|
+
});
|
|
61
|
+
|
|
62
|
+
export interface PostQueryDto {
|
|
63
|
+
page?: number;
|
|
64
|
+
pageSize?: number;
|
|
65
|
+
titleContains?: string;
|
|
66
|
+
userId?: number;
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
export const PostPagedResponseDto = createPagedResponseDtoClass({
|
|
70
|
+
name: "PostPagedResponseDto",
|
|
71
|
+
itemDto: PostDto,
|
|
72
|
+
description: "Paged post list response."
|
|
73
|
+
});
|
|
74
|
+
|
|
75
|
+
export const PostErrors = Errors(SimpleErrorDto, [
|
|
76
|
+
{ status: 400, description: "Invalid post id." },
|
|
77
|
+
{ status: 404, description: "Post not found." }
|
|
78
|
+
]);
|
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
import { BelongsTo, Column, Entity, PrimaryKey, col } from "metal-orm";
|
|
2
|
+
import type { BelongsToReference } from "metal-orm";
|
|
3
|
+
import { User } from "./user.entity";
|
|
4
|
+
|
|
5
|
+
@Entity({ tableName: "posts" })
|
|
6
|
+
export class Post {
|
|
7
|
+
@PrimaryKey(col.autoIncrement(col.int()))
|
|
8
|
+
id!: number;
|
|
9
|
+
|
|
10
|
+
@Column(col.notNull(col.text()))
|
|
11
|
+
title!: string;
|
|
12
|
+
|
|
13
|
+
@Column(col.text())
|
|
14
|
+
body?: string | null;
|
|
15
|
+
|
|
16
|
+
@Column(col.notNull(col.int()))
|
|
17
|
+
userId!: number;
|
|
18
|
+
|
|
19
|
+
@Column(col.notNull(col.text()))
|
|
20
|
+
createdAt!: string;
|
|
21
|
+
|
|
22
|
+
@BelongsTo({ target: () => User, foreignKey: "userId" })
|
|
23
|
+
user!: BelongsToReference<User>;
|
|
24
|
+
}
|
|
@@ -0,0 +1,305 @@
|
|
|
1
|
+
import {
|
|
2
|
+
Body,
|
|
3
|
+
Controller,
|
|
4
|
+
Delete,
|
|
5
|
+
Get,
|
|
6
|
+
HttpError,
|
|
7
|
+
Params,
|
|
8
|
+
Patch,
|
|
9
|
+
Post,
|
|
10
|
+
Put,
|
|
11
|
+
Query,
|
|
12
|
+
Returns,
|
|
13
|
+
MetalDto,
|
|
14
|
+
OmitDto,
|
|
15
|
+
PartialDto,
|
|
16
|
+
PickDto,
|
|
17
|
+
MergeDto,
|
|
18
|
+
Dto,
|
|
19
|
+
Field,
|
|
20
|
+
Errors,
|
|
21
|
+
t,
|
|
22
|
+
createMetalDtoOverrides,
|
|
23
|
+
type RequestContext
|
|
24
|
+
} from "../../src";
|
|
25
|
+
import { applyFilter, toPagedResponse, selectFromEntity, entityRef } from "metal-orm";
|
|
26
|
+
import { createSession } from "./db";
|
|
27
|
+
import {
|
|
28
|
+
withSession,
|
|
29
|
+
parsePagination,
|
|
30
|
+
parseFilter,
|
|
31
|
+
createPagedQueryDtoClass,
|
|
32
|
+
createPagedResponseDtoClass
|
|
33
|
+
} from "../../src";
|
|
34
|
+
import { User } from "./user.entity";
|
|
35
|
+
import { PostDto } from "./post.dtos";
|
|
36
|
+
|
|
37
|
+
const userRef = entityRef(User);
|
|
38
|
+
|
|
39
|
+
const userOverrides = createMetalDtoOverrides(User, {
|
|
40
|
+
overrides: {
|
|
41
|
+
email: t.nullable(t.string({ format: "email" }))
|
|
42
|
+
}
|
|
43
|
+
});
|
|
44
|
+
|
|
45
|
+
@MetalDto(User, {
|
|
46
|
+
description: "User returned by the API.",
|
|
47
|
+
overrides: userOverrides
|
|
48
|
+
})
|
|
49
|
+
export class UserDto {
|
|
50
|
+
declare id: number;
|
|
51
|
+
declare name: string;
|
|
52
|
+
declare email?: string | null;
|
|
53
|
+
declare createdAt: string;
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
@Dto()
|
|
57
|
+
class UserPostsDto {
|
|
58
|
+
@Field(t.array(t.ref(PostDto)))
|
|
59
|
+
posts!: PostDto[];
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
@MergeDto([UserDto, UserPostsDto], {
|
|
63
|
+
description: "User returned by the API with posts."
|
|
64
|
+
})
|
|
65
|
+
class UserWithPostsDto {}
|
|
66
|
+
|
|
67
|
+
const USER_MUTATION_KEYS: Array<keyof UserDto> = ["id", "createdAt"];
|
|
68
|
+
|
|
69
|
+
@OmitDto(UserDto, USER_MUTATION_KEYS)
|
|
70
|
+
export class CreateUserDto {
|
|
71
|
+
declare name: string;
|
|
72
|
+
declare email?: string | null;
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
@OmitDto(UserDto, USER_MUTATION_KEYS)
|
|
76
|
+
export class ReplaceUserDto {
|
|
77
|
+
declare name: string;
|
|
78
|
+
declare email?: string | null;
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
@PartialDto(ReplaceUserDto)
|
|
82
|
+
export class UpdateUserDto {
|
|
83
|
+
declare name?: string;
|
|
84
|
+
declare email?: string | null;
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
@PickDto(UserDto, ["id"])
|
|
88
|
+
export class UserParamsDto {
|
|
89
|
+
declare id: number;
|
|
90
|
+
}
|
|
91
|
+
|
|
92
|
+
const PagedQueryDto = createPagedQueryDtoClass({
|
|
93
|
+
name: "UserPagedQueryDto"
|
|
94
|
+
});
|
|
95
|
+
|
|
96
|
+
@Dto()
|
|
97
|
+
class UserFilterQueryDto {
|
|
98
|
+
@Field(t.optional(t.string({ minLength: 1 })))
|
|
99
|
+
nameContains?: string;
|
|
100
|
+
|
|
101
|
+
@Field(t.optional(t.string({ minLength: 1 })))
|
|
102
|
+
emailContains?: string;
|
|
103
|
+
}
|
|
104
|
+
|
|
105
|
+
@MergeDto([PagedQueryDto, UserFilterQueryDto])
|
|
106
|
+
export class UserQueryDto {
|
|
107
|
+
declare page?: number;
|
|
108
|
+
declare pageSize?: number;
|
|
109
|
+
declare nameContains?: string;
|
|
110
|
+
declare emailContains?: string;
|
|
111
|
+
}
|
|
112
|
+
|
|
113
|
+
const UserWithPostsPagedResponseDto = createPagedResponseDtoClass({
|
|
114
|
+
itemDto: UserWithPostsDto,
|
|
115
|
+
description: "Paged user list response with posts.",
|
|
116
|
+
name: "UserWithPostsPagedResponseDto"
|
|
117
|
+
});
|
|
118
|
+
|
|
119
|
+
@Dto()
|
|
120
|
+
class ErrorDto {
|
|
121
|
+
@Field(t.string())
|
|
122
|
+
message!: string;
|
|
123
|
+
}
|
|
124
|
+
|
|
125
|
+
export const UserErrors = Errors(ErrorDto, [
|
|
126
|
+
{ status: 400, description: "Invalid user id." },
|
|
127
|
+
{ status: 404, description: "User not found." }
|
|
128
|
+
]);
|
|
129
|
+
|
|
130
|
+
const USER_FILTER_MAPPINGS = {
|
|
131
|
+
nameContains: { field: "name" as const, operator: "contains" as const },
|
|
132
|
+
emailContains: { field: "email" as const, operator: "contains" as const }
|
|
133
|
+
};
|
|
134
|
+
|
|
135
|
+
type IntegerOptions = {
|
|
136
|
+
min?: number;
|
|
137
|
+
max?: number;
|
|
138
|
+
clamp?: boolean;
|
|
139
|
+
};
|
|
140
|
+
|
|
141
|
+
function parseInteger(value: unknown, options: IntegerOptions = {}): number | undefined {
|
|
142
|
+
if (typeof value !== "number" || !Number.isInteger(value)) {
|
|
143
|
+
return undefined;
|
|
144
|
+
}
|
|
145
|
+
const result = value;
|
|
146
|
+
if (options.min !== undefined && result < options.min) {
|
|
147
|
+
return options.clamp ? options.min : undefined;
|
|
148
|
+
}
|
|
149
|
+
if (options.max !== undefined && result > options.max) {
|
|
150
|
+
return options.clamp ? options.max : undefined;
|
|
151
|
+
}
|
|
152
|
+
return result;
|
|
153
|
+
}
|
|
154
|
+
|
|
155
|
+
function requireUserId(value: unknown): number {
|
|
156
|
+
const id = parseInteger(value, { min: 1 });
|
|
157
|
+
if (id === undefined) {
|
|
158
|
+
throw new HttpError(400, "Invalid user id.");
|
|
159
|
+
}
|
|
160
|
+
return id;
|
|
161
|
+
}
|
|
162
|
+
|
|
163
|
+
async function getUserOrThrow(session: any, id: number): Promise<User> {
|
|
164
|
+
const user = await session.find(User, id);
|
|
165
|
+
if (!user) {
|
|
166
|
+
throw new HttpError(404, "User not found.");
|
|
167
|
+
}
|
|
168
|
+
return user;
|
|
169
|
+
}
|
|
170
|
+
|
|
171
|
+
@Controller("/users")
|
|
172
|
+
export class UserController {
|
|
173
|
+
@Get("/")
|
|
174
|
+
@Query(UserQueryDto)
|
|
175
|
+
@Returns(UserWithPostsPagedResponseDto)
|
|
176
|
+
async list(ctx: RequestContext<unknown, UserQueryDto>) {
|
|
177
|
+
return withSession(createSession, async (session) => {
|
|
178
|
+
const { page, pageSize } = parsePagination((ctx.query ?? {}) as Record<string, unknown>);
|
|
179
|
+
const filters = parseFilter<User, "name" | "email">((ctx.query ?? {}) as Record<string, unknown>, USER_FILTER_MAPPINGS);
|
|
180
|
+
const query = applyFilter(
|
|
181
|
+
selectFromEntity(User)
|
|
182
|
+
.orderBy(userRef.id, "ASC")
|
|
183
|
+
.include("posts", {
|
|
184
|
+
columns: ["id", "title", "body", "userId", "createdAt"]
|
|
185
|
+
}),
|
|
186
|
+
User,
|
|
187
|
+
filters
|
|
188
|
+
);
|
|
189
|
+
const paged = await query.executePaged(session, { page, pageSize });
|
|
190
|
+
return toPagedResponse(paged);
|
|
191
|
+
});
|
|
192
|
+
}
|
|
193
|
+
|
|
194
|
+
@Get("/:id")
|
|
195
|
+
@Params(UserParamsDto)
|
|
196
|
+
@Returns(UserDto)
|
|
197
|
+
@UserErrors
|
|
198
|
+
async getOne(ctx: RequestContext<unknown, undefined, UserParamsDto>) {
|
|
199
|
+
return withSession(createSession, async (session) => {
|
|
200
|
+
const id = requireUserId(ctx.params.id);
|
|
201
|
+
const user = await getUserOrThrow(session, id);
|
|
202
|
+
return user as UserDto;
|
|
203
|
+
});
|
|
204
|
+
}
|
|
205
|
+
|
|
206
|
+
@Post("/")
|
|
207
|
+
@Body(CreateUserDto)
|
|
208
|
+
@Returns({ status: 201, schema: UserDto })
|
|
209
|
+
async create(ctx: RequestContext<CreateUserDto>) {
|
|
210
|
+
return withSession(createSession, async (session) => {
|
|
211
|
+
const user = new User();
|
|
212
|
+
user.name = ctx.body.name;
|
|
213
|
+
user.email = ctx.body.email ?? null;
|
|
214
|
+
user.createdAt = new Date().toISOString();
|
|
215
|
+
await session.persist(user);
|
|
216
|
+
await session.commit();
|
|
217
|
+
return user as UserDto;
|
|
218
|
+
});
|
|
219
|
+
}
|
|
220
|
+
|
|
221
|
+
@Put("/:id")
|
|
222
|
+
@Params(UserParamsDto)
|
|
223
|
+
@Body(ReplaceUserDto)
|
|
224
|
+
@Returns(UserDto)
|
|
225
|
+
@UserErrors
|
|
226
|
+
async replace(ctx: RequestContext<ReplaceUserDto, undefined, UserParamsDto>) {
|
|
227
|
+
return withSession(createSession, async (session) => {
|
|
228
|
+
const id = requireUserId(ctx.params.id);
|
|
229
|
+
const entity = await getUserOrThrow(session, id);
|
|
230
|
+
entity.name = ctx.body.name;
|
|
231
|
+
entity.email = ctx.body.email ?? null;
|
|
232
|
+
await session.commit();
|
|
233
|
+
return entity as UserDto;
|
|
234
|
+
});
|
|
235
|
+
}
|
|
236
|
+
|
|
237
|
+
@Patch("/:id")
|
|
238
|
+
@Params(UserParamsDto)
|
|
239
|
+
@Body(UpdateUserDto)
|
|
240
|
+
@Returns(UserDto)
|
|
241
|
+
@UserErrors
|
|
242
|
+
async update(ctx: RequestContext<UpdateUserDto, undefined, UserParamsDto>) {
|
|
243
|
+
return withSession(createSession, async (session) => {
|
|
244
|
+
const id = requireUserId(ctx.params.id);
|
|
245
|
+
const entity = await getUserOrThrow(session, id);
|
|
246
|
+
if (ctx.body.name !== undefined) {
|
|
247
|
+
entity.name = ctx.body.name;
|
|
248
|
+
}
|
|
249
|
+
if (ctx.body.email !== undefined) {
|
|
250
|
+
entity.email = ctx.body.email ?? null;
|
|
251
|
+
}
|
|
252
|
+
await session.commit();
|
|
253
|
+
return entity as UserDto;
|
|
254
|
+
});
|
|
255
|
+
}
|
|
256
|
+
|
|
257
|
+
@Get("/:id/posts")
|
|
258
|
+
@Params(UserParamsDto)
|
|
259
|
+
@Returns(t.array(t.ref(PostDto)))
|
|
260
|
+
@UserErrors
|
|
261
|
+
async listPosts(ctx: RequestContext<unknown, undefined, UserParamsDto>) {
|
|
262
|
+
return withSession(createSession, async (session) => {
|
|
263
|
+
const id = requireUserId(ctx.params.id);
|
|
264
|
+
const user = await getUserOrThrow(session, id);
|
|
265
|
+
return (await user.posts.load()) as PostDto[];
|
|
266
|
+
});
|
|
267
|
+
}
|
|
268
|
+
|
|
269
|
+
@Post("/:id/posts")
|
|
270
|
+
@Params(UserParamsDto)
|
|
271
|
+
@Body(t.object({
|
|
272
|
+
title: t.string({ minLength: 1 }),
|
|
273
|
+
body: t.nullable(t.string())
|
|
274
|
+
}))
|
|
275
|
+
@Returns({ status: 201, schema: PostDto })
|
|
276
|
+
@UserErrors
|
|
277
|
+
async createPost(
|
|
278
|
+
ctx: RequestContext<any, undefined, UserParamsDto>
|
|
279
|
+
) {
|
|
280
|
+
return withSession(createSession, async (session) => {
|
|
281
|
+
const id = requireUserId(ctx.params.id);
|
|
282
|
+
const user = await getUserOrThrow(session, id);
|
|
283
|
+
const post = user.posts.add({
|
|
284
|
+
title: ctx.body.title,
|
|
285
|
+
body: ctx.body.body ?? null,
|
|
286
|
+
createdAt: new Date().toISOString()
|
|
287
|
+
});
|
|
288
|
+
await session.commit();
|
|
289
|
+
return post as PostDto;
|
|
290
|
+
});
|
|
291
|
+
}
|
|
292
|
+
|
|
293
|
+
@Delete("/:id")
|
|
294
|
+
@Params(UserParamsDto)
|
|
295
|
+
@Returns({ status: 204 })
|
|
296
|
+
@UserErrors
|
|
297
|
+
async remove(ctx: RequestContext<unknown, undefined, UserParamsDto>) {
|
|
298
|
+
return withSession(createSession, async (session) => {
|
|
299
|
+
const id = requireUserId(ctx.params.id);
|
|
300
|
+
const user = await getUserOrThrow(session, id);
|
|
301
|
+
await session.remove(user);
|
|
302
|
+
await session.commit();
|
|
303
|
+
});
|
|
304
|
+
}
|
|
305
|
+
}
|