Skip to content

Commit ac2c832

Browse files
authored
Merge pull request #111 from volodymyrlp/dev
Release: tags, participants and trip photos
2 parents 8bb1eb4 + 1239045 commit ac2c832

22 files changed

Lines changed: 499 additions & 2 deletions

.gitignore

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -17,3 +17,6 @@ dist/
1717
.vscode/
1818
*.iml
1919
.DS_Store
20+
21+
# --- foglamp scan (edit token is a secret) ---
22+
.foglamp/

README.md

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -34,6 +34,10 @@ the two free-tier traps that already broke a release, live in [docs/DEPLOY.md](d
3434
/frontend — React + Vite SPA
3535
```
3636

37+
An interactive map of the architecture — the modules, the services they talk to and how the
38+
pieces connect — is published at <https://foglamp.dev/scan/mriyatrip-lplqrt>. It is an unlisted
39+
link and it expires on 27 November 2026.
40+
3741
## Local development
3842
1. Copy `.env.example``.env` and fill in the values. Optional for a first run —
3943
compose falls back to local defaults for everything except `ORS_API_KEY`.

backend/pom.xml

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -110,6 +110,11 @@
110110
<artifactId>spring-boot-starter-test</artifactId>
111111
<scope>test</scope>
112112
</dependency>
113+
<dependency>
114+
<groupId>org.springframework.security</groupId>
115+
<artifactId>spring-security-test</artifactId>
116+
<scope>test</scope>
117+
</dependency>
113118
</dependencies>
114119

115120
<build>

backend/src/main/java/travelplanner/config/SecurityConfig.java

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -67,7 +67,8 @@ public SecurityFilterChain securityFilterChain(HttpSecurity http) throws Excepti
6767
"/api/v1/auth/**",
6868
"/api/v1/landing/**",
6969
"/api/v1/trips/catalog",
70-
"/error"
70+
"/error",
71+
"/uploads/**"
7172
)
7273
.permitAll()
7374
.anyRequest()
Lines changed: 27 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,27 @@
1+
package travelplanner.config;
2+
3+
import java.io.IOException;
4+
import java.nio.file.Files;
5+
import java.nio.file.Path;
6+
import java.nio.file.Paths;
7+
import org.springframework.context.annotation.Configuration;
8+
import org.springframework.web.servlet.config.annotation.ResourceHandlerRegistry;
9+
import org.springframework.web.servlet.config.annotation.WebMvcConfigurer;
10+
11+
@Configuration
12+
public class WebConfig implements WebMvcConfigurer {
13+
14+
@Override
15+
public void addResourceHandlers(ResourceHandlerRegistry registry) {
16+
Path uploadDir = Paths.get("uploads").toAbsolutePath().normalize();
17+
18+
try {
19+
Files.createDirectories(uploadDir);
20+
} catch (IOException e) {
21+
throw new RuntimeException("Could not create uploads directory", e);
22+
}
23+
24+
registry.addResourceHandler("/uploads/**")
25+
.addResourceLocations("file:" + uploadDir.toString() + "/");
26+
}
27+
}

backend/src/main/java/travelplanner/controller/TripController.java

Lines changed: 30 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,12 +1,14 @@
11
package travelplanner.controller;
22

33
import jakarta.validation.Valid;
4+
import java.util.List;
45
import java.util.UUID;
56
import lombok.RequiredArgsConstructor;
67
import org.springframework.data.domain.Page;
78
import org.springframework.data.domain.Pageable;
89
import org.springframework.data.web.PageableDefault;
910
import org.springframework.http.HttpStatus;
11+
import org.springframework.http.MediaType;
1012
import org.springframework.http.ResponseEntity;
1113
import org.springframework.security.core.annotation.AuthenticationPrincipal;
1214
import org.springframework.web.bind.annotation.DeleteMapping;
@@ -18,6 +20,8 @@
1820
import org.springframework.web.bind.annotation.RequestMapping;
1921
import org.springframework.web.bind.annotation.RequestParam;
2022
import org.springframework.web.bind.annotation.RestController;
23+
import org.springframework.web.multipart.MultipartFile;
24+
import travelplanner.dto.trip.PhotoResponse;
2125
import travelplanner.dto.trip.TripCreateRequest;
2226
import travelplanner.dto.trip.TripResponse;
2327
import travelplanner.entity.User;
@@ -70,6 +74,32 @@ public ResponseEntity<Void> deleteTrip(@PathVariable UUID tripId) {
7074
return ResponseEntity.noContent().build();
7175
}
7276

77+
@PostMapping(value = "/{tripId}/cover", consumes = MediaType.MULTIPART_FORM_DATA_VALUE)
78+
public ResponseEntity<TripResponse> updateTripCover(
79+
@PathVariable UUID tripId,
80+
@RequestParam("file") MultipartFile file,
81+
@AuthenticationPrincipal User currentUser
82+
) {
83+
TripResponse response = tripService.updateTripCover(tripId, file, currentUser);
84+
return ResponseEntity.ok(response);
85+
}
86+
87+
@PostMapping(value = "/{tripId}/photos", consumes = MediaType.MULTIPART_FORM_DATA_VALUE)
88+
public ResponseEntity<PhotoResponse> uploadTripPhoto(
89+
@PathVariable UUID tripId,
90+
@RequestParam("file") MultipartFile file,
91+
@AuthenticationPrincipal User currentUser
92+
) {
93+
PhotoResponse response = tripService.uploadTripPhoto(tripId, file, currentUser);
94+
return ResponseEntity.ok(response);
95+
}
96+
97+
@GetMapping("/{tripId}/photos")
98+
public ResponseEntity<List<PhotoResponse>> getTripPhotos(@PathVariable UUID tripId) {
99+
List<PhotoResponse> response = tripService.getTripPhotos(tripId);
100+
return ResponseEntity.ok(response);
101+
}
102+
73103
@ExceptionHandler(EntityNotFoundException.class)
74104
public ResponseEntity<String> handleNotFound(EntityNotFoundException ex) {
75105
return ResponseEntity.status(HttpStatus.NOT_FOUND).body(ex.getMessage());
Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,11 @@
1+
package travelplanner.dto.trip;
2+
3+
import java.time.LocalDateTime;
4+
import java.util.UUID;
5+
6+
public record PhotoResponse(
7+
UUID photoId,
8+
String url,
9+
UUID uploaderId,
10+
LocalDateTime createdAt
11+
) {}
Lines changed: 46 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,46 @@
1+
package travelplanner.entity;
2+
3+
import jakarta.persistence.Column;
4+
import jakarta.persistence.Entity;
5+
import jakarta.persistence.FetchType;
6+
import jakarta.persistence.GeneratedValue;
7+
import jakarta.persistence.GenerationType;
8+
import jakarta.persistence.Id;
9+
import jakarta.persistence.JoinColumn;
10+
import jakarta.persistence.ManyToOne;
11+
import jakarta.persistence.Table;
12+
import java.time.LocalDateTime;
13+
import java.util.UUID;
14+
import lombok.Getter;
15+
import lombok.NoArgsConstructor;
16+
import lombok.Setter;
17+
import org.hibernate.annotations.JdbcTypeCode;
18+
import org.hibernate.type.SqlTypes;
19+
20+
@Entity
21+
@Table(name = "photos")
22+
@Getter
23+
@Setter
24+
@NoArgsConstructor
25+
public class Photo {
26+
27+
@Id
28+
@GeneratedValue(strategy = GenerationType.UUID)
29+
@JdbcTypeCode(SqlTypes.VARCHAR)
30+
@Column(name = "photo_id", columnDefinition = "VARCHAR(36)", nullable = false)
31+
private UUID photoId;
32+
33+
@ManyToOne(fetch = FetchType.LAZY)
34+
@JoinColumn(name = "trip_id", nullable = false)
35+
private Trip trip;
36+
37+
@ManyToOne(fetch = FetchType.LAZY)
38+
@JoinColumn(name = "uploader_id", nullable = false)
39+
private User uploader;
40+
41+
@Column(name = "url", nullable = false)
42+
private String url;
43+
44+
@Column(name = "created_at", insertable = false, updatable = false)
45+
private LocalDateTime createdAt;
46+
}
Lines changed: 31 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,31 @@
1+
package travelplanner.entity;
2+
3+
import jakarta.persistence.Column;
4+
import jakarta.persistence.Entity;
5+
import jakarta.persistence.GeneratedValue;
6+
import jakarta.persistence.GenerationType;
7+
import jakarta.persistence.Id;
8+
import jakarta.persistence.Table;
9+
import java.util.UUID;
10+
import lombok.Getter;
11+
import lombok.NoArgsConstructor;
12+
import lombok.Setter;
13+
import org.hibernate.annotations.JdbcTypeCode;
14+
import org.hibernate.type.SqlTypes;
15+
16+
@Entity
17+
@Table(name = "tags")
18+
@Getter
19+
@Setter
20+
@NoArgsConstructor
21+
public class Tag {
22+
23+
@Id
24+
@GeneratedValue(strategy = GenerationType.UUID)
25+
@JdbcTypeCode(SqlTypes.VARCHAR)
26+
@Column(name = "tag_id", columnDefinition = "VARCHAR(36)", nullable = false)
27+
private UUID tagId;
28+
29+
@Column(name = "name", nullable = false, unique = true)
30+
private String name;
31+
}

backend/src/main/java/travelplanner/entity/Trip.java

Lines changed: 23 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -8,13 +8,17 @@
88
import jakarta.persistence.GenerationType;
99
import jakarta.persistence.Id;
1010
import jakarta.persistence.JoinColumn;
11+
import jakarta.persistence.JoinTable;
12+
import jakarta.persistence.ManyToMany;
1113
import jakarta.persistence.ManyToOne;
1214
import jakarta.persistence.OneToMany;
1315
import jakarta.persistence.Table;
1416
import java.math.BigDecimal;
1517
import java.time.LocalDateTime;
1618
import java.util.ArrayList;
19+
import java.util.HashSet;
1720
import java.util.List;
21+
import java.util.Set;
1822
import java.util.UUID;
1923
import lombok.Getter;
2024
import lombok.NoArgsConstructor;
@@ -62,4 +66,23 @@ public class Trip {
6266

6367
@OneToMany(mappedBy = "trip", cascade = CascadeType.ALL, orphanRemoval = true)
6468
private List<TripDay> tripDays = new ArrayList<>();
69+
70+
@ManyToMany
71+
@JoinTable(
72+
name = "trip_tags",
73+
joinColumns = @JoinColumn(name = "trip_id"),
74+
inverseJoinColumns = @JoinColumn(name = "tag_id")
75+
)
76+
private Set<Tag> tags = new HashSet<>();
77+
78+
@ManyToMany
79+
@JoinTable(
80+
name = "trip_participants",
81+
joinColumns = @JoinColumn(name = "trip_id"),
82+
inverseJoinColumns = @JoinColumn(name = "user_id")
83+
)
84+
private Set<User> participants = new HashSet<>();
85+
86+
@OneToMany(mappedBy = "trip", cascade = CascadeType.ALL, orphanRemoval = true)
87+
private List<Photo> photos = new ArrayList<>();
6588
}

0 commit comments

Comments
 (0)