moved files to monorepo and updated controllers to start with /api
This commit is contained in:
@@ -0,0 +1,4 @@
|
||||
spring.application.name=etfoglasi-server
|
||||
spring.datasource.url=jdbc:postgresql://localhost:5432/etfo-db
|
||||
spring.datasource.username=test
|
||||
spring.datasource.password=test
|
||||
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
@@ -0,0 +1,52 @@
|
||||
plugins {
|
||||
id 'java'
|
||||
id 'org.springframework.boot' version '3.5.6'
|
||||
id 'io.spring.dependency-management' version '1.1.7'
|
||||
id 'com.netflix.dgs.codegen' version '7.0.3'
|
||||
}
|
||||
|
||||
group = 'dev.ksan'
|
||||
version = '0.0.1-SNAPSHOT'
|
||||
description = 'etfoglasi-server'
|
||||
|
||||
java {
|
||||
toolchain {
|
||||
languageVersion = JavaLanguageVersion.of(21)
|
||||
}
|
||||
}
|
||||
configurations.all {
|
||||
resolutionStrategy {
|
||||
force 'com.graphql-java:java-dataloader:3.3.0' // Force version 3.3.0
|
||||
}
|
||||
}
|
||||
|
||||
repositories {
|
||||
mavenCentral()
|
||||
maven { url 'https://repo.maven.apache.org/maven2' }
|
||||
}
|
||||
|
||||
dependencies {
|
||||
implementation 'org.springframework.boot:spring-boot-starter-data-jpa'
|
||||
implementation 'org.springframework.boot:spring-boot-starter-web'
|
||||
developmentOnly 'org.springframework.boot:spring-boot-devtools'
|
||||
developmentOnly 'org.springframework.boot:spring-boot-docker-compose'
|
||||
runtimeOnly 'org.postgresql:postgresql'
|
||||
testImplementation 'org.springframework.boot:spring-boot-starter-test'
|
||||
testRuntimeOnly 'org.junit.platform:junit-platform-launcher'
|
||||
implementation "org.springframework.boot:spring-boot-starter-security"
|
||||
implementation("org.simplejavamail:simple-java-mail:8.0.1")
|
||||
implementation("net.sourceforge.htmlunit:htmlunit:2.70.0")
|
||||
runtimeOnly("io.jsonwebtoken:jjwt-jackson:0.13.0")
|
||||
implementation("io.jsonwebtoken:jjwt-api:0.13.0")
|
||||
runtimeOnly("io.jsonwebtoken:jjwt-impl:0.13.0")
|
||||
}
|
||||
|
||||
generateJava {
|
||||
schemaPaths = ["${projectDir}/src/main/resources/graphql-client"]
|
||||
packageName = 'dev.ksan.etfoglasiserver.codegen'
|
||||
generateClient = true
|
||||
}
|
||||
|
||||
tasks.named('test') {
|
||||
useJUnitPlatform()
|
||||
}
|
||||
@@ -0,0 +1,13 @@
|
||||
services:
|
||||
postgres:
|
||||
image: 'postgres:16'
|
||||
container_name: etfo-db
|
||||
environment:
|
||||
- 'POSTGRES_DB=etfo-db'
|
||||
- 'POSTGRES_PASSWORD=test'
|
||||
- 'POSTGRES_USER=test'
|
||||
volumes:
|
||||
- ./init.sql:/docker-entrypoint-initdb.d/init.sql
|
||||
- ./.db:/var/lib/postgresql/data
|
||||
ports:
|
||||
- '5432:5432'
|
||||
BIN
Binary file not shown.
@@ -0,0 +1,7 @@
|
||||
distributionBase=GRADLE_USER_HOME
|
||||
distributionPath=wrapper/dists
|
||||
distributionUrl=https\://services.gradle.org/distributions/gradle-8.14.3-bin.zip
|
||||
networkTimeout=10000
|
||||
validateDistributionUrl=true
|
||||
zipStoreBase=GRADLE_USER_HOME
|
||||
zipStorePath=wrapper/dists
|
||||
+251
@@ -0,0 +1,251 @@
|
||||
#!/bin/sh
|
||||
|
||||
#
|
||||
# Copyright © 2015-2021 the original authors.
|
||||
#
|
||||
# Licensed under the Apache License, Version 2.0 (the "License");
|
||||
# you may not use this file except in compliance with the License.
|
||||
# You may obtain a copy of the License at
|
||||
#
|
||||
# https://www.apache.org/licenses/LICENSE-2.0
|
||||
#
|
||||
# Unless required by applicable law or agreed to in writing, software
|
||||
# distributed under the License is distributed on an "AS IS" BASIS,
|
||||
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
# See the License for the specific language governing permissions and
|
||||
# limitations under the License.
|
||||
#
|
||||
# SPDX-License-Identifier: Apache-2.0
|
||||
#
|
||||
|
||||
##############################################################################
|
||||
#
|
||||
# Gradle start up script for POSIX generated by Gradle.
|
||||
#
|
||||
# Important for running:
|
||||
#
|
||||
# (1) You need a POSIX-compliant shell to run this script. If your /bin/sh is
|
||||
# noncompliant, but you have some other compliant shell such as ksh or
|
||||
# bash, then to run this script, type that shell name before the whole
|
||||
# command line, like:
|
||||
#
|
||||
# ksh Gradle
|
||||
#
|
||||
# Busybox and similar reduced shells will NOT work, because this script
|
||||
# requires all of these POSIX shell features:
|
||||
# * functions;
|
||||
# * expansions «$var», «${var}», «${var:-default}», «${var+SET}»,
|
||||
# «${var#prefix}», «${var%suffix}», and «$( cmd )»;
|
||||
# * compound commands having a testable exit status, especially «case»;
|
||||
# * various built-in commands including «command», «set», and «ulimit».
|
||||
#
|
||||
# Important for patching:
|
||||
#
|
||||
# (2) This script targets any POSIX shell, so it avoids extensions provided
|
||||
# by Bash, Ksh, etc; in particular arrays are avoided.
|
||||
#
|
||||
# The "traditional" practice of packing multiple parameters into a
|
||||
# space-separated string is a well documented source of bugs and security
|
||||
# problems, so this is (mostly) avoided, by progressively accumulating
|
||||
# options in "$@", and eventually passing that to Java.
|
||||
#
|
||||
# Where the inherited environment variables (DEFAULT_JVM_OPTS, JAVA_OPTS,
|
||||
# and GRADLE_OPTS) rely on word-splitting, this is performed explicitly;
|
||||
# see the in-line comments for details.
|
||||
#
|
||||
# There are tweaks for specific operating systems such as AIX, CygWin,
|
||||
# Darwin, MinGW, and NonStop.
|
||||
#
|
||||
# (3) This script is generated from the Groovy template
|
||||
# https://github.com/gradle/gradle/blob/HEAD/platforms/jvm/plugins-application/src/main/resources/org/gradle/api/internal/plugins/unixStartScript.txt
|
||||
# within the Gradle project.
|
||||
#
|
||||
# You can find Gradle at https://github.com/gradle/gradle/.
|
||||
#
|
||||
##############################################################################
|
||||
|
||||
# Attempt to set APP_HOME
|
||||
|
||||
# Resolve links: $0 may be a link
|
||||
app_path=$0
|
||||
|
||||
# Need this for daisy-chained symlinks.
|
||||
while
|
||||
APP_HOME=${app_path%"${app_path##*/}"} # leaves a trailing /; empty if no leading path
|
||||
[ -h "$app_path" ]
|
||||
do
|
||||
ls=$( ls -ld "$app_path" )
|
||||
link=${ls#*' -> '}
|
||||
case $link in #(
|
||||
/*) app_path=$link ;; #(
|
||||
*) app_path=$APP_HOME$link ;;
|
||||
esac
|
||||
done
|
||||
|
||||
# This is normally unused
|
||||
# shellcheck disable=SC2034
|
||||
APP_BASE_NAME=${0##*/}
|
||||
# Discard cd standard output in case $CDPATH is set (https://github.com/gradle/gradle/issues/25036)
|
||||
APP_HOME=$( cd -P "${APP_HOME:-./}" > /dev/null && printf '%s\n' "$PWD" ) || exit
|
||||
|
||||
# Use the maximum available, or set MAX_FD != -1 to use that value.
|
||||
MAX_FD=maximum
|
||||
|
||||
warn () {
|
||||
echo "$*"
|
||||
} >&2
|
||||
|
||||
die () {
|
||||
echo
|
||||
echo "$*"
|
||||
echo
|
||||
exit 1
|
||||
} >&2
|
||||
|
||||
# OS specific support (must be 'true' or 'false').
|
||||
cygwin=false
|
||||
msys=false
|
||||
darwin=false
|
||||
nonstop=false
|
||||
case "$( uname )" in #(
|
||||
CYGWIN* ) cygwin=true ;; #(
|
||||
Darwin* ) darwin=true ;; #(
|
||||
MSYS* | MINGW* ) msys=true ;; #(
|
||||
NONSTOP* ) nonstop=true ;;
|
||||
esac
|
||||
|
||||
CLASSPATH="\\\"\\\""
|
||||
|
||||
|
||||
# Determine the Java command to use to start the JVM.
|
||||
if [ -n "$JAVA_HOME" ] ; then
|
||||
if [ -x "$JAVA_HOME/jre/sh/java" ] ; then
|
||||
# IBM's JDK on AIX uses strange locations for the executables
|
||||
JAVACMD=$JAVA_HOME/jre/sh/java
|
||||
else
|
||||
JAVACMD=$JAVA_HOME/bin/java
|
||||
fi
|
||||
if [ ! -x "$JAVACMD" ] ; then
|
||||
die "ERROR: JAVA_HOME is set to an invalid directory: $JAVA_HOME
|
||||
|
||||
Please set the JAVA_HOME variable in your environment to match the
|
||||
location of your Java installation."
|
||||
fi
|
||||
else
|
||||
JAVACMD=java
|
||||
if ! command -v java >/dev/null 2>&1
|
||||
then
|
||||
die "ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH.
|
||||
|
||||
Please set the JAVA_HOME variable in your environment to match the
|
||||
location of your Java installation."
|
||||
fi
|
||||
fi
|
||||
|
||||
# Increase the maximum file descriptors if we can.
|
||||
if ! "$cygwin" && ! "$darwin" && ! "$nonstop" ; then
|
||||
case $MAX_FD in #(
|
||||
max*)
|
||||
# In POSIX sh, ulimit -H is undefined. That's why the result is checked to see if it worked.
|
||||
# shellcheck disable=SC2039,SC3045
|
||||
MAX_FD=$( ulimit -H -n ) ||
|
||||
warn "Could not query maximum file descriptor limit"
|
||||
esac
|
||||
case $MAX_FD in #(
|
||||
'' | soft) :;; #(
|
||||
*)
|
||||
# In POSIX sh, ulimit -n is undefined. That's why the result is checked to see if it worked.
|
||||
# shellcheck disable=SC2039,SC3045
|
||||
ulimit -n "$MAX_FD" ||
|
||||
warn "Could not set maximum file descriptor limit to $MAX_FD"
|
||||
esac
|
||||
fi
|
||||
|
||||
# Collect all arguments for the java command, stacking in reverse order:
|
||||
# * args from the command line
|
||||
# * the main class name
|
||||
# * -classpath
|
||||
# * -D...appname settings
|
||||
# * --module-path (only if needed)
|
||||
# * DEFAULT_JVM_OPTS, JAVA_OPTS, and GRADLE_OPTS environment variables.
|
||||
|
||||
# For Cygwin or MSYS, switch paths to Windows format before running java
|
||||
if "$cygwin" || "$msys" ; then
|
||||
APP_HOME=$( cygpath --path --mixed "$APP_HOME" )
|
||||
CLASSPATH=$( cygpath --path --mixed "$CLASSPATH" )
|
||||
|
||||
JAVACMD=$( cygpath --unix "$JAVACMD" )
|
||||
|
||||
# Now convert the arguments - kludge to limit ourselves to /bin/sh
|
||||
for arg do
|
||||
if
|
||||
case $arg in #(
|
||||
-*) false ;; # don't mess with options #(
|
||||
/?*) t=${arg#/} t=/${t%%/*} # looks like a POSIX filepath
|
||||
[ -e "$t" ] ;; #(
|
||||
*) false ;;
|
||||
esac
|
||||
then
|
||||
arg=$( cygpath --path --ignore --mixed "$arg" )
|
||||
fi
|
||||
# Roll the args list around exactly as many times as the number of
|
||||
# args, so each arg winds up back in the position where it started, but
|
||||
# possibly modified.
|
||||
#
|
||||
# NB: a `for` loop captures its iteration list before it begins, so
|
||||
# changing the positional parameters here affects neither the number of
|
||||
# iterations, nor the values presented in `arg`.
|
||||
shift # remove old arg
|
||||
set -- "$@" "$arg" # push replacement arg
|
||||
done
|
||||
fi
|
||||
|
||||
|
||||
# Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script.
|
||||
DEFAULT_JVM_OPTS='"-Xmx64m" "-Xms64m"'
|
||||
|
||||
# Collect all arguments for the java command:
|
||||
# * DEFAULT_JVM_OPTS, JAVA_OPTS, and optsEnvironmentVar are not allowed to contain shell fragments,
|
||||
# and any embedded shellness will be escaped.
|
||||
# * For example: A user cannot expect ${Hostname} to be expanded, as it is an environment variable and will be
|
||||
# treated as '${Hostname}' itself on the command line.
|
||||
|
||||
set -- \
|
||||
"-Dorg.gradle.appname=$APP_BASE_NAME" \
|
||||
-classpath "$CLASSPATH" \
|
||||
-jar "$APP_HOME/gradle/wrapper/gradle-wrapper.jar" \
|
||||
"$@"
|
||||
|
||||
# Stop when "xargs" is not available.
|
||||
if ! command -v xargs >/dev/null 2>&1
|
||||
then
|
||||
die "xargs is not available"
|
||||
fi
|
||||
|
||||
# Use "xargs" to parse quoted args.
|
||||
#
|
||||
# With -n1 it outputs one arg per line, with the quotes and backslashes removed.
|
||||
#
|
||||
# In Bash we could simply go:
|
||||
#
|
||||
# readarray ARGS < <( xargs -n1 <<<"$var" ) &&
|
||||
# set -- "${ARGS[@]}" "$@"
|
||||
#
|
||||
# but POSIX shell has neither arrays nor command substitution, so instead we
|
||||
# post-process each arg (as a line of input to sed) to backslash-escape any
|
||||
# character that might be a shell metacharacter, then use eval to reverse
|
||||
# that process (while maintaining the separation between arguments), and wrap
|
||||
# the whole thing up as a single "set" statement.
|
||||
#
|
||||
# This will of course break if any of these variables contains a newline or
|
||||
# an unmatched quote.
|
||||
#
|
||||
|
||||
eval "set -- $(
|
||||
printf '%s\n' "$DEFAULT_JVM_OPTS $JAVA_OPTS $GRADLE_OPTS" |
|
||||
xargs -n1 |
|
||||
sed ' s~[^-[:alnum:]+,./:=@_]~\\&~g; ' |
|
||||
tr '\n' ' '
|
||||
)" '"$@"'
|
||||
|
||||
exec "$JAVACMD" "$@"
|
||||
Vendored
+94
@@ -0,0 +1,94 @@
|
||||
@rem
|
||||
@rem Copyright 2015 the original author or authors.
|
||||
@rem
|
||||
@rem Licensed under the Apache License, Version 2.0 (the "License");
|
||||
@rem you may not use this file except in compliance with the License.
|
||||
@rem You may obtain a copy of the License at
|
||||
@rem
|
||||
@rem https://www.apache.org/licenses/LICENSE-2.0
|
||||
@rem
|
||||
@rem Unless required by applicable law or agreed to in writing, software
|
||||
@rem distributed under the License is distributed on an "AS IS" BASIS,
|
||||
@rem WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
@rem See the License for the specific language governing permissions and
|
||||
@rem limitations under the License.
|
||||
@rem
|
||||
@rem SPDX-License-Identifier: Apache-2.0
|
||||
@rem
|
||||
|
||||
@if "%DEBUG%"=="" @echo off
|
||||
@rem ##########################################################################
|
||||
@rem
|
||||
@rem Gradle startup script for Windows
|
||||
@rem
|
||||
@rem ##########################################################################
|
||||
|
||||
@rem Set local scope for the variables with windows NT shell
|
||||
if "%OS%"=="Windows_NT" setlocal
|
||||
|
||||
set DIRNAME=%~dp0
|
||||
if "%DIRNAME%"=="" set DIRNAME=.
|
||||
@rem This is normally unused
|
||||
set APP_BASE_NAME=%~n0
|
||||
set APP_HOME=%DIRNAME%
|
||||
|
||||
@rem Resolve any "." and ".." in APP_HOME to make it shorter.
|
||||
for %%i in ("%APP_HOME%") do set APP_HOME=%%~fi
|
||||
|
||||
@rem Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script.
|
||||
set DEFAULT_JVM_OPTS="-Xmx64m" "-Xms64m"
|
||||
|
||||
@rem Find java.exe
|
||||
if defined JAVA_HOME goto findJavaFromJavaHome
|
||||
|
||||
set JAVA_EXE=java.exe
|
||||
%JAVA_EXE% -version >NUL 2>&1
|
||||
if %ERRORLEVEL% equ 0 goto execute
|
||||
|
||||
echo. 1>&2
|
||||
echo ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH. 1>&2
|
||||
echo. 1>&2
|
||||
echo Please set the JAVA_HOME variable in your environment to match the 1>&2
|
||||
echo location of your Java installation. 1>&2
|
||||
|
||||
goto fail
|
||||
|
||||
:findJavaFromJavaHome
|
||||
set JAVA_HOME=%JAVA_HOME:"=%
|
||||
set JAVA_EXE=%JAVA_HOME%/bin/java.exe
|
||||
|
||||
if exist "%JAVA_EXE%" goto execute
|
||||
|
||||
echo. 1>&2
|
||||
echo ERROR: JAVA_HOME is set to an invalid directory: %JAVA_HOME% 1>&2
|
||||
echo. 1>&2
|
||||
echo Please set the JAVA_HOME variable in your environment to match the 1>&2
|
||||
echo location of your Java installation. 1>&2
|
||||
|
||||
goto fail
|
||||
|
||||
:execute
|
||||
@rem Setup the command line
|
||||
|
||||
set CLASSPATH=
|
||||
|
||||
|
||||
@rem Execute Gradle
|
||||
"%JAVA_EXE%" %DEFAULT_JVM_OPTS% %JAVA_OPTS% %GRADLE_OPTS% "-Dorg.gradle.appname=%APP_BASE_NAME%" -classpath "%CLASSPATH%" -jar "%APP_HOME%\gradle\wrapper\gradle-wrapper.jar" %*
|
||||
|
||||
:end
|
||||
@rem End local scope for the variables with windows NT shell
|
||||
if %ERRORLEVEL% equ 0 goto mainEnd
|
||||
|
||||
:fail
|
||||
rem Set variable GRADLE_EXIT_CONSOLE if you need the _script_ return code instead of
|
||||
rem the _cmd.exe /c_ return code!
|
||||
set EXIT_CODE=%ERRORLEVEL%
|
||||
if %EXIT_CODE% equ 0 set EXIT_CODE=1
|
||||
if not ""=="%GRADLE_EXIT_CONSOLE%" exit %EXIT_CODE%
|
||||
exit /b %EXIT_CODE%
|
||||
|
||||
:mainEnd
|
||||
if "%OS%"=="Windows_NT" endlocal
|
||||
|
||||
:omega
|
||||
@@ -0,0 +1,85 @@
|
||||
-- This script was generated by the ERD tool in pgAdmin 4.
|
||||
-- Please log an issue at https://github.com/pgadmin-org/pgadmin4/issues/new/choose if you find any bugs, including reproduction steps.
|
||||
CREATE EXTENSION IF NOT EXISTS "uuid-ossp";
|
||||
BEGIN;
|
||||
|
||||
|
||||
CREATE TABLE IF NOT EXISTS public.alt_subject
|
||||
(
|
||||
id uuid NOT NULL DEFAULT uuid_generate_v4(),
|
||||
name character varying(255) COLLATE pg_catalog."default" NOT NULL,
|
||||
subject_id uuid NOT NULL,
|
||||
CONSTRAINT alt_subject_pkey PRIMARY KEY (id),
|
||||
CONSTRAINT alt_subject_name_key UNIQUE (name)
|
||||
);
|
||||
|
||||
|
||||
|
||||
CREATE TABLE IF NOT EXISTS public.entries
|
||||
(
|
||||
id uuid NOT NULL,
|
||||
subject_id uuid NOT NULL,
|
||||
title text,
|
||||
info_entry text,
|
||||
paragraph text,
|
||||
time_published timestamp without time zone NOT NULL,
|
||||
filepath text,
|
||||
group_name text,
|
||||
|
||||
CONSTRAINT entries_pkey PRIMARY KEY (id)
|
||||
);
|
||||
|
||||
CREATE TABLE IF NOT EXISTS public.subjects
|
||||
(
|
||||
id uuid NOT NULL DEFAULT uuid_generate_v4(),
|
||||
code bigint,
|
||||
name character varying(255) COLLATE pg_catalog."default" NOT NULL,
|
||||
CONSTRAINT subjects_pkey PRIMARY KEY (id)
|
||||
);
|
||||
|
||||
CREATE TABLE IF NOT EXISTS public."user-subject"
|
||||
(
|
||||
user_id uuid NOT NULL,
|
||||
subject_id uuid NOT NULL,
|
||||
CONSTRAINT "user-subject_pkey" PRIMARY KEY (user_id, subject_id)
|
||||
);
|
||||
|
||||
CREATE TABLE IF NOT EXISTS public.users
|
||||
(
|
||||
id uuid NOT NULL DEFAULT uuid_generate_v4(),
|
||||
email character varying(255) COLLATE pg_catalog."default" NOT NULL,
|
||||
password character varying(255) COLLATE pg_catalog."default" NOT NULL,
|
||||
reg_time timestamp without time zone NOT NULL DEFAULT CURRENT_TIMESTAMP,
|
||||
notification_type character varying(255) COLLATE pg_catalog."default" NOT NULL DEFAULT 'NO_NOTIFICATION'::character varying,
|
||||
CONSTRAINT users_pkey PRIMARY KEY (id),
|
||||
CONSTRAINT unique_email UNIQUE (email)
|
||||
);
|
||||
|
||||
ALTER TABLE IF EXISTS public.alt_subject
|
||||
ADD CONSTRAINT alt_subject_subject_id_fkey FOREIGN KEY (subject_id)
|
||||
REFERENCES public.subjects (id) MATCH SIMPLE
|
||||
ON UPDATE NO ACTION
|
||||
ON DELETE CASCADE;
|
||||
|
||||
|
||||
ALTER TABLE IF EXISTS public.entries
|
||||
ADD CONSTRAINT subject_for_key FOREIGN KEY (subject_id)
|
||||
REFERENCES public.subjects (id) MATCH SIMPLE
|
||||
ON UPDATE NO ACTION
|
||||
ON DELETE NO ACTION;
|
||||
|
||||
|
||||
ALTER TABLE IF EXISTS public."user-subject"
|
||||
ADD CONSTRAINT for_subject FOREIGN KEY (subject_id)
|
||||
REFERENCES public.subjects (id) MATCH SIMPLE
|
||||
ON UPDATE NO ACTION
|
||||
ON DELETE CASCADE;
|
||||
|
||||
|
||||
ALTER TABLE IF EXISTS public."user-subject"
|
||||
ADD CONSTRAINT for_user FOREIGN KEY (user_id)
|
||||
REFERENCES public.users (id) MATCH SIMPLE
|
||||
ON UPDATE NO ACTION
|
||||
ON DELETE CASCADE;
|
||||
|
||||
END;
|
||||
@@ -0,0 +1 @@
|
||||
rootProject.name = 'etfoglasi-server'
|
||||
@@ -0,0 +1,54 @@
|
||||
package dev.ksan.etfoglasiserver;
|
||||
|
||||
import dev.ksan.etfoglasiserver.service.Scraper;
|
||||
import java.util.Scanner;
|
||||
|
||||
import dev.ksan.etfoglasiserver.service.SubjectService;
|
||||
import dev.ksan.etfoglasiserver.util.SubjectLoader;
|
||||
import org.springframework.beans.factory.annotation.Autowired;
|
||||
import org.springframework.boot.CommandLineRunner;
|
||||
import org.springframework.boot.SpringApplication;
|
||||
import org.springframework.boot.autoconfigure.SpringBootApplication;
|
||||
import org.springframework.context.ApplicationContext;
|
||||
import org.springframework.context.annotation.Bean;
|
||||
|
||||
@SpringBootApplication
|
||||
public class EtfoglasiServerApplication {
|
||||
|
||||
public static void main(String[] args) {
|
||||
ApplicationContext context =
|
||||
SpringApplication.run(EtfoglasiServerApplication.class, args);
|
||||
|
||||
Scraper scraper = context.getBean(Scraper.class);
|
||||
|
||||
SubjectService subjectService =
|
||||
context.getBean(SubjectService.class);
|
||||
|
||||
if (subjectService.count() <= 1) {
|
||||
SubjectLoader subjectLoader =
|
||||
context.getBean(SubjectLoader.class);
|
||||
|
||||
subjectLoader.loadSubjects();
|
||||
|
||||
System.out.println("Loading...");
|
||||
subjectLoader.loadAlts();
|
||||
} else {
|
||||
System.out.println("Subjects already loaded, skipping...");
|
||||
}
|
||||
|
||||
new Thread(scraper).start();
|
||||
}
|
||||
|
||||
|
||||
@Bean
|
||||
public CommandLineRunner commandLineRunner(SubjectLoader subjectLoader) {
|
||||
return args -> {
|
||||
subjectLoader.loadAlts();
|
||||
try{
|
||||
}catch (Exception e){
|
||||
e.printStackTrace();
|
||||
}
|
||||
subjectLoader.loadAlts();
|
||||
};
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,53 @@
|
||||
package dev.ksan.etfoglasiserver.config;
|
||||
|
||||
import dev.ksan.etfoglasiserver.service.JWTService;
|
||||
import dev.ksan.etfoglasiserver.service.MyUserDetailsService;
|
||||
import dev.ksan.etfoglasiserver.service.UserService;
|
||||
import jakarta.servlet.FilterChain;
|
||||
import jakarta.servlet.ServletException;
|
||||
import jakarta.servlet.http.HttpServletRequest;
|
||||
import jakarta.servlet.http.HttpServletResponse;
|
||||
import org.springframework.beans.factory.annotation.Autowired;
|
||||
import org.springframework.context.ApplicationContext;
|
||||
import org.springframework.security.authentication.UsernamePasswordAuthenticationToken;
|
||||
import org.springframework.security.core.context.SecurityContextHolder;
|
||||
import org.springframework.security.core.userdetails.UserDetails;
|
||||
import org.springframework.security.web.authentication.WebAuthenticationDetailsSource;
|
||||
import org.springframework.stereotype.Component;
|
||||
import org.springframework.web.filter.OncePerRequestFilter;
|
||||
|
||||
import java.io.IOException;
|
||||
|
||||
@Component
|
||||
public class JwtFilter extends OncePerRequestFilter {
|
||||
|
||||
@Autowired
|
||||
private JWTService jwtService;
|
||||
|
||||
@Autowired
|
||||
ApplicationContext context;
|
||||
|
||||
@Override
|
||||
protected void doFilterInternal(HttpServletRequest request, HttpServletResponse response, FilterChain filterChain) throws ServletException, IOException {
|
||||
String authHeader = request.getHeader("Authorization");
|
||||
String token = null;
|
||||
String username = null;
|
||||
|
||||
if (authHeader != null && authHeader.startsWith("Bearer ")) {
|
||||
token = authHeader.substring(7);
|
||||
username = jwtService.extractEmail(token);
|
||||
}
|
||||
|
||||
if (username != null && SecurityContextHolder.getContext().getAuthentication() == null) {
|
||||
UserDetails userDetails = context.getBean(MyUserDetailsService.class).loadUserByUsername(username);
|
||||
if (jwtService.validateToken(token, userDetails)) {
|
||||
UsernamePasswordAuthenticationToken authToken = new UsernamePasswordAuthenticationToken(userDetails, null, userDetails.getAuthorities());
|
||||
authToken.setDetails(new WebAuthenticationDetailsSource()
|
||||
.buildDetails(request));
|
||||
SecurityContextHolder.getContext().setAuthentication(authToken);
|
||||
}
|
||||
}
|
||||
|
||||
filterChain.doFilter(request, response);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,64 @@
|
||||
package dev.ksan.etfoglasiserver.config;
|
||||
|
||||
import org.springframework.beans.factory.annotation.Autowired;
|
||||
import org.springframework.context.annotation.Bean;
|
||||
import org.springframework.context.annotation.Configuration;
|
||||
import org.springframework.security.authentication.AuthenticationManager;
|
||||
import org.springframework.security.authentication.AuthenticationProvider;
|
||||
import org.springframework.security.authentication.dao.DaoAuthenticationProvider;
|
||||
import org.springframework.security.config.Customizer;
|
||||
import org.springframework.security.config.annotation.authentication.configuration.AuthenticationConfiguration;
|
||||
import org.springframework.security.config.annotation.web.builders.HttpSecurity;
|
||||
import org.springframework.security.config.annotation.web.configuration.EnableWebSecurity;
|
||||
import org.springframework.security.config.http.SessionCreationPolicy;
|
||||
import org.springframework.security.core.userdetails.UserDetailsService;
|
||||
import org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder;
|
||||
import org.springframework.security.web.SecurityFilterChain;
|
||||
import org.springframework.security.web.authentication.UsernamePasswordAuthenticationFilter;
|
||||
|
||||
@Configuration
|
||||
@EnableWebSecurity
|
||||
public class SecurityConfig {
|
||||
|
||||
@Autowired
|
||||
private JwtFilter jwtFilter;
|
||||
|
||||
@Autowired
|
||||
private UserDetailsService userDetailsService;
|
||||
|
||||
@Bean
|
||||
public SecurityFilterChain securityFilterChain(HttpSecurity http) throws Exception {
|
||||
|
||||
return http.csrf(customizer -> customizer.disable()).
|
||||
authorizeHttpRequests(request -> request
|
||||
.requestMatchers("/api/login", "/api/register").permitAll()
|
||||
.requestMatchers("/api/subjects/**", "/api/entries", "/api/groups").permitAll()
|
||||
.anyRequest().authenticated()).
|
||||
httpBasic(Customizer.withDefaults()).
|
||||
sessionManagement(session -> session.sessionCreationPolicy(SessionCreationPolicy.STATELESS))
|
||||
.addFilterBefore(jwtFilter, UsernamePasswordAuthenticationFilter.class)
|
||||
.build();
|
||||
|
||||
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
||||
@Bean
|
||||
public AuthenticationProvider authenticationProvider() {
|
||||
DaoAuthenticationProvider provider = new DaoAuthenticationProvider(userDetailsService);
|
||||
provider.setPasswordEncoder(new BCryptPasswordEncoder(12));
|
||||
|
||||
|
||||
return provider;
|
||||
}
|
||||
|
||||
@Bean
|
||||
public AuthenticationManager authenticationManager(AuthenticationConfiguration config) throws Exception {
|
||||
return config.getAuthenticationManager();
|
||||
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
@@ -0,0 +1,58 @@
|
||||
package dev.ksan.etfoglasiserver.controller;
|
||||
|
||||
import dev.ksan.etfoglasiserver.dto.EntryDTO;
|
||||
import dev.ksan.etfoglasiserver.model.Entry;
|
||||
import dev.ksan.etfoglasiserver.service.EntryService;
|
||||
import java.util.List;
|
||||
import java.util.UUID;
|
||||
import org.springframework.beans.factory.annotation.Autowired;
|
||||
import org.springframework.data.domain.Page;
|
||||
import org.springframework.data.domain.PageRequest;
|
||||
import org.springframework.data.domain.Pageable;
|
||||
import org.springframework.data.domain.Sort;
|
||||
import org.springframework.data.web.PageableDefault;
|
||||
import org.springframework.web.bind.annotation.*;
|
||||
|
||||
@RestController
|
||||
@RequestMapping("/api")
|
||||
public class EntryController {
|
||||
@Autowired EntryService service;
|
||||
|
||||
@GetMapping("/entries/{entryId}")
|
||||
public Entry getEntry(@PathVariable String entryId) {
|
||||
return service.getEntryById(entryId);
|
||||
}
|
||||
|
||||
//ignore this for now
|
||||
@PostMapping("/entries")
|
||||
public void addEntry(@RequestBody EntryDTO entry) {
|
||||
service.addEntry(entry);
|
||||
}
|
||||
|
||||
|
||||
@GetMapping("/groups")
|
||||
public List<String> getGroups() {
|
||||
return service.getAllGroups();
|
||||
}
|
||||
|
||||
|
||||
@GetMapping("/entries")
|
||||
public Page<Entry> getEntries(
|
||||
@RequestParam(required = false) String subjectId,
|
||||
@RequestParam(required = false) String groupName,
|
||||
@RequestParam(defaultValue = "0") int page
|
||||
) {
|
||||
UUID parsedSubjectId = subjectId != null ? UUID.fromString(subjectId) : null;
|
||||
return service.getEntries(parsedSubjectId, groupName, page);
|
||||
}
|
||||
|
||||
@PutMapping("/entries")
|
||||
public void updateEntry(@RequestBody EntryDTO entry) {
|
||||
service.updateEntry(entry);
|
||||
}
|
||||
|
||||
@DeleteMapping("/entries/{entryId}")
|
||||
public void deleteEntry(@PathVariable String entryId) {
|
||||
service.deleteEntry(entryId);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,35 @@
|
||||
package dev.ksan.etfoglasiserver.controller;
|
||||
|
||||
import dev.ksan.etfoglasiserver.model.SubjectAlt;
|
||||
import dev.ksan.etfoglasiserver.service.SubjectAltService;
|
||||
import java.util.List;
|
||||
import java.util.UUID;
|
||||
import org.springframework.beans.factory.annotation.Autowired;
|
||||
import org.springframework.web.bind.annotation.*;
|
||||
|
||||
@RestController
|
||||
@RequestMapping("/api")
|
||||
public class SubjectAltController {
|
||||
|
||||
@Autowired SubjectAltService service;
|
||||
|
||||
@GetMapping("/subjectalt")
|
||||
public List<SubjectAlt> getSubjects() {
|
||||
return service.getSubjectAlts();
|
||||
}
|
||||
|
||||
@GetMapping("/subjectalt/{subjectId}")
|
||||
public SubjectAlt getSubject(@PathVariable UUID subjectId) {
|
||||
return service.getSubjectAlt(subjectId);
|
||||
}
|
||||
|
||||
@PostMapping("/subjectalt")
|
||||
public void addSubject(@RequestBody SubjectAlt subject) {
|
||||
service.addSubjectAlt(subject);
|
||||
}
|
||||
|
||||
@DeleteMapping("/subjectalt/{subjectId}")
|
||||
public void deleteSubject(@PathVariable UUID subjectId) {
|
||||
service.deleteSubjectAlt(subjectId);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,39 @@
|
||||
package dev.ksan.etfoglasiserver.controller;
|
||||
|
||||
import dev.ksan.etfoglasiserver.model.Subject;
|
||||
import dev.ksan.etfoglasiserver.service.SubjectService;
|
||||
import java.util.List;
|
||||
import java.util.UUID;
|
||||
import org.springframework.beans.factory.annotation.Autowired;
|
||||
import org.springframework.web.bind.annotation.*;
|
||||
|
||||
@RestController
|
||||
@RequestMapping("/api")
|
||||
public class SubjectController {
|
||||
@Autowired SubjectService service;
|
||||
|
||||
@GetMapping("/subjects")
|
||||
public List<Subject> getSubjects() {
|
||||
return service.getSubjects();
|
||||
}
|
||||
|
||||
@GetMapping("/subjects/{subjectId}")
|
||||
public Subject getSubject(@PathVariable UUID subjectId) {
|
||||
return service.getSubject(subjectId);
|
||||
}
|
||||
|
||||
@PostMapping("/subjects")
|
||||
public void addSubject(@RequestBody Subject subject) {
|
||||
service.addSubject(subject);
|
||||
}
|
||||
|
||||
@PutMapping("/subjects")
|
||||
public void updateSubject(@RequestBody Subject subject) {
|
||||
service.updateSubject(subject);
|
||||
}
|
||||
|
||||
@DeleteMapping("/subjects/{subjectId}")
|
||||
public void deleteSubject(@PathVariable UUID subjectId) {
|
||||
service.deleteSubject(subjectId);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,95 @@
|
||||
package dev.ksan.etfoglasiserver.controller;
|
||||
|
||||
import dev.ksan.etfoglasiserver.dto.JwtResponseDTO;
|
||||
import dev.ksan.etfoglasiserver.dto.UserCreationDTO;
|
||||
import dev.ksan.etfoglasiserver.dto.UserDTO;
|
||||
import dev.ksan.etfoglasiserver.dto.UserLoginDTO;
|
||||
import dev.ksan.etfoglasiserver.model.NotificationMethod;
|
||||
import dev.ksan.etfoglasiserver.service.UserService;
|
||||
import java.util.List;
|
||||
import java.util.UUID;
|
||||
import org.springframework.beans.factory.annotation.Autowired;
|
||||
import org.springframework.http.HttpStatus;
|
||||
import org.springframework.http.ResponseEntity;
|
||||
import org.springframework.web.bind.annotation.*;
|
||||
|
||||
@RestController
|
||||
@RequestMapping("/api")
|
||||
public class UserController {
|
||||
|
||||
@Autowired UserService service;
|
||||
|
||||
|
||||
@GetMapping("users/{userId}")
|
||||
public ResponseEntity<UserDTO> getUserById(@PathVariable UUID userId) {
|
||||
|
||||
UserDTO user = service.getUserById(userId);
|
||||
|
||||
if (user != null) {
|
||||
return new ResponseEntity<>(user, HttpStatus.OK);
|
||||
}
|
||||
return new ResponseEntity<>(HttpStatus.NOT_FOUND);
|
||||
}
|
||||
|
||||
@PutMapping("/users")
|
||||
public ResponseEntity<UserDTO> updateUser(@RequestBody UserCreationDTO user) {
|
||||
|
||||
UserDTO userUpdated = service.updateUser(user);
|
||||
if (userUpdated != null) {
|
||||
return new ResponseEntity<>(userUpdated, HttpStatus.OK);
|
||||
}
|
||||
return new ResponseEntity<>(HttpStatus.BAD_REQUEST);
|
||||
}
|
||||
|
||||
@PutMapping("/users/{userId}/subjects/{subjectId}")
|
||||
public ResponseEntity<UserDTO> addSubject(
|
||||
@PathVariable UUID userId,
|
||||
@PathVariable UUID subjectId
|
||||
) {
|
||||
|
||||
UserDTO updatedUser = service.addSubject(userId, subjectId);
|
||||
|
||||
return ResponseEntity.ok(updatedUser);
|
||||
}
|
||||
|
||||
@DeleteMapping("/users/{userId}/subjects/{subjectId}")
|
||||
public ResponseEntity<UserDTO> removeSubject(
|
||||
@PathVariable UUID userId,
|
||||
@PathVariable UUID subjectId
|
||||
) {
|
||||
|
||||
UserDTO updatedUser = service.removeSubject(userId, subjectId);
|
||||
|
||||
return ResponseEntity.ok(updatedUser);
|
||||
}
|
||||
|
||||
@PostMapping("/login")
|
||||
public ResponseEntity<JwtResponseDTO> login(@RequestBody UserLoginDTO user) {
|
||||
JwtResponseDTO userVer = service.verify(user);
|
||||
|
||||
if (userVer != null) return new ResponseEntity<>(userVer, HttpStatus.OK);
|
||||
return new ResponseEntity<>(HttpStatus.BAD_REQUEST);
|
||||
}
|
||||
|
||||
@PostMapping("/register")
|
||||
public ResponseEntity<UserDTO> register(@RequestBody UserCreationDTO user) {
|
||||
|
||||
UserDTO newUser = service.register(user);
|
||||
if (newUser != null) return new ResponseEntity<>(newUser, HttpStatus.CREATED);
|
||||
return new ResponseEntity<>(HttpStatus.BAD_REQUEST);
|
||||
}
|
||||
//
|
||||
// @DeleteMapping("/users/{userId}")
|
||||
// public void deleteUser(@PathVariable UUID userId) {
|
||||
// service.deleteUser(userId);
|
||||
// }
|
||||
|
||||
/*
|
||||
@PostMapping("/users")
|
||||
public void addUser(@RequestBody UserCreationDTO user) {
|
||||
NotificationMethod method = NotificationMethod.valueOf(user.getNotification());
|
||||
user.setNotificationMethod(method);
|
||||
service.addUser(user);
|
||||
}
|
||||
*/
|
||||
}
|
||||
@@ -0,0 +1,105 @@
|
||||
package dev.ksan.etfoglasiserver.dto;
|
||||
|
||||
import java.time.LocalDateTime;
|
||||
import java.util.List;
|
||||
import java.util.UUID;
|
||||
|
||||
public class EntryDTO {
|
||||
|
||||
private UUID id;
|
||||
private String title;
|
||||
private String group;
|
||||
private LocalDateTime time_published;
|
||||
private String info_entry;
|
||||
private String paragraph;
|
||||
private String filepath;
|
||||
private UUID subjectId;
|
||||
|
||||
public EntryDTO() {}
|
||||
public EntryDTO( String title, String group, LocalDateTime time_published, String info_entry, String paragraph, String filepath, UUID subjectId) {
|
||||
this.title = title;
|
||||
this.group = group;
|
||||
this.time_published = time_published;
|
||||
this.info_entry = info_entry;
|
||||
this.paragraph = paragraph;
|
||||
this.filepath = filepath;
|
||||
this.subjectId = subjectId;
|
||||
|
||||
}
|
||||
public EntryDTO( String title, LocalDateTime time_published, String info_entry, List<String> paragraphs, String filepath, UUID subjectId) {
|
||||
this.title = title;
|
||||
this.time_published = time_published;
|
||||
this.info_entry = info_entry;
|
||||
this.paragraph = String.join("\n",paragraphs);
|
||||
this.filepath = filepath;
|
||||
this.subjectId = subjectId;
|
||||
|
||||
}
|
||||
|
||||
public UUID getSubjectId() {
|
||||
return subjectId;
|
||||
}
|
||||
|
||||
public UUID getId() {
|
||||
return id;
|
||||
}
|
||||
|
||||
public void setId(UUID id) {
|
||||
this.id = id;
|
||||
}
|
||||
public void setSubjectId(UUID subjectId) {
|
||||
this.subjectId = subjectId;
|
||||
}
|
||||
|
||||
public String getTitle() {
|
||||
return title;
|
||||
}
|
||||
|
||||
public void setTitle(String title) {
|
||||
this.title = title;
|
||||
}
|
||||
|
||||
public LocalDateTime getTime_published() {
|
||||
return time_published;
|
||||
}
|
||||
|
||||
public void setParagraph(List<String> paragraphs) {
|
||||
this.paragraph = String.join("\n", paragraphs);
|
||||
}
|
||||
|
||||
public void setTime_published(LocalDateTime time_published) {
|
||||
this.time_published = time_published;
|
||||
}
|
||||
|
||||
public String getInfo_entry() {
|
||||
return info_entry;
|
||||
}
|
||||
|
||||
public void setInfo_entry(String info_entry) {
|
||||
this.info_entry = info_entry;
|
||||
}
|
||||
|
||||
public String getParagraph() {
|
||||
return paragraph;
|
||||
}
|
||||
|
||||
public void setParagraph(String paragraph) {
|
||||
this.paragraph = paragraph;
|
||||
}
|
||||
|
||||
public String getFilepath() {
|
||||
return filepath;
|
||||
}
|
||||
|
||||
public void setFilepath(String filepath) {
|
||||
this.filepath = filepath;
|
||||
}
|
||||
|
||||
public String getGroup() {
|
||||
return group;
|
||||
}
|
||||
|
||||
public void setGroup(String group) {
|
||||
this.group = group;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,44 @@
|
||||
package dev.ksan.etfoglasiserver.dto;
|
||||
|
||||
public class JwtResponseDTO {
|
||||
|
||||
private String token;
|
||||
private String email;
|
||||
private String userId;
|
||||
private String message;
|
||||
|
||||
public JwtResponseDTO(String token, String email, String userId, String message) {
|
||||
this.token = token;
|
||||
this.email = email;
|
||||
this.userId = userId;
|
||||
this.message = message;
|
||||
}
|
||||
|
||||
public String getUserId() {
|
||||
return userId;
|
||||
}
|
||||
|
||||
public String getToken() {
|
||||
return token;
|
||||
}
|
||||
|
||||
public void setToken(String token) {
|
||||
this.token = token;
|
||||
}
|
||||
|
||||
public String getEmail() {
|
||||
return email;
|
||||
}
|
||||
|
||||
public void setEmail(String email) {
|
||||
this.email = email;
|
||||
}
|
||||
|
||||
public String getMessage() {
|
||||
return message;
|
||||
}
|
||||
|
||||
public void setMessage(String message) {
|
||||
this.message = message;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,56 @@
|
||||
package dev.ksan.etfoglasiserver.dto;
|
||||
|
||||
import dev.ksan.etfoglasiserver.model.NotificationMethod;
|
||||
import dev.ksan.etfoglasiserver.model.Subject;
|
||||
|
||||
import java.util.Set;
|
||||
|
||||
public class UserCreationDTO {
|
||||
private String email;
|
||||
private String newEmail;
|
||||
private String password;
|
||||
private Set<Subject> subjectSet;
|
||||
private String notification;
|
||||
|
||||
public String getEmail() {
|
||||
return email;
|
||||
}
|
||||
|
||||
public void setEmail(String email) {
|
||||
this.email = email;
|
||||
}
|
||||
|
||||
public String getNewEmail() {
|
||||
return newEmail;
|
||||
}
|
||||
public String getPassword() {
|
||||
return password;
|
||||
}
|
||||
|
||||
public void setPassword(String password) {
|
||||
this.password = password;
|
||||
}
|
||||
|
||||
public Set<Subject> getSubjectSet() {
|
||||
return subjectSet;
|
||||
}
|
||||
|
||||
public void setSubjectSet(Set<Subject> subjectSet) {
|
||||
this.subjectSet = subjectSet;
|
||||
}
|
||||
|
||||
public String getNotification() {
|
||||
return notification;
|
||||
}
|
||||
|
||||
|
||||
public void setNotification(String notificationMethod) {
|
||||
this.notification = notificationMethod;
|
||||
}
|
||||
|
||||
public void setNewEmail(String newEmail) {
|
||||
this.newEmail = newEmail;
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
@@ -0,0 +1,47 @@
|
||||
package dev.ksan.etfoglasiserver.dto;
|
||||
|
||||
import dev.ksan.etfoglasiserver.model.NotificationMethod;
|
||||
import dev.ksan.etfoglasiserver.model.Subject;
|
||||
import java.util.Set;
|
||||
import java.util.UUID;
|
||||
|
||||
public class UserDTO {
|
||||
private UUID id;
|
||||
private String email;
|
||||
private Set<Subject> subjectSet;
|
||||
|
||||
public UserDTO() {}
|
||||
|
||||
public UserDTO(
|
||||
UUID id, String email, Set<Subject> subjectSet, NotificationMethod notificationMethod2) {
|
||||
this.id = id;
|
||||
this.email = email;
|
||||
this.subjectSet = subjectSet;
|
||||
}
|
||||
|
||||
public UUID getId() {
|
||||
return id;
|
||||
}
|
||||
|
||||
public void setId(UUID id) {
|
||||
this.id = id;
|
||||
}
|
||||
|
||||
public String getEmail() {
|
||||
return email;
|
||||
}
|
||||
|
||||
public void setEmail(String email) {
|
||||
this.email = email;
|
||||
}
|
||||
|
||||
public Set<Subject> getSubjectSet() {
|
||||
return subjectSet;
|
||||
}
|
||||
|
||||
public void setSubjectSet(Set<Subject> subjectSet) {
|
||||
this.subjectSet = subjectSet;
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
@@ -0,0 +1,23 @@
|
||||
package dev.ksan.etfoglasiserver.dto;
|
||||
|
||||
public class UserLoginDTO {
|
||||
|
||||
private String email;
|
||||
private String password;
|
||||
|
||||
public String getEmail() {
|
||||
return email;
|
||||
}
|
||||
|
||||
public void setEmail(String email) {
|
||||
this.email = email;
|
||||
}
|
||||
|
||||
public String getPassword() {
|
||||
return password;
|
||||
}
|
||||
|
||||
public void setPassword(String password) {
|
||||
this.password = password;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,178 @@
|
||||
package dev.ksan.etfoglasiserver.model;
|
||||
|
||||
import dev.ksan.etfoglasiserver.dto.EntryDTO;
|
||||
import dev.ksan.etfoglasiserver.util.HashGenerator;
|
||||
import jakarta.persistence.*;
|
||||
|
||||
import java.time.LocalDateTime;
|
||||
import java.util.List;
|
||||
import java.util.UUID;
|
||||
|
||||
@Entity
|
||||
@Table(name = "entries")
|
||||
public class Entry {
|
||||
@Id
|
||||
@GeneratedValue
|
||||
@Column(columnDefinition = "uuid", updatable = false, nullable = false)
|
||||
private UUID id;
|
||||
|
||||
|
||||
@ManyToOne(fetch = FetchType.EAGER)
|
||||
@JoinColumn(nullable = false)
|
||||
private Subject subject;
|
||||
|
||||
@Column private String title;
|
||||
@Column(name = "time_published")
|
||||
private LocalDateTime timePublished;
|
||||
|
||||
@Column(name = "group_name")
|
||||
private String groupName;
|
||||
|
||||
private String info_entry;
|
||||
private String paragraph;
|
||||
private String filepath;
|
||||
|
||||
public Entry() {}
|
||||
|
||||
public Entry(
|
||||
String title,
|
||||
LocalDateTime time_published,
|
||||
String info_entry,
|
||||
String paragraph,
|
||||
String filepath) {
|
||||
this.title = title;
|
||||
this.timePublished= time_published;
|
||||
this.info_entry = info_entry;
|
||||
this.paragraph = paragraph;
|
||||
this.filepath = filepath;
|
||||
}
|
||||
|
||||
public Entry(
|
||||
String title,
|
||||
LocalDateTime time_published,
|
||||
String info_entry,
|
||||
List<String> paragraph,
|
||||
String filepath) {
|
||||
this.title = title;
|
||||
this.timePublished= time_published;
|
||||
this.info_entry = info_entry;
|
||||
this.paragraph = String.join("\n", paragraph);
|
||||
this.filepath = filepath;
|
||||
}
|
||||
public Entry( String title,String group, LocalDateTime time_published, String info_entry, List<String> paragraphs, String filepath, Subject subjectId) {
|
||||
this.title = title;
|
||||
this.groupName = group;
|
||||
this.timePublished= time_published;
|
||||
this.info_entry = info_entry;
|
||||
this.paragraph = String.join("\n",paragraphs);
|
||||
this.filepath = filepath;
|
||||
this.subject= subjectId;
|
||||
|
||||
}
|
||||
public Entry(EntryDTO entry) {
|
||||
this.title = entry.getTitle();
|
||||
this.paragraph = entry.getParagraph();
|
||||
this.info_entry = entry.getInfo_entry();
|
||||
this.filepath = entry.getFilepath();
|
||||
this.timePublished= entry.getTime_published();
|
||||
}
|
||||
|
||||
public String getParagraph() {
|
||||
return paragraph;
|
||||
}
|
||||
|
||||
public Subject getSubject() {
|
||||
return subject;
|
||||
}
|
||||
|
||||
public void setSubject(Subject subject) {
|
||||
this.subject = subject;
|
||||
}
|
||||
|
||||
public void setParagraph(List<String> paragraphs) {
|
||||
this.paragraph = String.join("\n", paragraphs);
|
||||
}
|
||||
|
||||
public void setParagraph(String paragraph) {
|
||||
this.paragraph = paragraph;
|
||||
}
|
||||
|
||||
public void setTitle(String title) {
|
||||
this.title = title;
|
||||
}
|
||||
|
||||
public String getTitle() {
|
||||
return title;
|
||||
}
|
||||
|
||||
public LocalDateTime getTimePublished() {
|
||||
return timePublished;
|
||||
}
|
||||
|
||||
public String getInfo() {
|
||||
return info_entry;
|
||||
}
|
||||
public UUID getId() {
|
||||
return id;
|
||||
}
|
||||
@Override
|
||||
public boolean equals(Object o) {
|
||||
if (this == o) return true;
|
||||
if (o == null) return false;
|
||||
Entry subject = (Entry) o;
|
||||
if (title.equals(subject.getTitle())
|
||||
&& timePublished.equals(subject.getTimePublished())
|
||||
&& info_entry.equals(subject.getInfo())) {
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
return "Entry{"
|
||||
+ "id="
|
||||
+ id
|
||||
+ ", subject="
|
||||
+ (subject != null ? subject.getId() : "null")
|
||||
+ ", title='"
|
||||
+ title
|
||||
+ '\''
|
||||
+ ", time_published="
|
||||
+ timePublished
|
||||
+ ", info_entry='"
|
||||
+ info_entry
|
||||
+ '\''
|
||||
+ ", paragraph='"
|
||||
+ paragraph
|
||||
+ '\''
|
||||
+ ", filepath='"
|
||||
+ filepath
|
||||
+ '\''
|
||||
+ '}';
|
||||
}
|
||||
|
||||
public String getFilepath() {
|
||||
return filepath;
|
||||
}
|
||||
|
||||
public void setFilepath(String filepath) {
|
||||
this.filepath = filepath;
|
||||
}
|
||||
|
||||
public void setInfo_entry(String infoEntry) {
|
||||
this.info_entry = infoEntry;
|
||||
}
|
||||
|
||||
public void setTime_published(LocalDateTime timePublished) {
|
||||
this.timePublished= timePublished;
|
||||
}
|
||||
|
||||
public String getGroup() {
|
||||
return groupName;
|
||||
}
|
||||
|
||||
public void setGroup(String group) {
|
||||
this.groupName = group;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,8 @@
|
||||
package dev.ksan.etfoglasiserver.model;
|
||||
|
||||
public enum NotificationMethod {
|
||||
|
||||
NO_NOTIFICATION,
|
||||
EMAIL,
|
||||
PUSH_NOTIFICATION
|
||||
}
|
||||
@@ -0,0 +1,48 @@
|
||||
package dev.ksan.etfoglasiserver.model;
|
||||
|
||||
import jakarta.persistence.*;
|
||||
|
||||
import java.util.Set;
|
||||
import java.util.UUID;
|
||||
|
||||
@Entity
|
||||
@Table(name = "subjects")
|
||||
public class Subject {
|
||||
|
||||
@Id
|
||||
@GeneratedValue(strategy = GenerationType.AUTO)
|
||||
@Column(columnDefinition = "uuid")
|
||||
private UUID id;
|
||||
|
||||
@Column private long code;
|
||||
|
||||
@Column(nullable = false)
|
||||
private String name;
|
||||
|
||||
|
||||
|
||||
@ManyToMany(mappedBy = "subjectSet", fetch = FetchType.LAZY)
|
||||
Set<User> users;
|
||||
public Subject(){
|
||||
}
|
||||
|
||||
public UUID getId() {
|
||||
return id;
|
||||
}
|
||||
|
||||
public long getCode() {
|
||||
return code;
|
||||
}
|
||||
|
||||
public void setCode(long code) {
|
||||
this.code = code;
|
||||
}
|
||||
|
||||
public String getName() {
|
||||
return name;
|
||||
}
|
||||
|
||||
public void setName(String name) {
|
||||
this.name = name;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,53 @@
|
||||
package dev.ksan.etfoglasiserver.model;
|
||||
|
||||
import jakarta.persistence.Column;
|
||||
import jakarta.persistence.Entity;
|
||||
import jakarta.persistence.FetchType;
|
||||
import jakarta.persistence.GeneratedValue;
|
||||
import jakarta.persistence.GenerationType;
|
||||
import jakarta.persistence.Id;
|
||||
import jakarta.persistence.JoinColumn;
|
||||
import jakarta.persistence.ManyToOne;
|
||||
import jakarta.persistence.Table;
|
||||
import java.util.UUID;
|
||||
|
||||
@Entity
|
||||
@Table(name = "alt_subject")
|
||||
public class SubjectAlt {
|
||||
@Id
|
||||
@GeneratedValue(strategy = GenerationType.AUTO)
|
||||
private UUID id;
|
||||
|
||||
@Column(nullable = false)
|
||||
String name;
|
||||
|
||||
@ManyToOne(fetch = FetchType.EAGER)
|
||||
@JoinColumn(nullable = false)
|
||||
private Subject subject;
|
||||
|
||||
public SubjectAlt() {}
|
||||
public SubjectAlt(String name, Subject subject) {
|
||||
this.name = name;
|
||||
this.subject = subject;
|
||||
}
|
||||
|
||||
public UUID getId() {
|
||||
return id;
|
||||
}
|
||||
|
||||
public String getName() {
|
||||
return name;
|
||||
}
|
||||
|
||||
public void setName(String name) {
|
||||
this.name = name;
|
||||
}
|
||||
|
||||
public Subject getSubject() {
|
||||
return subject;
|
||||
}
|
||||
|
||||
public void setSubject(Subject subject) {
|
||||
this.subject = subject;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,92 @@
|
||||
package dev.ksan.etfoglasiserver.model;
|
||||
|
||||
import dev.ksan.etfoglasiserver.dto.UserCreationDTO;
|
||||
import jakarta.persistence.*;
|
||||
import java.time.LocalDateTime;
|
||||
import java.util.Set;
|
||||
import java.util.UUID;
|
||||
|
||||
@Entity
|
||||
@Table(name = "users")
|
||||
public class User {
|
||||
|
||||
@Id
|
||||
@GeneratedValue(strategy = GenerationType.AUTO)
|
||||
private UUID id;
|
||||
|
||||
@Column(nullable = false, length = 255)
|
||||
private String email;
|
||||
|
||||
@Column(nullable = false, length = 255)
|
||||
private String password;
|
||||
|
||||
@Column(
|
||||
nullable = false,
|
||||
updatable = false,
|
||||
insertable = false,
|
||||
columnDefinition = "TIMESTAMP DEFAULT CURRENT_TIMESTAMP")
|
||||
private LocalDateTime regTime;
|
||||
|
||||
@Enumerated(EnumType.STRING)
|
||||
@Column(name = "notification_type", nullable = false)
|
||||
private NotificationMethod notificationType = NotificationMethod.NO_NOTIFICATION;
|
||||
|
||||
@ManyToMany(fetch = FetchType.LAZY )
|
||||
@JoinTable(name = "user-subject", joinColumns = @JoinColumn(name = "user_id"),
|
||||
inverseJoinColumns = @JoinColumn(name = "subject_id"))
|
||||
private Set<Subject> subjectSet;
|
||||
|
||||
public User() {}
|
||||
|
||||
public User(UserCreationDTO user) {
|
||||
this.email = user.getEmail();
|
||||
this.password = user.getPassword();
|
||||
}
|
||||
|
||||
public UUID getId() {
|
||||
return id;
|
||||
}
|
||||
|
||||
public void setId(UUID id) {
|
||||
this.id = id;
|
||||
}
|
||||
|
||||
public String getEmail() {
|
||||
return email;
|
||||
}
|
||||
|
||||
public void setEmail(String email) {
|
||||
this.email = email;
|
||||
}
|
||||
|
||||
public String getPassword() {
|
||||
return password;
|
||||
}
|
||||
|
||||
|
||||
public void setPassword(String password) {
|
||||
this.password = password;
|
||||
}
|
||||
|
||||
public LocalDateTime getRegTime() {
|
||||
return regTime;
|
||||
}
|
||||
|
||||
public Set<Subject> getSubjectSet() {
|
||||
return subjectSet;
|
||||
}
|
||||
|
||||
public void setSubjectSet(Set<Subject> subjectSet) {
|
||||
this.subjectSet = subjectSet;
|
||||
}
|
||||
|
||||
|
||||
|
||||
public void setNotificationMethod(NotificationMethod notificationMethod) {
|
||||
this.notificationType = notificationMethod;
|
||||
}
|
||||
|
||||
public NotificationMethod getNotificationMethod() {
|
||||
return notificationType;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,52 @@
|
||||
package dev.ksan.etfoglasiserver.model;
|
||||
|
||||
import org.springframework.security.core.GrantedAuthority;
|
||||
import org.springframework.security.core.authority.SimpleGrantedAuthority;
|
||||
import org.springframework.security.core.userdetails.UserDetails;
|
||||
|
||||
import java.util.Collection;
|
||||
import java.util.Collections;
|
||||
|
||||
public class UserPrincipal implements UserDetails {
|
||||
|
||||
private User user;
|
||||
|
||||
public UserPrincipal(User user) {
|
||||
this.user = user;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Collection<? extends GrantedAuthority> getAuthorities() {
|
||||
return Collections.singleton(new SimpleGrantedAuthority("USER"));
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getPassword() {
|
||||
return user.getPassword();
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getUsername() {
|
||||
return user.getEmail();
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isAccountNonExpired() {
|
||||
return true;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isAccountNonLocked() {
|
||||
return true;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isCredentialsNonExpired() {
|
||||
return true;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isEnabled() {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,25 @@
|
||||
package dev.ksan.etfoglasiserver.repository;
|
||||
|
||||
import dev.ksan.etfoglasiserver.model.Entry;
|
||||
|
||||
import java.time.LocalDateTime;
|
||||
import java.util.Optional;
|
||||
import java.util.UUID;
|
||||
|
||||
import dev.ksan.etfoglasiserver.model.Subject;
|
||||
import org.springframework.data.domain.Page;
|
||||
import org.springframework.data.domain.Pageable;
|
||||
import org.springframework.data.jpa.repository.JpaRepository;
|
||||
import org.springframework.stereotype.Repository;
|
||||
|
||||
@Repository
|
||||
public interface EntryRepo extends JpaRepository<Entry, String> {
|
||||
|
||||
Optional<Entry> findByTitleAndTimePublishedAndSubject(String title, LocalDateTime timePublished, Subject subject);
|
||||
|
||||
Page<Entry> findAllByOrderByTimePublishedDesc(Pageable pageable);
|
||||
Page<Entry> findBySubjectOrderByTimePublishedDesc(Subject subject, Pageable pageable);
|
||||
Page<Entry> findByGroupNameOrderByTimePublishedDesc(String groupName, Pageable pageable);
|
||||
Page<Entry> findBySubjectAndGroupNameOrderByTimePublishedDesc(Subject subject, String groupName, Pageable pageable);
|
||||
}
|
||||
|
||||
@@ -0,0 +1,18 @@
|
||||
package dev.ksan.etfoglasiserver.repository;
|
||||
|
||||
import dev.ksan.etfoglasiserver.model.Subject;
|
||||
import dev.ksan.etfoglasiserver.model.SubjectAlt;
|
||||
|
||||
import java.util.List;
|
||||
import java.util.UUID;
|
||||
|
||||
import org.springframework.data.jpa.repository.JpaRepository;
|
||||
import org.springframework.data.jpa.repository.Query;
|
||||
import org.springframework.data.repository.query.Param;
|
||||
import org.springframework.stereotype.Repository;
|
||||
|
||||
@Repository
|
||||
public interface SubjectAltRepo extends JpaRepository<SubjectAlt, UUID> {
|
||||
@Query("SELECT y.subject FROM SubjectAlt y WHERE :title ILIKE CONCAT('%', y.name, '%')")
|
||||
public List<Subject> findSubjectIdWithTitle(@Param("title") String title);
|
||||
}
|
||||
@@ -0,0 +1,12 @@
|
||||
package dev.ksan.etfoglasiserver.repository;
|
||||
|
||||
import dev.ksan.etfoglasiserver.model.Subject;
|
||||
import java.util.UUID;
|
||||
import org.springframework.data.jpa.repository.JpaRepository;
|
||||
import org.springframework.stereotype.Repository;
|
||||
|
||||
@Repository
|
||||
public interface SubjectRepo extends JpaRepository<Subject, UUID> {
|
||||
boolean existsByName(String name);
|
||||
Subject findByName(String name);
|
||||
}
|
||||
@@ -0,0 +1,22 @@
|
||||
package dev.ksan.etfoglasiserver.repository;
|
||||
|
||||
import dev.ksan.etfoglasiserver.model.User;
|
||||
import org.springframework.data.jpa.repository.JpaRepository;
|
||||
import org.springframework.data.jpa.repository.Query;
|
||||
import org.springframework.data.repository.query.Param;
|
||||
import org.springframework.stereotype.Repository;
|
||||
|
||||
import java.util.List;
|
||||
import java.util.Optional;
|
||||
import java.util.UUID;
|
||||
|
||||
@Repository
|
||||
public interface UserRepo extends JpaRepository<User, UUID> {
|
||||
|
||||
@Query("SELECT u FROM User u WHERE u.email = :email")
|
||||
Optional<User> findByEmail(String email);
|
||||
|
||||
|
||||
@Query("SELECT u FROM User u JOIN u.subjectSet s WHERE s.id = :subjectId")
|
||||
List<User> findUsersBySubjectId(@Param("subjectId") UUID id);
|
||||
}
|
||||
@@ -0,0 +1,127 @@
|
||||
package dev.ksan.etfoglasiserver.service;
|
||||
|
||||
import dev.ksan.etfoglasiserver.dto.EntryDTO;
|
||||
import dev.ksan.etfoglasiserver.model.Entry;
|
||||
import dev.ksan.etfoglasiserver.model.Subject;
|
||||
import dev.ksan.etfoglasiserver.repository.EntryRepo;
|
||||
import dev.ksan.etfoglasiserver.repository.SubjectRepo;
|
||||
import java.util.List;
|
||||
import java.util.Objects;
|
||||
import java.util.Optional;
|
||||
import java.util.UUID;
|
||||
import org.springframework.beans.factory.annotation.Autowired;
|
||||
import org.springframework.data.domain.Page;
|
||||
import org.springframework.data.domain.PageRequest;
|
||||
import org.springframework.data.domain.Pageable;
|
||||
import org.springframework.data.domain.Sort;
|
||||
import org.springframework.http.HttpStatus;
|
||||
import org.springframework.stereotype.Service;
|
||||
import org.springframework.web.server.ResponseStatusException;
|
||||
|
||||
@Service
|
||||
public class EntryService {
|
||||
|
||||
@Autowired
|
||||
EntryRepo entryRepo;
|
||||
|
||||
@Autowired
|
||||
SubjectService subjectService;
|
||||
|
||||
public List<Entry> getEntries() {
|
||||
return entryRepo.findAll();
|
||||
}
|
||||
|
||||
public Entry getEntryById(String id) {
|
||||
return entryRepo.findById(id).orElseThrow(() -> new RuntimeException("Entry not found"));
|
||||
}
|
||||
|
||||
public void addEntry(EntryDTO entry) {
|
||||
|
||||
Subject subject =
|
||||
subjectService
|
||||
.findById(entry.getSubjectId());
|
||||
Entry newEntry = new Entry(entry);
|
||||
newEntry.setSubject(subject);
|
||||
entryRepo.save(newEntry);
|
||||
}
|
||||
|
||||
public void addEntry(Entry entry) {
|
||||
Optional<Entry> existing = entryRepo.findByTitleAndTimePublishedAndSubject(
|
||||
entry.getTitle(),
|
||||
entry.getTimePublished(),
|
||||
entry.getSubject()
|
||||
);
|
||||
|
||||
if (existing.isPresent()) {
|
||||
System.out.println("Duplicate entry, skipping");
|
||||
return;
|
||||
}
|
||||
|
||||
try {
|
||||
entryRepo.save(entry);
|
||||
} catch (Exception e) {
|
||||
System.out.println("Duplicate");
|
||||
return;
|
||||
}
|
||||
subjectService.notify(entry);
|
||||
}
|
||||
|
||||
public void updateEntry(EntryDTO entry) {
|
||||
Subject subject =
|
||||
subjectService
|
||||
.findById(entry.getSubjectId());
|
||||
Entry updateEntry = entryRepo.findById(entry.getId().toString()).orElseThrow(() -> new RuntimeException("Entry not found"));
|
||||
updateEntry.setSubject(subject);
|
||||
if (entry.getTitle() != null) {
|
||||
updateEntry.setTitle(entry.getTitle());
|
||||
}
|
||||
if (entry.getInfo_entry() != null) {
|
||||
updateEntry.setInfo_entry(entry.getInfo_entry());
|
||||
}
|
||||
if (entry.getParagraph() != null) {
|
||||
updateEntry.setParagraph(entry.getParagraph());
|
||||
}
|
||||
if (entry.getFilepath() != null) {
|
||||
updateEntry.setFilepath(entry.getFilepath());
|
||||
}
|
||||
if (entry.getTime_published() != null) {
|
||||
updateEntry.setTime_published(entry.getTime_published());
|
||||
}
|
||||
entryRepo.save(updateEntry);
|
||||
}
|
||||
|
||||
public void deleteEntry(String id) {
|
||||
entryRepo.deleteById(id);
|
||||
}
|
||||
|
||||
|
||||
public Page<Entry> getEntries(UUID subjectId, String groupName, int page) {
|
||||
Pageable pageable = PageRequest.of(page, 20, Sort.by("timePublished").descending());
|
||||
|
||||
if (subjectId != null && groupName != null) {
|
||||
Subject subject = subjectService.findById(subjectId);
|
||||
return entryRepo.findBySubjectAndGroupNameOrderByTimePublishedDesc(subject, groupName, pageable);
|
||||
}
|
||||
if (subjectId != null) {
|
||||
Subject subject = subjectService.findById(subjectId);
|
||||
return entryRepo.findBySubjectOrderByTimePublishedDesc(subject, pageable);
|
||||
}
|
||||
if (groupName != null) {
|
||||
return entryRepo.findByGroupNameOrderByTimePublishedDesc(groupName, pageable);
|
||||
}
|
||||
|
||||
return entryRepo.findAllByOrderByTimePublishedDesc(pageable);
|
||||
}
|
||||
|
||||
public List<String> getAllGroups() {
|
||||
List<Entry> allEntries = entryRepo.findAll();
|
||||
|
||||
// Extract unique group names
|
||||
return allEntries.stream()
|
||||
.map(Entry::getGroup)
|
||||
.filter(Objects::nonNull)
|
||||
.distinct()
|
||||
.sorted()
|
||||
.toList();
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,77 @@
|
||||
package dev.ksan.etfoglasiserver.service;
|
||||
|
||||
import dev.ksan.etfoglasiserver.model.User;
|
||||
import io.jsonwebtoken.Claims;
|
||||
import io.jsonwebtoken.Jwts;
|
||||
import java.security.NoSuchAlgorithmException;
|
||||
import java.util.*;
|
||||
import java.util.function.Function;
|
||||
import javax.crypto.KeyGenerator;
|
||||
import javax.crypto.SecretKey;
|
||||
|
||||
import io.jsonwebtoken.io.Decoders;
|
||||
import io.jsonwebtoken.security.Keys;
|
||||
import org.springframework.beans.factory.annotation.Autowired;
|
||||
import org.springframework.security.core.userdetails.UserDetails;
|
||||
import org.springframework.stereotype.Service;
|
||||
|
||||
@Service
|
||||
public class JWTService {
|
||||
|
||||
//TODO add persistent token env variable
|
||||
private String secretKey = "";
|
||||
|
||||
private JWTService() {
|
||||
try {
|
||||
KeyGenerator keyGen = KeyGenerator.getInstance("HmacSHA256");
|
||||
SecretKey secKey = keyGen.generateKey();
|
||||
secretKey = Base64.getEncoder().encodeToString(secKey.getEncoded());
|
||||
} catch (NoSuchAlgorithmException e) {
|
||||
throw new RuntimeException(e);
|
||||
}
|
||||
}
|
||||
|
||||
public String generateToken(String email) {
|
||||
Map<String, Object> claims = new HashMap<>();
|
||||
|
||||
|
||||
return Jwts.builder().claims().add(claims).subject(email)
|
||||
.issuedAt(new Date(System.currentTimeMillis())).expiration(new Date(System.currentTimeMillis()+60*60*300))
|
||||
.and().signWith(getKey()).compact();
|
||||
}
|
||||
|
||||
private SecretKey getKey() {
|
||||
byte[] keyBytes = Decoders.BASE64.decode(secretKey);
|
||||
return Keys.hmacShaKeyFor(keyBytes);
|
||||
}
|
||||
public String extractEmail(String token) {
|
||||
return extractClaim(token, Claims::getSubject);
|
||||
}
|
||||
|
||||
private <T> T extractClaim(String token, Function<Claims, T> claimResolver) {
|
||||
final Claims claims = extractAllClaims(token);
|
||||
return claimResolver.apply(claims);
|
||||
}
|
||||
|
||||
private Claims extractAllClaims(String token) {
|
||||
return Jwts.parser()
|
||||
.verifyWith(getKey())
|
||||
.build()
|
||||
.parseSignedClaims(token)
|
||||
.getPayload();
|
||||
}
|
||||
|
||||
|
||||
public boolean validateToken(String token, UserDetails userDetails) {
|
||||
final String userName = extractEmail(token);
|
||||
return (userName.equals(userDetails.getUsername()) && !isTokenExpired(token));
|
||||
}
|
||||
|
||||
private boolean isTokenExpired(String token) {
|
||||
return extractExpiration(token).before(new Date());
|
||||
}
|
||||
|
||||
private Date extractExpiration(String token) {
|
||||
return extractClaim(token, Claims::getExpiration);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,44 @@
|
||||
package dev.ksan.etfoglasiserver.service;
|
||||
|
||||
import org.simplejavamail.api.email.Email;
|
||||
import org.simplejavamail.api.mailer.Mailer;
|
||||
import org.simplejavamail.api.mailer.config.TransportStrategy;
|
||||
import org.simplejavamail.email.EmailBuilder;
|
||||
import org.simplejavamail.mailer.MailerBuilder;
|
||||
import org.springframework.stereotype.Service;
|
||||
|
||||
import java.io.BufferedReader;
|
||||
import java.io.FileReader;
|
||||
import java.io.IOException;
|
||||
|
||||
@Service
|
||||
public class MailService {
|
||||
String address;
|
||||
String passwd;
|
||||
Mailer mailer;
|
||||
|
||||
public MailService(){
|
||||
this(".creds");
|
||||
}
|
||||
public MailService(String filename) {
|
||||
try(BufferedReader br = new BufferedReader((new FileReader(filename)))) {
|
||||
address = br.readLine();
|
||||
passwd = br.readLine();
|
||||
}catch(IOException e) {
|
||||
e.printStackTrace();
|
||||
}
|
||||
this.mailer = MailerBuilder
|
||||
.withSMTPServer("smtp.gmail.com",587,address,passwd)
|
||||
.withTransportStrategy(TransportStrategy.SMTP)
|
||||
.withSessionTimeout(10_000).buildMailer();
|
||||
}
|
||||
|
||||
public void sendEmail(String to, String subject, String body) {
|
||||
Email email = EmailBuilder.startingBlank()
|
||||
.from(address).to(to)
|
||||
.withSubject(subject)
|
||||
.withPlainText(body).buildEmail();
|
||||
|
||||
mailer.sendMail(email);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,30 @@
|
||||
package dev.ksan.etfoglasiserver.service;
|
||||
|
||||
import dev.ksan.etfoglasiserver.model.User;
|
||||
import dev.ksan.etfoglasiserver.model.UserPrincipal;
|
||||
import dev.ksan.etfoglasiserver.repository.UserRepo;
|
||||
import java.util.Optional;
|
||||
import org.springframework.beans.factory.annotation.Autowired;
|
||||
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;
|
||||
|
||||
@Service
|
||||
public class MyUserDetailsService implements UserDetailsService {
|
||||
|
||||
@Autowired private UserRepo userRepo;
|
||||
|
||||
@Override
|
||||
public UserDetails loadUserByUsername(String email) throws UsernameNotFoundException {
|
||||
|
||||
Optional<User> user = userRepo.findByEmail(email);
|
||||
if (!user.isPresent()) {
|
||||
throw new UsernameNotFoundException("user not found");
|
||||
}
|
||||
|
||||
return new UserPrincipal(user.get());
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,144 @@
|
||||
package dev.ksan.etfoglasiserver.service;
|
||||
|
||||
import com.fasterxml.jackson.databind.JsonNode;
|
||||
import com.fasterxml.jackson.databind.ObjectMapper;
|
||||
|
||||
import dev.ksan.etfoglasiserver.model.Entry;
|
||||
import java.time.LocalDateTime;
|
||||
import java.time.format.DateTimeFormatter;
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
|
||||
|
||||
import dev.ksan.etfoglasiserver.model.Subject;
|
||||
import org.springframework.beans.factory.annotation.Autowired;
|
||||
import org.springframework.http.ResponseEntity;
|
||||
import org.springframework.stereotype.Component;
|
||||
import org.springframework.web.client.RestTemplate;
|
||||
|
||||
|
||||
@Component
|
||||
public class Scraper implements Runnable {
|
||||
|
||||
@Autowired SubjectAltService altService;
|
||||
@Autowired EntryService entryService;
|
||||
|
||||
private static final String BASE_URL = "https://efee.etf.unibl.org:8443";
|
||||
|
||||
private static final List<Integer> BOARD_IDS = List.of(1, 2, 3, 4, 20, 21, 30, 102);
|
||||
|
||||
private final RestTemplate restTemplate;
|
||||
private final ObjectMapper objectMapper;
|
||||
private volatile boolean running = true;
|
||||
|
||||
DateTimeFormatter formatter = DateTimeFormatter.ISO_LOCAL_DATE_TIME;
|
||||
|
||||
public Scraper() {
|
||||
this.restTemplate = new RestTemplate();
|
||||
this.objectMapper = new ObjectMapper();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void run() {
|
||||
while (running && !Thread.currentThread().isInterrupted()) {
|
||||
try {
|
||||
System.out.println("Fetching announcements via API...");
|
||||
|
||||
for (int boardId : BOARD_IDS) {
|
||||
fetchAndProcessBoard(boardId);
|
||||
}
|
||||
|
||||
Thread.sleep(600000);
|
||||
|
||||
} catch (InterruptedException e) {
|
||||
Thread.currentThread().interrupt();
|
||||
System.out.println("Scraper interrupted");
|
||||
} catch (Exception e) {
|
||||
e.printStackTrace();
|
||||
}
|
||||
}
|
||||
System.out.println("Scraper thread stopped");
|
||||
}
|
||||
|
||||
private void fetchAndProcessBoard(int boardId) {
|
||||
try {
|
||||
String url = BASE_URL + "/api/public/oglasne-ploce/" + boardId;
|
||||
ResponseEntity<String> response = restTemplate.getForEntity(url, String.class);
|
||||
|
||||
|
||||
|
||||
if (!response.getStatusCode().is2xxSuccessful() || response.getBody() == null) {
|
||||
System.out.println("No data for board " + boardId);
|
||||
return;
|
||||
}
|
||||
|
||||
JsonNode root = objectMapper.readTree(response.getBody());
|
||||
|
||||
// Handle both array response and single object
|
||||
Iterable<JsonNode> items = root.isArray() ? root : List.of(root);
|
||||
|
||||
for (JsonNode item : items) {
|
||||
processAnnouncement(item);
|
||||
}
|
||||
|
||||
} catch (Exception e) {
|
||||
System.err.println("Failed to fetch board " + boardId + ": " + e.getMessage());
|
||||
}
|
||||
}
|
||||
|
||||
private void processAnnouncement(JsonNode item) {
|
||||
try {
|
||||
String title = textOrEmpty(item, "naslov");
|
||||
String uvod = textOrEmpty(item, "uvod");
|
||||
String sadrzaj = textOrEmpty(item, "sadrzaj");
|
||||
String potpis = textOrEmpty(item, "potpis");
|
||||
String created = textOrEmpty(item, "vrijemeKreiranja");
|
||||
|
||||
// Board name maps to your old "groupName"
|
||||
String groupName = "";
|
||||
JsonNode board = item.get("oglasnaPloca");
|
||||
if (board != null && !board.isNull()) {
|
||||
groupName = textOrEmpty(board, "naziv");
|
||||
}
|
||||
|
||||
// Attachments (filepath)
|
||||
String filepath = null;
|
||||
JsonNode prilozi = item.get("oglasPrilozi");
|
||||
if (prilozi != null && prilozi.isArray() && !prilozi.isEmpty()) {
|
||||
filepath = textOrEmpty(prilozi.get(0), "putanja"); // adjust field name
|
||||
}
|
||||
|
||||
LocalDateTime dateTime = created.isEmpty()
|
||||
? LocalDateTime.now()
|
||||
: LocalDateTime.parse(created, formatter);
|
||||
|
||||
// Combine uvod + sadrzaj as paragraphs (mirrors old <p> tags)
|
||||
List<String> paragraphs = new ArrayList<>();
|
||||
if (!uvod.isEmpty()) paragraphs.add(uvod);
|
||||
if (!sadrzaj.isEmpty()) paragraphs.add(sadrzaj);
|
||||
if (!potpis.isEmpty()) paragraphs.add(potpis);
|
||||
|
||||
Subject subject = altService.findSubjectIdWithTitle(title);
|
||||
Entry entry = new Entry(
|
||||
title.trim(), groupName, dateTime,
|
||||
uvod, paragraphs, filepath, subject
|
||||
);
|
||||
|
||||
System.out.println("Entry: " + entry.getTitle());
|
||||
entryService.addEntry(entry);
|
||||
|
||||
} catch (Exception e) {
|
||||
System.err.println("Failed to process announcement: " + e.getMessage());
|
||||
}
|
||||
}
|
||||
|
||||
private static String textOrEmpty(JsonNode node, String field) {
|
||||
JsonNode value = node.get(field);
|
||||
return (value == null || value.isNull()) ? "" : value.asText();
|
||||
}
|
||||
|
||||
public void stop() {
|
||||
running = false;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,152 @@
|
||||
package dev.ksan.etfoglasiserver.service;
|
||||
|
||||
import com.gargoylesoftware.htmlunit.BrowserVersion;
|
||||
import com.gargoylesoftware.htmlunit.WebClient;
|
||||
import com.gargoylesoftware.htmlunit.html.*;
|
||||
import dev.ksan.etfoglasiserver.model.Entry;
|
||||
import dev.ksan.etfoglasiserver.model.Subject;
|
||||
import org.springframework.beans.factory.annotation.Autowired;
|
||||
import org.springframework.stereotype.Component;
|
||||
|
||||
import java.time.LocalDateTime;
|
||||
import java.time.format.DateTimeFormatter;
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
import java.util.logging.ConsoleHandler;
|
||||
import java.util.logging.Handler;
|
||||
import java.util.logging.Level;
|
||||
import java.util.logging.Logger;
|
||||
|
||||
@Component
|
||||
public class ScraperOld implements Runnable {
|
||||
|
||||
@Autowired SubjectAltService altService;
|
||||
@Autowired EntryService entryService;
|
||||
|
||||
private WebClient webClient;
|
||||
private volatile boolean running = true;
|
||||
|
||||
public ScraperOld() {
|
||||
this.webClient = new WebClient(BrowserVersion.CHROME);
|
||||
webClient.getOptions().setJavaScriptEnabled(true);
|
||||
webClient.getOptions().setCssEnabled(false);
|
||||
webClient.getOptions().setThrowExceptionOnScriptError(false);
|
||||
}
|
||||
|
||||
private static String getTextOrEmpty(HtmlElement parent, String xPath) {
|
||||
HtmlElement element = parent.getFirstByXPath(xPath);
|
||||
return element == null ? "" : element.asNormalizedText();
|
||||
}
|
||||
|
||||
DateTimeFormatter formatter = DateTimeFormatter.ofPattern("dd.MM.yyyy HH:mm:ss");
|
||||
|
||||
private void configureHtmlUnitLogging() {
|
||||
Logger htmlUnitLogger = Logger.getLogger("com.gargoylesoftware.htmlunit");
|
||||
htmlUnitLogger.setLevel(Level.SEVERE);
|
||||
Handler consoleHandler = new ConsoleHandler();
|
||||
consoleHandler.setLevel(Level.SEVERE);
|
||||
htmlUnitLogger.addHandler(consoleHandler);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void run() {
|
||||
configureHtmlUnitLogging();
|
||||
while (running && !Thread.currentThread().isInterrupted()) {
|
||||
|
||||
try {
|
||||
System.out.println("Performing WebClient task...");
|
||||
|
||||
HtmlPage mainPage = webClient.getPage("https://efee.etf.unibl.org/oglasi/");
|
||||
webClient.waitForBackgroundJavaScript(1000);
|
||||
|
||||
List<DomElement> rawToggles = mainPage.getByXPath("//a[@href='#']");
|
||||
List<HtmlAnchor> toggles = new ArrayList<>();
|
||||
for (DomElement el : rawToggles) {
|
||||
if (el instanceof HtmlAnchor) {
|
||||
toggles.add((HtmlAnchor) el);
|
||||
}
|
||||
}
|
||||
int ul_idSelection = 1;
|
||||
for (HtmlAnchor anchor : toggles) {
|
||||
HtmlElement span = anchor.getFirstByXPath(".//span[@class='ui-btn-text']");
|
||||
if (span == null) continue; // skip anchors with no text span
|
||||
|
||||
// Skip “clear text” buttons
|
||||
String spanText = span.asNormalizedText().toLowerCase();
|
||||
if (spanText.contains("clear text")) continue;
|
||||
|
||||
// Only get direct text nodes (skip child span with collapse text)
|
||||
List<DomText> textNodes = span.getByXPath("./text()");
|
||||
StringBuilder sb = new StringBuilder();
|
||||
for (DomText textNode : textNodes) {
|
||||
sb.append(textNode.asNormalizedText());
|
||||
}
|
||||
String groupName = sb.toString().trim();
|
||||
|
||||
System.out.println("Group name: " + groupName);
|
||||
|
||||
HtmlPage updatedPage = anchor.click();
|
||||
webClient.waitForBackgroundJavaScript(1000);
|
||||
|
||||
String ul_id = "ul_id_" + Integer.toString(ul_idSelection);
|
||||
|
||||
DomElement rawElement = updatedPage.getElementById(ul_id);
|
||||
HtmlElement listElement =
|
||||
rawElement instanceof HtmlElement ? (HtmlElement) rawElement : null;
|
||||
|
||||
if (listElement == null) {
|
||||
// System.out.println("An element with id " + ul_id + " was not found");
|
||||
ul_idSelection++;
|
||||
continue;
|
||||
}
|
||||
|
||||
List<HtmlElement> items = listElement.getElementsByTagName("li");
|
||||
for (HtmlElement item : items) {
|
||||
String title = getTextOrEmpty(item, ".//h1");
|
||||
String date = getTextOrEmpty(item, ".//h2[1]");
|
||||
String info = getTextOrEmpty(item, ".//h2[2]");
|
||||
List<String> paragraphs = new ArrayList<>();
|
||||
List<HtmlElement> pTags = item.getByXPath(".//p");
|
||||
String filepath = null; // default
|
||||
for (HtmlElement pTag : pTags) {
|
||||
paragraphs.add(pTag.asNormalizedText());
|
||||
|
||||
// check if this <p> contains an <a> link
|
||||
HtmlAnchor aTag = pTag.getFirstByXPath(".//a");
|
||||
if (aTag != null) {
|
||||
filepath = aTag.getHrefAttribute();
|
||||
}
|
||||
}
|
||||
|
||||
Subject subjectid = altService.findSubjectIdWithTitle(title);
|
||||
Entry entry =
|
||||
new Entry(
|
||||
title.trim(), groupName, LocalDateTime.parse(date, formatter),
|
||||
info, paragraphs, filepath, subjectid);
|
||||
|
||||
System.out.println("Subject "+entry.getTitle());
|
||||
System.out.println();
|
||||
|
||||
|
||||
entryService.addEntry(entry);
|
||||
}
|
||||
|
||||
ul_idSelection++;
|
||||
}
|
||||
|
||||
Thread.sleep(300000);
|
||||
|
||||
} catch (Exception e) {
|
||||
e.printStackTrace();
|
||||
throw new RuntimeException("Error in webscraper");
|
||||
} finally {
|
||||
this.webClient.close();
|
||||
}
|
||||
}
|
||||
System.out.println("WebScraper thread stopped");
|
||||
}
|
||||
|
||||
public void stop() {
|
||||
running = false;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,56 @@
|
||||
package dev.ksan.etfoglasiserver.service;
|
||||
|
||||
import dev.ksan.etfoglasiserver.model.Subject;
|
||||
import dev.ksan.etfoglasiserver.model.SubjectAlt;
|
||||
import dev.ksan.etfoglasiserver.repository.SubjectAltRepo;
|
||||
import java.util.List;
|
||||
import java.util.UUID;
|
||||
|
||||
import dev.ksan.etfoglasiserver.repository.SubjectRepo;
|
||||
import org.springframework.beans.factory.annotation.Autowired;
|
||||
import org.springframework.data.domain.PageRequest;
|
||||
import org.springframework.data.domain.Pageable;
|
||||
import org.springframework.stereotype.Service;
|
||||
|
||||
@Service
|
||||
public class SubjectAltService {
|
||||
@Autowired SubjectAltRepo subjectAltRepo;
|
||||
|
||||
public List<SubjectAlt> getSubjectAlts() {
|
||||
return subjectAltRepo.findAll();
|
||||
}
|
||||
|
||||
public SubjectAlt getSubjectAlt(UUID id) {
|
||||
return subjectAltRepo
|
||||
.findById(id)
|
||||
.orElseThrow(() -> new RuntimeException("subjectAlt not found"));
|
||||
}
|
||||
|
||||
// todo
|
||||
public void addSubjectAlt(SubjectAlt subjectAlt) {
|
||||
|
||||
try {
|
||||
|
||||
subjectAltRepo.save(subjectAlt);
|
||||
} catch (Exception e) {
|
||||
e.printStackTrace();
|
||||
}
|
||||
}
|
||||
|
||||
// todo add update method and modify add
|
||||
|
||||
public void deleteSubjectAlt(UUID id) {
|
||||
subjectAltRepo.deleteById(id);
|
||||
}
|
||||
|
||||
public Subject findSubjectIdWithTitle(String title) {
|
||||
List<Subject> result = subjectAltRepo.findSubjectIdWithTitle(title);
|
||||
|
||||
if(result.isEmpty() ) {
|
||||
result = subjectAltRepo.findSubjectIdWithTitle("Not Grouped");
|
||||
System.out.println(title);
|
||||
}
|
||||
|
||||
return result.get(0);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,98 @@
|
||||
package dev.ksan.etfoglasiserver.service;
|
||||
|
||||
import dev.ksan.etfoglasiserver.model.Entry;
|
||||
import dev.ksan.etfoglasiserver.model.NotificationMethod;
|
||||
import dev.ksan.etfoglasiserver.model.Subject;
|
||||
import dev.ksan.etfoglasiserver.model.User;
|
||||
import dev.ksan.etfoglasiserver.repository.SubjectRepo;
|
||||
import java.util.List;
|
||||
import java.util.UUID;
|
||||
|
||||
import dev.ksan.etfoglasiserver.repository.UserRepo;
|
||||
import org.simplejavamail.api.mailer.Mailer;
|
||||
import org.springframework.beans.factory.annotation.Autowired;
|
||||
import org.springframework.stereotype.Service;
|
||||
|
||||
@Service
|
||||
public class SubjectService {
|
||||
|
||||
@Autowired private SubjectRepo subjectRepo;
|
||||
@Autowired
|
||||
private UserRepo userRepo;
|
||||
@Autowired
|
||||
private UserService userService;
|
||||
|
||||
@Autowired
|
||||
private MailService mailService;
|
||||
|
||||
public List<Subject> getSubjects() {
|
||||
return subjectRepo.findAll();
|
||||
}
|
||||
|
||||
public Subject getSubject(UUID id) {
|
||||
return subjectRepo.findById(id).orElseThrow(() -> new RuntimeException("Subject not found"));
|
||||
}
|
||||
|
||||
public void addSubject(Subject subject) {
|
||||
|
||||
if (subjectRepo.existsByName(subject.getName())) {
|
||||
throw new RuntimeException("Subject already exists");
|
||||
}
|
||||
|
||||
Subject newSubject = new Subject();
|
||||
newSubject.setName(subject.getName());
|
||||
newSubject.setCode(subject.getCode());
|
||||
subjectRepo.save(newSubject);
|
||||
}
|
||||
|
||||
public void updateSubject(Subject subject) {
|
||||
|
||||
Subject existingSubject =
|
||||
subjectRepo
|
||||
.findById(subject.getId())
|
||||
.orElseThrow(() -> new RuntimeException("Subject not found"));
|
||||
existingSubject.setName(subject.getName());
|
||||
existingSubject.setCode(subject.getCode());
|
||||
subjectRepo.save(existingSubject);
|
||||
}
|
||||
|
||||
public void deleteSubject(UUID id) {
|
||||
subjectRepo.deleteById(id);
|
||||
}
|
||||
|
||||
public Subject getSubject(String name) {
|
||||
return subjectRepo.findByName(name);
|
||||
}
|
||||
public Subject findById(UUID id) {
|
||||
return subjectRepo.findById(id).orElseThrow(() -> new RuntimeException("Subject not found"));
|
||||
}
|
||||
|
||||
public void notify(Entry entry) {
|
||||
List<User> users = userService.findUsersBySubjectId(entry.getSubject().getId());
|
||||
|
||||
if(users == null || users.isEmpty()) return;
|
||||
for(User user : users) {
|
||||
if(user.getNotificationMethod() == NotificationMethod.EMAIL)
|
||||
this.sendEmailNotification(entry, user);
|
||||
if(user.getNotificationMethod() == NotificationMethod.PUSH_NOTIFICATION)
|
||||
this.sendPushNotification(entry, user);
|
||||
System.out.println(user.getEmail());
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
public void sendEmailNotification(Entry entry,User user) {
|
||||
|
||||
mailService.sendEmail(user.getEmail(),entry.getTitle(),entry.getParagraph());
|
||||
}
|
||||
|
||||
//todo
|
||||
public void sendPushNotification(Entry entry,User user) {
|
||||
System.out.println("Sending push notification");
|
||||
}
|
||||
|
||||
public long count() {
|
||||
|
||||
return subjectRepo.count();
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,203 @@
|
||||
package dev.ksan.etfoglasiserver.service;
|
||||
|
||||
import dev.ksan.etfoglasiserver.dto.JwtResponseDTO;
|
||||
import dev.ksan.etfoglasiserver.dto.UserCreationDTO;
|
||||
import dev.ksan.etfoglasiserver.dto.UserDTO;
|
||||
import dev.ksan.etfoglasiserver.dto.UserLoginDTO;
|
||||
import dev.ksan.etfoglasiserver.model.Subject;
|
||||
import dev.ksan.etfoglasiserver.model.User;
|
||||
import dev.ksan.etfoglasiserver.repository.SubjectRepo;
|
||||
import dev.ksan.etfoglasiserver.repository.UserRepo;
|
||||
|
||||
import java.util.List;
|
||||
import java.util.Optional;
|
||||
import java.util.UUID;
|
||||
import java.util.stream.Collectors;
|
||||
|
||||
import org.springframework.beans.factory.annotation.Autowired;
|
||||
import org.springframework.http.HttpStatus;
|
||||
import org.springframework.security.authentication.AuthenticationManager;
|
||||
import org.springframework.security.authentication.BadCredentialsException;
|
||||
import org.springframework.security.authentication.UsernamePasswordAuthenticationToken;
|
||||
import org.springframework.security.core.Authentication;
|
||||
import org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder;
|
||||
import org.springframework.stereotype.Service;
|
||||
import org.springframework.transaction.annotation.Transactional;
|
||||
|
||||
@Service
|
||||
@Transactional
|
||||
public class UserService {
|
||||
@Autowired
|
||||
SubjectRepo subjectRepo;
|
||||
|
||||
@Autowired UserRepo userRepo;
|
||||
|
||||
@Autowired private AuthenticationManager authManager;
|
||||
|
||||
@Autowired private JWTService jwtService;
|
||||
|
||||
|
||||
|
||||
|
||||
private BCryptPasswordEncoder encoder = new BCryptPasswordEncoder();
|
||||
|
||||
public List<UserDTO> getUsers() {
|
||||
return userRepo.findAll().stream().map(this::getUserDTO).collect(Collectors.toList());
|
||||
}
|
||||
|
||||
public UserDTO getUserById(UUID userId) {
|
||||
User user = userRepo.findById(userId).orElseThrow(() -> new RuntimeException("User not found"));
|
||||
|
||||
return getUserDTO(user);
|
||||
}
|
||||
|
||||
public void addUser(UserCreationDTO user) {
|
||||
|
||||
if (userRepo.findByEmail(user.getEmail()).isPresent()) {
|
||||
throw new RuntimeException("User already exists");
|
||||
}
|
||||
if (this.isValidEmail(user.getEmail()) && this.isValidPassword(user.getPassword())) {
|
||||
userRepo.save(new User(user));
|
||||
}
|
||||
}
|
||||
|
||||
public UserDTO updateUser(UserCreationDTO user) {
|
||||
Optional<User> existingUserOpt = userRepo.findByEmail(user.getEmail());
|
||||
|
||||
if (userRepo.findByEmail(user.getNewEmail()).isPresent()) {
|
||||
throw new RuntimeException("Email taken");
|
||||
}
|
||||
if (existingUserOpt.isPresent()) {
|
||||
|
||||
if (this.isValidEmail(user.getEmail())) {
|
||||
|
||||
User existingUser = existingUserOpt.get();
|
||||
if (user.getPassword() != null && user.getPassword().length() > 0) {
|
||||
|
||||
if (this.isValidPassword(user.getPassword())) {
|
||||
existingUser.setPassword(encoder.encode(user.getPassword()));
|
||||
} else throw new RuntimeException("Password too short");
|
||||
}
|
||||
if (user.getNewEmail() != null && !user.getNewEmail().equals(existingUser.getEmail())) {
|
||||
|
||||
existingUser.setEmail(user.getNewEmail());
|
||||
} else {
|
||||
|
||||
existingUser.setEmail(user.getEmail());
|
||||
}
|
||||
|
||||
existingUser.setSubjectSet(user.getSubjectSet());
|
||||
|
||||
userRepo.save(existingUser);
|
||||
|
||||
return getUserDTO(existingUser);
|
||||
|
||||
} else throw new RuntimeException("Invalid email");
|
||||
} else throw new RuntimeException("User not found");
|
||||
}
|
||||
|
||||
public void deleteUser(UUID userId) {
|
||||
userRepo.deleteById(userId);
|
||||
}
|
||||
|
||||
public UserDTO register(UserCreationDTO user) {
|
||||
|
||||
if (userRepo.findByEmail(user.getEmail()).isPresent()) {
|
||||
throw new RuntimeException("User already exists");
|
||||
}
|
||||
if (this.isValidEmail(user.getEmail()) && this.isValidPassword(user.getPassword())) {
|
||||
User newUser = new User(user);
|
||||
newUser.setPassword(encoder.encode(user.getPassword()));
|
||||
userRepo.save(newUser);
|
||||
return getUserDTO(newUser);
|
||||
}
|
||||
throw new RuntimeException("Error creating user");
|
||||
}
|
||||
|
||||
public UserDTO getUserDTO(User user) {
|
||||
|
||||
return new UserDTO(
|
||||
user.getId(), user.getEmail(), user.getSubjectSet(), user.getNotificationMethod());
|
||||
}
|
||||
|
||||
public boolean isValidPassword(String pass) {
|
||||
return pass.length() >= 8;
|
||||
}
|
||||
|
||||
public List<User> findUsersBySubjectId(UUID id) {
|
||||
List<User> users = userRepo.findUsersBySubjectId(id);
|
||||
if (users.isEmpty()) return null;
|
||||
return users;
|
||||
}
|
||||
|
||||
public boolean isValidEmail(String email) {
|
||||
if (email == null) {
|
||||
return false;
|
||||
}
|
||||
String regex = "^\\w+([-+.']\\w+)*@\\w+([-.]\\w+)*\\.\\w+([-.]\\w+)*$";
|
||||
|
||||
if (email.length() < 256 && email.matches(regex)) {
|
||||
return true;
|
||||
}
|
||||
throw new RuntimeException("Invalid email");
|
||||
}
|
||||
|
||||
public JwtResponseDTO verify(UserLoginDTO user) {
|
||||
|
||||
Authentication authentication = authManager.authenticate(
|
||||
new UsernamePasswordAuthenticationToken(user.getEmail(), user.getPassword())
|
||||
);
|
||||
|
||||
if (!authentication.isAuthenticated()) {
|
||||
throw new BadCredentialsException("Invalid username or password");
|
||||
}
|
||||
|
||||
String token = jwtService.generateToken(user.getEmail());
|
||||
|
||||
User foundUser = userRepo.findByEmail(user.getEmail())
|
||||
.orElseThrow(() -> new RuntimeException("User not found after authentication"));
|
||||
|
||||
return new JwtResponseDTO(
|
||||
token,
|
||||
foundUser.getEmail(),
|
||||
foundUser.getId().toString(),
|
||||
HttpStatus.OK.toString()
|
||||
);
|
||||
}
|
||||
|
||||
public Optional<User> getAccountByEmail(String email) {
|
||||
return userRepo.findByEmail(email);
|
||||
}
|
||||
|
||||
|
||||
public UserDTO addSubject(UUID userId, UUID subjectId) {
|
||||
|
||||
User user = userRepo.findById(userId)
|
||||
.orElseThrow(() -> new RuntimeException("User not found"));
|
||||
|
||||
Subject subject = subjectRepo.findById(subjectId)
|
||||
.orElseThrow(() -> new RuntimeException("Subject not found"));
|
||||
|
||||
user.getSubjectSet().add(subject);
|
||||
|
||||
userRepo.save(user);
|
||||
|
||||
return getUserDTO(user);
|
||||
}
|
||||
|
||||
public UserDTO removeSubject(UUID userId, UUID subjectId) {
|
||||
|
||||
User user = userRepo.findById(userId)
|
||||
.orElseThrow(() -> new RuntimeException("User not found"));
|
||||
|
||||
Subject subject = subjectRepo.findById(subjectId)
|
||||
.orElseThrow(() -> new RuntimeException("Subject not found"));
|
||||
|
||||
|
||||
user.getSubjectSet().remove(subject);
|
||||
|
||||
userRepo.save(user);
|
||||
|
||||
return getUserDTO(user);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,41 @@
|
||||
package dev.ksan.etfoglasiserver.util;
|
||||
|
||||
import dev.ksan.etfoglasiserver.model.Entry;
|
||||
import java.nio.charset.StandardCharsets;
|
||||
import java.security.MessageDigest;
|
||||
import java.util.Objects;
|
||||
|
||||
public class HashGenerator {
|
||||
|
||||
public static String hashEntry(Entry entry) {
|
||||
String data =
|
||||
Objects.toString(entry.getTitle(), "")
|
||||
+ Objects.toString(entry.getTimePublished(), "")
|
||||
+ Objects.toString(entry.getParagraph(), "")
|
||||
+ Objects.toString(entry.getFilepath(), "");
|
||||
|
||||
try {
|
||||
final MessageDigest digest = MessageDigest.getInstance("SHA3-256");
|
||||
|
||||
final byte[] hashbytes = digest.digest(data.getBytes(StandardCharsets.UTF_8));
|
||||
|
||||
String sha3Hex = bytesToHex(hashbytes);
|
||||
|
||||
return sha3Hex;
|
||||
} catch (Exception e) {
|
||||
throw new RuntimeException("Failed to generate hashed id of Entry: " + entry.getTitle());
|
||||
}
|
||||
}
|
||||
|
||||
private static String bytesToHex(byte[] hash) {
|
||||
StringBuilder hexString = new StringBuilder(2 * hash.length);
|
||||
for (int i = 0; i < hash.length; i++) {
|
||||
String hex = Integer.toHexString(0xff & hash[i]);
|
||||
if (hex.length() == 1) {
|
||||
hexString.append('0');
|
||||
}
|
||||
hexString.append(hex);
|
||||
}
|
||||
return hexString.toString();
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,84 @@
|
||||
package dev.ksan.etfoglasiserver.util;
|
||||
|
||||
import dev.ksan.etfoglasiserver.model.Subject;
|
||||
import dev.ksan.etfoglasiserver.model.SubjectAlt;
|
||||
import dev.ksan.etfoglasiserver.service.SubjectAltService;
|
||||
import dev.ksan.etfoglasiserver.service.SubjectService;
|
||||
import java.io.BufferedReader;
|
||||
import java.io.FileReader;
|
||||
import java.io.IOException;
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
import org.springframework.beans.factory.annotation.Autowired;
|
||||
import org.springframework.http.HttpEntity;
|
||||
import org.springframework.http.HttpHeaders;
|
||||
import org.springframework.http.MediaType;
|
||||
import org.springframework.stereotype.Service;
|
||||
import org.springframework.web.client.RestTemplate;
|
||||
|
||||
@Service
|
||||
public class SubjectLoader {
|
||||
|
||||
@Autowired SubjectService service;
|
||||
|
||||
@Autowired SubjectAltService altService;
|
||||
private static final String URLSubjects = "http://localhost:8080/api/subjects";
|
||||
|
||||
|
||||
private List<List<String>> alts = new ArrayList<>();
|
||||
|
||||
public void loadAlts() {
|
||||
for (List<String> list : alts) {
|
||||
Subject subject = service.getSubject(list.getFirst());
|
||||
|
||||
for (String item : list) altService.addSubjectAlt(new SubjectAlt(item, subject));
|
||||
}
|
||||
}
|
||||
|
||||
public void loadSubjects() {
|
||||
|
||||
List<String> subjects = new ArrayList<>();
|
||||
|
||||
List<String> codes = new ArrayList<>();
|
||||
|
||||
try (BufferedReader reader = new BufferedReader(new FileReader("./init/subjects.txt"))) {
|
||||
|
||||
String line;
|
||||
int counter = 0;
|
||||
while ((line = reader.readLine()) != null) {
|
||||
this.alts.add(new ArrayList<>());
|
||||
|
||||
String[] parts = line.split("\\|");
|
||||
for (int i = 0; i < parts.length; i++) {
|
||||
if (i == 0) {
|
||||
subjects.add(parts[i]);
|
||||
} else if (i == 1) {
|
||||
codes.add(parts[i]);
|
||||
} else {
|
||||
this.alts.get(counter).add(parts[i]);
|
||||
}
|
||||
}
|
||||
|
||||
counter++;
|
||||
}
|
||||
|
||||
} catch (IOException e) {
|
||||
throw new RuntimeException("Subjects file now found.");
|
||||
}
|
||||
|
||||
RestTemplate restTemplate = new RestTemplate();
|
||||
HttpHeaders headers = new HttpHeaders();
|
||||
headers.setContentType(MediaType.APPLICATION_JSON);
|
||||
try {
|
||||
for (String name : subjects) {
|
||||
String json = "{ \"name\": \"" + name + "\", \"code\": \"\" }";
|
||||
HttpEntity<String> request = new HttpEntity<>(json, headers);
|
||||
restTemplate.postForObject(URLSubjects, request, Void.class);
|
||||
System.out.println("Added subject: " + name);
|
||||
}
|
||||
|
||||
} catch (Exception e) {
|
||||
e.printStackTrace();
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,5 @@
|
||||
spring.application.name=etfoglasi-server
|
||||
spring.datasource.url=jdbc:postgresql://localhost:5432/etfo-db
|
||||
spring.datasource.username=test
|
||||
spring.datasource.password=test
|
||||
|
||||
@@ -0,0 +1,13 @@
|
||||
package dev.ksan.etfoglasiserver;
|
||||
|
||||
import org.junit.jupiter.api.Test;
|
||||
import org.springframework.boot.test.context.SpringBootTest;
|
||||
|
||||
@SpringBootTest
|
||||
class EtfoglasiServerApplicationTests {
|
||||
|
||||
@Test
|
||||
void contextLoads() {
|
||||
}
|
||||
|
||||
}
|
||||
Reference in New Issue
Block a user