Skip to content

Commit

Permalink
Enhancement/261 complete commercial endpoint functionality (#283)
Browse files Browse the repository at this point in the history
Co-authored-by: Alexander Stanik <astanik@users.noreply.github.com>
  • Loading branch information
XxILUSHAxX and astanik authored Jan 7, 2025
1 parent 9943119 commit 6b14afb
Show file tree
Hide file tree
Showing 11 changed files with 703 additions and 10 deletions.
2 changes: 1 addition & 1 deletion remsfal-core/pom.xml
Original file line number Diff line number Diff line change
Expand Up @@ -99,4 +99,4 @@
</plugin>
</plugins>
</build>
</project>
</project>
Original file line number Diff line number Diff line change
@@ -0,0 +1,101 @@
package de.remsfal.core.api.project;

import de.remsfal.core.api.ProjectEndpoint;
import de.remsfal.core.validation.PatchValidation;
import de.remsfal.core.validation.PostValidation;
import de.remsfal.core.validation.UUID;
import jakarta.validation.Valid;
import jakarta.validation.constraints.NotNull;
import jakarta.validation.groups.ConvertGroup;
import jakarta.ws.rs.core.MediaType;
import jakarta.ws.rs.core.Response;

import jakarta.ws.rs.Consumes;
import jakarta.ws.rs.Path;
import jakarta.ws.rs.GET;
import jakarta.ws.rs.POST;
import jakarta.ws.rs.DELETE;
import jakarta.ws.rs.PATCH;
import jakarta.ws.rs.PathParam;
import jakarta.ws.rs.Produces;


import org.eclipse.microprofile.openapi.annotations.Operation;
import org.eclipse.microprofile.openapi.annotations.headers.Header;
import org.eclipse.microprofile.openapi.annotations.parameters.Parameter;
import org.eclipse.microprofile.openapi.annotations.responses.APIResponse;

import de.remsfal.core.json.project.CommercialJson;

/**
* Endpoint for managing Commercial properties within buildings.
*/
@Path(ProjectEndpoint.CONTEXT + "/" + ProjectEndpoint.VERSION + "/"
+ ProjectEndpoint.SERVICE + "/{projectId}/" + PropertyEndpoint.SERVICE
+ "/{propertyId}/" + BuildingEndpoint.SERVICE
+ "/{buildingId}/" + CommercialEndpoint.SERVICE)
public interface CommercialEndpoint {

String SERVICE = "commercials";

@POST
@Consumes(MediaType.APPLICATION_JSON)
@Operation(summary = "Create a new commercial unit.")
@APIResponse(responseCode = "201", description = "Commercial unit created successfully",
headers = @Header(name = "Location", description = "URL of the new commercial"))
Response createCommercial(
@Parameter(description = "ID of the project", required = true)
@PathParam("projectId") @NotNull @UUID String projectId,
@Parameter(description = "ID of the building", required = true)
@PathParam("buildingId") @NotNull @UUID String buildingId,
@Parameter(description = "Commercial unit information", required = true)
@Valid @ConvertGroup(to = PostValidation.class) CommercialJson commercial
);

@GET
@Path("/{commercialId}")
@Produces(MediaType.APPLICATION_JSON)
@Operation(summary = "Retrieve information about a commercial unit.")
@APIResponse(responseCode = "404", description = "The commercial unit does not exist")
@APIResponse(responseCode = "401", description = "No user authentication provided via session cookie")
CommercialJson getCommercial(
@Parameter(description = "ID of the project", required = true)
@PathParam("projectId") @NotNull @UUID String projectId,
@Parameter(description = "ID of the building", required = true)
@PathParam("buildingId") @NotNull @UUID String buildingId,
@Parameter(description = "ID of the commercial unit", required = true)
@PathParam("commercialId") @NotNull @UUID String commercialId
);

@PATCH
@Path("/{commercialId}")
@Consumes(MediaType.APPLICATION_JSON)
@Produces(MediaType.APPLICATION_JSON)
@Operation(summary = "Update information of a commercial unit")
@APIResponse(responseCode = "401", description = "No user authentication provided via session cookie")
@APIResponse(responseCode = "404", description = "The commercial unit does not exist")
CommercialJson updateCommercial(
@Parameter(description = "ID of the project", required = true)
@PathParam("projectId") @NotNull @UUID String projectId,
@Parameter(description = "ID of the building", required = true)
@PathParam("buildingId") @NotNull @UUID String buildingId,
@Parameter(description = "ID of the commercial unit", required = true)
@PathParam("commercialId") @NotNull @UUID String commercialId,
@Parameter(description = "Commercial unit object with information", required = true)
@Valid @ConvertGroup(to = PatchValidation.class) CommercialJson commercial
);

@DELETE
@Path("/{commercialId}")
@Operation(summary = "Delete an existing commercial unit")
@APIResponse(responseCode = "204", description = "The commercial unit was deleted successfully")
@APIResponse(responseCode = "401", description = "No user authentication provided via session cookie")
void deleteCommercial(
@Parameter(description = "ID of the project", required = true)
@PathParam("projectId") @NotNull @UUID String projectId,
@Parameter(description = "ID of the building", required = true)
@PathParam("buildingId") @NotNull @UUID String buildingId,
@Parameter(description = "ID of the commercial unit", required = true)
@PathParam("commercialId") @NotNull @UUID String commercialId
);
}
Original file line number Diff line number Diff line change
Expand Up @@ -52,6 +52,25 @@ public abstract class CommercialJson implements CommercialModel {

@Null
@Nullable
public abstract Float getRent();
public abstract TenancyJson getTenancy();

/**
* Converts a {@link CommercialModel} to a {@link CommercialJson}.
*
* @param model the {@link CommercialModel} instance to convert.
* @return an immutable {@link CommercialJson} instance.
*/
public static CommercialJson valueOf(final CommercialModel model) {
return ImmutableCommercialJson.builder()
.id(model.getId())
.title(model.getTitle())
.location(model.getLocation())
.description(model.getDescription())
.commercialSpace(model.getCommercialSpace())
.usableSpace(model.getUsableSpace())
.heatingSpace(model.getHeatingSpace())
.tenancy(TenancyJson.valueOf(model.getTenancy()))
.build();
}

}
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@
/**
* @author Alexander Stanik [alexander.stanik@htw-berlin.de]
*/
public interface CommercialModel {
public interface CommercialModel extends RentalUnitModel {

String getId();

Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,55 @@
package de.remsfal.service.boundary.project;

import de.remsfal.core.api.project.CommercialEndpoint;
import de.remsfal.core.json.project.CommercialJson;
import de.remsfal.core.model.project.CommercialModel;
import de.remsfal.service.control.CommercialController;
import jakarta.enterprise.context.RequestScoped;
import jakarta.inject.Inject;
import jakarta.ws.rs.core.MediaType;
import jakarta.ws.rs.core.Response;

import java.net.URI;

/**
* Resource for managing Commercial units via the API.
*/
@RequestScoped
public class CommercialResource extends ProjectSubResource implements CommercialEndpoint {

@Inject
CommercialController controller;

@Override
public Response createCommercial(final String projectId, final String buildingId,
final CommercialJson commercial) {
checkPrivileges(projectId);
final CommercialModel model = controller.createCommercial(projectId, buildingId, commercial);
final URI location = uri.getAbsolutePathBuilder().path(model.getId()).build();
return Response.created(location)
.type(MediaType.APPLICATION_JSON)
.entity(CommercialJson.valueOf(model))
.build();
}

@Override
public CommercialJson getCommercial(final String projectId, final String buildingId, final String commercialId) {
checkPrivileges(projectId);
return CommercialJson.valueOf(controller.getCommercial(projectId, buildingId, commercialId));
}

@Override
public CommercialJson updateCommercial(final String projectId,
final String buildingId, final String commercialId,
final CommercialJson commercial) {
checkPrivileges(projectId);
return CommercialJson.valueOf(controller.updateCommercial(projectId, buildingId, commercialId, commercial));
}

@Override
public void deleteCommercial(final String projectId, final String buildingId,
final String commercialId) {
checkPrivileges(projectId);
controller.deleteCommercial(projectId, buildingId, commercialId);
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,91 @@
package de.remsfal.service.control;

import de.remsfal.core.model.project.CommercialModel;
import de.remsfal.service.entity.dao.CommercialRepository;
import de.remsfal.service.entity.dto.CommercialEntity;
import jakarta.enterprise.context.RequestScoped;
import jakarta.inject.Inject;
import jakarta.persistence.NoResultException;
import jakarta.transaction.Transactional;
import jakarta.ws.rs.NotFoundException;
import org.jboss.logging.Logger;

/**
* Controller for managing Commercial units.
*/
@RequestScoped
public class CommercialController {

@Inject
Logger logger;

@Inject
CommercialRepository commercialRepository;

@Inject
TenancyController tenancyController;

@Transactional
public CommercialModel createCommercial(final String projectId, final String buildingId,
final CommercialModel commercial) {
logger.infov("Creating a commercial (projectId={0}, buildingId={1}, commercial={2})",
projectId, buildingId, commercial);
CommercialEntity entity = CommercialEntity.fromModel(commercial);
entity.generateId();
entity.setProjectId(projectId);
entity.setBuildingId(buildingId);
commercialRepository.persistAndFlush(entity);
commercialRepository.getEntityManager().refresh(entity);
return getCommercial(projectId, buildingId, entity.getId());
}

public CommercialModel getCommercial(final String projectId,
final String buildingId, final String commercialId) {
logger.infov("Retrieving a commercial (projectId={0}, buildingId={1}, commercialId={2})",
projectId, buildingId, commercialId);
CommercialEntity entity = commercialRepository.findCommercialById(projectId, buildingId,commercialId)
.orElseThrow(() -> new NotFoundException("Commercial not exist"));

if (!entity.getProjectId().equals(projectId)) {
throw new NoResultException("Unable to find commercial, because the project ID is invalid");
}

return entity;
}

@Transactional
public CommercialModel updateCommercial(final String projectId, final String buildingId, final String commercialId,
final CommercialModel commercial) {
logger.infov("Updating a commercial (commercialId={0})", commercialId);
CommercialEntity entity = commercialRepository.findCommercialById(projectId, buildingId, commercialId)
.orElseThrow(() -> new NotFoundException("Commercial not exist"));

if (commercial.getTitle() != null) {
entity.setTitle(commercial.getTitle());
}
if (commercial.getLocation() != null) {
entity.setLocation(commercial.getLocation());
}
if (commercial.getCommercialSpace() != null) {
entity.setCommercialSpace(commercial.getCommercialSpace());
}
if (commercial.getHeatingSpace() != null) {
entity.setHeatingSpace(commercial.getHeatingSpace());
}
if (commercial.getTenancy() != null){
entity.setTenancy(tenancyController.updateTenancy(projectId, entity.getTenancy(), commercial.getTenancy()));
}
return commercialRepository.merge(entity);
}

@Transactional
public void deleteCommercial(final String projectId, final String buildingId,
final String commercialId) throws NotFoundException {
logger.infov("Delete a commercial (projectId{0} buildingId={1} commercialId{2})",
projectId, buildingId, commercialId);
if (commercialRepository.findCommercialById(projectId,buildingId,commercialId).isEmpty()) {
throw new NotFoundException("Commercial does not exist");
}
commercialRepository.deleteCommercialById(projectId, buildingId, commercialId);
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -3,14 +3,17 @@
import jakarta.enterprise.context.ApplicationScoped;

import de.remsfal.service.entity.dto.CommercialEntity;
import io.quarkus.panache.common.Parameters;

import java.util.List;
import java.util.Optional;

/**
* @author Alexander Stanik [alexander.stanik@htw-berlin.de]
*/
@ApplicationScoped
public class CommercialRepository extends AbstractRepository<CommercialEntity> {

public List<CommercialEntity> findCommercialByBuildingId(String buildingId) {
return getEntityManager()
.createQuery(
Expand All @@ -20,4 +23,19 @@ public List<CommercialEntity> findCommercialByBuildingId(String buildingId) {
.setParameter("buildingId", buildingId)
.getResultList();
}
}
public Optional<CommercialEntity> findCommercialById(final String projectId,
final String buildingId,
final String commercialId) {
return find("id = :id and projectId = :projectId and buildingId = :buildingId",
Parameters.with("id", commercialId)
.and("projectId", projectId)
.and("buildingId", buildingId)).singleResultOptional();
}

public long deleteCommercialById(final String projectId, final String buildingId, final String commercialId) {
return delete("id = :id and projectId = :projectId and buildingId = :buildingId",
Parameters.with("id", commercialId)
.and("projectId", projectId)
.and("buildingId", buildingId));
}
}
30 changes: 24 additions & 6 deletions remsfal-service/src/test/java/de/remsfal/service/TestData.java
Original file line number Diff line number Diff line change
Expand Up @@ -237,8 +237,8 @@ public static final ImmutableBuildingJson.Builder buildingBuilder2() {
.heatingSpace(TestData.BUILDING_HEATING_SPACE_2)
.isDifferentHeatingSpace(false);
}
// Default test tenancy

// Default test tenancy
public static final String TENANCY_ID = TestData.TENANCY_ID_1;
public static final String TENANCY_START = TestData.TENANCY_START_1;
public static final String TENANCY_END = TestData.TENANCY_END_1;
Expand Down Expand Up @@ -336,7 +336,6 @@ public static final ImmutableApartmentJson.Builder apartmentBuilder2() {
public static final Float COMMERCIAL_COMMERCIAL_SPACE = TestData.COMMERCIAL_COMMERCIAL_SPACE_1;
public static final Float COMMERCIAL_USABLE_SPACE = TestData.COMMERCIAL_USABLE_SPACE_1;
public static final Float COMMERCIAL_HEATING_SPACE = TestData.COMMERCIAL_HEATING_SPACE_1;
public static final Float COMMERCIAL_RENT = TestData.COMMERCIAL_RENT_1;

public static final ImmutableCommercialJson.Builder commercialBuilder() {
return commercialBuilder1();
Expand All @@ -350,7 +349,6 @@ public static final ImmutableCommercialJson.Builder commercialBuilder() {
public static final Float COMMERCIAL_COMMERCIAL_SPACE_1 = 423.92f;
public static final Float COMMERCIAL_USABLE_SPACE_1 = 53.9f;
public static final Float COMMERCIAL_HEATING_SPACE_1 = 204.27f;
public static final Float COMMERCIAL_RENT_1 = 3799.80f;

public static final ImmutableCommercialJson.Builder commercialBuilder1() {
return ImmutableCommercialJson
Expand All @@ -361,8 +359,28 @@ public static final ImmutableCommercialJson.Builder commercialBuilder1() {
.description(COMMERCIAL_DESCRIPTION_1)
.commercialSpace(COMMERCIAL_COMMERCIAL_SPACE_1)
.usableSpace(COMMERCIAL_USABLE_SPACE_1)
.heatingSpace(COMMERCIAL_HEATING_SPACE_1)
.rent(COMMERCIAL_RENT_1);
.heatingSpace(COMMERCIAL_HEATING_SPACE_1);
}

// Test commercial 1
public static final String COMMERCIAL_ID_2 = "b9440c43-b5c0-4951-9c23-000000000002";
public static final String COMMERCIAL_TITLE_2 = "Bäckerei Ekpmel";
public static final String COMMERCIAL_LOCATION_2 = "EG rechts";
public static final String COMMERCIAL_DESCRIPTION_2 = "Bäckerei mit Tischen hinter dem Haus";
public static final Float COMMERCIAL_COMMERCIAL_SPACE_2 = 450.92f;
public static final Float COMMERCIAL_USABLE_SPACE_2 = 100.9f;
public static final Float COMMERCIAL_HEATING_SPACE_2 = 134.27f;

public static final ImmutableCommercialJson.Builder commercialBuilder2() {
return ImmutableCommercialJson
.builder()
.id(COMMERCIAL_ID_2)
.title(COMMERCIAL_TITLE_2)
.location(COMMERCIAL_LOCATION_2)
.description(COMMERCIAL_DESCRIPTION_2)
.commercialSpace(COMMERCIAL_COMMERCIAL_SPACE_2)
.usableSpace(COMMERCIAL_USABLE_SPACE_2)
.heatingSpace(COMMERCIAL_HEATING_SPACE_2);
}

// Default test garage
Expand Down
Loading

0 comments on commit 6b14afb

Please sign in to comment.