aboutsummaryrefslogtreecommitdiffstats
path: root/src/main
diff options
context:
space:
mode:
Diffstat (limited to 'src/main')
-rw-r--r--src/main/java/org/berzerkula/builddb/BuilddbApplication.java19
-rw-r--r--src/main/java/org/berzerkula/builddb/config/SecurityConfig.java79
-rw-r--r--src/main/java/org/berzerkula/builddb/controllers/AccountController.java141
-rw-r--r--src/main/java/org/berzerkula/builddb/controllers/DashboardController.java27
-rw-r--r--src/main/java/org/berzerkula/builddb/controllers/HomeController.java47
-rw-r--r--src/main/java/org/berzerkula/builddb/controllers/PkgController.java169
-rw-r--r--src/main/java/org/berzerkula/builddb/models/AppUser.java107
-rw-r--r--src/main/java/org/berzerkula/builddb/models/Pkg.java118
-rw-r--r--src/main/java/org/berzerkula/builddb/models/PkgDto.java104
-rw-r--r--src/main/java/org/berzerkula/builddb/models/RegisterDto.java84
-rw-r--r--src/main/java/org/berzerkula/builddb/repositories/AppUserRepository.java10
-rw-r--r--src/main/java/org/berzerkula/builddb/repositories/PkgRepository.java8
-rw-r--r--src/main/java/org/berzerkula/builddb/services/AppUserService.java36
-rw-r--r--src/main/resources/application.properties15
-rw-r--r--src/main/resources/templates/actuatorDashboard.html39
-rw-r--r--src/main/resources/templates/admin.html53
-rw-r--r--src/main/resources/templates/client.html53
-rw-r--r--src/main/resources/templates/contact.html53
-rw-r--r--src/main/resources/templates/index.html37
-rw-r--r--src/main/resources/templates/login.html87
-rw-r--r--src/main/resources/templates/navbar.html77
-rw-r--r--src/main/resources/templates/pkgs/add.html109
-rw-r--r--src/main/resources/templates/pkgs/edit.html109
-rw-r--r--src/main/resources/templates/pkgs/index.html62
-rw-r--r--src/main/resources/templates/pkgs/sorting.html6
-rw-r--r--src/main/resources/templates/profile.html84
-rw-r--r--src/main/resources/templates/register.html155
-rw-r--r--src/main/resources/templates/user.html53
28 files changed, 1941 insertions, 0 deletions
diff --git a/src/main/java/org/berzerkula/builddb/BuilddbApplication.java b/src/main/java/org/berzerkula/builddb/BuilddbApplication.java
new file mode 100644
index 0000000..7ab9042
--- /dev/null
+++ b/src/main/java/org/berzerkula/builddb/BuilddbApplication.java
@@ -0,0 +1,19 @@
+package org.berzerkula.builddb;
+
+import com.ulisesbocchio.jasyptspringboot.annotation.EnableEncryptableProperties;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+import org.springframework.boot.SpringApplication;
+import org.springframework.boot.autoconfigure.SpringBootApplication;
+
+@EnableEncryptableProperties
+@SpringBootApplication
+public class BuilddbApplication {
+
+ private static final Logger logger = LoggerFactory.getLogger(BuilddbApplication.class);
+
+ public static void main(String[] args) {
+ SpringApplication.run(BuilddbApplication.class, args);
+ }
+
+}
diff --git a/src/main/java/org/berzerkula/builddb/config/SecurityConfig.java b/src/main/java/org/berzerkula/builddb/config/SecurityConfig.java
new file mode 100644
index 0000000..dbaacd5
--- /dev/null
+++ b/src/main/java/org/berzerkula/builddb/config/SecurityConfig.java
@@ -0,0 +1,79 @@
+package org.berzerkula.builddb.config;
+
+import org.springframework.context.annotation.Bean;
+import org.springframework.context.annotation.Configuration;
+import org.springframework.security.config.annotation.method.configuration.EnableMethodSecurity;
+import org.springframework.security.config.annotation.web.builders.HttpSecurity;
+import org.springframework.security.config.annotation.web.configuration.EnableWebSecurity;
+import org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder;
+import org.springframework.security.crypto.password.PasswordEncoder;
+import org.springframework.security.web.SecurityFilterChain;
+
+@Configuration
+@EnableWebSecurity
+@EnableMethodSecurity
+public class SecurityConfig {
+
+ @Bean
+ public SecurityFilterChain securityFilterChain(HttpSecurity http) throws Exception {
+ return http
+ .authorizeHttpRequests( auth -> auth
+ .requestMatchers("/").permitAll()
+ .requestMatchers("/actuator/**").hasRole("admin")
+ .requestMatchers("/env/**").hasRole("admin")
+ .requestMatchers("/health/**").hasRole("admin")
+ .requestMatchers("/info/**").hasRole("admin")
+ .requestMatchers("/contact").permitAll()
+ .requestMatchers("/pkgs/**").hasRole("client")
+ .requestMatchers("/register").permitAll()
+ .requestMatchers("/login").permitAll()
+ .requestMatchers("/logout").permitAll()
+ .anyRequest().authenticated()
+ )
+ .formLogin(form -> form
+ .loginPage("/login")
+ .usernameParameter("email")
+ .passwordParameter("password")
+ .defaultSuccessUrl("/", true)
+ )
+ .logout(config -> config.logoutSuccessUrl("/"))
+ .build();
+ }
+
+
+ @Bean
+ public PasswordEncoder passwordEncoder() {
+ return new BCryptPasswordEncoder();
+ }
+}
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/src/main/java/org/berzerkula/builddb/controllers/AccountController.java b/src/main/java/org/berzerkula/builddb/controllers/AccountController.java
new file mode 100644
index 0000000..6cec175
--- /dev/null
+++ b/src/main/java/org/berzerkula/builddb/controllers/AccountController.java
@@ -0,0 +1,141 @@
+package org.berzerkula.builddb.controllers;
+
+import java.util.Date;
+
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.security.core.Authentication;
+import org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder;
+import org.springframework.stereotype.Controller;
+import org.springframework.ui.Model;
+import org.springframework.validation.BindingResult;
+import org.springframework.validation.FieldError;
+import org.springframework.web.bind.annotation.GetMapping;
+import org.springframework.web.bind.annotation.ModelAttribute;
+import org.springframework.web.bind.annotation.PostMapping;
+
+import org.berzerkula.builddb.models.AppUser;
+import org.berzerkula.builddb.models.RegisterDto;
+import org.berzerkula.builddb.repositories.AppUserRepository;
+
+import jakarta.validation.Valid;
+
+@Controller
+public class AccountController {
+
+ @Autowired
+ private AppUserRepository repo;
+
+ @GetMapping("/profile")
+ public String profile(Authentication auth, Model model) {
+ AppUser user = repo.findByEmail(auth.getName());
+ model.addAttribute("appUser", user);
+
+ return "profile";
+ }
+
+ @GetMapping("/login")
+ public String login() {
+ return "login";
+ }
+
+ @GetMapping("/register")
+ public String register(Model model) {
+ RegisterDto registerDto = new RegisterDto();
+ model.addAttribute(registerDto);
+ model.addAttribute("success", false);
+ return "register";
+ }
+
+ @PostMapping("/register")
+ public String register(
+ Model model,
+ @Valid @ModelAttribute RegisterDto registerDto,
+ BindingResult result
+ ) {
+
+ if (!registerDto.getPassword().equals(registerDto.getConfirmPassword())) {
+ result.addError(
+ new FieldError("registerDto", "confirmPassword"
+ , "Password and Confirm Password do not match")
+ );
+ }
+
+
+ AppUser appUser = repo.findByEmail(registerDto.getEmail());
+ if (appUser != null) {
+ result.addError(
+ new FieldError("registerDto", "email"
+ , "Email address is already used")
+ );
+ }
+
+
+ if (result.hasErrors()) {
+ return "register";
+ }
+
+
+ try {
+ // create new account
+ var bCryptEncoder = new BCryptPasswordEncoder();
+
+
+ AppUser newUser = new AppUser();
+ newUser.setFirstName(registerDto.getFirstName());
+ newUser.setLastName(registerDto.getLastName());
+ newUser.setEmail(registerDto.getEmail());
+ newUser.setPhone(registerDto.getPhone());
+ newUser.setAddress(registerDto.getAddress());
+ newUser.setRole("client");
+ newUser.setCreatedAt(new Date());
+ newUser.setPassword(bCryptEncoder.encode(registerDto.getPassword()));
+
+ repo.save(newUser);
+
+
+ model.addAttribute("registerDto", new RegisterDto());
+ model.addAttribute("success", true);
+ }
+ catch(Exception ex) {
+ result.addError(
+ new FieldError("registerDto", "firstName"
+ , ex.getMessage())
+ );
+ }
+
+ return "register";
+ }
+}
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/src/main/java/org/berzerkula/builddb/controllers/DashboardController.java b/src/main/java/org/berzerkula/builddb/controllers/DashboardController.java
new file mode 100644
index 0000000..e928312
--- /dev/null
+++ b/src/main/java/org/berzerkula/builddb/controllers/DashboardController.java
@@ -0,0 +1,27 @@
+package org.berzerkula.builddb.controllers;
+
+import org.springframework.stereotype.Controller;
+import org.springframework.web.bind.annotation.GetMapping;
+
+@Controller
+public class DashboardController {
+
+ @GetMapping("/user")
+ public String userDashboard() {
+ return "user";
+ }
+
+ @GetMapping("/client")
+ public String clientDashboard() {
+ return "client";
+ }
+
+ @GetMapping("/admin")
+ public String adminDashboard() {
+ return "admin";
+ }
+
+ @GetMapping("/actuatorDashboard")
+ public String actuatorDashboard() { return "actuatorDashboard"; }
+
+}
diff --git a/src/main/java/org/berzerkula/builddb/controllers/HomeController.java b/src/main/java/org/berzerkula/builddb/controllers/HomeController.java
new file mode 100644
index 0000000..ad171c8
--- /dev/null
+++ b/src/main/java/org/berzerkula/builddb/controllers/HomeController.java
@@ -0,0 +1,47 @@
+package org.berzerkula.builddb.controllers;
+
+import org.springframework.stereotype.Controller;
+import org.springframework.web.bind.annotation.GetMapping;
+
+@Controller
+public class HomeController {
+
+ @GetMapping("/contact")
+ public String contact() {
+ return "contact";
+ }
+
+}
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/src/main/java/org/berzerkula/builddb/controllers/PkgController.java b/src/main/java/org/berzerkula/builddb/controllers/PkgController.java
new file mode 100644
index 0000000..404a58b
--- /dev/null
+++ b/src/main/java/org/berzerkula/builddb/controllers/PkgController.java
@@ -0,0 +1,169 @@
+package org.berzerkula.builddb.controllers;
+
+
+import jakarta.persistence.OrderBy;
+import jakarta.validation.Valid;
+import org.berzerkula.builddb.models.Pkg;
+import org.berzerkula.builddb.models.PkgDto;
+import org.berzerkula.builddb.repositories.PkgRepository;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.data.domain.Sort;
+import org.springframework.stereotype.Controller;
+import org.springframework.ui.Model;
+import org.springframework.validation.BindingResult;
+import org.springframework.web.bind.annotation.*;
+
+import java.util.List;
+import java.util.Objects;
+
+@Controller
+@RequestMapping("/pkgs")
+public class PkgController {
+
+ private static final Logger logger = LoggerFactory.getLogger(PkgController.class);
+
+ @Autowired
+ private PkgRepository repo;
+
+ @GetMapping({"", "/", "/index"})
+ public String showPkgList(Model model,
+ @RequestParam(defaultValue = "sequence,asc") String[] sort,
+ @RequestParam(defaultValue = "") String repotable) {
+ try {
+ String sortField = sort[0];
+ String sortDirection = sort[1];
+
+ Sort.Direction direction = sortDirection.equals("desc") ? Sort.Direction.DESC : Sort.Direction.ASC;
+ Sort.Order order = new Sort.Order(direction, sortField);
+
+ List<Pkg> pkgs = repo.findAll(Sort.by(order));
+
+ model.addAttribute("pkgs", pkgs);
+ model.addAttribute("sortField", sortField);
+ model.addAttribute("sortDirection", sortDirection);
+ model.addAttribute("reverseSortDirection", sortDirection.equals("asc") ? "desc" : "asc");
+
+ } catch (Exception e) {
+ logger.error(e.getMessage());
+ }
+
+ return "pkgs/index";
+ }
+
+ @GetMapping("/add")
+ public String showAddPkgForm(Model model) {
+ PkgDto pkgDto = new PkgDto();
+ model.addAttribute("pkgDto", pkgDto);
+ return "pkgs/add";
+ }
+
+ @PostMapping("/add")
+ public String addPkg(
+ @Valid
+ @ModelAttribute
+ PkgDto pkgDto, BindingResult result ) {
+
+ if (result.hasErrors()) {
+ return "pkgs/add";
+ }
+
+ try {
+
+ Pkg pkg = new Pkg();
+ pkg.setSequence(pkgDto.getSequence());
+ pkg.setName(pkgDto.getName());
+ pkg.setVersion(pkgDto.getVersion());
+ pkg.setConfigure(pkgDto.getConfigure());
+ pkg.setBuild(pkgDto.getBuild());
+ pkg.setInstall(pkgDto.getInstall());
+ pkg.setSetup(pkgDto.getSetup());
+ pkg.setNotes(pkgDto.getNotes());
+ pkg.setUrl(pkgDto.getUrl());
+
+ repo.save(pkg);
+
+ } catch (Exception e) {
+ logger.error(e.getMessage());
+ }
+
+ return "redirect:/pkgs";
+ }
+
+ @GetMapping("/edit")
+ public String showEditPkgForm(Model model, @RequestParam int id) {
+
+ try {
+ Pkg pkg = repo.findById(id).get();
+ model.addAttribute("pkg", pkg);
+
+ PkgDto pkgDto = new PkgDto();
+ pkgDto.setSequence(pkg.getSequence());
+ pkgDto.setName(pkg.getName());
+ pkgDto.setVersion(pkg.getVersion());
+ pkgDto.setConfigure(pkg.getConfigure());
+ pkgDto.setBuild(pkg.getBuild());
+ pkgDto.setInstall(pkg.getInstall());
+ pkgDto.setSetup(pkg.getSetup());
+ pkgDto.setNotes(pkg.getNotes());
+ pkgDto.setUrl(pkg.getUrl());
+
+ model.addAttribute("pkgDto", pkgDto);
+
+ } catch(Exception ex) {
+ logger.error("Exception: {}", ex.getMessage());
+ }
+
+ return "pkgs/edit";
+ }
+
+ @PostMapping("/edit")
+ public String editPkg(
+ Model model,
+ @RequestParam int id,
+ @Valid
+ @ModelAttribute PkgDto pkgDto, BindingResult result ) {
+
+ try {
+ Pkg pkg = repo.findById(id).get();
+ model.addAttribute("pkg", pkg);
+
+ if (result.hasErrors()) {
+ return "pkgs/edit";
+ }
+
+ pkg.setSequence(pkgDto.getSequence());
+ pkg.setName(pkgDto.getName());
+ pkg.setVersion(pkgDto.getVersion());
+ pkg.setConfigure(pkgDto.getConfigure());
+ pkg.setBuild(pkgDto.getBuild());
+ pkg.setInstall(pkgDto.getInstall());
+ pkg.setSetup(pkgDto.getSetup());
+ pkg.setNotes(pkgDto.getNotes());
+ pkg.setUrl(pkgDto.getUrl());
+
+ repo.save(pkg);
+
+ } catch(Exception ex) {
+ logger.error("Exception: {}", ex.getMessage());
+ }
+ return "redirect:/pkgs/#" + id;
+
+ }
+
+ @GetMapping("/delete")
+ public String deletePkg(@RequestParam int id) {
+
+ try {
+ Pkg pkg = repo.findById(id).get();
+
+ repo.delete(pkg);
+
+ } catch (Exception ex) {
+ logger.error("Exception: {}", ex.getMessage());
+ }
+
+ return "redirect:/pkgs/";
+ }
+}
diff --git a/src/main/java/org/berzerkula/builddb/models/AppUser.java b/src/main/java/org/berzerkula/builddb/models/AppUser.java
new file mode 100644
index 0000000..21c5a33
--- /dev/null
+++ b/src/main/java/org/berzerkula/builddb/models/AppUser.java
@@ -0,0 +1,107 @@
+package org.berzerkula.builddb.models;
+
+import jakarta.persistence.*;
+
+import java.util.Date;
+
+@Entity
+@Table(name="users")
+public class AppUser {
+
+ @Id
+ @GeneratedValue(strategy=GenerationType.IDENTITY)
+ private int id;
+
+ private String firstName;
+ private String lastName;
+
+ @Column(unique = true, nullable = false)
+ private String email;
+
+ private String phone;
+ private String address;
+ private String password;
+ private String role;
+ private Date createdAt;
+
+ public AppUser(String firstName, String lastName, String email, String phone, String address,
+ String password, String role) {
+ this.firstName = firstName;
+ this.lastName = lastName;
+ this.email = email;
+ this.password = password;
+ this.role = role;
+ this.createdAt = new Date();
+ }
+
+ public AppUser() {}
+
+ public int getId() {
+ return id;
+ }
+
+ public String getFirstName() {
+ return firstName;
+ }
+
+ public void setFirstName(String firstName) {
+ this.firstName = firstName;
+ }
+
+ public String getLastName() {
+ return lastName;
+ }
+
+ public void setLastName(String lastName) {
+ this.lastName = lastName;
+ }
+
+ public String getEmail() {
+ return email;
+ }
+
+ public void setEmail(String email) {
+ this.email = email;
+ }
+
+ public String getPhone() {
+ return phone;
+ }
+
+ public void setPhone(String phone) {
+ this.phone = phone;
+ }
+
+ public String getAddress() {
+ return address;
+ }
+
+ public void setAddress(String address) {
+ this.address = address;
+ }
+
+ public String getPassword() {
+ return password;
+ }
+
+ public void setPassword(String password) {
+ this.password = password;
+ }
+
+ public String getRole() {
+ return role;
+ }
+
+ public void setRole(String role) {
+ this.role = role;
+ }
+
+ public Date getCreatedAt() {
+ return createdAt;
+ }
+
+ public void setCreatedAt(Date createdAt) {
+ this.createdAt = createdAt;
+ }
+
+}
diff --git a/src/main/java/org/berzerkula/builddb/models/Pkg.java b/src/main/java/org/berzerkula/builddb/models/Pkg.java
new file mode 100644
index 0000000..5ce80ba
--- /dev/null
+++ b/src/main/java/org/berzerkula/builddb/models/Pkg.java
@@ -0,0 +1,118 @@
+package org.berzerkula.builddb.models;
+
+import jakarta.persistence.*;
+
+@Entity
+@Table(name = "rock5b_srv_bld")
+public class Pkg {
+ @Id
+ @GeneratedValue(strategy= GenerationType.IDENTITY)
+ private int id;
+
+ private String name;
+ private Integer sequence;
+ private String version;
+ @Column(columnDefinition = "TEXT")
+ private String configure;
+ @Column(columnDefinition = "TEXT")
+ private String build;
+ @Column(columnDefinition = "TEXT")
+ private String install;
+ @Column(columnDefinition = "TEXT")
+ private String setup;
+ @Column(columnDefinition = "TEXT")
+ private String notes;
+ @Column(columnDefinition = "TEXT")
+ private String url;
+
+ public Pkg(Integer sequence, String name, String version, String configure, String build, String install,
+ String setup, String notes, String url) {
+ this.sequence = sequence;
+ this.name = name;
+ this.version = version;
+ this.configure = configure;
+ this.build = build;
+ this.install = install;
+ this.setup = setup;
+ this.notes = notes;
+ this.url = url;
+ }
+
+ public Pkg() {}
+
+ public int getId() {
+ return id;
+ }
+
+ public String getName() {
+ return name;
+ }
+
+ public void setName(String name) {
+ this.name = name;
+ }
+
+ public Integer getSequence() {
+ return sequence;
+ }
+
+ public void setSequence(Integer seq) {
+ this.sequence = seq;
+ }
+
+ public String getVersion() {
+ return version;
+ }
+
+ public void setVersion(String version) {
+ this.version = version;
+ }
+
+ public String getConfigure() {
+ return configure;
+ }
+
+ public void setConfigure(String configure) {
+ this.configure = configure;
+ }
+
+ public String getBuild() {
+ return build;
+ }
+
+ public void setBuild(String build) {
+ this.build = build;
+ }
+
+ public String getInstall() {
+ return install;
+ }
+
+ public void setInstall(String install) {
+ this.install = install;
+ }
+
+ public String getSetup() {
+ return setup;
+ }
+
+ public void setSetup(String setup) {
+ this.setup = setup;
+ }
+
+ public String getNotes() {
+ return notes;
+ }
+
+ public void setNotes(String notes) {
+ this.notes = notes;
+ }
+
+ public String getUrl() {
+ return url;
+ }
+
+ public void setUrl(String url) {
+ this.url = url;
+ }
+}
diff --git a/src/main/java/org/berzerkula/builddb/models/PkgDto.java b/src/main/java/org/berzerkula/builddb/models/PkgDto.java
new file mode 100644
index 0000000..04c47ea
--- /dev/null
+++ b/src/main/java/org/berzerkula/builddb/models/PkgDto.java
@@ -0,0 +1,104 @@
+package org.berzerkula.builddb.models;
+
+import jakarta.validation.constraints.Digits;
+import jakarta.validation.constraints.NotEmpty;
+import jakarta.validation.constraints.NotNull;
+import org.springframework.data.annotation.ReadOnlyProperty;
+
+public class PkgDto {
+ @ReadOnlyProperty
+ private Integer id;
+
+ @NotNull(message = "Required")
+ @Digits(integer = 4, fraction = 0)
+ private Integer sequence;
+
+ @NotEmpty(message = "Required")
+ private String name;
+
+ @NotEmpty(message = "Required")
+ private String version;
+
+ private String configure;
+ private String build;
+ private String install;
+ private String setup;
+ private String notes;
+ private String url;
+
+ public Integer getSequence() {
+ return sequence;
+ }
+
+ public void setSequence(Integer sequence) {
+ this.sequence = sequence;
+ }
+
+ public String getName() {
+ return name;
+ }
+
+ public void setName(String name) {
+ this.name = name;
+ }
+
+ public String getVersion() {
+ return version;
+ }
+
+ public void setVersion(String version) {
+ this.version = version;
+ }
+
+ public String getConfigure() {
+ return configure;
+ }
+
+ public void setConfigure(String configure) {
+ this.configure = configure;
+ }
+
+ public String getBuild() {
+ return build;
+ }
+
+ public void setBuild(String build) {
+ this.build = build;
+ }
+
+ public String getInstall() {
+ return install;
+ }
+
+ public void setInstall(String install) {
+ this.install = install;
+ }
+
+ public String getSetup() {
+ return setup;
+ }
+
+ public void setSetup(String setup) {
+ this.setup = setup;
+ }
+
+ public String getNotes() {
+ return notes;
+ }
+
+ public void setNotes(String notes) {
+ this.notes = notes;
+ }
+
+ public Integer getId() {
+ return id;
+ }
+
+ public String getUrl() {
+ return url;
+ }
+
+ public void setUrl(String url) {
+ this.url = url;
+ }
+}
diff --git a/src/main/java/org/berzerkula/builddb/models/RegisterDto.java b/src/main/java/org/berzerkula/builddb/models/RegisterDto.java
new file mode 100644
index 0000000..eebf014
--- /dev/null
+++ b/src/main/java/org/berzerkula/builddb/models/RegisterDto.java
@@ -0,0 +1,84 @@
+package org.berzerkula.builddb.models;
+
+import jakarta.validation.constraints.*;
+
+public class RegisterDto {
+
+ @NotEmpty
+ private String firstName;
+
+ @NotEmpty
+ private String lastName;
+
+ @NotEmpty
+ @Email
+ private String email;
+
+ private String phone;
+
+ private String address;
+
+ @Size(min = 6, message = "Minimum Password length is 6 characters")
+ private String password;
+
+ private String confirmPassword;
+
+
+ public String getFirstName() {
+ return firstName;
+ }
+
+ public void setFirstName(String firstName) {
+ this.firstName = firstName;
+ }
+
+ public String getLastName() {
+ return lastName;
+ }
+
+ public void setLastName(String lastName) {
+ this.lastName = lastName;
+ }
+
+ public String getEmail() {
+ return email;
+ }
+
+ public void setEmail(String email) {
+ this.email = email;
+ }
+
+ public String getPhone() {
+ return phone;
+ }
+
+ public void setPhone(String phone) {
+ this.phone = phone;
+ }
+
+ public String getAddress() {
+ return address;
+ }
+
+ public void setAddress(String address) {
+ this.address = address;
+ }
+
+ public String getPassword() {
+ return password;
+ }
+
+ public void setPassword(String password) {
+ this.password = password;
+ }
+
+ public String getConfirmPassword() {
+ return confirmPassword;
+ }
+
+ public void setConfirmPassword(String confirmPassword) {
+ this.confirmPassword = confirmPassword;
+ }
+
+
+}
diff --git a/src/main/java/org/berzerkula/builddb/repositories/AppUserRepository.java b/src/main/java/org/berzerkula/builddb/repositories/AppUserRepository.java
new file mode 100644
index 0000000..756d56a
--- /dev/null
+++ b/src/main/java/org/berzerkula/builddb/repositories/AppUserRepository.java
@@ -0,0 +1,10 @@
+package org.berzerkula.builddb.repositories;
+
+import org.springframework.data.jpa.repository.JpaRepository;
+
+import org.berzerkula.builddb.models.AppUser;
+
+public interface AppUserRepository extends JpaRepository<AppUser, Integer> {
+
+ public AppUser findByEmail(String email);
+}
diff --git a/src/main/java/org/berzerkula/builddb/repositories/PkgRepository.java b/src/main/java/org/berzerkula/builddb/repositories/PkgRepository.java
new file mode 100644
index 0000000..30e80eb
--- /dev/null
+++ b/src/main/java/org/berzerkula/builddb/repositories/PkgRepository.java
@@ -0,0 +1,8 @@
+package org.berzerkula.builddb.repositories;
+
+import org.berzerkula.builddb.models.Pkg;
+import org.springframework.data.jpa.repository.JpaRepository;
+
+public interface PkgRepository extends JpaRepository<Pkg, Integer> {
+
+}
diff --git a/src/main/java/org/berzerkula/builddb/services/AppUserService.java b/src/main/java/org/berzerkula/builddb/services/AppUserService.java
new file mode 100644
index 0000000..02cc89f
--- /dev/null
+++ b/src/main/java/org/berzerkula/builddb/services/AppUserService.java
@@ -0,0 +1,36 @@
+package org.berzerkula.builddb.services;
+
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.security.core.userdetails.User;
+import org.springframework.security.core.userdetails.UserDetails;
+import org.springframework.security.core.userdetails.UserDetailsService;
+import org.springframework.security.core.userdetails.UsernameNotFoundException;
+import org.springframework.stereotype.Service;
+
+import org.berzerkula.builddb.models.AppUser;
+import org.berzerkula.builddb.repositories.AppUserRepository;
+
+@Service
+public class AppUserService implements UserDetailsService {
+ @Autowired
+ private AppUserRepository repo;
+
+ @Override
+ public UserDetails loadUserByUsername(String email) throws UsernameNotFoundException {
+ AppUser appUser = repo.findByEmail(email);
+
+
+ if (appUser != null) {
+ var springUser = User.withUsername(appUser.getEmail())
+ .password(appUser.getPassword())
+ .roles(appUser.getRole())
+ .build();
+
+ return springUser;
+ }
+
+
+ return null;
+ }
+
+}
diff --git a/src/main/resources/application.properties b/src/main/resources/application.properties
new file mode 100644
index 0000000..f6a7814
--- /dev/null
+++ b/src/main/resources/application.properties
@@ -0,0 +1,15 @@
+spring.application.name=builddb
+
+spring.datasource.driver-class-name=com.mysql.cj.jdbc.Driver
+spring.datasource.url=jdbc:mysql://nucleus.berzerkula.org:3306/builddb_users
+spring.datasource.username=kb0iic
+spring.datasource.password=ENC(1xjvlowHDUgzAYCjEEaYcSQOQHL2SHo8)
+
+spring.jpa.show-sql=true
+spring.jpa.hibernate.ddl-auto=update
+
+jasypt.encryptor.algorithm=PBEWithMD5AndDES
+jasypt.encryptor.iv-generator-classname=org.jasypt.iv.NoIvGenerator
+jasypt.encryptor.password=builddb
+
+management.endpoints.web.exposure.include=* \ No newline at end of file
diff --git a/src/main/resources/templates/actuatorDashboard.html b/src/main/resources/templates/actuatorDashboard.html
new file mode 100644
index 0000000..e97bb8f
--- /dev/null
+++ b/src/main/resources/templates/actuatorDashboard.html
@@ -0,0 +1,39 @@
+<!DOCTYPE html>
+<html xmlns:th="http://www.thymeleaf.org">
+<head>
+ <title>spring-boot-actuator</title>
+ <meta charset="UTF-8" />
+ <link href="https://cdn.jsdelivr.net/npm/bootstrap@5.3.3/dist/css/bootstrap.min.css" rel="stylesheet" integrity="sha384-QWTKZyjpPEjISv5WaRU9OFeRpok6YctnYmDr5pNlyT2bRjXh0JMhjY6hW+ALEwIH" crossorigin="anonymous">
+</head>
+<body>
+<div id="app" class="container">
+ <nav class="navbar navbar-expand-lg fixed-top bg-body-tertiary border-bottom">
+ <div class="container">
+ <button class="navbar-toggler" type="button" data-bs-toggle="collapse" data-bs-target="#navbarSupportedContent" aria-controls="navbarSupportedContent" aria-expanded="false" aria-label="Toggle navigation">
+ <span class="navbar-toggler-icon"></span>
+ </button>
+ <div class="collapse navbar-collapse" id="navbarSupportedContent">
+ <a class="navbar-brand" href="/actuatorDashboard">spring-boot-actuator</a>
+ <ul class="navbar-nav me-auto mb-2 mb-lg-0">
+ <li class="nav-link text-dark"><a th:href="@{/actuator/metrics}">/metrics</a></li>
+ <li class="nav-link text-dark"><a th:href="@{/actuator/env}">/env</a></li>
+ <li class="nav-link text-dark"><a th:href="@{/actuator/dump}">/dump</a></li>
+ <li class="nav-link text-dark"><a th:href="@{/actuator/health}">/health</a></li>
+ <li class="nav-link text-dark"><a th:href="@{/actuator/beans}">/beans</a></li>
+ <li class="nav-link text-dark"><a th:href="@{/actuator/autoconfig}">/autoconfig</a></li>
+ <li class="nav-link text-dark"><a th:href="@{/actuator/info}">/info</a></li>
+ <li class="nav-link text-dark"><a th:href="@{/actuator/shutdown}">/shutdown</a></li>
+ </ul>
+ </div>
+ </div>
+ </nav>
+
+ <hr>
+ <hr>
+
+ <a href="/" class="btn btn-link">Home</a>
+</div>
+
+<script src="https://cdn.jsdelivr.net/npm/bootstrap@5.3.3/dist/js/bootstrap.bundle.min.js" integrity="sha384-YvpcrYf0tY3lHB60NNkmXc5s9fDVZLESaAA55NDzOxhy9GkcIdslK1eN7N6jIeHz" crossorigin="anonymous"></script>
+</body>
+</html> \ No newline at end of file
diff --git a/src/main/resources/templates/admin.html b/src/main/resources/templates/admin.html
new file mode 100644
index 0000000..16f8f7f
--- /dev/null
+++ b/src/main/resources/templates/admin.html
@@ -0,0 +1,53 @@
+<!doctype html>
+<html lang="en">
+<head>
+ <meta charset="utf-8">
+ <meta name="viewport" content="width=device-width, initial-scale=1">
+ <title>builddb</title>
+ <link href="https://cdn.jsdelivr.net/npm/bootstrap@5.3.3/dist/css/bootstrap.min.css" rel="stylesheet" integrity="sha384-QWTKZyjpPEjISv5WaRU9OFeRpok6YctnYmDr5pNlyT2bRjXh0JMhjY6hW+ALEwIH" crossorigin="anonymous">
+</head>
+<body>
+
+<div class="container py-5">
+ <div class="rounded border p-4">
+ <h2 class="text-center mb-4">Admin Page</h2>
+ <hr>
+ <a href="/" class="btn btn-link">Home</a>
+ </div>
+</div>
+
+
+<script src="https://cdn.jsdelivr.net/npm/bootstrap@5.3.3/dist/js/bootstrap.bundle.min.js" integrity="sha384-YvpcrYf0tY3lHB60NNkmXc5s9fDVZLESaAA55NDzOxhy9GkcIdslK1eN7N6jIeHz" crossorigin="anonymous"></script>
+</body>
+</html>
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/src/main/resources/templates/client.html b/src/main/resources/templates/client.html
new file mode 100644
index 0000000..487d427
--- /dev/null
+++ b/src/main/resources/templates/client.html
@@ -0,0 +1,53 @@
+<!doctype html>
+<html lang="en">
+<head>
+ <meta charset="utf-8">
+ <meta name="viewport" content="width=device-width, initial-scale=1">
+ <title>builddb</title>
+ <link href="https://cdn.jsdelivr.net/npm/bootstrap@5.3.3/dist/css/bootstrap.min.css" rel="stylesheet" integrity="sha384-QWTKZyjpPEjISv5WaRU9OFeRpok6YctnYmDr5pNlyT2bRjXh0JMhjY6hW+ALEwIH" crossorigin="anonymous">
+</head>
+<body>
+
+<div class="container py-5">
+ <div class="rounded border p-4">
+ <h2 class="text-center mb-4">Client Page</h2>
+ <hr>
+ <a href="/" class="btn btn-link">Home</a>
+ </div>
+</div>
+
+
+<script src="https://cdn.jsdelivr.net/npm/bootstrap@5.3.3/dist/js/bootstrap.bundle.min.js" integrity="sha384-YvpcrYf0tY3lHB60NNkmXc5s9fDVZLESaAA55NDzOxhy9GkcIdslK1eN7N6jIeHz" crossorigin="anonymous"></script>
+</body>
+</html>
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/src/main/resources/templates/contact.html b/src/main/resources/templates/contact.html
new file mode 100644
index 0000000..f1c6a3f
--- /dev/null
+++ b/src/main/resources/templates/contact.html
@@ -0,0 +1,53 @@
+<!doctype html>
+<html lang="en">
+<head>
+ <meta charset="utf-8">
+ <meta name="viewport" content="width=device-width, initial-scale=1">
+ <title>builddb</title>
+ <link href="https://cdn.jsdelivr.net/npm/bootstrap@5.3.3/dist/css/bootstrap.min.css" rel="stylesheet" integrity="sha384-QWTKZyjpPEjISv5WaRU9OFeRpok6YctnYmDr5pNlyT2bRjXh0JMhjY6hW+ALEwIH" crossorigin="anonymous">
+</head>
+<body>
+
+<div class="container py-5">
+ <div class="rounded border p-4">
+ <h2 class="text-center mb-4">Contact</h2>
+ <hr>
+ <a href="/" class="btn btn-link">Home</a>
+ </div>
+</div>
+
+
+<script src="https://cdn.jsdelivr.net/npm/bootstrap@5.3.3/dist/js/bootstrap.bundle.min.js" integrity="sha384-YvpcrYf0tY3lHB60NNkmXc5s9fDVZLESaAA55NDzOxhy9GkcIdslK1eN7N6jIeHz" crossorigin="anonymous"></script>
+</body>
+</html>
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/src/main/resources/templates/index.html b/src/main/resources/templates/index.html
new file mode 100644
index 0000000..b33e45f
--- /dev/null
+++ b/src/main/resources/templates/index.html
@@ -0,0 +1,37 @@
+<!doctype html>
+<html lang="en" xmlns:th="http://www.thymeleaf.org"
+ xmlns:sec="http://www.thymeleaf.org">
+<head>
+ <meta charset="utf-8">
+ <meta name="viewport" content="width=device-width, initial-scale=1">
+ <title>builddb</title>
+ <link href="https://cdn.jsdelivr.net/npm/bootstrap@5.3.3/dist/css/bootstrap.min.css" rel="stylesheet" integrity="sha384-QWTKZyjpPEjISv5WaRU9OFeRpok6YctnYmDr5pNlyT2bRjXh0JMhjY6hW+ALEwIH" crossorigin="anonymous">
+ <link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/font-awesome/6.0.0/css/all.min.css">
+</head>
+<body>
+
+<nav th:insert="~{/navbar :: navigation}"></nav>
+
+<hr>
+
+<div class="container py-5">
+ <h1 class="text-center">Builddb</h1>
+
+
+
+ <a class="btn btn-primary me-3" sec:authorize="hasRole('client')" href="/pkgs">Packages</a>
+</div>
+
+<hr>
+
+<div th:if="${tableError}"
+ class="alert alert-danger alert-dismissible fade show" role="alert">
+
+ <strong>Invalid table!</strong>
+ <button type="button" class="btn-close" data-bs-dismiss="alert"
+ aria-label="Close"></button>
+</div>
+
+<script src="https://cdn.jsdelivr.net/npm/bootstrap@5.3.3/dist/js/bootstrap.bundle.min.js" integrity="sha384-YvpcrYf0tY3lHB60NNkmXc5s9fDVZLESaAA55NDzOxhy9GkcIdslK1eN7N6jIeHz" crossorigin="anonymous"></script>
+</body>
+</html> \ No newline at end of file
diff --git a/src/main/resources/templates/login.html b/src/main/resources/templates/login.html
new file mode 100644
index 0000000..0893fa0
--- /dev/null
+++ b/src/main/resources/templates/login.html
@@ -0,0 +1,87 @@
+<!doctype html>
+<html lang="en" xmlns:th="http://www.thymeleaf.org"
+ xmlns:sec="http://www.thymeleaf.org">
+<head>
+ <meta charset="utf-8">
+ <meta name="viewport" content="width=device-width, initial-scale=1">
+ <title>builddb</title>
+ <link href="https://cdn.jsdelivr.net/npm/bootstrap@5.3.3/dist/css/bootstrap.min.css" rel="stylesheet" integrity="sha384-QWTKZyjpPEjISv5WaRU9OFeRpok6YctnYmDr5pNlyT2bRjXh0JMhjY6hW+ALEwIH" crossorigin="anonymous">
+</head>
+<body>
+
+<div class="container py-5">
+ <div class="mx-auto rounded border p-4" style="width: 400px">
+ <h2 class="text-center mb-4">Login</h2>
+ <hr />
+
+
+ <div th:if="${param.error}"
+ class="alert alert-danger alert-dismissible fade show" role="alert">
+
+ <strong>Invalid Email or Password!</strong>
+ <button type="button" class="btn-close" data-bs-dismiss="alert"
+ aria-label="Close"></button>
+ </div>
+
+ <form method="post">
+ <input type="hidden" th:name="${_csrf.parameterName}"
+ th:value="${_csrf.token}" />
+
+
+ <div class="mb-3">
+ <label class="form-label">Email</label>
+ <input class="form-control" name="email" />
+ </div>
+
+ <div class="mb-3">
+ <label class="form-label">Password</label>
+ <input class="form-control" type="password" name="password" />
+ </div>
+
+ <div class="row mb-3">
+ <div class="col d-grid">
+ <button type="submit" class="btn btn-primary">Submit</button>
+ </div>
+ <div class="col d-grid">
+ <a href="/" class="btn btn-outline-primary">Cancel</a>
+ </div>
+ </div>
+ </form>
+ </div>
+</div>
+
+
+<script src="https://cdn.jsdelivr.net/npm/bootstrap@5.3.3/dist/js/bootstrap.bundle.min.js" integrity="sha384-YvpcrYf0tY3lHB60NNkmXc5s9fDVZLESaAA55NDzOxhy9GkcIdslK1eN7N6jIeHz" crossorigin="anonymous"></script>
+</body>
+</html>
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/src/main/resources/templates/navbar.html b/src/main/resources/templates/navbar.html
new file mode 100644
index 0000000..0aa60e0
--- /dev/null
+++ b/src/main/resources/templates/navbar.html
@@ -0,0 +1,77 @@
+<nav th:fragment="navigation" class="navbar navbar-expand-lg fixed-top bg-body-tertiary border-bottom">
+ <div class="container">
+ <a class="navbar-brand" href="/">builddb</a>
+ <button class="navbar-toggler" type="button" data-bs-toggle="collapse" data-bs-target="#navbarSupportedContent" aria-controls="navbarSupportedContent" aria-expanded="false" aria-label="Toggle navigation">
+ <span class="navbar-toggler-icon"></span>
+ </button>
+ <div class="collapse navbar-collapse" id="navbarSupportedContent">
+ <ul class="navbar-nav me-auto mb-2 mb-lg-0">
+ <li class="nav-item">
+ <a class="nav-link text-dark" href="/">Home</a>
+ </li>
+ <li class="nav-item">
+ <a class="nav-link text-dark" href="/contact">Contact</a>
+ </li>
+ <li class="nav-item">
+ <a class="nav-link text-dark" href="/user">User</a>
+ </li>
+ <li class="nav-item">
+ <a class="nav-link text-dark" sec:authorize="hasRole('client')" href="/client">Client</a>
+ </li>
+ <li class="nav-item">
+ <a class="nav-link text-dark" sec:authorize="hasRole('admin')" href="/admin">Admin</a>
+ </li>
+ <li class="nav-item">
+ <a class="nav-link text-dark" sec:authorize="hasRole('admin')" href="/actuatorDashboard">Actuator</a>
+ </li>
+ </ul>
+
+ <ul class="navbar-nav me-3" sec:authorize="hasRole('admin')">
+ <li class="nav-item dropdown">
+ <a class="nav-link dropdown-toggle text-dark" href="#" role="button" data-bs-toggle="dropdown" aria-expanded="false">
+ Admin
+ </a>
+ <ul class="dropdown-menu">
+ <li><a class="dropdown-item" href="/profile">Profile</a></li>
+ <li><a class="dropdown-item" href="/">Home</a></li>
+ </ul>
+ </li>
+ </ul>
+
+ <ul class="navbar-nav me-3" sec:authorize="hasRole('client')">
+ <li class="nav-item dropdown">
+ <a class="nav-link dropdown-toggle text-dark" href="#" role="button" data-bs-toggle="dropdown" aria-expanded="false">
+ Client
+ </a>
+ <ul class="dropdown-menu">
+ <li><a class="dropdown-item" href="/profile">Profile</a></li>
+ <li><a class="dropdown-item" href="/">Home</a></li>
+ </ul>
+ </li>
+ </ul>
+
+ <form sec:authorize="isAuthenticated()" method="post" action="/logout">
+ <input type="hidden" th:name="${_csrf.parameterName}"
+ th:value="${_csrf.token}" />
+
+ <button type="submit" class="btn btn-danger">
+ <i class="fa-solid fa-right-from-bracket"></i>
+ </button>
+ </form>
+
+ <ul class="navbar-nav" sec:authorize="!isAuthenticated()">
+ <li class="nav-item">
+ <a href="/register" class="btn btn-outline-primary me-2">
+ <i class="fa-regular fa-id-card"></i>
+ </a>
+ </li>
+ <li class="nav-item">
+ <a href="/login" class="btn btn-primary">
+ <i class="fa-solid fa-right-to-bracket"></i>
+ </a>
+ </li>
+ </ul>
+
+ </div>
+ </div>
+</nav> \ No newline at end of file
diff --git a/src/main/resources/templates/pkgs/add.html b/src/main/resources/templates/pkgs/add.html
new file mode 100644
index 0000000..107784f
--- /dev/null
+++ b/src/main/resources/templates/pkgs/add.html
@@ -0,0 +1,109 @@
+<!doctype html>
+<html lang="en" xmlns:th="http://www.thymeleaf.org"
+ xmlns:sec="http://www.thymeleaf.org">
+<head>
+ <meta charset="utf-8">
+ <meta name="viewport" content="width=device-width, initial-scale=1">
+ <title>builddb</title>
+ <link href="https://cdn.jsdelivr.net/npm/bootstrap@5.3.3/dist/css/bootstrap.min.css" rel="stylesheet" integrity="sha384-QWTKZyjpPEjISv5WaRU9OFeRpok6YctnYmDr5pNlyT2bRjXh0JMhjY6hW+ALEwIH" crossorigin="anonymous">
+</head>
+<body>
+
+<nav th:insert="~{/navbar :: navigation}"></nav>
+
+<hr>
+
+<div class="container">
+ <div class="col-md-8 mx-auto rounded border p-4 m-4">
+ <h2 class="text-center mb-5">Add Package</h2>
+
+ <form method="post" enctype="multipart/form-data" th:object="${pkgDto}">
+ <input type="hidden" th:name="${_csrf.parameterName}" th:value="${_csrf.token}" />
+
+ <div class="row mb-3">
+ <label class="col-sm-4 col-form-label">Sequence</label>
+ <div class="col-sm-8">
+ <input class="form-control" th:field="${pkgDto.sequence}" />
+ <p th:if="${#fields.hasErrors('sequence')}" th:errorclass="text-danger" th:errors="${pkgDto.sequence}"></p>
+ </div>
+ </div>
+
+ <div class="row mb-3">
+ <label class="col-sm-4 col-form-label">Name</label>
+ <div class="col-sm-8">
+ <input class="form-control" th:field="${pkgDto.name}" />
+ <p th:if="${#fields.hasErrors('name')}" th:errorclass="text-danger" th:errors="${pkgDto.name}"></p>
+ </div>
+ </div>
+
+ <div class="row mb-3">
+ <label class="col-sm-4 col-form-label">Version</label>
+ <div class="col-sm-8">
+ <input class="form-control" th:field="${pkgDto.version}" />
+ <p th:if="${#fields.hasErrors('version')}" th:errorclass="text-danger" th:errors="${pkgDto.version}"></p>
+ </div>
+ </div>
+
+ <div class="row mb-3">
+ <label class="col-sm-4 col-form-label">Configure</label>
+ <div class="col-sm-8">
+ <textarea class="form-control" th:field="${pkgDto.configure}" />
+ <p th:if="${#fields.hasErrors('configure')}" th:errorclass="text-danger" th:errors="${pkgDto.configure}"></p>
+ </div>
+ </div>
+
+ <div class="row mb-3">
+ <label class="col-sm-4 col-form-label">Build</label>
+ <div class="col-sm-8">
+ <textarea class="form-control" th:field="${pkgDto.build}" />
+ <p th:if="${#fields.hasErrors('build')}" th:errorclass="text-danger" th:errors="${pkgDto.build}"></p>
+ </div>
+ </div>
+
+ <div class="row mb-3">
+ <label class="col-sm-4 col-form-label">Install</label>
+ <div class="col-sm-8">
+ <textarea class="form-control" th:field="${pkgDto.install}" />
+ <p th:if="${#fields.hasErrors('install')}" th:errorclass="text-danger" th:errors="${pkgDto.install}"></p>
+ </div>
+ </div>
+
+ <div class="row mb-3">
+ <label class="col-sm-4 col-form-label">Setup</label>
+ <div class="col-sm-8">
+ <textarea class="form-control" th:field="${pkgDto.setup}" />
+ <p th:if="${#fields.hasErrors('setup')}" th:errorclass="text-danger" th:errors="${pkgDto.setup}"></p>
+ </div>
+ </div>
+
+ <div class="row mb-3">
+ <label class="col-sm-4 col-form-label">Notes</label>
+ <div class="col-sm-8">
+ <textarea class="form-control" th:field="${pkgDto.notes}" />
+ <p th:if="${#fields.hasErrors('notes')}" th:errorclass="text-danger" th:errors="${pkgDto.notes}"></p>
+ </div>
+ </div>
+
+ <div class="row mb-3">
+ <label class="col-sm-4 col-form-label">Url</label>
+ <div class="col-sm-8">
+ <textarea class="form-control" th:field="${pkgDto.url}" />
+ <p th:if="${#fields.hasErrors('Url')}" th:errorclass="text-danger" th:errors="${pkgDto.url}"></p>
+ </div>
+ </div>
+
+ <div class="row">
+ <div class="offset-sm-4 col-sm-4 d-grid">
+ <button type="submit" class="btn btn-primary">Submit</button>
+ </div>
+ <div class="col-sm-4 d-grid">
+ <a class="btn btn-outline-primary" href="/pkgs" role="button">Cancel</a>
+ </div>
+ </div>
+ </form>
+ </div>
+</div>
+
+<script src="https://cdn.jsdelivr.net/npm/bootstrap@5.3.3/dist/js/bootstrap.bundle.min.js" integrity="sha384-YvpcrYf0tY3lHB60NNkmXc5s9fDVZLESaAA55NDzOxhy9GkcIdslK1eN7N6jIeHz" crossorigin="anonymous"></script>
+</body>
+</html> \ No newline at end of file
diff --git a/src/main/resources/templates/pkgs/edit.html b/src/main/resources/templates/pkgs/edit.html
new file mode 100644
index 0000000..34ad9a8
--- /dev/null
+++ b/src/main/resources/templates/pkgs/edit.html
@@ -0,0 +1,109 @@
+<!doctype html>
+<html lang="en" xmlns:th="http://www.thymeleaf.org"
+ xmlns:sec="http://www.thymeleaf.org">
+<head>
+ <meta charset="utf-8">
+ <meta name="viewport" content="width=device-width, initial-scale=1">
+ <title>builddb</title>
+ <link href="https://cdn.jsdelivr.net/npm/bootstrap@5.3.3/dist/css/bootstrap.min.css" rel="stylesheet" integrity="sha384-QWTKZyjpPEjISv5WaRU9OFeRpok6YctnYmDr5pNlyT2bRjXh0JMhjY6hW+ALEwIH" crossorigin="anonymous">
+</head>
+<body>
+
+<nav th:insert="~{/navbar :: navigation}"></nav>
+
+<hr>
+
+<div class="container">
+ <div class="col-md-8 mx-auto rounded border p-4 m-4">
+ <h2 class="text-center mb-5">Edit Package</h2>
+
+ <form method="post" enctype="multipart/form-data" th:object="${pkgDto}">
+ <input type="hidden" th:name="${_csrf.parameterName}" th:value="${_csrf.token}" />
+
+ <div class="row mb-3">
+ <label class="col-sm-4 col-form-label">Sequence</label>
+ <div class="col-sm-8">
+ <input class="form-control" th:field="${pkgDto.sequence}" />
+ <p th:if="${#fields.hasErrors('sequence')}" th:errorclass="text-danger" th:errors="${pkgDto.sequence}"></p>
+ </div>
+ </div>
+
+ <div class="row mb-3">
+ <label class="col-sm-4 col-form-label">Name</label>
+ <div class="col-sm-8">
+ <input class="form-control" th:field="${pkgDto.name}" />
+ <p th:if="${#fields.hasErrors('name')}" th:errorclass="text-danger" th:errors="${pkgDto.name}"></p>
+ </div>
+ </div>
+
+ <div class="row mb-3">
+ <label class="col-sm-4 col-form-label">Version</label>
+ <div class="col-sm-8">
+ <input class="form-control" th:field="${pkgDto.version}" />
+ <p th:if="${#fields.hasErrors('version')}" th:errorclass="text-danger" th:errors="${pkgDto.version}"></p>
+ </div>
+ </div>
+
+ <div class="row mb-3">
+ <label class="col-sm-4 col-form-label">Configure</label>
+ <div class="col-sm-8">
+ <textarea class="form-control" th:field="${pkgDto.configure}" />
+ <p th:if="${#fields.hasErrors('configure')}" th:errorclass="text-danger" th:errors="${pkgDto.configure}"></p>
+ </div>
+ </div>
+
+ <div class="row mb-3">
+ <label class="col-sm-4 col-form-label">Build</label>
+ <div class="col-sm-8">
+ <textarea class="form-control" th:field="${pkgDto.build}" />
+ <p th:if="${#fields.hasErrors('build')}" th:errorclass="text-danger" th:errors="${pkgDto.build}"></p>
+ </div>
+ </div>
+
+ <div class="row mb-3">
+ <label class="col-sm-4 col-form-label">Install</label>
+ <div class="col-sm-8">
+ <textarea class="form-control" th:field="${pkgDto.install}" />
+ <p th:if="${#fields.hasErrors('install')}" th:errorclass="text-danger" th:errors="${pkgDto.install}"></p>
+ </div>
+ </div>
+
+ <div class="row mb-3">
+ <label class="col-sm-4 col-form-label">Setup</label>
+ <div class="col-sm-8">
+ <textarea class="form-control" th:field="${pkgDto.setup}" />
+ <p th:if="${#fields.hasErrors('setup')}" th:errorclass="text-danger" th:errors="${pkgDto.setup}"></p>
+ </div>
+ </div>
+
+ <div class="row mb-3">
+ <label class="col-sm-4 col-form-label">Notes</label>
+ <div class="col-sm-8">
+ <textarea class="form-control" th:field="${pkgDto.notes}" />
+ <p th:if="${#fields.hasErrors('notes')}" th:errorclass="text-danger" th:errors="${pkgDto.notes}"></p>
+ </div>
+ </div>
+
+ <div class="row mb-3">
+ <label class="col-sm-4 col-form-label">Url</label>
+ <div class="col-sm-8">
+ <textarea class="form-control" th:field="${pkgDto.url}" />
+ <p th:if="${#fields.hasErrors('Url')}" th:errorclass="text-danger" th:errors="${pkgDto.url}"></p>
+ </div>
+ </div>
+
+ <div class="row">
+ <div class="offset-sm-4 col-sm-4 d-grid">
+ <button type="submit" class="btn btn-primary">Submit</button>
+ </div>
+ <div class="col-sm-4 d-grid">
+ <a class="btn btn-outline-primary" href="/pkgs/" role="button">Cancel</a>
+ </div>
+ </div>
+ </form>
+ </div>
+</div>
+
+<script src="https://cdn.jsdelivr.net/npm/bootstrap@5.3.3/dist/js/bootstrap.bundle.min.js" integrity="sha384-YvpcrYf0tY3lHB60NNkmXc5s9fDVZLESaAA55NDzOxhy9GkcIdslK1eN7N6jIeHz" crossorigin="anonymous"></script>
+</body>
+</html> \ No newline at end of file
diff --git a/src/main/resources/templates/pkgs/index.html b/src/main/resources/templates/pkgs/index.html
new file mode 100644
index 0000000..04eb45a
--- /dev/null
+++ b/src/main/resources/templates/pkgs/index.html
@@ -0,0 +1,62 @@
+<!doctype html>
+<html lang="en">
+<head>
+ <meta charset="utf-8">
+ <meta name="viewport" content="width=device-width, initial-scale=1">
+ <title>Builddb</title>
+ <link href="https://cdn.jsdelivr.net/npm/bootstrap@5.3.3/dist/css/bootstrap.min.css" rel="stylesheet" integrity="sha384-QWTKZyjpPEjISv5WaRU9OFeRpok6YctnYmDr5pNlyT2bRjXh0JMhjY6hW+ALEwIH" crossorigin="anonymous">
+ <link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/font-awesome/6.0.0/css/all.min.css">
+</head>
+<body>
+<nav th:insert="~{/navbar :: navigation}"></nav>
+
+<hr>
+<hr>
+
+<div class="container">
+ <h1 class="text-center my-4">Packages</h1>
+ <a class="btn btn-primary fa fa-add" href="/pkgs/add"></a>
+
+ <hr>
+
+ <table class="table table-bordered table-hover table-striped">
+ <thead>
+ <tr>
+ <th id="0" th:replace="~{pkgs/sorting :: sorting('sequence', 'Seq')}">Seq</th>
+ <th th:replace="~{pkgs/sorting :: sorting('name', 'Name')}">Name</th>
+ <th>Version</th>
+ <th>Configure</th>
+ <th>Build</th>
+ <th>Install</th>
+ <th>Setup</th>
+ <th>Notes</th>
+ <th>Url</th>
+ <th>Action</th>
+ </tr>
+ </thead>
+ <tbody class="table-group-divider">
+ <tr th:each="pkg : ${pkgs}">
+ <td th:id="${pkg.id}" th:text="${pkg.sequence}"></td>
+ <td th:text="${pkg.name}"></td>
+ <td th:text="${pkg.version}"></td>
+ <td th:text="${pkg.configure}"></td>
+ <td th:text="${pkg.build}"></td>
+ <td th:text="${pkg.install}"></td>
+ <td th:text="${pkg.setup}"></td>
+ <td th:text="${pkg.notes}"></td>
+ <td th:text="${pkg.url}"></td>
+ <td style="white-space:nowrap">
+ <a class="btn btn-primary btn-sm fa fa-edit"
+ th:href="@{/pkgs/edit(id=${pkg.id})}"></a>
+ <a class="btn btn-danger btn-sm fa fa-trash"
+ th:href="@{/pkgs/delete(id=${pkg.id})}"
+ onclick="return confirm('Delete?')"></a>
+ </td>
+ </tr>
+ </tbody>
+ </table>
+</div>
+
+<script src="https://cdn.jsdelivr.net/npm/bootstrap@5.3.3/dist/js/bootstrap.bundle.min.js" integrity="sha384-YvpcrYf0tY3lHB60NNkmXc5s9fDVZLESaAA55NDzOxhy9GkcIdslK1eN7N6jIeHz" crossorigin="anonymous"></script>
+</body>
+</html> \ No newline at end of file
diff --git a/src/main/resources/templates/pkgs/sorting.html b/src/main/resources/templates/pkgs/sorting.html
new file mode 100644
index 0000000..199ef68
--- /dev/null
+++ b/src/main/resources/templates/pkgs/sorting.html
@@ -0,0 +1,6 @@
+<th scope="col" th:fragment="sorting(field, label)">
+ <a class="text-decoration-none text-dark"
+ th:href="@{'/pkgs?' + ${sortField!=null ? '&sort=' + field + ',' + (sortField == field ? reverseSortDirection : sortDirection) : ''}}">[[${label}]]</a>
+ <span th:if="${sortField == field}"
+ th:class="${sortDirection == 'asc' ? 'fas fa-arrow-down-short-wide' : 'fas fa-arrow-down-wide-short'}"></span>
+</th> \ No newline at end of file
diff --git a/src/main/resources/templates/profile.html b/src/main/resources/templates/profile.html
new file mode 100644
index 0000000..aa7fb20
--- /dev/null
+++ b/src/main/resources/templates/profile.html
@@ -0,0 +1,84 @@
+<!doctype html>
+<html lang="en" xmlns:th="http://www.thymeleaf.org">
+<head>
+ <meta charset="utf-8">
+ <meta name="viewport" content="width=device-width, initial-scale=1">
+ <title>builddb</title>
+ <link href="https://cdn.jsdelivr.net/npm/bootstrap@5.3.3/dist/css/bootstrap.min.css" rel="stylesheet" integrity="sha384-QWTKZyjpPEjISv5WaRU9OFeRpok6YctnYmDr5pNlyT2bRjXh0JMhjY6hW+ALEwIH" crossorigin="anonymous">
+</head>
+<body>
+
+<div class="container py-5">
+ <div class="rounded border p-4">
+ <h2 class="text-center mb-4">Profile</h2>
+ <hr>
+
+ <div class="row mb-2">
+ <div class="col">First Name</div>
+ <div class="col" th:text="${appUser.firstName}"></div>
+ </div>
+ <div class="row mb-2">
+ <div class="col">Last Name</div>
+ <div class="col" th:text="${appUser.lastName}"></div>
+ </div>
+ <div class="row mb-2">
+ <div class="col">Email</div>
+ <div class="col" th:text="${appUser.email}"></div>
+ </div>
+ <div class="row mb-2">
+ <div class="col">Phone</div>
+ <div class="col" th:text="${appUser.phone}"></div>
+ </div>
+ <div class="row mb-2">
+ <div class="col">Address</div>
+ <div class="col" th:text="${appUser.address}"></div>
+ </div>
+ <div class="row mb-2">
+ <div class="col">Role</div>
+ <div class="col" th:text="${appUser.role}"></div>
+ </div>
+ <div class="row mb-2">
+ <div class="col">Created At</div>
+ <div class="col" th:text="${appUser.createdAt}"></div>
+ </div>
+
+ <hr>
+ <a href="/" class="btn btn-link">Home</a>
+ </div>
+</div>
+
+
+<script src="https://cdn.jsdelivr.net/npm/bootstrap@5.3.3/dist/js/bootstrap.bundle.min.js" integrity="sha384-YvpcrYf0tY3lHB60NNkmXc5s9fDVZLESaAA55NDzOxhy9GkcIdslK1eN7N6jIeHz" crossorigin="anonymous"></script>
+</body>
+</html>
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/src/main/resources/templates/register.html b/src/main/resources/templates/register.html
new file mode 100644
index 0000000..31f240b
--- /dev/null
+++ b/src/main/resources/templates/register.html
@@ -0,0 +1,155 @@
+<!doctype html>
+<html lang="en" xmlns:th="http://www.thymeleaf.org">
+<head>
+ <meta charset="utf-8">
+ <meta name="viewport" content="width=device-width, initial-scale=1">
+ <title>builddb</title>
+ <link href="https://cdn.jsdelivr.net/npm/bootstrap@5.3.3/dist/css/bootstrap.min.css" rel="stylesheet" integrity="sha384-QWTKZyjpPEjISv5WaRU9OFeRpok6YctnYmDr5pNlyT2bRjXh0JMhjY6hW+ALEwIH" crossorigin="anonymous">
+</head>
+<body>
+
+<div class="container py-5">
+ <div class="row">
+ <div class="col-lg-6 mx-auto rounded border p-4">
+ <h2 class="text-center mb-4">Register</h2>
+ <hr />
+
+
+
+ <div th:if="${success}"
+ class="alert alert-success alert-dismissible fade show" role="alert">
+
+ <strong>Account Created Successfully!</strong>
+ <a class="ms-2" href="/login">Login</a>
+ <button type="button" class="btn-close" data-bs-dismiss="alert"
+ aria-label="Close"></button>
+ </div>
+
+
+ <form method="post" th:object="${registerDto}">
+ <input type="hidden" th:name="${_csrf.parameterName}"
+ th:value="${_csrf.token}" />
+
+
+
+ <div class="row mb-3">
+ <label class="col-sm-4 col-form-label">First Name*</label>
+ <div class="col-sm-8">
+ <input class="form-control" th:field="${registerDto.firstName}" >
+ <p th:if="${#fields.hasErrors('firstName')}"
+ th:errorclass="text-danger"
+ th:errors="${registerDto.firstName}" ></p>
+ </div>
+ </div>
+
+ <div class="row mb-3">
+ <label class="col-sm-4 col-form-label">Last Name*</label>
+ <div class="col-sm-8">
+ <input class="form-control" th:field="${registerDto.lastName}" >
+ <p th:if="${#fields.hasErrors('lastName')}"
+ th:errorclass="text-danger"
+ th:errors="${registerDto.lastName}" ></p>
+ </div>
+ </div>
+
+ <div class="row mb-3">
+ <label class="col-sm-4 col-form-label">Email*</label>
+ <div class="col-sm-8">
+ <input class="form-control" th:field="${registerDto.email}" >
+ <p th:if="${#fields.hasErrors('email')}"
+ th:errorclass="text-danger"
+ th:errors="${registerDto.email}" ></p>
+ </div>
+ </div>
+
+ <div class="row mb-3">
+ <label class="col-sm-4 col-form-label">Phone</label>
+ <div class="col-sm-8">
+ <input class="form-control" th:field="${registerDto.phone}" >
+ <p th:if="${#fields.hasErrors('phone')}"
+ th:errorclass="text-danger"
+ th:errors="${registerDto.phone}" ></p>
+ </div>
+ </div>
+
+ <div class="row mb-3">
+ <label class="col-sm-4 col-form-label">Address</label>
+ <div class="col-sm-8">
+ <input class="form-control" th:field="${registerDto.address}" >
+ <p th:if="${#fields.hasErrors('address')}"
+ th:errorclass="text-danger"
+ th:errors="${registerDto.address}" ></p>
+ </div>
+ </div>
+
+ <div class="row mb-3">
+ <label class="col-sm-4 col-form-label">Password*</label>
+ <div class="col-sm-8">
+ <input class="form-control" type="password"
+ th:field="${registerDto.password}" >
+ <p th:if="${#fields.hasErrors('password')}"
+ th:errorclass="text-danger"
+ th:errors="${registerDto.password}" ></p>
+ </div>
+ </div>
+
+ <div class="row mb-3">
+ <label class="col-sm-4 col-form-label">Confirm Password*</label>
+ <div class="col-sm-8">
+ <input class="form-control" type="password"
+ th:field="${registerDto.confirmPassword}" >
+ <p th:if="${#fields.hasErrors('confirmPassword')}"
+ th:errorclass="text-danger"
+ th:errors="${registerDto.confirmPassword}" ></p>
+ </div>
+ </div>
+
+ <div class="row mb-3">
+ <div class="offset-sm-4 col-sm-4 d-grid">
+ <button type="submit" class="btn btn-primary">Submit</button>
+ </div>
+ <div class="col-sm-4 d-grid">
+ <a href="/" class="btn btn-outline-primary">Cancel</a>
+ </div>
+ </div>
+
+ </form>
+ </div>
+ </div>
+</div>
+
+
+<script src="https://cdn.jsdelivr.net/npm/bootstrap@5.3.3/dist/js/bootstrap.bundle.min.js" integrity="sha384-YvpcrYf0tY3lHB60NNkmXc5s9fDVZLESaAA55NDzOxhy9GkcIdslK1eN7N6jIeHz" crossorigin="anonymous"></script>
+</body>
+</html>
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/src/main/resources/templates/user.html b/src/main/resources/templates/user.html
new file mode 100644
index 0000000..2791644
--- /dev/null
+++ b/src/main/resources/templates/user.html
@@ -0,0 +1,53 @@
+<!doctype html>
+<html lang="en">
+<head>
+ <meta charset="utf-8">
+ <meta name="viewport" content="width=device-width, initial-scale=1">
+ <title>builddb</title>
+ <link href="https://cdn.jsdelivr.net/npm/bootstrap@5.3.3/dist/css/bootstrap.min.css" rel="stylesheet" integrity="sha384-QWTKZyjpPEjISv5WaRU9OFeRpok6YctnYmDr5pNlyT2bRjXh0JMhjY6hW+ALEwIH" crossorigin="anonymous">
+</head>
+<body>
+
+<div class="container py-5">
+ <div class="rounded border p-4">
+ <h2 class="text-center mb-4">User Page</h2>
+ <hr>
+ <a href="/" class="btn btn-link">Home</a>
+ </div>
+</div>
+
+
+<script src="https://cdn.jsdelivr.net/npm/bootstrap@5.3.3/dist/js/bootstrap.bundle.min.js" integrity="sha384-YvpcrYf0tY3lHB60NNkmXc5s9fDVZLESaAA55NDzOxhy9GkcIdslK1eN7N6jIeHz" crossorigin="anonymous"></script>
+</body>
+</html>
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+