implemented project stargazing and watching and the userGrid view (#54)

Co-authored-by: MiniDigger <admin@minidigger.me>
This commit is contained in:
Jake Potrebic 2020-07-31 02:36:05 -07:00 committed by GitHub
parent c26110afa3
commit 1ed562d952
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
7 changed files with 120 additions and 36 deletions

View File

@ -27,10 +27,12 @@ import me.minidigger.hangar.util.AlertUtil.AlertType;
import me.minidigger.hangar.util.HangarException;
import me.minidigger.hangar.util.RouteHelper;
import me.minidigger.hangar.util.StringUtils;
import me.minidigger.hangar.util.TriFunction;
import org.apache.commons.logging.Log;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.http.MediaType;
import org.springframework.security.access.annotation.Secured;
import org.springframework.security.core.parameters.P;
import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.PathVariable;
@ -40,9 +42,10 @@ import org.springframework.web.bind.annotation.RequestParam;
import org.springframework.web.servlet.ModelAndView;
import org.springframework.web.servlet.mvc.support.RedirectAttributes;
import org.springframework.web.servlet.view.RedirectView;
import java.util.Collection;
import javax.servlet.http.HttpServletRequest;
import java.util.Set;
import java.util.function.Function;
import java.util.regex.Pattern;
@Controller
@ -323,15 +326,22 @@ public class ProjectsController extends HangarController {
return null; // TODO implement addMessage request controller
}
@RequestMapping("/{author}/{slug}/stars")
public Object showStargazers(@PathVariable Object author, @PathVariable Object slug, @RequestParam Object page) {
return null; // TODO implement showStargazers request controller
@GetMapping("/{author}/{slug}/stars")
public ModelAndView showStargazers(@PathVariable String author, @PathVariable String slug, @RequestParam(required = false, defaultValue = "1") Integer page) {
return showUserGrid(author, slug, page, "Stargazers", projectService::getProjectStargazers);
}
@Secured("ROLE_USER")
@RequestMapping("/{author}/{slug}/stars/toggle")
public Object toggleStarred(@PathVariable Object author, @PathVariable Object slug) {
return null; // TODO implement toggleStarred request controller
@PostMapping("/{author}/{slug}/stars/toggle")
@ResponseStatus(HttpStatus.OK)
public void toggleStarred(@PathVariable String author, @PathVariable String slug) {
ProjectData projectData = projectService.getProjectData(author, slug);
ScopedProjectData scopedProjectData = projectService.getScopedProjectData(projectData.getProject().getId());
if (scopedProjectData.isStarred()) {
userDao.get().removeStargazing(projectData.getProject().getId(), userService.getCurrentUser().getId());
} else {
userDao.get().setStargazing(projectData.getProject().getId(), userService.getCurrentUser().getId());
}
}
@Secured("ROLE_USER")
@ -346,15 +356,40 @@ public class ProjectsController extends HangarController {
// TODO user action logging
}
@RequestMapping("/{author}/{slug}/watchers")
public Object showWatchers(@PathVariable Object author, @PathVariable Object slug, @RequestParam Object page) {
return null; // TODO implement showWatchers request controller
@GetMapping("/{author}/{slug}/watchers")
public ModelAndView showWatchers(@PathVariable String author, @PathVariable String slug, @RequestParam(required = false, defaultValue = "1") Integer page) {
return showUserGrid(author, slug, page, "Watchers", projectService::getProjectWatchers);
}
@Secured("ROLE_USER")
@RequestMapping("/{author}/{slug}/watchers/{watching}")
public Object setWatching(@PathVariable Object author, @PathVariable Object slug, @PathVariable Object watching) {
return null; // TODO implement setWatching request controller
@PostMapping("/{author}/{slug}/watchers/{watching}")
@ResponseStatus(HttpStatus.OK)
public void setWatching(@PathVariable String author, @PathVariable String slug, @PathVariable boolean watching) {
ProjectData projectData = projectService.getProjectData(author, slug);
ScopedProjectData scopedProjectData = projectService.getScopedProjectData(projectData.getProject().getId());
if (scopedProjectData.isWatching() == watching) return; // No change
if (watching) {
userDao.get().setWatching(projectData.getProject().getId(), userService.getCurrentUser().getId());
} else {
userDao.get().removeWatching(projectData.getProject().getId(), userService.getCurrentUser().getId());
}
}
private ModelAndView showUserGrid(String author, String slug, Integer page, String title, TriFunction<Long, Integer, Integer, Collection<UsersTable>> getUsers) {
ProjectData projectData = projectService.getProjectData(author, slug);
ScopedProjectData scopedProjectData = projectService.getScopedProjectData(projectData.getProject().getId());
int pageSize = hangarConfig.projects.getUserGridPageSize();
int offset = (page - 1) * pageSize;
ModelAndView mav = new ModelAndView("projects/userGrid");
mav.addObject("title", title);
mav.addObject("p", projectData);
mav.addObject("sp", scopedProjectData);
mav.addObject("users", getUsers.apply(projectData.getProject().getId(), offset, pageSize));
mav.addObject("page", page);
mav.addObject("pageSize", pageSize);
return fillModel(mav);
}
}

View File

@ -82,4 +82,22 @@ public interface UserDao {
@UseStringTemplateEngine
@RegisterBeanMapper(Staff.class)
List<Staff> getStaff(long offset, long pageSize, @Define String sort);
@SqlQuery("SELECT u.* FROM project_watchers pw JOIN users u ON pw.user_id = u.id WHERE project_id = :projectId OFFSET :offset LIMIT :limit")
List<UsersTable> getProjectWatchers(long projectId, int offset, Integer limit);
@SqlUpdate("INSERT INTO project_watchers (project_id, user_id) VALUES (:projectId, :userId)")
void setWatching(long projectId, long userId);
@SqlUpdate("DELETE FROM project_watchers WHERE project_id = :projectId AND user_id = :userId")
void removeWatching(long projectId, long userId);
@SqlQuery("SELECT u.* FROM project_stars ps JOIN users u ON ps.user_id = u.id WHERE project_id = :projectId OFFSET :offset LIMIT :limit")
List<UsersTable> getProjectStargazers(long projectId, int offset, Integer limit);
@SqlUpdate("INSERT INTO project_stars (project_id, user_id) VALUES (:projectId, :userId)")
void setStargazing(long projectId, long userId);
@SqlUpdate("DELETE FROM project_stars WHERE project_id = :projectId AND user_id = :userId")
void removeStargazing(long projectId, long userId);
}

View File

@ -76,8 +76,8 @@ public class ProjectService {
String lastVisibilityChangeUser = null;
ProjectVersionsTable recommendedVersion = null;
String iconUrl = "";
long starCount = 0;
long watcherCount = 0;
long starCount = userDao.get().getProjectStargazers(projectsTable.getId(), 0, null).size();
long watcherCount = userDao.get().getProjectWatchers(projectsTable.getId(), 0, null).size();
ProjectViewSettings settings = new ProjectViewSettings(
projectsTable.getKeywords(),
projectsTable.getHomepage(),
@ -127,6 +127,14 @@ public class ProjectService {
// TODO user action log
}
public List<UsersTable> getProjectWatchers(long projectId, int offset, int limit) {
return userDao.get().getProjectWatchers(projectId, offset, limit);
}
public List<UsersTable> getProjectStargazers(long projectId, int offset, int limit) {
return userDao.get().getProjectStargazers(projectId, offset, limit);
}
public Project getProjectApi(String pluginId) { // TODO still probably have to work out a standard for how to handle the api models
ProjectsTable projectsTable = projectDao.get().getByPluginId(pluginId);
if (projectsTable == null) return null;

View File

@ -0,0 +1,7 @@
package me.minidigger.hangar.util;
@FunctionalInterface
public interface TriFunction<T, U, S, R> {
R apply(T t, U u, S s);
}

View File

@ -55,8 +55,8 @@ Documentation page within Project overview.
<p><@spring.messageArgs code="project.category.info" args=[p.project.category.title] /></p>
<p><@spring.messageArgs code="project.publishDate" args=[p.project.createdAt.format("yyyy-MM-dd")] /></p>
<p><span id="view-count"></span> views</p>
<p><span id="star-count"></span> <a href="${routes.getRouteUrl("projects.showStargazers", p.project.ownerName, p.project.slug, "")}">stars</a></p>
<p><span id="watcher-count"></span> <a href="${routes.getRouteUrl("projects.showWatchers", p.project.ownerName, p.project.slug, "")}">watchers</a></p>
<p><span id="star-count"></span> <a href="${routes.getRouteUrl("projects.showStargazers", p.project.ownerName, p.project.slug, "")}">star<#if p.starCount != 1>s</#if></a></p>
<p><span id="watcher-count"></span> <a href="${routes.getRouteUrl("projects.showWatchers", p.project.ownerName, p.project.slug, "")}">watcher<#if p.watcherCount != 1>s</#if></a></p>
<p><span id="download-count"></span> total downloads</p>
<#if p.project.licenseName?has_content && p.project.licenseUrl?has_content>
<p>

View File

@ -3,35 +3,51 @@
<#import "*/utils/userAvatar.ftlh" as userAvatar />
<#import "*/projects/view.ftlh" as projects />
@import controllers.sugar.Requests.OreRequest
@import models.viewhelper.{ProjectData, ScopedProjectData}
@import ore.OreConfig
@import ore.markdown.MarkdownRenderer
@import ore.models.user.User
@import util.syntax._
@(title: String, call: Int => Call, p: ProjectData, sp: ScopedProjectData, users: Seq[User], page: Int, pageSize: Int)(implicit messages: Messages, request: OreRequest[_], flash: Flash, config: OreConfig, renderer: MarkdownRenderer, assetsFinder: AssetsFinder)
<#--@import controllers.sugar.Requests.OreRequest-->
<#--@import models.viewhelper.{ProjectData, ScopedProjectData}-->
<#--@import ore.OreConfig-->
<#--@import ore.markdown.MarkdownRenderer-->
<#--@import ore.models.user.User-->
<#--@import util.syntax._-->
<#--@(title: String, call: Int => Call, p: ProjectData, sp: ScopedProjectData, users: Seq[User], page: Int, pageSize: Int)(implicit messages: Messages, request: OreRequest[_], flash: Flash, config: OreConfig, renderer: MarkdownRenderer, assetsFinder: AssetsFinder)-->
<#assign columns = 3>
<#-- @ftlvariable name="pageSize" type="java.lang.Integer" -->
<#-- @ftlvariable name="page" type="java.lang.Integer" -->
<@projects.view p=p sp=sp active="">
<div class="row user-grid">
<div class="col-xs-12">
<h3 class="text-bold mb-0">@title</h3>
<#-- @ftlvariable name="title" type="java.lang.String" -->
<h3 class="text-bold mb-0">${title}</h3>
<div class="extra-divider"></div>
</div>
<#list 1..columns as i>
<#list 1..columns as div>
<div class="col-md-4">
@users.iterator.zipWithIndex.filter(_._2 % columns == div).map(_._1).map { user =>
<div class="media">
<div class="media-left">
<@userAvatar.userAvatar userName=user.name avatarUrl=user.avatarUrl clazz="user-avatar-sm media-object" />
<#-- @ftlvariable name="users" type="java.util.Collection<me.minidigger.hangar.db.model.UsersTable>" -->
<#-- @ftlvariable name="gridUser" type="me.minidigger.hangar.db.model.UsersTable" -->
<#list users?filter(u -> (users?seq_index_of(u) + 1) % columns == div) as gridUser>
<div class="media">
<div class="media-left">
<@userAvatar.userAvatar userName=gridUser.name avatarUrl=gridUser.avatarUrl clazz="user-avatar-sm media-object" />
</div>
<div class="media-body center-vertically">
<a class="media-heading mb-0" href="${routes.getRouteUrl("users.showProjects", gridUser.name)}">${gridUser.name}</a>
</div>
</div>
<div class="media-body center-vertically">
<a class="media-heading mb-0" href="${routes.getRouteUrl("users.showProjects", user.name)}">${user.name}</a>
</div>
</div>
}
</#list>
<#-- @users.iterator.zipWithIndex.filter(_._2 % columns == div).map(_._1).map { user =>-->
<#-- <div class="media">-->
<#-- <div class="media-left">-->
<#-- <@userAvatar.userAvatar userName=user.name avatarUrl=user.avatarUrl clazz="user-avatar-sm media-object" />-->
<#-- </div>-->
<#-- <div class="media-body center-vertically">-->
<#-- <a class="media-heading mb-0" href="${routes.getRouteUrl("users.showProjects", user.name)}">${user.name}</a>-->
<#-- </div>-->
<#-- </div>-->
<#-- }-->
</div>
</#list>

View File

@ -86,7 +86,7 @@ Base template for Project overview.
}
-->
<#if p.getVisibility() != Visibility.SOFTDELETE>
<#if p.visibility != Visibility.SOFTDELETE>
<#if headerData.hasUser() && p.isOwner(headerData.getCurrentUser())>
<button class="btn btn-default btn-star">
<i id="icon-star" <#if sp.starred>