Use slug everywhere instead of app type
This commit is contained in:
parent
c563415795
commit
d563343669
|
@ -21,6 +21,7 @@ import Badge from "@components/ui/Badge";
|
||||||
export default function App({
|
export default function App({
|
||||||
name,
|
name,
|
||||||
type,
|
type,
|
||||||
|
slug,
|
||||||
logo,
|
logo,
|
||||||
body,
|
body,
|
||||||
categories,
|
categories,
|
||||||
|
@ -36,6 +37,7 @@ export default function App({
|
||||||
privacy,
|
privacy,
|
||||||
}: {
|
}: {
|
||||||
name: string;
|
name: string;
|
||||||
|
slug: string;
|
||||||
type: AppType["type"];
|
type: AppType["type"];
|
||||||
isGlobal?: AppType["isGlobal"];
|
isGlobal?: AppType["isGlobal"];
|
||||||
logo: string;
|
logo: string;
|
||||||
|
@ -60,6 +62,7 @@ export default function App({
|
||||||
useGrouping: false,
|
useGrouping: false,
|
||||||
}).format(price);
|
}).format(price);
|
||||||
const [installedApp, setInstalledApp] = useState(false);
|
const [installedApp, setInstalledApp] = useState(false);
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
async function getInstalledApp(appCredentialType: string) {
|
async function getInstalledApp(appCredentialType: string) {
|
||||||
const queryParam = new URLSearchParams();
|
const queryParam = new URLSearchParams();
|
||||||
|
@ -80,8 +83,9 @@ export default function App({
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
getInstalledApp(type);
|
getInstalledApp(slug);
|
||||||
}, [type]);
|
}, [slug]);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<>
|
<>
|
||||||
<Shell large isPublic>
|
<Shell large isPublic>
|
||||||
|
|
|
@ -7,8 +7,9 @@ import { getSession } from "@lib/auth";
|
||||||
export default async function handler(req: NextApiRequest, res: NextApiResponse) {
|
export default async function handler(req: NextApiRequest, res: NextApiResponse) {
|
||||||
req.session = await getSession({ req });
|
req.session = await getSession({ req });
|
||||||
if (req.method === "GET" && req.session && req.session.user.id && req.query) {
|
if (req.method === "GET" && req.session && req.session.user.id && req.query) {
|
||||||
const { "app-credential-type": appCredentialType } = req.query;
|
const { "app-credential-type": slug } = req.query;
|
||||||
if (!appCredentialType && Array.isArray(appCredentialType)) {
|
|
||||||
|
if (!slug && Array.isArray(slug)) {
|
||||||
return res.status(400);
|
return res.status(400);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -16,7 +17,7 @@ export default async function handler(req: NextApiRequest, res: NextApiResponse)
|
||||||
try {
|
try {
|
||||||
const installedApp = await prisma.credential.findFirst({
|
const installedApp = await prisma.credential.findFirst({
|
||||||
where: {
|
where: {
|
||||||
type: appCredentialType as string,
|
appId: slug as string,
|
||||||
userId: userId,
|
userId: userId,
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
|
|
|
@ -3,6 +3,27 @@ import { NextApiHandler, NextApiRequest, NextApiResponse } from "next";
|
||||||
import { getSession } from "@lib/auth";
|
import { getSession } from "@lib/auth";
|
||||||
import { HttpError } from "@lib/core/http/error";
|
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) => {
|
const handler = async (req: NextApiRequest, res: NextApiResponse) => {
|
||||||
// Check that user is authenticated
|
// Check that user is authenticated
|
||||||
req.session = await getSession({ req });
|
req.session = await getSession({ req });
|
||||||
|
@ -14,12 +35,13 @@ const handler = async (req: NextApiRequest, res: NextApiResponse) => {
|
||||||
}
|
}
|
||||||
|
|
||||||
const [_appName, apiEndpoint] = args;
|
const [_appName, apiEndpoint] = args;
|
||||||
const appName = _appName.split("_").join(""); // Transform `zoom_video` to `zoomvideo`;
|
const appName = getSlugFromLegacy(_appName);
|
||||||
|
|
||||||
try {
|
try {
|
||||||
/* Absolute path didn't work */
|
/* Absolute path didn't work */
|
||||||
const handlerMap = (await import("@calcom/app-store/apps.generated")).apiHandlers;
|
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;
|
const handler = handlers[apiEndpoint as keyof typeof handlers] as NextApiHandler;
|
||||||
|
|
||||||
if (typeof handler !== "function")
|
if (typeof handler !== "function")
|
||||||
|
|
|
@ -49,6 +49,7 @@ const components = {
|
||||||
function SingleAppPage({ data, source }: inferSSRProps<typeof getStaticProps>) {
|
function SingleAppPage({ data, source }: inferSSRProps<typeof getStaticProps>) {
|
||||||
return (
|
return (
|
||||||
<App
|
<App
|
||||||
|
slug={data.slug}
|
||||||
name={data.name}
|
name={data.name}
|
||||||
isGlobal={data.isGlobal}
|
isGlobal={data.isGlobal}
|
||||||
type={data.type}
|
type={data.type}
|
||||||
|
|
|
@ -622,7 +622,7 @@ const loggedInViewerRouter = createProtectedRouter()
|
||||||
const apps = getApps(credentials).map(
|
const apps = getApps(credentials).map(
|
||||||
({ credentials: _, credential: _1 /* don't leak to frontend */, ...app }) => ({
|
({ credentials: _, credential: _1 /* don't leak to frontend */, ...app }) => ({
|
||||||
...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
|
// `flatMap()` these work like `.filter()` but infers the types correctly
|
||||||
|
|
|
@ -14,16 +14,29 @@ Change name and description
|
||||||
|
|
||||||
## TODO
|
## TODO
|
||||||
|
|
||||||
- Put lowercase and - restriction only on App name
|
- Beta Release
|
||||||
- Add space restriction as well for Appname. Maybe look for valid dirname or slug regex
|
- Handle legacy apps which have dirname as something else and type as something else. type is used to do lookups with key
|
||||||
- Merge app-store:watch and app-store commands, introduce app-store --watch
|
- Add comment in config.json that this file shouldn't be modified manually.
|
||||||
- Get strong confirmation for deletion of app. Get the name of the app from user that he wants to delete
|
- Install button not coming
|
||||||
- App Description Missing
|
- Put lowercase and - restriction only on slug. Keep App Name and others unchanged. Also, use slug instead of appName for dirNames
|
||||||
- Select Box for App Type
|
- Add space restriction as well for Appname. Maybe look for valid dirname or slug regex
|
||||||
- Credentials table doesn't get new entries with cli. Figure out how to do it.
|
- Get strong confirmation for deletion of app. Get the name of the app from user that he wants to delete
|
||||||
- App already exists check. Ask user to run edit/regenerate command
|
- App Description Missing
|
||||||
- Allow deletion of App, cleaning up everything
|
- Select Box for App Type
|
||||||
- folder
|
- App types Validations
|
||||||
- prisma credentials table
|
- Credentials table doesn't get new entries with cli. Figure out how to do it.
|
||||||
- seed.config.json
|
- Using app/config.json -> Allow Editing App Details.
|
||||||
- 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.
|
||||||
|
|
|
@ -113,7 +113,7 @@ const CreateApp = ({ noDbUpdate }) => {
|
||||||
const fieldName = fields[inputIndex]?.name || "";
|
const fieldName = fields[inputIndex]?.name || "";
|
||||||
const fieldValue = appInputData[fieldName] || "";
|
const fieldValue = appInputData[fieldName] || "";
|
||||||
const appName = appInputData["appName"];
|
const appName = appInputData["appName"];
|
||||||
const appType = appInputData["appType"];
|
const appType = `${appName}_${appInputData["appType"]}`;
|
||||||
const appTitle = appInputData["appTitle"];
|
const appTitle = appInputData["appTitle"];
|
||||||
const publisherName = appInputData["publisherName"];
|
const publisherName = appInputData["publisherName"];
|
||||||
const publisherEmail = appInputData["publisherEmail"];
|
const publisherEmail = appInputData["publisherEmail"];
|
||||||
|
|
|
@ -8,11 +8,12 @@ import _package from "./package.json";
|
||||||
export const metadata = {
|
export const metadata = {
|
||||||
description: _package.description,
|
description: _package.description,
|
||||||
category: "other",
|
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,
|
rating: 0,
|
||||||
reviews: 0,
|
reviews: 0,
|
||||||
trending: true,
|
trending: true,
|
||||||
verified: true,
|
verified: true,
|
||||||
email: "CLI_BASE__PUBLISHER_EMAIL",
|
|
||||||
...config,
|
...config,
|
||||||
} as App;
|
} as App;
|
||||||
|
|
||||||
|
|
|
@ -2,16 +2,23 @@ import type { NextApiRequest, NextApiResponse } from "next";
|
||||||
|
|
||||||
import prisma from "@calcom/prisma";
|
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) {
|
export default async function handler(req: NextApiRequest, res: NextApiResponse) {
|
||||||
if (!req.session?.user?.id) {
|
if (!req.session?.user?.id) {
|
||||||
return res.status(401).json({ message: "You must be logged in to do this" });
|
return res.status(401).json({ message: "You must be logged in to do this" });
|
||||||
}
|
}
|
||||||
// TODO: Define appType once and import everywhere
|
// TODO: Define appType once and import everywhere
|
||||||
const appType = "CLI_BASE__APP_NAME_CLI_BASE__APP_TYPE";
|
const slug = appConfig.slug;
|
||||||
try {
|
try {
|
||||||
const alreadyInstalled = await prisma.credential.findFirst({
|
const alreadyInstalled = await prisma.credential.findFirst({
|
||||||
where: {
|
where: {
|
||||||
type: appType,
|
appId: slug,
|
||||||
userId: req.session.user.id,
|
userId: req.session.user.id,
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
|
@ -20,21 +27,23 @@ export default async function handler(req: NextApiRequest, res: NextApiResponse)
|
||||||
}
|
}
|
||||||
const installation = await prisma.credential.create({
|
const installation = await prisma.credential.create({
|
||||||
data: {
|
data: {
|
||||||
type: appType,
|
// TODO: Why do we need type in Credential? Why can't we simply use appId
|
||||||
|
type: slug,
|
||||||
key: {},
|
key: {},
|
||||||
userId: req.session.user.id,
|
userId: req.session.user.id,
|
||||||
appId: "CLI_BASE__APP_NAME",
|
appId: slug,
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
if (!installation) {
|
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) {
|
} catch (error: unknown) {
|
||||||
if (error instanceof Error) {
|
if (error instanceof Error) {
|
||||||
|
console.error(error.message);
|
||||||
return res.status(500).json({ message: error.message });
|
return res.status(500).json({ message: error.message });
|
||||||
}
|
}
|
||||||
return res.status(500);
|
return res.status(500);
|
||||||
}
|
}
|
||||||
|
|
||||||
return res.status(200).json({ url: "/apps/zapier/setup" });
|
return res.status(200).json({ url: "/apps/installed" });
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,11 +1,10 @@
|
||||||
import type { InstallAppButtonProps } from "@calcom/app-store/types";
|
import type { InstallAppButtonProps } from "@calcom/app-store/types";
|
||||||
|
|
||||||
import useAddAppMutation from "../../_utils/useAddAppMutation";
|
import useAddAppMutation from "../../_utils/useAddAppMutation";
|
||||||
|
import appConfig from "../config.json";
|
||||||
|
|
||||||
export default function InstallAppButton(props: InstallAppButtonProps) {
|
export default function InstallAppButton(props: InstallAppButtonProps) {
|
||||||
// eslint-disable-next-line @typescript-eslint/ban-ts-comment
|
const mutation = useAddAppMutation(appConfig.slug);
|
||||||
//@ts-ignore
|
|
||||||
const mutation = useAddAppMutation("CLI_BASE__APP_NAME_CLI_BASE__APP_TYPE");
|
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<>
|
<>
|
||||||
|
|
|
@ -98,6 +98,12 @@ if (isInWatchMode) {
|
||||||
debouncedGenerateFiles();
|
debouncedGenerateFiles();
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
|
.on("change", (filePath) => {
|
||||||
|
if (filePath.endsWith("config.json")) {
|
||||||
|
console.log("Config file changed");
|
||||||
|
debouncedGenerateFiles();
|
||||||
|
}
|
||||||
|
})
|
||||||
.on("unlinkDir", (dirPath) => {
|
.on("unlinkDir", (dirPath) => {
|
||||||
const appName = getAppName(dirPath);
|
const appName = getAppName(dirPath);
|
||||||
if (appName) {
|
if (appName) {
|
||||||
|
|
|
@ -8,11 +8,11 @@ import _package from "./package.json";
|
||||||
export const metadata = {
|
export const metadata = {
|
||||||
description: _package.description,
|
description: _package.description,
|
||||||
category: "other",
|
category: "other",
|
||||||
|
installed: true,
|
||||||
rating: 0,
|
rating: 0,
|
||||||
reviews: 0,
|
reviews: 0,
|
||||||
trending: true,
|
trending: true,
|
||||||
verified: true,
|
verified: true,
|
||||||
email: "CLI_BASE__PUBLISHER_EMAIL",
|
|
||||||
...config,
|
...config,
|
||||||
} as App;
|
} as App;
|
||||||
|
|
||||||
|
|
|
@ -2,16 +2,18 @@ import type { NextApiRequest, NextApiResponse } from "next";
|
||||||
|
|
||||||
import prisma from "@calcom/prisma";
|
import prisma from "@calcom/prisma";
|
||||||
|
|
||||||
|
import appConfig from "../config.json";
|
||||||
|
|
||||||
export default async function handler(req: NextApiRequest, res: NextApiResponse) {
|
export default async function handler(req: NextApiRequest, res: NextApiResponse) {
|
||||||
if (!req.session?.user?.id) {
|
if (!req.session?.user?.id) {
|
||||||
return res.status(401).json({ message: "You must be logged in to do this" });
|
return res.status(401).json({ message: "You must be logged in to do this" });
|
||||||
}
|
}
|
||||||
// TODO: Define appType once and import everywhere
|
// TODO: Define appType once and import everywhere
|
||||||
const appType = "CLI_BASE__APP_NAME_CLI_BASE__APP_TYPE";
|
const slug = appConfig.slug;
|
||||||
try {
|
try {
|
||||||
const alreadyInstalled = await prisma.credential.findFirst({
|
const alreadyInstalled = await prisma.credential.findFirst({
|
||||||
where: {
|
where: {
|
||||||
type: appType,
|
appId: slug,
|
||||||
userId: req.session.user.id,
|
userId: req.session.user.id,
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
|
@ -20,17 +22,19 @@ export default async function handler(req: NextApiRequest, res: NextApiResponse)
|
||||||
}
|
}
|
||||||
const installation = await prisma.credential.create({
|
const installation = await prisma.credential.create({
|
||||||
data: {
|
data: {
|
||||||
type: appType,
|
// TODO: Why do we need type in Credential? Why can't we simply use appId
|
||||||
|
type: slug,
|
||||||
key: {},
|
key: {},
|
||||||
userId: req.session.user.id,
|
userId: req.session.user.id,
|
||||||
appId: "CLI_BASE__APP_NAME",
|
appId: slug,
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
if (!installation) {
|
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) {
|
} catch (error: unknown) {
|
||||||
if (error instanceof Error) {
|
if (error instanceof Error) {
|
||||||
|
console.error(error.message);
|
||||||
return res.status(500).json({ message: error.message });
|
return res.status(500).json({ message: error.message });
|
||||||
}
|
}
|
||||||
return res.status(500);
|
return res.status(500);
|
||||||
|
|
|
@ -1,11 +1,12 @@
|
||||||
import type { InstallAppButtonProps } from "@calcom/app-store/types";
|
import type { InstallAppButtonProps } from "@calcom/app-store/types";
|
||||||
|
|
||||||
import useAddAppMutation from "../../_utils/useAddAppMutation";
|
import useAddAppMutation from "../../_utils/useAddAppMutation";
|
||||||
|
import appConfig from "../config.json";
|
||||||
|
|
||||||
export default function InstallAppButton(props: InstallAppButtonProps) {
|
export default function InstallAppButton(props: InstallAppButtonProps) {
|
||||||
// eslint-disable-next-line @typescript-eslint/ban-ts-comment
|
// eslint-disable-next-line @typescript-eslint/ban-ts-comment
|
||||||
//@ts-ignore
|
//@ts-ignore
|
||||||
const mutation = useAddAppMutation("CLI_BASE__APP_NAME_CLI_BASE__APP_TYPE");
|
const mutation = useAddAppMutation(appConfig.slug);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<>
|
<>
|
||||||
|
|
|
@ -1,7 +1,7 @@
|
||||||
{
|
{
|
||||||
"name": "demo",
|
"name": "demo",
|
||||||
"title": "it's a demo app",
|
"title": "it's a demo app",
|
||||||
"type": "other",
|
"type": "demo_other",
|
||||||
"slug": "demo",
|
"slug": "demo",
|
||||||
"imageSrc": "/api/app-store/demo/icon.svg",
|
"imageSrc": "/api/app-store/demo/icon.svg",
|
||||||
"logo": "/api/app-store/demo/icon.svg",
|
"logo": "/api/app-store/demo/icon.svg",
|
||||||
|
|
|
@ -2,9 +2,9 @@
|
||||||
width="640"
|
width="640"
|
||||||
height="360"
|
height="360"
|
||||||
src="https://www.loom.com/embed/f8d2cd9b2ac74f0c916f20c4441bd1da"
|
src="https://www.loom.com/embed/f8d2cd9b2ac74f0c916f20c4441bd1da"
|
||||||
frameborder="0"
|
frameBorder="0"
|
||||||
webkitallowfullscreen
|
webkitallowfullscreen="true"
|
||||||
mozallowfullscreen
|
mozallowfullscreen="true"
|
||||||
allowfullscreen></iframe>
|
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.
|
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.
|
||||||
|
|
|
@ -4,5 +4,11 @@
|
||||||
"dirName": "demo",
|
"dirName": "demo",
|
||||||
"categories": ["other"],
|
"categories": ["other"],
|
||||||
"type": "demo_other"
|
"type": "demo_other"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "demovideo",
|
||||||
|
"dirName": "demovideo",
|
||||||
|
"categories": ["video"],
|
||||||
|
"type": "demovideo_video"
|
||||||
}
|
}
|
||||||
]
|
]
|
||||||
|
|
Loading…
Reference in New Issue
Block a user