Java 3 - 2026

Week 5

Week 1 - Setup Pet Clinic App. Deployment using Docker.
Week 2 - Domains, AWS, ER Diagrams and Databases, Understand the Pet Clinic Setup.

Week 3 - MVC, Repository interfaces, Thymeleaf, Internationalization, Controller Unit Tests, 

Week 4 - Use Case Narrative, Sequence Diagram, State Chart Diagram, Create new objects.
Lesson 5 - Users, roles, and permissions setup from Java 2. User Registration.

Lesson 6 - Update and delete existing objects.

Lesson 7 - Homepage with content from the database. Custom not found and error pages.

Lesson 8 - Login and logout. Cookies. User permission access.

Lesson 9 - Edit and delete user profile. Password reset.
Lesson 10 - Filtering and limiting records by category.
Lesson 11 - Web Sockets. Date and currency formatting.
Lesson 12 - Email and SMS messaging. Failed login attempts.
Lesson 13 - Shopping Cart. Payment processing.

Course Plan

  • Title: Register New Student with Auto-School Detection

  • Actor: Anonymous User (to become Student by default)

  • Precondition: User is not logged in.

  • Main Success Scenario:

    1. User clicks "Register" in the top navigation bar.

    2. System displays the Registration Form (Fields: Email, Password).

    3. User enters valid data (e.g., alex.student@kirkwood.edu, StrongPass1!).

    4. System validates:

      • Email format is valid.

      • Email is not already registered.

      • Password meets strength requirements (8+ chars, mix of types).                                                                  (continued)

Use Case Narrative

  • Main Success Scenario (continued):

    1. System parses the email domain (kirkwood.edu) to find the associated School.

    2. System saves the new User with the STUDENT role.

    3. System automatically logs the user in (establishing a session).

    4. System redirects the user to the School Details Page for that domain (e.g., /schools/{id}).

  • Alternative Flows:

    • School Not Found: If the email domain (e.g., @gmail.com) does not match any registered school, the system saves the user but redirects them to a generic "Welcome" or "Select School" page.

    • Validation Fail: System re-displays the form with error messages.

Use Case Narrative

  • This diagram includes logic specific for my Athleagues project: Domain Parsing.

  • When a user enters their email address, we need to extract the part after the @ symbol and look it up in the SchoolRepository.

Sequence Diagram

sequenceDiagram
    actor User
    participant Controller as AuthController
    participant Service as UserService
    participant Security as HttpServletRequest
    participant Repo as SchoolRepository
    participant DB as Database

    User->>Controller: POST /register (email, password)
    
    %% Phase 1: Validation
    Controller->>Controller: Validate Input (@Valid)
    alt Validation Failed
        Controller-->>User: Return "auth/registerForm" (Display Errors)
    else Validation Passed
        
        %% Phase 2: Registration
        Note right of Controller: 1. Create Account
        Controller->>Service: registerNewUser(user)
        Service->>Service: Hash Password (BCrypt)
        Service->>DB: Save User & Roles
        DB-->>Service: Saved User
        Service-->>Controller: Return User

        %% Phase 3: Auto-Login
        Note right of Controller: 2. Auto-Login
        Controller->>Security: login(email, rawPassword)
        Security->>Security: Authenticate & Create Session
        Security-->>Controller: Session Established (JSESSIONID)

        %% Phase 4: School Detection
        Note right of Controller: 3. Domain Resolution
        loop Recursive Domain Check
            Controller->>Repo: findByDomain(currentDomain)
            Repo-->>Controller: Optional<School>
            opt School Not Found
                Controller->>Controller: Strip subdomain (student.kirkwood.edu -> kirkwood.edu)
            end
        end

        %% Phase 5: Redirect
        alt School Found
            Controller->>Controller: Add Flash Attribute (messageSuccess)
            Controller-->>User: Redirect to /schools/{id}
        else No School Found
            Controller->>Controller: Add Flash Attribute (messageWarning)
            Controller-->>User: Redirect to /
        end
    end
  • We will create additional unit tests after creating an AuthController class.

Unit Tests

Users

  • My computer ran slowly on day 9. More could have been accomplished.

2026

  • Use a tool like Insomnia, a simple browser extension, or Postman to send POST requests with a JSON body.

  • Make a new HTTP request. Change GET to POST.

  • Enter "http://localhost:8080/api/auth/register" as the URL.

  • Enter the following data as the body. Change the firstName and email as necessary.
    { "firstName": "Alex",
      "lastName": "Student",
      "email": "alex.student@kirkwood.edu",
      "password": "StrongPassword123" }

  • Go to the Headers tab. Ensure that a header named "Content-Type" with the value "application/json" is present.

  • Click Send. If a 201 status displays.

  • Check your database. A new user and user_role are created.

Register User

  • If you send a POST request with valid credentials to http://localhost:8080/api/auth/login, you will get a 200 OK response.

  • Use this as the JSON body.
    { "email": "alex.student@kirkwood.edu",
      "password": "StrongPassword123" }

  • If the password is wrong, Spring Security will throw a BadCredentialsException.

Authenticate User

  • When we run the unit tests, it currently ignores MySqlIntegrationTests and PostgresIntegrationTests. Since we're using MySQL, let's enable MySqlIntegrationTests.

  • Open MySqlIntegrationTests.java.

  • Replace these lines:
    @Testcontainers(disabledWithoutDocker = true)
    @DisabledInNativeImage
    @DisabledInAotMode

  • With this: @Testcontainers

  • Ensure Docker Desktop is running on your machine. The tests use "Testcontainers" to spin up a real MySQL instance on the fly.

  • Add an assertion to the testFindAll method to verify it worked
    assertThat(vets.findAll()).isNotEmpty();

Ignored Unit Tests

  • Add additional assertions to the MySqlIntegrationTests.findAll method.

  • The MySqlIntegrationTests class uses the @ActiveProfiles("mysql") annotation.

  • In application-mysql.properties, the configuration points specifically to schema.sql and data.sql

MySqlIntegrationTests

@Autowired
private VetRepository vets;

@Autowired
private UserRepository users;

@Autowired
private SchoolRepository schools;

@Test
void testFindAll() {
	vets.findAll();
	vets.findAll(); // served from cache
	assertThat(vets.findAll()).isNotEmpty();
	users.findAll();
	users.findAll(); // served from cache
	assertThat(users.findAll()).isNotEmpty();
	schools.findAll();
	schools.findAll(); // served from cache
	assertThat(schools.findAll()).isNotEmpty();
}

MySqlIntegrationTests

CREATE TABLE IF NOT EXISTS users (
  id INT AUTO_INCREMENT PRIMARY KEY,
  first_name VARCHAR(50),
  last_name VARCHAR(50),
  nickname VARCHAR(50),
  nickname_is_flagged TINYINT DEFAULT 0,
  email VARCHAR(255) NOT NULL,
  public_email TINYINT DEFAULT 0,
  phone VARCHAR(255),
  public_phone TINYINT DEFAULT 0,
  password_hash VARCHAR(255),
  created_at DATETIME DEFAULT CURRENT_TIMESTAMP,
  updated_at DATETIME DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP,
  deleted_at DATETIME,
  UNIQUE INDEX idx_users_email (email),
  INDEX idx_users_name (last_name, first_name)
);

CREATE TABLE IF NOT EXISTS roles (
   id INT AUTO_INCREMENT PRIMARY KEY,
   name VARCHAR(50) NOT NULL UNIQUE,
   description VARCHAR(255)
);

CREATE TABLE IF NOT EXISTS permissions (
   id INT AUTO_INCREMENT PRIMARY KEY,
   name VARCHAR(100) NOT NULL UNIQUE,
   description VARCHAR(255)
);

CREATE TABLE IF NOT EXISTS user_roles (
  user_id INT NOT NULL,
  role_id INT NOT NULL,
  PRIMARY KEY (user_id, role_id),
  FOREIGN KEY (user_id) REFERENCES users(id) ON DELETE CASCADE,
  FOREIGN KEY (role_id) REFERENCES roles(id) ON DELETE CASCADE
);

CREATE TABLE IF NOT EXISTS permission_role (
   permission_id INT NOT NULL,
   role_id INT NOT NULL,
   PRIMARY KEY (permission_id, role_id),
   FOREIGN KEY (permission_id) REFERENCES permissions(id) ON DELETE CASCADE,
   FOREIGN KEY (role_id) REFERENCES roles(id) ON DELETE CASCADE
);

CREATE TABLE IF NOT EXISTS schools (
  id INT AUTO_INCREMENT PRIMARY KEY,
  name VARCHAR(255) NOT NULL,
  domain VARCHAR(255) NOT NULL,
  status_id ENUM('ACTIVE', 'INACTIVE', 'SUSPENDED') DEFAULT 'ACTIVE',
  created_at DATETIME DEFAULT CURRENT_TIMESTAMP,
  updated_at DATETIME DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP,
  deleted_at DATETIME DEFAULT NULL,
  UNIQUE INDEX idx_schools_domain (domain)
  );

CREATE TABLE IF NOT EXISTS locations (
  id INT AUTO_INCREMENT PRIMARY KEY,
  school_id INT NOT NULL,
  parent_location_id INT NULL,
  name VARCHAR(255) NOT NULL,
  description TEXT,
  address VARCHAR(255),
  latitude DECIMAL(8,4),
  longitude DECIMAL(8,4),
  status_id ENUM('DRAFT', 'ACTIVE', 'CLOSED', 'COMING_SOON') DEFAULT 'ACTIVE',
  created_at DATETIME DEFAULT CURRENT_TIMESTAMP,
  updated_at DATETIME DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP,
  deleted_at DATETIME DEFAULT NULL,
  CONSTRAINT fk_locations_school FOREIGN KEY (school_id) REFERENCES schools(id) ON DELETE CASCADE,
  CONSTRAINT fk_locations_parent FOREIGN KEY (parent_location_id) REFERENCES locations(id) ON DELETE SET NULL
);
  • For database unit tests, the mysql profile relies strictly on SQL files found in src/main/resources/db/mysql/.
  • You need to add your database schema to the MySQL-specific schema file in order for the test database to know those tables exist.
  • Open schema.sql and add table definitions for your entities at the bottom.

MySqlIntegrationTests

INSERT IGNORE INTO roles (name, description) VALUES
('SCHOOL_ADMIN', 'Rec Center Admin: Can manage facilities, leagues, scores, and users.'),
('STUDENT', 'Student: Can join leagues, create teams, and view schedules.');

INSERT IGNORE INTO permissions (name, description) VALUES
('MANAGE_OWN_PROFILE', 'Allows user to update their personal info and password.'),
('USE_MESSAGING', 'Allows user to send/receive messages with other participants.'),
('VIEW_LEAGUES', 'Allows user to browse and search available leagues and activities.'),
('REGISTER_FOR_LEAGUE', 'Allows user to register as an individual for a league.'),
('CREATE_TEAM', 'Allows user to create a new team as a captain.'),
('MANAGE_TEAM_INVITATIONS', 'Allows user to accept or decline invitations to a team.'),
('VIEW_OWN_SCHEDULE', 'Allows user to view their personal and team game schedule.'),
('VIEW_STANDINGS', 'Allows user to view league standings and team statistics.'),
('MANAGE_FACILITIES', 'Allows user to C/R/U/D locations, fields, and courts.'),
('MANAGE_SCHEDULES', 'Allows user to C/R/U/D leagues, activities, and games.'),
('MANAGE_REGISTRATIONS', 'Allows user to view and approve team registrations.'),
('MANAGE_SCORES', 'Allows user to enter and confirm game scores.'),
('SEND_ANNOUNCEMENTS', 'Allows user to send messages to individuals, teams, and leagues.');

INSERT IGNORE INTO permission_role (role_id, permission_id) VALUES
((SELECT id FROM roles WHERE name = 'STUDENT'), (SELECT id FROM permissions WHERE name = 'MANAGE_OWN_PROFILE')),
((SELECT id FROM roles WHERE name = 'STUDENT'), (SELECT id FROM permissions WHERE name = 'USE_MESSAGING')),
((SELECT id FROM roles WHERE name = 'STUDENT'), (SELECT id FROM permissions WHERE name = 'VIEW_LEAGUES')),
((SELECT id FROM roles WHERE name = 'STUDENT'), (SELECT id FROM permissions WHERE name = 'REGISTER_FOR_LEAGUE')),
((SELECT id FROM roles WHERE name = 'STUDENT'), (SELECT id FROM permissions WHERE name = 'CREATE_TEAM')),
((SELECT id FROM roles WHERE name = 'STUDENT'), (SELECT id FROM permissions WHERE name = 'MANAGE_TEAM_INVITATIONS')),
((SELECT id FROM roles WHERE name = 'STUDENT'), (SELECT id FROM permissions WHERE name = 'VIEW_OWN_SCHEDULE')),
((SELECT id FROM roles WHERE name = 'STUDENT'), (SELECT id FROM permissions WHERE name = 'VIEW_STANDINGS'));

INSERT IGNORE INTO permission_role (role_id, permission_id) VALUES
((SELECT id FROM roles WHERE name = 'SCHOOL_ADMIN'), (SELECT id FROM permissions WHERE name = 'MANAGE_OWN_PROFILE')),
((SELECT id FROM roles WHERE name = 'SCHOOL_ADMIN'), (SELECT id FROM permissions WHERE name = 'USE_MESSAGING')),
((SELECT id FROM roles WHERE name = 'SCHOOL_ADMIN'), (SELECT id FROM permissions WHERE name = 'VIEW_LEAGUES')),
((SELECT id FROM roles WHERE name = 'SCHOOL_ADMIN'), (SELECT id FROM permissions WHERE name = 'REGISTER_FOR_LEAGUE')),
((SELECT id FROM roles WHERE name = 'SCHOOL_ADMIN'), (SELECT id FROM permissions WHERE name = 'CREATE_TEAM')),
((SELECT id FROM roles WHERE name = 'SCHOOL_ADMIN'), (SELECT id FROM permissions WHERE name = 'MANAGE_TEAM_INVITATIONS')),
((SELECT id FROM roles WHERE name = 'SCHOOL_ADMIN'), (SELECT id FROM permissions WHERE name = 'VIEW_OWN_SCHEDULE')),
((SELECT id FROM roles WHERE name = 'SCHOOL_ADMIN'), (SELECT id FROM permissions WHERE name = 'VIEW_STANDINGS')),
((SELECT id FROM roles WHERE name = 'SCHOOL_ADMIN'), (SELECT id FROM permissions WHERE name = 'MANAGE_FACILITIES')),
((SELECT id FROM roles WHERE name = 'SCHOOL_ADMIN'), (SELECT id FROM permissions WHERE name = 'MANAGE_SCHEDULES')),
((SELECT id FROM roles WHERE name = 'SCHOOL_ADMIN'), (SELECT id FROM permissions WHERE name = 'MANAGE_REGISTRATIONS')),
((SELECT id FROM roles WHERE name = 'SCHOOL_ADMIN'), (SELECT id FROM permissions WHERE name = 'MANAGE_SCORES')),
((SELECT id FROM roles WHERE name = 'SCHOOL_ADMIN'), (SELECT id FROM permissions WHERE name = 'SEND_ANNOUNCEMENTS'));

INSERT IGNORE INTO users (first_name, last_name, email, password_hash) VALUES
('Brett', 'School Admin', 'brett.baumgart@kirkwood.edu', 'hashed_password_for_brett'),
('Alex', 'Student', 'alex.student@student.kirkwood.edu', 'hashed_password_for_alex');

INSERT IGNORE INTO user_roles (user_id, role_id) VALUES
((SELECT id FROM users WHERE email = 'brett.baumgart@kirkwood.edu'), (SELECT id FROM roles WHERE name = 'SCHOOL_ADMIN')),
((SELECT id FROM users WHERE email = 'alex.student@student.kirkwood.edu'), (SELECT id FROM roles WHERE name = 'STUDENT'));

INSERT IGNORE INTO schools (name, domain, status_id) VALUES
('Kirkwood Community College', 'kirkwood.edu', 'active'),
('University of Iowa', 'uiowa.edu', 'active'),
('Iowa State University', 'iastate.edu', 'active'),
('University of Northern Iowa', 'uni.edu', 'active'),
('Coe College', 'coe.edu', 'active'),
('Mount Mercy University', 'mtmercy.edu', 'active'),
('Drake University', 'drake.edu', 'active'),
('Grinnell College', 'grinnell.edu', 'active'),
('Luther College', 'luther.edu', 'active'),
('Simpson College', 'simpson.edu', 'inactive'), -- Testing status
('Wartburg College', 'wartburg.edu', 'active'),
('Cornell College', 'cornellcollege.edu', 'active'),
('Loras College', 'loras.edu', 'active'),
('Clarke University', 'clarke.edu', 'suspended'), -- Testing status
('St. Ambrose University', 'sau.edu', 'active');

INSERT IGNORE INTO locations (school_id, name, description, address, status_id) VALUES
(1, 'Main Campus', 'The primary campus in Cedar Rapids', '6301 Kirkwood Blvd SW, Cedar Rapids, IA', 'active');

INSERT IGNORE INTO locations (school_id, name, description, address, status_id) VALUES
(2, 'Carver-Hawkeye Arena', 'Main sports arena', '1 Elliott Dr, Iowa City, IA', 'active');

INSERT IGNORE INTO locations (school_id, parent_location_id, name, description, status_id) VALUES
(1, 1, 'Michael J Gould Rec Center', 'Student recreation facility', 'active'),
(1, 1, 'Johnson Hall', 'Athletics building and gymnasium', 'active');

INSERT IGNORE INTO locations (school_id, parent_location_id, name, description, status_id) VALUES
(2, 2, 'Main Court', 'The primary basketball court', 'active'),
(2, 2, 'Weight Room', 'Athlete training facility', 'coming_soon');

INSERT IGNORE INTO locations (school_id, parent_location_id, name, description, status_id) VALUES
(1, 3, 'Basketball Court 1', 'North court', 'active'),
(1, 3, 'Basketball Court 2', 'South court', 'active');
  • You must add sample data to the MySQL-specific data file.
  • Open data.sql and add this to the bottom.
  • INSERT IGNORE is a MySQL-specific command that tells the database, "Try to insert this row. If a unique key (like the role name) already exists, just ignore this command and move on without throwing an error."

  • When you run the tests, the Docker container used by your test will initialize with the correct tables and data. Your tests will pass.

testOwnerDetails

  • This test is performing an Integration Test.
  • Unlike a Unit Test which tests a single Java class in isolation, this test spins up your entire Spring Boot application (Server, Database, Security, etc.) and makes a real HTTP request to it, just like a web browser would.
  • @SpringBootTest(webEnvironment = RANDOM_PORT)

    • This tells Spring to start the Tomcat server, but instead of using the default port 8080 (which might be busy), it picks a random available port (e.g., 60982).

  • @LocalServerPort int port;

    • Spring injects that random port number into this variable so your test knows where to send requests.

  • builder.rootUri("http://localhost:" + port):

    • This constructs the full URL (e.g., http://localhost:60982).

testSchoolDetails

  • template.exchange(...)

    • This sends a GET request to the path /owners/1 and waits for the server to process the request (query DB, render HTML)

  • assertThat(...):

    • It verifies that the server responded with "200 OK". The test fails if the ID didn't exist (404) or the server crashed (500).

  • Add this method inside your MySqlIntegrationTests class:

@Test
void testSchoolDetails() {
    RestTemplate template = builder.rootUri("http://localhost:" + port).build();
    ResponseEntity<String> result = template.exchange(RequestEntity.get("/schools/1").build(), String.class);
    assertThat(result.getStatusCode()).isEqualTo(HttpStatus.OK);
    assertThat(result.getBody()).contains("Kirkwood Community College");
}
  • Don't run the test until you implement the "/schools/{id}" mapping in the SchoolController class.

SchoolRepository

  • Instead of returning a plain School object (which might be null), have the findById method return the following:
    @Transactional(readOnly = true)
    Optional<School> findById(Integer id);

SchoolController

  • Add a @GetMapping method to handle requests to "/schools/{schoolId}"

  • The @PathVariable annotation tells Spring to look at the URL itself to find a value after /schools/.

  • {schoolId} defines a "wildcard" in your URL pattern

  • How it works: When a user visits /schools/1, Spring sees the {schoolId} placeholder, grabs the value 1, and forces it into your method parameter int schoolId.

@GetMapping("/schools/{schoolId}")
public ModelAndView showSchool(@PathVariable("schoolId") int schoolId) {
	ModelAndView mav = new ModelAndView("schools/schoolDetails");
	School school = schoolRepository.findById(schoolId)
		.orElseThrow(() -> new ResponseStatusException(HttpStatus.NOT_FOUND, "School not found"));
	mav.addObject(school);
	return mav;
}

SchoolController

  • In Spring MVC, you need to return two things to the user:

  • The View: Which HTML file to display (schoolDetails.html).

  • The Model: The actual data to populate that file (The "School" object).

  • ModelAndView is a container that holds both of these in one object. 

    • You instantiate the object with the view.

    • You call the .addObject() method to add the model.

  • The .orElseThrow() method only exists on Optional objects.

  • You can use Spring's built-in ResponseStatusException to handle requests for schoolId's that don't exist.

schoolDetails.html

  • Create a new view file at: templates/schools/schoolDetails.html

  • It uses the standard PetClinic layout, so it will have the navigation bar and footer automatically. I have added a dynamic header that pulls the school name from your database.

  • th:text="${school.name}": This line ensures that when your test runs assertThat(result.getBody()).contains("Kirkwood Community College"), it will pass because the HTML renders the actual name from the DB.

  • layout: Keeping the th:replace at the top ensures all your CSS/JS assets load, preventing 404s on static resources during browser testing.

<!DOCTYPE html>
<html xmlns:th="https://www.thymeleaf.org"
      th:replace="~{fragments/layout :: layout (~{::body},'schools')}">

<body>

<h2 class="text-center">Welcome to <span th:text="${school.name}">School Name</span></h2>

<div class="container mt-4">

  <div class="alert alert-info">
    <h4 class="alert-heading">Coming Soon!</h4>
    <p>Upcoming leagues, intramural events, and registration for
      <strong th:text="${school.name}">this school</strong> will appear here shortly.
    </p>
  </div>

</div>

</body>
</html>

I18nPropertiesSyncTest 

  • Hopefully you understand the importance of internationalization.

  • But if you are in a prototyping phase or just want to move forward without translating every single string immediately, disabling the test is a valid strategy.

  • Open your I18nPropertiesSyncTest.java and add the annotation to disable the whole class.
    @Disabled("Temporarily disabled until MVP is complete")

  • If only one specific test method is annoying you, you can disable just that one.
    @Test
    @Disabled
    void testToDisable() { /* ... */ }

  • Turn on Docker Desktop before running unit tests.

  • When we run unit tests, CrashControllerIntegrationTests fails because of a conflict between the original test design and our new Security implementation.

  • Our new SecurityConfig (and AuthController) requires a database connection to load (to verify users). This conflict can cause the test context to fail or behave unpredictably.

  • We need to remove the "No-Database" optimization. Since our application now has complex security dependencies (UserService, Roles, Database).

  • Remove lines 95-99—the inner TestConfiguration class and the @SpringBootApplication annotation entirely.

CrashControllerIntegrationTests

  • It is possible for a URL like `localhost:8080/schools/kirkwood` to be mapped to `localhost:8080/schools/1`.

    • Schools can be looked up by their id, or by the domain, minus the `.edu` part

  • Not recommended:

  • ​If you map `/{slug}` at the root level, `localhost:8080/{slug}`, Spring will try to match everything against it.

  • For example, if a user goes to /login, Spring thinks "login" is a school slug and checks the DB for login.edu it, which returns 404.

  • If a user goes to `/css/style.css`, Spring thinks "css" is a school slug and returns 404.

  • If you absolutely require root URLs (like linkedin.com/username), you have to write complex exclusion rules.

  • Using `/schools/kirkwood` is much safer and cleaner.

Slug

  • Here is how to implement "Smart URLs" that accept both IDs (/schools/1) and Slugs (/schools/kirkwood) in the same Controller.

  • Update the @GetMapping to use Regular Expressions to tell Spring: "If the URL is a number, call the ID method. If it is text, call the Slug method."

Slug

// Matches ONLY numbers (e.g., /schools/1)
@GetMapping("/schools/{schoolId:\\d+}")
public ModelAndView showSchoolById(@PathVariable("schoolId") int schoolId) {
	ModelAndView mav = new ModelAndView("schools/schoolDetails");
	School school = schoolRepository.findById(schoolId)
		.orElseThrow(() -> new ResponseStatusException(HttpStatus.NOT_FOUND, "School not found"));
	mav.addObject(school);
	return mav;
}

// Matches text (e.g., /schools/kirkwood)
@GetMapping("/schools/{slug:[a-zA-Z-]+}")
public ModelAndView showSchoolBySlug(@PathVariable("slug") String slug) {
	// Reconstruct the domain (User asked to assume ".edu")
	String fullDomain = slug + ".edu";

	ModelAndView mav = new ModelAndView("schools/schoolDetails");
	School school = schoolRepository.findByDomain(fullDomain)
		.orElseThrow(() -> new ResponseStatusException(HttpStatus.NOT_FOUND, "School not found"));
	mav.addObject(school);
	return mav;
}

Java 3 - Week 5

By Marc Hauschildt

Java 3 - Week 5

  • 130