Skip to content

Commit 6b14afb

Browse files
XxILUSHAxXastanik
andauthored
Enhancement/261 complete commercial endpoint functionality (#283)
Co-authored-by: Alexander Stanik <astanik@users.noreply.github.com>
1 parent 9943119 commit 6b14afb

File tree

11 files changed

+703
-10
lines changed

11 files changed

+703
-10
lines changed

remsfal-core/pom.xml

+1-1
Original file line numberDiff line numberDiff line change
@@ -99,4 +99,4 @@
9999
</plugin>
100100
</plugins>
101101
</build>
102-
</project>
102+
</project>
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,101 @@
1+
package de.remsfal.core.api.project;
2+
3+
import de.remsfal.core.api.ProjectEndpoint;
4+
import de.remsfal.core.validation.PatchValidation;
5+
import de.remsfal.core.validation.PostValidation;
6+
import de.remsfal.core.validation.UUID;
7+
import jakarta.validation.Valid;
8+
import jakarta.validation.constraints.NotNull;
9+
import jakarta.validation.groups.ConvertGroup;
10+
import jakarta.ws.rs.core.MediaType;
11+
import jakarta.ws.rs.core.Response;
12+
13+
import jakarta.ws.rs.Consumes;
14+
import jakarta.ws.rs.Path;
15+
import jakarta.ws.rs.GET;
16+
import jakarta.ws.rs.POST;
17+
import jakarta.ws.rs.DELETE;
18+
import jakarta.ws.rs.PATCH;
19+
import jakarta.ws.rs.PathParam;
20+
import jakarta.ws.rs.Produces;
21+
22+
23+
import org.eclipse.microprofile.openapi.annotations.Operation;
24+
import org.eclipse.microprofile.openapi.annotations.headers.Header;
25+
import org.eclipse.microprofile.openapi.annotations.parameters.Parameter;
26+
import org.eclipse.microprofile.openapi.annotations.responses.APIResponse;
27+
28+
import de.remsfal.core.json.project.CommercialJson;
29+
30+
/**
31+
* Endpoint for managing Commercial properties within buildings.
32+
*/
33+
@Path(ProjectEndpoint.CONTEXT + "/" + ProjectEndpoint.VERSION + "/"
34+
+ ProjectEndpoint.SERVICE + "/{projectId}/" + PropertyEndpoint.SERVICE
35+
+ "/{propertyId}/" + BuildingEndpoint.SERVICE
36+
+ "/{buildingId}/" + CommercialEndpoint.SERVICE)
37+
public interface CommercialEndpoint {
38+
39+
String SERVICE = "commercials";
40+
41+
@POST
42+
@Consumes(MediaType.APPLICATION_JSON)
43+
@Operation(summary = "Create a new commercial unit.")
44+
@APIResponse(responseCode = "201", description = "Commercial unit created successfully",
45+
headers = @Header(name = "Location", description = "URL of the new commercial"))
46+
Response createCommercial(
47+
@Parameter(description = "ID of the project", required = true)
48+
@PathParam("projectId") @NotNull @UUID String projectId,
49+
@Parameter(description = "ID of the building", required = true)
50+
@PathParam("buildingId") @NotNull @UUID String buildingId,
51+
@Parameter(description = "Commercial unit information", required = true)
52+
@Valid @ConvertGroup(to = PostValidation.class) CommercialJson commercial
53+
);
54+
55+
@GET
56+
@Path("/{commercialId}")
57+
@Produces(MediaType.APPLICATION_JSON)
58+
@Operation(summary = "Retrieve information about a commercial unit.")
59+
@APIResponse(responseCode = "404", description = "The commercial unit does not exist")
60+
@APIResponse(responseCode = "401", description = "No user authentication provided via session cookie")
61+
CommercialJson getCommercial(
62+
@Parameter(description = "ID of the project", required = true)
63+
@PathParam("projectId") @NotNull @UUID String projectId,
64+
@Parameter(description = "ID of the building", required = true)
65+
@PathParam("buildingId") @NotNull @UUID String buildingId,
66+
@Parameter(description = "ID of the commercial unit", required = true)
67+
@PathParam("commercialId") @NotNull @UUID String commercialId
68+
);
69+
70+
@PATCH
71+
@Path("/{commercialId}")
72+
@Consumes(MediaType.APPLICATION_JSON)
73+
@Produces(MediaType.APPLICATION_JSON)
74+
@Operation(summary = "Update information of a commercial unit")
75+
@APIResponse(responseCode = "401", description = "No user authentication provided via session cookie")
76+
@APIResponse(responseCode = "404", description = "The commercial unit does not exist")
77+
CommercialJson updateCommercial(
78+
@Parameter(description = "ID of the project", required = true)
79+
@PathParam("projectId") @NotNull @UUID String projectId,
80+
@Parameter(description = "ID of the building", required = true)
81+
@PathParam("buildingId") @NotNull @UUID String buildingId,
82+
@Parameter(description = "ID of the commercial unit", required = true)
83+
@PathParam("commercialId") @NotNull @UUID String commercialId,
84+
@Parameter(description = "Commercial unit object with information", required = true)
85+
@Valid @ConvertGroup(to = PatchValidation.class) CommercialJson commercial
86+
);
87+
88+
@DELETE
89+
@Path("/{commercialId}")
90+
@Operation(summary = "Delete an existing commercial unit")
91+
@APIResponse(responseCode = "204", description = "The commercial unit was deleted successfully")
92+
@APIResponse(responseCode = "401", description = "No user authentication provided via session cookie")
93+
void deleteCommercial(
94+
@Parameter(description = "ID of the project", required = true)
95+
@PathParam("projectId") @NotNull @UUID String projectId,
96+
@Parameter(description = "ID of the building", required = true)
97+
@PathParam("buildingId") @NotNull @UUID String buildingId,
98+
@Parameter(description = "ID of the commercial unit", required = true)
99+
@PathParam("commercialId") @NotNull @UUID String commercialId
100+
);
101+
}

remsfal-core/src/main/java/de/remsfal/core/json/project/CommercialJson.java

+20-1
Original file line numberDiff line numberDiff line change
@@ -52,6 +52,25 @@ public abstract class CommercialJson implements CommercialModel {
5252

5353
@Null
5454
@Nullable
55-
public abstract Float getRent();
55+
public abstract TenancyJson getTenancy();
56+
57+
/**
58+
* Converts a {@link CommercialModel} to a {@link CommercialJson}.
59+
*
60+
* @param model the {@link CommercialModel} instance to convert.
61+
* @return an immutable {@link CommercialJson} instance.
62+
*/
63+
public static CommercialJson valueOf(final CommercialModel model) {
64+
return ImmutableCommercialJson.builder()
65+
.id(model.getId())
66+
.title(model.getTitle())
67+
.location(model.getLocation())
68+
.description(model.getDescription())
69+
.commercialSpace(model.getCommercialSpace())
70+
.usableSpace(model.getUsableSpace())
71+
.heatingSpace(model.getHeatingSpace())
72+
.tenancy(TenancyJson.valueOf(model.getTenancy()))
73+
.build();
74+
}
5675

5776
}

remsfal-core/src/main/java/de/remsfal/core/model/project/CommercialModel.java

+1-1
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,7 @@
33
/**
44
* @author Alexander Stanik [alexander.stanik@htw-berlin.de]
55
*/
6-
public interface CommercialModel {
6+
public interface CommercialModel extends RentalUnitModel {
77

88
String getId();
99

Original file line numberDiff line numberDiff line change
@@ -0,0 +1,55 @@
1+
package de.remsfal.service.boundary.project;
2+
3+
import de.remsfal.core.api.project.CommercialEndpoint;
4+
import de.remsfal.core.json.project.CommercialJson;
5+
import de.remsfal.core.model.project.CommercialModel;
6+
import de.remsfal.service.control.CommercialController;
7+
import jakarta.enterprise.context.RequestScoped;
8+
import jakarta.inject.Inject;
9+
import jakarta.ws.rs.core.MediaType;
10+
import jakarta.ws.rs.core.Response;
11+
12+
import java.net.URI;
13+
14+
/**
15+
* Resource for managing Commercial units via the API.
16+
*/
17+
@RequestScoped
18+
public class CommercialResource extends ProjectSubResource implements CommercialEndpoint {
19+
20+
@Inject
21+
CommercialController controller;
22+
23+
@Override
24+
public Response createCommercial(final String projectId, final String buildingId,
25+
final CommercialJson commercial) {
26+
checkPrivileges(projectId);
27+
final CommercialModel model = controller.createCommercial(projectId, buildingId, commercial);
28+
final URI location = uri.getAbsolutePathBuilder().path(model.getId()).build();
29+
return Response.created(location)
30+
.type(MediaType.APPLICATION_JSON)
31+
.entity(CommercialJson.valueOf(model))
32+
.build();
33+
}
34+
35+
@Override
36+
public CommercialJson getCommercial(final String projectId, final String buildingId, final String commercialId) {
37+
checkPrivileges(projectId);
38+
return CommercialJson.valueOf(controller.getCommercial(projectId, buildingId, commercialId));
39+
}
40+
41+
@Override
42+
public CommercialJson updateCommercial(final String projectId,
43+
final String buildingId, final String commercialId,
44+
final CommercialJson commercial) {
45+
checkPrivileges(projectId);
46+
return CommercialJson.valueOf(controller.updateCommercial(projectId, buildingId, commercialId, commercial));
47+
}
48+
49+
@Override
50+
public void deleteCommercial(final String projectId, final String buildingId,
51+
final String commercialId) {
52+
checkPrivileges(projectId);
53+
controller.deleteCommercial(projectId, buildingId, commercialId);
54+
}
55+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,91 @@
1+
package de.remsfal.service.control;
2+
3+
import de.remsfal.core.model.project.CommercialModel;
4+
import de.remsfal.service.entity.dao.CommercialRepository;
5+
import de.remsfal.service.entity.dto.CommercialEntity;
6+
import jakarta.enterprise.context.RequestScoped;
7+
import jakarta.inject.Inject;
8+
import jakarta.persistence.NoResultException;
9+
import jakarta.transaction.Transactional;
10+
import jakarta.ws.rs.NotFoundException;
11+
import org.jboss.logging.Logger;
12+
13+
/**
14+
* Controller for managing Commercial units.
15+
*/
16+
@RequestScoped
17+
public class CommercialController {
18+
19+
@Inject
20+
Logger logger;
21+
22+
@Inject
23+
CommercialRepository commercialRepository;
24+
25+
@Inject
26+
TenancyController tenancyController;
27+
28+
@Transactional
29+
public CommercialModel createCommercial(final String projectId, final String buildingId,
30+
final CommercialModel commercial) {
31+
logger.infov("Creating a commercial (projectId={0}, buildingId={1}, commercial={2})",
32+
projectId, buildingId, commercial);
33+
CommercialEntity entity = CommercialEntity.fromModel(commercial);
34+
entity.generateId();
35+
entity.setProjectId(projectId);
36+
entity.setBuildingId(buildingId);
37+
commercialRepository.persistAndFlush(entity);
38+
commercialRepository.getEntityManager().refresh(entity);
39+
return getCommercial(projectId, buildingId, entity.getId());
40+
}
41+
42+
public CommercialModel getCommercial(final String projectId,
43+
final String buildingId, final String commercialId) {
44+
logger.infov("Retrieving a commercial (projectId={0}, buildingId={1}, commercialId={2})",
45+
projectId, buildingId, commercialId);
46+
CommercialEntity entity = commercialRepository.findCommercialById(projectId, buildingId,commercialId)
47+
.orElseThrow(() -> new NotFoundException("Commercial not exist"));
48+
49+
if (!entity.getProjectId().equals(projectId)) {
50+
throw new NoResultException("Unable to find commercial, because the project ID is invalid");
51+
}
52+
53+
return entity;
54+
}
55+
56+
@Transactional
57+
public CommercialModel updateCommercial(final String projectId, final String buildingId, final String commercialId,
58+
final CommercialModel commercial) {
59+
logger.infov("Updating a commercial (commercialId={0})", commercialId);
60+
CommercialEntity entity = commercialRepository.findCommercialById(projectId, buildingId, commercialId)
61+
.orElseThrow(() -> new NotFoundException("Commercial not exist"));
62+
63+
if (commercial.getTitle() != null) {
64+
entity.setTitle(commercial.getTitle());
65+
}
66+
if (commercial.getLocation() != null) {
67+
entity.setLocation(commercial.getLocation());
68+
}
69+
if (commercial.getCommercialSpace() != null) {
70+
entity.setCommercialSpace(commercial.getCommercialSpace());
71+
}
72+
if (commercial.getHeatingSpace() != null) {
73+
entity.setHeatingSpace(commercial.getHeatingSpace());
74+
}
75+
if (commercial.getTenancy() != null){
76+
entity.setTenancy(tenancyController.updateTenancy(projectId, entity.getTenancy(), commercial.getTenancy()));
77+
}
78+
return commercialRepository.merge(entity);
79+
}
80+
81+
@Transactional
82+
public void deleteCommercial(final String projectId, final String buildingId,
83+
final String commercialId) throws NotFoundException {
84+
logger.infov("Delete a commercial (projectId{0} buildingId={1} commercialId{2})",
85+
projectId, buildingId, commercialId);
86+
if (commercialRepository.findCommercialById(projectId,buildingId,commercialId).isEmpty()) {
87+
throw new NotFoundException("Commercial does not exist");
88+
}
89+
commercialRepository.deleteCommercialById(projectId, buildingId, commercialId);
90+
}
91+
}

remsfal-service/src/main/java/de/remsfal/service/entity/dao/CommercialRepository.java

+19-1
Original file line numberDiff line numberDiff line change
@@ -3,14 +3,17 @@
33
import jakarta.enterprise.context.ApplicationScoped;
44

55
import de.remsfal.service.entity.dto.CommercialEntity;
6+
import io.quarkus.panache.common.Parameters;
67

78
import java.util.List;
9+
import java.util.Optional;
810

911
/**
1012
* @author Alexander Stanik [alexander.stanik@htw-berlin.de]
1113
*/
1214
@ApplicationScoped
1315
public class CommercialRepository extends AbstractRepository<CommercialEntity> {
16+
1417
public List<CommercialEntity> findCommercialByBuildingId(String buildingId) {
1518
return getEntityManager()
1619
.createQuery(
@@ -20,4 +23,19 @@ public List<CommercialEntity> findCommercialByBuildingId(String buildingId) {
2023
.setParameter("buildingId", buildingId)
2124
.getResultList();
2225
}
23-
}
26+
public Optional<CommercialEntity> findCommercialById(final String projectId,
27+
final String buildingId,
28+
final String commercialId) {
29+
return find("id = :id and projectId = :projectId and buildingId = :buildingId",
30+
Parameters.with("id", commercialId)
31+
.and("projectId", projectId)
32+
.and("buildingId", buildingId)).singleResultOptional();
33+
}
34+
35+
public long deleteCommercialById(final String projectId, final String buildingId, final String commercialId) {
36+
return delete("id = :id and projectId = :projectId and buildingId = :buildingId",
37+
Parameters.with("id", commercialId)
38+
.and("projectId", projectId)
39+
.and("buildingId", buildingId));
40+
}
41+
}

remsfal-service/src/test/java/de/remsfal/service/TestData.java

+24-6
Original file line numberDiff line numberDiff line change
@@ -237,8 +237,8 @@ public static final ImmutableBuildingJson.Builder buildingBuilder2() {
237237
.heatingSpace(TestData.BUILDING_HEATING_SPACE_2)
238238
.isDifferentHeatingSpace(false);
239239
}
240-
241-
// Default test tenancy
240+
241+
// Default test tenancy
242242
public static final String TENANCY_ID = TestData.TENANCY_ID_1;
243243
public static final String TENANCY_START = TestData.TENANCY_START_1;
244244
public static final String TENANCY_END = TestData.TENANCY_END_1;
@@ -336,7 +336,6 @@ public static final ImmutableApartmentJson.Builder apartmentBuilder2() {
336336
public static final Float COMMERCIAL_COMMERCIAL_SPACE = TestData.COMMERCIAL_COMMERCIAL_SPACE_1;
337337
public static final Float COMMERCIAL_USABLE_SPACE = TestData.COMMERCIAL_USABLE_SPACE_1;
338338
public static final Float COMMERCIAL_HEATING_SPACE = TestData.COMMERCIAL_HEATING_SPACE_1;
339-
public static final Float COMMERCIAL_RENT = TestData.COMMERCIAL_RENT_1;
340339

341340
public static final ImmutableCommercialJson.Builder commercialBuilder() {
342341
return commercialBuilder1();
@@ -350,7 +349,6 @@ public static final ImmutableCommercialJson.Builder commercialBuilder() {
350349
public static final Float COMMERCIAL_COMMERCIAL_SPACE_1 = 423.92f;
351350
public static final Float COMMERCIAL_USABLE_SPACE_1 = 53.9f;
352351
public static final Float COMMERCIAL_HEATING_SPACE_1 = 204.27f;
353-
public static final Float COMMERCIAL_RENT_1 = 3799.80f;
354352

355353
public static final ImmutableCommercialJson.Builder commercialBuilder1() {
356354
return ImmutableCommercialJson
@@ -361,8 +359,28 @@ public static final ImmutableCommercialJson.Builder commercialBuilder1() {
361359
.description(COMMERCIAL_DESCRIPTION_1)
362360
.commercialSpace(COMMERCIAL_COMMERCIAL_SPACE_1)
363361
.usableSpace(COMMERCIAL_USABLE_SPACE_1)
364-
.heatingSpace(COMMERCIAL_HEATING_SPACE_1)
365-
.rent(COMMERCIAL_RENT_1);
362+
.heatingSpace(COMMERCIAL_HEATING_SPACE_1);
363+
}
364+
365+
// Test commercial 1
366+
public static final String COMMERCIAL_ID_2 = "b9440c43-b5c0-4951-9c23-000000000002";
367+
public static final String COMMERCIAL_TITLE_2 = "Bäckerei Ekpmel";
368+
public static final String COMMERCIAL_LOCATION_2 = "EG rechts";
369+
public static final String COMMERCIAL_DESCRIPTION_2 = "Bäckerei mit Tischen hinter dem Haus";
370+
public static final Float COMMERCIAL_COMMERCIAL_SPACE_2 = 450.92f;
371+
public static final Float COMMERCIAL_USABLE_SPACE_2 = 100.9f;
372+
public static final Float COMMERCIAL_HEATING_SPACE_2 = 134.27f;
373+
374+
public static final ImmutableCommercialJson.Builder commercialBuilder2() {
375+
return ImmutableCommercialJson
376+
.builder()
377+
.id(COMMERCIAL_ID_2)
378+
.title(COMMERCIAL_TITLE_2)
379+
.location(COMMERCIAL_LOCATION_2)
380+
.description(COMMERCIAL_DESCRIPTION_2)
381+
.commercialSpace(COMMERCIAL_COMMERCIAL_SPACE_2)
382+
.usableSpace(COMMERCIAL_USABLE_SPACE_2)
383+
.heatingSpace(COMMERCIAL_HEATING_SPACE_2);
366384
}
367385

368386
// Default test garage

0 commit comments

Comments
 (0)