diff --git a/doc/codiki_user_manual.odt b/doc/codiki_user_manual.odt index 8f851b6..3267274 100644 Binary files a/doc/codiki_user_manual.odt and b/doc/codiki_user_manual.odt differ diff --git a/src/main/java/org/codiki/core/config/CustomErrorController.java b/src/main/java/org/codiki/core/config/CustomErrorController.java new file mode 100644 index 0000000..f5119c2 --- /dev/null +++ b/src/main/java/org/codiki/core/config/CustomErrorController.java @@ -0,0 +1,58 @@ +package org.codiki.core.config; + +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.boot.web.servlet.error.ErrorAttributes; +import org.springframework.boot.web.servlet.error.ErrorController; +import org.springframework.util.Assert; +import org.springframework.web.bind.annotation.RequestMapping; +import org.springframework.web.bind.annotation.RestController; +import org.springframework.web.context.request.RequestAttributes; +import org.springframework.web.context.request.ServletRequestAttributes; +import org.springframework.web.context.request.ServletWebRequest; + +import javax.servlet.http.HttpServletRequest; +import java.util.Map; + +/** + * Controller that catch errors from spring rest or spring security and others, and transform them to JSON response. + */ +@RestController +@RequestMapping("/error") +public class CustomErrorController implements ErrorController { + + private final ErrorAttributes errorAttributes; + + @Autowired + public CustomErrorController(ErrorAttributes errorAttributes) { + Assert.notNull(errorAttributes, "ErrorAttributes must not be null"); + this.errorAttributes = errorAttributes; + } + + @Override + public String getErrorPath() { + return "/error"; + } + + @RequestMapping + public Map error(HttpServletRequest request){ + Map body = getErrorAttributes(request, getTraceParameter(request)); + String trace = (String) body.get("trace"); + if(trace != null){ + String[] lines = trace.split("\n\t"); + body.put("trace", lines); + } + return body; + } + + private boolean getTraceParameter(HttpServletRequest request) { + String parameter = request.getParameter("trace"); + if (parameter == null) { + return false; + } + return !"false".equals(parameter.toLowerCase()); + } + + private Map getErrorAttributes(HttpServletRequest request, boolean includeStackTrace) { + return errorAttributes.getErrorAttributes(new ServletWebRequest(request), includeStackTrace); + } +} \ No newline at end of file diff --git a/src/main/java/org/codiki/core/config/RobotsTxtController.java b/src/main/java/org/codiki/core/config/RobotsTxtController.java new file mode 100644 index 0000000..2475f9b --- /dev/null +++ b/src/main/java/org/codiki/core/config/RobotsTxtController.java @@ -0,0 +1,24 @@ +package org.codiki.core.config; + +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; +import org.springframework.web.bind.annotation.RequestMapping; +import org.springframework.web.bind.annotation.RestController; + +import javax.servlet.http.HttpServletResponse; +import java.io.IOException; + +@RestController +public class RobotsTxtController { + + private static final Logger LOG = LoggerFactory.getLogger(RobotsTxtController.class); + + @RequestMapping(value = "/robots.txt") + public void robots(HttpServletResponse response) { + try { + response.getWriter().write("User-agent: *\nDisallow: /\n"); + } catch (IOException ex) { + LOG.info("Error during robots.txt serving.", ex); + } + } +} diff --git a/src/main/java/org/codiki/core/security/RestAuthenticationEntryPoint.java b/src/main/java/org/codiki/core/security/RestAuthenticationEntryPoint.java index de0d1e2..65248cb 100755 --- a/src/main/java/org/codiki/core/security/RestAuthenticationEntryPoint.java +++ b/src/main/java/org/codiki/core/security/RestAuthenticationEntryPoint.java @@ -23,6 +23,6 @@ public class RestAuthenticationEntryPoint implements AuthenticationEntryPoint { @Override public void commence(HttpServletRequest request, HttpServletResponse response, AuthenticationException authException) throws IOException { - response.sendError(HttpServletResponse.SC_UNAUTHORIZED, "Unauthorized"); + response.sendError(HttpServletResponse.SC_UNAUTHORIZED); } } diff --git a/src/main/java/org/codiki/core/security/SecurityConfiguration.java b/src/main/java/org/codiki/core/security/SecurityConfiguration.java index 7f27cd6..49dcc0b 100755 --- a/src/main/java/org/codiki/core/security/SecurityConfiguration.java +++ b/src/main/java/org/codiki/core/security/SecurityConfiguration.java @@ -45,27 +45,31 @@ public class SecurityConfiguration extends WebSecurityConfigurerAdapter { @Override protected void configure(HttpSecurity http) throws Exception { http.authorizeRequests() + // Permits all .antMatchers( "/api/account/login", "/api/account/logout", - "/api/account/signin" + "/api/account/signin", + "/robots.txt" + ).permitAll() + .antMatchers( + HttpMethod.GET, + "/api/categories", + "/api/images", + "/api/posts", + "/api/categories/**", + "/api/images/**", + "/api/posts/**" ).permitAll() .antMatchers( "/api/images/uploadAvatar", "/api/images/myImages", - "/api/posts/myPosts", "/api/account/changePassword", - "/api/account/" + "/api/account/", + "/api/posts/myPosts", + "/api/posts/preview", + "/api/posts/" ).authenticated() - .antMatchers( - HttpMethod.GET, - "/api/categories", - "/api/images", - "/api/posts", - "/api/categories/**", - "/api/images/**", - "/api/posts/**" - ).permitAll() .anyRequest().permitAll() .and() // Allow to avoid login form at authentication failure from Angular app diff --git a/src/main/resources/application.yml b/src/main/resources/application.yml index 5202365..0dd5724 100644 --- a/src/main/resources/application.yml +++ b/src/main/resources/application.yml @@ -21,6 +21,9 @@ logging: server: # use-forward-headers=true + error: + whitelabel: + enabled: false # Disable html error responses. port: 8080 # ssl: # key-store: /home/takiguchi/Developpement/Java/codiki/keystore.p12 diff --git a/src/main/ts/src/app/account-settings/profil-edition/profil-edition.component.html b/src/main/ts/src/app/account-settings/profil-edition/profil-edition.component.html index d9b9d11..94500f9 100644 --- a/src/main/ts/src/app/account-settings/profil-edition/profil-edition.component.html +++ b/src/main/ts/src/app/account-settings/profil-edition/profil-edition.component.html @@ -39,9 +39,9 @@ Annuler - diff --git a/src/main/ts/src/app/account-settings/profil-edition/profil-edition.component.ts b/src/main/ts/src/app/account-settings/profil-edition/profil-edition.component.ts index 15c521b..ae9ff8f 100644 --- a/src/main/ts/src/app/account-settings/profil-edition/profil-edition.component.ts +++ b/src/main/ts/src/app/account-settings/profil-edition/profil-edition.component.ts @@ -80,7 +80,7 @@ export class ProfilEditionComponent implements OnInit { onSubmit(): void { this.profilEditionService.updateUser(this.model).subscribe(() => { - NotificationsComponent.success('Modification enregistrée.'); + NotificationsComponent.success('Modifications enregistrées.'); }, error => { NotificationsComponent.error('L\'adresse mail saisie n\'est pas disponible.'); }); diff --git a/src/main/ts/src/app/core/interceptors/unauthorized.interceptor.ts b/src/main/ts/src/app/core/interceptors/unauthorized.interceptor.ts index 764a89d..5610e41 100644 --- a/src/main/ts/src/app/core/interceptors/unauthorized.interceptor.ts +++ b/src/main/ts/src/app/core/interceptors/unauthorized.interceptor.ts @@ -4,6 +4,7 @@ import { HttpInterceptor, HttpRequest, HttpHandler, HttpEvent } from '@angular/c import { Observable, throwError } from 'rxjs'; import { catchError } from 'rxjs/operators'; import { AuthService } from '../services/auth.service'; +import { NotificationsComponent } from '../notifications/notifications.component'; @Injectable() export class UnauthorizedInterceptor implements HttpInterceptor { @@ -17,6 +18,7 @@ export class UnauthorizedInterceptor implements HttpInterceptor { if (err.status === 401) { this.authService.setAnonymous(); this.router.navigate(['/login']); + window.setTimeout(() => NotificationsComponent.error('Veuillez vous authentifier pour réaliser cette action.'), 500); } const error = (err.error && err.error.message) || err.statusText; diff --git a/src/main/ts/src/app/core/post-card/post-card.component.html b/src/main/ts/src/app/core/post-card/post-card.component.html new file mode 100644 index 0000000..bbbb314 --- /dev/null +++ b/src/main/ts/src/app/core/post-card/post-card.component.html @@ -0,0 +1,27 @@ +
+ + + + + + + +

{{post.title}}

+
+ + + {{post.description}} + +
+ + +
+
diff --git a/src/main/ts/src/app/core/post-card/post-card.component.ts b/src/main/ts/src/app/core/post-card/post-card.component.ts index 350d99f..fdf0cab 100755 --- a/src/main/ts/src/app/core/post-card/post-card.component.ts +++ b/src/main/ts/src/app/core/post-card/post-card.component.ts @@ -3,35 +3,11 @@ import { Post } from '../entities'; @Component({ selector: 'app-post-card', - template: ` -
-
- Article - -
-
-
-
-

{{post.title}}

-

{{post.description}}

-
-
- - Article écrit par {{post.author.name}} - ({{post.creationDate | date:'yyyy-MM-dd HH:mm:ss'}}) -
-
`, + templateUrl: './post-card.component.html', styles: [` - div.card { + .post-card { margin-bottom: 50px; - } - .card .card-data { - padding: 15px; - background-color: #f5f5f5; + cursor: pointer; } .creation-date-area { color: #bdbdbd; diff --git a/src/main/ts/src/app/posts/create-update/create-update-post.component.ts b/src/main/ts/src/app/posts/create-update/create-update-post.component.ts index 0e8092e..d944d09 100644 --- a/src/main/ts/src/app/posts/create-update/create-update-post.component.ts +++ b/src/main/ts/src/app/posts/create-update/create-update-post.component.ts @@ -159,11 +159,17 @@ export class CreateUpdatePostComponent implements OnInit { if (this.model.key) { this.createUpdatePostService.updatePost(this.model).subscribe(post => { - NotificationsComponent.error('Modification enregistrée'); + NotificationsComponent.success('Modification enregistrée'); + }, error => { + console.log(error); + NotificationsComponent.error('Une erreur est survenue lors de l\'enregistrement des modifications.'); }); } else { this.createUpdatePostService.addPost(this.model).subscribe(post => { this.router.navigate([`/posts/update/${post.key}`]); + }, error => { + console.log(error); + NotificationsComponent.error('Une erreur est survenue lors de la création du post.'); }); } } else { diff --git a/src/main/ts/src/assets/doc/codiki_user_manual.pdf b/src/main/ts/src/assets/doc/codiki_user_manual.pdf new file mode 100644 index 0000000..618cc85 Binary files /dev/null and b/src/main/ts/src/assets/doc/codiki_user_manual.pdf differ diff --git a/src/main/ts/src/index.html b/src/main/ts/src/index.html index 0a4976c..8a954a1 100755 --- a/src/main/ts/src/index.html +++ b/src/main/ts/src/index.html @@ -3,7 +3,8 @@ Codiki - + + diff --git a/src/test/java/org/codiki/core/services/ParserServiceTest.java b/src/test/java/org/codiki/core/services/ParserServiceTest.java new file mode 100644 index 0000000..ce0e3c3 --- /dev/null +++ b/src/test/java/org/codiki/core/services/ParserServiceTest.java @@ -0,0 +1,330 @@ +package org.codiki.core.services; + +import org.apache.commons.lang.StringEscapeUtils; +import org.junit.Before; +import org.junit.Test; + +import static org.assertj.core.api.Assertions.assertThat; + +public class ParserServiceTest { + + private ParserService parserService; + + @Before + public void setUp() { + parserService = new ParserService(); + } + + /* *********************************************************** */ + /* H E A D E R S P A R S I N G */ + /* *********************************************************** */ + + @Test + public void testParseHeaders_h1() { + final String strInit = "[h1]Header[/h1]"; + final String strExpected = "

Header

"; + + assertThat(parserService.parseHeaders(strInit)).isEqualTo(strExpected); + } + + @Test + public void testParseHeaders_h2() { + final String strInit = "[h2]Header[/h2]"; + final String strExpected = "

Header

"; + + assertThat(parserService.parseHeaders(strInit)).isEqualTo(strExpected); + } + + @Test + public void testParseHeaders_h3() { + final String strInit = "[h3]Header[/h3]"; + final String strExpected = "

Header

"; + + assertThat(parserService.parseHeaders(strInit)).isEqualTo(strExpected); + } + + @Test + public void testParseHeader_onlyCoupleOfTags() { + final String strInit = "[h1][h1]Test[/h1]"; + final String strExpected = "[h1]

Test

"; + + assertThat(parserService.parseHeaders(strInit)).isEqualTo(strExpected); + } + + @Test + public void testParseHeader_multipleCouples() { + final String strInit = "[h1]Test1[/h1] [h1]Test2[/h1]"; + final String strExpected = "

Test1

Test2

"; + + assertThat(parserService.parseHeaders(strInit)).isEqualTo(strExpected); + } + + @Test + public void testParseHeaders() { + final String strInit = "[h1]Header1[/h1]\n" + + " Some text in header1\n" + + " [h2]Header2[/h2]\n" + + " Some text in header2\n" + + " [h3]Header3[/h3]\n" + + " Some text in header3\n" + + " [h3][h3]Header3[/h3]\n" + + " Some text in header3\n" + + " [h2]Header2[/h2]\n" + + " Some text in header2\n" + + " [h3]Header3[/h3][/h3]\n" + + " Some text in header3\n" + + " [h1]Header1[/h1]\n" + + " Some text in header1"; + + final String strExpected = "

Header1

\n" + + " Some text in header1\n" + + "

Header2

\n" + + " Some text in header2\n" + + "

Header3

\n" + + " Some text in header3\n" + + " [h3]

Header3

\n" + + " Some text in header3\n" + + "

Header2

\n" + + " Some text in header2\n" + + "

Header3[/h3]

\n" + + " Some text in header3\n" + + "

Header1

\n" + + " Some text in header1"; + + assertThat(parserService.parseHeaders(strInit)).isEqualTo(strExpected); + } + + /* *********************************************************** */ + /* B A C K S P A C E S P A R S I N G */ + /* *********************************************************** */ + + @Test + public void testParseBackSpaces() { + final String strInit = "Hello\nworld!"; + final String strExpected = "Hello
world!"; + + assertThat(parserService.parseBackSpaces(strInit)).isEqualTo(strExpected); + } + + @Test + public void testParseBackSpaces_endTagCode() { + final String strInit = "[/code]\n\n"; + final String strExpected = "[/code]\n"; + + assertThat(parserService.parseBackSpaces(strInit)).isEqualTo(strExpected); + } + + @Test + public void testParseBackSpaces_endTagCode_2() { + final String strInit = "[/code]\n"; + final String strExpected = "[/code]
"; + + assertThat(parserService.parseBackSpaces(strInit)).isEqualTo(strExpected); + } + + /* *********************************************************** */ + /* I M A G E S P A R S I N G */ + /* *********************************************************** */ + + @Test + public void testParseImages_simpleImg() { + final String strInit = StringEscapeUtils.escapeHtml("[img src=\"pathToImage\" /]"); + final String strExpected = "\"\""; + + assertThat(parserService.parseImages(strInit)).isEqualTo(strExpected); + } + + @Test + public void testParseImages_subTagAlt() { + final String strInit = StringEscapeUtils.escapeHtml("[img src=\"pathToImage\" alt=\"alternative\" /]"); + final String strExpected = "\"alternative\""; + + assertThat(parserService.parseImages(strInit)).isEqualTo(strExpected); + } + + @Test + public void testParseImages_multipleImages() { + final String strInit = StringEscapeUtils.escapeHtml("[img src=\"pathToImage1\" /]\n" + + " [img src=\"pathToImage2\" /]"); + final String strExpected = "\"\"\n" + + " \"\""; + + assertThat(parserService.parseImages(strInit)).isEqualTo(strExpected); + } + + @Test + public void testParseImages() { + final String strInit = StringEscapeUtils.escapeHtml("[img src=\"pathToImage1\" /]\n" + + " [img src=\"pathToImage2\" alt=\"alternativeOfImage2\" /]\n" + + " [img src=\"pathToImage3\" lgd=\"legendOfImage3\" /]\n" + + " [img src=\"pathToImage4\" alt=\"alternativeOfImage4\" lgd=\"legendOfImage4\" /]\n" + + " [img src=\"pathToImage5\" \" /]\n" + + " [img src=\"pathToImage6\" src=\"pathToImage6_2\" /]\n" + + " [img src=\"pathToImage7\" alt=\"alternativeOfImage7\" alt=\"alternativeOfImage7_2\" /]\n" + + " [img src=\"pathToImage8\" alt=\"legendOfImage8\" alt=\"legendOfImage8_2\" /]\n" + + " [img alt=\"alternativeOfImage9\" src=\"pathToImage9\" lgd=\"legendOfImage9\" /]\n" + + " [img lgd=\"legendOfImage10\" src=\"pathToImage10\" alt=\"alternativeOfImage10\" /]\n" + + " [img lgd=\"legendOfImage11\" alt=\"alternativeOfImage11\" src=\"pathToImage11\" /]"); + + final String strExpected = "\"\"\n" + + " \"alternativeOfImage2\"\n" + + " [img src="pathToImage3" lgd="legendOfImage3" /]\n" + + " [img src="pathToImage4" alt="alternativeOfImage4" lgd="legendOfImage4" /]\n" + + " [img src="pathToImage5" " /]\n" + + " [img src="pathToImage6" src="pathToImage6_2" /]\n" + + " [img src="pathToImage7" alt="alternativeOfImage7" alt="alternativeOfImage7_2" /]\n" + + " [img src="pathToImage8" alt="legendOfImage8" alt="legendOfImage8_2" /]\n" + + " [img alt="alternativeOfImage9" src="pathToImage9" lgd="legendOfImage9" /]\n" + + " [img lgd="legendOfImage10" src="pathToImage10" alt="alternativeOfImage10" /]\n" + + " [img lgd="legendOfImage11" alt="alternativeOfImage11" src="pathToImage11" /]"; + + assertThat(parserService.parseImages(strInit)).isEqualTo(strExpected); + } + + /* *********************************************************** */ + /* L I N K S P A R S I N G */ + /* *********************************************************** */ + + @Test + public void testParseLinks_simpleLink() { + final String strInit = StringEscapeUtils.escapeHtml("[link href=\"pathToLink\" txt=\"textOfLink\" /]"); + final String strExpected = "textOfLink"; + + assertThat(parserService.parseLinks(strInit)).isEqualTo(strExpected); + } + + @Test + public void testParseLinks_multipleLinks() { + final String strInit = StringEscapeUtils.escapeHtml("[link href=\"pathToLink1\" txt=\"textOfLink1\" /]\n" + + " [link href=\"pathToLink2\" txt=\"textOfLink2\" /]"); + final String strExpected = "textOfLink1\n" + + " textOfLink2"; + + assertThat(parserService.parseLinks(strInit)).isEqualTo(strExpected); + } + + /* *********************************************************** */ + /* C O D E P A R S I N G */ + /* *********************************************************** */ + + @Test + public void testParseCode_simpleCode() { + final String strInit = parserService.parseBackSpaces(StringEscapeUtils.escapeHtml("[code lg=\"python\"]\n" + + "def foo():\n" + + " return \"Hello world!\"\n" + + "[/code]\n" + + "\n")); + final String strExpected = "
def foo():\n    return "Hello world!"\n
"; + + assertThat(parserService.parseCode(strInit)).isEqualTo(strExpected); + } + + @Test + public void testParseCode_multipleCodes() { + final String strInit = parserService.parseBackSpaces(StringEscapeUtils.escapeHtml("[code lg=\"python\"]\n" + + "def foo():\n" + + " return \"Hello world!\"\n" + + "[/code]\n" + + "\n" + + "[code lg=\"java\"]\n" + + "public static void main(final String... pArgs) {\n" + + " System.out.println(\"Hello world!\");\n" + + "}\n" + + "[/code]\n" + + "\n")); + final String strExpected = "
def foo():\n    return "Hello world!"\n
public static void main(final String... pArgs) {\n    System.out.println("Hello world!");\n}\n
"; + + assertThat(parserService.parseCode(strInit)).isEqualTo(strExpected); + } + + @Test + public void testParseLinks_backSlashAndCodeParsing() { + final String strInit = parserService.parseBackSpaces(StringEscapeUtils.escapeHtml("[code lg=\"python\"]\n" + + "def foo():\n" + + " return \"Hello world!\"\n" + + "[/code]\n" + + "\n")); + final String strExpected = "
def foo():\n    return "Hello world!"\n
"; + + assertThat(parserService.parseCode(strInit)).isEqualTo(strExpected); + } + + @Test + public void testParseLinks_backSlashAndCodeParsing_multiple() { + final String strInit = parserService.parseBackSpaces(StringEscapeUtils.escapeHtml("[code lg=\"python\"]\n" + + "def foo():\n" + + " return \"Hello world!\"\n" + + "[/code]\n" + + "\n" + + "[code lg=\"java\"]\n" + + "public static void main(final String... pArgs) {\n" + + " System.out.println(\"Hello world!\");\n" + + "}\n" + + "[/code]\n" + + "\n")); + final String strExpected = "
def foo():\n    return "Hello world!"\n
public static void main(final String... pArgs) {\n    System.out.println("Hello world!");\n}\n
"; + + assertThat(parserService.parseCode(strInit)).isEqualTo(strExpected); + } + + @Test + public void testParse() { + final String strInit = "\n" + + "[h1]Title 1[/h1]\n" + + "[h2]Title 2[/h2]\n" + + "[h3]Title 3[/h3]\n" + + "Some text on multiple\n" + + "lines\n" + + "like\n" + + "here!\n" + + "\n" + + "[img src=\"pathToImage\" alt=\"alternativeOfImage\" /]\n" + + "\n" + + "[code lg=\"java\"]\n" + + "public static void main(final String... pArgs) {\n" + + " System.out.println(\"Hello world!\");\n" + + "}\n" + + "[/code]\n" + + "\n" + + "\n" + + "[link href=\"pathToLink1\" txt=\"textOfLink1\" /]\n" + + "\n" + + "[img src=\"pathToImage2\" alt=\"alternativeOfImage2\" lgd=\"legendOfImage2\" /]\n" + + "[link href=\"pathToLink2\" txt=\"textOfLink2\" /]\n" + + "\n" + + "[code lg=\"python\"]\n" + + "def foo():\n" + + " return \"Hello world!\"\n" + + "[/code]\n" + + "\n" + + ""; + + final String strExpected = "
" + + "

Title 1


" + + "

Title 2


" + + "

Title 3


" + + "Some text on multiple
" + + "lines
" + + "like
" + + "here!
" + + "
" + + "\"alternativeOfImage\"
" + + "
" + + "
" +
+                "public static void main(final String... pArgs) {\n" +
+                "    System.out.println("Hello world!");\n" +
+                "}\n" +
+                "

" + + "textOfLink1
" + + "
" + + "[img src="pathToImage2" alt="alternativeOfImage2" lgd="legendOfImage2" /]
" + + "textOfLink2
" + + "
" + + "
" +
+                "def foo():\n" +
+                "    return "Hello world!"\n" +
+                "
"; + + assertThat(parserService.parse(strInit)).isEqualTo(strExpected); + } +}