project notes backend

This commit is contained in:
Jake Potrebic 2021-03-27 18:52:45 -07:00
parent 027b5b0bb2
commit fd3a882507
No known key found for this signature in database
GPG Key ID: 7C58557EC9C421F8
21 changed files with 258 additions and 284 deletions

View File

@ -497,7 +497,7 @@ const msgs: LocaleMessageObject = {
notes: {
header: 'Notes for',
noNotes: 'No notes found',
addNote: 'Add notes',
addNote: 'Add note',
notes: 'Notes',
placeholder: 'Add a note...',
},

View File

@ -7,15 +7,21 @@
</NuxtLink>
</v-card-title>
<v-card-text>
<v-text-field v-model="text" :placeholder="$t('notes.placeholder')">
<v-text-field v-model.trim="text" filled :placeholder="$t('notes.placeholder')" dense hide-details @keyup.enter="addNote">
<template #append-outer>
<v-btn @click="addNote">{{ $t('notes.addNote') }}</v-btn>
<v-btn class="input-append-btn" color="primary" :disabled="!text" :loading="loading" @click="addNote">{{ $t('notes.addNote') }}</v-btn>
</template>
</v-text-field>
<h2>{{ $t('notes.notes') }}</h2>
<v-data-table v-if="notes && notes.length > 0" :headers="headers" :items="notes" disable-filtering disable-sort hide-default-footer>
<template #item.message="{ item }">{{ item.message }}</template>
<template #item.user="{ item }">{{ item.user.name }}</template>
<h2 class="mt-2">{{ $t('notes.notes') }}</h2>
<v-data-table
v-if="notes && notes.length > 0"
:headers="headers"
:items="notes"
:loading="$fetchState.pending"
disable-filtering
disable-sort
hide-default-footer
>
<template #item.createdAt="{ item }">{{ $util.prettyDate(item.createdAt) }}</template>
</v-data-table>
<v-alert v-else type="info" prominent v-text="$t('notes.noNotes')" />
@ -24,42 +30,49 @@
</template>
<script lang="ts">
import { Component, Prop, Vue } from 'nuxt-property-decorator';
import { Project } from 'hangar-api';
import { Component } from 'nuxt-property-decorator';
import { Note } from 'hangar-internal';
import { GlobalPermission } from '~/utils/perms';
import { NamedPermission } from '~/types/enums';
import { HangarProjectMixin } from '~/components/mixins';
@Component
@GlobalPermission(NamedPermission.MOD_NOTES_AND_FLAGS)
export default class ProjectNotesPage extends Vue {
@Prop({ required: true })
project!: Project;
// todo load notes
notes: Array<Note> = [];
export default class ProjectNotesPage extends HangarProjectMixin {
notes: Note[] = [];
loading = false;
text: string = '';
addNote() {
const note = {
id: -1,
message: this.text,
user: this.$util.getCurrentUser(),
createdAt: new Date().toISOString(),
} as Note;
this.text = '';
this.notes.push(note);
// TODO add new note to server
if (!this.text) {
return;
}
this.loading = true;
this.$api
.requestInternal(`projects/notes/${this.project.id}`, true, 'post', {
content: this.text,
})
.then(() => {
this.$nuxt.refresh();
this.text = '';
})
.catch(this.$util.handleRequestError)
.finally(() => {
this.loading = false;
});
}
get headers() {
return [
{ text: 'Date', value: 'createdAt', width: '10%' },
{ text: 'User', value: 'user', width: '10%' },
{ text: 'User', value: 'userName', width: '10%' },
{ text: 'Message', value: 'message', width: '80%' },
];
}
async fetch() {
this.notes = (await this.$api.requestInternal<Note[]>(`projects/notes/${this.project.id}`, true).catch<any>(this.$util.handleRequestError)) || [];
}
}
</script>

View File

@ -1,6 +1,6 @@
declare module 'hangar-internal' {
import { FlagReason, Joinable, Table } from 'hangar-internal';
import { Project, User } from 'hangar-api';
import { Project } from 'hangar-api';
import { ProjectCategory, Visibility } from '~/types/enums';
interface ProjectOwner {
@ -58,8 +58,10 @@ declare module 'hangar-internal' {
}
interface Note extends Table {
projectId: number;
message: string;
user: User;
userId: number;
userName: string | null;
}
interface ProjectSettingsForm {

View File

@ -12,12 +12,14 @@ import io.papermc.hangar.model.internal.api.requests.projects.NewProjectForm;
import io.papermc.hangar.model.internal.api.requests.projects.ProjectSettingsForm;
import io.papermc.hangar.model.internal.api.responses.PossibleProjectOwner;
import io.papermc.hangar.model.internal.projects.HangarProject;
import io.papermc.hangar.model.internal.projects.HangarProjectNote;
import io.papermc.hangar.security.annotations.permission.PermissionRequired;
import io.papermc.hangar.security.annotations.unlocked.Unlocked;
import io.papermc.hangar.security.annotations.visibility.VisibilityRequired;
import io.papermc.hangar.security.annotations.visibility.VisibilityRequired.Type;
import io.papermc.hangar.service.internal.organizations.OrganizationService;
import io.papermc.hangar.service.internal.projects.ProjectFactory;
import io.papermc.hangar.service.internal.projects.ProjectNoteService;
import io.papermc.hangar.service.internal.projects.ProjectService;
import io.papermc.hangar.service.internal.users.UserService;
import org.springframework.beans.factory.annotation.Autowired;
@ -48,13 +50,15 @@ public class ProjectController extends HangarController {
private final ProjectService projectService;
private final UserService userService;
private final OrganizationService organizationService;
private final ProjectNoteService projectNoteService;
@Autowired
public ProjectController(ProjectFactory projectFactory, ProjectService projectService, UserService userService, OrganizationService organizationService) {
public ProjectController(ProjectFactory projectFactory, ProjectService projectService, UserService userService, OrganizationService organizationService, ProjectNoteService projectNoteService) {
this.projectFactory = projectFactory;
this.projectService = projectService;
this.userService = userService;
this.organizationService = organizationService;
this.projectNoteService = projectNoteService;
}
@GetMapping("/validateName")
@ -139,8 +143,17 @@ public class ProjectController extends HangarController {
userService.toggleWatching(projectId, state);
}
// @GetMapping("/project/{author}/{name}/flags")
// public void getProjectFlags(@PathVariable String author, @PathVariable String name) {
//
// }
@PermissionRequired(type = PermissionType.PROJECT, perms = NamedPermission.MOD_NOTES_AND_FLAGS, args = "{#projectId}")
@GetMapping(path = "/notes/{projectId}", produces = MediaType.APPLICATION_JSON_VALUE)
public ResponseEntity<List<HangarProjectNote>> getProjectNotes(@PathVariable long projectId) {
return ResponseEntity.ok(projectNoteService.getNotes(projectId));
}
@Unlocked
@ResponseStatus(HttpStatus.CREATED)
@PermissionRequired(type = PermissionType.PROJECT, perms = NamedPermission.MOD_NOTES_AND_FLAGS, args = "{#projectId}")
@PostMapping(path = "/notes/{projectId}", consumes = MediaType.APPLICATION_JSON_VALUE)
public void addProjectNote(@PathVariable long projectId, @RequestBody @Valid StringContent content) {
projectNoteService.addNote(projectId, content.getContent());
}
}

View File

@ -1,8 +1,5 @@
package io.papermc.hangar.controllerold;
import com.fasterxml.jackson.databind.JsonNode;
import com.fasterxml.jackson.databind.node.ArrayNode;
import com.fasterxml.jackson.databind.node.ObjectNode;
import io.papermc.hangar.config.hangar.HangarConfig;
import io.papermc.hangar.db.customtypes.LoggedActionType;
import io.papermc.hangar.db.customtypes.LoggedActionType.ProjectContext;
@ -15,11 +12,9 @@ import io.papermc.hangar.model.common.NamedPermission;
import io.papermc.hangar.model.common.Permission;
import io.papermc.hangar.model.common.projects.FlagReason;
import io.papermc.hangar.model.common.projects.Visibility;
import io.papermc.hangar.modelold.generated.Note;
import io.papermc.hangar.modelold.viewhelpers.ProjectData;
import io.papermc.hangar.modelold.viewhelpers.ScopedOrganizationData;
import io.papermc.hangar.modelold.viewhelpers.ScopedProjectData;
import io.papermc.hangar.modelold.viewhelpers.UserData;
import io.papermc.hangar.securityold.annotations.GlobalPermission;
import io.papermc.hangar.securityold.annotations.ProjectPermission;
import io.papermc.hangar.securityold.annotations.UserLock;
@ -36,7 +31,6 @@ import io.papermc.hangar.util.Routes;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.http.HttpStatus;
import org.springframework.http.MediaType;
import org.springframework.http.ResponseEntity;
import org.springframework.security.access.annotation.Secured;
import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.GetMapping;
@ -49,8 +43,6 @@ import org.springframework.web.servlet.ModelAndView;
import org.springframework.web.servlet.mvc.support.RedirectAttributes;
import javax.servlet.http.HttpServletRequest;
import java.util.ArrayList;
import java.util.List;
import java.util.function.Supplier;
import java.util.regex.Pattern;
@ -212,44 +204,6 @@ public class ProjectsController extends HangarController {
return Routes.PROJECTS_SHOW.getRedirect(author, slug);
}
@GlobalPermission(NamedPermission.MOD_NOTES_AND_FLAGS)
@Secured("ROLE_USER")
@GetMapping("/{author}/{slug}/notes")
public ModelAndView showNotes(@PathVariable String author, @PathVariable String slug) {
ModelAndView mv = new ModelAndView("projects/admin/notes");
ProjectsTable project = projectsTable.get();
List<Note> notes = new ArrayList<>();
ArrayNode messages = (ArrayNode) project.getNotes().getJson().get("messages");
if (messages != null) {
for (JsonNode message : messages) {
Note note = new Note().message(message.get("message").asText());
notes.add(note);
UserData user = userService.getUserData(message.get("user").asLong());
note.user(user.getUser().getName());
}
}
mv.addObject("project", project);
mv.addObject("notes", notes);
return fillModel(mv);
}
@GlobalPermission(NamedPermission.MOD_NOTES_AND_FLAGS)
@Secured("ROLE_USER")
@PostMapping("/{author}/{slug}/notes/addmessage")
public ResponseEntity<String> addMessage(@PathVariable String author, @PathVariable String slug, @RequestParam String content) {
ProjectsTable project = projectsTable.get();
ArrayNode messages = project.getNotes().getJson().withArray("messages");
ObjectNode note = messages.addObject();
note.put("message", content);
note.put("user", getCurrentUser().getId());
String json = project.getNotes().getJson().toString();
projectDao.get().updateNotes(json, project.getId());
return ResponseEntity.ok("Review");
}
@GlobalPermission(NamedPermission.REVIEWER)
@UserLock(route = Routes.PROJECTS_SHOW, args = "{#author, #slug}")
@Secured("ROLE_USER")

View File

@ -1,5 +1,7 @@
package io.papermc.hangar.db.dao.internal;
package io.papermc.hangar.db.dao.internal.projects;
import io.papermc.hangar.model.db.projects.ProjectFlagTable;
import io.papermc.hangar.model.internal.projects.HangarProjectFlag;
import org.jdbi.v3.sqlobject.config.RegisterConstructorMapper;
import org.jdbi.v3.sqlobject.customizer.BindBean;
import org.jdbi.v3.sqlobject.customizer.Timestamped;
@ -11,11 +13,8 @@ import org.springframework.stereotype.Repository;
import java.time.OffsetDateTime;
import java.util.List;
import io.papermc.hangar.model.db.projects.ProjectFlagTable;
import io.papermc.hangar.model.internal.projects.HangarProjectFlag;
@Repository
public interface HangarFlagsDAO {
public interface HangarProjectFlagsDAO {
@Timestamped
@GetGeneratedKeys

View File

@ -0,0 +1,19 @@
package io.papermc.hangar.db.dao.internal.projects;
import io.papermc.hangar.model.internal.projects.HangarProjectNote;
import org.jdbi.v3.sqlobject.config.RegisterConstructorMapper;
import org.jdbi.v3.sqlobject.statement.SqlQuery;
import org.springframework.stereotype.Repository;
import java.util.List;
@Repository
@RegisterConstructorMapper(HangarProjectNote.class)
public interface HangarProjectNotesDAO {
@SqlQuery("SELECT pn.*, u.name" +
" FROM project_notes pn" +
" LEFT JOIN users u ON pn.user_id = u.id" +
" WHERE pn.project_id = :projectId")
List<HangarProjectNote> getProjectNotes(long projectId);
}

View File

@ -1,4 +1,4 @@
package io.papermc.hangar.db.dao.internal;
package io.papermc.hangar.db.dao.internal.projects;
import io.papermc.hangar.model.internal.projects.HangarViewProjectPage;
import org.jdbi.v3.sqlobject.config.RegisterConstructorMapper;

View File

@ -1,4 +1,4 @@
package io.papermc.hangar.db.dao.internal;
package io.papermc.hangar.db.dao.internal.projects;
import io.papermc.hangar.db.mappers.PromotedVersionMapper;
import io.papermc.hangar.db.mappers.factories.JoinableMemberFactory;
@ -74,16 +74,17 @@ public interface HangarProjectsDAO {
List<JoinableMember<ProjectRoleTable>> getProjectMembers(long projectId, Long userId, @Define boolean canSeePending);
@RegisterConstructorMapper(HangarProjectInfo.class)
@SqlQuery("SELECT count(pv.*) public_versions," +
" count(pf.*) flag_count," +
" count(ps.*) star_count," +
" count(pw.*) watcher_count," +
" coalesce(jsonb_array_length(p.notes->'messages'), 0) note_count" +
@SqlQuery("SELECT count(DISTINCT pv.id) public_versions," +
" count(DISTINCT pf.id) flag_count," +
" count(DISTINCT ps.user_id) star_count," +
" count(DISTINCT pw.user_id) watcher_count," +
" count(DISTINCT pn.id) note_count" +
" FROM projects p" +
" LEFT JOIN project_versions pv ON p.id = pv.project_id AND pv.visibility = 0" +
" LEFT JOIN project_stars ps ON p.id = ps.project_id" +
" LEFT JOIN project_watchers pw ON p.id = pw.project_id" +
" LEFT JOIN project_flags pf ON p.id = pf.project_id" +
" LEFT JOIN project_notes pn ON p.id = pn.project_id" +
" WHERE p.id = :projectId" +
" GROUP BY p.id")
HangarProjectInfo getHangarProjectInfo(long projectId);

View File

@ -0,0 +1,17 @@
package io.papermc.hangar.db.dao.internal.table.projects;
import io.papermc.hangar.model.db.projects.ProjectNoteTable;
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.SqlUpdate;
import org.springframework.stereotype.Repository;
@Repository
@RegisterConstructorMapper(ProjectNoteTable.class)
public interface ProjectNotesDAO {
@Timestamped
@SqlUpdate("INSERT INTO project_notes (created_at, project_id, message, user_id) VALUES (:now, :projectId, :message, :userId)")
void insert(@BindBean ProjectNoteTable projectNoteTable);
}

View File

@ -0,0 +1,48 @@
package io.papermc.hangar.model.db.projects;
import io.papermc.hangar.model.db.Table;
import org.jdbi.v3.core.mapper.reflect.JdbiConstructor;
import java.time.OffsetDateTime;
public class ProjectNoteTable extends Table {
private final long projectId;
private final String message;
private final Long userId;
@JdbiConstructor
public ProjectNoteTable(OffsetDateTime createdAt, long id, long projectId, String message, Long userId) {
super(createdAt, id);
this.projectId = projectId;
this.message = message;
this.userId = userId;
}
public ProjectNoteTable(long projectId, String message, Long userId) {
this.projectId = projectId;
this.message = message;
this.userId = userId;
}
public long getProjectId() {
return projectId;
}
public String getMessage() {
return message;
}
public Long getUserId() {
return userId;
}
@Override
public String toString() {
return "ProjectNoteTable{" +
"projectId=" + projectId +
", message='" + message + '\'' +
", userId=" + userId +
"} " + super.toString();
}
}

View File

@ -1,6 +1,5 @@
package io.papermc.hangar.model.db.projects;
import io.papermc.hangar.db.customtypes.JSONB;
import io.papermc.hangar.model.ModelVisible;
import io.papermc.hangar.model.Visitable;
import io.papermc.hangar.model.common.projects.Category;
@ -26,7 +25,6 @@ public class ProjectTable extends Table implements Visitable, ModelVisible, Proj
private Category category;
private String description;
private Visibility visibility;
private JSONB notes;
private Collection<String> keywords;
private String homepage;
private String issues;
@ -63,7 +61,6 @@ public class ProjectTable extends Table implements Visitable, ModelVisible, Proj
this.category = other.category;
this.description = other.description;
this.visibility = other.visibility;
this.notes = other.notes;
this.keywords = other.keywords;
this.homepage = other.homepage;
this.issues = other.issues;
@ -75,7 +72,7 @@ public class ProjectTable extends Table implements Visitable, ModelVisible, Proj
}
@JdbiConstructor
public ProjectTable(OffsetDateTime createdAt, long id, String name, String slug, String ownerName, long ownerId, long topicId, long postId, @EnumByOrdinal Category category, String description, @EnumByOrdinal Visibility visibility, JSONB notes, Collection<String> keywords, String homepage, String issues, String source, String support, String licenseName, String licenseUrl, boolean forumSync) {
public ProjectTable(OffsetDateTime createdAt, long id, String name, String slug, String ownerName, long ownerId, long topicId, long postId, @EnumByOrdinal Category category, String description, @EnumByOrdinal Visibility visibility, Collection<String> keywords, String homepage, String issues, String source, String support, String licenseName, String licenseUrl, boolean forumSync) {
super(createdAt, id);
this.name = name;
this.slug = slug;
@ -86,7 +83,6 @@ public class ProjectTable extends Table implements Visitable, ModelVisible, Proj
this.category = category;
this.description = description;
this.visibility = visibility;
this.notes = notes;
this.keywords = keywords;
this.homepage = homepage;
this.issues = issues;
@ -168,14 +164,6 @@ public class ProjectTable extends Table implements Visitable, ModelVisible, Proj
this.visibility = visibility;
}
public JSONB getNotes() {
return notes;
}
public void setNotes(JSONB notes) {
this.notes = notes;
}
public Collection<String> getKeywords() {
return keywords;
}
@ -262,7 +250,6 @@ public class ProjectTable extends Table implements Visitable, ModelVisible, Proj
", category=" + category +
", description='" + description + '\'' +
", visibility=" + visibility +
", notes=" + notes +
", keywords=" + keywords +
", homepage='" + homepage + '\'' +
", issues='" + issues + '\'' +

View File

@ -0,0 +1,29 @@
package io.papermc.hangar.model.internal.projects;
import io.papermc.hangar.model.db.projects.ProjectNoteTable;
import org.jdbi.v3.core.mapper.reflect.JdbiConstructor;
import org.jetbrains.annotations.Nullable;
import java.time.OffsetDateTime;
public class HangarProjectNote extends ProjectNoteTable {
private final String userName;
@JdbiConstructor
public HangarProjectNote(OffsetDateTime createdAt, long id, long projectId, String message, Long userId, @Nullable String name) {
super(createdAt, id, projectId, message, userId);
this.userName = name;
}
public String getUserName() {
return userName;
}
@Override
public String toString() {
return "HangarProjectNote{" +
"userName='" + userName + '\'' +
"} " + super.toString();
}
}

View File

@ -1,109 +0,0 @@
package io.papermc.hangar.modelold.generated;
import com.fasterxml.jackson.annotation.JsonProperty;
import io.swagger.annotations.ApiModelProperty;
import org.springframework.validation.annotation.Validated;
import javax.validation.constraints.NotNull;
import java.time.Instant;
import java.time.OffsetDateTime;
import java.time.ZoneOffset;
import java.util.Objects;
@Validated
public class Note {
@JsonProperty("message")
private String message = null;
@JsonProperty("user")
private String user = null;
@JsonProperty("time")
private Long time = System.currentTimeMillis();
/**
* Get message
*
* @return message
**/
@ApiModelProperty(required = true, value = "")
@NotNull
public String getMessage() {
return message;
}
public void setMessage(String message) {
this.message = message;
}
public Note message(String message) {
this.message = message;
return this;
}
/**
* Get user
*
* @return user
**/
@ApiModelProperty(required = true, value = "")
@NotNull
public String getUser() {
return user;
}
public void setUser(String user) {
this.user = user;
}
public Note user(String user) {
this.user = user;
return this;
}
/**
*
* Get time
*
* @return time
**/
@ApiModelProperty(required = true, value = "")
@NotNull
public Long getTime() {
return time;
}
public void setTime(Long time) {
this.time = time;
}
public Note slug(Long time) {
this.time = time;
return this;
}
public OffsetDateTime toDateTime() {
return OffsetDateTime.ofInstant(Instant.ofEpochMilli(time), ZoneOffset.UTC);
}
@Override
public boolean equals(Object o) {
if (this == o) {
return true;
}
if (o == null || getClass() != o.getClass()) {
return false;
}
Note note = (Note) o;
return Objects.equals(this.message, note.message) &&
Objects.equals(this.user, note.user) &&
Objects.equals(this.time, note.time);
}
@Override
public int hashCode() {
return Objects.hash(message, user, time);
}
}

View File

@ -1,7 +1,7 @@
package io.papermc.hangar.service.internal.admin;
import io.papermc.hangar.db.dao.HangarDao;
import io.papermc.hangar.db.dao.internal.HangarFlagsDAO;
import io.papermc.hangar.db.dao.internal.projects.HangarProjectFlagsDAO;
import io.papermc.hangar.model.common.projects.FlagReason;
import io.papermc.hangar.model.db.projects.ProjectFlagTable;
import io.papermc.hangar.model.internal.projects.HangarProjectFlag;
@ -14,28 +14,28 @@ import java.util.List;
@Service
public class FlagService extends HangarService {
private final HangarFlagsDAO flagsDAO;
private final HangarProjectFlagsDAO hangarProjectFlagsDAO;
public FlagService(HangarDao<HangarFlagsDAO> flagsDAO) {
this.flagsDAO = flagsDAO.get();
public FlagService(HangarDao<HangarProjectFlagsDAO> hangarProjectFlagsDAO) {
this.hangarProjectFlagsDAO = hangarProjectFlagsDAO.get();
}
public void createFlag(long projectId, FlagReason reason, String comment) {
// TODO idk, we prolly need more checking here, plus notification? logs?
flagsDAO.insert(new ProjectFlagTable( projectId, getHangarPrincipal().getId(), reason, comment));
hangarProjectFlagsDAO.insert(new ProjectFlagTable( projectId, getHangarPrincipal().getId(), reason, comment));
}
public ProjectFlagTable markAsResolved(long flagId, boolean resolved) {
Long resolvedBy = resolved ? getHangarPrincipal().getId() : null;
OffsetDateTime resolvedAt = resolved ? OffsetDateTime.now() : null;
return flagsDAO.markAsResolved(flagId, resolved, resolvedBy, resolvedAt);
return hangarProjectFlagsDAO.markAsResolved(flagId, resolved, resolvedBy, resolvedAt);
}
public List<HangarProjectFlag> getFlags(String author, String slug) {
return flagsDAO.getFlags(author, slug);
return hangarProjectFlagsDAO.getFlags(author, slug);
}
public List<HangarProjectFlag> getFlags() {
return flagsDAO.getFlags();
return hangarProjectFlagsDAO.getFlags();
}
}

View File

@ -1,7 +1,7 @@
package io.papermc.hangar.service.internal.projects;
import io.papermc.hangar.db.dao.HangarDao;
import io.papermc.hangar.db.dao.internal.HangarProjectsDAO;
import io.papermc.hangar.db.dao.internal.projects.HangarProjectsDAO;
import io.papermc.hangar.db.dao.internal.table.projects.ProjectChannelsDAO;
import io.papermc.hangar.exceptions.HangarApiException;
import io.papermc.hangar.model.common.Color;

View File

@ -0,0 +1,33 @@
package io.papermc.hangar.service.internal.projects;
import io.papermc.hangar.db.dao.HangarDao;
import io.papermc.hangar.db.dao.internal.projects.HangarProjectNotesDAO;
import io.papermc.hangar.db.dao.internal.table.projects.ProjectNotesDAO;
import io.papermc.hangar.model.db.projects.ProjectNoteTable;
import io.papermc.hangar.model.internal.projects.HangarProjectNote;
import io.papermc.hangar.service.HangarService;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import java.util.List;
@Service
public class ProjectNoteService extends HangarService {
private final ProjectNotesDAO projectNotesDAO;
private final HangarProjectNotesDAO hangarProjectNotesDAO;
@Autowired
public ProjectNoteService(HangarDao<ProjectNotesDAO> projectNotesDAO, HangarDao<HangarProjectNotesDAO> hangarProjectNotesDAO) {
this.projectNotesDAO = projectNotesDAO.get();
this.hangarProjectNotesDAO = hangarProjectNotesDAO.get();
}
public List<HangarProjectNote> getNotes(long projectId) {
return hangarProjectNotesDAO.getProjectNotes(projectId);
}
public void addNote(long projectId, String msg) {
projectNotesDAO.insert(new ProjectNoteTable(projectId, msg, getHangarPrincipal().getId()));
}
}

View File

@ -3,7 +3,7 @@ package io.papermc.hangar.service.internal.projects;
import io.papermc.hangar.db.customtypes.LoggedActionType;
import io.papermc.hangar.db.customtypes.LoggedActionType.ProjectPageContext;
import io.papermc.hangar.db.dao.HangarDao;
import io.papermc.hangar.db.dao.internal.HangarProjectPagesDAO;
import io.papermc.hangar.db.dao.internal.projects.HangarProjectPagesDAO;
import io.papermc.hangar.db.dao.internal.table.projects.ProjectPagesDAO;
import io.papermc.hangar.exceptions.HangarApiException;
import io.papermc.hangar.model.db.projects.ProjectHomePageTable;

View File

@ -3,8 +3,8 @@ package io.papermc.hangar.service.internal.projects;
import io.papermc.hangar.db.customtypes.LoggedActionType;
import io.papermc.hangar.db.customtypes.LoggedActionType.ProjectContext;
import io.papermc.hangar.db.dao.HangarDao;
import io.papermc.hangar.db.dao.internal.HangarProjectsDAO;
import io.papermc.hangar.db.dao.internal.HangarUsersDAO;
import io.papermc.hangar.db.dao.internal.projects.HangarProjectsDAO;
import io.papermc.hangar.db.dao.internal.table.UserDAO;
import io.papermc.hangar.db.dao.internal.table.projects.ProjectsDAO;
import io.papermc.hangar.exceptions.HangarApiException;

View File

@ -0,0 +1,27 @@
ALTER TABLE project_visibility_changes DROP CONSTRAINT project_visibility_changes_created_by_fkey;
ALTER TABLE project_visibility_changes ADD CONSTRAINT project_visibility_changes_created_by_fkey FOREIGN KEY (created_by) REFERENCES users ON DELETE SET NULL;
ALTER TABLE project_visibility_changes DROP CONSTRAINT project_visibility_changes_resolved_by_fkey;
ALTER TABLE project_visibility_changes ADD CONSTRAINT project_visibility_changes_resolved_by_fkey FOREIGN KEY (resolved_by) REFERENCES users ON DELETE SET NULL;
ALTER TABLE project_version_visibility_changes DROP CONSTRAINT project_version_visibility_changes_created_by_fkey;
ALTER TABLE project_version_visibility_changes ADD CONSTRAINT project_version_visibility_changes_created_by_fkey FOREIGN KEY (created_by) REFERENCES users ON DELETE SET NULL;
ALTER TABLE project_version_visibility_changes DROP CONSTRAINT project_version_visibility_changes_resolved_by_fkey;
ALTER TABLE project_version_visibility_changes ADD CONSTRAINT project_version_visibility_changes_resolved_by_fkey FOREIGN KEY (resolved_by) REFERENCES users ON DELETE SET NULL;
CREATE TABLE project_notes
(
id bigserial NOT NULL
CONSTRAINT notes_pkey PRIMARY KEY,
created_at timestamp with time zone NOT NULL,
project_id bigint NOT NULL
CONSTRAINT notes_project_id_fkey
REFERENCES projects ON DELETE CASCADE,
message text NOT NULL,
user_id bigint
CONSTRAINT notes_user_id
REFERENCES users ON DELETE SET NULL
);
ALTER TABLE projects DROP COLUMN notes;

View File

@ -1,59 +0,0 @@
<#import "/spring.ftl" as spring />
<#import "*/utils/hangar.ftlh" as hangar />
<#import "*/layout/base.ftlh" as base />
<#assign scriptsVar>
<script type="text/javascript" src="${hangar.url("js/showNotes.js")}"></script>
<script nonce="${nonce}">
<#outputformat "JavaScript">
window.resourcePath = '${project.ownerName}/${project.slug}'
</#outputformat>
</script>
</#assign>
<#assign message><@spring.message "notes" /></#assign>
<@base.base title="${message}" additionalScripts=scriptsVar>
<div class="row">
<div class="col-md-12 header-flags">
<div class="clearfix">
<h1 class="float-left"><@spring.message "notes" /> <a href="${Routes.PROJECTS_SHOW.getRouteUrl(project.ownerName, project.slug)}">${project.ownerName}/${project.slug}</a></h1>
</div>
</div>
</div>
<div class="row">
<div class="col-md-12">
<div class="input-group" style="width: 100%;
margin-top: 1em;">
<textarea type="text" class="form-control textarea-addmessage" placeholder="Message"></textarea>
<div class="input-group-addon btn btn-note-addmessage-submit btn-primary"><i class="fas fa-clipboard"></i> <@spring.message "notes.addmessage" /></div>
</div>
</div>
</div>
<div class="row version-description">
<div class="col-md-12">
<h2><@spring.message "notes" /></h2>
</div>
<#if !notes?has_content>
<div class="col-md-12">
<div class="alert-notes alert alert-info" role="alert">
<i class="fas fa-info-circle"></i>
This project does not have notes
</div>
</div>
</#if>
<div class="col-md-12">
<table class="table table-sm setting-no-border table-notes-log">
<tbody>
<#list notes as note>
<tr>
<td class="note-fixed-with">${utils.prettifyDateTime(note.toDateTime())}</td>
<td class="note-fixed-with"><strong>${note.getUser()!"Unknown"}</strong></td>
<td><#outputformat "plainText">${markdownService.render(note.getMessage())}</#outputformat></td>
</tr>
</#list>
</tbody>
</table>
</div>
</div>
</@base.base>