Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
5 changes: 4 additions & 1 deletion ClientApp/src/app/app.module.ts
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@ import { HomeComponent } from './components/home/home.component';
import { CounterComponent } from './components/counter/counter.component';
import { SanctionedEntitiesComponent } from './components/sanctioned-entities/sanctioned-entities.component';
import { JumbotronCounterComponent } from './components/jumbotron-counter/jumbotron-counter.component';
import { CreateSanctionedEntityComponent } from './components/create-sanctioned-entity/create-sanctioned-entity.component';


@NgModule({
Expand All @@ -19,7 +20,8 @@ import { JumbotronCounterComponent } from './components/jumbotron-counter/jumbot
HomeComponent,
CounterComponent,
SanctionedEntitiesComponent,
JumbotronCounterComponent
JumbotronCounterComponent,
CreateSanctionedEntityComponent
],
imports: [
BrowserModule.withServerTransition({ appId: 'ng-cli-universal' }),
Expand All @@ -29,6 +31,7 @@ import { JumbotronCounterComponent } from './components/jumbotron-counter/jumbot
{ path: '', component: HomeComponent, pathMatch: 'full' },
{ path: 'counter', component: CounterComponent },
{ path: 'sanctioned-entities', component: SanctionedEntitiesComponent },
{ path: 'create-sanction-entity', component: CreateSanctionedEntityComponent }
])
],
providers: [],
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,6 @@ <h1>Counter</h1>

<p>This is a simple example of an Angular component.</p>

<p aria-live="polite">Current count: <strong>{{ currentCount }}</strong></p>
<p aria-live="polite">Current count: <strong>{{ counter$ | async}}</strong></p>

<button class="btn btn-primary" (click)="incrementCounter()">Increment</button>
14 changes: 11 additions & 3 deletions ClientApp/src/app/components/counter/counter.component.ts
Original file line number Diff line number Diff line change
@@ -1,13 +1,21 @@
import { Component } from '@angular/core';
import { Component, Input, OnDestroy } from '@angular/core';
import { BehaviorSubject, Observable } from 'rxjs';
import { CounterService } from 'src/app/services/counter.service';

@Component({
selector: 'app-counter-component',
templateUrl: './counter.component.html'
})
export class CounterComponent {
public currentCount = 0;
public counter$: Observable<number>;

@Input('minimalistMode') minimalistMode: boolean = false;

constructor(private counterService: CounterService) {
this.counter$ = this.counterService.getCounter();
}

public incrementCounter() {
this.currentCount++;
this.counterService.increaseCounter();
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,31 @@
<h1 id="formLabel">Create a sanction</h1>

<form class="row g-2 p-3" (ngSubmit)="save(form)" #form="ngForm">
<div class="col-md-6">
<label for="entityName" class="form-label">Name</label>
<input [(ngModel)]="newEntity.name" class="form-control" name="entityName" required
#entityName="ngModel" [class.is-invalid]="entityName.invalid && entityName.dirty">

<div class="invalid-feedback">Please provide an entity name.</div>
</div>
<br>
<div class="col-md-6">
<label for="domicile" class="form-label">Domicile</label>
<input [(ngModel)]="newEntity.domicile" class="form-control" name="domicile" required
#domicile="ngModel" [class.is-invalid]="domicile.invalid && domicile.dirty">

<div class="invalid-feedback">Please provide a domicile.</div>
</div>
<br>
<div class="col-md-6 form-check form-switch">
<label for="entityStatus" class="form-label">Is Entity Accepted?</label>
<input [(ngModel)]="newEntity.accepted" type="checkbox" role="switch" name="entityStatus" class="form-control form-check-input"
#entityStatus="ngModel">
</div>
<br>
<div class="col-md-6">
<div *ngIf="serverError" class="alert alert-danger" role="alert">{{serverError}}</div>

<button [disabled]="form.invalid" type="submit" class="btn btn-primary m-1">Submit</button>
</div>
</form>
Original file line number Diff line number Diff line change
@@ -0,0 +1,41 @@
import { Component } from '@angular/core';
import { SanctionedEntity } from '../../models/sanctioned-entity';
import { SanctionedEntitiesService } from '../../services/sanctioned-entities.service';
import { NgForm } from '@angular/forms';
import { Router } from '@angular/router';
import { catchError, throwError } from 'rxjs';

@Component({
selector: 'app-sanctioned-entities',
templateUrl: './create-sanctioned-entity.component.html'
})
export class CreateSanctionedEntityComponent {
public newEntity: SanctionedEntity;
public serverError: string|null = null;

constructor(private entitiesService: SanctionedEntitiesService, private router: Router) {
this.newEntity = { id: '', accepted: false, domicile: '', name: ''};
}

public save(form: NgForm): void {

if(form.valid && form.enabled){
this.entitiesService.createNewEntity(this.newEntity)
.pipe(
catchError(response => {
this.serverError = response.error.detail;

return throwError(response);
})
)
.subscribe(newEntity => {

if(newEntity){
this.router.navigate(['/sanctioned-entities']);
}

});
}
}

}
Original file line number Diff line number Diff line change
@@ -1,4 +1,15 @@
<div class="jumbotron text-center">
<p>Current count:</p>
<p><strong>Please include the counter here</strong></p>
<p><strong>{{ counter$ | async}}</strong></p>

<!-- <p><strong>Please include the counter here</strong></p> -->

<!--
NOTE: I have interpreted "Please include the counter here" as just including the counted number here,
but could've also been interpreted as include the whole component here in some form,
with maybe an input to turn off parts of the UI like the title in a more minimalist mode like below commented out
Wouldn't leave this comment here, would normally be a discussion on user story or during refinement meetings etc.
-->

<!-- <app-counter-component [minimalistMode]="true"></app-counter-component> -->
</div>
Original file line number Diff line number Diff line change
@@ -1,8 +1,16 @@
import { Component } from '@angular/core';
import { BehaviorSubject, Observable } from 'rxjs';
import { CounterService } from 'src/app/services/counter.service';

@Component({
selector: 'app-jumbotron-counter',
templateUrl: './jumbotron-counter.component.html'
})
export class JumbotronCounterComponent {
public counter$: Observable<number>;

constructor(private counterService: CounterService){
this.counter$ = this.counterService.getCounter();
}

}
Original file line number Diff line number Diff line change
@@ -1,7 +1,9 @@
<h1 id="tableLabel">Sanctioned Entities</h1>
<button class="btn btn-primary" [routerLink]="['/create-sanction-entity']">Create Sanction</button>

<p *ngIf="!entities"><em>Loading...</em></p>


<table class='table table-striped' aria-labelledby="tableLabel" *ngIf="entities">
<thead>
<tr>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -14,4 +14,5 @@ export class SanctionedEntitiesComponent {
this.entities = entities;
});
}

}
23 changes: 23 additions & 0 deletions ClientApp/src/app/services/counter.service.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
import { Injectable } from '@angular/core';
import { BehaviorSubject, Observable } from 'rxjs';

@Injectable({
providedIn: 'root'
})
export class CounterService {

private counterSubject: BehaviorSubject<number> = new BehaviorSubject(0);
private counter$ = this.counterSubject.asObservable();

constructor() {

}

public getCounter(): Observable<number> {
return this.counter$;
}

public increaseCounter() : void{
this.counterSubject.next(this.counterSubject.value + 1);
}
}
6 changes: 6 additions & 0 deletions ClientApp/src/app/services/sanctioned-entities.service.ts
Original file line number Diff line number Diff line change
Expand Up @@ -19,4 +19,10 @@ export class SanctionedEntitiesService {
const url = this.apiUrl + this.path;
return this.http.get<SanctionedEntity[]>(url);
}

public createNewEntity(newEntity: SanctionedEntity): Observable<boolean> {
const url = this.apiUrl + this.path;

return this.http.post<boolean>(url, newEntity);
}
}
25 changes: 23 additions & 2 deletions Controllers/SanctionedEntitiesController.cs
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
using ajgre_technical_interview.Services;
using ajgre_technical_interview.Models;
using ajgre_technical_interview.Services;
using Microsoft.AspNetCore.Mvc;

namespace ajgre_technical_interview.Controllers
Expand All @@ -8,10 +9,12 @@ namespace ajgre_technical_interview.Controllers
public class SanctionedEntitiesController : ControllerBase
{
private readonly IDatabaseService _databaseService;
private readonly ILogger<SanctionedEntitiesController> _logger;

public SanctionedEntitiesController(IDatabaseService databaseService)
public SanctionedEntitiesController(IDatabaseService databaseService, ILogger<SanctionedEntitiesController> logger)
{
_databaseService = databaseService;
_logger = logger;
}


Expand All @@ -25,9 +28,27 @@ public async Task<IActionResult> GetSanctionedEntities()
}
catch (Exception ex)
{
//A little logging is always nice to have!
_logger.LogError(ex, $"Exception occured in route '{nameof(GetSanctionedEntities)}'");
return Problem(ex.Message);
}

}

[HttpPost]
public async Task<IActionResult> CreateSanctionEntity([FromBody] SanctionedEntity newSanctionEntity)
{
try
{
var newEntity = await _databaseService.CreateSanctionedEntityAsync(newSanctionEntity);
return Ok(newEntity);
}
catch (Exception ex)
{
//A little logging is always nice to have!
_logger.LogError(ex, $"Exception occured in route '{nameof(CreateSanctionEntity)}'");
return Problem(ex.Message);
}
}
}
}
10 changes: 10 additions & 0 deletions Services/DatabaseService.cs
Original file line number Diff line number Diff line change
Expand Up @@ -29,6 +29,16 @@ public async Task<SanctionedEntity> GetSanctionedEntityByIdAsync(Guid id)

public async Task<SanctionedEntity> CreateSanctionedEntityAsync(SanctionedEntity sanctionedEntity)
{
//Validate that there is not a sanctioned entity with the same name and domicile already.
//Would normally seperate this validation into a step in a service sitting between DB and the controller.
var newEntityIsValid = !SanctionedEntities.Any(x => x.Name == sanctionedEntity.Name
&& x.Domicile == sanctionedEntity.Domicile);

if (!newEntityIsValid)
{
throw new Exception("A sanctioned entity with the same name and domicile already exists.");
}

SanctionedEntities.Add(sanctionedEntity);
return await Task.FromResult(sanctionedEntity);
}
Expand Down