Use slug everywhere instead of app type

This commit is contained in:
Hariom Balhara 2022-06-01 15:15:07 +05:30
parent c563415795
commit d563343669
17 changed files with 112 additions and 45 deletions

View File

@ -21,6 +21,7 @@ import Badge from "@components/ui/Badge";
export default function App({
name,
type,
slug,
logo,
body,
categories,
@ -36,6 +37,7 @@ export default function App({
privacy,
}: {
name: string;
slug: string;
type: AppType["type"];
isGlobal?: AppType["isGlobal"];
logo: string;
@ -60,6 +62,7 @@ export default function App({
useGrouping: false,
}).format(price);
const [installedApp, setInstalledApp] = useState(false);
useEffect(() => {
async function getInstalledApp(appCredentialType: string) {
const queryParam = new URLSearchParams();
@ -80,8 +83,9 @@ export default function App({
}
}
}
getInstalledApp(type);
}, [type]);
getInstalledApp(slug);
}, [slug]);
return (
<>
<Shell large isPublic>

View File

@ -7,8 +7,9 @@ import { getSession } from "@lib/auth";
export default async function handler(req: NextApiRequest, res: NextApiResponse) {
req.session = await getSession({ req });
if (req.method === "GET" && req.session && req.session.user.id && req.query) {
const { "app-credential-type": appCredentialType } = req.query;
if (!appCredentialType && Array.isArray(appCredentialType)) {
const { "app-credential-type": slug } = req.query;
if (!slug && Array.isArray(slug)) {
return res.status(400);
}
@ -16,7 +17,7 @@ export default async function handler(req: NextApiRequest, res: NextApiResponse)
try {
const installedApp = await prisma.credential.findFirst({
where: {
type: appCredentialType as string,
appId: slug as string,
userId: userId,
},
});

View File

@ -3,6 +3,27 @@ import { NextApiHandler, NextApiRequest, NextApiResponse } from "next";
import { getSession } from "@lib/auth";
import { HttpError } from "@lib/core/http/error";
function getSlugFromLegacy(legacySlug) {
const oldTypes = ["video", "other", "calendar", "web3", "payment", "messaging"];
// There can be two types of legacy slug
// - zoom_video
// - zoomvideo
// Transform `zoom_video` to `zoomvideo`;
let slug = legacySlug.split("_").join("");
// Transform zoomvideo to zoom
oldTypes.some((type) => {
const matcher = new RegExp(`(.+)${type}$`);
if (legacySlug.match(matcher)) {
slug = legacySlug.replace(matcher, "$1");
return true;
}
});
return slug;
}
const handler = async (req: NextApiRequest, res: NextApiResponse) => {
// Check that user is authenticated
req.session = await getSession({ req });
@ -14,12 +35,13 @@ const handler = async (req: NextApiRequest, res: NextApiResponse) => {
}
const [_appName, apiEndpoint] = args;
const appName = _appName.split("_").join(""); // Transform `zoom_video` to `zoomvideo`;
const appName = getSlugFromLegacy(_appName);
try {
/* Absolute path didn't work */
const handlerMap = (await import("@calcom/app-store/apps.generated")).apiHandlers;
const handlers = await handlerMap[appName as keyof typeof handlerMap];
const handlerKey = appName as keyof typeof handlerMap;
console.log(handlerKey);
const handlers = await handlerMap[handlerKey];
const handler = handlers[apiEndpoint as keyof typeof handlers] as NextApiHandler;
if (typeof handler !== "function")

View File

@ -49,6 +49,7 @@ const components = {
function SingleAppPage({ data, source }: inferSSRProps<typeof getStaticProps>) {
return (
<App
slug={data.slug}
name={data.name}
isGlobal={data.isGlobal}
type={data.type}

View File

@ -622,7 +622,7 @@ const loggedInViewerRouter = createProtectedRouter()
const apps = getApps(credentials).map(
({ credentials: _, credential: _1 /* don't leak to frontend */, ...app }) => ({
...app,
credentialIds: credentials.filter((c) => c.type === app.type).map((c) => c.id),
credentialIds: credentials.filter((c) => c.appId === app.slug).map((c) => c.id),
})
);
// `flatMap()` these work like `.filter()` but infers the types correctly

View File

@ -14,16 +14,29 @@ Change name and description
## TODO
- Put lowercase and - restriction only on App name
- Add space restriction as well for Appname. Maybe look for valid dirname or slug regex
- Merge app-store:watch and app-store commands, introduce app-store --watch
- Get strong confirmation for deletion of app. Get the name of the app from user that he wants to delete
- App Description Missing
- Select Box for App Type
- Credentials table doesn't get new entries with cli. Figure out how to do it.
- App already exists check. Ask user to run edit/regenerate command
- Allow deletion of App, cleaning up everything
- folder
- prisma credentials table
- seed.config.json
- Using app/config.json -> Allow Editing App Details.
- Beta Release
- Handle legacy apps which have dirname as something else and type as something else. type is used to do lookups with key
- Add comment in config.json that this file shouldn't be modified manually.
- Install button not coming
- Put lowercase and - restriction only on slug. Keep App Name and others unchanged. Also, use slug instead of appName for dirNames
- Add space restriction as well for Appname. Maybe look for valid dirname or slug regex
- Get strong confirmation for deletion of app. Get the name of the app from user that he wants to delete
- App Description Missing
- Select Box for App Type
- App types Validations
- Credentials table doesn't get new entries with cli. Figure out how to do it.
- Using app/config.json -> Allow Editing App Details.
- Edit App Type, Description, Title, Publisher Name, Email - Name shouldn't be allowed to change as that is unique.
- Improvements
- Merge app-store:watch and app-store commands, introduce app-store --watch
- Allow inputs in non interactive way as well - That would allow easily copy pasting commands.
- Maybe get dx to run app-store:watch
- App already exists check. Ask user to run edit/regenerate command
## Roadmap
- Allow editing and updating app from the cal app itself - including assets uploading when developing locally.
- Improvements in shared code across app
- Use baseApp/api/add.ts for all apps with configuration of credentials creation and redirection URL.

View File

@ -113,7 +113,7 @@ const CreateApp = ({ noDbUpdate }) => {
const fieldName = fields[inputIndex]?.name || "";
const fieldValue = appInputData[fieldName] || "";
const appName = appInputData["appName"];
const appType = appInputData["appType"];
const appType = `${appName}_${appInputData["appType"]}`;
const appTitle = appInputData["appTitle"];
const publisherName = appInputData["publisherName"];
const publisherEmail = appInputData["publisherEmail"];

View File

@ -8,11 +8,12 @@ import _package from "./package.json";
export const metadata = {
description: _package.description,
category: "other",
// FIXME: Currently for an app to be shown as installed, it must have this variable set. Either hardcoded or if it depends on some env variable, that should be checked here
installed: true,
rating: 0,
reviews: 0,
trending: true,
verified: true,
email: "CLI_BASE__PUBLISHER_EMAIL",
...config,
} as App;

View File

@ -2,16 +2,23 @@ import type { NextApiRequest, NextApiResponse } from "next";
import prisma from "@calcom/prisma";
import appConfig from "../config.json";
// TODO: There is a lot of code here that would be used by almost all apps
// - Login Validation
// - Looking up credential.
// - Creating credential would be specific to app, so there can be just createCredential method that app can expose
// - Redirection after successful installation can also be configured by app
export default async function handler(req: NextApiRequest, res: NextApiResponse) {
if (!req.session?.user?.id) {
return res.status(401).json({ message: "You must be logged in to do this" });
}
// TODO: Define appType once and import everywhere
const appType = "CLI_BASE__APP_NAME_CLI_BASE__APP_TYPE";
const slug = appConfig.slug;
try {
const alreadyInstalled = await prisma.credential.findFirst({
where: {
type: appType,
appId: slug,
userId: req.session.user.id,
},
});
@ -20,21 +27,23 @@ export default async function handler(req: NextApiRequest, res: NextApiResponse)
}
const installation = await prisma.credential.create({
data: {
type: appType,
// TODO: Why do we need type in Credential? Why can't we simply use appId
type: slug,
key: {},
userId: req.session.user.id,
appId: "CLI_BASE__APP_NAME",
appId: slug,
},
});
if (!installation) {
throw new Error("Unable to create user credential for CLI_BASE__APP_NAME");
throw new Error(`Unable to create user credential for ${slug}`);
}
} catch (error: unknown) {
if (error instanceof Error) {
console.error(error.message);
return res.status(500).json({ message: error.message });
}
return res.status(500);
}
return res.status(200).json({ url: "/apps/zapier/setup" });
return res.status(200).json({ url: "/apps/installed" });
}

View File

@ -1,11 +1,10 @@
import type { InstallAppButtonProps } from "@calcom/app-store/types";
import useAddAppMutation from "../../_utils/useAddAppMutation";
import appConfig from "../config.json";
export default function InstallAppButton(props: InstallAppButtonProps) {
// eslint-disable-next-line @typescript-eslint/ban-ts-comment
//@ts-ignore
const mutation = useAddAppMutation("CLI_BASE__APP_NAME_CLI_BASE__APP_TYPE");
const mutation = useAddAppMutation(appConfig.slug);
return (
<>

View File

@ -98,6 +98,12 @@ if (isInWatchMode) {
debouncedGenerateFiles();
}
})
.on("change", (filePath) => {
if (filePath.endsWith("config.json")) {
console.log("Config file changed");
debouncedGenerateFiles();
}
})
.on("unlinkDir", (dirPath) => {
const appName = getAppName(dirPath);
if (appName) {

View File

@ -8,11 +8,11 @@ import _package from "./package.json";
export const metadata = {
description: _package.description,
category: "other",
installed: true,
rating: 0,
reviews: 0,
trending: true,
verified: true,
email: "CLI_BASE__PUBLISHER_EMAIL",
...config,
} as App;

View File

@ -2,16 +2,18 @@ import type { NextApiRequest, NextApiResponse } from "next";
import prisma from "@calcom/prisma";
import appConfig from "../config.json";
export default async function handler(req: NextApiRequest, res: NextApiResponse) {
if (!req.session?.user?.id) {
return res.status(401).json({ message: "You must be logged in to do this" });
}
// TODO: Define appType once and import everywhere
const appType = "CLI_BASE__APP_NAME_CLI_BASE__APP_TYPE";
const slug = appConfig.slug;
try {
const alreadyInstalled = await prisma.credential.findFirst({
where: {
type: appType,
appId: slug,
userId: req.session.user.id,
},
});
@ -20,17 +22,19 @@ export default async function handler(req: NextApiRequest, res: NextApiResponse)
}
const installation = await prisma.credential.create({
data: {
type: appType,
// TODO: Why do we need type in Credential? Why can't we simply use appId
type: slug,
key: {},
userId: req.session.user.id,
appId: "CLI_BASE__APP_NAME",
appId: slug,
},
});
if (!installation) {
throw new Error("Unable to create user credential for CLI_BASE__APP_NAME");
throw new Error(`Unable to create user credential for ${slug}`);
}
} catch (error: unknown) {
if (error instanceof Error) {
console.error(error.message);
return res.status(500).json({ message: error.message });
}
return res.status(500);

View File

@ -1,11 +1,12 @@
import type { InstallAppButtonProps } from "@calcom/app-store/types";
import useAddAppMutation from "../../_utils/useAddAppMutation";
import appConfig from "../config.json";
export default function InstallAppButton(props: InstallAppButtonProps) {
// eslint-disable-next-line @typescript-eslint/ban-ts-comment
//@ts-ignore
const mutation = useAddAppMutation("CLI_BASE__APP_NAME_CLI_BASE__APP_TYPE");
const mutation = useAddAppMutation(appConfig.slug);
return (
<>

View File

@ -1,7 +1,7 @@
{
"name": "demo",
"title": "it's a demo app",
"type": "other",
"type": "demo_other",
"slug": "demo",
"imageSrc": "/api/app-store/demo/icon.svg",
"logo": "/api/app-store/demo/icon.svg",

View File

@ -2,9 +2,9 @@
width="640"
height="360"
src="https://www.loom.com/embed/f8d2cd9b2ac74f0c916f20c4441bd1da"
frameborder="0"
webkitallowfullscreen
mozallowfullscreen
allowfullscreen></iframe>
frameBorder="0"
webkitallowfullscreen="true"
mozallowfullscreen="true"
allowFullScreen></iframe>
Looking to honor May 4th? Search no further. Download this app to make your booking success page resemble a long time ago in a galaxy far far away.

View File

@ -4,5 +4,11 @@
"dirName": "demo",
"categories": ["other"],
"type": "demo_other"
},
{
"name": "demovideo",
"dirName": "demovideo",
"categories": ["video"],
"type": "demovideo_video"
}
]