From acb65f7b7eda3ace5ce1228e1846fbdd751890a1 Mon Sep 17 00:00:00 2001 From: MiniDigger | Martin Date: Sun, 18 Aug 2024 13:17:48 +0200 Subject: [PATCH] feat: finish project pinning (closes #164) --- .../internal/projects/ProjectController.java | 10 ++--- .../table/projects/PinnedProjectDAO.java | 10 ++--- .../papermc/hangar/db/dao/v1/UsersApiDAO.java | 6 ++- .../hangar/model/api/project/Project.java | 15 +------- .../model/api/project/ProjectCompact.java | 13 ++++++- .../model/db/projects/PinnedProjectTable.java | 38 ------------------- .../hangar/service/api/UsersApiService.java | 5 +-- .../projects/PinnedProjectService.java | 11 +++--- .../db/migration/R__04_pinned_views.sql | 15 ++++++-- .../src/components/projects/ProjectCard.vue | 21 ++++++++-- .../src/components/projects/ProjectList.vue | 6 ++- frontend/src/pages/[user]/index.vue | 20 ++++++---- 12 files changed, 78 insertions(+), 92 deletions(-) delete mode 100644 backend/src/main/java/io/papermc/hangar/model/db/projects/PinnedProjectTable.java diff --git a/backend/src/main/java/io/papermc/hangar/controller/internal/projects/ProjectController.java b/backend/src/main/java/io/papermc/hangar/controller/internal/projects/ProjectController.java index 3882df644..24d49761d 100644 --- a/backend/src/main/java/io/papermc/hangar/controller/internal/projects/ProjectController.java +++ b/backend/src/main/java/io/papermc/hangar/controller/internal/projects/ProjectController.java @@ -237,13 +237,13 @@ public void setProjectWatching(@PathVariable("id") final long projectId, @PathVa @ResponseStatus(HttpStatus.OK) @RateLimit(overdraft = 10, refillTokens = 3, refillSeconds = 10) @PermissionRequired(NamedPermission.EDIT_OWN_USER_SETTINGS) - @VisibilityRequired(type = VisibilityRequired.Type.PROJECT, args = "{#projectId}") - @PostMapping(path = "/project/{id}/pin/{state}") - public void setPinnedStatus(@PathVariable("id") final long projectId, @PathVariable final boolean state) { + @VisibilityRequired(type = VisibilityRequired.Type.PROJECT, args = "{#slug}") + @PostMapping(path = "/project/{slug}/pin/{state}") + public void setPinnedStatus(@PathVariable("slug") final String slug, @PathVariable final boolean state) { if (state) { - this.pinnedProjectService.addPinnedProject(this.getHangarUserId(), projectId); + this.pinnedProjectService.addPinnedProject(this.getHangarUserId(), slug); } else { - this.pinnedProjectService.removePinnedProject(this.getHangarUserId(), projectId); + this.pinnedProjectService.removePinnedProject(this.getHangarUserId(), slug); } } diff --git a/backend/src/main/java/io/papermc/hangar/db/dao/internal/table/projects/PinnedProjectDAO.java b/backend/src/main/java/io/papermc/hangar/db/dao/internal/table/projects/PinnedProjectDAO.java index f3ff5cc82..e61899512 100644 --- a/backend/src/main/java/io/papermc/hangar/db/dao/internal/table/projects/PinnedProjectDAO.java +++ b/backend/src/main/java/io/papermc/hangar/db/dao/internal/table/projects/PinnedProjectDAO.java @@ -1,22 +1,20 @@ package io.papermc.hangar.db.dao.internal.table.projects; import io.papermc.hangar.model.api.project.ProjectCompact; -import io.papermc.hangar.model.db.projects.PinnedProjectTable; import java.util.List; import org.jdbi.v3.spring5.JdbiRepository; import org.jdbi.v3.sqlobject.config.RegisterConstructorMapper; -import org.jdbi.v3.sqlobject.customizer.BindBean; import org.jdbi.v3.sqlobject.statement.SqlQuery; import org.jdbi.v3.sqlobject.statement.SqlUpdate; @JdbiRepository public interface PinnedProjectDAO { - @SqlUpdate("INSERT INTO pinned_user_projects (project_id, user_id) VALUES (:projectId, :userId) ON CONFLICT DO NOTHING") - void insert(@BindBean PinnedProjectTable pinnedProjectVersionTable); + @SqlUpdate("INSERT INTO pinned_user_projects (project_id, user_id) VALUES ((SELECT id FROM projects WHERE lower(slug) = lower(:slug)), :userId) ON CONFLICT DO NOTHING") + void insert(long userId, String slug); - @SqlUpdate("DELETE FROM pinned_user_projects WHERE project_id = :projectId AND user_id = :userId") - void delete(long projectId, long userId); + @SqlUpdate("DELETE FROM pinned_user_projects WHERE project_id = (SELECT id FROM projects WHERE lower(slug) = lower(:slug)) AND user_id = :userId") + void delete(long userId, String slug); @SqlQuery("SELECT * FROM pinned_projects WHERE user_id = :userId") @RegisterConstructorMapper(ProjectCompact.class) diff --git a/backend/src/main/java/io/papermc/hangar/db/dao/v1/UsersApiDAO.java b/backend/src/main/java/io/papermc/hangar/db/dao/v1/UsersApiDAO.java index 6c4f29a37..47e179186 100644 --- a/backend/src/main/java/io/papermc/hangar/db/dao/v1/UsersApiDAO.java +++ b/backend/src/main/java/io/papermc/hangar/db/dao/v1/UsersApiDAO.java @@ -37,7 +37,8 @@ public interface UsersApiDAO { hp.last_updated, hp.visibility, hp.avatar, - hp.avatar_fallback + hp.avatar_fallback, + hp.description FROM users u JOIN project_stars ps ON u.id = ps.user_id JOIN home_projects hp ON ps.project_id = hp.id @@ -79,7 +80,8 @@ SELECT count(*) hp.last_updated, hp.visibility, hp.avatar, - hp.avatar_fallback + hp.avatar_fallback, + hp.description FROM users u JOIN project_watchers pw ON u.id = pw.user_id JOIN home_projects hp ON pw.project_id = hp.id diff --git a/backend/src/main/java/io/papermc/hangar/model/api/project/Project.java b/backend/src/main/java/io/papermc/hangar/model/api/project/Project.java index dc37260ef..eea697837 100644 --- a/backend/src/main/java/io/papermc/hangar/model/api/project/Project.java +++ b/backend/src/main/java/io/papermc/hangar/model/api/project/Project.java @@ -3,7 +3,6 @@ import io.papermc.hangar.model.api.project.settings.ProjectSettings; import io.papermc.hangar.model.common.projects.Category; import io.papermc.hangar.model.common.projects.Visibility; -import io.papermc.hangar.util.AvatarUtil; import io.swagger.v3.oas.annotations.media.Schema; import java.time.OffsetDateTime; import org.jdbi.v3.core.enums.EnumByOrdinal; @@ -12,8 +11,6 @@ public class Project extends ProjectCompact { - @Schema(description = "The short description of the project") - private final String description; @Schema(description = "Information about your interactions with the project") private final UserActions userActions; @Schema(description = "The settings of the project") @@ -21,25 +18,18 @@ public class Project extends ProjectCompact { @JdbiConstructor public Project(final OffsetDateTime createdAt, final long id, final String name, @Nested final ProjectNamespace namespace, @Nested final ProjectStats stats, @EnumByOrdinal final Category category, final String description, final OffsetDateTime lastUpdated, @EnumByOrdinal final Visibility visibility, @Nested final UserActions userActions, @Nested final ProjectSettings settings, final String avatar, final String avatarFallback) { - super(createdAt, id, name, namespace, stats, category, lastUpdated, visibility); - this.description = description; + super(createdAt, id, name, namespace, description, stats, category, lastUpdated, visibility, avatar, avatarFallback); this.userActions = userActions; this.settings = settings; - this.setAvatarUrl(AvatarUtil.avatarUrl(avatar, avatarFallback)); } public Project(final Project other) { - super(other.createdAt, other.id, other.name, other.namespace, other.stats, other.category, other.lastUpdated, other.visibility); - this.description = other.description; + super(other.createdAt, other.id, other.name, other.namespace, other.description, other.stats, other.category, other.lastUpdated, other.visibility, null, null); this.userActions = other.userActions; this.settings = other.settings; this.avatarUrl = other.avatarUrl; } - public String getDescription() { - return this.description; - } - public UserActions getUserActions() { return this.userActions; } @@ -51,7 +41,6 @@ public ProjectSettings getSettings() { @Override public String toString() { return "Project{" + - "description='" + this.description + '\'' + ", lastUpdated=" + this.lastUpdated + ", userActions=" + this.userActions + ", settings=" + this.settings + diff --git a/backend/src/main/java/io/papermc/hangar/model/api/project/ProjectCompact.java b/backend/src/main/java/io/papermc/hangar/model/api/project/ProjectCompact.java index 0cbb55966..b697c53e6 100644 --- a/backend/src/main/java/io/papermc/hangar/model/api/project/ProjectCompact.java +++ b/backend/src/main/java/io/papermc/hangar/model/api/project/ProjectCompact.java @@ -6,8 +6,10 @@ import io.papermc.hangar.model.Visible; import io.papermc.hangar.model.common.projects.Category; import io.papermc.hangar.model.common.projects.Visibility; +import io.papermc.hangar.util.AvatarUtil; import io.swagger.v3.oas.annotations.media.Schema; import java.time.OffsetDateTime; +import org.checkerframework.checker.nullness.qual.Nullable; import org.jdbi.v3.core.enums.EnumByOrdinal; import org.jdbi.v3.core.mapper.Nested; @@ -29,16 +31,20 @@ public class ProjectCompact extends Model implements Named, Visible { protected final Visibility visibility; @Schema(description = "The url to the project's icon") protected String avatarUrl; + @Schema(description = "The short description of the project") + protected final String description; - public ProjectCompact(final OffsetDateTime createdAt, final long id, final String name, @Nested final ProjectNamespace namespace, @Nested final ProjectStats stats, @EnumByOrdinal final Category category, final OffsetDateTime lastUpdated, @EnumByOrdinal final Visibility visibility) { + public ProjectCompact(final OffsetDateTime createdAt, final long id, final String name, @Nested final ProjectNamespace namespace, final String description, @Nested final ProjectStats stats, @EnumByOrdinal final Category category, final OffsetDateTime lastUpdated, @EnumByOrdinal final Visibility visibility, @Nullable final String avatar, @Nullable final String avatarFallback) { super(createdAt); this.id = id; this.name = name; this.namespace = namespace; + this.description = description; this.stats = stats; this.category = category; this.visibility = visibility; this.lastUpdated = lastUpdated; + this.setAvatarUrl(AvatarUtil.avatarUrl(avatar, avatarFallback)); } @Override @@ -58,6 +64,10 @@ public Category getCategory() { return this.category; } + public String getDescription() { + return this.description; + } + public OffsetDateTime getLastUpdated() { return this.lastUpdated; } @@ -85,6 +95,7 @@ public String toString() { return "ProjectCompact{" + "name='" + this.name + '\'' + ", namespace=" + this.namespace + + ", description=" + this.description + ", stats=" + this.stats + ", category=" + this.category + ", visibility=" + this.visibility + diff --git a/backend/src/main/java/io/papermc/hangar/model/db/projects/PinnedProjectTable.java b/backend/src/main/java/io/papermc/hangar/model/db/projects/PinnedProjectTable.java deleted file mode 100644 index d406fa5bc..000000000 --- a/backend/src/main/java/io/papermc/hangar/model/db/projects/PinnedProjectTable.java +++ /dev/null @@ -1,38 +0,0 @@ -package io.papermc.hangar.model.db.projects; - -import io.papermc.hangar.model.db.Table; -import org.jdbi.v3.core.mapper.reflect.JdbiConstructor; - -public class PinnedProjectTable extends Table { - - private final long projectId; - private final long userId; - - @JdbiConstructor - public PinnedProjectTable(final long id, final long projectId, final long userId) { - super(id); - this.projectId = projectId; - this.userId = userId; - } - - public PinnedProjectTable(final long projectId, final long userId) { - this.projectId = projectId; - this.userId = userId; - } - - public long getProjectId() { - return this.projectId; - } - - public long getVersionId() { - return this.userId; - } - - @Override - public String toString() { - return "PinnedProjectTable{" + - "projectId=" + this.projectId + - ", userId=" + this.userId + - "} " + super.toString(); - } -} diff --git a/backend/src/main/java/io/papermc/hangar/service/api/UsersApiService.java b/backend/src/main/java/io/papermc/hangar/service/api/UsersApiService.java index 96e4c8113..2f336b8c4 100644 --- a/backend/src/main/java/io/papermc/hangar/service/api/UsersApiService.java +++ b/backend/src/main/java/io/papermc/hangar/service/api/UsersApiService.java @@ -190,9 +190,6 @@ public void supplyAvatarUrl(final User user) { } public List getUserPinned(final String userName) { - final List pinnedVersions = this.pinnedProjectService.getPinnedVersions(this.getUserRequired(userName, this.usersDAO::getUser, HangarUser.class).getId()); - // TODO rewrite avatar fetching - pinnedVersions.forEach(p -> p.setAvatarUrl(this.avatarService.getProjectAvatarUrl(p.getId(), p.getNamespace().getOwner()))); - return pinnedVersions; + return this.pinnedProjectService.getPinnedProjects(this.getUserRequired(userName, this.usersDAO::getUser, HangarUser.class).getId()); } } diff --git a/backend/src/main/java/io/papermc/hangar/service/internal/projects/PinnedProjectService.java b/backend/src/main/java/io/papermc/hangar/service/internal/projects/PinnedProjectService.java index 19b74835c..957b23c6f 100644 --- a/backend/src/main/java/io/papermc/hangar/service/internal/projects/PinnedProjectService.java +++ b/backend/src/main/java/io/papermc/hangar/service/internal/projects/PinnedProjectService.java @@ -3,7 +3,6 @@ import io.papermc.hangar.HangarComponent; import io.papermc.hangar.db.dao.internal.table.projects.PinnedProjectDAO; import io.papermc.hangar.model.api.project.ProjectCompact; -import io.papermc.hangar.model.db.projects.PinnedProjectTable; import java.util.List; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.stereotype.Service; @@ -18,15 +17,15 @@ public PinnedProjectService(final PinnedProjectDAO pinnedProjectDAO) { this.pinnedProjectDAO = pinnedProjectDAO; } - public void addPinnedProject(final long userId, final long projectId) { - this.pinnedProjectDAO.insert(new PinnedProjectTable(projectId, userId)); + public void addPinnedProject(final long userId, final String slug) { + this.pinnedProjectDAO.insert(userId, slug); } - public void removePinnedProject(final long userId, final long projectId) { - this.pinnedProjectDAO.delete(projectId, userId); + public void removePinnedProject(final long userId, final String slug) { + this.pinnedProjectDAO.delete(userId, slug); } - public List getPinnedVersions(final long userid) { + public List getPinnedProjects(final long userid) { return this.pinnedProjectDAO.pinnedProjects(userid); } } diff --git a/backend/src/main/resources/db/migration/R__04_pinned_views.sql b/backend/src/main/resources/db/migration/R__04_pinned_views.sql index 72848c0b6..f8a04e962 100644 --- a/backend/src/main/resources/db/migration/R__04_pinned_views.sql +++ b/backend/src/main/resources/db/migration/R__04_pinned_views.sql @@ -1,4 +1,5 @@ DROP VIEW IF EXISTS pinned_versions; + DROP VIEW IF EXISTS pinned_projects; CREATE OR REPLACE VIEW pinned_versions AS @@ -19,7 +20,7 @@ CREATE OR REPLACE VIEW pinned_versions AS JOIN platform_versions plv ON plv.id = pvpd.platform_version_id WHERE pvpd.version_id = pv.id ORDER BY plv.platform - ) AS platforms, + ) AS platforms, 'version' AS type, pv.project_id FROM pinned_project_versions ppv @@ -34,7 +35,7 @@ CREATE OR REPLACE VIEW pinned_versions AS JOIN platform_versions plv ON plv.id = pvpd.platform_version_id WHERE pvpd.version_id = pv.id ORDER BY plv.platform - ) AS platforms, + ) AS platforms, 'channel' AS type, pv.project_id FROM project_channels pc @@ -62,7 +63,10 @@ CREATE OR REPLACE VIEW pinned_projects AS name, created_at, license_type, - last_updated + description, + last_updated, + avatar, + avatar_fallback FROM (SELECT pp.id, pp.user_id, pp.project_id, @@ -81,7 +85,10 @@ CREATE OR REPLACE VIEW pinned_projects AS p.name, p.created_at, p.license_type, - hp.last_updated + p.description, + hp.last_updated, + hp.avatar, + hp.avatar_fallback FROM pinned_user_projects pp JOIN home_projects hp ON hp.id = pp.project_id JOIN projects p ON pp.project_id = p.id) AS pvs; diff --git a/frontend/src/components/projects/ProjectCard.vue b/frontend/src/components/projects/ProjectCard.vue index fc4cef36e..adefe1e89 100644 --- a/frontend/src/components/projects/ProjectCard.vue +++ b/frontend/src/components/projects/ProjectCard.vue @@ -2,10 +2,18 @@ import { type Project, type ProjectCompact, Tag, Visibility } from "~/types/backend"; const i18n = useI18n(); +const router = useRouter(); -defineProps<{ +const props = defineProps<{ project: Project | ProjectCompact; + canEdit?: boolean; + pinned?: boolean; }>(); + +async function togglePin() { + await useInternalApi(`/projects/project/${props.project.namespace.slug}/pin/${!props.pinned}`, "POST").catch(handleRequestError); + router.go(0); // I am lazy +}