diff --git a/.env.example b/.env.example index 744d577..2f99e82 100644 --- a/.env.example +++ b/.env.example @@ -1,28 +1,11 @@ -# Rename this file to `.env` and fill in the values. # You already have access to basic local functionality (UI, authentication, database read access). -# Optional variables for the backend server functionality (modifying user data, etc.) - -# For Firebase access. -# Open a GitHub issue with your contribution ideas and an admin will give you the key. -# TODO: find a way to give anyone moderate access to dev firebase. -GOOGLE_APPLICATION_CREDENTIALS_DEV="[...].json" - -# The URL where your local backend server is running. -# You can change the port if needed. -NEXT_PUBLIC_API_URL=localhost:8088 - - # Optional variables for full local functionality # For the location / distance filtering features. # Create a free account at https://rapidapi.com/wirefreethought/api/geodb-cities and get an API key. GEODB_API_KEY= -# For analytics like page views, user actions, feature usage, etc. -# Create a free account at https://posthog.com and get a project API key. Should start with "phc_". -POSTHOG_KEY= - # For sending emails (e.g. for user sign up, password reset, notifications, etc.). # Create a free account at https://resend.com and get an API key. Should start with "re_". -RESEND_API_KEY= \ No newline at end of file +RESEND_KEY= \ No newline at end of file diff --git a/README.md b/README.md index f21507a..1824cb1 100644 --- a/README.md +++ b/README.md @@ -100,6 +100,8 @@ yarn install ### Environment Variables +Almost all the features will work out of the box, so you can skip this step and come back later if you need to test the following services: email, geolocation. + We can't make the following information public, for security and privacy reasons: - Database, otherwise anyone could access all the user data (including private messages) - Firebase, otherwise anyone could remove users or modify the media files @@ -108,12 +110,7 @@ We can't make the following information public, for security and privacy reasons We separate all those services between production and local development, so that you can code freely without impacting the functioning of the platform. Contributors should use the default keys for local development. Production uses a separate environment with stricter rules and private keys that are not shared. -Most of the code will work out of the box. All you need to do is creating an `.env` file as a copy of `.env.example`: -```bash -cp .env.example .env -``` - -If you do need one of the few remaining services, you need to store your own secrets as environment variables. To do so, simply open `.env` and fill in the variables according to the instructions in the file. +If you do need one of the few remaining services, you need to set them up and store your own secrets as environment variables. To do so, simply open `.env` and fill in the variables according to the instructions in the file. ### Tests diff --git a/backend/api/src/serve.ts b/backend/api/src/serve.ts index db881fd..1e4ccd8 100644 --- a/backend/api/src/serve.ts +++ b/backend/api/src/serve.ts @@ -1,14 +1,14 @@ import * as admin from 'firebase-admin' -import {getLocalEnv, initAdmin} from 'shared/init-admin' -import {loadSecretsToEnv, getServiceAccountCredentials} from 'common/secrets' +import {initAdmin} from 'shared/init-admin' +import {loadSecretsToEnv} from 'common/secrets' import {log} from 'shared/utils' -import {LOCAL_DEV} from "common/envs/constants"; +import {IS_LOCAL} from "common/envs/constants"; import {METRIC_WRITER} from 'shared/monitoring/metric-writer' import {listen as webSocketListen} from 'shared/websockets/server' log('Api server starting up....') -if (LOCAL_DEV) { +if (IS_LOCAL) { initAdmin() } else { const projectId = process.env.GOOGLE_CLOUD_PROJECT @@ -21,9 +21,10 @@ if (LOCAL_DEV) { METRIC_WRITER.start() import {app} from './app' +import {getServiceAccountCredentials} from "shared/firebase-utils"; -const credentials = LOCAL_DEV - ? getServiceAccountCredentials(getLocalEnv()) +const credentials = IS_LOCAL + ? getServiceAccountCredentials() : // No explicit credentials needed for deployed service. undefined @@ -37,6 +38,5 @@ const startupProcess = async () => { }) webSocketListen(httpServer, '/ws') - log('Server started successfully') } -startupProcess() +startupProcess().then(r => log('Server started successfully')) diff --git a/backend/email/emails/functions/send-email.ts b/backend/email/emails/functions/send-email.ts index c7b0550..16529d3 100644 --- a/backend/email/emails/functions/send-email.ts +++ b/backend/email/emails/functions/send-email.ts @@ -20,6 +20,7 @@ export const sendEmail = async ( console.log(resend, payload, options) async function sendEmailThrottle(data: any, options: any) { + if (!resend) return { data: null, error: 'No Resend client' } return limit(() => resend.emails.send(data, options)) } @@ -45,6 +46,11 @@ let resend: Resend | null = null const getResend = () => { if (resend) return resend + if (!process.env.RESEND_KEY) { + console.log('No RESEND_KEY, skipping email send') + return + } + const apiKey = process.env.RESEND_KEY as string // console.log(`RESEND_KEY: ${apiKey}`) resend = new Resend(apiKey) diff --git a/backend/scripts/run-script.ts b/backend/scripts/run-script.ts index 9b111e9..0c314a2 100644 --- a/backend/scripts/run-script.ts +++ b/backend/scripts/run-script.ts @@ -1,22 +1,19 @@ -import { getLocalEnv, initAdmin } from 'shared/init-admin' -import { getServiceAccountCredentials, loadSecretsToEnv } from 'common/secrets' -import { - createSupabaseDirectClient, - type SupabaseDirectClient, -} from 'shared/supabase/init' +import {initAdmin} from 'shared/init-admin' +import {loadSecretsToEnv} from 'common/secrets' +import {createSupabaseDirectClient, type SupabaseDirectClient,} from 'shared/supabase/init' +import {getServiceAccountCredentials} from "shared/firebase-utils"; initAdmin() export const runScript = async ( main: (services: { pg: SupabaseDirectClient }) => Promise | any ) => { - const env = getLocalEnv() - const credentials = getServiceAccountCredentials(env) + const credentials = getServiceAccountCredentials() await loadSecretsToEnv(credentials) const pg = createSupabaseDirectClient() - await main({ pg }) + await main({pg}) process.exit() } diff --git a/backend/shared/src/constants.ts b/backend/shared/src/constants.ts new file mode 100644 index 0000000..71c0768 --- /dev/null +++ b/backend/shared/src/constants.ts @@ -0,0 +1,3 @@ +export const getLocalEnv = () => { + return (process.env.ENVIRONMENT?.toUpperCase() ?? 'DEV') as 'PROD' | 'DEV' +} \ No newline at end of file diff --git a/backend/shared/src/firebase-utils.ts b/backend/shared/src/firebase-utils.ts new file mode 100644 index 0000000..a453edc --- /dev/null +++ b/backend/shared/src/firebase-utils.ts @@ -0,0 +1,23 @@ +import {readFileSync} from "fs"; +import {ENV_CONFIG} from "common/envs/constants"; + +export const getServiceAccountCredentials = () => { + let keyPath = ENV_CONFIG.googleApplicationCredentials + if (keyPath == null) { + throw new Error( + `Please set the GOOGLE_APPLICATION_CREDENTIALS environment variable to contain the path to your key file.` + ) + } + + if (!keyPath.startsWith('/')) { + // Make relative paths relative to the current file + keyPath = __dirname + '/' + keyPath + // console.log(keyPath) + } + + try { + return JSON.parse(readFileSync(keyPath, {encoding: 'utf8'})) + } catch (e) { + throw new Error(`Failed to load service account key from ${keyPath}: ${e}`) + } +} \ No newline at end of file diff --git a/backend/shared/src/googleApplicationCredentials-dev.json b/backend/shared/src/googleApplicationCredentials-dev.json new file mode 100644 index 0000000..224eaf6 --- /dev/null +++ b/backend/shared/src/googleApplicationCredentials-dev.json @@ -0,0 +1,13 @@ +{ + "type": "service_account", + "project_id": "compass-57c3c", + "private_key_id": "ce2c7ef4aa137dd4726a8c9398c199ce051d4168", + "private_key": "-----BEGIN PRIVATE KEY-----\nMIIEvgIBADANBgkqhkiG9w0BAQEFAASCBKgwggSkAgEAAoIBAQCNkwHW3buaXX8K\nYWZMIPtHfObK1EmbsIbjbFZtWqcn1Ouw3nj/hCaQQhFmlAvGIP/P0A6d/mlr704o\nK26otjK3S290y/VhI3f2AoMh9af8ptK9mjMbmpwQI7cRu87M/EhbBlYeovntcNfj\n11tsMg83RYUIPXKuQddm2AejRDyhoHsB/QTdSLjDfUIR2pVvHSVbbQwm1cKiFAev\nPm01X3BFLOkzhUCYuzLI1hGXf7G4xm4XCTi2UQGZkI2FYjOgrQcNvkTB33DF0sze\nZCpc8iJsUyQYZxxvsxXd20CwPArsgy+6FU2+dGRrSE6lzwqYU9mnizqtjdp+VUh8\nkppRgZH7AgMBAAECggEAFxMKPjR2grLRZWY5j5fijKTBUvalpp/vZDrAnWMkklvk\nLDgeXXry9Bkoj+D6SEkRmJPPBhY0pXhj8y0dBJdpjbFYUZ96d2IaB7kiGVNaFVY1\nS9zJjqq02/aOPHAxRPyraFaQi77BYF8/eK2dg3VnQHlutMibG+a0TllQaV5SSX9J\n7cj4C6RX2p8Zvrmu6RsQ+dQWZFMwT8oHhuKCrL8+iw6bXYH1bSvxHLS+sCreIK7d\nYvY/DPxlC1rGaK8ovH5nuc3nQ+ECsWcDjcqjg4SmI7VwPpWeJ97exf3BGUmSFSOC\nCNfWu6bqhWleBkRozRCAuzQwesLkr38MsjU1iZS4VQKBgQDAG75trRLUHvuCoPpu\nHw4Ev1qpqVa1rE/16Zmzp7/wzoOP7yqoW2Og2adE5N2KUxR48nAMnR/uwRQcvlhX\n2Y7qEprl0AkXRXpneGiPFD/vzkTAjRWZdd0sIpo3+KCZcoO9OsJ8eA5Ch0bthv6q\nJBjd9VwgIaS5gd/eX76xBMV6ZQKBgQC8qL+bc6S3EiGoGf+Fzm+XXfXuVn0VA2sx\nhJjV/tfxxv6EIO90COsKy3CgaNOjU+NZ6jx+Kq07c4HBPA3c43qSRNUkWfQyyQgA\nPV3f5ZP40z+U4QnbhWSVSch91FCxwAnL9N7/KM77gAsHCdUuMlCVAYDr6JfDZiJR\nQ9X3cfLk3wKBgHn4o31rJ8s6KKIVpysH2JS3Ec8qzvzl/Ja7zHS+iyVPWUSnq0Pd\nUnIr/wHE9cv/V746312C3WVvfV+KkvikDxMa4PIMldkKqd7MGkbNqpKNOiWu7gnT\nRaviBFyJJR6IEJCyoAz7BMLEtQnWbhaEeK1kPSvBcJ6/kO3ViHNH/kHpAoGBAJ2Z\nLi8TBOc1y03dIfrKP6goAtiuAWF7cKF2DiK99/DudhE0XjQFeyuSVSx7RUisPEER\njqUqy3ndfOhKXZ5HnU3xGEh8qKWAECH7IZ927gyvk+6vqwdpwGOBtm1+3kYOkWCC\n14I5ueaYyR2BFke4Gl7PWb44mAbQHBzc2TITS3/rAoGBAKsCx9IQk1p0Am/+/NcW\nBnxnE5iNmaX3H4+pmtShl8gXxaI1DWnB7WbqjDOCu9HCnPZUdLXbrserny+ZSyNU\nnHE1InQn7T/xM7RXZRkZk09qmSRicXh6kS+cSBg1xU5vIPPsBTrWVmg74L9guNdo\nYQXUSGJaJbCZ6N35oz+Qf3NB\n-----END PRIVATE KEY-----\n", + "client_email": "dev-contributors@compass-57c3c.iam.gserviceaccount.com", + "client_id": "118371540020807340605", + "auth_uri": "https://accounts.google.com/o/oauth2/auth", + "token_uri": "https://oauth2.googleapis.com/token", + "auth_provider_x509_cert_url": "https://www.googleapis.com/oauth2/v1/certs", + "client_x509_cert_url": "https://www.googleapis.com/robot/v1/metadata/x509/dev-contributors%40compass-57c3c.iam.gserviceaccount.com", + "universe_domain": "googleapis.com" +} diff --git a/backend/shared/src/init-admin.ts b/backend/shared/src/init-admin.ts index 102db88..b5ef1d8 100644 --- a/backend/shared/src/init-admin.ts +++ b/backend/shared/src/init-admin.ts @@ -1,16 +1,12 @@ import * as admin from 'firebase-admin' -import { getServiceAccountCredentials } from 'common/secrets' -export const getLocalEnv = () => { - return (process.env.ENV?.toUpperCase() ?? 'STAGING') as 'PROD' | 'DEV' -} +import {getServiceAccountCredentials} from "shared/firebase-utils"; // Locally initialize Firebase Admin. export const initAdmin = () => { try { - const env = getLocalEnv() - const serviceAccount = getServiceAccountCredentials(env) + const serviceAccount = getServiceAccountCredentials() console.log( `Initializing connection to ${serviceAccount.project_id} Firebase...` ) diff --git a/backend/shared/src/supabase/init.ts b/backend/shared/src/supabase/init.ts index 0348b5a..8e201be 100644 --- a/backend/shared/src/supabase/init.ts +++ b/backend/shared/src/supabase/init.ts @@ -36,11 +36,11 @@ export type SupabaseTransaction = ITask<{}> export type SupabaseDirectClient = IDatabase<{}, IClient> | SupabaseTransaction export function getInstanceId() { - return process.env.SUPABASE_INSTANCE_ID ?? ENV_CONFIG.supabaseInstanceId + return ENV_CONFIG.supabaseInstanceId } export function getSupabasePwd() { - return ENV_CONFIG.supabaseServiceRoleKey ?? process.env.SUPABASE_DB_PASSWORD + return ENV_CONFIG.supabasePwd } const newClient = ( @@ -55,7 +55,7 @@ const newClient = ( // This host is IPV4 compatible, for the google cloud VM host: 'aws-1-us-west-1.pooler.supabase.com', port: 5432, - user: `postgres.ltzepxnhhnrnvovqblfr`, + user: `postgres.${instanceId}`, password: password, database: 'postgres', pool_mode: 'session', diff --git a/backend/shared/src/websockets/server.ts b/backend/shared/src/websockets/server.ts index 4d88eb7..e34e6fd 100644 --- a/backend/shared/src/websockets/server.ts +++ b/backend/shared/src/websockets/server.ts @@ -9,7 +9,7 @@ import { ServerMessage, CLIENT_MESSAGE_SCHEMA, } from 'common/api/websockets' -import {LOCAL_DEV} from "common/envs/constants"; +import {IS_LOCAL} from "common/envs/constants"; const SWITCHBOARD = new Switchboard() @@ -107,7 +107,7 @@ export function broadcastMulti(topics: string[], data: BroadcastPayload) { // it isn't secure to do this in prod for auth reasons (maybe?) // but it's super convenient for testing - if (LOCAL_DEV) { + if (IS_LOCAL) { const msg = { type: 'broadcast', topic: '*', topics, data } sendToSubscribers('*', msg) } diff --git a/backend/supabase/makefile b/backend/supabase/makefile index 98dbc24..91f1ea4 100644 --- a/backend/supabase/makefile +++ b/backend/supabase/makefile @@ -9,7 +9,7 @@ regen-types-prod: cd ../../common && npx prettier --write ./src/supabase/schema.ts regen-types-dev: - npx supabase gen types typescript --project-id ltzepxnhhnrnvovqblfr --schema public > ../../common/src/supabase/schema.ts + npx supabase gen types typescript --project-id zbspxezubpzxmuxciurg --schema public > ../../common/src/supabase/schema.ts cd ../../common && npx prettier --write ./src/supabase/schema.ts regen-schema: diff --git a/common/src/api/utils.ts b/common/src/api/utils.ts index 1198d39..c1143ef 100644 --- a/common/src/api/utils.ts +++ b/common/src/api/utils.ts @@ -1,4 +1,4 @@ -import { ENV_CONFIG } from 'common/envs/constants' +import {BACKEND_DOMAIN} from 'common/envs/constants' type ErrorCode = | 400 // your input is bad (like zod is mad) @@ -11,6 +11,7 @@ type ErrorCode = export class APIError extends Error { code: ErrorCode details?: unknown + constructor(code: ErrorCode, message: string, details?: unknown) { super(message) this.code = code @@ -19,20 +20,19 @@ export class APIError extends Error { } } +const prefix = 'v0' + export function pathWithPrefix(path: string) { - return `/v0${path}` + return `/${prefix}${path}` } export function getWebsocketUrl() { - const endpoint = process.env.NEXT_PUBLIC_API_URL ?? ENV_CONFIG.apiEndpoint - const protocol = endpoint.startsWith('localhost') ? 'ws' : 'wss' + const protocol = BACKEND_DOMAIN.startsWith('localhost') ? 'ws' : 'wss' - return `${protocol}://${endpoint}/ws` + return `${protocol}://${BACKEND_DOMAIN}/ws` } export function getApiUrl(path: string) { - const endpoint = process.env.NEXT_PUBLIC_API_URL ?? ENV_CONFIG.apiEndpoint - const protocol = endpoint.startsWith('localhost') ? 'http' : 'https' - const prefix = 'v0' - return `${protocol}://${endpoint}/${prefix}/${path}` + const protocol = BACKEND_DOMAIN.startsWith('localhost') ? 'http' : 'https' + return `${protocol}://${BACKEND_DOMAIN}/${prefix}/${path}` } diff --git a/common/src/envs/constants.ts b/common/src/envs/constants.ts index 1c602ad..7416925 100644 --- a/common/src/envs/constants.ts +++ b/common/src/envs/constants.ts @@ -15,7 +15,13 @@ export function isModId(id: string) { return MOD_IDS.includes(id) } -export const DOMAIN = ENV_CONFIG.domain +export const LOCAL_WEB_DOMAIN = 'localhost:3000'; +export const LOCAL_BACKEND_DOMAIN = 'localhost:8088'; +export const IS_LOCAL = !process.env.VERCEL && !process.env.K_SERVICE; +console.log(IS_LOCAL ? 'Running in local mode' : 'Running in deployed mode', isProd() ? '(prod)' : '(dev)'); + +export const DOMAIN = IS_LOCAL ? LOCAL_WEB_DOMAIN : ENV_CONFIG.domain +export const BACKEND_DOMAIN = IS_LOCAL ? LOCAL_BACKEND_DOMAIN : ENV_CONFIG.backendDomain export const FIREBASE_CONFIG = ENV_CONFIG.firebaseConfig export const PROJECT_ID = ENV_CONFIG.firebaseConfig.projectId @@ -98,7 +104,4 @@ export const RESERVED_PATHS = [ 'users', 'web', 'welcome', -] - -export const LOCAL_WEB_URL = 'http://localhost:3000'; -export const LOCAL_DEV = process.env.GOOGLE_CLOUD_PROJECT == null \ No newline at end of file +] \ No newline at end of file diff --git a/common/src/envs/dev.ts b/common/src/envs/dev.ts index ab16126..cbd8afd 100644 --- a/common/src/envs/dev.ts +++ b/common/src/envs/dev.ts @@ -2,9 +2,12 @@ import { EnvConfig, PROD_CONFIG } from './prod' export const DEV_CONFIG: EnvConfig = { ...PROD_CONFIG, + domain: 'dev.compassmeet.com', + backendDomain: 'api.dev.compassmeet.com', supabaseInstanceId: 'zbspxezubpzxmuxciurg', - supabaseServiceRoleKey: '09wATRREfAzyL5pc', // For database write access (dev). A 16-character password with digits and letters. + supabasePwd: 'FO3y0G7chzdq6aE7', // For database write access (dev). A 16-character password with digits and letters. supabaseAnonKey: 'eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJpc3MiOiJzdXBhYmFzZSIsInJlZiI6Inpic3B4ZXp1YnB6eG11eGNpdXJnIiwicm9sZSI6ImFub24iLCJpYXQiOjE3NTc2ODM0MTMsImV4cCI6MjA3MzI1OTQxM30.ZkM7zlawP8Nke0T3KJrqpOQ4DzqPaXTaJXLC2WU8Y7c', + googleApplicationCredentials: 'googleApplicationCredentials-dev.json', firebaseConfig: { apiKey: "AIzaSyBspL9glBXWbMsjmtt36dgb2yU0YGGhzKo", authDomain: "compass-57c3c.firebaseapp.com", @@ -15,5 +18,5 @@ export const DEV_CONFIG: EnvConfig = { appId: "1:297460199314:web:c45678c54285910e255b4b", measurementId: "G-N6LZ64EMJ2", region: 'us-west1', - } + }, } diff --git a/common/src/envs/prod.ts b/common/src/envs/prod.ts index b6b6a8f..7d36586 100644 --- a/common/src/envs/prod.ts +++ b/common/src/envs/prod.ts @@ -3,9 +3,10 @@ export type EnvConfig = { firebaseConfig: FirebaseConfig supabaseInstanceId: string supabaseAnonKey: string - supabaseServiceRoleKey?: string + supabasePwd?: string posthogKey: string - apiEndpoint: string + backendDomain: string + googleApplicationCredentials: string // IDs for v2 cloud functions -- find these by deploying a cloud function and // examining the URL, https://[name]-[cloudRunId]-[cloudRunRegion].a.run.app @@ -33,6 +34,11 @@ type FirebaseConfig = { export const PROD_CONFIG: EnvConfig = { posthogKey: 'phc_tFvQzHiMVdaAIgE38xqYomMN8q8SB5K45fqmkKNjfBU', domain: 'compassmeet.com', + backendDomain: 'api.compassmeet.com', + supabaseInstanceId: 'ltzepxnhhnrnvovqblfr', + supabaseAnonKey: process.env.NEXT_PUBLIC_SUPABASE_KEY || '', + supabasePwd: process.env.SUPABASE_DB_PASSWORD || '', + googleApplicationCredentials: process.env.GOOGLE_APPLICATION_CREDENTIALS || '', firebaseConfig: { apiKey: process.env.NEXT_PUBLIC_FIREBASE_API_KEY || '', authDomain: "compass-130ba.firebaseapp.com", @@ -46,12 +52,8 @@ export const PROD_CONFIG: EnvConfig = { }, cloudRunId: 'w3txbmd3ba', cloudRunRegion: 'uc', - supabaseInstanceId: 'ltzepxnhhnrnvovqblfr', - supabaseAnonKey: process.env.NEXT_PUBLIC_SUPABASE_KEY || '', - apiEndpoint: 'api.compassmeet.com', adminIds: [ '0vaZsIJk9zLVOWY4gb61gTrRIU73', // Martin ], - faviconPath: '/favicon.ico', } diff --git a/common/src/secrets.ts b/common/src/secrets.ts index ab02215..474bfec 100644 --- a/common/src/secrets.ts +++ b/common/src/secrets.ts @@ -1,6 +1,6 @@ -import { readFileSync } from 'fs' -import { SecretManagerServiceClient } from '@google-cloud/secret-manager' -import { zip } from 'lodash' +import {SecretManagerServiceClient} from '@google-cloud/secret-manager' +import {zip} from 'lodash' +import {IS_LOCAL} from "common/envs/constants"; // List of secrets that are available to backend (api, functions, scripts, etc.) // Edit them at: @@ -27,6 +27,9 @@ type SecretId = (typeof secrets)[number] // For deployed google cloud service, no credential is needed. // For local and Vercel deployments: requires credentials json object. export const getSecrets = async (credentials?: any, ...ids: SecretId[]) => { + if (!ids.length && IS_LOCAL) return {} + + console.log('Fetching secrets...') let client: SecretManagerServiceClient if (credentials) { const projectId = credentials['project_id'] @@ -71,29 +74,3 @@ export const loadSecretsToEnv = async (credentials?: any) => { } } -// Get service account credentials from Vercel environment variable or local file. -export const getServiceAccountCredentials = (env: 'PROD' | 'DEV') => { - // Vercel environment variable for service credential. - const value = - env === 'PROD' - ? process.env.PROD_FIREBASE_SERVICE_ACCOUNT_KEY - : process.env.DEV_FIREBASE_SERVICE_ACCOUNT_KEY - if (value && !process.env.LOCAL) { - return JSON.parse(value) - } - - // Local environment variable for service credential. - const envVar = `GOOGLE_APPLICATION_CREDENTIALS_${env}` - const keyPath = process.env[envVar] - if (keyPath == null) { - throw new Error( - `Please set the ${envVar} environment variable to contain the path to your ${env} environment key file.` - ) - } - - try { - return JSON.parse(readFileSync(keyPath, { encoding: 'utf8' })) - } catch (e) { - throw new Error(`Failed to load service account key from ${keyPath}.`) - } -} diff --git a/common/src/util/og.ts b/common/src/util/og.ts index 22f876a..63fbdbb 100644 --- a/common/src/util/og.ts +++ b/common/src/util/og.ts @@ -1,4 +1,4 @@ -import {DOMAIN, LOCAL_DEV, LOCAL_WEB_URL} from 'common/envs/constants' +import {DOMAIN} from 'common/envs/constants' // opengraph functions that run in static props or client-side, but not in the edge (in image creation) @@ -10,5 +10,5 @@ export function buildOgUrl

>( const generateUrlParams = (params: P) => new URLSearchParams(params).toString() - return `https://${domain ?? LOCAL_DEV ? LOCAL_WEB_URL : DOMAIN}/api/og/${endpoint}?` + generateUrlParams(props) + return `https://${domain ?? DOMAIN}/api/og/${endpoint}?` + generateUrlParams(props) } diff --git a/package.json b/package.json index 09daf42..3852f14 100644 --- a/package.json +++ b/package.json @@ -12,14 +12,16 @@ ], "scripts": { "verify": "yarn --cwd=common verify:dir; yarn --cwd=web verify:dir; yarn --cwd=backend/shared verify:dir", - "lint": "yarn --cwd=web lint-fix; eslint common --fix ; eslint backend/api --fix ; eslint backend/shared --fix", "dev": "./scripts/run_local.sh dev", + "lint": "yarn --cwd=web lint-fix; eslint common --fix ; eslint backend/api --fix ; eslint backend/shared --fix", + "dev": "./scripts/run_local.sh dev", "prod": "./scripts/run_local.sh prod", "clean-install": "./scripts/install.sh", "migrate": "./scripts/migrate.sh", "test": "jest", "test:watch": "jest --watch", "test:coverage": "jest --coverage", - "test:update": "jest --updateSnapshot" + "test:update": "jest --updateSnapshot", + "postinstall": "./scripts/post_install.sh" }, "dependencies": { "@playwright/test": "^1.54.2", diff --git a/scripts/post_install.sh b/scripts/post_install.sh new file mode 100755 index 0000000..de8dc46 --- /dev/null +++ b/scripts/post_install.sh @@ -0,0 +1,10 @@ +#!/bin/bash + +set -e + +cd "$(dirname "$0")"/.. + +if [ ! -f .env ]; then + cp .env.example .env + echo ".env file created from .env.example" +fi \ No newline at end of file diff --git a/scripts/run_local.sh b/scripts/run_local.sh index be01a69..71ba845 100755 --- a/scripts/run_local.sh +++ b/scripts/run_local.sh @@ -4,26 +4,27 @@ set -e cd "$(dirname "$0")"/.. -ENV=${1:-prod} -PROJECT=$2 -case $ENV in - dev) - NEXT_ENV=DEV ;; - prod) - NEXT_ENV=PROD ;; - *) - echo "Invalid environment; must be dev or prod." - exit 1 +ENVIRONMENT=${1:-dev} +echo "Running in $ENVIRONMENT environment" +case $ENVIRONMENT in + dev) + NEXT_ENV=DEV + ;; + prod) + NEXT_ENV=PROD + ;; + *) + echo "Unknown environment: $ENVIRONMENT" + exit 1 + ;; esac WEB_DIR=web -source .env - npx dotenv -e .env -- npx concurrently \ -n API,NEXT,TS \ -c white,magenta,cyan \ - "cross-env ENV=$NEXT_ENV ENVIRONMENT=$NEXT_ENV yarn --cwd=backend/api dev" \ + "cross-env ENVIRONMENT=$NEXT_ENV yarn --cwd=backend/api dev" \ "cross-env NEXT_PUBLIC_FIREBASE_ENV=$NEXT_ENV yarn --cwd=$WEB_DIR serve" \ "cross-env yarn --cwd=$WEB_DIR ts-watch" diff --git a/web/next.config.js b/web/next.config.js index 24eec3c..c8e3ae8 100644 --- a/web/next.config.js +++ b/web/next.config.js @@ -48,6 +48,7 @@ module.exports = { { hostname: 'picsum.photos' }, { hostname: '*.giphy.com' }, { hostname: 'ui-avatars.com' }, + { hostname: 'localhost' }, ], }, webpack: (config) => {