Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
40 commits
Select commit Hold shift + click to select a range
27c25a1
Merge pull request #4 from jtutor26/bug-fixes
jtutor26 Mar 13, 2026
e30ad5f
Created README.md
jtutor26 Mar 16, 2026
c0d5d97
Merge pull request #8 from jtutor26/documentation/readme
jtutor26 Mar 16, 2026
7464ead
Merge pull request #9 from jtutor26/bug-fixes
jjuarez26base Mar 16, 2026
4053e02
Use env vars for DB config and update ddl-auto
jtutor26 Mar 16, 2026
467c33f
Create Dockerfile
jtutor26 Mar 16, 2026
7dfe75d
styling for login & register page, added logo for landing
Aleahb00 Mar 17, 2026
69b47d3
Update README.md
jtutor26 Mar 17, 2026
2dfb5fe
Update deployment URL in README
jtutor26 Mar 17, 2026
b5a458d
Merge pull request #10 from jtutor26/deployment
jtutor26 Mar 17, 2026
e3628ac
Update live deployment URL in README
jtutor26 Mar 17, 2026
3bc1a67
Refactored logic into service layer
jtutor26 Mar 17, 2026
8f8309a
Created Repository Layer Test
jtutor26 Mar 17, 2026
7307e70
Created Service Tests
jtutor26 Mar 17, 2026
7e272d2
Merge branch 'main' into frontend
Aleahb00 Mar 17, 2026
4516ab4
dasboard say whaat
maybe-zala Mar 17, 2026
32c375a
Merge remote-tracking branch 'origin/main'
maybe-zala Mar 17, 2026
7775484
Parameterize database connection properties
jtutor26 Mar 17, 2026
0103274
Update datasource and server port properties
jtutor26 Mar 17, 2026
4cd835f
Fixed stylesheet errors
jtutor26 Mar 17, 2026
4bebdc2
Created Controller Layer Tests
jtutor26 Mar 17, 2026
268779b
Security Layer Tests
jtutor26 Mar 17, 2026
6d5f248
Merge pull request #11 from jtutor26/frontend
Aleahb00 Mar 17, 2026
1ff620e
Merge branch 'main' of github.com:jtutor26/hyperlink into tests
jtutor26 Mar 18, 2026
57bd205
New dashboard controller test case
jtutor26 Mar 18, 2026
a25b1f2
Added Test Cases
jtutor26 Mar 18, 2026
2027013
Update README.md
jtutor26 Mar 18, 2026
6834d89
Readme.md update
jtutor26 Mar 18, 2026
e60d4b1
continuing
maybe-zala Mar 18, 2026
02fc148
g
maybe-zala Mar 18, 2026
80b0e69
Added Nav bars to every HTML page, and put the necessary CSS in the a…
Mar 18, 2026
1816ac5
added templates template and styling, configured security to allow us…
Aleahb00 Mar 18, 2026
6bbb73f
Added or changed links in index and profile pages. Added logo to logi…
Mar 18, 2026
cb4ec73
Merge branch 'main' into temps
Aleahb00 Mar 18, 2026
03b9a59
Merge pull request #14 from jtutor26/temps
Aleahb00 Mar 18, 2026
50ef72d
load error
maybe-zala Mar 19, 2026
7830706
error fix
maybe-zala Mar 19, 2026
f955d5d
nav fix for landing, auth, profile, user profile templates example te…
Aleahb00 Mar 19, 2026
aa5ecf5
nav bug fixes, templates controller refacter to validate authenticati…
Aleahb00 Mar 19, 2026
243b1a9
Merge pull request #15 from jtutor26/temps
Aleahb00 Mar 19, 2026
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Binary file added .DS_Store
Binary file not shown.
14 changes: 14 additions & 0 deletions Dockerfile
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
FROM eclipse-temurin:25-jdk AS build
WORKDIR /app
COPY mvnw .
COPY .mvn .mvn
COPY pom.xml .
COPY src src
RUN chmod +x ./mvnw
RUN ./mvnw clean package -DskipTests

FROM eclipse-temurin:25-jre
WORKDIR /app
COPY --from=build /app/target/HyprLink-0.0.1-SNAPSHOT.jar app.jar
EXPOSE 8080
ENTRYPOINT ["java", "-jar", "app.jar"]
189 changes: 189 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,189 @@
# HyperLink (Spring Unit Project)

HyperLink is a Spring Boot + Thymeleaf web app for creating a personal "link in bio" profile page.
Users can register, sign in, edit their profile, and share a public profile page.

## Current project status

- Core authentication flow is implemented (`/login`, `/register`, secured routes).
- Dashboard profile editing is implemented, including social links and style options.
- File uploads are supported for profile and background images from the dashboard.
- Public profile rendering is implemented at `/profile/{id}`.
- Seed data is loaded at startup when the user table is empty.
- Automated tests are in place and currently passing (`48/48` in latest local run).

## What it does

- User registration and login with Spring Security.
- Password hashing with BCrypt.
- Dashboard editing for:
- display name
- age / pronouns / bio
- profile image URL or uploaded profile image
- uploaded/custom background image
- theme + link/button/text style options
- social links
- Public profile page by user ID (`/profile/{id}`).

## Tech stack

- Java 25
- Spring Boot 4.0.3
- Spring MVC + Thymeleaf
- Spring Data JPA (Hibernate)
- Spring Security
- PostgreSQL (runtime configuration)
- H2 in-memory database for tests
- Docker (multi-stage image)
- Maven Wrapper (`./mvnw`)

## Project structure

```text
src/main/java/com/basecamp/HyprLink
config/
DataLoader.java
controller/
AuthController.java
DashboardController.java
ProfileController.java
entity/
User.java
SocialLink.java
repository/
UserRepository.java
security/
SecurityConfig.java
CustomUserDetailService.java
service/
AuthService.java
DashboardService.java
ProfileService.java

src/main/resources
templates/
index.html
dashboard.html
profile.html
auth/login.html
auth/register.html
static/css/
auth.css
dashboard.css
default.css
landing.css
login.css
register.css
```

## Prerequisites

- JDK 25
- Maven (or use the included Maven Wrapper)
- PostgreSQL database for local app runtime
- (Optional) Docker

## Configuration

Main config: `src/main/resources/application.properties`

```properties
spring.datasource.url=${DB_URL}
spring.datasource.username=${DB_USERNAME}
spring.datasource.password=${DB_PASSWORD}
spring.datasource.driver-class-name=org.postgresql.Driver
spring.jpa.hibernate.ddl-auto=create
server.port=${PORT:8080}
```

Set local runtime variables:

```bash
export DB_URL="jdbc:postgresql://localhost:5432/your_database"
export DB_USERNAME="postgres"
export DB_PASSWORD="your_password"
```

## Run locally

```bash
./mvnw spring-boot:run
```

Then open:

- `http://localhost:8080/`
- `http://localhost:8080/login`
- `http://localhost:8080/register`

## Run with Docker

```bash
docker build -t hyperlink-app .
docker run -p 8080:8080 \
-e DB_URL="jdbc:postgresql://<host>:5432/<db>" \
-e DB_USERNAME="<username>" \
-e DB_PASSWORD="<password>" \
hyperlink-app
```

## Main routes

- `GET /` - landing/welcome page (template `index.html`)
- `GET /login` - login form
- `GET /register` - registration form
- `POST /register` - account creation
- `GET /dashboard` - authenticated profile editor
- `POST /dashboard/save` - save dashboard profile changes
- `GET /profile/{id}` - public profile page
- `GET /images/background-templates/{filename}` - serve background template images

## Seeded demo users

`DataLoader` creates these users on first startup when the database is empty:

- `johndoe` / `password123`
- `janesmith` / `password123`

## Testing

Run all tests:

```bash
./mvnw test
```

### Latest verified test result

- Date: `2026-03-18`
- Command: `./mvnw test`
- Result: `BUILD SUCCESS`
- Totals: `48 tests, 0 failures, 0 errors, 0 skipped`

### Test suites and cases

| Test class | Layer | Cases |
| --- | --- | ---: |
| `UserRepositoryTest` | Repository integration (`@SpringBootTest`, H2) | 17 |
| `SecurityConfigTest` | Security config unit | 4 |
| `CustomUserDetailServiceTest` | Security service unit | 2 |
| `ProfileControllerTest` | Controller unit | 3 |
| `DashboardControllerTest` | Controller unit | 3 |
| `AuthControllerTest` | Controller unit | 3 |
| `DashboardServiceTest` | Service unit | 7 |
| `AuthServiceTest` | Service unit | 5 |
| `ProfileServiceTest` | Service unit | 3 |
| `SpringUnitProjectApplicationTests` | Context load smoke test | 1 |

### Notes on test configuration

- Test properties are in `src/test/resources/application.properties`.
- Tests use H2 (`jdbc:h2:mem:testdb`) instead of PostgreSQL.
- Repository tests run with Spring context and transactions.
- Most controller/service/security tests use JUnit 5 + Mockito.

## Notes

- Security allows public access to `/`, `/login`, `/register`, `/profile/**`, and `/css/**`.
- Other routes require authentication.
- The app currently sets `spring.jpa.hibernate.ddl-auto=create` in main runtime config.
4 changes: 4 additions & 0 deletions pom.xml
Original file line number Diff line number Diff line change
Expand Up @@ -92,6 +92,10 @@
<artifactId>h2</artifactId>
<scope>test</scope>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-validation</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-security</artifactId>
Expand Down
Binary file added src/.DS_Store
Binary file not shown.
Binary file added src/main/.DS_Store
Binary file not shown.
Binary file added src/main/java/.DS_Store
Binary file not shown.
Binary file added src/main/java/com/.DS_Store
Binary file not shown.
Binary file added src/main/java/com/basecamp/.DS_Store
Binary file not shown.
53 changes: 35 additions & 18 deletions src/main/java/com/basecamp/HyprLink/controller/AuthController.java
Original file line number Diff line number Diff line change
@@ -1,27 +1,21 @@
package com.basecamp.HyprLink.controller;

import com.basecamp.HyprLink.entity.SocialLink;
import com.basecamp.HyprLink.entity.User;
import com.basecamp.HyprLink.repository.UserRepository;
import org.springframework.security.crypto.password.PasswordEncoder;
import com.basecamp.HyprLink.service.AuthService;
import org.springframework.stereotype.Controller;
import org.springframework.ui.Model;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.ModelAttribute;
import org.springframework.web.bind.annotation.PostMapping;

import java.util.ArrayList;
import java.util.List;

@Controller
public class AuthController {

private final UserRepository userRepository;
private final PasswordEncoder passwordEncoder;
private final AuthService authService;

public AuthController(UserRepository userRepository, PasswordEncoder passwordEncoder) {
this.userRepository = userRepository;
this.passwordEncoder = passwordEncoder;
public AuthController(AuthService authService) {
this.authService = authService;
}

@GetMapping("/login")
Expand All @@ -31,19 +25,42 @@ public String showLoginForm() {

@GetMapping("/register")
public String showRegistrationForm(Model model) {
User user = new User();
List<SocialLink> initialLinks = new ArrayList<>();
initialLinks.add(new SocialLink());
user.setSocialLinks(initialLinks);
model.addAttribute("user", new User());
model.addAttribute("user", authService.prepareRegistrationFormData());
model.addAttribute("themes", authService.getAvailableThemes());
return "auth/register";
}

@PostMapping("/register/check")
public String checkRegistrationForm(@ModelAttribute User user, Model model) {
boolean validUser = true;
model.addAttribute("themes", List.of("default", "dark"));
if (!authService.checkUsername(user.getUsername())) {
model.addAttribute("invalidUsername", true);
validUser = false;
System.out.print("Invalid username");
}
if (authService.checkUserDoesNotExist(user.getUsername())) {
model.addAttribute("userAlreadyExists", true);
if (validUser) {
validUser = false;
}
System.out.print("Username exists alredy");
}
if (!authService.checkPassword(user.getPassword())) {
model.addAttribute("invalidPassword", true);
if (validUser) {
validUser = false;
}
}
if (validUser) {
return registerUser(user);
}
return "auth/register";
}

@PostMapping("/register")
public String registerUser(@ModelAttribute User user) {
user.setPassword(passwordEncoder.encode(user.getPassword()));
userRepository.save(user);
return "redirect:/login?success"; // Redirect to login page after signing up
authService.registerUser(user);
return "redirect:/login?success";
}
}
Loading
Loading