Initial implementation for the backend persistence of activities (#5)
All checks were successful
Backend CI / Run Maven Tests (push) Successful in 27s
All checks were successful
Backend CI / Run Maven Tests (push) Successful in 27s
Reviewed-on: #5 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 #5.
This commit is contained in:
32
.clinerules/frontend-color-theme.md
Normal file
32
.clinerules/frontend-color-theme.md
Normal file
@@ -0,0 +1,32 @@
|
|||||||
|
## Brief overview
|
||||||
|
Project-specific frontend color theme guidelines for the vibing application. Defines a cohesive peach/coral/pink color palette with specific CSS custom properties, component styling patterns, and accessibility requirements.
|
||||||
|
|
||||||
|
## Color palette usage
|
||||||
|
- Always use the defined peach-coral color scheme: Primary Peach (#FFCDB2), Secondary Coral (#FFB4A2), Light Pink (#E5989B), Medium Mauve (#B5838D), Dark Purple (#6D6875)
|
||||||
|
- Implement CSS custom properties in src/index.css for all color definitions
|
||||||
|
- Use semantic color naming: --color-primary, --color-secondary, --color-bg-dark, --color-text-primary, etc.
|
||||||
|
- Support both dark and light modes using @media (prefers-color-scheme: light)
|
||||||
|
|
||||||
|
## Component styling patterns
|
||||||
|
- Buttons: Use gradient backgrounds, implement translateY(-2px) hover animations, apply consistent focus states with peach outline
|
||||||
|
- Cards: White backgrounds with subtle shadows, translateY(-8px) hover animations, 16px border radius, scale(1.05) image hover effects
|
||||||
|
- Links: Primary color #FFCDB2, hover colors #E5989B (dark mode) or #B5838D (light mode), no text decoration by default
|
||||||
|
- Use semantic CSS class names: .btn-primary, .btn-secondary, .btn-outline, .btn-ghost
|
||||||
|
|
||||||
|
## Interactive states and animations
|
||||||
|
- Apply subtle lift animations using transform: translateY(-2px) for hover effects
|
||||||
|
- Use rgba(255, 205, 178, 0.3) for focus rings and primary shadows
|
||||||
|
- Use rgba(229, 152, 155, 0.3) for secondary shadows
|
||||||
|
- Ensure hover states are distinct from focus states for accessibility
|
||||||
|
|
||||||
|
## File organization structure
|
||||||
|
- Place global styles and CSS custom properties in src/index.css
|
||||||
|
- Create individual .css files in src/components/ for component-specific styles
|
||||||
|
- Use src/App.css for utility classes
|
||||||
|
- Consider src/styles/ directory for additional theme files when needed
|
||||||
|
|
||||||
|
## Accessibility requirements
|
||||||
|
- Maintain minimum contrast ratios: 4.5:1 for normal text, 3:1 for large text
|
||||||
|
- Use --color-focus for focus indicators
|
||||||
|
- Test with color blindness simulators
|
||||||
|
- Ensure all interactive elements have proper focus states
|
||||||
38
.clinerules/git-workflow.md
Normal file
38
.clinerules/git-workflow.md
Normal file
@@ -0,0 +1,38 @@
|
|||||||
|
## Brief overview
|
||||||
|
Project-specific git workflow guidelines emphasizing feature branch development, conventional commit messages, and proper branch management practices. Never work directly on main/master branches.
|
||||||
|
|
||||||
|
## Branch management strategy
|
||||||
|
- Always create feature branches for all development work
|
||||||
|
- Use kebab-case naming: feature/user-authentication, bugfix/login-validation, hotfix/security-patch, refactor/api-endpoints
|
||||||
|
- Keep feature branches short-lived (1-3 days ideally)
|
||||||
|
- One feature per branch, don't mix different types of changes
|
||||||
|
- Delete branches after successful merge
|
||||||
|
|
||||||
|
## Commit message conventions
|
||||||
|
- Use conventional commit format: type(scope): description
|
||||||
|
- Use present tense ("add feature" not "added feature")
|
||||||
|
- Be specific and descriptive in commit messages
|
||||||
|
- Reference issue numbers when applicable: feat(auth): add OAuth login #123
|
||||||
|
- Make frequent, small commits with clear purposes
|
||||||
|
|
||||||
|
## Development workflow steps
|
||||||
|
- Always start by ensuring main branch is up to date: git checkout main && git pull origin main
|
||||||
|
- Create new feature branch: git checkout -b feature/your-feature-name
|
||||||
|
- Before pushing, rebase with main: git fetch origin && git rebase origin/main
|
||||||
|
- Push feature branch: git push origin feature/your-feature-name
|
||||||
|
- Create Pull Request from feature branch to main
|
||||||
|
- Only merge after code review approval
|
||||||
|
|
||||||
|
## Code review requirements
|
||||||
|
- Create PR for all changes, no direct commits to main
|
||||||
|
- Request reviews from team members
|
||||||
|
- Address all feedback before merging
|
||||||
|
- Ensure all tests pass before creating PR
|
||||||
|
- Self-review changes before requesting reviews
|
||||||
|
- Update documentation when needed
|
||||||
|
|
||||||
|
## Emergency procedures
|
||||||
|
- For critical issues, create hotfix branches from main
|
||||||
|
- Make minimal necessary changes in hotfixes
|
||||||
|
- Test thoroughly before creating emergency PR
|
||||||
|
- Follow same review process even for hotfixes
|
||||||
14
README.md
Normal file
14
README.md
Normal file
@@ -0,0 +1,14 @@
|
|||||||
|
# Vibing application
|
||||||
|
|
||||||
|
## Data model
|
||||||
|
|
||||||
|
```mermaid
|
||||||
|
classDiagram
|
||||||
|
class Activity {
|
||||||
|
+String name
|
||||||
|
+Location location
|
||||||
|
+Int priceRange
|
||||||
|
+List~String~ tags
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
@@ -49,8 +49,8 @@
|
|||||||
|
|
||||||
<!-- Database -->
|
<!-- Database -->
|
||||||
<dependency>
|
<dependency>
|
||||||
<groupId>com.h2database</groupId>
|
<groupId>org.postgresql</groupId>
|
||||||
<artifactId>h2</artifactId>
|
<artifactId>postgresql</artifactId>
|
||||||
<scope>runtime</scope>
|
<scope>runtime</scope>
|
||||||
</dependency>
|
</dependency>
|
||||||
|
|
||||||
@@ -75,18 +75,43 @@
|
|||||||
<scope>test</scope>
|
<scope>test</scope>
|
||||||
</dependency>
|
</dependency>
|
||||||
|
|
||||||
|
<dependency>
|
||||||
|
<groupId>com.h2database</groupId>
|
||||||
|
<artifactId>h2</artifactId>
|
||||||
|
<scope>test</scope>
|
||||||
|
</dependency>
|
||||||
|
|
||||||
<!-- JSON Processing -->
|
<!-- JSON Processing -->
|
||||||
<dependency>
|
<dependency>
|
||||||
<groupId>com.fasterxml.jackson.core</groupId>
|
<groupId>com.fasterxml.jackson.core</groupId>
|
||||||
<artifactId>jackson-databind</artifactId>
|
<artifactId>jackson-databind</artifactId>
|
||||||
</dependency>
|
</dependency>
|
||||||
|
|
||||||
|
<!-- Lombok -->
|
||||||
|
<dependency>
|
||||||
|
<groupId>org.projectlombok</groupId>
|
||||||
|
<artifactId>lombok</artifactId>
|
||||||
|
<version>1.18.38</version>
|
||||||
|
<scope>provided</scope>
|
||||||
|
</dependency>
|
||||||
|
|
||||||
<!-- API Documentation -->
|
<!-- API Documentation -->
|
||||||
<dependency>
|
<dependency>
|
||||||
<groupId>org.springdoc</groupId>
|
<groupId>org.springdoc</groupId>
|
||||||
<artifactId>springdoc-openapi-starter-webmvc-ui</artifactId>
|
<artifactId>springdoc-openapi-starter-webmvc-ui</artifactId>
|
||||||
<version>2.2.0</version>
|
<version>2.2.0</version>
|
||||||
</dependency>
|
</dependency>
|
||||||
|
|
||||||
|
<!-- Flyway -->
|
||||||
|
<dependency>
|
||||||
|
<groupId>org.flywaydb</groupId>
|
||||||
|
<artifactId>flyway-core</artifactId>
|
||||||
|
</dependency>
|
||||||
|
<dependency>
|
||||||
|
<groupId>org.flywaydb</groupId>
|
||||||
|
<artifactId>flyway-database-postgresql</artifactId>
|
||||||
|
<version>11.10.5</version>
|
||||||
|
</dependency>
|
||||||
</dependencies>
|
</dependencies>
|
||||||
|
|
||||||
<build>
|
<build>
|
||||||
|
|||||||
@@ -28,8 +28,7 @@ public class SecurityConfig {
|
|||||||
public SecurityFilterChain filterChain(HttpSecurity http) throws Exception {
|
public SecurityFilterChain filterChain(HttpSecurity http) throws Exception {
|
||||||
http
|
http
|
||||||
.csrf(AbstractHttpConfigurer::disable) // Disable CSRF for REST APIa
|
.csrf(AbstractHttpConfigurer::disable) // Disable CSRF for REST APIa
|
||||||
.authorizeHttpRequests(authz -> authz.anyRequest().permitAll())
|
.authorizeHttpRequests(authz -> authz.anyRequest().permitAll());
|
||||||
.headers(headers -> headers.frameOptions().disable()); // Allow H2 console frames
|
|
||||||
|
|
||||||
return http.build();
|
return http.build();
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -0,0 +1,117 @@
|
|||||||
|
package com.vibing.backend.controller;
|
||||||
|
|
||||||
|
import com.vibing.backend.model.Activity;
|
||||||
|
import com.vibing.backend.service.ActivityService;
|
||||||
|
|
||||||
|
import io.swagger.v3.oas.annotations.Operation;
|
||||||
|
import io.swagger.v3.oas.annotations.tags.Tag;
|
||||||
|
import lombok.AllArgsConstructor;
|
||||||
|
import org.springframework.http.ResponseEntity;
|
||||||
|
import org.springframework.web.bind.annotation.*;
|
||||||
|
|
||||||
|
import java.util.List;
|
||||||
|
import java.util.Optional;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* REST controller for Activity-related operations.
|
||||||
|
*/
|
||||||
|
@RestController
|
||||||
|
@RequestMapping("/activities")
|
||||||
|
@Tag(name = "Activity Management", description = "APIs for managing activities")
|
||||||
|
@CrossOrigin(origins = "*") // Configure this properly for production
|
||||||
|
@AllArgsConstructor
|
||||||
|
public class ActivityController {
|
||||||
|
|
||||||
|
private final ActivityService activityService;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get all activities.
|
||||||
|
*
|
||||||
|
* @return ResponseEntity containing list of all activities
|
||||||
|
*/
|
||||||
|
@GetMapping
|
||||||
|
@Operation(summary = "Get all activities", description = "Retrieve a list of all activities")
|
||||||
|
public ResponseEntity<List<Activity>> getAllActivities() {
|
||||||
|
List<Activity> activities = activityService.findAll();
|
||||||
|
return ResponseEntity.ok(activities);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get an activity by ID.
|
||||||
|
*
|
||||||
|
* @param id the activity ID
|
||||||
|
* @return ResponseEntity containing the activity if found
|
||||||
|
*/
|
||||||
|
@GetMapping("/{id}")
|
||||||
|
@Operation(summary = "Get activity by ID", description = "Retrieve a specific activity by its ID")
|
||||||
|
public ResponseEntity<Activity> getActivityById(@PathVariable Long id) {
|
||||||
|
Optional<Activity> activity = activityService.findById(id);
|
||||||
|
return activity.map(ResponseEntity::ok)
|
||||||
|
.orElse(ResponseEntity.notFound().build());
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get activities by name (case-insensitive search).
|
||||||
|
*
|
||||||
|
* @param name the activity name to search for
|
||||||
|
* @return ResponseEntity containing list of matching activities
|
||||||
|
*/
|
||||||
|
@GetMapping("/search/name")
|
||||||
|
@Operation(summary = "Search activities by name", description = "Search for activities by name (case-insensitive)")
|
||||||
|
public ResponseEntity<List<Activity>> getActivitiesByName(@RequestParam String name) {
|
||||||
|
List<Activity> activities = activityService.findByNameContainingIgnoreCase(name);
|
||||||
|
return ResponseEntity.ok(activities);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get activities by price range.
|
||||||
|
*
|
||||||
|
* @param priceRange the price range to filter by
|
||||||
|
* @return ResponseEntity containing list of activities with the specified price range
|
||||||
|
*/
|
||||||
|
@GetMapping("/search/price-range")
|
||||||
|
@Operation(summary = "Get activities by price range", description = "Retrieve activities with a specific price range")
|
||||||
|
public ResponseEntity<List<Activity>> getActivitiesByPriceRange(@RequestParam Integer priceRange) {
|
||||||
|
List<Activity> activities = activityService.findByPriceRange(priceRange);
|
||||||
|
return ResponseEntity.ok(activities);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get activities by location.
|
||||||
|
*
|
||||||
|
* @param locationId the location ID to filter by
|
||||||
|
* @return ResponseEntity containing list of activities at the specified location
|
||||||
|
*/
|
||||||
|
@GetMapping("/search/location")
|
||||||
|
@Operation(summary = "Get activities by location", description = "Retrieve activities at a specific location")
|
||||||
|
public ResponseEntity<List<Activity>> getActivitiesByLocation(@RequestParam Long locationId) {
|
||||||
|
List<Activity> activities = activityService.findByLocationId(locationId);
|
||||||
|
return ResponseEntity.ok(activities);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get activities by tag.
|
||||||
|
*
|
||||||
|
* @param tag the tag to search for
|
||||||
|
* @return ResponseEntity containing list of activities with the specified tag
|
||||||
|
*/
|
||||||
|
@GetMapping("/search/tag")
|
||||||
|
@Operation(summary = "Get activities by tag", description = "Retrieve activities that have a specific tag")
|
||||||
|
public ResponseEntity<List<Activity>> getActivitiesByTag(@RequestParam String tag) {
|
||||||
|
List<Activity> activities = activityService.findByTagsContaining(tag);
|
||||||
|
return ResponseEntity.ok(activities);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get activities by maximum price range.
|
||||||
|
*
|
||||||
|
* @param maxPriceRange the maximum price range
|
||||||
|
* @return ResponseEntity containing list of activities with price range <= maxPriceRange
|
||||||
|
*/
|
||||||
|
@GetMapping("/search/max-price")
|
||||||
|
@Operation(summary = "Get activities by maximum price range", description = "Retrieve activities with price range less than or equal to the specified value")
|
||||||
|
public ResponseEntity<List<Activity>> getActivitiesByMaxPriceRange(@RequestParam Integer maxPriceRange) {
|
||||||
|
List<Activity> activities = activityService.findByPriceRangeLessThanEqual(maxPriceRange);
|
||||||
|
return ResponseEntity.ok(activities);
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -1,142 +0,0 @@
|
|||||||
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);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
67
backend/src/main/java/com/vibing/backend/model/Activity.java
Normal file
67
backend/src/main/java/com/vibing/backend/model/Activity.java
Normal file
@@ -0,0 +1,67 @@
|
|||||||
|
package com.vibing.backend.model;
|
||||||
|
|
||||||
|
import jakarta.persistence.*;
|
||||||
|
import jakarta.validation.constraints.NotBlank;
|
||||||
|
import jakarta.validation.constraints.NotNull;
|
||||||
|
import jakarta.validation.constraints.Min;
|
||||||
|
import jakarta.validation.constraints.Max;
|
||||||
|
import lombok.Data;
|
||||||
|
import lombok.NoArgsConstructor;
|
||||||
|
import lombok.AllArgsConstructor;
|
||||||
|
import java.time.LocalDateTime;
|
||||||
|
import java.util.List;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Activity entity representing an activity in the Vibing application.
|
||||||
|
*/
|
||||||
|
@Entity
|
||||||
|
@Table(name = "activities")
|
||||||
|
@Data
|
||||||
|
@NoArgsConstructor
|
||||||
|
@AllArgsConstructor
|
||||||
|
public class Activity {
|
||||||
|
|
||||||
|
@Id
|
||||||
|
@GeneratedValue(strategy = GenerationType.IDENTITY)
|
||||||
|
private Long id;
|
||||||
|
|
||||||
|
@NotBlank(message = "Activity name is required")
|
||||||
|
@Column(nullable = false)
|
||||||
|
private String name;
|
||||||
|
|
||||||
|
@NotNull(message = "Location is required")
|
||||||
|
@ManyToOne(fetch = FetchType.LAZY)
|
||||||
|
@JoinColumn(name = "location_id", nullable = false)
|
||||||
|
private Location location;
|
||||||
|
|
||||||
|
@NotNull(message = "Price range is required")
|
||||||
|
@Min(value = 1, message = "Price range must be at least 1")
|
||||||
|
@Max(value = 5, message = "Price range must be at most 5")
|
||||||
|
@Column(name = "price_range", nullable = false)
|
||||||
|
private Integer priceRange;
|
||||||
|
|
||||||
|
@ElementCollection(fetch = FetchType.LAZY)
|
||||||
|
@CollectionTable(name = "activity_tags", joinColumns = @JoinColumn(name = "activity_id"))
|
||||||
|
@Column(name = "tag")
|
||||||
|
private List<String> tags;
|
||||||
|
|
||||||
|
@Column(name = "description")
|
||||||
|
private String description;
|
||||||
|
|
||||||
|
@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();
|
||||||
|
}
|
||||||
|
}
|
||||||
62
backend/src/main/java/com/vibing/backend/model/Location.java
Normal file
62
backend/src/main/java/com/vibing/backend/model/Location.java
Normal file
@@ -0,0 +1,62 @@
|
|||||||
|
package com.vibing.backend.model;
|
||||||
|
|
||||||
|
import jakarta.persistence.*;
|
||||||
|
import jakarta.validation.constraints.NotBlank;
|
||||||
|
import lombok.Data;
|
||||||
|
import lombok.NoArgsConstructor;
|
||||||
|
import lombok.AllArgsConstructor;
|
||||||
|
import java.time.LocalDateTime;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Location entity representing a location in the Vibing application.
|
||||||
|
*/
|
||||||
|
@Entity
|
||||||
|
@Table(name = "locations")
|
||||||
|
@Data
|
||||||
|
@NoArgsConstructor
|
||||||
|
@AllArgsConstructor
|
||||||
|
public class Location {
|
||||||
|
|
||||||
|
@Id
|
||||||
|
@GeneratedValue(strategy = GenerationType.IDENTITY)
|
||||||
|
private Long id;
|
||||||
|
|
||||||
|
@NotBlank(message = "Location name is required")
|
||||||
|
@Column(nullable = false)
|
||||||
|
private String name;
|
||||||
|
|
||||||
|
@Column
|
||||||
|
private String address;
|
||||||
|
|
||||||
|
@Column
|
||||||
|
private String city;
|
||||||
|
|
||||||
|
@Column
|
||||||
|
private String country;
|
||||||
|
|
||||||
|
@Column(name = "postal_code")
|
||||||
|
private String postalCode;
|
||||||
|
|
||||||
|
@Column(name = "latitude")
|
||||||
|
private Double latitude;
|
||||||
|
|
||||||
|
@Column(name = "longitude")
|
||||||
|
private Double longitude;
|
||||||
|
|
||||||
|
@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();
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -1,144 +0,0 @@
|
|||||||
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 +
|
|
||||||
'}';
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -0,0 +1,54 @@
|
|||||||
|
package com.vibing.backend.repository;
|
||||||
|
|
||||||
|
import com.vibing.backend.model.Activity;
|
||||||
|
import org.springframework.data.jpa.repository.JpaRepository;
|
||||||
|
import org.springframework.stereotype.Repository;
|
||||||
|
|
||||||
|
import java.util.List;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Repository interface for Activity entity operations.
|
||||||
|
*/
|
||||||
|
@Repository
|
||||||
|
public interface ActivityRepository extends JpaRepository<Activity, Long> {
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Find activities by name (case-insensitive).
|
||||||
|
*
|
||||||
|
* @param name the activity name to search for
|
||||||
|
* @return List of activities matching the name
|
||||||
|
*/
|
||||||
|
List<Activity> findByNameContainingIgnoreCase(String name);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Find activities by price range.
|
||||||
|
*
|
||||||
|
* @param priceRange the price range to search for
|
||||||
|
* @return List of activities with the specified price range
|
||||||
|
*/
|
||||||
|
List<Activity> findByPriceRange(Integer priceRange);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Find activities by location.
|
||||||
|
*
|
||||||
|
* @param locationId the location ID to search for
|
||||||
|
* @return List of activities at the specified location
|
||||||
|
*/
|
||||||
|
List<Activity> findByLocationId(Long locationId);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Find activities by tag.
|
||||||
|
*
|
||||||
|
* @param tag the tag to search for
|
||||||
|
* @return List of activities with the specified tag
|
||||||
|
*/
|
||||||
|
List<Activity> findByTagsContaining(String tag);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Find activities by price range less than or equal to.
|
||||||
|
*
|
||||||
|
* @param maxPriceRange the maximum price range
|
||||||
|
* @return List of activities with price range <= maxPriceRange
|
||||||
|
*/
|
||||||
|
List<Activity> findByPriceRangeLessThanEqual(Integer maxPriceRange);
|
||||||
|
}
|
||||||
@@ -1,46 +0,0 @@
|
|||||||
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);
|
|
||||||
}
|
|
||||||
@@ -0,0 +1,49 @@
|
|||||||
|
package com.vibing.backend.service;
|
||||||
|
|
||||||
|
import java.util.List;
|
||||||
|
import java.util.Optional;
|
||||||
|
|
||||||
|
import org.springframework.stereotype.Service;
|
||||||
|
|
||||||
|
import com.vibing.backend.model.Activity;
|
||||||
|
import com.vibing.backend.repository.ActivityRepository;
|
||||||
|
|
||||||
|
import lombok.AllArgsConstructor;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Service class for managing activities.
|
||||||
|
*/
|
||||||
|
@Service
|
||||||
|
@AllArgsConstructor
|
||||||
|
public class ActivityService {
|
||||||
|
|
||||||
|
private final ActivityRepository activityRepository;
|
||||||
|
|
||||||
|
public List<Activity> findAll() {
|
||||||
|
return activityRepository.findAll();
|
||||||
|
}
|
||||||
|
|
||||||
|
public Optional<Activity> findById(Long id) {
|
||||||
|
return activityRepository.findById(id);
|
||||||
|
}
|
||||||
|
|
||||||
|
public List<Activity> findByNameContainingIgnoreCase(String name) {
|
||||||
|
return activityRepository.findByNameContainingIgnoreCase(name);
|
||||||
|
}
|
||||||
|
|
||||||
|
public List<Activity> findByPriceRange(Integer priceRange) {
|
||||||
|
return activityRepository.findByPriceRange(priceRange);
|
||||||
|
}
|
||||||
|
|
||||||
|
public List<Activity> findByTagsContaining(String tag) {
|
||||||
|
return activityRepository.findByTagsContaining(tag);
|
||||||
|
}
|
||||||
|
|
||||||
|
public List<Activity> findByLocationId(Long locationId) {
|
||||||
|
return activityRepository.findByLocationId(locationId);
|
||||||
|
}
|
||||||
|
|
||||||
|
public List<Activity> findByPriceRangeLessThanEqual(Integer maxPriceRange) {
|
||||||
|
return activityRepository.findByPriceRangeLessThanEqual(maxPriceRange);
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -1,134 +0,0 @@
|
|||||||
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);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -9,27 +9,21 @@ spring:
|
|||||||
|
|
||||||
# Database Configuration
|
# Database Configuration
|
||||||
datasource:
|
datasource:
|
||||||
url: jdbc:h2:mem:testdb
|
url: jdbc:postgresql://localhost:5432/vibing
|
||||||
driver-class-name: org.h2.Driver
|
username: vibing
|
||||||
username: sa
|
password: vibing
|
||||||
password: password
|
driver-class-name: org.postgresql.Driver
|
||||||
|
|
||||||
# JPA Configuration
|
# JPA Configuration
|
||||||
jpa:
|
jpa:
|
||||||
database-platform: org.hibernate.dialect.H2Dialect
|
database-platform: org.hibernate.dialect.PostgreSQLDialect
|
||||||
hibernate:
|
hibernate:
|
||||||
ddl-auto: create-drop
|
ddl-auto: update
|
||||||
show-sql: true
|
show-sql: true
|
||||||
properties:
|
properties:
|
||||||
hibernate:
|
hibernate:
|
||||||
format_sql: true
|
format_sql: true
|
||||||
|
|
||||||
# H2 Console (for development)
|
|
||||||
h2:
|
|
||||||
console:
|
|
||||||
enabled: true
|
|
||||||
path: /h2-console
|
|
||||||
|
|
||||||
# Jackson Configuration
|
# Jackson Configuration
|
||||||
jackson:
|
jackson:
|
||||||
default-property-inclusion: non_null
|
default-property-inclusion: non_null
|
||||||
|
|||||||
@@ -0,0 +1,35 @@
|
|||||||
|
CREATE SCHEMA IF NOT EXISTS vibing;
|
||||||
|
|
||||||
|
CREATE TABLE IF NOT EXISTS vibing.locations (
|
||||||
|
id SERIAL PRIMARY KEY,
|
||||||
|
name TEXT NOT NULL,
|
||||||
|
address TEXT,
|
||||||
|
city TEXT,
|
||||||
|
country TEXT,
|
||||||
|
postal_code TEXT,
|
||||||
|
latitude DOUBLE PRECISION,
|
||||||
|
longitude DOUBLE PRECISION,
|
||||||
|
created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
|
||||||
|
updated_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP
|
||||||
|
);
|
||||||
|
|
||||||
|
CREATE TABLE IF NOT EXISTS vibing.activities (
|
||||||
|
id SERIAL PRIMARY KEY,
|
||||||
|
name TEXT NOT NULL,
|
||||||
|
description TEXT,
|
||||||
|
price_range INT NOT NULL,
|
||||||
|
location_id INT NOT NULL,
|
||||||
|
created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
|
||||||
|
updated_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
|
||||||
|
CONSTRAINT fk_location
|
||||||
|
FOREIGN KEY(location_id)
|
||||||
|
REFERENCES vibing.locations(id)
|
||||||
|
ON DELETE CASCADE
|
||||||
|
);
|
||||||
|
|
||||||
|
CREATE TABLE IF NOT EXISTS vibing.activity_tags (
|
||||||
|
activity_id INT NOT NULL,
|
||||||
|
tag TEXT NOT NULL,
|
||||||
|
FOREIGN KEY (activity_id) REFERENCES vibing.activities(id) ON DELETE CASCADE,
|
||||||
|
PRIMARY KEY (activity_id, tag)
|
||||||
|
);
|
||||||
@@ -2,11 +2,13 @@ package com.vibing.backend;
|
|||||||
|
|
||||||
import org.junit.jupiter.api.Test;
|
import org.junit.jupiter.api.Test;
|
||||||
import org.springframework.boot.test.context.SpringBootTest;
|
import org.springframework.boot.test.context.SpringBootTest;
|
||||||
|
import org.springframework.test.context.ActiveProfiles;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Basic test class for the Vibing Backend application.
|
* Basic test class for the Vibing Backend application.
|
||||||
*/
|
*/
|
||||||
@SpringBootTest
|
@SpringBootTest
|
||||||
|
@ActiveProfiles("test")
|
||||||
class VibingBackendApplicationTests {
|
class VibingBackendApplicationTests {
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
|
|||||||
21
backend/src/test/resources/application-test.yml
Normal file
21
backend/src/test/resources/application-test.yml
Normal file
@@ -0,0 +1,21 @@
|
|||||||
|
spring:
|
||||||
|
datasource:
|
||||||
|
url: jdbc:h2:mem:testdb;DB_CLOSE_DELAY=-1;DB_CLOSE_ON_EXIT=FALSE;MODE=PostgreSQL
|
||||||
|
username: sa
|
||||||
|
password:
|
||||||
|
driver-class-name: org.h2.Driver
|
||||||
|
|
||||||
|
h2:
|
||||||
|
console:
|
||||||
|
enabled: true
|
||||||
|
|
||||||
|
jpa:
|
||||||
|
hibernate:
|
||||||
|
ddl-auto: none # Let Flyway handle schema creation
|
||||||
|
show-sql: true
|
||||||
|
database-platform: org.hibernate.dialect.H2Dialect
|
||||||
|
|
||||||
|
flyway:
|
||||||
|
enabled: true
|
||||||
|
clean-disabled: false # Allow clean in tests
|
||||||
|
locations: classpath:db/migration
|
||||||
11
local-dev/compose.yml
Normal file
11
local-dev/compose.yml
Normal file
@@ -0,0 +1,11 @@
|
|||||||
|
services:
|
||||||
|
postgres:
|
||||||
|
image: postgres:15
|
||||||
|
ports:
|
||||||
|
- 5432:5432
|
||||||
|
environment:
|
||||||
|
POSTGRES_USER: vibing
|
||||||
|
POSTGRES_PASSWORD: vibing
|
||||||
|
POSTGRES_DB: vibing
|
||||||
|
volumes:
|
||||||
|
- postgres_data:/var/lib/postgresql/data
|
||||||
Reference in New Issue
Block a user