config.ts 3.2 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109
  1. import { z } from "zod";
  2. import { fromError } from "zod-validation-error";
  3. import path from "path";
  4. import fs from "fs";
  5. import yaml from "js-yaml";
  6. export const APP_PATH = path.join("config");
  7. const environmentSchema = z.object({
  8. app: z.object({
  9. name: z.string(),
  10. base_url: z.string().url(),
  11. log_level: z.enum(["debug", "info", "warn", "error"]),
  12. save_logs: z.string().transform((val) => val === "true"),
  13. }),
  14. server: z.object({
  15. external_port: z
  16. .string()
  17. .transform((val) => parseInt(val, 10))
  18. .pipe(z.number()),
  19. internal_port: z
  20. .string()
  21. .transform((val) => parseInt(val, 10))
  22. .pipe(z.number()),
  23. internal_hostname: z.string(),
  24. secure_cookies: z.string().transform((val) => val === "true"),
  25. }),
  26. traefik: z.object({
  27. http_entrypoint: z.string(),
  28. https_entrypoint: z.string().optional(),
  29. cert_resolver: z.string().optional(),
  30. }),
  31. rate_limit: z.object({
  32. window_minutes: z
  33. .string()
  34. .transform((val) => parseInt(val, 10))
  35. .pipe(z.number()),
  36. max_requests: z
  37. .string()
  38. .transform((val) => parseInt(val, 10))
  39. .pipe(z.number()),
  40. }),
  41. email: z
  42. .object({
  43. smtp_host: z.string().optional(),
  44. smtp_port: z
  45. .string()
  46. .optional()
  47. .transform((val) => {
  48. if (val) {
  49. return parseInt(val, 10);
  50. }
  51. return val;
  52. })
  53. .pipe(z.number().optional()),
  54. smtp_user: z.string().optional(),
  55. smtp_pass: z.string().optional(),
  56. no_reply: z.string().email().optional(),
  57. })
  58. .optional(),
  59. });
  60. const loadConfig = (configPath: string) => {
  61. try {
  62. const yamlContent = fs.readFileSync(configPath, "utf8");
  63. const config = yaml.load(yamlContent);
  64. return config;
  65. } catch (error) {
  66. if (error instanceof Error) {
  67. throw new Error(
  68. `Error loading configuration file: ${error.message}`,
  69. );
  70. }
  71. throw error;
  72. }
  73. };
  74. const configFilePath1 = path.join(APP_PATH, "config.yml");
  75. const configFilePath2 = path.join(APP_PATH, "config.yaml");
  76. let environment: any;
  77. if (fs.existsSync(configFilePath1)) {
  78. environment = loadConfig(configFilePath1);
  79. } else if (fs.existsSync(configFilePath2)) {
  80. environment = loadConfig(configFilePath2);
  81. }
  82. if (!environment) {
  83. throw new Error("No configuration file found");
  84. }
  85. const parsedConfig = environmentSchema.safeParse(environment);
  86. if (!parsedConfig.success) {
  87. const errors = fromError(parsedConfig.error);
  88. throw new Error(`Invalid configuration file: ${errors}`);
  89. }
  90. process.env.NEXT_PUBLIC_EXTERNAL_API_BASE_URL = new URL(
  91. "/api/v1",
  92. parsedConfig.data.app.base_url,
  93. ).href;
  94. process.env.NEXT_PUBLIC_INTERNAL_API_BASE_URL = new URL(
  95. "/api/v1",
  96. `http://${parsedConfig.data.server.internal_hostname}:${parsedConfig.data.server.external_port}`,
  97. ).href;
  98. process.env.NEXT_PUBLIC_APP_NAME = parsedConfig.data.app.name;
  99. export default parsedConfig.data;