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 authnRoutes from '@/routes/authn';
|
||||
import connectorRoutes from '@/routes/connector';
|
||||
import customPhraseRoutes from '@/routes/custom-phrase';
|
||||
import dashboardRoutes from '@/routes/dashboard';
|
||||
import logRoutes from '@/routes/log';
|
||||
import resourceRoutes from '@/routes/resource';
|
||||
|
@ -39,6 +40,7 @@ const createRouters = (provider: Provider) => {
|
|||
logRoutes(managementRouter);
|
||||
roleRoutes(managementRouter);
|
||||
dashboardRoutes(managementRouter);
|
||||
customPhraseRoutes(managementRouter);
|
||||
|
||||
const anonymousRouter: AnonymousRouter = new Router();
|
||||
wellKnownRoutes(anonymousRouter, provider);
|
||||
|
|
|
@ -155,3 +155,15 @@ export const adminConsoleConfigGuard = z.object({
|
|||
});
|
||||
|
||||
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