Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,7 @@ packages/**/yarn.lock
########
# root
.claude/settings.local.json
.claude/worktrees/
.idea/
.mcp.json
.vscode/
Expand Down
9 changes: 9 additions & 0 deletions jest.config.js
Original file line number Diff line number Diff line change
Expand Up @@ -119,8 +119,17 @@ const requestedProject = process.argv.find((arg) =>
Object.prototype.hasOwnProperty.call(projectMap, arg),
);

const mongoProjects = ["backend", "scripts"];
const needsMongoSetup =
!requestedProject || mongoProjects.includes(requestedProject);

/** @type { import("jest").Config } */
const config = {
globalSetup: needsMongoSetup ? "@shelf/jest-mongodb/lib/setup" : undefined,
globalTeardown: needsMongoSetup
? "@shelf/jest-mongodb/lib/teardown"
: undefined,

// All imported modules in your tests should be mocked automatically
// automock: false,

Expand Down
7 changes: 3 additions & 4 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -18,14 +18,13 @@
"@compass/scripts": "packages/scripts/src"
},
"scripts": {
"cli": "cross-env TZ=Etc/UTC yarn ts-node --pretty --transpile-only -r tsconfig-paths/register packages/scripts/src/cli",
"cli": "cross-env TZ=Etc/UTC node --env-file=packages/backend/.env.local -r ts-node/register/transpile-only -r tsconfig-paths/register packages/scripts/src/cli.ts",
"debug:supertokens": "export DEBUG=com.supertokens && yarn dev:backend",
"debug:web": "http-server build/web/",
"dev:backend": "yarn dev:backend-start",
"dev:backend -verbose": "export DEBUG=* && yarn dev:backend",
"dev:backend-start": "cd packages/backend && cross-env-shell TZ=Etc/UTC ts-node-dev --respawn --pretty --exit-child --debounce --transpile-only -r tsconfig-paths/register src/app.ts",
"dev:backend": "cd packages/backend && cross-env TZ=Etc/UTC node --env-file=.env.local ../../node_modules/ts-node-dev/lib/bin.js --respawn --pretty --exit-child --debounce --transpile-only -r tsconfig-paths/register src/app.ts",
"dev:update": "git checkout main && git pull && yarn",
"dev:web": "cd packages/web && yarn webpack serve --mode=development --node-env=local",
"dev:web": "cd packages/web && node --env-file=../backend/.env.local ../../node_modules/.bin/webpack serve --mode=development --node-env=local",
"postinstall": "husky install | chmod ug+x .husky/*",
"test": "cross-env TZ=Etc/UTC ./node_modules/.bin/jest",
"test:e2e": "playwright test",
Expand Down
13 changes: 6 additions & 7 deletions packages/backend/.env.local.example
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
# This is an example environment file
# Rename it to .env, replace the values, and
# Rename it to .env.local, replace the values, and
# restart the backend.

# Don't ever commit this file or share its contents.
Expand Down Expand Up @@ -56,13 +56,11 @@ SUPERTOKENS_URI=UNIQUE_URI_FROM_YOUR_SUPERTOKENS_ACCOUNT
SUPERTOKENS_KEY=UNIQUE_KEY_FROM_YOUR_SUPERTOKENS_ACCOUNT

####################################################
# 5. CLI (optional) #
# 5. Web #
####################################################
# Set these values to save time while using the CLI

# LOCAL_DOMAIN=localhost:3000
# STAGING_DOMAIN=staging.yourdomain.com
# PROD_DOMAIN=app.yourdomain.com
LOCAL_WEB_URL=http://localhost:9080
# STAGING_WEB_URL=https://staging.yourdomain.com
# PROD_WEB_URL=https://app.yourdomain.com

####################################################
# 6. Email - Kit (optional) #
Expand All @@ -74,6 +72,7 @@ SUPERTOKENS_KEY=UNIQUE_KEY_FROM_YOUR_SUPERTOKENS_ACCOUNT
# EMAILER_API_SECRET=UNIQUE_SECRET_FROM_YOUR_KIT_ACCOUNT
# EMAILER_USER_TAG_ID=YOUR_USER_TAG_ID


####################################################
# 7. Gcal Sync - NGrok (optional) #
####################################################
Expand Down
1 change: 0 additions & 1 deletion packages/backend/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,6 @@
"@googleapis/calendar": "^14.1.0",
"chalk": "4.1.2",
"cors": "^2.8.5",
"dotenv": "^16.0.0",
"exponential-backoff": "^3.1.2",
"express": "^4.17.1",
"helmet": "^7.0.0",
Expand Down
1 change: 1 addition & 0 deletions packages/backend/src/__tests__/backend.test.init.ts
Original file line number Diff line number Diff line change
Expand Up @@ -15,3 +15,4 @@ process.env["EMAILER_API_SECRET"] = "emailerApiSecret";
process.env["EMAILER_USER_TAG_ID"] = "910111213";
process.env["TOKEN_GCAL_NOTIFICATION"] = "secretToken1";
process.env["TOKEN_COMPASS_SYNC"] = "secretToken2";
process.env["LOCAL_WEB_URL"] = "http://localhost:9080";
2 changes: 2 additions & 0 deletions packages/backend/src/common/constants/env.constants.ts
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,7 @@ const EnvSchema = z
DB: z.string().nonempty(),
EMAILER_SECRET: z.string().nonempty().optional(),
EMAILER_USER_TAG_ID: z.string().nonempty().optional(),
LOCAL_WEB_URL: z.string().url(),
MONGO_URI: z.string().nonempty(),
NODE_ENV: z.nativeEnum(NodeEnv),
TZ: z.enum(["Etc/UTC", "UTC"]),
Expand Down Expand Up @@ -56,6 +57,7 @@ const processEnv = {
DB: IS_DEV ? "dev_calendar" : "prod_calendar",
EMAILER_SECRET: process.env["EMAILER_API_SECRET"],
EMAILER_USER_TAG_ID: process.env["EMAILER_USER_TAG_ID"],
LOCAL_WEB_URL: process.env["LOCAL_WEB_URL"],
MONGO_URI: process.env["MONGO_URI"],
NODE_ENV: _nodeEnv,
TZ: process.env["TZ"],
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -245,7 +245,7 @@ describe("supertokens.middleware", () => {
const emailPasswordConfig = (EmailPassword.init as jest.Mock).mock
.calls[0][0] as {
emailDelivery: {
service: {
override: (originalImplementation: { sendEmail: jest.Mock }) => {
sendEmail: (input: {
passwordResetLink: string;
user: { email: string };
Expand All @@ -254,15 +254,23 @@ describe("supertokens.middleware", () => {
};
};

await emailPasswordConfig.emailDelivery.service.sendEmail({
const originalSendEmail = jest.fn().mockResolvedValue(undefined);
const overridden = emailPasswordConfig.emailDelivery.override({
sendEmail: originalSendEmail,
});

await overridden.sendEmail({
passwordResetLink:
"http://localhost:1234/auth/reset-password?token=abc",
user: { email: "user@example.com" },
});

expect(buildResetPasswordLink).toHaveBeenCalledWith(
"http://localhost:1234/auth/reset-password?token=abc",
ENV.LOCAL_WEB_URL,
);
// In test env, sending is suppressed — originalSendEmail must not be called
expect(originalSendEmail).not.toHaveBeenCalled();
});

it("calls googleAuthService.handleGoogleAuth when ThirdParty signInUpPOST succeeds", async () => {
Expand Down
20 changes: 12 additions & 8 deletions packages/backend/src/common/middleware/supertokens.middleware.ts
Original file line number Diff line number Diff line change
Expand Up @@ -21,7 +21,6 @@ import { Status } from "@core/errors/status.codes";
import { Logger } from "@core/logger/winston.logger";
import { zObjectId } from "@core/types/type.utils";
import googleAuthService from "@backend/auth/services/google/google.auth.service";
import { IS_DEV } from "@backend/common/constants/env.constants";
import { ENV } from "@backend/common/constants/env.constants";
import {
type CreateGoogleSignInResponse,
Expand Down Expand Up @@ -175,22 +174,27 @@ export const initSupertokens = () => {
],
},
emailDelivery: {
service: {
override: (originalImplementation) => ({
...originalImplementation,
sendEmail: async (input) => {
const resetLink = buildResetPasswordLink(input.passwordResetLink);
const resetLink = buildResetPasswordLink(
input.passwordResetLink,
ENV.LOCAL_WEB_URL,
);

if (ENV.NODE_ENV === "test" || IS_DEV) {
if (ENV.NODE_ENV === "test") {
logger.info(
`Password reset link for ${input.user.email}: ${resetLink}`,
);
return;
}

logger.info(
`Password reset requested for ${input.user.email}; email delivery is disabled in this environment.`,
);
await originalImplementation.sendEmail({
...input,
passwordResetLink: resetLink,
});
},
},
}),
},
override: {
functions(originalImplementation) {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -91,10 +91,12 @@ describe("supertokens.middleware.util", () => {
describe("buildResetPasswordLink", () => {
it("rewrites reset links into the app auth flow", () => {
const token = faker.string.alphanumeric(32);
const frontendUrl = "http://localhost:9080";

expect(
buildResetPasswordLink(
`http://localhost:3567/auth/reset-password?token=${token}`,
frontendUrl,
),
).toBe(`http://localhost:9080/day?auth=reset&token=${token}`);
});
Expand All @@ -103,7 +105,9 @@ describe("supertokens.middleware.util", () => {
const passwordResetLink =
"http://localhost:3567/auth/reset-password?foo=bar";

expect(buildResetPasswordLink(passwordResetLink)).toBe(passwordResetLink);
expect(
buildResetPasswordLink(passwordResetLink, "http://localhost:9080"),
).toBe(passwordResetLink);
});
});

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,6 @@ import { ObjectId } from "mongodb";
import { createUserIdMapping, getUserIdMapping } from "supertokens-node";
import type { SessionContainerInterface } from "supertokens-node/recipe/session/types";
import type { APIInterface } from "supertokens-node/recipe/thirdparty/types";
import { PORT_DEFAULT_WEB } from "@core/constants/core.constants";
import { type GoogleSignInSuccess } from "@backend/auth/services/google/google.auth.types";

type ThirdPartySignInUpPost = NonNullable<APIInterface["signInUpPOST"]>;
Expand Down Expand Up @@ -33,15 +32,18 @@ export function getFormFieldValue(
return typeof field?.value === "string" ? field.value : undefined;
}

export function buildResetPasswordLink(passwordResetLink: string): string {
export function buildResetPasswordLink(
passwordResetLink: string,
frontendUrl: string,
): string {
const url = new URL(passwordResetLink);
const token = url.searchParams.get("token");

if (!token) {
return passwordResetLink;
}

const appUrl = new URL(`http://localhost:${PORT_DEFAULT_WEB}/day`);
const appUrl = new URL(`${frontendUrl}/day`);
appUrl.searchParams.set("auth", "reset");
appUrl.searchParams.set("token", token);

Expand Down
6 changes: 0 additions & 6 deletions packages/backend/src/common/services/mongo.service.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,6 @@ import {
type ClientSessionOptions,
type Collection,
type ConnectionClosedEvent,
type ConnectionReadyEvent,
type Db,
MongoClient,
ObjectId,
Expand Down Expand Up @@ -108,10 +107,6 @@ class MongoService {
logger.error(error.message, error);
}

private onReady(event: ConnectionReadyEvent): void {
logger.debug(`Connected to database: '${event.address}'`);
}

private onClose(event: ConnectionClosedEvent): void {
logger.debug(`Connection to database: '${event.address}' closed`);
}
Expand Down Expand Up @@ -175,7 +170,6 @@ class MongoService {
client.on("close", this.onDisconnect.bind(this));
client.on("error", this.onError.bind(this));
client.on("connectionClosed", this.onClose.bind(this));
client.on("connectionReady", this.onReady.bind(this));

return waitUntilEvent<MongoClient[], MongoService>(
client,
Expand Down
6 changes: 0 additions & 6 deletions packages/backend/src/init.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,4 @@
// sort-imports-ignore
import dotenv from "dotenv";
import moduleAlias from "module-alias";
import path from "path";

Expand All @@ -14,9 +13,4 @@ aliasApi.addAliases({

import { Logger } from "@core/logger/winston.logger";

const dotenvResult = dotenv.config();
if (dotenvResult.error) {
throw dotenvResult.error;
}

export const logger = Logger("app:root");
1 change: 0 additions & 1 deletion packages/scripts/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,6 @@
"@compass/core": "1.0.0",
"@inquirer/prompts": "^8.3.0",
"commander": "^10.0.0",
"dotenv": "^16.0.1",
"inquirer": "^8.0.0",
"open": "^10.1.0",
"shelljs": "^0.8.5",
Expand Down
5 changes: 1 addition & 4 deletions packages/scripts/src/cli.ts
Original file line number Diff line number Diff line change
@@ -1,13 +1,10 @@
// sort-imports-ignore
import "@scripts/init";

import { Command } from "commander";
import { CliValidator } from "@scripts/cli.validator";
import { runBuild } from "@scripts/commands/build";
import { startDeleteFlow } from "@scripts/commands/delete";
import { runMigrator } from "@scripts/commands/migrate";
import { ALL_PACKAGES, ENVIRONMENT } from "@scripts/common/cli.constants";
import { MigratorType } from "@scripts/common/cli.types";
import { Command } from "commander";

export default class CompassCLI {
private program: Command;
Expand Down
19 changes: 7 additions & 12 deletions packages/scripts/src/commands/delete.ts
Original file line number Diff line number Diff line change
Expand Up @@ -41,29 +41,24 @@ const getCleanupUrl = (): string => {
const env = process.env["NODE_ENV"] as Environment_Cli;

if (env === ENVIRONMENT.PROD) {
if (!CLI_ENV.PROD_DOMAIN) {
if (!CLI_ENV.PROD_WEB_URL) {
throw new Error(
'Unable to determine cleanup URL. NODE_ENV="production" but PROD_DOMAIN is not set.',
'Unable to determine cleanup URL. NODE_ENV="production" but PROD_WEB_URL is not set.',
);
}
return `https://${CLI_ENV.PROD_DOMAIN}/cleanup`;
return `${CLI_ENV.PROD_WEB_URL}/cleanup`;
}

if (env === ENVIRONMENT.STAG) {
if (!CLI_ENV.STAGING_DOMAIN) {
if (!CLI_ENV.STAGING_WEB_URL) {
throw new Error(
'Unable to determine cleanup URL. NODE_ENV="staging" but STAGING_DOMAIN is not set.',
'Unable to determine cleanup URL. NODE_ENV="staging" but STAGING_WEB_URL is not set.',
);
}
return `https://${CLI_ENV.STAGING_DOMAIN}/cleanup`;
return `${CLI_ENV.STAGING_WEB_URL}/cleanup`;
}

if (!CLI_ENV.LOCAL_WEB_DOMAIN) {
throw new Error(
'Unable to determine cleanup URL. NODE_ENV="local" but LOCAL_WEB_DOMAIN is not set.',
);
}
return `http://${CLI_ENV.LOCAL_WEB_DOMAIN}/cleanup`;
return `${CLI_ENV.LOCAL_WEB_URL}/cleanup`;
};

/**
Expand Down
16 changes: 11 additions & 5 deletions packages/scripts/src/common/cli.constants.ts
Original file line number Diff line number Diff line change
@@ -1,3 +1,10 @@
type CliEnv = {
LOCAL_WEB_URL: string;
STAGING_WEB_URL: string | undefined;
PROD_WEB_URL: string | undefined;
DEV_BROWSER: string | undefined;
};

export const ALL_PACKAGES = ["nodePckgs", "web"];

export const COMPASS_ROOT_DEV = process.cwd();
Expand All @@ -15,10 +22,9 @@ export const ENVIRONMENT = {
PROD: "production",
};

export const CLI_ENV = {
LOCAL_DOMAIN: process.env["LOCAL_DOMAIN"] || `localhost:3000`,
LOCAL_WEB_DOMAIN: process.env["LOCAL_WEB_DOMAIN"] || `localhost:9080`,
STAGING_DOMAIN: process.env["STAGING_DOMAIN"],
PROD_DOMAIN: process.env["PROD_DOMAIN"],
export const CLI_ENV: CliEnv = {
LOCAL_WEB_URL: process.env["LOCAL_WEB_URL"] || `http://localhost:9080`,
STAGING_WEB_URL: process.env["STAGING_WEB_URL"],
PROD_WEB_URL: process.env["PROD_WEB_URL"],
DEV_BROWSER: process.env["DEV_BROWSER"],
};
Loading
Loading