MERGE: dev ino feat/service-layer
This commit is contained in:
commit
6404335040
220 changed files with 12582 additions and 10400 deletions
|
@ -1,6 +1,7 @@
|
|||
import axios, { AxiosRequestConfig } from 'axios';
|
||||
import { getLogger, Logger } from '../logging/initalize.js';
|
||||
|
||||
// !!!! when logger is done -> change
|
||||
const logger: Logger = getLogger();
|
||||
|
||||
/**
|
||||
* Utility function to fetch data from an API endpoint with error handling.
|
||||
|
@ -8,35 +9,34 @@ import axios, { AxiosRequestConfig } from 'axios';
|
|||
*
|
||||
* @param url The API endpoint to fetch from.
|
||||
* @param description A short description of what is being fetched (for logging).
|
||||
* @param params
|
||||
* @param options Contains further options such as params (the query params) and responseType (whether the response
|
||||
* should be parsed as JSON ("json") or whether it should be returned as plain text ("text")
|
||||
* @returns The response data if successful, or null if an error occurs.
|
||||
*/
|
||||
export async function fetchWithLogging<T>(
|
||||
url: string,
|
||||
description: string,
|
||||
params?: Record<string, any>
|
||||
options?: {
|
||||
params?: Record<string, any>;
|
||||
query?: Record<string, any>;
|
||||
responseType?: 'json' | 'text';
|
||||
}
|
||||
): Promise<T | null> {
|
||||
try {
|
||||
const config: AxiosRequestConfig = params ? { params } : {};
|
||||
|
||||
const config: AxiosRequestConfig = options || {};
|
||||
const response = await axios.get<T>(url, config);
|
||||
return response.data;
|
||||
} catch (error: any) {
|
||||
if (error.response) {
|
||||
if (error.response.status === 404) {
|
||||
console.error(
|
||||
`❌ ERROR: ${description} not found (404) at "${url}".`
|
||||
);
|
||||
logger.debug(`❌ ERROR: ${description} not found (404) at "${url}".`);
|
||||
} else {
|
||||
console.error(
|
||||
logger.debug(
|
||||
`❌ ERROR: Failed to fetch ${description}. Status: ${error.response.status} - ${error.response.statusText} (URL: "${url}")`
|
||||
);
|
||||
}
|
||||
} else {
|
||||
console.error(
|
||||
`❌ ERROR: Network or unexpected error when fetching ${description}:`,
|
||||
error.message
|
||||
);
|
||||
logger.debug(`❌ ERROR: Network or unexpected error when fetching ${description}:`, error.message);
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
|
23
backend/src/util/async.ts
Normal file
23
backend/src/util/async.ts
Normal file
|
@ -0,0 +1,23 @@
|
|||
/**
|
||||
* Replace all occurrences of regex in str with the result of asyncFn called with the matching snippet and each of
|
||||
* the parts matched by a group in the regex as arguments.
|
||||
*
|
||||
* @param str The string where to replace the occurrences
|
||||
* @param regex
|
||||
* @param replacementFn
|
||||
*/
|
||||
export async function replaceAsync(str: string, regex: RegExp, replacementFn: (match: string, ...args: string[]) => Promise<string>) {
|
||||
const promises: Promise<string>[] = [];
|
||||
|
||||
// First run through matches: add all Promises resulting from the replacement function
|
||||
str.replace(regex, (full, ...args) => {
|
||||
promises.push(replacementFn(full, ...args));
|
||||
return full;
|
||||
});
|
||||
|
||||
// Wait for the replacements to get loaded. Reverse them so when popping them, we work in a FIFO manner.
|
||||
const replacements: string[] = await Promise.all(promises);
|
||||
|
||||
// Second run through matches: Replace them by their previously computed replacements.
|
||||
return str.replace(regex, () => replacements.pop()!);
|
||||
}
|
|
@ -1,5 +1,9 @@
|
|||
const PREFIX = 'DWENGO_';
|
||||
const DB_PREFIX = PREFIX + 'DB_';
|
||||
const IDP_PREFIX = PREFIX + 'AUTH_';
|
||||
const STUDENT_IDP_PREFIX = IDP_PREFIX + 'STUDENT_';
|
||||
const TEACHER_IDP_PREFIX = IDP_PREFIX + 'TEACHER_';
|
||||
const CORS_PREFIX = PREFIX + 'CORS_';
|
||||
|
||||
type EnvVar = { key: string; required?: boolean; defaultValue?: any };
|
||||
|
||||
|
@ -11,6 +15,18 @@ export const EnvVars: { [key: string]: EnvVar } = {
|
|||
DbUsername: { key: DB_PREFIX + 'USERNAME', required: true },
|
||||
DbPassword: { key: DB_PREFIX + 'PASSWORD', required: true },
|
||||
DbUpdate: { key: DB_PREFIX + 'UPDATE', defaultValue: false },
|
||||
LearningContentRepoApiBaseUrl: { key: PREFIX + 'LEARNING_CONTENT_REPO_API_BASE_URL', defaultValue: 'https://dwengo.org/backend/api' },
|
||||
FallbackLanguage: { key: PREFIX + 'FALLBACK_LANGUAGE', defaultValue: 'nl' },
|
||||
UserContentPrefix: { key: DB_PREFIX + 'USER_CONTENT_PREFIX', defaultValue: 'u_' },
|
||||
IdpStudentUrl: { key: STUDENT_IDP_PREFIX + 'URL', required: true },
|
||||
IdpStudentClientId: { key: STUDENT_IDP_PREFIX + 'CLIENT_ID', required: true },
|
||||
IdpStudentJwksEndpoint: { key: STUDENT_IDP_PREFIX + 'JWKS_ENDPOINT', required: true },
|
||||
IdpTeacherUrl: { key: TEACHER_IDP_PREFIX + 'URL', required: true },
|
||||
IdpTeacherClientId: { key: TEACHER_IDP_PREFIX + 'CLIENT_ID', required: true },
|
||||
IdpTeacherJwksEndpoint: { key: TEACHER_IDP_PREFIX + 'JWKS_ENDPOINT', required: true },
|
||||
IdpAudience: { key: IDP_PREFIX + 'AUDIENCE', defaultValue: 'account' },
|
||||
CorsAllowedOrigins: { key: CORS_PREFIX + 'ALLOWED_ORIGINS', defaultValue: '' },
|
||||
CorsAllowedHeaders: { key: CORS_PREFIX + 'ALLOWED_HEADERS', defaultValue: 'Authorization,Content-Type' },
|
||||
} as const;
|
||||
|
||||
/**
|
||||
|
@ -36,9 +52,7 @@ export function getNumericEnvVar(envVar: EnvVar): number {
|
|||
const valueString = getEnvVar(envVar);
|
||||
const value = parseInt(valueString);
|
||||
if (isNaN(value)) {
|
||||
throw new Error(
|
||||
`Invalid value for environment variable ${envVar.key}: ${valueString}. Expected a number.`
|
||||
);
|
||||
throw new Error(`Invalid value for environment variable ${envVar.key}: ${valueString}. Expected a number.`);
|
||||
} else {
|
||||
return value;
|
||||
}
|
||||
|
|
26
backend/src/util/links.ts
Normal file
26
backend/src/util/links.ts
Normal file
|
@ -0,0 +1,26 @@
|
|||
import { LearningObjectIdentifier } from '../interfaces/learning-content';
|
||||
|
||||
export function isValidHttpUrl(url: string): boolean {
|
||||
try {
|
||||
const parsedUrl = new URL(url, 'http://test.be');
|
||||
return parsedUrl.protocol === 'http:' || parsedUrl.protocol === 'https:';
|
||||
} catch (e) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
export function getUrlStringForLearningObject(learningObjectId: LearningObjectIdentifier) {
|
||||
let url = `/learningObject/${learningObjectId.hruid}/html?language=${learningObjectId.language}`;
|
||||
if (learningObjectId.version) {
|
||||
url += `&version=${learningObjectId.version}`;
|
||||
}
|
||||
return url;
|
||||
}
|
||||
|
||||
export function getUrlStringForLearningObjectHTML(learningObjectIdentifier: LearningObjectIdentifier): string {
|
||||
let url = `/learningObject/${learningObjectIdentifier.hruid}/html?language=${learningObjectIdentifier.language}`;
|
||||
if (learningObjectIdentifier.version) {
|
||||
url += `&version=${learningObjectIdentifier.version}`;
|
||||
}
|
||||
return url;
|
||||
}
|
|
@ -2,6 +2,9 @@ import fs from 'fs';
|
|||
import path from 'path';
|
||||
import yaml from 'js-yaml';
|
||||
import { FALLBACK_LANG } from '../config.js';
|
||||
import { getLogger, Logger } from '../logging/initalize.js';
|
||||
|
||||
const logger: Logger = getLogger();
|
||||
|
||||
export function loadTranslations<T>(language: string): T {
|
||||
try {
|
||||
|
@ -9,15 +12,8 @@ export function loadTranslations<T>(language: string): T {
|
|||
const yamlFile = fs.readFileSync(filePath, 'utf8');
|
||||
return yaml.load(yamlFile) as T;
|
||||
} catch (error) {
|
||||
console.error(
|
||||
`Cannot load translation for ${language}, fallen back to dutch`
|
||||
);
|
||||
console.error(error);
|
||||
const fallbackPath = path.join(
|
||||
process.cwd(),
|
||||
'_i18n',
|
||||
`${FALLBACK_LANG}.yml`
|
||||
);
|
||||
logger.warn(`Cannot load translation for ${language}, fallen back to dutch`, error);
|
||||
const fallbackPath = path.join(process.cwd(), '_i18n', `${FALLBACK_LANG}.yml`);
|
||||
return yaml.load(fs.readFileSync(fallbackPath, 'utf8')) as T;
|
||||
}
|
||||
}
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue