diff --git a/src/main/java/io/papermc/hangar/controller/api/ProjectsApiController.java b/src/main/java/io/papermc/hangar/controller/api/ProjectsApiController.java index c1e23c971..344cb7675 100644 --- a/src/main/java/io/papermc/hangar/controller/api/ProjectsApiController.java +++ b/src/main/java/io/papermc/hangar/controller/api/ProjectsApiController.java @@ -12,7 +12,6 @@ import io.papermc.hangar.model.generated.ProjectSortingStrategy; import io.papermc.hangar.model.generated.ProjectStatsDay; import io.papermc.hangar.model.generated.Tag; import io.papermc.hangar.service.api.ProjectApiService; -import io.papermc.hangar.service.project.ProjectService; import io.papermc.hangar.util.ApiUtil; import org.slf4j.Logger; import org.slf4j.LoggerFactory; @@ -37,14 +36,12 @@ public class ProjectsApiController implements ProjectsApi { private final HangarConfig hangarConfig; private final ApiAuthInfo apiAuthInfo; - private final ProjectService projectService; private final ProjectApiService projectApiService; @Autowired - public ProjectsApiController(HangarConfig hangarConfig, ApiAuthInfo apiAuthInfo, ProjectService projectService, ProjectApiService projectApiService) { + public ProjectsApiController(HangarConfig hangarConfig, ApiAuthInfo apiAuthInfo, ProjectApiService projectApiService) { this.hangarConfig = hangarConfig; this.apiAuthInfo = apiAuthInfo; - this.projectService = projectService; this.projectApiService = projectApiService; } @@ -68,7 +65,7 @@ public class ProjectsApiController implements ProjectsApi { boolean seeHidden = apiAuthInfo.getGlobalPerms().has(Permission.SeeHidden); Long requesterId = apiAuthInfo.getUser() == null ? null : apiAuthInfo.getUser().getId(); - List projects = projectService.getProjects( + List projects = projectApiService.getProjects( null, categories, parsedTags, @@ -82,7 +79,7 @@ public class ProjectsApiController implements ProjectsApi { offset ); - long count = projectService.countProjects( + long count = projectApiService.countProjects( null, categories, parsedTags, @@ -116,7 +113,8 @@ public class ProjectsApiController implements ProjectsApi { @Override @PreAuthorize("@authenticationService.authApiRequest(T(io.papermc.hangar.model.Permission).ViewPublicInfo, T(io.papermc.hangar.controller.util.ApiScope).forProject(#pluginId))") public ResponseEntity showProject(String pluginId) { - Project project = projectService.getProjectApi(pluginId); + boolean seeHidden = apiAuthInfo.getGlobalPerms().has(Permission.SeeHidden); + Project project = projectApiService.getProject(pluginId, seeHidden, apiAuthInfo.getUserId()); if (project == null) { log.error("Couldn't find a project for that pluginId"); return new ResponseEntity<>(HttpStatus.NOT_FOUND); diff --git a/src/main/java/io/papermc/hangar/db/dao/api/ProjectsApiDao.java b/src/main/java/io/papermc/hangar/db/dao/api/ProjectsApiDao.java index c2ba2ccb2..2ba2b777e 100644 --- a/src/main/java/io/papermc/hangar/db/dao/api/ProjectsApiDao.java +++ b/src/main/java/io/papermc/hangar/db/dao/api/ProjectsApiDao.java @@ -26,7 +26,7 @@ public interface ProjectsApiDao { @SqlQuery("SELECT p.created_at," + " p.plugin_id," + " p.name," + - " p.owner_name," + + " p.owner_name \"owner\"," + " p.slug," + " p.promoted_versions," + " p.views," + @@ -40,7 +40,7 @@ public interface ProjectsApiDao { " COALESCE(p.last_updated, p.created_at) AS last_updated," + " p.visibility, " + " " + - " EXISTS(SELECT * FROM project_stars s WHERE s.project_id = p.id AND s.user_id = :requesterId) AS user_stared, " + + " EXISTS(SELECT * FROM project_stars s WHERE s.project_id = p.id AND s.user_id = :requesterId) AS user_starred, " + " EXISTS(SELECT * FROM project_watchers s WHERE s.project_id = p.id AND s.user_id = :requesterId) AS user_watching, " + " " + " ps.homepage," + @@ -60,7 +60,7 @@ public interface ProjectsApiDao { " AND ( ) " + " AND EXISTS ( SELECT pv.tag_name FROM jsonb_to_recordset(p.promoted_versions) " + " AS pv(tag_name TEXT, tag_version TEXT) WHERE (pv.tag_name) in () ) " + - " ORDER BY " + + " ORDER BY " + " LIMIT :limit" + " OFFSET :offset") @RegisterColumnMapper(PromotedVersionMapper.class) diff --git a/src/main/java/io/papermc/hangar/model/ApiAuthInfo.java b/src/main/java/io/papermc/hangar/model/ApiAuthInfo.java index 4eb3422c7..99358dd1f 100644 --- a/src/main/java/io/papermc/hangar/model/ApiAuthInfo.java +++ b/src/main/java/io/papermc/hangar/model/ApiAuthInfo.java @@ -18,6 +18,10 @@ public class ApiAuthInfo { return user; } + public Long getUserId() { + return user == null ? null : user.getId(); + } + @Nested("u") public void setUser(UsersTable user) { this.user = user.getName() == null ? null : user; diff --git a/src/main/java/io/papermc/hangar/model/generated/Project.java b/src/main/java/io/papermc/hangar/model/generated/Project.java index 0287115cd..df7aace19 100644 --- a/src/main/java/io/papermc/hangar/model/generated/Project.java +++ b/src/main/java/io/papermc/hangar/model/generated/Project.java @@ -4,6 +4,7 @@ import com.fasterxml.jackson.annotation.JsonIgnore; import com.fasterxml.jackson.annotation.JsonProperty; import org.jdbi.v3.core.enums.EnumByOrdinal; +import org.jdbi.v3.core.mapper.Nested; import org.springframework.validation.annotation.Validated; import java.time.OffsetDateTime; @@ -111,6 +112,7 @@ public class Project { return namespace; } + @Nested public void setNamespace(ProjectNamespace namespace) { this.namespace = namespace; } @@ -130,6 +132,7 @@ public class Project { return stats; } + @Nested public void setStats(ProjectStatsAll stats) { this.stats = stats; } @@ -174,6 +177,7 @@ public class Project { return userActions; } + @Nested("user") public void setUserActions(UserActions userActions) { this.userActions = userActions; } @@ -182,6 +186,7 @@ public class Project { return settings; } + @Nested public void setSettings(ProjectSettings settings) { this.settings = settings; } diff --git a/src/main/java/io/papermc/hangar/service/api/ProjectApiService.java b/src/main/java/io/papermc/hangar/service/api/ProjectApiService.java index 11810a164..5be753e49 100644 --- a/src/main/java/io/papermc/hangar/service/api/ProjectApiService.java +++ b/src/main/java/io/papermc/hangar/service/api/ProjectApiService.java @@ -1,22 +1,91 @@ package io.papermc.hangar.service.api; +import io.papermc.hangar.config.hangar.HangarConfig; import io.papermc.hangar.db.dao.HangarDao; import io.papermc.hangar.db.dao.api.ProjectsApiDao; +import io.papermc.hangar.model.Category; +import io.papermc.hangar.model.generated.Project; import io.papermc.hangar.model.generated.ProjectMember; +import io.papermc.hangar.model.generated.ProjectSortingStrategy; import io.papermc.hangar.model.generated.ProjectStatsDay; +import io.papermc.hangar.model.generated.Tag; +import io.papermc.hangar.service.pluginupload.ProjectFiles; +import io.papermc.hangar.util.RouteHelper; +import io.papermc.hangar.util.TemplateHelper; import org.springframework.stereotype.Service; +import java.nio.file.Path; import java.time.LocalDate; import java.util.List; import java.util.Map; +import java.util.stream.Collectors; @Service public class ProjectApiService { + private final HangarConfig hangarConfig; private final HangarDao projectApiDao; + private final ProjectFiles projectFiles; + private final RouteHelper routeHelper; + private final TemplateHelper templateHelper; - public ProjectApiService(HangarDao projectApiDao) { + public ProjectApiService(HangarConfig hangarConfig, HangarDao projectApiDao, ProjectFiles projectFiles, RouteHelper routeHelper, TemplateHelper templateHelper) { + this.hangarConfig = hangarConfig; this.projectApiDao = projectApiDao; + this.projectFiles = projectFiles; + this.routeHelper = routeHelper; + this.templateHelper = templateHelper; + } + + public Project getProject(String pluginId, boolean seeHidden, Long requesterId) { + Project project = projectApiDao.get().listProjects(pluginId, null, seeHidden, requesterId, null, null, null, null, null, 1, 0).stream().findFirst().orElse(null); + if (project != null) { + setProjectIconUrl(project); + } + return project; + } + + public List getProjects(String pluginId, List categories, List tags, String query, String owner, boolean seeHidden, Long requesterId, ProjectSortingStrategy sort, boolean orderWithRelevance, long limit, long offset) { + String ordering = sort.getSql(); + if (orderWithRelevance && query != null && !query.isEmpty()) { + String relevance = "ts_rank(p.search_words, websearch_to_tsquery_postfix('english', :query)) DESC"; + if(query.endsWith(" ")) { + relevance = "ts_rank(p.search_words, websearch_to_tsquery('english', :query)) DESC"; + } + String orderingFirstHalf; + // 1483056000 is the Ore epoch + // 86400 seconds to days + // 604800‬ seconds to weeks + switch(sort){ + case STARS: orderingFirstHalf = "p.starts * "; break; + case DOWNLOADS: orderingFirstHalf ="(p.downloads / 100) * "; break; + case VIEWS: orderingFirstHalf ="(p.views / 200) *"; break; + case NEWEST: orderingFirstHalf ="((EXTRACT(EPOCH FROM p.created_at) - 1483056000) / 86400) *"; break; + case UPDATED: orderingFirstHalf ="((EXTRACT(EPOCH FROM p.last_updated) - 1483056000) / 604800) *"; break; + case ONLY_RELEVANCE: orderingFirstHalf =""; break; + case RECENT_DOWNLOADS : orderingFirstHalf ="p.recent_views *"; break; + case RECENT_VIEWS: orderingFirstHalf ="p.recent_downloads*"; break; + default: + orderingFirstHalf = " "; // Just in case and so that the ide doesnt complain + } + ordering = orderingFirstHalf + relevance; + } + List projects = projectApiDao.get().listProjects(pluginId, owner, seeHidden, requesterId, ordering, getCategoryNumbers(categories), getTagsNames(tags), trimQuery(query), getQueryStatement(query), limit, offset); + projects.forEach(this::setProjectIconUrl); + return projects; + } + + private void setProjectIconUrl(Project project) { + Path iconPath = projectFiles.getIconPath(project.getNamespace().getOwner(), project.getNamespace().getSlug()); + if (iconPath != null) { + project.setIconUrl(hangarConfig.getBaseUrl() + routeHelper.getRouteUrl("projects.showIcon", project.getNamespace().getOwner(), project.getNamespace().getSlug())); + } else { + project.setIconUrl(templateHelper.avatarUrl(project.getNamespace().getOwner())); + } + } + + public long countProjects(String pluginId, List categories, List tags, String query, String owner, boolean seeHidden, Long requesterId) { + return projectApiDao.get().countProjects(pluginId, owner, seeHidden, requesterId, getCategoryNumbers(categories), getTagsNames(tags), trimQuery(query), getQueryStatement(query)); } public List getProjectMembers(String pluginId, long limit, long offset) { @@ -26,4 +95,36 @@ public class ProjectApiService { public Map getProjectStats(String pluginId, LocalDate fromDate, LocalDate toDate) { return projectApiDao.get().projectStats(pluginId, fromDate, toDate); } + + private String trimQuery(String query){ + String trimmedQuery = null; + if(query != null && !query.isBlank()) { + trimmedQuery = query.trim(); // Ore#APIV2Queries line 169 && 200 + } + return trimmedQuery; + } + + private String getQueryStatement(String query){ + String queryStatement = null; + if(query != null && !query.isBlank()){ + if(query.endsWith(" ")) { + queryStatement = "p.search_words @@ websearch_to_tsquery('english', :query)"; + } else { + queryStatement = "p.search_words @@ websearch_to_tsquery_postfix('english', :query)"; + } + } + return queryStatement; + } + + private List getCategoryNumbers(List categories){ + return categories == null ? null : categories.stream().map(Category::getValue).collect(Collectors.toList()); + } + + private List getTagsNames(List tags){ + return tags == null ? null : tags.stream().filter(tag -> tag.getData() == null).map(Tag::getName).collect(Collectors.toList()); + } + + private List getTagsNamesAndVersion(List tags){ + return tags == null ? null : tags.stream().filter(tag -> tag.getData() != null).map(tag -> " (" + tag.getName() + "," + tag.getData() + ") ").collect(Collectors.toList()); + } } diff --git a/src/main/java/io/papermc/hangar/service/project/ProjectService.java b/src/main/java/io/papermc/hangar/service/project/ProjectService.java index b51446dec..021cea84b 100644 --- a/src/main/java/io/papermc/hangar/service/project/ProjectService.java +++ b/src/main/java/io/papermc/hangar/service/project/ProjectService.java @@ -214,71 +214,6 @@ public class ProjectService { return projectDao.get().getUnhealthyProjects(hangarConfig.projects.getStaleAge().toMillis()); } - // TODO move to API daos - public List getProjects(String pluginId, List categories, List tags, String query, String owner, boolean seeHidden, Long requesterId, ProjectSortingStrategy sort, boolean orderWithRelevance, long limit, long offset) { - String ordering = sort.getSql(); - if (orderWithRelevance && query != null && !query.isEmpty()) { - String relevance = "ts_rank(p.search_words, websearch_to_tsquery_postfix('english', :query)) DESC"; - if(query.endsWith(" ")) { - relevance = "ts_rank(p.search_words, websearch_to_tsquery('english', :query)) DESC"; - } - String orderingFirstHalf; - // 1483056000 is the Ore epoch - // 86400 seconds to days - // 604800‬ seconds to weeks - switch(sort){ - case STARS: orderingFirstHalf = "p.starts * "; break; - case DOWNLOADS: orderingFirstHalf ="(p.downloads / 100) * "; break; - case VIEWS: orderingFirstHalf ="(p.views / 200) *"; break; - case NEWEST: orderingFirstHalf ="((EXTRACT(EPOCH FROM p.created_at) - 1483056000) / 86400) *"; break; - case UPDATED: orderingFirstHalf ="((EXTRACT(EPOCH FROM p.last_updated) - 1483056000) / 604800) *"; break; - case ONLY_RELEVANCE: orderingFirstHalf =""; break; - case RECENT_DOWNLOADS : orderingFirstHalf ="p.recent_views *"; break; - case RECENT_VIEWS: orderingFirstHalf ="p.recent_downloads*"; break; - default: - orderingFirstHalf = " "; // Just in case and so that the ide doesnt complain - } - ordering = orderingFirstHalf + relevance; - } - return projectApiDao.get().listProjects(pluginId, owner, seeHidden, requesterId, ordering, getCategoryNumbers(categories), getTagsNames(tags), trimQuery(query), getQueryStatement(query), limit, offset); - } - - public long countProjects(String pluginId, List categories, List tags, String query, String owner, boolean seeHidden, Long requesterId) { - return projectApiDao.get().countProjects(pluginId, owner, seeHidden, requesterId, getCategoryNumbers(categories), getTagsNames(tags), trimQuery(query), getQueryStatement(query)); - } - - private String trimQuery(String query){ - String trimmedQuery = null; - if(query != null && !query.isBlank()) { - trimmedQuery = query.trim(); // Ore#APIV2Queries line 169 && 200 - } - return trimmedQuery; - } - - private String getQueryStatement(String query){ - String queryStatement = null; - if(query != null && !query.isBlank()){ - if(query.endsWith(" ")) { - queryStatement = "p.search_words @@ websearch_to_tsquery('english', :query)"; - } else { - queryStatement = "p.search_words @@ websearch_to_tsquery_postfix('english', :query)"; - } - } - return queryStatement; - } - - private List getCategoryNumbers(List categories){ - return categories == null ? null : categories.stream().map(Category::getValue).collect(Collectors.toList()); - } - - private List getTagsNames(List tags){ - return tags == null ? null : tags.stream().filter(tag -> tag.getData() == null).map(Tag::getName).collect(Collectors.toList()); - } - - private List getTagsNamesAndVersion(List tags){ - return tags == null ? null : tags.stream().filter(tag -> tag.getData() != null).map(tag -> " (" + tag.getName() + "," + tag.getData() + ") ").collect(Collectors.toList()); - } - public List getPluginsWithMissingFiles() { List projectMissingFiles = projectDao.get().allProjectsForMissingFiles(); return projectMissingFiles.stream()