mirror of https://github.com/halo-dev/halo
chore: clean code for next major version
Signed-off-by: Ryan Wang <i@ryanc.cc>pull/1840/head
parent
68d263bef3
commit
641264ba5c
|
@ -1,3 +0,0 @@
|
||||||
[submodule "default-theme"]
|
|
||||||
path = src/main/resources/templates/themes/anatole
|
|
||||||
url = https://github.com/halo-dev/halo-theme-anatole
|
|
148
build.gradle
148
build.gradle
|
@ -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
|
|
||||||
}
|
|
|
@ -1 +0,0 @@
|
||||||
version=1.5.0-SNAPSHOT
|
|
Binary file not shown.
|
@ -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
|
|
|
@ -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" "$@"
|
|
|
@ -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
|
|
|
@ -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'
|
|
|
@ -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);
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
|
@ -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;
|
|
||||||
}
|
|
|
@ -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 {
|
|
||||||
}
|
|
|
@ -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();
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -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);
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -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;
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -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;
|
|
||||||
}
|
|
||||||
});
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -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();
|
|
||||||
}
|
|
|
@ -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;
|
|
||||||
}
|
|
|
@ -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);
|
|
||||||
}
|
|
||||||
});
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -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);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
|
@ -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;
|
|
||||||
}
|
|
|
@ -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();
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -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 {
|
|
||||||
|
|
||||||
}
|
|
|
@ -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;
|
|
||||||
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -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());
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -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);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
|
@ -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();
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
|
@ -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));
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -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);
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
|
@ -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);
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -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());
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -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";
|
|
||||||
}
|
|
|
@ -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));
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -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();
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -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);
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
}
|
|
|
@ -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);
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -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);
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
|
@ -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);
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -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);
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -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();
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -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();
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -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("您和邮箱服务器的连接通畅");
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
|
@ -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();
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -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);
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -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);
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
|
@ -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();
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -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));
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -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();
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -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));
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -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();
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -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());
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -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();
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -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);
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -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));
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -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());
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -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();
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -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));
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -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);
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -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");
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -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));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -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();
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -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("您没有该分类的访问权限");
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -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);
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -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);
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -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);
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -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);
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
|
@ -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();
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -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("您没有该分类的访问权限");
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -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));
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -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();
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -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);
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -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);
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -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();
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -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);
|
|
||||||
}
|
|
||||||
});
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -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();
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -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;
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -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;
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -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);
|
|
||||||
}
|
|
||||||
});
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -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");
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -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");
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -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");
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -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");
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -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");
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -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");
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -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");
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -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;
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -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;
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
|
@ -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);
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
|
@ -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;
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
|
@ -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;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
|
@ -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();
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -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()
|
|
||||||
: "";
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -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);
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -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());
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -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());
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -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());
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -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());
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -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());
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -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());
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -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());
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
|
@ -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
Loading…
Reference in New Issue