diff --git a/.prettierignore b/.prettierignore index 8642881d2..a59f78c6d 100644 --- a/.prettierignore +++ b/.prettierignore @@ -1,2 +1,4 @@ /built/ /src/static/ +package.json +package-lock.json \ No newline at end of file diff --git a/package-lock.json b/package-lock.json index 56c353f49..4a83f8096 100644 --- a/package-lock.json +++ b/package-lock.json @@ -9,7 +9,7 @@ "version": "2.2.8", "license": "GPL-3.0", "dependencies": { - "@beabee/beabee-common": "^0.20.10", + "@beabee/beabee-common": "^0.21.0", "@captchafox/node": "^1.2.0", "@inquirer/prompts": "^3.3.0", "@sendgrid/mail": "^8.1.0", @@ -105,6 +105,7 @@ "rimraf": "^5.0.5", "sass": "1.57.*", "ts-jest": "^29.1.1", + "ts-node": "^10.9.2", "tsc-watch": "^6.0.4", "tsconfig": "^7.0.0", "typescript": "^5.3.3" @@ -851,9 +852,9 @@ "dev": true }, "node_modules/@beabee/beabee-common": { - "version": "0.20.10", - "resolved": "https://registry.npmjs.org/@beabee/beabee-common/-/beabee-common-0.20.10.tgz", - "integrity": "sha512-m3j0he0DATyxf582/nYMvV7wJGEqQCotTidQawU+PcJdJtDdmCmn9pMz3gVGRM0Lx7D4Pt2JWsxnzncM8H/f/g==", + "version": "0.21.0", + "resolved": "https://registry.npmjs.org/@beabee/beabee-common/-/beabee-common-0.21.0.tgz", + "integrity": "sha512-qzYgmd9mPa8faU+r26L0KtrTLck0NXkhENKDSbjXzGlWHMV/cPd78YMS4NR8qpBhcyAbGAyVkEffIAkpd1yHiQ==", "dependencies": { "date-fns": "^3.6.0" } @@ -880,6 +881,28 @@ "node": ">=0.1.90" } }, + "node_modules/@cspotcode/source-map-support": { + "version": "0.8.1", + "resolved": "https://registry.npmjs.org/@cspotcode/source-map-support/-/source-map-support-0.8.1.tgz", + "integrity": "sha512-IchNf6dN4tHoMFIn/7OE8LWZ19Y6q/67Bmf6vnGREv8RSbBVb9LPJxEcnwrcwX6ixSvaiGoomAUvu4YSxXrVgw==", + "devOptional": true, + "dependencies": { + "@jridgewell/trace-mapping": "0.3.9" + }, + "engines": { + "node": ">=12" + } + }, + "node_modules/@cspotcode/source-map-support/node_modules/@jridgewell/trace-mapping": { + "version": "0.3.9", + "resolved": "https://registry.npmjs.org/@jridgewell/trace-mapping/-/trace-mapping-0.3.9.tgz", + "integrity": "sha512-3Belt6tdc8bPgAtbcmdtNJlirVoTmEb5e2gC94PnkwEW9jI6CAHUeoG85tjWP5WquqfavoMtMwiG4P926ZKKuQ==", + "devOptional": true, + "dependencies": { + "@jridgewell/resolve-uri": "^3.0.3", + "@jridgewell/sourcemap-codec": "^1.4.10" + } + }, "node_modules/@dabh/diagnostics": { "version": "2.0.3", "resolved": "https://registry.npmjs.org/@dabh/diagnostics/-/diagnostics-2.0.3.tgz", @@ -1841,6 +1864,30 @@ "node": ">=10" } }, + "node_modules/@tsconfig/node10": { + "version": "1.0.11", + "resolved": "https://registry.npmjs.org/@tsconfig/node10/-/node10-1.0.11.tgz", + "integrity": "sha512-DcRjDCujK/kCk/cUe8Xz8ZSpm8mS3mNNpta+jGCA6USEDfktlNvm1+IuZ9eTcDbNk41BHwpHHeW+N1lKCz4zOw==", + "devOptional": true + }, + "node_modules/@tsconfig/node12": { + "version": "1.0.11", + "resolved": "https://registry.npmjs.org/@tsconfig/node12/-/node12-1.0.11.tgz", + "integrity": "sha512-cqefuRsh12pWyGsIoBKJA9luFu3mRxCA+ORZvA4ktLSzIuCUtWVxGIuXigEwO5/ywWFMZ2QEGKWvkZG1zDMTag==", + "devOptional": true + }, + "node_modules/@tsconfig/node14": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/@tsconfig/node14/-/node14-1.0.3.tgz", + "integrity": "sha512-ysT8mhdixWK6Hw3i1V2AeRqZ5WfXg1G43mqoYlM2nc6388Fq5jcXyr5mRsqViLx/GJYdoL0bfXD8nmF+Zn/Iow==", + "devOptional": true + }, + "node_modules/@tsconfig/node16": { + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/@tsconfig/node16/-/node16-1.0.4.tgz", + "integrity": "sha512-vxhUy4J8lyeyinH7Azl1pdd43GJhZH/tP2weN8TntQblOY+A0XbT8DJk1/oCPuOOyg/Ja757rG0CgHcWC8OfMA==", + "devOptional": true + }, "node_modules/@tsconfig/node20": { "version": "20.1.2", "resolved": "https://registry.npmjs.org/@tsconfig/node20/-/node20-20.1.2.tgz", @@ -2357,6 +2404,15 @@ "node": ">=0.4.0" } }, + "node_modules/acorn-walk": { + "version": "8.3.2", + "resolved": "https://registry.npmjs.org/acorn-walk/-/acorn-walk-8.3.2.tgz", + "integrity": "sha512-cjkyv4OtNCIeqhHrfS81QWXoCBPExR/J62oyEqepVw8WaQeSqpW2uhuLPh1m9eWhDuOo/jUXVTlifvesOWp/4A==", + "devOptional": true, + "engines": { + "node": ">=0.4.0" + } + }, "node_modules/ajv": { "version": "8.12.0", "resolved": "https://registry.npmjs.org/ajv/-/ajv-8.12.0.tgz", @@ -2516,6 +2572,12 @@ "integrity": "sha512-Xg+9RwCg/0p32teKdGMPTPnVXKD0w3DfHnFTficozsAgsvq2XenPJq/MYpzzQ/v8zrOyJn6Ds39VA4JIDwFfqw==", "dev": true }, + "node_modules/arg": { + "version": "4.1.3", + "resolved": "https://registry.npmjs.org/arg/-/arg-4.1.3.tgz", + "integrity": "sha512-58S9QDqG0Xx27YwPSt9fJxivjYl432YCwfDMfZ+71RAqUrZef7LrKQZ3LHLOwCS4FLNBplP533Zx895SeOCHvA==", + "devOptional": true + }, "node_modules/argparse": { "version": "1.0.10", "resolved": "https://registry.npmjs.org/argparse/-/argparse-1.0.10.tgz", @@ -4142,6 +4204,12 @@ "node": "^14.15.0 || ^16.10.0 || >=18.0.0" } }, + "node_modules/create-require": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/create-require/-/create-require-1.1.1.tgz", + "integrity": "sha512-dcKFX3jn0MpIaXjisoRvexIJVEKzaq7z2rZKxf+MSr9TkdmHmsU4m2lcLojrj/FHl8mk5VxMmYA+ftRkP/3oKQ==", + "devOptional": true + }, "node_modules/cross-spawn": { "version": "7.0.3", "resolved": "https://registry.npmjs.org/cross-spawn/-/cross-spawn-7.0.3.tgz", @@ -4558,6 +4626,15 @@ "node": ">=0.10.0" } }, + "node_modules/diff": { + "version": "4.0.2", + "resolved": "https://registry.npmjs.org/diff/-/diff-4.0.2.tgz", + "integrity": "sha512-58lmxKSA4BNyLz+HHMUzlOEpg09FV+ev6ZMe3vJihgdxzgcwZ8VoEEPmALCZG9LmqfVoNMMKpttIYTVG6uDY7A==", + "devOptional": true, + "engines": { + "node": ">=0.3.1" + } + }, "node_modules/diff-sequences": { "version": "29.6.3", "resolved": "https://registry.npmjs.org/diff-sequences/-/diff-sequences-29.6.3.tgz", @@ -8701,7 +8778,7 @@ "version": "1.3.6", "resolved": "https://registry.npmjs.org/make-error/-/make-error-1.3.6.tgz", "integrity": "sha512-s8UhlNe7vPKomQhC1qFelMokr/Sc3AgNbso3n74mVPA5LTZwkB9NlXf4XPamLxJE8h0gh73rM94xvwRT2CVInw==", - "dev": true + "devOptional": true }, "node_modules/make-iterator": { "version": "1.0.1", @@ -13170,6 +13247,61 @@ "integrity": "sha512-3wdGidZyq5PB084XLES5TpOSRA3wjXAlIWMhum2kRcv/41Sn2emQ0dycQW4uZXLejwKvg6EsvbdlVL+FYEct7A==", "dev": true }, + "node_modules/ts-node": { + "version": "10.9.2", + "resolved": "https://registry.npmjs.org/ts-node/-/ts-node-10.9.2.tgz", + "integrity": "sha512-f0FFpIdcHgn8zcPSbf1dRevwt047YMnaiJM3u2w2RewrB+fob/zePZcrOyQoLMMO7aBIddLcQIEK5dYjkLnGrQ==", + "devOptional": true, + "dependencies": { + "@cspotcode/source-map-support": "^0.8.0", + "@tsconfig/node10": "^1.0.7", + "@tsconfig/node12": "^1.0.7", + "@tsconfig/node14": "^1.0.0", + "@tsconfig/node16": "^1.0.2", + "acorn": "^8.4.1", + "acorn-walk": "^8.1.1", + "arg": "^4.1.0", + "create-require": "^1.1.0", + "diff": "^4.0.1", + "make-error": "^1.1.1", + "v8-compile-cache-lib": "^3.0.1", + "yn": "3.1.1" + }, + "bin": { + "ts-node": "dist/bin.js", + "ts-node-cwd": "dist/bin-cwd.js", + "ts-node-esm": "dist/bin-esm.js", + "ts-node-script": "dist/bin-script.js", + "ts-node-transpile-only": "dist/bin-transpile.js", + "ts-script": "dist/bin-script-deprecated.js" + }, + "peerDependencies": { + "@swc/core": ">=1.2.50", + "@swc/wasm": ">=1.2.50", + "@types/node": "*", + "typescript": ">=2.7" + }, + "peerDependenciesMeta": { + "@swc/core": { + "optional": true + }, + "@swc/wasm": { + "optional": true + } + } + }, + "node_modules/ts-node/node_modules/acorn": { + "version": "8.11.3", + "resolved": "https://registry.npmjs.org/acorn/-/acorn-8.11.3.tgz", + "integrity": "sha512-Y9rRfJG5jcKOE0CLisYbojUjIrIEE7AGMzA/Sm4BslANhbS+cDMpgBdcPT91oJ7OuJ9hYJBx59RjbhxVnrF8Xg==", + "devOptional": true, + "bin": { + "acorn": "bin/acorn" + }, + "engines": { + "node": ">=0.4.0" + } + }, "node_modules/tsc-watch": { "version": "6.0.4", "resolved": "https://registry.npmjs.org/tsc-watch/-/tsc-watch-6.0.4.tgz", @@ -13493,7 +13625,7 @@ "version": "5.3.3", "resolved": "https://registry.npmjs.org/typescript/-/typescript-5.3.3.tgz", "integrity": "sha512-pXWcraxM0uxAS+tN0AG/BF2TyqmHO014Z070UsJ+pFvYuRSq8KH8DmWpnbXe0pEPDHXZV3FcAbJkijJ5oNEnWw==", - "dev": true, + "devOptional": true, "bin": { "tsc": "bin/tsc", "tsserver": "bin/tsserver" @@ -13745,6 +13877,12 @@ "uuid": "dist/bin/uuid" } }, + "node_modules/v8-compile-cache-lib": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/v8-compile-cache-lib/-/v8-compile-cache-lib-3.0.1.tgz", + "integrity": "sha512-wa7YjyUGfNZngI/vtK0UHAN+lgDCxBPCylVXGp0zu59Fz5aiGtNXaq3DhIov063MorB+VfufLh3JlF2KdTK3xg==", + "devOptional": true + }, "node_modules/v8-to-istanbul": { "version": "9.2.0", "resolved": "https://registry.npmjs.org/v8-to-istanbul/-/v8-to-istanbul-9.2.0.tgz", @@ -14366,6 +14504,15 @@ "node": ">= 4.0.0" } }, + "node_modules/yn": { + "version": "3.1.1", + "resolved": "https://registry.npmjs.org/yn/-/yn-3.1.1.tgz", + "integrity": "sha512-Ux4ygGWsu2c7isFWe8Yu1YluJmqVhxqK2cLXNQA5AcC3QfbGNpM7fu0Y8b/z16pXLnFxZYvWhd3fhBY9DLmC6Q==", + "devOptional": true, + "engines": { + "node": ">=6" + } + }, "node_modules/yocto-queue": { "version": "0.1.0", "resolved": "https://registry.npmjs.org/yocto-queue/-/yocto-queue-0.1.0.tgz", diff --git a/package.json b/package.json index 17339181d..968bca9bc 100644 --- a/package.json +++ b/package.json @@ -24,6 +24,7 @@ "dev:api:watch": "tsc-watch -p tsconfig.build.json --noClear --onSuccess 'docker compose restart api_app router'", "dev:api:logs": "while true; do docker compose logs -f --tail=0 api_app; sleep 1; done", "build": "rimraf built/ && gulp build && tsc -p ./tsconfig.build.json", + "generate:index": "ts-node scripts/generate-index.ts", "watch": "tsc-watch -p tsconfig.build.json --noClear", "check": "concurrently npm:check:*", "check:tsc": "tsc --noEmit", @@ -34,7 +35,7 @@ "test": "jest --setupFiles dotenv/config" }, "dependencies": { - "@beabee/beabee-common": "^0.20.10", + "@beabee/beabee-common": "^0.21.0", "@captchafox/node": "^1.2.0", "@inquirer/prompts": "^3.3.0", "@sendgrid/mail": "^8.1.0", @@ -132,7 +133,8 @@ "ts-jest": "^29.1.1", "tsc-watch": "^6.0.4", "tsconfig": "^7.0.0", - "typescript": "^5.3.3" + "typescript": "^5.3.3", + "ts-node": "^10.9.2" }, "browserslist": [ "last 1 versions", @@ -153,4 +155,4 @@ "postcss": "8.4.32" } } -} +} \ No newline at end of file diff --git a/scripts/generate-index.ts b/scripts/generate-index.ts new file mode 100644 index 000000000..4ba94c51b --- /dev/null +++ b/scripts/generate-index.ts @@ -0,0 +1,41 @@ +// Simple script to generate index.ts files for each folder listed in the paths array + +const PATHS = [ + "./src/api/controllers", + "./src/api/decorators", + "./src/api/dto", + "./src/api/errors", + "./src/api/interceptors", + "./src/api/middlewares", + "./src/api/params", + "./src/api/transformers", + "./src/api/utils", + "./src/api/validators", + "./src/enums", + "./src/models", + "./src/type" +]; + +import { readdirSync, writeFileSync } from "fs"; + +const encoder = new TextEncoder(); + +const generateIndex = (paths: string[]) => { + for (const path of paths) { + const files = readdirSync(path); + // Sort files by file name + files.sort((a, b) => a.localeCompare(b)); + + let indexContent = ""; + + for (const file of files) { + if (file.endsWith(".ts") && file !== "index.ts") { + indexContent += `export * from "./${file.split(".")[0]}.js";\n`; + } + } + + writeFileSync(`${path}/index.ts`, encoder.encode(indexContent)); + } +}; + +generateIndex(PATHS); diff --git a/src/api/controllers/CalloutController.ts b/src/api/controllers/CalloutController.ts index 70611f30b..02ce315d9 100644 --- a/src/api/controllers/CalloutController.ts +++ b/src/api/controllers/CalloutController.ts @@ -55,7 +55,7 @@ import CalloutResponseTag from "@models/CalloutResponseTag"; import CalloutTag from "@models/CalloutTag"; import Contact from "@models/Contact"; -import { CalloutCaptcha } from "@enums/callout-captcha"; +import { CalloutCaptcha } from "@beabee/beabee-common"; import { AuthInfo } from "@type/auth-info"; diff --git a/src/api/controllers/ContentController.ts b/src/api/controllers/ContentController.ts index c7be096f3..9f779432e 100644 --- a/src/api/controllers/ContentController.ts +++ b/src/api/controllers/ContentController.ts @@ -4,23 +4,27 @@ import { Get, JsonController, Params, - Patch + Patch, + BadRequestError } from "routing-controllers"; import PartialBody from "@api/decorators/PartialBody"; import { - GetContactsContentDto, + GetContentContactsDto, GetContentDto, - GetEmailContentDto, - GetGeneralContentDto, - GetJoinContentDto, - GetJoinSetupContentDto, - GetProfileContentDto, - GetShareContentDto, - GetTelegramContentDto -} from "@api/dto/ContentDto"; + GetContentEmailDto, + GetContentGeneralDto, + GetContentJoinDto, + GetContentJoinSetupDto, + GetContentProfileDto, + GetContentShareDto, + GetContentPaymentDto, + GetContentTelegramDto +} from "@api/dto"; import { ContentParams } from "@api/params/ContentParams"; import ContentTransformer from "@api/transformers/ContentTransformer"; +import { stripeTaxRateUpdateOrCreateDefault } from "@core/lib/stripe"; +import OptionsService from "@core/services/OptionsService"; @JsonController("/content") export class ContentController { @@ -32,71 +36,95 @@ export class ContentController { @Authorized("admin") @Patch("/contacts") async updateContacts( - @PartialBody() data: GetContactsContentDto - ): Promise { - ContentTransformer.updateOne("contacts", data); + @PartialBody() data: GetContentContactsDto + ): Promise { + await ContentTransformer.updateOne("contacts", data); return ContentTransformer.fetchOne("contacts"); } @Authorized("admin") @Patch("/email") async updateEmail( - @PartialBody() data: GetEmailContentDto - ): Promise { - ContentTransformer.updateOne("email", data); + @PartialBody() data: GetContentEmailDto + ): Promise { + await ContentTransformer.updateOne("email", data); return ContentTransformer.fetchOne("email"); } @Authorized("admin") @Patch("/general") async updateGeneral( - @PartialBody() data: GetGeneralContentDto - ): Promise { - ContentTransformer.updateOne("general", data); + @PartialBody() data: GetContentGeneralDto + ): Promise { + await ContentTransformer.updateOne("general", data); return ContentTransformer.fetchOne("general"); } @Authorized("admin") @Patch("/join") async updateJoin( - @PartialBody() data: GetJoinContentDto - ): Promise { - ContentTransformer.updateOne("join", data); + @PartialBody() data: GetContentJoinDto + ): Promise { + await ContentTransformer.updateOne("join", data); return ContentTransformer.fetchOne("join"); } @Authorized("admin") @Patch("/join/setup") async updateJoinSetup( - @PartialBody() data: GetJoinSetupContentDto - ): Promise { - ContentTransformer.updateOne("join/setup", data); + @PartialBody() data: GetContentJoinSetupDto + ): Promise { + await ContentTransformer.updateOne("join/setup", data); return ContentTransformer.fetchOne("join/setup"); } @Authorized("admin") @Patch("/profile") async updateProfile( - @PartialBody() data: GetProfileContentDto - ): Promise { - ContentTransformer.updateOne("profile", data); + @PartialBody() data: GetContentProfileDto + ): Promise { + await ContentTransformer.updateOne("profile", data); return ContentTransformer.fetchOne("profile"); } @Authorized("admin") @Patch("/share") async updateShare( - @PartialBody() data: GetShareContentDto - ): Promise { - ContentTransformer.updateOne("share", data); + @PartialBody() data: GetContentShareDto + ): Promise { + await ContentTransformer.updateOne("share", data); return ContentTransformer.fetchOne("share"); } + @Authorized("admin") + @Patch("/payment") + async updatePayment( + @PartialBody() data: GetContentPaymentDto + ): Promise { + if (data.taxRateEnabled && data.taxRate === undefined) { + throw new BadRequestError( + "taxRate must be provided when taxRateEnabled is true" + ); + } + + const taxRateObj = await stripeTaxRateUpdateOrCreateDefault( + { + active: data.taxRateEnabled, + percentage: data.taxRate + }, + OptionsService.getText("tax-rate-stripe-default-id") + ); + await OptionsService.set("tax-rate-stripe-default-id", taxRateObj.id); + + await ContentTransformer.updateOne("payment", data); + return ContentTransformer.fetchOne("payment"); + } + @Authorized("admin") @Patch("/telegram") async updateTelegram( - @PartialBody() data: GetTelegramContentDto - ): Promise { + @PartialBody() data: GetContentTelegramDto + ): Promise { await ContentTransformer.updateOne("telegram", data); return ContentTransformer.fetchOne("telegram"); } diff --git a/src/api/controllers/index.ts b/src/api/controllers/index.ts new file mode 100644 index 000000000..5e0543033 --- /dev/null +++ b/src/api/controllers/index.ts @@ -0,0 +1,17 @@ +export * from "./ApiKeyController.js"; +export * from "./AuthController.js"; +export * from "./CalloutController.js"; +export * from "./CalloutResponseCommentController.js"; +export * from "./CalloutResponseController.js"; +export * from "./ContactController.js"; +export * from "./ContentController.js"; +export * from "./EmailController.js"; +export * from "./NoticeController.js"; +export * from "./PaymentController.js"; +export * from "./ResetDeviceController.js"; +export * from "./ResetPasswordController.js"; +export * from "./RootController.js"; +export * from "./SegmentController.js"; +export * from "./SignupController.js"; +export * from "./StatsController.js"; +export * from "./UploadController.js"; diff --git a/src/api/decorators/index.ts b/src/api/decorators/index.ts new file mode 100644 index 000000000..704eb77d2 --- /dev/null +++ b/src/api/decorators/index.ts @@ -0,0 +1,4 @@ +export * from "./CalloutId.js"; +export * from "./CurrentAuth.js"; +export * from "./PartialBody.js"; +export * from "./TargetUser.js"; diff --git a/src/api/dto/CalloutDto.ts b/src/api/dto/CalloutDto.ts index c5e2aa8ca..b6b3f5a94 100644 --- a/src/api/dto/CalloutDto.ts +++ b/src/api/dto/CalloutDto.ts @@ -1,4 +1,10 @@ -import { ItemStatus } from "@beabee/beabee-common"; +import { + ItemStatus, + CalloutAccess, + CalloutCaptcha, + CalloutChannel, + CalloutData +} from "@beabee/beabee-common"; import { Transform, TransformFnParams, @@ -30,11 +36,6 @@ import IsMapBounds from "@api/validators/IsMapBounds"; import IsLngLat from "@api/validators/IsLngLat"; import IsVariantsObject from "@api/validators/IsVariantsObject"; -import { CalloutAccess } from "@enums/callout-access"; -import { CalloutCaptcha } from "@enums/callout-captcha"; -import { CalloutChannel } from "@enums/callout-channel"; - -import { CalloutData } from "@type/callout-data"; import { CalloutMapSchema } from "@type/callout-map-schema"; import { CalloutResponseViewSchema } from "@type/callout-response-view-schema"; diff --git a/src/api/dto/CalloutVariantDto.ts b/src/api/dto/CalloutVariantDto.ts index f8489c24b..5d0a22ac3 100644 --- a/src/api/dto/CalloutVariantDto.ts +++ b/src/api/dto/CalloutVariantDto.ts @@ -1,7 +1,7 @@ import { CalloutVariantData, CalloutVariantNavigationData -} from "@type/callout-variant-data"; +} from "@beabee/beabee-common"; import { IsString, IsOptional, IsUrl, IsObject } from "class-validator"; export class CalloutVariantDto implements CalloutVariantData { diff --git a/src/api/dto/ContentDto.ts b/src/api/dto/ContentDto.ts index 6a7d90157..a91788060 100644 --- a/src/api/dto/ContentDto.ts +++ b/src/api/dto/ContentDto.ts @@ -1,7 +1,17 @@ import { ContributionPeriod, PaymentMethod, - StripeFeeCountry + StripeFeeCountry, + ContentContactsData, + ContentEmailData, + ContentGeneralData, + ContentJoinData, + ContentJoinPeriodData, + ContentJoinSetupData, + ContentProfileData, + ContentShareData, + ContentPaymentData, + ContentId } from "@beabee/beabee-common"; import { Type } from "class-transformer"; import { @@ -15,23 +25,11 @@ import { } from "class-validator"; import { LinkDto } from "@api/dto/LinkDto"; +import { GetContentTelegramDto } from "@api/dto/ContentTelegramDto"; import { Locale } from "@locale"; -import { - ContactsContentData, - EmailContentData, - GeneralContentData, - JoinContentData, - JoinContentPeriodData, - JoinSetupContentData, - ProfileContentData, - ShareContentData, - TelegramContentData -} from "@type/content-data"; -import { ContentId } from "@type/content-id"; - -export class GetContactsContentDto implements ContactsContentData { +export class GetContentContactsDto implements ContentContactsData { @IsString({ each: true }) tags!: string[]; @@ -39,7 +37,7 @@ export class GetContactsContentDto implements ContactsContentData { manualPaymentSources!: string[]; } -export class GetEmailContentDto implements EmailContentData { +export class GetContentEmailDto implements ContentEmailData { @IsString() supportEmail!: string; @@ -50,7 +48,7 @@ export class GetEmailContentDto implements EmailContentData { footer!: string; } -export class GetGeneralContentDto implements GeneralContentData { +export class GetContentGeneralDto implements ContentGeneralData { @IsString() organisationName!: string; @@ -95,7 +93,7 @@ export class GetGeneralContentDto implements GeneralContentData { footerLinks!: LinkDto[]; } -class GetJoinContentPeriodDto implements JoinContentPeriodData { +class GetContentJoinPeriodDto implements ContentJoinPeriodData { @IsEnum(ContributionPeriod) name!: ContributionPeriod; @@ -103,7 +101,7 @@ class GetJoinContentPeriodDto implements JoinContentPeriodData { presetAmounts!: number[]; } -export class GetJoinContentDto implements JoinContentData { +export class GetContentJoinDto implements ContentJoinData { @IsString() title!: string; @@ -117,8 +115,8 @@ export class GetJoinContentDto implements JoinContentData { initialPeriod!: ContributionPeriod; @ValidateNested({ each: true }) - @Type(() => GetJoinContentPeriodDto) - periods!: GetJoinContentPeriodDto[]; + @Type(() => GetContentJoinPeriodDto) + periods!: GetContentJoinPeriodDto[]; @IsBoolean() showNoContribution!: boolean; @@ -132,14 +130,16 @@ export class GetJoinContentDto implements JoinContentData { @IsBoolean() showAbsorbFee!: boolean; + /** @deprecated Use {@link GetContentPaymentDto.stripePublicKey} instead. */ @IsString() stripePublicKey!: string; + /** @deprecated Use {@link GetContentPaymentDto.stripeCountry} instead. */ @IsIn(["eu", "gb", "ca"]) stripeCountry!: StripeFeeCountry; } -export class GetJoinSetupContentDto implements JoinSetupContentData { +export class GetContentJoinSetupDto implements ContentJoinSetupData { @IsString() welcome!: string; @@ -177,12 +177,12 @@ export class GetJoinSetupContentDto implements JoinSetupContentData { surveySlug!: string; } -export class GetProfileContentDto implements ProfileContentData { +export class GetContentProfileDto implements ContentProfileData { @IsString() introMessage!: string; } -export class GetShareContentDto implements ShareContentData { +export class GetContentShareDto implements ContentShareData { @IsString() title!: string; @@ -196,27 +196,37 @@ export class GetShareContentDto implements ShareContentData { twitterHandle!: string; } -export class GetTelegramContentDto implements TelegramContentData { - /** Markdown formatted welcome message */ +export class GetContentPaymentDto implements ContentPaymentData { @IsString() - welcomeMessageMd!: string; + stripePublicKey!: string; + + @IsIn(["eu", "gb", "ca"]) + stripeCountry!: StripeFeeCountry; + + @IsBoolean() + taxRateEnabled!: boolean; + + @IsNumber() + taxRate!: number; } export type GetContentDto = Id extends "contacts" - ? GetContactsContentDto + ? GetContentContactsDto : never | Id extends "email" - ? GetEmailContentDto + ? GetContentEmailDto : never | Id extends "general" - ? GetGeneralContentDto + ? GetContentGeneralDto : never | Id extends "join" - ? GetJoinContentDto + ? GetContentJoinDto : never | Id extends "join/setup" - ? GetJoinSetupContentDto + ? GetContentJoinSetupDto : never | Id extends "profile" - ? GetProfileContentDto + ? GetContentProfileDto : never | Id extends "share" - ? GetShareContentDto - : never | Id extends "telegram" - ? GetTelegramContentDto - : never; + ? GetContentShareDto + : never | Id extends "payment" + ? GetContentPaymentDto + : never | Id extends "telegram" + ? GetContentTelegramDto + : never; diff --git a/src/api/dto/ContentTelegramDto.ts b/src/api/dto/ContentTelegramDto.ts new file mode 100644 index 000000000..f7a1ca8b6 --- /dev/null +++ b/src/api/dto/ContentTelegramDto.ts @@ -0,0 +1,8 @@ +import { IsString } from "class-validator"; +import type { ContentTelegramData } from "@beabee/beabee-common"; + +export class GetContentTelegramDto implements ContentTelegramData { + /** Markdown formatted welcome message */ + @IsString() + welcomeMessageMd!: string; +} diff --git a/src/api/dto/index.ts b/src/api/dto/index.ts new file mode 100644 index 000000000..cb22392f4 --- /dev/null +++ b/src/api/dto/index.ts @@ -0,0 +1,30 @@ +export * from "./AddressDto.js"; +export * from "./ApiKeyDto.js"; +export * from "./BaseDto.js"; +export * from "./CalloutDto.js"; +export * from "./CalloutFormDto.js"; +export * from "./CalloutResponseCommentDto.js"; +export * from "./CalloutResponseDto.js"; +export * from "./CalloutTagDto.js"; +export * from "./CalloutVariantDto.js"; +export * from "./ContactDto.js"; +export * from "./ContactMfaDto.js"; +export * from "./ContactProfileDto.js"; +export * from "./ContactRoleDto.js"; +export * from "./ContentDto.js"; +export * from "./ContentTelegramDto.js"; +export * from "./ContributionDto.js"; +export * from "./EmailDto.js"; +export * from "./JoinFlowDto.js"; +export * from "./LinkDto.js"; +export * from "./LoginDto.js"; +export * from "./NoticeDto.js"; +export * from "./PaginatedDto.js"; +export * from "./PaymentDto.js"; +export * from "./PaymentFlowDto.js"; +export * from "./ResetDeviceDto.js"; +export * from "./ResetPasswordDto.js"; +export * from "./SegmentDto.js"; +export * from "./SignupFlowDto.js"; +export * from "./StatsDto.js"; +export * from "./UploadFlowDto.js"; diff --git a/src/api/errors/index.ts b/src/api/errors/index.ts new file mode 100644 index 000000000..317c9ffcd --- /dev/null +++ b/src/api/errors/index.ts @@ -0,0 +1,10 @@ +export * from "./BadRequestError.js"; +export * from "./CantUpdateContribution.js"; +export * from "./DuplicateEmailError.js"; +export * from "./DuplicateId.js"; +export * from "./ExternalEmailTemplate.js"; +export * from "./InvalidCalloutResponse.js"; +export * from "./InvalidRuleError.js"; +export * from "./NoPaymentMethod.js"; +export * from "./NotFoundError.js"; +export * from "./UnauthorizedError.js"; diff --git a/src/api/interceptors/index.ts b/src/api/interceptors/index.ts new file mode 100644 index 000000000..8f9951792 --- /dev/null +++ b/src/api/interceptors/index.ts @@ -0,0 +1 @@ +export * from "./ValidateResponseInterceptor.js"; diff --git a/src/api/middlewares/index.ts b/src/api/middlewares/index.ts new file mode 100644 index 000000000..6407bd4de --- /dev/null +++ b/src/api/middlewares/index.ts @@ -0,0 +1 @@ +export * from "./AuthMiddleware.js"; diff --git a/src/api/params/ContentParams.ts b/src/api/params/ContentParams.ts index 39ce45d62..3fb6df0b1 100644 --- a/src/api/params/ContentParams.ts +++ b/src/api/params/ContentParams.ts @@ -1,16 +1,7 @@ -import { ContentId } from "@type/content-id"; +import { ContentId, contentIds } from "@beabee/beabee-common"; import { IsIn } from "class-validator"; export class ContentParams { - @IsIn([ - "contacts", - "email", - "general", - "join", - "join/setup", - "profile", - "share", - "telegram" - ] satisfies ContentId[]) + @IsIn(contentIds) id!: ContentId; } diff --git a/src/api/params/index.ts b/src/api/params/index.ts new file mode 100644 index 000000000..2143d8992 --- /dev/null +++ b/src/api/params/index.ts @@ -0,0 +1,4 @@ +export * from "./ContactRoleParams.js"; +export * from "./ContentParams.js"; +export * from "./SignupConfirmEmailParams.js"; +export * from "./UUIDParams.js"; diff --git a/src/api/transformers/ContentTransformer.ts b/src/api/transformers/ContentTransformer.ts index f515ae3b4..da1cce7dc 100644 --- a/src/api/transformers/ContentTransformer.ts +++ b/src/api/transformers/ContentTransformer.ts @@ -1,42 +1,41 @@ import { plainToInstance } from "class-transformer"; - +import { ContentId, ContentData } from "@beabee/beabee-common"; import { createQueryBuilder, getRepository } from "@core/database"; import { getEmailFooter } from "@core/utils/email"; import OptionsService, { OptionKey } from "@core/services/OptionsService"; import { - GetContactsContentDto, + GetContentContactsDto, GetContentDto, - GetEmailContentDto, - GetGeneralContentDto, - GetJoinContentDto, - GetJoinSetupContentDto, - GetProfileContentDto, - GetShareContentDto, - GetTelegramContentDto -} from "@api/dto/ContentDto"; + GetContentEmailDto, + GetContentGeneralDto, + GetContentJoinDto, + GetContentJoinSetupDto, + GetContentProfileDto, + GetContentShareDto, + GetContentPaymentDto, + GetContentTelegramDto +} from "@api/dto/index"; import Content from "@models/Content"; import config from "@config"; -import { ContentId } from "@type/content-id"; -import { ContentData } from "@type/content-data"; - class ContentTransformer { convert( id: Id, data: ContentData ): GetContentDto { const Dto = { - contacts: GetContactsContentDto, - email: GetEmailContentDto, - general: GetGeneralContentDto, - join: GetJoinContentDto, - "join/setup": GetJoinSetupContentDto, - profile: GetProfileContentDto, - share: GetShareContentDto, - telegram: GetTelegramContentDto + contacts: GetContentContactsDto, + email: GetContentEmailDto, + general: GetContentGeneralDto, + join: GetContentJoinDto, + "join/setup": GetContentJoinSetupDto, + profile: GetContentProfileDto, + share: GetContentShareDto, + payment: GetContentPaymentDto, + telegram: GetContentTelegramDto }[id]; return plainToInstance(Dto as any, data); @@ -206,6 +205,12 @@ const contentData = { title: ["option", "share-title", "text"], twitterHandle: ["option", "share-twitter-handle", "text"] }), + payment: withValue<"payment">({ + stripePublicKey: ["readonly", () => config.stripe.publicKey], + stripeCountry: ["readonly", () => config.stripe.country], + taxRateEnabled: ["option", "tax-rate-enabled", "bool"], + taxRate: ["option", "tax-rate-percentage", "int"] + }), telegram: withValue<"telegram">({ welcomeMessageMd: [ "data", diff --git a/src/api/transformers/index.ts b/src/api/transformers/index.ts new file mode 100644 index 000000000..2613dfc2b --- /dev/null +++ b/src/api/transformers/index.ts @@ -0,0 +1,20 @@ +export * from "./AddressTransformer.js"; +export * from "./ApiKeyTransformer.js"; +export * from "./BaseCalloutResponseTransformer.js"; +export * from "./BaseContactTransformer.js"; +export * from "./BaseTransformer.js"; +export * from "./CalloutResponseCommentTransformer.js"; +export * from "./CalloutResponseExporter.js"; +export * from "./CalloutResponseMapTransformer.js"; +export * from "./CalloutResponseTransformer.js"; +export * from "./CalloutTagTransformer.js"; +export * from "./CalloutTransformer.js"; +export * from "./CalloutVariantTransformer.js"; +export * from "./ContactExporter.js"; +export * from "./ContactProfileTransformer.js"; +export * from "./ContactRoleTransformer.js"; +export * from "./ContactTransformer.js"; +export * from "./ContentTransformer.js"; +export * from "./NoticeTransformer.js"; +export * from "./PaymentTransformer.js"; +export * from "./SegmentTransformer.js"; diff --git a/src/api/utils/auth.ts b/src/api/utils/auth.ts new file mode 100644 index 000000000..d5cd99ca5 --- /dev/null +++ b/src/api/utils/auth.ts @@ -0,0 +1,12 @@ +import { Request } from "express"; + +import Contact from "@models/Contact"; + +export function login(req: Request, contact: Contact): Promise { + return new Promise((resolve, reject) => { + req.login(contact, (error) => { + if (error) reject(error); + else resolve(); + }); + }); +} diff --git a/src/api/utils/index.ts b/src/api/utils/index.ts index ac58c32ef..8df46be01 100644 --- a/src/api/utils/index.ts +++ b/src/api/utils/index.ts @@ -1,67 +1,4 @@ -import { ValidatorOptions, validate } from "class-validator"; -import { Request } from "express"; - -import Contact from "@models/Contact"; -import { BadRequestError } from "routing-controllers"; - -export function login(req: Request, contact: Contact): Promise { - return new Promise((resolve, reject) => { - req.login(contact, (error) => { - if (error) reject(error); - else resolve(); - }); - }); -} - -/** - * Validate an object using the same base options as the main API validator - * @param object The object - * @param validationOptions Other validation options - */ -export async function validateOrReject( - object: object, - validationOptions?: ValidatorOptions -): Promise { - const errors = await validate(object, { - whitelist: true, - forbidNonWhitelisted: true, - forbidUnknownValues: true, - validationError: { - target: false, - value: false - }, - ...validationOptions - }); - - if (errors.length > 0) { - const error: any = new BadRequestError( - `Invalid data, check 'errors' property for more info` - ); - error.errors = errors; - throw error; - } -} - -export function groupBy( - items: T[], - keyFn: (item: T) => string -): { [key: string]: T[] | undefined } { - const result: { [key: string]: T[] } = {}; - for (const item of items) { - const value = keyFn(item); - if (!result[value]) result[value] = []; - result[value].push(item); - } - return result; -} - -export function prefixKeys( - prefix: string, - obj: Record -): Record { - const newObj: any = {}; - for (const key in obj) { - newObj[`${prefix}${key}`] = obj[key]; - } - return newObj; -} +export * from "./auth.js"; +export * from "./objects.js"; +export * from "./rules.js"; +export * from "./validation.js"; diff --git a/src/api/utils/objects.ts b/src/api/utils/objects.ts new file mode 100644 index 000000000..bf577c54e --- /dev/null +++ b/src/api/utils/objects.ts @@ -0,0 +1,23 @@ +export function groupBy( + items: T[], + keyFn: (item: T) => string +): { [key: string]: T[] | undefined } { + const result: { [key: string]: T[] } = {}; + for (const item of items) { + const value = keyFn(item); + if (!result[value]) result[value] = []; + result[value].push(item); + } + return result; +} + +export function prefixKeys( + prefix: string, + obj: Record +): Record { + const newObj: any = {}; + for (const key in obj) { + newObj[`${prefix}${key}`] = obj[key]; + } + return newObj; +} diff --git a/src/api/utils/validation.ts b/src/api/utils/validation.ts new file mode 100644 index 000000000..8ed55c899 --- /dev/null +++ b/src/api/utils/validation.ts @@ -0,0 +1,31 @@ +import { ValidatorOptions, validate } from "class-validator"; +import { BadRequestError } from "routing-controllers"; + +/** + * Validate an object using the same base options as the main API validator + * @param object The object + * @param validationOptions Other validation options + */ +export async function validateOrReject( + object: object, + validationOptions?: ValidatorOptions +): Promise { + const errors = await validate(object, { + whitelist: true, + forbidNonWhitelisted: true, + forbidUnknownValues: true, + validationError: { + target: false, + value: false + }, + ...validationOptions + }); + + if (errors.length > 0) { + const error: any = new BadRequestError( + `Invalid data, check 'errors' property for more info` + ); + error.errors = errors; + throw error; + } +} diff --git a/src/api/validators/index.ts b/src/api/validators/index.ts new file mode 100644 index 000000000..546e3d367 --- /dev/null +++ b/src/api/validators/index.ts @@ -0,0 +1,9 @@ +export * from "./IsLngLat.js"; +export * from "./IsMapBounds.js"; +export * from "./IsPassword.js"; +export * from "./IsSlug.js"; +export * from "./IsType.js"; +export * from "./IsUrl.js"; +export * from "./IsValidPayFee.js"; +export * from "./IsVariantsObject.js"; +export * from "./MinContributionAmount.js"; diff --git a/src/apps/settings/apps/content/app.ts b/src/apps/settings/apps/content/app.ts index 3e304677f..21b7dd6e4 100644 --- a/src/apps/settings/apps/content/app.ts +++ b/src/apps/settings/apps/content/app.ts @@ -8,7 +8,7 @@ import OptionsService from "@core/services/OptionsService"; import Content from "@models/Content"; -import { ContentId } from "@type/content-id"; +import type { ContentId } from "@beabee/beabee-common"; const app = express(); @@ -28,6 +28,7 @@ app.get( join: get("join"), joinSetup: get("join/setup"), profile: get("profile"), + payment: get("payment"), telegram: get("telegram") }); }) @@ -59,6 +60,7 @@ const parseData = { contacts: (d: any) => d, share: (d: any) => d, email: (d: any) => d, + payment: (d: any) => d, telegram: (d: any) => d } as const; diff --git a/src/core/defaults.json b/src/core/defaults.json index 6334503b2..a8bb55271 100644 --- a/src/core/defaults.json +++ b/src/core/defaults.json @@ -2,34 +2,29 @@ "organisation": "UK Makerspace", "software-name": "Membership System", "logo": "", - "home-text": "Hello there Makers!", "home-link-text": "Find out more", "home-link-url": "http://example.com/about", "home-redirect-url": "/login", - "user-home-url": "/profile", "admin-home-url": "/admin", "referral-short-url": "http://example.com/refer", - "support-email": "membership@example.com", "support-email-from": "UK Makerspace Membership", - "contribution-min-monthly-amount": "1", + "tax-rate-enabled": "false", + "tax-rate-percentage": "7", + "tax-rate-stripe-default-id": "", "show-absorb-fee": "true", "show-mail-opt-in": "", - "available-tags": "", "available-manual-payment-sources": "", - "join-survey": "", "cancellation-survey": "", - "share-twitter-handle": "", "share-title": "", "share-description": "", "share-image": "", - "footer-strapline": "", "footer-impressum-link-url": "", "footer-privacy-link-text": "Privacy policy", @@ -38,24 +33,18 @@ "footer-terms-link-url": "http://example.com/terms", "footer-facebook-link-url": "", "footer-twitter-link-url": "", - "email-templates": "{}", - "newsletter-active-member-tag": "Active member", "newsletter-default-groups": "", "newsletter-archive-on-expired": "true", "newsletter-resync-status": "", "newsletter-resync-data": "", - "tracking-code": "", - "switch-webhook-gc": "true", "switch-webhook-mailchimp": "true", "switch-webhook-stripe": "true", - "locale": "en", "theme": "{}", - "flash-logged-in": "Login successful", "flash-logged-out": "Logged out", "flash-login-failed": "Login unsuccessful", @@ -64,34 +53,25 @@ "flash-inactive-membership": "Your membership is inactive", "flash-403": "You do not have access to this area", "flash-information-ommited": "Important information was ommited", - "flash-password-changed": "Password changed", "flash-password-invalid": "Incorrect current password", "flash-password-reset-code-err": "Invalid password reset code", - "flash-password-reset": "If there is an account associated with % you will receive an email shortly", "flash-password-reset-attempt": "Since your last login someone requested a password reset, this has now been cancelled", - "flash-email-duplicate": "Email address already in use", - "flash-account-locked": "Maximum number of password attempts exceeded", "flash-account-attempts": "There has been % attempt(s) to login to your account since you last logged in", - "flash-member-added": "Contact added", "flash-member-updated": "Contact updated", "flash-member-permanently-deleted": "Contact permanently deleted", "flash-member-login-override-generated": "Login override code generated", "flash-member-password-reset-generated": "Password reset code generated", "flash-member-add-invalid-direct-debit": "Invalid direct debit, you must specify the first and last name", - "flash-account-updated": "Your account details have been updated", "flash-delivery-updated": "Your delivery details have been updated", - "flash-emails-sent": "Email sent", "flash-emails-templates-updated": "Email templates updated", - "flash-newsletter-resync-started": "The resyncing process has started", - "flash-permission-404": "Permission not found", "flash-permission-created": "Permission created", "flash-permission-updated": "Permission updated", @@ -100,9 +80,7 @@ "flash-permission-name-required": "Permissions must have a name", "flash-permission-slug-required": "Permissions must have a slug", "flash-permission-duplicate": "User already has this permission", - "flash-gocardless-updated": "GoCardless updated", - "flash-contribution-cancelled": "Contribution cancelled", "flash-contribution-cancellation-err": "Error cancelling contribution", "flash-contribution-updated": "Contribution updated", @@ -113,7 +91,6 @@ "flash-contribution-doesnt-exist": "You don't have an active contribution", "flash-contribution-restarted": "You have successfully restarted your membership", "flash-contribution-restart-code-err": "Invalid restart code", - "flash-2fa-disabled": "Two factor authentication has been disabled", "flash-2fa-enabled": "Two factor authentication has been enabled", "flash-2fa-setup-failed": "The verification code did not match", @@ -125,34 +102,26 @@ "flash-2fa-already-enabled": "Two factor authentication is already enabled", "flash-2fa-already-disabled": "Two factor authentication is already disabled", "flash-2fa-unable-to-disable": "Unable to disable two factor authentication", - "flash-transaction-date-in-future": "Transactions do not occour in the future", - "flash-option-404": "Option not found", "flash-option-updated": "Option updated", "flash-option-reset": "Option reset to default", - "flash-exports-created": "Export created", "flash-exports-added": "Added items to export", "flash-exports-added-one": "Added item to export", "flash-exports-updated": "Updated export status", "flash-exports-deleted": "Export deleted", "flash-exports-ineligible": "Item is not eligible to be in this export.", - "flash-gifts-date-in-the-past": "The gift start date can't be in the past", "flash-gifts-email-duplicate": "The giftee's email address is already registered", "flash-gifts-already-activated": "The gift has already been activated, please login to continue", - "flash-migration-manual-to-auto-sent": "Email sent to manual contributors", - "flash-notices-created": "Notice created", "flash-notices-updated": "Notice updated", "flash-notices-deleted": "Notice deleted", - "flash-pages-created": "Page settings created", "flash-pages-updated": "Page settings updated", "flash-pages-deleted": "Page settings deleted", - "flash-polls-created": "Callout created", "flash-polls-updated": "Callout updated", "flash-polls-deleted": "Callout deleted", @@ -163,54 +132,42 @@ "flash-polls-guest-fields-missing": "Please provide your name and email address", "flash-polls-only-anonymous": "This poll only allows anonymous submissions", "flash-polls-responses-password-protected": "This poll's responses are passsword protected", - "flash-project-created": "Project created", "flash-project-updated": "Project updated", "flash-project-deleted": "Project deleted", "flash-project-members-added": "Members have been added to project", - "flash-referral-gifts-updated": "Referral gift updated", "flash-referral-gifts-options-updated": "Referral gift options updated", "flash-referral-gifts-stock-updated": "Referral gift stock updated", "flash-referral-gifts-deleted": "Referral gift deleted", "flash-referral-gifts-option-deleted": "Referral gift option deleted", "flash-referral-gifts-stock-deleted": "Referral gift stock deleted", - "flash-segment-created": "Segment created", "flash-segment-updated": "Segment updated", "flash-segment-deleted": "Segment deleted", "flash-segment-no-rule-group": "No filters found for the segment", "flash-segment-created-ongoing-email": "An ongoing email has been setup for the segment", "flash-segment-preview-email": "Preview the email below before sending", - "flash-transactional-email-created": "Email created", "flash-transactional-email-updated": "Email updated", "flash-transactional-email-sending": "Email is sending!", "flash-transactional-email-deleted": "Email and mailings deleted", - "flash-referral-code-invalid": "Invalid referral code", "flash-referral-gift-invalid": "This gift is not available", "flash-referral-gift-chosen": "You have chosen your referral gift", - "flash-address-required": "Address is required", - "flash-validation-error-generic": "The data you provided was invalid", - "flash-validation-error.amount-minimum": "The minimum contribution is £1", "flash-validation-error.amount-required": "Contribution amount is required", "flash-validation-error.period-required": "Contribution period is required", "flash-validation-error.amountOther-minimum": "The minimum contribution is £1", "flash-validation-error.payFee-required": "You must absorb the transaction fee for £1 contributions", - "flash-validation-error.delivery_line1-required": "Delivery address line 1 is required", "flash-validation-error.delivery_city-required": "Delivery town/city is required", - "flash-validation-error.verify-const": "Passwords did not match", - "flash-validation-error.email-format": "Email was invalid", "flash-validation-error.postcode-format": "Postcode was invalid", "flash-validation-error.password-format": "Password must be at least 8 characters and with at least one number, lower and upper case letter", "flash-validation-error.url-format": "URL was invalid", - "flash-validation-error.candidates-maxItems": "You can only choose at most three candidates" } diff --git a/src/core/lib/stripe.ts b/src/core/lib/stripe.ts index 8b29a8298..08dc7d3ef 100644 --- a/src/core/lib/stripe.ts +++ b/src/core/lib/stripe.ts @@ -1,7 +1,94 @@ import Stripe from "stripe"; import config from "@config"; +import currentLocale from "@locale"; +import type { StripeTaxRateCreateParams } from "@type/stripe-tax-rate-create-params"; -export default new Stripe(config.stripe.secretKey, { +export const stripe = new Stripe(config.stripe.secretKey, { apiVersion: "2023-10-16", typescript: true }); + +/** + * Disable a tax rate. + * + * @param id The id of the tax rate + * @param options The options for the request + * @returns The response from Stripe + */ +export const stripeTaxRateDisable = async ( + id: string, + options?: Stripe.RequestOptions +): Promise> => { + console.debug("Disabling tax rate", id); + return stripe.taxRates.update(id, { active: false }, options); +}; + +/** + * Create a default tax rate. + * + * @param data The data to create the tax rate with + * @param options The options for the request + * @returns The response from Stripe + */ +export const stripeTaxRateCreateDefault = async ( + data: StripeTaxRateCreateParams, + options?: Stripe.RequestOptions +): Promise> => { + data.active = data.active !== false; + data.metadata ||= {}; + + return stripe.taxRates.create( + { + inclusive: true, + ...data, + metadata: { + "created-by": "beabee", + ...data.metadata + }, + display_name: data.display_name || currentLocale().taxRate.invoiceName + }, + options + ); +}; + +/** + * Update or create a tax rate. + * + * @param data The data to create the tax rate with + * @param id The id of the tax rate to update or disable + * @param options The options for the request + * @returns + */ +export const stripeTaxRateUpdateOrCreateDefault = async ( + data: StripeTaxRateCreateParams, + id?: string, + options?: Stripe.RequestOptions +): Promise> => { + // If the id and percentage is set, we need to check if the percentage is the same + if (id) { + // If the percentage is not set, we can just update the tax rate + if (data.percentage === undefined) { + return stripe.taxRates.update(id, data, options); + } + + let oldTaxRate = await stripe.taxRates.retrieve(id); + + // If the percentage is set, but the same, we can also just update the tax rate + if (oldTaxRate.percentage === data.percentage) { + const updateData: Stripe.TaxRateUpdateParams & + Partial = { + ...data + }; + delete updateData.percentage; + return stripe.taxRates.update(id, updateData, options); + } + // If the percentage is different, we need to disable the old tax rate and create a new one + else { + await stripeTaxRateDisable(id, options); + } + } + + return stripeTaxRateCreateDefault(data, options); +}; + +export { Stripe }; diff --git a/src/core/providers/payment-flow/StripeProvider.ts b/src/core/providers/payment-flow/StripeProvider.ts index a07ca32f1..f43658c78 100644 --- a/src/core/providers/payment-flow/StripeProvider.ts +++ b/src/core/providers/payment-flow/StripeProvider.ts @@ -1,4 +1,4 @@ -import stripe from "@core/lib/stripe"; +import { stripe } from "@core/lib/stripe"; import { log as mainLogger } from "@core/logging"; import { paymentMethodToStripeType } from "@core/utils/payment/stripe"; diff --git a/src/core/providers/payment/GCProvider.ts b/src/core/providers/payment/GCProvider.ts index cbe9a8378..ed59bafc0 100644 --- a/src/core/providers/payment/GCProvider.ts +++ b/src/core/providers/payment/GCProvider.ts @@ -9,8 +9,7 @@ import { updateSubscription, createSubscription, prorateSubscription, - hasPendingPayment, - getSubscriptionNextChargeDate + hasPendingPayment } from "@core/utils/payment/gocardless"; import { calcRenewalDate } from "@core/utils/payment"; diff --git a/src/core/providers/payment/StripeProvider.ts b/src/core/providers/payment/StripeProvider.ts index ad421ec28..faa7aeccf 100644 --- a/src/core/providers/payment/StripeProvider.ts +++ b/src/core/providers/payment/StripeProvider.ts @@ -5,7 +5,7 @@ import Stripe from "stripe"; import { PaymentProvider, UpdateContributionResult } from "."; import { CompletedPaymentFlow } from "@core/providers/payment-flow"; -import stripe from "@core/lib/stripe"; +import { stripe } from "@core/lib/stripe"; import { log as mainLogger } from "@core/logging"; import { getActualAmount, PaymentForm } from "@core/utils"; import { calcRenewalDate, getChargeableAmount } from "@core/utils/payment"; diff --git a/src/core/services/CalloutsService.ts b/src/core/services/CalloutsService.ts index fddaaddbf..5c01569dc 100644 --- a/src/core/services/CalloutsService.ts +++ b/src/core/services/CalloutsService.ts @@ -1,6 +1,8 @@ import { GetCalloutFormSchema, - CalloutResponseAnswersSlide + CalloutResponseAnswersSlide, + CalloutAccess, + CreateCalloutData } from "@beabee/beabee-common"; import slugify from "slugify"; import { BadRequestError } from "routing-controllers"; @@ -26,9 +28,6 @@ import DuplicateId from "@api/errors/DuplicateId"; import InvalidCalloutResponse from "@api/errors/InvalidCalloutResponse"; import NotFoundError from "@api/errors/NotFoundError"; -import { CalloutAccess } from "@enums/callout-access"; -import { CreateCalloutData } from "@type/callout-data"; - const log = mainLogger.child({ app: "callouts-service" }); class CalloutsService { diff --git a/src/core/services/GiftService.ts b/src/core/services/GiftService.ts index 36f1356be..5b8fae338 100644 --- a/src/core/services/GiftService.ts +++ b/src/core/services/GiftService.ts @@ -4,7 +4,7 @@ import moment from "moment"; import { getRepository } from "@core/database"; import { log as mainLogger } from "@core/logging"; -import stripe from "@core/lib/stripe"; +import { stripe, Stripe } from "@core/lib/stripe"; import { isDuplicateIndex } from "@core/utils"; import { generateContactCode } from "@core/utils/contact"; @@ -24,12 +24,17 @@ const log = mainLogger.child({ app: "gift-service" }); export default class GiftService { private static readonly giftMonthlyAmount = 3; + /** + * Create a gift flow and return the Stripe session ID + * @param giftForm + * @returns Stripe session ID + */ static async createGiftFlow(giftForm: GiftForm): Promise { log.info("Create gift flow", giftForm); const giftFlow = await GiftService.createGiftFlowWithCode(giftForm); - const session = await stripe.checkout.sessions.create({ + const params: Stripe.Checkout.SessionCreateParams = { success_url: config.audience + "/gift/thanks/" + giftFlow.id, cancel_url: config.audience + "/gift", customer_email: giftForm.fromEmail, @@ -48,7 +53,17 @@ export default class GiftService { } } ] - }); + }; + + if (OptionsService.getBool("tax-rate-enabled")) { + params.subscription_data = { + default_tax_rates: [ + OptionsService.getText("tax-rate-stripe-default-id") + ] + }; + } + + const session = await stripe.checkout.sessions.create(params); await getRepository(GiftFlow).update(giftFlow.id, { sessionId: session.id diff --git a/src/core/services/OptionsService.ts b/src/core/services/OptionsService.ts index cbc16418c..e4c06a53c 100644 --- a/src/core/services/OptionsService.ts +++ b/src/core/services/OptionsService.ts @@ -15,6 +15,10 @@ interface OptionWithDefault extends Option { default: boolean; } +/** + * The OptionsService is a in-memory store with database persistence. + * It should really only be used for settings where they need to be read synchronously elsewhere in the application. + */ class OptionsService { private optionCache: Record | undefined; diff --git a/src/core/utils/payment/stripe.ts b/src/core/utils/payment/stripe.ts index c3559f18d..19c4dc1f6 100644 --- a/src/core/utils/payment/stripe.ts +++ b/src/core/utils/payment/stripe.ts @@ -5,9 +5,10 @@ import { PaymentSource } from "@beabee/beabee-common"; import { differenceInMonths } from "date-fns"; -import Stripe from "stripe"; -import stripe from "@core/lib/stripe"; +import OptionsService from "@core/services/OptionsService"; + +import { stripe, Stripe } from "@core/lib/stripe"; import { log as mainLogger } from "@core/logging"; import { PaymentForm } from "@core/utils"; import { getChargeableAmount } from "@core/utils/payment"; @@ -78,7 +79,7 @@ export async function createSubscription( renewalDate }); - return await stripe.subscriptions.create({ + const params: Stripe.SubscriptionCreateParams = { customer: customerId, items: [{ price_data: getPriceData(paymentForm, paymentMethod) }], off_session: true, @@ -87,9 +88,24 @@ export async function createSubscription( billing_cycle_anchor: Math.floor(+renewalDate / 1000), proration_behavior: "none" }) - }); + }; + + if (OptionsService.getBool("tax-rate-enabled")) { + params.default_tax_rates = [ + OptionsService.getText("tax-rate-stripe-default-id") + ]; + } + + return await stripe.subscriptions.create(params); } +/** + * Update a subscription with a new payment method. + * @param subscriptionId + * @param paymentForm + * @param paymentMethod + * @returns + */ export async function updateSubscription( subscriptionId: string, paymentForm: PaymentForm, @@ -129,9 +145,7 @@ export async function updateSubscription( const startNow = prorationAmount === 0 || paymentForm.prorate; if (startNow) { - // Start new contribution immediately (monthly or prorated annuals) - log.info(`Updating subscription for ${subscription.id}`); - await stripe.subscriptions.update(subscriptionId, { + const params: Stripe.SubscriptionUpdateParams = { items: [newSubscriptionItem], ...(prorationAmount > 0 ? { @@ -145,7 +159,11 @@ export async function updateSubscription( // Stripe starts the new billing cycle immediately trial_end: subscription.current_period_end }) - }); + }; + + // Start new contribution immediately (monthly or prorated annuals) + log.info(`Updating subscription for ${subscription.id}`); + await stripe.subscriptions.update(subscriptionId, params); } else { // Schedule the change for the next period log.info(`Creating new schedule for ${subscription.id}`); diff --git a/src/enums/callout-access.ts b/src/enums/callout-access.ts deleted file mode 100644 index 451033acd..000000000 --- a/src/enums/callout-access.ts +++ /dev/null @@ -1,6 +0,0 @@ -export enum CalloutAccess { - Member = "member", - Guest = "guest", - Anonymous = "anonymous", - OnlyAnonymous = "only-anonymous" -} diff --git a/src/enums/callout-captcha.ts b/src/enums/callout-captcha.ts deleted file mode 100644 index a29b7368b..000000000 --- a/src/enums/callout-captcha.ts +++ /dev/null @@ -1,5 +0,0 @@ -export enum CalloutCaptcha { - None = "none", - Guest = "guest", - All = "all" -} diff --git a/src/enums/callout-channel.ts b/src/enums/callout-channel.ts deleted file mode 100644 index 2afc4ac48..000000000 --- a/src/enums/callout-channel.ts +++ /dev/null @@ -1,3 +0,0 @@ -export enum CalloutChannel { - Telegram = "telegram" -} diff --git a/src/enums/index.ts b/src/enums/index.ts new file mode 100644 index 000000000..fbd4020fb --- /dev/null +++ b/src/enums/index.ts @@ -0,0 +1,5 @@ +export * from "./contact-mfa-type.js"; +export * from "./get-contact-with.js"; +export * from "./login-codes.js"; +export * from "./reset-security-flow-error-code.js"; +export * from "./reset-security-flow-type.js"; diff --git a/src/locales/current.ts b/src/locales/current.ts index ca17dfe8c..bd6783625 100644 --- a/src/locales/current.ts +++ b/src/locales/current.ts @@ -1,3 +1,5 @@ +// TODO: Move this to it's own beabee-locale package because we use it beabee and beabee-frontend + import OptionsService from "@core/services/OptionsService"; import localeDe from "./de.json"; @@ -7,7 +9,7 @@ import localePt from "./pt.json"; import localeRu from "./ru.json"; import localeIt from "./it.json"; -const locales = { +export const locales = { de: localeDe, "de@informal": localeDeInformal, en: localeEn, diff --git a/src/locales/de.json b/src/locales/de.json index 55d5e088d..858d34993 100644 --- a/src/locales/de.json +++ b/src/locales/de.json @@ -206,6 +206,9 @@ "twitterHandle": "Twitter-Handle" } }, + "payment": { + "paymentTitle": "Zahlungen" + }, "theme": { "bodyFont": "Text", "colorNames": { @@ -545,6 +548,7 @@ "pageCount": "Seite {pageNumber} von {pageTotal}", "paymentStatus": { "cancelled": "Gekündigt", + "draft": "Entwurf", "failed": "Fehlgeschlagen", "pending": "In Bearbeitung", "successful": "Erfolgreich" @@ -1115,7 +1119,10 @@ "now": "Jetzt Mitglied werden! ", "paymentMethod": "Zahlungsart", "poweredBy": "Powered by beabee", - "privacy": "Datenschutzerklärung" + "privacy": "Datenschutzerklärung", + "tax": { + "included": "Beitrag inklusive {taxRate}% MwSt." + } }, "joinPayment": { "genericError": "Upps! Etwas ist schiefgelaufen. Bitte wenden Sie sich an den Support, sollte das erneut passieren.", @@ -1352,5 +1359,8 @@ "title": "Tag entfernen" }, "name": "Name" + }, + "taxRate": { + "invoiceName": "MwSt" } } diff --git a/src/locales/de@easy.json b/src/locales/de@easy.json index 7859c930b..1d10c2190 100644 --- a/src/locales/de@easy.json +++ b/src/locales/de@easy.json @@ -46,6 +46,7 @@ }, "socialSharing": {} }, + "payment": {}, "theme": { "colorNames": {}, "presetColors": {} @@ -139,7 +140,9 @@ "loading": "Der Computer arbeitet.", "newsletterStatus": {}, "noResults": "Der Computer hat keine Ergebnisse gefunden.", - "paymentStatus": {}, + "paymentStatus": { + "draft": "Hier ist ein erster Vorschlag." + }, "role": {}, "selectNone": "keine", "selectOne": "Hier kannst du ein Bild / eine Unterschrift / … aus-suchen", @@ -321,7 +324,8 @@ "errors": { "confirmEmail": {}, "failed": {} - } + }, + "tax": {} }, "joinPayment": {}, "joinSetup": {}, @@ -386,5 +390,8 @@ "setPassword": {}, "tagEditor": { "confirmDelete": {} + }, + "taxRate": { + "invoiceName": "MwSt" } } diff --git a/src/locales/de@informal.json b/src/locales/de@informal.json index a9c05f08d..43c6e7c85 100644 --- a/src/locales/de@informal.json +++ b/src/locales/de@informal.json @@ -206,6 +206,9 @@ "twitterHandle": "Twitter-Handle" } }, + "payment": { + "paymentTitle": "Zahlungen" + }, "theme": { "bodyFont": "Text", "colorNames": { @@ -545,6 +548,7 @@ "pageCount": "Seite {pageNumber} von {pageTotal}", "paymentStatus": { "cancelled": "Gekündigt", + "draft": "Entwurf", "failed": "Fehlgeschlagen", "pending": "In Bearbeitung", "successful": "Erfolgreich" @@ -1115,7 +1119,10 @@ "now": "Jetzt Mitglied werden! ", "paymentMethod": "Zahlungsart", "poweredBy": "Powered by beabee", - "privacy": "Datenschutzerklärung" + "privacy": "Datenschutzerklärung", + "tax": { + "included": "Beitrag inklusive {taxRate}% MwSt." + } }, "joinPayment": { "genericError": "Upps! Etwas ist schiefgelaufen. Bitte wende dich an den Support, sollte das erneut passieren.", @@ -1352,5 +1359,8 @@ "title": "Tag entfernen" }, "name": "Name" + }, + "taxRate": { + "invoiceName": "MwSt" } } diff --git a/src/locales/en.json b/src/locales/en.json index 698ac6014..0bf0e68ea 100644 --- a/src/locales/en.json +++ b/src/locales/en.json @@ -206,6 +206,11 @@ "twitterHandle": "Twitter handle" } }, + "payment": { + "paymentTitle": "Payment", + "taxRate": "VAT rate", + "taxRateEnabled": "Enable VAT" + }, "theme": { "bodyFont": "Body text", "colorNames": { @@ -549,6 +554,7 @@ "pageCount": "Page {pageNumber} out of {pageTotal}", "paymentStatus": { "cancelled": "Cancelled", + "draft": "Draft", "failed": "Failed", "pending": "Pending", "successful": "Successful" @@ -1121,7 +1127,10 @@ "now": "Join now!", "paymentMethod": "Payment method", "poweredBy": "Powered by beabee", - "privacy": "Privacy Policy" + "privacy": "Privacy Policy", + "tax": { + "included": "Contribution includes {taxRate}% VAT." + } }, "joinPayment": { "genericError": "Oops, something went wrong! Contact support if this happens again.", @@ -1358,5 +1367,8 @@ "title": "Remove tag" }, "name": "Name" + }, + "taxRate": { + "invoiceName": "VAT" } } diff --git a/src/locales/it.json b/src/locales/it.json index c55d6485a..59b33f680 100644 --- a/src/locales/it.json +++ b/src/locales/it.json @@ -160,6 +160,7 @@ "pageCount": "Pagina {pageNumber} di {pageTotal}", "paymentStatus": { "cancelled": "Cancellato", + "draft": "Bozza", "failed": "Errore", "pending": "In attesa", "successful": "Riuscito" @@ -383,7 +384,8 @@ "memberAccount": "Il tuo account", "memberAlready": "Sei già iscritto?", "paymentMethod": "Metodo di pagamento", - "privacy": "Privacy" + "privacy": "Privacy", + "tax": {} }, "joinPayment": { "genericError": "Ops! Qualcosa è andato storto", @@ -485,9 +487,12 @@ }, "setPassword": { "description": "Imposta una nuova password.", - "title": "Impposta una password" + "title": "Imposta una password" }, "tagEditor": { "confirmDelete": {} + }, + "taxRate": { + "invoiceName": "VAT" } } diff --git a/src/locales/nl.json b/src/locales/nl.json index 6229ab097..6ffd5dd42 100644 --- a/src/locales/nl.json +++ b/src/locales/nl.json @@ -49,6 +49,7 @@ }, "socialSharing": {} }, + "payment": {}, "theme": { "colorNames": {}, "presetColors": {} @@ -144,7 +145,9 @@ "loading": "Ladt...", "newsletterStatus": {}, "noResults": "Geen resultaten gevonden", - "paymentStatus": {}, + "paymentStatus": { + "draft": "Ontwerp" + }, "role": {}, "selectNone": "Geen", "selectOne": "Kiies er één", @@ -333,7 +336,8 @@ "errors": { "confirmEmail": {}, "failed": {} - } + }, + "tax": {} }, "joinPayment": {}, "joinSetup": {}, @@ -407,5 +411,8 @@ "setPassword": {}, "tagEditor": { "confirmDelete": {} + }, + "taxRate": { + "invoiceName": "???" } } diff --git a/src/locales/pt.json b/src/locales/pt.json index 6f192b235..0f8f6b3ca 100644 --- a/src/locales/pt.json +++ b/src/locales/pt.json @@ -153,6 +153,7 @@ }, "socialSharing": {} }, + "payment": {}, "theme": { "colorNames": {}, "presetColors": {} @@ -342,6 +343,7 @@ "pageCount": "Página {pageNumber} de {pageTotal}", "paymentStatus": { "cancelled": "Cancelado", + "draft": "Rascunho", "failed": "Falhado", "pending": "A aguardar", "successful": "Sucesso" @@ -737,7 +739,8 @@ "now": "Entrar agora!", "paymentMethod": "Forma de pagamento", "poweredBy": "Powered by beabee", - "privacy": "Política de Privacidade" + "privacy": "Política de Privacidade", + "tax": {} }, "joinPayment": { "genericError": "Ups, houve aqui um erro. Fala connosco se isto voltar a acontecer.", @@ -899,5 +902,8 @@ "title": "Remover etiqueta" }, "name": "Nome" + }, + "taxRate": { + "invoiceName": "???" } } diff --git a/src/locales/ru.json b/src/locales/ru.json index c74681db4..375b618d5 100644 --- a/src/locales/ru.json +++ b/src/locales/ru.json @@ -146,6 +146,7 @@ }, "socialSharing": {} }, + "payment": {}, "theme": { "colorNames": {}, "presetColors": {} @@ -313,6 +314,7 @@ "pageCount": "Страница {pageNumber} из {pageTotal}", "paymentStatus": { "cancelled": "Отменён", + "draft": "Черновик", "failed": "Ошибка", "pending": "В работе", "successful": "Успех" @@ -696,7 +698,8 @@ "now": "Станьте участником комьюнити", "paymentMethod": "Способ оплаты", "poweredBy": "Технологии beabee", - "privacy": "Политика конфиденциальности" + "privacy": "Политика конфиденциальности", + "tax": {} }, "joinPayment": { "genericError": "Что-то пошло не так! Если эта проблема возникнет снова, свяжитесь со службой поддержки.", @@ -844,5 +847,8 @@ }, "tagEditor": { "confirmDelete": {} + }, + "taxRate": { + "invoiceName": "???" } } diff --git a/src/models/Callout.ts b/src/models/Callout.ts index 7f6cc19a0..e1f8375c7 100644 --- a/src/models/Callout.ts +++ b/src/models/Callout.ts @@ -1,4 +1,10 @@ -import { SetCalloutFormSchema } from "@beabee/beabee-common"; +import { + SetCalloutFormSchema, + CalloutData, + CalloutAccess, + CalloutCaptcha, + CalloutChannel +} from "@beabee/beabee-common"; import { Column, CreateDateColumn, @@ -7,17 +13,12 @@ import { PrimaryGeneratedColumn } from "typeorm"; -import { CalloutAccess } from "@enums/callout-access"; -import { CalloutCaptcha } from "@enums/callout-captcha"; - import ItemWithStatus from "./ItemWithStatus"; import type CalloutResponse from "./CalloutResponse"; import type CalloutTag from "./CalloutTag"; import type CalloutVariant from "./CalloutVariant"; -import { CalloutChannel } from "@enums/callout-channel"; -import { CalloutResponseViewSchema } from "@type/callout-response-view-schema"; -import { CalloutData } from "@type/callout-data"; +import { CalloutResponseViewSchema } from "@type/index"; @Entity() export default class Callout extends ItemWithStatus implements CalloutData { diff --git a/src/models/CalloutVariant.ts b/src/models/CalloutVariant.ts index 8cc164a13..bf0fdb531 100644 --- a/src/models/CalloutVariant.ts +++ b/src/models/CalloutVariant.ts @@ -1,6 +1,6 @@ import { Column, Entity, ManyToOne, PrimaryColumn } from "typeorm"; import type Callout from "./Callout"; -import { CalloutVariantNavigationData } from "@type/callout-variant-data"; +import { CalloutVariantNavigationData } from "@beabee/beabee-common"; @Entity() export default class CalloutVariant { diff --git a/src/models/Content.ts b/src/models/Content.ts index 0707da790..c2a11750a 100644 --- a/src/models/Content.ts +++ b/src/models/Content.ts @@ -1,5 +1,5 @@ import { Column, Entity, PrimaryColumn, UpdateDateColumn } from "typeorm"; -import { ContentId } from "@type/content-id"; +import { ContentId } from "@beabee/beabee-common"; @Entity() export default class Content { diff --git a/src/models/index.ts b/src/models/index.ts new file mode 100644 index 000000000..581c23721 --- /dev/null +++ b/src/models/index.ts @@ -0,0 +1,36 @@ +export * from "./ApiKey.js"; +export * from "./Callout.js"; +export * from "./CalloutResponse.js"; +export * from "./CalloutResponseComment.js"; +export * from "./CalloutResponseTag.js"; +export * from "./CalloutTag.js"; +export * from "./CalloutVariant.js"; +export * from "./Contact.js"; +export * from "./ContactContribution.js"; +export * from "./ContactMfa.js"; +export * from "./ContactProfile.js"; +export * from "./ContactRole.js"; +export * from "./Content.js"; +export * from "./Email.js"; +export * from "./EmailMailing.js"; +export * from "./Export.js"; +export * from "./ExportItem.js"; +export * from "./GiftFlow.js"; +export * from "./ItemWithStatus.js"; +export * from "./JoinFlow.js"; +export * from "./JoinForm.js"; +export * from "./Notice.js"; +export * from "./Option.js"; +export * from "./PageSettings.js"; +export * from "./Password.js"; +export * from "./Payment.js"; +export * from "./Project.js"; +export * from "./ProjectContact.js"; +export * from "./ProjectEngagement.js"; +export * from "./Referral.js"; +export * from "./ReferralGift.js"; +export * from "./ResetSecurityFlow.js"; +export * from "./Segment.js"; +export * from "./SegmentContact.js"; +export * from "./SegmentOngoingEmail.js"; +export * from "./UploadFlow.js"; diff --git a/src/tools/gocardless/migrate-to-stripe.ts b/src/tools/gocardless/migrate-to-stripe.ts index de99cd197..e7ba39e31 100644 --- a/src/tools/gocardless/migrate-to-stripe.ts +++ b/src/tools/gocardless/migrate-to-stripe.ts @@ -8,7 +8,7 @@ import { Equal, In } from "typeorm"; import { createQueryBuilder, getRepository } from "@core/database"; import { runApp } from "@core/server"; -import stripe from "@core/lib/stripe"; +import { stripe } from "@core/lib/stripe"; import { stripeTypeToPaymentMethod } from "@core/utils/payment/stripe"; import PaymentService from "@core/services/PaymentService"; diff --git a/src/tools/stripe/resync.ts b/src/tools/stripe/resync.ts index 3b94783e3..148b5ddbc 100644 --- a/src/tools/stripe/resync.ts +++ b/src/tools/stripe/resync.ts @@ -5,7 +5,7 @@ import { In } from "typeorm"; import { getRepository } from "@core/database"; import { runApp } from "@core/server"; -import stripe from "@core/lib/stripe"; +import { stripe } from "@core/lib/stripe"; import ContactsService from "@core/services/ContactsService"; import ContactContribution from "@models/ContactContribution"; diff --git a/src/type/callout-data.ts b/src/type/callout-data.ts deleted file mode 100644 index 06cb4f18c..000000000 --- a/src/type/callout-data.ts +++ /dev/null @@ -1,26 +0,0 @@ -import { CalloutAccess } from "@enums/callout-access"; -import { CalloutCaptcha } from "@enums/callout-captcha"; - -import { CalloutResponseViewSchema } from "./callout-response-view-schema"; -import { CalloutVariantData } from "./callout-variant-data"; -import { SetCalloutFormSchema } from "@beabee/beabee-common"; -import { CalloutChannel } from "@enums/callout-channel"; - -export interface CalloutData { - slug?: string; - image: string; - starts: Date | null; - expires: Date | null; - allowUpdate: boolean; - allowMultiple: boolean; - access: CalloutAccess; - captcha: CalloutCaptcha; - hidden: boolean; - channels: CalloutChannel[] | null; - responseViewSchema?: CalloutResponseViewSchema | null; -} - -export interface CreateCalloutData extends CalloutData { - formSchema: SetCalloutFormSchema; - variants: Record; -} diff --git a/src/type/callout-variant-data.ts b/src/type/callout-variant-data.ts deleted file mode 100644 index dcdab6c4d..000000000 --- a/src/type/callout-variant-data.ts +++ /dev/null @@ -1,18 +0,0 @@ -export interface CalloutVariantData { - title: string; - excerpt: string; - intro: string; - thanksTitle: string; - thanksText: string; - thanksRedirect: string | null; - shareTitle: string | null; - shareDescription: string | null; - slideNavigation: Record; - componentText: Record; -} - -export interface CalloutVariantNavigationData { - prevText: string; - nextText: string; - submitText: string; -} diff --git a/src/type/content-data.ts b/src/type/content-data.ts deleted file mode 100644 index 12d94ead1..000000000 --- a/src/type/content-data.ts +++ /dev/null @@ -1,109 +0,0 @@ -import { - ContributionPeriod, - PaymentMethod, - StripeFeeCountry -} from "@beabee/beabee-common"; - -import { Locale } from "@locale"; -import { ContentId } from "./content-id"; - -interface FooterLink { - url: string; - text: string; -} - -export interface ContactsContentData { - tags: string[]; - manualPaymentSources: string[]; -} - -export interface EmailContentData { - supportEmail: string; - supportEmailName: string; - footer: string; -} - -export interface GeneralContentData { - organisationName: string; - logoUrl: string; - siteUrl: string; - supportEmail: string; - privacyLink: string; - termsLink: string; - impressumLink: string; - locale: Locale; - theme: object; - currencyCode: string; - currencySymbol: string; - backgroundUrl: string; - hideContribution: boolean; - footerLinks: FooterLink[]; -} - -export interface JoinContentPeriodData { - name: ContributionPeriod; - presetAmounts: number[]; -} - -export interface JoinContentData { - title: string; - subtitle: string; - initialAmount: number; - initialPeriod: ContributionPeriod; - minMonthlyAmount: number; - periods: JoinContentPeriodData[]; - showAbsorbFee: boolean; - showNoContribution: boolean; - paymentMethods: PaymentMethod[]; - stripePublicKey: string; - stripeCountry: StripeFeeCountry; -} - -export interface JoinSetupContentData { - welcome: string; - newsletterText: string; - newsletterOptIn: string; - newsletterTitle: string; - showNewsletterOptIn: boolean; - showMailOptIn: boolean; - mailTitle: string; - mailText: string; - mailOptIn: string; - surveySlug: string; - surveyRequired: boolean; - surveyText: string; -} - -export interface ProfileContentData { - introMessage: string; -} - -export interface ShareContentData { - title: string; - description: string; - image: string; - twitterHandle: string; -} - -export interface TelegramContentData { - welcomeMessageMd: string; -} - -export type ContentData = - Id extends "contacts" - ? ContactsContentData - : never | Id extends "email" - ? EmailContentData - : never | Id extends "general" - ? GeneralContentData - : never | Id extends "join" - ? JoinContentData - : never | Id extends "join/setup" - ? JoinSetupContentData - : never | Id extends "profile" - ? ProfileContentData - : never | Id extends "share" - ? ShareContentData - : never | Id extends "telegram" - ? TelegramContentData - : never; diff --git a/src/type/content-id.ts b/src/type/content-id.ts deleted file mode 100644 index 54450fa3e..000000000 --- a/src/type/content-id.ts +++ /dev/null @@ -1,9 +0,0 @@ -export type ContentId = - | "join" - | "join/setup" - | "profile" - | "general" - | "contacts" - | "share" - | "email" - | "telegram"; diff --git a/src/type/index.ts b/src/type/index.ts new file mode 100644 index 000000000..337dac97c --- /dev/null +++ b/src/type/index.ts @@ -0,0 +1,17 @@ +export * from "./address.js"; +export * from "./auth-info.js"; +export * from "./callout-map-schema.js"; +export * from "./callout-response-view-schema.js"; +export * from "./complete-urls.js"; +export * from "./contact-mfa-secure.js"; +export * from "./contribution-info.js"; +export * from "./create-contact-mfa-data.js"; +export * from "./delete-contact-mfa-data.js"; +export * from "./filter-handlers.js"; +export * from "./network-service-map.js"; +export * from "./network-service.js"; +export * from "./passport-local-done-callback.js"; +export * from "./passport-local-strategy-options.js"; +export * from "./passport-local-verify-options.js"; +export * from "./passport-login-info.js"; +export * from "./stripe-tax-rate-create-params.js"; diff --git a/src/type/stripe-tax-rate-create-params.ts b/src/type/stripe-tax-rate-create-params.ts new file mode 100644 index 000000000..22b54f0d9 --- /dev/null +++ b/src/type/stripe-tax-rate-create-params.ts @@ -0,0 +1,3 @@ +import type Stripe from "stripe"; +export type StripeTaxRateCreateParams = Stripe.TaxRateUpdateParams & + Pick; diff --git a/src/webhooks/handlers/stripe.ts b/src/webhooks/handlers/stripe.ts index ad8b61d82..528ec1890 100644 --- a/src/webhooks/handlers/stripe.ts +++ b/src/webhooks/handlers/stripe.ts @@ -6,7 +6,7 @@ import Stripe from "stripe"; import { getRepository } from "@core/database"; import { log as mainLogger } from "@core/logging"; -import stripe from "@core/lib/stripe"; +import { stripe } from "@core/lib/stripe"; import { wrapAsync } from "@core/utils"; import { convertStatus } from "@core/utils/payment/stripe";