From 28f1c1199bffac76345f3de7810850c9530eeaca Mon Sep 17 00:00:00 2001 From: Youngsun Yoo Date: Fri, 9 Aug 2024 14:36:11 -0400 Subject: [PATCH 1/9] pushing latest changes from deleted branch --- backend/controllers/admin.controller.ts | 25 +++++++++++++++++++++--- backend/controllers/report.controller.ts | 2 +- backend/routes/admin.routes.ts | 4 +++- frontend/src/pages/Admin/Reports.tsx | 1 + 4 files changed, 27 insertions(+), 5 deletions(-) diff --git a/backend/controllers/admin.controller.ts b/backend/controllers/admin.controller.ts index 9605ff6..f926cad 100644 --- a/backend/controllers/admin.controller.ts +++ b/backend/controllers/admin.controller.ts @@ -1,12 +1,13 @@ import { debug } from "debug"; import expressAsyncHandler from "express-async-handler"; -import { validationResult } from "express-validator"; +import { validationResult, param, body } from "express-validator"; import { FilterQuery } from "mongoose"; import { PostStatus } from "../constants"; -import { AuthorizationError } from "../errors"; +import { AuthorizationError, ValidationError } from "../errors"; import { PostDto } from "../models/posts"; import { UserDto } from "../models/users"; -import { PostService, ResendService, UserService } from "../services"; +import { ReportDto } from "../models/reports"; +import { PostService, ReportService, ResendService, UserService } from "../services"; import { PaginatedResponse, PostDocument, UserDocument } from "../types"; import { tryParsePaginationQuery, @@ -31,6 +32,7 @@ export class AdminController { private postService: PostService, private userService: UserService, private resendService: ResendService, + private reportService: ReportService, ) {} pre = (req) => { @@ -121,6 +123,23 @@ export class AdminController { res.json(userDto); }); + getUserReports = expressAsyncHandler(async (req, res, next) => { + await param("userId").notEmpty().run(req); + + const errors = validationResult(req); + if (!errors.isEmpty()) { + throw new ValidationError(errors.array()); + } + + const { userId } = req.params; + + const reports = await this.reportService.getUserReports(userId); + console.log(reports); + res + .status(200) + .json(reports.map((report) => ReportDto.fromDocument(report))); + }); + getUsersToVerify = expressAsyncHandler(async (req, res, next) => { this.pre(req); diff --git a/backend/controllers/report.controller.ts b/backend/controllers/report.controller.ts index a8e40f0..1389fed 100644 --- a/backend/controllers/report.controller.ts +++ b/backend/controllers/report.controller.ts @@ -171,7 +171,7 @@ export class ReportController { const { userId } = req.params; const reports = await this.reportService.getUserReports(userId); - + console.log(reports); res .status(200) .json(reports.map((report) => ReportDto.fromDocument(report))); diff --git a/backend/routes/admin.routes.ts b/backend/routes/admin.routes.ts index b8a5b43..0af7558 100644 --- a/backend/routes/admin.routes.ts +++ b/backend/routes/admin.routes.ts @@ -1,7 +1,7 @@ import { Router } from "express"; import { AdminController } from "../controllers/admin.controller"; import { ensureAdmin, ensureAuthenticated } from "../middlewares"; -import { PostService, ResendService, UserService } from "../services"; +import { PostService, ResendService, UserService, ReportService } from "../services"; // * middleware function to create route handlers const router = Router(); @@ -9,10 +9,12 @@ const router = Router(); const postService = new PostService(); const userService = new UserService(); const resendService = new ResendService(); +const reportService = new ReportService(); const adminController = new AdminController( postService, userService, resendService, + reportService, ); // * wire up routes with controller diff --git a/frontend/src/pages/Admin/Reports.tsx b/frontend/src/pages/Admin/Reports.tsx index ba77ca8..4f3e341 100644 --- a/frontend/src/pages/Admin/Reports.tsx +++ b/frontend/src/pages/Admin/Reports.tsx @@ -16,6 +16,7 @@ export const ReportsPage = () => { per_page: perPage, page: page, }); + console.log(reportedPostsResponse); const handlePerPageChange = (val: string) => { // update parent container state From 4967d02ea8a24c56c2ca2b2bca9e41b7d769528a Mon Sep 17 00:00:00 2001 From: Weijie Wang Date: Fri, 9 Aug 2024 14:40:22 -0400 Subject: [PATCH 2/9] add location_information --- frontend/public/locales/en/common.json | 1 + 1 file changed, 1 insertion(+) diff --git a/frontend/public/locales/en/common.json b/frontend/public/locales/en/common.json index 57e6eb4..4909212 100644 --- a/frontend/public/locales/en/common.json +++ b/frontend/public/locales/en/common.json @@ -277,6 +277,7 @@ "active": "Active", "inactive": "Inactive", "location_information": "Location information", + "report_information": "Report information", "postal_code": "Postal code", "organization_information": "Organization information", "phone_number": "Phone number", From 1cd032a286eee532df90260ae9d8c404c8518df7 Mon Sep 17 00:00:00 2001 From: Weijie Wang Date: Fri, 9 Aug 2024 14:40:50 -0400 Subject: [PATCH 3/9] ajouter location_information --- frontend/public/locales/fr/common.json | 1 + 1 file changed, 1 insertion(+) diff --git a/frontend/public/locales/fr/common.json b/frontend/public/locales/fr/common.json index 6f3b26b..7d7bd5b 100644 --- a/frontend/public/locales/fr/common.json +++ b/frontend/public/locales/fr/common.json @@ -277,6 +277,7 @@ "active": "Actif", "inactive": "Inactif", "location_information": "Informations de localisation", + "report_information": "Informations sur les rapports", "postal_code": "Code postal", "organization_information": "Informations sur l'organisation", "phone_number": "Numéro de téléphone", From 9799b55effbd8eb26773b433193f35e058be53d6 Mon Sep 17 00:00:00 2001 From: Weijie Wang Date: Fri, 9 Aug 2024 14:44:04 -0400 Subject: [PATCH 4/9] add report block in userdetails --- frontend/src/components/Users/UserDetails.tsx | 17 +++++++++++++++++ 1 file changed, 17 insertions(+) diff --git a/frontend/src/components/Users/UserDetails.tsx b/frontend/src/components/Users/UserDetails.tsx index 0ff5cb7..a8679a4 100644 --- a/frontend/src/components/Users/UserDetails.tsx +++ b/frontend/src/components/Users/UserDetails.tsx @@ -132,6 +132,7 @@ export const UserDetails = ({ lastName, active, location, + report, organization, activeStatusChangeReason, } = user; @@ -221,6 +222,22 @@ export const UserDetails = ({ )} + {report && ( +
+

+ {t("users.report_information")} +

+
+
+
+ {t("users.postal_code")} +
+
{"Hi"}
+
+
+
+ )} + {organization && (

From 6c929de1c8ef7703373d05269367cd68dc1aa18a Mon Sep 17 00:00:00 2001 From: Youngsun Yoo Date: Tue, 27 Aug 2024 21:01:16 -0600 Subject: [PATCH 5/9] fix: work on backend logic for pulling reports from user --- backend/controllers/admin.controller.ts | 22 ++++++-- backend/controllers/report.controller.ts | 2 +- backend/models/reports/report.dto.ts | 6 ++- backend/services/report.service.ts | 69 ++++++++++++++++++++++-- backend/types.ts | 2 +- 5 files changed, 91 insertions(+), 10 deletions(-) diff --git a/backend/controllers/admin.controller.ts b/backend/controllers/admin.controller.ts index f926cad..a996fb1 100644 --- a/backend/controllers/admin.controller.ts +++ b/backend/controllers/admin.controller.ts @@ -1,12 +1,12 @@ import { debug } from "debug"; import expressAsyncHandler from "express-async-handler"; -import { validationResult, param, body } from "express-validator"; +import { param, validationResult } from "express-validator"; import { FilterQuery } from "mongoose"; import { PostStatus } from "../constants"; import { AuthorizationError, ValidationError } from "../errors"; import { PostDto } from "../models/posts"; -import { UserDto } from "../models/users"; import { ReportDto } from "../models/reports"; +import { UserDto } from "../models/users"; import { PostService, ReportService, ResendService, UserService } from "../services"; import { PaginatedResponse, PostDocument, UserDocument } from "../types"; import { @@ -113,17 +113,31 @@ export class AdminController { const { id } = req.params; const user = await this.userService.getUserById(id); + const reports = await this.reportService.getUserReports(id); if (!user) { res.status(404).json({ error: `User ${id} not found.` }); return; } + if (!reports) { + res.status(404).json({ error: `Reports from ${id} not found.` }); + return; + } + const userDto = UserDto.fromDocument(user); + console.log(reports); + const reportDtos = reports.map((report) => { + ReportDto.fromAggregate(report); + }); + console.log({...reportDtos}); + + // console.log({userDto, ...reportDtos}); + res.json(userDto); }); - getUserReports = expressAsyncHandler(async (req, res, next) => { + getUserReportsById = expressAsyncHandler(async (req, res, next) => { await param("userId").notEmpty().run(req); const errors = validationResult(req); @@ -134,7 +148,7 @@ export class AdminController { const { userId } = req.params; const reports = await this.reportService.getUserReports(userId); - console.log(reports); + res .status(200) .json(reports.map((report) => ReportDto.fromDocument(report))); diff --git a/backend/controllers/report.controller.ts b/backend/controllers/report.controller.ts index 1389fed..7c96cb4 100644 --- a/backend/controllers/report.controller.ts +++ b/backend/controllers/report.controller.ts @@ -98,6 +98,7 @@ export class ReportController { const { id } = req.params; const reports = await this.reportService.getReportedPost(id); + console.log(reports); if (!reports) { throw new NotFoundError(`Error finding report ${id}.`); @@ -171,7 +172,6 @@ export class ReportController { const { userId } = req.params; const reports = await this.reportService.getUserReports(userId); - console.log(reports); res .status(200) .json(reports.map((report) => ReportDto.fromDocument(report))); diff --git a/backend/models/reports/report.dto.ts b/backend/models/reports/report.dto.ts index 4e1612e..160754b 100644 --- a/backend/models/reports/report.dto.ts +++ b/backend/models/reports/report.dto.ts @@ -1,5 +1,5 @@ import { Document } from "mongoose"; -import { PostDocument, Report, UserDocument } from "../../types"; +import { PostDocument, Report, UserDocument, ReportDocument } from "../../types"; import { PostDto } from "../posts"; import { UserDto } from "../users"; @@ -25,6 +25,10 @@ export class ReportDto { const report = document.toObject() as Report; return new ReportDto(document.id, report); } + static fromAggregate(document: ReportDocument): ReportDto { + return new ReportDto(document._id, document); + } + } export class ReportedPostDto { diff --git a/backend/services/report.service.ts b/backend/services/report.service.ts index 62bedb7..cd57221 100644 --- a/backend/services/report.service.ts +++ b/backend/services/report.service.ts @@ -1,7 +1,9 @@ import { ObjectId } from "mongodb"; +import { PipelineStage, Types } from "mongoose"; import { ReportModel } from "../models/reports"; +import { PostModel } from "../models/posts"; +import { UserModel } from "../models/users"; import { PostDocument, Report, ReportDocument } from "../types"; - export class ReportService { async createReport(report: Partial) { const newReport = new ReportModel({ @@ -20,7 +22,7 @@ export class ReportService { async getReportedPosts( page: number, - limit: number + limit: number, ): Promise< [{ _id: string; outstanding_reports: number; post: PostDocument }[], number] > { @@ -123,7 +125,68 @@ export class ReportService { } async getUserReports(userId: string): Promise { - const reports = await ReportModel.find({ userId: userId }); + + const pipeline: PipelineStage[] = [ + { + $lookup: { + from: "posts", + localField: "post", + foreignField: "_id", + pipeline: [ + { + $match: { + author: new Types.ObjectId(userId) + }, + }, + ], + as: "postDetails", + }, + }, + { + $unwind: { + path: "$postDetails", + }, + }, + { + $group: { + _id: "$status", + report: { $push: "$$ROOT" }, + count: { $sum: 1 }, + }, + }, + { + $sort: { + count: -1, + updatedAt: -1, + createdAt: -1, + }, + }, + { $unset: ["_id", "count"] }, + { + $unwind: { + path: "$report", + preserveNullAndEmptyArrays: true, + }, + }, + { + $set: { + _id: "$report._id", + reporter: "$report.reporter", + post: "$report.post", + status: "$report.status", + notes: "$report.notes", + createdAt: "$report.createdAt", + updatedAt: "$report.updatedAt", + }, + }, + { $unset: "report" }, + { $unset: "postDetails" }, + ]; + + const reports = await ReportModel.aggregate(pipeline); + // unwind (expand) the post field inside report + await PostModel.populate(reports, { path: 'post' }); + await UserModel.populate(reports, { path: 'reporter' }); return reports; } diff --git a/backend/types.ts b/backend/types.ts index 8fc3a2b..fb07922 100644 --- a/backend/types.ts +++ b/backend/types.ts @@ -93,7 +93,7 @@ export type UserDocument = User & export type Report = { _id: string; reporter: User; - resolver: User; + resolver?: User; post: Post | PostDocument; status: "resolved" | "unresolved"; notes: string; From 723896889d8b32ce89d2e91f835afac42aeaf235 Mon Sep 17 00:00:00 2001 From: Youngsun Yoo Date: Thu, 29 Aug 2024 00:50:07 -0600 Subject: [PATCH 6/9] fix: expand api types as user reports and display basic information of report in the form of skeleton --- backend/controllers/admin.controller.ts | 23 +++--- backend/types.ts | 2 +- frontend/src/components/Users/UserDetails.tsx | 82 +++++++++++++------ frontend/src/pages/Admin/Reports.tsx | 1 - frontend/src/pages/Admin/Users.tsx | 6 -- .../Admin/components/ReportSummaryItem.tsx | 30 +++++++ frontend/src/store/services/api.ts | 2 +- frontend/src/store/services/types.ts | 5 ++ 8 files changed, 105 insertions(+), 46 deletions(-) create mode 100644 frontend/src/pages/Admin/components/ReportSummaryItem.tsx diff --git a/backend/controllers/admin.controller.ts b/backend/controllers/admin.controller.ts index a996fb1..cd83840 100644 --- a/backend/controllers/admin.controller.ts +++ b/backend/controllers/admin.controller.ts @@ -7,7 +7,12 @@ import { AuthorizationError, ValidationError } from "../errors"; import { PostDto } from "../models/posts"; import { ReportDto } from "../models/reports"; import { UserDto } from "../models/users"; -import { PostService, ReportService, ResendService, UserService } from "../services"; +import { + PostService, + ReportService, + ResendService, + UserService, +} from "../services"; import { PaginatedResponse, PostDocument, UserDocument } from "../types"; import { tryParsePaginationQuery, @@ -96,7 +101,6 @@ export class AdminController { res.json(response); }); - getUserById = expressAsyncHandler(async (req, res, next) => { this.pre(req); @@ -126,15 +130,10 @@ export class AdminController { } const userDto = UserDto.fromDocument(user); - console.log(reports); - const reportDtos = reports.map((report) => { - ReportDto.fromAggregate(report); - }); - console.log({...reportDtos}); + const reportDtos = reports.map((report) => ReportDto.fromAggregate(report)); - // console.log({userDto, ...reportDtos}); - res.json(userDto); + res.json({user: userDto, reports: reportDtos}); }); getUserReportsById = expressAsyncHandler(async (req, res, next) => { @@ -183,13 +182,13 @@ export class AdminController { { updatedAt: -1, createdAt: -1 }, ); - let userDtos : UserDto[]; + let userDtos: UserDto[]; if (reported_user) { userDtos = users.map((user) => UserDto.fromAggregate(user)); - }else { + } else { userDtos = users.map((user) => UserDto.fromDocument(user)); } - + const response: PaginatedResponse = { data: userDtos || [], page: page, diff --git a/backend/types.ts b/backend/types.ts index fb07922..8fc3a2b 100644 --- a/backend/types.ts +++ b/backend/types.ts @@ -93,7 +93,7 @@ export type UserDocument = User & export type Report = { _id: string; reporter: User; - resolver?: User; + resolver: User; post: Post | PostDocument; status: "resolved" | "unresolved"; notes: string; diff --git a/frontend/src/components/Users/UserDetails.tsx b/frontend/src/components/Users/UserDetails.tsx index a8679a4..a363ed7 100644 --- a/frontend/src/components/Users/UserDetails.tsx +++ b/frontend/src/components/Users/UserDetails.tsx @@ -12,6 +12,7 @@ import { import { faCancel, faCheckCircle, + faRectangleList, faRecycle, faTrash, } from "@fortawesome/free-solid-svg-icons"; @@ -22,6 +23,8 @@ import { useToggleUserActiveAdminMutation, useVerifyUserAdminMutation, } from "@services/api"; +import { ApiModel } from "@services/api"; +import { ReportSummaryItem } from "@pages/Admin/components/ReportSummaryItem"; import { getStatusIndicator } from "@utils"; import { useEffect, useState } from "react"; import { useTranslation } from "react-i18next"; @@ -50,7 +53,7 @@ export const UserDetails = ({ const [toggleUserActive] = useToggleUserActiveAdminMutation(); const { - data: user, + data: data, isLoading, isError, error: getUserError, @@ -63,19 +66,19 @@ export const UserDetails = ({ }, [isError]); const onVerify = async () => { - if (!user?.id) { + if (!data?.user?.id) { onError({ status: 500, message: "User ID must be available" }); return; } - verifyUserApi({ userId: user.id }); + verifyUserApi({ userId: data?.user?.id }); }; const onToggleActive = async () => { - if (!user?.id) { + if (!data?.user?.id) { onError({ status: 500, message: "User ID must be available" }); return; } - toggleUserActive({ userId: user.id, reason: reason }); + toggleUserActive({ userId: data?.user?.id, reason: reason }); setReason(""); setActiveDialogOpen(false); }; @@ -108,7 +111,7 @@ export const UserDetails = ({ return

{t("loading")}

; } - if (!user) { + if (!data?.user) { return

{t("errors.unknown_server_error")}

; } @@ -132,11 +135,12 @@ export const UserDetails = ({ lastName, active, location, - report, organization, activeStatusChangeReason, - } = user; - const verified = user.organization?.verified ?? true; + } = data?.user!; + + const userReports = data?.reports!; + const verified = data?.user!.organization?.verified ?? true; return (
@@ -206,6 +210,50 @@ export const UserDetails = ({
+
+

+ {t("users.report_information")} +

+ {userReports === undefined || userReports.length === 0 ? ( +
+ + + {t("reports.no_reports_found")} + +
+ ) : ( +
    + {userReports.map( + ( + report, + // { + // id: string; + // reporter: User; + // resolver?: User; + // post: ApiModel.Post; + // status: "resolved" | "unresolved"; + // notes: string; + // } + ) => { + return ( + + ); + }, + )} +
+ )} +
+ {location?.postalCode && (

@@ -222,22 +270,6 @@ export const UserDetails = ({

)} - {report && ( -
-

- {t("users.report_information")} -

-
-
-
- {t("users.postal_code")} -
-
{"Hi"}
-
-
-
- )} - {organization && (

diff --git a/frontend/src/pages/Admin/Reports.tsx b/frontend/src/pages/Admin/Reports.tsx index 4f3e341..ba77ca8 100644 --- a/frontend/src/pages/Admin/Reports.tsx +++ b/frontend/src/pages/Admin/Reports.tsx @@ -16,7 +16,6 @@ export const ReportsPage = () => { per_page: perPage, page: page, }); - console.log(reportedPostsResponse); const handlePerPageChange = (val: string) => { // update parent container state diff --git a/frontend/src/pages/Admin/Users.tsx b/frontend/src/pages/Admin/Users.tsx index b627be5..2f7e46a 100644 --- a/frontend/src/pages/Admin/Users.tsx +++ b/frontend/src/pages/Admin/Users.tsx @@ -18,12 +18,6 @@ export const AdminUsersPage = () => { user_type: userType, reported_user: withReport, }); - console.log({ - per_page: perPage, - page: page, - user_type: userType, - reported_user: withReport, - }); return (
diff --git a/frontend/src/pages/Admin/components/ReportSummaryItem.tsx b/frontend/src/pages/Admin/components/ReportSummaryItem.tsx new file mode 100644 index 0000000..f2577cb --- /dev/null +++ b/frontend/src/pages/Admin/components/ReportSummaryItem.tsx @@ -0,0 +1,30 @@ +import { faChevronRight } from "@fortawesome/free-solid-svg-icons"; +import { FontAwesomeIcon } from "@fortawesome/react-fontawesome"; + +interface ReportSummaryItemProps { + id: string; + summary: string; +} + +export const ReportSummaryItem = ({ id, summary }: ReportSummaryItemProps) => { + return ( +
  • +
    +
    +
    +

    + {summary} +

    +
    +
    +
    +
    + +
    +
    +
  • + ); +}; diff --git a/frontend/src/store/services/api.ts b/frontend/src/store/services/api.ts index 9033497..93b1a7c 100644 --- a/frontend/src/store/services/api.ts +++ b/frontend/src/store/services/api.ts @@ -391,7 +391,7 @@ export const api = createApi({ ] : [{ type: "users", id: "admin-list" }], }), - getUserAdmin: builder.query({ + getUserAdmin: builder.query({ query: ({ userId }) => ({ url: `admin/users/${userId}`, method: "GET" }), providesTags: (result, error, args) => [ { type: "users", id: args.userId }, diff --git a/frontend/src/store/services/types.ts b/frontend/src/store/services/types.ts index 90af5a9..058c3c0 100644 --- a/frontend/src/store/services/types.ts +++ b/frontend/src/store/services/types.ts @@ -56,6 +56,11 @@ export namespace ApiModel { notes: string; }; + export type UserReports = { + user: User; + reports: Report[]; + }; + export type UserOrganization = { name: string; address: Address; From 140e7981d947e908edea7b27fd0aeaf2b1ce9a32 Mon Sep 17 00:00:00 2001 From: Youngsun Yoo Date: Sat, 31 Aug 2024 10:58:20 -0600 Subject: [PATCH 7/9] fix: add UI for report information component --- backend/controllers/admin.controller.ts | 6 +- backend/services/report.service.ts | 6 ++ frontend/src/components/Users/UserDetails.tsx | 53 +++++++----- .../Admin/components/ReportSummaryItem.tsx | 81 +++++++++++++++---- 4 files changed, 107 insertions(+), 39 deletions(-) diff --git a/backend/controllers/admin.controller.ts b/backend/controllers/admin.controller.ts index cd83840..11e3e61 100644 --- a/backend/controllers/admin.controller.ts +++ b/backend/controllers/admin.controller.ts @@ -129,11 +129,13 @@ export class AdminController { return; } + + const userDto = UserDto.fromDocument(user); const reportDtos = reports.map((report) => ReportDto.fromAggregate(report)); + console.log(reportDtos); - - res.json({user: userDto, reports: reportDtos}); + res.json({ user: userDto, reports: reportDtos }); }); getUserReportsById = expressAsyncHandler(async (req, res, next) => { diff --git a/backend/services/report.service.ts b/backend/services/report.service.ts index cd57221..ccf001d 100644 --- a/backend/services/report.service.ts +++ b/backend/services/report.service.ts @@ -179,6 +179,12 @@ export class ReportService { updatedAt: "$report.updatedAt", }, }, + { + $unwind: { + path: "$report.post.item", + preserveNullAndEmptyArrays: true, + }, + }, { $unset: "report" }, { $unset: "postDetails" }, ]; diff --git a/frontend/src/components/Users/UserDetails.tsx b/frontend/src/components/Users/UserDetails.tsx index a363ed7..cff6cf8 100644 --- a/frontend/src/components/Users/UserDetails.tsx +++ b/frontend/src/components/Users/UserDetails.tsx @@ -17,14 +17,14 @@ import { faTrash, } from "@fortawesome/free-solid-svg-icons"; import { FontAwesomeIcon } from "@fortawesome/react-fontawesome"; +import { ReportSummaryItem } from "@pages/Admin/components/ReportSummaryItem"; import { DialogClose } from "@radix-ui/react-dialog"; import { + ApiModel, useGetUserAdminQuery, useToggleUserActiveAdminMutation, useVerifyUserAdminMutation, } from "@services/api"; -import { ApiModel } from "@services/api"; -import { ReportSummaryItem } from "@pages/Admin/components/ReportSummaryItem"; import { getStatusIndicator } from "@utils"; import { useEffect, useState } from "react"; import { useTranslation } from "react-i18next"; @@ -47,13 +47,18 @@ export const UserDetails = ({ const [activeDialogOopen, setActiveDialogOpen] = useState(false); const [serverMessage, setServerMessage] = useState(); const [reason, setReason] = useState(""); + const [unresolvedReports, setUnresolvedReports] = useState( + [], + ); + const [resolvedReports, setResolvedReports] = useState([]); + console.log(unresolvedReports); const [verifyUserApi, { isSuccess: isVerifySuccess, error: verifyError }] = useVerifyUserAdminMutation(); const [toggleUserActive] = useToggleUserActiveAdminMutation(); const { - data: data, + data: reportResponse, isLoading, isError, error: getUserError, @@ -65,20 +70,32 @@ export const UserDetails = ({ } }, [isError]); + useEffect(() => { + const unresolved = reportResponse?.reports?.filter( + (x) => x.status === "unresolved", + )!; + setUnresolvedReports(unresolved); + + const resolved = reportResponse?.reports?.filter( + (x) => x.status === "resolved", + )!; + setResolvedReports(resolved); + }, [reportResponse]); + const onVerify = async () => { - if (!data?.user?.id) { + if (!reportResponse?.user?.id) { onError({ status: 500, message: "User ID must be available" }); return; } - verifyUserApi({ userId: data?.user?.id }); + verifyUserApi({ userId: reportResponse?.user?.id }); }; const onToggleActive = async () => { - if (!data?.user?.id) { + if (!reportResponse?.user?.id) { onError({ status: 500, message: "User ID must be available" }); return; } - toggleUserActive({ userId: data?.user?.id, reason: reason }); + toggleUserActive({ userId: reportResponse?.user?.id, reason: reason }); setReason(""); setActiveDialogOpen(false); }; @@ -111,7 +128,7 @@ export const UserDetails = ({ return

    {t("loading")}

    ; } - if (!data?.user) { + if (!reportResponse?.user) { return

    {t("errors.unknown_server_error")}

    ; } @@ -137,10 +154,9 @@ export const UserDetails = ({ location, organization, activeStatusChangeReason, - } = data?.user!; + } = reportResponse?.user!; - const userReports = data?.reports!; - const verified = data?.user!.organization?.verified ?? true; + const verified = reportResponse?.user!.organization?.verified ?? true; return (
    @@ -214,7 +230,8 @@ export const UserDetails = ({

    {t("users.report_information")}

    - {userReports === undefined || userReports.length === 0 ? ( + {reportResponse?.reports === undefined || + reportResponse?.reports.length === 0 ? (
    - {userReports.map( + {unresolvedReports?.map( ( report, // { @@ -241,13 +258,7 @@ export const UserDetails = ({ // notes: string; // } ) => { - return ( - - ); + return ; }, )} diff --git a/frontend/src/pages/Admin/components/ReportSummaryItem.tsx b/frontend/src/pages/Admin/components/ReportSummaryItem.tsx index f2577cb..b37d6a0 100644 --- a/frontend/src/pages/Admin/components/ReportSummaryItem.tsx +++ b/frontend/src/pages/Admin/components/ReportSummaryItem.tsx @@ -1,29 +1,78 @@ -import { faChevronRight } from "@fortawesome/free-solid-svg-icons"; +import { Badge } from "@components/Badge"; +import autoAnimate from "@formkit/auto-animate"; +import { + faChevronDown, + faChevronRight, +} from "@fortawesome/free-solid-svg-icons"; import { FontAwesomeIcon } from "@fortawesome/react-fontawesome"; - +import { ApiModel } from "@store/services/types"; +import { useEffect, useRef, useState } from "react"; +import { useTranslation } from "react-i18next"; interface ReportSummaryItemProps { - id: string; - summary: string; + report: ApiModel.Report; } -export const ReportSummaryItem = ({ id, summary }: ReportSummaryItemProps) => { +const SummaryItem = (props) => { + const [isExpanded, setIsExpanded] = useState(false); + const parent = useRef(null); + + useEffect(() => { + parent.current && autoAnimate(parent.current); + }, [parent]); + + return ( +
    +
    + +
    + {isExpanded && ( +
    +

    {props.label}

    +
    + {props.children} +
    +
    + )} +
    + ); +}; + +export const ReportSummaryItem = ({ report }: ReportSummaryItemProps) => { + const { t } = useTranslation(); return ( -
  • -
    -
    +
  • +
    +
    -

    - {summary} +

    + {t("reports.reporter")}: + {report.reporter.displayName}

    -
    -
    -
    -
    + +

    {report.notes}

    +
  • ); From 82664eabc947bfee369f3abb3a9a1ec473775143 Mon Sep 17 00:00:00 2001 From: Ryan Date: Sat, 31 Aug 2024 14:39:38 -0400 Subject: [PATCH 8/9] fix: 92 resolve post item being nested --- backend/controllers/admin.controller.ts | 4 +- backend/seeder/seed.reports.ts | 10 ++- backend/services/report.service.ts | 50 +++++--------- frontend/public/locales/en/common.json | 1 + frontend/public/locales/fr/common.json | 1 + frontend/src/components/Users/UserDetails.tsx | 66 ++++++++----------- .../Admin/components/ReportSummaryItem.tsx | 34 +++++++--- 7 files changed, 78 insertions(+), 88 deletions(-) diff --git a/backend/controllers/admin.controller.ts b/backend/controllers/admin.controller.ts index 11e3e61..0038775 100644 --- a/backend/controllers/admin.controller.ts +++ b/backend/controllers/admin.controller.ts @@ -129,11 +129,9 @@ export class AdminController { return; } - - const userDto = UserDto.fromDocument(user); const reportDtos = reports.map((report) => ReportDto.fromAggregate(report)); - console.log(reportDtos); + console.log(JSON.stringify(reportDtos, null, 2)); res.json({ user: userDto, reports: reportDtos }); }); diff --git a/backend/seeder/seed.reports.ts b/backend/seeder/seed.reports.ts index 0d9ac42..6ab814f 100644 --- a/backend/seeder/seed.reports.ts +++ b/backend/seeder/seed.reports.ts @@ -5,12 +5,16 @@ import { PostDocument, ReportDocument, UserDocument } from "../types.js"; export const fakeReport = ( reporter: UserDocument, - post: PostDocument + post: PostDocument, ): ReportDocument => { + // 80% chance of being resolved + const status = + Math.random() <= 0.8 ? ReportStatus.RESOLVED : ReportStatus.UNRESOLVED; + const report = new ReportModel({ reporter: reporter, post: post, - status: ReportStatus.UNRESOLVED, + status: status, notes: faker.word.words({ count: { min: 5, max: 100 } }), }); @@ -20,7 +24,7 @@ export const fakeReport = ( export const seedReports = async ( destroy: boolean, reporter: UserDocument, - posts: PostDocument[] + posts: PostDocument[], ): Promise => { if (destroy) { console.log("🚀 ~ file: seed.reports.ts ~ seedReports ~ destroy:", destroy); diff --git a/backend/services/report.service.ts b/backend/services/report.service.ts index ccf001d..7045d09 100644 --- a/backend/services/report.service.ts +++ b/backend/services/report.service.ts @@ -1,7 +1,6 @@ import { ObjectId } from "mongodb"; import { PipelineStage, Types } from "mongoose"; import { ReportModel } from "../models/reports"; -import { PostModel } from "../models/posts"; import { UserModel } from "../models/users"; import { PostDocument, Report, ReportDocument } from "../types"; export class ReportService { @@ -125,32 +124,25 @@ export class ReportService { } async getUserReports(userId: string): Promise { - const pipeline: PipelineStage[] = [ { $lookup: { from: "posts", localField: "post", foreignField: "_id", - pipeline: [ - { - $match: { - author: new Types.ObjectId(userId) - }, - }, - ], - as: "postDetails", + as: "post", }, }, + { $unwind: "$post" }, { - $unwind: { - path: "$postDetails", + $match: { + "post.author": new Types.ObjectId(userId), }, }, { $group: { _id: "$status", - report: { $push: "$$ROOT" }, + reports: { $push: "$$ROOT" }, count: { $sum: 1 }, }, }, @@ -161,38 +153,28 @@ export class ReportService { createdAt: -1, }, }, - { $unset: ["_id", "count"] }, { - $unwind: { - path: "$report", - preserveNullAndEmptyArrays: true, + $project: { + _id: 0, + reports: 1, }, }, { - $set: { - _id: "$report._id", - reporter: "$report.reporter", - post: "$report.post", - status: "$report.status", - notes: "$report.notes", - createdAt: "$report.createdAt", - updatedAt: "$report.updatedAt", - }, + $unwind: "$reports", }, { - $unwind: { - path: "$report.post.item", - preserveNullAndEmptyArrays: true, + $replaceRoot: { + newRoot: "$reports", }, }, - { $unset: "report" }, - { $unset: "postDetails" }, ]; const reports = await ReportModel.aggregate(pipeline); - // unwind (expand) the post field inside report - await PostModel.populate(reports, { path: 'post' }); - await UserModel.populate(reports, { path: 'reporter' }); + + await UserModel.populate(reports, { path: "reporter" }); + await UserModel.populate(reports, { path: "resolver" }); + + console.log(JSON.stringify(reports, null, 2)); return reports; } diff --git a/frontend/public/locales/en/common.json b/frontend/public/locales/en/common.json index 4909212..24f102b 100644 --- a/frontend/public/locales/en/common.json +++ b/frontend/public/locales/en/common.json @@ -1,5 +1,6 @@ { "title": "Title", + "open": "Open", "add": "Add", "edit": "Edit", "delete": "Delete", diff --git a/frontend/public/locales/fr/common.json b/frontend/public/locales/fr/common.json index 7d7bd5b..5d49869 100644 --- a/frontend/public/locales/fr/common.json +++ b/frontend/public/locales/fr/common.json @@ -1,5 +1,6 @@ { "title": "Titre", + "open": "Ouvrir", "add": "Ajouter", "edit": "Modifier", "delete": "Supprimer", diff --git a/frontend/src/components/Users/UserDetails.tsx b/frontend/src/components/Users/UserDetails.tsx index cff6cf8..55eab10 100644 --- a/frontend/src/components/Users/UserDetails.tsx +++ b/frontend/src/components/Users/UserDetails.tsx @@ -226,45 +226,6 @@ export const UserDetails = ({
    -
    -

    - {t("users.report_information")} -

    - {reportResponse?.reports === undefined || - reportResponse?.reports.length === 0 ? ( -
    - - - {t("reports.no_reports_found")} - -
    - ) : ( -
      - {unresolvedReports?.map( - ( - report, - // { - // id: string; - // reporter: User; - // resolver?: User; - // post: ApiModel.Post; - // status: "resolved" | "unresolved"; - // notes: string; - // } - ) => { - return ; - }, - )} -
    - )} -
    - {location?.postalCode && (

    @@ -424,6 +385,33 @@ export const UserDetails = ({

    + +
    +

    + {t("users.report_information")} +

    + {reportResponse?.reports === undefined || + reportResponse?.reports.length === 0 ? ( +
    + + + {t("reports.no_reports_found")} + +
    + ) : ( +
      + {unresolvedReports?.map((report) => { + return ; + })} +
    + )} +
    ); }; diff --git a/frontend/src/pages/Admin/components/ReportSummaryItem.tsx b/frontend/src/pages/Admin/components/ReportSummaryItem.tsx index b37d6a0..4903dab 100644 --- a/frontend/src/pages/Admin/components/ReportSummaryItem.tsx +++ b/frontend/src/pages/Admin/components/ReportSummaryItem.tsx @@ -1,4 +1,5 @@ import { Badge } from "@components/Badge"; +import { Link } from "@components/Controls"; import autoAnimate from "@formkit/auto-animate"; import { faChevronDown, @@ -8,11 +9,19 @@ import { FontAwesomeIcon } from "@fortawesome/react-fontawesome"; import { ApiModel } from "@store/services/types"; import { useEffect, useRef, useState } from "react"; import { useTranslation } from "react-i18next"; + +interface SummaryItemProps { + label: string; + postId: string; + children: React.ReactNode; +} + interface ReportSummaryItemProps { report: ApiModel.Report; } -const SummaryItem = (props) => { +const SummaryItem = ({ label, postId, children }: SummaryItemProps) => { + const { t } = useTranslation(); const [isExpanded, setIsExpanded] = useState(false); const parent = useRef(null); @@ -22,7 +31,7 @@ const SummaryItem = (props) => { return (
    -
    +
    -
    +
    {isExpanded && ( -
    -

    {props.label}

    -
    - {props.children} -
    +
    +
    + {children} +
    + + {t("open")} +
    )}
    @@ -51,6 +63,10 @@ const SummaryItem = (props) => { export const ReportSummaryItem = ({ report }: ReportSummaryItemProps) => { const { t } = useTranslation(); + const categoryString = + t(`posts.item_categories.${report.post.item.category}`) || ""; + const summaryLabel = `[${categoryString}] ${report.post.item.name}`; + return (
  • { text={t(`reports.${report.status}`)} />
  • - +

    {report.notes}

    From 460b02887e41abb24b38551da0a53810361ad56b Mon Sep 17 00:00:00 2001 From: Youngsun Yoo Date: Sat, 31 Aug 2024 16:55:05 -0600 Subject: [PATCH 9/9] minor-change: remove all console statements --- backend/controllers/admin.controller.ts | 1 - backend/controllers/report.controller.ts | 1 - backend/services/report.service.ts | 2 -- frontend/src/components/Users/UserDetails.tsx | 1 - frontend/src/pages/Account/components/UpdateUserInfoForm.tsx | 1 - frontend/src/pages/Admin/ReportsByUser.tsx | 3 --- 6 files changed, 9 deletions(-) diff --git a/backend/controllers/admin.controller.ts b/backend/controllers/admin.controller.ts index 0038775..4457fa5 100644 --- a/backend/controllers/admin.controller.ts +++ b/backend/controllers/admin.controller.ts @@ -131,7 +131,6 @@ export class AdminController { const userDto = UserDto.fromDocument(user); const reportDtos = reports.map((report) => ReportDto.fromAggregate(report)); - console.log(JSON.stringify(reportDtos, null, 2)); res.json({ user: userDto, reports: reportDtos }); }); diff --git a/backend/controllers/report.controller.ts b/backend/controllers/report.controller.ts index 7c96cb4..102cc81 100644 --- a/backend/controllers/report.controller.ts +++ b/backend/controllers/report.controller.ts @@ -98,7 +98,6 @@ export class ReportController { const { id } = req.params; const reports = await this.reportService.getReportedPost(id); - console.log(reports); if (!reports) { throw new NotFoundError(`Error finding report ${id}.`); diff --git a/backend/services/report.service.ts b/backend/services/report.service.ts index 7045d09..983a61d 100644 --- a/backend/services/report.service.ts +++ b/backend/services/report.service.ts @@ -174,8 +174,6 @@ export class ReportService { await UserModel.populate(reports, { path: "reporter" }); await UserModel.populate(reports, { path: "resolver" }); - console.log(JSON.stringify(reports, null, 2)); - return reports; } } diff --git a/frontend/src/components/Users/UserDetails.tsx b/frontend/src/components/Users/UserDetails.tsx index 55eab10..d07f834 100644 --- a/frontend/src/components/Users/UserDetails.tsx +++ b/frontend/src/components/Users/UserDetails.tsx @@ -51,7 +51,6 @@ export const UserDetails = ({ [], ); const [resolvedReports, setResolvedReports] = useState([]); - console.log(unresolvedReports); const [verifyUserApi, { isSuccess: isVerifySuccess, error: verifyError }] = useVerifyUserAdminMutation(); diff --git a/frontend/src/pages/Account/components/UpdateUserInfoForm.tsx b/frontend/src/pages/Account/components/UpdateUserInfoForm.tsx index bd59ef6..3f5a8df 100644 --- a/frontend/src/pages/Account/components/UpdateUserInfoForm.tsx +++ b/frontend/src/pages/Account/components/UpdateUserInfoForm.tsx @@ -63,7 +63,6 @@ export const UpdateUserInfoForm = () => { // handle server error message useEffect(() => { if (error) { - console.log(error); if ("status" in error) { const err: any = "error" in error ? error.error : error.data; setServerMessage(err.errors.join(",") ?? "An error occurred"); diff --git a/frontend/src/pages/Admin/ReportsByUser.tsx b/frontend/src/pages/Admin/ReportsByUser.tsx index 8f66006..dcd1949 100644 --- a/frontend/src/pages/Admin/ReportsByUser.tsx +++ b/frontend/src/pages/Admin/ReportsByUser.tsx @@ -55,12 +55,9 @@ export const ReportsByUserPage = () => { // useMemo to filter reportedPosts by loggedInUserId const filteredReportedPosts = useMemo(() => { - console.log("Reported Posts:", reportedPostsResponse?.data); - console.log("Logged In User ID:", loggedInUserId); // Assuming each reportedPost has a post.author.id you want to match against loggedInUserId return ( reportedPostsResponse?.data.filter((reportedPost) => { - console.log("Post Author ID:", reportedPost.post.author.id); // Debug log return reportedPost.post.author.id !== loggedInUserId; }) || [] );