forked from Archives/Athou_commafeed
Compare commits
140 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
a16d9877cc | ||
|
|
c24e9e083c | ||
|
|
101602c6f6 | ||
|
|
18a7bd1fd1 | ||
|
|
dfbd556bb8 | ||
|
|
040cdde8ba | ||
|
|
06373480ae | ||
|
|
5713a78f2e | ||
|
|
b9f2f17a24 | ||
|
|
9adc993472 | ||
|
|
dcd5f3d529 | ||
|
|
18e70a0e6b | ||
|
|
5ad57d1608 | ||
|
|
74eaf48ceb | ||
|
|
30bb0cb291 | ||
|
|
b50e6b93bd | ||
|
|
a0b5a1462d | ||
|
|
4910f93c94 | ||
|
|
4a52bd0cb7 | ||
|
|
b0bfb73952 | ||
|
|
69d049a69a | ||
|
|
7d75153362 | ||
|
|
748bfa31ae | ||
|
|
e7d995edbc | ||
|
|
a144fb2e48 | ||
|
|
7521013e11 | ||
|
|
c6321fc6b2 | ||
|
|
7d92d5d096 | ||
|
|
ab201d5016 | ||
|
|
efa38d5ee9 | ||
|
|
e8769d09a8 | ||
|
|
a216444825 | ||
|
|
fee3e10e6b | ||
|
|
4d71a8f3c2 | ||
|
|
fc104b0b01 | ||
|
|
3dcb351b36 | ||
|
|
600d05d08f | ||
|
|
6b6ff70ad3 | ||
|
|
891f660738 | ||
|
|
6901b9b728 | ||
|
|
c7f211a7f8 | ||
|
|
c48ea1152c | ||
|
|
f5d0eb94b4 | ||
|
|
cebeef04a0 | ||
|
|
3e77a83ca6 | ||
|
|
c872b335e7 | ||
|
|
cc1e173552 | ||
|
|
35e0567705 | ||
|
|
fb2add305e | ||
|
|
74d4c18c4c | ||
|
|
da3ce07485 | ||
|
|
c7ab179a9e | ||
|
|
6fd11fcd56 | ||
|
|
3966cf165b | ||
|
|
0b2ada5d1c | ||
|
|
4278101bbe | ||
|
|
8b43af49fc | ||
|
|
6e29e8426b | ||
|
|
af11d3c771 | ||
|
|
e5c5af4d57 | ||
|
|
3dbdf5adf2 | ||
|
|
4d7a030b70 | ||
|
|
3351262dd7 | ||
|
|
5ec4377502 | ||
|
|
9c8402c3a5 | ||
|
|
928a45e48e | ||
|
|
1d088c5eae | ||
|
|
cdcf81ab7c | ||
|
|
9f196bafe9 | ||
|
|
5c9e1406a1 | ||
|
|
0b42e00b29 | ||
|
|
88b98a138f | ||
|
|
136c37885d | ||
|
|
812988b31a | ||
|
|
191680a01b | ||
|
|
467d1a754d | ||
|
|
d1973922cd | ||
|
|
3b7689975d | ||
|
|
3386a71c5e | ||
|
|
7bb65a5e76 | ||
|
|
f3a9c8e0e2 | ||
|
|
22861ca8d0 | ||
|
|
19118ea241 | ||
|
|
4a9dc7249f | ||
|
|
5dad9c2eb8 | ||
|
|
d6b35b00b9 | ||
|
|
fda8ab500b | ||
|
|
66df421de2 | ||
|
|
33c62f08ca | ||
|
|
b660602809 | ||
|
|
6dfce2ca30 | ||
|
|
655e20e99e | ||
|
|
f2b80bdc08 | ||
|
|
10af873fa5 | ||
|
|
d87a5b14f8 | ||
|
|
b87a18b993 | ||
|
|
c4185034e4 | ||
|
|
9d64426b00 | ||
|
|
c81cc8bea4 | ||
|
|
90e680d6be | ||
|
|
04c0833111 | ||
|
|
06151eab3b | ||
|
|
3dcb8590f6 | ||
|
|
a9b313aa4a | ||
|
|
1f2e35060b | ||
|
|
a96862fffa | ||
|
|
68cb8e194d | ||
|
|
c164926c54 | ||
|
|
de7516116d | ||
|
|
fccfe5b088 | ||
|
|
23aa5fa0a3 | ||
|
|
d384c0a141 | ||
|
|
18058c2a36 | ||
|
|
71727202f3 | ||
|
|
eee0b949de | ||
|
|
3cbbb67b0c | ||
|
|
7879f66e78 | ||
|
|
c14ac37495 | ||
|
|
73a77183aa | ||
|
|
09cfa21091 | ||
|
|
c193571ece | ||
|
|
04bc92b071 | ||
|
|
94e58a449c | ||
|
|
9d044195aa | ||
|
|
caff34cc3b | ||
|
|
34c5c0b1f7 | ||
|
|
906801e13c | ||
|
|
dad4c6b866 | ||
|
|
090462022f | ||
|
|
cbf9f65fb4 | ||
|
|
5a493cd55d | ||
|
|
dfc204ef05 | ||
|
|
56c6e2d29c | ||
|
|
db03dd12a0 | ||
|
|
6c67e6363a | ||
|
|
e2888beb4c | ||
|
|
bba9166885 | ||
|
|
504e4eab3e | ||
|
|
2e475c35cc | ||
|
|
ccf18758fb |
@@ -1,6 +1,23 @@
|
||||
#!/bin/bash
|
||||
cd $OPENSHIFT_REPO_DIR
|
||||
if [ ! -d $OPENSHIFT_DATA_DIR/jdk1.8.0_20 ]
|
||||
then
|
||||
cd $OPENSHIFT_DATA_DIR
|
||||
wget http://www.java.net/download/jdk8u20/archive/b17/binaries/jdk-8u20-ea-bin-b17-linux-x64-04_jun_2014.tar.gz
|
||||
tar xvf *.tar.gz
|
||||
rm -f *.tar.gz
|
||||
fi
|
||||
if [ ! -d $OPENSHIFT_DATA_DIR/apache-maven-3.2.3 ]
|
||||
then
|
||||
cd $OPENSHIFT_DATA_DIR
|
||||
wget http://archive.apache.org/dist/maven/maven-3/3.2.3/binaries/apache-maven-3.2.3-bin.tar.gz
|
||||
tar xvf *.tar.gz
|
||||
rm -f *.tar.gz
|
||||
fi
|
||||
export M2=$OPENSHIFT_DATA_DIR/apache-maven-3.2.3/bin
|
||||
export JAVA_HOME=$OPENSHIFT_DATA_DIR/jdk1.8.0_20
|
||||
export PATH=$JAVA_HOME/bin:$M2:$PATH
|
||||
|
||||
cd $OPENSHIFT_REPO_DIR
|
||||
rm -rf $OPENSHIFT_REPO_DIR/node
|
||||
rm -rf $OPENSHIFT_REPO_DIR/node_modules
|
||||
rm -rf $OPENSHIFT_TMP_DIR/npm
|
||||
@@ -16,7 +33,4 @@ export HOME="$OPENSHIFT_TMP_DIR/local"
|
||||
|
||||
export NPM_CONFIG_ARCH="x64"
|
||||
|
||||
npm install npm
|
||||
export PATH="$OPENSHIFT_REPO_DIR/node_modules/.bin:$PATH"
|
||||
|
||||
mvn clean package -DskipTests -Dos.arch=x64
|
||||
mvn clean package -DskipTests -Dos.arch=x64 -s .openshift/settings.xml
|
||||
|
||||
@@ -1,3 +1,4 @@
|
||||
#!/bin/bash
|
||||
cd $OPENSHIFT_REPO_DIR
|
||||
nohup java -jar target/commafeed.jar server .openshift/config.mysql.yml > ${OPENSHIFT_DIY_LOG_DIR}/commafeed.log 2>&1 &
|
||||
export JAVA_HOME=$OPENSHIFT_DATA_DIR/jdk1.8.0_20
|
||||
nohup $JAVA_HOME/bin/java -jar target/commafeed.jar server .openshift/config.mysql.yml > ${OPENSHIFT_DIY_LOG_DIR}/commafeed.log 2>&1 &
|
||||
|
||||
@@ -13,6 +13,9 @@ app:
|
||||
# put your google analytics tracking code here
|
||||
googleAnalyticsTrackingCode:
|
||||
|
||||
# put your google server key (used for youtube favicon fetching)
|
||||
googleAuthKey:
|
||||
|
||||
# number of http threads
|
||||
backgroundThreads: 3
|
||||
|
||||
|
||||
0
.openshift/markers/java8
Normal file
0
.openshift/markers/java8
Normal file
@@ -1,3 +1,41 @@
|
||||
<settings>
|
||||
<localRepository>$OPENSHIFT_DATA_DIR</localRepository>
|
||||
<mirrors>
|
||||
<mirror>
|
||||
<id>nexus</id>
|
||||
<mirrorOf>central</mirrorOf>
|
||||
<url>http://mirror1.ops.rhcloud.com/nexus/content/groups/public</url>
|
||||
</mirror>
|
||||
</mirrors>
|
||||
<profiles>
|
||||
<profile>
|
||||
<id>nexus</id>
|
||||
<repositories>
|
||||
<repository>
|
||||
<id>central</id>
|
||||
<url>http://central</url>
|
||||
<releases>
|
||||
<enabled>true</enabled>
|
||||
</releases>
|
||||
<snapshots>
|
||||
<enabled>true</enabled>
|
||||
</snapshots>
|
||||
</repository>
|
||||
</repositories>
|
||||
<pluginRepositories>
|
||||
<pluginRepository>
|
||||
<id>central</id>
|
||||
<url>http://central</url>
|
||||
<releases>
|
||||
<enabled>true</enabled>
|
||||
</releases>
|
||||
<snapshots>
|
||||
<enabled>true</enabled>
|
||||
</snapshots>
|
||||
</pluginRepository>
|
||||
</pluginRepositories>
|
||||
</profile>
|
||||
</profiles>
|
||||
<activeProfiles>
|
||||
<activeProfile>nexus</activeProfile>
|
||||
</activeProfiles>
|
||||
</settings>
|
||||
|
||||
@@ -1,5 +1,3 @@
|
||||
language: java
|
||||
jdk:
|
||||
- openjdk7
|
||||
- oraclejdk7
|
||||
- oraclejdk8
|
||||
@@ -1,3 +1,9 @@
|
||||
v 2.2.0
|
||||
- fix youtube and instagram favicon fetching
|
||||
- mark as read filter was lost when a feed was rearranged with drag&drop
|
||||
- feed entry categories are now displayed if available
|
||||
- various performance and dependencies upgrades
|
||||
- java8 is now required
|
||||
v 2.1.0
|
||||
- dropwizard upgrade to 0.8.0
|
||||
- you have to remove the "app.contextPath" setting from your yml file, you can optionally use server.applicationContextPath instead
|
||||
|
||||
22
README.md
22
README.md
@@ -7,7 +7,7 @@ Google Reader inspired self-hosted RSS reader, based on Dropwizard and AngularJS
|
||||
## Related open-source projects
|
||||
|
||||
|
||||
Android apps: [News+ extension](https://github.com/Athou/commafeed-newsplus) - [Android app](https://github.com/doomrobo/CommaFeed-Android-Reader)
|
||||
Android apps: [News+ extension](https://github.com/Athou/commafeed-newsplus)
|
||||
|
||||
Browser extensions: [Chrome](https://github.com/Athou/commafeed-chrome) - [Firefox](https://github.com/Athou/commafeed-firefox) - [Opera](https://github.com/Athou/commafeed-opera) - [Safari](https://github.com/Athou/commafeed-safari)
|
||||
|
||||
@@ -20,19 +20,19 @@ Browser extensions: [Chrome](https://github.com/Athou/commafeed-chrome) - [Firef
|
||||
mvn clean package
|
||||
cp config.yml.example config.yml
|
||||
vi config.yml
|
||||
java -jar target/commafeed.jar server config.yml
|
||||
java -Djava.net.preferIPv4Stack=true -jar target/commafeed.jar server config.yml
|
||||
|
||||
### The long version
|
||||
|
||||
CommaFeed 2.0 has been rewritten to use Dropwizard and gulp instead of using tomee and wro4j. The latest version of the 1.x branch is available [here](https://github.com/Athou/commafeed/tree/1.x).
|
||||
|
||||
For storage, you can either use an embedded H2 database (use it only to test CommaFeed) or an external MySQL, PostgreSQL or SQLServer database.
|
||||
You also need Maven 3.x (and a Java 1.7+ JDK) installed in order to build the application.
|
||||
You also need Maven 3.x (and a Java 1.8+ JDK) installed in order to build the application.
|
||||
|
||||
To install maven and openjdk on Ubuntu, issue the following commands
|
||||
|
||||
sudo apt-get install g++ build-essential openjdk-7-jdk maven
|
||||
# Make sure java7 is the selected java version
|
||||
sudo apt-get install g++ build-essential openjdk-8-jdk maven
|
||||
# Make sure java8 is the selected java version
|
||||
sudo update-alternatives --config java
|
||||
sudo update-alternatives --config javac
|
||||
|
||||
@@ -51,7 +51,7 @@ Now build the application
|
||||
Copy `config.yml.example` to `config.yml` then edit the file to your liking.
|
||||
Issue the following command to run the app, the server will listen by default on `http://localhost:8082`. The default user is `admin` and the default password is `admin`.
|
||||
|
||||
java -jar target/commafeed.jar server config.yml
|
||||
java -Djava.net.preferIPv4Stack=true -jar target/commafeed.jar server config.yml
|
||||
|
||||
You can use a proxy http server such as nginx or apache.
|
||||
|
||||
@@ -64,6 +64,10 @@ You can use a proxy http server such as nginx or apache.
|
||||
git remote add upstream -m master https://github.com/Athou/commafeed.git
|
||||
git pull -s recursive -X theirs upstream master
|
||||
git push
|
||||
|
||||
# To upgrade an existing openshift installation
|
||||
git pull upstream master
|
||||
git push
|
||||
|
||||
## Translate CommaFeed into your language
|
||||
|
||||
@@ -75,11 +79,11 @@ The language has to be referenced in the `src/main/app/js/i18n.js` file to be pi
|
||||
|
||||
## Themes
|
||||
|
||||
To create a theme, create a new file `src/main/webapp/sass/themes/_<theme>.scss`. Your styles should be wrapped in a `#theme-<theme>` element and use the [SCSS format](http://sass-lang.com/) which is a superset of CSS.
|
||||
To create a theme, create a new file `src/main/app/sass/themes/_<theme>.scss`. Your styles should be wrapped in a `#theme-<theme>` element and use the [SCSS format](http://sass-lang.com/) which is a superset of CSS.
|
||||
|
||||
Don't forget to reference your theme in `src/main/webapp/sass/app.scss` and in `src/main/webapp/js/controllers.js` (look for `$scope.themes`).
|
||||
Don't forget to reference your theme in `src/main/app/sass/app.scss` and in `src/main/app/js/controllers.js` (look for `$scope.themes`).
|
||||
|
||||
See [_test.scss](https://github.com/Athou/commafeed/blob/master/src/main/webapp/sass/themes/_test.scss) for an example.
|
||||
See [_test.scss](https://github.com/Athou/commafeed/blob/master/src/main/app/sass/themes/_test.scss) for an example.
|
||||
|
||||
|
||||
## Local development
|
||||
|
||||
36
bower.json
36
bower.json
@@ -2,36 +2,36 @@
|
||||
"name": "commafeed",
|
||||
"version": "2.0.0",
|
||||
"dependencies": {
|
||||
"jquery": "2.1.1",
|
||||
"jquery": "2.1.3",
|
||||
"jquery-ui": "1.10.3",
|
||||
"jquery-mousewheel": "3.1.12",
|
||||
"lodash": "2.4.1",
|
||||
"bootstrap": "3.3.1",
|
||||
"lodash": "3.4.0",
|
||||
"bootstrap": "3.3.2",
|
||||
"font-awesome": "3.2.1",
|
||||
"angular": "1.3.2",
|
||||
"angular-resource": "1.3.2",
|
||||
"angular-route": "1.3.2",
|
||||
"angular-sanitize": "1.3.2",
|
||||
"angular-touch": "1.3.2",
|
||||
"angular-animate": "1.3.2",
|
||||
"angular-ui-router": "0.2.12",
|
||||
"angular": "1.3.14",
|
||||
"angular-resource": "1.3.14",
|
||||
"angular-route": "1.3.14",
|
||||
"angular-sanitize": "1.3.14",
|
||||
"angular-touch": "1.3.14",
|
||||
"angular-animate": "1.3.14",
|
||||
"angular-ui-router": "0.2.13",
|
||||
"angular-ui-utils": "0.1.0",
|
||||
"angular-ui-select2": "0.0.5",
|
||||
"angular-bootstrap": "0.2.0",
|
||||
"angular-loading-bar": "0.6.0",
|
||||
"angular-translate": "2.4.2",
|
||||
"angular-translate-loader-static-files": "2.4.2",
|
||||
"angular-translate": "2.6.1",
|
||||
"angular-translate-loader-static-files": "2.6.1",
|
||||
"ngInfiniteScroll": "1.0.0",
|
||||
"ng-grid": "2.0.6",
|
||||
"mousetrap": "1.4.6",
|
||||
"momentjs": "2.8.3",
|
||||
"devicejs": "0.1.16",
|
||||
"momentjs": "2.9.0",
|
||||
"devicejs": "0.2.4",
|
||||
"readabilicons": "arc90/readability-readabilicons#34c55561c5b8ec6e90714b50237c06b13cb9d59c",
|
||||
"zocial": "samcollins/css-social-buttons#1f59ecacde475e563fb6771667597493ec4eecb6",
|
||||
"swagger-ui": "2.0.24"
|
||||
"zocial-less": "1.0.0",
|
||||
"swagger-ui": "2.1.8-M1"
|
||||
},
|
||||
"resolutions": {
|
||||
"angular": "1.3.2",
|
||||
"angular-translate": "2.4.2"
|
||||
"angular": "1.3.14",
|
||||
"angular-translate": "2.6.1"
|
||||
}
|
||||
}
|
||||
|
||||
@@ -13,6 +13,9 @@ app:
|
||||
# put your google analytics tracking code here
|
||||
googleAnalyticsTrackingCode:
|
||||
|
||||
# put your google server key (used for youtube favicon fetching)
|
||||
googleAuthKey:
|
||||
|
||||
# number of http threads
|
||||
backgroundThreads: 3
|
||||
|
||||
@@ -77,12 +80,7 @@ database:
|
||||
password: sa
|
||||
properties:
|
||||
charSet: UTF-8
|
||||
maxWaitForConnection: 1s
|
||||
validationQuery: "/* CommaFeed Health Check */ SELECT 1"
|
||||
minSize: 1
|
||||
maxSize: 50
|
||||
checkConnectionWhileIdle: true
|
||||
maxConnectionAge: 30m
|
||||
validationQuery: "/* CommaFeed Health Check */ SELECT 1"
|
||||
|
||||
server:
|
||||
applicationConnectors:
|
||||
|
||||
@@ -13,6 +13,9 @@ app:
|
||||
# put your google analytics tracking code here
|
||||
googleAnalyticsTrackingCode:
|
||||
|
||||
# put your google server key (used for youtube favicon fetching)
|
||||
googleAuthKey:
|
||||
|
||||
# number of http threads
|
||||
backgroundThreads: 3
|
||||
|
||||
@@ -78,12 +81,10 @@ database:
|
||||
password: sa
|
||||
properties:
|
||||
charSet: UTF-8
|
||||
maxWaitForConnection: 1s
|
||||
validationQuery: "/* CommaFeed Health Check */ SELECT 1"
|
||||
minSize: 1
|
||||
maxSize: 50
|
||||
checkConnectionWhileIdle: true
|
||||
maxConnectionAge: 30m
|
||||
validationQuery: "/* CommaFeed Health Check */ SELECT 1"
|
||||
minSize: 1
|
||||
maxSize: 50
|
||||
maxConnectionAge: 30m
|
||||
|
||||
server:
|
||||
applicationConnectors:
|
||||
|
||||
27
gulpfile.js
27
gulpfile.js
@@ -4,7 +4,6 @@ var revReplace = require('gulp-rev-replace');
|
||||
var minifyCSS = require('gulp-minify-css');
|
||||
var uglify = require('gulp-uglify');
|
||||
var filter = require('gulp-filter');
|
||||
var bower = require('gulp-bower');
|
||||
var connect = require('gulp-connect');
|
||||
var modRewrite = require('connect-modrewrite');
|
||||
var sass = require('gulp-sass');
|
||||
@@ -15,10 +14,6 @@ var SRC_DIR = 'src/main/app/';
|
||||
var TEMP_DIR = 'target/gulp/'
|
||||
var BUILD_DIR = 'target/classes/assets/';
|
||||
|
||||
gulp.task('bower', function() {
|
||||
return bower();
|
||||
});
|
||||
|
||||
gulp.task('images', function() {
|
||||
return gulp.src(SRC_DIR + 'images/**/*').pipe(gulp.dest(BUILD_DIR + 'images'));
|
||||
});
|
||||
@@ -27,31 +22,32 @@ gulp.task('i18n', function() {
|
||||
return gulp.src(SRC_DIR + 'i18n/**/*.js').pipe(gulp.dest(BUILD_DIR + 'i18n'));
|
||||
});
|
||||
|
||||
gulp.task('favicons', function() {
|
||||
gulp.task('resources', function() {
|
||||
var favicons_png = SRC_DIR + '*.png';
|
||||
var favicons_ico = SRC_DIR + '*.ico';
|
||||
var favicons_svg = SRC_DIR + '*.svg';
|
||||
return gulp.src([favicons_png, favicons_ico, favicons_svg]).pipe(gulp.dest(BUILD_DIR));
|
||||
var manifest = SRC_DIR + 'manifest.json';
|
||||
return gulp.src([favicons_png, favicons_ico, favicons_svg, manifest]).pipe(gulp.dest(BUILD_DIR));
|
||||
});
|
||||
|
||||
gulp.task('sass', function() {
|
||||
return gulp.src(SRC_DIR + 'sass/app.scss').pipe(sass()).pipe(gulp.dest(TEMP_DIR + 'css'));
|
||||
});
|
||||
|
||||
gulp.task('fonts', ['bower'], function() {
|
||||
gulp.task('fonts', function() {
|
||||
var font_awesome = SRC_DIR + 'lib/font-awesome/font/fontawesome-webfont.*';
|
||||
var zocial = SRC_DIR + 'lib/zocial/css/zocial-regular-*';
|
||||
var zocial = SRC_DIR + 'lib/zocial-less/css/zocial-regular-*';
|
||||
var readabilicons = SRC_DIR + 'lib/readabilicons/webfont/fonts/readabilicons-*';
|
||||
return gulp.src([font_awesome, zocial, readabilicons]).pipe(gulp.dest(BUILD_DIR + 'font'));
|
||||
});
|
||||
|
||||
gulp.task('select2', ['bower'], function() {
|
||||
gulp.task('select2', function() {
|
||||
var gif = SRC_DIR + 'lib/select2/*.gif';
|
||||
var png = SRC_DIR + 'lib/select2/*.png';
|
||||
return gulp.src([gif, png]).pipe(gulp.dest(BUILD_DIR + 'css'));
|
||||
});
|
||||
|
||||
gulp.task('swagger-ui', ['bower'], function() {
|
||||
gulp.task('swagger-ui', function() {
|
||||
var index_html = SRC_DIR + 'api/index.html';
|
||||
var lib = SRC_DIR + 'lib/swagger-ui/dist/**/*';
|
||||
return gulp.src([lib, index_html]).pipe(gulp.dest(BUILD_DIR + 'api'));
|
||||
@@ -65,7 +61,7 @@ gulp.task('template-cache', function() {
|
||||
return gulp.src(SRC_DIR + 'templates/**/*.html').pipe(templateCache(options)).pipe(gulp.dest(TEMP_DIR + 'js'));
|
||||
});
|
||||
|
||||
gulp.task('build-dev', ['images', 'i18n', 'favicons', 'sass', 'fonts', 'select2', 'swagger-ui', 'template-cache', 'bower'], function() {
|
||||
gulp.task('build-dev', ['images', 'i18n', 'resources', 'sass', 'fonts', 'select2', 'swagger-ui', 'template-cache'], function() {
|
||||
var assets = useref.assets({
|
||||
searchPath : [SRC_DIR, TEMP_DIR]
|
||||
});
|
||||
@@ -75,7 +71,7 @@ gulp.task('build-dev', ['images', 'i18n', 'favicons', 'sass', 'fonts', 'select2'
|
||||
revReplace()).pipe(gulp.dest(BUILD_DIR)).pipe(connect.reload());
|
||||
});
|
||||
|
||||
gulp.task('build', ['images', 'i18n', 'favicons', 'sass', 'fonts', 'select2', 'swagger-ui', 'template-cache', 'bower'], function() {
|
||||
gulp.task('build', ['images', 'i18n', 'resources', 'sass', 'fonts', 'select2', 'swagger-ui', 'template-cache'], function() {
|
||||
var assets = useref.assets({
|
||||
searchPath : [SRC_DIR, TEMP_DIR]
|
||||
});
|
||||
@@ -101,8 +97,9 @@ gulp.task('serve', function() {
|
||||
connect.server({
|
||||
root : BUILD_DIR,
|
||||
port : 8082,
|
||||
livereload: true,
|
||||
livereload : true,
|
||||
middleware : function() {
|
||||
var api = '^/api/(.*)$ http://localhost:8083/rest/$1 [P]';
|
||||
var rest = '^/rest/(.*)$ http://localhost:8083/rest/$1 [P]';
|
||||
var next = '^/next(.*)$ http://localhost:8083/next$1 [P]';
|
||||
var logout = '^/logout$ http://localhost:8083/logout [P]';
|
||||
@@ -114,4 +111,4 @@ gulp.task('serve', function() {
|
||||
});
|
||||
|
||||
gulp.task('dev', ['build-dev', 'watch', 'serve']);
|
||||
gulp.task('default', ['build']);
|
||||
gulp.task('default', ['build']);
|
||||
|
||||
22
package.json
22
package.json
@@ -4,17 +4,17 @@
|
||||
"main": "main.js",
|
||||
"private": true,
|
||||
"devDependencies": {
|
||||
"gulp": "3.8.10",
|
||||
"gulp-rev": "2.0.1",
|
||||
"gulp-rev-replace": "0.3.1",
|
||||
"gulp-minify-css": "0.3.11",
|
||||
"gulp-uglify": "1.0.1",
|
||||
"gulp-filter": "1.0.2",
|
||||
"gulp-bower": "0.0.7",
|
||||
"bower": "1.4.1",
|
||||
"gulp": "3.8.11",
|
||||
"gulp-rev": "4.0.0",
|
||||
"gulp-rev-replace": "0.4.1",
|
||||
"gulp-minify-css": "1.1.5",
|
||||
"gulp-uglify": "1.2.0",
|
||||
"gulp-filter": "2.0.2",
|
||||
"gulp-connect": "2.2.0",
|
||||
"connect-modrewrite": "0.7.9",
|
||||
"gulp-sass": "1.1.0",
|
||||
"gulp-useref": "1.0.2",
|
||||
"gulp-angular-templatecache": "1.4.2"
|
||||
"connect-modrewrite": "0.8.1",
|
||||
"gulp-sass": "2.0.1",
|
||||
"gulp-useref": "1.1.2",
|
||||
"gulp-angular-templatecache": "1.6.0"
|
||||
}
|
||||
}
|
||||
|
||||
167
pom.xml
167
pom.xml
@@ -4,20 +4,20 @@
|
||||
<modelVersion>4.0.0</modelVersion>
|
||||
<groupId>com.commafeed</groupId>
|
||||
<artifactId>commafeed</artifactId>
|
||||
<version>2.1.0</version>
|
||||
<version>2.2.0</version>
|
||||
<packaging>jar</packaging>
|
||||
<name>CommaFeed</name>
|
||||
|
||||
<prerequisites>
|
||||
<maven>3.0.0</maven>
|
||||
<maven>3.0.5</maven>
|
||||
</prerequisites>
|
||||
|
||||
<properties>
|
||||
<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
|
||||
<java.version>1.7</java.version>
|
||||
<dropwizard.version>0.8.0-rc1</dropwizard.version>
|
||||
<guice.version>4.0-beta5</guice.version>
|
||||
<querydsl.version>3.6.0</querydsl.version>
|
||||
<java.version>1.8</java.version>
|
||||
<dropwizard.version>0.8.1</dropwizard.version>
|
||||
<guice.version>4.0</guice.version>
|
||||
<querydsl.version>3.6.4</querydsl.version>
|
||||
<rome.version>1.5.0</rome.version>
|
||||
</properties>
|
||||
|
||||
@@ -35,6 +35,39 @@
|
||||
<filtering>true</filtering>
|
||||
</resource>
|
||||
</resources>
|
||||
|
||||
<pluginManagement>
|
||||
<plugins>
|
||||
<plugin>
|
||||
<groupId>org.eclipse.m2e</groupId>
|
||||
<artifactId>lifecycle-mapping</artifactId>
|
||||
<version>1.0.0</version>
|
||||
<configuration>
|
||||
<lifecycleMappingMetadata>
|
||||
<pluginExecutions>
|
||||
<pluginExecution>
|
||||
<pluginExecutionFilter>
|
||||
<groupId>com.github.eirslett</groupId>
|
||||
<artifactId>frontend-maven-plugin</artifactId>
|
||||
<versionRange>[0.0.22,)</versionRange>
|
||||
<goals>
|
||||
<goal>npm</goal>
|
||||
<goal>gulp</goal>
|
||||
<goal>bower</goal>
|
||||
</goals>
|
||||
</pluginExecutionFilter>
|
||||
<action>
|
||||
<execute>
|
||||
<runOnIncremental>false</runOnIncremental>
|
||||
</execute>
|
||||
</action>
|
||||
</pluginExecution>
|
||||
</pluginExecutions>
|
||||
</lifecycleMappingMetadata>
|
||||
</configuration>
|
||||
</plugin>
|
||||
</plugins>
|
||||
</pluginManagement>
|
||||
<plugins>
|
||||
<plugin>
|
||||
<groupId>org.apache.maven.plugins</groupId>
|
||||
@@ -48,7 +81,7 @@
|
||||
<plugin>
|
||||
<groupId>pl.project13.maven</groupId>
|
||||
<artifactId>git-commit-id-plugin</artifactId>
|
||||
<version>2.1.11</version>
|
||||
<version>2.1.13</version>
|
||||
<executions>
|
||||
<execution>
|
||||
<goals>
|
||||
@@ -99,7 +132,7 @@
|
||||
<plugin>
|
||||
<groupId>com.github.eirslett</groupId>
|
||||
<artifactId>frontend-maven-plugin</artifactId>
|
||||
<version>0.0.19</version>
|
||||
<version>0.0.22</version>
|
||||
<executions>
|
||||
<execution>
|
||||
<id>install node and npm</id>
|
||||
@@ -108,8 +141,8 @@
|
||||
</goals>
|
||||
<phase>generate-resources</phase>
|
||||
<configuration>
|
||||
<nodeVersion>v0.10.30</nodeVersion>
|
||||
<npmVersion>1.3.8</npmVersion>
|
||||
<nodeVersion>v0.12.4</nodeVersion>
|
||||
<npmVersion>2.10.1</npmVersion>
|
||||
</configuration>
|
||||
</execution>
|
||||
<execution>
|
||||
@@ -119,6 +152,15 @@
|
||||
</goals>
|
||||
<phase>generate-resources</phase>
|
||||
</execution>
|
||||
<execution>
|
||||
<id>bower install</id>
|
||||
<goals>
|
||||
<goal>bower</goal>
|
||||
</goals>
|
||||
<configuration>
|
||||
<arguments>install</arguments>
|
||||
</configuration>
|
||||
</execution>
|
||||
<execution>
|
||||
<id>gulp build</id>
|
||||
<goals>
|
||||
@@ -131,7 +173,7 @@
|
||||
<plugin>
|
||||
<groupId>org.apache.maven.plugins</groupId>
|
||||
<artifactId>maven-jar-plugin</artifactId>
|
||||
<version>2.5</version>
|
||||
<version>2.6</version>
|
||||
<configuration>
|
||||
<archive>
|
||||
<manifest>
|
||||
@@ -147,19 +189,13 @@
|
||||
<dependency>
|
||||
<groupId>org.projectlombok</groupId>
|
||||
<artifactId>lombok</artifactId>
|
||||
<version>1.14.8</version>
|
||||
<version>1.16.4</version>
|
||||
<scope>provided</scope>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>org.slf4j</groupId>
|
||||
<artifactId>slf4j-api</artifactId>
|
||||
<version>1.7.7</version>
|
||||
</dependency>
|
||||
|
||||
<dependency>
|
||||
<groupId>org.apache.commons</groupId>
|
||||
<artifactId>commons-jexl</artifactId>
|
||||
<version>2.1.1</version>
|
||||
<version>1.7.12</version>
|
||||
</dependency>
|
||||
|
||||
<dependency>
|
||||
@@ -183,22 +219,11 @@
|
||||
<artifactId>dropwizard-hibernate</artifactId>
|
||||
<version>${dropwizard.version}</version>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>io.dropwizard</groupId>
|
||||
<artifactId>dropwizard-client</artifactId>
|
||||
<version>${dropwizard.version}</version>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>io.dropwizard</groupId>
|
||||
<artifactId>dropwizard-migrations</artifactId>
|
||||
<version>${dropwizard.version}</version>
|
||||
</dependency>
|
||||
<!-- TODO remove when dropwizard 0.8.0 is released -->
|
||||
<dependency>
|
||||
<groupId>org.liquibase</groupId>
|
||||
<artifactId>liquibase-core</artifactId>
|
||||
<version>3.3.1</version>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>io.dropwizard</groupId>
|
||||
<artifactId>dropwizard-assets</artifactId>
|
||||
@@ -208,22 +233,29 @@
|
||||
<groupId>io.dropwizard</groupId>
|
||||
<artifactId>dropwizard-forms</artifactId>
|
||||
<version>${dropwizard.version}</version>
|
||||
<type>pom</type>
|
||||
</dependency>
|
||||
|
||||
<dependency>
|
||||
<groupId>org.apache.httpcomponents</groupId>
|
||||
<artifactId>httpclient</artifactId>
|
||||
<version>4.5</version>
|
||||
<exclusions>
|
||||
<exclusion>
|
||||
<artifactId>commons-logging</artifactId>
|
||||
<groupId>commons-logging</groupId>
|
||||
</exclusion>
|
||||
</exclusions>
|
||||
</dependency>
|
||||
|
||||
<dependency>
|
||||
<groupId>com.wordnik</groupId>
|
||||
<artifactId>swagger-jaxrs_2.10</artifactId>
|
||||
<version>1.3.11</version>
|
||||
<artifactId>swagger-jaxrs</artifactId>
|
||||
<version>1.5.3-M1</version>
|
||||
<exclusions>
|
||||
<exclusion>
|
||||
<groupId>javax.ws.rs</groupId>
|
||||
<artifactId>jsr311-api</artifactId>
|
||||
</exclusion>
|
||||
<exclusion>
|
||||
<artifactId>commons-lang</artifactId>
|
||||
<groupId>commons-lang</groupId>
|
||||
</exclusion>
|
||||
</exclusions>
|
||||
</dependency>
|
||||
|
||||
@@ -240,6 +272,11 @@
|
||||
<version>${querydsl.version}</version>
|
||||
</dependency>
|
||||
|
||||
<dependency>
|
||||
<groupId>com.google.guava</groupId>
|
||||
<artifactId>guava</artifactId>
|
||||
<version>18.0</version>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>commons-io</groupId>
|
||||
<artifactId>commons-io</artifactId>
|
||||
@@ -258,60 +295,96 @@
|
||||
<dependency>
|
||||
<groupId>org.apache.commons</groupId>
|
||||
<artifactId>commons-math3</artifactId>
|
||||
<version>3.3</version>
|
||||
<version>3.5</version>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>org.apache.commons</groupId>
|
||||
<artifactId>commons-jexl</artifactId>
|
||||
<version>2.1.1</version>
|
||||
<exclusions>
|
||||
<exclusion>
|
||||
<artifactId>commons-logging</artifactId>
|
||||
<groupId>commons-logging</groupId>
|
||||
</exclusion>
|
||||
</exclusions>
|
||||
</dependency>
|
||||
|
||||
<dependency>
|
||||
<groupId>redis.clients</groupId>
|
||||
<artifactId>jedis</artifactId>
|
||||
<version>2.6.1</version>
|
||||
<version>2.7.2</version>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>com.sun.mail</groupId>
|
||||
<artifactId>javax.mail</artifactId>
|
||||
<version>1.5.2</version>
|
||||
<version>1.5.3</version>
|
||||
</dependency>
|
||||
|
||||
<!-- upgrade jdom to 2.0.5 for performance reasons (https://github.com/hunterhacker/jdom/issues/112) -->
|
||||
<dependency>
|
||||
<groupId>com.rometools</groupId>
|
||||
<artifactId>rome</artifactId>
|
||||
<version>${rome.version}</version>
|
||||
<exclusions>
|
||||
<exclusion>
|
||||
<artifactId>jdom</artifactId>
|
||||
<groupId>org.jdom</groupId>
|
||||
</exclusion>
|
||||
</exclusions>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>com.rometools</groupId>
|
||||
<artifactId>rome-opml</artifactId>
|
||||
<version>${rome.version}</version>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>org.jdom</groupId>
|
||||
<artifactId>jdom2</artifactId>
|
||||
<version>2.0.6</version>
|
||||
</dependency>
|
||||
|
||||
<dependency>
|
||||
<groupId>org.jsoup</groupId>
|
||||
<artifactId>jsoup</artifactId>
|
||||
<version>1.8.1</version>
|
||||
<version>1.8.2</version>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>com.ibm.icu</groupId>
|
||||
<artifactId>icu4j</artifactId>
|
||||
<version>54.1.1</version>
|
||||
<version>55.1</version>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>net.sourceforge.cssparser</groupId>
|
||||
<artifactId>cssparser</artifactId>
|
||||
<version>0.9.14</version>
|
||||
<version>0.9.16</version>
|
||||
</dependency>
|
||||
|
||||
<dependency>
|
||||
<groupId>com.google.apis</groupId>
|
||||
<artifactId>google-api-services-youtube</artifactId>
|
||||
<version>v3-rev139-1.20.0</version>
|
||||
<exclusions>
|
||||
<exclusion>
|
||||
<groupId>com.google.guava</groupId>
|
||||
<artifactId>guava-jdk5</artifactId>
|
||||
</exclusion>
|
||||
</exclusions>
|
||||
</dependency>
|
||||
|
||||
<dependency>
|
||||
<groupId>com.h2database</groupId>
|
||||
<artifactId>h2</artifactId>
|
||||
<version>1.4.182</version>
|
||||
<version>1.4.187</version>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>mysql</groupId>
|
||||
<artifactId>mysql-connector-java</artifactId>
|
||||
<version>5.1.34</version>
|
||||
<version>5.1.35</version>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>org.postgresql</groupId>
|
||||
<artifactId>postgresql</artifactId>
|
||||
<version>9.3-1102-jdbc41</version>
|
||||
<version>9.4-1201-jdbc41</version>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>net.sourceforge.jtds</groupId>
|
||||
@@ -328,7 +401,7 @@
|
||||
<dependency>
|
||||
<groupId>org.mockito</groupId>
|
||||
<artifactId>mockito-core</artifactId>
|
||||
<version>1.10.8</version>
|
||||
<version>2.0.11-beta</version>
|
||||
<scope>test</scope>
|
||||
</dependency>
|
||||
</dependencies>
|
||||
|
||||
@@ -2,7 +2,7 @@
|
||||
<html>
|
||||
<head>
|
||||
<title>Swagger UI</title>
|
||||
<link href='//fonts.googleapis.com/css?family=Droid+Sans:400,700' rel='stylesheet' type='text/css'/>
|
||||
<link href='css/typography.css' media='screen' rel='stylesheet' type='text/css'/>
|
||||
<link href='css/reset.css' media='screen' rel='stylesheet' type='text/css'/>
|
||||
<link href='css/screen.css' media='screen' rel='stylesheet' type='text/css'/>
|
||||
<link href='css/reset.css' media='print' rel='stylesheet' type='text/css'/>
|
||||
@@ -12,25 +12,23 @@
|
||||
<script src='lib/jquery.slideto.min.js' type='text/javascript'></script>
|
||||
<script src='lib/jquery.wiggle.min.js' type='text/javascript'></script>
|
||||
<script src='lib/jquery.ba-bbq.min.js' type='text/javascript'></script>
|
||||
<script src='lib/handlebars-1.0.0.js' type='text/javascript'></script>
|
||||
<script src='lib/handlebars-2.0.0.js' type='text/javascript'></script>
|
||||
<script src='lib/underscore-min.js' type='text/javascript'></script>
|
||||
<script src='lib/backbone-min.js' type='text/javascript'></script>
|
||||
<script src='lib/swagger.js' type='text/javascript'></script>
|
||||
<script src='lib/swagger-client.js' type='text/javascript'></script>
|
||||
<script src='swagger-ui.js' type='text/javascript'></script>
|
||||
<script src='lib/highlight.7.3.pack.js' type='text/javascript'></script>
|
||||
<script src='lib/marked.js' type='text/javascript'></script>
|
||||
|
||||
<!-- enabling this will enable oauth2 implicit scope support -->
|
||||
<script src='lib/swagger-oauth.js' type='text/javascript'></script>
|
||||
|
||||
<script type="text/javascript">
|
||||
$(function () {
|
||||
window.swaggerUi = new SwaggerUi({
|
||||
url: "../rest/api-docs",
|
||||
url: "../rest/swagger.json",
|
||||
dom_id: "swagger-ui-container",
|
||||
supportedSubmitMethods: ['get', 'post', 'put', 'delete'],
|
||||
onComplete: function(swaggerApi, swaggerUi){
|
||||
log("Loaded SwaggerUI");
|
||||
|
||||
if(typeof initOAuth == "function") {
|
||||
/*
|
||||
initOAuth({
|
||||
|
||||
@@ -99,7 +99,7 @@
|
||||
"queued_for_refresh" : "Queued for refresh",
|
||||
"feed_url" : "Feed URL",
|
||||
"filtering_expression" : "Filtering expression",
|
||||
"filtering_expression_help" : "If not empty, an expression evaluating to 'true' or 'false'. If false, new entries for this feed will be marked as read automatically.\nAvailable variables are 'title', 'content', 'url' and 'author' and their content is converted to lower case to ease string comparison.\nExample: url.contains('youtube') or (author eq 'athou' and title.contains('github').\nComplete available syntax is available <a href='http://commons.apache.org/proper/commons-jexl/reference/syntax.html' target='_blank'>here</a>.",
|
||||
"filtering_expression_help" : "If not empty, an expression evaluating to 'true' or 'false'. If false, new entries for this feed will be marked as read automatically.\nAvailable variables are 'title', 'content', 'url' 'author' and 'categories' and their content is converted to lower case to ease string comparison.\nExample: url.contains('youtube') or (author eq 'athou' and title.contains('github').\nComplete available syntax is available <a href='http://commons.apache.org/proper/commons-jexl/reference/syntax.html' target='_blank'>here</a>.",
|
||||
"generate_api_key_first" : "Generate an API key in your profile first.",
|
||||
"unsubscribe" : "Unsubscribe",
|
||||
"unsubscribe_confirmation" : "Are you sure you want to unsubscribe from this feed?",
|
||||
|
||||
@@ -31,7 +31,7 @@
|
||||
},
|
||||
"new_category" : {
|
||||
"name" : "Nome",
|
||||
"parent" : "Pai"
|
||||
"parent" : "Subcategoría de "
|
||||
},
|
||||
"toolbar" : {
|
||||
"unread" : "Sen Ler",
|
||||
@@ -39,12 +39,12 @@
|
||||
"previous_entry" : "Entrada Anterior",
|
||||
"next_entry" : "Próxima Entrada",
|
||||
"refresh" : "Actualizar",
|
||||
"refresh_all" : "Force refresh all my feeds ",
|
||||
"refresh_all" : "Forzar a actualización de todas as fontes ",
|
||||
"sort_by_asc_desc" : "Ordenar por data asc/desc",
|
||||
"titles_only" : "Só títulos",
|
||||
"expanded_view" : "Vista expandida",
|
||||
"mark_all_as_read" : "Marcar todos como lidos",
|
||||
"mark_all_older_12_hours" : "Items older than 12 hours ",
|
||||
"mark_all_older_12_hours" : "Elementos anteriores a 12 h. ",
|
||||
"mark_all_older_day" : "Artigos anteriores a un día",
|
||||
"mark_all_older_week" : "Artigos de máis de unha semana",
|
||||
"mark_all_older_two_weeks" : "Artigos de máis de dúas semanas",
|
||||
@@ -56,14 +56,14 @@
|
||||
"donate" : "Doar"
|
||||
},
|
||||
"view" : {
|
||||
"entry_source" : "from ",
|
||||
"entry_author" : "by ",
|
||||
"entry_source" : "desde ",
|
||||
"entry_author" : "por ",
|
||||
"error_while_loading_feed" : "Erro mentras se cargaba esta fonte",
|
||||
"keep_unread" : "Gardar non lidos",
|
||||
"no_unread_items" : "non ten elementos sen ler.",
|
||||
"mark_up_to_here" : "Mark as read up to here ",
|
||||
"search_for" : "searching for: ",
|
||||
"no_search_results" : "No match found for the requested keywords "
|
||||
"mark_up_to_here" : "Marcar como lidos ate aquí ",
|
||||
"search_for" : "buscando por: ",
|
||||
"no_search_results" : "Sen coincidencias para as palabras introducidas "
|
||||
},
|
||||
"feedsearch" : {
|
||||
"hint" : "Escriba unha suscrición...",
|
||||
@@ -80,8 +80,8 @@
|
||||
"scroll_marks" : "En vista expandida, o desplazamento polas entradas márcaas como lidas."
|
||||
},
|
||||
"appearance" : "Aspecto",
|
||||
"scroll_speed" : "Scrolling speed when navigating between entries (in milliseconds) ",
|
||||
"scroll_speed_help" : "set to 0 to disable ",
|
||||
"scroll_speed" : "Velocidade de desplazamento navegando entre entradas (en milisegundos) ",
|
||||
"scroll_speed_help" : "escriba 0 para deshabilitar ",
|
||||
"theme" : "Decorado",
|
||||
"submit_your_theme" : "Envíe o seu decorado",
|
||||
"custom_css" : "CSS Personalizado"
|
||||
@@ -89,21 +89,21 @@
|
||||
"details" : {
|
||||
"feed_details" : "Detalles de fontes",
|
||||
"url" : "URL",
|
||||
"website" : "Website ",
|
||||
"website" : "Sitio web ",
|
||||
"name" : "Nome",
|
||||
"category" : "Categoría",
|
||||
"position" : "Position ",
|
||||
"position" : "Posición ",
|
||||
"last_refresh" : "Última actualización",
|
||||
"message" : "Last refresh message ",
|
||||
"message" : "Última mensaxe da actualización ",
|
||||
"next_refresh" : "Próxima actualización",
|
||||
"queued_for_refresh" : "En cola para actualizar",
|
||||
"feed_url" : "URL da fonte",
|
||||
"generate_api_key_first" : "Antes debes xerar unha chave API no teu perfil.",
|
||||
"unsubscribe" : "Rematar suscripción",
|
||||
"unsubscribe_confirmation" : "Are you sure you want to unsubscribe from this feed? ",
|
||||
"delete_category_confirmation" : "Are you sure you want to delete this category? ",
|
||||
"unsubscribe_confirmation" : "Seguro que queres desuscribirte de esta fonte? ",
|
||||
"delete_category_confirmation" : "Seguro que queres eliminar esta categoría? ",
|
||||
"category_details" : "Detalles da categoría",
|
||||
"tag_details" : "Tag details ",
|
||||
"tag_details" : "Detalles da etiqueta ",
|
||||
"parent_category" : "Categoría principal"
|
||||
},
|
||||
"profile" : {
|
||||
@@ -119,7 +119,7 @@
|
||||
"generate_new_api_key_info" : "Ao cambiar o contrasinal xerarase unha nova chave API",
|
||||
"opml_export" : "Exportación de OPML",
|
||||
"delete_account" : "Eliminar conta",
|
||||
"delete_account_confirmation" : "Delete your acount? There's no turning back! "
|
||||
"delete_account_confirmation" : "Eliminar conta? Non hai volta atrás! "
|
||||
},
|
||||
"about" : {
|
||||
"rest_api" : {
|
||||
@@ -158,7 +158,7 @@
|
||||
"open_next_entry" : "abrir próxima entrada",
|
||||
"open_previous_entry" : "abrir entrada anterior",
|
||||
"spacebar" : "space/shift+space ",
|
||||
"move_page_down_up" : "moves the page down/up ",
|
||||
"move_page_down_up" : "move a páxina arriba/abaixo ",
|
||||
"focus_next_entry" : "Establecer o foco na próxima entrada sen abrila",
|
||||
"focus_previous_entry" : "Establecer o foco na entrada anterior sen abrila",
|
||||
"open_next_feed" : "abrir a seguinte fonte ou categoría",
|
||||
@@ -170,11 +170,11 @@
|
||||
"mark_current_entry" : "marcar como lida/non lida a entrada actual",
|
||||
"mark_all_as_read" : "marcar todas as entradas como lidas",
|
||||
"open_in_new_tab_mark_as_read" : "abrir entrada nunha nova lapela e marcar como lida",
|
||||
"fullscreen" : "toggle full screen mode ",
|
||||
"font_size" : "increase/decrease font size of the current entry ",
|
||||
"go_to_all" : "go to the All view ",
|
||||
"go_to_starred" : "go to the Starred view ",
|
||||
"fullscreen" : "habilita a pantalla completa ",
|
||||
"font_size" : "aumenta/diminúe o tamaño da letra da entrada activa ",
|
||||
"go_to_all" : "ir a vista TODOS",
|
||||
"go_to_starred" : "ir a vista Destacados ",
|
||||
"feed_search" : "navegue ate unha suscrición introducindo o nome da suscrición"
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -3,15 +3,15 @@
|
||||
"save" : "Salva",
|
||||
"cancel" : "Cancella",
|
||||
"delete" : "Elimina",
|
||||
"required" : "Required",
|
||||
"required" : "Richiesto",
|
||||
"download" : "Download",
|
||||
"link" : "Link",
|
||||
"bookmark" : "Segnalibro ",
|
||||
"bookmark" : "Segnalibro",
|
||||
"close" : "Chiudi",
|
||||
"tags" : "Tags "
|
||||
"tags" : "Etichette "
|
||||
},
|
||||
"tree" : {
|
||||
"subscribe" : "Iscriviti",
|
||||
"subscribe" : "Abbonati",
|
||||
"import" : "Importa",
|
||||
"new_category" : "Nuova categoria",
|
||||
"all" : "Tutto",
|
||||
@@ -19,19 +19,19 @@
|
||||
},
|
||||
"subscribe" : {
|
||||
"feed_url" : "Feed URL",
|
||||
"feed_name" : "Nome Feed",
|
||||
"feed_name" : "Nome feed",
|
||||
"category" : "Categoria"
|
||||
},
|
||||
"import" : {
|
||||
"google_reader_prefix" : "Importa i tuoi feed dal tuo ",
|
||||
"google_reader_prefix" : "Permettimi di importare i tuoi feed dal tuo ",
|
||||
"google_reader_suffix" : " account.",
|
||||
"google_download" : "Oppure, carica il tuo subscriptions.xml",
|
||||
"google_download_link" : "Scaricalo da qui",
|
||||
"google_download" : "Oppure, carica il tuo file subscriptions.xml.",
|
||||
"google_download_link" : "Scaricalo da qui.",
|
||||
"xml_file" : "OPML File"
|
||||
},
|
||||
"new_category" : {
|
||||
"name" : "Nome",
|
||||
"parent" : "Parent"
|
||||
"parent" : "Gruppo"
|
||||
},
|
||||
"toolbar" : {
|
||||
"unread" : "Non letti",
|
||||
@@ -39,12 +39,12 @@
|
||||
"previous_entry" : "Precedente",
|
||||
"next_entry" : "Successivo",
|
||||
"refresh" : "Ricarica",
|
||||
"refresh_all" : "Force refresh all my feeds ",
|
||||
"sort_by_asc_desc" : "Sort by date asc/desc",
|
||||
"titles_only" : "Solo titoli",
|
||||
"refresh_all" : "Forza l'aggiornamento di tutte i miei feed",
|
||||
"sort_by_asc_desc" : "Ordina per data ascendente/decrescente",
|
||||
"titles_only" : "Solo i titoli",
|
||||
"expanded_view" : "Espandi",
|
||||
"mark_all_as_read" : "Contrassegna tutto come già letto",
|
||||
"mark_all_older_12_hours" : "Items older than 12 hours ",
|
||||
"mark_all_as_read" : "Segna tutto come già letto",
|
||||
"mark_all_older_12_hours" : "Elementi più vecchi di 12 ore",
|
||||
"mark_all_older_day" : "Elementi più vecchi di un giorno",
|
||||
"mark_all_older_week" : "Elementi più vecchi di una settimana",
|
||||
"mark_all_older_two_weeks" : "Elementi più vecchi di due settimane",
|
||||
@@ -52,129 +52,131 @@
|
||||
"profile" : "Profilo",
|
||||
"admin" : "Admin",
|
||||
"about" : "Informazioni",
|
||||
"logout" : "Logout",
|
||||
"logout" : "Esci",
|
||||
"donate" : "Dona"
|
||||
},
|
||||
"view" : {
|
||||
"entry_source" : "from ",
|
||||
"entry_author" : "by ",
|
||||
"error_while_loading_feed" : "Si è verificato un errore, mentre caricavo il feed",
|
||||
"keep_unread" : "Mantiene come da leggere",
|
||||
"entry_source" : "da ",
|
||||
"entry_author" : "di ",
|
||||
"error_while_loading_feed" : "Si è verificato un errore durante il caricamento di questo feed",
|
||||
"keep_unread" : "Mantiene come non leggere",
|
||||
"no_unread_items" : "Non ci sono elementi da leggere.",
|
||||
"mark_up_to_here" : "Mark as read up to here ",
|
||||
"search_for" : "searching for: ",
|
||||
"no_search_results" : "No match found for the requested keywords "
|
||||
"mark_up_to_here" : "Segna come letto fino qui",
|
||||
"search_for" : "cercando: ",
|
||||
"no_search_results" : "Nessun risultato trovato per le parole chiave cercate"
|
||||
},
|
||||
"feedsearch" : {
|
||||
"hint" : "Type in a subscription... ",
|
||||
"help" : "Use the return key to select and arrow keys to navigate. ",
|
||||
"result_prefix" : "Le tue sottoscrizioni"
|
||||
"hint" : "Digita in una sottoscrizione... ",
|
||||
"help" : "Usa il tasto invio per selezionare e le frecce per navigare.",
|
||||
"result_prefix" : "Le tue sottoscrizioni:"
|
||||
},
|
||||
"settings" : {
|
||||
"general" : {
|
||||
"value" : "Generali",
|
||||
"language" : "Lingua",
|
||||
"language_contribute" : "Contribuisci con le traduzioni",
|
||||
"show_unread" : "Show feeds and categories with no unread entries",
|
||||
"social_buttons" : "Visualizza i social button",
|
||||
"scroll_marks" : "Marca come letto quando scorri"
|
||||
"language_contribute" : "Contribuisci nelle traduzioni",
|
||||
"show_unread" : "Mostra i feed e le categorie con elementi non letti",
|
||||
"social_buttons" : "Mostra i pulsanti social network di condivisione",
|
||||
"scroll_marks" : "In modalità estesa, segna come letto le voci quando scorri"
|
||||
},
|
||||
"appearance" : "Appearance ",
|
||||
"scroll_speed" : "Scrolling speed when navigating between entries (in milliseconds) ",
|
||||
"scroll_speed_help" : "set to 0 to disable ",
|
||||
"theme" : "Tema ",
|
||||
"submit_your_theme" : "Sottoponi il tuo tema ",
|
||||
"custom_css" : "Css modificato"
|
||||
"appearance" : "Aspetto",
|
||||
"scroll_speed" : "Velocità dello scorrimento durante la navigazione tra i feed (in millisecondi) ",
|
||||
"scroll_speed_help" : "Imposta 0 per disabilitare",
|
||||
"theme" : "Tema",
|
||||
"submit_your_theme" : "Proponi il tuo tema",
|
||||
"custom_css" : "CSS personalizzato"
|
||||
},
|
||||
"details" : {
|
||||
"feed_details" : "Dettagli feed",
|
||||
"url" : "URL",
|
||||
"website" : "Website ",
|
||||
"url" : "URL ",
|
||||
"website" : "Sito Web",
|
||||
"name" : "Nome",
|
||||
"category" : "Categoria",
|
||||
"position" : "Posizione ",
|
||||
"position" : "Posizione",
|
||||
"last_refresh" : "Ultimo aggiornamento",
|
||||
"message" : "Last refresh message ",
|
||||
"next_refresh" : "Next refresh ",
|
||||
"queued_for_refresh" : "In attesa per l'aggiornamento ",
|
||||
"feed_url" : "Feed URL",
|
||||
"generate_api_key_first" : "Generate an API key in your profile first.",
|
||||
"unsubscribe" : "Annulla l\"'\"iscrizione",
|
||||
"unsubscribe_confirmation" : "Are you sure you want to unsubscribe from this feed? ",
|
||||
"delete_category_confirmation" : "Are you sure you want to delete this category? ",
|
||||
"message" : "Ultimo messaggio di aggiornamento",
|
||||
"next_refresh" : "Prossimo aggiornamento",
|
||||
"queued_for_refresh" : "In attesa per l'aggiornamento",
|
||||
"feed_url" : "URL del feed ",
|
||||
"filtering_expression" : "Espressione del filtro",
|
||||
"filtering_expression_help" : "Se non è vuoto, una espressione viene misurata in 'true' o 'false'. Se falsa, i nuovi elementi di questo feed verranno segnati automaticamente come letti.\nLe variabili accettate sono 'title', 'content', 'url' 'author' e 'categories' e il loro contenuto è convertito in minuscolo per una facile confronto di stringhe.\Esempio: url.contains('youtube') o (author eq 'athou' and title.contains('github').\nLa sintassi completa è disponibile <a href='http://commons.apache.org/proper/commons-jexl/reference/syntax.html' target='_blank'>qui</a>.",
|
||||
"generate_api_key_first" : "Genera prima una chiave API nelle impostazioni del tuo profilo.",
|
||||
"unsubscribe" : "Annulla la sottoscrizione",
|
||||
"unsubscribe_confirmation" : "Sei sicuro di voler annullare la sottoscrizione da questo feed?",
|
||||
"delete_category_confirmation" : "Sei sicuro di voler eliminare questa categoria?",
|
||||
"category_details" : "Dettagli categoria",
|
||||
"tag_details" : "Tag details ",
|
||||
"parent_category" : "Parent category"
|
||||
"tag_details" : "Dettagli etichette ",
|
||||
"parent_category" : "Categoria principale"
|
||||
},
|
||||
"profile" : {
|
||||
"user_name" : "User name",
|
||||
"user_name" : "Nome utente",
|
||||
"email" : "E-mail",
|
||||
"change_password" : "Cambia password",
|
||||
"confirm_password" : "Conferma password",
|
||||
"minimum_6_chars" : "Minimo 6 caratteri",
|
||||
"passwords_do_not_match" : "Le password non corrispondono",
|
||||
"api_key" : "API key",
|
||||
"api_key_not_generated" : "Non generata ancora",
|
||||
"generate_new_api_key" : "Genera una nuova chiave API",
|
||||
"generate_new_api_key_info" : "Cambiando la password sarà generata una nuova chiave API",
|
||||
"api_key" : "chiave API",
|
||||
"api_key_not_generated" : "Non ancora generata",
|
||||
"generate_new_api_key" : "Genera una nuova chiave API ",
|
||||
"generate_new_api_key_info" : "Cambiando la password sarà generata una nuova chiave API ì",
|
||||
"opml_export" : "Esporta OPML",
|
||||
"delete_account" : "Elimina account",
|
||||
"delete_account_confirmation" : "Delete your acount? There's no turning back! "
|
||||
"delete_account" : "Elimina il profilo",
|
||||
"delete_account_confirmation" : "Eliminare il tuo profilo? Non si può tornare indietro!"
|
||||
},
|
||||
"about" : {
|
||||
"rest_api" : {
|
||||
"value" : "REST API",
|
||||
"line1" : "CommaFeed is built on top of JAX-RS and AngularJS. As such, a REST API is available.",
|
||||
"link_to_documentation" : "Link alla documentazione."
|
||||
"line1" : "CommaFeed è costruito sopra JAX-RS e AngularJS. Ed ovviamente, una REST API è disponibile.",
|
||||
"link_to_documentation" : "Collegamento alla documentazione."
|
||||
},
|
||||
"keyboard_shortcuts" : "Scorciatoie da tastiera",
|
||||
"version" : "CommaFeed version ",
|
||||
"line1_prefix" : "Commefeed è un progetto open-source. I codici sono hostati su ",
|
||||
"keyboard_shortcuts" : "Scorciatoie da tastiera",
|
||||
"version" : "Versione di CommaFeed",
|
||||
"line1_prefix" : "CommaFeed è un progetto open source. I codici sono ospitati su ",
|
||||
"line1_suffix" : ".",
|
||||
"line2_prefix" : "Se hai qualche problema, segnalalo sulla pagina del ",
|
||||
"line2_suffix" : " progetto.",
|
||||
"line3" : "Se ti piace il progetto, prendi in considerazione una donazione per supportare lo sviluppatore e contribuire a coprire i costi di mantenenimento di questo sito on-line.",
|
||||
"line4" : "Per chi preferisce Bitcoin, questo è l\"'\"indirizzo ",
|
||||
"line3" : "Se ti piace il progetto, considera una donazione per supportare lo sviluppatore ed a aiutare per coprire i costi di mantenenimento di questo sito online.",
|
||||
"line4" : "Se preferisci i Bitcoin, questo è l'indirizzo",
|
||||
"goodies" : {
|
||||
"value" : "Goodies",
|
||||
"android_app" : "Android app ",
|
||||
"subscribe_url" : "Subscribe URL ",
|
||||
"chrome_extension" : "Estenzione per Chrome ",
|
||||
"android_app" : "Applicazione Android",
|
||||
"subscribe_url" : "Sottoscrivi URL",
|
||||
"chrome_extension" : "Estensione per Chrome",
|
||||
"firefox_extension" : "Estensione per Firefox",
|
||||
"opera_extension" : "Estensione per Opera",
|
||||
"subscribe_bookmarklet" : "Add subscription bookmarklet (click) ",
|
||||
"subscribe_bookmarklet_asc" : "Oldest first ",
|
||||
"subscribe_bookmarklet_desc" : "Newest first ",
|
||||
"next_unread_bookmarklet" : "Next unread item bookmarklet (drag to bookmark bar) "
|
||||
"subscribe_bookmarklet" : "Aggiungi la sottoscrizione ai segnalibri (clicca)",
|
||||
"subscribe_bookmarklet_asc" : "I più vecchi prima",
|
||||
"subscribe_bookmarklet_desc" : "I più nuovi prima",
|
||||
"next_unread_bookmarklet" : "Prossimo elemento non letto nei segnalibri (trascinali nella barra dei segnalibri)"
|
||||
},
|
||||
"translation" : {
|
||||
"value" : "Traduzioni",
|
||||
"message" : "Abbiamo bisogno del tuo aiuto per tradurre CommaFeed.",
|
||||
"link" : "Vedi come aiutare con le traduzioni."
|
||||
"link" : "Vedi come aiutarci nella traduzioni."
|
||||
},
|
||||
"announcements" : "Annunci",
|
||||
"shortcuts" : {
|
||||
"mouse_middleclick" : "mouse middleclick",
|
||||
"open_next_entry" : "open next entry",
|
||||
"open_previous_entry" : "open previous entry",
|
||||
"spacebar" : "space/shift+space ",
|
||||
"move_page_down_up" : "moves the page down/up ",
|
||||
"focus_next_entry" : "set focus on next entry without opening it ",
|
||||
"focus_previous_entry" : "set focus on previous entry without opening it ",
|
||||
"open_next_feed" : "open next feed or category ",
|
||||
"open_previous_feed" : "open previous feed or category ",
|
||||
"open_close_current_entry" : "open/close current entry",
|
||||
"open_current_entry_in_new_window" : "open current entry in a new window",
|
||||
"open_current_entry_in_new_window_background" : "open current entry in a new window in the background ",
|
||||
"star_unstar" : "star/unstar current entry",
|
||||
"mark_current_entry" : "mark as read/unread current entry",
|
||||
"mark_all_as_read" : "mark all entries as read",
|
||||
"open_in_new_tab_mark_as_read" : "open entry in new tab and mark as read",
|
||||
"fullscreen" : "toggle full screen mode ",
|
||||
"font_size" : "increase/decrease font size of the current entry ",
|
||||
"go_to_all" : "go to the All view ",
|
||||
"go_to_starred" : "go to the Starred view ",
|
||||
"feed_search" : "navigate to a subscription by entering the subscription name "
|
||||
"mouse_middleclick" : "click centrale del mouse",
|
||||
"open_next_entry" : "apri l'elemento successivo",
|
||||
"open_previous_entry" : "apri l'elemento precedente",
|
||||
"spacebar" : "spazio/shift+spazio",
|
||||
"move_page_down_up" : "muovi la pagina sopra/sotto",
|
||||
"focus_next_entry" : "imposta il fuoco sull'elemento successivo senza aprirlo",
|
||||
"focus_previous_entry" : "imposta il fuoco sull'elemento precedente senza aprirlo",
|
||||
"open_next_feed" : "apri il feed successivo od una categoria",
|
||||
"open_previous_feed" : "apri il feed precedente od una categoria",
|
||||
"open_close_current_entry" : "apri/chiusi la categoria corrente",
|
||||
"open_current_entry_in_new_window" : "apri il corrente elemento in una nuova finestra",
|
||||
"open_current_entry_in_new_window_background" : "apri il corrente elemento in una nuova finestra in secondo piano",
|
||||
"star_unstar" : "segna/togli il segno all'elemento corrente",
|
||||
"mark_current_entry" : "segna come letto/non letto l'elemento corrente",
|
||||
"mark_all_as_read" : "segna come letti tutti gli elementi",
|
||||
"open_in_new_tab_mark_as_read" : "apri l'elemento in una nuova finestra e segnala come letta",
|
||||
"fullscreen" : "alterna la modalità a schermo intero",
|
||||
"font_size" : "aumenta/decrementa la grandezza del font dell'elemento corrente",
|
||||
"go_to_all" : "vai nella visione totale",
|
||||
"go_to_starred" : "vai nella visione dei preferiti",
|
||||
"feed_search" : "naviga in una sottoscrizione scrivendo il suo nome"
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -8,7 +8,7 @@
|
||||
"link" : "Bağlantı",
|
||||
"bookmark" : "Yer imi",
|
||||
"close" : "Kapat",
|
||||
"tags" : "Tags "
|
||||
"tags" : "Etiketler "
|
||||
},
|
||||
"tree" : {
|
||||
"subscribe" : "Abone ol",
|
||||
@@ -24,7 +24,7 @@
|
||||
},
|
||||
"import" : {
|
||||
"google_reader_prefix" : "Aboneliklerinizi ",
|
||||
"google_reader_suffix" : " hesabınızdan aktarmama izin verin.",
|
||||
"google_reader_suffix" : "Hesabınızdan aktarmama izin verin.",
|
||||
"google_download" : "Veya, subscriptions.xml dosyanızı yükleyin.",
|
||||
"google_download_link" : "Buradan indirebilirsiniz.",
|
||||
"xml_file" : "OPML dosyası"
|
||||
@@ -39,15 +39,15 @@
|
||||
"previous_entry" : "Önceki ileti",
|
||||
"next_entry" : "Sonraki ileti",
|
||||
"refresh" : "Yenile",
|
||||
"refresh_all" : "Force refresh all my feeds ",
|
||||
"refresh_all" : "Tüm yayınları yenilemek için zorla",
|
||||
"sort_by_asc_desc" : "Tarihe göre sırala artan/azalan",
|
||||
"titles_only" : "Sadece başlıklar",
|
||||
"expanded_view" : "Genişletilmiş görünüm",
|
||||
"mark_all_as_read" : "Tümünü okundu işaretle",
|
||||
"mark_all_older_12_hours" : "Items older than 12 hours ",
|
||||
"mark_all_older_day" : "Bir günden eski yazılar",
|
||||
"mark_all_older_week" : "Bir haftadan eski yazılar",
|
||||
"mark_all_older_two_weeks" : "İki haftadan eski yazılar",
|
||||
"mark_all_older_12_hours" : "12 saatten daha eski yayınlar ",
|
||||
"mark_all_older_day" : "Bir günden eski yayınlar",
|
||||
"mark_all_older_week" : "Bir haftadan eski yayınlar",
|
||||
"mark_all_older_two_weeks" : "İki haftadan eski yayınlar",
|
||||
"settings" : "Ayarlar",
|
||||
"profile" : "Profil",
|
||||
"admin" : "Yönetim",
|
||||
@@ -56,14 +56,14 @@
|
||||
"donate" : "Bağış"
|
||||
},
|
||||
"view" : {
|
||||
"entry_source" : "from ",
|
||||
"entry_author" : "by ",
|
||||
"entry_source" : "kaynak: ",
|
||||
"entry_author" : "yazar: ",
|
||||
"error_while_loading_feed" : "Bu aboneliği çekerken hata oluştu.",
|
||||
"keep_unread" : "Okunmadı olarak sakla",
|
||||
"no_unread_items" : "okunmamış ileti yok.",
|
||||
"mark_up_to_here" : "Mark as read up to here ",
|
||||
"no_unread_items" : "Okunmamış ileti yok.",
|
||||
"mark_up_to_here" : "Buraya kadar olan bütün yayınları okundu olarak işaretle!",
|
||||
"search_for" : "searching for: ",
|
||||
"no_search_results" : "No match found for the requested keywords "
|
||||
"no_search_results" : "İstenen anahtar kelimeler için eşleşme bulunamadı"
|
||||
},
|
||||
"feedsearch" : {
|
||||
"hint" : "Bir abonelik yazın...",
|
||||
@@ -80,8 +80,8 @@
|
||||
"scroll_marks" : "Genişletilmiş görünümde götüntülenen iletileri okunmuş işaretle"
|
||||
},
|
||||
"appearance" : "Görünüm",
|
||||
"scroll_speed" : "Scrolling speed when navigating between entries (in milliseconds) ",
|
||||
"scroll_speed_help" : "set to 0 to disable ",
|
||||
"scroll_speed" : "İçerikler arasında gezinirken kaydırma hızı (milisaniye cinsinden)",
|
||||
"scroll_speed_help" : "ayarı kapatmak için 0 yazınız",
|
||||
"theme" : "Tema",
|
||||
"submit_your_theme" : "Tema gönder",
|
||||
"custom_css" : "Kişiselleştirilmiş CSS"
|
||||
@@ -100,10 +100,10 @@
|
||||
"feed_url" : "Yayın URL'si",
|
||||
"generate_api_key_first" : "Öncelikle profilinizden bir API anahtarı oluşturun.",
|
||||
"unsubscribe" : "Aboneliği iptal et",
|
||||
"unsubscribe_confirmation" : "Are you sure you want to unsubscribe from this feed? ",
|
||||
"delete_category_confirmation" : "Are you sure you want to delete this category? ",
|
||||
"unsubscribe_confirmation" : "Bu yayından çıkmak istediğinizden emin misiniz? ",
|
||||
"delete_category_confirmation" : "Bu kategoriyi silmek istediğinizden emin misiniz? ",
|
||||
"category_details" : "Kategori detayları",
|
||||
"tag_details" : "Tag details ",
|
||||
"tag_details" : "Etiket detayları ",
|
||||
"parent_category" : "Üst kategori"
|
||||
},
|
||||
"profile" : {
|
||||
@@ -116,10 +116,10 @@
|
||||
"api_key" : "API anahtarı",
|
||||
"api_key_not_generated" : "Henüz oluşturulmadı",
|
||||
"generate_new_api_key" : "Yeni bir API anahtarı oluştur",
|
||||
"generate_new_api_key_info" : "Şifre değiştirmek API anahtarının da değiştirilmesine neden olcak.",
|
||||
"generate_new_api_key_info" : "Şifreyi değiştirmek API anahtarının da değiştirilmesine neden olcak.",
|
||||
"opml_export" : "OPML dışa aktar",
|
||||
"delete_account" : "Hesabı sil",
|
||||
"delete_account_confirmation" : "Delete your acount? There's no turning back! "
|
||||
"delete_account_confirmation" : "Hesabı silmek istediğinize emin misiniz? Bu işlemde geri dönüş yoktur! "
|
||||
},
|
||||
"about" : {
|
||||
"rest_api" : {
|
||||
@@ -128,24 +128,24 @@
|
||||
"link_to_documentation" : "Dökümantasyon için tıklayın."
|
||||
},
|
||||
"keyboard_shortcuts" : "Klavye kısayolları",
|
||||
"version" : "CommaFeed version ",
|
||||
"version" : "CommaFeed versiyon ",
|
||||
"line1_prefix" : "CommaFeed bir açık kaynak projedir. Kaynak dosyaları ",
|
||||
"line1_suffix" : " adresinde yayınlanır.",
|
||||
"line1_suffix" : "adresinde yayınlanır.",
|
||||
"line2_prefix" : "Lütfen, bir hata ile karşılaşırsanız bunu ",
|
||||
"line2_suffix" : " projesinde hatalar sayfasından rapor edin.",
|
||||
"line2_suffix" : "projesinde hatalar sayfasından rapor edin.",
|
||||
"line3" : "Eğer bu projeyi beğendiyseniz, lütfen bağış yaparak geliştiriciye bu sayfayı ayakta tutmasında yardımcı olun.",
|
||||
"line4" : "Bitcoin'i tercih edenler için adres ",
|
||||
"goodies" : {
|
||||
"value" : "Extralar",
|
||||
"android_app" : "Android app ",
|
||||
"value" : "Ekstralar",
|
||||
"android_app" : "Android eklentisi",
|
||||
"subscribe_url" : "Abonelik URL'si",
|
||||
"chrome_extension" : "Chrome eklentisi",
|
||||
"firefox_extension" : "Firefox eklentisi",
|
||||
"opera_extension" : "Opera eklentisi",
|
||||
"subscribe_bookmarklet" : "Bookmarklet'a abonelik ekle (tıklayın)",
|
||||
"subscribe_bookmarklet_asc" : "Oldest first ",
|
||||
"subscribe_bookmarklet_desc" : "Newest first ",
|
||||
"next_unread_bookmarklet" : "Bookmarklet'daki en son okunmamış ileti (Sık kullanılan çubuğuna sürükleyin)"
|
||||
"subscribe_bookmarklet" : "Yer imilerine abonelik ekle (tıklayın)",
|
||||
"subscribe_bookmarklet_asc" : "Eskiler önce",
|
||||
"subscribe_bookmarklet_desc" : "Yeniler önce ",
|
||||
"next_unread_bookmarklet" : "Yer imilerindeki en son okunmamış ileti (Sık kullanılan çubuğuna sürükleyin)"
|
||||
},
|
||||
"translation" : {
|
||||
"value" : "Çeviri",
|
||||
@@ -158,7 +158,7 @@
|
||||
"open_next_entry" : "sonraki öğeyi görüntüle",
|
||||
"open_previous_entry" : "önceki öğeyi görüntüle",
|
||||
"spacebar" : "space/shift+space ",
|
||||
"move_page_down_up" : "moves the page down/up ",
|
||||
"move_page_down_up" : "sayfayı aşağı/yukarı hareket ettir",
|
||||
"focus_next_entry" : "sonraki öğeyi görüntülemeden işaretle",
|
||||
"focus_previous_entry" : "önceki öğeyi görüntülemeden işaretle",
|
||||
"open_next_feed" : "sonraki aboneliği veya kategoriyi görüntüle",
|
||||
@@ -170,11 +170,11 @@
|
||||
"mark_current_entry" : "görüntülenen öğeyi okundu/okunmadı işaretle",
|
||||
"mark_all_as_read" : "tümünü okundu işaretle",
|
||||
"open_in_new_tab_mark_as_read" : "öğeyi yeni bir sekmede aç ve okundu işaretle",
|
||||
"fullscreen" : "toggle full screen mode ",
|
||||
"font_size" : "increase/decrease font size of the current entry ",
|
||||
"go_to_all" : "go to the All view ",
|
||||
"go_to_starred" : "go to the Starred view ",
|
||||
"fullscreen" : "tam ekran moduna geç ",
|
||||
"font_size" : "mevcut içerik için yazı boyunutunu arttır/azalt",
|
||||
"go_to_all" : "Tüm öğeleri görüntüle",
|
||||
"go_to_starred" : "yıldızlı öğerleri görüntüle",
|
||||
"feed_search" : "abonelik ismini yazarak aboneliğe git"
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -4,6 +4,10 @@
|
||||
<title>CommaFeed</title>
|
||||
<meta charset="utf-8">
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0, maximum-scale=1.0, user-scalable=no" />
|
||||
<meta name="apple-mobile-web-app-capable" content="yes">
|
||||
<meta name="apple-mobile-web-app-status-bar-style" content="black-translucent">
|
||||
<meta name="mobile-web-app-capable" content="yes">
|
||||
<link rel="manifest" href="manifest.json">
|
||||
<link rel="shortcut icon" type="image/x-icon" href="favicon.ico" />
|
||||
<link rel="apple-touch-icon" href="app-icon-57.png" />
|
||||
<link rel="apple-touch-icon" sizes="72x72" href="app-icon-72.png" />
|
||||
@@ -41,7 +45,7 @@
|
||||
</div>
|
||||
|
||||
<!-- build:js js/app.js -->
|
||||
<script type="text/javascript" src="lib/lodash/dist/lodash.js"></script>
|
||||
<script type="text/javascript" src="lib/lodash/lodash.js"></script>
|
||||
<script type="text/javascript" src="lib/jquery/dist/jquery.js"></script>
|
||||
<script type="text/javascript" src="lib/jquery-ui/ui/jquery-ui.js"></script>
|
||||
<script type="text/javascript" src="lib/jquery-mousewheel/jquery.mousewheel.js"></script>
|
||||
|
||||
@@ -308,7 +308,8 @@ module.directive('droppable', ['CategoryService', 'FeedService', function(Catego
|
||||
|
||||
var data = {
|
||||
id : source.id,
|
||||
name : source.name
|
||||
name : source.name,
|
||||
filter : source.filter
|
||||
};
|
||||
|
||||
if (source.children) {
|
||||
|
||||
31
src/main/app/manifest.json
Normal file
31
src/main/app/manifest.json
Normal file
@@ -0,0 +1,31 @@
|
||||
{
|
||||
"name": "CommaFeed",
|
||||
"icons": [
|
||||
{
|
||||
"src": "app-icon-72.png",
|
||||
"sizes": "72x72",
|
||||
"type": "image/png",
|
||||
"density": "1.5"
|
||||
},
|
||||
{
|
||||
"src": "app-icon-114.png",
|
||||
"sizes": "96x96",
|
||||
"type": "image/png",
|
||||
"density": "2.0"
|
||||
},
|
||||
{
|
||||
"src": "app-icon-144.png",
|
||||
"sizes": "144x144",
|
||||
"type": "image/png",
|
||||
"density": "3.0"
|
||||
},
|
||||
{
|
||||
"src": "app-icon-192.png",
|
||||
"sizes": "192x192",
|
||||
"type": "image/png",
|
||||
"density": "4.0"
|
||||
}
|
||||
],
|
||||
"start_url": "/",
|
||||
"display": "standalone"
|
||||
}
|
||||
@@ -51,6 +51,9 @@
|
||||
<span class="entry-author-prefix">{{ 'view.entry_author' | translate }}</span>
|
||||
<span class="entry-author-name">{{entry.author}}</span>
|
||||
</span>
|
||||
<span class="entry-categories" ng-if="entry.categories">
|
||||
<span class="entry-categories-name">({{entry.categories}})</span>
|
||||
</span>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@@ -3,6 +3,7 @@ package com.commafeed;
|
||||
import io.dropwizard.Application;
|
||||
import io.dropwizard.assets.AssetsBundle;
|
||||
import io.dropwizard.db.DataSourceFactory;
|
||||
import io.dropwizard.forms.MultiPartBundle;
|
||||
import io.dropwizard.hibernate.HibernateBundle;
|
||||
import io.dropwizard.migrations.MigrationsBundle;
|
||||
import io.dropwizard.server.DefaultServerFactory;
|
||||
@@ -11,8 +12,10 @@ import io.dropwizard.setup.Bootstrap;
|
||||
import io.dropwizard.setup.Environment;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.util.Arrays;
|
||||
import java.util.Date;
|
||||
import java.util.EnumSet;
|
||||
import java.util.List;
|
||||
import java.util.Set;
|
||||
import java.util.concurrent.ScheduledExecutorService;
|
||||
|
||||
@@ -23,8 +26,8 @@ import javax.servlet.ServletRequest;
|
||||
import javax.servlet.ServletResponse;
|
||||
import javax.servlet.http.HttpServletRequest;
|
||||
|
||||
import org.apache.commons.lang3.StringUtils;
|
||||
import org.eclipse.jetty.server.session.SessionHandler;
|
||||
import org.glassfish.jersey.media.multipart.MultiPartFeature;
|
||||
import org.hibernate.cfg.AvailableSettings;
|
||||
|
||||
import com.commafeed.backend.feed.FeedRefreshTaskGiver;
|
||||
@@ -57,19 +60,13 @@ import com.commafeed.frontend.servlet.CustomCssServlet;
|
||||
import com.commafeed.frontend.servlet.LogoutServlet;
|
||||
import com.commafeed.frontend.servlet.NextUnreadServlet;
|
||||
import com.commafeed.frontend.session.SessionHelperFactoryProvider;
|
||||
import com.fasterxml.jackson.annotation.JsonInclude;
|
||||
import com.google.inject.Guice;
|
||||
import com.google.inject.Injector;
|
||||
import com.google.inject.Key;
|
||||
import com.google.inject.TypeLiteral;
|
||||
import com.wordnik.swagger.config.ConfigFactory;
|
||||
import com.wordnik.swagger.config.ScannerFactory;
|
||||
import com.wordnik.swagger.config.SwaggerConfig;
|
||||
import com.wordnik.swagger.jaxrs.config.DefaultJaxrsScanner;
|
||||
import com.wordnik.swagger.jaxrs.listing.ApiDeclarationProvider;
|
||||
import com.wordnik.swagger.jaxrs.listing.ApiListingResourceJSON;
|
||||
import com.wordnik.swagger.jaxrs.listing.ResourceListingProvider;
|
||||
import com.wordnik.swagger.jaxrs.reader.DefaultJaxrsApiReader;
|
||||
import com.wordnik.swagger.reader.ClassReaders;
|
||||
import com.wordnik.swagger.jaxrs.config.BeanConfig;
|
||||
import com.wordnik.swagger.jaxrs.listing.ApiListingResource;
|
||||
|
||||
public class CommaFeedApplication extends Application<CommaFeedConfiguration> {
|
||||
|
||||
@@ -96,6 +93,9 @@ public class CommaFeedApplication extends Application<CommaFeedConfiguration> {
|
||||
|
||||
// keep using old id generator for backward compatibility
|
||||
factory.getProperties().put(AvailableSettings.USE_NEW_ID_GENERATOR_MAPPINGS, "false");
|
||||
|
||||
factory.getProperties().put(AvailableSettings.STATEMENT_BATCH_SIZE, "50");
|
||||
factory.getProperties().put(AvailableSettings.BATCH_VERSIONED_DATA, "true");
|
||||
return factory;
|
||||
}
|
||||
});
|
||||
@@ -108,6 +108,7 @@ public class CommaFeedApplication extends Application<CommaFeedConfiguration> {
|
||||
});
|
||||
|
||||
bootstrap.addBundle(new AssetsBundle("/assets/", "/", "index.html"));
|
||||
bootstrap.addBundle(new MultiPartBundle());
|
||||
}
|
||||
|
||||
@Override
|
||||
@@ -134,9 +135,6 @@ public class CommaFeedApplication extends Application<CommaFeedConfiguration> {
|
||||
environment.jersey().register(injector.getInstance(ServerREST.class));
|
||||
environment.jersey().register(injector.getInstance(UserREST.class));
|
||||
|
||||
// @FormDataParam support
|
||||
environment.jersey().register(MultiPartFeature.class);
|
||||
|
||||
// Servlets
|
||||
environment.servlets().addServlet("next", injector.getInstance(NextUnreadServlet.class)).addMapping("/next");
|
||||
environment.servlets().addServlet("logout", injector.getInstance(LogoutServlet.class)).addMapping("/logout");
|
||||
@@ -161,14 +159,19 @@ public class CommaFeedApplication extends Application<CommaFeedConfiguration> {
|
||||
environment.lifecycle().manage(injector.getInstance(FeedRefreshUpdater.class));
|
||||
|
||||
// Swagger
|
||||
environment.jersey().register(new ApiListingResourceJSON());
|
||||
environment.jersey().register(new ApiDeclarationProvider());
|
||||
environment.jersey().register(new ResourceListingProvider());
|
||||
ScannerFactory.setScanner(new DefaultJaxrsScanner());
|
||||
ClassReaders.setReader(new DefaultJaxrsApiReader());
|
||||
SwaggerConfig swaggerConfig = ConfigFactory.config();
|
||||
swaggerConfig.setApiVersion("1");
|
||||
environment.jersey().register(new ApiListingResource());
|
||||
environment.getObjectMapper().setSerializationInclusion(JsonInclude.Include.NON_NULL);
|
||||
|
||||
String modelsPackage = "com.commafeed.frontend.model";
|
||||
String requestsPackage = "com.commafeed.frontend.model.request";
|
||||
String endpointsPackage = "com.commafeed.frontend.resource";
|
||||
List<String> packages = Arrays.asList(modelsPackage, requestsPackage, endpointsPackage);
|
||||
BeanConfig swaggerConfig = new BeanConfig();
|
||||
swaggerConfig.setTitle("CommaFeed");
|
||||
swaggerConfig.setVersion("1");
|
||||
swaggerConfig.setBasePath("/rest");
|
||||
swaggerConfig.setResourcePackage(StringUtils.join(packages, ","));
|
||||
swaggerConfig.setScan(true);
|
||||
|
||||
// cache configuration
|
||||
// prevent caching on REST resources, except for favicons
|
||||
|
||||
@@ -77,6 +77,8 @@ public class CommaFeedConfiguration extends Configuration {
|
||||
|
||||
private String googleAnalyticsTrackingCode;
|
||||
|
||||
private String googleAuthKey;
|
||||
|
||||
@NotNull
|
||||
@Min(1)
|
||||
@Valid
|
||||
|
||||
@@ -34,7 +34,7 @@ import org.apache.http.client.methods.HttpGet;
|
||||
import org.apache.http.client.methods.HttpUriRequest;
|
||||
import org.apache.http.client.protocol.HttpClientContext;
|
||||
import org.apache.http.config.ConnectionConfig;
|
||||
import org.apache.http.conn.ssl.SSLConnectionSocketFactory;
|
||||
import org.apache.http.conn.ssl.NoopHostnameVerifier;
|
||||
import org.apache.http.impl.client.CloseableHttpClient;
|
||||
import org.apache.http.impl.client.HttpClientBuilder;
|
||||
import org.apache.http.impl.client.HttpClients;
|
||||
@@ -58,9 +58,6 @@ public class HttpGetter {
|
||||
|
||||
private static SSLContext SSL_CONTEXT = null;
|
||||
static {
|
||||
// fix for "handshake alert: unrecognized_name"
|
||||
System.setProperty("jsse.enableSNIExtension", "false");
|
||||
|
||||
try {
|
||||
SSL_CONTEXT = SSLContext.getInstance("TLS");
|
||||
SSL_CONTEXT.init(new KeyManager[0], new TrustManager[] { new DefaultTrustManager() }, new SecureRandom());
|
||||
@@ -184,8 +181,8 @@ public class HttpGetter {
|
||||
builder.addInterceptorFirst(REMOVE_INCORRECT_CONTENT_ENCODING);
|
||||
builder.disableAutomaticRetries();
|
||||
|
||||
builder.setSslcontext(SSL_CONTEXT);
|
||||
builder.setHostnameVerifier(SSLConnectionSocketFactory.ALLOW_ALL_HOSTNAME_VERIFIER);
|
||||
builder.setSSLContext(SSL_CONTEXT);
|
||||
builder.setSSLHostnameVerifier(NoopHostnameVerifier.INSTANCE);
|
||||
|
||||
RequestConfig.Builder configBuilder = RequestConfig.custom();
|
||||
configBuilder.setCookieSpec(CookieSpecs.IGNORE_COOKIES);
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
package com.commafeed.backend.cache;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
import java.util.Set;
|
||||
import java.util.concurrent.TimeUnit;
|
||||
@@ -18,7 +19,6 @@ import com.commafeed.frontend.model.Category;
|
||||
import com.commafeed.frontend.model.UnreadCount;
|
||||
import com.fasterxml.jackson.core.JsonProcessingException;
|
||||
import com.fasterxml.jackson.databind.ObjectMapper;
|
||||
import com.google.common.collect.Lists;
|
||||
|
||||
@Slf4j
|
||||
@RequiredArgsConstructor
|
||||
@@ -30,7 +30,7 @@ public class RedisCacheService extends CacheService {
|
||||
|
||||
@Override
|
||||
public List<String> getLastEntries(Feed feed) {
|
||||
List<String> list = Lists.newArrayList();
|
||||
List<String> list = new ArrayList<>();
|
||||
try (Jedis jedis = pool.getResource()) {
|
||||
String key = buildRedisEntryKey(feed);
|
||||
Set<String> members = jedis.smembers(key);
|
||||
|
||||
@@ -2,6 +2,7 @@ package com.commafeed.backend.dao;
|
||||
|
||||
import java.util.List;
|
||||
import java.util.Objects;
|
||||
import java.util.stream.Collectors;
|
||||
|
||||
import javax.inject.Inject;
|
||||
import javax.inject.Singleton;
|
||||
@@ -12,7 +13,6 @@ import com.commafeed.backend.model.FeedCategory;
|
||||
import com.commafeed.backend.model.QFeedCategory;
|
||||
import com.commafeed.backend.model.QUser;
|
||||
import com.commafeed.backend.model.User;
|
||||
import com.google.common.collect.Lists;
|
||||
import com.mysema.query.types.Predicate;
|
||||
|
||||
@Singleton
|
||||
@@ -54,14 +54,7 @@ public class FeedCategoryDAO extends GenericDAO<FeedCategory> {
|
||||
}
|
||||
|
||||
public List<FeedCategory> findAllChildrenCategories(User user, FeedCategory parent) {
|
||||
List<FeedCategory> list = Lists.newArrayList();
|
||||
List<FeedCategory> all = findAll(user);
|
||||
for (FeedCategory cat : all) {
|
||||
if (isChild(cat, parent)) {
|
||||
list.add(cat);
|
||||
}
|
||||
}
|
||||
return list;
|
||||
return findAll(user).stream().filter(c -> isChild(c, parent)).collect(Collectors.toList());
|
||||
}
|
||||
|
||||
private boolean isChild(FeedCategory child, FeedCategory parent) {
|
||||
|
||||
@@ -17,6 +17,7 @@ import com.commafeed.backend.model.QUser;
|
||||
import com.google.common.collect.Iterables;
|
||||
import com.mysema.query.BooleanBuilder;
|
||||
import com.mysema.query.jpa.hibernate.HibernateQuery;
|
||||
import com.mysema.query.jpa.hibernate.HibernateSubQuery;
|
||||
|
||||
@Singleton
|
||||
public class FeedDAO extends GenericDAO<Feed> {
|
||||
@@ -33,12 +34,14 @@ public class FeedDAO extends GenericDAO<Feed> {
|
||||
disabledDatePredicate.or(feed.disabledUntil.isNull());
|
||||
disabledDatePredicate.or(feed.disabledUntil.lt(new Date()));
|
||||
|
||||
HibernateQuery query = newQuery().from(feed);
|
||||
HibernateQuery query = null;
|
||||
if (lastLoginThreshold != null) {
|
||||
QFeedSubscription subs = QFeedSubscription.feedSubscription;
|
||||
QUser user = QUser.user;
|
||||
query.join(feed.subscriptions, subs).join(subs.user, user).where(disabledDatePredicate, user.lastLogin.gt(lastLoginThreshold));
|
||||
query = newQuery().from(subs);
|
||||
query.join(subs.feed, feed).join(subs.user, user).where(disabledDatePredicate, user.lastLogin.gt(lastLoginThreshold));
|
||||
} else {
|
||||
query = newQuery().from(feed);
|
||||
query.where(disabledDatePredicate);
|
||||
}
|
||||
|
||||
@@ -60,6 +63,7 @@ public class FeedDAO extends GenericDAO<Feed> {
|
||||
|
||||
public List<Feed> findWithoutSubscriptions(int max) {
|
||||
QFeedSubscription sub = QFeedSubscription.feedSubscription;
|
||||
return newQuery().from(feed).leftJoin(feed.subscriptions, sub).where(sub.id.isNull()).limit(max).list(feed);
|
||||
return newQuery().from(feed).where(new HibernateSubQuery().from(sub).where(sub.feed.eq(feed)).notExists()).limit(max).list(feed);
|
||||
// return newQuery().from(feed).leftJoin(feed.subscriptions, sub).where(sub.id.isNull()).limit(max).list(feed);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -11,6 +11,7 @@ import com.commafeed.backend.model.FeedEntryContent;
|
||||
import com.commafeed.backend.model.QFeedEntry;
|
||||
import com.commafeed.backend.model.QFeedEntryContent;
|
||||
import com.google.common.collect.Iterables;
|
||||
import com.mysema.query.jpa.hibernate.HibernateSubQuery;
|
||||
|
||||
@Singleton
|
||||
public class FeedEntryContentDAO extends GenericDAO<FeedEntryContent> {
|
||||
@@ -30,8 +31,10 @@ public class FeedEntryContentDAO extends GenericDAO<FeedEntryContent> {
|
||||
|
||||
public int deleteWithoutEntries(int max) {
|
||||
QFeedEntry entry = QFeedEntry.feedEntry;
|
||||
List<FeedEntryContent> list = newQuery().from(content).leftJoin(content.entries, entry).where(entry.id.isNull()).limit(max)
|
||||
.list(content);
|
||||
|
||||
HibernateSubQuery subQuery = new HibernateSubQuery().from(entry).where(entry.content.id.eq(content.id));
|
||||
List<FeedEntryContent> list = newQuery().from(content).where(subQuery.notExists()).limit(max).list(content);
|
||||
|
||||
int deleted = list.size();
|
||||
delete(list);
|
||||
return deleted;
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
package com.commafeed.backend.dao;
|
||||
|
||||
import java.util.List;
|
||||
import java.util.stream.Collectors;
|
||||
|
||||
import javax.inject.Inject;
|
||||
import javax.inject.Singleton;
|
||||
@@ -15,7 +16,6 @@ import com.commafeed.backend.model.Feed;
|
||||
import com.commafeed.backend.model.FeedEntry;
|
||||
import com.commafeed.backend.model.QFeedEntry;
|
||||
import com.google.common.collect.Iterables;
|
||||
import com.google.common.collect.Lists;
|
||||
import com.mysema.query.Tuple;
|
||||
import com.mysema.query.types.expr.NumberExpression;
|
||||
|
||||
@@ -36,20 +36,19 @@ public class FeedEntryDAO extends GenericDAO<FeedEntry> {
|
||||
}
|
||||
|
||||
public List<FeedCapacity> findFeedsExceedingCapacity(long maxCapacity, long max) {
|
||||
List<FeedCapacity> list = Lists.newArrayList();
|
||||
NumberExpression<Long> count = entry.id.countDistinct();
|
||||
NumberExpression<Long> count = entry.id.count();
|
||||
List<Tuple> tuples = newQuery().from(entry).groupBy(entry.feed).having(count.gt(maxCapacity)).limit(max).list(entry.feed.id, count);
|
||||
for (Tuple tuple : tuples) {
|
||||
list.add(new FeedCapacity(tuple.get(entry.feed.id), tuple.get(count)));
|
||||
}
|
||||
return list;
|
||||
return tuples.stream().map(t -> new FeedCapacity(t.get(entry.feed.id), t.get(count))).collect(Collectors.toList());
|
||||
}
|
||||
|
||||
public int delete(Long feedId, long max) {
|
||||
List<FeedEntry> list = newQuery().from(entry).where(entry.feed.id.eq(feedId)).limit(max).list(entry);
|
||||
return delete(list);
|
||||
}
|
||||
|
||||
public int deleteOldEntries(Long feedId, long max) {
|
||||
List<FeedEntry> list = newQuery().from(entry).where(entry.feed.id.eq(feedId)).orderBy(entry.updated.asc()).limit(max).list(entry);
|
||||
int deleted = list.size();
|
||||
delete(list);
|
||||
return deleted;
|
||||
return delete(list);
|
||||
}
|
||||
|
||||
@AllArgsConstructor
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
package com.commafeed.backend.dao;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.Comparator;
|
||||
import java.util.Date;
|
||||
import java.util.List;
|
||||
@@ -7,6 +8,7 @@ import java.util.List;
|
||||
import javax.inject.Inject;
|
||||
import javax.inject.Singleton;
|
||||
|
||||
import org.apache.commons.collections4.CollectionUtils;
|
||||
import org.apache.commons.lang3.builder.CompareToBuilder;
|
||||
import org.hibernate.SessionFactory;
|
||||
|
||||
@@ -27,7 +29,6 @@ import com.commafeed.backend.model.User;
|
||||
import com.commafeed.backend.model.UserSettings.ReadingOrder;
|
||||
import com.commafeed.frontend.model.UnreadCount;
|
||||
import com.google.common.collect.Iterables;
|
||||
import com.google.common.collect.Lists;
|
||||
import com.google.common.collect.Ordering;
|
||||
import com.mysema.query.BooleanBuilder;
|
||||
import com.mysema.query.Tuple;
|
||||
@@ -103,7 +104,11 @@ public class FeedEntryStatusDAO extends GenericDAO<FeedEntryStatus> {
|
||||
query.orderBy(status.entryUpdated.desc(), status.id.desc());
|
||||
}
|
||||
|
||||
query.offset(offset).limit(limit).setTimeout(config.getApplicationSettings().getQueryTimeout());
|
||||
query.offset(offset).limit(limit);
|
||||
int timeout = config.getApplicationSettings().getQueryTimeout();
|
||||
if (timeout > 0) {
|
||||
query.setTimeout(timeout / 1000);
|
||||
}
|
||||
|
||||
List<FeedEntryStatus> statuses = query.list(status);
|
||||
for (FeedEntryStatus status : statuses) {
|
||||
@@ -118,7 +123,7 @@ public class FeedEntryStatusDAO extends GenericDAO<FeedEntryStatus> {
|
||||
|
||||
HibernateQuery query = newQuery().from(entry).where(entry.feed.eq(sub.getFeed()));
|
||||
|
||||
if (keywords != null) {
|
||||
if (CollectionUtils.isNotEmpty(keywords)) {
|
||||
query.join(entry.content, content);
|
||||
|
||||
for (FeedEntryKeyword keyword : keywords) {
|
||||
@@ -216,7 +221,7 @@ public class FeedEntryStatusDAO extends GenericDAO<FeedEntryStatus> {
|
||||
List<FeedEntryStatus> placeholders = set.asList();
|
||||
int size = placeholders.size();
|
||||
if (size < offset) {
|
||||
return Lists.newArrayList();
|
||||
return new ArrayList<>();
|
||||
}
|
||||
placeholders = placeholders.subList(Math.max(offset, 0), size);
|
||||
|
||||
@@ -224,7 +229,7 @@ public class FeedEntryStatusDAO extends GenericDAO<FeedEntryStatus> {
|
||||
if (onlyIds) {
|
||||
statuses = placeholders;
|
||||
} else {
|
||||
statuses = Lists.newArrayList();
|
||||
statuses = new ArrayList<>();
|
||||
for (FeedEntryStatus placeholder : placeholders) {
|
||||
Long statusId = placeholder.getId();
|
||||
FeedEntry entry = feedEntryDAO.findById(placeholder.getEntry().getId());
|
||||
|
||||
@@ -1,6 +1,8 @@
|
||||
package com.commafeed.backend.dao;
|
||||
|
||||
import java.util.List;
|
||||
import java.util.Set;
|
||||
import java.util.stream.Collectors;
|
||||
|
||||
import javax.inject.Inject;
|
||||
import javax.inject.Singleton;
|
||||
@@ -13,9 +15,7 @@ import com.commafeed.backend.model.FeedSubscription;
|
||||
import com.commafeed.backend.model.Models;
|
||||
import com.commafeed.backend.model.QFeedSubscription;
|
||||
import com.commafeed.backend.model.User;
|
||||
import com.google.common.base.Function;
|
||||
import com.google.common.collect.Iterables;
|
||||
import com.google.common.collect.Lists;
|
||||
import com.mysema.query.jpa.hibernate.HibernateQuery;
|
||||
|
||||
@Singleton
|
||||
@@ -60,26 +60,13 @@ public class FeedSubscriptionDAO extends GenericDAO<FeedSubscription> {
|
||||
}
|
||||
|
||||
public List<FeedSubscription> findByCategories(User user, List<FeedCategory> categories) {
|
||||
List<Long> categoryIds = Lists.transform(categories, new Function<FeedCategory, Long>() {
|
||||
@Override
|
||||
public Long apply(FeedCategory input) {
|
||||
return input.getId();
|
||||
}
|
||||
});
|
||||
|
||||
List<FeedSubscription> subscriptions = Lists.newArrayList();
|
||||
for (FeedSubscription sub : findAll(user)) {
|
||||
if (sub.getCategory() != null && categoryIds.contains(sub.getCategory().getId())) {
|
||||
subscriptions.add(sub);
|
||||
}
|
||||
}
|
||||
return subscriptions;
|
||||
Set<Long> categoryIds = categories.stream().map(c -> c.getId()).collect(Collectors.toSet());
|
||||
return findAll(user).stream().filter(s -> s.getCategory() != null && categoryIds.contains(s.getCategory().getId()))
|
||||
.collect(Collectors.toList());
|
||||
}
|
||||
|
||||
private List<FeedSubscription> initRelations(List<FeedSubscription> list) {
|
||||
for (FeedSubscription sub : list) {
|
||||
initRelations(sub);
|
||||
}
|
||||
list.forEach(s -> initRelations(s));
|
||||
return list;
|
||||
}
|
||||
|
||||
|
||||
@@ -24,19 +24,7 @@ public abstract class GenericDAO<T extends AbstractModel> extends AbstractDAO<T>
|
||||
}
|
||||
|
||||
public void saveOrUpdate(Collection<T> models) {
|
||||
for (T model : models) {
|
||||
persist(model);
|
||||
}
|
||||
}
|
||||
|
||||
public void merge(T model) {
|
||||
currentSession().merge(model);
|
||||
}
|
||||
|
||||
public void merge(Collection<T> models) {
|
||||
for (T model : models) {
|
||||
merge(model);
|
||||
}
|
||||
models.forEach(m -> persist(m));
|
||||
}
|
||||
|
||||
public T findById(Long id) {
|
||||
@@ -50,9 +38,7 @@ public abstract class GenericDAO<T extends AbstractModel> extends AbstractDAO<T>
|
||||
}
|
||||
|
||||
public int delete(Collection<T> objects) {
|
||||
for (T object : objects) {
|
||||
delete(object);
|
||||
}
|
||||
objects.forEach(o -> delete(o));
|
||||
return objects.size();
|
||||
}
|
||||
|
||||
|
||||
@@ -5,17 +5,26 @@ import org.hibernate.SessionFactory;
|
||||
import org.hibernate.Transaction;
|
||||
import org.hibernate.context.internal.ManagedSessionContext;
|
||||
|
||||
public abstract class UnitOfWork<T> {
|
||||
public class UnitOfWork {
|
||||
|
||||
private SessionFactory sessionFactory;
|
||||
|
||||
public UnitOfWork(SessionFactory sessionFactory) {
|
||||
this.sessionFactory = sessionFactory;
|
||||
@FunctionalInterface
|
||||
public static interface SessionRunner {
|
||||
public void runInSession();
|
||||
}
|
||||
|
||||
protected abstract T runInSession() throws Exception;
|
||||
@FunctionalInterface
|
||||
public static interface SessionRunnerReturningValue<T> {
|
||||
public T runInSession();
|
||||
}
|
||||
|
||||
public T run() {
|
||||
public static void run(SessionFactory sessionFactory, SessionRunner sessionRunner) {
|
||||
run(sessionFactory, () -> {
|
||||
sessionRunner.runInSession();
|
||||
return null;
|
||||
});
|
||||
}
|
||||
|
||||
public static <T> T run(SessionFactory sessionFactory, SessionRunnerReturningValue<T> sessionRunner) {
|
||||
final Session session = sessionFactory.openSession();
|
||||
if (ManagedSessionContext.hasBind(sessionFactory)) {
|
||||
throw new IllegalStateException("Already in a unit of work!");
|
||||
@@ -25,11 +34,11 @@ public abstract class UnitOfWork<T> {
|
||||
ManagedSessionContext.bind(session);
|
||||
session.beginTransaction();
|
||||
try {
|
||||
t = runInSession();
|
||||
t = sessionRunner.runInSession();
|
||||
commitTransaction(session);
|
||||
} catch (Exception e) {
|
||||
rollbackTransaction(session);
|
||||
this.<RuntimeException> rethrow(e);
|
||||
UnitOfWork.<RuntimeException> rethrow(e);
|
||||
}
|
||||
} finally {
|
||||
session.close();
|
||||
@@ -38,14 +47,14 @@ public abstract class UnitOfWork<T> {
|
||||
return t;
|
||||
}
|
||||
|
||||
private void rollbackTransaction(Session session) {
|
||||
private static void rollbackTransaction(Session session) {
|
||||
final Transaction txn = session.getTransaction();
|
||||
if (txn != null && txn.isActive()) {
|
||||
txn.rollback();
|
||||
}
|
||||
}
|
||||
|
||||
private void commitTransaction(Session session) {
|
||||
private static void commitTransaction(Session session) {
|
||||
final Transaction txn = session.getTransaction();
|
||||
if (txn != null && txn.isActive()) {
|
||||
txn.commit();
|
||||
@@ -53,7 +62,7 @@ public abstract class UnitOfWork<T> {
|
||||
}
|
||||
|
||||
@SuppressWarnings("unchecked")
|
||||
private <E extends Exception> void rethrow(Exception e) throws E {
|
||||
private static <E extends Exception> void rethrow(Exception e) throws E {
|
||||
throw (E) e;
|
||||
}
|
||||
|
||||
|
||||
@@ -6,7 +6,6 @@ import javax.inject.Singleton;
|
||||
import org.hibernate.SessionFactory;
|
||||
|
||||
import com.commafeed.backend.model.QUser;
|
||||
import com.commafeed.backend.model.QUserRole;
|
||||
import com.commafeed.backend.model.User;
|
||||
|
||||
@Singleton
|
||||
@@ -20,18 +19,15 @@ public class UserDAO extends GenericDAO<User> {
|
||||
}
|
||||
|
||||
public User findByName(String name) {
|
||||
return newQuery().from(user).where(user.name.equalsIgnoreCase(name)).leftJoin(user.roles, QUserRole.userRole).fetch()
|
||||
.uniqueResult(user);
|
||||
return newQuery().from(user).where(user.name.equalsIgnoreCase(name)).uniqueResult(user);
|
||||
}
|
||||
|
||||
public User findByApiKey(String key) {
|
||||
return newQuery().from(user).where(user.apiKey.equalsIgnoreCase(key)).leftJoin(user.roles, QUserRole.userRole).fetch()
|
||||
.uniqueResult(user);
|
||||
return newQuery().from(user).where(user.apiKey.equalsIgnoreCase(key)).uniqueResult(user);
|
||||
}
|
||||
|
||||
public User findByEmail(String email) {
|
||||
return newQuery().from(user).where(user.email.equalsIgnoreCase(email)).leftJoin(user.roles, QUserRole.userRole).fetch()
|
||||
.uniqueResult(user);
|
||||
return newQuery().from(user).where(user.email.equalsIgnoreCase(email)).uniqueResult(user);
|
||||
}
|
||||
|
||||
public long count() {
|
||||
|
||||
@@ -2,6 +2,7 @@ package com.commafeed.backend.dao;
|
||||
|
||||
import java.util.List;
|
||||
import java.util.Set;
|
||||
import java.util.stream.Collectors;
|
||||
|
||||
import javax.inject.Inject;
|
||||
import javax.inject.Singleton;
|
||||
@@ -12,7 +13,6 @@ import com.commafeed.backend.model.QUserRole;
|
||||
import com.commafeed.backend.model.User;
|
||||
import com.commafeed.backend.model.UserRole;
|
||||
import com.commafeed.backend.model.UserRole.Role;
|
||||
import com.google.common.collect.Sets;
|
||||
|
||||
@Singleton
|
||||
public class UserRoleDAO extends GenericDAO<UserRole> {
|
||||
@@ -33,10 +33,6 @@ public class UserRoleDAO extends GenericDAO<UserRole> {
|
||||
}
|
||||
|
||||
public Set<Role> findRoles(User user) {
|
||||
Set<Role> list = Sets.newHashSet();
|
||||
for (UserRole role : findAll(user)) {
|
||||
list.add(role.getRole());
|
||||
}
|
||||
return list;
|
||||
return findAll(user).stream().map(r -> r.getRole()).collect(Collectors.toSet());
|
||||
}
|
||||
}
|
||||
|
||||
@@ -3,6 +3,8 @@ package com.commafeed.backend.favicon;
|
||||
import java.util.Arrays;
|
||||
import java.util.List;
|
||||
|
||||
import lombok.Getter;
|
||||
import lombok.RequiredArgsConstructor;
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
|
||||
import org.apache.commons.lang3.StringUtils;
|
||||
@@ -18,7 +20,7 @@ public abstract class AbstractFaviconFetcher {
|
||||
|
||||
protected static int TIMEOUT = 4000;
|
||||
|
||||
public abstract byte[] fetch(Feed feed);
|
||||
public abstract Favicon fetch(Feed feed);
|
||||
|
||||
protected boolean isValidIconResponse(byte[] content, String contentType) {
|
||||
if (content == null) {
|
||||
@@ -48,4 +50,11 @@ public abstract class AbstractFaviconFetcher {
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
@RequiredArgsConstructor
|
||||
@Getter
|
||||
public static class Favicon {
|
||||
private final byte[] icon;
|
||||
private final String mediaType;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -28,9 +28,15 @@ public class DefaultFaviconFetcher extends AbstractFaviconFetcher {
|
||||
private final HttpGetter getter;
|
||||
|
||||
@Override
|
||||
public byte[] fetch(Feed feed) {
|
||||
String url = feed.getLink() != null ? feed.getLink() : feed.getUrl();
|
||||
public Favicon fetch(Feed feed) {
|
||||
Favicon icon = fetch(feed.getLink());
|
||||
if (icon == null) {
|
||||
icon = fetch(feed.getUrl());
|
||||
}
|
||||
return icon;
|
||||
}
|
||||
|
||||
private Favicon fetch(String url) {
|
||||
if (url == null) {
|
||||
log.debug("url is null");
|
||||
return null;
|
||||
@@ -47,7 +53,7 @@ public class DefaultFaviconFetcher extends AbstractFaviconFetcher {
|
||||
url = url.substring(0, firstSlash);
|
||||
}
|
||||
|
||||
byte[] icon = getIconAtRoot(url);
|
||||
Favicon icon = getIconAtRoot(url);
|
||||
|
||||
if (icon == null) {
|
||||
icon = getIconInPage(url);
|
||||
@@ -56,7 +62,7 @@ public class DefaultFaviconFetcher extends AbstractFaviconFetcher {
|
||||
return icon;
|
||||
}
|
||||
|
||||
private byte[] getIconAtRoot(String url) {
|
||||
private Favicon getIconAtRoot(String url) {
|
||||
byte[] bytes = null;
|
||||
String contentType = null;
|
||||
|
||||
@@ -67,23 +73,25 @@ public class DefaultFaviconFetcher extends AbstractFaviconFetcher {
|
||||
bytes = result.getContent();
|
||||
contentType = result.getContentType();
|
||||
} catch (Exception e) {
|
||||
log.debug("Failed to retrieve iconAtRoot for url {}: ", url, e);
|
||||
log.debug("Failed to retrieve iconAtRoot for url {}: ", url);
|
||||
log.trace("Failed to retrieve iconAtRoot for url {}: ", url, e);
|
||||
}
|
||||
|
||||
if (!isValidIconResponse(bytes, contentType)) {
|
||||
bytes = null;
|
||||
return null;
|
||||
}
|
||||
return bytes;
|
||||
return new Favicon(bytes, contentType);
|
||||
}
|
||||
|
||||
private byte[] getIconInPage(String url) {
|
||||
private Favicon getIconInPage(String url) {
|
||||
|
||||
Document doc = null;
|
||||
try {
|
||||
HttpResult result = getter.getBinary(url, TIMEOUT);
|
||||
doc = Jsoup.parse(new String(result.getContent()), url);
|
||||
} catch (Exception e) {
|
||||
log.debug("Failed to retrieve page to find icon", e);
|
||||
log.debug("Failed to retrieve page to find icon");
|
||||
log.trace("Failed to retrieve page to find icon", e);
|
||||
return null;
|
||||
}
|
||||
|
||||
@@ -109,7 +117,8 @@ public class DefaultFaviconFetcher extends AbstractFaviconFetcher {
|
||||
bytes = result.getContent();
|
||||
contentType = result.getContentType();
|
||||
} catch (Exception e) {
|
||||
log.debug("Failed to retrieve icon found in page {}", href, e);
|
||||
log.debug("Failed to retrieve icon found in page {}", href);
|
||||
log.trace("Failed to retrieve icon found in page {}", href, e);
|
||||
return null;
|
||||
}
|
||||
|
||||
@@ -118,6 +127,6 @@ public class DefaultFaviconFetcher extends AbstractFaviconFetcher {
|
||||
return null;
|
||||
}
|
||||
|
||||
return bytes;
|
||||
return new Favicon(bytes, contentType);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -26,7 +26,7 @@ public class FacebookFaviconFetcher extends AbstractFaviconFetcher {
|
||||
private final HttpGetter getter;
|
||||
|
||||
@Override
|
||||
public byte[] fetch(Feed feed) {
|
||||
public Favicon fetch(Feed feed) {
|
||||
String url = feed.getUrl();
|
||||
|
||||
if (!url.toLowerCase().contains("www.facebook.com")) {
|
||||
@@ -54,9 +54,9 @@ public class FacebookFaviconFetcher extends AbstractFaviconFetcher {
|
||||
}
|
||||
|
||||
if (!isValidIconResponse(bytes, contentType)) {
|
||||
bytes = null;
|
||||
return null;
|
||||
}
|
||||
return bytes;
|
||||
return new Favicon(bytes, contentType);
|
||||
}
|
||||
|
||||
private String extractUserName(String url) {
|
||||
|
||||
@@ -1,18 +1,31 @@
|
||||
package com.commafeed.backend.favicon;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.nio.charset.StandardCharsets;
|
||||
import java.util.List;
|
||||
import java.util.Optional;
|
||||
|
||||
import javax.inject.Inject;
|
||||
import javax.inject.Singleton;
|
||||
|
||||
import lombok.RequiredArgsConstructor;
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
|
||||
import org.jsoup.Jsoup;
|
||||
import org.jsoup.nodes.Document;
|
||||
import org.jsoup.select.Elements;
|
||||
import org.apache.http.NameValuePair;
|
||||
import org.apache.http.client.utils.URLEncodedUtils;
|
||||
|
||||
import com.commafeed.CommaFeedConfiguration;
|
||||
import com.commafeed.backend.HttpGetter;
|
||||
import com.commafeed.backend.HttpGetter.HttpResult;
|
||||
import com.commafeed.backend.model.Feed;
|
||||
import com.google.api.client.http.HttpRequest;
|
||||
import com.google.api.client.http.HttpRequestInitializer;
|
||||
import com.google.api.client.http.javanet.NetHttpTransport;
|
||||
import com.google.api.client.json.jackson2.JacksonFactory;
|
||||
import com.google.api.services.youtube.YouTube;
|
||||
import com.google.api.services.youtube.model.Channel;
|
||||
import com.google.api.services.youtube.model.ChannelListResponse;
|
||||
import com.google.api.services.youtube.model.Thumbnail;
|
||||
|
||||
@Slf4j
|
||||
@RequiredArgsConstructor(onConstructor = @__({ @Inject }))
|
||||
@@ -20,40 +33,60 @@ import com.commafeed.backend.model.Feed;
|
||||
public class YoutubeFaviconFetcher extends AbstractFaviconFetcher {
|
||||
|
||||
private final HttpGetter getter;
|
||||
private final CommaFeedConfiguration config;
|
||||
|
||||
@Override
|
||||
public byte[] fetch(Feed feed) {
|
||||
public Favicon fetch(Feed feed) {
|
||||
String url = feed.getUrl();
|
||||
|
||||
if (!url.toLowerCase().contains("://gdata.youtube.com/")) {
|
||||
if (!url.toLowerCase().contains("youtube.com/feeds/videos.xml")) {
|
||||
return null;
|
||||
}
|
||||
|
||||
String userName = extractUserName(url);
|
||||
if (userName == null) {
|
||||
String googleAuthKey = config.getApplicationSettings().getGoogleAuthKey();
|
||||
if (googleAuthKey == null) {
|
||||
log.debug("no google auth key configured");
|
||||
return null;
|
||||
}
|
||||
|
||||
String profileUrl = "https://gdata.youtube.com/feeds/users/" + userName;
|
||||
|
||||
byte[] bytes = null;
|
||||
String contentType = null;
|
||||
|
||||
try {
|
||||
log.debug("Getting YouTube user's icon, {}", url);
|
||||
|
||||
// initial get to translate username to obscure user thumbnail URL
|
||||
HttpResult profileResult = getter.getBinary(profileUrl, TIMEOUT);
|
||||
Document doc = Jsoup.parse(new String(profileResult.getContent()), profileUrl);
|
||||
|
||||
Elements thumbnails = doc.select("media|thumbnail");
|
||||
if (thumbnails.isEmpty()) {
|
||||
List<NameValuePair> params = URLEncodedUtils.parse(url.substring(url.indexOf("?") + 1), StandardCharsets.UTF_8);
|
||||
Optional<NameValuePair> userId = params.stream().filter(nvp -> nvp.getName().equalsIgnoreCase("user")).findFirst();
|
||||
Optional<NameValuePair> channelId = params.stream().filter(nvp -> nvp.getName().equalsIgnoreCase("channel")).findFirst();
|
||||
System.out.println(userId.isPresent());
|
||||
if (!userId.isPresent() && !channelId.isPresent()) {
|
||||
return null;
|
||||
}
|
||||
String thumbnailUrl = thumbnails.get(0).attr("abs:url");
|
||||
|
||||
// final get to actually retrieve the thumbnail
|
||||
HttpResult iconResult = getter.getBinary(thumbnailUrl, TIMEOUT);
|
||||
YouTube youtube = new YouTube.Builder(new NetHttpTransport(), JacksonFactory.getDefaultInstance(),
|
||||
new HttpRequestInitializer() {
|
||||
@Override
|
||||
public void initialize(HttpRequest request) throws IOException {
|
||||
}
|
||||
}).setApplicationName("CommaFeed").build();
|
||||
|
||||
YouTube.Channels.List list = youtube.channels().list("snippet");
|
||||
list.setKey(googleAuthKey);
|
||||
if (userId.isPresent()) {
|
||||
list.setForUsername(userId.get().getValue());
|
||||
} else {
|
||||
list.setId(channelId.get().getValue());
|
||||
}
|
||||
|
||||
log.debug("contacting youtube api");
|
||||
ChannelListResponse response = list.execute();
|
||||
if (response.getItems().isEmpty()) {
|
||||
log.debug("youtube api returned no items");
|
||||
return null;
|
||||
}
|
||||
|
||||
Channel channel = response.getItems().get(0);
|
||||
Thumbnail thumbnail = channel.getSnippet().getThumbnails().getDefault();
|
||||
|
||||
log.debug("fetching favicon");
|
||||
HttpResult iconResult = getter.getBinary(thumbnail.getUrl(), TIMEOUT);
|
||||
bytes = iconResult.getContent();
|
||||
contentType = iconResult.getContentType();
|
||||
} catch (Exception e) {
|
||||
@@ -61,23 +94,8 @@ public class YoutubeFaviconFetcher extends AbstractFaviconFetcher {
|
||||
}
|
||||
|
||||
if (!isValidIconResponse(bytes, contentType)) {
|
||||
bytes = null;
|
||||
}
|
||||
return bytes;
|
||||
}
|
||||
|
||||
private String extractUserName(String url) {
|
||||
int apiOrBase = url.indexOf("/users/");
|
||||
if (apiOrBase == -1) {
|
||||
return null;
|
||||
}
|
||||
|
||||
int userEndSlash = url.indexOf('/', apiOrBase + "/users/".length());
|
||||
if (userEndSlash == -1) {
|
||||
return null;
|
||||
}
|
||||
|
||||
return url.substring(apiOrBase + "/users/".length(), userEndSlash);
|
||||
return new Favicon(bytes, contentType);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
package com.commafeed.backend.feed;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
|
||||
import lombok.Getter;
|
||||
@@ -7,8 +8,6 @@ import lombok.RequiredArgsConstructor;
|
||||
|
||||
import org.apache.commons.lang3.StringUtils;
|
||||
|
||||
import com.google.common.collect.Lists;
|
||||
|
||||
/**
|
||||
* A keyword used in a search query
|
||||
*/
|
||||
@@ -24,7 +23,7 @@ public class FeedEntryKeyword {
|
||||
private final Mode mode;
|
||||
|
||||
public static List<FeedEntryKeyword> fromQueryString(String keywords) {
|
||||
List<FeedEntryKeyword> list = Lists.newArrayList();
|
||||
List<FeedEntryKeyword> list = new ArrayList<>();
|
||||
if (keywords != null) {
|
||||
for (String keyword : StringUtils.split(keywords)) {
|
||||
boolean not = false;
|
||||
|
||||
@@ -41,7 +41,7 @@ public class FeedFetcher {
|
||||
byte[] content = result.getContent();
|
||||
|
||||
try {
|
||||
fetchedFeed = parser.parse(feedUrl, content);
|
||||
fetchedFeed = parser.parse(result.getUrlAfterRedirect(), content);
|
||||
} catch (FeedException e) {
|
||||
if (extractFeedUrlFromHtml) {
|
||||
String extractedUrl = extractFeedUrl(StringUtils.newStringUtf8(result.getContent()), feedUrl);
|
||||
@@ -50,7 +50,7 @@ public class FeedFetcher {
|
||||
|
||||
result = getter.getBinary(extractedUrl, lastModified, eTag, timeout);
|
||||
content = result.getContent();
|
||||
fetchedFeed = parser.parse(feedUrl, content);
|
||||
fetchedFeed = parser.parse(result.getUrlAfterRedirect(), content);
|
||||
} else {
|
||||
throw e;
|
||||
}
|
||||
|
||||
@@ -1,9 +1,11 @@
|
||||
package com.commafeed.backend.feed;
|
||||
|
||||
import java.io.StringReader;
|
||||
import java.nio.charset.Charset;
|
||||
import java.text.DateFormat;
|
||||
import java.util.Date;
|
||||
import java.util.List;
|
||||
import java.util.stream.Collectors;
|
||||
|
||||
import javax.inject.Inject;
|
||||
import javax.inject.Singleton;
|
||||
@@ -19,10 +21,7 @@ import org.xml.sax.InputSource;
|
||||
import com.commafeed.backend.model.Feed;
|
||||
import com.commafeed.backend.model.FeedEntry;
|
||||
import com.commafeed.backend.model.FeedEntryContent;
|
||||
import com.google.common.base.Function;
|
||||
import com.google.common.collect.Collections2;
|
||||
import com.google.common.collect.Iterables;
|
||||
import com.rometools.rome.feed.synd.SyndContent;
|
||||
import com.rometools.rome.feed.synd.SyndEnclosure;
|
||||
import com.rometools.rome.feed.synd.SyndEntry;
|
||||
import com.rometools.rome.feed.synd.SyndFeed;
|
||||
@@ -42,20 +41,13 @@ public class FeedParser {
|
||||
private static final Date START = new Date(86400000);
|
||||
private static final Date END = new Date(1000l * Integer.MAX_VALUE - 86400000);
|
||||
|
||||
private static final Function<SyndContent, String> CONTENT_TO_STRING = new Function<SyndContent, String>() {
|
||||
@Override
|
||||
public String apply(SyndContent content) {
|
||||
return content.getValue();
|
||||
}
|
||||
};
|
||||
|
||||
public FetchedFeed parse(String feedUrl, byte[] xml) throws FeedException {
|
||||
FetchedFeed fetchedFeed = new FetchedFeed();
|
||||
Feed feed = fetchedFeed.getFeed();
|
||||
List<FeedEntry> entries = fetchedFeed.getEntries();
|
||||
|
||||
try {
|
||||
String encoding = FeedUtils.guessEncoding(xml);
|
||||
Charset encoding = FeedUtils.guessEncoding(xml);
|
||||
String xmlString = FeedUtils.trimInvalidXmlCharacters(new String(xml, encoding));
|
||||
if (xmlString == null) {
|
||||
throw new FeedException("Input string is null for url " + feedUrl);
|
||||
@@ -85,7 +77,7 @@ public class FeedParser {
|
||||
}
|
||||
entry.setGuid(FeedUtils.truncate(guid, 2048));
|
||||
entry.setUpdated(validateDate(getEntryUpdateDate(item), true));
|
||||
entry.setUrl(FeedUtils.truncate(FeedUtils.toAbsoluteUrl(item.getLink(), feed.getLink(), feed.getUrlAfterRedirect()), 2048));
|
||||
entry.setUrl(FeedUtils.truncate(FeedUtils.toAbsoluteUrl(item.getLink(), feed.getLink(), feedUrl), 2048));
|
||||
|
||||
// if link is empty but guid is used as url
|
||||
if (StringUtils.isBlank(entry.getUrl()) && StringUtils.startsWith(entry.getGuid(), "http")) {
|
||||
@@ -94,6 +86,8 @@ public class FeedParser {
|
||||
|
||||
FeedEntryContent content = new FeedEntryContent();
|
||||
content.setContent(getContent(item));
|
||||
content.setCategories(FeedUtils.truncate(
|
||||
item.getCategories().stream().map(c -> c.getName()).collect(Collectors.joining(", ")), 4096));
|
||||
content.setTitle(getTitle(item));
|
||||
content.setAuthor(StringUtils.trimToNull(item.getAuthor()));
|
||||
SyndEnclosure enclosure = Iterables.getFirst(item.getEnclosures(), null);
|
||||
@@ -172,7 +166,7 @@ public class FeedParser {
|
||||
if (item.getContents().isEmpty()) {
|
||||
content = item.getDescription() == null ? null : item.getDescription().getValue();
|
||||
} else {
|
||||
content = StringUtils.join(Collections2.transform(item.getContents(), CONTENT_TO_STRING), System.lineSeparator());
|
||||
content = item.getContents().stream().map(c -> c.getValue()).collect(Collectors.joining(System.lineSeparator()));
|
||||
}
|
||||
return StringUtils.trimToNull(content);
|
||||
}
|
||||
|
||||
@@ -1,40 +1,45 @@
|
||||
package com.commafeed.backend.feed;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.Date;
|
||||
import java.util.LinkedHashMap;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.Queue;
|
||||
import java.util.concurrent.ConcurrentLinkedQueue;
|
||||
import java.util.stream.Collectors;
|
||||
|
||||
import javax.inject.Inject;
|
||||
import javax.inject.Singleton;
|
||||
|
||||
import org.apache.commons.codec.digest.DigestUtils;
|
||||
import org.apache.commons.lang3.time.DateUtils;
|
||||
import org.hibernate.SessionFactory;
|
||||
|
||||
import com.codahale.metrics.Gauge;
|
||||
import com.codahale.metrics.Meter;
|
||||
import com.codahale.metrics.MetricRegistry;
|
||||
import com.commafeed.CommaFeedConfiguration;
|
||||
import com.commafeed.backend.dao.FeedDAO;
|
||||
import com.commafeed.backend.dao.UnitOfWork;
|
||||
import com.commafeed.backend.model.Feed;
|
||||
import com.google.common.collect.Lists;
|
||||
import com.google.common.collect.Maps;
|
||||
import com.google.common.collect.Queues;
|
||||
|
||||
@Singleton
|
||||
public class FeedQueues {
|
||||
|
||||
private SessionFactory sessionFactory;
|
||||
private final FeedDAO feedDAO;
|
||||
private final CommaFeedConfiguration config;
|
||||
|
||||
private Queue<FeedRefreshContext> addQueue = Queues.newConcurrentLinkedQueue();
|
||||
private Queue<FeedRefreshContext> takeQueue = Queues.newConcurrentLinkedQueue();
|
||||
private Queue<Feed> giveBackQueue = Queues.newConcurrentLinkedQueue();
|
||||
private Queue<FeedRefreshContext> addQueue = new ConcurrentLinkedQueue<>();
|
||||
private Queue<FeedRefreshContext> takeQueue = new ConcurrentLinkedQueue<>();
|
||||
private Queue<Feed> giveBackQueue = new ConcurrentLinkedQueue<>();
|
||||
|
||||
private Meter refill;
|
||||
|
||||
@Inject
|
||||
public FeedQueues(FeedDAO feedDAO, CommaFeedConfiguration config, MetricRegistry metrics) {
|
||||
public FeedQueues(SessionFactory sessionFactory, FeedDAO feedDAO, CommaFeedConfiguration config, MetricRegistry metrics) {
|
||||
this.sessionFactory = sessionFactory;
|
||||
this.config = config;
|
||||
this.feedDAO = feedDAO;
|
||||
|
||||
@@ -78,13 +83,7 @@ public class FeedQueues {
|
||||
public void add(Feed feed, boolean urgent) {
|
||||
int refreshInterval = config.getApplicationSettings().getRefreshIntervalMinutes();
|
||||
if (feed.getLastUpdated() == null || feed.getLastUpdated().before(DateUtils.addMinutes(new Date(), -1 * refreshInterval))) {
|
||||
boolean alreadyQueued = false;
|
||||
for (FeedRefreshContext context : addQueue) {
|
||||
if (context.getFeed().getId().equals(feed.getId())) {
|
||||
alreadyQueued = true;
|
||||
break;
|
||||
}
|
||||
}
|
||||
boolean alreadyQueued = addQueue.stream().anyMatch(c -> c.getFeed().getId().equals(feed.getId()));
|
||||
if (!alreadyQueued) {
|
||||
addQueue.add(new FeedRefreshContext(feed, urgent));
|
||||
}
|
||||
@@ -97,7 +96,7 @@ public class FeedQueues {
|
||||
private void refill() {
|
||||
refill.mark();
|
||||
|
||||
List<FeedRefreshContext> contexts = Lists.newArrayList();
|
||||
List<FeedRefreshContext> contexts = new ArrayList<>();
|
||||
int batchSize = Math.min(100, 3 * config.getApplicationSettings().getBackgroundThreads());
|
||||
|
||||
// add feeds we got from the add() method
|
||||
@@ -109,7 +108,7 @@ public class FeedQueues {
|
||||
// add feeds that are up to refresh from the database
|
||||
int count = batchSize - contexts.size();
|
||||
if (count > 0) {
|
||||
List<Feed> feeds = feedDAO.findNextUpdatable(count, getLastLoginThreshold());
|
||||
List<Feed> feeds = UnitOfWork.run(sessionFactory, () -> feedDAO.findNextUpdatable(count, getLastLoginThreshold()));
|
||||
for (Feed feed : feeds) {
|
||||
contexts.add(new FeedRefreshContext(feed, false));
|
||||
}
|
||||
@@ -117,7 +116,7 @@ public class FeedQueues {
|
||||
|
||||
// set the disabledDate as we use it in feedDAO to decide what to refresh next. We also use a map to remove
|
||||
// duplicates.
|
||||
Map<Long, FeedRefreshContext> map = Maps.newLinkedHashMap();
|
||||
Map<Long, FeedRefreshContext> map = new LinkedHashMap<>();
|
||||
for (FeedRefreshContext context : contexts) {
|
||||
Feed feed = context.getFeed();
|
||||
feed.setDisabledUntil(DateUtils.addMinutes(new Date(), config.getApplicationSettings().getRefreshIntervalMinutes()));
|
||||
@@ -135,11 +134,8 @@ public class FeedQueues {
|
||||
}
|
||||
|
||||
// update all feeds in the database
|
||||
List<Feed> feeds = Lists.newArrayList();
|
||||
for (FeedRefreshContext context : map.values()) {
|
||||
feeds.add(context.getFeed());
|
||||
}
|
||||
feedDAO.merge(feeds);
|
||||
List<Feed> feeds = map.values().stream().map(c -> c.getFeed()).collect(Collectors.toList());
|
||||
UnitOfWork.run(sessionFactory, () -> feedDAO.saveOrUpdate(feeds));
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
@@ -2,9 +2,14 @@ package com.commafeed.backend.feed;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
import lombok.Getter;
|
||||
import lombok.Setter;
|
||||
|
||||
import com.commafeed.backend.model.Feed;
|
||||
import com.commafeed.backend.model.FeedEntry;
|
||||
|
||||
@Getter
|
||||
@Setter
|
||||
public class FeedRefreshContext {
|
||||
private Feed feed;
|
||||
private List<FeedEntry> entries;
|
||||
@@ -14,29 +19,4 @@ public class FeedRefreshContext {
|
||||
this.feed = feed;
|
||||
this.urgent = isUrgent;
|
||||
}
|
||||
|
||||
public Feed getFeed() {
|
||||
return feed;
|
||||
}
|
||||
|
||||
public void setFeed(Feed feed) {
|
||||
this.feed = feed;
|
||||
}
|
||||
|
||||
public boolean isUrgent() {
|
||||
return urgent;
|
||||
}
|
||||
|
||||
public void setUrgent(boolean urgent) {
|
||||
this.urgent = urgent;
|
||||
}
|
||||
|
||||
public List<FeedEntry> getEntries() {
|
||||
return entries;
|
||||
}
|
||||
|
||||
public void setEntries(List<FeedEntry> entries) {
|
||||
this.entries = entries;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -10,13 +10,10 @@ import javax.inject.Singleton;
|
||||
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
|
||||
import org.hibernate.SessionFactory;
|
||||
|
||||
import com.codahale.metrics.Meter;
|
||||
import com.codahale.metrics.MetricRegistry;
|
||||
import com.commafeed.CommaFeedConfiguration;
|
||||
import com.commafeed.backend.dao.FeedDAO;
|
||||
import com.commafeed.backend.dao.UnitOfWork;
|
||||
|
||||
/**
|
||||
* Infinite loop fetching feeds from @FeedQueues and queuing them to the {@link FeedRefreshWorker} pool.
|
||||
@@ -26,7 +23,6 @@ import com.commafeed.backend.dao.UnitOfWork;
|
||||
@Singleton
|
||||
public class FeedRefreshTaskGiver implements Managed {
|
||||
|
||||
private final SessionFactory sessionFactory;
|
||||
private final FeedQueues queues;
|
||||
private final FeedRefreshWorker worker;
|
||||
|
||||
@@ -36,9 +32,8 @@ public class FeedRefreshTaskGiver implements Managed {
|
||||
private Meter threadWaited;
|
||||
|
||||
@Inject
|
||||
public FeedRefreshTaskGiver(SessionFactory sessionFactory, FeedQueues queues, FeedDAO feedDAO, FeedRefreshWorker worker,
|
||||
CommaFeedConfiguration config, MetricRegistry metrics) {
|
||||
this.sessionFactory = sessionFactory;
|
||||
public FeedRefreshTaskGiver(FeedQueues queues, FeedDAO feedDAO, FeedRefreshWorker worker, CommaFeedConfiguration config,
|
||||
MetricRegistry metrics) {
|
||||
this.queues = queues;
|
||||
this.worker = worker;
|
||||
|
||||
@@ -68,12 +63,7 @@ public class FeedRefreshTaskGiver implements Managed {
|
||||
public void run() {
|
||||
while (!executor.isShutdown()) {
|
||||
try {
|
||||
FeedRefreshContext context = new UnitOfWork<FeedRefreshContext>(sessionFactory) {
|
||||
@Override
|
||||
protected FeedRefreshContext runInSession() throws Exception {
|
||||
return queues.take();
|
||||
}
|
||||
}.run();
|
||||
FeedRefreshContext context = queues.take();
|
||||
if (context != null) {
|
||||
feedRefreshed.mark();
|
||||
worker.updateFeed(context);
|
||||
|
||||
@@ -2,12 +2,14 @@ package com.commafeed.backend.feed;
|
||||
|
||||
import io.dropwizard.lifecycle.Managed;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.Arrays;
|
||||
import java.util.Date;
|
||||
import java.util.Iterator;
|
||||
import java.util.List;
|
||||
import java.util.concurrent.TimeUnit;
|
||||
import java.util.concurrent.locks.Lock;
|
||||
import java.util.stream.Collectors;
|
||||
|
||||
import javax.inject.Inject;
|
||||
import javax.inject.Singleton;
|
||||
@@ -35,7 +37,6 @@ import com.commafeed.backend.model.FeedSubscription;
|
||||
import com.commafeed.backend.model.User;
|
||||
import com.commafeed.backend.service.FeedUpdateService;
|
||||
import com.commafeed.backend.service.PubSubService;
|
||||
import com.google.common.collect.Lists;
|
||||
import com.google.common.util.concurrent.Striped;
|
||||
|
||||
@Slf4j
|
||||
@@ -112,7 +113,7 @@ public class FeedRefreshUpdater implements Managed {
|
||||
feed.setMessage("Feed has no entries");
|
||||
} else {
|
||||
List<String> lastEntries = cache.getLastEntries(feed);
|
||||
List<String> currentEntries = Lists.newArrayList();
|
||||
List<String> currentEntries = new ArrayList<>();
|
||||
|
||||
List<FeedSubscription> subscriptions = null;
|
||||
for (FeedEntry entry : entries) {
|
||||
@@ -120,12 +121,7 @@ public class FeedRefreshUpdater implements Managed {
|
||||
if (!lastEntries.contains(cacheKey)) {
|
||||
log.debug("cache miss for {}", entry.getUrl());
|
||||
if (subscriptions == null) {
|
||||
subscriptions = new UnitOfWork<List<FeedSubscription>>(sessionFactory) {
|
||||
@Override
|
||||
protected List<FeedSubscription> runInSession() throws Exception {
|
||||
return feedSubscriptionDAO.findByFeed(feed);
|
||||
}
|
||||
}.run();
|
||||
subscriptions = UnitOfWork.run(sessionFactory, () -> feedSubscriptionDAO.findByFeed(feed));
|
||||
}
|
||||
ok &= addEntry(feed, entry, subscriptions);
|
||||
entryCacheMiss.mark();
|
||||
@@ -143,10 +139,7 @@ public class FeedRefreshUpdater implements Managed {
|
||||
}
|
||||
|
||||
if (CollectionUtils.isNotEmpty(subscriptions)) {
|
||||
List<User> users = Lists.newArrayList();
|
||||
for (FeedSubscription sub : subscriptions) {
|
||||
users.add(sub.getUser());
|
||||
}
|
||||
List<User> users = subscriptions.stream().map(s -> s.getUser()).collect(Collectors.toList());
|
||||
cache.invalidateUnreadCount(subscriptions.toArray(new FeedSubscription[0]));
|
||||
cache.invalidateUserRootCategory(users.toArray(new User[0]));
|
||||
}
|
||||
@@ -190,12 +183,7 @@ public class FeedRefreshUpdater implements Managed {
|
||||
locked1 = lock1.tryLock(1, TimeUnit.MINUTES);
|
||||
locked2 = lock2.tryLock(1, TimeUnit.MINUTES);
|
||||
if (locked1 && locked2) {
|
||||
boolean inserted = new UnitOfWork<Boolean>(sessionFactory) {
|
||||
@Override
|
||||
protected Boolean runInSession() throws Exception {
|
||||
return feedUpdateService.addEntry(feed, entry, subscriptions);
|
||||
}
|
||||
}.run();
|
||||
boolean inserted = UnitOfWork.run(sessionFactory, () -> feedUpdateService.addEntry(feed, entry, subscriptions));
|
||||
if (inserted) {
|
||||
entryInserted.mark();
|
||||
}
|
||||
|
||||
@@ -4,6 +4,8 @@ import io.dropwizard.lifecycle.Managed;
|
||||
|
||||
import java.util.Date;
|
||||
import java.util.List;
|
||||
import java.util.Optional;
|
||||
import java.util.stream.Collectors;
|
||||
|
||||
import javax.inject.Inject;
|
||||
import javax.inject.Singleton;
|
||||
@@ -20,7 +22,6 @@ import com.commafeed.backend.HttpGetter.NotModifiedException;
|
||||
import com.commafeed.backend.feed.FeedRefreshExecutor.Task;
|
||||
import com.commafeed.backend.model.Feed;
|
||||
import com.commafeed.backend.model.FeedEntry;
|
||||
import com.google.common.base.Optional;
|
||||
|
||||
/**
|
||||
* Calls {@link FeedFetcher} and handles its outcome
|
||||
@@ -84,12 +85,17 @@ public class FeedRefreshWorker implements Managed {
|
||||
int refreshInterval = config.getApplicationSettings().getRefreshIntervalMinutes();
|
||||
Date disabledUntil = DateUtils.addMinutes(new Date(), refreshInterval);
|
||||
try {
|
||||
String url = Optional.fromNullable(feed.getUrlAfterRedirect()).or(feed.getUrl());
|
||||
String url = Optional.ofNullable(feed.getUrlAfterRedirect()).orElse(feed.getUrl());
|
||||
FetchedFeed fetchedFeed = fetcher.fetch(url, false, feed.getLastModifiedHeader(), feed.getEtagHeader(),
|
||||
feed.getLastPublishedDate(), feed.getLastContentHash());
|
||||
// stops here if NotModifiedException or any other exception is thrown
|
||||
List<FeedEntry> entries = fetchedFeed.getEntries();
|
||||
|
||||
Integer maxFeedCapacity = config.getApplicationSettings().getMaxFeedCapacity();
|
||||
if (maxFeedCapacity > 0) {
|
||||
entries = entries.stream().limit(maxFeedCapacity).collect(Collectors.toList());
|
||||
}
|
||||
|
||||
if (config.getApplicationSettings().getHeavyLoad()) {
|
||||
disabledUntil = FeedUtils.buildDisabledUntil(fetchedFeed.getFeed().getLastEntryDate(), fetchedFeed.getFeed()
|
||||
.getAverageEntryInterval(), disabledUntil);
|
||||
|
||||
@@ -3,12 +3,15 @@ package com.commafeed.backend.feed;
|
||||
import java.io.StringReader;
|
||||
import java.net.MalformedURLException;
|
||||
import java.net.URL;
|
||||
import java.nio.charset.Charset;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Arrays;
|
||||
import java.util.Collections;
|
||||
import java.util.Date;
|
||||
import java.util.Iterator;
|
||||
import java.util.List;
|
||||
import java.util.regex.Pattern;
|
||||
import java.util.stream.Collectors;
|
||||
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
|
||||
@@ -32,7 +35,6 @@ import com.commafeed.backend.feed.FeedEntryKeyword.Mode;
|
||||
import com.commafeed.backend.model.FeedEntry;
|
||||
import com.commafeed.backend.model.FeedSubscription;
|
||||
import com.commafeed.frontend.model.Entry;
|
||||
import com.google.common.collect.Lists;
|
||||
import com.ibm.icu.text.CharsetDetector;
|
||||
import com.ibm.icu.text.CharsetMatch;
|
||||
import com.steadystate.css.parser.CSSOMParser;
|
||||
@@ -99,14 +101,14 @@ public class FeedUtils {
|
||||
* feed
|
||||
*
|
||||
*/
|
||||
public static String guessEncoding(byte[] bytes) {
|
||||
public static Charset guessEncoding(byte[] bytes) {
|
||||
String extracted = extractDeclaredEncoding(bytes);
|
||||
if (StringUtils.startsWithIgnoreCase(extracted, "iso-8859-")) {
|
||||
if (StringUtils.endsWith(extracted, "1") == false) {
|
||||
return extracted;
|
||||
return Charset.forName(extracted);
|
||||
}
|
||||
} else if (StringUtils.startsWithIgnoreCase(extracted, "windows-")) {
|
||||
return extracted;
|
||||
return Charset.forName(extracted);
|
||||
}
|
||||
return detectEncoding(bytes);
|
||||
}
|
||||
@@ -114,7 +116,7 @@ public class FeedUtils {
|
||||
/**
|
||||
* Detect encoding by analyzing characters in the array
|
||||
*/
|
||||
public static String detectEncoding(byte[] bytes) {
|
||||
public static Charset detectEncoding(byte[] bytes) {
|
||||
String encoding = "UTF-8";
|
||||
|
||||
CharsetDetector detector = new CharsetDetector();
|
||||
@@ -126,15 +128,11 @@ public class FeedUtils {
|
||||
if (encoding.equalsIgnoreCase("ISO-8859-1")) {
|
||||
encoding = "windows-1252";
|
||||
}
|
||||
return encoding;
|
||||
return Charset.forName(encoding);
|
||||
}
|
||||
|
||||
public static String replaceHtmlEntitiesWithNumericEntities(String source) {
|
||||
String result = source;
|
||||
for (String entity : HtmlEntities.NUMERIC_MAPPING.keySet()) {
|
||||
result = StringUtils.replace(result, entity, HtmlEntities.NUMERIC_MAPPING.get(entity));
|
||||
}
|
||||
return result;
|
||||
return StringUtils.replaceEach(source, HtmlEntities.HTML_ENTITIES, HtmlEntities.NUMERIC_ENTITIES);
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -229,7 +227,7 @@ public class FeedUtils {
|
||||
String rule = "";
|
||||
CSSOMParser parser = new CSSOMParser();
|
||||
try {
|
||||
List<String> rules = Lists.newArrayList();
|
||||
List<String> rules = new ArrayList<>();
|
||||
CSSStyleDeclaration decl = parser.parseStyleDeclaration(new InputSource(new StringReader(orig)));
|
||||
|
||||
for (int i = 0; i < decl.getLength(); i++) {
|
||||
@@ -254,7 +252,7 @@ public class FeedUtils {
|
||||
String rule = "";
|
||||
CSSOMParser parser = new CSSOMParser();
|
||||
try {
|
||||
List<String> rules = Lists.newArrayList();
|
||||
List<String> rules = new ArrayList<>();
|
||||
CSSStyleDeclaration decl = parser.parseStyleDeclaration(new InputSource(new StringReader(orig)));
|
||||
|
||||
for (int i = 0; i < decl.getLength(); i++) {
|
||||
@@ -388,13 +386,7 @@ public class FeedUtils {
|
||||
}
|
||||
|
||||
public static List<Long> getSortedTimestamps(List<FeedEntry> entries) {
|
||||
List<Long> timestamps = Lists.newArrayList();
|
||||
for (FeedEntry entry : entries) {
|
||||
timestamps.add(entry.getUpdated().getTime());
|
||||
}
|
||||
Collections.sort(timestamps);
|
||||
Collections.reverse(timestamps);
|
||||
return timestamps;
|
||||
return entries.stream().map(t -> t.getUpdated().getTime()).sorted(Collections.reverseOrder()).collect(Collectors.toList());
|
||||
}
|
||||
|
||||
public static String removeTrailingSlash(String url) {
|
||||
@@ -438,12 +430,8 @@ public class FeedUtils {
|
||||
}
|
||||
|
||||
public static boolean isRelative(final String url) {
|
||||
// the regex means "doesn't start with 'scheme://'"
|
||||
if ((url != null) && (url.startsWith("/") == false) && (!url.matches("^\\w+\\:\\/\\/.*")) && !(url.startsWith("#"))) {
|
||||
return true;
|
||||
} else {
|
||||
return false;
|
||||
}
|
||||
// the regex means "start with 'scheme://'"
|
||||
return url.startsWith("/") || url.startsWith("#") || !url.matches("^\\w+\\:\\/\\/.*");
|
||||
}
|
||||
|
||||
public static String getFaviconUrl(FeedSubscription subscription, String publicUrl) {
|
||||
|
||||
@@ -1,58 +1,23 @@
|
||||
package com.commafeed.backend.feed;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
|
||||
import lombok.Getter;
|
||||
import lombok.Setter;
|
||||
|
||||
import com.commafeed.backend.model.Feed;
|
||||
import com.commafeed.backend.model.FeedEntry;
|
||||
import com.google.common.collect.Lists;
|
||||
|
||||
@Getter
|
||||
@Setter
|
||||
public class FetchedFeed {
|
||||
|
||||
private Feed feed = new Feed();
|
||||
private List<FeedEntry> entries = Lists.newArrayList();
|
||||
private List<FeedEntry> entries = new ArrayList<>();
|
||||
|
||||
private String title;
|
||||
private String urlAfterRedirect;
|
||||
private long fetchDuration;
|
||||
|
||||
public Feed getFeed() {
|
||||
return feed;
|
||||
}
|
||||
|
||||
public void setFeed(Feed feed) {
|
||||
this.feed = feed;
|
||||
}
|
||||
|
||||
public List<FeedEntry> getEntries() {
|
||||
return entries;
|
||||
}
|
||||
|
||||
public void setEntries(List<FeedEntry> entries) {
|
||||
this.entries = entries;
|
||||
}
|
||||
|
||||
public String getTitle() {
|
||||
return title;
|
||||
}
|
||||
|
||||
public void setTitle(String title) {
|
||||
this.title = title;
|
||||
}
|
||||
|
||||
public long getFetchDuration() {
|
||||
return fetchDuration;
|
||||
}
|
||||
|
||||
public void setFetchDuration(long fetchDuration) {
|
||||
this.fetchDuration = fetchDuration;
|
||||
}
|
||||
|
||||
public String getUrlAfterRedirect() {
|
||||
return urlAfterRedirect;
|
||||
}
|
||||
|
||||
public void setUrlAfterRedirect(String urlAfterRedirect) {
|
||||
this.urlAfterRedirect = urlAfterRedirect;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -1,15 +1,14 @@
|
||||
package com.commafeed.backend.feed;
|
||||
|
||||
import java.util.Collections;
|
||||
import java.util.LinkedHashMap;
|
||||
import java.util.Map;
|
||||
|
||||
import com.google.common.collect.Maps;
|
||||
|
||||
public class HtmlEntities {
|
||||
public static final Map<String, String> NUMERIC_MAPPING = Collections.unmodifiableMap(loadMap());
|
||||
public static final String[] HTML_ENTITIES;
|
||||
public static final String[] NUMERIC_ENTITIES;
|
||||
|
||||
private static synchronized Map<String, String> loadMap() {
|
||||
Map<String, String> map = Maps.newLinkedHashMap();
|
||||
static {
|
||||
Map<String, String> map = new LinkedHashMap<>();
|
||||
map.put("Á", "Á");
|
||||
map.put("á", "á");
|
||||
map.put("Â", "Â");
|
||||
@@ -261,6 +260,7 @@ public class HtmlEntities {
|
||||
map.put("‍", "‍");
|
||||
map.put("‌", "‌");
|
||||
|
||||
return map;
|
||||
HTML_ENTITIES = map.keySet().toArray(new String[map.size()]);
|
||||
NUMERIC_ENTITIES = map.values().toArray(new String[map.size()]);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,12 +1,9 @@
|
||||
package com.commafeed.backend.model;
|
||||
|
||||
import java.util.Date;
|
||||
import java.util.Set;
|
||||
|
||||
import javax.persistence.CascadeType;
|
||||
import javax.persistence.Column;
|
||||
import javax.persistence.Entity;
|
||||
import javax.persistence.OneToMany;
|
||||
import javax.persistence.Table;
|
||||
import javax.persistence.Temporal;
|
||||
import javax.persistence.TemporalType;
|
||||
@@ -103,12 +100,6 @@ public class Feed extends AbstractModel {
|
||||
@Column(length = 40)
|
||||
private String lastContentHash;
|
||||
|
||||
@OneToMany(mappedBy = "feed", cascade = CascadeType.REMOVE)
|
||||
private Set<FeedEntry> entries;
|
||||
|
||||
@OneToMany(mappedBy = "feed")
|
||||
private Set<FeedSubscription> subscriptions;
|
||||
|
||||
/**
|
||||
* detected hub for pubsubhubbub
|
||||
*/
|
||||
|
||||
@@ -11,6 +11,8 @@ import javax.persistence.Table;
|
||||
import lombok.Getter;
|
||||
import lombok.Setter;
|
||||
|
||||
import org.hibernate.annotations.Type;
|
||||
|
||||
@Entity
|
||||
@Table(name = "FEEDENTRYCONTENTS")
|
||||
@SuppressWarnings("serial")
|
||||
@@ -26,6 +28,7 @@ public class FeedEntryContent extends AbstractModel {
|
||||
|
||||
@Lob
|
||||
@Column(length = Integer.MAX_VALUE)
|
||||
@Type(type = "org.hibernate.type.StringClobType")
|
||||
private String content;
|
||||
|
||||
@Column(length = 40)
|
||||
@@ -40,6 +43,9 @@ public class FeedEntryContent extends AbstractModel {
|
||||
@Column(length = 255)
|
||||
private String enclosureType;
|
||||
|
||||
@Column(length = 4096)
|
||||
private String categories;
|
||||
|
||||
@OneToMany(mappedBy = "content")
|
||||
private Set<FeedEntry> entries;
|
||||
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
package com.commafeed.backend.model;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.Date;
|
||||
import java.util.List;
|
||||
|
||||
@@ -16,8 +17,6 @@ import javax.persistence.Transient;
|
||||
import lombok.Getter;
|
||||
import lombok.Setter;
|
||||
|
||||
import com.google.common.collect.Lists;
|
||||
|
||||
@Entity
|
||||
@Table(name = "FEEDENTRYSTATUSES")
|
||||
@SuppressWarnings("serial")
|
||||
@@ -41,7 +40,7 @@ public class FeedEntryStatus extends AbstractModel {
|
||||
private boolean markable;
|
||||
|
||||
@Transient
|
||||
private List<FeedEntryTag> tags = Lists.newArrayList();
|
||||
private List<FeedEntryTag> tags = new ArrayList<>();
|
||||
|
||||
/**
|
||||
* Denormalization starts here
|
||||
|
||||
@@ -1,13 +1,9 @@
|
||||
package com.commafeed.backend.model;
|
||||
|
||||
import java.util.Date;
|
||||
import java.util.Set;
|
||||
|
||||
import javax.persistence.CascadeType;
|
||||
import javax.persistence.Column;
|
||||
import javax.persistence.Entity;
|
||||
import javax.persistence.FetchType;
|
||||
import javax.persistence.OneToMany;
|
||||
import javax.persistence.Table;
|
||||
import javax.persistence.Temporal;
|
||||
import javax.persistence.TemporalType;
|
||||
@@ -16,10 +12,6 @@ import lombok.Getter;
|
||||
import lombok.Setter;
|
||||
|
||||
import org.apache.commons.lang3.time.DateUtils;
|
||||
import org.hibernate.annotations.Cascade;
|
||||
|
||||
import com.commafeed.backend.model.UserRole.Role;
|
||||
import com.google.common.collect.Sets;
|
||||
|
||||
@Entity
|
||||
@Table(name = "USERS")
|
||||
@@ -58,27 +50,10 @@ public class User extends AbstractModel {
|
||||
@Temporal(TemporalType.TIMESTAMP)
|
||||
private Date recoverPasswordTokenDate;
|
||||
|
||||
@OneToMany(mappedBy = "user", cascade = { CascadeType.PERSIST, CascadeType.REMOVE })
|
||||
@Cascade({ org.hibernate.annotations.CascadeType.PERSIST, org.hibernate.annotations.CascadeType.SAVE_UPDATE,
|
||||
org.hibernate.annotations.CascadeType.REMOVE })
|
||||
private Set<UserRole> roles = Sets.newHashSet();
|
||||
|
||||
@OneToMany(mappedBy = "user", fetch = FetchType.LAZY, cascade = CascadeType.REMOVE)
|
||||
private Set<FeedSubscription> subscriptions;
|
||||
|
||||
@Column(name = "last_full_refresh")
|
||||
@Temporal(TemporalType.TIMESTAMP)
|
||||
private Date lastFullRefresh;
|
||||
|
||||
public boolean hasRole(Role role) {
|
||||
for (UserRole userRole : getRoles()) {
|
||||
if (userRole.getRole() == role) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
public boolean shouldRefreshFeedsAt(Date when) {
|
||||
return (lastFullRefresh == null || lastFullRefreshMoreThan30MinutesBefore(when));
|
||||
}
|
||||
|
||||
@@ -13,6 +13,8 @@ import javax.persistence.Table;
|
||||
import lombok.Getter;
|
||||
import lombok.Setter;
|
||||
|
||||
import org.hibernate.annotations.Type;
|
||||
|
||||
@Entity
|
||||
@Table(name = "USERSETTINGS")
|
||||
@SuppressWarnings("serial")
|
||||
@@ -59,6 +61,7 @@ public class UserSettings extends AbstractModel {
|
||||
|
||||
@Lob
|
||||
@Column(length = Integer.MAX_VALUE)
|
||||
@Type(type = "org.hibernate.type.StringClobType")
|
||||
private String customCss;
|
||||
|
||||
@Column(name = "scroll_speed")
|
||||
|
||||
@@ -1,7 +1,9 @@
|
||||
package com.commafeed.backend.opml;
|
||||
|
||||
import java.util.Collections;
|
||||
import java.util.Date;
|
||||
import java.util.List;
|
||||
import java.util.stream.Collectors;
|
||||
|
||||
import javax.inject.Inject;
|
||||
import javax.inject.Singleton;
|
||||
@@ -13,9 +15,7 @@ import com.commafeed.backend.dao.FeedSubscriptionDAO;
|
||||
import com.commafeed.backend.model.FeedCategory;
|
||||
import com.commafeed.backend.model.FeedSubscription;
|
||||
import com.commafeed.backend.model.User;
|
||||
import com.google.common.base.Function;
|
||||
import com.google.common.collect.Multimap;
|
||||
import com.google.common.collect.Multimaps;
|
||||
import com.google.common.base.MoreObjects;
|
||||
import com.rometools.opml.feed.opml.Attribute;
|
||||
import com.rometools.opml.feed.opml.Opml;
|
||||
import com.rometools.opml.feed.opml.Outline;
|
||||
@@ -24,14 +24,6 @@ import com.rometools.opml.feed.opml.Outline;
|
||||
@Singleton
|
||||
public class OPMLExporter {
|
||||
|
||||
private static Long ROOT_CATEGORY_ID = new Long(-1);
|
||||
private static final Function<FeedSubscription, Long> SUBSCRIPTION_TO_CATEGORYID = new Function<FeedSubscription, Long>() {
|
||||
@Override
|
||||
public Long apply(FeedSubscription sub) {
|
||||
return sub.getCategory() == null ? ROOT_CATEGORY_ID : sub.getCategory().getId();
|
||||
}
|
||||
};
|
||||
|
||||
private final FeedCategoryDAO feedCategoryDAO;
|
||||
private final FeedSubscriptionDAO feedSubscriptionDAO;
|
||||
|
||||
@@ -42,17 +34,20 @@ public class OPMLExporter {
|
||||
opml.setCreated(new Date());
|
||||
|
||||
List<FeedCategory> categories = feedCategoryDAO.findAll(user);
|
||||
Multimap<Long, FeedSubscription> subscriptions = Multimaps.index(feedSubscriptionDAO.findAll(user), SUBSCRIPTION_TO_CATEGORYID);
|
||||
Collections.sort(categories,
|
||||
(e1, e2) -> MoreObjects.firstNonNull(e1.getPosition(), 0) - MoreObjects.firstNonNull(e2.getPosition(), 0));
|
||||
|
||||
List<FeedSubscription> subscriptions = feedSubscriptionDAO.findAll(user);
|
||||
Collections.sort(subscriptions,
|
||||
(e1, e2) -> MoreObjects.firstNonNull(e1.getPosition(), 0) - MoreObjects.firstNonNull(e2.getPosition(), 0));
|
||||
|
||||
// export root categories
|
||||
for (FeedCategory cat : categories) {
|
||||
if (cat.getParent() == null) {
|
||||
opml.getOutlines().add(buildCategoryOutline(cat, subscriptions));
|
||||
}
|
||||
for (FeedCategory cat : categories.stream().filter(c -> c.getParent() == null).collect(Collectors.toList())) {
|
||||
opml.getOutlines().add(buildCategoryOutline(cat, categories, subscriptions));
|
||||
}
|
||||
|
||||
// export root subscriptions
|
||||
for (FeedSubscription sub : subscriptions.get(ROOT_CATEGORY_ID)) {
|
||||
for (FeedSubscription sub : subscriptions.stream().filter(s -> s.getCategory() == null).collect(Collectors.toList())) {
|
||||
opml.getOutlines().add(buildSubscriptionOutline(sub));
|
||||
}
|
||||
|
||||
@@ -60,16 +55,18 @@ public class OPMLExporter {
|
||||
|
||||
}
|
||||
|
||||
private Outline buildCategoryOutline(FeedCategory cat, Multimap<Long, FeedSubscription> subscriptions) {
|
||||
private Outline buildCategoryOutline(FeedCategory cat, List<FeedCategory> categories, List<FeedSubscription> subscriptions) {
|
||||
Outline outline = new Outline();
|
||||
outline.setText(cat.getName());
|
||||
outline.setTitle(cat.getName());
|
||||
|
||||
for (FeedCategory child : cat.getChildren()) {
|
||||
outline.getChildren().add(buildCategoryOutline(child, subscriptions));
|
||||
for (FeedCategory child : categories.stream().filter(c -> c.getParent() != null && c.getParent().getId().equals(cat.getId()))
|
||||
.collect(Collectors.toList())) {
|
||||
outline.getChildren().add(buildCategoryOutline(child, categories, subscriptions));
|
||||
}
|
||||
|
||||
for (FeedSubscription sub : subscriptions.get(cat.getId())) {
|
||||
for (FeedSubscription sub : subscriptions.stream()
|
||||
.filter(s -> s.getCategory() != null && s.getCategory().getId().equals(cat.getId())).collect(Collectors.toList())) {
|
||||
outline.getChildren().add(buildSubscriptionOutline(sub));
|
||||
}
|
||||
return outline;
|
||||
|
||||
@@ -38,8 +38,8 @@ public class OPMLImporter {
|
||||
try {
|
||||
Opml feed = (Opml) input.build(new StringReader(xml));
|
||||
List<Outline> outlines = feed.getOutlines();
|
||||
for (Outline outline : outlines) {
|
||||
handleOutline(user, outline, null);
|
||||
for (int i = 0; i < outlines.size(); i++) {
|
||||
handleOutline(user, outlines.get(i), null, i);
|
||||
}
|
||||
} catch (Exception e) {
|
||||
log.error(e.getMessage(), e);
|
||||
@@ -47,7 +47,7 @@ public class OPMLImporter {
|
||||
|
||||
}
|
||||
|
||||
private void handleOutline(User user, Outline outline, FeedCategory parent) {
|
||||
private void handleOutline(User user, Outline outline, FeedCategory parent, int position) {
|
||||
List<Outline> children = outline.getChildren();
|
||||
if (CollectionUtils.isNotEmpty(children)) {
|
||||
String name = FeedUtils.truncate(outline.getText(), 128);
|
||||
@@ -64,11 +64,12 @@ public class OPMLImporter {
|
||||
category.setName(name);
|
||||
category.setParent(parent);
|
||||
category.setUser(user);
|
||||
category.setPosition(position);
|
||||
feedCategoryDAO.saveOrUpdate(category);
|
||||
}
|
||||
|
||||
for (Outline child : children) {
|
||||
handleOutline(user, child, category);
|
||||
for (int i = 0; i < children.size(); i++) {
|
||||
handleOutline(user, children.get(i), category, i);
|
||||
}
|
||||
} else {
|
||||
String name = FeedUtils.truncate(outline.getText(), 128);
|
||||
@@ -80,7 +81,7 @@ public class OPMLImporter {
|
||||
}
|
||||
// make sure we continue with the import process even if a feed failed
|
||||
try {
|
||||
feedSubscriptionService.subscribe(user, outline.getXmlUrl(), name, parent);
|
||||
feedSubscriptionService.subscribe(user, outline.getXmlUrl(), name, parent, position);
|
||||
} catch (FeedSubscriptionException e) {
|
||||
throw e;
|
||||
} catch (Exception e) {
|
||||
|
||||
@@ -1,9 +1,13 @@
|
||||
package com.commafeed.backend.rome;
|
||||
|
||||
import java.util.Locale;
|
||||
|
||||
import org.jdom2.Document;
|
||||
import org.jdom2.Element;
|
||||
|
||||
import com.rometools.opml.io.impl.OPML10Parser;
|
||||
import com.rometools.rome.feed.WireFeed;
|
||||
import com.rometools.rome.io.FeedException;
|
||||
|
||||
/**
|
||||
* Support for OPML 1.1 parsing
|
||||
@@ -26,4 +30,10 @@ public class OPML11Parser extends OPML10Parser {
|
||||
return false;
|
||||
|
||||
};
|
||||
|
||||
@Override
|
||||
public WireFeed parse(Document document, boolean validate, Locale locale) throws IllegalArgumentException, FeedException {
|
||||
document.getRootElement().getChildren().add(new Element("head"));
|
||||
return super.parse(document, validate, locale);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -18,7 +18,6 @@ import com.commafeed.backend.dao.FeedEntryDAO.FeedCapacity;
|
||||
import com.commafeed.backend.dao.FeedEntryStatusDAO;
|
||||
import com.commafeed.backend.dao.UnitOfWork;
|
||||
import com.commafeed.backend.model.Feed;
|
||||
import com.commafeed.backend.model.FeedEntryStatus;
|
||||
|
||||
/**
|
||||
* Contains utility methods for cleaning the database
|
||||
@@ -41,14 +40,18 @@ public class DatabaseCleaningService {
|
||||
log.info("cleaning feeds without subscriptions");
|
||||
long total = 0;
|
||||
int deleted = 0;
|
||||
long entriesTotal = 0;
|
||||
do {
|
||||
deleted = new UnitOfWork<Integer>(sessionFactory) {
|
||||
@Override
|
||||
protected Integer runInSession() throws Exception {
|
||||
List<Feed> feeds = feedDAO.findWithoutSubscriptions(1);
|
||||
return feedDAO.delete(feeds);
|
||||
};
|
||||
}.run();
|
||||
List<Feed> feeds = UnitOfWork.run(sessionFactory, () -> feedDAO.findWithoutSubscriptions(1));
|
||||
for (Feed feed : feeds) {
|
||||
int entriesDeleted = 0;
|
||||
do {
|
||||
entriesDeleted = UnitOfWork.run(sessionFactory, () -> feedEntryDAO.delete(feed.getId(), BATCH_SIZE));
|
||||
entriesTotal += entriesDeleted;
|
||||
log.info("removed {} entries for feeds without subscriptions", entriesTotal);
|
||||
} while (entriesDeleted > 0);
|
||||
}
|
||||
deleted = UnitOfWork.run(sessionFactory, () -> feedDAO.delete(feeds));
|
||||
total += deleted;
|
||||
log.info("removed {} feeds without subscriptions", total);
|
||||
} while (deleted != 0);
|
||||
@@ -61,12 +64,7 @@ public class DatabaseCleaningService {
|
||||
long total = 0;
|
||||
int deleted = 0;
|
||||
do {
|
||||
deleted = new UnitOfWork<Integer>(sessionFactory) {
|
||||
@Override
|
||||
protected Integer runInSession() throws Exception {
|
||||
return feedEntryContentDAO.deleteWithoutEntries(BATCH_SIZE);
|
||||
}
|
||||
}.run();
|
||||
deleted = UnitOfWork.run(sessionFactory, () -> feedEntryContentDAO.deleteWithoutEntries(BATCH_SIZE));
|
||||
total += deleted;
|
||||
log.info("removed {} contents without entries", total);
|
||||
} while (deleted != 0);
|
||||
@@ -77,13 +75,8 @@ public class DatabaseCleaningService {
|
||||
public long cleanEntriesForFeedsExceedingCapacity(final int maxFeedCapacity) {
|
||||
long total = 0;
|
||||
while (true) {
|
||||
List<FeedCapacity> feeds = new UnitOfWork<List<FeedCapacity>>(sessionFactory) {
|
||||
@Override
|
||||
protected List<FeedCapacity> runInSession() throws Exception {
|
||||
return feedEntryDAO.findFeedsExceedingCapacity(maxFeedCapacity, BATCH_SIZE);
|
||||
}
|
||||
}.run();
|
||||
|
||||
List<FeedCapacity> feeds = UnitOfWork.run(sessionFactory,
|
||||
() -> feedEntryDAO.findFeedsExceedingCapacity(maxFeedCapacity, BATCH_SIZE));
|
||||
if (feeds.isEmpty()) {
|
||||
break;
|
||||
}
|
||||
@@ -92,12 +85,8 @@ public class DatabaseCleaningService {
|
||||
long remaining = feed.getCapacity() - maxFeedCapacity;
|
||||
do {
|
||||
final long rem = remaining;
|
||||
int deleted = new UnitOfWork<Integer>(sessionFactory) {
|
||||
@Override
|
||||
protected Integer runInSession() throws Exception {
|
||||
return feedEntryDAO.deleteOldEntries(feed.getId(), Math.min(BATCH_SIZE, rem));
|
||||
};
|
||||
}.run();
|
||||
int deleted = UnitOfWork.run(sessionFactory,
|
||||
() -> feedEntryDAO.deleteOldEntries(feed.getId(), Math.min(BATCH_SIZE, rem)));
|
||||
total += deleted;
|
||||
remaining -= deleted;
|
||||
log.info("removed {} entries for feeds exceeding capacity", total);
|
||||
@@ -113,15 +102,10 @@ public class DatabaseCleaningService {
|
||||
long total = 0;
|
||||
int deleted = 0;
|
||||
do {
|
||||
deleted = new UnitOfWork<Integer>(sessionFactory) {
|
||||
@Override
|
||||
protected Integer runInSession() throws Exception {
|
||||
List<FeedEntryStatus> list = feedEntryStatusDAO.getOldStatuses(olderThan, BATCH_SIZE);
|
||||
return feedEntryStatusDAO.delete(list);
|
||||
}
|
||||
}.run();
|
||||
deleted = UnitOfWork.run(sessionFactory,
|
||||
() -> feedEntryStatusDAO.delete(feedEntryStatusDAO.getOldStatuses(olderThan, BATCH_SIZE)));
|
||||
total += deleted;
|
||||
log.info("cleaned {} old read statuses", total);
|
||||
log.info("removed {} old read statuses", total);
|
||||
} while (deleted != 0);
|
||||
log.info("cleanup done: {} old read statuses deleted", total);
|
||||
return total;
|
||||
|
||||
@@ -38,11 +38,6 @@ public class FeedEntryFilteringService {
|
||||
private static JexlEngine initEngine() {
|
||||
// classloader that prevents object creation
|
||||
ClassLoader cl = new ClassLoader() {
|
||||
@Override
|
||||
public Class<?> loadClass(String name) throws ClassNotFoundException {
|
||||
return null;
|
||||
}
|
||||
|
||||
@Override
|
||||
protected Class<?> loadClass(String name, boolean resolve) throws ClassNotFoundException {
|
||||
return null;
|
||||
@@ -94,6 +89,7 @@ public class FeedEntryFilteringService {
|
||||
context.set("content", entry.getContent().getContent() == null ? "" : Jsoup.parse(entry.getContent().getContent()).text()
|
||||
.toLowerCase());
|
||||
context.set("url", entry.getUrl() == null ? "" : entry.getUrl().toLowerCase());
|
||||
context.set("categories", entry.getContent().getCategories() == null ? "" : entry.getContent().getCategories().toLowerCase());
|
||||
|
||||
Callable<Object> callable = script.callable(context);
|
||||
Future<Object> future = executor.submit(callable);
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
package com.commafeed.backend.service;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.Date;
|
||||
import java.util.List;
|
||||
|
||||
@@ -17,7 +18,6 @@ import com.commafeed.backend.model.FeedEntry;
|
||||
import com.commafeed.backend.model.FeedEntryStatus;
|
||||
import com.commafeed.backend.model.FeedSubscription;
|
||||
import com.commafeed.backend.model.User;
|
||||
import com.google.common.collect.Lists;
|
||||
|
||||
@RequiredArgsConstructor(onConstructor = @__({ @Inject }))
|
||||
@Singleton
|
||||
@@ -80,7 +80,7 @@ public class FeedEntryService {
|
||||
}
|
||||
|
||||
private void markList(List<FeedEntryStatus> statuses, Date olderThan) {
|
||||
List<FeedEntryStatus> list = Lists.newArrayList();
|
||||
List<FeedEntryStatus> list = new ArrayList<>();
|
||||
for (FeedEntryStatus status : statuses) {
|
||||
if (!status.isRead()) {
|
||||
Date inserted = status.getEntry().getInserted();
|
||||
|
||||
@@ -1,7 +1,8 @@
|
||||
package com.commafeed.backend.service;
|
||||
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.Set;
|
||||
import java.util.stream.Collectors;
|
||||
|
||||
import javax.inject.Inject;
|
||||
import javax.inject.Singleton;
|
||||
@@ -13,9 +14,6 @@ import com.commafeed.backend.dao.FeedEntryTagDAO;
|
||||
import com.commafeed.backend.model.FeedEntry;
|
||||
import com.commafeed.backend.model.FeedEntryTag;
|
||||
import com.commafeed.backend.model.User;
|
||||
import com.google.common.base.Function;
|
||||
import com.google.common.collect.Lists;
|
||||
import com.google.common.collect.Maps;
|
||||
|
||||
@RequiredArgsConstructor(onConstructor = @__({ @Inject }))
|
||||
@Singleton
|
||||
@@ -30,29 +28,12 @@ public class FeedEntryTagService {
|
||||
return;
|
||||
}
|
||||
|
||||
List<FeedEntryTag> tags = feedEntryTagDAO.findByEntry(user, entry);
|
||||
Map<String, FeedEntryTag> tagMap = Maps.uniqueIndex(tags, new Function<FeedEntryTag, String>() {
|
||||
@Override
|
||||
public String apply(FeedEntryTag input) {
|
||||
return input.getName();
|
||||
}
|
||||
});
|
||||
List<FeedEntryTag> existingTags = feedEntryTagDAO.findByEntry(user, entry);
|
||||
Set<String> existingTagNames = existingTags.stream().map(t -> t.getName()).collect(Collectors.toSet());
|
||||
|
||||
List<FeedEntryTag> addList = Lists.newArrayList();
|
||||
List<FeedEntryTag> removeList = Lists.newArrayList();
|
||||
|
||||
for (String tagName : tagNames) {
|
||||
FeedEntryTag tag = tagMap.get(tagName);
|
||||
if (tag == null) {
|
||||
addList.add(new FeedEntryTag(user, entry, tagName));
|
||||
}
|
||||
}
|
||||
|
||||
for (FeedEntryTag tag : tags) {
|
||||
if (!tagNames.contains(tag.getName())) {
|
||||
removeList.add(tag);
|
||||
}
|
||||
}
|
||||
List<FeedEntryTag> addList = tagNames.stream().filter(name -> !existingTagNames.contains(name))
|
||||
.map(name -> new FeedEntryTag(user, entry, name)).collect(Collectors.toList());
|
||||
List<FeedEntryTag> removeList = existingTags.stream().filter(tag -> !tagNames.contains(tag.getName())).collect(Collectors.toList());
|
||||
|
||||
feedEntryTagDAO.saveOrUpdate(addList);
|
||||
feedEntryTagDAO.delete(removeList);
|
||||
|
||||
@@ -12,6 +12,7 @@ import org.apache.commons.io.IOUtils;
|
||||
|
||||
import com.commafeed.backend.dao.FeedDAO;
|
||||
import com.commafeed.backend.favicon.AbstractFaviconFetcher;
|
||||
import com.commafeed.backend.favicon.AbstractFaviconFetcher.Favicon;
|
||||
import com.commafeed.backend.feed.FeedUtils;
|
||||
import com.commafeed.backend.model.Feed;
|
||||
|
||||
@@ -21,7 +22,7 @@ public class FeedService {
|
||||
private final FeedDAO feedDAO;
|
||||
private final Set<AbstractFaviconFetcher> faviconFetchers;
|
||||
|
||||
private byte[] defaultFavicon;
|
||||
private Favicon defaultFavicon;
|
||||
|
||||
@Inject
|
||||
public FeedService(FeedDAO feedDAO, Set<AbstractFaviconFetcher> faviconFetchers) {
|
||||
@@ -29,7 +30,7 @@ public class FeedService {
|
||||
this.faviconFetchers = faviconFetchers;
|
||||
|
||||
try {
|
||||
defaultFavicon = IOUtils.toByteArray(getClass().getResource("/images/default_favicon.gif"));
|
||||
defaultFavicon = new Favicon(IOUtils.toByteArray(getClass().getResource("/images/default_favicon.gif")), "image/gif");
|
||||
} catch (IOException e) {
|
||||
throw new RuntimeException("could not load default favicon", e);
|
||||
}
|
||||
@@ -49,9 +50,9 @@ public class FeedService {
|
||||
return feed;
|
||||
}
|
||||
|
||||
public byte[] fetchFavicon(Feed feed) {
|
||||
public Favicon fetchFavicon(Feed feed) {
|
||||
|
||||
byte[] icon = null;
|
||||
Favicon icon = null;
|
||||
for (AbstractFaviconFetcher faviconFetcher : faviconFetchers) {
|
||||
icon = faviconFetcher.fetch(feed);
|
||||
if (icon != null) {
|
||||
|
||||
@@ -2,6 +2,7 @@ package com.commafeed.backend.service;
|
||||
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.stream.Collectors;
|
||||
|
||||
import javax.inject.Inject;
|
||||
import javax.inject.Singleton;
|
||||
@@ -23,7 +24,6 @@ import com.commafeed.backend.model.FeedSubscription;
|
||||
import com.commafeed.backend.model.Models;
|
||||
import com.commafeed.backend.model.User;
|
||||
import com.commafeed.frontend.model.UnreadCount;
|
||||
import com.google.common.collect.Maps;
|
||||
|
||||
@Slf4j
|
||||
@RequiredArgsConstructor(onConstructor = @__({ @Inject }))
|
||||
@@ -44,7 +44,15 @@ public class FeedSubscriptionService {
|
||||
private final CacheService cache;
|
||||
private final CommaFeedConfiguration config;
|
||||
|
||||
public Feed subscribe(User user, String url, String title, FeedCategory category) {
|
||||
public Feed subscribe(User user, String url, String title) {
|
||||
return subscribe(user, url, title, null, 0);
|
||||
}
|
||||
|
||||
public Feed subscribe(User user, String url, String title, FeedCategory parent) {
|
||||
return subscribe(user, url, title, parent, 0);
|
||||
}
|
||||
|
||||
public Feed subscribe(User user, String url, String title, FeedCategory category, int position) {
|
||||
|
||||
final String pubUrl = config.getApplicationSettings().getPublicUrl();
|
||||
if (StringUtils.isBlank(pubUrl)) {
|
||||
@@ -63,7 +71,7 @@ public class FeedSubscriptionService {
|
||||
sub.setUser(user);
|
||||
}
|
||||
sub.setCategory(category);
|
||||
sub.setPosition(0);
|
||||
sub.setPosition(position);
|
||||
sub.setTitle(FeedUtils.truncate(title, 128));
|
||||
feedSubscriptionDAO.saveOrUpdate(sub);
|
||||
|
||||
@@ -92,12 +100,7 @@ public class FeedSubscriptionService {
|
||||
}
|
||||
|
||||
public Map<Long, UnreadCount> getUnreadCount(User user) {
|
||||
Map<Long, UnreadCount> map = Maps.newHashMap();
|
||||
List<FeedSubscription> subs = feedSubscriptionDAO.findAll(user);
|
||||
for (FeedSubscription sub : subs) {
|
||||
map.put(sub.getId(), getUnreadCount(user, sub));
|
||||
}
|
||||
return map;
|
||||
return feedSubscriptionDAO.findAll(user).stream().collect(Collectors.toMap(s -> s.getId(), s -> getUnreadCount(user, s)));
|
||||
}
|
||||
|
||||
private UnreadCount getUnreadCount(User user, FeedSubscription sub) {
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
package com.commafeed.backend.service;
|
||||
|
||||
import java.util.Optional;
|
||||
import java.util.Properties;
|
||||
|
||||
import javax.inject.Inject;
|
||||
@@ -17,7 +18,6 @@ import lombok.RequiredArgsConstructor;
|
||||
import com.commafeed.CommaFeedConfiguration;
|
||||
import com.commafeed.CommaFeedConfiguration.ApplicationSettings;
|
||||
import com.commafeed.backend.model.User;
|
||||
import com.google.common.base.Optional;
|
||||
|
||||
/**
|
||||
* Mailing service
|
||||
@@ -35,7 +35,7 @@ public class MailService {
|
||||
|
||||
final String username = settings.getSmtpUserName();
|
||||
final String password = settings.getSmtpPassword();
|
||||
final String fromAddress = Optional.fromNullable(settings.getSmtpFromAddress()).or(settings.getSmtpUserName());
|
||||
final String fromAddress = Optional.ofNullable(settings.getSmtpFromAddress()).orElse(settings.getSmtpUserName());
|
||||
|
||||
String dest = user.getEmail();
|
||||
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
package com.commafeed.backend.service;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
|
||||
import javax.inject.Inject;
|
||||
@@ -26,7 +27,6 @@ import com.commafeed.backend.feed.FeedQueues;
|
||||
import com.commafeed.backend.feed.FeedUtils;
|
||||
import com.commafeed.backend.model.Feed;
|
||||
import com.commafeed.frontend.resource.PubSubHubbubCallbackREST;
|
||||
import com.google.common.collect.Lists;
|
||||
|
||||
/**
|
||||
* Sends push subscription requests. Callback is handled by {@link PubSubHubbubCallbackREST}
|
||||
@@ -57,7 +57,7 @@ public class PubSubService {
|
||||
log.debug("sending new pubsub subscription to {} for {}", hub, topic);
|
||||
|
||||
HttpPost post = new HttpPost(hub);
|
||||
List<NameValuePair> nvp = Lists.newArrayList();
|
||||
List<NameValuePair> nvp = new ArrayList<>();
|
||||
nvp.add(new BasicNameValuePair("hub.callback", publicUrl + "/rest/push/callback"));
|
||||
nvp.add(new BasicNameValuePair("hub.topic", topic));
|
||||
nvp.add(new BasicNameValuePair("hub.mode", "subscribe"));
|
||||
|
||||
@@ -44,15 +44,10 @@ public class StartupService implements Managed {
|
||||
@Override
|
||||
public void start() throws Exception {
|
||||
updateSchema();
|
||||
new UnitOfWork<Void>(sessionFactory) {
|
||||
@Override
|
||||
protected Void runInSession() throws Exception {
|
||||
if (userDAO.count() == 0) {
|
||||
initialData();
|
||||
}
|
||||
return null;
|
||||
}
|
||||
}.run();
|
||||
long count = UnitOfWork.run(sessionFactory, () -> userDAO.count());
|
||||
if (count == 0) {
|
||||
UnitOfWork.run(sessionFactory, () -> initialData());
|
||||
}
|
||||
}
|
||||
|
||||
private void updateSchema() {
|
||||
|
||||
@@ -2,6 +2,8 @@ package com.commafeed.backend.service;
|
||||
|
||||
import java.util.Collection;
|
||||
import java.util.Date;
|
||||
import java.util.Optional;
|
||||
import java.util.Set;
|
||||
import java.util.UUID;
|
||||
|
||||
import javax.inject.Inject;
|
||||
@@ -14,13 +16,14 @@ import org.apache.commons.lang3.StringUtils;
|
||||
|
||||
import com.commafeed.CommaFeedConfiguration;
|
||||
import com.commafeed.backend.dao.FeedCategoryDAO;
|
||||
import com.commafeed.backend.dao.FeedSubscriptionDAO;
|
||||
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.service.internal.PostLoginActivities;
|
||||
import com.google.common.base.Optional;
|
||||
import com.google.common.base.Preconditions;
|
||||
|
||||
@RequiredArgsConstructor(onConstructor = @__({ @Inject }))
|
||||
@@ -28,7 +31,9 @@ import com.google.common.base.Preconditions;
|
||||
public class UserService {
|
||||
|
||||
private final FeedCategoryDAO feedCategoryDAO;
|
||||
private final FeedSubscriptionDAO feedSubscriptionDAO;
|
||||
private final UserDAO userDAO;
|
||||
private final UserRoleDAO userRoleDAO;
|
||||
private final UserSettingsDAO userSettingsDAO;
|
||||
|
||||
private final PasswordEncryptionService encryptionService;
|
||||
@@ -41,7 +46,7 @@ public class UserService {
|
||||
*/
|
||||
public Optional<User> login(String nameOrEmail, String password) {
|
||||
if (nameOrEmail == null || password == null) {
|
||||
return Optional.absent();
|
||||
return Optional.empty();
|
||||
}
|
||||
|
||||
User user = userDAO.findByName(nameOrEmail);
|
||||
@@ -55,7 +60,7 @@ public class UserService {
|
||||
return Optional.of(user);
|
||||
}
|
||||
}
|
||||
return Optional.absent();
|
||||
return Optional.empty();
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -63,7 +68,7 @@ public class UserService {
|
||||
*/
|
||||
public Optional<User> login(String apiKey) {
|
||||
if (apiKey == null) {
|
||||
return Optional.absent();
|
||||
return Optional.empty();
|
||||
}
|
||||
|
||||
User user = userDAO.findByApiKey(apiKey);
|
||||
@@ -71,7 +76,7 @@ public class UserService {
|
||||
performPostLoginActivities(user);
|
||||
return Optional.of(user);
|
||||
}
|
||||
return Optional.absent();
|
||||
return Optional.empty();
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -114,16 +119,18 @@ public class UserService {
|
||||
user.setCreated(new Date());
|
||||
user.setSalt(salt);
|
||||
user.setPassword(encryptionService.getEncryptedPassword(password, salt));
|
||||
for (Role role : roles) {
|
||||
user.getRoles().add(new UserRole(user, role));
|
||||
}
|
||||
userDAO.saveOrUpdate(user);
|
||||
for (Role role : roles) {
|
||||
userRoleDAO.saveOrUpdate(new UserRole(user, role));
|
||||
}
|
||||
return user;
|
||||
}
|
||||
|
||||
public void unregister(User user) {
|
||||
feedCategoryDAO.delete(feedCategoryDAO.findAll(user));
|
||||
userSettingsDAO.delete(userSettingsDAO.findByUser(user));
|
||||
userRoleDAO.delete(userRoleDAO.findAll(user));
|
||||
feedSubscriptionDAO.delete(feedSubscriptionDAO.findAll(user));
|
||||
userDAO.delete(user);
|
||||
}
|
||||
|
||||
@@ -131,4 +138,8 @@ public class UserService {
|
||||
byte[] key = encryptionService.getEncryptedPassword(UUID.randomUUID().toString(), user.getSalt());
|
||||
return DigestUtils.sha1Hex(key);
|
||||
}
|
||||
|
||||
public Set<Role> getRoles(User user) {
|
||||
return userRoleDAO.findRoles(user);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -39,7 +39,7 @@ public class PostLoginActivities {
|
||||
saveUser = true;
|
||||
}
|
||||
if (saveUser) {
|
||||
userDAO.merge(user);
|
||||
userDAO.saveOrUpdate(user);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -1,5 +1,8 @@
|
||||
package com.commafeed.frontend.auth;
|
||||
|
||||
import java.util.Optional;
|
||||
import java.util.Set;
|
||||
|
||||
import javax.inject.Inject;
|
||||
import javax.servlet.http.HttpServletRequest;
|
||||
import javax.ws.rs.WebApplicationException;
|
||||
@@ -18,7 +21,6 @@ import com.commafeed.backend.model.User;
|
||||
import com.commafeed.backend.model.UserRole.Role;
|
||||
import com.commafeed.backend.service.UserService;
|
||||
import com.commafeed.frontend.session.SessionHelper;
|
||||
import com.google.common.base.Optional;
|
||||
|
||||
@RequiredArgsConstructor
|
||||
public class SecurityCheckFactory extends AbstractContainerRequestValueFactory<User> {
|
||||
@@ -45,7 +47,8 @@ public class SecurityCheckFactory extends AbstractContainerRequestValueFactory<U
|
||||
}
|
||||
|
||||
if (user.isPresent()) {
|
||||
if (user.get().hasRole(role)) {
|
||||
Set<Role> roles = userService.getRoles(user.get());
|
||||
if (roles.contains(role)) {
|
||||
return user.get();
|
||||
} else {
|
||||
throw new WebApplicationException(Response.status(Response.Status.FORBIDDEN)
|
||||
@@ -82,7 +85,7 @@ public class SecurityCheckFactory extends AbstractContainerRequestValueFactory<U
|
||||
}
|
||||
}
|
||||
}
|
||||
return Optional.absent();
|
||||
return Optional.empty();
|
||||
}
|
||||
|
||||
private Optional<User> apiKeyLogin() {
|
||||
@@ -90,7 +93,7 @@ public class SecurityCheckFactory extends AbstractContainerRequestValueFactory<U
|
||||
if (apiKey != null && apiKeyAllowed) {
|
||||
return userService.login(apiKey);
|
||||
}
|
||||
return Optional.absent();
|
||||
return Optional.empty();
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -1,11 +1,11 @@
|
||||
package com.commafeed.frontend.model;
|
||||
|
||||
import java.io.Serializable;
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
|
||||
import lombok.Data;
|
||||
|
||||
import com.google.common.collect.Lists;
|
||||
import com.wordnik.swagger.annotations.ApiModel;
|
||||
import com.wordnik.swagger.annotations.ApiModelProperty;
|
||||
|
||||
@@ -24,10 +24,10 @@ public class Category implements Serializable {
|
||||
private String name;
|
||||
|
||||
@ApiModelProperty("category children categories")
|
||||
private List<Category> children = Lists.newArrayList();
|
||||
private List<Category> children = new ArrayList<>();
|
||||
|
||||
@ApiModelProperty("category feeds")
|
||||
private List<Subscription> feeds = Lists.newArrayList();
|
||||
private List<Subscription> feeds = new ArrayList<>();
|
||||
|
||||
@ApiModelProperty("wether the category is expanded or collapsed")
|
||||
private boolean expanded;
|
||||
|
||||
@@ -1,11 +1,11 @@
|
||||
package com.commafeed.frontend.model;
|
||||
|
||||
import java.io.Serializable;
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
|
||||
import lombok.Data;
|
||||
|
||||
import com.google.common.collect.Lists;
|
||||
import com.wordnik.swagger.annotations.ApiModel;
|
||||
import com.wordnik.swagger.annotations.ApiModelProperty;
|
||||
|
||||
@@ -39,7 +39,7 @@ public class Entries implements Serializable {
|
||||
private int limit;
|
||||
|
||||
@ApiModelProperty("list of entries")
|
||||
private List<Entry> entries = Lists.newArrayList();
|
||||
private List<Entry> entries = new ArrayList<>();
|
||||
|
||||
@ApiModelProperty("if true, the unread flag was ignored in the request, all entries are returned regardless of their read status")
|
||||
private boolean ignoredReadStatus;
|
||||
|
||||
@@ -4,6 +4,7 @@ import java.io.Serializable;
|
||||
import java.util.Arrays;
|
||||
import java.util.Date;
|
||||
import java.util.List;
|
||||
import java.util.stream.Collectors;
|
||||
|
||||
import lombok.Data;
|
||||
|
||||
@@ -11,9 +12,7 @@ import com.commafeed.backend.feed.FeedUtils;
|
||||
import com.commafeed.backend.model.FeedEntry;
|
||||
import com.commafeed.backend.model.FeedEntryContent;
|
||||
import com.commafeed.backend.model.FeedEntryStatus;
|
||||
import com.commafeed.backend.model.FeedEntryTag;
|
||||
import com.commafeed.backend.model.FeedSubscription;
|
||||
import com.google.common.collect.Lists;
|
||||
import com.rometools.rome.feed.synd.SyndContent;
|
||||
import com.rometools.rome.feed.synd.SyndContentImpl;
|
||||
import com.rometools.rome.feed.synd.SyndEnclosure;
|
||||
@@ -48,12 +47,7 @@ public class Entry implements Serializable {
|
||||
entry.setFeedUrl(sub.getFeed().getUrl());
|
||||
entry.setFeedLink(sub.getFeed().getLink());
|
||||
entry.setIconUrl(FeedUtils.getFaviconUrl(sub, publicUrl));
|
||||
|
||||
List<String> tags = Lists.newArrayList();
|
||||
for (FeedEntryTag tag : status.getTags()) {
|
||||
tags.add(tag.getName());
|
||||
}
|
||||
entry.setTags(tags);
|
||||
entry.setTags(status.getTags().stream().map(t -> t.getName()).collect(Collectors.toList()));
|
||||
|
||||
if (content != null) {
|
||||
entry.setRtl(FeedUtils.isRTL(feedEntry));
|
||||
@@ -62,6 +56,7 @@ public class Entry implements Serializable {
|
||||
entry.setAuthor(content.getAuthor());
|
||||
entry.setEnclosureUrl(content.getEnclosureUrl());
|
||||
entry.setEnclosureType(content.getEnclosureType());
|
||||
entry.setCategories(content.getCategories());
|
||||
}
|
||||
|
||||
return entry;
|
||||
@@ -101,6 +96,9 @@ public class Entry implements Serializable {
|
||||
@ApiModelProperty("entry content")
|
||||
private String content;
|
||||
|
||||
@ApiModelProperty("comma-separated list of categories")
|
||||
private String categories;
|
||||
|
||||
@ApiModelProperty("wether entry content and title are rtl")
|
||||
private boolean rtl;
|
||||
|
||||
|
||||
@@ -2,6 +2,7 @@ package com.commafeed.frontend.resource;
|
||||
|
||||
import io.dropwizard.hibernate.UnitOfWork;
|
||||
|
||||
import java.util.HashMap;
|
||||
import java.util.Map;
|
||||
import java.util.Set;
|
||||
|
||||
@@ -36,7 +37,6 @@ import com.commafeed.frontend.auth.SecurityCheck;
|
||||
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;
|
||||
@@ -123,11 +123,7 @@ public class AdminREST {
|
||||
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);
|
||||
}
|
||||
}
|
||||
userModel.setAdmin(userRoleDAO.findAll(u).stream().anyMatch(r -> r.getRole() == Role.ADMIN));
|
||||
return Response.ok(userModel).build();
|
||||
}
|
||||
|
||||
@@ -136,7 +132,7 @@ public class AdminREST {
|
||||
@UnitOfWork
|
||||
@ApiOperation(value = "Get all users", notes = "Get all users", response = UserModel.class, responseContainer = "List")
|
||||
public Response getUsers(@SecurityCheck(Role.ADMIN) User user) {
|
||||
Map<Long, UserModel> users = Maps.newHashMap();
|
||||
Map<Long, UserModel> users = new HashMap<>();
|
||||
for (UserRole role : userRoleDAO.findAll()) {
|
||||
User u = role.getUser();
|
||||
Long key = u.getId();
|
||||
|
||||
@@ -3,6 +3,7 @@ package com.commafeed.frontend.resource;
|
||||
import io.dropwizard.hibernate.UnitOfWork;
|
||||
|
||||
import java.io.StringWriter;
|
||||
import java.util.Arrays;
|
||||
import java.util.Collections;
|
||||
import java.util.Comparator;
|
||||
import java.util.Date;
|
||||
@@ -10,6 +11,8 @@ import java.util.Iterator;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.Objects;
|
||||
import java.util.Optional;
|
||||
import java.util.stream.Collectors;
|
||||
|
||||
import javax.inject.Inject;
|
||||
import javax.inject.Singleton;
|
||||
@@ -57,10 +60,8 @@ 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.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;
|
||||
@@ -127,14 +128,11 @@ public class CategoryREST {
|
||||
|
||||
List<Long> excludedIds = null;
|
||||
if (StringUtils.isNotEmpty(excludedSubscriptionIds)) {
|
||||
excludedIds = Lists.newArrayList();
|
||||
for (String excludedId : excludedSubscriptionIds.split(",")) {
|
||||
excludedIds.add(Long.valueOf(excludedId));
|
||||
}
|
||||
excludedIds = Arrays.stream(excludedSubscriptionIds.split(",")).map(Long::valueOf).collect(Collectors.toList());
|
||||
}
|
||||
|
||||
if (ALL.equals(id)) {
|
||||
entries.setName(Optional.fromNullable(tag).or("All"));
|
||||
entries.setName(Optional.ofNullable(tag).orElse("All"));
|
||||
List<FeedSubscription> subs = feedSubscriptionDAO.findAll(user);
|
||||
removeExcludedSubscriptions(subs, excludedIds);
|
||||
List<FeedEntryStatus> list = feedEntryStatusDAO.findBySubscriptions(user, subs, unreadOnly, entryKeywords, newerThanDate,
|
||||
@@ -216,14 +214,8 @@ public class CategoryREST {
|
||||
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);
|
||||
feed.setLink(config.getApplicationSettings().getPublicUrl());
|
||||
feed.setEntries(entries.getEntries().stream().map(e -> e.asRss()).collect(Collectors.toList()));
|
||||
|
||||
SyndFeedOutput output = new SyndFeedOutput();
|
||||
StringWriter writer = new StringWriter();
|
||||
|
||||
@@ -12,6 +12,8 @@ import java.util.Comparator;
|
||||
import java.util.Date;
|
||||
import java.util.List;
|
||||
import java.util.Objects;
|
||||
import java.util.Optional;
|
||||
import java.util.stream.Collectors;
|
||||
|
||||
import javax.inject.Inject;
|
||||
import javax.inject.Singleton;
|
||||
@@ -44,6 +46,7 @@ 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.favicon.AbstractFaviconFetcher.Favicon;
|
||||
import com.commafeed.backend.feed.FeedEntryKeyword;
|
||||
import com.commafeed.backend.feed.FeedFetcher;
|
||||
import com.commafeed.backend.feed.FeedQueues;
|
||||
@@ -78,9 +81,7 @@ 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.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;
|
||||
@@ -218,14 +219,8 @@ public class FeedREST {
|
||||
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);
|
||||
feed.setLink(config.getApplicationSettings().getPublicUrl());
|
||||
feed.setEntries(entries.getEntries().stream().map(e -> e.asRss()).collect(Collectors.toList()));
|
||||
|
||||
SyndFeedOutput output = new SyndFeedOutput();
|
||||
StringWriter writer = new StringWriter();
|
||||
@@ -346,9 +341,9 @@ public class FeedREST {
|
||||
return Response.status(Status.NOT_FOUND).build();
|
||||
}
|
||||
Feed feed = subscription.getFeed();
|
||||
byte[] icon = feedService.fetchFavicon(feed);
|
||||
Favicon icon = feedService.fetchFavicon(feed);
|
||||
|
||||
ResponseBuilder builder = Response.ok(icon, "image/x-icon");
|
||||
ResponseBuilder builder = Response.ok(icon.getIcon(), Optional.ofNullable(icon.getMediaType()).orElse("image/x-icon"));
|
||||
|
||||
CacheControl cacheControl = new CacheControl();
|
||||
cacheControl.setMaxAge(2592000);
|
||||
@@ -402,7 +397,7 @@ public class FeedREST {
|
||||
url = fetchFeedInternal(url).getUrl();
|
||||
|
||||
FeedInfo info = fetchFeedInternal(url);
|
||||
feedSubscriptionService.subscribe(user, info.getUrl(), info.getTitle(), null);
|
||||
feedSubscriptionService.subscribe(user, info.getUrl(), info.getTitle());
|
||||
} catch (Exception e) {
|
||||
log.info("Could not subscribe to url {} : {}", url, e.getMessage());
|
||||
}
|
||||
@@ -443,8 +438,7 @@ public class FeedREST {
|
||||
try {
|
||||
feedEntryFilteringService.filterMatchesEntry(req.getFilter(), TEST_ENTRY);
|
||||
} catch (FeedEntryFilterException e) {
|
||||
Throwable root = Throwables.getRootCause(e);
|
||||
return Response.status(Status.BAD_REQUEST).entity(root.getMessage()).type(MediaType.TEXT_PLAIN).build();
|
||||
return Response.status(Status.BAD_REQUEST).entity(e.getCause().getMessage()).type(MediaType.TEXT_PLAIN).build();
|
||||
}
|
||||
|
||||
FeedSubscription subscription = feedSubscriptionDAO.findById(user, req.getId());
|
||||
|
||||
@@ -6,6 +6,7 @@ import io.dropwizard.jersey.validation.ValidationErrorMessage;
|
||||
import java.util.Arrays;
|
||||
import java.util.Collections;
|
||||
import java.util.Date;
|
||||
import java.util.Optional;
|
||||
import java.util.UUID;
|
||||
|
||||
import javax.inject.Inject;
|
||||
@@ -56,7 +57,6 @@ import com.commafeed.frontend.model.request.PasswordResetRequest;
|
||||
import com.commafeed.frontend.model.request.ProfileModificationRequest;
|
||||
import com.commafeed.frontend.model.request.RegistrationRequest;
|
||||
import com.commafeed.frontend.session.SessionHelper;
|
||||
import com.google.common.base.Optional;
|
||||
import com.google.common.base.Preconditions;
|
||||
import com.google.common.collect.ImmutableList;
|
||||
import com.wordnik.swagger.annotations.Api;
|
||||
@@ -215,7 +215,7 @@ public class UserREST {
|
||||
if (request.isNewApiKey()) {
|
||||
user.setApiKey(userService.generateApiKey(user));
|
||||
}
|
||||
userDAO.merge(user);
|
||||
userDAO.saveOrUpdate(user);
|
||||
return Response.ok().build();
|
||||
}
|
||||
|
||||
@@ -235,7 +235,7 @@ public class UserREST {
|
||||
public ImmutableList<String> getErrors() {
|
||||
return ImmutableList.of(e.getMessage());
|
||||
}
|
||||
}).build();
|
||||
}).type(MediaType.TEXT_PLAIN).build();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -260,7 +260,7 @@ public class UserREST {
|
||||
public Response sendPasswordReset(@Valid PasswordResetRequest req) {
|
||||
User user = userDAO.findByEmail(req.getEmail());
|
||||
if (user == null) {
|
||||
return Response.status(Status.PRECONDITION_FAILED).entity("Email not found.").build();
|
||||
return Response.status(Status.PRECONDITION_FAILED).entity("Email not found.").type(MediaType.TEXT_PLAIN).build();
|
||||
}
|
||||
try {
|
||||
user.setRecoverPasswordToken(DigestUtils.sha1Hex(UUID.randomUUID().toString()));
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
package com.commafeed.frontend.servlet;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.util.Optional;
|
||||
|
||||
import javax.inject.Inject;
|
||||
import javax.inject.Singleton;
|
||||
@@ -18,7 +19,6 @@ import com.commafeed.backend.dao.UserSettingsDAO;
|
||||
import com.commafeed.backend.model.User;
|
||||
import com.commafeed.backend.model.UserSettings;
|
||||
import com.commafeed.frontend.session.SessionHelper;
|
||||
import com.google.common.base.Optional;
|
||||
|
||||
@SuppressWarnings("serial")
|
||||
@RequiredArgsConstructor(onConstructor = @__({ @Inject }))
|
||||
@@ -32,23 +32,12 @@ public class CustomCssServlet extends HttpServlet {
|
||||
protected void doGet(final HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
|
||||
resp.setContentType("text/css");
|
||||
|
||||
final Optional<User> user = new UnitOfWork<Optional<User>>(sessionFactory) {
|
||||
@Override
|
||||
protected Optional<User> runInSession() throws Exception {
|
||||
return new SessionHelper(req).getLoggedInUser();
|
||||
}
|
||||
}.run();
|
||||
final Optional<User> user = new SessionHelper(req).getLoggedInUser();
|
||||
if (!user.isPresent()) {
|
||||
return;
|
||||
}
|
||||
|
||||
UserSettings settings = new UnitOfWork<UserSettings>(sessionFactory) {
|
||||
@Override
|
||||
protected UserSettings runInSession() {
|
||||
return userSettingsDAO.findByUser(user.get());
|
||||
}
|
||||
}.run();
|
||||
|
||||
UserSettings settings = UnitOfWork.run(sessionFactory, () -> userSettingsDAO.findByUser(user.get()));
|
||||
if (settings == null || settings.getCustomCss() == null) {
|
||||
return;
|
||||
}
|
||||
|
||||
@@ -2,6 +2,7 @@ package com.commafeed.frontend.servlet;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.util.List;
|
||||
import java.util.Optional;
|
||||
|
||||
import javax.inject.Inject;
|
||||
import javax.inject.Singleton;
|
||||
@@ -28,7 +29,6 @@ import com.commafeed.backend.model.UserSettings.ReadingOrder;
|
||||
import com.commafeed.backend.service.UserService;
|
||||
import com.commafeed.frontend.resource.CategoryREST;
|
||||
import com.commafeed.frontend.session.SessionHelper;
|
||||
import com.google.common.base.Optional;
|
||||
import com.google.common.collect.Iterables;
|
||||
|
||||
@SuppressWarnings("serial")
|
||||
@@ -51,17 +51,11 @@ public class NextUnreadServlet extends HttpServlet {
|
||||
final String categoryId = req.getParameter(PARAM_CATEGORYID);
|
||||
String orderParam = req.getParameter(PARAM_READINGORDER);
|
||||
|
||||
final Optional<User> user = new UnitOfWork<Optional<User>>(sessionFactory) {
|
||||
@Override
|
||||
protected Optional<User> runInSession() throws Exception {
|
||||
SessionHelper sessionHelper = new SessionHelper(req);
|
||||
Optional<User> loggedInUser = sessionHelper.getLoggedInUser();
|
||||
if (loggedInUser.isPresent()) {
|
||||
userService.performPostLoginActivities(loggedInUser.get());
|
||||
}
|
||||
return loggedInUser;
|
||||
}
|
||||
}.run();
|
||||
SessionHelper sessionHelper = new SessionHelper(req);
|
||||
Optional<User> user = sessionHelper.getLoggedInUser();
|
||||
if (user.isPresent()) {
|
||||
UnitOfWork.run(sessionFactory, () -> userService.performPostLoginActivities(user.get()));
|
||||
}
|
||||
if (!user.isPresent()) {
|
||||
resp.sendRedirect(resp.encodeRedirectURL(config.getApplicationSettings().getPublicUrl()));
|
||||
return;
|
||||
@@ -69,32 +63,31 @@ public class NextUnreadServlet extends HttpServlet {
|
||||
|
||||
final ReadingOrder order = StringUtils.equals(orderParam, "asc") ? ReadingOrder.asc : ReadingOrder.desc;
|
||||
|
||||
FeedEntryStatus status = new UnitOfWork<FeedEntryStatus>(sessionFactory) {
|
||||
@Override
|
||||
protected FeedEntryStatus runInSession() throws Exception {
|
||||
FeedEntryStatus status = null;
|
||||
if (StringUtils.isBlank(categoryId) || CategoryREST.ALL.equals(categoryId)) {
|
||||
List<FeedSubscription> subs = feedSubscriptionDAO.findAll(user.get());
|
||||
List<FeedEntryStatus> statuses = feedEntryStatusDAO.findBySubscriptions(user.get(), subs, true, null, null, 0, 1,
|
||||
order, true, false, null);
|
||||
status = Iterables.getFirst(statuses, null);
|
||||
} else {
|
||||
FeedCategory category = feedCategoryDAO.findById(user.get(), Long.valueOf(categoryId));
|
||||
if (category != null) {
|
||||
List<FeedCategory> children = feedCategoryDAO.findAllChildrenCategories(user.get(), category);
|
||||
List<FeedSubscription> subscriptions = feedSubscriptionDAO.findByCategories(user.get(), children);
|
||||
List<FeedEntryStatus> statuses = feedEntryStatusDAO.findBySubscriptions(user.get(), subscriptions, true, null,
|
||||
null, 0, 1, order, true, false, null);
|
||||
status = Iterables.getFirst(statuses, null);
|
||||
FeedEntryStatus status = UnitOfWork.run(
|
||||
sessionFactory,
|
||||
() -> {
|
||||
FeedEntryStatus s = null;
|
||||
if (StringUtils.isBlank(categoryId) || CategoryREST.ALL.equals(categoryId)) {
|
||||
List<FeedSubscription> subs = feedSubscriptionDAO.findAll(user.get());
|
||||
List<FeedEntryStatus> statuses = feedEntryStatusDAO.findBySubscriptions(user.get(), subs, true, null, null, 0, 1,
|
||||
order, true, false, null);
|
||||
s = Iterables.getFirst(statuses, null);
|
||||
} else {
|
||||
FeedCategory category = feedCategoryDAO.findById(user.get(), Long.valueOf(categoryId));
|
||||
if (category != null) {
|
||||
List<FeedCategory> children = feedCategoryDAO.findAllChildrenCategories(user.get(), category);
|
||||
List<FeedSubscription> subscriptions = feedSubscriptionDAO.findByCategories(user.get(), children);
|
||||
List<FeedEntryStatus> statuses = feedEntryStatusDAO.findBySubscriptions(user.get(), subscriptions, true, null,
|
||||
null, 0, 1, order, true, false, null);
|
||||
s = Iterables.getFirst(statuses, null);
|
||||
}
|
||||
}
|
||||
}
|
||||
if (status != null) {
|
||||
status.setRead(true);
|
||||
feedEntryStatusDAO.saveOrUpdate(status);
|
||||
}
|
||||
return status;
|
||||
}
|
||||
}.run();
|
||||
if (s != null) {
|
||||
s.setRead(true);
|
||||
feedEntryStatusDAO.saveOrUpdate(s);
|
||||
}
|
||||
return s;
|
||||
});
|
||||
|
||||
if (status == null) {
|
||||
resp.sendRedirect(resp.encodeRedirectURL(config.getApplicationSettings().getPublicUrl()));
|
||||
@@ -103,5 +96,4 @@ public class NextUnreadServlet extends HttpServlet {
|
||||
resp.sendRedirect(resp.encodeRedirectURL(url));
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -1,39 +1,40 @@
|
||||
package com.commafeed.frontend.session;
|
||||
|
||||
import java.util.Optional;
|
||||
|
||||
import javax.servlet.http.HttpServletRequest;
|
||||
import javax.servlet.http.HttpSession;
|
||||
|
||||
import lombok.RequiredArgsConstructor;
|
||||
|
||||
import com.commafeed.backend.model.User;
|
||||
import com.google.common.base.Optional;
|
||||
|
||||
@RequiredArgsConstructor()
|
||||
public class SessionHelper {
|
||||
|
||||
|
||||
private static final String SESSION_KEY_USER = "user";
|
||||
|
||||
|
||||
private final HttpServletRequest request;
|
||||
|
||||
|
||||
public Optional<User> getLoggedInUser() {
|
||||
Optional<HttpSession> session = getSession(false);
|
||||
|
||||
|
||||
if (session.isPresent()) {
|
||||
User user = (User) session.get().getAttribute(SESSION_KEY_USER);
|
||||
return Optional.fromNullable(user);
|
||||
return Optional.ofNullable(user);
|
||||
}
|
||||
|
||||
return Optional.absent();
|
||||
|
||||
return Optional.empty();
|
||||
}
|
||||
|
||||
|
||||
public void setLoggedInUser(User user) {
|
||||
Optional<HttpSession> session = getSession(true);
|
||||
session.get().setAttribute(SESSION_KEY_USER, user);
|
||||
}
|
||||
|
||||
|
||||
private Optional<HttpSession> getSession(boolean force) {
|
||||
HttpSession session = request.getSession(force);
|
||||
return Optional.fromNullable(session);
|
||||
return Optional.ofNullable(session);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -168,7 +168,7 @@ public class URLCanonicalizer {
|
||||
return "";
|
||||
}
|
||||
|
||||
final StringBuffer sb = new StringBuffer(100);
|
||||
final StringBuilder sb = new StringBuilder(100);
|
||||
for (Map.Entry<String, String> pair : sortedParamMap.entrySet()) {
|
||||
final String key = pair.getKey().toLowerCase();
|
||||
if (key.equals("jsessionid") || key.equals("phpsessid") || key.equals("aspsessionid")) {
|
||||
|
||||
@@ -5,6 +5,7 @@
|
||||
|
||||
<changeSet author="athou" id="create-app-settings">
|
||||
<validCheckSum>7:6d3ad493d25dd9c50067e804efc9ffcc</validCheckSum>
|
||||
<validCheckSum>7:896a68c1651397288c40f717ce0397b4</validCheckSum>
|
||||
<preConditions onFail="MARK_RAN" onFailMessage="existing table skipping">
|
||||
<not>
|
||||
<tableExists tableName="APPLICATIONSETTINGS" />
|
||||
@@ -56,6 +57,7 @@
|
||||
|
||||
<changeSet author="athou" id="create-ffe">
|
||||
<validCheckSum>7:eccd6b37116ab35ee963aa46152e1ae5</validCheckSum>
|
||||
<validCheckSum>7:ac622ab04aec79a7e5854d25511abaef</validCheckSum>
|
||||
<preConditions onFail="MARK_RAN" onFailMessage="existing table skipping">
|
||||
<not>
|
||||
<tableExists tableName="FEED_FEEDENTRIES" />
|
||||
@@ -76,6 +78,7 @@
|
||||
|
||||
<changeSet author="athou" id="create-cat">
|
||||
<validCheckSum>7:93155e15f0feabe936e1de35711bf85b</validCheckSum>
|
||||
<validCheckSum>7:c52f258e54d34156208cbfd2d8547fbd</validCheckSum>
|
||||
<preConditions onFail="MARK_RAN" onFailMessage="existing table skipping">
|
||||
<not>
|
||||
<tableExists tableName="FEEDCATEGORIES" />
|
||||
@@ -104,6 +107,7 @@
|
||||
|
||||
<changeSet author="athou" id="create-entries">
|
||||
<validCheckSum>7:2d9e82da5573ac551df31a13f3bc40e5</validCheckSum>
|
||||
<validCheckSum>7:c3cc179801e812635b53849301a1a1d1</validCheckSum>
|
||||
<preConditions onFail="MARK_RAN" onFailMessage="existing table skipping">
|
||||
<not>
|
||||
<tableExists tableName="FEEDENTRIES" />
|
||||
@@ -133,6 +137,7 @@
|
||||
|
||||
<changeSet author="athou" id="create-contents">
|
||||
<validCheckSum>7:a2d83b0f7d1bf97a7553e94dd6100edf</validCheckSum>
|
||||
<validCheckSum>7:1c45f6b6a6e7583dd4c090a4a3930758</validCheckSum>
|
||||
<preConditions onFail="MARK_RAN" onFailMessage="existing table skipping">
|
||||
<not>
|
||||
<tableExists tableName="FEEDENTRYCONTENTS" />
|
||||
@@ -154,6 +159,7 @@
|
||||
|
||||
<changeSet author="athou" id="create-statuses">
|
||||
<validCheckSum>7:a9cf194a01c16b937a897aea934f09ae</validCheckSum>
|
||||
<validCheckSum>7:6a386e0b08e98bdba9ce55e26ab90eba</validCheckSum>
|
||||
<preConditions onFail="MARK_RAN" onFailMessage="existing table skipping">
|
||||
<not>
|
||||
<tableExists tableName="FEEDENTRYSTATUSES" />
|
||||
@@ -181,6 +187,7 @@
|
||||
|
||||
<changeSet author="athou" id="create-feeds">
|
||||
<validCheckSum>7:e3a44d2e0f774dcb4efe36702c8d5f3f</validCheckSum>
|
||||
<validCheckSum>7:604b2bb0b62b7f0529e50e63c2b2cf0c</validCheckSum>
|
||||
<preConditions onFail="MARK_RAN" onFailMessage="existing table skipping">
|
||||
<not>
|
||||
<tableExists tableName="FEEDS" />
|
||||
@@ -221,6 +228,7 @@
|
||||
|
||||
<changeSet author="athou" id="create-subs">
|
||||
<validCheckSum>7:36e92eac052c7d2ce0ef75e3ec2cdf8d</validCheckSum>
|
||||
<validCheckSum>7:248affcafffd2243f8b0d16750e17af0</validCheckSum>
|
||||
<preConditions onFail="MARK_RAN" onFailMessage="existing table skipping">
|
||||
<not>
|
||||
<tableExists tableName="FEEDSUBSCRIPTIONS" />
|
||||
@@ -249,6 +257,7 @@
|
||||
|
||||
<changeSet author="athou" id="create-seq">
|
||||
<validCheckSum>7:6d68765b2116ba88680d69c03b3cefd2</validCheckSum>
|
||||
<validCheckSum>7:6112f92b437b4d0ecfcdf038fd04ed2f</validCheckSum>
|
||||
<preConditions onFail="MARK_RAN" onFailMessage="existing table skipping">
|
||||
<not>
|
||||
<tableExists tableName="hibernate_sequences" />
|
||||
@@ -265,6 +274,7 @@
|
||||
|
||||
<changeSet author="athou" id="create-roles">
|
||||
<validCheckSum>7:eefc98cfa1b9bbf51fa6acd7a0d49c1b</validCheckSum>
|
||||
<validCheckSum>7:abbff58b88c8cebfb4548d17730a262d</validCheckSum>
|
||||
<preConditions onFail="MARK_RAN" onFailMessage="existing table skipping">
|
||||
<not>
|
||||
<tableExists tableName="USERROLES" />
|
||||
@@ -287,6 +297,7 @@
|
||||
</changeSet>
|
||||
|
||||
<changeSet author="athou" id="create-users">
|
||||
<validCheckSum>7:750e0990a8edebd0252df7d4adc7aa7c</validCheckSum>
|
||||
<preConditions onFail="MARK_RAN" onFailMessage="existing table skipping">
|
||||
<not>
|
||||
<tableExists tableName="USERS" />
|
||||
@@ -321,6 +332,7 @@
|
||||
|
||||
<changeSet author="athou" id="create-user-settings">
|
||||
<validCheckSum>7:985d6607a4350e032ea345d9a2f2f0c0</validCheckSum>
|
||||
<validCheckSum>7:722eaff49d04d43c5b26da0929d3f707</validCheckSum>
|
||||
<preConditions onFail="MARK_RAN" onFailMessage="existing table skipping">
|
||||
<not>
|
||||
<tableExists tableName="USERSETTINGS" />
|
||||
|
||||
11
src/main/resources/changelogs/db.changelog-2.2.xml
Normal file
11
src/main/resources/changelogs/db.changelog-2.2.xml
Normal file
@@ -0,0 +1,11 @@
|
||||
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
|
||||
<databaseChangeLog xmlns="http://www.liquibase.org/xml/ns/dbchangelog" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
|
||||
xsi:schemaLocation="http://www.liquibase.org/xml/ns/dbchangelog http://www.liquibase.org/xml/ns/dbchangelog/dbchangelog-3.1.xsd">
|
||||
|
||||
<changeSet id="add-content-categories" author="athou">
|
||||
<addColumn tableName="FEEDENTRYCONTENTS">
|
||||
<column name="categories" type="varchar(4096)" />
|
||||
</addColumn>
|
||||
</changeSet>
|
||||
|
||||
</databaseChangeLog>
|
||||
@@ -10,5 +10,6 @@
|
||||
<include file="changelogs/db.changelog-1.4.xml" />
|
||||
<include file="changelogs/db.changelog-1.5.xml" />
|
||||
<include file="changelogs/db.changelog-2.1.xml" />
|
||||
<include file="changelogs/db.changelog-2.2.xml" />
|
||||
|
||||
</databaseChangeLog>
|
||||
@@ -44,11 +44,21 @@ public class FeedUtilsTest {
|
||||
@Test
|
||||
public void testToAbsoluteUrl() {
|
||||
String expected = "http://a.com/blog/entry/1";
|
||||
|
||||
// usual cases
|
||||
Assert.assertEquals(expected, FeedUtils.toAbsoluteUrl("http://a.com/blog/entry/1", "http://a.com/feed/", "http://a.com/feed/"));
|
||||
Assert.assertEquals(expected, FeedUtils.toAbsoluteUrl("http://a.com/blog/entry/1", "http://a.com/feed", "http://a.com/feed"));
|
||||
|
||||
// relative links
|
||||
Assert.assertEquals(expected, FeedUtils.toAbsoluteUrl("../blog/entry/1", "http://a.com/feed/", "http://a.com/feed/"));
|
||||
Assert.assertEquals(expected, FeedUtils.toAbsoluteUrl("../blog/entry/1", "feed.xml", "http://a.com/feed/feed.xml"));
|
||||
|
||||
// root-relative links
|
||||
Assert.assertEquals(expected, FeedUtils.toAbsoluteUrl("/blog/entry/1", "/feed", "http://a.com/feed"));
|
||||
|
||||
// real cases
|
||||
Assert.assertEquals("https://github.com/erusev/parsedown/releases/tag/1.3.0", FeedUtils.toAbsoluteUrl(
|
||||
"/erusev/parsedown/releases/tag/1.3.0", "/erusev/parsedown/releases", "https://github.com/erusev/parsedown/tags.atom"));
|
||||
Assert.assertEquals("http://ergoemacs.org/emacs/elisp_all_about_lines.html",
|
||||
FeedUtils.toAbsoluteUrl("elisp_all_about_lines.html", "blog.xml", "http://ergoemacs.org/emacs/blog.xml"));
|
||||
|
||||
@@ -62,4 +72,10 @@ public class FeedUtilsTest {
|
||||
Assert.assertEquals("UTF-8", FeedUtils.extractDeclaredEncoding("<?xml encoding='UTF-8' ?>".getBytes()));
|
||||
Assert.assertEquals("UTF-8", FeedUtils.extractDeclaredEncoding("<?xml encoding='UTF-8'?>".getBytes()));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testReplaceHtmlEntitiesWithNumericEntities() {
|
||||
String source = "<source>T´l´phone ′</source>";
|
||||
Assert.assertEquals("<source>T´l´phone ′</source>", FeedUtils.replaceHtmlEntitiesWithNumericEntities(source));
|
||||
}
|
||||
}
|
||||
|
||||
@@ -46,7 +46,7 @@ public class OPMLImporterTest {
|
||||
importer.importOpml(user, xml);
|
||||
|
||||
Mockito.verify(feedSubscriptionService).subscribe(Mockito.eq(user), Mockito.anyString(), Mockito.anyString(),
|
||||
Mockito.any(FeedCategory.class));
|
||||
Mockito.any(FeedCategory.class), Mockito.anyInt());
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -42,7 +42,7 @@ public class FeedEntryFilteringServiceTest {
|
||||
|
||||
@Test
|
||||
public void simpleExpression() throws FeedEntryFilterException {
|
||||
Assert.assertTrue(service.filterMatchesEntry("author eq 'athou'", entry));
|
||||
Assert.assertTrue(service.filterMatchesEntry("author.toString() eq 'athou'", entry));
|
||||
}
|
||||
|
||||
@Test(expected = FeedEntryFilterException.class)
|
||||
@@ -67,8 +67,19 @@ public class FeedEntryFilteringServiceTest {
|
||||
|
||||
@Test
|
||||
public void handlesNullCorrectly() throws FeedEntryFilterException {
|
||||
entry.setUrl(null);
|
||||
entry.setContent(new FeedEntryContent());
|
||||
service.filterMatchesEntry("author eq 'athou'", entry);
|
||||
}
|
||||
|
||||
@Test(expected = FeedEntryFilterException.class)
|
||||
public void incorrectScriptThrowsException() throws FeedEntryFilterException {
|
||||
service.filterMatchesEntry("aa eqz bb", entry);
|
||||
}
|
||||
|
||||
@Test(expected = FeedEntryFilterException.class)
|
||||
public void incorrectReturnTypeThrowsException() throws FeedEntryFilterException {
|
||||
service.filterMatchesEntry("1", entry);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -9,6 +9,8 @@ import static org.mockito.Mockito.doNothing;
|
||||
import static org.mockito.Mockito.verify;
|
||||
import static org.mockito.Mockito.when;
|
||||
|
||||
import java.util.Optional;
|
||||
|
||||
import org.junit.Before;
|
||||
import org.junit.Test;
|
||||
import org.mockito.Mock;
|
||||
@@ -16,165 +18,177 @@ import org.mockito.MockitoAnnotations;
|
||||
|
||||
import com.commafeed.CommaFeedConfiguration;
|
||||
import com.commafeed.backend.dao.FeedCategoryDAO;
|
||||
import com.commafeed.backend.dao.FeedSubscriptionDAO;
|
||||
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.service.internal.PostLoginActivities;
|
||||
import com.google.common.base.Optional;
|
||||
|
||||
public class UserServiceTest {
|
||||
|
||||
private static final byte[] SALT = new byte[]{1,2,3};
|
||||
private static final byte[] ENCRYPTED_PASSWORD = new byte[]{5,6,7};
|
||||
|
||||
@Mock private CommaFeedConfiguration commaFeedConfiguration;
|
||||
@Mock private FeedCategoryDAO feedCategoryDAO;
|
||||
@Mock private UserDAO userDAO;
|
||||
@Mock private UserSettingsDAO userSettingsDAO;
|
||||
@Mock private PasswordEncryptionService passwordEncryptionService;
|
||||
@Mock private PostLoginActivities postLoginActivities;
|
||||
|
||||
|
||||
private static final byte[] SALT = new byte[] { 1, 2, 3 };
|
||||
private static final byte[] ENCRYPTED_PASSWORD = new byte[] { 5, 6, 7 };
|
||||
|
||||
@Mock
|
||||
private CommaFeedConfiguration commaFeedConfiguration;
|
||||
@Mock
|
||||
private FeedCategoryDAO feedCategoryDAO;
|
||||
@Mock
|
||||
private FeedSubscriptionDAO feedSubscriptionDAO;
|
||||
@Mock
|
||||
private UserDAO userDAO;
|
||||
@Mock
|
||||
private UserSettingsDAO userSettingsDAO;
|
||||
@Mock
|
||||
private UserRoleDAO userRoleDAO;
|
||||
@Mock
|
||||
private PasswordEncryptionService passwordEncryptionService;
|
||||
@Mock
|
||||
private PostLoginActivities postLoginActivities;
|
||||
|
||||
private User disabledUser;
|
||||
private User normalUser;
|
||||
|
||||
|
||||
private UserService userService;
|
||||
|
||||
@Before public void
|
||||
before_each_test() {
|
||||
@Before
|
||||
public void before_each_test() {
|
||||
MockitoAnnotations.initMocks(this);
|
||||
|
||||
userService = new UserService(feedCategoryDAO, userDAO, userSettingsDAO, passwordEncryptionService, commaFeedConfiguration, postLoginActivities);
|
||||
|
||||
|
||||
userService = new UserService(feedCategoryDAO, feedSubscriptionDAO, userDAO, userRoleDAO, userSettingsDAO,
|
||||
passwordEncryptionService, commaFeedConfiguration, postLoginActivities);
|
||||
|
||||
disabledUser = new User();
|
||||
disabledUser.setDisabled(true);
|
||||
|
||||
|
||||
normalUser = new User();
|
||||
normalUser.setDisabled(false);
|
||||
normalUser.setSalt(SALT);
|
||||
normalUser.setPassword(ENCRYPTED_PASSWORD);
|
||||
}
|
||||
|
||||
@Test public void
|
||||
calling_login_should_not_return_user_object_when_given_null_nameOrEmail() {
|
||||
|
||||
@Test
|
||||
public void calling_login_should_not_return_user_object_when_given_null_nameOrEmail() {
|
||||
Optional<User> user = userService.login(null, "password");
|
||||
assertFalse(user.isPresent());
|
||||
}
|
||||
|
||||
@Test public void
|
||||
calling_login_should_not_return_user_object_when_given_null_password() {
|
||||
|
||||
@Test
|
||||
public void calling_login_should_not_return_user_object_when_given_null_password() {
|
||||
Optional<User> user = userService.login("testusername", null);
|
||||
assertFalse(user.isPresent());
|
||||
}
|
||||
|
||||
@Test public void
|
||||
calling_login_should_lookup_user_by_name() {
|
||||
|
||||
@Test
|
||||
public void calling_login_should_lookup_user_by_name() {
|
||||
userService.login("test", "password");
|
||||
verify(userDAO).findByName("test");
|
||||
}
|
||||
|
||||
@Test public void
|
||||
calling_login_should_lookup_user_by_email_if_lookup_by_name_failed() {
|
||||
|
||||
@Test
|
||||
public void calling_login_should_lookup_user_by_email_if_lookup_by_name_failed() {
|
||||
when(userDAO.findByName("test@test.com")).thenReturn(null);
|
||||
userService.login("test@test.com", "password");
|
||||
verify(userDAO).findByEmail("test@test.com");
|
||||
}
|
||||
|
||||
@Test public void
|
||||
calling_login_should_not_return_user_object_if_could_not_find_user_by_name_or_email() {
|
||||
|
||||
@Test
|
||||
public void calling_login_should_not_return_user_object_if_could_not_find_user_by_name_or_email() {
|
||||
when(userDAO.findByName("test@test.com")).thenReturn(null);
|
||||
when(userDAO.findByEmail("test@test.com")).thenReturn(null);
|
||||
|
||||
|
||||
Optional<User> user = userService.login("test@test.com", "password");
|
||||
|
||||
|
||||
assertFalse(user.isPresent());
|
||||
}
|
||||
|
||||
@Test public void
|
||||
calling_login_should_not_return_user_object_if_user_is_disabled() {
|
||||
|
||||
@Test
|
||||
public void calling_login_should_not_return_user_object_if_user_is_disabled() {
|
||||
when(userDAO.findByName("test")).thenReturn(disabledUser);
|
||||
Optional<User> user = userService.login("test", "password");
|
||||
assertFalse(user.isPresent());
|
||||
}
|
||||
|
||||
@Test public void
|
||||
calling_login_should_try_to_authenticate_user_who_is_not_disabled() {
|
||||
|
||||
@Test
|
||||
public void calling_login_should_try_to_authenticate_user_who_is_not_disabled() {
|
||||
when(userDAO.findByName("test")).thenReturn(normalUser);
|
||||
when(passwordEncryptionService.authenticate(anyString(), any(byte[].class), any(byte[].class))).thenReturn(false);
|
||||
|
||||
|
||||
userService.login("test", "password");
|
||||
|
||||
|
||||
verify(passwordEncryptionService).authenticate("password", ENCRYPTED_PASSWORD, SALT);
|
||||
}
|
||||
|
||||
@Test public void
|
||||
calling_login_should_not_return_user_object_on_unsuccessful_authentication() {
|
||||
|
||||
@Test
|
||||
public void calling_login_should_not_return_user_object_on_unsuccessful_authentication() {
|
||||
when(userDAO.findByName("test")).thenReturn(normalUser);
|
||||
when(passwordEncryptionService.authenticate(anyString(), any(byte[].class), any(byte[].class))).thenReturn(false);
|
||||
|
||||
|
||||
Optional<User> authenticatedUser = userService.login("test", "password");
|
||||
|
||||
|
||||
assertFalse(authenticatedUser.isPresent());
|
||||
}
|
||||
|
||||
@Test public void
|
||||
calling_login_should_execute_post_login_activities_for_user_on_successful_authentication() {
|
||||
|
||||
@Test
|
||||
public void calling_login_should_execute_post_login_activities_for_user_on_successful_authentication() {
|
||||
when(userDAO.findByName("test")).thenReturn(normalUser);
|
||||
when(passwordEncryptionService.authenticate(anyString(), any(byte[].class), any(byte[].class))).thenReturn(true);
|
||||
doNothing().when(postLoginActivities).executeFor(any(User.class));
|
||||
|
||||
|
||||
userService.login("test", "password");
|
||||
|
||||
|
||||
verify(postLoginActivities).executeFor(normalUser);
|
||||
}
|
||||
|
||||
@Test public void
|
||||
calling_login_should_return_user_object_on_successful_authentication() {
|
||||
|
||||
@Test
|
||||
public void calling_login_should_return_user_object_on_successful_authentication() {
|
||||
when(userDAO.findByName("test")).thenReturn(normalUser);
|
||||
when(passwordEncryptionService.authenticate(anyString(), any(byte[].class), any(byte[].class))).thenReturn(true);
|
||||
doNothing().when(postLoginActivities).executeFor(any(User.class));
|
||||
|
||||
|
||||
Optional<User> authenticatedUser = userService.login("test", "password");
|
||||
|
||||
|
||||
assertTrue(authenticatedUser.isPresent());
|
||||
assertEquals(normalUser, authenticatedUser.get());
|
||||
}
|
||||
|
||||
@Test public void
|
||||
api_login_should_not_return_user_if_apikey_null() {
|
||||
|
||||
@Test
|
||||
public void api_login_should_not_return_user_if_apikey_null() {
|
||||
Optional<User> user = userService.login(null);
|
||||
assertFalse(user.isPresent());
|
||||
}
|
||||
|
||||
@Test public void
|
||||
api_login_should_lookup_user_by_apikey() {
|
||||
|
||||
@Test
|
||||
public void api_login_should_lookup_user_by_apikey() {
|
||||
when(userDAO.findByApiKey("apikey")).thenReturn(null);
|
||||
userService.login("apikey");
|
||||
verify(userDAO).findByApiKey("apikey");
|
||||
}
|
||||
|
||||
@Test public void
|
||||
api_login_should_not_return_user_if_user_not_found_from_lookup_by_apikey() {
|
||||
|
||||
@Test
|
||||
public void api_login_should_not_return_user_if_user_not_found_from_lookup_by_apikey() {
|
||||
when(userDAO.findByApiKey("apikey")).thenReturn(null);
|
||||
Optional<User> user = userService.login("apikey");
|
||||
assertFalse(user.isPresent());
|
||||
}
|
||||
|
||||
@Test public void
|
||||
api_login_should_not_return_user_if_user_found_from_apikey_lookup_is_disabled() {
|
||||
|
||||
@Test
|
||||
public void api_login_should_not_return_user_if_user_found_from_apikey_lookup_is_disabled() {
|
||||
when(userDAO.findByApiKey("apikey")).thenReturn(disabledUser);
|
||||
Optional<User> user = userService.login("apikey");
|
||||
assertFalse(user.isPresent());
|
||||
}
|
||||
|
||||
@Test public void
|
||||
api_login_should_perform_post_login_activities_if_user_found_from_apikey_lookup_not_disabled() {
|
||||
|
||||
@Test
|
||||
public void api_login_should_perform_post_login_activities_if_user_found_from_apikey_lookup_not_disabled() {
|
||||
when(userDAO.findByApiKey("apikey")).thenReturn(normalUser);
|
||||
userService.login("apikey");
|
||||
verify(postLoginActivities).executeFor(normalUser);
|
||||
}
|
||||
|
||||
@Test public void
|
||||
api_login_should_return_user_if_user_found_from_apikey_lookup_not_disabled() {
|
||||
|
||||
@Test
|
||||
public void api_login_should_return_user_if_user_found_from_apikey_lookup_not_disabled() {
|
||||
when(userDAO.findByApiKey("apikey")).thenReturn(normalUser);
|
||||
Optional<User> returnedUser = userService.login("apikey");
|
||||
assertEquals(normalUser, returnedUser.get());
|
||||
|
||||
@@ -4,13 +4,14 @@ import static org.mockito.Mockito.mock;
|
||||
import static org.mockito.Mockito.verify;
|
||||
import static org.mockito.Mockito.when;
|
||||
|
||||
import java.util.Optional;
|
||||
|
||||
import org.junit.Test;
|
||||
|
||||
import com.commafeed.backend.model.User;
|
||||
import com.commafeed.backend.service.UserService;
|
||||
import com.commafeed.backend.service.internal.PostLoginActivities;
|
||||
import com.commafeed.frontend.session.SessionHelper;
|
||||
import com.google.common.base.Optional;
|
||||
|
||||
public class SecurityCheckFactoryTest {
|
||||
|
||||
@@ -23,7 +24,7 @@ public class SecurityCheckFactoryTest {
|
||||
|
||||
PostLoginActivities postLoginActivities = mock(PostLoginActivities.class);
|
||||
|
||||
UserService service = new UserService(null, null, null, null, null, postLoginActivities);
|
||||
UserService service = new UserService(null, null, null, null, null, null, null, postLoginActivities);
|
||||
|
||||
SecurityCheckFactory factory = new SecurityCheckFactory(null, false);
|
||||
factory.userService = service;
|
||||
|
||||
@@ -8,6 +8,7 @@ import static org.mockito.Mockito.verify;
|
||||
import static org.mockito.Mockito.when;
|
||||
|
||||
import java.util.Arrays;
|
||||
import java.util.Optional;
|
||||
|
||||
import org.junit.Test;
|
||||
import org.mockito.InOrder;
|
||||
@@ -19,93 +20,92 @@ import com.commafeed.backend.service.UserService;
|
||||
import com.commafeed.frontend.model.request.LoginRequest;
|
||||
import com.commafeed.frontend.model.request.RegistrationRequest;
|
||||
import com.commafeed.frontend.session.SessionHelper;
|
||||
import com.google.common.base.Optional;
|
||||
|
||||
public class UserRestTest {
|
||||
|
||||
@Test public void
|
||||
login_should_not_populate_http_session_if_unsuccessfull() {
|
||||
|
||||
@Test
|
||||
public void login_should_not_populate_http_session_if_unsuccessfull() {
|
||||
// Absent user
|
||||
Optional<User> absentUser = Optional.absent();
|
||||
|
||||
Optional<User> absentUser = Optional.empty();
|
||||
|
||||
// Create UserService partial mock
|
||||
UserService service = mock(UserService.class);
|
||||
when(service.login("user", "password")).thenReturn(absentUser);
|
||||
|
||||
|
||||
UserREST userREST = new UserREST(null, null, null, service, null, null, null);
|
||||
SessionHelper sessionHelper = mock(SessionHelper.class);
|
||||
|
||||
|
||||
LoginRequest req = new LoginRequest();
|
||||
req.setName("user");
|
||||
req.setPassword("password");
|
||||
|
||||
|
||||
userREST.login(req, sessionHelper);
|
||||
|
||||
|
||||
verify(sessionHelper, never()).setLoggedInUser(any(User.class));
|
||||
}
|
||||
|
||||
@Test public void
|
||||
login_should_populate_http_session_if_successfull() {
|
||||
|
||||
@Test
|
||||
public void login_should_populate_http_session_if_successfull() {
|
||||
// Create a user
|
||||
User user = new User();
|
||||
|
||||
|
||||
// Create UserService mock
|
||||
UserService service = mock(UserService.class);
|
||||
when(service.login("user", "password")).thenReturn(Optional.of(user));
|
||||
|
||||
|
||||
LoginRequest req = new LoginRequest();
|
||||
req.setName("user");
|
||||
req.setPassword("password");
|
||||
|
||||
|
||||
UserREST userREST = new UserREST(null, null, null, service, null, null, null);
|
||||
SessionHelper sessionHelper = mock(SessionHelper.class);
|
||||
|
||||
|
||||
userREST.login(req, sessionHelper);
|
||||
|
||||
|
||||
verify(sessionHelper).setLoggedInUser(user);
|
||||
}
|
||||
|
||||
@Test public void
|
||||
register_should_register_and_then_login() {
|
||||
|
||||
@Test
|
||||
public void register_should_register_and_then_login() {
|
||||
// Create UserService mock
|
||||
UserService service = mock(UserService.class);
|
||||
|
||||
|
||||
RegistrationRequest req = new RegistrationRequest();
|
||||
req.setName("user");
|
||||
req.setPassword("password");
|
||||
req.setEmail("test@test.com");
|
||||
|
||||
|
||||
InOrder inOrder = inOrder(service);
|
||||
|
||||
|
||||
SessionHelper sessionHelper = mock(SessionHelper.class);
|
||||
UserREST userREST = new UserREST(null, null, null, service, null, null, null);
|
||||
|
||||
|
||||
userREST.register(req, sessionHelper);
|
||||
|
||||
|
||||
inOrder.verify(service).register("user", "password", "test@test.com", Arrays.asList(Role.USER));
|
||||
inOrder.verify(service).login("user", "password");
|
||||
}
|
||||
|
||||
@Test public void
|
||||
register_should_populate_http_session() {
|
||||
|
||||
@Test
|
||||
public void register_should_populate_http_session() {
|
||||
// Create a user
|
||||
User user = new User();
|
||||
|
||||
|
||||
// Create UserService mock
|
||||
UserService service = mock(UserService.class);
|
||||
when(service.register(any(String.class), any(String.class), any(String.class), Matchers.anyListOf(Role.class))).thenReturn(user);
|
||||
when(service.login(any(String.class), any(String.class))).thenReturn(Optional.of(user));
|
||||
|
||||
|
||||
RegistrationRequest req = new RegistrationRequest();
|
||||
req.setName("user");
|
||||
req.setPassword("password");
|
||||
req.setEmail("test@test.com");
|
||||
|
||||
|
||||
SessionHelper sessionHelper = mock(SessionHelper.class);
|
||||
UserREST userREST = new UserREST(null, null, null, service, null, null, null);
|
||||
|
||||
|
||||
userREST.register(req, sessionHelper);
|
||||
|
||||
|
||||
verify(sessionHelper).setLoggedInUser(user);
|
||||
}
|
||||
|
||||
|
||||
@@ -4,6 +4,8 @@ import static org.mockito.Mockito.mock;
|
||||
import static org.mockito.Mockito.verify;
|
||||
import static org.mockito.Mockito.when;
|
||||
|
||||
import java.util.Optional;
|
||||
|
||||
import javax.servlet.http.HttpServletRequest;
|
||||
import javax.servlet.http.HttpSession;
|
||||
|
||||
@@ -11,7 +13,6 @@ import org.junit.Assert;
|
||||
import org.junit.Test;
|
||||
|
||||
import com.commafeed.backend.model.User;
|
||||
import com.google.common.base.Optional;
|
||||
|
||||
public class SessionHelperTest {
|
||||
|
||||
|
||||
Reference in New Issue
Block a user