0
Fork 0
mirror of https://github.com/logto-io/logto.git synced 2024-12-16 20:26:19 -05:00

refactor: remove demo app db entity (#3165)

This commit is contained in:
Gao Sun 2023-02-21 11:45:06 +08:00 committed by GitHub
parent dfa17ba555
commit e3f88f5250
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
12 changed files with 104 additions and 92 deletions

View file

@ -4,7 +4,6 @@ import path from 'path';
import { import {
defaultSignInExperience, defaultSignInExperience,
createDefaultAdminConsoleConfig, createDefaultAdminConsoleConfig,
createDemoAppApplication,
defaultTenantId, defaultTenantId,
adminTenantId, adminTenantId,
defaultManagementApi, defaultManagementApi,
@ -125,8 +124,6 @@ export const seedTables = async (
await Promise.all([ await Promise.all([
connection.query(insertInto(createDefaultAdminConsoleConfig(defaultTenantId), 'logto_configs')), connection.query(insertInto(createDefaultAdminConsoleConfig(defaultTenantId), 'logto_configs')),
connection.query(insertInto(defaultSignInExperience, 'sign_in_experiences')), connection.query(insertInto(defaultSignInExperience, 'sign_in_experiences')),
// TODO: @gao remove demo app
connection.query(insertInto(createDemoAppApplication(defaultTenantId), 'applications')),
updateDatabaseTimestamp(connection, latestTimestamp), updateDatabaseTimestamp(connection, latestTimestamp),
]); ]);
}; };

View file

@ -1,9 +1,7 @@
import type { AdminConsoleKey } from '@logto/phrases'; import type { AdminConsoleKey } from '@logto/phrases';
import type { Application } from '@logto/schemas'; import { AppearanceMode } from '@logto/schemas';
import { AppearanceMode, demoAppApplicationId } from '@logto/schemas';
import { useContext, useMemo } from 'react'; import { useContext, useMemo } from 'react';
import { useNavigate } from 'react-router-dom'; import { useNavigate } from 'react-router-dom';
import useSWR from 'swr';
import CheckDemoDark from '@/assets/images/check-demo-dark.svg'; import CheckDemoDark from '@/assets/images/check-demo-dark.svg';
import CheckDemo from '@/assets/images/check-demo.svg'; import CheckDemo from '@/assets/images/check-demo.svg';
@ -19,7 +17,6 @@ import SocialDark from '@/assets/images/social-dark.svg';
import Social from '@/assets/images/social.svg'; import Social from '@/assets/images/social.svg';
import { ConnectorsTabs } from '@/consts/page-tabs'; import { ConnectorsTabs } from '@/consts/page-tabs';
import { AppEndpointsContext } from '@/containers/AppEndpointsProvider'; import { AppEndpointsContext } from '@/containers/AppEndpointsProvider';
import { RequestError } from '@/hooks/use-api';
import useConfigs from '@/hooks/use-configs'; import useConfigs from '@/hooks/use-configs';
import useDocumentationUrl from '@/hooks/use-documentation-url'; import useDocumentationUrl from '@/hooks/use-documentation-url';
import { useTheme } from '@/hooks/use-theme'; import { useTheme } from '@/hooks/use-theme';
@ -41,21 +38,7 @@ const useGetStartedMetadata = () => {
const { userEndpoint } = useContext(AppEndpointsContext); const { userEndpoint } = useContext(AppEndpointsContext);
const theme = useTheme(); const theme = useTheme();
const isLightMode = theme === AppearanceMode.LightMode; const isLightMode = theme === AppearanceMode.LightMode;
const { data: demoApp, error } = useSWR<Application, RequestError>(
`api/applications/${demoAppApplicationId}`,
{
shouldRetryOnError: (error: unknown) => {
if (error instanceof RequestError) {
return error.status !== 404;
}
return true;
},
}
);
const navigate = useNavigate(); const navigate = useNavigate();
const isLoadingDemoApp = !demoApp && !error;
const hideDemo = error?.status === 404;
const data = useMemo(() => { const data = useMemo(() => {
const metadataItems: GetStartedMetadata[] = [ const metadataItems: GetStartedMetadata[] = [
@ -66,7 +49,6 @@ const useGetStartedMetadata = () => {
icon: isLightMode ? CheckDemo : CheckDemoDark, icon: isLightMode ? CheckDemo : CheckDemoDark,
buttonText: 'general.check_out', buttonText: 'general.check_out',
isComplete: configs?.demoChecked, isComplete: configs?.demoChecked,
isHidden: hideDemo,
onClick: async () => { onClick: async () => {
void updateConfigs({ demoChecked: true }); void updateConfigs({ demoChecked: true });
window.open(new URL('/demo-app', userEndpoint), '_blank'); window.open(new URL('/demo-app', userEndpoint), '_blank');
@ -142,7 +124,6 @@ const useGetStartedMetadata = () => {
configs?.passwordlessConfigured, configs?.passwordlessConfigured,
configs?.socialSignInConfigured, configs?.socialSignInConfigured,
configs?.furtherReadingsChecked, configs?.furtherReadingsChecked,
hideDemo,
updateConfigs, updateConfigs,
userEndpoint, userEndpoint,
navigate, navigate,
@ -153,7 +134,7 @@ const useGetStartedMetadata = () => {
data, data,
completedCount: data.filter(({ isComplete }) => isComplete).length, completedCount: data.filter(({ isComplete }) => isComplete).length,
totalCount: data.length, totalCount: data.length,
isLoading: isLoadingDemoApp, isLoading: false,
}; };
}; };

View file

@ -1,5 +1,6 @@
import { adminTenantId } from '@logto/schemas'; import { adminTenantId } from '@logto/schemas';
import { trySafe } from '@silverhand/essentials'; import type { Optional } from '@silverhand/essentials';
import { deduplicate, trySafe } from '@silverhand/essentials';
import type GlobalValues from './GlobalValues.js'; import type GlobalValues from './GlobalValues.js';
@ -23,3 +24,29 @@ export const getTenantEndpoint = (
return tenantUrl; return tenantUrl;
}; };
export const getTenantLocalhost = (
id: string,
{ urlSet, adminUrlSet, isDomainBasedMultiTenancy }: GlobalValues
): Optional<URL> => {
const adminUrl = trySafe(() => adminUrlSet.localhostUrl);
if (adminUrl && id === adminTenantId) {
return adminUrl;
}
if (!isDomainBasedMultiTenancy) {
return trySafe(() => urlSet.localhostUrl);
}
};
export const getTenantUrls = (id: string, globalValues: GlobalValues): URL[] => {
const endpoint = getTenantEndpoint(id, globalValues);
const localhost = getTenantLocalhost(id, globalValues);
return deduplicate(
[endpoint.toString(), localhost?.toString()].filter(
(value): value is string => typeof value === 'string'
)
).map((element) => new URL(element));
};

View file

@ -1,22 +0,0 @@
import { demoAppApplicationId } from '@logto/schemas';
import type { MiddlewareType } from 'koa';
import type Queries from '#src/tenants/Queries.js';
export default function koaCheckDemoApp<StateT, ContextT, ResponseBodyT>(
queries: Queries
): MiddlewareType<StateT, ContextT, ResponseBodyT> {
const { findApplicationById } = queries.applications;
return async (ctx, next) => {
try {
await findApplicationById(demoAppApplicationId);
await next();
return;
} catch {
ctx.throw(404);
}
};
}

View file

@ -1,13 +1,13 @@
import type { CreateApplication, OidcClientMetadata } from '@logto/schemas'; import type { CreateApplication } from '@logto/schemas';
import { ApplicationType, adminConsoleApplicationId, demoAppApplicationId } from '@logto/schemas'; import { ApplicationType, adminConsoleApplicationId, demoAppApplicationId } from '@logto/schemas';
import { tryThat } from '@logto/shared'; import { tryThat } from '@logto/shared';
import { deduplicate } from '@silverhand/essentials';
import { addSeconds } from 'date-fns'; import { addSeconds } from 'date-fns';
import type { AdapterFactory, AllClientMetadata } from 'oidc-provider'; import type { AdapterFactory, AllClientMetadata } from 'oidc-provider';
import { errors } from 'oidc-provider'; import { errors } from 'oidc-provider';
import snakecaseKeys from 'snakecase-keys'; import snakecaseKeys from 'snakecase-keys';
import { EnvSet, UserApps } from '#src/env-set/index.js'; import { EnvSet } from '#src/env-set/index.js';
import { getTenantUrls } from '#src/env-set/utils.js';
import type Queries from '#src/tenants/Queries.js'; import type Queries from '#src/tenants/Queries.js';
import { appendPath } from '#src/utils/url.js'; import { appendPath } from '#src/utils/url.js';
@ -28,18 +28,18 @@ const buildAdminConsoleClientMetadata = (envSet: EnvSet): AllClientMetadata => {
}; };
}; };
const buildDemoAppUris = ( const buildDemoAppClientMetadata = (envSet: EnvSet): AllClientMetadata => {
oidcClientMetadata: OidcClientMetadata const urls = getTenantUrls(envSet.tenantId, EnvSet.values).map((url) =>
): Pick<OidcClientMetadata, 'redirectUris' | 'postLogoutRedirectUris'> => { appendPath(url, '/demo-app').toString()
const { urlSet } = EnvSet.values; );
const urls = urlSet.deduplicated().map((url) => appendPath(url, UserApps.DemoApp).toString());
const data = { return {
redirectUris: deduplicate([...urls, ...oidcClientMetadata.redirectUris]), ...getConstantClientMetadata(envSet, ApplicationType.SPA),
postLogoutRedirectUris: deduplicate([...urls, ...oidcClientMetadata.postLogoutRedirectUris]), client_id: demoAppApplicationId,
client_name: 'Demo App',
redirect_uris: urls,
post_logout_redirect_uris: urls,
}; };
return data;
}; };
export default function postgresAdapter( export default function postgresAdapter(
@ -76,8 +76,6 @@ export default function postgresAdapter(
client_name, client_name,
...getConstantClientMetadata(envSet, type), ...getConstantClientMetadata(envSet, type),
...snakecaseKeys(oidcClientMetadata), ...snakecaseKeys(oidcClientMetadata),
...(client_id === demoAppApplicationId &&
snakecaseKeys(buildDemoAppUris(oidcClientMetadata))),
// `node-oidc-provider` won't camelCase custom parameter keys, so we need to keep the keys camelCased // `node-oidc-provider` won't camelCase custom parameter keys, so we need to keep the keys camelCased
...customClientMetadata, ...customClientMetadata,
}); });
@ -90,6 +88,10 @@ export default function postgresAdapter(
return buildAdminConsoleClientMetadata(envSet); return buildAdminConsoleClientMetadata(envSet);
} }
if (id === demoAppApplicationId) {
return buildDemoAppClientMetadata(envSet);
}
return transpileClient( return transpileClient(
await tryThat(findApplicationById(id), new errors.InvalidClient(`invalid client ${id}`)) await tryThat(findApplicationById(id), new errors.InvalidClient(`invalid client ${id}`))
); );

View file

@ -25,7 +25,6 @@ const middlewareList = Object.freeze(
'oidc-error-handler', 'oidc-error-handler',
'slonik-error-handler', 'slonik-error-handler',
'spa-proxy', 'spa-proxy',
'check-demo-app',
'console-redirect-proxy', 'console-redirect-proxy',
].map((name) => [name, buildMockMiddleware(name)] as const) ].map((name) => [name, buildMockMiddleware(name)] as const)
); );

View file

@ -7,7 +7,6 @@ import mount from 'koa-mount';
import type Provider from 'oidc-provider'; import type Provider from 'oidc-provider';
import { AdminApps, EnvSet, UserApps } from '#src/env-set/index.js'; import { AdminApps, EnvSet, UserApps } from '#src/env-set/index.js';
import koaCheckDemoApp from '#src/middleware/koa-check-demo-app.js';
import koaConnectorErrorHandler from '#src/middleware/koa-connector-error-handler.js'; import koaConnectorErrorHandler from '#src/middleware/koa-connector-error-handler.js';
import koaConsoleRedirectProxy from '#src/middleware/koa-console-redirect-proxy.js'; import koaConsoleRedirectProxy from '#src/middleware/koa-console-redirect-proxy.js';
import koaErrorHandler from '#src/middleware/koa-error-handler.js'; import koaErrorHandler from '#src/middleware/koa-error-handler.js';
@ -97,10 +96,7 @@ export default class Tenant implements TenantContext {
app.use( app.use(
mount( mount(
'/' + UserApps.DemoApp, '/' + UserApps.DemoApp,
compose([ koaSpaProxy(mountedApps, UserApps.DemoApp, 5003, UserApps.DemoApp)
koaCheckDemoApp(this.queries),
koaSpaProxy(mountedApps, UserApps.DemoApp, 5003, UserApps.DemoApp),
])
) )
); );
} }

View file

@ -1,4 +1,4 @@
import { ApplicationType, demoAppApplicationId } from '@logto/schemas'; import { ApplicationType } from '@logto/schemas';
import { HTTPError } from 'got'; import { HTTPError } from 'got';
import { import {
@ -9,12 +9,6 @@ import {
} from '#src/api/index.js'; } from '#src/api/index.js';
describe('admin console application', () => { describe('admin console application', () => {
it('should get demo app details successfully', async () => {
const demoApp = await getApplication(demoAppApplicationId);
expect(demoApp.id).toBe(demoAppApplicationId);
});
it('should create application successfully', async () => { it('should create application successfully', async () => {
const applicationName = 'test-create-app'; const applicationName = 'test-create-app';
const applicationType = ApplicationType.SPA; const applicationType = ApplicationType.SPA;

View file

@ -0,0 +1,52 @@
import { generateStandardId } from '@logto/core-kit';
import chalk from 'chalk';
import inquirer from 'inquirer';
import { sql } from 'slonik';
import type { AlterationScript } from '../lib/types/alteration.js';
const defaultTenantId = 'default';
const alteration: AlterationScript = {
up: async (pool) => {
const isCi = process.env.CI;
const { confirm } = await inquirer.prompt<{ confirm: boolean }>({
type: 'confirm',
name: 'confirm',
message: String(
chalk.bold(chalk.yellow('***CAUTION***')) +
'\n' +
'The application `demo-app` will be removed from your database.\n' +
'Usually this is harmless since the demo app will be still functional with predefined data.\n' +
'Are you sure to continue?'
),
default: false,
when: !isCi,
});
if (!isCi && !confirm) {
throw new Error('User cancelled alteration.');
}
await pool.query(sql`
delete from applications where id = 'demo-app';
`);
},
down: async (pool) => {
await pool.query(sql`
insert into applications
(tenant_id, id, secret, name, description, type, oidc_client_metadata)
values (
'default',
'demo-app',
${generateStandardId()},
'Demo App',
'Logto demo app.',
'SPA',
'{ "redirectUris": [], "postLogoutRedirectUris": [] }'::jsonb
)
`);
},
};
export default alteration;

View file

@ -48,6 +48,7 @@
"@types/node": "^18.11.18", "@types/node": "^18.11.18",
"@types/pluralize": "^0.0.29", "@types/pluralize": "^0.0.29",
"camelcase": "^7.0.0", "camelcase": "^7.0.0",
"chalk": "^5.0.0",
"eslint": "^8.34.0", "eslint": "^8.34.0",
"jest": "^29.1.2", "jest": "^29.1.2",
"lint-staged": "^13.0.0", "lint-staged": "^13.0.0",

View file

@ -1,8 +1,3 @@
import { generateStandardId } from '@logto/core-kit';
import type { CreateApplication } from '../db-entries/index.js';
import { ApplicationType } from '../db-entries/index.js';
/** /**
* The fixed application ID for Admin Console. * The fixed application ID for Admin Console.
* *
@ -11,14 +6,3 @@ import { ApplicationType } from '../db-entries/index.js';
export const adminConsoleApplicationId = 'admin-console'; export const adminConsoleApplicationId = 'admin-console';
export const demoAppApplicationId = 'demo-app'; export const demoAppApplicationId = 'demo-app';
/** @deprecated Demo app database entity will be removed soon. */
export const createDemoAppApplication = (forTenantId: string): Readonly<CreateApplication> => ({
tenantId: forTenantId,
id: demoAppApplicationId,
secret: generateStandardId(),
name: 'Demo App',
description: 'Logto demo app.',
type: ApplicationType.SPA,
oidcClientMetadata: { redirectUris: [], postLogoutRedirectUris: [] },
});

View file

@ -653,6 +653,7 @@ importers:
'@types/pluralize': ^0.0.29 '@types/pluralize': ^0.0.29
'@withtyped/server': ^0.8.0 '@withtyped/server': ^0.8.0
camelcase: ^7.0.0 camelcase: ^7.0.0
chalk: ^5.0.0
eslint: ^8.34.0 eslint: ^8.34.0
jest: ^29.1.2 jest: ^29.1.2
lint-staged: ^13.0.0 lint-staged: ^13.0.0
@ -680,6 +681,7 @@ importers:
'@types/node': 18.11.18 '@types/node': 18.11.18
'@types/pluralize': 0.0.29 '@types/pluralize': 0.0.29
camelcase: 7.0.0 camelcase: 7.0.0
chalk: 5.1.2
eslint: 8.34.0 eslint: 8.34.0
jest: 29.1.2_@types+node@18.11.18 jest: 29.1.2_@types+node@18.11.18
lint-staged: 13.0.0 lint-staged: 13.0.0
@ -5283,7 +5285,6 @@ packages:
/chalk/5.1.2: /chalk/5.1.2:
resolution: {integrity: sha512-E5CkT4jWURs1Vy5qGJye+XwCkNj7Od3Af7CP6SujMetSMkLs8Do2RWJK5yx1wamHV/op8Rz+9rltjaTQWDnEFQ==} resolution: {integrity: sha512-E5CkT4jWURs1Vy5qGJye+XwCkNj7Od3Af7CP6SujMetSMkLs8Do2RWJK5yx1wamHV/op8Rz+9rltjaTQWDnEFQ==}
engines: {node: ^12.17.0 || ^14.13 || >=16.0.0} engines: {node: ^12.17.0 || ^14.13 || >=16.0.0}
dev: false
/char-regex/1.0.2: /char-regex/1.0.2:
resolution: {integrity: sha512-kWWXztvZ5SBQV+eRgKFeh8q5sLuZY2+8WUIzlxWVTg+oGwY14qylx1KbKzHd8P6ZYkAg0xyIDU9JMHhyJMZ1jw==} resolution: {integrity: sha512-kWWXztvZ5SBQV+eRgKFeh8q5sLuZY2+8WUIzlxWVTg+oGwY14qylx1KbKzHd8P6ZYkAg0xyIDU9JMHhyJMZ1jw==}