.
3 | *
4 | * Permission is hereby granted, free of charge, to any person obtaining a copy
5 | * of this software and associated documentation files (the "Software"), to deal
6 | * in the Software without restriction, including without limitation the rights
7 | * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
8 | * copies of the Software, and to permit persons to whom the Software is
9 | * furnished to do so, subject to the following conditions:
10 | *
11 | * The above copyright notice and this permission notice shall be included in all
12 | * copies or substantial portions of the Software.
13 | *
14 | * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
15 | * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
16 | * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
17 | * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
18 | * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
19 | * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
20 | * SOFTWARE.
21 | */
22 | package com.huntly.jpa.repository;
23 |
24 | import com.huntly.jpa.model.PersonIdCard;
25 | import org.springframework.data.jpa.repository.JpaRepository;
26 | import org.springframework.data.jpa.repository.JpaSpecificationExecutor;
27 |
28 | public interface PersonIdCardRepository extends JpaRepository, JpaSpecificationExecutor {
29 | }
30 |
--------------------------------------------------------------------------------
/app/server/huntly-jpa/src/test/java/com/huntly/jpa/repository/PersonRepository.java:
--------------------------------------------------------------------------------
1 | /**
2 | * Copyright © 2019, Wen Hao .
3 | *
4 | * Permission is hereby granted, free of charge, to any person obtaining a copy
5 | * of this software and associated documentation files (the "Software"), to deal
6 | * in the Software without restriction, including without limitation the rights
7 | * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
8 | * copies of the Software, and to permit persons to whom the Software is
9 | * furnished to do so, subject to the following conditions:
10 | *
11 | * The above copyright notice and this permission notice shall be included in all
12 | * copies or substantial portions of the Software.
13 | *
14 | * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
15 | * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
16 | * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
17 | * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
18 | * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
19 | * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
20 | * SOFTWARE.
21 | */
22 | package com.huntly.jpa.repository;
23 |
24 | import com.huntly.jpa.model.Person;
25 | import org.springframework.data.jpa.repository.JpaRepository;
26 | import org.springframework.data.jpa.repository.JpaSpecificationExecutor;
27 |
28 | public interface PersonRepository extends JpaRepository, JpaSpecificationExecutor, JpaSpecificationExecutorWithProjection {
29 | }
30 |
--------------------------------------------------------------------------------
/app/server/huntly-jpa/src/test/java/com/huntly/jpa/repository/PhoneRepository.java:
--------------------------------------------------------------------------------
1 | /**
2 | * Copyright © 2019, Wen Hao .
3 | *
4 | * Permission is hereby granted, free of charge, to any person obtaining a copy
5 | * of this software and associated documentation files (the "Software"), to deal
6 | * in the Software without restriction, including without limitation the rights
7 | * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
8 | * copies of the Software, and to permit persons to whom the Software is
9 | * furnished to do so, subject to the following conditions:
10 | *
11 | * The above copyright notice and this permission notice shall be included in all
12 | * copies or substantial portions of the Software.
13 | *
14 | * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
15 | * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
16 | * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
17 | * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
18 | * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
19 | * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
20 | * SOFTWARE.
21 | */
22 | package com.huntly.jpa.repository;
23 |
24 | import com.huntly.jpa.model.Phone;
25 | import org.springframework.data.jpa.repository.JpaRepository;
26 | import org.springframework.data.jpa.repository.JpaSpecificationExecutor;
27 |
28 | public interface PhoneRepository extends JpaRepository, JpaSpecificationExecutor {
29 | }
30 |
--------------------------------------------------------------------------------
/app/server/huntly-jpa/src/test/resources/application.properties:
--------------------------------------------------------------------------------
1 | #
2 | # Copyright © 2019, Wen Hao .
3 | #
4 | # Permission is hereby granted, free of charge, to any person obtaining a copy
5 | # of this software and associated documentation files (the "Software"), to deal
6 | # in the Software without restriction, including without limitation the rights
7 | # to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
8 | # copies of the Software, and to permit persons to whom the Software is
9 | # furnished to do so, subject to the following conditions:
10 | #
11 | # The above copyright notice and this permission notice shall be included in all
12 | # copies or substantial portions of the Software.
13 | #
14 | # THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
15 | # IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
16 | # FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
17 | # AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
18 | # LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
19 | # OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
20 | # SOFTWARE.
21 | #
22 |
23 | spring.datasource.url=jdbc:h2:mem:jpa-spec;DB_CLOSE_DELAY=-1;DB_CLOSE_ON_EXIT=FALSE
24 | spring.datasource.driverClassName=org.h2.Driver
25 | spring.datasource.username=sa
26 | spring.datasource.password=
27 | spring.jpa.database-platform=org.hibernate.dialect.H2Dialect
28 |
29 | spring.jpa.generate-ddl = true
30 | spring.h2.console.enabled = true
31 | spring.h2.console.settings.web-allow-others = true
32 | spring.jpa.hibernate.ddl-auto = none
33 | spring.jpa.show-sql = true
34 | spring.jpa.properties.hibernate.format_sql = true
--------------------------------------------------------------------------------
/app/server/huntly-server/src/main/java/com/huntly/server/HuntlyServerApplication.java:
--------------------------------------------------------------------------------
1 | package com.huntly.server;
2 |
3 | import org.springframework.boot.SpringApplication;
4 | import org.springframework.boot.autoconfigure.SpringBootApplication;
5 | import org.springframework.scheduling.annotation.EnableAsync;
6 | import org.springframework.scheduling.annotation.EnableScheduling;
7 |
8 | /**
9 | * @author lcomplete
10 | */
11 | @SpringBootApplication
12 | @EnableScheduling
13 | @EnableAsync
14 | public class HuntlyServerApplication {
15 | public static void main(String[] args){
16 | SpringApplication.run(HuntlyServerApplication.class,args);
17 | }
18 | }
19 |
--------------------------------------------------------------------------------
/app/server/huntly-server/src/main/java/com/huntly/server/cache/CacheService.java:
--------------------------------------------------------------------------------
1 | package com.huntly.server.cache;
2 |
3 | import com.huntly.server.domain.entity.Connector;
4 | import com.huntly.server.domain.entity.Source;
5 | import com.huntly.server.repository.ConnectorRepository;
6 | import com.huntly.server.repository.SourceRepository;
7 | import org.springframework.stereotype.Service;
8 |
9 | import java.util.Optional;
10 |
11 | /**
12 | * @author lcomplete
13 | */
14 | @Service
15 | public class CacheService {
16 |
17 | private final ConnectorRepository connectorRepository;
18 |
19 | private final SourceRepository sourceRepository;
20 |
21 | public CacheService(ConnectorRepository connectorRepository, SourceRepository sourceRepository) {
22 | this.connectorRepository = connectorRepository;
23 | this.sourceRepository = sourceRepository;
24 | }
25 |
26 | // todo add cache
27 | public Optional getConnector(Integer id) {
28 | return connectorRepository.findById(id);
29 | }
30 |
31 | // todo add Cacheable
32 | public Optional getSource(Integer id) {
33 | var source = sourceRepository.findById(id);
34 | return source;
35 | }
36 | }
37 |
--------------------------------------------------------------------------------
/app/server/huntly-server/src/main/java/com/huntly/server/config/DocketConfig.java:
--------------------------------------------------------------------------------
1 | package com.huntly.server.config;
2 |
3 | import org.springframework.context.annotation.Bean;
4 | import org.springframework.context.annotation.Configuration;
5 | import springfox.documentation.builders.ApiInfoBuilder;
6 | import springfox.documentation.service.ApiInfo;
7 | import springfox.documentation.spi.DocumentationType;
8 | import springfox.documentation.spring.web.plugins.Docket;
9 |
10 | @Configuration
11 | public class DocketConfig {
12 | @Bean
13 | public Docket docket() {
14 | Docket docket = new Docket(DocumentationType.OAS_30)
15 | .forCodeGeneration(true)
16 | .apiInfo(apiInfo());
17 | return docket;
18 | }
19 |
20 | private ApiInfo apiInfo(){
21 | return new ApiInfoBuilder()
22 | .title("huntly api doc")
23 | .version("3.0")
24 | .description("huntly api doc for code generation")
25 | .build();
26 | }
27 | }
28 |
--------------------------------------------------------------------------------
/app/server/huntly-server/src/main/java/com/huntly/server/config/HuntlyProperties.java:
--------------------------------------------------------------------------------
1 | package com.huntly.server.config;
2 |
3 | import lombok.Data;
4 | import org.springframework.boot.context.properties.ConfigurationProperties;
5 | import org.springframework.stereotype.Component;
6 |
7 | /**
8 | * @author lcomplete
9 | */
10 | @Data
11 | @ConfigurationProperties(prefix = "huntly")
12 | @Component
13 | public class HuntlyProperties {
14 | private String jwtSecret;
15 |
16 | private int jwtExpirationDays;
17 |
18 | private boolean enableFetchThreadPool = true;
19 |
20 | private Integer connectorFetchCorePoolSize;
21 |
22 | private Integer connectorFetchMaxPoolSize;
23 |
24 | private Integer defaultFeedFetchIntervalSeconds = 600;
25 |
26 | private String luceneDir;
27 |
28 | private String dataDir;
29 | }
30 |
--------------------------------------------------------------------------------
/app/server/huntly-server/src/main/java/com/huntly/server/config/ServiceExecutorConfig.java:
--------------------------------------------------------------------------------
1 | package com.huntly.server.config;
2 |
3 | import org.springframework.context.annotation.Configuration;
4 | import org.springframework.scheduling.annotation.AsyncConfigurer;
5 | import org.springframework.scheduling.concurrent.ThreadPoolTaskExecutor;
6 |
7 | import java.util.concurrent.Executor;
8 |
9 | /**
10 | * @author lcomplete
11 | */
12 | @Configuration
13 | public class ServiceExecutorConfig implements AsyncConfigurer {
14 |
15 | @Override
16 | public Executor getAsyncExecutor() {
17 | ThreadPoolTaskExecutor taskExecutor = new ThreadPoolTaskExecutor();
18 | taskExecutor.setCorePoolSize(2);
19 | taskExecutor.setMaxPoolSize(10);
20 | taskExecutor.setQueueCapacity(10000);
21 | taskExecutor.initialize();
22 | return taskExecutor;
23 | }
24 | }
25 |
--------------------------------------------------------------------------------
/app/server/huntly-server/src/main/java/com/huntly/server/config/WebConfig.java:
--------------------------------------------------------------------------------
1 | package com.huntly.server.config;
2 |
3 | import com.huntly.jpa.repository.support.CustomJpaRepository;
4 | import org.springframework.context.annotation.Configuration;
5 | import org.springframework.data.jpa.repository.config.EnableJpaRepositories;
6 |
7 | /**
8 | * @author lcomplete
9 | */
10 | @Configuration
11 | @EnableJpaRepositories(repositoryBaseClass = CustomJpaRepository.class, basePackages = {"com.huntly.server.repository"})
12 | public class WebConfig {
13 |
14 | }
15 |
--------------------------------------------------------------------------------
/app/server/huntly-server/src/main/java/com/huntly/server/connector/ConnectorProperties.java:
--------------------------------------------------------------------------------
1 | package com.huntly.server.connector;
2 |
3 | import com.huntly.server.domain.model.ProxySetting;
4 | import lombok.Getter;
5 | import lombok.Setter;
6 |
7 | import java.time.Instant;
8 |
9 | /**
10 | * @author lcomplete
11 | */
12 | @Getter
13 | @Setter
14 | public class ConnectorProperties {
15 | private Instant lastFetchAt;
16 |
17 | private String subscribeUrl;
18 |
19 | private String apiToken;
20 |
21 | private Boolean crawlFullContent;
22 |
23 | private ProxySetting proxySetting;
24 | }
25 |
--------------------------------------------------------------------------------
/app/server/huntly-server/src/main/java/com/huntly/server/connector/ConnectorType.java:
--------------------------------------------------------------------------------
1 | package com.huntly.server.connector;
2 |
3 | import com.huntly.common.enums.BaseEnum;
4 |
5 | /**
6 | * @author lcomplete
7 | */
8 |
9 | public enum ConnectorType implements BaseEnum {
10 | RSS(1),
11 | GITHUB(2);
12 |
13 | private final Integer code;
14 |
15 | public Integer getCode() {
16 | return code;
17 | }
18 |
19 | @Override
20 | public String getDesc() {
21 | return null;
22 | }
23 |
24 | ConnectorType(Integer code) {
25 | this.code = code;
26 | }
27 | }
28 |
--------------------------------------------------------------------------------
/app/server/huntly-server/src/main/java/com/huntly/server/connector/InfoConnector.java:
--------------------------------------------------------------------------------
1 | package com.huntly.server.connector;
2 |
3 | import com.huntly.interfaces.external.model.CapturePage;
4 | import com.huntly.server.util.HttpUtils;
5 | import org.apache.commons.lang3.StringUtils;
6 |
7 | import java.net.InetSocketAddress;
8 | import java.net.ProxySelector;
9 | import java.net.http.HttpClient;
10 | import java.time.Duration;
11 | import java.util.List;
12 |
13 | /**
14 | * @author lcomplete
15 | */
16 | public abstract class InfoConnector {
17 |
18 | protected HttpClient buildHttpClient(ConnectorProperties properties) {
19 | return HttpUtils.buildHttpClient(properties.getProxySetting());
20 | }
21 |
22 | public abstract List fetchNewestPages();
23 |
24 | public abstract List fetchAllPages();
25 |
26 | public abstract CapturePage fetchPageContent(CapturePage capturePage);
27 | }
28 |
--------------------------------------------------------------------------------
/app/server/huntly-server/src/main/java/com/huntly/server/connector/InfoConnectorFactory.java:
--------------------------------------------------------------------------------
1 | package com.huntly.server.connector;
2 |
3 | import com.huntly.common.enums.BaseEnum;
4 | import com.huntly.server.connector.github.GithubConnector;
5 | import com.huntly.server.connector.rss.RSSConnector;
6 | import lombok.experimental.UtilityClass;
7 | import org.apache.commons.lang3.NotImplementedException;
8 | import org.apache.commons.lang3.StringUtils;
9 |
10 | /**
11 | * @author lcomplete
12 | */
13 | @UtilityClass
14 | public class InfoConnectorFactory {
15 | public static InfoConnector createInfoConnector(Integer connectorType, ConnectorProperties connectorProperties) {
16 | ConnectorType type = BaseEnum.valueOf(ConnectorType.class, connectorType);
17 | if (type == null) {
18 | return null;
19 | }
20 | if (ConnectorType.RSS.equals(type)) {
21 | return new RSSConnector(connectorProperties);
22 | }
23 | if (ConnectorType.GITHUB.equals(type) && StringUtils.isNotBlank(connectorProperties.getApiToken())) {
24 | return new GithubConnector(connectorProperties);
25 | }
26 | throw new NotImplementedException("connector type not implemented");
27 | }
28 | }
29 |
--------------------------------------------------------------------------------
/app/server/huntly-server/src/main/java/com/huntly/server/controller/ConnectorController.java:
--------------------------------------------------------------------------------
1 | package com.huntly.server.controller;
2 |
3 | import com.huntly.interfaces.external.dto.FolderConnectorView;
4 | import com.huntly.server.domain.entity.Connector;
5 | import com.huntly.server.service.ConnectorService;
6 | import org.springframework.web.bind.annotation.*;
7 |
8 | import javax.validation.Valid;
9 | import javax.validation.constraints.NotNull;
10 |
11 | /**
12 | * @author lcomplete
13 | */
14 | @RestController
15 | @RequestMapping("/api/connector")
16 | public class ConnectorController {
17 |
18 | private final ConnectorService connectorService;
19 |
20 | public ConnectorController(ConnectorService connectorService) {
21 | this.connectorService = connectorService;
22 | }
23 |
24 | @GetMapping("folder-connectors")
25 | public FolderConnectorView getFolderConnectorView() {
26 | return connectorService.getFolderConnectorView(true);
27 | }
28 |
29 | @GetMapping("/{id}")
30 | public Connector getConnectorById(@Valid @NotNull @PathVariable("id") Integer id) {
31 | return connectorService.findById(id);
32 | }
33 |
34 | }
35 |
--------------------------------------------------------------------------------
/app/server/huntly-server/src/main/java/com/huntly/server/controller/FolderController.java:
--------------------------------------------------------------------------------
1 | package com.huntly.server.controller;
2 |
3 | import com.huntly.server.domain.entity.Folder;
4 | import com.huntly.server.service.FolderService;
5 | import org.springframework.validation.annotation.Validated;
6 | import org.springframework.web.bind.annotation.*;
7 |
8 | import javax.validation.Valid;
9 | import javax.validation.constraints.NotNull;
10 |
11 | /**
12 | * @author lcomplete
13 | */
14 | @Validated
15 | @RestController
16 | @RequestMapping("/api/folder")
17 | public class FolderController {
18 |
19 | private final FolderService folderService;
20 |
21 | public FolderController(FolderService folderService) {
22 | this.folderService = folderService;
23 | }
24 |
25 | @GetMapping("/{id}")
26 | public Folder getFolderById(@Valid @NotNull @PathVariable("id") Integer id) {
27 | return folderService.findById(id);
28 | }
29 |
30 | }
31 |
--------------------------------------------------------------------------------
/app/server/huntly-server/src/main/java/com/huntly/server/controller/HealthController.java:
--------------------------------------------------------------------------------
1 | package com.huntly.server.controller;
2 |
3 | import org.springframework.web.bind.annotation.GetMapping;
4 | import org.springframework.web.bind.annotation.RequestMapping;
5 | import org.springframework.web.bind.annotation.RestController;
6 |
7 | /**
8 | * @author lcomplete
9 | */
10 | @RestController
11 | @RequestMapping("/api/health")
12 | public class HealthController {
13 | @GetMapping
14 | public String health() {
15 | return "OK";
16 | }
17 | }
18 |
--------------------------------------------------------------------------------
/app/server/huntly-server/src/main/java/com/huntly/server/controller/ReactAppController.java:
--------------------------------------------------------------------------------
1 | package com.huntly.server.controller;
2 |
3 | import org.springframework.stereotype.Controller;
4 | import org.springframework.web.bind.annotation.RequestMapping;
5 | import springfox.documentation.annotations.ApiIgnore;
6 |
7 | /**
8 | * @author lcomplete
9 | */
10 | @Controller
11 | @ApiIgnore
12 | public class ReactAppController {
13 | @RequestMapping(value = {"/", "/{x:[\\w\\-]+}", "/{x:^(?!api$).*$}/**/{y:[\\w\\-]+}"})
14 | public String getIndex() {
15 | return "/index.html";
16 | }
17 | }
18 |
--------------------------------------------------------------------------------
/app/server/huntly-server/src/main/java/com/huntly/server/controller/SearchController.java:
--------------------------------------------------------------------------------
1 | package com.huntly.server.controller;
2 |
3 | import com.huntly.interfaces.external.dto.PageSearchResult;
4 | import com.huntly.interfaces.external.query.SearchQuery;
5 | import com.huntly.server.domain.entity.SearchHistory;
6 | import com.huntly.server.service.LuceneService;
7 | import com.huntly.server.service.SearchHistoryService;
8 | import com.huntly.server.service.TweetTrackService;
9 | import org.springframework.web.bind.annotation.*;
10 |
11 | import java.util.List;
12 |
13 | /**
14 | * @author lcomplete
15 | */
16 | @RestController
17 | @RequestMapping("/api/search")
18 | public class SearchController {
19 |
20 | private final LuceneService luceneService;
21 |
22 | private final SearchHistoryService searchHistoryService;
23 |
24 | public SearchController(LuceneService luceneService, SearchHistoryService searchHistoryService) {
25 | this.luceneService = luceneService;
26 | this.searchHistoryService = searchHistoryService;
27 | }
28 |
29 | @PostMapping
30 | public PageSearchResult searchPages(@RequestBody SearchQuery searchQuery) {
31 | searchHistoryService.save(searchQuery.getQ(), searchQuery.getQueryOptions());
32 | return luceneService.searchPages(searchQuery);
33 | }
34 |
35 | @GetMapping("/recent")
36 | public List getRecentSearches() {
37 | return searchHistoryService.getRecentSearches(5);
38 | }
39 |
40 | }
41 |
--------------------------------------------------------------------------------
/app/server/huntly-server/src/main/java/com/huntly/server/data/dialect/identity/SQLiteDialectIdentityColumnSupport.java:
--------------------------------------------------------------------------------
1 | package com.huntly.server.data.dialect.identity;
2 |
3 | import org.hibernate.dialect.identity.IdentityColumnSupportImpl;
4 |
5 | public class SQLiteDialectIdentityColumnSupport extends IdentityColumnSupportImpl {
6 | @Override
7 | public boolean supportsIdentityColumns() {
8 | return true;
9 | }
10 |
11 | @Override
12 | public boolean supportsInsertSelectIdentity() {
13 | return true; // https://sqlite.org/lang_returning.html
14 | }
15 |
16 | @Override
17 | public boolean hasDataTypeInIdentityColumn() {
18 | // As specified in NHibernate dialect
19 | // FIXME true
20 | return false;
21 | }
22 |
23 | @Override
24 | public String appendIdentitySelectToInsert(String insertString) {
25 | return insertString + " RETURNING rowid";
26 | }
27 |
28 | @Override
29 | public String getIdentitySelectString(String table, String column, int type) {
30 | return "select last_insert_rowid()";
31 | }
32 |
33 | @Override
34 | public String getIdentityColumnString(int type) {
35 | // return "integer primary key autoincrement";
36 | // FIXME "autoincrement"
37 | return "integer";
38 | }
39 | }
--------------------------------------------------------------------------------
/app/server/huntly-server/src/main/java/com/huntly/server/domain/constant/AppConstants.java:
--------------------------------------------------------------------------------
1 | package com.huntly.server.domain.constant;
2 |
3 | import lombok.experimental.UtilityClass;
4 |
5 | /**
6 | * @author lcomplete
7 | */
8 | @UtilityClass
9 | public class AppConstants {
10 | public static final String DEFAULT_LUCENE_DIR = "lucene";
11 |
12 | public static final String HTTP_FEED_CACHE_DIR = "feed_cache";
13 |
14 | public static final Long HTTP_FEED_CACHE_MAXSIZE = 50L * 1024L * 1024L; // 50 MB
15 |
16 | public static final Integer DEFAULT_COLD_DATA_KEEP_DAYS = 60;
17 |
18 | public static final String AUTH_TOKEN_COOKIE_NAME = "auth_token";
19 |
20 | public static final Integer DEFAULT_CONNECTOR_FETCH_CORE_POOL_SIZE = 3;
21 | public static final Integer DEFAULT_CONNECTOR_FETCH_MAX_POOL_SIZE = 30;
22 |
23 | public static final Integer GITHUB_DEFAULT_FETCH_PAGE_SIZE = 20;
24 | }
25 |
--------------------------------------------------------------------------------
/app/server/huntly-server/src/main/java/com/huntly/server/domain/constant/DocFields.java:
--------------------------------------------------------------------------------
1 | package com.huntly.server.domain.constant;
2 |
3 | import lombok.experimental.UtilityClass;
4 |
5 | /**
6 | * lucene page doc fields
7 | * @author lcomplete
8 | */
9 | @UtilityClass
10 | public class DocFields {
11 | public static final String ID = "id";
12 | public static final String TITLE = "title";
13 | public static final String CONTENT = "content";
14 | public static final String DESCRIPTION = "description";
15 | public static final String SOURCE_ID = "sourceId";
16 | public static final String CONNECTOR_ID = "connectorId";
17 | public static final String CONNECTOR_TYPE = "connectorType";
18 | public static final String FOLDER_ID = "folderId";
19 | public static final String CREATED_AT = "createdAt";
20 | public static final String LAST_READ_AT = "lastReadAt";
21 | public static final String LIBRARY_SAVE_STATUS = "librarySaveStatus";
22 | public static final String STARRED = "starred";
23 | public static final String READ_LATER = "readLater";
24 | @Deprecated
25 | public static final String URL = "url";
26 | public static final String URL_TEXT = "url_text";
27 | public static final String THUMB_URL = "thumbUrl";
28 | public static final String PAGE_JSON_PROPERTIES= "pageJsonProperties";
29 | public static final String CONTENT_TYPE = "contentType";
30 | public static final String AUTHOR = "author_text";
31 | }
32 |
--------------------------------------------------------------------------------
/app/server/huntly-server/src/main/java/com/huntly/server/domain/entity/ConnectorSetting.java:
--------------------------------------------------------------------------------
1 | package com.huntly.server.domain.entity;
2 |
3 | import lombok.Data;
4 |
5 | import javax.persistence.*;
6 | import java.io.Serializable;
7 |
8 | /**
9 | * @author lcomplete
10 | */
11 | @Entity
12 | @Data
13 | @Table(name = "connector_setting")
14 | public class ConnectorSetting implements Serializable {
15 |
16 | private static final long serialVersionUID = 1L;
17 |
18 | @Id
19 | @Column(name = "id")
20 | @GeneratedValue(strategy = GenerationType.IDENTITY)
21 | private Integer id;
22 |
23 | @Column(name = "connector_id")
24 | private Integer connectorId;
25 |
26 | @Column(name = "setting_key")
27 | private String settingKey;
28 |
29 | @Column(name = "setting_value")
30 | private String settingValue;
31 |
32 | }
33 |
--------------------------------------------------------------------------------
/app/server/huntly-server/src/main/java/com/huntly/server/domain/entity/Folder.java:
--------------------------------------------------------------------------------
1 | package com.huntly.server.domain.entity;
2 |
3 | import lombok.Data;
4 |
5 | import javax.persistence.*;
6 | import java.io.Serializable;
7 | import java.time.Instant;
8 | import java.util.ArrayList;
9 | import java.util.List;
10 |
11 | @Data
12 | @Entity
13 | @Table(name = "folder")
14 | public class Folder implements Serializable {
15 |
16 | private static final long serialVersionUID = 1L;
17 |
18 | @Id
19 | @Column(name = "id")
20 | @GeneratedValue(strategy = GenerationType.IDENTITY)
21 | private Integer id;
22 |
23 | @Column(name = "name")
24 | private String name;
25 |
26 | @Column(name = "display_sequence")
27 | private Integer displaySequence;
28 |
29 | @Column(name = "created_at")
30 | private Instant createdAt;
31 |
32 | @Transient
33 | private List connectors = new ArrayList<>();
34 | }
35 |
--------------------------------------------------------------------------------
/app/server/huntly-server/src/main/java/com/huntly/server/domain/entity/GlobalSetting.java:
--------------------------------------------------------------------------------
1 | package com.huntly.server.domain.entity;
2 |
3 | import lombok.Data;
4 | import lombok.Getter;
5 | import lombok.Setter;
6 |
7 | import javax.persistence.*;
8 | import java.io.Serializable;
9 | import java.time.Instant;
10 |
11 | /**
12 | * @author lcomplete
13 | */
14 | @Data
15 | @Entity
16 | @Table(name = "global_setting")
17 | public class GlobalSetting implements Serializable {
18 | private static final long serialVersionUID = 1L;
19 |
20 | @Id
21 | @Column(name = "id")
22 | @GeneratedValue(strategy = GenerationType.IDENTITY)
23 | private Integer id;
24 |
25 | @Column(name = "proxy_host")
26 | private String proxyHost;
27 |
28 | @Column(name = "proxy_port")
29 | private Integer proxyPort;
30 |
31 | @Column(name = "is_enable_proxy")
32 | private Boolean enableProxy;
33 |
34 | @Column(name = "cold_data_keep_days")
35 | private Integer coldDataKeepDays;
36 |
37 | @Column(name = "open_api_key")
38 | private String openApiKey;
39 |
40 | @Column(name = "auto_save_site_blacklists")
41 | private String autoSaveSiteBlacklists;
42 |
43 | @Column(name = "created_at")
44 | private Instant createdAt;
45 |
46 | @Column(name = "updated_at")
47 | private Instant updatedAt;
48 |
49 | @Transient
50 | private Boolean changedOpenApiKey;
51 | }
52 |
--------------------------------------------------------------------------------
/app/server/huntly-server/src/main/java/com/huntly/server/domain/entity/PageArticleContent.java:
--------------------------------------------------------------------------------
1 | package com.huntly.server.domain.entity;
2 |
3 | import lombok.Data;
4 |
5 | import javax.persistence.*;
6 | import java.io.Serializable;
7 | import java.time.Instant;
8 |
9 | /**
10 | * @author lcomplete
11 | */
12 | @Data
13 | @Entity
14 | @Table(name = "page_article_content")
15 | public class PageArticleContent implements Serializable {
16 | private static final long serialVersionUID = 1L;
17 |
18 | @Id
19 | @Column(name = "id")
20 | @GeneratedValue(strategy = GenerationType.IDENTITY)
21 | private Long id;
22 |
23 | @Column(name = "page_id")
24 | private Long pageId;
25 |
26 | @Column(name = "content")
27 | private String content;
28 |
29 | @Column(name = "article_content_category")
30 | private Integer articleContentCategory;
31 |
32 | @Column(name = "updated_at")
33 | private Instant updatedAt;
34 | }
35 |
--------------------------------------------------------------------------------
/app/server/huntly-server/src/main/java/com/huntly/server/domain/entity/PageProperties.java:
--------------------------------------------------------------------------------
1 | package com.huntly.server.domain.entity;
2 |
3 | import lombok.Data;
4 |
5 | import javax.persistence.*;
6 | import java.io.Serializable;
7 |
8 | /**
9 | * @author lcomplete
10 | */
11 | @Data
12 | @Entity
13 | @Table(name="page_properties")
14 | public class PageProperties implements Serializable {
15 |
16 | private static final long serialVersionUID = 1L;
17 |
18 | @Id
19 | @Column(name = "id")
20 | @GeneratedValue(strategy = GenerationType.IDENTITY)
21 | private Long id;
22 |
23 | @Column(name = "page_id")
24 | private Long pageId;
25 |
26 | @Column(name = "property_key")
27 | private String propertyKey;
28 |
29 | @Column(name = "property_value")
30 | private String propertyValue;
31 | }
32 |
--------------------------------------------------------------------------------
/app/server/huntly-server/src/main/java/com/huntly/server/domain/entity/PageRelation.java:
--------------------------------------------------------------------------------
1 | package com.huntly.server.domain.entity;
2 |
3 | import lombok.Data;
4 | import org.hibernate.annotations.DynamicUpdate;
5 |
6 | import javax.persistence.*;
7 |
8 | /**
9 | * @author lcomplete
10 | */
11 | @Data
12 | @Entity
13 | @Table(name = "page_relation")
14 | public class PageRelation {
15 | @Id
16 | @Column(name = "page_id")
17 | private Long pageId;
18 |
19 | @Column(name = "page_unique_id")
20 | private String pageUniqueId;
21 |
22 | @Column(name = "page_self_thread_id")
23 | private String pageSelfThreadId;
24 |
25 | @Column(name = "page_conversation_id")
26 | private String pageConversationId;
27 |
28 | @Column(name = "page_reply_to_id")
29 | private String pageReplyToId;
30 | }
31 |
--------------------------------------------------------------------------------
/app/server/huntly-server/src/main/java/com/huntly/server/domain/entity/SearchHistory.java:
--------------------------------------------------------------------------------
1 | package com.huntly.server.domain.entity;
2 |
3 | import lombok.Data;
4 |
5 | import javax.persistence.*;
6 | import java.io.Serializable;
7 | import java.time.Instant;
8 |
9 | /**
10 | * @author lcomplete
11 | */
12 | @Data
13 | @Entity
14 | @Table(name = "search_history")
15 | public class SearchHistory implements Serializable {
16 |
17 | private static final long serialVersionUID = 1L;
18 |
19 | @Id
20 | @GeneratedValue(strategy = GenerationType.IDENTITY)
21 | @Column(name = "id")
22 | private Long id;
23 |
24 | @Column(name = "query")
25 | private String query;
26 |
27 | @Column(name = "options")
28 | private String options;
29 |
30 | @Column(name = "search_at")
31 | private Instant searchAt;
32 | }
33 |
--------------------------------------------------------------------------------
/app/server/huntly-server/src/main/java/com/huntly/server/domain/entity/Source.java:
--------------------------------------------------------------------------------
1 | package com.huntly.server.domain.entity;
2 |
3 | import lombok.Data;
4 |
5 | import javax.persistence.*;
6 | import java.io.Serializable;
7 |
8 | @Data
9 | @Entity
10 | @Table(name = "source")
11 | public class Source implements Serializable {
12 |
13 | private static final long serialVersionUID = -5460676068284121149L;
14 |
15 | @Id
16 | @Column(name = "id")
17 | @GeneratedValue(strategy = GenerationType.IDENTITY)
18 | private Integer id;
19 |
20 | @Column(name = "site_name")
21 | private String siteName;
22 |
23 | @Column(name = "home_url")
24 | private String homeUrl;
25 |
26 | @Column(name = "subscribe_url")
27 | private String subscribeUrl;
28 |
29 | @Column(name = "domain")
30 | private String domain;
31 |
32 | @Column(name = "favicon_url")
33 | private String faviconUrl;
34 |
35 | }
36 |
--------------------------------------------------------------------------------
/app/server/huntly-server/src/main/java/com/huntly/server/domain/entity/TweetTrack.java:
--------------------------------------------------------------------------------
1 | package com.huntly.server.domain.entity;
2 |
3 | import lombok.Data;
4 |
5 | import javax.persistence.*;
6 | import java.io.Serializable;
7 | import java.time.Instant;
8 |
9 | /**
10 | * @author lcomplete
11 | */
12 | @Data
13 | @Entity
14 | @Table(name = "tweet_track")
15 | public class TweetTrack implements Serializable {
16 |
17 | private static final long serialVersionUID = 1L;
18 |
19 | @Id
20 | @Column(name = "id")
21 | @GeneratedValue(strategy = GenerationType.IDENTITY)
22 | private Long id;
23 |
24 | @Column(name = "tweet_id")
25 | private String tweetId;
26 |
27 | @Column(name = "read_at")
28 | private Instant readAt;
29 |
30 | @Column(name = "is_set_read_at")
31 | private Boolean setReadAt;
32 | }
33 |
--------------------------------------------------------------------------------
/app/server/huntly-server/src/main/java/com/huntly/server/domain/entity/TwitterUserSetting.java:
--------------------------------------------------------------------------------
1 | package com.huntly.server.domain.entity;
2 |
3 | import lombok.Data;
4 |
5 | import javax.persistence.*;
6 | import java.io.Serializable;
7 | import java.time.Instant;
8 |
9 | /**
10 | * @author lcomplete
11 | */
12 | @Data
13 | @Entity
14 | @Table(name = "twitter_user_setting")
15 | public class TwitterUserSetting implements Serializable {
16 | private static final long serialVersionUID = 1L;
17 |
18 | @Id
19 | @Column(name = "id")
20 | @GeneratedValue(strategy = GenerationType.IDENTITY)
21 | private Integer id;
22 |
23 | @Column(name = "name")
24 | private String name;
25 |
26 | @Column(name = "screen_name")
27 | private String screenName;
28 |
29 | @Column(name = "bookmark_to_library_type")
30 | private Integer bookmarkToLibraryType;
31 |
32 | @Column(name = "like_to_library_type")
33 | private Integer likeToLibraryType;
34 |
35 | @Column(name = "retweet_to_library_type")
36 | private Integer tweetToLibraryType;
37 |
38 | @Column(name = "is_myself")
39 | private Boolean myself;
40 |
41 | @Column(name = "created_at")
42 | private Instant createdAt;
43 |
44 | @Column(name = "updated_at")
45 | private Instant updatedAt;
46 | }
47 |
--------------------------------------------------------------------------------
/app/server/huntly-server/src/main/java/com/huntly/server/domain/entity/User.java:
--------------------------------------------------------------------------------
1 | package com.huntly.server.domain.entity;
2 |
3 | import lombok.Data;
4 |
5 | import javax.persistence.*;
6 | import java.io.Serializable;
7 | import java.time.Instant;
8 |
9 | @Data
10 | @Entity
11 | @Table(name="users")
12 | public class User implements Serializable {
13 |
14 | private static final long serialVersionUID = 1L;
15 |
16 | @Id
17 | @Column(name = "id")
18 | @GeneratedValue(strategy = GenerationType.IDENTITY)
19 | private Long id;
20 |
21 | @Column(name = "username")
22 | private String username;
23 |
24 | @Column(name = "password")
25 | private String password;
26 |
27 | @Column(name = "created_at")
28 | private Instant createdAt;
29 |
30 | @Column(name = "updated_at")
31 | private Instant updatedAt;
32 |
33 | @Column(name = "last_login_at")
34 | private Instant lastLoginAt;
35 | }
36 |
--------------------------------------------------------------------------------
/app/server/huntly-server/src/main/java/com/huntly/server/domain/enums/ArticleContentCategory.java:
--------------------------------------------------------------------------------
1 | package com.huntly.server.domain.enums;
2 |
3 | /**
4 | * @author lcomplete
5 | */
6 | public enum ArticleContentCategory {
7 | RAW_CONTENT(0),
8 | SUMMARY(1);
9 |
10 | private final int code;
11 |
12 | ArticleContentCategory(int code) {
13 | this.code = code;
14 | }
15 |
16 | public int getCode(){
17 | return code;
18 | }
19 | }
20 |
--------------------------------------------------------------------------------
/app/server/huntly-server/src/main/java/com/huntly/server/domain/exceptions/ConnectorFetchException.java:
--------------------------------------------------------------------------------
1 | package com.huntly.server.domain.exceptions;
2 |
3 | import com.huntly.common.exceptions.BaseException;
4 |
5 | /**
6 | * @author lcomplete
7 | */
8 | public class ConnectorFetchException extends BaseException {
9 |
10 | public ConnectorFetchException() {
11 | super();
12 | }
13 |
14 | public ConnectorFetchException(String msg) {
15 | super(msg);
16 | }
17 |
18 | public ConnectorFetchException(Throwable throwable) {
19 | super(throwable);
20 | }
21 |
22 | public ConnectorFetchException(String msg, Throwable throwable) {
23 | super(msg, throwable);
24 | }
25 |
26 | public ConnectorFetchException(int status, String msg) {
27 | super(status, msg);
28 | }
29 |
30 | public ConnectorFetchException(int status, Throwable throwable) {
31 | super(status, throwable);
32 | }
33 |
34 | public ConnectorFetchException(int status, String msg, Throwable throwable) {
35 | super(status, msg, throwable);
36 | }
37 | }
38 |
--------------------------------------------------------------------------------
/app/server/huntly-server/src/main/java/com/huntly/server/domain/mapper/ConnectorItemMapper.java:
--------------------------------------------------------------------------------
1 | package com.huntly.server.domain.mapper;
2 |
3 | import com.huntly.interfaces.external.dto.ConnectorItem;
4 | import com.huntly.server.domain.entity.Connector;
5 | import org.mapstruct.Mapper;
6 | import org.mapstruct.factory.Mappers;
7 |
8 | @Mapper
9 | public interface ConnectorItemMapper {
10 | ConnectorItemMapper INSTANCE = Mappers.getMapper(ConnectorItemMapper.class);
11 |
12 | ConnectorItem fromConnector(Connector connector);
13 | }
14 |
--------------------------------------------------------------------------------
/app/server/huntly-server/src/main/java/com/huntly/server/domain/model/ProxySetting.java:
--------------------------------------------------------------------------------
1 | package com.huntly.server.domain.model;
2 |
3 | import lombok.Getter;
4 | import lombok.Setter;
5 |
6 | /**
7 | * @author lcomplete
8 | */
9 | @Getter
10 | @Setter
11 | public class ProxySetting {
12 | private String host;
13 | private Integer port;
14 | }
15 |
--------------------------------------------------------------------------------
/app/server/huntly-server/src/main/java/com/huntly/server/domain/vo/PageDetail.java:
--------------------------------------------------------------------------------
1 | package com.huntly.server.domain.vo;
2 |
3 | import com.huntly.interfaces.external.dto.ConnectorItem;
4 | import com.huntly.server.domain.entity.Page;
5 | import com.huntly.server.domain.entity.PageArticleContent;
6 | import com.huntly.server.domain.entity.Source;
7 | import lombok.Getter;
8 | import lombok.Setter;
9 |
10 | import java.util.List;
11 |
12 | /**
13 | * @author lcomplete
14 | */
15 | @Getter
16 | @Setter
17 | public class PageDetail {
18 | private Page page;
19 |
20 | private ConnectorItem connector;
21 |
22 | private Source source;
23 |
24 | private List pageContents;
25 | }
26 |
--------------------------------------------------------------------------------
/app/server/huntly-server/src/main/java/com/huntly/server/event/EventPublisher.java:
--------------------------------------------------------------------------------
1 | package com.huntly.server.event;
2 |
3 | import org.springframework.context.ApplicationEventPublisher;
4 | import org.springframework.stereotype.Component;
5 |
6 | /**
7 | * @author lcomplete
8 | */
9 | @Component
10 | public class EventPublisher {
11 | private final ApplicationEventPublisher applicationEventPublisher;
12 |
13 | public EventPublisher(ApplicationEventPublisher eventPublisher) {
14 | this.applicationEventPublisher = eventPublisher;
15 | }
16 |
17 | public void publishInboxChangedEvent(InboxChangedEvent inboxChangedEvent){
18 | applicationEventPublisher.publishEvent(inboxChangedEvent);
19 | }
20 |
21 | public void publishTweetPageCaptureEvent(TweetPageCaptureEvent tweetPageCaptureEvent) {
22 | applicationEventPublisher.publishEvent(tweetPageCaptureEvent);
23 | }
24 | }
25 |
--------------------------------------------------------------------------------
/app/server/huntly-server/src/main/java/com/huntly/server/event/InboxChangedEvent.java:
--------------------------------------------------------------------------------
1 | package com.huntly.server.event;
2 |
3 | import lombok.AllArgsConstructor;
4 | import lombok.Getter;
5 | import lombok.Setter;
6 | import lombok.experimental.Accessors;
7 |
8 | /**
9 | * @author lcomplete
10 | */
11 | @Setter
12 | @Getter
13 | @Accessors(chain = true)
14 | public class InboxChangedEvent {
15 | public InboxChangedEvent(Integer connectorId) {
16 | this.connectorId = connectorId;
17 | }
18 |
19 | private Integer connectorId;
20 |
21 | /**
22 | * if this data is null, will compute inbox count at time
23 | */
24 | private Integer inboxCount;
25 |
26 |
27 | }
28 |
--------------------------------------------------------------------------------
/app/server/huntly-server/src/main/java/com/huntly/server/event/InboxChangedListener.java:
--------------------------------------------------------------------------------
1 | package com.huntly.server.event;
2 |
3 | import com.huntly.server.service.ConnectorService;
4 | import com.huntly.server.service.PageService;
5 | import org.springframework.context.event.EventListener;
6 | import org.springframework.scheduling.annotation.Async;
7 | import org.springframework.stereotype.Component;
8 |
9 | /**
10 | * @author lcomplete
11 | */
12 | @Component
13 | public class InboxChangedListener {
14 |
15 | private final ConnectorService connectorService;
16 |
17 | public InboxChangedListener(ConnectorService connectorService) {
18 | this.connectorService = connectorService;
19 | }
20 |
21 | @EventListener
22 | public void inboxChangedEvent(InboxChangedEvent event) {
23 | if (event.getConnectorId() != null && event.getConnectorId() > 0) {
24 | if (event.getInboxCount() != null) {
25 | connectorService.updateInboxCount(event.getConnectorId(), event.getInboxCount());
26 | } else {
27 | connectorService.updateInboxCount(event.getConnectorId());
28 | }
29 | }
30 | }
31 | }
32 |
--------------------------------------------------------------------------------
/app/server/huntly-server/src/main/java/com/huntly/server/event/TweetPageCaptureEvent.java:
--------------------------------------------------------------------------------
1 | package com.huntly.server.event;
2 |
3 | import com.huntly.server.domain.entity.Page;
4 | import lombok.AllArgsConstructor;
5 | import lombok.Getter;
6 | import lombok.Setter;
7 |
8 | /**
9 | * @author lcomplete
10 | */
11 | @Getter
12 | @Setter
13 | @AllArgsConstructor
14 | public class TweetPageCaptureEvent {
15 | private Page page;
16 |
17 | private String loginScreenName;
18 |
19 | private String browserScreenName;
20 | }
21 |
--------------------------------------------------------------------------------
/app/server/huntly-server/src/main/java/com/huntly/server/event/TweetPageCaptureListener.java:
--------------------------------------------------------------------------------
1 | package com.huntly.server.event;
2 |
3 | import com.huntly.server.service.CapturePageService;
4 | import org.springframework.context.event.EventListener;
5 | import org.springframework.scheduling.annotation.Async;
6 | import org.springframework.stereotype.Component;
7 |
8 | /**
9 | * @author lcomplete
10 | */
11 | @Component
12 | public class TweetPageCaptureListener {
13 | private final CapturePageService capturePageService;
14 |
15 | public TweetPageCaptureListener(CapturePageService capturePageService) {
16 | this.capturePageService = capturePageService;
17 | }
18 |
19 | @EventListener
20 | @Async
21 | public void tweetPageCaptureEvent(TweetPageCaptureEvent event) {
22 | capturePageService.saveTweetPage(event.getPage(), event.getLoginScreenName(), event.getBrowserScreenName());
23 | }
24 | }
25 |
--------------------------------------------------------------------------------
/app/server/huntly-server/src/main/java/com/huntly/server/repository/BaseRepository.java:
--------------------------------------------------------------------------------
1 | package com.huntly.server.repository;
2 |
3 | import com.huntly.jpa.repository.JpaRepositoryWithLimit;
4 | import com.huntly.jpa.repository.JpaSpecificationExecutorWithProjection;
5 | import org.springframework.data.jpa.repository.JpaRepository;
6 | import org.springframework.data.jpa.repository.JpaSpecificationExecutor;
7 | import org.springframework.data.repository.NoRepositoryBean;
8 |
9 | import java.io.Serializable;
10 |
11 | /**
12 | * @author lcomplete
13 | */
14 | @NoRepositoryBean
15 | public interface BaseRepository extends JpaRepository, JpaSpecificationExecutor, JpaSpecificationExecutorWithProjection, JpaRepositoryWithLimit {
16 | }
17 |
--------------------------------------------------------------------------------
/app/server/huntly-server/src/main/java/com/huntly/server/repository/ConnectorRepository.java:
--------------------------------------------------------------------------------
1 | package com.huntly.server.repository;
2 |
3 | import com.huntly.server.domain.entity.Connector;
4 | import org.springframework.data.domain.Sort;
5 | import org.springframework.data.jpa.repository.JpaRepository;
6 | import org.springframework.data.jpa.repository.JpaSpecificationExecutor;
7 |
8 | import java.util.List;
9 | import java.util.Optional;
10 |
11 | /**
12 | * @author lcomplete
13 | */
14 | public interface ConnectorRepository extends JpaRepository, JpaSpecificationExecutor {
15 | List findByEnabledTrue();
16 |
17 | Optional findBySubscribeUrlAndType(String subscribeUrl, Integer type);
18 |
19 | List findByFolderId(Integer folderId);
20 |
21 | List findByFolderIdAndType(Integer folderId,Integer type, Sort ascending);
22 |
23 | List findByType(Integer type);
24 | }
--------------------------------------------------------------------------------
/app/server/huntly-server/src/main/java/com/huntly/server/repository/ConnectorSettingRepository.java:
--------------------------------------------------------------------------------
1 | package com.huntly.server.repository;
2 |
3 | import com.huntly.server.domain.entity.ConnectorSetting;
4 | import org.springframework.data.jpa.repository.JpaRepository;
5 | import org.springframework.data.jpa.repository.JpaSpecificationExecutor;
6 |
7 | import java.util.List;
8 |
9 | public interface ConnectorSettingRepository extends JpaRepository, JpaSpecificationExecutor {
10 | public List findAllByConnectorId(Integer connectorId);
11 | }
--------------------------------------------------------------------------------
/app/server/huntly-server/src/main/java/com/huntly/server/repository/FolderRepository.java:
--------------------------------------------------------------------------------
1 | package com.huntly.server.repository;
2 |
3 | import com.huntly.server.domain.entity.Folder;
4 | import org.springframework.data.jpa.repository.JpaRepository;
5 | import org.springframework.data.jpa.repository.JpaSpecificationExecutor;
6 | import org.springframework.stereotype.Repository;
7 |
8 | import java.util.List;
9 | import java.util.Optional;
10 |
11 | @Repository
12 | public interface FolderRepository extends JpaRepository, JpaSpecificationExecutor {
13 | Optional findByName(String name);
14 | }
--------------------------------------------------------------------------------
/app/server/huntly-server/src/main/java/com/huntly/server/repository/GlobalSettingRepository.java:
--------------------------------------------------------------------------------
1 | package com.huntly.server.repository;
2 |
3 | import com.huntly.server.domain.entity.GlobalSetting;
4 | import org.springframework.data.jpa.repository.JpaRepository;
5 | import org.springframework.data.jpa.repository.JpaSpecificationExecutor;
6 | import org.springframework.stereotype.Repository;
7 |
8 | /**
9 | * @author lcomplete
10 | */
11 | @Repository
12 | public interface GlobalSettingRepository extends JpaRepository, JpaSpecificationExecutor {
13 |
14 | }
15 |
--------------------------------------------------------------------------------
/app/server/huntly-server/src/main/java/com/huntly/server/repository/PageArticleContentRepository.java:
--------------------------------------------------------------------------------
1 | package com.huntly.server.repository;
2 |
3 | import com.huntly.server.domain.entity.PageArticleContent;
4 | import org.springframework.stereotype.Repository;
5 | import org.springframework.transaction.annotation.Transactional;
6 |
7 | import java.util.List;
8 | import java.util.Optional;
9 |
10 | /**
11 | * @author lcomplete
12 | */
13 | @Repository
14 | public interface PageArticleContentRepository extends BaseRepository {
15 | Optional findByPageIdAndArticleContentCategory(Long pageId, Integer articleContentCategory);
16 |
17 | @Transactional
18 | void deleteByPageIdAndArticleContentCategory(Long pageId, Integer articleContentCategory);
19 |
20 | List findAllByPageId(Long pageId);
21 |
22 | @Transactional
23 | void deleteByPageId(Long pageId);
24 | }
25 |
--------------------------------------------------------------------------------
/app/server/huntly-server/src/main/java/com/huntly/server/repository/SearchHistoryRepository.java:
--------------------------------------------------------------------------------
1 | package com.huntly.server.repository;
2 |
3 | import com.huntly.server.domain.entity.SearchHistory;
4 | import org.springframework.data.jpa.repository.JpaRepository;
5 | import org.springframework.data.jpa.repository.JpaSpecificationExecutor;
6 | import org.springframework.stereotype.Repository;
7 |
8 | import java.util.List;
9 |
10 | /**
11 | * @author lcomplete
12 | */
13 | @Repository
14 | public interface SearchHistoryRepository extends JpaRepository, JpaSpecificationExecutor {
15 | List findTop10ByOrderByIdDesc();
16 | }
17 |
--------------------------------------------------------------------------------
/app/server/huntly-server/src/main/java/com/huntly/server/repository/SourceRepository.java:
--------------------------------------------------------------------------------
1 | package com.huntly.server.repository;
2 |
3 | import com.huntly.server.domain.entity.Source;
4 | import org.springframework.data.jpa.repository.JpaRepository;
5 | import org.springframework.data.jpa.repository.JpaSpecificationExecutor;
6 | import org.springframework.stereotype.Repository;
7 |
8 | import java.util.Optional;
9 |
10 | @Repository
11 | public interface SourceRepository extends JpaRepository, JpaSpecificationExecutor {
12 |
13 | Optional findByDomain(String domain);
14 | }
--------------------------------------------------------------------------------
/app/server/huntly-server/src/main/java/com/huntly/server/repository/TweetTrackRepository.java:
--------------------------------------------------------------------------------
1 | package com.huntly.server.repository;
2 |
3 | import com.huntly.jpa.repository.JpaRepositoryWithLimit;
4 | import com.huntly.server.domain.entity.TweetTrack;
5 | import org.springframework.data.jpa.repository.JpaRepository;
6 | import org.springframework.data.jpa.repository.JpaSpecificationExecutor;
7 | import org.springframework.data.jpa.repository.Modifying;
8 | import org.springframework.data.jpa.repository.Query;
9 | import org.springframework.stereotype.Repository;
10 | import org.springframework.transaction.annotation.Transactional;
11 |
12 | import java.time.Instant;
13 | import java.util.Optional;
14 |
15 | /**
16 | * @author lcomplete
17 | */
18 | @Repository
19 | public interface TweetTrackRepository extends JpaRepository, JpaSpecificationExecutor,JpaRepositoryWithLimit {
20 | Optional findByTweetId(String tweetId);
21 |
22 | @Transactional
23 | @Modifying(clearAutomatically = true)
24 | @Query("DELETE FROM TweetTrack t WHERE t.readAt < :createdBefore")
25 | Integer deleteHistoryTrack(Instant createdBefore);
26 | }
27 |
--------------------------------------------------------------------------------
/app/server/huntly-server/src/main/java/com/huntly/server/repository/TwitterUserSettingRepository.java:
--------------------------------------------------------------------------------
1 | package com.huntly.server.repository;
2 |
3 | import com.huntly.server.domain.entity.TwitterUserSetting;
4 | import org.springframework.data.jpa.repository.JpaRepository;
5 | import org.springframework.data.jpa.repository.JpaSpecificationExecutor;
6 | import org.springframework.stereotype.Repository;
7 |
8 | import java.util.Optional;
9 |
10 | @Repository
11 | public interface TwitterUserSettingRepository extends JpaRepository, JpaSpecificationExecutor {
12 | Optional findByScreenName(String screenName);
13 | }
14 |
--------------------------------------------------------------------------------
/app/server/huntly-server/src/main/java/com/huntly/server/repository/UserRepository.java:
--------------------------------------------------------------------------------
1 | package com.huntly.server.repository;
2 |
3 | import com.huntly.server.domain.entity.User;
4 | import org.springframework.data.jpa.repository.JpaRepository;
5 | import org.springframework.data.jpa.repository.JpaSpecificationExecutor;
6 | import org.springframework.stereotype.Repository;
7 |
8 | import java.util.Optional;
9 |
10 | @Repository
11 | public interface UserRepository extends JpaRepository, JpaSpecificationExecutor {
12 | Optional findByUsername(String username);
13 | }
14 |
--------------------------------------------------------------------------------
/app/server/huntly-server/src/main/java/com/huntly/server/repository/custom/PageItemRepository.java:
--------------------------------------------------------------------------------
1 | package com.huntly.server.repository.custom;
2 |
3 | import com.huntly.interfaces.external.dto.PageItem;
4 | import com.huntly.interfaces.external.query.PageListQuery;
5 |
6 | import java.util.List;
7 |
8 | /**
9 | * @author lcomplete
10 | */
11 | public interface PageItemRepository {
12 | List list(PageListQuery listQuery);
13 | }
14 |
--------------------------------------------------------------------------------
/app/server/huntly-server/src/main/java/com/huntly/server/repository/custom/PageItemRepositoryImpl.java:
--------------------------------------------------------------------------------
1 | package com.huntly.server.repository.custom;
2 |
3 | import com.huntly.interfaces.external.dto.PageItem;
4 | import com.huntly.interfaces.external.query.PageListQuery;
5 | import org.springframework.stereotype.Repository;
6 |
7 | import javax.persistence.EntityManager;
8 | import javax.persistence.PersistenceContext;
9 | import java.util.List;
10 |
11 | @Repository
12 | public class PageItemRepositoryImpl implements PageItemRepository {
13 |
14 | @PersistenceContext
15 | private EntityManager entityManager;
16 |
17 | @Override
18 | public List list(PageListQuery listQuery) {
19 | return null;
20 | }
21 | }
22 |
--------------------------------------------------------------------------------
/app/server/huntly-server/src/main/java/com/huntly/server/security/jwt/UnAuthEntryPointJwt.java:
--------------------------------------------------------------------------------
1 | package com.huntly.server.security.jwt;
2 |
3 | import com.fasterxml.jackson.databind.ObjectMapper;
4 | import com.huntly.common.api.model.ErrorDetail;
5 | import com.huntly.common.api.model.ErrorResponse;
6 | import lombok.extern.slf4j.Slf4j;
7 | import org.springframework.http.MediaType;
8 | import org.springframework.security.core.AuthenticationException;
9 | import org.springframework.security.web.AuthenticationEntryPoint;
10 | import org.springframework.stereotype.Component;
11 |
12 | import javax.servlet.ServletException;
13 | import javax.servlet.http.HttpServletRequest;
14 | import javax.servlet.http.HttpServletResponse;
15 | import java.io.IOException;
16 |
17 | /**
18 | * @author lcomplete
19 | */
20 | @Component
21 | @Slf4j
22 | public class UnAuthEntryPointJwt implements AuthenticationEntryPoint {
23 |
24 | @Override
25 | public void commence(HttpServletRequest request, HttpServletResponse response, AuthenticationException authException)
26 | throws IOException, ServletException {
27 | response.setContentType(MediaType.APPLICATION_JSON_VALUE);
28 | response.setStatus(HttpServletResponse.SC_UNAUTHORIZED);
29 |
30 | ErrorDetail errorDetail = new ErrorDetail();
31 | errorDetail.setCode(HttpServletResponse.SC_UNAUTHORIZED);
32 | errorDetail.setMessage(authException.getMessage());
33 | errorDetail.setType("Unauthorized");
34 |
35 | ErrorResponse errorResponse = new ErrorResponse();
36 | errorResponse.setError(errorDetail);
37 | final ObjectMapper mapper = new ObjectMapper();
38 | mapper.writeValue(response.getOutputStream(), errorResponse);
39 | }
40 | }
41 |
--------------------------------------------------------------------------------
/app/server/huntly-server/src/main/java/com/huntly/server/security/services/UserDetailsServiceImpl.java:
--------------------------------------------------------------------------------
1 | package com.huntly.server.security.services;
2 |
3 | import com.huntly.common.exceptions.NoSuchDataException;
4 | import com.huntly.server.domain.entity.User;
5 | import com.huntly.server.repository.UserRepository;
6 | import org.springframework.security.core.userdetails.UserDetails;
7 | import org.springframework.security.core.userdetails.UserDetailsService;
8 | import org.springframework.security.core.userdetails.UsernameNotFoundException;
9 | import org.springframework.stereotype.Service;
10 |
11 | /**
12 | * @author lcomplete
13 | */
14 | @Service
15 | public class UserDetailsServiceImpl implements UserDetailsService {
16 | private final UserRepository userRepository;
17 |
18 | public UserDetailsServiceImpl(UserRepository userRepository) {
19 | this.userRepository = userRepository;
20 | }
21 |
22 | @Override
23 | public UserDetails loadUserByUsername(String username) throws UsernameNotFoundException {
24 | User user = userRepository.findByUsername(username).orElseThrow(() -> new NoSuchDataException("User not found with username: " + username));
25 | return UserDetailsImpl.build(user);
26 | }
27 | }
28 |
--------------------------------------------------------------------------------
/app/server/huntly-server/src/main/java/com/huntly/server/service/BasePageService.java:
--------------------------------------------------------------------------------
1 | package com.huntly.server.service;
2 |
3 | import com.huntly.server.domain.entity.Page;
4 | import com.huntly.server.repository.PageRepository;
5 | import org.springframework.transaction.annotation.Transactional;
6 |
7 | import java.time.Instant;
8 |
9 | /**
10 | * @author lcomplete
11 | */
12 | public abstract class BasePageService {
13 |
14 | protected PageRepository pageRepository;
15 |
16 | LuceneService luceneService;
17 |
18 | protected BasePageService(PageRepository pageRepository, LuceneService luceneService) {
19 | this.pageRepository = pageRepository;
20 | this.luceneService = luceneService;
21 | }
22 |
23 | //@Transactional(rollbackFor = Exception.class)
24 | protected Page save(Page page) {
25 | page.setUpdatedAt(Instant.now());
26 | pageRepository.save(page);
27 | luceneService.indexPage(page);
28 | return page;
29 | }
30 |
31 | protected void deleteById(Long id){
32 | pageRepository.deleteById(id);
33 | luceneService.deletePage(id);
34 | }
35 | }
36 |
--------------------------------------------------------------------------------
/app/server/huntly-server/src/main/java/com/huntly/server/service/ConnectorSettingService.java:
--------------------------------------------------------------------------------
1 | package com.huntly.server.service;
2 |
3 | import com.huntly.server.repository.ConnectorSettingRepository;
4 | import org.springframework.beans.factory.annotation.Autowired;
5 | import org.springframework.stereotype.Service;
6 |
7 | @Service
8 | public class ConnectorSettingService {
9 |
10 | @Autowired
11 | private ConnectorSettingRepository connectorSettingRepository;
12 |
13 | }
14 |
--------------------------------------------------------------------------------
/app/server/huntly-server/src/main/java/com/huntly/server/service/SourceService.java:
--------------------------------------------------------------------------------
1 | package com.huntly.server.service;
2 |
3 | import com.huntly.server.domain.entity.Source;
4 | import com.huntly.server.repository.SourceRepository;
5 | import org.springframework.stereotype.Service;
6 |
7 | import java.util.NoSuchElementException;
8 | import java.util.Optional;
9 |
10 | @Service
11 | public class SourceService {
12 |
13 | private final SourceRepository sourceRepository;
14 |
15 | public SourceService(SourceRepository sourceRepository) {
16 | this.sourceRepository = sourceRepository;
17 | }
18 |
19 | public Optional findById(Integer id) {
20 | return sourceRepository.findById(id);
21 | }
22 |
23 | private Source requireOne(Integer id) {
24 | return sourceRepository.findById(id)
25 | .orElseThrow(() -> new NoSuchElementException("Resource not found: " + id));
26 | }
27 | }
28 |
--------------------------------------------------------------------------------
/app/server/huntly-server/src/main/java/com/huntly/server/task/ConnectorScheduledTask.java:
--------------------------------------------------------------------------------
1 | package com.huntly.server.task;
2 |
3 | import com.huntly.server.service.ConnectorFetchService;
4 | import lombok.extern.slf4j.Slf4j;
5 | import org.springframework.scheduling.annotation.Scheduled;
6 | import org.springframework.stereotype.Component;
7 |
8 | /**
9 | * @author lcomplete
10 | */
11 | @Component
12 | @Slf4j
13 | public class ConnectorScheduledTask {
14 | private final ConnectorFetchService connectorFetchService;
15 |
16 | public ConnectorScheduledTask(ConnectorFetchService connectorFetchService) {
17 | this.connectorFetchService = connectorFetchService;
18 | }
19 |
20 | @Scheduled(initialDelay = 1000 * 10, fixedDelay = 1000 * 60)
21 | public void connectorFetchPages() {
22 | connectorFetchService.fetchAllConnectPages();
23 | }
24 | }
25 |
--------------------------------------------------------------------------------
/app/server/huntly-server/src/main/java/com/huntly/server/task/TweetTrackTask.java:
--------------------------------------------------------------------------------
1 | package com.huntly.server.task;
2 |
3 | import com.huntly.server.service.TweetTrackService;
4 | import lombok.extern.slf4j.Slf4j;
5 | import org.springframework.scheduling.annotation.Scheduled;
6 | import org.springframework.stereotype.Component;
7 |
8 | import java.time.Instant;
9 | import java.time.temporal.ChronoUnit;
10 |
11 | /**
12 | * @author lcomplete
13 | */
14 | @Component
15 | @Slf4j
16 | public class TweetTrackTask {
17 | private final TweetTrackService tweetTrackService;
18 |
19 | public TweetTrackTask(TweetTrackService tweetTrackService) {
20 | this.tweetTrackService = tweetTrackService;
21 | }
22 |
23 | @Scheduled(initialDelay = 1000 * 5, fixedDelay = 1000 * 60 * 5)
24 | public void trackRead() {
25 | tweetTrackService.trackNotSetReads();
26 | }
27 |
28 | @Scheduled(initialDelay = 1000 * 60, fixedDelay = 1000 * 60 * 60)
29 | public void cleanHistoryTrack() {
30 | Instant createdBefore = Instant.now().minus(1, ChronoUnit.DAYS);
31 | Integer effectCount = tweetTrackService.cleanHistoryTrack(createdBefore);
32 | log.info("clean history track, effect count: {}", effectCount);
33 | }
34 | }
35 |
--------------------------------------------------------------------------------
/app/server/huntly-server/src/main/java/com/huntly/server/util/HtmlText.java:
--------------------------------------------------------------------------------
1 | package com.huntly.server.util;
2 |
3 | import lombok.Data;
4 |
5 | /**
6 | * @author lcomplete
7 | */
8 | @Data
9 | public class HtmlText {
10 | private String html;
11 |
12 | private String text;
13 | }
14 |
--------------------------------------------------------------------------------
/app/server/huntly-server/src/main/java/com/huntly/server/util/JSONUtils.java:
--------------------------------------------------------------------------------
1 | package com.huntly.server.util;
2 |
3 | import com.fasterxml.jackson.core.JsonProcessingException;
4 | import com.fasterxml.jackson.databind.ObjectMapper;
5 | import com.fasterxml.jackson.databind.SerializationFeature;
6 | import com.fasterxml.jackson.datatype.jsr310.JavaTimeModule;
7 | import lombok.experimental.UtilityClass;
8 |
9 | /**
10 | * @author lcomplete
11 | */
12 | @UtilityClass
13 | public class JSONUtils {
14 | public static String toJson(Object obj) {
15 | ObjectMapper mapper = new ObjectMapper();
16 | mapper.registerModule(new JavaTimeModule());
17 | mapper.configure(SerializationFeature.WRITE_DATES_AS_TIMESTAMPS, false);
18 | try {
19 | return mapper.writeValueAsString(obj);
20 | } catch (JsonProcessingException e) {
21 | throw new RuntimeException(e);
22 | }
23 | }
24 | }
25 |
--------------------------------------------------------------------------------
/app/server/huntly-server/src/main/java/com/huntly/server/util/PageSizeUtils.java:
--------------------------------------------------------------------------------
1 | package com.huntly.server.util;
2 |
3 | import lombok.experimental.UtilityClass;
4 | import org.apache.commons.lang3.ObjectUtils;
5 |
6 | @UtilityClass
7 | public class PageSizeUtils {
8 |
9 | public static final int DEFAULT_PAGE_SIZE = 30;
10 |
11 | public static final int MAX_PAGE_SIZE = 500;
12 |
13 | public static int getPageSize(int requestPageSize, int defaultPageSize, int maxPageSize) {
14 | if (requestPageSize <= 0) {
15 | requestPageSize = defaultPageSize;
16 | }
17 | return Math.min(requestPageSize, maxPageSize);
18 | }
19 |
20 | public static int getPageSize(int requestPageSize, int defaultPageSize) {
21 | return getPageSize(requestPageSize, defaultPageSize, MAX_PAGE_SIZE);
22 | }
23 |
24 | public static int getPageSize(int requestPageSize) {
25 | return getPageSize(requestPageSize, DEFAULT_PAGE_SIZE, MAX_PAGE_SIZE);
26 | }
27 | }
28 |
--------------------------------------------------------------------------------
/app/server/huntly-server/src/main/resources/META-INF/services/org.hibernate.boot.spi.MetadataBuilderInitializer:
--------------------------------------------------------------------------------
1 | com.huntly.server.data.dialect.SQLiteMetadataBuilderInitializer
--------------------------------------------------------------------------------
/app/server/huntly-server/src/main/resources/application.yml:
--------------------------------------------------------------------------------
1 | app:
2 | id: huntly-server
3 |
4 | spring:
5 | datasource:
6 | driver-class-name: org.sqlite.JDBC
7 | url: jdbc:sqlite:${huntly.dataDir:}db.sqlite?date_class=TEXT
8 | hikari:
9 | maximum-pool-size: 1
10 | jpa:
11 | show-sql: false
12 | hibernate:
13 | ddl-auto: update
14 | database-platform: com.huntly.server.data.dialect.SQLiteDialect
15 | mvc:
16 | pathmatch:
17 | matching-strategy: ant_path_matcher # use this to make springfox work
18 | web:
19 | resources:
20 | cache:
21 | cachecontrol:
22 | max-age: 7d
23 |
24 | huntly:
25 | jwtSecret: MTI2ZTc1NzAtMjJlMy00MmVlLTkwYmQtOTVjNGM4ZTRhN2YzMTI2ZTc1NzAtMjJlMy00MmVlLTkwYmQtOTVjNGM4ZTRhN2Yz
26 | jwtExpirationDays: 365
27 | connectorFetchCorePoolSize: 3
28 |
29 | server:
30 | servlet:
31 | context-path: /
32 |
--------------------------------------------------------------------------------
/app/server/huntly-server/src/test/java/com/huntly/server/connector/twitter/TweetParserTest.java:
--------------------------------------------------------------------------------
1 | package com.huntly.server.connector.twitter;
2 |
3 | import com.huntly.interfaces.external.model.InterceptTweets;
4 | import org.junit.jupiter.api.Test;
5 |
6 | import java.io.IOException;
7 | import java.nio.charset.StandardCharsets;
8 |
9 | import static org.assertj.core.api.Assertions.assertThat;
10 |
11 | class TweetParserTest {
12 |
13 | String getTestJsonData() throws IOException {
14 | ClassLoader classLoader=getClass().getClassLoader();
15 | try (var stream = classLoader.getResourceAsStream("tweet_timeline.json")) {
16 | assert stream != null;
17 | var bytes = stream.readAllBytes();
18 | return new String(bytes, StandardCharsets.UTF_8);
19 | }
20 | }
21 |
22 | @Test
23 | void tweetsToPages() throws IOException {
24 | InterceptTweets interceptTweets= new InterceptTweets();
25 | interceptTweets.setCategory("timeline");
26 | interceptTweets.setJsonData(getTestJsonData());
27 | TweetParser tweetParser = new TweetParser();
28 | var pages= tweetParser.tweetsToPages(interceptTweets);
29 | System.out.println(pages.size());
30 | assertThat(pages).hasSizeGreaterThanOrEqualTo(30);
31 | }
32 | }
--------------------------------------------------------------------------------
/app/server/huntly-server/src/test/java/com/huntly/server/page/ContentCleaner.java:
--------------------------------------------------------------------------------
1 | package com.huntly.server.page;
2 |
3 | import com.huntly.common.util.TextUtils;
4 | import com.huntly.server.util.HtmlText;
5 | import com.huntly.server.util.HtmlUtils;
6 | import org.apache.commons.lang3.StringUtils;
7 |
8 | public class ContentCleaner {
9 | private final String content;
10 |
11 | private final String description;
12 |
13 | private final String baseUri;
14 |
15 | private String cleanHtml;
16 |
17 | private String cleanDescription;
18 |
19 | private String cleanText;
20 |
21 | public ContentCleaner(String content, String description, String baseUri) {
22 | this.content = content;
23 | this.description = description;
24 | this.baseUri = baseUri;
25 |
26 | extract();
27 | }
28 |
29 | private void extract() {
30 | boolean hasContent = StringUtils.isNotBlank(content);
31 | boolean hasDescription = StringUtils.isNotBlank(description);
32 | if (!hasContent && !hasDescription) {
33 | return;
34 | }
35 | HtmlText htmlText = HtmlUtils.clean(hasContent ? content : description, baseUri);
36 | cleanDescription = hasDescription ? HtmlUtils.clean(this.description, baseUri).getText() : TextUtils.trimTruncate(htmlText.getText(), 512);
37 | cleanHtml = htmlText.getHtml();
38 | cleanText = htmlText.getText();
39 | }
40 |
41 | public String getCleanHtml() {
42 | return cleanHtml;
43 | }
44 |
45 | public String getCleanDescription() {
46 | return cleanDescription;
47 | }
48 |
49 | public String getCleanText() {
50 | return cleanText;
51 | }
52 | }
53 |
--------------------------------------------------------------------------------
/app/server/huntly-server/src/test/java/com/huntly/server/page/ContentCleanerTest.java:
--------------------------------------------------------------------------------
1 | package com.huntly.server.page;
2 |
3 | import org.junit.jupiter.api.Test;
4 |
5 | import static org.assertj.core.api.Assertions.assertThat;
6 | import static org.junit.jupiter.api.Assertions.*;
7 |
8 | class ContentCleanerTest {
9 |
10 | @Test
11 | void getCleanHtml_Styled() {
12 | ContentCleaner cleaner = new ContentCleaner("test
","desc","http://codelc.com");
13 | //System.out.println(cleaner.getCleanHtml());
14 | assertThat(cleaner.getCleanHtml()).contains("style");
15 | }
16 |
17 | @Test
18 | void getCleanHtml_SpanStyled() {
19 | ContentCleaner cleaner = new ContentCleaner("test","desc","http://codelc.com");
20 | //System.out.println(cleaner.getCleanHtml());
21 | assertThat(cleaner.getCleanHtml()).contains("span");
22 | assertThat(cleaner.getCleanHtml()).contains("style");
23 | }
24 |
25 | @Test
26 | void getCleanDescription() {
27 | }
28 |
29 | @Test
30 | void getCleanText() {
31 | }
32 | }
--------------------------------------------------------------------------------
/app/tauri/.gitignore:
--------------------------------------------------------------------------------
1 | # Logs
2 | logs
3 | *.log
4 | npm-debug.log*
5 | yarn-debug.log*
6 | yarn-error.log*
7 | pnpm-debug.log*
8 | lerna-debug.log*
9 |
10 | node_modules
11 | dist
12 | dist-ssr
13 | *.local
14 |
15 | # Editor directories and files
16 | .vscode/*
17 | !.vscode/extensions.json
18 | .idea
19 | .DS_Store
20 | *.suo
21 | *.ntvs*
22 | *.njsproj
23 | *.sln
24 | *.sw?
25 |
26 | app.settings.json
27 | **.jar
28 |
29 | /src-tauri/server_bin
--------------------------------------------------------------------------------
/app/tauri/.vscode/extensions.json:
--------------------------------------------------------------------------------
1 | {
2 | "recommendations": ["tauri-apps.tauri-vscode", "rust-lang.rust-analyzer"]
3 | }
4 |
--------------------------------------------------------------------------------
/app/tauri/README.md:
--------------------------------------------------------------------------------
1 | # Tauri + React + Typescript
2 |
3 | This template should help get you started developing with Tauri, React and Typescript in Vite.
4 |
5 | ## Recommended IDE Setup
6 |
7 | - [VS Code](https://code.visualstudio.com/) + [Tauri](https://marketplace.visualstudio.com/items?itemName=tauri-apps.tauri-vscode) + [rust-analyzer](https://marketplace.visualstudio.com/items?itemName=rust-lang.rust-analyzer)
8 |
--------------------------------------------------------------------------------
/app/tauri/index.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 | Huntly
8 |
9 |
10 |
11 |
12 |
13 |
14 |
15 |
--------------------------------------------------------------------------------
/app/tauri/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "tauri",
3 | "private": true,
4 | "version": "0.0.0",
5 | "type": "module",
6 | "scripts": {
7 | "dev": "vite",
8 | "build": "tsc && vite build",
9 | "preview": "vite preview",
10 | "tauri": "tauri"
11 | },
12 | "dependencies": {
13 | "@emotion/react": "^11.10.6",
14 | "@emotion/styled": "^11.10.6",
15 | "@mui/icons-material": "^5.11.11",
16 | "@mui/material": "^5.11.15",
17 | "@tauri-apps/api": "^1.2.0",
18 | "formik": "^2.2.9",
19 | "react": "^18.2.0",
20 | "react-dom": "^18.2.0",
21 | "tauri-plugin-autostart-api": "https://github.com/tauri-apps/tauri-plugin-autostart",
22 | "yup": "^1.0.2"
23 | },
24 | "devDependencies": {
25 | "@tauri-apps/cli": "^1.2.3",
26 | "@types/node": "^18.7.10",
27 | "@types/react": "^18.0.15",
28 | "@types/react-dom": "^18.0.6",
29 | "@vitejs/plugin-react": "^3.0.0",
30 | "autoprefixer": "^10.4.14",
31 | "postcss": "^8.4.21",
32 | "tailwindcss": "^3.3.1",
33 | "typescript": "^4.6.4",
34 | "vite": "^4.0.0"
35 | }
36 | }
37 |
--------------------------------------------------------------------------------
/app/tauri/postcss.config.js:
--------------------------------------------------------------------------------
1 | export default {
2 | plugins: {
3 | tailwindcss: {},
4 | autoprefixer: {},
5 | },
6 | }
7 |
--------------------------------------------------------------------------------
/app/tauri/src-tauri/.gitignore:
--------------------------------------------------------------------------------
1 | # Generated by Cargo
2 | # will have compiled files and executables
3 | /target/
4 |
5 |
--------------------------------------------------------------------------------
/app/tauri/src-tauri/Cargo.toml:
--------------------------------------------------------------------------------
1 | [package]
2 | name = "tauri"
3 | version = "0.0.0"
4 | description = "A Tauri App"
5 | authors = ["you"]
6 | license = ""
7 | repository = ""
8 | edition = "2021"
9 |
10 | # See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
11 |
12 | [build-dependencies]
13 | tauri-build = { version = "1.2.1", features = [] }
14 | embed-resource = "2.1"
15 |
16 | [dependencies]
17 | tauri = { version = "1.2.4", features = ["fs-read-file", "fs-write-file", "shell-open", "system-tray"] }
18 | tauri-plugin-autostart = { git = "https://github.com/tauri-apps/plugins-workspace", branch = "dev" }
19 | serde = { version = "1.0", features = ["derive"] }
20 | reqwest = { version = "0.11.16", features = ["json"] }
21 | serde_json = "1.0"
22 | lazy_static = "1.4"
23 |
24 | [features]
25 | # this feature is used for production builds or when `devPath` points to the filesystem
26 | # DO NOT REMOVE!!
27 | custom-protocol = ["tauri/custom-protocol"]
28 |
--------------------------------------------------------------------------------
/app/tauri/src-tauri/build.rs:
--------------------------------------------------------------------------------
1 | extern crate embed_resource;
2 |
3 | fn main() {
4 | tauri_build::build();
5 | // embed_resource::compile("huntly-manifest.rc", embed_resource::NONE);
6 | }
7 |
--------------------------------------------------------------------------------
/app/tauri/src-tauri/huntly-manifest.rc:
--------------------------------------------------------------------------------
1 | #define RT_MANIFEST 24
2 | 1 RT_MANIFEST "huntly.exe.manifest"
--------------------------------------------------------------------------------
/app/tauri/src-tauri/huntly.exe.manifest:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
9 |
10 |
--------------------------------------------------------------------------------
/app/tauri/src-tauri/icons/favicon-128x128.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/lcomplete/huntly/d47594a2a7655d137304924a4cc10f9a00fed8c1/app/tauri/src-tauri/icons/favicon-128x128.png
--------------------------------------------------------------------------------
/app/tauri/src-tauri/icons/favicon-128x128@2x.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/lcomplete/huntly/d47594a2a7655d137304924a4cc10f9a00fed8c1/app/tauri/src-tauri/icons/favicon-128x128@2x.png
--------------------------------------------------------------------------------
/app/tauri/src-tauri/icons/favicon-16x16.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/lcomplete/huntly/d47594a2a7655d137304924a4cc10f9a00fed8c1/app/tauri/src-tauri/icons/favicon-16x16.png
--------------------------------------------------------------------------------
/app/tauri/src-tauri/icons/favicon-32x32.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/lcomplete/huntly/d47594a2a7655d137304924a4cc10f9a00fed8c1/app/tauri/src-tauri/icons/favicon-32x32.png
--------------------------------------------------------------------------------
/app/tauri/src-tauri/icons/icon.icns:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/lcomplete/huntly/d47594a2a7655d137304924a4cc10f9a00fed8c1/app/tauri/src-tauri/icons/icon.icns
--------------------------------------------------------------------------------
/app/tauri/src-tauri/icons/icon.ico:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/lcomplete/huntly/d47594a2a7655d137304924a4cc10f9a00fed8c1/app/tauri/src-tauri/icons/icon.ico
--------------------------------------------------------------------------------
/app/tauri/src-tauri/tauri.conf.json:
--------------------------------------------------------------------------------
1 | {
2 | "build": {
3 | "beforeDevCommand": "yarn dev",
4 | "beforeBuildCommand": "yarn build",
5 | "devPath": "http://localhost:1420",
6 | "distDir": "../dist",
7 | "withGlobalTauri": false
8 | },
9 | "package": {
10 | "productName": "Huntly",
11 | "version": "0.3.5"
12 | },
13 | "tauri": {
14 | "allowlist": {
15 | "all": false,
16 | "shell": {
17 | "all": false,
18 | "open": true
19 | },
20 | "fs": {
21 | "readFile": true,
22 | "writeFile": true,
23 | "scope": ["$APP/*", "$RESOURCE/*","server_bin/*"]
24 | }
25 | },
26 | "bundle": {
27 | "active": true,
28 | "icon": [
29 | "icons/favicon-16x16.png",
30 | "icons/favicon-32x32.png",
31 | "icons/favicon-128x128.png",
32 | "icons/favicon-128x128@2x.png",
33 | "icons/icon.icns",
34 | "icons/icon.ico"
35 | ],
36 | "identifier": "lcomplete.huntly",
37 | "targets": "all",
38 | "resources": ["./server_bin/*"]
39 | },
40 | "security": {
41 | "csp": ""
42 | },
43 | "updater": {
44 | "active": false
45 | },
46 | "windows": [
47 | {
48 | "fullscreen": false,
49 | "resizable": false,
50 | "title": "Huntly",
51 | "width": 550,
52 | "height": 550
53 | }
54 | ],
55 | "systemTray": {
56 | "iconPath": "icons/icon.ico",
57 | "iconAsTemplate": true,
58 | "menuOnLeftClick": true
59 | }
60 | }
61 | }
62 |
--------------------------------------------------------------------------------
/app/tauri/src/App.css:
--------------------------------------------------------------------------------
1 | @tailwind base;
2 | @tailwind components;
3 | @tailwind utilities;
4 |
5 | html,body,#root{
6 | @apply h-full;
7 | }
--------------------------------------------------------------------------------
/app/tauri/src/main.tsx:
--------------------------------------------------------------------------------
1 | import React from "react";
2 | import ReactDOM from "react-dom/client";
3 | import App from "./App";
4 | import { CssBaseline, StyledEngineProvider } from "@mui/material";
5 |
6 | ReactDOM.createRoot(document.getElementById("root") as HTMLElement).render(
7 |
8 |
9 |
10 |
11 | );
12 |
--------------------------------------------------------------------------------
/app/tauri/src/vite-env.d.ts:
--------------------------------------------------------------------------------
1 | ///
2 |
--------------------------------------------------------------------------------
/app/tauri/tailwind.config.js:
--------------------------------------------------------------------------------
1 | /** @type {import('tailwindcss').Config} */
2 | export default {
3 | content: ["./src/**/*.{js,jsx,ts,tsx}"],
4 | theme: {
5 | extend: {},
6 | },
7 | corePlugins: {
8 | // Remove Tailwind CSS's preflight style so it can use the MUI's preflight instead (CssBaseline).
9 | preflight: false,
10 | },
11 | plugins: [],
12 | }
13 |
14 |
--------------------------------------------------------------------------------
/app/tauri/tsconfig.json:
--------------------------------------------------------------------------------
1 | {
2 | "compilerOptions": {
3 | "target": "ESNext",
4 | "useDefineForClassFields": true,
5 | "lib": ["DOM", "DOM.Iterable", "ESNext"],
6 | "allowJs": false,
7 | "skipLibCheck": true,
8 | "esModuleInterop": false,
9 | "allowSyntheticDefaultImports": true,
10 | "strict": true,
11 | "forceConsistentCasingInFileNames": true,
12 | "module": "ESNext",
13 | "moduleResolution": "Node",
14 | "resolveJsonModule": true,
15 | "isolatedModules": true,
16 | "noEmit": true,
17 | "jsx": "react-jsx"
18 | },
19 | "include": ["src"],
20 | "references": [{ "path": "./tsconfig.node.json" }]
21 | }
22 |
--------------------------------------------------------------------------------
/app/tauri/tsconfig.node.json:
--------------------------------------------------------------------------------
1 | {
2 | "compilerOptions": {
3 | "composite": true,
4 | "module": "ESNext",
5 | "moduleResolution": "Node",
6 | "allowSyntheticDefaultImports": true
7 | },
8 | "include": ["vite.config.ts"]
9 | }
10 |
--------------------------------------------------------------------------------
/app/tauri/vite.config.ts:
--------------------------------------------------------------------------------
1 | import { defineConfig } from "vite";
2 | import react from "@vitejs/plugin-react";
3 |
4 | const mobile =
5 | process.env.TAURI_PLATFORM === "android" ||
6 | process.env.TAURI_PLATFORM === "ios";
7 |
8 | // https://vitejs.dev/config/
9 | export default defineConfig(async () => ({
10 | plugins: [react()],
11 |
12 | // Vite options tailored for Tauri development and only applied in `tauri dev` or `tauri build`
13 | // prevent vite from obscuring rust errors
14 | clearScreen: false,
15 | // tauri expects a fixed port, fail if that port is not available
16 | server: {
17 | port: 1420,
18 | strictPort: true,
19 | },
20 | // to make use of `TAURI_DEBUG` and other env variables
21 | // https://tauri.studio/v1/api/config#buildconfig.beforedevcommand
22 | envPrefix: ["VITE_", "TAURI_"],
23 | build: {
24 | // Tauri supports es2021
25 | target: process.env.TAURI_PLATFORM == "windows" ? "chrome105" : "safari13",
26 | // don't minify for debug builds
27 | minify: !process.env.TAURI_DEBUG ? "esbuild" : false,
28 | // produce sourcemaps for debug builds
29 | sourcemap: !!process.env.TAURI_DEBUG,
30 | },
31 | }));
32 |
--------------------------------------------------------------------------------
/static/architect.puml:
--------------------------------------------------------------------------------
1 | @startuml
2 | allowmixing
3 |
4 | node 浏览器插件
5 |
6 | node 前端网站
7 |
8 | database SQLite [
9 | SQLite
10 | ----
11 | 网页浏览数据
12 | ]
13 | database Lucene [
14 | Lucene
15 | ----
16 | 文本索引
17 | ]
18 |
19 | package 桌面应用 {
20 | node 客户端 [
21 | 客户端
22 | ----
23 | Tauri
24 | ]
25 | node 服务端 [
26 | 服务端
27 | ----
28 | Spring Boot
29 | ]
30 | }
31 |
32 |
33 | 浏览器插件 --> 服务端 : 捕获数据
34 | 前端网站 --> 服务端 : REST API
35 | 客户端 -right-> 服务端 : 启动
36 | 服务端 -down-> SQLite : 存储
37 | 服务端 -down-> Lucene : 搜索
38 |
39 | @enduml
40 |
--------------------------------------------------------------------------------
/static/images/intro1.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/lcomplete/huntly/d47594a2a7655d137304924a4cc10f9a00fed8c1/static/images/intro1.png
--------------------------------------------------------------------------------
/static/images/intro2.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/lcomplete/huntly/d47594a2a7655d137304924a4cc10f9a00fed8c1/static/images/intro2.png
--------------------------------------------------------------------------------
/static/images/jb_beam.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/lcomplete/huntly/d47594a2a7655d137304924a4cc10f9a00fed8c1/static/images/jb_beam.png
--------------------------------------------------------------------------------
/static/images/wechat.JPG:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/lcomplete/huntly/d47594a2a7655d137304924a4cc10f9a00fed8c1/static/images/wechat.JPG
--------------------------------------------------------------------------------
/static/images/zfb.JPG:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/lcomplete/huntly/d47594a2a7655d137304924a4cc10f9a00fed8c1/static/images/zfb.JPG
--------------------------------------------------------------------------------