Compare commits
11
Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
995e69e302 | ||
|
|
698bb0e99f | ||
|
|
3c0455bff7 | ||
|
|
ed109df111 | ||
|
|
e8de49732b | ||
|
|
123261aad3 | ||
|
|
1ed458a9c1 | ||
|
|
3da5840e8b | ||
|
|
ed30cf85dc | ||
|
|
1d74d8f04b | ||
|
|
1d97cc4e31 |
No files matched your search
@@ -0,0 +1,25 @@
|
|||||||
|
# See here for image contents: https://github.com/microsoft/vscode-dev-containers/tree/v0.209.6/containers/java/.devcontainer/base.Dockerfile
|
||||||
|
|
||||||
|
# [Choice] Java version (use -bullseye variants on local arm64/Apple Silicon): 17, 17-bullseye, 17-buster
|
||||||
|
ARG VARIANT="17"
|
||||||
|
FROM mcr.microsoft.com/vscode/devcontainers/java:0-${VARIANT}
|
||||||
|
|
||||||
|
# [Option] Install Maven
|
||||||
|
ARG INSTALL_MAVEN="false"
|
||||||
|
ARG MAVEN_VERSION=""
|
||||||
|
# [Option] Install Gradle
|
||||||
|
ARG INSTALL_GRADLE="false"
|
||||||
|
ARG GRADLE_VERSION=""
|
||||||
|
RUN if [ "${INSTALL_MAVEN}" = "true" ]; then su vscode -c "umask 0002 && . /usr/local/sdkman/bin/sdkman-init.sh && sdk install maven \"${MAVEN_VERSION}\""; fi \
|
||||||
|
&& if [ "${INSTALL_GRADLE}" = "true" ]; then su vscode -c "umask 0002 && . /usr/local/sdkman/bin/sdkman-init.sh && sdk install gradle \"${GRADLE_VERSION}\""; fi
|
||||||
|
|
||||||
|
# [Choice] Node.js version: none, lts/*, 16, 14, 12, 10
|
||||||
|
ARG NODE_VERSION="none"
|
||||||
|
RUN if [ "${NODE_VERSION}" != "none" ]; then su vscode -c "umask 0002 && . /usr/local/share/nvm/nvm.sh && nvm install ${NODE_VERSION} 2>&1"; fi
|
||||||
|
|
||||||
|
# [Optional] Uncomment this section to install additional OS packages.
|
||||||
|
# RUN apt-get update && export DEBIAN_FRONTEND=noninteractive \
|
||||||
|
# && apt-get -y install --no-install-recommends <your-package-list-here>
|
||||||
|
|
||||||
|
# [Optional] Uncomment this line to install global node packages.
|
||||||
|
# RUN su vscode -c "source /usr/local/share/nvm/nvm.sh && npm install -g <your-package-here>" 2>&1
|
||||||
@@ -0,0 +1,57 @@
|
|||||||
|
// For format details, see https://aka.ms/devcontainer.json. For config options, see the README at:
|
||||||
|
// https://github.com/microsoft/vscode-dev-containers/tree/v0.209.6/containers/java
|
||||||
|
{
|
||||||
|
"name": "QuestionBank",
|
||||||
|
"build": {
|
||||||
|
"dockerfile": "Dockerfile",
|
||||||
|
"args": {
|
||||||
|
// Update the VARIANT arg to pick a Java version: 17, 19
|
||||||
|
// Append -bullseye or -buster to pin to an OS version.
|
||||||
|
// Use the -bullseye variants on local arm64/Apple Silicon.
|
||||||
|
"VARIANT": "21-bullseye",
|
||||||
|
// Options
|
||||||
|
// maven and gradle wrappers are used by default, we don't need them installed globally
|
||||||
|
// "INSTALL_MAVEN": "true",
|
||||||
|
// "INSTALL_GRADLE": "false",
|
||||||
|
"NODE_VERSION": "24.20.0"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
|
||||||
|
"customizations": {
|
||||||
|
"vscode": {
|
||||||
|
// Set *default* container specific settings.json values on container create.
|
||||||
|
"settings": {
|
||||||
|
"java.jdt.ls.java.home": "/docker-java-home"
|
||||||
|
},
|
||||||
|
|
||||||
|
// Add the IDs of extensions you want installed when the container is created.
|
||||||
|
"extensions": [
|
||||||
|
"christian-kohler.npm-intellisense",
|
||||||
|
"firsttris.vscode-jest-runner",
|
||||||
|
"ms-vscode.vscode-typescript-tslint-plugin",
|
||||||
|
"dbaeumer.vscode-eslint",
|
||||||
|
"vscjava.vscode-java-pack",
|
||||||
|
"pivotal.vscode-boot-dev-pack",
|
||||||
|
"esbenp.prettier-vscode"
|
||||||
|
]
|
||||||
|
}
|
||||||
|
},
|
||||||
|
|
||||||
|
// Use 'forwardPorts' to make a list of ports inside the container available locally.
|
||||||
|
"forwardPorts": [9060, 3001, 9000, 8080],
|
||||||
|
|
||||||
|
// Use 'postCreateCommand' to run commands after the container is created.
|
||||||
|
// "postCreateCommand": "java -version",
|
||||||
|
|
||||||
|
// Comment out connect as root instead. More info: https://aka.ms/vscode-remote/containers/non-root.
|
||||||
|
"remoteUser": "vscode",
|
||||||
|
|
||||||
|
// Mount the workspace folder to the same path inside the container, so docker-in-docker containers work correctly. See https://github.com/jhipster/generator-jhipster/issues/31239
|
||||||
|
"workspaceMount": "source=${localWorkspaceFolder},target=${localWorkspaceFolder},type=bind",
|
||||||
|
"workspaceFolder": "${localWorkspaceFolder}",
|
||||||
|
|
||||||
|
"features": {
|
||||||
|
"docker-in-docker": "latest",
|
||||||
|
"docker-from-docker": "latest"
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,24 @@
|
|||||||
|
# EditorConfig helps developers define and maintain consistent
|
||||||
|
# coding styles between different editors and IDEs
|
||||||
|
# editorconfig.org
|
||||||
|
|
||||||
|
root = true
|
||||||
|
|
||||||
|
[*]
|
||||||
|
|
||||||
|
# We recommend you to keep these unchanged
|
||||||
|
end_of_line = lf
|
||||||
|
charset = utf-8
|
||||||
|
trim_trailing_whitespace = true
|
||||||
|
insert_final_newline = true
|
||||||
|
|
||||||
|
# Change these settings to your own preference
|
||||||
|
indent_style = space
|
||||||
|
indent_size = 2
|
||||||
|
|
||||||
|
[*.md]
|
||||||
|
trim_trailing_whitespace = false
|
||||||
|
|
||||||
|
# Generated by jhipster:java:bootstrap generator
|
||||||
|
[*.java]
|
||||||
|
indent_size = 4
|
||||||
+149
@@ -0,0 +1,149 @@
|
|||||||
|
# This file is inspired by https://github.com/alexkaratarakis/gitattributes
|
||||||
|
#
|
||||||
|
# Auto detect text files and perform LF normalization
|
||||||
|
# https://davidlaing.com/2012/09/19/customise-your-gitattributes-to-become-a-git-ninja/
|
||||||
|
* text=auto
|
||||||
|
|
||||||
|
# The above will handle all files NOT found below
|
||||||
|
# These files are text and should be normalized (Convert crlf => lf)
|
||||||
|
|
||||||
|
*.bat text eol=crlf
|
||||||
|
*.cmd text eol=crlf
|
||||||
|
*.ps1 text eol=crlf
|
||||||
|
*.coffee text
|
||||||
|
*.css text
|
||||||
|
*.cql text
|
||||||
|
*.df text
|
||||||
|
*.ejs text
|
||||||
|
*.html text
|
||||||
|
*.java text
|
||||||
|
*.js text
|
||||||
|
*.json text
|
||||||
|
*.less text
|
||||||
|
*.properties text
|
||||||
|
*.sass text
|
||||||
|
*.scss text
|
||||||
|
*.sh text eol=lf
|
||||||
|
*.sql text
|
||||||
|
*.ts text
|
||||||
|
*.xml text
|
||||||
|
*.yaml text
|
||||||
|
*.yml text
|
||||||
|
|
||||||
|
# Documents
|
||||||
|
*.doc diff=astextplain
|
||||||
|
*.DOC diff=astextplain
|
||||||
|
*.docx diff=astextplain
|
||||||
|
*.DOCX diff=astextplain
|
||||||
|
*.dot diff=astextplain
|
||||||
|
*.DOT diff=astextplain
|
||||||
|
*.pdf diff=astextplain
|
||||||
|
*.PDF diff=astextplain
|
||||||
|
*.rtf diff=astextplain
|
||||||
|
*.RTF diff=astextplain
|
||||||
|
*.markdown text
|
||||||
|
*.md text
|
||||||
|
*.adoc text
|
||||||
|
*.textile text
|
||||||
|
*.mustache text
|
||||||
|
*.csv text
|
||||||
|
*.tab text
|
||||||
|
*.tsv text
|
||||||
|
*.txt text
|
||||||
|
AUTHORS text
|
||||||
|
CHANGELOG text
|
||||||
|
CHANGES text
|
||||||
|
CONTRIBUTING text
|
||||||
|
COPYING text
|
||||||
|
copyright text
|
||||||
|
*COPYRIGHT* text
|
||||||
|
INSTALL text
|
||||||
|
license text
|
||||||
|
LICENSE text
|
||||||
|
NEWS text
|
||||||
|
readme text
|
||||||
|
*README* text
|
||||||
|
TODO text
|
||||||
|
|
||||||
|
# Graphics
|
||||||
|
*.png binary
|
||||||
|
*.jpg binary
|
||||||
|
*.jpeg binary
|
||||||
|
*.gif binary
|
||||||
|
*.tif binary
|
||||||
|
*.tiff binary
|
||||||
|
*.ico binary
|
||||||
|
# SVG treated as an asset (binary) by default. If you want to treat it as text,
|
||||||
|
# comment-out the following line and uncomment the line after.
|
||||||
|
*.svg binary
|
||||||
|
#*.svg text
|
||||||
|
*.eps binary
|
||||||
|
|
||||||
|
# These files are binary and should be left untouched
|
||||||
|
# (binary is a macro for -text -diff)
|
||||||
|
*.class binary
|
||||||
|
*.jar binary
|
||||||
|
*.war binary
|
||||||
|
|
||||||
|
## LINTERS
|
||||||
|
.csslintrc text
|
||||||
|
.eslintrc text
|
||||||
|
.jscsrc text
|
||||||
|
.jshintrc text
|
||||||
|
.jshintignore text
|
||||||
|
.stylelintrc text
|
||||||
|
|
||||||
|
## CONFIGS
|
||||||
|
*.conf text
|
||||||
|
*.config text
|
||||||
|
.editorconfig text
|
||||||
|
.gitattributes text
|
||||||
|
.gitconfig text
|
||||||
|
.gitignore text
|
||||||
|
.htaccess text
|
||||||
|
*.npmignore text
|
||||||
|
|
||||||
|
## HEROKU
|
||||||
|
Procfile text
|
||||||
|
.slugignore text
|
||||||
|
|
||||||
|
## AUDIO
|
||||||
|
*.kar binary
|
||||||
|
*.m4a binary
|
||||||
|
*.mid binary
|
||||||
|
*.midi binary
|
||||||
|
*.mp3 binary
|
||||||
|
*.ogg binary
|
||||||
|
*.ra binary
|
||||||
|
|
||||||
|
## VIDEO
|
||||||
|
*.3gpp binary
|
||||||
|
*.3gp binary
|
||||||
|
*.as binary
|
||||||
|
*.asf binary
|
||||||
|
*.asx binary
|
||||||
|
*.fla binary
|
||||||
|
*.flv binary
|
||||||
|
*.m4v binary
|
||||||
|
*.mng binary
|
||||||
|
*.mov binary
|
||||||
|
*.mp4 binary
|
||||||
|
*.mpeg binary
|
||||||
|
*.mpg binary
|
||||||
|
*.swc binary
|
||||||
|
*.swf binary
|
||||||
|
*.webm binary
|
||||||
|
|
||||||
|
## ARCHIVES
|
||||||
|
*.7z binary
|
||||||
|
*.gz binary
|
||||||
|
*.rar binary
|
||||||
|
*.tar binary
|
||||||
|
*.zip binary
|
||||||
|
|
||||||
|
## FONTS
|
||||||
|
*.ttf binary
|
||||||
|
*.eot binary
|
||||||
|
*.otf binary
|
||||||
|
*.woff binary
|
||||||
|
*.woff2 binary
|
||||||
+153
@@ -0,0 +1,153 @@
|
|||||||
|
######################
|
||||||
|
# Node
|
||||||
|
######################
|
||||||
|
/node/
|
||||||
|
node_tmp/
|
||||||
|
node_modules/
|
||||||
|
npm-debug.log.*
|
||||||
|
/.awcache/*
|
||||||
|
/.cache-loader/*
|
||||||
|
/dist/
|
||||||
|
|
||||||
|
######################
|
||||||
|
# SASS
|
||||||
|
######################
|
||||||
|
.sass-cache/
|
||||||
|
|
||||||
|
######################
|
||||||
|
# Eclipse
|
||||||
|
######################
|
||||||
|
*.pydevproject
|
||||||
|
.project
|
||||||
|
.metadata
|
||||||
|
tmp/
|
||||||
|
*.tmp
|
||||||
|
*.bak
|
||||||
|
*.swp
|
||||||
|
*~.nib
|
||||||
|
local.properties
|
||||||
|
.classpath
|
||||||
|
.settings/
|
||||||
|
.loadpath
|
||||||
|
.factorypath
|
||||||
|
|
||||||
|
# External tool builders
|
||||||
|
.externalToolBuilders/**
|
||||||
|
|
||||||
|
# Locally stored "Eclipse launch configurations"
|
||||||
|
*.launch
|
||||||
|
|
||||||
|
# CDT-specific
|
||||||
|
.cproject
|
||||||
|
|
||||||
|
# PDT-specific
|
||||||
|
.buildpath
|
||||||
|
|
||||||
|
# STS-specific
|
||||||
|
/.sts4-cache/*
|
||||||
|
|
||||||
|
######################
|
||||||
|
# IntelliJ
|
||||||
|
######################
|
||||||
|
.idea/
|
||||||
|
*.iml
|
||||||
|
*.iws
|
||||||
|
*.ipr
|
||||||
|
*.ids
|
||||||
|
*.orig
|
||||||
|
classes/
|
||||||
|
out/
|
||||||
|
|
||||||
|
######################
|
||||||
|
# Visual Studio Code
|
||||||
|
######################
|
||||||
|
.vscode/*
|
||||||
|
!.vscode/settings.json
|
||||||
|
!.vscode/tasks.json
|
||||||
|
!.vscode/launch.json
|
||||||
|
!.vscode/extensions.json
|
||||||
|
*.code-workspace
|
||||||
|
|
||||||
|
######################
|
||||||
|
# Maven
|
||||||
|
######################
|
||||||
|
/log/
|
||||||
|
/target/
|
||||||
|
|
||||||
|
######################
|
||||||
|
# Gradle
|
||||||
|
######################
|
||||||
|
.gradle/
|
||||||
|
/build/
|
||||||
|
/buildSrc/.gradle/
|
||||||
|
/buildSrc/build/
|
||||||
|
|
||||||
|
######################
|
||||||
|
# Package Files
|
||||||
|
######################
|
||||||
|
*.jar
|
||||||
|
*.war
|
||||||
|
*.ear
|
||||||
|
*.db
|
||||||
|
|
||||||
|
######################
|
||||||
|
# Windows
|
||||||
|
######################
|
||||||
|
# Windows image file caches
|
||||||
|
Thumbs.db
|
||||||
|
|
||||||
|
# Folder config file
|
||||||
|
Desktop.ini
|
||||||
|
|
||||||
|
######################
|
||||||
|
# Mac OSX
|
||||||
|
######################
|
||||||
|
.DS_Store
|
||||||
|
.svn
|
||||||
|
|
||||||
|
# Thumbnails
|
||||||
|
._*
|
||||||
|
|
||||||
|
# Files that might appear on external disk
|
||||||
|
.Spotlight-V100
|
||||||
|
.Trashes
|
||||||
|
|
||||||
|
######################
|
||||||
|
# Directories
|
||||||
|
######################
|
||||||
|
/bin/
|
||||||
|
/deploy/
|
||||||
|
|
||||||
|
######################
|
||||||
|
# Logs
|
||||||
|
######################
|
||||||
|
*.log*
|
||||||
|
|
||||||
|
######################
|
||||||
|
# Others
|
||||||
|
######################
|
||||||
|
*.class
|
||||||
|
*.*~
|
||||||
|
*~
|
||||||
|
.merge_file*
|
||||||
|
|
||||||
|
######################
|
||||||
|
# Gradle Wrapper
|
||||||
|
######################
|
||||||
|
!gradle/wrapper/gradle-wrapper.jar
|
||||||
|
|
||||||
|
######################
|
||||||
|
# Maven Wrapper
|
||||||
|
######################
|
||||||
|
!.mvn/wrapper/maven-wrapper.jar
|
||||||
|
|
||||||
|
######################
|
||||||
|
# ESLint
|
||||||
|
######################
|
||||||
|
.eslintcache
|
||||||
|
|
||||||
|
######################
|
||||||
|
# Code coverage
|
||||||
|
######################
|
||||||
|
coverage/
|
||||||
|
.nyc_output/
|
||||||
@@ -0,0 +1,6 @@
|
|||||||
|
if [ -z "$(which lint-staged)" ]; then
|
||||||
|
echo 'Running package manager install'
|
||||||
|
./npmw install
|
||||||
|
fi
|
||||||
|
|
||||||
|
lint-staged
|
||||||
@@ -0,0 +1,46 @@
|
|||||||
|
{
|
||||||
|
"annotations": {
|
||||||
|
"changelogDate": "20261020093324"
|
||||||
|
},
|
||||||
|
"applications": ["*"],
|
||||||
|
"dto": "mapstruct",
|
||||||
|
"fields": [
|
||||||
|
{
|
||||||
|
"fieldName": "optionKey",
|
||||||
|
"fieldType": "OptionKey",
|
||||||
|
"fieldValidateRules": ["required"],
|
||||||
|
"fieldValues": "A,B,C,D"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"fieldName": "content",
|
||||||
|
"fieldType": "String",
|
||||||
|
"fieldValidateRules": ["required", "maxlength"],
|
||||||
|
"fieldValidateRulesMaxlength": "500"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"fieldName": "displayOrder",
|
||||||
|
"fieldType": "Integer",
|
||||||
|
"fieldValidateRules": ["required", "min", "max"],
|
||||||
|
"fieldValidateRulesMax": "4",
|
||||||
|
"fieldValidateRulesMin": "1"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"fieldName": "correct",
|
||||||
|
"fieldType": "Boolean",
|
||||||
|
"fieldValidateRules": ["required"]
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"name": "Choice",
|
||||||
|
"relationships": [
|
||||||
|
{
|
||||||
|
"otherEntityName": "question",
|
||||||
|
"otherEntityRelationshipName": "choice",
|
||||||
|
"relationshipName": "question",
|
||||||
|
"relationshipSide": "right",
|
||||||
|
"relationshipType": "many-to-one",
|
||||||
|
"relationshipValidateRules": "required"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"searchEngine": "no",
|
||||||
|
"service": "serviceClass"
|
||||||
|
}
|
||||||
@@ -0,0 +1,62 @@
|
|||||||
|
{
|
||||||
|
"annotations": {
|
||||||
|
"changelogDate": "20261020093326"
|
||||||
|
},
|
||||||
|
"applications": ["*"],
|
||||||
|
"dto": "mapstruct",
|
||||||
|
"fields": [
|
||||||
|
{
|
||||||
|
"fieldName": "questionTextSnapshot",
|
||||||
|
"fieldType": "String",
|
||||||
|
"fieldValidateRules": ["required", "maxlength"],
|
||||||
|
"fieldValidateRulesMaxlength": "2000"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"fieldName": "explanationSnapshot",
|
||||||
|
"fieldType": "String",
|
||||||
|
"fieldValidateRules": ["maxlength"],
|
||||||
|
"fieldValidateRulesMaxlength": "2000"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"fieldName": "selectedOption",
|
||||||
|
"fieldType": "OptionKey",
|
||||||
|
"fieldValues": "A,B,C,D"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"fieldName": "selectedContentSnapshot",
|
||||||
|
"fieldType": "String",
|
||||||
|
"fieldValidateRules": ["maxlength"],
|
||||||
|
"fieldValidateRulesMaxlength": "500"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"fieldName": "correct",
|
||||||
|
"fieldType": "Boolean"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"fieldName": "awardedPoints",
|
||||||
|
"fieldType": "Integer",
|
||||||
|
"fieldValidateRules": ["min"],
|
||||||
|
"fieldValidateRulesMin": "0"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"name": "ExamAnswer",
|
||||||
|
"relationships": [
|
||||||
|
{
|
||||||
|
"otherEntityName": "question",
|
||||||
|
"relationshipName": "question",
|
||||||
|
"relationshipSide": "left",
|
||||||
|
"relationshipType": "many-to-one",
|
||||||
|
"relationshipValidateRules": "required"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"otherEntityName": "examAttempt",
|
||||||
|
"otherEntityRelationshipName": "answer",
|
||||||
|
"relationshipName": "attempt",
|
||||||
|
"relationshipSide": "right",
|
||||||
|
"relationshipType": "many-to-one",
|
||||||
|
"relationshipValidateRules": "required"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"searchEngine": "no",
|
||||||
|
"service": "serviceClass"
|
||||||
|
}
|
||||||
@@ -0,0 +1,60 @@
|
|||||||
|
{
|
||||||
|
"annotations": {
|
||||||
|
"changelogDate": "20261020093325"
|
||||||
|
},
|
||||||
|
"applications": ["*"],
|
||||||
|
"dto": "mapstruct",
|
||||||
|
"fields": [
|
||||||
|
{
|
||||||
|
"fieldName": "startedAt",
|
||||||
|
"fieldType": "Instant",
|
||||||
|
"fieldValidateRules": ["required"]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"fieldName": "submittedAt",
|
||||||
|
"fieldType": "Instant"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"fieldName": "status",
|
||||||
|
"fieldType": "AttemptStatus",
|
||||||
|
"fieldValidateRules": ["required"],
|
||||||
|
"fieldValues": "IN_PROGRESS,SUBMITTED"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"fieldName": "score",
|
||||||
|
"fieldType": "Integer",
|
||||||
|
"fieldValidateRules": ["min"],
|
||||||
|
"fieldValidateRulesMin": "0"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"name": "ExamAttempt",
|
||||||
|
"pagination": "pagination",
|
||||||
|
"relationships": [
|
||||||
|
{
|
||||||
|
"otherEntityName": "examAnswer",
|
||||||
|
"otherEntityRelationshipName": "attempt",
|
||||||
|
"relationshipName": "answer",
|
||||||
|
"relationshipSide": "left",
|
||||||
|
"relationshipType": "one-to-many"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"otherEntityField": "name",
|
||||||
|
"otherEntityName": "subject",
|
||||||
|
"relationshipName": "subject",
|
||||||
|
"relationshipSide": "left",
|
||||||
|
"relationshipType": "many-to-one",
|
||||||
|
"relationshipValidateRules": "required"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"otherEntityField": "login",
|
||||||
|
"otherEntityName": "user",
|
||||||
|
"relationshipName": "user",
|
||||||
|
"relationshipSide": "left",
|
||||||
|
"relationshipType": "many-to-one",
|
||||||
|
"relationshipValidateRules": "required",
|
||||||
|
"relationshipWithBuiltInEntity": true
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"searchEngine": "no",
|
||||||
|
"service": "serviceClass"
|
||||||
|
}
|
||||||
@@ -0,0 +1,48 @@
|
|||||||
|
{
|
||||||
|
"annotations": {
|
||||||
|
"changelogDate": "20261020093323"
|
||||||
|
},
|
||||||
|
"applications": ["*"],
|
||||||
|
"dto": "mapstruct",
|
||||||
|
"fields": [
|
||||||
|
{
|
||||||
|
"fieldName": "prompt",
|
||||||
|
"fieldType": "String",
|
||||||
|
"fieldValidateRules": ["required", "minlength", "maxlength"],
|
||||||
|
"fieldValidateRulesMaxlength": "2000",
|
||||||
|
"fieldValidateRulesMinlength": "5"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"fieldName": "explanation",
|
||||||
|
"fieldType": "String",
|
||||||
|
"fieldValidateRules": ["maxlength"],
|
||||||
|
"fieldValidateRulesMaxlength": "2000"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"fieldName": "active",
|
||||||
|
"fieldType": "Boolean",
|
||||||
|
"fieldValidateRules": ["required"]
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"name": "Question",
|
||||||
|
"pagination": "pagination",
|
||||||
|
"relationships": [
|
||||||
|
{
|
||||||
|
"otherEntityName": "choice",
|
||||||
|
"otherEntityRelationshipName": "question",
|
||||||
|
"relationshipName": "choice",
|
||||||
|
"relationshipSide": "left",
|
||||||
|
"relationshipType": "one-to-many"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"otherEntityField": "name",
|
||||||
|
"otherEntityName": "subject",
|
||||||
|
"relationshipName": "subject",
|
||||||
|
"relationshipSide": "left",
|
||||||
|
"relationshipType": "many-to-one",
|
||||||
|
"relationshipValidateRules": "required"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"searchEngine": "no",
|
||||||
|
"service": "serviceClass"
|
||||||
|
}
|
||||||
@@ -0,0 +1,32 @@
|
|||||||
|
{
|
||||||
|
"annotations": {
|
||||||
|
"changelogDate": "20261020093322"
|
||||||
|
},
|
||||||
|
"applications": ["*"],
|
||||||
|
"dto": "mapstruct",
|
||||||
|
"fields": [
|
||||||
|
{
|
||||||
|
"fieldName": "name",
|
||||||
|
"fieldType": "String",
|
||||||
|
"fieldValidateRules": ["required", "minlength", "maxlength"],
|
||||||
|
"fieldValidateRulesMaxlength": "100",
|
||||||
|
"fieldValidateRulesMinlength": "2"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"fieldName": "description",
|
||||||
|
"fieldType": "String",
|
||||||
|
"fieldValidateRules": ["maxlength"],
|
||||||
|
"fieldValidateRulesMaxlength": "1000"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"fieldName": "active",
|
||||||
|
"fieldType": "Boolean",
|
||||||
|
"fieldValidateRules": ["required"]
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"name": "Subject",
|
||||||
|
"pagination": "pagination",
|
||||||
|
"relationships": [],
|
||||||
|
"searchEngine": "no",
|
||||||
|
"service": "serviceClass"
|
||||||
|
}
|
||||||
@@ -0,0 +1,3 @@
|
|||||||
|
module.exports = {
|
||||||
|
'{,.blueprint/**/,src/**/}*.{md,json,yml,js,cjs,mjs,ts,cts,mts,java,html,vue,css,scss}': ['prettier --write'],
|
||||||
|
};
|
||||||
@@ -0,0 +1 @@
|
|||||||
|
|
||||||
Vendored
BIN
Binary file not shown.
+2
@@ -0,0 +1,2 @@
|
|||||||
|
distributionType=only-script
|
||||||
|
distributionUrl=https://repo.maven.apache.org/maven2/org/apache/maven/apache-maven/3.9.16/apache-maven-3.9.16-bin.zip
|
||||||
@@ -0,0 +1,10 @@
|
|||||||
|
node_modules
|
||||||
|
package-lock.json
|
||||||
|
.git
|
||||||
|
|
||||||
|
# Generated by jhipster:java-simple-application:maven
|
||||||
|
target
|
||||||
|
.mvn
|
||||||
|
|
||||||
|
# Generated by jhipster:client
|
||||||
|
target/classes/static/
|
||||||
+14
@@ -0,0 +1,14 @@
|
|||||||
|
endOfLine: auto
|
||||||
|
printWidth: 140
|
||||||
|
singleQuote: true
|
||||||
|
tabWidth: 2
|
||||||
|
useTabs: false
|
||||||
|
arrowParens: avoid
|
||||||
|
bracketSameLine: false
|
||||||
|
plugins:
|
||||||
|
- prettier-plugin-packagejson
|
||||||
|
- prettier-plugin-java
|
||||||
|
overrides:
|
||||||
|
- files: "*.java"
|
||||||
|
options:
|
||||||
|
tabWidth: 4
|
||||||
+28
@@ -0,0 +1,28 @@
|
|||||||
|
{
|
||||||
|
"generator-jhipster": {
|
||||||
|
"applicationType": "monolith",
|
||||||
|
"authenticationType": "jwt",
|
||||||
|
"baseName": "questionBank",
|
||||||
|
"buildTool": "maven",
|
||||||
|
"cacheProvider": "no",
|
||||||
|
"clientFramework": "vue",
|
||||||
|
"creationTimestamp": 1792485949603,
|
||||||
|
"databaseMigration": "liquibase",
|
||||||
|
"databaseType": "sql",
|
||||||
|
"defaultCommand": "app",
|
||||||
|
"devDatabaseType": "postgresql",
|
||||||
|
"entities": ["Subject", "Question", "Choice", "ExamAttempt", "ExamAnswer"],
|
||||||
|
"jhipsterVersion": "9.3.0",
|
||||||
|
"jwtSecretKey": "MzY1ZDU0MDM3M2Y5YWRiMDYxYjBmYzZlOTMxMWFiNmYyMDEyZTUzZjYxOWFjNjhlNjU2NzcxYjkwY2Y0MGIwOGUyNTc5NWQyOGY3OGJkMjExZjhkYzU3ZGU4ZDc0ZmZiMTYwMzY0NGM4ZWZmODRhZTZjNzBlNjk5MWJkYjM5YjI=",
|
||||||
|
"languages": ["en"],
|
||||||
|
"lastLiquibaseTimestamp": 1792488806000,
|
||||||
|
"messageBroker": "no",
|
||||||
|
"nativeLanguage": "en",
|
||||||
|
"packageName": "com.example.questionbank",
|
||||||
|
"prodDatabaseType": "postgresql",
|
||||||
|
"searchEngine": "no",
|
||||||
|
"serviceDiscoveryType": "no",
|
||||||
|
"websocket": "no",
|
||||||
|
"withAdminUi": true
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,241 @@
|
|||||||
|
# questionBank
|
||||||
|
|
||||||
|
This application was generated using JHipster 9.3.0, you can find documentation and help at [https://www.jhipster.tech/documentation-archive/v9.3.0](https://www.jhipster.tech/documentation-archive/v9.3.0).
|
||||||
|
|
||||||
|
## Project Structure
|
||||||
|
|
||||||
|
Node is required for generation and recommended for development. `package.json` is always generated for a better development experience with prettier, commit hooks, scripts and so on.
|
||||||
|
|
||||||
|
In the project root, JHipster generates configuration files for tools like git, prettier, eslint, husky, and others that are well known and you can find references in the web.
|
||||||
|
|
||||||
|
`/src/*` structure follows default Java structure.
|
||||||
|
|
||||||
|
- `.yo-rc.json` - Yeoman configuration file
|
||||||
|
JHipster configuration is stored in this file at `generator-jhipster` key. You may find `generator-jhipster-*` for specific blueprints configuration.
|
||||||
|
- `.yo-resolve` (optional) - Yeoman conflict resolver
|
||||||
|
Allows to use a specific action when conflicts are found skipping prompts for files that matches a pattern. Each line should match `[pattern] [action]` with pattern been a [Minimatch](https://github.com/isaacs/minimatch#minimatch) pattern and action been one of skip (default if omitted) or force. Lines starting with `#` are considered comments and are ignored.
|
||||||
|
- `.jhipster/*.json` - JHipster entity configuration files
|
||||||
|
|
||||||
|
- `npmw` - wrapper to use locally installed npm.
|
||||||
|
JHipster installs Node and npm locally using the build tool by default. This wrapper makes sure npm is installed locally and uses it avoiding some differences different versions can cause. By using `./npmw` instead of the traditional `npm` you can configure a Node-less environment to develop or test your application.
|
||||||
|
- `/src/main/docker` - Docker configurations for the application and services that the application depends on
|
||||||
|
|
||||||
|
## Development
|
||||||
|
|
||||||
|
The build system will install automatically the recommended version of Node and npm.
|
||||||
|
|
||||||
|
We provide a wrapper to launch npm.
|
||||||
|
You will only need to run this command when dependencies change in [package.json](package.json).
|
||||||
|
|
||||||
|
```bash
|
||||||
|
./npmw install
|
||||||
|
```
|
||||||
|
|
||||||
|
We use npm scripts and Vite as our build system.
|
||||||
|
|
||||||
|
Run the following commands in two separate terminals to create a blissful development experience where your browser
|
||||||
|
auto-refreshes when files change on your hard drive.
|
||||||
|
|
||||||
|
```bash
|
||||||
|
./npmw run backend:start
|
||||||
|
./npmw run start
|
||||||
|
```
|
||||||
|
|
||||||
|
Npm is also used to manage CSS and JavaScript dependencies used in this application. You can upgrade dependencies by
|
||||||
|
specifying a newer version in [package.json](package.json). You can also run `./npmw update` and `./npmw install` to manage dependencies.
|
||||||
|
Add the `help` flag on any command to see how you can use it. For example, `./npmw help update`.
|
||||||
|
|
||||||
|
The `./npmw run` command will list all the scripts available to run for this project.
|
||||||
|
|
||||||
|
### PWA Support
|
||||||
|
|
||||||
|
JHipster ships with PWA (Progressive Web App) support, and it's turned off by default. One of the main components of a PWA is a service worker.
|
||||||
|
|
||||||
|
The service worker initialization code is commented out by default. To enable it, uncomment the following code in `src/main/webapp/index.html`:
|
||||||
|
|
||||||
|
```html
|
||||||
|
<script>
|
||||||
|
if ('serviceWorker' in navigator) {
|
||||||
|
navigator.serviceWorker.register('./service-worker.js').then(function () {
|
||||||
|
console.log('Service Worker Registered');
|
||||||
|
});
|
||||||
|
}
|
||||||
|
</script>
|
||||||
|
```
|
||||||
|
|
||||||
|
Note: [Workbox](https://developer.chrome.com/docs/workbox) powers JHipster's service worker. It dynamically generates the `service-worker.js` file.
|
||||||
|
|
||||||
|
### Managing dependencies
|
||||||
|
|
||||||
|
For example, to add [Leaflet](https://leafletjs.com/) library as a runtime dependency of your application, you would run the following command:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
./npmw install --save --save-exact leaflet
|
||||||
|
```
|
||||||
|
|
||||||
|
To benefit from TypeScript type definitions from [DefinitelyTyped](https://definitelytyped.org/) repository in development, you would run the following command:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
./npmw install --save-dev --save-exact @types/leaflet
|
||||||
|
```
|
||||||
|
|
||||||
|
Then you would import the JS and CSS files specified in library's installation instructions so that [Vite][] knows about them:
|
||||||
|
Note: There are still a few other things remaining to do for Leaflet that we won't detail here.
|
||||||
|
|
||||||
|
For further instructions on how to develop with JHipster, have a look at [Using JHipster in development](https://www.jhipster.tech/development/).
|
||||||
|
|
||||||
|
## Building for production
|
||||||
|
|
||||||
|
### Packaging as jar
|
||||||
|
|
||||||
|
To build the final jar and optimize the questionBank application for production, run:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
./mvnw -Pprod clean verify
|
||||||
|
```
|
||||||
|
|
||||||
|
This will concatenate and minify the client CSS and JavaScript files. It will also modify `index.html` so it references these new files.
|
||||||
|
To ensure everything worked, run:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
java -jar target/*.jar
|
||||||
|
```
|
||||||
|
|
||||||
|
Then navigate to [http://localhost:8080](http://localhost:8080) in your browser.
|
||||||
|
|
||||||
|
Refer to [Using JHipster in production][] for more details.
|
||||||
|
|
||||||
|
### Packaging as war
|
||||||
|
|
||||||
|
To package your application as a war in order to deploy it to an application server, run:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
./mvnw -Pprod,war clean verify
|
||||||
|
```
|
||||||
|
|
||||||
|
### JHipster Control Center
|
||||||
|
|
||||||
|
JHipster Control Center can help you manage and control your application(s). You can start a local control center server (accessible on http://localhost:7419) with:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
docker compose -f src/main/docker/jhipster-control-center.yml up
|
||||||
|
```
|
||||||
|
|
||||||
|
## Testing
|
||||||
|
|
||||||
|
### Spring Boot tests
|
||||||
|
|
||||||
|
To launch your application's tests, run:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
./mvnw verify
|
||||||
|
```
|
||||||
|
|
||||||
|
### Client tests
|
||||||
|
|
||||||
|
Unit tests are run by Vitest. They're located near components and can be run with:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
./npmw test
|
||||||
|
```
|
||||||
|
|
||||||
|
## Others
|
||||||
|
|
||||||
|
### Code quality using Sonar
|
||||||
|
|
||||||
|
Sonar is used to analyse code quality. You can start a local Sonar server (accessible on http://localhost:9001) with:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
docker compose -f src/main/docker/sonar.yml up -d
|
||||||
|
```
|
||||||
|
|
||||||
|
Note: we have turned off forced authentication redirect for UI in [src/main/docker/sonar.yml](src/main/docker/sonar.yml) for out of the box experience while trying out SonarQube, for real use cases turn it back on.
|
||||||
|
|
||||||
|
You can run a Sonar analysis with using the [sonar-scanner](https://docs.sonarqube.org/display/SCAN/Analyzing+with+SonarQube+Scanner) or by using the maven plugin.
|
||||||
|
|
||||||
|
Then, run a Sonar analysis:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
./mvnw -Pprod clean verify sonar:sonar -Dsonar.login=admin -Dsonar.password=admin
|
||||||
|
```
|
||||||
|
|
||||||
|
If you need to re-run the Sonar phase, please be sure to specify at least the `initialize` phase since Sonar properties are loaded from the sonar-project.properties file.
|
||||||
|
|
||||||
|
```bash
|
||||||
|
./mvnw initialize sonar:sonar -Dsonar.login=admin -Dsonar.password=admin
|
||||||
|
```
|
||||||
|
|
||||||
|
Additionally, Instead of passing `sonar.password` and `sonar.login` as CLI arguments, these parameters can be configured from [sonar-project.properties](sonar-project.properties) as shown below:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
sonar.login=admin
|
||||||
|
sonar.password=admin
|
||||||
|
```
|
||||||
|
|
||||||
|
For more information, refer to the [Code quality page][].
|
||||||
|
|
||||||
|
### Docker Compose support
|
||||||
|
|
||||||
|
JHipster generates a number of Docker Compose configuration files in the [src/main/docker/](src/main/docker/) folder to launch required third party services.
|
||||||
|
|
||||||
|
For example, to start required services in Docker containers, run:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
docker compose -f src/main/docker/services.yml up -d
|
||||||
|
```
|
||||||
|
|
||||||
|
To stop and remove the containers, run:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
docker compose -f src/main/docker/services.yml down
|
||||||
|
```
|
||||||
|
|
||||||
|
[Spring Docker Compose Integration](https://docs.spring.io/spring-boot/reference/features/dev-services.html) is enabled by default. It's possible to disable it in `application.yml`:
|
||||||
|
|
||||||
|
```yaml
|
||||||
|
spring:
|
||||||
|
...
|
||||||
|
docker:
|
||||||
|
compose:
|
||||||
|
enabled: false
|
||||||
|
```
|
||||||
|
|
||||||
|
You can also fully dockerize your application and all the services that it depends on.
|
||||||
|
To achieve this, first build a Docker image of your app by running:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
npm run java:docker
|
||||||
|
```
|
||||||
|
|
||||||
|
Or build an arm64 Docker image when using an arm64 processor OS, i.e., Apple Silicon chips (M*), running:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
npm run java:docker:arm64
|
||||||
|
```
|
||||||
|
|
||||||
|
Then run:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
docker compose -f src/main/docker/app.yml up -d
|
||||||
|
```
|
||||||
|
|
||||||
|
For more information refer to [Docker and Docker-Compose](https://www.jhipster.tech/documentation-archive/v9.3.0/docker-compose/), this page also contains information on the Docker Compose sub-generator (`jhipster docker-compose`), which is able to generate Docker configurations for one or several JHipster applications.
|
||||||
|
|
||||||
|
## Continuous Integration (optional)
|
||||||
|
|
||||||
|
To configure CI for your project, run the ci-cd sub-generator (`jhipster ci-cd`), this will let you generate configuration files for a number of Continuous Integration systems. Consult the [Setting up Continuous Integration](https://www.jhipster.tech/documentation-archive/v9.3.0/setting-up-ci/) page for more information.
|
||||||
|
|
||||||
|
## References
|
||||||
|
|
||||||
|
- [JHipster Homepage and latest documentation](https://www.jhipster.tech/)
|
||||||
|
- [JHipster 9.3.0 archive](https://www.jhipster.tech/documentation-archive/v9.3.0)
|
||||||
|
- [Using JHipster in development](https://www.jhipster.tech/documentation-archive/v9.3.0/development/)
|
||||||
|
- [Using Docker and Docker-Compose](https://www.jhipster.tech/documentation-archive/v9.3.0/docker-compose)
|
||||||
|
- [Using JHipster in production](https://www.jhipster.tech/documentation-archive/v9.3.0/production/)
|
||||||
|
- [Running tests page](https://www.jhipster.tech/documentation-archive/v9.3.0/running-tests/)
|
||||||
|
- [Code quality page](https://www.jhipster.tech/documentation-archive/v9.3.0/code-quality/)
|
||||||
|
- [Setting up Continuous Integration](https://www.jhipster.tech/documentation-archive/v9.3.0/setting-up-ci/)
|
||||||
|
- [Node.js](https://nodejs.org/)
|
||||||
|
- [NPM](https://www.npmjs.com/)
|
||||||
|
- [Leaflet](https://leafletjs.com/)
|
||||||
|
- [DefinitelyTyped](https://definitelytyped.org/)
|
||||||
@@ -0,0 +1,18 @@
|
|||||||
|
<?xml version="1.0"?>
|
||||||
|
<!DOCTYPE module PUBLIC "-//Puppy Crawl//DTD Check Configuration 1.3//EN"
|
||||||
|
"https://checkstyle.org/dtds/configuration_1_3.dtd">
|
||||||
|
<module name="Checker">
|
||||||
|
|
||||||
|
<!-- Configure checker to use UTF-8 encoding -->
|
||||||
|
<property name="charset" value="UTF-8"/>
|
||||||
|
<!-- Configure checker to run on files with these extensions -->
|
||||||
|
<property name="fileExtensions" value=""/>
|
||||||
|
<!-- For detailed checkstyle configuration, see https://github.com/spring-io/nohttp/tree/master/nohttp-checkstyle -->
|
||||||
|
<module name="io.spring.nohttp.checkstyle.check.NoHttpCheck"/>
|
||||||
|
<!-- Allow suppression with comments
|
||||||
|
// CHECKSTYLE:OFF
|
||||||
|
... ignored content ...
|
||||||
|
// CHECKSTYLE:ON
|
||||||
|
-->
|
||||||
|
<module name="SuppressWithPlainTextCommentFilter"/>
|
||||||
|
</module>
|
||||||
@@ -0,0 +1,66 @@
|
|||||||
|
import js from '@eslint/js';
|
||||||
|
import { defineConfig } from 'eslint/config';
|
||||||
|
import prettier from 'eslint-plugin-prettier/recommended';
|
||||||
|
import vue from 'eslint-plugin-vue';
|
||||||
|
import globals from 'globals';
|
||||||
|
import tseslint from 'typescript-eslint';
|
||||||
|
// jhipster-needle-eslint-add-import - JHipster will add additional import here
|
||||||
|
|
||||||
|
export default defineConfig(
|
||||||
|
{
|
||||||
|
languageOptions: {
|
||||||
|
globals: {
|
||||||
|
...globals.node,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{ ignores: ['src/main/docker/'] },
|
||||||
|
{ ignores: ['target/classes/static/', 'target/'] },
|
||||||
|
js.configs.recommended,
|
||||||
|
...tseslint.configs.recommended.map(config =>
|
||||||
|
config.name === 'typescript-eslint/base' ? config : { ...config, files: ['**/*.ts', '**/*.tsx', '**/*.mts', '**/*.cts'] },
|
||||||
|
),
|
||||||
|
{
|
||||||
|
files: ['**/*.{js,cjs,mjs}'],
|
||||||
|
rules: {
|
||||||
|
'no-unused-vars': ['error', { argsIgnorePattern: '^_' }],
|
||||||
|
},
|
||||||
|
},
|
||||||
|
...vue.configs['flat/recommended'],
|
||||||
|
{
|
||||||
|
files: ['**/*.vue'],
|
||||||
|
languageOptions: {
|
||||||
|
parserOptions: { parser: '@typescript-eslint/parser' },
|
||||||
|
globals: { ...globals.browser },
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
files: ['src/main/webapp/**/*.vue', 'src/main/webapp/**/*.ts'],
|
||||||
|
languageOptions: {
|
||||||
|
globals: { ...globals.browser },
|
||||||
|
},
|
||||||
|
rules: {
|
||||||
|
'no-console': process.env.NODE_ENV === 'production' ? 'warn' : 'off',
|
||||||
|
'no-debugger': process.env.NODE_ENV === 'production' ? 'warn' : 'off',
|
||||||
|
'vue/multi-word-component-names': 'off',
|
||||||
|
'@typescript-eslint/no-explicit-any': 'off',
|
||||||
|
'@typescript-eslint/no-unused-vars': 'off',
|
||||||
|
'@typescript-eslint/explicit-module-boundary-types': 'off',
|
||||||
|
'@typescript-eslint/no-empty-function': 'off',
|
||||||
|
'@typescript-eslint/ban-ts-comment': 'off',
|
||||||
|
'@typescript-eslint/no-var-requires': 'off',
|
||||||
|
'@typescript-eslint/consistent-type-imports': 'error',
|
||||||
|
'vue/no-v-text-v-html-on-component': ['error', { allow: ['router-link', 'b-alert', 'b-badge', 'b-button', 'b-link'] }],
|
||||||
|
'vue/no-reserved-component-names': 'off',
|
||||||
|
'vue/attributes-order': 'off',
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
files: ['src/main/webapp/**/*.spec.ts'],
|
||||||
|
rules: {
|
||||||
|
'@typescript-eslint/no-empty-function': 'off',
|
||||||
|
},
|
||||||
|
},
|
||||||
|
// jhipster-needle-eslint-add-config - JHipster will add additional config here
|
||||||
|
prettier,
|
||||||
|
);
|
||||||
@@ -0,0 +1,295 @@
|
|||||||
|
#!/bin/sh
|
||||||
|
# ----------------------------------------------------------------------------
|
||||||
|
# Licensed to the Apache Software Foundation (ASF) under one
|
||||||
|
# or more contributor license agreements. See the NOTICE file
|
||||||
|
# distributed with this work for additional information
|
||||||
|
# regarding copyright ownership. The ASF licenses this file
|
||||||
|
# to you 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
|
||||||
|
#
|
||||||
|
# http://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.
|
||||||
|
# ----------------------------------------------------------------------------
|
||||||
|
|
||||||
|
# ----------------------------------------------------------------------------
|
||||||
|
# Apache Maven Wrapper startup batch script, version 3.3.4
|
||||||
|
#
|
||||||
|
# Optional ENV vars
|
||||||
|
# -----------------
|
||||||
|
# JAVA_HOME - location of a JDK home dir, required when download maven via java source
|
||||||
|
# MVNW_REPOURL - repo url base for downloading maven distribution
|
||||||
|
# MVNW_USERNAME/MVNW_PASSWORD - user and password for downloading maven
|
||||||
|
# MVNW_VERBOSE - true: enable verbose log; debug: trace the mvnw script; others: silence the output
|
||||||
|
# ----------------------------------------------------------------------------
|
||||||
|
|
||||||
|
set -euf
|
||||||
|
[ "${MVNW_VERBOSE-}" != debug ] || set -x
|
||||||
|
|
||||||
|
# OS specific support.
|
||||||
|
native_path() { printf %s\\n "$1"; }
|
||||||
|
case "$(uname)" in
|
||||||
|
CYGWIN* | MINGW*)
|
||||||
|
[ -z "${JAVA_HOME-}" ] || JAVA_HOME="$(cygpath --unix "$JAVA_HOME")"
|
||||||
|
native_path() { cygpath --path --windows "$1"; }
|
||||||
|
;;
|
||||||
|
esac
|
||||||
|
|
||||||
|
# set JAVACMD and JAVACCMD
|
||||||
|
set_java_home() {
|
||||||
|
# For Cygwin and MinGW, ensure paths are in Unix format before anything is touched
|
||||||
|
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"
|
||||||
|
JAVACCMD="$JAVA_HOME/jre/sh/javac"
|
||||||
|
else
|
||||||
|
JAVACMD="$JAVA_HOME/bin/java"
|
||||||
|
JAVACCMD="$JAVA_HOME/bin/javac"
|
||||||
|
|
||||||
|
if [ ! -x "$JAVACMD" ] || [ ! -x "$JAVACCMD" ]; then
|
||||||
|
echo "The JAVA_HOME environment variable is not defined correctly, so mvnw cannot run." >&2
|
||||||
|
echo "JAVA_HOME is set to \"$JAVA_HOME\", but \"\$JAVA_HOME/bin/java\" or \"\$JAVA_HOME/bin/javac\" does not exist." >&2
|
||||||
|
return 1
|
||||||
|
fi
|
||||||
|
fi
|
||||||
|
else
|
||||||
|
JAVACMD="$(
|
||||||
|
'set' +e
|
||||||
|
'unset' -f command 2>/dev/null
|
||||||
|
'command' -v java
|
||||||
|
)" || :
|
||||||
|
JAVACCMD="$(
|
||||||
|
'set' +e
|
||||||
|
'unset' -f command 2>/dev/null
|
||||||
|
'command' -v javac
|
||||||
|
)" || :
|
||||||
|
|
||||||
|
if [ ! -x "${JAVACMD-}" ] || [ ! -x "${JAVACCMD-}" ]; then
|
||||||
|
echo "The java/javac command does not exist in PATH nor is JAVA_HOME set, so mvnw cannot run." >&2
|
||||||
|
return 1
|
||||||
|
fi
|
||||||
|
fi
|
||||||
|
}
|
||||||
|
|
||||||
|
# hash string like Java String::hashCode
|
||||||
|
hash_string() {
|
||||||
|
str="${1:-}" h=0
|
||||||
|
while [ -n "$str" ]; do
|
||||||
|
char="${str%"${str#?}"}"
|
||||||
|
h=$(((h * 31 + $(LC_CTYPE=C printf %d "'$char")) % 4294967296))
|
||||||
|
str="${str#?}"
|
||||||
|
done
|
||||||
|
printf %x\\n $h
|
||||||
|
}
|
||||||
|
|
||||||
|
verbose() { :; }
|
||||||
|
[ "${MVNW_VERBOSE-}" != true ] || verbose() { printf %s\\n "${1-}"; }
|
||||||
|
|
||||||
|
die() {
|
||||||
|
printf %s\\n "$1" >&2
|
||||||
|
exit 1
|
||||||
|
}
|
||||||
|
|
||||||
|
trim() {
|
||||||
|
# MWRAPPER-139:
|
||||||
|
# Trims trailing and leading whitespace, carriage returns, tabs, and linefeeds.
|
||||||
|
# Needed for removing poorly interpreted newline sequences when running in more
|
||||||
|
# exotic environments such as mingw bash on Windows.
|
||||||
|
printf "%s" "${1}" | tr -d '[:space:]'
|
||||||
|
}
|
||||||
|
|
||||||
|
scriptDir="$(dirname "$0")"
|
||||||
|
scriptName="$(basename "$0")"
|
||||||
|
|
||||||
|
# parse distributionUrl and optional distributionSha256Sum, requires .mvn/wrapper/maven-wrapper.properties
|
||||||
|
while IFS="=" read -r key value; do
|
||||||
|
case "${key-}" in
|
||||||
|
distributionUrl) distributionUrl=$(trim "${value-}") ;;
|
||||||
|
distributionSha256Sum) distributionSha256Sum=$(trim "${value-}") ;;
|
||||||
|
esac
|
||||||
|
done <"$scriptDir/.mvn/wrapper/maven-wrapper.properties"
|
||||||
|
[ -n "${distributionUrl-}" ] || die "cannot read distributionUrl property in $scriptDir/.mvn/wrapper/maven-wrapper.properties"
|
||||||
|
|
||||||
|
case "${distributionUrl##*/}" in
|
||||||
|
maven-mvnd-*bin.*)
|
||||||
|
MVN_CMD=mvnd.sh _MVNW_REPO_PATTERN=/maven/mvnd/
|
||||||
|
case "${PROCESSOR_ARCHITECTURE-}${PROCESSOR_ARCHITEW6432-}:$(uname -a)" in
|
||||||
|
*AMD64:CYGWIN* | *AMD64:MINGW*) distributionPlatform=windows-amd64 ;;
|
||||||
|
:Darwin*x86_64) distributionPlatform=darwin-amd64 ;;
|
||||||
|
:Darwin*arm64) distributionPlatform=darwin-aarch64 ;;
|
||||||
|
:Linux*x86_64*) distributionPlatform=linux-amd64 ;;
|
||||||
|
*)
|
||||||
|
echo "Cannot detect native platform for mvnd on $(uname)-$(uname -m), use pure java version" >&2
|
||||||
|
distributionPlatform=linux-amd64
|
||||||
|
;;
|
||||||
|
esac
|
||||||
|
distributionUrl="${distributionUrl%-bin.*}-$distributionPlatform.zip"
|
||||||
|
;;
|
||||||
|
maven-mvnd-*) MVN_CMD=mvnd.sh _MVNW_REPO_PATTERN=/maven/mvnd/ ;;
|
||||||
|
*) MVN_CMD="mvn${scriptName#mvnw}" _MVNW_REPO_PATTERN=/org/apache/maven/ ;;
|
||||||
|
esac
|
||||||
|
|
||||||
|
# apply MVNW_REPOURL and calculate MAVEN_HOME
|
||||||
|
# maven home pattern: ~/.m2/wrapper/dists/{apache-maven-<version>,maven-mvnd-<version>-<platform>}/<hash>
|
||||||
|
[ -z "${MVNW_REPOURL-}" ] || distributionUrl="$MVNW_REPOURL$_MVNW_REPO_PATTERN${distributionUrl#*"$_MVNW_REPO_PATTERN"}"
|
||||||
|
distributionUrlName="${distributionUrl##*/}"
|
||||||
|
distributionUrlNameMain="${distributionUrlName%.*}"
|
||||||
|
distributionUrlNameMain="${distributionUrlNameMain%-bin}"
|
||||||
|
MAVEN_USER_HOME="${MAVEN_USER_HOME:-${HOME}/.m2}"
|
||||||
|
MAVEN_HOME="${MAVEN_USER_HOME}/wrapper/dists/${distributionUrlNameMain-}/$(hash_string "$distributionUrl")"
|
||||||
|
|
||||||
|
exec_maven() {
|
||||||
|
unset MVNW_VERBOSE MVNW_USERNAME MVNW_PASSWORD MVNW_REPOURL || :
|
||||||
|
exec "$MAVEN_HOME/bin/$MVN_CMD" "$@" || die "cannot exec $MAVEN_HOME/bin/$MVN_CMD"
|
||||||
|
}
|
||||||
|
|
||||||
|
if [ -d "$MAVEN_HOME" ]; then
|
||||||
|
verbose "found existing MAVEN_HOME at $MAVEN_HOME"
|
||||||
|
exec_maven "$@"
|
||||||
|
fi
|
||||||
|
|
||||||
|
case "${distributionUrl-}" in
|
||||||
|
*?-bin.zip | *?maven-mvnd-?*-?*.zip) ;;
|
||||||
|
*) die "distributionUrl is not valid, must match *-bin.zip or maven-mvnd-*.zip, but found '${distributionUrl-}'" ;;
|
||||||
|
esac
|
||||||
|
|
||||||
|
# prepare tmp dir
|
||||||
|
if TMP_DOWNLOAD_DIR="$(mktemp -d)" && [ -d "$TMP_DOWNLOAD_DIR" ]; then
|
||||||
|
clean() { rm -rf -- "$TMP_DOWNLOAD_DIR"; }
|
||||||
|
trap clean HUP INT TERM EXIT
|
||||||
|
else
|
||||||
|
die "cannot create temp dir"
|
||||||
|
fi
|
||||||
|
|
||||||
|
mkdir -p -- "${MAVEN_HOME%/*}"
|
||||||
|
|
||||||
|
# Download and Install Apache Maven
|
||||||
|
verbose "Couldn't find MAVEN_HOME, downloading and installing it ..."
|
||||||
|
verbose "Downloading from: $distributionUrl"
|
||||||
|
verbose "Downloading to: $TMP_DOWNLOAD_DIR/$distributionUrlName"
|
||||||
|
|
||||||
|
# select .zip or .tar.gz
|
||||||
|
if ! command -v unzip >/dev/null; then
|
||||||
|
distributionUrl="${distributionUrl%.zip}.tar.gz"
|
||||||
|
distributionUrlName="${distributionUrl##*/}"
|
||||||
|
fi
|
||||||
|
|
||||||
|
# verbose opt
|
||||||
|
__MVNW_QUIET_WGET=--quiet __MVNW_QUIET_CURL=--silent __MVNW_QUIET_UNZIP=-q __MVNW_QUIET_TAR=''
|
||||||
|
[ "${MVNW_VERBOSE-}" != true ] || __MVNW_QUIET_WGET='' __MVNW_QUIET_CURL='' __MVNW_QUIET_UNZIP='' __MVNW_QUIET_TAR=v
|
||||||
|
|
||||||
|
# normalize http auth
|
||||||
|
case "${MVNW_PASSWORD:+has-password}" in
|
||||||
|
'') MVNW_USERNAME='' MVNW_PASSWORD='' ;;
|
||||||
|
has-password) [ -n "${MVNW_USERNAME-}" ] || MVNW_USERNAME='' MVNW_PASSWORD='' ;;
|
||||||
|
esac
|
||||||
|
|
||||||
|
if [ -z "${MVNW_USERNAME-}" ] && command -v wget >/dev/null; then
|
||||||
|
verbose "Found wget ... using wget"
|
||||||
|
wget ${__MVNW_QUIET_WGET:+"$__MVNW_QUIET_WGET"} "$distributionUrl" -O "$TMP_DOWNLOAD_DIR/$distributionUrlName" || die "wget: Failed to fetch $distributionUrl"
|
||||||
|
elif [ -z "${MVNW_USERNAME-}" ] && command -v curl >/dev/null; then
|
||||||
|
verbose "Found curl ... using curl"
|
||||||
|
curl ${__MVNW_QUIET_CURL:+"$__MVNW_QUIET_CURL"} -f -L -o "$TMP_DOWNLOAD_DIR/$distributionUrlName" "$distributionUrl" || die "curl: Failed to fetch $distributionUrl"
|
||||||
|
elif set_java_home; then
|
||||||
|
verbose "Falling back to use Java to download"
|
||||||
|
javaSource="$TMP_DOWNLOAD_DIR/Downloader.java"
|
||||||
|
targetZip="$TMP_DOWNLOAD_DIR/$distributionUrlName"
|
||||||
|
cat >"$javaSource" <<-END
|
||||||
|
public class Downloader extends java.net.Authenticator
|
||||||
|
{
|
||||||
|
protected java.net.PasswordAuthentication getPasswordAuthentication()
|
||||||
|
{
|
||||||
|
return new java.net.PasswordAuthentication( System.getenv( "MVNW_USERNAME" ), System.getenv( "MVNW_PASSWORD" ).toCharArray() );
|
||||||
|
}
|
||||||
|
public static void main( String[] args ) throws Exception
|
||||||
|
{
|
||||||
|
setDefault( new Downloader() );
|
||||||
|
java.nio.file.Files.copy( java.net.URI.create( args[0] ).toURL().openStream(), java.nio.file.Paths.get( args[1] ).toAbsolutePath().normalize() );
|
||||||
|
}
|
||||||
|
}
|
||||||
|
END
|
||||||
|
# For Cygwin/MinGW, switch paths to Windows format before running javac and java
|
||||||
|
verbose " - Compiling Downloader.java ..."
|
||||||
|
"$(native_path "$JAVACCMD")" "$(native_path "$javaSource")" || die "Failed to compile Downloader.java"
|
||||||
|
verbose " - Running Downloader.java ..."
|
||||||
|
"$(native_path "$JAVACMD")" -cp "$(native_path "$TMP_DOWNLOAD_DIR")" Downloader "$distributionUrl" "$(native_path "$targetZip")"
|
||||||
|
fi
|
||||||
|
|
||||||
|
# If specified, validate the SHA-256 sum of the Maven distribution zip file
|
||||||
|
if [ -n "${distributionSha256Sum-}" ]; then
|
||||||
|
distributionSha256Result=false
|
||||||
|
if [ "$MVN_CMD" = mvnd.sh ]; then
|
||||||
|
echo "Checksum validation is not supported for maven-mvnd." >&2
|
||||||
|
echo "Please disable validation by removing 'distributionSha256Sum' from your maven-wrapper.properties." >&2
|
||||||
|
exit 1
|
||||||
|
elif command -v sha256sum >/dev/null; then
|
||||||
|
if echo "$distributionSha256Sum $TMP_DOWNLOAD_DIR/$distributionUrlName" | sha256sum -c - >/dev/null 2>&1; then
|
||||||
|
distributionSha256Result=true
|
||||||
|
fi
|
||||||
|
elif command -v shasum >/dev/null; then
|
||||||
|
if echo "$distributionSha256Sum $TMP_DOWNLOAD_DIR/$distributionUrlName" | shasum -a 256 -c >/dev/null 2>&1; then
|
||||||
|
distributionSha256Result=true
|
||||||
|
fi
|
||||||
|
else
|
||||||
|
echo "Checksum validation was requested but neither 'sha256sum' or 'shasum' are available." >&2
|
||||||
|
echo "Please install either command, or disable validation by removing 'distributionSha256Sum' from your maven-wrapper.properties." >&2
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
|
if [ $distributionSha256Result = false ]; then
|
||||||
|
echo "Error: Failed to validate Maven distribution SHA-256, your Maven distribution might be compromised." >&2
|
||||||
|
echo "If you updated your Maven version, you need to update the specified distributionSha256Sum property." >&2
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
|
fi
|
||||||
|
|
||||||
|
# unzip and move
|
||||||
|
if command -v unzip >/dev/null; then
|
||||||
|
unzip ${__MVNW_QUIET_UNZIP:+"$__MVNW_QUIET_UNZIP"} "$TMP_DOWNLOAD_DIR/$distributionUrlName" -d "$TMP_DOWNLOAD_DIR" || die "failed to unzip"
|
||||||
|
else
|
||||||
|
tar xzf${__MVNW_QUIET_TAR:+"$__MVNW_QUIET_TAR"} "$TMP_DOWNLOAD_DIR/$distributionUrlName" -C "$TMP_DOWNLOAD_DIR" || die "failed to untar"
|
||||||
|
fi
|
||||||
|
|
||||||
|
# Find the actual extracted directory name (handles snapshots where filename != directory name)
|
||||||
|
actualDistributionDir=""
|
||||||
|
|
||||||
|
# First try the expected directory name (for regular distributions)
|
||||||
|
if [ -d "$TMP_DOWNLOAD_DIR/$distributionUrlNameMain" ]; then
|
||||||
|
if [ -f "$TMP_DOWNLOAD_DIR/$distributionUrlNameMain/bin/$MVN_CMD" ]; then
|
||||||
|
actualDistributionDir="$distributionUrlNameMain"
|
||||||
|
fi
|
||||||
|
fi
|
||||||
|
|
||||||
|
# If not found, search for any directory with the Maven executable (for snapshots)
|
||||||
|
if [ -z "$actualDistributionDir" ]; then
|
||||||
|
# enable globbing to iterate over items
|
||||||
|
set +f
|
||||||
|
for dir in "$TMP_DOWNLOAD_DIR"/*; do
|
||||||
|
if [ -d "$dir" ]; then
|
||||||
|
if [ -f "$dir/bin/$MVN_CMD" ]; then
|
||||||
|
actualDistributionDir="$(basename "$dir")"
|
||||||
|
break
|
||||||
|
fi
|
||||||
|
fi
|
||||||
|
done
|
||||||
|
set -f
|
||||||
|
fi
|
||||||
|
|
||||||
|
if [ -z "$actualDistributionDir" ]; then
|
||||||
|
verbose "Contents of $TMP_DOWNLOAD_DIR:"
|
||||||
|
verbose "$(ls -la "$TMP_DOWNLOAD_DIR")"
|
||||||
|
die "Could not find Maven distribution directory in extracted archive"
|
||||||
|
fi
|
||||||
|
|
||||||
|
verbose "Found extracted Maven distribution directory: $actualDistributionDir"
|
||||||
|
printf %s\\n "$distributionUrl" >"$TMP_DOWNLOAD_DIR/$actualDistributionDir/mvnw.url"
|
||||||
|
mv -- "$TMP_DOWNLOAD_DIR/$actualDistributionDir" "$MAVEN_HOME" || [ -d "$MAVEN_HOME" ] || die "fail to move MAVEN_HOME"
|
||||||
|
|
||||||
|
clean || :
|
||||||
|
exec_maven "$@"
|
||||||
@@ -0,0 +1,189 @@
|
|||||||
|
<# : batch portion
|
||||||
|
@REM ----------------------------------------------------------------------------
|
||||||
|
@REM Licensed to the Apache Software Foundation (ASF) under one
|
||||||
|
@REM or more contributor license agreements. See the NOTICE file
|
||||||
|
@REM distributed with this work for additional information
|
||||||
|
@REM regarding copyright ownership. The ASF licenses this file
|
||||||
|
@REM to you under the Apache License, Version 2.0 (the
|
||||||
|
@REM "License"); you may not use this file except in compliance
|
||||||
|
@REM with the License. You may obtain a copy of the License at
|
||||||
|
@REM
|
||||||
|
@REM http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
@REM
|
||||||
|
@REM Unless required by applicable law or agreed to in writing,
|
||||||
|
@REM software distributed under the License is distributed on an
|
||||||
|
@REM "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
|
||||||
|
@REM KIND, either express or implied. See the License for the
|
||||||
|
@REM specific language governing permissions and limitations
|
||||||
|
@REM under the License.
|
||||||
|
@REM ----------------------------------------------------------------------------
|
||||||
|
|
||||||
|
@REM ----------------------------------------------------------------------------
|
||||||
|
@REM Apache Maven Wrapper startup batch script, version 3.3.4
|
||||||
|
@REM
|
||||||
|
@REM Optional ENV vars
|
||||||
|
@REM MVNW_REPOURL - repo url base for downloading maven distribution
|
||||||
|
@REM MVNW_USERNAME/MVNW_PASSWORD - user and password for downloading maven
|
||||||
|
@REM MVNW_VERBOSE - true: enable verbose log; others: silence the output
|
||||||
|
@REM ----------------------------------------------------------------------------
|
||||||
|
|
||||||
|
@IF "%__MVNW_ARG0_NAME__%"=="" (SET __MVNW_ARG0_NAME__=%~nx0)
|
||||||
|
@SET __MVNW_CMD__=
|
||||||
|
@SET __MVNW_ERROR__=
|
||||||
|
@SET __MVNW_PSMODULEP_SAVE=%PSModulePath%
|
||||||
|
@SET PSModulePath=
|
||||||
|
@FOR /F "usebackq tokens=1* delims==" %%A IN (`powershell -noprofile "& {$scriptDir='%~dp0'; $script='%__MVNW_ARG0_NAME__%'; icm -ScriptBlock ([Scriptblock]::Create((Get-Content -Raw '%~f0'))) -NoNewScope}"`) DO @(
|
||||||
|
IF "%%A"=="MVN_CMD" (set __MVNW_CMD__=%%B) ELSE IF "%%B"=="" (echo %%A) ELSE (echo %%A=%%B)
|
||||||
|
)
|
||||||
|
@SET PSModulePath=%__MVNW_PSMODULEP_SAVE%
|
||||||
|
@SET __MVNW_PSMODULEP_SAVE=
|
||||||
|
@SET __MVNW_ARG0_NAME__=
|
||||||
|
@SET MVNW_USERNAME=
|
||||||
|
@SET MVNW_PASSWORD=
|
||||||
|
@IF NOT "%__MVNW_CMD__%"=="" ("%__MVNW_CMD__%" %*)
|
||||||
|
@echo Cannot start maven from wrapper >&2 && exit /b 1
|
||||||
|
@GOTO :EOF
|
||||||
|
: end batch / begin powershell #>
|
||||||
|
|
||||||
|
$ErrorActionPreference = "Stop"
|
||||||
|
if ($env:MVNW_VERBOSE -eq "true") {
|
||||||
|
$VerbosePreference = "Continue"
|
||||||
|
}
|
||||||
|
|
||||||
|
# calculate distributionUrl, requires .mvn/wrapper/maven-wrapper.properties
|
||||||
|
$distributionUrl = (Get-Content -Raw "$scriptDir/.mvn/wrapper/maven-wrapper.properties" | ConvertFrom-StringData).distributionUrl
|
||||||
|
if (!$distributionUrl) {
|
||||||
|
Write-Error "cannot read distributionUrl property in $scriptDir/.mvn/wrapper/maven-wrapper.properties"
|
||||||
|
}
|
||||||
|
|
||||||
|
switch -wildcard -casesensitive ( $($distributionUrl -replace '^.*/','') ) {
|
||||||
|
"maven-mvnd-*" {
|
||||||
|
$USE_MVND = $true
|
||||||
|
$distributionUrl = $distributionUrl -replace '-bin\.[^.]*$',"-windows-amd64.zip"
|
||||||
|
$MVN_CMD = "mvnd.cmd"
|
||||||
|
break
|
||||||
|
}
|
||||||
|
default {
|
||||||
|
$USE_MVND = $false
|
||||||
|
$MVN_CMD = $script -replace '^mvnw','mvn'
|
||||||
|
break
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
# apply MVNW_REPOURL and calculate MAVEN_HOME
|
||||||
|
# maven home pattern: ~/.m2/wrapper/dists/{apache-maven-<version>,maven-mvnd-<version>-<platform>}/<hash>
|
||||||
|
if ($env:MVNW_REPOURL) {
|
||||||
|
$MVNW_REPO_PATTERN = if ($USE_MVND -eq $False) { "/org/apache/maven/" } else { "/maven/mvnd/" }
|
||||||
|
$distributionUrl = "$env:MVNW_REPOURL$MVNW_REPO_PATTERN$($distributionUrl -replace "^.*$MVNW_REPO_PATTERN",'')"
|
||||||
|
}
|
||||||
|
$distributionUrlName = $distributionUrl -replace '^.*/',''
|
||||||
|
$distributionUrlNameMain = $distributionUrlName -replace '\.[^.]*$','' -replace '-bin$',''
|
||||||
|
|
||||||
|
$MAVEN_M2_PATH = "$HOME/.m2"
|
||||||
|
if ($env:MAVEN_USER_HOME) {
|
||||||
|
$MAVEN_M2_PATH = "$env:MAVEN_USER_HOME"
|
||||||
|
}
|
||||||
|
|
||||||
|
if (-not (Test-Path -Path $MAVEN_M2_PATH)) {
|
||||||
|
New-Item -Path $MAVEN_M2_PATH -ItemType Directory | Out-Null
|
||||||
|
}
|
||||||
|
|
||||||
|
$MAVEN_WRAPPER_DISTS = $null
|
||||||
|
if ((Get-Item $MAVEN_M2_PATH).Target[0] -eq $null) {
|
||||||
|
$MAVEN_WRAPPER_DISTS = "$MAVEN_M2_PATH/wrapper/dists"
|
||||||
|
} else {
|
||||||
|
$MAVEN_WRAPPER_DISTS = (Get-Item $MAVEN_M2_PATH).Target[0] + "/wrapper/dists"
|
||||||
|
}
|
||||||
|
|
||||||
|
$MAVEN_HOME_PARENT = "$MAVEN_WRAPPER_DISTS/$distributionUrlNameMain"
|
||||||
|
$MAVEN_HOME_NAME = ([System.Security.Cryptography.SHA256]::Create().ComputeHash([byte[]][char[]]$distributionUrl) | ForEach-Object {$_.ToString("x2")}) -join ''
|
||||||
|
$MAVEN_HOME = "$MAVEN_HOME_PARENT/$MAVEN_HOME_NAME"
|
||||||
|
|
||||||
|
if (Test-Path -Path "$MAVEN_HOME" -PathType Container) {
|
||||||
|
Write-Verbose "found existing MAVEN_HOME at $MAVEN_HOME"
|
||||||
|
Write-Output "MVN_CMD=$MAVEN_HOME/bin/$MVN_CMD"
|
||||||
|
exit $?
|
||||||
|
}
|
||||||
|
|
||||||
|
if (! $distributionUrlNameMain -or ($distributionUrlName -eq $distributionUrlNameMain)) {
|
||||||
|
Write-Error "distributionUrl is not valid, must end with *-bin.zip, but found $distributionUrl"
|
||||||
|
}
|
||||||
|
|
||||||
|
# prepare tmp dir
|
||||||
|
$TMP_DOWNLOAD_DIR_HOLDER = New-TemporaryFile
|
||||||
|
$TMP_DOWNLOAD_DIR = New-Item -Itemtype Directory -Path "$TMP_DOWNLOAD_DIR_HOLDER.dir"
|
||||||
|
$TMP_DOWNLOAD_DIR_HOLDER.Delete() | Out-Null
|
||||||
|
trap {
|
||||||
|
if ($TMP_DOWNLOAD_DIR.Exists) {
|
||||||
|
try { Remove-Item $TMP_DOWNLOAD_DIR -Recurse -Force | Out-Null }
|
||||||
|
catch { Write-Warning "Cannot remove $TMP_DOWNLOAD_DIR" }
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
New-Item -Itemtype Directory -Path "$MAVEN_HOME_PARENT" -Force | Out-Null
|
||||||
|
|
||||||
|
# Download and Install Apache Maven
|
||||||
|
Write-Verbose "Couldn't find MAVEN_HOME, downloading and installing it ..."
|
||||||
|
Write-Verbose "Downloading from: $distributionUrl"
|
||||||
|
Write-Verbose "Downloading to: $TMP_DOWNLOAD_DIR/$distributionUrlName"
|
||||||
|
|
||||||
|
$webclient = New-Object System.Net.WebClient
|
||||||
|
if ($env:MVNW_USERNAME -and $env:MVNW_PASSWORD) {
|
||||||
|
$webclient.Credentials = New-Object System.Net.NetworkCredential($env:MVNW_USERNAME, $env:MVNW_PASSWORD)
|
||||||
|
}
|
||||||
|
[Net.ServicePointManager]::SecurityProtocol = [Net.SecurityProtocolType]::Tls12
|
||||||
|
$webclient.DownloadFile($distributionUrl, "$TMP_DOWNLOAD_DIR/$distributionUrlName") | Out-Null
|
||||||
|
|
||||||
|
# If specified, validate the SHA-256 sum of the Maven distribution zip file
|
||||||
|
$distributionSha256Sum = (Get-Content -Raw "$scriptDir/.mvn/wrapper/maven-wrapper.properties" | ConvertFrom-StringData).distributionSha256Sum
|
||||||
|
if ($distributionSha256Sum) {
|
||||||
|
if ($USE_MVND) {
|
||||||
|
Write-Error "Checksum validation is not supported for maven-mvnd. `nPlease disable validation by removing 'distributionSha256Sum' from your maven-wrapper.properties."
|
||||||
|
}
|
||||||
|
Import-Module $PSHOME\Modules\Microsoft.PowerShell.Utility -Function Get-FileHash
|
||||||
|
if ((Get-FileHash "$TMP_DOWNLOAD_DIR/$distributionUrlName" -Algorithm SHA256).Hash.ToLower() -ne $distributionSha256Sum) {
|
||||||
|
Write-Error "Error: Failed to validate Maven distribution SHA-256, your Maven distribution might be compromised. If you updated your Maven version, you need to update the specified distributionSha256Sum property."
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
# unzip and move
|
||||||
|
Expand-Archive "$TMP_DOWNLOAD_DIR/$distributionUrlName" -DestinationPath "$TMP_DOWNLOAD_DIR" | Out-Null
|
||||||
|
|
||||||
|
# Find the actual extracted directory name (handles snapshots where filename != directory name)
|
||||||
|
$actualDistributionDir = ""
|
||||||
|
|
||||||
|
# First try the expected directory name (for regular distributions)
|
||||||
|
$expectedPath = Join-Path "$TMP_DOWNLOAD_DIR" "$distributionUrlNameMain"
|
||||||
|
$expectedMvnPath = Join-Path "$expectedPath" "bin/$MVN_CMD"
|
||||||
|
if ((Test-Path -Path $expectedPath -PathType Container) -and (Test-Path -Path $expectedMvnPath -PathType Leaf)) {
|
||||||
|
$actualDistributionDir = $distributionUrlNameMain
|
||||||
|
}
|
||||||
|
|
||||||
|
# If not found, search for any directory with the Maven executable (for snapshots)
|
||||||
|
if (!$actualDistributionDir) {
|
||||||
|
Get-ChildItem -Path "$TMP_DOWNLOAD_DIR" -Directory | ForEach-Object {
|
||||||
|
$testPath = Join-Path $_.FullName "bin/$MVN_CMD"
|
||||||
|
if (Test-Path -Path $testPath -PathType Leaf) {
|
||||||
|
$actualDistributionDir = $_.Name
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!$actualDistributionDir) {
|
||||||
|
Write-Error "Could not find Maven distribution directory in extracted archive"
|
||||||
|
}
|
||||||
|
|
||||||
|
Write-Verbose "Found extracted Maven distribution directory: $actualDistributionDir"
|
||||||
|
Rename-Item -Path "$TMP_DOWNLOAD_DIR/$actualDistributionDir" -NewName $MAVEN_HOME_NAME | Out-Null
|
||||||
|
try {
|
||||||
|
Move-Item -Path "$TMP_DOWNLOAD_DIR/$MAVEN_HOME_NAME" -Destination $MAVEN_HOME_PARENT | Out-Null
|
||||||
|
} catch {
|
||||||
|
if (! (Test-Path -Path "$MAVEN_HOME" -PathType Container)) {
|
||||||
|
Write-Error "fail to move MAVEN_HOME"
|
||||||
|
}
|
||||||
|
} finally {
|
||||||
|
try { Remove-Item $TMP_DOWNLOAD_DIR -Recurse -Force | Out-Null }
|
||||||
|
catch { Write-Warning "Cannot remove $TMP_DOWNLOAD_DIR" }
|
||||||
|
}
|
||||||
|
|
||||||
|
Write-Output "MVN_CMD=$MAVEN_HOME/bin/$MVN_CMD"
|
||||||
@@ -0,0 +1,34 @@
|
|||||||
|
#!/bin/sh
|
||||||
|
|
||||||
|
basedir=$(dirname "$0")
|
||||||
|
|
||||||
|
if [ -f "$basedir/mvnw" ]; then
|
||||||
|
bindir="$basedir/target/node"
|
||||||
|
repodir="$basedir/target/node/node_modules"
|
||||||
|
installCommand="$basedir/mvnw --batch-mode -ntp -Pwebapp frontend:install-node-and-npm@install-node-and-npm"
|
||||||
|
else
|
||||||
|
echo "Using npm installed globally"
|
||||||
|
exec npm "$@"
|
||||||
|
fi
|
||||||
|
|
||||||
|
NPM_EXE="$repodir/npm/bin/npm-cli.js"
|
||||||
|
NODE_EXE="$bindir/node"
|
||||||
|
|
||||||
|
if [ ! -x "$NPM_EXE" ] || [ ! -x "$NODE_EXE" ]; then
|
||||||
|
$installCommand || true
|
||||||
|
fi
|
||||||
|
|
||||||
|
if [ -x "$NODE_EXE" ]; then
|
||||||
|
echo "Using node installed locally $($NODE_EXE --version)"
|
||||||
|
PATH="$bindir:$PATH"
|
||||||
|
else
|
||||||
|
NODE_EXE='node'
|
||||||
|
fi
|
||||||
|
|
||||||
|
if [ ! -x "$NPM_EXE" ]; then
|
||||||
|
echo "Local npm not found, using npm installed globally"
|
||||||
|
npm "$@"
|
||||||
|
else
|
||||||
|
echo "Using npm installed locally $($NODE_EXE $NPM_EXE --version)"
|
||||||
|
$NODE_EXE $NPM_EXE "$@"
|
||||||
|
fi
|
||||||
@@ -0,0 +1,24 @@
|
|||||||
|
@echo off
|
||||||
|
|
||||||
|
setlocal
|
||||||
|
|
||||||
|
set NPMW_DIR=%~dp0
|
||||||
|
|
||||||
|
set NODE_EXE=^"^"
|
||||||
|
set NODE_PATH=%NPMW_DIR%target\node\
|
||||||
|
set NPM_EXE=^"%NPMW_DIR%target\node\npm.cmd^"
|
||||||
|
set INSTALL_NPM_COMMAND=^"%NPMW_DIR%mvnw.cmd^" -Pwebapp frontend:install-node-and-npm@install-node-and-npm
|
||||||
|
|
||||||
|
if not exist %NPM_EXE% (
|
||||||
|
call %INSTALL_NPM_COMMAND%
|
||||||
|
)
|
||||||
|
|
||||||
|
if exist %NODE_EXE% (
|
||||||
|
Rem execute local npm with local node, whilst adding local node location to the PATH for this CMD session
|
||||||
|
endlocal & echo "%PATH%"|find /i "%NODE_PATH%;">nul || set "PATH=%NODE_PATH%;%PATH%" & call %NODE_EXE% %NPM_EXE% %*
|
||||||
|
) else if exist %NPM_EXE% (
|
||||||
|
Rem execute local npm, whilst adding local npm location to the PATH for this CMD session
|
||||||
|
endlocal & echo "%PATH%"|find /i "%NODE_PATH%;">nul || set "PATH=%NODE_PATH%;%PATH%" & call %NPM_EXE% %*
|
||||||
|
) else (
|
||||||
|
call npm %*
|
||||||
|
)
|
||||||
Generated
+12613
File diff suppressed because it is too large.
Load diff
+141
@@ -0,0 +1,141 @@
|
|||||||
|
{
|
||||||
|
"name": "question-bank",
|
||||||
|
"version": "0.0.0",
|
||||||
|
"private": true,
|
||||||
|
"description": "Description for Question Bank",
|
||||||
|
"license": "UNLICENSED",
|
||||||
|
"type": "module",
|
||||||
|
"scripts": {
|
||||||
|
"app:start": "./mvnw -ntp --batch-mode",
|
||||||
|
"app:up": "docker compose -f src/main/docker/app.yml up --wait",
|
||||||
|
"backend:build-cache": "./mvnw dependency:go-offline -ntp",
|
||||||
|
"backend:debug": "./mvnw -Dspring-boot.run.jvmArguments=\"-agentlib:jdwp=transport=dt_socket,server=y,suspend=n,address=*:8000\"",
|
||||||
|
"backend:doc:test": "./mvnw -ntp javadoc:javadoc --batch-mode",
|
||||||
|
"backend:info": "./mvnw --version",
|
||||||
|
"backend:nohttp:test": "./mvnw -ntp checkstyle:check --batch-mode",
|
||||||
|
"backend:start": "./mvnw -Dskip.installnodenpm -Dskip.npm -ntp --batch-mode",
|
||||||
|
"backend:unit:test": "./mvnw -ntp -Dskip.installnodenpm -Dskip.npm verify --batch-mode -Dlogging.level.ROOT=OFF -Dlogging.level.tech.jhipster=OFF -Dlogging.level.com.example.questionbank=OFF -Dlogging.level.org.springframework=OFF -Dlogging.level.org.springframework.web=OFF -Dlogging.level.org.springframework.security=OFF",
|
||||||
|
"build": "npm run webapp:prod --",
|
||||||
|
"build-watch": "concurrently 'npm run webapp:build:dev -- --watch' npm:backend:start",
|
||||||
|
"ci:backend:test": "npm run backend:info && npm run backend:doc:test && npm run backend:nohttp:test && npm run backend:unit:test -- -P$npm_package_config_default_environment",
|
||||||
|
"ci:e2e:package": "npm run java:$npm_package_config_packaging:$npm_package_config_default_environment -- -Pe2e -Denforcer.skip=true",
|
||||||
|
"ci:e2e:prepare": "npm run ci:e2e:prepare:docker",
|
||||||
|
"ci:e2e:prepare:docker": "npm run services:up --if-present && docker ps -a",
|
||||||
|
"preci:e2e:server:start": "npm run services:db:await --if-present && npm run services:others:await --if-present",
|
||||||
|
"ci:e2e:server:start": "java -jar target/e2e.$npm_package_config_packaging --spring.profiles.active=e2e,secret-samples,$npm_package_config_default_environment -Dlogging.level.ROOT=OFF -Dlogging.level.tech.jhipster=OFF -Dlogging.level.com.example.questionbank=OFF -Dlogging.level.org.springframework=OFF -Dlogging.level.org.springframework.web=OFF -Dlogging.level.org.springframework.security=OFF --logging.level.org.springframework.web=ERROR",
|
||||||
|
"ci:e2e:teardown": "npm run ci:e2e:teardown:docker --if-present",
|
||||||
|
"ci:e2e:teardown:docker": "docker compose -f src/main/docker/services.yml down -v && docker ps -a",
|
||||||
|
"ci:frontend:build": "npm run webapp:build:$npm_package_config_default_environment",
|
||||||
|
"ci:frontend:test": "npm run ci:frontend:build && npm test",
|
||||||
|
"ci:server:await": "echo \"Waiting for server at port $npm_package_config_backend_port to start\" && wait-on -t 180000 http-get://127.0.0.1:$npm_package_config_backend_port/management/health && echo \"Server at port $npm_package_config_backend_port started\"",
|
||||||
|
"clean-www": "rimraf target/classes/static/",
|
||||||
|
"cleanup": "rimraf target/",
|
||||||
|
"docker:db:down": "docker compose -f src/main/docker/postgresql.yml down -v",
|
||||||
|
"docker:db:up": "docker compose -f src/main/docker/postgresql.yml up --wait",
|
||||||
|
"java:docker": "./mvnw -ntp verify -DskipTests -Pprod jib:dockerBuild",
|
||||||
|
"java:docker:arm64": "npm run java:docker -- -Djib-maven-plugin.architecture=arm64",
|
||||||
|
"java:docker:dev": "npm run java:docker -- -Pdev,webapp",
|
||||||
|
"java:docker:prod": "npm run java:docker -- -Pprod",
|
||||||
|
"java:jar": "./mvnw -ntp verify -DskipTests --batch-mode",
|
||||||
|
"java:jar:dev": "npm run java:jar -- -Pdev,webapp",
|
||||||
|
"java:jar:prod": "npm run java:jar -- -Pprod",
|
||||||
|
"java:war": "./mvnw -ntp verify -DskipTests --batch-mode -Pwar",
|
||||||
|
"java:war:dev": "npm run java:war -- -Pdev,webapp",
|
||||||
|
"java:war:prod": "npm run java:war -- -Pprod",
|
||||||
|
"lint": "eslint .",
|
||||||
|
"lint:fix": "eslint . --fix",
|
||||||
|
"prepare": "husky",
|
||||||
|
"prettier:check": "prettier --check \"{,.blueprint/**/,src/**/}*.{md,json,yml,js,cjs,mjs,ts,cts,mts,java,html,vue,css,scss}\"",
|
||||||
|
"prettier:format": "prettier --write \"{,.blueprint/**/,src/**/}*.{md,json,yml,js,cjs,mjs,ts,cts,mts,java,html,vue,css,scss}\"",
|
||||||
|
"serve": "npm run start --",
|
||||||
|
"services:up": "docker compose -f src/main/docker/services.yml up --wait",
|
||||||
|
"start": "npm run webapp:dev --",
|
||||||
|
"start-tls": "npm run webapp:dev -- --env.tls",
|
||||||
|
"pretest": "npm run lint",
|
||||||
|
"test": "npm run vitest-run --",
|
||||||
|
"test:watch": "npm run vitest",
|
||||||
|
"vite-build": "vite build",
|
||||||
|
"vite-serve": "vite",
|
||||||
|
"vitest": "vitest",
|
||||||
|
"vitest-run": "vitest --run --coverage",
|
||||||
|
"watch": "concurrently npm:start npm:backend:start",
|
||||||
|
"webapp:build": "npm run clean-www && npm run webapp:build:dev --",
|
||||||
|
"webapp:build:dev": "npm run vite-build",
|
||||||
|
"webapp:build:prod": "npm run vite-build",
|
||||||
|
"webapp:dev": "npm run vite-serve",
|
||||||
|
"webapp:prod": "npm run clean-www && npm run webapp:build:prod --",
|
||||||
|
"webapp:serve": "npm run vite-serve",
|
||||||
|
"webapp:test": "npm run test --"
|
||||||
|
},
|
||||||
|
"config": {
|
||||||
|
"backend_port": 8080,
|
||||||
|
"default_environment": "prod",
|
||||||
|
"packaging": "jar"
|
||||||
|
},
|
||||||
|
"browserslist": [
|
||||||
|
"> 1%",
|
||||||
|
"last 2 versions",
|
||||||
|
"not ie <= 8"
|
||||||
|
],
|
||||||
|
"dependencies": {
|
||||||
|
"@fortawesome/fontawesome-svg-core": "7.3.1",
|
||||||
|
"@fortawesome/free-solid-svg-icons": "7.3.1",
|
||||||
|
"@fortawesome/vue-fontawesome": "3.3.3",
|
||||||
|
"@vuelidate/core": "2.0.3",
|
||||||
|
"@vuelidate/validators": "2.0.4",
|
||||||
|
"@vueuse/core": "14.4.0",
|
||||||
|
"axios": "1.19.0",
|
||||||
|
"bootstrap": "5.3.7",
|
||||||
|
"bootstrap-vue-next": "1.0.1",
|
||||||
|
"dayjs": "1.11.23",
|
||||||
|
"deepmerge": "4.3.1",
|
||||||
|
"pinia": "4.0.3",
|
||||||
|
"vue": "3.5.41",
|
||||||
|
"vue-i18n": "11.4.9",
|
||||||
|
"vue-router": "5.2.0"
|
||||||
|
},
|
||||||
|
"devDependencies": {
|
||||||
|
"@eslint/js": "10.0.1",
|
||||||
|
"@pinia/testing": "2.0.1",
|
||||||
|
"@tsconfig/node18": "18.2.7",
|
||||||
|
"@types/node": "22.20.1",
|
||||||
|
"@vitejs/plugin-vue": "6.0.8",
|
||||||
|
"@vitest/coverage-v8": "4.1.11",
|
||||||
|
"@vue/test-utils": "2.4.11",
|
||||||
|
"@vue/tsconfig": "0.9.1",
|
||||||
|
"autoprefixer": "10.5.4",
|
||||||
|
"axios-mock-adapter": "2.1.0",
|
||||||
|
"concurrently": "10.0.5",
|
||||||
|
"eslint": "10.9.1",
|
||||||
|
"eslint-config-prettier": "10.1.8",
|
||||||
|
"eslint-plugin-prettier": "5.5.6",
|
||||||
|
"eslint-plugin-vue": "10.10.0",
|
||||||
|
"generator-jhipster": "9.3.0",
|
||||||
|
"globals": "17.11.0",
|
||||||
|
"happy-dom": "20.11.6",
|
||||||
|
"husky": "9.1.7",
|
||||||
|
"jiti": "2.7.0",
|
||||||
|
"lint-staged": "17.3.0",
|
||||||
|
"postcss-import": "17.0.0",
|
||||||
|
"postcss-url": "10.1.4",
|
||||||
|
"prettier": "3.9.6",
|
||||||
|
"prettier-plugin-java": "2.10.3",
|
||||||
|
"prettier-plugin-packagejson": "3.0.2",
|
||||||
|
"rimraf": "6.1.3",
|
||||||
|
"sass": "1.70.0",
|
||||||
|
"swagger-ui-dist": "5.32.14",
|
||||||
|
"typescript": "6.0.3",
|
||||||
|
"typescript-eslint": "8.68.0",
|
||||||
|
"vite": "8.2.2",
|
||||||
|
"vite-plugin-static-copy": "4.1.1",
|
||||||
|
"vitest": "4.1.11",
|
||||||
|
"vitest-sonar-reporter": "3.0.0",
|
||||||
|
"wait-on": "9.1.0"
|
||||||
|
},
|
||||||
|
"engines": {
|
||||||
|
"node": ">=24.20.0"
|
||||||
|
},
|
||||||
|
"cacheDirectories": [
|
||||||
|
"node_modules"
|
||||||
|
]
|
||||||
|
}
|
||||||
@@ -0,0 +1,62 @@
|
|||||||
|
enum OptionKey {
|
||||||
|
A,
|
||||||
|
B,
|
||||||
|
C,
|
||||||
|
D
|
||||||
|
}
|
||||||
|
|
||||||
|
enum AttemptStatus {
|
||||||
|
IN_PROGRESS,
|
||||||
|
SUBMITTED
|
||||||
|
}
|
||||||
|
|
||||||
|
entity Subject {
|
||||||
|
name String required minlength(2) maxlength(100),
|
||||||
|
description String maxlength(1000),
|
||||||
|
active Boolean required
|
||||||
|
}
|
||||||
|
|
||||||
|
entity Question {
|
||||||
|
prompt String required minlength(5) maxlength(2000),
|
||||||
|
explanation String maxlength(2000),
|
||||||
|
active Boolean required
|
||||||
|
}
|
||||||
|
|
||||||
|
entity Choice {
|
||||||
|
optionKey OptionKey required,
|
||||||
|
content String required maxlength(500),
|
||||||
|
displayOrder Integer required min(1) max(4),
|
||||||
|
correct Boolean required
|
||||||
|
}
|
||||||
|
|
||||||
|
entity ExamAttempt {
|
||||||
|
startedAt Instant required,
|
||||||
|
submittedAt Instant,
|
||||||
|
status AttemptStatus required,
|
||||||
|
score Integer min(0)
|
||||||
|
}
|
||||||
|
|
||||||
|
entity ExamAnswer {
|
||||||
|
questionTextSnapshot String required maxlength(2000),
|
||||||
|
explanationSnapshot String maxlength(2000),
|
||||||
|
selectedOption OptionKey,
|
||||||
|
selectedContentSnapshot String maxlength(500),
|
||||||
|
correct Boolean,
|
||||||
|
awardedPoints Integer min(0)
|
||||||
|
}
|
||||||
|
|
||||||
|
relationship OneToMany {
|
||||||
|
Question{choice} to Choice{question required}
|
||||||
|
ExamAttempt{answer} to ExamAnswer{attempt required}
|
||||||
|
}
|
||||||
|
|
||||||
|
relationship ManyToOne {
|
||||||
|
Question{subject(name) required} to Subject
|
||||||
|
ExamAttempt{subject(name) required} to Subject
|
||||||
|
ExamAttempt{user(login) required} to User with builtInEntity
|
||||||
|
ExamAnswer{question required} to Question
|
||||||
|
}
|
||||||
|
|
||||||
|
dto Subject, Question, Choice, ExamAttempt, ExamAnswer with mapstruct
|
||||||
|
service Subject, Question, Choice, ExamAttempt, ExamAnswer with serviceClass
|
||||||
|
paginate Subject, Question, ExamAttempt with pagination
|
||||||
@@ -0,0 +1,55 @@
|
|||||||
|
sonar.projectKey = questionBank
|
||||||
|
sonar.projectName = questionBank generated by jhipster
|
||||||
|
|
||||||
|
# Typescript tests files must be inside sources and tests, otherwise `INFO: Test execution data ignored for 80 unknown
|
||||||
|
# files, including:` is shown.
|
||||||
|
sonar.sources = src
|
||||||
|
sonar.tests = src
|
||||||
|
sonar.host.url = http://localhost:9001
|
||||||
|
sonar.sourceEncoding = UTF-8
|
||||||
|
|
||||||
|
sonar.test.inclusions = src/test/**/*.*, src/main/webapp/app/**/*.spec.ts
|
||||||
|
sonar.junit.reportPaths = target/surefire-reports,target/failsafe-reports
|
||||||
|
sonar.java.codeCoveragePlugin = jacoco
|
||||||
|
sonar.coverage.jacoco.xmlReportPaths = target/site/**/jacoco*.xml
|
||||||
|
sonar.exclusions = src/main/webapp/content/**/*.*, src/main/webapp/i18n/*.ts, target/classes/static/**/*.*
|
||||||
|
sonar.javascript.lcov.reportPaths = target/test-results/lcov.info
|
||||||
|
sonar.testExecutionReportPaths = target/test-results/jest/TESTS-results-sonar.xml
|
||||||
|
|
||||||
|
sonar.issue.ignore.multicriteria = S7027-domain, S3437, S7027-dto, UndocumentedApi, S125, S4502, S4684, S5145, S6437, S6437-2, S6418
|
||||||
|
# Rule https://rules.sonarsource.com/java/RSPEC-7027 is ignored for entities
|
||||||
|
sonar.issue.ignore.multicriteria.S7027-domain.resourceKey = src/main/java/com/example/questionbank/domain/**/*
|
||||||
|
sonar.issue.ignore.multicriteria.S7027-domain.ruleKey = javaarchitecture:S7027
|
||||||
|
# Rule https://rules.sonarsource.com/java/RSPEC-3437 is ignored
|
||||||
|
sonar.issue.ignore.multicriteria.S3437.resourceKey = src/main/java/com/example/questionbank/**/*
|
||||||
|
sonar.issue.ignore.multicriteria.S3437.ruleKey = squid:S3437
|
||||||
|
# Rule https://rules.sonarsource.com/java/RSPEC-7027 is ignored for dtos
|
||||||
|
sonar.issue.ignore.multicriteria.S7027-dto.resourceKey = src/main/java/com/example/questionbank/service/dto/**/*
|
||||||
|
sonar.issue.ignore.multicriteria.S7027-dto.ruleKey = javaarchitecture:S7027
|
||||||
|
# Rule https://rules.sonarsource.com/java/RSPEC-1176 is ignored, as we want to follow "clean code" guidelines and
|
||||||
|
# classes, methods and arguments names should be self-explanatory
|
||||||
|
sonar.issue.ignore.multicriteria.UndocumentedApi.resourceKey = src/main/java/com/example/questionbank/**/*
|
||||||
|
sonar.issue.ignore.multicriteria.UndocumentedApi.ruleKey = squid:UndocumentedApi
|
||||||
|
# Rule https://rules.sonarsource.com/xml/RSPEC-125 is ignored, we provide commented examples
|
||||||
|
sonar.issue.ignore.multicriteria.S125.resourceKey = src/main/resources/logback-spring.xml
|
||||||
|
sonar.issue.ignore.multicriteria.S125.ruleKey = xml:S125
|
||||||
|
# Rule https://rules.sonarsource.com/java/RSPEC-4502 is ignored, as for JWT tokens we are not subject to CSRF attack
|
||||||
|
sonar.issue.ignore.multicriteria.S4502.resourceKey = src/main/java/com/example/questionbank/config/SecurityConfiguration.java
|
||||||
|
sonar.issue.ignore.multicriteria.S4502.ruleKey = java:S4502
|
||||||
|
# Rule https://rules.sonarsource.com/java/RSPEC-4684
|
||||||
|
sonar.issue.ignore.multicriteria.S4684.resourceKey = src/main/java/com/example/questionbank/web/rest/**/*
|
||||||
|
sonar.issue.ignore.multicriteria.S4684.ruleKey = java:S4684
|
||||||
|
# Rule https://rules.sonarsource.com/java/RSPEC-5145 is ignored, as we use log filter to format log messages
|
||||||
|
sonar.issue.ignore.multicriteria.S5145.resourceKey = src/main/java/com/example/questionbank/**/*
|
||||||
|
sonar.issue.ignore.multicriteria.S5145.ruleKey = javasecurity:S5145
|
||||||
|
# Rule https://rules.sonarsource.com/java/RSPEC-6437 is ignored, hardcoded passwords are provided for development
|
||||||
|
# purposes
|
||||||
|
sonar.issue.ignore.multicriteria.S6437.resourceKey = src/main/resources/config/application-secret-samples.yml
|
||||||
|
sonar.issue.ignore.multicriteria.S6437.ruleKey = java:S6437
|
||||||
|
# Rule https://rules.sonarsource.com/java/RSPEC-6437 is ignored, hardcoded passwords are provided for development
|
||||||
|
# purposes
|
||||||
|
sonar.issue.ignore.multicriteria.S6437-2.resourceKey = src/main/resources/config/application-tls.yml
|
||||||
|
sonar.issue.ignore.multicriteria.S6437-2.ruleKey = java:S6437
|
||||||
|
# Rule yaml:S6418 is ignored, hardcoded passwords are provided for development and testing purposes
|
||||||
|
sonar.issue.ignore.multicriteria.S6418.resourceKey = src/main/resources/config/application-secret-samples.yml
|
||||||
|
sonar.issue.ignore.multicriteria.S6418.ruleKey = yaml:S6418
|
||||||
@@ -0,0 +1,31 @@
|
|||||||
|
# This configuration is intended for development purpose, it's **your** responsibility to harden it for production
|
||||||
|
name: questionbank
|
||||||
|
services:
|
||||||
|
app:
|
||||||
|
image: questionbank
|
||||||
|
environment:
|
||||||
|
- _JAVA_OPTIONS=-Xmx512m -Xms256m
|
||||||
|
- SPRING_PROFILES_ACTIVE=prod,api-docs,secret-samples
|
||||||
|
- MANAGEMENT_PROMETHEUS_METRICS_EXPORT_ENABLED=true
|
||||||
|
- SPRING_DATASOURCE_URL=jdbc:postgresql://postgresql:5432/questionBank
|
||||||
|
- SPRING_DATASOURCE_USERNAME=questionBank
|
||||||
|
- SPRING_DATASOURCE_PASSWORD=
|
||||||
|
- SPRING_LIQUIBASE_URL=jdbc:postgresql://postgresql:5432/questionBank
|
||||||
|
ports:
|
||||||
|
- 127.0.0.1:8080:8080
|
||||||
|
healthcheck:
|
||||||
|
test:
|
||||||
|
- CMD-SHELL
|
||||||
|
- "bash -c 'exec 3<>/dev/tcp/127.0.0.1/8080; printf \"GET
|
||||||
|
/management/health HTTP/1.0\\r\\nHost: localhost\\r\\n\\r\\n\" >&3;
|
||||||
|
grep -q \"\\\"status\\\":\\\"UP\\\"\" <&3'"
|
||||||
|
interval: 5s
|
||||||
|
timeout: 5s
|
||||||
|
retries: 40
|
||||||
|
depends_on:
|
||||||
|
postgresql:
|
||||||
|
condition: service_healthy
|
||||||
|
postgresql:
|
||||||
|
extends:
|
||||||
|
file: ./postgresql.yml
|
||||||
|
service: postgresql
|
||||||
File diff suppressed because it is too large.
Load diff
@@ -0,0 +1,11 @@
|
|||||||
|
apiVersion: 1
|
||||||
|
|
||||||
|
providers:
|
||||||
|
- name: 'Prometheus'
|
||||||
|
orgId: 1
|
||||||
|
folder: ''
|
||||||
|
type: file
|
||||||
|
disableDeletion: false
|
||||||
|
editable: true
|
||||||
|
options:
|
||||||
|
path: /etc/grafana/provisioning/dashboards
|
||||||
@@ -0,0 +1,50 @@
|
|||||||
|
apiVersion: 1
|
||||||
|
|
||||||
|
# list of datasources that should be deleted from the database
|
||||||
|
deleteDatasources:
|
||||||
|
- name: Prometheus
|
||||||
|
orgId: 1
|
||||||
|
|
||||||
|
# list of datasources to insert/update depending
|
||||||
|
# whats available in the database
|
||||||
|
datasources:
|
||||||
|
# <string, required> name of the datasource. Required
|
||||||
|
- name: Prometheus
|
||||||
|
# <string, required> datasource type. Required
|
||||||
|
type: prometheus
|
||||||
|
# <string, required> access mode. direct or proxy. Required
|
||||||
|
access: proxy
|
||||||
|
# <int> org id. will default to orgId 1 if not specified
|
||||||
|
orgId: 1
|
||||||
|
# <string> url
|
||||||
|
# On macOS, replace localhost by host.docker.internal
|
||||||
|
url: http://localhost:9090
|
||||||
|
# <string> database password, if used
|
||||||
|
password:
|
||||||
|
# <string> database user, if used
|
||||||
|
user:
|
||||||
|
# <string> database name, if used
|
||||||
|
database:
|
||||||
|
# <bool> enable/disable basic auth
|
||||||
|
basicAuth: false
|
||||||
|
# <string> basic auth username
|
||||||
|
basicAuthUser: admin
|
||||||
|
# <string> basic auth password
|
||||||
|
basicAuthPassword: admin
|
||||||
|
# <bool> enable/disable with credentials headers
|
||||||
|
withCredentials:
|
||||||
|
# <bool> mark as default datasource. Max one per org
|
||||||
|
isDefault: true
|
||||||
|
# <map> fields that will be converted to json and stored in json_data
|
||||||
|
jsonData:
|
||||||
|
graphiteVersion: '1.1'
|
||||||
|
tlsAuth: false
|
||||||
|
tlsAuthWithCACert: false
|
||||||
|
# <string> json object of data that will be encrypted.
|
||||||
|
secureJsonData:
|
||||||
|
tlsCACert: '...'
|
||||||
|
tlsClientCert: '...'
|
||||||
|
tlsClientKey: '...'
|
||||||
|
version: 1
|
||||||
|
# <bool> allow users to edit datasources from the UI.
|
||||||
|
editable: true
|
||||||
@@ -0,0 +1,51 @@
|
|||||||
|
## How to use JHCC docker compose
|
||||||
|
# To allow JHCC to reach JHipster application from a docker container note that we set the host as host.docker.internal
|
||||||
|
# To reach the application from a browser, you need to add '127.0.0.1 host.docker.internal' to your hosts file.
|
||||||
|
### Discovery mode
|
||||||
|
# JHCC supports 3 kinds of discovery mode: Consul, Eureka and static
|
||||||
|
# In order to use one, please set SPRING_PROFILES_ACTIVE to one (and only one) of these values: consul,eureka,static
|
||||||
|
### Discovery properties
|
||||||
|
# According to the discovery mode choose as Spring profile, you have to set the right properties
|
||||||
|
# please note that current properties are set to run JHCC with default values, personalize them if needed
|
||||||
|
# and remove those from other modes. You can only have one mode active.
|
||||||
|
#### Eureka
|
||||||
|
# - EUREKA_CLIENT_SERVICE_URL_DEFAULTZONE=http://admin:admin@host.docker.internal:8761/eureka/
|
||||||
|
#### Consul
|
||||||
|
# - SPRING_CLOUD_CONSUL_HOST=host.docker.internal
|
||||||
|
# - SPRING_CLOUD_CONSUL_PORT=8500
|
||||||
|
#### Static
|
||||||
|
# Add instances to "MyApp"
|
||||||
|
# - SPRING_CLOUD_DISCOVERY_CLIENT_SIMPLE_INSTANCES_MYAPP_0_URI=http://host.docker.internal:8081
|
||||||
|
# - SPRING_CLOUD_DISCOVERY_CLIENT_SIMPLE_INSTANCES_MYAPP_1_URI=http://host.docker.internal:8082
|
||||||
|
# Or add a new application named MyNewApp
|
||||||
|
# - SPRING_CLOUD_DISCOVERY_CLIENT_SIMPLE_INSTANCES_MYNEWAPP_0_URI=http://host.docker.internal:8080
|
||||||
|
# This configuration is intended for development purpose, it's **your** responsibility to harden it for production
|
||||||
|
|
||||||
|
#### IMPORTANT
|
||||||
|
# If you choose Consul or Eureka mode:
|
||||||
|
# Do not forget to remove the prefix "127.0.0.1" in front of their port in order to expose them.
|
||||||
|
# This is required because JHCC needs to communicate with Consul or Eureka.
|
||||||
|
# - In Consul mode, the ports are in the consul.yml file.
|
||||||
|
# - In Eureka mode, the ports are in the jhipster-registry.yml file.
|
||||||
|
|
||||||
|
name: questionbank
|
||||||
|
services:
|
||||||
|
jhipster-control-center:
|
||||||
|
image: 'jhipster/jhipster-control-center:v0.5.0'
|
||||||
|
command:
|
||||||
|
- /bin/sh
|
||||||
|
- -c
|
||||||
|
# Patch /etc/hosts to support resolving host.docker.internal to the internal IP address used by the host in all OSes
|
||||||
|
- echo "`ip route | grep default | cut -d ' ' -f3` host.docker.internal" | tee -a /etc/hosts > /dev/null && java -jar /jhipster-control-center.jar
|
||||||
|
environment:
|
||||||
|
- _JAVA_OPTIONS=-Xmx512m -Xms256m
|
||||||
|
- SPRING_PROFILES_ACTIVE=prod,api-docs,static
|
||||||
|
- SPRING_SECURITY_USER_PASSWORD=admin
|
||||||
|
# The token should have the same value as the one declared in you Spring configuration under the jhipster.security.authentication.jwt.base64-secret configuration's entry
|
||||||
|
- JHIPSTER_SECURITY_AUTHENTICATION_JWT_BASE64_SECRET=MzY1ZDU0MDM3M2Y5YWRiMDYxYjBmYzZlOTMxMWFiNmYyMDEyZTUzZjYxOWFjNjhlNjU2NzcxYjkwY2Y0MGIwOGUyNTc5NWQyOGY3OGJkMjExZjhkYzU3ZGU4ZDc0ZmZiMTYwMzY0NGM4ZWZmODRhZTZjNzBlNjk5MWJkYjM5YjI=
|
||||||
|
- SPRING_CLOUD_DISCOVERY_CLIENT_SIMPLE_INSTANCES_QUESTIONBANK_0_URI=http://host.docker.internal:8080
|
||||||
|
- LOGGING_FILE_NAME=/tmp/jhipster-control-center.log
|
||||||
|
# If you want to expose these ports outside your dev PC,
|
||||||
|
# remove the "127.0.0.1:" prefix
|
||||||
|
ports:
|
||||||
|
- 127.0.0.1:7419:7419
|
||||||
@@ -0,0 +1,40 @@
|
|||||||
|
#!/bin/bash
|
||||||
|
|
||||||
|
echo "The application will start in ${JHIPSTER_SLEEP}s..." && sleep ${JHIPSTER_SLEEP}
|
||||||
|
|
||||||
|
# usage: file_env VAR [DEFAULT]
|
||||||
|
# ie: file_env 'XYZ_DB_PASSWORD' 'example'
|
||||||
|
# (will allow for "$XYZ_DB_PASSWORD_FILE" to fill in the value of
|
||||||
|
# "$XYZ_DB_PASSWORD" from a file, especially for Docker's secrets feature)
|
||||||
|
file_env() {
|
||||||
|
local var="$1"
|
||||||
|
local file_var="${var}_FILE"
|
||||||
|
local def="${2:-}"
|
||||||
|
if [[ ${!var:-} && ${!file_var:-} ]]; then
|
||||||
|
echo >&2 "error: both $var and $file_var are set (but are exclusive)"
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
|
local val="$def"
|
||||||
|
if [[ ${!var:-} ]]; then
|
||||||
|
val="${!var}"
|
||||||
|
elif [[ ${!file_var:-} ]]; then
|
||||||
|
val="$(< "${!file_var}")"
|
||||||
|
fi
|
||||||
|
|
||||||
|
if [[ -n $val ]]; then
|
||||||
|
export "$var"="$val"
|
||||||
|
fi
|
||||||
|
|
||||||
|
unset "$file_var"
|
||||||
|
return 0
|
||||||
|
}
|
||||||
|
|
||||||
|
file_env 'SPRING_DATASOURCE_URL'
|
||||||
|
file_env 'SPRING_DATASOURCE_USERNAME'
|
||||||
|
file_env 'SPRING_DATASOURCE_PASSWORD'
|
||||||
|
file_env 'SPRING_LIQUIBASE_URL'
|
||||||
|
file_env 'SPRING_LIQUIBASE_USER'
|
||||||
|
file_env 'SPRING_LIQUIBASE_PASSWORD'
|
||||||
|
file_env 'JHIPSTER_REGISTRY_PASSWORD'
|
||||||
|
|
||||||
|
exec java ${JAVA_OPTS} -noverify -XX:+AlwaysPreTouch -cp /app/resources/:/app/classes/:/app/libs/* "com.example.questionbank.QuestionBankApp" "$@"
|
||||||
@@ -0,0 +1,31 @@
|
|||||||
|
# This configuration is intended for development purpose, it's **your** responsibility to harden it for production
|
||||||
|
name: questionbank
|
||||||
|
services:
|
||||||
|
prometheus:
|
||||||
|
image: prom/prometheus:v3.14.0
|
||||||
|
volumes:
|
||||||
|
- ./prometheus/:/etc/prometheus/
|
||||||
|
command:
|
||||||
|
- '--config.file=/etc/prometheus/prometheus.yml'
|
||||||
|
# If you want to expose these ports outside your dev PC,
|
||||||
|
# remove the "127.0.0.1:" prefix
|
||||||
|
ports:
|
||||||
|
- 127.0.0.1:9090:9090
|
||||||
|
# On macOS, remove next line and replace localhost by host.docker.internal in prometheus/prometheus.yml and
|
||||||
|
# grafana/provisioning/datasources/datasource.yml
|
||||||
|
network_mode: 'host' # to test locally running service
|
||||||
|
grafana:
|
||||||
|
image: grafana/grafana:13.2.0
|
||||||
|
volumes:
|
||||||
|
- ./grafana/provisioning/:/etc/grafana/provisioning/
|
||||||
|
environment:
|
||||||
|
- GF_SECURITY_ADMIN_PASSWORD=admin
|
||||||
|
- GF_USERS_ALLOW_SIGN_UP=false
|
||||||
|
- GF_INSTALL_PLUGINS=grafana-piechart-panel
|
||||||
|
# If you want to expose these ports outside your dev PC,
|
||||||
|
# remove the "127.0.0.1:" prefix
|
||||||
|
ports:
|
||||||
|
- 127.0.0.1:3000:3000
|
||||||
|
# On macOS, remove next line and replace localhost by host.docker.internal in prometheus/prometheus.yml and
|
||||||
|
# grafana/provisioning/datasources/datasource.yml
|
||||||
|
network_mode: 'host' # to test locally running service
|
||||||
@@ -0,0 +1,19 @@
|
|||||||
|
# This configuration is intended for development purpose, it's **your** responsibility to harden it for production
|
||||||
|
name: questionbank
|
||||||
|
services:
|
||||||
|
postgresql:
|
||||||
|
image: postgres:18.6
|
||||||
|
# volumes:
|
||||||
|
# - ~/volumes/jhipster/questionBank/postgresql/:/var/lib/postgresql/data/
|
||||||
|
environment:
|
||||||
|
- POSTGRES_USER=questionBank
|
||||||
|
- POSTGRES_HOST_AUTH_METHOD=trust
|
||||||
|
healthcheck:
|
||||||
|
test: ['CMD-SHELL', 'pg_isready -U $${POSTGRES_USER}']
|
||||||
|
interval: 5s
|
||||||
|
timeout: 5s
|
||||||
|
retries: 10
|
||||||
|
# If you want to expose these ports outside your dev PC,
|
||||||
|
# remove the "127.0.0.1:" prefix
|
||||||
|
ports:
|
||||||
|
- 127.0.0.1:5432:5432
|
||||||
@@ -0,0 +1,31 @@
|
|||||||
|
# Sample global config for monitoring JHipster applications
|
||||||
|
global:
|
||||||
|
scrape_interval: 15s # By default, scrape targets every 15 seconds.
|
||||||
|
evaluation_interval: 15s # By default, scrape targets every 15 seconds.
|
||||||
|
# scrape_timeout is set to the global default (10s).
|
||||||
|
|
||||||
|
# Attach these labels to any time series or alerts when communicating with
|
||||||
|
# external systems (federation, remote storage, Alertmanager).
|
||||||
|
external_labels:
|
||||||
|
monitor: 'jhipster'
|
||||||
|
|
||||||
|
# A scrape configuration containing exactly one endpoint to scrape:
|
||||||
|
# Here it's Prometheus itself.
|
||||||
|
scrape_configs:
|
||||||
|
# The job name is added as a label `job=<job_name>` to any timeseries scraped from this config.
|
||||||
|
- job_name: 'prometheus'
|
||||||
|
|
||||||
|
# Override the global default and scrape targets from this job every 5 seconds.
|
||||||
|
scrape_interval: 5s
|
||||||
|
|
||||||
|
# scheme defaults to 'http' enable https in case your application is server via https
|
||||||
|
#scheme: https
|
||||||
|
# basic auth is not needed by default. See https://www.jhipster.tech/monitoring/#configuring-metrics-forwarding for details
|
||||||
|
#basic_auth:
|
||||||
|
# username: admin
|
||||||
|
# password: admin
|
||||||
|
metrics_path: /management/prometheus
|
||||||
|
static_configs:
|
||||||
|
- targets:
|
||||||
|
# On macOS, replace localhost by host.docker.internal
|
||||||
|
- localhost:8080
|
||||||
@@ -0,0 +1,7 @@
|
|||||||
|
# This configuration is intended for development purpose, it's **your** responsibility to harden it for production
|
||||||
|
name: questionbank
|
||||||
|
services:
|
||||||
|
postgresql:
|
||||||
|
extends:
|
||||||
|
file: ./postgresql.yml
|
||||||
|
service: postgresql
|
||||||
@@ -0,0 +1,15 @@
|
|||||||
|
# This configuration is intended for development purpose, it's **your** responsibility to harden it for production
|
||||||
|
name: questionbank
|
||||||
|
services:
|
||||||
|
sonar:
|
||||||
|
container_name: sonarqube
|
||||||
|
image: sonarqube:26.8.0.126808-community
|
||||||
|
# Forced authentication redirect for UI is turned off for out of the box experience while trying out SonarQube
|
||||||
|
# For real use cases delete SONAR_FORCEAUTHENTICATION variable or set SONAR_FORCEAUTHENTICATION=true
|
||||||
|
environment:
|
||||||
|
- SONAR_FORCEAUTHENTICATION=false
|
||||||
|
# If you want to expose these ports outside your dev PC,
|
||||||
|
# remove the "127.0.0.1:" prefix
|
||||||
|
ports:
|
||||||
|
- 127.0.0.1:9001:9000
|
||||||
|
- 127.0.0.1:9000:9000
|
||||||
@@ -0,0 +1,19 @@
|
|||||||
|
package com.example.questionbank;
|
||||||
|
|
||||||
|
import org.springframework.boot.builder.SpringApplicationBuilder;
|
||||||
|
import org.springframework.boot.web.servlet.support.SpringBootServletInitializer;
|
||||||
|
import tech.jhipster.config.DefaultProfileUtil;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* This is a helper Java class that provides an alternative to creating a {@code web.xml}.
|
||||||
|
* This will be invoked only when the application is deployed to a Servlet container like Tomcat, JBoss etc.
|
||||||
|
*/
|
||||||
|
public class ApplicationWebXml extends SpringBootServletInitializer {
|
||||||
|
|
||||||
|
@Override
|
||||||
|
protected SpringApplicationBuilder configure(SpringApplicationBuilder application) {
|
||||||
|
// set a default to use when no profile is configured.
|
||||||
|
DefaultProfileUtil.addDefaultProfile(application.application());
|
||||||
|
return application.sources(QuestionBankApp.class);
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,12 @@
|
|||||||
|
package com.example.questionbank;
|
||||||
|
|
||||||
|
import jakarta.annotation.Generated;
|
||||||
|
import java.lang.annotation.ElementType;
|
||||||
|
import java.lang.annotation.Retention;
|
||||||
|
import java.lang.annotation.RetentionPolicy;
|
||||||
|
import java.lang.annotation.Target;
|
||||||
|
|
||||||
|
@Generated(value = "JHipster", comments = "Generated by JHipster 9.3.0")
|
||||||
|
@Retention(RetentionPolicy.SOURCE)
|
||||||
|
@Target({ ElementType.TYPE })
|
||||||
|
public @interface GeneratedByJHipster {}
|
||||||
@@ -0,0 +1,110 @@
|
|||||||
|
package com.example.questionbank;
|
||||||
|
|
||||||
|
import com.example.questionbank.config.ApplicationProperties;
|
||||||
|
import com.example.questionbank.config.CRLFLogConverter;
|
||||||
|
import jakarta.annotation.PostConstruct;
|
||||||
|
import java.net.InetAddress;
|
||||||
|
import java.net.UnknownHostException;
|
||||||
|
import java.util.Collection;
|
||||||
|
import java.util.List;
|
||||||
|
import java.util.Optional;
|
||||||
|
import org.apache.commons.lang3.StringUtils;
|
||||||
|
import org.slf4j.Logger;
|
||||||
|
import org.slf4j.LoggerFactory;
|
||||||
|
import org.springframework.boot.SpringApplication;
|
||||||
|
import org.springframework.boot.autoconfigure.SpringBootApplication;
|
||||||
|
import org.springframework.boot.context.properties.EnableConfigurationProperties;
|
||||||
|
import org.springframework.boot.liquibase.autoconfigure.LiquibaseProperties;
|
||||||
|
import org.springframework.core.env.Environment;
|
||||||
|
import tech.jhipster.config.DefaultProfileUtil;
|
||||||
|
import tech.jhipster.config.JHipsterConstants;
|
||||||
|
|
||||||
|
@SpringBootApplication()
|
||||||
|
@EnableConfigurationProperties({ LiquibaseProperties.class, ApplicationProperties.class })
|
||||||
|
public class QuestionBankApp {
|
||||||
|
|
||||||
|
private static final Logger LOG = LoggerFactory.getLogger(QuestionBankApp.class);
|
||||||
|
|
||||||
|
private final Environment env;
|
||||||
|
|
||||||
|
public QuestionBankApp(Environment env) {
|
||||||
|
this.env = env;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Initializes questionBank.
|
||||||
|
* <p>
|
||||||
|
* Spring profiles can be configured with a program argument --spring.profiles.active=your-active-profile
|
||||||
|
* <p>
|
||||||
|
* You can find more information on how profiles work with JHipster on <a href="https://www.jhipster.tech/profiles/">https://www.jhipster.tech/profiles/</a>.
|
||||||
|
*/
|
||||||
|
@PostConstruct
|
||||||
|
public void initApplication() {
|
||||||
|
Collection<String> activeProfiles = List.of(env.getActiveProfiles());
|
||||||
|
if (
|
||||||
|
activeProfiles.contains(JHipsterConstants.SPRING_PROFILE_DEVELOPMENT) &&
|
||||||
|
activeProfiles.contains(JHipsterConstants.SPRING_PROFILE_PRODUCTION)
|
||||||
|
) {
|
||||||
|
LOG.error(
|
||||||
|
"You have misconfigured your application! It should not run " + "with both the 'dev' and 'prod' profiles at the same time."
|
||||||
|
);
|
||||||
|
}
|
||||||
|
if (
|
||||||
|
activeProfiles.contains(JHipsterConstants.SPRING_PROFILE_DEVELOPMENT) &&
|
||||||
|
activeProfiles.contains(JHipsterConstants.SPRING_PROFILE_CLOUD)
|
||||||
|
) {
|
||||||
|
LOG.error(
|
||||||
|
"You have misconfigured your application! It should not " + "run with both the 'dev' and 'cloud' profiles at the same time."
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Main method, used to run the application.
|
||||||
|
*
|
||||||
|
* @param args the command line arguments.
|
||||||
|
*/
|
||||||
|
public static void main(String[] args) {
|
||||||
|
var app = new SpringApplication(QuestionBankApp.class);
|
||||||
|
DefaultProfileUtil.addDefaultProfile(app);
|
||||||
|
Environment env = app.run(args).getEnvironment();
|
||||||
|
logApplicationStartup(env);
|
||||||
|
}
|
||||||
|
|
||||||
|
private static void logApplicationStartup(Environment env) {
|
||||||
|
String protocol = Optional.ofNullable(env.getProperty("server.ssl.key-store"))
|
||||||
|
.map(key -> "https")
|
||||||
|
.orElse("http");
|
||||||
|
String applicationName = env.getProperty("spring.application.name");
|
||||||
|
String serverPort = env.getProperty("server.port");
|
||||||
|
String contextPath = Optional.ofNullable(env.getProperty("server.servlet.context-path"))
|
||||||
|
.filter(StringUtils::isNotBlank)
|
||||||
|
.orElse("/");
|
||||||
|
var hostAddress = "localhost";
|
||||||
|
try {
|
||||||
|
hostAddress = InetAddress.getLocalHost().getHostAddress();
|
||||||
|
} catch (UnknownHostException e) {
|
||||||
|
LOG.warn("The host name could not be determined, using `localhost` as fallback");
|
||||||
|
}
|
||||||
|
LOG.info(
|
||||||
|
CRLFLogConverter.CRLF_SAFE_MARKER,
|
||||||
|
"""
|
||||||
|
|
||||||
|
----------------------------------------------------------
|
||||||
|
\tApplication '{}' is running! Access URLs:
|
||||||
|
\tLocal: \t\t{}://localhost:{}{}
|
||||||
|
\tExternal: \t{}://{}:{}{}
|
||||||
|
\tProfile(s): \t{}
|
||||||
|
----------------------------------------------------------""",
|
||||||
|
applicationName,
|
||||||
|
protocol,
|
||||||
|
serverPort,
|
||||||
|
contextPath,
|
||||||
|
protocol,
|
||||||
|
hostAddress,
|
||||||
|
serverPort,
|
||||||
|
contextPath,
|
||||||
|
env.getActiveProfiles().length == 0 ? env.getDefaultProfiles() : env.getActiveProfiles()
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,131 @@
|
|||||||
|
package com.example.questionbank.aop.logging;
|
||||||
|
|
||||||
|
import java.util.Arrays;
|
||||||
|
import org.aspectj.lang.JoinPoint;
|
||||||
|
import org.aspectj.lang.ProceedingJoinPoint;
|
||||||
|
import org.aspectj.lang.annotation.AfterThrowing;
|
||||||
|
import org.aspectj.lang.annotation.Around;
|
||||||
|
import org.aspectj.lang.annotation.Aspect;
|
||||||
|
import org.aspectj.lang.annotation.Pointcut;
|
||||||
|
import org.slf4j.Logger;
|
||||||
|
import org.slf4j.LoggerFactory;
|
||||||
|
import org.springframework.core.env.Environment;
|
||||||
|
import org.springframework.core.env.Profiles;
|
||||||
|
import tech.jhipster.config.JHipsterConstants;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Aspect for logging execution of service and repository Spring components.
|
||||||
|
*
|
||||||
|
* By default, it only runs with the "dev" profile.
|
||||||
|
*/
|
||||||
|
@Aspect
|
||||||
|
public class LoggingAspect {
|
||||||
|
|
||||||
|
private final Environment env;
|
||||||
|
|
||||||
|
public LoggingAspect(Environment env) {
|
||||||
|
this.env = env;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Pointcut that matches all repositories, services and Web REST endpoints.
|
||||||
|
*/
|
||||||
|
@Pointcut(
|
||||||
|
"""
|
||||||
|
within(@org.springframework.stereotype.Repository *)
|
||||||
|
|| within(@org.springframework.stereotype.Service *)
|
||||||
|
|| within(@org.springframework.web.bind.annotation.RestController *)
|
||||||
|
"""
|
||||||
|
)
|
||||||
|
public void springBeanPointcut() {
|
||||||
|
// Method is empty as this is just a Pointcut, the implementations are in the advice methods.
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Pointcut that matches all Spring beans in the application's main packages.
|
||||||
|
*/
|
||||||
|
@Pointcut(
|
||||||
|
"""
|
||||||
|
within(com.example.questionbank.repository..*)
|
||||||
|
|| within(com.example.questionbank.service..*)
|
||||||
|
|| within(com.example.questionbank.web.rest..*)
|
||||||
|
"""
|
||||||
|
)
|
||||||
|
public void applicationPackagePointcut() {
|
||||||
|
// Method is empty as this is just a Pointcut, the implementations are in the advice methods.
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Retrieves the {@link Logger} associated to the given {@link JoinPoint}.
|
||||||
|
*
|
||||||
|
* @param joinPoint join point we want the logger for.
|
||||||
|
* @return {@link Logger} associated to the given {@link JoinPoint}.
|
||||||
|
*/
|
||||||
|
private Logger logger(JoinPoint joinPoint) {
|
||||||
|
return LoggerFactory.getLogger(joinPoint.getSignature().getDeclaringTypeName());
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Advice that logs methods throwing exceptions.
|
||||||
|
*
|
||||||
|
* @param joinPoint join point for advice.
|
||||||
|
* @param e exception.
|
||||||
|
*/
|
||||||
|
@AfterThrowing(pointcut = "applicationPackagePointcut() && springBeanPointcut()", throwing = "e")
|
||||||
|
public void logAfterThrowing(JoinPoint joinPoint, Throwable e) {
|
||||||
|
logException(joinPoint, e);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Advice that logs when a method is entered and exited.
|
||||||
|
*
|
||||||
|
* @param joinPoint join point for advice.
|
||||||
|
* @return result.
|
||||||
|
* @throws Throwable rethrows the exception thrown by the advised method.
|
||||||
|
*/
|
||||||
|
@Around("applicationPackagePointcut() && springBeanPointcut()")
|
||||||
|
public Object logAround(ProceedingJoinPoint joinPoint) throws Throwable {
|
||||||
|
var log = logger(joinPoint);
|
||||||
|
if (log.isDebugEnabled()) {
|
||||||
|
log.debug("Enter: {}() with argument[s] = {}", joinPoint.getSignature().getName(), Arrays.toString(joinPoint.getArgs()));
|
||||||
|
}
|
||||||
|
try {
|
||||||
|
Object result = joinPoint.proceed();
|
||||||
|
logExit(log, joinPoint, result);
|
||||||
|
return result;
|
||||||
|
} catch (IllegalArgumentException e) {
|
||||||
|
logIllegalArgument(log, joinPoint);
|
||||||
|
throw e;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private void logExit(Logger log, JoinPoint joinPoint, Object result) {
|
||||||
|
if (log.isDebugEnabled()) {
|
||||||
|
log.debug("Exit: {}() with result = {}", joinPoint.getSignature().getName(), result);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private void logIllegalArgument(Logger log, JoinPoint joinPoint) {
|
||||||
|
if (log.isErrorEnabled()) {
|
||||||
|
log.error("Illegal argument: {} in {}()", Arrays.toString(joinPoint.getArgs()), joinPoint.getSignature().getName());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private void logException(JoinPoint joinPoint, Throwable e) {
|
||||||
|
if (env.acceptsProfiles(Profiles.of(JHipsterConstants.SPRING_PROFILE_DEVELOPMENT))) {
|
||||||
|
logger(joinPoint).error(
|
||||||
|
"Exception in {}() with cause = '{}' and exception = '{}'",
|
||||||
|
joinPoint.getSignature().getName(),
|
||||||
|
e.getCause() != null ? e.getCause() : "NULL",
|
||||||
|
e.getMessage(),
|
||||||
|
e
|
||||||
|
);
|
||||||
|
} else {
|
||||||
|
logger(joinPoint).error(
|
||||||
|
"Exception in {}() with cause = {}",
|
||||||
|
joinPoint.getSignature().getName(),
|
||||||
|
e.getCause() != null ? String.valueOf(e.getCause()) : "NULL"
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,4 @@
|
|||||||
|
/**
|
||||||
|
* Logging aspect.
|
||||||
|
*/
|
||||||
|
package com.example.questionbank.aop.logging;
|
||||||
@@ -0,0 +1,38 @@
|
|||||||
|
package com.example.questionbank.config;
|
||||||
|
|
||||||
|
import org.springframework.boot.context.properties.ConfigurationProperties;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Properties specific to Question Bank.
|
||||||
|
* <p>
|
||||||
|
* Properties are configured in the {@code application.yml} file.
|
||||||
|
* See {@link tech.jhipster.config.JHipsterProperties} for a good example.
|
||||||
|
*/
|
||||||
|
@ConfigurationProperties(prefix = "application", ignoreUnknownFields = false)
|
||||||
|
public class ApplicationProperties {
|
||||||
|
|
||||||
|
private final Liquibase liquibase = new Liquibase();
|
||||||
|
|
||||||
|
// jhipster-needle-application-properties-property
|
||||||
|
|
||||||
|
public Liquibase getLiquibase() {
|
||||||
|
return liquibase;
|
||||||
|
}
|
||||||
|
|
||||||
|
// jhipster-needle-application-properties-property-getter
|
||||||
|
|
||||||
|
public static class Liquibase {
|
||||||
|
|
||||||
|
private Boolean asyncStart = true;
|
||||||
|
|
||||||
|
public Boolean getAsyncStart() {
|
||||||
|
return asyncStart;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setAsyncStart(Boolean asyncStart) {
|
||||||
|
this.asyncStart = asyncStart;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// jhipster-needle-application-properties-property-class
|
||||||
|
}
|
||||||
@@ -0,0 +1,48 @@
|
|||||||
|
package com.example.questionbank.config;
|
||||||
|
|
||||||
|
import java.util.concurrent.Executor;
|
||||||
|
import org.slf4j.Logger;
|
||||||
|
import org.slf4j.LoggerFactory;
|
||||||
|
import org.springframework.aop.interceptor.AsyncUncaughtExceptionHandler;
|
||||||
|
import org.springframework.aop.interceptor.SimpleAsyncUncaughtExceptionHandler;
|
||||||
|
import org.springframework.boot.autoconfigure.task.TaskExecutionProperties;
|
||||||
|
import org.springframework.context.annotation.Bean;
|
||||||
|
import org.springframework.context.annotation.Configuration;
|
||||||
|
import org.springframework.context.annotation.Profile;
|
||||||
|
import org.springframework.scheduling.annotation.AsyncConfigurer;
|
||||||
|
import org.springframework.scheduling.annotation.EnableAsync;
|
||||||
|
import org.springframework.scheduling.annotation.EnableScheduling;
|
||||||
|
import org.springframework.scheduling.concurrent.ThreadPoolTaskExecutor;
|
||||||
|
import tech.jhipster.async.ExceptionHandlingAsyncTaskExecutor;
|
||||||
|
|
||||||
|
@Configuration
|
||||||
|
@EnableAsync
|
||||||
|
@EnableScheduling
|
||||||
|
@Profile("!testdev & !testprod")
|
||||||
|
public class AsyncConfiguration implements AsyncConfigurer {
|
||||||
|
|
||||||
|
private static final Logger LOG = LoggerFactory.getLogger(AsyncConfiguration.class);
|
||||||
|
|
||||||
|
private final TaskExecutionProperties taskExecutionProperties;
|
||||||
|
|
||||||
|
public AsyncConfiguration(TaskExecutionProperties taskExecutionProperties) {
|
||||||
|
this.taskExecutionProperties = taskExecutionProperties;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
@Bean(name = "taskExecutor")
|
||||||
|
public Executor getAsyncExecutor() {
|
||||||
|
LOG.debug("Creating Async Task Executor");
|
||||||
|
var executor = new ThreadPoolTaskExecutor();
|
||||||
|
executor.setCorePoolSize(taskExecutionProperties.getPool().getCoreSize());
|
||||||
|
executor.setMaxPoolSize(taskExecutionProperties.getPool().getMaxSize());
|
||||||
|
executor.setQueueCapacity(taskExecutionProperties.getPool().getQueueCapacity());
|
||||||
|
executor.setThreadNamePrefix(taskExecutionProperties.getThreadNamePrefix());
|
||||||
|
return new ExceptionHandlingAsyncTaskExecutor(executor);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public AsyncUncaughtExceptionHandler getAsyncUncaughtExceptionHandler() {
|
||||||
|
return new SimpleAsyncUncaughtExceptionHandler();
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,69 @@
|
|||||||
|
package com.example.questionbank.config;
|
||||||
|
|
||||||
|
import ch.qos.logback.classic.spi.ILoggingEvent;
|
||||||
|
import ch.qos.logback.core.pattern.CompositeConverter;
|
||||||
|
import java.util.Collections;
|
||||||
|
import java.util.HashMap;
|
||||||
|
import java.util.List;
|
||||||
|
import java.util.Map;
|
||||||
|
import org.slf4j.Marker;
|
||||||
|
import org.slf4j.MarkerFactory;
|
||||||
|
import org.springframework.boot.ansi.AnsiColor;
|
||||||
|
import org.springframework.boot.ansi.AnsiElement;
|
||||||
|
import org.springframework.boot.ansi.AnsiOutput;
|
||||||
|
import org.springframework.boot.ansi.AnsiStyle;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Log filter to prevent attackers from forging log entries by submitting input containing CRLF characters.
|
||||||
|
* CRLF characters are replaced with a red colored _ character.
|
||||||
|
*
|
||||||
|
* @see <a href="https://owasp.org/www-community/attacks/Log_Injection">Log Forging Description</a>
|
||||||
|
* @see <a href="https://github.com/jhipster/generator-jhipster/issues/14949">JHipster issue</a>
|
||||||
|
*/
|
||||||
|
public class CRLFLogConverter extends CompositeConverter<ILoggingEvent> {
|
||||||
|
|
||||||
|
public static final Marker CRLF_SAFE_MARKER = MarkerFactory.getMarker("CRLF_SAFE");
|
||||||
|
|
||||||
|
private static final String[] SAFE_LOGS = {
|
||||||
|
"org.hibernate",
|
||||||
|
"org.springframework.boot.autoconfigure",
|
||||||
|
"org.springframework.boot.diagnostics",
|
||||||
|
};
|
||||||
|
private static final Map<String, AnsiElement> ELEMENTS;
|
||||||
|
|
||||||
|
static {
|
||||||
|
Map<String, AnsiElement> ansiElements = new HashMap<>();
|
||||||
|
ansiElements.put("faint", AnsiStyle.FAINT);
|
||||||
|
ansiElements.put("red", AnsiColor.RED);
|
||||||
|
ansiElements.put("green", AnsiColor.GREEN);
|
||||||
|
ansiElements.put("yellow", AnsiColor.YELLOW);
|
||||||
|
ansiElements.put("blue", AnsiColor.BLUE);
|
||||||
|
ansiElements.put("magenta", AnsiColor.MAGENTA);
|
||||||
|
ansiElements.put("cyan", AnsiColor.CYAN);
|
||||||
|
ELEMENTS = Collections.unmodifiableMap(ansiElements);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
protected String transform(ILoggingEvent event, String in) {
|
||||||
|
AnsiElement element = ELEMENTS.get(getFirstOption());
|
||||||
|
List<Marker> markers = event.getMarkerList();
|
||||||
|
if ((markers != null && !markers.isEmpty() && markers.getFirst().contains(CRLF_SAFE_MARKER)) || isLoggerSafe(event)) {
|
||||||
|
return in;
|
||||||
|
}
|
||||||
|
String replacement = element == null ? "_" : toAnsiString("_", element);
|
||||||
|
return in.replaceAll("[\n\r\t]", replacement);
|
||||||
|
}
|
||||||
|
|
||||||
|
protected boolean isLoggerSafe(ILoggingEvent event) {
|
||||||
|
for (String safeLogger : SAFE_LOGS) {
|
||||||
|
if (event.getLoggerName().startsWith(safeLogger)) {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
protected String toAnsiString(String in, AnsiElement element) {
|
||||||
|
return AnsiOutput.toString(element, in);
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,15 @@
|
|||||||
|
package com.example.questionbank.config;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Application constants.
|
||||||
|
*/
|
||||||
|
public final class Constants {
|
||||||
|
|
||||||
|
// Regex for acceptable logins
|
||||||
|
public static final String LOGIN_REGEX = "^(?>[a-zA-Z0-9!$&*+=?^_`{|}~.-]+@[a-zA-Z0-9-]+(?:\\.[a-zA-Z0-9-]+)*)|(?>[_.@A-Za-z0-9-]+)$";
|
||||||
|
|
||||||
|
public static final String SYSTEM = "system";
|
||||||
|
public static final String DEFAULT_LANGUAGE = "en";
|
||||||
|
|
||||||
|
private Constants() {}
|
||||||
|
}
|
||||||
@@ -0,0 +1,12 @@
|
|||||||
|
package com.example.questionbank.config;
|
||||||
|
|
||||||
|
import org.springframework.context.annotation.Configuration;
|
||||||
|
import org.springframework.data.jpa.repository.config.EnableJpaAuditing;
|
||||||
|
import org.springframework.data.jpa.repository.config.EnableJpaRepositories;
|
||||||
|
import org.springframework.transaction.annotation.EnableTransactionManagement;
|
||||||
|
|
||||||
|
@Configuration
|
||||||
|
@EnableJpaRepositories({ "com.example.questionbank.repository" })
|
||||||
|
@EnableJpaAuditing(auditorAwareRef = "springSecurityAuditorAware")
|
||||||
|
@EnableTransactionManagement
|
||||||
|
public class DatabaseConfiguration {}
|
||||||
@@ -0,0 +1,20 @@
|
|||||||
|
package com.example.questionbank.config;
|
||||||
|
|
||||||
|
import org.springframework.context.annotation.Configuration;
|
||||||
|
import org.springframework.format.FormatterRegistry;
|
||||||
|
import org.springframework.format.datetime.standard.DateTimeFormatterRegistrar;
|
||||||
|
import org.springframework.web.servlet.config.annotation.WebMvcConfigurer;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Configure the converters to use the ISO format for dates by default.
|
||||||
|
*/
|
||||||
|
@Configuration
|
||||||
|
public class DateTimeFormatConfiguration implements WebMvcConfigurer {
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void addFormatters(FormatterRegistry registry) {
|
||||||
|
var registrar = new DateTimeFormatterRegistrar();
|
||||||
|
registrar.setUseIsoFormat(true);
|
||||||
|
registrar.registerFormatters(registry);
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,18 @@
|
|||||||
|
package com.example.questionbank.config;
|
||||||
|
|
||||||
|
import org.springframework.context.annotation.Bean;
|
||||||
|
import org.springframework.context.annotation.Configuration;
|
||||||
|
import tools.jackson.datatype.hibernate7.Hibernate7Module;
|
||||||
|
import tools.jackson.datatype.hibernate7.Hibernate7Module.Feature;
|
||||||
|
|
||||||
|
@Configuration
|
||||||
|
public class JacksonHibernateConfiguration {
|
||||||
|
|
||||||
|
/*
|
||||||
|
* Support for Hibernate types in Jackson.
|
||||||
|
*/
|
||||||
|
@Bean
|
||||||
|
public Hibernate7Module hibernate7Module() {
|
||||||
|
return new Hibernate7Module().configure(Feature.SERIALIZE_IDENTIFIER_FOR_LAZY_NOT_LOADED_OBJECTS, true);
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,83 @@
|
|||||||
|
package com.example.questionbank.config;
|
||||||
|
|
||||||
|
import java.util.concurrent.Executor;
|
||||||
|
import javax.sql.DataSource;
|
||||||
|
import liquibase.integration.spring.SpringLiquibase;
|
||||||
|
import org.slf4j.Logger;
|
||||||
|
import org.slf4j.LoggerFactory;
|
||||||
|
import org.springframework.beans.factory.ObjectProvider;
|
||||||
|
import org.springframework.beans.factory.annotation.Qualifier;
|
||||||
|
import org.springframework.boot.jdbc.autoconfigure.DataSourceProperties;
|
||||||
|
import org.springframework.boot.liquibase.autoconfigure.LiquibaseDataSource;
|
||||||
|
import org.springframework.boot.liquibase.autoconfigure.LiquibaseProperties;
|
||||||
|
import org.springframework.context.annotation.Bean;
|
||||||
|
import org.springframework.context.annotation.Configuration;
|
||||||
|
import org.springframework.core.env.Environment;
|
||||||
|
import org.springframework.util.CollectionUtils;
|
||||||
|
import org.springframework.util.StringUtils;
|
||||||
|
import tech.jhipster.config.JHipsterConstants;
|
||||||
|
import tech.jhipster.config.liquibase.SpringLiquibaseUtil;
|
||||||
|
|
||||||
|
@Configuration
|
||||||
|
public class LiquibaseConfiguration {
|
||||||
|
|
||||||
|
private static final Logger LOG = LoggerFactory.getLogger(LiquibaseConfiguration.class);
|
||||||
|
|
||||||
|
private final Environment env;
|
||||||
|
|
||||||
|
public LiquibaseConfiguration(Environment env) {
|
||||||
|
this.env = env;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Bean
|
||||||
|
public SpringLiquibase liquibase(
|
||||||
|
@Qualifier("taskExecutor") Executor executor,
|
||||||
|
LiquibaseProperties liquibaseProperties,
|
||||||
|
@LiquibaseDataSource ObjectProvider<DataSource> liquibaseDataSource,
|
||||||
|
ObjectProvider<DataSource> dataSource,
|
||||||
|
ApplicationProperties applicationProperties,
|
||||||
|
DataSourceProperties dataSourceProperties
|
||||||
|
) {
|
||||||
|
SpringLiquibase liquibase;
|
||||||
|
if (Boolean.TRUE.equals(applicationProperties.getLiquibase().getAsyncStart())) {
|
||||||
|
liquibase = SpringLiquibaseUtil.createAsyncSpringLiquibase(
|
||||||
|
this.env,
|
||||||
|
executor,
|
||||||
|
liquibaseDataSource.getIfAvailable(),
|
||||||
|
liquibaseProperties,
|
||||||
|
dataSource.getIfUnique(),
|
||||||
|
dataSourceProperties
|
||||||
|
);
|
||||||
|
} else {
|
||||||
|
liquibase = SpringLiquibaseUtil.createSpringLiquibase(
|
||||||
|
liquibaseDataSource.getIfAvailable(),
|
||||||
|
liquibaseProperties,
|
||||||
|
dataSource.getIfUnique(),
|
||||||
|
dataSourceProperties
|
||||||
|
);
|
||||||
|
}
|
||||||
|
liquibase.setChangeLog("classpath:config/liquibase/master.xml");
|
||||||
|
if (!CollectionUtils.isEmpty(liquibaseProperties.getContexts())) {
|
||||||
|
liquibase.setContexts(StringUtils.collectionToCommaDelimitedString(liquibaseProperties.getContexts()));
|
||||||
|
}
|
||||||
|
liquibase.setDefaultSchema(liquibaseProperties.getDefaultSchema());
|
||||||
|
liquibase.setLiquibaseSchema(liquibaseProperties.getLiquibaseSchema());
|
||||||
|
liquibase.setLiquibaseTablespace(liquibaseProperties.getLiquibaseTablespace());
|
||||||
|
liquibase.setDatabaseChangeLogLockTable(liquibaseProperties.getDatabaseChangeLogLockTable());
|
||||||
|
liquibase.setDatabaseChangeLogTable(liquibaseProperties.getDatabaseChangeLogTable());
|
||||||
|
liquibase.setDropFirst(liquibaseProperties.isDropFirst());
|
||||||
|
if (!CollectionUtils.isEmpty(liquibaseProperties.getLabelFilter())) {
|
||||||
|
liquibase.setLabelFilter(StringUtils.collectionToCommaDelimitedString(liquibaseProperties.getLabelFilter()));
|
||||||
|
}
|
||||||
|
liquibase.setChangeLogParameters(liquibaseProperties.getParameters());
|
||||||
|
liquibase.setRollbackFile(liquibaseProperties.getRollbackFile());
|
||||||
|
liquibase.setTestRollbackOnUpdate(liquibaseProperties.isTestRollbackOnUpdate());
|
||||||
|
if (env.matchesProfiles(JHipsterConstants.SPRING_PROFILE_NO_LIQUIBASE)) {
|
||||||
|
liquibase.setShouldRun(false);
|
||||||
|
} else {
|
||||||
|
liquibase.setShouldRun(liquibaseProperties.isEnabled());
|
||||||
|
LOG.debug("Configuring Liquibase");
|
||||||
|
}
|
||||||
|
return liquibase;
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,17 @@
|
|||||||
|
package com.example.questionbank.config;
|
||||||
|
|
||||||
|
import com.example.questionbank.aop.logging.LoggingAspect;
|
||||||
|
import org.springframework.context.annotation.*;
|
||||||
|
import org.springframework.core.env.Environment;
|
||||||
|
import tech.jhipster.config.JHipsterConstants;
|
||||||
|
|
||||||
|
@Configuration
|
||||||
|
@EnableAspectJAutoProxy
|
||||||
|
public class LoggingAspectConfiguration {
|
||||||
|
|
||||||
|
@Bean
|
||||||
|
@Profile(JHipsterConstants.SPRING_PROFILE_DEVELOPMENT)
|
||||||
|
public LoggingAspect loggingAspect(Environment env) {
|
||||||
|
return new LoggingAspect(env);
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,47 @@
|
|||||||
|
package com.example.questionbank.config;
|
||||||
|
|
||||||
|
import static tech.jhipster.config.logging.LoggingUtils.*;
|
||||||
|
|
||||||
|
import ch.qos.logback.classic.LoggerContext;
|
||||||
|
import java.util.HashMap;
|
||||||
|
import java.util.Map;
|
||||||
|
import org.slf4j.LoggerFactory;
|
||||||
|
import org.springframework.beans.factory.annotation.Value;
|
||||||
|
import org.springframework.context.annotation.Configuration;
|
||||||
|
import tech.jhipster.config.JHipsterProperties;
|
||||||
|
import tools.jackson.core.JacksonException;
|
||||||
|
import tools.jackson.databind.ObjectMapper;
|
||||||
|
|
||||||
|
/*
|
||||||
|
* Configures the console and Logstash log appenders from the app properties
|
||||||
|
*/
|
||||||
|
@Configuration
|
||||||
|
public class LoggingConfiguration {
|
||||||
|
|
||||||
|
public LoggingConfiguration(
|
||||||
|
@Value("${spring.application.name}") String appName,
|
||||||
|
@Value("${server.port}") String serverPort,
|
||||||
|
JHipsterProperties jHipsterProperties,
|
||||||
|
ObjectMapper mapper
|
||||||
|
) throws JacksonException {
|
||||||
|
LoggerContext context = (LoggerContext) LoggerFactory.getILoggerFactory();
|
||||||
|
|
||||||
|
Map<String, String> map = new HashMap<>();
|
||||||
|
map.put("app_name", appName);
|
||||||
|
map.put("app_port", serverPort);
|
||||||
|
var customFields = mapper.writeValueAsString(map);
|
||||||
|
|
||||||
|
var loggingProperties = jHipsterProperties.getLogging();
|
||||||
|
var logstashProperties = loggingProperties.getLogstash();
|
||||||
|
|
||||||
|
if (loggingProperties.isUseJsonFormat()) {
|
||||||
|
addJsonConsoleAppender(context, customFields);
|
||||||
|
}
|
||||||
|
if (logstashProperties.isEnabled()) {
|
||||||
|
addLogstashTcpSocketAppender(context, customFields, logstashProperties);
|
||||||
|
}
|
||||||
|
if (loggingProperties.isUseJsonFormat() || logstashProperties.isEnabled()) {
|
||||||
|
addContextListener(context, customFields, loggingProperties);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,85 @@
|
|||||||
|
package com.example.questionbank.config;
|
||||||
|
|
||||||
|
import static org.springframework.security.config.Customizer.withDefaults;
|
||||||
|
|
||||||
|
import com.example.questionbank.security.*;
|
||||||
|
import com.example.questionbank.web.filter.SpaWebFilter;
|
||||||
|
import org.springframework.context.annotation.Bean;
|
||||||
|
import org.springframework.context.annotation.Configuration;
|
||||||
|
import org.springframework.http.HttpMethod;
|
||||||
|
import org.springframework.security.config.annotation.method.configuration.EnableMethodSecurity;
|
||||||
|
import org.springframework.security.config.annotation.web.builders.HttpSecurity;
|
||||||
|
import org.springframework.security.config.annotation.web.configurers.HeadersConfigurer.FrameOptionsConfig;
|
||||||
|
import org.springframework.security.config.http.SessionCreationPolicy;
|
||||||
|
import org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder;
|
||||||
|
import org.springframework.security.crypto.password.PasswordEncoder;
|
||||||
|
import org.springframework.security.oauth2.server.resource.web.BearerTokenAuthenticationEntryPoint;
|
||||||
|
import org.springframework.security.oauth2.server.resource.web.access.BearerTokenAccessDeniedHandler;
|
||||||
|
import org.springframework.security.web.SecurityFilterChain;
|
||||||
|
import org.springframework.security.web.authentication.www.BasicAuthenticationFilter;
|
||||||
|
import org.springframework.security.web.header.writers.ReferrerPolicyHeaderWriter;
|
||||||
|
import tech.jhipster.config.JHipsterProperties;
|
||||||
|
|
||||||
|
@Configuration
|
||||||
|
@EnableMethodSecurity(securedEnabled = true)
|
||||||
|
public class SecurityConfiguration {
|
||||||
|
|
||||||
|
private final JHipsterProperties jHipsterProperties;
|
||||||
|
|
||||||
|
public SecurityConfiguration(JHipsterProperties jHipsterProperties) {
|
||||||
|
this.jHipsterProperties = jHipsterProperties;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Bean
|
||||||
|
public PasswordEncoder passwordEncoder() {
|
||||||
|
return new BCryptPasswordEncoder();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Bean
|
||||||
|
public SecurityFilterChain filterChain(HttpSecurity http) {
|
||||||
|
http.cors(withDefaults())
|
||||||
|
.csrf(csrf -> csrf.disable())
|
||||||
|
.addFilterAfter(new SpaWebFilter(), BasicAuthenticationFilter.class)
|
||||||
|
.headers(headers ->
|
||||||
|
headers
|
||||||
|
.contentSecurityPolicy(csp -> csp.policyDirectives(jHipsterProperties.getSecurity().getContentSecurityPolicy()))
|
||||||
|
.frameOptions(FrameOptionsConfig::sameOrigin)
|
||||||
|
.referrerPolicy(referrer -> referrer.policy(ReferrerPolicyHeaderWriter.ReferrerPolicy.STRICT_ORIGIN_WHEN_CROSS_ORIGIN))
|
||||||
|
.permissionsPolicyHeader(permissions ->
|
||||||
|
permissions.policy(
|
||||||
|
"camera=(), fullscreen=(self), geolocation=(), gyroscope=(), magnetometer=(), microphone=(), midi=(), payment=(), sync-xhr=()"
|
||||||
|
)
|
||||||
|
)
|
||||||
|
)
|
||||||
|
.authorizeHttpRequests(authz ->
|
||||||
|
// prettier-ignore
|
||||||
|
authz
|
||||||
|
.requestMatchers("/index.html", "/*.js", "/*.txt", "/*.json", "/*.map", "/*.css").permitAll()
|
||||||
|
.requestMatchers("/*.ico", "/*.png", "/*.svg", "/*.webapp").permitAll()
|
||||||
|
.requestMatchers("/assets/**").permitAll()
|
||||||
|
.requestMatchers("/swagger-ui/**").permitAll()
|
||||||
|
.requestMatchers(HttpMethod.POST, "/api/authenticate").permitAll()
|
||||||
|
.requestMatchers(HttpMethod.GET, "/api/authenticate").permitAll()
|
||||||
|
.requestMatchers("/api/register").permitAll()
|
||||||
|
.requestMatchers("/api/activate").permitAll()
|
||||||
|
.requestMatchers("/api/account/reset-password/init").permitAll()
|
||||||
|
.requestMatchers("/api/account/reset-password/finish").permitAll()
|
||||||
|
.requestMatchers("/api/admin/**").hasAuthority(AuthoritiesConstants.ADMIN)
|
||||||
|
.requestMatchers("/api/**").authenticated()
|
||||||
|
.requestMatchers("/v3/api-docs/**").hasAuthority(AuthoritiesConstants.ADMIN)
|
||||||
|
.requestMatchers("/management/health").permitAll()
|
||||||
|
.requestMatchers("/management/health/**").permitAll()
|
||||||
|
.requestMatchers("/management/info").permitAll()
|
||||||
|
.requestMatchers("/management/prometheus").permitAll()
|
||||||
|
.requestMatchers("/management/**").hasAuthority(AuthoritiesConstants.ADMIN)
|
||||||
|
)
|
||||||
|
.sessionManagement(session -> session.sessionCreationPolicy(SessionCreationPolicy.STATELESS))
|
||||||
|
.exceptionHandling(exceptions ->
|
||||||
|
exceptions
|
||||||
|
.authenticationEntryPoint(new BearerTokenAuthenticationEntryPoint())
|
||||||
|
.accessDeniedHandler(new BearerTokenAccessDeniedHandler())
|
||||||
|
)
|
||||||
|
.oauth2ResourceServer(oauth2 -> oauth2.jwt(withDefaults()));
|
||||||
|
return http.build();
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,62 @@
|
|||||||
|
package com.example.questionbank.config;
|
||||||
|
|
||||||
|
import static com.example.questionbank.security.SecurityUtils.JWT_ALGORITHM;
|
||||||
|
|
||||||
|
import com.example.questionbank.management.SecurityMetersService;
|
||||||
|
import com.nimbusds.jose.jwk.source.ImmutableSecret;
|
||||||
|
import com.nimbusds.jose.util.Base64;
|
||||||
|
import javax.crypto.SecretKey;
|
||||||
|
import javax.crypto.spec.SecretKeySpec;
|
||||||
|
import org.slf4j.Logger;
|
||||||
|
import org.slf4j.LoggerFactory;
|
||||||
|
import org.springframework.beans.factory.annotation.Value;
|
||||||
|
import org.springframework.context.annotation.Bean;
|
||||||
|
import org.springframework.context.annotation.Configuration;
|
||||||
|
import org.springframework.security.oauth2.jwt.JwtDecoder;
|
||||||
|
import org.springframework.security.oauth2.jwt.JwtEncoder;
|
||||||
|
import org.springframework.security.oauth2.jwt.NimbusJwtDecoder;
|
||||||
|
import org.springframework.security.oauth2.jwt.NimbusJwtEncoder;
|
||||||
|
|
||||||
|
@Configuration
|
||||||
|
public class SecurityJwtConfiguration {
|
||||||
|
|
||||||
|
private static final Logger LOG = LoggerFactory.getLogger(SecurityJwtConfiguration.class);
|
||||||
|
|
||||||
|
@Value("${jhipster.security.authentication.jwt.base64-secret}")
|
||||||
|
private String jwtKey;
|
||||||
|
|
||||||
|
@Bean
|
||||||
|
public JwtDecoder jwtDecoder(SecurityMetersService metersService) {
|
||||||
|
NimbusJwtDecoder jwtDecoder = NimbusJwtDecoder.withSecretKey(getSecretKey()).macAlgorithm(JWT_ALGORITHM).build();
|
||||||
|
return token -> {
|
||||||
|
try {
|
||||||
|
return jwtDecoder.decode(token);
|
||||||
|
} catch (Exception e) {
|
||||||
|
if (e.getMessage().contains("Invalid signature")) {
|
||||||
|
metersService.trackTokenInvalidSignature();
|
||||||
|
} else if (e.getMessage().contains("Jwt expired at")) {
|
||||||
|
metersService.trackTokenExpired();
|
||||||
|
} else if (
|
||||||
|
e.getMessage().contains("Invalid JWT serialization") ||
|
||||||
|
e.getMessage().contains("Malformed token") ||
|
||||||
|
e.getMessage().contains("Invalid unsecured/JWS/JWE")
|
||||||
|
) {
|
||||||
|
metersService.trackTokenMalformed();
|
||||||
|
} else {
|
||||||
|
LOG.error("Unknown JWT error {}", e.getMessage());
|
||||||
|
}
|
||||||
|
throw e;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
@Bean
|
||||||
|
public JwtEncoder jwtEncoder() {
|
||||||
|
return new NimbusJwtEncoder(new ImmutableSecret<>(getSecretKey()));
|
||||||
|
}
|
||||||
|
|
||||||
|
private SecretKey getSecretKey() {
|
||||||
|
byte[] keyBytes = Base64.from(jwtKey).decode();
|
||||||
|
return new SecretKeySpec(keyBytes, 0, keyBytes.length, JWT_ALGORITHM.getName());
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,47 @@
|
|||||||
|
package com.example.questionbank.config;
|
||||||
|
|
||||||
|
import java.util.concurrent.TimeUnit;
|
||||||
|
import org.springframework.context.annotation.Configuration;
|
||||||
|
import org.springframework.context.annotation.Profile;
|
||||||
|
import org.springframework.http.CacheControl;
|
||||||
|
import org.springframework.web.servlet.config.annotation.ResourceHandlerRegistration;
|
||||||
|
import org.springframework.web.servlet.config.annotation.ResourceHandlerRegistry;
|
||||||
|
import org.springframework.web.servlet.config.annotation.WebMvcConfigurer;
|
||||||
|
import tech.jhipster.config.JHipsterConstants;
|
||||||
|
import tech.jhipster.config.JHipsterProperties;
|
||||||
|
|
||||||
|
@Configuration
|
||||||
|
@Profile({ JHipsterConstants.SPRING_PROFILE_PRODUCTION })
|
||||||
|
public class StaticResourcesWebConfiguration implements WebMvcConfigurer {
|
||||||
|
|
||||||
|
protected static final String[] RESOURCE_LOCATIONS = { "classpath:/static/", "classpath:/static/content/", "classpath:/static/i18n/" };
|
||||||
|
protected static final String[] RESOURCE_PATHS = { "/*.js", "/*.css", "/*.svg", "/*.png", "*.ico", "/content/**", "/i18n/*" };
|
||||||
|
|
||||||
|
private final JHipsterProperties jhipsterProperties;
|
||||||
|
|
||||||
|
public StaticResourcesWebConfiguration(JHipsterProperties jHipsterProperties) {
|
||||||
|
this.jhipsterProperties = jHipsterProperties;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void addResourceHandlers(ResourceHandlerRegistry registry) {
|
||||||
|
ResourceHandlerRegistration resourceHandlerRegistration = appendResourceHandler(registry);
|
||||||
|
initializeResourceHandler(resourceHandlerRegistration);
|
||||||
|
}
|
||||||
|
|
||||||
|
protected ResourceHandlerRegistration appendResourceHandler(ResourceHandlerRegistry registry) {
|
||||||
|
return registry.addResourceHandler(RESOURCE_PATHS);
|
||||||
|
}
|
||||||
|
|
||||||
|
protected void initializeResourceHandler(ResourceHandlerRegistration resourceHandlerRegistration) {
|
||||||
|
resourceHandlerRegistration.addResourceLocations(RESOURCE_LOCATIONS).setCacheControl(getCacheControl());
|
||||||
|
}
|
||||||
|
|
||||||
|
protected CacheControl getCacheControl() {
|
||||||
|
return CacheControl.maxAge(getJHipsterHttpCacheProperty(), TimeUnit.DAYS).cachePublic();
|
||||||
|
}
|
||||||
|
|
||||||
|
private int getJHipsterHttpCacheProperty() {
|
||||||
|
return jhipsterProperties.getHttp().getCache().getTimeToLiveInDays();
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,96 @@
|
|||||||
|
package com.example.questionbank.config;
|
||||||
|
|
||||||
|
import static java.net.URLDecoder.decode;
|
||||||
|
|
||||||
|
import jakarta.servlet.*;
|
||||||
|
import java.io.File;
|
||||||
|
import java.nio.charset.StandardCharsets;
|
||||||
|
import java.nio.file.Path;
|
||||||
|
import org.slf4j.Logger;
|
||||||
|
import org.slf4j.LoggerFactory;
|
||||||
|
import org.springframework.boot.web.server.*;
|
||||||
|
import org.springframework.boot.web.server.servlet.ConfigurableServletWebServerFactory;
|
||||||
|
import org.springframework.boot.web.servlet.ServletContextInitializer;
|
||||||
|
import org.springframework.context.annotation.Bean;
|
||||||
|
import org.springframework.context.annotation.Configuration;
|
||||||
|
import org.springframework.core.env.Environment;
|
||||||
|
import org.springframework.util.CollectionUtils;
|
||||||
|
import org.springframework.web.cors.CorsConfiguration;
|
||||||
|
import org.springframework.web.cors.UrlBasedCorsConfigurationSource;
|
||||||
|
import org.springframework.web.filter.CorsFilter;
|
||||||
|
import tech.jhipster.config.JHipsterProperties;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Configuration of web application with Servlet 3.0 APIs.
|
||||||
|
*/
|
||||||
|
@Configuration
|
||||||
|
public class WebConfigurer implements ServletContextInitializer, WebServerFactoryCustomizer<WebServerFactory> {
|
||||||
|
|
||||||
|
private static final Logger LOG = LoggerFactory.getLogger(WebConfigurer.class);
|
||||||
|
|
||||||
|
private final Environment env;
|
||||||
|
|
||||||
|
private final JHipsterProperties jHipsterProperties;
|
||||||
|
|
||||||
|
public WebConfigurer(Environment env, JHipsterProperties jHipsterProperties) {
|
||||||
|
this.env = env;
|
||||||
|
this.jHipsterProperties = jHipsterProperties;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void onStartup(ServletContext servletContext) {
|
||||||
|
if (env.getActiveProfiles().length != 0) {
|
||||||
|
LOG.info("Web application configuration, using profiles: {}", (Object[]) env.getActiveProfiles());
|
||||||
|
}
|
||||||
|
|
||||||
|
LOG.info("Web application fully configured");
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Customize the Servlet engine: Mime types, the document root, the cache.
|
||||||
|
*/
|
||||||
|
@Override
|
||||||
|
public void customize(WebServerFactory server) {
|
||||||
|
// When running in an IDE or with ./mvnw spring-boot:run, set location of the static web assets.
|
||||||
|
setLocationForStaticAssets(server);
|
||||||
|
}
|
||||||
|
|
||||||
|
private void setLocationForStaticAssets(WebServerFactory server) {
|
||||||
|
if (server instanceof ConfigurableServletWebServerFactory servletWebServer) {
|
||||||
|
File root;
|
||||||
|
String prefixPath = resolvePathPrefix();
|
||||||
|
root = Path.of(prefixPath + "target/classes/static/").toFile();
|
||||||
|
if (root.exists() && root.isDirectory()) {
|
||||||
|
servletWebServer.setDocumentRoot(root);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Resolve path prefix to static resources.
|
||||||
|
*/
|
||||||
|
private String resolvePathPrefix() {
|
||||||
|
String fullExecutablePath = decode(this.getClass().getResource("").getPath(), StandardCharsets.UTF_8);
|
||||||
|
String rootPath = Path.of(".").toUri().normalize().getPath();
|
||||||
|
String extractedPath = fullExecutablePath.replace(rootPath, "");
|
||||||
|
int extractionEndIndex = extractedPath.indexOf("target/");
|
||||||
|
if (extractionEndIndex <= 0) {
|
||||||
|
return "";
|
||||||
|
}
|
||||||
|
return extractedPath.substring(0, extractionEndIndex);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Bean
|
||||||
|
public CorsFilter corsFilter() {
|
||||||
|
UrlBasedCorsConfigurationSource source = new UrlBasedCorsConfigurationSource();
|
||||||
|
CorsConfiguration config = jHipsterProperties.getCors();
|
||||||
|
if (!CollectionUtils.isEmpty(config.getAllowedOrigins()) || !CollectionUtils.isEmpty(config.getAllowedOriginPatterns())) {
|
||||||
|
LOG.debug("Registering CORS filter");
|
||||||
|
source.registerCorsConfiguration("/api/**", config);
|
||||||
|
source.registerCorsConfiguration("/management/**", config);
|
||||||
|
source.registerCorsConfiguration("/v3/api-docs", config);
|
||||||
|
source.registerCorsConfiguration("/swagger-ui/**", config);
|
||||||
|
}
|
||||||
|
return new CorsFilter(source);
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,4 @@
|
|||||||
|
/**
|
||||||
|
* Application configuration.
|
||||||
|
*/
|
||||||
|
package com.example.questionbank.config;
|
||||||
@@ -0,0 +1,77 @@
|
|||||||
|
package com.example.questionbank.domain;
|
||||||
|
|
||||||
|
import com.fasterxml.jackson.annotation.JsonIgnoreProperties;
|
||||||
|
import jakarta.persistence.Column;
|
||||||
|
import jakarta.persistence.EntityListeners;
|
||||||
|
import jakarta.persistence.MappedSuperclass;
|
||||||
|
import java.io.Serial;
|
||||||
|
import java.io.Serializable;
|
||||||
|
import java.time.Instant;
|
||||||
|
import org.springframework.data.annotation.CreatedBy;
|
||||||
|
import org.springframework.data.annotation.CreatedDate;
|
||||||
|
import org.springframework.data.annotation.LastModifiedBy;
|
||||||
|
import org.springframework.data.annotation.LastModifiedDate;
|
||||||
|
import org.springframework.data.jpa.domain.support.AuditingEntityListener;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Base abstract class for entities which will hold definitions for created, last modified, created by,
|
||||||
|
* last modified by attributes.
|
||||||
|
*/
|
||||||
|
@MappedSuperclass
|
||||||
|
@EntityListeners(AuditingEntityListener.class)
|
||||||
|
@JsonIgnoreProperties(value = { "createdBy", "createdDate", "lastModifiedBy", "lastModifiedDate" }, allowGetters = true)
|
||||||
|
public abstract class AbstractAuditingEntity<T> implements Serializable {
|
||||||
|
|
||||||
|
@Serial
|
||||||
|
private static final long serialVersionUID = 1L;
|
||||||
|
|
||||||
|
public abstract T getId();
|
||||||
|
|
||||||
|
@CreatedBy
|
||||||
|
@Column(name = "created_by", nullable = false, length = 50, updatable = false)
|
||||||
|
private String createdBy;
|
||||||
|
|
||||||
|
@CreatedDate
|
||||||
|
@Column(name = "created_date", updatable = false)
|
||||||
|
private Instant createdDate = Instant.now();
|
||||||
|
|
||||||
|
@LastModifiedBy
|
||||||
|
@Column(name = "last_modified_by", length = 50)
|
||||||
|
private String lastModifiedBy;
|
||||||
|
|
||||||
|
@LastModifiedDate
|
||||||
|
@Column(name = "last_modified_date")
|
||||||
|
private Instant lastModifiedDate = Instant.now();
|
||||||
|
|
||||||
|
public String getCreatedBy() {
|
||||||
|
return createdBy;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setCreatedBy(String createdBy) {
|
||||||
|
this.createdBy = createdBy;
|
||||||
|
}
|
||||||
|
|
||||||
|
public Instant getCreatedDate() {
|
||||||
|
return createdDate;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setCreatedDate(Instant createdDate) {
|
||||||
|
this.createdDate = createdDate;
|
||||||
|
}
|
||||||
|
|
||||||
|
public String getLastModifiedBy() {
|
||||||
|
return lastModifiedBy;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setLastModifiedBy(String lastModifiedBy) {
|
||||||
|
this.lastModifiedBy = lastModifiedBy;
|
||||||
|
}
|
||||||
|
|
||||||
|
public Instant getLastModifiedDate() {
|
||||||
|
return lastModifiedDate;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setLastModifiedDate(Instant lastModifiedDate) {
|
||||||
|
this.lastModifiedDate = lastModifiedDate;
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,96 @@
|
|||||||
|
package com.example.questionbank.domain;
|
||||||
|
|
||||||
|
import com.fasterxml.jackson.annotation.JsonIgnoreProperties;
|
||||||
|
import jakarta.persistence.*;
|
||||||
|
import jakarta.validation.constraints.*;
|
||||||
|
import java.io.Serial;
|
||||||
|
import java.io.Serializable;
|
||||||
|
import java.util.Objects;
|
||||||
|
import org.springframework.data.domain.Persistable;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* A Authority.
|
||||||
|
*/
|
||||||
|
@Entity
|
||||||
|
@Table(name = "jhi_authority")
|
||||||
|
@JsonIgnoreProperties(value = { "new", "id" })
|
||||||
|
@SuppressWarnings("common-java:DuplicatedBlocks")
|
||||||
|
public class Authority implements Serializable, Persistable<String> {
|
||||||
|
|
||||||
|
@Serial
|
||||||
|
private static final long serialVersionUID = 1L;
|
||||||
|
|
||||||
|
@NotNull
|
||||||
|
@Size(max = 50)
|
||||||
|
@Id
|
||||||
|
@Column(name = "name", length = 50, nullable = false)
|
||||||
|
private String name;
|
||||||
|
|
||||||
|
@org.springframework.data.annotation.Transient
|
||||||
|
@Transient
|
||||||
|
private boolean isPersisted;
|
||||||
|
|
||||||
|
// jhipster-needle-entity-add-field - JHipster will add fields here
|
||||||
|
|
||||||
|
public String getName() {
|
||||||
|
return this.name;
|
||||||
|
}
|
||||||
|
|
||||||
|
public Authority name(String name) {
|
||||||
|
this.setName(name);
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setName(String name) {
|
||||||
|
this.name = name;
|
||||||
|
}
|
||||||
|
|
||||||
|
@PostLoad
|
||||||
|
@PostPersist
|
||||||
|
public void updateEntityState() {
|
||||||
|
this.setIsPersisted();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public String getId() {
|
||||||
|
return this.name;
|
||||||
|
}
|
||||||
|
|
||||||
|
@org.springframework.data.annotation.Transient
|
||||||
|
@Transient
|
||||||
|
@Override
|
||||||
|
public boolean isNew() {
|
||||||
|
return !this.isPersisted;
|
||||||
|
}
|
||||||
|
|
||||||
|
public Authority setIsPersisted() {
|
||||||
|
this.isPersisted = true;
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
|
||||||
|
// jhipster-needle-entity-add-getters-setters - JHipster will add getters and setters here
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public boolean equals(Object o) {
|
||||||
|
if (this == o) {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
if (!(o instanceof Authority)) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
return getName() != null && getName().equals(((Authority) o).getName());
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public int hashCode() {
|
||||||
|
return Objects.hashCode(getName());
|
||||||
|
}
|
||||||
|
|
||||||
|
// prettier-ignore
|
||||||
|
@Override
|
||||||
|
public String toString() {
|
||||||
|
return "Authority{" +
|
||||||
|
"name=" + getName() +
|
||||||
|
"}";
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,162 @@
|
|||||||
|
package com.example.questionbank.domain;
|
||||||
|
|
||||||
|
import com.example.questionbank.domain.enumeration.OptionKey;
|
||||||
|
import com.fasterxml.jackson.annotation.JsonIgnoreProperties;
|
||||||
|
import jakarta.persistence.*;
|
||||||
|
import jakarta.validation.constraints.*;
|
||||||
|
import java.io.Serial;
|
||||||
|
import java.io.Serializable;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* A Choice.
|
||||||
|
*/
|
||||||
|
@Entity
|
||||||
|
@Table(name = "choice")
|
||||||
|
@SuppressWarnings("common-java:DuplicatedBlocks")
|
||||||
|
public class Choice implements Serializable {
|
||||||
|
|
||||||
|
@Serial
|
||||||
|
private static final long serialVersionUID = 1L;
|
||||||
|
|
||||||
|
@Id
|
||||||
|
@GeneratedValue(strategy = GenerationType.SEQUENCE, generator = "sequenceGenerator")
|
||||||
|
@SequenceGenerator(name = "sequenceGenerator")
|
||||||
|
@Column(name = "id")
|
||||||
|
private Long id;
|
||||||
|
|
||||||
|
@NotNull
|
||||||
|
@Enumerated(EnumType.STRING)
|
||||||
|
@Column(name = "option_key", nullable = false)
|
||||||
|
private OptionKey optionKey;
|
||||||
|
|
||||||
|
@NotNull
|
||||||
|
@Size(max = 500)
|
||||||
|
@Column(name = "content", length = 500, nullable = false)
|
||||||
|
private String content;
|
||||||
|
|
||||||
|
@NotNull
|
||||||
|
@Min(value = 1)
|
||||||
|
@Max(value = 4)
|
||||||
|
@Column(name = "display_order", nullable = false)
|
||||||
|
private Integer displayOrder;
|
||||||
|
|
||||||
|
@NotNull
|
||||||
|
@Column(name = "correct", nullable = false)
|
||||||
|
private Boolean correct;
|
||||||
|
|
||||||
|
@ManyToOne(optional = false)
|
||||||
|
@NotNull
|
||||||
|
@JsonIgnoreProperties(value = { "choices", "subject" }, allowSetters = true)
|
||||||
|
private Question question;
|
||||||
|
|
||||||
|
// jhipster-needle-entity-add-field - JHipster will add fields here
|
||||||
|
|
||||||
|
public Long getId() {
|
||||||
|
return this.id;
|
||||||
|
}
|
||||||
|
|
||||||
|
public Choice id(Long id) {
|
||||||
|
this.setId(id);
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setId(Long id) {
|
||||||
|
this.id = id;
|
||||||
|
}
|
||||||
|
|
||||||
|
public OptionKey getOptionKey() {
|
||||||
|
return this.optionKey;
|
||||||
|
}
|
||||||
|
|
||||||
|
public Choice optionKey(OptionKey optionKey) {
|
||||||
|
this.setOptionKey(optionKey);
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setOptionKey(OptionKey optionKey) {
|
||||||
|
this.optionKey = optionKey;
|
||||||
|
}
|
||||||
|
|
||||||
|
public String getContent() {
|
||||||
|
return this.content;
|
||||||
|
}
|
||||||
|
|
||||||
|
public Choice content(String content) {
|
||||||
|
this.setContent(content);
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setContent(String content) {
|
||||||
|
this.content = content;
|
||||||
|
}
|
||||||
|
|
||||||
|
public Integer getDisplayOrder() {
|
||||||
|
return this.displayOrder;
|
||||||
|
}
|
||||||
|
|
||||||
|
public Choice displayOrder(Integer displayOrder) {
|
||||||
|
this.setDisplayOrder(displayOrder);
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setDisplayOrder(Integer displayOrder) {
|
||||||
|
this.displayOrder = displayOrder;
|
||||||
|
}
|
||||||
|
|
||||||
|
public Boolean getCorrect() {
|
||||||
|
return this.correct;
|
||||||
|
}
|
||||||
|
|
||||||
|
public Choice correct(Boolean correct) {
|
||||||
|
this.setCorrect(correct);
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setCorrect(Boolean correct) {
|
||||||
|
this.correct = correct;
|
||||||
|
}
|
||||||
|
|
||||||
|
public Question getQuestion() {
|
||||||
|
return this.question;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setQuestion(Question question) {
|
||||||
|
this.question = question;
|
||||||
|
}
|
||||||
|
|
||||||
|
public Choice question(Question question) {
|
||||||
|
this.setQuestion(question);
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
|
||||||
|
// jhipster-needle-entity-add-getters-setters - JHipster will add getters and setters here
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public boolean equals(Object o) {
|
||||||
|
if (this == o) {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
if (!(o instanceof Choice)) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
return getId() != null && getId().equals(((Choice) o).getId());
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public int hashCode() {
|
||||||
|
// see https://vladmihalcea.com/how-to-implement-equals-and-hashcode-using-the-jpa-entity-identifier/
|
||||||
|
return getClass().hashCode();
|
||||||
|
}
|
||||||
|
|
||||||
|
// prettier-ignore
|
||||||
|
@Override
|
||||||
|
public String toString() {
|
||||||
|
return "Choice{" +
|
||||||
|
"id=" + getId() +
|
||||||
|
", optionKey='" + getOptionKey() + "'" +
|
||||||
|
", content='" + getContent() + "'" +
|
||||||
|
", displayOrder=" + getDisplayOrder() +
|
||||||
|
", correct='" + getCorrect() + "'" +
|
||||||
|
"}";
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,212 @@
|
|||||||
|
package com.example.questionbank.domain;
|
||||||
|
|
||||||
|
import com.example.questionbank.domain.enumeration.OptionKey;
|
||||||
|
import com.fasterxml.jackson.annotation.JsonIgnoreProperties;
|
||||||
|
import jakarta.persistence.*;
|
||||||
|
import jakarta.validation.constraints.*;
|
||||||
|
import java.io.Serial;
|
||||||
|
import java.io.Serializable;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* A ExamAnswer.
|
||||||
|
*/
|
||||||
|
@Entity
|
||||||
|
@Table(name = "exam_answer")
|
||||||
|
@SuppressWarnings("common-java:DuplicatedBlocks")
|
||||||
|
public class ExamAnswer implements Serializable {
|
||||||
|
|
||||||
|
@Serial
|
||||||
|
private static final long serialVersionUID = 1L;
|
||||||
|
|
||||||
|
@Id
|
||||||
|
@GeneratedValue(strategy = GenerationType.SEQUENCE, generator = "sequenceGenerator")
|
||||||
|
@SequenceGenerator(name = "sequenceGenerator")
|
||||||
|
@Column(name = "id")
|
||||||
|
private Long id;
|
||||||
|
|
||||||
|
@NotNull
|
||||||
|
@Size(max = 2000)
|
||||||
|
@Column(name = "question_text_snapshot", length = 2000, nullable = false)
|
||||||
|
private String questionTextSnapshot;
|
||||||
|
|
||||||
|
@Size(max = 2000)
|
||||||
|
@Column(name = "explanation_snapshot", length = 2000)
|
||||||
|
private String explanationSnapshot;
|
||||||
|
|
||||||
|
@Enumerated(EnumType.STRING)
|
||||||
|
@Column(name = "selected_option")
|
||||||
|
private OptionKey selectedOption;
|
||||||
|
|
||||||
|
@Size(max = 500)
|
||||||
|
@Column(name = "selected_content_snapshot", length = 500)
|
||||||
|
private String selectedContentSnapshot;
|
||||||
|
|
||||||
|
@Column(name = "correct")
|
||||||
|
private Boolean correct;
|
||||||
|
|
||||||
|
@Min(value = 0)
|
||||||
|
@Column(name = "awarded_points")
|
||||||
|
private Integer awardedPoints;
|
||||||
|
|
||||||
|
@ManyToOne(optional = false)
|
||||||
|
@NotNull
|
||||||
|
@JsonIgnoreProperties(value = { "choices", "subject" }, allowSetters = true)
|
||||||
|
private Question question;
|
||||||
|
|
||||||
|
@ManyToOne(optional = false)
|
||||||
|
@NotNull
|
||||||
|
@JsonIgnoreProperties(value = { "answers", "subject", "user" }, allowSetters = true)
|
||||||
|
private ExamAttempt attempt;
|
||||||
|
|
||||||
|
// jhipster-needle-entity-add-field - JHipster will add fields here
|
||||||
|
|
||||||
|
public Long getId() {
|
||||||
|
return this.id;
|
||||||
|
}
|
||||||
|
|
||||||
|
public ExamAnswer id(Long id) {
|
||||||
|
this.setId(id);
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setId(Long id) {
|
||||||
|
this.id = id;
|
||||||
|
}
|
||||||
|
|
||||||
|
public String getQuestionTextSnapshot() {
|
||||||
|
return this.questionTextSnapshot;
|
||||||
|
}
|
||||||
|
|
||||||
|
public ExamAnswer questionTextSnapshot(String questionTextSnapshot) {
|
||||||
|
this.setQuestionTextSnapshot(questionTextSnapshot);
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setQuestionTextSnapshot(String questionTextSnapshot) {
|
||||||
|
this.questionTextSnapshot = questionTextSnapshot;
|
||||||
|
}
|
||||||
|
|
||||||
|
public String getExplanationSnapshot() {
|
||||||
|
return this.explanationSnapshot;
|
||||||
|
}
|
||||||
|
|
||||||
|
public ExamAnswer explanationSnapshot(String explanationSnapshot) {
|
||||||
|
this.setExplanationSnapshot(explanationSnapshot);
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setExplanationSnapshot(String explanationSnapshot) {
|
||||||
|
this.explanationSnapshot = explanationSnapshot;
|
||||||
|
}
|
||||||
|
|
||||||
|
public OptionKey getSelectedOption() {
|
||||||
|
return this.selectedOption;
|
||||||
|
}
|
||||||
|
|
||||||
|
public ExamAnswer selectedOption(OptionKey selectedOption) {
|
||||||
|
this.setSelectedOption(selectedOption);
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setSelectedOption(OptionKey selectedOption) {
|
||||||
|
this.selectedOption = selectedOption;
|
||||||
|
}
|
||||||
|
|
||||||
|
public String getSelectedContentSnapshot() {
|
||||||
|
return this.selectedContentSnapshot;
|
||||||
|
}
|
||||||
|
|
||||||
|
public ExamAnswer selectedContentSnapshot(String selectedContentSnapshot) {
|
||||||
|
this.setSelectedContentSnapshot(selectedContentSnapshot);
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setSelectedContentSnapshot(String selectedContentSnapshot) {
|
||||||
|
this.selectedContentSnapshot = selectedContentSnapshot;
|
||||||
|
}
|
||||||
|
|
||||||
|
public Boolean getCorrect() {
|
||||||
|
return this.correct;
|
||||||
|
}
|
||||||
|
|
||||||
|
public ExamAnswer correct(Boolean correct) {
|
||||||
|
this.setCorrect(correct);
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setCorrect(Boolean correct) {
|
||||||
|
this.correct = correct;
|
||||||
|
}
|
||||||
|
|
||||||
|
public Integer getAwardedPoints() {
|
||||||
|
return this.awardedPoints;
|
||||||
|
}
|
||||||
|
|
||||||
|
public ExamAnswer awardedPoints(Integer awardedPoints) {
|
||||||
|
this.setAwardedPoints(awardedPoints);
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setAwardedPoints(Integer awardedPoints) {
|
||||||
|
this.awardedPoints = awardedPoints;
|
||||||
|
}
|
||||||
|
|
||||||
|
public Question getQuestion() {
|
||||||
|
return this.question;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setQuestion(Question question) {
|
||||||
|
this.question = question;
|
||||||
|
}
|
||||||
|
|
||||||
|
public ExamAnswer question(Question question) {
|
||||||
|
this.setQuestion(question);
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
|
||||||
|
public ExamAttempt getAttempt() {
|
||||||
|
return this.attempt;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setAttempt(ExamAttempt examAttempt) {
|
||||||
|
this.attempt = examAttempt;
|
||||||
|
}
|
||||||
|
|
||||||
|
public ExamAnswer attempt(ExamAttempt examAttempt) {
|
||||||
|
this.setAttempt(examAttempt);
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
|
||||||
|
// jhipster-needle-entity-add-getters-setters - JHipster will add getters and setters here
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public boolean equals(Object o) {
|
||||||
|
if (this == o) {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
if (!(o instanceof ExamAnswer)) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
return getId() != null && getId().equals(((ExamAnswer) o).getId());
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public int hashCode() {
|
||||||
|
// see https://vladmihalcea.com/how-to-implement-equals-and-hashcode-using-the-jpa-entity-identifier/
|
||||||
|
return getClass().hashCode();
|
||||||
|
}
|
||||||
|
|
||||||
|
// prettier-ignore
|
||||||
|
@Override
|
||||||
|
public String toString() {
|
||||||
|
return "ExamAnswer{" +
|
||||||
|
"id=" + getId() +
|
||||||
|
", questionTextSnapshot='" + getQuestionTextSnapshot() + "'" +
|
||||||
|
", explanationSnapshot='" + getExplanationSnapshot() + "'" +
|
||||||
|
", selectedOption='" + getSelectedOption() + "'" +
|
||||||
|
", selectedContentSnapshot='" + getSelectedContentSnapshot() + "'" +
|
||||||
|
", correct='" + getCorrect() + "'" +
|
||||||
|
", awardedPoints=" + getAwardedPoints() +
|
||||||
|
"}";
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,212 @@
|
|||||||
|
package com.example.questionbank.domain;
|
||||||
|
|
||||||
|
import com.example.questionbank.domain.enumeration.AttemptStatus;
|
||||||
|
import com.fasterxml.jackson.annotation.JsonIgnoreProperties;
|
||||||
|
import jakarta.persistence.*;
|
||||||
|
import jakarta.validation.constraints.*;
|
||||||
|
import java.io.Serial;
|
||||||
|
import java.io.Serializable;
|
||||||
|
import java.time.Instant;
|
||||||
|
import java.util.HashSet;
|
||||||
|
import java.util.Set;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* A ExamAttempt.
|
||||||
|
*/
|
||||||
|
@Entity
|
||||||
|
@Table(name = "exam_attempt")
|
||||||
|
@SuppressWarnings("common-java:DuplicatedBlocks")
|
||||||
|
public class ExamAttempt implements Serializable {
|
||||||
|
|
||||||
|
@Serial
|
||||||
|
private static final long serialVersionUID = 1L;
|
||||||
|
|
||||||
|
@Id
|
||||||
|
@GeneratedValue(strategy = GenerationType.SEQUENCE, generator = "sequenceGenerator")
|
||||||
|
@SequenceGenerator(name = "sequenceGenerator")
|
||||||
|
@Column(name = "id")
|
||||||
|
private Long id;
|
||||||
|
|
||||||
|
@NotNull
|
||||||
|
@Column(name = "started_at", nullable = false)
|
||||||
|
private Instant startedAt;
|
||||||
|
|
||||||
|
@Column(name = "submitted_at")
|
||||||
|
private Instant submittedAt;
|
||||||
|
|
||||||
|
@NotNull
|
||||||
|
@Enumerated(EnumType.STRING)
|
||||||
|
@Column(name = "status", nullable = false)
|
||||||
|
private AttemptStatus status;
|
||||||
|
|
||||||
|
@Min(value = 0)
|
||||||
|
@Column(name = "score")
|
||||||
|
private Integer score;
|
||||||
|
|
||||||
|
@OneToMany(fetch = FetchType.LAZY, mappedBy = "attempt")
|
||||||
|
@JsonIgnoreProperties(value = { "question", "attempt" }, allowSetters = true)
|
||||||
|
private Set<ExamAnswer> answers = new HashSet<>();
|
||||||
|
|
||||||
|
@ManyToOne(optional = false)
|
||||||
|
@NotNull
|
||||||
|
private Subject subject;
|
||||||
|
|
||||||
|
@ManyToOne(optional = false)
|
||||||
|
@NotNull
|
||||||
|
private User user;
|
||||||
|
|
||||||
|
// jhipster-needle-entity-add-field - JHipster will add fields here
|
||||||
|
|
||||||
|
public Long getId() {
|
||||||
|
return this.id;
|
||||||
|
}
|
||||||
|
|
||||||
|
public ExamAttempt id(Long id) {
|
||||||
|
this.setId(id);
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setId(Long id) {
|
||||||
|
this.id = id;
|
||||||
|
}
|
||||||
|
|
||||||
|
public Instant getStartedAt() {
|
||||||
|
return this.startedAt;
|
||||||
|
}
|
||||||
|
|
||||||
|
public ExamAttempt startedAt(Instant startedAt) {
|
||||||
|
this.setStartedAt(startedAt);
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setStartedAt(Instant startedAt) {
|
||||||
|
this.startedAt = startedAt;
|
||||||
|
}
|
||||||
|
|
||||||
|
public Instant getSubmittedAt() {
|
||||||
|
return this.submittedAt;
|
||||||
|
}
|
||||||
|
|
||||||
|
public ExamAttempt submittedAt(Instant submittedAt) {
|
||||||
|
this.setSubmittedAt(submittedAt);
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setSubmittedAt(Instant submittedAt) {
|
||||||
|
this.submittedAt = submittedAt;
|
||||||
|
}
|
||||||
|
|
||||||
|
public AttemptStatus getStatus() {
|
||||||
|
return this.status;
|
||||||
|
}
|
||||||
|
|
||||||
|
public ExamAttempt status(AttemptStatus status) {
|
||||||
|
this.setStatus(status);
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setStatus(AttemptStatus status) {
|
||||||
|
this.status = status;
|
||||||
|
}
|
||||||
|
|
||||||
|
public Integer getScore() {
|
||||||
|
return this.score;
|
||||||
|
}
|
||||||
|
|
||||||
|
public ExamAttempt score(Integer score) {
|
||||||
|
this.setScore(score);
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setScore(Integer score) {
|
||||||
|
this.score = score;
|
||||||
|
}
|
||||||
|
|
||||||
|
public Set<ExamAnswer> getAnswers() {
|
||||||
|
return this.answers;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setAnswers(Set<ExamAnswer> examAnswers) {
|
||||||
|
if (this.answers != null) {
|
||||||
|
this.answers.forEach(i -> i.setAttempt(null));
|
||||||
|
}
|
||||||
|
if (examAnswers != null) {
|
||||||
|
examAnswers.forEach(i -> i.setAttempt(this));
|
||||||
|
}
|
||||||
|
this.answers = examAnswers;
|
||||||
|
}
|
||||||
|
|
||||||
|
public ExamAttempt answers(Set<ExamAnswer> examAnswers) {
|
||||||
|
this.setAnswers(examAnswers);
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
|
||||||
|
public ExamAttempt addAnswer(ExamAnswer examAnswer) {
|
||||||
|
this.answers.add(examAnswer);
|
||||||
|
examAnswer.setAttempt(this);
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
|
||||||
|
public ExamAttempt removeAnswer(ExamAnswer examAnswer) {
|
||||||
|
this.answers.remove(examAnswer);
|
||||||
|
examAnswer.setAttempt(null);
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
|
||||||
|
public Subject getSubject() {
|
||||||
|
return this.subject;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setSubject(Subject subject) {
|
||||||
|
this.subject = subject;
|
||||||
|
}
|
||||||
|
|
||||||
|
public ExamAttempt subject(Subject subject) {
|
||||||
|
this.setSubject(subject);
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
|
||||||
|
public User getUser() {
|
||||||
|
return this.user;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setUser(User user) {
|
||||||
|
this.user = user;
|
||||||
|
}
|
||||||
|
|
||||||
|
public ExamAttempt user(User user) {
|
||||||
|
this.setUser(user);
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
|
||||||
|
// jhipster-needle-entity-add-getters-setters - JHipster will add getters and setters here
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public boolean equals(Object o) {
|
||||||
|
if (this == o) {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
if (!(o instanceof ExamAttempt)) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
return getId() != null && getId().equals(((ExamAttempt) o).getId());
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public int hashCode() {
|
||||||
|
// see https://vladmihalcea.com/how-to-implement-equals-and-hashcode-using-the-jpa-entity-identifier/
|
||||||
|
return getClass().hashCode();
|
||||||
|
}
|
||||||
|
|
||||||
|
// prettier-ignore
|
||||||
|
@Override
|
||||||
|
public String toString() {
|
||||||
|
return "ExamAttempt{" +
|
||||||
|
"id=" + getId() +
|
||||||
|
", startedAt='" + getStartedAt() + "'" +
|
||||||
|
", submittedAt='" + getSubmittedAt() + "'" +
|
||||||
|
", status='" + getStatus() + "'" +
|
||||||
|
", score=" + getScore() +
|
||||||
|
"}";
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,176 @@
|
|||||||
|
package com.example.questionbank.domain;
|
||||||
|
|
||||||
|
import com.fasterxml.jackson.annotation.JsonIgnoreProperties;
|
||||||
|
import jakarta.persistence.*;
|
||||||
|
import jakarta.validation.constraints.*;
|
||||||
|
import java.io.Serial;
|
||||||
|
import java.io.Serializable;
|
||||||
|
import java.util.HashSet;
|
||||||
|
import java.util.Set;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* A Question.
|
||||||
|
*/
|
||||||
|
@Entity
|
||||||
|
@Table(name = "question")
|
||||||
|
@SuppressWarnings("common-java:DuplicatedBlocks")
|
||||||
|
public class Question implements Serializable {
|
||||||
|
|
||||||
|
@Serial
|
||||||
|
private static final long serialVersionUID = 1L;
|
||||||
|
|
||||||
|
@Id
|
||||||
|
@GeneratedValue(strategy = GenerationType.SEQUENCE, generator = "sequenceGenerator")
|
||||||
|
@SequenceGenerator(name = "sequenceGenerator")
|
||||||
|
@Column(name = "id")
|
||||||
|
private Long id;
|
||||||
|
|
||||||
|
@NotNull
|
||||||
|
@Size(min = 5, max = 2000)
|
||||||
|
@Column(name = "prompt", length = 2000, nullable = false)
|
||||||
|
private String prompt;
|
||||||
|
|
||||||
|
@Size(max = 2000)
|
||||||
|
@Column(name = "explanation", length = 2000)
|
||||||
|
private String explanation;
|
||||||
|
|
||||||
|
@NotNull
|
||||||
|
@Column(name = "active", nullable = false)
|
||||||
|
private Boolean active;
|
||||||
|
|
||||||
|
@OneToMany(fetch = FetchType.LAZY, mappedBy = "question")
|
||||||
|
@JsonIgnoreProperties(value = { "question" }, allowSetters = true)
|
||||||
|
private Set<Choice> choices = new HashSet<>();
|
||||||
|
|
||||||
|
@ManyToOne(optional = false)
|
||||||
|
@NotNull
|
||||||
|
private Subject subject;
|
||||||
|
|
||||||
|
// jhipster-needle-entity-add-field - JHipster will add fields here
|
||||||
|
|
||||||
|
public Long getId() {
|
||||||
|
return this.id;
|
||||||
|
}
|
||||||
|
|
||||||
|
public Question id(Long id) {
|
||||||
|
this.setId(id);
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setId(Long id) {
|
||||||
|
this.id = id;
|
||||||
|
}
|
||||||
|
|
||||||
|
public String getPrompt() {
|
||||||
|
return this.prompt;
|
||||||
|
}
|
||||||
|
|
||||||
|
public Question prompt(String prompt) {
|
||||||
|
this.setPrompt(prompt);
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setPrompt(String prompt) {
|
||||||
|
this.prompt = prompt;
|
||||||
|
}
|
||||||
|
|
||||||
|
public String getExplanation() {
|
||||||
|
return this.explanation;
|
||||||
|
}
|
||||||
|
|
||||||
|
public Question explanation(String explanation) {
|
||||||
|
this.setExplanation(explanation);
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setExplanation(String explanation) {
|
||||||
|
this.explanation = explanation;
|
||||||
|
}
|
||||||
|
|
||||||
|
public Boolean getActive() {
|
||||||
|
return this.active;
|
||||||
|
}
|
||||||
|
|
||||||
|
public Question active(Boolean active) {
|
||||||
|
this.setActive(active);
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setActive(Boolean active) {
|
||||||
|
this.active = active;
|
||||||
|
}
|
||||||
|
|
||||||
|
public Set<Choice> getChoices() {
|
||||||
|
return this.choices;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setChoices(Set<Choice> choices) {
|
||||||
|
if (this.choices != null) {
|
||||||
|
this.choices.forEach(i -> i.setQuestion(null));
|
||||||
|
}
|
||||||
|
if (choices != null) {
|
||||||
|
choices.forEach(i -> i.setQuestion(this));
|
||||||
|
}
|
||||||
|
this.choices = choices;
|
||||||
|
}
|
||||||
|
|
||||||
|
public Question choices(Set<Choice> choices) {
|
||||||
|
this.setChoices(choices);
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
|
||||||
|
public Question addChoice(Choice choice) {
|
||||||
|
this.choices.add(choice);
|
||||||
|
choice.setQuestion(this);
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
|
||||||
|
public Question removeChoice(Choice choice) {
|
||||||
|
this.choices.remove(choice);
|
||||||
|
choice.setQuestion(null);
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
|
||||||
|
public Subject getSubject() {
|
||||||
|
return this.subject;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setSubject(Subject subject) {
|
||||||
|
this.subject = subject;
|
||||||
|
}
|
||||||
|
|
||||||
|
public Question subject(Subject subject) {
|
||||||
|
this.setSubject(subject);
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
|
||||||
|
// jhipster-needle-entity-add-getters-setters - JHipster will add getters and setters here
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public boolean equals(Object o) {
|
||||||
|
if (this == o) {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
if (!(o instanceof Question)) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
return getId() != null && getId().equals(((Question) o).getId());
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public int hashCode() {
|
||||||
|
// see https://vladmihalcea.com/how-to-implement-equals-and-hashcode-using-the-jpa-entity-identifier/
|
||||||
|
return getClass().hashCode();
|
||||||
|
}
|
||||||
|
|
||||||
|
// prettier-ignore
|
||||||
|
@Override
|
||||||
|
public String toString() {
|
||||||
|
return "Question{" +
|
||||||
|
"id=" + getId() +
|
||||||
|
", prompt='" + getPrompt() + "'" +
|
||||||
|
", explanation='" + getExplanation() + "'" +
|
||||||
|
", active='" + getActive() + "'" +
|
||||||
|
"}";
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,121 @@
|
|||||||
|
package com.example.questionbank.domain;
|
||||||
|
|
||||||
|
import jakarta.persistence.*;
|
||||||
|
import jakarta.validation.constraints.*;
|
||||||
|
import java.io.Serial;
|
||||||
|
import java.io.Serializable;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* A Subject.
|
||||||
|
*/
|
||||||
|
@Entity
|
||||||
|
@Table(name = "subject")
|
||||||
|
@SuppressWarnings("common-java:DuplicatedBlocks")
|
||||||
|
public class Subject implements Serializable {
|
||||||
|
|
||||||
|
@Serial
|
||||||
|
private static final long serialVersionUID = 1L;
|
||||||
|
|
||||||
|
@Id
|
||||||
|
@GeneratedValue(strategy = GenerationType.SEQUENCE, generator = "sequenceGenerator")
|
||||||
|
@SequenceGenerator(name = "sequenceGenerator")
|
||||||
|
@Column(name = "id")
|
||||||
|
private Long id;
|
||||||
|
|
||||||
|
@NotNull
|
||||||
|
@Size(min = 2, max = 100)
|
||||||
|
@Column(name = "name", length = 100, nullable = false)
|
||||||
|
private String name;
|
||||||
|
|
||||||
|
@Size(max = 1000)
|
||||||
|
@Column(name = "description", length = 1000)
|
||||||
|
private String description;
|
||||||
|
|
||||||
|
@NotNull
|
||||||
|
@Column(name = "active", nullable = false)
|
||||||
|
private Boolean active;
|
||||||
|
|
||||||
|
// jhipster-needle-entity-add-field - JHipster will add fields here
|
||||||
|
|
||||||
|
public Long getId() {
|
||||||
|
return this.id;
|
||||||
|
}
|
||||||
|
|
||||||
|
public Subject id(Long id) {
|
||||||
|
this.setId(id);
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setId(Long id) {
|
||||||
|
this.id = id;
|
||||||
|
}
|
||||||
|
|
||||||
|
public String getName() {
|
||||||
|
return this.name;
|
||||||
|
}
|
||||||
|
|
||||||
|
public Subject name(String name) {
|
||||||
|
this.setName(name);
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setName(String name) {
|
||||||
|
this.name = name;
|
||||||
|
}
|
||||||
|
|
||||||
|
public String getDescription() {
|
||||||
|
return this.description;
|
||||||
|
}
|
||||||
|
|
||||||
|
public Subject description(String description) {
|
||||||
|
this.setDescription(description);
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setDescription(String description) {
|
||||||
|
this.description = description;
|
||||||
|
}
|
||||||
|
|
||||||
|
public Boolean getActive() {
|
||||||
|
return this.active;
|
||||||
|
}
|
||||||
|
|
||||||
|
public Subject active(Boolean active) {
|
||||||
|
this.setActive(active);
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setActive(Boolean active) {
|
||||||
|
this.active = active;
|
||||||
|
}
|
||||||
|
|
||||||
|
// jhipster-needle-entity-add-getters-setters - JHipster will add getters and setters here
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public boolean equals(Object o) {
|
||||||
|
if (this == o) {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
if (!(o instanceof Subject)) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
return getId() != null && getId().equals(((Subject) o).getId());
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public int hashCode() {
|
||||||
|
// see https://vladmihalcea.com/how-to-implement-equals-and-hashcode-using-the-jpa-entity-identifier/
|
||||||
|
return getClass().hashCode();
|
||||||
|
}
|
||||||
|
|
||||||
|
// prettier-ignore
|
||||||
|
@Override
|
||||||
|
public String toString() {
|
||||||
|
return "Subject{" +
|
||||||
|
"id=" + getId() +
|
||||||
|
", name='" + getName() + "'" +
|
||||||
|
", description='" + getDescription() + "'" +
|
||||||
|
", active='" + getActive() + "'" +
|
||||||
|
"}";
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,231 @@
|
|||||||
|
package com.example.questionbank.domain;
|
||||||
|
|
||||||
|
import com.example.questionbank.config.Constants;
|
||||||
|
import com.fasterxml.jackson.annotation.JsonIgnore;
|
||||||
|
import jakarta.persistence.*;
|
||||||
|
import jakarta.validation.constraints.Email;
|
||||||
|
import jakarta.validation.constraints.NotNull;
|
||||||
|
import jakarta.validation.constraints.Pattern;
|
||||||
|
import jakarta.validation.constraints.Size;
|
||||||
|
import java.io.Serial;
|
||||||
|
import java.io.Serializable;
|
||||||
|
import java.time.Instant;
|
||||||
|
import java.util.HashSet;
|
||||||
|
import java.util.Locale;
|
||||||
|
import java.util.Set;
|
||||||
|
import org.apache.commons.lang3.StringUtils;
|
||||||
|
import org.hibernate.annotations.BatchSize;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* A user.
|
||||||
|
*/
|
||||||
|
@Entity
|
||||||
|
@Table(name = "jhi_user")
|
||||||
|
public class User extends AbstractAuditingEntity<Long> implements Serializable {
|
||||||
|
|
||||||
|
@Serial
|
||||||
|
private static final long serialVersionUID = 1L;
|
||||||
|
|
||||||
|
@Id
|
||||||
|
@GeneratedValue(strategy = GenerationType.SEQUENCE, generator = "sequenceGenerator")
|
||||||
|
@SequenceGenerator(name = "sequenceGenerator")
|
||||||
|
@Column(name = "id")
|
||||||
|
private Long id;
|
||||||
|
|
||||||
|
@NotNull
|
||||||
|
@Pattern(regexp = Constants.LOGIN_REGEX)
|
||||||
|
@Size(min = 1, max = 50)
|
||||||
|
@Column(name = "login", length = 50, unique = true, nullable = false)
|
||||||
|
private String login;
|
||||||
|
|
||||||
|
@JsonIgnore
|
||||||
|
@NotNull
|
||||||
|
@Size(min = 60, max = 60)
|
||||||
|
@Column(name = "password_hash", length = 60, nullable = false)
|
||||||
|
private String password;
|
||||||
|
|
||||||
|
@Size(max = 50)
|
||||||
|
@Column(name = "first_name", length = 50)
|
||||||
|
private String firstName;
|
||||||
|
|
||||||
|
@Size(max = 50)
|
||||||
|
@Column(name = "last_name", length = 50)
|
||||||
|
private String lastName;
|
||||||
|
|
||||||
|
@Email
|
||||||
|
@Size(min = 5, max = 254)
|
||||||
|
@Column(name = "email", length = 254, unique = true)
|
||||||
|
private String email;
|
||||||
|
|
||||||
|
@NotNull
|
||||||
|
@Column(name = "activated", nullable = false)
|
||||||
|
private boolean activated = false;
|
||||||
|
|
||||||
|
@Size(min = 2, max = 10)
|
||||||
|
@Column(name = "lang_key", length = 10)
|
||||||
|
private String langKey;
|
||||||
|
|
||||||
|
@Size(max = 256)
|
||||||
|
@Column(name = "image_url", length = 256)
|
||||||
|
private String imageUrl;
|
||||||
|
|
||||||
|
@Size(max = 20)
|
||||||
|
@Column(name = "activation_key", length = 20)
|
||||||
|
@JsonIgnore
|
||||||
|
private String activationKey;
|
||||||
|
|
||||||
|
@Size(max = 20)
|
||||||
|
@Column(name = "reset_key", length = 20)
|
||||||
|
@JsonIgnore
|
||||||
|
private String resetKey;
|
||||||
|
|
||||||
|
@Column(name = "reset_date")
|
||||||
|
private Instant resetDate = null;
|
||||||
|
|
||||||
|
@JsonIgnore
|
||||||
|
@ManyToMany
|
||||||
|
@JoinTable(
|
||||||
|
name = "jhi_user_authority",
|
||||||
|
joinColumns = { @JoinColumn(name = "user_id", referencedColumnName = "id") },
|
||||||
|
inverseJoinColumns = { @JoinColumn(name = "authority_name", referencedColumnName = "name") }
|
||||||
|
)
|
||||||
|
@BatchSize(size = 20)
|
||||||
|
private Set<Authority> authorities = new HashSet<>();
|
||||||
|
|
||||||
|
public Long getId() {
|
||||||
|
return id;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setId(Long id) {
|
||||||
|
this.id = id;
|
||||||
|
}
|
||||||
|
|
||||||
|
public String getLogin() {
|
||||||
|
return login;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Lowercase the login before saving it in database
|
||||||
|
public void setLogin(String login) {
|
||||||
|
this.login = StringUtils.lowerCase(login, Locale.ENGLISH);
|
||||||
|
}
|
||||||
|
|
||||||
|
public String getPassword() {
|
||||||
|
return password;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setPassword(String password) {
|
||||||
|
this.password = password;
|
||||||
|
}
|
||||||
|
|
||||||
|
public String getFirstName() {
|
||||||
|
return firstName;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setFirstName(String firstName) {
|
||||||
|
this.firstName = firstName;
|
||||||
|
}
|
||||||
|
|
||||||
|
public String getLastName() {
|
||||||
|
return lastName;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setLastName(String lastName) {
|
||||||
|
this.lastName = lastName;
|
||||||
|
}
|
||||||
|
|
||||||
|
public String getEmail() {
|
||||||
|
return email;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setEmail(String email) {
|
||||||
|
this.email = email;
|
||||||
|
}
|
||||||
|
|
||||||
|
public String getImageUrl() {
|
||||||
|
return imageUrl;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setImageUrl(String imageUrl) {
|
||||||
|
this.imageUrl = imageUrl;
|
||||||
|
}
|
||||||
|
|
||||||
|
public boolean isActivated() {
|
||||||
|
return activated;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setActivated(boolean activated) {
|
||||||
|
this.activated = activated;
|
||||||
|
}
|
||||||
|
|
||||||
|
public String getActivationKey() {
|
||||||
|
return activationKey;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setActivationKey(String activationKey) {
|
||||||
|
this.activationKey = activationKey;
|
||||||
|
}
|
||||||
|
|
||||||
|
public String getResetKey() {
|
||||||
|
return resetKey;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setResetKey(String resetKey) {
|
||||||
|
this.resetKey = resetKey;
|
||||||
|
}
|
||||||
|
|
||||||
|
public Instant getResetDate() {
|
||||||
|
return resetDate;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setResetDate(Instant resetDate) {
|
||||||
|
this.resetDate = resetDate;
|
||||||
|
}
|
||||||
|
|
||||||
|
public String getLangKey() {
|
||||||
|
return langKey;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setLangKey(String langKey) {
|
||||||
|
this.langKey = langKey;
|
||||||
|
}
|
||||||
|
|
||||||
|
public Set<Authority> getAuthorities() {
|
||||||
|
return authorities;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setAuthorities(Set<Authority> authorities) {
|
||||||
|
this.authorities = authorities;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public boolean equals(Object o) {
|
||||||
|
if (this == o) {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
if (!(o instanceof User)) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
return id != null && id.equals(((User) o).id);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public int hashCode() {
|
||||||
|
// see https://vladmihalcea.com/how-to-implement-equals-and-hashcode-using-the-jpa-entity-identifier/
|
||||||
|
return getClass().hashCode();
|
||||||
|
}
|
||||||
|
|
||||||
|
// prettier-ignore
|
||||||
|
@Override
|
||||||
|
public String toString() {
|
||||||
|
return "User{" +
|
||||||
|
"login='" + login + '\'' +
|
||||||
|
", firstName='" + firstName + '\'' +
|
||||||
|
", lastName='" + lastName + '\'' +
|
||||||
|
", email='" + email + '\'' +
|
||||||
|
", imageUrl='" + imageUrl + '\'' +
|
||||||
|
", activated='" + activated + '\'' +
|
||||||
|
", langKey='" + langKey + '\'' +
|
||||||
|
", activationKey='" + activationKey + '\'' +
|
||||||
|
"}";
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,9 @@
|
|||||||
|
package com.example.questionbank.domain.enumeration;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The AttemptStatus enumeration.
|
||||||
|
*/
|
||||||
|
public enum AttemptStatus {
|
||||||
|
IN_PROGRESS,
|
||||||
|
SUBMITTED,
|
||||||
|
}
|
||||||
@@ -0,0 +1,11 @@
|
|||||||
|
package com.example.questionbank.domain.enumeration;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The OptionKey enumeration.
|
||||||
|
*/
|
||||||
|
public enum OptionKey {
|
||||||
|
A,
|
||||||
|
B,
|
||||||
|
C,
|
||||||
|
D,
|
||||||
|
}
|
||||||
@@ -0,0 +1,4 @@
|
|||||||
|
/**
|
||||||
|
* This package file was generated by JHipster
|
||||||
|
*/
|
||||||
|
package com.example.questionbank.domain.enumeration;
|
||||||
@@ -0,0 +1,4 @@
|
|||||||
|
/**
|
||||||
|
* Domain objects.
|
||||||
|
*/
|
||||||
|
package com.example.questionbank.domain;
|
||||||
@@ -0,0 +1,50 @@
|
|||||||
|
package com.example.questionbank.management;
|
||||||
|
|
||||||
|
import io.micrometer.core.instrument.Counter;
|
||||||
|
import io.micrometer.core.instrument.MeterRegistry;
|
||||||
|
import org.springframework.stereotype.Service;
|
||||||
|
|
||||||
|
@Service
|
||||||
|
public class SecurityMetersService {
|
||||||
|
|
||||||
|
public static final String INVALID_TOKENS_METER_NAME = "security.authentication.invalid-tokens";
|
||||||
|
public static final String INVALID_TOKENS_METER_DESCRIPTION =
|
||||||
|
"Indicates validation error count of the tokens presented by the clients.";
|
||||||
|
public static final String INVALID_TOKENS_METER_BASE_UNIT = "errors";
|
||||||
|
public static final String INVALID_TOKENS_METER_CAUSE_DIMENSION = "cause";
|
||||||
|
|
||||||
|
private final Counter tokenInvalidSignatureCounter;
|
||||||
|
private final Counter tokenExpiredCounter;
|
||||||
|
private final Counter tokenUnsupportedCounter;
|
||||||
|
private final Counter tokenMalformedCounter;
|
||||||
|
|
||||||
|
public SecurityMetersService(MeterRegistry registry) {
|
||||||
|
this.tokenInvalidSignatureCounter = invalidTokensCounterForCauseBuilder("invalid-signature").register(registry);
|
||||||
|
this.tokenExpiredCounter = invalidTokensCounterForCauseBuilder("expired").register(registry);
|
||||||
|
this.tokenUnsupportedCounter = invalidTokensCounterForCauseBuilder("unsupported").register(registry);
|
||||||
|
this.tokenMalformedCounter = invalidTokensCounterForCauseBuilder("malformed").register(registry);
|
||||||
|
}
|
||||||
|
|
||||||
|
private Counter.Builder invalidTokensCounterForCauseBuilder(String cause) {
|
||||||
|
return Counter.builder(INVALID_TOKENS_METER_NAME)
|
||||||
|
.baseUnit(INVALID_TOKENS_METER_BASE_UNIT)
|
||||||
|
.description(INVALID_TOKENS_METER_DESCRIPTION)
|
||||||
|
.tag(INVALID_TOKENS_METER_CAUSE_DIMENSION, cause);
|
||||||
|
}
|
||||||
|
|
||||||
|
public void trackTokenInvalidSignature() {
|
||||||
|
this.tokenInvalidSignatureCounter.increment();
|
||||||
|
}
|
||||||
|
|
||||||
|
public void trackTokenExpired() {
|
||||||
|
this.tokenExpiredCounter.increment();
|
||||||
|
}
|
||||||
|
|
||||||
|
public void trackTokenUnsupported() {
|
||||||
|
this.tokenUnsupportedCounter.increment();
|
||||||
|
}
|
||||||
|
|
||||||
|
public void trackTokenMalformed() {
|
||||||
|
this.tokenMalformedCounter.increment();
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,4 @@
|
|||||||
|
/**
|
||||||
|
* Application management.
|
||||||
|
*/
|
||||||
|
package com.example.questionbank.management;
|
||||||
@@ -0,0 +1,4 @@
|
|||||||
|
/**
|
||||||
|
* Application root.
|
||||||
|
*/
|
||||||
|
package com.example.questionbank;
|
||||||
@@ -0,0 +1,12 @@
|
|||||||
|
package com.example.questionbank.repository;
|
||||||
|
|
||||||
|
import com.example.questionbank.domain.Authority;
|
||||||
|
import org.springframework.data.jpa.repository.*;
|
||||||
|
import org.springframework.stereotype.Repository;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Spring Data JPA repository for the Authority entity.
|
||||||
|
*/
|
||||||
|
@SuppressWarnings("unused")
|
||||||
|
@Repository
|
||||||
|
public interface AuthorityRepository extends JpaRepository<Authority, String> {}
|
||||||
@@ -0,0 +1,12 @@
|
|||||||
|
package com.example.questionbank.repository;
|
||||||
|
|
||||||
|
import com.example.questionbank.domain.Choice;
|
||||||
|
import org.springframework.data.jpa.repository.*;
|
||||||
|
import org.springframework.stereotype.Repository;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Spring Data JPA repository for the Choice entity.
|
||||||
|
*/
|
||||||
|
@SuppressWarnings("unused")
|
||||||
|
@Repository
|
||||||
|
public interface ChoiceRepository extends JpaRepository<Choice, Long> {}
|
||||||
@@ -0,0 +1,12 @@
|
|||||||
|
package com.example.questionbank.repository;
|
||||||
|
|
||||||
|
import com.example.questionbank.domain.ExamAnswer;
|
||||||
|
import org.springframework.data.jpa.repository.*;
|
||||||
|
import org.springframework.stereotype.Repository;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Spring Data JPA repository for the ExamAnswer entity.
|
||||||
|
*/
|
||||||
|
@SuppressWarnings("unused")
|
||||||
|
@Repository
|
||||||
|
public interface ExamAnswerRepository extends JpaRepository<ExamAnswer, Long> {}
|
||||||
@@ -0,0 +1,48 @@
|
|||||||
|
package com.example.questionbank.repository;
|
||||||
|
|
||||||
|
import com.example.questionbank.domain.ExamAttempt;
|
||||||
|
import java.util.List;
|
||||||
|
import java.util.List;
|
||||||
|
import java.util.Optional;
|
||||||
|
import org.springframework.data.domain.Page;
|
||||||
|
import org.springframework.data.domain.Pageable;
|
||||||
|
import org.springframework.data.jpa.repository.*;
|
||||||
|
import org.springframework.data.repository.query.Param;
|
||||||
|
import org.springframework.stereotype.Repository;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Spring Data JPA repository for the ExamAttempt entity.
|
||||||
|
*/
|
||||||
|
@Repository
|
||||||
|
public interface ExamAttemptRepository extends JpaRepository<ExamAttempt, Long> {
|
||||||
|
List<ExamAttempt> findAllByUserLogin(String login);
|
||||||
|
|
||||||
|
@Query("select examAttempt from ExamAttempt examAttempt where examAttempt.user.login = ?#{authentication.name}")
|
||||||
|
List<ExamAttempt> findByUserIsCurrentUser();
|
||||||
|
|
||||||
|
default Optional<ExamAttempt> findOneWithEagerRelationships(Long id) {
|
||||||
|
return this.findOneWithToOneRelationships(id);
|
||||||
|
}
|
||||||
|
|
||||||
|
default List<ExamAttempt> findAllWithEagerRelationships() {
|
||||||
|
return this.findAllWithToOneRelationships();
|
||||||
|
}
|
||||||
|
|
||||||
|
default Page<ExamAttempt> findAllWithEagerRelationships(Pageable pageable) {
|
||||||
|
return this.findAllWithToOneRelationships(pageable);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Query(
|
||||||
|
value = "select examAttempt from ExamAttempt examAttempt left join fetch examAttempt.subject left join fetch examAttempt.user",
|
||||||
|
countQuery = "select count(examAttempt) from ExamAttempt examAttempt"
|
||||||
|
)
|
||||||
|
Page<ExamAttempt> findAllWithToOneRelationships(Pageable pageable);
|
||||||
|
|
||||||
|
@Query("select examAttempt from ExamAttempt examAttempt left join fetch examAttempt.subject left join fetch examAttempt.user")
|
||||||
|
List<ExamAttempt> findAllWithToOneRelationships();
|
||||||
|
|
||||||
|
@Query(
|
||||||
|
"select examAttempt from ExamAttempt examAttempt left join fetch examAttempt.subject left join fetch examAttempt.user where examAttempt.id =:id"
|
||||||
|
)
|
||||||
|
Optional<ExamAttempt> findOneWithToOneRelationships(@Param("id") Long id);
|
||||||
|
}
|
||||||
@@ -0,0 +1,43 @@
|
|||||||
|
package com.example.questionbank.repository;
|
||||||
|
|
||||||
|
import com.example.questionbank.domain.Question;
|
||||||
|
import java.util.List;
|
||||||
|
import java.util.List;
|
||||||
|
import java.util.Optional;
|
||||||
|
import org.springframework.data.domain.Page;
|
||||||
|
import org.springframework.data.domain.Pageable;
|
||||||
|
import org.springframework.data.jpa.repository.*;
|
||||||
|
import org.springframework.data.repository.query.Param;
|
||||||
|
import org.springframework.stereotype.Repository;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Spring Data JPA repository for the Question entity.
|
||||||
|
*/
|
||||||
|
@Repository
|
||||||
|
public interface QuestionRepository extends JpaRepository<Question, Long> {
|
||||||
|
List<Question> findAllBySubjectIdAndActiveTrue(Long subjectId);
|
||||||
|
|
||||||
|
default Optional<Question> findOneWithEagerRelationships(Long id) {
|
||||||
|
return this.findOneWithToOneRelationships(id);
|
||||||
|
}
|
||||||
|
|
||||||
|
default List<Question> findAllWithEagerRelationships() {
|
||||||
|
return this.findAllWithToOneRelationships();
|
||||||
|
}
|
||||||
|
|
||||||
|
default Page<Question> findAllWithEagerRelationships(Pageable pageable) {
|
||||||
|
return this.findAllWithToOneRelationships(pageable);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Query(
|
||||||
|
value = "select question from Question question left join fetch question.subject",
|
||||||
|
countQuery = "select count(question) from Question question"
|
||||||
|
)
|
||||||
|
Page<Question> findAllWithToOneRelationships(Pageable pageable);
|
||||||
|
|
||||||
|
@Query("select question from Question question left join fetch question.subject")
|
||||||
|
List<Question> findAllWithToOneRelationships();
|
||||||
|
|
||||||
|
@Query("select question from Question question left join fetch question.subject where question.id =:id")
|
||||||
|
Optional<Question> findOneWithToOneRelationships(@Param("id") Long id);
|
||||||
|
}
|
||||||
@@ -0,0 +1,12 @@
|
|||||||
|
package com.example.questionbank.repository;
|
||||||
|
|
||||||
|
import com.example.questionbank.domain.Subject;
|
||||||
|
import org.springframework.data.jpa.repository.*;
|
||||||
|
import org.springframework.stereotype.Repository;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Spring Data JPA repository for the Subject entity.
|
||||||
|
*/
|
||||||
|
@SuppressWarnings("unused")
|
||||||
|
@Repository
|
||||||
|
public interface SubjectRepository extends JpaRepository<Subject, Long> {}
|
||||||
@@ -0,0 +1,34 @@
|
|||||||
|
package com.example.questionbank.repository;
|
||||||
|
|
||||||
|
import com.example.questionbank.domain.User;
|
||||||
|
import java.time.Instant;
|
||||||
|
import java.util.List;
|
||||||
|
import java.util.Optional;
|
||||||
|
import org.springframework.data.domain.*;
|
||||||
|
import org.springframework.data.jpa.repository.EntityGraph;
|
||||||
|
import org.springframework.data.jpa.repository.JpaRepository;
|
||||||
|
import org.springframework.stereotype.Repository;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Spring Data JPA repository for the {@link User} entity.
|
||||||
|
*/
|
||||||
|
@Repository
|
||||||
|
public interface UserRepository extends JpaRepository<User, Long> {
|
||||||
|
Optional<User> findOneByActivationKey(String activationKey);
|
||||||
|
|
||||||
|
List<User> findAllByActivatedIsFalseAndActivationKeyIsNotNullAndCreatedDateBefore(Instant dateTime);
|
||||||
|
|
||||||
|
Optional<User> findOneByResetKey(String resetKey);
|
||||||
|
|
||||||
|
Optional<User> findOneByEmailIgnoreCase(String email);
|
||||||
|
|
||||||
|
Optional<User> findOneByLogin(String login);
|
||||||
|
|
||||||
|
@EntityGraph(attributePaths = "authorities")
|
||||||
|
Optional<User> findOneWithAuthoritiesByLogin(String login);
|
||||||
|
|
||||||
|
@EntityGraph(attributePaths = "authorities")
|
||||||
|
Optional<User> findOneWithAuthoritiesByEmailIgnoreCase(String email);
|
||||||
|
|
||||||
|
Page<User> findAllByIdNotNullAndActivatedIsTrue(Pageable pageable);
|
||||||
|
}
|
||||||
@@ -0,0 +1,4 @@
|
|||||||
|
/**
|
||||||
|
* Repository layer.
|
||||||
|
*/
|
||||||
|
package com.example.questionbank.repository;
|
||||||
@@ -0,0 +1,15 @@
|
|||||||
|
package com.example.questionbank.security;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Constants for Spring Security authorities.
|
||||||
|
*/
|
||||||
|
public final class AuthoritiesConstants {
|
||||||
|
|
||||||
|
public static final String ADMIN = "ROLE_ADMIN";
|
||||||
|
|
||||||
|
public static final String USER = "ROLE_USER";
|
||||||
|
|
||||||
|
public static final String ANONYMOUS = "ROLE_ANONYMOUS";
|
||||||
|
|
||||||
|
private AuthoritiesConstants() {}
|
||||||
|
}
|
||||||
@@ -0,0 +1,90 @@
|
|||||||
|
package com.example.questionbank.security;
|
||||||
|
|
||||||
|
import com.example.questionbank.domain.Authority;
|
||||||
|
import com.example.questionbank.domain.User;
|
||||||
|
import com.example.questionbank.repository.UserRepository;
|
||||||
|
import java.util.*;
|
||||||
|
import org.hibernate.validator.internal.constraintvalidators.bv.EmailValidator;
|
||||||
|
import org.slf4j.Logger;
|
||||||
|
import org.slf4j.LoggerFactory;
|
||||||
|
import org.springframework.security.core.GrantedAuthority;
|
||||||
|
import org.springframework.security.core.authority.SimpleGrantedAuthority;
|
||||||
|
import org.springframework.security.core.userdetails.UserDetails;
|
||||||
|
import org.springframework.security.core.userdetails.UserDetailsService;
|
||||||
|
import org.springframework.security.core.userdetails.UsernameNotFoundException;
|
||||||
|
import org.springframework.stereotype.Service;
|
||||||
|
import org.springframework.transaction.annotation.Transactional;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Authenticate a user from the database.
|
||||||
|
*/
|
||||||
|
@Service("userDetailsService")
|
||||||
|
public class DomainUserDetailsService implements UserDetailsService {
|
||||||
|
|
||||||
|
private static final Logger LOG = LoggerFactory.getLogger(DomainUserDetailsService.class);
|
||||||
|
|
||||||
|
private final UserRepository userRepository;
|
||||||
|
|
||||||
|
public DomainUserDetailsService(UserRepository userRepository) {
|
||||||
|
this.userRepository = userRepository;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
@Transactional(readOnly = true)
|
||||||
|
public UserDetails loadUserByUsername(final String login) {
|
||||||
|
LOG.debug("Authenticating {}", login);
|
||||||
|
|
||||||
|
if (new EmailValidator().isValid(login, null)) {
|
||||||
|
return userRepository
|
||||||
|
.findOneWithAuthoritiesByEmailIgnoreCase(login)
|
||||||
|
.map(user -> createSpringSecurityUser(login, user))
|
||||||
|
.orElseThrow(() -> new UsernameNotFoundException("User with email " + login + " was not found in the database"));
|
||||||
|
}
|
||||||
|
|
||||||
|
String lowercaseLogin = login.toLowerCase(Locale.ENGLISH);
|
||||||
|
return userRepository
|
||||||
|
.findOneWithAuthoritiesByLogin(lowercaseLogin)
|
||||||
|
.map(user -> createSpringSecurityUser(lowercaseLogin, user))
|
||||||
|
.orElseThrow(() -> new UsernameNotFoundException("User " + lowercaseLogin + " was not found in the database"));
|
||||||
|
}
|
||||||
|
|
||||||
|
private org.springframework.security.core.userdetails.User createSpringSecurityUser(String lowercaseLogin, User user) {
|
||||||
|
if (!user.isActivated()) {
|
||||||
|
throw new UserNotActivatedException("User " + lowercaseLogin + " was not activated");
|
||||||
|
}
|
||||||
|
return UserWithId.fromUser(user);
|
||||||
|
}
|
||||||
|
|
||||||
|
public static class UserWithId extends org.springframework.security.core.userdetails.User {
|
||||||
|
|
||||||
|
private final Long id;
|
||||||
|
|
||||||
|
public UserWithId(String login, String password, Collection<? extends GrantedAuthority> authorities, Long id) {
|
||||||
|
super(login, password, authorities);
|
||||||
|
this.id = id;
|
||||||
|
}
|
||||||
|
|
||||||
|
public Long getId() {
|
||||||
|
return id;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public boolean equals(Object obj) {
|
||||||
|
return super.equals(obj);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public int hashCode() {
|
||||||
|
return super.hashCode();
|
||||||
|
}
|
||||||
|
|
||||||
|
public static UserWithId fromUser(User user) {
|
||||||
|
return new UserWithId(
|
||||||
|
user.getLogin(),
|
||||||
|
user.getPassword(),
|
||||||
|
user.getAuthorities().stream().map(Authority::getName).map(SimpleGrantedAuthority::new).toList(),
|
||||||
|
user.getId()
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,129 @@
|
|||||||
|
package com.example.questionbank.security;
|
||||||
|
|
||||||
|
import java.util.List;
|
||||||
|
import java.util.Optional;
|
||||||
|
import java.util.stream.Stream;
|
||||||
|
import org.springframework.security.authentication.AnonymousAuthenticationToken;
|
||||||
|
import org.springframework.security.core.Authentication;
|
||||||
|
import org.springframework.security.core.GrantedAuthority;
|
||||||
|
import org.springframework.security.core.context.SecurityContext;
|
||||||
|
import org.springframework.security.core.context.SecurityContextHolder;
|
||||||
|
import org.springframework.security.core.userdetails.UserDetails;
|
||||||
|
import org.springframework.security.oauth2.core.ClaimAccessor;
|
||||||
|
import org.springframework.security.oauth2.jose.jws.MacAlgorithm;
|
||||||
|
import org.springframework.security.oauth2.jwt.Jwt;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Utility class for Spring Security.
|
||||||
|
*/
|
||||||
|
public final class SecurityUtils {
|
||||||
|
|
||||||
|
public static final MacAlgorithm JWT_ALGORITHM = MacAlgorithm.HS512;
|
||||||
|
|
||||||
|
public static final String AUTHORITIES_CLAIM = "auth";
|
||||||
|
|
||||||
|
public static final String USER_ID_CLAIM = "userId";
|
||||||
|
|
||||||
|
private SecurityUtils() {}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get the login of the current user.
|
||||||
|
*
|
||||||
|
* @return the login of the current user.
|
||||||
|
*/
|
||||||
|
public static Optional<String> getCurrentUserLogin() {
|
||||||
|
SecurityContext securityContext = SecurityContextHolder.getContext();
|
||||||
|
return Optional.ofNullable(extractPrincipal(securityContext.getAuthentication()));
|
||||||
|
}
|
||||||
|
|
||||||
|
private static String extractPrincipal(Authentication authentication) {
|
||||||
|
// AnonymousAuthenticationToken has a String principal that would fall through below
|
||||||
|
if (authentication == null || authentication instanceof AnonymousAuthenticationToken) {
|
||||||
|
return null;
|
||||||
|
} else if (authentication.getPrincipal() instanceof UserDetails springSecurityUser) {
|
||||||
|
return springSecurityUser.getUsername();
|
||||||
|
} else if (authentication.getPrincipal() instanceof Jwt jwt) {
|
||||||
|
return jwt.getSubject();
|
||||||
|
} else if (authentication.getPrincipal() instanceof String s) {
|
||||||
|
return s;
|
||||||
|
}
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get the JWT of the current user.
|
||||||
|
*
|
||||||
|
* @return the JWT of the current user.
|
||||||
|
*/
|
||||||
|
public static Optional<String> getCurrentUserJWT() {
|
||||||
|
SecurityContext securityContext = SecurityContextHolder.getContext();
|
||||||
|
return Optional.ofNullable(securityContext.getAuthentication()).map(SecurityUtils::extractCredentials);
|
||||||
|
}
|
||||||
|
|
||||||
|
private static String extractCredentials(Authentication authentication) {
|
||||||
|
if (authentication.getCredentials() instanceof String token) {
|
||||||
|
return token;
|
||||||
|
} else if (authentication.getCredentials() instanceof Jwt jwt) {
|
||||||
|
return jwt.getTokenValue();
|
||||||
|
}
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get the Id of the current user.
|
||||||
|
*
|
||||||
|
* @return the Id of the current user.
|
||||||
|
*/
|
||||||
|
public static Optional<Long> getCurrentUserId() {
|
||||||
|
SecurityContext securityContext = SecurityContextHolder.getContext();
|
||||||
|
return Optional.ofNullable(securityContext.getAuthentication())
|
||||||
|
.filter(authentication -> authentication.getPrincipal() instanceof ClaimAccessor)
|
||||||
|
.map(authentication -> (ClaimAccessor) authentication.getPrincipal())
|
||||||
|
.map(principal -> principal.getClaim(USER_ID_CLAIM));
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Check if a user is authenticated.
|
||||||
|
*
|
||||||
|
* @return true if the user is authenticated, false otherwise.
|
||||||
|
*/
|
||||||
|
public static boolean isAuthenticated() {
|
||||||
|
Authentication authentication = SecurityContextHolder.getContext().getAuthentication();
|
||||||
|
return authentication != null && getAuthorities(authentication).noneMatch(AuthoritiesConstants.ANONYMOUS::equals);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Checks if the current user has any of the authorities.
|
||||||
|
*
|
||||||
|
* @param authorities the authorities to check.
|
||||||
|
* @return true if the current user has any of the authorities, false otherwise.
|
||||||
|
*/
|
||||||
|
public static boolean hasCurrentUserAnyOfAuthorities(String... authorities) {
|
||||||
|
Authentication authentication = SecurityContextHolder.getContext().getAuthentication();
|
||||||
|
return authentication != null && getAuthorities(authentication).anyMatch(authority -> List.of(authorities).contains(authority));
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Checks if the current user has none of the authorities.
|
||||||
|
*
|
||||||
|
* @param authorities the authorities to check.
|
||||||
|
* @return true if the current user has none of the authorities, false otherwise.
|
||||||
|
*/
|
||||||
|
public static boolean hasCurrentUserNoneOfAuthorities(String... authorities) {
|
||||||
|
return !hasCurrentUserAnyOfAuthorities(authorities);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Checks if the current user has a specific authority.
|
||||||
|
*
|
||||||
|
* @param authority the authority to check.
|
||||||
|
* @return true if the current user has the authority, false otherwise.
|
||||||
|
*/
|
||||||
|
public static boolean hasCurrentUserThisAuthority(String authority) {
|
||||||
|
return hasCurrentUserAnyOfAuthorities(authority);
|
||||||
|
}
|
||||||
|
|
||||||
|
private static Stream<String> getAuthorities(Authentication authentication) {
|
||||||
|
return authentication.getAuthorities().stream().map(GrantedAuthority::getAuthority);
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,18 @@
|
|||||||
|
package com.example.questionbank.security;
|
||||||
|
|
||||||
|
import com.example.questionbank.config.Constants;
|
||||||
|
import java.util.Optional;
|
||||||
|
import org.springframework.data.domain.AuditorAware;
|
||||||
|
import org.springframework.stereotype.Component;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Implementation of {@link AuditorAware} based on Spring Security.
|
||||||
|
*/
|
||||||
|
@Component
|
||||||
|
public class SpringSecurityAuditorAware implements AuditorAware<String> {
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public Optional<String> getCurrentAuditor() {
|
||||||
|
return Optional.of(SecurityUtils.getCurrentUserLogin().orElse(Constants.SYSTEM));
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,21 @@
|
|||||||
|
package com.example.questionbank.security;
|
||||||
|
|
||||||
|
import java.io.Serial;
|
||||||
|
import org.springframework.security.core.AuthenticationException;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* This exception is thrown in case of a not activated user trying to authenticate.
|
||||||
|
*/
|
||||||
|
public class UserNotActivatedException extends AuthenticationException {
|
||||||
|
|
||||||
|
@Serial
|
||||||
|
private static final long serialVersionUID = 1L;
|
||||||
|
|
||||||
|
public UserNotActivatedException(String message) {
|
||||||
|
super(message);
|
||||||
|
}
|
||||||
|
|
||||||
|
public UserNotActivatedException(String message, Throwable t) {
|
||||||
|
super(message, t);
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,4 @@
|
|||||||
|
/**
|
||||||
|
* Application security utilities.
|
||||||
|
*/
|
||||||
|
package com.example.questionbank.security;
|
||||||
@@ -0,0 +1,112 @@
|
|||||||
|
package com.example.questionbank.service;
|
||||||
|
|
||||||
|
import com.example.questionbank.domain.Choice;
|
||||||
|
import com.example.questionbank.repository.ChoiceRepository;
|
||||||
|
import com.example.questionbank.service.dto.ChoiceDTO;
|
||||||
|
import com.example.questionbank.service.mapper.ChoiceMapper;
|
||||||
|
import java.util.LinkedList;
|
||||||
|
import java.util.List;
|
||||||
|
import java.util.Optional;
|
||||||
|
import java.util.stream.Collectors;
|
||||||
|
import org.slf4j.Logger;
|
||||||
|
import org.slf4j.LoggerFactory;
|
||||||
|
import org.springframework.stereotype.Service;
|
||||||
|
import org.springframework.transaction.annotation.Transactional;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Service Implementation for managing {@link com.example.questionbank.domain.Choice}.
|
||||||
|
*/
|
||||||
|
@Service
|
||||||
|
@Transactional
|
||||||
|
public class ChoiceService {
|
||||||
|
|
||||||
|
private static final Logger LOG = LoggerFactory.getLogger(ChoiceService.class);
|
||||||
|
|
||||||
|
private final ChoiceRepository choiceRepository;
|
||||||
|
|
||||||
|
private final ChoiceMapper choiceMapper;
|
||||||
|
|
||||||
|
public ChoiceService(ChoiceRepository choiceRepository, ChoiceMapper choiceMapper) {
|
||||||
|
this.choiceRepository = choiceRepository;
|
||||||
|
this.choiceMapper = choiceMapper;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Save a choice.
|
||||||
|
*
|
||||||
|
* @param choiceDTO the entity to save.
|
||||||
|
* @return the persisted entity.
|
||||||
|
*/
|
||||||
|
public ChoiceDTO save(ChoiceDTO choiceDTO) {
|
||||||
|
LOG.debug("Request to save Choice : {}", choiceDTO);
|
||||||
|
Choice choice = choiceMapper.toEntity(choiceDTO);
|
||||||
|
choice = choiceRepository.save(choice);
|
||||||
|
return choiceMapper.toDto(choice);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Update a choice.
|
||||||
|
*
|
||||||
|
* @param choiceDTO the entity to save.
|
||||||
|
* @return the persisted entity.
|
||||||
|
*/
|
||||||
|
public ChoiceDTO update(ChoiceDTO choiceDTO) {
|
||||||
|
LOG.debug("Request to update Choice : {}", choiceDTO);
|
||||||
|
Choice choice = choiceMapper.toEntity(choiceDTO);
|
||||||
|
choice = choiceRepository.save(choice);
|
||||||
|
return choiceMapper.toDto(choice);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Partially update a choice.
|
||||||
|
*
|
||||||
|
* @param choiceDTO the entity to update partially.
|
||||||
|
* @return the persisted entity.
|
||||||
|
*/
|
||||||
|
public Optional<ChoiceDTO> partialUpdate(ChoiceDTO choiceDTO) {
|
||||||
|
LOG.debug("Request to partially update Choice : {}", choiceDTO);
|
||||||
|
|
||||||
|
return choiceRepository
|
||||||
|
.findById(choiceDTO.getId())
|
||||||
|
.map(existingChoice -> {
|
||||||
|
choiceMapper.partialUpdate(existingChoice, choiceDTO);
|
||||||
|
|
||||||
|
return existingChoice;
|
||||||
|
})
|
||||||
|
.map(choiceRepository::save)
|
||||||
|
.map(choiceMapper::toDto);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get all the choices.
|
||||||
|
*
|
||||||
|
* @return the list of entities.
|
||||||
|
*/
|
||||||
|
@Transactional(readOnly = true)
|
||||||
|
public List<ChoiceDTO> findAll() {
|
||||||
|
LOG.debug("Request to get all Choices");
|
||||||
|
return choiceRepository.findAll().stream().map(choiceMapper::toDto).collect(Collectors.toCollection(LinkedList::new));
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get one choice by id.
|
||||||
|
*
|
||||||
|
* @param id the id of the entity.
|
||||||
|
* @return the entity.
|
||||||
|
*/
|
||||||
|
@Transactional(readOnly = true)
|
||||||
|
public Optional<ChoiceDTO> findOne(Long id) {
|
||||||
|
LOG.debug("Request to get Choice : {}", id);
|
||||||
|
return choiceRepository.findById(id).map(choiceMapper::toDto);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Delete the choice by id.
|
||||||
|
*
|
||||||
|
* @param id the id of the entity.
|
||||||
|
*/
|
||||||
|
public void delete(Long id) {
|
||||||
|
LOG.debug("Request to delete Choice : {}", id);
|
||||||
|
choiceRepository.deleteById(id);
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,13 @@
|
|||||||
|
package com.example.questionbank.service;
|
||||||
|
|
||||||
|
import java.io.Serial;
|
||||||
|
|
||||||
|
public class EmailAlreadyUsedException extends RuntimeException {
|
||||||
|
|
||||||
|
@Serial
|
||||||
|
private static final long serialVersionUID = 1L;
|
||||||
|
|
||||||
|
public EmailAlreadyUsedException() {
|
||||||
|
super("Email is already in use!");
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,112 @@
|
|||||||
|
package com.example.questionbank.service;
|
||||||
|
|
||||||
|
import com.example.questionbank.domain.ExamAnswer;
|
||||||
|
import com.example.questionbank.repository.ExamAnswerRepository;
|
||||||
|
import com.example.questionbank.service.dto.ExamAnswerDTO;
|
||||||
|
import com.example.questionbank.service.mapper.ExamAnswerMapper;
|
||||||
|
import java.util.LinkedList;
|
||||||
|
import java.util.List;
|
||||||
|
import java.util.Optional;
|
||||||
|
import java.util.stream.Collectors;
|
||||||
|
import org.slf4j.Logger;
|
||||||
|
import org.slf4j.LoggerFactory;
|
||||||
|
import org.springframework.stereotype.Service;
|
||||||
|
import org.springframework.transaction.annotation.Transactional;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Service Implementation for managing {@link com.example.questionbank.domain.ExamAnswer}.
|
||||||
|
*/
|
||||||
|
@Service
|
||||||
|
@Transactional
|
||||||
|
public class ExamAnswerService {
|
||||||
|
|
||||||
|
private static final Logger LOG = LoggerFactory.getLogger(ExamAnswerService.class);
|
||||||
|
|
||||||
|
private final ExamAnswerRepository examAnswerRepository;
|
||||||
|
|
||||||
|
private final ExamAnswerMapper examAnswerMapper;
|
||||||
|
|
||||||
|
public ExamAnswerService(ExamAnswerRepository examAnswerRepository, ExamAnswerMapper examAnswerMapper) {
|
||||||
|
this.examAnswerRepository = examAnswerRepository;
|
||||||
|
this.examAnswerMapper = examAnswerMapper;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Save a examAnswer.
|
||||||
|
*
|
||||||
|
* @param examAnswerDTO the entity to save.
|
||||||
|
* @return the persisted entity.
|
||||||
|
*/
|
||||||
|
public ExamAnswerDTO save(ExamAnswerDTO examAnswerDTO) {
|
||||||
|
LOG.debug("Request to save ExamAnswer : {}", examAnswerDTO);
|
||||||
|
ExamAnswer examAnswer = examAnswerMapper.toEntity(examAnswerDTO);
|
||||||
|
examAnswer = examAnswerRepository.save(examAnswer);
|
||||||
|
return examAnswerMapper.toDto(examAnswer);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Update a examAnswer.
|
||||||
|
*
|
||||||
|
* @param examAnswerDTO the entity to save.
|
||||||
|
* @return the persisted entity.
|
||||||
|
*/
|
||||||
|
public ExamAnswerDTO update(ExamAnswerDTO examAnswerDTO) {
|
||||||
|
LOG.debug("Request to update ExamAnswer : {}", examAnswerDTO);
|
||||||
|
ExamAnswer examAnswer = examAnswerMapper.toEntity(examAnswerDTO);
|
||||||
|
examAnswer = examAnswerRepository.save(examAnswer);
|
||||||
|
return examAnswerMapper.toDto(examAnswer);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Partially update a examAnswer.
|
||||||
|
*
|
||||||
|
* @param examAnswerDTO the entity to update partially.
|
||||||
|
* @return the persisted entity.
|
||||||
|
*/
|
||||||
|
public Optional<ExamAnswerDTO> partialUpdate(ExamAnswerDTO examAnswerDTO) {
|
||||||
|
LOG.debug("Request to partially update ExamAnswer : {}", examAnswerDTO);
|
||||||
|
|
||||||
|
return examAnswerRepository
|
||||||
|
.findById(examAnswerDTO.getId())
|
||||||
|
.map(existingExamAnswer -> {
|
||||||
|
examAnswerMapper.partialUpdate(existingExamAnswer, examAnswerDTO);
|
||||||
|
|
||||||
|
return existingExamAnswer;
|
||||||
|
})
|
||||||
|
.map(examAnswerRepository::save)
|
||||||
|
.map(examAnswerMapper::toDto);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get all the examAnswers.
|
||||||
|
*
|
||||||
|
* @return the list of entities.
|
||||||
|
*/
|
||||||
|
@Transactional(readOnly = true)
|
||||||
|
public List<ExamAnswerDTO> findAll() {
|
||||||
|
LOG.debug("Request to get all ExamAnswers");
|
||||||
|
return examAnswerRepository.findAll().stream().map(examAnswerMapper::toDto).collect(Collectors.toCollection(LinkedList::new));
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get one examAnswer by id.
|
||||||
|
*
|
||||||
|
* @param id the id of the entity.
|
||||||
|
* @return the entity.
|
||||||
|
*/
|
||||||
|
@Transactional(readOnly = true)
|
||||||
|
public Optional<ExamAnswerDTO> findOne(Long id) {
|
||||||
|
LOG.debug("Request to get ExamAnswer : {}", id);
|
||||||
|
return examAnswerRepository.findById(id).map(examAnswerMapper::toDto);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Delete the examAnswer by id.
|
||||||
|
*
|
||||||
|
* @param id the id of the entity.
|
||||||
|
*/
|
||||||
|
public void delete(Long id) {
|
||||||
|
LOG.debug("Request to delete ExamAnswer : {}", id);
|
||||||
|
examAnswerRepository.deleteById(id);
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,121 @@
|
|||||||
|
package com.example.questionbank.service;
|
||||||
|
|
||||||
|
import com.example.questionbank.domain.ExamAttempt;
|
||||||
|
import com.example.questionbank.repository.ExamAttemptRepository;
|
||||||
|
import com.example.questionbank.service.dto.ExamAttemptDTO;
|
||||||
|
import com.example.questionbank.service.mapper.ExamAttemptMapper;
|
||||||
|
import java.util.Optional;
|
||||||
|
import org.slf4j.Logger;
|
||||||
|
import org.slf4j.LoggerFactory;
|
||||||
|
import org.springframework.data.domain.Page;
|
||||||
|
import org.springframework.data.domain.Pageable;
|
||||||
|
import org.springframework.stereotype.Service;
|
||||||
|
import org.springframework.transaction.annotation.Transactional;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Service Implementation for managing {@link com.example.questionbank.domain.ExamAttempt}.
|
||||||
|
*/
|
||||||
|
@Service
|
||||||
|
@Transactional
|
||||||
|
public class ExamAttemptService {
|
||||||
|
|
||||||
|
private static final Logger LOG = LoggerFactory.getLogger(ExamAttemptService.class);
|
||||||
|
|
||||||
|
private final ExamAttemptRepository examAttemptRepository;
|
||||||
|
|
||||||
|
private final ExamAttemptMapper examAttemptMapper;
|
||||||
|
|
||||||
|
public ExamAttemptService(ExamAttemptRepository examAttemptRepository, ExamAttemptMapper examAttemptMapper) {
|
||||||
|
this.examAttemptRepository = examAttemptRepository;
|
||||||
|
this.examAttemptMapper = examAttemptMapper;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Save a examAttempt.
|
||||||
|
*
|
||||||
|
* @param examAttemptDTO the entity to save.
|
||||||
|
* @return the persisted entity.
|
||||||
|
*/
|
||||||
|
public ExamAttemptDTO save(ExamAttemptDTO examAttemptDTO) {
|
||||||
|
LOG.debug("Request to save ExamAttempt : {}", examAttemptDTO);
|
||||||
|
ExamAttempt examAttempt = examAttemptMapper.toEntity(examAttemptDTO);
|
||||||
|
examAttempt = examAttemptRepository.save(examAttempt);
|
||||||
|
return examAttemptMapper.toDto(examAttempt);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Update a examAttempt.
|
||||||
|
*
|
||||||
|
* @param examAttemptDTO the entity to save.
|
||||||
|
* @return the persisted entity.
|
||||||
|
*/
|
||||||
|
public ExamAttemptDTO update(ExamAttemptDTO examAttemptDTO) {
|
||||||
|
LOG.debug("Request to update ExamAttempt : {}", examAttemptDTO);
|
||||||
|
ExamAttempt examAttempt = examAttemptMapper.toEntity(examAttemptDTO);
|
||||||
|
examAttempt = examAttemptRepository.save(examAttempt);
|
||||||
|
return examAttemptMapper.toDto(examAttempt);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Partially update a examAttempt.
|
||||||
|
*
|
||||||
|
* @param examAttemptDTO the entity to update partially.
|
||||||
|
* @return the persisted entity.
|
||||||
|
*/
|
||||||
|
public Optional<ExamAttemptDTO> partialUpdate(ExamAttemptDTO examAttemptDTO) {
|
||||||
|
LOG.debug("Request to partially update ExamAttempt : {}", examAttemptDTO);
|
||||||
|
|
||||||
|
return examAttemptRepository
|
||||||
|
.findById(examAttemptDTO.getId())
|
||||||
|
.map(existingExamAttempt -> {
|
||||||
|
examAttemptMapper.partialUpdate(existingExamAttempt, examAttemptDTO);
|
||||||
|
|
||||||
|
return existingExamAttempt;
|
||||||
|
})
|
||||||
|
.map(examAttemptRepository::save)
|
||||||
|
.map(examAttemptMapper::toDto);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get all the examAttempts.
|
||||||
|
*
|
||||||
|
* @param pageable the pagination information.
|
||||||
|
* @return the list of entities.
|
||||||
|
*/
|
||||||
|
@Transactional(readOnly = true)
|
||||||
|
public Page<ExamAttemptDTO> findAll(Pageable pageable) {
|
||||||
|
LOG.debug("Request to get all ExamAttempts");
|
||||||
|
return examAttemptRepository.findAll(pageable).map(examAttemptMapper::toDto);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get all the examAttempts with eager load of many-to-many relationships.
|
||||||
|
*
|
||||||
|
* @return the list of entities.
|
||||||
|
*/
|
||||||
|
public Page<ExamAttemptDTO> findAllWithEagerRelationships(Pageable pageable) {
|
||||||
|
return examAttemptRepository.findAllWithEagerRelationships(pageable).map(examAttemptMapper::toDto);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get one examAttempt by id.
|
||||||
|
*
|
||||||
|
* @param id the id of the entity.
|
||||||
|
* @return the entity.
|
||||||
|
*/
|
||||||
|
@Transactional(readOnly = true)
|
||||||
|
public Optional<ExamAttemptDTO> findOne(Long id) {
|
||||||
|
LOG.debug("Request to get ExamAttempt : {}", id);
|
||||||
|
return examAttemptRepository.findOneWithEagerRelationships(id).map(examAttemptMapper::toDto);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Delete the examAttempt by id.
|
||||||
|
*
|
||||||
|
* @param id the id of the entity.
|
||||||
|
*/
|
||||||
|
public void delete(Long id) {
|
||||||
|
LOG.debug("Request to delete ExamAttempt : {}", id);
|
||||||
|
examAttemptRepository.deleteById(id);
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,13 @@
|
|||||||
|
package com.example.questionbank.service;
|
||||||
|
|
||||||
|
import java.io.Serial;
|
||||||
|
|
||||||
|
public class InvalidPasswordException extends RuntimeException {
|
||||||
|
|
||||||
|
@Serial
|
||||||
|
private static final long serialVersionUID = 1L;
|
||||||
|
|
||||||
|
public InvalidPasswordException() {
|
||||||
|
super("Incorrect password");
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,120 @@
|
|||||||
|
package com.example.questionbank.service;
|
||||||
|
|
||||||
|
import com.example.questionbank.domain.User;
|
||||||
|
import jakarta.mail.MessagingException;
|
||||||
|
import jakarta.mail.internet.MimeMessage;
|
||||||
|
import java.nio.charset.StandardCharsets;
|
||||||
|
import java.util.Locale;
|
||||||
|
import org.slf4j.Logger;
|
||||||
|
import org.slf4j.LoggerFactory;
|
||||||
|
import org.springframework.context.MessageSource;
|
||||||
|
import org.springframework.mail.MailException;
|
||||||
|
import org.springframework.mail.javamail.JavaMailSender;
|
||||||
|
import org.springframework.mail.javamail.MimeMessageHelper;
|
||||||
|
import org.springframework.scheduling.annotation.Async;
|
||||||
|
import org.springframework.stereotype.Service;
|
||||||
|
import org.thymeleaf.context.Context;
|
||||||
|
import org.thymeleaf.spring6.SpringTemplateEngine;
|
||||||
|
import tech.jhipster.config.JHipsterProperties;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Service for sending emails asynchronously.
|
||||||
|
* <p>
|
||||||
|
* We use the {@link Async} annotation to send emails asynchronously.
|
||||||
|
*/
|
||||||
|
@Service
|
||||||
|
public class MailService {
|
||||||
|
|
||||||
|
private static final Logger LOG = LoggerFactory.getLogger(MailService.class);
|
||||||
|
|
||||||
|
private static final String USER = "user";
|
||||||
|
|
||||||
|
private static final String BASE_URL = "baseUrl";
|
||||||
|
|
||||||
|
private final JHipsterProperties jHipsterProperties;
|
||||||
|
|
||||||
|
private final JavaMailSender javaMailSender;
|
||||||
|
|
||||||
|
private final MessageSource messageSource;
|
||||||
|
|
||||||
|
private final SpringTemplateEngine templateEngine;
|
||||||
|
|
||||||
|
public MailService(
|
||||||
|
JHipsterProperties jHipsterProperties,
|
||||||
|
JavaMailSender javaMailSender,
|
||||||
|
MessageSource messageSource,
|
||||||
|
SpringTemplateEngine templateEngine
|
||||||
|
) {
|
||||||
|
this.jHipsterProperties = jHipsterProperties;
|
||||||
|
this.javaMailSender = javaMailSender;
|
||||||
|
this.messageSource = messageSource;
|
||||||
|
this.templateEngine = templateEngine;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Async
|
||||||
|
public void sendEmail(String to, String subject, String content, boolean isMultipart, boolean isHtml) {
|
||||||
|
sendEmailSync(to, subject, content, isMultipart, isHtml);
|
||||||
|
}
|
||||||
|
|
||||||
|
private void sendEmailSync(String to, String subject, String content, boolean isMultipart, boolean isHtml) {
|
||||||
|
LOG.debug(
|
||||||
|
"Send email[multipart '{}' and html '{}'] to '{}' with subject '{}' and content={}",
|
||||||
|
isMultipart,
|
||||||
|
isHtml,
|
||||||
|
to,
|
||||||
|
subject,
|
||||||
|
content
|
||||||
|
);
|
||||||
|
|
||||||
|
// Prepare message using a Spring helper
|
||||||
|
MimeMessage mimeMessage = javaMailSender.createMimeMessage();
|
||||||
|
try {
|
||||||
|
MimeMessageHelper message = new MimeMessageHelper(mimeMessage, isMultipart, StandardCharsets.UTF_8.name());
|
||||||
|
message.setTo(to);
|
||||||
|
message.setFrom(jHipsterProperties.getMail().getFrom());
|
||||||
|
message.setSubject(subject);
|
||||||
|
message.setText(content, isHtml);
|
||||||
|
javaMailSender.send(mimeMessage);
|
||||||
|
LOG.debug("Sent email to User '{}'", to);
|
||||||
|
} catch (MailException | MessagingException e) {
|
||||||
|
LOG.warn("Email could not be sent to user '{}'", to, e);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Async
|
||||||
|
public void sendEmailFromTemplate(User user, String templateName, String titleKey) {
|
||||||
|
sendEmailFromTemplateSync(user, templateName, titleKey);
|
||||||
|
}
|
||||||
|
|
||||||
|
private void sendEmailFromTemplateSync(User user, String templateName, String titleKey) {
|
||||||
|
if (user.getEmail() == null) {
|
||||||
|
LOG.debug("Email doesn't exist for user '{}'", user.getLogin());
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
Locale locale = Locale.forLanguageTag(user.getLangKey());
|
||||||
|
Context context = new Context(locale);
|
||||||
|
context.setVariable(USER, user);
|
||||||
|
context.setVariable(BASE_URL, jHipsterProperties.getMail().getBaseUrl());
|
||||||
|
String content = templateEngine.process(templateName, context);
|
||||||
|
String subject = messageSource.getMessage(titleKey, null, locale);
|
||||||
|
sendEmailSync(user.getEmail(), subject, content, false, true);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Async
|
||||||
|
public void sendActivationEmail(User user) {
|
||||||
|
LOG.debug("Sending activation email to '{}'", user.getEmail());
|
||||||
|
sendEmailFromTemplateSync(user, "mail/activationEmail", "email.activation.title");
|
||||||
|
}
|
||||||
|
|
||||||
|
@Async
|
||||||
|
public void sendCreationEmail(User user) {
|
||||||
|
LOG.debug("Sending creation email to '{}'", user.getEmail());
|
||||||
|
sendEmailFromTemplateSync(user, "mail/creationEmail", "email.activation.title");
|
||||||
|
}
|
||||||
|
|
||||||
|
@Async
|
||||||
|
public void sendPasswordResetMail(User user) {
|
||||||
|
LOG.debug("Sending password reset email to '{}'", user.getEmail());
|
||||||
|
sendEmailFromTemplateSync(user, "mail/passwordResetEmail", "email.reset.title");
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,121 @@
|
|||||||
|
package com.example.questionbank.service;
|
||||||
|
|
||||||
|
import com.example.questionbank.domain.Question;
|
||||||
|
import com.example.questionbank.repository.QuestionRepository;
|
||||||
|
import com.example.questionbank.service.dto.QuestionDTO;
|
||||||
|
import com.example.questionbank.service.mapper.QuestionMapper;
|
||||||
|
import java.util.Optional;
|
||||||
|
import org.slf4j.Logger;
|
||||||
|
import org.slf4j.LoggerFactory;
|
||||||
|
import org.springframework.data.domain.Page;
|
||||||
|
import org.springframework.data.domain.Pageable;
|
||||||
|
import org.springframework.stereotype.Service;
|
||||||
|
import org.springframework.transaction.annotation.Transactional;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Service Implementation for managing {@link com.example.questionbank.domain.Question}.
|
||||||
|
*/
|
||||||
|
@Service
|
||||||
|
@Transactional
|
||||||
|
public class QuestionService {
|
||||||
|
|
||||||
|
private static final Logger LOG = LoggerFactory.getLogger(QuestionService.class);
|
||||||
|
|
||||||
|
private final QuestionRepository questionRepository;
|
||||||
|
|
||||||
|
private final QuestionMapper questionMapper;
|
||||||
|
|
||||||
|
public QuestionService(QuestionRepository questionRepository, QuestionMapper questionMapper) {
|
||||||
|
this.questionRepository = questionRepository;
|
||||||
|
this.questionMapper = questionMapper;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Save a question.
|
||||||
|
*
|
||||||
|
* @param questionDTO the entity to save.
|
||||||
|
* @return the persisted entity.
|
||||||
|
*/
|
||||||
|
public QuestionDTO save(QuestionDTO questionDTO) {
|
||||||
|
LOG.debug("Request to save Question : {}", questionDTO);
|
||||||
|
Question question = questionMapper.toEntity(questionDTO);
|
||||||
|
question = questionRepository.save(question);
|
||||||
|
return questionMapper.toDto(question);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Update a question.
|
||||||
|
*
|
||||||
|
* @param questionDTO the entity to save.
|
||||||
|
* @return the persisted entity.
|
||||||
|
*/
|
||||||
|
public QuestionDTO update(QuestionDTO questionDTO) {
|
||||||
|
LOG.debug("Request to update Question : {}", questionDTO);
|
||||||
|
Question question = questionMapper.toEntity(questionDTO);
|
||||||
|
question = questionRepository.save(question);
|
||||||
|
return questionMapper.toDto(question);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Partially update a question.
|
||||||
|
*
|
||||||
|
* @param questionDTO the entity to update partially.
|
||||||
|
* @return the persisted entity.
|
||||||
|
*/
|
||||||
|
public Optional<QuestionDTO> partialUpdate(QuestionDTO questionDTO) {
|
||||||
|
LOG.debug("Request to partially update Question : {}", questionDTO);
|
||||||
|
|
||||||
|
return questionRepository
|
||||||
|
.findById(questionDTO.getId())
|
||||||
|
.map(existingQuestion -> {
|
||||||
|
questionMapper.partialUpdate(existingQuestion, questionDTO);
|
||||||
|
|
||||||
|
return existingQuestion;
|
||||||
|
})
|
||||||
|
.map(questionRepository::save)
|
||||||
|
.map(questionMapper::toDto);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get all the questions.
|
||||||
|
*
|
||||||
|
* @param pageable the pagination information.
|
||||||
|
* @return the list of entities.
|
||||||
|
*/
|
||||||
|
@Transactional(readOnly = true)
|
||||||
|
public Page<QuestionDTO> findAll(Pageable pageable) {
|
||||||
|
LOG.debug("Request to get all Questions");
|
||||||
|
return questionRepository.findAll(pageable).map(questionMapper::toDto);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get all the questions with eager load of many-to-many relationships.
|
||||||
|
*
|
||||||
|
* @return the list of entities.
|
||||||
|
*/
|
||||||
|
public Page<QuestionDTO> findAllWithEagerRelationships(Pageable pageable) {
|
||||||
|
return questionRepository.findAllWithEagerRelationships(pageable).map(questionMapper::toDto);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get one question by id.
|
||||||
|
*
|
||||||
|
* @param id the id of the entity.
|
||||||
|
* @return the entity.
|
||||||
|
*/
|
||||||
|
@Transactional(readOnly = true)
|
||||||
|
public Optional<QuestionDTO> findOne(Long id) {
|
||||||
|
LOG.debug("Request to get Question : {}", id);
|
||||||
|
return questionRepository.findOneWithEagerRelationships(id).map(questionMapper::toDto);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Delete the question by id.
|
||||||
|
*
|
||||||
|
* @param id the id of the entity.
|
||||||
|
*/
|
||||||
|
public void delete(Long id) {
|
||||||
|
LOG.debug("Request to delete Question : {}", id);
|
||||||
|
questionRepository.deleteById(id);
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,112 @@
|
|||||||
|
package com.example.questionbank.service;
|
||||||
|
|
||||||
|
import com.example.questionbank.domain.Subject;
|
||||||
|
import com.example.questionbank.repository.SubjectRepository;
|
||||||
|
import com.example.questionbank.service.dto.SubjectDTO;
|
||||||
|
import com.example.questionbank.service.mapper.SubjectMapper;
|
||||||
|
import java.util.Optional;
|
||||||
|
import org.slf4j.Logger;
|
||||||
|
import org.slf4j.LoggerFactory;
|
||||||
|
import org.springframework.data.domain.Page;
|
||||||
|
import org.springframework.data.domain.Pageable;
|
||||||
|
import org.springframework.stereotype.Service;
|
||||||
|
import org.springframework.transaction.annotation.Transactional;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Service Implementation for managing {@link com.example.questionbank.domain.Subject}.
|
||||||
|
*/
|
||||||
|
@Service
|
||||||
|
@Transactional
|
||||||
|
public class SubjectService {
|
||||||
|
|
||||||
|
private static final Logger LOG = LoggerFactory.getLogger(SubjectService.class);
|
||||||
|
|
||||||
|
private final SubjectRepository subjectRepository;
|
||||||
|
|
||||||
|
private final SubjectMapper subjectMapper;
|
||||||
|
|
||||||
|
public SubjectService(SubjectRepository subjectRepository, SubjectMapper subjectMapper) {
|
||||||
|
this.subjectRepository = subjectRepository;
|
||||||
|
this.subjectMapper = subjectMapper;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Save a subject.
|
||||||
|
*
|
||||||
|
* @param subjectDTO the entity to save.
|
||||||
|
* @return the persisted entity.
|
||||||
|
*/
|
||||||
|
public SubjectDTO save(SubjectDTO subjectDTO) {
|
||||||
|
LOG.debug("Request to save Subject : {}", subjectDTO);
|
||||||
|
Subject subject = subjectMapper.toEntity(subjectDTO);
|
||||||
|
subject = subjectRepository.save(subject);
|
||||||
|
return subjectMapper.toDto(subject);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Update a subject.
|
||||||
|
*
|
||||||
|
* @param subjectDTO the entity to save.
|
||||||
|
* @return the persisted entity.
|
||||||
|
*/
|
||||||
|
public SubjectDTO update(SubjectDTO subjectDTO) {
|
||||||
|
LOG.debug("Request to update Subject : {}", subjectDTO);
|
||||||
|
Subject subject = subjectMapper.toEntity(subjectDTO);
|
||||||
|
subject = subjectRepository.save(subject);
|
||||||
|
return subjectMapper.toDto(subject);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Partially update a subject.
|
||||||
|
*
|
||||||
|
* @param subjectDTO the entity to update partially.
|
||||||
|
* @return the persisted entity.
|
||||||
|
*/
|
||||||
|
public Optional<SubjectDTO> partialUpdate(SubjectDTO subjectDTO) {
|
||||||
|
LOG.debug("Request to partially update Subject : {}", subjectDTO);
|
||||||
|
|
||||||
|
return subjectRepository
|
||||||
|
.findById(subjectDTO.getId())
|
||||||
|
.map(existingSubject -> {
|
||||||
|
subjectMapper.partialUpdate(existingSubject, subjectDTO);
|
||||||
|
|
||||||
|
return existingSubject;
|
||||||
|
})
|
||||||
|
.map(subjectRepository::save)
|
||||||
|
.map(subjectMapper::toDto);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get all the subjects.
|
||||||
|
*
|
||||||
|
* @param pageable the pagination information.
|
||||||
|
* @return the list of entities.
|
||||||
|
*/
|
||||||
|
@Transactional(readOnly = true)
|
||||||
|
public Page<SubjectDTO> findAll(Pageable pageable) {
|
||||||
|
LOG.debug("Request to get all Subjects");
|
||||||
|
return subjectRepository.findAll(pageable).map(subjectMapper::toDto);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get one subject by id.
|
||||||
|
*
|
||||||
|
* @param id the id of the entity.
|
||||||
|
* @return the entity.
|
||||||
|
*/
|
||||||
|
@Transactional(readOnly = true)
|
||||||
|
public Optional<SubjectDTO> findOne(Long id) {
|
||||||
|
LOG.debug("Request to get Subject : {}", id);
|
||||||
|
return subjectRepository.findById(id).map(subjectMapper::toDto);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Delete the subject by id.
|
||||||
|
*
|
||||||
|
* @param id the id of the entity.
|
||||||
|
*/
|
||||||
|
public void delete(Long id) {
|
||||||
|
LOG.debug("Request to delete Subject : {}", id);
|
||||||
|
subjectRepository.deleteById(id);
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,288 @@
|
|||||||
|
package com.example.questionbank.service;
|
||||||
|
|
||||||
|
import com.example.questionbank.config.Constants;
|
||||||
|
import com.example.questionbank.domain.Authority;
|
||||||
|
import com.example.questionbank.domain.User;
|
||||||
|
import com.example.questionbank.repository.AuthorityRepository;
|
||||||
|
import com.example.questionbank.repository.UserRepository;
|
||||||
|
import com.example.questionbank.security.AuthoritiesConstants;
|
||||||
|
import com.example.questionbank.security.SecurityUtils;
|
||||||
|
import com.example.questionbank.service.dto.AdminUserDTO;
|
||||||
|
import com.example.questionbank.service.dto.UserDTO;
|
||||||
|
import java.time.Instant;
|
||||||
|
import java.time.temporal.ChronoUnit;
|
||||||
|
import java.util.*;
|
||||||
|
import java.util.stream.Collectors;
|
||||||
|
import org.slf4j.Logger;
|
||||||
|
import org.slf4j.LoggerFactory;
|
||||||
|
import org.springframework.data.domain.Page;
|
||||||
|
import org.springframework.data.domain.Pageable;
|
||||||
|
import org.springframework.scheduling.annotation.Scheduled;
|
||||||
|
import org.springframework.security.crypto.password.PasswordEncoder;
|
||||||
|
import org.springframework.stereotype.Service;
|
||||||
|
import org.springframework.transaction.annotation.Transactional;
|
||||||
|
import tech.jhipster.security.RandomUtil;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Service class for managing users.
|
||||||
|
*/
|
||||||
|
@Service
|
||||||
|
@Transactional
|
||||||
|
public class UserService {
|
||||||
|
|
||||||
|
private static final Logger LOG = LoggerFactory.getLogger(UserService.class);
|
||||||
|
|
||||||
|
private final UserRepository userRepository;
|
||||||
|
|
||||||
|
private final PasswordEncoder passwordEncoder;
|
||||||
|
|
||||||
|
private final AuthorityRepository authorityRepository;
|
||||||
|
|
||||||
|
public UserService(UserRepository userRepository, PasswordEncoder passwordEncoder, AuthorityRepository authorityRepository) {
|
||||||
|
this.userRepository = userRepository;
|
||||||
|
this.passwordEncoder = passwordEncoder;
|
||||||
|
this.authorityRepository = authorityRepository;
|
||||||
|
}
|
||||||
|
|
||||||
|
public Optional<User> activateRegistration(String key) {
|
||||||
|
LOG.debug("Activating user for activation key {}", key);
|
||||||
|
return userRepository.findOneByActivationKey(key).map(user -> {
|
||||||
|
// activate given user for the registration key.
|
||||||
|
user.setActivated(true);
|
||||||
|
user.setActivationKey(null);
|
||||||
|
LOG.debug("Activated user: {}", user);
|
||||||
|
return user;
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
public Optional<User> completePasswordReset(String newPassword, String key) {
|
||||||
|
LOG.debug("Reset user password for reset key {}", key);
|
||||||
|
return userRepository
|
||||||
|
.findOneByResetKey(key)
|
||||||
|
.filter(user -> user.getResetDate().isAfter(Instant.now().minus(1, ChronoUnit.DAYS)))
|
||||||
|
.map(user -> {
|
||||||
|
user.setPassword(passwordEncoder.encode(newPassword));
|
||||||
|
user.setResetKey(null);
|
||||||
|
user.setResetDate(null);
|
||||||
|
return user;
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
public Optional<User> requestPasswordReset(String mail) {
|
||||||
|
return userRepository
|
||||||
|
.findOneByEmailIgnoreCase(mail)
|
||||||
|
.filter(User::isActivated)
|
||||||
|
.map(user -> {
|
||||||
|
user.setResetKey(RandomUtil.generateResetKey());
|
||||||
|
user.setResetDate(Instant.now());
|
||||||
|
return user;
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
public User registerUser(AdminUserDTO userDTO, String password) {
|
||||||
|
userRepository.findOneByLogin(userDTO.getLogin().toLowerCase()).ifPresent(existingUser -> {
|
||||||
|
boolean removed = removeNonActivatedUser(existingUser);
|
||||||
|
if (!removed) {
|
||||||
|
throw new UsernameAlreadyUsedException();
|
||||||
|
}
|
||||||
|
});
|
||||||
|
userRepository.findOneByEmailIgnoreCase(userDTO.getEmail()).ifPresent(existingUser -> {
|
||||||
|
boolean removed = removeNonActivatedUser(existingUser);
|
||||||
|
if (!removed) {
|
||||||
|
throw new EmailAlreadyUsedException();
|
||||||
|
}
|
||||||
|
});
|
||||||
|
User newUser = new User();
|
||||||
|
String encryptedPassword = passwordEncoder.encode(password);
|
||||||
|
newUser.setLogin(userDTO.getLogin().toLowerCase());
|
||||||
|
// new user gets initially a generated password
|
||||||
|
newUser.setPassword(encryptedPassword);
|
||||||
|
newUser.setFirstName(userDTO.getFirstName());
|
||||||
|
newUser.setLastName(userDTO.getLastName());
|
||||||
|
if (userDTO.getEmail() != null) {
|
||||||
|
newUser.setEmail(userDTO.getEmail().toLowerCase());
|
||||||
|
}
|
||||||
|
newUser.setImageUrl(userDTO.getImageUrl());
|
||||||
|
newUser.setLangKey(userDTO.getLangKey());
|
||||||
|
// new user is not active
|
||||||
|
newUser.setActivated(false);
|
||||||
|
// new user gets registration key
|
||||||
|
newUser.setActivationKey(RandomUtil.generateActivationKey());
|
||||||
|
Set<Authority> authorities = new HashSet<>();
|
||||||
|
authorityRepository.findById(AuthoritiesConstants.USER).ifPresent(authorities::add);
|
||||||
|
newUser.setAuthorities(authorities);
|
||||||
|
userRepository.save(newUser);
|
||||||
|
LOG.debug("Created Information for User: {}", newUser);
|
||||||
|
return newUser;
|
||||||
|
}
|
||||||
|
|
||||||
|
private boolean removeNonActivatedUser(User existingUser) {
|
||||||
|
if (existingUser.isActivated()) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
userRepository.delete(existingUser);
|
||||||
|
userRepository.flush();
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
public User createUser(AdminUserDTO userDTO) {
|
||||||
|
User user = new User();
|
||||||
|
user.setLogin(userDTO.getLogin().toLowerCase());
|
||||||
|
user.setFirstName(userDTO.getFirstName());
|
||||||
|
user.setLastName(userDTO.getLastName());
|
||||||
|
if (userDTO.getEmail() != null) {
|
||||||
|
user.setEmail(userDTO.getEmail().toLowerCase());
|
||||||
|
}
|
||||||
|
user.setImageUrl(userDTO.getImageUrl());
|
||||||
|
if (userDTO.getLangKey() == null) {
|
||||||
|
user.setLangKey(Constants.DEFAULT_LANGUAGE); // default language
|
||||||
|
} else {
|
||||||
|
user.setLangKey(userDTO.getLangKey());
|
||||||
|
}
|
||||||
|
String encryptedPassword = passwordEncoder.encode(RandomUtil.generatePassword());
|
||||||
|
user.setPassword(encryptedPassword);
|
||||||
|
user.setResetKey(RandomUtil.generateResetKey());
|
||||||
|
user.setResetDate(Instant.now());
|
||||||
|
user.setActivated(true);
|
||||||
|
if (userDTO.getAuthorities() != null) {
|
||||||
|
Set<Authority> authorities = userDTO
|
||||||
|
.getAuthorities()
|
||||||
|
.stream()
|
||||||
|
.map(authorityRepository::findById)
|
||||||
|
.filter(Optional::isPresent)
|
||||||
|
.map(Optional::get)
|
||||||
|
.collect(Collectors.toSet());
|
||||||
|
user.setAuthorities(authorities);
|
||||||
|
}
|
||||||
|
userRepository.save(user);
|
||||||
|
LOG.debug("Created Information for User: {}", user);
|
||||||
|
return user;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Update all information for a specific user, and return the modified user.
|
||||||
|
*
|
||||||
|
* @param userDTO user to update.
|
||||||
|
* @return updated user.
|
||||||
|
*/
|
||||||
|
public Optional<AdminUserDTO> updateUser(AdminUserDTO userDTO) {
|
||||||
|
return Optional.of(userRepository.findById(userDTO.getId()))
|
||||||
|
.filter(Optional::isPresent)
|
||||||
|
.map(Optional::get)
|
||||||
|
.map(user -> {
|
||||||
|
user.setLogin(userDTO.getLogin().toLowerCase());
|
||||||
|
user.setFirstName(userDTO.getFirstName());
|
||||||
|
user.setLastName(userDTO.getLastName());
|
||||||
|
if (userDTO.getEmail() != null) {
|
||||||
|
user.setEmail(userDTO.getEmail().toLowerCase());
|
||||||
|
}
|
||||||
|
user.setImageUrl(userDTO.getImageUrl());
|
||||||
|
user.setActivated(userDTO.isActivated());
|
||||||
|
user.setLangKey(userDTO.getLangKey());
|
||||||
|
Set<Authority> managedAuthorities = user.getAuthorities();
|
||||||
|
managedAuthorities.clear();
|
||||||
|
userDTO
|
||||||
|
.getAuthorities()
|
||||||
|
.stream()
|
||||||
|
.map(authorityRepository::findById)
|
||||||
|
.filter(Optional::isPresent)
|
||||||
|
.map(Optional::get)
|
||||||
|
.forEach(managedAuthorities::add);
|
||||||
|
userRepository.save(user);
|
||||||
|
LOG.debug("Changed Information for User: {}", user);
|
||||||
|
return user;
|
||||||
|
})
|
||||||
|
.map(AdminUserDTO::new);
|
||||||
|
}
|
||||||
|
|
||||||
|
public void deleteUser(String login) {
|
||||||
|
userRepository.findOneByLogin(login).ifPresent(user -> {
|
||||||
|
userRepository.delete(user);
|
||||||
|
LOG.debug("Deleted User: {}", user);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Update basic information (first name, last name, email, language) for the current user.
|
||||||
|
*
|
||||||
|
* @param firstName first name of user.
|
||||||
|
* @param lastName last name of user.
|
||||||
|
* @param email email id of user.
|
||||||
|
* @param langKey language key.
|
||||||
|
* @param imageUrl image URL of user.
|
||||||
|
*/
|
||||||
|
public void updateUser(String firstName, String lastName, String email, String langKey, String imageUrl) {
|
||||||
|
SecurityUtils.getCurrentUserLogin()
|
||||||
|
.flatMap(userRepository::findOneByLogin)
|
||||||
|
.ifPresent(user -> {
|
||||||
|
user.setFirstName(firstName);
|
||||||
|
user.setLastName(lastName);
|
||||||
|
if (email != null) {
|
||||||
|
user.setEmail(email.toLowerCase());
|
||||||
|
}
|
||||||
|
user.setLangKey(langKey);
|
||||||
|
user.setImageUrl(imageUrl);
|
||||||
|
userRepository.save(user);
|
||||||
|
LOG.debug("Changed Information for User: {}", user);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
@Transactional
|
||||||
|
public void changePassword(String currentClearTextPassword, String newPassword) {
|
||||||
|
SecurityUtils.getCurrentUserLogin()
|
||||||
|
.flatMap(userRepository::findOneByLogin)
|
||||||
|
.ifPresent(user -> {
|
||||||
|
String currentEncryptedPassword = user.getPassword();
|
||||||
|
if (!passwordEncoder.matches(currentClearTextPassword, currentEncryptedPassword)) {
|
||||||
|
throw new InvalidPasswordException();
|
||||||
|
}
|
||||||
|
String encryptedPassword = passwordEncoder.encode(newPassword);
|
||||||
|
user.setPassword(encryptedPassword);
|
||||||
|
LOG.debug("Changed password for User: {}", user);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
@Transactional(readOnly = true)
|
||||||
|
public Page<AdminUserDTO> getAllManagedUsers(Pageable pageable) {
|
||||||
|
return userRepository.findAll(pageable).map(AdminUserDTO::new);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Transactional(readOnly = true)
|
||||||
|
public Page<UserDTO> getAllPublicUsers(Pageable pageable) {
|
||||||
|
return userRepository.findAllByIdNotNullAndActivatedIsTrue(pageable).map(UserDTO::new);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Transactional(readOnly = true)
|
||||||
|
public Optional<User> getUserWithAuthoritiesByLogin(String login) {
|
||||||
|
return userRepository.findOneWithAuthoritiesByLogin(login);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Transactional(readOnly = true)
|
||||||
|
public Optional<User> getUserWithAuthorities() {
|
||||||
|
return SecurityUtils.getCurrentUserLogin().flatMap(userRepository::findOneWithAuthoritiesByLogin);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Not activated users should be automatically deleted after 3 days.
|
||||||
|
* <p>
|
||||||
|
* This is scheduled to get fired every day, at 01:00 (am).
|
||||||
|
*/
|
||||||
|
@Scheduled(cron = "0 0 1 * * ?")
|
||||||
|
public void removeNotActivatedUsers() {
|
||||||
|
userRepository
|
||||||
|
.findAllByActivatedIsFalseAndActivationKeyIsNotNullAndCreatedDateBefore(Instant.now().minus(3, ChronoUnit.DAYS))
|
||||||
|
.forEach(user -> {
|
||||||
|
LOG.debug("Deleting not activated user {}", user.getLogin());
|
||||||
|
userRepository.delete(user);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Gets a list of all the authorities.
|
||||||
|
* @return a list of all the authorities.
|
||||||
|
*/
|
||||||
|
@Transactional(readOnly = true)
|
||||||
|
public List<String> getAuthorities() {
|
||||||
|
return authorityRepository.findAll().stream().map(Authority::getName).toList();
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,13 @@
|
|||||||
|
package com.example.questionbank.service;
|
||||||
|
|
||||||
|
import java.io.Serial;
|
||||||
|
|
||||||
|
public class UsernameAlreadyUsedException extends RuntimeException {
|
||||||
|
|
||||||
|
@Serial
|
||||||
|
private static final long serialVersionUID = 1L;
|
||||||
|
|
||||||
|
public UsernameAlreadyUsedException() {
|
||||||
|
super("Login name already used!");
|
||||||
|
}
|
||||||
|
}
|
||||||
Loaded 100 of 508 files, more files were not shown because too many files have changed in this diff.
Show more
Reference in new issue
Block a user