Skip to content
Snippets Groups Projects
Commit ac7e90fc authored by Jonny Ngo Luong's avatar Jonny Ngo Luong
Browse files

Merge branch 'master' into '8-frontend-bruker-form'

# Conflicts:
#   server/src/controllers/postController/index.ts
parents b46209f9 3254b38b
No related branches found
No related tags found
1 merge request!12Resolve "Frontend: Bruker form"
Showing
with 354 additions and 35 deletions
...@@ -9,6 +9,7 @@ import { UserProfileComponent } from './users/user-profile/user-profile.componen ...@@ -9,6 +9,7 @@ import { UserProfileComponent } from './users/user-profile/user-profile.componen
const routes: Routes = [ const routes: Routes = [
{ path: 'annonse/ny', component: PostFormComponent }, { path: 'annonse/ny', component: PostFormComponent },
{ path: 'annonse/rediger/:id', component: PostFormComponent },
{ path: 'annonse', component: PostListComponent }, { path: 'annonse', component: PostListComponent },
{ path: 'annonse/:id', component: PostDetailsComponent }, { path: 'annonse/:id', component: PostDetailsComponent },
......
...@@ -5,3 +5,6 @@ ...@@ -5,3 +5,6 @@
<br> <br>
<p>Publiseringstidspunkt: {{post.getTimestamp}}</p> <p>Publiseringstidspunkt: {{post.getTimestamp}}</p>
<p>Eier: {{post.getOwner}}</p> <p>Eier: {{post.getOwner}}</p>
<app-button text="Rediger annonse" (click)="editPost()"></app-button>
<app-button text="Slett annonse" (click)="deletePost()"></app-button>
import { HttpClientTestingModule } from '@angular/common/http/testing';
import { ComponentFixture, TestBed } from '@angular/core/testing'; import { ComponentFixture, TestBed } from '@angular/core/testing';
import { ActivatedRoute } from '@angular/router';
import { RouterTestingModule } from '@angular/router/testing';
import { Post } from 'src/app/models/post.model';
import { PostService } from '../post.service';
import { PostDetailsComponent } from './post-details.component'; import { PostDetailsComponent } from './post-details.component';
describe('PostDetailsComponent', () => { describe('PostDetailsComponent', () => {
let component: PostDetailsComponent; let component: PostDetailsComponent;
let fixture: ComponentFixture<PostDetailsComponent>; let fixture: ComponentFixture<PostDetailsComponent>;
let mockPostService;
beforeEach(async () => { beforeEach(async () => {
// PostService mock setup
mockPostService = jasmine.createSpyObj(['getPost', 'deletePost']);
mockPostService.getPost.and.returnValue(
new Promise<Post>(
(resolve) => {
resolve(new Post({
id: 5,
title: "Test",
description: "TestDescription",
timestamp: 23947298,
owner: "user",
imageUrl: null,
price: 49,
categoryid: 2
}));
})
);
mockPostService.deletePost.and.returnValue(
new Promise<any>(
(resolve) => {
resolve({data: []})
})
);
await TestBed.configureTestingModule({ await TestBed.configureTestingModule({
declarations: [ PostDetailsComponent ] declarations: [ PostDetailsComponent ],
imports: [ HttpClientTestingModule, RouterTestingModule ],
providers: [
{ provide: ActivatedRoute, useValue: { snapshot: {params: {id: 5}}}},
{ provide: PostService, useValue: mockPostService }
]
}) })
.compileComponents(); .compileComponents();
}); });
...@@ -22,4 +57,29 @@ describe('PostDetailsComponent', () => { ...@@ -22,4 +57,29 @@ describe('PostDetailsComponent', () => {
it('should create', () => { it('should create', () => {
expect(component).toBeTruthy(); expect(component).toBeTruthy();
}); });
it('should get post with id from url parameter', async () => {
// Waits for ngOnInit and checks that we get post
fixture.whenStable().then(() => {
expect(mockPostService.getPost).toHaveBeenCalledWith(5);
expect(component.post).toEqual(new Post({
id: 5,
title: "Test",
description: "TestDescription",
timestamp: 23947298,
owner: "user",
imageUrl: null,
price: 49,
categoryid: 2
}));
});
});
it('should delete post with id', async () => {
// Waits for ngOnInit and checks that we can delete post
fixture.whenStable().then(() => {
component.deletePost();
expect(mockPostService.deletePost).toHaveBeenCalledWith(5);
});
});
}); });
import { Component, OnInit } from '@angular/core'; import { Component, OnInit } from '@angular/core';
import { Post } from 'src/app/models/post.model'; import { Post } from 'src/app/models/post.model';
import { PostService } from '../post.service'; import { PostService } from '../post.service';
import { ActivatedRoute } from '@angular/router' import { ActivatedRoute, Router } from '@angular/router'
@Component({ @Component({
selector: 'app-post-details', selector: 'app-post-details',
...@@ -12,19 +12,35 @@ export class PostDetailsComponent implements OnInit { ...@@ -12,19 +12,35 @@ export class PostDetailsComponent implements OnInit {
post: Post = new Post(); post: Post = new Post();
constructor(private postService: PostService, private activatedRoute: ActivatedRoute) { } constructor(private postService: PostService, private activatedRoute: ActivatedRoute, private router: Router) { }
ngOnInit(): void { ngOnInit(): void {
// Gets id parameter from URL // Gets id parameter from URL
this.activatedRoute.params.subscribe(params => { const id = this.activatedRoute.snapshot.params["id"];
const id = params["id"];
// Gets Post with id from database // Gets Post with id from database
this.postService.getPost(id).then(post => { this.postService.getPost(id).then(post => {
this.post = post; this.post = post;
}).catch(error => { }).catch(error => {
console.log(error); console.log(error);
}); });
}
/**
* Moves to edit page
*/
editPost() {
this.router.navigateByUrl("/annonse/rediger/" + this.post.getId);
}
/**
* Deletes post in database and navigates to post list
*/
deletePost() {
this.postService.deletePost(this.post.getId).then(data => {
console.log("Successfully deleted post: " + this.post.getId);
this.router.navigateByUrl("/annonse");
}).catch(error => {
console.log(error);
}); });
} }
} }
<div class="postform"> <div class="postform">
<h3>Lag annonse</h3> <h3>{{id ? 'Rediger annonse' : 'Lag annonse'}}</h3>
<app-text-input [(inputModel)]="title" label="Tittel" (blur)="checkForm()"></app-text-input> <app-text-input [(inputModel)]="title" label="Tittel" (blur)="checkForm()"></app-text-input>
...@@ -8,8 +8,8 @@ ...@@ -8,8 +8,8 @@
<app-number-input [(inputModel)]="price" label="Pris" (blur)="checkForm()"></app-number-input> <app-number-input [(inputModel)]="price" label="Pris" (blur)="checkForm()"></app-number-input>
<app-select [(inputModel)]="categoryid" (change)="checkForm()" label="Kategori"> <app-select [(inputModel)]="categoryid" (change)="checkForm()" label="Kategori">
<option value="0" selected hidden>Velg kategori</option> <option value="0" [selected]="categoryid == 0" hidden>Velg kategori</option>
<option *ngFor="let category of categories" [value]="category.getCategoryId">{{category.getName}}</option> <option *ngFor="let category of categories" [value]="category.getCategoryId" [selected]="categoryid == category.getCategoryId">{{category.getName}}</option>
</app-select> </app-select>
<app-text-input [(inputModel)]="imageUrl" label="Bilde URL" (blur)="showImage(imageUrl)"></app-text-input> <app-text-input [(inputModel)]="imageUrl" label="Bilde URL" (blur)="showImage(imageUrl)"></app-text-input>
...@@ -18,4 +18,5 @@ ...@@ -18,4 +18,5 @@
<p>{{statusMessage}}</p> <p>{{statusMessage}}</p>
<app-button (click)="publishPost()" text="Publiser"></app-button> <app-button (click)="publishPost()" text="Publiser"></app-button>
<app-button *ngIf="id" (click)="deletePost()" text="Slett annonse"></app-button>
</div> </div>
import { HttpClientTestingModule } from '@angular/common/http/testing'; import { HttpClientTestingModule } from '@angular/common/http/testing';
import { ComponentFixture, fakeAsync, TestBed, tick } from '@angular/core/testing'; import { ComponentFixture, fakeAsync, TestBed } from '@angular/core/testing';
import { FormsModule } from '@angular/forms'; import { FormsModule } from '@angular/forms';
import { Router } from '@angular/router'; import { Router } from '@angular/router';
import { RouterTestingModule } from '@angular/router/testing'; import { RouterTestingModule } from '@angular/router/testing';
...@@ -17,7 +17,7 @@ describe('PostFormComponent', () => { ...@@ -17,7 +17,7 @@ describe('PostFormComponent', () => {
beforeEach(async () => { beforeEach(async () => {
// PostService mock setup // PostService mock setup
mockPostService = jasmine.createSpyObj(['getAllCategories', 'addPost']); mockPostService = jasmine.createSpyObj(['getAllCategories', 'addPost', 'deletePost']);
mockPostService.getAllCategories.and.returnValue( mockPostService.getAllCategories.and.returnValue(
new Promise<Array<Category>>( new Promise<Array<Category>>(
(resolve) => { (resolve) => {
...@@ -30,6 +30,12 @@ describe('PostFormComponent', () => { ...@@ -30,6 +30,12 @@ describe('PostFormComponent', () => {
resolve("success") resolve("success")
}) })
); );
mockPostService.deletePost.and.returnValue(
new Promise<any>(
(resolve) => {
resolve({data: []})
})
);
await TestBed.configureTestingModule({ await TestBed.configureTestingModule({
declarations: [ PostFormComponent ], declarations: [ PostFormComponent ],
...@@ -109,4 +115,22 @@ describe('PostFormComponent', () => { ...@@ -109,4 +115,22 @@ describe('PostFormComponent', () => {
component.showImage("test"); component.showImage("test");
expect(component.displayImageUrl).toBe("test"); expect(component.displayImageUrl).toBe("test");
}); });
it('should delete post with id', async () => {
component.id = 5;
// Waits for ngOnInit and checks that we can delete post
fixture.whenStable().then(() => {
component.deletePost();
expect(mockPostService.deletePost).toHaveBeenCalledWith(5);
});
});
it('should not delete new post', async () => {
// Waits for ngOnInit and checks that we can delete post
fixture.whenStable().then(() => {
component.deletePost();
expect(mockPostService.deletePost).not.toHaveBeenCalledWith(5);
});
});
}); });
import { Component, OnInit } from '@angular/core'; import { Component, OnInit } from '@angular/core';
import { Router } from '@angular/router'; import { ActivatedRoute, Router } from '@angular/router';
import { Category } from 'src/app/models/category.model'; import { Category } from 'src/app/models/category.model';
import { Post } from 'src/app/models/post.model'; import { Post } from 'src/app/models/post.model';
import { PostService } from '../post.service'; import { PostService } from '../post.service';
...@@ -11,6 +11,8 @@ import { PostService } from '../post.service'; ...@@ -11,6 +11,8 @@ import { PostService } from '../post.service';
}) })
export class PostFormComponent implements OnInit { export class PostFormComponent implements OnInit {
id = 0;
title: string = ""; title: string = "";
description: string = ""; description: string = "";
price: number = 0; price: number = 0;
...@@ -22,9 +24,27 @@ export class PostFormComponent implements OnInit { ...@@ -22,9 +24,27 @@ export class PostFormComponent implements OnInit {
categories: Array<Category>; categories: Array<Category>;
constructor(private postService: PostService, private router: Router) { } constructor(private postService: PostService, private router: Router, private activatedRoute: ActivatedRoute) { }
ngOnInit() { ngOnInit() {
const id = this.activatedRoute.snapshot.params["id"];
if (id) {
this.id = id;
// Gets Post with id from database
this.postService.getPost(id).then(post => {
this.title = post.getTitle;
this.description = post.getDescription;
this.price = post.getPrice;
this.categoryid = post.getCategory;
this.imageUrl = post.getImageUrl;
this.showImage(this.imageUrl);
}).catch(error => {
console.log(error);
});
}
// Gets all categories and displays them in dropdown // Gets all categories and displays them in dropdown
this.postService.getAllCategories().then(categories => { this.postService.getAllCategories().then(categories => {
this.categories = categories; this.categories = categories;
...@@ -77,12 +97,37 @@ export class PostFormComponent implements OnInit { ...@@ -77,12 +97,37 @@ export class PostFormComponent implements OnInit {
categoryid: this.categoryid categoryid: this.categoryid
}); });
// Adds post to database and changes page afterwards if (this.id) {
this.postService.addPost(newPost).then(status => { // Updates post with id in database and changes page afterwards
console.log("Post was added: " + status); this.postService.updatePost(this.id, newPost).then(status => {
this.router.navigateByUrl("/"); console.log("Post was added: " + status);
this.router.navigateByUrl("/annonse");
}).catch(error => {
console.log("Error adding post: " + error);
});
} else {
// Adds post to database and changes page afterwards
this.postService.addPost(newPost).then(status => {
console.log("Post was added: " + status);
this.router.navigateByUrl("/annonse");
}).catch(error => {
console.log("Error adding post: " + error);
});
}
}
}
/**
* Deletes post in database and navigates to post list.
* Post can only be deleted if we are updating, not adding.
*/
deletePost() {
if (this.id) {
this.postService.deletePost(this.id).then(data => {
console.log("Successfully deleted post: " + this.id);
this.router.navigateByUrl("/annonse");
}).catch(error => { }).catch(error => {
console.log("Error adding post: " + error); console.log(error);
}); });
} }
} }
......
import { HttpClientTestingModule } from '@angular/common/http/testing';
import { ComponentFixture, TestBed } from '@angular/core/testing'; import { ComponentFixture, TestBed } from '@angular/core/testing';
import { PostListComponent } from './post-list.component'; import { PostListComponent } from './post-list.component';
...@@ -8,7 +9,8 @@ describe('PostListComponent', () => { ...@@ -8,7 +9,8 @@ describe('PostListComponent', () => {
beforeEach(async () => { beforeEach(async () => {
await TestBed.configureTestingModule({ await TestBed.configureTestingModule({
declarations: [ PostListComponent ] declarations: [ PostListComponent ],
imports: [ HttpClientTestingModule ]
}) })
.compileComponents(); .compileComponents();
}); });
......
import { ComponentFixture, TestBed } from '@angular/core/testing'; import { ComponentFixture, TestBed } from '@angular/core/testing';
import { RouterTestingModule } from '@angular/router/testing';
import { PostThumbnailComponent } from './post-thumbnail.component'; import { PostThumbnailComponent } from './post-thumbnail.component';
...@@ -8,7 +9,8 @@ describe('PostThumbnailComponent', () => { ...@@ -8,7 +9,8 @@ describe('PostThumbnailComponent', () => {
beforeEach(async () => { beforeEach(async () => {
await TestBed.configureTestingModule({ await TestBed.configureTestingModule({
declarations: [ PostThumbnailComponent ] declarations: [ PostThumbnailComponent ],
imports: [ RouterTestingModule ]
}) })
.compileComponents(); .compileComponents();
}); });
......
...@@ -176,5 +176,89 @@ describe('PostService', () => { ...@@ -176,5 +176,89 @@ describe('PostService', () => {
req.error(new ErrorEvent("400")); req.error(new ErrorEvent("400"));
}); });
}); });
describe('deletePost', () => {
it('should delete post', () => {
// Deletes post with id = 2
service.deletePost(2)
.then(data => {})
.catch(error => {
fail();
});
// Mocks and checks HTTP request
const req = httpMock.expectOne("api/post/2");
expect(req.request.method).toBe("DELETE");
req.flush({
data: []
});
});
it('should reject on http error', () => {
// Deletes post with id = 5, but should catch HTTP error
service.deletePost(5).then(data => {
fail();
}).catch(error => {});
// Mocks and checks HTTP request
const req = httpMock.expectOne("api/post/5");
expect(req.request.method).toBe("DELETE");
req.error(new ErrorEvent("400"));
});
});
describe('updatePost', () => {
it('should update post', () => {
let post = new Post({
id: 2,
title: "Test",
description: "TestDescription",
timestamp: 23947298,
owner: "user",
imageUrl: null,
price: 49,
categoryid: 2
});
// Updates post with id = 2
service.updatePost(2, post)
.then(data => {})
.catch(error => {
fail();
});
// Mocks and checks HTTP request
const req = httpMock.expectOne("api/post/2");
expect(req.request.method).toBe("PUT");
req.flush({
data: []
});
});
it('should reject on http error', () => {
let post = new Post({
id: 2,
title: "Test",
description: "TestDescription",
timestamp: 23947298,
owner: "user",
imageUrl: null,
price: 49,
categoryid: 2
});
// Updates post with id = 2, but should catch HTTP error
service.updatePost(2, post).then(data => {
fail();
}).catch(error => {});
// Mocks and checks HTTP request
const req = httpMock.expectOne("api/post/2");
expect(req.request.method).toBe("PUT");
req.error(new ErrorEvent("400"));
});
});
}); });
...@@ -11,6 +11,8 @@ export class PostService { ...@@ -11,6 +11,8 @@ export class PostService {
postUrl = "api/post/"; postUrl = "api/post/";
categoryUrl = "api/category/"; categoryUrl = "api/category/";
categories: Array<Category>;
constructor(private http: HttpClient) { } constructor(private http: HttpClient) { }
/** /**
...@@ -110,6 +112,11 @@ export class PostService { ...@@ -110,6 +112,11 @@ export class PostService {
getAllCategories(): Promise<Array<Category>>{ getAllCategories(): Promise<Array<Category>>{
return new Promise<Array<Category>>( return new Promise<Array<Category>>(
(resolve, reject) => { (resolve, reject) => {
if (this.categories) {
resolve(this.categories);
return;
}
this.get_all_categories().subscribe((data: any) => { this.get_all_categories().subscribe((data: any) => {
try { try {
let outputCategories = []; let outputCategories = [];
...@@ -122,6 +129,8 @@ export class PostService { ...@@ -122,6 +129,8 @@ export class PostService {
return; return;
} }
} }
this.categories = outputCategories;
resolve(outputCategories); resolve(outputCategories);
} catch (err: any){ } catch (err: any){
reject(err); reject(err);
...@@ -138,4 +147,54 @@ export class PostService { ...@@ -138,4 +147,54 @@ export class PostService {
private get_all_categories() { private get_all_categories() {
return this.http.get(this.categoryUrl); return this.http.get(this.categoryUrl);
} }
/**
* Delete post in database by id.
*/
deletePost(id: number): Promise<any> {
return new Promise<any>(
(resolve, reject) => {
this.delete_post(id).subscribe((data: any) => {
try {
resolve(data);
} catch (err: any) {
reject(err);
}
},
(err: any) => {
console.log(err.message);
reject(err);
});
}
);
}
private delete_post(id: number) {
return this.http.delete(this.postUrl + id);
}
/**
* Update post in database by id.
*/
updatePost(id: number, post: Post): Promise<any> {
return new Promise<any>(
(resolve, reject) => {
this.update_post(id, post).subscribe((data: any) => {
try {
resolve(data);
} catch (err: any) {
reject(err);
}
},
(err: any) => {
console.log(err.message);
reject(err);
});
}
);
}
private update_post(id: number, post: Post) {
return this.http.put(this.postUrl + id, post.serialize());
}
} }
...@@ -14,23 +14,26 @@ router.route("/").post(async (request: Request, response: Response) => { ...@@ -14,23 +14,26 @@ router.route("/").post(async (request: Request, response: Response) => {
const { const {
title, title,
description, description,
price,
timestamp, timestamp,
owner, owner,
category, categoryid,
imageUrl, imageUrl,
} = request.body; } = request.body;
try { try {
const post: IPost = { const post: IPost = {
title: title, title: title,
description: description, description: description,
price: price,
timestamp: timestamp, timestamp: timestamp,
owner: owner, owner: owner,
categoryid: category, categoryid: categoryid,
imageUrl: imageUrl, imageUrl: imageUrl,
}; };
if (Object.values(post).filter((p) => p == undefined).length > 0) if (Object.values(post).filter((p) => p == undefined).length > 0)
return response.status(500).send("Error"); return response.status(500).send("Error");
const input = `INSERT INTO post(title, description, timestamp, owner, category, imageUrl) VALUES (?,?,?,?,?,?)`; const input = `INSERT INTO post(title, description, price, timestamp, owner, categoryid, imageUrl) VALUES (?,?,?,?,?,?,?)`;
return response.status(200).json(await query(input, Object.values(post))); return response.status(200).json(await query(input, Object.values(post)));
} catch (error) { } catch (error) {
return response.status(400).send("Bad Request"); return response.status(400).send("Bad Request");
...@@ -42,9 +45,8 @@ router.route("/").post(async (request: Request, response: Response) => { ...@@ -42,9 +45,8 @@ router.route("/").post(async (request: Request, response: Response) => {
router.route("/").get(async (request: Request, response: Response) => { router.route("/").get(async (request: Request, response: Response) => {
const { categoryid } = request.query as { [key: string]: string }; const { categoryid } = request.query as { [key: string]: string };
try { try {
let input = `SELECT p.id, p.title, p.description, p.timestamp, p.owner, category.name, p.imageUrl let input = `SELECT p.id, p.title, p.description, p.price, p.timestamp, p.owner, p.categoryid, p.imageUrl
FROM post as p FROM post as p`;
INNER JOIN category ON category.categoryid = p.categoryid`;
if (categoryid) input += ` WHERE p.categoryid=${categoryid}`; if (categoryid) input += ` WHERE p.categoryid=${categoryid}`;
response.status(200).json(await query(input, "")); response.status(200).json(await query(input, ""));
} catch (error) { } catch (error) {
...@@ -56,9 +58,9 @@ router.route("/").get(async (request: Request, response: Response) => { ...@@ -56,9 +58,9 @@ router.route("/").get(async (request: Request, response: Response) => {
router.route("/:id").get(async (request: Request, response: Response) => { router.route("/:id").get(async (request: Request, response: Response) => {
const postId: string = request.params.id as string; const postId: string = request.params.id as string;
try { try {
const input = `SELECT p.id, p.title, p.description, p.timestamp, p.owner, category.name, p.imageUrl const input = `SELECT p.id, p.title, p.description, p.price, p.timestamp, p.owner, p.categoryid, p.imageUrl
FROM post as p FROM post as p
INNER JOIN category ON category.categoryid = p.categoryid WHERE p.id=?;`; WHERE p.id=?;`;
response.status(200).json(await query(input, [postId])); response.status(200).json(await query(input, [postId]));
} catch (error) { } catch (error) {
response.status(400).send("Bad Request"); response.status(400).send("Bad Request");
...@@ -69,10 +71,29 @@ router.route("/:id").get(async (request: Request, response: Response) => { ...@@ -69,10 +71,29 @@ router.route("/:id").get(async (request: Request, response: Response) => {
// Edit post with id `/api/post/:id` // Edit post with id `/api/post/:id`
router.route("/:id").put(async (request: Request, response: Response) => { router.route("/:id").put(async (request: Request, response: Response) => {
const postId: string = request.params.id as string; const postId: string = request.params.id as string;
const {
title,
description,
price,
timestamp,
owner,
categoryid,
imageUrl,
} = request.body;
try { try {
const post: IPost = {
title: title,
description: description,
price: price,
timestamp: timestamp,
owner: owner,
categoryid: categoryid,
imageUrl: imageUrl,
};
response response
.status(200) .status(200)
.json(await query("SELECT * FROM post WHERE id=?;", [postId])); .json(await query("UPDATE post SET title=?, description=?, price=?, timestamp=?, categoryid=?, imageUrl=? WHERE id=?;", [title, description, price, timestamp, categoryid, imageUrl, postId]));
} catch (error) { } catch (error) {
response.status(400).send("Bad Request"); response.status(400).send("Bad Request");
} }
...@@ -85,7 +106,7 @@ router.route("/:id").delete(async (request: Request, response: Response) => { ...@@ -85,7 +106,7 @@ router.route("/:id").delete(async (request: Request, response: Response) => {
try { try {
response response
.status(200) .status(200)
.json(await query("SELECT * FROM post WHERE id=?;", [postId])); .json(await query("DELETE FROM post WHERE id=?;", [postId]));
} catch (error) { } catch (error) {
response.status(400).send("Bad Request"); response.status(400).send("Bad Request");
} }
......
...@@ -2,6 +2,7 @@ ...@@ -2,6 +2,7 @@
interface IPost { interface IPost {
title: string; title: string;
description: string; description: string;
price: string;
timestamp: number; timestamp: number;
owner: string; owner: string;
categoryid: number; categoryid: number;
......
0% Loading or .
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment