Skip to content

Commit

Permalink
feat: added admin remove account option - fix #267 (#283)
Browse files Browse the repository at this point in the history
* feat: added admin remove account option - fix #267

* feat: added admin remove account option - fix #267
  • Loading branch information
pagoru authored Jan 27, 2025
1 parent f62df5d commit 89bad7b
Show file tree
Hide file tree
Showing 16 changed files with 201 additions and 43 deletions.
2 changes: 1 addition & 1 deletion app/client/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,7 @@
"vite-tsconfig-paths": "4.3.2"
},
"dependencies": {
"@oh/components": "npm:@jsr/[email protected].26",
"@oh/components": "npm:@jsr/[email protected].29",
"@vitejs/plugin-react": "^4.3.1",
"@vitejs/plugin-react-refresh": "1.3.6",
"dayjs": "1.11.13",
Expand Down
56 changes: 46 additions & 10 deletions app/client/src/modules/admin/components/users/users.component.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -9,17 +9,21 @@ import {
CrossIconComponent,
ButtonComponent,
SelectorComponent,
ConfirmationModalComponent,
} from "@oh/components";
import { User } from "shared/types";
import { EMAIL_REGEX, USERNAME_REGEX } from "shared/consts";

//@ts-ignore
import styles from "./users.module.scss";
import { useModal } from "@oh/components";

export const AdminUsersComponent = () => {
const { users, updateUser, refresh, resendVerificationUser } = useAdmin();
const { users, updateUser, deleteUser, refresh, resendVerificationUser } =
useAdmin();

const [selectedUser, setSelectedUser] = useState<User>();
const { open, close } = useModal();

const today = dayjs(Date.now());

Expand All @@ -37,6 +41,7 @@ export const AdminUsersComponent = () => {
email,
createdAt: dayjs(createdAt).valueOf(),
admin: selectedUser.admin,
languages: selectedUser.languages,
};

await updateUser(user);
Expand All @@ -61,6 +66,10 @@ export const AdminUsersComponent = () => {
),
[selectedUser, adminOptions],
);
const $onRemoveAccount = useCallback(async () => {
await deleteUser(selectedUser);
refresh();
}, [deleteUser, selectedUser]);

return (
<div>
Expand Down Expand Up @@ -126,16 +135,37 @@ export const AdminUsersComponent = () => {
}
/>
</div>
<ButtonComponent>Update</ButtonComponent>
<div className={styles.actions}>
{selectedUser.admin ? null : (
<ButtonComponent
color="grey"
onClick={() =>
open({
children: (
<ConfirmationModalComponent
description={`Are you sure you want to delete ${selectedUser.username}'s account?`}
onClose={close}
onConfirm={$onRemoveAccount}
/>
),
})
}
>
Delete
</ButtonComponent>
)}
{/*//@ts-ignore*/}
{selectedUser.verified !== "✅" ? (
<ButtonComponent
color="yellow"
onClick={onResendVerificationEmail}
>
Resend verification email
</ButtonComponent>
) : null}
<ButtonComponent>Update</ButtonComponent>
</div>
</FormComponent>
{selectedUser.verified !== "✅" ? (
<ButtonComponent
color="yellow"
onClick={onResendVerificationEmail}
>
Resend verification email
</ButtonComponent>
) : null}
</div>
) : null}
<TableComponent
Expand Down Expand Up @@ -181,6 +211,7 @@ export const AdminUsersComponent = () => {
verified: user.verified
? "✅"
: `⏳ ${Math.floor(remainingMinutes / 60)} hours ${remainingMinutes % 60} minutes`,
githubLogin: user.githubLogin,
createdAt: dayjs(user.createdAt).format("YYYY/MM/DD HH:mm:ss"),
};
})}
Expand Down Expand Up @@ -208,6 +239,11 @@ export const AdminUsersComponent = () => {
key: "otp",
label: "2FA",
},
{
sortable: true,
key: "githubLogin",
label: "github",
},
{
sortable: true,
key: "verified",
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -40,3 +40,8 @@
}
}
}
.actions {
display: flex;
gap: 1rem;
justify-content: space-between;
}
Original file line number Diff line number Diff line change
@@ -1,11 +1,14 @@
import { Outlet } from "react-router";
import { UserProvider } from "shared/hooks";
import React from "react";
import { ModalProvider } from "@oh/components";

export const ProvidersComponent = () => {
return (
<UserProvider>
<Outlet />
</UserProvider>
<ModalProvider>
<UserProvider>
<Outlet />
</UserProvider>
</ModalProvider>
);
};
Original file line number Diff line number Diff line change
Expand Up @@ -50,8 +50,8 @@ export const HotelsComponent = () => {
founded at {dayjs(hotel.createdAt).format("DD MMM YYYY")}
</label>
<a>
{hotel.accounts} account{hotel.accounts === 1 ? "" : "s"}{" "}
already joined!
{hotel.accounts} user{hotel.accounts === 1 ? "" : "s"} already
joined!
</a>
</div>
<div className={styles.contentActions}>
Expand Down
8 changes: 0 additions & 8 deletions app/client/src/modules/register/register.component.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -49,14 +49,6 @@ export const RegisterComponent: React.FC = () => {
const rePassword = data.get("rePassword") as string;
const language = data.get("language") as string;

console.log({
email,
username,
password,
rePassword,
captchaId,
languages: [language],
});
register({
email,
username,
Expand Down
14 changes: 14 additions & 0 deletions app/client/src/shared/hooks/useAdmin.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,7 @@ type AdminState = {
update: () => Promise<void>;

updateUser: (user: User) => Promise<void>;
deleteUser: (user: User) => Promise<void>;
resendVerificationUser: (accountId: string) => Promise<void>;

refresh: () => void;
Expand Down Expand Up @@ -62,6 +63,18 @@ export const AdminProvider: React.FunctionComponent<ProviderProps> = ({
[fetch, getAccountHeaders],
);

const deleteUser = useCallback(
(user: User) => {
return fetch({
method: RequestMethod.DELETE,
pathname: "/admin/user",
headers: getAccountHeaders(),
body: user,
});
},
[fetch, getAccountHeaders],
);

const resendVerificationUser = useCallback(
(accountId: string) => {
return fetch({
Expand Down Expand Up @@ -149,6 +162,7 @@ export const AdminProvider: React.FunctionComponent<ProviderProps> = ({
value={{
users,
updateUser,
deleteUser,
resendVerificationUser,

tokens,
Expand Down
4 changes: 1 addition & 3 deletions app/client/src/shared/hooks/useMyHotels.ts
Original file line number Diff line number Diff line change
Expand Up @@ -35,7 +35,7 @@ export const useMyHotels = () => {

const update = useCallback(
async (hotelId: string, name: string, $public: boolean) => {
const { data } = await fetch({
await fetch({
method: RequestMethod.PATCH,
pathname: `/user/@me/hotel`,
headers: getAccountHeaders(),
Expand All @@ -45,8 +45,6 @@ export const useMyHotels = () => {
public: $public,
},
});

return data.hotels;
},
[fetch, getAccountHeaders],
);
Expand Down
11 changes: 6 additions & 5 deletions app/client/yarn.lock
Original file line number Diff line number Diff line change
Expand Up @@ -496,12 +496,13 @@ __metadata:
languageName: node
linkType: hard

"@oh/components@npm:@jsr/[email protected].26":
version: 0.1.26
resolution: "@jsr/oh__components@npm:0.1.26::__archiveUrl=https%3A%2F%2Fnpm.jsr.io%2F~%2F11%2F%40jsr%2Foh__components%2F0.1.26.tgz"
"@oh/components@npm:@jsr/[email protected].29":
version: 0.1.29
resolution: "@jsr/oh__components@npm:0.1.29::__archiveUrl=https%3A%2F%2Fnpm.jsr.io%2F~%2F11%2F%40jsr%2Foh__components%2F0.1.29.tgz"
dependencies:
react: "npm:18.3.1"
checksum: 10c0/8c8ee65118dbe052cdbc3f5329678755d81aeddbedc32831dcb59669815b7030203416e776bab1575c9970ad8542daa084cf30b883c705bbfd2b77f6533d6d79
react-dom: "npm:18.3.1"
checksum: 10c0/f528efaa57089c988272db1d1271a7700b9afa1f931e68e23f7ed4ea7f9dbeccbcfc2fc1c6068afb5a8f784d921cebe7c672ebd3d55e84947d4f7d0b384c9d16
languageName: node
linkType: hard

Expand Down Expand Up @@ -2056,7 +2057,7 @@ __metadata:
version: 0.0.0-use.local
resolution: "root-workspace-0b6124@workspace:."
dependencies:
"@oh/components": "npm:@jsr/[email protected].26"
"@oh/components": "npm:@jsr/[email protected].29"
"@rollup/plugin-alias": "npm:5.1.0"
"@types/js-cookie": "npm:3.0.6"
"@types/react": "npm:18.3.3"
Expand Down
9 changes: 0 additions & 9 deletions app/server/src/modules/api/v3/account/login.request.ts
Original file line number Diff line number Diff line change
Expand Up @@ -65,15 +65,6 @@ export const loginPostRequest: RequestType = {
message: "Email or password not valid!",
});

const accountByVerifyId = await System.db.get([
"accountsByVerifyId",
account.accountId,
]);
if (accountByVerifyId)
return getResponse(HttpStatusCode.FORBIDDEN, {
message: "Account is not verified!",
});

const accountOTP = await System.db.get([
"otpByAccountId",
account.accountId,
Expand Down
3 changes: 2 additions & 1 deletion app/server/src/modules/api/v3/admin/main.ts
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@ import {
} from "./tokens.request.ts";
import { hotelsDeleteRequest, hotelsGetRequest } from "./hotels.request.ts";
import { usersGetRequest } from "./users.request.ts";
import { userPatchRequest } from "./user.request.ts";
import { userDeleteRequest, userPatchRequest } from "./user.request.ts";
import { userResendVerificationRequest } from "./user-resend-verification.request.ts";

export const adminRequestList: RequestType[] = getPathRequestList({
Expand All @@ -20,6 +20,7 @@ export const adminRequestList: RequestType[] = getPathRequestList({
tokensGetRequest,
tokensPostRequest,
usersGetRequest,
userDeleteRequest,
userPatchRequest,
userResendVerificationRequest,
hotelsGetRequest,
Expand Down
17 changes: 16 additions & 1 deletion app/server/src/modules/api/v3/admin/user.request.ts
Original file line number Diff line number Diff line change
Expand Up @@ -10,11 +10,26 @@ import { System } from "modules/system/main.ts";
import { EMAIL_REGEX, USERNAME_REGEX } from "shared/consts/main.ts";
import { getEmailHash, getEncryptedEmail } from "shared/utils/account.utils.ts";

export const userDeleteRequest: RequestType = {
method: RequestMethod.DELETE,
pathname: "/user",
kind: RequestKind.ADMIN,
func: async (request: Request) => {
if (!(await hasRequestAccess({ request, admin: true })))
return getResponse(HttpStatusCode.FORBIDDEN);

let { accountId } = await request.json();
await System.accounts.remove(accountId);

return getResponse(HttpStatusCode.OK);
},
};

export const userPatchRequest: RequestType = {
method: RequestMethod.PATCH,
pathname: "/user",
kind: RequestKind.ADMIN,
func: async (request: Request, url: URL) => {
func: async (request: Request) => {
if (!(await hasRequestAccess({ request, admin: true })))
return getResponse(HttpStatusCode.FORBIDDEN);

Expand Down
23 changes: 23 additions & 0 deletions app/server/src/modules/system/accounts.ts
Original file line number Diff line number Diff line change
Expand Up @@ -22,9 +22,32 @@ export const accounts = () => {
return await System.db.get(["accounts", accountId]);
};

const remove = async (accountId: string) => {
const account = await get(accountId);

await System.db.delete(["accounts", accountId]);
await System.db.delete(["accountsByEmail", account.emailHash]);
await System.db.delete(["accountsByRefreshToken", accountId]);
await System.db.delete(["accountsByToken", accountId]);
await System.db.delete(["accountsByUsername", account.username]);

await System.db.delete(["emailsByHash", account.emailHash]);

await System.db.delete(["github", accountId]);
await System.db.delete(["githubState", accountId]);

await System.connections.removeAll(accountId);
await System.hotels.removeAll(accountId);

await System.db.delete(["hotelsByAccountId", accountId]);

await System.admins.remove(accountId);
};

return {
getList,
get,
getFromRequest,
remove,
};
};
14 changes: 14 additions & 0 deletions app/server/src/modules/system/connections.ts
Original file line number Diff line number Diff line change
Expand Up @@ -293,10 +293,24 @@ export const connections = () => {
return connections;
};

const removeAll = async (accountId: string) => {
await System.db.delete(["connections", accountId]);

const connections = (
await System.db.list({
prefix: ["integrationsByAccountId", accountId],
})
).map(({ value }) => value);

for (const connection of connections)
await remove(accountId, connection.hotelId, connection.integrationId);
};

return {
generate,
verify,
remove,
removeAll,
ping,
getList,
getListByHotelIdIntegrationId,
Expand Down
Loading

0 comments on commit 89bad7b

Please sign in to comment.