mirror of
https://github.com/logto-io/logto.git
synced 2024-12-30 20:33:54 -05:00
feat(core,schemas): add phrases schema and GET /custom-phrases/:languageKey route (#1905)
This commit is contained in:
parent
03fe4beb43
commit
7242aa8c2b
6 changed files with 121 additions and 0 deletions
14
packages/core/src/queries/custom-phrase.ts
Normal file
14
packages/core/src/queries/custom-phrase.ts
Normal file
|
@ -0,0 +1,14 @@
|
||||||
|
import { CustomPhrase, CustomPhrases } from '@logto/schemas';
|
||||||
|
import { sql } from 'slonik';
|
||||||
|
|
||||||
|
import { convertToIdentifiers } from '@/database/utils';
|
||||||
|
import envSet from '@/env-set';
|
||||||
|
|
||||||
|
const { table, fields } = convertToIdentifiers(CustomPhrases);
|
||||||
|
|
||||||
|
export const findCustomPhraseByLanguageKey = async (languageKey: string): Promise<CustomPhrase> =>
|
||||||
|
envSet.pool.one<CustomPhrase>(sql`
|
||||||
|
select ${sql.join(Object.values(fields), sql`, `)}
|
||||||
|
from ${table}
|
||||||
|
where ${fields.languageKey} = ${languageKey}
|
||||||
|
`);
|
62
packages/core/src/routes/custom-phrase.test.ts
Normal file
62
packages/core/src/routes/custom-phrase.test.ts
Normal file
|
@ -0,0 +1,62 @@
|
||||||
|
import { CustomPhrase } from '@logto/schemas';
|
||||||
|
|
||||||
|
import RequestError from '@/errors/RequestError';
|
||||||
|
import phraseRoutes from '@/routes/custom-phrase';
|
||||||
|
import { createRequester } from '@/utils/test-utils';
|
||||||
|
|
||||||
|
const mockLanguageKey = 'en-US';
|
||||||
|
|
||||||
|
const mockCustomPhrases: Record<string, CustomPhrase> = {
|
||||||
|
[mockLanguageKey]: {
|
||||||
|
languageKey: mockLanguageKey,
|
||||||
|
translation: {
|
||||||
|
input: {
|
||||||
|
username: 'Username',
|
||||||
|
password: 'Password',
|
||||||
|
email: 'Email',
|
||||||
|
phone_number: 'Phone number',
|
||||||
|
confirm_password: 'Confirm password',
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
};
|
||||||
|
|
||||||
|
const findCustomPhraseByLanguageKey = jest.fn(async (languageKey: string) => {
|
||||||
|
const mockCustomPhrase = mockCustomPhrases[languageKey];
|
||||||
|
|
||||||
|
if (!mockCustomPhrase) {
|
||||||
|
throw new RequestError({ code: 'entity.not_found', status: 404 });
|
||||||
|
}
|
||||||
|
|
||||||
|
return mockCustomPhrase;
|
||||||
|
});
|
||||||
|
|
||||||
|
jest.mock('@/queries/custom-phrase', () => ({
|
||||||
|
findCustomPhraseByLanguageKey: async (key: string) => findCustomPhraseByLanguageKey(key),
|
||||||
|
}));
|
||||||
|
|
||||||
|
describe('customPhraseRoutes', () => {
|
||||||
|
const phraseRequest = createRequester({ authedRoutes: phraseRoutes });
|
||||||
|
|
||||||
|
afterEach(() => {
|
||||||
|
jest.clearAllMocks();
|
||||||
|
});
|
||||||
|
|
||||||
|
describe('GET /custom-phrases/:languageKey', () => {
|
||||||
|
it('should call findCustomPhraseByLanguageKey once', async () => {
|
||||||
|
await phraseRequest.get(`/custom-phrases/${mockLanguageKey}`);
|
||||||
|
expect(findCustomPhraseByLanguageKey).toBeCalledTimes(1);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should return the specified custom phrase existing in the database', async () => {
|
||||||
|
const response = await phraseRequest.get(`/custom-phrases/${mockLanguageKey}`);
|
||||||
|
expect(response.status).toEqual(200);
|
||||||
|
expect(response.body).toEqual(mockCustomPhrases[mockLanguageKey]);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should return 404 status code when there is no specified custom phrase in the database', async () => {
|
||||||
|
const response = await phraseRequest.get('/custom-phrases/en-UK');
|
||||||
|
expect(response.status).toEqual(404);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
});
|
26
packages/core/src/routes/custom-phrase.ts
Normal file
26
packages/core/src/routes/custom-phrase.ts
Normal file
|
@ -0,0 +1,26 @@
|
||||||
|
import { CustomPhrases } from '@logto/schemas';
|
||||||
|
|
||||||
|
import koaGuard from '@/middleware/koa-guard';
|
||||||
|
import { findCustomPhraseByLanguageKey } from '@/queries/custom-phrase';
|
||||||
|
|
||||||
|
import { AuthedRouter } from './types';
|
||||||
|
|
||||||
|
export default function phraseRoutes<T extends AuthedRouter>(router: T) {
|
||||||
|
router.get(
|
||||||
|
'/custom-phrases/:languageKey',
|
||||||
|
koaGuard({
|
||||||
|
// Next up: guard languageKey by enum LanguageKey (that will be provided by @sijie later.)
|
||||||
|
params: CustomPhrases.createGuard.pick({ languageKey: true }),
|
||||||
|
response: CustomPhrases.guard,
|
||||||
|
}),
|
||||||
|
async (ctx, next) => {
|
||||||
|
const {
|
||||||
|
params: { languageKey },
|
||||||
|
} = ctx.guard;
|
||||||
|
|
||||||
|
ctx.body = await findCustomPhraseByLanguageKey(languageKey);
|
||||||
|
|
||||||
|
return next();
|
||||||
|
}
|
||||||
|
);
|
||||||
|
}
|
|
@ -10,6 +10,7 @@ import adminUserRoutes from '@/routes/admin-user';
|
||||||
import applicationRoutes from '@/routes/application';
|
import applicationRoutes from '@/routes/application';
|
||||||
import authnRoutes from '@/routes/authn';
|
import authnRoutes from '@/routes/authn';
|
||||||
import connectorRoutes from '@/routes/connector';
|
import connectorRoutes from '@/routes/connector';
|
||||||
|
import customPhraseRoutes from '@/routes/custom-phrase';
|
||||||
import dashboardRoutes from '@/routes/dashboard';
|
import dashboardRoutes from '@/routes/dashboard';
|
||||||
import logRoutes from '@/routes/log';
|
import logRoutes from '@/routes/log';
|
||||||
import resourceRoutes from '@/routes/resource';
|
import resourceRoutes from '@/routes/resource';
|
||||||
|
@ -39,6 +40,7 @@ const createRouters = (provider: Provider) => {
|
||||||
logRoutes(managementRouter);
|
logRoutes(managementRouter);
|
||||||
roleRoutes(managementRouter);
|
roleRoutes(managementRouter);
|
||||||
dashboardRoutes(managementRouter);
|
dashboardRoutes(managementRouter);
|
||||||
|
customPhraseRoutes(managementRouter);
|
||||||
|
|
||||||
const anonymousRouter: AnonymousRouter = new Router();
|
const anonymousRouter: AnonymousRouter = new Router();
|
||||||
wellKnownRoutes(anonymousRouter, provider);
|
wellKnownRoutes(anonymousRouter, provider);
|
||||||
|
|
|
@ -155,3 +155,15 @@ export const adminConsoleConfigGuard = z.object({
|
||||||
});
|
});
|
||||||
|
|
||||||
export type AdminConsoleConfig = z.infer<typeof adminConsoleConfigGuard>;
|
export type AdminConsoleConfig = z.infer<typeof adminConsoleConfigGuard>;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Phrases
|
||||||
|
*/
|
||||||
|
|
||||||
|
export type Translation = {
|
||||||
|
[key: string]: string | Translation;
|
||||||
|
};
|
||||||
|
|
||||||
|
export const translationGuard: z.ZodType<Translation> = z.lazy(() =>
|
||||||
|
z.record(z.string().or(translationGuard))
|
||||||
|
);
|
||||||
|
|
5
packages/schemas/tables/custom_phrases.sql
Normal file
5
packages/schemas/tables/custom_phrases.sql
Normal file
|
@ -0,0 +1,5 @@
|
||||||
|
create table custom_phrases (
|
||||||
|
language_key varchar(16) not null,
|
||||||
|
translation jsonb /* @use Translation */ not null,
|
||||||
|
primary key(language_key)
|
||||||
|
);
|
Loading…
Reference in a new issue