chore: clean code for next major version

Signed-off-by: Ryan Wang <i@ryanc.cc>
pull/1840/head
Ryan Wang 2022-03-03 11:34:42 +08:00
parent 68d263bef3
commit 641264ba5c
716 changed files with 0 additions and 65249 deletions

3
.gitmodules vendored
View File

@ -1,3 +0,0 @@
[submodule "default-theme"]
path = src/main/resources/templates/themes/anatole
url = https://github.com/halo-dev/halo-theme-anatole

View File

@ -1,148 +0,0 @@
plugins {
id "org.springframework.boot" version "2.5.10"
id "io.spring.dependency-management" version "1.0.11.RELEASE"
id "checkstyle"
id "java"
}
group = "run.halo.app"
description = "Halo, An excellent open source blog publishing application."
sourceCompatibility = JavaVersion.VERSION_11
checkstyle {
toolVersion = "8.39"
showViolations = false
ignoreFailures = false
}
repositories {
maven { url 'https://maven.aliyun.com/repository/public/' }
maven { url 'https://maven.aliyun.com/repository/spring/' }
mavenLocal()
mavenCentral()
}
configurations {
implementation {
exclude module: "spring-boot-starter-tomcat"
exclude module: "slf4j-log4j12"
exclude module: 'junit'
}
compileOnly {
extendsFrom annotationProcessor
}
}
configurations.all {
// Aligning log4j dependency versions to 2.17.1
resolutionStrategy.eachDependency { DependencyResolveDetails details ->
if (details.requested.group == 'org.apache.logging.log4j') {
details.useVersion '2.17.1'
}
}
}
bootJar {
manifest {
attributes "Implementation-Title": "Halo Application",
"Implementation-Version": archiveVersion
}
}
ext {
guavaVersion = '31.0.1-jre'
upyunSdkVersion = '4.2.3'
qiniuSdkVersion = '7.8.0'
aliyunSdkVersion = '3.13.2'
baiduSdkVersion = '0.10.36'
qcloudSdkVersion = '5.6.61'
minioSdkVersion = '7.1.4'
swaggerVersion = "3.0.0"
commonsFileUploadVersion = "1.4"
commonsLangVersion = '3.12.0'
httpclientVersion = '4.5.13'
jgitVersion = "5.9.0.202009080501-r"
flexmarkVersion = "0.62.2"
thumbnailatorVersion = '0.4.14'
image4jVersion = "0.7zensight1"
flywayVersion = "7.15.0"
h2Version = "1.4.199"
levelDbVersion = "0.12"
annotationsVersion = "3.0.1u2"
zxingVersion = '3.4.1'
huaweiObsVersion = '3.21.8.1'
templateInheritanceVersion = "0.4.RELEASE"
jsoupVersion = '1.14.3'
diffUtilsVersion = '4.11'
}
dependencies {
implementation "org.springframework.boot:spring-boot-starter-actuator"
implementation "org.springframework.boot:spring-boot-starter-data-jpa"
implementation "org.springframework.boot:spring-boot-starter-web"
implementation "org.springframework.boot:spring-boot-starter-jetty"
implementation "org.springframework.boot:spring-boot-starter-freemarker"
implementation 'org.springframework.boot:spring-boot-starter-validation'
implementation "com.sun.mail:jakarta.mail"
implementation "com.google.guava:guava:$guavaVersion"
implementation "com.upyun:java-sdk:$upyunSdkVersion"
implementation "com.qiniu:qiniu-java-sdk:$qiniuSdkVersion"
implementation "com.aliyun.oss:aliyun-sdk-oss:$aliyunSdkVersion"
implementation "com.baidubce:bce-java-sdk:$baiduSdkVersion"
implementation "com.qcloud:cos_api:$qcloudSdkVersion"
// TODO Upgrade huaweicloud sdk dependence to fix log4j 0-day vulnerability
implementation("com.huaweicloud:esdk-obs-java:$huaweiObsVersion")
implementation "io.minio:minio:$minioSdkVersion"
implementation "io.springfox:springfox-boot-starter:$swaggerVersion"
implementation "commons-fileupload:commons-fileupload:$commonsFileUploadVersion"
implementation "org.apache.commons:commons-lang3:$commonsLangVersion"
implementation "org.apache.httpcomponents:httpclient:$httpclientVersion"
implementation "com.fasterxml.jackson.dataformat:jackson-dataformat-yaml"
implementation "org.eclipse.jgit:org.eclipse.jgit:$jgitVersion"
implementation "com.google.code.findbugs:annotations:$annotationsVersion"
implementation "com.vladsch.flexmark:flexmark:$flexmarkVersion"
implementation "com.vladsch.flexmark:flexmark-ext-attributes:$flexmarkVersion"
implementation "com.vladsch.flexmark:flexmark-ext-autolink:$flexmarkVersion"
implementation "com.vladsch.flexmark:flexmark-ext-emoji:$flexmarkVersion"
implementation "com.vladsch.flexmark:flexmark-ext-escaped-character:$flexmarkVersion"
implementation "com.vladsch.flexmark:flexmark-ext-gfm-strikethrough:$flexmarkVersion"
implementation "com.vladsch.flexmark:flexmark-ext-gfm-tasklist:$flexmarkVersion"
implementation "com.vladsch.flexmark:flexmark-ext-ins:$flexmarkVersion"
implementation "com.vladsch.flexmark:flexmark-ext-media-tags:$flexmarkVersion"
implementation "com.vladsch.flexmark:flexmark-ext-tables:$flexmarkVersion"
implementation "com.vladsch.flexmark:flexmark-ext-toc:$flexmarkVersion"
implementation "com.vladsch.flexmark:flexmark-ext-superscript:$flexmarkVersion"
implementation "com.vladsch.flexmark:flexmark-ext-yaml-front-matter:$flexmarkVersion"
implementation "com.vladsch.flexmark:flexmark-ext-gitlab:$flexmarkVersion"
implementation "kr.pe.kwonnam.freemarker:freemarker-template-inheritance:$templateInheritanceVersion"
implementation "net.coobird:thumbnailator:$thumbnailatorVersion"
implementation "net.sf.image4j:image4j:$image4jVersion"
implementation "org.flywaydb:flyway-core:$flywayVersion"
implementation "com.google.zxing:core:$zxingVersion"
implementation "io.github.java-diff-utils:java-diff-utils:$diffUtilsVersion"
implementation "org.iq80.leveldb:leveldb:$levelDbVersion"
runtimeOnly "com.h2database:h2:$h2Version"
runtimeOnly "mysql:mysql-connector-java"
compileOnly "org.projectlombok:lombok"
annotationProcessor "org.projectlombok:lombok"
testCompileOnly "org.projectlombok:lombok"
testAnnotationProcessor "org.projectlombok:lombok"
testImplementation("org.springframework.boot:spring-boot-starter-test") {
exclude group: 'org.junit.vintage', module: 'junit-vintage-engine'
}
testImplementation "org.jsoup:jsoup:$jsoupVersion"
developmentOnly "org.springframework.boot:spring-boot-devtools"
}
test {
useJUnitPlatform()
testLogging.showStandardStreams = true
}

View File

@ -1 +0,0 @@
version=1.5.0-SNAPSHOT

Binary file not shown.

View File

@ -1,5 +0,0 @@
distributionBase=GRADLE_USER_HOME
distributionPath=wrapper/dists
distributionUrl=https\://services.gradle.org/distributions/gradle-7.0-bin.zip
zipStoreBase=GRADLE_USER_HOME
zipStorePath=wrapper/dists

185
gradlew vendored
View File

@ -1,185 +0,0 @@
#!/usr/bin/env sh
#
# Copyright 2015 the original author or authors.
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
# https://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.
#
##############################################################################
##
## Gradle start up script for UN*X
##
##############################################################################
# Attempt to set APP_HOME
# Resolve links: $0 may be a link
PRG="$0"
# Need this for relative symlinks.
while [ -h "$PRG" ] ; do
ls=`ls -ld "$PRG"`
link=`expr "$ls" : '.*-> \(.*\)$'`
if expr "$link" : '/.*' > /dev/null; then
PRG="$link"
else
PRG=`dirname "$PRG"`"/$link"
fi
done
SAVED="`pwd`"
cd "`dirname \"$PRG\"`/" >/dev/null
APP_HOME="`pwd -P`"
cd "$SAVED" >/dev/null
APP_NAME="Gradle"
APP_BASE_NAME=`basename "$0"`
# Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script.
DEFAULT_JVM_OPTS='"-Xmx64m" "-Xms64m"'
# Use the maximum available, or set MAX_FD != -1 to use that value.
MAX_FD="maximum"
warn () {
echo "$*"
}
die () {
echo
echo "$*"
echo
exit 1
}
# OS specific support (must be 'true' or 'false').
cygwin=false
msys=false
darwin=false
nonstop=false
case "`uname`" in
CYGWIN* )
cygwin=true
;;
Darwin* )
darwin=true
;;
MINGW* )
msys=true
;;
NONSTOP* )
nonstop=true
;;
esac
CLASSPATH=$APP_HOME/gradle/wrapper/gradle-wrapper.jar
# Determine the Java command to use to start the JVM.
if [ -n "$JAVA_HOME" ] ; then
if [ -x "$JAVA_HOME/jre/sh/java" ] ; then
# IBM's JDK on AIX uses strange locations for the executables
JAVACMD="$JAVA_HOME/jre/sh/java"
else
JAVACMD="$JAVA_HOME/bin/java"
fi
if [ ! -x "$JAVACMD" ] ; then
die "ERROR: JAVA_HOME is set to an invalid directory: $JAVA_HOME
Please set the JAVA_HOME variable in your environment to match the
location of your Java installation."
fi
else
JAVACMD="java"
which java >/dev/null 2>&1 || die "ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH.
Please set the JAVA_HOME variable in your environment to match the
location of your Java installation."
fi
# Increase the maximum file descriptors if we can.
if [ "$cygwin" = "false" -a "$darwin" = "false" -a "$nonstop" = "false" ] ; then
MAX_FD_LIMIT=`ulimit -H -n`
if [ $? -eq 0 ] ; then
if [ "$MAX_FD" = "maximum" -o "$MAX_FD" = "max" ] ; then
MAX_FD="$MAX_FD_LIMIT"
fi
ulimit -n $MAX_FD
if [ $? -ne 0 ] ; then
warn "Could not set maximum file descriptor limit: $MAX_FD"
fi
else
warn "Could not query maximum file descriptor limit: $MAX_FD_LIMIT"
fi
fi
# For Darwin, add options to specify how the application appears in the dock
if $darwin; then
GRADLE_OPTS="$GRADLE_OPTS \"-Xdock:name=$APP_NAME\" \"-Xdock:icon=$APP_HOME/media/gradle.icns\""
fi
# For Cygwin or MSYS, switch paths to Windows format before running java
if [ "$cygwin" = "true" -o "$msys" = "true" ] ; then
APP_HOME=`cygpath --path --mixed "$APP_HOME"`
CLASSPATH=`cygpath --path --mixed "$CLASSPATH"`
JAVACMD=`cygpath --unix "$JAVACMD"`
# We build the pattern for arguments to be converted via cygpath
ROOTDIRSRAW=`find -L / -maxdepth 1 -mindepth 1 -type d 2>/dev/null`
SEP=""
for dir in $ROOTDIRSRAW ; do
ROOTDIRS="$ROOTDIRS$SEP$dir"
SEP="|"
done
OURCYGPATTERN="(^($ROOTDIRS))"
# Add a user-defined pattern to the cygpath arguments
if [ "$GRADLE_CYGPATTERN" != "" ] ; then
OURCYGPATTERN="$OURCYGPATTERN|($GRADLE_CYGPATTERN)"
fi
# Now convert the arguments - kludge to limit ourselves to /bin/sh
i=0
for arg in "$@" ; do
CHECK=`echo "$arg"|egrep -c "$OURCYGPATTERN" -`
CHECK2=`echo "$arg"|egrep -c "^-"` ### Determine if an option
if [ $CHECK -ne 0 ] && [ $CHECK2 -eq 0 ] ; then ### Added a condition
eval `echo args$i`=`cygpath --path --ignore --mixed "$arg"`
else
eval `echo args$i`="\"$arg\""
fi
i=`expr $i + 1`
done
case $i in
0) set -- ;;
1) set -- "$args0" ;;
2) set -- "$args0" "$args1" ;;
3) set -- "$args0" "$args1" "$args2" ;;
4) set -- "$args0" "$args1" "$args2" "$args3" ;;
5) set -- "$args0" "$args1" "$args2" "$args3" "$args4" ;;
6) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" ;;
7) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" ;;
8) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" ;;
9) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" "$args8" ;;
esac
fi
# Escape application args
save () {
for i do printf %s\\n "$i" | sed "s/'/'\\\\''/g;1s/^/'/;\$s/\$/' \\\\/" ; done
echo " "
}
APP_ARGS=`save "$@"`
# Collect all arguments for the java command, following the shell quoting and substitution rules
eval set -- $DEFAULT_JVM_OPTS $JAVA_OPTS $GRADLE_OPTS "\"-Dorg.gradle.appname=$APP_BASE_NAME\"" -classpath "\"$CLASSPATH\"" org.gradle.wrapper.GradleWrapperMain "$APP_ARGS"
exec "$JAVACMD" "$@"

89
gradlew.bat vendored
View File

@ -1,89 +0,0 @@
@rem
@rem Copyright 2015 the original author or authors.
@rem
@rem Licensed under the Apache License, Version 2.0 (the "License");
@rem you may not use this file except in compliance with the License.
@rem You may obtain a copy of the License at
@rem
@rem https://www.apache.org/licenses/LICENSE-2.0
@rem
@rem Unless required by applicable law or agreed to in writing, software
@rem distributed under the License is distributed on an "AS IS" BASIS,
@rem WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
@rem See the License for the specific language governing permissions and
@rem limitations under the License.
@rem
@if "%DEBUG%" == "" @echo off
@rem ##########################################################################
@rem
@rem Gradle startup script for Windows
@rem
@rem ##########################################################################
@rem Set local scope for the variables with windows NT shell
if "%OS%"=="Windows_NT" setlocal
set DIRNAME=%~dp0
if "%DIRNAME%" == "" set DIRNAME=.
set APP_BASE_NAME=%~n0
set APP_HOME=%DIRNAME%
@rem Resolve any "." and ".." in APP_HOME to make it shorter.
for %%i in ("%APP_HOME%") do set APP_HOME=%%~fi
@rem Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script.
set DEFAULT_JVM_OPTS="-Xmx64m" "-Xms64m"
@rem Find java.exe
if defined JAVA_HOME goto findJavaFromJavaHome
set JAVA_EXE=java.exe
%JAVA_EXE% -version >NUL 2>&1
if "%ERRORLEVEL%" == "0" goto execute
echo.
echo ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH.
echo.
echo Please set the JAVA_HOME variable in your environment to match the
echo location of your Java installation.
goto fail
:findJavaFromJavaHome
set JAVA_HOME=%JAVA_HOME:"=%
set JAVA_EXE=%JAVA_HOME%/bin/java.exe
if exist "%JAVA_EXE%" goto execute
echo.
echo ERROR: JAVA_HOME is set to an invalid directory: %JAVA_HOME%
echo.
echo Please set the JAVA_HOME variable in your environment to match the
echo location of your Java installation.
goto fail
:execute
@rem Setup the command line
set CLASSPATH=%APP_HOME%\gradle\wrapper\gradle-wrapper.jar
@rem Execute Gradle
"%JAVA_EXE%" %DEFAULT_JVM_OPTS% %JAVA_OPTS% %GRADLE_OPTS% "-Dorg.gradle.appname=%APP_BASE_NAME%" -classpath "%CLASSPATH%" org.gradle.wrapper.GradleWrapperMain %*
:end
@rem End local scope for the variables with windows NT shell
if "%ERRORLEVEL%"=="0" goto mainEnd
:fail
rem Set variable GRADLE_EXIT_CONSOLE if you need the _script_ return code instead of
rem the _cmd.exe /c_ return code!
if not "" == "%GRADLE_EXIT_CONSOLE%" exit 1
exit /b 1
:mainEnd
if "%OS%"=="Windows_NT" endlocal
:omega

View File

@ -1,10 +0,0 @@
pluginManagement {
repositories {
maven { url 'https://maven.aliyun.com/repository/gradle-plugin' }
maven { url 'https://maven.aliyun.com/repository/spring-plugin' }
maven { url 'https://repo.spring.io/milestone' }
gradlePluginPortal()
}
}
rootProject.name = 'halo'

View File

@ -1,24 +0,0 @@
package run.halo.app;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
/**
* Halo main class.
*
* @author ryanwang
* @date 2017-11-14
*/
@SpringBootApplication
public class Application {
public static void main(String[] args) {
// Customize the spring config location
System.setProperty("spring.config.additional-location",
"optional:file:${user.home}/.halo/,optional:file:${user.home}/halo-dev/");
// Run application
SpringApplication.run(Application.class, args);
}
}

View File

@ -1,26 +0,0 @@
package run.halo.app.annotation;
import java.lang.annotation.Documented;
import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;
import org.springframework.core.annotation.AliasFor;
import run.halo.app.model.enums.Mode;
/**
* 访api
*
* @author guqing
* @date 2020-02-14 13:48
*/
@Target(ElementType.METHOD)
@Retention(RetentionPolicy.RUNTIME)
@Documented
public @interface DisableOnCondition {
@AliasFor("mode")
Mode value() default Mode.DEMO;
@AliasFor("value")
Mode mode() default Mode.DEMO;
}

View File

@ -1,19 +0,0 @@
package run.halo.app.annotation;
import java.lang.annotation.ElementType;
import java.lang.annotation.Inherited;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;
/**
* @author giveup
* @description SensitiveConceal
* @date 8:18 PM 26/5/2020
*/
@Target(ElementType.METHOD)
@Retention(RetentionPolicy.RUNTIME)
@Inherited
public @interface SensitiveConceal {
}

View File

@ -1,45 +0,0 @@
package run.halo.app.aspect;
import lombok.extern.slf4j.Slf4j;
import org.aspectj.lang.ProceedingJoinPoint;
import org.aspectj.lang.annotation.Around;
import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.annotation.Pointcut;
import org.springframework.stereotype.Component;
import run.halo.app.annotation.DisableOnCondition;
import run.halo.app.config.properties.HaloProperties;
import run.halo.app.exception.ForbiddenException;
import run.halo.app.model.enums.Mode;
/**
* DisableApi
*
* @author guqing
* @date 2020-02-14 14:08
*/
@Aspect
@Slf4j
@Component
public class DisableOnConditionAspect {
private final HaloProperties haloProperties;
public DisableOnConditionAspect(HaloProperties haloProperties) {
this.haloProperties = haloProperties;
}
@Pointcut("within(run.halo.app.controller..*)")
public void pointcut() {
}
@Around("pointcut() && @annotation(disableApi)")
public Object around(ProceedingJoinPoint joinPoint,
DisableOnCondition disableApi) throws Throwable {
Mode mode = disableApi.mode();
if (haloProperties.getMode().equals(mode)) {
throw new ForbiddenException("禁止访问");
}
return joinPoint.proceed();
}
}

View File

@ -1,46 +0,0 @@
package run.halo.app.aspect;
import org.aspectj.lang.ProceedingJoinPoint;
import org.aspectj.lang.annotation.Around;
import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.annotation.Pointcut;
import org.springframework.stereotype.Component;
import run.halo.app.model.entity.BaseComment;
import run.halo.app.security.context.SecurityContextHolder;
/**
* @author giveup
* @description SensitiveMaskAspect
* @date 10:22 PM 25/5/2020
*/
@Aspect
@Component
public class SensitiveConcealAspect {
@Pointcut("within(run.halo.app.repository..*) "
+ "&& @annotation(run.halo.app.annotation.SensitiveConceal)")
public void pointCut() {
}
private Object sensitiveMask(Object comment) {
if (comment instanceof BaseComment) {
((BaseComment) comment).setEmail("");
((BaseComment) comment).setIpAddress("");
}
return comment;
}
@Around("pointCut()")
public Object mask(ProceedingJoinPoint joinPoint) throws Throwable {
Object result = joinPoint.proceed();
if (SecurityContextHolder.getContext().isAuthenticated()) {
return result;
}
if (result instanceof Iterable) {
((Iterable<?>) result).forEach(this::sensitiveMask);
}
return sensitiveMask(result);
}
}

View File

@ -1,119 +0,0 @@
package run.halo.app.cache;
import java.util.Date;
import java.util.Optional;
import java.util.concurrent.TimeUnit;
import lombok.extern.slf4j.Slf4j;
import org.springframework.lang.NonNull;
import org.springframework.lang.Nullable;
import org.springframework.util.Assert;
import run.halo.app.config.properties.HaloProperties;
import run.halo.app.utils.DateUtils;
/**
* Abstract cache store.
*
* @author johnniang
* @date 3/28/19
*/
@Slf4j
public abstract class AbstractCacheStore<K, V> implements CacheStore<K, V> {
protected HaloProperties haloProperties;
/**
* Get cache wrapper by key.
*
* @param key key must not be null
* @return an optional cache wrapper
*/
@NonNull
abstract Optional<CacheWrapper<V>> getInternal(@NonNull K key);
/**
* Puts the cache wrapper.
*
* @param key key must not be null
* @param cacheWrapper cache wrapper must not be null
*/
abstract void putInternal(@NonNull K key, @NonNull CacheWrapper<V> cacheWrapper);
/**
* Puts the cache wrapper if the key is absent.
*
* @param key key must not be null
* @param cacheWrapper cache wrapper must not be null
* @return true if the key is absent and the value is set, false if the key is present
* before, or null if any other reason
*/
abstract Boolean putInternalIfAbsent(@NonNull K key, @NonNull CacheWrapper<V> cacheWrapper);
@Override
public Optional<V> get(K key) {
Assert.notNull(key, "Cache key must not be blank");
return getInternal(key).map(cacheWrapper -> {
// Check expiration
if (cacheWrapper.getExpireAt() != null
&& cacheWrapper.getExpireAt().before(run.halo.app.utils.DateUtils.now())) {
// Expired then delete it
log.warn("Cache key: [{}] has been expired", key);
// Delete the key
delete(key);
// Return null
return null;
}
return cacheWrapper.getData();
});
}
@Override
public void put(K key, V value, long timeout, TimeUnit timeUnit) {
putInternal(key, buildCacheWrapper(value, timeout, timeUnit));
}
@Override
public void put(K key, V value) {
putInternal(key, buildCacheWrapper(value, 0, null));
}
@Override
public Boolean putIfAbsent(K key, V value, long timeout, TimeUnit timeUnit) {
return putInternalIfAbsent(key, buildCacheWrapper(value, timeout, timeUnit));
}
/**
* Builds cache wrapper.
*
* @param value cache value must not be null
* @param timeout the key expiry time, if the expiry time is less than 1, the cache won't be
* expired
* @param timeUnit timeout unit must
* @return cache wrapper
*/
@NonNull
private CacheWrapper<V> buildCacheWrapper(@NonNull V value, long timeout,
@Nullable TimeUnit timeUnit) {
Assert.notNull(value, "Cache value must not be null");
Assert.isTrue(timeout >= 0, "Cache expiration timeout must not be less than 1");
Date now = run.halo.app.utils.DateUtils.now();
Date expireAt = null;
if (timeout > 0 && timeUnit != null) {
expireAt = DateUtils.add(now, timeout, timeUnit);
}
// Build cache wrapper
CacheWrapper<V> cacheWrapper = new CacheWrapper<>();
cacheWrapper.setCreateAt(now);
cacheWrapper.setExpireAt(expireAt);
cacheWrapper.setData(value);
return cacheWrapper;
}
}

View File

@ -1,62 +0,0 @@
package run.halo.app.cache;
import com.fasterxml.jackson.core.JsonProcessingException;
import com.fasterxml.jackson.core.type.TypeReference;
import java.io.IOException;
import java.util.Optional;
import java.util.concurrent.TimeUnit;
import lombok.extern.slf4j.Slf4j;
import org.springframework.lang.NonNull;
import org.springframework.util.Assert;
import run.halo.app.exception.ServiceException;
import run.halo.app.utils.JsonUtils;
/**
* String cache store.
*
* @author johnniang
*/
@Slf4j
public abstract class AbstractStringCacheStore extends AbstractCacheStore<String, String> {
protected Optional<CacheWrapper<String>> jsonToCacheWrapper(String json) {
Assert.hasText(json, "json value must not be null");
CacheWrapper<String> cacheWrapper = null;
try {
cacheWrapper = JsonUtils.jsonToObject(json, new TypeReference<>() {});
} catch (Exception e) {
log.debug("Failed to convert json to wrapper value bytes: [{}]", json, e);
}
return Optional.ofNullable(cacheWrapper);
}
public <T> void putAny(String key, T value) {
try {
put(key, JsonUtils.objectToJson(value));
} catch (JsonProcessingException e) {
throw new ServiceException("Failed to convert " + value + " to json", e);
}
}
public <T> void putAny(@NonNull String key, @NonNull T value, long timeout,
@NonNull TimeUnit timeUnit) {
try {
put(key, JsonUtils.objectToJson(value), timeout, timeUnit);
} catch (JsonProcessingException e) {
throw new ServiceException("Failed to convert " + value + " to json", e);
}
}
public <T> Optional<T> getAny(String key, Class<T> type) {
Assert.notNull(type, "Type must not be null");
return get(key).map(value -> {
try {
return JsonUtils.jsonToObject(value, type);
} catch (IOException e) {
log.error("Failed to convert json to type: " + type.getName(), e);
return null;
}
});
}
}

View File

@ -1,70 +0,0 @@
package run.halo.app.cache;
import java.util.LinkedHashMap;
import java.util.Optional;
import java.util.concurrent.TimeUnit;
import org.springframework.lang.NonNull;
/**
* Cache store interface.
*
* @param <K> cache key type
* @param <V> cache value type
* @author johnniang
* *
*/
public interface CacheStore<K, V> {
/**
* Gets by cache key.
*
* @param key must not be null
* @return cache value
*/
@NonNull
Optional<V> get(@NonNull K key);
/**
* Puts a cache which will be expired.
*
* @param key cache key must not be null
* @param value cache value must not be null
* @param timeout the key expiration must not be less than 1
* @param timeUnit timeout unit
*/
void put(@NonNull K key, @NonNull V value, long timeout, @NonNull TimeUnit timeUnit);
/**
* Puts a non-expired cache.
*
* @param key cache key must not be null
* @param value cache value must not be null
*/
void put(@NonNull K key, @NonNull V value);
/**
* Puts a cache which will be expired if the key is absent.
*
* @param key cache key must not be null
* @param value cache value must not be null
* @param timeout the key expiration must not be less than 1
* @param timeUnit timeout unit must not be null
* @return true if the key is absent and the value is set, false if the key is present
* before, or null if any other reason
*/
Boolean putIfAbsent(@NonNull K key, @NonNull V value, long timeout, @NonNull TimeUnit timeUnit);
/**
* Delete a key.
*
* @param key cache key must not be null
*/
void delete(@NonNull K key);
/**
* Returns a view of the entries stored in this cache as a none thread-safe map.
* Modifications made to the map do not directly affect the cache.
*/
LinkedHashMap<K, V> toMap();
}

View File

@ -1,37 +0,0 @@
package run.halo.app.cache;
import java.io.Serializable;
import java.util.Date;
import lombok.AllArgsConstructor;
import lombok.Data;
import lombok.EqualsAndHashCode;
import lombok.NoArgsConstructor;
import lombok.ToString;
/**
* Cache wrapper.
*
* @author johnniang
*/
@Data
@ToString
@EqualsAndHashCode
@NoArgsConstructor
@AllArgsConstructor
class CacheWrapper<V> implements Serializable {
/**
* Cache data
*/
private V data;
/**
* Expired time.
*/
private Date expireAt;
/**
* Create time.
*/
private Date createAt;
}

View File

@ -1,136 +0,0 @@
package run.halo.app.cache;
import java.util.LinkedHashMap;
import java.util.Optional;
import java.util.Timer;
import java.util.TimerTask;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.locks.Lock;
import java.util.concurrent.locks.ReentrantLock;
import javax.annotation.PreDestroy;
import lombok.extern.slf4j.Slf4j;
import org.springframework.lang.NonNull;
import org.springframework.util.Assert;
/**
* In-memory cache store.
*
* @author johnniang
*/
@Slf4j
public class InMemoryCacheStore extends AbstractStringCacheStore {
/**
* Cleaner schedule period. (ms)
*/
private static final long PERIOD = 60 * 1000;
/**
* Cache container.
*/
private static final ConcurrentHashMap<String, CacheWrapper<String>> CACHE_CONTAINER =
new ConcurrentHashMap<>();
private final Timer timer;
/**
* Lock.
*/
private final Lock lock = new ReentrantLock();
public InMemoryCacheStore() {
// Run a cache store cleaner
timer = new Timer();
timer.scheduleAtFixedRate(new CacheExpiryCleaner(), 0, PERIOD);
}
@Override
@NonNull
Optional<CacheWrapper<String>> getInternal(@NonNull String key) {
Assert.hasText(key, "Cache key must not be blank");
return Optional.ofNullable(CACHE_CONTAINER.get(key));
}
@Override
void putInternal(@NonNull String key, @NonNull CacheWrapper<String> cacheWrapper) {
Assert.hasText(key, "Cache key must not be blank");
Assert.notNull(cacheWrapper, "Cache wrapper must not be null");
// Put the cache wrapper
CacheWrapper<String> putCacheWrapper = CACHE_CONTAINER.put(key, cacheWrapper);
log.debug("Put [{}] cache result: [{}], original cache wrapper: [{}]", key, putCacheWrapper,
cacheWrapper);
}
@Override
Boolean putInternalIfAbsent(@NonNull String key, @NonNull CacheWrapper<String> cacheWrapper) {
Assert.hasText(key, "Cache key must not be blank");
Assert.notNull(cacheWrapper, "Cache wrapper must not be null");
log.debug("Preparing to put key: [{}], value: [{}]", key, cacheWrapper);
lock.lock();
try {
// Get the value before
Optional<String> valueOptional = get(key);
if (valueOptional.isPresent()) {
log.warn("Failed to put the cache, because the key: [{}] has been present already",
key);
return false;
}
// Put the cache wrapper
putInternal(key, cacheWrapper);
log.debug("Put successfully");
return true;
} finally {
lock.unlock();
}
}
@Override
public void delete(@NonNull String key) {
Assert.hasText(key, "Cache key must not be blank");
CACHE_CONTAINER.remove(key);
log.debug("Removed key: [{}]", key);
}
@Override
public LinkedHashMap<String, String> toMap() {
LinkedHashMap<String, String> map = new LinkedHashMap<>();
CACHE_CONTAINER.forEach((key, value) -> map.put(key, value.getData()));
return map;
}
@PreDestroy
public void preDestroy() {
log.debug("Cancelling all timer tasks");
timer.cancel();
clear();
}
public void clear() {
CACHE_CONTAINER.clear();
}
/**
* Cache cleaner.
*
* @author johnniang
* @date 03/28/19
*/
private class CacheExpiryCleaner extends TimerTask {
@Override
public void run() {
CACHE_CONTAINER.keySet().forEach(key -> {
if (!InMemoryCacheStore.this.get(key).isPresent()) {
log.debug("Deleted the cache: [{}] for expiration", key);
}
});
}
}
}

View File

@ -1,182 +0,0 @@
package run.halo.app.cache;
import com.fasterxml.jackson.core.JsonProcessingException;
import java.io.File;
import java.io.IOException;
import java.nio.charset.Charset;
import java.util.Date;
import java.util.LinkedHashMap;
import java.util.Map;
import java.util.Optional;
import java.util.Timer;
import java.util.TimerTask;
import javax.annotation.PostConstruct;
import javax.annotation.PreDestroy;
import lombok.extern.slf4j.Slf4j;
import org.iq80.leveldb.DB;
import org.iq80.leveldb.DBFactory;
import org.iq80.leveldb.DBIterator;
import org.iq80.leveldb.Options;
import org.iq80.leveldb.WriteBatch;
import org.iq80.leveldb.impl.Iq80DBFactory;
import org.springframework.lang.NonNull;
import org.springframework.util.Assert;
import org.springframework.util.StringUtils;
import run.halo.app.config.properties.HaloProperties;
import run.halo.app.utils.JsonUtils;
/**
* level-db cache store
* Create by Pencilso on 2020/1/9 7:20
*/
@Slf4j
public class LevelCacheStore extends AbstractStringCacheStore {
/**
* Cleaner schedule period. (ms)
*/
private static final long PERIOD = 60 * 1000;
private static DB LEVEL_DB;
private Timer timer;
public LevelCacheStore(HaloProperties haloProperties) {
super.haloProperties = haloProperties;
}
@PostConstruct
public void init() {
if (LEVEL_DB != null) {
return;
}
try {
//work path
File folder = new File(haloProperties.getWorkDir() + ".leveldb");
DBFactory factory = new Iq80DBFactory();
Options options = new Options();
options.createIfMissing(true);
//open leveldb store folder
LEVEL_DB = factory.open(folder, options);
timer = new Timer();
timer.scheduleAtFixedRate(new CacheExpiryCleaner(), 0, PERIOD);
} catch (Exception ex) {
log.error("init leveldb error ", ex);
}
}
/**
*
*/
@PreDestroy
public void preDestroy() {
try {
LEVEL_DB.close();
timer.cancel();
} catch (IOException e) {
log.error("close leveldb error ", e);
}
}
@Override
@NonNull
Optional<CacheWrapper<String>> getInternal(@NonNull String key) {
Assert.hasText(key, "Cache key must not be blank");
byte[] bytes = LEVEL_DB.get(stringToBytes(key));
if (bytes != null) {
String valueJson = bytesToString(bytes);
return StringUtils.isEmpty(valueJson) ? Optional.empty() :
jsonToCacheWrapper(valueJson);
}
return Optional.empty();
}
@Override
void putInternal(@NonNull String key, @NonNull CacheWrapper<String> cacheWrapper) {
putInternalIfAbsent(key, cacheWrapper);
}
@Override
Boolean putInternalIfAbsent(@NonNull String key, @NonNull CacheWrapper<String> cacheWrapper) {
Assert.hasText(key, "Cache key must not be blank");
Assert.notNull(cacheWrapper, "Cache wrapper must not be null");
try {
LEVEL_DB.put(
stringToBytes(key),
stringToBytes(JsonUtils.objectToJson(cacheWrapper))
);
return true;
} catch (JsonProcessingException e) {
log.warn("Put cache fail json2object key: [{}] value:[{}]", key, cacheWrapper);
}
log.debug("Cache key: [{}], original cache wrapper: [{}]", key, cacheWrapper);
return false;
}
@Override
public void delete(@NonNull String key) {
LEVEL_DB.delete(stringToBytes(key));
log.debug("cache remove key: [{}]", key);
}
@Override
public LinkedHashMap<String, String> toMap() {
LinkedHashMap<String, String> map = new LinkedHashMap<>();
LEVEL_DB.forEach(entry -> {
String key = bytesToString(entry.getKey());
String valueJson = bytesToString(entry.getValue());
Optional<CacheWrapper<String>> cacheWrapperOptional = jsonToCacheWrapper(valueJson);
if (cacheWrapperOptional.isPresent()) {
map.put(key, cacheWrapperOptional.get().getData());
} else {
map.put(key, null);
}
});
return map;
}
private byte[] stringToBytes(String str) {
return str.getBytes(Charset.defaultCharset());
}
private String bytesToString(byte[] bytes) {
return new String(bytes, Charset.defaultCharset());
}
private class CacheExpiryCleaner extends TimerTask {
@Override
public void run() {
//batch
WriteBatch writeBatch = LEVEL_DB.createWriteBatch();
DBIterator iterator = LEVEL_DB.iterator();
long currentTimeMillis = System.currentTimeMillis();
while (iterator.hasNext()) {
Map.Entry<byte[], byte[]> next = iterator.next();
if (next.getKey() == null || next.getValue() == null) {
continue;
}
String valueJson = bytesToString(next.getValue());
Optional<CacheWrapper<String>> stringCacheWrapper =
StringUtils.isEmpty(valueJson) ? Optional.empty() :
jsonToCacheWrapper(valueJson);
if (stringCacheWrapper.isPresent()) {
//get expireat time
long expireAtTime = stringCacheWrapper.map(CacheWrapper::getExpireAt)
.map(Date::getTime)
.orElse(0L);
//if expire
if (expireAtTime != 0 && currentTimeMillis > expireAtTime) {
writeBatch.delete(next.getKey());
log.debug("deleted the cache: [{}] for expiration",
bytesToString(next.getKey()));
}
}
}
LEVEL_DB.write(writeBatch);
}
}
}

View File

@ -1,74 +0,0 @@
package run.halo.app.cache.lock;
import java.lang.annotation.Documented;
import java.lang.annotation.ElementType;
import java.lang.annotation.Inherited;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;
import java.util.concurrent.TimeUnit;
import org.springframework.core.annotation.AliasFor;
/**
* Cache lock annotation.
*
* @author johnniang
* @date 3/28/19
*/
@Target(ElementType.METHOD)
@Retention(RetentionPolicy.RUNTIME)
@Documented
@Inherited
public @interface CacheLock {
/**
* Cache prefix, default is ""
*
* @return cache prefix
*/
@AliasFor("value")
String prefix() default "";
/**
* Alias of prefix, default is ""
*
* @return alias of prefix
*/
@AliasFor("prefix")
String value() default "";
/**
* Expired time, default is 5.
*
* @return expired time
*/
long expired() default 5;
/**
* Time unit, default is TimeUnit.SECONDS.
*
* @return time unit
*/
TimeUnit timeUnit() default TimeUnit.SECONDS;
/**
* Delimiter, default is ':'
*
* @return delimiter
*/
String delimiter() default ":";
/**
* Whether delete cache after method invocation.
*
* @return true if delete cache after method invocation; false otherwise
*/
boolean autoDelete() default true;
/**
* Whether trace the request info.
*
* @return true if trace the request info; false otherwise
*/
boolean traceRequest() default false;
}

View File

@ -1,127 +0,0 @@
package run.halo.app.cache.lock;
import java.lang.annotation.Annotation;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.lang3.StringUtils;
import org.aspectj.lang.ProceedingJoinPoint;
import org.aspectj.lang.annotation.Around;
import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.reflect.MethodSignature;
import org.springframework.context.annotation.Configuration;
import org.springframework.lang.NonNull;
import org.springframework.util.Assert;
import run.halo.app.cache.AbstractStringCacheStore;
import run.halo.app.exception.FrequentAccessException;
import run.halo.app.exception.ServiceException;
import run.halo.app.utils.ServletUtils;
/**
* Interceptor for cache lock annotation.
*
* @author johnniang
* @date 3/28/19
*/
@Slf4j
@Aspect
@Configuration
public class CacheLockInterceptor {
private static final String CACHE_LOCK_PREFIX = "cache_lock_";
private static final String CACHE_LOCK_VALUE = "locked";
private final AbstractStringCacheStore cacheStore;
public CacheLockInterceptor(AbstractStringCacheStore cacheStore) {
this.cacheStore = cacheStore;
}
@Around("@annotation(run.halo.app.cache.lock.CacheLock)")
public Object interceptCacheLock(ProceedingJoinPoint joinPoint) throws Throwable {
// Get method signature
MethodSignature methodSignature = (MethodSignature) joinPoint.getSignature();
log.debug("Starting locking: [{}]", methodSignature.toString());
// Get cache lock
CacheLock cacheLock = methodSignature.getMethod().getAnnotation(CacheLock.class);
// Build cache lock key
String cacheLockKey = buildCacheLockKey(cacheLock, joinPoint);
log.debug("Built lock key: [{}]", cacheLockKey);
try {
// Get from cache
Boolean cacheResult = cacheStore
.putIfAbsent(cacheLockKey, CACHE_LOCK_VALUE, cacheLock.expired(),
cacheLock.timeUnit());
if (cacheResult == null) {
throw new ServiceException("Unknown reason of cache " + cacheLockKey)
.setErrorData(cacheLockKey);
}
if (!cacheResult) {
throw new FrequentAccessException("访问过于频繁,请稍后再试!").setErrorData(cacheLockKey);
}
// Proceed the method
return joinPoint.proceed();
} finally {
// Delete the cache
if (cacheLock.autoDelete()) {
cacheStore.delete(cacheLockKey);
log.debug("Deleted the cache lock: [{}]", cacheLock);
}
}
}
private String buildCacheLockKey(@NonNull CacheLock cacheLock,
@NonNull ProceedingJoinPoint joinPoint) {
Assert.notNull(cacheLock, "Cache lock must not be null");
Assert.notNull(joinPoint, "Proceeding join point must not be null");
// Get the method
MethodSignature methodSignature = (MethodSignature) joinPoint.getSignature();
// Build the cache lock key
StringBuilder cacheKeyBuilder = new StringBuilder(CACHE_LOCK_PREFIX);
String delimiter = cacheLock.delimiter();
if (StringUtils.isNotBlank(cacheLock.prefix())) {
cacheKeyBuilder.append(cacheLock.prefix());
} else {
cacheKeyBuilder.append(methodSignature.getMethod().toString());
}
// Handle cache lock key building
Annotation[][] parameterAnnotations = methodSignature.getMethod().getParameterAnnotations();
for (int i = 0; i < parameterAnnotations.length; i++) {
log.debug("Parameter annotation[{}] = {}", i, parameterAnnotations[i]);
for (int j = 0; j < parameterAnnotations[i].length; j++) {
Annotation annotation = parameterAnnotations[i][j];
log.debug("Parameter annotation[{}][{}]: {}", i, j, annotation);
if (annotation instanceof CacheParam) {
// Get current argument
Object arg = joinPoint.getArgs()[i];
log.debug("Cache param args: [{}]", arg);
// Append to the cache key
cacheKeyBuilder.append(delimiter).append(arg.toString());
}
}
}
if (cacheLock.traceRequest()) {
// Append http request info
cacheKeyBuilder.append(delimiter).append(ServletUtils.getRequestIp());
}
return cacheKeyBuilder.toString();
}
}

View File

@ -1,22 +0,0 @@
package run.halo.app.cache.lock;
import java.lang.annotation.Documented;
import java.lang.annotation.ElementType;
import java.lang.annotation.Inherited;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;
/**
* Cache parameter annotation.
*
* @author johnniang
* @date 3/28/19
*/
@Target(ElementType.PARAMETER)
@Retention(RetentionPolicy.RUNTIME)
@Documented
@Inherited
public @interface CacheParam {
}

View File

@ -1,83 +0,0 @@
package run.halo.app.config;
import com.fasterxml.jackson.databind.ObjectMapper;
import java.security.KeyManagementException;
import java.security.KeyStoreException;
import java.security.NoSuchAlgorithmException;
import lombok.extern.slf4j.Slf4j;
import org.springframework.boot.autoconfigure.condition.ConditionalOnMissingBean;
import org.springframework.boot.context.properties.EnableConfigurationProperties;
import org.springframework.boot.web.client.RestTemplateBuilder;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.context.annotation.Import;
import org.springframework.data.jpa.repository.config.EnableJpaRepositories;
import org.springframework.http.client.HttpComponentsClientHttpRequestFactory;
import org.springframework.http.converter.json.Jackson2ObjectMapperBuilder;
import org.springframework.scheduling.annotation.EnableAsync;
import org.springframework.scheduling.annotation.EnableScheduling;
import org.springframework.web.client.RestTemplate;
import run.halo.app.cache.AbstractStringCacheStore;
import run.halo.app.cache.InMemoryCacheStore;
import run.halo.app.cache.LevelCacheStore;
import run.halo.app.config.attributeconverter.AttributeConverterAutoGenerateConfiguration;
import run.halo.app.config.properties.HaloProperties;
import run.halo.app.repository.base.BaseRepositoryImpl;
import run.halo.app.utils.HttpClientUtils;
/**
* Halo configuration.
*
* @author johnniang
*/
@Slf4j
@EnableAsync
@EnableScheduling
@Configuration(proxyBeanMethods = false)
@EnableConfigurationProperties(HaloProperties.class)
@EnableJpaRepositories(basePackages = "run.halo.app.repository", repositoryBaseClass =
BaseRepositoryImpl.class)
@Import(AttributeConverterAutoGenerateConfiguration.class)
public class HaloConfiguration {
private final HaloProperties haloProperties;
public HaloConfiguration(HaloProperties haloProperties) {
this.haloProperties = haloProperties;
}
@Bean
ObjectMapper objectMapper(Jackson2ObjectMapperBuilder builder) {
builder.failOnEmptyBeans(false);
return builder.build();
}
@Bean
RestTemplate httpsRestTemplate(RestTemplateBuilder builder)
throws NoSuchAlgorithmException, KeyStoreException, KeyManagementException {
RestTemplate httpsRestTemplate = builder.build();
httpsRestTemplate.setRequestFactory(
new HttpComponentsClientHttpRequestFactory(HttpClientUtils.createHttpsClient(
(int) haloProperties.getDownloadTimeout().toMillis())));
return httpsRestTemplate;
}
@Bean
@ConditionalOnMissingBean
AbstractStringCacheStore stringCacheStore() {
AbstractStringCacheStore stringCacheStore;
switch (haloProperties.getCache()) {
case "level":
stringCacheStore = new LevelCacheStore(this.haloProperties);
break;
case "memory":
default:
//memory or default
stringCacheStore = new InMemoryCacheStore();
break;
}
log.info("Halo cache store load impl : [{}]", stringCacheStore.getClass());
return stringCacheStore;
}
}

View File

@ -1,210 +0,0 @@
package run.halo.app.config;
import static run.halo.app.model.support.HaloConst.FILE_SEPARATOR;
import static run.halo.app.utils.HaloUtils.URL_SEPARATOR;
import static run.halo.app.utils.HaloUtils.ensureBoth;
import static run.halo.app.utils.HaloUtils.ensureSuffix;
import com.fasterxml.jackson.databind.ObjectMapper;
import freemarker.template.TemplateModel;
import java.io.IOException;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.concurrent.TimeUnit;
import javax.servlet.MultipartConfigElement;
import javax.servlet.http.HttpServletRequest;
import kr.pe.kwonnam.freemarker.inheritance.BlockDirective;
import kr.pe.kwonnam.freemarker.inheritance.PutDirective;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.fileupload.FileUploadBase;
import org.apache.commons.fileupload.servlet.ServletRequestContext;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.boot.autoconfigure.ImportAutoConfiguration;
import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty;
import org.springframework.boot.autoconfigure.web.servlet.MultipartAutoConfiguration;
import org.springframework.boot.autoconfigure.web.servlet.MultipartProperties;
import org.springframework.boot.autoconfigure.web.servlet.WebMvcRegistrations;
import org.springframework.boot.context.properties.EnableConfigurationProperties;
import org.springframework.boot.jackson.JsonComponentModule;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.core.io.FileUrlResource;
import org.springframework.data.domain.PageImpl;
import org.springframework.data.web.PageableHandlerMethodArgumentResolver;
import org.springframework.data.web.SortHandlerMethodArgumentResolver;
import org.springframework.format.FormatterRegistry;
import org.springframework.http.CacheControl;
import org.springframework.http.converter.HttpMessageConverter;
import org.springframework.http.converter.json.Jackson2ObjectMapperBuilder;
import org.springframework.http.converter.json.MappingJackson2HttpMessageConverter;
import org.springframework.lang.NonNull;
import org.springframework.util.StringUtils;
import org.springframework.web.method.support.HandlerMethodArgumentResolver;
import org.springframework.web.multipart.MultipartResolver;
import org.springframework.web.multipart.commons.CommonsMultipartResolver;
import org.springframework.web.servlet.config.annotation.ResourceHandlerRegistry;
import org.springframework.web.servlet.config.annotation.ViewControllerRegistry;
import org.springframework.web.servlet.config.annotation.WebMvcConfigurer;
import org.springframework.web.servlet.mvc.method.annotation.RequestMappingHandlerMapping;
import run.halo.app.config.properties.HaloProperties;
import run.halo.app.core.PageJacksonSerializer;
import run.halo.app.core.freemarker.inheritance.ThemeExtendsDirective;
import run.halo.app.factory.StringToEnumConverterFactory;
import run.halo.app.security.resolver.AuthenticationArgumentResolver;
/**
* Halo mvc configuration.
*
* @author ryanwang
* @date 2018-01-02
*/
@Slf4j
@Configuration
@EnableConfigurationProperties(MultipartProperties.class)
@ImportAutoConfiguration(exclude = MultipartAutoConfiguration.class)
public class HaloMvcConfiguration implements WebMvcConfigurer {
private static final String FILE_PROTOCOL = "file:///";
private final PageableHandlerMethodArgumentResolver pageableResolver;
private final SortHandlerMethodArgumentResolver sortResolver;
private final HaloProperties haloProperties;
@Value("${springfox.documentation.swagger-ui.base-url:}")
private String swaggerBaseUrl;
public HaloMvcConfiguration(PageableHandlerMethodArgumentResolver pageableResolver,
SortHandlerMethodArgumentResolver sortResolver,
HaloProperties haloProperties) {
this.pageableResolver = pageableResolver;
this.sortResolver = sortResolver;
this.haloProperties = haloProperties;
}
// @Bean
public Map<String, TemplateModel> freemarkerLayoutDirectives() {
Map<String, TemplateModel> freemarkerLayoutDirectives = new HashMap<>();
freemarkerLayoutDirectives.put("extends", new ThemeExtendsDirective());
freemarkerLayoutDirectives.put("block", new BlockDirective());
freemarkerLayoutDirectives.put("put", new PutDirective());
return freemarkerLayoutDirectives;
}
/**
* Configuring multipartResolver for large file upload..
*
* @return new multipartResolver
*/
@Bean(name = "multipartResolver")
@ConditionalOnProperty(prefix = "spring.servlet.multipart", name = "enabled",
havingValue = "true", matchIfMissing = true)
MultipartResolver multipartResolver(MultipartProperties multipartProperties)
throws IOException {
MultipartConfigElement multipartConfigElement = multipartProperties.createMultipartConfig();
CommonsMultipartResolver resolver = new CommonsMultipartResolver() {
@Override
public boolean isMultipart(@NonNull HttpServletRequest request) {
final var method = request.getMethod();
if (!"POST".equalsIgnoreCase(method) && !"PUT".equalsIgnoreCase(method)) {
return false;
}
return FileUploadBase.isMultipartContent(new ServletRequestContext(request));
}
};
resolver.setDefaultEncoding("UTF-8");
resolver.setMaxUploadSize(multipartConfigElement.getMaxRequestSize());
resolver.setMaxUploadSizePerFile(multipartConfigElement.getMaxFileSize());
var location = multipartProperties.getLocation();
if (StringUtils.hasText(location)) {
FileUrlResource resource = new FileUrlResource(location);
resolver.setUploadTempDir(resource);
}
//lazy multipart parsing, throwing parse exceptions once the application attempts to
// obtain multipart files
resolver.setResolveLazily(multipartProperties.isResolveLazily());
return resolver;
}
@Bean
WebMvcRegistrations webMvcRegistrations() {
return new WebMvcRegistrations() {
@Override
public RequestMappingHandlerMapping getRequestMappingHandlerMapping() {
return new HaloRequestMappingHandlerMapping(haloProperties);
}
};
}
@Override
public void extendMessageConverters(List<HttpMessageConverter<?>> converters) {
converters.stream()
.filter(c -> c instanceof MappingJackson2HttpMessageConverter)
.findFirst()
.ifPresent(converter -> {
MappingJackson2HttpMessageConverter mappingJackson2HttpMessageConverter =
(MappingJackson2HttpMessageConverter) converter;
Jackson2ObjectMapperBuilder builder = Jackson2ObjectMapperBuilder.json();
JsonComponentModule module = new JsonComponentModule();
module.addSerializer(PageImpl.class, new PageJacksonSerializer());
ObjectMapper objectMapper = builder.modules(module).build();
mappingJackson2HttpMessageConverter.setObjectMapper(objectMapper);
});
}
@Override
public void addArgumentResolvers(List<HandlerMethodArgumentResolver> resolvers) {
resolvers.add(new AuthenticationArgumentResolver());
resolvers.add(pageableResolver);
resolvers.add(sortResolver);
}
@Override
public void addViewControllers(ViewControllerRegistry registry) {
// for backward compatibility
registry.addViewController("/swagger-ui.html")
.setViewName("redirect:" + swaggerBaseUrl + "/swagger-ui/");
}
/**
* Configuring static resource path
*
* @param registry registry
*/
@Override
public void addResourceHandlers(ResourceHandlerRegistry registry) {
String workDir = FILE_PROTOCOL + ensureSuffix(haloProperties.getWorkDir(), FILE_SEPARATOR);
// register /** resource handler.
registry.addResourceHandler("/**")
.addResourceLocations("classpath:/admin/")
.addResourceLocations(workDir + "static/");
// register /themes/** resource handler.
registry.addResourceHandler("/themes/**")
.addResourceLocations(workDir + "templates/themes/");
String uploadUrlPattern =
ensureBoth(haloProperties.getUploadUrlPrefix(), URL_SEPARATOR) + "**";
String adminPathPattern = ensureSuffix(haloProperties.getAdminPath(), URL_SEPARATOR) + "**";
registry.addResourceHandler(uploadUrlPattern)
.setCacheControl(CacheControl.maxAge(7L, TimeUnit.DAYS))
.addResourceLocations(workDir + "upload/");
registry.addResourceHandler(adminPathPattern)
.addResourceLocations("classpath:/admin/");
// If doc is enable
registry.addResourceHandler("swagger-ui.html")
.addResourceLocations("classpath:/META-INF/resources/");
registry.addResourceHandler("/webjars/**")
.addResourceLocations("classpath:/META-INF/resources/webjars/");
}
@Override
public void addFormatters(FormatterRegistry registry) {
registry.addConverterFactory(new StringToEnumConverterFactory());
}
}

View File

@ -1,102 +0,0 @@
package run.halo.app.config;
import static run.halo.app.utils.HaloUtils.URL_SEPARATOR;
import static run.halo.app.utils.HaloUtils.ensureBoth;
import java.io.IOException;
import java.nio.file.Files;
import java.nio.file.Path;
import java.util.HashSet;
import java.util.Set;
import java.util.stream.Stream;
import javax.servlet.http.HttpServletRequest;
import lombok.extern.slf4j.Slf4j;
import org.springframework.context.ApplicationListener;
import org.springframework.util.AntPathMatcher;
import org.springframework.util.PathMatcher;
import org.springframework.web.method.HandlerMethod;
import org.springframework.web.servlet.mvc.method.annotation.RequestMappingHandlerMapping;
import run.halo.app.config.properties.HaloProperties;
import run.halo.app.event.StaticStorageChangedEvent;
/**
* @author ryanwang
* @date 2020-03-24
*/
@Slf4j
public class HaloRequestMappingHandlerMapping extends RequestMappingHandlerMapping
implements ApplicationListener<StaticStorageChangedEvent> {
private final Set<String> blackPatterns = new HashSet<>(16);
private final PathMatcher pathMatcher;
private final HaloProperties haloProperties;
public HaloRequestMappingHandlerMapping(HaloProperties haloProperties) {
this.haloProperties = haloProperties;
this.initBlackPatterns();
pathMatcher = new AntPathMatcher();
}
@Override
protected HandlerMethod lookupHandlerMethod(String lookupPath, HttpServletRequest request)
throws Exception {
log.debug("Looking path: [{}]", lookupPath);
for (String blackPattern : blackPatterns) {
if (this.pathMatcher.match(blackPattern, lookupPath)) {
log.debug("Skipped path [{}] with pattern: [{}]", lookupPath, blackPattern);
return null;
}
}
return super.lookupHandlerMethod(lookupPath, request);
}
private void initBlackPatterns() {
String uploadUrlPattern =
ensureBoth(haloProperties.getUploadUrlPrefix(), URL_SEPARATOR) + "**";
String adminPathPattern =
ensureBoth(haloProperties.getAdminPath(), URL_SEPARATOR) + "?*/**";
blackPatterns.add("/themes/**");
blackPatterns.add("/js/**");
blackPatterns.add("/images/**");
blackPatterns.add("/fonts/**");
blackPatterns.add("/css/**");
blackPatterns.add("/assets/**");
blackPatterns.add("/color.less");
blackPatterns.add("/swagger-ui.html");
blackPatterns.add("/swagger-ui/**");
blackPatterns.add("/csrf");
blackPatterns.add("/webjars/**");
blackPatterns.add(uploadUrlPattern);
blackPatterns.add(adminPathPattern);
}
@Override
public void onApplicationEvent(StaticStorageChangedEvent event) {
Path staticPath = event.getStaticPath();
try (Stream<Path> rootPathStream = Files.list(staticPath)) {
synchronized (this) {
blackPatterns.clear();
initBlackPatterns();
rootPathStream.forEach(rootPath -> {
if (Files.isDirectory(rootPath)) {
String directoryPattern = "/" + rootPath.getFileName().toString()
+ "/**";
blackPatterns.add(directoryPattern);
log.debug("Exclude for folder path pattern: [{}]", directoryPattern);
} else {
String pathPattern = "/" + rootPath.getFileName().toString();
blackPatterns.add(pathPattern);
log.debug("Exclude for file path pattern: [{}]", pathPattern);
}
}
);
}
} catch (IOException e) {
log.error("Failed to refresh static directory mapping", e);
}
}
}

View File

@ -1,265 +0,0 @@
package run.halo.app.config;
import static run.halo.app.model.support.HaloConst.ADMIN_TOKEN_HEADER_NAME;
import static run.halo.app.model.support.HaloConst.ADMIN_TOKEN_QUERY_NAME;
import static run.halo.app.model.support.HaloConst.API_ACCESS_KEY_HEADER_NAME;
import static run.halo.app.model.support.HaloConst.API_ACCESS_KEY_QUERY_NAME;
import static run.halo.app.model.support.HaloConst.HALO_VERSION;
import static run.halo.app.utils.SwaggerUtils.customMixin;
import static run.halo.app.utils.SwaggerUtils.propertyBuilder;
import static springfox.documentation.schema.AlternateTypeRules.newRule;
import com.fasterxml.classmate.TypeResolver;
import io.swagger.models.auth.In;
import java.lang.reflect.Type;
import java.time.temporal.Temporal;
import java.util.Arrays;
import java.util.Collections;
import java.util.List;
import lombok.extern.slf4j.Slf4j;
import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.core.Ordered;
import org.springframework.data.domain.Page;
import org.springframework.data.domain.Pageable;
import org.springframework.data.domain.Sort;
import org.springframework.lang.NonNull;
import org.springframework.util.AntPathMatcher;
import org.springframework.util.Assert;
import org.springframework.util.PathMatcher;
import run.halo.app.utils.SwaggerUtils;
import springfox.documentation.builders.ApiInfoBuilder;
import springfox.documentation.builders.PathSelectors;
import springfox.documentation.builders.RequestHandlerSelectors;
import springfox.documentation.schema.AlternateTypeRule;
import springfox.documentation.schema.AlternateTypeRuleConvention;
import springfox.documentation.schema.WildcardType;
import springfox.documentation.service.ApiInfo;
import springfox.documentation.service.ApiKey;
import springfox.documentation.service.AuthorizationScope;
import springfox.documentation.service.Contact;
import springfox.documentation.service.SecurityReference;
import springfox.documentation.service.SecurityScheme;
import springfox.documentation.spi.service.contexts.SecurityContext;
import springfox.documentation.spring.web.plugins.Docket;
import springfox.documentation.swagger.web.DocExpansion;
import springfox.documentation.swagger.web.ModelRendering;
import springfox.documentation.swagger.web.OperationsSorter;
import springfox.documentation.swagger.web.SecurityConfiguration;
import springfox.documentation.swagger.web.SecurityConfigurationBuilder;
import springfox.documentation.swagger.web.TagsSorter;
import springfox.documentation.swagger.web.UiConfiguration;
import springfox.documentation.swagger.web.UiConfigurationBuilder;
/**
* Swagger configuration.
*
* @author johnniang
*/
@Slf4j
@Configuration
@ConditionalOnProperty(
value = "springfox.documentation.enabled",
havingValue = "true",
matchIfMissing = true)
public class SwaggerConfiguration {
@Bean
public Docket haloDefaultApi() {
return buildApiDocket("run.halo.app.content.api",
"run.halo.app.controller.content.api",
"/api/content/**")
.securitySchemes(contentApiKeys())
.securityContexts(contentSecurityContext());
}
@Bean
public Docket haloAdminApi() {
return buildApiDocket("run.halo.app.admin.api",
"run.halo.app.controller.admin",
"/api/admin/**")
.securitySchemes(adminApiKeys())
.securityContexts(adminSecurityContext());
}
@Bean
SecurityConfiguration security() {
return SecurityConfigurationBuilder.builder()
.clientId("halo-app-client-id")
.clientSecret("halo-app-client-secret")
.realm("halo-app-realm")
.appName("halo-app")
.scopeSeparator(",")
.additionalQueryStringParams(null)
.useBasicAuthenticationWithAccessCodeGrant(false)
.build();
}
@Bean
UiConfiguration uiConfig() {
return UiConfigurationBuilder.builder()
.deepLinking(true)
.displayOperationId(false)
.defaultModelsExpandDepth(1)
.defaultModelExpandDepth(1)
.defaultModelRendering(ModelRendering.EXAMPLE)
.displayRequestDuration(false)
.docExpansion(DocExpansion.NONE)
.filter(false)
.maxDisplayedTags(null)
.operationsSorter(OperationsSorter.ALPHA)
.showExtensions(false)
.showCommonExtensions(false)
.tagsSorter(TagsSorter.ALPHA)
.supportedSubmitMethods(UiConfiguration.Constants.DEFAULT_SUBMIT_METHODS)
.validatorUrl(null)
.build();
}
private Docket buildApiDocket(@NonNull String groupName, @NonNull String basePackage,
@NonNull String antPattern) {
Assert.hasText(groupName, "Group name must not be blank");
Assert.hasText(basePackage, "Base package must not be blank");
Assert.hasText(antPattern, "Ant pattern must not be blank");
return SwaggerUtils.defaultDocket()
.groupName(groupName)
.select()
.apis(RequestHandlerSelectors.basePackage(basePackage))
.paths(PathSelectors.ant(antPattern))
.build()
.apiInfo(apiInfo())
.directModelSubstitute(Temporal.class, String.class);
}
private List<SecurityScheme> adminApiKeys() {
return Arrays.asList(
new ApiKey(ADMIN_TOKEN_HEADER_NAME, ADMIN_TOKEN_HEADER_NAME, In.HEADER.name()),
new ApiKey(ADMIN_TOKEN_QUERY_NAME, ADMIN_TOKEN_QUERY_NAME, In.QUERY.name())
);
}
private List<SecurityContext> adminSecurityContext() {
final PathMatcher pathMatcher = new AntPathMatcher();
return Collections.singletonList(
SecurityContext.builder()
.securityReferences(adminApiAuths())
.operationSelector(operationContext -> {
var requestMappingPattern = operationContext.requestMappingPattern();
return pathMatcher.match("/api/admin/**/*", requestMappingPattern);
})
.build()
);
}
private List<SecurityScheme> contentApiKeys() {
return Arrays.asList(
new ApiKey(API_ACCESS_KEY_HEADER_NAME, API_ACCESS_KEY_HEADER_NAME, In.HEADER.name()),
new ApiKey(API_ACCESS_KEY_QUERY_NAME, API_ACCESS_KEY_QUERY_NAME, In.QUERY.name())
);
}
private List<SecurityContext> contentSecurityContext() {
final PathMatcher pathMatcher = new AntPathMatcher();
return Collections.singletonList(
SecurityContext.builder()
.securityReferences(contentApiAuths())
.operationSelector(operationContext -> {
var requestMappingPattern = operationContext.requestMappingPattern();
return pathMatcher.match("/api/content/**/*", requestMappingPattern);
})
.build()
);
}
private List<SecurityReference> adminApiAuths() {
AuthorizationScope[] authorizationScopes =
{new AuthorizationScope("Admin api", "Access admin api")};
return Arrays.asList(new SecurityReference(ADMIN_TOKEN_HEADER_NAME, authorizationScopes),
new SecurityReference(ADMIN_TOKEN_QUERY_NAME, authorizationScopes));
}
private List<SecurityReference> contentApiAuths() {
AuthorizationScope[] authorizationScopes =
{new AuthorizationScope("content api", "Access content api")};
return Arrays.asList(new SecurityReference(API_ACCESS_KEY_HEADER_NAME, authorizationScopes),
new SecurityReference(API_ACCESS_KEY_QUERY_NAME, authorizationScopes));
}
private ApiInfo apiInfo() {
return new ApiInfoBuilder()
.title("Halo API Documentation")
.description("Documentation for Halo API")
.version(HALO_VERSION)
.termsOfServiceUrl("https://github.com/halo-dev")
.contact(
new Contact("halo-dev", "https://github.com/halo-dev/halo/issues", "hi@halo.run"))
.license("GNU General Public License v3.0")
.licenseUrl("https://github.com/halo-dev/halo/blob/master/LICENSE")
.build();
}
@Bean
public AlternateTypeRuleConvention customizeConvention(TypeResolver resolver) {
return new AlternateTypeRuleConvention() {
@Override
public int getOrder() {
return Ordered.LOWEST_PRECEDENCE;
}
@Override
public List<AlternateTypeRule> rules() {
return Arrays.asList(
newRule(resolver.resolve(Page.class, WildcardType.class),
resolver.resolve(CustomizedPage.class, WildcardType.class)),
newRule(resolver.resolve(Pageable.class), resolver.resolve(pageableMixin())),
newRule(resolver.resolve(Sort.class), resolver.resolve(sortMixin())));
}
};
}
private Type sortMixin() {
return customMixin(Sort.class,
Collections.singletonList(propertyBuilder(String[].class, "sort")));
}
private Type pageableMixin() {
return customMixin(Pageable.class, Arrays.asList(
propertyBuilder(Integer.class, "page"),
propertyBuilder(Integer.class, "size"),
propertyBuilder(String[].class, "sort")
));
}
/**
* Alternative page type.
*
* @param <T> content type
* @author johnniang
*/
interface CustomizedPage<T> {
List<T> getContent();
int getPage();
int getPages();
long getTotal();
int getRpp();
boolean getHasNext();
boolean getHasPrevious();
boolean getIsFirst();
boolean getIsEmpty();
boolean getHasContent();
}
}

View File

@ -1,20 +0,0 @@
package run.halo.app.config.attributeconverter;
import org.springframework.beans.factory.config.ConfigurableListableBeanFactory;
import org.springframework.boot.autoconfigure.orm.jpa.EntityManagerFactoryBuilderCustomizer;
import org.springframework.context.annotation.Bean;
/**
* Jpa configuration.
*
* @author johnniang
*/
public class AttributeConverterAutoGenerateConfiguration {
@Bean
EntityManagerFactoryBuilderCustomizer entityManagerFactoryBuilderCustomizer(
ConfigurableListableBeanFactory factory) {
return builder -> builder.setPersistenceUnitPostProcessors(
new AutoGenerateConverterPersistenceUnitPostProcessor(factory));
}
}

View File

@ -1,71 +0,0 @@
package run.halo.app.config.attributeconverter;
import static net.bytebuddy.description.annotation.AnnotationDescription.Builder.ofType;
import static net.bytebuddy.description.type.TypeDescription.Generic.Builder.parameterizedType;
import static net.bytebuddy.implementation.FieldAccessor.ofField;
import static net.bytebuddy.implementation.MethodDelegation.to;
import static net.bytebuddy.matcher.ElementMatchers.isDefaultConstructor;
import static net.bytebuddy.matcher.ElementMatchers.named;
import java.lang.reflect.Modifier;
import javax.persistence.AttributeConverter;
import javax.persistence.Converter;
import net.bytebuddy.ByteBuddy;
import net.bytebuddy.NamingStrategy;
import net.bytebuddy.description.type.TypeDescription;
import net.bytebuddy.dynamic.loading.ClassLoadingStrategy;
import net.bytebuddy.implementation.MethodCall;
import org.apache.commons.lang3.StringUtils;
/**
* Attribute converter auto generator.
*
* @author johnniang
*/
class AttributeConverterAutoGenerator {
/**
* Auto generation suffix.
*/
public static final String AUTO_GENERATION_SUFFIX = "$AttributeConverterGeneratedByByteBuddy";
private final ClassLoader classLoader;
public AttributeConverterAutoGenerator(ClassLoader classLoader) {
this.classLoader = classLoader;
}
public <T> Class<?> generate(Class<T> clazz) {
try {
return new ByteBuddy()
.with(new NamingStrategy.AbstractBase() {
@Override
protected String name(TypeDescription superClass) {
return clazz.getName() + AUTO_GENERATION_SUFFIX;
}
})
.subclass(
parameterizedType(AttributeConverter.class, clazz, Integer.class).build())
.annotateType(ofType(Converter.class).define("autoApply", true).build())
.constructor(isDefaultConstructor())
.intercept(MethodCall.invoke(Object.class.getDeclaredConstructor())
.andThen(ofField("enumType").setsValue(clazz)))
.defineField("enumType", Class.class, Modifier.PRIVATE | Modifier.FINAL)
.method(named("convertToDatabaseColumn"))
.intercept(to(AttributeConverterInterceptor.class))
.method(named("convertToEntityAttribute"))
.intercept(to(AttributeConverterInterceptor.class))
.make()
.load(this.classLoader, ClassLoadingStrategy.Default.INJECTION.allowExistingTypes())
.getLoaded();
} catch (NoSuchMethodException e) {
// should never happen
throw new RuntimeException("Failed to get declared constructor.", e);
}
}
public static boolean isGeneratedByByteBuddy(String className) {
return StringUtils.endsWith(className, AUTO_GENERATION_SUFFIX);
}
}

View File

@ -1,27 +0,0 @@
package run.halo.app.config.attributeconverter;
import net.bytebuddy.implementation.bind.annotation.FieldValue;
import net.bytebuddy.implementation.bind.annotation.RuntimeType;
import run.halo.app.model.enums.ValueEnum;
/**
* Attribute Converter Interceptor.
*
* @author johnniang
*/
public class AttributeConverterInterceptor {
private AttributeConverterInterceptor() {
}
@RuntimeType
public static <T extends Enum<T> & ValueEnum<V>, V> V convertToDatabaseColumn(T attribute) {
return attribute == null ? null : attribute.getValue();
}
@RuntimeType
public static <T extends Enum<T> & ValueEnum<V>, V> T convertToEntityAttribute(V dbData,
@FieldValue("enumType") Class<T> enumType) {
return dbData == null ? null : ValueEnum.valueToEnum(enumType, dbData);
}
}

View File

@ -1,55 +0,0 @@
package run.halo.app.config.attributeconverter;
import static java.util.stream.Collectors.toUnmodifiableSet;
import java.util.Set;
import org.springframework.beans.factory.config.ConfigurableListableBeanFactory;
import org.springframework.context.annotation.ClassPathScanningCandidateComponentProvider;
import org.springframework.core.type.filter.AssignableTypeFilter;
import org.springframework.orm.jpa.persistenceunit.MutablePersistenceUnitInfo;
import org.springframework.orm.jpa.persistenceunit.PersistenceUnitPostProcessor;
import org.springframework.util.ClassUtils;
import run.halo.app.model.enums.ValueEnum;
import run.halo.app.model.properties.PropertyEnum;
/**
* Attribute converter persistence unit post processor.
*
* @author johnniang
*/
class AutoGenerateConverterPersistenceUnitPostProcessor implements PersistenceUnitPostProcessor {
private static final String PACKAGE_TO_SCAN = "run.halo.app";
private final ConfigurableListableBeanFactory factory;
public AutoGenerateConverterPersistenceUnitPostProcessor(
ConfigurableListableBeanFactory factory) {
this.factory = factory;
}
@Override
public void postProcessPersistenceUnitInfo(MutablePersistenceUnitInfo pui) {
var generator = new AttributeConverterAutoGenerator(factory.getBeanClassLoader());
findValueEnumClasses()
.stream()
.map(generator::generate)
.map(Class::getName)
.forEach(pui::addManagedClassName);
}
private Set<Class<?>> findValueEnumClasses() {
var scanner = new ClassPathScanningCandidateComponentProvider(false);
// include ValueEnum class
scanner.addIncludeFilter(new AssignableTypeFilter(ValueEnum.class));
// exclude PropertyEnum class
scanner.addExcludeFilter(new AssignableTypeFilter(PropertyEnum.class));
return scanner.findCandidateComponents(PACKAGE_TO_SCAN)
.stream()
.filter(bd -> bd.getBeanClassName() != null)
.map(bd -> ClassUtils.resolveClassName(bd.getBeanClassName(), null))
.collect(toUnmodifiableSet());
}
}

View File

@ -1,79 +0,0 @@
package run.halo.app.config.properties;
import static run.halo.app.model.support.HaloConst.FILE_SEPARATOR;
import static run.halo.app.model.support.HaloConst.TEMP_DIR;
import static run.halo.app.model.support.HaloConst.USER_HOME;
import static run.halo.app.utils.HaloUtils.ensureSuffix;
import java.time.Duration;
import lombok.Data;
import org.springframework.boot.context.properties.ConfigurationProperties;
import run.halo.app.model.enums.Mode;
/**
* Halo configuration properties.
*
* @author johnniang
* @author ryanwang
* @date 2019-03-15
*/
@Data
@ConfigurationProperties("halo")
public class HaloProperties {
/**
* Authentication enabled.
*/
private boolean authEnabled = true;
/**
* Halo startup mode.
*/
private Mode mode = Mode.PRODUCTION;
/**
* Admin path.
*/
private String adminPath = "admin";
/**
* Work directory.
*/
private String workDir = ensureSuffix(USER_HOME, FILE_SEPARATOR) + ".halo" + FILE_SEPARATOR;
/**
* Halo backup directory.(Not recommended to modify this config);
*/
private String backupDir =
ensureSuffix(TEMP_DIR, FILE_SEPARATOR) + "halo-backup" + FILE_SEPARATOR;
/**
* Halo backup markdown directory.(Not recommended to modify this config);
*/
private String backupMarkdownDir =
ensureSuffix(TEMP_DIR, FILE_SEPARATOR) + "halo-backup-markdown" + FILE_SEPARATOR;
/**
* Halo data export directory.
*/
private String dataExportDir =
ensureSuffix(TEMP_DIR, FILE_SEPARATOR) + "halo-data-export" + FILE_SEPARATOR;
/**
* Upload prefix.
*/
private String uploadUrlPrefix = "upload";
/**
* Download Timeout.
*/
private Duration downloadTimeout = Duration.ofSeconds(30);
/**
* cache store impl
* memory
* level
*/
private String cache = "memory";
}

View File

@ -1,115 +0,0 @@
package run.halo.app.controller.admin.api;
import io.swagger.annotations.ApiOperation;
import javax.validation.Valid;
import lombok.extern.slf4j.Slf4j;
import org.springframework.http.HttpStatus;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.PathVariable;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.PutMapping;
import org.springframework.web.bind.annotation.RequestBody;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestParam;
import org.springframework.web.bind.annotation.RestController;
import run.halo.app.annotation.DisableOnCondition;
import run.halo.app.cache.lock.CacheLock;
import run.halo.app.model.dto.EnvironmentDTO;
import run.halo.app.model.dto.LoginPreCheckDTO;
import run.halo.app.model.entity.User;
import run.halo.app.model.enums.MFAType;
import run.halo.app.model.params.LoginParam;
import run.halo.app.model.params.ResetPasswordParam;
import run.halo.app.model.params.ResetPasswordSendCodeParam;
import run.halo.app.model.properties.PrimaryProperties;
import run.halo.app.model.support.BaseResponse;
import run.halo.app.security.token.AuthToken;
import run.halo.app.service.AdminService;
import run.halo.app.service.OptionService;
/**
* Admin controller.
*
* @author johnniang
* @author ryanwang
* @date 2019-03-19
*/
@Slf4j
@RestController
@RequestMapping("/api/admin")
public class AdminController {
private final AdminService adminService;
private final OptionService optionService;
public AdminController(AdminService adminService, OptionService optionService) {
this.adminService = adminService;
this.optionService = optionService;
}
@GetMapping(value = "/is_installed")
@ApiOperation("Checks Installation status")
public boolean isInstall() {
return optionService.getByPropertyOrDefault(PrimaryProperties.IS_INSTALLED, Boolean.class,
false);
}
@PostMapping("login/precheck")
@ApiOperation("Login")
@CacheLock(autoDelete = false, prefix = "login_precheck")
public LoginPreCheckDTO authPreCheck(@RequestBody @Valid LoginParam loginParam) {
final User user = adminService.authenticate(loginParam);
return new LoginPreCheckDTO(MFAType.useMFA(user.getMfaType()));
}
@PostMapping("login")
@ApiOperation("Login")
@CacheLock(autoDelete = false, prefix = "login_auth")
public AuthToken auth(@RequestBody @Valid LoginParam loginParam) {
return adminService.authCodeCheck(loginParam);
}
@PostMapping("logout")
@ApiOperation("Logs out (Clear session)")
@CacheLock(autoDelete = false)
public void logout() {
adminService.clearToken();
}
@PostMapping("password/code")
@ApiOperation("Sends reset password verify code")
@CacheLock(autoDelete = false)
@DisableOnCondition
public void sendResetCode(@RequestBody @Valid ResetPasswordSendCodeParam param) {
adminService.sendResetPasswordCode(param);
}
@PutMapping("password/reset")
@ApiOperation("Resets password by verify code")
@CacheLock(autoDelete = false)
@DisableOnCondition
public void resetPassword(@RequestBody @Valid ResetPasswordParam param) {
adminService.resetPasswordByCode(param);
}
@PostMapping("refresh/{refreshToken}")
@ApiOperation("Refreshes token")
@CacheLock(autoDelete = false)
public AuthToken refresh(@PathVariable("refreshToken") String refreshToken) {
return adminService.refreshToken(refreshToken);
}
@GetMapping("environments")
@ApiOperation("Gets environments info")
public EnvironmentDTO getEnvironments() {
return adminService.getEnvironments();
}
@GetMapping(value = "halo/logfile")
@ApiOperation("Gets halo log file content")
@DisableOnCondition
public BaseResponse<String> getLogFiles(@RequestParam("lines") Long lines) {
return BaseResponse.ok(HttpStatus.OK.getReasonPhrase(), adminService.getLogFiles(lines));
}
}

View File

@ -1,113 +0,0 @@
package run.halo.app.controller.admin.api;
import static org.springframework.data.domain.Sort.Direction.DESC;
import io.swagger.annotations.ApiOperation;
import java.util.LinkedList;
import java.util.List;
import javax.validation.Valid;
import org.springframework.data.domain.Page;
import org.springframework.data.domain.Pageable;
import org.springframework.data.web.PageableDefault;
import org.springframework.http.MediaType;
import org.springframework.web.bind.annotation.DeleteMapping;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.PathVariable;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.PutMapping;
import org.springframework.web.bind.annotation.RequestBody;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestPart;
import org.springframework.web.bind.annotation.RestController;
import org.springframework.web.multipart.MultipartFile;
import run.halo.app.model.dto.AttachmentDTO;
import run.halo.app.model.entity.Attachment;
import run.halo.app.model.enums.AttachmentType;
import run.halo.app.model.params.AttachmentParam;
import run.halo.app.model.params.AttachmentQuery;
import run.halo.app.service.AttachmentService;
/**
* Attachment controller.
*
* @author johnniang
* @date 2019-03-21
*/
@RestController
@RequestMapping("/api/admin/attachments")
public class AttachmentController {
private final AttachmentService attachmentService;
public AttachmentController(AttachmentService attachmentService) {
this.attachmentService = attachmentService;
}
@GetMapping
public Page<AttachmentDTO> pageBy(
@PageableDefault(sort = "createTime", direction = DESC) Pageable pageable,
AttachmentQuery attachmentQuery) {
return attachmentService.pageDtosBy(pageable, attachmentQuery);
}
@GetMapping("{id:\\d+}")
@ApiOperation("Gets attachment detail by id")
public AttachmentDTO getBy(@PathVariable("id") Integer id) {
Attachment attachment = attachmentService.getById(id);
return attachmentService.convertToDto(attachment);
}
@PutMapping("{attachmentId:\\d+}")
@ApiOperation("Updates a attachment")
public AttachmentDTO updateBy(@PathVariable("attachmentId") Integer attachmentId,
@RequestBody @Valid AttachmentParam attachmentParam) {
Attachment attachment = attachmentService.getById(attachmentId);
attachmentParam.update(attachment);
return new AttachmentDTO().convertFrom(attachmentService.update(attachment));
}
@DeleteMapping("{id:\\d+}")
@ApiOperation("Deletes attachment permanently by id")
public AttachmentDTO deletePermanently(@PathVariable("id") Integer id) {
return attachmentService.convertToDto(attachmentService.removePermanently(id));
}
@DeleteMapping
@ApiOperation("Deletes attachments permanently in batch by id array")
public List<Attachment> deletePermanentlyInBatch(@RequestBody List<Integer> ids) {
return attachmentService.removePermanently(ids);
}
@PostMapping("upload")
@ApiOperation("Uploads single file")
public AttachmentDTO uploadAttachment(@RequestPart("file") MultipartFile file) {
return attachmentService.convertToDto(attachmentService.upload(file));
}
@PostMapping(value = "uploads", consumes = MediaType.MULTIPART_FORM_DATA_VALUE)
@ApiOperation("Uploads multi files (Invalid in Swagger UI)")
public List<AttachmentDTO> uploadAttachments(@RequestPart("files") MultipartFile[] files) {
List<AttachmentDTO> result = new LinkedList<>();
for (MultipartFile file : files) {
// Upload single file
Attachment attachment = attachmentService.upload(file);
// Convert and add
result.add(attachmentService.convertToDto(attachment));
}
return result;
}
@GetMapping("media_types")
@ApiOperation("Lists all of media types")
public List<String> listMediaTypes() {
return attachmentService.listAllMediaType();
}
@GetMapping("types")
@ApiOperation("Lists all of types.")
public List<AttachmentType> listTypes() {
return attachmentService.listAllType();
}
}

View File

@ -1,248 +0,0 @@
package run.halo.app.controller.admin.api;
import static run.halo.app.service.BackupService.BackupType.JSON_DATA;
import static run.halo.app.service.BackupService.BackupType.MARKDOWN;
import static run.halo.app.service.BackupService.BackupType.WHOLE_SITE;
import io.swagger.annotations.ApiOperation;
import java.io.IOException;
import java.nio.file.Files;
import java.nio.file.Path;
import java.nio.file.Paths;
import java.util.List;
import java.util.Objects;
import java.util.stream.Collectors;
import javax.servlet.http.HttpServletRequest;
import lombok.extern.slf4j.Slf4j;
import org.springframework.core.io.Resource;
import org.springframework.http.HttpHeaders;
import org.springframework.http.MediaType;
import org.springframework.http.ResponseEntity;
import org.springframework.web.bind.annotation.DeleteMapping;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.PathVariable;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RequestBody;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestParam;
import org.springframework.web.bind.annotation.RequestPart;
import org.springframework.web.bind.annotation.RestController;
import org.springframework.web.multipart.MultipartFile;
import run.halo.app.annotation.DisableOnCondition;
import run.halo.app.config.properties.HaloProperties;
import run.halo.app.exception.NotFoundException;
import run.halo.app.model.dto.BackupDTO;
import run.halo.app.model.dto.post.BasePostDetailDTO;
import run.halo.app.model.params.PostMarkdownParam;
import run.halo.app.service.BackupService;
/**
* Backup controller
*
* @author johnniang
* @author ryanwang
* @author Raremaa
* @date 2019-04-26
*/
@RestController
@RequestMapping("/api/admin/backups")
@Slf4j
public class BackupController {
private final BackupService backupService;
private final HaloProperties haloProperties;
public BackupController(BackupService backupService, HaloProperties haloProperties) {
this.backupService = backupService;
this.haloProperties = haloProperties;
}
@GetMapping("work-dir/fetch")
public BackupDTO getWorkDirBackup(@RequestParam("filename") String filename) {
return backupService
.getBackup(Paths.get(haloProperties.getBackupDir(), filename), WHOLE_SITE)
.orElseThrow(() ->
new NotFoundException("备份文件 " + filename + " 不存在或已删除!").setErrorData(filename));
}
@GetMapping("data/fetch")
public BackupDTO getDataBackup(@RequestParam("filename") String filename) {
return backupService
.getBackup(Paths.get(haloProperties.getDataExportDir(), filename), JSON_DATA)
.orElseThrow(() ->
new NotFoundException("备份文件 " + filename + " 不存在或已删除!").setErrorData(filename));
}
@GetMapping("markdown/fetch")
public BackupDTO getMarkdownBackup(@RequestParam("filename") String filename) {
return backupService
.getBackup(Paths.get(haloProperties.getBackupMarkdownDir(), filename), MARKDOWN)
.orElseThrow(() ->
new NotFoundException("备份文件 " + filename + " 不存在或已删除!").setErrorData(filename));
}
@PostMapping("work-dir")
@ApiOperation("Backups work directory")
@DisableOnCondition
public BackupDTO backupHalo(@RequestBody List<String> options) {
return backupService.backupWorkDirectory(options);
}
@GetMapping("work-dir/options")
@ApiOperation("Gets items that can be backed up")
public List<String> listBackupItems() throws IOException {
return Files.list(Paths.get(haloProperties.getWorkDir()))
.map(Path::getFileName)
.filter(Objects::nonNull)
.map(Path::toString)
.sorted()
.collect(Collectors.toList());
}
@GetMapping("work-dir")
@ApiOperation("Gets all work directory backups")
public List<BackupDTO> listBackups() {
return backupService.listWorkDirBackups();
}
@GetMapping("work-dir/{filename:.+}")
@ApiOperation("Downloads a work directory backup file")
@DisableOnCondition
public ResponseEntity<Resource> downloadBackup(@PathVariable("filename") String filename,
HttpServletRequest request) {
log.info("Trying to download backup file: [{}]", filename);
// Load file as resource
Resource backupResource =
backupService.loadFileAsResource(haloProperties.getBackupDir(), filename);
String contentType = MediaType.APPLICATION_OCTET_STREAM_VALUE;
// Try to determine file's content type
try {
contentType =
request.getServletContext().getMimeType(backupResource.getFile().getAbsolutePath());
} catch (IOException e) {
log.warn("Could not determine file type", e);
// Ignore this error
}
return ResponseEntity.ok()
.contentType(MediaType.parseMediaType(contentType))
.header(HttpHeaders.CONTENT_DISPOSITION,
"attachment; filename=\"" + backupResource.getFilename() + "\"")
.body(backupResource);
}
@DeleteMapping("work-dir")
@ApiOperation("Deletes a work directory backup")
@DisableOnCondition
public void deleteBackup(@RequestParam("filename") String filename) {
backupService.deleteWorkDirBackup(filename);
}
@PostMapping("markdown/import")
@ApiOperation("Imports markdown")
public BasePostDetailDTO backupMarkdowns(@RequestPart("file") MultipartFile file)
throws IOException {
return backupService.importMarkdown(file);
}
@PostMapping("data")
@ApiOperation("Exports all data")
@DisableOnCondition
public BackupDTO exportData() {
return backupService.exportData();
}
@GetMapping("data")
@ApiOperation("Lists all exported data")
public List<BackupDTO> listExportedData() {
return backupService.listExportedData();
}
@DeleteMapping("data")
@ApiOperation("Deletes a exported data")
@DisableOnCondition
public void deleteExportedData(@RequestParam("filename") String filename) {
backupService.deleteExportedData(filename);
}
@GetMapping("data/{fileName:.+}")
@ApiOperation("Downloads a exported data")
@DisableOnCondition
public ResponseEntity<Resource> downloadExportedData(@PathVariable("fileName") String fileName,
HttpServletRequest request) {
log.info("Try to download exported data file: [{}]", fileName);
// Load exported data as resource
Resource exportDataResource =
backupService.loadFileAsResource(haloProperties.getDataExportDir(), fileName);
String contentType = MediaType.APPLICATION_OCTET_STREAM_VALUE;
// Try to determine file's content type
try {
contentType = request.getServletContext()
.getMimeType(exportDataResource.getFile().getAbsolutePath());
} catch (IOException e) {
log.warn("Could not determine file type", e);
}
return ResponseEntity.ok()
.contentType(MediaType.parseMediaType(contentType))
.header(HttpHeaders.CONTENT_DISPOSITION,
"attachment; filename=\"" + exportDataResource.getFilename() + "\"")
.body(exportDataResource);
}
@PostMapping("markdown/export")
@ApiOperation("Exports markdowns")
@DisableOnCondition
public BackupDTO exportMarkdowns(@RequestBody PostMarkdownParam postMarkdownParam)
throws IOException {
return backupService.exportMarkdowns(postMarkdownParam);
}
@GetMapping("markdown/export")
@ApiOperation("Gets all markdown backups")
public List<BackupDTO> listMarkdowns() {
return backupService.listMarkdowns();
}
@DeleteMapping("markdown/export")
@ApiOperation("Deletes a markdown backup")
@DisableOnCondition
public void deleteMarkdown(@RequestParam("filename") String filename) {
backupService.deleteMarkdown(filename);
}
@GetMapping("markdown/export/{fileName:.+}")
@ApiOperation("Downloads a work markdown backup file")
@DisableOnCondition
public ResponseEntity<Resource> downloadMarkdown(@PathVariable("fileName") String fileName,
HttpServletRequest request) {
log.info("Try to download markdown backup file: [{}]", fileName);
// Load file as resource
Resource backupResource =
backupService.loadFileAsResource(haloProperties.getBackupMarkdownDir(), fileName);
String contentType = MediaType.APPLICATION_OCTET_STREAM_VALUE;
// Try to determine file's content type
try {
contentType =
request.getServletContext().getMimeType(backupResource.getFile().getAbsolutePath());
} catch (IOException e) {
log.warn("Could not determine file type", e);
// Ignore this error
}
return ResponseEntity.ok()
.contentType(MediaType.parseMediaType(contentType))
.header(HttpHeaders.CONTENT_DISPOSITION,
"attachment; filename=\"" + backupResource.getFilename() + "\"")
.body(backupResource);
}
}

View File

@ -1,112 +0,0 @@
package run.halo.app.controller.admin.api;
import static org.springframework.data.domain.Sort.Direction.ASC;
import io.swagger.annotations.ApiOperation;
import java.util.List;
import java.util.Objects;
import java.util.stream.Collectors;
import javax.validation.Valid;
import org.springframework.data.domain.Sort;
import org.springframework.data.web.SortDefault;
import org.springframework.web.bind.annotation.DeleteMapping;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.PathVariable;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.PutMapping;
import org.springframework.web.bind.annotation.RequestBody;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestParam;
import org.springframework.web.bind.annotation.RestController;
import run.halo.app.model.dto.CategoryDTO;
import run.halo.app.model.entity.Category;
import run.halo.app.model.params.CategoryParam;
import run.halo.app.model.vo.CategoryVO;
import run.halo.app.service.CategoryService;
import run.halo.app.service.PostCategoryService;
/**
* Category controller.
*
* @author johnniang
* @date 2019-03-21
*/
@RestController
@RequestMapping("/api/admin/categories")
public class CategoryController {
private final CategoryService categoryService;
private final PostCategoryService postCategoryService;
public CategoryController(CategoryService categoryService,
PostCategoryService postCategoryService) {
this.categoryService = categoryService;
this.postCategoryService = postCategoryService;
}
@GetMapping("{categoryId:\\d+}")
@ApiOperation("Gets category detail")
public CategoryDTO getBy(@PathVariable("categoryId") Integer categoryId) {
return categoryService.convertTo(categoryService.getById(categoryId));
}
@GetMapping
@ApiOperation("Lists all categories")
public List<? extends CategoryDTO> listAll(
@SortDefault(sort = "priority", direction = ASC) Sort sort,
@RequestParam(name = "more", required = false, defaultValue = "false") boolean more) {
if (more) {
return postCategoryService.listCategoryWithPostCountDto(sort);
}
return categoryService.convertTo(categoryService.listAll(sort));
}
@GetMapping("tree_view")
@ApiOperation("List all categories as tree")
public List<CategoryVO> listAsTree(
@SortDefault(sort = "priority", direction = ASC) Sort sort) {
return categoryService.listAsTree(sort);
}
@PostMapping
@ApiOperation("Creates category")
public CategoryDTO createBy(@RequestBody @Valid CategoryParam categoryParam) {
// Convert to category
Category category = categoryParam.convertTo();
// Save it
return categoryService.convertTo(categoryService.create(category));
}
@PutMapping("{categoryId:\\d+}")
@ApiOperation("Updates category")
public CategoryDTO updateBy(@PathVariable("categoryId") Integer categoryId,
@RequestBody @Valid CategoryParam categoryParam
) {
Category categoryToUpdate = categoryService.getById(categoryId);
categoryParam.update(categoryToUpdate);
return categoryService.convertTo(categoryService.update(categoryToUpdate));
}
@PutMapping("/batch")
@ApiOperation("Updates category in batch")
public List<CategoryDTO> updateBatchBy(@RequestBody List<@Valid CategoryParam> categoryParams) {
List<Category> categoriesToUpdate = categoryParams.stream()
.filter(categoryParam -> Objects.nonNull(categoryParam.getId()))
.map(categoryParam -> {
Category categoryToUpdate = categoryService.getById(categoryParam.getId());
categoryParam.update(categoryToUpdate);
return categoryToUpdate;
})
.collect(Collectors.toList());
return categoryService.convertTo(categoryService.updateInBatch(categoriesToUpdate));
}
@DeleteMapping("{categoryId:\\d+}")
@ApiOperation("Deletes category")
public void deletePermanently(@PathVariable("categoryId") Integer categoryId) {
categoryService.removeCategoryAndPostCategoryBy(categoryId);
}
}

View File

@ -1,304 +0,0 @@
package run.halo.app.controller.admin.api;
import io.swagger.annotations.ApiOperation;
import java.util.Collections;
import java.util.HashMap;
import java.util.HashSet;
import java.util.Map;
import java.util.Set;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.codec.digest.DigestUtils;
import org.apache.commons.lang3.StringUtils;
import org.springframework.context.ApplicationEventPublisher;
import org.springframework.lang.Nullable;
import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RequestBody;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.ResponseBody;
import run.halo.app.cache.lock.CacheLock;
import run.halo.app.event.logger.LogEvent;
import run.halo.app.exception.BadRequestException;
import run.halo.app.model.entity.Category;
import run.halo.app.model.entity.PostComment;
import run.halo.app.model.entity.User;
import run.halo.app.model.enums.LogType;
import run.halo.app.model.enums.PostStatus;
import run.halo.app.model.params.CategoryParam;
import run.halo.app.model.params.InstallParam;
import run.halo.app.model.params.MenuParam;
import run.halo.app.model.params.PostParam;
import run.halo.app.model.params.SheetParam;
import run.halo.app.model.properties.BlogProperties;
import run.halo.app.model.properties.OtherProperties;
import run.halo.app.model.properties.PrimaryProperties;
import run.halo.app.model.properties.PropertyEnum;
import run.halo.app.model.support.BaseResponse;
import run.halo.app.model.support.CreateCheck;
import run.halo.app.model.vo.PostDetailVO;
import run.halo.app.service.CategoryService;
import run.halo.app.service.MenuService;
import run.halo.app.service.OptionService;
import run.halo.app.service.PostCommentService;
import run.halo.app.service.PostService;
import run.halo.app.service.SheetService;
import run.halo.app.service.UserService;
import run.halo.app.utils.ValidationUtils;
/**
* Installation controller.
*
* @author ryanwang
* @date 2019-03-17
*/
@Slf4j
@Controller
@RequestMapping("/api/admin/installations")
public class InstallController {
private final UserService userService;
private final CategoryService categoryService;
private final PostService postService;
private final SheetService sheetService;
private final PostCommentService postCommentService;
private final OptionService optionService;
private final MenuService menuService;
private final ApplicationEventPublisher eventPublisher;
public InstallController(UserService userService,
CategoryService categoryService,
PostService postService,
SheetService sheetService,
PostCommentService postCommentService,
OptionService optionService,
MenuService menuService,
ApplicationEventPublisher eventPublisher) {
this.userService = userService;
this.categoryService = categoryService;
this.postService = postService;
this.sheetService = sheetService;
this.postCommentService = postCommentService;
this.optionService = optionService;
this.menuService = menuService;
this.eventPublisher = eventPublisher;
}
@PostMapping
@ResponseBody
@CacheLock
@ApiOperation("Initializes the blog")
public BaseResponse<String> installBlog(@RequestBody InstallParam installParam) {
// Validate manually
ValidationUtils.validate(installParam, CreateCheck.class);
// Check is installed
boolean isInstalled = optionService
.getByPropertyOrDefault(PrimaryProperties.IS_INSTALLED, Boolean.class, false);
if (isInstalled) {
throw new BadRequestException("该博客已初始化,不能再次安装!");
}
// Initialize settings
initSettings(installParam);
// Create default user
User user = createUser(installParam);
// Create default category
Category category = createDefaultCategoryIfAbsent();
// Create default post
PostDetailVO post = createDefaultPostIfAbsent(category);
// Create default sheet
createDefaultSheet();
// Create default postComment
createDefaultComment(post);
// Create default menu
createDefaultMenu();
eventPublisher.publishEvent(
new LogEvent(this, user.getId().toString(), LogType.BLOG_INITIALIZED, "博客已成功初始化")
);
return BaseResponse.ok("安装完成!");
}
private void createDefaultMenu() {
long menuCount = menuService.count();
if (menuCount > 0) {
return;
}
MenuParam menuIndex = new MenuParam();
menuIndex.setName("首页");
menuIndex.setUrl("/");
menuIndex.setPriority(1);
menuService.create(menuIndex.convertTo());
MenuParam menuArchive = new MenuParam();
menuArchive.setName("文章归档");
menuArchive.setUrl("/archives");
menuArchive.setPriority(2);
menuService.create(menuArchive.convertTo());
MenuParam menuCategory = new MenuParam();
menuCategory.setName("默认分类");
menuCategory.setUrl("/categories/default");
menuCategory.setPriority(3);
menuService.create(menuCategory.convertTo());
MenuParam menuSheet = new MenuParam();
menuSheet.setName("关于页面");
menuSheet.setUrl("/s/about");
menuSheet.setPriority(4);
menuService.create(menuSheet.convertTo());
}
@Nullable
private void createDefaultComment(@Nullable PostDetailVO post) {
if (post == null) {
return;
}
long commentCount = postCommentService.count();
if (commentCount > 0) {
return;
}
PostComment comment = new PostComment();
comment.setAuthor("Halo");
comment.setAuthorUrl("https://halo.run");
comment.setContent(
"欢迎使用 Halo这是你的第一条评论头像来自 [Gravatar](https://cn.gravatar.com)"
+ "你也可以通过注册 [Gravatar]"
+ "(https://cn.gravatar.com) 来显示自己的头像。");
comment.setEmail("hi@halo.run");
comment.setPostId(post.getId());
postCommentService.create(comment);
}
@Nullable
private PostDetailVO createDefaultPostIfAbsent(@Nullable Category category) {
long publishedCount = postService.countByStatus(PostStatus.PUBLISHED);
if (publishedCount > 0) {
return null;
}
PostParam postParam = new PostParam();
postParam.setSlug("hello-halo");
postParam.setTitle("Hello Halo");
postParam.setStatus(PostStatus.PUBLISHED);
postParam.setOriginalContent("## Hello Halo\n"
+ "\n"
+ "如果你看到了这一篇文章,那么证明你已经安装成功了,感谢使用 [Halo](https://halo.run) 进行创作,希望能够使用愉快。\n"
+ "\n"
+ "## 相关链接\n"
+ "\n"
+ "- 官网:[https://halo.run](https://halo.run)\n"
+ "- 文档:[https://docs.halo.run](https://docs.halo.run)\n"
+ "- 社区:[https://bbs.halo.run](https://bbs.halo.run)\n"
+ "- 主题仓库:[https://halo.run/themes.html](https://halo.run/themes.html)\n"
+ "- 开源地址:[https://github.com/halo-dev/halo](https://github.com/halo-dev/halo)\n"
+ "\n"
+ "在使用过程中,有任何问题都可以通过以上链接找寻答案,或者联系我们。\n"
+ "\n"
+ "> 这是一篇自动生成的文章,请删除这篇文章之后开始你的创作吧!\n"
+ "\n");
Set<Integer> categoryIds = new HashSet<>();
if (category != null) {
categoryIds.add(category.getId());
postParam.setCategoryIds(categoryIds);
}
return postService
.createBy(postParam.convertTo(), Collections.emptySet(), categoryIds, false);
}
@Nullable
private void createDefaultSheet() {
long publishedCount = sheetService.countByStatus(PostStatus.PUBLISHED);
if (publishedCount > 0) {
return;
}
SheetParam sheetParam = new SheetParam();
sheetParam.setSlug("about");
sheetParam.setTitle("关于页面");
sheetParam.setStatus(PostStatus.PUBLISHED);
sheetParam.setOriginalContent("## 关于页面\n"
+ "\n"
+ "这是一个自定义页面,你可以在后台的 `页面` -> `所有页面` -> `自定义页面` 找到它,"
+ "你可以用于新建关于页面、留言板页面等等。发挥你自己的想象力!\n"
+ "\n"
+ "> 这是一篇自动生成的页面,你可以在后台删除它。");
sheetService.createBy(sheetParam.convertTo(), false);
}
@Nullable
private Category createDefaultCategoryIfAbsent() {
long categoryCount = categoryService.count();
if (categoryCount > 0) {
return null;
}
CategoryParam category = new CategoryParam();
category.setName("默认分类");
category.setSlug("default");
category.setDescription("这是你的默认分类,如不需要,删除即可。");
ValidationUtils.validate(category);
return categoryService.create(category.convertTo());
}
private User createUser(InstallParam installParam) {
// Get user
return userService.getCurrentUser().map(user -> {
// Update this user
installParam.update(user);
// Set password manually
userService.setPassword(user, installParam.getPassword());
// Update user
return userService.update(user);
}).orElseGet(() -> {
String gravatar =
"//cn.gravatar.com/avatar/" + DigestUtils.md5Hex(installParam.getEmail())
+ "?s=256&d=mm";
installParam.setAvatar(gravatar);
return userService.createBy(installParam);
});
}
private void initSettings(InstallParam installParam) {
// Init default properties
Map<PropertyEnum, String> properties = new HashMap<>(11);
properties.put(PrimaryProperties.IS_INSTALLED, Boolean.TRUE.toString());
properties.put(BlogProperties.BLOG_LOCALE, installParam.getLocale());
properties.put(BlogProperties.BLOG_TITLE, installParam.getTitle());
properties.put(BlogProperties.BLOG_URL,
StringUtils.isBlank(installParam.getUrl()) ? optionService.getBlogBaseUrl() :
installParam.getUrl());
properties.put(OtherProperties.GLOBAL_ABSOLUTE_PATH_ENABLED, Boolean.FALSE.toString());
// Create properties
optionService.saveProperties(properties);
}
}

View File

@ -1,126 +0,0 @@
package run.halo.app.controller.admin.api;
import static org.springframework.data.domain.Sort.Direction.DESC;
import io.swagger.annotations.ApiOperation;
import java.util.List;
import org.springframework.data.domain.Page;
import org.springframework.data.domain.PageRequest;
import org.springframework.data.domain.Pageable;
import org.springframework.data.domain.Sort;
import org.springframework.data.web.PageableDefault;
import org.springframework.data.web.SortDefault;
import org.springframework.web.bind.annotation.DeleteMapping;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.PathVariable;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.PutMapping;
import org.springframework.web.bind.annotation.RequestBody;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestParam;
import org.springframework.web.bind.annotation.RestController;
import run.halo.app.model.dto.BaseCommentDTO;
import run.halo.app.model.entity.JournalComment;
import run.halo.app.model.enums.CommentStatus;
import run.halo.app.model.params.CommentQuery;
import run.halo.app.model.params.JournalCommentParam;
import run.halo.app.model.vo.BaseCommentVO;
import run.halo.app.model.vo.BaseCommentWithParentVO;
import run.halo.app.model.vo.JournalCommentWithJournalVO;
import run.halo.app.service.JournalCommentService;
import run.halo.app.service.OptionService;
/**
* Journal comment controller.
*
* @author johnniang
* @author guqing
* @date 2019-04-25
*/
@RestController
@RequestMapping("/api/admin/journals/comments")
public class JournalCommentController {
private final JournalCommentService journalCommentService;
private final OptionService optionService;
public JournalCommentController(JournalCommentService journalCommentService,
OptionService optionService) {
this.journalCommentService = journalCommentService;
this.optionService = optionService;
}
@GetMapping
@ApiOperation("Lists journal comments")
public Page<JournalCommentWithJournalVO> pageBy(
@PageableDefault(sort = "createTime", direction = DESC) Pageable pageable,
CommentQuery commentQuery) {
Page<JournalComment> journalCommentPage =
journalCommentService.pageBy(commentQuery, pageable);
return journalCommentService.convertToWithJournalVo(journalCommentPage);
}
@GetMapping("latest")
@ApiOperation("Lists latest journal comments")
public List<JournalCommentWithJournalVO> listLatest(
@RequestParam(name = "top", defaultValue = "10") int top,
@RequestParam(name = "status", required = false) CommentStatus status) {
List<JournalComment> latestComments =
journalCommentService.pageLatest(top, status).getContent();
return journalCommentService.convertToWithJournalVo(latestComments);
}
@GetMapping("{journalId:\\d+}/tree_view")
@ApiOperation("Lists comments with tree view")
public Page<BaseCommentVO> listCommentTree(@PathVariable("journalId") Integer journalId,
@RequestParam(name = "page", required = false, defaultValue = "0") int page,
@SortDefault(sort = "createTime", direction = DESC) Sort sort) {
return journalCommentService.pageVosAllBy(journalId,
PageRequest.of(page, optionService.getCommentPageSize(), sort));
}
@GetMapping("{journalId:\\d+}/list_view")
@ApiOperation("Lists comment with list view")
public Page<BaseCommentWithParentVO> listComments(@PathVariable("journalId") Integer journalId,
@RequestParam(name = "page", required = false, defaultValue = "0") int page,
@SortDefault(sort = "createTime", direction = DESC) Sort sort) {
return journalCommentService.pageWithParentVoBy(journalId,
PageRequest.of(page, optionService.getCommentPageSize(), sort));
}
@PostMapping
@ApiOperation("Creates a journal comment")
public BaseCommentDTO createCommentBy(@RequestBody JournalCommentParam journalCommentParam) {
JournalComment journalComment = journalCommentService.createBy(journalCommentParam);
return journalCommentService.convertTo(journalComment);
}
@PutMapping("{commentId:\\d+}/status/{status}")
@ApiOperation("Updates comment status")
public BaseCommentDTO updateStatusBy(@PathVariable("commentId") Long commentId,
@PathVariable("status") CommentStatus status) {
// Update comment status
JournalComment updatedJournalComment =
journalCommentService.updateStatus(commentId, status);
return journalCommentService.convertTo(updatedJournalComment);
}
@PutMapping("/{commentId:\\d+}")
@ApiOperation("Updates a journal comment by comment id")
public BaseCommentDTO updateCommentBy(@PathVariable Long commentId,
@RequestBody JournalCommentParam journalCommentParam) {
JournalComment commentToUpdate = journalCommentService.getById(commentId);
journalCommentParam.update(commentToUpdate);
return journalCommentService.convertTo(journalCommentService.update(commentToUpdate));
}
@DeleteMapping("{commentId:\\d+}")
@ApiOperation("Deletes comment permanently and recursively")
public BaseCommentDTO deleteBy(@PathVariable("commentId") Long commentId) {
JournalComment deletedJournalComment = journalCommentService.removeById(commentId);
return journalCommentService.convertTo(deletedJournalComment);
}
}

View File

@ -1,84 +0,0 @@
package run.halo.app.controller.admin.api;
import static org.springframework.data.domain.Sort.Direction.DESC;
import io.swagger.annotations.ApiOperation;
import java.util.List;
import javax.validation.Valid;
import org.springframework.data.domain.Page;
import org.springframework.data.domain.Pageable;
import org.springframework.data.web.PageableDefault;
import org.springframework.web.bind.annotation.DeleteMapping;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.PathVariable;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.PutMapping;
import org.springframework.web.bind.annotation.RequestBody;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestParam;
import org.springframework.web.bind.annotation.RestController;
import run.halo.app.model.dto.JournalDTO;
import run.halo.app.model.dto.JournalWithCmtCountDTO;
import run.halo.app.model.entity.Journal;
import run.halo.app.model.params.JournalParam;
import run.halo.app.model.params.JournalQuery;
import run.halo.app.service.JournalService;
/**
* Journal controller.
*
* @author johnniang
* @author ryanwang
* @date 2019-04-25
*/
@RestController
@RequestMapping("/api/admin/journals")
public class JournalController {
private final JournalService journalService;
public JournalController(JournalService journalService) {
this.journalService = journalService;
}
@GetMapping
@ApiOperation("Lists journals")
public Page<JournalWithCmtCountDTO> pageBy(
@PageableDefault(sort = "createTime", direction = DESC) Pageable pageable,
JournalQuery journalQuery) {
Page<Journal> journalPage = journalService.pageBy(journalQuery, pageable);
return journalService.convertToCmtCountDto(journalPage);
}
@GetMapping("latest")
@ApiOperation("Gets latest journals")
public List<JournalWithCmtCountDTO> pageLatest(
@RequestParam(name = "top", defaultValue = "10") int top) {
List<Journal> journals = journalService.pageLatest(top).getContent();
return journalService.convertToCmtCountDto(journals);
}
@PostMapping
@ApiOperation("Creates a journal")
public JournalDTO createBy(@RequestBody @Valid JournalParam journalParam) {
Journal createdJournal = journalService.createBy(journalParam);
return journalService.convertTo(createdJournal);
}
@PutMapping("{id:\\d+}")
@ApiOperation("Updates a Journal")
public JournalDTO updateBy(@PathVariable("id") Integer id,
@RequestBody @Valid JournalParam journalParam) {
Journal journal = journalService.getById(id);
journalParam.update(journal);
Journal updatedJournal = journalService.updateBy(journal);
return journalService.convertTo(updatedJournal);
}
@DeleteMapping("{journalId:\\d+}")
@ApiOperation("Delete journal")
public JournalDTO deleteBy(@PathVariable("journalId") Integer journalId) {
Journal deletedJournal = journalService.removeById(journalId);
return journalService.convertTo(deletedJournal);
}
}

View File

@ -1,78 +0,0 @@
package run.halo.app.controller.admin.api;
import static org.springframework.data.domain.Sort.Direction.ASC;
import static org.springframework.data.domain.Sort.Direction.DESC;
import io.swagger.annotations.ApiOperation;
import java.util.List;
import javax.validation.Valid;
import org.springframework.data.domain.Sort;
import org.springframework.data.web.SortDefault;
import org.springframework.web.bind.annotation.DeleteMapping;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.PathVariable;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.PutMapping;
import org.springframework.web.bind.annotation.RequestBody;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
import run.halo.app.model.dto.LinkDTO;
import run.halo.app.model.entity.Link;
import run.halo.app.model.params.LinkParam;
import run.halo.app.service.LinkService;
/**
* Link Controller
*
* @author ryanwang
* @date 2019-03-21
*/
@RestController
@RequestMapping("/api/admin/links")
public class LinkController {
private final LinkService linkService;
public LinkController(LinkService linkService) {
this.linkService = linkService;
}
@GetMapping
@ApiOperation("Lists links")
public List<LinkDTO> listLinks(@SortDefault(sort = "team", direction = DESC) Sort sort) {
return linkService.listDtos(sort.and(Sort.by(ASC, "priority")));
}
@GetMapping("{id:\\d+}")
@ApiOperation("Gets link detail by id")
public LinkDTO getBy(@PathVariable("id") Integer id) {
return new LinkDTO().convertFrom(linkService.getById(id));
}
@PostMapping
@ApiOperation("Creates a link")
public LinkDTO createBy(@RequestBody @Valid LinkParam linkParam) {
Link link = linkService.createBy(linkParam);
return new LinkDTO().convertFrom(link);
}
@PutMapping("{id:\\d+}")
@ApiOperation("Updates a link")
public LinkDTO updateBy(@PathVariable("id") Integer id,
@RequestBody @Valid LinkParam linkParam) {
Link link = linkService.updateBy(id, linkParam);
return new LinkDTO().convertFrom(link);
}
@DeleteMapping("{id:\\d+}")
@ApiOperation("Delete link by id")
public void deletePermanently(@PathVariable("id") Integer id) {
linkService.removeById(id);
}
@GetMapping("teams")
@ApiOperation("Lists all link teams")
public List<String> teams() {
return linkService.listAllTeams();
}
}

View File

@ -1,53 +0,0 @@
package run.halo.app.controller.admin.api;
import static org.springframework.data.domain.Sort.Direction.DESC;
import io.swagger.annotations.ApiOperation;
import java.util.List;
import org.springframework.data.domain.Page;
import org.springframework.data.domain.Pageable;
import org.springframework.data.web.PageableDefault;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestParam;
import org.springframework.web.bind.annotation.RestController;
import run.halo.app.model.dto.LogDTO;
import run.halo.app.model.entity.Log;
import run.halo.app.service.LogService;
/**
* Log controller.
*
* @author johnniang
* @date 2019-03-19
*/
@RestController
@RequestMapping("/api/admin/logs")
public class LogController {
private final LogService logService;
public LogController(LogService logService) {
this.logService = logService;
}
@GetMapping("latest")
@ApiOperation("Pages latest logs")
public List<LogDTO> pageLatest(@RequestParam(name = "top", defaultValue = "10") int top) {
return logService.pageLatest(top).getContent();
}
@GetMapping
@ApiOperation("Lists logs")
public Page<LogDTO> pageBy(
@PageableDefault(sort = "createTime", direction = DESC) Pageable pageable) {
Page<Log> logPage = logService.listAll(pageable);
return logPage.map(log -> new LogDTO().convertFrom(log));
}
@GetMapping("clear")
@ApiOperation("Clears all logs")
public void clear() {
logService.removeAll();
}
}

View File

@ -1,46 +0,0 @@
package run.halo.app.controller.admin.api;
import io.swagger.annotations.ApiOperation;
import javax.validation.Valid;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RequestBody;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
import run.halo.app.annotation.DisableOnCondition;
import run.halo.app.mail.MailService;
import run.halo.app.model.params.MailParam;
import run.halo.app.model.support.BaseResponse;
/**
* Mail controller.
*
* @author johnniang
* @date 2019-05-07
*/
@RestController
@RequestMapping("/api/admin/mails")
public class MailController {
private final MailService mailService;
public MailController(MailService mailService) {
this.mailService = mailService;
}
@PostMapping("test")
@ApiOperation("Tests the SMTP service")
@DisableOnCondition
public BaseResponse<String> testMail(@Valid @RequestBody MailParam mailParam) {
mailService.sendTextMail(mailParam.getTo(), mailParam.getSubject(), mailParam.getContent());
return BaseResponse.ok("已发送,请查收。若确认没有收到邮件,请检查服务器日志");
}
@PostMapping("test/connection")
@ApiOperation("Test connection with email server")
@DisableOnCondition
public BaseResponse<String> testConnection() {
mailService.testConnection();
return BaseResponse.ok("您和邮箱服务器的连接通畅");
}
}

View File

@ -1,142 +0,0 @@
package run.halo.app.controller.admin.api;
import static org.springframework.data.domain.Sort.Direction.ASC;
import static org.springframework.data.domain.Sort.Direction.DESC;
import io.swagger.annotations.ApiOperation;
import java.util.List;
import java.util.Objects;
import java.util.stream.Collectors;
import javax.validation.Valid;
import org.springframework.data.domain.Sort;
import org.springframework.data.web.SortDefault;
import org.springframework.web.bind.annotation.DeleteMapping;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.PathVariable;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.PutMapping;
import org.springframework.web.bind.annotation.RequestBody;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestParam;
import org.springframework.web.bind.annotation.RestController;
import run.halo.app.model.dto.MenuDTO;
import run.halo.app.model.dto.base.InputConverter;
import run.halo.app.model.entity.Menu;
import run.halo.app.model.params.MenuParam;
import run.halo.app.model.vo.MenuVO;
import run.halo.app.service.MenuService;
/**
* Menu controller.
*
* @author johnniang
* @author ryanwang
* @date 2019-04-03
*/
@RestController
@RequestMapping("/api/admin/menus")
public class MenuController {
private final MenuService menuService;
public MenuController(MenuService menuService) {
this.menuService = menuService;
}
@GetMapping
@ApiOperation("Lists all menus")
public List<MenuDTO> listAll(@SortDefault(sort = "team", direction = DESC) Sort sort) {
return menuService.listDtos(sort.and(Sort.by(ASC, "priority")));
}
@GetMapping("tree_view")
@ApiOperation("Lists menus as tree")
public List<MenuVO> listAsTree(@SortDefault(sort = "team", direction = DESC) Sort sort) {
return menuService.listAsTree(sort.and(Sort.by(ASC, "priority")));
}
@GetMapping("team/tree_view")
@ApiOperation("Lists menus as tree by team")
public List<MenuVO> listDefaultsAsTreeByTeam(
@SortDefault(sort = "priority", direction = ASC) Sort sort,
@RequestParam(name = "team") String team) {
return menuService.listByTeamAsTree(team, sort);
}
@GetMapping("{menuId:\\d+}")
@ApiOperation("Gets menu detail by id")
public MenuDTO getBy(@PathVariable("menuId") Integer menuId) {
return new MenuDTO().convertFrom(menuService.getById(menuId));
}
@PostMapping
@ApiOperation("Creates a menu")
public MenuDTO createBy(@RequestBody @Valid MenuParam menuParam) {
return new MenuDTO().convertFrom(menuService.createBy(menuParam));
}
@PostMapping("/batch")
public List<MenuDTO> createBatchBy(@RequestBody @Valid List<MenuParam> menuParams) {
List<Menu> menus = menuParams
.stream()
.map(InputConverter::convertTo)
.collect(Collectors.toList());
return menuService.createInBatch(menus).stream()
.map(menu -> (MenuDTO) new MenuDTO().convertFrom(menu))
.collect(Collectors.toList());
}
@PutMapping("{menuId:\\d+}")
@ApiOperation("Updates a menu")
public MenuDTO updateBy(@PathVariable("menuId") Integer menuId,
@RequestBody @Valid MenuParam menuParam) {
// Get the menu
Menu menu = menuService.getById(menuId);
// Update changed properties of the menu
menuParam.update(menu);
// Update menu in database
return new MenuDTO().convertFrom(menuService.update(menu));
}
@PutMapping("/batch")
public List<MenuDTO> updateBatchBy(@RequestBody @Valid List<MenuParam> menuParams) {
List<Menu> menus = menuParams
.stream()
.filter(menuParam -> Objects.nonNull(menuParam.getId()))
.map(InputConverter::convertTo)
.collect(Collectors.toList());
return menuService.updateInBatch(menus).stream()
.map(menu -> (MenuDTO) new MenuDTO().convertFrom(menu))
.collect(Collectors.toList());
}
@DeleteMapping("{menuId:\\d+}")
@ApiOperation("Deletes a menu")
public MenuDTO deleteBy(@PathVariable("menuId") Integer menuId) {
List<Menu> menus = menuService.listByParentId(menuId);
if (null != menus && menus.size() > 0) {
menus.forEach(menu -> {
menu.setParentId(0);
menuService.update(menu);
});
}
return new MenuDTO().convertFrom(menuService.removeById(menuId));
}
@DeleteMapping("/batch")
public List<MenuDTO> deleteBatchBy(@RequestBody List<Integer> menuIds) {
List<Menu> menus = menuService.listAllByIds(menuIds);
menuService.removeInBatch(menuIds);
return menus.stream()
.map(menu -> (MenuDTO) new MenuDTO().convertFrom(menu))
.collect(Collectors.toList());
}
@GetMapping("teams")
@ApiOperation("Lists all menu teams")
public List<String> teams() {
return menuService.listAllTeams();
}
}

View File

@ -1,44 +0,0 @@
package run.halo.app.controller.admin.api;
import io.swagger.annotations.ApiOperation;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestPart;
import org.springframework.web.bind.annotation.RestController;
import org.springframework.web.multipart.MultipartFile;
import run.halo.app.exception.BadRequestException;
import run.halo.app.model.enums.MigrateType;
import run.halo.app.model.properties.PrimaryProperties;
import run.halo.app.service.MigrateService;
import run.halo.app.service.OptionService;
/**
* Migrate controller
*
* @author ryanwang
* @date 2019-10-29
*/
@RestController
@RequestMapping("/api/admin/migrations")
public class MigrateController {
private final MigrateService migrateService;
private final OptionService optionService;
public MigrateController(MigrateService migrateService,
OptionService optionService) {
this.migrateService = migrateService;
this.optionService = optionService;
}
@PostMapping("halo")
@ApiOperation("Migrate from Halo")
public void migrateHalo(@RequestPart("file") MultipartFile file) {
if (optionService.getByPropertyOrDefault(
PrimaryProperties.IS_INSTALLED, Boolean.class, false)) {
throw new BadRequestException("无法在博客初始化完成之后迁移数据");
}
migrateService.migrate(file, MigrateType.HALO);
}
}

View File

@ -1,114 +0,0 @@
package run.halo.app.controller.admin.api;
import static org.springframework.data.domain.Sort.Direction.DESC;
import io.swagger.annotations.ApiOperation;
import java.util.List;
import java.util.Map;
import javax.validation.Valid;
import org.springframework.data.domain.Page;
import org.springframework.data.domain.Pageable;
import org.springframework.data.web.PageableDefault;
import org.springframework.web.bind.annotation.DeleteMapping;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.PathVariable;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.PutMapping;
import org.springframework.web.bind.annotation.RequestBody;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
import run.halo.app.annotation.DisableOnCondition;
import run.halo.app.model.dto.OptionDTO;
import run.halo.app.model.dto.OptionSimpleDTO;
import run.halo.app.model.entity.Option;
import run.halo.app.model.params.OptionParam;
import run.halo.app.model.params.OptionQuery;
import run.halo.app.service.OptionService;
/**
* Option Controller.
*
* @author johnniang
* @author ryanwang
* @date 2019-03-20
*/
@RestController
@RequestMapping("/api/admin/options")
public class OptionController {
private final OptionService optionService;
public OptionController(OptionService optionService) {
this.optionService = optionService;
}
@GetMapping
@ApiOperation("Lists options")
public List<OptionDTO> listAll() {
return optionService.listDtos();
}
@PostMapping("saving")
@ApiOperation("Saves options")
@DisableOnCondition
public void saveOptions(@Valid @RequestBody List<OptionParam> optionParams) {
optionService.save(optionParams);
}
@GetMapping("map_view")
@ApiOperation("Lists all options with map view")
public Map<String, Object> listAllWithMapView() {
return optionService.listOptions();
}
@PostMapping("map_view/keys")
@ApiOperation("Lists options with map view by keys")
public Map<String, Object> listAllWithMapView(@RequestBody List<String> keys) {
return optionService.listOptions(keys);
}
@GetMapping("list_view")
@ApiOperation("Lists all options with list view")
public Page<OptionSimpleDTO> listAllWithListView(
@PageableDefault(sort = "updateTime", direction = DESC) Pageable pageable,
OptionQuery optionQuery) {
return optionService.pageDtosBy(pageable, optionQuery);
}
@GetMapping("{id:\\d+}")
@ApiOperation("Gets option detail by id")
public OptionSimpleDTO getBy(@PathVariable("id") Integer id) {
Option option = optionService.getById(id);
return optionService.convertToDto(option);
}
@PostMapping
@ApiOperation("Creates option")
@DisableOnCondition
public void createBy(@RequestBody @Valid OptionParam optionParam) {
optionService.save(optionParam);
}
@PutMapping("{optionId:\\d+}")
@ApiOperation("Updates option")
@DisableOnCondition
public void updateBy(@PathVariable("optionId") Integer optionId,
@RequestBody @Valid OptionParam optionParam) {
optionService.update(optionId, optionParam);
}
@DeleteMapping("{optionId:\\d+}")
@ApiOperation("Deletes option")
@DisableOnCondition
public void deletePermanently(@PathVariable("optionId") Integer optionId) {
optionService.removePermanently(optionId);
}
@PostMapping("map_view/saving")
@ApiOperation("Saves options by option map")
@DisableOnCondition
public void saveOptionsWithMapView(@RequestBody Map<String, Object> optionMap) {
optionService.save(optionMap);
}
}

View File

@ -1,144 +0,0 @@
package run.halo.app.controller.admin.api;
import static org.springframework.data.domain.Sort.Direction.DESC;
import io.swagger.annotations.ApiOperation;
import java.util.List;
import java.util.Objects;
import java.util.stream.Collectors;
import javax.validation.Valid;
import org.springframework.data.domain.Page;
import org.springframework.data.domain.Pageable;
import org.springframework.data.domain.Sort;
import org.springframework.data.web.PageableDefault;
import org.springframework.data.web.SortDefault;
import org.springframework.validation.annotation.Validated;
import org.springframework.web.bind.annotation.DeleteMapping;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.PathVariable;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.PutMapping;
import org.springframework.web.bind.annotation.RequestBody;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
import run.halo.app.cache.lock.CacheLock;
import run.halo.app.cache.lock.CacheParam;
import run.halo.app.model.dto.PhotoDTO;
import run.halo.app.model.entity.Photo;
import run.halo.app.model.params.PhotoParam;
import run.halo.app.model.params.PhotoQuery;
import run.halo.app.service.PhotoService;
/**
* Photo controller
*
* @author ryanwang
* @date 2019-03-21
*/
@Validated
@RestController
@RequestMapping("/api/admin/photos")
public class PhotoController {
private final PhotoService photoService;
public PhotoController(PhotoService photoService) {
this.photoService = photoService;
}
@GetMapping(value = "latest")
@ApiOperation("Lists latest photos")
public List<PhotoDTO> listPhotos(
@SortDefault(sort = "createTime", direction = Sort.Direction.DESC) Sort sort) {
return photoService.listDtos(sort);
}
@GetMapping
@ApiOperation("Lists photos")
public Page<PhotoDTO> pageBy(
@PageableDefault(sort = "createTime", direction = DESC) Pageable pageable,
PhotoQuery photoQuery) {
return photoService.pageDtosBy(pageable, photoQuery);
}
@GetMapping("{photoId:\\d+}")
@ApiOperation("Gets photo detail by id")
public PhotoDTO getBy(@PathVariable("photoId") Integer photoId) {
return new PhotoDTO().convertFrom(photoService.getById(photoId));
}
@DeleteMapping("{photoId:\\d+}")
@ApiOperation("Deletes photo by id")
public void deletePermanently(@PathVariable("photoId") Integer photoId) {
photoService.removeById(photoId);
}
@DeleteMapping("/batch")
@ApiOperation("Deletes photos permanently in batch by id array")
public List<PhotoDTO> deletePermanentlyInBatch(@RequestBody List<Integer> ids) {
return ids.stream().map(photoService::removeById)
.map(photo -> (PhotoDTO) new PhotoDTO().convertFrom(photo))
.collect(Collectors.toList());
}
@PostMapping
@ApiOperation("Creates a photo")
public PhotoDTO createBy(@Valid @RequestBody PhotoParam photoParam) {
return new PhotoDTO().convertFrom(photoService.createBy(photoParam));
}
@PostMapping("/batch")
@ApiOperation("Batch creation photos")
public List<PhotoDTO> createBatchBy(@RequestBody List<@Valid PhotoParam> photoParams) {
return photoParams.stream()
.map(photoParam -> {
PhotoDTO photoDto = new PhotoDTO();
photoDto.convertFrom(photoService.createBy(photoParam));
return photoDto;
})
.collect(Collectors.toList());
}
@PutMapping("{photoId:\\d+}")
@ApiOperation("Updates a photo")
public PhotoDTO updateBy(@PathVariable("photoId") Integer photoId,
@RequestBody @Valid PhotoParam photoParam) {
// Get the photo
Photo photo = photoService.getById(photoId);
// Update changed properties of the photo
photoParam.update(photo);
// Update menu in database
return new PhotoDTO().convertFrom(photoService.update(photo));
}
@PutMapping("/batch")
@ApiOperation("Updates photo in batch")
public List<PhotoDTO> updateBatchBy(@RequestBody List<@Valid PhotoParam> photoParams) {
List<Photo> photosToUpdate = photoParams.stream()
.filter(photoParam -> Objects.nonNull(photoParam.getId()))
.map(photoParam -> {
Photo photoToUpdate = photoService.getById(photoParam.getId());
photoParam.update(photoToUpdate);
return photoToUpdate;
})
.collect(Collectors.toList());
return photoService.updateInBatch(photosToUpdate).stream()
.map(photo -> (PhotoDTO) new PhotoDTO().convertFrom(photo))
.collect(Collectors.toList());
}
@PutMapping("{photoId:\\d+}/likes")
@ApiOperation("Likes a photo")
@CacheLock(autoDelete = false, traceRequest = true)
public void likes(@PathVariable @CacheParam Integer photoId) {
photoService.increaseLike(photoId);
}
@GetMapping("teams")
@ApiOperation("Lists all of photo teams")
public List<String> listTeams() {
return photoService.listAllTeams();
}
}

View File

@ -1,149 +0,0 @@
package run.halo.app.controller.admin.api;
import static org.springframework.data.domain.Sort.Direction.DESC;
import io.swagger.annotations.ApiOperation;
import java.util.List;
import javax.validation.Valid;
import org.springframework.data.domain.Page;
import org.springframework.data.domain.PageRequest;
import org.springframework.data.domain.Pageable;
import org.springframework.data.domain.Sort;
import org.springframework.data.web.PageableDefault;
import org.springframework.data.web.SortDefault;
import org.springframework.web.bind.annotation.DeleteMapping;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.PathVariable;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.PutMapping;
import org.springframework.web.bind.annotation.RequestBody;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestParam;
import org.springframework.web.bind.annotation.RestController;
import run.halo.app.model.dto.BaseCommentDTO;
import run.halo.app.model.entity.PostComment;
import run.halo.app.model.enums.CommentStatus;
import run.halo.app.model.params.CommentQuery;
import run.halo.app.model.params.PostCommentParam;
import run.halo.app.model.vo.BaseCommentVO;
import run.halo.app.model.vo.BaseCommentWithParentVO;
import run.halo.app.model.vo.PostCommentWithPostVO;
import run.halo.app.service.OptionService;
import run.halo.app.service.PostCommentService;
/**
* Post comment controller.
*
* @author johnniang
* @author ryanwang
* @date 2019-03-29
*/
@RestController
@RequestMapping("/api/admin/posts/comments")
public class PostCommentController {
private final PostCommentService postCommentService;
private final OptionService optionService;
public PostCommentController(PostCommentService postCommentService,
OptionService optionService) {
this.postCommentService = postCommentService;
this.optionService = optionService;
}
@GetMapping
@ApiOperation("Lists post comments")
public Page<PostCommentWithPostVO> pageBy(
@PageableDefault(sort = "createTime", direction = DESC) Pageable pageable,
CommentQuery commentQuery) {
Page<PostComment> commentPage = postCommentService.pageBy(commentQuery, pageable);
return postCommentService.convertToWithPostVo(commentPage);
}
@GetMapping("latest")
@ApiOperation("Pages post latest comments")
public List<PostCommentWithPostVO> listLatest(
@RequestParam(name = "top", defaultValue = "10") int top,
@RequestParam(name = "status", required = false) CommentStatus status) {
// Get latest comment
List<PostComment> content = postCommentService.pageLatest(top, status).getContent();
// Convert and return
return postCommentService.convertToWithPostVo(content);
}
@GetMapping("{postId:\\d+}/tree_view")
@ApiOperation("Lists post comments with tree view")
public Page<BaseCommentVO> listCommentTree(@PathVariable("postId") Integer postId,
@RequestParam(name = "page", required = false, defaultValue = "0") int page,
@SortDefault(sort = "createTime", direction = DESC) Sort sort) {
return postCommentService
.pageVosAllBy(postId, PageRequest.of(page, optionService.getCommentPageSize(), sort));
}
@GetMapping("{postId:\\d+}/list_view")
@ApiOperation("Lists post comment with list view")
public Page<BaseCommentWithParentVO> listComments(@PathVariable("postId") Integer postId,
@RequestParam(name = "page", required = false, defaultValue = "0") int page,
@SortDefault(sort = "createTime", direction = DESC) Sort sort) {
return postCommentService.pageWithParentVoBy(postId,
PageRequest.of(page, optionService.getCommentPageSize(), sort));
}
@PostMapping
@ApiOperation("Creates a post comment (new or reply)")
public BaseCommentDTO createBy(@RequestBody PostCommentParam postCommentParam) {
PostComment createdPostComment = postCommentService.createBy(postCommentParam);
return postCommentService.convertTo(createdPostComment);
}
@PutMapping("{commentId:\\d+}/status/{status}")
@ApiOperation("Updates post comment status")
public BaseCommentDTO updateStatusBy(@PathVariable("commentId") Long commentId,
@PathVariable("status") CommentStatus status) {
// Update comment status
PostComment updatedPostComment = postCommentService.updateStatus(commentId, status);
return postCommentService.convertTo(updatedPostComment);
}
@PutMapping("status/{status}")
@ApiOperation("Updates post comment status in batch")
public List<BaseCommentDTO> updateStatusInBatch(
@PathVariable(name = "status") CommentStatus status,
@RequestBody List<Long> ids) {
List<PostComment> comments = postCommentService.updateStatusByIds(ids, status);
return postCommentService.convertTo(comments);
}
@DeleteMapping("{commentId:\\d+}")
@ApiOperation("Deletes post comment permanently and recursively")
public BaseCommentDTO deletePermanently(@PathVariable("commentId") Long commentId) {
PostComment deletedPostComment = postCommentService.removeById(commentId);
return postCommentService.convertTo(deletedPostComment);
}
@DeleteMapping
@ApiOperation("Delete post comments permanently in batch by id array")
public List<PostComment> deletePermanentlyInBatch(@RequestBody List<Long> ids) {
return postCommentService.removeByIds(ids);
}
@GetMapping("{commentId:\\d+}")
@ApiOperation("Gets a post comment by comment id")
public PostCommentWithPostVO getBy(@PathVariable("commentId") Long commentId) {
PostComment comment = postCommentService.getById(commentId);
return postCommentService.convertToWithPostVo(comment);
}
@PutMapping("{commentId:\\d+}")
@ApiOperation("Updates a post comment")
public BaseCommentDTO updateBy(@Valid @RequestBody PostCommentParam commentParam,
@PathVariable("commentId") Long commentId) {
PostComment commentToUpdate = postCommentService.getById(commentId);
commentParam.update(commentToUpdate);
return postCommentService.convertTo(postCommentService.update(commentToUpdate));
}
}

View File

@ -1,215 +0,0 @@
package run.halo.app.controller.admin.api;
import static org.springframework.data.domain.Sort.Direction.DESC;
import io.swagger.annotations.ApiOperation;
import java.io.UnsupportedEncodingException;
import java.net.URISyntaxException;
import java.net.URLEncoder;
import java.nio.charset.StandardCharsets;
import java.util.List;
import java.util.concurrent.TimeUnit;
import javax.validation.Valid;
import org.apache.http.client.utils.URIBuilder;
import org.springframework.data.domain.Page;
import org.springframework.data.domain.Pageable;
import org.springframework.data.web.PageableDefault;
import org.springframework.web.bind.annotation.DeleteMapping;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.PathVariable;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.PutMapping;
import org.springframework.web.bind.annotation.RequestBody;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestParam;
import org.springframework.web.bind.annotation.RestController;
import run.halo.app.cache.AbstractStringCacheStore;
import run.halo.app.model.dto.post.BasePostDetailDTO;
import run.halo.app.model.dto.post.BasePostMinimalDTO;
import run.halo.app.model.dto.post.BasePostSimpleDTO;
import run.halo.app.model.entity.Post;
import run.halo.app.model.enums.PostStatus;
import run.halo.app.model.params.PostContentParam;
import run.halo.app.model.params.PostParam;
import run.halo.app.model.params.PostQuery;
import run.halo.app.model.vo.PostDetailVO;
import run.halo.app.service.OptionService;
import run.halo.app.service.PostService;
import run.halo.app.service.assembler.PostAssembler;
import run.halo.app.utils.HaloUtils;
/**
* Post controller.
*
* @author johnniang
* @author ryanwang
* @author guqing
* @date 2019-03-19
*/
@RestController
@RequestMapping("/api/admin/posts")
public class PostController {
private final PostService postService;
private final AbstractStringCacheStore cacheStore;
private final OptionService optionService;
private final PostAssembler postAssembler;
public PostController(PostService postService,
AbstractStringCacheStore cacheStore,
OptionService optionService,
PostAssembler postAssembler) {
this.postService = postService;
this.cacheStore = cacheStore;
this.optionService = optionService;
this.postAssembler = postAssembler;
}
@GetMapping
@ApiOperation("Lists posts")
public Page<? extends BasePostSimpleDTO> pageBy(
@PageableDefault(sort = {"topPriority", "createTime"}, direction = DESC) Pageable pageable,
PostQuery postQuery,
@RequestParam(value = "more", defaultValue = "true") Boolean more) {
Page<Post> postPage = postService.pageBy(postQuery, pageable);
if (more) {
return postAssembler.convertToListVo(postPage);
}
return postAssembler.convertToSimple(postPage);
}
@GetMapping("latest")
@ApiOperation("Pages latest post")
public List<BasePostMinimalDTO> pageLatest(
@RequestParam(name = "top", defaultValue = "10") int top) {
return postAssembler.convertToMinimal(postService.pageLatest(top).getContent());
}
@GetMapping("status/{status}")
@ApiOperation("Gets a page of post by post status")
public Page<? extends BasePostSimpleDTO> pageByStatus(
@PathVariable(name = "status") PostStatus status,
@RequestParam(value = "more", required = false, defaultValue = "false") Boolean more,
@PageableDefault(sort = "createTime", direction = DESC) Pageable pageable) {
Page<Post> posts = postService.pageBy(status, pageable);
if (more) {
return postAssembler.convertToListVo(posts);
}
return postAssembler.convertToSimple(posts);
}
@GetMapping("{postId:\\d+}")
@ApiOperation("Gets a post")
public PostDetailVO getBy(@PathVariable("postId") Integer postId) {
Post post = postService.getWithLatestContentById(postId);
return postAssembler.convertToDetailVo(post);
}
@PutMapping("{postId:\\d+}/likes")
@ApiOperation("Likes a post")
public void likes(@PathVariable("postId") Integer postId) {
postService.increaseLike(postId);
}
@PostMapping
@ApiOperation("Creates a post")
public PostDetailVO createBy(@Valid @RequestBody PostParam postParam,
@RequestParam(value = "autoSave", required = false, defaultValue = "false") Boolean autoSave
) {
// Convert to
Post post = postParam.convertTo();
return postService.createBy(post, postParam.getTagIds(), postParam.getCategoryIds(),
postParam.getPostMetas(), autoSave);
}
@PutMapping("{postId:\\d+}")
@ApiOperation("Updates a post")
public PostDetailVO updateBy(@Valid @RequestBody PostParam postParam,
@PathVariable("postId") Integer postId,
@RequestParam(value = "autoSave", required = false, defaultValue = "false") Boolean autoSave
) {
// Get the post info
Post postToUpdate = postService.getWithLatestContentById(postId);
postParam.update(postToUpdate);
return postService.updateBy(postToUpdate, postParam.getTagIds(), postParam.getCategoryIds(),
postParam.getPostMetas(), autoSave);
}
@PutMapping("{postId:\\d+}/status/{status}")
@ApiOperation("Updates post status")
public BasePostMinimalDTO updateStatusBy(
@PathVariable("postId") Integer postId,
@PathVariable("status") PostStatus status) {
Post post = postService.updateStatus(status, postId);
return new BasePostMinimalDTO().convertFrom(post);
}
@PutMapping("status/{status}")
@ApiOperation("Updates post status in batch")
public List<Post> updateStatusInBatch(@PathVariable(name = "status") PostStatus status,
@RequestBody List<Integer> ids) {
return postService.updateStatusByIds(ids, status);
}
@PutMapping("{postId:\\d+}/status/draft/content")
@ApiOperation("Updates draft")
public BasePostDetailDTO updateDraftBy(
@PathVariable("postId") Integer postId,
@RequestBody PostContentParam contentParam) {
Post postToUse = postService.getById(postId);
String formattedContent = contentParam.decideContentBy(postToUse.getEditorType());
// Update draft content
Post post = postService.updateDraftContent(formattedContent,
contentParam.getOriginalContent(), postId);
return postAssembler.convertToDetail(post);
}
@DeleteMapping("{postId:\\d+}")
@ApiOperation("Deletes a photo permanently")
public void deletePermanently(@PathVariable("postId") Integer postId) {
postService.removeById(postId);
}
@DeleteMapping
@ApiOperation("Deletes posts permanently in batch by id array")
public List<Post> deletePermanentlyInBatch(@RequestBody List<Integer> ids) {
return postService.removeByIds(ids);
}
@GetMapping(value = {"preview/{postId:\\d+}", "{postId:\\d+}/preview"})
@ApiOperation("Gets a post preview link")
public String preview(@PathVariable("postId") Integer postId)
throws UnsupportedEncodingException, URISyntaxException {
Post post = postService.getById(postId);
post.setSlug(URLEncoder.encode(post.getSlug(), StandardCharsets.UTF_8.name()));
BasePostMinimalDTO postMinimalDTO = postAssembler.convertToMinimal(post);
String token = HaloUtils.simpleUUID();
// cache preview token
cacheStore.putAny(token, token, 10, TimeUnit.MINUTES);
StringBuilder previewUrl = new StringBuilder();
if (!optionService.isEnabledAbsolutePath()) {
previewUrl.append(optionService.getBlogBaseUrl());
}
previewUrl.append(postMinimalDTO.getFullPath());
// build preview post url and return
return new URIBuilder(previewUrl.toString())
.addParameter("token", token)
.build().toString();
}
}

View File

@ -1,147 +0,0 @@
package run.halo.app.controller.admin.api;
import static org.springframework.data.domain.Sort.Direction.DESC;
import io.swagger.annotations.ApiOperation;
import java.util.List;
import javax.validation.Valid;
import org.springframework.data.domain.Page;
import org.springframework.data.domain.PageRequest;
import org.springframework.data.domain.Pageable;
import org.springframework.data.domain.Sort;
import org.springframework.data.web.PageableDefault;
import org.springframework.data.web.SortDefault;
import org.springframework.web.bind.annotation.DeleteMapping;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.PathVariable;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.PutMapping;
import org.springframework.web.bind.annotation.RequestBody;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestParam;
import org.springframework.web.bind.annotation.RestController;
import run.halo.app.model.dto.BaseCommentDTO;
import run.halo.app.model.entity.SheetComment;
import run.halo.app.model.enums.CommentStatus;
import run.halo.app.model.params.CommentQuery;
import run.halo.app.model.params.SheetCommentParam;
import run.halo.app.model.vo.BaseCommentVO;
import run.halo.app.model.vo.BaseCommentWithParentVO;
import run.halo.app.model.vo.SheetCommentWithSheetVO;
import run.halo.app.service.OptionService;
import run.halo.app.service.SheetCommentService;
/**
* Sheet comment controller.
*
* @author johnniang
* @author ryanwang
* @date 2019-04-25
*/
@RestController
@RequestMapping("/api/admin/sheets/comments")
public class SheetCommentController {
private final SheetCommentService sheetCommentService;
private final OptionService optionService;
public SheetCommentController(SheetCommentService sheetCommentService,
OptionService optionService) {
this.sheetCommentService = sheetCommentService;
this.optionService = optionService;
}
@GetMapping
@ApiOperation("Lists sheet comments")
public Page<SheetCommentWithSheetVO> pageBy(
@PageableDefault(sort = "createTime", direction = DESC) Pageable pageable,
CommentQuery commentQuery) {
Page<SheetComment> sheetCommentPage = sheetCommentService.pageBy(commentQuery, pageable);
return sheetCommentService.convertToWithSheetVo(sheetCommentPage);
}
@GetMapping("latest")
@ApiOperation("Lists latest sheet comments")
public List<SheetCommentWithSheetVO> listLatest(
@RequestParam(name = "top", defaultValue = "10") int top,
@RequestParam(name = "status", required = false) CommentStatus status) {
Page<SheetComment> sheetCommentPage = sheetCommentService.pageLatest(top, status);
return sheetCommentService.convertToWithSheetVo(sheetCommentPage.getContent());
}
@GetMapping("{sheetId:\\d+}/tree_view")
@ApiOperation("Lists sheet comments with tree view")
public Page<BaseCommentVO> listCommentTree(@PathVariable("sheetId") Integer sheetId,
@RequestParam(name = "page", required = false, defaultValue = "0") int page,
@SortDefault(sort = "createTime", direction = DESC) Sort sort) {
return sheetCommentService
.pageVosAllBy(sheetId, PageRequest.of(page, optionService.getCommentPageSize(), sort));
}
@GetMapping("{sheetId:\\d+}/list_view")
@ApiOperation("Lists sheet comment with list view")
public Page<BaseCommentWithParentVO> listComments(@PathVariable("sheetId") Integer sheetId,
@RequestParam(name = "page", required = false, defaultValue = "0") int page,
@SortDefault(sort = "createTime", direction = DESC) Sort sort) {
return sheetCommentService.pageWithParentVoBy(sheetId,
PageRequest.of(page, optionService.getCommentPageSize(), sort));
}
@PostMapping
@ApiOperation("Creates a sheet comment (new or reply)")
public BaseCommentDTO createBy(@RequestBody SheetCommentParam commentParam) {
SheetComment createdComment = sheetCommentService.createBy(commentParam);
return sheetCommentService.convertTo(createdComment);
}
@PutMapping("{commentId:\\d+}/status/{status}")
@ApiOperation("Updates sheet comment status")
public BaseCommentDTO updateStatusBy(@PathVariable("commentId") Long commentId,
@PathVariable("status") CommentStatus status) {
// Update comment status
SheetComment updatedSheetComment = sheetCommentService.updateStatus(commentId, status);
return sheetCommentService.convertTo(updatedSheetComment);
}
@PutMapping("status/{status}")
@ApiOperation("Updates sheet comment status in batch")
public List<BaseCommentDTO> updateStatusInBatch(
@PathVariable(name = "status") CommentStatus status,
@RequestBody List<Long> ids) {
List<SheetComment> comments = sheetCommentService.updateStatusByIds(ids, status);
return sheetCommentService.convertTo(comments);
}
@DeleteMapping("{commentId:\\d+}")
@ApiOperation("Deletes sheet comment permanently and recursively")
public BaseCommentDTO deletePermanently(@PathVariable("commentId") Long commentId) {
SheetComment deletedSheetComment = sheetCommentService.removeById(commentId);
return sheetCommentService.convertTo(deletedSheetComment);
}
@DeleteMapping
@ApiOperation("Deletes sheet comments permanently in batch by id array")
public List<SheetComment> deletePermanentlyInBatch(@RequestBody List<Long> ids) {
return sheetCommentService.removeByIds(ids);
}
@GetMapping("{commentId:\\d+}")
@ApiOperation("Gets a sheet comment by comment id")
public SheetCommentWithSheetVO getBy(@PathVariable("commentId") Long commentId) {
SheetComment comment = sheetCommentService.getById(commentId);
return sheetCommentService.convertToWithSheetVo(comment);
}
@PutMapping("{commentId:\\d+}")
@ApiOperation("Updates a sheet comment")
public BaseCommentDTO updateBy(@Valid @RequestBody SheetCommentParam commentParam,
@PathVariable("commentId") Long commentId) {
SheetComment commentToUpdate = sheetCommentService.getById(commentId);
commentParam.update(commentToUpdate);
return sheetCommentService.convertTo(sheetCommentService.update(commentToUpdate));
}
}

View File

@ -1,178 +0,0 @@
package run.halo.app.controller.admin.api;
import static org.springframework.data.domain.Sort.Direction.DESC;
import io.swagger.annotations.ApiOperation;
import java.io.UnsupportedEncodingException;
import java.net.URLEncoder;
import java.nio.charset.StandardCharsets;
import java.util.List;
import java.util.concurrent.TimeUnit;
import javax.validation.Valid;
import org.springframework.data.domain.Page;
import org.springframework.data.domain.Pageable;
import org.springframework.data.web.PageableDefault;
import org.springframework.web.bind.annotation.DeleteMapping;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.PathVariable;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.PutMapping;
import org.springframework.web.bind.annotation.RequestBody;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestParam;
import org.springframework.web.bind.annotation.RestController;
import run.halo.app.cache.AbstractStringCacheStore;
import run.halo.app.model.dto.IndependentSheetDTO;
import run.halo.app.model.dto.post.BasePostDetailDTO;
import run.halo.app.model.dto.post.BasePostMinimalDTO;
import run.halo.app.model.entity.Sheet;
import run.halo.app.model.enums.PostStatus;
import run.halo.app.model.params.PostContentParam;
import run.halo.app.model.params.SheetParam;
import run.halo.app.model.vo.SheetDetailVO;
import run.halo.app.model.vo.SheetListVO;
import run.halo.app.service.OptionService;
import run.halo.app.service.SheetService;
import run.halo.app.service.assembler.SheetAssembler;
import run.halo.app.utils.HaloUtils;
/**
* Sheet controller.
*
* @author johnniang
* @author ryanwang
* @date 19-4-24
*/
@RestController
@RequestMapping("/api/admin/sheets")
public class SheetController {
private final SheetService sheetService;
private final AbstractStringCacheStore cacheStore;
private final OptionService optionService;
private final SheetAssembler sheetAssembler;
public SheetController(SheetService sheetService,
AbstractStringCacheStore cacheStore,
OptionService optionService,
SheetAssembler sheetAssembler) {
this.sheetService = sheetService;
this.cacheStore = cacheStore;
this.optionService = optionService;
this.sheetAssembler = sheetAssembler;
}
@GetMapping("{sheetId:\\d+}")
@ApiOperation("Gets a sheet")
public SheetDetailVO getBy(@PathVariable("sheetId") Integer sheetId) {
Sheet sheet = sheetService.getWithLatestContentById(sheetId);
return sheetAssembler.convertToDetailVo(sheet);
}
@GetMapping
@ApiOperation("Gets a page of sheet")
public Page<SheetListVO> pageBy(
@PageableDefault(sort = "createTime", direction = DESC) Pageable pageable) {
Page<Sheet> sheetPage = sheetService.pageBy(pageable);
return sheetAssembler.convertToListVo(sheetPage);
}
@GetMapping("independent")
@ApiOperation("Lists independent sheets")
public List<IndependentSheetDTO> independentSheets() {
return sheetService.listIndependentSheets();
}
@PostMapping
@ApiOperation("Creates a sheet")
public SheetDetailVO createBy(@RequestBody @Valid SheetParam sheetParam,
@RequestParam(value = "autoSave", required = false, defaultValue = "false")
Boolean autoSave) {
Sheet sheet =
sheetService.createBy(sheetParam.convertTo(), sheetParam.getSheetMetas(), autoSave);
return sheetAssembler.convertToDetailVo(sheet);
}
@PutMapping("{sheetId:\\d+}")
@ApiOperation("Updates a sheet")
public SheetDetailVO updateBy(
@PathVariable("sheetId") Integer sheetId,
@RequestBody @Valid SheetParam sheetParam,
@RequestParam(value = "autoSave", required = false, defaultValue = "false")
Boolean autoSave) {
Sheet sheetToUpdate = sheetService.getWithLatestContentById(sheetId);
sheetParam.update(sheetToUpdate);
Sheet sheet = sheetService.updateBy(sheetToUpdate, sheetParam.getSheetMetas(), autoSave);
return sheetAssembler.convertToDetailVo(sheet);
}
@PutMapping("{sheetId:\\d+}/{status}")
@ApiOperation("Updates a sheet")
public void updateStatusBy(
@PathVariable("sheetId") Integer sheetId,
@PathVariable("status") PostStatus status) {
Sheet sheet = sheetService.getById(sheetId);
// Set status
sheet.setStatus(status);
// Update
sheetService.update(sheet);
}
@PutMapping("{sheetId:\\d+}/status/draft/content")
@ApiOperation("Updates draft")
public BasePostDetailDTO updateDraftBy(
@PathVariable("sheetId") Integer sheetId,
@RequestBody PostContentParam contentParam) {
Sheet sheetToUse = sheetService.getById(sheetId);
String formattedContent = contentParam.decideContentBy(sheetToUse.getEditorType());
// Update draft content
Sheet sheet = sheetService.updateDraftContent(formattedContent,
contentParam.getOriginalContent(), sheetId);
return sheetAssembler.convertToDetail(sheet);
}
@DeleteMapping("{sheetId:\\d+}")
@ApiOperation("Deletes a sheet")
public SheetDetailVO deleteBy(@PathVariable("sheetId") Integer sheetId) {
Sheet sheet = sheetService.removeById(sheetId);
return sheetAssembler.convertToDetailVo(sheet);
}
@GetMapping("preview/{sheetId:\\d+}")
@ApiOperation("Gets a sheet preview link")
public String preview(@PathVariable("sheetId") Integer sheetId)
throws UnsupportedEncodingException {
Sheet sheet = sheetService.getById(sheetId);
sheet.setSlug(URLEncoder.encode(sheet.getSlug(), StandardCharsets.UTF_8.name()));
BasePostMinimalDTO sheetMinimalDTO = sheetAssembler.convertToMinimal(sheet);
String token = HaloUtils.simpleUUID();
// cache preview token
cacheStore.putAny(token, token, 10, TimeUnit.MINUTES);
StringBuilder previewUrl = new StringBuilder();
if (!optionService.isEnabledAbsolutePath()) {
previewUrl.append(optionService.getBlogBaseUrl());
}
previewUrl.append(sheetMinimalDTO.getFullPath())
.append("?token=")
.append(token);
// build preview post url and return
return previewUrl.toString();
}
}

View File

@ -1,73 +0,0 @@
package run.halo.app.controller.admin.api;
import io.swagger.annotations.ApiOperation;
import java.util.List;
import org.springframework.web.bind.annotation.DeleteMapping;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.PutMapping;
import org.springframework.web.bind.annotation.RequestBody;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestParam;
import org.springframework.web.bind.annotation.RequestPart;
import org.springframework.web.bind.annotation.RestController;
import org.springframework.web.multipart.MultipartFile;
import run.halo.app.model.params.StaticContentParam;
import run.halo.app.model.support.StaticFile;
import run.halo.app.service.StaticStorageService;
/**
* Static storage controller.
*
* @author ryanwang
* @date 2019-12-06
*/
@RestController
@RequestMapping("/api/admin/statics")
public class StaticStorageController {
private final StaticStorageService staticStorageService;
public StaticStorageController(StaticStorageService staticStorageService) {
this.staticStorageService = staticStorageService;
}
@GetMapping
@ApiOperation("Lists static files")
public List<StaticFile> list() {
return staticStorageService.listStaticFolder();
}
@DeleteMapping
@ApiOperation("Deletes file by relative path")
public void deletePermanently(@RequestParam("path") String path) {
staticStorageService.delete(path);
}
@PostMapping
@ApiOperation("Creates a folder")
public void createFolder(String basePath,
@RequestParam("folderName") String folderName) {
staticStorageService.createFolder(basePath, folderName);
}
@PostMapping("upload")
@ApiOperation("Uploads static file")
public void upload(String basePath,
@RequestPart("file") MultipartFile file) {
staticStorageService.upload(basePath, file);
}
@PostMapping("rename")
@ApiOperation("Renames static file")
public void rename(String basePath,
String newName) {
staticStorageService.rename(basePath, newName);
}
@PutMapping("files")
@ApiOperation("Save static file")
public void save(@RequestBody StaticContentParam param) {
staticStorageService.save(param.getPath(), param.getContent());
}
}

View File

@ -1,38 +0,0 @@
package run.halo.app.controller.admin.api;
import io.swagger.annotations.ApiOperation;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
import run.halo.app.model.dto.StatisticDTO;
import run.halo.app.model.dto.StatisticWithUserDTO;
import run.halo.app.service.StatisticService;
/**
* Statistic controller.
*
* @author ryanwang
* @date 2019-12-16
*/
@RestController
@RequestMapping("/api/admin/statistics")
public class StatisticController {
private final StatisticService statisticService;
public StatisticController(StatisticService statisticService) {
this.statisticService = statisticService;
}
@GetMapping
@ApiOperation("Gets blog statistics.")
public StatisticDTO statistics() {
return statisticService.getStatistic();
}
@GetMapping("user")
@ApiOperation("Gets blog statistics with user")
public StatisticWithUserDTO statisticsWithUser() {
return statisticService.getStatisticWithUser();
}
}

View File

@ -1,100 +0,0 @@
package run.halo.app.controller.admin.api;
import io.swagger.annotations.ApiOperation;
import io.swagger.annotations.ApiParam;
import java.util.List;
import javax.validation.Valid;
import lombok.extern.slf4j.Slf4j;
import org.springframework.data.domain.Sort;
import org.springframework.data.web.SortDefault;
import org.springframework.web.bind.annotation.DeleteMapping;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.PathVariable;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.PutMapping;
import org.springframework.web.bind.annotation.RequestBody;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestParam;
import org.springframework.web.bind.annotation.RestController;
import run.halo.app.model.dto.TagDTO;
import run.halo.app.model.entity.Tag;
import run.halo.app.model.params.TagParam;
import run.halo.app.service.PostTagService;
import run.halo.app.service.TagService;
/**
* Tag controller.
*
* @author johnniang
* @date 3/20/19
*/
@Slf4j
@RestController
@RequestMapping("/api/admin/tags")
public class TagController {
private final TagService tagService;
private final PostTagService postTagService;
public TagController(TagService tagService,
PostTagService postTagService) {
this.tagService = tagService;
this.postTagService = postTagService;
}
@GetMapping
@ApiOperation("Lists tags")
public List<? extends TagDTO> listTags(
@SortDefault(sort = "createTime", direction = Sort.Direction.DESC) Sort sort,
@ApiParam("Return more information(post count) if it is set")
@RequestParam(name = "more", required = false, defaultValue = "false") Boolean more) {
if (more) {
return postTagService.listTagWithCountDtos(sort);
}
return tagService.convertTo(tagService.listAll(sort));
}
@PostMapping
@ApiOperation("Creates a tag")
public TagDTO createTag(@Valid @RequestBody TagParam tagParam) {
// Convert to tag
Tag tag = tagParam.convertTo();
log.debug("Tag to be created: [{}]", tag);
// Create and convert
return tagService.convertTo(tagService.create(tag));
}
@GetMapping("{tagId:\\d+}")
@ApiOperation("Gets tag detail by id")
public TagDTO getBy(@PathVariable("tagId") Integer tagId) {
return tagService.convertTo(tagService.getById(tagId));
}
@PutMapping("{tagId:\\d+}")
@ApiOperation("Updates a tag")
public TagDTO updateBy(@PathVariable("tagId") Integer tagId,
@Valid @RequestBody TagParam tagParam) {
// Get old tag
Tag tag = tagService.getById(tagId);
// Update tag
tagParam.update(tag);
// Update tag
return tagService.convertTo(tagService.update(tag));
}
@DeleteMapping("{tagId:\\d+}")
@ApiOperation("Deletes a tag")
public TagDTO deletePermanently(@PathVariable("tagId") Integer tagId) {
// Remove the tag
Tag deletedTag = tagService.removeById(tagId);
// Remove the post tag relationship
postTagService.removeByTagId(tagId);
return tagService.convertTo(deletedTag);
}
}

View File

@ -1,234 +0,0 @@
package run.halo.app.controller.admin.api;
import io.swagger.annotations.ApiOperation;
import java.util.List;
import java.util.Map;
import java.util.Set;
import org.springframework.http.HttpStatus;
import org.springframework.web.bind.annotation.DeleteMapping;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.PathVariable;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.PutMapping;
import org.springframework.web.bind.annotation.RequestBody;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestParam;
import org.springframework.web.bind.annotation.RequestPart;
import org.springframework.web.bind.annotation.RestController;
import org.springframework.web.multipart.MultipartFile;
import run.halo.app.annotation.DisableOnCondition;
import run.halo.app.cache.lock.CacheLock;
import run.halo.app.handler.theme.config.support.Group;
import run.halo.app.handler.theme.config.support.Item;
import run.halo.app.handler.theme.config.support.ThemeProperty;
import run.halo.app.model.params.ThemeContentParam;
import run.halo.app.model.support.BaseResponse;
import run.halo.app.model.support.ThemeFile;
import run.halo.app.service.ThemeService;
import run.halo.app.service.ThemeSettingService;
import run.halo.app.utils.ServiceUtils;
/**
* Theme controller.
*
* @author ryanwang
* @author guqing
* @date 2019-03-20
*/
@RestController
@RequestMapping("/api/admin/themes")
public class ThemeController {
private final ThemeService themeService;
private final ThemeSettingService themeSettingService;
public ThemeController(ThemeService themeService,
ThemeSettingService themeSettingService) {
this.themeService = themeService;
this.themeSettingService = themeSettingService;
}
@GetMapping("{themeId:.+}")
@ApiOperation("Gets theme property by theme id")
public ThemeProperty getBy(@PathVariable("themeId") String themeId) {
return themeService.getThemeOfNonNullBy(themeId);
}
@GetMapping
@ApiOperation("Lists all themes")
public List<ThemeProperty> listAll() {
return themeService.getThemes();
}
@GetMapping("activation/files")
@ApiOperation("Lists all activate theme files")
public List<ThemeFile> listFiles() {
return themeService.listThemeFolderBy(themeService.getActivatedThemeId());
}
@GetMapping("{themeId:.+}/files")
@ApiOperation("Lists theme files by theme id")
public List<ThemeFile> listFiles(@PathVariable("themeId") String themeId) {
return themeService.listThemeFolderBy(themeId);
}
@GetMapping("files/content")
@ApiOperation("Gets template content")
public BaseResponse<String> getContentBy(@RequestParam(name = "path") String path) {
return BaseResponse
.ok(HttpStatus.OK.getReasonPhrase(), themeService.getTemplateContent(path));
}
@GetMapping("{themeId:.+}/files/content")
@ApiOperation("Gets template content by theme id")
public BaseResponse<String> getContentBy(@PathVariable("themeId") String themeId,
@RequestParam(name = "path") String path) {
return BaseResponse
.ok(HttpStatus.OK.getReasonPhrase(), themeService.getTemplateContent(themeId, path));
}
@PutMapping("files/content")
@ApiOperation("Updates template content")
@DisableOnCondition
public void updateContentBy(@RequestBody ThemeContentParam param) {
themeService.saveTemplateContent(param.getPath(), param.getContent());
}
@PutMapping("{themeId:.+}/files/content")
@ApiOperation("Updates template content by theme id")
@DisableOnCondition
public void updateContentBy(@PathVariable("themeId") String themeId,
@RequestBody ThemeContentParam param) {
themeService.saveTemplateContent(themeId, param.getPath(), param.getContent());
}
@GetMapping("activation/template/custom/sheet")
@ApiOperation("Gets custom sheet templates")
public List<String> customSheetTemplate() {
return themeService.listCustomTemplates(themeService.getActivatedThemeId(),
ThemeService.CUSTOM_SHEET_PREFIX);
}
@GetMapping("activation/template/custom/post")
@ApiOperation("Gets custom post templates")
public List<String> customPostTemplate() {
return themeService.listCustomTemplates(themeService.getActivatedThemeId(),
ThemeService.CUSTOM_POST_PREFIX);
}
@PostMapping("{themeId:.+}/activation")
@ApiOperation("Activates a theme")
public ThemeProperty active(@PathVariable("themeId") String themeId) {
return themeService.activateTheme(themeId);
}
@GetMapping("activation")
@ApiOperation("Gets activate theme")
public ThemeProperty getActivateTheme() {
return themeService.getThemeOfNonNullBy(themeService.getActivatedThemeId());
}
@GetMapping("activation/configurations")
@ApiOperation("Fetches activated theme configuration")
public BaseResponse<Object> fetchConfig() {
return BaseResponse.ok(themeService.fetchConfig(themeService.getActivatedThemeId()));
}
@GetMapping("{themeId:.+}/configurations")
@ApiOperation("Fetches theme configuration by theme id")
public List<Group> fetchConfig(@PathVariable("themeId") String themeId) {
return themeService.fetchConfig(themeId);
}
@GetMapping("{themeId:.+}/configurations/groups/{group}")
@ApiOperation("Fetches theme configuration by theme id and group name")
public Set<Item> fetchConfigByGroup(@PathVariable("themeId") String themeId,
@PathVariable String group) {
return themeService.fetchConfigItemsBy(themeId, group);
}
@GetMapping("{themeId:.+}/configurations/groups")
@ApiOperation("Fetches theme configuration group names by theme id")
public Set<String> fetchConfigGroups(@PathVariable("themeId") String themeId) {
return ServiceUtils.fetchProperty(themeService.fetchConfig(themeId), Group::getName);
}
@GetMapping("activation/settings")
@ApiOperation("Lists activated theme settings")
public Map<String, Object> listSettingsBy() {
return themeSettingService.listAsMapBy(themeService.getActivatedThemeId());
}
@GetMapping("{themeId:.+}/settings")
@ApiOperation("Lists theme settings by theme id")
public Map<String, Object> listSettingsBy(@PathVariable("themeId") String themeId) {
return themeSettingService.listAsMapBy(themeId);
}
@GetMapping("{themeId:.+}/groups/{group}/settings")
@ApiOperation("Lists theme settings by theme id and group name")
public Map<String, Object> listSettingsBy(@PathVariable("themeId") String themeId,
@PathVariable String group) {
return themeSettingService.listAsMapBy(themeId, group);
}
@PostMapping("activation/settings")
@ApiOperation("Saves theme settings")
public void saveSettingsBy(@RequestBody Map<String, Object> settings) {
themeSettingService.save(settings, themeService.getActivatedThemeId());
}
@PostMapping("{themeId:.+}/settings")
@ApiOperation("Saves theme settings")
@CacheLock(prefix = "save_theme_setting_by_themeId")
public void saveSettingsBy(@PathVariable("themeId") String themeId,
@RequestBody Map<String, Object> settings) {
themeSettingService.save(settings, themeId);
}
@DeleteMapping("{themeId:.+}")
@ApiOperation("Deletes a theme")
@DisableOnCondition
public void deleteBy(@PathVariable("themeId") String themeId,
@RequestParam(value = "deleteSettings", defaultValue = "false") Boolean deleteSettings) {
themeService.deleteTheme(themeId, deleteSettings);
}
@PostMapping("upload")
@ApiOperation("Uploads a theme")
public ThemeProperty uploadTheme(@RequestPart("file") MultipartFile file) {
return themeService.upload(file);
}
@PutMapping("upload/{themeId:.+}")
@ApiOperation("Upgrades theme by file")
public ThemeProperty updateThemeByUpload(@PathVariable("themeId") String themeId,
@RequestPart("file") MultipartFile file) {
return themeService.update(themeId, file);
}
@PostMapping("fetching")
@ApiOperation("Fetches a new theme")
public ThemeProperty fetchTheme(@RequestParam("uri") String uri) {
return themeService.fetch(uri);
}
@PutMapping("fetching/{themeId:.+}")
@ApiOperation("Upgrades theme from remote")
public ThemeProperty updateThemeByFetching(@PathVariable("themeId") String themeId) {
return themeService.update(themeId);
}
@PostMapping("reload")
@ApiOperation("Reloads themes")
public void reload() {
themeService.reload();
}
@GetMapping(value = "activation/template/exists")
@ApiOperation("Determines if template exists")
public BaseResponse<Boolean> exists(@RequestParam(value = "template") String template) {
return BaseResponse.ok(themeService.templateExists(template));
}
}

View File

@ -1,121 +0,0 @@
package run.halo.app.controller.admin.api;
import io.swagger.annotations.ApiOperation;
import javax.validation.Valid;
import org.apache.commons.lang3.StringUtils;
import org.springframework.util.Base64Utils;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.PutMapping;
import org.springframework.web.bind.annotation.RequestBody;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
import run.halo.app.annotation.DisableOnCondition;
import run.halo.app.cache.lock.CacheLock;
import run.halo.app.exception.BadRequestException;
import run.halo.app.model.dto.UserDTO;
import run.halo.app.model.entity.User;
import run.halo.app.model.enums.MFAType;
import run.halo.app.model.params.MultiFactorAuthParam;
import run.halo.app.model.params.PasswordParam;
import run.halo.app.model.params.UserParam;
import run.halo.app.model.support.BaseResponse;
import run.halo.app.model.support.UpdateCheck;
import run.halo.app.model.vo.MultiFactorAuthVO;
import run.halo.app.service.UserService;
import run.halo.app.utils.HaloUtils;
import run.halo.app.utils.TwoFactorAuthUtils;
import run.halo.app.utils.ValidationUtils;
/**
* User controller.
*
* @author johnniang
* @date 2019-03-19
*/
@RestController
@RequestMapping("/api/admin/users")
public class UserController {
private final UserService userService;
public UserController(UserService userService) {
this.userService = userService;
}
@GetMapping("profiles")
@ApiOperation("Gets user profile")
public UserDTO getProfile(User user) {
return new UserDTO().convertFrom(user);
}
@PutMapping("profiles")
@ApiOperation("Updates user profile")
@DisableOnCondition
public UserDTO updateProfile(@RequestBody UserParam userParam, User user) {
// Validate the user param
ValidationUtils.validate(userParam, UpdateCheck.class);
// Update properties
userParam.update(user);
// Update user and convert to dto
return new UserDTO().convertFrom(userService.update(user));
}
@PutMapping("profiles/password")
@ApiOperation("Updates user's password")
@DisableOnCondition
public BaseResponse<String> updatePassword(@RequestBody @Valid PasswordParam passwordParam,
User user) {
userService.updatePassword(passwordParam.getOldPassword(), passwordParam.getNewPassword(),
user.getId());
return BaseResponse.ok("密码修改成功");
}
@PutMapping("mfa/generate")
@ApiOperation("Generate Multi-Factor Auth qr image")
@DisableOnCondition
public MultiFactorAuthVO generateMFAQrImage(
@RequestBody MultiFactorAuthParam multiFactorAuthParam, User user) {
if (MFAType.NONE == user.getMfaType()) {
if (MFAType.TFA_TOTP == multiFactorAuthParam.getMfaType()) {
String mfaKey = TwoFactorAuthUtils.generateTFAKey();
String optAuthUrl =
TwoFactorAuthUtils.generateOtpAuthUrl(user.getNickname(), mfaKey);
String qrImageBase64 = "data:image/png;base64,"
+ Base64Utils.encodeToString(
HaloUtils.generateQrCodeToPng(optAuthUrl, 128, 128));
return new MultiFactorAuthVO(qrImageBase64, optAuthUrl, mfaKey, MFAType.TFA_TOTP);
} else {
throw new BadRequestException("暂不支持的 MFA 认证的方式");
}
} else {
throw new BadRequestException("MFA 认证已启用,无需重复操作");
}
}
@PutMapping("mfa/update")
@ApiOperation("Updates user's Multi Factor Auth")
@CacheLock(autoDelete = false, prefix = "mfa")
@DisableOnCondition
public MultiFactorAuthVO updateMFAuth(
@RequestBody @Valid MultiFactorAuthParam multiFactorAuthParam, User user) {
if (StringUtils.isNotBlank(user.getMfaKey())
&& MFAType.useMFA(multiFactorAuthParam.getMfaType())) {
return new MultiFactorAuthVO(MFAType.TFA_TOTP);
} else if (StringUtils.isBlank(user.getMfaKey())
&& !MFAType.useMFA(multiFactorAuthParam.getMfaType())) {
return new MultiFactorAuthVO(MFAType.NONE);
} else {
final String mfaKey = StringUtils.isNotBlank(user.getMfaKey()) ? user.getMfaKey() :
multiFactorAuthParam.getMfaKey();
TwoFactorAuthUtils.validateTFACode(mfaKey, multiFactorAuthParam.getAuthcode());
}
// update MFA key
User updateUser = userService
.updateMFA(multiFactorAuthParam.getMfaType(), multiFactorAuthParam.getMfaKey(),
user.getId());
return new MultiFactorAuthVO(updateUser.getMfaType());
}
}

View File

@ -1,334 +0,0 @@
package run.halo.app.controller.content;
import static run.halo.app.model.support.HaloConst.POST_PASSWORD_TEMPLATE;
import static run.halo.app.model.support.HaloConst.SUFFIX_FTL;
import java.io.UnsupportedEncodingException;
import javax.servlet.http.HttpServletRequest;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.lang3.StringUtils;
import org.springframework.stereotype.Controller;
import org.springframework.ui.Model;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.PathVariable;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestParam;
import org.springframework.web.context.request.RequestContextHolder;
import org.springframework.web.context.request.ServletRequestAttributes;
import run.halo.app.cache.lock.CacheLock;
import run.halo.app.controller.content.auth.ContentAuthenticationManager;
import run.halo.app.controller.content.auth.ContentAuthenticationRequest;
import run.halo.app.controller.content.model.CategoryModel;
import run.halo.app.controller.content.model.JournalModel;
import run.halo.app.controller.content.model.LinkModel;
import run.halo.app.controller.content.model.PhotoModel;
import run.halo.app.controller.content.model.PostModel;
import run.halo.app.controller.content.model.SheetModel;
import run.halo.app.controller.content.model.TagModel;
import run.halo.app.exception.AuthenticationException;
import run.halo.app.exception.NotFoundException;
import run.halo.app.exception.UnsupportedException;
import run.halo.app.model.dto.CategoryDTO;
import run.halo.app.model.dto.post.BasePostMinimalDTO;
import run.halo.app.model.entity.Category;
import run.halo.app.model.entity.Post;
import run.halo.app.model.entity.Sheet;
import run.halo.app.model.enums.EncryptTypeEnum;
import run.halo.app.model.enums.PostPermalinkType;
import run.halo.app.model.enums.PostStatus;
import run.halo.app.model.enums.SheetPermalinkType;
import run.halo.app.service.CategoryService;
import run.halo.app.service.OptionService;
import run.halo.app.service.PostService;
import run.halo.app.service.SheetService;
import run.halo.app.service.ThemeService;
import run.halo.app.service.assembler.PostRenderAssembler;
/**
* @author ryanwang
* @author guqing
* @date 2020-01-07
*/
@Slf4j
@Controller
@RequestMapping
public class ContentContentController {
private final PostModel postModel;
private final SheetModel sheetModel;
private final CategoryModel categoryModel;
private final TagModel tagModel;
private final JournalModel journalModel;
private final PhotoModel photoModel;
private final LinkModel linkModel;
private final OptionService optionService;
private final PostService postService;
private final SheetService sheetService;
private final CategoryService categoryService;
private final ThemeService themeService;
private final PostRenderAssembler postRenderAssembler;
private final ContentAuthenticationManager providerManager;
public ContentContentController(PostModel postModel,
SheetModel sheetModel,
CategoryModel categoryModel,
TagModel tagModel,
JournalModel journalModel,
PhotoModel photoModel,
LinkModel linkModel,
OptionService optionService,
PostService postService,
SheetService sheetService,
CategoryService categoryService,
ThemeService themeService,
PostRenderAssembler postRenderAssembler,
ContentAuthenticationManager providerManager) {
this.postModel = postModel;
this.sheetModel = sheetModel;
this.categoryModel = categoryModel;
this.tagModel = tagModel;
this.journalModel = journalModel;
this.photoModel = photoModel;
this.linkModel = linkModel;
this.optionService = optionService;
this.postService = postService;
this.sheetService = sheetService;
this.categoryService = categoryService;
this.themeService = themeService;
this.postRenderAssembler = postRenderAssembler;
this.providerManager = providerManager;
}
@GetMapping("{prefix}")
public String content(@PathVariable("prefix") String prefix,
@RequestParam(value = "token", required = false) String token,
Model model) {
if (optionService.getArchivesPrefix().equals(prefix)) {
return postModel.archives(1, model);
}
if (optionService.getCategoriesPrefix().equals(prefix)) {
return categoryModel.list(model);
}
if (optionService.getTagsPrefix().equals(prefix)) {
return tagModel.list(model);
}
if (optionService.getJournalsPrefix().equals(prefix)) {
return journalModel.list(1, model);
}
if (optionService.getPhotosPrefix().equals(prefix)) {
return photoModel.list(1, model);
}
if (optionService.getLinksPrefix().equals(prefix)) {
return linkModel.list(model);
}
if (optionService.getSheetPermalinkType().equals(SheetPermalinkType.ROOT)) {
Sheet sheet = sheetService.getBySlug(prefix);
return sheetModel.content(sheet, token, model);
}
throw buildPathNotFoundException();
}
@GetMapping("{prefix}/page/{page:\\d+}")
public String content(@PathVariable("prefix") String prefix,
@PathVariable(value = "page") Integer page,
HttpServletRequest request,
Model model) {
if (optionService.getArchivesPrefix().equals(prefix)) {
return postModel.archives(page, model);
}
if (optionService.getJournalsPrefix().equals(prefix)) {
return journalModel.list(page, model);
}
if (optionService.getPhotosPrefix().equals(prefix)) {
return photoModel.list(page, model);
}
throw buildPathNotFoundException();
}
@GetMapping("{prefix}/{slug}")
public String content(@PathVariable("prefix") String prefix,
@PathVariable("slug") String slug,
@RequestParam(value = "token", required = false) String token,
Model model) {
PostPermalinkType postPermalinkType = optionService.getPostPermalinkType();
if (optionService.getArchivesPrefix().equals(prefix)) {
if (postPermalinkType.equals(PostPermalinkType.DEFAULT)) {
Post post = postService.getBySlug(slug);
return postModel.content(post, token, model);
}
if (postPermalinkType.equals(PostPermalinkType.ID_SLUG)
&& StringUtils.isNumeric(slug)) {
Post post = postService.getById(Integer.parseInt(slug));
return postModel.content(post, token, model);
}
}
if (optionService.getCategoriesPrefix().equals(prefix)) {
return categoryModel.listPost(model, slug, 1);
}
if (optionService.getTagsPrefix().equals(prefix)) {
return tagModel.listPost(model, slug, 1);
}
if (postPermalinkType.equals(PostPermalinkType.YEAR) && prefix.length() == 4
&& StringUtils.isNumeric(prefix)) {
Post post = postService.getBy(Integer.parseInt(prefix), slug);
return postModel.content(post, token, model);
}
if (optionService.getSheetPermalinkType().equals(SheetPermalinkType.SECONDARY)
&& optionService.getSheetPrefix().equals(prefix)) {
Sheet sheet = sheetService.getBySlug(slug);
return sheetModel.content(sheet, token, model);
}
throw buildPathNotFoundException();
}
@GetMapping("{prefix}/{slug}/page/{page:\\d+}")
public String content(@PathVariable("prefix") String prefix,
@PathVariable("slug") String slug,
@PathVariable("page") Integer page,
Model model) {
if (optionService.getCategoriesPrefix().equals(prefix)) {
return categoryModel.listPost(model, slug, page);
}
if (optionService.getTagsPrefix().equals(prefix)) {
return tagModel.listPost(model, slug, page);
}
throw buildPathNotFoundException();
}
@GetMapping("{year:\\d+}/{month:\\d+}/{slug}")
public String content(@PathVariable("year") Integer year,
@PathVariable("month") Integer month,
@PathVariable("slug") String slug,
@RequestParam(value = "token", required = false) String token,
Model model) {
PostPermalinkType postPermalinkType = optionService.getPostPermalinkType();
if (postPermalinkType.equals(PostPermalinkType.DATE)) {
Post post = postService.getBy(year, month, slug);
return postModel.content(post, token, model);
}
throw buildPathNotFoundException();
}
@GetMapping("{year:\\d+}/{month:\\d+}/{day:\\d+}/{slug}")
public String content(@PathVariable("year") Integer year,
@PathVariable("month") Integer month,
@PathVariable("day") Integer day,
@PathVariable("slug") String slug,
@RequestParam(value = "token", required = false) String token,
Model model) {
PostPermalinkType postPermalinkType = optionService.getPostPermalinkType();
if (postPermalinkType.equals(PostPermalinkType.DAY)) {
Post post = postService.getBy(year, month, day, slug);
return postModel.content(post, token, model);
}
throw buildPathNotFoundException();
}
@PostMapping(value = "content/{type}/{slug:.*}/authentication")
@CacheLock(traceRequest = true, expired = 2)
public String password(@PathVariable("type") String type,
@PathVariable("slug") String slug,
@RequestParam(value = "password") String password,
HttpServletRequest request) throws UnsupportedEncodingException {
if (EncryptTypeEnum.POST.getName().equals(type)) {
return authenticatePost(slug, type, password, request);
} else if (EncryptTypeEnum.CATEGORY.getName().equals(type)) {
return authenticateCategory(slug, type, password, request);
} else {
throw new UnsupportedException("未知的加密类型");
}
}
private String authenticatePost(String slug, String type, String password,
HttpServletRequest request) {
ContentAuthenticationRequest authRequest = new ContentAuthenticationRequest();
authRequest.setPassword(password);
Post post = postService.getBy(PostStatus.INTIMATE, slug);
authRequest.setId(post.getId());
authRequest.setPrincipal(EncryptTypeEnum.POST.getName());
try {
providerManager.authenticate(authRequest);
BasePostMinimalDTO basePostMinimal = postRenderAssembler.convertToMinimal(post);
return "redirect:" + buildRedirectUrl(basePostMinimal.getFullPath());
} catch (AuthenticationException e) {
request.setAttribute("errorMsg", e.getMessage());
request.setAttribute("type", type);
request.setAttribute("slug", slug);
return getPasswordPageUriToForward();
}
}
private String authenticateCategory(String slug, String type, String password,
HttpServletRequest request) {
ContentAuthenticationRequest authRequest = new ContentAuthenticationRequest();
authRequest.setPassword(password);
Category category = categoryService.getBySlugOfNonNull(slug);
authRequest.setId(category.getId());
authRequest.setPrincipal(EncryptTypeEnum.CATEGORY.getName());
try {
providerManager.authenticate(authRequest);
CategoryDTO categoryDto = categoryService.convertTo(category);
return "redirect:" + buildRedirectUrl(categoryDto.getFullPath());
} catch (AuthenticationException e) {
request.setAttribute("errorMsg", e.getMessage());
request.setAttribute("type", type);
request.setAttribute("slug", slug);
return getPasswordPageUriToForward();
}
}
private String getPasswordPageUriToForward() {
if (themeService.templateExists(POST_PASSWORD_TEMPLATE + SUFFIX_FTL)) {
return themeService.render(POST_PASSWORD_TEMPLATE);
}
return "common/template/" + POST_PASSWORD_TEMPLATE;
}
private NotFoundException buildPathNotFoundException() {
var requestAttributes = RequestContextHolder.currentRequestAttributes();
var requestUri = "";
if (requestAttributes instanceof ServletRequestAttributes) {
requestUri =
((ServletRequestAttributes) requestAttributes).getRequest().getRequestURI();
}
return new NotFoundException("无法定位到该路径:" + requestUri);
}
private String buildRedirectUrl(String fullPath) {
StringBuilder redirectUrl = new StringBuilder();
if (!optionService.isEnabledAbsolutePath()) {
redirectUrl.append(optionService.getBlogBaseUrl());
}
redirectUrl.append(fullPath);
return redirectUrl.toString();
}
}

View File

@ -1,306 +0,0 @@
package run.halo.app.controller.content;
import static org.springframework.data.domain.Sort.Direction.DESC;
import freemarker.template.Template;
import freemarker.template.TemplateException;
import java.io.IOException;
import java.sql.Timestamp;
import java.text.SimpleDateFormat;
import java.util.List;
import java.util.Locale;
import java.util.OptionalLong;
import javax.servlet.http.HttpServletResponse;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.lang3.RegExUtils;
import org.springframework.data.domain.Page;
import org.springframework.data.domain.PageRequest;
import org.springframework.data.domain.Pageable;
import org.springframework.data.domain.Sort;
import org.springframework.data.web.PageableDefault;
import org.springframework.http.MediaType;
import org.springframework.lang.NonNull;
import org.springframework.stereotype.Controller;
import org.springframework.ui.Model;
import org.springframework.ui.freemarker.FreeMarkerTemplateUtils;
import org.springframework.util.Assert;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.PathVariable;
import org.springframework.web.bind.annotation.ResponseBody;
import org.springframework.web.servlet.view.freemarker.FreeMarkerConfigurer;
import run.halo.app.model.dto.CategoryDTO;
import run.halo.app.model.entity.Category;
import run.halo.app.model.entity.Post;
import run.halo.app.model.enums.PostStatus;
import run.halo.app.model.vo.PostDetailVO;
import run.halo.app.service.CategoryService;
import run.halo.app.service.OptionService;
import run.halo.app.service.PostCategoryService;
import run.halo.app.service.PostService;
import run.halo.app.service.assembler.PostRenderAssembler;
/**
* @author ryanwang
* @author guqing
* @date 2019-03-21
*/
@Slf4j
@Controller
public class ContentFeedController {
private static final String UTF_8_SUFFIX = ";charset=UTF-8";
private static final String XML_INVALID_CHAR = "[\\x00-\\x1F\\x7F]";
private static final String XML_MEDIA_TYPE = MediaType.APPLICATION_XML_VALUE + UTF_8_SUFFIX;
private static final String LAST_MODIFIED_HEADER = "Last-Modified";
private final PostService postService;
private final PostRenderAssembler postRenderAssembler;
private final CategoryService categoryService;
private final PostCategoryService postCategoryService;
private final OptionService optionService;
private final FreeMarkerConfigurer freeMarker;
public ContentFeedController(PostService postService,
PostRenderAssembler postRenderAssembler, CategoryService categoryService,
PostCategoryService postCategoryService,
OptionService optionService,
FreeMarkerConfigurer freeMarker) {
this.postService = postService;
this.postRenderAssembler = postRenderAssembler;
this.categoryService = categoryService;
this.postCategoryService = postCategoryService;
this.optionService = optionService;
this.freeMarker = freeMarker;
}
/**
* Get post rss.
*
* @param model model
* @return rss xml content
* @throws IOException throw IOException
* @throws TemplateException throw TemplateException
*/
@GetMapping(value = {"feed", "feed.xml", "rss", "rss.xml"}, produces = XML_MEDIA_TYPE)
@ResponseBody
public String feed(Model model, HttpServletResponse response)
throws IOException, TemplateException {
List<PostDetailVO> posts = buildPosts(buildPostPageable(optionService.getRssPageSize()));
model.addAttribute("posts", posts);
Timestamp lastModified = this.getLastModifiedTime(posts);
this.lastModified2ResponseHeader(response, lastModified);
model.addAttribute("lastModified", lastModified);
Template template = freeMarker.getConfiguration().getTemplate("common/web/rss.ftl");
return FreeMarkerTemplateUtils.processTemplateIntoString(template, model);
}
/**
* Get category post rss.
*
* @param model model
* @param slug slug
* @return rss xml content
* @throws IOException throw IOException
* @throws TemplateException throw TemplateException
*/
@GetMapping(value = {"feed/categories/{slug}",
"feed/categories/{slug}.xml"}, produces = XML_MEDIA_TYPE)
@ResponseBody
public String feed(Model model, @PathVariable(name = "slug") String slug,
HttpServletResponse response)
throws IOException, TemplateException {
Category category = categoryService.getBySlugOfNonNull(slug);
CategoryDTO categoryDTO = categoryService.convertTo(category);
List<PostDetailVO> posts =
buildCategoryPosts(buildPostPageable(optionService.getRssPageSize()), categoryDTO);
model.addAttribute("category", categoryDTO);
model.addAttribute("posts", posts);
Timestamp lastModified = this.getLastModifiedTime(posts);
this.lastModified2ResponseHeader(response, lastModified);
model.addAttribute("lastModified", lastModified);
Template template = freeMarker.getConfiguration().getTemplate("common/web/rss.ftl");
return FreeMarkerTemplateUtils.processTemplateIntoString(template, model);
}
/**
* Get atom.xml
*
* @param model model
* @return atom xml content
* @throws IOException IOException
* @throws TemplateException TemplateException
*/
@GetMapping(value = {"atom", "atom.xml"}, produces = XML_MEDIA_TYPE)
@ResponseBody
public String atom(Model model, HttpServletResponse response)
throws IOException, TemplateException {
List<PostDetailVO> posts = buildPosts(buildPostPageable(optionService.getRssPageSize()));
model.addAttribute("posts", posts);
Timestamp lastModified = this.getLastModifiedTime(posts);
this.lastModified2ResponseHeader(response, lastModified);
model.addAttribute("lastModified", lastModified);
Template template = freeMarker.getConfiguration().getTemplate("common/web/atom.ftl");
return FreeMarkerTemplateUtils.processTemplateIntoString(template, model);
}
/**
* Get category posts atom.xml
*
* @param model model
* @param slug slug
* @return atom xml content
* @throws IOException throw IOException
* @throws TemplateException throw TemplateException
*/
@GetMapping(value = {"atom/categories/{slug}",
"atom/categories/{slug}.xml"}, produces = XML_MEDIA_TYPE)
@ResponseBody
public String atom(Model model, @PathVariable(name = "slug") String slug,
HttpServletResponse response)
throws IOException, TemplateException {
Category category = categoryService.getBySlugOfNonNull(slug);
CategoryDTO categoryDTO = categoryService.convertTo(category);
List<PostDetailVO> posts =
buildCategoryPosts(buildPostPageable(optionService.getRssPageSize()), categoryDTO);
model.addAttribute("category", categoryDTO);
model.addAttribute("posts", posts);
Timestamp lastModified = this.getLastModifiedTime(posts);
this.lastModified2ResponseHeader(response, lastModified);
model.addAttribute("lastModified", lastModified);
Template template = freeMarker.getConfiguration().getTemplate("common/web/atom.ftl");
return FreeMarkerTemplateUtils.processTemplateIntoString(template, model);
}
/**
* Get sitemap.xml.
*
* @param model model
* @return sitemap xml content.
* @throws IOException IOException
* @throws TemplateException TemplateException
*/
@GetMapping(value = {"sitemap", "sitemap.xml"}, produces = XML_MEDIA_TYPE)
@ResponseBody
public String sitemapXml(Model model,
@PageableDefault(size = Integer.MAX_VALUE, sort = "createTime", direction = DESC)
Pageable pageable) throws IOException, TemplateException {
model.addAttribute("posts", buildPosts(pageable));
Template template = freeMarker.getConfiguration().getTemplate("common/web/sitemap_xml.ftl");
return FreeMarkerTemplateUtils.processTemplateIntoString(template, model);
}
/**
* Get sitemap.html.
*
* @param model model
* @return template path: common/web/sitemap_html
*/
@GetMapping(value = "sitemap.html")
public String sitemapHtml(Model model,
@PageableDefault(size = Integer.MAX_VALUE, sort = "createTime", direction = DESC)
Pageable pageable) {
model.addAttribute("posts", buildPosts(pageable));
return "common/web/sitemap_html";
}
/**
* Get robots.txt
*
* @param model model
* @return robots.txt content
* @throws IOException IOException
* @throws TemplateException TemplateException
*/
@GetMapping(value = "robots.txt", produces = MediaType.TEXT_PLAIN_VALUE)
@ResponseBody
public String robots(Model model) throws IOException, TemplateException {
Template template = freeMarker.getConfiguration().getTemplate("common/web/robots.ftl");
return FreeMarkerTemplateUtils.processTemplateIntoString(template, model);
}
/**
* Builds page info for post.
*
* @param size page size
* @return page info
*/
@NonNull
private Pageable buildPostPageable(int size) {
return PageRequest.of(0, size, Sort.by(DESC, "createTime"));
}
/**
* Build posts.
*
* @param pageable pageable
* @return list of post detail vo
*/
private List<PostDetailVO> buildPosts(@NonNull Pageable pageable) {
Assert.notNull(pageable, "Pageable must not be null");
Page<Post> postPage = postService.pageBy(PostStatus.PUBLISHED, pageable);
Page<PostDetailVO> posts = convertToDetailPageVo(postPage);
return posts.getContent();
}
/**
* Converts to a page of detail vo.
* Notes: this method will escape the XML tag characters in the post content and summary.
*
* @param postPage post page must not be null
* @return a page of post detail vo that content and summary escaped.
*/
@NonNull
private Page<PostDetailVO> convertToDetailPageVo(Page<Post> postPage) {
Assert.notNull(postPage, "The postPage must not be null.");
Page<PostDetailVO> posts = postRenderAssembler.convertToDetailVo(postPage);
posts.getContent().forEach(postDetailVO -> {
postDetailVO.setContent(
RegExUtils.replaceAll(postDetailVO.getContent(), XML_INVALID_CHAR, ""));
postDetailVO
.setSummary(RegExUtils.replaceAll(postDetailVO.getSummary(), XML_INVALID_CHAR, ""));
});
return posts;
}
/**
* Build category posts.
*
* @param pageable pageable must not be null.
* @param category category
* @return list of post detail vo.
*/
private List<PostDetailVO> buildCategoryPosts(@NonNull Pageable pageable,
@NonNull CategoryDTO category) {
Assert.notNull(pageable, "Pageable must not be null");
Assert.notNull(category, "Category slug must not be null");
Page<Post> postPage =
postCategoryService.pagePostBy(category.getId(), PostStatus.PUBLISHED, pageable);
Page<PostDetailVO> posts = convertToDetailPageVo(postPage);
return posts.getContent();
}
private Timestamp getLastModifiedTime(List<PostDetailVO> posts) {
OptionalLong lastModifiedTimestamp =
posts.stream().mapToLong(post -> post.getEditTime().getTime()).max();
if (lastModifiedTimestamp.isEmpty()) {
return new Timestamp(System.currentTimeMillis());
}
return new Timestamp(lastModifiedTimestamp.getAsLong());
}
private void lastModified2ResponseHeader(HttpServletResponse response, Timestamp time) {
SimpleDateFormat dateFormat =
new SimpleDateFormat("EEE, dd MMM yyyy HH:mm:ss zzz", Locale.ENGLISH);
response.setHeader(LAST_MODIFIED_HEADER, dateFormat.format(time));
}
}

View File

@ -1,74 +0,0 @@
package run.halo.app.controller.content;
import java.util.Objects;
import lombok.extern.slf4j.Slf4j;
import org.springframework.stereotype.Controller;
import org.springframework.ui.Model;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.PathVariable;
import org.springframework.web.bind.annotation.RequestMapping;
import run.halo.app.controller.content.model.PostModel;
import run.halo.app.model.entity.Post;
import run.halo.app.model.enums.PostPermalinkType;
import run.halo.app.service.OptionService;
import run.halo.app.service.PostService;
/**
* Blog index page controller
*
* @author ryanwang
* @date 2019-03-17
*/
@Slf4j
@Controller
@RequestMapping
public class ContentIndexController {
private final PostService postService;
private final OptionService optionService;
private final PostModel postModel;
public ContentIndexController(PostService postService,
OptionService optionService,
PostModel postModel) {
this.postService = postService;
this.optionService = optionService;
this.postModel = postModel;
}
/**
* Render blog index
*
* @param p post id
* @param model model
* @return template path: themes/{theme}/index.ftl
*/
@GetMapping
public String index(Integer p, String token, Model model) {
PostPermalinkType permalinkType = optionService.getPostPermalinkType();
if (PostPermalinkType.ID.equals(permalinkType) && !Objects.isNull(p)) {
Post post = postService.getById(p);
return postModel.content(post, token, model);
}
return this.index(model, 1);
}
/**
* Render blog index
*
* @param model model
* @param page current page number
* @return template path: themes/{theme}/index.ftl
*/
@GetMapping(value = "page/{page}")
public String index(Model model,
@PathVariable(value = "page") Integer page) {
return postModel.list(page, model);
}
}

View File

@ -1,88 +0,0 @@
package run.halo.app.controller.content;
import static org.springframework.data.domain.Sort.Direction.DESC;
import org.springframework.data.domain.Page;
import org.springframework.data.domain.PageRequest;
import org.springframework.data.domain.Pageable;
import org.springframework.data.domain.Sort;
import org.springframework.data.web.SortDefault;
import org.springframework.stereotype.Controller;
import org.springframework.ui.Model;
import org.springframework.web.bind.annotation.GetMapping;
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.util.HtmlUtils;
import run.halo.app.model.entity.Post;
import run.halo.app.model.vo.PostListVO;
import run.halo.app.service.OptionService;
import run.halo.app.service.PostService;
import run.halo.app.service.ThemeService;
import run.halo.app.service.assembler.PostRenderAssembler;
/**
* Search controller.
*
* @author ryanwang
* @date 2019-04-21
*/
@Controller
@RequestMapping(value = "/search")
public class ContentSearchController {
private final PostService postService;
private final PostRenderAssembler postRenderAssembler;
private final OptionService optionService;
private final ThemeService themeService;
public ContentSearchController(PostService postService,
PostRenderAssembler postRenderAssembler, OptionService optionService,
ThemeService themeService) {
this.postService = postService;
this.postRenderAssembler = postRenderAssembler;
this.optionService = optionService;
this.themeService = themeService;
}
/**
* Render post search page.
*
* @param model model
* @param keyword keyword
* @return template path : themes/{theme}/search.ftl
*/
@GetMapping
public String search(Model model,
@RequestParam(value = "keyword") String keyword) {
return this.search(model, HtmlUtils.htmlEscape(keyword), 1, Sort.by(DESC, "createTime"));
}
/**
* Render post search page.
*
* @param model model
* @param keyword keyword
* @return template path :themes/{theme}/search.ftl
*/
@GetMapping(value = "page/{page}")
public String search(Model model,
@RequestParam(value = "keyword") String keyword,
@PathVariable(value = "page") Integer page,
@SortDefault(sort = "createTime", direction = DESC) Sort sort) {
final Pageable pageable = PageRequest.of(page - 1, optionService.getPostPageSize(), sort);
final Page<Post> postPage = postService.pageBy(keyword, pageable);
final Page<PostListVO> posts = postRenderAssembler.convertToListVo(postPage);
model.addAttribute("is_search", true);
model.addAttribute("keyword", HtmlUtils.htmlEscape(keyword));
model.addAttribute("posts", posts);
model.addAttribute("meta_keywords", optionService.getSeoKeywords());
model.addAttribute("meta_description", optionService.getSeoDescription());
return themeService.render("search");
}
}

View File

@ -1,98 +0,0 @@
package run.halo.app.controller.content;
import java.io.IOException;
import javax.servlet.http.HttpServletResponse;
import org.apache.commons.lang3.StringUtils;
import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.ResponseBody;
import run.halo.app.config.properties.HaloProperties;
import run.halo.app.exception.ServiceException;
import run.halo.app.model.entity.User;
import run.halo.app.model.properties.BlogProperties;
import run.halo.app.model.support.HaloConst;
import run.halo.app.service.OptionService;
import run.halo.app.service.UserService;
import run.halo.app.utils.HaloUtils;
/**
* Main controller.
*
* @author ryanwang
* @date 2019-04-23
*/
@Controller
public class MainController {
/**
* Index redirect uri.
*/
private static final String INDEX_REDIRECT_URI = "index.html";
/**
* Install redirect uri.
*/
private static final String INSTALL_REDIRECT_URI = INDEX_REDIRECT_URI + "#install";
private final UserService userService;
private final OptionService optionService;
private final HaloProperties haloProperties;
public MainController(UserService userService, OptionService optionService,
HaloProperties haloProperties) {
this.userService = userService;
this.optionService = optionService;
this.haloProperties = haloProperties;
}
@GetMapping("${halo.admin-path:admin}")
public void admin(HttpServletResponse response) throws IOException {
String adminIndexRedirectUri =
HaloUtils.ensureBoth(haloProperties.getAdminPath(), HaloUtils.URL_SEPARATOR)
+ INDEX_REDIRECT_URI;
response.sendRedirect(adminIndexRedirectUri);
}
@GetMapping("version")
@ResponseBody
public String version() {
return HaloConst.HALO_VERSION;
}
@GetMapping("install")
public void installation(HttpServletResponse response) throws IOException {
String installRedirectUri =
StringUtils.appendIfMissing(this.haloProperties.getAdminPath(), "/")
+ INSTALL_REDIRECT_URI;
response.sendRedirect(installRedirectUri);
}
@GetMapping("avatar")
public void avatar(HttpServletResponse response) throws IOException {
User user =
userService.getCurrentUser().orElseThrow(() -> new ServiceException("未查询到博主信息"));
if (StringUtils.isNotEmpty(user.getAvatar())) {
response.sendRedirect(HaloUtils.normalizeUrl(user.getAvatar()));
}
}
@GetMapping("logo")
public void logo(HttpServletResponse response) throws IOException {
String blogLogo =
optionService.getByProperty(BlogProperties.BLOG_LOGO).orElse("").toString();
if (StringUtils.isNotEmpty(blogLogo)) {
response.sendRedirect(HaloUtils.normalizeUrl(blogLogo));
}
}
@GetMapping("favicon.ico")
public void favicon(HttpServletResponse response) throws IOException {
String favicon =
optionService.getByProperty(BlogProperties.BLOG_FAVICON).orElse("").toString();
if (StringUtils.isNotEmpty(favicon)) {
response.sendRedirect(HaloUtils.normalizeUrl(favicon));
}
}
}

View File

@ -1,36 +0,0 @@
package run.halo.app.controller.content.api;
import java.util.List;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
import run.halo.app.model.vo.ArchiveMonthVO;
import run.halo.app.model.vo.ArchiveYearVO;
import run.halo.app.service.PostService;
/**
* Content archive controller.
*
* @author johnniang
* @date 2019-04-02
*/
@RestController("ApiContentArchiveController")
@RequestMapping("/api/content/archives")
public class ArchiveController {
private final PostService postService;
public ArchiveController(PostService postService) {
this.postService = postService;
}
@GetMapping("years")
public List<ArchiveYearVO> listYearArchives() {
return postService.listYearArchives();
}
@GetMapping("months")
public List<ArchiveMonthVO> listMonthArchives() {
return postService.listMonthArchives();
}
}

View File

@ -1,117 +0,0 @@
package run.halo.app.controller.content.api;
import static org.springframework.data.domain.Sort.Direction.DESC;
import com.google.common.collect.Sets;
import io.swagger.annotations.ApiOperation;
import java.util.List;
import java.util.Set;
import org.springframework.data.domain.Page;
import org.springframework.data.domain.Pageable;
import org.springframework.data.domain.Sort;
import org.springframework.data.web.PageableDefault;
import org.springframework.data.web.SortDefault;
import org.springframework.util.Assert;
import org.springframework.web.bind.annotation.GetMapping;
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.RestController;
import run.halo.app.controller.content.auth.CategoryAuthentication;
import run.halo.app.controller.content.auth.ContentAuthenticationManager;
import run.halo.app.controller.content.auth.ContentAuthenticationRequest;
import run.halo.app.exception.ForbiddenException;
import run.halo.app.model.dto.CategoryDTO;
import run.halo.app.model.entity.Category;
import run.halo.app.model.entity.Post;
import run.halo.app.model.enums.EncryptTypeEnum;
import run.halo.app.model.enums.PostStatus;
import run.halo.app.model.vo.PostListVO;
import run.halo.app.service.CategoryService;
import run.halo.app.service.PostCategoryService;
import run.halo.app.service.assembler.PostRenderAssembler;
/**
* Content category controller.
*
* @author ryanwang
* @date 2019-06-09
*/
@RestController("ApiContentCategoryController")
@RequestMapping("/api/content/categories")
public class CategoryController {
private final CategoryService categoryService;
private final PostCategoryService postCategoryService;
private final PostRenderAssembler postRenderAssembler;
private final CategoryAuthentication categoryAuthentication;
private final ContentAuthenticationManager contentAuthenticationManager;
public CategoryController(CategoryService categoryService,
PostCategoryService postCategoryService,
PostRenderAssembler postRenderAssembler,
CategoryAuthentication categoryAuthentication,
ContentAuthenticationManager contentAuthenticationManager) {
this.categoryService = categoryService;
this.postCategoryService = postCategoryService;
this.postRenderAssembler = postRenderAssembler;
this.categoryAuthentication = categoryAuthentication;
this.contentAuthenticationManager = contentAuthenticationManager;
}
@GetMapping
@ApiOperation("Lists categories")
public List<? extends CategoryDTO> listCategories(
@SortDefault(sort = "updateTime", direction = DESC) Sort sort,
@RequestParam(name = "more", required = false, defaultValue = "false") Boolean more) {
if (more) {
return postCategoryService.listCategoryWithPostCountDto(sort);
}
return categoryService.convertTo(categoryService.listAll(sort));
}
@GetMapping("{slug}/posts")
@ApiOperation("Lists posts by category slug")
public Page<PostListVO> listPostsBy(@PathVariable("slug") String slug,
@RequestParam(value = "password", required = false) String password,
@PageableDefault(sort = {"topPriority", "updateTime"}, direction = DESC)
Pageable pageable) {
// Get category by slug
Category category = categoryService.getBySlugOfNonNull(slug);
Set<PostStatus> statusesToQuery = Sets.immutableEnumSet(PostStatus.PUBLISHED);
if (allowIntimatePosts(category.getId(), password)) {
statusesToQuery = Sets.immutableEnumSet(PostStatus.PUBLISHED, PostStatus.INTIMATE);
}
Page<Post> postPage =
postCategoryService.pagePostBy(category.getId(), statusesToQuery, pageable);
return postRenderAssembler.convertToListVo(postPage);
}
private boolean allowIntimatePosts(Integer categoryId, String password) {
Assert.notNull(categoryId, "The categoryId must not be null.");
if (!categoryService.isPrivate(categoryId)) {
return false;
}
if (categoryAuthentication.isAuthenticated(categoryId)) {
return true;
}
if (password != null) {
ContentAuthenticationRequest authRequest =
ContentAuthenticationRequest.of(categoryId, password,
EncryptTypeEnum.CATEGORY.getName());
// authenticate this request,throw an error if authenticate failed
contentAuthenticationManager.authenticate(authRequest);
return true;
}
throw new ForbiddenException("您没有该分类的访问权限");
}
}

View File

@ -1,134 +0,0 @@
package run.halo.app.controller.content.api;
import static org.springframework.data.domain.Sort.Direction.DESC;
import io.swagger.annotations.ApiOperation;
import java.nio.charset.StandardCharsets;
import java.util.List;
import org.springframework.data.domain.Page;
import org.springframework.data.domain.PageRequest;
import org.springframework.data.domain.Pageable;
import org.springframework.data.domain.Sort;
import org.springframework.data.web.PageableDefault;
import org.springframework.data.web.SortDefault;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.PathVariable;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RequestBody;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestParam;
import org.springframework.web.bind.annotation.RestController;
import org.springframework.web.util.HtmlUtils;
import run.halo.app.cache.lock.CacheLock;
import run.halo.app.cache.lock.CacheParam;
import run.halo.app.model.dto.BaseCommentDTO;
import run.halo.app.model.dto.JournalDTO;
import run.halo.app.model.dto.JournalWithCmtCountDTO;
import run.halo.app.model.entity.Journal;
import run.halo.app.model.entity.JournalComment;
import run.halo.app.model.enums.CommentStatus;
import run.halo.app.model.enums.JournalType;
import run.halo.app.model.params.JournalCommentParam;
import run.halo.app.model.vo.BaseCommentVO;
import run.halo.app.model.vo.BaseCommentWithParentVO;
import run.halo.app.model.vo.CommentWithHasChildrenVO;
import run.halo.app.service.JournalCommentService;
import run.halo.app.service.JournalService;
import run.halo.app.service.OptionService;
/**
* Content journal controller.
*
* @author johnniang
* @author ryanwang
* @date 2019-04-26
*/
@RestController("ApiContentJournalController")
@RequestMapping("/api/content/journals")
public class JournalController {
private final JournalService journalService;
private final JournalCommentService journalCommentService;
private final OptionService optionService;
public JournalController(JournalService journalService,
JournalCommentService journalCommentService,
OptionService optionService) {
this.journalService = journalService;
this.journalCommentService = journalCommentService;
this.optionService = optionService;
}
@GetMapping
@ApiOperation("Lists journals")
public Page<JournalWithCmtCountDTO> pageBy(
@PageableDefault(sort = "createTime", direction = DESC) Pageable pageable) {
Page<Journal> journals = journalService.pageBy(JournalType.PUBLIC, pageable);
return journalService.convertToCmtCountDto(journals);
}
@GetMapping("{journalId:\\d+}")
@ApiOperation("Gets a journal detail")
public JournalWithCmtCountDTO getBy(@PathVariable("journalId") Integer journalId) {
Journal journal = journalService.getById(journalId);
return journalService.convertTo(journal);
}
@GetMapping("{journalId:\\d+}/comments/top_view")
public Page<CommentWithHasChildrenVO> listTopComments(
@PathVariable("journalId") Integer journalId,
@RequestParam(name = "page", required = false, defaultValue = "0") int page,
@SortDefault(sort = "createTime", direction = DESC) Sort sort) {
return journalCommentService.pageTopCommentsBy(journalId, CommentStatus.PUBLISHED,
PageRequest.of(page, optionService.getCommentPageSize(), sort));
}
@GetMapping("{journalId:\\d+}/comments/{commentParentId:\\d+}/children")
public List<BaseCommentDTO> listChildrenBy(@PathVariable("journalId") Integer journalId,
@PathVariable("commentParentId") Long commentParentId,
@SortDefault(sort = "createTime", direction = DESC) Sort sort) {
// Find all children comments
List<JournalComment> postComments = journalCommentService
.listChildrenBy(journalId, commentParentId, CommentStatus.PUBLISHED, sort);
// Convert to base comment dto
return journalCommentService.convertTo(postComments);
}
@GetMapping("{journalId:\\d+}/comments/tree_view")
@ApiOperation("Lists comments with tree view")
public Page<BaseCommentVO> listCommentsTree(@PathVariable("journalId") Integer journalId,
@RequestParam(name = "page", required = false, defaultValue = "0") int page,
@SortDefault(sort = "createTime", direction = DESC) Sort sort) {
return journalCommentService
.pageVosBy(journalId, PageRequest.of(page, optionService.getCommentPageSize(), sort));
}
@GetMapping("{journalId:\\d+}/comments/list_view")
@ApiOperation("Lists comment with list view")
public Page<BaseCommentWithParentVO> listComments(@PathVariable("journalId") Integer journalId,
@RequestParam(name = "page", required = false, defaultValue = "0") int page,
@SortDefault(sort = "createTime", direction = DESC) Sort sort) {
return journalCommentService.pageWithParentVoBy(journalId,
PageRequest.of(page, optionService.getCommentPageSize(), sort));
}
@PostMapping("comments")
@ApiOperation("Comments a post")
@CacheLock(autoDelete = false, traceRequest = true)
public BaseCommentDTO comment(@RequestBody JournalCommentParam journalCommentParam) {
// Escape content
journalCommentParam.setContent(HtmlUtils
.htmlEscape(journalCommentParam.getContent(), StandardCharsets.UTF_8.displayName()));
return journalCommentService.convertTo(journalCommentService.createBy(journalCommentParam));
}
@PostMapping("{id:\\d+}/likes")
@ApiOperation("Likes a journal")
@CacheLock(autoDelete = false, traceRequest = true)
public void like(@PathVariable("id") @CacheParam Integer id) {
journalService.increaseLike(id);
}
}

View File

@ -1,44 +0,0 @@
package run.halo.app.controller.content.api;
import static org.springframework.data.domain.Sort.Direction.DESC;
import io.swagger.annotations.ApiOperation;
import java.util.List;
import org.springframework.data.domain.Sort;
import org.springframework.data.web.SortDefault;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
import run.halo.app.model.dto.LinkDTO;
import run.halo.app.model.vo.LinkTeamVO;
import run.halo.app.service.LinkService;
/**
* Content link controller.
*
* @author johnniang
* @author ryanwang
* @date 2019-04-03
*/
@RestController("ApiContentLinkController")
@RequestMapping("/api/content/links")
public class LinkController {
private final LinkService linkService;
public LinkController(LinkService linkService) {
this.linkService = linkService;
}
@GetMapping
@ApiOperation("List all links")
public List<LinkDTO> listLinks(@SortDefault(sort = "createTime", direction = DESC) Sort sort) {
return linkService.listDtos(sort);
}
@GetMapping("team_view")
@ApiOperation("List all links with team view")
public List<LinkTeamVO> listTeamVos(Sort sort) {
return linkService.listTeamVos(sort);
}
}

View File

@ -1,45 +0,0 @@
package run.halo.app.controller.content.api;
import static org.springframework.data.domain.Sort.Direction.DESC;
import io.swagger.annotations.ApiOperation;
import java.util.List;
import org.springframework.data.domain.Sort;
import org.springframework.data.web.SortDefault;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
import run.halo.app.model.dto.MenuDTO;
import run.halo.app.model.vo.MenuVO;
import run.halo.app.service.MenuService;
/**
* Content menu controller.
*
* @author johnniang
* @author ryanwang
* @date 2019-04-03
*/
@RestController("ApiContentMenuController")
@RequestMapping("/api/content/menus")
public class MenuController {
private final MenuService menuService;
public MenuController(MenuService menuService) {
this.menuService = menuService;
}
@GetMapping
@ApiOperation("Lists all menus")
public List<MenuDTO> listAll(@SortDefault(sort = "priority", direction = DESC) Sort sort) {
return menuService.listDtos(sort);
}
@GetMapping(value = "tree_view")
@ApiOperation("Lists menus with tree view")
public List<MenuVO> listMenusTree(
@SortDefault(sort = "createTime", direction = DESC) Sort sort) {
return menuService.listAsTree(sort);
}
}

View File

@ -1,82 +0,0 @@
package run.halo.app.controller.content.api;
import io.swagger.annotations.ApiOperation;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;
import java.util.Map;
import java.util.stream.Collectors;
import org.springframework.util.CollectionUtils;
import org.springframework.util.StringUtils;
import org.springframework.web.bind.annotation.GetMapping;
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.RestController;
import run.halo.app.model.dto.OptionDTO;
import run.halo.app.model.properties.CommentProperties;
import run.halo.app.model.support.BaseResponse;
import run.halo.app.service.ClientOptionService;
/**
* Content option controller.
*
* @author johnniang
* @date 2019-04-03
*/
@RestController("ApiContentOptionController")
@RequestMapping("/api/content/options")
public class OptionController {
private final ClientOptionService optionService;
public OptionController(ClientOptionService clientOptionService) {
this.optionService = clientOptionService;
}
@GetMapping("list_view")
@ApiOperation("Lists all options with list view")
public List<OptionDTO> listAll() {
return optionService.listDtos();
}
@GetMapping("map_view")
@ApiOperation("Lists options with map view")
public Map<String, Object> listAllWithMapView(
@Deprecated(since = "1.4.8", forRemoval = true)
@RequestParam(value = "key", required = false) List<String> keyList,
@RequestParam(value = "keys", required = false) String keys) {
// handle for key list
if (!CollectionUtils.isEmpty(keyList)) {
return optionService.listOptions(keyList);
}
// handle for keys
if (StringUtils.hasText(keys)) {
var nameSet = Arrays.stream(keys.split(","))
.map(String::trim)
.collect(Collectors.toUnmodifiableSet());
return optionService.listOptions(nameSet);
}
// list all
return optionService.listOptions();
}
@GetMapping("keys/{key}")
@ApiOperation("Gets option value by option key")
public BaseResponse<Object> getBy(@PathVariable("key") String key) {
Object optionValue = optionService.getByKey(key).orElse(null);
return BaseResponse.ok(optionValue);
}
@GetMapping("comment")
@ApiOperation("Options for comment(@deprecated, use /bulk api instead of this.)")
@Deprecated
public Map<String, Object> comment() {
List<String> keys = new ArrayList<>();
keys.add(CommentProperties.GRAVATAR_DEFAULT.getValue());
keys.add(CommentProperties.CONTENT_PLACEHOLDER.getValue());
keys.add(CommentProperties.GRAVATAR_SOURCE.getValue());
return optionService.listOptions(keys);
}
}

View File

@ -1,59 +0,0 @@
package run.halo.app.controller.content.api;
import static org.springframework.data.domain.Sort.Direction.DESC;
import io.swagger.annotations.ApiOperation;
import java.util.List;
import org.springframework.data.domain.Page;
import org.springframework.data.domain.Pageable;
import org.springframework.data.domain.Sort;
import org.springframework.data.web.PageableDefault;
import org.springframework.data.web.SortDefault;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
import run.halo.app.model.dto.PhotoDTO;
import run.halo.app.model.params.PhotoQuery;
import run.halo.app.service.PhotoService;
/**
* Content photo controller.
*
* @author ryanwang
* @date 2019-09-21
*/
@RestController("ApiContentPhotoController")
@RequestMapping("/api/content/photos")
public class PhotoController {
private final PhotoService photoService;
public PhotoController(PhotoService photoService) {
this.photoService = photoService;
}
/**
* List all photos
*
* @param sort sort
* @return all of photos
*/
@GetMapping(value = "latest")
public List<PhotoDTO> listPhotos(
@SortDefault(sort = "updateTime", direction = Sort.Direction.DESC) Sort sort) {
return photoService.listDtos(sort);
}
@GetMapping
public Page<PhotoDTO> pageBy(
@PageableDefault(sort = "updateTime", direction = DESC) Pageable pageable,
PhotoQuery photoQuery) {
return photoService.pageDtosBy(pageable, photoQuery);
}
@GetMapping("teams")
@ApiOperation("Lists all of photo teams")
public List<String> listTeams() {
return photoService.listAllTeams();
}
}

View File

@ -1,255 +0,0 @@
package run.halo.app.controller.content.api;
import static org.springframework.data.domain.Sort.Direction.DESC;
import io.swagger.annotations.ApiOperation;
import java.nio.charset.StandardCharsets;
import java.util.List;
import java.util.Set;
import org.springframework.data.domain.Page;
import org.springframework.data.domain.PageRequest;
import org.springframework.data.domain.Pageable;
import org.springframework.data.domain.Sort;
import org.springframework.data.web.PageableDefault;
import org.springframework.data.web.SortDefault;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.PathVariable;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RequestBody;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestParam;
import org.springframework.web.bind.annotation.RestController;
import org.springframework.web.util.HtmlUtils;
import run.halo.app.cache.lock.CacheLock;
import run.halo.app.cache.lock.CacheParam;
import run.halo.app.controller.content.auth.PostAuthentication;
import run.halo.app.exception.ForbiddenException;
import run.halo.app.exception.NotFoundException;
import run.halo.app.model.dto.BaseCommentDTO;
import run.halo.app.model.dto.post.BasePostSimpleDTO;
import run.halo.app.model.entity.Post;
import run.halo.app.model.entity.PostComment;
import run.halo.app.model.enums.CommentStatus;
import run.halo.app.model.enums.PostStatus;
import run.halo.app.model.params.PostCommentParam;
import run.halo.app.model.params.PostQuery;
import run.halo.app.model.vo.BaseCommentVO;
import run.halo.app.model.vo.BaseCommentWithParentVO;
import run.halo.app.model.vo.CommentWithHasChildrenVO;
import run.halo.app.model.vo.PostDetailVO;
import run.halo.app.model.vo.PostListVO;
import run.halo.app.service.OptionService;
import run.halo.app.service.PostCommentService;
import run.halo.app.service.PostService;
import run.halo.app.service.assembler.PostRenderAssembler;
/**
* Content post controller.
*
* @author johnniang
* @author ryanwang
* @author guqing
* @date 2019-04-02
*/
@RestController("ApiContentPostController")
@RequestMapping("/api/content/posts")
public class PostController {
private final PostService postService;
private final PostCommentService postCommentService;
private final OptionService optionService;
private final PostRenderAssembler postRenderAssembler;
private final PostAuthentication postAuthentication;
public PostController(PostService postService,
PostCommentService postCommentService,
OptionService optionService, PostRenderAssembler postRenderAssembler,
PostAuthentication postAuthentication) {
this.postService = postService;
this.postCommentService = postCommentService;
this.optionService = optionService;
this.postRenderAssembler = postRenderAssembler;
this.postAuthentication = postAuthentication;
}
//CS304 issue for https://github.com/halo-dev/halo/issues/1351
/**
* Enable users search published articles with keywords.
*
* @param pageable store the priority of the sort algorithm
* @param keyword search articles with keyword
* @param categoryId search articles with categoryId
* @return published articles that contains keywords and specific categoryId
*/
@GetMapping
@ApiOperation("Lists posts")
public Page<PostListVO> pageBy(
@PageableDefault(sort = {"topPriority", "createTime"}, direction = DESC) Pageable pageable,
@RequestParam(value = "keyword", required = false) String keyword,
@RequestParam(value = "categoryId", required = false) Integer categoryId) {
PostQuery postQuery = new PostQuery();
postQuery.setKeyword(keyword);
postQuery.setCategoryId(categoryId);
postQuery.setStatuses(Set.of(PostStatus.PUBLISHED));
Page<Post> postPage = postService.pageBy(postQuery, pageable);
return postRenderAssembler.convertToListVo(postPage);
}
@PostMapping(value = "search")
@ApiOperation("Lists posts by keyword")
public Page<BasePostSimpleDTO> pageBy(@RequestParam(value = "keyword") String keyword,
@PageableDefault(sort = "createTime", direction = DESC) Pageable pageable) {
Page<Post> postPage = postService.pageBy(keyword, pageable);
return postRenderAssembler.convertToSimple(postPage);
}
@GetMapping("{postId:\\d+}")
@ApiOperation("Gets a post")
public PostDetailVO getBy(@PathVariable("postId") Integer postId,
@RequestParam(value = "formatDisabled", required = false, defaultValue = "true")
Boolean formatDisabled,
@RequestParam(value = "sourceDisabled", required = false, defaultValue = "false")
Boolean sourceDisabled) {
Post post = postService.getById(postId);
checkAuthenticate(postId);
PostDetailVO postDetailVO = postRenderAssembler.convertToDetailVo(post);
if (formatDisabled) {
// Clear the format content
postDetailVO.setContent(null);
}
if (sourceDisabled) {
// Clear the original content
postDetailVO.setOriginalContent(null);
}
postService.publishVisitEvent(postDetailVO.getId());
return postDetailVO;
}
@GetMapping("/slug")
@ApiOperation("Gets a post")
public PostDetailVO getBy(@RequestParam("slug") String slug,
@RequestParam(value = "formatDisabled", required = false, defaultValue = "true")
Boolean formatDisabled,
@RequestParam(value = "sourceDisabled", required = false, defaultValue = "false")
Boolean sourceDisabled) {
Post post = postService.getBySlug(slug);
checkAuthenticate(post.getId());
PostDetailVO postDetailVO = postRenderAssembler.convertToDetailVo(post);
if (formatDisabled) {
// Clear the format content
postDetailVO.setContent(null);
}
if (sourceDisabled) {
// Clear the original content
postDetailVO.setOriginalContent(null);
}
postService.publishVisitEvent(postDetailVO.getId());
return postDetailVO;
}
@GetMapping("{postId:\\d+}/prev")
@ApiOperation("Gets previous post by current post id.")
public PostDetailVO getPrevPostBy(@PathVariable("postId") Integer postId) {
Post post = postService.getById(postId);
Post prevPost =
postService.getPrevPost(post).orElseThrow(() -> new NotFoundException("查询不到该文章的信息"));
checkAuthenticate(prevPost.getId());
return postRenderAssembler.convertToDetailVo(prevPost);
}
@GetMapping("{postId:\\d+}/next")
@ApiOperation("Gets next post by current post id.")
public PostDetailVO getNextPostBy(@PathVariable("postId") Integer postId) {
Post post = postService.getById(postId);
Post nextPost =
postService.getNextPost(post).orElseThrow(() -> new NotFoundException("查询不到该文章的信息"));
checkAuthenticate(nextPost.getId());
return postRenderAssembler.convertToDetailVo(nextPost);
}
@GetMapping("{postId:\\d+}/comments/top_view")
public Page<CommentWithHasChildrenVO> listTopComments(@PathVariable("postId") Integer postId,
@RequestParam(name = "page", required = false, defaultValue = "0") int page,
@SortDefault(sort = "createTime", direction = DESC) Sort sort) {
checkAuthenticate(postId);
return postCommentService.pageTopCommentsBy(postId, CommentStatus.PUBLISHED,
PageRequest.of(page, optionService.getCommentPageSize(), sort));
}
@GetMapping("{postId:\\d+}/comments/{commentParentId:\\d+}/children")
public List<BaseCommentDTO> listChildrenBy(@PathVariable("postId") Integer postId,
@PathVariable("commentParentId") Long commentParentId,
@SortDefault(sort = "createTime", direction = DESC) Sort sort) {
checkAuthenticate(postId);
// Find all children comments
List<PostComment> postComments = postCommentService
.listChildrenBy(postId, commentParentId, CommentStatus.PUBLISHED, sort);
// Convert to base comment dto
return postCommentService.convertTo(postComments);
}
@GetMapping("{postId:\\d+}/comments/tree_view")
@ApiOperation("Lists comments with tree view")
public Page<BaseCommentVO> listCommentsTree(@PathVariable("postId") Integer postId,
@RequestParam(name = "page", required = false, defaultValue = "0") int page,
@SortDefault(sort = "createTime", direction = DESC) Sort sort) {
checkAuthenticate(postId);
return postCommentService
.pageVosBy(postId, PageRequest.of(page, optionService.getCommentPageSize(), sort));
}
@GetMapping("{postId:\\d+}/comments/list_view")
@ApiOperation("Lists comment with list view")
public Page<BaseCommentWithParentVO> listComments(@PathVariable("postId") Integer postId,
@RequestParam(name = "page", required = false, defaultValue = "0") int page,
@SortDefault(sort = "createTime", direction = DESC) Sort sort) {
checkAuthenticate(postId);
return postCommentService.pageWithParentVoBy(postId,
PageRequest.of(page, optionService.getCommentPageSize(), sort));
}
@PostMapping("comments")
@ApiOperation("Comments a post")
@CacheLock(autoDelete = false, traceRequest = true)
public BaseCommentDTO comment(@RequestBody PostCommentParam postCommentParam) {
checkAuthenticate(postCommentParam.getPostId());
postCommentService.validateCommentBlackListStatus();
// Escape content
postCommentParam.setContent(HtmlUtils
.htmlEscape(postCommentParam.getContent(), StandardCharsets.UTF_8.displayName()));
return postCommentService.convertTo(postCommentService.createBy(postCommentParam));
}
@PostMapping("{postId:\\d+}/likes")
@ApiOperation("Likes a post")
@CacheLock(autoDelete = false, traceRequest = true)
public void like(@PathVariable("postId") @CacheParam Integer postId) {
checkAuthenticate(postId);
postService.increaseLike(postId);
}
private void checkAuthenticate(Integer postId) {
if (!postAuthentication.isAuthenticated(postId)) {
throw new ForbiddenException("您没有该分类的访问权限");
}
}
}

View File

@ -1,175 +0,0 @@
package run.halo.app.controller.content.api;
import static org.springframework.data.domain.Sort.Direction.DESC;
import io.swagger.annotations.ApiOperation;
import java.nio.charset.StandardCharsets;
import java.util.List;
import org.springframework.data.domain.Page;
import org.springframework.data.domain.PageRequest;
import org.springframework.data.domain.Pageable;
import org.springframework.data.domain.Sort;
import org.springframework.data.web.PageableDefault;
import org.springframework.data.web.SortDefault;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.PathVariable;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RequestBody;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestParam;
import org.springframework.web.bind.annotation.RestController;
import org.springframework.web.util.HtmlUtils;
import run.halo.app.cache.lock.CacheLock;
import run.halo.app.model.dto.BaseCommentDTO;
import run.halo.app.model.entity.Sheet;
import run.halo.app.model.entity.SheetComment;
import run.halo.app.model.enums.CommentStatus;
import run.halo.app.model.enums.PostStatus;
import run.halo.app.model.params.SheetCommentParam;
import run.halo.app.model.vo.BaseCommentVO;
import run.halo.app.model.vo.BaseCommentWithParentVO;
import run.halo.app.model.vo.CommentWithHasChildrenVO;
import run.halo.app.model.vo.SheetDetailVO;
import run.halo.app.model.vo.SheetListVO;
import run.halo.app.service.OptionService;
import run.halo.app.service.SheetCommentService;
import run.halo.app.service.SheetService;
import run.halo.app.service.assembler.SheetRenderAssembler;
/**
* Content sheet controller.
*
* @author johnniang
* @author ryanwang
* @date 2019-04-26
*/
@RestController("ApiContentSheetController")
@RequestMapping("/api/content/sheets")
public class SheetController {
private final SheetService sheetService;
private final SheetRenderAssembler sheetRenderAssembler;
private final SheetCommentService sheetCommentService;
private final OptionService optionService;
public SheetController(SheetService sheetService,
SheetRenderAssembler sheetRenderAssembler,
SheetCommentService sheetCommentService,
OptionService optionService) {
this.sheetService = sheetService;
this.sheetRenderAssembler = sheetRenderAssembler;
this.sheetCommentService = sheetCommentService;
this.optionService = optionService;
}
@GetMapping
@ApiOperation("Lists sheets")
public Page<SheetListVO> pageBy(
@PageableDefault(sort = "createTime", direction = DESC) Pageable pageable) {
Page<Sheet> sheetPage = sheetService.pageBy(PostStatus.PUBLISHED, pageable);
return sheetRenderAssembler.convertToListVo(sheetPage);
}
@GetMapping("{sheetId:\\d+}")
@ApiOperation("Gets a sheet")
public SheetDetailVO getBy(@PathVariable("sheetId") Integer sheetId,
@RequestParam(value = "formatDisabled", required = false, defaultValue = "true")
Boolean formatDisabled,
@RequestParam(value = "sourceDisabled", required = false, defaultValue = "false")
Boolean sourceDisabled) {
Sheet sheet = sheetService.getById(sheetId);
SheetDetailVO sheetDetailVO = sheetRenderAssembler.convertToDetailVo(sheet);
if (formatDisabled) {
// Clear the format content
sheetDetailVO.setContent(null);
}
if (sourceDisabled) {
// Clear the original content
sheetDetailVO.setOriginalContent(null);
}
sheetService.publishVisitEvent(sheetDetailVO.getId());
return sheetDetailVO;
}
@GetMapping("/slug")
@ApiOperation("Gets a sheet by slug")
public SheetDetailVO getBy(@RequestParam("slug") String slug,
@RequestParam(value = "formatDisabled", required = false, defaultValue = "true")
Boolean formatDisabled,
@RequestParam(value = "sourceDisabled", required = false, defaultValue = "false")
Boolean sourceDisabled) {
Sheet sheet = sheetService.getBySlug(slug);
SheetDetailVO sheetDetailVO = sheetRenderAssembler.convertToDetailVo(sheet);
if (formatDisabled) {
// Clear the format content
sheetDetailVO.setContent(null);
}
if (sourceDisabled) {
// Clear the original content
sheetDetailVO.setOriginalContent(null);
}
sheetService.publishVisitEvent(sheetDetailVO.getId());
return sheetDetailVO;
}
@GetMapping("{sheetId:\\d+}/comments/top_view")
public Page<CommentWithHasChildrenVO> listTopComments(@PathVariable("sheetId") Integer sheetId,
@RequestParam(name = "page", required = false, defaultValue = "0") int page,
@SortDefault(sort = "createTime", direction = DESC) Sort sort) {
return sheetCommentService.pageTopCommentsBy(sheetId, CommentStatus.PUBLISHED,
PageRequest.of(page, optionService.getCommentPageSize(), sort));
}
@GetMapping("{sheetId:\\d+}/comments/{commentParentId:\\d+}/children")
public List<BaseCommentDTO> listChildrenBy(@PathVariable("sheetId") Integer sheetId,
@PathVariable("commentParentId") Long commentParentId,
@SortDefault(sort = "createTime", direction = DESC) Sort sort) {
// Find all children comments
List<SheetComment> sheetComments = sheetCommentService
.listChildrenBy(sheetId, commentParentId, CommentStatus.PUBLISHED, sort);
// Convert to base comment dto
return sheetCommentService.convertTo(sheetComments);
}
@GetMapping("{sheetId:\\d+}/comments/tree_view")
@ApiOperation("Lists comments with tree view")
public Page<BaseCommentVO> listCommentsTree(@PathVariable("sheetId") Integer sheetId,
@RequestParam(name = "page", required = false, defaultValue = "0") int page,
@SortDefault(sort = "createTime", direction = DESC) Sort sort) {
return sheetCommentService
.pageVosBy(sheetId, PageRequest.of(page, optionService.getCommentPageSize(), sort));
}
@GetMapping("{sheetId:\\d+}/comments/list_view")
@ApiOperation("Lists comment with list view")
public Page<BaseCommentWithParentVO> listComments(@PathVariable("sheetId") Integer sheetId,
@RequestParam(name = "page", required = false, defaultValue = "0") int page,
@SortDefault(sort = "createTime", direction = DESC) Sort sort) {
return sheetCommentService.pageWithParentVoBy(sheetId,
PageRequest.of(page, optionService.getCommentPageSize(), sort));
}
@PostMapping("comments")
@ApiOperation("Comments a post")
@CacheLock(autoDelete = false, traceRequest = true)
public BaseCommentDTO comment(@RequestBody SheetCommentParam sheetCommentParam) {
// Escape content
sheetCommentParam.setContent(HtmlUtils
.htmlEscape(sheetCommentParam.getContent(), StandardCharsets.UTF_8.displayName()));
return sheetCommentService.convertTo(sheetCommentService.createBy(sheetCommentParam));
}
}

View File

@ -1,38 +0,0 @@
package run.halo.app.controller.content.api;
import io.swagger.annotations.ApiOperation;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
import run.halo.app.model.dto.StatisticDTO;
import run.halo.app.model.dto.StatisticWithUserDTO;
import run.halo.app.service.StatisticService;
/**
* Content statistic controller.
*
* @author ryan0up
* @date 2019-12-16
*/
@RestController("ApiContentStatisticController")
@RequestMapping("/api/content/statistics")
public class StatisticController {
private final StatisticService statisticService;
public StatisticController(StatisticService statisticService) {
this.statisticService = statisticService;
}
@GetMapping
@ApiOperation("Gets blog statistics.")
public StatisticDTO statistics() {
return statisticService.getStatistic();
}
@GetMapping("user")
@ApiOperation("Gets blog statistics with user")
public StatisticWithUserDTO statisticsWithUser() {
return statisticService.getStatisticWithUser();
}
}

View File

@ -1,77 +0,0 @@
package run.halo.app.controller.content.api;
import static org.springframework.data.domain.Sort.Direction.DESC;
import io.swagger.annotations.ApiOperation;
import io.swagger.annotations.ApiParam;
import java.util.List;
import org.springframework.data.domain.Page;
import org.springframework.data.domain.Pageable;
import org.springframework.data.domain.Sort;
import org.springframework.data.web.PageableDefault;
import org.springframework.data.web.SortDefault;
import org.springframework.web.bind.annotation.GetMapping;
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.RestController;
import run.halo.app.model.dto.TagDTO;
import run.halo.app.model.entity.Post;
import run.halo.app.model.entity.Tag;
import run.halo.app.model.enums.PostStatus;
import run.halo.app.model.vo.PostListVO;
import run.halo.app.service.PostTagService;
import run.halo.app.service.TagService;
import run.halo.app.service.assembler.PostRenderAssembler;
/**
* Content tag controller.
*
* @author johnniang
* @author ryanwang
* @date 2019-04-02
*/
@RestController("ApiContentTagController")
@RequestMapping("/api/content/tags")
public class TagController {
private final TagService tagService;
private final PostTagService postTagService;
private final PostRenderAssembler postRenderAssembler;
public TagController(TagService tagService,
PostTagService postTagService,
PostRenderAssembler postRenderAssembler) {
this.tagService = tagService;
this.postTagService = postTagService;
this.postRenderAssembler = postRenderAssembler;
}
@GetMapping
@ApiOperation("Lists tags")
public List<? extends TagDTO> listTags(
@SortDefault(sort = "updateTime", direction = DESC) Sort sort,
@ApiParam("If the param is true, post count of tag will be returned")
@RequestParam(name = "more", required = false, defaultValue = "false") Boolean more) {
if (more) {
return postTagService.listTagWithCountDtos(sort);
}
return tagService.convertTo(tagService.listAll(sort));
}
@GetMapping("{slug}/posts")
@ApiOperation("Lists posts by tag slug")
public Page<PostListVO> listPostsBy(@PathVariable("slug") String slug,
@PageableDefault(sort = {"topPriority", "updateTime"}, direction = DESC)
Pageable pageable) {
// Get tag by slug
Tag tag = tagService.getBySlugOfNonNull(slug);
// Get posts, convert and return
Page<Post> postPage =
postTagService.pagePostsBy(tag.getId(), PostStatus.PUBLISHED, pageable);
return postRenderAssembler.convertToListVo(postPage);
}
}

View File

@ -1,55 +0,0 @@
package run.halo.app.controller.content.api;
import io.swagger.annotations.ApiOperation;
import java.util.Map;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.PathVariable;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
import run.halo.app.handler.theme.config.support.ThemeProperty;
import run.halo.app.service.ThemeService;
import run.halo.app.service.ThemeSettingService;
/**
* Content theme controller.
*
* @author ryanwang
* @date 2020-01-17
*/
@RestController("ApiContentThemeController")
@RequestMapping("/api/content/themes")
public class ThemeController {
private final ThemeService themeService;
private final ThemeSettingService themeSettingService;
public ThemeController(ThemeService themeService, ThemeSettingService themeSettingService) {
this.themeService = themeService;
this.themeSettingService = themeSettingService;
}
@GetMapping("activation")
@ApiOperation("Gets activated theme property")
public ThemeProperty getBy() {
return themeService.getThemeOfNonNullBy(themeService.getActivatedThemeId());
}
@GetMapping("{themeId:.+}")
@ApiOperation("Gets theme property by theme id")
public ThemeProperty getBy(@PathVariable("themeId") String themeId) {
return themeService.getThemeOfNonNullBy(themeId);
}
@GetMapping("activation/settings")
@ApiOperation("Lists activated theme settings")
public Map<String, Object> listSettingsBy() {
return themeSettingService.listAsMapBy(themeService.getActivatedThemeId());
}
@GetMapping("{themeId:.+}/settings")
@ApiOperation("Lists theme settings by theme id")
public Map<String, Object> listSettingsBy(@PathVariable("themeId") String themeId) {
return themeSettingService.listAsMapBy(themeId);
}
}

View File

@ -1,32 +0,0 @@
package run.halo.app.controller.content.api;
import io.swagger.annotations.ApiOperation;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
import run.halo.app.model.dto.UserDTO;
import run.halo.app.service.UserService;
/**
* Content user controller.
*
* @author johnniang
* @date 2019-04-03
*/
@RestController("ApiContentUserController")
@RequestMapping("/api/content/users")
public class UserController {
private final UserService userService;
public UserController(UserService userService) {
this.userService = userService;
}
@GetMapping("profile")
@ApiOperation("Gets blogger profile")
public UserDTO getProfile() {
return userService.getCurrentUser().map(user -> (UserDTO) new UserDTO().convertFrom(user))
.get();
}
}

View File

@ -1,85 +0,0 @@
package run.halo.app.controller.content.auth;
import java.util.concurrent.TimeUnit;
import org.apache.commons.lang3.StringUtils;
import org.springframework.lang.NonNull;
import org.springframework.stereotype.Component;
import run.halo.app.cache.AbstractStringCacheStore;
import run.halo.app.model.entity.Category;
import run.halo.app.model.enums.EncryptTypeEnum;
import run.halo.app.service.CategoryService;
/**
* Authentication for category.
*
* @author guqing
* @date 2022-02-23
*/
@Component
public class CategoryAuthentication implements ContentAuthentication {
private final CategoryService categoryService;
private final AbstractStringCacheStore cacheStore;
public CategoryAuthentication(CategoryService categoryService,
AbstractStringCacheStore cacheStore) {
this.categoryService = categoryService;
this.cacheStore = cacheStore;
}
@Override
@NonNull
public Object getPrincipal() {
return EncryptTypeEnum.CATEGORY.getName();
}
@Override
public boolean isAuthenticated(Integer categoryId) {
Category category = categoryService.getById(categoryId);
if (StringUtils.isBlank(category.getPassword())) {
// All parent category is not encrypted
if (categoryService.lookupFirstEncryptedBy(category.getId()).isEmpty()) {
return true;
}
}
String sessionId = getSessionId();
// No session is represent a client request
if (StringUtils.isEmpty(sessionId)) {
return false;
}
String cacheKey =
buildCacheKey(sessionId, getPrincipal().toString(), String.valueOf(categoryId));
return cacheStore.get(cacheKey).isPresent();
}
@Override
public void setAuthenticated(Integer resourceId, boolean isAuthenticated) {
String sessionId = getSessionId();
// No session is represent a client request
if (StringUtils.isEmpty(sessionId)) {
return;
}
String cacheKey =
buildCacheKey(sessionId, getPrincipal().toString(), String.valueOf(resourceId));
if (isAuthenticated) {
cacheStore.putAny(cacheKey, StringUtils.EMPTY, 1, TimeUnit.DAYS);
return;
}
cacheStore.delete(cacheKey);
}
@Override
public void clearByResourceId(Integer resourceId) {
String resourceCachePrefix =
StringUtils.joinWith(":", CACHE_PREFIX, getPrincipal(), resourceId);
cacheStore.toMap().forEach((key, value) -> {
if (StringUtils.startsWith(key, resourceCachePrefix)) {
cacheStore.delete(key);
}
});
}
}

View File

@ -1,79 +0,0 @@
package run.halo.app.controller.content.auth;
import java.util.Optional;
import javax.servlet.http.HttpServletRequest;
import org.apache.commons.lang3.StringUtils;
import org.springframework.util.Assert;
import run.halo.app.utils.ServletUtils;
/**
* Content authentication.
*
* @author guqing
* @date 2022-02-23
*/
public interface ContentAuthentication {
/**
* The identity of the principal being authenticated.
*
* @return authentication principal.
*/
Object getPrincipal();
/**
* whether the resource been authenticated by a sessionId.
*
* @param resourceId resourceId to authentication
* @see HttpServletRequest#getRequestedSessionId()
* @return true if the resourceId has been authenticated by a sessionId
*/
boolean isAuthenticated(Integer resourceId);
/**
* Set authentication state.
*
* @param resourceId resource identity
* @param isAuthenticated authentication state
* @see HttpServletRequest#getRequestedSessionId()
*/
void setAuthenticated(Integer resourceId, boolean isAuthenticated);
/**
* Clear authentication state.
*
* @param resourceId resource id.
*/
void clearByResourceId(Integer resourceId);
String CACHE_PREFIX = "CONTENT_AUTHENTICATED";
/**
* build authentication cache key.
*
* @param sessionId session id
* @param principal authentication principal
* @param value principal identity
* @return cache key
*/
default String buildCacheKey(String sessionId, String principal,
String value) {
Assert.notNull(sessionId, "The sessionId must not be null.");
Assert.notNull(principal, "The principal must not be null.");
Assert.notNull(value, "The value must not be null.");
return StringUtils.joinWith(":", CACHE_PREFIX, principal, value, sessionId);
}
/**
* Gets request session id.
*
* @return request session id.
*/
default String getSessionId() {
Optional<HttpServletRequest> currentRequest = ServletUtils.getCurrentRequest();
if (currentRequest.isEmpty()) {
return StringUtils.EMPTY;
}
return currentRequest.get().getRequestedSessionId();
}
}

View File

@ -1,140 +0,0 @@
package run.halo.app.controller.content.auth;
import java.util.List;
import java.util.Objects;
import java.util.stream.Collectors;
import org.apache.commons.lang3.StringUtils;
import org.springframework.context.event.EventListener;
import org.springframework.stereotype.Component;
import org.springframework.util.CollectionUtils;
import run.halo.app.event.category.CategoryUpdatedEvent;
import run.halo.app.event.post.PostUpdatedEvent;
import run.halo.app.exception.AuthenticationException;
import run.halo.app.exception.NotFoundException;
import run.halo.app.model.entity.Category;
import run.halo.app.model.entity.Post;
import run.halo.app.model.enums.EncryptTypeEnum;
import run.halo.app.service.CategoryService;
import run.halo.app.service.PostCategoryService;
import run.halo.app.service.PostService;
/**
* Content authentication manager.
*
* @author guqing
* @date 2022-02-24
*/
@Component
public class ContentAuthenticationManager {
private final CategoryService categoryService;
private final CategoryAuthentication categoryAuthentication;
private final PostService postService;
private final PostAuthentication postAuthentication;
private final PostCategoryService postCategoryService;
public ContentAuthenticationManager(CategoryService categoryService,
CategoryAuthentication categoryAuthentication, PostService postService,
PostAuthentication postAuthentication,
PostCategoryService postCategoryService) {
this.categoryService = categoryService;
this.categoryAuthentication = categoryAuthentication;
this.postService = postService;
this.postAuthentication = postAuthentication;
this.postCategoryService = postCategoryService;
}
public ContentAuthentication authenticate(ContentAuthenticationRequest authRequest) throws
AuthenticationException {
if (EncryptTypeEnum.POST.getName().equals(authRequest.getPrincipal())) {
return authenticatePost(authRequest);
}
if (EncryptTypeEnum.CATEGORY.getName().equals(authRequest.getPrincipal())) {
return authenticateCategory(authRequest);
}
throw new NotFoundException(
"Could not be found suitable authentication processor for ["
+ authRequest.getPrincipal() + "]");
}
@EventListener(CategoryUpdatedEvent.class)
public void categoryUpdatedListener(CategoryUpdatedEvent event) {
Category category = event.getCategory();
categoryAuthentication.clearByResourceId(category.getId());
}
@EventListener(PostUpdatedEvent.class)
public void postUpdatedListener(PostUpdatedEvent event) {
Post post = event.getPost();
postAuthentication.clearByResourceId(post.getId());
}
private PostAuthentication authenticatePost(ContentAuthenticationRequest authRequest) {
Post post = postService.getById(authRequest.getId());
if (StringUtils.isNotBlank(post.getPassword())) {
if (StringUtils.equals(post.getPassword(), authRequest.getPassword())) {
postAuthentication.setAuthenticated(post.getId(), true);
return postAuthentication;
} else {
throw new AuthenticationException("密码不正确");
}
} else {
List<Category> encryptedCategories = postCategoryService.listCategoriesBy(post.getId())
.stream()
.filter(category -> categoryService.isPrivate(category.getId()))
.collect(Collectors.toList());
// The post has no password and does not belong to any encryption categories.
// Return it directly
if (CollectionUtils.isEmpty(encryptedCategories)) {
return postAuthentication;
}
// Try all categories until the password is correct
for (Category category : encryptedCategories) {
if (StringUtils.equals(category.getPassword(), authRequest.getPassword())) {
postAuthentication.setAuthenticated(post.getId(), true);
return postAuthentication;
}
}
for (Category category : encryptedCategories) {
boolean authenticated = categoryService.lookupFirstEncryptedBy(category.getId())
.filter(parentCategory -> StringUtils.equals(parentCategory.getPassword(),
authRequest.getPassword()))
.isPresent();
if (authenticated) {
postAuthentication.setAuthenticated(post.getId(), true);
return postAuthentication;
}
}
throw new AuthenticationException("密码不正确");
}
}
private CategoryAuthentication authenticateCategory(ContentAuthenticationRequest authRequest) {
Category category = categoryService.getById(authRequest.getId());
if (category.getPassword() == null) {
String parentPassword = categoryService.lookupFirstEncryptedBy(category.getId())
.map(Category::getPassword)
.orElse(null);
if (parentPassword == null) {
return categoryAuthentication;
}
category.setPassword(parentPassword);
}
if (StringUtils.equals(category.getPassword(), authRequest.getPassword())) {
categoryAuthentication.setAuthenticated(category.getId(), true);
return categoryAuthentication;
}
// Finds the first encrypted parent category to authenticate
Category parentCategory =
categoryService.lookupFirstEncryptedBy(authRequest.getId())
.orElseThrow(() -> new AuthenticationException("密码不正确"));
if (!Objects.equals(parentCategory.getPassword(), authRequest.getPassword())) {
throw new AuthenticationException("密码不正确");
}
categoryAuthentication.setAuthenticated(category.getId(), true);
return categoryAuthentication;
}
}

View File

@ -1,54 +0,0 @@
package run.halo.app.controller.content.auth;
import lombok.Data;
/**
* Authentication request for {@link ContentAuthenticationManager}.
*
* @author guqing
* @date 2022-02-24
*/
@Data
public class ContentAuthenticationRequest implements ContentAuthentication {
private Integer id;
private String password;
private String principal;
@Override
public Object getPrincipal() {
return this.principal;
}
@Override
public boolean isAuthenticated(Integer resourceId) {
return false;
}
@Override
public void setAuthenticated(Integer resourceId, boolean isAuthenticated) {
throw new UnsupportedOperationException();
}
@Override
public void clearByResourceId(Integer resourceId) {
throw new UnsupportedOperationException();
}
/**
* Creates a {@link ContentAuthenticationRequest}.
*
* @param id resource id
* @param password resource password
* @param principal authentication principal
* @return a {@link ContentAuthenticationRequest} instance.
*/
public static ContentAuthenticationRequest of(Integer id, String password, String principal) {
ContentAuthenticationRequest request = new ContentAuthenticationRequest();
request.setId(id);
request.setPassword(password);
request.setPrincipal(principal);
return request;
}
}

View File

@ -1,104 +0,0 @@
package run.halo.app.controller.content.auth;
import java.util.List;
import java.util.concurrent.TimeUnit;
import org.apache.commons.lang3.StringUtils;
import org.springframework.stereotype.Component;
import run.halo.app.cache.AbstractStringCacheStore;
import run.halo.app.model.entity.Post;
import run.halo.app.model.entity.PostCategory;
import run.halo.app.model.enums.EncryptTypeEnum;
import run.halo.app.service.CategoryService;
import run.halo.app.service.PostCategoryService;
import run.halo.app.service.PostService;
/**
* Authentication for post.
*
* @author guqing
* @date 2022-02-24
*/
@Component
public class PostAuthentication implements ContentAuthentication {
private final PostService postService;
private final CategoryService categoryService;
private final PostCategoryService postCategoryService;
private final AbstractStringCacheStore cacheStore;
private final CategoryAuthentication categoryAuthentication;
public PostAuthentication(PostService postService,
CategoryService categoryService,
PostCategoryService postCategoryService,
AbstractStringCacheStore cacheStore,
CategoryAuthentication categoryAuthentication) {
this.postService = postService;
this.categoryService = categoryService;
this.postCategoryService = postCategoryService;
this.cacheStore = cacheStore;
this.categoryAuthentication = categoryAuthentication;
}
@Override
public Object getPrincipal() {
return EncryptTypeEnum.POST.getName();
}
@Override
public boolean isAuthenticated(Integer postId) {
Post post = postService.getById(postId);
if (StringUtils.isBlank(post.getPassword())) {
List<PostCategory> postCategories = postCategoryService.listByPostId(postId);
boolean categoryEncrypted = postCategories.stream()
.anyMatch(postCategory -> categoryService.isPrivate(postCategory.getCategoryId()));
if (!categoryEncrypted) {
return true;
}
boolean anyCategoryAuthenticated = postCategories.stream()
.anyMatch(postCategory ->
categoryAuthentication.isAuthenticated(postCategory.getCategoryId()));
if (anyCategoryAuthenticated) {
return true;
}
}
String sessionId = getSessionId();
// No session is represent a client request
if (StringUtils.isEmpty(sessionId)) {
return false;
}
String cacheKey =
buildCacheKey(sessionId, getPrincipal().toString(), String.valueOf(postId));
return cacheStore.get(cacheKey).isPresent();
}
@Override
public void setAuthenticated(Integer resourceId, boolean isAuthenticated) {
String sessionId = getSessionId();
// No session is represent a client request
if (StringUtils.isEmpty(sessionId)) {
return;
}
String cacheKey =
buildCacheKey(sessionId, getPrincipal().toString(), String.valueOf(resourceId));
if (isAuthenticated) {
cacheStore.putAny(cacheKey, StringUtils.EMPTY, 1, TimeUnit.DAYS);
return;
}
cacheStore.delete(cacheKey);
}
@Override
public void clearByResourceId(Integer resourceId) {
String resourceCachePrefix =
StringUtils.joinWith(":", CACHE_PREFIX, getPrincipal(), resourceId);
cacheStore.toMap().forEach((key, value) -> {
if (StringUtils.startsWith(key, resourceCachePrefix)) {
cacheStore.delete(key);
}
});
}
}

View File

@ -1,126 +0,0 @@
package run.halo.app.controller.content.model;
import static org.springframework.data.domain.Sort.Direction.DESC;
import static run.halo.app.model.support.HaloConst.POST_PASSWORD_TEMPLATE;
import static run.halo.app.model.support.HaloConst.SUFFIX_FTL;
import com.google.common.collect.Sets;
import java.util.Set;
import org.apache.commons.lang3.StringUtils;
import org.springframework.data.domain.Page;
import org.springframework.data.domain.PageRequest;
import org.springframework.data.domain.Pageable;
import org.springframework.data.domain.Sort;
import org.springframework.stereotype.Component;
import org.springframework.ui.Model;
import run.halo.app.controller.content.auth.CategoryAuthentication;
import run.halo.app.model.dto.CategoryDTO;
import run.halo.app.model.entity.Category;
import run.halo.app.model.entity.Post;
import run.halo.app.model.enums.EncryptTypeEnum;
import run.halo.app.model.enums.PostStatus;
import run.halo.app.model.vo.PostListVO;
import run.halo.app.service.CategoryService;
import run.halo.app.service.OptionService;
import run.halo.app.service.PostCategoryService;
import run.halo.app.service.ThemeService;
import run.halo.app.service.assembler.PostRenderAssembler;
/**
* Category Model.
*
* @author ryanwang
* @author guqing
* @date 2020-01-11
*/
@Component
public class CategoryModel {
private final CategoryService categoryService;
private final ThemeService themeService;
private final PostCategoryService postCategoryService;
private final PostRenderAssembler postRenderAssembler;
private final OptionService optionService;
private final CategoryAuthentication categoryAuthentication;
public CategoryModel(CategoryService categoryService,
ThemeService themeService,
PostCategoryService postCategoryService,
PostRenderAssembler postRenderAssembler, OptionService optionService,
CategoryAuthentication categoryAuthentication) {
this.categoryService = categoryService;
this.themeService = themeService;
this.postCategoryService = postCategoryService;
this.postRenderAssembler = postRenderAssembler;
this.optionService = optionService;
this.categoryAuthentication = categoryAuthentication;
}
/**
* List categories.
*
* @param model model
* @return template name
*/
public String list(Model model) {
model.addAttribute("is_categories", true);
model.addAttribute("meta_keywords", optionService.getSeoKeywords());
model.addAttribute("meta_description", optionService.getSeoDescription());
return themeService.render("categories");
}
/**
* List category posts.
*
* @param model model
* @param slug slug
* @param page current page
* @return template name
*/
public String listPost(Model model, String slug, Integer page) {
// Get category by slug
final Category category = categoryService.getBySlugOfNonNull(slug);
if (!categoryAuthentication.isAuthenticated(category.getId())) {
model.addAttribute("slug", category.getSlug());
model.addAttribute("type", EncryptTypeEnum.CATEGORY.getName());
if (themeService.templateExists(POST_PASSWORD_TEMPLATE + SUFFIX_FTL)) {
return themeService.render(POST_PASSWORD_TEMPLATE);
}
return "common/template/" + POST_PASSWORD_TEMPLATE;
}
Set<PostStatus> statuses = Sets.immutableEnumSet(PostStatus.PUBLISHED);
if (categoryService.isPrivate(category.getId())) {
statuses = Sets.immutableEnumSet(PostStatus.INTIMATE);
}
CategoryDTO categoryDTO = categoryService.convertTo(category);
final Pageable pageable = PageRequest.of(page - 1,
optionService.getArchivesPageSize(),
Sort.by(DESC, "topPriority", "createTime"));
Page<Post> postPage =
postCategoryService.pagePostBy(category.getId(), statuses, pageable);
Page<PostListVO> posts = postRenderAssembler.convertToListVo(postPage);
// Generate meta description.
if (StringUtils.isNotEmpty(category.getDescription())) {
model.addAttribute("meta_description", category.getDescription());
} else {
model.addAttribute("meta_description", optionService.getSeoDescription());
}
model.addAttribute("is_category", true);
model.addAttribute("posts", posts);
model.addAttribute("category", categoryDTO);
model.addAttribute("meta_keywords", optionService.getSeoKeywords());
return themeService.render("category");
}
}

View File

@ -1,56 +0,0 @@
package run.halo.app.controller.content.model;
import static org.springframework.data.domain.Sort.Direction.DESC;
import org.springframework.data.domain.Page;
import org.springframework.data.domain.PageRequest;
import org.springframework.data.domain.Pageable;
import org.springframework.data.domain.Sort;
import org.springframework.stereotype.Component;
import org.springframework.ui.Model;
import run.halo.app.model.entity.Journal;
import run.halo.app.model.enums.JournalType;
import run.halo.app.model.properties.SheetProperties;
import run.halo.app.service.JournalService;
import run.halo.app.service.OptionService;
import run.halo.app.service.ThemeService;
/**
* @author ryanwang
* @date 2020-02-11
*/
@Component
public class JournalModel {
private final JournalService journalService;
private final OptionService optionService;
private final ThemeService themeService;
public JournalModel(JournalService journalService,
OptionService optionService,
ThemeService themeService) {
this.journalService = journalService;
this.optionService = optionService;
this.themeService = themeService;
}
public String list(Integer page, Model model) {
int pageSize = optionService
.getByPropertyOrDefault(SheetProperties.JOURNALS_PAGE_SIZE, Integer.class,
Integer.parseInt(SheetProperties.JOURNALS_PAGE_SIZE.defaultValue()));
Pageable pageable =
PageRequest.of(page >= 1 ? page - 1 : page, pageSize, Sort.by(DESC, "createTime"));
Page<Journal> journals = journalService.pageBy(JournalType.PUBLIC, pageable);
model.addAttribute("is_journals", true);
model.addAttribute("journals", journalService.convertToCmtCountDto(journals));
model.addAttribute("meta_keywords", optionService.getSeoKeywords());
model.addAttribute("meta_description", optionService.getSeoDescription());
return themeService.render("journals");
}
}

View File

@ -1,31 +0,0 @@
package run.halo.app.controller.content.model;
import org.springframework.stereotype.Component;
import org.springframework.ui.Model;
import run.halo.app.service.OptionService;
import run.halo.app.service.ThemeService;
/**
* @author ryanwang
* @date 2020-03-04
*/
@Component
public class LinkModel {
private final ThemeService themeService;
private final OptionService optionService;
public LinkModel(ThemeService themeService,
OptionService optionService) {
this.themeService = themeService;
this.optionService = optionService;
}
public String list(Model model) {
model.addAttribute("is_links", true);
model.addAttribute("meta_keywords", optionService.getSeoKeywords());
model.addAttribute("meta_description", optionService.getSeoDescription());
return themeService.render("links");
}
}

View File

@ -1,55 +0,0 @@
package run.halo.app.controller.content.model;
import static org.springframework.data.domain.Sort.Direction.DESC;
import org.springframework.data.domain.Page;
import org.springframework.data.domain.PageRequest;
import org.springframework.data.domain.Pageable;
import org.springframework.data.domain.Sort;
import org.springframework.stereotype.Component;
import org.springframework.ui.Model;
import run.halo.app.model.dto.PhotoDTO;
import run.halo.app.model.properties.SheetProperties;
import run.halo.app.service.OptionService;
import run.halo.app.service.PhotoService;
import run.halo.app.service.ThemeService;
/**
* @author ryanwang
* @date 2020-02-11
*/
@Component
public class PhotoModel {
private final PhotoService photoService;
private final ThemeService themeService;
private final OptionService optionService;
public PhotoModel(PhotoService photoService,
ThemeService themeService,
OptionService optionService) {
this.photoService = photoService;
this.themeService = themeService;
this.optionService = optionService;
}
public String list(Integer page, Model model) {
int pageSize = optionService.getByPropertyOrDefault(SheetProperties.PHOTOS_PAGE_SIZE,
Integer.class,
Integer.parseInt(SheetProperties.PHOTOS_PAGE_SIZE.defaultValue()));
Pageable pageable =
PageRequest.of(page >= 1 ? page - 1 : page, pageSize, Sort.by(DESC, "createTime"));
Page<PhotoDTO> photos = photoService.pageBy(pageable);
model.addAttribute("is_photos", true);
model.addAttribute("photos", photos);
model.addAttribute("meta_keywords", optionService.getSeoKeywords());
model.addAttribute("meta_description", optionService.getSeoDescription());
return themeService.render("photos");
}
}

View File

@ -1,208 +0,0 @@
package run.halo.app.controller.content.model;
import static run.halo.app.model.support.HaloConst.POST_PASSWORD_TEMPLATE;
import static run.halo.app.model.support.HaloConst.SUFFIX_FTL;
import java.util.List;
import java.util.stream.Collectors;
import org.apache.commons.lang3.StringUtils;
import org.springframework.data.domain.Page;
import org.springframework.data.domain.PageRequest;
import org.springframework.data.domain.Pageable;
import org.springframework.data.domain.Sort;
import org.springframework.stereotype.Component;
import org.springframework.ui.Model;
import run.halo.app.cache.AbstractStringCacheStore;
import run.halo.app.controller.content.auth.PostAuthentication;
import run.halo.app.exception.ForbiddenException;
import run.halo.app.exception.NotFoundException;
import run.halo.app.model.entity.Category;
import run.halo.app.model.entity.Post;
import run.halo.app.model.entity.PostMeta;
import run.halo.app.model.entity.Tag;
import run.halo.app.model.enums.EncryptTypeEnum;
import run.halo.app.model.enums.PostStatus;
import run.halo.app.model.vo.ArchiveYearVO;
import run.halo.app.model.vo.PostListVO;
import run.halo.app.service.CategoryService;
import run.halo.app.service.OptionService;
import run.halo.app.service.PostCategoryService;
import run.halo.app.service.PostMetaService;
import run.halo.app.service.PostService;
import run.halo.app.service.PostTagService;
import run.halo.app.service.TagService;
import run.halo.app.service.ThemeService;
import run.halo.app.service.assembler.PostRenderAssembler;
/**
* Post Model
*
* @author ryanwang
* @author guqing
* @date 2020-01-07
*/
@Component
public class PostModel {
private final PostRenderAssembler postRenderAssembler;
private final PostService postService;
private final ThemeService themeService;
private final PostCategoryService postCategoryService;
private final CategoryService categoryService;
private final PostTagService postTagService;
private final TagService tagService;
private final PostMetaService postMetaService;
private final OptionService optionService;
private final AbstractStringCacheStore cacheStore;
private final PostAuthentication postAuthentication;
public PostModel(PostRenderAssembler postRenderAssembler,
PostService postService,
ThemeService themeService,
PostCategoryService postCategoryService,
CategoryService categoryService,
PostMetaService postMetaService,
PostTagService postTagService,
TagService tagService,
OptionService optionService,
AbstractStringCacheStore cacheStore,
PostAuthentication postAuthentication) {
this.postRenderAssembler = postRenderAssembler;
this.postService = postService;
this.themeService = themeService;
this.postCategoryService = postCategoryService;
this.categoryService = categoryService;
this.postMetaService = postMetaService;
this.postTagService = postTagService;
this.tagService = tagService;
this.optionService = optionService;
this.cacheStore = cacheStore;
this.postAuthentication = postAuthentication;
}
public String content(Post post, String token, Model model) {
if (PostStatus.RECYCLE.equals(post.getStatus())) {
// Articles in the recycle bin are not allowed to be accessed.
throw new NotFoundException("查询不到该文章的信息");
} else if (StringUtils.isNotBlank(token)) {
// If the token is not empty, it means it is an admin request,
// then verify the token.
// verify token
String cachedToken = cacheStore.getAny(token, String.class)
.orElseThrow(() -> new ForbiddenException("您没有该文章的访问权限"));
if (!cachedToken.equals(token)) {
throw new ForbiddenException("您没有该文章的访问权限");
}
} else if (PostStatus.DRAFT.equals(post.getStatus())) {
// Drafts are not allowed bo be accessed by outsiders.
throw new NotFoundException("查询不到该文章的信息");
} else if (PostStatus.INTIMATE.equals(post.getStatus())
&& !postAuthentication.isAuthenticated(post.getId())
) {
// Encrypted articles must has the correct password before they can be accessed.
model.addAttribute("slug", post.getSlug());
model.addAttribute("type", EncryptTypeEnum.POST.getName());
if (themeService.templateExists(POST_PASSWORD_TEMPLATE + SUFFIX_FTL)) {
return themeService.render(POST_PASSWORD_TEMPLATE);
}
return "common/template/" + POST_PASSWORD_TEMPLATE;
}
post = postService.getById(post.getId());
postService.publishVisitEvent(post.getId());
postService.getPrevPost(post)
.ifPresent(prevPost -> model.addAttribute("prevPost",
postRenderAssembler.convertToDetailVo(prevPost)));
postService.getNextPost(post)
.ifPresent(nextPost -> model.addAttribute("nextPost",
postRenderAssembler.convertToDetailVo(nextPost)));
List<Category> categories = postCategoryService.listCategoriesBy(post.getId());
List<Tag> tags = postTagService.listTagsBy(post.getId());
List<PostMeta> metas = postMetaService.listBy(post.getId());
// Generate meta keywords.
if (StringUtils.isNotEmpty(post.getMetaKeywords())) {
model.addAttribute("meta_keywords", post.getMetaKeywords());
} else {
model.addAttribute("meta_keywords",
tags.stream().map(Tag::getName).collect(Collectors.joining(",")));
}
// Generate meta description.
if (StringUtils.isNotEmpty(post.getMetaDescription())) {
model.addAttribute("meta_description", post.getMetaDescription());
} else {
model.addAttribute("meta_description",
postService.generateDescription(post.getContent().getContent()));
}
model.addAttribute("is_post", true);
if (StringUtils.isNotBlank(token)) {
model.addAttribute("post", postRenderAssembler.convertToPreviewDetailVo(post));
} else {
model.addAttribute("post", postRenderAssembler.convertToDetailVo(post));
}
model.addAttribute("categories", categoryService.convertTo(categories));
model.addAttribute("tags", tagService.convertTo(tags));
model.addAttribute("metas", postMetaService.convertToMap(metas));
if (themeService.templateExists(
ThemeService.CUSTOM_POST_PREFIX + post.getTemplate() + SUFFIX_FTL)) {
return themeService.render(ThemeService.CUSTOM_POST_PREFIX + post.getTemplate());
}
return themeService.render("post");
}
public String list(Integer page, Model model) {
int pageSize = optionService.getPostPageSize();
Pageable pageable = PageRequest
.of(page >= 1 ? page - 1 : page, pageSize, postService.getPostDefaultSort());
Page<Post> postPage = postService.pageBy(PostStatus.PUBLISHED, pageable);
Page<PostListVO> posts = postRenderAssembler.convertToListVo(postPage);
model.addAttribute("is_index", true);
model.addAttribute("posts", posts);
model.addAttribute("meta_keywords", optionService.getSeoKeywords());
model.addAttribute("meta_description", optionService.getSeoDescription());
return themeService.render("index");
}
public String archives(Integer page, Model model) {
int pageSize = optionService.getArchivesPageSize();
Pageable pageable = PageRequest
.of(page >= 1 ? page - 1 : page, pageSize, Sort.by(Sort.Direction.DESC, "createTime"));
Page<Post> postPage = postService.pageBy(PostStatus.PUBLISHED, pageable);
Page<PostListVO> posts = postRenderAssembler.convertToListVo(postPage);
List<ArchiveYearVO> archives =
postRenderAssembler.convertToYearArchives(postPage.getContent());
model.addAttribute("is_archives", true);
model.addAttribute("posts", posts);
model.addAttribute("archives", archives);
model.addAttribute("meta_keywords", optionService.getSeoKeywords());
model.addAttribute("meta_description", optionService.getSeoDescription());
return themeService.render("archives");
}
}

View File

@ -1,109 +0,0 @@
package run.halo.app.controller.content.model;
import java.util.List;
import org.apache.commons.lang3.StringUtils;
import org.springframework.stereotype.Component;
import org.springframework.ui.Model;
import run.halo.app.cache.AbstractStringCacheStore;
import run.halo.app.exception.ForbiddenException;
import run.halo.app.model.entity.Sheet;
import run.halo.app.model.entity.SheetMeta;
import run.halo.app.model.enums.PostStatus;
import run.halo.app.model.support.HaloConst;
import run.halo.app.model.vo.SheetDetailVO;
import run.halo.app.service.OptionService;
import run.halo.app.service.SheetMetaService;
import run.halo.app.service.SheetService;
import run.halo.app.service.ThemeService;
import run.halo.app.service.assembler.SheetRenderAssembler;
/**
* Sheet model.
*
* @author ryanwang
* @date 2020-01-07
*/
@Component
public class SheetModel {
private final SheetService sheetService;
private final SheetRenderAssembler sheetRenderAssembler;
private final SheetMetaService sheetMetaService;
private final AbstractStringCacheStore cacheStore;
private final ThemeService themeService;
private final OptionService optionService;
public SheetModel(SheetService sheetService,
SheetRenderAssembler sheetRenderAssembler,
SheetMetaService sheetMetaService,
AbstractStringCacheStore cacheStore,
ThemeService themeService,
OptionService optionService) {
this.sheetService = sheetService;
this.sheetRenderAssembler = sheetRenderAssembler;
this.sheetMetaService = sheetMetaService;
this.cacheStore = cacheStore;
this.themeService = themeService;
this.optionService = optionService;
}
/**
* Sheet content.
*
* @param sheet sheet
* @param token token
* @param model model
* @return template name
*/
public String content(Sheet sheet, String token, Model model) {
SheetDetailVO sheetDetailVo;
if (StringUtils.isEmpty(token)) {
sheet = sheetService.getBy(PostStatus.PUBLISHED, sheet.getSlug());
sheetDetailVo = sheetRenderAssembler.convertToDetailVo(sheet);
} else {
// verify token
String cachedToken = cacheStore.getAny(token, String.class)
.orElseThrow(() -> new ForbiddenException("您没有该页面的访问权限"));
if (!cachedToken.equals(token)) {
throw new ForbiddenException("您没有该页面的访问权限");
}
sheetDetailVo = sheetRenderAssembler.convertToPreviewDetailVo(sheet);
}
sheetService.publishVisitEvent(sheet.getId());
List<SheetMeta> metas = sheetMetaService.listBy(sheet.getId());
// Generate meta keywords.
if (StringUtils.isNotEmpty(sheet.getMetaKeywords())) {
model.addAttribute("meta_keywords", sheet.getMetaKeywords());
} else {
model.addAttribute("meta_keywords", optionService.getSeoKeywords());
}
// Generate meta description.
if (StringUtils.isNotEmpty(sheet.getMetaDescription())) {
model.addAttribute("meta_description", sheet.getMetaDescription());
} else {
model.addAttribute("meta_description",
sheetService.generateDescription(sheet.getContent().getContent()));
}
// sheet and post all can use
model.addAttribute("sheet", sheetDetailVo);
model.addAttribute("post", sheetDetailVo);
model.addAttribute("is_sheet", true);
model.addAttribute("metas", sheetMetaService.convertToMap(metas));
if (themeService.templateExists(
ThemeService.CUSTOM_SHEET_PREFIX + sheet.getTemplate() + HaloConst.SUFFIX_FTL)) {
return themeService.render(ThemeService.CUSTOM_SHEET_PREFIX + sheet.getTemplate());
}
return themeService.render("sheet");
}
}

View File

@ -1,78 +0,0 @@
package run.halo.app.controller.content.model;
import static org.springframework.data.domain.Sort.Direction.DESC;
import org.springframework.data.domain.Page;
import org.springframework.data.domain.PageRequest;
import org.springframework.data.domain.Pageable;
import org.springframework.data.domain.Sort;
import org.springframework.stereotype.Component;
import org.springframework.ui.Model;
import run.halo.app.model.dto.TagDTO;
import run.halo.app.model.entity.Post;
import run.halo.app.model.entity.Tag;
import run.halo.app.model.enums.PostStatus;
import run.halo.app.model.vo.PostListVO;
import run.halo.app.service.OptionService;
import run.halo.app.service.PostTagService;
import run.halo.app.service.TagService;
import run.halo.app.service.ThemeService;
import run.halo.app.service.assembler.PostRenderAssembler;
/**
* Tag Model.
*
* @author ryanwang
* @date 2020-01-11
*/
@Component
public class TagModel {
private final TagService tagService;
private final PostRenderAssembler postRenderAssembler;
private final PostTagService postTagService;
private final OptionService optionService;
private final ThemeService themeService;
public TagModel(TagService tagService,
PostRenderAssembler postRenderAssembler,
PostTagService postTagService,
OptionService optionService,
ThemeService themeService) {
this.tagService = tagService;
this.postRenderAssembler = postRenderAssembler;
this.postTagService = postTagService;
this.optionService = optionService;
this.themeService = themeService;
}
public String list(Model model) {
model.addAttribute("is_tags", true);
model.addAttribute("meta_keywords", optionService.getSeoKeywords());
model.addAttribute("meta_description", optionService.getSeoDescription());
return themeService.render("tags");
}
public String listPost(Model model, String slug, Integer page) {
// Get tag by slug
final Tag tag = tagService.getBySlugOfNonNull(slug);
TagDTO tagDTO = tagService.convertTo(tag);
final Pageable pageable = PageRequest
.of(page - 1, optionService.getArchivesPageSize(), Sort.by(DESC, "createTime"));
Page<Post> postPage =
postTagService.pagePostsBy(tag.getId(), PostStatus.PUBLISHED, pageable);
Page<PostListVO> posts = postRenderAssembler.convertToListVo(postPage);
model.addAttribute("is_tag", true);
model.addAttribute("posts", posts);
model.addAttribute("tag", tagDTO);
model.addAttribute("meta_keywords", optionService.getSeoKeywords());
model.addAttribute("meta_description", optionService.getSeoDescription());
return themeService.render("tag");
}
}

View File

@ -1,61 +0,0 @@
package run.halo.app.controller.error;
import java.util.List;
import javax.servlet.RequestDispatcher;
import javax.servlet.http.HttpServletRequest;
import lombok.extern.slf4j.Slf4j;
import org.springframework.boot.autoconfigure.web.ServerProperties;
import org.springframework.boot.autoconfigure.web.servlet.error.BasicErrorController;
import org.springframework.boot.autoconfigure.web.servlet.error.ErrorViewResolver;
import org.springframework.boot.web.servlet.error.ErrorAttributes;
import org.springframework.http.HttpStatus;
import org.springframework.stereotype.Component;
import org.springframework.web.util.NestedServletException;
import run.halo.app.exception.AbstractHaloException;
/**
* Default error controller.
*
* @author johnniang
*/
@Component
@Slf4j
public class DefaultErrorController extends BasicErrorController {
public DefaultErrorController(
ErrorAttributes errorAttributes,
ServerProperties serverProperties,
List<ErrorViewResolver> errorViewResolvers) {
super(errorAttributes, serverProperties.getError(), errorViewResolvers);
}
@Override
protected HttpStatus getStatus(HttpServletRequest request) {
var status = super.getStatus(request);
// deduce status
var exception = request.getAttribute(RequestDispatcher.ERROR_EXCEPTION);
if (exception instanceof NestedServletException) {
var nse = (NestedServletException) exception;
if (nse.getCause() instanceof AbstractHaloException) {
status = resolveHaloException((AbstractHaloException) nse.getCause(), request);
}
} else if (exception instanceof AbstractHaloException) {
status = resolveHaloException((AbstractHaloException) exception, request);
}
return status;
}
private HttpStatus resolveHaloException(AbstractHaloException haloException,
HttpServletRequest request) {
HttpStatus status = haloException.getStatus();
if (log.isDebugEnabled()) {
log.error("Halo exception occurred.", haloException);
}
// reset status
request.setAttribute(RequestDispatcher.ERROR_STATUS_CODE, status.value());
request.setAttribute(RequestDispatcher.ERROR_EXCEPTION, haloException);
request.setAttribute(RequestDispatcher.ERROR_MESSAGE, haloException.getMessage());
request.setAttribute(RequestDispatcher.ERROR_EXCEPTION_TYPE, haloException.getClass());
return status;
}
}

View File

@ -1,79 +0,0 @@
package run.halo.app.controller.error;
import java.util.Collections;
import java.util.EnumMap;
import java.util.HashMap;
import java.util.Map;
import javax.servlet.http.HttpServletRequest;
import org.springframework.boot.autoconfigure.template.TemplateAvailabilityProviders;
import org.springframework.boot.autoconfigure.web.servlet.error.ErrorViewResolver;
import org.springframework.context.ApplicationContext;
import org.springframework.http.HttpStatus;
import org.springframework.http.HttpStatus.Series;
import org.springframework.stereotype.Component;
import org.springframework.web.servlet.ModelAndView;
import run.halo.app.service.ThemeService;
@Component
public class DefaultErrorViewResolver implements ErrorViewResolver {
private static final Map<Series, String> SERIES_VIEWS;
static {
EnumMap<Series, String> views = new EnumMap<>(Series.class);
views.put(Series.CLIENT_ERROR, "4xx");
views.put(Series.SERVER_ERROR, "5xx");
SERIES_VIEWS = Collections.unmodifiableMap(views);
}
private final ThemeService themeService;
private final TemplateAvailabilityProviders templateAvailabilityProviders;
private final ApplicationContext applicationContext;
public DefaultErrorViewResolver(ThemeService themeService,
ApplicationContext applicationContext) {
this.themeService = themeService;
this.applicationContext = applicationContext;
this.templateAvailabilityProviders = new TemplateAvailabilityProviders(applicationContext);
}
@Override
public ModelAndView resolveErrorView(HttpServletRequest request, HttpStatus status,
Map<String, Object> model) {
// for compatibility
var errorModel = new HashMap<>(model);
// resolve with status code. eg: 400.ftl
var modelAndView = resolve(String.valueOf(status.value()), errorModel);
// resolve with status series. eg: 4xx.ftl
if (modelAndView == null && SERIES_VIEWS.containsKey(status.series())) {
modelAndView = resolve(SERIES_VIEWS.get(status.series()), errorModel);
}
// resolve error template. eg: error.ftl
if (modelAndView == null) {
modelAndView = resolve("error", errorModel);
}
if (modelAndView == null) {
// resolve common error template
modelAndView = new ModelAndView("common/error/error", errorModel);
}
return modelAndView;
}
private ModelAndView resolve(String viewName, Map<String, Object> model) {
var errorViewName = this.themeService.render(viewName);
var provider =
this.templateAvailabilityProviders.getProvider(errorViewName, this.applicationContext);
if (provider != null) {
return new ModelAndView(errorViewName, model);
}
return null;
}
}

View File

@ -1,88 +0,0 @@
package run.halo.app.core;
import org.springframework.core.MethodParameter;
import org.springframework.http.HttpStatus;
import org.springframework.http.MediaType;
import org.springframework.http.converter.HttpMessageConverter;
import org.springframework.http.converter.json.AbstractJackson2HttpMessageConverter;
import org.springframework.http.converter.json.MappingJacksonValue;
import org.springframework.http.server.ServerHttpRequest;
import org.springframework.http.server.ServerHttpResponse;
import org.springframework.http.server.ServletServerHttpResponse;
import org.springframework.lang.NonNull;
import org.springframework.lang.Nullable;
import org.springframework.web.bind.annotation.ControllerAdvice;
import org.springframework.web.servlet.mvc.method.annotation.ResponseBodyAdvice;
import run.halo.app.model.support.BaseResponse;
/**
* Controller advice for comment result.
*
* @author johnniang
*/
@ControllerAdvice("run.halo.app.controller")
public class CommonResultControllerAdvice implements ResponseBodyAdvice<Object> {
@Override
public boolean supports(MethodParameter returnType,
@NonNull Class<? extends HttpMessageConverter<?>> converterType) {
return AbstractJackson2HttpMessageConverter.class.isAssignableFrom(converterType);
}
@Override
@NonNull
public final Object beforeBodyWrite(@Nullable Object body,
@NonNull MethodParameter returnType,
@NonNull MediaType contentType,
@NonNull Class<? extends HttpMessageConverter<?>> converterType,
@NonNull ServerHttpRequest request,
@NonNull ServerHttpResponse response) {
MappingJacksonValue container = getOrCreateContainer(body);
// The contain body will never be null
beforeBodyWriteInternal(container, contentType, returnType, request, response);
return container;
}
/**
* Wrap the body in a {@link MappingJacksonValue} value container (for providing
* additional serialization instructions) or simply cast it if already wrapped.
*/
private MappingJacksonValue getOrCreateContainer(Object body) {
return body instanceof MappingJacksonValue ? (MappingJacksonValue) body :
new MappingJacksonValue(body);
}
private void beforeBodyWriteInternal(MappingJacksonValue bodyContainer,
MediaType contentType,
MethodParameter returnType,
ServerHttpRequest request,
ServerHttpResponse response) {
// Get return body
Object returnBody = bodyContainer.getValue();
if (returnBody instanceof BaseResponse) {
// If the return body is instance of BaseResponse, then just do nothing
BaseResponse<?> baseResponse = (BaseResponse<?>) returnBody;
HttpStatus status = HttpStatus.resolve(baseResponse.getStatus());
if (status == null) {
status = HttpStatus.INTERNAL_SERVER_ERROR;
}
response.setStatusCode(status);
return;
}
// get status
var status = HttpStatus.OK;
if (response instanceof ServletServerHttpResponse) {
var servletResponse =
((ServletServerHttpResponse) response).getServletResponse();
status = HttpStatus.resolve(servletResponse.getStatus());
if (status == null) {
status = HttpStatus.INTERNAL_SERVER_ERROR;
}
}
var baseResponse = new BaseResponse<>(status.value(), status.getReasonPhrase(), returnBody);
bodyContainer.setValue(baseResponse);
}
}

View File

@ -1,161 +0,0 @@
package run.halo.app.core;
import java.util.Map;
import javax.validation.ConstraintViolationException;
import lombok.extern.slf4j.Slf4j;
import org.springframework.dao.DataIntegrityViolationException;
import org.springframework.http.HttpStatus;
import org.springframework.http.ResponseEntity;
import org.springframework.http.converter.HttpMessageNotReadableException;
import org.springframework.util.Assert;
import org.springframework.web.HttpMediaTypeNotAcceptableException;
import org.springframework.web.HttpRequestMethodNotSupportedException;
import org.springframework.web.bind.MethodArgumentNotValidException;
import org.springframework.web.bind.MissingServletRequestParameterException;
import org.springframework.web.bind.annotation.ExceptionHandler;
import org.springframework.web.bind.annotation.ResponseStatus;
import org.springframework.web.bind.annotation.RestControllerAdvice;
import org.springframework.web.multipart.MaxUploadSizeExceededException;
import org.springframework.web.servlet.NoHandlerFoundException;
import run.halo.app.exception.AbstractHaloException;
import run.halo.app.model.support.BaseResponse;
import run.halo.app.utils.ExceptionUtils;
import run.halo.app.utils.ValidationUtils;
/**
* Exception handler of controller.
*
* @author johnniang
*/
@RestControllerAdvice(value = {"run.halo.app.controller.admin.api",
"run.halo.app.controller.content.api"})
@Slf4j
public class ControllerExceptionHandler {
@ExceptionHandler(DataIntegrityViolationException.class)
@ResponseStatus(HttpStatus.BAD_REQUEST)
public BaseResponse<?> handleDataIntegrityViolationException(
DataIntegrityViolationException e) {
BaseResponse<?> baseResponse = handleBaseException(e);
if (e.getCause() instanceof org.hibernate.exception.ConstraintViolationException) {
baseResponse = handleBaseException(e.getCause());
}
baseResponse.setMessage("字段验证错误,请完善后重试!");
return baseResponse;
}
@ExceptionHandler(MissingServletRequestParameterException.class)
@ResponseStatus(HttpStatus.BAD_REQUEST)
public BaseResponse<?> handleMissingServletRequestParameterException(
MissingServletRequestParameterException e) {
BaseResponse<?> baseResponse = handleBaseException(e);
baseResponse.setMessage(
String.format("请求字段缺失, 类型为 %s名称为 %s", e.getParameterType(), e.getParameterName()));
return baseResponse;
}
@ExceptionHandler(ConstraintViolationException.class)
@ResponseStatus(HttpStatus.BAD_REQUEST)
public BaseResponse<?> handleConstraintViolationException(ConstraintViolationException e) {
BaseResponse<Map<String, String>> baseResponse = handleBaseException(e);
baseResponse.setStatus(HttpStatus.BAD_REQUEST.value());
baseResponse.setMessage("字段验证错误,请完善后重试!");
baseResponse.setData(ValidationUtils.mapWithValidError(e.getConstraintViolations()));
return baseResponse;
}
@ExceptionHandler(MethodArgumentNotValidException.class)
@ResponseStatus(HttpStatus.BAD_REQUEST)
public BaseResponse<?> handleMethodArgumentNotValidException(
MethodArgumentNotValidException e) {
BaseResponse<Map<String, String>> baseResponse = handleBaseException(e);
baseResponse.setStatus(HttpStatus.BAD_REQUEST.value());
baseResponse.setMessage("字段验证错误,请完善后重试!");
Map<String, String> errMap =
ValidationUtils.mapWithFieldError(e.getBindingResult().getFieldErrors());
baseResponse.setData(errMap);
return baseResponse;
}
@ExceptionHandler(HttpRequestMethodNotSupportedException.class)
@ResponseStatus(HttpStatus.BAD_REQUEST)
public BaseResponse<?> handleHttpRequestMethodNotSupportedException(
HttpRequestMethodNotSupportedException e) {
BaseResponse<?> baseResponse = handleBaseException(e);
baseResponse.setStatus(HttpStatus.BAD_REQUEST.value());
return baseResponse;
}
@ExceptionHandler(HttpMediaTypeNotAcceptableException.class)
@ResponseStatus(HttpStatus.NOT_ACCEPTABLE)
public BaseResponse<?> handleHttpMediaTypeNotAcceptableException(
HttpMediaTypeNotAcceptableException e) {
BaseResponse<?> baseResponse = handleBaseException(e);
baseResponse.setStatus(HttpStatus.NOT_ACCEPTABLE.value());
return baseResponse;
}
@ExceptionHandler(HttpMessageNotReadableException.class)
@ResponseStatus(HttpStatus.BAD_REQUEST)
public BaseResponse<?> handleHttpMessageNotReadableException(
HttpMessageNotReadableException e) {
BaseResponse<?> baseResponse = handleBaseException(e);
baseResponse.setStatus(HttpStatus.BAD_REQUEST.value());
baseResponse.setMessage("缺失请求主体");
return baseResponse;
}
@ExceptionHandler(NoHandlerFoundException.class)
@ResponseStatus(HttpStatus.BAD_GATEWAY)
public BaseResponse<?> handleNoHandlerFoundException(NoHandlerFoundException e) {
BaseResponse<?> baseResponse = handleBaseException(e);
HttpStatus status = HttpStatus.BAD_GATEWAY;
baseResponse.setStatus(status.value());
baseResponse.setMessage(status.getReasonPhrase());
return baseResponse;
}
@ExceptionHandler(MaxUploadSizeExceededException.class)
@ResponseStatus(HttpStatus.BAD_REQUEST)
public BaseResponse<?> handleUploadSizeExceededException(MaxUploadSizeExceededException e) {
BaseResponse<Object> response = handleBaseException(e);
response.setStatus(HttpStatus.BAD_REQUEST.value());
response.setMessage("当前请求超出最大限制:" + e.getMaxUploadSize() + " bytes");
return response;
}
@ExceptionHandler(AbstractHaloException.class)
public ResponseEntity<BaseResponse<?>> handleHaloException(AbstractHaloException e) {
BaseResponse<Object> baseResponse = handleBaseException(e);
baseResponse.setStatus(e.getStatus().value());
baseResponse.setData(e.getErrorData());
return new ResponseEntity<>(baseResponse, e.getStatus());
}
@ExceptionHandler(Exception.class)
@ResponseStatus(HttpStatus.INTERNAL_SERVER_ERROR)
public BaseResponse<?> handleGlobalException(Exception e) {
BaseResponse<?> baseResponse = handleBaseException(e);
HttpStatus status = HttpStatus.INTERNAL_SERVER_ERROR;
baseResponse.setStatus(status.value());
baseResponse.setMessage(status.getReasonPhrase());
return baseResponse;
}
private <T> BaseResponse<T> handleBaseException(Throwable t) {
Assert.notNull(t, "Throwable must not be null");
BaseResponse<T> baseResponse = new BaseResponse<>();
baseResponse.setMessage(t.getMessage());
log.error("Captured an exception:", t);
if (log.isDebugEnabled()) {
baseResponse.setDevMessage(ExceptionUtils.getStackTrace(t));
}
return baseResponse;
}
}

View File

@ -1,144 +0,0 @@
package run.halo.app.core;
import com.fasterxml.jackson.core.JsonProcessingException;
import java.lang.reflect.Method;
import java.util.Objects;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import lombok.extern.slf4j.Slf4j;
import org.aspectj.lang.ProceedingJoinPoint;
import org.aspectj.lang.annotation.Around;
import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.annotation.Pointcut;
import org.aspectj.lang.reflect.MethodSignature;
import org.springframework.core.io.Resource;
import org.springframework.http.ResponseEntity;
import org.springframework.lang.NonNull;
import org.springframework.stereotype.Component;
import org.springframework.util.Assert;
import org.springframework.util.StopWatch;
import org.springframework.web.context.request.RequestContextHolder;
import org.springframework.web.context.request.ServletRequestAttributes;
import org.springframework.web.multipart.MultipartFile;
import run.halo.app.utils.JsonUtils;
import run.halo.app.utils.ServletUtils;
/**
* @author johnniang
*/
@Aspect
@Component
@Slf4j
public class ControllerLogAop {
@Pointcut("@within(org.springframework.web.bind.annotation.RestController)")
public void restController() {
}
@Pointcut("@within(org.springframework.stereotype.Controller)")
public void controller() {
}
@Around("controller() || restController()")
public Object controller(ProceedingJoinPoint joinPoint) throws Throwable {
MethodSignature signature = (MethodSignature) joinPoint.getSignature();
final Method method = signature.getMethod();
if (method == null || !log.isDebugEnabled()) {
// should never happen
return joinPoint.proceed();
}
String className = joinPoint.getTarget().getClass().getSimpleName();
String methodName = joinPoint.getSignature().getName();
Object[] args = joinPoint.getArgs();
// Get request attribute
ServletRequestAttributes requestAttributes =
(ServletRequestAttributes) RequestContextHolder.getRequestAttributes();
HttpServletRequest request = Objects.requireNonNull(requestAttributes).getRequest();
final StopWatch watch = new StopWatch(request.getRequestURI());
watch.start("PrintRequest");
printRequestLog(request, className, methodName, args);
watch.stop();
watch.start(className + "#" + methodName);
final Object returnObj = joinPoint.proceed();
watch.stop();
watch.start("PrintResponse");
printResponseLog(request, className, methodName, returnObj);
watch.stop();
if (log.isDebugEnabled()) {
log.debug("Usage:\n{}", watch.prettyPrint());
}
return returnObj;
}
private void printRequestLog(HttpServletRequest request, String clazzName, String methodName,
Object[] args) throws JsonProcessingException {
log.debug("Request URL: [{}], URI: [{}], Request Method: [{}], IP: [{}]",
request.getRequestURL(),
request.getRequestURI(),
request.getMethod(),
ServletUtils.getClientIP(request));
if (args == null || !log.isDebugEnabled()) {
return;
}
boolean shouldNotLog = false;
for (Object arg : args) {
if (arg == null
|| arg instanceof HttpServletRequest
|| arg instanceof HttpServletResponse
|| arg instanceof MultipartFile
|| arg.getClass().isAssignableFrom(MultipartFile[].class)) {
shouldNotLog = true;
break;
}
}
if (!shouldNotLog) {
String requestBody = JsonUtils.objectToJson(args);
log.debug("{}.{} Parameters: [{}]", clazzName, methodName, requestBody);
}
}
private void printResponseLog(HttpServletRequest request, String className, String methodName,
Object returnObj)
throws JsonProcessingException {
if (log.isDebugEnabled()) {
String returnData = "";
if (returnObj != null) {
if (returnObj instanceof ResponseEntity) {
ResponseEntity<?> responseEntity = (ResponseEntity<?>) returnObj;
if (responseEntity.getBody() instanceof Resource) {
returnData = "[ BINARY DATA ]";
} else if (responseEntity.getBody() != null) {
returnData = toString(responseEntity.getBody());
}
} else {
returnData = toString(returnObj);
}
}
log.debug("{}.{} Response: [{}]", className, methodName, returnData);
}
}
@NonNull
private String toString(@NonNull Object obj) throws JsonProcessingException {
Assert.notNull(obj, "Return object must not be null");
String toString;
if (obj.getClass().isAssignableFrom(byte[].class) && obj instanceof Resource) {
toString = "[ BINARY DATA ]";
} else {
toString = JsonUtils.objectToJson(obj);
}
return toString;
}
}

View File

@ -1,43 +0,0 @@
package run.halo.app.core;
import com.fasterxml.jackson.core.JsonGenerator;
import com.fasterxml.jackson.databind.JsonSerializer;
import com.fasterxml.jackson.databind.SerializerProvider;
import java.io.IOException;
import org.springframework.data.domain.Page;
import run.halo.app.model.support.CommentPage;
/**
* Custom serializer for Page object.
*
* @author johnniang
* @date 3/19/19
*/
public class PageJacksonSerializer extends JsonSerializer<Page> {
@Override
public void serialize(Page page, JsonGenerator generator, SerializerProvider serializers)
throws IOException {
generator.writeStartObject();
generator.writeObjectField("content", page.getContent());
generator.writeNumberField("pages", page.getTotalPages());
generator.writeNumberField("total", page.getTotalElements());
generator.writeNumberField("page", page.getNumber());
generator.writeNumberField("rpp", page.getSize());
generator.writeBooleanField("hasNext", page.hasNext());
generator.writeBooleanField("hasPrevious", page.hasPrevious());
generator.writeBooleanField("isFirst", page.isFirst());
generator.writeBooleanField("isLast", page.isLast());
generator.writeBooleanField("isEmpty", page.isEmpty());
generator.writeBooleanField("hasContent", page.hasContent());
// Handle comment page
if (page instanceof CommentPage) {
CommentPage commentPage = (CommentPage) page;
generator.writeNumberField("commentCount", commentPage.getCommentCount());
}
generator.writeEndObject();
}
}

View File

@ -1,46 +0,0 @@
package run.halo.app.core.freemarker.inheritance;
import freemarker.core.Environment;
import freemarker.template.SimpleScalar;
import freemarker.template.TemplateDirectiveBody;
import freemarker.template.TemplateException;
import freemarker.template.TemplateModel;
import java.io.IOException;
import java.util.Map;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
import kr.pe.kwonnam.freemarker.inheritance.ExtendsDirective;
import org.springframework.stereotype.Component;
/**
* @author LIlGG
* @date 2021/3/4
*/
@Component
public class ThemeExtendsDirective extends ExtendsDirective {
private static final Pattern THEME_TEMPLATE_PATH_PATTERN = Pattern.compile("^themes/.*?/");
@Override
@SuppressWarnings("unchecked")
public void execute(Environment env, Map params, TemplateModel[] loopVars,
TemplateDirectiveBody body) throws TemplateException, IOException {
String currTemplateName = getTemplateRelativePath(env);
String name = ((SimpleScalar) params.get("name")).getAsString();
String includeTemplateName = env.rootBasedToAbsoluteTemplateName(
env.toFullTemplateName(currTemplateName, name)
);
params.put("name", new SimpleScalar(includeTemplateName));
super.execute(env, params, loopVars, body);
}
private String getTemplateRelativePath(Environment env) {
String templateName = env.getCurrentTemplate().getName();
Matcher matcher = THEME_TEMPLATE_PATH_PATTERN.matcher(templateName);
return matcher.find()
? matcher.group()
: "";
}
}

View File

@ -1,48 +0,0 @@
package run.halo.app.core.freemarker.method;
import freemarker.template.Configuration;
import freemarker.template.SimpleNumber;
import freemarker.template.TemplateMethodModelEx;
import freemarker.template.TemplateModelException;
import java.util.List;
import org.apache.commons.lang3.RandomUtils;
import org.springframework.stereotype.Component;
import run.halo.app.utils.HaloUtils;
/**
* Freemarker template random method.
*
* @author ryanwang
* @date 2018-12-21
*/
@Component
public class RandomMethod implements TemplateMethodModelEx {
/**
* Constructor.
*
* @param configuration injected by spring.
*/
public RandomMethod(Configuration configuration) {
configuration.setSharedVariable("randomMethod", this);
}
/**
*
*
* @param arguments
* @return Object
* @throws TemplateModelException TemplateModelException
*/
@Override
public Object exec(List arguments) throws TemplateModelException {
if (arguments.size() != 2) {
throw new TemplateModelException("Wrong arguments! 2 arguments are needed");
}
SimpleNumber argOne = (SimpleNumber) arguments.get(0);
SimpleNumber argTwo = (SimpleNumber) arguments.get(1);
int start = argOne.getAsNumber().intValue();
int end = argTwo.getAsNumber().intValue();
return RandomUtils.nextInt(start, end);
}
}

View File

@ -1,75 +0,0 @@
package run.halo.app.core.freemarker.tag;
import static org.springframework.data.domain.Sort.Direction.ASC;
import freemarker.core.Environment;
import freemarker.template.Configuration;
import freemarker.template.DefaultObjectWrapperBuilder;
import freemarker.template.TemplateDirectiveBody;
import freemarker.template.TemplateDirectiveModel;
import freemarker.template.TemplateException;
import freemarker.template.TemplateModel;
import java.io.IOException;
import java.util.List;
import java.util.Map;
import org.springframework.data.domain.Sort;
import org.springframework.stereotype.Component;
import run.halo.app.model.entity.Category;
import run.halo.app.model.support.HaloConst;
import run.halo.app.service.CategoryService;
import run.halo.app.service.PostCategoryService;
/**
* Freemarker custom tag of category.
*
* @author ryanwang
* @date 2019-03-22
*/
@Component
public class CategoryTagDirective implements TemplateDirectiveModel {
private final CategoryService categoryService;
private final PostCategoryService postCategoryService;
public CategoryTagDirective(Configuration configuration,
CategoryService categoryService,
PostCategoryService postCategoryService) {
this.categoryService = categoryService;
this.postCategoryService = postCategoryService;
configuration.setSharedVariable("categoryTag", this);
}
@Override
public void execute(Environment env, Map params, TemplateModel[] loopVars,
TemplateDirectiveBody body) throws TemplateException, IOException {
final DefaultObjectWrapperBuilder builder =
new DefaultObjectWrapperBuilder(Configuration.VERSION_2_3_25);
if (params.containsKey(HaloConst.METHOD_KEY)) {
String method = params.get(HaloConst.METHOD_KEY).toString();
switch (method) {
case "list":
env.setVariable("categories", builder.build().wrap(postCategoryService
.listCategoryWithPostCountDto(Sort.by(ASC, "priority"))));
break;
case "tree":
env.setVariable("categories", builder.build()
.wrap(categoryService.listAsTree(Sort.by(ASC, "priority"))));
break;
case "listByPostId":
Integer postId = Integer.parseInt(params.get("postId").toString());
List<Category> categories = postCategoryService.listCategoriesBy(postId);
env.setVariable("categories",
builder.build().wrap(categoryService.convertTo(categories)));
break;
case "count":
env.setVariable("count", builder.build().wrap(categoryService.count()));
break;
default:
break;
}
}
body.render(env.getOut());
}
}

View File

@ -1,60 +0,0 @@
package run.halo.app.core.freemarker.tag;
import freemarker.core.Environment;
import freemarker.template.Configuration;
import freemarker.template.DefaultObjectWrapperBuilder;
import freemarker.template.TemplateDirectiveBody;
import freemarker.template.TemplateDirectiveModel;
import freemarker.template.TemplateException;
import freemarker.template.TemplateModel;
import java.io.IOException;
import java.util.Map;
import org.springframework.data.domain.Page;
import org.springframework.stereotype.Component;
import run.halo.app.model.entity.PostComment;
import run.halo.app.model.enums.CommentStatus;
import run.halo.app.model.support.HaloConst;
import run.halo.app.service.PostCommentService;
/**
* Freemarker custom tag of comment.
*
* @author ryanwang
* @date 2019-03-22
*/
@Component
public class CommentTagDirective implements TemplateDirectiveModel {
private final PostCommentService postCommentService;
public CommentTagDirective(Configuration configuration, PostCommentService postCommentService) {
this.postCommentService = postCommentService;
configuration.setSharedVariable("commentTag", this);
}
@Override
public void execute(Environment env, Map params, TemplateModel[] loopVars,
TemplateDirectiveBody body) throws TemplateException, IOException {
final DefaultObjectWrapperBuilder builder =
new DefaultObjectWrapperBuilder(Configuration.VERSION_2_3_25);
if (params.containsKey(HaloConst.METHOD_KEY)) {
String method = params.get(HaloConst.METHOD_KEY).toString();
switch (method) {
case "latest":
int top = Integer.parseInt(params.get("top").toString());
Page<PostComment> postComments =
postCommentService.pageLatest(top, CommentStatus.PUBLISHED);
env.setVariable("comments",
builder.build().wrap(postCommentService.convertToWithPostVo(postComments)));
break;
case "count":
env.setVariable("count", builder.build().wrap(postCommentService.count()));
break;
default:
break;
}
}
body.render(env.getOut());
}
}

View File

@ -1,67 +0,0 @@
package run.halo.app.core.freemarker.tag;
import static org.springframework.data.domain.Sort.Direction.DESC;
import freemarker.core.Environment;
import freemarker.template.Configuration;
import freemarker.template.DefaultObjectWrapperBuilder;
import freemarker.template.TemplateDirectiveBody;
import freemarker.template.TemplateDirectiveModel;
import freemarker.template.TemplateException;
import freemarker.template.TemplateModel;
import java.io.IOException;
import java.util.Map;
import org.springframework.data.domain.Sort;
import org.springframework.stereotype.Component;
import run.halo.app.model.support.HaloConst;
import run.halo.app.service.LinkService;
/**
* Freemarker custom tag of link.
*
* @author ryanwang
* @date 2019-03-22
*/
@Component
public class LinkTagDirective implements TemplateDirectiveModel {
private final LinkService linkService;
public LinkTagDirective(Configuration configuration, LinkService linkService) {
this.linkService = linkService;
configuration.setSharedVariable("linkTag", this);
}
@Override
public void execute(Environment env, Map params, TemplateModel[] loopVars,
TemplateDirectiveBody body) throws TemplateException, IOException {
final DefaultObjectWrapperBuilder builder =
new DefaultObjectWrapperBuilder(Configuration.VERSION_2_3_25);
if (params.containsKey(HaloConst.METHOD_KEY)) {
String method = params.get(HaloConst.METHOD_KEY).toString();
switch (method) {
case "list":
env.setVariable("links", builder.build().wrap(linkService.listAll()));
break;
case "listByRandom":
env.setVariable("links", builder.build().wrap(linkService.listAllByRandom()));
break;
case "listTeams":
env.setVariable("teams",
builder.build().wrap(linkService.listTeamVos(Sort.by(DESC, "createTime"))));
break;
case "listTeamsByRandom":
env.setVariable("teams", builder.build()
.wrap(linkService.listTeamVosByRandom(Sort.by(DESC, "createTime"))));
break;
case "count":
env.setVariable("count", builder.build().wrap(linkService.count()));
break;
default:
break;
}
}
body.render(env.getOut());
}
}

View File

@ -1,89 +0,0 @@
package run.halo.app.core.freemarker.tag;
import static org.springframework.data.domain.Sort.Direction.DESC;
import freemarker.core.Environment;
import freemarker.template.Configuration;
import freemarker.template.DefaultObjectWrapperBuilder;
import freemarker.template.TemplateDirectiveBody;
import freemarker.template.TemplateDirectiveModel;
import freemarker.template.TemplateException;
import freemarker.template.TemplateModel;
import java.io.IOException;
import java.util.Map;
import org.springframework.data.domain.Sort;
import org.springframework.stereotype.Component;
import run.halo.app.model.properties.PrimaryProperties;
import run.halo.app.model.support.HaloConst;
import run.halo.app.service.MenuService;
import run.halo.app.service.OptionService;
/**
* Freemarker custom tag of menu.
*
* @author ryanwang
* @date 2019-03-22
*/
@Component
public class MenuTagDirective implements TemplateDirectiveModel {
private static final String METHOD_KEY = "method";
private final MenuService menuService;
private final OptionService optionService;
public MenuTagDirective(Configuration configuration, MenuService menuService,
OptionService optionService) {
this.menuService = menuService;
this.optionService = optionService;
configuration.setSharedVariable("menuTag", this);
}
@Override
public void execute(Environment env, Map params, TemplateModel[] loopVars,
TemplateDirectiveBody body) throws TemplateException, IOException {
final DefaultObjectWrapperBuilder builder =
new DefaultObjectWrapperBuilder(Configuration.VERSION_2_3_25);
if (params.containsKey(HaloConst.METHOD_KEY)) {
String method = params.get(HaloConst.METHOD_KEY).toString();
switch (method) {
case "list":
String listTeam = optionService
.getByPropertyOrDefault(PrimaryProperties.DEFAULT_MENU_TEAM, String.class,
"");
env.setVariable("menus", builder.build()
.wrap(menuService.listByTeam(listTeam, Sort.by(DESC, "priority"))));
break;
case "tree":
String treeTeam = optionService
.getByPropertyOrDefault(PrimaryProperties.DEFAULT_MENU_TEAM, String.class,
"");
env.setVariable("menus", builder.build()
.wrap(menuService.listByTeamAsTree(treeTeam, Sort.by(DESC, "priority"))));
break;
case "listTeams":
env.setVariable("teams",
builder.build().wrap(menuService.listTeamVos(Sort.by(DESC, "priority"))));
break;
case "listByTeam":
String team = params.get("team").toString();
env.setVariable("menus", builder.build()
.wrap(menuService.listByTeam(team, Sort.by(DESC, "priority"))));
break;
case "treeByTeam":
String treeTeamParam = params.get("team").toString();
env.setVariable("menus", builder.build().wrap(
menuService.listByTeamAsTree(treeTeamParam, Sort.by(DESC, "priority"))));
break;
case "count":
env.setVariable("count", builder.build().wrap(menuService.count()));
break;
default:
break;
}
}
body.render(env.getOut());
}
}

View File

@ -1,325 +0,0 @@
package run.halo.app.core.freemarker.tag;
import static run.halo.app.model.support.HaloConst.URL_SEPARATOR;
import freemarker.core.Environment;
import freemarker.template.Configuration;
import freemarker.template.DefaultObjectWrapperBuilder;
import freemarker.template.TemplateDirectiveBody;
import freemarker.template.TemplateDirectiveModel;
import freemarker.template.TemplateException;
import freemarker.template.TemplateModel;
import java.io.IOException;
import java.util.ArrayList;
import java.util.List;
import java.util.Map;
import org.springframework.stereotype.Component;
import run.halo.app.model.support.HaloConst;
import run.halo.app.model.support.Pagination;
import run.halo.app.model.support.RainbowPage;
import run.halo.app.service.OptionService;
import run.halo.app.utils.HaloUtils;
/**
* @author ryanwang
* @date 2020-03-07
*/
@Component
public class PaginationTagDirective implements TemplateDirectiveModel {
private final OptionService optionService;
public PaginationTagDirective(Configuration configuration,
OptionService optionService) {
this.optionService = optionService;
configuration.setSharedVariable("paginationTag", this);
}
@Override
public void execute(Environment env, Map params, TemplateModel[] loopVars,
TemplateDirectiveBody body) throws TemplateException, IOException {
final DefaultObjectWrapperBuilder builder =
new DefaultObjectWrapperBuilder(Configuration.VERSION_2_3_25);
if (params.containsKey(HaloConst.METHOD_KEY)) {
// Get params
String method = params.get(HaloConst.METHOD_KEY).toString();
int page = Integer.parseInt(params.get("page").toString());
int total = Integer.parseInt(params.get("total").toString());
int display = Integer.parseInt(params.get("display").toString());
// Create pagination object.
Pagination pagination = new Pagination();
// Generate next page full path and pre page full path.
StringBuilder nextPageFullPath = new StringBuilder();
StringBuilder prevPageFullPath = new StringBuilder();
if (optionService.isEnabledAbsolutePath()) {
nextPageFullPath.append(optionService.getBlogBaseUrl());
prevPageFullPath.append(optionService.getBlogBaseUrl());
}
int[] rainbow = HaloUtils.rainbow(page + 1, total, display);
List<RainbowPage> rainbowPages = new ArrayList<>();
StringBuilder fullPath = new StringBuilder();
if (optionService.isEnabledAbsolutePath()) {
fullPath.append(optionService.getBlogBaseUrl());
}
String pathSuffix = optionService.getPathSuffix();
switch (method) {
case "index":
nextPageFullPath.append("/page/")
.append(page + 2)
.append(pathSuffix);
if (page == 1) {
prevPageFullPath.append(URL_SEPARATOR);
} else {
prevPageFullPath.append("/page/")
.append(page)
.append(pathSuffix);
}
fullPath.append("/page/");
for (int current : rainbow) {
RainbowPage rainbowPage = new RainbowPage();
rainbowPage.setPage(current);
rainbowPage.setFullPath(fullPath.toString() + current + pathSuffix);
rainbowPage.setIsCurrent(page + 1 == current);
rainbowPages.add(rainbowPage);
}
break;
case "archives":
nextPageFullPath.append(URL_SEPARATOR)
.append(optionService.getArchivesPrefix());
prevPageFullPath.append(URL_SEPARATOR)
.append(optionService.getArchivesPrefix());
nextPageFullPath.append("/page/")
.append(page + 2)
.append(pathSuffix);
if (page == 1) {
prevPageFullPath.append(pathSuffix);
} else {
prevPageFullPath.append("/page/")
.append(page)
.append(pathSuffix);
}
fullPath.append(URL_SEPARATOR)
.append(optionService.getArchivesPrefix());
fullPath.append("/page/");
for (int current : rainbow) {
RainbowPage rainbowPage = new RainbowPage();
rainbowPage.setPage(current);
rainbowPage.setFullPath(fullPath.toString() + current + pathSuffix);
rainbowPage.setIsCurrent(page + 1 == current);
rainbowPages.add(rainbowPage);
}
break;
case "search":
String keyword = params.get("keyword").toString();
nextPageFullPath.append(URL_SEPARATOR)
.append("search");
prevPageFullPath.append(URL_SEPARATOR)
.append("search");
nextPageFullPath.append("/page/")
.append(page + 2)
.append(pathSuffix)
.append("?keyword=")
.append(keyword);
if (page == 1) {
prevPageFullPath.append(pathSuffix)
.append("?keyword=")
.append(keyword);
} else {
prevPageFullPath.append("/page/")
.append(page)
.append(pathSuffix)
.append("?keyword=")
.append(keyword);
}
fullPath.append(URL_SEPARATOR)
.append("search");
fullPath.append("/page/");
for (int current : rainbow) {
RainbowPage rainbowPage = new RainbowPage();
rainbowPage.setPage(current);
rainbowPage.setFullPath(
fullPath.toString() + current + pathSuffix + "?keyword=" + keyword);
rainbowPage.setIsCurrent(page + 1 == current);
rainbowPages.add(rainbowPage);
}
break;
case "tagPosts":
String tagSlug = params.get("slug").toString();
nextPageFullPath.append(URL_SEPARATOR)
.append(optionService.getTagsPrefix())
.append(URL_SEPARATOR)
.append(tagSlug);
prevPageFullPath.append(URL_SEPARATOR)
.append(optionService.getTagsPrefix())
.append(URL_SEPARATOR)
.append(tagSlug);
nextPageFullPath.append("/page/")
.append(page + 2)
.append(pathSuffix);
if (page == 1) {
prevPageFullPath.append(pathSuffix);
} else {
prevPageFullPath.append("/page/")
.append(page)
.append(pathSuffix);
}
fullPath.append(URL_SEPARATOR)
.append(optionService.getTagsPrefix())
.append(URL_SEPARATOR)
.append(tagSlug);
fullPath.append("/page/");
for (int current : rainbow) {
RainbowPage rainbowPage = new RainbowPage();
rainbowPage.setPage(current);
rainbowPage.setFullPath(fullPath.toString() + current + pathSuffix);
rainbowPage.setIsCurrent(page + 1 == current);
rainbowPages.add(rainbowPage);
}
break;
case "categoryPosts":
String categorySlug = params.get("slug").toString();
nextPageFullPath.append(URL_SEPARATOR)
.append(optionService.getCategoriesPrefix())
.append(URL_SEPARATOR)
.append(categorySlug);
prevPageFullPath.append(URL_SEPARATOR)
.append(optionService.getCategoriesPrefix())
.append(URL_SEPARATOR)
.append(categorySlug);
nextPageFullPath.append("/page/")
.append(page + 2)
.append(pathSuffix);
if (page == 1) {
prevPageFullPath.append(pathSuffix);
} else {
prevPageFullPath.append("/page/")
.append(page)
.append(pathSuffix);
}
fullPath.append(URL_SEPARATOR)
.append(optionService.getCategoriesPrefix())
.append(URL_SEPARATOR)
.append(categorySlug);
fullPath.append("/page/");
for (int current : rainbow) {
RainbowPage rainbowPage = new RainbowPage();
rainbowPage.setPage(current);
rainbowPage.setFullPath(fullPath.toString() + current + pathSuffix);
rainbowPage.setIsCurrent(page + 1 == current);
rainbowPages.add(rainbowPage);
}
break;
case "photos":
nextPageFullPath.append(URL_SEPARATOR)
.append(optionService.getPhotosPrefix());
prevPageFullPath.append(URL_SEPARATOR)
.append(optionService.getPhotosPrefix());
nextPageFullPath.append("/page/")
.append(page + 2)
.append(pathSuffix);
if (page == 1) {
prevPageFullPath.append(pathSuffix);
} else {
prevPageFullPath.append("/page/")
.append(page)
.append(pathSuffix);
}
fullPath.append(URL_SEPARATOR)
.append(optionService.getPhotosPrefix());
fullPath.append("/page/");
for (int current : rainbow) {
RainbowPage rainbowPage = new RainbowPage();
rainbowPage.setPage(current);
rainbowPage.setFullPath(fullPath.toString() + current + pathSuffix);
rainbowPage.setIsCurrent(page + 1 == current);
rainbowPages.add(rainbowPage);
}
break;
case "journals":
nextPageFullPath.append(URL_SEPARATOR)
.append(optionService.getJournalsPrefix());
prevPageFullPath.append(URL_SEPARATOR)
.append(optionService.getJournalsPrefix());
nextPageFullPath.append("/page/")
.append(page + 2)
.append(pathSuffix);
if (page == 1) {
prevPageFullPath.append(pathSuffix);
} else {
prevPageFullPath.append("/page/")
.append(page)
.append(pathSuffix);
}
fullPath.append(URL_SEPARATOR)
.append(optionService.getJournalsPrefix());
fullPath.append("/page/");
for (int current : rainbow) {
RainbowPage rainbowPage = new RainbowPage();
rainbowPage.setPage(current);
rainbowPage.setFullPath(fullPath.toString() + current + pathSuffix);
rainbowPage.setIsCurrent(page + 1 == current);
rainbowPages.add(rainbowPage);
}
break;
default:
break;
}
pagination.setNextPageFullPath(nextPageFullPath.toString());
pagination.setPrevPageFullPath(prevPageFullPath.toString());
pagination.setRainbowPages(rainbowPages);
pagination.setHasNext(total != page + 1);
pagination.setHasPrev(page != 0);
env.setVariable("pagination", builder.build().wrap(pagination));
}
body.render(env.getOut());
}
}

View File

@ -1,65 +0,0 @@
package run.halo.app.core.freemarker.tag;
import static org.springframework.data.domain.Sort.Direction.DESC;
import freemarker.core.Environment;
import freemarker.template.Configuration;
import freemarker.template.DefaultObjectWrapperBuilder;
import freemarker.template.TemplateDirectiveBody;
import freemarker.template.TemplateDirectiveModel;
import freemarker.template.TemplateException;
import freemarker.template.TemplateModel;
import java.io.IOException;
import java.util.Map;
import org.springframework.data.domain.Sort;
import org.springframework.stereotype.Component;
import run.halo.app.model.support.HaloConst;
import run.halo.app.service.PhotoService;
/**
* Freemarker custom tag of photo.
*
* @author ryanwang
* @date 2019-04-21
*/
@Component
public class PhotoTagDirective implements TemplateDirectiveModel {
private final PhotoService photoService;
public PhotoTagDirective(Configuration configuration, PhotoService photoService) {
this.photoService = photoService;
configuration.setSharedVariable("photoTag", this);
}
@Override
public void execute(Environment env, Map params, TemplateModel[] loopVars,
TemplateDirectiveBody body) throws TemplateException, IOException {
final DefaultObjectWrapperBuilder builder =
new DefaultObjectWrapperBuilder(Configuration.VERSION_2_3_25);
if (params.containsKey(HaloConst.METHOD_KEY)) {
String method = params.get(HaloConst.METHOD_KEY).toString();
switch (method) {
case "list":
env.setVariable("photos", builder.build().wrap(photoService.listAll()));
break;
case "listTeams":
env.setVariable("teams", builder.build()
.wrap(photoService.listTeamVos(Sort.by(DESC, "createTime"))));
break;
case "listByTeam":
String team = params.get("team").toString();
env.setVariable("photos", builder.build()
.wrap(photoService.listByTeam(team, Sort.by(DESC, "createTime"))));
break;
case "count":
env.setVariable("count", builder.build().wrap(photoService.count()));
break;
default:
break;
}
}
body.render(env.getOut());
}
}

View File

@ -1,121 +0,0 @@
package run.halo.app.core.freemarker.tag;
import com.google.common.collect.Sets;
import freemarker.core.Environment;
import freemarker.template.Configuration;
import freemarker.template.DefaultObjectWrapperBuilder;
import freemarker.template.TemplateDirectiveBody;
import freemarker.template.TemplateDirectiveModel;
import freemarker.template.TemplateException;
import freemarker.template.TemplateModel;
import java.io.IOException;
import java.util.List;
import java.util.Map;
import org.springframework.stereotype.Component;
import run.halo.app.model.entity.Post;
import run.halo.app.model.enums.PostStatus;
import run.halo.app.model.support.HaloConst;
import run.halo.app.service.PostCategoryService;
import run.halo.app.service.PostService;
import run.halo.app.service.PostTagService;
import run.halo.app.service.assembler.PostRenderAssembler;
/**
* Freemarker custom tag of post.
*
* @author ryanwang
* @date 2018-04-26
*/
@Component
public class PostTagDirective implements TemplateDirectiveModel {
private final PostService postService;
private final PostRenderAssembler postRenderAssembler;
private final PostTagService postTagService;
private final PostCategoryService postCategoryService;
public PostTagDirective(Configuration configuration,
PostService postService,
PostRenderAssembler postRenderAssembler,
PostTagService postTagService,
PostCategoryService postCategoryService) {
this.postService = postService;
this.postRenderAssembler = postRenderAssembler;
this.postTagService = postTagService;
this.postCategoryService = postCategoryService;
configuration.setSharedVariable("postTag", this);
}
@Override
public void execute(Environment env, Map params, TemplateModel[] loopVars,
TemplateDirectiveBody body) throws TemplateException, IOException {
final DefaultObjectWrapperBuilder builder =
new DefaultObjectWrapperBuilder(Configuration.VERSION_2_3_25);
if (params.containsKey(HaloConst.METHOD_KEY)) {
String method = params.get(HaloConst.METHOD_KEY).toString();
switch (method) {
case "latest":
int top = Integer.parseInt(params.get("top").toString());
env.setVariable("posts", builder.build()
.wrap(postRenderAssembler.convertToListVo(postService.listLatest(top))));
break;
case "count":
env.setVariable("count",
builder.build().wrap(postService.countByStatus(PostStatus.PUBLISHED)));
break;
case "archiveYear":
env.setVariable("archives",
builder.build().wrap(postService.listYearArchives()));
break;
case "archiveMonth":
env.setVariable("archives",
builder.build().wrap(postService.listMonthArchives()));
break;
case "archive":
String type = params.get("type").toString();
env.setVariable("archives", builder.build().wrap(
"year".equals(type) ? postService.listYearArchives() :
postService.listMonthArchives()));
break;
case "listByCategoryId":
Integer categoryId = Integer.parseInt(params.get("categoryId").toString());
env.setVariable("posts", builder.build()
.wrap(postRenderAssembler.convertToListVo(
postCategoryService.listPostBy(categoryId,
Sets.immutableEnumSet(PostStatus.PUBLISHED,
PostStatus.INTIMATE)))));
break;
case "listByCategorySlug":
String categorySlug = params.get("categorySlug").toString();
List<Post> posts =
postCategoryService.listPostBy(categorySlug,
Sets.immutableEnumSet(PostStatus.PUBLISHED, PostStatus.INTIMATE));
env.setVariable("posts",
builder.build().wrap(postRenderAssembler.convertToListVo(posts)));
break;
case "listByTagId":
Integer tagId = Integer.parseInt(params.get("tagId").toString());
env.setVariable("posts", builder.build().wrap(postRenderAssembler
.convertToListVo(postTagService.listPostsBy(tagId, PostStatus.PUBLISHED))));
break;
case "listByTagSlug":
String tagSlug = params.get("tagSlug").toString();
env.setVariable("posts", builder.build()
.wrap(
postRenderAssembler.convertToListVo(
postTagService.listPostsBy(tagSlug, PostStatus.PUBLISHED)
)
)
);
break;
default:
break;
}
}
body.render(env.getOut());
}
}

View File

@ -1,70 +0,0 @@
package run.halo.app.core.freemarker.tag;
import static org.springframework.data.domain.Sort.Direction.DESC;
import freemarker.core.Environment;
import freemarker.template.Configuration;
import freemarker.template.DefaultObjectWrapperBuilder;
import freemarker.template.TemplateDirectiveBody;
import freemarker.template.TemplateDirectiveModel;
import freemarker.template.TemplateException;
import freemarker.template.TemplateModel;
import java.io.IOException;
import java.util.List;
import java.util.Map;
import org.springframework.data.domain.Sort;
import org.springframework.stereotype.Component;
import run.halo.app.model.entity.Tag;
import run.halo.app.model.support.HaloConst;
import run.halo.app.service.PostTagService;
import run.halo.app.service.TagService;
/**
* Freemarker custom tag of tag.
*
* @author ryanwang
* @date 2019-03-22
*/
@Component
public class TagTagDirective implements TemplateDirectiveModel {
private final TagService tagService;
private final PostTagService postTagService;
public TagTagDirective(Configuration configuration,
TagService tagService,
PostTagService postTagService) {
this.tagService = tagService;
this.postTagService = postTagService;
configuration.setSharedVariable("tagTag", this);
}
@Override
public void execute(Environment env, Map params, TemplateModel[] loopVars,
TemplateDirectiveBody body) throws TemplateException, IOException {
final DefaultObjectWrapperBuilder builder =
new DefaultObjectWrapperBuilder(Configuration.VERSION_2_3_25);
if (params.containsKey(HaloConst.METHOD_KEY)) {
String method = params.get(HaloConst.METHOD_KEY).toString();
switch (method) {
case "list":
env.setVariable("tags", builder.build()
.wrap(postTagService.listTagWithCountDtos(Sort.by(DESC, "createTime"))));
break;
case "listByPostId":
Integer postId = Integer.parseInt(params.get("postId").toString());
List<Tag> tags = postTagService.listTagsBy(postId);
env.setVariable("tags", builder.build().wrap(tagService.convertTo(tags)));
break;
case "count":
env.setVariable("count", builder.build().wrap(tagService.count()));
break;
default:
break;
}
}
body.render(env.getOut());
}
}

Some files were not shown because too many files have changed in this diff Show More