diff --git a/Docker/dist/docker-compose.yaml b/Docker/dist/docker-compose.yaml index 420941e7e..db8bc5c07 100644 --- a/Docker/dist/docker-compose.yaml +++ b/Docker/dist/docker-compose.yaml @@ -1,6 +1,8 @@ services: client: - image: bluewaveuptime/uptime_client:latest + build: + context: ../../ + dockerfile: Docker/dist/client.Dockerfile restart: always environment: UPTIME_APP_API_BASE_URL: "http://localhost:5000/api/v1" @@ -11,7 +13,9 @@ services: depends_on: - server server: - image: bluewaveuptime/uptime_server:latest + build: + context: ../../ + dockerfile: Docker/dist/server.Dockerfile restart: always ports: - "5000:5000" @@ -24,7 +28,9 @@ services: # volumes: # - /var/run/docker.sock:/var/run/docker.sock:ro redis: - image: bluewaveuptime/uptime_redis:latest + build: + context: ../../ + dockerfile: Docker/dist/redis.Dockerfile restart: always ports: - "6379:6379" @@ -37,7 +43,9 @@ services: retries: 5 start_period: 5s mongodb: - image: bluewaveuptime/uptime_database_mongo:latest + build: + context: ../../ + dockerfile: Docker/dist/mongoDB.Dockerfile restart: always volumes: - ./mongo/data:/data/db diff --git a/Server/controllers/authController.js b/Server/controllers/authController.js index 05a9b9ccb..b5c21d619 100644 --- a/Server/controllers/authController.js +++ b/Server/controllers/authController.js @@ -16,11 +16,12 @@ import { handleValidationError, handleError } from "./controllerUtils.js"; const SERVICE_NAME = "authController"; class AuthController { - constructor(db, settingsService, emailService, jobQueue) { + constructor(db, settingsService, emailService, jobQueue, stringService) { this.db = db; this.settingsService = settingsService; this.emailService = emailService; this.jobQueue = jobQueue; + this.stringService = stringService; } /** @@ -153,7 +154,7 @@ class AuthController { // Compare password const match = await user.comparePassword(password); if (match !== true) { - const error = new Error(errorMessages.AUTH_INCORRECT_PASSWORD(req.language)); + const error = new Error(this.stringService.authIncorrectPassword); error.status = 401; next(error); return; @@ -206,7 +207,7 @@ class AuthController { if (!refreshToken) { // No refresh token provided - const error = new Error(errorMessages.NO_REFRESH_TOKEN(req.language)); + const error = new Error(this.stringService.noRefreshToken); error.status = 401; error.service = SERVICE_NAME; error.method = "refreshAuthToken"; @@ -221,8 +222,8 @@ class AuthController { // Invalid or expired refresh token, trigger logout const errorMessage = refreshErr.name === "TokenExpiredError" - ? errorMessages.EXPIRED_REFRESH_TOKEN - : errorMessages.INVALID_REFRESH_TOKEN; + ? this.stringService.expiredAuthToken + : this.stringService.invalidAuthToken; const error = new Error(errorMessage); error.status = 401; error.service = SERVICE_NAME; @@ -276,7 +277,7 @@ class AuthController { // TODO is this neccessary any longer? Verify ownership middleware should handle this if (req.params.userId !== req.user._id.toString()) { - const error = new Error(errorMessages.AUTH_UNAUTHORIZED(req.language)); + const error = new Error(this.stringService.unauthorized); error.status = 401; error.service = SERVICE_NAME; next(error); @@ -300,7 +301,7 @@ class AuthController { // If not a match, throw a 403 // 403 instead of 401 to avoid triggering axios interceptor if (!match) { - const error = new Error(errorMessages.AUTH_INCORRECT_PASSWORD(req.language)); + const error = new Error(this.stringService.authIncorrectPassword); error.status = 403; next(error); return; diff --git a/Server/index.js b/Server/index.js index 1b908436b..8bd4be260 100644 --- a/Server/index.js +++ b/Server/index.js @@ -76,6 +76,7 @@ import IORedis from "ioredis"; import TranslationService from './service/translationService.js'; import languageMiddleware from './middleware/languageMiddleware.js'; +import StringService from './service/stringService.js'; const SERVICE_NAME = "Server"; const SHUTDOWN_TIMEOUT = 1000; @@ -178,7 +179,8 @@ const startApp = async () => { const networkService = new NetworkService(axios, ping, logger, http, Docker, net); const statusService = new StatusService(db, logger); const notificationService = new NotificationService(emailService, db, logger); - const translationService = new TranslationService(logger); + const translationService = new TranslationService(logger, networkService); + const stringService = new StringService(translationService); const jobQueue = new JobQueue( db, @@ -200,7 +202,7 @@ const startApp = async () => { ServiceRegistry.register(StatusService.SERVICE_NAME, statusService); ServiceRegistry.register(NotificationService.SERVICE_NAME, notificationService); ServiceRegistry.register(TranslationService.SERVICE_NAME, translationService); - + ServiceRegistry.register(StringService.SERVICE_NAME, stringService); await translationService.initialize(); @@ -217,7 +219,8 @@ const startApp = async () => { ServiceRegistry.get(MongoDB.SERVICE_NAME), ServiceRegistry.get(SettingsService.SERVICE_NAME), ServiceRegistry.get(EmailService.SERVICE_NAME), - ServiceRegistry.get(JobQueue.SERVICE_NAME) + ServiceRegistry.get(JobQueue.SERVICE_NAME), + ServiceRegistry.get(StringService.SERVICE_NAME) ); const monitorController = new MonitorController( @@ -279,7 +282,7 @@ const startApp = async () => { app.use(cors()); app.use(express.json()); app.use(helmet()); - app.use(languageMiddleware); + app.use(languageMiddleware(stringService, translationService)); // Swagger UI app.use("/api-docs", swaggerUi.serve, swaggerUi.setup(openApiSpec)); diff --git a/Server/middleware/languageMiddleware.js b/Server/middleware/languageMiddleware.js index 52e7b9a2e..11c2f2aec 100644 --- a/Server/middleware/languageMiddleware.js +++ b/Server/middleware/languageMiddleware.js @@ -1,7 +1,9 @@ -const languageMiddleware = (req, res, next) => { +const languageMiddleware = (stringService, translationService) => (req, res, next) => { const acceptLanguage = req.headers['accept-language'] || 'en'; + const language = acceptLanguage.split(',')[0].slice(0, 2).toLowerCase(); - req.language = acceptLanguage.split(',')[0].slice(0, 2).toLowerCase(); + translationService.setLanguage(language); + stringService.setLanguage(language); next(); }; diff --git a/Server/service/networkService.js b/Server/service/networkService.js index 5e5f62721..f57abdd74 100644 --- a/Server/service/networkService.js +++ b/Server/service/networkService.js @@ -13,6 +13,8 @@ const UPROCK_ENDPOINT = "https://api.uprock.com/checkmate/push"; */ class NetworkService { static SERVICE_NAME = SERVICE_NAME; + static POEDITOR_BASE_URL = 'https://api.poeditor.com/v2'; + constructor(axios, ping, logger, http, Docker, net) { this.TYPE_PING = "ping"; this.TYPE_HTTP = "http"; @@ -30,6 +32,17 @@ class NetworkService { this.http = http; this.Docker = Docker; this.net = net; + + this.apiToken = process.env.POEDITOR_API_TOKEN; + this.projectId = process.env.POEDITOR_PROJECT_ID; + + if (!this.apiToken || !this.projectId) { + this.logger.error({ + message: 'POEditor API token or project ID is missing in environment variables', + service: this.SERVICE_NAME, + method: 'constructor' + }); + } } /** @@ -398,6 +411,95 @@ class NetworkService { return this.handleUnsupportedType(type); } } + + async getPoEditorLanguages() { + try { + const params = new URLSearchParams(); + params.append('api_token', this.apiToken); + params.append('id', this.projectId); + + const response = await this.axios.post(`${NetworkService.POEDITOR_BASE_URL}/languages/list`, params, { + headers: { + 'Content-Type': 'application/x-www-form-urlencoded' + } + }); + + return response.data.result.languages.map(lang => lang.code); + } catch (error) { + error.service = this.SERVICE_NAME; + error.method = "getPoEditorLanguages"; + throw error; + } + } + + async exportPoEditorTranslations(language) { + try { + const params = new URLSearchParams(); + params.append('api_token', this.apiToken); + params.append('id', this.projectId); + params.append('language', language); + params.append('type', 'key_value_json'); + + const exportResponse = await this.axios.post(`${NetworkService.POEDITOR_BASE_URL}/projects/export`, params, { + headers: { + 'Content-Type': 'application/x-www-form-urlencoded' + } + }); + + const { url } = exportResponse.data.result; + const translationsResponse = await this.axios.get(url); + return translationsResponse.data; + } catch (error) { + error.service = this.SERVICE_NAME; + error.method = "exportPoEditorTranslations"; + throw error; + } + } + + async getPoEditorTerms() { + try { + const params = new URLSearchParams(); + params.append('api_token', this.apiToken); + params.append('id', this.projectId); + + const response = await this.axios.post(`${NetworkService.POEDITOR_BASE_URL}/terms/list`, params, { + headers: { + 'Content-Type': 'application/x-www-form-urlencoded' + } + }); + + return response.data.result?.terms?.map(term => term.term.trim()) || []; + } catch (error) { + error.service = this.SERVICE_NAME; + error.method = "getPoEditorTerms"; + throw error; + } + } + + async addPoEditorTerms(terms) { + try { + const formattedTerms = terms.map(termObj => ({ + term: Object.keys(termObj)[0] + })); + + const params = new URLSearchParams(); + params.append('api_token', this.apiToken); + params.append('id', this.projectId); + params.append('data', JSON.stringify(formattedTerms)); + + const response = await this.axios.post(`${NetworkService.POEDITOR_BASE_URL}/terms/add`, params, { + headers: { + 'Content-Type': 'application/x-www-form-urlencoded' + } + }); + + return response.data; + } catch (error) { + error.service = this.SERVICE_NAME; + error.method = "addPoEditorTerms"; + throw error; + } + } } export default NetworkService; diff --git a/Server/service/stringService.js b/Server/service/stringService.js new file mode 100644 index 000000000..3c95dacd9 --- /dev/null +++ b/Server/service/stringService.js @@ -0,0 +1,322 @@ +class StringService { + static SERVICE_NAME = "StringService"; + + constructor(translationService) { + if (StringService.instance) { + return StringService.instance; + } + + this.translationService = translationService; + this._language = 'en'; // default language + StringService.instance = this; + } + + setLanguage(language) { + this._language = language; + } + + get language() { + return this._language; + } + + // Auth Messages + get dontHaveAccount() { + return this.translationService.getTranslation('dontHaveAccount'); + } + + get email() { + return this.translationService.getTranslation('email'); + } + + get forgotPassword() { + return this.translationService.getTranslation('forgotPassword'); + } + + get password() { + return this.translationService.getTranslation('password'); + } + + get signUp() { + return this.translationService.getTranslation('signUp'); + } + + get submit() { + return this.translationService.getTranslation('submit'); + } + + get title() { + return this.translationService.getTranslation('title'); + } + + get continue() { + return this.translationService.getTranslation('continue'); + } + + get enterEmail() { + return this.translationService.getTranslation('enterEmail'); + } + + get authLoginTitle() { + return this.translationService.getTranslation('authLoginTitle'); + } + + get authLoginEnterPassword() { + return this.translationService.getTranslation('authLoginEnterPassword'); + } + + get commonPassword() { + return this.translationService.getTranslation('commonPassword'); + } + + get commonBack() { + return this.translationService.getTranslation('commonBack'); + } + + get authForgotPasswordTitle() { + return this.translationService.getTranslation('authForgotPasswordTitle'); + } + + get authForgotPasswordResetPassword() { + return this.translationService.getTranslation('authForgotPasswordResetPassword'); + } + + get createPassword() { + return this.translationService.getTranslation('createPassword'); + } + + get createAPassword() { + return this.translationService.getTranslation('createAPassword'); + } + + get authRegisterAlreadyHaveAccount() { + return this.translationService.getTranslation('authRegisterAlreadyHaveAccount'); + } + + get commonAppName() { + return this.translationService.getTranslation('commonAppName'); + } + + get authLoginEnterEmail() { + return this.translationService.getTranslation('authLoginEnterEmail'); + } + + get authRegisterTitle() { + return this.translationService.getTranslation('authRegisterTitle'); + } + + get monitorGetAll() { + return this.translationService.getTranslation('monitorGetAll'); + } + + get monitorGetById() { + return this.translationService.getTranslation('monitorGetById'); + } + + get monitorCreate() { + return this.translationService.getTranslation('monitorCreate'); + } + + get monitorEdit() { + return this.translationService.getTranslation('monitorEdit'); + } + + get monitorDelete() { + return this.translationService.getTranslation('monitorDelete'); + } + + get monitorPause() { + return this.translationService.getTranslation('monitorPause'); + } + + get monitorResume() { + return this.translationService.getTranslation('monitorResume'); + } + + get monitorDemoAdded() { + return this.translationService.getTranslation('monitorDemoAdded'); + } + + get monitorStatsById() { + return this.translationService.getTranslation('monitorStatsById'); + } + + get monitorCertificate() { + return this.translationService.getTranslation('monitorCertificate'); + } + + // Maintenance Window Messages + get maintenanceWindowCreate() { + return this.translationService.getTranslation('maintenanceWindowCreate'); + } + + get maintenanceWindowGetById() { + return this.translationService.getTranslation('maintenanceWindowGetById'); + } + + get maintenanceWindowGetByTeam() { + return this.translationService.getTranslation('maintenanceWindowGetByTeam'); + } + + get maintenanceWindowDelete() { + return this.translationService.getTranslation('maintenanceWindowDelete'); + } + + get maintenanceWindowEdit() { + return this.translationService.getTranslation('maintenanceWindowEdit'); + } + + // Error Messages + get unknownError() { + return this.translationService.getTranslation('unknownError'); + } + + get friendlyError() { + return this.translationService.getTranslation('friendlyError'); + } + + get authIncorrectPassword() { + return this.translationService.getTranslation('authIncorrectPassword'); + } + + get unauthorized() { + return this.translationService.getTranslation('unauthorized'); + } + + get authAdminExists() { + return this.translationService.getTranslation('authAdminExists'); + } + + get authInviteNotFound() { + return this.translationService.getTranslation('authInviteNotFound'); + } + + get unknownService() { + return this.translationService.getTranslation('unknownService'); + } + + get noAuthToken() { + return this.translationService.getTranslation('noAuthToken'); + } + + get invalidAuthToken() { + return this.translationService.getTranslation('invalidAuthToken'); + } + + get expiredAuthToken() { + return this.translationService.getTranslation('expiredAuthToken'); + } + + // Queue Messages + get queueGetMetrics() { + return this.translationService.getTranslation('queueGetMetrics'); + } + + get queueAddJob() { + return this.translationService.getTranslation('queueAddJob'); + } + + get queueObliterate() { + return this.translationService.getTranslation('queueObliterate'); + } + + // Job Queue Messages + get jobQueueDeleteJobSuccess() { + return this.translationService.getTranslation('jobQueueDeleteJobSuccess'); + } + + get jobQueuePauseJob() { + return this.translationService.getTranslation('jobQueuePauseJob'); + } + + get jobQueueResumeJob() { + return this.translationService.getTranslation('jobQueueResumeJob'); + } + + // Status Page Messages + get statusPageByUrl() { + return this.translationService.getTranslation('statusPageByUrl'); + } + + get statusPageCreate() { + return this.translationService.getTranslation('statusPageCreate'); + } + + get statusPageNotFound() { + return this.translationService.getTranslation('statusPageNotFound'); + } + + get statusPageUrlNotUnique() { + return this.translationService.getTranslation('statusPageUrlNotUnique'); + } + + // Docker Messages + get dockerFail() { + return this.translationService.getTranslation('dockerFail'); + } + + get dockerNotFound() { + return this.translationService.getTranslation('dockerNotFound'); + } + + get dockerSuccess() { + return this.translationService.getTranslation('dockerSuccess'); + } + + // Port Messages + get portFail() { + return this.translationService.getTranslation('portFail'); + } + + get portSuccess() { + return this.translationService.getTranslation('portSuccess'); + } + + // Alert Messages + get alertCreate() { + return this.translationService.getTranslation('alertCreate'); + } + + get alertGetByUser() { + return this.translationService.getTranslation('alertGetByUser'); + } + + get alertGetByMonitor() { + return this.translationService.getTranslation('alertGetByMonitor'); + } + + get alertGetById() { + return this.translationService.getTranslation('alertGetById'); + } + + get alertEdit() { + return this.translationService.getTranslation('alertEdit'); + } + + get alertDelete() { + return this.translationService.getTranslation('alertDelete'); + } + + getDeletedCount(count) { + return this.translationService.getTranslation('deletedCount') + .replace('{count}', count); + } + + get pingSuccess() { + return this.translationService.getTranslation('pingSuccess'); + } + + get getAppSettings() { + return this.translationService.getTranslation('getAppSettings'); + } + + get updateAppSettings() { + return this.translationService.getTranslation('updateAppSettings'); + } + + getDbFindMonitorById(monitorId) { + return this.translationService.getTranslation('dbFindMonitorById') + .replace('${monitorId}', monitorId); + } +} + +export default StringService; \ No newline at end of file diff --git a/Server/service/translationService.js b/Server/service/translationService.js index 5da13b45b..9c21d9b34 100644 --- a/Server/service/translationService.js +++ b/Server/service/translationService.js @@ -1,27 +1,34 @@ -import axios from 'axios'; import fs from 'fs'; import path from 'path'; -import { formattedKey } from '../utils/formattedKey.js'; class TranslationService { static SERVICE_NAME = 'TranslationService'; - constructor(logger) { + constructor(logger, networkService) { this.logger = logger; + this.networkService = networkService; this.translations = {}; - this.apiToken = process.env.POEDITOR_API_TOKEN; - this.projectId = process.env.POEDITOR_PROJECT_ID; - this.baseUrl = 'https://api.poeditor.com/v2'; + this._language = 'en'; this.localesDir = path.join(process.cwd(), 'locales'); } + setLanguage(language) { + this._language = language; + } + + get language() { + return this._language; + } + async initialize() { try { const loadedFromFiles = await this.loadFromFiles(); - if (!loadedFromFiles) { await this.loadTranslations(); } + + // Yeni eklenen terimleri POEditor'e gönder + await this.syncTermsWithPOEditor(); } catch (error) { this.logger.error({ message: error.message, @@ -70,16 +77,47 @@ class TranslationService { } async loadTranslations() { + let hasError = false; try { const languages = await this.getLanguages(); for (const language of languages) { - const translations = await this.exportTranslations(language); - this.translations[language] = translations; + try { + const translations = await this.exportTranslations(language); + this.translations[language] = translations; + } catch (error) { + hasError = true; + this.logger.error({ + message: `Failed to fetch translations from POEditor for language ${language}: ${error.message}`, + service: 'TranslationService', + method: 'loadTranslations', + stack: error.stack + }); + } + } + + if (hasError || Object.keys(this.translations[this._language]).length === 0) { + this.logger.error({ + message: 'Failed to fetch translations from POEditor, using locales_en.json', + service: 'TranslationService', + method: 'loadTranslations' + }); + + + // Load translations from locales_en.json in utils directory + const utilsPath = path.join(process.cwd(), 'utils'); + const utilsFilePath = path.join(utilsPath, 'locales_en.json'); + if (fs.existsSync(utilsFilePath)) { + const content = fs.readFileSync(utilsFilePath, 'utf8'); + this.translations['en'] = JSON.parse(content); + } else { + throw new Error('locales_en.json file not found'); + } } await this.saveTranslations(); } catch (error) { + hasError = true; this.logger.error({ message: error.message, service: 'TranslationService', @@ -91,17 +129,7 @@ class TranslationService { async getLanguages() { try { - const params = new URLSearchParams(); - params.append('api_token', this.apiToken); - params.append('id', this.projectId); - - const response = await axios.post(`${this.baseUrl}/languages/list`, params, { - headers: { - 'Content-Type': 'application/x-www-form-urlencoded' - } - }); - - return response.data.result.languages.map(lang => lang.code); + return await this.networkService.getPoEditorLanguages(); } catch (error) { this.logger.error({ message: error.message, @@ -115,22 +143,7 @@ class TranslationService { async exportTranslations(language) { try { - const params = new URLSearchParams(); - params.append('api_token', this.apiToken); - params.append('id', this.projectId); - params.append('language', language); - params.append('type', 'key_value_json'); - - const exportResponse = await axios.post(`${this.baseUrl}/projects/export`, params, { - headers: { - 'Content-Type': 'application/x-www-form-urlencoded' - } - }); - - const { url } = exportResponse.data.result; - - const translationsResponse = await axios.get(url); - return translationsResponse.data; + return await this.networkService.exportPoEditorTranslations(language); } catch (error) { this.logger.error({ message: error.message, @@ -153,6 +166,11 @@ class TranslationService { fs.writeFileSync(filePath, JSON.stringify(translations, null, 2)); } + const utilsPath = path.join(process.cwd(), 'utils'); + const enTranslations = this.translations['en'] || {}; + const utilsFilePath = path.join(utilsPath, 'locales_en.json'); + fs.writeFileSync(utilsFilePath, JSON.stringify(enTranslations, null, 2)); + this.logger.info({ message: 'Translations saved to files successfully', service: 'TranslationService', @@ -168,10 +186,11 @@ class TranslationService { } } - getTranslation(key, language = 'en') { - const formattedKeyText = formattedKey(key); + getTranslation(key) { + let language = this._language; + try { - return this.translations[language]?.[formattedKeyText] || this.translations['en']?.[formattedKeyText] || formattedKeyText; + return this.translations[language]?.[key] || this.translations['en']?.[key] || key; } catch (error) { this.logger.error({ message: error.message, @@ -182,6 +201,94 @@ class TranslationService { return key; } } + + async getTermsFromPOEditor() { + try { + return await this.networkService.getPoEditorTerms(); + } catch (error) { + this.logger.error({ + message: error.message, + service: 'TranslationService', + method: 'getTermsFromPOEditor', + stack: error.stack + }); + return []; + } + } + + async addTermsToPOEditor(terms) { + try { + if (!terms.length) return; + + const response = await this.networkService.addPoEditorTerms(terms); + + if (response.response?.status === 'fail') { + throw new Error(response.response.message || 'Failed to add terms to POEditor'); + } + + this.logger.info({ + message: `${terms.length} new terms added to POEditor`, + service: 'TranslationService', + method: 'addTermsToPOEditor', + response: response + }); + + return response; + } catch (error) { + this.logger.error({ + message: `Failed to add terms to POEditor: ${error.message}`, + service: 'TranslationService', + method: 'addTermsToPOEditor', + stack: error.stack, + terms: terms + }); + throw error; + } + } + + async syncTermsWithPOEditor() { + try { + const utilsPath = path.join(process.cwd(), 'utils'); + const utilsFilePath = path.join(utilsPath, 'locales_en.json'); + const enTranslations = JSON.parse(fs.readFileSync(utilsFilePath, 'utf8')); + const localTerms = Object.keys(enTranslations) + .map(term => term); + + const poeditorTerms = await this.getTermsFromPOEditor(); + + const newTerms = localTerms?.filter(term => !poeditorTerms?.includes(term)); + + + this.logger.info({ + message: `Comparison results - New terms found: ${newTerms.length}`, + sampleNewTerms: newTerms.slice(0, 5), + service: 'TranslationService', + method: 'syncTermsWithPOEditor' + }); + + if (newTerms.length > 0) { + const formattedTerms = newTerms.map(term => ({ + [term]: enTranslations[term] || '', + })); + + await this.addTermsToPOEditor(formattedTerms); + + } else { + this.logger.info({ + message: 'No new terms found to synchronize', + service: 'TranslationService', + method: 'syncTermsWithPOEditor' + }); + } + } catch (error) { + this.logger.error({ + message: error.message, + service: 'TranslationService', + method: 'syncTermsWithPOEditor', + stack: error.stack + }); + } + } } export default TranslationService; \ No newline at end of file diff --git a/Server/tests/controllers/queueController.test.js b/Server/tests/controllers/queueController.test.js index b04504186..72450a0bd 100644 --- a/Server/tests/controllers/queueController.test.js +++ b/Server/tests/controllers/queueController.test.js @@ -8,10 +8,10 @@ import { import { successMessages } from "../../utils/messages.js"; import sinon from "sinon"; -describe("Queue Controller - getMetrics", function() { +describe("Queue Controller - getMetrics", function () { let req, res, next; - beforeEach(function() { + beforeEach(function () { req = { headers: {}, params: {}, @@ -32,14 +32,14 @@ describe("Queue Controller - getMetrics", function() { sinon.restore(); }); - it("should throw an error if getMetrics throws an error", async function() { + it("should throw an error if getMetrics throws an error", async function () { req.jobQueue.getMetrics.throws(new Error("getMetrics error")); await getMetrics(req, res, next); expect(next.firstCall.args[0]).to.be.an("error"); expect(next.firstCall.args[0].message).to.equal("getMetrics error"); }); - it("should return a success message and data if getMetrics is successful", async function() { + it("should return a success message and data if getMetrics is successful", async function () { const data = { data: "metrics" }; req.jobQueue.getMetrics.returns(data); await getMetrics(req, res, next); @@ -52,10 +52,10 @@ describe("Queue Controller - getMetrics", function() { }); }); -describe("Queue Controller - getJobs", function() { +describe("Queue Controller - getJobs", function () { let req, res, next; - beforeEach(function() { + beforeEach(function () { req = { headers: {}, params: {}, @@ -76,14 +76,14 @@ describe("Queue Controller - getJobs", function() { sinon.restore(); }); - it("should reject with an error if getJobs throws an error", async function() { + it("should reject with an error if getJobs throws an error", async function () { req.jobQueue.getJobStats.throws(new Error("getJobs error")); await getJobs(req, res, next); expect(next.firstCall.args[0]).to.be.an("error"); expect(next.firstCall.args[0].message).to.equal("getJobs error"); }); - it("should return a success message and data if getJobs is successful", async function() { + it("should return a success message and data if getJobs is successful", async function () { const data = { data: "jobs" }; req.jobQueue.getJobStats.returns(data); await getJobs(req, res, next); @@ -96,10 +96,10 @@ describe("Queue Controller - getJobs", function() { }); }); -describe("Queue Controller - addJob", function() { +describe("Queue Controller - addJob", function () { let req, res, next; - beforeEach(function() { + beforeEach(function () { req = { headers: {}, params: {}, @@ -120,14 +120,14 @@ describe("Queue Controller - addJob", function() { sinon.restore(); }); - it("should reject with an error if addJob throws an error", async function() { + it("should reject with an error if addJob throws an error", async function () { req.jobQueue.addJob.throws(new Error("addJob error")); await addJob(req, res, next); expect(next.firstCall.args[0]).to.be.an("error"); expect(next.firstCall.args[0].message).to.equal("addJob error"); }); - it("should return a success message if addJob is successful", async function() { + it("should return a success message if addJob is successful", async function () { req.jobQueue.addJob.resolves(); await addJob(req, res, next); expect(res.status.firstCall.args[0]).to.equal(200); @@ -138,10 +138,10 @@ describe("Queue Controller - addJob", function() { }); }); -describe("Queue Controller - obliterateQueue", function() { +describe("Queue Controller - obliterateQueue", function () { let req, res, next; - beforeEach(function() { + beforeEach(function () { req = { headers: {}, params: {}, @@ -162,14 +162,14 @@ describe("Queue Controller - obliterateQueue", function() { sinon.restore(); }); - it("should reject with an error if obliterateQueue throws an error", async function() { + it("should reject with an error if obliterateQueue throws an error", async function () { req.jobQueue.obliterate.throws(new Error("obliterateQueue error")); await obliterateQueue(req, res, next); expect(next.firstCall.args[0]).to.be.an("error"); expect(next.firstCall.args[0].message).to.equal("obliterateQueue error"); }); - it("should return a success message if obliterateQueue is successful", async function() { + it("should return a success message if obliterateQueue is successful", async function () { req.jobQueue.obliterate.resolves(); await obliterateQueue(req, res, next); expect(res.status.firstCall.args[0]).to.equal(200); diff --git a/Server/utils/formattedKey.js b/Server/utils/formattedKey.js deleted file mode 100644 index b60e6783b..000000000 --- a/Server/utils/formattedKey.js +++ /dev/null @@ -1,14 +0,0 @@ -/** - * Converts a snake_case or SCREAMING_SNAKE_CASE key to camelCase - * Example: AUTH_INCORRECT_PASSWORD -> authIncorrectPassword - * @param {string} key - The key to format - * @returns {string} - The formatted key in camelCase - */ -export const formattedKey = (key) => { - return key.toLowerCase() - .split('_') - .map((word, index) => - index === 0 ? word : word.charAt(0).toUpperCase() + word.slice(1) - ) - .join(''); -}; \ No newline at end of file diff --git a/Server/utils/locales_en.json b/Server/utils/locales_en.json new file mode 100644 index 000000000..58eb0cf49 --- /dev/null +++ b/Server/utils/locales_en.json @@ -0,0 +1,143 @@ +{ + "dontHaveAccount": "Don't have account", + "email": "E-mail", + "forgotPassword": "Forgot Password", + "password": "password", + "signUp": "Sign up", + "submit": "Submit", + "title": "Title", + "continue": "Continue", + "enterEmail": "Enter your email", + "authLoginTitle": "Log In", + "authLoginEnterPassword": "Enter your password", + "commonPassword": "Password", + "commonBack": "Back", + "authForgotPasswordTitle": "Forgot password?", + "authForgotPasswordResetPassword": "Reset password", + "createPassword": "Create your password", + "createAPassword": "Create a password", + "authRegisterAlreadyHaveAccount": "Already have an account?", + "commonAppName": "BlueWave Uptime", + "authLoginEnterEmail": "Enter your email", + "authRegisterTitle": "Create an account", + "authRegisterStepOneTitle": "Create your account", + "authRegisterStepOneDescription": "Enter your details to get started", + "authRegisterStepTwoTitle": "Set up your profile", + "authRegisterStepTwoDescription": "Tell us more about yourself", + "authRegisterStepThreeTitle": "Almost done!", + "authRegisterStepThreeDescription": "Review your information", + "authForgotPasswordDescription": "No worries, we'll send you reset instructions.", + "authForgotPasswordSendInstructions": "Send instructions", + "authForgotPasswordBackTo": "Back to", + "authCheckEmailTitle": "Check your email", + "authCheckEmailDescription": "We sent a password reset link to {{email}}", + "authCheckEmailResendEmail": "Resend email", + "authCheckEmailBackTo": "Back to", + "goBackTo": "Go back to", + "authCheckEmailDidntReceiveEmail": "Didn't receive the email?", + "authCheckEmailClickToResend": "Click to resend", + "authSetNewPasswordTitle": "Set new password", + "authSetNewPasswordDescription": "Your new password must be different from previously used passwords.", + "authSetNewPasswordNewPassword": "New password", + "authSetNewPasswordConfirmPassword": "Confirm password", + "confirmPassword": "Confirm your password", + "authSetNewPasswordResetPassword": "Reset password", + "authSetNewPasswordBackTo": "Back to", + "authPasswordMustBeAtLeast": "Must be at least", + "authPasswordCharactersLong": "8 characters long", + "authPasswordMustContainAtLeast": "Must contain at least", + "authPasswordSpecialCharacter": "one special character", + "authPasswordOneNumber": "one number", + "authPasswordUpperCharacter": "one upper character", + "authPasswordLowerCharacter": "one lower character", + "authPasswordConfirmAndPassword": "Confirm password and password", + "authPasswordMustMatch": "must match", + "friendlyError": "Something went wrong...", + "unknownError": "An unknown error occurred", + "unauthorized": "Unauthorized access", + "authAdminExists": "Admin already exists", + "authInviteNotFound": "Invite not found", + "unknownService": "Unknown service", + "noAuthToken": "No auth token provided", + "invalidAuthToken": "Invalid auth token", + "expiredAuthToken": "Token expired", + "noRefreshToken": "No refresh token provided", + "invalidRefreshToken": "Invalid refresh token", + "expiredRefreshToken": "Refresh token expired", + "requestNewAccessToken": "Request new access token", + "invalidPayload": "Invalid payload", + "verifyOwnerNotFound": "Document not found", + "verifyOwnerUnauthorized": "Unauthorized access", + "insufficientPermissions": "Insufficient permissions", + "dbUserExists": "User already exists", + "dbUserNotFound": "User not found", + "dbTokenNotFound": "Token not found", + "dbResetPasswordBadMatch": "New password must be different from old password", + "dbFindMonitorById": "Monitor with id ${monitorId} not found", + "dbDeleteChecks": "No checks found for monitor with id ${monitorId}", + "authIncorrectPassword": "Incorrect password", + "authUnauthorized": "Unauthorized access", + "monitorGetById": "Monitor not found", + "monitorGetByUserId": "No monitors found for user", + "jobQueueWorkerClose": "Error closing worker", + "jobQueueDeleteJob": "Job not found in queue", + "jobQueueObliterate": "Error obliterating queue", + "pingCannotResolve": "No response", + "statusPageNotFound": "Status page not found", + "statusPageUrlNotUnique": "Status page url must be unique", + "dockerFail": "Failed to fetch Docker container information", + "dockerNotFound": "Docker container not found", + "portFail": "Failed to connect to port", + "alertCreate": "Alert created successfully", + "alertGetByUser": "Got alerts successfully", + "alertGetByMonitor": "Got alerts by Monitor successfully", + "alertGetById": "Got alert by Id successfully", + "alertEdit": "Alert edited successfully", + "alertDelete": "Alert deleted successfully", + "authCreateUser": "User created successfully", + "authLoginUser": "User logged in successfully", + "authLogoutUser": "User logged out successfully", + "authUpdateUser": "User updated successfully", + "authCreateRecoveryToken": "Recovery token created successfully", + "authVerifyRecoveryToken": "Recovery token verified successfully", + "authResetPassword": "Password reset successfully", + "authAdminCheck": "Admin check completed successfully", + "authDeleteUser": "User deleted successfully", + "authTokenRefreshed": "Auth token is refreshed", + "authGetAllUsers": "Got all users successfully", + "inviteIssued": "Invite sent successfully", + "inviteVerified": "Invite verified successfully", + "checkCreate": "Check created successfully", + "checkGet": "Got checks successfully", + "checkDelete": "Checks deleted successfully", + "checkUpdateTtl": "Checks TTL updated successfully", + "monitorGetAll": "Got all monitors successfully", + "monitorStatsById": "Got monitor stats by Id successfully", + "monitorGetByIdSuccess": "Got monitor by Id successfully", + "monitorGetByTeamId": "Got monitors by Team Id successfully", + "monitorGetByUserIdSuccess": "Got monitor for ${userId} successfully", + "monitorCreate": "Monitor created successfully", + "monitorDelete": "Monitor deleted successfully", + "monitorEdit": "Monitor edited successfully", + "monitorCertificate": "Got monitor certificate successfully", + "monitorDemoAdded": "Successfully added demo monitors", + "queueGetMetrics": "Got metrics successfully", + "queueAddJob": "Job added successfully", + "queueObliterate": "Queue obliterated", + "jobQueueDeleteJobSuccess": "Job removed successfully", + "jobQueuePauseJob": "Job paused successfully", + "jobQueueResumeJob": "Job resumed successfully", + "maintenanceWindowGetById": "Got Maintenance Window by Id successfully", + "maintenanceWindowCreate": "Maintenance Window created successfully", + "maintenanceWindowGetByTeam": "Got Maintenance Windows by Team successfully", + "maintenanceWindowDelete": "Maintenance Window deleted successfully", + "maintenanceWindowEdit": "Maintenance Window edited successfully", + "pingSuccess": "Success", + "getAppSettings": "Got app settings successfully", + "updateAppSettings": "Updated app settings successfully", + "statusPageByUrl": "Got status page by url successfully", + "statusPageCreate": "Status page created successfully", + "newTermsAdded": "New terms added to POEditor", + "dockerSuccess": "Docker container status fetched successfully", + "portSuccess": "Port connected successfully" +} \ No newline at end of file