diff --git a/.gitignore b/.gitignore
index 40be120c..fac11e90 100644
--- a/.gitignore
+++ b/.gitignore
@@ -5,6 +5,10 @@ target
/src/test/test.iml
<<<<<<< HEAD
/.idea
+<<<<<<< HEAD
+src/main/resources/account-private-key-google-api-devstarter.json
+=======
=======
/.idea
>>>>>>> 080cd17ae17b68b4cec6512498198d8332690c7c
+>>>>>>> 969a9f11bc9654c66161ec7983f43c0638a92a2e
diff --git a/README.md b/README.md
index a058d17c..72d58a2f 100644
--- a/README.md
+++ b/README.md
@@ -2,7 +2,7 @@
============================
[презетация](http://youtu.be/__ibkaMRHZI), [ii.ayfaar.org](http://ii.ayfaar.org), [канал YouTube](https://www.youtube.com/channel/UCx7OZ2t2mEiaW6kem5lfl9w)
-Ключевые слова: OOP, [SOLID](http://ru.wikipedia.org/wiki/SOLID_(%D0%BE%D0%B1%D1%8A%D0%B5%D0%BA%D1%82%D0%BD%D0%BE-%D0%BE%D1%80%D0%B8%D0%B5%D0%BD%D1%82%D0%B8%D1%80%D0%BE%D0%B2%D0%B0%D0%BD%D0%BD%D0%BE%D0%B5_%D0%BF%D1%80%D0%BE%D0%B3%D1%80%D0%B0%D0%BC%D0%BC%D0%B8%D1%80%D0%BE%D0%B2%D0%B0%D0%BD%D0%B8%D0%B5)), Java, J2SE, Hibernate, Spring (IoC, MVC), JUnit, JavaScript, HTML5, CSS3, [KendoUI](www.kendoui.com), [AngularJS](https://angularjs.org), MySQL, Maven, git, TDD, CI, IntelliJ IDEA
+Ключевые слова: OOP, [SOLID](http://ru.wikipedia.org/wiki/SOLID_(%D0%BE%D0%B1%D1%8A%D0%B5%D0%BA%D1%82%D0%BD%D0%BE-%D0%BE%D1%80%D0%B8%D0%B5%D0%BD%D1%82%D0%B8%D1%80%D0%BE%D0%B2%D0%B0%D0%BD%D0%BD%D0%BE%D0%B5_%D0%BF%D1%80%D0%BE%D0%B3%D1%80%D0%B0%D0%BC%D0%BC%D0%B8%D1%80%D0%BE%D0%B2%D0%B0%D0%BD%D0%B8%D0%B5)), Java 8, J2SE, Hibernate, [Spring Boot](https://spring.io/) (JPA, IoC, MVC), JUnit, JavaScript, HTML5, CSS3, [KendoUI](www.kendoui.com), [AngularJS](https://angularjs.org), MySQL, Maven, git, TDD, CI, IntelliJ IDEA
Мой скайп: iu3116
@@ -28,27 +28,23 @@
С чего начать (Java)
====================
-**Видео инструкция https://www.youtube.com/watch?v=mbwN4eaES78**
-
Устанавливаем:
1. GIT http://msysgit.github.io
-2. Добавляем git.exe в [переменную окружения Path](http://clip2net.com/s/iuLWXk) и перезагружаем windows
+2. Добавляем git.exe в переменную окружения Path и перезагружаем windows
3. Выполняем тестовую задачу [Тренировка работы с git](https://github.com/devstarter/ii/issues/4)
4. [IntelliJ IDEA](http://www.jetbrains.com/idea/download/)
-5. [Java version 1.7](https://www.java.com/en/download)
-6. [Java SE Development Kit 7](http://www.oracle.com/technetwork/java/javase/downloads/jdk7-downloads-1880260.html)
+5. [Java](https://www.java.com/en/download)
+6. [Java SE Development Kit](http://www.oracle.com/technetwork/java/javase/downloads/index.html)
7. [MySQL](http://dev.mysql.com/downloads/mysql/) или [XAMPP](https://www.apachefriends.org/index.html) [wiki/База данных](https://github.com/devstarter/ii/wiki/%D0%91%D0%B0%D0%B7%D0%B0-%D0%B4%D0%B0%D0%BD%D0%BD%D1%8B%D1%85)
-8. Не обязательно, [Apache Tomcat](http://tomcat.apache.org/download-70.cgi) или [XAMPP](https://www.apachefriends.org/index.html)
Окрываем проект:
1. Зарегистрируйтесь в [GitHub](https://github.com)
2. Сделайте Fork (копию) [этого кода](https://github.com/devstarter/ii) из своего акаунта
3. Скачайте его на свой компьютер `git clone https://github.com/<ваш акаунт>/ii.git`
-4. Откройте проект с помощью IDEA
-5. Устанавите плагин [lombok](http://plugins.jetbrains.com/plugin/6317) для IDEA
-6. Запустите тест [RunTest.java](https://github.com/devstarter/ii/blob/master/src/test/java/RunTest.java)
+4. Устанавите плагин [lombok](http://plugins.jetbrains.com/plugin/6317) для IDEA
+5. [Открываем проект в IntelliJ IDEA](https://github.com/devstarter/ii/wiki/%D0%9E%D1%82%D0%BA%D1%80%D1%8B%D1%82%D0%B8%D0%B5-%D0%BF%D1%80%D0%BE%D0%B5%D0%BA%D1%82%D0%B0-%D0%B2-IntelliJ-IDEA)
Настраиваем базу данных (MySQL) [Видео](https://www.youtube.com/watch?v=l-ZGmR98d-4):
@@ -56,11 +52,9 @@
2. Качаем [последний дамп данных](https://github.com/devstarter/ii/tree/master/db)
3. Импортируем дамп
-Запускаем проект (не обязательно):
+Запускаем проект:
-1. Добавляем Run Configuration для Tomcat в IDEA
-2. Запускаем эту конфигурацию
+Конфигурация в IDEA [Run/Debug Configuration](https://cloud.githubusercontent.com/assets/1183619/14000544/b469080e-f152-11e5-9a3a-c1acb737b5d8.png)
-Подробнее в видео https://www.youtube.com/watch?v=mbwN4eaES78
[![Презетация](http://img.youtube.com/vi/__ibkaMRHZI/0.jpg)](http://youtu.be/__ibkaMRHZI)
diff --git a/db/dump-latest.7z b/db/dump-latest.7z
new file mode 100644
index 00000000..16b04364
Binary files /dev/null and b/db/dump-latest.7z differ
diff --git a/db/dump-latest.sql.tgz b/db/dump-latest.sql.tgz
deleted file mode 100644
index 82f802c5..00000000
Binary files a/db/dump-latest.sql.tgz and /dev/null differ
diff --git a/pom.xml b/pom.xml
index 8169c581..3e43cdef 100755
--- a/pom.xml
+++ b/pom.xml
@@ -1,398 +1,310 @@
+
-
- 4.0.0
+ xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
+ 4.0.0
org.ayfaar
app
- 1.0-SNAPSHOT
- war
+ 1.5-SNAPSHOT
+ jar
Ayfaar II App
-
- 2.0-SNAPSHOT
- 1.7.1
- 3.0
- 3.2.5.RELEASE
- 3.1.3.RELEASE
- 4.2.5.Final
- 1.0.6
- 1.6.1
- 2.1.2
- 6.1.24
- 1.6
- UTF-8
- 4.11
- 1.6
- 1.6
-
+
+ org.springframework.boot
+ spring-boot-starter-parent
+ 1.3.3.RELEASE
+
+
+
+
+ 8.0.9
+ UTF-8
+ 1.8
+ 1.8
+ 1.8
+ 1.8
+ 4.11
+ 3.9
+ 0.6.0
+ 1.14
+
+
+
+
+ one.util
+ streamex
+ ${streamex.version}
+
+
+ org.hamcrest
+ hamcrest-all
+ 1.3
+ test
+
+
+ com.jayway.restassured
+ rest-assured
+ 2.9.0
+ test
+
+
+ org.apache.poi
+ poi-ooxml
+ ${poi.version}
+ test
+
+
+ org.apache.poi
+ poi
+ ${poi.version}
+ test
+
+
+ org.jsoup
+ jsoup
+ 1.7.3
+ test
+
+
+ net.sf.opencsv
+ opencsv
+ 2.0
+ test
+
+
+ org.mockito
+ mockito-all
+ 1.9.5
+ test
+
+
+ org.mockito
+ mockito-core
+ 1.9.5
+ test
+
+
+ junit
+ junit
+ ${junit.version}
+ test
+
+
+
+
+ commons-collections
+ commons-collections
+ 3.2.1
+
+
+ org.apache.commons
+ commons-lang3
+ 3.0
+
+
+ commons-lang
+ commons-lang
+ 2.6
+
+
+ commons-io
+ commons-io
+ 2.4
+
-
-
- net.sourceforge.htmlcleaner
- htmlcleaner
- 2.8
-
-
- com.evernote
- evernote-api
- 1.25.1
-
-
- net.sf.opencsv
- opencsv
- 2.0
-
-
- net.sourceforge
- jwbf
- 2.0.0
-
-
- org.docx4j
- docx4j
- 2.8.1
-
-
- joda-time
- joda-time
- 2.3
-
-
- net.sf.dozer
- dozer
- 5.4.0
-
-
- org.projectlombok
- lombok
- 0.12.0
- provided
-
-
- com.fasterxml.jackson.core
- jackson-core
- 2.2.2
-
-
- com.fasterxml.jackson.core
- jackson-annotations
- 2.2.2
-
-
- com.fasterxml.jackson.core
- jackson-databind
- 2.2.2
-
+
+ com.intellij
+ annotations
+ 12.0
+
+
+ commons-beanutils
+ commons-beanutils
+ 1.8.3
+
+
+
-
- org.mockito
- mockito-all
- 1.9.5
- test
-
-
- org.mockito
- mockito-core
- 1.9.5
- test
-
-
- org.reflections
- reflections
- 0.9.9-RC1
-
-
- javax.mail
- mail
- 1.4.6
-
-
- org.thymeleaf
- thymeleaf-spring3
- 2.0.16
-
-
- org.aspectj
- aspectjrt
- ${aspects.version}
-
-
- org.aspectj
- aspectjweaver
- ${aspects.version}
-
-
- junit
- junit
- ${junit.version}
-
-
- net.coobird
- thumbnailator
- 0.4.2
-
-
- commons-fileupload
- commons-fileupload
- 1.2.2
-
-
- commons-collections
- commons-collections
- 3.2.1
-
-
- org.apache.commons
- commons-lang3
- 3.0
-
-
- commons-io
- commons-io
- 2.4
-
-
- com.intellij
- annotations
- 12.0
-
-
- commons-beanutils
- commons-beanutils
- 1.8.3
-
-
- org.springframework
- spring-aspects
- ${spring.version}
-
-
- org.springframework
- spring-expression
- ${spring.version}
-
-
- org.springframework
- spring-tx
- ${spring.version}
-
-
-
- org.codehaus.jackson
- jackson-mapper-asl
- 1.8.5
-
+
+ org.springframework.boot
+ spring-boot-starter-aop
+
+
+ org.springframework.boot
+ spring-boot-starter-mail
+
+
+ org.springframework.boot
+ spring-boot-starter-data-jpa
+
+
+ org.springframework.boot
+ spring-boot-starter-data-rest
+
+
+ org.projectlombok
+ lombok
+ 1.16.6
+
+
+ org.springframework.boot
+ spring-boot-starter-web
+
+
+ org.springframework.boot
+ spring-boot-starter-thymeleaf
+ test
+
+
+ org.springframework.boot
+ spring-boot-starter-security
+
+
+
+ mysql
+ mysql-connector-java
+ runtime
+
+
+ org.springframework.boot
+ spring-boot-starter-test
+ test
+
+
+ javax.inject
+ javax.inject
+ 1
+
+
+ com.pushbullet
+ pushbullet-api
+ 1.1-SNAPSHOT
+
+
+ com.github.dfabulich
+ sitemapgen4j
+ 1.0.4
+
-
-
- org.slf4j
- jcl-over-slf4j
- ${slf4j.version}
-
-
- org.slf4j
- slf4j-api
- ${slf4j.version}
-
-
- org.slf4j
- jul-to-slf4j
- ${slf4j.version}
-
-
- ch.qos.logback
- logback-classic
- ${logback.version}
-
-
- ch.qos.logback
- logback-core
- ${logback.version}
-
-
- ch.qos.logback
- logback-access
- ${logback.version}
-
+
+
+ org.apache.httpcomponents
+ httpclient
+ 4.5.2
+
+
+ org.apache.httpcomponents
+ httpmime
+ 4.5.2
+
+
+ com.google.api-client
+ google-api-client
+ 1.22.0
+
+
+ com.google.apis
+ google-api-services-oauth2
+ v2-rev109-1.22.0
+
+
+ com.google.apis
+ google-api-services-drive
+ v2-rev135-1.19.0
+
+
+ com.google.apis
+ google-api-services-sheets
+ v4-rev38-1.22.0
+
+
+ com.google.apis
+ google-api-services-youtube
+ v3-rev162-1.21.0
+
+
-
- org.springframework
- spring-web
- ${spring.version}
-
-
- org.springframework
- spring-test
- ${spring.version}
-
-
- org.springframework
- spring-webmvc
- ${spring.version}
-
-
- org.springframework
- spring-orm
- ${spring.version}
-
-
- org.springframework
- spring-jdbc
- ${spring.version}
-
-
- org.springframework
- spring-context
- ${spring.version}
-
-
- org.springframework
- spring-context-support
- ${spring.version}
-
+
+ com.google.http-client
+ google-http-client
+ 1.21.0
+
+
+ com.google.http-client
+ google-http-client-jackson2
+ 1.21.0
+
+
+ com.google.oauth-client
+ google-oauth-client-jetty
+ 1.21.0
+
-
-
- org.springframework.security
- spring-security-core
- ${spring.security.version}
-
-
- org.springframework.security
- spring-security-web
- ${spring.security.version}
-
-
- org.springframework.security
- spring-security-config
- ${spring.security.version}
-
-
- org.springframework.security
- spring-security-taglibs
- ${spring.security.version}
-
-
-
- org.hibernate
- hibernate-core
- ${hibernate.version}
-
-
- org.hibernate
- hibernate-envers
- ${hibernate.version}
-
-
- org.hibernate
- hibernate-validator
- 4.0.2.GA
-
-
- javax.validation
- validation-api
- 1.0.0.GA
-
-
- mysql
- mysql-connector-java
- 5.1.13
-
-
-
- javax.servlet
- javax.servlet-api
- 3.0.1
- provided
-
+
+
+ org.apache.poi
+ poi-scratchpad
+ 3.9
+
+
+ net.sf.dozer
+ dozer
+ 5.4.0
+
-
-
- javax.persistence
- persistence-api
- 1.0.2
-
-
- javassist
- javassist
- 3.12.1.GA
-
-
- cglib
- cglib
- 2.2
-
+
+ org.apache.tika
+ tika-core
+ ${tika.version}
+ test
+
+
+ org.apache.tika
+ tika-parsers
+ ${tika.version}
+ test
+
-
- org.hsqldb
- hsqldb
- 2.2.4
- test
-
-
-
-
-
-
-
-
- openshift
-
- ii
-
-
- maven-war-plugin
- 2.1.1
-
- webapps
- ROOT
-
-
-
-
-
-
-
-
- eap
- http://maven.repository.redhat.com/techpreview/all
-
- true
-
-
- true
-
-
-
- russian-morphology.lucene.apache.org
- Lucene Russian Morphology Repository for Maven
- http://russianmorphology.googlecode.com/svn/repo/releases/
-
-
-
-
- eap
- http://maven.repository.redhat.com/techpreview/all
-
- true
-
-
- true
-
-
-
-
-
+
+
+
+ ii
+
+
+ org.springframework.boot
+ spring-boot-maven-plugin
+
org.apache.maven.plugins
maven-surefire-plugin
@@ -401,12 +313,69 @@
-Dfile.encoding=UTF-8
+
+ org.apache.maven.plugins
+ maven-compiler-plugin
+
+
+ 1.8
+
+
-
-
+
+
+
+
+ eap
+ http://maven.repository.redhat.com/techpreview/all
+
+ true
+
+
+ true
+
+
+
+ spring-snapshots
+ Spring Snapshots
+ https://repo.spring.io/snapshot
+
+ true
+
+
+
+ spring-milestones
+ Spring Milestones
+ https://repo.spring.io/milestone
+
+ false
+
+
+
+ pushbullet.java-mvn-repo
+ https://raw.github.com/devstarter/pushbullet.java/mvn-repo/
+
+ true
+ always
+
+
+
+
+
+ spring-snapshots
+ Spring Snapshots
+ https://repo.spring.io/snapshot
+
+ true
+
+
+
+ spring-milestones
+ Spring Milestones
+ https://repo.spring.io/milestone
+
+ false
+
+
+
diff --git a/src/main/java/org/ayfaar/app/Application.java b/src/main/java/org/ayfaar/app/Application.java
new file mode 100644
index 00000000..7d590219
--- /dev/null
+++ b/src/main/java/org/ayfaar/app/Application.java
@@ -0,0 +1,36 @@
+package org.ayfaar.app;
+
+import lombok.extern.slf4j.Slf4j;
+import org.dozer.spring.DozerBeanMapperFactoryBean;
+import org.springframework.boot.SpringApplication;
+import org.springframework.boot.autoconfigure.SpringBootApplication;
+import org.springframework.boot.orm.jpa.EntityScan;
+import org.springframework.cache.annotation.EnableCaching;
+import org.springframework.context.ConfigurableApplicationContext;
+import org.springframework.context.annotation.Bean;
+import org.springframework.context.annotation.ComponentScan;
+import org.springframework.context.annotation.EnableAspectJAutoProxy;
+import org.springframework.context.annotation.ImportResource;
+import org.springframework.data.jpa.repository.config.EnableJpaRepositories;
+import org.springframework.scheduling.annotation.EnableAsync;
+
+@SpringBootApplication
+@EnableJpaRepositories
+@EnableCaching
+@EnableAsync
+@EnableAspectJAutoProxy
+@Slf4j
+@EntityScan("org.ayfaar.app.model")
+@ComponentScan("org.ayfaar.app")
+@ImportResource({"classpath:hibernate.xml", "classpath:spring-basic.xml"})
+public class Application {
+ public static void main(String[] args) {
+ final ConfigurableApplicationContext context = SpringApplication.run(Application.class, args);
+ log.info("Open in browser: " + context.getEnvironment().getProperty("this-url"));
+ }
+
+ @Bean
+ public DozerBeanMapperFactoryBean dozerBeanMapperFactoryBean() {
+ return new DozerBeanMapperFactoryBean();
+ }
+}
\ No newline at end of file
diff --git a/src/main/java/org/ayfaar/app/annotations/ContentsCache.java b/src/main/java/org/ayfaar/app/annotations/ContentsCache.java
new file mode 100644
index 00000000..1df185ec
--- /dev/null
+++ b/src/main/java/org/ayfaar/app/annotations/ContentsCache.java
@@ -0,0 +1,15 @@
+package org.ayfaar.app.annotations;
+
+import org.springframework.cache.annotation.Cacheable;
+
+import java.lang.annotation.Retention;
+import java.lang.annotation.Target;
+
+import static java.lang.annotation.ElementType.METHOD;
+import static java.lang.annotation.RetentionPolicy.RUNTIME;
+
+@Target({METHOD})
+@Retention(RUNTIME)
+@Cacheable(value = "DBCache", key = "new org.ayfaar.app.controllers.search.cache.ContentsCacheKey(#name)")
+public @interface ContentsCache {
+}
diff --git a/src/main/java/org/ayfaar/app/annotations/Moderated.java b/src/main/java/org/ayfaar/app/annotations/Moderated.java
index b43478f6..85247142 100644
--- a/src/main/java/org/ayfaar/app/annotations/Moderated.java
+++ b/src/main/java/org/ayfaar/app/annotations/Moderated.java
@@ -1,5 +1,7 @@
package org.ayfaar.app.annotations;
+import org.ayfaar.app.services.moderation.Action;
+
import java.lang.annotation.Retention;
import java.lang.annotation.Target;
@@ -9,4 +11,6 @@
@Target({METHOD})
@Retention(RUNTIME)
public @interface Moderated {
+ Action value();
+ String command();
}
diff --git a/src/main/java/org/ayfaar/app/annotations/SearchResultCache.java b/src/main/java/org/ayfaar/app/annotations/SearchResultCache.java
new file mode 100644
index 00000000..95137cc9
--- /dev/null
+++ b/src/main/java/org/ayfaar/app/annotations/SearchResultCache.java
@@ -0,0 +1,16 @@
+package org.ayfaar.app.annotations;
+
+import org.springframework.cache.annotation.Cacheable;
+
+import java.lang.annotation.Retention;
+import java.lang.annotation.Target;
+
+import static java.lang.annotation.ElementType.METHOD;
+import static java.lang.annotation.RetentionPolicy.RUNTIME;
+
+
+@Target({METHOD})
+@Retention(RUNTIME)
+@Cacheable(value = "DBCache", key = "new org.ayfaar.app.controllers.search.cache.SearchCacheKey(#query, #startFrom, #pageNumber)")
+public @interface SearchResultCache {
+}
diff --git a/src/main/java/org/ayfaar/app/configs/Profile.java b/src/main/java/org/ayfaar/app/configs/Profile.java
new file mode 100644
index 00000000..3a23e96c
--- /dev/null
+++ b/src/main/java/org/ayfaar/app/configs/Profile.java
@@ -0,0 +1,5 @@
+package org.ayfaar.app.configs;
+
+public enum Profile {
+ prod, dev
+}
diff --git a/src/main/java/org/ayfaar/app/configs/SecurityConfig.java b/src/main/java/org/ayfaar/app/configs/SecurityConfig.java
new file mode 100644
index 00000000..4d42617a
--- /dev/null
+++ b/src/main/java/org/ayfaar/app/configs/SecurityConfig.java
@@ -0,0 +1,51 @@
+package org.ayfaar.app.configs;
+
+import org.ayfaar.app.model.User;
+import org.springframework.context.annotation.Configuration;
+import org.springframework.security.authentication.AuthenticationProvider;
+import org.springframework.security.authentication.UsernamePasswordAuthenticationToken;
+import org.springframework.security.config.annotation.authentication.builders.AuthenticationManagerBuilder;
+import org.springframework.security.config.annotation.method.configuration.EnableGlobalMethodSecurity;
+import org.springframework.security.config.annotation.web.builders.HttpSecurity;
+import org.springframework.security.config.annotation.web.configuration.EnableWebSecurity;
+import org.springframework.security.config.annotation.web.configuration.WebSecurityConfigurerAdapter;
+import org.springframework.security.core.Authentication;
+import org.springframework.security.core.AuthenticationException;
+import org.springframework.security.core.GrantedAuthority;
+import org.springframework.security.core.authority.AuthorityUtils;
+import org.springframework.stereotype.Component;
+
+import java.util.Collection;
+
+@Configuration
+@EnableWebSecurity
+@EnableGlobalMethodSecurity(prePostEnabled = true)
+public class SecurityConfig extends WebSecurityConfigurerAdapter {
+ @Override
+ protected void configure(HttpSecurity http) throws Exception {
+ http.authorizeRequests()
+ .antMatchers("/static/old/adm.html")
+ .hasAnyAuthority("ROLE_EDITOR", "ROLE_ADMIN")
+ .and().logout().logoutSuccessUrl("/")
+ .and().csrf().disable();
+ }
+
+ @Override
+ protected void configure(AuthenticationManagerBuilder auth) throws Exception {
+ auth.authenticationProvider(new CustomAuthenticationProvider());
+ }
+
+ @Component
+ public static class CustomAuthenticationProvider implements AuthenticationProvider {
+ public Authentication authenticate(Authentication authentication) throws AuthenticationException {
+ User user = (User)authentication.getPrincipal();
+ Collection extends GrantedAuthority> authorities = AuthorityUtils.createAuthorityList(user.getRole().toString());
+ return new UsernamePasswordAuthenticationToken(user, null, authorities);
+ }
+
+ @Override
+ public boolean supports(Class> authentication) {
+ return true;
+ }
+ }
+}
diff --git a/src/main/java/org/ayfaar/app/configs/WebMvcConfig.java b/src/main/java/org/ayfaar/app/configs/WebMvcConfig.java
new file mode 100644
index 00000000..acda1185
--- /dev/null
+++ b/src/main/java/org/ayfaar/app/configs/WebMvcConfig.java
@@ -0,0 +1,85 @@
+package org.ayfaar.app.configs;
+
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.beans.factory.annotation.Value;
+import org.springframework.boot.autoconfigure.AutoConfigureAfter;
+import org.springframework.boot.autoconfigure.web.DispatcherServletAutoConfiguration;
+import org.springframework.boot.autoconfigure.web.WebMvcAutoConfiguration;
+import org.springframework.context.annotation.Bean;
+import org.springframework.context.annotation.Configuration;
+import org.springframework.core.env.Environment;
+import org.springframework.core.io.Resource;
+import org.springframework.web.filter.OncePerRequestFilter;
+import org.springframework.web.servlet.config.annotation.ResourceHandlerRegistry;
+import org.springframework.web.servlet.resource.AppCacheManifestTransformer;
+import org.springframework.web.servlet.resource.PathResourceResolver;
+import org.springframework.web.servlet.resource.ResourceResolverChain;
+import org.springframework.web.servlet.resource.ResourceUrlEncodingFilter;
+import javax.servlet.FilterChain;
+import javax.servlet.ServletException;
+import javax.servlet.http.HttpServletRequest;
+import javax.servlet.http.HttpServletResponse;
+import java.io.IOException;
+import java.util.List;
+import java.util.regex.Matcher;
+import java.util.regex.Pattern;
+
+@Configuration
+@AutoConfigureAfter(DispatcherServletAutoConfiguration.class)
+public class WebMvcConfig extends WebMvcAutoConfiguration.WebMvcAutoConfigurationAdapter {
+
+ @Autowired
+ private Environment env;
+
+ @Value("${sitemap-dir}")
+ private String sitemapDir;
+
+ @Bean
+ public ResourceUrlEncodingFilter resourceUrlEncodingFilter() {
+ return new ResourceUrlEncodingFilter();
+ }
+
+ @Bean OncePerRequestFilter chromeTabSupportFilter() {
+ return new OncePerRequestFilter() {
+ @Override
+ protected void doFilterInternal(HttpServletRequest request, HttpServletResponse response, FilterChain filterChain) throws ServletException, IOException {
+ Matcher matcher = Pattern
+ .compile("index\\.php\\?option=com_search&searchword=(.*)")
+ .matcher(request.getRequestURI() + "?" + request.getQueryString());
+ if (matcher.find()) {
+ response.sendRedirect(matcher.group(1).replace("+", "%20"));
+ } else {
+ filterChain.doFilter(request, response);
+ }
+ }
+ };
+ }
+
+ @Override
+ public void addResourceHandlers(ResourceHandlerRegistry registry) {
+ boolean devMode = this.env.acceptsProfiles("dev");
+ boolean useResourceCache = !devMode;
+ Integer cachePeriod = devMode ? 0 : null;
+
+ registry.addResourceHandler("/**")
+ .addResourceLocations("classpath:static/", "file:"+sitemapDir)
+ .setCachePeriod(cachePeriod)
+ .resourceChain(useResourceCache)
+ .addResolver(new CustomPathResourceResolver())
+ .addTransformer(new AppCacheManifestTransformer());
+ }
+
+ private class CustomPathResourceResolver extends PathResourceResolver {
+ @Override
+ public Resource resolveResource(HttpServletRequest request, String requestPath, List extends Resource> locations, ResourceResolverChain chain) {
+ if (requestPath.startsWith("template/") || requestPath.equals("sitemap.xml")) {
+ // don't change path
+ } else if (requestPath.startsWith("static/")) {
+ requestPath = requestPath.replaceFirst("static/", "");
+ } else {
+ requestPath = "index.html";
+ }
+ return super.resolveResource(request, requestPath, locations, chain);
+ }
+ }
+}
diff --git a/src/main/java/org/ayfaar/app/controllers/ArticleController.java b/src/main/java/org/ayfaar/app/controllers/ArticleController.java
index 352d80bc..75470d13 100644
--- a/src/main/java/org/ayfaar/app/controllers/ArticleController.java
+++ b/src/main/java/org/ayfaar/app/controllers/ArticleController.java
@@ -3,7 +3,8 @@
import org.ayfaar.app.dao.ArticleDao;
import org.ayfaar.app.model.Article;
import org.ayfaar.app.model.Term;
-import org.ayfaar.app.utils.AliasesMap;
+import org.ayfaar.app.utils.TermServiceImpl;
+import org.ayfaar.app.utils.TermsMarker;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.PathVariable;
@@ -15,18 +16,19 @@
import static org.springframework.util.Assert.notNull;
@Controller
-@RequestMapping("article")
+@RequestMapping("api/article")
public class ArticleController {
@Autowired ArticleDao articleDao;
- @Autowired AliasesMap aliasesMap;
-
+ @Autowired TermServiceImpl aliasesMap;
+ @Autowired TermsMarker termsMarker;
@RequestMapping("{id}")
@ResponseBody
public Article get(@PathVariable Integer id) {
Article article = articleDao.get("id", id);
notNull(article, "Article not found");
+ article.setContent(termsMarker.mark(article.getContent()));
return article;
}
diff --git a/src/main/java/org/ayfaar/app/controllers/AuthController.java b/src/main/java/org/ayfaar/app/controllers/AuthController.java
new file mode 100644
index 00000000..60508f8a
--- /dev/null
+++ b/src/main/java/org/ayfaar/app/controllers/AuthController.java
@@ -0,0 +1,98 @@
+package org.ayfaar.app.controllers;
+
+import org.ayfaar.app.configs.SecurityConfig;
+import org.ayfaar.app.dao.CommonDao;
+import org.ayfaar.app.model.User;
+import org.ayfaar.app.services.moderation.Action;
+import org.ayfaar.app.services.moderation.ModerationService;
+import org.springframework.security.authentication.UsernamePasswordAuthenticationToken;
+import org.springframework.security.core.Authentication;
+import org.springframework.security.core.context.SecurityContextHolder;
+import org.springframework.web.bind.annotation.*;
+
+import javax.inject.Inject;
+import javax.servlet.http.HttpServletResponse;
+import java.io.IOException;
+import java.util.Date;
+
+@RestController
+@RequestMapping("api/auth")
+public class AuthController {
+
+ private final CommonDao commonDao;
+ private ModerationService moderationService;
+ private final SecurityConfig.CustomAuthenticationProvider customAuthenticationProvider;
+
+ @Inject
+ public AuthController(SecurityConfig.CustomAuthenticationProvider customAuthenticationProvider, CommonDao commonDao, ModerationService moderationService) {
+ this.customAuthenticationProvider = customAuthenticationProvider;
+ this.commonDao = commonDao;
+ this.moderationService = moderationService;
+ }
+
+ @RequestMapping(method = RequestMethod.POST)
+ /*
+ Регистрируем нового пользователя и/или (если такой уже есть) назначаем его текущим для этой сессии
+
+ Пример входных данных:
+ access_token:CAANCEx9hQ8ABACe5zBAPE1fThMsaJDHQ0oolOvZCsiOAoFgbj65BiZC5qFG557wYl71CRLZBBipi1JeZCZABkeD7PuurKplra04wvaGSiNnHdnWQZAqZBt1sLtps38DDOJ0RAUNlSDKnMjAkt7bZClUtxLCCF1lQk4NLIXMtuxXiKkLCnojk7KtoQbZBRbPTqzdadfbifnGUrOAZDZD
+ email:sllouyssgort@gmail.com
+ first_name:Sllouyssgort
+ id:1059404344124694
+ last_name:Smaay-Grriyss
+ name:Sllouyssgort Smaay-Grriyss
+ picture:https://graph.facebook.com/1059404344124694/picture
+ thumbnail:https://graph.facebook.com/1059404344124694/picture
+ timezone:3
+ verified:true
+ auth_provider:vk
+ */
+ public User registrate(@RequestParam String access_token,
+ @RequestParam String email,
+ @RequestParam String first_name,
+ @RequestParam String last_name,
+ @RequestParam String name,
+ @RequestParam String picture,
+ @RequestParam String thumbnail,
+ @RequestParam(required=false) String timezone,
+ @RequestParam Long id,
+ @RequestParam OAuthProvider auth_provider) throws IOException{
+ User user = commonDao.getOpt(User.class, "email", email).orElse(
+ User.builder()
+ .accessToken(access_token)
+ .email(email)
+ .oauthProvider(auth_provider)
+ .firstName(first_name)
+ .lastName(last_name)
+ .name(name)
+ .thumbnail(thumbnail)
+ .picture(picture)
+ .timezone(timezone)
+ .providerId(id)
+ .build());
+
+ boolean newUserFlag = user.getId() == null;
+
+ if (!user.getAccessToken().equals(access_token)){
+ user.setAccessToken(access_token);
+ }
+ user.setLastVisitAt(new Date());
+ commonDao.save(user);
+
+ Authentication request = new UsernamePasswordAuthenticationToken(user, null);
+ Authentication authentication = customAuthenticationProvider.authenticate(request);
+ SecurityContextHolder.getContext().setAuthentication(authentication);
+ if (newUserFlag) moderationService.notice(Action.NEW_USER, user.getName());
+ return user;
+ }
+
+ @RequestMapping("login-as/{userId}")
+ public void loginAs(@PathVariable Integer userId, HttpServletResponse response) throws IOException{
+ User user = commonDao.getOpt(User.class, userId).get();
+
+ Authentication request = new UsernamePasswordAuthenticationToken(user, null);
+ Authentication authentication = customAuthenticationProvider.authenticate(request);
+ SecurityContextHolder.getContext().setAuthentication(authentication);
+ response.sendRedirect("/%D1%8F");
+ }
+}
diff --git a/src/main/java/org/ayfaar/app/controllers/CategoryController.java b/src/main/java/org/ayfaar/app/controllers/CategoryController.java
index da5a2ad7..38f2831b 100644
--- a/src/main/java/org/ayfaar/app/controllers/CategoryController.java
+++ b/src/main/java/org/ayfaar/app/controllers/CategoryController.java
@@ -5,6 +5,9 @@
import org.ayfaar.app.dao.LinkDao;
import org.ayfaar.app.dao.TermDao;
import org.ayfaar.app.model.Category;
+import org.ayfaar.app.utils.ContentsService;
+import org.ayfaar.app.utils.UriGenerator;
+import org.ayfaar.app.utils.contents.ContentsHelper;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.RequestMapping;
@@ -18,12 +21,23 @@
import static org.springframework.web.bind.annotation.RequestMethod.POST;
@Controller
-@RequestMapping("category")
+@RequestMapping("api/category")
public class CategoryController {
@Autowired CommonDao commonDao;
@Autowired TermDao termDao;
@Autowired LinkDao linkDao;
@Autowired ItemDao itemDao;
+ @Autowired ContentsHelper contentsHelper;
+ @Autowired ContentsService contentsService;
+
+// @ContentsCache
+ @RequestMapping
+ @ResponseBody
+ public Object getContents(@RequestParam("name") String name) {
+ name = UriGenerator.getValueFromUri(Category.class, name);
+ name = name.replace("параграф:", "");
+ return contentsHelper.createContents(name);
+ }
@RequestMapping(value = "add", method = POST)
@ResponseBody
@@ -55,4 +69,9 @@ public List autoComplete(@RequestParam("filter[filters][0][value]") Stri
}
return names;
}
+
+ @RequestMapping("reload")
+ public void reload() {
+ contentsService.reload();
+ }
}
diff --git a/src/main/java/org/ayfaar/app/controllers/DocumentController.java b/src/main/java/org/ayfaar/app/controllers/DocumentController.java
new file mode 100644
index 00000000..f836b21d
--- /dev/null
+++ b/src/main/java/org/ayfaar/app/controllers/DocumentController.java
@@ -0,0 +1,85 @@
+package org.ayfaar.app.controllers;
+
+import com.google.api.services.drive.model.File;
+import lombok.extern.slf4j.Slf4j;
+import org.ayfaar.app.annotations.Moderated;
+import org.ayfaar.app.dao.CommonDao;
+import org.ayfaar.app.model.Document;
+import org.ayfaar.app.services.moderation.Action;
+import org.ayfaar.app.services.moderation.ModerationService;
+import org.ayfaar.app.utils.GoogleService;
+import org.springframework.data.domain.Pageable;
+import org.springframework.data.web.PageableDefault;
+import org.springframework.util.Assert;
+import org.springframework.web.bind.annotation.*;
+
+import javax.inject.Inject;
+import java.io.IOException;
+import java.util.List;
+import java.util.Optional;
+
+import static org.ayfaar.app.utils.UriGenerator.generate;
+import static org.springframework.data.domain.Sort.Direction.DESC;
+import static org.springframework.util.Assert.hasLength;
+
+@Slf4j
+@RestController
+@RequestMapping("api/document")
+public class DocumentController {
+ @Inject CommonDao commonDao;
+ @Inject GoogleService googleService;
+ @Inject ModerationService moderationService;
+
+ @RequestMapping(method = RequestMethod.POST)
+ @Moderated(value = Action.DOCUMENT_ADD, command = "@documentController.create")
+ public Document create(@RequestParam String url,
+ @RequestParam(required = false) Optional name,
+ @RequestParam(required = false) String author,
+ @RequestParam(required = false) String annotation) throws IOException {
+ Assert.hasLength(url);
+ if(!url.contains("google.com")){
+ File file = googleService.uploadToGoogleDrive(url, name.orElse("Новый документ"));
+ url = file.getAlternateLink();
+ }
+ final String docId = GoogleService.extractDocIdFromUrl(url);
+ return commonDao.getOpt(Document.class, generate(Document.class, docId))
+ .orElseGet(() -> {
+ final GoogleService.DocInfo docInfo = googleService.getDocInfo(docId);
+ final Document document = Document.builder()
+ .id(docId)
+ .name(name.orElse(docInfo.title))
+ .annotation(annotation)
+ .author(author)
+ .thumbnail(docInfo.thumbnailLink)
+ .mimeType(docInfo.mimeType)
+ .icon(docInfo.iconLink)
+ .downloadUrl(docInfo.downloadUrl)
+ .build();
+ commonDao.save(document);
+ moderationService.notice(Action.DOCUMENT_CREATED, document.getName(), document.getUri());
+ return document;
+ });
+
+ }
+
+ @RequestMapping("{id}")
+ public Document get(@PathVariable String id) {
+ return commonDao.get(Document.class, generate(Document.class, id));
+ }
+
+ @RequestMapping("last")
+ public List getLast(@PageableDefault(size = 9, sort = "createdAt", direction = DESC) Pageable pageable) {
+ return commonDao.getPage(Document.class, pageable);
+ }
+
+ @RequestMapping(value = "update-name", method = RequestMethod.POST)
+ @Moderated(value = Action.DOCUMENT_RENAME, command = "@documentController.updateTitle")
+ public void updateTitle(@RequestParam String uri, @RequestParam String title) {
+ hasLength(uri);
+ Document document = commonDao.getOpt(Document.class, "uri", uri).orElseThrow(() -> new RuntimeException("Couldn't rename, document is not defined!"));
+ final String oldName = document.getName();
+ document.setName(title);
+ commonDao.save(document);
+ moderationService.notice(Action.DOCUMENT_RENAMED, oldName, document.getName(), document.getUri());
+ }
+}
diff --git a/src/main/java/org/ayfaar/app/controllers/ImageController.java b/src/main/java/org/ayfaar/app/controllers/ImageController.java
new file mode 100644
index 00000000..b165e0cf
--- /dev/null
+++ b/src/main/java/org/ayfaar/app/controllers/ImageController.java
@@ -0,0 +1,160 @@
+package org.ayfaar.app.controllers;
+
+
+import com.google.api.services.drive.model.File;
+import lombok.extern.slf4j.Slf4j;
+import one.util.streamex.StreamEx;
+import org.ayfaar.app.dao.CommonDao;
+import org.ayfaar.app.model.Image;
+import org.ayfaar.app.services.images.ImageService;
+import org.ayfaar.app.services.links.LinkService;
+import org.ayfaar.app.services.topics.TopicService;
+import org.ayfaar.app.utils.GoogleService;
+import org.ayfaar.app.utils.SearchSuggestions;
+import org.dozer.Mapper;
+import org.springframework.data.domain.Pageable;
+import org.springframework.data.web.PageableDefault;
+import org.springframework.util.Assert;
+import org.springframework.web.bind.annotation.*;
+
+import javax.inject.Inject;
+import java.io.IOException;
+import java.util.*;
+
+import static java.lang.Math.min;
+import static org.ayfaar.app.utils.UriGenerator.generate;
+import static org.springframework.data.domain.Sort.Direction.DESC;
+import static org.springframework.util.Assert.hasLength;
+
+@Slf4j
+@RestController
+@RequestMapping("api/image")
+public class ImageController {
+ @Inject CommonDao commonDao;
+ @Inject GoogleService googleService;
+ @Inject ImageService imageService;
+ @Inject LinkService linkService;
+ @Inject SearchSuggestions searchSuggestions;
+ @Inject TopicService topicService;
+ @Inject Mapper mapper;
+
+ private static final int MAX_SUGGESTIONS = 7;
+
+ @RequestMapping(method = RequestMethod.POST)
+ public Image create(@RequestParam String url,
+ @RequestParam(required = false) Optional name) throws IOException {
+
+ Assert.hasLength(url);
+ if(!url.contains("google.com")){
+ File file = googleService.uploadToGoogleDrive(url, name.orElse("Новая иллюстрация"));
+ url = file.getAlternateLink();
+ }
+ final String imgId = GoogleService.extractImageIdFromUrl(url);
+ return commonDao.getOpt(Image.class,generate(Image.class,imgId))
+ .orElseGet(() -> {
+ final GoogleService.ImageInfo imageInfo = googleService.getImageInfo(imgId);
+ final Image image = Image.builder()
+ .id(imgId)
+ .name(name.orElse(imageInfo.title))
+ .downloadUrl(imageInfo.downloadUrl)
+ .mimeType(imageInfo.mimeType)
+ .thumbnail(imageInfo.thumbnailLink)
+ .build();
+ commonDao.save(image);
+ imageService.registerImage(image);
+ return image;
+ });
+ }
+
+ @RequestMapping()
+ public List getAll() {
+ return imageService.getAllImages();
+ }
+
+ @RequestMapping("{id}")
+ public Image get(@PathVariable String id) {
+ return commonDao.get(Image.class, generate(Image.class, id));
+ }
+
+ @RequestMapping("last")
+ public List getLast(@PageableDefault(size = 9, sort = "createdAt", direction = DESC) Pageable pageable) {
+ return commonDao.getPage(Image.class, pageable);
+ }
+
+ @RequestMapping(value = "update-name", method = RequestMethod.POST)
+ public void updateTitle(@RequestParam String uri, @RequestParam String title) {
+ hasLength(uri);
+ Image image = commonDao.getOpt(Image.class, "uri", uri).orElseThrow(() -> new RuntimeException("Couldn't rename, image is not defined!"));
+ image.setName(title);
+ commonDao.save(image);
+ }
+
+ @RequestMapping("{id}/remove")
+ public void remove(@PathVariable String id) {
+ commonDao.getOpt(Image.class, "id", id).ifPresent(image -> {
+ imageService.removeImage(image);
+ commonDao.remove(image);
+ });
+ }
+
+ @RequestMapping(value = "update-comment", method = RequestMethod.POST)
+ public void updateComment(@RequestParam String uri, @RequestParam String comment) {
+ hasLength(uri);
+ Image image = commonDao.getOpt(Image.class, "uri", uri).orElseThrow(() -> new RuntimeException("Couldn't update comment, image is not defined!"));
+ image.setComment(comment);
+ commonDao.save(image);
+ }
+
+ @RequestMapping("search")
+ public List search(@RequestParam String q){
+ Map allSuggestions = new LinkedHashMap<>();
+ List items = new ArrayList<>();
+ items.add(Suggestions.IMAGES);
+ items.add(Suggestions.TOPIC); //image-keywords
+ for (Suggestions item : items) {
+ Queue queriesQueue = searchSuggestions.getQueue(q);
+ List> suggestions = getSuggestions(queriesQueue, item);
+ allSuggestions.putAll(searchSuggestions.getAllSuggestions(q,suggestions));
+ }
+
+ return StreamEx.of(allSuggestions.entrySet())
+ .map(entry -> {
+ final ImageEx imageEx = mapper.map(imageService.getByUri(entry.getKey()), ImageEx.class);
+ if (!entry.getValue().equals(imageEx.getName().toLowerCase())) imageEx.hint = entry.getValue();
+ return imageEx;
+ })
+ .toList();
+ }
+
+ private List> getSuggestions(Queue queriesQueue, Suggestions item) {
+ List> suggestions = new ArrayList<>();
+
+ while (suggestions.size() < MAX_SUGGESTIONS && queriesQueue.peek() != null) {
+ List extends Map.Entry> founded = null;
+ Map mapUriWithNames = null;
+
+ switch (item) {
+ case TOPIC://image-keywords
+ mapUriWithNames = imageService.getImagesKeywords();
+ break;
+ case IMAGES:
+ mapUriWithNames = imageService.getAllUriNames();
+ break;
+ }
+
+ founded = searchSuggestions.getSuggested(queriesQueue.poll(), suggestions, mapUriWithNames.entrySet(), Map.Entry::getValue);
+
+ suggestions.addAll(founded.subList(0, min(MAX_SUGGESTIONS - suggestions.size(), founded.size())));
+ }
+
+ Collections.sort(suggestions, (e1, e2) -> Integer.valueOf(e1.getValue().length()).compareTo(e2.getValue().length()));
+ return suggestions;
+ }
+
+ public static class ImageEx extends Image {
+ public String hint;
+
+ public ImageEx() {
+ }
+ }
+}
diff --git a/src/main/java/org/ayfaar/app/controllers/IntegrationController.java b/src/main/java/org/ayfaar/app/controllers/IntegrationController.java
new file mode 100644
index 00000000..45bb19af
--- /dev/null
+++ b/src/main/java/org/ayfaar/app/controllers/IntegrationController.java
@@ -0,0 +1,75 @@
+package org.ayfaar.app.controllers;
+
+import org.apache.commons.lang.ArrayUtils;
+import org.ayfaar.app.dao.ItemDao;
+import org.ayfaar.app.utils.RegExpUtils;
+import org.ayfaar.app.utils.TermService;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.stereotype.Controller;
+import org.springframework.web.bind.annotation.*;
+
+import java.net.URLDecoder;
+import java.util.*;
+import java.util.regex.Matcher;
+import java.util.regex.Pattern;
+
+import static java.util.regex.Pattern.compile;
+
+@Controller
+@RequestMapping("api/integration")
+public class IntegrationController {
+ @Autowired
+ TermService termService;
+ @Autowired ItemDao itemDao;
+
+ private List allItemNumbers;
+ private Map>> cache = new HashMap>>();
+ private String[] ignoreTerms = {"интеллект", "чувство", "мысль", "воля", "время", "мир", "личность", "жизнь", "ген",
+ "молекула", "атом", "элементарный", "форма", "окружающая действительность", "днк",
+ "трансформация", "сознание", "мироздание", "закон", "реальность", "энергия", "истина", "масса", "вселенная",
+ "идеи", "разум", "масса", "инерция", "земля", "аспект", "орис", "осознанность", "любовь", "пространство",
+ "мудрость"
+ };
+
+ @RequestMapping
+ @ResponseBody
+ public Object t(@RequestBody String text, @RequestHeader("Referer") String referer, @RequestParam String id) {
+ String cacheKey = referer+"#"+id;
+ if (cache.containsKey(cacheKey)) return cache.get(cacheKey);
+
+ Map contains = new LinkedHashMap();
+ text = URLDecoder.decode(text).toLowerCase();
+
+ // terms
+ for (Map.Entry entry : termService.getAll()) {
+ String key = entry.getKey();
+ TermService.TermProvider provider = entry.getValue();
+ if (ArrayUtils.contains(ignoreTerms, provider.getName().toLowerCase())) continue;
+ Matcher matcher = compile("((" + RegExpUtils.W + ")|^)" + key + "((" + RegExpUtils.W + ")|$)", Pattern.UNICODE_CHARACTER_CLASS).matcher(text);
+ if (matcher.find()) {
+ contains.put(key, provider.getName());
+ text = text.replaceAll(key, "");
+ }
+ }
+ // item numbers
+ if (allItemNumbers == null) {
+ allItemNumbers = itemDao.getAllNumbers();
+ }
+ for (String itemNumber : allItemNumbers) {
+ Matcher matcher = compile("((" + RegExpUtils.W + ")|^|\\[)" + itemNumber + "((" + RegExpUtils.W + ")|$|\\])", Pattern.UNICODE_CHARACTER_CLASS).matcher(text);
+ if (matcher.find()) {
+ contains.put(itemNumber, itemNumber);
+ }
+ }
+
+ final List> list = new ArrayList>(contains.entrySet());
+ Collections.sort(list, new Comparator>() {
+ @Override
+ public int compare(Map.Entry o1, Map.Entry o2) {
+ return Integer.compare(o2.getKey().length(), o1.getKey().length());
+ }
+ });
+ cache.put(cacheKey, list);
+ return list.isEmpty() ? "" : list;
+ }
+}
diff --git a/src/main/java/org/ayfaar/app/controllers/ItemController.java b/src/main/java/org/ayfaar/app/controllers/ItemController.java
index a29dff2d..9102bf4a 100644
--- a/src/main/java/org/ayfaar/app/controllers/ItemController.java
+++ b/src/main/java/org/ayfaar/app/controllers/ItemController.java
@@ -6,8 +6,11 @@
import org.ayfaar.app.model.Item;
import org.ayfaar.app.model.Link;
import org.ayfaar.app.model.Term;
-import org.ayfaar.app.spring.Model;
-import org.ayfaar.app.utils.AliasesMap;
+import org.ayfaar.app.utils.TermService;
+import org.ayfaar.app.utils.TermServiceImpl;
+import org.ayfaar.app.utils.TermsTaggingUpdater;
+import org.ayfaar.app.utils.exceptions.ExceptionCode;
+import org.ayfaar.app.utils.exceptions.LogicalException;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Controller;
import org.springframework.ui.ModelMap;
@@ -16,22 +19,27 @@
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.ResponseBody;
+import javax.servlet.http.HttpServletResponse;
+import java.io.IOException;
+
import static org.ayfaar.app.utils.ValueObjectUtils.getModelMap;
import static org.springframework.util.Assert.notNull;
import static org.springframework.web.bind.annotation.RequestMethod.POST;
@Controller
-@RequestMapping("item")
+@RequestMapping("api/item")
public class ItemController {
@Autowired CommonDao commonDao;
@Autowired ItemDao itemDao;
@Autowired TermDao termDao;
@Autowired TermController termController;
- @Autowired AliasesMap aliasesMap;
+ @Autowired TermService termService;
+ @Autowired TermServiceImpl aliasesMap;
+ @Autowired TermsTaggingUpdater taggingUpdater;
@RequestMapping(value = "{number}", method = POST)
- @Model
+ @ResponseBody
public Item add(@PathVariable String number, @RequestBody String content) {
Item item = itemDao.getByNumber(number);
if (item == null) {
@@ -42,11 +50,17 @@ public Item add(@PathVariable String number, @RequestBody String content) {
return item;
}
+ @RequestMapping("{number}!")
+ public void update(@PathVariable String number, HttpServletResponse response) throws IOException {
+ taggingUpdater.update(itemDao.getByNumber(number));
+ response.sendRedirect(number);
+ }
+
@RequestMapping("{number}")
- @Model
+ @ResponseBody
public ModelMap get(@PathVariable String number) {
Item item = itemDao.getByNumber(number);
- notNull(item, "Item not found");
+ if (item == null) throw new LogicalException(ExceptionCode.ITEM_NOT_FOUND, number);
ModelMap modelMap = (ModelMap) getModelMap(item);
// modelMap.put("linkedTerms", getLinkedTerms(item));
Item next = itemDao.get(item.getNext());
@@ -86,7 +100,6 @@ public static String formatNumber(int number, int length) {
}
@RequestMapping("{number}/linked-terms")
- @Model
@ResponseBody
public Object getLinkedTerms(@PathVariable String number) {
Item item = itemDao.getByNumber(number);
@@ -96,13 +109,13 @@ public Object getLinkedTerms(@PathVariable String number) {
}
@RequestMapping("{number}/{term}")
- @Model
+ @ResponseBody
public Link assignToTerm(@PathVariable String number, @PathVariable String term) {
return assignToTermWithWeight(number, term, null);
}
@RequestMapping("{number}/{term}/{weight}")
- @Model
+ @ResponseBody
public Link assignToTermWithWeight(@PathVariable String number,
@PathVariable String term,
@PathVariable Byte weight) {
diff --git a/src/main/java/org/ayfaar/app/controllers/LinkController.java b/src/main/java/org/ayfaar/app/controllers/LinkController.java
index 4323c128..9e33f39d 100644
--- a/src/main/java/org/ayfaar/app/controllers/LinkController.java
+++ b/src/main/java/org/ayfaar/app/controllers/LinkController.java
@@ -8,31 +8,35 @@
import org.ayfaar.app.model.Link;
import org.ayfaar.app.model.Term;
import org.ayfaar.app.model.TermMorph;
-import org.ayfaar.app.utils.AliasesMap;
+import org.ayfaar.app.services.links.LinkService;
import org.ayfaar.app.utils.EmailNotifier;
+import org.ayfaar.app.utils.TermService;
+import org.ayfaar.app.utils.TermsMarker;
import org.springframework.beans.factory.annotation.Autowired;
-import org.springframework.stereotype.Controller;
-import org.springframework.web.bind.annotation.PathVariable;
-import org.springframework.web.bind.annotation.RequestMapping;
-import org.springframework.web.bind.annotation.RequestParam;
-import org.springframework.web.bind.annotation.ResponseBody;
+import org.springframework.data.domain.Pageable;
+import org.springframework.data.web.PageableDefault;
+import org.springframework.web.bind.annotation.*;
import javax.mail.MessagingException;
-
import java.util.List;
+import static org.springframework.data.domain.Sort.Direction.DESC;
import static org.springframework.web.bind.annotation.RequestMethod.POST;
-@Controller
-@RequestMapping("link")
+@RestController
+@RequestMapping("api/link")
public class LinkController {
@Autowired LinkDao linkDao;
@Autowired TermDao termDao;
@Autowired ItemDao itemDao;
@Autowired TermController termController;
@Autowired EmailNotifier emailNotifier;
- @Autowired AliasesMap aliasesMap;
+ @Autowired TermService termService;
@Autowired TermMorphDao termMorphDao;
+ @Autowired TermsMarker termsMarker;
+// @Autowired ApplicationEventPublisher eventPublisher;
+ @Autowired LinkService linkService;
+
@RequestMapping(value = "addQuote", method = POST)
@ResponseBody
@@ -44,16 +48,17 @@ public Integer link(@RequestParam("term") String termName,
}
Term term = termDao.getByName(termName);
if (term == null) {
- if (aliasesMap.get(termName) != null) {
- term = aliasesMap.get(termName).getTerm();
+ if (termService.get(termName) != null) {
+ term = termService.getTerm(termName);
} else {
term = termController.add(termName);
}
}
Item item = itemDao.getByNumber(itemNumber);
- Link link = linkDao.save(new Link(term, item, quote.isEmpty() ? null : quote));
-
- emailNotifier.newQuoteLink(term.getName(), itemNumber, quote, link.getLinkId());
+ Link link = linkDao.save(new Link(term, item,
+ quote.isEmpty() ? null : quote,
+ quote.isEmpty() ? null : termsMarker.mark(quote)));
+ linkService.registerNew(link);
return link.getLinkId();
}
@@ -70,6 +75,8 @@ public Integer addAlias(@RequestParam("term1") String term,
if (type != null && type == 0) {
// Morph
termMorphDao.save(new TermMorph(alias, primTerm.getUri()));
+ // need it to start tagging update for this term and all morph aliases
+// eventPublisher.publishEvent(new TermUpdatedEvent(primTerm, alias));
return 1;
}
Term aliasTerm = termDao.getByName(alias);
@@ -78,7 +85,9 @@ public Integer addAlias(@RequestParam("term1") String term,
}
Link link = linkDao.save(new Link(primTerm, aliasTerm, type));
- emailNotifier.newLink(term, alias, link.getLinkId());
+ //emailNotifier.newLink(term, alias, link.getLinkId());
+// eventPublisher.publishEvent(new NewLinkEvent(term, alias, link));
+ linkService.registerNew(link);
return link.getLinkId();
}
@@ -93,4 +102,35 @@ public void remove(@PathVariable Integer id) {
public List getCreatedFromSearch(){
return linkDao.getList("source", "search");
}
+
+ @RequestMapping("last")
+ public List getLast(@PageableDefault(size = 10, sort = "createdAt", direction = DESC) Pageable pageable) {
+ return linkDao.getPage(pageable);
+ }
+
+ @RequestMapping("update/rate")
+ public void updateRate(@RequestParam String uri1,
+ @RequestParam String uri2,
+ @RequestParam Float value) {
+ linkService.getByUris(uri1, uri2).get().updater().rate(value).commit();
+ }
+
+ @RequestMapping("update/comment")
+ public void updateComment(@RequestParam String uri1,
+ @RequestParam String uri2,
+ @RequestParam String value) {
+ linkService.getByUris(uri1, uri2).get().updater().comment(value).commit();
+ }
+
+ @RequestMapping("update/quote")
+ public void updateQuote(@RequestParam String uri1,
+ @RequestParam String uri2,
+ @RequestParam String value) {
+ linkService.getByUris(uri1, uri2).get().updater().quote(value).commit();
+ }
+
+ @RequestMapping("reload")
+ public void reload() {
+ linkService.reload();
+ }
}
diff --git a/src/main/java/org/ayfaar/app/controllers/MaintenanceController.java b/src/main/java/org/ayfaar/app/controllers/MaintenanceController.java
new file mode 100644
index 00000000..48d4e64e
--- /dev/null
+++ b/src/main/java/org/ayfaar/app/controllers/MaintenanceController.java
@@ -0,0 +1,47 @@
+package org.ayfaar.app.controllers;
+
+import org.ayfaar.app.services.EntityLoader;
+import org.ayfaar.app.sync.GetVideosFormYoutube;
+import org.ayfaar.app.sync.RecordSynchronizer;
+import org.ayfaar.app.sync.VocabularySynchronizer;
+import org.ayfaar.app.translation.TopicTranslationSynchronizer;
+import org.springframework.web.bind.annotation.RequestMapping;
+import org.springframework.web.bind.annotation.RestController;
+
+import javax.inject.Inject;
+import java.io.IOException;
+
+@RestController
+@RequestMapping("api")
+public class MaintenanceController {
+ @Inject EntityLoader entityLoader;
+ @Inject RecordSynchronizer recordSynchronizer;
+ @Inject TopicTranslationSynchronizer topicTranslationSynchronizer;
+ @Inject GetVideosFormYoutube getVideosFormYoutube;
+ @Inject VocabularySynchronizer vocabularySynchronizer;
+
+ @RequestMapping("entity-loader/clear")
+ public void clearEntityLoader() {
+ entityLoader.clear();
+ }
+
+ @RequestMapping("sync/records")
+ public void synchronizeRecords() throws IOException {
+ recordSynchronizer.synchronize();
+ }
+
+ @RequestMapping("sync/translations")
+ public void synchronizeTranslations() throws IOException {
+ topicTranslationSynchronizer.synchronize();
+ }
+
+ @RequestMapping("sync/videos")
+ public void synchronizeVideos() throws IOException {
+ getVideosFormYoutube.synchronize();
+ }
+
+ @RequestMapping("sync/vocabulary")
+ public void synchronizeVocabulary() throws IOException {
+ vocabularySynchronizer.synchronize();
+ }
+}
diff --git a/src/main/java/org/ayfaar/app/controllers/ModerationController.java b/src/main/java/org/ayfaar/app/controllers/ModerationController.java
new file mode 100644
index 00000000..ab24f4d9
--- /dev/null
+++ b/src/main/java/org/ayfaar/app/controllers/ModerationController.java
@@ -0,0 +1,97 @@
+package org.ayfaar.app.controllers;
+
+import one.util.streamex.StreamEx;
+import org.ayfaar.app.dao.CommonDao;
+import org.ayfaar.app.model.ActionEvent;
+import org.ayfaar.app.model.PendingAction;
+import org.ayfaar.app.model.User;
+import org.ayfaar.app.services.moderation.ModerationService;
+import org.ayfaar.app.services.user.UserService;
+import org.ayfaar.app.utils.CurrentUserProvider;
+import org.hibernate.criterion.Restrictions;
+import org.springframework.data.domain.Pageable;
+import org.springframework.data.web.PageableDefault;
+import org.springframework.security.access.prepost.PreAuthorize;
+import org.springframework.security.core.annotation.AuthenticationPrincipal;
+import org.springframework.transaction.annotation.Transactional;
+import org.springframework.web.bind.annotation.PathVariable;
+import org.springframework.web.bind.annotation.RequestMapping;
+import org.springframework.web.bind.annotation.RequestMethod;
+import org.springframework.web.bind.annotation.RestController;
+
+import javax.inject.Inject;
+import java.util.List;
+import java.util.Objects;
+
+import static java.util.Comparator.comparingInt;
+import static org.springframework.data.domain.Sort.Direction.DESC;
+
+@RestController
+@RequestMapping("api/moderation")
+@PreAuthorize("authenticated")
+public class ModerationController {
+ private final CommonDao commonDao;
+ private final ModerationService service;
+ private final UserService userService;
+ private CurrentUserProvider currentUserProvider;
+
+ @Inject
+ public ModerationController(ModerationService service, CommonDao commonDao, UserService userService, CurrentUserProvider currentUserProvider) {
+ this.service = service;
+ this.commonDao = commonDao;
+ this.userService = userService;
+ this.currentUserProvider = currentUserProvider;
+ }
+
+ @RequestMapping("pending_actions")
+ public List getPendingActions(@AuthenticationPrincipal User currentUser) {
+ // show only my users (this user can be linked with another as children for personal moderation)
+ return StreamEx.of(commonDao.getList(PendingAction.class, "confirmedBy", null))
+ .filter(a -> currentUserProvider.getCurrentAccessLevel().accept(a.getAction().getRequiredAccessLevel())
+ || Objects.equals(currentUser.getId(), a.getInitiatedBy()))
+ .reverseSorted(comparingInt(PendingAction::getId))
+ .map(PendingActionPresentation::new)
+ .map(presentation -> {
+ presentation.owner = Objects.equals(currentUser.getId(), presentation.initiatedBy);
+ return presentation;
+ })
+ .toList();
+ }
+
+ @RequestMapping("last_actions")
+ @Transactional
+ public List getLastActions(@PageableDefault(sort = "createdAt", direction = DESC) Pageable pageable,
+ @AuthenticationPrincipal User currentUser) {
+ Integer hiddenActionEventId = currentUser.getHiddenActionEventId();
+ if (hiddenActionEventId == null) hiddenActionEventId = 0;
+ //noinspection unchecked
+ return commonDao.getCriteria(ActionEvent.class, pageable)
+ .add(Restrictions.ne("createdBy", currentUser.getId()))
+ .add(Restrictions.gt("id", hiddenActionEventId))
+ .list();
+ }
+
+ @RequestMapping("{id}/confirm")
+ public void confirm(@PathVariable Integer id) {
+ final PendingAction action = commonDao.getOpt(PendingAction.class, id).get();
+ service.confirm(action);
+ }
+
+ @RequestMapping(value = "{id}/cancel", method = RequestMethod.POST)
+ public void cancel(@PathVariable Integer id) {
+ service.cancel(id);
+ }
+
+ private class PendingActionPresentation {
+ public Integer id;
+ public String text;
+ public Integer initiatedBy;
+ public Boolean owner; // is this action created by current user
+
+ public PendingActionPresentation(PendingAction action) {
+ id = action.getId();
+ text = action.getMessage();
+ initiatedBy = action.getInitiatedBy();//userService.getPresentation(action.getInitiatedBy());
+ }
+ }
+}
diff --git a/src/main/java/org/ayfaar/app/controllers/NewItemController.java b/src/main/java/org/ayfaar/app/controllers/NewItemController.java
new file mode 100644
index 00000000..35d22e96
--- /dev/null
+++ b/src/main/java/org/ayfaar/app/controllers/NewItemController.java
@@ -0,0 +1,163 @@
+package org.ayfaar.app.controllers;
+
+import org.ayfaar.app.dao.ItemDao;
+import org.ayfaar.app.model.Item;
+import org.ayfaar.app.utils.ContentsService;
+import org.ayfaar.app.utils.ContentsService.ParagraphProvider;
+import org.ayfaar.app.utils.TermsMarker;
+import org.ayfaar.app.utils.TermsTaggingUpdater;
+import org.springframework.core.task.AsyncTaskExecutor;
+import org.springframework.stereotype.Controller;
+import org.springframework.web.bind.annotation.PathVariable;
+import org.springframework.web.bind.annotation.RequestMapping;
+import org.springframework.web.bind.annotation.RequestParam;
+import org.springframework.web.bind.annotation.ResponseBody;
+
+import javax.inject.Inject;
+import java.util.ArrayList;
+import java.util.List;
+import java.util.Optional;
+import java.util.stream.Collectors;
+
+import static java.lang.String.format;
+import static org.ayfaar.app.controllers.ItemController.getPrev;
+import static org.ayfaar.app.utils.UriGenerator.generate;
+import static org.springframework.util.Assert.notNull;
+import static org.springframework.util.StringUtils.isEmpty;
+
+@Controller
+@RequestMapping("api/v2/item")
+public class NewItemController {
+
+ private static final int MAXIMUM_RANGE_SIZE = 200;
+ @Inject ItemDao itemDao;
+ @Inject ContentsService contentsService;
+ @Inject TermsMarker termsMarker;
+ @Inject AsyncTaskExecutor taskExecutor;
+ @Inject TermsTaggingUpdater taggingUpdater;
+
+ @RequestMapping
+ @ResponseBody
+ public ItemPresentation get(@RequestParam String number) {
+ final Item item = itemDao.getByNumber(number);
+ notNull(item, format("Item `%s` not found", number));
+
+ if (item.getNext() != null) {
+ taskExecutor.execute(() -> {
+ final Item nextItem = itemDao.get(item.getNext());
+ taggingUpdater.update(nextItem);
+ });
+ }
+ if (isEmpty(item.getTaggedContent())) {
+ taggingUpdater.update(item);
+ itemDao.save(item);
+ }
+ return new ItemPresentation(item, generate(Item.class, getPrev(item.getNumber())), true);
+ }
+
+ @RequestMapping("{number}/{more}more")
+ @ResponseBody
+ public List getMore(@PathVariable String number, @PathVariable Integer more) {
+ List presentations = new ArrayList();
+
+ for (Item item : itemDao.getNext(number, more)) {
+ presentations.add(new ItemPresentation(item));
+ }
+ return presentations;
+ }
+
+ @RequestMapping("range")
+ @ResponseBody
+ public List get(@RequestParam String from, @RequestParam String to) {
+ Item item = itemDao.getByNumber(from);
+ notNull(item, format("Item `%s` not found", from));
+ notNull(itemDao.getByNumber(to), format("Item `%s` not found", to));
+
+ List items = new ArrayList();
+
+ final ItemPresentation itemPresentation = new ItemPresentation(item);
+// itemPresentation.parents = parents(item.getNumber());
+ items.add(itemPresentation);
+
+ while (!item.getNumber().equals(to)) {
+ item = itemDao.get(item.getNext());
+ items.add(new ItemPresentation(item));
+ if (items.size() > MAXIMUM_RANGE_SIZE) {
+ throw new RuntimeException(format("Maximum range size reached (from %s to %s)", from, to));
+ }
+ }
+
+ return items;
+ }
+
+ private class ItemPresentation {
+ public final String number;
+ public final String content;
+ public String uri;
+ public String next;
+ public String previous;
+ public List parents;
+
+ public ItemPresentation(Item item, String previous, Boolean loadParents) {
+ this.uri = item.getUri();
+ this.content = item.getTaggedContent();
+ this.previous = previous;
+ number = item.getNumber();
+ next = item.getNext();
+ parents = loadParents ? getParents(item.getNumber()) : null;
+ }
+ public ItemPresentation(Item item) {
+ this(item, null, false);
+ }
+ }
+
+ public class ParentPresentation {
+ public String from;
+ public String to;
+ public final String name;
+ public final String uri;
+
+ public ParentPresentation(String name, String uri) {
+ this.name = name;
+ this.uri = uri;
+ }
+
+ public ParentPresentation(String name, String uri, String from, String to) {
+ this(name, uri);
+ this.from = from;
+ this.to = to;
+ }
+ }
+
+ @RequestMapping(value = "{number}/content", produces = "text/plain; charset=utf-8")
+ @ResponseBody
+ public String getContent(@PathVariable String number) {
+ Item item = itemDao.getByNumber(number);
+ if (item != null) {
+ return item.getTaggedContent();
+ }
+ return null;
+ }
+
+ @RequestMapping("{number}/mark")
+ public void mark(@PathVariable String number) {
+ Item item = itemDao.getByNumber(number);
+ if (item != null) {
+ item.setTaggedContent(termsMarker.mark(item.getContent()));
+ itemDao.save(item);
+ }
+ }
+
+ private List getParents(String number) {
+ List parents = new ArrayList<>();
+ Optional extends ParagraphProvider> paragraphOpt = contentsService.getByItemNumber(number);
+ if (paragraphOpt.isPresent()) {
+ final ParagraphProvider paragraph = paragraphOpt.get();
+ parents.addAll(paragraph.parents().stream()
+ .map(parent -> new ParentPresentation(parent.extractCategoryName(), parent.uri()))
+ .collect(Collectors.toList()));
+ parents.add(new ParentPresentation(paragraph.name(), paragraph.uri(), paragraph.from(), paragraph.to()));
+ }
+ return parents;
+ }
+}
diff --git a/src/main/java/org/ayfaar/app/controllers/NewSearchController.java b/src/main/java/org/ayfaar/app/controllers/NewSearchController.java
index 46ee4584..619bf000 100644
--- a/src/main/java/org/ayfaar/app/controllers/NewSearchController.java
+++ b/src/main/java/org/ayfaar/app/controllers/NewSearchController.java
@@ -1,115 +1,176 @@
package org.ayfaar.app.controllers;
-import org.apache.commons.lang.NotImplementedException;
+import lombok.Data;
+import org.ayfaar.app.annotations.SearchResultCache;
import org.ayfaar.app.controllers.search.Quote;
import org.ayfaar.app.controllers.search.SearchQuotesHelper;
import org.ayfaar.app.controllers.search.SearchResultPage;
+import org.ayfaar.app.controllers.search.cache.DBCache;
import org.ayfaar.app.dao.SearchDao;
import org.ayfaar.app.model.Item;
-import org.ayfaar.app.model.Term;
+import org.ayfaar.app.services.itemRange.ItemRangeService;
+import org.ayfaar.app.utils.ContentsService;
+import org.ayfaar.app.utils.ContentsService.ContentsProvider;
+import org.ayfaar.app.utils.RegExpUtils;
+import org.ayfaar.app.utils.StringUtils;
+import org.ayfaar.app.utils.TermService;
import org.springframework.stereotype.Controller;
+import org.springframework.web.bind.annotation.RequestMapping;
+import org.springframework.web.bind.annotation.RequestParam;
+import org.springframework.web.bind.annotation.ResponseBody;
import javax.inject.Inject;
-import java.util.List;
+import java.io.IOException;
+import java.util.*;
import static java.util.Arrays.asList;
+import static java.util.stream.Collectors.toList;
+import static org.ayfaar.app.utils.TermService.TermProvider;
-//todo пометить как контролер и зделать доступнім по адресу "v2/search"
+@Controller
+@RequestMapping("api/v2/search")
public class NewSearchController {
public static final int PAGE_SIZE = 20;
- @Inject
- private SearchQuotesHelper handleItems;
-
- @Inject
- private SearchDao searchDao;
-
- private List searchQueries;
-
+ @Inject SearchQuotesHelper handleItems;
+ @Inject SearchDao searchDao;
+ @Inject TermService termService;
+ @Inject ContentsService contentsService;
+ @Inject DBCache cache;
+ @Inject ItemRangeService itemRangeService;
+// @Inject ApplicationEventPublisher eventPublisher;
+// @Inject CacheUpdater cacheUpdater;
/**
* Поиск будет производить только по содержимому Item
- * todo сделать этот метод доступным через веб
*
* @param pageNumber номер страницы
*/
- public SearchResultPage search(String query, Integer pageNumber, String fromItemNumber) {
+ @SearchResultCache
+ @RequestMapping
+ @ResponseBody
+ // возвращаем Object чтобы можно было вернуть закешированный json или SearchResultPage
+ public Object search(@RequestParam String query,
+ @RequestParam Integer pageNumber,
+ @RequestParam(required = false) String startFrom) {
// 1. Очищаем введённую фразу от лишних пробелов по краям и переводим в нижний регистр
query = prepareQuery(query);
- // 2. Проверяем есть ли кеш, если да возвращаем его
- if (hasCached(query, pageNumber, fromItemNumber)) {
- return getCache(query, pageNumber, fromItemNumber);
- }
-
SearchResultPage page = new SearchResultPage();
+ page.setHasMore(false);
// 3. Определить термин ли это
- Term term = getTerm(query);
- // если нет поискать в разных падежах
- if (term == null) {
- term = findTermInMorphs(query);
- }
-
+ Optional providerOpt = termService.get(query);
// 3.1. Если да, Получить все синониме термина
List- foundItems;
- // указывает сколько результатов поиска нужно пропустиьб, то есть когда ищем следующую страницу
+ // указывает сколько результатов поиска нужно пропустить, то есть когда ищем следующую страницу
int skipResults = pageNumber*PAGE_SIZE;
- if (term != null) {
+ List searchQueries;
+ if (providerOpt.isPresent()) {
// 3.2. Получить все падежи по всем терминам
- searchQueries = getAllMorphs(term);
+ searchQueries = providerOpt.get().getAllAliasesWithAllMorphs();
// 4. Произвести поиск
- // 4.1. Сначала поискать совпадение термина в различных падежах
- foundItems = searchDao.searchInDb(searchQueries, skipResults, PAGE_SIZE, fromItemNumber);
- // 4.2. Если количества не достаточно для заполнения страницы то поискать по синонимам
- List aliases = getAllAliases(term);
- List aliasesSearchQueries = getAllMorphs(aliases);
- foundItems.addAll(searchDao.searchInDb(searchQueries, skipResults, PAGE_SIZE - foundItems.size(), fromItemNumber));
- searchQueries.addAll(aliasesSearchQueries);
+ foundItems = searchDao.findInItems(searchQueries, skipResults, PAGE_SIZE + 1, startFrom);
+
+// if (foundItems.isEmpty()) {
+// eventPublisher.publishEvent(new LinkPushEvent("Не найдено - "+provider.getName(), provider.getName()));
+// }
} else {
// 4. Поиск фразы (не термин)
- foundItems = searchDao.searchInDb(query, skipResults, PAGE_SIZE, fromItemNumber);
+ query = query.replace("!", "");
+ searchQueries = asList(query.replace("%", "\\%").replace("*", "%"));
+ foundItems = searchDao.findInItems(searchQueries, skipResults, PAGE_SIZE + 1, startFrom);
+ searchQueries = asList(query.replace("%", ""));
}
- page.setHasMore(false);
+ if (foundItems.size() > PAGE_SIZE ) {
+ foundItems.remove(foundItems.size() - 1);
+ page.setHasMore(true);
+ }
// 5. Обработка найденных пунктов
List
quotes = handleItems.createQuotes(foundItems, searchQueries);
page.setQuotes(quotes);
- // 6. Вернуть результат
+ // 7. Вернуть результат
return page;
}
- private List getAllMorphs(Term term) {
- return getAllMorphs(asList(term));
- }
- private List getAllMorphs(List terms) {
- throw new NotImplementedException();
- }
+ @RequestMapping("categories")
+ @ResponseBody
+ public Object inCategories(@RequestParam String query) {
+ final Optional providerOpt = termService.get(query);
+ List searchQueries;
+ if (providerOpt.isPresent()) {
+ TermProvider provider = providerOpt.get();
+ provider = provider.getMainOrThis();
+ searchQueries = provider.getAllAliasesAndAbbreviationsWithAllMorphs();
+ } else {
+ query = query.replace("*", RegExpUtils.w + "+");
+ searchQueries = Collections.singletonList(query);
+ }
+ List foundCategoryProviders = contentsService.descriptionContains(searchQueries);
+
+ providerOpt
+ .ifPresent(termProvider -> itemRangeService.getParagraphsByMainTerm(termProvider.getMainOrThis().getName())
+ .map(paragraphCode -> contentsService.getParagraph(paragraphCode))
+ .filter(Optional::isPresent)
+ .map(Optional::get)
+ .sorted(Comparator.comparing(o -> Double.valueOf(o.from())))
+ .forEachOrdered(foundCategoryProviders::add));
+
+
+ List presentations = new ArrayList<>();
+ for (ContentsService.ContentsProvider p : foundCategoryProviders) {
+ String strongMarkedDescription = StringUtils.markWithStrong(p.description(), searchQueries);
+ FoundCategoryPresentation presentation = new FoundCategoryPresentation(p.path(), p.uri(), strongMarkedDescription);
+ presentations.add(presentation);
+ }
- private List getAllAliases(Term term) {
- throw new NotImplementedException();
- }
+ if (presentations.size() > 20) {
+ // reduce result amount
+ presentations = presentations.stream()
+ .sorted(Comparator.comparingInt(o -> o.getDescription().indexOf("strong")))
+ .limit(20)
+ .collect(toList());
+ }
- private Term findTermInMorphs(String query) {
- throw new NotImplementedException();
+ return presentations;
}
- private Term getTerm(String query) {
- throw new NotImplementedException();
+ private String prepareQuery(String query) {
+ if (query != null) {
+ query = query.replace("Обсуждение:", "");
+ query = query.replace("_", " ");
+ query = query.toLowerCase().trim();
+ }
+ return query;
}
- private SearchResultPage getCache(String query, Integer page, String fromItemNumber) {
- throw new NotImplementedException();
+ @RequestMapping("cache/clean")
+ public void cleanCache() {
+ cache.clear();
}
- private boolean hasCached(String query, Integer page, String fromItemNumber) {
- throw new NotImplementedException();
+ @RequestMapping("cache/update")
+ public void updateCache() throws IOException {
+// cacheUpdater.update();
}
- private String prepareQuery(String query) {
- throw new NotImplementedException();
+ public Object searchWithoutCache(String query, Integer pageNumber, String fromItemNumber) {
+ return search(query, pageNumber, fromItemNumber);
}
+ @Data
+ private class FoundCategoryPresentation {
+ private final String path;
+ private final String uri;
+ private final String description;
+
+ public FoundCategoryPresentation(String path, String uri, String description) {
+ this.path = path;
+ this.uri = uri;
+ this.description = description;
+ }
+ }
}
diff --git a/src/main/java/org/ayfaar/app/controllers/NewSuggestionsController.java b/src/main/java/org/ayfaar/app/controllers/NewSuggestionsController.java
new file mode 100644
index 00000000..0c6fd3fe
--- /dev/null
+++ b/src/main/java/org/ayfaar/app/controllers/NewSuggestionsController.java
@@ -0,0 +1,161 @@
+package org.ayfaar.app.controllers;
+
+import lombok.extern.slf4j.Slf4j;
+import one.util.streamex.StreamEx;
+import org.apache.commons.lang3.tuple.ImmutablePair;
+import org.ayfaar.app.dao.TermDao;
+import org.ayfaar.app.model.Term;
+import org.ayfaar.app.services.ItemService;
+import org.ayfaar.app.services.document.DocumentService;
+import org.ayfaar.app.services.images.ImageService;
+import org.ayfaar.app.services.moderation.UserRole;
+import org.ayfaar.app.services.record.RecordService;
+import org.ayfaar.app.services.topics.TopicService;
+import org.ayfaar.app.services.videoResource.VideoResourceService;
+import org.ayfaar.app.utils.*;
+import org.ayfaar.app.utils.contents.ContentsUtils;
+import org.springframework.web.bind.annotation.RequestMapping;
+import org.springframework.web.bind.annotation.RequestParam;
+import org.springframework.web.bind.annotation.ResponseBody;
+import org.springframework.web.bind.annotation.RestController;
+
+import javax.inject.Inject;
+import java.util.*;
+import java.util.stream.Collectors;
+
+import static java.lang.Math.min;
+
+@Slf4j
+@RestController
+@RequestMapping("api/suggestions")
+public class NewSuggestionsController {
+
+ @Inject TermService termService;
+ @Inject TopicService topicService;
+ @Inject ContentsService contentsService;
+ @Inject DocumentService documentService;
+ @Inject VideoResourceService videoResourceService;
+ @Inject RecordService recordService;
+ @Inject ItemService itemService;
+ @Inject ContentsUtils contentsUtils;
+ @Inject ImageService imageService;
+ @Inject SearchSuggestions searchSuggestions;
+ @Inject CurrentUserProvider currentUserProvider;
+
+ private List escapeChars = Arrays.asList("(", ")", "[", "]", "{", "}");
+ private static final int MAX_SUGGESTIONS = 5;
+ private static final int MAX_WORDS_PARAGRAPH_AFTER_SEARCH = 4;
+
+ @RequestMapping("term")
+ @ResponseBody
+ public Collection suggestionTerms(@RequestParam String q) {
+ return suggestions(q, true, false, false, false, false, false, false, false, false, false, false)
+ .values();
+ }
+
+ @RequestMapping("all")
+ @ResponseBody
+ public Map suggestions(@RequestParam String q,
+ @RequestParam(required = false, defaultValue = "true") boolean with_terms,
+ @RequestParam(required = false, defaultValue = "true") boolean with_topic,
+ @RequestParam(required = false, defaultValue = "true") boolean with_category_name,
+ @RequestParam(required = false, defaultValue = "true") boolean with_category_description,
+ @RequestParam(required = false, defaultValue = "true") boolean with_doc,
+ @RequestParam(required = false, defaultValue = "true") boolean with_video,
+ @RequestParam(required = false, defaultValue = "true") boolean with_video_code,
+ @RequestParam(required = false, defaultValue = "true") boolean with_item,
+ @RequestParam(required = false, defaultValue = "true") boolean with_record_name,
+ @RequestParam(required = false, defaultValue = "true") boolean with_record_code,
+ @RequestParam(required = false, defaultValue = "true") boolean with_images
+ ) {
+ Map allSuggestions = new LinkedHashMap<>();
+ List items = new ArrayList<>();
+ if (with_terms) items.add(Suggestions.TERM); //default
+ if (with_topic) items.add(Suggestions.TOPIC);
+ if (with_category_name) items.add(Suggestions.CATEGORY_NAME);
+ if (with_category_description) items.add(Suggestions.CATEGORY_DESCRIPTION);
+ if (with_doc) items.add(Suggestions.DOCUMENT);
+ if (with_video) items.add(Suggestions.VIDEO);
+ if (with_video_code) items.add(Suggestions.VIDEO_CODE);
+ if (with_record_name) items.add(Suggestions.RECORD_NAME);
+ if (with_record_code) items.add(Suggestions.RECORD_CODE);
+ if (with_item) items.add(Suggestions.ITEM);
+ if (with_images) items.add(Suggestions.IMAGES);
+ for (Suggestions item : items) {
+ Queue queriesQueue = searchSuggestions.getQueue(q);
+ List> suggestions = getSuggestions(queriesQueue, item);
+ allSuggestions.putAll(searchSuggestions.getAllSuggestions(q,suggestions));
+ }
+
+ if (!currentUserProvider.getCurrentAccessLevel().accept(UserRole.ROLE_EDITOR)) {
+ // remove duplications by values, but not for editors and admins
+ Set existing = new HashSet<>();
+ allSuggestions = allSuggestions.entrySet()
+ .stream()
+ .filter(entry -> existing.add(entry.getValue().toLowerCase()))
+ .collect(Collectors.toMap(Map.Entry::getKey,
+ Map.Entry::getValue,
+ (v1, v2) -> v1,
+ LinkedHashMap::new));
+ }
+
+ return allSuggestions;
+ }
+
+ private List> getSuggestions(Queue queriesQueue, Suggestions item) {
+ List> suggestions = new ArrayList<>();
+
+ while (suggestions.size() < MAX_SUGGESTIONS && queriesQueue.peek() != null) {
+ List extends Map.Entry> founded = null;
+ Map mapUriWithNames = null;
+ // fixme: в некоторых методах getAllUriNames при каждом вызове getSuggestions, происходит запрос в БД для получения всех имён, это не рационально, я бы сделал логику кеширования имён и обновления кеша в случае добавления/изменения видео или документа
+ // или можно сделать RegExp запрос в БД и тогда не нужен кеш вовсе, просто из соображений скорости я стараюсь уменьшить запросы в БД
+ switch (item) {
+ case TERM:
+ Collection allInfoTerms = termService.getAllInfoTerms();
+ final List suggested = searchSuggestions.getSuggested(queriesQueue.poll(), suggestions, allInfoTerms, TermDao.TermInfo::getName);
+ founded = StreamEx.of(suggested)
+ .map((i) -> new ImmutablePair<>(UriGenerator.generate(Term.class, i.getName()), i.getName()))
+ .toList();
+ break;
+ case TOPIC:
+ mapUriWithNames = topicService.getAllUriNames();
+ break;
+ case CATEGORY_NAME:
+ mapUriWithNames = contentsService.getAllUriNames();
+ break;
+ case CATEGORY_DESCRIPTION:
+ mapUriWithNames = contentsService.getAllUriDescription();
+ break;
+ case DOCUMENT:
+ mapUriWithNames = documentService.getAllUriNames();
+ break;
+ case VIDEO:
+ mapUriWithNames = videoResourceService.getAllUriNames();
+ break;
+ case VIDEO_CODE:
+ mapUriWithNames = videoResourceService.getAllUriCodes();
+ break;
+ case ITEM:
+ mapUriWithNames = itemService.getAllUriNumbers();
+ break;
+ case RECORD_NAME:
+ mapUriWithNames = recordService.getAllUriNames();
+ break;
+ case RECORD_CODE:
+ mapUriWithNames = recordService.getAllUriCodes();
+ break;
+ case IMAGES:
+ mapUriWithNames = imageService.getAllUriNames();
+ break;
+ }
+ if (item != Suggestions.TERM)
+ founded = searchSuggestions.getSuggested(queriesQueue.poll(), suggestions, mapUriWithNames.entrySet(), Map.Entry::getValue);
+
+ suggestions.addAll(founded.subList(0, min(MAX_SUGGESTIONS - suggestions.size(), founded.size())));
+ }
+
+ Collections.sort(suggestions, (e1, e2) -> Integer.valueOf(e1.getValue().length()).compareTo(e2.getValue().length()));
+ return suggestions;
+ }
+}
\ No newline at end of file
diff --git a/src/main/java/org/ayfaar/app/controllers/NewTermController.java b/src/main/java/org/ayfaar/app/controllers/NewTermController.java
new file mode 100644
index 00000000..ce359d2e
--- /dev/null
+++ b/src/main/java/org/ayfaar/app/controllers/NewTermController.java
@@ -0,0 +1,61 @@
+package org.ayfaar.app.controllers;
+
+import org.ayfaar.app.utils.RegExpUtils;
+import org.ayfaar.app.utils.TermService;
+import org.ayfaar.app.utils.TermServiceImpl;
+import org.ayfaar.app.utils.TermsTaggingUpdater;
+import org.springframework.core.task.AsyncTaskExecutor;
+import org.springframework.web.bind.annotation.*;
+
+import javax.inject.Inject;
+import java.util.ArrayList;
+import java.util.HashMap;
+import java.util.List;
+import java.util.Map;
+import java.util.regex.Matcher;
+import java.util.regex.Pattern;
+
+import static java.util.Collections.sort;
+import static java.util.regex.Pattern.compile;
+
+@RestController
+@RequestMapping("api/v2/term")
+public class NewTermController {
+ @Inject TermServiceImpl termsMap;
+ @Inject TermsTaggingUpdater taggingUpdater;
+ @Inject AsyncTaskExecutor taskExecutor;
+ @Inject SuggestionsController suggestionsController;
+
+ @RequestMapping("{termName}/mark")
+ public void mark(@PathVariable final String termName) {
+ taskExecutor.submit(() -> taggingUpdater.update(termName));
+ }
+
+ @RequestMapping(value = "get-terms-in-text", method = RequestMethod.POST)
+ public Object getTerms(@RequestParam String text) {
+ Map contains = new HashMap<>();
+ text = text.toLowerCase();
+
+ for (Map.Entry entry : termsMap.getAll()) {
+ String key = entry.getKey();
+ Matcher matcher = compile("((" + RegExpUtils.W + ")|^)" + key
+ + "((" + RegExpUtils.W + ")|$)", Pattern.UNICODE_CHARACTER_CLASS)
+ .matcher(text);
+ if (matcher.find()) {
+ int count = 1;
+ while (matcher.find()) count++;
+ contains.put(entry.getValue().getName(), count);
+ text = text.replaceAll(key, "");
+ }
+ }
+
+ final List> sorted = new ArrayList>(contains.entrySet());
+ sort(sorted, (o1, o2) -> o2.getValue().compareTo(o1.getValue()));
+ return sorted;
+ }
+
+ @RequestMapping("suggest")
+ public List suggest(@RequestParam String q) {
+ return suggestionsController.suggestions(q);
+ }
+}
diff --git a/src/main/java/org/ayfaar/app/controllers/OAuthProvider.java b/src/main/java/org/ayfaar/app/controllers/OAuthProvider.java
new file mode 100644
index 00000000..49c2b0ea
--- /dev/null
+++ b/src/main/java/org/ayfaar/app/controllers/OAuthProvider.java
@@ -0,0 +1,5 @@
+package org.ayfaar.app.controllers;
+
+public enum OAuthProvider {
+ vk, facebook
+}
diff --git a/src/main/java/org/ayfaar/app/controllers/RecordController.java b/src/main/java/org/ayfaar/app/controllers/RecordController.java
new file mode 100644
index 00000000..9b8966c2
--- /dev/null
+++ b/src/main/java/org/ayfaar/app/controllers/RecordController.java
@@ -0,0 +1,133 @@
+package org.ayfaar.app.controllers;
+
+import lombok.extern.slf4j.Slf4j;
+import org.ayfaar.app.annotations.Moderated;
+import org.ayfaar.app.dao.RecordDao;
+import org.ayfaar.app.event.EventPublisher;
+import org.ayfaar.app.event.RecordRenamedEvent;
+import org.ayfaar.app.model.Record;
+import org.ayfaar.app.model.User;
+import org.ayfaar.app.services.moderation.Action;
+import org.ayfaar.app.services.moderation.ModerationService;
+import org.ayfaar.app.services.moderation.UserRole;
+import org.ayfaar.app.services.topics.TopicProvider;
+import org.ayfaar.app.services.topics.TopicService;
+import org.ayfaar.app.sync.RecordSynchronizer;
+import org.ayfaar.app.utils.Transliterator;
+import org.springframework.data.domain.Pageable;
+import org.springframework.data.web.PageableDefault;
+import org.springframework.http.HttpHeaders;
+import org.springframework.security.core.annotation.AuthenticationPrincipal;
+import org.springframework.web.bind.annotation.*;
+
+import javax.inject.Inject;
+import javax.servlet.http.HttpServletResponse;
+import java.io.IOException;
+import java.io.InputStream;
+import java.io.OutputStream;
+import java.net.URL;
+import java.text.SimpleDateFormat;
+import java.util.HashMap;
+import java.util.List;
+import java.util.Map;
+import java.util.stream.Collectors;
+
+import static org.springframework.data.domain.Sort.Direction.DESC;
+
+@RestController
+@RequestMapping("api/record")
+@Slf4j
+public class RecordController {
+
+ private final TopicService topicService;
+ private final ModerationService moderationService;
+ private final RecordSynchronizer recordSynchronizer;
+ private EventPublisher publisher;
+ private final RecordDao recordDao;
+
+ @Inject
+ public RecordController(RecordDao recordDao, TopicService topicService, ModerationService moderationService, RecordSynchronizer recordSynchronizer, EventPublisher publisher) {
+ this.recordDao = recordDao;
+ this.topicService = topicService;
+ this.moderationService = moderationService;
+ this.recordSynchronizer = recordSynchronizer;
+ this.publisher = publisher;
+ }
+
+ @RequestMapping()
+ public List