projects can now be added and updated

Thu, 14 May 2020 22:48:01 +0200

author
Mike Becker <universe@uap-core.de>
date
Thu, 14 May 2020 22:48:01 +0200
changeset 47
57cfb94ab99f
parent 46
1574965c7dc7
child 48
63200a99ea77

projects can now be added and updated

src/main/java/de/uapcore/lightpit/AbstractLightPITServlet.java file | annotate | diff | comparison | revisions
src/main/java/de/uapcore/lightpit/Constants.java file | annotate | diff | comparison | revisions
src/main/java/de/uapcore/lightpit/Functions.java file | annotate | diff | comparison | revisions
src/main/java/de/uapcore/lightpit/dao/AbstractDao.java file | annotate | diff | comparison | revisions
src/main/java/de/uapcore/lightpit/dao/Functions.java file | annotate | diff | comparison | revisions
src/main/java/de/uapcore/lightpit/dao/GenericDao.java file | annotate | diff | comparison | revisions
src/main/java/de/uapcore/lightpit/dao/postgres/PGDataAccessObjects.java file | annotate | diff | comparison | revisions
src/main/java/de/uapcore/lightpit/dao/postgres/PGProjectDao.java file | annotate | diff | comparison | revisions
src/main/java/de/uapcore/lightpit/dao/postgres/PGUserDao.java file | annotate | diff | comparison | revisions
src/main/java/de/uapcore/lightpit/entities/Project.java file | annotate | diff | comparison | revisions
src/main/java/de/uapcore/lightpit/entities/User.java file | annotate | diff | comparison | revisions
src/main/java/de/uapcore/lightpit/modules/HomeModule.java file | annotate | diff | comparison | revisions
src/main/java/de/uapcore/lightpit/modules/ProjectsModule.java file | annotate | diff | comparison | revisions
src/main/resources/localization/home.properties file | annotate | diff | comparison | revisions
src/main/resources/localization/home_de.properties file | annotate | diff | comparison | revisions
src/main/resources/localization/lightpit.properties file | annotate | diff | comparison | revisions
src/main/resources/localization/lightpit_de.properties file | annotate | diff | comparison | revisions
src/main/resources/localization/projects.properties file | annotate | diff | comparison | revisions
src/main/resources/localization/projects_de.properties file | annotate | diff | comparison | revisions
src/main/webapp/WEB-INF/dynamic_fragments/commit-successful.jsp file | annotate | diff | comparison | revisions
src/main/webapp/WEB-INF/dynamic_fragments/error.jsp file | annotate | diff | comparison | revisions
src/main/webapp/WEB-INF/dynamic_fragments/home.jsp file | annotate | diff | comparison | revisions
src/main/webapp/WEB-INF/dynamic_fragments/project-form.jsp file | annotate | diff | comparison | revisions
src/main/webapp/WEB-INF/dynamic_fragments/projects.jsp file | annotate | diff | comparison | revisions
src/main/webapp/WEB-INF/jsp/site.jsp file | annotate | diff | comparison | revisions
src/main/webapp/home.css file | annotate | diff | comparison | revisions
src/main/webapp/lightpit.css file | annotate | diff | comparison | revisions
     1.1 --- a/src/main/java/de/uapcore/lightpit/AbstractLightPITServlet.java	Wed May 13 21:46:26 2020 +0200
     1.2 +++ b/src/main/java/de/uapcore/lightpit/AbstractLightPITServlet.java	Thu May 14 22:48:01 2020 +0200
     1.3 @@ -39,6 +39,7 @@
     1.4  import javax.servlet.http.HttpServletResponse;
     1.5  import javax.servlet.http.HttpSession;
     1.6  import java.io.IOException;
     1.7 +import java.lang.reflect.Constructor;
     1.8  import java.lang.reflect.Method;
     1.9  import java.lang.reflect.Modifier;
    1.10  import java.sql.Connection;
    1.11 @@ -225,6 +226,18 @@
    1.12      }
    1.13  
    1.14      /**
    1.15 +     * @param req      the servlet request object
    1.16 +     * @param location the location where to redirect
    1.17 +     * @see Constants#REQ_ATTR_REDIRECT_LOCATION
    1.18 +     */
    1.19 +    public void setRedirectLocation(HttpServletRequest req, String location) {
    1.20 +        if (location.startsWith("./")) {
    1.21 +            location = location.replaceFirst("\\./", Functions.baseHref(req));
    1.22 +        }
    1.23 +        req.setAttribute(Constants.REQ_ATTR_REDIRECT_LOCATION, location);
    1.24 +    }
    1.25 +
    1.26 +    /**
    1.27       * Specifies the name of an additional stylesheet used by the module.
    1.28       * <p>
    1.29       * Setting an additional stylesheet is optional, but quite common for HTML
    1.30 @@ -240,6 +253,30 @@
    1.31          req.setAttribute(Constants.REQ_ATTR_STYLESHEET, Functions.enforceExt(stylesheet, ".css"));
    1.32      }
    1.33  
    1.34 +    /**
    1.35 +     * Obtains a request parameter of the specified type.
    1.36 +     * The specified type must have a single-argument constructor accepting a string to perform conversion.
    1.37 +     * The constructor of the specified type may throw an exception on conversion failures.
    1.38 +     *
    1.39 +     * @param req the servlet request object
    1.40 +     * @param clazz the class object of the expected type
    1.41 +     * @param name the name of the parameter
    1.42 +     * @param <T> the expected type
    1.43 +     * @return the parameter value or an empty optional, if no parameter with the specified name was found
    1.44 +     */
    1.45 +    public<T> Optional<T> getParameter(HttpServletRequest req, Class<T> clazz, String name) {
    1.46 +        final String paramValue = req.getParameter(name);
    1.47 +        if (paramValue == null) return Optional.empty();
    1.48 +        if (clazz.equals(String.class)) return Optional.of((T)paramValue);
    1.49 +        try {
    1.50 +            final Constructor<T> ctor = clazz.getConstructor(String.class);
    1.51 +            return Optional.of(ctor.newInstance(paramValue));
    1.52 +        } catch (ReflectiveOperationException e) {
    1.53 +            throw new RuntimeException(e);
    1.54 +        }
    1.55 +
    1.56 +    }
    1.57 +
    1.58      private void forwardToFullView(HttpServletRequest req, HttpServletResponse resp)
    1.59              throws IOException, ServletException {
    1.60  
    1.61 @@ -287,6 +324,7 @@
    1.62          }
    1.63  
    1.64          // set some internal request attributes
    1.65 +        req.setAttribute(Constants.REQ_ATTR_BASE_HREF, Functions.baseHref(req));
    1.66          req.setAttribute(Constants.REQ_ATTR_PATH, Functions.fullPath(req));
    1.67          Optional.ofNullable(moduleInfo).ifPresent((proxy) -> req.setAttribute(Constants.REQ_ATTR_MODULE_INFO, proxy));
    1.68  
     2.1 --- a/src/main/java/de/uapcore/lightpit/Constants.java	Wed May 13 21:46:26 2020 +0200
     2.2 +++ b/src/main/java/de/uapcore/lightpit/Constants.java	Thu May 14 22:48:01 2020 +0200
     2.3 @@ -40,6 +40,7 @@
     2.4  
     2.5      public static final String DYN_FRAGMENT_PATH_PREFIX = "/WEB-INF/dynamic_fragments/";
     2.6  
     2.7 +    public static final String DYN_FRAGMENT_COMMIT_SUCCESSFUL = "commit-successful";
     2.8  
     2.9      /**
    2.10       * Name for the context parameter specifying the available languages.
    2.11 @@ -77,6 +78,11 @@
    2.12      public static final String REQ_ATTR_SUB_MENU = fqn(AbstractLightPITServlet.class, "subMenu");
    2.13  
    2.14      /**
    2.15 +     * Key for the request attribute containing the base href.
    2.16 +     */
    2.17 +    public static final String REQ_ATTR_BASE_HREF = fqn(AbstractLightPITServlet.class, "base_href");
    2.18 +
    2.19 +    /**
    2.20       * Key for the request attribute containing the full path information (servlet path + path info).
    2.21       */
    2.22      public static final String REQ_ATTR_PATH = fqn(AbstractLightPITServlet.class, "path");
    2.23 @@ -91,6 +97,11 @@
    2.24       */
    2.25      public static final String REQ_ATTR_STYLESHEET = fqn(AbstractLightPITServlet.class, "extraCss");
    2.26  
    2.27 +    /**
    2.28 +     * Key for a location the page shall redirect to.
    2.29 +     * Will be used in a meta element.
    2.30 +     */
    2.31 +    public static final String REQ_ATTR_REDIRECT_LOCATION = fqn(AbstractLightPITServlet.class, "redirectLocation");
    2.32  
    2.33      /**
    2.34       * Key for the current language selection within the session.
     3.1 --- a/src/main/java/de/uapcore/lightpit/Functions.java	Wed May 13 21:46:26 2020 +0200
     3.2 +++ b/src/main/java/de/uapcore/lightpit/Functions.java	Thu May 14 22:48:01 2020 +0200
     3.3 @@ -66,15 +66,12 @@
     3.4          return fqn(clazz.getName(), name);
     3.5      }
     3.6  
     3.7 -    public static String fullPath(LightPITModule module, RequestMapping mapping) {
     3.8 -        StringBuilder sb = new StringBuilder();
     3.9 -        sb.append(module.modulePath());
    3.10 -        sb.append('/');
    3.11 -        if (!mapping.requestPath().isEmpty()) {
    3.12 -            sb.append(mapping.requestPath().isEmpty());
    3.13 -            sb.append('/');
    3.14 -        }
    3.15 -        return sb.toString();
    3.16 +    public static String baseHref(HttpServletRequest req) {
    3.17 +        return String.format("%s://%s:%d%s/",
    3.18 +                req.getScheme(),
    3.19 +                req.getServerName(),
    3.20 +                req.getServerPort(),
    3.21 +                req.getContextPath());
    3.22      }
    3.23  
    3.24      public static String fullPath(HttpServletRequest req) {
     4.1 --- a/src/main/java/de/uapcore/lightpit/dao/AbstractDao.java	Wed May 13 21:46:26 2020 +0200
     4.2 +++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
     4.3 @@ -1,96 +0,0 @@
     4.4 -/*
     4.5 - * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS HEADER.
     4.6 - *
     4.7 - * Copyright 2018 Mike Becker. All rights reserved.
     4.8 - *
     4.9 - * Redistribution and use in source and binary forms, with or without
    4.10 - * modification, are permitted provided that the following conditions are met:
    4.11 - *
    4.12 - *   1. Redistributions of source code must retain the above copyright
    4.13 - *      notice, this list of conditions and the following disclaimer.
    4.14 - *
    4.15 - *   2. Redistributions in binary form must reproduce the above copyright
    4.16 - *      notice, this list of conditions and the following disclaimer in the
    4.17 - *      documentation and/or other materials provided with the distribution.
    4.18 - *
    4.19 - * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
    4.20 - * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
    4.21 - * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
    4.22 - * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE
    4.23 - * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
    4.24 - * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
    4.25 - * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
    4.26 - * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
    4.27 - * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
    4.28 - * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
    4.29 - * POSSIBILITY OF SUCH DAMAGE.
    4.30 - *
    4.31 - */
    4.32 -package de.uapcore.lightpit.dao;
    4.33 -
    4.34 -import java.sql.PreparedStatement;
    4.35 -import java.sql.ResultSet;
    4.36 -import java.sql.SQLException;
    4.37 -import java.sql.Types;
    4.38 -import java.util.ArrayList;
    4.39 -import java.util.List;
    4.40 -import java.util.Optional;
    4.41 -import java.util.function.Function;
    4.42 -
    4.43 -public abstract class AbstractDao<T> implements GenericDao<T> {
    4.44 -
    4.45 -    private final PreparedStatement listQuery;
    4.46 -
    4.47 -    protected AbstractDao(PreparedStatement listQuery) {
    4.48 -        this.listQuery = listQuery;
    4.49 -    }
    4.50 -
    4.51 -    public final T mapColumns(ResultSet result) throws SQLException {
    4.52 -        return mapColumns(result, "");
    4.53 -    }
    4.54 -
    4.55 -    public abstract T mapColumns(ResultSet result, String qualifier) throws SQLException;
    4.56 -
    4.57 -    /**
    4.58 -     * Qualifies a column label if an qualifier is specified.
    4.59 -     *
    4.60 -     * @param qualifier an optional qualifier
    4.61 -     * @param label     the column label
    4.62 -     * @return the label, qualified if necessary
    4.63 -     */
    4.64 -    protected final String qual(String qualifier, String label) {
    4.65 -        if (qualifier == null || qualifier.isBlank()) {
    4.66 -            return label;
    4.67 -        } else {
    4.68 -            return qualifier + "." + label;
    4.69 -        }
    4.70 -    }
    4.71 -
    4.72 -    protected final void setStringOrNull(PreparedStatement stmt, int index, String str) throws SQLException {
    4.73 -        if (str == null || str.isBlank()) {
    4.74 -            stmt.setNull(index, Types.VARCHAR);
    4.75 -        } else {
    4.76 -            stmt.setString(index, str);
    4.77 -        }
    4.78 -    }
    4.79 -
    4.80 -    protected final <T> void setForeignKeyOrNull(PreparedStatement stmt, int index, T instance, Function<? super T, Integer> keyGetter) throws SQLException {
    4.81 -        Integer key = Optional.ofNullable(instance).map(keyGetter).orElse(null);
    4.82 -        if (key == null) {
    4.83 -            stmt.setNull(index, Types.INTEGER);
    4.84 -        } else {
    4.85 -            stmt.setInt(index, key);
    4.86 -        }
    4.87 -    }
    4.88 -
    4.89 -    @Override
    4.90 -    public List<T> list() throws SQLException {
    4.91 -        List<T> list = new ArrayList<>();
    4.92 -        try (ResultSet result = listQuery.executeQuery()) {
    4.93 -            while (result.next()) {
    4.94 -                list.add(mapColumns(result));
    4.95 -            }
    4.96 -        }
    4.97 -        return list;
    4.98 -    }
    4.99 -}
     5.1 --- /dev/null	Thu Jan 01 00:00:00 1970 +0000
     5.2 +++ b/src/main/java/de/uapcore/lightpit/dao/Functions.java	Thu May 14 22:48:01 2020 +0200
     5.3 @@ -0,0 +1,62 @@
     5.4 +/*
     5.5 + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS HEADER.
     5.6 + *
     5.7 + * Copyright 2018 Mike Becker. All rights reserved.
     5.8 + *
     5.9 + * Redistribution and use in source and binary forms, with or without
    5.10 + * modification, are permitted provided that the following conditions are met:
    5.11 + *
    5.12 + *   1. Redistributions of source code must retain the above copyright
    5.13 + *      notice, this list of conditions and the following disclaimer.
    5.14 + *
    5.15 + *   2. Redistributions in binary form must reproduce the above copyright
    5.16 + *      notice, this list of conditions and the following disclaimer in the
    5.17 + *      documentation and/or other materials provided with the distribution.
    5.18 + *
    5.19 + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
    5.20 + * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
    5.21 + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
    5.22 + * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE
    5.23 + * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
    5.24 + * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
    5.25 + * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
    5.26 + * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
    5.27 + * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
    5.28 + * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
    5.29 + * POSSIBILITY OF SUCH DAMAGE.
    5.30 + *
    5.31 + */
    5.32 +package de.uapcore.lightpit.dao;
    5.33 +
    5.34 +import java.sql.PreparedStatement;
    5.35 +import java.sql.SQLException;
    5.36 +import java.sql.Types;
    5.37 +import java.util.Optional;
    5.38 +import java.util.function.Function;
    5.39 +
    5.40 +/**
    5.41 + * Some DAO utilities.
    5.42 + */
    5.43 +public final class Functions {
    5.44 +
    5.45 +    public static void setStringOrNull(PreparedStatement stmt, int index, String str) throws SQLException {
    5.46 +        if (str == null || str.isBlank()) {
    5.47 +            stmt.setNull(index, Types.VARCHAR);
    5.48 +        } else {
    5.49 +            stmt.setString(index, str);
    5.50 +        }
    5.51 +    }
    5.52 +
    5.53 +    public static <T> void setForeignKeyOrNull(PreparedStatement stmt, int index, T instance, Function<? super T, Integer> keyGetter) throws SQLException {
    5.54 +        Integer key = Optional.ofNullable(instance).map(keyGetter).orElse(null);
    5.55 +        if (key == null) {
    5.56 +            stmt.setNull(index, Types.INTEGER);
    5.57 +        } else {
    5.58 +            stmt.setInt(index, key);
    5.59 +        }
    5.60 +    }
    5.61 +
    5.62 +    private Functions() {
    5.63 +
    5.64 +    }
    5.65 +}
     6.1 --- a/src/main/java/de/uapcore/lightpit/dao/GenericDao.java	Wed May 13 21:46:26 2020 +0200
     6.2 +++ b/src/main/java/de/uapcore/lightpit/dao/GenericDao.java	Thu May 14 22:48:01 2020 +0200
     6.3 @@ -41,6 +41,15 @@
     6.4      List<T> list() throws SQLException;
     6.5  
     6.6      /**
     6.7 +     * Finds an entity by its integer ID.
     6.8 +     *
     6.9 +     * @param id the id
    6.10 +     * @return the enity or null if there is no such entity
    6.11 +     * @throws SQLException on any kind of SQL errors
    6.12 +     */
    6.13 +    T find(int id) throws SQLException;
    6.14 +
    6.15 +    /**
    6.16       * Inserts an instance into database.
    6.17       *
    6.18       * @param instance the instance to insert
     7.1 --- a/src/main/java/de/uapcore/lightpit/dao/postgres/PGDataAccessObjects.java	Wed May 13 21:46:26 2020 +0200
     7.2 +++ b/src/main/java/de/uapcore/lightpit/dao/postgres/PGDataAccessObjects.java	Thu May 14 22:48:01 2020 +0200
     7.3 @@ -41,9 +41,8 @@
     7.4      private final ProjectDao projectDao;
     7.5  
     7.6      public PGDataAccessObjects(Connection connection) throws SQLException {
     7.7 -        final PGUserDao pgUserDao = new PGUserDao(connection);
     7.8 -        userDao = pgUserDao;
     7.9 -        projectDao = new PGProjectDao(connection, pgUserDao);
    7.10 +        userDao = new PGUserDao(connection);
    7.11 +        projectDao = new PGProjectDao(connection);
    7.12      }
    7.13  
    7.14      @Override
     8.1 --- a/src/main/java/de/uapcore/lightpit/dao/postgres/PGProjectDao.java	Wed May 13 21:46:26 2020 +0200
     8.2 +++ b/src/main/java/de/uapcore/lightpit/dao/postgres/PGProjectDao.java	Thu May 14 22:48:01 2020 +0200
     8.3 @@ -28,7 +28,7 @@
     8.4   */
     8.5  package de.uapcore.lightpit.dao.postgres;
     8.6  
     8.7 -import de.uapcore.lightpit.dao.AbstractDao;
     8.8 +import de.uapcore.lightpit.dao.GenericDao;
     8.9  import de.uapcore.lightpit.dao.ProjectDao;
    8.10  import de.uapcore.lightpit.entities.Project;
    8.11  import de.uapcore.lightpit.entities.User;
    8.12 @@ -37,18 +37,31 @@
    8.13  import java.sql.PreparedStatement;
    8.14  import java.sql.ResultSet;
    8.15  import java.sql.SQLException;
    8.16 +import java.util.ArrayList;
    8.17 +import java.util.List;
    8.18  import java.util.Objects;
    8.19  
    8.20 -public final class PGProjectDao extends AbstractDao<Project> implements ProjectDao {
    8.21 +import static de.uapcore.lightpit.dao.Functions.setForeignKeyOrNull;
    8.22 +import static de.uapcore.lightpit.dao.Functions.setStringOrNull;
    8.23  
    8.24 -    private final PGUserDao userDao;
    8.25 +public final class PGProjectDao implements ProjectDao, GenericDao<Project> {
    8.26  
    8.27 -    private final PreparedStatement insert;
    8.28 -    private final PreparedStatement update;
    8.29 +    private final PreparedStatement insert, update, list, find;
    8.30  
    8.31 -    public PGProjectDao(Connection connection, PGUserDao userDao) throws SQLException {
    8.32 -        super(connection.prepareStatement(
    8.33 -                "select * from lpit_project join lpit_user owner on lpit_project.owner = owner.userid"));
    8.34 +    public PGProjectDao(Connection connection) throws SQLException {
    8.35 +        list = connection.prepareStatement(
    8.36 +                "select id, name, description, repourl, " +
    8.37 +                        "userid, username, lastname, givenname, mail " +
    8.38 +                        "from lpit_project " +
    8.39 +                        "left join lpit_user owner on lpit_project.owner = owner.userid " +
    8.40 +                        "order by name");
    8.41 +
    8.42 +        find = connection.prepareStatement(
    8.43 +                "select id, name, description, repourl, " +
    8.44 +                        "userid, username, lastname, givenname, mail " +
    8.45 +                        "from lpit_project " +
    8.46 +                        "left join lpit_user owner on lpit_project.owner = owner.userid " +
    8.47 +                        "where id = ?");
    8.48  
    8.49          insert = connection.prepareStatement(
    8.50                  "insert into lpit_project (name, description, repourl, owner) values (?, ?, ?, ?)"
    8.51 @@ -56,17 +69,24 @@
    8.52          update = connection.prepareStatement(
    8.53                  "update lpit_project set name = ?, description = ?, repourl = ?, owner = ? where id = ?"
    8.54          );
    8.55 -
    8.56 -        this.userDao = userDao;
    8.57      }
    8.58  
    8.59 -    @Override
    8.60 -    public Project mapColumns(ResultSet result, String q) throws SQLException {
    8.61 -        final var proj = new Project(result.getInt(qual(q, "id")));
    8.62 -        proj.setName(result.getString(qual(q, "name")));
    8.63 -        proj.setDescription(result.getString(qual(q, "description")));
    8.64 -        proj.setRepoUrl(result.getString(qual(q, "repourl")));
    8.65 -        proj.setOwner(userDao.mapColumns(result, "owner"));
    8.66 +    public Project mapColumns(ResultSet result) throws SQLException {
    8.67 +        final var proj = new Project(result.getInt("id"));
    8.68 +        proj.setName(result.getString("name"));
    8.69 +        proj.setDescription(result.getString("description"));
    8.70 +        proj.setRepoUrl(result.getString("repourl"));
    8.71 +
    8.72 +        final int id = result.getInt("userid");
    8.73 +        if (id != 0) {
    8.74 +            final var user = new User(id);
    8.75 +            user.setUsername(result.getString("username"));
    8.76 +            user.setGivenname(result.getString("givenname"));
    8.77 +            user.setLastname(result.getString("lastname"));
    8.78 +            user.setMail(result.getString("mail"));
    8.79 +            proj.setOwner(user);
    8.80 +        }
    8.81 +
    8.82          return proj;
    8.83      }
    8.84  
    8.85 @@ -87,6 +107,30 @@
    8.86          setStringOrNull(update, 2, instance.getDescription());
    8.87          setStringOrNull(update, 3, instance.getRepoUrl());
    8.88          setForeignKeyOrNull(update, 4, instance.getOwner(), User::getUserID);
    8.89 +        update.setInt(5, instance.getId());
    8.90          return update.executeUpdate() > 0;
    8.91      }
    8.92 +
    8.93 +    @Override
    8.94 +    public List<Project> list() throws SQLException {
    8.95 +        List<Project> projects = new ArrayList<>();
    8.96 +        try (var result = list.executeQuery()) {
    8.97 +            while (result.next()) {
    8.98 +                projects.add(mapColumns(result));
    8.99 +            }
   8.100 +        }
   8.101 +        return projects;
   8.102 +    }
   8.103 +
   8.104 +    @Override
   8.105 +    public Project find(int id) throws SQLException {
   8.106 +        find.setInt(1, id);
   8.107 +        try (var result = find.executeQuery()) {
   8.108 +            if (result.next()) {
   8.109 +                return mapColumns(result);
   8.110 +            } else {
   8.111 +                return null;
   8.112 +            }
   8.113 +        }
   8.114 +    }
   8.115  }
     9.1 --- a/src/main/java/de/uapcore/lightpit/dao/postgres/PGUserDao.java	Wed May 13 21:46:26 2020 +0200
     9.2 +++ b/src/main/java/de/uapcore/lightpit/dao/postgres/PGUserDao.java	Thu May 14 22:48:01 2020 +0200
     9.3 @@ -28,7 +28,7 @@
     9.4   */
     9.5  package de.uapcore.lightpit.dao.postgres;
     9.6  
     9.7 -import de.uapcore.lightpit.dao.AbstractDao;
     9.8 +import de.uapcore.lightpit.dao.GenericDao;
     9.9  import de.uapcore.lightpit.dao.UserDao;
    9.10  import de.uapcore.lightpit.entities.User;
    9.11  
    9.12 @@ -36,26 +36,41 @@
    9.13  import java.sql.PreparedStatement;
    9.14  import java.sql.ResultSet;
    9.15  import java.sql.SQLException;
    9.16 +import java.util.ArrayList;
    9.17 +import java.util.List;
    9.18  import java.util.Objects;
    9.19  
    9.20 -public final class PGUserDao extends AbstractDao<User> implements UserDao {
    9.21 +import static de.uapcore.lightpit.dao.Functions.setStringOrNull;
    9.22  
    9.23 -    private final PreparedStatement insert;
    9.24 -    private final PreparedStatement update;
    9.25 +public final class PGUserDao implements UserDao, GenericDao<User> {
    9.26 +
    9.27 +    public static final String[] COLUMNS = {
    9.28 +            "id", "username", "lastname", "givenname", "mail"
    9.29 +    };
    9.30 +
    9.31 +    private final PreparedStatement insert, update, list, find;
    9.32  
    9.33      public PGUserDao(Connection connection) throws SQLException {
    9.34 -        super(connection.prepareStatement("select * from lpit_user where userid >= 0 order by username"));
    9.35 +        list = connection.prepareStatement(
    9.36 +                "select userid, username, lastname, givenname, mail " +
    9.37 +                        "from lpit_user where userid >= 0 " +
    9.38 +                        "order by username");
    9.39 +        find = connection.prepareStatement(
    9.40 +                "select userid, username, lastname, givenname, mail " +
    9.41 +                        "from lpit_user where userid = ? ");
    9.42  
    9.43          insert = connection.prepareStatement("insert into lpit_user (username, lastname, givenname, mail) values (?, ?, ?, ?)");
    9.44          update = connection.prepareStatement("update lpit_user set lastname = ?, givenname = ?, mail = ? where userid = ?");
    9.45      }
    9.46  
    9.47 -    @Override
    9.48 -    public User mapColumns(ResultSet result, String q) throws SQLException {
    9.49 -        final var user = new User(result.getInt(qual(q, "userid")));
    9.50 -        user.setUsername(result.getString(qual(q, "username")));
    9.51 -        user.setGivenname(result.getString(qual(q, "givenname")));
    9.52 -        user.setLastname(result.getString(qual(q, "lastname")));
    9.53 +    public User mapColumns(ResultSet result) throws SQLException {
    9.54 +        final int id = result.getInt("userid");
    9.55 +        if (id == 0) return null;
    9.56 +        final var user = new User(id);
    9.57 +        user.setUsername(result.getString("username"));
    9.58 +        user.setGivenname(result.getString("givenname"));
    9.59 +        user.setLastname(result.getString("lastname"));
    9.60 +        user.setMail(result.getString("mail"));
    9.61          return user;
    9.62      }
    9.63  
    9.64 @@ -77,4 +92,27 @@
    9.65          update.setInt(4, instance.getUserID());
    9.66          return update.executeUpdate() > 0;
    9.67      }
    9.68 +
    9.69 +    @Override
    9.70 +    public List<User> list() throws SQLException {
    9.71 +        List<User> users = new ArrayList<>();
    9.72 +        try (var result = list.executeQuery()) {
    9.73 +            while (result.next()) {
    9.74 +                users.add(mapColumns(result));
    9.75 +            }
    9.76 +        }
    9.77 +        return users;
    9.78 +    }
    9.79 +
    9.80 +    @Override
    9.81 +    public User find(int id) throws SQLException {
    9.82 +        find.setInt(1, id);
    9.83 +        try (var result = find.executeQuery()) {
    9.84 +            if (result.next()) {
    9.85 +                return mapColumns(result);
    9.86 +            } else {
    9.87 +                return null;
    9.88 +            }
    9.89 +        }
    9.90 +    }
    9.91  }
    10.1 --- a/src/main/java/de/uapcore/lightpit/entities/Project.java	Wed May 13 21:46:26 2020 +0200
    10.2 +++ b/src/main/java/de/uapcore/lightpit/entities/Project.java	Thu May 14 22:48:01 2020 +0200
    10.3 @@ -28,8 +28,6 @@
    10.4   */
    10.5  package de.uapcore.lightpit.entities;
    10.6  
    10.7 -import java.util.ArrayList;
    10.8 -import java.util.List;
    10.9  import java.util.Objects;
   10.10  
   10.11  public class Project {
   10.12 @@ -40,8 +38,6 @@
   10.13      private String repoUrl;
   10.14      private User owner;
   10.15  
   10.16 -    private final List<Version> versions = new ArrayList<>();
   10.17 -
   10.18      public Project(int id) {
   10.19          this.id = id;
   10.20      }
   10.21 @@ -82,10 +78,6 @@
   10.22          this.owner = owner;
   10.23      }
   10.24  
   10.25 -    public List<Version> getVersions() {
   10.26 -        return versions;
   10.27 -    }
   10.28 -
   10.29      @Override
   10.30      public boolean equals(Object o) {
   10.31          if (this == o) return true;
    11.1 --- a/src/main/java/de/uapcore/lightpit/entities/User.java	Wed May 13 21:46:26 2020 +0200
    11.2 +++ b/src/main/java/de/uapcore/lightpit/entities/User.java	Thu May 14 22:48:01 2020 +0200
    11.3 @@ -80,6 +80,19 @@
    11.4          this.lastname = lastname;
    11.5      }
    11.6  
    11.7 +    public String getDisplayname() {
    11.8 +        StringBuilder dn = new StringBuilder();
    11.9 +        dn.append(givenname);
   11.10 +        dn.append(' ');
   11.11 +        dn.append(lastname);
   11.12 +        dn.append(' ');
   11.13 +        if (mail != null && !mail.isBlank()) {
   11.14 +            dn.append("<"+mail+">");
   11.15 +        }
   11.16 +        final var str = dn.toString().trim();
   11.17 +        return str.isBlank() ? username : str;
   11.18 +    }
   11.19 +
   11.20      @Override
   11.21      public boolean equals(Object o) {
   11.22          if (this == o) return true;
    12.1 --- a/src/main/java/de/uapcore/lightpit/modules/HomeModule.java	Wed May 13 21:46:26 2020 +0200
    12.2 +++ b/src/main/java/de/uapcore/lightpit/modules/HomeModule.java	Thu May 14 22:48:01 2020 +0200
    12.3 @@ -31,6 +31,7 @@
    12.4  import de.uapcore.lightpit.*;
    12.5  
    12.6  import javax.servlet.annotation.WebServlet;
    12.7 +import javax.servlet.http.HttpServletRequest;
    12.8  
    12.9  /**
   12.10   * Entry point for the application.
   12.11 @@ -47,7 +48,10 @@
   12.12  public final class HomeModule extends AbstractLightPITServlet {
   12.13  
   12.14      @RequestMapping(method = HttpMethod.GET)
   12.15 -    public ResponseType handle() {
   12.16 +    public ResponseType handle(HttpServletRequest req) {
   12.17 +
   12.18 +        setDynamicFragment(req, "home");
   12.19 +        setStylesheet(req, "home");
   12.20  
   12.21          return ResponseType.HTML;
   12.22      }
    13.1 --- a/src/main/java/de/uapcore/lightpit/modules/ProjectsModule.java	Wed May 13 21:46:26 2020 +0200
    13.2 +++ b/src/main/java/de/uapcore/lightpit/modules/ProjectsModule.java	Thu May 14 22:48:01 2020 +0200
    13.3 @@ -31,9 +31,13 @@
    13.4  
    13.5  import de.uapcore.lightpit.*;
    13.6  import de.uapcore.lightpit.dao.DataAccessObjects;
    13.7 +import de.uapcore.lightpit.entities.Project;
    13.8 +import de.uapcore.lightpit.entities.User;
    13.9  
   13.10  import javax.servlet.annotation.WebServlet;
   13.11  import javax.servlet.http.HttpServletRequest;
   13.12 +import java.sql.SQLException;
   13.13 +import java.util.Optional;
   13.14  
   13.15  @LightPITModule(
   13.16          bundleBaseName = "localization.projects",
   13.17 @@ -47,12 +51,59 @@
   13.18  public final class ProjectsModule extends AbstractLightPITServlet {
   13.19  
   13.20      @RequestMapping(method = HttpMethod.GET)
   13.21 -    public ResponseType index(HttpServletRequest req, DataAccessObjects dao) {
   13.22 +    public ResponseType index(HttpServletRequest req, DataAccessObjects dao) throws SQLException {
   13.23 +        final var projectDao = dao.getProjectDao();
   13.24 +
   13.25 +        req.setAttribute("projects", projectDao.list());
   13.26 +        setDynamicFragment(req, "projects");
   13.27  
   13.28          return ResponseType.HTML;
   13.29      }
   13.30  
   13.31 -    @RequestMapping(method = HttpMethod.GET, requestPath = "versions", menuKey = "menu.versions")
   13.32 +    @RequestMapping(requestPath = "edit", method = HttpMethod.GET)
   13.33 +    public ResponseType displayCreateForm(HttpServletRequest req, DataAccessObjects dao) throws SQLException {
   13.34 +        final var projectDao = dao.getProjectDao();
   13.35 +
   13.36 +        Optional<Integer> id = getParameter(req, Integer.class, "id");
   13.37 +        if (id.isPresent()) {
   13.38 +            req.setAttribute("project", Optional.ofNullable(projectDao.find(id.get())).orElse(new Project(-1)));
   13.39 +        } else {
   13.40 +            req.setAttribute("project", new Project(-1));
   13.41 +        }
   13.42 +
   13.43 +        setDynamicFragment(req, "project-form");
   13.44 +
   13.45 +        return ResponseType.HTML;
   13.46 +    }
   13.47 +
   13.48 +    @RequestMapping(requestPath = "commit", method = HttpMethod.POST)
   13.49 +    public ResponseType commit(HttpServletRequest req, DataAccessObjects dao) {
   13.50 +
   13.51 +        Project project = new Project(-1);
   13.52 +        try {
   13.53 +            project = new Project(getParameter(req, Integer.class, "id").orElseThrow());
   13.54 +            project.setName(getParameter(req, String.class, "name").orElseThrow());
   13.55 +            getParameter(req, String.class, "description").ifPresent(project::setDescription);
   13.56 +            getParameter(req, String.class, "repoUrl").ifPresent(project::setRepoUrl);
   13.57 +            getParameter(req, Integer.class, "owner").map(
   13.58 +                    ownerId -> ownerId >= 0 ? new User(ownerId) : null
   13.59 +            ).ifPresent(project::setOwner);
   13.60 +
   13.61 +            dao.getProjectDao().saveOrUpdate(project);
   13.62 +
   13.63 +            setRedirectLocation(req, "./projects/");
   13.64 +            setDynamicFragment(req, Constants.DYN_FRAGMENT_COMMIT_SUCCESSFUL);
   13.65 +        } catch (NullPointerException | NumberFormatException | SQLException ex) {
   13.66 +            // TODO: set request attribute with error text
   13.67 +            req.setAttribute("project", project);
   13.68 +            setDynamicFragment(req, "project-form");
   13.69 +        }
   13.70 +
   13.71 +        return ResponseType.HTML;
   13.72 +    }
   13.73 +
   13.74 +
   13.75 +    @RequestMapping(requestPath = "versions", method = HttpMethod.GET, menuKey = "menu.versions")
   13.76      public ResponseType versions(HttpServletRequest req, DataAccessObjects dao) {
   13.77  
   13.78          return ResponseType.HTML;
    14.1 --- a/src/main/resources/localization/home.properties	Wed May 13 21:46:26 2020 +0200
    14.2 +++ b/src/main/resources/localization/home.properties	Thu May 14 22:48:01 2020 +0200
    14.3 @@ -24,3 +24,5 @@
    14.4  name = Home Page
    14.5  description = The default page that is displayed when visiting the site.
    14.6  menuLabel = Home
    14.7 +
    14.8 +version=LightPIT - Version 0.1 (Snapshot) 
    14.9 \ No newline at end of file
    15.1 --- a/src/main/resources/localization/home_de.properties	Wed May 13 21:46:26 2020 +0200
    15.2 +++ b/src/main/resources/localization/home_de.properties	Thu May 14 22:48:01 2020 +0200
    15.3 @@ -24,3 +24,5 @@
    15.4  name = Startseite
    15.5  description = Die Seite, die dem Benutzer standardm\u00e4\u00dfig beim Besuch angezeigt wird.
    15.6  menuLabel = Startseite
    15.7 +
    15.8 +version=LightPIT - Version 0.1 (Entwicklungsversion) 
    16.1 --- /dev/null	Thu Jan 01 00:00:00 1970 +0000
    16.2 +++ b/src/main/resources/localization/lightpit.properties	Thu May 14 22:48:01 2020 +0200
    16.3 @@ -0,0 +1,28 @@
    16.4 +# Copyright 2018 Mike Becker. All rights reserved.
    16.5 +#
    16.6 +# Redistribution and use in source and binary forms, with or without
    16.7 +# modification, are permitted provided that the following conditions are met:
    16.8 +#
    16.9 +# 1. Redistributions of source code must retain the above copyright
   16.10 +# notice, this list of conditions and the following disclaimer.
   16.11 +#
   16.12 +# 2. Redistributions in binary form must reproduce the above copyright
   16.13 +# notice, this list of conditions and the following disclaimer in the
   16.14 +# documentation and/or other materials provided with the distribution.
   16.15 +#
   16.16 +# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
   16.17 +# AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
   16.18 +# IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
   16.19 +# DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
   16.20 +# FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
   16.21 +# DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
   16.22 +# SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
   16.23 +# CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
   16.24 +# OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
   16.25 +# OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
   16.26 +
   16.27 +button.okay=OK
   16.28 +button.cancel=Cancel
   16.29 +
   16.30 +commit.success=Operation successful - you will be redirected in a second.
   16.31 +commit.redirect-link=If redirection does not work, click the following link:
   16.32 \ No newline at end of file
    17.1 --- /dev/null	Thu Jan 01 00:00:00 1970 +0000
    17.2 +++ b/src/main/resources/localization/lightpit_de.properties	Thu May 14 22:48:01 2020 +0200
    17.3 @@ -0,0 +1,28 @@
    17.4 +# Copyright 2018 Mike Becker. All rights reserved.
    17.5 +#
    17.6 +# Redistribution and use in source and binary forms, with or without
    17.7 +# modification, are permitted provided that the following conditions are met:
    17.8 +#
    17.9 +# 1. Redistributions of source code must retain the above copyright
   17.10 +# notice, this list of conditions and the following disclaimer.
   17.11 +#
   17.12 +# 2. Redistributions in binary form must reproduce the above copyright
   17.13 +# notice, this list of conditions and the following disclaimer in the
   17.14 +# documentation and/or other materials provided with the distribution.
   17.15 +#
   17.16 +# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
   17.17 +# AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
   17.18 +# IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
   17.19 +# DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
   17.20 +# FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
   17.21 +# DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
   17.22 +# SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
   17.23 +# CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
   17.24 +# OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
   17.25 +# OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
   17.26 +
   17.27 +button.okay=OK
   17.28 +button.cancel=Abbrechen
   17.29 +
   17.30 +commit.success=Operation erfolgreich - Sie werden jeden Moment weitergeleitet.
   17.31 +commit.redirect-link=Falls die Weiterleitung nicht klappt, klicken Sie bitte hier:
    18.1 --- a/src/main/resources/localization/projects.properties	Wed May 13 21:46:26 2020 +0200
    18.2 +++ b/src/main/resources/localization/projects.properties	Thu May 14 22:48:01 2020 +0200
    18.3 @@ -20,7 +20,20 @@
    18.4  # CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
    18.5  # OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
    18.6  # OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
    18.7 +
    18.8  name=Project Management
    18.9  description=Allows the configuration of projects.
   18.10  menuLabel=Projects
   18.11 +
   18.12  menu.versions=Versions
   18.13 +
   18.14 +button.create=New Project
   18.15 +
   18.16 +no-projects=Welcome to LightPIT. Start off by creating a new project!
   18.17 +
   18.18 +thead.name=Name
   18.19 +thead.description=Description
   18.20 +thead.repoUrl=Repository
   18.21 +thead.owner=Project Lead
   18.22 +
   18.23 +placeholder.null-owner=Unassigned
    19.1 --- a/src/main/resources/localization/projects_de.properties	Wed May 13 21:46:26 2020 +0200
    19.2 +++ b/src/main/resources/localization/projects_de.properties	Thu May 14 22:48:01 2020 +0200
    19.3 @@ -20,7 +20,20 @@
    19.4  # CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
    19.5  # OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
    19.6  # OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
    19.7 +
    19.8  name=Projektverwaltung
    19.9  description=Erlaubt die Konfiguration von Projekten.
   19.10  menuLabel=Projekte
   19.11 -menu.versions=Versionen
   19.12 \ No newline at end of file
   19.13 +
   19.14 +menu.versions=Versionen
   19.15 +
   19.16 +button.create=Neues Projekt
   19.17 +
   19.18 +no-projects=Wilkommen bei LightPIT. Beginnen Sie mit der Erstellung eines Projektes!
   19.19 +
   19.20 +thead.name=Name
   19.21 +thead.description=Beschreibung
   19.22 +thead.repoUrl=Repository
   19.23 +thead.owner=Projektleitung
   19.24 +
   19.25 +placeholder.null-owner=Nicht Zugewiesen
    20.1 --- /dev/null	Thu Jan 01 00:00:00 1970 +0000
    20.2 +++ b/src/main/webapp/WEB-INF/dynamic_fragments/commit-successful.jsp	Thu May 14 22:48:01 2020 +0200
    20.3 @@ -0,0 +1,34 @@
    20.4 +<%--
    20.5 +DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS HEADER.
    20.6 +
    20.7 +Copyright 2018 Mike Becker. All rights reserved.
    20.8 +
    20.9 +Redistribution and use in source and binary forms, with or without
   20.10 +modification, are permitted provided that the following conditions are met:
   20.11 +
   20.12 +1. Redistributions of source code must retain the above copyright
   20.13 +notice, this list of conditions and the following disclaimer.
   20.14 +
   20.15 +2. Redistributions in binary form must reproduce the above copyright
   20.16 +notice, this list of conditions and the following disclaimer in the
   20.17 +documentation and/or other materials provided with the distribution.
   20.18 +
   20.19 +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
   20.20 +AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
   20.21 +IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
   20.22 +DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
   20.23 +FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
   20.24 +DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
   20.25 +SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
   20.26 +CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
   20.27 +OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
   20.28 +OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
   20.29 +--%>
   20.30 +<%@page pageEncoding="UTF-8" %>
   20.31 +<%@taglib prefix="c" uri="http://java.sun.com/jsp/jstl/core" %>
   20.32 +<%@taglib prefix="fmt" uri="http://java.sun.com/jsp/jstl/fmt" %>
   20.33 +
   20.34 +<c:set scope="page" var="redirectLocation" value="${requestScope[Constants.REQ_ATTR_REDIRECT_LOCATION]}"/>
   20.35 +
   20.36 +<fmt:message bundle="${lightpit_bundle}" key="commit.success" />
   20.37 +<fmt:message bundle="${lightpit_bundle}" key="commit.redirect-link" />
    21.1 --- a/src/main/webapp/WEB-INF/dynamic_fragments/error.jsp	Wed May 13 21:46:26 2020 +0200
    21.2 +++ b/src/main/webapp/WEB-INF/dynamic_fragments/error.jsp	Thu May 14 22:48:01 2020 +0200
    21.3 @@ -25,11 +25,13 @@
    21.4  OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. 
    21.5  --%>
    21.6  <%@page pageEncoding="UTF-8" %>
    21.7 +<%@page import="de.uapcore.lightpit.Constants" %>
    21.8  <%@page import="de.uapcore.lightpit.modules.ErrorModule" %>
    21.9  <%@taglib prefix="c" uri="http://java.sun.com/jsp/jstl/core" %>
   21.10  <%@taglib prefix="fmt" uri="http://java.sun.com/jsp/jstl/fmt" %>
   21.11  <%@ taglib prefix="fn" uri="http://java.sun.com/jsp/jstl/functions" %>
   21.12  
   21.13 +<c:set scope="page" var="baseHref" value="${requestScope[Constants.REQ_ATTR_BASE_HREF]}" />
   21.14  <c:set scope="page" var="errorCode" value="${requestScope[ErrorModule.REQ_ATTR_ERROR_CODE]}"/>
   21.15  <c:set scope="page" var="returnLink" value="${requestScope[ErrorModule.REQ_ATTR_RETURN_LINK]}"/>
   21.16  
    22.1 --- /dev/null	Thu Jan 01 00:00:00 1970 +0000
    22.2 +++ b/src/main/webapp/WEB-INF/dynamic_fragments/home.jsp	Thu May 14 22:48:01 2020 +0200
    22.3 @@ -0,0 +1,32 @@
    22.4 +<%--
    22.5 +DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS HEADER.
    22.6 +
    22.7 +Copyright 2018 Mike Becker. All rights reserved.
    22.8 +
    22.9 +Redistribution and use in source and binary forms, with or without
   22.10 +modification, are permitted provided that the following conditions are met:
   22.11 +
   22.12 +1. Redistributions of source code must retain the above copyright
   22.13 +notice, this list of conditions and the following disclaimer.
   22.14 +
   22.15 +2. Redistributions in binary form must reproduce the above copyright
   22.16 +notice, this list of conditions and the following disclaimer in the
   22.17 +documentation and/or other materials provided with the distribution.
   22.18 +
   22.19 +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
   22.20 +AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
   22.21 +IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
   22.22 +DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
   22.23 +FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
   22.24 +DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
   22.25 +SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
   22.26 +CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
   22.27 +OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
   22.28 +OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
   22.29 +--%>
   22.30 +<%@page pageEncoding="UTF-8" %>
   22.31 +<%@taglib prefix="fmt" uri="http://java.sun.com/jsp/jstl/fmt" %>
   22.32 +
   22.33 +<div class="smalltext">
   22.34 +    <fmt:message key="version" />
   22.35 +</div>
   22.36 \ No newline at end of file
    23.1 --- /dev/null	Thu Jan 01 00:00:00 1970 +0000
    23.2 +++ b/src/main/webapp/WEB-INF/dynamic_fragments/project-form.jsp	Thu May 14 22:48:01 2020 +0200
    23.3 @@ -0,0 +1,75 @@
    23.4 +<%--
    23.5 +DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS HEADER.
    23.6 +
    23.7 +Copyright 2018 Mike Becker. All rights reserved.
    23.8 +
    23.9 +Redistribution and use in source and binary forms, with or without
   23.10 +modification, are permitted provided that the following conditions are met:
   23.11 +
   23.12 +1. Redistributions of source code must retain the above copyright
   23.13 +notice, this list of conditions and the following disclaimer.
   23.14 +
   23.15 +2. Redistributions in binary form must reproduce the above copyright
   23.16 +notice, this list of conditions and the following disclaimer in the
   23.17 +documentation and/or other materials provided with the distribution.
   23.18 +
   23.19 +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
   23.20 +AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
   23.21 +IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
   23.22 +DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
   23.23 +FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
   23.24 +DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
   23.25 +SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
   23.26 +CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
   23.27 +OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
   23.28 +OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
   23.29 +--%>
   23.30 +<%@page pageEncoding="UTF-8" %>
   23.31 +<%@page import="de.uapcore.lightpit.Constants" %>
   23.32 +<%@taglib prefix="c" uri="http://java.sun.com/jsp/jstl/core" %>
   23.33 +<%@taglib prefix="fmt" uri="http://java.sun.com/jsp/jstl/fmt" %>
   23.34 +
   23.35 +<c:set scope="page" var="moduleInfo" value="${requestScope[Constants.REQ_ATTR_MODULE_INFO]}"/>
   23.36 +
   23.37 +<jsp:useBean id="project" type="de.uapcore.lightpit.entities.Project" scope="request"/>
   23.38 +
   23.39 +<form action="./${moduleInfo.modulePath}/commit" method="post">
   23.40 +    <table class="formtable" style="width: 80ch">
   23.41 +        <colgroup>
   23.42 +            <col>
   23.43 +            <col style="width: 100%">
   23.44 +        </colgroup>
   23.45 +        <tbody>
   23.46 +        <tr>
   23.47 +            <th><fmt:message key="thead.name"/></th>
   23.48 +            <td><input name="name" type="text" maxlength="20" required value="${project.name}"/> </td>
   23.49 +        </tr>
   23.50 +        <tr>
   23.51 +            <th class="vtop"><fmt:message key="thead.description"/></th>
   23.52 +            <td><input type="text" name="description" maxlength="200" value="${project.description}" /> </td>
   23.53 +        </tr>
   23.54 +        <tr>
   23.55 +            <th><fmt:message key="thead.repoUrl"/></th>
   23.56 +            <td><input name="repoUrl" type="url" maxlength="50" value="${project.repoUrl}" /> </td>
   23.57 +        </tr>
   23.58 +        <tr>
   23.59 +            <th><fmt:message key="thead.owner"/></th>
   23.60 +            <td>
   23.61 +                <select name="owner">
   23.62 +                    <option value="-1"><fmt:message key="placeholder.null-owner" /> </option>
   23.63 +                    <!-- TODO: add user selection -->
   23.64 +                </select>
   23.65 +            </td>
   23.66 +        </tr>
   23.67 +        </tbody>
   23.68 +        <tfoot>
   23.69 +        <tr>
   23.70 +            <td colspan="2">
   23.71 +                <input type="hidden" name="id" value="${project.id}" />
   23.72 +                <a href="./${moduleInfo.modulePath}" class="button"><fmt:message bundle="${lightpit_bundle}" key="button.cancel"/></a>
   23.73 +                <button type="submit"><fmt:message bundle="${lightpit_bundle}" key="button.okay" /></button>
   23.74 +            </td>
   23.75 +        </tr>
   23.76 +        </tfoot>
   23.77 +    </table>
   23.78 +</form>
    24.1 --- /dev/null	Thu Jan 01 00:00:00 1970 +0000
    24.2 +++ b/src/main/webapp/WEB-INF/dynamic_fragments/projects.jsp	Thu May 14 22:48:01 2020 +0200
    24.3 @@ -0,0 +1,83 @@
    24.4 +<%--
    24.5 +DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS HEADER.
    24.6 +
    24.7 +Copyright 2018 Mike Becker. All rights reserved.
    24.8 +
    24.9 +Redistribution and use in source and binary forms, with or without
   24.10 +modification, are permitted provided that the following conditions are met:
   24.11 +
   24.12 +1. Redistributions of source code must retain the above copyright
   24.13 +notice, this list of conditions and the following disclaimer.
   24.14 +
   24.15 +2. Redistributions in binary form must reproduce the above copyright
   24.16 +notice, this list of conditions and the following disclaimer in the
   24.17 +documentation and/or other materials provided with the distribution.
   24.18 +
   24.19 +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
   24.20 +AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
   24.21 +IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
   24.22 +DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
   24.23 +FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
   24.24 +DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
   24.25 +SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
   24.26 +CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
   24.27 +OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
   24.28 +OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
   24.29 +--%>
   24.30 +<%@page pageEncoding="UTF-8" %>
   24.31 +<%@page import="de.uapcore.lightpit.Constants" %>
   24.32 +<%@taglib prefix="c" uri="http://java.sun.com/jsp/jstl/core" %>
   24.33 +<%@taglib prefix="fmt" uri="http://java.sun.com/jsp/jstl/fmt" %>
   24.34 +
   24.35 +<c:set scope="page" var="moduleInfo" value="${requestScope[Constants.REQ_ATTR_MODULE_INFO]}"/>
   24.36 +
   24.37 +<jsp:useBean id="projects" type="java.util.List<de.uapcore.lightpit.entities.Project>" scope="request"/>
   24.38 +
   24.39 +<c:if test="${empty projects}">
   24.40 +    <div class="info-box">
   24.41 +    <fmt:message key="no-projects" />
   24.42 +    </div>
   24.43 +</c:if>
   24.44 +
   24.45 +<div id="tool-area">
   24.46 +    <a href="./${moduleInfo.modulePath}/edit" class="button"><fmt:message key="button.create" /></a>
   24.47 +</div>
   24.48 +
   24.49 +<c:if test="${not empty projects}">
   24.50 +<table class="datatable medskip">
   24.51 +    <colgroup>
   24.52 +        <col>
   24.53 +        <col style="width: 15%">
   24.54 +        <col style="width: 35%">
   24.55 +        <col style="width: 30%">
   24.56 +        <col style="width: 20%">
   24.57 +    </colgroup>
   24.58 +    <thead>
   24.59 +    <tr>
   24.60 +        <th></th>
   24.61 +        <th><fmt:message key="thead.name"/></th>
   24.62 +        <th><fmt:message key="thead.description"/></th>
   24.63 +        <th><fmt:message key="thead.repoUrl"/></th>
   24.64 +        <th><fmt:message key="thead.owner"/></th>
   24.65 +    </tr>
   24.66 +    </thead>
   24.67 +    <tbody>
   24.68 +    <c:forEach var="project" items="${projects}">
   24.69 +        <tr>
   24.70 +            <td><a href="./${moduleInfo.modulePath}/edit?id=${project.id}">&#x270e;</a></td>
   24.71 +            <td><c:out value="${project.name}"/></td>
   24.72 +            <td><c:out value="${project.description}"/></td>
   24.73 +            <td>
   24.74 +                <c:if test="${not empty project.repoUrl}">
   24.75 +                <a target="_blank" href="<c:out value="${project.repoUrl}"/>"><c:out value="${project.repoUrl}"/></a>
   24.76 +                </c:if>
   24.77 +            </td>
   24.78 +            <td>
   24.79 +            <c:if test="${not empty project.owner}"><c:out value="${project.owner.displayname}"/></c:if>
   24.80 +            <c:if test="${empty project.owner}"><fmt:message key="placeholder.null-owner" /></c:if>
   24.81 +            </td>
   24.82 +        </tr>
   24.83 +    </c:forEach>
   24.84 +    </tbody>
   24.85 +</table>
   24.86 +</c:if>
    25.1 --- a/src/main/webapp/WEB-INF/jsp/site.jsp	Wed May 13 21:46:26 2020 +0200
    25.2 +++ b/src/main/webapp/WEB-INF/jsp/site.jsp	Thu May 14 22:48:01 2020 +0200
    25.3 @@ -31,7 +31,7 @@
    25.4  <%@taglib prefix="fn" uri="http://java.sun.com/jsp/jstl/functions" %>
    25.5  
    25.6  <%-- Make the base href easily available at request scope --%>
    25.7 -<c:set scope="request" var="baseHref" value="${pageContext.request.scheme}://${pageContext.request.serverName}:${pageContext.request.serverPort}${pageContext.request.contextPath}/" />
    25.8 +<c:set scope="page" var="baseHref" value="${requestScope[Constants.REQ_ATTR_BASE_HREF]}" />
    25.9  
   25.10  <%-- Define an alias for the request path --%>
   25.11  <c:set scope="page" var="requestPath" value="${requestScope[Constants.REQ_ATTR_PATH]}"/>
   25.12 @@ -45,6 +45,9 @@
   25.13  <%-- Define an alias for the fragment name --%>
   25.14  <c:set scope="page" var="fragment" value="${requestScope[Constants.REQ_ATTR_FRAGMENT]}"/>
   25.15  
   25.16 +<%-- Define an alias for the optional redirect location --%>
   25.17 +<c:set scope="page" var="redirectLocation" value="${requestScope[Constants.REQ_ATTR_REDIRECT_LOCATION]}"/>
   25.18 +
   25.19  <%-- Define an alias for the additional stylesheet --%>
   25.20  <c:set scope="page" var="extraCss" value="${requestScope[Constants.REQ_ATTR_STYLESHEET]}"/>
   25.21  
   25.22 @@ -66,6 +69,9 @@
   25.23              </fmt:bundle>
   25.24          </title>
   25.25          <meta charset="UTF-8">
   25.26 +        <c:if test="${not empty redirectLocation}">
   25.27 +        <meta http-equiv="refresh" content="0; URL=${redirectLocation}">
   25.28 +        </c:if>
   25.29          <link rel="stylesheet" href="lightpit.css" type="text/css">
   25.30          <c:if test="${not empty extraCss}">
   25.31          <link rel="stylesheet" href="${extraCss}" type="text/css">
   25.32 @@ -87,6 +93,7 @@
   25.33          <div id="content-area">
   25.34              <c:if test="${not empty fragment}">
   25.35                  <fmt:setBundle scope="request" basename="${moduleInfo.bundleBaseName}"/>
   25.36 +                <fmt:setBundle scope="request" var="lightpit_bundle" basename="localization.lightpit"/>
   25.37                  <c:import url="${fragment}" />
   25.38              </c:if>
   25.39          </div>
    26.1 --- a/src/main/webapp/lightpit.css	Wed May 13 21:46:26 2020 +0200
    26.2 +++ b/src/main/webapp/lightpit.css	Thu May 14 22:48:01 2020 +0200
    26.3 @@ -28,22 +28,17 @@
    26.4   */
    26.5  
    26.6  html {
    26.7 -    background: #f8f8f8;
    26.8 +    font-family: sans-serif;
    26.9 +    background: white;
   26.10 +    color: #1c204e;
   26.11 +    margin: 0;
   26.12 +    padding: 0;
   26.13  }
   26.14  
   26.15  body {
   26.16 -    background: white;
   26.17 -    font-family: serif;
   26.18 -    
   26.19 -    border-color: #505050;
   26.20 -    border-style: solid;
   26.21 -    border-width: 1pt;
   26.22 -    
   26.23 -    color: #1c202e;
   26.24 -}
   26.25 -
   26.26 -h1, h2, h3, h4, #mainMenu, #subMenu {
   26.27 -    font-family: sans-serif;
   26.28 +    height: 100%;
   26.29 +    margin: 0;
   26.30 +    padding: 0;
   26.31  }
   26.32  
   26.33  a {
   26.34 @@ -88,6 +83,33 @@
   26.35      padding: 1em;
   26.36  }
   26.37  
   26.38 +button, a.button {
   26.39 +    display: inline-block;
   26.40 +    font-size: medium;
   26.41 +    border-style: outset;
   26.42 +    border-width: 2pt;
   26.43 +    border-color: #6060cc;
   26.44 +    color: inherit;
   26.45 +    background: #f0f0f0;
   26.46 +
   26.47 +    padding: .25em .5em .25em .5em;
   26.48 +    cursor: default;
   26.49 +    text-decoration: none;
   26.50 +}
   26.51 +
   26.52 +button:hover, a.button:hover {
   26.53 +    background: #f0f0ff;
   26.54 +}
   26.55 +
   26.56 +button[type=submit] {
   26.57 +    background: #20a0ff;
   26.58 +    color: white;
   26.59 +}
   26.60 +
   26.61 +button[type=submit]:hover {
   26.62 +    background: #1090cf;
   26.63 +}
   26.64 +
   26.65  th {
   26.66      text-align: left;
   26.67  }
   26.68 @@ -101,6 +123,7 @@
   26.69  }
   26.70  
   26.71  table.datatable th {
   26.72 +    white-space: nowrap;
   26.73      font-weight: bold;
   26.74      background: lightsteelblue;
   26.75  }
   26.76 @@ -116,6 +139,35 @@
   26.77      background: lightblue;
   26.78  }
   26.79  
   26.80 +table.formtable {
   26.81 +    border-style: none;
   26.82 +    border-collapse: separate;
   26.83 +    border-spacing: 1em;
   26.84 +}
   26.85 +
   26.86 +table.formtable th {
   26.87 +    font-weight: bold;
   26.88 +    text-align: left;
   26.89 +    vertical-align: center;
   26.90 +    white-space: nowrap;
   26.91 +}
   26.92 +
   26.93 +table.formtable tbody td > * {
   26.94 +    width: 100%;
   26.95 +}
   26.96 +
   26.97 +table.formtable tfoot td {
   26.98 +    text-align: right;
   26.99 +}
  26.100 +
  26.101 +.fullwidth {
  26.102 +    width: 100%;
  26.103 +}
  26.104 +
  26.105 +.vtop {
  26.106 +    vertical-align: top;
  26.107 +}
  26.108 +
  26.109  .hcenter {
  26.110      text-align: center;
  26.111  }
  26.112 @@ -126,4 +178,16 @@
  26.113  
  26.114  .nowrap {
  26.115      white-space: nowrap;
  26.116 +}
  26.117 +
  26.118 +.medskip {
  26.119 +    margin-top: .5em;
  26.120 +}
  26.121 +
  26.122 +.info-box {
  26.123 +    margin: 2em;
  26.124 +    border-style: dashed;
  26.125 +    border-width: 1pt;
  26.126 +    border-color: deepskyblue;
  26.127 +    padding: 1em;
  26.128  }
  26.129 \ No newline at end of file

mercurial