diff --git a/.gitignore b/.gitignore index 8b80a1bf68f..ad98481ff3a 100644 --- a/.gitignore +++ b/.gitignore @@ -166,6 +166,7 @@ config/donation-settings.js config/superblocks.js config/superblocks.test.js config/challenge-types.js +config/constants.js ### Generated utils files ### utils/index.js diff --git a/.prettierignore b/.prettierignore index 408119a2648..2f5120e38b5 100644 --- a/.prettierignore +++ b/.prettierignore @@ -7,13 +7,7 @@ client/config/browser-scripts/*.json curriculum/challenges/_meta/*/* curriculum/challenges/**/* config/**/*.json -config/i18n.js -config/misc.js -config/certification-settings.js -config/donation-settings.js -config/superblocks.js -config/superblocks.test.js -config/challenge-types.js +config/*.js utils/index.js utils/get-lines.js utils/get-lines.test.js diff --git a/api/package.json b/api/package.json index c7c2ecbf201..1f519bf5086 100644 --- a/api/package.json +++ b/api/package.json @@ -24,13 +24,14 @@ "mongodb": "^4.16.0", "nanoid": "3", "no-profanity": "^1.4.2", - "nodemon": "2.0.22", "nodemailer": "^6.9.3", + "nodemon": "2.0.22", "query-string": "^7.1.3" }, "description": "The freeCodeCamp.org open-source codebase and curriculum", "devDependencies": { "@fastify/type-provider-typebox": "3.5.0", + "@total-typescript/ts-reset": "^0.4.0", "@types/express-session": "1.17.7", "@types/jsonwebtoken": "^9.0.2", "@types/nodemailer": "^6.4.8", diff --git a/api/src/reset.d.ts b/api/src/reset.d.ts new file mode 100644 index 00000000000..12bd3edc94a --- /dev/null +++ b/api/src/reset.d.ts @@ -0,0 +1 @@ +import '@total-typescript/ts-reset'; diff --git a/api/src/routes/settings.ts b/api/src/routes/settings.ts index 8d0db308253..7c4a3e5b2b4 100644 --- a/api/src/routes/settings.ts +++ b/api/src/routes/settings.ts @@ -1,8 +1,7 @@ import { type FastifyPluginCallbackTypebox } from '@fastify/type-provider-typebox'; import { isProfane } from 'no-profanity'; import { isValidUsername } from '../../../utils/validate'; -// we have to use this file as JavaScript because it is used by the old api. -import { blocklistedUsernames } from '../../../config/constants.js'; +import { blocklistedUsernames } from '../../../config/constants'; import { schemas } from '../schemas'; /** diff --git a/config/constants.test.ts b/config/constants.test.ts new file mode 100644 index 00000000000..afb3f3840ed --- /dev/null +++ b/config/constants.test.ts @@ -0,0 +1,26 @@ +import { blocklistedUsernames } from './constants'; + +describe('constants', () => { + describe('blocklistedUsernames', () => { + it('should not contain duplicate values', () => { + const uniqueValues = new Set(blocklistedUsernames); + expect(blocklistedUsernames.length).toEqual(uniqueValues.size); + }); + + it('should contain all the letters in the latin alphabet', () => { + const alphabet = 'abcdefghijklmnopqrstuvwxyz'.split(''); + expect(blocklistedUsernames).toEqual(expect.arrayContaining(alphabet)); + }); + }); +}); + +// Type tests: +type BlocklistedUsernames = (typeof blocklistedUsernames)[number]; + +type HasString = string extends BlocklistedUsernames ? true : false; + +type Expect = T; + +// @ts-expect-error - This is intended to fail since we want to ensure that blocklistedUsernames is an array of literals +// eslint-disable-next-line @typescript-eslint/no-unused-vars +type Test = Expect; diff --git a/config/constants.js b/config/constants.ts similarity index 93% rename from config/constants.js rename to config/constants.ts index 41daee9856e..8087e96e6c8 100644 --- a/config/constants.js +++ b/config/constants.ts @@ -1,10 +1,33 @@ -let alphabet = ''; +const alphabet = [ + 'a', + 'b', + 'c', + 'd', + 'e', + 'f', + 'g', + 'h', + 'i', + 'j', + 'k', + 'l', + 'm', + 'n', + 'o', + 'p', + 'q', + 'r', + 's', + 't', + 'u', + 'v', + 'w', + 'x', + 'y', + 'z' +] as const; -for (let i = 0; i < 26; i++) { - alphabet += String.fromCharCode(97 + i); -} - -const i18nConstants = [ +export const i18nConstants = [ // reserved paths for localizations 'afrikaans', 'arabic', @@ -37,9 +60,9 @@ const i18nConstants = [ 'turkish', 'ukrainian', 'vietnamese' -]; +] as const; -let blocklist = [ +export const blocklistedUsernames = [ ...alphabet, ...i18nConstants, 'about', @@ -172,11 +195,9 @@ let blocklist = [ '509', '510', '511', - 'about', 'about-us', 'abuse', 'access', - 'account', 'accounts', 'ad', 'add', @@ -199,7 +220,6 @@ let blocklist = [ 'alpha', 'amp', 'analytics', - 'api', 'app', 'apps', 'asc', @@ -242,7 +262,6 @@ let blocklist = [ 'channel', 'channels', 'chart', - 'chat', 'checkout', 'clear', 'client', @@ -260,7 +279,6 @@ let blocklist = [ 'contest', 'cookies', 'copy', - 'copyright', 'count', 'create', 'crossdomain.xml', @@ -307,7 +325,6 @@ let blocklist = [ 'error', 'errors', 'event', - 'events', 'example', 'exception', 'exit', @@ -331,12 +348,10 @@ let blocklist = [ 'followers', 'following', 'fonts', - 'forgot', 'forgot-password', 'forgotpassword', 'form', 'forms', - 'forum', 'forums', 'friend', 'friends', @@ -386,12 +401,10 @@ let blocklist = [ 'isatap', 'issues', 'it', - 'jobs', 'join', 'js', 'json', 'keybase.txt', - 'learn', 'legal', 'license', 'licensing', @@ -403,8 +416,6 @@ let blocklist = [ 'localdomain', 'localhost', 'lock', - 'login', - 'logout', 'lost-password', 'mail', 'mail0', @@ -419,7 +430,6 @@ let blocklist = [ 'mail9', 'mailer-daemon', 'mailerdaemon', - 'map', 'marketing', 'marketplace', 'master', @@ -440,7 +450,6 @@ let blocklist = [ 'net', 'network', 'new', - 'news', 'newsletter', 'newsletters', 'next', @@ -503,13 +512,10 @@ let blocklist = [ 'previous', 'pricing', 'print', - 'privacy', - 'privacy-policy', 'private', 'prod', 'product', 'production', - 'profile', 'profiles', 'project', 'projects', @@ -529,7 +535,6 @@ let blocklist = [ 'report', 'request', 'request-password', - 'reset', 'reset-password', 'response', 'return', @@ -551,16 +556,12 @@ let blocklist = [ 'secure', 'security', 'select', - 'services', 'session', 'sessions', 'settings', 'setup', 'share', 'shift', - 'shop', - 'signin', - 'signup', 'site', 'sitemap', 'sites', @@ -591,7 +592,6 @@ let blocklist = [ 'sudo', 'super', 'superuser', - 'support', 'survey', 'sync', 'sysadmin', @@ -601,7 +601,6 @@ let blocklist = [ 'tags', 'team', 'telnet', - 'terms', 'terms-of-use', 'test', 'testimonials', @@ -625,12 +624,9 @@ let blocklist = [ 'undefined', 'unfollow', 'unlike', - 'unsubscribe', 'update', 'upgrade', 'usenet', - 'user', - 'username', 'users', 'uucp', 'var', @@ -644,7 +640,6 @@ let blocklist = [ 'website', 'widget', 'widgets', - 'wiki', 'wpad', 'write', 'www', @@ -657,7 +652,4 @@ let blocklist = [ 'yourname', 'yourusername', 'zlib' -]; - -exports.blocklistedUsernames = [...new Set(blocklist)]; -exports.i18nConstants = i18nConstants; +] as const; diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index 76e5455a403..f49750592a8 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -247,6 +247,9 @@ importers: '@fastify/type-provider-typebox': specifier: 3.5.0 version: 3.5.0(@sinclair/typebox@0.31.5) + '@total-typescript/ts-reset': + specifier: ^0.4.0 + version: 0.4.0 '@types/express-session': specifier: 1.17.7 version: 1.17.7 @@ -2965,7 +2968,7 @@ packages: '@babel/traverse': 7.22.11 '@babel/types': 7.22.11 convert-source-map: 1.9.0 - debug: 4.3.4(supports-color@8.1.1) + debug: 4.3.4 gensync: 1.0.0-beta.2 json5: 2.2.3 semver: 6.3.1 @@ -3187,7 +3190,7 @@ packages: '@babel/core': 7.22.11 '@babel/helper-compilation-targets': 7.22.10 '@babel/helper-plugin-utils': 7.22.5 - debug: 4.3.4(supports-color@8.1.1) + debug: 4.3.4 lodash.debounce: 4.0.8 resolve: 1.22.4 transitivePeerDependencies: @@ -6365,7 +6368,7 @@ packages: '@babel/helper-split-export-declaration': 7.22.6 '@babel/parser': 7.22.11 '@babel/types': 7.22.11 - debug: 4.3.4(supports-color@8.1.1) + debug: 4.3.4 globals: 11.12.0 transitivePeerDependencies: - supports-color @@ -6700,7 +6703,7 @@ packages: engines: {node: ^12.22.0 || ^14.17.0 || >=16.0.0} dependencies: ajv: 6.12.6 - debug: 4.3.4(supports-color@8.1.1) + debug: 4.3.4 espree: 9.6.1 globals: 13.21.0 ignore: 5.2.4 @@ -7190,7 +7193,7 @@ packages: engines: {node: '>=10.10.0'} dependencies: '@humanwhocodes/object-schema': 1.2.1 - debug: 4.3.4(supports-color@8.1.1) + debug: 4.3.4 minimatch: 3.1.2 transitivePeerDependencies: - supports-color @@ -11411,7 +11414,7 @@ packages: '@typescript-eslint/scope-manager': 5.62.0 '@typescript-eslint/type-utils': 5.62.0(eslint@8.48.0)(typescript@4.9.5) '@typescript-eslint/utils': 5.62.0(eslint@8.48.0)(typescript@4.9.5) - debug: 4.3.4(supports-color@8.1.1) + debug: 4.3.4 eslint: 8.48.0 graphemer: 1.4.0 ignore: 5.2.4 @@ -11500,7 +11503,7 @@ packages: '@typescript-eslint/scope-manager': 5.62.0 '@typescript-eslint/types': 5.62.0 '@typescript-eslint/typescript-estree': 5.62.0(typescript@4.9.5) - debug: 4.3.4(supports-color@8.1.1) + debug: 4.3.4 eslint: 8.48.0 typescript: 4.9.5 transitivePeerDependencies: @@ -11532,7 +11535,7 @@ packages: dependencies: '@typescript-eslint/typescript-estree': 5.62.0(typescript@4.9.5) '@typescript-eslint/utils': 5.62.0(eslint@8.48.0)(typescript@4.9.5) - debug: 4.3.4(supports-color@8.1.1) + debug: 4.3.4 eslint: 8.48.0 tsutils: 3.21.0(typescript@4.9.5) typescript: 4.9.5 @@ -11603,7 +11606,7 @@ packages: dependencies: '@typescript-eslint/types': 5.62.0 '@typescript-eslint/visitor-keys': 5.62.0 - debug: 4.3.4(supports-color@8.1.1) + debug: 4.3.4 globby: 11.1.0 is-glob: 4.0.3 semver: 7.5.4 @@ -12048,7 +12051,7 @@ packages: resolution: {integrity: sha512-RZNwNclF7+MS/8bDg70amg32dyeZGZxiDuQmZxKLAlQjr3jGyLx+4Kkk58UO7D2QdgFIQCovuSuZESne6RG6XQ==} engines: {node: '>= 6.0.0'} dependencies: - debug: 4.3.4(supports-color@8.1.1) + debug: 4.3.4 transitivePeerDependencies: - supports-color @@ -12681,7 +12684,7 @@ packages: resolution: {integrity: sha512-TAlMYvOuwGyLK3PfBb5WKBXZmXz2fVCgv23d6zZFdle/q3gPjmxBaeuC0pY0Dzs5PWMSgfqqEZkrye19GlDTgw==} dependencies: archy: 1.0.0 - debug: 4.3.4(supports-color@8.1.1) + debug: 4.3.4 fastq: 1.15.0 transitivePeerDependencies: - supports-color @@ -15310,7 +15313,7 @@ packages: express-session: ^1.17.1 mongodb: ^4.1.0 dependencies: - debug: 4.3.4(supports-color@8.1.1) + debug: 4.3.4 express-session: 1.17.3 kruptein: 3.0.6 mongodb: 4.16.0 @@ -16176,6 +16179,16 @@ packages: ms: 2.0.0 dev: false + /debug@3.2.7: + resolution: {integrity: sha512-CFjzYYAi4ThfiQvizrFQevTTXHtnCqWfe7x1AhgEscTz6ZbLbfoLRLPugTQyBth6f8ZERVUSyWHFD/7Wu4t1XQ==} + peerDependencies: + supports-color: '*' + peerDependenciesMeta: + supports-color: + optional: true + dependencies: + ms: 2.1.3 + /debug@3.2.7(supports-color@5.5.0): resolution: {integrity: sha512-CFjzYYAi4ThfiQvizrFQevTTXHtnCqWfe7x1AhgEscTz6ZbLbfoLRLPugTQyBth6f8ZERVUSyWHFD/7Wu4t1XQ==} peerDependencies: @@ -16210,6 +16223,17 @@ packages: ms: 2.1.2 dev: true + /debug@4.3.4: + resolution: {integrity: sha512-PRWFHuSU3eDtQJPvnNY7Jcket1j0t5OuOsFzPPzsekD52Zl8qUfFIPEiswXqIvHWGVHOgX+7G/vCNNhehwxfkQ==} + engines: {node: '>=6.0'} + peerDependencies: + supports-color: '*' + peerDependenciesMeta: + supports-color: + optional: true + dependencies: + ms: 2.1.2 + /debug@4.3.4(supports-color@8.1.1): resolution: {integrity: sha512-PRWFHuSU3eDtQJPvnNY7Jcket1j0t5OuOsFzPPzsekD52Zl8qUfFIPEiswXqIvHWGVHOgX+7G/vCNNhehwxfkQ==} engines: {node: '>=6.0'} @@ -16668,7 +16692,7 @@ packages: /docsify-server-renderer@4.13.1: resolution: {integrity: sha512-XNJeCK3zp+mVO7JZFn0bH4hNBAMMC1MbuCU7CBsjLHYn4NHrjIgCBGmylzEan3/4Qm6kbSzQx8XzUK5T7GQxHw==} dependencies: - debug: 4.3.4(supports-color@8.1.1) + debug: 4.3.4 docsify: 4.13.1 node-fetch: 2.7.0 resolve-pathname: 3.0.0 @@ -17433,7 +17457,7 @@ packages: /eslint-import-resolver-node@0.3.9: resolution: {integrity: sha512-WFj2isz22JahUv+B788TlO3N6zL3nNJGU8CcZbPZvVEkBPaJdCV4vy5wyghty5ROFbCRnm132v8BScu5/1BQ8g==} dependencies: - debug: 3.2.7(supports-color@8.1.1) + debug: 3.2.7 is-core-module: 2.13.0 resolve: 1.22.4 transitivePeerDependencies: @@ -17446,10 +17470,10 @@ packages: eslint: '*' eslint-plugin-import: '*' dependencies: - debug: 4.3.4(supports-color@8.1.1) + debug: 4.3.4 enhanced-resolve: 5.15.0 eslint: 8.48.0 - eslint-module-utils: 2.8.0(@typescript-eslint/parser@5.62.0)(eslint-import-resolver-node@0.3.9)(eslint-import-resolver-typescript@3.5.5)(eslint@8.48.0) + eslint-module-utils: 2.8.0(@typescript-eslint/parser@5.62.0)(eslint-import-resolver-typescript@3.5.5)(eslint@8.48.0) eslint-plugin-import: 2.28.1(@typescript-eslint/parser@5.62.0)(eslint-import-resolver-typescript@3.5.5)(eslint@8.48.0) get-tsconfig: 4.7.0 globby: 13.2.2 @@ -17513,13 +17537,41 @@ packages: optional: true dependencies: '@typescript-eslint/parser': 5.62.0(eslint@8.48.0)(typescript@4.9.5) - debug: 3.2.7(supports-color@8.1.1) + debug: 3.2.7 eslint: 8.48.0 eslint-import-resolver-node: 0.3.9 eslint-import-resolver-typescript: 3.5.5(@typescript-eslint/parser@5.62.0)(eslint-plugin-import@2.28.1)(eslint@8.48.0) transitivePeerDependencies: - supports-color + /eslint-module-utils@2.8.0(@typescript-eslint/parser@5.62.0)(eslint-import-resolver-typescript@3.5.5)(eslint@8.48.0): + resolution: {integrity: sha512-aWajIYfsqCKRDgUfjEXNN/JlrzauMuSEy5sbd7WXbtW3EH6A6MpwEh42c7qD+MqQo9QMJ6fWLAeIJynx0g6OAw==} + engines: {node: '>=4'} + peerDependencies: + '@typescript-eslint/parser': '*' + eslint: '*' + eslint-import-resolver-node: '*' + eslint-import-resolver-typescript: '*' + eslint-import-resolver-webpack: '*' + peerDependenciesMeta: + '@typescript-eslint/parser': + optional: true + eslint: + optional: true + eslint-import-resolver-node: + optional: true + eslint-import-resolver-typescript: + optional: true + eslint-import-resolver-webpack: + optional: true + dependencies: + '@typescript-eslint/parser': 5.62.0(eslint@8.48.0)(typescript@4.9.5) + debug: 3.2.7 + eslint: 8.48.0 + eslint-import-resolver-typescript: 3.5.5(@typescript-eslint/parser@5.62.0)(eslint-plugin-import@2.28.1)(eslint@8.48.0) + transitivePeerDependencies: + - supports-color + /eslint-plugin-filenames-simple@0.8.0(eslint@8.48.0): resolution: {integrity: sha512-8+uBzNBE5gSUMQv7bmMBiOD26eKzD4/5flPtD5Vl3dzZLXotSwXK3W7ZZqKQfU0Qyoborh+LqbN76EfmbBcU8A==} engines: {node: ^14.17.0 || ^16.0.0 || ^18.0.0} @@ -17622,7 +17674,7 @@ packages: array.prototype.findlastindex: 1.2.2 array.prototype.flat: 1.3.1 array.prototype.flatmap: 1.3.1 - debug: 3.2.7(supports-color@8.1.1) + debug: 3.2.7 doctrine: 2.1.0 eslint: 8.48.0 eslint-import-resolver-node: 0.3.9 @@ -17684,7 +17736,7 @@ packages: '@es-joy/jsdoccomment': 0.39.4 are-docs-informative: 0.0.2 comment-parser: 1.3.1 - debug: 4.3.4(supports-color@8.1.1) + debug: 4.3.4 escape-string-regexp: 4.0.0 eslint: 8.48.0 esquery: 1.5.0 @@ -17979,7 +18031,7 @@ packages: ajv: 6.12.6 chalk: 4.1.2 cross-spawn: 7.0.3 - debug: 4.3.4(supports-color@8.1.1) + debug: 4.3.4 doctrine: 3.0.0 escape-string-regexp: 4.0.0 eslint-scope: 7.2.2 @@ -18908,7 +18960,7 @@ packages: debug: optional: true dependencies: - debug: 4.3.4(supports-color@8.1.1) + debug: 4.3.4 /for-each@0.3.3: resolution: {integrity: sha512-jqYfLp7mo9vIyQf8ykW2v7A+2N4QjeCeI5+Dz9XraiO1ign81wjiH7Fb9vSOWvQfNtmSa4H2RoQTrrXivdUZmw==} @@ -20999,7 +21051,7 @@ packages: engines: {node: '>= 6'} dependencies: agent-base: 6.0.2 - debug: 4.3.4(supports-color@8.1.1) + debug: 4.3.4 transitivePeerDependencies: - supports-color @@ -21942,7 +21994,7 @@ packages: resolution: {integrity: sha512-n3s8EwkdFIJCG3BPKBYvskgXGoy88ARzvegkitk60NxRdwltLOTaH7CUiMRXvwYorl0Q712iEjcWB+fK/MrWVw==} engines: {node: '>=10'} dependencies: - debug: 4.3.4(supports-color@8.1.1) + debug: 4.3.4 istanbul-lib-coverage: 3.2.0 source-map: 0.6.1 transitivePeerDependencies: @@ -23285,7 +23337,7 @@ packages: resolution: {integrity: sha512-pJ4XLQP4Q9HTxl6RVDLJ8Cyh1uitSs0CzDBAz1uoJ4sRD/Bk7cFSXL1FUXDW3zJ7YnfliJx6eu8Jn283bpZ4Yg==} engines: {node: '>=10'} dependencies: - debug: 4.3.4(supports-color@8.1.1) + debug: 4.3.4 rfdc: 1.3.0 uri-js: 4.4.1 transitivePeerDependencies: @@ -23582,7 +23634,7 @@ packages: cli-truncate: 3.1.0 colorette: 2.0.20 commander: 9.5.0 - debug: 4.3.4(supports-color@8.1.1) + debug: 4.3.4 execa: 6.1.0 lilconfig: 2.0.6 listr2: 5.0.8 @@ -31022,7 +31074,7 @@ packages: arg: 5.0.2 bluebird: 3.7.2 check-more-types: 2.24.0 - debug: 4.3.4(supports-color@8.1.1) + debug: 4.3.4 execa: 5.1.1 lazy-ass: 1.6.0 ps-tree: 1.2.0 @@ -31629,7 +31681,7 @@ packages: dependencies: component-emitter: 1.3.0 cookiejar: 2.1.4 - debug: 4.3.4(supports-color@8.1.1) + debug: 4.3.4 fast-safe-stringify: 2.1.1 form-data: 4.0.0 formidable: 2.1.2