Binary file not shown.
@@ -28,11 +28,7 @@ export class AuthInterceptor implements HttpInterceptor {
|
|||||||
if (
|
if (
|
||||||
error instanceof HttpErrorResponse &&
|
error instanceof HttpErrorResponse &&
|
||||||
!req.url.includes('auth/signin') &&
|
!req.url.includes('auth/signin') &&
|
||||||
(error.status === 401
|
(error.status === 401) /// must be only 401 without 500 and 0
|
||||||
|| error.status === 500
|
|
||||||
|| error.status === 0
|
|
||||||
|
|
||||||
) /// must be only 401 without 500 and 0
|
|
||||||
) {
|
) {
|
||||||
return this.handle401Error(req, next);
|
return this.handle401Error(req, next);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -141,8 +141,10 @@ export class MainComponent implements OnInit{
|
|||||||
this.storageService.clean();
|
this.storageService.clean();
|
||||||
|
|
||||||
//window.location.reload();
|
//window.location.reload();
|
||||||
this.router.navigate([environment.default_page]);
|
this.router.navigate([environment.default_page]).then(() => {
|
||||||
window.location.reload();
|
window.location.reload();
|
||||||
|
});
|
||||||
|
//window.location.reload();
|
||||||
},
|
},
|
||||||
error: err => {
|
error: err => {
|
||||||
console.log(err);
|
console.log(err);
|
||||||
@@ -175,7 +177,7 @@ export class MainComponent implements OnInit{
|
|||||||
this.authService.login(this.dialogLoginData.username, this.dialogLoginData.password).subscribe(
|
this.authService.login(this.dialogLoginData.username, this.dialogLoginData.password).subscribe(
|
||||||
data => {
|
data => {
|
||||||
|
|
||||||
//this.storageService.saveToken(data.token);
|
|
||||||
this.storageService.saveUser(data);
|
this.storageService.saveUser(data);
|
||||||
|
|
||||||
this.isLoggedIn = true;
|
this.isLoggedIn = true;
|
||||||
@@ -191,8 +193,7 @@ export class MainComponent implements OnInit{
|
|||||||
}
|
}
|
||||||
);
|
);
|
||||||
|
|
||||||
// do something here with the data
|
|
||||||
dialogSubmitSubscription.unsubscribe();
|
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
+34
-1
@@ -1 +1,34 @@
|
|||||||
<p>tutorial-add.component works!</p>
|
<mat-card appearance="outlined">
|
||||||
|
<mat-card-header>
|
||||||
|
<mat-card-title>Add tutorial</mat-card-title>
|
||||||
|
</mat-card-header>
|
||||||
|
<mat-card-content >
|
||||||
|
<mat-form-field class="example-full-width">
|
||||||
|
<mat-label>Title</mat-label>
|
||||||
|
<input matInput required
|
||||||
|
[(ngModel)]="tutorial.title">
|
||||||
|
</mat-form-field>
|
||||||
|
|
||||||
|
<mat-tab-group>
|
||||||
|
<mat-tab label="Markdown text">
|
||||||
|
<textarea class="variable-textarea" [(ngModel)]="tutorial.description"></textarea>
|
||||||
|
<markdown class="variable-binding" [data]="tutorial.description"></markdown>
|
||||||
|
</mat-tab>
|
||||||
|
<mat-tab label="Result">
|
||||||
|
<markdown class="preview" [data]="tutorial.description"></markdown>
|
||||||
|
</mat-tab>
|
||||||
|
<mat-tab label="Example">
|
||||||
|
<textarea class="variable-textarea" [(ngModel)]="markdown"></textarea>
|
||||||
|
<markdown class="variable-binding" [data]="markdown"></markdown>
|
||||||
|
</mat-tab>
|
||||||
|
</mat-tab-group>
|
||||||
|
</mat-card-content>
|
||||||
|
<mat-card-actions>
|
||||||
|
<button matButton (click)="saveTutorial()" *ngIf="!submitted">Save</button>
|
||||||
|
<div *ngIf="submitted">
|
||||||
|
<h4>Tutorial was submitted successfully!</h4>
|
||||||
|
<button matButton (click)="newTutorial()">Add new tutorial</button>
|
||||||
|
</div>
|
||||||
|
</mat-card-actions>
|
||||||
|
</mat-card>
|
||||||
|
|
||||||
|
|||||||
@@ -0,0 +1,51 @@
|
|||||||
|
.submit-form {
|
||||||
|
max-width: 400px;
|
||||||
|
margin: auto;
|
||||||
|
}
|
||||||
|
|
||||||
|
mat-card{
|
||||||
|
margin: 20px;
|
||||||
|
}
|
||||||
|
mat-card-title{
|
||||||
|
color: cyan;
|
||||||
|
}
|
||||||
|
|
||||||
|
.example-form {
|
||||||
|
min-width: 150px;
|
||||||
|
max-width: 500px;
|
||||||
|
width: 100%;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
.example-full-width {
|
||||||
|
width: 100%;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
.variable-binding,
|
||||||
|
.variable-textarea {
|
||||||
|
width: 49%;
|
||||||
|
}
|
||||||
|
|
||||||
|
.variable-textarea {
|
||||||
|
border-radius: 5px;
|
||||||
|
box-shadow: 0 1px 2px rgba(0,0,0,.07);
|
||||||
|
min-height: 420px;
|
||||||
|
padding: 8px;
|
||||||
|
transition: all 300ms ease-out;
|
||||||
|
}
|
||||||
|
|
||||||
|
.variable-textarea:hover {
|
||||||
|
box-shadow: 0 6px 12px 3px rgba(0,0,0,.09),
|
||||||
|
0 2px 3px 1px rgba(0,0,0,.06);
|
||||||
|
}
|
||||||
|
|
||||||
|
.variable-binding {
|
||||||
|
display: block;
|
||||||
|
float: right;
|
||||||
|
}
|
||||||
|
|
||||||
|
.preview {
|
||||||
|
/* display: block;
|
||||||
|
float: right;*/
|
||||||
|
}
|
||||||
|
|||||||
@@ -1,11 +1,91 @@
|
|||||||
import { Component } from '@angular/core';
|
import {Component, CUSTOM_ELEMENTS_SCHEMA} from '@angular/core';
|
||||||
|
import {UserApiService} from '../user-api.service';
|
||||||
|
import {TokenStorageService} from '../../services/token-storage.service';
|
||||||
|
import {EventBusService} from '../../_shared/event-bus.service';
|
||||||
|
import {Router} from '@angular/router';
|
||||||
|
import {AuthService} from '../../services/auth.service';
|
||||||
|
import {Tutorial} from '../../models/tutorial.model';
|
||||||
|
import {MarkdownComponent} from 'ngx-markdown';
|
||||||
|
import {MatButton} from '@angular/material/button';
|
||||||
|
import {MatCard, MatCardActions, MatCardContent, MatCardHeader} from '@angular/material/card';
|
||||||
|
import {MatFormField, MatInput, MatLabel} from '@angular/material/input';
|
||||||
|
import {MatTab, MatTabGroup} from '@angular/material/tabs';
|
||||||
|
import {NgIf} from '@angular/common';
|
||||||
|
import {FormsModule, ReactiveFormsModule} from '@angular/forms';
|
||||||
|
|
||||||
@Component({
|
@Component({
|
||||||
selector: 'app-tutorial-add.component',
|
selector: 'app-tutorial-add.component',
|
||||||
imports: [],
|
imports: [
|
||||||
|
MarkdownComponent,
|
||||||
|
MatButton,
|
||||||
|
MatCard,
|
||||||
|
MatCardActions,
|
||||||
|
MatCardContent,
|
||||||
|
MatCardHeader,
|
||||||
|
MatFormField,
|
||||||
|
MatInput,
|
||||||
|
MatLabel,
|
||||||
|
MatTab,
|
||||||
|
MatTabGroup,
|
||||||
|
NgIf,
|
||||||
|
ReactiveFormsModule,
|
||||||
|
FormsModule
|
||||||
|
],
|
||||||
templateUrl: './tutorial-add.component.html',
|
templateUrl: './tutorial-add.component.html',
|
||||||
styleUrl: './tutorial-add.component.scss'
|
styleUrl: './tutorial-add.component.scss',
|
||||||
|
schemas: [CUSTOM_ELEMENTS_SCHEMA]
|
||||||
})
|
})
|
||||||
export class TutorialAddComponent {
|
export class TutorialAddComponent {
|
||||||
|
tutorial: Tutorial = new Tutorial();
|
||||||
|
submitted = false
|
||||||
|
|
||||||
|
markdown = `## Markdown __rulez__!
|
||||||
|
---
|
||||||
|
|
||||||
|
### Syntax highlight
|
||||||
|
\`\`\`typescript
|
||||||
|
const language = 'typescript';
|
||||||
|
\`\`\`
|
||||||
|
|
||||||
|
### Lists
|
||||||
|
1. Ordered list
|
||||||
|
2. Another bullet point
|
||||||
|
- Unordered list
|
||||||
|
- Another unordered bullet
|
||||||
|
|
||||||
|
### Blockquote
|
||||||
|
> Blockquote to the max`;
|
||||||
|
|
||||||
|
|
||||||
|
constructor(private userApiService: UserApiService,
|
||||||
|
private storageService: TokenStorageService,
|
||||||
|
private eventBusService: EventBusService,
|
||||||
|
private router: Router, private authService: AuthService) {
|
||||||
|
|
||||||
|
this.tutorial.description = this.markdown;
|
||||||
|
}
|
||||||
|
saveTutorial(): void {
|
||||||
|
const data = {
|
||||||
|
title: this.tutorial.title,
|
||||||
|
description: this.tutorial.description
|
||||||
|
};
|
||||||
|
|
||||||
|
this.userApiService.create(data)
|
||||||
|
.subscribe(
|
||||||
|
response => {
|
||||||
|
console.log(response);
|
||||||
|
this.submitted = true;
|
||||||
|
},
|
||||||
|
error => {
|
||||||
|
console.log(error);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
newTutorial(): void {
|
||||||
|
this.submitted = false;
|
||||||
|
this.tutorial = {
|
||||||
|
title: '',
|
||||||
|
description: '',
|
||||||
|
published: false
|
||||||
|
};
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
+2
-3
@@ -56,8 +56,7 @@ export class TutorialsListComponent implements OnInit {
|
|||||||
if (
|
if (
|
||||||
(
|
(
|
||||||
error.status === 401
|
error.status === 401
|
||||||
|| error.status === 500
|
|
||||||
|| error.status === 0
|
|
||||||
)
|
)
|
||||||
&& this.storageService.isLoggedIn()
|
&& this.storageService.isLoggedIn()
|
||||||
) {
|
) {
|
||||||
@@ -77,7 +76,7 @@ export class TutorialsListComponent implements OnInit {
|
|||||||
|
|
||||||
//window.location.reload();
|
//window.location.reload();
|
||||||
this.router.navigate(['main/generate-image']).then(() => {
|
this.router.navigate(['main/generate-image']).then(() => {
|
||||||
window.location.reload();
|
//window.location.reload();
|
||||||
})
|
})
|
||||||
|
|
||||||
},
|
},
|
||||||
|
|||||||
@@ -1,7 +1,7 @@
|
|||||||
import { Injectable } from '@angular/core';
|
import { Injectable } from '@angular/core';
|
||||||
import {Observable} from 'rxjs';
|
import {Observable} from 'rxjs';
|
||||||
import {Tutorial} from '../models/tutorial.model';
|
import {Tutorial} from '../models/tutorial.model';
|
||||||
import {HttpClient} from '@angular/common/http';
|
import {HttpClient, HttpHeaders} from '@angular/common/http';
|
||||||
|
|
||||||
@Injectable({
|
@Injectable({
|
||||||
providedIn: 'root'
|
providedIn: 'root'
|
||||||
@@ -9,11 +9,19 @@ import {HttpClient} from '@angular/common/http';
|
|||||||
export class UserApiService {
|
export class UserApiService {
|
||||||
baseUrl = 'http://localhost:8080/api/user';
|
baseUrl = 'http://localhost:8080/api/user';
|
||||||
|
|
||||||
|
httpOptions = {
|
||||||
|
headers: new HttpHeaders({ 'Content-Type': 'application/json' } )
|
||||||
|
};
|
||||||
|
|
||||||
constructor(private http: HttpClient) {
|
constructor(private http: HttpClient) {
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
getUserAllTutorials(): Observable<Tutorial[]> {
|
getUserAllTutorials(): Observable<Tutorial[]> {
|
||||||
return this.http.get<Tutorial[]>(`${this.baseUrl}/tutorials`,{withCredentials: true});
|
return this.http.get<Tutorial[]>(`${this.baseUrl}/tutorials`, this.httpOptions);
|
||||||
|
}
|
||||||
|
|
||||||
|
create(data: any): Observable<any> {
|
||||||
|
return this.http.post(`${this.baseUrl}/tutorial-add`, data);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -24,7 +24,7 @@ import java.util.Optional;
|
|||||||
|
|
||||||
|
|
||||||
|
|
||||||
@CrossOrigin(origins = "http://localhost:4200", maxAge = 3600, allowCredentials="true")
|
//@CrossOrigin(origins = "http://localhost:4200", maxAge = 3600, allowCredentials="true")
|
||||||
@RestController
|
@RestController
|
||||||
@RequestMapping("/api")
|
@RequestMapping("/api")
|
||||||
public class TutorialController {
|
public class TutorialController {
|
||||||
@@ -117,7 +117,7 @@ public class TutorialController {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@PostMapping("/tutorials")
|
@PostMapping("user/tutorial-add")
|
||||||
public ResponseEntity<Tutorial> createTutorial(@RequestBody Tutorial tutorial) {
|
public ResponseEntity<Tutorial> createTutorial(@RequestBody Tutorial tutorial) {
|
||||||
|
|
||||||
Authentication authentication = SecurityContextHolder.getContext().getAuthentication();
|
Authentication authentication = SecurityContextHolder.getContext().getAuthentication();
|
||||||
|
|||||||
@@ -16,6 +16,9 @@ import org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder;
|
|||||||
import org.springframework.security.crypto.password.PasswordEncoder;
|
import org.springframework.security.crypto.password.PasswordEncoder;
|
||||||
import org.springframework.security.web.SecurityFilterChain;
|
import org.springframework.security.web.SecurityFilterChain;
|
||||||
import org.springframework.security.web.authentication.UsernamePasswordAuthenticationFilter;
|
import org.springframework.security.web.authentication.UsernamePasswordAuthenticationFilter;
|
||||||
|
import org.springframework.web.cors.CorsConfiguration;
|
||||||
|
import org.springframework.web.cors.UrlBasedCorsConfigurationSource;
|
||||||
|
import org.springframework.web.filter.CorsFilter;
|
||||||
import org.springframework.web.servlet.config.annotation.CorsRegistry;
|
import org.springframework.web.servlet.config.annotation.CorsRegistry;
|
||||||
import org.springframework.web.servlet.config.annotation.WebMvcConfigurer;
|
import org.springframework.web.servlet.config.annotation.WebMvcConfigurer;
|
||||||
|
|
||||||
@@ -134,7 +137,9 @@ public class WebSecurityConfig implements WebMvcConfigurer {// extends WebSecuri
|
|||||||
// Allow public access to tutorials (without login)
|
// Allow public access to tutorials (without login)
|
||||||
.requestMatchers("/api/public/tutorials").permitAll()
|
.requestMatchers("/api/public/tutorials").permitAll()
|
||||||
|
|
||||||
.requestMatchers("/api/user").permitAll()
|
.requestMatchers("/api/public/tutorials/**").permitAll()
|
||||||
|
|
||||||
|
.requestMatchers("/api/user/**").permitAll()
|
||||||
|
|
||||||
.anyRequest().authenticated()
|
.anyRequest().authenticated()
|
||||||
);
|
);
|
||||||
@@ -145,4 +150,16 @@ public class WebSecurityConfig implements WebMvcConfigurer {// extends WebSecuri
|
|||||||
|
|
||||||
return http.build();
|
return http.build();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Bean
|
||||||
|
public CorsFilter corsFilter() {
|
||||||
|
UrlBasedCorsConfigurationSource source = new UrlBasedCorsConfigurationSource();
|
||||||
|
CorsConfiguration config = new CorsConfiguration();
|
||||||
|
config.setAllowCredentials(true);
|
||||||
|
config.addAllowedOrigin("http://localhost:4200");
|
||||||
|
config.addAllowedHeader("*");
|
||||||
|
config.addAllowedMethod("*");
|
||||||
|
source.registerCorsConfiguration("/**", config);
|
||||||
|
return new CorsFilter(source);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -25,6 +25,7 @@ public class AuthEntryPointJwt implements AuthenticationEntryPoint {
|
|||||||
public void commence(HttpServletRequest request, HttpServletResponse response, AuthenticationException authException)
|
public void commence(HttpServletRequest request, HttpServletResponse response, AuthenticationException authException)
|
||||||
throws IOException, ServletException {
|
throws IOException, ServletException {
|
||||||
logger.error("Unauthorized error: {}", authException.getMessage());
|
logger.error("Unauthorized error: {}", authException.getMessage());
|
||||||
|
logger.error("Unauthorized error to resource {}", request.getRequestURI());
|
||||||
|
|
||||||
response.sendError(HttpServletResponse.SC_UNAUTHORIZED, "Error: Unauthorized");
|
response.sendError(HttpServletResponse.SC_UNAUTHORIZED, "Error: Unauthorized");
|
||||||
response.setContentType(MediaType.APPLICATION_JSON_VALUE);
|
response.setContentType(MediaType.APPLICATION_JSON_VALUE);
|
||||||
|
|||||||
@@ -1,9 +1,37 @@
|
|||||||
spring.application.name=jambotron
|
spring.application.name=jambotron
|
||||||
|
|
||||||
|
#============Localhost Configurations========================
|
||||||
|
spring.datasource.url= jdbc:postgresql://localhost:5432/jambotronDB
|
||||||
|
spring.datasource.username= admin
|
||||||
|
spring.datasource.password= postgrespw
|
||||||
|
=======
|
||||||
spring.datasource.url= jdbc:postgresql://ep-red-breeze-a2cxhq5f.eu-central-1.pg.koyeb.app:5432/jambotronDB
|
spring.datasource.url= jdbc:postgresql://ep-red-breeze-a2cxhq5f.eu-central-1.pg.koyeb.app:5432/jambotronDB
|
||||||
spring.datasource.username= koyeb-adm
|
spring.datasource.username= koyeb-adm
|
||||||
spring.datasource.password= npg_HfFEUA7bay1i
|
spring.datasource.password= npg_HfFEUA7bay1i
|
||||||
|
|
||||||
|
#
|
||||||
|
spring.flyway.baseline-on-migrate=true
|
||||||
|
spring.flyway.validate-on-migrate=true
|
||||||
|
|
||||||
|
spring.flyway.url=jdbc:postgresql://localhost:5432/jambotronDB
|
||||||
|
spring.flyway.user=admin
|
||||||
|
spring.flyway.password=postgrespw
|
||||||
|
|
||||||
|
|
||||||
|
#============Koyeb Configurations========================
|
||||||
|
|
||||||
|
#spring.datasource.url= jdbc:postgresql://ep-red-breeze-a2cxhq5f.eu-central-1.pg.koyeb.app/jambotronDB
|
||||||
|
#spring.datasource.username= koyeb-adm
|
||||||
|
#spring.datasource.password= npg_HfFEUA7bay1i
|
||||||
|
#
|
||||||
|
#spring.flyway.baseline-on-migrate=true
|
||||||
|
#spring.flyway.validate-on-migrate=true
|
||||||
|
#
|
||||||
|
#spring.flyway.url=jdbc:postgresql://ep-red-breeze-a2cxhq5f.eu-central-1.pg.koyeb.app/jambotronDB
|
||||||
|
#spring.flyway.user=koyeb-adm
|
||||||
|
#spring.flyway.password=npg_HfFEUA7bay1i
|
||||||
|
|
||||||
|
|
||||||
#spring.datasource.url=${SPRING_DATASOURCE_URL}
|
#spring.datasource.url=${SPRING_DATASOURCE_URL}
|
||||||
#spring.datasource.username=${SPRING_DATASOURCE_USERNAME}
|
#spring.datasource.username=${SPRING_DATASOURCE_USERNAME}
|
||||||
#spring.datasource.password=${SPRING_DATASOURCE_PASSWORD}
|
#spring.datasource.password=${SPRING_DATASOURCE_PASSWORD}
|
||||||
@@ -38,13 +66,6 @@ spring.mvc.throw-exception-if-no-handler-found=true
|
|||||||
|
|
||||||
spring.docker.compose.file=../Docker/docker-compose.yml
|
spring.docker.compose.file=../Docker/docker-compose.yml
|
||||||
|
|
||||||
spring.flyway.baseline-on-migrate=true
|
|
||||||
spring.flyway.validate-on-migrate=true
|
|
||||||
|
|
||||||
spring.flyway.user=koyeb-adm
|
|
||||||
spring.flyway.password=npg_HfFEUA7bay1i
|
|
||||||
spring.flyway.url=jdbc:postgresql://ep-red-breeze-a2cxhq5f.eu-central-1.pg.koyeb.app:5432/jambotronDB
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|||||||
@@ -0,0 +1,27 @@
|
|||||||
|
DROP TABLE IF EXISTS public.tutorials;
|
||||||
|
|
||||||
|
CREATE TABLE IF NOT EXISTS public.tutorials
|
||||||
|
(
|
||||||
|
description character varying(255) COLLATE pg_catalog."default",
|
||||||
|
published boolean,
|
||||||
|
title character varying(255) COLLATE pg_catalog."default",
|
||||||
|
id bigint NOT NULL GENERATED BY DEFAULT AS IDENTITY ( INCREMENT 1 START 1 MINVALUE 1 MAXVALUE 9223372036854775807 CACHE 1 ),
|
||||||
|
userid bigint,
|
||||||
|
CONSTRAINT "tutorial_user_FK" FOREIGN KEY (userid)
|
||||||
|
REFERENCES public.users (id) MATCH SIMPLE
|
||||||
|
ON UPDATE NO ACTION
|
||||||
|
ON DELETE NO ACTION
|
||||||
|
NOT VALID
|
||||||
|
)
|
||||||
|
|
||||||
|
TABLESPACE pg_default;
|
||||||
|
|
||||||
|
|
||||||
|
-- Index: fki_tutorial_user_FK
|
||||||
|
|
||||||
|
-- DROP INDEX IF EXISTS public."fki_tutorial_user_FK";
|
||||||
|
|
||||||
|
CREATE INDEX IF NOT EXISTS "fki_tutorial_user_FK"
|
||||||
|
ON public.tutorials USING btree
|
||||||
|
(userid ASC NULLS LAST)
|
||||||
|
TABLESPACE pg_default;
|
||||||
Reference in New Issue
Block a user