Skip to content

Commit

Permalink
feat: create building and show in list view
Browse files Browse the repository at this point in the history
  • Loading branch information
thongdanghoang committed Feb 15, 2025
1 parent c9e8bc7 commit 75415bc
Show file tree
Hide file tree
Showing 25 changed files with 627 additions and 84 deletions.
Original file line number Diff line number Diff line change
@@ -1,20 +1,16 @@
package enterprise.dtos;

import green_buildings.commons.api.BaseDTO;
import jakarta.validation.constraints.DecimalMin;
import jakarta.validation.constraints.Min;
import jakarta.validation.constraints.NotBlank;
import lombok.Builder;

import java.math.BigDecimal;
import java.util.UUID;

@Builder
public record BuildingDTO(
UUID id,
int version,
@NotBlank String name,
@Min(1) int floors,
@DecimalMin(value = "0.0", inclusive = true) BigDecimal squareMeters
long numberOfDevices
) implements BaseDTO {
}
Original file line number Diff line number Diff line change
Expand Up @@ -7,16 +7,12 @@
import jakarta.persistence.JoinColumn;
import jakarta.persistence.ManyToOne;
import jakarta.persistence.Table;
import jakarta.validation.constraints.DecimalMin;
import jakarta.validation.constraints.Min;
import jakarta.validation.constraints.NotBlank;
import jakarta.validation.constraints.NotNull;
import lombok.Getter;
import lombok.NoArgsConstructor;
import lombok.Setter;

import java.math.BigDecimal;

@Entity
@Table(name = "buildings")
@Getter
Expand All @@ -33,11 +29,7 @@ public class BuildingEntity extends AbstractAuditableEntity {
@Column(name = "name", nullable = false)
private String name;

@Min(value = 1)
@Column(name = "floors", nullable = false)
private int floors;
@Column(name = "number_of_devices", nullable = false)
private long numberOfDevices;

@DecimalMin(value = "0.0", inclusive = true)
@Column(name = "square_meters", nullable = false, precision = 15, scale = 3)
private BigDecimal squareMeters;
}
Original file line number Diff line number Diff line change
Expand Up @@ -14,12 +14,15 @@
import org.springframework.http.HttpStatus;
import org.springframework.http.ResponseEntity;
import org.springframework.security.core.annotation.AuthenticationPrincipal;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.PathVariable;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RequestBody;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;

import java.util.Objects;
import java.util.UUID;

@RestController
@RequestMapping("/api/buildings")
Expand All @@ -34,8 +37,14 @@ public class BuildingController {
private final BuildingService buildingService;
private final EnterpriseService enterpriseService;

@GetMapping("/{id}")
public ResponseEntity<BuildingDTO> getBuildingById(@PathVariable UUID id) {
var building = buildingService.findById(id);
return ResponseEntity.ok(buildingMapper.toDto(building.orElseThrow()));
}

@PostMapping("/search")
public ResponseEntity<SearchResultDTO<BuildingDTO>> getEnterpriseBuildings(@RequestBody SearchCriteriaDTO<Void> searchCriteria,
public ResponseEntity<SearchResultDTO<BuildingDTO>> searchEnterpriseBuildings(@RequestBody SearchCriteriaDTO<Void> searchCriteria,
@AuthenticationPrincipal UserContextData userContextData) {
var enterpriseIdFromContext = Objects.requireNonNull(userContextData.getEnterpriseId());
var pageable = CommonMapper.toPageable(searchCriteria.page(), searchCriteria.sort());
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -4,11 +4,14 @@
import org.springframework.data.domain.Page;
import org.springframework.data.domain.Pageable;

import java.util.Optional;
import java.util.UUID;

public interface BuildingService {

BuildingEntity createBuilding(BuildingEntity building);

Optional<BuildingEntity> findById(UUID id);

Page<BuildingEntity> getEnterpriseBuildings(UUID enterpriseId, Pageable page);
}
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@
import org.springframework.data.domain.Pageable;
import org.springframework.stereotype.Service;

import java.util.Optional;
import java.util.UUID;

@Service
Expand All @@ -23,6 +24,11 @@ public BuildingEntity createBuilding(BuildingEntity building) {
return buildingRepository.save(building);
}

@Override
public Optional<BuildingEntity> findById(UUID id) {
return buildingRepository.findById(id);
}

@Override
public Page<BuildingEntity> getEnterpriseBuildings(UUID enterpriseId, Pageable page) {
return buildingRepository.findByEnterpriseId(enterpriseId, page);
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
ALTER TABLE buildings
DROP COLUMN floors;
ALTER TABLE buildings
DROP COLUMN square_meters;

ALTER TABLE buildings
ADD COLUMN number_of_devices BIGINT NOT NULL default 0;
Original file line number Diff line number Diff line change
Expand Up @@ -10,8 +10,6 @@
import org.springframework.boot.test.context.SpringBootTest;
import org.springframework.boot.test.web.server.LocalServerPort;

import java.math.BigDecimal;

@SpringBootTest(webEnvironment = SpringBootTest.WebEnvironment.RANDOM_PORT)
class BuildingControllerTest extends TestcontainersConfigs {

Expand Down Expand Up @@ -51,8 +49,7 @@ void getEnterpriseBuildings_withInvalidToken_returns401() {
void createBuilding_withValidToken_returns201() {
var building = BuildingDTO.builder()
.name("Building 1")
.floors(1)
.squareMeters(BigDecimal.valueOf(123.45))
.numberOfDevices(10)
.build();
RestAssured.given()
.auth().oauth2(getToken("enterprise.owner@greenbuildings.com", "enterprise.owner"))
Expand Down
14 changes: 10 additions & 4 deletions sep490-frontend/src/app/app.module.ts
Original file line number Diff line number Diff line change
Expand Up @@ -17,18 +17,19 @@ import {environment} from '../environments/environment';
import {AppRoutingConstants} from './app-routing.constant';
import {AppRoutingModule} from './app-routing.module';
import {AppComponent} from './app.component';
import {DashboardComponent} from './components/dashboard/dashboard.component';
import {FooterComponent} from './components/footer/footer.component';
import {ForbiddenComponent} from './components/forbidden/forbidden.component';
import {HeaderComponent} from './components/header/header.component';
import {HomeComponent} from './components/home/home.component';
import {NotFoundComponent} from './components/not-found/not-found.component';
import {PricingComponent} from './components/pricing/pricing.component';
import {SidebarComponent} from './components/sidebar/sidebar.component';
import {UnauthorizedComponent} from './components/unauthorized/unauthorized.component';
import {CoreModule} from './modules/core/core.module';
import {HttpErrorHandlerInterceptor} from './modules/core/services/http-error-handler.interceptor';
import {LanguageInterceptor} from './modules/shared/interceptor/language.interceptor';
import {SharedModule} from './modules/shared/shared.module';
import {SidebarComponent} from './components/sidebar/sidebar.component';
import {PricingComponent} from './components/pricing/pricing.component';
import {DashboardComponent} from './components/dashboard/dashboard.component';

enum OidcScopes {
OPENID = 'openid',
Expand Down Expand Up @@ -110,7 +111,12 @@ export function HttpLoaderFactory(http: HttpClient): TranslateHttpLoader {
multi: true
},
{provide: HTTP_INTERCEPTORS, useClass: LanguageInterceptor, multi: true},
{provide: HTTP_INTERCEPTORS, useClass: AuthInterceptor, multi: true}
{provide: HTTP_INTERCEPTORS, useClass: AuthInterceptor, multi: true},
{
provide: HTTP_INTERCEPTORS,
useClass: HttpErrorHandlerInterceptor,
multi: true
}
],
bootstrap: [AppComponent]
})
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -30,7 +30,7 @@ <h1 class="font-bold text-lg text-primary">
<p-button
severity="primary"
[label]="'enterprise.Users.search' | translate"
icon="pi pi-user-plus"
icon="pi pi-search"
outlined
(onClick)="search()"
/>
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,77 @@
import {
HttpEvent,
HttpHandler,
HttpInterceptor,
HttpRequest
} from '@angular/common/http';
import {Injectable} from '@angular/core';
import {TranslateService} from '@ngx-translate/core';
import {MessageService} from 'primeng/api';
import {Observable, catchError, throwError} from 'rxjs';
import {BusinessErrorParam} from '../../shared/models/models';

@Injectable()
export class HttpErrorHandlerInterceptor implements HttpInterceptor {
constructor(
private readonly messageService: MessageService,
private readonly translateService: TranslateService
) {}

intercept(
req: HttpRequest<any>,
next: HttpHandler
): Observable<HttpEvent<any>> {
return next.handle(req).pipe(
catchError((error: any) => {
if (error.error && error.error.errorType === 'TECHNICAL') {
this.handleTechnicalErrorException(error);
}
if (error.error && error.error.errorType === 'BUSINESS') {
this.handleBusinessErrorException(error);
}
return throwError(() => error);
})
);
}

private handleTechnicalErrorException(error: any): void {
const message = this.translateService.instant(
'common.error.technicalServerError',
{correlationId: error.error.correlationId}
);
this.messageService.add({
severity: 'error',
summary: 'Error',
detail: message,
sticky: true
});
error.error.errorNotified = true;
}

private handleBusinessErrorException(error: any): void {
if (!error.error.field) {
this.messageService.add({
severity: 'error',
summary: 'Error',
detail: this.translateService.instant(
`validation.${error.error.i18nKey}`,
this.convertErrorParams(error.error.args)
),
sticky: true
});
}
error.error.errorNotified = true;
}

private convertErrorParams(params: BusinessErrorParam[]): {
[key: string]: string;
} {
return params?.reduce(
(result, arg: BusinessErrorParam) => {
result[arg.key] = arg.value;
return result;
},
{} as {[key: string]: string}
);
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
:host {
@apply flex flex-col gap-6;
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,73 @@
<span>
<p-button
(onClick)="back()"
class="*:min-w-28"
label="{{ 'common.back' | translate }}"
severity="primary"
outlined="true"
icon="pi pi-arrow-left"
/>
</span>

<div class="font-medium text-2xl">
{{
(isEdit
? "enterprise.buildings.details.edit"
: "enterprise.buildings.details.create"
) | translate
}}
</div>

<form
[formGroup]="formGroup"
class="grid grid-cols-2 border border-black rounded-lg p-4 gap-4"
>
<div class="flex flex-col gap-2" errorMessages>
<label class="font-medium text-lg" for="name">{{
"enterprise.buildings.details.labels.name" | translate
}}</label>
<input pInputText formControlName="name" id="name" type="text" />
<form-field-error></form-field-error>
</div>
<span></span>

<div class="flex flex-col gap-2" errorMessages>
<label class="font-medium text-lg" for="numberOfDevices">{{
"enterprise.buildings.details.labels.numberOfDevices" | translate
}}</label>
<input
pInputText
formControlName="numberOfDevices"
id="numberOfDevices"
type="number"
/>
<form-field-error></form-field-error>
</div>
<div class="flex items-center gap-2">
<i class="pi pi-info-circle text-lg"></i>
{{
"enterprise.buildings.details.labels.maxNumberOfDevices" | translate
}}
</div>
</form>

<div class="flex gap-4 justify-end">
<p-button
(onClick)="reset()"
class="*:min-w-24"
label="{{ 'common.revert' | translate }}"
[outlined]="true"
/>
<p-button
(onClick)="submit()"
class="*:min-w-16"
label="{{ (isEdit ? 'common.save' : 'common.create') | translate }}"
/>
</div>

<ng-template>
<p-floatlabel variant="on">
<input pInputText formControlName="name" id="on_label" />
<label for="on_label">On Label</label>
</p-floatlabel>
</ng-template>
Loading

0 comments on commit 75415bc

Please sign in to comment.