diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 1a9648f2..7d81ac50 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -24,5 +24,5 @@ jobs: path: ~/.m2 key: ${{ runner.os }}-m2-${{ hashFiles('**/pom.xml') }} restore-keys: ${{ runner.os }}-m2 - - name: Build with maven - run: mvn --batch-mode --update-snapshots -P integration verify + - name: Build with mill + run: ./mill test diff --git a/.github/workflows/typescript-generate.yml b/.github/workflows/typescript-generate.yml index 538a4a08..4e0b094e 100644 --- a/.github/workflows/typescript-generate.yml +++ b/.github/workflows/typescript-generate.yml @@ -55,7 +55,7 @@ jobs: --password-stdin - name: Generate typescript types run: | - mvn -P integration verify -Dtest=VersionServiceIntegrationTest + ./mill generateTypescript cd typescript/ yarn yarn generate-typescript @@ -65,7 +65,7 @@ jobs: git config user.email "$KNOWIT_EMAIL" git config user.name "github-actions" - git add typescript/openapi.json + git add typescript/openapi.json git add typescript/*.ts git commit -m "AUTOMATION: Generated new typescript files" git push diff --git a/.gitignore b/.gitignore index c1e614e1..419f039b 100644 --- a/.gitignore +++ b/.gitignore @@ -5,8 +5,12 @@ target *.backup .env +.env.* .yarn/ +# Mill +out/ + # Elastic Beanstalk Files .elasticbeanstalk/* !.elasticbeanstalk/*.cfg.yml diff --git a/.spotless-formats.json b/.spotless-formats.json new file mode 100644 index 00000000..7e0a317d --- /dev/null +++ b/.spotless-formats.json @@ -0,0 +1,51 @@ +[ + { + "includes": ["glob:build.mill"], + "steps": [ + { + "$type": "ScalaFmt" + } + ] + }, + { + "includes": ["glob:**.java"], + "steps": [ + { + "$type": "PalantirJavaFormat", + "version": "2.81.0", + "style": "PALANTIR", + "formatJavadoc": false + }, + { + "$type": "Indent", + "type": "SPACE", + "numSpacesPerTab": "4" + }, + { + "$type": "LicenseHeader", + "delimiter": "package ", + "header": { + "file": "license-header" + } + }, + { + "$type": "TrimTrailingWhitespace" + }, + { + "$type": "EndWithNewline" + }, + { + "$type": "CleanthatJava", + "version": "2.23", + "sourceJdkVersion": "21", + "includeDraft": false, + "mutators": [ + "SafeAndConsensual", + "ArithmethicAssignment", + "ForEachToIterableForEach", + "LiteralsFirstInComparisons" + ] + } + ] + } +] diff --git a/build.mill b/build.mill new file mode 100644 index 00000000..e7c85367 --- /dev/null +++ b/build.mill @@ -0,0 +1,74 @@ +//| mill-version: 1.1.0-RC3 +//| mill-jvm-version: temurin:21 +package build + +import mill._ +import mill.javalib.* +import mill.javalib.publish.* +import mill.javalib.spotless.* +import mill.javalib.spring.boot.SpringBootModule +import millbuild.* + +object `package` extends MavenModule, SpringBootModule, SpotlessModule { + def jvmId = "temurin:21" + def javacOptions = super.javacOptions() ++ Seq("-parameters") + override def springBootPlatformVersion = "3.5.9" + + def mvnDeps = super.mvnDeps() ++ Seq( + Deps.testcontainersPostgresql, + Deps.springBootStarter, + Deps.springBootStarterWeb, + Deps.springBootStarterValidation, + Deps.springBootConfigurationProcessor, + Deps.springdocOpenapiStarterWebmvcApi, + Deps.postgresql, + Deps.commonsCompress, + Deps.springBootStarterDataJpa, + Deps.springBootDevtools, + Deps.springSecurityWeb, + Deps.springBootStarterSecurity, + Deps.commonsLang3, + Deps.commonsCodec, + Deps.logbackCore, + Deps.logbackJsonClassic, + Deps.logbackJackson, + Deps.javaJwt, + Deps.jwksRsa, + Deps.liquibaseCore, + Deps.preliquibaseSpringBootStarter, + Deps.javamelodySpringBootStarter, + Deps.springBootStarterActuator, + Deps.micrometerRegistryPrometheus, + Deps.hypersistenceUtilsHibernate63, + Deps.jacksonModuleJakartaXmlbindAnnotations, + Deps.jacksonDatatypeJdk8, + Deps.jsoup + ) + + object test extends SpringBootTestsModule, MavenTests, TestModule.Junit5 { + def javacOptions = super.javacOptions() ++ Seq("-parameters") + + def mvnDeps = super.mvnDeps() ++ Seq( + Deps.springBootStarterTest, + Deps.testcontainersJunitJupiter + ) + } + + def artifactName = "taxonomy-api" + + def pomParentProject = Some( + Artifact("org.springframework.boot", "spring-boot-starter-parent", "3.5.9") + ) + + def pomSettings = PomSettings( + "NDLA taxonomy", + "no.ndla.taxonomy", + "", + Seq(), + VersionControl(None, None, None, None), + Seq() + ) + + def mainClass = Some("no.ndla.taxonomy.TaxonomyApplication") + +} diff --git a/mill b/mill new file mode 100755 index 00000000..509aca3f --- /dev/null +++ b/mill @@ -0,0 +1,181 @@ +#!/usr/bin/env sh + +set -e + +if [ -z "${DEFAULT_MILL_VERSION}" ] ; then DEFAULT_MILL_VERSION="1.1.0-RC3"; fi + +if [ -z "${GITHUB_RELEASE_CDN}" ] ; then GITHUB_RELEASE_CDN=""; fi + +if [ -z "$MILL_MAIN_CLI" ] ; then MILL_MAIN_CLI="${0}"; fi + +MILL_REPO_URL="https://github.com/com-lihaoyi/mill" + +MILL_BUILD_SCRIPT="" + +if [ -f "build.mill" ] ; then + MILL_BUILD_SCRIPT="build.mill" +elif [ -f "build.mill.scala" ] ; then + MILL_BUILD_SCRIPT="build.mill.scala" +elif [ -f "build.sc" ] ; then + MILL_BUILD_SCRIPT="build.sc" +fi + +# `s/.*://`: +# This is a greedy match that removes everything from the beginning of the line up to (and including) the last +# colon (:). This effectively isolates the value part of the declaration. +# +# `s/#.*//`: +# This removes any comments at the end of the line. +# +# `s/['\"]//g`: +# This removes all single and double quotes from the string, wherever they appear (g is for "global"). +# +# `s/^[[:space:]]*//; s/[[:space:]]*$//`: +# These two expressions trim any leading or trailing whitespace ([[:space:]] matches spaces and tabs). +TRIM_VALUE_SED="s/.*://; s/#.*//; s/['\"]//g; s/^[[:space:]]*//; s/[[:space:]]*$//" + +if [ -z "${MILL_VERSION}" ] ; then + if [ -f ".mill-version" ] ; then + MILL_VERSION="$(tr '\r' '\n' < .mill-version | head -n 1 2> /dev/null)" + elif [ -f ".config/mill-version" ] ; then + MILL_VERSION="$(tr '\r' '\n' < .config/mill-version | head -n 1 2> /dev/null)" + elif [ -f "build.mill.yaml" ] ; then + MILL_VERSION="$(grep -E "mill-version:" "build.mill.yaml" | sed -E "$TRIM_VALUE_SED")" + elif [ -n "${MILL_BUILD_SCRIPT}" ] ; then + MILL_VERSION="$(grep -E "//\|.*mill-version" "${MILL_BUILD_SCRIPT}" | sed -E "$TRIM_VALUE_SED")" + fi +fi + +if [ -z "${MILL_VERSION}" ] ; then MILL_VERSION="${DEFAULT_MILL_VERSION}"; fi + +MILL_USER_CACHE_DIR="${XDG_CACHE_HOME:-${HOME}/.cache}/mill" + +if [ -z "${MILL_FINAL_DOWNLOAD_FOLDER}" ] ; then MILL_FINAL_DOWNLOAD_FOLDER="${MILL_USER_CACHE_DIR}/download"; fi + +MILL_NATIVE_SUFFIX="-native" +MILL_JVM_SUFFIX="-jvm" +FULL_MILL_VERSION=$MILL_VERSION +ARTIFACT_SUFFIX="" +set_artifact_suffix(){ + if [ "$(expr substr $(uname -s) 1 5 2>/dev/null)" = "Linux" ]; then + if [ "$(uname -m)" = "aarch64" ]; then ARTIFACT_SUFFIX="-native-linux-aarch64" + else ARTIFACT_SUFFIX="-native-linux-amd64"; fi + elif [ "$(uname)" = "Darwin" ]; then + if [ "$(uname -m)" = "arm64" ]; then ARTIFACT_SUFFIX="-native-mac-aarch64" + else ARTIFACT_SUFFIX="-native-mac-amd64"; fi + else + echo "This native mill launcher supports only Linux and macOS." 1>&2 + exit 1 + fi +} + +case "$MILL_VERSION" in + *"$MILL_NATIVE_SUFFIX") + MILL_VERSION=${MILL_VERSION%"$MILL_NATIVE_SUFFIX"} + set_artifact_suffix + ;; + + *"$MILL_JVM_SUFFIX") + MILL_VERSION=${MILL_VERSION%"$MILL_JVM_SUFFIX"} + ;; + + *) + case "$MILL_VERSION" in + 0.1.*) ;; + 0.2.*) ;; + 0.3.*) ;; + 0.4.*) ;; + 0.5.*) ;; + 0.6.*) ;; + 0.7.*) ;; + 0.8.*) ;; + 0.9.*) ;; + 0.10.*) ;; + 0.11.*) ;; + 0.12.*) ;; + *) + set_artifact_suffix + esac + ;; +esac + +MILL="${MILL_FINAL_DOWNLOAD_FOLDER}/$MILL_VERSION$ARTIFACT_SUFFIX" + +# If not already downloaded, download it +if [ ! -s "${MILL}" ] || [ "$MILL_TEST_DRY_RUN_LAUNCHER_SCRIPT" = "1" ] ; then + case $MILL_VERSION in + 0.0.* | 0.1.* | 0.2.* | 0.3.* | 0.4.* ) + MILL_DOWNLOAD_SUFFIX="" + MILL_DOWNLOAD_FROM_MAVEN=0 + ;; + 0.5.* | 0.6.* | 0.7.* | 0.8.* | 0.9.* | 0.10.* | 0.11.0-M* ) + MILL_DOWNLOAD_SUFFIX="-assembly" + MILL_DOWNLOAD_FROM_MAVEN=0 + ;; + *) + MILL_DOWNLOAD_SUFFIX="-assembly" + MILL_DOWNLOAD_FROM_MAVEN=1 + ;; + esac + case $MILL_VERSION in + 0.12.0 | 0.12.1 | 0.12.2 | 0.12.3 | 0.12.4 | 0.12.5 | 0.12.6 | 0.12.7 | 0.12.8 | 0.12.9 | 0.12.10 | 0.12.11 ) + MILL_DOWNLOAD_EXT="jar" + ;; + 0.12.* ) + MILL_DOWNLOAD_EXT="exe" + ;; + 0.* ) + MILL_DOWNLOAD_EXT="jar" + ;; + *) + MILL_DOWNLOAD_EXT="exe" + ;; + esac + + MILL_TEMP_DOWNLOAD_FILE="${MILL_OUTPUT_DIR:-out}/mill-temp-download" + mkdir -p "$(dirname "${MILL_TEMP_DOWNLOAD_FILE}")" + + if [ "$MILL_DOWNLOAD_FROM_MAVEN" = "1" ] ; then + MILL_DOWNLOAD_URL="https://repo1.maven.org/maven2/com/lihaoyi/mill-dist${ARTIFACT_SUFFIX}/${MILL_VERSION}/mill-dist${ARTIFACT_SUFFIX}-${MILL_VERSION}.${MILL_DOWNLOAD_EXT}" + else + MILL_VERSION_TAG=$(echo "$MILL_VERSION" | sed -E 's/([^-]+)(-M[0-9]+)?(-.*)?/\1\2/') + MILL_DOWNLOAD_URL="${GITHUB_RELEASE_CDN}${MILL_REPO_URL}/releases/download/${MILL_VERSION_TAG}/${MILL_VERSION}${MILL_DOWNLOAD_SUFFIX}" + unset MILL_VERSION_TAG + fi + + + if [ "$MILL_TEST_DRY_RUN_LAUNCHER_SCRIPT" = "1" ] ; then + echo $MILL_DOWNLOAD_URL + echo $MILL + exit 0 + fi + + echo "Downloading mill ${MILL_VERSION} from ${MILL_DOWNLOAD_URL} ..." 1>&2 + curl -f -L -o "${MILL_TEMP_DOWNLOAD_FILE}" "${MILL_DOWNLOAD_URL}" + + chmod +x "${MILL_TEMP_DOWNLOAD_FILE}" + + mkdir -p "${MILL_FINAL_DOWNLOAD_FOLDER}" + mv "${MILL_TEMP_DOWNLOAD_FILE}" "${MILL}" + + unset MILL_TEMP_DOWNLOAD_FILE + unset MILL_DOWNLOAD_SUFFIX +fi + +MILL_FIRST_ARG="" +if [ "$1" = "--bsp" ] || [ "${1#"-i"}" != "$1" ] || [ "$1" = "--interactive" ] || [ "$1" = "--no-server" ] || [ "$1" = "--no-daemon" ] || [ "$1" = "--repl" ] || [ "$1" = "--help" ] ; then + # Need to preserve the first position of those listed options + MILL_FIRST_ARG=$1 + shift +fi + +unset MILL_FINAL_DOWNLOAD_FOLDER +unset MILL_OLD_DOWNLOAD_PATH +unset OLD_MILL +unset MILL_VERSION +unset MILL_REPO_URL + +# -D mill.main.cli is for compatibility with Mill 0.10.9 - 0.13.0-M2 +# We don't quote MILL_FIRST_ARG on purpose, so we can expand the empty value without quotes +# shellcheck disable=SC2086 +exec "${MILL}" $MILL_FIRST_ARG -D "mill.main.cli=${MILL_MAIN_CLI}" "$@" diff --git a/mill-build/src/Deps.scala b/mill-build/src/Deps.scala new file mode 100644 index 00000000..49b17066 --- /dev/null +++ b/mill-build/src/Deps.scala @@ -0,0 +1,61 @@ +package millbuild + +import mill.javalib.* + +object Deps { + + val commonsCodec = mvn"commons-codec:commons-codec:1.18.0" + val commonsCompress = mvn"org.apache.commons:commons-compress:1.28.0" + val commonsLang3 = mvn"org.apache.commons:commons-lang3:3.17.0" + val hypersistenceUtilsHibernate63 = + mvn"io.hypersistence:hypersistence-utils-hibernate-63:3.10.1" + val jacksonDatatypeJdk8 = + mvn"com.fasterxml.jackson.datatype:jackson-datatype-jdk8:2.19.4" + val jacksonModuleJakartaXmlbindAnnotations = + mvn"com.fasterxml.jackson.module:jackson-module-jakarta-xmlbind-annotations:2.19.4" + val javaJwt = mvn"com.auth0:java-jwt:4.5.0" + val javamelodySpringBootStarter = + mvn"net.bull.javamelody:javamelody-spring-boot-starter:2.5.0" + val junitJupiter = mvn"org.junit.jupiter:junit-jupiter:5.12.0" + val junitJupiterApi = mvn"org.junit.jupiter:junit-jupiter-api:5.12.0" + val junitJupiterEngine = mvn"org.junit.jupiter:junit-jupiter-engine:5.12.0" + val junitJupiterParams = mvn"org.junit.jupiter:junit-jupiter-params:5.12.0" + val jsoup = mvn"org.jsoup:jsoup:1.21.1" + val jwksRsa = mvn"com.auth0:jwks-rsa:0.23.0" + val liquibaseCore = mvn"org.liquibase:liquibase-core:4.31.1" + val logbackCore = mvn"ch.qos.logback:logback-core:1.5.21" + val logbackJackson = mvn"ch.qos.logback.contrib:logback-jackson:0.1.5" + val logbackJsonClassic = + mvn"ch.qos.logback.contrib:logback-json-classic:0.1.5" + val micrometerRegistryPrometheus = + mvn"io.micrometer:micrometer-registry-prometheus:1.15.6" + val postgresql = mvn"org.postgresql:postgresql:42.7.8" + val preliquibaseSpringBootStarter = + mvn"net.lbruun.springboot:preliquibase-spring-boot-starter:1.6.1" + val springBootConfigurationProcessor = + mvn"org.springframework.boot:spring-boot-configuration-processor" + val springBootDevtools = + mvn"org.springframework.boot:spring-boot-devtools" + val springBootStarter = + mvn"org.springframework.boot:spring-boot-starter" + val springBootStarterActuator = + mvn"org.springframework.boot:spring-boot-starter-actuator" + val springBootStarterDataJpa = + mvn"org.springframework.boot:spring-boot-starter-data-jpa" + val springBootStarterSecurity = + mvn"org.springframework.boot:spring-boot-starter-security" + val springBootStarterValidation = + mvn"org.springframework.boot:spring-boot-starter-validation" + val springBootStarterTest = + mvn"org.springframework.boot:spring-boot-starter-test" + val springBootStarterWeb = + mvn"org.springframework.boot:spring-boot-starter-web" + val springSecurityWeb = + mvn"org.springframework.security:spring-security-web:6.5.7" + val springdocOpenapiStarterWebmvcApi = + mvn"org.springdoc:springdoc-openapi-starter-webmvc-api:2.8.14" + val testcontainersJunitJupiter = + mvn"org.testcontainers:testcontainers-junit-jupiter:2.0.2" + val testcontainersPostgresql = + mvn"org.testcontainers:testcontainers-postgresql:2.0.2" +} diff --git a/mill-build/src/TypescriptPlugin.scala b/mill-build/src/TypescriptPlugin.scala new file mode 100644 index 00000000..f46623df --- /dev/null +++ b/mill-build/src/TypescriptPlugin.scala @@ -0,0 +1,124 @@ +package millbuild + +import mill._ +import mill.javalib.JavaModule + +object TypescriptPluginHelper { + + def fetchOpenApi( + apiUrl: String, + timeoutMs: Long, + backoffMs: Long + ): Option[String] = { + val url = new java.net.URI(apiUrl).toURL() + val startTime = System.currentTimeMillis() + var result: Option[String] = None + + while (result.isEmpty && (System.currentTimeMillis() - startTime) < timeoutMs) { + try { + val conn = url.openConnection().asInstanceOf[java.net.HttpURLConnection] + conn.setRequestMethod("GET") + conn.setConnectTimeout(3000) + conn.setReadTimeout(10000) + val code = conn.getResponseCode + + if (code == 200) { + val is = conn.getInputStream + val bytes = is.readAllBytes() + is.close() + result = Some(new String(bytes, java.nio.charset.StandardCharsets.UTF_8)) + println(s"Successfully fetched API docs (${bytes.length} bytes)") + } else { + println(s"Got HTTP $code, retrying...") + } + } catch { + case _: Throwable => + if (result.isEmpty) Thread.sleep(backoffMs) + } + } + + result + } + + def startApp( + javaCmd: String, + classpath: String, + mainClass: String, + profile: String + ): os.SubProcess = { + os.proc( + javaCmd, + "-cp", + classpath, + mainClass, + s"--spring.profiles.active=$profile" + ).spawn(stdout = os.Inherit, stderr = os.Inherit) + } + + def stopApp(proc: os.SubProcess): Unit = { + try { + proc.destroy() + Thread.sleep(1000) + } catch { + case _: Throwable => () + } + } +} + +trait TypescriptPlugin extends JavaModule { + + def typescriptProfile: String = "typescript" + def typescriptApiUrl: String = "http://localhost:5000/api-docs" + def typescriptTimeoutMs: Long = 120000L + def typescriptBackoffMs: Long = 500L + + def generateTypescript() = Task.Command { + val cp = runClasspath().map(_.path) + val classpath = cp.map(_.toString).mkString(java.io.File.pathSeparator) + + val javaCmd = sys.props.get("java.home") match { + case Some(h) => + val javaPath = os.Path(h) / "bin" / "java" + if (os.exists(javaPath)) javaPath.toString else "java" + case None => "java" + } + + val mainCls = mainClass().getOrElse { + throw new RuntimeException("mainClass must be defined") + } + + println(s"Starting Spring Boot app with profile: $typescriptProfile") + + val proc = TypescriptPluginHelper.startApp(javaCmd, classpath, mainCls, typescriptProfile) + + println(s"Polling $typescriptApiUrl until available...") + + val apiContent = TypescriptPluginHelper.fetchOpenApi( + typescriptApiUrl, + typescriptTimeoutMs, + typescriptBackoffMs + ) + + try { + apiContent match { + case Some(body) => + var projectRoot = os.pwd + while (!os.exists(projectRoot / "build.mill") && projectRoot != os.root) { + projectRoot = projectRoot / os.up + } + val outFile = projectRoot / "typescript" / "openapi.json" + os.makeDir.all(outFile / os.up) + os.write.over(outFile, body) + println(s"✓ Wrote OpenAPI spec to $outFile") + + case None => + throw new RuntimeException( + s"Failed to fetch $typescriptApiUrl within ${typescriptTimeoutMs}ms timeout" + ) + } + } finally { + println("Shutting down Spring Boot app...") + TypescriptPluginHelper.stopApp(proc) + } + } +} diff --git a/src/main/java/no/ndla/taxonomy/domain/ResourceType.java b/src/main/java/no/ndla/taxonomy/domain/ResourceType.java index 6ce94d2e..dd0588c2 100644 --- a/src/main/java/no/ndla/taxonomy/domain/ResourceType.java +++ b/src/main/java/no/ndla/taxonomy/domain/ResourceType.java @@ -13,7 +13,6 @@ import java.util.*; import java.util.stream.Collectors; import org.hibernate.annotations.Type; -import org.jetbrains.annotations.NotNull; @Entity public class ResourceType extends DomainObject implements Comparable { @@ -88,7 +87,7 @@ void preRemove() { } @Override - public int compareTo(@NotNull ResourceType o) { + public int compareTo(ResourceType o) { if (this.order == -1 || o.order == -1) { return this.getPublicId().compareTo(o.getPublicId()); } diff --git a/src/main/java/no/ndla/taxonomy/service/OldUrlCanonifier.java b/src/main/java/no/ndla/taxonomy/service/OldUrlCanonifier.java index 7505faad..5d3f6e83 100644 --- a/src/main/java/no/ndla/taxonomy/service/OldUrlCanonifier.java +++ b/src/main/java/no/ndla/taxonomy/service/OldUrlCanonifier.java @@ -32,7 +32,7 @@ public String canonify(String oldUrl) { nodeId = token.substring(nodeStartsAt); } else if (token.contains("fag=")) { int start = token.indexOf("fag="); - int ampersandIndex = token.indexOf("&"); + int ampersandIndex = token.indexOf('&'); int end = ampersandIndex > start ? ampersandIndex : token.length(); fagId = "?" + token.substring(start, end); } @@ -44,8 +44,8 @@ private String discardKnownNodeSuffixes(String oldUrl) { for (String suffix : KNOWN_NODE_SUFFIXES) { if (oldUrl.contains(suffix)) { int start = oldUrl.indexOf(suffix); - int indexOfSlashAfter = oldUrl.indexOf("/", start + 1); - int indexOfQuestionMark = oldUrl.indexOf("?", start); + int indexOfSlashAfter = oldUrl.indexOf('/', start + 1); + int indexOfQuestionMark = oldUrl.indexOf('?', start); String partToRemove; if (indexOfSlashAfter != -1) { partToRemove = oldUrl.substring(start, indexOfSlashAfter + 1); @@ -68,7 +68,7 @@ private String replaceKnownNodePrefixes(String oldUrl) { } private int findNodeStartsAt(String token) { - return token.substring(0, token.lastIndexOf("/")).lastIndexOf("/"); + return token.substring(0, token.lastIndexOf('/')).lastIndexOf('/'); } private String[] tokenize(String oldUrl) { diff --git a/src/main/java/no/ndla/taxonomy/service/UrlResolverServiceImpl.java b/src/main/java/no/ndla/taxonomy/service/UrlResolverServiceImpl.java index e2f9a61c..0943626f 100644 --- a/src/main/java/no/ndla/taxonomy/service/UrlResolverServiceImpl.java +++ b/src/main/java/no/ndla/taxonomy/service/UrlResolverServiceImpl.java @@ -109,9 +109,9 @@ private List getCachedUrlOldRig(String oldUrl) { private Optional getNodeId(String url) { if (url != null) { if (url.contains("?") && url.contains("/")) { - return Optional.of(url.substring(url.lastIndexOf("/"), url.indexOf("?"))); + return Optional.of(url.substring(url.lastIndexOf('/'), url.indexOf('?'))); } else if (url.contains("/")) { - return Optional.of(url.substring(url.lastIndexOf("/"))); + return Optional.of(url.substring(url.lastIndexOf('/'))); } } return Optional.empty(); diff --git a/src/main/java/no/ndla/taxonomy/util/PrettyUrlUtil.java b/src/main/java/no/ndla/taxonomy/util/PrettyUrlUtil.java index 5ef4c801..a291f99d 100644 --- a/src/main/java/no/ndla/taxonomy/util/PrettyUrlUtil.java +++ b/src/main/java/no/ndla/taxonomy/util/PrettyUrlUtil.java @@ -91,6 +91,6 @@ public static String getHashFromPath(String title) { if (!title.contains("/r/") && !title.contains("/e/") && !title.contains("/f/")) { return ""; } - return title.substring(title.lastIndexOf("/") + 1); + return title.substring(title.lastIndexOf('/') + 1); } } diff --git a/src/main/resources/application-typescript.yml b/src/main/resources/application-typescript.yml index a27021d5..e7de24cd 100644 --- a/src/main/resources/application-typescript.yml +++ b/src/main/resources/application-typescript.yml @@ -2,7 +2,7 @@ server.port: 5000 spring: datasource: - url: jdbc:tc:postgresql:17.5:///taxonomy_api + url: jdbc:tc:postgresql:17.5:///public username: test password: test hikari: diff --git a/src/test/java/no/ndla/taxonomy/TestUtils.java b/src/test/java/no/ndla/taxonomy/TestUtils.java index af567586..95ecb352 100644 --- a/src/test/java/no/ndla/taxonomy/TestUtils.java +++ b/src/test/java/no/ndla/taxonomy/TestUtils.java @@ -141,7 +141,7 @@ public MockHttpServletResponse updateResource(String path, Object command, Resul public static URI getId(MockHttpServletResponse response) { String location = response.getHeader("Location"); - return URI.create(location.substring(location.lastIndexOf("/") + 1)); + return URI.create(location.substring(location.lastIndexOf('/') + 1)); } public V getObject(Class theClass, MockHttpServletResponse response) throws Exception {