#OK. I forfeit. I need your help for Spring Boot and Thymeleaf integration

1 messages · Page 1 of 1 (latest)

fiery jay
#

Hello,

I'm a bit lost with how to correctly display stuff on Thymeleaf.

Let's say I have a User entity :

@Entity
@Table(name = "users", uniqueConstraints = { @UniqueConstraint(name = "uk_users_username", columnNames = "username"),
        @UniqueConstraint(name = "uk_users_email", columnNames = "email") })
public class User extends BaseEntity {

    @Column(name = "username", nullable = false, unique = true, length = 50)
    @NotBlank
    @Size(min = 3, max = 50)
    private String username;

    @Column(name = "email", nullable = false, unique = true, length = 254)
    @NotBlank
    @Size(max = 254)
    private String email;

    @Column(name = "password_hash", nullable = false, length = 100)
    @NotBlank
    @Size(min = 60, max = 100) // longueur hash bcrypt
    private String passwordHash;

    private boolean enabled = false;

    private boolean emailVerified = false;
        // getters and setters
}

and I want a userpage with something like "Hello {Username}, welcome to your homepage !. You're using this {email} right now."

What is the correct course of action ? As always, I've tried before asking. I navigate between overly complicated tutorials with notions I've never even seen or weird stuff that doesn't make any sense.

Here's the last try I've made :

  1. A DTO to expose only what's needed :
public record UserResponse(String email, String username) {
    public static UserResponse from(User user) {
        return new UserResponse(user.getEmail(), user.getUsername());
    }
}
hybrid ospreyBOT
#

<@&1004656351647117403> please have a look, thanks.

fiery jay
#
  1. A UserService :
@Service
public class UserService {

    private final UserRepository repo;

    private final PasswordEncoder encoder;

    public UserService(UserRepository repo, PasswordEncoder encoder) {
        this.repo = repo;
        this.encoder = encoder;
    }

    @Transactional
    public void register(RegisterRequest f) {
        var username = f.getUsername().trim();
        var email = f.getEmail().trim().toLowerCase();

        this.repo.findByUsername(username).ifPresent((u) -> {
            throw new IllegalArgumentException("username_taken");
        });
        this.repo.findByEmail(email).ifPresent((u) -> {
            throw new IllegalArgumentException("email_taken");
        });

        var newUser = new User();
        newUser.setUsername(username);
        newUser.setEmail(email);
        newUser.setPasswordHash(this.encoder.encode(f.getPassword()));

        this.repo.save(newUser);
    }

    public Optional<UserResponse> userResponse(String email) {
        return this.repo.findByEmail(email.toLowerCase()).map(UserResponse::from);
    }

}
#
  1. A JpaUserDetailsService (which I'm not totally sure I understand conceptually wise)
@Service
public class JpaUserDetailsService implements UserDetailsService {

    @Autowired
    private final UserRepository users;

    public JpaUserDetailsService(UserRepository users) {
        this.users = users;
    }

    @Override
    @Transactional(readOnly = true)
    public UserDetails loadUserByUsername(String login) throws UsernameNotFoundException {
        var email = login.trim().toLowerCase();
        var u = this.users.findByEmail(email).orElseThrow(() -> new UsernameNotFoundException("User not found"));
        return org.springframework.security.core.userdetails.User.withUsername(u.getEmail())
            .password(u.getPasswordHash())
            .disabled(!u.isEnabled())
            .build();

    }
#
  1. The part of my controller supposed to expose the DTO to Thymeleaf :
@GetMapping("/account")
    public String userPage(Authentication auth, Model model) {
        var email = auth.getName();
        var profile = this.userService.userResponse(email)
            .orElseThrow(() -> new IllegalStateException("Utilisateur introuvable"));
        model.addAttribute("user", profile);
        return "user/userpage";
    }
  1. The template
<!DOCTYPE html>
<html xmlns:th="https://www.thymeleaf.org"
      th:replace="~{fragments/base :: layout (~{::title}, ~{::body})}"
      lang="fr">
<head>
    <meta charset="UTF-8">
    <title>Welcome</title>
</head>
<body>
<section class="container d-flex justify-content-center py-4">
    <div class="card mt-4" style="max-width: 75rem;">
        <div th:objetc="${user}" class="card-body">
            <h2>Bienvenue, <span th:text="*{username}">username</span></h2>
            <p class="card-text">Tu es sur ta page de profil !</p>
        </div>
    </div>
</section>
</body>
</html>

Before even trying to solve the problem I have (a beautiful and poetic 500 error), is it even the good way of doing things ?

fossil nacelle
#

You have a typo in your thymeleaf template th:objetc="${user}" -> th:object="${user}"

fiery jay
#

Eh.. That was one stupid mistake and of course it works right now, thank you.

But I'm still wondering about the "best practice" aspect. Is it how I'm supposed to do it ?

#

On Django for example, you call the object from the database, and you pass it to the view so you can display object attributes. Ok, here, there is a DTO in the middle. But still, I don't have the impression I'm doing something similar

#
var email = auth.getName(); 

for example. Should'nt we use something safer like the ID ?

#

And having :

A controller, calling a service, calling the repository to then send a DTO feels already a lot enough for me for such a trivial functionality. Why adding the JpaUserDetailsService ? I understand it's a link between security and the repo, but shouldn't it be integrated in the service directly ?

barren elbow
#

remember the reason for the DTO is because automatically going from JPA -> JSON is a horrible thing

#

if you are going to render html the downside of just using the JPA entity is coupling your view code to JPA

#

you can accept that downside if you wish

barren elbow
#

you have to remember that all of this is made up and stupid

fiery jay
#

That's interesting. I was so focused on API at first but then afterward shifted toward a MVC structure so I can go one step at a time.

I could do things simpler for this MVC-SSR oriented tutorial.

fiery jay
patent ermine
#

Have you heard about pebble?

barren elbow
#

but mind you simplest != easiest

#

it means "lowest amount of interacting machinery"

fiery jay
# patent ermine Have you heard about pebble?

Yeah. A template engine like twig and such.

I can't say I'm not interested but it's either frontend framework or thymeleaf on every job offer. That's why I didn't pursue further and stayed on Thymeleaf

patent ermine
barren elbow
#

at least i think so

patent ermine
#

Ok so the probloem with template engine like these, is that they add java logic to the template, it always go wrong pretty fast.

fiery jay
#

Ok I get it now. I can do everything in the controller. Or controller + repo. Or controller + service + repo. And add DTO if I want. I think I'm gonna do several pages with a different methodology for each but all of them getting the same result. This tuto app is my "how do you do that again?" knowledge base for future projects so it makes sense in that context

#

Damn it's hard to get the first time. Tutorials always make things so hard and complex for nothing. I'd love a documentation like the Symfony one. Best learning resource I've ever seen for a framework

barren elbow
fiery jay
#

Well symfony has an interesting approach where it's :

  • framework concept
  • simple application of concept
  • push further
  • examples.

I love this approach

#

The doctrine pages are a perfect example of it. You want to learn persistance ?! Well this is the simple way. You want the hard way now ? Hold my beer

barren elbow
#

remember simple != easy

fiery jay
#

Yeah sorry, subtlety lost with translation.

barren elbow
#

its

                 simple
                   ^
                   |
                   |
                   |
                   |
easy <-------------|----------------> hard
                   |
                   |
                   |
                   |
                   v
                 complex
#

for instance, making this graph by hand was simple, but it took awhile

#
                 simple
                   ^
                   |   X
                   |
                   |   
                   |
easy <-------------|----------------> hard
                   |
                   |
                   |
                   |
                   v
                 complex
#

JPA and other ORMs make it easy to query the database but also introduce a lot of moving parts and hard to reason about stuff that can kill an app's perf

#
                 simple
                   ^
                   |   X
                   |
                   |   
                   |
easy <-------------|----------------> hard
                   |
                   |
                   |
                   |
          X        v
                 complex
#

the spectrum comes up very often

fiery jay
#

Yeah I perfectly get the difference. It's just bad words chosen as it's not my native language and I sometimes translate a bit rough

barren elbow
#

all good

#

i also just wanted to draw that

fiery jay
#

Hehe

barren elbow
#

i'll be saving it for later

fiery jay
#

I'm a diagram and illustration kind of guy, I get it

#

Worked for years as a sysadmin and tech support and I firmly believe a good drawing is better than hundreds of words

#

I was the "IT guy speaking like a human" of my department 😄

barren elbow
#

i will say that the AI profile pic made a different first impression

fiery jay
#

Curious about the impression it gave.

For my defense, I'm reaaaally bad at drawing
and wanted a kitten/octopus profile pic for obvious reasons

barren elbow
#

and its negative: its either "young kid" or "business idiot"

fiery jay
#

Oh... Illustration received as "drawing" ? I meant like "illustration by example" or "simple drawings to explain stuff". Not the artistic ones

#

Well young kid, I'm 35... I guess I'm the business idiot then

barren elbow
patent ermine
# barren elbow

Btw this pic, is way better, it show originality at least.

#

You can even ask question about it.

barren elbow
#

the general rule is: something you slapped together or asked a 3 year old to do, gives a better impression than AI art

patent ermine
#

Like why are the eye diamond?

barren elbow
#

made it worse

#

but for real, just give it a shot

patent ermine
fiery jay
#

Not to diss your efforts, I mean there's hidden talent for sure, but's that's nightmare fuel 😄

#

Looks like a sleep paralysis

#

I will make mine when I have some time. Right now I need to quickly improve in my field so I can be at least employable in the upcoming months

fiery jay
#

I think I got it.

For educational purposes, I made 4 routes to serve the same goal : displaying the username on a single Thymeleaf template.

First one the controller does everything, even the JQL request. So all concerns in the same place.

Second one, the controller makes use of the User repository.

Third one, the controller makes use of a service, which is responsible for the user fetching with the repository.

Fourth one does the same as #3 but the results are mapped to a DTO.

All 4 are working. I think I'm getting the hang of it