feat(Moderation): implement moderation reports feature
All checks were successful
Deploy to production / deploy (push) Successful in 2m1s

- Added moderationRouter to handle moderation-related API routes.
- Introduced new methods in AdminController for fetching all regions, region types, and creating regions.
- Enhanced adminRouter with routes for moderation reports and status updates.
- Updated navigationController to include moderation reports in the admin menu.
- Implemented frontend components for reporting messages in the forum and managing moderation reports.
- Added internationalization support for moderation-related texts in multiple languages.
This commit is contained in:
Torsten Schulz (local)
2026-04-27 14:52:19 +02:00
parent 7fc9b55b59
commit a02fe1f008
36 changed files with 1162 additions and 17 deletions

View File

@@ -22,6 +22,10 @@ import RegionData from "../models/falukant/data/region.js";
import RegionType from "../models/falukant/type/region.js";
import BranchType from "../models/falukant/type/branch.js";
import RegionDistance from "../models/falukant/data/region_distance.js";
import ProductType from "../models/falukant/type/product.js";
import TownProductWorth from "../models/falukant/data/town_product_worth.js";
import Weather from "../models/falukant/data/weather.js";
import WeatherType from "../models/falukant/type/weather.js";
import Room from '../models/chat/room.js';
import UserParam from '../models/community/user_param.js';
import Image from '../models/community/image.js';
@@ -753,6 +757,129 @@ class AdminService {
return regions;
}
async getFalukantAllRegions(userId) {
if (!(await this.hasUserAccess(userId, 'falukantusers'))) {
throw new Error('noaccess');
}
const regions = await RegionData.findAll({
attributes: ['id', 'name', 'map', 'regionTypeId', 'parentId'],
include: [
{
model: RegionType,
as: 'regionType',
attributes: ['id', 'labelTr', 'parentId'],
},
],
order: [['name', 'ASC']],
});
return regions;
}
async getFalukantRegionTypes(userId) {
if (!(await this.hasUserAccess(userId, 'falukantusers'))) {
throw new Error('noaccess');
}
const types = await RegionType.findAll({
attributes: ['id', 'labelTr', 'parentId'],
order: [['labelTr', 'ASC']],
});
return types;
}
async createFalukantRegion(userId, { name, regionTypeId, parentId } = {}) {
if (!(await this.hasUserAccess(userId, 'falukantusers'))) {
throw new Error('noaccess');
}
const regionName = String(name || '').trim();
const typeId = Number(regionTypeId);
const parentRegionId = parentId == null || parentId === '' ? null : Number(parentId);
if (!regionName) {
throw new Error('missingName');
}
if (!Number.isInteger(typeId) || typeId <= 0) {
throw new Error('missingRegionType');
}
if (parentRegionId != null && (!Number.isInteger(parentRegionId) || parentRegionId <= 0)) {
throw new Error('invalidParent');
}
const regionType = await RegionType.findByPk(typeId, { attributes: ['id', 'labelTr', 'parentId'] });
if (!regionType) {
throw new Error('regionTypeNotFound');
}
let parentRegion = null;
if (parentRegionId != null) {
parentRegion = await RegionData.findByPk(parentRegionId, { attributes: ['id', 'regionTypeId'] });
if (!parentRegion) {
throw new Error('parentRegionNotFound');
}
}
if (regionType.parentId != null) {
if (parentRegionId == null) {
throw new Error('missingParent');
}
if (!parentRegion || parentRegion.regionTypeId !== regionType.parentId) {
throw new Error('invalidParentRegionType');
}
} else if (parentRegionId != null) {
throw new Error('parentNotAllowedForType');
}
const randomWorthPercent = () => Math.floor(Math.random() * 31) + 55; // 5585
const createdId = await sequelize.transaction(async (t) => {
const region = await RegionData.create(
{
name: regionName,
regionTypeId: regionType.id,
parentId: parentRegionId,
map: {},
},
{ transaction: t }
);
const products = await ProductType.findAll({ attributes: ['id'], transaction: t });
if (products.length > 0) {
await TownProductWorth.bulkCreate(
products.map((p) => ({
productId: p.id,
regionId: region.id,
worthPercent: randomWorthPercent(),
})),
{ transaction: t, ignoreDuplicates: true }
);
}
if (regionType.labelTr === 'city') {
const weatherTypes = await WeatherType.findAll({ attributes: ['id'], transaction: t });
if (weatherTypes.length > 0) {
const randomWeatherType = weatherTypes[Math.floor(Math.random() * weatherTypes.length)];
await Weather.findOrCreate({
where: { regionId: region.id },
defaults: { weatherTypeId: randomWeatherType.id },
transaction: t,
});
}
}
return region.id;
});
const created = await RegionData.findByPk(createdId, {
attributes: ['id', 'name', 'map', 'regionTypeId', 'parentId'],
include: [{ model: RegionType, as: 'regionType', attributes: ['id', 'labelTr', 'parentId'] }],
});
return created;
}
async getTitlesOfNobility(userId) {
if (!(await this.hasUserAccess(userId, 'falukantusers'))) {
throw new Error('noaccess');