From 8c2775b0d726ec6f3a1699858f5a2ca8ab9dce56 Mon Sep 17 00:00:00 2001 From: Simon Abler Date: Sun, 15 Mar 2026 15:55:24 +0100 Subject: [PATCH 01/17] WIP --- .dockerignore | 68 + .editorconfig | 13 + .gitignore | 51 + .prettierignore | 6 + .prettierrc | 3 + LICENSE.md | 42 + OPEN_SOURCE_NOTICES.md | 18 + apps/server-e2e/eslint.config.mjs | 3 + apps/server-e2e/jest.config.ts | 18 + apps/server-e2e/project.json | 17 + apps/server-e2e/src/server/server.spec.ts | 10 + apps/server-e2e/src/support/global-setup.ts | 16 + .../server-e2e/src/support/global-teardown.ts | 10 + apps/server-e2e/src/support/test-setup.ts | 9 + apps/server-e2e/tsconfig.json | 13 + apps/server-e2e/tsconfig.spec.json | 9 + apps/server/README.md | 44 + apps/server/entrypoint.backend.sh | 48 + apps/server/eslint.config.mjs | 3 + apps/server/jest.config.ts | 21 + apps/server/package-lock.json | 280 + apps/server/package.json | 9 + apps/server/project.json | 73 + apps/server/src/app/app.controller.spec.ts | 22 + apps/server/src/app/app.controller.ts | 12 + apps/server/src/app/app.module.ts | 27 + apps/server/src/app/app.service.ts | 8 + apps/server/src/app/common/base.service.ts | 80 + .../app/common/decorators/apires.decorator.ts | 53 + .../app/common/decorators/isset.decorator.ts | 24 + apps/server/src/app/common/dto/id.dto.ts | 13 + .../src/app/common/dto/time-range.dto.ts | 13 + .../src/app/common/enums/errorcodes.enum.ts | 3 + .../src/app/common/filters/errors.filter.ts | 37 + .../src/app/common/pdfAnnotation/adler.svg | 242 + .../app/common/pdfAnnotation/fonts/ARIALN.TTF | Bin 0 -> 175956 bytes .../common/pdfAnnotation/fonts/ARIALNB.TTF | Bin 0 -> 180740 bytes .../common/pdfAnnotation/fonts/ARIALNBI.TTF | Bin 0 -> 180084 bytes .../common/pdfAnnotation/fonts/ARIALNI.TTF | Bin 0 -> 181124 bytes .../app/common/pdfAnnotation/fonts/arial.ttf | Bin 0 -> 1036584 bytes .../common/pdfAnnotation/fonts/arialbd.ttf | Bin 0 -> 980756 bytes .../common/pdfAnnotation/fonts/arialbi.ttf | Bin 0 -> 721144 bytes .../app/common/pdfAnnotation/fonts/ariali.ttf | Bin 0 -> 717428 bytes .../app/common/pdfAnnotation/fonts/ariblk.ttf | Bin 0 -> 167592 bytes .../src/app/common/pdfAnnotation/gdfort.jpg | Bin 0 -> 39158 bytes .../src/app/common/pdfAnnotation/logo.svg | 53 + apps/server/src/app/common/res.model.ts | 40 + .../common/serializers/model.serializer.ts | 13 + .../app/common/services/pdfmaker.service.ts | 610 + .../app/common/services/printer.service.ts | 21 + apps/server/src/app/common/shared.module.ts | 11 + .../src/app/config/app/config.module.ts | 37 + .../src/app/config/app/config.service.ts | 44 + .../src/app/config/app/configuration.ts | 14 + .../config/database/mysql/config.module.ts | 32 + .../config/database/mysql/config.service.ts | 36 + .../config/database/mysql/configuration.ts | 12 + .../config/database/sqlite/config.module.ts | 28 + .../config/database/sqlite/config.service.ts | 24 + .../config/database/sqlite/configuration.ts | 8 + .../article/article-group.repository.ts | 27 + .../models/article/article-group.service.ts | 17 + .../src/app/models/article/article.module.ts | 28 + .../app/models/article/article.repository.ts | 27 + .../models/article/article.service.spec.ts | 226 + .../src/app/models/article/article.service.ts | 278 + .../controllers/article-group.controller.ts | 85 + .../controllers/article.controller.spec.ts | 64 + .../article/controllers/article.controller.ts | 157 + .../article/dto/create-article-group.dto.ts | 14 + .../models/article/dto/create-article.dto.ts | 78 + .../article/dto/update-article-group.dto.ts | 5 + .../models/article/dto/update-article.dto.ts | 10 + .../article/dto/update-inventory.dto.ts | 12 + .../article/entities/article-group.entity.ts | 35 + .../entities/article-import.csv-entity.ts | 60 + .../models/article/entities/article.entity.ts | 84 + .../article/entities/inventory.entity.ts | 39 + .../interfaces/article-group.interface.ts | 3 + .../article/interfaces/article.interface.ts | 18 + .../article/interfaces/inventory.interface.ts | 10 + .../models/article/inventory.repository.ts | 27 + .../app/models/article/inventory.service.ts | 23 + .../serializers/article-group.serializer.ts | 27 + .../article/serializers/article.serializer.ts | 59 + .../serializers/inventory.serializer.ts | 35 + .../app/models/bills/annotation.repository.ts | 24 + .../app/models/bills/annotation.service.ts | 18 + .../src/app/models/bills/bill.module.ts | 59 + .../src/app/models/bills/bill.repository.ts | 24 + .../src/app/models/bills/bill.service.spec.ts | 53 + .../src/app/models/bills/bill.service.ts | 269 + .../controllers/annotation.controller.ts | 87 + .../bills/controllers/bill.controller.spec.ts | 104 + .../bills/controllers/bill.controller.ts | 153 + .../bills/controllers/dashboard.controller.ts | 60 + .../controllers/order-entry.controller.ts | 90 + .../bills/controllers/slipsheet.controller.ts | 256 + .../models/bills/dashboard.service.spec.ts | 219 + .../src/app/models/bills/dashboard.service.ts | 555 + .../app/models/bills/discount.repository.ts | 21 + .../src/app/models/bills/discount.service.ts | 45 + .../models/bills/dto/add-order-entry.dto.ts | 70 + .../bills/dto/add-update-discount.dto.ts | 25 + .../models/bills/dto/create-annotation.dto.ts | 13 + .../app/models/bills/dto/create-bill.dto.ts | 6 + .../bills/dto/dashboard-summary-query.dto.ts | 51 + .../models/bills/dto/update-annotation.dto.ts | 8 + .../app/models/bills/dto/update-bill.dto.ts | 22 + .../bills/dto/update-order-entry.dto.ts | 11 + .../bills/entities/annotation.entity.ts | 32 + .../app/models/bills/entities/bill.entity.ts | 40 + .../models/bills/entities/discount.entity.ts | 41 + .../bills/entities/order-entry.entity.ts | 49 + .../models/bills/entities/slipsheet.entity.ts | 59 + .../app/models/bills/enums/bill-state.enum.ts | 5 + .../bills/enums/slipsheet-state.enum.ts | 5 + .../bills/interfaces/annotation.interface.ts | 6 + .../models/bills/interfaces/bill.interface.ts | 10 + .../bills/interfaces/discount.interface.ts | 9 + .../bills/interfaces/order-entry.interface.ts | 12 + .../bills/interfaces/slipsheet.interface.ts | 15 + .../models/bills/order-entry.repository.ts | 24 + .../app/models/bills/order-entry.service.ts | 141 + .../serializers/annotation.serializer.ts | 30 + .../bills/serializers/bill.serializer.ts | 40 + .../bills/serializers/discount.serializer.ts | 28 + .../serializers/order-entry.serializer.ts | 50 + .../bills/serializers/slipsheet.serializer.ts | 59 + .../app/models/bills/slipsheet.repository.ts | 24 + .../src/app/models/bills/slipsheet.service.ts | 221 + .../controllers/customer.controller.spec.ts | 92 + .../controllers/customer.controller.ts | 182 + .../app/models/customer/customer.module.ts | 24 + .../models/customer/customer.repository.ts | 27 + .../models/customer/customer.service.spec.ts | 32 + .../app/models/customer/customer.service.ts | 15 + .../customer/dto/create-customer.dto.ts | 90 + .../customer/dto/update-customer.dto.ts | 8 + .../customer/entities/customer.entity.ts | 60 + .../customer/interfaces/customer.interface.ts | 14 + .../serializers/customer.serializer.ts | 57 + .../server/src/app/models/model.repository.ts | 135 + .../models/users/decorators/user.decorator.ts | 8 + .../app/models/users/dto/create-user.dto.ts | 43 + .../models/users/dto/login-response.dto.ts | 27 + .../app/models/users/dto/update-user.dto.ts | 29 + .../app/models/users/entities/user.entity.ts | 39 + .../models/users/interfaces/user.interface.ts | 6 + .../users/serializers/user.serializer.ts | 36 + .../src/app/models/users/users.controller.ts | 112 + .../src/app/models/users/users.module.ts | 16 + .../src/app/models/users/users.repository.ts | 24 + .../app/models/users/users.service.spec.ts | 95 + .../src/app/models/users/users.service.ts | 72 + .../database/sqlite/provider.module.ts | 28 + .../utils/mocks/auditLogServiceMockFactory.ts | 8 + apps/server/src/app/utils/mocks/mock-type.ts | 4 + apps/server/src/app/utils/mocks/mock.ts | 99 + .../app/utils/mocks/repositoryMockFactory.ts | 15 + .../src/app/utils/mocks/serviceMockFactory.ts | 10 + apps/server/src/assets/.gitkeep | 0 apps/server/src/assets/watermark/.gitkeep | 1 + apps/server/src/assets/watermark/README.md | 9 + apps/server/src/datasource.ts | 34 + apps/server/src/main.ts | 97 + .../migrations/1742000000000-InitialSchema.ts | 29 + apps/server/tsconfig.app.json | 13 + apps/server/tsconfig.json | 17 + apps/server/tsconfig.spec.json | 15 + apps/server/tsconfig.typeorm.json | 18 + apps/server/webpack.config.js | 36 + apps/sims-e2e/eslint.config.mjs | 12 + apps/sims-e2e/playwright.config.js | 27 + apps/sims-e2e/project.json | 9 + apps/sims-e2e/src/example.spec.ts | 8 + apps/sims-e2e/tsconfig.json | 24 + apps/sims/eslint.config.mjs | 37 + apps/sims/jest.config.ts | 21 + apps/sims/project.json | 186 + apps/sims/proxy.conf.json | 8 + apps/sims/public/apple-touch-icon.png | Bin 0 -> 1580 bytes apps/sims/public/favicon.ico | Bin 0 -> 1833 bytes apps/sims/public/favicon.svg | 8 + apps/sims/public/robots.txt | 12 + apps/sims/public/schueler-import-vorlage.csv | 3 + apps/sims/public/sitemap.xml | 28 + apps/sims/src/app/_nav.ts | 47 + apps/sims/src/app/app.component.css | 0 apps/sims/src/app/app.component.spec.ts | 18 + apps/sims/src/app/app.component.ts | 76 + apps/sims/src/app/app.module.ts | 88 + apps/sims/src/app/app.routing.ts | 71 + .../default-layout.component.html | 74 + .../default-layout.component.ts | 57 + .../app/containers/default-layout/index.ts | 1 + apps/sims/src/app/containers/index.ts | 1 + .../src/app/helpers/atLeastOneValidator.ts | 21 + apps/sims/src/app/helpers/auth.guard.ts | 43 + .../src/app/helpers/backend.interceptor.ts | 45 + apps/sims/src/app/helpers/datatable.german.ts | 30 + .../sims/src/app/helpers/error.interceptor.ts | 36 + apps/sims/src/app/helpers/fake-backend.ts | 426 + apps/sims/src/app/helpers/helperFunction.ts | 64 + apps/sims/src/app/helpers/index.ts | 4 + .../src/app/models/article-group.model.ts | 12 + apps/sims/src/app/models/article.model.ts | 57 + apps/sims/src/app/models/bill.model.ts | 26 + apps/sims/src/app/models/billWithShop.ts | 30 + apps/sims/src/app/models/customer.model.ts | 47 + .../sims/src/app/models/deliverySlip.model.ts | 20 + apps/sims/src/app/models/discount.model.ts | 14 + apps/sims/src/app/models/index.ts | 7 + apps/sims/src/app/models/order.model.ts | 22 + .../sims/src/app/models/shoppingcart.model.ts | 72 + apps/sims/src/app/models/user.model.ts | 19 + .../services/article-group.service.spec.ts | 19 + .../src/app/services/article-group.service.ts | 28 + .../src/app/services/article.service.spec.ts | 19 + apps/sims/src/app/services/article.service.ts | 130 + .../services/authentication.service.spec.ts | 23 + .../app/services/authentication.service.ts | 59 + .../src/app/services/bill.service.spec.ts | 19 + apps/sims/src/app/services/bill.service.ts | 104 + .../src/app/services/customer.service.spec.ts | 20 + .../sims/src/app/services/customer.service.ts | 133 + .../app/services/dashboard.service.spec.ts | 60 + .../src/app/services/dashboard.service.ts | 138 + apps/sims/src/app/services/index.ts | 2 + .../app/services/shoppingcart.service.spec.ts | 61 + .../src/app/services/shoppingcart.service.ts | 238 + .../src/app/services/user.service.spec.ts | 23 + apps/sims/src/app/services/user.service.ts | 102 + apps/sims/src/app/shared.module.ts | 21 + .../app/views/admin/admin-routing.module.ts | 48 + apps/sims/src/app/views/admin/admin.module.ts | 29 + .../app/views/admin/bills/bills.component.css | 0 .../views/admin/bills/bills.component.html | 122 + .../views/admin/bills/bills.component.spec.ts | 45 + .../app/views/admin/bills/bills.component.ts | 198 + .../customer-detail.component.css | 9 + .../customer-detail.component.html | 194 + .../customer-detail.component.spec.ts | 81 + .../customer-detail.component.ts | 473 + .../customer-edit/customer-edit.component.css | 11 + .../customer-edit.component.html | 248 + .../customer-edit.component.spec.ts | 82 + .../customer-edit/customer-edit.component.ts | 284 + .../admin/customer/customer.component.css | 5 + .../admin/customer/customer.component.html | 97 + .../admin/customer/customer.component.spec.ts | 37 + .../admin/customer/customer.component.ts | 64 + .../admin/discount/discount.component.css | 0 .../admin/discount/discount.component.html | 1 + .../admin/discount/discount.component.spec.ts | 25 + .../admin/discount/discount.component.ts | 15 + .../article-edit/article-edit.component.css | 0 .../article-edit/article-edit.component.html | 184 + .../article-edit.component.spec.ts | 54 + .../article-edit/article-edit.component.ts | 204 + .../views/article/article-routing.module.ts | 24 + .../src/app/views/article/article.module.ts | 26 + .../article/article/article.component.css | 7 + .../article/article/article.component.html | 310 + .../article/article/article.component.spec.ts | 54 + .../article/article/article.component.ts | 157 + .../modals/question-prompt.component.ts | 42 + .../dashboard/dashboard-routing.module.ts | 44 + .../views/dashboard/dashboard.component.html | 253 + .../views/dashboard/dashboard.component.scss | 282 + .../dashboard/dashboard.component.spec.ts | 97 + .../views/dashboard/dashboard.component.ts | 168 + .../app/views/dashboard/dashboard.module.ts | 26 + .../inventory/inventory.component.css | 0 .../inventory/inventory.component.html | 56 + .../inventory/inventory.component.spec.ts | 37 + .../inventory/inventory.component.ts | 69 + .../verbrauch/verbrauch.component.css | 0 .../verbrauch/verbrauch.component.html | 1 + .../verbrauch/verbrauch.component.spec.ts | 25 + .../verbrauch/verbrauch.component.ts | 15 + .../src/app/views/error/404.component.html | 22 + .../sims/src/app/views/error/404.component.ts | 10 + .../src/app/views/error/500.component.html | 22 + .../sims/src/app/views/error/500.component.ts | 10 + .../src/app/views/login/login.component.html | 49 + .../src/app/views/login/login.component.ts | 7 + .../mobile/inventory/inventory.component.css | 0 .../mobile/inventory/inventory.component.html | 100 + .../inventory/inventory.component.spec.ts | 46 + .../mobile/inventory/inventory.component.ts | 118 + .../app/views/mobile/mobile-routing.module.ts | 35 + .../mobile-tracking.component.css | 16 + .../mobile-tracking.component.html | 118 + .../mobile-tracking.component.spec.ts | 61 + .../mobile-tracking.component.ts | 149 + .../app/views/mobile/mobile.component.html | 4 + .../app/views/mobile/mobile.component.scss | 0 .../src/app/views/mobile/mobile.component.ts | 23 + .../src/app/views/mobile/mobile.module.ts | 22 + .../shoppingcart/shoppingcart.component.html | 210 + .../shoppingcart/shoppingcart.component.scss | 58 + .../shoppingcart.component.spec.ts | 76 + .../shoppingcart/shoppingcart.component.ts | 210 + apps/sims/src/assets/.gitkeep | 0 apps/sims/src/assets/favicon.ico | Bin 0 -> 1150 bytes apps/sims/src/assets/img/avatars/1.jpg | Bin 0 -> 1913 bytes apps/sims/src/assets/img/avatars/2.jpg | Bin 0 -> 2105 bytes apps/sims/src/assets/img/avatars/3.jpg | Bin 0 -> 1645 bytes apps/sims/src/assets/img/avatars/4.jpg | Bin 0 -> 2580 bytes apps/sims/src/assets/img/avatars/5.jpg | Bin 0 -> 19058 bytes apps/sims/src/assets/img/avatars/6.jpg | Bin 0 -> 1608 bytes apps/sims/src/assets/img/avatars/7.jpg | Bin 0 -> 2059 bytes apps/sims/src/assets/img/avatars/8.jpg | Bin 0 -> 20466 bytes apps/sims/src/assets/img/brand/logo.svg | 44 + apps/sims/src/assets/img/brand/sygnet.svg | 17 + .../sims/src/environments/environment.prod.ts | 5 + apps/sims/src/environments/environment.ts | 10 + apps/sims/src/index.html | 27 + apps/sims/src/main.ts | 17 + apps/sims/src/polyfills.ts | 96 + apps/sims/src/scss/_custom.scss | 57 + apps/sims/src/scss/_variables.scss | 1 + apps/sims/src/scss/style.scss | 14 + apps/sims/src/scss/vendors/_variables.scss | 4 + .../sims/src/scss/vendors/chart.js/chart.scss | 48 + apps/sims/src/test.ts | 20 + apps/sims/src/tsconfig.app.json | 25 + apps/sims/src/tsconfig.spec.json | 20 + apps/sims/src/typings.d.ts | 5 + apps/sims/tsconfig.app.json | 17 + apps/sims/tsconfig.json | 35 + apps/sims/tsconfig.server.json | 12 + apps/sims/tsconfig.spec.json | 17 + conf/postgres/.env.template | 4 + docker-compose.dev.yml | 56 + docker-compose.yml | 46 + dockerfiles/Dockerfile.backend | 49 + dockerfiles/Dockerfile.frontend | 43 + dockerfiles/nginx.conf | 22 + eslint.config.mjs | 42 + ... Image 11. M\303\244rz 2026, 21_11_42.png" | Bin 0 -> 298459 bytes ... Image 11. M\303\244rz 2026, 21_11_45.png" | Bin 0 -> 169611 bytes images/klara-preview.svg | 193 + images/klara.png | Bin 0 -> 164492 bytes jest.config.ts | 6 + jest.preset.js | 3 + libs/domain/jest.config.ts | 10 + libs/domain/project.json | 32 + libs/domain/src/dtos/assessment.dto.ts | 69 + libs/domain/src/dtos/auth-user.dto.spec.ts | 32 + libs/domain/src/dtos/auth-user.dto.ts | 6 + libs/domain/src/dtos/class.dto.ts | 56 + libs/domain/src/dtos/index.ts | 7 + libs/domain/src/dtos/note.dto.spec.ts | 217 + libs/domain/src/dtos/note.dto.ts | 60 + libs/domain/src/dtos/parent.dto.ts | 14 + libs/domain/src/dtos/student.dto.spec.ts | 80 + libs/domain/src/dtos/student.dto.ts | 51 + libs/domain/src/enums/index.ts | 11 + libs/domain/src/index.ts | 3 + libs/domain/src/interfaces/index.ts | 2 + libs/domain/tsconfig.json | 16 + libs/domain/tsconfig.lib.json | 10 + libs/domain/tsconfig.spec.json | 10 + nx.json | 86 + package-lock.json | 34529 ++++++++++++++++ package.json | 156 + tsconfig.base.json | 22 + 369 files changed, 53581 insertions(+) create mode 100644 .dockerignore create mode 100644 .editorconfig create mode 100644 .gitignore create mode 100644 .prettierignore create mode 100644 .prettierrc create mode 100644 LICENSE.md create mode 100644 OPEN_SOURCE_NOTICES.md create mode 100644 apps/server-e2e/eslint.config.mjs create mode 100644 apps/server-e2e/jest.config.ts create mode 100644 apps/server-e2e/project.json create mode 100644 apps/server-e2e/src/server/server.spec.ts create mode 100644 apps/server-e2e/src/support/global-setup.ts create mode 100644 apps/server-e2e/src/support/global-teardown.ts create mode 100644 apps/server-e2e/src/support/test-setup.ts create mode 100644 apps/server-e2e/tsconfig.json create mode 100644 apps/server-e2e/tsconfig.spec.json create mode 100644 apps/server/README.md create mode 100644 apps/server/entrypoint.backend.sh create mode 100644 apps/server/eslint.config.mjs create mode 100644 apps/server/jest.config.ts create mode 100644 apps/server/package-lock.json create mode 100644 apps/server/package.json create mode 100644 apps/server/project.json create mode 100644 apps/server/src/app/app.controller.spec.ts create mode 100644 apps/server/src/app/app.controller.ts create mode 100644 apps/server/src/app/app.module.ts create mode 100644 apps/server/src/app/app.service.ts create mode 100644 apps/server/src/app/common/base.service.ts create mode 100644 apps/server/src/app/common/decorators/apires.decorator.ts create mode 100644 apps/server/src/app/common/decorators/isset.decorator.ts create mode 100644 apps/server/src/app/common/dto/id.dto.ts create mode 100644 apps/server/src/app/common/dto/time-range.dto.ts create mode 100644 apps/server/src/app/common/enums/errorcodes.enum.ts create mode 100644 apps/server/src/app/common/filters/errors.filter.ts create mode 100644 apps/server/src/app/common/pdfAnnotation/adler.svg create mode 100644 apps/server/src/app/common/pdfAnnotation/fonts/ARIALN.TTF create mode 100644 apps/server/src/app/common/pdfAnnotation/fonts/ARIALNB.TTF create mode 100644 apps/server/src/app/common/pdfAnnotation/fonts/ARIALNBI.TTF create mode 100644 apps/server/src/app/common/pdfAnnotation/fonts/ARIALNI.TTF create mode 100644 apps/server/src/app/common/pdfAnnotation/fonts/arial.ttf create mode 100644 apps/server/src/app/common/pdfAnnotation/fonts/arialbd.ttf create mode 100644 apps/server/src/app/common/pdfAnnotation/fonts/arialbi.ttf create mode 100644 apps/server/src/app/common/pdfAnnotation/fonts/ariali.ttf create mode 100644 apps/server/src/app/common/pdfAnnotation/fonts/ariblk.ttf create mode 100644 apps/server/src/app/common/pdfAnnotation/gdfort.jpg create mode 100644 apps/server/src/app/common/pdfAnnotation/logo.svg create mode 100644 apps/server/src/app/common/res.model.ts create mode 100644 apps/server/src/app/common/serializers/model.serializer.ts create mode 100644 apps/server/src/app/common/services/pdfmaker.service.ts create mode 100644 apps/server/src/app/common/services/printer.service.ts create mode 100644 apps/server/src/app/common/shared.module.ts create mode 100644 apps/server/src/app/config/app/config.module.ts create mode 100644 apps/server/src/app/config/app/config.service.ts create mode 100644 apps/server/src/app/config/app/configuration.ts create mode 100644 apps/server/src/app/config/database/mysql/config.module.ts create mode 100644 apps/server/src/app/config/database/mysql/config.service.ts create mode 100644 apps/server/src/app/config/database/mysql/configuration.ts create mode 100644 apps/server/src/app/config/database/sqlite/config.module.ts create mode 100644 apps/server/src/app/config/database/sqlite/config.service.ts create mode 100644 apps/server/src/app/config/database/sqlite/configuration.ts create mode 100644 apps/server/src/app/models/article/article-group.repository.ts create mode 100644 apps/server/src/app/models/article/article-group.service.ts create mode 100644 apps/server/src/app/models/article/article.module.ts create mode 100644 apps/server/src/app/models/article/article.repository.ts create mode 100644 apps/server/src/app/models/article/article.service.spec.ts create mode 100644 apps/server/src/app/models/article/article.service.ts create mode 100644 apps/server/src/app/models/article/controllers/article-group.controller.ts create mode 100644 apps/server/src/app/models/article/controllers/article.controller.spec.ts create mode 100644 apps/server/src/app/models/article/controllers/article.controller.ts create mode 100644 apps/server/src/app/models/article/dto/create-article-group.dto.ts create mode 100644 apps/server/src/app/models/article/dto/create-article.dto.ts create mode 100644 apps/server/src/app/models/article/dto/update-article-group.dto.ts create mode 100644 apps/server/src/app/models/article/dto/update-article.dto.ts create mode 100644 apps/server/src/app/models/article/dto/update-inventory.dto.ts create mode 100644 apps/server/src/app/models/article/entities/article-group.entity.ts create mode 100644 apps/server/src/app/models/article/entities/article-import.csv-entity.ts create mode 100644 apps/server/src/app/models/article/entities/article.entity.ts create mode 100644 apps/server/src/app/models/article/entities/inventory.entity.ts create mode 100644 apps/server/src/app/models/article/interfaces/article-group.interface.ts create mode 100644 apps/server/src/app/models/article/interfaces/article.interface.ts create mode 100644 apps/server/src/app/models/article/interfaces/inventory.interface.ts create mode 100644 apps/server/src/app/models/article/inventory.repository.ts create mode 100644 apps/server/src/app/models/article/inventory.service.ts create mode 100644 apps/server/src/app/models/article/serializers/article-group.serializer.ts create mode 100644 apps/server/src/app/models/article/serializers/article.serializer.ts create mode 100644 apps/server/src/app/models/article/serializers/inventory.serializer.ts create mode 100644 apps/server/src/app/models/bills/annotation.repository.ts create mode 100644 apps/server/src/app/models/bills/annotation.service.ts create mode 100644 apps/server/src/app/models/bills/bill.module.ts create mode 100644 apps/server/src/app/models/bills/bill.repository.ts create mode 100644 apps/server/src/app/models/bills/bill.service.spec.ts create mode 100644 apps/server/src/app/models/bills/bill.service.ts create mode 100644 apps/server/src/app/models/bills/controllers/annotation.controller.ts create mode 100644 apps/server/src/app/models/bills/controllers/bill.controller.spec.ts create mode 100644 apps/server/src/app/models/bills/controllers/bill.controller.ts create mode 100644 apps/server/src/app/models/bills/controllers/dashboard.controller.ts create mode 100644 apps/server/src/app/models/bills/controllers/order-entry.controller.ts create mode 100644 apps/server/src/app/models/bills/controllers/slipsheet.controller.ts create mode 100644 apps/server/src/app/models/bills/dashboard.service.spec.ts create mode 100644 apps/server/src/app/models/bills/dashboard.service.ts create mode 100644 apps/server/src/app/models/bills/discount.repository.ts create mode 100644 apps/server/src/app/models/bills/discount.service.ts create mode 100644 apps/server/src/app/models/bills/dto/add-order-entry.dto.ts create mode 100644 apps/server/src/app/models/bills/dto/add-update-discount.dto.ts create mode 100644 apps/server/src/app/models/bills/dto/create-annotation.dto.ts create mode 100644 apps/server/src/app/models/bills/dto/create-bill.dto.ts create mode 100644 apps/server/src/app/models/bills/dto/dashboard-summary-query.dto.ts create mode 100644 apps/server/src/app/models/bills/dto/update-annotation.dto.ts create mode 100644 apps/server/src/app/models/bills/dto/update-bill.dto.ts create mode 100644 apps/server/src/app/models/bills/dto/update-order-entry.dto.ts create mode 100644 apps/server/src/app/models/bills/entities/annotation.entity.ts create mode 100644 apps/server/src/app/models/bills/entities/bill.entity.ts create mode 100644 apps/server/src/app/models/bills/entities/discount.entity.ts create mode 100644 apps/server/src/app/models/bills/entities/order-entry.entity.ts create mode 100644 apps/server/src/app/models/bills/entities/slipsheet.entity.ts create mode 100644 apps/server/src/app/models/bills/enums/bill-state.enum.ts create mode 100644 apps/server/src/app/models/bills/enums/slipsheet-state.enum.ts create mode 100644 apps/server/src/app/models/bills/interfaces/annotation.interface.ts create mode 100644 apps/server/src/app/models/bills/interfaces/bill.interface.ts create mode 100644 apps/server/src/app/models/bills/interfaces/discount.interface.ts create mode 100644 apps/server/src/app/models/bills/interfaces/order-entry.interface.ts create mode 100644 apps/server/src/app/models/bills/interfaces/slipsheet.interface.ts create mode 100644 apps/server/src/app/models/bills/order-entry.repository.ts create mode 100644 apps/server/src/app/models/bills/order-entry.service.ts create mode 100644 apps/server/src/app/models/bills/serializers/annotation.serializer.ts create mode 100644 apps/server/src/app/models/bills/serializers/bill.serializer.ts create mode 100644 apps/server/src/app/models/bills/serializers/discount.serializer.ts create mode 100644 apps/server/src/app/models/bills/serializers/order-entry.serializer.ts create mode 100644 apps/server/src/app/models/bills/serializers/slipsheet.serializer.ts create mode 100644 apps/server/src/app/models/bills/slipsheet.repository.ts create mode 100644 apps/server/src/app/models/bills/slipsheet.service.ts create mode 100644 apps/server/src/app/models/customer/controllers/customer.controller.spec.ts create mode 100644 apps/server/src/app/models/customer/controllers/customer.controller.ts create mode 100644 apps/server/src/app/models/customer/customer.module.ts create mode 100644 apps/server/src/app/models/customer/customer.repository.ts create mode 100644 apps/server/src/app/models/customer/customer.service.spec.ts create mode 100644 apps/server/src/app/models/customer/customer.service.ts create mode 100644 apps/server/src/app/models/customer/dto/create-customer.dto.ts create mode 100644 apps/server/src/app/models/customer/dto/update-customer.dto.ts create mode 100644 apps/server/src/app/models/customer/entities/customer.entity.ts create mode 100644 apps/server/src/app/models/customer/interfaces/customer.interface.ts create mode 100644 apps/server/src/app/models/customer/serializers/customer.serializer.ts create mode 100644 apps/server/src/app/models/model.repository.ts create mode 100644 apps/server/src/app/models/users/decorators/user.decorator.ts create mode 100644 apps/server/src/app/models/users/dto/create-user.dto.ts create mode 100644 apps/server/src/app/models/users/dto/login-response.dto.ts create mode 100644 apps/server/src/app/models/users/dto/update-user.dto.ts create mode 100644 apps/server/src/app/models/users/entities/user.entity.ts create mode 100644 apps/server/src/app/models/users/interfaces/user.interface.ts create mode 100644 apps/server/src/app/models/users/serializers/user.serializer.ts create mode 100644 apps/server/src/app/models/users/users.controller.ts create mode 100644 apps/server/src/app/models/users/users.module.ts create mode 100644 apps/server/src/app/models/users/users.repository.ts create mode 100644 apps/server/src/app/models/users/users.service.spec.ts create mode 100644 apps/server/src/app/models/users/users.service.ts create mode 100644 apps/server/src/app/providers/database/sqlite/provider.module.ts create mode 100644 apps/server/src/app/utils/mocks/auditLogServiceMockFactory.ts create mode 100644 apps/server/src/app/utils/mocks/mock-type.ts create mode 100644 apps/server/src/app/utils/mocks/mock.ts create mode 100644 apps/server/src/app/utils/mocks/repositoryMockFactory.ts create mode 100644 apps/server/src/app/utils/mocks/serviceMockFactory.ts create mode 100644 apps/server/src/assets/.gitkeep create mode 100644 apps/server/src/assets/watermark/.gitkeep create mode 100644 apps/server/src/assets/watermark/README.md create mode 100644 apps/server/src/datasource.ts create mode 100644 apps/server/src/main.ts create mode 100644 apps/server/src/migrations/1742000000000-InitialSchema.ts create mode 100644 apps/server/tsconfig.app.json create mode 100644 apps/server/tsconfig.json create mode 100644 apps/server/tsconfig.spec.json create mode 100644 apps/server/tsconfig.typeorm.json create mode 100644 apps/server/webpack.config.js create mode 100644 apps/sims-e2e/eslint.config.mjs create mode 100644 apps/sims-e2e/playwright.config.js create mode 100644 apps/sims-e2e/project.json create mode 100644 apps/sims-e2e/src/example.spec.ts create mode 100644 apps/sims-e2e/tsconfig.json create mode 100644 apps/sims/eslint.config.mjs create mode 100644 apps/sims/jest.config.ts create mode 100644 apps/sims/project.json create mode 100644 apps/sims/proxy.conf.json create mode 100644 apps/sims/public/apple-touch-icon.png create mode 100644 apps/sims/public/favicon.ico create mode 100644 apps/sims/public/favicon.svg create mode 100644 apps/sims/public/robots.txt create mode 100644 apps/sims/public/schueler-import-vorlage.csv create mode 100644 apps/sims/public/sitemap.xml create mode 100644 apps/sims/src/app/_nav.ts create mode 100644 apps/sims/src/app/app.component.css create mode 100644 apps/sims/src/app/app.component.spec.ts create mode 100644 apps/sims/src/app/app.component.ts create mode 100644 apps/sims/src/app/app.module.ts create mode 100644 apps/sims/src/app/app.routing.ts create mode 100644 apps/sims/src/app/containers/default-layout/default-layout.component.html create mode 100644 apps/sims/src/app/containers/default-layout/default-layout.component.ts create mode 100644 apps/sims/src/app/containers/default-layout/index.ts create mode 100644 apps/sims/src/app/containers/index.ts create mode 100644 apps/sims/src/app/helpers/atLeastOneValidator.ts create mode 100644 apps/sims/src/app/helpers/auth.guard.ts create mode 100644 apps/sims/src/app/helpers/backend.interceptor.ts create mode 100644 apps/sims/src/app/helpers/datatable.german.ts create mode 100644 apps/sims/src/app/helpers/error.interceptor.ts create mode 100644 apps/sims/src/app/helpers/fake-backend.ts create mode 100644 apps/sims/src/app/helpers/helperFunction.ts create mode 100644 apps/sims/src/app/helpers/index.ts create mode 100644 apps/sims/src/app/models/article-group.model.ts create mode 100644 apps/sims/src/app/models/article.model.ts create mode 100644 apps/sims/src/app/models/bill.model.ts create mode 100644 apps/sims/src/app/models/billWithShop.ts create mode 100644 apps/sims/src/app/models/customer.model.ts create mode 100644 apps/sims/src/app/models/deliverySlip.model.ts create mode 100644 apps/sims/src/app/models/discount.model.ts create mode 100644 apps/sims/src/app/models/index.ts create mode 100644 apps/sims/src/app/models/order.model.ts create mode 100644 apps/sims/src/app/models/shoppingcart.model.ts create mode 100644 apps/sims/src/app/models/user.model.ts create mode 100644 apps/sims/src/app/services/article-group.service.spec.ts create mode 100644 apps/sims/src/app/services/article-group.service.ts create mode 100644 apps/sims/src/app/services/article.service.spec.ts create mode 100644 apps/sims/src/app/services/article.service.ts create mode 100644 apps/sims/src/app/services/authentication.service.spec.ts create mode 100644 apps/sims/src/app/services/authentication.service.ts create mode 100644 apps/sims/src/app/services/bill.service.spec.ts create mode 100644 apps/sims/src/app/services/bill.service.ts create mode 100644 apps/sims/src/app/services/customer.service.spec.ts create mode 100644 apps/sims/src/app/services/customer.service.ts create mode 100644 apps/sims/src/app/services/dashboard.service.spec.ts create mode 100644 apps/sims/src/app/services/dashboard.service.ts create mode 100644 apps/sims/src/app/services/index.ts create mode 100644 apps/sims/src/app/services/shoppingcart.service.spec.ts create mode 100644 apps/sims/src/app/services/shoppingcart.service.ts create mode 100644 apps/sims/src/app/services/user.service.spec.ts create mode 100644 apps/sims/src/app/services/user.service.ts create mode 100644 apps/sims/src/app/shared.module.ts create mode 100644 apps/sims/src/app/views/admin/admin-routing.module.ts create mode 100644 apps/sims/src/app/views/admin/admin.module.ts create mode 100644 apps/sims/src/app/views/admin/bills/bills.component.css create mode 100644 apps/sims/src/app/views/admin/bills/bills.component.html create mode 100644 apps/sims/src/app/views/admin/bills/bills.component.spec.ts create mode 100644 apps/sims/src/app/views/admin/bills/bills.component.ts create mode 100644 apps/sims/src/app/views/admin/customer-detail/customer-detail.component.css create mode 100644 apps/sims/src/app/views/admin/customer-detail/customer-detail.component.html create mode 100644 apps/sims/src/app/views/admin/customer-detail/customer-detail.component.spec.ts create mode 100644 apps/sims/src/app/views/admin/customer-detail/customer-detail.component.ts create mode 100644 apps/sims/src/app/views/admin/customer-edit/customer-edit.component.css create mode 100644 apps/sims/src/app/views/admin/customer-edit/customer-edit.component.html create mode 100644 apps/sims/src/app/views/admin/customer-edit/customer-edit.component.spec.ts create mode 100644 apps/sims/src/app/views/admin/customer-edit/customer-edit.component.ts create mode 100644 apps/sims/src/app/views/admin/customer/customer.component.css create mode 100644 apps/sims/src/app/views/admin/customer/customer.component.html create mode 100644 apps/sims/src/app/views/admin/customer/customer.component.spec.ts create mode 100644 apps/sims/src/app/views/admin/customer/customer.component.ts create mode 100644 apps/sims/src/app/views/admin/discount/discount.component.css create mode 100644 apps/sims/src/app/views/admin/discount/discount.component.html create mode 100644 apps/sims/src/app/views/admin/discount/discount.component.spec.ts create mode 100644 apps/sims/src/app/views/admin/discount/discount.component.ts create mode 100644 apps/sims/src/app/views/article/article-edit/article-edit.component.css create mode 100644 apps/sims/src/app/views/article/article-edit/article-edit.component.html create mode 100644 apps/sims/src/app/views/article/article-edit/article-edit.component.spec.ts create mode 100644 apps/sims/src/app/views/article/article-edit/article-edit.component.ts create mode 100644 apps/sims/src/app/views/article/article-routing.module.ts create mode 100644 apps/sims/src/app/views/article/article.module.ts create mode 100644 apps/sims/src/app/views/article/article/article.component.css create mode 100644 apps/sims/src/app/views/article/article/article.component.html create mode 100644 apps/sims/src/app/views/article/article/article.component.spec.ts create mode 100644 apps/sims/src/app/views/article/article/article.component.ts create mode 100644 apps/sims/src/app/views/common/modals/question-prompt.component.ts create mode 100644 apps/sims/src/app/views/dashboard/dashboard-routing.module.ts create mode 100644 apps/sims/src/app/views/dashboard/dashboard.component.html create mode 100644 apps/sims/src/app/views/dashboard/dashboard.component.scss create mode 100644 apps/sims/src/app/views/dashboard/dashboard.component.spec.ts create mode 100644 apps/sims/src/app/views/dashboard/dashboard.component.ts create mode 100644 apps/sims/src/app/views/dashboard/dashboard.module.ts create mode 100644 apps/sims/src/app/views/dashboard/inventory/inventory.component.css create mode 100644 apps/sims/src/app/views/dashboard/inventory/inventory.component.html create mode 100644 apps/sims/src/app/views/dashboard/inventory/inventory.component.spec.ts create mode 100644 apps/sims/src/app/views/dashboard/inventory/inventory.component.ts create mode 100644 apps/sims/src/app/views/dashboard/verbrauch/verbrauch.component.css create mode 100644 apps/sims/src/app/views/dashboard/verbrauch/verbrauch.component.html create mode 100644 apps/sims/src/app/views/dashboard/verbrauch/verbrauch.component.spec.ts create mode 100644 apps/sims/src/app/views/dashboard/verbrauch/verbrauch.component.ts create mode 100644 apps/sims/src/app/views/error/404.component.html create mode 100644 apps/sims/src/app/views/error/404.component.ts create mode 100644 apps/sims/src/app/views/error/500.component.html create mode 100644 apps/sims/src/app/views/error/500.component.ts create mode 100644 apps/sims/src/app/views/login/login.component.html create mode 100644 apps/sims/src/app/views/login/login.component.ts create mode 100644 apps/sims/src/app/views/mobile/inventory/inventory.component.css create mode 100644 apps/sims/src/app/views/mobile/inventory/inventory.component.html create mode 100644 apps/sims/src/app/views/mobile/inventory/inventory.component.spec.ts create mode 100644 apps/sims/src/app/views/mobile/inventory/inventory.component.ts create mode 100644 apps/sims/src/app/views/mobile/mobile-routing.module.ts create mode 100644 apps/sims/src/app/views/mobile/mobile-tracking/mobile-tracking.component.css create mode 100644 apps/sims/src/app/views/mobile/mobile-tracking/mobile-tracking.component.html create mode 100644 apps/sims/src/app/views/mobile/mobile-tracking/mobile-tracking.component.spec.ts create mode 100644 apps/sims/src/app/views/mobile/mobile-tracking/mobile-tracking.component.ts create mode 100644 apps/sims/src/app/views/mobile/mobile.component.html create mode 100644 apps/sims/src/app/views/mobile/mobile.component.scss create mode 100644 apps/sims/src/app/views/mobile/mobile.component.ts create mode 100644 apps/sims/src/app/views/mobile/mobile.module.ts create mode 100644 apps/sims/src/app/views/shoppingcart/shoppingcart.component.html create mode 100644 apps/sims/src/app/views/shoppingcart/shoppingcart.component.scss create mode 100644 apps/sims/src/app/views/shoppingcart/shoppingcart.component.spec.ts create mode 100644 apps/sims/src/app/views/shoppingcart/shoppingcart.component.ts create mode 100644 apps/sims/src/assets/.gitkeep create mode 100644 apps/sims/src/assets/favicon.ico create mode 100644 apps/sims/src/assets/img/avatars/1.jpg create mode 100644 apps/sims/src/assets/img/avatars/2.jpg create mode 100644 apps/sims/src/assets/img/avatars/3.jpg create mode 100644 apps/sims/src/assets/img/avatars/4.jpg create mode 100644 apps/sims/src/assets/img/avatars/5.jpg create mode 100644 apps/sims/src/assets/img/avatars/6.jpg create mode 100644 apps/sims/src/assets/img/avatars/7.jpg create mode 100644 apps/sims/src/assets/img/avatars/8.jpg create mode 100644 apps/sims/src/assets/img/brand/logo.svg create mode 100644 apps/sims/src/assets/img/brand/sygnet.svg create mode 100644 apps/sims/src/environments/environment.prod.ts create mode 100644 apps/sims/src/environments/environment.ts create mode 100644 apps/sims/src/index.html create mode 100644 apps/sims/src/main.ts create mode 100644 apps/sims/src/polyfills.ts create mode 100644 apps/sims/src/scss/_custom.scss create mode 100644 apps/sims/src/scss/_variables.scss create mode 100644 apps/sims/src/scss/style.scss create mode 100644 apps/sims/src/scss/vendors/_variables.scss create mode 100644 apps/sims/src/scss/vendors/chart.js/chart.scss create mode 100644 apps/sims/src/test.ts create mode 100644 apps/sims/src/tsconfig.app.json create mode 100644 apps/sims/src/tsconfig.spec.json create mode 100644 apps/sims/src/typings.d.ts create mode 100644 apps/sims/tsconfig.app.json create mode 100644 apps/sims/tsconfig.json create mode 100644 apps/sims/tsconfig.server.json create mode 100644 apps/sims/tsconfig.spec.json create mode 100644 conf/postgres/.env.template create mode 100644 docker-compose.dev.yml create mode 100644 docker-compose.yml create mode 100644 dockerfiles/Dockerfile.backend create mode 100644 dockerfiles/Dockerfile.frontend create mode 100644 dockerfiles/nginx.conf create mode 100644 eslint.config.mjs create mode 100644 "images/ChatGPT Image 11. M\303\244rz 2026, 21_11_42.png" create mode 100644 "images/ChatGPT Image 11. M\303\244rz 2026, 21_11_45.png" create mode 100644 images/klara-preview.svg create mode 100644 images/klara.png create mode 100644 jest.config.ts create mode 100644 jest.preset.js create mode 100644 libs/domain/jest.config.ts create mode 100644 libs/domain/project.json create mode 100644 libs/domain/src/dtos/assessment.dto.ts create mode 100644 libs/domain/src/dtos/auth-user.dto.spec.ts create mode 100644 libs/domain/src/dtos/auth-user.dto.ts create mode 100644 libs/domain/src/dtos/class.dto.ts create mode 100644 libs/domain/src/dtos/index.ts create mode 100644 libs/domain/src/dtos/note.dto.spec.ts create mode 100644 libs/domain/src/dtos/note.dto.ts create mode 100644 libs/domain/src/dtos/parent.dto.ts create mode 100644 libs/domain/src/dtos/student.dto.spec.ts create mode 100644 libs/domain/src/dtos/student.dto.ts create mode 100644 libs/domain/src/enums/index.ts create mode 100644 libs/domain/src/index.ts create mode 100644 libs/domain/src/interfaces/index.ts create mode 100644 libs/domain/tsconfig.json create mode 100644 libs/domain/tsconfig.lib.json create mode 100644 libs/domain/tsconfig.spec.json create mode 100644 nx.json create mode 100644 package-lock.json create mode 100644 package.json create mode 100644 tsconfig.base.json diff --git a/.dockerignore b/.dockerignore new file mode 100644 index 0000000..9a2b043 --- /dev/null +++ b/.dockerignore @@ -0,0 +1,68 @@ +##### General build context hygiene for Nx/Node/Angular/NestJS repo + +# VCS and metadata +.git +.gitignore +.gitattributes +.github + +# Node/Nx/Angular outputs and caches +node_modules +dist +tmp +out-tsc +coverage +.nx/cache +.nx/workspace-data +.angular +.eslintcache +.cache +.turbo + +# Logs +logs +*.log +npm-debug.log* +yarn-debug.log* +yarn-error.log* +pnpm-debug.log* +testem.log +libpeerconnection.log + +# IDE/editor junk +.vscode +.idea +.project +.classpath +.c9/ +*.launch +.settings/ +*.sublime-workspace +.DS_Store +Thumbs.db + +# Types, typings and sass caches +/typings +/.sass-cache + +# Environment files (keep examples) +.env +.env.* +!.env.example +!.env.*.example + +# Playwright/Jest/E2E artifacts +playwright-report +test-results +.nyc_output +apps/*-e2e/playwright-report +apps/*-e2e/test-results + +# Local databases and dumps +*.sqlite +*.db +signpacks.sqlite + +# Misc +.cursor +/postgres diff --git a/.editorconfig b/.editorconfig new file mode 100644 index 0000000..6e87a00 --- /dev/null +++ b/.editorconfig @@ -0,0 +1,13 @@ +# Editor configuration, see http://editorconfig.org +root = true + +[*] +charset = utf-8 +indent_style = space +indent_size = 2 +insert_final_newline = true +trim_trailing_whitespace = true + +[*.md] +max_line_length = off +trim_trailing_whitespace = false diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..29b8fb3 --- /dev/null +++ b/.gitignore @@ -0,0 +1,51 @@ +# See https://docs.github.com/en/get-started/getting-started-with-git/ignoring-files for more about ignoring files. + +# compiled output +dist +tmp +out-tsc + +# dependencies +node_modules + +# IDEs and editors +/.idea +.project +.classpath +.c9/ +*.launch +.settings/ +*.sublime-workspace + +# IDE - VSCode +.vscode/* +!.vscode/settings.json +!.vscode/tasks.json +!.vscode/launch.json +!.vscode/extensions.json + +# misc +/.sass-cache +/connect.lock +/coverage +/libpeerconnection.log +npm-debug.log +yarn-error.log +testem.log +/typings + +# System Files +.DS_Store +Thumbs.db + +.nx/cache +.nx/workspace-data +.cursor/rules/nx-rules.mdc +.github/instructions/nx.instructions.md + +.angular + +*.sqlite +.vscode +.env* +!conf/postgres/.env.template \ No newline at end of file diff --git a/.prettierignore b/.prettierignore new file mode 100644 index 0000000..113709c --- /dev/null +++ b/.prettierignore @@ -0,0 +1,6 @@ +# Add files here to ignore them from prettier formatting +/dist +/coverage +/.nx/cache +/.nx/workspace-data +.angular diff --git a/.prettierrc b/.prettierrc new file mode 100644 index 0000000..544138b --- /dev/null +++ b/.prettierrc @@ -0,0 +1,3 @@ +{ + "singleQuote": true +} diff --git a/LICENSE.md b/LICENSE.md new file mode 100644 index 0000000..d91706d --- /dev/null +++ b/LICENSE.md @@ -0,0 +1,42 @@ +# PolyForm Noncommercial License 1.0.0 + +Copyright (c) 2025 [Dein Name oder Deine Firma] + +Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the “Software”), to use, copy, modify, and distribute the Software, subject to the following conditions: + +--- + +## 1. Noncommercial Use Only +You may use the Software only for **noncommercial purposes**. +Noncommercial purposes include personal use, educational use, academic research, experimentation, and internal use within a noncommercial organization. + +**Commercial use is prohibited.** This includes, but is not limited to: +- offering the Software as a paid service (SaaS) +- selling, licensing, or sublicensing the Software +- using the Software in a product or service for which you receive compensation +- integrating the Software into commercial platforms, tools, or workflows + +--- + +## 2. Redistribution +Redistribution of modified or unmodified copies must include this LICENSE file. +Any redistribution remains subject to the noncommercial restriction. + +--- + +## 3. No Trademark Rights +This license does not grant you rights to use the names, logos, or trademarks of the licensor. + +--- + +## 4. Termination +Any violation of these terms will result in the automatic termination of your rights under this license. + +--- + +## 5. Disclaimer +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE, AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES, OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT, OR OTHERWISE, ARISING FROM, OUT OF, OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + +--- + +ℹ️ Weitere Infos zur Lizenz: [PolyForm Noncommercial 1.0.0](https://polyformproject.org/licenses/noncommercial/1.0.0/) diff --git a/OPEN_SOURCE_NOTICES.md b/OPEN_SOURCE_NOTICES.md new file mode 100644 index 0000000..35d6050 --- /dev/null +++ b/OPEN_SOURCE_NOTICES.md @@ -0,0 +1,18 @@ +# Open Source Notices + +This project builds upon the following open-source libraries: + +- Angular - MIT License, Copyright (c) Google LLC. +- NestJS - MIT License, Copyright (c) 2017-2025 Kamil Mysliwiec and Contributors. +- Nx - MIT License, Copyright (c) Nx, Inc. +- Bootstrap - MIT License, Copyright (c) The Bootstrap Authors. +- Lucide (including lucide-angular) - ISC License, Copyright (c) Lucide Contributors. +- RxJS - Apache License 2.0, Copyright (c) Google LLC and contributors. +- sharp - Apache License 2.0, Copyright (c) Lovell Fuller and contributors. +- bwip-js - MIT License, Copyright (c) Metafloor. +- qrcode - MIT License, Copyright (c) Soldair (Ryan Day). +- sanitize-html - MIT License, Copyright (c) Apostrophe Technologies. +- marked - MIT License, Copyright (c) Christopher Jeffrey. +- uuid - MIT License, Copyright (c) 2010-2025 Robert Kieffer and Contributors. + +Additional dependencies are documented in the project package manifests. diff --git a/apps/server-e2e/eslint.config.mjs b/apps/server-e2e/eslint.config.mjs new file mode 100644 index 0000000..b7f6277 --- /dev/null +++ b/apps/server-e2e/eslint.config.mjs @@ -0,0 +1,3 @@ +import baseConfig from '../../eslint.config.mjs'; + +export default [...baseConfig]; diff --git a/apps/server-e2e/jest.config.ts b/apps/server-e2e/jest.config.ts new file mode 100644 index 0000000..3da0016 --- /dev/null +++ b/apps/server-e2e/jest.config.ts @@ -0,0 +1,18 @@ +export default { + displayName: 'server-e2e', + preset: '../../jest.preset.js', + globalSetup: '/src/support/global-setup.ts', + globalTeardown: '/src/support/global-teardown.ts', + setupFiles: ['/src/support/test-setup.ts'], + testEnvironment: 'node', + transform: { + '^.+\\.[tj]s$': [ + 'ts-jest', + { + tsconfig: '/tsconfig.spec.json', + }, + ], + }, + moduleFileExtensions: ['ts', 'js', 'html'], + coverageDirectory: '../../coverage/server-e2e', +}; diff --git a/apps/server-e2e/project.json b/apps/server-e2e/project.json new file mode 100644 index 0000000..eacf740 --- /dev/null +++ b/apps/server-e2e/project.json @@ -0,0 +1,17 @@ +{ + "name": "server-e2e", + "$schema": "../../node_modules/nx/schemas/project-schema.json", + "projectType": "application", + "implicitDependencies": ["server"], + "targets": { + "e2e": { + "executor": "@nx/jest:jest", + "outputs": ["{workspaceRoot}/coverage/{e2eProjectRoot}"], + "options": { + "jestConfig": "apps/server-e2e/jest.config.ts", + "passWithNoTests": true + }, + "dependsOn": ["server:build", "server:serve"] + } + } +} diff --git a/apps/server-e2e/src/server/server.spec.ts b/apps/server-e2e/src/server/server.spec.ts new file mode 100644 index 0000000..e8ac2a6 --- /dev/null +++ b/apps/server-e2e/src/server/server.spec.ts @@ -0,0 +1,10 @@ +import axios from 'axios'; + +describe('GET /api', () => { + it('should return a message', async () => { + const res = await axios.get(`/api`); + + expect(res.status).toBe(200); + expect(res.data).toEqual({ message: 'Hello API' }); + }); +}); diff --git a/apps/server-e2e/src/support/global-setup.ts b/apps/server-e2e/src/support/global-setup.ts new file mode 100644 index 0000000..76a5879 --- /dev/null +++ b/apps/server-e2e/src/support/global-setup.ts @@ -0,0 +1,16 @@ +import { waitForPortOpen } from '@nx/node/utils'; + +/* eslint-disable */ +var __TEARDOWN_MESSAGE__: string; + +module.exports = async function () { + // Start services that that the app needs to run (e.g. database, docker-compose, etc.). + console.log('\nSetting up...\n'); + + const host = process.env.HOST ?? 'localhost'; + const port = process.env.PORT ? Number(process.env.PORT) : 3000; + await waitForPortOpen(port, { host }); + + // Hint: Use `globalThis` to pass variables to global teardown. + globalThis.__TEARDOWN_MESSAGE__ = '\nTearing down...\n'; +}; diff --git a/apps/server-e2e/src/support/global-teardown.ts b/apps/server-e2e/src/support/global-teardown.ts new file mode 100644 index 0000000..a28dd11 --- /dev/null +++ b/apps/server-e2e/src/support/global-teardown.ts @@ -0,0 +1,10 @@ +import { killPort } from '@nx/node/utils'; +/* eslint-disable */ + +module.exports = async function () { + // Put clean up logic here (e.g. stopping services, docker-compose, etc.). + // Hint: `globalThis` is shared between setup and teardown. + const port = process.env.PORT ? Number(process.env.PORT) : 3000; + await killPort(port); + console.log(globalThis.__TEARDOWN_MESSAGE__); +}; diff --git a/apps/server-e2e/src/support/test-setup.ts b/apps/server-e2e/src/support/test-setup.ts new file mode 100644 index 0000000..c185541 --- /dev/null +++ b/apps/server-e2e/src/support/test-setup.ts @@ -0,0 +1,9 @@ +/* eslint-disable */ +import axios from 'axios'; + +module.exports = async function () { + // Configure axios for tests to use. + const host = process.env.HOST ?? 'localhost'; + const port = process.env.PORT ?? '3000'; + axios.defaults.baseURL = `http://${host}:${port}`; +}; diff --git a/apps/server-e2e/tsconfig.json b/apps/server-e2e/tsconfig.json new file mode 100644 index 0000000..ed633e1 --- /dev/null +++ b/apps/server-e2e/tsconfig.json @@ -0,0 +1,13 @@ +{ + "extends": "../../tsconfig.base.json", + "files": [], + "include": [], + "references": [ + { + "path": "./tsconfig.spec.json" + } + ], + "compilerOptions": { + "esModuleInterop": true + } +} diff --git a/apps/server-e2e/tsconfig.spec.json b/apps/server-e2e/tsconfig.spec.json new file mode 100644 index 0000000..d7f9cf2 --- /dev/null +++ b/apps/server-e2e/tsconfig.spec.json @@ -0,0 +1,9 @@ +{ + "extends": "./tsconfig.json", + "compilerOptions": { + "outDir": "../../dist/out-tsc", + "module": "commonjs", + "types": ["jest", "node"] + }, + "include": ["jest.config.ts", "src/**/*.ts"] +} diff --git a/apps/server/README.md b/apps/server/README.md new file mode 100644 index 0000000..ea362de --- /dev/null +++ b/apps/server/README.md @@ -0,0 +1,44 @@ +# Server (NestJS) – Sims + +Backend-Services für Barcodes/GS1, QR-Codes, Watermarking, Utility-Endpunkte und Signpacks. Läuft mit NestJS, globalem Prefix `/api` und Swagger-UI unter `/api`. + +## Schnellstart + +- Install: `npm install` +- Dev-Start: `nx run server:serve` oder `npm run start` +- Swagger: `http://localhost:3000/api` +- Globales Prefix: alle Routen unter `/api/...` + +## Umgebung (ENV) + +- `PORT` (default `3000`) +- `TYPEORM_URL` Postgres-URL (optional). Wenn gesetzt → Postgres; sonst SQLite. +- `TYPEORM_DB` Pfad der SQLite-DB (default `./signpacks.sqlite`) +- `FILE_MAX_BYTES` Upload-Limit für Signpacks (default 25 MiB) +- Throttling: per `@nestjs/throttler` konfiguriert; Standard 100 req/60s pro IP/API-Key. + +## Features / Routen + +- Barcode: `/api/barcode` – Standardbarcodes (PNG/SVG), GS1 (PNG/SVG), Registry +- QR: `/api/qr` – QR aus diversen Datentypen (PNG/SVG) +- Watermark: `/api/watermark/apply` – Wasserzeichen (Logo/Text) via multipart/form-data +- Utils: `/api/utils` – Echo/IDs/Slugify/Hash/Markdown +- Signpacks: `/api/signpacks` – Upload/Verwaltung signierter Dateien mit Token + +Details pro Modul: siehe je Modul-README unter `apps/server/src/app//README.md`. + +## Ordnerstruktur (Auszug) + +- `src/main.ts` – Bootstrap mit globalem Prefix und Swagger +- `src/app/app.module.ts` – Module, Throttling, Config, TypeORM +- `src/app/barcode` – Barcode/GS1 +- `src/app/qr` – QR Codes +- `src/app/watermark` – Bild-Wasserzeichen +- `src/app/utils` – Dienstprogramme +- `src/app/signpack` – Signpack-Flow inkl. Downloads +- `src/assets/watermark` – optionales Default-Logo + +## Proxy/CORS + +- CORS ist aktiviert. Frontend spricht i. d. R. gegen `/api` (Relativ-URL) oder gegen `API_BASE_URL` des Frontends. + diff --git a/apps/server/entrypoint.backend.sh b/apps/server/entrypoint.backend.sh new file mode 100644 index 0000000..4576c17 --- /dev/null +++ b/apps/server/entrypoint.backend.sh @@ -0,0 +1,48 @@ +#!/bin/sh +set -e + +# .env aus /app/.env laden (Docker mountet es als Volume dorthin) +# Nur wenn vorhanden – in reinen ENV-Var-Setups (CI, k8s) nicht nötig +if [ -f "/app/.env" ]; then + set -o allexport + . /app/.env + set +o allexport +fi + +echo "[sims] Running database migrations..." +node -e " +const { DataSource } = require('typeorm'); +const path = require('path'); + +const ds = new DataSource({ + type: 'postgres', + host: process.env.TYPEORM_HOST || 'localhost', + port: Number(process.env.TYPEORM_PORT || 5432), + username: process.env.TYPEORM_USERNAME || 'user', + password: process.env.TYPEORM_PASSWORD || 'CHANGEME', + database: process.env.TYPEORM_DATABASE || 'sims', + entities: [], + migrations: [path.join(__dirname, 'migrations', '*.js')], + synchronize: false, + migrationsRun: false, +}); + +ds.initialize() + .then(() => ds.runMigrations()) + .then((ran) => { + if (ran.length === 0) { + console.log('[sims] No pending migrations.'); + } else { + console.log('[sims] Migrations applied: ' + ran.map(function(m){ return m.name; }).join(', ')); + } + return ds.destroy(); + }) + .then(() => process.exit(0)) + .catch(function(e) { + console.error('[sims] Migration failed:', e.message); + process.exit(1); + }); +" + +echo "[sims] Starting app..." +node main.js diff --git a/apps/server/eslint.config.mjs b/apps/server/eslint.config.mjs new file mode 100644 index 0000000..b7f6277 --- /dev/null +++ b/apps/server/eslint.config.mjs @@ -0,0 +1,3 @@ +import baseConfig from '../../eslint.config.mjs'; + +export default [...baseConfig]; diff --git a/apps/server/jest.config.ts b/apps/server/jest.config.ts new file mode 100644 index 0000000..f846e98 --- /dev/null +++ b/apps/server/jest.config.ts @@ -0,0 +1,21 @@ +export default { + displayName: 'server', + preset: '../../jest.preset.js', + testEnvironment: 'node', + transform: { + '^.+\\.[tj]s$': ['ts-jest', { tsconfig: '/tsconfig.spec.json', isolatedModules: true }], + }, + moduleFileExtensions: ['ts', 'js', 'html'], + coverageDirectory: '../../coverage/apps/server', + testEnvironmentOptions: { + customExportConditions: ['require', 'default', 'node'], + }, + transformIgnorePatterns: [ + 'node_modules/(?!(@scure|@noble)/)', + ], + moduleNameMapper: { + // Replace the native sharp module with a pure-JS stub so watermark tests + // run in any CI environment without requiring the platform binary. + '^sharp$': '/src/__mocks__/sharp.ts', + }, +}; diff --git a/apps/server/package-lock.json b/apps/server/package-lock.json new file mode 100644 index 0000000..e4693bb --- /dev/null +++ b/apps/server/package-lock.json @@ -0,0 +1,280 @@ +{ + "name": "server", + "lockfileVersion": 3, + "requires": true, + "packages": { + "": { + "dependencies": { + "jsonwebtoken": "^9.0.3", + "otplib": "^13.3.0" + }, + "devDependencies": { + "@types/jsonwebtoken": "^9.0.10" + } + }, + "node_modules/@noble/hashes": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/@noble/hashes/-/hashes-2.0.1.tgz", + "integrity": "sha512-XlOlEbQcE9fmuXxrVTXCTlG2nlRXa9Rj3rr5Ue/+tX+nmkgbX720YHh0VR3hBF9xDvwnb8D2shVGOwNx+ulArw==", + "license": "MIT", + "engines": { + "node": ">= 20.19.0" + }, + "funding": { + "url": "https://paulmillr.com/funding/" + } + }, + "node_modules/@otplib/core": { + "version": "13.3.0", + "resolved": "https://registry.npmjs.org/@otplib/core/-/core-13.3.0.tgz", + "integrity": "sha512-pnQDOuCmFVeF/XnboJq9TOJgLoo2idNPJKMymOF8vGqJJ+ReKRYM9bUGjNPRWC0tHjMwu1TXbnzyBp494JgRag==", + "license": "MIT" + }, + "node_modules/@otplib/hotp": { + "version": "13.3.0", + "resolved": "https://registry.npmjs.org/@otplib/hotp/-/hotp-13.3.0.tgz", + "integrity": "sha512-XJMZGz2bg4QJwK7ulvl1GUI2VMn/flaIk/E/BTKAejHsX2kUtPF1bRhlZ2+elq8uU5Fs9Z9FHcQK2CPZNQbbUQ==", + "license": "MIT", + "dependencies": { + "@otplib/core": "13.3.0", + "@otplib/uri": "13.3.0" + } + }, + "node_modules/@otplib/plugin-base32-scure": { + "version": "13.3.0", + "resolved": "https://registry.npmjs.org/@otplib/plugin-base32-scure/-/plugin-base32-scure-13.3.0.tgz", + "integrity": "sha512-/jYbL5S6GB0Ie3XGEWtLIr9s5ZICl/BfmNL7+8/W7usZaUU4GiyLd2S+JGsNCslPyqNekSudD864nDAvRI0s8w==", + "license": "MIT", + "dependencies": { + "@otplib/core": "13.3.0", + "@scure/base": "^2.0.0" + } + }, + "node_modules/@otplib/plugin-crypto-noble": { + "version": "13.3.0", + "resolved": "https://registry.npmjs.org/@otplib/plugin-crypto-noble/-/plugin-crypto-noble-13.3.0.tgz", + "integrity": "sha512-wmV+jBVncepgwv99G7Plrdzd0tHfbpXk2U+OD7MO7DzpDqOYEgOPi+IIneksJSTL8QvWdfi+uQEuhnER4fKouA==", + "license": "MIT", + "dependencies": { + "@noble/hashes": "^2.0.1", + "@otplib/core": "13.3.0" + } + }, + "node_modules/@otplib/totp": { + "version": "13.3.0", + "resolved": "https://registry.npmjs.org/@otplib/totp/-/totp-13.3.0.tgz", + "integrity": "sha512-XfjGNoN8d9S3Ove2j7AwkVV7+QDFsV7Lm7YwSiezNaHffkWtJ60aJYpmf+01dARdPST71U2ptueMsRJso4sq4A==", + "license": "MIT", + "dependencies": { + "@otplib/core": "13.3.0", + "@otplib/hotp": "13.3.0", + "@otplib/uri": "13.3.0" + } + }, + "node_modules/@otplib/uri": { + "version": "13.3.0", + "resolved": "https://registry.npmjs.org/@otplib/uri/-/uri-13.3.0.tgz", + "integrity": "sha512-3oh6nBXy+cm3UX9cxEAGZiDrfxHU2gfelYFV+XNCx+8dq39VaQVymwlU2yjPZiMAi/3agaUeEftf2RwM5F+Cyg==", + "license": "MIT", + "dependencies": { + "@otplib/core": "13.3.0" + } + }, + "node_modules/@scure/base": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/@scure/base/-/base-2.0.0.tgz", + "integrity": "sha512-3E1kpuZginKkek01ovG8krQ0Z44E3DHPjc5S2rjJw9lZn3KSQOs8S7wqikF/AH7iRanHypj85uGyxk0XAyC37w==", + "license": "MIT", + "funding": { + "url": "https://paulmillr.com/funding/" + } + }, + "node_modules/@types/jsonwebtoken": { + "version": "9.0.10", + "resolved": "https://registry.npmjs.org/@types/jsonwebtoken/-/jsonwebtoken-9.0.10.tgz", + "integrity": "sha512-asx5hIG9Qmf/1oStypjanR7iKTv0gXQ1Ov/jfrX6kS/EO0OFni8orbmGCn0672NHR3kXHwpAwR+B368ZGN/2rA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@types/ms": "*", + "@types/node": "*" + } + }, + "node_modules/@types/ms": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/@types/ms/-/ms-2.1.0.tgz", + "integrity": "sha512-GsCCIZDE/p3i96vtEqx+7dBUGXrc7zeSK3wwPHIaRThS+9OhWIXRqzs4d6k1SVU8g91DrNRWxWUGhp5KXQb2VA==", + "dev": true, + "license": "MIT" + }, + "node_modules/@types/node": { + "version": "25.3.5", + "resolved": "https://registry.npmjs.org/@types/node/-/node-25.3.5.tgz", + "integrity": "sha512-oX8xrhvpiyRCQkG1MFchB09f+cXftgIXb3a7UUa4Y3wpmZPw5tyZGTLWhlESOLq1Rq6oDlc8npVU2/9xiCuXMA==", + "dev": true, + "license": "MIT", + "dependencies": { + "undici-types": "~7.18.0" + } + }, + "node_modules/buffer-equal-constant-time": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/buffer-equal-constant-time/-/buffer-equal-constant-time-1.0.1.tgz", + "integrity": "sha512-zRpUiDwd/xk6ADqPMATG8vc9VPrkck7T07OIx0gnjmJAnHnTVXNQG3vfvWNuiZIkwu9KrKdA1iJKfsfTVxE6NA==", + "license": "BSD-3-Clause" + }, + "node_modules/ecdsa-sig-formatter": { + "version": "1.0.11", + "resolved": "https://registry.npmjs.org/ecdsa-sig-formatter/-/ecdsa-sig-formatter-1.0.11.tgz", + "integrity": "sha512-nagl3RYrbNv6kQkeJIpt6NJZy8twLB/2vtz6yN9Z4vRKHN4/QZJIEbqohALSgwKdnksuY3k5Addp5lg8sVoVcQ==", + "license": "Apache-2.0", + "dependencies": { + "safe-buffer": "^5.0.1" + } + }, + "node_modules/jsonwebtoken": { + "version": "9.0.3", + "resolved": "https://registry.npmjs.org/jsonwebtoken/-/jsonwebtoken-9.0.3.tgz", + "integrity": "sha512-MT/xP0CrubFRNLNKvxJ2BYfy53Zkm++5bX9dtuPbqAeQpTVe0MQTFhao8+Cp//EmJp244xt6Drw/GVEGCUj40g==", + "license": "MIT", + "dependencies": { + "jws": "^4.0.1", + "lodash.includes": "^4.3.0", + "lodash.isboolean": "^3.0.3", + "lodash.isinteger": "^4.0.4", + "lodash.isnumber": "^3.0.3", + "lodash.isplainobject": "^4.0.6", + "lodash.isstring": "^4.0.1", + "lodash.once": "^4.0.0", + "ms": "^2.1.1", + "semver": "^7.5.4" + }, + "engines": { + "node": ">=12", + "npm": ">=6" + } + }, + "node_modules/jwa": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/jwa/-/jwa-2.0.1.tgz", + "integrity": "sha512-hRF04fqJIP8Abbkq5NKGN0Bbr3JxlQ+qhZufXVr0DvujKy93ZCbXZMHDL4EOtodSbCWxOqR8MS1tXA5hwqCXDg==", + "license": "MIT", + "dependencies": { + "buffer-equal-constant-time": "^1.0.1", + "ecdsa-sig-formatter": "1.0.11", + "safe-buffer": "^5.0.1" + } + }, + "node_modules/jws": { + "version": "4.0.1", + "resolved": "https://registry.npmjs.org/jws/-/jws-4.0.1.tgz", + "integrity": "sha512-EKI/M/yqPncGUUh44xz0PxSidXFr/+r0pA70+gIYhjv+et7yxM+s29Y+VGDkovRofQem0fs7Uvf4+YmAdyRduA==", + "license": "MIT", + "dependencies": { + "jwa": "^2.0.1", + "safe-buffer": "^5.0.1" + } + }, + "node_modules/lodash.includes": { + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/lodash.includes/-/lodash.includes-4.3.0.tgz", + "integrity": "sha512-W3Bx6mdkRTGtlJISOvVD/lbqjTlPPUDTMnlXZFnVwi9NKJ6tiAk6LVdlhZMm17VZisqhKcgzpO5Wz91PCt5b0w==", + "license": "MIT" + }, + "node_modules/lodash.isboolean": { + "version": "3.0.3", + "resolved": "https://registry.npmjs.org/lodash.isboolean/-/lodash.isboolean-3.0.3.tgz", + "integrity": "sha512-Bz5mupy2SVbPHURB98VAcw+aHh4vRV5IPNhILUCsOzRmsTmSQ17jIuqopAentWoehktxGd9e/hbIXq980/1QJg==", + "license": "MIT" + }, + "node_modules/lodash.isinteger": { + "version": "4.0.4", + "resolved": "https://registry.npmjs.org/lodash.isinteger/-/lodash.isinteger-4.0.4.tgz", + "integrity": "sha512-DBwtEWN2caHQ9/imiNeEA5ys1JoRtRfY3d7V9wkqtbycnAmTvRRmbHKDV4a0EYc678/dia0jrte4tjYwVBaZUA==", + "license": "MIT" + }, + "node_modules/lodash.isnumber": { + "version": "3.0.3", + "resolved": "https://registry.npmjs.org/lodash.isnumber/-/lodash.isnumber-3.0.3.tgz", + "integrity": "sha512-QYqzpfwO3/CWf3XP+Z+tkQsfaLL/EnUlXWVkIk5FUPc4sBdTehEqZONuyRt2P67PXAk+NXmTBcc97zw9t1FQrw==", + "license": "MIT" + }, + "node_modules/lodash.isplainobject": { + "version": "4.0.6", + "resolved": "https://registry.npmjs.org/lodash.isplainobject/-/lodash.isplainobject-4.0.6.tgz", + "integrity": "sha512-oSXzaWypCMHkPC3NvBEaPHf0KsA5mvPrOPgQWDsbg8n7orZ290M0BmC/jgRZ4vcJ6DTAhjrsSYgdsW/F+MFOBA==", + "license": "MIT" + }, + "node_modules/lodash.isstring": { + "version": "4.0.1", + "resolved": "https://registry.npmjs.org/lodash.isstring/-/lodash.isstring-4.0.1.tgz", + "integrity": "sha512-0wJxfxH1wgO3GrbuP+dTTk7op+6L41QCXbGINEmD+ny/G/eCqGzxyCsh7159S+mgDDcoarnBw6PC1PS5+wUGgw==", + "license": "MIT" + }, + "node_modules/lodash.once": { + "version": "4.1.1", + "resolved": "https://registry.npmjs.org/lodash.once/-/lodash.once-4.1.1.tgz", + "integrity": "sha512-Sb487aTOCr9drQVL8pIxOzVhafOjZN9UU54hiN8PU3uAiSV7lx1yYNpbNmex2PK6dSJoNTSJUUswT651yww3Mg==", + "license": "MIT" + }, + "node_modules/ms": { + "version": "2.1.3", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.3.tgz", + "integrity": "sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==", + "license": "MIT" + }, + "node_modules/otplib": { + "version": "13.3.0", + "resolved": "https://registry.npmjs.org/otplib/-/otplib-13.3.0.tgz", + "integrity": "sha512-VYMKyyDG8yt2q+z58sz54/EIyTh7+tyMrjeemR44iVh5+dkKtIs57irTqxjH+IkAL1uMmG1JIFhG5CxTpqdU5g==", + "license": "MIT", + "dependencies": { + "@otplib/core": "13.3.0", + "@otplib/hotp": "13.3.0", + "@otplib/plugin-base32-scure": "13.3.0", + "@otplib/plugin-crypto-noble": "13.3.0", + "@otplib/totp": "13.3.0", + "@otplib/uri": "13.3.0" + } + }, + "node_modules/safe-buffer": { + "version": "5.2.1", + "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.2.1.tgz", + "integrity": "sha512-rp3So07KcdmmKbGvgaNxQSJr7bGVSVk5S9Eq1F+ppbRo70+YeaDxkw5Dd8NPN+GD6bjnYm2VuPuCXmpuYvmCXQ==", + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/feross" + }, + { + "type": "patreon", + "url": "https://www.patreon.com/feross" + }, + { + "type": "consulting", + "url": "https://feross.org/support" + } + ], + "license": "MIT" + }, + "node_modules/semver": { + "version": "7.7.4", + "resolved": "https://registry.npmjs.org/semver/-/semver-7.7.4.tgz", + "integrity": "sha512-vFKC2IEtQnVhpT78h1Yp8wzwrf8CM+MzKMHGJZfBtzhZNycRFnXsHk6E5TxIkkMsgNS7mdX3AGB7x2QM2di4lA==", + "license": "ISC", + "bin": { + "semver": "bin/semver.js" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/undici-types": { + "version": "7.18.2", + "resolved": "https://registry.npmjs.org/undici-types/-/undici-types-7.18.2.tgz", + "integrity": "sha512-AsuCzffGHJybSaRrmr5eHr81mwJU3kjw6M+uprWvCXiNeN9SOGwQ3Jn8jb8m3Z6izVgknn1R0FTCEAP2QrLY/w==", + "dev": true, + "license": "MIT" + } + } +} diff --git a/apps/server/package.json b/apps/server/package.json new file mode 100644 index 0000000..0e502e3 --- /dev/null +++ b/apps/server/package.json @@ -0,0 +1,9 @@ +{ + "dependencies": { + "jsonwebtoken": "^9.0.3", + "otplib": "^13.3.0" + }, + "devDependencies": { + "@types/jsonwebtoken": "^9.0.10" + } +} diff --git a/apps/server/project.json b/apps/server/project.json new file mode 100644 index 0000000..1f59c91 --- /dev/null +++ b/apps/server/project.json @@ -0,0 +1,73 @@ +{ + "name": "server", + "$schema": "../../node_modules/nx/schemas/project-schema.json", + "sourceRoot": "apps/server/src", + "projectType": "application", + "tags": [], + "targets": { + "build": { + "executor": "nx:run-commands", + "options": { + "command": "webpack-cli build", + "args": ["--node-env=production"], + "cwd": "apps/server" + }, + "configurations": { + "development": { + "args": ["--node-env=development"] + } + } + }, + "prune-lockfile": { + "dependsOn": ["build"], + "cache": true, + "executor": "@nx/js:prune-lockfile", + "outputs": [ + "{workspaceRoot}/dist/apps/server/package.json", + "{workspaceRoot}/dist/apps/server/package-lock.json" + ], + "options": { + "buildTarget": "build" + } + }, + "copy-workspace-modules": { + "dependsOn": ["build"], + "cache": true, + "outputs": ["{workspaceRoot}/dist/apps/server/workspace_modules"], + "executor": "@nx/js:copy-workspace-modules", + "options": { + "buildTarget": "build" + } + }, + "prune": { + "dependsOn": ["prune-lockfile", "copy-workspace-modules"], + "executor": "nx:noop" + }, + "serve": { + "continuous": true, + "executor": "@nx/js:node", + "defaultConfiguration": "development", + "dependsOn": ["build"], + "options": { + "buildTarget": "server:build", + "runBuildTargetDependencies": false + }, + "configurations": { + "development": { + "buildTarget": "server:build:development" + }, + "production": { + "buildTarget": "server:build:production" + } + } + }, + "test": { + "executor": "@nx/jest:jest", + "outputs": ["{workspaceRoot}/coverage/{projectRoot}"], + "options": { + "jestConfig": "apps/server/jest.config.ts", + "passWithNoTests": true + } + } + } +} diff --git a/apps/server/src/app/app.controller.spec.ts b/apps/server/src/app/app.controller.spec.ts new file mode 100644 index 0000000..bc6c2bf --- /dev/null +++ b/apps/server/src/app/app.controller.spec.ts @@ -0,0 +1,22 @@ +import { Test, TestingModule } from '@nestjs/testing'; +import { AppController } from './app.controller'; +import { AppService } from './app.service'; + +describe('AppController', () => { + let appController: AppController; + + beforeEach(async () => { + const app: TestingModule = await Test.createTestingModule({ + controllers: [AppController], + providers: [AppService], + }).compile(); + + appController = app.get(AppController); + }); + + describe('root', () => { + it('should return current API version', () => { + expect(appController.getVersion()).toBe('V1'); + }); + }); +}); diff --git a/apps/server/src/app/app.controller.ts b/apps/server/src/app/app.controller.ts new file mode 100644 index 0000000..c70d51b --- /dev/null +++ b/apps/server/src/app/app.controller.ts @@ -0,0 +1,12 @@ +import { Controller, Get } from '@nestjs/common'; +import { AppService } from './app.service'; + +@Controller() +export class AppController { + constructor(private readonly appService: AppService) {} + + @Get() + getVersion(): string { + return this.appService.getVersion(); + } +} diff --git a/apps/server/src/app/app.module.ts b/apps/server/src/app/app.module.ts new file mode 100644 index 0000000..d0501b8 --- /dev/null +++ b/apps/server/src/app/app.module.ts @@ -0,0 +1,27 @@ +import { Module } from '@nestjs/common'; +import { ConfigModule } from '@nestjs/config'; +import { AppController } from './app.controller'; +import { AppService } from './app.service'; +import { SharedModule } from './common/shared.module'; +import { AppConfigModule } from './config/app/config.module'; +import { SqliteConfigModule } from './config/database/sqlite/config.module'; +import { ArticleModule } from './models/article/article.module'; +import { BillModule } from './models/bills/bill.module'; +import { CustomerModule } from './models/customer/customer.module'; +import { SqliteDatabaseProviderModule } from './providers/database/sqlite/provider.module'; + +const ENV = process.env.NODE_ENV; +@Module({ + imports: [ + AppConfigModule, + SqliteConfigModule, + SqliteDatabaseProviderModule, + ArticleModule, + CustomerModule, + BillModule, + SharedModule + ], + controllers: [AppController], + providers: [AppService], +}) +export class AppModule {} diff --git a/apps/server/src/app/app.service.ts b/apps/server/src/app/app.service.ts new file mode 100644 index 0000000..30169a3 --- /dev/null +++ b/apps/server/src/app/app.service.ts @@ -0,0 +1,8 @@ +import { Injectable } from '@nestjs/common'; + +@Injectable() +export class AppService { + getVersion(): string { + return 'V1'; + } +} diff --git a/apps/server/src/app/common/base.service.ts b/apps/server/src/app/common/base.service.ts new file mode 100644 index 0000000..4f175c0 --- /dev/null +++ b/apps/server/src/app/common/base.service.ts @@ -0,0 +1,80 @@ +import { Injectable, NotFoundException } from '@nestjs/common'; +import { InjectRepository } from '@nestjs/typeorm'; +import { ModelRepository } from 'src/models/model.repository'; +import { DeepPartial } from 'typeorm'; +import { ModelEntity } from './serializers/model.serializer'; + +@Injectable() +export class BaseService { + + constructor( + private readonly repository: ModelRepository + ) {} + + get( + id: number, + relations: string[] = [], + throwsException = true, + ): Promise { + return this.repository.get(id, relations, throwsException); + } + + getAll( + relations: string[] = [], + throwsException = true, + ): Promise { + return this.repository.findAll({ + relations, + throwsException, + }); + } + + async delete(id: number, throwsException = true): Promise { + return this.repository + .delete(id) + .then((result) => { + if (throwsException && (!result.affected || result.affected === 0)) { + return Promise.reject(new NotFoundException('Model not found.')); + } + return Promise.resolve(true); + }) + .catch((error) => Promise.reject(error)); + } + + + async getByName( + name: string, + relations: string[] = [], + throwsException = false, + ): Promise { + return this.repository + .findOne({ + where: { name: name }, + relations, + }) + .then((entity) => { + if (!entity && throwsException) { + return Promise.reject(new NotFoundException('Model not found.')); + } + + return Promise.resolve( + entity ? this.repository.transform(entity) : null, + ); + }) + .catch((error) => Promise.reject(error)); + } + + async create(inputs: DeepPartial): Promise { + return await this.repository.createEntity(inputs); + } + + async update( + id: number, + inputs: DeepPartial, + ): Promise { + return await this.repository.updateEntity( + id, + inputs, + ); + } +} diff --git a/apps/server/src/app/common/decorators/apires.decorator.ts b/apps/server/src/app/common/decorators/apires.decorator.ts new file mode 100644 index 0000000..d10b2e5 --- /dev/null +++ b/apps/server/src/app/common/decorators/apires.decorator.ts @@ -0,0 +1,53 @@ +import { Type, applyDecorators } from '@nestjs/common'; +import { + ApiCreatedResponse, + ApiNotFoundResponse, + ApiOkResponse, + ApiUnprocessableEntityResponse, + getSchemaPath, +} from '@nestjs/swagger'; +import { ReS } from '../res.model'; + +export const ApiReS = >( + model: TModel, + description: string, + status?: number, +) => { + if (status === 201) { + return applyDecorators( + ApiCreatedResponse({ + description, + schema: { + allOf: [ + { $ref: getSchemaPath(ReS) }, + { + properties: { + data: { + $ref: getSchemaPath(model), + }, + }, + }, + ], + }, + }), + ); + } + + return applyDecorators( + ApiOkResponse({ + description, + schema: { + allOf: [ + { $ref: getSchemaPath(ReS) }, + { + properties: { + data: { + $ref: getSchemaPath(model), + }, + }, + }, + ], + }, + }), + ); +}; diff --git a/apps/server/src/app/common/decorators/isset.decorator.ts b/apps/server/src/app/common/decorators/isset.decorator.ts new file mode 100644 index 0000000..0e612d9 --- /dev/null +++ b/apps/server/src/app/common/decorators/isset.decorator.ts @@ -0,0 +1,24 @@ +import { registerDecorator, ValidationOptions, ValidationArguments } from 'class-validator'; + +export function IsSet( + condition: (object: any, value: any) => boolean, + validationOptions?: ValidationOptions) { + return function (object: Object, propertyName: string) { + registerDecorator({ + name: 'isSet', + target: object.constructor, + propertyName: propertyName, + constraints: [condition], + options: validationOptions, + validator: { + validate(value: any, args: ValidationArguments) { + const [relatedPropertyName] = args.constraints; + console.log(value); + console.log(args.object); + const relatedValue = relatedPropertyName(args.object); + return !relatedValue; + }, + }, + }); + }; +} \ No newline at end of file diff --git a/apps/server/src/app/common/dto/id.dto.ts b/apps/server/src/app/common/dto/id.dto.ts new file mode 100644 index 0000000..4165ec2 --- /dev/null +++ b/apps/server/src/app/common/dto/id.dto.ts @@ -0,0 +1,13 @@ +import { ApiProperty } from '@nestjs/swagger'; +import { Type } from 'class-transformer'; +import { IsNotEmpty, IsNumber, IsOptional, Length, ValidateNested } from 'class-validator'; + +export class IdDto { + @ApiProperty({ + example: '1', + description: 'ID', + }) + @IsNotEmpty() + @IsNumber() + id: number; +} diff --git a/apps/server/src/app/common/dto/time-range.dto.ts b/apps/server/src/app/common/dto/time-range.dto.ts new file mode 100644 index 0000000..0c14c15 --- /dev/null +++ b/apps/server/src/app/common/dto/time-range.dto.ts @@ -0,0 +1,13 @@ +import { ApiProperty } from '@nestjs/swagger'; +import { Type } from 'class-transformer'; +import { IsDateString, IsNotEmpty, IsNumber, IsOptional, Length, ValidateNested } from 'class-validator'; + +export class TimeRangeDto { + + @IsDateString() + from: string; + + @IsDateString() + to: string; + +} diff --git a/apps/server/src/app/common/enums/errorcodes.enum.ts b/apps/server/src/app/common/enums/errorcodes.enum.ts new file mode 100644 index 0000000..3e2d899 --- /dev/null +++ b/apps/server/src/app/common/enums/errorcodes.enum.ts @@ -0,0 +1,3 @@ +export declare enum ErrorCodes { + UserNotInHeader = 'user not in token', +} diff --git a/apps/server/src/app/common/filters/errors.filter.ts b/apps/server/src/app/common/filters/errors.filter.ts new file mode 100644 index 0000000..4614eea --- /dev/null +++ b/apps/server/src/app/common/filters/errors.filter.ts @@ -0,0 +1,37 @@ +import { + ExceptionFilter, + Catch, + HttpException, + ArgumentsHost, + HttpStatus, +} from '@nestjs/common'; +import { ReE } from '../res.model'; + +@Catch() +export class ErrorFilter implements ExceptionFilter { + catch(error: Error, host: ArgumentsHost) { + const ctx = host.switchToHttp(); + const response = ctx.getResponse(); + const request = ctx.getRequest(); + const statusCode: number = + error instanceof HttpException + ? error.getStatus() + : HttpStatus.INTERNAL_SERVER_ERROR; + + let message; + console.error(error); + if (error instanceof HttpException) { + const errorMessage = (error.getResponse() as any)?.message; + message = Array.isArray(errorMessage) ? errorMessage : [errorMessage]; + } else { + message = [error.message]; + } + + const name = + error instanceof HttpException + ? (error.getResponse() as any)?.error || error.name + : error.name; + + response.status(statusCode).json(ReE.FromData(statusCode, name, message)); + } +} diff --git a/apps/server/src/app/common/pdfAnnotation/adler.svg b/apps/server/src/app/common/pdfAnnotation/adler.svg new file mode 100644 index 0000000..f32b05e --- /dev/null +++ b/apps/server/src/app/common/pdfAnnotation/adler.svg @@ -0,0 +1,242 @@ + + + + + + + image/svg+xml + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/apps/server/src/app/common/pdfAnnotation/fonts/ARIALN.TTF b/apps/server/src/app/common/pdfAnnotation/fonts/ARIALN.TTF new file mode 100644 index 0000000000000000000000000000000000000000..94907a3dfc67bedb2ba9f245afe628ae98c07f1f GIT binary patch literal 175956 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 z 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