From b373232888a63be24221513bd72fa2f7e79ef7ee Mon Sep 17 00:00:00 2001 From: DIYgod Date: Fri, 4 Jul 2025 18:16:44 +0800 Subject: [PATCH] feat(desktop): real user deletion --- apps/desktop/layer/renderer/src/lib/auth.ts | 1 + .../src/pages/settings/(settings)/profile.tsx | 12 +- .../layer/renderer/src/queries/auth.ts | 12 +- packages/internal/shared/src/auth.ts | 4 + packages/internal/shared/src/hono.ts | 418 +++++++++++++++++- 5 files changed, 420 insertions(+), 27 deletions(-) diff --git a/apps/desktop/layer/renderer/src/lib/auth.ts b/apps/desktop/layer/renderer/src/lib/auth.ts index 32f35dae3..ac6fc6b4f 100644 --- a/apps/desktop/layer/renderer/src/lib/auth.ts +++ b/apps/desktop/layer/renderer/src/lib/auth.ts @@ -19,6 +19,7 @@ export const { authClient } = auth export const { changeEmail, changePassword, + deleteUserCustom, forgetPassword, getAccountInfo, getProviders, diff --git a/apps/desktop/layer/renderer/src/pages/settings/(settings)/profile.tsx b/apps/desktop/layer/renderer/src/pages/settings/(settings)/profile.tsx index b6386854b..66df78868 100644 --- a/apps/desktop/layer/renderer/src/pages/settings/(settings)/profile.tsx +++ b/apps/desktop/layer/renderer/src/pages/settings/(settings)/profile.tsx @@ -6,12 +6,13 @@ import { useIsInMASReview } from "~/atoms/server-configs" import { useModalStack } from "~/components/ui/modal/stacked/hooks" import { AccountManagement } from "~/modules/profile/account-management" import { EmailManagement } from "~/modules/profile/email-management" +import { useTOTPModalWrapper } from "~/modules/profile/hooks" import { ProfileSettingForm } from "~/modules/profile/profile-setting-form" import { TwoFactor } from "~/modules/profile/two-factor" import { UpdatePasswordForm } from "~/modules/profile/update-password-form" import { SettingsTitle } from "~/modules/settings/title" import { defineSettingPageData } from "~/modules/settings/utils" -import { signOut } from "~/queries/auth" +import { deleteUser } from "~/queries/auth" const iconName = "i-mgc-user-setting-cute-re" const priority = (1000 << 3) + 10 @@ -24,6 +25,7 @@ export const loader = defineSettingPageData({ export function Component() { const { present } = useModalStack() const isInMASReview = useIsInMASReview() + const preset = useTOTPModalWrapper(deleteUser, { force: true }) return ( <> @@ -37,7 +39,6 @@ export function Component() { - {/* TODO: Temporary fake account deletion feature */} {isInMASReview && (
@@ -52,7 +53,12 @@ export function Component() { Are you sure you want to delete your account? This action is irreversible and may take up to two days to take effect.

-
diff --git a/apps/desktop/layer/renderer/src/queries/auth.ts b/apps/desktop/layer/renderer/src/queries/auth.ts index 90400110b..e71a7d880 100644 --- a/apps/desktop/layer/renderer/src/queries/auth.ts +++ b/apps/desktop/layer/renderer/src/queries/auth.ts @@ -7,7 +7,7 @@ import type { FetchError } from "ofetch" import { QUERY_PERSIST_KEY } from "~/constants" import { useAuthQuery } from "~/hooks/common" -import { getAccountInfo, signOut as signOutFn } from "~/lib/auth" +import { deleteUserCustom as deleteUserFn, getAccountInfo, signOut as signOutFn } from "~/lib/auth" import { ipcServices } from "~/lib/client" import { defineQuery } from "~/lib/defineQuery" import { clearLocalPersistStoreData } from "~/store/utils/clear" @@ -94,3 +94,13 @@ export const signOut = async () => { await signOutFn() window.location.reload() } + +export const deleteUser = async ({ TOTPCode }: { TOTPCode?: string }) => { + if (!TOTPCode) { + return + } + await deleteUserFn({ + TOTPCode, + }) + await signOut() +} diff --git a/packages/internal/shared/src/auth.ts b/packages/internal/shared/src/auth.ts index 63968d821..da4d26548 100644 --- a/packages/internal/shared/src/auth.ts +++ b/packages/internal/shared/src/auth.ts @@ -14,6 +14,10 @@ export const baseAuthPlugins = [ id: "getAccountInfo", $InferServerPlugin: {} as Extract, }, + { + id: "deleteUserCustom", + $InferServerPlugin: {} as Extract, + }, { id: "oneTimeToken", $InferServerPlugin: {} as Extract, diff --git a/packages/internal/shared/src/hono.ts b/packages/internal/shared/src/hono.ts index 73fc67b21..ab141adda 100644 --- a/packages/internal/shared/src/hono.ts +++ b/packages/internal/shared/src/hono.ts @@ -2,8 +2,11 @@ import * as hono_hono_base from 'hono/hono-base'; import * as hono_types from 'hono/types'; import * as hono_utils_http_status from 'hono/utils/http-status'; import { HttpBindings } from '@hono/node-server'; +import * as ai from 'ai'; import * as zod from 'zod'; import { z } from 'zod'; +import * as zod_v4_core from 'zod/v4/core'; +import * as zod_v4 from 'zod/v4'; import * as better_call from 'better-call'; import * as drizzle_orm_pg_core from 'drizzle-orm/pg-core'; import { AnyPgColumn } from 'drizzle-orm/pg-core'; @@ -17,6 +20,58 @@ type Env = { Bindings: HttpBindings; }; +declare const tools: { + displayFeeds: ai.Tool<{ + feedIds: string[]; + }, { + feedList: { + feed: { + id: string; + url: string; + title: string | null; + description: string | null; + siteUrl: string | null; + image: string | null; + checkedAt: Date; + lastModifiedHeader: string | null; + etagHeader: string | null; + ttl: number | null; + errorMessage: string | null; + errorAt: Date | null; + ownerUserId: string | null; + language: string | null; + migrateTo: string | null; + rsshubRoute: string | null; + rsshubNamespace: string | null; + nsfw: boolean | null; + }; + analytics: { + feedId: string; + updatesPerWeek: number | null; + subscriptionCount: number | null; + latestEntryPublishedAt: Date | null; + view: number | null; + } | null; + }[]; + }>; + getFeeds: ai.Tool<{ + select: ("id" | "image" | "description" | "title" | "url" | "siteUrl" | "checkedAt" | "lastModifiedHeader" | "etagHeader" | "ttl" | "errorMessage" | "errorAt" | "ownerUserId" | "language" | "migrateTo" | "rsshubRoute" | "rsshubNamespace" | "nsfw")[]; + ids: string[]; + }, { + feeds: Record[]; + }>; + getFeedEntries: ai.Tool<{ + select: ("id" | "description" | "title" | "content" | "author" | "url" | "language" | "feedId" | "guid" | "media" | "categories" | "attachments" | "extra" | "authorUrl" | "authorAvatar" | "insertedAt" | "publishedAt")[]; + feedId: string; + }, { + entries: Record[]; + }>; + getEntry: ai.Tool<{ + id: string; + select: ("id" | "description" | "title" | "content" | "author" | "url" | "language" | "feedId" | "guid" | "media" | "categories" | "attachments" | "extra" | "authorUrl" | "authorAvatar" | "insertedAt" | "publishedAt")[]; + }, Record | null>; +}; + declare const authPlugins: ({ id: "customGetProviders"; endpoints: { @@ -133,6 +188,47 @@ declare const authPlugins: ({ path: "/get-account-info"; }; }; +} | { + id: "deleteUserCustom"; + endpoints: { + deleteUserCustom: { + (inputCtx_0: { + body: { + TOTPCode: string; + }; + } & { + method?: "POST" | undefined; + } & { + query?: Record | undefined; + } & { + params?: Record; + } & { + request?: Request; + } & { + headers?: HeadersInit; + } & { + asResponse?: boolean; + returnHeaders?: boolean; + use?: better_call.Middleware[]; + path?: string; + } & { + asResponse?: AsResponse | undefined; + returnHeaders?: ReturnHeaders | undefined; + }): Promise<[AsResponse] extends [true] ? Response : [ReturnHeaders] extends [true] ? { + headers: Headers; + response: void; + } : void>; + options: { + method: "POST"; + body: zod_v4.ZodObject<{ + TOTPCode: zod_v4.ZodString; + }, zod_v4_core.$strip>; + } & { + use: any[]; + }; + path: "/delete-user-custom"; + }; + }; } | { id: "oneTimeToken"; endpoints: { @@ -5093,23 +5189,6 @@ declare const readabilities: drizzle_orm_pg_core.PgTableWithColumns<{ name: "readabilities"; schema: undefined; columns: { - id: drizzle_orm_pg_core.PgColumn<{ - name: "id"; - tableName: "readabilities"; - dataType: "string"; - columnType: "PgText"; - data: string; - driverParam: string; - notNull: true; - hasDefault: true; - isPrimaryKey: true; - isAutoincrement: false; - hasRuntimeDefault: true; - enumValues: [string, ...string[]]; - baseColumn: never; - identity: undefined; - generated: undefined; - }, {}, {}>; entryId: drizzle_orm_pg_core.PgColumn<{ name: "entry_id"; tableName: "readabilities"; @@ -5119,7 +5198,7 @@ declare const readabilities: drizzle_orm_pg_core.PgTableWithColumns<{ driverParam: string; notNull: true; hasDefault: false; - isPrimaryKey: false; + isPrimaryKey: true; isAutoincrement: false; hasRuntimeDefault: false; enumValues: [string, ...string[]]; @@ -5144,6 +5223,23 @@ declare const readabilities: drizzle_orm_pg_core.PgTableWithColumns<{ identity: undefined; generated: undefined; }, {}, {}>; + updatedAt: drizzle_orm_pg_core.PgColumn<{ + name: "updated_at"; + tableName: "readabilities"; + dataType: "date"; + columnType: "PgTimestamp"; + data: Date; + driverParam: string; + notNull: false; + hasDefault: false; + isPrimaryKey: false; + isAutoincrement: false; + hasRuntimeDefault: false; + enumValues: undefined; + baseColumn: never; + identity: undefined; + generated: undefined; + }, {}, {}>; }; dialect: "pg"; }>; @@ -6387,6 +6483,23 @@ declare const user: drizzle_orm_pg_core.PgTableWithColumns<{ identity: undefined; generated: undefined; }, {}, {}>; + deleted: drizzle_orm_pg_core.PgColumn<{ + name: "deleted"; + tableName: "user"; + dataType: "boolean"; + columnType: "PgBoolean"; + data: boolean; + driverParam: boolean; + notNull: false; + hasDefault: false; + isPrimaryKey: false; + isAutoincrement: false; + hasRuntimeDefault: false; + enumValues: undefined; + baseColumn: never; + identity: undefined; + generated: undefined; + }, {}, {}>; bio: drizzle_orm_pg_core.PgColumn<{ name: "bio"; tableName: "user"; @@ -6658,6 +6771,23 @@ declare const users: drizzle_orm_pg_core.PgTableWithColumns<{ identity: undefined; generated: undefined; }, {}, {}>; + deleted: drizzle_orm_pg_core.PgColumn<{ + name: "deleted"; + tableName: "user"; + dataType: "boolean"; + columnType: "PgBoolean"; + data: boolean; + driverParam: boolean; + notNull: false; + hasDefault: false; + isPrimaryKey: false; + isAutoincrement: false; + hasRuntimeDefault: false; + enumValues: undefined; + baseColumn: never; + identity: undefined; + generated: undefined; + }, {}, {}>; bio: drizzle_orm_pg_core.PgColumn<{ name: "bio"; tableName: "user"; @@ -6743,6 +6873,7 @@ declare const usersOpenApiSchema: zod.ZodObject; isAnonymous: zod.ZodNullable; suspended: zod.ZodNullable; + deleted: zod.ZodNullable; bio: zod.ZodNullable; website: zod.ZodNullable; socialLinks: zod.ZodNullable | null; bio: string | null; website: string | null; + deleted: boolean | null; }; session: { id: string; @@ -11146,6 +11291,7 @@ declare const auth: { socialLinks: Record | null; bio: string | null; website: string | null; + deleted: boolean | null; }; session: { id: string; @@ -11323,6 +11469,47 @@ declare const auth: { path: "/get-account-info"; }; }; + } | { + id: "deleteUserCustom"; + endpoints: { + deleteUserCustom: { + (inputCtx_0: { + body: { + TOTPCode: string; + }; + } & { + method?: "POST" | undefined; + } & { + query?: Record | undefined; + } & { + params?: Record; + } & { + request?: Request; + } & { + headers?: HeadersInit; + } & { + asResponse?: boolean; + returnHeaders?: boolean; + use?: better_call.Middleware[]; + path?: string; + } & { + asResponse?: AsResponse_1 | undefined; + returnHeaders?: ReturnHeaders_14 | undefined; + }): Promise<[AsResponse_1] extends [true] ? Response : [ReturnHeaders_14] extends [true] ? { + headers: Headers; + response: void; + } : void>; + options: { + method: "POST"; + body: zod_v4.ZodObject<{ + TOTPCode: zod_v4.ZodString; + }, zod_v4_core.$strip>; + } & { + use: any[]; + }; + path: "/delete-user-custom"; + }; + }; } | { id: "oneTimeToken"; endpoints: { @@ -11542,6 +11729,9 @@ declare const auth: { website: { type: "string"; }; + deleted: { + type: "boolean"; + }; }; changeEmail: { enabled: true; @@ -12822,6 +13012,7 @@ declare const auth: { socialLinks: Record | null; bio: string | null; website: string | null; + deleted: boolean | null; }; session: { id: string; @@ -12858,6 +13049,7 @@ declare const auth: { socialLinks: Record | null; bio: string | null; website: string | null; + deleted: boolean | null; }; session: { id: string; @@ -13035,6 +13227,47 @@ declare const auth: { path: "/get-account-info"; }; }; + } | { + id: "deleteUserCustom"; + endpoints: { + deleteUserCustom: { + (inputCtx_0: { + body: { + TOTPCode: string; + }; + } & { + method?: "POST" | undefined; + } & { + query?: Record | undefined; + } & { + params?: Record; + } & { + request?: Request; + } & { + headers?: HeadersInit; + } & { + asResponse?: boolean; + returnHeaders?: boolean; + use?: better_call.Middleware[]; + path?: string; + } & { + asResponse?: AsResponse | undefined; + returnHeaders?: ReturnHeaders | undefined; + }): Promise<[AsResponse] extends [true] ? Response : [ReturnHeaders] extends [true] ? { + headers: Headers; + response: void; + } : void>; + options: { + method: "POST"; + body: zod_v4.ZodObject<{ + TOTPCode: zod_v4.ZodString; + }, zod_v4_core.$strip>; + } & { + use: any[]; + }; + path: "/delete-user-custom"; + }; + }; } | { id: "oneTimeToken"; endpoints: { @@ -14592,6 +14825,44 @@ declare const auth: { }; path: "/get-account-info"; }; + } & { + deleteUserCustom: { + (inputCtx_0: { + body: { + TOTPCode: string; + }; + } & { + method?: "POST" | undefined; + } & { + query?: Record | undefined; + } & { + params?: Record; + } & { + request?: Request; + } & { + headers?: HeadersInit; + } & { + asResponse?: boolean; + returnHeaders?: boolean; + use?: better_call.Middleware[]; + path?: string; + } & { + asResponse?: AsResponse | undefined; + returnHeaders?: ReturnHeaders | undefined; + }): Promise<[AsResponse] extends [true] ? Response : [ReturnHeaders] extends [true] ? { + headers: Headers; + response: void; + } : void>; + options: { + method: "POST"; + body: zod_v4.ZodObject<{ + TOTPCode: zod_v4.ZodString; + }, zod_v4_core.$strip>; + } & { + use: any[]; + }; + path: "/delete-user-custom"; + }; } & { generateOneTimeToken: { (inputCtx_0?: ({ @@ -15721,6 +15992,7 @@ declare const auth: { socialLinks: Record | null; bio: string | null; website: string | null; + deleted: boolean | null; }; session: { id: string; @@ -15757,6 +16029,7 @@ declare const auth: { socialLinks: Record | null; bio: string | null; website: string | null; + deleted: boolean | null; }; session: { id: string; @@ -15854,6 +16127,9 @@ declare const auth: { website: { type: "string"; }; + deleted: { + type: "boolean"; + }; }; changeEmail: { enabled: true; @@ -17134,6 +17410,7 @@ declare const auth: { socialLinks: Record | null; bio: string | null; website: string | null; + deleted: boolean | null; }; session: { id: string; @@ -17170,6 +17447,7 @@ declare const auth: { socialLinks: Record | null; bio: string | null; website: string | null; + deleted: boolean | null; }; session: { id: string; @@ -17347,6 +17625,47 @@ declare const auth: { path: "/get-account-info"; }; }; + } | { + id: "deleteUserCustom"; + endpoints: { + deleteUserCustom: { + (inputCtx_0: { + body: { + TOTPCode: string; + }; + } & { + method?: "POST" | undefined; + } & { + query?: Record | undefined; + } & { + params?: Record; + } & { + request?: Request; + } & { + headers?: HeadersInit; + } & { + asResponse?: boolean; + returnHeaders?: boolean; + use?: better_call.Middleware[]; + path?: string; + } & { + asResponse?: AsResponse | undefined; + returnHeaders?: ReturnHeaders | undefined; + }): Promise<[AsResponse] extends [true] ? Response : [ReturnHeaders] extends [true] ? { + headers: Headers; + response: void; + } : void>; + options: { + method: "POST"; + body: zod_v4.ZodObject<{ + TOTPCode: zod_v4.ZodString; + }, zod_v4_core.$strip>; + } & { + use: any[]; + }; + path: "/delete-user-custom"; + }; + }; } | { id: "oneTimeToken"; endpoints: { @@ -17495,6 +17814,7 @@ declare const auth: { updatedAt: Date; image?: string | null | undefined | undefined; handle: string; + deleted: boolean; bio: string; website: string; socialLinks: string; @@ -17803,10 +18123,13 @@ declare const _routes: hono_hono_base.HonoBase, "/">; type AppType = typeof _routes; -export { type ActionItem, type ActionsModel, type AirdropActivity, type AppType, type AttachmentsModel, type AuthSession, type AuthUser, CommonEntryFields, type ConditionItem, type DetailModel, type EntriesModel, type ExtraModel, type FeedModel, type ListModel, type MediaModel, type MessagingData, MessagingType, type SettingsModel, type UrlReadsModel, account, achievements, achievementsOpenAPISchema, actions, actionsItemOpenAPISchema, actionsOpenAPISchema, actionsRelations, activities, activitiesOpenAPISchema, activityEnum, airdrops, airdropsOpenAPISchema, attachmentsZodSchema, authPlugins, boosts, captcha, collections, collectionsOpenAPISchema, collectionsRelations, detailModelSchema, entries, entriesOpenAPISchema, entriesRelations, extraZodSchema, feedAnalytics, feedAnalyticsOpenAPISchema, feedAnalyticsRelations, feedPowerTokens, feedPowerTokensOpenAPISchema, feedPowerTokensRelations, feeds, feedsOpenAPISchema, feedsRelations, inboxHandleSchema, inboxes, inboxesEntries, inboxesEntriesInsertOpenAPISchema, type inboxesEntriesModel, inboxesEntriesOpenAPISchema, inboxesEntriesRelations, inboxesOpenAPISchema, inboxesRelations, invitations, invitationsOpenAPISchema, invitationsRelations, languageSchema, levels, levelsOpenAPISchema, levelsRelations, listAnalytics, listAnalyticsOpenAPISchema, listAnalyticsRelations, lists, listsOpenAPISchema, listsRelations, listsSubscriptions, listsSubscriptionsOpenAPISchema, listsSubscriptionsRelations, lower, mediaZodSchema, messaging, messagingOpenAPISchema, messagingRelations, readabilities, rsshub, rsshubAnalytics, rsshubAnalyticsOpenAPISchema, rsshubOpenAPISchema, rsshubPurchase, rsshubUsage, rsshubUsageOpenAPISchema, rsshubUsageRelations, session, settings, subscriptions, subscriptionsOpenAPISchema, subscriptionsRelations, timeline, timelineOpenAPISchema, timelineRelations, transactionType, transactions, transactionsOpenAPISchema, transactionsRelations, trendingFeeds, trendingFeedsOpenAPISchema, trendingFeedsRelations, twoFactor, uploads, urlReads, urlReadsOpenAPISchema, user, users, usersOpenApiSchema, usersRelations, verification, wallets, walletsOpenAPISchema, walletsRelations }; +export { type ActionItem, type ActionsModel, type AirdropActivity, type AppType, type AttachmentsModel, type AuthSession, type AuthUser, CommonEntryFields, type ConditionItem, type DetailModel, type EntriesModel, type ExtraModel, type FeedModel, type ListModel, type MediaModel, type MessagingData, MessagingType, type SettingsModel, type UrlReadsModel, account, achievements, achievementsOpenAPISchema, actions, actionsItemOpenAPISchema, actionsOpenAPISchema, actionsRelations, activities, activitiesOpenAPISchema, activityEnum, airdrops, airdropsOpenAPISchema, attachmentsZodSchema, authPlugins, boosts, captcha, collections, collectionsOpenAPISchema, collectionsRelations, detailModelSchema, entries, entriesOpenAPISchema, entriesRelations, extraZodSchema, feedAnalytics, feedAnalyticsOpenAPISchema, feedAnalyticsRelations, feedPowerTokens, feedPowerTokensOpenAPISchema, feedPowerTokensRelations, feeds, feedsOpenAPISchema, feedsRelations, inboxHandleSchema, inboxes, inboxesEntries, inboxesEntriesInsertOpenAPISchema, type inboxesEntriesModel, inboxesEntriesOpenAPISchema, inboxesEntriesRelations, inboxesOpenAPISchema, inboxesRelations, invitations, invitationsOpenAPISchema, invitationsRelations, languageSchema, levels, levelsOpenAPISchema, levelsRelations, listAnalytics, listAnalyticsOpenAPISchema, listAnalyticsRelations, lists, listsOpenAPISchema, listsRelations, listsSubscriptions, listsSubscriptionsOpenAPISchema, listsSubscriptionsRelations, lower, mediaZodSchema, messaging, messagingOpenAPISchema, messagingRelations, readabilities, rsshub, rsshubAnalytics, rsshubAnalyticsOpenAPISchema, rsshubOpenAPISchema, rsshubPurchase, rsshubUsage, rsshubUsageOpenAPISchema, rsshubUsageRelations, session, settings, subscriptions, subscriptionsOpenAPISchema, subscriptionsRelations, timeline, timelineOpenAPISchema, timelineRelations, tools, transactionType, transactions, transactionsOpenAPISchema, transactionsRelations, trendingFeeds, trendingFeedsOpenAPISchema, trendingFeedsRelations, twoFactor, uploads, urlReads, urlReadsOpenAPISchema, user, users, usersOpenApiSchema, usersRelations, verification, wallets, walletsOpenAPISchema, walletsRelations };