mirror of
https://github.com/wasp-lang/wasp.git
synced 2024-09-11 15:26:19 +03:00
[MAGE] add delete user option (#1577)
* add delete user functionality * refactor to deleteMyself * delete user relevant info from projects * update status to deleted
This commit is contained in:
parent
080a1fdbf9
commit
3825ef94ed
6
wasp-ai/.prettierrc
Normal file
6
wasp-ai/.prettierrc
Normal file
@ -0,0 +1,6 @@
|
||||
{
|
||||
"semi": true,
|
||||
"singleQuote": false,
|
||||
"trailingComma": "es5",
|
||||
"printWidth": 100
|
||||
}
|
@ -107,6 +107,11 @@ action createFeedback {
|
||||
entities: [Feedback]
|
||||
}
|
||||
|
||||
action deleteMyself {
|
||||
fn: import { deleteMyself } from "@server/operations.js",
|
||||
entities: [User, Project, File, Log]
|
||||
}
|
||||
|
||||
query getFeedback {
|
||||
fn: import { getFeedback } from "@server/operations.js",
|
||||
entities: [Feedback]
|
||||
@ -155,7 +160,7 @@ entity SocialLogin {=psl
|
||||
providerId String
|
||||
|
||||
userId Int
|
||||
user User @relation(fields: [userId], references: [id])
|
||||
user User @relation(fields: [userId], references: [id], onDelete: Cascade)
|
||||
createdAt DateTime @default(now())
|
||||
psl=}
|
||||
|
||||
|
@ -0,0 +1,5 @@
|
||||
-- DropForeignKey
|
||||
ALTER TABLE "SocialLogin" DROP CONSTRAINT "SocialLogin_userId_fkey";
|
||||
|
||||
-- AddForeignKey
|
||||
ALTER TABLE "SocialLogin" ADD CONSTRAINT "SocialLogin_userId_fkey" FOREIGN KEY ("userId") REFERENCES "User"("id") ON DELETE CASCADE ON UPDATE CASCADE;
|
@ -49,9 +49,9 @@ export function MyDialog({ isOpen, onClose, title, children }) {
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
>
|
||||
<path
|
||||
fill-rule="evenodd"
|
||||
fillRule="evenodd"
|
||||
d="M4.293 4.293a1 1 0 011.414 0L10 8.586l4.293-4.293a1 1 0 111.414 1.414L11.414 10l4.293 4.293a1 1 0 01-1.414 1.414L10 11.414l-4.293 4.293a1 1 0 01-1.414-1.414L8.586 10 4.293 5.707a1 1 0 010-1.414z"
|
||||
clip-rule="evenodd"
|
||||
clipRule="evenodd"
|
||||
></path>
|
||||
</svg>
|
||||
<span className="sr-only">Close modal</span>
|
||||
|
@ -7,6 +7,7 @@ export function StatusPill({ children, status, className = "", sm = false }) {
|
||||
error: "bg-red-100 text-red-700",
|
||||
cancelled: "bg-red-100 text-red-700",
|
||||
warning: "bg-yellow-100 text-yellow-700",
|
||||
deleted: "bg-red-100 text-red-700",
|
||||
};
|
||||
return (
|
||||
<div className={`flex items-center ${className}`}>
|
||||
|
@ -77,7 +77,7 @@ export function WaitingRoomContent(props) {
|
||||
|
||||
<div className="grid grid-cols-1 gap-2 lg:grid-cols-2 lg:gap-4">
|
||||
{showcaseSamples.map((sample) => (
|
||||
<ShowcaseCard {...sample} />
|
||||
<ShowcaseCard key={sample.name} {...sample} />
|
||||
))}
|
||||
</div>
|
||||
</>
|
||||
|
@ -42,7 +42,11 @@ export const ResultPage = () => {
|
||||
data: appGenerationResult,
|
||||
isError,
|
||||
isLoading,
|
||||
} = useQuery(getAppGenerationResult, { appId }, { enabled: !!appId && !generationDone, refetchInterval: 3000 });
|
||||
} = useQuery(
|
||||
getAppGenerationResult,
|
||||
{ appId },
|
||||
{ enabled: !!appId && !generationDone, refetchInterval: 3000 }
|
||||
);
|
||||
const [activeFilePath, setActiveFilePath] = useState(null);
|
||||
const [currentStatus, setCurrentStatus] = useState({
|
||||
status: "idle",
|
||||
@ -211,7 +215,10 @@ export const ResultPage = () => {
|
||||
|
||||
return (
|
||||
<div className="container">
|
||||
<Header currentStatus={currentStatus} StatusPill={!!appGenerationResult?.project && StatusPill}>
|
||||
<Header
|
||||
currentStatus={currentStatus}
|
||||
StatusPill={!!appGenerationResult?.project && StatusPill}
|
||||
>
|
||||
<FaqButton />
|
||||
<HomeButton />
|
||||
<ProfileButton />
|
||||
@ -225,7 +232,8 @@ export const ResultPage = () => {
|
||||
{isError && (
|
||||
<div className="mb-4 bg-red-50 p-8 rounded-xl">
|
||||
<div className="text-red-500">
|
||||
We couldn't find the app generation result. Maybe the link is incorrect or the app generation has failed.
|
||||
We couldn't find the app generation result. Maybe the link is incorrect or the app
|
||||
generation has failed.
|
||||
</div>
|
||||
<Link className="button gray sm mt-4 inline-block" to="/">
|
||||
Generate a new one
|
||||
@ -244,7 +252,16 @@ export const ResultPage = () => {
|
||||
</>
|
||||
)}
|
||||
|
||||
<Logs logs={logs} status={currentStatus.status} onRetry={retry} />
|
||||
{appGenerationResult?.project.status.includes("deleted") ? (
|
||||
<div className="flex flex-col items-center justify-center gap-1 mb-4 bg-red-50 text-gray-700 p-8 rounded-xl">
|
||||
<span>This app has been deleted. </span>
|
||||
<Link className="underline sm inline-block" to="/">
|
||||
← Go back and generate a new app
|
||||
</Link>
|
||||
</div>
|
||||
) : (
|
||||
<Logs logs={logs} status={currentStatus.status} onRetry={retry} />
|
||||
)}
|
||||
|
||||
<div
|
||||
className="overflow-hidden
|
||||
@ -264,31 +281,45 @@ export const ResultPage = () => {
|
||||
onClick={() => window.open("https://github.com/wasp-lang/wasp/tree/wasp-ai")}
|
||||
>
|
||||
<span>
|
||||
🔮 This is a Wasp powered project. If you like it, <span className="underline">star us on GitHub</span>!
|
||||
🔮 This is a Wasp powered project. If you like it,{" "}
|
||||
<span className="underline">star us on GitHub</span>!
|
||||
</span>
|
||||
</span>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{currentStatus.status === "pending" && (
|
||||
<WaitingRoomContent numberOfProjectsAheadInQueue={appGenerationResult?.numberOfProjectsAheadInQueue || 0} />
|
||||
<WaitingRoomContent
|
||||
numberOfProjectsAheadInQueue={appGenerationResult?.numberOfProjectsAheadInQueue || 0}
|
||||
/>
|
||||
)}
|
||||
|
||||
{interestingFilePaths.length > 0 && (
|
||||
<>
|
||||
<div className="mb-2 flex items-center justify-between">
|
||||
<h2 className="text-xl font-bold text-gray-800">{appGenerationResult?.project?.name}</h2>
|
||||
<h2 className="text-xl font-bold text-gray-800">
|
||||
{appGenerationResult?.project?.name}
|
||||
</h2>
|
||||
</div>
|
||||
<button className="button gray block w-full mb-4 md:hidden" onClick={toggleMobileFileBrowser}>
|
||||
{isMobileFileBrowserOpen ? "Close" : "Open"} file browser ({interestingFilePaths.length} files)
|
||||
<button
|
||||
className="button gray block w-full mb-4 md:hidden"
|
||||
onClick={toggleMobileFileBrowser}
|
||||
>
|
||||
{isMobileFileBrowserOpen ? "Close" : "Open"} file browser ({interestingFilePaths.length}{" "}
|
||||
files)
|
||||
</button>
|
||||
<div className="grid gap-4 md:grid-cols-[320px_1fr] mt-4 overflow-x-auto md:overflow-x-visible">
|
||||
<aside className={isMobileFileBrowserOpen ? "" : "hidden md:block"}>
|
||||
<div className="mb-2">
|
||||
<RunTheAppModal onDownloadZip={downloadZip} disabled={currentStatus.status !== "success"} />
|
||||
<RunTheAppModal
|
||||
onDownloadZip={downloadZip}
|
||||
disabled={currentStatus.status !== "success"}
|
||||
/>
|
||||
</div>
|
||||
{currentStatus.status !== "success" && (
|
||||
<small className="text-gray-500 text-center block my-2">The app is still being generated.</small>
|
||||
<small className="text-gray-500 text-center block my-2">
|
||||
The app is still being generated.
|
||||
</small>
|
||||
)}
|
||||
<div>
|
||||
<ShareButton />
|
||||
@ -353,6 +384,7 @@ function getStatusPillData(generationResult) {
|
||||
success: "success",
|
||||
failure: "error",
|
||||
cancelled: "cancelled",
|
||||
deleted: "deleted"
|
||||
};
|
||||
|
||||
const queueCardinalNumber = getCardinalNumber(generationResult.numberOfProjectsAheadInQueue);
|
||||
@ -363,6 +395,7 @@ function getStatusPillData(generationResult) {
|
||||
success: "Finished",
|
||||
failure: "There was an error",
|
||||
cancelled: "The generation was cancelled",
|
||||
deleted: "The project was deleted"
|
||||
};
|
||||
|
||||
return {
|
||||
@ -392,12 +425,16 @@ export function OnSuccessModal({ isOpen, setIsOpen, appGenerationResult }) {
|
||||
const logText = appGenerationResult?.project?.logs?.find((log) =>
|
||||
log.content.includes("tokens usage")
|
||||
)?.content;
|
||||
const regex = /total tokens usage: ~(\d+(\.\d+)?)/i;
|
||||
const match = logText?.match(regex);
|
||||
if (match && match[1]) {
|
||||
const num = Number(match[1]) * 1000;
|
||||
if (Number.isInteger(num)) {
|
||||
setNumTokensSpent(num);
|
||||
|
||||
if (logText) {
|
||||
const regex = /Total\s+tokens\s+usage\s*:\s*~\s*(\d+(?:\.\d+){0,1})\s*k\b/;
|
||||
const match = logText.match(regex);
|
||||
|
||||
if (match) {
|
||||
const num = parseFloat(match[1]);
|
||||
setNumTokensSpent(num * 1000);
|
||||
} else {
|
||||
console.log("Failed to parse total number of tokens used: no regex match.");
|
||||
}
|
||||
}
|
||||
}, [appGenerationResult]);
|
||||
@ -416,10 +453,15 @@ export function OnSuccessModal({ isOpen, setIsOpen, appGenerationResult }) {
|
||||
}
|
||||
|
||||
return (
|
||||
<MyDialog isOpen={isOpen} onClose={() => setIsOpen(false)} title={<span>Your App is Ready! 🎉</span>}>
|
||||
<MyDialog
|
||||
isOpen={isOpen}
|
||||
onClose={() => setIsOpen(false)}
|
||||
title={<span>Your App is Ready! 🎉</span>}
|
||||
>
|
||||
<div className="mt-6 space-y-5">
|
||||
<p className="text-base leading-relaxed text-gray-500">
|
||||
We've made this tool completely <span className="font-semibold">free</span> and cover all the costs 😇
|
||||
We've made this tool completely <span className="font-semibold">free</span> and cover all
|
||||
the costs 😇
|
||||
</p>
|
||||
{numTokensSpent > 0 && (
|
||||
<table className="bg-slate-50 rounded-lg divide-y divide-gray-100 w-full text-base leading-relaxed text-gray-500 text-sm">
|
||||
@ -435,7 +477,9 @@ export function OnSuccessModal({ isOpen, setIsOpen, appGenerationResult }) {
|
||||
<td className="p-2 text-gray-600"> Cost to generate your app: </td>
|
||||
<td className="p-2 text-gray-600">
|
||||
{" "}
|
||||
<FormattedText>{`$${((Number(numTokensSpent) / 1000) * 0.004).toFixed(2)}`}</FormattedText>{" "}
|
||||
<FormattedText>{`$${((Number(numTokensSpent) / 1000) * 0.004).toFixed(
|
||||
2
|
||||
)}`}</FormattedText>{" "}
|
||||
</td>
|
||||
</tr>
|
||||
{numTotalProjects && (
|
||||
@ -477,7 +521,9 @@ export default function RunTheAppModal({ disabled, onDownloadZip }) {
|
||||
return (
|
||||
<>
|
||||
<button
|
||||
className={`button flex items-center justify-center gap-1 w-full${!disabled ? " animate-jumping" : ""}`}
|
||||
className={`button flex items-center justify-center gap-1 w-full${
|
||||
!disabled ? " animate-jumping" : ""
|
||||
}`}
|
||||
disabled={disabled}
|
||||
onClick={() => setShowModal(true)}
|
||||
>
|
||||
@ -497,11 +543,16 @@ export default function RunTheAppModal({ disabled, onDownloadZip }) {
|
||||
Congrats, your full-stack web app is ready! 🎉
|
||||
<br />
|
||||
App is implemented in{" "}
|
||||
<a href="https://wasp-lang.dev" target="_blank" rel="noopener noreferrer" className="underline">
|
||||
<a
|
||||
href="https://wasp-lang.dev"
|
||||
target="_blank"
|
||||
rel="noopener noreferrer"
|
||||
className="underline"
|
||||
>
|
||||
Wasp
|
||||
</a>{" "}
|
||||
web framework, using React, Node.js and Prisma, and is completely full-stack (frontend + backend +
|
||||
database).
|
||||
web framework, using React, Node.js and Prisma, and is completely full-stack (frontend +
|
||||
backend + database).
|
||||
</p>
|
||||
|
||||
<WarningAboutAI />
|
||||
@ -511,7 +562,11 @@ export default function RunTheAppModal({ disabled, onDownloadZip }) {
|
||||
<div className="mt-6 bg-slate-100 rounded-lg p-4 text-base text-slate-800">
|
||||
<h2 className="font-bold flex items-center space-x-1">
|
||||
<span>1. Install Wasp CLI</span>
|
||||
<a href="https://wasp-lang.dev/docs/quick-start#installation-1" target="blank" rel="noopener noreferrer">
|
||||
<a
|
||||
href="https://wasp-lang.dev/docs/quick-start#installation-1"
|
||||
target="blank"
|
||||
rel="noopener noreferrer"
|
||||
>
|
||||
{" "}
|
||||
<RxQuestionMarkCircled className="text-base" />{" "}
|
||||
</a>
|
||||
@ -521,8 +576,14 @@ export default function RunTheAppModal({ disabled, onDownloadZip }) {
|
||||
curl -sSL https://get.wasp-lang.dev/installer.sh | sh
|
||||
</pre>
|
||||
|
||||
<h2 className="font-bold mt-4"> 2. Download the generated app files and unzip them: </h2>
|
||||
<button className="button flex items-center justify-center gap-1 w-full mt-2" onClick={onDownloadZip}>
|
||||
<h2 className="font-bold mt-4">
|
||||
{" "}
|
||||
2. Download the generated app files and unzip them:{" "}
|
||||
</h2>
|
||||
<button
|
||||
className="button flex items-center justify-center gap-1 w-full mt-2"
|
||||
onClick={onDownloadZip}
|
||||
>
|
||||
Download ZIP <PiDownloadDuotone className="inline-block" size={20} />
|
||||
</button>
|
||||
|
||||
@ -536,11 +597,17 @@ export default function RunTheAppModal({ disabled, onDownloadZip }) {
|
||||
</pre>
|
||||
</div>
|
||||
|
||||
<p className="text-base leading-relaxed text-gray-500">Congratulations, you are now running your app! 🎉</p>
|
||||
<p className="text-base leading-relaxed text-gray-500">
|
||||
Congratulations, you are now running your app! 🎉
|
||||
</p>
|
||||
|
||||
<div className="bg-pink-50 text-pink-800 p-4 rounded">
|
||||
If you like this project,{" "}
|
||||
<a href="https://github.com/wasp-lang/wasp" target="_blank" className="underline text-pink-600">
|
||||
<a
|
||||
href="https://github.com/wasp-lang/wasp"
|
||||
target="_blank"
|
||||
className="underline text-pink-600"
|
||||
>
|
||||
star us on GitHub
|
||||
</a>{" "}
|
||||
⭐️
|
||||
@ -559,8 +626,8 @@ function WarningAboutAI() {
|
||||
<p className="text-sm leading-5 font-medium">⚠️ Experimental tech</p>
|
||||
<div className="mt-2 text-sm leading-5">
|
||||
<p>
|
||||
Since this is a GPT generated app, it might contain some mistakes, proportional to how complex the app is.
|
||||
If there are some in your app, check out{" "}
|
||||
Since this is a GPT generated app, it might contain some mistakes, proportional to how
|
||||
complex the app is. If there are some in your app, check out{" "}
|
||||
<a
|
||||
href="https://wasp-lang.dev/docs"
|
||||
target="_blank"
|
||||
@ -578,8 +645,8 @@ function WarningAboutAI() {
|
||||
>
|
||||
Discord
|
||||
</a>
|
||||
! You can also try generating the app again to get different results (try playing with the creativity
|
||||
level).
|
||||
! You can also try generating the app again to get different results (try playing with
|
||||
the creativity level).
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
@ -632,17 +699,22 @@ function Feedback({ projectId }) {
|
||||
>
|
||||
<form onSubmit={handleSubmit}>
|
||||
<label className="text-slate-700 block mb-2 mt-8">
|
||||
How likely are you to recommend this tool to a friend? <span className="text-red-500">*</span>
|
||||
How likely are you to recommend this tool to a friend?{" "}
|
||||
<span className="text-red-500">*</span>
|
||||
</label>
|
||||
<div className="mx-auto w-full max-w-md">
|
||||
<RadioGroup value={score} onChange={setScore}>
|
||||
<div className="flex space-x-2">
|
||||
{scoreOptions.map((option) => (
|
||||
<RadioGroup.Option value={option}>
|
||||
<RadioGroup.Option key={option} value={option}>
|
||||
{({ active, checked }) => (
|
||||
<div
|
||||
className={`
|
||||
${active ? "ring-2 ring-white ring-opacity-60 ring-offset-2 ring-offset-sky-300" : ""}
|
||||
${
|
||||
active
|
||||
? "ring-2 ring-white ring-opacity-60 ring-offset-2 ring-offset-sky-300"
|
||||
: ""
|
||||
}
|
||||
|
||||
|
||||
${checked ? "bg-sky-900 bg-opacity-75 text-white" : ""}
|
||||
|
@ -1,20 +1,27 @@
|
||||
import { useState } from "react";
|
||||
import { useQuery } from "@wasp/queries";
|
||||
import getProjectsByUser from "@wasp/queries/getProjectsByUser";
|
||||
import { Link } from "@wasp/router";
|
||||
import { Color } from "../components/Color";
|
||||
import { Header } from "../components/Header";
|
||||
import { PiDownloadDuotone } from "react-icons/pi";
|
||||
import { PiDownloadDuotone, PiSealWarningDuotone } from "react-icons/pi";
|
||||
import logout from "@wasp/auth/logout";
|
||||
import { FiLogOut } from "react-icons/fi";
|
||||
import { format } from "timeago.js";
|
||||
import { StatusPill } from "../components/StatusPill";
|
||||
import { getTailwindClassNameForProjectBrandColor, getTailwindClassNameForProjectStatus, projectStatusToDisplayableText } from "../project/utils";
|
||||
import {
|
||||
getTailwindClassNameForProjectBrandColor,
|
||||
getTailwindClassNameForProjectStatus,
|
||||
projectStatusToDisplayableText,
|
||||
} from "../project/utils";
|
||||
import { HomeButton } from "../components/Header";
|
||||
import deleteMyself from "@wasp/actions/deleteMyself";
|
||||
import { MyDialog } from "../components/Dialog";
|
||||
|
||||
export function UserPage({ user }) {
|
||||
const [isDeleteUserModalOpen, setIsDeleteUserModalOpen] = useState(false);
|
||||
const { data: projects, isLoading, error } = useQuery(getProjectsByUser);
|
||||
|
||||
if (isLoading) return "Loading Projects...";
|
||||
if (error) return "Error loading projects.";
|
||||
|
||||
return (
|
||||
@ -22,6 +29,13 @@ export function UserPage({ user }) {
|
||||
<Header>
|
||||
<HomeButton />
|
||||
</Header>
|
||||
|
||||
<DeleteUserModal
|
||||
isOpen={isDeleteUserModalOpen}
|
||||
setIsOpen={setIsDeleteUserModalOpen}
|
||||
deleteUser={deleteMyself}
|
||||
/>
|
||||
|
||||
<div className="big-box">
|
||||
<div className="flex items-center justify-between pb-6 pl-1">
|
||||
<p className="text-gray-700 mr-2 whitespace-nowrap">
|
||||
@ -34,9 +48,18 @@ export function UserPage({ user }) {
|
||||
</div>
|
||||
</button>
|
||||
</div>
|
||||
<div className="sm:rounded-lg shadow-md overflow-x-auto ">
|
||||
<UserProjectsTable projects={projects} />
|
||||
</div>
|
||||
{isLoading ? (
|
||||
"Loading..."
|
||||
) : (
|
||||
<div className="sm:rounded-lg shadow-md overflow-x-auto ">
|
||||
<UserProjectsTable projects={projects} />
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
<div className="flex justify-end pt-8 px-10">
|
||||
<button onClick={() => setIsDeleteUserModalOpen(true)} className="text-xs text-gray-500 hover:underline">
|
||||
*I want to delete my account.
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
@ -63,7 +86,7 @@ function UserProjectsTable({ projects }) {
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
{projects?.length > 0 &&
|
||||
{!!projects && projects?.length > 0 ? (
|
||||
projects.map((project) => (
|
||||
<tr className="bg-white border-t" key={project.id}>
|
||||
<th scope="row" className="px-6 py-4 font-medium text-gray-900 whitespace-nowrap flex items-center gap-2">
|
||||
@ -100,8 +123,54 @@ function UserProjectsTable({ projects }) {
|
||||
</Link>
|
||||
</td>
|
||||
</tr>
|
||||
))}
|
||||
))
|
||||
) : (
|
||||
<tr className="bg-white border-t">
|
||||
<td colSpan={5} className="text-center py-4">
|
||||
you have not generated any apps yet.
|
||||
</td>
|
||||
</tr>
|
||||
)}
|
||||
</tbody>
|
||||
</table>
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
function DeleteUserModal({ isOpen, setIsOpen, deleteUser }) {
|
||||
async function deleteUserHandler() {
|
||||
await deleteUser();
|
||||
logout();
|
||||
}
|
||||
|
||||
return (
|
||||
<MyDialog
|
||||
isOpen={isOpen}
|
||||
onClose={() => setIsOpen(false)}
|
||||
title={
|
||||
<div className="flex items-center gap-2">
|
||||
<PiSealWarningDuotone arie-hidden="true" /> Are You Sure You Want to Delete Your Account?
|
||||
</div>
|
||||
}
|
||||
>
|
||||
<div className="mt-10 space-y-10">
|
||||
<p className="px-8 text-base leading-relaxed text-center text-gray-500">
|
||||
You will lose access to your current projects and data.
|
||||
</p>
|
||||
<div className="flex items-center justify-between">
|
||||
<button
|
||||
className="px-4 py-2 text-base font-medium text-white bg-red-600 border border-transparent rounded-md shadow-sm hover:bg-red-700 focus:outline-none focus:ring-2 focus:ring-offset-2 focus:ring-red-500"
|
||||
onClick={deleteUserHandler}
|
||||
>
|
||||
Delete Account
|
||||
</button>
|
||||
<button
|
||||
className="px-4 py-2 text-base font-medium text-gray-700 bg-gray-100 border border-transparent rounded-md shadow-sm hover:bg-gray-200 focus:outline-none focus:ring-2 focus:ring-offset-2 focus:ring-gray-500"
|
||||
onClick={() => setIsOpen(false)}
|
||||
>
|
||||
Cancel
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
</MyDialog>
|
||||
);
|
||||
}
|
||||
|
@ -1,9 +1,20 @@
|
||||
import { RegisterZipDownload, StartGeneratingNewApp, CreateFeedback } from "@wasp/actions/types";
|
||||
import { GetAppGenerationResult, GetStats, GetFeedback, GetNumProjects, GetProjectsByUser } from "@wasp/queries/types";
|
||||
import {
|
||||
RegisterZipDownload,
|
||||
StartGeneratingNewApp,
|
||||
CreateFeedback,
|
||||
DeleteMyself,
|
||||
} from "@wasp/actions/types";
|
||||
import {
|
||||
GetAppGenerationResult,
|
||||
GetStats,
|
||||
GetFeedback,
|
||||
GetNumProjects,
|
||||
GetProjectsByUser,
|
||||
} from "@wasp/queries/types";
|
||||
import HttpError from "@wasp/core/HttpError.js";
|
||||
import { checkPendingAppsJob } from "@wasp/jobs/checkPendingAppsJob.js";
|
||||
import { getNowInUTC } from "./utils.js";
|
||||
import type { Project } from "@wasp/entities";
|
||||
import type { Project, User } from "@wasp/entities";
|
||||
|
||||
export const startGeneratingNewApp: StartGeneratingNewApp<
|
||||
{
|
||||
@ -206,10 +217,10 @@ export const getStats = (async (_args, context) => {
|
||||
zipDownloadedAt: true,
|
||||
user: {
|
||||
select: {
|
||||
email: true
|
||||
}
|
||||
}
|
||||
}
|
||||
email: true,
|
||||
},
|
||||
},
|
||||
},
|
||||
});
|
||||
|
||||
return {
|
||||
@ -237,3 +248,50 @@ export const getProjectsByUser: GetProjectsByUser<void, Project[]> = async (_arg
|
||||
},
|
||||
});
|
||||
};
|
||||
|
||||
export const deleteMyself: DeleteMyself<void, User> = async (args, context) => {
|
||||
if (!context.user) {
|
||||
throw new HttpError(401, "Not authorized");
|
||||
}
|
||||
try {
|
||||
await context.entities.Log.deleteMany({
|
||||
where: {
|
||||
project: {
|
||||
user: {
|
||||
id: context.user.id,
|
||||
},
|
||||
},
|
||||
},
|
||||
});
|
||||
await context.entities.File.deleteMany({
|
||||
where: {
|
||||
project: {
|
||||
user: {
|
||||
id: context.user.id,
|
||||
},
|
||||
},
|
||||
},
|
||||
});
|
||||
await context.entities.Project.updateMany({
|
||||
where: {
|
||||
user: {
|
||||
id: context.user.id,
|
||||
},
|
||||
},
|
||||
data: {
|
||||
zipDownloadedAt: undefined,
|
||||
name: "Deleted project",
|
||||
description: "Deleted project",
|
||||
status: "deleted",
|
||||
},
|
||||
});
|
||||
return await context.entities.User.delete({
|
||||
where: {
|
||||
id: context.user.id,
|
||||
},
|
||||
});
|
||||
} catch (error) {
|
||||
console.error(error);
|
||||
throw new HttpError(500, "Error deleting user");
|
||||
}
|
||||
};
|
||||
|
Loading…
Reference in New Issue
Block a user