Skip to content
Snippets Groups Projects
Commit 3254b38b authored by Martin Immanuel Burgos's avatar Martin Immanuel Burgos
Browse files

Merge branch '11-redigere-annonse' into 'master'

Resolve "Redigere annonse"

Closes #11

See merge request !15
parents 7ea14179 53042f18
No related branches found
No related tags found
2 merge requests!15Resolve "Redigere annonse",!10Issue: Post can be deleted (#10)
Showing
with 354 additions and 35 deletions
......@@ -6,6 +6,7 @@ import { PostListComponent } from './posts/post-list/post-list.component';
const routes: Routes = [
{ path: 'annonse/ny', component: PostFormComponent },
{ path: 'annonse/rediger/:id', component: PostFormComponent },
{ path: 'annonse', component: PostListComponent },
{ path: 'annonse/:id', component: PostDetailsComponent }
];
......
......@@ -5,3 +5,6 @@
<br>
<p>Publiseringstidspunkt: {{post.getTimestamp}}</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 { 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';
describe('PostDetailsComponent', () => {
let component: PostDetailsComponent;
let fixture: ComponentFixture<PostDetailsComponent>;
let mockPostService;
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({
declarations: [ PostDetailsComponent ]
declarations: [ PostDetailsComponent ],
imports: [ HttpClientTestingModule, RouterTestingModule ],
providers: [
{ provide: ActivatedRoute, useValue: { snapshot: {params: {id: 5}}}},
{ provide: PostService, useValue: mockPostService }
]
})
.compileComponents();
});
......@@ -22,4 +57,29 @@ describe('PostDetailsComponent', () => {
it('should create', () => {
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 { Post } from 'src/app/models/post.model';
import { PostService } from '../post.service';
import { ActivatedRoute } from '@angular/router'
import { ActivatedRoute, Router } from '@angular/router'
@Component({
selector: 'app-post-details',
......@@ -12,19 +12,35 @@ export class PostDetailsComponent implements OnInit {
post: Post = new Post();
constructor(private postService: PostService, private activatedRoute: ActivatedRoute) { }
constructor(private postService: PostService, private activatedRoute: ActivatedRoute, private router: Router) { }
ngOnInit(): void {
// Gets id parameter from URL
this.activatedRoute.params.subscribe(params => {
const id = params["id"];
const id = this.activatedRoute.snapshot.params["id"];
// Gets Post with id from database
this.postService.getPost(id).then(post => {
this.post = post;
}).catch(error => {
console.log(error);
});
// Gets Post with id from database
this.postService.getPost(id).then(post => {
this.post = post;
}).catch(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">
<h3>Lag annonse</h3>
<h3>{{id ? 'Rediger annonse' : 'Lag annonse'}}</h3>
<app-text-input [(inputModel)]="title" label="Tittel" (blur)="checkForm()"></app-text-input>
......@@ -8,8 +8,8 @@
<app-number-input [(inputModel)]="price" label="Pris" (blur)="checkForm()"></app-number-input>
<app-select [(inputModel)]="categoryid" (change)="checkForm()" label="Kategori">
<option value="0" selected hidden>Velg kategori</option>
<option *ngFor="let category of categories" [value]="category.getCategoryId">{{category.getName}}</option>
<option value="0" [selected]="categoryid == 0" hidden>Velg kategori</option>
<option *ngFor="let category of categories" [value]="category.getCategoryId" [selected]="categoryid == category.getCategoryId">{{category.getName}}</option>
</app-select>
<app-text-input [(inputModel)]="imageUrl" label="Bilde URL" (blur)="showImage(imageUrl)"></app-text-input>
......@@ -18,4 +18,5 @@
<p>{{statusMessage}}</p>
<app-button (click)="publishPost()" text="Publiser"></app-button>
<app-button *ngIf="id" (click)="deletePost()" text="Slett annonse"></app-button>
</div>
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 { Router } from '@angular/router';
import { RouterTestingModule } from '@angular/router/testing';
......@@ -17,7 +17,7 @@ describe('PostFormComponent', () => {
beforeEach(async () => {
// PostService mock setup
mockPostService = jasmine.createSpyObj(['getAllCategories', 'addPost']);
mockPostService = jasmine.createSpyObj(['getAllCategories', 'addPost', 'deletePost']);
mockPostService.getAllCategories.and.returnValue(
new Promise<Array<Category>>(
(resolve) => {
......@@ -30,6 +30,12 @@ describe('PostFormComponent', () => {
resolve("success")
})
);
mockPostService.deletePost.and.returnValue(
new Promise<any>(
(resolve) => {
resolve({data: []})
})
);
await TestBed.configureTestingModule({
declarations: [ PostFormComponent ],
......@@ -109,4 +115,22 @@ describe('PostFormComponent', () => {
component.showImage("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 { Router } from '@angular/router';
import { ActivatedRoute, Router } from '@angular/router';
import { Category } from 'src/app/models/category.model';
import { Post } from 'src/app/models/post.model';
import { PostService } from '../post.service';
......@@ -11,6 +11,8 @@ import { PostService } from '../post.service';
})
export class PostFormComponent implements OnInit {
id = 0;
title: string = "";
description: string = "";
price: number = 0;
......@@ -22,9 +24,27 @@ export class PostFormComponent implements OnInit {
categories: Array<Category>;
constructor(private postService: PostService, private router: Router) { }
constructor(private postService: PostService, private router: Router, private activatedRoute: ActivatedRoute) { }
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
this.postService.getAllCategories().then(categories => {
this.categories = categories;
......@@ -77,12 +97,37 @@ export class PostFormComponent implements OnInit {
categoryid: this.categoryid
});
// Adds post to database and changes page afterwards
this.postService.addPost(newPost).then(status => {
console.log("Post was added: " + status);
this.router.navigateByUrl("/");
if (this.id) {
// Updates post with id in database and changes page afterwards
this.postService.updatePost(this.id, newPost).then(status => {
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 => {
console.log("Error adding post: " + error);
console.log(error);
});
}
}
......
import { HttpClientTestingModule } from '@angular/common/http/testing';
import { ComponentFixture, TestBed } from '@angular/core/testing';
import { PostListComponent } from './post-list.component';
......@@ -8,7 +9,8 @@ describe('PostListComponent', () => {
beforeEach(async () => {
await TestBed.configureTestingModule({
declarations: [ PostListComponent ]
declarations: [ PostListComponent ],
imports: [ HttpClientTestingModule ]
})
.compileComponents();
});
......
import { ComponentFixture, TestBed } from '@angular/core/testing';
import { RouterTestingModule } from '@angular/router/testing';
import { PostThumbnailComponent } from './post-thumbnail.component';
......@@ -8,7 +9,8 @@ describe('PostThumbnailComponent', () => {
beforeEach(async () => {
await TestBed.configureTestingModule({
declarations: [ PostThumbnailComponent ]
declarations: [ PostThumbnailComponent ],
imports: [ RouterTestingModule ]
})
.compileComponents();
});
......
......@@ -176,5 +176,89 @@ describe('PostService', () => {
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 {
postUrl = "api/post/";
categoryUrl = "api/category/";
categories: Array<Category>;
constructor(private http: HttpClient) { }
/**
......@@ -110,6 +112,11 @@ export class PostService {
getAllCategories(): Promise<Array<Category>>{
return new Promise<Array<Category>>(
(resolve, reject) => {
if (this.categories) {
resolve(this.categories);
return;
}
this.get_all_categories().subscribe((data: any) => {
try {
let outputCategories = [];
......@@ -122,6 +129,8 @@ export class PostService {
return;
}
}
this.categories = outputCategories;
resolve(outputCategories);
} catch (err: any){
reject(err);
......@@ -138,4 +147,54 @@ export class PostService {
private get_all_categories() {
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) => {
const {
title,
description,
price,
timestamp,
owner,
category,
categoryid,
imageUrl,
} = request.body;
try {
const post: IPost = {
title: title,
description: description,
price: price,
timestamp: timestamp,
owner: owner,
category: category,
categoryid: categoryid,
imageUrl: imageUrl,
};
if (Object.values(post).filter((p) => p == undefined).length > 0)
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)));
} catch (error) {
return response.status(400).send("Bad Request");
......@@ -42,9 +45,8 @@ router.route("/").post(async (request: Request, response: Response) => {
router.route("/").get(async (request: Request, response: Response) => {
const { categoryid } = request.query as { [key: string]: string };
try {
let input = `SELECT p.id, p.title, p.description, p.timestamp, p.owner, category.name, p.imageUrl
FROM post as p
INNER JOIN category ON category.categoryid = p.categoryid`;
let input = `SELECT p.id, p.title, p.description, p.price, p.timestamp, p.owner, p.categoryid, p.imageUrl
FROM post as p`;
if (categoryid) input += ` WHERE p.categoryid=${categoryid}`;
response.status(200).json(await query(input, ""));
} catch (error) {
......@@ -56,9 +58,9 @@ router.route("/").get(async (request: Request, response: Response) => {
router.route("/:id").get(async (request: Request, response: Response) => {
const postId: string = request.params.id as string;
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
INNER JOIN category ON category.categoryid = p.categoryid WHERE p.id=?;`;
WHERE p.id=?;`;
response.status(200).json(await query(input, [postId]));
} catch (error) {
response.status(400).send("Bad Request");
......@@ -69,10 +71,29 @@ router.route("/:id").get(async (request: Request, response: Response) => {
// Edit post with id `/api/post/:id`
router.route("/:id").put(async (request: Request, response: Response) => {
const postId: string = request.params.id as string;
const {
title,
description,
price,
timestamp,
owner,
categoryid,
imageUrl,
} = request.body;
try {
const post: IPost = {
title: title,
description: description,
price: price,
timestamp: timestamp,
owner: owner,
categoryid: categoryid,
imageUrl: imageUrl,
};
response
.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) {
response.status(400).send("Bad Request");
}
......@@ -85,7 +106,7 @@ router.route("/:id").delete(async (request: Request, response: Response) => {
try {
response
.status(200)
.json(await query("SELECT * FROM post WHERE id=?;", [postId]));
.json(await query("DELETE FROM post WHERE id=?;", [postId]));
} catch (error) {
response.status(400).send("Bad Request");
}
......
......@@ -2,6 +2,7 @@
interface IPost {
title: string;
description: string;
price: string;
timestamp: number;
owner: string;
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