feat(api): migrate to esm (#61915)

This commit is contained in:
Oliver Eyton-Williams
2025-09-19 09:58:06 +02:00
committed by GitHub
parent 68614b43a9
commit bed3811952
240 changed files with 664 additions and 610 deletions

5
.gitignore vendored
View File

@@ -153,10 +153,6 @@ jspm_packages/
### Generated config files ###
shared/config/curriculum.json
shared/config/*.js
### Generated utils files ###
shared/utils/*.js
### Old Generated files ###
# These files are no longer generated by the client, but can
@@ -200,6 +196,7 @@ curriculum/curricula.json
curriculum/dist
curriculum/build
curriculum/test/blocks-generated
shared-dist
### Playwright ###

View File

@@ -19,3 +19,4 @@ shared/utils/get-lines.test.js
shared/utils/is-audited.js
shared/utils/validate.js
shared/utils/validate.test.js
shared-dist

View File

@@ -9,8 +9,8 @@ import {
ExamEnvironmentChallenge
} from '@prisma/client';
import { ObjectId } from 'mongodb';
import { defaultUserId } from '../vitest.utils';
import { examEnvironmentPostExamAttempt } from '../src/exam-environment/schemas';
import { defaultUserId } from '../vitest.utils.js';
import { examEnvironmentPostExamAttempt } from '../src/exam-environment/schemas/index.js';
export const oid = () => new ObjectId().toString();

View File

@@ -17,8 +17,8 @@
"@sentry/node": "9.1.0",
"@sinclair/typebox": "^0.34.33",
"@types/pino": "^7.0.5",
"ajv": "8.12.0",
"ajv-formats": "2.1.1",
"ajv": "8.17.1",
"ajv-formats": "3.0.1",
"date-fns": "4.1.0",
"date-fns-tz": "3.2.0",
"dotenv": "16.4.5",
@@ -28,6 +28,7 @@
"joi": "17.12.2",
"jsonwebtoken": "9.0.2",
"lodash": "4.17.21",
"lodash-es": "4.17.21",
"mongodb": "6.10.0",
"nanoid": "3",
"no-profanity": "1.5.1",
@@ -36,12 +37,13 @@
"pino-pretty": "10.2.3",
"query-string": "7.1.3",
"stripe": "16.0.0",
"validator": "13.11.0"
"validator": "13.15.15"
},
"description": "The freeCodeCamp.org open-source codebase and curriculum",
"devDependencies": {
"@total-typescript/ts-reset": "0.5.1",
"@types/jsonwebtoken": "9.0.5",
"@types/lodash-es": "^4.17.12",
"@types/nodemailer": "6.4.14",
"@types/supertest": "2.0.16",
"@types/validator": "13.11.2",
@@ -62,6 +64,7 @@
"license": "BSD-3-Clause",
"main": "none",
"name": "@freecodecamp/api",
"type": "module",
"private": true,
"repository": {
"type": "git",

View File

@@ -3,8 +3,6 @@ import fastifyAccepts from '@fastify/accepts';
import fastifySwagger from '@fastify/swagger';
import fastifySwaggerUI from '@fastify/swagger-ui';
import type { TypeBoxTypeProvider } from '@fastify/type-provider-typebox';
import Ajv from 'ajv';
import addFormats from 'ajv-formats';
import uriResolver from 'fast-uri';
import Fastify, {
FastifyBaseLogger,
@@ -14,25 +12,27 @@ import Fastify, {
RawRequestDefaultExpression,
RawServerDefault
} from 'fastify';
import { Ajv } from 'ajv';
import addFormats from 'ajv-formats';
import prismaPlugin from './db/prisma';
import cookies from './plugins/cookies';
import cors from './plugins/cors';
import { NodemailerProvider } from './plugins/mail-providers/nodemailer';
import { SESProvider } from './plugins/mail-providers/ses';
import mailer from './plugins/mailer';
import redirectWithMessage from './plugins/redirect-with-message';
import security from './plugins/security';
import auth from './plugins/auth';
import bouncer from './plugins/bouncer';
import errorHandling from './plugins/error-handling';
import csrf from './plugins/csrf';
import notFound from './plugins/not-found';
import shadowCapture from './plugins/shadow-capture';
import growthBook from './plugins/growth-book';
import prismaPlugin from './db/prisma.js';
import cookies from './plugins/cookies.js';
import cors from './plugins/cors.js';
import { NodemailerProvider } from './plugins/mail-providers/nodemailer.js';
import { SESProvider } from './plugins/mail-providers/ses.js';
import mailer from './plugins/mailer.js';
import redirectWithMessage from './plugins/redirect-with-message.js';
import security from './plugins/security.js';
import auth from './plugins/auth.js';
import bouncer from './plugins/bouncer.js';
import errorHandling from './plugins/error-handling.js';
import csrf from './plugins/csrf.js';
import notFound from './plugins/not-found.js';
import shadowCapture from './plugins/shadow-capture.js';
import growthBook from './plugins/growth-book.js';
import * as publicRoutes from './routes/public';
import * as protectedRoutes from './routes/protected';
import * as publicRoutes from './routes/public/index.js';
import * as protectedRoutes from './routes/protected/index.js';
import {
API_LOCATION,
@@ -44,14 +44,14 @@ import {
FCC_ENABLE_SENTRY_ROUTES,
GROWTHBOOK_FASTIFY_API_HOST,
GROWTHBOOK_FASTIFY_CLIENT_KEY
} from './utils/env';
import { isObjectID } from './utils/validation';
import { getLogger } from './utils/logger';
} from './utils/env.js';
import { isObjectID } from './utils/validation.js';
import { getLogger } from './utils/logger.js';
import {
examEnvironmentOpenRoutes,
examEnvironmentValidatedTokenRoutes
} from './exam-environment/routes/exam-environment';
import { dailyCodingChallengeRoutes } from './daily-coding-challenge/routes/daily-coding-challenge';
} from './exam-environment/routes/exam-environment.js';
import { dailyCodingChallengeRoutes } from './daily-coding-challenge/routes/daily-coding-challenge.js';
type FastifyInstanceWithTypeProvider = FastifyInstance<
RawServerDefault,
@@ -74,7 +74,7 @@ const ajv = new Ajv({
});
// add the default formatters from avj-formats
addFormats(ajv);
addFormats.default(ajv);
ajv.addFormat('objectid', {
type: 'string',
validate: (str: string) => isObjectID(str)

View File

@@ -1,8 +1,8 @@
import { describe, it, expect, beforeEach, afterEach } from 'vitest';
import { addDays } from 'date-fns';
import { setupServer, superRequest } from '../../../vitest.utils';
import { getNowUsCentral, getUtcMidnight } from '../utils/helpers';
import { setupServer, superRequest } from '../../../vitest.utils.js';
import { getNowUsCentral, getUtcMidnight } from '../utils/helpers.js';
function dateToDateParam(date: Date): string {
return date.toISOString().split('T')[0] as string;

View File

@@ -1,11 +1,11 @@
import { type FastifyPluginCallbackTypebox } from '@fastify/type-provider-typebox';
import * as schemas from '../schemas';
import * as schemas from '../schemas/index.js';
import {
getNowUsCentral,
getUtcMidnight,
dateStringToUtcMidnight
} from '../utils/helpers';
} from '../utils/helpers.js';
/**
* Plugin containing public GET routes for the daily coding challenges.

View File

@@ -1 +1 @@
export { dailyCodingChallenge } from './daily-coding-challenge';
export { dailyCodingChallenge } from './daily-coding-challenge.js';

View File

@@ -1,6 +1,7 @@
import { describe, it, expect, beforeEach, afterAll } from 'vitest';
import { defaultUserEmail, setupServer } from '../../vitest.utils';
import { createUserInput } from '../utils/create-user';
import { defaultUserEmail, setupServer } from '../../vitest.utils.js';
import { createUserInput } from '../utils/create-user.js';
describe('prisma client extensions', () => {
setupServer();

View File

@@ -3,7 +3,7 @@ import { FastifyPluginAsync } from 'fastify';
import { PrismaClient } from '@prisma/client';
// importing MONGOHQ_URL so we can mock it in testing.
import { MONGOHQ_URL } from '../utils/env';
import { MONGOHQ_URL } from '../utils/env.js';
declare module 'fastify' {
interface FastifyInstance {

View File

@@ -17,17 +17,17 @@ import {
defaultUserId,
devLogin,
setupServer
} from '../../../vitest.utils';
} from '../../../vitest.utils.js';
import {
examEnvironmentPostExamAttempt,
examEnvironmentPostExamGeneratedExam
} from '../schemas';
import * as mock from '../../../__mocks__/exam-environment-exam';
import { constructUserExam } from '../utils/exam-environment';
import { JWT_SECRET } from '../../utils/env';
} from '../schemas/index.js';
import * as mock from '../../../__mocks__/exam-environment-exam.js';
import { constructUserExam } from '../utils/exam-environment.js';
import { JWT_SECRET } from '../../utils/env.js';
vi.mock('../../utils/env', async importOriginal => {
const actual = await importOriginal<typeof import('../../utils/env')>();
const actual = await importOriginal<typeof import('../../utils/env.js')>();
return {
...actual,
FCC_ENABLE_EXAM_ENVIRONMENT: 'true',
@@ -531,7 +531,10 @@ describe('/exam-environment/', () => {
it('should unwind (delete) the exam attempt if the user exam cannot be constructed', async () => {
const _mockConstructUserExam = vi
.spyOn(await import('../utils/exam-environment'), 'constructUserExam')
.spyOn(
await import('../utils/exam-environment.js'),
'constructUserExam'
)
.mockImplementationOnce(() => {
throw new Error('Test error');
});

View File

@@ -1,22 +1,22 @@
/* eslint-disable jsdoc/require-returns, jsdoc/require-param */
import { type FastifyPluginCallbackTypebox } from '@fastify/type-provider-typebox';
import { PrismaClientValidationError } from '@prisma/client/runtime/library';
import { PrismaClientValidationError } from '@prisma/client/runtime/library.js';
import { type FastifyInstance, type FastifyReply } from 'fastify';
import { ExamEnvironmentExamModerationStatus } from '@prisma/client';
import jwt from 'jsonwebtoken';
import * as schemas from '../schemas';
import { mapErr, syncMapErr, UpdateReqType } from '../../utils';
import { JWT_SECRET } from '../../utils/env';
import * as schemas from '../schemas/index.js';
import { mapErr, syncMapErr, UpdateReqType } from '../../utils/index.js';
import { JWT_SECRET } from '../../utils/env.js';
import {
checkPrerequisites,
constructEnvExamAttempt,
constructUserExam,
userAttemptToDatabaseAttemptQuestionSets,
validateAttempt
} from '../utils/exam-environment';
import { ERRORS } from '../utils/errors';
import { isObjectID } from '../../utils/validation';
} from '../utils/exam-environment.js';
import { ERRORS } from '../utils/errors.js';
import { isObjectID } from '../../utils/validation.js';
/**
* Wrapper for endpoints related to the exam environment desktop app.

View File

@@ -1,5 +1,5 @@
import { Type } from '@fastify/type-provider-typebox';
import { STANDARD_ERROR } from '../utils/errors';
import { STANDARD_ERROR } from '../utils/errors.js';
export const examEnvironmentPostExamAttempt = {
body: Type.Object({

View File

@@ -1,5 +1,5 @@
import { Type } from '@fastify/type-provider-typebox';
import { STANDARD_ERROR } from '../utils/errors';
import { STANDARD_ERROR } from '../utils/errors.js';
export const examEnvironmentPostExamGeneratedExam = {
body: Type.Object({

View File

@@ -1,5 +1,5 @@
import { Type } from '@fastify/type-provider-typebox';
import { STANDARD_ERROR } from '../utils/errors';
import { STANDARD_ERROR } from '../utils/errors.js';
export const examEnvironmentExams = {
headers: Type.Object({
'exam-environment-authorization-token': Type.String()

View File

@@ -3,8 +3,8 @@ export {
examEnvironmentGetExamAttempts,
examEnvironmentGetExamAttempt,
examEnvironmentGetExamAttemptsByExamId
} from './exam-environment-exam-attempt';
export { examEnvironmentPostExamGeneratedExam } from './exam-environment-exam-generated-exam';
export { examEnvironmentTokenMeta } from './token-meta';
export { examEnvironmentExams } from './exam-environment-exams';
export { examEnvironmentGetExamMappingsByChallengeId } from './challenges';
} from './exam-environment-exam-attempt.js';
export { examEnvironmentPostExamGeneratedExam } from './exam-environment-exam-generated-exam.js';
export { examEnvironmentTokenMeta } from './token-meta.js';
export { examEnvironmentExams } from './exam-environment-exams.js';
export { examEnvironmentGetExamMappingsByChallengeId } from './challenges.js';

View File

@@ -1,5 +1,5 @@
import { Type } from '@fastify/type-provider-typebox';
import { STANDARD_ERROR } from '../utils/errors';
import { STANDARD_ERROR } from '../utils/errors.js';
export const examEnvironmentTokenMeta = {
headers: Type.Object({

View File

@@ -9,9 +9,9 @@ import {
examAttempt,
generatedExam,
oid
} from '../../../__mocks__/exam-environment-exam';
import * as schemas from '../schemas';
import { setupServer } from '../../../vitest.utils';
} from '../../../__mocks__/exam-environment-exam.js';
import * as schemas from '../schemas/index.js';
import { setupServer } from '../../../vitest.utils.js';
import {
checkAttemptAgainstGeneratedExam,
checkPrerequisites,
@@ -21,7 +21,7 @@ import {
validateAttempt,
compareAnswers,
shuffleArray
} from './exam-environment';
} from './exam-environment.js';
// NOTE: Whilst the tests could be run against a single generation of exam,
// it is more useful to run the tests against a new generation each time.

View File

@@ -16,10 +16,10 @@ import {
} from '@prisma/client';
import type { FastifyBaseLogger, FastifyInstance } from 'fastify';
import { type Static } from '@fastify/type-provider-typebox';
import { omit } from 'lodash';
import * as schemas from '../schemas';
import { mapErr } from '../../utils';
import { ERRORS } from './errors';
import { omit } from 'lodash-es';
import * as schemas from '../schemas/index.js';
import { mapErr } from '../../utils/index.js';
import { ERRORS } from './errors.js';
interface CompletedChallengeId {
completedChallenges: {

View File

@@ -5,7 +5,7 @@ import {
DEPLOYMENT_VERSION,
SENTRY_DSN,
SENTRY_ENVIRONMENT
} from './utils/env';
} from './utils/env.js';
const shouldIgnoreError = (error: FastifyError): boolean => {
return !!error.statusCode && error.statusCode < 500;

View File

@@ -1,13 +1,12 @@
import { expect } from 'vitest';
import { nanoidCharSet } from '../../utils/create-user';
import { nanoidCharSet } from '../../utils/create-user.js';
const uuidRe = /^[a-f0-9]{8}-([a-f0-9]{4}-){3}[a-f0-9]{12}$/;
const fccUuidRe = /^fcc-[a-f0-9]{8}-([a-f0-9]{4}-){3}[a-f0-9]{12}$/;
const unsubscribeIdRe = new RegExp(`^[${nanoidCharSet}]{21}$`);
const mongodbIdRe = /^[a-f0-9]{24}$/;
// eslint-disable-next-line jsdoc/require-jsdoc
export const newUser = (email: string) => ({
about: '',
@@ -91,5 +90,4 @@ export const newUser = (email: string) => ({
verificationToken: null,
website: null,
yearsTopContributor: []
}
)
});

View File

@@ -8,14 +8,14 @@ import {
} from 'vitest';
import Fastify, { FastifyInstance } from 'fastify';
import { checkCanConnectToDb, defaultUserEmail } from '../../vitest.utils';
import { HOME_LOCATION } from '../utils/env';
import { devAuth } from '../plugins/auth-dev';
import prismaPlugin from '../db/prisma';
import auth from './auth';
import cookies from './cookies';
import { checkCanConnectToDb, defaultUserEmail } from '../../vitest.utils.js';
import { HOME_LOCATION } from '../utils/env.js';
import { devAuth } from '../plugins/auth-dev.js';
import prismaPlugin from '../db/prisma.js';
import auth from './auth.js';
import cookies from './cookies.js';
import { newUser } from './__fixtures__/user';
import { newUser } from './__fixtures__/user.js';
describe('dev login', () => {
let fastify: FastifyInstance;

View File

@@ -4,9 +4,9 @@ import {
getRedirectParams,
getPrefixedLandingPath,
haveSamePath
} from '../utils/redirection';
import { findOrCreateUser } from '../routes/helpers/auth-helpers';
import { createAccessToken } from '../utils/tokens';
} from '../utils/redirection.js';
import { findOrCreateUser } from '../routes/helpers/auth-helpers.js';
import { createAccessToken } from '../utils/tokens.js';
const trimTrailingSlash = (str: string) =>
str.endsWith('/') ? str.slice(0, -1) : str;

View File

@@ -2,10 +2,13 @@ import { describe, test, expect, beforeEach, afterEach } from 'vitest';
import Fastify, { FastifyInstance } from 'fastify';
import jwt from 'jsonwebtoken';
import { COOKIE_DOMAIN, JWT_SECRET } from '../utils/env';
import { type Token, createAccessToken } from '../utils/tokens';
import cookies, { sign as signCookie, unsign as unsignCookie } from './cookies';
import auth from './auth';
import { COOKIE_DOMAIN, JWT_SECRET } from '../utils/env.js';
import { type Token, createAccessToken } from '../utils/tokens.js';
import cookies, {
sign as signCookie,
unsign as unsignCookie
} from './cookies.js';
import auth from './auth.js';
async function setupServer() {
const fastify = Fastify();

View File

@@ -3,9 +3,9 @@ import fp from 'fastify-plugin';
import jwt from 'jsonwebtoken';
import { type user } from '@prisma/client';
import { JWT_SECRET } from '../utils/env';
import { type Token, isExpired } from '../utils/tokens';
import { ERRORS } from '../exam-environment/utils/errors';
import { JWT_SECRET } from '../utils/env.js';
import { type Token, isExpired } from '../utils/tokens.js';
import { ERRORS } from '../exam-environment/utils/errors.js';
declare module 'fastify' {
interface FastifyReply {

View File

@@ -11,20 +11,20 @@ import {
} from 'vitest';
import Fastify, { FastifyInstance } from 'fastify';
import { createUserInput } from '../utils/create-user';
import { AUTH0_DOMAIN, HOME_LOCATION } from '../utils/env';
import prismaPlugin from '../db/prisma';
import cookies, { sign, unsign } from './cookies';
import { auth0Client } from './auth0';
import redirectWithMessage, { formatMessage } from './redirect-with-message';
import auth from './auth';
import bouncer from './bouncer';
import { newUser } from './__fixtures__/user';
import { createUserInput } from '../utils/create-user.js';
import { AUTH0_DOMAIN, HOME_LOCATION } from '../utils/env.js';
import prismaPlugin from '../db/prisma.js';
import cookies, { sign, unsign } from './cookies.js';
import { auth0Client } from './auth0.js';
import redirectWithMessage, { formatMessage } from './redirect-with-message.js';
import auth from './auth.js';
import bouncer from './bouncer.js';
import { newUser } from './__fixtures__/user.js';
const COOKIE_DOMAIN = 'test.com';
vi.mock('../utils/env', async importOriginal => ({
...(await importOriginal<typeof import('../utils/env')>()),
...(await importOriginal<typeof import('../utils/env.js')>()),
COOKIE_DOMAIN: 'test.com'
}));

View File

@@ -4,7 +4,7 @@ import { Type } from '@sinclair/typebox';
import { Value } from '@sinclair/typebox/value';
import fp from 'fastify-plugin';
import { isError } from 'lodash';
import { isError } from 'lodash-es';
import {
API_LOCATION,
AUTH0_CLIENT_ID,
@@ -12,10 +12,10 @@ import {
AUTH0_DOMAIN,
COOKIE_DOMAIN,
HOME_LOCATION
} from '../utils/env';
import { findOrCreateUser } from '../routes/helpers/auth-helpers';
import { createAccessToken } from '../utils/tokens';
import { getLoginRedirectParams } from '../utils/redirection';
} from '../utils/env.js';
import { findOrCreateUser } from '../routes/helpers/auth-helpers.js';
import { createAccessToken } from '../utils/tokens.js';
import { getLoginRedirectParams } from '../utils/redirection.js';
declare module 'fastify' {
interface FastifyInstance {

View File

@@ -11,11 +11,11 @@ import {
import Fastify, { type FastifyInstance } from 'fastify';
import { type user } from '@prisma/client';
import { HOME_LOCATION } from '../utils/env';
import bouncer from './bouncer';
import auth from './auth';
import cookies from './cookies';
import redirectWithMessage, { formatMessage } from './redirect-with-message';
import { HOME_LOCATION } from '../utils/env.js';
import bouncer from './bouncer.js';
import auth from './auth.js';
import cookies from './cookies.js';
import redirectWithMessage, { formatMessage } from './redirect-with-message.js';
let authorizeSpy: MockInstance<FastifyInstance['authorize']>;

View File

@@ -4,7 +4,7 @@ import type {
FastifyReply
} from 'fastify';
import fp from 'fastify-plugin';
import { getRedirectParams } from '../utils/redirection';
import { getRedirectParams } from '../utils/redirection.js';
declare module 'fastify' {
interface FastifyInstance {

View File

@@ -1,10 +1,10 @@
import { describe, test, expect, beforeEach, afterEach, vi } from 'vitest';
import Fastify, { FastifyInstance } from 'fastify';
import cookies, { type CookieSerializeOptions, sign } from './cookies';
import { cookieUpdate } from './cookie-update';
import cookies, { type CookieSerializeOptions, sign } from './cookies.js';
import { cookieUpdate } from './cookie-update.js';
vi.mock('../utils/env', async importOriginal => {
const actual = await importOriginal<typeof import('../utils/env')>();
const actual = await importOriginal<typeof import('../utils/env.js')>();
return {
...actual,
COOKIE_DOMAIN: 'www.example.com',

View File

@@ -1,6 +1,6 @@
import { FastifyPluginCallback } from 'fastify';
import type { CookieSerializeOptions } from './cookies';
import type { CookieSerializeOptions } from './cookies.js';
type Options = { cookies: string[]; attributes: CookieSerializeOptions };

View File

@@ -2,11 +2,11 @@ import { describe, test, expect, beforeEach, afterEach, vi } from 'vitest';
import Fastify, { type FastifyInstance } from 'fastify';
import fastifyCookie from '@fastify/cookie';
import { COOKIE_SECRET } from '../utils/env';
import cookies from './cookies';
import { COOKIE_SECRET } from '../utils/env.js';
import cookies from './cookies.js';
vi.mock('../utils/env', async importOriginal => {
const actual = await importOriginal<typeof import('../utils/env')>();
const actual = await importOriginal<typeof import('../utils/env.js')>();
return {
...actual,
COOKIE_DOMAIN: 'www.example.com',

View File

@@ -6,8 +6,8 @@ import {
COOKIE_DOMAIN,
COOKIE_SECRET,
FREECODECAMP_NODE_ENV
} from '../utils/env';
import { CSRF_COOKIE, CSRF_SECRET_COOKIE } from './csrf';
} from '../utils/env.js';
import { CSRF_COOKIE, CSRF_SECRET_COOKIE } from './csrf.js';
export { type CookieSerializeOptions } from '@fastify/cookie';

View File

@@ -1,6 +1,6 @@
import { describe, test, expect, beforeAll, afterAll, vi } from 'vitest';
import Fastify, { FastifyInstance, LogLevel } from 'fastify';
import cors from './cors';
import cors from './cors.js';
const NON_DEBUG_LOG_LEVELS: LogLevel[] = [
'fatal',

View File

@@ -1,8 +1,8 @@
import { FastifyPluginCallback } from 'fastify';
import fp from 'fastify-plugin';
import { HOME_LOCATION } from '../utils/env';
import { allowedOrigins } from '../utils/allowed-origins';
import { HOME_LOCATION } from '../utils/env.js';
import { allowedOrigins } from '../utils/allowed-origins.js';
const cors: FastifyPluginCallback = (fastify, _options, done) => {
fastify.options('*', (_req, reply) => {

View File

@@ -1,12 +1,12 @@
import { describe, test, expect, beforeEach, vi } from 'vitest';
import Fastify, { type FastifyInstance } from 'fastify';
import { COOKIE_DOMAIN } from '../utils/env';
import cookies from './cookies';
import csrf, { CSRF_COOKIE, CSRF_SECRET_COOKIE } from './csrf';
import { COOKIE_DOMAIN } from '../utils/env.js';
import cookies from './cookies.js';
import csrf, { CSRF_COOKIE, CSRF_SECRET_COOKIE } from './csrf.js';
vi.mock('../utils/env', async importOriginal => {
const actual = await importOriginal<typeof import('../utils/env')>();
const actual = await importOriginal<typeof import('../utils/env.js')>();
return {
...actual,
COOKIE_DOMAIN: 'www.example.com',

View File

@@ -13,8 +13,8 @@ import accepts from '@fastify/accepts';
import { http, HttpResponse } from 'msw';
import { setupServer } from 'msw/node';
vi.mock('../utils/env', async importOriginal => {
const actual = await importOriginal<typeof import('../utils/env')>();
vi.mock('../utils/env.js', async importOriginal => {
const actual = await importOriginal<typeof import('../utils/env.js')>();
return {
...actual,
SENTRY_DSN: 'https://anything@goes/123'
@@ -22,8 +22,8 @@ vi.mock('../utils/env', async importOriginal => {
});
import '../instrument';
import errorHandling from './error-handling';
import redirectWithMessage, { formatMessage } from './redirect-with-message';
import errorHandling from './error-handling.js';
import redirectWithMessage, { formatMessage } from './redirect-with-message.js';
const delay = (ms: number) => new Promise(resolve => setTimeout(resolve, ms));

View File

@@ -2,7 +2,7 @@ import type { FastifyPluginCallback } from 'fastify';
import * as Sentry from '@sentry/node';
import fp from 'fastify-plugin';
import { getRedirectParams } from '../utils/redirection';
import { getRedirectParams } from '../utils/redirection.js';
declare module 'fastify' {
interface FastifyInstance {

View File

@@ -1,9 +1,9 @@
import { describe, test, expect, beforeAll, afterAll, vi } from 'vitest';
import Fastify, { type FastifyInstance } from 'fastify';
import growthBook from './growth-book';
import growthBook from './growth-book.js';
vi.mock('../utils/env', async importOriginal => {
const actual = await importOriginal<typeof import('../utils/env')>();
const actual = await importOriginal<typeof import('../utils/env.js')>();
return {
...actual,
// We're only interested in the production behaviour

View File

@@ -2,7 +2,7 @@ import { GrowthBook, Options } from '@growthbook/growthbook';
import { FastifyPluginAsync } from 'fastify';
import fp from 'fastify-plugin';
import { FREECODECAMP_NODE_ENV } from '../utils/env';
import { FREECODECAMP_NODE_ENV } from '../utils/env.js';
declare module 'fastify' {
interface FastifyInstance {

View File

@@ -1,7 +1,7 @@
import nodemailer, { Transporter } from 'nodemailer';
import { MailProvider, SendEmailArgs } from '../mailer';
import { MAILHOG_HOST } from '../../utils/env';
import { MailProvider, SendEmailArgs } from '../mailer.js';
import { MAILHOG_HOST } from '../../utils/env.js';
/**
* NodemailerProvider is a wrapper around nodemailer that provides a clean

View File

@@ -4,8 +4,8 @@ import {
SendEmailCommand
} from '@aws-sdk/client-ses';
import { MailProvider, SendEmailArgs } from '../mailer';
import { SES_ID, SES_SECRET, SES_REGION } from '../../utils/env';
import { MailProvider, SendEmailArgs } from '../mailer.js';
import { SES_ID, SES_SECRET, SES_REGION } from '../../utils/env.js';
/**
* SESProvider is a wrapper around nodemailer that provides a clean interface

View File

@@ -1,7 +1,7 @@
import { describe, test, expect, vi } from 'vitest';
import Fastify from 'fastify';
import mailer from './mailer';
import mailer from './mailer.js';
describe('mailer', () => {
test('should send an email via the provider', async () => {

View File

@@ -2,8 +2,8 @@ import { describe, beforeEach, afterEach, it, expect } from 'vitest';
import Fastify, { type FastifyInstance } from 'fastify';
import accepts from '@fastify/accepts';
import notFound from './not-found';
import redirectWithMessage, { formatMessage } from './redirect-with-message';
import notFound from './not-found.js';
import redirectWithMessage, { formatMessage } from './redirect-with-message.js';
describe('fourOhFour', () => {
let fastify: FastifyInstance;

View File

@@ -2,7 +2,7 @@ import type { FastifyPluginCallback } from 'fastify';
import fp from 'fastify-plugin';
import { getRedirectParams } from '../utils/redirection';
import { getRedirectParams } from '../utils/redirection.js';
/**
* Plugin for handling missing endpoints.

View File

@@ -2,7 +2,7 @@ import { describe, test, expect, beforeEach } from 'vitest';
import Fastify, { FastifyInstance } from 'fastify';
import qs from 'query-string';
import redirectWithMessage from './redirect-with-message';
import redirectWithMessage from './redirect-with-message.js';
async function setupServer() {
const fastify = Fastify();

View File

@@ -1,7 +1,7 @@
import { FastifyPluginCallback } from 'fastify';
import fp from 'fastify-plugin';
import { FREECODECAMP_NODE_ENV } from '../utils/env';
import { FREECODECAMP_NODE_ENV } from '../utils/env.js';
const securityHeaders: FastifyPluginCallback = (fastify, _options, done) => {
// OWASP recommended headers

View File

@@ -1,11 +1,13 @@
import { randomUUID } from 'crypto';
import { appendFileSync, mkdirSync } from 'fs';
import { join } from 'path';
import type { FastifyPluginCallback } from 'fastify';
import type {
FastifyPluginCallback,
FastifyReply,
FastifyRequest
} from 'fastify';
import fp from 'fastify-plugin';
import { FastifyReply } from 'fastify/types/reply';
import { FastifyRequest } from 'fastify/types/request';
const LOGS_DIRECTORY = 'logs';
const REQUEST_CAPTURE_FILE = 'request-capture.jsonl';

View File

@@ -1,10 +1,10 @@
import { describe, test, expect, beforeAll, afterEach, vi } from 'vitest';
import Fastify, { FastifyInstance } from 'fastify';
import db from '../../db/prisma';
import { createUserInput } from '../../utils/create-user';
import { checkCanConnectToDb } from '../../../vitest.utils';
import { findOrCreateUser } from './auth-helpers';
import db from '../../db/prisma.js';
import { createUserInput } from '../../utils/create-user.js';
import { checkCanConnectToDb } from '../../../vitest.utils.js';
import { findOrCreateUser } from './auth-helpers.js';
const captureException = vi.fn();

View File

@@ -1,5 +1,5 @@
import { FastifyInstance } from 'fastify';
import { createUserInput } from '../../utils/create-user';
import { createUserInput } from '../../utils/create-user.js';
/**
* Finds an existing user with the given email or creates a new user if none exists.

View File

@@ -1,5 +1,6 @@
import { describe, test, expect } from 'vitest';
import { getFallbackFullStackDate } from './certificate-utils';
import { getFallbackFullStackDate } from './certificate-utils.js';
const fullStackChallenges = [
{

View File

@@ -2,8 +2,8 @@ import { Prisma } from '@prisma/client';
import {
certSlugTypeMap,
certIds
} from '../../../../shared/config/certification-settings';
import { normalizeDate } from '../../utils/normalize';
} from '../../../../shared/config/certification-settings.js';
import { normalizeDate } from '../../utils/normalize.js';
const {
legacyInfosecQaId,

View File

@@ -4,14 +4,14 @@ import type {
CompletedChallenge
} from '@prisma/client';
import { createFetchMock } from '../../../vitest.utils';
import { createFetchMock } from '../../../vitest.utils.js';
import {
canSubmitCodeRoadCertProject,
verifyTrophyWithMicrosoft,
decodeFiles,
decodeBase64,
encodeBase64
} from './challenge-helpers';
} from './challenge-helpers.js';
const id = 'abc';

View File

@@ -1,6 +1,6 @@
import { isProfane } from 'no-profanity';
import { blocklistedUsernames } from '../../../../shared/config/constants';
import { blocklistedUsernames } from '../../../../shared/config/constants.js';
/**
* Checks if a username is restricted (i.e. It's profane or reserved).

View File

@@ -1,4 +1,4 @@
import _ from 'lodash';
import { pick, omit } from 'lodash-es';
// user flags that the api-server returns as false if they're missing in the
// user document. Since Prisma returns null for missing fields, we need to
@@ -45,5 +45,5 @@ type NullableFlags = (typeof nullableFlags)[number];
export function splitUser<U extends Record<NullableFlags, unknown>>(
user: U
): [Pick<U, NullableFlags>, Omit<U, NullableFlags>] {
return [_.pick(user, nullableFlags), _.omit(user, nullableFlags)];
return [pick(user, nullableFlags), omit(user, nullableFlags)];
}

View File

@@ -7,14 +7,15 @@ import {
beforeEach,
vi
} from 'vitest';
import { Certification } from '../../../../shared/config/certification-settings';
import { Certification } from '../../../../shared/config/certification-settings.js';
import {
defaultUserEmail,
defaultUserId,
devLogin,
setupServer,
superRequest
} from '../../../vitest.utils';
} from '../../../vitest.utils.js';
describe('certificate routes', () => {
setupServer();

View File

@@ -1,8 +1,8 @@
import type { CompletedChallenge } from '@prisma/client';
import isEmail from 'validator/lib/isEmail';
import validator from 'validator';
import type { FastifyPluginCallbackTypebox } from '@fastify/type-provider-typebox';
import { getChallenges } from '../../utils/get-challenges';
import { getChallenges } from '../../utils/get-challenges.js';
import {
certIds,
certSlugTypeMap,
@@ -12,13 +12,13 @@ import {
legacyCertifications,
legacyFullStackCertification,
upcomingCertifications
} from '../../../../shared/config/certification-settings';
} from '../../../../shared/config/certification-settings.js';
import * as schemas from '../../schemas';
import { normalizeChallenges, removeNulls } from '../../utils/normalize';
import * as schemas from '../../schemas.js';
import { normalizeChallenges, removeNulls } from '../../utils/normalize.js';
import { SHOW_UPCOMING_CHANGES } from '../../utils/env';
import { isKnownCertSlug } from '../helpers/certificate-utils';
import { SHOW_UPCOMING_CHANGES } from '../../utils/env.js';
import { isKnownCertSlug } from '../helpers/certificate-utils.js';
const {
legacyFrontEndChallengeId,
@@ -403,7 +403,7 @@ export const protectedCertificateRoutes: FastifyPluginCallbackTypebox = (
.map(x => certSlugTypeMap[x])
.every(certType => updatedIsCertMap[certType]);
const shouldSendCertifiedEmailToCamper =
email && isEmail(email) && hasCompletedAllCerts;
email && validator.default.isEmail(email) && hasCompletedAllCerts;
if (shouldSendCertifiedEmailToCamper) {
const notifyUser = {

View File

@@ -11,7 +11,7 @@ import {
vi.mock('../helpers/challenge-helpers', async () => {
const originalModule = await vi.importActual<
typeof import('../helpers/challenge-helpers')
typeof import('../helpers/challenge-helpers.js')
>('../helpers/challenge-helpers');
return {
@@ -23,12 +23,12 @@ vi.mock('../helpers/challenge-helpers', async () => {
/* eslint-disable @typescript-eslint/no-unsafe-member-access */
/* eslint-disable @typescript-eslint/no-unsafe-assignment */
import { omit } from 'lodash';
import { omit } from 'lodash-es';
import { Static } from '@fastify/type-provider-typebox';
import { DailyCodingChallengeLanguage } from '@prisma/client';
import request from 'supertest';
import { challengeTypes } from '../../../../shared/config/challenge-types';
import { challengeTypes } from '../../../../shared/config/challenge-types.js';
import {
defaultUserId,
devLogin,
@@ -38,7 +38,7 @@ import {
defaultUserEmail,
createSuperRequest,
defaultUsername
} from '../../../vitest.utils';
} from '../../../vitest.utils.js';
import {
completedExamChallengeOneCorrect,
completedExamChallengeTwoCorrect,
@@ -53,16 +53,28 @@ import {
examWithTwoCorrect,
examWithAllCorrect,
type ExamSubmission
} from '../../../__mocks__/exam';
import { Answer } from '../../utils/exam-types';
import type { getSessionUser } from '../../schemas/user/get-session-user';
import { verifyTrophyWithMicrosoft } from '../helpers/challenge-helpers';
} from '../../../__mocks__/exam.js';
import { Answer } from '../../utils/exam-types.js';
import type { getSessionUser } from '../../schemas/user/get-session-user.js';
import { verifyTrophyWithMicrosoft } from '../helpers/challenge-helpers.js';
const mockVerifyTrophyWithMicrosoft = vi.mocked(verifyTrophyWithMicrosoft);
const EXISTING_COMPLETED_DATE = new Date('2024-11-08').getTime();
const DATE_NOW = Date.now();
vi.mock('../helpers/challenge-helpers.js', async () => {
const originalModule = await vi.importActual<
typeof import('../helpers/challenge-helpers.js')
>('../helpers/challenge-helpers');
return {
__esModule: true,
...originalModule,
verifyTrophyWithMicrosoft: vi.fn()
};
});
const isValidChallengeCompletionErrorMsg = {
type: 'error',
message: 'That does not appear to be a valid challenge submission.'

View File

@@ -1,12 +1,13 @@
import { type FastifyPluginCallbackTypebox } from '@fastify/type-provider-typebox';
import jwt from 'jsonwebtoken';
import { uniqBy, matches } from 'lodash';
import { CompletedExam, ExamResults, SavedChallengeFile } from '@prisma/client';
import isURL from 'validator/lib/isURL';
import type { FastifyBaseLogger, FastifyInstance, FastifyReply } from 'fastify';
import { uniqBy, matches } from 'lodash-es';
import { challengeTypes } from '../../../../shared/config/challenge-types';
import * as schemas from '../../schemas';
import validator from 'validator';
import { challengeTypes } from '../../../../shared/config/challenge-types.js';
import * as schemas from '../../schemas.js';
import {
jsCertProjectIds,
multifileCertProjectIds,
@@ -15,28 +16,31 @@ import {
type CompletedChallenge,
saveUserChallengeData,
msTrophyChallenges
} from '../../utils/common-challenge-functions';
import { JWT_SECRET } from '../../utils/env';
} from '../../utils/common-challenge-functions.js';
import { JWT_SECRET } from '../../utils/env.js';
import {
formatCoderoadChallengeCompletedValidation,
formatProjectCompletedValidation
} from '../../utils/error-formatting';
import { getChallenges } from '../../utils/get-challenges';
import { ProgressTimestamp, getPoints } from '../../utils/progress';
} from '../../utils/error-formatting.js';
import { getChallenges } from '../../utils/get-challenges.js';
import { ProgressTimestamp, getPoints } from '../../utils/progress.js';
import {
validateExamFromDbSchema,
validateGeneratedExamSchema,
validateUserCompletedExamSchema,
validateExamResultsSchema
} from '../../utils/exam-schemas';
import { generateRandomExam, createExamResults } from '../../utils/exam';
} from '../../utils/exam-schemas.js';
import { generateRandomExam, createExamResults } from '../../utils/exam.js';
import {
canSubmitCodeRoadCertProject,
decodeFiles,
verifyTrophyWithMicrosoft
} from '../helpers/challenge-helpers';
import { UpdateReqType } from '../../utils';
import { normalizeChallengeType, normalizeDate } from '../../utils/normalize';
} from '../helpers/challenge-helpers.js';
import { UpdateReqType } from '../../utils/index.js';
import {
normalizeChallengeType,
normalizeDate
} from '../../utils/normalize.js';
interface JwtPayload {
userToken: string;
@@ -94,7 +98,7 @@ export const challengeRoutes: FastifyPluginCallbackTypebox = (
// - `solution` needs to exist, but does not have to be valid URL
// - `githubLink` needs to exist and be valid URL
if (challengeType === challengeTypes.backEndProject) {
if (!solution || !isURL(githubLink + '')) {
if (!solution || !validator.default.isURL(githubLink + '')) {
logger.warn(
{ solution, githubLink },
'Invalid backEndProject submission'
@@ -104,7 +108,7 @@ export const challengeRoutes: FastifyPluginCallbackTypebox = (
message: 'That does not appear to be a valid challenge submission.'
});
}
} else if (solution && !isURL(solution + '')) {
} else if (solution && !validator.default.isURL(solution + '')) {
logger.warn({ solution }, 'Invalid solution URL');
return void reply.code(403).send({
type: 'error',

View File

@@ -5,8 +5,8 @@ import {
setupServer,
defaultUserEmail,
defaultUserId
} from '../../../vitest.utils';
import { createUserInput } from '../../utils/create-user';
} from '../../../vitest.utils.js';
import { createUserInput } from '../../utils/create-user.js';
const testEWalletEmail = 'baz@bar.com';
const testSubscriptionId = 'sub_test_id';

View File

@@ -1,9 +1,9 @@
import { type FastifyPluginCallbackTypebox } from '@fastify/type-provider-typebox';
import Stripe from 'stripe';
import * as schemas from '../../schemas';
import { donationSubscriptionConfig } from '../../../../shared/config/donation-settings';
import { STRIPE_SECRET_KEY, HOME_LOCATION } from '../../utils/env';
import * as schemas from '../../schemas.js';
import { donationSubscriptionConfig } from '../../../../shared/config/donation-settings.js';
import { STRIPE_SECRET_KEY, HOME_LOCATION } from '../../utils/env.js';
/**
* Plugin for the donation endpoints requiring auth.

View File

@@ -1,5 +1,5 @@
export * from './certificate';
export * from './challenge';
export * from './donate';
export * from './settings';
export * from './user';
export * from './certificate.js';
export * from './challenge.js';
export * from './donate.js';
export * from './settings.js';
export * from './user.js';

View File

@@ -16,15 +16,15 @@ import {
createSuperRequest,
defaultUserId,
defaultUserEmail
} from '../../../vitest.utils';
import { formatMessage } from '../../plugins/redirect-with-message';
import { createUserInput } from '../../utils/create-user';
import { API_LOCATION, HOME_LOCATION } from '../../utils/env';
} from '../../../vitest.utils.js';
import { formatMessage } from '../../plugins/redirect-with-message.js';
import { createUserInput } from '../../utils/create-user.js';
import { API_LOCATION, HOME_LOCATION } from '../../utils/env.js';
import {
isPictureWithProtocol,
getWaitMessage,
validateSocialUrl
} from './settings';
} from './settings.js';
const baseProfileUI = {
isLocked: false,

View File

@@ -3,14 +3,12 @@ import type { FastifyInstance } from 'fastify';
import { differenceInMinutes } from 'date-fns';
import validator from 'validator';
import { isValidUsername } from '../../../../shared/utils/validate';
import * as schemas from '../../schemas';
import { createAuthToken, isExpired } from '../../utils/tokens';
import { API_LOCATION } from '../../utils/env';
import { getRedirectParams } from '../../utils/redirection';
import { isRestricted } from '../helpers/is-restricted';
const { isEmail } = validator;
import { isValidUsername } from '../../../../shared/utils/validate.js';
import * as schemas from '../../schemas.js';
import { createAuthToken, isExpired } from '../../utils/tokens.js';
import { API_LOCATION } from '../../utils/env.js';
import { getRedirectParams } from '../../utils/redirection.js';
import { isRestricted } from '../helpers/is-restricted.js';
type WaitMesssageArgs = {
sentAt: Date | null;
@@ -768,7 +766,7 @@ export const settingRedirectRoutes: FastifyPluginCallbackTypebox = (
const email = Buffer.from(req.query.email, 'base64').toString();
const { origin } = getRedirectParams(req);
if (!isEmail(email)) {
if (!validator.default.isEmail(email)) {
logger.warn(`Invalid email ${email}`);
return reply.redirectWithMessage(origin, redirectMessage);
}

View File

@@ -15,9 +15,9 @@ import {
import jwt, { JwtPayload } from 'jsonwebtoken';
import { DailyCodingChallengeLanguage, type Prisma } from '@prisma/client';
import { ObjectId } from 'mongodb';
import _ from 'lodash';
import { omit } from 'lodash-es';
import { createUserInput } from '../../utils/create-user';
import { createUserInput } from '../../utils/create-user.js';
import {
defaultUserId,
defaultUserEmail,
@@ -27,15 +27,15 @@ import {
createSuperRequest,
defaultUsername,
resetDefaultUser
} from '../../../vitest.utils';
import { JWT_SECRET } from '../../utils/env';
} from '../../../vitest.utils.js';
import { JWT_SECRET } from '../../utils/env.js';
import {
clearEnvExam,
seedEnvExam,
seedEnvExamAttempt,
seedExamEnvExamAuthToken
} from '../../../__mocks__/exam-environment-exam';
import { getMsTranscriptApiUrl } from './user';
} from '../../../__mocks__/exam-environment-exam.js';
import { getMsTranscriptApiUrl } from './user.js';
const mockedFetch = vi.fn();
vi.spyOn(globalThis, 'fetch').mockImplementation(mockedFetch);
@@ -43,7 +43,9 @@ vi.spyOn(globalThis, 'fetch').mockImplementation(mockedFetch);
let mockDeploymentEnv = 'staging';
vi.mock('../../utils/env', async () => {
const actualEnv =
await vi.importActual<typeof import('../../utils/env')>('../../utils/env');
await vi.importActual<typeof import('../../utils/env.js')>(
'../../utils/env'
);
return {
...actualEnv,
get DEPLOYMENT_ENV() {
@@ -828,7 +830,7 @@ describe('userRoutes', () => {
const setCookies = res.get('Set-Cookie');
const publicUser = {
..._.omit(minimalUserData, ['externalId', 'unsubscribeId']),
...omit(minimalUserData, ['externalId', 'unsubscribeId']),
...computedProperties,
id: testUser.id,
joinDate: new ObjectId(testUser.id).getTimestamp().toISOString(),

View File

@@ -1,18 +1,17 @@
import type { FastifyPluginCallbackTypebox } from '@fastify/type-provider-typebox';
import { ObjectId } from 'mongodb';
import _ from 'lodash';
import { FastifyInstance, FastifyReply } from 'fastify';
import jwt from 'jsonwebtoken';
import { PrismaClientKnownRequestError } from '@prisma/client/runtime/library';
import { PrismaClientKnownRequestError } from '@prisma/client/runtime/library.js';
import * as schemas from '../../schemas';
import * as examEnvironmentSchemas from '../../exam-environment/schemas';
import { createResetProperties } from '../../utils/create-user';
import { customNanoid } from '../../utils/ids';
import { encodeUserToken } from '../../utils/tokens';
import { trimTags } from '../../utils/validation';
import { generateReportEmail } from '../../utils/email-templates';
import { splitUser } from '../helpers/user-utils';
import * as schemas from '../../schemas.js';
import * as examEnvironmentSchemas from '../../exam-environment/schemas/index.js';
import { createResetProperties } from '../../utils/create-user.js';
import { customNanoid } from '../../utils/ids.js';
import { encodeUserToken } from '../../utils/tokens.js';
import { trimTags } from '../../utils/validation.js';
import { generateReportEmail } from '../../utils/email-templates.js';
import { splitUser } from '../helpers/user-utils.js';
import {
normalizeChallenges,
normalizeFlags,
@@ -20,20 +19,20 @@ import {
normalizeSurveys,
normalizeTwitter,
removeNulls
} from '../../utils/normalize';
import { mapErr, type UpdateReqType } from '../../utils';
} from '../../utils/normalize.js';
import { mapErr, type UpdateReqType } from '../../utils/index.js';
import {
getCalendar,
getPoints,
ProgressTimestamp
} from '../../utils/progress';
import { DEPLOYMENT_ENV, JWT_SECRET } from '../../utils/env';
} from '../../utils/progress.js';
import { DEPLOYMENT_ENV, JWT_SECRET } from '../../utils/env.js';
import {
getExamAttemptHandler,
getExamAttemptsByExamIdHandler,
getExamAttemptsHandler
} from '../../exam-environment/routes/exam-environment';
import { ERRORS } from '../../exam-environment/utils/errors';
} from '../../exam-environment/routes/exam-environment.js';
import { ERRORS } from '../../exam-environment/utils/errors.js';
/**
* Helper function to get the api url from the shared transcript link.

View File

@@ -1,6 +1,6 @@
import type { FastifyPluginCallback } from 'fastify';
import { devAuth } from '../../plugins/auth-dev';
import { devAuth } from '../../plugins/auth-dev.js';
/**
* Route handler for development login.

View File

@@ -4,8 +4,8 @@ import {
setupServer,
superRequest,
createSuperRequest
} from '../../../vitest.utils';
import { AUTH0_DOMAIN } from '../../utils/env';
} from '../../../vitest.utils.js';
import { AUTH0_DOMAIN } from '../../utils/env.js';
const mockedFetch = vi.fn();
vi.spyOn(globalThis, 'fetch').mockImplementation(mockedFetch);
@@ -28,7 +28,9 @@ const mockAuth0ValidEmail = () => ({
vi.mock('../../utils/env', async () => {
const actual =
await vi.importActual<typeof import('../../utils/env')>('../../utils/env');
await vi.importActual<typeof import('../../utils/env.js')>(
'../../utils/env'
);
return {
...actual,
FCC_ENABLE_DEV_LOGIN_MODE: false

View File

@@ -1,10 +1,10 @@
import type { FastifyPluginCallback, FastifyRequest } from 'fastify';
import isEmail from 'validator/lib/isEmail';
import validator from 'validator';
import { AUTH0_DOMAIN } from '../../utils/env';
import { auth0Client } from '../../plugins/auth0';
import { createAccessToken } from '../../utils/tokens';
import { findOrCreateUser } from '../helpers/auth-helpers';
import { AUTH0_DOMAIN } from '../../utils/env.js';
import { auth0Client } from '../../plugins/auth0.js';
import { createAccessToken } from '../../utils/tokens.js';
import { findOrCreateUser } from '../helpers/auth-helpers.js';
const getEmailFromAuth0 = async (
req: FastifyRequest
@@ -55,7 +55,7 @@ export const mobileAuth0Routes: FastifyPluginCallback = (
type: 'danger'
});
}
if (!isEmail(email)) {
if (!validator.default.isEmail(email)) {
logger.error('Email is incorrectly formatted for login');
return reply.status(400).send({

View File

@@ -14,8 +14,8 @@ import {
resetDefaultUser,
setupServer,
superRequest
} from '../../../vitest.utils';
import { getFallbackFullStackDate } from '../helpers/certificate-utils';
} from '../../../vitest.utils.js';
import { getFallbackFullStackDate } from '../helpers/certificate-utils.js';
const DATE_NOW = Date.now();

View File

@@ -1,19 +1,19 @@
import { type FastifyPluginCallbackTypebox } from '@fastify/type-provider-typebox';
import { find } from 'lodash';
import * as schemas from '../../schemas';
import { find } from 'lodash-es';
import * as schemas from '../../schemas.js';
import {
certSlugTypeMap,
certTypeTitleMap,
certTypeIdMap,
completionHours,
oldDataVizId
} from '../../../../shared/config/certification-settings';
} from '../../../../shared/config/certification-settings.js';
import {
getFallbackFullStackDate,
isKnownCertSlug
} from '../helpers/certificate-utils';
import { normalizeDate } from '../../utils/normalize';
} from '../helpers/certificate-utils.js';
import { normalizeDate } from '../../utils/normalize.js';
/**
* Plugin for the unprotected certificate endpoints.

View File

@@ -1,8 +1,8 @@
import request from 'supertest';
import { describe, test, expect } from 'vitest';
import { setupServer } from '../../../vitest.utils';
import { endpoints } from './deprecated-endpoints';
import { setupServer } from '../../../vitest.utils.js';
import { endpoints } from './deprecated-endpoints.js';
describe('Deprecated endpoints', () => {
setupServer();

View File

@@ -1,6 +1,6 @@
import { type FastifyPluginCallbackTypebox } from '@fastify/type-provider-typebox';
import * as schemas from '../../schemas';
import * as schemas from '../../schemas.js';
type Endpoints = [string, 'GET' | 'POST'][];

View File

@@ -1,7 +1,7 @@
import { describe, test, expect } from 'vitest';
import { setupServer, superRequest } from '../../../vitest.utils';
import { setupServer, superRequest } from '../../../vitest.utils.js';
import { unsubscribeEndpoints } from './deprecated-unsubscribe';
import { unsubscribeEndpoints } from './deprecated-unsubscribe.js';
const urlEncodedMessage =
'?messages=info%5B0%5D%3DWe%2520are%2520no%2520longer%2520able%2520to%2520process%2520this%2520unsubscription%2520request.%2520Please%2520go%2520to%2520your%2520settings%2520to%2520update%2520your%2520email%2520preferences';

View File

@@ -1,6 +1,6 @@
import { FastifyPluginCallbackTypebox } from '@fastify/type-provider-typebox';
import { getRedirectParams } from '../../utils/redirection';
import { getRedirectParams } from '../../utils/redirection.js';
type Endpoint = [string, 'GET' | 'POST'];

View File

@@ -1,5 +1,5 @@
import { describe, test, expect, beforeAll, vi } from 'vitest';
import { setupServer, superRequest } from '../../../vitest.utils';
import { setupServer, superRequest } from '../../../vitest.utils.js';
const testEWalletEmail = 'baz@bar.com';
const testSubscriptionId = 'sub_test_id';

View File

@@ -1,14 +1,14 @@
import { type FastifyPluginCallbackTypebox } from '@fastify/type-provider-typebox';
import Stripe from 'stripe';
import { STRIPE_SECRET_KEY } from '../../utils/env';
import { STRIPE_SECRET_KEY } from '../../utils/env.js';
import {
donationSubscriptionConfig,
allStripeProductIdsArray
} from '../../../../shared/config/donation-settings';
import * as schemas from '../../schemas';
import { inLastFiveMinutes } from '../../utils/validate-donation';
import { findOrCreateUser } from '../helpers/auth-helpers';
} from '../../../../shared/config/donation-settings.js';
import * as schemas from '../../schemas.js';
import { inLastFiveMinutes } from '../../utils/validate-donation.js';
import { findOrCreateUser } from '../helpers/auth-helpers.js';
/**
* Plugin for public donation endpoints.

View File

@@ -1,9 +1,9 @@
/* eslint-disable @typescript-eslint/no-unsafe-member-access */
import type { Prisma } from '@prisma/client';
import { describe, test, expect } from 'vitest';
import { setupServer, superRequest } from '../../../vitest.utils';
import { HOME_LOCATION } from '../../utils/env';
import { createUserInput } from '../../utils/create-user';
import { setupServer, superRequest } from '../../../vitest.utils.js';
import { HOME_LOCATION } from '../../utils/env.js';
import { createUserInput } from '../../utils/create-user.js';
const urlEncodedInfoMessage1 =
'?messages=info%5B0%5D%3DWe%2520could%2520not%2520find%2520an%2520account%2520to%2520unsubscribe.';

View File

@@ -1,6 +1,6 @@
import { type FastifyPluginCallbackTypebox } from '@fastify/type-provider-typebox';
import * as schemas from '../../schemas';
import { getRedirectParams } from '../../utils/redirection';
import * as schemas from '../../schemas.js';
import { getRedirectParams } from '../../utils/redirection.js';
/**
* Endpoints to set 'sendQuincyEmail' to true or false using 'unsubscribeId'.

View File

@@ -1,11 +1,11 @@
export * from './auth-dev';
export * from './auth';
export * from './certificate';
export * from './deprecated-endpoints';
export * from './deprecated-unsubscribe';
export * from './donate';
export * from './email-subscription';
export * from './signout';
export * from './status';
export * from './user';
export * from './sentry';
export * from './auth-dev.js';
export * from './auth.js';
export * from './certificate.js';
export * from './deprecated-endpoints.js';
export * from './deprecated-unsubscribe.js';
export * from './donate.js';
export * from './email-subscription.js';
export * from './signout.js';
export * from './status.js';
export * from './user.js';
export * from './sentry.js';

View File

@@ -1,8 +1,8 @@
import { type FastifyPluginCallbackTypebox } from '@fastify/type-provider-typebox';
import { type FastifyInstance, type FastifyReply } from 'fastify';
import { UpdateReqType } from '../../utils';
import * as schemas from '../../schemas';
import { UpdateReqType } from '../../utils/index.js';
import * as schemas from '../../schemas.js';
/**
* Plugin for Sentry-related endpoints.

View File

@@ -1,6 +1,6 @@
import { describe, it, expect, beforeEach } from 'vitest';
import { devLogin, setupServer, superRequest } from '../../../vitest.utils';
import { HOME_LOCATION } from '../../utils/env';
import { devLogin, setupServer, superRequest } from '../../../vitest.utils.js';
import { HOME_LOCATION } from '../../utils/env.js';
describe('GET /signout', () => {
setupServer();

View File

@@ -1,6 +1,6 @@
import type { FastifyPluginCallback } from 'fastify';
import { getRedirectParams } from '../../utils/redirection';
import { getRedirectParams } from '../../utils/redirection.js';
/**
* Route handler for signing out.

View File

@@ -1,6 +1,6 @@
import { describe, test, expect } from 'vitest';
import { setupServer, superRequest } from '../../../vitest.utils';
import { DEPLOYMENT_VERSION } from '../../utils/env';
import { setupServer, superRequest } from '../../../vitest.utils.js';
import { DEPLOYMENT_VERSION } from '../../utils/env.js';
describe('/status', () => {
setupServer();

View File

@@ -1,6 +1,6 @@
import { type FastifyPluginCallbackTypebox } from '@fastify/type-provider-typebox';
import { DEPLOYMENT_VERSION } from '../../utils/env';
import { DEPLOYMENT_VERSION } from '../../utils/env.js';
/**
* Plugin for the health check endpoint.

View File

@@ -1,6 +1,6 @@
import type { Prisma } from '@prisma/client';
import { ObjectId } from 'mongodb';
import _ from 'lodash';
import { omit } from 'lodash-es';
import {
describe,
it,
@@ -12,13 +12,13 @@ import {
vi
} from 'vitest';
import { createUserInput } from '../../utils/create-user';
import { createUserInput } from '../../utils/create-user.js';
import {
defaultUserEmail,
setupServer,
createSuperRequest
} from '../../../vitest.utils';
import { replacePrivateData } from './user';
} from '../../../vitest.utils.js';
import { replacePrivateData } from './user.js';
const mockedFetch = vi.fn();
vi.spyOn(globalThis, 'fetch').mockImplementation(mockedFetch);
@@ -378,7 +378,7 @@ describe('userRoutes', () => {
// it should contain the entire body.
const publicUser = {
// TODO(Post-MVP, maybe): return completedSurveys?
..._.omit(publicUserData, 'completedSurveys'),
...omit(publicUserData, 'completedSurveys'),
username: publicUsername,
joinDate: new ObjectId(testUser.id).getTimestamp().toISOString(),
profileUI: unlockedUserProfileUI
@@ -592,9 +592,7 @@ describe('get-public-profile helpers', () => {
});
test('returns the expected public user object if all showX flags are true', () => {
expect(replacePrivateData(user)).toEqual(
_.omit(user, ['id', 'profileUI'])
);
expect(replacePrivateData(user)).toEqual(omit(user, ['id', 'profileUI']));
});
});
});

View File

@@ -1,11 +1,11 @@
import { Portfolio } from '@prisma/client';
import { type FastifyPluginCallbackTypebox } from '@fastify/type-provider-typebox';
import { ObjectId } from 'mongodb';
import _ from 'lodash';
import { omit } from 'lodash-es';
import { isRestricted } from '../helpers/is-restricted';
import * as schemas from '../../schemas';
import { splitUser } from '../helpers/user-utils';
import { isRestricted } from '../helpers/is-restricted.js';
import * as schemas from '../../schemas.js';
import { splitUser } from '../helpers/user-utils.js';
import {
normalizeChallenges,
NormalizedChallenge,
@@ -13,14 +13,14 @@ import {
normalizeProfileUI,
normalizeTwitter,
removeNulls
} from '../../utils/normalize';
} from '../../utils/normalize.js';
import {
Calendar,
getCalendar,
getPoints,
ProgressTimestamp
} from '../../utils/progress';
import { challengeTypes } from '../../../../shared/config/challenge-types';
} from '../../utils/progress.js';
import { challengeTypes } from '../../../../shared/config/challenge-types.js';
type ProfileUI = Partial<{
isLocked: boolean;
@@ -137,7 +137,7 @@ export const userPublicGetRoutes: FastifyPluginCallbackTypebox = (
const [flags, rest] = splitUser(user);
const publicUser = _.omit(rest, [
const publicUser = omit(rest, [
'currentChallengeId',
'email',
'emailVerified',

View File

@@ -1,8 +1,8 @@
import { describe, test, expect } from 'vitest';
import Ajv from 'ajv';
import secureSchema from 'ajv/lib/refs/json-schema-secure.json';
import secureSchema from 'ajv/lib/refs/json-schema-secure.json' with { type: 'json' };
import { Ajv } from 'ajv';
import * as schemas from './schemas';
import * as schemas from './schemas.js';
// it's not strict, but that's okay - we're not using it to validate data
const ajv = new Ajv({ strictTypes: false });

View File

@@ -1,45 +1,45 @@
export { getPublicProfile } from './schemas/users/get-public-profile';
export { userExists } from './schemas/users/exists';
export { certSlug } from './schemas/certificate/cert-slug';
export { certificateVerify } from './schemas/certificate/certificate-verify';
export { backendChallengeCompleted } from './schemas/challenge/backend-challenge-completed';
export { coderoadChallengeCompleted } from './schemas/challenge/coderoad-challenge-completed';
export { exam } from './schemas/challenge/exam';
export { examChallengeCompleted } from './schemas/challenge/exam-challenge-completed';
export { dailyCodingChallengeCompleted } from './schemas/challenge/daily-coding-challenge-completed';
export { modernChallengeCompleted } from './schemas/challenge/modern-challenge-completed';
export { msTrophyChallengeCompleted } from './schemas/challenge/ms-trophy-challenge-completed';
export { projectCompleted } from './schemas/challenge/project-completed';
export { saveChallenge } from './schemas/challenge/save-challenge';
export { submitQuizAttempt } from './schemas/challenge/submit-quiz-attempt';
export { deprecatedEndpoints } from './schemas/deprecated';
export { addDonation } from './schemas/donate/add-donation';
export { chargeStripeCard } from './schemas/donate/charge-stripe-card';
export { chargeStripe } from './schemas/donate/charge-stripe';
export { createStripePaymentIntent } from './schemas/donate/create-stripe-payment-intent';
export { updateStripeCard } from './schemas/donate/update-stripe-card';
export { resubscribe } from './schemas/email-subscription/resubscribe';
export { unsubscribe } from './schemas/email-subscription/unsubscribe';
export { updateMyAbout } from './schemas/settings/update-my-about';
export { confirmEmail } from './schemas/settings/confirm-email';
export { updateMyClassroomMode } from './schemas/settings/update-my-classroom-mode';
export { updateMyEmail } from './schemas/settings/update-my-email';
export { updateMyHonesty } from './schemas/settings/update-my-honesty';
export { updateMyKeyboardShortcuts } from './schemas/settings/update-my-keyboard-shortcuts';
export { updateMyPortfolio } from './schemas/settings/update-my-portfolio';
export { updateMyPrivacyTerms } from './schemas/settings/update-my-privacy-terms';
export { updateMyProfileUI } from './schemas/settings/update-my-profile-ui';
export { updateMyQuincyEmail } from './schemas/settings/update-my-quincy-email';
export { updateMySocials } from './schemas/settings/update-my-socials';
export { updateMyTheme } from './schemas/settings/update-my-theme';
export { updateMyUsername } from './schemas/settings/update-my-username';
export { deleteMsUsername } from './schemas/user/delete-ms-username';
export { deleteMyAccount } from './schemas/user/delete-my-account';
export { deleteUserToken } from './schemas/user/delete-user-token';
export { getSessionUser } from './schemas/user/get-session-user';
export { postMsUsername } from './schemas/user/post-ms-username';
export { reportUser } from './schemas/user/report-user';
export { resetMyProgress } from './schemas/user/reset-my-progress';
export { submitSurvey } from './schemas/user/submit-survey';
export { userExamEnvironmentToken } from './schemas/user/exam-environment-token';
export { sentryPostEvent } from './schemas/sentry/event';
export { getPublicProfile } from './schemas/users/get-public-profile.js';
export { userExists } from './schemas/users/exists.js';
export { certSlug } from './schemas/certificate/cert-slug.js';
export { certificateVerify } from './schemas/certificate/certificate-verify.js';
export { backendChallengeCompleted } from './schemas/challenge/backend-challenge-completed.js';
export { coderoadChallengeCompleted } from './schemas/challenge/coderoad-challenge-completed.js';
export { exam } from './schemas/challenge/exam.js';
export { examChallengeCompleted } from './schemas/challenge/exam-challenge-completed.js';
export { dailyCodingChallengeCompleted } from './schemas/challenge/daily-coding-challenge-completed.js';
export { modernChallengeCompleted } from './schemas/challenge/modern-challenge-completed.js';
export { msTrophyChallengeCompleted } from './schemas/challenge/ms-trophy-challenge-completed.js';
export { projectCompleted } from './schemas/challenge/project-completed.js';
export { saveChallenge } from './schemas/challenge/save-challenge.js';
export { submitQuizAttempt } from './schemas/challenge/submit-quiz-attempt.js';
export { deprecatedEndpoints } from './schemas/deprecated/index.js';
export { addDonation } from './schemas/donate/add-donation.js';
export { chargeStripeCard } from './schemas/donate/charge-stripe-card.js';
export { chargeStripe } from './schemas/donate/charge-stripe.js';
export { createStripePaymentIntent } from './schemas/donate/create-stripe-payment-intent.js';
export { updateStripeCard } from './schemas/donate/update-stripe-card.js';
export { resubscribe } from './schemas/email-subscription/resubscribe.js';
export { unsubscribe } from './schemas/email-subscription/unsubscribe.js';
export { updateMyAbout } from './schemas/settings/update-my-about.js';
export { confirmEmail } from './schemas/settings/confirm-email.js';
export { updateMyClassroomMode } from './schemas/settings/update-my-classroom-mode.js';
export { updateMyEmail } from './schemas/settings/update-my-email.js';
export { updateMyHonesty } from './schemas/settings/update-my-honesty.js';
export { updateMyKeyboardShortcuts } from './schemas/settings/update-my-keyboard-shortcuts.js';
export { updateMyPortfolio } from './schemas/settings/update-my-portfolio.js';
export { updateMyPrivacyTerms } from './schemas/settings/update-my-privacy-terms.js';
export { updateMyProfileUI } from './schemas/settings/update-my-profile-ui.js';
export { updateMyQuincyEmail } from './schemas/settings/update-my-quincy-email.js';
export { updateMySocials } from './schemas/settings/update-my-socials.js';
export { updateMyTheme } from './schemas/settings/update-my-theme.js';
export { updateMyUsername } from './schemas/settings/update-my-username.js';
export { deleteMsUsername } from './schemas/user/delete-ms-username.js';
export { deleteMyAccount } from './schemas/user/delete-my-account.js';
export { deleteUserToken } from './schemas/user/delete-user-token.js';
export { getSessionUser } from './schemas/user/get-session-user.js';
export { postMsUsername } from './schemas/user/post-ms-username.js';
export { reportUser } from './schemas/user/report-user.js';
export { resetMyProgress } from './schemas/user/reset-my-progress.js';
export { submitSurvey } from './schemas/user/submit-survey.js';
export { userExamEnvironmentToken } from './schemas/user/exam-environment-token.js';
export { sentryPostEvent } from './schemas/sentry/event.js';

View File

@@ -1,6 +1,6 @@
import { Type } from '@fastify/type-provider-typebox';
import { Certification } from '../../../../shared/config/certification-settings';
import { genericError } from '../types';
import { Certification } from '../../../../shared/config/certification-settings.js';
import { genericError } from '../types.js';
export const certSlug = {
params: Type.Object({

View File

@@ -1,5 +1,5 @@
import { Type } from '@fastify/type-provider-typebox';
import { genericError, isCertMap } from '../types';
import { genericError, isCertMap } from '../types.js';
export const certificateVerify = {
// TODO(POST_MVP): Remove partial validation from route for schema validation

View File

@@ -1,5 +1,5 @@
import { Type } from '@fastify/type-provider-typebox';
import { genericError } from '../types';
import { genericError } from '../types.js';
export const backendChallengeCompleted = {
body: Type.Object({

View File

@@ -1,5 +1,5 @@
import { Type } from '@fastify/type-provider-typebox';
import { examResults, genericError } from '../types';
import { examResults, genericError } from '../types.js';
export const examChallengeCompleted = {
body: Type.Object({

View File

@@ -1,5 +1,5 @@
import { Type } from '@fastify/type-provider-typebox';
import { genericError } from '../types';
import { genericError } from '../types.js';
export const exam = {
params: Type.Object({

View File

@@ -1,5 +1,5 @@
import { Type } from '@fastify/type-provider-typebox';
import { genericError, savedChallenge } from '../types';
import { genericError, savedChallenge } from '../types.js';
export const modernChallengeCompleted = {
body: Type.Object({

View File

@@ -1,5 +1,5 @@
import { Type } from '@fastify/type-provider-typebox';
import { genericError } from '../types';
import { genericError } from '../types.js';
export const projectCompleted = {
body: Type.Object({

View File

@@ -1,5 +1,5 @@
import { Type } from '@fastify/type-provider-typebox';
import { file, genericError, savedChallenge } from '../types';
import { file, genericError, savedChallenge } from '../types.js';
export const saveChallenge = {
body: Type.Object({

View File

@@ -1,5 +1,5 @@
import { Type } from '@fastify/type-provider-typebox';
import { genericError } from '../types';
import { genericError } from '../types.js';
export const submitQuizAttempt = {
body: Type.Object({

View File

@@ -1,5 +1,5 @@
import { Type } from '@fastify/type-provider-typebox';
import { genericError } from '../types';
import { genericError } from '../types.js';
export const updateStripeCard = {
body: Type.Object({}),

View File

@@ -1,5 +1,5 @@
import { Type } from '@fastify/type-provider-typebox';
import { genericError } from '../types';
import { genericError } from '../types.js';
export const updateMyClassroomMode = {
body: Type.Object({

Some files were not shown because too many files have changed in this diff Show More