-
Notifications
You must be signed in to change notification settings - Fork 0
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
[SOA-5] Review reported content (#34)
* feat 🎸 (be): add routing for post moderation index * styles 💅 : add post moderation index page * styles 💅 (wip): progress on update status form * feat 🎸 (be): controlled serialisation and route for updating status * fix ✅ (be): uniqueness constraint correction * styles 💅 : adaptions for the admin update status interface * feat 🎸 (be): update post status and add visibility scope to post * fix ✅ (be): scoped field name * perf ⚡️ : improve feed list performance with partial prop reload * styles 💅 : style adjustments on modal card-preview * styles 💅 : nit and decompose admin header * fix ✅ (be): correction to middleware and missing cascade * test 🧪 : add minimal admin_post_report tests * fix ✅ (be): add new migration for the fields update
- Loading branch information
1 parent
dfe2fd7
commit b185878
Showing
30 changed files
with
1,493 additions
and
51 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -17,3 +17,4 @@ AWS_ACCESS_KEY_ID= | |
AWS_SECRET_ACCESS_KEY= | ||
AWS_REGION= | ||
S3_BUCKET= | ||
VITE_APP_NAME=SocialAdonis |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,59 @@ | ||
import { PageObject } from '@adonisjs/inertia/types' | ||
import { inject } from '@adonisjs/core' | ||
import AdminPostReportService from '#services/admin_post_report_service' | ||
import { PaginatedResponse } from '#interfaces/pagination' | ||
import { PostReportResponse } from '#interfaces/post' | ||
import { adminUpdatePostReportValidator } from '#validators/post_report' | ||
import PostReport from '#models/post_report' | ||
import { errorsReducer } from '#utils/index' | ||
import { errors } from '@vinejs/vine' | ||
import type { HttpContext } from '@adonisjs/core/http' | ||
|
||
@inject() | ||
export default class AdminPostReportsController { | ||
constructor(private readonly service: AdminPostReportService) {} | ||
|
||
async index(ctx: HttpContext): Promise< | ||
| string | ||
| PageObject<{ | ||
reports: PaginatedResponse<PostReportResponse> | ||
}> | ||
> { | ||
const currentUserId = ctx.auth.user?.id! | ||
const page = ctx.request.qs().page || 1 | ||
|
||
const filters: Record<'reason' | 'status', string[] | null> = { | ||
reason: ctx.request.qs().reason ? [ctx.request.qs().reason].flat() : null, | ||
status: ctx.request.qs().status ? [ctx.request.qs().status].flat() : null, | ||
} | ||
|
||
const reports = await this.service.index(currentUserId, filters, page) | ||
|
||
return ctx.inertia.render('admin/post_reports/index', { | ||
reports, | ||
}) | ||
} | ||
|
||
async update(ctx: HttpContext) { | ||
const reportId = ctx.request.params().id | ||
const report = await PostReport.findOrFail(reportId) | ||
|
||
if (await ctx.bouncer.with('PostReportPolicy').denies('edit', report)) { | ||
return ctx.response.forbidden('Only admin is able to take action on report status.') | ||
} | ||
|
||
try { | ||
const payload = ctx.request.body() | ||
const data = await adminUpdatePostReportValidator.validate(payload) | ||
report.status = data.status | ||
await report.save() | ||
return this.index(ctx) | ||
} catch (error) { | ||
if (error instanceof errors.E_VALIDATION_ERROR) { | ||
const reducedErrors = errorsReducer(error.messages) | ||
return ctx.response.badRequest(reducedErrors) | ||
} | ||
return ctx.response.badRequest() | ||
} | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,72 @@ | ||
import { PaginatedResponse } from '#interfaces/pagination' | ||
import { PostReportResponse } from '#interfaces/post' | ||
import PostReport from '#models/post_report' | ||
import PostsService from '#services/posts_service' | ||
import { UserService } from '#services/user_service' | ||
import { inject } from '@adonisjs/core' | ||
import { UUID } from 'node:crypto' | ||
|
||
@inject() | ||
export default class AdminPostReportService { | ||
constructor( | ||
private readonly userService: UserService, | ||
private readonly postService: PostsService | ||
) {} | ||
|
||
async index( | ||
currentUserId: UUID, | ||
filters: Record<'reason' | 'status', string[] | null>, | ||
currentPage: number | ||
): Promise<PaginatedResponse<PostReportResponse>> { | ||
let query = PostReport.query() | ||
.orderBy('updated_at', 'desc') | ||
.preload('post', (post) => { | ||
post.preload('user') | ||
post.withCount('reports', (q) => q.as('reportsCount')) | ||
}) | ||
.preload('user') | ||
|
||
if (filters.reason) { | ||
query.whereIn('reason', filters.reason) | ||
} | ||
|
||
if (filters.status) { | ||
query.whereIn('status', filters.status) | ||
} | ||
|
||
const result = await query.paginate(currentPage, 10) | ||
|
||
const { meta } = result.toJSON() | ||
|
||
const serialized = [] | ||
for (const record of result) { | ||
const resource = await this.serialize(currentUserId, record) | ||
serialized.push(resource) | ||
} | ||
|
||
return { | ||
data: serialized, | ||
meta, | ||
} | ||
} | ||
|
||
private async serialize(currentUserId: UUID, report: PostReport): Promise<PostReportResponse> { | ||
const user = await this.userService.serialize(report.user) | ||
const post = await this.postService.serialize(currentUserId, report.post) | ||
const data = report.toJSON() | ||
|
||
const resource: PostReportResponse = { | ||
id: report.id, | ||
postId: data.postId, | ||
userId: data.userId, | ||
reason: data.reason, | ||
status: data.status, | ||
description: data.description, | ||
post: { ...post, reportCount: report.post.$extras.reportsCount }, | ||
user, | ||
updatedAt: data.updatedAt, | ||
createdAt: data.createdAt, | ||
} | ||
return resource | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1,15 +1,17 @@ | ||
import factory from '@adonisjs/lucid/factories' | ||
import User from '#models/user' | ||
import User, { AccountRole } from '#models/user' | ||
import { PostFactory } from '#database/factories/post_factory' | ||
|
||
export const UserFactory = factory | ||
.define(User, async ({ faker }) => { | ||
return { | ||
role: AccountRole.USER, | ||
name: faker.person.firstName(), | ||
surname: faker.person.lastName(), | ||
email: faker.internet.email(), | ||
password: faker.internet.password(), | ||
} | ||
}) | ||
.state('admin', (user) => (user.role = AccountRole.ADMIN)) | ||
.relation('posts', () => PostFactory) | ||
.build() |
21 changes: 21 additions & 0 deletions
21
...grations/1732037163235_create_add_uniqueness_to_post_reports_and_delete_cascades_table.ts
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,21 @@ | ||
import { BaseSchema } from '@adonisjs/lucid/schema' | ||
|
||
export default class extends BaseSchema { | ||
protected tableName = 'post_reports' | ||
|
||
async up() { | ||
this.schema.alterTable(this.tableName, (table) => { | ||
table.dropForeign('post_id'); | ||
table.uuid('post_id').references('posts.id').notNullable().onDelete('CASCADE').alter() | ||
table.unique(['user_id', 'post_id']) | ||
}) | ||
} | ||
|
||
async down() { | ||
this.schema.alterTable(this.tableName, (table) => { | ||
table.dropForeign('post_id'); | ||
table.uuid('post_id').references('posts.id').notNullable().alter() | ||
table.dropUnique(['user_id', 'post_id']) | ||
}) | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Oops, something went wrong.