diff --git a/.github/workflows/e2e-test.yml b/.github/workflows/e2e-test.yml index d0426f85f..29089544f 100644 --- a/.github/workflows/e2e-test.yml +++ b/.github/workflows/e2e-test.yml @@ -39,6 +39,10 @@ jobs: - name: Build and run run: | + cd packages/ufb-shared + yarn + yarn build + cd ../../ docker-compose -f "./docker/docker-compose.e2e.yml" up -d - name: Setup e2e test diff --git a/.github/workflows/test-on-pr.yml b/.github/workflows/test-on-pr.yml index e814c1bf7..5e0a1fd90 100644 --- a/.github/workflows/test-on-pr.yml +++ b/.github/workflows/test-on-pr.yml @@ -23,10 +23,10 @@ jobs: - name: setup environment variables (with opensearch) run: | echo "JWT_SECRET=${{ vars.TEST_JWT_SECRET }}" >> ./apps/api/.env.test - echo "OS_USE=true" >> ./apps/api/.env.test - echo "OS_NODE=http://localhost:9200" >> ./apps/api/.env.test - echo "OS_USERNAME=''" >> ./apps/api/.env.test - echo "OS_PASSWORD=''" >> ./apps/api/.env.test + echo "OPENSEARCH_USE=true" >> ./apps/api/.env.test + echo "OPENSEARCH_NODE=http://localhost:9200" >> ./apps/api/.env.test + echo "OPENSEARCH_USERNAME=''" >> ./apps/api/.env.test + echo "OPENSEARCH_PASSWORD=''" >> ./apps/api/.env.test - name: Run Tests run: yarn test @@ -35,7 +35,7 @@ jobs: run: | rm ./apps/api/.env.test echo "JWT_SECRET=${{ vars.TEST_JWT_SECRET }}" >> ./apps/api/.env.test - echo "OS_USE=false" >> ./apps/api/.env.test + echo "OPENSEARCH_USE=false" >> ./apps/api/.env.test - name: Run Tests run: yarn test diff --git a/apps/api/.env.example b/apps/api/.env.example index a709c3293..ed56cf300 100644 --- a/apps/api/.env.example +++ b/apps/api/.env.example @@ -1,28 +1,33 @@ -JWT_SECRET= # required +# Required enviroment variables +JWT_SECRET=DEV -MYSQL_PRIMARY_URL= # required, default: mysql://userfeedback:userfeedback@localhost:13306/userfeedback -MYSQL_SECONDARY_URLS= # required, default: ["mysql://userfeedback:userfeedback@localhost:13306/userfeedback"] +MYSQL_PRIMARY_URL=mysql://userfeedback:userfeedback@localhost:13306/userfeedback # required -SMTP_USE= # default: false -SMTP_HOST= # required if SMTP_USE=true -SMTP_PORT= # required if SMTP_USE=true -SMTP_USERNAME= # required if SMTP_USE=true -SMTP_PASSWORD= # required if SMTP_USE=true -SMTP_SENDER= # required if SMTP_USE=true -SMTP_BASE_URL= # required if SMTP_USE=true +BASE_URL=http://localhost:3000 # default: http://localhost:3000 -OS_USE= # default: false -OS_NODE= # default: http://localhost:9200 -OS_USERNAME= # default: "" -OS_PASSWORD= # default: "" +ACCESS_TOKEN_EXPIRED_TIME=10m # default: 10m +REFESH_TOKEN_EXPIRED_TIME=1h # default: 1h -APP_PORT= # default: 4000 -APP_ADDRESS= # default: 0.0.0.0 +# Optional enviroment variables -AUTO_MIGRATION= # default: false +# APP_PORT=4000 # default: 4000 +# APP_ADDRESS=0.0.0.0 # default: 0.0.0.0 -MASTER_API_KEY= # default: none -BASE_URL= # default: http://localhost:3000 +# MYSQL_SECONDARY_URLS= ["mysql://userfeedback:userfeedback@localhost:13306/userfeedback"] # optional -ACCESS_TOKEN_EXPIRED_TIME= # default: 10m -REFESH_TOKEN_EXPIRED_TIME= # default: 1h \ No newline at end of file +# SMTP_USE=false # default: false +# SMTP_HOST= # required if SMTP_USE=true +# SMTP_PORT= # required if SMTP_USE=true +# SMTP_USERNAME= # required if SMTP_USE=true +# SMTP_PASSWORD= # required if SMTP_USE=true +# SMTP_SENDER= # required if SMTP_USE=true +# SMTP_BASE_URL= # required if SMTP_USE=true + +# OPENSEARCH_USE=false # default: false +# OPENSEARCH_NODE= # required if OPENSEARCH_USE=true +# OPENSEARCH_USERNAME= # required if OPENSEARCH_USE=true +# OPENSEARCH_PASSWORD= # required if OPENSEARCH_USE=true + +# AUTO_MIGRATION=true # default: true + +# MASTER_API_KEY= # default: none \ No newline at end of file diff --git a/apps/api/README.md b/apps/api/README.md index f4d82750e..fe83cab61 100644 --- a/apps/api/README.md +++ b/apps/api/README.md @@ -68,29 +68,40 @@ npm run migration:run ## Environment Variables -| Environment | Description | Default Value | -| ------------------------- | -------------------------------------------------------------------------------- | ------------------------------------------------------------------ | -| JWT_SECRET | JWT secret | # required | -| MYSQL_PRIMARY_URL | mysql url | mysql://userfeedback:userfeedback@localhost:13306/userfeedback | -| MYSQL_SECONDARY_URLS | mysql sub urls (must be json array format) | ["mysql://userfeedback:userfeedback@localhost:13306/userfeedback"] | -| SMTP_USE | flag for using smtp server (for email verification on creating user) | false | -| SMTP_HOST | smtp server host | localhost | -| SMTP_PORT | smtp server port | 25 | -| SMTP_USERNAME | smtp auth username | | -| SMTP_PASSWORD | smtp auth password | | -| SMTP_SENDER | mail sender email | noreplay@linecorp.com | -| SMTP_BASE_URL | default UserFeedback URL for mail to be redirected | http://localhost:3000 | -| APP_PORT | the post that the server is running on | 4000 | -| APP_ADDRESS | the address that the server is running on | 0.0.0.0 | -| OS_USE | flag for using opensearch (for better performance on searching feedback) | false | -| OS_NODE | opensearch node url | http://localhost:9200 | -| OS_USERNAME | opensearch username if exists | | -| OS_PASSWORD | opensearch password if exists | | -| AUTO_MIGRATION | set 'true' if you want to make the database migration automatically | | -| MASTER_API_KEY | set a key if you want to make a master key for creating feedback | | -| NODE_OPTIONS | set some options if you want to add for node execution (e.g. max_old_space_size) | | -| ACCESS_TOKEN_EXPIRED_TIME | set expired time of access token | 10m | -| REFESH_TOKEN_EXPIRED_TIME | set expired time of refresh token | 1h | +The following is a list of environment variables used by the application, along with their descriptions and default values. + +### Required Environment Variables + +| Environment | Description | Default Value | +| --------------------------- | -------------------------------------------- | -------------------------------------------------------- | +| `JWT_SECRET` | Secret key for signing JSON Web Tokens (JWT) | _required_ | +| `MYSQL_PRIMARY_URL` | Primary MySQL connection URL | `mysql://userfeedback:userfeedback@localhost:13306/test` | +| `BASE_URL` | Base URL of the application | `http://localhost:3000` | +| `ACCESS_TOKEN_EXPIRED_TIME` | Duration until the access token expires | `10m` | +| `REFESH_TOKEN_EXPIRED_TIME` | Duration until the refresh token expires | `1h` | + +### Optional Environment Variables + +| Environment | Description | Default Value | +| ---------------------- | -------------------------------------------------------------- | ----------------------------------- | +| `APP_PORT` | The port that the server runs on | `4000` | +| `APP_ADDRESS` | The address that the server binds to | `0.0.0.0` | +| `MYSQL_SECONDARY_URLS` | Secondary MySQL connection URLs (must be in JSON array format) | _optional_ | +| `SMTP_USE` | Flag to enable SMTP server usage (for email verification) | `false` | +| `SMTP_HOST` | SMTP server host | _required if `SMTP_USE=true`_ | +| `SMTP_PORT` | SMTP server port | _required if `SMTP_USE=true`_ | +| `SMTP_USERNAME` | SMTP server authentication username | _required if `SMTP_USE=true`_ | +| `SMTP_PASSWORD` | SMTP server authentication password | _required if `SMTP_USE=true`_ | +| `SMTP_SENDER` | Email address used as sender in emails | _required if `SMTP_USE=true`_ | +| `SMTP_BASE_URL` | Base URL for emails to link back to the application | _required if `SMTP_USE=true`_ | +| `OPENSEARCH_USE` | Flag to enable OpenSearch integration | `false` | +| `OPENSEARCH_NODE` | OpenSearch node URL | _required if `OPENSEARCH_USE=true`_ | +| `OPENSEARCH_USERNAME` | OpenSearch username (if authentication is enabled) | _required if `OPENSEARCH_USE=true`_ | +| `OPENSEARCH_PASSWORD` | OpenSearch password (if authentication is enabled) | _required if `OPENSEARCH_USE=true`_ | +| `AUTO_MIGRATION` | Automatically perform database migration on application start | `true` | +| `MASTER_API_KEY` | Master API key for privileged operations | _none_ | + +Please ensure that you set the required environment variables before starting the application. Optional variables can be set as needed based on your specific configuration and requirements. ## Swagger diff --git a/apps/api/src/configs/app.config.ts b/apps/api/src/configs/app.config.ts index 5b240ce49..e1d395da5 100644 --- a/apps/api/src/configs/app.config.ts +++ b/apps/api/src/configs/app.config.ts @@ -20,9 +20,11 @@ import Joi from 'joi'; export const appConfigSchema = Joi.object({ APP_PORT: Joi.number().default(4000), APP_ADDRESS: Joi.string().default('0.0.0.0'), + BASE_URL: Joi.string().required(), }); export const appConfig = registerAs('app', () => ({ port: process.env.APP_PORT, address: process.env.APP_ADDRESS, + baseUrl: process.env.APP_BASE_URL, })); diff --git a/apps/api/src/configs/modules/typeorm-config/typeorm-config.service.ts b/apps/api/src/configs/modules/typeorm-config/typeorm-config.service.ts index 111dcef34..5c7df42ec 100644 --- a/apps/api/src/configs/modules/typeorm-config/typeorm-config.service.ts +++ b/apps/api/src/configs/modules/typeorm-config/typeorm-config.service.ts @@ -41,7 +41,9 @@ export class TypeOrmConfigService implements TypeOrmOptionsFactory { type: 'mysql', replication: { master: { url: main_url }, - slaves: sub_urls.map((url) => ({ url })), + slaves: sub_urls.length + ? sub_urls.map((url) => ({ url })) + : [{ url: main_url }], }, entities: [join(__dirname, '../../../**/*.entity.{ts,js}')], migrations: [join(__dirname, 'migrations/*.{ts,js}')], diff --git a/apps/api/src/configs/mysql.config.ts b/apps/api/src/configs/mysql.config.ts index 08c920f15..d19c86e27 100644 --- a/apps/api/src/configs/mysql.config.ts +++ b/apps/api/src/configs/mysql.config.ts @@ -21,22 +21,22 @@ dotenv.config(); export const mysqlConfigSchema = Joi.object({ MYSQL_PRIMARY_URL: Joi.string().required(), - MYSQL_SECONDARY_URLS: Joi.string() - .custom((value, helpers) => { - const urls = JSON.parse(value); - for (const url of urls) { - if (!url.startsWith('mysql://')) { - return helpers.error('any.invalid'); - } + MYSQL_SECONDARY_URLS: Joi.string().custom((value, helpers) => { + const urls = JSON.parse(value); + for (const url of urls) { + if (!url.startsWith('mysql://')) { + return helpers.error('any.invalid'); } - return value; - }, 'custom validation') - .required(), - AUTO_MIGRATION: Joi.boolean().default(false), + } + return value; + }, 'custom validation'), + AUTO_MIGRATION: Joi.boolean().default(true), }); export const mysqlConfig = registerAs('mysql', () => ({ main_url: process.env.MYSQL_PRIMARY_URL, - sub_urls: JSON.parse(process.env.MYSQL_SECONDARY_URLS), + sub_urls: process.env.MYSQL_SECONDARY_URLS + ? JSON.parse(process.env.MYSQL_SECONDARY_URLS) + : [], auto_migration: process.env.AUTO_MIGRATION === 'true', })); diff --git a/apps/api/src/configs/opensearch.config.ts b/apps/api/src/configs/opensearch.config.ts index 37681d7d9..55a52c8f6 100644 --- a/apps/api/src/configs/opensearch.config.ts +++ b/apps/api/src/configs/opensearch.config.ts @@ -17,18 +17,18 @@ import { registerAs } from '@nestjs/config'; import Joi from 'joi'; export const opensearchConfigSchema = Joi.object({ - OS_USE: Joi.boolean().default(false), - OS_NODE: Joi.string().when('OS_USE', { + OPENSEARCH_USE: Joi.boolean().default(false), + OPENSEARCH_NODE: Joi.string().when('OPENSEARCH_USE', { is: true, then: Joi.required(), otherwise: Joi.optional(), }), - OS_USERNAME: Joi.string().allow('').when('OS_USE', { + OPENSEARCH_USERNAME: Joi.string().allow('').when('OPENSEARCH_USE', { is: true, then: Joi.required(), otherwise: Joi.optional(), }), - OS_PASSWORD: Joi.string().allow('').when('OS_USE', { + OPENSEARCH_PASSWORD: Joi.string().allow('').when('OPENSEARCH_USE', { is: true, then: Joi.required(), otherwise: Joi.optional(), @@ -36,8 +36,8 @@ export const opensearchConfigSchema = Joi.object({ }); export const opensearchConfig = registerAs('opensearch', () => ({ - use: process.env.OS_USE === 'true', - node: process.env.OS_NODE, - username: process.env.OS_USERNAME, - password: process.env.OS_PASSWORD, + use: process.env.OPENSEARCH_USE === 'true', + node: process.env.OPENSEARCH_NODE, + username: process.env.OPENSEARCH_USERNAME, + password: process.env.OPENSEARCH_PASSWORD, })); diff --git a/apps/api/src/configs/smtp.config.ts b/apps/api/src/configs/smtp.config.ts index 2b4af6c75..976f77f76 100644 --- a/apps/api/src/configs/smtp.config.ts +++ b/apps/api/src/configs/smtp.config.ts @@ -18,12 +18,36 @@ import Joi from 'joi'; export const smtpConfigSchema = Joi.object({ SMTP_USE: Joi.boolean().default(false), - SMTP_HOST: Joi.string().default('localhost'), - SMTP_PORT: Joi.number().default(25), - SMTP_USERNAME: Joi.string().default(''), - SMTP_PASSWORD: Joi.string().default(''), - SMTP_SENDER: Joi.string().default('noreplay@linecorp.com'), - SMTP_BASE_URL: Joi.string().default('http://localhost:3000'), + SMTP_HOST: Joi.string().when('SMTP_USE', { + is: true, + then: Joi.required(), + otherwise: Joi.optional(), + }), + SMTP_PORT: Joi.number().when('SMTP_USE', { + is: true, + then: Joi.required(), + otherwise: Joi.optional(), + }), + SMTP_USERNAME: Joi.string().when('SMTP_USE', { + is: true, + then: Joi.required(), + otherwise: Joi.optional(), + }), + SMTP_PASSWORD: Joi.string().when('SMTP_USE', { + is: true, + then: Joi.required(), + otherwise: Joi.optional(), + }), + SMTP_SENDER: Joi.string().when('SMTP_USE', { + is: true, + then: Joi.required(), + otherwise: Joi.optional(), + }), + SMTP_BASE_URL: Joi.string().when('SMTP_USE', { + is: true, + then: Joi.required(), + otherwise: Joi.optional(), + }), }); export const smtpConfig = registerAs('smtp', () => ({ diff --git a/apps/e2e/playwright.config.ts b/apps/e2e/playwright.config.ts index 998261619..176cd17c3 100644 --- a/apps/e2e/playwright.config.ts +++ b/apps/e2e/playwright.config.ts @@ -92,6 +92,7 @@ export default defineConfig({ MYSQL_SECONDARY_URLS: '["mysql://userfeedback:userfeedback@localhost:13307/e2e"]', AUTO_MIGRATION: 'true', + MASTER_API_KEY: 'MASTER_API_KEY', }, }, { diff --git a/apps/web/.env.example b/apps/web/.env.example index 05c8dfe0e..a2bfc6730 100644 --- a/apps/web/.env.example +++ b/apps/web/.env.example @@ -1,4 +1,4 @@ -NEXT_PUBLIC_API_BASE_URL= -API_BASE_URL= -SESSION_PASSWORD= -NEXT_PUBLIC_MAX_DAYS= +NEXT_PUBLIC_API_BASE_URL=http://localhost:4000 +API_BASE_URL=http://127.0.0.1:4000 +SESSION_PASSWORD=mysupersessionpasswordatleast32characterslong +NEXT_PUBLIC_MAX_DAYS=90 diff --git a/apps/web/README.md b/apps/web/README.md index 6c2910983..7f9a8b695 100644 --- a/apps/web/README.md +++ b/apps/web/README.md @@ -61,13 +61,20 @@ yarn build ## Environment Variables +### Required Environment Variables + | Environment | Description | Default Value | | ------------------------ | ------------------------------------------------------- | -------------------------------------------- | | NEXT_PUBLIC_API_BASE_URL | api base url in client side (ex. http://localhost:4000) | | -| NEXT_PUBLIC_MAX_DAYS | query maximum days | 90 | | API_BASE_URL | api base url in server side | | | SESSION_PASSWORD | session password | complex_password_at_least_32_characters_long | +### Optional Environment Variables + +| Environment | Description | Default Value | +| -------------------- | ------------------ | ------------- | +| NEXT_PUBLIC_MAX_DAYS | query maximum days | 90 | + ## Learn More To learn NextJS, check out the [NextJS documentation](https://nextjs.org/). diff --git a/apps/web/src/env.mjs b/apps/web/src/env.mjs index 0e8d5dc84..d97bb76d2 100644 --- a/apps/web/src/env.mjs +++ b/apps/web/src/env.mjs @@ -18,15 +18,12 @@ import { z } from 'zod'; export const env = createEnv({ server: { - API_BASE_URL: z.string().url().default('http://127.0.0.1:4000'), - SESSION_PASSWORD: z - .string() - .min(32) - .default('complex_password_at_least_32_characters_long'), + API_BASE_URL: z.string().url(), + SESSION_PASSWORD: z.string().min(32), }, client: { NEXT_PUBLIC_MAX_DAYS: z.coerce.number().default(90), - NEXT_PUBLIC_API_BASE_URL: z.string().url().default('http://localhost:4000'), + NEXT_PUBLIC_API_BASE_URL: z.string().url(), }, runtimeEnv: { API_BASE_URL: process.env.API_BASE_URL, diff --git a/docker/docker-compose.e2e.yml b/docker/docker-compose.e2e.yml index 6f61c6f81..46d737529 100644 --- a/docker/docker-compose.e2e.yml +++ b/docker/docker-compose.e2e.yml @@ -32,7 +32,7 @@ services: environment: - JWT_SECRET=jwtsecretjwtsecretjwtsecret - MYSQL_PRIMARY_URL=mysql://userfeedback:userfeedback@host.docker.internal:13307/e2e - - MYSQL_SECONDARY_URLS=["mysql://userfeedback:userfeedback@host.docker.internal:13307/e2e"] + - BASE_URL=http://host.docker.internal:3000 - SMTP_HOST=localhost - SMTP_PORT=25 - SMTP_SENDER=abc@feedback.user diff --git a/docker/docker-compose.yml b/docker/docker-compose.yml index 5173d7901..d423078f2 100644 --- a/docker/docker-compose.yml +++ b/docker/docker-compose.yml @@ -36,15 +36,12 @@ services: environment: - JWT_SECRET=jwtsecretjwtsecretjwtsecret - MYSQL_PRIMARY_URL=mysql://userfeedback:userfeedback@mysql:3306/userfeedback - - MYSQL_SECONDARY_URLS=["mysql://userfeedback:userfeedback@mysql:3306/userfeedback"] - - SMTP_HOST=smtp4dev - - SMTP_PORT=25 - - SMTP_SENDER=abc@feedback.user - - SMTP_BASE_URL=http://localhost:3000 + - BASE_URL=http://api:3000 + - ACCESS_TOKEN_EXPIRED_TIME=10m + - REFESH_TOKEN_EXPIRED_TIME=1h - APP_PORT=4000 - APP_ADDRESS=api - AUTO_MIGRATION=true - - MASTER_API_KEY=MASTER_API_KEY - NODE_OPTIONS="--max_old_space_size=3072" networks: - app_network diff --git a/turbo.json b/turbo.json index 515136b76..744e44ffb 100644 --- a/turbo.json +++ b/turbo.json @@ -1,41 +1,72 @@ { "$schema": "https://turbo.build/schema.json", - "globalDependencies": ["**/.env"], + "globalDependencies": [ + "**/.env" + ], "pipeline": { "topo": { - "dependsOn": ["^topo"] + "dependsOn": [ + "^topo" + ] }, "build": { - "dependsOn": ["^build"], - "outputs": ["dist/**", ".next/**", "next-env.d.ts", "!.next/cache/**"] + "dependsOn": [ + "^build" + ], + "outputs": [ + "dist/**", + ".next/**", + "next-env.d.ts", + "!.next/cache/**" + ] }, "dev": { "cache": false, "persistent": true }, "web#dev": { - "dependsOn": ["@ufb/tailwind#build", "@ufb/shared#build"] + "dependsOn": [ + "@ufb/tailwind#build", + "@ufb/shared#build" + ] }, "api#dev": { - "dependsOn": ["@ufb/shared#build"] + "dependsOn": [ + "@ufb/shared#build" + ] }, "@ufb/tailwind#build": { - "outputs": ["dist/**"] + "outputs": [ + "dist/**" + ] }, "format": { - "outputs": ["node_modules/.cache/.prettiercache"], + "outputs": [ + "node_modules/.cache/.prettiercache" + ], "outputMode": "new-only" }, "lint": { - "dependsOn": ["topo"], - "outputs": ["node_modules/.cache/.eslintcache"] + "dependsOn": [ + "topo" + ], + "outputs": [ + "node_modules/.cache/.eslintcache" + ] }, "typecheck": { - "dependsOn": ["topo"], - "outputs": ["node_modules/.cache/tsbuildinfo.json"] + "dependsOn": [ + "topo" + ], + "outputs": [ + "node_modules/.cache/tsbuildinfo.json" + ] }, "test": { - "dependsOn": ["topo", "@ufb/shared#build"] + "dependsOn": [ + "topo", + "@ufb/shared#build" + ] }, "clean": { "cache": false @@ -49,10 +80,10 @@ "SESSION_PASSWORD", "API_BASE_URL", "NEXT_PUBLIC_API_BASE_URL", - "OS_USE", - "OS_NODE", - "OS_USERNAME", - "OS_PASSWORD", + "OPENSEARCH_USE", + "OPENSEARCH_NODE", + "OPENSEARCH_USERNAME", + "OPENSEARCH_PASSWORD", "APP_PORT", "APP_ADDRESS", "JWT_SECRET", @@ -70,4 +101,4 @@ "BASE_URL", "NEXT_PUBLIC_MAX_DAYS" ] -} +} \ No newline at end of file