Vote3Resource.java
/*
* Copyright © 2023-2025 The CTAN Team and individual authors
*
* This file is distributed under the 3-clause BSD license.
* See file LICENSE for details.
*/
package org.ctan.site.resources.catalogue;
import java.time.LocalDateTime;
import org.ctan.site.domain.account.User;
import org.ctan.site.domain.account.User.PrintableUser;
import org.ctan.site.domain.catalogue.Pkg;
import org.ctan.site.domain.site.Vote;
import org.ctan.site.services.catalogue.VoteService;
import org.ctan.site.services.catalogue.VoteService.VoteSummaryTo;
import org.ctan.site.services.catalogue.VoteService.VoteTo;
import org.ctan.site.stores.PkgStore;
import org.ctan.site.stores.UserStore;
import org.ctan.site.stores.VoteStore;
import org.glassfish.jersey.media.multipart.FormDataParam;
import edu.umd.cs.findbugs.annotations.SuppressFBWarnings;
import io.dropwizard.hibernate.UnitOfWork;
import jakarta.annotation.security.PermitAll;
import jakarta.ws.rs.DELETE;
import jakarta.ws.rs.DefaultValue;
import jakarta.ws.rs.GET;
import jakarta.ws.rs.POST;
import jakarta.ws.rs.Path;
import jakarta.ws.rs.PathParam;
import jakarta.ws.rs.Produces;
import jakarta.ws.rs.QueryParam;
import jakarta.ws.rs.WebApplicationException;
import jakarta.ws.rs.core.MediaType;
import jakarta.ws.rs.core.Response.Status;
import lombok.NonNull;
/**
* The class <code>Vote3Resource</code> contains the controller for the vote
* resource.
*
* @author <a href="mailto:gene@ctan.org">Gerd Neugebauer</a>
*/
@Path("/3.0")
@Produces({MediaType.APPLICATION_JSON, MediaType.APPLICATION_XML})
public class Vote3Resource {
/**
* The field <code>voteStore</code> contains the vote store.
*/
private VoteStore voteStore;
/**
* The field <code>pkgStore</code> contains the package store.
*/
private PkgStore pkgStore;
/**
* The field <code>userStore</code> contains the user store.
*/
private UserStore userStore;
/**
* The field <code>voteService</code> contains the vote service.
*/
private @NonNull VoteService voteService;
/**
* This is the constructor for <code>Vote3Resource</code>.
*
* @param voteStore the vote store
* @param voteService the vote service
* @param pkgStore the package store
* @param userStore the user store
*/
@SuppressFBWarnings(value = {"CT_CONSTRUCTOR_THROW", "EI_EXPOSE_REP2"})
public Vote3Resource(@NonNull VoteStore voteStore,
@NonNull VoteService voteService, @NonNull PkgStore pkgStore,
@NonNull UserStore userStore) {
this.voteStore = voteStore;
this.voteService = voteService;
this.pkgStore = pkgStore;
this.userStore = userStore;
}
/**
* The method <code>deleteVote</code> provides means to delete a vote.
*
* @param pkg the name of the package
* @param account the account name
* @return <code>true</code> iff the deletion was successful
*/
@DELETE
@Path("/vote")
@PermitAll
@UnitOfWork(value = "siteDb")
public boolean deleteVote(@FormDataParam("pkg") String pkg,
@FormDataParam("account") String account) {
Pkg p = pkgStore.getByKey(pkg);
if (p == null) {
throw new WebApplicationException("unknown package",
Status.BAD_REQUEST);
}
if (account == null) {
throw new WebApplicationException("undefined user",
Status.BAD_REQUEST);
}
User user = userStore.getByAccount(account); // TODO a&a
if (user == null) {
throw new WebApplicationException("unknown user",
Status.BAD_REQUEST);
}
Vote vote = voteStore.getByAccountAndPkg(user, p);
if (vote == null) {
throw new WebApplicationException("unknown vote",
Status.BAD_REQUEST);
}
return voteStore.remove(vote);
}
/**
* The method <code>listVotesByAccount</code> provides means to retrieve a
* list of votes for a user.
*
* @param uid the user id
* @param page the page
* @param size the page size
* @return a list of matching author summaries
*/
@GET
@Path("/votes/by/{user}")
@PermitAll
@UnitOfWork(value = "siteDb")
public VoteSummaryTo listVotesByAccount(
@PathParam("user") String uid,
@DefaultValue("0") @QueryParam("page") long page,
@DefaultValue("16") @QueryParam("size") long size) {
if (page < 0L) {
throw new WebApplicationException("negative page",
Status.BAD_REQUEST);
}
if (size <= 0L) {
throw new WebApplicationException("non-positive size",
Status.BAD_REQUEST);
}
User user = userStore.getByAccount(uid);
if (user == null) {
throw new WebApplicationException(Status.NOT_FOUND);
}
return voteService.find(user, page, size);
}
/**
* The method <code>listVotesByPkg</code> provides means to retrieve a list
* of votes for a package.
*
* @param pkgKey the package name
* @param page the page
* @param size the page size
* @return a list of matching author summaries
*/
@GET
@Path("/votes/{pkg}")
@PermitAll
@UnitOfWork(value = "siteDb")
public VoteSummaryTo listVotesByPkg(
@PathParam("pkg") String pkgKey,
@DefaultValue("0") @QueryParam("page") long page,
@DefaultValue("16") @QueryParam("size") long size) {
if (page < 0L) {
throw new WebApplicationException("negative page",
Status.BAD_REQUEST);
}
if (size <= 0L) {
throw new WebApplicationException("non-positive size",
Status.BAD_REQUEST);
}
Pkg pkg = pkgStore.getByKey(pkgKey);
if (pkg == null) {
throw new WebApplicationException(Status.NOT_FOUND);
}
return voteService.find(pkg, page, size);
}
/**
* The method <code>vote</code> provides means to update a vote.
*
* @param pkg the package name
* @param rating the rating in the range [1, 5]
* @param expertise the expertise in the range [0, 4]
* @param comment the comment
* @param account the account name of the user
* @return the updated vote record
*/
@POST
@Path("/vote")
@PermitAll
@UnitOfWork(value = "siteDb")
public VoteTo vote(@FormDataParam("pkg") String pkg,
@FormDataParam("rating") int rating,
@FormDataParam("expertise") int expertise,
@FormDataParam("comment") String comment,
@FormDataParam("account") String account) {
Pkg p = pkgStore.getByKey(pkg);
if (p == null) {
throw new WebApplicationException("unknown package",
Status.BAD_REQUEST);
}
User user = userStore.getByAccount(account); // TODO a&a
if (user == null) {
throw new WebApplicationException("unknown user",
Status.BAD_REQUEST);
}
if (rating < 1 || rating > 5) {
throw new WebApplicationException("invalid rating",
Status.BAD_REQUEST);
}
if (expertise < 0 || expertise > 4) {
throw new WebApplicationException("invalid expertise",
Status.BAD_REQUEST);
}
Vote vote = voteStore.getByAccountAndPkg(user, p);
if (vote == null) {
vote = Vote.builder()
.comment(comment)
.pkg(p)
.rating(rating)
.expertise(expertise)
.lastModified(LocalDateTime.now())
.user(user)
.build();
} else {
vote.setComment(comment);
vote.setExpertise(expertise);
vote.setRating(rating);
vote.setLastModified(LocalDateTime.now());
}
vote = voteStore.save(vote);
PrintableUser printable = user.getPrintable();
return VoteTo.builder()
.account(printable.getAccount())
.comment(vote.getComment())
.date(vote.getDate())
.expertise(vote.getExpertise())
.rating(vote.getRating())
.user(printable.getName())
.build();
}
}