Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 2 additions & 0 deletions .github/workflows/ci.yml
Original file line number Diff line number Diff line change
Expand Up @@ -13,11 +13,13 @@ env:
APP_MAPBOX_ACCESS_TOKEN: ${{ vars.APP_MAPBOX_ACCESS_TOKEN }}
APP_RISK_ADMIN_URL: ${{ vars.APP_RISK_ADMIN_URL }}
APP_RISK_API_ENDPOINT: ${{ vars.APP_RISK_API_ENDPOINT }}
APP_TRANSLATION_API_ENDPOINT: ${{ vars.APP_TRANSLATION_API_ENDPOINT }}
APP_SENTRY_DSN: ${{ vars.APP_SENTRY_DSN }}
APP_SENTRY_NORMALIZE_DEPTH: ${{ vars.APP_SENTRY_NORMALIZE_DEPTH }}
APP_SENTRY_TRACES_SAMPLE_RATE: ${{ vars.APP_SENTRY_TRACES_SAMPLE_RATE }}
APP_SHOW_ENV_BANNER: ${{ vars.APP_SHOW_ENV_BANNER }}
APP_TINY_API_KEY: ${{ vars.APP_TINY_API_KEY }}
APP_TRANSLATION_API_KEY: ${{ vars.APP_TRANSLATION_API_KEY }}
APP_TITLE: ${{ vars.APP_TITLE }}
GITHUB_WORKFLOW: true

Expand Down
4 changes: 4 additions & 0 deletions app/env.ts
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,10 @@ export default defineConfig({
return value as ('production' | 'staging' | 'testing' | `alpha-${number}` | 'development' | 'APP_ENVIRONMENT_PLACEHOLDER');
},
APP_API_ENDPOINT: Schema.string({ format: 'url', protocol: true, tld: false }),

APP_TRANSLATION_API_ENDPOINT: Schema.string({ format: 'url', protocol: true, tld: false }),
APP_TRANSLATION_API_KEY: Schema.string(),

APP_ADMIN_URL: Schema.string.optional({ format: 'url', protocol: true, tld: false }),
APP_MAPBOX_ACCESS_TOKEN: Schema.string(),
APP_TINY_API_KEY: Schema.string(),
Expand Down
5 changes: 4 additions & 1 deletion app/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -13,12 +13,15 @@
"translatte": "tsx scripts/translatte/main.ts",
"translatte:generate": "pnpm translatte generate-migration ../translationMigrations ./src/**/i18n.json ../packages/ui/src/**/i18n.json",
"translatte:lint": "pnpm translatte lint ./src/**/i18n.json ../packages/ui/src/**/i18n.json",
"initialize:type": "mkdir -p generated/ && pnpm initialize:type:go-api && pnpm initialize:type:risk-api",
"initialize:type": "mkdir -p generated/ && pnpm initialize:type:go-api && pnpm initialize:type:risk-api && pnpm initialize:type:translations",
"initialize:type:go-api": "test -f ./generated/types.ts && true || cp types.stub.ts ./generated/types.ts",
"initialize:type:risk-api": "test -f ./generated/riskTypes.ts && true || cp types.stub.ts ./generated/riskTypes.ts",
"initialize:type:translations": "test -f ./generated/translationTypes.ts && true || cp types.stub.ts ./generated/translationTypes.ts",
"generate:type": "pnpm generate:type:go-api && pnpm generate:type:risk-api",
"generate:type:go-api": "GO_API_HASH=$(git rev-parse HEAD:go-api); dotenv -- cross-var openapi-typescript https://raw.githubusercontent.com/IFRCGo/go-api-artifacts/refs/heads/main/generated/$GO_API_HASH/openapi-schema.yaml -o ./generated/types.ts --alphabetize",
"generate:type:risk-api": "dotenv -- cross-var openapi-typescript ../go-risk-module-api/openapi-schema.yaml -o ./generated/riskTypes.ts --alphabetize",
"generate:type:translations": "dotenv -- cross-var openapi-typescript \"%APP_TRANSLATION_API_ENDPOINT%swagger/v1/swagger.json/\" -o ./generated/translationTypes.ts --alphabetize",
"postgenerate:type:translations": "tsx scripts/fix-generated.ts",
"prestart": "pnpm initialize:type",
"start": "pnpm -F @ifrc-go/ui build && vite",
"prebuild": "pnpm initialize:type",
Expand Down
9 changes: 9 additions & 0 deletions app/scripts/fix-generated.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
import { readFileSync, writeFileSync } from 'fs';

const path = 'generated/translationTypes.ts';

const content = readFileSync(path, 'utf-8');

// If already added, skip
writeFileSync(path, `// eslint-disable-next-line @typescript-eslint/ban-ts-comment\n // @ts-nocheck\n${content}`);
console.log('✔ Added // @ts-nocheck to translationTypes.ts');
103 changes: 103 additions & 0 deletions app/scripts/translatte/commands/pushStringsFromExcelToIfrc.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,103 @@
import { readFileSync } from "fs";

// FIXME: get this from params
const applicationId = 18;

function resolveUrl(from: string, to: string) {
const resolvedUrl = new URL(to, new URL(from, 'resolve://'));
if (resolvedUrl.protocol === 'resolve:') {
const { pathname, search, hash } = resolvedUrl;
return pathname + search + hash;
}
return resolvedUrl.toString();
}

/*
async function fetchTranslations(ifrcApiUrl: string, ifrcApiKey: string) {
const endpoint = resolveUrl(ifrcApiUrl, `Application/${applicationId}/Translation/`);

const headers: RequestInit['headers'] = {
'Accept': 'application/json',
'X-API-KEY': ifrcApiKey,
}

const promise = fetch(
endpoint,
{
method: 'GET',
headers,
}
);

return promise;
}

async function postTranslation(ifrcApiUrl: string, ifrcApiKey: string) {
const endpoint = resolveUrl(ifrcApiUrl, `Application/${applicationId}/Translation`);

const headers: RequestInit['headers'] = {
// 'Accept': 'application/json',
'X-API-KEY': ifrcApiKey,
'Content-Type': 'application/json',
}

const promise = fetch(
endpoint,
{
method: 'POST',
headers,
body: JSON.stringify({
page: 'home',
keyName: 'pageTitle',
value: 'IFRC GO | Home',
languageCode: 'en',
}),
}
);

return promise;
}
*/

async function fullAppImport(importFilePath: string, ifrcApiUrl: string, ifrcApiKey: string) {
const endpoint = resolveUrl(ifrcApiUrl, `Application/${applicationId}/Translation/fullappimport`);
const translationFile = readFileSync(importFilePath);
const uint8FileData = new Uint8Array(translationFile);
const blob = new Blob([uint8FileData], {
type: 'application/vnd.openxmlformats-officedocument.spreadsheetml.sheet',
});

const formData = new FormData();
formData.append('files', blob, 'translations.xlsx');

const headers: RequestInit['headers'] = {
'Accept': 'application/json',
'X-API-KEY': ifrcApiKey,
}

const promise = fetch(
endpoint,
{
method: 'POST',
headers,
body: formData,
}
);

return promise;
}

async function pushStringsFromExcelToIfrc(importFilePath: string, apiUrl: string, apiKey: string) {
const response = await fullAppImport(importFilePath, apiUrl, apiKey);

try {
const responseJson = await response.json();
console.info(responseJson);
} catch(e) {
console.info(e);
const responseText = await response.text();
console.info(responseText);
}
}

export default pushStringsFromExcelToIfrc;
4 changes: 2 additions & 2 deletions app/scripts/translatte/commands/syncEnStrings.ts
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
import { isDefined, isFalsyString } from "@togglecorp/fujs";
import { fetchServerState, postLanguageStrings, writeFilePromisify } from "../utils";

async function syncEnStrings(sourceApiUrl: string, desinationApiUrl: string, authToken: string) {
async function syncEnStrings(sourceApiUrl: string, destinationApiUrl: string, authToken: string) {
const serverStrings = await fetchServerState(sourceApiUrl, authToken);
const enStrings = serverStrings.filter((string) => string.language === 'en');

Expand All @@ -23,7 +23,7 @@ async function syncEnStrings(sourceApiUrl: string, desinationApiUrl: string, aut
const result = await postLanguageStrings(
'en',
actions,
desinationApiUrl,
destinationApiUrl,
authToken,
)

Expand Down
34 changes: 33 additions & 1 deletion app/scripts/translatte/main.ts
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,7 @@ import exportServerStringsToExcel from './commands/exportServerStringsToExcel';
import clearServerStrings from './commands/clearServerStrings';
import pushStringsDref from './commands/pushStringsDref';
import syncEnStrings from './commands/syncEnStrings';
import pushStringsFromExcelToIfrc from './commands/pushStringsFromExcelToIfrc';

const currentDir = cwd();

Expand Down Expand Up @@ -256,6 +257,37 @@ yargs(hideBin(process.argv))
);
},
)
.command(
'push-strings-from-excel-to-ifrc <IMPORT_FILE_PATH>',
'Import migration from excel file and push it to server',
(yargs) => {
yargs.positional('IMPORT_FILE_PATH', {
type: 'string',
describe: 'Find the import file on IMPORT_FILE_PATH',
});
yargs.options({
'api-key': {
type: 'string',
describe: 'API key to access the API server',
require: true,
},
'api-url': {
type: 'string',
describe: 'URL for the API server',
require: true,
}
});
},
async (argv) => {
const importFilePath = (argv.IMPORT_FILE_PATH as string);

await pushStringsFromExcelToIfrc(
importFilePath,
argv.apiUrl as string,
argv.apiKey as string,
);
},
)
.command(
'push-strings-dref <IMPORT_FILE_PATH>',
'IMPORTANT!!! Temporary command, do not use!',
Expand Down Expand Up @@ -283,7 +315,7 @@ yargs(hideBin(process.argv))
await pushStringsDref(
importFilePath,
argv.apiUrl as string,
argv.authToken as string,
argv.apiKey as string,
);
},
)
Expand Down
4 changes: 4 additions & 0 deletions app/src/config.ts
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,8 @@ const {
APP_MAPBOX_ACCESS_TOKEN,
APP_TINY_API_KEY,
APP_RISK_API_ENDPOINT,
APP_TRANSLATION_API_ENDPOINT,
APP_TRANSLATION_API_KEY,
APP_SDT_URL,
APP_POWER_BI_REPORT_ID_1,
APP_SENTRY_DSN,
Expand All @@ -30,9 +32,11 @@ export const api = APP_API_ENDPOINT;
export const adminUrl = APP_ADMIN_URL ?? `${api}admin/`;
export const mbtoken = APP_MAPBOX_ACCESS_TOKEN;
export const riskApi = APP_RISK_API_ENDPOINT;
export const translationApi = APP_TRANSLATION_API_ENDPOINT;
export const sdtUrl = APP_SDT_URL;
export const powerBiReportId1 = APP_POWER_BI_REPORT_ID_1;

export const translationApiKey = APP_TRANSLATION_API_KEY;
export const tinyApiKey = APP_TINY_API_KEY;
export const sentryAppDsn = APP_SENTRY_DSN;
export const sentryTracesSampleRate = APP_SENTRY_TRACES_SAMPLE_RATE;
Expand Down
12 changes: 6 additions & 6 deletions app/src/utils/resolveUrl.ts
Original file line number Diff line number Diff line change
@@ -1,9 +1,9 @@
// eslint-disable-next-line import/prefer-default-export
export function resolveUrl(from: string, to: string) {
const resolvedUrl = new URL(to, new URL(from, 'resolve://'));
if (resolvedUrl.protocol === 'resolve:') {
const { pathname, search, hash } = resolvedUrl;
return pathname + search + hash;
}
export function resolveUrl(base: string, endpoint: string) {
const baseSafe = base.endsWith('/') ? base : `${base}/`;
const endpointSafe = endpoint.startsWith('.') ? endpoint : `.${endpoint}`;

const resolvedUrl = new URL(endpointSafe, baseSafe);

return resolvedUrl.toString();
}
44 changes: 39 additions & 5 deletions app/src/utils/restRequest/go.ts
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,8 @@ import { type ContextInterface } from '@togglecorp/toggle-request';
import {
api,
riskApi,
translationApi,
translationApiKey,
} from '#config';
import { type UserAuth } from '#contexts/user';
import {
Expand Down Expand Up @@ -39,8 +41,10 @@ export interface TransformedError {
debugMessage: string;
}

type ApiType = 'go' | 'risk' | 'translation';

export interface AdditionalOptions {
apiType?: 'go' | 'risk';
apiType?: ApiType;
formData?: boolean;
isCsvRequest?: boolean;
enforceEnglishForQuery?: boolean;
Expand Down Expand Up @@ -111,6 +115,18 @@ type GoContextInterface = ContextInterface<
AdditionalOptions
>;

function getEndPoint(apiType: ApiType | undefined) {
if (apiType === 'risk') {
return riskApi;
}

if (apiType === 'translation') {
return translationApi;
}

return api;
}

export const processGoUrls: GoContextInterface['transformUrl'] = (url, _, additionalOptions) => {
if (isFalsyString(url)) {
return '';
Expand All @@ -123,10 +139,12 @@ export const processGoUrls: GoContextInterface['transformUrl'] = (url, _, additi

const { apiType } = additionalOptions;

return resolveUrl(
apiType === 'risk' ? riskApi : api,
url,
const resolvedUrl = resolveUrl(
getEndPoint(apiType),
`.${url}`,
);

return resolvedUrl;
};

type Literal = string | number | boolean | File;
Expand Down Expand Up @@ -164,6 +182,7 @@ export const processGoOptions: GoContextInterface['transformOptions'] = (
} = requestOptions;

const {
apiType,
formData,
isCsvRequest,
isExcelRequest,
Expand All @@ -176,10 +195,15 @@ export const processGoOptions: GoContextInterface['transformOptions'] = (
const user = getFromStorage<UserAuth | undefined>(KEY_USER_STORAGE);
const token = user?.token;

// FIXME: only inject on go apis
const defaultHeaders: HeadersInit = {
Authorization: token ? `Token ${token}` : '',
};

if (apiType === 'translation') {
defaultHeaders['x-api-key'] = translationApiKey;
}

if (method === 'GET') {
// Query
defaultHeaders['Accept-Language'] = enforceEnglishForQuery ? 'en' : currentLanguage;
Expand Down Expand Up @@ -239,7 +263,17 @@ const isSuccessfulStatus = (status: number): boolean => status >= 200 && status

const isContentTypeExcel = (res: Response): boolean => res.headers.get('content-type') === CONTENT_TYPE_EXCEL;

const isContentTypeJson = (res: Response): boolean => res.headers.get('content-type') === CONTENT_TYPE_JSON;
const isContentTypeJson = (res: Response): boolean => {
const contentTypeHeaders = res.headers.get('content-type');

if (isNotDefined(contentTypeHeaders)) {
return false;
}

const mediaTypes = contentTypeHeaders.split('; ');

return mediaTypes[0]?.toLowerCase() === CONTENT_TYPE_JSON;
};

const isLoginRedirect = (url: string): boolean => new URL(url).pathname.includes('login');

Expand Down
Loading
Loading