diff --git a/client/src/app/app-routing.module.ts b/client/src/app/app-routing.module.ts index 15fb2c5826cf6db2157224957e954c5df0894cfe..498abb33f3b3e938c1e3b78f45a9d964f42b35d2 100644 --- a/client/src/app/app-routing.module.ts +++ b/client/src/app/app-routing.module.ts @@ -1,9 +1,13 @@ import { NgModule } from '@angular/core'; import { RouterModule, Routes } from '@angular/router'; +import { PostDetailsComponent } from './posts/post-details/post-details.component'; import { PostFormComponent } from './posts/post-form/post-form.component'; +import { PostListComponent } from './posts/post-list/post-list.component'; const routes: Routes = [ - { path: 'annonse/ny', component: PostFormComponent } + { path: 'annonse/ny', component: PostFormComponent }, + { path: 'annonse', component: PostListComponent }, + { path: 'annonse/:id', component: PostDetailsComponent } ]; @NgModule({ diff --git a/client/src/app/app.component.html b/client/src/app/app.component.html index 60ff33247e03dad1c41683451345750984ae608b..dda8c3ef3dbb62485c8a275bad07e7f2d5e106ab 100644 --- a/client/src/app/app.component.html +++ b/client/src/app/app.component.html @@ -1,3 +1,12 @@ -<h1>SellPoint</h1> +<div class="navbar"> + <a class="logo" href="/">SELLPOINT</a> + <nav> + <a href="/">/</a> + <a href="/annonse">/annonse</a> + <a href="/annonse/ny">/annonse/ny</a> + </nav> +</div> -<router-outlet></router-outlet> \ No newline at end of file +<div class="wrapper"> + <router-outlet></router-outlet> +</div> diff --git a/client/src/app/app.component.scss b/client/src/app/app.component.scss index e69de29bb2d1d6434b8b29ae775ad8c2e48c5391..ae8ad49fdcdba6c323fef0198ad7fc773b3e656f 100644 --- a/client/src/app/app.component.scss +++ b/client/src/app/app.component.scss @@ -0,0 +1,25 @@ +div.navbar { + background-color: #666; + display: flex; + justify-content: space-between; + align-items: center; + height: 70px; + + a.logo { + padding: 10px; + background-color: white; + width: 200px; + margin-left: 10px; + } + nav { + a{ + padding: 10px; + margin: 10px; + background-color: white; + } + } +} + +div.wrapper { + padding: 10px; +} diff --git a/client/src/app/models/category.model.ts b/client/src/app/models/category.model.ts new file mode 100644 index 0000000000000000000000000000000000000000..e24c400101e3c498d51cf9def9b721308c05e871 --- /dev/null +++ b/client/src/app/models/category.model.ts @@ -0,0 +1,45 @@ +import { Deserializable } from "./deserializable.model"; +import { Serializable } from "./serializable.model"; + +export class Category implements Deserializable, Serializable { + private categoryid: number; + private name: string; + + constructor(input: any = null) { + if (input) { + this.deserialize(input); + } else { + this.categoryid = 0; + this.name = null; + } + } + + deserialize(input: Object): this { + Object.assign(this, input); + return this; + } + + serialize(): Object { + return { + categoryid: this.categoryid, + name: this.name + }; + } + + get getCategoryId() { + return this.categoryid; + } + + set setCategoryId(categoryid: number) { + this.categoryid = categoryid; + } + + get getName() { + return this.name; + } + + set setName(name: string) { + this.name = name; + } + +} \ No newline at end of file diff --git a/client/src/app/models/post.model.ts b/client/src/app/models/post.model.ts index 3e4c006cb7243a96675b70a20b0623a594870703..fcf5e849bd889d4fa77683b0569df936e20a88bd 100644 --- a/client/src/app/models/post.model.ts +++ b/client/src/app/models/post.model.ts @@ -13,14 +13,7 @@ export class Post implements Deserializable, Serializable { constructor(input: any = null) { if (input) { - this.id = input.id; - this.title = input.title; - this.description = input.description; - this.timestamp = input.timestamp; - this.owner = input.owner; - this.imageUrl = input.imageUrl; - this.price = input.price; - this.categoryid = input.categoryid; + this.deserialize(input); } else { this.id = 0; this.title = null; diff --git a/client/src/app/posts/post-details/post-details.component.html b/client/src/app/posts/post-details/post-details.component.html new file mode 100644 index 0000000000000000000000000000000000000000..82d2c974c54aecb3fb5fa36438205f2b5edb0f36 --- /dev/null +++ b/client/src/app/posts/post-details/post-details.component.html @@ -0,0 +1,7 @@ +<h3>Tittel: {{post.getTitle}}</h3> + +<img [src]="post.getImageUrl"> +<p>Beskrivelse: {{post.getDescription}}</p> +<br> +<p>Publiseringstidspunkt: {{post.getTimestamp}}</p> +<p>Eier: {{post.getOwner}}</p> diff --git a/client/src/app/posts/post-details/post-details.component.scss b/client/src/app/posts/post-details/post-details.component.scss new file mode 100644 index 0000000000000000000000000000000000000000..e69de29bb2d1d6434b8b29ae775ad8c2e48c5391 diff --git a/client/src/app/posts/post-details/post-details.component.spec.ts b/client/src/app/posts/post-details/post-details.component.spec.ts new file mode 100644 index 0000000000000000000000000000000000000000..90f92b94c0e46f064508801e1d57403a5fb1c26d --- /dev/null +++ b/client/src/app/posts/post-details/post-details.component.spec.ts @@ -0,0 +1,25 @@ +import { ComponentFixture, TestBed } from '@angular/core/testing'; + +import { PostDetailsComponent } from './post-details.component'; + +describe('PostDetailsComponent', () => { + let component: PostDetailsComponent; + let fixture: ComponentFixture<PostDetailsComponent>; + + beforeEach(async () => { + await TestBed.configureTestingModule({ + declarations: [ PostDetailsComponent ] + }) + .compileComponents(); + }); + + beforeEach(() => { + fixture = TestBed.createComponent(PostDetailsComponent); + component = fixture.componentInstance; + fixture.detectChanges(); + }); + + it('should create', () => { + expect(component).toBeTruthy(); + }); +}); diff --git a/client/src/app/posts/post-details/post-details.component.ts b/client/src/app/posts/post-details/post-details.component.ts new file mode 100644 index 0000000000000000000000000000000000000000..72dd9a89f30421c3ba8c25a6a8feeeb687ee87e6 --- /dev/null +++ b/client/src/app/posts/post-details/post-details.component.ts @@ -0,0 +1,30 @@ +import { Component, OnInit } from '@angular/core'; +import { Post } from 'src/app/models/post.model'; +import { PostService } from '../post.service'; +import { ActivatedRoute } from '@angular/router' + +@Component({ + selector: 'app-post-details', + templateUrl: './post-details.component.html', + styleUrls: ['./post-details.component.scss'] +}) +export class PostDetailsComponent implements OnInit { + + post: Post = new Post(); + + constructor(private postService: PostService, private activatedRoute: ActivatedRoute) { } + + ngOnInit(): void { + // Gets id parameter from URL + this.activatedRoute.params.subscribe(params => { + const id = params["id"]; + + // Gets Post with id from database + this.postService.getPost(id).then(post => { + this.post = post; + }).catch(error => { + console.log(error); + }); + }); + } +} diff --git a/client/src/app/posts/post-form/post-form.component.html b/client/src/app/posts/post-form/post-form.component.html index e89803521b22e8a339b97868c51233890838d3f4..0ea8224c49c7c4c83cf7cfda58a8dc9fecf79b2a 100644 --- a/client/src/app/posts/post-form/post-form.component.html +++ b/client/src/app/posts/post-form/post-form.component.html @@ -1,18 +1,21 @@ -<h3>Lag annonse</h3> +<div class="postform"> + <h3>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> -<app-text-input [(inputModel)]="description" label="Beskrivelse" (blur)="checkForm()"></app-text-input> + <app-text-input [(inputModel)]="description" label="Beskrivelse" (blur)="checkForm()"></app-text-input> -<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"> - <option value="0" selected>Velg kategori</option> - <option value="1">Antikviteter og Kunst</option> - <option value="2">Dyr og Utstyr</option> - <option value="3">Elektronikk og Hvitevarer</option> -</app-select> + <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> + </app-select> -<p>{{statusMessage}}</p> + <app-text-input [(inputModel)]="imageUrl" label="Bilde URL" (blur)="showImage(imageUrl)"></app-text-input> + <img [src]="displayImageUrl" (error)="showImage('https://i.stack.imgur.com/y9DpT.jpg')"> -<app-button (click)="publishPost()" text="Publiser"></app-button> \ No newline at end of file + <p>{{statusMessage}}</p> + + <app-button (click)="publishPost()" text="Publiser"></app-button> +</div> diff --git a/client/src/app/posts/post-form/post-form.component.scss b/client/src/app/posts/post-form/post-form.component.scss index e69de29bb2d1d6434b8b29ae775ad8c2e48c5391..26460c8a9adbeda02916bc0ca5f0f1e0549600c5 100644 --- a/client/src/app/posts/post-form/post-form.component.scss +++ b/client/src/app/posts/post-form/post-form.component.scss @@ -0,0 +1,4 @@ +div.postform { + display: flex; + flex-direction: column; +} diff --git a/client/src/app/posts/post-form/post-form.component.spec.ts b/client/src/app/posts/post-form/post-form.component.spec.ts index 7e82535b45a1a2c9cfa883b910a956bd3dc4ff89..31c4d5b8657d2bc3ea4af3df65d67e7624b33d83 100644 --- a/client/src/app/posts/post-form/post-form.component.spec.ts +++ b/client/src/app/posts/post-form/post-form.component.spec.ts @@ -3,6 +3,9 @@ import { ComponentFixture, fakeAsync, TestBed, tick } from '@angular/core/testin import { FormsModule } from '@angular/forms'; import { Router } from '@angular/router'; import { RouterTestingModule } from '@angular/router/testing'; +import { Category } from 'src/app/models/category.model'; +import { SharedModule } from 'src/app/shared/shared.module'; +import { PostService } from '../post.service'; import { PostFormComponent } from './post-form.component'; @@ -10,11 +13,28 @@ describe('PostFormComponent', () => { let component: PostFormComponent; let fixture: ComponentFixture<PostFormComponent>; let router: Router; + let mockPostService; beforeEach(async () => { + // PostService mock setup + mockPostService = jasmine.createSpyObj(['getAllCategories', 'addPost']); + mockPostService.getAllCategories.and.returnValue( + new Promise<Array<Category>>( + (resolve) => { + resolve([new Category({categoryid: 1, name: "Elektronikk"}), new Category({categoryid: 2, name: "Bil"})]) + }) + ); + mockPostService.addPost.and.returnValue( + new Promise<string>( + (resolve) => { + resolve("success") + }) + ); + await TestBed.configureTestingModule({ declarations: [ PostFormComponent ], - imports: [ HttpClientTestingModule, RouterTestingModule, FormsModule ] + imports: [ HttpClientTestingModule, RouterTestingModule, FormsModule, SharedModule ], + providers: [ { provide: PostService, useValue: mockPostService } ] }) .compileComponents(); }); @@ -27,11 +47,20 @@ describe('PostFormComponent', () => { router = TestBed.inject(Router); }); - it('should create', () => { + it('should create and get all categories', async () => { expect(component).toBeTruthy(); + + // Waits for ngOnInit and checks that we get categories + fixture.whenStable().then(() => { + expect(mockPostService.getAllCategories).toHaveBeenCalled(); + expect(component.categories.length).toBe(2); + expect(component.categories[0].getCategoryId).toBe(1); + expect(component.categories[1].getName).toBe("Bil"); + }); }); it('should validate form', () => { + // Tests all if-sentences in checkForm expect(component.checkForm()).toBeFalse(); expect(component.statusMessage).toBe("Tittelen kan ikke være tom"); @@ -58,17 +87,26 @@ describe('PostFormComponent', () => { }); it('should stop publishing invalid post', fakeAsync(() => { + // Tests that publishing should be stopped on invalid post component.publishPost(); expect(component.statusMessage).toBe("Tittelen kan ikke være tom"); })); it('should route after publishing post', () => { + // Tests that url is changed after post is published component.title = "Title"; component.description = "Description"; component.price = 50; component.categoryid = 2; component.publishPost(); - + + expect(mockPostService.addPost).toHaveBeenCalled(); expect(router.url).toBe('/'); }); + + it('should show image', () => { + // Tests that image is updated with new URL + component.showImage("test"); + expect(component.displayImageUrl).toBe("test"); + }); }); diff --git a/client/src/app/posts/post-form/post-form.component.ts b/client/src/app/posts/post-form/post-form.component.ts index 5864463a491b3e0ac1ee152b7db60a0017e1b836..14be51a44f2ec8bb6ae959d52e81e657037a65db 100644 --- a/client/src/app/posts/post-form/post-form.component.ts +++ b/client/src/app/posts/post-form/post-form.component.ts @@ -1,5 +1,6 @@ import { Component, OnInit } from '@angular/core'; import { 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'; @@ -14,12 +15,22 @@ export class PostFormComponent implements OnInit { description: string = ""; price: number = 0; categoryid: number = 0; + imageUrl: string; + displayImageUrl: string; statusMessage: string = ""; + categories: Array<Category>; + constructor(private postService: PostService, private router: Router) { } ngOnInit() { + // Gets all categories and displays them in dropdown + this.postService.getAllCategories().then(categories => { + this.categories = categories; + }).catch (error => { + console.log("Error adding catrgories:" + error); + }); } /** @@ -61,11 +72,12 @@ export class PostFormComponent implements OnInit { description: this.description, timestamp: new Date(), owner: "admin", - imageUrl: "", + imageUrl: this.imageUrl, price: this.price, 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("/"); @@ -75,10 +87,17 @@ export class PostFormComponent implements OnInit { } } + /** + * Sets the image source to be the url. + */ + showImage(url) { + this.displayImageUrl = url; + } + /** * Sets a status message. */ - setStatusMessage(message: string){ + setStatusMessage(message: string) { this.statusMessage = message; } } \ No newline at end of file diff --git a/client/src/app/posts/post-list/post-list.component.html b/client/src/app/posts/post-list/post-list.component.html new file mode 100644 index 0000000000000000000000000000000000000000..49aa9a1fd89cf255bf9400e1339c1ad261950e86 --- /dev/null +++ b/client/src/app/posts/post-list/post-list.component.html @@ -0,0 +1,3 @@ +<div> + <app-post-thumbnail *ngFor="let post of allPosts" [post]="post"></app-post-thumbnail> +</div> diff --git a/client/src/app/posts/post-list/post-list.component.scss b/client/src/app/posts/post-list/post-list.component.scss new file mode 100644 index 0000000000000000000000000000000000000000..e69de29bb2d1d6434b8b29ae775ad8c2e48c5391 diff --git a/client/src/app/posts/post-list/post-list.component.spec.ts b/client/src/app/posts/post-list/post-list.component.spec.ts new file mode 100644 index 0000000000000000000000000000000000000000..2d39c24944619ae92c499e826d476de93f005a4c --- /dev/null +++ b/client/src/app/posts/post-list/post-list.component.spec.ts @@ -0,0 +1,25 @@ +import { ComponentFixture, TestBed } from '@angular/core/testing'; + +import { PostListComponent } from './post-list.component'; + +describe('PostListComponent', () => { + let component: PostListComponent; + let fixture: ComponentFixture<PostListComponent>; + + beforeEach(async () => { + await TestBed.configureTestingModule({ + declarations: [ PostListComponent ] + }) + .compileComponents(); + }); + + beforeEach(() => { + fixture = TestBed.createComponent(PostListComponent); + component = fixture.componentInstance; + fixture.detectChanges(); + }); + + it('should create', () => { + expect(component).toBeTruthy(); + }); +}); diff --git a/client/src/app/posts/post-list/post-list.component.ts b/client/src/app/posts/post-list/post-list.component.ts new file mode 100644 index 0000000000000000000000000000000000000000..2bd3302dcdd9662c616bd358e90b1e1f7f72240f --- /dev/null +++ b/client/src/app/posts/post-list/post-list.component.ts @@ -0,0 +1,25 @@ +import { Component, OnInit } from '@angular/core'; +import { Post } from 'src/app/models/post.model'; +import { PostService } from '../post.service'; + +@Component({ + selector: 'app-post-list', + templateUrl: './post-list.component.html', + styleUrls: ['./post-list.component.scss'] +}) +export class PostListComponent implements OnInit { + + allPosts: Array<Post> = [] + + constructor(private postService: PostService) { } + + ngOnInit(): void { + // Gets all posts from database, and displays them + this.postService.getAllPosts().then(posts => { + this.allPosts = posts; + }).catch(error => { + console.log(error); + }); + } + +} diff --git a/client/src/app/posts/post-thumbnail/post-thumbnail.component.html b/client/src/app/posts/post-thumbnail/post-thumbnail.component.html new file mode 100644 index 0000000000000000000000000000000000000000..5dc53d6c2b01a049a1cc19fcadd92df48e20c2fa --- /dev/null +++ b/client/src/app/posts/post-thumbnail/post-thumbnail.component.html @@ -0,0 +1,3 @@ +<div class="postthumb"> + <a href="javascript:void(0)"(click)="goToPost()">{{post.getTitle}}</a> +</div> diff --git a/client/src/app/posts/post-thumbnail/post-thumbnail.component.scss b/client/src/app/posts/post-thumbnail/post-thumbnail.component.scss new file mode 100644 index 0000000000000000000000000000000000000000..7be5993b0afb5d440f9cfafc34bb0c66e9b80fa7 --- /dev/null +++ b/client/src/app/posts/post-thumbnail/post-thumbnail.component.scss @@ -0,0 +1,5 @@ +div.postthumb { + padding: 10px; + margin: 10px; + background-color: rgba(0,0,0,.1); +} diff --git a/client/src/app/posts/post-thumbnail/post-thumbnail.component.spec.ts b/client/src/app/posts/post-thumbnail/post-thumbnail.component.spec.ts new file mode 100644 index 0000000000000000000000000000000000000000..ea7282db0c1f44a777d0bbf58a7ca927de9d6fd7 --- /dev/null +++ b/client/src/app/posts/post-thumbnail/post-thumbnail.component.spec.ts @@ -0,0 +1,25 @@ +import { ComponentFixture, TestBed } from '@angular/core/testing'; + +import { PostThumbnailComponent } from './post-thumbnail.component'; + +describe('PostThumbnailComponent', () => { + let component: PostThumbnailComponent; + let fixture: ComponentFixture<PostThumbnailComponent>; + + beforeEach(async () => { + await TestBed.configureTestingModule({ + declarations: [ PostThumbnailComponent ] + }) + .compileComponents(); + }); + + beforeEach(() => { + fixture = TestBed.createComponent(PostThumbnailComponent); + component = fixture.componentInstance; + fixture.detectChanges(); + }); + + it('should create', () => { + expect(component).toBeTruthy(); + }); +}); diff --git a/client/src/app/posts/post-thumbnail/post-thumbnail.component.ts b/client/src/app/posts/post-thumbnail/post-thumbnail.component.ts new file mode 100644 index 0000000000000000000000000000000000000000..207940e9aa3d6c09bafe45cef88daa7e9050baaa --- /dev/null +++ b/client/src/app/posts/post-thumbnail/post-thumbnail.component.ts @@ -0,0 +1,23 @@ +import { Component, OnInit, Input } from '@angular/core'; +import { Router } from '@angular/router'; +import { Post } from 'src/app/models/post.model'; + +@Component({ + selector: 'app-post-thumbnail', + templateUrl: './post-thumbnail.component.html', + styleUrls: ['./post-thumbnail.component.scss'] +}) +export class PostThumbnailComponent implements OnInit { + + @Input() + post: Post = new Post(); + + constructor(private router: Router) { } + + ngOnInit(): void { + } + + goToPost() { + this.router.navigateByUrl("annonse/" + this.post.getId); + } +} diff --git a/client/src/app/posts/post.module.ts b/client/src/app/posts/post.module.ts index 5599cc30a3eb472c97bfcc9d6212aacfda387cb6..cb644f8fa2f88bad7645ba04604115aa406a2da0 100644 --- a/client/src/app/posts/post.module.ts +++ b/client/src/app/posts/post.module.ts @@ -3,12 +3,18 @@ import { CommonModule } from '@angular/common'; import { PostFormComponent } from './post-form/post-form.component'; import { SharedModule } from '../shared/shared.module'; import { FormsModule } from '@angular/forms'; +import { PostDetailsComponent } from './post-details/post-details.component'; +import { PostListComponent } from './post-list/post-list.component'; +import { PostThumbnailComponent } from './post-thumbnail/post-thumbnail.component'; @NgModule({ declarations: [ - PostFormComponent + PostFormComponent, + PostDetailsComponent, + PostListComponent, + PostThumbnailComponent ], imports: [ CommonModule, diff --git a/client/src/app/posts/post.service.spec.ts b/client/src/app/posts/post.service.spec.ts index 24c073bf45c74c0c777218e4220d8f29d1f7179b..0439fbe95c5eafe960d20c4e678c5672ab71c278 100644 --- a/client/src/app/posts/post.service.spec.ts +++ b/client/src/app/posts/post.service.spec.ts @@ -1,5 +1,6 @@ import { HttpClientTestingModule, HttpTestingController } from '@angular/common/http/testing'; import { TestBed } from '@angular/core/testing'; +import { Category } from '../models/category.model'; import { Post } from '../models/post.model'; import { PostService } from './post.service'; @@ -22,6 +23,7 @@ describe('PostService', () => { describe('getPost', () => { it('should get a post', () => { + // Gets post and checks values service.getPost(1).then(post => { expect(post.getId).toBe(1); expect(post.getTitle).toBe("Test"); @@ -29,6 +31,7 @@ describe('PostService', () => { fail(); }); + // Mocks and checks HTTP request const req = httpMock.expectOne("api/post/1"); expect(req.request.method).toBe("GET"); req.flush({ @@ -46,10 +49,12 @@ describe('PostService', () => { }); it('should reject on invalid post', () => { + // Gets invalid post, should go to catch service.getPost(2).then(post => { fail(); }).catch(error => {}); + // Mocks and checks HTTP request const req = httpMock.expectOne("api/post/2"); expect(req.request.method).toBe("GET"); req.flush({ @@ -63,10 +68,12 @@ describe('PostService', () => { }); it('should reject on http error', () => { + // Gets HTTP error instead of post, should catch service.getPost(2).then(post => { fail(); }).catch(error => {}); + // Mocks and checks HTTP request const req = httpMock.expectOne("api/post/2"); expect(req.request.method).toBe("GET"); req.error(new ErrorEvent("400")); @@ -86,12 +93,14 @@ describe('PostService', () => { categoryid: 2 }); + // Adds post service.addPost(post) .then(post => {}) .catch(error => { fail(); }); + // Mocks and checks HTTP request const req = httpMock.expectOne("api/post/"); expect(req.request.method).toBe("POST"); expect(req.request.body).toEqual(post.serialize()); @@ -114,15 +123,58 @@ describe('PostService', () => { categoryid: 2 }); + // Adds post, gets HTTP error, should catch service.addPost(post).then(post => { fail(); }).catch(error => {}); + // Mocks and checks HTTP request const req = httpMock.expectOne("api/post/"); expect(req.request.method).toBe("POST"); expect(req.request.body).toEqual(post.serialize()); req.error(new ErrorEvent("400")); }); }); + + describe('getAllCategories', () => { + it('should get categories', () => { + + // Gets all categories and checks values + service.getAllCategories() + .then(categories => { + expect(categories.length).toBe(3); + expect(categories[0] instanceof Category).toBeTrue(); + expect(categories[1].getCategoryId).toBe(2); + expect(categories[2].getName).toBe("Elektronikk"); + }) + .catch(error => { + fail(); + }); + + // Mocks and checks HTTP request + const req = httpMock.expectOne("api/category/"); + expect(req.request.method).toBe("GET"); + req.flush({ + data: [ + {categoryid: 1, name: "Dyr"}, + {categoryid: 2, name: "Bil"}, + {categoryid: 3, name: "Elektronikk"} + ] + }); + }); + + it('should reject on http error', () => { + + // Gets HTTP error, should catch + service.getAllCategories().then(categories => { + fail(); + }).catch(error => {}); + + // Mocks and checks HTTP request + const req = httpMock.expectOne("api/category/"); + expect(req.request.method).toBe("GET"); + req.error(new ErrorEvent("400")); + }); + }); }); diff --git a/client/src/app/posts/post.service.ts b/client/src/app/posts/post.service.ts index e476800c7e5c82b55cb3c2c5f4ad54c4bfe784c3..e28949586aaf3f30b7c0a8441aae4cc882aa2374 100644 --- a/client/src/app/posts/post.service.ts +++ b/client/src/app/posts/post.service.ts @@ -1,5 +1,6 @@ import { HttpClient } from '@angular/common/http'; import { Injectable } from '@angular/core'; +import { Category } from '../models/category.model'; import { Post } from '../models/post.model'; @Injectable({ @@ -8,9 +9,45 @@ import { Post } from '../models/post.model'; export class PostService { postUrl = "api/post/"; + categoryUrl = "api/category/"; constructor(private http: HttpClient) { } + /** + * Get all posts from database. + */ + getAllPosts(): Promise<Array<Post>> { + return new Promise<Array<Post>>( + (resolve, reject) => { + this.get_all_posts().subscribe((data: any) => { + try { + let outputPosts = []; + for (let post of data.data) { + outputPosts.push(new Post(post)); + + if (post.getId == 0) { + reject("Could not deserialize Post"); + return; + } + } + + resolve(outputPosts); + } catch (err: any) { + reject(err); + } + }, + (err: any) => { + console.log(err.message); + reject(err); + }); + } + ); + } + + private get_all_posts() { + return this.http.get(this.postUrl); + } + /** * Get post from database by id. */ @@ -19,8 +56,7 @@ export class PostService { (resolve, reject) => { this.get_post(id).subscribe((data: any) => { try { - const post = new Post(); - post.deserialize(data.data[0]); + const post = new Post(data.data[0]); if (post.getId == 0) { reject("Could not find Post with id: " + id); return; @@ -66,4 +102,40 @@ export class PostService { private add_post(post: Post) { return this.http.post(this.postUrl, post.serialize()); } + + + /** + * Get all categories from database. + */ + getAllCategories(): Promise<Array<Category>>{ + return new Promise<Array<Category>>( + (resolve, reject) => { + this.get_all_categories().subscribe((data: any) => { + try { + let outputCategories = []; + for (let dataCategory of data.data) { + const category = new Category(dataCategory); + outputCategories.push(category); + + if (category.getCategoryId == 0) { + reject("Could not deserialize category"); + return; + } + } + resolve(outputCategories); + } catch (err: any){ + reject(err); + } + }, + (err: any) => { + console.log(err.message); + reject(err); + }); + } + ); + } + + private get_all_categories() { + return this.http.get(this.categoryUrl); + } } diff --git a/client/src/app/shared/number-input/number-input.component.spec.ts b/client/src/app/shared/number-input/number-input.component.spec.ts index 2ff4b9f58e39fd3382d19ef53f1a2e00d68d8806..0342fc6b221817a7ae7d46960c532cac207bee74 100644 --- a/client/src/app/shared/number-input/number-input.component.spec.ts +++ b/client/src/app/shared/number-input/number-input.component.spec.ts @@ -1,4 +1,5 @@ import { ComponentFixture, TestBed } from '@angular/core/testing'; +import { FormsModule } from '@angular/forms'; import { NumberInputComponent } from './number-input.component'; @@ -8,7 +9,9 @@ describe('NumberInputComponent', () => { beforeEach(async () => { await TestBed.configureTestingModule({ - declarations: [ NumberInputComponent ] + declarations: [ NumberInputComponent ], + imports: [ FormsModule ] + }) .compileComponents(); }); diff --git a/client/src/app/shared/select/select.component.spec.ts b/client/src/app/shared/select/select.component.spec.ts index 2fb4207eb6dd5656d0ae4555b2ae7dbec7b18249..aa38c80dcd80f2ebe7cbf8c50de66ce85af2e895 100644 --- a/client/src/app/shared/select/select.component.spec.ts +++ b/client/src/app/shared/select/select.component.spec.ts @@ -1,4 +1,5 @@ import { ComponentFixture, TestBed } from '@angular/core/testing'; +import { FormsModule } from '@angular/forms'; import { SelectComponent } from './select.component'; @@ -8,7 +9,8 @@ describe('SelectComponent', () => { beforeEach(async () => { await TestBed.configureTestingModule({ - declarations: [ SelectComponent ] + declarations: [ SelectComponent ], + imports: [ FormsModule ] }) .compileComponents(); }); diff --git a/client/src/app/shared/text-input/text-input.component.spec.ts b/client/src/app/shared/text-input/text-input.component.spec.ts index f895776fe5022ace055990ea6f39868f964b89ec..c54aff01908e42b3e3b7ff1b29d4e844861a8cd3 100644 --- a/client/src/app/shared/text-input/text-input.component.spec.ts +++ b/client/src/app/shared/text-input/text-input.component.spec.ts @@ -1,4 +1,5 @@ import { ComponentFixture, TestBed } from '@angular/core/testing'; +import { FormsModule } from '@angular/forms'; import { TextInputComponent } from './text-input.component'; @@ -8,7 +9,8 @@ describe('TextInputComponent', () => { beforeEach(async () => { await TestBed.configureTestingModule({ - declarations: [ TextInputComponent ] + declarations: [ TextInputComponent ], + imports: [ FormsModule ] }) .compileComponents(); }); diff --git a/client/src/styles.scss b/client/src/styles.scss index 90d4ee0072ce3fc41812f8af910219f9eea3c3de..06cad1ecf084f21d1d2b7e142b23d9d6ea636de4 100644 --- a/client/src/styles.scss +++ b/client/src/styles.scss @@ -1 +1,6 @@ /* You can add global styles to this file, and also import other style files */ +* { + padding: 0; + margin: 0; + box-sizing: border-box; +} diff --git a/server/src/controllers/categoryController/index.ts b/server/src/controllers/categoryController/index.ts index 04d2f97730303afe3925758ab137e6e0f6f6df4f..ca491fea8591f37105f304d533c261041fb00c23 100644 --- a/server/src/controllers/categoryController/index.ts +++ b/server/src/controllers/categoryController/index.ts @@ -11,7 +11,7 @@ const category = new Category(); // - hente et bestemt kategori (get) // SELECT * FROM category WHERE categoryid = #num; // - remove specific cateogry (post) -// INSERT INTO `jonnynl_tdt4140`.`category` (`categoryid`, `navn`) VALUES ('4', 'ad'); +// INSERT INTO `jonnynl_tdt4140`.`category` (`categoryid`, `name`) VALUES ('4', 'ad'); // - add category (post) // DELETE FROM `jonnynl_tdt4140`.`category` WHERE (`categoryid` = '3'); diff --git a/server/src/controllers/postcontroller/index.ts b/server/src/controllers/postcontroller/index.ts index a296498fdf50da35a230cc7d198b9cf76a0d2dd4..433d6e7563b4315111ac37320443c47552644502 100644 --- a/server/src/controllers/postcontroller/index.ts +++ b/server/src/controllers/postcontroller/index.ts @@ -11,18 +11,18 @@ const category = new Category(); // Create posts `/api/post/` //'{"title":"test3","description":"test3","timestamp":123123,"owner":"test3","category":"test3","imageUrl":"test3"}' router.route('/').post(async (request: Request, response: Response) => { - const {title, description, timestamp, owner, category, imageUrl} = request.body; + const {title, description, timestamp, owner, categoryid, imageUrl} = request.body; try { const post: IPost = { "title": title, "description": description, "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, timestamp, owner, categoryid, imageUrl) VALUES (?,?,?,?,?,?)`) return response.status(200).json( await query(input,Object.values(post)) ); diff --git a/server/src/models/post.ts b/server/src/models/post.ts index 1a5a4cc8f8d6a513ba2e37b2c85bacd4fca3a257..a7e5a566baf8388dd99a8a014264714219808a4c 100644 --- a/server/src/models/post.ts +++ b/server/src/models/post.ts @@ -4,7 +4,7 @@ interface IPost { description: string; timestamp: number; owner: string; - category: string; + categoryid: number; imageUrl: string; }