Backend project initialized
All checks were successful
Backend CI / Run Maven Tests (push) Successful in 27s

Co-authored-by: Jarno Kiesiläinen <jarnokie@gmail.com>
Co-committed-by: Jarno Kiesiläinen <jarnokie@gmail.com>
This commit was merged in pull request #4.
This commit is contained in:
2025-08-02 19:27:17 +00:00
committed by jarno
parent 47ac6f4c74
commit c3764176ac
15 changed files with 1181 additions and 0 deletions

View File

@@ -0,0 +1,16 @@
package com.vibing.backend;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
/**
* Main Spring Boot application class for the Vibing backend service.
* This class serves as the entry point for the REST API backend.
*/
@SpringBootApplication
public class VibingBackendApplication {
public static void main(String[] args) {
SpringApplication.run(VibingBackendApplication.class, args);
}
}

View File

@@ -0,0 +1,36 @@
package com.vibing.backend.config;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.security.config.annotation.web.builders.HttpSecurity;
import org.springframework.security.config.annotation.web.configuration.EnableWebSecurity;
import org.springframework.security.config.annotation.web.configurers.AbstractHttpConfigurer;
import org.springframework.security.web.SecurityFilterChain;
/**
* Security configuration for the Vibing backend application.
* This is a basic configuration that can be extended for production use.
*/
@Configuration
@EnableWebSecurity
public class SecurityConfig {
/**
* Configure security filter chain.
* For development purposes, this allows all requests.
* In production, you should implement proper authentication and authorization.
*
* @param http the HttpSecurity object
* @return SecurityFilterChain
* @throws Exception if configuration fails
*/
@Bean
public SecurityFilterChain filterChain(HttpSecurity http) throws Exception {
http
.csrf(AbstractHttpConfigurer::disable) // Disable CSRF for REST APIa
.authorizeHttpRequests(authz -> authz.anyRequest().permitAll())
.headers(headers -> headers.frameOptions().disable()); // Allow H2 console frames
return http.build();
}
}

View File

@@ -0,0 +1,49 @@
package com.vibing.backend.controller;
import io.swagger.v3.oas.annotations.Operation;
import io.swagger.v3.oas.annotations.tags.Tag;
import org.springframework.http.ResponseEntity;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
import java.time.LocalDateTime;
import java.util.HashMap;
import java.util.Map;
/**
* Health check controller for monitoring the application status.
*/
@RestController
@RequestMapping("/health")
@Tag(name = "Health Check", description = "APIs for checking application health")
public class HealthController {
/**
* Get the health status of the application.
*
* @return ResponseEntity containing health information
*/
@GetMapping
@Operation(summary = "Health check", description = "Check if the application is running")
public ResponseEntity<Map<String, Object>> healthCheck() {
Map<String, Object> healthInfo = new HashMap<>();
healthInfo.put("status", "UP");
healthInfo.put("timestamp", LocalDateTime.now());
healthInfo.put("service", "Vibing Backend");
healthInfo.put("version", "1.0.0");
return ResponseEntity.ok(healthInfo);
}
/**
* Get a simple ping response.
*
* @return ResponseEntity with "pong" message
*/
@GetMapping("/ping")
@Operation(summary = "Ping", description = "Simple ping endpoint")
public ResponseEntity<String> ping() {
return ResponseEntity.ok("pong");
}
}

View File

@@ -0,0 +1,142 @@
package com.vibing.backend.controller;
import com.vibing.backend.model.User;
import com.vibing.backend.service.UserService;
import io.swagger.v3.oas.annotations.Operation;
import io.swagger.v3.oas.annotations.tags.Tag;
import jakarta.validation.Valid;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.http.HttpStatus;
import org.springframework.http.ResponseEntity;
import org.springframework.web.bind.annotation.*;
import java.util.List;
import java.util.Optional;
/**
* REST controller for User-related operations.
*/
@RestController
@RequestMapping("/users")
@Tag(name = "User Management", description = "APIs for managing users")
@CrossOrigin(origins = "*") // Configure this properly for production
public class UserController {
private final UserService userService;
@Autowired
public UserController(UserService userService) {
this.userService = userService;
}
/**
* Get all users.
*
* @return ResponseEntity containing list of users
*/
@GetMapping
@Operation(summary = "Get all users", description = "Retrieve a list of all users")
public ResponseEntity<List<User>> getAllUsers() {
List<User> users = userService.getAllUsers();
return ResponseEntity.ok(users);
}
/**
* Get a user by ID.
*
* @param id the user ID
* @return ResponseEntity containing the user if found
*/
@GetMapping("/{id}")
@Operation(summary = "Get user by ID", description = "Retrieve a specific user by their ID")
public ResponseEntity<User> getUserById(@PathVariable Long id) {
Optional<User> user = userService.getUserById(id);
return user.map(ResponseEntity::ok)
.orElse(ResponseEntity.notFound().build());
}
/**
* Get a user by username.
*
* @param username the username
* @return ResponseEntity containing the user if found
*/
@GetMapping("/username/{username}")
@Operation(summary = "Get user by username", description = "Retrieve a specific user by their username")
public ResponseEntity<User> getUserByUsername(@PathVariable String username) {
Optional<User> user = userService.getUserByUsername(username);
return user.map(ResponseEntity::ok)
.orElse(ResponseEntity.notFound().build());
}
/**
* Create a new user.
*
* @param user the user to create
* @return ResponseEntity containing the created user
*/
@PostMapping
@Operation(summary = "Create a new user", description = "Create a new user account")
public ResponseEntity<User> createUser(@Valid @RequestBody User user) {
try {
User createdUser = userService.createUser(user);
return ResponseEntity.status(HttpStatus.CREATED).body(createdUser);
} catch (RuntimeException e) {
return ResponseEntity.badRequest().build();
}
}
/**
* Update an existing user.
*
* @param id the user ID
* @param user the updated user data
* @return ResponseEntity containing the updated user
*/
@PutMapping("/{id}")
@Operation(summary = "Update user", description = "Update an existing user's information")
public ResponseEntity<User> updateUser(@PathVariable Long id, @Valid @RequestBody User user) {
Optional<User> updatedUser = userService.updateUser(id, user);
return updatedUser.map(ResponseEntity::ok)
.orElse(ResponseEntity.notFound().build());
}
/**
* Delete a user by ID.
*
* @param id the user ID
* @return ResponseEntity indicating success or failure
*/
@DeleteMapping("/{id}")
@Operation(summary = "Delete user", description = "Delete a user by their ID")
public ResponseEntity<Void> deleteUser(@PathVariable Long id) {
boolean deleted = userService.deleteUser(id);
return deleted ? ResponseEntity.noContent().build() : ResponseEntity.notFound().build();
}
/**
* Check if a username exists.
*
* @param username the username to check
* @return ResponseEntity with boolean indicating existence
*/
@GetMapping("/check-username/{username}")
@Operation(summary = "Check username availability", description = "Check if a username is already taken")
public ResponseEntity<Boolean> checkUsernameExists(@PathVariable String username) {
boolean exists = userService.userExistsByUsername(username);
return ResponseEntity.ok(exists);
}
/**
* Check if an email exists.
*
* @param email the email to check
* @return ResponseEntity with boolean indicating existence
*/
@GetMapping("/check-email/{email}")
@Operation(summary = "Check email availability", description = "Check if an email is already registered")
public ResponseEntity<Boolean> checkEmailExists(@PathVariable String email) {
boolean exists = userService.userExistsByEmail(email);
return ResponseEntity.ok(exists);
}
}

View File

@@ -0,0 +1,144 @@
package com.vibing.backend.model;
import jakarta.persistence.*;
import jakarta.validation.constraints.Email;
import jakarta.validation.constraints.NotBlank;
import jakarta.validation.constraints.Size;
import java.time.LocalDateTime;
/**
* User entity representing a user in the Vibing application.
*/
@Entity
@Table(name = "users")
public class User {
@Id
@GeneratedValue(strategy = GenerationType.IDENTITY)
private Long id;
@NotBlank(message = "Username is required")
@Size(min = 3, max = 50, message = "Username must be between 3 and 50 characters")
@Column(unique = true, nullable = false)
private String username;
@NotBlank(message = "Email is required")
@Email(message = "Email should be valid")
@Column(unique = true, nullable = false)
private String email;
@NotBlank(message = "Password is required")
@Size(min = 6, message = "Password must be at least 6 characters")
@Column(nullable = false)
private String password;
@Column(name = "first_name")
private String firstName;
@Column(name = "last_name")
private String lastName;
@Column(name = "created_at")
private LocalDateTime createdAt;
@Column(name = "updated_at")
private LocalDateTime updatedAt;
@PrePersist
protected void onCreate() {
createdAt = LocalDateTime.now();
updatedAt = LocalDateTime.now();
}
@PreUpdate
protected void onUpdate() {
updatedAt = LocalDateTime.now();
}
// Constructors
public User() {}
public User(String username, String email, String password) {
this.username = username;
this.email = email;
this.password = password;
}
// Getters and Setters
public Long getId() {
return id;
}
public void setId(Long id) {
this.id = id;
}
public String getUsername() {
return username;
}
public void setUsername(String username) {
this.username = username;
}
public String getEmail() {
return email;
}
public void setEmail(String email) {
this.email = email;
}
public String getPassword() {
return password;
}
public void setPassword(String password) {
this.password = password;
}
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 LocalDateTime getCreatedAt() {
return createdAt;
}
public void setCreatedAt(LocalDateTime createdAt) {
this.createdAt = createdAt;
}
public LocalDateTime getUpdatedAt() {
return updatedAt;
}
public void setUpdatedAt(LocalDateTime updatedAt) {
this.updatedAt = updatedAt;
}
@Override
public String toString() {
return "User{" +
"id=" + id +
", username='" + username + '\'' +
", email='" + email + '\'' +
", firstName='" + firstName + '\'' +
", lastName='" + lastName + '\'' +
", createdAt=" + createdAt +
", updatedAt=" + updatedAt +
'}';
}
}

View File

@@ -0,0 +1,46 @@
package com.vibing.backend.repository;
import com.vibing.backend.model.User;
import org.springframework.data.jpa.repository.JpaRepository;
import org.springframework.stereotype.Repository;
import java.util.Optional;
/**
* Repository interface for User entity operations.
*/
@Repository
public interface UserRepository extends JpaRepository<User, Long> {
/**
* Find a user by username.
*
* @param username the username to search for
* @return Optional containing the user if found
*/
Optional<User> findByUsername(String username);
/**
* Find a user by email.
*
* @param email the email to search for
* @return Optional containing the user if found
*/
Optional<User> findByEmail(String email);
/**
* Check if a user exists by username.
*
* @param username the username to check
* @return true if user exists, false otherwise
*/
boolean existsByUsername(String username);
/**
* Check if a user exists by email.
*
* @param email the email to check
* @return true if user exists, false otherwise
*/
boolean existsByEmail(String email);
}

View File

@@ -0,0 +1,134 @@
package com.vibing.backend.service;
import com.vibing.backend.model.User;
import com.vibing.backend.repository.UserRepository;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import java.util.List;
import java.util.Optional;
/**
* Service class for User-related business operations.
*/
@Service
public class UserService {
private final UserRepository userRepository;
@Autowired
public UserService(UserRepository userRepository) {
this.userRepository = userRepository;
}
/**
* Get all users.
*
* @return List of all users
*/
public List<User> getAllUsers() {
return userRepository.findAll();
}
/**
* Get a user by ID.
*
* @param id the user ID
* @return Optional containing the user if found
*/
public Optional<User> getUserById(Long id) {
return userRepository.findById(id);
}
/**
* Get a user by username.
*
* @param username the username
* @return Optional containing the user if found
*/
public Optional<User> getUserByUsername(String username) {
return userRepository.findByUsername(username);
}
/**
* Get a user by email.
*
* @param email the email
* @return Optional containing the user if found
*/
public Optional<User> getUserByEmail(String email) {
return userRepository.findByEmail(email);
}
/**
* Create a new user.
*
* @param user the user to create
* @return the created user
*/
public User createUser(User user) {
// Check if username already exists
if (userRepository.existsByUsername(user.getUsername())) {
throw new RuntimeException("Username already exists: " + user.getUsername());
}
// Check if email already exists
if (userRepository.existsByEmail(user.getEmail())) {
throw new RuntimeException("Email already exists: " + user.getEmail());
}
return userRepository.save(user);
}
/**
* Update an existing user.
*
* @param id the user ID
* @param user the updated user data
* @return Optional containing the updated user if found
*/
public Optional<User> updateUser(Long id, User user) {
return userRepository.findById(id)
.map(existingUser -> {
existingUser.setUsername(user.getUsername());
existingUser.setEmail(user.getEmail());
existingUser.setFirstName(user.getFirstName());
existingUser.setLastName(user.getLastName());
return userRepository.save(existingUser);
});
}
/**
* Delete a user by ID.
*
* @param id the user ID
* @return true if user was deleted, false if not found
*/
public boolean deleteUser(Long id) {
if (userRepository.existsById(id)) {
userRepository.deleteById(id);
return true;
}
return false;
}
/**
* Check if a user exists by username.
*
* @param username the username to check
* @return true if user exists, false otherwise
*/
public boolean userExistsByUsername(String username) {
return userRepository.existsByUsername(username);
}
/**
* Check if a user exists by email.
*
* @param email the email to check
* @return true if user exists, false otherwise
*/
public boolean userExistsByEmail(String email) {
return userRepository.existsByEmail(email);
}
}

View File

@@ -0,0 +1,62 @@
server:
port: 8080
servlet:
context-path: /api
spring:
application:
name: vibing-backend
# Database Configuration
datasource:
url: jdbc:h2:mem:testdb
driver-class-name: org.h2.Driver
username: sa
password: password
# JPA Configuration
jpa:
database-platform: org.hibernate.dialect.H2Dialect
hibernate:
ddl-auto: create-drop
show-sql: true
properties:
hibernate:
format_sql: true
# H2 Console (for development)
h2:
console:
enabled: true
path: /h2-console
# Jackson Configuration
jackson:
default-property-inclusion: non_null
serialization:
write-dates-as-timestamps: false
deserialization:
fail-on-unknown-properties: false
# Logging Configuration
logging:
level:
com.vibing.backend: DEBUG
org.springframework.security: DEBUG
org.springframework.web: DEBUG
pattern:
console: "%d{yyyy-MM-dd HH:mm:ss} - %msg%n"
# API Documentation
springdoc:
api-docs:
path: /api-docs
swagger-ui:
path: /swagger-ui.html
operations-sorter: method
# Security Configuration
security:
jwt:
secret: your-secret-key-here-change-in-production
expiration: 86400000 # 24 hours in milliseconds

View File

@@ -0,0 +1,16 @@
package com.vibing.backend;
import org.junit.jupiter.api.Test;
import org.springframework.boot.test.context.SpringBootTest;
/**
* Basic test class for the Vibing Backend application.
*/
@SpringBootTest
class VibingBackendApplicationTests {
@Test
void contextLoads() {
// This test verifies that the Spring application context loads successfully
}
}