Skip to content

Commit 18b37d3

Browse files
committed
feat: implement stickers API
1 parent bf603c8 commit 18b37d3

19 files changed

Lines changed: 1231 additions & 8 deletions
Lines changed: 133 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,133 @@
1+
package com.datadoghq.stickerlandia.stickeraward;
2+
3+
import com.datadoghq.stickerlandia.stickeraward.beans.CreateStickerCommand;
4+
import com.datadoghq.stickerlandia.stickeraward.beans.StickerAssignmentsResponse;
5+
import com.datadoghq.stickerlandia.stickeraward.beans.StickerCatalogResponse;
6+
import com.datadoghq.stickerlandia.stickeraward.beans.StickerCreatedResponse;
7+
import com.datadoghq.stickerlandia.stickeraward.beans.StickerMetadata;
8+
import com.datadoghq.stickerlandia.stickeraward.beans.UpdateStickerCommand;
9+
import io.smallrye.common.constraint.NotNull;
10+
import jakarta.ws.rs.Consumes;
11+
import jakarta.ws.rs.DELETE;
12+
import jakarta.ws.rs.DefaultValue;
13+
import jakarta.ws.rs.GET;
14+
import jakarta.ws.rs.POST;
15+
import jakarta.ws.rs.PUT;
16+
import jakarta.ws.rs.Path;
17+
import jakarta.ws.rs.PathParam;
18+
import jakarta.ws.rs.Produces;
19+
import jakarta.ws.rs.QueryParam;
20+
import jakarta.ws.rs.core.Response;
21+
import org.eclipse.microprofile.openapi.annotations.Operation;
22+
import jakarta.inject.Inject;
23+
24+
import java.io.InputStream;
25+
26+
@Path("/api/award/v1/stickers")
27+
public class StickerCatalogResource {
28+
29+
@Inject
30+
StickerCatalogService stickerCatalogService;
31+
32+
@GET
33+
@Produces("application/json")
34+
@Operation(description = "Get all stickers in the catalog")
35+
public StickerCatalogResponse getAllStickers(
36+
@QueryParam("page") @DefaultValue("0") int page,
37+
@QueryParam("size") @DefaultValue("20") int size) {
38+
return stickerCatalogService.getAllStickers(page, size);
39+
}
40+
41+
@POST
42+
@Produces("application/json")
43+
@Consumes("application/json")
44+
@Operation(description = "Create a new sticker in the catalog")
45+
public Response createSticker(@NotNull CreateStickerCommand data) {
46+
StickerCreatedResponse createdSticker = stickerCatalogService.createSticker(data);
47+
return Response.status(Response.Status.CREATED)
48+
.entity(createdSticker)
49+
.build();
50+
}
51+
52+
@GET
53+
@Path("/{stickerId}")
54+
@Produces("application/json")
55+
@Operation(description = "Get a specific sticker's metadata")
56+
public Response getStickerMetadata(@PathParam("stickerId") String stickerId) {
57+
StickerMetadata metadata = stickerCatalogService.getStickerMetadata(stickerId);
58+
if (metadata == null) {
59+
return Response.status(Response.Status.NOT_FOUND).build();
60+
}
61+
return Response.ok(metadata).build();
62+
}
63+
64+
@PUT
65+
@Path("/{stickerId}")
66+
@Produces("application/json")
67+
@Consumes("application/json")
68+
@Operation(description = "Update a sticker's metadata")
69+
public Response updateStickerMetadata(
70+
@PathParam("stickerId") String stickerId,
71+
@NotNull UpdateStickerCommand data) {
72+
StickerMetadata updated = stickerCatalogService.updateStickerMetadata(stickerId, data);
73+
if (updated == null) {
74+
return Response.status(Response.Status.NOT_FOUND).build();
75+
}
76+
return Response.ok(updated).build();
77+
}
78+
79+
@DELETE
80+
@Path("/{stickerId}")
81+
@Operation(description = "Delete a sticker from the catalog. A sticker can only be deleted if it is not assigned to anyone.")
82+
public Response deleteSticker(@PathParam("stickerId") String stickerId) {
83+
try {
84+
boolean deleted = stickerCatalogService.deleteSticker(stickerId);
85+
if (!deleted) {
86+
return Response.status(Response.Status.NOT_FOUND).build();
87+
}
88+
return Response.noContent().build();
89+
} catch (IllegalStateException e) {
90+
return Response.status(Response.Status.BAD_REQUEST)
91+
.entity("Cannot delete sticker that is assigned to users")
92+
.build();
93+
}
94+
}
95+
96+
@GET
97+
@Path("/{stickerId}/image")
98+
@Produces("image/png")
99+
@Operation(description = "Get the sticker image")
100+
public Response getStickerImage(@PathParam("stickerId") String stickerId) {
101+
return Response.status(Response.Status.NOT_IMPLEMENTED)
102+
.entity("Image storage not implemented yet")
103+
.build();
104+
}
105+
106+
@PUT
107+
@Path("/{stickerId}/image")
108+
@Produces("application/json")
109+
@Consumes("image/png")
110+
@Operation(description = "Upload or update the sticker image")
111+
public Response uploadStickerImage(
112+
@PathParam("stickerId") String stickerId,
113+
@NotNull InputStream data) {
114+
return Response.status(Response.Status.NOT_IMPLEMENTED)
115+
.entity("Image upload not implemented yet")
116+
.build();
117+
}
118+
119+
@GET
120+
@Path("/{stickerId}/assignments")
121+
@Produces("application/json")
122+
@Operation(description = "Get users to which this sticker is assigned")
123+
public Response getStickerAssignments(
124+
@PathParam("stickerId") String stickerId,
125+
@QueryParam("page") @DefaultValue("0") int page,
126+
@QueryParam("size") @DefaultValue("20") int size) {
127+
StickerAssignmentsResponse data = stickerCatalogService.getStickerAssignments(stickerId, page, size);
128+
if (data == null) {
129+
return Response.status(Response.Status.NOT_FOUND).build();
130+
}
131+
return Response.ok(data).build();
132+
}
133+
}
Lines changed: 165 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,165 @@
1+
package com.datadoghq.stickerlandia.stickeraward;
2+
3+
import com.datadoghq.stickerlandia.stickeraward.beans.CreateStickerCommand;
4+
import com.datadoghq.stickerlandia.stickeraward.beans.PagedResponse;
5+
import com.datadoghq.stickerlandia.stickeraward.beans.StickerAssignmentsResponse;
6+
import com.datadoghq.stickerlandia.stickeraward.beans.StickerCatalogResponse;
7+
import com.datadoghq.stickerlandia.stickeraward.beans.StickerCreatedResponse;
8+
import com.datadoghq.stickerlandia.stickeraward.beans.StickerMetadata;
9+
import com.datadoghq.stickerlandia.stickeraward.beans.UpdateStickerCommand;
10+
import com.datadoghq.stickerlandia.stickeraward.beans.UserAssignment;
11+
import com.datadoghq.stickerlandia.stickeraward.entity.Sticker;
12+
import com.datadoghq.stickerlandia.stickeraward.entity.StickerAssignment;
13+
import io.quarkus.hibernate.orm.panache.PanacheQuery;
14+
import io.quarkus.panache.common.Page;
15+
import jakarta.enterprise.context.ApplicationScoped;
16+
import jakarta.transaction.Transactional;
17+
18+
import java.time.Instant;
19+
import java.util.Date;
20+
import java.util.List;
21+
import java.util.UUID;
22+
import java.util.stream.Collectors;
23+
24+
@ApplicationScoped
25+
public class StickerCatalogService {
26+
27+
@Transactional
28+
public StickerCreatedResponse createSticker(CreateStickerCommand command) {
29+
String stickerId = "sticker-" + UUID.randomUUID().toString().substring(0, 8);
30+
31+
Sticker sticker = new Sticker(
32+
stickerId,
33+
command.getStickerName(),
34+
command.getStickerDescription(),
35+
null, // Image URL will be set when image is uploaded
36+
command.getStickerQuantityRemaining()
37+
);
38+
39+
sticker.persist();
40+
41+
StickerCreatedResponse response = new StickerCreatedResponse();
42+
response.setStickerId(sticker.getStickerId());
43+
response.setStickerName(sticker.getName());
44+
response.setImageUrl(buildImageUrl(sticker.getStickerId()));
45+
return response;
46+
}
47+
48+
public StickerCatalogResponse getAllStickers(int page, int size) {
49+
PanacheQuery<Sticker> query = Sticker.findAll();
50+
List<Sticker> stickers = query.page(Page.of(page, size)).list();
51+
long totalCount = query.count();
52+
53+
List<StickerMetadata> stickerMetadataList = stickers.stream()
54+
.map(this::toStickerMetadata)
55+
.collect(Collectors.toList());
56+
57+
PagedResponse pagination = new PagedResponse();
58+
pagination.setPage(page);
59+
pagination.setSize(size);
60+
pagination.setTotal((int) totalCount);
61+
pagination.setTotalPages((int) Math.ceil((double) totalCount / size));
62+
63+
StickerCatalogResponse response = new StickerCatalogResponse();
64+
response.setStickers(stickerMetadataList);
65+
response.setPagination(pagination);
66+
return response;
67+
}
68+
69+
public StickerMetadata getStickerMetadata(String stickerId) {
70+
Sticker sticker = Sticker.findById(stickerId);
71+
if (sticker == null) {
72+
return null;
73+
}
74+
return toStickerMetadata(sticker);
75+
}
76+
77+
@Transactional
78+
public StickerMetadata updateStickerMetadata(String stickerId, UpdateStickerCommand command) {
79+
Sticker sticker = Sticker.findById(stickerId);
80+
if (sticker == null) {
81+
return null;
82+
}
83+
84+
if (command.getStickerName() != null) {
85+
sticker.setName(command.getStickerName());
86+
}
87+
if (command.getStickerDescription() != null) {
88+
sticker.setDescription(command.getStickerDescription());
89+
}
90+
if (command.getStickerQuantityRemaining() != null) {
91+
sticker.setStickerQuantityRemaining(command.getStickerQuantityRemaining());
92+
}
93+
94+
sticker.setUpdatedAt(Instant.now());
95+
sticker.persist();
96+
97+
return toStickerMetadata(sticker);
98+
}
99+
100+
@Transactional
101+
public boolean deleteSticker(String stickerId) {
102+
Sticker sticker = Sticker.findById(stickerId);
103+
if (sticker == null) {
104+
return false;
105+
}
106+
107+
// Check if sticker is assigned to any users
108+
long assignmentCount = StickerAssignment.countActiveByStickerId(stickerId);
109+
if (assignmentCount > 0) {
110+
throw new IllegalStateException("Cannot delete sticker that is assigned to users");
111+
}
112+
113+
sticker.delete();
114+
return true;
115+
}
116+
117+
public StickerAssignmentsResponse getStickerAssignments(String stickerId, int page, int size) {
118+
Sticker sticker = Sticker.findById(stickerId);
119+
if (sticker == null) {
120+
return null;
121+
}
122+
123+
PanacheQuery<StickerAssignment> query = StickerAssignment.find("sticker.stickerId = ?1 AND removedAt IS NULL", stickerId);
124+
List<StickerAssignment> assignments = query.page(Page.of(page, size)).list();
125+
long totalCount = query.count();
126+
127+
List<UserAssignment> userAssignments = assignments.stream()
128+
.map(assignment -> {
129+
UserAssignment ua = new UserAssignment();
130+
ua.setUserId(assignment.getUserId());
131+
ua.setAssignedAt(Date.from(assignment.getAssignedAt()));
132+
ua.setReason(assignment.getReason());
133+
return ua;
134+
})
135+
.collect(Collectors.toList());
136+
137+
PagedResponse pagination = new PagedResponse();
138+
pagination.setPage(page);
139+
pagination.setSize(size);
140+
pagination.setTotal((int) totalCount);
141+
pagination.setTotalPages((int) Math.ceil((double) totalCount / size));
142+
143+
StickerAssignmentsResponse response = new StickerAssignmentsResponse();
144+
response.setStickerId(stickerId);
145+
response.setAssignments(userAssignments);
146+
response.setPagination(pagination);
147+
return response;
148+
}
149+
150+
private StickerMetadata toStickerMetadata(Sticker sticker) {
151+
StickerMetadata metadata = new StickerMetadata();
152+
metadata.setStickerId(sticker.getStickerId());
153+
metadata.setStickerName(sticker.getName());
154+
metadata.setStickerDescription(sticker.getDescription());
155+
metadata.setStickerQuantityRemaining(sticker.getStickerQuantityRemaining());
156+
metadata.setImageUrl(buildImageUrl(sticker.getStickerId()));
157+
metadata.setCreatedAt(Date.from(sticker.getCreatedAt()));
158+
metadata.setUpdatedAt(sticker.getUpdatedAt() != null ? Date.from(sticker.getUpdatedAt()) : null);
159+
return metadata;
160+
}
161+
162+
private String buildImageUrl(String stickerId) {
163+
return "/api/award/v1/stickers/" + stickerId + "/image";
164+
}
165+
}
Lines changed: 55 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,55 @@
1+
package com.datadoghq.stickerlandia.stickeraward.beans;
2+
3+
import com.fasterxml.jackson.annotation.JsonInclude;
4+
import com.fasterxml.jackson.annotation.JsonProperty;
5+
import com.fasterxml.jackson.annotation.JsonPropertyDescription;
6+
import com.fasterxml.jackson.annotation.JsonPropertyOrder;
7+
8+
@JsonInclude(JsonInclude.Include.NON_NULL)
9+
@JsonPropertyOrder({
10+
"stickerName",
11+
"stickerDescription",
12+
"stickerQuantityRemaining"
13+
})
14+
public class CreateStickerCommand {
15+
16+
@JsonProperty("stickerName")
17+
private String stickerName;
18+
19+
@JsonProperty("stickerDescription")
20+
private String stickerDescription;
21+
22+
@JsonProperty("stickerQuantityRemaining")
23+
@JsonPropertyDescription("Quantity remaining (-1 for infinite)")
24+
private Integer stickerQuantityRemaining;
25+
26+
@JsonProperty("stickerName")
27+
public String getStickerName() {
28+
return stickerName;
29+
}
30+
31+
@JsonProperty("stickerName")
32+
public void setStickerName(String stickerName) {
33+
this.stickerName = stickerName;
34+
}
35+
36+
@JsonProperty("stickerDescription")
37+
public String getStickerDescription() {
38+
return stickerDescription;
39+
}
40+
41+
@JsonProperty("stickerDescription")
42+
public void setStickerDescription(String stickerDescription) {
43+
this.stickerDescription = stickerDescription;
44+
}
45+
46+
@JsonProperty("stickerQuantityRemaining")
47+
public Integer getStickerQuantityRemaining() {
48+
return stickerQuantityRemaining;
49+
}
50+
51+
@JsonProperty("stickerQuantityRemaining")
52+
public void setStickerQuantityRemaining(Integer stickerQuantityRemaining) {
53+
this.stickerQuantityRemaining = stickerQuantityRemaining;
54+
}
55+
}

0 commit comments

Comments
 (0)