From 00a74179365f7769c812f2934e6cdc455ed37170 Mon Sep 17 00:00:00 2001 From: creations Date: Tue, 10 Jun 2025 15:16:31 -0400 Subject: [PATCH] move environment to src/environment add smtp env vars, move some other items --- bun.lock | 3 + environment/constants/database/index.ts | 5 - package.json | 6 +- src/commands.ts | 6 +- {environment => src/environment}/config.ts | 30 ++++- src/environment/constants/database/index.ts | 5 + .../environment}/constants/index.ts | 0 .../environment}/constants/server.ts | 0 .../environment}/constants/validation.ts | 0 .../environment}/database/cassandra.ts | 0 .../environment}/database/index.ts | 0 .../migrations/up/001_create_users.sql | 0 {environment => src/environment}/jwt.ts | 7 -- src/environment/mailer.ts | 39 ++++++ src/lib/validation/general.ts | 99 +++++++++++++++ src/lib/validation/index.ts | 3 + src/lib/validation/mailer.ts | 50 ++++++++ src/lib/validation/url.ts | 113 ++++++++++++++++++ src/routes/health.ts | 39 ++++-- src/routes/user/[id].ts | 2 +- src/server.ts | 2 +- tsconfig.json | 1 - types/config/environment.ts | 1 + types/config/index.ts | 1 + types/config/mailer.ts | 42 +++++++ types/lib/validation.ts | 22 +++- types/server/requests/base.ts | 8 ++ types/server/requests/health.ts | 17 +++ types/server/requests/index.ts | 2 + types/server/requests/user/base.ts | 9 +- 30 files changed, 470 insertions(+), 42 deletions(-) delete mode 100644 environment/constants/database/index.ts rename {environment => src/environment}/config.ts (64%) create mode 100644 src/environment/constants/database/index.ts rename {environment => src/environment}/constants/index.ts (100%) rename {environment => src/environment}/constants/server.ts (100%) rename {environment => src/environment}/constants/validation.ts (100%) rename {environment => src/environment}/database/cassandra.ts (100%) rename {environment => src/environment}/database/index.ts (100%) rename {environment => src/environment}/database/migrations/up/001_create_users.sql (100%) rename {environment => src/environment}/jwt.ts (73%) create mode 100644 src/environment/mailer.ts create mode 100644 src/lib/validation/general.ts create mode 100644 src/lib/validation/mailer.ts create mode 100644 src/lib/validation/url.ts create mode 100644 types/config/mailer.ts create mode 100644 types/server/requests/base.ts create mode 100644 types/server/requests/health.ts diff --git a/bun.lock b/bun.lock index 400d2ac..3b9211c 100644 --- a/bun.lock +++ b/bun.lock @@ -7,6 +7,7 @@ "@atums/echo": "latest", "cassandra-driver": "latest", "fast-jwt": "latest", + "nodemailer": "^7.0.3", "pika-id": "latest", }, "devDependencies": { @@ -71,6 +72,8 @@ "mnemonist": ["mnemonist@0.40.3", "", { "dependencies": { "obliterator": "^2.0.4" } }, "sha512-Vjyr90sJ23CKKH/qPAgUKicw/v6pRoamxIEDFOF8uSgFME7DqPRpHgRTejWVjkdGg5dXj0/NyxZHZ9bcjH+2uQ=="], + "nodemailer": ["nodemailer@7.0.3", "", {}, "sha512-Ajq6Sz1x7cIK3pN6KesGTah+1gnwMnx5gKl3piQlQQE/PwyJ4Mbc8is2psWYxK3RJTVeqsDaCv8ZzXLCDHMTZw=="], + "obliterator": ["obliterator@2.0.5", "", {}, "sha512-42CPE9AhahZRsMNslczq0ctAEtqk8Eka26QofnqC346BZdHDySk3LWka23LI7ULIw11NmltpiLagIq8gBozxTw=="], "pika-id": ["pika-id@1.1.3", "", {}, "sha512-+82ue4qBu3GipX0ulJOd7lBlNccJuXnt6zquhF6ekk4WiIO98fV54fkUU3NCienmvKrYu97Cqpk5T3jYOtJRVA=="], diff --git a/environment/constants/database/index.ts b/environment/constants/database/index.ts deleted file mode 100644 index 691ce82..0000000 --- a/environment/constants/database/index.ts +++ /dev/null @@ -1,5 +0,0 @@ -import { resolve } from "node:path"; - -const migrationsPath = resolve("environment", "database", "migrations"); - -export { migrationsPath }; diff --git a/package.json b/package.json index 7a63730..4d88736 100644 --- a/package.json +++ b/package.json @@ -4,6 +4,7 @@ "type": "module", "scripts": { "start": "bun run src/index.ts", + "command": "bun run src/command.ts", "dev": "bun run --hot src/index.ts --dev", "lint": "bunx biome check", "lint:fix": "bunx biome check --fix", @@ -17,9 +18,8 @@ "@atums/echo": "latest", "cassandra-driver": "latest", "fast-jwt": "latest", + "nodemailer": "^7.0.3", "pika-id": "latest" }, - "trustedDependencies": [ - "@biomejs/biome" - ] + "trustedDependencies": ["@biomejs/biome"] } diff --git a/src/commands.ts b/src/commands.ts index f9354d4..b895c5e 100644 --- a/src/commands.ts +++ b/src/commands.ts @@ -82,9 +82,9 @@ function showHelp(): void { echo.info(" --help Show this help message"); echo.info(""); echo.info("Examples:"); - echo.info(" bun run src/index.ts --reset cassandra"); - echo.info(" bun run src/index.ts --reset redis"); - echo.info(" bun run src/index.ts --reset all"); + echo.info(" bun run command --reset cassandra"); + echo.info(" bun run command --reset redis"); + echo.info(" bun run command --reset all"); } export async function handleCommands(): Promise { diff --git a/environment/config.ts b/src/environment/config.ts similarity index 64% rename from environment/config.ts rename to src/environment/config.ts index 28fb633..009c1a2 100644 --- a/environment/config.ts +++ b/src/environment/config.ts @@ -1,7 +1,9 @@ import { echo } from "@atums/echo"; -import { validateJWTConfig } from "#lib/validation"; +import { validateJWTConfig, validateMailerConfig } from "#lib/validation"; +import { isValidUrl } from "#lib/validation/url"; import { cassandraConfig, validateCassandraConfig } from "./database/cassandra"; import { jwt } from "./jwt"; +import { mailerConfig } from "./mailer"; import type { Environment } from "#types/config"; @@ -10,6 +12,7 @@ const environment: Environment = { host: process.env.HOST || "0.0.0.0", development: process.env.NODE_ENV === "development" || process.argv.includes("--dev"), + fqdn: process.env.FRONTEND_FQDN || "", }; function verifyRequiredVariables(): void { @@ -44,7 +47,6 @@ function verifyRequiredVariables(): void { } const validateCassandra = validateCassandraConfig(cassandraConfig); - if (!validateCassandra.isValid) { echo.error("Cassandra configuration validation failed:"); for (const error of validateCassandra.errors) { @@ -54,13 +56,35 @@ function verifyRequiredVariables(): void { } const validateJWT = validateJWTConfig(jwt); - if (!validateJWT.valid) { echo.error("JWT configuration validation failed:"); echo.error(`- ${validateJWT.error}`); hasError = true; } + const validateMailer = validateMailerConfig(mailerConfig); + if (!validateMailer.isValid) { + echo.error("Mailer configuration validation failed:"); + for (const error of validateMailer.errors) { + echo.error(`- ${error}`); + } + hasError = true; + } + + const urlValidation = isValidUrl(environment.fqdn, { + requireProtocol: true, + failOnTrailingSlash: true, + allowedProtocols: ["http", "https"], + allowLocalhost: environment.development, + allowIP: environment.development, + }); + + if (!urlValidation.valid) { + echo.error("FRONTEND_FQDN validation failed:"); + echo.error(`- ${urlValidation.error}`); + hasError = true; + } + if (hasError) { process.exit(1); } diff --git a/src/environment/constants/database/index.ts b/src/environment/constants/database/index.ts new file mode 100644 index 0000000..3fbd0fb --- /dev/null +++ b/src/environment/constants/database/index.ts @@ -0,0 +1,5 @@ +import { resolve } from "node:path"; + +const migrationsPath = resolve("src", "environment", "database", "migrations"); + +export { migrationsPath }; diff --git a/environment/constants/index.ts b/src/environment/constants/index.ts similarity index 100% rename from environment/constants/index.ts rename to src/environment/constants/index.ts diff --git a/environment/constants/server.ts b/src/environment/constants/server.ts similarity index 100% rename from environment/constants/server.ts rename to src/environment/constants/server.ts diff --git a/environment/constants/validation.ts b/src/environment/constants/validation.ts similarity index 100% rename from environment/constants/validation.ts rename to src/environment/constants/validation.ts diff --git a/environment/database/cassandra.ts b/src/environment/database/cassandra.ts similarity index 100% rename from environment/database/cassandra.ts rename to src/environment/database/cassandra.ts diff --git a/environment/database/index.ts b/src/environment/database/index.ts similarity index 100% rename from environment/database/index.ts rename to src/environment/database/index.ts diff --git a/environment/database/migrations/up/001_create_users.sql b/src/environment/database/migrations/up/001_create_users.sql similarity index 100% rename from environment/database/migrations/up/001_create_users.sql rename to src/environment/database/migrations/up/001_create_users.sql diff --git a/environment/jwt.ts b/src/environment/jwt.ts similarity index 73% rename from environment/jwt.ts rename to src/environment/jwt.ts index e9fa186..eb6c35c 100644 --- a/environment/jwt.ts +++ b/src/environment/jwt.ts @@ -1,5 +1,4 @@ import { getExpirationInSeconds } from "#lib/utils"; -import { validateJWTConfig } from "#lib/validation"; import type { JWTConfig } from "#types/config"; @@ -16,12 +15,6 @@ function createJWTConfig(): JWTConfig { algorithm: jwtAlgorithm, }; - const validation = validateJWTConfig(configForValidation); - - if (!validation.valid) { - throw new Error(`JWT Configuration Error: ${validation.error}`); - } - return configForValidation; } diff --git a/src/environment/mailer.ts b/src/environment/mailer.ts new file mode 100644 index 0000000..f334549 --- /dev/null +++ b/src/environment/mailer.ts @@ -0,0 +1,39 @@ +import type { MailerConfig } from "#types/config"; + +function createMailerConfig(): MailerConfig { + const smtpAddress = process.env.SMTP_ADDRESS || ""; + const smtpPort = process.env.SMTP_PORT || "587"; + const smtpFrom = process.env.SMTP_FROM || ""; + const smtpUsername = process.env.SMTP_USERNAME || ""; + const smtpPassword = process.env.SMTP_PASSWORD || ""; + const smtpSecure = process.env.SMTP_SECURE || "false"; + const smtpPool = process.env.SMTP_POOL || "true"; + + const configForValidation: MailerConfig = { + address: smtpAddress, + port: Number.parseInt(smtpPort, 10), + from: smtpFrom, + username: smtpUsername, + password: smtpPassword, + secure: smtpSecure === "true", + pool: smtpPool === "true", + connectionTimeout: Number.parseInt( + process.env.SMTP_CONNECTION_TIMEOUT || "30000", + 10, + ), + socketTimeout: Number.parseInt( + process.env.SMTP_SOCKET_TIMEOUT || "30000", + 10, + ), + maxConnections: Number.parseInt( + process.env.SMTP_MAX_CONNECTIONS || "5", + 10, + ), + maxMessages: Number.parseInt(process.env.SMTP_MAX_MESSAGES || "100", 10), + replyTo: process.env.SMTP_REPLY_TO || smtpFrom, + }; + + return configForValidation; +} + +export const mailerConfig = createMailerConfig(); diff --git a/src/lib/validation/general.ts b/src/lib/validation/general.ts new file mode 100644 index 0000000..e5bcaff --- /dev/null +++ b/src/lib/validation/general.ts @@ -0,0 +1,99 @@ +function isValidPort(port: number): boolean { + return Number.isInteger(port) && port > 0 && port <= 65535; +} + +function isIPAddress(hostname: string): boolean { + // IPv4 + const ipv4Regex = + /^(?:(?:25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)\.){3}(?:25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)$/; + + // IPv6 (simplified) + const ipv6Regex = /^(?:[0-9a-fA-F]{1,4}:){7}[0-9a-fA-F]{1,4}$|^::1$|^::$/; + + return ipv4Regex.test(hostname) || ipv6Regex.test(hostname); +} + +function isValidHostname(hostname: string): boolean { + if (!hostname || hostname.length === 0 || hostname.length > 253) { + return false; + } + + // check for valid hostname format + const hostnameRegex = + /^[a-zA-Z0-9]([a-zA-Z0-9-]{0,61}[a-zA-Z0-9])?(\.[a-zA-Z0-9]([a-zA-Z0-9-]{0,61}[a-zA-Z0-9])?)*$/; + + if (!hostnameRegex.test(hostname)) { + return false; + } + + if (hostname.startsWith("-") || hostname.endsWith("-")) { + return false; + } + + const labels = hostname.split("."); + for (const label of labels) { + if (label.startsWith("-") || label.endsWith("-") || label.length > 63) { + return false; + } + } + + return true; +} + +function hasSuspiciousPatterns(url: string): boolean { + // check for multiple dots in a row + if (/\.{2,}/.test(url)) return true; + + // check for suspicious URL encoding + if (/%[0-9a-fA-F]{2}%[0-9a-fA-F]{2}/.test(url)) return true; + + // check for double slashes in path (excluding protocol) + if (/(?= 0x00 && charCode <= 0x1f) || + (charCode >= 0x7f && charCode <= 0x9f) + ) { + return true; + } + + // unicode zero-width and formatting characters + if ( + charCode === 0x200b || + charCode === 0x200c || + charCode === 0x200d || + charCode === 0xfeff + ) { + return true; + } + } + + return false; +} + +function isValidLength(value: string, min: number, max: number): boolean { + return value.length >= min && value.length <= max; +} + +function isNonEmptyString(value: string): boolean { + return typeof value === "string" && value.trim().length > 0; +} + +function normalizeString(value: string): string { + return value.trim().normalize("NFC"); +} + +export { + isValidPort, + isIPAddress, + isValidHostname, + hasSuspiciousPatterns, + isValidLength, + isNonEmptyString, + normalizeString, +}; diff --git a/src/lib/validation/index.ts b/src/lib/validation/index.ts index 29b453e..53c2574 100644 --- a/src/lib/validation/index.ts +++ b/src/lib/validation/index.ts @@ -2,3 +2,6 @@ export * from "./name"; export * from "./password"; export * from "./email"; export * from "./jwt"; +export * from "./url"; +export * from "./general"; +export * from "./mailer"; diff --git a/src/lib/validation/mailer.ts b/src/lib/validation/mailer.ts new file mode 100644 index 0000000..0d0321b --- /dev/null +++ b/src/lib/validation/mailer.ts @@ -0,0 +1,50 @@ +import { isValidEmail } from "./email"; +import { isValidHostname, isValidPort } from "./general"; + +import type { MailerConfig } from "#types/config"; + +function validateMailerConfig(config: MailerConfig): { + isValid: boolean; + errors: string[]; +} { + const errors: string[] = []; + + const isValidSMTPAddress = isValidHostname(config.address); + + if (!isValidSMTPAddress) { + errors.push(`Invalid SMTP address: ${config.address}`); + } + + if (!isValidPort(config.port)) { + errors.push( + `Invalid SMTP port: ${config.port}. Port must be between 1 and 65535`, + ); + } + + if (!isValidEmail(config.from)) { + errors.push(`Invalid from email address: ${config.from}`); + } + + if (!isValidEmail(config.username)) { + errors.push(`Invalid username email address: ${config.username}`); + } + + if (!config.password || config.password.trim().length === 0) { + errors.push("SMTP password is required"); + } + + if (config.port === 465 && !config.secure) { + errors.push("Port 465 requires SMTP_SECURE=true"); + } + + if (config.port === 587 && config.secure) { + errors.push("Port 587 typically uses SMTP_SECURE=false with STARTTLS"); + } + + return { + isValid: errors.length === 0, + errors, + }; +} + +export { isValidPort, isValidEmail, validateMailerConfig }; diff --git a/src/lib/validation/url.ts b/src/lib/validation/url.ts new file mode 100644 index 0000000..1acb4d0 --- /dev/null +++ b/src/lib/validation/url.ts @@ -0,0 +1,113 @@ +import { hasSuspiciousPatterns, isIPAddress, isValidHostname } from "./general"; + +import type { UrlValidationOptions, UrlValidationResult } from "#types/lib"; + +function isValidUrl( + rawUrl: string, + options: UrlValidationOptions = {}, +): UrlValidationResult { + const { + failOnTrailingSlash = false, + removeTrailingSlash = false, + allowedProtocols = ["http", "https"], + requireProtocol = true, + allowLocalhost = true, + allowIP = true, + maxLength = 2048, + } = options; + + if (typeof rawUrl !== "string") { + return { valid: false, error: "URL must be a string" }; + } + + const url = rawUrl.trim(); + + if (!url) { + return { valid: false, error: "URL is required" }; + } + + if (url.length > maxLength) { + return { + valid: false, + error: `URL exceeds maximum length of ${maxLength} characters`, + }; + } + + let urlToValidate = url; + + // add protocol if missing and required + if (requireProtocol && !url.match(/^[a-zA-Z][a-zA-Z0-9+.-]*:/)) { + urlToValidate = `https://${url}`; + } + + let parsedUrl: URL; + try { + parsedUrl = new URL(urlToValidate); + } catch { + return { valid: false, error: "Invalid URL format" }; + } + + const protocol = parsedUrl.protocol.slice(0, -1); + if (!allowedProtocols.includes(protocol)) { + return { + valid: false, + error: `Invalid protocol: ${protocol}. Allowed protocols: ${allowedProtocols.join(", ")}`, + }; + } + + const hostname = parsedUrl.hostname.toLowerCase(); + + if ( + !allowLocalhost && + (hostname === "localhost" || hostname === "127.0.0.1") + ) { + return { valid: false, error: "Localhost URLs are not allowed" }; + } + + if (!allowIP && isIPAddress(hostname)) { + return { valid: false, error: "IP addresses are not allowed" }; + } + + if (!isValidHostname(hostname) && !isIPAddress(hostname)) { + return { valid: false, error: "Invalid hostname format" }; + } + + if (hasSuspiciousPatterns(url)) { + return { valid: false, error: "URL contains suspicious patterns" }; + } + + const originalHasTrailingSlash = + urlToValidate.endsWith("/") && !urlToValidate.endsWith("://"); + + if (originalHasTrailingSlash && failOnTrailingSlash) { + return { + valid: false, + error: "URL must not end with a trailing slash", + }; + } + + let normalizedUrl = parsedUrl.toString(); + + const normalizedHasTrailingSlash = + normalizedUrl.endsWith("/") && normalizedUrl !== `${parsedUrl.origin}/`; + + if (normalizedHasTrailingSlash && removeTrailingSlash) { + normalizedUrl = normalizedUrl.slice(0, -1); + } + + return { + valid: true, + url: urlToValidate, + normalizedUrl, + }; +} + +function normalizeUrl( + url: string, + options: UrlValidationOptions = {}, +): string | null { + const result = isValidUrl(url, options); + return result.valid ? result.normalizedUrl || result.url || null : null; +} + +export { isValidUrl, normalizeUrl }; diff --git a/src/routes/health.ts b/src/routes/health.ts index c4d7e49..49d8ef8 100644 --- a/src/routes/health.ts +++ b/src/routes/health.ts @@ -1,7 +1,7 @@ import { redis } from "bun"; import { cassandra } from "#lib/database"; -import type { ExtendedRequest, RouteDef } from "#types/server"; +import type { ExtendedRequest, HealthResponse, RouteDef } from "#types/server"; const routeDef: RouteDef = { method: "GET", @@ -9,21 +9,42 @@ const routeDef: RouteDef = { returns: "application/json", }; +async function checkRedisHealth(): Promise { + try { + await redis.exists("__health_check__"); + return "healthy"; + } catch (error) { + return "unhealthy"; + } +} + async function handler(request: ExtendedRequest): Promise { const cassandraHealth = cassandra.getHealthStatus(); - const redisHealth = await redis - .connect() - .then(() => "healthy") - .catch(() => "unhealthy"); + const redisHealth = await checkRedisHealth(); - return Response.json({ - status: "healthy", + const isHealthy = cassandraHealth.connected && redisHealth === "healthy"; + + const response: HealthResponse = { + code: isHealthy ? 200 : 503, + success: isHealthy, + message: isHealthy + ? "All services are healthy" + : "One or more services are unhealthy", timestamp: new Date().toISOString(), requestTime: `${(performance.now() - request.startPerf).toFixed(2)}ms`, services: { - cassandra: cassandraHealth, - redis: redisHealth, + cassandra: { + status: cassandraHealth.connected ? "healthy" : "unhealthy", + hosts: cassandraHealth.hosts, + }, + redis: { + status: redisHealth, + }, }, + }; + + return Response.json(response, { + status: isHealthy ? 200 : 503, }); } diff --git a/src/routes/user/[id].ts b/src/routes/user/[id].ts index 53766cb..df2fe2b 100644 --- a/src/routes/user/[id].ts +++ b/src/routes/user/[id].ts @@ -94,7 +94,7 @@ async function handler(request: ExtendedRequest): Promise { const response: UserInfoResponse = { code: 404, success: false, - error: identifier ? "User not found" : "User not found", + error: "User not found", }; return Response.json(response, { status: 404 }); } diff --git a/src/server.ts b/src/server.ts index 806ce57..b76237e 100644 --- a/src/server.ts +++ b/src/server.ts @@ -1,7 +1,7 @@ import { resolve } from "node:path"; import { type Echo, echo } from "@atums/echo"; import { environment } from "#environment/config"; -import { reqLoggerIgnores } from "#environment/constants/server"; +import { reqLoggerIgnores } from "#environment/constants"; import { noFileLog } from "#index"; import { webSocketHandler } from "#websocket"; diff --git a/tsconfig.json b/tsconfig.json index 0ed7a0d..0821282 100644 --- a/tsconfig.json +++ b/tsconfig.json @@ -3,7 +3,6 @@ "baseUrl": "./", "paths": { "#*": ["src/*"], - "#environment/*": ["environment/*"], "#types/*": ["types/*"] }, "typeRoots": ["./node_modules/@types"], diff --git a/types/config/environment.ts b/types/config/environment.ts index 990a762..400d15f 100644 --- a/types/config/environment.ts +++ b/types/config/environment.ts @@ -2,6 +2,7 @@ type Environment = { port: number; host: string; development: boolean; + fqdn: string; }; export type { Environment }; diff --git a/types/config/index.ts b/types/config/index.ts index 168f0c1..a91a197 100644 --- a/types/config/index.ts +++ b/types/config/index.ts @@ -1,3 +1,4 @@ export * from "./environment"; export * from "./database"; export * from "./auth"; +export * from "./mailer"; diff --git a/types/config/mailer.ts b/types/config/mailer.ts new file mode 100644 index 0000000..cd02ab6 --- /dev/null +++ b/types/config/mailer.ts @@ -0,0 +1,42 @@ +type MailerConfig = { + address: string; + port: number; + from: string; + username: string; + password: string; + secure: boolean; + pool: boolean; + connectionTimeout: number; + socketTimeout: number; + maxConnections: number; + maxMessages: number; + replyTo: string; +}; + +type EmailOptions = { + to: string | string[]; + subject: string; + text?: string; + html?: string; + from?: string; + replyTo?: string; + cc?: string | string[]; + bcc?: string | string[]; + attachments?: EmailAttachment[]; +}; + +type EmailAttachment = { + filename: string; + content?: Buffer | string; + path?: string; + contentType?: string; + cid?: string; // content-ID for embedded images +}; + +type EmailResult = { + success: boolean; + messageId?: string; + error?: string; +}; + +export type { MailerConfig, EmailOptions, EmailAttachment, EmailResult }; diff --git a/types/lib/validation.ts b/types/lib/validation.ts index c65bf6c..ef3b509 100644 --- a/types/lib/validation.ts +++ b/types/lib/validation.ts @@ -10,4 +10,24 @@ type validationResult = { name?: string; }; -export type { genericValidation, validationResult }; +interface UrlValidationOptions { + failOnTrailingSlash?: boolean; + removeTrailingSlash?: boolean; + allowedProtocols?: string[]; + requireProtocol?: boolean; + allowLocalhost?: boolean; + allowIP?: boolean; + maxLength?: number; +} + +interface UrlValidationResult extends validationResult { + url?: string; + normalizedUrl?: string; +} + +export type { + genericValidation, + validationResult, + UrlValidationOptions, + UrlValidationResult, +}; diff --git a/types/server/requests/base.ts b/types/server/requests/base.ts new file mode 100644 index 0000000..4b2586a --- /dev/null +++ b/types/server/requests/base.ts @@ -0,0 +1,8 @@ +interface BaseResponse { + code: number; + success: boolean; + error?: string; + message?: string; +} + +export type { BaseResponse }; diff --git a/types/server/requests/health.ts b/types/server/requests/health.ts new file mode 100644 index 0000000..16cb8fd --- /dev/null +++ b/types/server/requests/health.ts @@ -0,0 +1,17 @@ +import type { BaseResponse } from "./base"; + +interface HealthResponse extends BaseResponse { + timestamp?: string; + requestTime?: string; + services?: { + cassandra: { + status: string; + hosts: number; + }; + redis: { + status: string; + }; + }; +} + +export type { HealthResponse }; diff --git a/types/server/requests/index.ts b/types/server/requests/index.ts index 7616f9e..89a792f 100644 --- a/types/server/requests/index.ts +++ b/types/server/requests/index.ts @@ -1 +1,3 @@ export * from "./user"; +export * from "./health"; +export * from "./base"; diff --git a/types/server/requests/user/base.ts b/types/server/requests/user/base.ts index b386e59..7f1a48f 100644 --- a/types/server/requests/user/base.ts +++ b/types/server/requests/user/base.ts @@ -1,10 +1,3 @@ -interface BaseResponse { - code: number; - success: boolean; - error?: string; - message?: string; -} - interface UserResponse { id: string; username: string; @@ -25,4 +18,4 @@ interface UserRow { updated_at: Date; } -export type { BaseResponse, UserResponse, UserRow }; +export type { UserResponse, UserRow };