Improvements
This commit is contained in:
parent
d563343669
commit
8ecaa95dc9
|
@ -74,13 +74,6 @@
|
|||
"isBackground": false,
|
||||
"problemMatcher": []
|
||||
},
|
||||
{
|
||||
"label": "AppStoreCli-build:watch",
|
||||
"type": "shell",
|
||||
"command": "cd packages/app-store-cli && yarn build:watch",
|
||||
"isBackground": false,
|
||||
"problemMatcher": []
|
||||
},
|
||||
{
|
||||
"label": "AppStoreWatch",
|
||||
"type": "shell",
|
||||
|
|
|
@ -1,29 +1,10 @@
|
|||
import { NextApiHandler, NextApiRequest, NextApiResponse } from "next";
|
||||
|
||||
import { deriveAppKeyFromSlug } from "@calcom/lib/deriveAppKeyFromSlug";
|
||||
|
||||
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 });
|
||||
|
@ -34,13 +15,12 @@ const handler = async (req: NextApiRequest, res: NextApiResponse) => {
|
|||
return res.status(404).json({ message: `API route not found` });
|
||||
}
|
||||
|
||||
const [_appName, apiEndpoint] = args;
|
||||
const appName = getSlugFromLegacy(_appName);
|
||||
const [appName, apiEndpoint] = args;
|
||||
try {
|
||||
/* Absolute path didn't work */
|
||||
const handlerMap = (await import("@calcom/app-store/apps.generated")).apiHandlers;
|
||||
const handlerKey = appName as keyof typeof handlerMap;
|
||||
console.log(handlerKey);
|
||||
|
||||
const handlerKey = deriveAppKeyFromSlug(appName, handlerMap);
|
||||
const handlers = await handlerMap[handlerKey];
|
||||
const handler = handlers[apiEndpoint as keyof typeof handlers] as NextApiHandler;
|
||||
|
||||
|
|
|
@ -6,23 +6,21 @@
|
|||
"node": ">=10"
|
||||
},
|
||||
"scripts": {
|
||||
"build:watch": "tsc --watch",
|
||||
"build": "tsc && chmod +x dist/cli.js",
|
||||
"start": "npm run build && dist/cli.js",
|
||||
"pretest": "npm run build",
|
||||
"test": "ava",
|
||||
"cli": "chmod +x dist/cli.js && dist/cli.js"
|
||||
"cli": "ts-node --transpile-only src/cli.tsx"
|
||||
},
|
||||
"files": [
|
||||
"dist/cli.js"
|
||||
],
|
||||
"dependencies": {
|
||||
"@calcom/lib": "*",
|
||||
"ink": "^3.2.0",
|
||||
"ink-text-input": "^4.0.3",
|
||||
"meow": "^9.0.0",
|
||||
"react": "^17.0.2"
|
||||
},
|
||||
"devDependencies": {
|
||||
"ts-node": "^10.6.0",
|
||||
"@ava/typescript": "^3.0.1",
|
||||
"@types/react": "^18.0.9",
|
||||
"ava": "^4.2.0",
|
||||
|
|
|
@ -15,6 +15,7 @@ Change name and description
|
|||
## TODO
|
||||
|
||||
- Beta Release
|
||||
- Print slug after creation of app. Also, mention that it would be same as dir name
|
||||
- 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
|
||||
|
@ -34,9 +35,16 @@ Change name and description
|
|||
- Maybe get dx to run app-store:watch
|
||||
- App already exists check. Ask user to run edit/regenerate command
|
||||
|
||||
### Why we shouldn't have appType
|
||||
|
||||
- App can have multiple types and thus categories is more suitable for this.
|
||||
- The reason we seem to be using appType is to refer to an app uniquely but we already have app slug for that.
|
||||
|
||||
## 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.
|
||||
- Delete creation side effects if App creation fails - Might make debugging difficult
|
||||
- This is so that web app doesn't break because of additional app folders or faulty db-seed
|
||||
|
|
@ -5,12 +5,20 @@ import TextInput from "ink-text-input";
|
|||
import path from "path";
|
||||
import React, { FC, useEffect, useRef, useState } from "react";
|
||||
|
||||
function sanitizeAppName(value: string): string {
|
||||
return value.toLowerCase().replace(/-/g, "_");
|
||||
const slugify = (str: string) => {
|
||||
// It is to be a valid dir name, a valid JS variable name and a valid URL path
|
||||
return str.replace(/[^a-zA-Z0-9-]/g, "_").toLowerCase();
|
||||
};
|
||||
|
||||
function getSlugFromAppName(appName: string | null): string | null {
|
||||
if (!appName) {
|
||||
return appName;
|
||||
}
|
||||
return slugify(appName);
|
||||
}
|
||||
|
||||
function getAppDirPath(appName: any) {
|
||||
return path.join(appStoreDir, `${appName}`);
|
||||
function getAppDirPath(slug: any) {
|
||||
return path.join(appStoreDir, `${slug}`);
|
||||
}
|
||||
|
||||
const appStoreDir = path.resolve(__dirname, "..", "..", "app-store");
|
||||
|
@ -24,29 +32,30 @@ const execSync = (...args) => {
|
|||
function absolutePath(appRelativePath) {
|
||||
return path.join(appStoreDir, appRelativePath);
|
||||
}
|
||||
const updatePackageJson = ({ appName, appDirPath }) => {
|
||||
const updatePackageJson = ({ slug, appDirPath }) => {
|
||||
const packageJsonConfig = JSON.parse(fs.readFileSync(`${appDirPath}/package.json`).toString());
|
||||
packageJsonConfig.name = `@calcom/${appName}`;
|
||||
packageJsonConfig.name = `@calcom/${slug}`;
|
||||
// packageJsonConfig.description = `@calcom/${appName}`;
|
||||
fs.writeFileSync(`${appDirPath}/package.json`, JSON.stringify(packageJsonConfig, null, 2));
|
||||
};
|
||||
|
||||
const BaseAppFork = {
|
||||
create: function* ({ appType, appName, appTitle, publisherName, publisherEmail }) {
|
||||
const appDirPath = getAppDirPath(appName);
|
||||
create: function* ({ appType, appName, slug, appTitle, publisherName, publisherEmail }) {
|
||||
const appDirPath = getAppDirPath(slug);
|
||||
yield "Forking base app";
|
||||
execSync(`mkdir -p ${appDirPath}`);
|
||||
execSync(`cp -r ${absolutePath("_baseApp/*")} ${appDirPath}`);
|
||||
updatePackageJson({ appName, appDirPath });
|
||||
updatePackageJson({ slug, appDirPath });
|
||||
|
||||
let config = {
|
||||
name: appName,
|
||||
title: appTitle,
|
||||
type: appType,
|
||||
slug: appName,
|
||||
imageSrc: `/api/app-store/${appName}/icon.svg`,
|
||||
logo: `/api/app-store/${appName}/icon.svg`,
|
||||
url: `https://cal.com/apps/${appName}`,
|
||||
// @deprecated - It shouldn't exist.
|
||||
type: slug,
|
||||
slug: slug,
|
||||
imageSrc: `/api/app-store/${slug}/icon.svg`,
|
||||
logo: `/api/app-store/${slug}/icon.svg`,
|
||||
url: `https://cal.com/apps/${slug}`,
|
||||
variant: appType,
|
||||
publisher: publisherName,
|
||||
email: publisherEmail,
|
||||
|
@ -59,22 +68,21 @@ const BaseAppFork = {
|
|||
fs.writeFileSync(`${appDirPath}/config.json`, JSON.stringify(config, null, 2));
|
||||
yield "Forked base app";
|
||||
},
|
||||
delete: function ({ appName }) {
|
||||
const appDirPath = getAppDirPath(appName);
|
||||
delete: function ({ slug }) {
|
||||
const appDirPath = getAppDirPath(slug);
|
||||
execSync(`rm -rf ${appDirPath}`);
|
||||
},
|
||||
};
|
||||
|
||||
const Seed = {
|
||||
seedConfigPath: absolutePath("../prisma/seed-app-store.config.json"),
|
||||
update: function ({ appName, appType, noDbUpdate }) {
|
||||
update: function ({ slug, appType, noDbUpdate }) {
|
||||
const seedConfig = JSON.parse(fs.readFileSync(this.seedConfigPath).toString());
|
||||
if (!seedConfig.find((app) => app.name === appName)) {
|
||||
if (!seedConfig.find((app) => app.slug === slug)) {
|
||||
seedConfig.push({
|
||||
name: appName,
|
||||
dirName: appName,
|
||||
dirName: slug,
|
||||
categories: [appType],
|
||||
type: `${appName}_${appType}`,
|
||||
slug: slug,
|
||||
});
|
||||
}
|
||||
|
||||
|
@ -83,12 +91,12 @@ const Seed = {
|
|||
execSync(`cd ${workspaceDir} && yarn db-seed`);
|
||||
}
|
||||
},
|
||||
revert: async function ({ appName, noDbUpdate }) {
|
||||
revert: async function ({ slug, noDbUpdate }) {
|
||||
let seedConfig = JSON.parse(fs.readFileSync(this.seedConfigPath).toString());
|
||||
seedConfig = seedConfig.filter((app) => app.name !== appName);
|
||||
seedConfig = seedConfig.filter((app) => app.slug !== slug);
|
||||
fs.writeFileSync(this.seedConfigPath, JSON.stringify(seedConfig, null, 2));
|
||||
if (!noDbUpdate) {
|
||||
execSync(`yarn workspace @calcom/prisma delete-app ${appName}`);
|
||||
execSync(`yarn workspace @calcom/prisma delete-app ${slug}`);
|
||||
}
|
||||
},
|
||||
};
|
||||
|
@ -113,23 +121,23 @@ const CreateApp = ({ noDbUpdate }) => {
|
|||
const fieldName = fields[inputIndex]?.name || "";
|
||||
const fieldValue = appInputData[fieldName] || "";
|
||||
const appName = appInputData["appName"];
|
||||
const appType = `${appName}_${appInputData["appType"]}`;
|
||||
const appType = appInputData["appType"];
|
||||
const appTitle = appInputData["appTitle"];
|
||||
const publisherName = appInputData["publisherName"];
|
||||
const publisherEmail = appInputData["publisherEmail"];
|
||||
const [result, setResult] = useState("...");
|
||||
const { exit } = useApp();
|
||||
const slug = getSlugFromAppName(appName);
|
||||
const allFieldsFilled = inputIndex === fields.length;
|
||||
|
||||
useEffect(() => {
|
||||
// When all fields have been filled
|
||||
if (allFieldsFilled) {
|
||||
const it = BaseAppFork.create({ appType, appName, appTitle, publisherName, publisherEmail });
|
||||
const it = BaseAppFork.create({ appType, appName, slug, appTitle, publisherName, publisherEmail });
|
||||
for (const item of it) {
|
||||
setResult(item);
|
||||
}
|
||||
|
||||
Seed.update({ appName, appType, noDbUpdate });
|
||||
Seed.update({ slug, appType, noDbUpdate });
|
||||
|
||||
generateAppFiles();
|
||||
|
||||
|
@ -162,9 +170,6 @@ const CreateApp = ({ noDbUpdate }) => {
|
|||
});
|
||||
}}
|
||||
onChange={(value) => {
|
||||
if (value) {
|
||||
value = sanitizeAppName(value);
|
||||
}
|
||||
setAppInputData((appInputData) => {
|
||||
return {
|
||||
...appInputData,
|
||||
|
@ -177,24 +182,23 @@ const CreateApp = ({ noDbUpdate }) => {
|
|||
);
|
||||
};
|
||||
|
||||
const DeleteApp = ({ noDbUpdate, appName }) => {
|
||||
appName = sanitizeAppName(appName);
|
||||
BaseAppFork.delete({ appName });
|
||||
Seed.revert({ appName });
|
||||
const DeleteApp = ({ noDbUpdate, slug }) => {
|
||||
BaseAppFork.delete({ slug });
|
||||
Seed.revert({ slug });
|
||||
generateAppFiles();
|
||||
return <Text>Deleted App {appName}.</Text>;
|
||||
return <Text>Deleted App {slug}.</Text>;
|
||||
};
|
||||
|
||||
const App: FC<{ noDbUpdate?: boolean; command: "create" | "delete"; appName?: string }> = ({
|
||||
const App: FC<{ noDbUpdate?: boolean; command: "create" | "delete"; slug?: string }> = ({
|
||||
command,
|
||||
noDbUpdate,
|
||||
appName,
|
||||
slug,
|
||||
}) => {
|
||||
if (command === "create") {
|
||||
return <CreateApp noDbUpdate={noDbUpdate} />;
|
||||
}
|
||||
if (command === "delete") {
|
||||
return <DeleteApp appName={appName} noDbUpdate={noDbUpdate} />;
|
||||
return <DeleteApp slug={slug} noDbUpdate={noDbUpdate} />;
|
||||
}
|
||||
};
|
||||
module.exports = App;
|
||||
|
|
|
@ -18,7 +18,7 @@ const cli = meow(
|
|||
noDbUpdate: {
|
||||
type: "boolean",
|
||||
},
|
||||
name: {
|
||||
slug: {
|
||||
type: "string",
|
||||
},
|
||||
},
|
||||
|
@ -35,9 +35,9 @@ if (command !== "create" && command != "delete") {
|
|||
cli.showHelp();
|
||||
}
|
||||
|
||||
let appName = null;
|
||||
let slug = null;
|
||||
|
||||
if (command === "delete") {
|
||||
appName = cli.flags.name;
|
||||
slug = cli.flags.slug;
|
||||
}
|
||||
render(<App appName={appName} command={command} noDbUpdate={cli.flags.noDbUpdate} />);
|
||||
render(<App slug={slug} command={command} noDbUpdate={cli.flags.noDbUpdate} />);
|
||||
|
|
|
@ -5,7 +5,15 @@
|
|||
"esModuleInterop": true,
|
||||
"outDir": "dist",
|
||||
"noEmitOnError": false,
|
||||
"target": "ES2020"
|
||||
"target": "ES2020",
|
||||
"baseUrl": "."
|
||||
},
|
||||
"include": ["src"]
|
||||
"include": [
|
||||
"next-env.d.ts",
|
||||
"../../packages/types/*.d.ts",
|
||||
"../../packages/types/next-auth.d.ts",
|
||||
"./src/**/*.ts",
|
||||
"./src/**/*.tsx",
|
||||
"../lib/**/*.ts"
|
||||
]
|
||||
}
|
||||
|
|
|
@ -28,6 +28,8 @@ export default async function handler(req: NextApiRequest, res: NextApiResponse)
|
|||
const installation = await prisma.credential.create({
|
||||
data: {
|
||||
// TODO: Why do we need type in Credential? Why can't we simply use appId
|
||||
// Using slug as type for new credentials so that we keep on using type in requests.
|
||||
// `deriveAppKeyFromSlug` should be able to handle old type and new type which is equal to slug
|
||||
type: slug,
|
||||
key: {},
|
||||
userId: req.session.user.id,
|
||||
|
|
|
@ -5,7 +5,9 @@ import { WEBAPP_URL } from "@calcom/lib/constants";
|
|||
import { App } from "@calcom/types/App";
|
||||
|
||||
function useAddAppMutation(type: App["type"], options?: Parameters<typeof useMutation>[2]) {
|
||||
const appName = type.replace(/_/g, "");
|
||||
// FIXME: Ensure that existing apps keep on working
|
||||
// const appName = type.replace(/_/g, "");
|
||||
const appName = type;
|
||||
const mutation = useMutation(async () => {
|
||||
const state: IntegrationOAuthCallbackState = {
|
||||
returnTo: WEBAPP_URL + "/apps/installed" + location.search,
|
||||
|
|
|
@ -25,6 +25,9 @@ function getAppName(candidatePath) {
|
|||
}
|
||||
|
||||
function generateFiles() {
|
||||
let clientOutput = [`import dynamic from "next/dynamic"`];
|
||||
let serverOutput = [];
|
||||
|
||||
fs.readdirSync(`${__dirname}`).forEach(function (dir) {
|
||||
if (fs.statSync(`${__dirname}/${dir}`).isDirectory()) {
|
||||
if (!getAppName(dir)) {
|
||||
|
@ -34,9 +37,6 @@ function generateFiles() {
|
|||
}
|
||||
});
|
||||
|
||||
let clientOutput = [`import dynamic from "next/dynamic"`];
|
||||
let serverOutput = [];
|
||||
|
||||
function forEachAppDir(callback) {
|
||||
for (let i = 0; i < appDirs.length; i++) {
|
||||
callback(appDirs[i]);
|
||||
|
@ -98,12 +98,12 @@ if (isInWatchMode) {
|
|||
debouncedGenerateFiles();
|
||||
}
|
||||
})
|
||||
.on("change", (filePath) => {
|
||||
if (filePath.endsWith("config.json")) {
|
||||
console.log("Config file changed");
|
||||
debouncedGenerateFiles();
|
||||
}
|
||||
})
|
||||
// .on("change", (filePath) => {
|
||||
// if (filePath.endsWith("config.json")) {
|
||||
// console.log("Config file changed");
|
||||
// debouncedGenerateFiles();
|
||||
// }
|
||||
// })
|
||||
.on("unlinkDir", (dirPath) => {
|
||||
const appName = getAppName(dirPath);
|
||||
if (appName) {
|
||||
|
|
|
@ -1,6 +1,7 @@
|
|||
import { useSession } from "next-auth/react";
|
||||
|
||||
import { WEBAPP_URL } from "@calcom/lib/constants";
|
||||
import { deriveAppKeyFromSlug } from "@calcom/lib/deriveAppKeyFromSlug";
|
||||
import { useLocale } from "@calcom/lib/hooks/useLocale";
|
||||
import type { App } from "@calcom/types/App";
|
||||
import Button from "@calcom/ui/Button";
|
||||
|
@ -15,14 +16,8 @@ export const InstallAppButton = (
|
|||
) => {
|
||||
const { status } = useSession();
|
||||
const { t } = useLocale();
|
||||
let appName = props.type.replace(/_/g, "");
|
||||
let InstallAppButtonComponent = InstallAppButtonMap[appName as keyof typeof InstallAppButtonMap];
|
||||
/** So we can either call it by simple name (ex. `slack`, `giphy`) instead of
|
||||
* `slackmessaging`, `giphyother` while maintaining retro-compatibility. */
|
||||
if (!InstallAppButtonComponent) {
|
||||
[appName] = props.type.split("_");
|
||||
InstallAppButtonComponent = InstallAppButtonMap[appName as keyof typeof InstallAppButtonMap];
|
||||
}
|
||||
const key = deriveAppKeyFromSlug(props.type, InstallAppButtonMap);
|
||||
const InstallAppButtonComponent = InstallAppButtonMap[key as keyof typeof InstallAppButtonMap];
|
||||
if (!InstallAppButtonComponent) return null;
|
||||
if (status === "unauthenticated")
|
||||
return (
|
||||
|
|
|
@ -1,12 +0,0 @@
|
|||
{
|
||||
"name": "demo",
|
||||
"title": "it's a demo app",
|
||||
"type": "demo_other",
|
||||
"slug": "demo",
|
||||
"imageSrc": "/api/app-store/demo/icon.svg",
|
||||
"logo": "/api/app-store/demo/icon.svg",
|
||||
"url": "https://cal.com/apps/demo",
|
||||
"variant": "other",
|
||||
"publisher": "hariom",
|
||||
"email": "hariombalhara@gmail.com"
|
||||
}
|
|
@ -8,6 +8,7 @@ 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,
|
|
@ -4,6 +4,11 @@ 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" });
|
||||
|
@ -23,6 +28,8 @@ export default async function handler(req: NextApiRequest, res: NextApiResponse)
|
|||
const installation = await prisma.credential.create({
|
||||
data: {
|
||||
// TODO: Why do we need type in Credential? Why can't we simply use appId
|
||||
// Using slug as type for new credentials so that we keep on using type in requests.
|
||||
// `deriveAppKeyFromSlug` should be able to handle old type and new type which is equal to slug
|
||||
type: slug,
|
||||
key: {},
|
||||
userId: req.session.user.id,
|
||||
|
@ -40,5 +47,5 @@ export default async function handler(req: NextApiRequest, res: NextApiResponse)
|
|||
return res.status(500);
|
||||
}
|
||||
|
||||
return res.status(200).json({ url: "/apps/zapier/setup" });
|
||||
return res.status(200).json({ url: "/apps/installed" });
|
||||
}
|
|
@ -4,8 +4,6 @@ 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(appConfig.slug);
|
||||
|
||||
return (
|
|
@ -0,0 +1,12 @@
|
|||
{
|
||||
"name": "Demo App",
|
||||
"title": "It is a demo app",
|
||||
"type": "demo_app",
|
||||
"slug": "demo_app",
|
||||
"imageSrc": "/api/app-store/demo_app/icon.svg",
|
||||
"logo": "/api/app-store/demo_app/icon.svg",
|
||||
"url": "https://cal.com/apps/demo_app",
|
||||
"variant": "other",
|
||||
"publisher": "hariom",
|
||||
"email": "har@gmail"
|
||||
}
|
|
@ -1,7 +1,7 @@
|
|||
{
|
||||
"$schema": "https://json.schemastore.org/package.json",
|
||||
"private": true,
|
||||
"name": "@calcom/demo",
|
||||
"name": "@calcom/demo_app",
|
||||
"version": "0.0.0",
|
||||
"main": "./index.ts",
|
||||
"description": "Your app description goes here.",
|
|
@ -0,0 +1,23 @@
|
|||
export function deriveAppKeyFromSlug(legacySlug, map) {
|
||||
const oldTypes = ["video", "other", "calendar", "web3", "payment", "messaging"];
|
||||
const handlerKey = legacySlug as keyof typeof map;
|
||||
const handlers = map[handlerKey];
|
||||
if (handlers) {
|
||||
return handlerKey;
|
||||
}
|
||||
// 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;
|
||||
}
|
|
@ -1,5 +0,0 @@
|
|||
export const slugify = (str: string) => {
|
||||
return str.replace(/[^a-zA-Z0-9-]/g, "-").toLowerCase();
|
||||
};
|
||||
|
||||
export default slugify;
|
|
@ -1,19 +1,25 @@
|
|||
import prisma from ".";
|
||||
|
||||
require("dotenv").config({ path: "../../.env" });
|
||||
|
||||
// TODO: Put some restrictions here to run it on local DB only.
|
||||
// Production DB currently doesn't support app deletion
|
||||
async function main() {
|
||||
const appName = process.argv[2];
|
||||
const appId = process.argv[2];
|
||||
try {
|
||||
await prisma.app.delete({
|
||||
where: {
|
||||
slug: appName,
|
||||
slug: appId,
|
||||
},
|
||||
});
|
||||
console.log(`Deleted app from DB: '${appName}'`);
|
||||
await prisma.credential.deleteMany({
|
||||
where: {
|
||||
appId: appId,
|
||||
},
|
||||
});
|
||||
console.log(`Deleted app from DB: '${appId}'`);
|
||||
} catch (e) {
|
||||
if (e.code === "P2025") {
|
||||
console.log(`App '${appName}' already deleted from DB`);
|
||||
console.log(`App '${appId}' already deleted from DB`);
|
||||
return;
|
||||
}
|
||||
throw e;
|
||||
|
|
|
@ -1,14 +1,12 @@
|
|||
[
|
||||
{
|
||||
"name": "demo",
|
||||
"dirName": "demo",
|
||||
"dirName": "demo app",
|
||||
"categories": ["other"],
|
||||
"type": "demo_other"
|
||||
"slug": "demo app"
|
||||
},
|
||||
{
|
||||
"name": "demovideo",
|
||||
"dirName": "demovideo",
|
||||
"categories": ["video"],
|
||||
"type": "demovideo_video"
|
||||
"dirName": "demo_app",
|
||||
"categories": ["other"],
|
||||
"slug": "demo_app"
|
||||
}
|
||||
]
|
||||
|
|
|
@ -139,7 +139,7 @@ async function main() {
|
|||
);
|
||||
for (let i = 0; i < generatedApps.length; i++) {
|
||||
const generatedApp = generatedApps[i];
|
||||
await createApp(generatedApp.name, generatedApp.dirName, generatedApp.categories, generatedApp.type);
|
||||
await createApp(generatedApp.slug, generatedApp.dirName, generatedApp.categories, generatedApp.type);
|
||||
}
|
||||
}
|
||||
|
||||
|
|
Loading…
Reference in New Issue
Block a user