Refactorization and add metrics system.

This commit is contained in:
2019-08-01 21:47:15 +02:00
parent b3942828be
commit b7bfd817e9
19 changed files with 257 additions and 35 deletions

BIN
keystore.p12 Executable file → Normal file

Binary file not shown.

View File

@@ -5,7 +5,7 @@
<groupId>org.codiki</groupId> <groupId>org.codiki</groupId>
<artifactId>codiki</artifactId> <artifactId>codiki</artifactId>
<version>1.0.1</version> <version>1.1.0</version>
<packaging>jar</packaging> <packaging>jar</packaging>
<name>codiki</name> <name>codiki</name>

View File

@@ -46,8 +46,16 @@ public class AccountController {
@JsonView(View.UserDTO.class) @JsonView(View.UserDTO.class)
@PostMapping("/login") @PostMapping("/login")
public User login(@RequestBody final User pUser) throws BadCredentialsException { public User login(@RequestBody final User pUser, HttpServletResponse pResponse) throws BadCredentialsException {
return accountService.authenticate(pUser); User result = null;
try {
result = accountService.authenticate(pUser);
} catch(BadCredentialsException ex) {
pResponse.setStatus(HttpServletResponse.SC_UNAUTHORIZED);
}
return result;
} }
@GetMapping("/logout") @GetMapping("/logout")

View File

@@ -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();
}
}

View File

@@ -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;
}
}

View File

@@ -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();
}
}

View File

@@ -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;
}
}

View File

@@ -18,6 +18,7 @@ import org.codiki.core.entities.persistence.User;
import org.codiki.core.repositories.PostRepository; import org.codiki.core.repositories.PostRepository;
import org.codiki.core.services.ParserService; import org.codiki.core.services.ParserService;
import org.codiki.core.services.UserService; import org.codiki.core.services.UserService;
import org.codiki.core.utils.StringUtils;
import org.springframework.beans.factory.annotation.Autowired; import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.data.domain.PageRequest; import org.springframework.data.domain.PageRequest;
import org.springframework.web.bind.annotation.DeleteMapping; import org.springframework.web.bind.annotation.DeleteMapping;
@@ -36,23 +37,37 @@ import com.fasterxml.jackson.annotation.JsonView;
public class PostController { public class PostController {
private static final int LIMIT_POSTS_HOME = 20; private static final int LIMIT_POSTS_HOME = 20;
/** Service to parse post content. */
@Autowired
private ParserService parserService; private ParserService parserService;
/** Posts repository. */
@Autowired
private PostRepository postRepository; private PostRepository postRepository;
/** Posts business service. */
@Autowired
private PostService postService; private PostService postService;
/** Users business service. */
@Autowired
private UserService userService; 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 @GetMapping
public List<PostDTO> getAll() { public List<PostDTO> getAll() {
return StreamSupport.stream(postRepository.findAll().spliterator(), false) return StreamSupport.stream(postRepository.findAll().spliterator(), false)
.map(PostDTO::new).collect(Collectors.toList()); .map(PostDTO::new)
.collect(Collectors.toList());
} }
@JsonView(View.PostDTO.class) @JsonView(View.PostDTO.class)
@@ -70,16 +85,11 @@ public class PostController {
@GetMapping("/{postKey}/source") @GetMapping("/{postKey}/source")
public Post getByKeyAndSource(@PathVariable("postKey")final String pPostKey, public Post getByKeyAndSource(@PathVariable("postKey")final String pPostKey,
final HttpServletResponse response) { final HttpServletResponse response) {
Post result = null; return postRepository.getByKey(pPostKey)
.orElseGet(() -> {
final Optional<Post> post = postRepository.getByKey(pPostKey);
if(post.isPresent()) {
result = post.get();
} else {
response.setStatus(HttpServletResponse.SC_NOT_FOUND); response.setStatus(HttpServletResponse.SC_NOT_FOUND);
} return null;
});
return result;
} }
@JsonView(View.PostDTO.class) @JsonView(View.PostDTO.class)
@@ -121,9 +131,10 @@ public class PostController {
@JsonView(View.PostDTO.class) @JsonView(View.PostDTO.class)
@PostMapping("/preview") @PostMapping("/preview")
public Post preview(@RequestBody final Post pPost) { 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" ? "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.getImage());
pPost.setText(parserService.parse(pPost.getText())); pPost.setText(parserService.parse(pPost.getText()));
return pPost; return pPost;
@@ -133,15 +144,7 @@ public class PostController {
@PostMapping("/") @PostMapping("/")
public Post insert(@RequestBody final PostDTO pPost, final HttpServletRequest pRequest, public Post insert(@RequestBody final PostDTO pPost, final HttpServletRequest pRequest,
final HttpServletResponse pResponse, final Principal pPrincipal) { final HttpServletResponse pResponse, final Principal pPrincipal) {
Post result = null; return postService.insert(pPost, pRequest, pResponse, pPrincipal).orElse(null);
Optional<Post> postCreated = postService.insert(pPost, pRequest, pResponse, pPrincipal);
if(postCreated.isPresent()) {
result = postCreated.get();
}
return result;
} }
@PutMapping("/") @PutMapping("/")

View File

@@ -1,6 +1,8 @@
app: app:
name: Codiki name: Codiki
description: A wiki application. description: A wiki application.
version: 1.2.0
platform: develop
codiki: codiki:
files: files:

View File

@@ -36,6 +36,7 @@ import { ForbiddenComponent } from './forbidden/forbidden.component';
import { SearchComponent } from './search/search.component'; import { SearchComponent } from './search/search.component';
import { SigninComponent } from './signin/signin.component'; import { SigninComponent } from './signin/signin.component';
import { VersionRevisionComponent } from './version-revisions/version-revisions.component'; import { VersionRevisionComponent } from './version-revisions/version-revisions.component';
import { HealthCheckComponent } from './health-check/health-check.component';
// Reusable components // Reusable components
import { PostCardComponent } from './core/post-card/post-card.component'; 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 { SearchService } from './search/search.service';
import { SigninService } from './signin/signin.service'; import { SigninService } from './signin/signin.service';
import { VersionRevisionService } from './version-revisions/version-revisions.service'; import { VersionRevisionService } from './version-revisions/version-revisions.service';
import { HealthCheckService } from './health-check/health-check.service';
@NgModule({ @NgModule({
declarations: [ declarations: [
@@ -81,7 +83,8 @@ import { VersionRevisionService } from './version-revisions/version-revisions.se
SearchComponent, SearchComponent,
SearchBarComponent, SearchBarComponent,
ProgressBarComponent, ProgressBarComponent,
ForbiddenComponent ForbiddenComponent,
HealthCheckComponent
], ],
imports: [ imports: [
BrowserModule, BrowserModule,
@@ -110,6 +113,7 @@ import { VersionRevisionService } from './version-revisions/version-revisions.se
CreateUpdatePostService, CreateUpdatePostService,
VersionRevisionService, VersionRevisionService,
SearchService, SearchService,
HealthCheckService,
{ provide: HTTP_INTERCEPTORS, useClass: UnauthorizedInterceptor, multi: true } { provide: HTTP_INTERCEPTORS, useClass: UnauthorizedInterceptor, multi: true }
], ],
bootstrap: [AppComponent] bootstrap: [AppComponent]

View File

@@ -14,6 +14,7 @@ import { PostComponent } from './posts/post.component';
import { ByCategoryComponent } from './posts/byCategory/by-category.component'; import { ByCategoryComponent } from './posts/byCategory/by-category.component';
import { CreateUpdatePostComponent } from './posts/create-update/create-update-post.component'; import { CreateUpdatePostComponent } from './posts/create-update/create-update-post.component';
import { VersionRevisionComponent } from './version-revisions/version-revisions.component'; import { VersionRevisionComponent } from './version-revisions/version-revisions.component';
import { HealthCheckComponent } from './health-check/health-check.component';
import { SearchComponent } from './search/search.component'; import { SearchComponent } from './search/search.component';
export const appRoutes: Routes = [ export const appRoutes: Routes = [
@@ -31,6 +32,7 @@ export const appRoutes: Routes = [
{ path: 'changePassword', component: ChangePasswordComponent, canActivate: [AuthGuard] }, { path: 'changePassword', component: ChangePasswordComponent, canActivate: [AuthGuard] },
{ path: 'profilEdit', component: ProfilEditionComponent, canActivate: [AuthGuard] }, { path: 'profilEdit', component: ProfilEditionComponent, canActivate: [AuthGuard] },
{ path: 'versionrevisions', component: VersionRevisionComponent }, { path: 'versionrevisions', component: VersionRevisionComponent },
{ path: 'healthCheck', component: HealthCheckComponent },
{ path: '', redirectTo: '/home', pathMatch: 'full' }, { path: '', redirectTo: '/home', pathMatch: 'full' },
{ path: '**', redirectTo: '/home' } { path: '**', redirectTo: '/home' }
]; ];

View File

@@ -73,3 +73,11 @@ export class VersionRevision {
public bugfix: boolean public bugfix: boolean
) { } ) { }
} }
export class Metrics {
constructor(
public version: String,
public uptime: Date,
public platform: String
) { }
}

View File

@@ -7,6 +7,13 @@
<i id="appVersion" routerLink="/versionrevisions" mdbTooltip="Notes de versions" placement="top" mdbRippleRadius> <i id="appVersion" routerLink="/versionrevisions" mdbTooltip="Notes de versions" placement="top" mdbRippleRadius>
{{appVersion}} {{appVersion}}
</i> </i>
<i id="healthCheck"
class="fa fa-heartbeat"
routerLink="/healthCheck"
mdbTooltip="Ligne de vie"
placement="top"
mdbRippleRadius>
</i>
</span> </span>
<span class="float-right"> <span class="float-right">
<a target="_blank" href="./assets/doc/codiki_user_manual.pdf" mdbTooltip="Manuel d'utilisation" placement="top" mdbRippleRadius> <a target="_blank" href="./assets/doc/codiki_user_manual.pdf" mdbTooltip="Manuel d'utilisation" placement="top" mdbRippleRadius>

View File

@@ -23,3 +23,8 @@ span.anticopy {
display: inline; display: inline;
cursor: pointer; cursor: pointer;
} }
#healthCheck {
margin-left: 10px;
cursor: pointer;
}

View File

@@ -0,0 +1,23 @@
<div class="col-md-8 offset-md-2 col-lg-6 offset-lg-3">
<mdb-card>
<!--Card content-->
<mdb-card-body>
<!--Title-->
<mdb-card-title>
<h4>Status du serveur</h4>
</mdb-card-title>
<ul class="list-group list-group-flush">
<li class="list-group-item">
Plateforme <mdb-badge pill="true" primary="true" class="float-right">{{metrics.platform}}</mdb-badge>
</li>
<li class="list-group-item">
Version <mdb-badge pill="true" primary="true" class="float-right">{{metrics.version}}</mdb-badge>
</li>
<li class="list-group-item">
Démarré depuis {{metrics.uptime}}
</li>
</ul>
</mdb-card-body>
</mdb-card>
</div>

View File

@@ -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;
});
}
}

View File

@@ -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<Metrics> {
return this.http.get<Metrics>('/api/metrics/healthCheck');
}
}

View File

@@ -1,5 +1,5 @@
import { Injectable } from '@angular/core'; import { Injectable } from '@angular/core';
import { HttpClient, HttpHeaders } from '@angular/common/http'; import { HttpClient } from '@angular/common/http';
import { Observable } from 'rxjs'; import { Observable } from 'rxjs';
import { Post } from '../core/entities'; import { Post } from '../core/entities';