├── src
├── main
│ ├── frontend
│ │ ├── themes
│ │ │ └── vj
│ │ │ │ ├── styles.css
│ │ │ │ ├── theme.json
│ │ │ │ └── main-layout.css
│ │ └── index.html
│ ├── resources
│ │ ├── i18n
│ │ │ └── messages_de.properties
│ │ ├── META-INF
│ │ │ └── resources
│ │ │ │ └── icons
│ │ │ │ └── icon.png
│ │ ├── db
│ │ │ └── migration
│ │ │ │ ├── V001__create_user.sql
│ │ │ │ └── V002__create_person.sql
│ │ ├── banner.txt
│ │ └── application.properties
│ └── java
│ │ └── ch
│ │ └── martinelli
│ │ └── vj
│ │ ├── domain
│ │ ├── user
│ │ │ ├── Role.java
│ │ │ ├── UserWithRoles.java
│ │ │ └── UserService.java
│ │ └── person
│ │ │ └── PersonService.java
│ │ ├── VjApplication.java
│ │ ├── configuration
│ │ └── VjJooqConfiguration.java
│ │ ├── security
│ │ ├── AuthenticatedUser.java
│ │ ├── UserDetailsServiceImpl.java
│ │ └── SecurityConfiguration.java
│ │ └── ui
│ │ ├── views
│ │ ├── helloworld
│ │ │ └── HelloWorldView.java
│ │ ├── login
│ │ │ └── LoginView.java
│ │ ├── user
│ │ │ └── UserView.java
│ │ └── person
│ │ │ └── PersonView.java
│ │ ├── components
│ │ └── Notifier.java
│ │ ├── i18n
│ │ └── TranslationProvider.java
│ │ └── layout
│ │ └── MainLayout.java
└── test
│ ├── resources
│ ├── junit-platform.properties
│ ├── application.properties
│ └── db
│ │ └── migration
│ │ └── afterMigrate.sql
│ └── java
│ └── ch
│ └── martinelli
│ └── vj
│ ├── TestVjApplication.java
│ ├── TestVjConfiguration.java
│ └── ui
│ ├── views
│ └── helloworld
│ │ ├── HelloWorldViewIT.java
│ │ └── HelloWorldViewTest.java
│ ├── PlaywrightIT.java
│ └── KaribuTest.java
├── .mvn
└── wrapper
│ ├── maven-wrapper.jar
│ ├── maven-wrapper.properties
│ └── MavenWrapperDownloader.java
├── .gitignore
├── .github
├── workflows
│ └── build.yml
└── dependabot.yml
├── README.md
├── mvnw.cmd
├── mvnw
├── LICENSE
└── pom.xml
/src/main/frontend/themes/vj/styles.css:
--------------------------------------------------------------------------------
1 | @import url('./main-layout.css');
--------------------------------------------------------------------------------
/.mvn/wrapper/maven-wrapper.jar:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/simasch/vaadin-jooq-template/HEAD/.mvn/wrapper/maven-wrapper.jar
--------------------------------------------------------------------------------
/src/main/frontend/themes/vj/theme.json:
--------------------------------------------------------------------------------
1 | {
2 | "lumoImports" : [ "typography", "color", "spacing", "badge", "utility" ]
3 | }
--------------------------------------------------------------------------------
/src/main/resources/i18n/messages_de.properties:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/simasch/vaadin-jooq-template/HEAD/src/main/resources/i18n/messages_de.properties
--------------------------------------------------------------------------------
/src/test/resources/junit-platform.properties:
--------------------------------------------------------------------------------
1 | junit.jupiter.displayname.generator.default=org.junit.jupiter.api.DisplayNameGenerator$ReplaceUnderscores
2 |
--------------------------------------------------------------------------------
/src/main/resources/META-INF/resources/icons/icon.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/simasch/vaadin-jooq-template/HEAD/src/main/resources/META-INF/resources/icons/icon.png
--------------------------------------------------------------------------------
/src/main/java/ch/martinelli/vj/domain/user/Role.java:
--------------------------------------------------------------------------------
1 | package ch.martinelli.vj.domain.user;
2 |
3 | public class Role {
4 |
5 | public static final String USER = "USER";
6 | public static final String ADMIN = "ADMIN";
7 |
8 | private Role() {
9 | }
10 | }
11 |
--------------------------------------------------------------------------------
/src/test/java/ch/martinelli/vj/TestVjApplication.java:
--------------------------------------------------------------------------------
1 | package ch.martinelli.vj;
2 |
3 | import org.springframework.boot.SpringApplication;
4 |
5 | public class TestVjApplication {
6 |
7 | public static void main(String[] args) {
8 | SpringApplication.from(VjApplication::main).with(TestVjConfiguration.class).run(args);
9 | }
10 | }
11 |
--------------------------------------------------------------------------------
/src/main/resources/db/migration/V001__create_user.sql:
--------------------------------------------------------------------------------
1 | create table "user"
2 | (
3 | username varchar(100) primary key,
4 | first_name varchar(100) not null,
5 | last_name varchar(100) not null,
6 | hashed_password varchar not null,
7 | picture bytea
8 | );
9 |
10 | create table user_role
11 | (
12 | username varchar(100) not null,
13 | role varchar(100) not null,
14 |
15 | primary key (username, role)
16 | );
--------------------------------------------------------------------------------
/.gitignore:
--------------------------------------------------------------------------------
1 | /target/
2 | .idea/
3 | .vscode/
4 | .settings
5 | .project
6 | .classpath
7 |
8 | *.iml
9 | .DS_Store
10 |
11 | # The following files are generated/updated by vaadin-maven-plugin
12 | node_modules/
13 | frontend/generated/
14 | pnpmfile.js
15 | vite.generated.ts
16 |
17 | # Browser drivers for local integration tests
18 | drivers/
19 | # Error screenshots generated by TestBench for failed integration tests
20 | error-screenshots/
21 | webpack.generated.js
22 | /src/main/frontend/generated/
23 |
--------------------------------------------------------------------------------
/.github/workflows/build.yml:
--------------------------------------------------------------------------------
1 | name: Java CI with Maven
2 |
3 | on:
4 | push:
5 | branches: [ "main" ]
6 | pull_request:
7 | branches: [ "main" ]
8 |
9 | jobs:
10 | build:
11 |
12 | runs-on: ubuntu-latest
13 |
14 | steps:
15 | - uses: actions/checkout@v4
16 | - name: Set up JDK 21
17 | uses: actions/setup-java@v4
18 | with:
19 | java-version: '21'
20 | distribution: 'temurin'
21 | cache: maven
22 | - name: Build with Maven
23 | run: mvn -B verify
24 |
--------------------------------------------------------------------------------
/src/main/frontend/themes/vj/main-layout.css:
--------------------------------------------------------------------------------
1 | vaadin-scroller[slot="drawer"] {
2 | padding: var(--lumo-space-s);
3 | }
4 |
5 | vaadin-side-nav-item vaadin-icon {
6 | padding: 0;
7 | }
8 |
9 | [slot="drawer"]:is(header, footer) {
10 | display: flex;
11 | align-items: center;
12 | gap: var(--lumo-space-s);
13 | padding: var(--lumo-space-s) var(--lumo-space-m);
14 | min-height: var(--lumo-size-xl);
15 | box-sizing: border-box;
16 | }
17 |
18 | [slot="drawer"]:is(header, footer):is(:empty) {
19 | display: none;
20 | }
21 |
--------------------------------------------------------------------------------
/src/main/java/ch/martinelli/vj/VjApplication.java:
--------------------------------------------------------------------------------
1 | package ch.martinelli.vj;
2 |
3 | import com.vaadin.flow.component.page.AppShellConfigurator;
4 | import com.vaadin.flow.theme.Theme;
5 | import org.springframework.boot.SpringApplication;
6 | import org.springframework.boot.autoconfigure.SpringBootApplication;
7 |
8 | @SpringBootApplication
9 | @Theme(value = "vj")
10 | public class VjApplication implements AppShellConfigurator {
11 |
12 | public static void main(String[] args) {
13 | SpringApplication.run(VjApplication.class, args);
14 | }
15 | }
16 |
--------------------------------------------------------------------------------
/src/main/resources/db/migration/V002__create_person.sql:
--------------------------------------------------------------------------------
1 | create sequence person_seq start with 1000;
2 |
3 | create table person
4 | (
5 | id bigint not null default nextval('person_seq') primary key,
6 | version bigint not null,
7 | first_name varchar not null,
8 | last_name varchar not null,
9 | email varchar not null,
10 | phone varchar not null,
11 | date_of_birth date not null,
12 | occupation varchar not null,
13 | role varchar not null,
14 | important bool not null default false
15 | );
--------------------------------------------------------------------------------
/.github/dependabot.yml:
--------------------------------------------------------------------------------
1 | # To get started with Dependabot version updates, you'll need to specify which
2 | # package ecosystems to update and where the package manifests are located.
3 | # Please see the documentation for all configuration options:
4 | # https://docs.github.com/code-security/dependabot/dependabot-version-updates/configuration-options-for-the-dependabot.yml-file
5 |
6 | version: 2
7 | updates:
8 | - package-ecosystem: "maven"
9 | directory: "/"
10 | schedule:
11 | interval: "daily"
12 | - package-ecosystem: "github-actions"
13 | directory: "/"
14 | schedule:
15 | interval: "daily"
--------------------------------------------------------------------------------
/src/main/frontend/index.html:
--------------------------------------------------------------------------------
1 |
2 |
5 |
6 |
7 |
8 |
9 |
10 |
17 |
18 |
19 |
20 |
21 |
22 |
23 |
24 |
--------------------------------------------------------------------------------
/src/main/resources/banner.txt:
--------------------------------------------------------------------------------
1 | __ __ _ _ _ ___ ___ ___ _____ _ _
2 | \ \ / /_ _ __ _ __| (_)_ __ (_)/ _ \ / _ \ / _ \ |_ _|__ _ __ ___ _ __ | | __ _| |_ ___
3 | \ \ / / _` |/ _` |/ _` | | '_ \ | | | | | | | | | | | | |/ _ \ '_ ` _ \| '_ \| |/ _` | __/ _ \
4 | \ V / (_| | (_| | (_| | | | | | | | |_| | |_| | |_| | | | __/ | | | | | |_) | | (_| | || __/
5 | \_/ \__,_|\__,_|\__,_|_|_| |_| _/ |\___/ \___/ \__\_\ |_|\___|_| |_| |_| .__/|_|\__,_|\__\___|
6 | |__/ |_|
7 |
--------------------------------------------------------------------------------
/src/main/java/ch/martinelli/vj/configuration/VjJooqConfiguration.java:
--------------------------------------------------------------------------------
1 | package ch.martinelli.vj.configuration;
2 |
3 | import org.jooq.impl.DefaultConfiguration;
4 | import org.springframework.boot.autoconfigure.jooq.DefaultConfigurationCustomizer;
5 | import org.springframework.context.annotation.Bean;
6 | import org.springframework.context.annotation.Configuration;
7 |
8 | @Configuration
9 | public class VjJooqConfiguration {
10 |
11 | @Bean
12 | public DefaultConfigurationCustomizer configurationCustomizer() {
13 | // Enable optimistic locking
14 | return (DefaultConfiguration c) -> c.settings()
15 | .withExecuteWithOptimisticLocking(true);
16 | }
17 | }
18 |
--------------------------------------------------------------------------------
/src/test/java/ch/martinelli/vj/TestVjConfiguration.java:
--------------------------------------------------------------------------------
1 | package ch.martinelli.vj;
2 |
3 | import org.springframework.boot.test.context.TestConfiguration;
4 | import org.springframework.boot.testcontainers.service.connection.ServiceConnection;
5 | import org.springframework.context.annotation.Bean;
6 | import org.testcontainers.containers.PostgreSQLContainer;
7 | import org.testcontainers.utility.DockerImageName;
8 |
9 | @TestConfiguration(proxyBeanMethods = false)
10 | public class TestVjConfiguration {
11 |
12 | @Bean
13 | @ServiceConnection
14 | PostgreSQLContainer> postgresContainer() {
15 | return new PostgreSQLContainer<>(DockerImageName.parse("postgres:16.4"));
16 | }
17 |
18 | }
19 |
--------------------------------------------------------------------------------
/src/test/resources/application.properties:
--------------------------------------------------------------------------------
1 | spring.mustache.check-template-location=false
2 |
3 | # Launch the default browser when starting the application in development mode
4 | vaadin.launch-browser=true
5 | # To improve the performance during development.
6 | # For more information https://vaadin.com/docs/flow/spring/tutorial-spring-configuration.html#special-configuration-parameters
7 | vaadin.whitelisted-packages=com.vaadin,org.vaadin,dev.hilla,ch.martinelli.vj
8 |
9 | # For encryption of JWT tokens
10 | # Change this parameter in production servers, you can generate a key by running
11 | # `openssl rand -base64 32`
12 | # and then passing the result as a parameter or environment variable
13 | jwt.auth.secret=88Q43JslymItiFR+rIuQ84fWkbUQCmu519cu2tfjAyo=
14 |
15 | logging.level.org.atmosphere=warn
16 | logging.level.org.jooq=debug
--------------------------------------------------------------------------------
/src/main/java/ch/martinelli/vj/domain/user/UserWithRoles.java:
--------------------------------------------------------------------------------
1 | package ch.martinelli.vj.domain.user;
2 |
3 | import ch.martinelli.vj.db.tables.records.UserRecord;
4 |
5 | import java.util.HashSet;
6 | import java.util.List;
7 | import java.util.Set;
8 |
9 | public class UserWithRoles {
10 | private final UserRecord user;
11 | private Set roles;
12 |
13 | public UserWithRoles() {
14 | this.user = new UserRecord();
15 | this.roles = new HashSet<>();
16 | }
17 |
18 | public UserWithRoles(UserRecord user, List roles) {
19 | this.user = user;
20 | this.roles = new HashSet<>(roles);
21 | }
22 |
23 | public UserRecord getUser() {
24 | return user;
25 | }
26 |
27 | public Set getRoles() {
28 | return roles;
29 | }
30 |
31 | public void setRoles(Set roles) {
32 | this.roles = roles;
33 | }
34 | }
35 |
--------------------------------------------------------------------------------
/src/main/resources/application.properties:
--------------------------------------------------------------------------------
1 | server.port=${PORT:8080}
2 |
3 | spring.mustache.check-template-location=false
4 |
5 | # Launch the default browser when starting the application in development mode
6 | vaadin.launch-browser=true
7 | # To improve the performance during development.
8 | # For more information https://vaadin.com/docs/flow/spring/tutorial-spring-configuration.html#special-configuration-parameters
9 | vaadin.whitelisted-packages=com.vaadin,org.vaadin,dev.hilla,ch.martinelli.vj
10 |
11 | spring.datasource.url=jdbc:postgresql://localhost:5432/vj
12 | spring.datasource.username=vj
13 | spring.datasource.password=vj
14 |
15 | # For encryption of JWT tokens
16 | # Change this parameter in production servers, you can generate a key by running
17 | # `openssl rand -base64 32`
18 | # and then passing the result as a parameter or environment variable
19 | jwt.auth.secret=88Q43JslymItiFR+rIuQ84fWkbUQCmu519cu2tfjAyo=
20 |
21 | logging.level.org.atmosphere=warn
--------------------------------------------------------------------------------
/.mvn/wrapper/maven-wrapper.properties:
--------------------------------------------------------------------------------
1 | # Licensed to the Apache Software Foundation (ASF) under one
2 | # or more contributor license agreements. See the NOTICE file
3 | # distributed with this work for additional information
4 | # regarding copyright ownership. The ASF licenses this file
5 | # to you under the Apache License, Version 2.0 (the
6 | # "License"); you may not use this file except in compliance
7 | # with the License. You may obtain a copy of the License at
8 | #
9 | # http://www.apache.org/licenses/LICENSE-2.0
10 | #
11 | # Unless required by applicable law or agreed to in writing,
12 | # software distributed under the License is distributed on an
13 | # "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
14 | # KIND, either express or implied. See the License for the
15 | # specific language governing permissions and limitations
16 | # under the License.
17 | distributionUrl=https://repo.maven.apache.org/maven2/org/apache/maven/apache-maven/3.8.4/apache-maven-3.8.4-bin.zip
18 | wrapperUrl=https://repo.maven.apache.org/maven2/org/apache/maven/wrapper/maven-wrapper/3.1.0/maven-wrapper-3.1.0.jar
19 |
--------------------------------------------------------------------------------
/src/test/java/ch/martinelli/vj/ui/views/helloworld/HelloWorldViewIT.java:
--------------------------------------------------------------------------------
1 | package ch.martinelli.vj.ui.views.helloworld;
2 |
3 | import ch.martinelli.vj.ui.PlaywrightIT;
4 | import in.virit.mopo.Mopo;
5 | import org.junit.jupiter.api.Test;
6 |
7 | import static org.assertj.core.api.Assertions.assertThat;
8 |
9 | class HelloWorldViewIT extends PlaywrightIT {
10 |
11 | @Test
12 | void say_hello() {
13 | page.navigate("http://localhost:%d".formatted(localServerPort));
14 | var mopo = new Mopo(page);
15 |
16 | var appName = page.locator("h1");
17 | assertThat(appName.innerText()).isEqualTo("Vaadin jOOQ Template");
18 |
19 | var title = page.locator("h2");
20 | assertThat(title.innerText()).isEqualTo("Hello World");
21 |
22 | page.locator("vaadin-text-field[id='name'] > input").fill("Test");
23 | mopo.click("id=say-hello");
24 |
25 | var notification = page.locator("vaadin-notification-card");
26 | System.out.println(notification.innerText());
27 | assertThat(notification.innerText()).endsWith("Hello Test");
28 | }
29 | }
--------------------------------------------------------------------------------
/src/main/java/ch/martinelli/vj/security/AuthenticatedUser.java:
--------------------------------------------------------------------------------
1 | package ch.martinelli.vj.security;
2 |
3 | import ch.martinelli.vj.db.tables.records.UserRecord;
4 | import ch.martinelli.vj.domain.user.UserService;
5 | import com.vaadin.flow.spring.security.AuthenticationContext;
6 | import org.springframework.security.oauth2.jwt.Jwt;
7 | import org.springframework.stereotype.Component;
8 |
9 | import java.util.Optional;
10 |
11 | @Component
12 | public class AuthenticatedUser {
13 |
14 | private final AuthenticationContext authenticationContext;
15 | private final UserService userService;
16 |
17 | public AuthenticatedUser(AuthenticationContext authenticationContext, UserService userService) {
18 | this.userService = userService;
19 | this.authenticationContext = authenticationContext;
20 | }
21 |
22 | public Optional get() {
23 | return authenticationContext.getAuthenticatedUser(Jwt.class)
24 | .flatMap(jwt -> userService.findUserByUsername(jwt.getSubject()));
25 | }
26 |
27 | public void logout() {
28 | authenticationContext.logout();
29 | }
30 |
31 | }
32 |
--------------------------------------------------------------------------------
/src/test/java/ch/martinelli/vj/ui/views/helloworld/HelloWorldViewTest.java:
--------------------------------------------------------------------------------
1 | package ch.martinelli.vj.ui.views.helloworld;
2 |
3 | import ch.martinelli.vj.ui.KaribuTest;
4 | import com.vaadin.flow.component.UI;
5 | import com.vaadin.flow.component.button.Button;
6 | import com.vaadin.flow.component.html.H1;
7 | import com.vaadin.flow.component.html.H2;
8 | import com.vaadin.flow.component.textfield.TextField;
9 | import org.junit.jupiter.api.BeforeEach;
10 | import org.junit.jupiter.api.Test;
11 |
12 | import static com.github.mvysny.kaributesting.v10.LocatorJ.*;
13 | import static com.github.mvysny.kaributesting.v10.NotificationsKt.expectNotifications;
14 | import static org.assertj.core.api.Assertions.assertThat;
15 |
16 | class HelloWorldViewTest extends KaribuTest {
17 |
18 | @BeforeEach
19 | void navigate() {
20 | UI.getCurrent().navigate(HelloWorldView.class);
21 | }
22 |
23 | @Test
24 | void say_hello() {
25 | var appName = _get(H1.class);
26 | assertThat(appName.getText()).isEqualTo("Vaadin jOOQ Template");
27 |
28 | var title = _get(H2.class);
29 | assertThat(title.getText()).isEqualTo("Hello World");
30 |
31 | _setValue(_get(TextField.class, s -> s.withId("name")), "Test");
32 | _click(_get(Button.class, s -> s.withId("say-hello")));
33 |
34 | expectNotifications("Hello Test");
35 | }
36 | }
--------------------------------------------------------------------------------
/src/main/java/ch/martinelli/vj/domain/person/PersonService.java:
--------------------------------------------------------------------------------
1 | package ch.martinelli.vj.domain.person;
2 |
3 | import ch.martinelli.vj.db.tables.records.PersonRecord;
4 | import org.jooq.DSLContext;
5 | import org.jooq.OrderField;
6 | import org.springframework.stereotype.Service;
7 | import org.springframework.transaction.annotation.Transactional;
8 |
9 | import java.util.List;
10 | import java.util.Optional;
11 |
12 | import static ch.martinelli.vj.db.tables.Person.PERSON;
13 |
14 | @Service
15 | public class PersonService {
16 |
17 | private final DSLContext ctx;
18 |
19 | public PersonService(DSLContext ctx) {
20 | this.ctx = ctx;
21 | }
22 |
23 | public List findAll(int offset, int limit, List> orderFields) {
24 | return ctx.selectFrom(PERSON)
25 | .orderBy(orderFields)
26 | .offset(offset)
27 | .limit(limit)
28 | .fetch();
29 | }
30 |
31 | @Transactional
32 | public void save(PersonRecord person) {
33 | ctx.attach(person);
34 | person.store();
35 | }
36 |
37 | public Optional findById(long id) {
38 | return ctx.selectFrom(PERSON)
39 | .where(PERSON.ID.eq(id))
40 | .fetchOptional();
41 | }
42 |
43 | @Transactional
44 | public void deleteById(long id) {
45 | ctx.deleteFrom(PERSON)
46 | .where(PERSON.ID.eq(id))
47 | .execute();
48 | }
49 | }
50 |
--------------------------------------------------------------------------------
/src/main/java/ch/martinelli/vj/ui/views/helloworld/HelloWorldView.java:
--------------------------------------------------------------------------------
1 | package ch.martinelli.vj.ui.views.helloworld;
2 |
3 | import ch.martinelli.vj.ui.components.Notifier;
4 | import ch.martinelli.vj.ui.layout.MainLayout;
5 | import com.vaadin.flow.component.Key;
6 | import com.vaadin.flow.component.button.Button;
7 | import com.vaadin.flow.component.orderedlayout.HorizontalLayout;
8 | import com.vaadin.flow.component.textfield.TextField;
9 | import com.vaadin.flow.router.HasDynamicTitle;
10 | import com.vaadin.flow.router.Route;
11 | import com.vaadin.flow.router.RouteAlias;
12 | import com.vaadin.flow.server.auth.AnonymousAllowed;
13 |
14 | @Route(value = "hello", layout = MainLayout.class)
15 | @RouteAlias(value = "", layout = MainLayout.class)
16 | @AnonymousAllowed
17 | public class HelloWorldView extends HorizontalLayout implements HasDynamicTitle {
18 |
19 | public HelloWorldView() {
20 | setMargin(true);
21 |
22 | var name = new TextField(getTranslation("Your name"));
23 | name.setId("name");
24 |
25 | var sayHello = new Button(getTranslation("Say hello"));
26 | sayHello.setId("say-hello");
27 | sayHello.addClickListener(e -> Notifier.info(getTranslation("Hello {0}", name.getValue())));
28 | sayHello.addClickShortcut(Key.ENTER);
29 |
30 | add(name, sayHello);
31 |
32 | setVerticalComponentAlignment(Alignment.END, name, sayHello);
33 | }
34 |
35 | @Override
36 | public String getPageTitle() {
37 | return getTranslation("Hello World");
38 | }
39 | }
40 |
--------------------------------------------------------------------------------
/src/main/java/ch/martinelli/vj/security/UserDetailsServiceImpl.java:
--------------------------------------------------------------------------------
1 | package ch.martinelli.vj.security;
2 |
3 | import ch.martinelli.vj.db.tables.records.UserRecord;
4 | import ch.martinelli.vj.domain.user.UserService;
5 | import org.springframework.security.core.authority.SimpleGrantedAuthority;
6 | import org.springframework.security.core.userdetails.User;
7 | import org.springframework.security.core.userdetails.UserDetails;
8 | import org.springframework.security.core.userdetails.UserDetailsService;
9 | import org.springframework.security.core.userdetails.UsernameNotFoundException;
10 | import org.springframework.stereotype.Service;
11 | import org.springframework.transaction.annotation.Transactional;
12 |
13 | import java.util.List;
14 |
15 |
16 | @Service
17 | public class UserDetailsServiceImpl implements UserDetailsService {
18 |
19 | private final UserService userService;
20 |
21 | public UserDetailsServiceImpl(UserService userService) {
22 | this.userService = userService;
23 | }
24 |
25 | @Override
26 | @Transactional(readOnly = true)
27 | public UserDetails loadUserByUsername(String username) throws UsernameNotFoundException {
28 | var user = userService.findUserByUsername(username)
29 | .orElseThrow(() -> new UsernameNotFoundException("No user present with username: " + username));
30 | return new User(user.getUsername(), user.getHashedPassword(), getAuthorities(user));
31 | }
32 |
33 | private List getAuthorities(UserRecord user) {
34 | return userService.findRolesByUsername(user.getUsername())
35 | .stream().map(role -> new SimpleGrantedAuthority("ROLE_" + role.getRole())).toList();
36 | }
37 |
38 | }
39 |
--------------------------------------------------------------------------------
/src/test/java/ch/martinelli/vj/ui/PlaywrightIT.java:
--------------------------------------------------------------------------------
1 | package ch.martinelli.vj.ui;
2 |
3 | import ch.martinelli.vj.TestVjConfiguration;
4 | import com.microsoft.playwright.*;
5 | import org.junit.jupiter.api.AfterAll;
6 | import org.junit.jupiter.api.AfterEach;
7 | import org.junit.jupiter.api.BeforeAll;
8 | import org.junit.jupiter.api.BeforeEach;
9 | import org.springframework.boot.test.context.SpringBootTest;
10 | import org.springframework.boot.test.web.server.LocalServerPort;
11 | import org.springframework.context.annotation.Import;
12 |
13 |
14 | @Import(TestVjConfiguration.class)
15 | @SpringBootTest(webEnvironment = SpringBootTest.WebEnvironment.RANDOM_PORT)
16 | public abstract class PlaywrightIT {
17 |
18 | @LocalServerPort
19 | protected Integer localServerPort;
20 |
21 | private static Playwright playwright;
22 | private static Browser browser;
23 | protected Page page;
24 | private BrowserContext browserContext;
25 |
26 | @BeforeAll
27 | static void setUpClass() {
28 | playwright = Playwright.create();
29 | BrowserType browserType = playwright.chromium();
30 | BrowserType.LaunchOptions launchOptions = new BrowserType.LaunchOptions();
31 | // set to false if you want to see the browser during development
32 | launchOptions.headless = true;
33 | browser = browserType.launch(launchOptions);
34 | }
35 |
36 | @AfterAll
37 | static void tearDownClass() {
38 | browser.close();
39 | playwright.close();
40 | }
41 |
42 | @BeforeEach
43 | void setUp() {
44 | browserContext = browser.newContext();
45 | page = browserContext.newPage();
46 | }
47 |
48 | @AfterEach
49 | void tearDown() {
50 | page.close();
51 | browserContext.close();
52 | }
53 | }
54 |
--------------------------------------------------------------------------------
/src/main/java/ch/martinelli/vj/ui/components/Notifier.java:
--------------------------------------------------------------------------------
1 | package ch.martinelli.vj.ui.components;
2 |
3 | import com.vaadin.flow.component.button.Button;
4 | import com.vaadin.flow.component.html.NativeLabel;
5 | import com.vaadin.flow.component.notification.Notification;
6 | import com.vaadin.flow.component.notification.NotificationVariant;
7 | import com.vaadin.flow.component.orderedlayout.HorizontalLayout;
8 | import com.vaadin.flow.theme.lumo.LumoUtility;
9 |
10 | public class Notifier extends Notification {
11 |
12 | public static final int DURATION = 3000;
13 |
14 | public static void info(String message) {
15 | showNotification(message);
16 | }
17 |
18 | public static void success(String message) {
19 | var notification = showNotification(message);
20 | notification.addThemeVariants(NotificationVariant.LUMO_SUCCESS);
21 | }
22 |
23 | public static void warn(String message) {
24 | var notification = showNotification(message);
25 | notification.addThemeVariants(NotificationVariant.LUMO_WARNING);
26 | }
27 |
28 | public static void error(String message) {
29 | var text = new NativeLabel(message);
30 | var close = new Button("OK");
31 |
32 | var content = new HorizontalLayout(text, close);
33 | content.addClassName(LumoUtility.AlignItems.CENTER);
34 |
35 | var notification = new Notification(content);
36 | notification.addThemeVariants(NotificationVariant.LUMO_ERROR);
37 | notification.setPosition(Position.TOP_END);
38 |
39 | close.addClickListener(buttonClickEvent -> notification.close());
40 | notification.open();
41 | close.focus();
42 | }
43 |
44 | private static Notification showNotification(String message) {
45 | return show(message, DURATION, Position.TOP_END);
46 | }
47 |
48 | }
49 |
--------------------------------------------------------------------------------
/src/main/java/ch/martinelli/vj/ui/views/login/LoginView.java:
--------------------------------------------------------------------------------
1 | package ch.martinelli.vj.ui.views.login;
2 |
3 | import ch.martinelli.vj.security.AuthenticatedUser;
4 | import com.vaadin.flow.component.login.LoginI18n;
5 | import com.vaadin.flow.component.login.LoginOverlay;
6 | import com.vaadin.flow.router.BeforeEnterEvent;
7 | import com.vaadin.flow.router.BeforeEnterObserver;
8 | import com.vaadin.flow.router.PageTitle;
9 | import com.vaadin.flow.router.Route;
10 | import com.vaadin.flow.router.internal.RouteUtil;
11 | import com.vaadin.flow.server.VaadinService;
12 | import com.vaadin.flow.server.auth.AnonymousAllowed;
13 |
14 | @AnonymousAllowed
15 | @PageTitle("Login")
16 | @Route(value = "login")
17 | public class LoginView extends LoginOverlay implements BeforeEnterObserver {
18 |
19 | private final transient AuthenticatedUser authenticatedUser;
20 |
21 | public LoginView(AuthenticatedUser authenticatedUser) {
22 | this.authenticatedUser = authenticatedUser;
23 | setAction(RouteUtil.getRoutePath(VaadinService.getCurrent().getContext(), getClass()));
24 |
25 | LoginI18n i18n = LoginI18n.createDefault();
26 | i18n.setHeader(new LoginI18n.Header());
27 | i18n.getHeader().setTitle("Vaadin jOOQ Template");
28 | i18n.getHeader().setDescription("Login using user/user or admin/admin");
29 | i18n.setAdditionalInformation(null);
30 | setI18n(i18n);
31 |
32 | setForgotPasswordButtonVisible(false);
33 | setOpened(true);
34 | }
35 |
36 | @Override
37 | public void beforeEnter(BeforeEnterEvent event) {
38 | if (authenticatedUser.get().isPresent()) {
39 | // Already logged in
40 | setOpened(false);
41 | event.forwardTo("");
42 | }
43 |
44 | setError(event.getLocation().getQueryParameters().getParameters().containsKey("error"));
45 | }
46 | }
47 |
--------------------------------------------------------------------------------
/src/main/java/ch/martinelli/vj/security/SecurityConfiguration.java:
--------------------------------------------------------------------------------
1 | package ch.martinelli.vj.security;
2 |
3 | import ch.martinelli.vj.ui.views.login.LoginView;
4 | import com.vaadin.flow.spring.security.VaadinWebSecurity;
5 | import org.springframework.beans.factory.annotation.Value;
6 | import org.springframework.context.annotation.Bean;
7 | import org.springframework.context.annotation.Configuration;
8 | import org.springframework.security.config.annotation.web.builders.HttpSecurity;
9 | import org.springframework.security.config.annotation.web.configuration.EnableWebSecurity;
10 | import org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder;
11 | import org.springframework.security.crypto.password.PasswordEncoder;
12 | import org.springframework.security.oauth2.jose.jws.JwsAlgorithms;
13 | import org.springframework.security.web.util.matcher.AntPathRequestMatcher;
14 |
15 | import javax.crypto.spec.SecretKeySpec;
16 | import java.util.Base64;
17 |
18 | @EnableWebSecurity
19 | @Configuration
20 | public class SecurityConfiguration extends VaadinWebSecurity {
21 |
22 | private final String authSecret;
23 |
24 | public SecurityConfiguration(@Value("${jwt.auth.secret}") String authSecret) {
25 | this.authSecret = authSecret;
26 | }
27 |
28 | @Bean
29 | public PasswordEncoder passwordEncoder() {
30 | return new BCryptPasswordEncoder();
31 | }
32 |
33 | @Override
34 | protected void configure(HttpSecurity http) throws Exception {
35 | http.authorizeHttpRequests(
36 | authorize -> authorize.requestMatchers(new AntPathRequestMatcher("/images/*.png")).permitAll());
37 |
38 | super.configure(http);
39 |
40 | setLoginView(http, LoginView.class);
41 |
42 | // https://vaadin.com/blog/jwt-authentication-with-vaadin-flow-for-better-developer-and-user-experience
43 | setStatelessAuthentication(http,
44 | new SecretKeySpec(Base64.getDecoder().decode(authSecret), JwsAlgorithms.HS256),
45 | "ch.martinelli.vj", 3600);
46 | }
47 |
48 | }
49 |
--------------------------------------------------------------------------------
/src/main/java/ch/martinelli/vj/ui/i18n/TranslationProvider.java:
--------------------------------------------------------------------------------
1 | package ch.martinelli.vj.ui.i18n;
2 |
3 | import com.vaadin.flow.i18n.I18NProvider;
4 | import org.slf4j.Logger;
5 | import org.slf4j.LoggerFactory;
6 | import org.springframework.stereotype.Component;
7 |
8 | import java.text.MessageFormat;
9 | import java.util.List;
10 | import java.util.Locale;
11 | import java.util.MissingResourceException;
12 | import java.util.ResourceBundle;
13 |
14 | /**
15 | * All the texts are written in English.
16 | * This I18NProvider only translates messages when the Locale is not English.
17 | */
18 | @Component
19 | public class TranslationProvider implements I18NProvider {
20 |
21 | private static final Logger LOGGER = LoggerFactory.getLogger(TranslationProvider.class);
22 |
23 | @Override
24 | public List getProvidedLocales() {
25 | return List.of(Locale.of("en"), Locale.of("de"));
26 | }
27 |
28 | @Override
29 | public String getTranslation(String key, Locale locale, Object... params) {
30 | if (key == null) {
31 | LOGGER.warn("Translation called with empty key!");
32 | return "";
33 | }
34 |
35 | if (locale.getLanguage().equals("en")) {
36 | // This is the default language. The key is in English, so we don't need to translate it
37 | if (params.length > 0) {
38 | return new MessageFormat(key, locale).format(params);
39 | } else {
40 | return key;
41 | }
42 | }
43 |
44 | var bundle = ResourceBundle.getBundle("i18n.messages", locale);
45 | if (bundle == null) {
46 | return key;
47 | }
48 |
49 | try {
50 | var value = bundle.getString(key);
51 | if (params.length > 0) {
52 | return new MessageFormat(value, locale).format(params);
53 | } else {
54 | return value;
55 | }
56 | } catch (MissingResourceException e) {
57 | LOGGER.warn("Missing translation for key {}", key);
58 | return "!" + locale.getLanguage() + ": " + key;
59 | }
60 | }
61 |
62 | }
63 |
--------------------------------------------------------------------------------
/src/main/java/ch/martinelli/vj/domain/user/UserService.java:
--------------------------------------------------------------------------------
1 | package ch.martinelli.vj.domain.user;
2 |
3 | import ch.martinelli.vj.db.tables.records.UserRecord;
4 | import ch.martinelli.vj.db.tables.records.UserRoleRecord;
5 | import org.jooq.DSLContext;
6 | import org.jooq.OrderField;
7 | import org.jooq.Record1;
8 | import org.springframework.stereotype.Service;
9 | import org.springframework.transaction.annotation.Transactional;
10 |
11 | import java.util.List;
12 | import java.util.Optional;
13 |
14 | import static ch.martinelli.vj.db.tables.User.USER;
15 | import static ch.martinelli.vj.db.tables.UserRole.USER_ROLE;
16 | import static org.jooq.Records.mapping;
17 | import static org.jooq.impl.DSL.multiset;
18 | import static org.jooq.impl.DSL.select;
19 |
20 |
21 | @Transactional(readOnly = true)
22 | @Service
23 | public class UserService {
24 |
25 | private final DSLContext ctx;
26 |
27 | public UserService(DSLContext ctx) {
28 | this.ctx = ctx;
29 | }
30 |
31 | public Optional findUserByUsername(String username) {
32 | return ctx.selectFrom(USER)
33 | .where(USER.USERNAME.eq(username))
34 | .fetchOptional();
35 | }
36 |
37 | public Optional findUserWithRolesByUsername(String username) {
38 | return ctx.select(USER,
39 | multiset(select(USER_ROLE.ROLE)
40 | .from(USER_ROLE)
41 | .where(USER_ROLE.USERNAME.eq(USER.USERNAME))
42 | ).convertFrom(r -> r.map(Record1::value1)))
43 | .from(USER)
44 | .where(USER.USERNAME.eq(username))
45 | .fetchOptional(mapping(UserWithRoles::new));
46 | }
47 |
48 | public List findRolesByUsername(String username) {
49 | return ctx.selectFrom(USER_ROLE)
50 | .where(USER_ROLE.USERNAME.eq(username))
51 | .fetch();
52 | }
53 |
54 | public List findAllUserWithRoles(int offset, int limit, List> orderFields) {
55 | return ctx.select(USER,
56 | multiset(select(USER_ROLE.ROLE)
57 | .from(USER_ROLE)
58 | .where(USER_ROLE.USERNAME.eq(USER.USERNAME))
59 | ).convertFrom(r -> r.map(Record1::value1)))
60 | .from(USER)
61 | .orderBy(orderFields)
62 | .offset(offset)
63 | .limit(limit)
64 | .fetch(mapping(UserWithRoles::new));
65 | }
66 |
67 | @Transactional
68 | public void save(UserWithRoles userWithRoles) {
69 | var user = userWithRoles.getUser();
70 | ctx.attach(user);
71 | user.store();
72 |
73 | // First delete all assigned roles
74 | ctx.deleteFrom(USER_ROLE)
75 | .where(USER_ROLE.USERNAME.eq(user.getUsername()))
76 | .execute();
77 |
78 | // Then add all roles
79 | for (var role : userWithRoles.getRoles()) {
80 | var userRole = new UserRoleRecord(user.getUsername(), role);
81 | ctx.attach(userRole);
82 | userRole.store();
83 | }
84 | }
85 |
86 | @Transactional
87 | public void deleteByUsername(String username) {
88 | ctx.deleteFrom(USER_ROLE)
89 | .where(USER_ROLE.USERNAME.eq(username))
90 | .execute();
91 | ctx.deleteFrom(USER)
92 | .where(USER.USERNAME.eq(username))
93 | .execute();
94 | }
95 | }
96 |
--------------------------------------------------------------------------------
/src/test/java/ch/martinelli/vj/ui/KaribuTest.java:
--------------------------------------------------------------------------------
1 | package ch.martinelli.vj.ui;
2 |
3 | import ch.martinelli.vj.TestVjConfiguration;
4 | import com.github.mvysny.fakeservlet.FakeRequest;
5 | import com.github.mvysny.kaributesting.v10.MockVaadin;
6 | import com.github.mvysny.kaributesting.v10.Routes;
7 | import com.github.mvysny.kaributesting.v10.spring.MockSpringServlet;
8 | import com.vaadin.flow.component.UI;
9 | import com.vaadin.flow.server.VaadinServletRequest;
10 | import kotlin.jvm.functions.Function0;
11 | import org.junit.jupiter.api.AfterEach;
12 | import org.junit.jupiter.api.BeforeAll;
13 | import org.junit.jupiter.api.BeforeEach;
14 | import org.springframework.beans.factory.annotation.Autowired;
15 | import org.springframework.boot.test.context.SpringBootTest;
16 | import org.springframework.context.ApplicationContext;
17 | import org.springframework.context.annotation.Import;
18 | import org.springframework.security.authentication.UsernamePasswordAuthenticationToken;
19 | import org.springframework.security.core.authority.SimpleGrantedAuthority;
20 | import org.springframework.security.core.context.SecurityContextHolder;
21 | import org.springframework.security.core.userdetails.User;
22 |
23 | import java.util.List;
24 | import java.util.stream.Collectors;
25 |
26 | @Import(TestVjConfiguration.class)
27 | @SpringBootTest
28 | public abstract class KaribuTest {
29 |
30 | private static Routes routes;
31 |
32 | @Autowired
33 | protected ApplicationContext ctx;
34 |
35 | @BeforeAll
36 | public static void discoverRoutes() {
37 | routes = new Routes().autoDiscoverViews(KaribuTest.class.getPackageName());
38 | }
39 |
40 | @BeforeEach
41 | public void setup() {
42 | final Function0 uiFactory = UI::new;
43 | var servlet = new MockSpringServlet(routes, ctx, uiFactory);
44 | MockVaadin.setup(uiFactory, servlet);
45 | }
46 |
47 | @AfterEach
48 | public void tearDown() {
49 | logout();
50 | MockVaadin.tearDown();
51 | }
52 |
53 | protected void login(String user, String pass, final List roles) {
54 | // taken from https://www.baeldung.com/manually-set-user-authentication-spring-security
55 | // also see https://github.com/mvysny/karibu-testing/issues/47 for more details.
56 | final List authorities =
57 | roles.stream().map(it -> new SimpleGrantedAuthority("ROLE_" + it)).collect(Collectors.toList());
58 |
59 | var userDetails = new User(user, pass, authorities);
60 | var authReq = new UsernamePasswordAuthenticationToken(userDetails, pass, authorities);
61 | var sc = SecurityContextHolder.getContext();
62 | sc.setAuthentication(authReq);
63 |
64 | // however, you also need to make sure that ViewAccessChecker works properly;
65 | // that requires a correct MockRequest.userPrincipal and MockRequest.isUserInRole()
66 | var request = (FakeRequest) VaadinServletRequest.getCurrent().getRequest();
67 | request.setUserPrincipalInt(authReq);
68 | request.setUserInRole((principal, role) -> roles.contains(role));
69 | }
70 |
71 | protected void logout() {
72 | try {
73 | SecurityContextHolder.getContext().setAuthentication(null);
74 | if (VaadinServletRequest.getCurrent() != null) {
75 | var request = (FakeRequest) VaadinServletRequest.getCurrent().getRequest();
76 | request.setUserPrincipalInt(null);
77 | request.setUserInRole((principal, role) -> false);
78 | }
79 | } catch (IllegalStateException ignore) {
80 | }
81 | }
82 | }
83 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | # Vaadin/jOOQ Template
2 |
3 | **This repo has been moved to [https://github.com/martinellich/vaadin-jooq-template](https://github.com/martinellich/vaadin-jooq-template)**
4 |
5 | ## Introduction
6 |
7 | This is a template project that shows how to integrate [Vaadin](https://vaadin.com) and [jOOQ](https://jooq.org)
8 | and how to test it with [Karibu Testing](https://github.com/mvysny/karibu-testing) and [Playwright](https://playwright.dev).
9 |
10 | It uses [Testcontainers](https://testcontainers.com) for generating the jOOQ classes and integration testing and
11 | Flyway for the database migrations.
12 |
13 | This project can be used as a starting point to create your own Vaadin application with jOOQ.
14 | It contains all the necessary configuration and some examples to get you started.
15 |
16 | ## Running the Application
17 |
18 | Before running the application, the jOOQ metamodel has to be generated using the Maven plugin:
19 |
20 | ./mvnw compile
21 |
22 | Then you can simply run the application with a database started by Testcontainers from your IDE using the `TestVjApplication`.
23 |
24 | **Important:**
25 | This class uses the [Spring Boot Testcontainers support](https://spring.io/blog/2023/06/23/improved-testcontainers-support-in-spring-boot-3-1/), introduced with Spring Boot 3.1.
26 | Thus, [Docker](https://www.docker.com) or [Testcontainers Cloud](https://testcontainers.com/cloud/) must be running on your local computer.
27 |
28 | ## Testing the Application
29 |
30 | There are two base classes:
31 |
32 | - `KaribuTest` can be used for fast [browser-less testing](https://mvysny.github.io/browserless-web-testing/), aka UI unit test. Karibu sets up a Vaadin mock environment.
33 | - `PlaywrightIT` configures Playwright for E2E tests. This class uses SpringBootTest at a random port.
34 |
35 | The Playwright test uses [Mopo](https://github.com/viritin/mopo),
36 | which simplifies the testing of Vaadin applications with Playwright.
37 |
38 | ## Deploying to Production
39 |
40 | To create a production build, call `mvnw clean package -Pproduction` (Windows),
41 | or `./mvnw clean package -Pproduction` (Mac & Linux).
42 |
43 | This will build a JAR file with all the dependencies and front-end resources,
44 | ready to be deployed. You can find the file in the `target` folder after the build completes.
45 |
46 | Once the JAR file is built, you can run it using
47 | `java -jar target/vaadin-jooq-template-.jar`
48 |
49 | ## Project structure
50 |
51 | - `layout/MainLayout.java` in `src/main/java` contains the navigation setup using [App Layout](https://vaadin.com/docs/components/app-layout).
52 | - `views` package in `src/main/java` contains the server-side Java views of your application.
53 | - `themes` folder in `frontend/` contains the custom CSS styles.
54 |
55 | ## Security Configuration
56 |
57 | The example uses JWT authentication for a better developer experience (for example, you don't have to re-login during development).
58 | Read more in the blog of [Matti Tahvonen](https://vaadin.com/blog/jwt-authentication-with-vaadin-flow-for-better-developer-and-user-experience).
59 |
60 | ## Useful links
61 |
62 | ### Vaadin
63 |
64 | - Check out the [Vaadin Developer Portal](https://vaadin.com/developers).
65 | - Read the documentation at [vaadin.com/docs](https://vaadin.com/docs).
66 | - Create new projects at [start.vaadin.com](https://start.vaadin.com/).
67 | - Find a collection of solutions to common use cases at [cookbook.vaadin.com](https://cookbook.vaadin.com/).
68 | - Find add-ons at [vaadin.com/directory](https://vaadin.com/directory).
69 |
70 | ### jOOQ
71 |
72 | - Read the documentation at [jooq.org/learn](https://www.jooq.org/learn/).
73 | - Browse the [Blog](https://blog.jooq.org).
74 |
75 | ### Spring Boot
76 |
77 | - Explore the [Spring Boot project page](https://spring.io/projects/spring-boot/).
78 |
79 | ### Testcontainers
80 |
81 | - Go to the [Testcontainers website](https://testcontainers.com).
82 |
83 | ### Karibu Testing
84 |
85 | - Check out the [GitHub project](https://github.com/mvysny/karibu-testing).
86 |
87 | ### Playwright
88 |
89 | - Read the [documentation](https://playwright.dev).
90 |
--------------------------------------------------------------------------------
/src/main/java/ch/martinelli/vj/ui/layout/MainLayout.java:
--------------------------------------------------------------------------------
1 | package ch.martinelli.vj.ui.layout;
2 |
3 | import ch.martinelli.vj.security.AuthenticatedUser;
4 | import ch.martinelli.vj.ui.views.helloworld.HelloWorldView;
5 | import ch.martinelli.vj.ui.views.person.PersonView;
6 | import ch.martinelli.vj.ui.views.user.UserView;
7 | import com.vaadin.flow.component.applayout.AppLayout;
8 | import com.vaadin.flow.component.applayout.DrawerToggle;
9 | import com.vaadin.flow.component.avatar.Avatar;
10 | import com.vaadin.flow.component.html.*;
11 | import com.vaadin.flow.component.icon.VaadinIcon;
12 | import com.vaadin.flow.component.menubar.MenuBar;
13 | import com.vaadin.flow.component.orderedlayout.Scroller;
14 | import com.vaadin.flow.component.sidenav.SideNav;
15 | import com.vaadin.flow.component.sidenav.SideNavItem;
16 | import com.vaadin.flow.router.HasDynamicTitle;
17 | import com.vaadin.flow.router.PageTitle;
18 | import com.vaadin.flow.server.StreamResource;
19 | import com.vaadin.flow.server.auth.AccessAnnotationChecker;
20 | import com.vaadin.flow.theme.lumo.LumoIcon;
21 | import com.vaadin.flow.theme.lumo.LumoUtility;
22 |
23 | import java.io.ByteArrayInputStream;
24 |
25 | public class MainLayout extends AppLayout {
26 |
27 | private final transient AuthenticatedUser authenticatedUser;
28 | private final AccessAnnotationChecker accessChecker;
29 |
30 | private H2 viewTitle;
31 |
32 | public MainLayout(AuthenticatedUser authenticatedUser, AccessAnnotationChecker accessChecker) {
33 | this.authenticatedUser = authenticatedUser;
34 | this.accessChecker = accessChecker;
35 |
36 | setPrimarySection(Section.DRAWER);
37 | addDrawerContent();
38 | addHeaderContent();
39 | }
40 |
41 | private void addHeaderContent() {
42 | var toggle = new DrawerToggle();
43 | toggle.setAriaLabel("Menu toggle");
44 |
45 | viewTitle = new H2();
46 | viewTitle.addClassNames(LumoUtility.FontSize.LARGE, LumoUtility.Margin.NONE);
47 |
48 | addToNavbar(true, toggle, viewTitle);
49 | }
50 |
51 | private void addDrawerContent() {
52 | var appName = new H1("Vaadin jOOQ Template");
53 | appName.addClassNames(LumoUtility.FontSize.LARGE, LumoUtility.Margin.NONE);
54 |
55 | var header = new Header(appName);
56 |
57 | var scroller = new Scroller(createNavigation());
58 |
59 | addToDrawer(header, scroller, createFooter());
60 | }
61 |
62 | private SideNav createNavigation() {
63 | var nav = new SideNav();
64 |
65 | if (accessChecker.hasAccess(HelloWorldView.class)) {
66 | nav.addItem(new SideNavItem(getTranslation("Hello World"), HelloWorldView.class, VaadinIcon.GLOBE.create()));
67 |
68 | }
69 | if (accessChecker.hasAccess(PersonView.class)) {
70 | nav.addItem(new SideNavItem(getTranslation("Persons"), PersonView.class, VaadinIcon.ARCHIVES.create()));
71 | }
72 | if (accessChecker.hasAccess(UserView.class)) {
73 | nav.addItem(new SideNavItem(getTranslation("Users"), UserView.class, VaadinIcon.USER.create()));
74 | }
75 |
76 | return nav;
77 | }
78 |
79 | private Footer createFooter() {
80 | var layout = new Footer();
81 |
82 | var optionalUserRecord = authenticatedUser.get();
83 | if (optionalUserRecord.isPresent()) {
84 | var user = optionalUserRecord.get();
85 |
86 | var avatar = new Avatar("%s %s".formatted(user.getFirstName(), user.getLastName()));
87 | var resource = new StreamResource("profile-pic", () -> new ByteArrayInputStream(user.getPicture()));
88 | avatar.setImageResource(resource);
89 | avatar.setThemeName("xsmall");
90 | avatar.getElement().setAttribute("tabindex", "-1");
91 |
92 | var userMenu = new MenuBar();
93 | userMenu.setThemeName("tertiary-inline contrast");
94 |
95 | var userName = userMenu.addItem("");
96 |
97 | var div = new Div();
98 | div.add(avatar);
99 | div.add("%s %s".formatted(user.getFirstName(), user.getLastName()));
100 | div.add(LumoIcon.DROPDOWN.create());
101 | div.addClassNames(LumoUtility.Display.FLEX, LumoUtility.AlignItems.CENTER, LumoUtility.Gap.SMALL);
102 | userName.add(div);
103 | userName.getSubMenu().addItem(getTranslation("Sign out"), e -> authenticatedUser.logout());
104 |
105 | layout.add(userMenu);
106 | } else {
107 | var loginLink = new Anchor("login", getTranslation("Sign in"));
108 | layout.add(loginLink);
109 | }
110 |
111 | return layout;
112 | }
113 |
114 | @Override
115 | protected void afterNavigation() {
116 | super.afterNavigation();
117 | viewTitle.setText(getCurrentPageTitle());
118 | }
119 |
120 | private String getCurrentPageTitle() {
121 | if (getContent() instanceof HasDynamicTitle hasDynamicTitle) {
122 | return hasDynamicTitle.getPageTitle() == null ? "" : hasDynamicTitle.getPageTitle();
123 | } else {
124 | var title = getContent().getClass().getAnnotation(PageTitle.class);
125 | return title == null ? "" : title.value();
126 | }
127 | }
128 | }
129 |
--------------------------------------------------------------------------------
/.mvn/wrapper/MavenWrapperDownloader.java:
--------------------------------------------------------------------------------
1 | /*
2 | * Copyright 2007-present the original author or authors.
3 | *
4 | * Licensed under the Apache License, Version 2.0 (the "License");
5 | * you may not use this file except in compliance with the License.
6 | * You may obtain a copy of the License at
7 | *
8 | * http://www.apache.org/licenses/LICENSE-2.0
9 | *
10 | * Unless required by applicable law or agreed to in writing, software
11 | * distributed under the License is distributed on an "AS IS" BASIS,
12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 | * See the License for the specific language governing permissions and
14 | * limitations under the License.
15 | */
16 | import java.io.*;
17 | import java.net.*;
18 | import java.nio.channels.*;
19 | import java.util.Properties;
20 |
21 | public class MavenWrapperDownloader {
22 |
23 | private static final String WRAPPER_VERSION = "0.5.6";
24 | /**
25 | * Default URL to download the maven-wrapper.jar from, if no 'downloadUrl' is
26 | * provided.
27 | */
28 | private static final String DEFAULT_DOWNLOAD_URL = "https://repo.maven.apache.org/maven2/io/takari/maven-wrapper/"
29 | + WRAPPER_VERSION + "/maven-wrapper-" + WRAPPER_VERSION + ".jar";
30 |
31 | /**
32 | * Path to the maven-wrapper.properties file, which might contain a downloadUrl
33 | * property to use instead of the default one.
34 | */
35 | private static final String MAVEN_WRAPPER_PROPERTIES_PATH = ".mvn/wrapper/maven-wrapper.properties";
36 |
37 | /**
38 | * Path where the maven-wrapper.jar will be saved to.
39 | */
40 | private static final String MAVEN_WRAPPER_JAR_PATH = ".mvn/wrapper/maven-wrapper.jar";
41 |
42 | /**
43 | * Name of the property which should be used to override the default download
44 | * url for the wrapper.
45 | */
46 | private static final String PROPERTY_NAME_WRAPPER_URL = "wrapperUrl";
47 |
48 | public static void main(String args[]) {
49 | System.out.println("- Downloader started");
50 | File baseDirectory = new File(args[0]);
51 | System.out.println("- Using base directory: " + baseDirectory.getAbsolutePath());
52 |
53 | // If the maven-wrapper.properties exists, read it and check if it contains a
54 | // custom
55 | // wrapperUrl parameter.
56 | File mavenWrapperPropertyFile = new File(baseDirectory, MAVEN_WRAPPER_PROPERTIES_PATH);
57 | String url = DEFAULT_DOWNLOAD_URL;
58 | if (mavenWrapperPropertyFile.exists()) {
59 | FileInputStream mavenWrapperPropertyFileInputStream = null;
60 | try {
61 | mavenWrapperPropertyFileInputStream = new FileInputStream(mavenWrapperPropertyFile);
62 | Properties mavenWrapperProperties = new Properties();
63 | mavenWrapperProperties.load(mavenWrapperPropertyFileInputStream);
64 | url = mavenWrapperProperties.getProperty(PROPERTY_NAME_WRAPPER_URL, url);
65 | } catch (IOException e) {
66 | System.out.println("- ERROR loading '" + MAVEN_WRAPPER_PROPERTIES_PATH + "'");
67 | } finally {
68 | try {
69 | if (mavenWrapperPropertyFileInputStream != null) {
70 | mavenWrapperPropertyFileInputStream.close();
71 | }
72 | } catch (IOException e) {
73 | // Ignore ...
74 | }
75 | }
76 | }
77 | System.out.println("- Downloading from: " + url);
78 |
79 | File outputFile = new File(baseDirectory.getAbsolutePath(), MAVEN_WRAPPER_JAR_PATH);
80 | if (!outputFile.getParentFile().exists()) {
81 | if (!outputFile.getParentFile().mkdirs()) {
82 | System.out.println(
83 | "- ERROR creating output directory '" + outputFile.getParentFile().getAbsolutePath() + "'");
84 | }
85 | }
86 | System.out.println("- Downloading to: " + outputFile.getAbsolutePath());
87 | try {
88 | downloadFileFromURL(url, outputFile);
89 | System.out.println("Done");
90 | System.exit(0);
91 | } catch (Throwable e) {
92 | System.out.println("- Error downloading");
93 | e.printStackTrace();
94 | System.exit(1);
95 | }
96 | }
97 |
98 | private static void downloadFileFromURL(String urlString, File destination) throws Exception {
99 | if (System.getenv("MVNW_USERNAME") != null && System.getenv("MVNW_PASSWORD") != null) {
100 | String username = System.getenv("MVNW_USERNAME");
101 | char[] password = System.getenv("MVNW_PASSWORD").toCharArray();
102 | Authenticator.setDefault(new Authenticator() {
103 | @Override
104 | protected PasswordAuthentication getPasswordAuthentication() {
105 | return new PasswordAuthentication(username, password);
106 | }
107 | });
108 | }
109 | URL website = new URL(urlString);
110 | ReadableByteChannel rbc;
111 | rbc = Channels.newChannel(website.openStream());
112 | FileOutputStream fos = new FileOutputStream(destination);
113 | fos.getChannel().transferFrom(rbc, 0, Long.MAX_VALUE);
114 | fos.close();
115 | rbc.close();
116 | }
117 |
118 | }
119 |
--------------------------------------------------------------------------------
/mvnw.cmd:
--------------------------------------------------------------------------------
1 | @REM ----------------------------------------------------------------------------
2 | @REM Licensed to the Apache Software Foundation (ASF) under one
3 | @REM or more contributor license agreements. See the NOTICE file
4 | @REM distributed with this work for additional information
5 | @REM regarding copyright ownership. The ASF licenses this file
6 | @REM to you under the Apache License, Version 2.0 (the
7 | @REM "License"); you may not use this file except in compliance
8 | @REM with the License. You may obtain a copy of the License at
9 | @REM
10 | @REM http://www.apache.org/licenses/LICENSE-2.0
11 | @REM
12 | @REM Unless required by applicable law or agreed to in writing,
13 | @REM software distributed under the License is distributed on an
14 | @REM "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
15 | @REM KIND, either express or implied. See the License for the
16 | @REM specific language governing permissions and limitations
17 | @REM under the License.
18 | @REM ----------------------------------------------------------------------------
19 |
20 | @REM ----------------------------------------------------------------------------
21 | @REM Maven Start Up Batch script
22 | @REM
23 | @REM Required ENV vars:
24 | @REM JAVA_HOME - location of a JDK home dir
25 | @REM
26 | @REM Optional ENV vars
27 | @REM M2_HOME - location of maven2's installed home dir
28 | @REM MAVEN_BATCH_ECHO - set to 'on' to enable the echoing of the batch commands
29 | @REM MAVEN_BATCH_PAUSE - set to 'on' to wait for a keystroke before ending
30 | @REM MAVEN_OPTS - parameters passed to the Java VM when running Maven
31 | @REM e.g. to debug Maven itself, use
32 | @REM set MAVEN_OPTS=-Xdebug -Xrunjdwp:transport=dt_socket,server=y,suspend=y,address=8000
33 | @REM MAVEN_SKIP_RC - flag to disable loading of mavenrc files
34 | @REM ----------------------------------------------------------------------------
35 |
36 | @REM Begin all REM lines with '@' in case MAVEN_BATCH_ECHO is 'on'
37 | @echo off
38 | @REM set title of command window
39 | title %0
40 | @REM enable echoing by setting MAVEN_BATCH_ECHO to 'on'
41 | @if "%MAVEN_BATCH_ECHO%" == "on" echo %MAVEN_BATCH_ECHO%
42 |
43 | @REM set %HOME% to equivalent of $HOME
44 | if "%HOME%" == "" (set "HOME=%HOMEDRIVE%%HOMEPATH%")
45 |
46 | @REM Execute a user defined script before this one
47 | if not "%MAVEN_SKIP_RC%" == "" goto skipRcPre
48 | @REM check for pre script, once with legacy .bat ending and once with .cmd ending
49 | if exist "%USERPROFILE%\mavenrc_pre.bat" call "%USERPROFILE%\mavenrc_pre.bat" %*
50 | if exist "%USERPROFILE%\mavenrc_pre.cmd" call "%USERPROFILE%\mavenrc_pre.cmd" %*
51 | :skipRcPre
52 |
53 | @setlocal
54 |
55 | set ERROR_CODE=0
56 |
57 | @REM To isolate internal variables from possible post scripts, we use another setlocal
58 | @setlocal
59 |
60 | @REM ==== START VALIDATION ====
61 | if not "%JAVA_HOME%" == "" goto OkJHome
62 |
63 | echo.
64 | echo Error: JAVA_HOME not found in your environment. >&2
65 | echo Please set the JAVA_HOME variable in your environment to match the >&2
66 | echo location of your Java installation. >&2
67 | echo.
68 | goto error
69 |
70 | :OkJHome
71 | if exist "%JAVA_HOME%\bin\java.exe" goto init
72 |
73 | echo.
74 | echo Error: JAVA_HOME is set to an invalid directory. >&2
75 | echo JAVA_HOME = "%JAVA_HOME%" >&2
76 | echo Please set the JAVA_HOME variable in your environment to match the >&2
77 | echo location of your Java installation. >&2
78 | echo.
79 | goto error
80 |
81 | @REM ==== END VALIDATION ====
82 |
83 | :init
84 |
85 | @REM Find the project base dir, i.e. the directory that contains the folder ".mvn".
86 | @REM Fallback to current working directory if not found.
87 |
88 | set MAVEN_PROJECTBASEDIR=%MAVEN_BASEDIR%
89 | IF NOT "%MAVEN_PROJECTBASEDIR%"=="" goto endDetectBaseDir
90 |
91 | set EXEC_DIR=%CD%
92 | set WDIR=%EXEC_DIR%
93 | :findBaseDir
94 | IF EXIST "%WDIR%"\.mvn goto baseDirFound
95 | cd ..
96 | IF "%WDIR%"=="%CD%" goto baseDirNotFound
97 | set WDIR=%CD%
98 | goto findBaseDir
99 |
100 | :baseDirFound
101 | set MAVEN_PROJECTBASEDIR=%WDIR%
102 | cd "%EXEC_DIR%"
103 | goto endDetectBaseDir
104 |
105 | :baseDirNotFound
106 | set MAVEN_PROJECTBASEDIR=%EXEC_DIR%
107 | cd "%EXEC_DIR%"
108 |
109 | :endDetectBaseDir
110 |
111 | IF NOT EXIST "%MAVEN_PROJECTBASEDIR%\.mvn\jvm.config" goto endReadAdditionalConfig
112 |
113 | @setlocal EnableExtensions EnableDelayedExpansion
114 | for /F "usebackq delims=" %%a in ("%MAVEN_PROJECTBASEDIR%\.mvn\jvm.config") do set JVM_CONFIG_MAVEN_PROPS=!JVM_CONFIG_MAVEN_PROPS! %%a
115 | @endlocal & set JVM_CONFIG_MAVEN_PROPS=%JVM_CONFIG_MAVEN_PROPS%
116 |
117 | :endReadAdditionalConfig
118 |
119 | SET MAVEN_JAVA_EXE="%JAVA_HOME%\bin\java.exe"
120 | set WRAPPER_JAR="%MAVEN_PROJECTBASEDIR%\.mvn\wrapper\maven-wrapper.jar"
121 | set WRAPPER_LAUNCHER=org.apache.maven.wrapper.MavenWrapperMain
122 |
123 | set DOWNLOAD_URL="https://repo.maven.apache.org/maven2/org/apache/maven/wrapper/maven-wrapper/3.1.0/maven-wrapper-3.1.0.jar"
124 |
125 | FOR /F "usebackq tokens=1,2 delims==" %%A IN ("%MAVEN_PROJECTBASEDIR%\.mvn\wrapper\maven-wrapper.properties") DO (
126 | IF "%%A"=="wrapperUrl" SET DOWNLOAD_URL=%%B
127 | )
128 |
129 | @REM Extension to allow automatically downloading the maven-wrapper.jar from Maven-central
130 | @REM This allows using the maven wrapper in projects that prohibit checking in binary data.
131 | if exist %WRAPPER_JAR% (
132 | if "%MVNW_VERBOSE%" == "true" (
133 | echo Found %WRAPPER_JAR%
134 | )
135 | ) else (
136 | if not "%MVNW_REPOURL%" == "" (
137 | SET DOWNLOAD_URL="%MVNW_REPOURL%/org/apache/maven/wrapper/maven-wrapper/3.1.0/maven-wrapper-3.1.0.jar"
138 | )
139 | if "%MVNW_VERBOSE%" == "true" (
140 | echo Couldn't find %WRAPPER_JAR%, downloading it ...
141 | echo Downloading from: %DOWNLOAD_URL%
142 | )
143 |
144 | powershell -Command "&{"^
145 | "$webclient = new-object System.Net.WebClient;"^
146 | "if (-not ([string]::IsNullOrEmpty('%MVNW_USERNAME%') -and [string]::IsNullOrEmpty('%MVNW_PASSWORD%'))) {"^
147 | "$webclient.Credentials = new-object System.Net.NetworkCredential('%MVNW_USERNAME%', '%MVNW_PASSWORD%');"^
148 | "}"^
149 | "[Net.ServicePointManager]::SecurityProtocol = [Net.SecurityProtocolType]::Tls12; $webclient.DownloadFile('%DOWNLOAD_URL%', '%WRAPPER_JAR%')"^
150 | "}"
151 | if "%MVNW_VERBOSE%" == "true" (
152 | echo Finished downloading %WRAPPER_JAR%
153 | )
154 | )
155 | @REM End of extension
156 |
157 | @REM Provide a "standardized" way to retrieve the CLI args that will
158 | @REM work with both Windows and non-Windows executions.
159 | set MAVEN_CMD_LINE_ARGS=%*
160 |
161 | %MAVEN_JAVA_EXE% ^
162 | %JVM_CONFIG_MAVEN_PROPS% ^
163 | %MAVEN_OPTS% ^
164 | %MAVEN_DEBUG_OPTS% ^
165 | -classpath %WRAPPER_JAR% ^
166 | "-Dmaven.multiModuleProjectDirectory=%MAVEN_PROJECTBASEDIR%" ^
167 | %WRAPPER_LAUNCHER% %MAVEN_CONFIG% %*
168 | if ERRORLEVEL 1 goto error
169 | goto end
170 |
171 | :error
172 | set ERROR_CODE=1
173 |
174 | :end
175 | @endlocal & set ERROR_CODE=%ERROR_CODE%
176 |
177 | if not "%MAVEN_SKIP_RC%"=="" goto skipRcPost
178 | @REM check for post script, once with legacy .bat ending and once with .cmd ending
179 | if exist "%USERPROFILE%\mavenrc_post.bat" call "%USERPROFILE%\mavenrc_post.bat"
180 | if exist "%USERPROFILE%\mavenrc_post.cmd" call "%USERPROFILE%\mavenrc_post.cmd"
181 | :skipRcPost
182 |
183 | @REM pause the script if MAVEN_BATCH_PAUSE is set to 'on'
184 | if "%MAVEN_BATCH_PAUSE%"=="on" pause
185 |
186 | if "%MAVEN_TERMINATE_CMD%"=="on" exit %ERROR_CODE%
187 |
188 | cmd /C exit /B %ERROR_CODE%
189 |
--------------------------------------------------------------------------------
/src/main/java/ch/martinelli/vj/ui/views/user/UserView.java:
--------------------------------------------------------------------------------
1 | package ch.martinelli.vj.ui.views.user;
2 |
3 | import ch.martinelli.vj.domain.user.Role;
4 | import ch.martinelli.vj.domain.user.UserService;
5 | import ch.martinelli.vj.domain.user.UserWithRoles;
6 | import ch.martinelli.vj.ui.components.Notifier;
7 | import ch.martinelli.vj.ui.layout.MainLayout;
8 | import com.vaadin.flow.component.UI;
9 | import com.vaadin.flow.component.button.Button;
10 | import com.vaadin.flow.component.button.ButtonVariant;
11 | import com.vaadin.flow.component.combobox.MultiSelectComboBox;
12 | import com.vaadin.flow.component.confirmdialog.ConfirmDialog;
13 | import com.vaadin.flow.component.formlayout.FormLayout;
14 | import com.vaadin.flow.component.grid.ColumnTextAlign;
15 | import com.vaadin.flow.component.grid.Grid;
16 | import com.vaadin.flow.component.grid.GridSortOrder;
17 | import com.vaadin.flow.component.grid.GridVariant;
18 | import com.vaadin.flow.component.html.Div;
19 | import com.vaadin.flow.component.icon.VaadinIcon;
20 | import com.vaadin.flow.component.orderedlayout.HorizontalLayout;
21 | import com.vaadin.flow.component.orderedlayout.VerticalLayout;
22 | import com.vaadin.flow.component.splitlayout.SplitLayout;
23 | import com.vaadin.flow.component.textfield.PasswordField;
24 | import com.vaadin.flow.component.textfield.TextField;
25 | import com.vaadin.flow.data.binder.Binder;
26 | import com.vaadin.flow.data.binder.ValidationException;
27 | import com.vaadin.flow.router.*;
28 | import io.seventytwo.vaadinjooq.util.VaadinJooqUtil;
29 | import jakarta.annotation.security.RolesAllowed;
30 | import org.jooq.exception.DataAccessException;
31 | import org.springframework.security.crypto.password.PasswordEncoder;
32 |
33 | import java.util.Set;
34 |
35 | import static ch.martinelli.vj.db.tables.User.USER;
36 |
37 | @RolesAllowed(Role.ADMIN)
38 | @Route(value = "users", layout = MainLayout.class)
39 | public class UserView extends Div implements HasUrlParameter, HasDynamicTitle {
40 |
41 | private final transient UserService userService;
42 | private final transient PasswordEncoder passwordEncoder;
43 | private final Grid grid = new Grid<>();
44 | private final Button cancel = new Button(getTranslation("Cancel"));
45 | private final Button save = new Button(getTranslation("Save"));
46 | private final Binder binder = new Binder<>();
47 | private transient UserWithRoles user;
48 | private TextField usernameField;
49 |
50 | public UserView(UserService userService, PasswordEncoder passwordEncoder) {
51 | this.userService = userService;
52 | this.passwordEncoder = passwordEncoder;
53 |
54 | setSizeFull();
55 |
56 | // Create UI
57 | var splitLayout = new SplitLayout();
58 | splitLayout.setSizeFull();
59 | splitLayout.setSplitterPosition(80);
60 | add(splitLayout);
61 |
62 | // Configure Grid
63 | grid.setSizeFull();
64 | grid.addThemeVariants(GridVariant.LUMO_NO_BORDER);
65 |
66 | var usernameColumn = grid.addColumn(u -> u.getUser().getUsername())
67 | .setHeader(getTranslation("Username"))
68 | .setSortable(true).setSortProperty(USER.USERNAME.getName())
69 | .setAutoWidth(true);
70 | grid.addColumn(u -> u.getUser().getFirstName())
71 | .setHeader(getTranslation("First Name"))
72 | .setSortable(true).setSortProperty(USER.FIRST_NAME.getName())
73 | .setAutoWidth(true);
74 | grid.addColumn(u -> u.getUser().getLastName())
75 | .setHeader(getTranslation("Last Name"))
76 | .setSortable(true).setSortProperty(USER.LAST_NAME.getName())
77 | .setAutoWidth(true);
78 | grid.addColumn(u -> String.join(", ", u.getRoles()))
79 | .setHeader(getTranslation("Roles"))
80 | .setAutoWidth(true);
81 |
82 | var addIcon = VaadinIcon.PLUS.create();
83 | addIcon.addClickListener(e -> clearForm());
84 | grid.addComponentColumn(u -> {
85 | var deleteIcon = VaadinIcon.TRASH.create();
86 | deleteIcon.addClickListener(e ->
87 | new ConfirmDialog(
88 | getTranslation("Delete User?"),
89 | getTranslation("Do you really want to delete the user {0}?", u.getUser().getUsername()),
90 | getTranslation("Delete"),
91 | confirmEvent -> {
92 | userService.deleteByUsername(u.getUser().getUsername());
93 | clearForm();
94 | refreshGrid();
95 | },
96 | getTranslation("Cancel"),
97 | cancelEvent -> {
98 | })
99 | .open());
100 | return deleteIcon;
101 | })
102 | .setTextAlign(ColumnTextAlign.END)
103 | .setHeader(addIcon);
104 |
105 | grid.sort(GridSortOrder.asc(usernameColumn).build());
106 | grid.setItems(query ->
107 | userService.findAllUserWithRoles(query.getOffset(), query.getLimit(), VaadinJooqUtil.orderFields(USER, query)).stream()
108 | );
109 |
110 | // when a row is selected or deselected, populate form
111 | grid.asSingleSelect().addValueChangeListener(event -> {
112 | if (event.getValue() != null) {
113 | UI.getCurrent().navigate(UserView.class, event.getValue().getUser().getUsername());
114 | } else {
115 | clearForm();
116 | UI.getCurrent().navigate(UserView.class);
117 | }
118 | });
119 |
120 | var gridLayout = new VerticalLayout(grid);
121 | gridLayout.setSizeFull();
122 | splitLayout.addToPrimary(gridLayout);
123 |
124 | var form = createForm();
125 | var buttons = createButtonLayout();
126 |
127 | var formLayout = new VerticalLayout(form, buttons);
128 | formLayout.setSizeFull();
129 | splitLayout.addToSecondary(formLayout);
130 | }
131 |
132 | private void clearForm() {
133 | usernameField.setReadOnly(false);
134 | user = null;
135 | binder.readBean(null);
136 | }
137 |
138 | @Override
139 | public void setParameter(BeforeEvent beforeEvent, @OptionalParameter String username) {
140 | if (username != null) {
141 | userService.findUserWithRolesByUsername(username).ifPresent(userRecord -> user = userRecord);
142 | } else {
143 | user = null;
144 | }
145 | binder.readBean(user);
146 | grid.select(user);
147 |
148 | if (user != null && user.getUser().getUsername() != null) {
149 | usernameField.setReadOnly(true);
150 | }
151 | }
152 |
153 | private FormLayout createForm() {
154 | var formLayout = new FormLayout();
155 |
156 | usernameField = new TextField(getTranslation("Username"));
157 | binder.forField(usernameField)
158 | .asRequired()
159 | .bind(u -> u.getUser().getUsername(), (u, s) -> u.getUser().setUsername(s));
160 |
161 | var firstNameField = new TextField(getTranslation("First Name"));
162 | binder.forField(firstNameField)
163 | .asRequired()
164 | .bind(u -> u.getUser().getFirstName(), (u, s) -> u.getUser().setFirstName(s));
165 |
166 | var lastNameField = new TextField(getTranslation("Last Name"));
167 | binder.forField(lastNameField)
168 | .asRequired()
169 | .bind(u -> u.getUser().getLastName(), (u, s) -> u.getUser().setLastName(s));
170 |
171 | var passwordField = new PasswordField(getTranslation("Password"));
172 | binder.forField(passwordField)
173 | .asRequired()
174 | .bind(u -> "", (u, s) -> u.getUser().setHashedPassword(passwordEncoder.encode(s)));
175 |
176 | var roleMultiSelect = new MultiSelectComboBox(getTranslation("Roles"));
177 | binder.forField(roleMultiSelect)
178 | .bind(UserWithRoles::getRoles, UserWithRoles::setRoles);
179 |
180 | roleMultiSelect.setItems(Set.of(Role.ADMIN, Role.USER));
181 |
182 | formLayout.add(usernameField, firstNameField, lastNameField, passwordField, roleMultiSelect);
183 |
184 | return formLayout;
185 | }
186 |
187 | private HorizontalLayout createButtonLayout() {
188 | var buttonLayout = new HorizontalLayout();
189 |
190 | cancel.addClickListener(e -> {
191 | clearForm();
192 | refreshGrid();
193 | });
194 |
195 | save.addClickListener(e -> {
196 | if (binder.validate().isOk()) {
197 | try {
198 | if (user == null) {
199 | user = new UserWithRoles();
200 | }
201 |
202 | binder.writeChangedBindingsToBean(user);
203 |
204 | try {
205 | userService.save(user);
206 | Notifier.success(getTranslation("User saved"));
207 | } catch (DataAccessException ex) {
208 | Notifier.error(getTranslation("User could not be saved!"));
209 | }
210 | } catch (ValidationException ex) {
211 | Notifier.error(getTranslation("There have been validation errors!"));
212 | ex.getValidationErrors().forEach(validationResult ->
213 | Notifier.error(validationResult.getErrorMessage()));
214 | }
215 |
216 | clearForm();
217 | refreshGrid();
218 |
219 | UI.getCurrent().navigate(UserView.class);
220 | }
221 | });
222 |
223 | cancel.addThemeVariants(ButtonVariant.LUMO_TERTIARY);
224 | save.addThemeVariants(ButtonVariant.LUMO_PRIMARY);
225 |
226 | buttonLayout.add(save, cancel);
227 |
228 | return buttonLayout;
229 | }
230 |
231 | private void refreshGrid() {
232 | grid.select(null);
233 | grid.getDataProvider().refreshAll();
234 | }
235 |
236 | @Override
237 | public String getPageTitle() {
238 | return getTranslation("Users");
239 | }
240 | }
241 |
--------------------------------------------------------------------------------
/src/main/java/ch/martinelli/vj/ui/views/person/PersonView.java:
--------------------------------------------------------------------------------
1 | package ch.martinelli.vj.ui.views.person;
2 |
3 | import ch.martinelli.vj.db.tables.records.PersonRecord;
4 | import ch.martinelli.vj.domain.person.PersonService;
5 | import ch.martinelli.vj.domain.user.Role;
6 | import ch.martinelli.vj.ui.components.Notifier;
7 | import ch.martinelli.vj.ui.layout.MainLayout;
8 | import com.vaadin.flow.component.UI;
9 | import com.vaadin.flow.component.button.Button;
10 | import com.vaadin.flow.component.button.ButtonVariant;
11 | import com.vaadin.flow.component.checkbox.Checkbox;
12 | import com.vaadin.flow.component.confirmdialog.ConfirmDialog;
13 | import com.vaadin.flow.component.datepicker.DatePicker;
14 | import com.vaadin.flow.component.formlayout.FormLayout;
15 | import com.vaadin.flow.component.grid.ColumnTextAlign;
16 | import com.vaadin.flow.component.grid.Grid;
17 | import com.vaadin.flow.component.grid.GridSortOrder;
18 | import com.vaadin.flow.component.grid.GridVariant;
19 | import com.vaadin.flow.component.html.Div;
20 | import com.vaadin.flow.component.icon.VaadinIcon;
21 | import com.vaadin.flow.component.orderedlayout.HorizontalLayout;
22 | import com.vaadin.flow.component.orderedlayout.VerticalLayout;
23 | import com.vaadin.flow.component.splitlayout.SplitLayout;
24 | import com.vaadin.flow.component.textfield.EmailField;
25 | import com.vaadin.flow.component.textfield.TextField;
26 | import com.vaadin.flow.data.binder.Binder;
27 | import com.vaadin.flow.data.binder.ValidationException;
28 | import com.vaadin.flow.router.*;
29 | import io.seventytwo.vaadinjooq.util.VaadinJooqUtil;
30 | import jakarta.annotation.security.RolesAllowed;
31 | import org.jooq.exception.DataAccessException;
32 |
33 | import static ch.martinelli.vj.db.tables.Person.PERSON;
34 |
35 | @RolesAllowed({Role.USER, Role.ADMIN})
36 | @Route(value = "persons", layout = MainLayout.class)
37 | public class PersonView extends Div implements HasUrlParameter, HasDynamicTitle {
38 |
39 | private final transient PersonService personService;
40 |
41 | private final Grid grid = new Grid<>();
42 |
43 | private final Button cancel = new Button(getTranslation("Cancel"));
44 | private final Button save = new Button(getTranslation("Save"));
45 |
46 | private final Binder binder = new Binder<>();
47 |
48 | private PersonRecord person;
49 |
50 | public PersonView(PersonService personService) {
51 | this.personService = personService;
52 |
53 | setSizeFull();
54 |
55 | // Create UI
56 | var splitLayout = new SplitLayout();
57 | splitLayout.setSizeFull();
58 | splitLayout.setSplitterPosition(80);
59 | add(splitLayout);
60 |
61 | // Configure Grid
62 | grid.setSizeFull();
63 | grid.addThemeVariants(GridVariant.LUMO_NO_BORDER);
64 |
65 | var firstNameColumn = grid.addColumn(PersonRecord::getFirstName)
66 | .setHeader(getTranslation("First Name"))
67 | .setSortable(true).setSortProperty(PERSON.FIRST_NAME.getName())
68 | .setAutoWidth(true);
69 | grid.addColumn(PersonRecord::getLastName)
70 | .setHeader(getTranslation("Last Name"))
71 | .setSortable(true).setSortProperty(PERSON.LAST_NAME.getName())
72 | .setAutoWidth(true);
73 | grid.addColumn(PersonRecord::getEmail)
74 | .setHeader(getTranslation("E-Mail"))
75 | .setSortable(true).setSortProperty(PERSON.EMAIL.getName())
76 | .setAutoWidth(true);
77 | grid.addComponentColumn(p -> {
78 | var importantCheckbox = new Checkbox();
79 | importantCheckbox.setReadOnly(true);
80 | importantCheckbox.setValue(p.getImportant());
81 | return importantCheckbox;
82 | })
83 | .setHeader(getTranslation("Important"))
84 | .setAutoWidth(true);
85 |
86 | var addIcon = VaadinIcon.PLUS.create();
87 | addIcon.addClickListener(e -> clearForm());
88 | grid.addComponentColumn(p -> {
89 | var deleteIcon = VaadinIcon.TRASH.create();
90 | deleteIcon.addClickListener(e ->
91 | new ConfirmDialog(
92 | getTranslation("Delete Person?"),
93 | getTranslation("Do you really want to delete the person {0} {1}?", p.getFirstName(), p.getLastName()),
94 | getTranslation("Delete"),
95 | confirmEvent -> {
96 | personService.deleteById(p.getId());
97 | clearForm();
98 | refreshGrid();
99 | },
100 | getTranslation("Cancel"),
101 | cancelEvent -> {
102 | })
103 | .open());
104 | return deleteIcon;
105 | })
106 | .setTextAlign(ColumnTextAlign.END)
107 | .setHeader(addIcon);
108 |
109 | grid.sort(GridSortOrder.asc(firstNameColumn).build());
110 | grid.setItems(query ->
111 | personService.findAll(query.getOffset(), query.getLimit(), VaadinJooqUtil.orderFields(PERSON, query)).stream()
112 | );
113 | // when a row is selected or deselected, populate form
114 | grid.asSingleSelect().addValueChangeListener(event -> {
115 | if (event.getValue() != null) {
116 | UI.getCurrent().navigate(PersonView.class, event.getValue().getId());
117 | } else {
118 | clearForm();
119 | UI.getCurrent().navigate(PersonView.class);
120 | }
121 | });
122 |
123 | var gridLayout = new VerticalLayout(grid);
124 | gridLayout.setSizeFull();
125 | splitLayout.addToPrimary(gridLayout);
126 |
127 | var form = createForm();
128 | var buttons = createButtonLayout();
129 |
130 | var formLayout = new VerticalLayout(form, buttons);
131 | formLayout.setSizeFull();
132 | splitLayout.addToSecondary(formLayout);
133 | }
134 |
135 | private void clearForm() {
136 | person = null;
137 | binder.readBean(null);
138 | }
139 |
140 | @Override
141 | public void setParameter(BeforeEvent beforeEvent, @OptionalParameter Long personId) {
142 | if (personId != null) {
143 | personService.findById(personId).ifPresent(personRecord -> person = personRecord);
144 | } else {
145 | person = null;
146 | }
147 | binder.readBean(person);
148 | grid.select(person);
149 | }
150 |
151 | private FormLayout createForm() {
152 | var formLayout = new FormLayout();
153 |
154 | var firstNameField = new TextField(getTranslation("First Name"));
155 | binder.forField(firstNameField)
156 | .asRequired()
157 | .bind(PersonRecord::getFirstName, PersonRecord::setFirstName);
158 |
159 | var lastNameField = new TextField(getTranslation("Last Name"));
160 | binder.forField(lastNameField)
161 | .asRequired()
162 | .bind(PersonRecord::getLastName, PersonRecord::setLastName);
163 |
164 | var emailField = new EmailField(getTranslation("Email"));
165 | binder.forField(emailField)
166 | .asRequired()
167 | .bind(PersonRecord::getEmail, PersonRecord::setEmail);
168 |
169 | var phoneField = new TextField(getTranslation("Phone"));
170 | binder.forField(phoneField)
171 | .asRequired()
172 | .bind(PersonRecord::getPhone, PersonRecord::setPhone);
173 |
174 | var dateOfBirthField = new DatePicker(getTranslation("Date of birth"));
175 | binder.forField(dateOfBirthField)
176 | .asRequired()
177 | .bind(PersonRecord::getDateOfBirth, PersonRecord::setDateOfBirth);
178 |
179 | var occupationField = new TextField(getTranslation("Occupation"));
180 | binder.forField(occupationField)
181 | .asRequired()
182 | .bind(PersonRecord::getOccupation, PersonRecord::setOccupation);
183 |
184 | var roleField = new TextField(getTranslation("Role"));
185 | binder.forField(roleField)
186 | .asRequired()
187 | .bind(PersonRecord::getRole, PersonRecord::setRole);
188 |
189 | var importantCheckbox = new Checkbox(getTranslation("Important"));
190 | binder.forField(importantCheckbox)
191 | .bind(PersonRecord::getImportant, PersonRecord::setImportant);
192 |
193 | formLayout.add(firstNameField, lastNameField, emailField, phoneField, dateOfBirthField, occupationField, roleField, importantCheckbox);
194 |
195 | return formLayout;
196 | }
197 |
198 | private HorizontalLayout createButtonLayout() {
199 | var buttonLayout = new HorizontalLayout();
200 |
201 | cancel.addClickListener(e -> {
202 | clearForm();
203 | refreshGrid();
204 | });
205 |
206 | save.addClickListener(e -> {
207 | if (binder.validate().isOk()) {
208 | try {
209 | if (person == null) {
210 | person = new PersonRecord();
211 | }
212 |
213 | binder.writeChangedBindingsToBean(person);
214 |
215 | try {
216 | personService.save(person);
217 | Notifier.success(getTranslation("Person saved"));
218 | } catch (DataAccessException ex) {
219 | Notifier.error(getTranslation("Person could not be saved!"));
220 | }
221 | } catch (ValidationException ex) {
222 | Notifier.error(getTranslation("There have been validation errors!"));
223 | ex.getValidationErrors().forEach(validationResult ->
224 | Notifier.error(validationResult.getErrorMessage()));
225 | }
226 |
227 | clearForm();
228 | refreshGrid();
229 |
230 | UI.getCurrent().navigate(PersonView.class);
231 | }
232 | });
233 |
234 | cancel.addThemeVariants(ButtonVariant.LUMO_TERTIARY);
235 | save.addThemeVariants(ButtonVariant.LUMO_PRIMARY);
236 |
237 | buttonLayout.add(save, cancel);
238 |
239 | return buttonLayout;
240 | }
241 |
242 | private void refreshGrid() {
243 | grid.select(null);
244 | grid.getDataProvider().refreshAll();
245 | }
246 |
247 | @Override
248 | public String getPageTitle() {
249 | return getTranslation("Persons");
250 | }
251 | }
252 |
--------------------------------------------------------------------------------
/mvnw:
--------------------------------------------------------------------------------
1 | #!/bin/sh
2 | # ----------------------------------------------------------------------------
3 | # Licensed to the Apache Software Foundation (ASF) under one
4 | # or more contributor license agreements. See the NOTICE file
5 | # distributed with this work for additional information
6 | # regarding copyright ownership. The ASF licenses this file
7 | # to you under the Apache License, Version 2.0 (the
8 | # "License"); you may not use this file except in compliance
9 | # with the License. You may obtain a copy of the License at
10 | #
11 | # http://www.apache.org/licenses/LICENSE-2.0
12 | #
13 | # Unless required by applicable law or agreed to in writing,
14 | # software distributed under the License is distributed on an
15 | # "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
16 | # KIND, either express or implied. See the License for the
17 | # specific language governing permissions and limitations
18 | # under the License.
19 | # ----------------------------------------------------------------------------
20 |
21 | # ----------------------------------------------------------------------------
22 | # Maven Start Up Batch script
23 | #
24 | # Required ENV vars:
25 | # ------------------
26 | # JAVA_HOME - location of a JDK home dir
27 | #
28 | # Optional ENV vars
29 | # -----------------
30 | # M2_HOME - location of maven2's installed home dir
31 | # MAVEN_OPTS - parameters passed to the Java VM when running Maven
32 | # e.g. to debug Maven itself, use
33 | # set MAVEN_OPTS=-Xdebug -Xrunjdwp:transport=dt_socket,server=y,suspend=y,address=8000
34 | # MAVEN_SKIP_RC - flag to disable loading of mavenrc files
35 | # ----------------------------------------------------------------------------
36 |
37 | if [ -z "$MAVEN_SKIP_RC" ] ; then
38 |
39 | if [ -f /usr/local/etc/mavenrc ] ; then
40 | . /usr/local/etc/mavenrc
41 | fi
42 |
43 | if [ -f /etc/mavenrc ] ; then
44 | . /etc/mavenrc
45 | fi
46 |
47 | if [ -f "$HOME/.mavenrc" ] ; then
48 | . "$HOME/.mavenrc"
49 | fi
50 |
51 | fi
52 |
53 | # OS specific support. $var _must_ be set to either true or false.
54 | cygwin=false;
55 | darwin=false;
56 | mingw=false
57 | case "`uname`" in
58 | CYGWIN*) cygwin=true ;;
59 | MINGW*) mingw=true;;
60 | Darwin*) darwin=true
61 | # Use /usr/libexec/java_home if available, otherwise fall back to /Library/Java/Home
62 | # See https://developer.apple.com/library/mac/qa/qa1170/_index.html
63 | if [ -z "$JAVA_HOME" ]; then
64 | if [ -x "/usr/libexec/java_home" ]; then
65 | export JAVA_HOME="`/usr/libexec/java_home`"
66 | else
67 | export JAVA_HOME="/Library/Java/Home"
68 | fi
69 | fi
70 | ;;
71 | esac
72 |
73 | if [ -z "$JAVA_HOME" ] ; then
74 | if [ -r /etc/gentoo-release ] ; then
75 | JAVA_HOME=`java-config --jre-home`
76 | fi
77 | fi
78 |
79 | if [ -z "$M2_HOME" ] ; then
80 | ## resolve links - $0 may be a link to maven's home
81 | PRG="$0"
82 |
83 | # need this for relative symlinks
84 | while [ -h "$PRG" ] ; do
85 | ls=`ls -ld "$PRG"`
86 | link=`expr "$ls" : '.*-> \(.*\)$'`
87 | if expr "$link" : '/.*' > /dev/null; then
88 | PRG="$link"
89 | else
90 | PRG="`dirname "$PRG"`/$link"
91 | fi
92 | done
93 |
94 | saveddir=`pwd`
95 |
96 | M2_HOME=`dirname "$PRG"`/..
97 |
98 | # make it fully qualified
99 | M2_HOME=`cd "$M2_HOME" && pwd`
100 |
101 | cd "$saveddir"
102 | # echo Using m2 at $M2_HOME
103 | fi
104 |
105 | # For Cygwin, ensure paths are in UNIX format before anything is touched
106 | if $cygwin ; then
107 | [ -n "$M2_HOME" ] &&
108 | M2_HOME=`cygpath --unix "$M2_HOME"`
109 | [ -n "$JAVA_HOME" ] &&
110 | JAVA_HOME=`cygpath --unix "$JAVA_HOME"`
111 | [ -n "$CLASSPATH" ] &&
112 | CLASSPATH=`cygpath --path --unix "$CLASSPATH"`
113 | fi
114 |
115 | # For Mingw, ensure paths are in UNIX format before anything is touched
116 | if $mingw ; then
117 | [ -n "$M2_HOME" ] &&
118 | M2_HOME="`(cd "$M2_HOME"; pwd)`"
119 | [ -n "$JAVA_HOME" ] &&
120 | JAVA_HOME="`(cd "$JAVA_HOME"; pwd)`"
121 | fi
122 |
123 | if [ -z "$JAVA_HOME" ]; then
124 | javaExecutable="`which javac`"
125 | if [ -n "$javaExecutable" ] && ! [ "`expr \"$javaExecutable\" : '\([^ ]*\)'`" = "no" ]; then
126 | # readlink(1) is not available as standard on Solaris 10.
127 | readLink=`which readlink`
128 | if [ ! `expr "$readLink" : '\([^ ]*\)'` = "no" ]; then
129 | if $darwin ; then
130 | javaHome="`dirname \"$javaExecutable\"`"
131 | javaExecutable="`cd \"$javaHome\" && pwd -P`/javac"
132 | else
133 | javaExecutable="`readlink -f \"$javaExecutable\"`"
134 | fi
135 | javaHome="`dirname \"$javaExecutable\"`"
136 | javaHome=`expr "$javaHome" : '\(.*\)/bin'`
137 | JAVA_HOME="$javaHome"
138 | export JAVA_HOME
139 | fi
140 | fi
141 | fi
142 |
143 | if [ -z "$JAVACMD" ] ; then
144 | if [ -n "$JAVA_HOME" ] ; then
145 | if [ -x "$JAVA_HOME/jre/sh/java" ] ; then
146 | # IBM's JDK on AIX uses strange locations for the executables
147 | JAVACMD="$JAVA_HOME/jre/sh/java"
148 | else
149 | JAVACMD="$JAVA_HOME/bin/java"
150 | fi
151 | else
152 | JAVACMD="`\\unset -f command; \\command -v java`"
153 | fi
154 | fi
155 |
156 | if [ ! -x "$JAVACMD" ] ; then
157 | echo "Error: JAVA_HOME is not defined correctly." >&2
158 | echo " We cannot execute $JAVACMD" >&2
159 | exit 1
160 | fi
161 |
162 | if [ -z "$JAVA_HOME" ] ; then
163 | echo "Warning: JAVA_HOME environment variable is not set."
164 | fi
165 |
166 | CLASSWORLDS_LAUNCHER=org.codehaus.plexus.classworlds.launcher.Launcher
167 |
168 | # traverses directory structure from process work directory to filesystem root
169 | # first directory with .mvn subdirectory is considered project base directory
170 | find_maven_basedir() {
171 |
172 | if [ -z "$1" ]
173 | then
174 | echo "Path not specified to find_maven_basedir"
175 | return 1
176 | fi
177 |
178 | basedir="$1"
179 | wdir="$1"
180 | while [ "$wdir" != '/' ] ; do
181 | if [ -d "$wdir"/.mvn ] ; then
182 | basedir=$wdir
183 | break
184 | fi
185 | # workaround for JBEAP-8937 (on Solaris 10/Sparc)
186 | if [ -d "${wdir}" ]; then
187 | wdir=`cd "$wdir/.."; pwd`
188 | fi
189 | # end of workaround
190 | done
191 | echo "${basedir}"
192 | }
193 |
194 | # concatenates all lines of a file
195 | concat_lines() {
196 | if [ -f "$1" ]; then
197 | echo "$(tr -s '\n' ' ' < "$1")"
198 | fi
199 | }
200 |
201 | BASE_DIR=`find_maven_basedir "$(pwd)"`
202 | if [ -z "$BASE_DIR" ]; then
203 | exit 1;
204 | fi
205 |
206 | ##########################################################################################
207 | # Extension to allow automatically downloading the maven-wrapper.jar from Maven-central
208 | # This allows using the maven wrapper in projects that prohibit checking in binary data.
209 | ##########################################################################################
210 | if [ -r "$BASE_DIR/.mvn/wrapper/maven-wrapper.jar" ]; then
211 | if [ "$MVNW_VERBOSE" = true ]; then
212 | echo "Found .mvn/wrapper/maven-wrapper.jar"
213 | fi
214 | else
215 | if [ "$MVNW_VERBOSE" = true ]; then
216 | echo "Couldn't find .mvn/wrapper/maven-wrapper.jar, downloading it ..."
217 | fi
218 | if [ -n "$MVNW_REPOURL" ]; then
219 | jarUrl="$MVNW_REPOURL/org/apache/maven/wrapper/maven-wrapper/3.1.0/maven-wrapper-3.1.0.jar"
220 | else
221 | jarUrl="https://repo.maven.apache.org/maven2/org/apache/maven/wrapper/maven-wrapper/3.1.0/maven-wrapper-3.1.0.jar"
222 | fi
223 | while IFS="=" read key value; do
224 | case "$key" in (wrapperUrl) jarUrl="$value"; break ;;
225 | esac
226 | done < "$BASE_DIR/.mvn/wrapper/maven-wrapper.properties"
227 | if [ "$MVNW_VERBOSE" = true ]; then
228 | echo "Downloading from: $jarUrl"
229 | fi
230 | wrapperJarPath="$BASE_DIR/.mvn/wrapper/maven-wrapper.jar"
231 | if $cygwin; then
232 | wrapperJarPath=`cygpath --path --windows "$wrapperJarPath"`
233 | fi
234 |
235 | if command -v wget > /dev/null; then
236 | if [ "$MVNW_VERBOSE" = true ]; then
237 | echo "Found wget ... using wget"
238 | fi
239 | if [ -z "$MVNW_USERNAME" ] || [ -z "$MVNW_PASSWORD" ]; then
240 | wget "$jarUrl" -O "$wrapperJarPath" || rm -f "$wrapperJarPath"
241 | else
242 | wget --http-user=$MVNW_USERNAME --http-password=$MVNW_PASSWORD "$jarUrl" -O "$wrapperJarPath" || rm -f "$wrapperJarPath"
243 | fi
244 | elif command -v curl > /dev/null; then
245 | if [ "$MVNW_VERBOSE" = true ]; then
246 | echo "Found curl ... using curl"
247 | fi
248 | if [ -z "$MVNW_USERNAME" ] || [ -z "$MVNW_PASSWORD" ]; then
249 | curl -o "$wrapperJarPath" "$jarUrl" -f
250 | else
251 | curl --user $MVNW_USERNAME:$MVNW_PASSWORD -o "$wrapperJarPath" "$jarUrl" -f
252 | fi
253 |
254 | else
255 | if [ "$MVNW_VERBOSE" = true ]; then
256 | echo "Falling back to using Java to download"
257 | fi
258 | javaClass="$BASE_DIR/.mvn/wrapper/MavenWrapperDownloader.java"
259 | # For Cygwin, switch paths to Windows format before running javac
260 | if $cygwin; then
261 | javaClass=`cygpath --path --windows "$javaClass"`
262 | fi
263 | if [ -e "$javaClass" ]; then
264 | if [ ! -e "$BASE_DIR/.mvn/wrapper/MavenWrapperDownloader.class" ]; then
265 | if [ "$MVNW_VERBOSE" = true ]; then
266 | echo " - Compiling MavenWrapperDownloader.java ..."
267 | fi
268 | # Compiling the Java class
269 | ("$JAVA_HOME/bin/javac" "$javaClass")
270 | fi
271 | if [ -e "$BASE_DIR/.mvn/wrapper/MavenWrapperDownloader.class" ]; then
272 | # Running the downloader
273 | if [ "$MVNW_VERBOSE" = true ]; then
274 | echo " - Running MavenWrapperDownloader.java ..."
275 | fi
276 | ("$JAVA_HOME/bin/java" -cp .mvn/wrapper MavenWrapperDownloader "$MAVEN_PROJECTBASEDIR")
277 | fi
278 | fi
279 | fi
280 | fi
281 | ##########################################################################################
282 | # End of extension
283 | ##########################################################################################
284 |
285 | export MAVEN_PROJECTBASEDIR=${MAVEN_BASEDIR:-"$BASE_DIR"}
286 | if [ "$MVNW_VERBOSE" = true ]; then
287 | echo $MAVEN_PROJECTBASEDIR
288 | fi
289 | MAVEN_OPTS="$(concat_lines "$MAVEN_PROJECTBASEDIR/.mvn/jvm.config") $MAVEN_OPTS"
290 |
291 | # For Cygwin, switch paths to Windows format before running java
292 | if $cygwin; then
293 | [ -n "$M2_HOME" ] &&
294 | M2_HOME=`cygpath --path --windows "$M2_HOME"`
295 | [ -n "$JAVA_HOME" ] &&
296 | JAVA_HOME=`cygpath --path --windows "$JAVA_HOME"`
297 | [ -n "$CLASSPATH" ] &&
298 | CLASSPATH=`cygpath --path --windows "$CLASSPATH"`
299 | [ -n "$MAVEN_PROJECTBASEDIR" ] &&
300 | MAVEN_PROJECTBASEDIR=`cygpath --path --windows "$MAVEN_PROJECTBASEDIR"`
301 | fi
302 |
303 | # Provide a "standardized" way to retrieve the CLI args that will
304 | # work with both Windows and non-Windows executions.
305 | MAVEN_CMD_LINE_ARGS="$MAVEN_CONFIG $@"
306 | export MAVEN_CMD_LINE_ARGS
307 |
308 | WRAPPER_LAUNCHER=org.apache.maven.wrapper.MavenWrapperMain
309 |
310 | exec "$JAVACMD" \
311 | $MAVEN_OPTS \
312 | $MAVEN_DEBUG_OPTS \
313 | -classpath "$MAVEN_PROJECTBASEDIR/.mvn/wrapper/maven-wrapper.jar" \
314 | "-Dmaven.home=${M2_HOME}" \
315 | "-Dmaven.multiModuleProjectDirectory=${MAVEN_PROJECTBASEDIR}" \
316 | ${WRAPPER_LAUNCHER} $MAVEN_CONFIG "$@"
317 |
--------------------------------------------------------------------------------
/LICENSE:
--------------------------------------------------------------------------------
1 | Apache License
2 | Version 2.0, January 2004
3 | http://www.apache.org/licenses/
4 |
5 | TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION
6 |
7 | 1. Definitions.
8 |
9 | "License" shall mean the terms and conditions for use, reproduction,
10 | and distribution as defined by Sections 1 through 9 of this document.
11 |
12 | "Licensor" shall mean the copyright owner or entity authorized by
13 | the copyright owner that is granting the License.
14 |
15 | "Legal Entity" shall mean the union of the acting entity and all
16 | other entities that control, are controlled by, or are under common
17 | control with that entity. For the purposes of this definition,
18 | "control" means (i) the power, direct or indirect, to cause the
19 | direction or management of such entity, whether by contract or
20 | otherwise, or (ii) ownership of fifty percent (50%) or more of the
21 | outstanding shares, or (iii) beneficial ownership of such entity.
22 |
23 | "You" (or "Your") shall mean an individual or Legal Entity
24 | exercising permissions granted by this License.
25 |
26 | "Source" form shall mean the preferred form for making modifications,
27 | including but not limited to software source code, documentation
28 | source, and configuration files.
29 |
30 | "Object" form shall mean any form resulting from mechanical
31 | transformation or translation of a Source form, including but
32 | not limited to compiled object code, generated documentation,
33 | and conversions to other media types.
34 |
35 | "Work" shall mean the work of authorship, whether in Source or
36 | Object form, made available under the License, as indicated by a
37 | copyright notice that is included in or attached to the work
38 | (an example is provided in the Appendix below).
39 |
40 | "Derivative Works" shall mean any work, whether in Source or Object
41 | form, that is based on (or derived from) the Work and for which the
42 | editorial revisions, annotations, elaborations, or other modifications
43 | represent, as a whole, an original work of authorship. For the purposes
44 | of this License, Derivative Works shall not include works that remain
45 | separable from, or merely link (or bind by name) to the interfaces of,
46 | the Work and Derivative Works thereof.
47 |
48 | "Contribution" shall mean any work of authorship, including
49 | the original version of the Work and any modifications or additions
50 | to that Work or Derivative Works thereof, that is intentionally
51 | submitted to Licensor for inclusion in the Work by the copyright owner
52 | or by an individual or Legal Entity authorized to submit on behalf of
53 | the copyright owner. For the purposes of this definition, "submitted"
54 | means any form of electronic, verbal, or written communication sent
55 | to the Licensor or its representatives, including but not limited to
56 | communication on electronic mailing lists, source code control systems,
57 | and issue tracking systems that are managed by, or on behalf of, the
58 | Licensor for the purpose of discussing and improving the Work, but
59 | excluding communication that is conspicuously marked or otherwise
60 | designated in writing by the copyright owner as "Not a Contribution."
61 |
62 | "Contributor" shall mean Licensor and any individual or Legal Entity
63 | on behalf of whom a Contribution has been received by Licensor and
64 | subsequently incorporated within the Work.
65 |
66 | 2. Grant of Copyright License. Subject to the terms and conditions of
67 | this License, each Contributor hereby grants to You a perpetual,
68 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable
69 | copyright license to reproduce, prepare Derivative Works of,
70 | publicly display, publicly perform, sublicense, and distribute the
71 | Work and such Derivative Works in Source or Object form.
72 |
73 | 3. Grant of Patent License. Subject to the terms and conditions of
74 | this License, each Contributor hereby grants to You a perpetual,
75 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable
76 | (except as stated in this section) patent license to make, have made,
77 | use, offer to sell, sell, import, and otherwise transfer the Work,
78 | where such license applies only to those patent claims licensable
79 | by such Contributor that are necessarily infringed by their
80 | Contribution(s) alone or by combination of their Contribution(s)
81 | with the Work to which such Contribution(s) was submitted. If You
82 | institute patent litigation against any entity (including a
83 | cross-claim or counterclaim in a lawsuit) alleging that the Work
84 | or a Contribution incorporated within the Work constitutes direct
85 | or contributory patent infringement, then any patent licenses
86 | granted to You under this License for that Work shall terminate
87 | as of the date such litigation is filed.
88 |
89 | 4. Redistribution. You may reproduce and distribute copies of the
90 | Work or Derivative Works thereof in any medium, with or without
91 | modifications, and in Source or Object form, provided that You
92 | meet the following conditions:
93 |
94 | (a) You must give any other recipients of the Work or
95 | Derivative Works a copy of this License; and
96 |
97 | (b) You must cause any modified files to carry prominent notices
98 | stating that You changed the files; and
99 |
100 | (c) You must retain, in the Source form of any Derivative Works
101 | that You distribute, all copyright, patent, trademark, and
102 | attribution notices from the Source form of the Work,
103 | excluding those notices that do not pertain to any part of
104 | the Derivative Works; and
105 |
106 | (d) If the Work includes a "NOTICE" text file as part of its
107 | distribution, then any Derivative Works that You distribute must
108 | include a readable copy of the attribution notices contained
109 | within such NOTICE file, excluding those notices that do not
110 | pertain to any part of the Derivative Works, in at least one
111 | of the following places: within a NOTICE text file distributed
112 | as part of the Derivative Works; within the Source form or
113 | documentation, if provided along with the Derivative Works; or,
114 | within a display generated by the Derivative Works, if and
115 | wherever such third-party notices normally appear. The contents
116 | of the NOTICE file are for informational purposes only and
117 | do not modify the License. You may add Your own attribution
118 | notices within Derivative Works that You distribute, alongside
119 | or as an addendum to the NOTICE text from the Work, provided
120 | that such additional attribution notices cannot be construed
121 | as modifying the License.
122 |
123 | You may add Your own copyright statement to Your modifications and
124 | may provide additional or different license terms and conditions
125 | for use, reproduction, or distribution of Your modifications, or
126 | for any such Derivative Works as a whole, provided Your use,
127 | reproduction, and distribution of the Work otherwise complies with
128 | the conditions stated in this License.
129 |
130 | 5. Submission of Contributions. Unless You explicitly state otherwise,
131 | any Contribution intentionally submitted for inclusion in the Work
132 | by You to the Licensor shall be under the terms and conditions of
133 | this License, without any additional terms or conditions.
134 | Notwithstanding the above, nothing herein shall supersede or modify
135 | the terms of any separate license agreement you may have executed
136 | with Licensor regarding such Contributions.
137 |
138 | 6. Trademarks. This License does not grant permission to use the trade
139 | names, trademarks, service marks, or product names of the Licensor,
140 | except as required for reasonable and customary use in describing the
141 | origin of the Work and reproducing the content of the NOTICE file.
142 |
143 | 7. Disclaimer of Warranty. Unless required by applicable law or
144 | agreed to in writing, Licensor provides the Work (and each
145 | Contributor provides its Contributions) on an "AS IS" BASIS,
146 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
147 | implied, including, without limitation, any warranties or conditions
148 | of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A
149 | PARTICULAR PURPOSE. You are solely responsible for determining the
150 | appropriateness of using or redistributing the Work and assume any
151 | risks associated with Your exercise of permissions under this License.
152 |
153 | 8. Limitation of Liability. In no event and under no legal theory,
154 | whether in tort (including negligence), contract, or otherwise,
155 | unless required by applicable law (such as deliberate and grossly
156 | negligent acts) or agreed to in writing, shall any Contributor be
157 | liable to You for damages, including any direct, indirect, special,
158 | incidental, or consequential damages of any character arising as a
159 | result of this License or out of the use or inability to use the
160 | Work (including but not limited to damages for loss of goodwill,
161 | work stoppage, computer failure or malfunction, or any and all
162 | other commercial damages or losses), even if such Contributor
163 | has been advised of the possibility of such damages.
164 |
165 | 9. Accepting Warranty or Additional Liability. While redistributing
166 | the Work or Derivative Works thereof, You may choose to offer,
167 | and charge a fee for, acceptance of support, warranty, indemnity,
168 | or other liability obligations and/or rights consistent with this
169 | License. However, in accepting such obligations, You may act only
170 | on Your own behalf and on Your sole responsibility, not on behalf
171 | of any other Contributor, and only if You agree to indemnify,
172 | defend, and hold each Contributor harmless for any liability
173 | incurred by, or claims asserted against, such Contributor by reason
174 | of your accepting any such warranty or additional liability.
175 |
176 | END OF TERMS AND CONDITIONS
177 |
178 | APPENDIX: How to apply the Apache License to your work.
179 |
180 | To apply the Apache License to your work, attach the following
181 | boilerplate notice, with the fields enclosed by brackets "[]"
182 | replaced with your own identifying information. (Don't include
183 | the brackets!) The text should be enclosed in the appropriate
184 | comment syntax for the file format. We also recommend that a
185 | file or class name and description of purpose be included on the
186 | same "printed page" as the copyright notice for easier
187 | identification within third-party archives.
188 |
189 | Copyright [yyyy] [name of copyright owner]
190 |
191 | Licensed under the Apache License, Version 2.0 (the "License");
192 | you may not use this file except in compliance with the License.
193 | You may obtain a copy of the License at
194 |
195 | http://www.apache.org/licenses/LICENSE-2.0
196 |
197 | Unless required by applicable law or agreed to in writing, software
198 | distributed under the License is distributed on an "AS IS" BASIS,
199 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
200 | See the License for the specific language governing permissions and
201 | limitations under the License.
202 |
--------------------------------------------------------------------------------
/pom.xml:
--------------------------------------------------------------------------------
1 |
2 |
5 | 4.0.0
6 |
7 |
8 | org.springframework.boot
9 | spring-boot-starter-parent
10 | 3.3.4
11 |
12 |
13 | ch.martinelli.template
14 | vaadin-jooq-template
15 | 0.0.1-SNAPSHOT
16 |
17 |
18 | 21
19 | 24.5.0
20 | 3.19.13
21 | 0.0.3
22 | 2.1.8
23 | 1.48.0
24 | 0.0.3
25 | 1.3.0
26 | 0.8.12
27 | 4.0.0.4121
28 |
29 |
30 |
31 |
32 |
33 | com.vaadin
34 | vaadin-bom
35 | ${vaadin.version}
36 | pom
37 | import
38 |
39 |
40 |
41 |
42 |
43 |
44 | com.vaadin
45 |
46 | vaadin-core
47 |
48 |
49 | com.vaadin
50 | vaadin-spring-boot-starter
51 |
52 |
53 | org.springframework.boot
54 | spring-boot-starter-security
55 |
56 |
57 | org.springframework.boot
58 | spring-boot-starter-validation
59 |
60 |
61 | org.springframework.boot
62 | spring-boot-starter-jooq
63 |
64 |
65 | io.seventytwo.oss
66 | vaadin-jooq
67 | 2.0.4
68 |
69 |
70 |
71 | org.springframework.security
72 | spring-security-oauth2-jose
73 |
74 |
75 | org.springframework.security
76 | spring-security-oauth2-resource-server
77 |
78 |
79 |
80 | org.flywaydb
81 | flyway-core
82 |
83 |
84 | org.flywaydb
85 | flyway-database-postgresql
86 |
87 |
88 |
89 | org.postgresql
90 | postgresql
91 | runtime
92 |
93 |
94 |
95 | org.springframework.boot
96 | spring-boot-devtools
97 | true
98 |
99 |
100 |
101 | org.springframework.boot
102 | spring-boot-starter-test
103 | test
104 |
105 |
106 |
107 | org.testcontainers
108 | junit-jupiter
109 | test
110 |
111 |
112 | org.springframework.boot
113 | spring-boot-testcontainers
114 | test
115 |
116 |
117 | org.testcontainers
118 | postgresql
119 | test
120 |
121 |
122 |
123 | com.github.mvysny.kaributesting
124 | karibu-testing-v10-spring
125 | ${karibu-testing.version}
126 | test
127 |
128 |
129 | com.microsoft.playwright
130 | playwright
131 | ${playwright.version}
132 | test
133 |
134 |
135 | in.virit
136 | mopo
137 | ${mopo.version}
138 |
139 |
140 |
141 | com.tngtech.archunit
142 | archunit
143 | ${archunit.version}
144 | test
145 |
146 |
147 |
148 |
149 | spring-boot:run
150 |
151 |
152 | org.testcontainers
153 | testcontainers-jooq-codegen-maven-plugin
154 | ${testcontainers-jooq-codegen-maven-plugin.version}
155 |
156 |
157 | org.testcontainers
158 | postgresql
159 | ${testcontainers.version}
160 |
161 |
162 | org.postgresql
163 | postgresql
164 | ${postgresql.version}
165 |
166 |
167 |
168 |
169 | generate-jooq-sources
170 |
171 | generate
172 |
173 | generate-sources
174 |
175 |
176 | POSTGRES
177 | postgres:16.4
178 |
179 |
180 | filesystem:src/main/resources/db/migration
181 |
182 |
183 |
184 |
185 | public
186 | flyway_schema_history
187 |
188 | version
189 |
190 |
191 | ch.martinelli.vj.db
192 |
193 |
194 |
195 |
196 |
197 |
198 |
199 |
200 | org.springframework.boot
201 | spring-boot-maven-plugin
202 |
203 | -Xdebug -Xrunjdwp:transport=dt_socket,server=y,suspend=n,address=5679
204 | 500
205 | 240
206 |
207 |
208 |
209 | com.vaadin
210 | vaadin-maven-plugin
211 | ${vaadin.version}
212 |
213 |
214 |
215 | prepare-frontend
216 |
217 |
218 |
219 |
220 |
221 | org.apache.maven.plugins
222 | maven-failsafe-plugin
223 |
224 |
225 |
226 | integration-test
227 | verify
228 |
229 |
230 |
231 |
232 | false
233 | true
234 |
235 |
236 |
237 | org.jacoco
238 | jacoco-maven-plugin
239 | ${jacoco.version}
240 |
241 |
242 |
243 | prepare-agent
244 |
245 |
246 | ${project.build.directory}/jacoco-ut.exec
247 |
248 |
249 |
250 | pre-integration-prepare
251 |
252 | prepare-agent-integration
253 |
254 |
255 |
256 | report
257 | post-integration-test
258 |
259 | merge
260 |
261 |
262 |
263 |
264 | ${project.build.directory}
265 |
266 | *.exec
267 |
268 |
269 |
270 |
271 |
272 |
273 | merged-report-generation
274 | verify
275 |
276 | report
277 |
278 |
279 |
280 |
281 |
282 | org.sonarsource.scanner.maven
283 | sonar-maven-plugin
284 | ${sonar-maven-plugin.version}
285 |
286 |
287 |
288 |
289 |
290 |
291 |
292 | production
293 |
294 |
295 |
296 | com.vaadin
297 | vaadin-core
298 |
299 |
300 | com.vaadin
301 | vaadin-dev
302 |
303 |
304 |
305 |
306 |
307 |
308 |
309 | com.vaadin
310 | vaadin-maven-plugin
311 | ${vaadin.version}
312 |
313 |
314 |
315 | build-frontend
316 |
317 | compile
318 |
319 |
320 |
321 |
322 |
323 |
324 |
325 |
326 |
327 |
328 | vaadin-prereleases
329 | https://maven.vaadin.com/vaadin-prereleases
330 |
331 | true
332 |
333 |
334 |
335 | Vaadin Directory
336 | https://maven.vaadin.com/vaadin-addons
337 |
338 | false
339 |
340 |
341 |
342 |
343 |
344 |
345 | vaadin-prereleases
346 | https://maven.vaadin.com/vaadin-prereleases
347 |
348 | true
349 |
350 |
351 |
352 |
353 |
--------------------------------------------------------------------------------
/src/test/resources/db/migration/afterMigrate.sql:
--------------------------------------------------------------------------------
1 | insert into person(version, id,first_name,last_name,email,phone,date_of_birth,occupation,role,important) values (1, 1,'Eula','Lane','eula.lane@jigrormo.ye','(762) 526-5961','1954-05-15','Insurance Clerk','Worker',false) on conflict do nothing;
2 | insert into person(version, id,first_name,last_name,email,phone,date_of_birth,occupation,role,important) values (1, 2,'Barry','Rodriquez','barry.rodriquez@zun.mm','(267) 955-5124','2013-05-15','Mortarman','Manager',false) on conflict do nothing;
3 | insert into person(version, id,first_name,last_name,email,phone,date_of_birth,occupation,role,important) values (1, 3,'Eugenia','Selvi','eugenia.selvi@capfad.vn','(680) 368-2192','1973-04-30','Beer Coil Cleaner','External',false) on conflict do nothing;
4 | insert into person(version, id,first_name,last_name,email,phone,date_of_birth,occupation,role,important) values (1, 4,'Alejandro','Miles','alejandro.miles@dec.bn','(281) 301-2039','2013-06-17','Scale Attendant','Worker',false) on conflict do nothing;
5 | insert into person(version, id,first_name,last_name,email,phone,date_of_birth,occupation,role,important) values (1, 5,'Cora','Tesi','cora.tesi@bivo.yt','(600) 616-7955','1971-08-15','Clinical Audiologist','Supervisor',false) on conflict do nothing;
6 | insert into person(version, id,first_name,last_name,email,phone,date_of_birth,occupation,role,important) values (1, 6,'Marguerite','Ishii','marguerite.ishii@judbilo.gn','(882) 813-1374','1937-05-12','Parking Meter Collector','Supervisor',false) on conflict do nothing;
7 | insert into person(version, id,first_name,last_name,email,phone,date_of_birth,occupation,role,important) values (1, 7,'Mildred','Jacobs','mildred.jacobs@joraf.wf','(642) 665-1763','1966-12-15','Business Unit Manager','Manager',false) on conflict do nothing;
8 | insert into person(version, id,first_name,last_name,email,phone,date_of_birth,occupation,role,important) values (1, 8,'Gene','Goodman','gene.goodman@kem.tl','(383) 458-2132','2009-10-25','Technical Communicator','External',false) on conflict do nothing;
9 | insert into person(version, id,first_name,last_name,email,phone,date_of_birth,occupation,role,important) values (1, 9,'Lettie','Bennett','lettie.bennett@odeter.bb','(769) 335-6771','1958-12-30','Correctional Officer Sergeant','Worker',false) on conflict do nothing;
10 | insert into person(version, id,first_name,last_name,email,phone,date_of_birth,occupation,role,important) values (1, 10,'Mabel','Leach','mabel.leach@lisohuje.vi','(803) 586-8035','1945-12-06','Food Chemist','Supervisor',false) on conflict do nothing;
11 | insert into person(version, id,first_name,last_name,email,phone,date_of_birth,occupation,role,important) values (1, 11,'Jordan','Miccinesi','jordan.miccinesi@duod.gy','(531) 919-2280','1982-01-17','Signals Intelligence/Electronic Warfare Chief','Manager',false) on conflict do nothing;
12 | insert into person(version, id,first_name,last_name,email,phone,date_of_birth,occupation,role,important) values (1, 12,'Marie','Parkes','marie.parkes@nowufpus.ph','(814) 667-8937','1942-11-18','Language Pathologist','External',false) on conflict do nothing;
13 | insert into person(version, id,first_name,last_name,email,phone,date_of_birth,occupation,role,important) values (1, 13,'Rose','Gray','rose.gray@kagu.hr','(713) 311-8766','1957-11-17','Wildlife Officer','Worker',false) on conflict do nothing;
14 | insert into person(version, id,first_name,last_name,email,phone,date_of_birth,occupation,role,important) values (1, 14,'Garrett','Stokes','garrett.stokes@fef.bg','(381) 421-2371','2008-08-28','Bindery Machine Operator','Manager',false) on conflict do nothing;
15 | insert into person(version, id,first_name,last_name,email,phone,date_of_birth,occupation,role,important) values (1, 15,'Barbara','Matthieu','barbara.matthieu@derwogi.jm','(940) 463-7299','1929-08-24','Instructional Aide','External',false) on conflict do nothing;
16 | insert into person(version, id,first_name,last_name,email,phone,date_of_birth,occupation,role,important) values (1, 16,'Jean','Rhodes','jean.rhodes@wehovuce.gu','(777) 435-9570','1949-01-31','Clinical Psychiatrist','Worker',false) on conflict do nothing;
17 | insert into person(version, id,first_name,last_name,email,phone,date_of_birth,occupation,role,important) values (1, 17,'Jack','Romoli','jack.romoli@zamum.bw','(517) 393-9630','1974-11-27','Mortician Investigator','Supervisor',false) on conflict do nothing;
18 | insert into person(version, id,first_name,last_name,email,phone,date_of_birth,occupation,role,important) values (1, 18,'Pearl','Holden','pearl.holden@dunebuh.cr','(711) 904-3669','1949-03-24','Rod Buster Helper','Manager',false) on conflict do nothing;
19 | insert into person(version, id,first_name,last_name,email,phone,date_of_birth,occupation,role,important) values (1, 19,'Belle','Montero','belle.montero@repiwid.si','(935) 404-4792','1932-04-16','Classroom Aide','External',false) on conflict do nothing;
20 | insert into person(version, id,first_name,last_name,email,phone,date_of_birth,occupation,role,important) values (1, 20,'Olive','Molina','olive.molina@razuppa.ga','(935) 267-8492','1933-10-27','Traditional Chinese Herbalist','Worker',false) on conflict do nothing;
21 | insert into person(version, id,first_name,last_name,email,phone,date_of_birth,occupation,role,important) values (1, 21,'Minerva','Todd','minerva.todd@kulmenim.ad','(763) 948-4815','1950-03-30','Electronic Drafter','Supervisor',false) on conflict do nothing;
22 | insert into person(version, id,first_name,last_name,email,phone,date_of_birth,occupation,role,important) values (1, 22,'Bobby','Pearson','bobby.pearson@ib.kg','(238) 240-2561','2014-08-22','Vault Teller','Worker',true) on conflict do nothing;
23 | insert into person(version, id,first_name,last_name,email,phone,date_of_birth,occupation,role,important) values (1, 23,'Larry','Ciappi','larry.ciappi@ba.lk','(410) 257-1723','1995-09-07','Fire Sprinkler Installer','Supervisor',false) on conflict do nothing;
24 | insert into person(version, id,first_name,last_name,email,phone,date_of_birth,occupation,role,important) values (1, 24,'Ronnie','Salucci','ronnie.salucci@tohhij.lv','(566) 726-3346','1974-03-11','Brewery Pumper','Manager',false) on conflict do nothing;
25 | insert into person(version, id,first_name,last_name,email,phone,date_of_birth,occupation,role,important) values (1, 25,'Walter','Grossi','walter.grossi@tuvo.sa','(416) 906-7221','1987-04-29','Kitchen Chef','External',false) on conflict do nothing;
26 | insert into person(version, id,first_name,last_name,email,phone,date_of_birth,occupation,role,important) values (1, 26,'Frances','Koopmans','frances.koopmans@foga.tw','(611) 712-1562','1966-06-20','Medical Esthetician','Worker',false) on conflict do nothing;
27 | insert into person(version, id,first_name,last_name,email,phone,date_of_birth,occupation,role,important) values (1, 27,'Frances','Fujimoto','frances.fujimoto@uswuzzub.jp','(919) 887-8542','1935-11-24','Auto Tire Worker','Supervisor',false) on conflict do nothing;
28 | insert into person(version, id,first_name,last_name,email,phone,date_of_birth,occupation,role,important) values (1, 28,'Olivia','Vidal','olivia.vidal@hivwerip.vc','(982) 684-7650','1933-03-03','Semi-Truck Driver','Manager',false) on conflict do nothing;
29 | insert into person(version, id,first_name,last_name,email,phone,date_of_birth,occupation,role,important) values (1, 29,'Edna','Henry','edna.henry@gugusu.rw','(811) 931-8202','1947-01-15','Command And Control','External',false) on conflict do nothing;
30 | insert into person(version, id,first_name,last_name,email,phone,date_of_birth,occupation,role,important) values (1, 30,'Lydia','Brun','lydia.brun@zedekak.md','(927) 400-3928','1929-02-27','Drywall Hanger','Worker',false) on conflict do nothing;
31 | insert into person(version, id,first_name,last_name,email,phone,date_of_birth,occupation,role,important) values (1, 31,'Jay','Blake','jay.blake@ral.mk','(365) 345-1498','2009-03-17','Real Property Evaluator','Manager',false) on conflict do nothing;
32 | insert into person(version, id,first_name,last_name,email,phone,date_of_birth,occupation,role,important) values (1, 32,'Isabel','Serafini','isabel.serafini@turuhu.bh','(656) 968-9869','1973-03-25','Human Performance Professor','Manager',false) on conflict do nothing;
33 | insert into person(version, id,first_name,last_name,email,phone,date_of_birth,occupation,role,important) values (1, 33,'Rebecca','Carter','rebecca.carter@omjo.et','(739) 612-6585','1958-10-12','V/Stol Landing Signal Officer','External',false) on conflict do nothing;
34 | insert into person(version, id,first_name,last_name,email,phone,date_of_birth,occupation,role,important) values (1, 34,'Maurice','Fabbrini','maurice.fabbrini@rig.bh','(485) 521-2687','1992-08-17','Air Control/Anti-Air Warfare Officer','Supervisor',false) on conflict do nothing;
35 | insert into person(version, id,first_name,last_name,email,phone,date_of_birth,occupation,role,important) values (1, 35,'Ollie','Turnbull','ollie.turnbull@sicewap.org','(835) 620-3330','1944-04-13','General Superintendent','Manager',false) on conflict do nothing;
36 | insert into person(version, id,first_name,last_name,email,phone,date_of_birth,occupation,role,important) values (1, 36,'Jerry','Hopkins','jerry.hopkins@fo.mh','(211) 851-5960','2014-05-08','Child Protective Services Social Worker','External',true) on conflict do nothing;
37 | insert into person(version, id,first_name,last_name,email,phone,date_of_birth,occupation,role,important) values (1, 37,'Nora','Lyons','nora.lyons@gegijap.na','(811) 311-5257','1945-02-19','Lens Grinder and Polisher','Worker',false) on conflict do nothing;
38 | insert into person(version, id,first_name,last_name,email,phone,date_of_birth,occupation,role,important) values (1, 38,'Anne','Weiß','anne.weiß@kuvesa.pe','(843) 836-3759','1940-09-07','Civil Engineering Professor','Worker',false) on conflict do nothing;
39 | insert into person(version, id,first_name,last_name,email,phone,date_of_birth,occupation,role,important) values (1, 39,'Louise','Gauthier','louise.gauthier@lapahu.mt','(913) 235-1856','1929-12-18','Mobile Home Servicer','Supervisor',false) on conflict do nothing;
40 | insert into person(version, id,first_name,last_name,email,phone,date_of_birth,occupation,role,important) values (1, 40,'Lloyd','Fani','lloyd.fani@zev.ru','(467) 487-7239','1991-11-22','Floor Refinisher','Supervisor',false) on conflict do nothing;
41 | insert into person(version, id,first_name,last_name,email,phone,date_of_birth,occupation,role,important) values (1, 41,'Maud','Dunn','maud.dunn@nabeaga.ni','(724) 340-3634','1955-02-13','Senior Sales Associate','Manager',false) on conflict do nothing;
42 | insert into person(version, id,first_name,last_name,email,phone,date_of_birth,occupation,role,important) values (1, 42,'Henry','Gigli','henry.gigli@kaot.ps','(413) 229-8428','1988-04-17','Tile Designer','Worker',false) on conflict do nothing;
43 | insert into person(version, id,first_name,last_name,email,phone,date_of_birth,occupation,role,important) values (1, 43,'Virgie','Werner','virgie.werner@tawuctuj.cf','(886) 292-9749','1941-05-27','Econometrics Professor','Supervisor',false) on conflict do nothing;
44 | insert into person(version, id,first_name,last_name,email,phone,date_of_birth,occupation,role,important) values (1, 44,'Gregory','Cozzi','gregory.cozzi@eh.ru','(418) 472-1239','1994-03-20','Basketball Player','Manager',false) on conflict do nothing;
45 | insert into person(version, id,first_name,last_name,email,phone,date_of_birth,occupation,role,important) values (1, 45,'Lucinda','Gil','lucinda.gil@fajjusuz.kr','(961) 233-3461','1934-03-11','Indirect Fire Infantryman','External',false) on conflict do nothing;
46 | insert into person(version, id,first_name,last_name,email,phone,date_of_birth,occupation,role,important) values (1, 46,'Gertrude','Verbeek','gertrude.verbeek@pave.cc','(605) 226-4037','1964-03-24','Licensed Esthetician','Worker',false) on conflict do nothing;
47 | insert into person(version, id,first_name,last_name,email,phone,date_of_birth,occupation,role,important) values (1, 47,'Mattie','Graham','mattie.graham@ispaviw.gt','(719) 765-1705','1957-01-20','Antisubmarine Warfare Intelligence Officer','Supervisor',false) on conflict do nothing;
48 | insert into person(version, id,first_name,last_name,email,phone,date_of_birth,occupation,role,important) values (1, 48,'Bryan','Shaw','bryan.shaw@ha.ee','(232) 228-5539','2018-09-16','Research Assistant','Manager',true) on conflict do nothing;
49 | insert into person(version, id,first_name,last_name,email,phone,date_of_birth,occupation,role,important) values (1, 49,'Essie','Adams','essie.adams@iliat.cw','(768) 554-8377','1958-03-28','Cigar Roller','External',false) on conflict do nothing;
50 | insert into person(version, id,first_name,last_name,email,phone,date_of_birth,occupation,role,important) values (1, 50,'Gary','Osborne','gary.osborne@do.ga','(311) 731-7079','2009-01-14','Customer Support Representative','Worker',false) on conflict do nothing;
51 | insert into person(version, id,first_name,last_name,email,phone,date_of_birth,occupation,role,important) values (1, 51,'Richard','Silva','richard.silva@wi.lc','(207) 554-6244','2014-08-26','Programmer','Manager',true) on conflict do nothing;
52 | insert into person(version, id,first_name,last_name,email,phone,date_of_birth,occupation,role,important) values (1, 52,'Dustin','Pestelli','dustin.pestelli@iwage.la','(558) 913-2855','1977-12-30','Global Engineering Manager','Manager',false) on conflict do nothing;
53 | insert into person(version, id,first_name,last_name,email,phone,date_of_birth,occupation,role,important) values (1, 53,'Henrietta','Hilton','henrietta.hilton@joopoju.pn','(832) 759-6654','1943-10-24','Telegraph and Teletype Operator','External',false) on conflict do nothing;
54 | insert into person(version, id,first_name,last_name,email,phone,date_of_birth,occupation,role,important) values (1, 54,'Francisco','Giordano','francisco.giordano@gojawu.tn','(482) 736-8079','1988-04-01','Hairpiece Stylist','Manager',false) on conflict do nothing;
55 | insert into person(version, id,first_name,last_name,email,phone,date_of_birth,occupation,role,important) values (1, 55,'Cynthia','Sardi','cynthia.sardi@afigoh.mm','(677) 345-2680','1973-09-20','Tobacco Buyer','External',false) on conflict do nothing;
56 | insert into person(version, id,first_name,last_name,email,phone,date_of_birth,occupation,role,important) values (1, 56,'Lula','Testi','lula.testi@benom.tj','(610) 374-7581','1971-07-18','Marine Steamfitter','Worker',false) on conflict do nothing;
57 | insert into person(version, id,first_name,last_name,email,phone,date_of_birth,occupation,role,important) values (1, 57,'Bess','Lucas','bess.lucas@jevakbe.cd','(982) 583-8067','1928-12-26','Attending Anesthesiologist','Supervisor',false) on conflict do nothing;
58 | insert into person(version, id,first_name,last_name,email,phone,date_of_birth,occupation,role,important) values (1, 58,'Linnie','Driessen','linnie.driessen@darhow.tr','(680) 266-3167','1967-09-26','Certified Indoor Environmentalist','External',false) on conflict do nothing;
59 | insert into person(version, id,first_name,last_name,email,phone,date_of_birth,occupation,role,important) values (1, 59,'Eva','Tesi','eva.tesi@dupid.cf','(611) 955-4652','1971-08-08','Land Management Forester','Worker',false) on conflict do nothing;
60 | insert into person(version, id,first_name,last_name,email,phone,date_of_birth,occupation,role,important) values (1, 60,'Augusta','Sakai','augusta.sakai@comouc.ee','(940) 714-8088','1936-05-15','Digital Proofing and Platemaker','Worker',false) on conflict do nothing;
61 | insert into person(version, id,first_name,last_name,email,phone,date_of_birth,occupation,role,important) values (1, 61,'Mathilda','Schwarz','mathilda.schwarz@igunisi.ao','(868) 481-5125','1941-09-08','Public Health Veterinarian','Manager',false) on conflict do nothing;
62 | insert into person(version, id,first_name,last_name,email,phone,date_of_birth,occupation,role,important) values (1, 62,'Joe','Riley','joe.riley@pe.vu','(225) 395-2772','2017-02-16','Statement Processor','External',true) on conflict do nothing;
63 | insert into person(version, id,first_name,last_name,email,phone,date_of_birth,occupation,role,important) values (1, 63,'Leon','McGee','leon.mcgee@puk.se','(365) 837-6888','2010-12-03','Computer Applications Developer','Worker',false) on conflict do nothing;
64 | insert into person(version, id,first_name,last_name,email,phone,date_of_birth,occupation,role,important) values (1, 64,'Florence','Viviani','florence.viviani@vegub.no','(606) 352-8734','1969-09-25','African History Professor','Supervisor',false) on conflict do nothing;
65 | insert into person(version, id,first_name,last_name,email,phone,date_of_birth,occupation,role,important) values (1, 65,'Lee','Miceli','lee.miceli@rucwi.pf','(555) 800-7339','1981-12-29','Gastroenterology Professor','Manager',false) on conflict do nothing;
66 | insert into person(version, id,first_name,last_name,email,phone,date_of_birth,occupation,role,important) values (1, 66,'Celia','Sodi','celia.sodi@agijit.iq','(657) 357-3671','1972-10-21','Laboratory Animal Caretaker','External',false) on conflict do nothing;
67 | insert into person(version, id,first_name,last_name,email,phone,date_of_birth,occupation,role,important) values (1, 67,'Aaron','Misuri','aaron.misuri@loolu.lu','(523) 789-5485','1981-09-22','Water Pump Installer','Worker',false) on conflict do nothing;
68 | insert into person(version, id,first_name,last_name,email,phone,date_of_birth,occupation,role,important) values (1, 68,'Fanny','Parkinson','fanny.parkinson@tupwovali.cw','(766) 966-7387','1949-07-04','Orthopedic Cast Specialist','Supervisor',false) on conflict do nothing;
69 | insert into person(version, id,first_name,last_name,email,phone,date_of_birth,occupation,role,important) values (1, 69,'Phoebe','Vitale','phoebe.vitale@hidge.fo','(672) 613-2954','1969-11-04','Budget Coordinator','Manager',false) on conflict do nothing;
70 | insert into person(version, id,first_name,last_name,email,phone,date_of_birth,occupation,role,important) values (1, 70,'Edith','Brennan','edith.brennan@liowci.ir','(803) 549-9387','1948-04-15','Railroad Engineer','Manager',false) on conflict do nothing;
71 | insert into person(version, id,first_name,last_name,email,phone,date_of_birth,occupation,role,important) values (1, 71,'Jeremy','Marilli','jeremy.marilli@vesa.pf','(526) 435-1819','1983-10-19','Chief Projectionist','External',false) on conflict do nothing;
72 | insert into person(version, id,first_name,last_name,email,phone,date_of_birth,occupation,role,important) values (1, 72,'Kathryn','Huet','kathryn.huet@wupikdoh.by','(937) 855-5936','1927-04-07','Telecasting Engineer','Worker',false) on conflict do nothing;
73 | insert into person(version, id,first_name,last_name,email,phone,date_of_birth,occupation,role,important) values (1, 73,'Lelia','Matsuo','lelia.matsuo@dajsiphaj.az','(960) 335-6192','1935-04-18','Drama Therapist','Supervisor',false) on conflict do nothing;
74 | insert into person(version, id,first_name,last_name,email,phone,date_of_birth,occupation,role,important) values (1, 74,'Virginia','Woods','virginia.woods@soofpe.ht','(735) 809-2611','1955-01-24','General Superintendent','Manager',false) on conflict do nothing;
75 | insert into person(version, id,first_name,last_name,email,phone,date_of_birth,occupation,role,important) values (1, 75,'Sally','Aoki','sally.aoki@aruzusjas.tc','(857) 797-7918','1937-03-27','Technical Communicator','External',false) on conflict do nothing;
76 | insert into person(version, id,first_name,last_name,email,phone,date_of_birth,occupation,role,important) values (1, 76,'Isabelle','de Ridder','isabelle.deridder@ufeco.in','(659) 331-1543','1963-01-11','Leisure Studies Professor','Worker',false) on conflict do nothing;
77 | insert into person(version, id,first_name,last_name,email,phone,date_of_birth,occupation,role,important) values (1, 77,'Rosie','Murphy','rosie.murphy@uneehi.id','(759) 639-8597','1958-07-14','Air Conditioning Service Technician','Supervisor',false) on conflict do nothing;
78 | insert into person(version, id,first_name,last_name,email,phone,date_of_birth,occupation,role,important) values (1, 78,'Lou','Meyer','lou.meyer@hahinaba.gm','(942) 352-4854','1929-04-08','Business Unit Manager','External',false) on conflict do nothing;
79 | insert into person(version, id,first_name,last_name,email,phone,date_of_birth,occupation,role,important) values (1, 79,'Rodney','Love','rodney.love@zun.ph','(247) 867-8287','2012-08-02','Job Estimator','Worker',false) on conflict do nothing;
80 | insert into person(version, id,first_name,last_name,email,phone,date_of_birth,occupation,role,important) values (1, 80,'Kenneth','Bianchini','kenneth.bianchini@jo.ws','(302) 793-9936','2001-07-25','Correctional Officer Sergeant','Worker',false) on conflict do nothing;
81 | insert into person(version, id,first_name,last_name,email,phone,date_of_birth,occupation,role,important) values (1, 81,'Essie','Dietrich','essie.dietrich@goltuefo.mn','(861) 740-6628','1939-03-27','Parking Meter Collector','Supervisor',false) on conflict do nothing;
82 | insert into person(version, id,first_name,last_name,email,phone,date_of_birth,occupation,role,important) values (1, 82,'Leila','Simon','leila.simon@lupuwuzo.gw','(953) 866-9992','1930-12-22','Signals Intelligence/Electronic Warfare Chief','External',false) on conflict do nothing;
83 | insert into person(version, id,first_name,last_name,email,phone,date_of_birth,occupation,role,important) values (1, 83,'Eva','Pierre','eva.pierre@reduzris.ee','(915) 491-8384','1928-01-03','Clinical Psychiatrist','Worker',false) on conflict do nothing;
84 | insert into person(version, id,first_name,last_name,email,phone,date_of_birth,occupation,role,important) values (1, 84,'Landon','Moretti','landon.moretti@pubsav.sk','(584) 909-6235','1981-04-07','Wildlife Officer','Supervisor',false) on conflict do nothing;
85 | insert into person(version, id,first_name,last_name,email,phone,date_of_birth,occupation,role,important) values (1, 85,'Mittie','Sardi','mittie.sardi@lullip.nf','(673) 849-4256','1973-09-15','Food Chemist','Manager',false) on conflict do nothing;
86 | insert into person(version, id,first_name,last_name,email,phone,date_of_birth,occupation,role,important) values (1, 86,'Corey','McDaniel','corey.mcdaniel@aba.tc','(268) 208-9643','2012-11-19','Advanced Foreign Counterintelligence Specialist (Afcs)','Supervisor',false) on conflict do nothing;
87 | insert into person(version, id,first_name,last_name,email,phone,date_of_birth,occupation,role,important) values (1, 87,'Hester','Stein','hester.stein@kettujwo.eu','(873) 489-6641','1939-09-12','Forest Fire Officer','Manager',false) on conflict do nothing;
88 | insert into person(version, id,first_name,last_name,email,phone,date_of_birth,occupation,role,important) values (1, 88,'Danny','Lowe','danny.lowe@ju.sd','(243) 974-5539','2013-12-25','Catalogue Illustrator','External',true) on conflict do nothing;
89 | insert into person(version, id,first_name,last_name,email,phone,date_of_birth,occupation,role,important) values (1, 89,'Lillie','Winter','lillie.winter@vioburez.vi','(816) 699-1291','1945-11-19','Knife Grinder','Worker',false) on conflict do nothing;
90 | insert into person(version, id,first_name,last_name,email,phone,date_of_birth,occupation,role,important) values (1, 90,'Brandon','Borchi','brandon.borchi@ig.al','(319) 401-1090','2000-09-10','Neuropsychiatrist','Supervisor',false) on conflict do nothing;
91 | insert into person(version, id,first_name,last_name,email,phone,date_of_birth,occupation,role,important) values (1, 91,'Isaac','Bernardi','isaac.bernardi@omu.bj','(359) 691-6408','2002-02-21','Bottle Packer','Manager',false) on conflict do nothing;
92 | insert into person(version, id,first_name,last_name,email,phone,date_of_birth,occupation,role,important) values (1, 92,'Clyde','Crawford','clyde.crawford@luw.dz','(273) 892-4646','2019-02-24','Special Education Kindergarten Teacher','External',true) on conflict do nothing;
93 | insert into person(version, id,first_name,last_name,email,phone,date_of_birth,occupation,role,important) values (1, 93,'Paul','Sherman','paul.sherman@pi.cf','(304) 610-2881','2008-08-02','Commercial Art Instructor','Worker',false) on conflict do nothing;
94 | insert into person(version, id,first_name,last_name,email,phone,date_of_birth,occupation,role,important) values (1, 94,'Craig','Russell','craig.russell@zu.nz','(237) 969-2900','2020-05-30','Ferryboat Captain','Supervisor',true) on conflict do nothing;
95 | insert into person(version, id,first_name,last_name,email,phone,date_of_birth,occupation,role,important) values (1, 95,'John','Sutton','john.sutton@ag.ee','(207) 424-6468','2013-08-11','School Social Worker','Manager',false) on conflict do nothing;
96 | insert into person(version, id,first_name,last_name,email,phone,date_of_birth,occupation,role,important) values (1, 96,'Francisco','Formigli','francisco.formigli@fopav.tn','(481) 661-8179','1990-07-26','Joint Terminal Attack Controller','External',false) on conflict do nothing;
97 | insert into person(version, id,first_name,last_name,email,phone,date_of_birth,occupation,role,important) values (1, 97,'Gary','Baker','gary.baker@ji.cf','(212) 510-3444','2022-08-15','VP Sales','Worker',true) on conflict do nothing;
98 | insert into person(version, id,first_name,last_name,email,phone,date_of_birth,occupation,role,important) values (1, 98,'Earl','Giovannoni','earl.giovannoni@lojet.ge','(433) 862-3076','1987-12-24','Auto Service Station Attendant','Manager',false) on conflict do nothing;
99 | insert into person(version, id,first_name,last_name,email,phone,date_of_birth,occupation,role,important) values (1, 99,'Helen','Zanieri','helen.zanieri@ukve.tn','(619) 506-4452','1969-05-07','Healthcare Social Worker','External',false) on conflict do nothing;
100 | insert into person(version, id,first_name,last_name,email,phone,date_of_birth,occupation,role,important) values (1, 100,'Agnes','Toccafondi','agnes.toccafondi@viipo.ae','(616) 688-6883','1971-05-27','Comedian','External',false) on conflict do nothing;
101 |
102 | insert into "user" (username, first_name, last_name, hashed_password, picture) values ('user','John', 'Normal','$2a$10$xdbKoM48VySZqVSU/cSlVeJn0Z04XCZ7KZBjUBC00eKo5uLswyOpe','\xffd8ffe000104a46494600010101004800480000ffe20c584943435f50524f46494c4500010100000c484c696e6f021000006d6e74725247422058595a2007ce00020009000600310000616373704d5346540000000049454320735247420000000000000000000000000000f6d6000100000000d32d4850202000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000001163707274000001500000003364657363000001840000006c77747074000001f000000014626b707400000204000000147258595a00000218000000146758595a0000022c000000146258595a0000024000000014646d6e640000025400000070646d6464000002c400000088767565640000034c0000008676696577000003d4000000246c756d69000003f8000000146d6561730000040c0000002474656368000004300000000c725452430000043c0000080c675452430000043c0000080c625452430000043c0000080c7465787400000000436f70797269676874202863292031393938204865776c6574742d5061636b61726420436f6d70616e790000646573630000000000000012735247422049454336313936362d322e31000000000000000000000012735247422049454336313936362d322e31000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000058595a20000000000000f35100010000000116cc58595a200000000000000000000000000000000058595a200000000000006fa2000038f50000039058595a2000000000000062990000b785000018da58595a2000000000000024a000000f840000b6cf64657363000000000000001649454320687474703a2f2f7777772e6965632e636800000000000000000000001649454320687474703a2f2f7777772e6965632e63680000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000064657363000000000000002e4945432036313936362d322e312044656661756c742052474220636f6c6f7572207370616365202d207352474200000000000000000000002e4945432036313936362d322e312044656661756c742052474220636f6c6f7572207370616365202d20735247420000000000000000000000000000000000000000000064657363000000000000002c5265666572656e63652056696577696e6720436f6e646974696f6e20696e2049454336313936362d322e3100000000000000000000002c5265666572656e63652056696577696e6720436f6e646974696f6e20696e2049454336313936362d322e31000000000000000000000000000000000000000000000000000076696577000000000013a4fe00145f2e0010cf140003edcc0004130b00035c9e0000000158595a2000000000004c09560050000000571fe76d6561730000000000000001000000000000000000000000000000000000028f0000000273696720000000004352542063757276000000000000040000000005000a000f00140019001e00230028002d00320037003b00400045004a004f00540059005e00630068006d00720077007c00810086008b00900095009a009f00a400a900ae00b200b700bc00c100c600cb00d000d500db00e000e500eb00f000f600fb01010107010d01130119011f0125012b01320138013e0145014c0152015901600167016e0175017c0183018b0192019a01a101a901b101b901c101c901d101d901e101e901f201fa0203020c0214021d0226022f02380241024b0254025d02670271027a0284028e029802a202ac02b602c102cb02d502e002eb02f50300030b03160321032d03380343034f035a03660372037e038a039603a203ae03ba03c703d303e003ec03f9040604130420042d043b0448045504630471047e048c049a04a804b604c404d304e104f004fe050d051c052b053a05490558056705770586059605a605b505c505d505e505f6060606160627063706480659066a067b068c069d06af06c006d106e306f507070719072b073d074f076107740786079907ac07bf07d207e507f8080b081f08320846085a086e0882089608aa08be08d208e708fb09100925093a094f09640979098f09a409ba09cf09e509fb0a110a270a3d0a540a6a0a810a980aae0ac50adc0af30b0b0b220b390b510b690b800b980bb00bc80be10bf90c120c2a0c430c5c0c750c8e0ca70cc00cd90cf30d0d0d260d400d5a0d740d8e0da90dc30dde0df80e130e2e0e490e640e7f0e9b0eb60ed20eee0f090f250f410f5e0f7a0f960fb30fcf0fec1009102610431061107e109b10b910d710f511131131114f116d118c11aa11c911e81207122612451264128412a312c312e31303132313431363138313a413c513e5140614271449146a148b14ad14ce14f01512153415561578159b15bd15e0160316261649166c168f16b216d616fa171d17411765178917ae17d217f7181b18401865188a18af18d518fa19201945196b199119b719dd1a041a2a1a511a771a9e1ac51aec1b141b3b1b631b8a1bb21bda1c021c2a1c521c7b1ca31ccc1cf51d1e1d471d701d991dc31dec1e161e401e6a1e941ebe1ee91f131f3e1f691f941fbf1fea20152041206c209820c420f0211c2148217521a121ce21fb22272255228222af22dd230a23382366239423c223f0241f244d247c24ab24da250925382568259725c725f726272657268726b726e827182749277a27ab27dc280d283f287128a228d429062938296b299d29d02a022a352a682a9b2acf2b022b362b692b9d2bd12c052c392c6e2ca22cd72d0c2d412d762dab2de12e162e4c2e822eb72eee2f242f5a2f912fc72ffe3035306c30a430db3112314a318231ba31f2322a3263329b32d4330d3346337f33b833f1342b3465349e34d83513354d358735c235fd3637367236ae36e937243760379c37d738143850388c38c839053942397f39bc39f93a363a743ab23aef3b2d3b6b3baa3be83c273c653ca43ce33d223d613da13de03e203e603ea03ee03f213f613fa23fe24023406440a640e74129416a41ac41ee4230427242b542f7433a437d43c044034447448a44ce45124555459a45de4622466746ab46f04735477b47c04805484b489148d7491d496349a949f04a374a7d4ac44b0c4b534b9a4be24c2a4c724cba4d024d4a4d934ddc4e254e6e4eb74f004f494f934fdd5027507150bb51065150519b51e65231527c52c75313535f53aa53f65442548f54db5528557555c2560f565c56a956f75744579257e0582f587d58cb591a596959b85a075a565aa65af55b455b955be55c355c865cd65d275d785dc95e1a5e6c5ebd5f0f5f615fb36005605760aa60fc614f61a261f56249629c62f06343639763eb6440649464e9653d659265e7663d669266e8673d679367e9683f689668ec6943699a69f16a486a9f6af76b4f6ba76bff6c576caf6d086d606db96e126e6b6ec46f1e6f786fd1702b708670e0713a719571f0724b72a67301735d73b87414747074cc7528758575e1763e769b76f8775677b37811786e78cc792a798979e77a467aa57b047b637bc27c217c817ce17d417da17e017e627ec27f237f847fe5804780a8810a816b81cd8230829282f4835783ba841d848084e3854785ab860e867286d7873b879f8804886988ce8933899989fe8a648aca8b308b968bfc8c638cca8d318d988dff8e668ece8f368f9e9006906e90d6913f91a89211927a92e3934d93b69420948a94f4955f95c99634969f970a977597e0984c98b89924999099fc9a689ad59b429baf9c1c9c899cf79d649dd29e409eae9f1d9f8b9ffaa069a0d8a147a1b6a226a296a306a376a3e6a456a4c7a538a5a9a61aa68ba6fda76ea7e0a852a8c4a937a9a9aa1caa8fab02ab75abe9ac5cacd0ad44adb8ae2daea1af16af8bb000b075b0eab160b1d6b24bb2c2b338b3aeb425b49cb513b58ab601b679b6f0b768b7e0b859b8d1b94ab9c2ba3bbab5bb2ebba7bc21bc9bbd15bd8fbe0abe84beffbf7abff5c070c0ecc167c1e3c25fc2dbc358c3d4c451c4cec54bc5c8c646c6c3c741c7bfc83dc8bcc93ac9b9ca38cab7cb36cbb6cc35ccb5cd35cdb5ce36ceb6cf37cfb8d039d0bad13cd1bed23fd2c1d344d3c6d449d4cbd54ed5d1d655d6d8d75cd7e0d864d8e8d96cd9f1da76dafbdb80dc05dc8add10dd96de1cdea2df29dfafe036e0bde144e1cce253e2dbe363e3ebe473e4fce584e60de696e71fe7a9e832e8bce946e9d0ea5beae5eb70ebfbec86ed11ed9cee28eeb4ef40efccf058f0e5f172f1fff28cf319f3a7f434f4c2f550f5def66df6fbf78af819f8a8f938f9c7fa57fae7fb77fc07fc98fd29fdbafe4bfedcff6dffffffdb004300090606080605090807080a09090a0d160e0d0c0c0d1a131410161f1c21201f1c1e1e2327322a23252f251e1e2b3b2c2f3335383838212a3d413c364132373835ffdb004301090a0a0d0b0d190e0e1935241e243535353535353535353535353535353535353535353535353535353535353535353535353535353535353535353535353535ffc0001108001a001a03012200021101031101ffc4001a000002020300000000000000000000000005060107030408ffc4002e10000103020404030901000000000000000102030400110506122113314161075171151623243234425263d1ffc40017010101010100000000000000000000000001030002ffc4001a110003000301000000000000000000000000010203122131ffda000c03010002110311003f00796d8bf4a0f27376090a7ae2bf21c41697c371ee02cb28579172da47973b5e995b481b9e94baec44c9c36630f14f001712b6cb62ca049ff68c95a2298637e0614ca4a2e2c41dc11c8d63e0d46070bd9d97e1425385d547610d6b50dd5615b5a3b5764d8b39c33fc7cb0fa61351d52e6adbd653ab4a1b04eda8f7b1e40d2546f112ecc97a74171f9ab376d285da3a7b904f4f3b1268467d5139f315b927e327aff3450547d07d6b54aaf422dcf516165df1516db8db38fb2d9413a4cb676d3dd483d3d0d59a14149052a4949dc1bd736382e8503cad56b6013247bb9877cc3bf6ad7e67f414a03fffd9') on conflict do nothing;
103 | insert into "user" (username, first_name, last_name, hashed_password, picture) values ('admin','Emma', 'Powerful','$2a$10$jpLNVNeA7Ar/ZQ2DKbKCm.MuT2ESe.Qop96jipKMq7RaUgCoQedV.','\xffd8ffe000104a46494600010101004800480000ffe20c584943435f50524f46494c4500010100000c484c696e6f021000006d6e74725247422058595a2007ce00020009000600310000616373704d5346540000000049454320735247420000000000000000000000000000f6d6000100000000d32d4850202000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000001163707274000001500000003364657363000001840000006c77747074000001f000000014626b707400000204000000147258595a00000218000000146758595a0000022c000000146258595a0000024000000014646d6e640000025400000070646d6464000002c400000088767565640000034c0000008676696577000003d4000000246c756d69000003f8000000146d6561730000040c0000002474656368000004300000000c725452430000043c0000080c675452430000043c0000080c625452430000043c0000080c7465787400000000436f70797269676874202863292031393938204865776c6574742d5061636b61726420436f6d70616e790000646573630000000000000012735247422049454336313936362d322e31000000000000000000000012735247422049454336313936362d322e31000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000058595a20000000000000f35100010000000116cc58595a200000000000000000000000000000000058595a200000000000006fa2000038f50000039058595a2000000000000062990000b785000018da58595a2000000000000024a000000f840000b6cf64657363000000000000001649454320687474703a2f2f7777772e6965632e636800000000000000000000001649454320687474703a2f2f7777772e6965632e63680000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000064657363000000000000002e4945432036313936362d322e312044656661756c742052474220636f6c6f7572207370616365202d207352474200000000000000000000002e4945432036313936362d322e312044656661756c742052474220636f6c6f7572207370616365202d20735247420000000000000000000000000000000000000000000064657363000000000000002c5265666572656e63652056696577696e6720436f6e646974696f6e20696e2049454336313936362d322e3100000000000000000000002c5265666572656e63652056696577696e6720436f6e646974696f6e20696e2049454336313936362d322e31000000000000000000000000000000000000000000000000000076696577000000000013a4fe00145f2e0010cf140003edcc0004130b00035c9e0000000158595a2000000000004c09560050000000571fe76d6561730000000000000001000000000000000000000000000000000000028f0000000273696720000000004352542063757276000000000000040000000005000a000f00140019001e00230028002d00320037003b00400045004a004f00540059005e00630068006d00720077007c00810086008b00900095009a009f00a400a900ae00b200b700bc00c100c600cb00d000d500db00e000e500eb00f000f600fb01010107010d01130119011f0125012b01320138013e0145014c0152015901600167016e0175017c0183018b0192019a01a101a901b101b901c101c901d101d901e101e901f201fa0203020c0214021d0226022f02380241024b0254025d02670271027a0284028e029802a202ac02b602c102cb02d502e002eb02f50300030b03160321032d03380343034f035a03660372037e038a039603a203ae03ba03c703d303e003ec03f9040604130420042d043b0448045504630471047e048c049a04a804b604c404d304e104f004fe050d051c052b053a05490558056705770586059605a605b505c505d505e505f6060606160627063706480659066a067b068c069d06af06c006d106e306f507070719072b073d074f076107740786079907ac07bf07d207e507f8080b081f08320846085a086e0882089608aa08be08d208e708fb09100925093a094f09640979098f09a409ba09cf09e509fb0a110a270a3d0a540a6a0a810a980aae0ac50adc0af30b0b0b220b390b510b690b800b980bb00bc80be10bf90c120c2a0c430c5c0c750c8e0ca70cc00cd90cf30d0d0d260d400d5a0d740d8e0da90dc30dde0df80e130e2e0e490e640e7f0e9b0eb60ed20eee0f090f250f410f5e0f7a0f960fb30fcf0fec1009102610431061107e109b10b910d710f511131131114f116d118c11aa11c911e81207122612451264128412a312c312e31303132313431363138313a413c513e5140614271449146a148b14ad14ce14f01512153415561578159b15bd15e0160316261649166c168f16b216d616fa171d17411765178917ae17d217f7181b18401865188a18af18d518fa19201945196b199119b719dd1a041a2a1a511a771a9e1ac51aec1b141b3b1b631b8a1bb21bda1c021c2a1c521c7b1ca31ccc1cf51d1e1d471d701d991dc31dec1e161e401e6a1e941ebe1ee91f131f3e1f691f941fbf1fea20152041206c209820c420f0211c2148217521a121ce21fb22272255228222af22dd230a23382366239423c223f0241f244d247c24ab24da250925382568259725c725f726272657268726b726e827182749277a27ab27dc280d283f287128a228d429062938296b299d29d02a022a352a682a9b2acf2b022b362b692b9d2bd12c052c392c6e2ca22cd72d0c2d412d762dab2de12e162e4c2e822eb72eee2f242f5a2f912fc72ffe3035306c30a430db3112314a318231ba31f2322a3263329b32d4330d3346337f33b833f1342b3465349e34d83513354d358735c235fd3637367236ae36e937243760379c37d738143850388c38c839053942397f39bc39f93a363a743ab23aef3b2d3b6b3baa3be83c273c653ca43ce33d223d613da13de03e203e603ea03ee03f213f613fa23fe24023406440a640e74129416a41ac41ee4230427242b542f7433a437d43c044034447448a44ce45124555459a45de4622466746ab46f04735477b47c04805484b489148d7491d496349a949f04a374a7d4ac44b0c4b534b9a4be24c2a4c724cba4d024d4a4d934ddc4e254e6e4eb74f004f494f934fdd5027507150bb51065150519b51e65231527c52c75313535f53aa53f65442548f54db5528557555c2560f565c56a956f75744579257e0582f587d58cb591a596959b85a075a565aa65af55b455b955be55c355c865cd65d275d785dc95e1a5e6c5ebd5f0f5f615fb36005605760aa60fc614f61a261f56249629c62f06343639763eb6440649464e9653d659265e7663d669266e8673d679367e9683f689668ec6943699a69f16a486a9f6af76b4f6ba76bff6c576caf6d086d606db96e126e6b6ec46f1e6f786fd1702b708670e0713a719571f0724b72a67301735d73b87414747074cc7528758575e1763e769b76f8775677b37811786e78cc792a798979e77a467aa57b047b637bc27c217c817ce17d417da17e017e627ec27f237f847fe5804780a8810a816b81cd8230829282f4835783ba841d848084e3854785ab860e867286d7873b879f8804886988ce8933899989fe8a648aca8b308b968bfc8c638cca8d318d988dff8e668ece8f368f9e9006906e90d6913f91a89211927a92e3934d93b69420948a94f4955f95c99634969f970a977597e0984c98b89924999099fc9a689ad59b429baf9c1c9c899cf79d649dd29e409eae9f1d9f8b9ffaa069a0d8a147a1b6a226a296a306a376a3e6a456a4c7a538a5a9a61aa68ba6fda76ea7e0a852a8c4a937a9a9aa1caa8fab02ab75abe9ac5cacd0ad44adb8ae2daea1af16af8bb000b075b0eab160b1d6b24bb2c2b338b3aeb425b49cb513b58ab601b679b6f0b768b7e0b859b8d1b94ab9c2ba3bbab5bb2ebba7bc21bc9bbd15bd8fbe0abe84beffbf7abff5c070c0ecc167c1e3c25fc2dbc358c3d4c451c4cec54bc5c8c646c6c3c741c7bfc83dc8bcc93ac9b9ca38cab7cb36cbb6cc35ccb5cd35cdb5ce36ceb6cf37cfb8d039d0bad13cd1bed23fd2c1d344d3c6d449d4cbd54ed5d1d655d6d8d75cd7e0d864d8e8d96cd9f1da76dafbdb80dc05dc8add10dd96de1cdea2df29dfafe036e0bde144e1cce253e2dbe363e3ebe473e4fce584e60de696e71fe7a9e832e8bce946e9d0ea5beae5eb70ebfbec86ed11ed9cee28eeb4ef40efccf058f0e5f172f1fff28cf319f3a7f434f4c2f550f5def66df6fbf78af819f8a8f938f9c7fa57fae7fb77fc07fc98fd29fdbafe4bfedcff6dffffffdb004300090606080605090807080a09090a0d160e0d0c0c0d1a131410161f1c21201f1c1e1e2327322a23252f251e1e2b3b2c2f3335383838212a3d413c364132373835ffdb004301090a0a0d0b0d190e0e1935241e243535353535353535353535353535353535353535353535353535353535353535353535353535353535353535353535353535ffc0001108001a001a03012200021101031101ffc400190000020301000000000000000000000000040500020706ffc4002b100001030302050109000000000000000001020311000405061213213141517314152233376182a2b1ffc400160101010100000000000000000000000000020103ffc4001a110003010101010000000000000000000000010221031112ffda000c03010002110311003f00ccd02051d7f84c958636defae2dcb76f7090b6d532483d2476a0c2429241e7222b43ba2de574f58a5e7dd71a36282d927e127985a48fb181e68f4af9487ce15b68ce1077a4cf51536d428e15c2900c804a41ec62ad14d699bc1f6274c3d91c35c655e743166cee4a206e5bcb1d93e04f2934d34d241d0d93f783894b4d3db994adcd813b809dbf9f9eb4ef1ff4cf17e8b47f6a49af9b43790b1421094a38cb3b408131d6b57cd35a49b72f0e598c7a9ec81b70a0a093f311cc19ee0d303a695b8c5d263d334669648f666390ea3fa69dc0f15661781aa7e9ffd9') on conflict do nothing;
104 |
105 | insert into user_role (username, role) values ('user', 'USER') on conflict do nothing;
106 | insert into user_role (username, role) values ('admin', 'USER') on conflict do nothing;
107 | insert into user_role (username, role) values ('admin', 'ADMIN') on conflict do nothing;
108 |
--------------------------------------------------------------------------------