From ef3be90ec29bef66141c02ff42370c887c1f8630 Mon Sep 17 00:00:00 2001
From: vincanger <70215737+vincanger@users.noreply.github.com>
Date: Fri, 17 Nov 2023 12:44:13 +0100
Subject: [PATCH 1/4] add delete user functionality
---
wasp-ai/.prettierrc | 6 ++
wasp-ai/main.wasp | 7 +-
.../20231117102034_delete_user/migration.sql | 5 ++
wasp-ai/src/client/components/Dialog.jsx | 4 +-
.../client/components/WaitingRoomContent.jsx | 2 +-
wasp-ai/src/client/pages/ResultPage.jsx | 19 ++--
wasp-ai/src/client/pages/UserPage.jsx | 88 +++++++++++++++++--
wasp-ai/src/server/operations.ts | 24 +++--
8 files changed, 129 insertions(+), 26 deletions(-)
create mode 100644 wasp-ai/.prettierrc
create mode 100644 wasp-ai/migrations/20231117102034_delete_user/migration.sql
diff --git a/wasp-ai/.prettierrc b/wasp-ai/.prettierrc
new file mode 100644
index 0000000000..5bc7e9e888
--- /dev/null
+++ b/wasp-ai/.prettierrc
@@ -0,0 +1,6 @@
+{
+ "semi": true,
+ "singleQuote": false,
+ "trailingComma": "es5",
+ "printWidth": 120
+}
diff --git a/wasp-ai/main.wasp b/wasp-ai/main.wasp
index 78646385a6..db588d6ac6 100644
--- a/wasp-ai/main.wasp
+++ b/wasp-ai/main.wasp
@@ -106,6 +106,11 @@ action createFeedback {
entities: [Feedback]
}
+action deleteUser {
+ fn: import { deleteUser } from "@server/operations.js",
+ entities: [User]
+}
+
query getFeedback {
fn: import { getFeedback } from "@server/operations.js",
entities: [Feedback]
@@ -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)
createdAt DateTime @default(now())
psl=}
diff --git a/wasp-ai/migrations/20231117102034_delete_user/migration.sql b/wasp-ai/migrations/20231117102034_delete_user/migration.sql
new file mode 100644
index 0000000000..7f4a42b4b8
--- /dev/null
+++ b/wasp-ai/migrations/20231117102034_delete_user/migration.sql
@@ -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;
diff --git a/wasp-ai/src/client/components/Dialog.jsx b/wasp-ai/src/client/components/Dialog.jsx
index f6e0b7b1f2..c7142000c0 100644
--- a/wasp-ai/src/client/components/Dialog.jsx
+++ b/wasp-ai/src/client/components/Dialog.jsx
@@ -49,9 +49,9 @@ export function MyDialog({ isOpen, onClose, title, children }) {
xmlns="http://www.w3.org/2000/svg"
>
Close modal
diff --git a/wasp-ai/src/client/components/WaitingRoomContent.jsx b/wasp-ai/src/client/components/WaitingRoomContent.jsx
index 717e5076c9..ba4e566c43 100644
--- a/wasp-ai/src/client/components/WaitingRoomContent.jsx
+++ b/wasp-ai/src/client/components/WaitingRoomContent.jsx
@@ -77,7 +77,7 @@ export function WaitingRoomContent(props) {
{showcaseSamples.map((sample) => (
-
+
))}
>
diff --git a/wasp-ai/src/client/pages/ResultPage.jsx b/wasp-ai/src/client/pages/ResultPage.jsx
index fc855b4c3d..9272c47e6e 100644
--- a/wasp-ai/src/client/pages/ResultPage.jsx
+++ b/wasp-ai/src/client/pages/ResultPage.jsx
@@ -392,14 +392,19 @@ 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('No match found');
}
}
+
}, [appGenerationResult]);
useEffect(() => {
@@ -638,7 +643,7 @@ function Feedback({ projectId }) {
{scoreOptions.map((option) => (
-
+
{({ active, checked }) => (
+
+
+
@@ -34,9 +49,18 @@ export function UserPage({ user }) {
-
-
-
+ {isLoading ? (
+ "Loading..."
+ ) : (
+
+
+
+ )}
+
+
+ setIsDeleteUserModalOpen(true)} className="text-xs text-gray-500 hover:underline">
+ *I want to delete my account.
+
);
@@ -63,7 +87,7 @@ function UserProjectsTable({ projects }) {
- {projects?.length > 0 &&
+ {!!projects && projects?.length > 0 ? (
projects.map((project) => (
@@ -100,8 +124,54 @@ function UserProjectsTable({ projects }) {
- ))}
+ ))
+ ) : (
+
+
+ you have not generated any apps yet.
+
+
+ )}
);
-}
\ No newline at end of file
+}
+
+function DeleteUserModal({ isOpen, setIsOpen, deleteUser, user }) {
+ async function deleteUserHandler() {
+ await deleteUser({ userId: user.id });
+ logout();
+ }
+
+ return (
+ setIsOpen(false)}
+ title={
+
+
Are You Sure You Want to Delete Your Account?
+
+ }
+ >
+
+
+ You will lose access to your current projects and data.
+
+
+
+ Delete Account
+
+ setIsOpen(false)}
+ >
+ Cancel
+
+
+
+
+ );
+}
diff --git a/wasp-ai/src/server/operations.ts b/wasp-ai/src/server/operations.ts
index 3ab2fdea1e..674114c502 100644
--- a/wasp-ai/src/server/operations.ts
+++ b/wasp-ai/src/server/operations.ts
@@ -1,9 +1,9 @@
-import { RegisterZipDownload, StartGeneratingNewApp, CreateFeedback } from "@wasp/actions/types";
+import { RegisterZipDownload, StartGeneratingNewApp, CreateFeedback, DeleteUser } 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 +206,10 @@ export const getStats = (async (_args, context) => {
zipDownloadedAt: true,
user: {
select: {
- email: true
- }
- }
- }
+ email: true,
+ },
+ },
+ },
});
return {
@@ -237,3 +237,15 @@ export const getProjectsByUser: GetProjectsByUser = async (_arg
},
});
};
+
+export const deleteUser: DeleteUser<{ userId: number }, User> = async (args, context) => {
+ if (!context.user || context.user.id !== args.userId) {
+ throw new HttpError(401, "Not authorized");
+ }
+
+ return await context.entities.User.delete({
+ where: {
+ id: args.userId,
+ },
+ });
+};
From 23640bda58cf663e57d943fa6cc0317e4d926351 Mon Sep 17 00:00:00 2001
From: vincanger <70215737+vincanger@users.noreply.github.com>
Date: Fri, 17 Nov 2023 15:35:56 +0100
Subject: [PATCH 2/4] refactor to deleteMyself
---
wasp-ai/.prettierrc | 2 +-
wasp-ai/main.wasp | 4 ++--
wasp-ai/src/client/pages/ResultPage.jsx | 2 +-
wasp-ai/src/client/pages/UserPage.jsx | 9 ++++-----
wasp-ai/src/server/operations.ts | 8 ++++----
5 files changed, 12 insertions(+), 13 deletions(-)
diff --git a/wasp-ai/.prettierrc b/wasp-ai/.prettierrc
index 5bc7e9e888..59e8c2d7a7 100644
--- a/wasp-ai/.prettierrc
+++ b/wasp-ai/.prettierrc
@@ -2,5 +2,5 @@
"semi": true,
"singleQuote": false,
"trailingComma": "es5",
- "printWidth": 120
+ "printWidth": 100
}
diff --git a/wasp-ai/main.wasp b/wasp-ai/main.wasp
index db588d6ac6..18ab868404 100644
--- a/wasp-ai/main.wasp
+++ b/wasp-ai/main.wasp
@@ -106,8 +106,8 @@ action createFeedback {
entities: [Feedback]
}
-action deleteUser {
- fn: import { deleteUser } from "@server/operations.js",
+action deleteMyself {
+ fn: import { deleteMyself } from "@server/operations.js",
entities: [User]
}
diff --git a/wasp-ai/src/client/pages/ResultPage.jsx b/wasp-ai/src/client/pages/ResultPage.jsx
index 9272c47e6e..e8850184fd 100644
--- a/wasp-ai/src/client/pages/ResultPage.jsx
+++ b/wasp-ai/src/client/pages/ResultPage.jsx
@@ -401,7 +401,7 @@ export function OnSuccessModal({ isOpen, setIsOpen, appGenerationResult }) {
const num = parseFloat(match[1]);
setNumTokensSpent(num * 1000);
} else {
- console.log('No match found');
+ console.log("Failed to parse total number of tokens used: no regex match.");
}
}
diff --git a/wasp-ai/src/client/pages/UserPage.jsx b/wasp-ai/src/client/pages/UserPage.jsx
index 38f8951af7..d25200045f 100644
--- a/wasp-ai/src/client/pages/UserPage.jsx
+++ b/wasp-ai/src/client/pages/UserPage.jsx
@@ -15,7 +15,7 @@ import {
projectStatusToDisplayableText,
} from "../project/utils";
import { HomeButton } from "../components/Header";
-import deleteUser from "@wasp/actions/deleteUser";
+import deleteMyself from "@wasp/actions/deleteMyself";
import { MyDialog } from "../components/Dialog";
export function UserPage({ user }) {
@@ -33,8 +33,7 @@ export function UserPage({ user }) {
@@ -137,9 +136,9 @@ function UserProjectsTable({ projects }) {
);
}
-function DeleteUserModal({ isOpen, setIsOpen, deleteUser, user }) {
+function DeleteUserModal({ isOpen, setIsOpen, deleteUser }) {
async function deleteUserHandler() {
- await deleteUser({ userId: user.id });
+ await deleteUser();
logout();
}
diff --git a/wasp-ai/src/server/operations.ts b/wasp-ai/src/server/operations.ts
index 674114c502..274ad4214b 100644
--- a/wasp-ai/src/server/operations.ts
+++ b/wasp-ai/src/server/operations.ts
@@ -1,4 +1,4 @@
-import { RegisterZipDownload, StartGeneratingNewApp, CreateFeedback, DeleteUser } from "@wasp/actions/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";
@@ -238,14 +238,14 @@ export const getProjectsByUser: GetProjectsByUser
= async (_arg
});
};
-export const deleteUser: DeleteUser<{ userId: number }, User> = async (args, context) => {
- if (!context.user || context.user.id !== args.userId) {
+export const deleteMyself: DeleteMyself = async (args, context) => {
+ if (!context.user) {
throw new HttpError(401, "Not authorized");
}
return await context.entities.User.delete({
where: {
- id: args.userId,
+ id: context.user.id,
},
});
};
From bd607e0adde299347d52f110b261dc2686b69653 Mon Sep 17 00:00:00 2001
From: vincanger <70215737+vincanger@users.noreply.github.com>
Date: Tue, 21 Nov 2023 14:23:39 +0100
Subject: [PATCH 3/4] delete user relevant info from projects
---
wasp-ai/main.wasp | 2 +-
wasp-ai/src/server/operations.ts | 61 +++++++++++++++++++++++++++-----
2 files changed, 54 insertions(+), 9 deletions(-)
diff --git a/wasp-ai/main.wasp b/wasp-ai/main.wasp
index 18ab868404..cb4cd80177 100644
--- a/wasp-ai/main.wasp
+++ b/wasp-ai/main.wasp
@@ -108,7 +108,7 @@ action createFeedback {
action deleteMyself {
fn: import { deleteMyself } from "@server/operations.js",
- entities: [User]
+ entities: [User, Project, File, Log]
}
query getFeedback {
diff --git a/wasp-ai/src/server/operations.ts b/wasp-ai/src/server/operations.ts
index 274ad4214b..70bb70e0ad 100644
--- a/wasp-ai/src/server/operations.ts
+++ b/wasp-ai/src/server/operations.ts
@@ -1,5 +1,16 @@
-import { RegisterZipDownload, StartGeneratingNewApp, CreateFeedback, DeleteMyself } 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";
@@ -242,10 +253,44 @@ export const deleteMyself: DeleteMyself = async (args, context) => {
if (!context.user) {
throw new HttpError(401, "Not authorized");
}
-
- return await context.entities.User.delete({
- where: {
- id: context.user.id,
- },
- });
+ 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",
+ },
+ });
+ return await context.entities.User.delete({
+ where: {
+ id: context.user.id,
+ },
+ });
+ } catch (error) {
+ console.error(error);
+ throw new HttpError(500, "Error deleting user");
+ }
};
From 6516c62b5d266123170fe2394b6b3f078a575580 Mon Sep 17 00:00:00 2001
From: vincanger <70215737+vincanger@users.noreply.github.com>
Date: Tue, 21 Nov 2023 15:07:19 +0100
Subject: [PATCH 4/4] update status to deleted
---
wasp-ai/src/client/components/StatusPill.jsx | 1 +
wasp-ai/src/client/pages/ResultPage.jsx | 129 ++++++++++++++-----
wasp-ai/src/server/operations.ts | 1 +
3 files changed, 100 insertions(+), 31 deletions(-)
diff --git a/wasp-ai/src/client/components/StatusPill.jsx b/wasp-ai/src/client/components/StatusPill.jsx
index fc8234a755..ab5e2d04a2 100644
--- a/wasp-ai/src/client/components/StatusPill.jsx
+++ b/wasp-ai/src/client/components/StatusPill.jsx
@@ -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 (
diff --git a/wasp-ai/src/client/pages/ResultPage.jsx b/wasp-ai/src/client/pages/ResultPage.jsx
index e8850184fd..c73d092179 100644
--- a/wasp-ai/src/client/pages/ResultPage.jsx
+++ b/wasp-ai/src/client/pages/ResultPage.jsx
@@ -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 (