basic contact overview

This commit is contained in:
Jörg Henke
2022-04-07 20:52:38 +02:00
parent 94bec47d4d
commit 9fe9555853
73 changed files with 996 additions and 1488 deletions

View File

@ -14,23 +14,16 @@
</classpathentry>
<classpathentry kind="src" output="bin/test" path="src/test/java">
<attributes>
<attribute name="test" value="true"/>
<attribute name="gradle_scope" value="test"/>
<attribute name="gradle_used_by_scope" value="test"/>
<attribute name="test" value="true"/>
</attributes>
</classpathentry>
<classpathentry kind="src" output="bin/main" path="src/main/webapp">
<classpathentry kind="con" path="org.eclipse.jdt.launching.JRE_CONTAINER/org.eclipse.jdt.internal.debug.ui.launcher.StandardVMType/JavaSE-11/">
<attributes>
<attribute name="gradle_scope" value="main"/>
<attribute name="gradle_used_by_scope" value="main,test"/>
</attributes>
</classpathentry>
<classpathentry kind="con" path="org.eclipse.jdt.launching.JRE_CONTAINER/org.eclipse.jdt.internal.debug.ui.launcher.StandardVMType/JavaSE-11/"/>
<classpathentry kind="con" path="org.eclipse.jst.j2ee.internal.web.container"/>
<classpathentry kind="con" path="org.eclipse.buildship.core.gradleclasspathcontainer">
<attributes>
<attribute name="org.eclipse.jst.component.dependency" value="/WEB-INF/lib"/>
<attribute name="module" value="true"/>
</attributes>
</classpathentry>
<classpathentry kind="con" path="org.eclipse.buildship.core.gradleclasspathcontainer"/>
<classpathentry kind="output" path="bin/default"/>
</classpath>

View File

@ -10,6 +10,11 @@
<arguments>
</arguments>
</buildCommand>
<buildCommand>
<name>org.eclipse.buildship.core.gradleprojectbuilder</name>
<arguments>
</arguments>
</buildCommand>
<buildCommand>
<name>org.eclipse.wst.common.project.facet.core.builder</name>
<arguments>
@ -20,11 +25,6 @@
<arguments>
</arguments>
</buildCommand>
<buildCommand>
<name>org.eclipse.buildship.core.gradleprojectbuilder</name>
<arguments>
</arguments>
</buildCommand>
</buildSpec>
<natures>
<nature>org.eclipse.jdt.core.javanature</nature>

View File

@ -1,10 +1,17 @@
<?xml version="1.0" encoding="UTF-8"?>
<project-modules id="moduleCoreId" project-version="1.5.0">
<?xml version="1.0" encoding="UTF-8"?><project-modules id="moduleCoreId" project-version="1.5.0">
<wb-module deploy-name="timetrack">
<property name="context-root" value="timetrack"/>
<wb-resource deploy-path="/WEB-INF/classes" source-path="src/main/resources"/>
<wb-resource deploy-path="/WEB-INF/classes" source-path="src/main/webapp"/>
<wb-resource deploy-path="/WEB-INF/classes" source-path="src/main/java"/>
<wb-resource deploy-path="/" source-path="src/main/webapp"/>
</wb-module>
</project-modules>

View File

@ -1,77 +1,70 @@
buildscript {
repositories {
mavenLocal()
mavenCentral()
maven { url "https://repo.maven.apache.org/maven2" }
maven { url "https://plugins.gradle.org/m2/" }
maven {
url "https://gitlab.com/jottyfan/libs/-/raw/main"
}
jcenter()
}
dependencies {
classpath 'com.google.code.gson:gson:2.8.6'
classpath 'org.postgresql:postgresql:42.2.19'
classpath 'org.jfrog.buildinfo:build-info-extractor-gradle:latest.release'
}
plugins {
id 'org.springframework.boot' version '2.6.5'
id 'io.spring.dependency-management' version '1.0.11.RELEASE'
id 'java'
}
apply plugin: 'java'
apply plugin: 'maven-publish'
apply plugin: 'war'
apply plugin: 'eclipse'
group = 'de.jottyfan'
version = '1.2.0'
sourceCompatibility = '11'
group = 'jottyfan'
version = '1.1.9.1'
ext['spring-framework.version'] = '5.3.18'
description = """timetrack"""
sourceCompatibility = 11
targetCompatibility = 11
sourceSets {
main.java.srcDirs = ['src/main/java']
main.resources.srcDirs = ['src/main/resources', 'src/main/webapp']
}
tasks.withType(JavaCompile) {
options.encoding = 'UTF-8'
}
repositories {
mavenLocal()
mavenCentral()
maven { url "https://www.jottyfan.de/mvnrepo" }
maven { url "https://repo.maven.apache.org/maven2" }
// maven { url "https://gitlab.com/jottyfan/libs/-/raw/main" }
}
dependencyManagement {
imports {
mavenBom 'org.keycloak.bom:keycloak-adapter-bom:17.0.1'
}
}
dependencies {
implementation 'org.jboss.weld.servlet:weld-servlet:2.4.8.Final'
implementation 'org.apache.logging.log4j:log4j-api:2.17.1'
implementation 'org.apache.logging.log4j:log4j-core:2.17.1'
implementation 'org.apache.logging.log4j:log4j-to-slf4j:2.17.1'
implementation 'org.webjars:bootstrap:5.1.3'
implementation 'org.webjars:font-awesome:5.15.4'
implementation 'org.webjars:jquery:3.6.0'
implementation 'org.webjars:popper.js:2.9.3'
implementation 'org.webjars:datatables:1.11.3'
implementation 'org.webjars:jquery-ui:1.13.0'
implementation 'org.webjars.bowergithub.datatables:datatables:1.10.21'
implementation 'org.keycloak:keycloak-spring-boot-starter:17.0.1'
implementation 'org.springframework.boot:spring-boot-starter-jooq'
implementation 'org.springframework.boot:spring-boot-starter-security'
implementation 'org.springframework.boot:spring-boot-starter-thymeleaf'
implementation 'org.springframework.boot:spring-boot-starter-web'
implementation 'org.thymeleaf.extras:thymeleaf-extras-springsecurity5'
implementation 'de.jottyfan:timetrackjooq:0.1.0'
implementation 'org.apache.myfaces.core:myfaces-api:2.3.8'
implementation 'org.apache.myfaces.core:myfaces-impl:2.3.8'
implementation 'net.bootsfaces:bootsfaces:1.5.0'
implementation 'org.postgresql:postgresql:42.2.19'
implementation 'org.jooq:jooq:3.14.4'
implementation 'com.google.code.gson:gson:2.8.6'
implementation 'org.jasypt:jasypt:1.9.3'
implementation 'javax.servlet:javax.servlet-api:4.0.1'
implementation 'org.apache.logging.log4j:log4j-core:2.16.0'
implementation 'org.apache.logging.log4j:log4j-api:2.16.0'
implementation 'nz.net.ultraq.thymeleaf:thymeleaf-layout-dialect:3.0.0'
testImplementation 'org.junit.jupiter:junit-jupiter-api:5.8.0-M1'
testImplementation 'org.junit.jupiter:junit-jupiter-engine:5.8.0-M1'
implementation 'org.postgresql:postgresql:42.2.19'
developmentOnly 'org.springframework.boot:spring-boot-devtools'
runtimeOnly 'org.postgresql:postgresql'
testImplementation 'org.springframework.boot:spring-boot-starter-test'
testImplementation 'org.springframework.security:spring-security-test'
}
war {
archiveName 'timetrack.war'
manifest {
attributes('Implementation-Version': project.version)
}
test {
useJUnitPlatform()
}
// add version to manifest
springBoot {
buildInfo()
}

20
debian/build.sh vendored Executable file
View File

@ -0,0 +1,20 @@
#!/bin/bash
mkdir -p timetrack/var/lib
cd ..
./gradlew clean build
G=$(grep "^version =" build.gradle | sed -e "s/version = //g" | sed -e "s/'//g")
echo "found version $G"
cp -v build/libs/timetrack-$G.jar debian/timetrack/var/lib/timetrack.jar
cd debian
sed -i timetrack/DEBIAN/control -e "s/Version: 0.0.0/Version: $G/"
V=$(grep "Version" timetrack/DEBIAN/control | sed -e "s/Version: //g")
fakeroot dpkg -b timetrack timetrack_$V.deb

1
debian/timetrack/DEBIAN/conffiles vendored Normal file
View File

@ -0,0 +1 @@
/etc/timetrack.properties

9
debian/timetrack/DEBIAN/control vendored Normal file
View File

@ -0,0 +1,9 @@
Package: timetrack
Version: 0.0.0
Architecture: amd64
Maintainer: Jörg Henke <jottyfan@gmx.de>
Depends: default-jdk
Section: ship
Priority: optional
Description: timetrack application to track work times
Timetrack is a web application to track my work times for my daily reporting.

17
debian/timetrack/DEBIAN/postinst vendored Executable file
View File

@ -0,0 +1,17 @@
#!/bin/bash
mkdir -p /var/run/timetrack
groupadd timetrack || true
useradd -r -g timetrack -d /etc/timetrack -s /sbin/nologin timetrack || true
chown timetrack:timetrack -R /etc/timetrack || true
chown timetrack:timetrack -R /var/run/timetrack || true
chown timetrack:timetrack /usr/bin/timetrack || true
cd /var/lib
systemctl daemon-reload || true
systemctl enable timetrack || true
echo "+------------------------------------------------------------------------------+"
echo "| configure timetrack in /etc/timetrack.properties; consider a port change... |"
echo "| start timetrack by calling sudo systemctl restart timetrack |"
echo "+------------------------------------------------------------------------------+"

View File

@ -0,0 +1,14 @@
[Unit]
Description=Timetrack
After=syslog.target network.target
Before=httpd.service
[Service]
User=timetack
Group=timetrack
PIDFile=/var/run/timetrack/timetrack.pid
ExecStart=/usr/bin/timetrack
StandardOutput=null
[Install]
WantedBy=multi-user.target

3
debian/timetrack/usr/bin/timetrack vendored Executable file
View File

@ -0,0 +1,3 @@
#!/bin/bash
java -Dspring.config.location=/etc/timetrack.properties -jar /var/lib/timetrack.jar

View File

@ -1,50 +0,0 @@
package de.jooqfaces;
/**
*
* @author henkej
*
*/
public enum EJooqFacesApplicationScope {
/**
* jooqFacesUrl
*/
JOOQ_FACES_URL("jooqFacesUrl"),
/**
* jooqFacesDriver
*/
JOOQ_FACES_DRIVER("jooqFacesDriver"),
/**
* jooqFacesSqldialect
*/
JOOQ_FACES_SQLDIALECT("jooqFacesSqldialect"),
/**
* jooqFacesProperties
*/
JOOQ_FACES_PROPERTIES("jooqFacesProperties"),
/**
* jooqFacesConnectionPool
*/
JOOQ_FACES_CONNECTIONPOOL("jooqFacesConnectionPool"),
/**
* jooqFacesMaxPoolSize
*/
JOOQ_FACES_MAXPOOLSIZE("jooqFacesMaxPoolSize"),
/**
* jooqFacesParamAutocommit
*/
JOOQ_FACES_PARAM_AUTOCOMMIT("jooqFacesParamAutocommit");
private final String s;
private EJooqFacesApplicationScope(String s) {
this.s = s;
}
/**
* @return the value
*/
public final String get() {
return s;
}
}

View File

@ -1,24 +0,0 @@
package de.jooqfaces;
/**
*
* @author jotty
*
*/
public enum EJooqFacesConnectionPool {
CP_HIKARI("hikari");
private final String value;
private EJooqFacesConnectionPool(String value) {
this.value = value;
}
public String get() {
return value;
}
public static final String getHikari() {
return CP_HIKARI.get();
}
}

View File

@ -1,20 +0,0 @@
package de.jooqfaces;
/**
*
* @author henkej
*
*/
public enum EJooqFacesSessionScope {
CONNECTION("connection");
private final String value;
private EJooqFacesSessionScope(String value) {
this.value = value;
}
public String get() {
return value;
}
}

View File

@ -1,350 +0,0 @@
package de.jooqfaces;
import java.sql.*;
import java.util.*;
import javax.faces.application.*;
import javax.faces.application.FacesMessage.*;
import javax.faces.component.*;
import javax.faces.context.*;
import javax.faces.render.*;
import javax.servlet.*;
import javax.sql.*;
import org.apache.logging.log4j.*;
import org.jooq.*;
import org.jooq.impl.*;
/**
*
* @author jotty
*
*/
public class JooqFacesContext extends FacesContext {
private static final Logger LOGGER = LogManager.getLogger(JooqFacesContext.class);
private FacesContext facesContext;
private Connection connection;
public JooqFacesContext(FacesContext facesContext) {
this.facesContext = facesContext;
setCurrentInstance(this);
}
/**
* get the jooq dsl context from the faces context session map<br />
* <br />
* <b>Always</b> call getJooq() within a <b>try-catch closure</b>, as the DSLContext is a closure; if not, your
* connections might run out
*
* @return the jooq context
* @throws ClassNotFoundException
* on driver errors; check if you have attached the correct jdbc driver class
* @throws SQLException
* on sql errors
*/
public CloseableDSLContext getJooq() throws ClassNotFoundException, SQLException {
ExternalContext externalContext = facesContext.getExternalContext();
if (externalContext == null) {
throw new JooqFacesException("external context of current faces context is null");
}
ServletContext servletContext = (ServletContext) externalContext.getContext();
if (servletContext == null) {
throw new JooqFacesException("servlet context of current faces context is null");
}
SQLDialect dialect = getSqlDialect(servletContext);
createConnectionIfNull(externalContext, servletContext);
return new DefaultCloseableDSLContext(new DefaultConnectionProvider(connection), dialect);
}
/**
* get the database connection from the session map; if not found, create a new one and add it to the session map
*
* @param sessionMap
* the session map
* @param externalContext
* the external context
* @param servletContext
* the servlet context
* @return the connection
* @throws ClassNotFoundException
* on driver errors (e.g. missing jdbc lib)
* @throws SQLException
* on sql errors
*/
private void createConnectionIfNull(ExternalContext externalContext, ServletContext servletContext)
throws ClassNotFoundException, SQLException {
if (connection == null) { // caching the connection within the faces context makes it faster on the jsf life cycle
Map<String, Object> sessionMap = externalContext.getSessionMap();
if (sessionMap == null) {
throw new JooqFacesException("session map of current faces context is null");
}
DataSource dataSource = (DataSource) sessionMap.get(EJooqFacesSessionScope.CONNECTION.get());
if (dataSource == null || dataSource.getConnection() == null || dataSource.getConnection().isClosed()) {
LOGGER.debug("creating new connection pool");
dataSource = getDataSourceFromServletContext(servletContext);
externalContext.getSessionMap().put(EJooqFacesSessionScope.CONNECTION.get(), dataSource);
}
connection = dataSource.getConnection();
String autoCommit = servletContext.getInitParameter(EJooqFacesApplicationScope.JOOQ_FACES_PARAM_AUTOCOMMIT.get());
connection.setAutoCommit("true".equals(autoCommit)); // default false for postgreSQL, the database of my choice
}
}
/**
* get data source from connection pool if defined in servlet context (see
* EJooqFacesApplicationScope.CONNECTION_POOL); if not defined, return a plain data source
*
* @param servletContext
* @return
*/
private static final DataSource getDataSourceFromServletContext(ServletContext servletContext)
throws ClassNotFoundException {
String driver = getDriver(servletContext);
if (driver == null) {
throw new JooqFacesException(
"undefined driver in application scope, define it in your web.xml's context-param on name "
+ EJooqFacesApplicationScope.JOOQ_FACES_DRIVER.get());
}
String url = getUrl(servletContext);
if (url == null) {
throw new JooqFacesException(
"undefined connection data url in application scope, define it in your web.xml's context-param on name "
+ EJooqFacesApplicationScope.JOOQ_FACES_URL.get());
}
Integer maxPoolSize = getMaxPoolSize(servletContext);
if (maxPoolSize == null) {
LOGGER.debug("maxPoolSize not set, setting it to 20");
maxPoolSize = 20;
}
String connectionPool = getConnectionPool(servletContext);
if (connectionPool == null) {
LOGGER.warn(
"no connection pool set in servlet context (see EJooqFacesApplicationScope.JOOQ_FACES_CONNECTIONPOOL), using plain connection");
}
return new PoollessDataSource(driver, url);
}
/**
* get the connection from the servlet context
*
* @param servletContext
* the servlet context
* @return the connection
* @throws ClassNotFoundException
* on driver errors (e.g. missing jdbc lib)
* @throws SQLException
* on sql errors
*/
private static final Connection getConnectionFromServletContext(ServletContext servletContext)
throws ClassNotFoundException, SQLException {
DataSource dataSource = getDataSourceFromServletContext(servletContext);
return dataSource.getConnection();
}
/**
* get a jooq connection from servlet context (for such cases as the deployment phase where the faces context is still
* not available)
*
* @param servletContext
* the servlet context
* @return a jooq connection
* @throws ClassNotFoundException
* on driver errors (e.g. missing jdbc lib)
* @throws SQLException
* on sql errors
*/
public static final DSLContext getJooqFromServletContext(ServletContext servletContext)
throws ClassNotFoundException, SQLException {
SQLDialect dialect = getSqlDialect(servletContext);
Connection con = getConnectionFromServletContext(servletContext);
return DSL.using(con, dialect);
}
@Override
public void addMessage(String clientId, FacesMessage message) {
facesContext.addMessage(clientId, message);
}
@Override
public Application getApplication() {
return facesContext.getApplication();
}
@Override
public Iterator<String> getClientIdsWithMessages() {
return facesContext.getClientIdsWithMessages();
}
@Override
public ExternalContext getExternalContext() {
return facesContext.getExternalContext();
}
@Override
public Severity getMaximumSeverity() {
return facesContext.getMaximumSeverity();
}
@Override
public Iterator<FacesMessage> getMessages() {
return facesContext.getMessages();
}
@Override
public Iterator<FacesMessage> getMessages(String clientId) {
return facesContext.getMessages(clientId);
}
@Override
public RenderKit getRenderKit() {
return facesContext.getRenderKit();
}
@Override
public boolean getRenderResponse() {
return facesContext.getRenderResponse();
}
@Override
public boolean getResponseComplete() {
return facesContext.getResponseComplete();
}
@Override
public ResponseStream getResponseStream() {
return facesContext.getResponseStream();
}
@Override
public ResponseWriter getResponseWriter() {
return facesContext.getResponseWriter();
}
@Override
public UIViewRoot getViewRoot() {
return facesContext.getViewRoot();
}
@Override
public void release() {
facesContext.release();
}
@Override
public void renderResponse() {
facesContext.renderResponse();
}
@Override
public void responseComplete() {
facesContext.responseComplete();
}
@Override
public void setResponseStream(ResponseStream responseStream) {
facesContext.setResponseStream(responseStream);
}
@Override
public void setResponseWriter(ResponseWriter responseWriter) {
facesContext.setResponseWriter(responseWriter);
}
@Override
public void setViewRoot(UIViewRoot root) {
facesContext.setViewRoot(root);
}
/**
* get the connection pool from initial context
*
* @param servletContext
* the context
* @return the connection pool string or null
*/
private static final String getConnectionPool(ServletContext servletContext) {
return servletContext.getInitParameter(EJooqFacesApplicationScope.JOOQ_FACES_CONNECTIONPOOL.get());
}
/**
* get the max pool size from initial context if any
*
* @param servletContext
* the context of this function call
* @return the max pool size or null
*/
private static final Integer getMaxPoolSize(ServletContext servletContext) {
String maxPoolSize = servletContext.getInitParameter(EJooqFacesApplicationScope.JOOQ_FACES_MAXPOOLSIZE.get());
return maxPoolSize == null ? null : Integer.valueOf(maxPoolSize);
}
/**
* get driver from initial context
*
* @param servletContext
* the context of this function call
* @return the parameter value of the jooq faces driver
*/
private static final String getDriver(ServletContext servletContext) {
return servletContext.getInitParameter(EJooqFacesApplicationScope.JOOQ_FACES_DRIVER.get());
}
/**
* get driver connection url from initial context
*
* @param servletContext
* the context of this function call
* @return the parameter value of the jooq faces url
*/
private static final String getUrl(ServletContext servletContext) {
return servletContext.getInitParameter(EJooqFacesApplicationScope.JOOQ_FACES_URL.get());
}
/**
* find jooq sql dialect class for dialectName
*
* @param dialectName
* name of dialect
* @return SQLDialect if found, null otherwise
*/
private static final SQLDialect findDialect(String dialectName) {
if (dialectName == null) {
LOGGER.error("Sql dialect name is null");
return null;
} else {
for (SQLDialect dialect : SQLDialect.values()) {
LOGGER.trace("Sql dialect comparing: dialectName={}, loopDialect={}", dialectName, dialect);
if (dialectName.equalsIgnoreCase(dialect.name())) {
LOGGER.debug("Sql dialect found: dialectName={}, foundDialect={}", dialectName, dialect);
return dialect;
}
}
LOGGER.error("Sql dialect not found: dialectName={}", dialectName);
return null;
}
}
/**
* get jooq sql dialect from initial context
*
* @param servletContext
* the context of this function call
* @return the dialect or null
*/
private static final SQLDialect getSqlDialect(ServletContext servletContext) {
String dialectName = servletContext.getInitParameter(EJooqFacesApplicationScope.JOOQ_FACES_SQLDIALECT.get());
return getSqlDialect(dialectName);
}
/**
* get sql dialect from name
*
* @param name
* the dialect name
* @return the dialect or null
*/
public static final SQLDialect getSqlDialect(String name) {
return findDialect(name);
}
}

View File

@ -1,26 +0,0 @@
package de.jooqfaces;
import javax.faces.*;
import javax.faces.context.*;
import javax.faces.lifecycle.*;
/**
*
* @author jotty
*
*/
public class JooqFacesContextFactory extends FacesContextFactory {
private FacesContextFactory facesContextFactory;
public JooqFacesContextFactory(FacesContextFactory facesContextFactory) {
this.facesContextFactory = facesContextFactory;
}
@Override
public FacesContext getFacesContext(Object context, Object request, Object response, Lifecycle lifecycle)
throws FacesException {
FacesContext facesContext = facesContextFactory.getFacesContext(context, request, response, lifecycle);
return new JooqFacesContext(facesContext);
}
}

View File

@ -1,18 +0,0 @@
package de.jooqfaces;
/**
*
* @author jotty
*
*/
public class JooqFacesException extends RuntimeException {
private static final long serialVersionUID = 1L;
public JooqFacesException(String message) {
super(message);
}
public JooqFacesException(Exception e) {
super(e);
}
}

View File

@ -1,72 +0,0 @@
package de.jooqfaces;
import java.io.*;
import java.sql.*;
import java.util.logging.*;
import javax.sql.*;
/**
*
* @author jotty
*
*/
public class PoollessDataSource implements DataSource {
private final String driver;
private final String url;
public PoollessDataSource(String driver, String url) {
this.driver = driver;
this.url = url;
}
@Override
public PrintWriter getLogWriter() throws SQLException {
return null;
}
@Override
public int getLoginTimeout() throws SQLException {
return 0;
}
@Override
public Logger getParentLogger() throws SQLFeatureNotSupportedException {
return null;
}
@Override
public void setLogWriter(PrintWriter out) throws SQLException {
}
@Override
public void setLoginTimeout(int seconds) throws SQLException {
}
@Override
public boolean isWrapperFor(Class<?> iface) throws SQLException {
return false;
}
@Override
public <T> T unwrap(Class<T> iface) throws SQLException {
return null;
}
@Override
public Connection getConnection() throws SQLException {
try {
Class.forName(driver);
} catch (ClassNotFoundException e) {
throw new SQLException(e);
}
return DriverManager.getConnection(url);
}
@Override
public Connection getConnection(String username, String password) throws SQLException {
return null;
}
}

View File

@ -1,90 +0,0 @@
package de.jooqfaces;
import java.io.*;
import java.sql.*;
import java.util.*;import javax.servlet.*;
import org.apache.logging.log4j.*;
/**
*
* @author henkej
*
*/
public class PropertiesDeploymentListener implements ServletContextListener {
private static final Logger LOGGER = LogManager.getLogger(PropertiesDeploymentListener.class);
@Override
public void contextDestroyed(ServletContextEvent event) {
try {
Enumeration<Driver> drivers = DriverManager.getDrivers();
while (drivers.hasMoreElements()) {
DriverManager.deregisterDriver(drivers.nextElement());
}
} catch (SQLException | SecurityException e) {
LOGGER.error("Error deregistering drivers", e);
}
}
@Override
public void contextInitialized(ServletContextEvent event) {
try {
ServletContext ctx = event.getServletContext();
beforeInitialization(ctx);
String propertiesFileName = (String) ctx.getInitParameter(EJooqFacesApplicationScope.JOOQ_FACES_PROPERTIES.get());
if (propertiesFileName == null) {
throw new IOException(
"undefined properties file name in application scope, define it in your web.xml's context-param on name "
+ EJooqFacesApplicationScope.JOOQ_FACES_PROPERTIES.get());
}
Properties properties = new Properties();
properties.load(new FileInputStream(propertiesFileName));
for (Map.Entry<Object, Object> entry : properties.entrySet()) {
String key = (String) entry.getKey();
String value = (String) entry.getValue();
ctx.setInitParameter(key, value);
}
// ensure to have all needed parameters loaded
if (ctx.getInitParameter(EJooqFacesApplicationScope.JOOQ_FACES_SQLDIALECT.get()) == null) {
throw new IOException("no " + EJooqFacesApplicationScope.JOOQ_FACES_SQLDIALECT.get()
+ " defined in your properties file " + propertiesFileName);
}
if (ctx.getInitParameter(EJooqFacesApplicationScope.JOOQ_FACES_URL.get()) == null) {
throw new IOException("no " + EJooqFacesApplicationScope.JOOQ_FACES_URL.get() + " defined in your properties file "
+ propertiesFileName);
}
if (ctx.getInitParameter(EJooqFacesApplicationScope.JOOQ_FACES_DRIVER.get()) == null) {
throw new IOException("no " + EJooqFacesApplicationScope.JOOQ_FACES_DRIVER.get()
+ " defined in your properties file " + propertiesFileName);
}
afterInitialization(ctx);
} catch (IOException e) {
LOGGER.error("Error loading needed parameters from properties file", e);
}
}
/**
* executed directly after initialization if no exception is thrown
*
* @param ctx
* the context to use
* @throws IOException
* for input output exceptions
*/
public void afterInitialization(ServletContext ctx) throws IOException {
// to be implemented in extending classes
}
/**
* executed directly before initialization after getting the context from the servlet
*
* @param ctx
* the context to use
* @throws IOException
* for input output exceptions
*/
public void beforeInitialization(ServletContext ctx) throws IOException {
// to be implemented in extending classes
}
}

View File

@ -0,0 +1,15 @@
package de.jottyfan.timetrack;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.transaction.annotation.EnableTransactionManagement;
@SpringBootApplication
@EnableTransactionManagement
public class TimetrackApplication {
public static void main(String[] args) {
SpringApplication.run(TimetrackApplication.class, args);
}
}

View File

@ -0,0 +1,41 @@
package de.jottyfan.timetrack.config;
import javax.sql.DataSource;
import org.jooq.SQLDialect;
import org.jooq.impl.DataSourceConnectionProvider;
import org.jooq.impl.DefaultConfiguration;
import org.jooq.impl.DefaultDSLContext;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.jdbc.datasource.TransactionAwareDataSourceProxy;
/**
*
* @author henkej
*
*/
@Configuration
public class InitialConfiguration {
@Autowired
private DataSource dataSource;
@Bean
public DataSourceConnectionProvider connectionProvider() {
return new DataSourceConnectionProvider(new TransactionAwareDataSourceProxy(dataSource));
}
@Bean
public DefaultDSLContext dsl() {
return new DefaultDSLContext(configuration());
}
public DefaultConfiguration configuration() {
DefaultConfiguration jooqConfiguration = new DefaultConfiguration();
jooqConfiguration.set(connectionProvider());
jooqConfiguration.set(SQLDialect.POSTGRES);
// jooqConfiguration.set(new DefaultExecuteListenerProvider(exceptionTransformer()));
return jooqConfiguration;
}
}

View File

@ -0,0 +1,56 @@
package de.jottyfan.timetrack.config;
import org.keycloak.adapters.springboot.KeycloakSpringBootConfigResolver;
import org.keycloak.adapters.springsecurity.KeycloakConfiguration;
import org.keycloak.adapters.springsecurity.authentication.KeycloakAuthenticationProvider;
import org.keycloak.adapters.springsecurity.config.KeycloakWebSecurityConfigurerAdapter;
import org.keycloak.adapters.springsecurity.management.HttpSessionManager;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.autoconfigure.condition.ConditionalOnMissingBean;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.ComponentScan;
import org.springframework.security.config.annotation.authentication.builders.AuthenticationManagerBuilder;
import org.springframework.security.config.annotation.method.configuration.EnableGlobalMethodSecurity;
import org.springframework.security.config.annotation.web.builders.HttpSecurity;
import org.springframework.security.core.authority.mapping.SimpleAuthorityMapper;
import org.springframework.security.core.session.SessionRegistryImpl;
import org.springframework.security.web.authentication.session.RegisterSessionAuthenticationStrategy;
import org.springframework.security.web.authentication.session.SessionAuthenticationStrategy;
@KeycloakConfiguration
@ComponentScan(basePackageClasses = KeycloakSpringBootConfigResolver.class)
@EnableGlobalMethodSecurity(prePostEnabled = true, securedEnabled = true, jsr250Enabled = true)
public class SecurityConfig extends KeycloakWebSecurityConfigurerAdapter {
@Autowired
public void configureGlobal(AuthenticationManagerBuilder auth) throws Exception {
KeycloakAuthenticationProvider keycloakAuthenticationProvider = keycloakAuthenticationProvider();
keycloakAuthenticationProvider.setGrantedAuthoritiesMapper(new SimpleAuthorityMapper());
auth.authenticationProvider(keycloakAuthenticationProvider);
}
@Bean
public KeycloakSpringBootConfigResolver KeycloakConfigResolver() {
return new KeycloakSpringBootConfigResolver();
}
@Bean
@Override
@ConditionalOnMissingBean(HttpSessionManager.class)
protected HttpSessionManager httpSessionManager() {
return new HttpSessionManager();
}
@Bean
@Override
protected SessionAuthenticationStrategy sessionAuthenticationStrategy() {
return new RegisterSessionAuthenticationStrategy(new SessionRegistryImpl());
}
@Override
protected void configure(HttpSecurity http) throws Exception {
super.configure(http);
http.authorizeRequests().antMatchers("/public/**").permitAll();
http.csrf().disable();
}
}

View File

@ -1,18 +0,0 @@
package de.jottyfan.timetrack.help;
public enum Application
{
CONNECTION("connection");
private final String value;
private Application(String value)
{
this.value = value;
}
public String get()
{
return value;
}
}

View File

@ -1,37 +0,0 @@
package de.jottyfan.timetrack.help;
import java.io.*;
import javax.servlet.*;
/**
*
* @author henkej
*
*/
public class EncodingFilter implements Filter
{
private String encoding;
@Override
public void destroy()
{
}
@Override
public void doFilter(ServletRequest servletRequest, ServletResponse servletResponse, FilterChain filterChain) throws IOException, ServletException
{
servletRequest.setCharacterEncoding(encoding);
filterChain.doFilter(servletRequest, servletResponse);
}
@Override
public void init(FilterConfig filterConfig) throws ServletException
{
encoding = filterConfig.getInitParameter("encoding");
if (encoding == null)
{
encoding = "UTF-8";
}
}
}

View File

@ -1,31 +0,0 @@
package de.jottyfan.timetrack.help;
import java.time.LocalDate;
import java.time.format.DateTimeFormatter;
import javax.faces.component.UIComponent;
import javax.faces.context.FacesContext;
import javax.faces.convert.Converter;
import javax.faces.convert.ConverterException;
import javax.faces.convert.FacesConverter;
/**
*
* @author jotty
*
*/
@FacesConverter("de.jottyfan.timetrack.help.LocalDateConverter")
public class LocalDateConverter implements Converter<LocalDate> {
protected final DateTimeFormatter dtf = DateTimeFormatter.ofPattern("dd.MM.yyyy");
@Override
public LocalDate getAsObject(FacesContext context, UIComponent component, String value) throws ConverterException {
return LocalDate.from(dtf.parse(value));
}
@Override
public String getAsString(FacesContext context, UIComponent component, LocalDate value) throws ConverterException {
return value.format(dtf);
}
}

View File

@ -0,0 +1,32 @@
package de.jottyfan.timetrack.help;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.boot.info.BuildProperties;
import org.springframework.stereotype.Component;
/**
*
* @author henkej
*
*/
@Component
public class ManifestBean {
@Value("${server.servlet.context-path:}")
private String contextPath;
@Autowired(required = false)
private BuildProperties buildProperties;
public String getVersion() {
StringBuilder buf = new StringBuilder();
buf.append("Version ");
buf.append(buildProperties == null ? "?" : buildProperties.getVersion());
return buf.toString();
}
public String getContextPath() {
return (contextPath != null && !contextPath.isBlank()) ? String.format("/%s/", contextPath) : "/";
}
}

View File

@ -1,20 +0,0 @@
package de.jottyfan.timetrack.help;
/**
*
* @author henkej
*
*/
public abstract class Navigation
{
/**
* navigate to page referenced by p
*
* @param p
* @return
*/
public String navigateTo(Pages p)
{
return p.get();
}
}

View File

@ -1,94 +0,0 @@
package de.jottyfan.timetrack.help;
/**
*
* @author henkej
*
*/
public enum Pages
{
/**
* contact list
*/
CONTACT_LIST("/pages/contact/list.jsf"),
/**
* contact item
*/
CONTACT_ITEM("/pages/contact/item.jsf"),
/**
* note list
*/
NOTE_LIST("/pages/note/list.jsf"),
/**
* note item
*/
NOTE_ITEM("/pages/note/item.jsf"),
/**
* todo list
*/
TODO_LIST("/pages/todo/list.jsf"),
/**
* todo item
*/
TODO_ITEM("/pages/todo/item.jsf"),
/**
* done init
*/
DONE_INIT("/pages/done/init.jsf"),
/**
* done edit
*/
DONE_EDIT("/pages/done/edit.jsf"),
/**
* done delete
*/
DONE_DELETE("/pages/done/delete.jsf"),
/**
* done add
*/
DONE_ADD("/pages/done/add.jsf"),
/**
* done clear
*/
DONE_CLEAR("/pages/done/clear.jsf"),
/**
* done read
*/
DONE_READ("/pages/done/read.jsf"),
/**
* organize init
*/
ORGANIZE_LIST("/pages/organize/list.jsf"),
/**
* organize week
*/
ORGANIZE_WEEK("/pages/organize/week.jsf"),
/**
* organize delete
*/
ORGANIZE_DELETE("/pages/organize/delete.jsf"),
/**
* organize add
*/
ORGANIZE_ADD("/pages/organize/add.jsf"),
/**
* organize edit
*/
ORGANIZE_EDIT("/pages/organize/edit.jsf"),
/**
* start
*/
START("/pages/start.jsf");
private final String value;
private Pages(String value)
{
this.value = value;
}
public String get()
{
return value;
}
}

View File

@ -1,61 +0,0 @@
package de.jottyfan.timetrack.help;
import java.io.Serializable;
import java.util.ArrayList;
import java.util.List;
import javax.enterprise.context.SessionScoped;
import javax.inject.Named;
/**
*
* @author henkej
*
*/
@Named
@SessionScoped
public class ThemeBean implements Serializable
{
private static final long serialVersionUID = 1L;
private String currentTheme;
public List<String> getValidThemes(){
List<String> list = new ArrayList<>();
list.add("cerulean");
list.add("cosmo");
list.add("cyborg");
list.add("darkly");
list.add("default");
list.add("flatly");
list.add("journal");
list.add("lumen");
list.add("other");
list.add("paper");
list.add("patternfly");
list.add("readable");
list.add("sandstone");
list.add("simplex");
list.add("slate");
list.add("spacelab");
list.add("superhero");
list.add("united");
list.add("yeti");
return list;
}
public void setCurrentTheme(String currentTheme)
{
this.currentTheme = currentTheme;
}
/**
* contains current theme for web.xml's theme chooser
*
* @return current theme
*/
public String getCurrentTheme()
{
return currentTheme == null ? "default" : (getValidThemes().contains(currentTheme) ? currentTheme : "default");
}
}

View File

@ -1,66 +0,0 @@
package de.jottyfan.timetrack.modules.contact;
import java.io.Serializable;
import javax.enterprise.context.RequestScoped;
import javax.faces.context.FacesContext;
import javax.inject.Inject;
import javax.inject.Named;
import de.jooqfaces.JooqFacesContext;
import de.jottyfan.timetrack.help.Navigation;
import de.jottyfan.timetrack.help.Pages;
import de.jottyfan.timetrack.modules.ControlInterface;
/**
*
* @author jotty
*
*/
@Named
@RequestScoped
public class ContactControl extends Navigation implements ControlInterface, Serializable {
private static final long serialVersionUID = 1L;
@Inject
@Named("contactModel")
private ContactModel model;
public String toStart() {
return navigateTo(Pages.START);
}
public String toList() {
boolean ready = model.init((JooqFacesContext) FacesContext.getCurrentInstance());
return ready ? navigateTo(Pages.CONTACT_LIST) : toStart();
}
public String toItem(ContactBean bean) {
model.setBean(bean);
return navigateTo(Pages.CONTACT_ITEM);
}
public String toItem() {
model.setBean(new ContactBean(null));
return navigateTo(Pages.CONTACT_ITEM);
}
public String doAdd() {
boolean ready = model.add((JooqFacesContext) FacesContext.getCurrentInstance());
return ready ? toList() : navigateTo(Pages.CONTACT_ITEM);
}
public String doUpdate() {
boolean ready = model.update((JooqFacesContext) FacesContext.getCurrentInstance());
return ready ? toList() : navigateTo(Pages.CONTACT_ITEM);
}
public String doDelete() {
boolean ready = model.delete((JooqFacesContext) FacesContext.getCurrentInstance());
return ready ? toList() : navigateTo(Pages.CONTACT_ITEM);
}
public Integer getAmount() {
return model.getAmount((JooqFacesContext) FacesContext.getCurrentInstance());
}
}

View File

@ -1,176 +0,0 @@
package de.jottyfan.timetrack.modules.contact;
import static de.jottyfan.timetrack.db.contact.Tables.T_CONTACT;
import java.sql.SQLException;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;
import org.apache.logging.log4j.LogManager;
import org.apache.logging.log4j.Logger;
import org.jooq.CloseableDSLContext;
import org.jooq.DeleteConditionStep;
import org.jooq.InsertValuesStep4;
import org.jooq.Record1;
import org.jooq.Record5;
import org.jooq.SelectJoinStep;
import org.jooq.UpdateConditionStep;
import org.jooq.exception.DataAccessException;
import org.jooq.impl.DSL;
import de.jooqfaces.JooqFacesContext;
import de.jottyfan.timetrack.db.contact.enums.EnumContacttype;
import de.jottyfan.timetrack.db.contact.tables.records.TContactRecord;
import de.jottyfan.timetrack.modules.JooqGateway;
/**
*
* @author jotty
*
*/
public class ContactGateway extends JooqGateway {
private static final Logger LOGGER = LogManager.getLogger(ContactGateway.class);
public ContactGateway(JooqFacesContext facesContext) {
super(facesContext);
}
/**
* get sorted list of contacts
*
* @return a list (an empty one at least)
* @throws SQLException
* @throws ClassNotFoundException
* @throws DataAccessException
*/
public List<ContactBean> getAll() throws DataAccessException, ClassNotFoundException, SQLException {
try (CloseableDSLContext jooq = getJooq()) {
SelectJoinStep<Record5<Integer, String, String, String, EnumContacttype>> sql = jooq
// @formatter:off
.select(T_CONTACT.PK,
T_CONTACT.FORENAME,
T_CONTACT.SURNAME,
T_CONTACT.CONTACT,
T_CONTACT.TYPE)
.from(T_CONTACT);
// @formatter:on
LOGGER.debug("{}", sql.toString());
List<ContactBean> list = new ArrayList<>();
for (Record5<Integer, String, String, String, EnumContacttype> r : sql.fetch()) {
ContactBean bean = new ContactBean(r.get(T_CONTACT.PK));
bean.setForename(r.get(T_CONTACT.FORENAME));
bean.setSurname(r.get(T_CONTACT.SURNAME));
bean.setContact(r.get(T_CONTACT.CONTACT));
bean.setType(r.get(T_CONTACT.TYPE));
list.add(bean);
}
list.sort((o1, o2) -> o1 == null ? 0 : o1.compareTo(o2));
return list;
}
}
/**
* delete a contact from the database
*
* @param pk
* the id of the contact
* @return the number of affected database rows, should be 1
* @throws SQLException
* @throws ClassNotFoundException
* @throws DataAccessException
*/
public Integer delete(Integer pk) throws DataAccessException, ClassNotFoundException, SQLException {
try (CloseableDSLContext jooq = getJooq()) {
DeleteConditionStep<TContactRecord> sql = jooq
// @formatter:off
.deleteFrom(T_CONTACT)
.where(T_CONTACT.PK.eq(pk));
// @formatter:on
LOGGER.debug("{}", sql.toString());
return sql.execute();
}
}
/**
* add a contact to the database
*
* @param bean
* the contact information
* @return the number of affected database rows, should be 1
* @throws SQLException
* @throws ClassNotFoundException
* @throws DataAccessException
*/
public Integer add(ContactBean bean) throws DataAccessException, ClassNotFoundException, SQLException {
try (CloseableDSLContext jooq = getJooq()) {
InsertValuesStep4<TContactRecord, String, String, String, EnumContacttype> sql = jooq
// @formatter:off
.insertInto(T_CONTACT,
T_CONTACT.FORENAME,
T_CONTACT.SURNAME,
T_CONTACT.CONTACT,
T_CONTACT.TYPE)
.values(bean.getForename(), bean.getSurname(), bean.getContact(), bean.getType());
// @formatter:on
LOGGER.debug("{}", sql.toString());
return sql.execute();
}
}
/**
* update a contact in the database, referencing its pk
*
* @param bean
* the contact information
* @return the number of affected database rows, should be 1
* @throws SQLException
* @throws ClassNotFoundException
* @throws DataAccessException
*/
public Integer update(ContactBean bean) throws DataAccessException, ClassNotFoundException, SQLException {
try (CloseableDSLContext jooq = getJooq()) {
UpdateConditionStep<TContactRecord> sql = jooq
// @formatter:off
.update(T_CONTACT)
.set(T_CONTACT.FORENAME, bean.getForename())
.set(T_CONTACT.SURNAME, bean.getSurname())
.set(T_CONTACT.CONTACT, bean.getContact())
.set(T_CONTACT.TYPE, bean.getType())
.where(T_CONTACT.PK.eq(bean.getPk()));
// @formatter:on
LOGGER.debug("{}", sql.toString());
return sql.execute();
}
}
/**
* get number of entries in t_contact
*
* @return number of entries
* @throws SQLException
* @throws ClassNotFoundException
* @throws DataAccessException
*/
public Integer getAmount() throws DataAccessException, ClassNotFoundException, SQLException {
try (CloseableDSLContext jooq = getJooq()) {
SelectJoinStep<Record1<Integer>> sql = jooq
// @formatter:off
.selectCount()
.from(T_CONTACT);
// @formatter:on
LOGGER.debug("{}", sql.toString());
return sql.fetchOne(DSL.count());
}
}
/**
* get all enum types
*
* @return list of enum types
*/
public List<EnumContacttype> getTypes() {
return new ArrayList<>(Arrays.asList(EnumContacttype.values()));
}
}

View File

@ -1,111 +0,0 @@
package de.jottyfan.timetrack.modules.contact;
import java.io.Serializable;
import java.sql.SQLException;
import java.util.ArrayList;
import java.util.List;
import javax.enterprise.context.SessionScoped;
import javax.faces.application.FacesMessage;
import javax.inject.Named;
import org.jooq.exception.DataAccessException;
import de.jooqfaces.JooqFacesContext;
import de.jottyfan.timetrack.db.contact.enums.EnumContacttype;
import de.jottyfan.timetrack.modules.Model;
/**
*
* @author jotty
*
*/
@Named
@SessionScoped
public class ContactModel implements Model, Serializable {
private static final long serialVersionUID = 1L;
private ContactBean bean;
private List<ContactBean> list;
private List<EnumContacttype> types;
public boolean init(JooqFacesContext facesContext) {
bean = new ContactBean(null);
try {
ContactGateway gw = new ContactGateway(facesContext);
list = gw.getAll();
types = gw.getTypes();
return true;
} catch (DataAccessException | ClassNotFoundException | SQLException e) {
facesContext.addMessage(null,
new FacesMessage(FacesMessage.SEVERITY_ERROR, "error on loading data from db", e.getMessage()));
list = new ArrayList<>();
types = new ArrayList<>();
return false;
}
}
public boolean delete(JooqFacesContext facesContext) {
try {
Integer affected = new ContactGateway(facesContext).delete(bean.getPk());
return affected.equals(1);
} catch (DataAccessException | ClassNotFoundException | SQLException e) {
facesContext.addMessage(null,
new FacesMessage(FacesMessage.SEVERITY_ERROR, "error on deleting data from db", e.getMessage()));
return false;
}
}
public boolean add(JooqFacesContext facesContext) {
try {
Integer affected = new ContactGateway(facesContext).add(bean);
return affected.equals(1);
} catch (DataAccessException | ClassNotFoundException | SQLException e) {
facesContext.addMessage(null,
new FacesMessage(FacesMessage.SEVERITY_ERROR, "error on adding data to db", e.getMessage()));
return false;
}
}
public boolean update(JooqFacesContext facesContext) {
try {
Integer affected = new ContactGateway(facesContext).update(bean);
return affected.equals(1);
} catch (DataAccessException | ClassNotFoundException | SQLException e) {
facesContext.addMessage(null,
new FacesMessage(FacesMessage.SEVERITY_ERROR, "error on updating data to db", e.getMessage()));
return false;
}
}
public Integer getAmount(JooqFacesContext facesContext) {
try {
return new ContactGateway(facesContext).getAmount();
} catch (DataAccessException | ClassNotFoundException | SQLException e) {
facesContext.addMessage(null,
new FacesMessage(FacesMessage.SEVERITY_ERROR, "error on getting size of contacts", e.getMessage()));
return -1;
}
}
public List<ContactBean> getList() {
return list;
}
public void setBean(ContactBean bean) {
this.bean = bean;
}
@Override
public ContactBean getBean() {
return bean;
}
public List<EnumContacttype> getTypes() {
return types;
}
public void setTypes(List<EnumContacttype> types) {
this.types = types;
}
}

View File

@ -1,16 +1,15 @@
package de.jottyfan.timetrack.modules.contact;
package de.jottyfan.timetrack.spring.contact;
import java.io.Serializable;
import de.jottyfan.timetrack.db.contact.enums.EnumContacttype;
import de.jottyfan.timetrack.modules.Bean;
/**
*
* @author jotty
*
*/
public class ContactBean implements Bean, Serializable, Comparable<ContactBean> {
public class ContactBean implements Serializable, Comparable<ContactBean> {
private static final long serialVersionUID = 1L;
private final Integer pk;

View File

@ -0,0 +1,90 @@
package de.jottyfan.timetrack.spring.contact;
import java.util.List;
import javax.annotation.security.RolesAllowed;
import javax.servlet.ServletException;
import javax.servlet.http.HttpServletRequest;
import org.apache.logging.log4j.LogManager;
import org.apache.logging.log4j.Logger;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Controller;
import org.springframework.ui.Model;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.ModelAttribute;
import org.springframework.web.bind.annotation.PathVariable;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestMethod;
import org.springframework.web.bind.annotation.ResponseBody;
/**
*
* @author henkej
*
*/
@Controller
public class ContactController {
private static final Logger LOGGER = LogManager.getLogger(ContactController.class);
private final HttpServletRequest request;
@Autowired
private IContactService contactService;
@Autowired
public ContactController(HttpServletRequest request) {
this.request = request;
}
@GetMapping("/logout")
public String getLogout(HttpServletRequest request) throws ServletException {
request.logout();
return "redirect:/";
}
@GetMapping("/")
public String getIndex() {
return "public/index";
}
@ModelAttribute("currentUser")
@ResponseBody
public String getCurrentUser() {
return contactService.getCurrentUser(request);
}
@RolesAllowed("timetrack_user")
@RequestMapping(value = "/contact/contacts")
public String getList(Model model, @ModelAttribute ContactBean bean) {
List<ContactBean> list = contactService.toList();
model.addAttribute("contactList", list);
return "contact/contacts";
}
@RolesAllowed("timetrack_user")
@RequestMapping(value = "/contact/add/")
public String toAdd(Model model) {
return "contact/item";
}
@RolesAllowed("timetrack_user")
@RequestMapping(value = "/contact/insert", method = RequestMethod.POST)
public String doInsert(Model model, @ModelAttribute ContactBean bean) {
Integer amount = contactService.doInsert(bean);
return amount.equals(1) ? "contact/contacts" : "concact/item";
}
@RolesAllowed("timetrack_user")
@GetMapping("/contact/edit/{id}")
public String toEdit(@PathVariable Integer id, Model model) {
return "not yet implemented";
}
@RolesAllowed("timetrack_user")
@RequestMapping(value = "/contact/delete/")
public String doDelete(Model model, @ModelAttribute ContactBean bean) {
Integer amount = contactService.doDelete(bean.getPk());
return amount.equals(1) ? "contact/contacts" : "contact/contacts";
}
}

View File

@ -0,0 +1,19 @@
package de.jottyfan.timetrack.spring.contact;
import java.util.List;
import javax.servlet.http.HttpServletRequest;
/**
*
* @author henkej
*
*/
public interface IContactService {
public List<ContactBean> toList();
public Integer doInsert(ContactBean bean);
public Integer doUdate(ContactBean bean);
public Integer doDelete(Integer pk);
public Integer getAmount();
public String getCurrentUser(HttpServletRequest request);
}

View File

@ -0,0 +1,169 @@
package de.jottyfan.timetrack.spring.contact.impl;
import static de.jottyfan.timetrack.db.contact.Tables.T_CONTACT;
import java.sql.SQLException;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;
import org.apache.logging.log4j.LogManager;
import org.apache.logging.log4j.Logger;
import org.jooq.DSLContext;
import org.jooq.DeleteConditionStep;
import org.jooq.InsertValuesStep4;
import org.jooq.Record1;
import org.jooq.Record5;
import org.jooq.SelectJoinStep;
import org.jooq.UpdateConditionStep;
import org.jooq.exception.DataAccessException;
import org.jooq.impl.DSL;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Repository;
import de.jottyfan.timetrack.db.contact.enums.EnumContacttype;
import de.jottyfan.timetrack.db.contact.tables.records.TContactRecord;
import de.jottyfan.timetrack.spring.contact.ContactBean;
/**
*
* @author jotty
*
*/
@Repository
public class ContactGateway {
private static final Logger LOGGER = LogManager.getLogger(ContactGateway.class);
private final DSLContext jooq;
public ContactGateway(@Autowired DSLContext jooq) throws Exception {
this.jooq = jooq;
}
public DSLContext getJooq() {
return this.jooq;
}
/**
* get sorted list of contacts
*
* @return a list (an empty one at least)
* @throws SQLException
* @throws ClassNotFoundException
* @throws DataAccessException
*/
public List<ContactBean> getAll() throws DataAccessException, ClassNotFoundException, SQLException {
SelectJoinStep<Record5<Integer, String, String, String, EnumContacttype>> sql = getJooq()
// @formatter:off
.select(T_CONTACT.PK,
T_CONTACT.FORENAME,
T_CONTACT.SURNAME,
T_CONTACT.CONTACT,
T_CONTACT.TYPE)
.from(T_CONTACT);
// @formatter:on
LOGGER.debug("{}", sql.toString());
List<ContactBean> list = new ArrayList<>();
for (Record5<Integer, String, String, String, EnumContacttype> r : sql.fetch()) {
ContactBean bean = new ContactBean(r.get(T_CONTACT.PK));
bean.setForename(r.get(T_CONTACT.FORENAME));
bean.setSurname(r.get(T_CONTACT.SURNAME));
bean.setContact(r.get(T_CONTACT.CONTACT));
bean.setType(r.get(T_CONTACT.TYPE));
list.add(bean);
}
list.sort((o1, o2) -> o1 == null ? 0 : o1.compareTo(o2));
return list;
}
/**
* delete a contact from the database
*
* @param pk the id of the contact
* @return the number of affected database rows, should be 1
* @throws SQLException
* @throws ClassNotFoundException
* @throws DataAccessException
*/
public Integer delete(Integer pk) throws DataAccessException, ClassNotFoundException, SQLException {
DeleteConditionStep<TContactRecord> sql = getJooq()
// @formatter:off
.deleteFrom(T_CONTACT)
.where(T_CONTACT.PK.eq(pk));
// @formatter:on
LOGGER.debug("{}", sql.toString());
return sql.execute();
}
/**
* add a contact to the database
*
* @param bean the contact information
* @return the number of affected database rows, should be 1
* @throws SQLException
* @throws ClassNotFoundException
* @throws DataAccessException
*/
public Integer add(ContactBean bean) throws DataAccessException, ClassNotFoundException, SQLException {
InsertValuesStep4<TContactRecord, String, String, String, EnumContacttype> sql = getJooq()
// @formatter:off
.insertInto(T_CONTACT,
T_CONTACT.FORENAME,
T_CONTACT.SURNAME,
T_CONTACT.CONTACT,
T_CONTACT.TYPE)
.values(bean.getForename(), bean.getSurname(), bean.getContact(), bean.getType());
// @formatter:on
LOGGER.debug("{}", sql.toString());
return sql.execute();
}
/**
* update a contact in the database, referencing its pk
*
* @param bean the contact information
* @return the number of affected database rows, should be 1
* @throws SQLException
* @throws ClassNotFoundException
* @throws DataAccessException
*/
public Integer update(ContactBean bean) throws DataAccessException, ClassNotFoundException, SQLException {
UpdateConditionStep<TContactRecord> sql = getJooq()
// @formatter:off
.update(T_CONTACT)
.set(T_CONTACT.FORENAME, bean.getForename())
.set(T_CONTACT.SURNAME, bean.getSurname())
.set(T_CONTACT.CONTACT, bean.getContact())
.set(T_CONTACT.TYPE, bean.getType())
.where(T_CONTACT.PK.eq(bean.getPk()));
// @formatter:on
LOGGER.debug("{}", sql.toString());
return sql.execute();
}
/**
* get number of entries in t_contact
*
* @return number of entries
* @throws SQLException
* @throws ClassNotFoundException
* @throws DataAccessException
*/
public Integer getAmount() throws DataAccessException, ClassNotFoundException, SQLException {
SelectJoinStep<Record1<Integer>> sql = getJooq()
// @formatter:off
.selectCount()
.from(T_CONTACT);
// @formatter:on
LOGGER.debug("{}", sql.toString());
return sql.fetchOne(DSL.count());
}
/**
* get all enum types
*
* @return list of enum types
*/
public List<EnumContacttype> getTypes() {
return new ArrayList<>(Arrays.asList(EnumContacttype.values()));
}
}

View File

@ -0,0 +1,82 @@
package de.jottyfan.timetrack.spring.contact.impl;
import java.util.ArrayList;
import java.util.List;
import javax.servlet.http.HttpServletRequest;
import org.apache.logging.log4j.LogManager;
import org.apache.logging.log4j.Logger;
import org.jooq.DSLContext;
import org.keycloak.KeycloakSecurityContext;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;
import de.jottyfan.timetrack.spring.contact.ContactBean;
import de.jottyfan.timetrack.spring.contact.IContactService;
/**
*
* @author henkej
*
*/
@Service
@Transactional(transactionManager = "transactionManager")
public class ContactService implements IContactService {
private static final Logger LOGGER = LogManager.getLogger(ContactService.class);
@Autowired
private DSLContext dsl;
@Override
public String getCurrentUser(HttpServletRequest request) {
KeycloakSecurityContext ksc = (KeycloakSecurityContext) request.getAttribute(KeycloakSecurityContext.class.getName());
return ksc == null ? "" : ksc.getIdToken().getPreferredUsername();
}
@Override
public List<ContactBean> toList() {
try {
return new ContactGateway(dsl).getAll();
} catch (Exception e) {
LOGGER.error(e);
return new ArrayList<>();
}
}
@Override
public Integer doInsert(ContactBean bean) {
try {
return new ContactGateway(dsl).add(bean);
} catch (Exception e) {
LOGGER.error(e);
return 0;
}
}
@Override
public Integer doUdate(ContactBean bean) {
try {
return new ContactGateway(dsl).update(bean);
} catch (Exception e) {
LOGGER.error(e);
return 0;
}
}
@Override
public Integer doDelete(Integer pk) {
try {
return new ContactGateway(dsl).delete(pk);
} catch (Exception e) {
LOGGER.error(e);
return 0;
}
}
@Override
public Integer getAmount() {
return toList().size();
}
}

View File

@ -0,0 +1,181 @@
package de.jottyfan.timetrack.modules;
import java.math.BigDecimal;
import java.util.ArrayList;
import java.util.List;
import javax.annotation.security.RolesAllowed;
import javax.servlet.ServletException;
import javax.servlet.http.HttpServletRequest;
import org.apache.logging.log4j.LogManager;
import org.apache.logging.log4j.Logger;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Controller;
import org.springframework.ui.Model;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.ModelAttribute;
import org.springframework.web.bind.annotation.PathVariable;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestMethod;
import org.springframework.web.bind.annotation.ResponseBody;
/**
*
* @author henkej
*
*/
@Controller
public class TimetrackController {
private static final Logger LOGGER = LogManager.getLogger(TimetrackController.class);
private final HttpServletRequest request;
@Autowired
private ITimetrackService fuelService;
private List<FuelBean> cachedFuels;
@Autowired
public TimetrackController(HttpServletRequest request) {
this.request = request;
}
@GetMapping("/logout")
public String getLogout(HttpServletRequest request) throws ServletException {
request.logout();
return "redirect:/";
}
@RolesAllowed("car_user")
@GetMapping("/secure/welcome")
public String getWelcome() {
return "secure/welcome";
}
@GetMapping("/")
public String getIndex() {
this.cachedFuels = null;
return "public/index";
}
@RolesAllowed("car_user")
@GetMapping("/secure/to_table")
public String getTable() {
this.cachedFuels = null;
return "secure/table";
}
@RolesAllowed("car_user")
@RequestMapping(value = "/secure/to_bean", method = RequestMethod.GET)
public String getBean(Model model) {
FuelBean bean = model.containsAttribute("fuelBean") ? (FuelBean) model.getAttribute("fuelBean") : new FuelBean();
model.addAttribute("fuelBean", bean);
return "secure/bean";
}
@RolesAllowed("car_user")
@RequestMapping(value = "/secure/to_existing_bean/{fkFuelBean}", method = RequestMethod.GET)
public String getBean(Model model, @PathVariable Integer fkFuelBean) throws Exception {
FuelBean bean = getFuelBean(fkFuelBean);
model.addAttribute("fuelBean", bean);
return "secure/bean";
}
private FuelBean getFuelBean(Integer fkFuelBean) throws Exception {
for (FuelBean bean : getFuels()) {
if (bean.getPk().equals(fkFuelBean)) {
return bean;
}
}
throw new Exception("bean not found");
}
@RolesAllowed("car_user")
@RequestMapping(value = "/secure/do_upsert", method = RequestMethod.POST)
public String getUpsert(Model model, @ModelAttribute FuelBean fuelBean) {
Integer affected = fuelService.upsert(fuelBean);
model.addAttribute("fuelBean", fuelBean);
LOGGER.info("affected rows: {}", affected);
return affected > 0 ? getTable() : getBean(model);
}
@RolesAllowed("car_user")
@GetMapping("/secure/jsonfuels")
@ResponseBody
public List<FuelBean> getJsonFuels() {
List<FuelBean> fuels = fuelService.findAll();
return fuels;
}
@ModelAttribute("fuels")
public List<FuelBean> getFuels() {
if (cachedFuels == null || cachedFuels.size() < 1) {
cachedFuels = fuelService.findAll();
}
return cachedFuels;
}
@ModelAttribute("currentUser")
@ResponseBody
public String getCurrentUser() {
return fuelService.getCurrentUser(request);
}
@ModelAttribute("averagelkm")
@ResponseBody
public BigDecimal getAverageLiterPerKm() {
List<FuelBean> fuels = getFuels();
Integer minMileage = 1000000; // my car won't even reach this milestone :)
Integer maxMileage = 0;
BigDecimal summedAmount = new BigDecimal(0);
for (FuelBean bean : fuels) {
summedAmount = summedAmount.add(bean.getAmount());
minMileage = bean.getMileage() < minMileage ? bean.getMileage() : minMileage;
maxMileage = bean.getMileage() > maxMileage ? bean.getMileage() : maxMileage;
}
BigDecimal totalMileage = new BigDecimal(maxMileage - minMileage);
BigDecimal calculated = totalMileage.intValue() != 0
? new BigDecimal((summedAmount.doubleValue() / totalMileage.doubleValue()) * 100d)
: new BigDecimal(0);
return calculated;
}
@ModelAttribute("averageel")
@ResponseBody
public BigDecimal getAverageEuroPerLiter() {
List<FuelBean> fuels = getFuels();
BigDecimal summedLiter = new BigDecimal(0);
BigDecimal summedEuro = new BigDecimal(0);
for (FuelBean bean : fuels) {
summedLiter = summedLiter.add(bean.getAmount());
summedEuro = summedEuro.add(bean.getPrice());
}
BigDecimal calculated = summedLiter.intValue() != 0
? new BigDecimal(summedEuro.doubleValue() / summedLiter.doubleValue())
: new BigDecimal(0);
return calculated;
}
@ModelAttribute("chartjsdata")
@ResponseBody
public List<BigDecimal> getChartjsData() {
List<FuelBean> fuels = getFuels();
List<BigDecimal> list = new ArrayList<>();
for (FuelBean bean : fuels) {
list.add(bean.getEuro_per_l());
}
return list;
}
@ModelAttribute("chartjslabel")
@ResponseBody
public List<Integer> getChartjsLabel() {
List<FuelBean> fuels = getFuels();
List<Integer> list = new ArrayList<>();
for (FuelBean bean : fuels) {
list.add(bean.getMileage());
}
return list;
}
}

View File

@ -0,0 +1,21 @@
# jooq
spring.datasource.driver-class-name=org.postgresql.Driver
spring.datasource.url=jdbc:postgresql://localhost:5432/timetrack
spring.datasource.username=timetrack
spring.datasource.password=timetrack
# application
server.port = 8083
server.servlet.context-path=/timetrack
# keycloak
keycloak.auth-server-url = http://localhost:8080/
keycloak.realm = jottyfan
keycloak.resource = timetrack
keycloak.public-client = true
keycloak.security-constraints[0].authRoles[0] = timetrack_user
keycloak.security-constraints[0].securityCollections[0].patterns[0] = /*
#keycloak.credentia
keycloak.use-resource-role-mappings=true
#keycloak.bearer-only=true

View File

@ -1,3 +1,12 @@
html {
height: 100%;
}
body {
background-color: #eee;
height: calc(100% - 76px);
}
.body {
height: 100%;
width: 100%;
@ -7,7 +16,8 @@
.page {
height: 100%;
width: 100%;
background-image: linear-gradient(to bottom, #ffffff 10%, #afffff 40%)
padding-bottom: 12px;
background-image: linear-gradient(to bottom, #eee, #777)
!important;
}

View File

@ -0,0 +1,31 @@
<!DOCTYPE html>
<html xmlns:th="http://www.thymeleaf.org" xmlns:layout="http://www.ultraq.net.nz/thymeleaf/layout"
xmlns:sec="http://www.thymeleaf.org/extras/spring-security" layout:decorate="~{layout/main.html}">
<head>
<title>Kontakte</title>
</head>
<body>
<ul layout:fragment="menu">
<li class="nav-item" sec:authorize="hasRole('timetrack_user')"><a class="nav-link" th:href="@{/contact/add}">Neuen
Kontakt anlegen</a></li>
</ul>
<main layout:fragment="content">
<div class="row row-cols-12 ro-cols-lg-4 ro-cols-md-3 ro-cols-sd-2 g-4" style="margin: 8px">
<div class="col" th:each="contact : ${contactList}">
<div class="card text-dark bg-light shadow" style="width: 18rem">
<div class="card-header text-center">
<font th:text="${contact.forename} + ' ' + ${contact.surname}" style="font-size: larger"></font>
</div>
<div class="card-body">
<div class="d-flex justify-content-center align-items-center">
<span th:text="${contact.type} + ': ' + ${contact.contact}"></span> <a th:href="@{/contact/edit/{id}(id=${contact.pk})}"
sec:authorize="hasRole('timetrack_user')" style="margin-left: 8px;"> <i class="fa fa-edit"></i>
</a>
</div>
</div>
</div>
</div>
</div>
</main>
</body>
</html>

View File

@ -0,0 +1,34 @@
<!DOCTYPE html>
<html xmlns:th="http://www.thymeleaf.org" xmlns:layout="http://www.ultraq.net.nz/thymeleaf/layout"
xmlns:sec="http://www.thymeleaf.org/extras/spring-security" layout:decorate="~{layout/main.html}">
<head>
<title>Kontakt aktualisieren</title>
</head>
<body>
<ul layout:fragment="menu">
<li class="nav-item" sec:authorize="hasRole('timetrack_user')"><a class="nav-link" th:href="@{/contact/contacts}">abbrechen</a></li>
</ul>
<main layout:fragment="content">
<div class="container">
<form th:action="@{/contact/insert}" th:object="${contactBean}" method="post">
<p>
Inhalt von Eintrag <span th:text="*{pk}"></span>:
</p>
<input type="hidden" th:field="*{pk}" />
<div class="form-group">
<label>Vorname</label> <input type="text" th:field="*{forename}" class="form-control" />
</div>
<div class="form-group">
<label>Nachname</label> <input type="text" th:field="*{surname}" class="form-control" />
</div>
<div class="form-group">
<label>Kontakt</label> <input type="text" th:field="*{contact}" class="form-control" />
</div>
<div class="form-group">
<label>Typ</label> <input type="text" th:field="*{type}" class="form-control" />
</div>
</form>
</div>
</main>
</body>
</html>

View File

@ -0,0 +1,20 @@
<!DOCTYPE html>
<html xmlns:th="http://www.thymeleaf.org" xmlns:layout="http://www.ultraq.net.nz/thymeleaf/layout"
xmlns:sec="http://www.thymeleaf.org/extras/spring-security" layout:decorate="~{layout/main.html}">
<head>
<title>Timetrack</title>
</head>
<body>
<ul layout:fragment="menu">
</ul>
<main layout:fragment="content">
<div style="margin: 10vh">
<div class="alert alert-danger" role="alert" style="vertical-align: middle; align: center">
<p>Es ist ein Fehler aufgetreten.</p>
<a th:href="@{/}" class="alert-link">Ach, Mist...</a>
</div>
</div>
</main>
</body>
</html>

View File

@ -0,0 +1,46 @@
<!DOCTYPE html>
<html xmlns:th="http://www.thymeleaf.org" xmlns:layout="http://www.ultraq.net.nz/thymeleaf/layout">
<head>
<meta charset="UTF-8" />
<link rel="stylesheet" type="text/css" th:href="@{/webjars/bootstrap/5.1.3/css/bootstrap.min.css}" />
<link rel="stylesheet" type="text/css" th:href="@{/webjars/datatables/1.11.3/css/dataTables.bootstrap5.min.css}" />
<link rel="stylesheet" type="text/css" th:href="@{/webjars/font-awesome/5.15.4/css/all.min.css}">
<link rel="stylesheet" type="text/css" th:href="@{/css/style.css}">
<link rel="icon" type="image/png" sizes="32x32" th:href="@{/png/favicon/favicon-32x32.png}">
<link rel="icon" type="image/png" sizes="16x16" th:href="@{/png/favicon/favicon-16x16.png}">
<script th:src="@{/webjars/jquery/3.6.0/jquery.min.js}"></script>
<script th:src="@{/webjars/bootstrap/5.1.3/js/bootstrap.bundle.min.js}"></script>
<script th:src="@{/webjars/datatables/1.11.3/js/jquery.dataTables.min.js}"></script>
<script th:src="@{/webjars/datatables/1.11.3/js/dataTables.bootstrap5.min.js}"></script>
</head>
<body>
<!-- Navigation -->
<nav class="navbar navbar-expand-lg navbar-light bg-light static-top">
<div class="container-fluid" style="width: 98%">
<a class="navbar-brand" th:href="@{/}"> <i class="fa fa-calendar-alt"></i> Timetrack
<div class="version" th:text="${@manifestBean.getVersion()}"></div>
</a>
<button class="navbar-toggler" type="button" data-bs-toggle="collapse" data-bs-target="#navbarResponsive"
aria-controls="navbarResponsive" aria-expanded="false" aria-label="Toggle navigation">
<span class="navbar-toggler-icon"></span>
</button>
<div class="collapse navbar-collapse" id="navbarResponsive">
<ul class="navbar-nav ml-auto">
<li class="nav-item dropdown"><a class="nav-link dropdown-toggle" href="#" id="navbarScrollingDropdown" role="button"
data-bs-toggle="dropdown" aria-expanded="false"> Module </a>
<ul class="dropdown-menu dropdown-menu-light" aria-labelledby="navbarScrollingDropdown">
<li><a class="dropdown-item" th:href="@{/contact/contacts}">Kontakte</a></li>
<li><hr /></li>
<li><a class="dropdown-item" th:href="@{/logout}">[[${currentUser}]] abmelden</a></li>
</ul></li>
<li layout:fragment="menu" style="list-style-type: none"></li>
</ul>
</div>
</div>
</nav>
<main layout:fragment="content" class="page body"></main>
</body>
</html>

View File

@ -0,0 +1,12 @@
<!DOCTYPE html>
<html xmlns:th="http://www.thymeleaf.org" xmlns:layout="http://www.ultraq.net.nz/thymeleaf/layout"
xmlns:sec="http://www.thymeleaf.org/extras/spring-security" layout:decorate="~{layout/main.html}">
<head>
<title>Timetrack</title>
</head>
<body>
<ul layout:fragment="menu">
</ul>
<main layout:fragment="content"></main>
</body>
</html>

View File

@ -1,28 +0,0 @@
package de.jottyfan.timetrack.help;
import static org.junit.jupiter.api.Assertions.assertEquals;
import java.text.SimpleDateFormat;
import java.time.LocalDate;
import java.util.Date;
import org.junit.jupiter.api.Test;
/**
*
* @author jotty
*
*/
public class TestLocalDateConverter {
@Test
public void testGetAsObject() {
String now = new SimpleDateFormat("dd.MM.yyyy").format(new Date());
assertEquals(LocalDate.now(), new LocalDateConverter().getAsObject(null, null, now));
}
@Test
public void testGetAsString() {
String now = new SimpleDateFormat("dd.MM.yyyy").format(new Date());
assertEquals(now, new LocalDateConverter().getAsString(null, null, LocalDate.now()));
}
}

View File

@ -1,67 +0,0 @@
package de.jottyfan.timetrack.moduls.done;
import static org.junit.jupiter.api.Assertions.assertEquals;
import static org.junit.jupiter.api.Assertions.assertNull;
import java.text.ParseException;
import java.text.SimpleDateFormat;
import java.time.LocalDateTime;
import java.time.format.DateTimeFormatter;
import java.util.Date;
import org.junit.jupiter.api.Test;
import de.jottyfan.timetrack.modules.done.DoneBean;
/**
*
* @author henkej
*
*/
public class TestDoneBean {
@Test
public void testGetLocalDateTimeFromHHmm() {
LocalDateTime ldt = LocalDateTime.now();
ldt = ldt.withSecond(0).withNano(0);
assertEquals(ldt.withHour(0).withMinute(0), new DoneBean().getLocalDateTimeFromHHmm("0:0", LocalDateTime.now()));
assertEquals(ldt.withHour(0).withMinute(5), new DoneBean().getLocalDateTimeFromHHmm("0:5", LocalDateTime.now()));
assertEquals(ldt.withHour(3).withMinute(2), new DoneBean().getLocalDateTimeFromHHmm("3:2", LocalDateTime.now()));
assertEquals(ldt.withHour(3).withMinute(2), new DoneBean().getLocalDateTimeFromHHmm("03:2", LocalDateTime.now()));
assertEquals(ldt.withHour(10).withMinute(2), new DoneBean().getLocalDateTimeFromHHmm("10:2", LocalDateTime.now()));
assertEquals(ldt.withHour(12).withMinute(2), new DoneBean().getLocalDateTimeFromHHmm("12:02", LocalDateTime.now()));
assertEquals(ldt.withHour(12).withMinute(20), new DoneBean().getLocalDateTimeFromHHmm("12:20", LocalDateTime.now()));
assertEquals(null, new DoneBean().getLocalDateTimeFromHHmm(null, LocalDateTime.now()));
assertEquals(null, new DoneBean().getLocalDateTimeFromHHmm("", LocalDateTime.now()));
assertEquals(null, new DoneBean().getLocalDateTimeFromHHmm(" ", LocalDateTime.now()));
assertEquals(ldt.withHour(0).withMinute(0), new DoneBean().getLocalDateTimeFromHHmm("0", LocalDateTime.now()));
assertEquals(ldt.withHour(1).withMinute(0), new DoneBean().getLocalDateTimeFromHHmm("1", LocalDateTime.now()));
assertEquals(ldt.withHour(5).withMinute(0), new DoneBean().getLocalDateTimeFromHHmm("5", LocalDateTime.now()));
}
@Test
public void testSetDay() throws ParseException {
String today = new SimpleDateFormat("dd.MM.yyyy").format(new Date());
DateTimeFormatter sdf = DateTimeFormatter.ofPattern("dd.MM.yyyy HH:mm");
DateTimeFormatter sd = DateTimeFormatter.ofPattern("dd.MM.yyyy");
DoneBean bean = new DoneBean();
bean.setDay(new Date());
assertNull(bean.getTimeFrom());
assertNull(bean.getTimeUntil());
bean.setTimeFromString("08:00");
LocalDateTime ts1 = bean.getTimeFrom();
assertEquals(today.concat(" 08:00"), ts1.format(sdf));
bean.setDay(new SimpleDateFormat("dd.MM.yyyy").parse("01.01.2001"));
assertEquals("01.01.2001", bean.getTimeFrom().format(sd));
assertNull(bean.getTimeUntil());
bean.setTimeUntilString("10:00");
LocalDateTime ts2 = bean.getTimeUntil();
assertEquals(today.concat(" 10:00"), ts2.format(sdf));
bean.setDay(new SimpleDateFormat("dd.MM.yyyy").parse("01.01.2001"));
assertEquals("01.01.2001", sd.format(bean.getTimeFrom()));
assertEquals("01.01.2001", sd.format(bean.getTimeUntil()));
}
}

View File

@ -1,23 +0,0 @@
package de.jottyfan.timetrack.moduls.done;
import static org.junit.jupiter.api.Assertions.assertEquals;
import org.junit.jupiter.api.Test;
import de.jottyfan.timetrack.modules.done.RgbColor;
/**
*
* @author henkej
*
*/
public class TestRgbColor {
@Test
public void testDetermineRgbColor() {
RgbColor c = new RgbColor();
assertEquals("rgb(146,204,204)", c.determineRgbColor(null, null, null));
assertEquals("rgb(61,66,66)", c.determineRgbColor("Ship", "alles", "Mail"));
assertEquals("rgb(43,85,85)", c.determineRgbColor("Square", "Entwicklung_Programmierung", "alles"));
}
}

View File

@ -1,25 +0,0 @@
package de.jottyfan.timetrack.moduls.done;
import static org.junit.jupiter.api.Assertions.assertEquals;
import org.junit.jupiter.api.Test;
import de.jottyfan.timetrack.modules.done.WholeDaySummaryBean;
/**
*
* @author henkej
*
*/
public class TestWholeDaySummaryBean {
@Test
public void testGetOvertime() {
assertEquals("00:00", new WholeDaySummaryBean("", "", "07:48", "", 468).getOvertime());
assertEquals("00:12", new WholeDaySummaryBean("", "", "08:00", "", 468).getOvertime());
assertEquals("-00:48", new WholeDaySummaryBean("", "", "07:00", "", 468).getOvertime());
assertEquals("-07:48", new WholeDaySummaryBean("", "", "00:00", "", 468).getOvertime());
assertEquals("-07:48", new WholeDaySummaryBean("", "", "", "", 468).getOvertime());
assertEquals("", new WholeDaySummaryBean("", "", "a:b", "", 468).getOvertime());
assertEquals("00:00", new WholeDaySummaryBean("", "", "08:00", "", 480).getOvertime());
}
}