Skip to content

Commit

Permalink
Merge pull request #51 from PaulKreft/edit-username
Browse files Browse the repository at this point in the history
Edit username
  • Loading branch information
PaulKreft authored Feb 29, 2024
2 parents 250d406 + 1ff950a commit 8c192e4
Show file tree
Hide file tree
Showing 32 changed files with 383 additions and 185 deletions.
Original file line number Diff line number Diff line change
@@ -1,29 +1,14 @@
package de.neuefische.paulkreft.backend.exception;

import org.springframework.beans.factory.annotation.Value;
import org.springframework.http.HttpStatus;
import org.springframework.security.core.userdetails.UsernameNotFoundException;
import org.springframework.security.oauth2.core.OAuth2AuthenticationException;
import org.springframework.web.bind.annotation.ControllerAdvice;
import org.springframework.web.bind.annotation.ExceptionHandler;
import org.springframework.web.bind.annotation.ResponseStatus;
import org.springframework.web.servlet.ModelAndView;
import org.springframework.web.servlet.resource.NoResourceFoundException;

@ControllerAdvice
public class GlobalExceptionHandler {
private final String environment;

public GlobalExceptionHandler(@Value("${app.environment}") String environment) {
this.environment = environment;
}

@ExceptionHandler({NoResourceFoundException.class})
public ModelAndView noResourceFoundHandler(NoResourceFoundException e) {
String basePath = environment.equals("production") ? "/" : "http://localhost:5173/";
String route = environment.equals("production") ? "" : e.getResourcePath();
return new ModelAndView("redirect:" + basePath + route);
}

@ExceptionHandler({EmailAlreadyRegisteredException.class})
@ResponseStatus(HttpStatus.CONFLICT)
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,39 @@
package de.neuefische.paulkreft.backend.fallback;

import org.springframework.context.annotation.Configuration;
import org.springframework.core.io.ClassPathResource;
import org.springframework.core.io.Resource;
import org.springframework.web.servlet.config.annotation.ResourceHandlerRegistry;
import org.springframework.web.servlet.config.annotation.WebMvcConfigurer;
import org.springframework.web.servlet.resource.PathResourceResolver;

import java.io.IOException;

@Configuration
public class Fallback implements WebMvcConfigurer {

public static final String DEFAULT_STARTING_PAGE = "static/index.html";

static class ReactRoutingPathResourceResolver extends PathResourceResolver {
@Override
protected Resource getResource(String resourcePath, Resource location) throws IOException {
var requestedResource = location.createRelative(resourcePath);

// Is this a request to a real file?
if (requestedResource.exists() && requestedResource.isReadable()) {
return requestedResource;
}

// It seems to be only a frontend-routing request (Single-Page-Application).
return new ClassPathResource(DEFAULT_STARTING_PAGE);
}
}

@Override
public void addResourceHandlers(ResourceHandlerRegistry registry) {
registry.addResourceHandler("/**")
.addResourceLocations("classpath:/static/")
.resourceChain(true)
.addResolver(new ReactRoutingPathResourceResolver());
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -91,6 +91,18 @@ public UserGet createUser(String name, String email, String password) {
}

public UserGet updateUser(User user) {
if(user.name() == null) {
throw new IllegalArgumentException("Username cannot be null");
}

if(user.name().length() > 16) {
throw new IllegalArgumentException("Username too long");
}

if(user.name().length() < 5) {
throw new IllegalArgumentException("Username too short");
}

return new UserGet(usersRepo.save(user));
}

Expand Down

This file was deleted.

Original file line number Diff line number Diff line change
@@ -0,0 +1,53 @@
package de.neuefische.paulkreft.backend.fallback;

import org.junit.jupiter.api.Assertions;
import org.junit.jupiter.api.Test;
import org.springframework.core.io.ClassPathResource;
import org.springframework.core.io.Resource;

import java.io.IOException;

import static org.mockito.Mockito.mock;
import static org.mockito.Mockito.when;

class FallbackTest {

@Test
void expectRelativeResource_ifItExists() throws IOException {

// GIVEN
var resolver = new Fallback.ReactRoutingPathResourceResolver();
var location = mock(Resource.class);
var relativeLocation = mock(Resource.class);
var resourcePath = "index.html";
when(location.createRelative(resourcePath)).thenReturn(relativeLocation);
when(relativeLocation.exists()).thenReturn(true);
when(relativeLocation.isReadable()).thenReturn(true);

// WHEN
var actual = resolver.getResource(resourcePath, location);

// THEN
Assertions.assertEquals(relativeLocation, actual);
}

@Test
void expectIndexHtml_ifRequestedResourceDoesNotExist() throws IOException {

// GIVEN
var resolver = new Fallback.ReactRoutingPathResourceResolver();
var location = mock(Resource.class);
var relativeLocation = mock(Resource.class);
var resourcePath = "index.html";
when(location.createRelative(resourcePath)).thenReturn(relativeLocation);
when(relativeLocation.exists()).thenReturn(false);

// WHEN
var actual = resolver.getResource(resourcePath, location);

// THEN
ClassPathResource expected = new ClassPathResource("static/index.html");
Assertions.assertEquals(expected, actual);
}

}
Original file line number Diff line number Diff line change
Expand Up @@ -50,7 +50,7 @@ class UserControllerIntegrationTest {
@BeforeEach
public void instantiateTestUser() {
Instant now = Instant.parse("2016-06-09T00:00:00.00Z");
testUser = new User("123", "Paul", "[email protected]", "", now, now);
testUser = new User("123", "Paule", "[email protected]", "", now, now);
}

@Test
Expand Down Expand Up @@ -81,7 +81,7 @@ void testGetUser_whenReturningUserIsLoggedIn_returnReturningUser() throws Except
{
"id":"123",
"email":"[email protected]",
"name":"Paul",
"name":"Paule",
"lastActive": "2016-06-09T00:00:00Z",
"createdAt": "2016-06-09T00:00:00Z"
}
Expand Down Expand Up @@ -178,7 +178,7 @@ void testGetUser_whenReturningEmailUserIsLoggedIn_returnReturningUser() throws E
.andExpect(content().json(
"""
{"id":"123",
"name":"Paul",
"name":"Paule",
"email":"[email protected]",
"lastActive":"2016-06-09T00:00:00Z",
"createdAt":"2016-06-09T00:00:00Z"
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -10,10 +10,13 @@
import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.Test;
import org.junit.jupiter.api.function.Executable;
import org.junit.jupiter.params.ParameterizedTest;
import org.junit.jupiter.params.provider.MethodSource;
import org.mockito.Mockito;

import java.security.Principal;
import java.time.Instant;
import java.util.stream.Stream;

import static org.junit.jupiter.api.Assertions.*;
import static org.mockito.ArgumentMatchers.any;
Expand Down Expand Up @@ -167,7 +170,6 @@ void existsByEmailTest_whenCheckExistingUser_returnTrue() {
verifyNoMoreInteractions(idService, usersRepo, timeService, githubService);
}


@Test
void existsByEmailTest_whenCheckNonExistingUser_returnFalse() {
// Given
Expand All @@ -182,4 +184,42 @@ void existsByEmailTest_whenCheckNonExistingUser_returnFalse() {
verify(usersRepo, times(1)).existsUserByEmail(nonExistingEmail);
verifyNoMoreInteractions(idService, usersRepo, timeService, githubService);
}

@Test
void updateUserTest_whenUpdateUserGet_ReturnUserGet() {
// Given
when(usersRepo.save(testUser)).thenReturn(testUser);

// When
UserGet expected = new UserGet(testUser);
UserGet actual = userService.updateUser(testUser);

// Then
assertEquals(expected, actual);
verify(usersRepo, times(1)).save(testUser);
verifyNoMoreInteractions(idService, usersRepo, timeService, githubService);
}

@ParameterizedTest
@MethodSource("provideUsernames")
void updateUserTest_whenInvalidUsername_throwIllegalArgumentException(String invalidUsername) {
// Given
when(usersRepo.save(testUser)).thenReturn(testUser);

// When
User updatedUser = testUser.withName(invalidUsername);
Executable executable = () -> userService.updateUser(updatedUser);

// Then
assertThrows(IllegalArgumentException.class, executable);
verifyNoMoreInteractions(idService, usersRepo, timeService, githubService);
}

private static Stream<String> provideUsernames() {
return Stream.of(
"NameWithSeventeen", // Too long
"Name", // Too short
null // Null
);
}
}
12 changes: 12 additions & 0 deletions frontend/src/App.css
Original file line number Diff line number Diff line change
@@ -1,3 +1,15 @@
input:focus {
outline: none;
}

/* Chrome, Safari, Edge, Opera */
input::-webkit-outer-spin-button,
input::-webkit-inner-spin-button {
-webkit-appearance: none;
margin: 0;
}

/* Firefox */
input[type=number] {
-moz-appearance: textfield;
}
6 changes: 5 additions & 1 deletion frontend/src/App.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -47,6 +47,10 @@ function App() {
navigate("/");
});

const updateUser = (user: User) => {
setUser(user);
};

if (isLoading) {
return (
<div className="flex h-screen w-screen items-center justify-center">
Expand All @@ -63,7 +67,7 @@ function App() {
<Route path="/play" element={<Play userId={user?.id} />} />
<Route path="/multiplayer" element={<LobbyEntrance user={user} />} />
<Route path="/multiplayer/lobby/:id" element={<MultiplayerSession user={user} />} />
<Route path="/profile" element={<Profile user={user} />} />
<Route path="/profile" element={<Profile user={user} updateUser={updateUser} />} />
<Route path="/login" element={<Login />} />
<Route path="/login/email" element={<EmailLogin login={login} />} />
<Route path="/signup" element={<SignUp />} />
Expand Down
12 changes: 12 additions & 0 deletions frontend/src/assets/checkmark.svg
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
2 changes: 1 addition & 1 deletion frontend/src/components/Button.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,7 @@ type ButtonProps = {
export const Button: React.FC<ButtonProps> = ({ children, color, isActive, onClick }) => {
return (
<button
className={cn("xs:px-5 xs:py-2 xs:text-xl rounded-2xl px-3 py-1 text-lg")}
className={cn("rounded-2xl px-3 py-1 text-lg xs:px-5 xs:py-2 xs:text-xl")}
style={{
backgroundColor: isActive ? color : "white",
color: isActive ? "white" : color,
Expand Down
19 changes: 7 additions & 12 deletions frontend/src/components/EmailLogin.tsx
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
import React, { FormEvent, useEffect, useState } from "react";
import { cn } from "../lib/utils.ts";
import {Spinner} from "./Spinner.tsx";
import { Spinner } from "./Spinner.tsx";

type EmailLoginProps = {
login: (email: string, password: string) => void;
Expand Down Expand Up @@ -33,15 +33,15 @@ export const EmailLogin: React.FC<EmailLoginProps> = ({ login }) => {

if (isLoggingIn) {
return (
<div className="flex flex-1 items-center justify-center">
<Spinner />
</div>
<div className="flex flex-1 items-center justify-center">
<Spinner />
</div>
);
}

return (
<div className="mx-auto flex flex-1 flex-col items-center justify-center pb-20">
<div className="flex flex-col items-center rounded-2xl border-2 border-black px-20 pb-24 pt-12">
<div className="flex flex-col items-center rounded-2xl border-black px-20 pb-24 pt-12 sm:border-2">
<h2 className="pb-16 text-4xl">Log in with Email</h2>
<form className="" onSubmit={loginWithEmail} noValidate>
<div className="mb-1 pl-1 text-lg">Email</div>
Expand All @@ -56,9 +56,7 @@ export const EmailLogin: React.FC<EmailLoginProps> = ({ login }) => {
onChange={(event) => setEmail(event.target.value)}
required
/>
<div
className={cn("mt-1 text-center text-sm", email && !isEmailValid ? "text-red" : "text-transparent")}
>
<div className={cn("mt-1 text-center text-sm", email && !isEmailValid ? "text-red" : "text-transparent")}>
Invalid email format
</div>

Expand All @@ -75,10 +73,7 @@ export const EmailLogin: React.FC<EmailLoginProps> = ({ login }) => {
required
/>
<div
className={cn(
"mt-1 text-center text-sm",
password && !isPasswordValid ? "text-red" : "text-transparent",
)}
className={cn("mt-1 text-center text-sm", password && !isPasswordValid ? "text-red" : "text-transparent")}
>
Invalid password format
</div>
Expand Down
Loading

0 comments on commit 8c192e4

Please sign in to comment.