Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

[MAGE] add delete user option #1577

Merged
merged 4 commits into from
Nov 23, 2023
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
6 changes: 6 additions & 0 deletions wasp-ai/.prettierrc
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
{
"semi": true,
"singleQuote": false,
"trailingComma": "es5",
"printWidth": 100
}
7 changes: 6 additions & 1 deletion wasp-ai/main.wasp
Original file line number Diff line number Diff line change
Expand Up @@ -106,6 +106,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]
Expand Down Expand Up @@ -154,7 +159,7 @@ entity SocialLogin {=psl
providerId String

userId Int
user User @relation(fields: [userId], references: [id])
user User @relation(fields: [userId], references: [id], onDelete: Cascade)
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Thought: once projects are deleted, we will never know they existed, so our number of created projects will become smaller. Would be cool if they only thing that is left is this idea of "there was a project that was deleted". But to be honest I am not sure myself how to implement that simply, so probably best to just ignore it, doesn't matter.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

this isnt for projects. when the user is deleted it deletes the social login entity, but leaves their projects in the DB.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think it's fine to leave the generated apps in the database in case they've been shared. there is no connection whatsoever to the user that created it.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think it would still be good if they can delete their projects, since they were authored by them.

I would suggest on of the following:

  1. Delete all the project they created, and that is it, simple.
  2. Give them choice -> they can delete also the projects, or they can leave them.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

ok I've gone ahead and deleted all user-relevant info leaving just the project shell :)

createdAt DateTime @default(now())
psl=}

Expand Down
5 changes: 5 additions & 0 deletions wasp-ai/migrations/20231117102034_delete_user/migration.sql
Original file line number Diff line number Diff line change
@@ -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;
4 changes: 2 additions & 2 deletions wasp-ai/src/client/components/Dialog.jsx
Original file line number Diff line number Diff line change
Expand Up @@ -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>
Expand Down
1 change: 1 addition & 0 deletions wasp-ai/src/client/components/StatusPill.jsx
Original file line number Diff line number Diff line change
Expand Up @@ -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}`}>
Expand Down
2 changes: 1 addition & 1 deletion wasp-ai/src/client/components/WaitingRoomContent.jsx
Original file line number Diff line number Diff line change
Expand Up @@ -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>
</>
Expand Down
144 changes: 108 additions & 36 deletions wasp-ai/src/client/pages/ResultPage.jsx
Original file line number Diff line number Diff line change
Expand Up @@ -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",
Expand Down Expand Up @@ -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 />
Expand All @@ -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
Expand All @@ -244,7 +252,16 @@ export const ResultPage = () => {
</>
)}

<Logs logs={logs} status={currentStatus.status} onRetry={retry} />
{appGenerationResult?.project.status.includes("deleted") ? (
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This "deleted" here, would be great to have it imported from somewhere, so we don't have duplication. But this project in general is not very careful about this, so if all the other statuses are handled badly like this then we can postpone this refactoring to be refactored all together.

<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="/">
&#x2190; Go back and generate a new app
</Link>
</div>
) : (
<Logs logs={logs} status={currentStatus.status} onRetry={retry} />
)}

<div
className="overflow-hidden
Expand All @@ -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 />
Expand Down Expand Up @@ -353,6 +384,7 @@ function getStatusPillData(generationResult) {
success: "success",
failure: "error",
cancelled: "cancelled",
deleted: "deleted"
};

const queueCardinalNumber = getCardinalNumber(generationResult.numberOfProjectsAheadInQueue);
Expand All @@ -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 {
Expand Down Expand Up @@ -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]);
Expand All @@ -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">
Expand All @@ -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 && (
Expand Down Expand Up @@ -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)}
>
Expand All @@ -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 />
Expand All @@ -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>
Expand All @@ -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>

Expand All @@ -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>{" "}
⭐️
Expand All @@ -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"
Expand All @@ -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>
Expand Down Expand Up @@ -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" : ""}
Expand Down
Loading
Loading