flag stuff

Signed-off-by: MiniDigger <admin@minidigger.me>
This commit is contained in:
MiniDigger 2021-03-22 20:13:28 +01:00
parent 4327d485ba
commit 68b1949019
14 changed files with 248 additions and 88 deletions

View File

@ -39,19 +39,25 @@ export default class FlagModal extends mixins(HangarFormModal, HangarProjectMixi
submitFlag() {
this.loading = true;
// TODO flag endpoint
setTimeout(
(self: FlagModal) => {
self.loading = false;
self.dialog = false;
},
1000,
this
);
this.$api
.requestInternal('flags/', true, 'POST', {
project_id: this.project.id,
reason: this.form.selection,
comment: this.form.comment,
})
.then(() => {
this.loading = false;
this.dialog = false;
this.$util.success(this.$t('project.flag.flagSend'));
})
.catch((e) => {
this.$util.handleRequestError(e);
this.loading = false;
});
}
async fetch() {
this.flagReasons.push(...(await this.$api.requestInternal<FlagReason[]>('data/flagReasons', true)));
this.flagReasons.push(...(await this.$api.requestInternal<FlagReason[]>('data/flagReasons', true).catch(this.$util.handleRequestError)));
}
}
</script>

View File

@ -102,6 +102,7 @@ const msgs: LocaleMessageObject = {
},
flag: {
flagProject: 'Flag {0}?',
flagSend: 'Successfully flagged, thanks for making this community a better place!',
},
tabs: {
docs: 'Docs',
@ -590,6 +591,8 @@ const msgs: LocaleMessageObject = {
msgProjectOwner: 'Message owner',
markResolved: 'Mark resolved',
visibilityActions: 'Visibility actions',
line1: '{0} reported {1} on {2}',
line2: 'Reason: {0}, Comment: {1}',
},
validation: {
required: '{0} is required',

View File

@ -6,11 +6,11 @@
</v-card-title>
<v-card-text>
<v-data-table v-if="flags && flags.length > 0" :headers="headers" :items="flags" disable-filtering disable-sort hide-default-footer>
<template #item.user="{ item }">{{ item.user.name }}</template>
<template #item.reason="{ item }">{{ item.reason.title }}</template>
<template #item.createdAt="{ item }">{{ $util.prettyDate(item.createdAt) }}</template>
<template #item.user="{ item }">{{ item.reportedByName }}</template>
<template #item.reason="{ item }">{{ item.reason }}</template>
<template #item.createdAt="{ item }">{{ $util.prettyDateTime(item.createdAt) }}</template>
<template #item.resolved="{ item }">
<span v-if="item.resolved">{{ $t('flags.resolved', [item.resolvedBy.name, $util.prettyDate(item.resolvedAt)]) }}</span>
<span v-if="item.resolved">{{ $t('flags.resolved', [item.resolvedByName, $util.prettyDate(item.resolvedAt)]) }}</span>
<span v-else v-text="$t('flags.notResolved')"></span>
</template>
</v-data-table>
@ -23,6 +23,7 @@
import { Component, Prop, Vue } from 'nuxt-property-decorator';
import { Project } from 'hangar-api';
import { Flag } from 'hangar-internal';
import { Context } from '@nuxt/types';
import { GlobalPermission } from '~/utils/perms';
import { NamedPermission } from '~/types/enums';
@ -32,19 +33,7 @@ export default class ProjectFlagsPage extends Vue {
@Prop({ required: true })
project!: Project;
// todo load flags
flags: Array<Flag> = [
{
id: 1,
createdAt: '2021-02-13T12:18:52.456Z',
user: this.$util.dummyUser(),
comment: 'Naughty',
reason: { title: 'Test', type: 'Test' },
resolved: true,
resolvedAt: '2021-02-13T12:18:52.456Z',
resolvedBy: this.$util.dummyUser(),
},
] as Array<Flag>;
flags!: Flag[];
get headers() {
return [
@ -55,6 +44,11 @@ export default class ProjectFlagsPage extends Vue {
{ text: 'Resolved', value: 'resolved' },
];
}
async asyncData({ $api, $util }: Context) {
const flags = await $api.requestInternal<Flag[]>(`flags/`, false).catch<any>($util.handlePageRequestError);
return { flags };
}
}
</script>

View File

@ -5,17 +5,17 @@
<v-list v-if="flags.length > 0">
<v-list-item v-for="flag in flags" :key="flag.id">
<v-list-item-avatar>
<UserAvatar :username="flag.user.name" clazz="user-avatar-xs"></UserAvatar>
<UserAvatar :username="flag.reportedByName" clazz="user-avatar-xs"></UserAvatar>
</v-list-item-avatar>
<!-- todo flag content -->
<v-list-item-content>
<v-list-item-title>dddddddd</v-list-item-title>
<v-list-item-title>dddddddd</v-list-item-title>
<v-list-item-title>{{
$t('flagReview.line1', [flag.reportedByName, flag.projectNamespace, $util.prettyDateTime(flag.createdAt)])
}}</v-list-item-title>
<v-list-item-subtitle>{{ $t('flagReview.line2', [flag.reason, flag.comment]) }}</v-list-item-subtitle>
</v-list-item-content>
<v-list-item-action>
<v-btn small :to="$util.forumUrl(flag.user.name)"><v-icon>mdi-reply</v-icon>{{ $t('flagReview.msgUser') }}</v-btn>
<!-- todo the subject isnt part of the model? -->
<v-btn small :to="$util.forumUrl(flag.user.name)"><v-icon>mdi-reply</v-icon>{{ $t('flagReview.msgProjectOwner') }}</v-btn>
<v-btn small :href="$util.forumUrl(flag.reportedByName)"><v-icon>mdi-reply</v-icon>{{ $t('flagReview.msgUser') }}</v-btn>
<v-btn small :href="$util.forumUrl(flag.projectOwnerName)"><v-icon>mdi-reply</v-icon>{{ $t('flagReview.msgProjectOwner') }}</v-btn>
<v-menu offset-y>
<template #activator="{ on, attrs }">
<v-btn small v-bind="attrs" v-on="on"><v-icon>mdi-eye</v-icon>{{ $t('flagReview.visibilityActions') }}</v-btn>
@ -38,6 +38,7 @@
<script lang="ts">
import { Component, Vue } from 'nuxt-property-decorator';
import { Flag } from 'hangar-internal';
import { Context } from '@nuxt/types';
import UserAvatar from '~/components/users/UserAvatar.vue';
import { NamedPermission, Visibility } from '~/types/enums';
import { GlobalPermission } from '~/utils/perms';
@ -47,19 +48,12 @@ import { GlobalPermission } from '~/utils/perms';
})
@GlobalPermission(NamedPermission.MOD_NOTES_AND_FLAGS)
export default class AdminFlagsPage extends Vue {
// todo load from server
flags: Flag[] = [
{
createdAt: new Date().toString(),
id: 1,
resolvedBy: this.$util.dummyUser(),
resolved: false,
resolvedAt: '',
user: this.$util.dummyUser(),
reason: { type: 'TEST', title: 'TEST' },
comment: 'Test',
},
];
flags!: Flag[];
async asyncData({ $api, $util }: Context) {
const flags = await $api.requestInternal<Flag[]>(`flags/`, false).catch<any>($util.handlePageRequestError);
return { flags };
}
get visibilities(): Visibility[] {
return Object.keys(Visibility) as Visibility[];

View File

@ -1,7 +1,7 @@
declare module 'hangar-internal' {
import { FlagReason, Joinable, Table } from 'hangar-internal';
import { Project, User } from 'hangar-api';
import { ProjectCategory } from '~/types/enums';
import { ProjectCategory, Visibility } from '~/types/enums';
interface ProjectOwner {
id: number;
@ -42,12 +42,19 @@ declare module 'hangar-internal' {
}
interface Flag extends Table {
user: User;
userId: number;
reportedByName: string;
reason: FlagReason;
resolved: boolean;
isResolved: boolean;
comment: string;
resolvedAt: string;
resolvedBy: User;
resolvedBy: number;
resolvedByName: string;
projectId: number;
projectOwnerName: string;
projectSlug: string;
projectNamespace: string;
projectVisibility: Visibility;
}
interface Note extends Table {

View File

@ -0,0 +1,51 @@
package io.papermc.hangar.controller.internal;
import org.springframework.http.HttpStatus;
import org.springframework.http.MediaType;
import org.springframework.security.access.annotation.Secured;
import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.PathVariable;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RequestBody;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.ResponseBody;
import org.springframework.web.bind.annotation.ResponseStatus;
import java.util.List;
import io.papermc.hangar.controller.HangarController;
import io.papermc.hangar.controllerold.forms.FlagForm;
import io.papermc.hangar.model.internal.projects.HangarProjectFlag;
import io.papermc.hangar.service.internal.admin.FlagService;
@Controller
@Secured("ROLE_USER")
@RequestMapping(path = "/api/internal/flags", produces = MediaType.APPLICATION_JSON_VALUE)
public class FlagController extends HangarController {
private final FlagService flagService;
public FlagController(FlagService flagService) {
this.flagService = flagService;
}
@PostMapping("/")
@ResponseStatus(HttpStatus.NO_CONTENT)
public void flag(@RequestBody FlagForm form) {
flagService.createFlag(form.getProjectId(), getHangarUserId(), form.getReason(), form.getComment());
}
@ResponseBody
@GetMapping("/{author}/{slug}")
public List<HangarProjectFlag> getFlags(@PathVariable("author") String author, @PathVariable("slug") String slug) {
return flagService.getFlags(author, slug);
}
@ResponseBody
@GetMapping("/")
public List<HangarProjectFlag> getFlags() {
return flagService.getFlags();
}
}

View File

@ -0,0 +1,46 @@
package io.papermc.hangar.controllerold.forms;
import com.fasterxml.jackson.annotation.JsonCreator;
import com.fasterxml.jackson.annotation.JsonProperty;
import org.jetbrains.annotations.NotNull;
import java.util.StringJoiner;
import io.papermc.hangar.model.common.projects.FlagReason;
public class FlagForm {
private final String comment;
private final long projectId;
private final FlagReason reason;
@JsonCreator(mode = JsonCreator.Mode.PROPERTIES)
public FlagForm(@JsonProperty(value = "comment", required = true) @NotNull String comment,
@JsonProperty(value = "project_id", required = true) long projectId,
@JsonProperty(value = "reason", required = true) @NotNull FlagReason reason) {
this.comment = comment;
this.projectId = projectId;
this.reason = reason;
}
public String getComment() {
return comment;
}
public long getProjectId() {
return projectId;
}
public FlagReason getReason() {
return reason;
}
@Override
public String toString() {
return new StringJoiner(", ", FlagForm.class.getSimpleName() + "[", "]")
.add("comment='" + comment + "'")
.add("projectId=" + projectId)
.add("reason=" + reason)
.toString();
}
}

View File

@ -0,0 +1,44 @@
package io.papermc.hangar.db.dao.internal;
import org.jdbi.v3.sqlobject.config.RegisterConstructorMapper;
import org.jdbi.v3.sqlobject.customizer.BindBean;
import org.jdbi.v3.sqlobject.customizer.Timestamped;
import org.jdbi.v3.sqlobject.statement.GetGeneratedKeys;
import org.jdbi.v3.sqlobject.statement.SqlQuery;
import org.jdbi.v3.sqlobject.statement.SqlUpdate;
import org.springframework.stereotype.Repository;
import java.util.List;
import io.papermc.hangar.model.db.projects.ProjectFlagTable;
import io.papermc.hangar.model.internal.projects.HangarProjectFlag;
@Repository
public interface HangarFlagsDAO {
@Timestamped
@GetGeneratedKeys
@RegisterConstructorMapper(ProjectFlagTable.class)
@SqlUpdate("INSERT INTO project_flags (created_at, project_id, user_id, reason, comment) VALUES (:now, :projectId, :userId, :reason, :comment)")
ProjectFlagTable insert(@BindBean ProjectFlagTable projectFlagsTable);
@RegisterConstructorMapper(HangarProjectFlag.class)
@SqlQuery("SELECT pf.*, fu.name reported_by_name, ru.name resolved_by_name, p.owner_name project_owner_name, p.slug project_slug, p.visibility project_visibility " +
"FROM project_flags pf " +
" JOIN projects p ON pf.project_id = p.id " +
" JOIN users fu ON pf.user_id = fu.id " +
" LEFT OUTER JOIN users ru ON ru.id = pf.resolved_by " +
"WHERE lower(p.owner_name) = lower(:author) AND lower(p.slug) = lower(:slug)" +
"GROUP BY pf.id, fu.id, ru.id, p.id")
List<HangarProjectFlag> getFlags(String author, String slug);
@RegisterConstructorMapper(HangarProjectFlag.class)
@SqlQuery("SELECT pf.*, fu.name reported_by_name, ru.name resolved_by_name, p.owner_name project_owner_name, p.slug project_slug, p.visibility project_visibility " +
"FROM project_flags pf " +
" JOIN projects p ON pf.project_id = p.id " +
" JOIN users fu ON pf.user_id = fu.id " +
" LEFT OUTER JOIN users ru ON ru.id = pf.resolved_by " +
"WHERE NOT pf.resolved " +
"GROUP BY pf.id, fu.id, ru.id, p.id")
List<HangarProjectFlag> getFlags();
}

View File

@ -7,7 +7,6 @@ import io.papermc.hangar.model.db.UserTable;
import io.papermc.hangar.model.db.roles.ProjectRoleTable;
import io.papermc.hangar.model.internal.projects.HangarChannel;
import io.papermc.hangar.model.internal.projects.HangarProject.HangarProjectInfo;
import io.papermc.hangar.model.internal.projects.HangarProjectFlag;
import io.papermc.hangar.model.internal.user.JoinableMember;
import org.apache.commons.lang3.tuple.Pair;
import org.jdbi.v3.sqlobject.config.RegisterColumnMapper;
@ -89,20 +88,6 @@ public interface HangarProjectsDAO {
" GROUP BY p.id")
HangarProjectInfo getHangarProjectInfo(long projectId);
@RegisterConstructorMapper(HangarProjectFlag.class)
@SqlQuery("SELECT pf.*, " +
" u.name reported_by_name," +
" u2.name resolved_by_name," +
" p.owner_name project_owner_name," +
" p.slug project_slug," +
" p.visibility project_visibility" +
" FROM project_flags pf" +
" JOIN users u ON pf.user_id = u.id" +
" LEFT JOIN users u2 ON pf.resolved_by = u2.id" +
" JOIN projects p ON pf.project_id = p.id" +
" WHERE lower(p.owner_name) = lower(:author) AND lower(p.slug) = lower(:slug)")
List<HangarProjectFlag> getHangarProjectFlags(String author, String slug);
@RegisterConstructorMapper(HangarChannel.class)
@SqlQuery("SELECT pc.*," +
" (SELECT count(*) FROM project_versions pv WHERE pv.channel_id = pc.id) as version_count" +

View File

@ -12,18 +12,18 @@ public class ProjectFlagTable extends Table {
private final long projectId;
private final long userId;
private final FlagReason reason;
private boolean isResolved;
private boolean resolved;
private final String comment;
private OffsetDateTime resolvedAt;
private long resolvedBy;
@JdbiConstructor
public ProjectFlagTable(OffsetDateTime createdAt, long id, long projectId, long userId, @EnumByOrdinal FlagReason reason, boolean isResolved, String comment, OffsetDateTime resolvedAt, long resolvedBy) {
public ProjectFlagTable(OffsetDateTime createdAt, long id, long projectId, long userId, @EnumByOrdinal FlagReason reason, boolean resolved, String comment, OffsetDateTime resolvedAt, long resolvedBy) {
super(createdAt, id);
this.projectId = projectId;
this.userId = userId;
this.reason = reason;
this.isResolved = isResolved;
this.resolved = resolved;
this.comment = comment;
this.resolvedAt = resolvedAt;
this.resolvedBy = resolvedBy;
@ -50,11 +50,11 @@ public class ProjectFlagTable extends Table {
}
public boolean isResolved() {
return isResolved;
return resolved;
}
public void setResolved(boolean resolved) {
isResolved = resolved;
this.resolved = resolved;
}
public String getComment() {
@ -80,13 +80,13 @@ public class ProjectFlagTable extends Table {
@Override
public String toString() {
return "ProjectFlagTable{" +
"projectId=" + projectId +
", userId=" + userId +
", reason=" + reason +
", isResolved=" + isResolved +
", comment='" + comment + '\'' +
", resolvedAt=" + resolvedAt +
", resolvedBy=" + resolvedBy +
"} " + super.toString();
"projectId=" + projectId +
", userId=" + userId +
", reason=" + reason +
", isResolved=" + resolved +
", comment='" + comment + '\'' +
", resolvedAt=" + resolvedAt +
", resolvedBy=" + resolvedBy +
"} " + super.toString();
}
}

View File

@ -15,8 +15,8 @@ public class HangarProjectFlag extends ProjectFlagTable {
private final String projectSlug;
private final Visibility projectVisibility;
public HangarProjectFlag(OffsetDateTime createdAt, long id, long projectId, long userId, FlagReason reason, boolean isResolved, String comment, OffsetDateTime resolvedAt, long resolvedBy, String reportedByName, String resolvedByName, String projectOwnerName, String projectSlug, @EnumByOrdinal Visibility projectVisibility) {
super(createdAt, id, projectId, userId, reason, isResolved, comment, resolvedAt, resolvedBy);
public HangarProjectFlag(OffsetDateTime createdAt, long id, long projectId, long userId, @EnumByOrdinal FlagReason reason, boolean resolved, String comment, OffsetDateTime resolvedAt, long resolvedBy, String reportedByName, String resolvedByName, String projectOwnerName, String projectSlug, @EnumByOrdinal Visibility projectVisibility) {
super(createdAt, id, projectId, userId, reason, resolved, comment, resolvedAt, resolvedBy);
this.reportedByName = reportedByName;
this.resolvedByName = resolvedByName;
this.projectOwnerName = projectOwnerName;

View File

@ -0,0 +1,35 @@
package io.papermc.hangar.service.internal.admin;
import org.springframework.stereotype.Service;
import java.util.List;
import io.papermc.hangar.db.dao.HangarDao;
import io.papermc.hangar.db.dao.internal.HangarFlagsDAO;
import io.papermc.hangar.model.common.projects.FlagReason;
import io.papermc.hangar.model.db.projects.ProjectFlagTable;
import io.papermc.hangar.model.internal.projects.HangarProjectFlag;
import io.papermc.hangar.service.HangarService;
@Service
public class FlagService extends HangarService {
private final HangarFlagsDAO flagsDAO;
public FlagService(HangarDao<HangarFlagsDAO> flagsDAO) {
this.flagsDAO = flagsDAO.get();
}
public void createFlag(long projectId, long userId, FlagReason reason, String comment) {
// TODO idk, we prolly need more checking here, plus notification? logs?
flagsDAO.insert(new ProjectFlagTable( projectId, userId, reason, comment));
}
public List<HangarProjectFlag> getFlags(String author, String slug) {
return flagsDAO.getFlags(author, slug);
}
public List<HangarProjectFlag> getFlags() {
return flagsDAO.getFlags();
}
}

View File

@ -22,7 +22,6 @@ import io.papermc.hangar.model.internal.api.requests.EditMembersForm.Member;
import io.papermc.hangar.model.internal.api.requests.projects.ProjectSettingsForm;
import io.papermc.hangar.model.internal.projects.HangarProject;
import io.papermc.hangar.model.internal.projects.HangarProject.HangarProjectInfo;
import io.papermc.hangar.model.internal.projects.HangarProjectFlag;
import io.papermc.hangar.model.internal.projects.HangarProjectPage;
import io.papermc.hangar.service.HangarService;
import io.papermc.hangar.service.PermissionService;
@ -226,11 +225,6 @@ public class ProjectService extends HangarService {
consumer.accept(member, projectRoleTable);
}
// TODO implement flag view
public List<HangarProjectFlag> getHangarProjectFlags(String author, String slug) {
return hangarProjectsDAO.getHangarProjectFlags(author, slug);
}
public void refreshHomeProjects() {
hangarProjectsDAO.refreshHomeProjects();
}

View File

@ -13,7 +13,8 @@ import java.time.OffsetDateTime;
import java.util.List;
import java.util.stream.Collectors;
@Service
@Deprecated
@Service("flagServiceOld")
public class FlagService extends HangarService {
private final HangarDao<FlagDao> flagDao;