mirror of
https://github.com/Athou/commafeed.git
synced 2026-03-21 21:37:29 +00:00
removed wicket and tomee, use dropwizard instead. remove wro4j, use gulp instead
This commit is contained in:
226
src/main/java/com/commafeed/frontend/resource/AdminREST.java
Normal file
226
src/main/java/com/commafeed/frontend/resource/AdminREST.java
Normal file
@@ -0,0 +1,226 @@
|
||||
package com.commafeed.frontend.resource;
|
||||
|
||||
import io.dropwizard.auth.Auth;
|
||||
import io.dropwizard.hibernate.UnitOfWork;
|
||||
|
||||
import java.util.Map;
|
||||
import java.util.Set;
|
||||
|
||||
import javax.ws.rs.Consumes;
|
||||
import javax.ws.rs.GET;
|
||||
import javax.ws.rs.POST;
|
||||
import javax.ws.rs.Path;
|
||||
import javax.ws.rs.PathParam;
|
||||
import javax.ws.rs.Produces;
|
||||
import javax.ws.rs.core.MediaType;
|
||||
import javax.ws.rs.core.Response;
|
||||
import javax.ws.rs.core.Response.Status;
|
||||
|
||||
import lombok.AllArgsConstructor;
|
||||
|
||||
import org.apache.commons.lang.StringUtils;
|
||||
|
||||
import com.codahale.metrics.MetricRegistry;
|
||||
import com.commafeed.CommaFeedApplication;
|
||||
import com.commafeed.CommaFeedConfiguration;
|
||||
import com.commafeed.CommaFeedConfiguration.ApplicationSettings;
|
||||
import com.commafeed.backend.dao.UserDAO;
|
||||
import com.commafeed.backend.dao.UserRoleDAO;
|
||||
import com.commafeed.backend.model.User;
|
||||
import com.commafeed.backend.model.UserRole;
|
||||
import com.commafeed.backend.model.UserRole.Role;
|
||||
import com.commafeed.backend.service.DatabaseCleaningService;
|
||||
import com.commafeed.backend.service.PasswordEncryptionService;
|
||||
import com.commafeed.backend.service.UserService;
|
||||
import com.commafeed.frontend.model.UserModel;
|
||||
import com.commafeed.frontend.model.request.IDRequest;
|
||||
import com.google.common.base.Preconditions;
|
||||
import com.google.common.collect.Maps;
|
||||
import com.google.common.collect.Sets;
|
||||
import com.wordnik.swagger.annotations.Api;
|
||||
import com.wordnik.swagger.annotations.ApiOperation;
|
||||
import com.wordnik.swagger.annotations.ApiParam;
|
||||
|
||||
@Path("/admin")
|
||||
@Api(value = "/admin", description = "Operations about application administration")
|
||||
@Produces(MediaType.APPLICATION_JSON)
|
||||
@Consumes(MediaType.APPLICATION_JSON)
|
||||
@AllArgsConstructor
|
||||
public class AdminREST {
|
||||
|
||||
// TODO check roles
|
||||
|
||||
private final UserDAO userDAO;
|
||||
private final UserRoleDAO userRoleDAO;
|
||||
private final UserService userService;
|
||||
private final PasswordEncryptionService encryptionService;
|
||||
private final DatabaseCleaningService cleaner;
|
||||
private final CommaFeedConfiguration config;
|
||||
private final MetricRegistry metrics;
|
||||
|
||||
@Path("/user/save")
|
||||
@POST
|
||||
@UnitOfWork
|
||||
@ApiOperation(value = "Save or update a user", notes = "Save or update a user. If the id is not specified, a new user will be created")
|
||||
public Response save(@Auth User user, @ApiParam(required = true) UserModel userModel) {
|
||||
Preconditions.checkNotNull(userModel);
|
||||
Preconditions.checkNotNull(userModel.getName());
|
||||
|
||||
Long id = userModel.getId();
|
||||
if (id == null) {
|
||||
Preconditions.checkNotNull(userModel.getPassword());
|
||||
|
||||
Set<Role> roles = Sets.newHashSet(Role.USER);
|
||||
if (userModel.isAdmin()) {
|
||||
roles.add(Role.ADMIN);
|
||||
}
|
||||
try {
|
||||
userService.register(userModel.getName(), userModel.getPassword(), userModel.getEmail(), roles, true);
|
||||
} catch (Exception e) {
|
||||
return Response.status(Status.CONFLICT).entity(e.getMessage()).build();
|
||||
}
|
||||
} else {
|
||||
User u = userDAO.findById(id);
|
||||
if (CommaFeedApplication.USERNAME_ADMIN.equals(u.getName()) && !userModel.isEnabled()) {
|
||||
return Response.status(Status.FORBIDDEN).entity("You cannot disable the admin user.").build();
|
||||
}
|
||||
u.setName(userModel.getName());
|
||||
if (StringUtils.isNotBlank(userModel.getPassword())) {
|
||||
u.setPassword(encryptionService.getEncryptedPassword(userModel.getPassword(), u.getSalt()));
|
||||
}
|
||||
u.setEmail(userModel.getEmail());
|
||||
u.setDisabled(!userModel.isEnabled());
|
||||
userDAO.saveOrUpdate(u);
|
||||
|
||||
Set<Role> roles = userRoleDAO.findRoles(u);
|
||||
if (userModel.isAdmin() && !roles.contains(Role.ADMIN)) {
|
||||
userRoleDAO.saveOrUpdate(new UserRole(u, Role.ADMIN));
|
||||
} else if (!userModel.isAdmin() && roles.contains(Role.ADMIN)) {
|
||||
if (CommaFeedApplication.USERNAME_ADMIN.equals(u.getName())) {
|
||||
return Response.status(Status.FORBIDDEN).entity("You cannot remove the admin role from the admin user.").build();
|
||||
}
|
||||
for (UserRole userRole : userRoleDAO.findAll(u)) {
|
||||
if (userRole.getRole() == Role.ADMIN) {
|
||||
userRoleDAO.delete(userRole);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
return Response.ok().build();
|
||||
|
||||
}
|
||||
|
||||
@Path("/user/get/{id}")
|
||||
@GET
|
||||
@UnitOfWork
|
||||
@ApiOperation(value = "Get user information", notes = "Get user information", response = UserModel.class)
|
||||
public Response getUser(@Auth User user, @ApiParam(value = "user id", required = true) @PathParam("id") Long id) {
|
||||
Preconditions.checkNotNull(id);
|
||||
User u = userDAO.findById(id);
|
||||
UserModel userModel = new UserModel();
|
||||
userModel.setId(u.getId());
|
||||
userModel.setName(u.getName());
|
||||
userModel.setEmail(u.getEmail());
|
||||
userModel.setEnabled(!u.isDisabled());
|
||||
for (UserRole role : userRoleDAO.findAll(u)) {
|
||||
if (role.getRole() == Role.ADMIN) {
|
||||
userModel.setAdmin(true);
|
||||
}
|
||||
}
|
||||
return Response.ok(userModel).build();
|
||||
}
|
||||
|
||||
@Path("/user/getAll")
|
||||
@GET
|
||||
@UnitOfWork
|
||||
@ApiOperation(value = "Get all users", notes = "Get all users", response = UserModel.class, responseContainer = "List")
|
||||
public Response getUsers(@Auth User user) {
|
||||
Map<Long, UserModel> users = Maps.newHashMap();
|
||||
for (UserRole role : userRoleDAO.findAll()) {
|
||||
User u = role.getUser();
|
||||
Long key = user.getId();
|
||||
UserModel userModel = users.get(key);
|
||||
if (userModel == null) {
|
||||
userModel = new UserModel();
|
||||
userModel.setId(u.getId());
|
||||
userModel.setName(u.getName());
|
||||
userModel.setEmail(u.getEmail());
|
||||
userModel.setEnabled(!u.isDisabled());
|
||||
userModel.setCreated(u.getCreated());
|
||||
userModel.setLastLogin(u.getLastLogin());
|
||||
users.put(key, userModel);
|
||||
}
|
||||
if (role.getRole() == Role.ADMIN) {
|
||||
userModel.setAdmin(true);
|
||||
}
|
||||
}
|
||||
return Response.ok(users.values()).build();
|
||||
}
|
||||
|
||||
@Path("/user/delete")
|
||||
@POST
|
||||
@UnitOfWork
|
||||
@ApiOperation(value = "Delete a user", notes = "Delete a user, and all his subscriptions")
|
||||
public Response delete(@Auth User user, @ApiParam(required = true) IDRequest req) {
|
||||
Preconditions.checkNotNull(req);
|
||||
Preconditions.checkNotNull(req.getId());
|
||||
|
||||
User u = userDAO.findById(req.getId());
|
||||
if (u == null) {
|
||||
return Response.status(Status.NOT_FOUND).build();
|
||||
}
|
||||
if (CommaFeedApplication.USERNAME_ADMIN.equals(u.getName())) {
|
||||
return Response.status(Status.FORBIDDEN).entity("You cannot delete the admin user.").build();
|
||||
}
|
||||
userService.unregister(u);
|
||||
return Response.ok().build();
|
||||
}
|
||||
|
||||
@Path("/settings")
|
||||
@GET
|
||||
@UnitOfWork
|
||||
@ApiOperation(value = "Retrieve application settings", notes = "Retrieve application settings", response = ApplicationSettings.class)
|
||||
public Response getSettings(@Auth User user) {
|
||||
return Response.ok(config.getApplicationSettings()).build();
|
||||
}
|
||||
|
||||
@Path("/metrics")
|
||||
@GET
|
||||
@UnitOfWork
|
||||
@ApiOperation(value = "Retrieve server metrics")
|
||||
public Response getMetrics(@Auth User user) {
|
||||
return Response.ok(metrics).build();
|
||||
}
|
||||
|
||||
@Path("/cleanup/entries")
|
||||
@GET
|
||||
@UnitOfWork
|
||||
@ApiOperation(value = "Entries cleanup", notes = "Delete entries without subscriptions")
|
||||
public Response cleanupEntries(@Auth User user) {
|
||||
Map<String, Long> map = Maps.newHashMap();
|
||||
map.put("entries_without_subscriptions", cleaner.cleanEntriesWithoutSubscriptions());
|
||||
return Response.ok(map).build();
|
||||
}
|
||||
|
||||
@Path("/cleanup/feeds")
|
||||
@GET
|
||||
@UnitOfWork
|
||||
@ApiOperation(value = "Feeds cleanup", notes = "Delete feeds without subscriptions")
|
||||
public Response cleanupFeeds(@Auth User user) {
|
||||
Map<String, Long> map = Maps.newHashMap();
|
||||
map.put("feeds_without_subscriptions", cleaner.cleanFeedsWithoutSubscriptions());
|
||||
return Response.ok(map).build();
|
||||
}
|
||||
|
||||
@Path("/cleanup/content")
|
||||
@GET
|
||||
@UnitOfWork
|
||||
@ApiOperation(value = "Content cleanup", notes = "Delete contents without entries")
|
||||
public Response cleanupContents(@Auth User user) {
|
||||
Map<String, Long> map = Maps.newHashMap();
|
||||
map.put("contents_without_entries", cleaner.cleanContentsWithoutEntries());
|
||||
return Response.ok(map).build();
|
||||
}
|
||||
|
||||
}
|
||||
471
src/main/java/com/commafeed/frontend/resource/CategoryREST.java
Normal file
471
src/main/java/com/commafeed/frontend/resource/CategoryREST.java
Normal file
@@ -0,0 +1,471 @@
|
||||
package com.commafeed.frontend.resource;
|
||||
|
||||
import io.dropwizard.auth.Auth;
|
||||
import io.dropwizard.hibernate.UnitOfWork;
|
||||
|
||||
import java.io.StringWriter;
|
||||
import java.util.Collections;
|
||||
import java.util.Comparator;
|
||||
import java.util.Date;
|
||||
import java.util.Iterator;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
|
||||
import javax.ws.rs.Consumes;
|
||||
import javax.ws.rs.DefaultValue;
|
||||
import javax.ws.rs.GET;
|
||||
import javax.ws.rs.POST;
|
||||
import javax.ws.rs.Path;
|
||||
import javax.ws.rs.Produces;
|
||||
import javax.ws.rs.QueryParam;
|
||||
import javax.ws.rs.core.MediaType;
|
||||
import javax.ws.rs.core.Response;
|
||||
import javax.ws.rs.core.Response.Status;
|
||||
|
||||
import lombok.AllArgsConstructor;
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
|
||||
import org.apache.commons.collections.CollectionUtils;
|
||||
import org.apache.commons.lang.ObjectUtils;
|
||||
import org.apache.commons.lang.StringUtils;
|
||||
|
||||
import com.commafeed.CommaFeedConfiguration;
|
||||
import com.commafeed.backend.cache.CacheService;
|
||||
import com.commafeed.backend.dao.FeedCategoryDAO;
|
||||
import com.commafeed.backend.dao.FeedEntryStatusDAO;
|
||||
import com.commafeed.backend.dao.FeedSubscriptionDAO;
|
||||
import com.commafeed.backend.feed.FeedUtils;
|
||||
import com.commafeed.backend.model.FeedCategory;
|
||||
import com.commafeed.backend.model.FeedEntryStatus;
|
||||
import com.commafeed.backend.model.FeedSubscription;
|
||||
import com.commafeed.backend.model.User;
|
||||
import com.commafeed.backend.model.UserSettings.ReadingMode;
|
||||
import com.commafeed.backend.model.UserSettings.ReadingOrder;
|
||||
import com.commafeed.backend.service.FeedEntryService;
|
||||
import com.commafeed.backend.service.FeedSubscriptionService;
|
||||
import com.commafeed.frontend.model.Category;
|
||||
import com.commafeed.frontend.model.Entries;
|
||||
import com.commafeed.frontend.model.Entry;
|
||||
import com.commafeed.frontend.model.Subscription;
|
||||
import com.commafeed.frontend.model.UnreadCount;
|
||||
import com.commafeed.frontend.model.request.AddCategoryRequest;
|
||||
import com.commafeed.frontend.model.request.CategoryModificationRequest;
|
||||
import com.commafeed.frontend.model.request.CollapseRequest;
|
||||
import com.commafeed.frontend.model.request.IDRequest;
|
||||
import com.commafeed.frontend.model.request.MarkRequest;
|
||||
import com.google.common.base.Optional;
|
||||
import com.google.common.base.Preconditions;
|
||||
import com.google.common.collect.Lists;
|
||||
import com.sun.syndication.feed.synd.SyndEntry;
|
||||
import com.sun.syndication.feed.synd.SyndFeed;
|
||||
import com.sun.syndication.feed.synd.SyndFeedImpl;
|
||||
import com.sun.syndication.io.SyndFeedOutput;
|
||||
import com.wordnik.swagger.annotations.Api;
|
||||
import com.wordnik.swagger.annotations.ApiOperation;
|
||||
import com.wordnik.swagger.annotations.ApiParam;
|
||||
|
||||
@Path("/category")
|
||||
@Api(value = "/category", description = "Operations about user categories")
|
||||
@Slf4j
|
||||
@Produces(MediaType.APPLICATION_JSON)
|
||||
@Consumes(MediaType.APPLICATION_JSON)
|
||||
@AllArgsConstructor
|
||||
public class CategoryREST {
|
||||
|
||||
public static final String ALL = "all";
|
||||
public static final String STARRED = "starred";
|
||||
|
||||
private final FeedCategoryDAO feedCategoryDAO;
|
||||
private final FeedEntryStatusDAO feedEntryStatusDAO;
|
||||
private final FeedSubscriptionDAO feedSubscriptionDAO;
|
||||
private final FeedEntryService feedEntryService;
|
||||
private final FeedSubscriptionService feedSubscriptionService;
|
||||
private final CacheService cache;
|
||||
private final CommaFeedConfiguration config;
|
||||
|
||||
@Path("/entries")
|
||||
@GET
|
||||
@UnitOfWork
|
||||
@ApiOperation(value = "Get category entries", notes = "Get a list of category entries", response = Entries.class)
|
||||
public Response getCategoryEntries(
|
||||
@Auth User user,
|
||||
@ApiParam(value = "id of the category, 'all' or 'starred'", required = true) @QueryParam("id") String id,
|
||||
@ApiParam(value = "all entries or only unread ones", allowableValues = "all,unread", required = true) @DefaultValue("unread") @QueryParam("readType") ReadingMode readType,
|
||||
@ApiParam(value = "only entries newer than this") @QueryParam("newerThan") Long newerThan,
|
||||
@ApiParam(value = "offset for paging") @DefaultValue("0") @QueryParam("offset") int offset,
|
||||
@ApiParam(value = "limit for paging, default 20, maximum 1000") @DefaultValue("20") @QueryParam("limit") int limit,
|
||||
@ApiParam(value = "date ordering", allowableValues = "asc,desc") @QueryParam("order") @DefaultValue("desc") ReadingOrder order,
|
||||
@ApiParam(
|
||||
value = "search for keywords in either the title or the content of the entries, separated by spaces, 3 characters minimum") @QueryParam("keywords") String keywords,
|
||||
@ApiParam(value = "return only entry ids") @DefaultValue("false") @QueryParam("onlyIds") boolean onlyIds,
|
||||
@ApiParam(value = "comma-separated list of excluded subscription ids") @QueryParam("excludedSubscriptionIds") String excludedSubscriptionIds,
|
||||
@ApiParam(value = "keep only entries tagged with this tag") @QueryParam("tag") String tag) {
|
||||
|
||||
Preconditions.checkNotNull(readType);
|
||||
|
||||
keywords = StringUtils.trimToNull(keywords);
|
||||
Preconditions.checkArgument(keywords == null || StringUtils.length(keywords) >= 3);
|
||||
|
||||
limit = Math.min(limit, 1000);
|
||||
limit = Math.max(0, limit);
|
||||
|
||||
Entries entries = new Entries();
|
||||
entries.setOffset(offset);
|
||||
entries.setLimit(limit);
|
||||
boolean unreadOnly = readType == ReadingMode.unread;
|
||||
if (StringUtils.isBlank(id)) {
|
||||
id = ALL;
|
||||
}
|
||||
|
||||
Date newerThanDate = newerThan == null ? null : new Date(Long.valueOf(newerThan));
|
||||
|
||||
List<Long> excludedIds = null;
|
||||
if (StringUtils.isNotEmpty(excludedSubscriptionIds)) {
|
||||
excludedIds = Lists.newArrayList();
|
||||
for (String excludedId : excludedSubscriptionIds.split(",")) {
|
||||
excludedIds.add(Long.valueOf(excludedId));
|
||||
}
|
||||
}
|
||||
|
||||
if (ALL.equals(id)) {
|
||||
entries.setName(Optional.fromNullable(tag).or("All"));
|
||||
List<FeedSubscription> subs = feedSubscriptionDAO.findAll(user);
|
||||
removeExcludedSubscriptions(subs, excludedIds);
|
||||
List<FeedEntryStatus> list = feedEntryStatusDAO.findBySubscriptions(user, subs, unreadOnly, keywords, newerThanDate, offset,
|
||||
limit + 1, order, true, onlyIds, tag);
|
||||
|
||||
for (FeedEntryStatus status : list) {
|
||||
entries.getEntries().add(
|
||||
Entry.build(status, config.getApplicationSettings().getPublicUrl(), config.getApplicationSettings()
|
||||
.isImageProxyEnabled()));
|
||||
}
|
||||
|
||||
} else if (STARRED.equals(id)) {
|
||||
entries.setName("Starred");
|
||||
List<FeedEntryStatus> starred = feedEntryStatusDAO.findStarred(user, newerThanDate, offset, limit + 1, order, !onlyIds);
|
||||
for (FeedEntryStatus status : starred) {
|
||||
entries.getEntries().add(
|
||||
Entry.build(status, config.getApplicationSettings().getPublicUrl(), config.getApplicationSettings()
|
||||
.isImageProxyEnabled()));
|
||||
}
|
||||
} else {
|
||||
FeedCategory parent = feedCategoryDAO.findById(user, Long.valueOf(id));
|
||||
if (parent != null) {
|
||||
List<FeedCategory> categories = feedCategoryDAO.findAllChildrenCategories(user, parent);
|
||||
List<FeedSubscription> subs = feedSubscriptionDAO.findByCategories(user, categories);
|
||||
removeExcludedSubscriptions(subs, excludedIds);
|
||||
List<FeedEntryStatus> list = feedEntryStatusDAO.findBySubscriptions(user, subs, unreadOnly, keywords, newerThanDate,
|
||||
offset, limit + 1, order, true, onlyIds, tag);
|
||||
|
||||
for (FeedEntryStatus status : list) {
|
||||
entries.getEntries().add(
|
||||
Entry.build(status, config.getApplicationSettings().getPublicUrl(), config.getApplicationSettings()
|
||||
.isImageProxyEnabled()));
|
||||
}
|
||||
entries.setName(parent.getName());
|
||||
} else {
|
||||
return Response.status(Status.NOT_FOUND).entity("<message>category not found</message>").build();
|
||||
}
|
||||
}
|
||||
|
||||
boolean hasMore = entries.getEntries().size() > limit;
|
||||
if (hasMore) {
|
||||
entries.setHasMore(true);
|
||||
entries.getEntries().remove(entries.getEntries().size() - 1);
|
||||
}
|
||||
|
||||
entries.setTimestamp(System.currentTimeMillis());
|
||||
entries.setIgnoredReadStatus(STARRED.equals(id) || keywords != null || tag != null);
|
||||
FeedUtils.removeUnwantedFromSearch(entries.getEntries(), keywords);
|
||||
return Response.ok(entries).build();
|
||||
}
|
||||
|
||||
@Path("/entriesAsFeed")
|
||||
@GET
|
||||
@UnitOfWork
|
||||
@ApiOperation(value = "Get category entries as feed", notes = "Get a feed of category entries")
|
||||
@Produces(MediaType.APPLICATION_XML)
|
||||
public Response getCategoryEntriesAsFeed(
|
||||
@Auth User user,
|
||||
@ApiParam(value = "id of the category, 'all' or 'starred'", required = true) @QueryParam("id") String id,
|
||||
@ApiParam(value = "all entries or only unread ones", allowableValues = "all,unread", required = true) @DefaultValue("all") @QueryParam("readType") ReadingMode readType,
|
||||
@ApiParam(value = "only entries newer than this") @QueryParam("newerThan") Long newerThan,
|
||||
@ApiParam(value = "offset for paging") @DefaultValue("0") @QueryParam("offset") int offset,
|
||||
@ApiParam(value = "limit for paging, default 20, maximum 1000") @DefaultValue("20") @QueryParam("limit") int limit,
|
||||
@ApiParam(value = "date ordering", allowableValues = "asc,desc") @QueryParam("order") @DefaultValue("desc") ReadingOrder order,
|
||||
@ApiParam(
|
||||
value = "search for keywords in either the title or the content of the entries, separated by spaces, 3 characters minimum") @QueryParam("keywords") String keywords,
|
||||
@ApiParam(value = "return only entry ids") @DefaultValue("false") @QueryParam("onlyIds") boolean onlyIds,
|
||||
@ApiParam(value = "comma-separated list of excluded subscription ids") @QueryParam("excludedSubscriptionIds") String excludedSubscriptionIds,
|
||||
@ApiParam(value = "keep only entries tagged with this tag") @QueryParam("tag") String tag) {
|
||||
|
||||
Response response = getCategoryEntries(user, id, readType, newerThan, offset, limit, order, keywords, onlyIds,
|
||||
excludedSubscriptionIds, tag);
|
||||
if (response.getStatus() != Status.OK.getStatusCode()) {
|
||||
return response;
|
||||
}
|
||||
Entries entries = (Entries) response.getEntity();
|
||||
|
||||
SyndFeed feed = new SyndFeedImpl();
|
||||
feed.setFeedType("rss_2.0");
|
||||
feed.setTitle("CommaFeed - " + entries.getName());
|
||||
feed.setDescription("CommaFeed - " + entries.getName());
|
||||
String publicUrl = config.getApplicationSettings().getPublicUrl();
|
||||
feed.setLink(publicUrl);
|
||||
|
||||
List<SyndEntry> children = Lists.newArrayList();
|
||||
for (Entry entry : entries.getEntries()) {
|
||||
children.add(entry.asRss());
|
||||
}
|
||||
feed.setEntries(children);
|
||||
|
||||
SyndFeedOutput output = new SyndFeedOutput();
|
||||
StringWriter writer = new StringWriter();
|
||||
try {
|
||||
output.output(feed, writer);
|
||||
} catch (Exception e) {
|
||||
writer.write("Could not get feed information");
|
||||
log.error(e.getMessage(), e);
|
||||
}
|
||||
return Response.ok(writer.toString()).build();
|
||||
}
|
||||
|
||||
@Path("/mark")
|
||||
@POST
|
||||
@UnitOfWork
|
||||
@ApiOperation(value = "Mark category entries", notes = "Mark feed entries of this category as read")
|
||||
public Response markCategoryEntries(@Auth User user, @ApiParam(value = "category id, or 'all'", required = true) MarkRequest req) {
|
||||
Preconditions.checkNotNull(req);
|
||||
Preconditions.checkNotNull(req.getId());
|
||||
|
||||
Date olderThan = req.getOlderThan() == null ? null : new Date(req.getOlderThan());
|
||||
|
||||
if (ALL.equals(req.getId())) {
|
||||
List<FeedSubscription> subs = feedSubscriptionDAO.findAll(user);
|
||||
removeExcludedSubscriptions(subs, req.getExcludedSubscriptions());
|
||||
feedEntryService.markSubscriptionEntries(user, subs, olderThan);
|
||||
} else if (STARRED.equals(req.getId())) {
|
||||
feedEntryService.markStarredEntries(user, olderThan);
|
||||
} else {
|
||||
FeedCategory parent = feedCategoryDAO.findById(user, Long.valueOf(req.getId()));
|
||||
List<FeedCategory> categories = feedCategoryDAO.findAllChildrenCategories(user, parent);
|
||||
List<FeedSubscription> subs = feedSubscriptionDAO.findByCategories(user, categories);
|
||||
removeExcludedSubscriptions(subs, req.getExcludedSubscriptions());
|
||||
feedEntryService.markSubscriptionEntries(user, subs, olderThan);
|
||||
}
|
||||
return Response.ok().build();
|
||||
}
|
||||
|
||||
private void removeExcludedSubscriptions(List<FeedSubscription> subs, List<Long> excludedIds) {
|
||||
if (CollectionUtils.isNotEmpty(excludedIds)) {
|
||||
Iterator<FeedSubscription> it = subs.iterator();
|
||||
while (it.hasNext()) {
|
||||
FeedSubscription sub = it.next();
|
||||
if (excludedIds.contains(sub.getId())) {
|
||||
it.remove();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Path("/add")
|
||||
@POST
|
||||
@UnitOfWork
|
||||
@ApiOperation(value = "Add a category", notes = "Add a new feed category", response = Long.class)
|
||||
public Response addCategory(@Auth User user, @ApiParam(required = true) AddCategoryRequest req) {
|
||||
Preconditions.checkNotNull(req);
|
||||
Preconditions.checkNotNull(req.getName());
|
||||
|
||||
FeedCategory cat = new FeedCategory();
|
||||
cat.setName(req.getName());
|
||||
cat.setUser(user);
|
||||
cat.setPosition(0);
|
||||
String parentId = req.getParentId();
|
||||
if (parentId != null && !ALL.equals(parentId)) {
|
||||
FeedCategory parent = new FeedCategory();
|
||||
parent.setId(Long.valueOf(parentId));
|
||||
cat.setParent(parent);
|
||||
}
|
||||
feedCategoryDAO.saveOrUpdate(cat);
|
||||
cache.invalidateUserRootCategory(user);
|
||||
return Response.ok(cat.getId()).build();
|
||||
}
|
||||
|
||||
@POST
|
||||
@Path("/delete")
|
||||
@UnitOfWork
|
||||
@ApiOperation(value = "Delete a category", notes = "Delete an existing feed category")
|
||||
public Response deleteCategory(@Auth User user, @ApiParam(required = true) IDRequest req) {
|
||||
|
||||
Preconditions.checkNotNull(req);
|
||||
Preconditions.checkNotNull(req.getId());
|
||||
|
||||
FeedCategory cat = feedCategoryDAO.findById(user, req.getId());
|
||||
if (cat != null) {
|
||||
List<FeedSubscription> subs = feedSubscriptionDAO.findByCategory(user, cat);
|
||||
for (FeedSubscription sub : subs) {
|
||||
sub.setCategory(null);
|
||||
}
|
||||
feedSubscriptionDAO.saveOrUpdate(subs);
|
||||
List<FeedCategory> categories = feedCategoryDAO.findAllChildrenCategories(user, cat);
|
||||
for (FeedCategory child : categories) {
|
||||
if (!child.getId().equals(cat.getId()) && child.getParent().getId().equals(cat.getId())) {
|
||||
child.setParent(null);
|
||||
}
|
||||
}
|
||||
feedCategoryDAO.saveOrUpdate(categories);
|
||||
|
||||
feedCategoryDAO.delete(cat);
|
||||
cache.invalidateUserRootCategory(user);
|
||||
return Response.ok().build();
|
||||
} else {
|
||||
return Response.status(Status.NOT_FOUND).build();
|
||||
}
|
||||
}
|
||||
|
||||
@POST
|
||||
@Path("/modify")
|
||||
@UnitOfWork
|
||||
@ApiOperation(value = "Rename a category", notes = "Rename an existing feed category")
|
||||
public Response modifyCategory(@Auth User user, @ApiParam(required = true) CategoryModificationRequest req) {
|
||||
Preconditions.checkNotNull(req);
|
||||
Preconditions.checkNotNull(req.getId());
|
||||
|
||||
FeedCategory category = feedCategoryDAO.findById(user, req.getId());
|
||||
|
||||
if (StringUtils.isNotBlank(req.getName())) {
|
||||
category.setName(req.getName());
|
||||
}
|
||||
|
||||
FeedCategory parent = null;
|
||||
if (req.getParentId() != null && !CategoryREST.ALL.equals(req.getParentId())
|
||||
&& !StringUtils.equals(req.getParentId(), String.valueOf(req.getId()))) {
|
||||
parent = feedCategoryDAO.findById(user, Long.valueOf(req.getParentId()));
|
||||
}
|
||||
category.setParent(parent);
|
||||
|
||||
if (req.getPosition() != null) {
|
||||
List<FeedCategory> categories = feedCategoryDAO.findByParent(user, parent);
|
||||
Collections.sort(categories, new Comparator<FeedCategory>() {
|
||||
@Override
|
||||
public int compare(FeedCategory o1, FeedCategory o2) {
|
||||
return ObjectUtils.compare(o1.getPosition(), o2.getPosition());
|
||||
}
|
||||
});
|
||||
|
||||
int existingIndex = -1;
|
||||
for (int i = 0; i < categories.size(); i++) {
|
||||
if (ObjectUtils.equals(categories.get(i).getId(), category.getId())) {
|
||||
existingIndex = i;
|
||||
}
|
||||
}
|
||||
if (existingIndex != -1) {
|
||||
categories.remove(existingIndex);
|
||||
}
|
||||
|
||||
categories.add(Math.min(req.getPosition(), categories.size()), category);
|
||||
for (int i = 0; i < categories.size(); i++) {
|
||||
categories.get(i).setPosition(i);
|
||||
}
|
||||
feedCategoryDAO.saveOrUpdate(categories);
|
||||
} else {
|
||||
feedCategoryDAO.saveOrUpdate(category);
|
||||
}
|
||||
|
||||
feedCategoryDAO.saveOrUpdate(category);
|
||||
cache.invalidateUserRootCategory(user);
|
||||
return Response.ok().build();
|
||||
}
|
||||
|
||||
@POST
|
||||
@Path("/collapse")
|
||||
@UnitOfWork
|
||||
@ApiOperation(value = "Collapse a category", notes = "Save collapsed or expanded status for a category")
|
||||
public Response collapse(@Auth User user, @ApiParam(required = true) CollapseRequest req) {
|
||||
Preconditions.checkNotNull(req);
|
||||
Preconditions.checkNotNull(req.getId());
|
||||
|
||||
FeedCategory category = feedCategoryDAO.findById(user, Long.valueOf(req.getId()));
|
||||
if (category == null) {
|
||||
return Response.status(Status.NOT_FOUND).build();
|
||||
}
|
||||
category.setCollapsed(req.isCollapse());
|
||||
feedCategoryDAO.saveOrUpdate(category);
|
||||
cache.invalidateUserRootCategory(user);
|
||||
return Response.ok().build();
|
||||
}
|
||||
|
||||
@GET
|
||||
@Path("/unreadCount")
|
||||
@UnitOfWork
|
||||
@ApiOperation(value = "Get unread count for feed subscriptions", response = UnreadCount.class, responseContainer = "List")
|
||||
public Response getUnreadCount(@Auth User user) {
|
||||
Map<Long, UnreadCount> unreadCount = feedSubscriptionService.getUnreadCount(user);
|
||||
return Response.ok(Lists.newArrayList(unreadCount.values())).build();
|
||||
}
|
||||
|
||||
@GET
|
||||
@Path("/get")
|
||||
@UnitOfWork
|
||||
@ApiOperation(value = "Get feed categories", notes = "Get all categories and subscriptions of the user", response = Category.class)
|
||||
public Response getSubscriptions(@Auth User user) {
|
||||
Category root = cache.getUserRootCategory(user);
|
||||
if (root == null) {
|
||||
log.debug("tree cache miss for {}", user.getId());
|
||||
List<FeedCategory> categories = feedCategoryDAO.findAll(user);
|
||||
List<FeedSubscription> subscriptions = feedSubscriptionDAO.findAll(user);
|
||||
Map<Long, UnreadCount> unreadCount = feedSubscriptionService.getUnreadCount(user);
|
||||
|
||||
root = buildCategory(null, categories, subscriptions, unreadCount);
|
||||
root.setId("all");
|
||||
root.setName("All");
|
||||
cache.setUserRootCategory(user, root);
|
||||
}
|
||||
|
||||
return Response.ok(root).build();
|
||||
}
|
||||
|
||||
private Category buildCategory(Long id, List<FeedCategory> categories, List<FeedSubscription> subscriptions,
|
||||
Map<Long, UnreadCount> unreadCount) {
|
||||
Category category = new Category();
|
||||
category.setId(String.valueOf(id));
|
||||
category.setExpanded(true);
|
||||
|
||||
for (FeedCategory c : categories) {
|
||||
if ((id == null && c.getParent() == null) || (c.getParent() != null && ObjectUtils.equals(c.getParent().getId(), id))) {
|
||||
Category child = buildCategory(c.getId(), categories, subscriptions, unreadCount);
|
||||
child.setId(String.valueOf(c.getId()));
|
||||
child.setName(c.getName());
|
||||
child.setPosition(c.getPosition());
|
||||
if (c.getParent() != null && c.getParent().getId() != null) {
|
||||
child.setParentId(String.valueOf(c.getParent().getId()));
|
||||
}
|
||||
child.setExpanded(!c.isCollapsed());
|
||||
category.getChildren().add(child);
|
||||
}
|
||||
}
|
||||
Collections.sort(category.getChildren(), new Comparator<Category>() {
|
||||
@Override
|
||||
public int compare(Category o1, Category o2) {
|
||||
return ObjectUtils.compare(o1.getPosition(), o2.getPosition());
|
||||
}
|
||||
});
|
||||
|
||||
for (FeedSubscription subscription : subscriptions) {
|
||||
if ((id == null && subscription.getCategory() == null)
|
||||
|| (subscription.getCategory() != null && ObjectUtils.equals(subscription.getCategory().getId(), id))) {
|
||||
UnreadCount uc = unreadCount.get(subscription.getId());
|
||||
Subscription sub = Subscription.build(subscription, config.getApplicationSettings().getPublicUrl(), uc);
|
||||
category.getFeeds().add(sub);
|
||||
}
|
||||
}
|
||||
Collections.sort(category.getFeeds(), new Comparator<Subscription>() {
|
||||
@Override
|
||||
public int compare(Subscription o1, Subscription o2) {
|
||||
return ObjectUtils.compare(o1.getPosition(), o2.getPosition());
|
||||
}
|
||||
});
|
||||
return category;
|
||||
}
|
||||
|
||||
}
|
||||
105
src/main/java/com/commafeed/frontend/resource/EntryREST.java
Normal file
105
src/main/java/com/commafeed/frontend/resource/EntryREST.java
Normal file
@@ -0,0 +1,105 @@
|
||||
package com.commafeed.frontend.resource;
|
||||
|
||||
import io.dropwizard.auth.Auth;
|
||||
import io.dropwizard.hibernate.UnitOfWork;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
import javax.ws.rs.Consumes;
|
||||
import javax.ws.rs.GET;
|
||||
import javax.ws.rs.POST;
|
||||
import javax.ws.rs.Path;
|
||||
import javax.ws.rs.Produces;
|
||||
import javax.ws.rs.core.MediaType;
|
||||
import javax.ws.rs.core.Response;
|
||||
|
||||
import lombok.AllArgsConstructor;
|
||||
|
||||
import com.commafeed.backend.dao.FeedEntryTagDAO;
|
||||
import com.commafeed.backend.model.User;
|
||||
import com.commafeed.backend.service.FeedEntryService;
|
||||
import com.commafeed.backend.service.FeedEntryTagService;
|
||||
import com.commafeed.frontend.model.request.MarkRequest;
|
||||
import com.commafeed.frontend.model.request.MultipleMarkRequest;
|
||||
import com.commafeed.frontend.model.request.StarRequest;
|
||||
import com.commafeed.frontend.model.request.TagRequest;
|
||||
import com.google.common.base.Preconditions;
|
||||
import com.wordnik.swagger.annotations.Api;
|
||||
import com.wordnik.swagger.annotations.ApiOperation;
|
||||
import com.wordnik.swagger.annotations.ApiParam;
|
||||
|
||||
@Path("/entry")
|
||||
@Api(value = "/entry", description = "Operations about feed entries")
|
||||
@Produces(MediaType.APPLICATION_JSON)
|
||||
@Consumes(MediaType.APPLICATION_JSON)
|
||||
@AllArgsConstructor
|
||||
public class EntryREST {
|
||||
|
||||
private final FeedEntryTagDAO feedEntryTagDAO;
|
||||
private final FeedEntryService feedEntryService;
|
||||
private final FeedEntryTagService feedEntryTagService;
|
||||
|
||||
@Path("/mark")
|
||||
@POST
|
||||
@UnitOfWork
|
||||
@ApiOperation(value = "Mark a feed entry", notes = "Mark a feed entry as read/unread")
|
||||
public Response markFeedEntry(@Auth User user, @ApiParam(value = "Mark Request", required = true) MarkRequest req) {
|
||||
Preconditions.checkNotNull(req);
|
||||
Preconditions.checkNotNull(req.getId());
|
||||
|
||||
feedEntryService.markEntry(user, Long.valueOf(req.getId()), req.isRead());
|
||||
return Response.ok().build();
|
||||
}
|
||||
|
||||
@Path("/markMultiple")
|
||||
@POST
|
||||
@UnitOfWork
|
||||
@ApiOperation(value = "Mark multiple feed entries", notes = "Mark feed entries as read/unread")
|
||||
public Response markFeedEntries(@Auth User user, @ApiParam(value = "Multiple Mark Request", required = true) MultipleMarkRequest req) {
|
||||
Preconditions.checkNotNull(req);
|
||||
Preconditions.checkNotNull(req.getRequests());
|
||||
|
||||
for (MarkRequest r : req.getRequests()) {
|
||||
markFeedEntry(user, r);
|
||||
}
|
||||
|
||||
return Response.ok().build();
|
||||
}
|
||||
|
||||
@Path("/star")
|
||||
@POST
|
||||
@UnitOfWork
|
||||
@ApiOperation(value = "Mark a feed entry", notes = "Mark a feed entry as read/unread")
|
||||
public Response starFeedEntry(@Auth User user, @ApiParam(value = "Star Request", required = true) StarRequest req) {
|
||||
Preconditions.checkNotNull(req);
|
||||
Preconditions.checkNotNull(req.getId());
|
||||
Preconditions.checkNotNull(req.getFeedId());
|
||||
|
||||
feedEntryService.starEntry(user, Long.valueOf(req.getId()), req.getFeedId(), req.isStarred());
|
||||
|
||||
return Response.ok().build();
|
||||
}
|
||||
|
||||
@Path("/tags")
|
||||
@GET
|
||||
@UnitOfWork
|
||||
@ApiOperation(value = "Get list of tags for the user", notes = "Get list of tags for the user")
|
||||
public Response getTags(@Auth User user) {
|
||||
List<String> tags = feedEntryTagDAO.findByUser(user);
|
||||
return Response.ok(tags).build();
|
||||
}
|
||||
|
||||
@Path("/tag")
|
||||
@POST
|
||||
@UnitOfWork
|
||||
@ApiOperation(value = "Mark a feed entry", notes = "Mark a feed entry as read/unread")
|
||||
public Response tagFeedEntry(@Auth User user, @ApiParam(value = "Tag Request", required = true) TagRequest req) {
|
||||
Preconditions.checkNotNull(req);
|
||||
Preconditions.checkNotNull(req.getEntryId());
|
||||
|
||||
feedEntryTagService.updateTags(user, req.getEntryId(), req.getTags());
|
||||
|
||||
return Response.ok().build();
|
||||
}
|
||||
|
||||
}
|
||||
510
src/main/java/com/commafeed/frontend/resource/FeedREST.java
Normal file
510
src/main/java/com/commafeed/frontend/resource/FeedREST.java
Normal file
@@ -0,0 +1,510 @@
|
||||
package com.commafeed.frontend.resource;
|
||||
|
||||
import io.dropwizard.auth.Auth;
|
||||
import io.dropwizard.hibernate.UnitOfWork;
|
||||
|
||||
import java.io.InputStream;
|
||||
import java.io.StringWriter;
|
||||
import java.net.URI;
|
||||
import java.util.Arrays;
|
||||
import java.util.Calendar;
|
||||
import java.util.Collections;
|
||||
import java.util.Comparator;
|
||||
import java.util.Date;
|
||||
import java.util.List;
|
||||
|
||||
import javax.ws.rs.Consumes;
|
||||
import javax.ws.rs.DefaultValue;
|
||||
import javax.ws.rs.GET;
|
||||
import javax.ws.rs.POST;
|
||||
import javax.ws.rs.Path;
|
||||
import javax.ws.rs.PathParam;
|
||||
import javax.ws.rs.Produces;
|
||||
import javax.ws.rs.QueryParam;
|
||||
import javax.ws.rs.WebApplicationException;
|
||||
import javax.ws.rs.core.CacheControl;
|
||||
import javax.ws.rs.core.MediaType;
|
||||
import javax.ws.rs.core.Response;
|
||||
import javax.ws.rs.core.Response.ResponseBuilder;
|
||||
import javax.ws.rs.core.Response.Status;
|
||||
|
||||
import lombok.AllArgsConstructor;
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
|
||||
import org.apache.commons.io.IOUtils;
|
||||
import org.apache.commons.lang.ObjectUtils;
|
||||
import org.apache.commons.lang.StringUtils;
|
||||
|
||||
import com.commafeed.CommaFeedApplication;
|
||||
import com.commafeed.CommaFeedConfiguration;
|
||||
import com.commafeed.backend.cache.CacheService;
|
||||
import com.commafeed.backend.dao.FeedCategoryDAO;
|
||||
import com.commafeed.backend.dao.FeedEntryStatusDAO;
|
||||
import com.commafeed.backend.dao.FeedSubscriptionDAO;
|
||||
import com.commafeed.backend.feed.FaviconFetcher;
|
||||
import com.commafeed.backend.feed.FeedFetcher;
|
||||
import com.commafeed.backend.feed.FeedQueues;
|
||||
import com.commafeed.backend.feed.FeedUtils;
|
||||
import com.commafeed.backend.feed.FetchedFeed;
|
||||
import com.commafeed.backend.model.Feed;
|
||||
import com.commafeed.backend.model.FeedCategory;
|
||||
import com.commafeed.backend.model.FeedEntryStatus;
|
||||
import com.commafeed.backend.model.FeedSubscription;
|
||||
import com.commafeed.backend.model.User;
|
||||
import com.commafeed.backend.model.UserSettings.ReadingMode;
|
||||
import com.commafeed.backend.model.UserSettings.ReadingOrder;
|
||||
import com.commafeed.backend.opml.OPMLExporter;
|
||||
import com.commafeed.backend.opml.OPMLImporter;
|
||||
import com.commafeed.backend.service.FeedEntryService;
|
||||
import com.commafeed.backend.service.FeedSubscriptionService;
|
||||
import com.commafeed.frontend.model.Entries;
|
||||
import com.commafeed.frontend.model.Entry;
|
||||
import com.commafeed.frontend.model.FeedInfo;
|
||||
import com.commafeed.frontend.model.Subscription;
|
||||
import com.commafeed.frontend.model.UnreadCount;
|
||||
import com.commafeed.frontend.model.request.FeedInfoRequest;
|
||||
import com.commafeed.frontend.model.request.FeedModificationRequest;
|
||||
import com.commafeed.frontend.model.request.IDRequest;
|
||||
import com.commafeed.frontend.model.request.MarkRequest;
|
||||
import com.commafeed.frontend.model.request.SubscribeRequest;
|
||||
import com.google.common.base.Preconditions;
|
||||
import com.google.common.base.Throwables;
|
||||
import com.google.common.collect.Lists;
|
||||
import com.sun.jersey.multipart.FormDataParam;
|
||||
import com.sun.syndication.feed.opml.Opml;
|
||||
import com.sun.syndication.feed.synd.SyndEntry;
|
||||
import com.sun.syndication.feed.synd.SyndFeed;
|
||||
import com.sun.syndication.feed.synd.SyndFeedImpl;
|
||||
import com.sun.syndication.io.SyndFeedOutput;
|
||||
import com.sun.syndication.io.WireFeedOutput;
|
||||
import com.wordnik.swagger.annotations.Api;
|
||||
import com.wordnik.swagger.annotations.ApiOperation;
|
||||
import com.wordnik.swagger.annotations.ApiParam;
|
||||
|
||||
@Path("/feed")
|
||||
@Api(value = "/feed", description = "Operations about feeds")
|
||||
@Slf4j
|
||||
@Produces(MediaType.APPLICATION_JSON)
|
||||
@Consumes(MediaType.APPLICATION_JSON)
|
||||
@AllArgsConstructor
|
||||
public class FeedREST {
|
||||
|
||||
private final FeedSubscriptionDAO feedSubscriptionDAO;
|
||||
private final FeedCategoryDAO feedCategoryDAO;
|
||||
private final FeedEntryStatusDAO feedEntryStatusDAO;
|
||||
private final FaviconFetcher faviconFetcher;
|
||||
private final FeedFetcher feedFetcher;
|
||||
private final FeedEntryService feedEntryService;
|
||||
private final FeedSubscriptionService feedSubscriptionService;
|
||||
private final FeedQueues queues;
|
||||
private final OPMLImporter opmlImporter;
|
||||
private final OPMLExporter opmlExporter;
|
||||
private final CacheService cache;
|
||||
private final CommaFeedConfiguration config;
|
||||
|
||||
@Path("/entries")
|
||||
@GET
|
||||
@UnitOfWork
|
||||
@ApiOperation(value = "Get feed entries", notes = "Get a list of feed entries", response = Entries.class)
|
||||
public Response getFeedEntries(
|
||||
@Auth User user,
|
||||
@ApiParam(value = "id of the feed", required = true) @QueryParam("id") String id,
|
||||
@ApiParam(value = "all entries or only unread ones", allowableValues = "all,unread", required = true) @DefaultValue("unread") @QueryParam("readType") ReadingMode readType,
|
||||
@ApiParam(value = "only entries newer than this") @QueryParam("newerThan") Long newerThan,
|
||||
@ApiParam(value = "offset for paging") @DefaultValue("0") @QueryParam("offset") int offset,
|
||||
@ApiParam(value = "limit for paging, default 20, maximum 1000") @DefaultValue("20") @QueryParam("limit") int limit,
|
||||
@ApiParam(value = "date ordering", allowableValues = "asc,desc") @QueryParam("order") @DefaultValue("desc") ReadingOrder order,
|
||||
@ApiParam(
|
||||
value = "search for keywords in either the title or the content of the entries, separated by spaces, 3 characters minimum") @QueryParam("keywords") String keywords,
|
||||
@ApiParam(value = "return only entry ids") @DefaultValue("false") @QueryParam("onlyIds") boolean onlyIds) {
|
||||
|
||||
Preconditions.checkNotNull(id);
|
||||
Preconditions.checkNotNull(readType);
|
||||
|
||||
keywords = StringUtils.trimToNull(keywords);
|
||||
Preconditions.checkArgument(keywords == null || StringUtils.length(keywords) >= 3);
|
||||
|
||||
limit = Math.min(limit, 1000);
|
||||
limit = Math.max(0, limit);
|
||||
|
||||
Entries entries = new Entries();
|
||||
entries.setOffset(offset);
|
||||
entries.setLimit(limit);
|
||||
|
||||
boolean unreadOnly = readType == ReadingMode.unread;
|
||||
|
||||
Date newerThanDate = newerThan == null ? null : new Date(Long.valueOf(newerThan));
|
||||
|
||||
FeedSubscription subscription = feedSubscriptionDAO.findById(user, Long.valueOf(id));
|
||||
if (subscription != null) {
|
||||
entries.setName(subscription.getTitle());
|
||||
entries.setMessage(subscription.getFeed().getMessage());
|
||||
entries.setErrorCount(subscription.getFeed().getErrorCount());
|
||||
entries.setFeedLink(subscription.getFeed().getLink());
|
||||
|
||||
List<FeedEntryStatus> list = feedEntryStatusDAO.findBySubscriptions(user, Arrays.asList(subscription), unreadOnly, keywords,
|
||||
newerThanDate, offset, limit + 1, order, true, onlyIds, null);
|
||||
|
||||
for (FeedEntryStatus status : list) {
|
||||
entries.getEntries().add(
|
||||
Entry.build(status, config.getApplicationSettings().getPublicUrl(), config.getApplicationSettings()
|
||||
.isImageProxyEnabled()));
|
||||
}
|
||||
|
||||
boolean hasMore = entries.getEntries().size() > limit;
|
||||
if (hasMore) {
|
||||
entries.setHasMore(true);
|
||||
entries.getEntries().remove(entries.getEntries().size() - 1);
|
||||
}
|
||||
} else {
|
||||
return Response.status(Status.NOT_FOUND).entity("<message>feed not found</message>").build();
|
||||
}
|
||||
|
||||
entries.setTimestamp(System.currentTimeMillis());
|
||||
entries.setIgnoredReadStatus(keywords != null);
|
||||
FeedUtils.removeUnwantedFromSearch(entries.getEntries(), keywords);
|
||||
return Response.ok(entries).build();
|
||||
}
|
||||
|
||||
@Path("/entriesAsFeed")
|
||||
@GET
|
||||
@UnitOfWork
|
||||
@ApiOperation(value = "Get feed entries as a feed", notes = "Get a feed of feed entries")
|
||||
@Produces(MediaType.APPLICATION_XML)
|
||||
public Response getFeedEntriesAsFeed(
|
||||
@Auth User user,
|
||||
@ApiParam(value = "id of the feed", required = true) @QueryParam("id") String id,
|
||||
@ApiParam(value = "all entries or only unread ones", allowableValues = "all,unread", required = true) @DefaultValue("all") @QueryParam("readType") ReadingMode readType,
|
||||
@ApiParam(value = "only entries newer than this") @QueryParam("newerThan") Long newerThan,
|
||||
@ApiParam(value = "offset for paging") @DefaultValue("0") @QueryParam("offset") int offset,
|
||||
@ApiParam(value = "limit for paging, default 20, maximum 1000") @DefaultValue("20") @QueryParam("limit") int limit,
|
||||
@ApiParam(value = "date ordering", allowableValues = "asc,desc") @QueryParam("order") @DefaultValue("desc") ReadingOrder order,
|
||||
@ApiParam(
|
||||
value = "search for keywords in either the title or the content of the entries, separated by spaces, 3 characters minimum") @QueryParam("keywords") String keywords,
|
||||
@ApiParam(value = "return only entry ids") @DefaultValue("false") @QueryParam("onlyIds") boolean onlyIds) {
|
||||
|
||||
Response response = getFeedEntries(user, id, readType, newerThan, offset, limit, order, keywords, onlyIds);
|
||||
if (response.getStatus() != Status.OK.getStatusCode()) {
|
||||
return response;
|
||||
}
|
||||
Entries entries = (Entries) response.getEntity();
|
||||
|
||||
SyndFeed feed = new SyndFeedImpl();
|
||||
feed.setFeedType("rss_2.0");
|
||||
feed.setTitle("CommaFeed - " + entries.getName());
|
||||
feed.setDescription("CommaFeed - " + entries.getName());
|
||||
String publicUrl = config.getApplicationSettings().getPublicUrl();
|
||||
feed.setLink(publicUrl);
|
||||
|
||||
List<SyndEntry> children = Lists.newArrayList();
|
||||
for (Entry entry : entries.getEntries()) {
|
||||
children.add(entry.asRss());
|
||||
}
|
||||
feed.setEntries(children);
|
||||
|
||||
SyndFeedOutput output = new SyndFeedOutput();
|
||||
StringWriter writer = new StringWriter();
|
||||
try {
|
||||
output.output(feed, writer);
|
||||
} catch (Exception e) {
|
||||
writer.write("Could not get feed information");
|
||||
log.error(e.getMessage(), e);
|
||||
}
|
||||
return Response.ok(writer.toString()).build();
|
||||
}
|
||||
|
||||
private FeedInfo fetchFeedInternal(String url) {
|
||||
FeedInfo info = null;
|
||||
url = StringUtils.trimToEmpty(url);
|
||||
url = prependHttp(url);
|
||||
try {
|
||||
FetchedFeed feed = feedFetcher.fetch(url, true, null, null, null, null);
|
||||
info = new FeedInfo();
|
||||
info.setUrl(feed.getUrlAfterRedirect());
|
||||
info.setTitle(feed.getTitle());
|
||||
|
||||
} catch (Exception e) {
|
||||
log.debug(e.getMessage(), e);
|
||||
throw new WebApplicationException(e, Response.status(Status.INTERNAL_SERVER_ERROR).entity(e.getMessage()).build());
|
||||
}
|
||||
return info;
|
||||
}
|
||||
|
||||
@POST
|
||||
@Path("/fetch")
|
||||
@UnitOfWork
|
||||
@ApiOperation(value = "Fetch a feed", notes = "Fetch a feed by its url", response = FeedInfo.class)
|
||||
public Response fetchFeed(@Auth User user, @ApiParam(value = "feed url", required = true) FeedInfoRequest req) {
|
||||
Preconditions.checkNotNull(req);
|
||||
Preconditions.checkNotNull(req.getUrl());
|
||||
|
||||
FeedInfo info = null;
|
||||
try {
|
||||
info = fetchFeedInternal(req.getUrl());
|
||||
} catch (Exception e) {
|
||||
return Response.status(Status.INTERNAL_SERVER_ERROR).entity(Throwables.getStackTraceAsString(Throwables.getRootCause(e)))
|
||||
.build();
|
||||
}
|
||||
return Response.ok(info).build();
|
||||
}
|
||||
|
||||
@Path("/refreshAll")
|
||||
@GET
|
||||
@UnitOfWork
|
||||
@ApiOperation(value = "Queue all feeds of the user for refresh", notes = "Manually add all feeds of the user to the refresh queue")
|
||||
public Response queueAllForRefresh(@Auth User user) {
|
||||
feedSubscriptionService.refreshAll(user);
|
||||
return Response.ok().build();
|
||||
}
|
||||
|
||||
@Path("/refresh")
|
||||
@POST
|
||||
@UnitOfWork
|
||||
@ApiOperation(value = "Queue a feed for refresh", notes = "Manually add a feed to the refresh queue")
|
||||
public Response queueForRefresh(@Auth User user, @ApiParam(value = "Feed id") IDRequest req) {
|
||||
|
||||
Preconditions.checkNotNull(req);
|
||||
Preconditions.checkNotNull(req.getId());
|
||||
|
||||
FeedSubscription sub = feedSubscriptionDAO.findById(user, req.getId());
|
||||
if (sub != null) {
|
||||
Feed feed = sub.getFeed();
|
||||
queues.add(feed, true);
|
||||
return Response.ok().build();
|
||||
}
|
||||
return Response.ok(Status.NOT_FOUND).build();
|
||||
}
|
||||
|
||||
@Path("/mark")
|
||||
@POST
|
||||
@UnitOfWork
|
||||
@ApiOperation(value = "Mark feed entries", notes = "Mark feed entries as read (unread is not supported)")
|
||||
public Response markFeedEntries(@Auth User user, @ApiParam(value = "Mark request") MarkRequest req) {
|
||||
Preconditions.checkNotNull(req);
|
||||
Preconditions.checkNotNull(req.getId());
|
||||
|
||||
Date olderThan = req.getOlderThan() == null ? null : new Date(req.getOlderThan());
|
||||
|
||||
FeedSubscription subscription = feedSubscriptionDAO.findById(user, Long.valueOf(req.getId()));
|
||||
if (subscription != null) {
|
||||
feedEntryService.markSubscriptionEntries(user, Arrays.asList(subscription), olderThan);
|
||||
}
|
||||
return Response.ok().build();
|
||||
}
|
||||
|
||||
@GET
|
||||
@Path("/get/{id}")
|
||||
@UnitOfWork
|
||||
@ApiOperation(value = "", notes = "")
|
||||
public Response get(@Auth User user, @ApiParam(value = "user id", required = true) @PathParam("id") Long id) {
|
||||
|
||||
Preconditions.checkNotNull(id);
|
||||
FeedSubscription sub = feedSubscriptionDAO.findById(user, id);
|
||||
if (sub == null) {
|
||||
return Response.status(Status.NOT_FOUND).build();
|
||||
}
|
||||
UnreadCount unreadCount = feedSubscriptionService.getUnreadCount(user).get(id);
|
||||
return Response.ok(Subscription.build(sub, config.getApplicationSettings().getPublicUrl(), unreadCount)).build();
|
||||
}
|
||||
|
||||
@GET
|
||||
@Path("/favicon/{id}")
|
||||
@UnitOfWork
|
||||
@ApiOperation(value = "Fetch a feed's icon", notes = "Fetch a feed's icon")
|
||||
public Response getFavicon(@Auth User user, @ApiParam(value = "subscription id") @PathParam("id") Long id) {
|
||||
|
||||
Preconditions.checkNotNull(id);
|
||||
FeedSubscription subscription = feedSubscriptionDAO.findById(user, id);
|
||||
if (subscription == null) {
|
||||
return Response.status(Status.NOT_FOUND).build();
|
||||
}
|
||||
Feed feed = subscription.getFeed();
|
||||
String url = feed.getLink() != null ? feed.getLink() : feed.getUrl();
|
||||
byte[] icon = faviconFetcher.fetch(url);
|
||||
|
||||
ResponseBuilder builder = null;
|
||||
if (icon == null) {
|
||||
String baseUrl = FeedUtils.removeTrailingSlash(config.getApplicationSettings().getPublicUrl());
|
||||
builder = Response.status(Status.MOVED_PERMANENTLY).location(URI.create(baseUrl + "/images/default_favicon.gif"));
|
||||
} else {
|
||||
builder = Response.ok(icon, "image/x-icon");
|
||||
}
|
||||
|
||||
CacheControl cacheControl = new CacheControl();
|
||||
cacheControl.setMaxAge(2592000);
|
||||
cacheControl.setPrivate(true);
|
||||
// trying to replicate "public, max-age=2592000"
|
||||
builder.cacheControl(cacheControl);
|
||||
|
||||
Calendar calendar = Calendar.getInstance();
|
||||
calendar.add(Calendar.MONTH, 1);
|
||||
builder.expires(calendar.getTime());
|
||||
builder.lastModified(CommaFeedApplication.STARTUP_TIME);
|
||||
|
||||
return builder.build();
|
||||
}
|
||||
|
||||
@POST
|
||||
@Path("/subscribe")
|
||||
@UnitOfWork
|
||||
@ApiOperation(value = "Subscribe to a feed", notes = "Subscribe to a feed")
|
||||
public Response subscribe(@Auth User user, @ApiParam(value = "subscription request", required = true) SubscribeRequest req) {
|
||||
Preconditions.checkNotNull(req);
|
||||
Preconditions.checkNotNull(req.getTitle());
|
||||
Preconditions.checkNotNull(req.getUrl());
|
||||
|
||||
String url = prependHttp(req.getUrl());
|
||||
try {
|
||||
url = fetchFeedInternal(url).getUrl();
|
||||
|
||||
FeedCategory category = null;
|
||||
if (req.getCategoryId() != null && !CategoryREST.ALL.equals(req.getCategoryId())) {
|
||||
category = feedCategoryDAO.findById(Long.valueOf(req.getCategoryId()));
|
||||
}
|
||||
FeedInfo info = fetchFeedInternal(url);
|
||||
feedSubscriptionService.subscribe(user, info.getUrl(), req.getTitle(), category);
|
||||
} catch (Exception e) {
|
||||
log.error("Failed to subscribe to URL {}: {}", url, e.getMessage(), e);
|
||||
return Response.status(Status.SERVICE_UNAVAILABLE).entity("Failed to subscribe to URL " + url + ": " + e.getMessage()).build();
|
||||
}
|
||||
return Response.ok().build();
|
||||
}
|
||||
|
||||
@GET
|
||||
@Path("/subscribe")
|
||||
@UnitOfWork
|
||||
@ApiOperation(value = "Subscribe to a feed", notes = "Subscribe to a feed")
|
||||
public Response subscribe(@Auth User user, @ApiParam(value = "feed url", required = true) @QueryParam("url") String url) {
|
||||
|
||||
try {
|
||||
Preconditions.checkNotNull(url);
|
||||
|
||||
url = prependHttp(url);
|
||||
url = fetchFeedInternal(url).getUrl();
|
||||
|
||||
FeedInfo info = fetchFeedInternal(url);
|
||||
feedSubscriptionService.subscribe(user, info.getUrl(), info.getTitle(), null);
|
||||
} catch (Exception e) {
|
||||
log.info("Could not subscribe to url {} : {}", url, e.getMessage());
|
||||
}
|
||||
return Response.temporaryRedirect(URI.create(config.getApplicationSettings().getPublicUrl())).build();
|
||||
}
|
||||
|
||||
private String prependHttp(String url) {
|
||||
if (!url.startsWith("http")) {
|
||||
url = "http://" + url;
|
||||
}
|
||||
return url;
|
||||
}
|
||||
|
||||
@POST
|
||||
@Path("/unsubscribe")
|
||||
@UnitOfWork
|
||||
@ApiOperation(value = "Unsubscribe from a feed", notes = "Unsubscribe from a feed")
|
||||
public Response unsubscribe(@Auth User user, @ApiParam(required = true) IDRequest req) {
|
||||
Preconditions.checkNotNull(req);
|
||||
Preconditions.checkNotNull(req.getId());
|
||||
|
||||
boolean deleted = feedSubscriptionService.unsubscribe(user, req.getId());
|
||||
if (deleted) {
|
||||
return Response.ok().build();
|
||||
} else {
|
||||
return Response.status(Status.NOT_FOUND).build();
|
||||
}
|
||||
}
|
||||
|
||||
@POST
|
||||
@Path("/modify")
|
||||
@UnitOfWork
|
||||
@ApiOperation(value = "Modify a subscription", notes = "Modify a feed subscription")
|
||||
public Response modify(@Auth User user, @ApiParam(value = "subscription id", required = true) FeedModificationRequest req) {
|
||||
Preconditions.checkNotNull(req);
|
||||
Preconditions.checkNotNull(req.getId());
|
||||
|
||||
FeedSubscription subscription = feedSubscriptionDAO.findById(user, req.getId());
|
||||
|
||||
if (StringUtils.isNotBlank(req.getName())) {
|
||||
subscription.setTitle(req.getName());
|
||||
}
|
||||
|
||||
FeedCategory parent = null;
|
||||
if (req.getCategoryId() != null && !CategoryREST.ALL.equals(req.getCategoryId())) {
|
||||
parent = feedCategoryDAO.findById(user, Long.valueOf(req.getCategoryId()));
|
||||
}
|
||||
subscription.setCategory(parent);
|
||||
|
||||
if (req.getPosition() != null) {
|
||||
List<FeedSubscription> subs = feedSubscriptionDAO.findByCategory(user, parent);
|
||||
Collections.sort(subs, new Comparator<FeedSubscription>() {
|
||||
@Override
|
||||
public int compare(FeedSubscription o1, FeedSubscription o2) {
|
||||
return ObjectUtils.compare(o1.getPosition(), o2.getPosition());
|
||||
}
|
||||
});
|
||||
|
||||
int existingIndex = -1;
|
||||
for (int i = 0; i < subs.size(); i++) {
|
||||
if (ObjectUtils.equals(subs.get(i).getId(), subscription.getId())) {
|
||||
existingIndex = i;
|
||||
}
|
||||
}
|
||||
if (existingIndex != -1) {
|
||||
subs.remove(existingIndex);
|
||||
}
|
||||
|
||||
subs.add(Math.min(req.getPosition(), subs.size()), subscription);
|
||||
for (int i = 0; i < subs.size(); i++) {
|
||||
subs.get(i).setPosition(i);
|
||||
}
|
||||
feedSubscriptionDAO.saveOrUpdate(subs);
|
||||
} else {
|
||||
feedSubscriptionDAO.saveOrUpdate(subscription);
|
||||
}
|
||||
cache.invalidateUserRootCategory(user);
|
||||
return Response.ok().build();
|
||||
}
|
||||
|
||||
@POST
|
||||
@Path("/import")
|
||||
@UnitOfWork
|
||||
@Consumes(MediaType.MULTIPART_FORM_DATA)
|
||||
@ApiOperation(value = "OPML import", notes = "Import an OPML file, posted as a FORM with the 'file' name")
|
||||
public Response importOpml(@Auth User user, @FormDataParam("file") InputStream input) {
|
||||
|
||||
String publicUrl = config.getApplicationSettings().getPublicUrl();
|
||||
if (StringUtils.isBlank(publicUrl)) {
|
||||
throw new WebApplicationException(Response.status(Status.INTERNAL_SERVER_ERROR)
|
||||
.entity("Set the public URL in the admin section.").build());
|
||||
}
|
||||
|
||||
if (CommaFeedApplication.USERNAME_DEMO.equals(user.getName())) {
|
||||
return Response.status(Status.FORBIDDEN).entity("Import is disabled for the demo account").build();
|
||||
}
|
||||
try {
|
||||
String opml = IOUtils.toString(input, "UTF-8");
|
||||
opmlImporter.importOpml(user, opml);
|
||||
} catch (Exception e) {
|
||||
log.error(e.getMessage(), e);
|
||||
throw new WebApplicationException(Response.status(Status.INTERNAL_SERVER_ERROR).entity(e.getMessage()).build());
|
||||
}
|
||||
return Response.seeOther(URI.create(config.getApplicationSettings().getPublicUrl())).build();
|
||||
}
|
||||
|
||||
@GET
|
||||
@Path("/export")
|
||||
@UnitOfWork
|
||||
@Produces(MediaType.APPLICATION_XML)
|
||||
@ApiOperation(value = "OPML export", notes = "Export an OPML file of the user's subscriptions")
|
||||
public Response exportOpml(@Auth User user) {
|
||||
Opml opml = opmlExporter.export(user);
|
||||
WireFeedOutput output = new WireFeedOutput();
|
||||
String opmlString = null;
|
||||
try {
|
||||
opmlString = output.outputString(opml);
|
||||
} catch (Exception e) {
|
||||
return Response.status(Status.INTERNAL_SERVER_ERROR).entity(e).build();
|
||||
}
|
||||
return Response.ok(opmlString).build();
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,121 @@
|
||||
package com.commafeed.frontend.resource;
|
||||
|
||||
import io.dropwizard.hibernate.UnitOfWork;
|
||||
|
||||
import java.util.Date;
|
||||
import java.util.List;
|
||||
|
||||
import javax.servlet.http.HttpServletRequest;
|
||||
import javax.ws.rs.Consumes;
|
||||
import javax.ws.rs.GET;
|
||||
import javax.ws.rs.POST;
|
||||
import javax.ws.rs.Path;
|
||||
import javax.ws.rs.Produces;
|
||||
import javax.ws.rs.QueryParam;
|
||||
import javax.ws.rs.core.Context;
|
||||
import javax.ws.rs.core.MediaType;
|
||||
import javax.ws.rs.core.Response;
|
||||
import javax.ws.rs.core.Response.Status;
|
||||
|
||||
import lombok.RequiredArgsConstructor;
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
|
||||
import org.apache.commons.io.IOUtils;
|
||||
import org.apache.commons.lang.ArrayUtils;
|
||||
import org.apache.commons.lang.StringUtils;
|
||||
|
||||
import com.codahale.metrics.MetricRegistry;
|
||||
import com.commafeed.CommaFeedConfiguration;
|
||||
import com.commafeed.backend.dao.FeedDAO;
|
||||
import com.commafeed.backend.feed.FeedParser;
|
||||
import com.commafeed.backend.feed.FeedQueues;
|
||||
import com.commafeed.backend.feed.FetchedFeed;
|
||||
import com.commafeed.backend.model.Feed;
|
||||
import com.google.common.base.Preconditions;
|
||||
|
||||
@Path("/push")
|
||||
@Slf4j
|
||||
@RequiredArgsConstructor
|
||||
public class PubSubHubbubCallbackREST {
|
||||
|
||||
@Context
|
||||
private HttpServletRequest request;
|
||||
|
||||
private final FeedDAO feedDAO;
|
||||
private final FeedParser parser;
|
||||
private final FeedQueues queues;
|
||||
private final CommaFeedConfiguration config;
|
||||
private final MetricRegistry metricRegistry;
|
||||
|
||||
@Path("/callback")
|
||||
@GET
|
||||
@UnitOfWork
|
||||
@Produces(MediaType.TEXT_PLAIN)
|
||||
public Response verify(@QueryParam("hub.mode") String mode, @QueryParam("hub.topic") String topic,
|
||||
@QueryParam("hub.challenge") String challenge, @QueryParam("hub.lease_seconds") String leaseSeconds,
|
||||
@QueryParam("hub.verify_token") String verifyToken) {
|
||||
if (!config.getApplicationSettings().isPubsubhubbub()) {
|
||||
return Response.status(Status.FORBIDDEN).entity("pubsubhubbub is disabled").build();
|
||||
}
|
||||
|
||||
Preconditions.checkArgument(StringUtils.isNotEmpty(topic));
|
||||
Preconditions.checkArgument("subscribe".equals(mode));
|
||||
|
||||
log.debug("confirmation callback received for {}", topic);
|
||||
|
||||
List<Feed> feeds = feedDAO.findByTopic(topic);
|
||||
|
||||
if (feeds.isEmpty() == false) {
|
||||
for (Feed feed : feeds) {
|
||||
log.debug("activated push notifications for {}", feed.getPushTopic());
|
||||
feed.setPushLastPing(new Date());
|
||||
}
|
||||
feedDAO.saveOrUpdate(feeds);
|
||||
return Response.ok(challenge).build();
|
||||
} else {
|
||||
log.debug("rejecting callback: no push info for {}", topic);
|
||||
return Response.status(Status.NOT_FOUND).build();
|
||||
}
|
||||
}
|
||||
|
||||
@Path("/callback")
|
||||
@POST
|
||||
@UnitOfWork
|
||||
@Consumes({ MediaType.APPLICATION_ATOM_XML, "application/rss+xml" })
|
||||
public Response callback() {
|
||||
|
||||
if (!config.getApplicationSettings().isPubsubhubbub()) {
|
||||
return Response.status(Status.FORBIDDEN).entity("pubsubhubbub is disabled").build();
|
||||
}
|
||||
|
||||
try {
|
||||
byte[] bytes = IOUtils.toByteArray(request.getInputStream());
|
||||
|
||||
if (ArrayUtils.isEmpty(bytes)) {
|
||||
return Response.status(Status.BAD_REQUEST).entity("empty body received").build();
|
||||
}
|
||||
|
||||
FetchedFeed fetchedFeed = parser.parse(null, bytes);
|
||||
String topic = fetchedFeed.getFeed().getPushTopic();
|
||||
if (StringUtils.isBlank(topic)) {
|
||||
return Response.status(Status.BAD_REQUEST).entity("empty topic received").build();
|
||||
}
|
||||
|
||||
log.debug("content callback received for {}", topic);
|
||||
List<Feed> feeds = feedDAO.findByTopic(topic);
|
||||
if (feeds.isEmpty()) {
|
||||
return Response.status(Status.BAD_REQUEST).entity("no feeds found for that topic").build();
|
||||
}
|
||||
|
||||
for (Feed feed : feeds) {
|
||||
log.debug("pushing content to queue for {}", feed.getUrl());
|
||||
queues.add(feed, false);
|
||||
}
|
||||
metricRegistry.meter(MetricRegistry.name(getClass(), "pushReceived")).mark();
|
||||
|
||||
} catch (Exception e) {
|
||||
log.error("Could not parse pubsub callback: " + e.getMessage());
|
||||
}
|
||||
return Response.ok().build();
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,68 @@
|
||||
package com.commafeed.frontend.resource;
|
||||
|
||||
import io.dropwizard.auth.Auth;
|
||||
import io.dropwizard.hibernate.UnitOfWork;
|
||||
|
||||
import javax.ws.rs.Consumes;
|
||||
import javax.ws.rs.GET;
|
||||
import javax.ws.rs.Path;
|
||||
import javax.ws.rs.Produces;
|
||||
import javax.ws.rs.QueryParam;
|
||||
import javax.ws.rs.core.MediaType;
|
||||
import javax.ws.rs.core.Response;
|
||||
import javax.ws.rs.core.Response.Status;
|
||||
|
||||
import lombok.AllArgsConstructor;
|
||||
|
||||
import com.commafeed.CommaFeedConfiguration;
|
||||
import com.commafeed.backend.HttpGetter;
|
||||
import com.commafeed.backend.HttpGetter.HttpResult;
|
||||
import com.commafeed.backend.feed.FeedUtils;
|
||||
import com.commafeed.backend.model.User;
|
||||
import com.commafeed.backend.service.ApplicationPropertiesService;
|
||||
import com.commafeed.frontend.model.ServerInfo;
|
||||
import com.wordnik.swagger.annotations.Api;
|
||||
import com.wordnik.swagger.annotations.ApiOperation;
|
||||
|
||||
@Path("/server")
|
||||
@Api(value = "/server", description = "Operations about server infos")
|
||||
@Produces(MediaType.APPLICATION_JSON)
|
||||
@Consumes(MediaType.APPLICATION_JSON)
|
||||
@AllArgsConstructor
|
||||
public class ServerREST {
|
||||
|
||||
private final HttpGetter httpGetter;
|
||||
private final CommaFeedConfiguration config;
|
||||
private final ApplicationPropertiesService applicationPropertiesService;
|
||||
|
||||
@Path("/get")
|
||||
@GET
|
||||
@UnitOfWork
|
||||
@ApiOperation(value = "Get server infos", notes = "Get server infos", response = ServerInfo.class)
|
||||
public Response get(@Auth User user) {
|
||||
ServerInfo infos = new ServerInfo();
|
||||
infos.setAnnouncement(config.getApplicationSettings().getAnnouncement());
|
||||
infos.setVersion(applicationPropertiesService.getVersion());
|
||||
infos.setGitCommit(applicationPropertiesService.getGitCommit());
|
||||
return Response.ok(infos).build();
|
||||
}
|
||||
|
||||
@Path("/proxy")
|
||||
@GET
|
||||
@UnitOfWork
|
||||
@ApiOperation(value = "proxy image")
|
||||
@Produces("image/png")
|
||||
public Response get(@Auth User user, @QueryParam("u") String url) {
|
||||
if (!config.getApplicationSettings().isImageProxyEnabled()) {
|
||||
return Response.status(Status.FORBIDDEN).build();
|
||||
}
|
||||
|
||||
url = FeedUtils.imageProxyDecoder(url);
|
||||
try {
|
||||
HttpResult result = httpGetter.getBinary(url, 20000);
|
||||
return Response.ok(result.getContent()).build();
|
||||
} catch (Exception e) {
|
||||
return Response.status(Status.SERVICE_UNAVAILABLE).entity(e.getMessage()).build();
|
||||
}
|
||||
}
|
||||
}
|
||||
220
src/main/java/com/commafeed/frontend/resource/UserREST.java
Normal file
220
src/main/java/com/commafeed/frontend/resource/UserREST.java
Normal file
@@ -0,0 +1,220 @@
|
||||
package com.commafeed.frontend.resource;
|
||||
|
||||
import io.dropwizard.auth.Auth;
|
||||
import io.dropwizard.hibernate.UnitOfWork;
|
||||
|
||||
import java.util.Arrays;
|
||||
|
||||
import javax.ws.rs.Consumes;
|
||||
import javax.ws.rs.GET;
|
||||
import javax.ws.rs.POST;
|
||||
import javax.ws.rs.Path;
|
||||
import javax.ws.rs.Produces;
|
||||
import javax.ws.rs.core.MediaType;
|
||||
import javax.ws.rs.core.Response;
|
||||
import javax.ws.rs.core.Response.Status;
|
||||
|
||||
import lombok.AllArgsConstructor;
|
||||
|
||||
import org.apache.commons.lang.StringUtils;
|
||||
|
||||
import com.commafeed.CommaFeedApplication;
|
||||
import com.commafeed.backend.dao.UserDAO;
|
||||
import com.commafeed.backend.dao.UserRoleDAO;
|
||||
import com.commafeed.backend.dao.UserSettingsDAO;
|
||||
import com.commafeed.backend.model.User;
|
||||
import com.commafeed.backend.model.UserRole;
|
||||
import com.commafeed.backend.model.UserRole.Role;
|
||||
import com.commafeed.backend.model.UserSettings;
|
||||
import com.commafeed.backend.model.UserSettings.ReadingMode;
|
||||
import com.commafeed.backend.model.UserSettings.ReadingOrder;
|
||||
import com.commafeed.backend.model.UserSettings.ViewMode;
|
||||
import com.commafeed.backend.service.PasswordEncryptionService;
|
||||
import com.commafeed.backend.service.UserService;
|
||||
import com.commafeed.frontend.model.Settings;
|
||||
import com.commafeed.frontend.model.UserModel;
|
||||
import com.commafeed.frontend.model.request.ProfileModificationRequest;
|
||||
import com.commafeed.frontend.model.request.RegistrationRequest;
|
||||
import com.google.common.base.Preconditions;
|
||||
import com.wordnik.swagger.annotations.Api;
|
||||
import com.wordnik.swagger.annotations.ApiOperation;
|
||||
import com.wordnik.swagger.annotations.ApiParam;
|
||||
|
||||
@Path("/user")
|
||||
@Api(value = "/user", description = "Operations about the user")
|
||||
@Produces(MediaType.APPLICATION_JSON)
|
||||
@Consumes(MediaType.APPLICATION_JSON)
|
||||
@AllArgsConstructor
|
||||
public class UserREST {
|
||||
|
||||
private final UserDAO userDAO;
|
||||
private final UserRoleDAO userRoleDAO;
|
||||
private final UserSettingsDAO userSettingsDAO;
|
||||
private final UserService userService;
|
||||
private final PasswordEncryptionService encryptionService;
|
||||
|
||||
@Path("/settings")
|
||||
@GET
|
||||
@UnitOfWork
|
||||
@ApiOperation(value = "Retrieve user settings", notes = "Retrieve user settings", response = Settings.class)
|
||||
public Response getSettings(@Auth User user) {
|
||||
Settings s = new Settings();
|
||||
UserSettings settings = userSettingsDAO.findByUser(user);
|
||||
if (settings != null) {
|
||||
s.setReadingMode(settings.getReadingMode().name());
|
||||
s.setReadingOrder(settings.getReadingOrder().name());
|
||||
s.setViewMode(settings.getViewMode().name());
|
||||
s.setShowRead(settings.isShowRead());
|
||||
|
||||
s.setEmail(settings.isEmail());
|
||||
s.setGmail(settings.isGmail());
|
||||
s.setFacebook(settings.isFacebook());
|
||||
s.setTwitter(settings.isTwitter());
|
||||
s.setGoogleplus(settings.isGoogleplus());
|
||||
s.setTumblr(settings.isTumblr());
|
||||
s.setPocket(settings.isPocket());
|
||||
s.setInstapaper(settings.isInstapaper());
|
||||
s.setBuffer(settings.isBuffer());
|
||||
s.setReadability(settings.isReadability());
|
||||
|
||||
s.setScrollMarks(settings.isScrollMarks());
|
||||
s.setTheme(settings.getTheme());
|
||||
s.setCustomCss(settings.getCustomCss());
|
||||
s.setLanguage(settings.getLanguage());
|
||||
s.setScrollSpeed(settings.getScrollSpeed());
|
||||
} else {
|
||||
s.setReadingMode(ReadingMode.unread.name());
|
||||
s.setReadingOrder(ReadingOrder.desc.name());
|
||||
s.setViewMode(ViewMode.title.name());
|
||||
s.setShowRead(true);
|
||||
s.setTheme("default");
|
||||
|
||||
s.setEmail(true);
|
||||
s.setGmail(true);
|
||||
s.setFacebook(true);
|
||||
s.setTwitter(true);
|
||||
s.setGoogleplus(true);
|
||||
s.setTumblr(true);
|
||||
s.setPocket(true);
|
||||
s.setInstapaper(true);
|
||||
s.setBuffer(true);
|
||||
s.setReadability(true);
|
||||
|
||||
s.setScrollMarks(true);
|
||||
s.setLanguage("en");
|
||||
s.setScrollSpeed(400);
|
||||
}
|
||||
return Response.ok(s).build();
|
||||
}
|
||||
|
||||
@Path("/settings")
|
||||
@POST
|
||||
@UnitOfWork
|
||||
@ApiOperation(value = "Save user settings", notes = "Save user settings")
|
||||
public Response saveSettings(@Auth User user, @ApiParam(required = true) Settings settings) {
|
||||
Preconditions.checkNotNull(settings);
|
||||
|
||||
UserSettings s = userSettingsDAO.findByUser(user);
|
||||
if (s == null) {
|
||||
s = new UserSettings();
|
||||
s.setUser(user);
|
||||
}
|
||||
s.setReadingMode(ReadingMode.valueOf(settings.getReadingMode()));
|
||||
s.setReadingOrder(ReadingOrder.valueOf(settings.getReadingOrder()));
|
||||
s.setShowRead(settings.isShowRead());
|
||||
s.setViewMode(ViewMode.valueOf(settings.getViewMode()));
|
||||
s.setScrollMarks(settings.isScrollMarks());
|
||||
s.setTheme(settings.getTheme());
|
||||
s.setCustomCss(settings.getCustomCss());
|
||||
s.setLanguage(settings.getLanguage());
|
||||
s.setScrollSpeed(settings.getScrollSpeed());
|
||||
|
||||
s.setEmail(settings.isEmail());
|
||||
s.setGmail(settings.isGmail());
|
||||
s.setFacebook(settings.isFacebook());
|
||||
s.setTwitter(settings.isTwitter());
|
||||
s.setGoogleplus(settings.isGoogleplus());
|
||||
s.setTumblr(settings.isTumblr());
|
||||
s.setPocket(settings.isPocket());
|
||||
s.setInstapaper(settings.isInstapaper());
|
||||
s.setBuffer(settings.isBuffer());
|
||||
s.setReadability(settings.isReadability());
|
||||
|
||||
userSettingsDAO.saveOrUpdate(s);
|
||||
return Response.ok().build();
|
||||
|
||||
}
|
||||
|
||||
@Path("/profile")
|
||||
@GET
|
||||
@UnitOfWork
|
||||
@ApiOperation(value = "Retrieve user's profile", response = UserModel.class)
|
||||
public Response get(@Auth User user) {
|
||||
UserModel userModel = new UserModel();
|
||||
userModel.setId(user.getId());
|
||||
userModel.setName(user.getName());
|
||||
userModel.setEmail(user.getEmail());
|
||||
userModel.setEnabled(!user.isDisabled());
|
||||
userModel.setApiKey(user.getApiKey());
|
||||
for (UserRole role : userRoleDAO.findAll(user)) {
|
||||
if (role.getRole() == Role.ADMIN) {
|
||||
userModel.setAdmin(true);
|
||||
}
|
||||
}
|
||||
return Response.ok(userModel).build();
|
||||
}
|
||||
|
||||
@Path("/profile")
|
||||
@POST
|
||||
@UnitOfWork
|
||||
@ApiOperation(value = "Save user's profile")
|
||||
public Response save(@Auth User user, @ApiParam(required = true) ProfileModificationRequest request) {
|
||||
Preconditions.checkArgument(StringUtils.isBlank(request.getPassword()) || request.getPassword().length() >= 6);
|
||||
if (StringUtils.isNotBlank(request.getEmail())) {
|
||||
User u = userDAO.findByEmail(request.getEmail());
|
||||
Preconditions.checkArgument(u == null || user.getId().equals(u.getId()));
|
||||
}
|
||||
|
||||
if (CommaFeedApplication.USERNAME_DEMO.equals(user.getName())) {
|
||||
return Response.status(Status.FORBIDDEN).build();
|
||||
}
|
||||
|
||||
user.setEmail(StringUtils.trimToNull(request.getEmail()));
|
||||
if (StringUtils.isNotBlank(request.getPassword())) {
|
||||
byte[] password = encryptionService.getEncryptedPassword(request.getPassword(), user.getSalt());
|
||||
user.setPassword(password);
|
||||
user.setApiKey(userService.generateApiKey(user));
|
||||
}
|
||||
if (request.isNewApiKey()) {
|
||||
user.setApiKey(userService.generateApiKey(user));
|
||||
}
|
||||
userDAO.merge(user);
|
||||
return Response.ok().build();
|
||||
}
|
||||
|
||||
@Path("/register")
|
||||
@POST
|
||||
@UnitOfWork
|
||||
@ApiOperation(value = "Register a new account")
|
||||
public Response register(@ApiParam(required = true) RegistrationRequest req) {
|
||||
try {
|
||||
userService.register(req.getName(), req.getPassword(), req.getEmail(), Arrays.asList(Role.USER));
|
||||
return Response.ok().build();
|
||||
} catch (Exception e) {
|
||||
return Response.status(Status.INTERNAL_SERVER_ERROR).entity(e.getMessage()).build();
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@Path("/profile/deleteAccount")
|
||||
@POST
|
||||
@UnitOfWork
|
||||
@ApiOperation(value = "Delete the user account")
|
||||
public Response delete(@Auth User user) {
|
||||
if (CommaFeedApplication.USERNAME_ADMIN.equals(user.getName()) || CommaFeedApplication.USERNAME_DEMO.equals(user.getName())) {
|
||||
return Response.status(Status.FORBIDDEN).build();
|
||||
}
|
||||
userService.unregister(user);
|
||||
return Response.ok().build();
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user