diff --git a/keystore.p12 b/keystore.p12 old mode 100755 new mode 100644 index 3ea1242..1296fc2 Binary files a/keystore.p12 and b/keystore.p12 differ diff --git a/pom.xml b/pom.xml index e683c45..41f9d50 100755 --- a/pom.xml +++ b/pom.xml @@ -5,7 +5,7 @@ org.codiki codiki - 1.0.1 + 1.1.0 jar codiki diff --git a/src/main/java/org/codiki/account/AccountController.java b/src/main/java/org/codiki/account/AccountController.java index 15ed7e0..c44870c 100755 --- a/src/main/java/org/codiki/account/AccountController.java +++ b/src/main/java/org/codiki/account/AccountController.java @@ -46,8 +46,16 @@ public class AccountController { @JsonView(View.UserDTO.class) @PostMapping("/login") - public User login(@RequestBody final User pUser) throws BadCredentialsException { - return accountService.authenticate(pUser); + public User login(@RequestBody final User pUser, HttpServletResponse pResponse) throws BadCredentialsException { + User result = null; + + try { + result = accountService.authenticate(pUser); + } catch(BadCredentialsException ex) { + pResponse.setStatus(HttpServletResponse.SC_UNAUTHORIZED); + } + + return result; } @GetMapping("/logout") diff --git a/src/main/java/org/codiki/core/config/CoreConfig.java b/src/main/java/org/codiki/core/config/CoreConfig.java new file mode 100644 index 0000000..a2f181f --- /dev/null +++ b/src/main/java/org/codiki/core/config/CoreConfig.java @@ -0,0 +1,14 @@ +package org.codiki.core.config; + +import org.springframework.context.annotation.Bean; +import org.springframework.context.annotation.Configuration; + +import java.time.Clock; + +@Configuration +public class CoreConfig { + @Bean + public Clock clock() { + return Clock.systemDefaultZone(); + } +} diff --git a/src/main/java/org/codiki/core/entities/dto/Metrics.java b/src/main/java/org/codiki/core/entities/dto/Metrics.java new file mode 100644 index 0000000..02393ab --- /dev/null +++ b/src/main/java/org/codiki/core/entities/dto/Metrics.java @@ -0,0 +1,37 @@ +package org.codiki.core.entities.dto; + +import java.time.LocalDate; +import java.time.LocalDateTime; + +public class Metrics { + /** Rest API version number. */ + private String version; + /** Application start date. */ + private LocalDateTime uptime; + /** Platform name. */ + private String platform; + + public String getVersion() { + return version; + } + + public void setVersion(String version) { + this.version = version; + } + + public LocalDateTime getUptime() { + return uptime; + } + + public void setUptime(LocalDateTime uptime) { + this.uptime = uptime; + } + + public String getPlatform() { + return platform; + } + + public void setPlatform(String platform) { + this.platform = platform; + } +} diff --git a/src/main/java/org/codiki/metrics/MetricsController.java b/src/main/java/org/codiki/metrics/MetricsController.java new file mode 100644 index 0000000..910c5e4 --- /dev/null +++ b/src/main/java/org/codiki/metrics/MetricsController.java @@ -0,0 +1,26 @@ +package org.codiki.metrics; + +import org.codiki.core.entities.dto.Metrics; +import org.springframework.web.bind.annotation.GetMapping; +import org.springframework.web.bind.annotation.RequestMapping; +import org.springframework.web.bind.annotation.RestController; + +@RestController +@RequestMapping("/api/metrics") +public class MetricsController { + + private MetricsService metricsService; + + /** + * Constructor. + * @param metricsService Metrics service. + */ + public MetricsController(MetricsService metricsService) { + this.metricsService = metricsService; + } + + @GetMapping("/healthCheck") + public Metrics healthCheck() { + return metricsService.getMetrics(); + } +} diff --git a/src/main/java/org/codiki/metrics/MetricsService.java b/src/main/java/org/codiki/metrics/MetricsService.java new file mode 100644 index 0000000..94a5e93 --- /dev/null +++ b/src/main/java/org/codiki/metrics/MetricsService.java @@ -0,0 +1,44 @@ +package org.codiki.metrics; + +import org.codiki.core.entities.dto.Metrics; +import org.springframework.beans.factory.annotation.Value; +import org.springframework.stereotype.Service; + +import java.time.Clock; +import java.time.LocalDateTime; + +@Service +public class MetricsService { + /** Application start date. */ + private LocalDateTime uptime; + /** Application version number. */ + private String appVersion; + /** Platform name. */ + private String appPlatform; + + /** + * Constructor. + * @param clock System clock. + */ + public MetricsService(Clock clock, + @Value("${app.version}") String appVersion, + @Value("${app.platform}") String appPlatform) { + uptime = LocalDateTime.now(clock); + this.appVersion = appVersion; + this.appPlatform = appPlatform; + } + + /** + * Returns application metrics like uptime, version number or platform name and others. + * @return Application metrics. + */ + public Metrics getMetrics() { + Metrics metrics = new Metrics(); + + metrics.setUptime(uptime); + metrics.setVersion(appVersion); + metrics.setPlatform(appPlatform); + + return metrics; + } +} diff --git a/src/main/java/org/codiki/posts/PostController.java b/src/main/java/org/codiki/posts/PostController.java index 9ca880c..6110cef 100755 --- a/src/main/java/org/codiki/posts/PostController.java +++ b/src/main/java/org/codiki/posts/PostController.java @@ -18,6 +18,7 @@ import org.codiki.core.entities.persistence.User; import org.codiki.core.repositories.PostRepository; import org.codiki.core.services.ParserService; import org.codiki.core.services.UserService; +import org.codiki.core.utils.StringUtils; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.data.domain.PageRequest; import org.springframework.web.bind.annotation.DeleteMapping; @@ -36,23 +37,37 @@ import com.fasterxml.jackson.annotation.JsonView; public class PostController { private static final int LIMIT_POSTS_HOME = 20; - - @Autowired + /** Service to parse post content. */ private ParserService parserService; - - @Autowired + /** Posts repository. */ private PostRepository postRepository; - - @Autowired + /** Posts business service. */ private PostService postService; - - @Autowired + /** Users business service. */ private UserService userService; - + + /** + * Constructor. + * @param parserService Service to parse post content. + * @param postRepository Posts repository. + * @param postService Posts business service. + * @param userService Users business service. + */ + public PostController(ParserService parserService, + PostRepository postRepository, + PostService postService, + UserService userService) { + this.parserService = parserService; + this.postRepository = postRepository; + this.postService = postService; + this.userService = userService; + } + @GetMapping public List getAll() { return StreamSupport.stream(postRepository.findAll().spliterator(), false) - .map(PostDTO::new).collect(Collectors.toList()); + .map(PostDTO::new) + .collect(Collectors.toList()); } @JsonView(View.PostDTO.class) @@ -70,16 +85,11 @@ public class PostController { @GetMapping("/{postKey}/source") public Post getByKeyAndSource(@PathVariable("postKey")final String pPostKey, final HttpServletResponse response) { - Post result = null; - - final Optional post = postRepository.getByKey(pPostKey); - if(post.isPresent()) { - result = post.get(); - } else { - response.setStatus(HttpServletResponse.SC_NOT_FOUND); - } - - return result; + return postRepository.getByKey(pPostKey) + .orElseGet(() -> { + response.setStatus(HttpServletResponse.SC_NOT_FOUND); + return null; + }); } @JsonView(View.PostDTO.class) @@ -121,9 +131,10 @@ public class PostController { @JsonView(View.PostDTO.class) @PostMapping("/preview") public Post preview(@RequestBody final Post pPost) { - pPost.setImage(pPost.getImage() == null || "".equals(pPost.getImage()) + pPost.setImage(StringUtils.isNull(pPost.getImage()) ? "https://news-cdn.softpedia.com/images/news2/this-is-the-default-wallpaper-of-the-gnome-3-20-desktop-environment-500743-2.jpg" : pPost.getImage()); + pPost.setText(parserService.parse(pPost.getText())); return pPost; @@ -133,15 +144,7 @@ public class PostController { @PostMapping("/") public Post insert(@RequestBody final PostDTO pPost, final HttpServletRequest pRequest, final HttpServletResponse pResponse, final Principal pPrincipal) { - Post result = null; - - Optional postCreated = postService.insert(pPost, pRequest, pResponse, pPrincipal); - - if(postCreated.isPresent()) { - result = postCreated.get(); - } - - return result; + return postService.insert(pPost, pRequest, pResponse, pPrincipal).orElse(null); } @PutMapping("/") diff --git a/src/main/resources/application.yml b/src/main/resources/application.yml index 654b71d..25418f2 100644 --- a/src/main/resources/application.yml +++ b/src/main/resources/application.yml @@ -1,6 +1,8 @@ app: name: Codiki description: A wiki application. + version: 1.2.0 + platform: develop codiki: files: diff --git a/src/main/ts/src/app/app.module.ts b/src/main/ts/src/app/app.module.ts index 81719e3..8fe9db7 100755 --- a/src/main/ts/src/app/app.module.ts +++ b/src/main/ts/src/app/app.module.ts @@ -36,6 +36,7 @@ import { ForbiddenComponent } from './forbidden/forbidden.component'; import { SearchComponent } from './search/search.component'; import { SigninComponent } from './signin/signin.component'; import { VersionRevisionComponent } from './version-revisions/version-revisions.component'; +import { HealthCheckComponent } from './health-check/health-check.component'; // Reusable components import { PostCardComponent } from './core/post-card/post-card.component'; @@ -57,6 +58,7 @@ import { CreateUpdatePostService } from './posts/create-update/create-update-pos import { SearchService } from './search/search.service'; import { SigninService } from './signin/signin.service'; import { VersionRevisionService } from './version-revisions/version-revisions.service'; +import { HealthCheckService } from './health-check/health-check.service'; @NgModule({ declarations: [ @@ -81,7 +83,8 @@ import { VersionRevisionService } from './version-revisions/version-revisions.se SearchComponent, SearchBarComponent, ProgressBarComponent, - ForbiddenComponent + ForbiddenComponent, + HealthCheckComponent ], imports: [ BrowserModule, @@ -110,6 +113,7 @@ import { VersionRevisionService } from './version-revisions/version-revisions.se CreateUpdatePostService, VersionRevisionService, SearchService, + HealthCheckService, { provide: HTTP_INTERCEPTORS, useClass: UnauthorizedInterceptor, multi: true } ], bootstrap: [AppComponent] diff --git a/src/main/ts/src/app/app.routing.ts b/src/main/ts/src/app/app.routing.ts index 4504b6c..cec34a5 100755 --- a/src/main/ts/src/app/app.routing.ts +++ b/src/main/ts/src/app/app.routing.ts @@ -14,6 +14,7 @@ import { PostComponent } from './posts/post.component'; import { ByCategoryComponent } from './posts/byCategory/by-category.component'; import { CreateUpdatePostComponent } from './posts/create-update/create-update-post.component'; import { VersionRevisionComponent } from './version-revisions/version-revisions.component'; +import { HealthCheckComponent } from './health-check/health-check.component'; import { SearchComponent } from './search/search.component'; export const appRoutes: Routes = [ @@ -31,6 +32,7 @@ export const appRoutes: Routes = [ { path: 'changePassword', component: ChangePasswordComponent, canActivate: [AuthGuard] }, { path: 'profilEdit', component: ProfilEditionComponent, canActivate: [AuthGuard] }, { path: 'versionrevisions', component: VersionRevisionComponent }, + { path: 'healthCheck', component: HealthCheckComponent }, { path: '', redirectTo: '/home', pathMatch: 'full' }, { path: '**', redirectTo: '/home' } ]; diff --git a/src/main/ts/src/app/core/entities.ts b/src/main/ts/src/app/core/entities.ts index 6c7a445..e4b96b3 100755 --- a/src/main/ts/src/app/core/entities.ts +++ b/src/main/ts/src/app/core/entities.ts @@ -73,3 +73,11 @@ export class VersionRevision { public bugfix: boolean ) { } } + +export class Metrics { + constructor( + public version: String, + public uptime: Date, + public platform: String + ) { } +} diff --git a/src/main/ts/src/app/footer/footer.component.html b/src/main/ts/src/app/footer/footer.component.html index 0ff665c..6377b3e 100644 --- a/src/main/ts/src/app/footer/footer.component.html +++ b/src/main/ts/src/app/footer/footer.component.html @@ -7,6 +7,13 @@ {{appVersion}} + + diff --git a/src/main/ts/src/app/footer/footer.component.scss b/src/main/ts/src/app/footer/footer.component.scss index 3004449..c29f09d 100644 --- a/src/main/ts/src/app/footer/footer.component.scss +++ b/src/main/ts/src/app/footer/footer.component.scss @@ -23,3 +23,8 @@ span.anticopy { display: inline; cursor: pointer; } + +#healthCheck { + margin-left: 10px; + cursor: pointer; +} diff --git a/src/main/ts/src/app/health-check/health-check.component.html b/src/main/ts/src/app/health-check/health-check.component.html new file mode 100644 index 0000000..5c986d6 --- /dev/null +++ b/src/main/ts/src/app/health-check/health-check.component.html @@ -0,0 +1,23 @@ +
+ + + + + +

Status du serveur

+
+ +
    +
  • + Plateforme {{metrics.platform}} +
  • +
  • + Version {{metrics.version}} +
  • +
  • + Démarré depuis {{metrics.uptime}} +
  • +
+
+
+
diff --git a/src/main/ts/src/app/health-check/health-check.component.scss b/src/main/ts/src/app/health-check/health-check.component.scss new file mode 100644 index 0000000..e69de29 diff --git a/src/main/ts/src/app/health-check/health-check.component.ts b/src/main/ts/src/app/health-check/health-check.component.ts new file mode 100644 index 0000000..c31ce0d --- /dev/null +++ b/src/main/ts/src/app/health-check/health-check.component.ts @@ -0,0 +1,23 @@ +import { HealthCheckService } from './health-check.service'; +import { Component, OnInit } from '@angular/core'; +import { Metrics } from '../core/entities'; + +@Component({ + selector: 'app-health-check', + templateUrl: './health-check.component.html', + styleUrls: ['./health-check.component.scss'] +}) +export class HealthCheckComponent implements OnInit { + metrics: Metrics = new Metrics('1.0.0', null, '?'); + + constructor( + private healthCheckService: HealthCheckService + ) {} + + ngOnInit() { + this.healthCheckService.healthCheck().subscribe(metrics => { + this.metrics = metrics; + }); + } + +} diff --git a/src/main/ts/src/app/health-check/health-check.service.ts b/src/main/ts/src/app/health-check/health-check.service.ts new file mode 100644 index 0000000..f5d1e68 --- /dev/null +++ b/src/main/ts/src/app/health-check/health-check.service.ts @@ -0,0 +1,16 @@ +import { Injectable } from '@angular/core'; +import { HttpClient } from '@angular/common/http'; +import { Observable } from 'rxjs'; +import { Metrics } from '../core/entities'; + +@Injectable({ + providedIn: 'root' +}) +export class HealthCheckService { + + constructor(private http: HttpClient) {} + + healthCheck(): Observable { + return this.http.get('/api/metrics/healthCheck'); + } +} diff --git a/src/main/ts/src/app/home/home.service.ts b/src/main/ts/src/app/home/home.service.ts index ea16f7e..1050880 100755 --- a/src/main/ts/src/app/home/home.service.ts +++ b/src/main/ts/src/app/home/home.service.ts @@ -1,5 +1,5 @@ import { Injectable } from '@angular/core'; -import { HttpClient, HttpHeaders } from '@angular/common/http'; +import { HttpClient } from '@angular/common/http'; import { Observable } from 'rxjs'; import { Post } from '../core/entities';