moved project api to its own service

This commit is contained in:
Jake Potrebic 2020-09-02 18:13:32 -07:00
parent f3a01dc9bb
commit 5809d70caf
No known key found for this signature in database
GPG Key ID: 7C58557EC9C421F8
6 changed files with 119 additions and 76 deletions

View File

@ -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<Project> projects = projectService.getProjects(
List<Project> 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<Project> 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);

View File

@ -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, " +
" <if(requesterId)> " +
" 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, " +
" <endif>" +
" ps.homepage," +
@ -60,7 +60,7 @@ public interface ProjectsApiDao {
" <if(query)> AND ( <queryStatement> ) <endif> " +
" <if(tags)> 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 (<tags>) ) <endif> " +
" ORDER BY <orderBy> " +
" <if(orderBy)>ORDER BY <orderBy><endif> " +
" LIMIT :limit" +
" OFFSET :offset")
@RegisterColumnMapper(PromotedVersionMapper.class)

View File

@ -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;

View File

@ -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;
}

View File

@ -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<ProjectsApiDao> projectApiDao;
private final ProjectFiles projectFiles;
private final RouteHelper routeHelper;
private final TemplateHelper templateHelper;
public ProjectApiService(HangarDao<ProjectsApiDao> projectApiDao) {
public ProjectApiService(HangarConfig hangarConfig, HangarDao<ProjectsApiDao> 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<Project> getProjects(String pluginId, List<Category> categories, List<Tag> 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<Project> 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<Category> categories, List<Tag> 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<ProjectMember> getProjectMembers(String pluginId, long limit, long offset) {
@ -26,4 +95,36 @@ public class ProjectApiService {
public Map<String, ProjectStatsDay> 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<Integer> getCategoryNumbers(List<Category> categories){
return categories == null ? null : categories.stream().map(Category::getValue).collect(Collectors.toList());
}
private List<String> getTagsNames(List<Tag> tags){
return tags == null ? null : tags.stream().filter(tag -> tag.getData() == null).map(Tag::getName).collect(Collectors.toList());
}
private List<String> getTagsNamesAndVersion(List<Tag> tags){
return tags == null ? null : tags.stream().filter(tag -> tag.getData() != null).map(tag -> " (" + tag.getName() + "," + tag.getData() + ") ").collect(Collectors.toList());
}
}

View File

@ -214,71 +214,6 @@ public class ProjectService {
return projectDao.get().getUnhealthyProjects(hangarConfig.projects.getStaleAge().toMillis());
}
// TODO move to API daos
public List<Project> getProjects(String pluginId, List<Category> categories, List<Tag> 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<Category> categories, List<Tag> 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<Integer> getCategoryNumbers(List<Category> categories){
return categories == null ? null : categories.stream().map(Category::getValue).collect(Collectors.toList());
}
private List<String> getTagsNames(List<Tag> tags){
return tags == null ? null : tags.stream().filter(tag -> tag.getData() == null).map(Tag::getName).collect(Collectors.toList());
}
private List<String> getTagsNamesAndVersion(List<Tag> tags){
return tags == null ? null : tags.stream().filter(tag -> tag.getData() != null).map(tag -> " (" + tag.getName() + "," + tag.getData() + ") ").collect(Collectors.toList());
}
public List<ProjectMissingFile> getPluginsWithMissingFiles() {
List<ProjectMissingFile> projectMissingFiles = projectDao.get().allProjectsForMissingFiles();
return projectMissingFiles.stream()