2014-08-08 16:49:02 +02:00
package com.commafeed.frontend.resource ;
2013-04-18 12:50:44 +02:00
2014-08-08 16:49:02 +02:00
import io.dropwizard.hibernate.UnitOfWork ;
import java.io.InputStream ;
2013-04-30 07:12:45 +02:00
import java.io.StringWriter ;
2013-05-19 10:03:26 +02:00
import java.net.URI ;
2013-07-19 11:17:19 +02:00
import java.util.Arrays ;
2013-04-18 12:50:44 +02:00
import java.util.Calendar ;
2013-06-04 07:26:32 +02:00
import java.util.Collections ;
import java.util.Comparator ;
2013-04-18 12:50:44 +02:00
import java.util.Date ;
import java.util.List ;
2014-10-28 16:36:09 +01:00
import java.util.Objects ;
2013-04-18 12:50:44 +02:00
2014-08-17 14:16:30 +02:00
import javax.inject.Inject ;
import javax.inject.Singleton ;
2013-04-18 12:50:44 +02:00
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 ;
2013-04-30 16:17:34 +02:00
import javax.ws.rs.PathParam ;
2013-04-28 09:19:27 +02:00
import javax.ws.rs.Produces ;
2013-04-18 12:50:44 +02:00
import javax.ws.rs.QueryParam ;
import javax.ws.rs.WebApplicationException ;
2013-06-11 00:05:09 +04:30
import javax.ws.rs.core.CacheControl ;
2013-04-18 12:50:44 +02:00
import javax.ws.rs.core.MediaType ;
import javax.ws.rs.core.Response ;
2013-06-11 00:05:09 +04:30
import javax.ws.rs.core.Response.ResponseBuilder ;
2013-04-18 12:50:44 +02:00
import javax.ws.rs.core.Response.Status ;
2014-08-17 14:16:30 +02:00
import lombok.RequiredArgsConstructor ;
2013-08-11 11:45:32 +02:00
import lombok.extern.slf4j.Slf4j ;
2013-04-18 12:50:44 +02:00
import org.apache.commons.io.IOUtils ;
2014-10-28 16:36:09 +01:00
import org.apache.commons.lang3.ObjectUtils ;
import org.apache.commons.lang3.StringUtils ;
2014-10-28 16:31:49 +01:00
import org.glassfish.jersey.media.multipart.FormDataParam ;
2013-04-18 12:50:44 +02:00
2014-08-08 16:49:02 +02:00
import com.commafeed.CommaFeedApplication ;
import com.commafeed.CommaFeedConfiguration ;
2013-07-02 18:07:08 +02:00
import com.commafeed.backend.cache.CacheService ;
2013-07-02 16:10:59 +02:00
import com.commafeed.backend.dao.FeedCategoryDAO ;
import com.commafeed.backend.dao.FeedEntryStatusDAO ;
import com.commafeed.backend.dao.FeedSubscriptionDAO ;
2014-11-07 10:53:49 +01:00
import com.commafeed.backend.feed.FeedEntryKeyword ;
2014-08-08 16:49:02 +02:00
import com.commafeed.backend.feed.FeedFetcher ;
import com.commafeed.backend.feed.FeedQueues ;
import com.commafeed.backend.feed.FeedUtils ;
import com.commafeed.backend.feed.FetchedFeed ;
2013-06-11 13:01:12 +02:00
import com.commafeed.backend.model.Feed ;
2013-04-18 12:50:44 +02:00
import com.commafeed.backend.model.FeedCategory ;
2014-11-04 16:01:37 +01:00
import com.commafeed.backend.model.FeedEntry ;
import com.commafeed.backend.model.FeedEntryContent ;
2013-04-18 12:50:44 +02:00
import com.commafeed.backend.model.FeedEntryStatus ;
import com.commafeed.backend.model.FeedSubscription ;
2014-08-08 16:49:02 +02:00
import com.commafeed.backend.model.User ;
2013-08-01 17:11:57 +02:00
import com.commafeed.backend.model.UserSettings.ReadingMode ;
2013-04-18 12:50:44 +02:00
import com.commafeed.backend.model.UserSettings.ReadingOrder ;
2013-08-18 16:29:07 +02:00
import com.commafeed.backend.opml.OPMLExporter ;
import com.commafeed.backend.opml.OPMLImporter ;
2014-11-10 09:49:59 +01:00
import com.commafeed.backend.service.FeedEntryFilteringService ;
import com.commafeed.backend.service.FeedEntryFilteringService.FeedEntryFilterException ;
2014-08-08 16:49:02 +02:00
import com.commafeed.backend.service.FeedEntryService ;
2014-10-26 12:25:44 +01:00
import com.commafeed.backend.service.FeedService ;
2014-08-08 16:49:02 +02:00
import com.commafeed.backend.service.FeedSubscriptionService ;
2014-08-08 21:57:16 +02:00
import com.commafeed.frontend.auth.SecurityCheck ;
2013-04-18 12:50:44 +02:00
import com.commafeed.frontend.model.Entries ;
import com.commafeed.frontend.model.Entry ;
2013-04-25 13:14:56 +02:00
import com.commafeed.frontend.model.FeedInfo ;
2013-04-30 16:17:34 +02:00
import com.commafeed.frontend.model.Subscription ;
2013-08-08 17:00:35 +02:00
import com.commafeed.frontend.model.UnreadCount ;
2013-07-11 09:08:39 +02:00
import com.commafeed.frontend.model.request.FeedInfoRequest ;
2013-05-01 21:56:59 +02:00
import com.commafeed.frontend.model.request.FeedModificationRequest ;
2013-04-25 13:14:56 +02:00
import com.commafeed.frontend.model.request.IDRequest ;
2013-04-18 12:50:44 +02:00
import com.commafeed.frontend.model.request.MarkRequest ;
import com.commafeed.frontend.model.request.SubscribeRequest ;
import com.google.common.base.Preconditions ;
2013-10-23 07:55:14 +02:00
import com.google.common.base.Throwables ;
2013-04-30 07:12:45 +02:00
import com.google.common.collect.Lists ;
2014-08-15 13:51:13 +02:00
import com.rometools.opml.feed.opml.Opml ;
import com.rometools.rome.feed.synd.SyndEntry ;
import com.rometools.rome.feed.synd.SyndFeed ;
import com.rometools.rome.feed.synd.SyndFeedImpl ;
import com.rometools.rome.io.SyndFeedOutput ;
import com.rometools.rome.io.WireFeedOutput ;
2013-04-18 12:50:44 +02:00
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 " )
2013-08-11 11:45:32 +02:00
@Slf4j
2014-08-08 16:49:02 +02:00
@Produces ( MediaType . APPLICATION_JSON )
@Consumes ( MediaType . APPLICATION_JSON )
2014-08-17 14:16:30 +02:00
@RequiredArgsConstructor ( onConstructor = @__ ( { @Inject } ) )
@Singleton
2014-08-08 16:49:02 +02:00
public class FeedREST {
2014-11-04 16:01:37 +01:00
private static final FeedEntry TEST_ENTRY = initTestEntry ( ) ;
private static FeedEntry initTestEntry ( ) {
FeedEntry entry = new FeedEntry ( ) ;
entry . setUrl ( " https://github.com/Athou/commafeed " ) ;
FeedEntryContent content = new FeedEntryContent ( ) ;
content . setAuthor ( " Athou " ) ;
content . setTitle ( " Merge pull request #662 from Athou/dw8 " ) ;
content . setContent ( " Merge pull request #662 from Athou/dw8 " ) ;
entry . setContent ( content ) ;
return entry ;
}
2014-08-08 16:49:02 +02:00
private final FeedSubscriptionDAO feedSubscriptionDAO ;
private final FeedCategoryDAO feedCategoryDAO ;
private final FeedEntryStatusDAO feedEntryStatusDAO ;
private final FeedFetcher feedFetcher ;
2014-10-26 12:25:44 +01:00
private final FeedService feedService ;
2014-08-08 16:49:02 +02:00
private final FeedEntryService feedEntryService ;
private final FeedSubscriptionService feedSubscriptionService ;
2014-11-10 09:49:59 +01:00
private final FeedEntryFilteringService feedEntryFilteringService ;
2014-08-08 16:49:02 +02:00
private final FeedQueues queues ;
private final OPMLImporter opmlImporter ;
private final OPMLExporter opmlExporter ;
private final CacheService cache ;
private final CommaFeedConfiguration config ;
2013-07-02 16:10:59 +02:00
2013-04-18 12:50:44 +02:00
@Path ( " /entries " )
@GET
2014-08-08 16:49:02 +02:00
@UnitOfWork
@ApiOperation ( value = " Get feed entries " , notes = " Get a list of feed entries " , response = Entries . class )
2013-08-11 11:50:30 +02:00
public Response getFeedEntries (
2014-08-08 21:57:16 +02:00
@SecurityCheck User user ,
2013-08-11 11:50:30 +02:00
@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 ,
2013-08-09 12:53:21 +02:00
@ApiParam ( value = " return only entry ids " ) @DefaultValue ( " false " ) @QueryParam ( " onlyIds " ) boolean onlyIds ) {
2013-04-18 12:50:44 +02:00
Preconditions . checkNotNull ( id ) ;
Preconditions . checkNotNull ( readType ) ;
2013-08-08 17:00:35 +02:00
2013-08-07 15:26:43 +02:00
keywords = StringUtils . trimToNull ( keywords ) ;
Preconditions . checkArgument ( keywords = = null | | StringUtils . length ( keywords ) > = 3 ) ;
2014-11-07 10:53:49 +01:00
List < FeedEntryKeyword > entryKeywords = FeedEntryKeyword . fromQueryString ( keywords ) ;
2013-05-29 11:40:30 +02:00
2013-08-09 12:18:35 +02:00
limit = Math . min ( limit , 1000 ) ;
2013-05-29 11:40:30 +02:00
limit = Math . max ( 0 , limit ) ;
2013-04-18 12:50:44 +02:00
Entries entries = new Entries ( ) ;
2013-08-01 11:17:38 +02:00
entries . setOffset ( offset ) ;
entries . setLimit ( limit ) ;
2013-08-01 17:11:57 +02:00
boolean unreadOnly = readType = = ReadingMode . unread ;
2013-04-18 12:50:44 +02:00
2014-08-14 09:40:40 +02:00
Date newerThanDate = newerThan = = null ? null : new Date ( newerThan ) ;
2013-06-08 14:09:46 +02:00
2014-08-08 16:49:02 +02:00
FeedSubscription subscription = feedSubscriptionDAO . findById ( user , Long . valueOf ( id ) ) ;
2013-04-18 12:50:44 +02:00
if ( subscription ! = null ) {
entries . setName ( subscription . getTitle ( ) ) ;
entries . setMessage ( subscription . getFeed ( ) . getMessage ( ) ) ;
entries . setErrorCount ( subscription . getFeed ( ) . getErrorCount ( ) ) ;
2013-07-01 21:25:09 +01:00
entries . setFeedLink ( subscription . getFeed ( ) . getLink ( ) ) ;
2013-04-18 12:50:44 +02:00
2014-11-07 10:53:49 +01:00
List < FeedEntryStatus > list = feedEntryStatusDAO . findBySubscriptions ( user , Arrays . asList ( subscription ) , unreadOnly ,
entryKeywords , newerThanDate , offset , limit + 1 , order , true , onlyIds , null ) ;
2013-06-20 18:45:58 +02:00
for ( FeedEntryStatus status : list ) {
2013-05-26 15:36:55 +02:00
entries . getEntries ( ) . add (
2014-08-08 16:49:02 +02:00
Entry . build ( status , config . getApplicationSettings ( ) . getPublicUrl ( ) , config . getApplicationSettings ( )
2014-12-04 10:52:41 +01:00
. getImageProxyEnabled ( ) ) ) ;
2013-04-18 12:50:44 +02:00
}
2013-06-08 14:09:46 +02:00
boolean hasMore = entries . getEntries ( ) . size ( ) > limit ;
if ( hasMore ) {
entries . setHasMore ( true ) ;
entries . getEntries ( ) . remove ( entries . getEntries ( ) . size ( ) - 1 ) ;
}
2013-08-30 15:52:31 +02:00
} else {
return Response . status ( Status . NOT_FOUND ) . entity ( " <message>feed not found</message> " ) . build ( ) ;
2013-04-18 12:50:44 +02:00
}
2013-06-25 16:57:48 +02:00
entries . setTimestamp ( System . currentTimeMillis ( ) ) ;
2014-03-06 15:46:19 +01:00
entries . setIgnoredReadStatus ( keywords ! = null ) ;
2014-11-07 10:53:49 +01:00
FeedUtils . removeUnwantedFromSearch ( entries . getEntries ( ) , entryKeywords ) ;
2013-05-17 19:39:52 +02:00
return Response . ok ( entries ) . build ( ) ;
2013-04-18 12:50:44 +02:00
}
2013-04-30 07:12:45 +02:00
@Path ( " /entriesAsFeed " )
@GET
2014-08-08 16:49:02 +02:00
@UnitOfWork
2013-04-30 07:12:45 +02:00
@ApiOperation ( value = " Get feed entries as a feed " , notes = " Get a feed of feed entries " )
@Produces ( MediaType . APPLICATION_XML )
2013-10-24 09:49:39 +02:00
public Response getFeedEntriesAsFeed (
2014-08-08 21:57:16 +02:00
@SecurityCheck ( apiKeyAllowed = true ) User user ,
2013-10-24 09:49:39 +02:00
@ApiParam ( value = " id of the feed " , required = true ) @QueryParam ( " id " ) String id ,
2013-10-29 07:05:19 +01:00
@ApiParam ( value = " all entries or only unread ones " , allowableValues = " all,unread " , required = true ) @DefaultValue ( " all " ) @QueryParam ( " readType " ) ReadingMode readType ,
2013-10-24 09:49:39 +02:00
@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 ) {
2013-04-30 07:12:45 +02:00
2014-08-08 16:49:02 +02:00
Response response = getFeedEntries ( user , id , readType , newerThan , offset , limit , order , keywords , onlyIds ) ;
2013-08-30 15:52:31 +02:00
if ( response . getStatus ( ) ! = Status . OK . getStatusCode ( ) ) {
return response ;
}
Entries entries = ( Entries ) response . getEntity ( ) ;
2013-04-30 07:12:45 +02:00
SyndFeed feed = new SyndFeedImpl ( ) ;
feed . setFeedType ( " rss_2.0 " ) ;
feed . setTitle ( " CommaFeed - " + entries . getName ( ) ) ;
feed . setDescription ( " CommaFeed - " + entries . getName ( ) ) ;
2014-08-08 16:49:02 +02:00
String publicUrl = config . getApplicationSettings ( ) . getPublicUrl ( ) ;
2013-04-30 07:12:45 +02:00
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 ) ;
}
2013-05-17 19:39:52 +02:00
return Response . ok ( writer . toString ( ) ) . build ( ) ;
2013-04-30 07:12:45 +02:00
}
2013-05-21 12:57:32 +02:00
private FeedInfo fetchFeedInternal ( String url ) {
2013-04-25 13:14:56 +02:00
FeedInfo info = null ;
2013-04-18 12:50:44 +02:00
url = StringUtils . trimToEmpty ( url ) ;
url = prependHttp ( url ) ;
try {
2013-07-25 09:17:33 +02:00
FetchedFeed feed = feedFetcher . fetch ( url , true , null , null , null , null ) ;
2013-04-25 13:14:56 +02:00
info = new FeedInfo ( ) ;
2013-08-22 15:35:34 +02:00
info . setUrl ( feed . getUrlAfterRedirect ( ) ) ;
2013-04-25 13:14:56 +02:00
info . setTitle ( feed . getTitle ( ) ) ;
2013-04-18 12:50:44 +02:00
} catch ( Exception e ) {
2013-08-06 13:07:50 +02:00
log . debug ( e . getMessage ( ) , e ) ;
2013-07-25 09:17:33 +02:00
throw new WebApplicationException ( e , Response . status ( Status . INTERNAL_SERVER_ERROR ) . entity ( e . getMessage ( ) ) . build ( ) ) ;
2013-04-18 12:50:44 +02:00
}
2013-05-21 12:57:32 +02:00
return info ;
}
2013-07-11 09:08:39 +02:00
@POST
2013-05-21 12:57:32 +02:00
@Path ( " /fetch " )
2014-08-08 16:49:02 +02:00
@UnitOfWork
@ApiOperation ( value = " Fetch a feed " , notes = " Fetch a feed by its url " , response = FeedInfo . class )
2014-08-08 21:57:16 +02:00
public Response fetchFeed ( @SecurityCheck User user , @ApiParam ( value = " feed url " , required = true ) FeedInfoRequest req ) {
2013-07-11 09:08:39 +02:00
Preconditions . checkNotNull ( req ) ;
Preconditions . checkNotNull ( req . getUrl ( ) ) ;
2013-05-21 12:57:32 +02:00
FeedInfo info = null ;
try {
2013-07-11 09:08:39 +02:00
info = fetchFeedInternal ( req . getUrl ( ) ) ;
2013-05-21 12:57:32 +02:00
} catch ( Exception e ) {
2014-02-14 15:41:17 +01:00
return Response . status ( Status . INTERNAL_SERVER_ERROR ) . entity ( Throwables . getStackTraceAsString ( Throwables . getRootCause ( e ) ) )
2014-11-21 10:06:48 +01:00
. type ( MediaType . TEXT_PLAIN ) . build ( ) ;
2013-05-21 12:57:32 +02:00
}
2013-05-17 19:39:52 +02:00
return Response . ok ( info ) . build ( ) ;
2013-04-18 12:50:44 +02:00
}
2013-07-27 11:21:26 +02:00
@Path ( " /refreshAll " )
@GET
2014-08-08 16:49:02 +02:00
@UnitOfWork
2013-07-27 11:21:26 +02:00
@ApiOperation ( value = " Queue all feeds of the user for refresh " , notes = " Manually add all feeds of the user to the refresh queue " )
2014-08-08 21:57:16 +02:00
public Response queueAllForRefresh ( @SecurityCheck User user ) {
2014-08-08 16:49:02 +02:00
feedSubscriptionService . refreshAll ( user ) ;
2013-08-13 09:25:55 +02:00
return Response . ok ( ) . build ( ) ;
2013-07-27 11:21:26 +02:00
}
2013-04-27 10:44:16 +02:00
@Path ( " /refresh " )
@POST
2014-08-08 16:49:02 +02:00
@UnitOfWork
2013-04-27 10:44:16 +02:00
@ApiOperation ( value = " Queue a feed for refresh " , notes = " Manually add a feed to the refresh queue " )
2014-08-08 21:57:16 +02:00
public Response queueForRefresh ( @SecurityCheck User user , @ApiParam ( value = " Feed id " ) IDRequest req ) {
2013-06-22 20:06:09 +02:00
Preconditions . checkNotNull ( req ) ;
Preconditions . checkNotNull ( req . getId ( ) ) ;
2014-08-08 16:49:02 +02:00
FeedSubscription sub = feedSubscriptionDAO . findById ( user , req . getId ( ) ) ;
2013-06-22 20:06:09 +02:00
if ( sub ! = null ) {
Feed feed = sub . getFeed ( ) ;
2014-08-08 16:49:02 +02:00
queues . add ( feed , true ) ;
2013-08-13 09:25:55 +02:00
return Response . ok ( ) . build ( ) ;
2013-06-22 20:06:09 +02:00
}
return Response . ok ( Status . NOT_FOUND ) . build ( ) ;
2013-04-27 10:44:16 +02:00
}
2013-04-18 12:50:44 +02:00
@Path ( " /mark " )
@POST
2014-08-08 16:49:02 +02:00
@UnitOfWork
2013-04-18 12:50:44 +02:00
@ApiOperation ( value = " Mark feed entries " , notes = " Mark feed entries as read (unread is not supported) " )
2014-08-08 21:57:16 +02:00
public Response markFeedEntries ( @SecurityCheck User user , @ApiParam ( value = " Mark request " ) MarkRequest req ) {
2013-04-18 12:50:44 +02:00
Preconditions . checkNotNull ( req ) ;
Preconditions . checkNotNull ( req . getId ( ) ) ;
2013-07-25 09:17:33 +02:00
Date olderThan = req . getOlderThan ( ) = = null ? null : new Date ( req . getOlderThan ( ) ) ;
2014-11-01 02:10:31 -04:00
String keywords = req . getKeywords ( ) ;
2014-11-07 10:53:49 +01:00
List < FeedEntryKeyword > entryKeywords = FeedEntryKeyword . fromQueryString ( keywords ) ;
2013-04-18 12:50:44 +02:00
2014-08-08 16:49:02 +02:00
FeedSubscription subscription = feedSubscriptionDAO . findById ( user , Long . valueOf ( req . getId ( ) ) ) ;
2013-06-13 13:15:46 +02:00
if ( subscription ! = null ) {
2014-11-07 10:53:49 +01:00
feedEntryService . markSubscriptionEntries ( user , Arrays . asList ( subscription ) , olderThan , entryKeywords ) ;
2013-06-13 13:15:46 +02:00
}
2013-08-13 09:25:55 +02:00
return Response . ok ( ) . build ( ) ;
2013-04-18 12:50:44 +02:00
}
2013-04-30 16:17:34 +02:00
@GET
@Path ( " /get/{id} " )
2014-08-08 16:49:02 +02:00
@UnitOfWork
2013-04-30 16:17:34 +02:00
@ApiOperation ( value = " " , notes = " " )
2014-08-08 21:57:16 +02:00
public Response get ( @SecurityCheck User user , @ApiParam ( value = " user id " , required = true ) @PathParam ( " id " ) Long id ) {
2013-04-30 16:17:34 +02:00
Preconditions . checkNotNull ( id ) ;
2014-08-08 16:49:02 +02:00
FeedSubscription sub = feedSubscriptionDAO . findById ( user , id ) ;
2013-05-22 22:30:35 +02:00
if ( sub = = null ) {
return Response . status ( Status . NOT_FOUND ) . build ( ) ;
}
2014-08-08 16:49:02 +02:00
UnreadCount unreadCount = feedSubscriptionService . getUnreadCount ( user ) . get ( id ) ;
return Response . ok ( Subscription . build ( sub , config . getApplicationSettings ( ) . getPublicUrl ( ) , unreadCount ) ) . build ( ) ;
2013-04-30 16:17:34 +02:00
}
2013-06-11 00:05:09 +04:30
@GET
2013-06-11 14:42:17 +02:00
@Path ( " /favicon/{id} " )
2014-08-08 16:49:02 +02:00
@UnitOfWork
2013-06-11 14:42:17 +02:00
@ApiOperation ( value = " Fetch a feed's icon " , notes = " Fetch a feed's icon " )
2014-08-08 21:57:16 +02:00
public Response getFavicon ( @SecurityCheck User user , @ApiParam ( value = " subscription id " ) @PathParam ( " id " ) Long id ) {
2013-06-11 07:18:04 +02:00
2013-06-11 13:01:12 +02:00
Preconditions . checkNotNull ( id ) ;
2014-08-08 16:49:02 +02:00
FeedSubscription subscription = feedSubscriptionDAO . findById ( user , id ) ;
2013-06-11 13:01:12 +02:00
if ( subscription = = null ) {
return Response . status ( Status . NOT_FOUND ) . build ( ) ;
}
Feed feed = subscription . getFeed ( ) ;
2014-10-26 12:25:44 +01:00
byte [ ] icon = feedService . fetchFavicon ( feed ) ;
2013-06-11 07:18:04 +02:00
2014-10-26 18:13:09 +01:00
ResponseBuilder builder = Response . ok ( icon , " image/x-icon " ) ;
2013-06-11 00:05:09 +04:30
CacheControl cacheControl = new CacheControl ( ) ;
cacheControl . setMaxAge ( 2592000 ) ;
2014-10-26 17:48:25 +01:00
cacheControl . setPrivate ( false ) ;
2013-06-11 07:18:04 +02:00
builder . cacheControl ( cacheControl ) ;
2013-06-11 00:05:09 +04:30
Calendar calendar = Calendar . getInstance ( ) ;
calendar . add ( Calendar . MONTH , 1 ) ;
2013-06-11 07:18:04 +02:00
builder . expires ( calendar . getTime ( ) ) ;
2014-08-08 16:49:02 +02:00
builder . lastModified ( CommaFeedApplication . STARTUP_TIME ) ;
2013-06-11 00:05:09 +04:30
2013-06-11 07:18:04 +02:00
return builder . build ( ) ;
2013-06-11 00:05:09 +04:30
}
2013-04-18 12:50:44 +02:00
@POST
@Path ( " /subscribe " )
2014-08-08 16:49:02 +02:00
@UnitOfWork
2013-04-18 12:50:44 +02:00
@ApiOperation ( value = " Subscribe to a feed " , notes = " Subscribe to a feed " )
2014-08-08 21:57:16 +02:00
public Response subscribe ( @SecurityCheck User user , @ApiParam ( value = " subscription request " , required = true ) SubscribeRequest req ) {
2013-04-18 12:50:44 +02:00
Preconditions . checkNotNull ( req ) ;
Preconditions . checkNotNull ( req . getTitle ( ) ) ;
Preconditions . checkNotNull ( req . getUrl ( ) ) ;
String url = prependHttp ( req . getUrl ( ) ) ;
2013-05-20 22:14:56 +03:00
try {
2013-05-21 12:57:32 +02:00
url = fetchFeedInternal ( url ) . getUrl ( ) ;
2014-02-14 15:41:17 +01:00
FeedCategory category = null ;
if ( req . getCategoryId ( ) ! = null & & ! CategoryREST . ALL . equals ( req . getCategoryId ( ) ) ) {
category = feedCategoryDAO . findById ( Long . valueOf ( req . getCategoryId ( ) ) ) ;
}
2013-05-21 12:57:32 +02:00
FeedInfo info = fetchFeedInternal ( url ) ;
2014-08-08 16:49:02 +02:00
feedSubscriptionService . subscribe ( user , info . getUrl ( ) , req . getTitle ( ) , category ) ;
2013-05-20 22:14:56 +03:00
} catch ( Exception e ) {
2014-08-08 16:49:02 +02:00
log . error ( " Failed to subscribe to URL {}: {} " , url , e . getMessage ( ) , e ) ;
2013-07-25 09:17:33 +02:00
return Response . status ( Status . SERVICE_UNAVAILABLE ) . entity ( " Failed to subscribe to URL " + url + " : " + e . getMessage ( ) ) . build ( ) ;
2013-05-20 22:14:56 +03:00
}
2013-08-13 09:25:55 +02:00
return Response . ok ( ) . build ( ) ;
2013-04-18 12:50:44 +02:00
}
2013-05-19 10:03:26 +02:00
@GET
@Path ( " /subscribe " )
2014-08-08 16:49:02 +02:00
@UnitOfWork
2013-05-19 10:03:26 +02:00
@ApiOperation ( value = " Subscribe to a feed " , notes = " Subscribe to a feed " )
2014-08-08 21:57:16 +02:00
public Response subscribe ( @SecurityCheck User user , @ApiParam ( value = " feed url " , required = true ) @QueryParam ( " url " ) String url ) {
2013-05-19 10:03:26 +02:00
try {
Preconditions . checkNotNull ( url ) ;
url = prependHttp ( url ) ;
2013-05-21 12:57:32 +02:00
url = fetchFeedInternal ( url ) . getUrl ( ) ;
2013-05-19 10:03:26 +02:00
2013-05-21 12:57:32 +02:00
FeedInfo info = fetchFeedInternal ( url ) ;
2014-08-08 16:49:02 +02:00
feedSubscriptionService . subscribe ( user , info . getUrl ( ) , info . getTitle ( ) , null ) ;
2013-05-19 10:03:26 +02:00
} catch ( Exception e ) {
log . info ( " Could not subscribe to url {} : {} " , url , e . getMessage ( ) ) ;
}
2014-08-08 16:49:02 +02:00
return Response . temporaryRedirect ( URI . create ( config . getApplicationSettings ( ) . getPublicUrl ( ) ) ) . build ( ) ;
2013-05-19 10:03:26 +02:00
}
2013-04-18 12:50:44 +02:00
private String prependHttp ( String url ) {
if ( ! url . startsWith ( " http " ) ) {
url = " http:// " + url ;
}
return url ;
}
@POST
@Path ( " /unsubscribe " )
2014-08-08 16:49:02 +02:00
@UnitOfWork
2013-06-05 16:42:52 +02:00
@ApiOperation ( value = " Unsubscribe from a feed " , notes = " Unsubscribe from a feed " )
2014-08-08 21:57:16 +02:00
public Response unsubscribe ( @SecurityCheck User user , @ApiParam ( required = true ) IDRequest req ) {
2013-04-18 12:50:44 +02:00
Preconditions . checkNotNull ( req ) ;
Preconditions . checkNotNull ( req . getId ( ) ) ;
2014-08-08 16:49:02 +02:00
boolean deleted = feedSubscriptionService . unsubscribe ( user , req . getId ( ) ) ;
2013-07-25 10:21:11 +02:00
if ( deleted ) {
2013-08-13 09:25:55 +02:00
return Response . ok ( ) . build ( ) ;
2013-04-18 12:50:44 +02:00
} else {
return Response . status ( Status . NOT_FOUND ) . build ( ) ;
}
}
@POST
2013-04-30 16:17:34 +02:00
@Path ( " /modify " )
2014-08-08 16:49:02 +02:00
@UnitOfWork
2013-04-30 16:17:34 +02:00
@ApiOperation ( value = " Modify a subscription " , notes = " Modify a feed subscription " )
2014-08-08 21:57:16 +02:00
public Response modify ( @SecurityCheck User user , @ApiParam ( value = " subscription id " , required = true ) FeedModificationRequest req ) {
2013-04-18 12:50:44 +02:00
Preconditions . checkNotNull ( req ) ;
Preconditions . checkNotNull ( req . getId ( ) ) ;
2014-11-04 16:01:37 +01:00
try {
2014-11-10 09:49:59 +01:00
feedEntryFilteringService . filterMatchesEntry ( req . getFilter ( ) , TEST_ENTRY ) ;
2014-11-07 08:46:09 +01:00
} catch ( FeedEntryFilterException e ) {
2014-11-04 16:01:37 +01:00
Throwable root = Throwables . getRootCause ( e ) ;
2014-11-22 11:53:53 +01:00
return Response . status ( Status . BAD_REQUEST ) . entity ( root . getMessage ( ) ) . type ( MediaType . TEXT_PLAIN ) . build ( ) ;
2014-11-04 16:01:37 +01:00
}
2014-08-08 16:49:02 +02:00
FeedSubscription subscription = feedSubscriptionDAO . findById ( user , req . getId ( ) ) ;
2014-11-04 16:01:37 +01:00
subscription . setFilter ( req . getFilter ( ) ) ;
2013-06-05 20:51:29 +02:00
if ( StringUtils . isNotBlank ( req . getName ( ) ) ) {
subscription . setTitle ( req . getName ( ) ) ;
}
2013-04-30 16:17:34 +02:00
FeedCategory parent = null ;
2013-07-25 09:17:33 +02:00
if ( req . getCategoryId ( ) ! = null & & ! CategoryREST . ALL . equals ( req . getCategoryId ( ) ) ) {
2014-08-08 16:49:02 +02:00
parent = feedCategoryDAO . findById ( user , Long . valueOf ( req . getCategoryId ( ) ) ) ;
2013-04-30 16:17:34 +02:00
}
subscription . setCategory ( parent ) ;
2013-06-02 11:51:20 +02:00
if ( req . getPosition ( ) ! = null ) {
2014-08-08 16:49:02 +02:00
List < FeedSubscription > subs = feedSubscriptionDAO . findByCategory ( user , parent ) ;
2013-06-04 07:26:32 +02:00
Collections . sort ( subs , new Comparator < FeedSubscription > ( ) {
@Override
public int compare ( FeedSubscription o1 , FeedSubscription o2 ) {
2013-07-25 09:17:33 +02:00
return ObjectUtils . compare ( o1 . getPosition ( ) , o2 . getPosition ( ) ) ;
2013-06-04 07:26:32 +02:00
}
} ) ;
2013-06-05 20:51:29 +02:00
2013-06-02 11:51:20 +02:00
int existingIndex = - 1 ;
for ( int i = 0 ; i < subs . size ( ) ; i + + ) {
2014-10-28 16:36:09 +01:00
if ( Objects . equals ( subs . get ( i ) . getId ( ) , subscription . getId ( ) ) ) {
2013-06-02 11:51:20 +02:00
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 ) ;
}
2013-06-06 09:54:17 +02:00
feedSubscriptionDAO . saveOrUpdate ( subs ) ;
2013-06-02 11:51:20 +02:00
} else {
2013-06-06 09:54:17 +02:00
feedSubscriptionDAO . saveOrUpdate ( subscription ) ;
2013-06-02 11:51:20 +02:00
}
2014-08-08 16:49:02 +02:00
cache . invalidateUserRootCategory ( user ) ;
2013-08-13 09:25:55 +02:00
return Response . ok ( ) . build ( ) ;
2013-04-18 12:50:44 +02:00
}
@POST
@Path ( " /import " )
2014-08-08 16:49:02 +02:00
@UnitOfWork
2013-04-18 12:50:44 +02:00
@Consumes ( MediaType . MULTIPART_FORM_DATA )
2013-04-28 09:19:27 +02:00
@ApiOperation ( value = " OPML import " , notes = " Import an OPML file, posted as a FORM with the 'file' name " )
2014-08-08 21:57:16 +02:00
public Response importOpml ( @SecurityCheck User user , @FormDataParam ( " file " ) InputStream input ) {
2013-06-02 10:41:47 +02:00
2014-08-08 16:49:02 +02:00
String publicUrl = config . getApplicationSettings ( ) . getPublicUrl ( ) ;
2013-06-02 10:41:47 +02:00
if ( StringUtils . isBlank ( publicUrl ) ) {
2013-07-25 09:17:33 +02:00
throw new WebApplicationException ( Response . status ( Status . INTERNAL_SERVER_ERROR )
2013-06-02 10:41:47 +02:00
. entity ( " Set the public URL in the admin section. " ) . build ( ) ) ;
}
2014-08-08 16:49:02 +02:00
if ( CommaFeedApplication . USERNAME_DEMO . equals ( user . getName ( ) ) ) {
2013-07-25 09:17:33 +02:00
return Response . status ( Status . FORBIDDEN ) . entity ( " Import is disabled for the demo account " ) . build ( ) ;
2013-05-22 12:43:36 +02:00
}
2013-04-18 12:50:44 +02:00
try {
2014-08-08 16:49:02 +02:00
String opml = IOUtils . toString ( input , " UTF-8 " ) ;
opmlImporter . importOpml ( user , opml ) ;
2013-04-18 12:50:44 +02:00
} catch ( Exception e ) {
2014-08-08 16:49:02 +02:00
log . error ( e . getMessage ( ) , e ) ;
2013-07-25 09:17:33 +02:00
throw new WebApplicationException ( Response . status ( Status . INTERNAL_SERVER_ERROR ) . entity ( e . getMessage ( ) ) . build ( ) ) ;
2013-04-18 12:50:44 +02:00
}
2014-08-08 16:49:02 +02:00
return Response . seeOther ( URI . create ( config . getApplicationSettings ( ) . getPublicUrl ( ) ) ) . build ( ) ;
2013-04-18 12:50:44 +02:00
}
2013-04-28 09:19:27 +02:00
@GET
@Path ( " /export " )
2014-08-08 16:49:02 +02:00
@UnitOfWork
2013-04-28 09:19:27 +02:00
@Produces ( MediaType . APPLICATION_XML )
@ApiOperation ( value = " OPML export " , notes = " Export an OPML file of the user's subscriptions " )
2014-08-08 21:57:16 +02:00
public Response exportOpml ( @SecurityCheck User user ) {
2014-08-08 16:49:02 +02:00
Opml opml = opmlExporter . export ( user ) ;
2013-04-28 09:19:27 +02:00
WireFeedOutput output = new WireFeedOutput ( ) ;
String opmlString = null ;
try {
opmlString = output . outputString ( opml ) ;
} catch ( Exception e ) {
2013-07-25 09:17:33 +02:00
return Response . status ( Status . INTERNAL_SERVER_ERROR ) . entity ( e ) . build ( ) ;
2013-04-28 09:19:27 +02:00
}
return Response . ok ( opmlString ) . build ( ) ;
}
2013-04-18 12:50:44 +02:00
}