├── post.js ├── front-end ├── src │ ├── assets │ │ ├── .gitkeep │ │ ├── seen.png │ │ ├── no-image.png │ │ ├── delete_icon.png │ │ ├── email-5-xxl.png │ │ ├── reply_icon.png │ │ ├── ok-hand-sign.png │ │ ├── 800px-EBay_logo.png │ │ ├── check-mark-xxl.png │ │ ├── auction-bidding-ss-1920_zmdrak.png │ │ └── Loudly_Crying_Face_Emoji_grande.webp │ ├── app │ │ ├── admin │ │ │ ├── admin.component.css │ │ │ ├── admin.component.spec.ts │ │ │ ├── admin.component.ts │ │ │ └── admin.component.html │ │ ├── register │ │ │ ├── register.component.css │ │ │ ├── register.component.spec.ts │ │ │ ├── register.component.ts │ │ │ └── register.component.html │ │ ├── recommendations │ │ │ ├── recommendations.component.css │ │ │ ├── recommendations.component.html │ │ │ ├── recommendations.component.spec.ts │ │ │ └── recommendations.component.ts │ │ ├── home │ │ │ ├── index.ts │ │ │ ├── home.component.css │ │ │ ├── home.component.html │ │ │ └── home.component.ts │ │ ├── login │ │ │ ├── index.ts │ │ │ ├── login.component.html │ │ │ └── login.component.ts │ │ ├── map-details │ │ │ ├── map-details.component.html │ │ │ ├── map-details.component.css │ │ │ ├── map-details.component.spec.ts │ │ │ └── map-details.component.ts │ │ ├── _models │ │ │ ├── role.ts │ │ │ ├── bid.ts │ │ │ ├── index.ts │ │ │ ├── message.ts │ │ │ ├── user.ts │ │ │ └── auction.ts │ │ ├── _alert │ │ │ ├── index.ts │ │ │ ├── alert.component.html │ │ │ ├── alert.module.ts │ │ │ ├── alert.model.ts │ │ │ ├── alert.component.ts │ │ │ └── alert.service.ts │ │ ├── _helpers │ │ │ ├── index.ts │ │ │ ├── must-match.directive.ts │ │ │ ├── auth.guard.ts │ │ │ ├── jwt.interceptor.ts │ │ │ ├── error.interceptor.ts │ │ │ ├── must-match.validator.ts │ │ │ └── role-guard.service.ts │ │ ├── _services │ │ │ ├── index.ts │ │ │ ├── bid.service.ts │ │ │ ├── auction.service.ts │ │ │ ├── user.service.ts │ │ │ ├── messages.service.ts │ │ │ ├── authentication.service.ts │ │ │ └── auctions.service.ts │ │ ├── auctionform │ │ │ ├── auctionform.component.css │ │ │ ├── auctionform.component.spec.ts │ │ │ ├── auctionform.component.ts │ │ │ └── auctionform.component.html │ │ ├── page-not-found │ │ │ ├── page-not-found.component.html │ │ │ ├── page-not-found.component.ts │ │ │ ├── page-not-found.component.css │ │ │ └── page-not-found.component.spec.ts │ │ ├── app.component.css │ │ ├── inbox │ │ │ ├── inbox.component.css │ │ │ ├── inbox.component.ts │ │ │ └── inbox.component.html │ │ ├── thanks-signup │ │ │ ├── thanks-signup.component.html │ │ │ ├── thanks-signup.component.css │ │ │ ├── thanks-signup.component.ts │ │ │ └── thanks-signup.component.spec.ts │ │ ├── sent │ │ │ ├── sent.component.css │ │ │ ├── sent.component.ts │ │ │ └── sent.component.html │ │ ├── email │ │ │ ├── email.component.css │ │ │ ├── email.component.html │ │ │ ├── email.component.ts │ │ │ └── email.component.spec.ts │ │ ├── landing │ │ │ ├── landing.component.html │ │ │ ├── landing.component.ts │ │ │ ├── landing.component.spec.ts │ │ │ └── landing.component.css │ │ ├── manage-auctions │ │ │ ├── manage-auctions.component.css │ │ │ ├── manage-auctions.component.spec.ts │ │ │ ├── manage-auctions.component.html │ │ │ └── manage-auctions.component.ts │ │ ├── user-info │ │ │ ├── user-info.component.spec.ts │ │ │ ├── user-info.component.css │ │ │ ├── user-info.component.ts │ │ │ └── user-info.component.html │ │ ├── auction-info │ │ │ ├── auction-info.component.spec.ts │ │ │ ├── auction-info.component.css │ │ │ ├── auction-info.component.html │ │ │ └── auction-info.component.ts │ │ ├── auction-detail │ │ │ ├── auction-detail.component.spec.ts │ │ │ ├── auction-detail.component.html │ │ │ ├── auction-detail.component.ts │ │ │ └── auction-detail.component.css │ │ ├── app.component.ts │ │ ├── app.routing.ts │ │ ├── app.module.ts │ │ └── app.component.html │ ├── favicon.ico │ ├── environments │ │ ├── environment.prod.ts │ │ └── environment.ts │ ├── styles.css │ ├── main.ts │ ├── test.ts │ ├── index.html │ └── polyfills.ts ├── e2e │ ├── tsconfig.json │ ├── src │ │ ├── app.po.ts │ │ └── app.e2e-spec.ts │ └── protractor.conf.js ├── .editorconfig ├── tsconfig.app.json ├── tsconfig.spec.json ├── browserslist ├── tsconfig.json ├── .gitignore ├── README.md ├── karma.conf.js ├── package.json ├── tslint.json └── angular.json ├── README.pdf ├── project.pdf ├── Screenshots ├── admin_page.png ├── email_page.png ├── front-page.png ├── long_page.png ├── user_page.png ├── auction_page.png ├── landing_page.png ├── register_page.png └── manage_auctions_page.png ├── nodemon.json ├── api ├── models │ ├── category.js │ ├── bidder.js │ ├── product.js │ ├── message.js │ ├── auction.js │ ├── user.js │ └── bid.js └── routes │ ├── categories.js │ ├── products.js │ ├── bids.js │ └── messages.js ├── server.js ├── post_categories.js ├── LICENSE.md ├── package.json ├── .gitignore ├── README.md └── app.js /post.js: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /front-end/src/assets/.gitkeep: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /front-end/src/app/admin/admin.component.css: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /front-end/src/app/register/register.component.css: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /front-end/src/app/recommendations/recommendations.component.css: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /front-end/src/app/home/index.ts: -------------------------------------------------------------------------------- 1 | export * from './home.component'; -------------------------------------------------------------------------------- /front-end/src/app/login/index.ts: -------------------------------------------------------------------------------- 1 | export * from './login.component'; -------------------------------------------------------------------------------- /README.pdf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/hectortav/ebay-clone/HEAD/README.pdf -------------------------------------------------------------------------------- /project.pdf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/hectortav/ebay-clone/HEAD/project.pdf -------------------------------------------------------------------------------- /front-end/src/app/map-details/map-details.component.html: -------------------------------------------------------------------------------- 1 |
-------------------------------------------------------------------------------- /Screenshots/admin_page.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/hectortav/ebay-clone/HEAD/Screenshots/admin_page.png -------------------------------------------------------------------------------- /Screenshots/email_page.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/hectortav/ebay-clone/HEAD/Screenshots/email_page.png -------------------------------------------------------------------------------- /Screenshots/front-page.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/hectortav/ebay-clone/HEAD/Screenshots/front-page.png -------------------------------------------------------------------------------- /Screenshots/long_page.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/hectortav/ebay-clone/HEAD/Screenshots/long_page.png -------------------------------------------------------------------------------- /Screenshots/user_page.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/hectortav/ebay-clone/HEAD/Screenshots/user_page.png -------------------------------------------------------------------------------- /front-end/src/app/_models/role.ts: -------------------------------------------------------------------------------- 1 | export enum Role { 2 | user = 'user', 3 | admin = 'admin' 4 | } -------------------------------------------------------------------------------- /front-end/src/favicon.ico: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/hectortav/ebay-clone/HEAD/front-end/src/favicon.ico -------------------------------------------------------------------------------- /nodemon.json: -------------------------------------------------------------------------------- 1 | { 2 | "env": { 3 | "MONGO_ATLAS_PW": "admin", 4 | "JWT_KEY": "secret" 5 | } 6 | } 7 | -------------------------------------------------------------------------------- /Screenshots/auction_page.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/hectortav/ebay-clone/HEAD/Screenshots/auction_page.png -------------------------------------------------------------------------------- /Screenshots/landing_page.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/hectortav/ebay-clone/HEAD/Screenshots/landing_page.png -------------------------------------------------------------------------------- /front-end/src/app/map-details/map-details.component.css: -------------------------------------------------------------------------------- 1 | .map { 2 | width: 100%; 3 | height: 100%; 4 | } -------------------------------------------------------------------------------- /Screenshots/register_page.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/hectortav/ebay-clone/HEAD/Screenshots/register_page.png -------------------------------------------------------------------------------- /front-end/src/assets/seen.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/hectortav/ebay-clone/HEAD/front-end/src/assets/seen.png -------------------------------------------------------------------------------- /front-end/src/assets/no-image.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/hectortav/ebay-clone/HEAD/front-end/src/assets/no-image.png -------------------------------------------------------------------------------- /Screenshots/manage_auctions_page.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/hectortav/ebay-clone/HEAD/Screenshots/manage_auctions_page.png -------------------------------------------------------------------------------- /front-end/src/assets/delete_icon.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/hectortav/ebay-clone/HEAD/front-end/src/assets/delete_icon.png -------------------------------------------------------------------------------- /front-end/src/assets/email-5-xxl.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/hectortav/ebay-clone/HEAD/front-end/src/assets/email-5-xxl.png -------------------------------------------------------------------------------- /front-end/src/assets/reply_icon.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/hectortav/ebay-clone/HEAD/front-end/src/assets/reply_icon.png -------------------------------------------------------------------------------- /front-end/src/assets/ok-hand-sign.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/hectortav/ebay-clone/HEAD/front-end/src/assets/ok-hand-sign.png -------------------------------------------------------------------------------- /front-end/src/assets/800px-EBay_logo.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/hectortav/ebay-clone/HEAD/front-end/src/assets/800px-EBay_logo.png -------------------------------------------------------------------------------- /front-end/src/assets/check-mark-xxl.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/hectortav/ebay-clone/HEAD/front-end/src/assets/check-mark-xxl.png -------------------------------------------------------------------------------- /front-end/src/app/_alert/index.ts: -------------------------------------------------------------------------------- 1 | export * from './alert.module'; 2 | export * from './alert.service'; 3 | export * from './alert.model'; 4 | -------------------------------------------------------------------------------- /front-end/src/environments/environment.prod.ts: -------------------------------------------------------------------------------- 1 | export const environment = { 2 | production: true, 3 | apiUrl: 'http://localhost:4000' 4 | }; 5 | -------------------------------------------------------------------------------- /front-end/src/assets/auction-bidding-ss-1920_zmdrak.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/hectortav/ebay-clone/HEAD/front-end/src/assets/auction-bidding-ss-1920_zmdrak.png -------------------------------------------------------------------------------- /front-end/src/app/_models/bid.ts: -------------------------------------------------------------------------------- 1 | export class Bid { 2 | _id?: string; 3 | auction: string; 4 | bidder: string; 5 | time: Date; 6 | amount: number; 7 | } -------------------------------------------------------------------------------- /front-end/src/assets/Loudly_Crying_Face_Emoji_grande.webp: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/hectortav/ebay-clone/HEAD/front-end/src/assets/Loudly_Crying_Face_Emoji_grande.webp -------------------------------------------------------------------------------- /front-end/src/app/_models/index.ts: -------------------------------------------------------------------------------- 1 | export * from './user'; 2 | export * from './auction'; 3 | export * from './role'; 4 | export * from './bid'; 5 | export * from './message'; -------------------------------------------------------------------------------- /front-end/src/app/_helpers/index.ts: -------------------------------------------------------------------------------- 1 | export * from './auth.guard'; 2 | export * from './error.interceptor'; 3 | export * from './jwt.interceptor'; 4 | export * from './role-guard.service'; -------------------------------------------------------------------------------- /front-end/src/styles.css: -------------------------------------------------------------------------------- 1 | a { 2 | cursor: pointer 3 | } 4 | 5 | * { 6 | font-family: 'Open Sans', sans-serif; 7 | } 8 | 9 | body { 10 | background-color: #FAFAFA 11 | } -------------------------------------------------------------------------------- /front-end/src/app/_models/message.ts: -------------------------------------------------------------------------------- 1 | export class Message { 2 | _id?: string; 3 | sender: string; 4 | receiver: string; 5 | subject: string; 6 | time?: Date; 7 | text: string; 8 | read: Boolean; 9 | } -------------------------------------------------------------------------------- /api/models/category.js: -------------------------------------------------------------------------------- 1 | const mongoose = require('mongoose'); 2 | 3 | const categorySchema = mongoose.Schema({ 4 | name: {type: String, required: true } 5 | }); 6 | 7 | module.exports = mongoose.model('Category', categorySchema); -------------------------------------------------------------------------------- /front-end/src/app/_services/index.ts: -------------------------------------------------------------------------------- 1 | export * from './authentication.service'; 2 | export * from './user.service'; 3 | export * from './auction.service'; 4 | export * from './auctions.service'; 5 | export * from './bid.service'; 6 | export * from './messages.service'; -------------------------------------------------------------------------------- /front-end/src/app/auctionform/auctionform.component.css: -------------------------------------------------------------------------------- 1 | input[type="file"] { 2 | display: none; 3 | } 4 | 5 | .custom-file-upload { 6 | border: 1px solid #ccc; 7 | display: inline-block; 8 | padding: 6px 12px; 9 | cursor: pointer; 10 | } -------------------------------------------------------------------------------- /api/models/bidder.js: -------------------------------------------------------------------------------- 1 | const mongoose = require('mongoose'); 2 | 3 | const bidderSchema = mongoose.Schema({ 4 | _id: mongoose.Schema.Types.ObjectId, 5 | rating: { type: Number, required: true } 6 | }); 7 | 8 | module.exports = mongoose.model('Bidder', productSchema); 9 | -------------------------------------------------------------------------------- /front-end/src/app/page-not-found/page-not-found.component.html: -------------------------------------------------------------------------------- 1 | 2 | crying 3 |

Oh no! Page not found.

4 |

Go back to the homepage.

5 | -------------------------------------------------------------------------------- /front-end/src/app/app.component.css: -------------------------------------------------------------------------------- 1 | .navbar .navbar-nav .nav-link { 2 | color: rgb(98, 112, 124); 3 | font-weight: 600; 4 | margin-left: 20px; 5 | } 6 | 7 | .active { 8 | color: black !important; 9 | } 10 | 11 | .allButFooter { 12 | min-height: calc(100vh - 40px); 13 | } -------------------------------------------------------------------------------- /front-end/e2e/tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": "../tsconfig.json", 3 | "compilerOptions": { 4 | "outDir": "../out-tsc/e2e", 5 | "module": "commonjs", 6 | "target": "es5", 7 | "types": [ 8 | "jasmine", 9 | "jasminewd2", 10 | "node" 11 | ] 12 | } 13 | } 14 | -------------------------------------------------------------------------------- /front-end/src/app/inbox/inbox.component.css: -------------------------------------------------------------------------------- 1 | .buttons-container { 2 | margin-top: 35px; 3 | float: right; 4 | display: flex; 5 | } 6 | 7 | .subject { 8 | position: absolute; 9 | left: 340px; 10 | } 11 | 12 | .time { 13 | font-weight: normal; 14 | font-size: 0.85rem; 15 | float: right; 16 | } -------------------------------------------------------------------------------- /front-end/.editorconfig: -------------------------------------------------------------------------------- 1 | # Editor configuration, see https://editorconfig.org 2 | root = true 3 | 4 | [*] 5 | charset = utf-8 6 | indent_style = space 7 | indent_size = 2 8 | insert_final_newline = true 9 | trim_trailing_whitespace = true 10 | 11 | [*.md] 12 | max_line_length = off 13 | trim_trailing_whitespace = false 14 | -------------------------------------------------------------------------------- /front-end/e2e/src/app.po.ts: -------------------------------------------------------------------------------- 1 | import { browser, by, element } from 'protractor'; 2 | 3 | export class AppPage { 4 | navigateTo() { 5 | return browser.get(browser.baseUrl) as Promise; 6 | } 7 | 8 | getTitleText() { 9 | return element(by.css('app-root h1')).getText() as Promise; 10 | } 11 | } 12 | -------------------------------------------------------------------------------- /front-end/src/app/_alert/alert.component.html: -------------------------------------------------------------------------------- 1 | 2 | 3 |
4 | {{alert.message}} 5 | × 6 |
-------------------------------------------------------------------------------- /front-end/src/app/thanks-signup/thanks-signup.component.html: -------------------------------------------------------------------------------- 1 |
2 | Good-work-img 3 |

Thanks for registering. Your account is under review from our administrators.

4 |

Please wait while we redirect you to the homepage...

5 |
-------------------------------------------------------------------------------- /front-end/src/app/_alert/alert.module.ts: -------------------------------------------------------------------------------- 1 | import { NgModule } from '@angular/core'; 2 | import { CommonModule } from '@angular/common'; 3 | 4 | import { AlertComponent } from './alert.component'; 5 | 6 | @NgModule({ 7 | imports: [CommonModule], 8 | declarations: [AlertComponent], 9 | exports: [AlertComponent] 10 | }) 11 | export class AlertModule { } -------------------------------------------------------------------------------- /front-end/tsconfig.app.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": "./tsconfig.json", 3 | "compilerOptions": { 4 | "outDir": "./out-tsc/app", 5 | "types": [] 6 | }, 7 | "files": [ 8 | "src/main.ts", 9 | "src/polyfills.ts" 10 | ], 11 | "include": [ 12 | "src/**/*.ts" 13 | ], 14 | "exclude": [ 15 | "src/test.ts", 16 | "src/**/*.spec.ts" 17 | ] 18 | } 19 | -------------------------------------------------------------------------------- /front-end/tsconfig.spec.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": "./tsconfig.json", 3 | "compilerOptions": { 4 | "outDir": "./out-tsc/spec", 5 | "types": [ 6 | "jasmine", 7 | "node" 8 | ] 9 | }, 10 | "files": [ 11 | "src/test.ts", 12 | "src/polyfills.ts" 13 | ], 14 | "include": [ 15 | "src/**/*.spec.ts", 16 | "src/**/*.d.ts" 17 | ] 18 | } 19 | -------------------------------------------------------------------------------- /front-end/src/app/_alert/alert.model.ts: -------------------------------------------------------------------------------- 1 | export class Alert { 2 | type: AlertType; 3 | message: string; 4 | alertId: string; 5 | keepAfterRouteChange: boolean; 6 | 7 | constructor(init?:Partial) { 8 | Object.assign(this, init); 9 | } 10 | } 11 | 12 | export enum AlertType { 13 | Success, 14 | Error, 15 | Info, 16 | Warning 17 | } -------------------------------------------------------------------------------- /front-end/src/app/thanks-signup/thanks-signup.component.css: -------------------------------------------------------------------------------- 1 | div { 2 | padding-top: 200px; 3 | } 4 | 5 | img { 6 | width: 14%; 7 | padding-bottom: 70px; 8 | } 9 | 10 | h3 { 11 | padding-top: -10px; 12 | font-size: 2rem; 13 | } 14 | 15 | a { 16 | text-decoration: none; 17 | letter-spacing: 2px; 18 | font-weight: bold; 19 | font-size: 1.2rem; 20 | } -------------------------------------------------------------------------------- /front-end/src/app/_models/user.ts: -------------------------------------------------------------------------------- 1 | import { Role } from "./role"; 2 | 3 | export class User { 4 | _id: string; 5 | username: string; 6 | firstname: string; 7 | lastname: string; 8 | email: string; 9 | phone: string; 10 | address: string; 11 | city: string; 12 | afm: string; 13 | role: Role; 14 | token?: string; 15 | verified?: any; 16 | rating?: any; 17 | } -------------------------------------------------------------------------------- /api/models/product.js: -------------------------------------------------------------------------------- 1 | const mongoose = require('mongoose'); 2 | 3 | const productSchema = mongoose.Schema({ 4 | _id: mongoose.Schema.Types.ObjectId, 5 | name: { type: String, required: true }, 6 | category: { type: String, required: true }, 7 | location: { type: String, required: true }, 8 | country: { type: String, required: true } 9 | }); 10 | 11 | module.exports = mongoose.model('Product', productSchema); 12 | -------------------------------------------------------------------------------- /front-end/src/app/page-not-found/page-not-found.component.ts: -------------------------------------------------------------------------------- 1 | import { Component, OnInit } from '@angular/core'; 2 | 3 | @Component({ 4 | selector: 'app-page-not-found', 5 | templateUrl: './page-not-found.component.html', 6 | styleUrls: ['./page-not-found.component.css'] 7 | }) 8 | export class PageNotFoundComponent implements OnInit { 9 | 10 | constructor() { } 11 | 12 | ngOnInit() { 13 | } 14 | 15 | } 16 | -------------------------------------------------------------------------------- /front-end/src/app/page-not-found/page-not-found.component.css: -------------------------------------------------------------------------------- 1 | body { 2 | justify-content: center; 3 | align-items: center; 4 | text-align: center; 5 | padding-top: 200px; 6 | } 7 | 8 | img { 9 | width: 17%; 10 | } 11 | 12 | .content h3 { 13 | padding-top: 40px; 14 | font-size: 2rem; 15 | } 16 | 17 | a { 18 | text-decoration: none; 19 | letter-spacing: 2px; 20 | font-weight: bold; 21 | font-size: 1.2rem; 22 | } -------------------------------------------------------------------------------- /front-end/src/main.ts: -------------------------------------------------------------------------------- 1 | import { enableProdMode } from '@angular/core'; 2 | import { platformBrowserDynamic } from '@angular/platform-browser-dynamic'; 3 | 4 | import { AppModule } from './app/app.module'; 5 | import { environment } from './environments/environment'; 6 | 7 | if (environment.production) { 8 | enableProdMode(); 9 | } 10 | 11 | platformBrowserDynamic().bootstrapModule(AppModule) 12 | .catch(err => console.error(err)); 13 | -------------------------------------------------------------------------------- /front-end/src/app/_models/auction.ts: -------------------------------------------------------------------------------- 1 | export class Auction { 2 | _id?: string; 3 | name: string; 4 | category: string[]; 5 | location: string; 6 | country: string; 7 | currently: string; 8 | first_bid: Number; 9 | no_bids?: number; 10 | started: Date; 11 | ends: Date; 12 | description: string; 13 | latitude?: number; 14 | longitude?: number; 15 | seller: string; 16 | bids?: string[]; 17 | images?: string[]; 18 | } -------------------------------------------------------------------------------- /front-end/src/app/recommendations/recommendations.component.html: -------------------------------------------------------------------------------- 1 |
2 | 9 |
-------------------------------------------------------------------------------- /front-end/browserslist: -------------------------------------------------------------------------------- 1 | # This file is used by the build system to adjust CSS and JS output to support the specified browsers below. 2 | # For additional information regarding the format and rule options, please see: 3 | # https://github.com/browserslist/browserslist#queries 4 | 5 | # You can see what browsers were selected by your queries by running: 6 | # npx browserslist 7 | 8 | > 0.5% 9 | last 2 versions 10 | Firefox ESR 11 | not dead 12 | not IE 9-11 # For IE 9-11 support, remove 'not'. -------------------------------------------------------------------------------- /front-end/src/app/sent/sent.component.css: -------------------------------------------------------------------------------- 1 | .seen { 2 | float: right; 3 | font-size: 0.8rem; 4 | position: relative; 5 | top: 38px; 6 | } 7 | 8 | .seen img { 9 | width: 20px; 10 | } 11 | 12 | .subject { 13 | position: absolute; 14 | left: 340px; 15 | } 16 | 17 | .time { 18 | font-weight: normal; 19 | font-size: 0.85rem; 20 | float: right; 21 | } 22 | 23 | .buttons-container { 24 | margin-top: 35px; 25 | float: left; 26 | display: flex; 27 | } 28 | -------------------------------------------------------------------------------- /front-end/src/app/thanks-signup/thanks-signup.component.ts: -------------------------------------------------------------------------------- 1 | import { Component, OnInit } from '@angular/core'; 2 | import { Router } from '@angular/router'; 3 | 4 | @Component({ 5 | selector: 'app-thanks-signup', 6 | templateUrl: `./thanks-signup.component.html`, 7 | styleUrls: [`./thanks-signup.component.css`] 8 | }) 9 | export class ThanksSignupComponent implements OnInit { 10 | 11 | constructor(private router: Router) { } 12 | 13 | ngOnInit() { 14 | setTimeout(() => { 15 | this.router.navigate(['']); 16 | }, 6000); 17 | } 18 | 19 | } 20 | -------------------------------------------------------------------------------- /front-end/src/app/email/email.component.css: -------------------------------------------------------------------------------- 1 | .center { 2 | padding-top: 25px; 3 | display: flex; 4 | justify-content: center; 5 | font-size: 1.13rem; 6 | } 7 | 8 | .active { 9 | background-color: #E8EAED; 10 | border-radius: 30px; 11 | width: 11%; 12 | height: 5%; 13 | margin: 0 0 0 0; 14 | padding: 0 0 0 0; 15 | } 16 | 17 | .active-second { 18 | font-weight: bold; 19 | color: black; 20 | padding-left: 20px; 21 | padding-right: 20px; 22 | padding-top: 5px; 23 | padding-bottom: 5px; 24 | } 25 | 26 | a { 27 | color: #6A6B6D; 28 | text-decoration: none; 29 | } -------------------------------------------------------------------------------- /front-end/src/app/landing/landing.component.html: -------------------------------------------------------------------------------- 1 | 2 |
3 |
4 | Login 5 |
6 |
7 |

Ready to Shop What's Hot?

8 |

OUT WITH THE OLD. IN WITH THE NEW.

9 | Shop Now    > 10 | Don't have an account? Register 11 |
12 |
13 | -------------------------------------------------------------------------------- /server.js: -------------------------------------------------------------------------------- 1 | const https = require('https'); 2 | 3 | const app = require('./app'); 4 | const fs = require('fs'); 5 | 6 | const port = process.env.PORT || 3000; 7 | 8 | const server = https.createServer({ 9 | key: fs.readFileSync('localhost.key'), 10 | cert: fs.readFileSync('localhost.cert') 11 | }, app); 12 | 13 | server.listen(port); 14 | 15 | /* 16 | var tls = require('tls'); 17 | var fs = require('fs'); 18 | 19 | var options = { 20 | key : fs.readFileSync('mykey.pem'), 21 | cert: fs.readFileSync('my-cert.pem') 22 | } 23 | 24 | tls.createServer(options,function(s) 25 | { 26 | s.write("welcome!\n"); 27 | s.pipe(s); 28 | }).listen(3000);*/ -------------------------------------------------------------------------------- /api/models/message.js: -------------------------------------------------------------------------------- 1 | const mongoose = require('mongoose'); 2 | 3 | const messageSchema = mongoose.Schema({ 4 | _id: mongoose.Schema.Types.ObjectId, 5 | sender: { type: mongoose.Schema.Types.ObjectId, red: 'User' , required: true}, 6 | receiver: { type: mongoose.Schema.Types.ObjectId, red: 'User' , required: true}, 7 | sender_username: {type: String }, 8 | receiver_username: {type: String }, 9 | subject: {type: String, required: true }, 10 | time: {type: Date}, 11 | text: {type: String, required: true }, 12 | read: {type: Boolean, default: false} 13 | }); 14 | 15 | module.exports = mongoose.model('Message', messageSchema); -------------------------------------------------------------------------------- /front-end/src/app/email/email.component.html: -------------------------------------------------------------------------------- 1 | 2 |
3 |
4 | Inbox 6 |
7 |

/

8 |
9 | Sent 10 | Mail 11 |
12 |
13 | 14 | 15 |
16 | 17 |
-------------------------------------------------------------------------------- /front-end/tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "compileOnSave": false, 3 | "compilerOptions": { 4 | "baseUrl": "./", 5 | "outDir": "./dist/out-tsc", 6 | "sourceMap": true, 7 | "declaration": false, 8 | "downlevelIteration": true, 9 | "experimentalDecorators": true, 10 | "module": "esnext", 11 | "moduleResolution": "node", 12 | "importHelpers": true, 13 | "target": "es2015", 14 | "typeRoots": [ 15 | "node_modules/@types" 16 | ], 17 | "lib": [ 18 | "es2018", 19 | "dom" 20 | ] 21 | }, 22 | "angularCompilerOptions": { 23 | "fullTemplateTypeCheck": true, 24 | "strictInjectionParameters": true 25 | } 26 | } 27 | -------------------------------------------------------------------------------- /front-end/src/app/_services/bid.service.ts: -------------------------------------------------------------------------------- 1 | import { Injectable } from '@angular/core'; 2 | import { HttpClient } from '@angular/common/http'; 3 | import { Bid } from '../_models'; 4 | import { environment } from 'src/environments/environment'; 5 | 6 | @Injectable({ providedIn: 'root' }) 7 | export class BidService { 8 | constructor(private http: HttpClient) { } 9 | 10 | getById(id: string) { 11 | return this.http.get(`${environment.apiUrl}/bids/${id}`); 12 | } 13 | 14 | newBid(bid: Bid) { 15 | return this.http.post(`${environment.apiUrl}/bids`, bid); 16 | } 17 | 18 | getAllBids(id: string) { 19 | return this.http.get(`${environment.apiUrl}/auctions/${id}/bids`); 20 | } 21 | } -------------------------------------------------------------------------------- /front-end/src/test.ts: -------------------------------------------------------------------------------- 1 | // This file is required by karma.conf.js and loads recursively all the .spec and framework files 2 | 3 | import 'zone.js/dist/zone-testing'; 4 | import { getTestBed } from '@angular/core/testing'; 5 | import { 6 | BrowserDynamicTestingModule, 7 | platformBrowserDynamicTesting 8 | } from '@angular/platform-browser-dynamic/testing'; 9 | 10 | declare const require: any; 11 | 12 | // First, initialize the Angular testing environment. 13 | getTestBed().initTestEnvironment( 14 | BrowserDynamicTestingModule, 15 | platformBrowserDynamicTesting() 16 | ); 17 | // Then we find all the tests. 18 | const context = require.context('./', true, /\.spec\.ts$/); 19 | // And load the modules. 20 | context.keys().map(context); 21 | -------------------------------------------------------------------------------- /front-end/src/app/manage-auctions/manage-auctions.component.css: -------------------------------------------------------------------------------- 1 | .selectedAuction { 2 | font-weight: bold; 3 | } 4 | 5 | .newForm { 6 | padding-bottom: 30px; 7 | } 8 | 9 | .newForm button { 10 | font-weight: bold; 11 | } 12 | 13 | .newForm img { 14 | width: 16px; 15 | transform: rotate(10deg); 16 | margin-top: -4px; 17 | } 18 | 19 | .newForm span { 20 | padding-left: 9px; 21 | } 22 | 23 | .auctionsContainer { 24 | padding: 70px 0 0 50px; 25 | } 26 | 27 | .auctions { 28 | width: 50em; 29 | } 30 | 31 | .auctions h5 { 32 | color: gray; 33 | font-weight: bold; 34 | letter-spacing: 1px; 35 | } 36 | 37 | .recs { 38 | padding-top: 40px; 39 | } 40 | 41 | .recs button { 42 | font-weight: bold; 43 | } -------------------------------------------------------------------------------- /front-end/src/app/landing/landing.component.ts: -------------------------------------------------------------------------------- 1 | import { Component, OnInit } from '@angular/core'; 2 | import { Router } from '@angular/router'; 3 | import { AuthenticationService } from '../_services'; 4 | 5 | @Component({ 6 | selector: 'app-landing', 7 | templateUrl: './landing.component.html', 8 | styleUrls: ['./landing.component.css'] 9 | }) 10 | export class LandingComponent implements OnInit { 11 | 12 | constructor( 13 | private router: Router, 14 | private authenticationService: AuthenticationService 15 | ) { 16 | // redirect to home if already logged in 17 | if (this.authenticationService.currentUserValue) { 18 | this.router.navigate(['/home']); 19 | } 20 | } 21 | 22 | ngOnInit() { 23 | } 24 | 25 | } 26 | -------------------------------------------------------------------------------- /front-end/e2e/src/app.e2e-spec.ts: -------------------------------------------------------------------------------- 1 | import { AppPage } from './app.po'; 2 | import { browser, logging } from 'protractor'; 3 | 4 | describe('workspace-project App', () => { 5 | let page: AppPage; 6 | 7 | beforeEach(() => { 8 | page = new AppPage(); 9 | }); 10 | 11 | it('should display welcome message', () => { 12 | page.navigateTo(); 13 | expect(page.getTitleText()).toEqual('Welcome to front-end!'); 14 | }); 15 | 16 | afterEach(async () => { 17 | // Assert that there are no errors emitted from the browser 18 | const logs = await browser.manage().logs().get(logging.Type.BROWSER); 19 | expect(logs).not.toContain(jasmine.objectContaining({ 20 | level: logging.Level.SEVERE, 21 | } as logging.Entry)); 22 | }); 23 | }); 24 | -------------------------------------------------------------------------------- /front-end/src/app/email/email.component.ts: -------------------------------------------------------------------------------- 1 | import { Component, OnInit } from '@angular/core'; 2 | import { first } from 'rxjs/operators'; 3 | import { MessagesService } from '../_services'; 4 | import { Message } from '../_models'; 5 | 6 | @Component({ 7 | selector: 'app-email', 8 | templateUrl: './email.component.html', 9 | styleUrls: ['./email.component.css'] 10 | }) 11 | export class EmailComponent implements OnInit { 12 | loading = false; 13 | messages: Message[]; 14 | currentFolder: string; 15 | 16 | constructor( 17 | private messagesService: MessagesService 18 | ) { } 19 | 20 | ngOnInit() { 21 | this.currentFolder = 'inbox'; 22 | } 23 | 24 | folderChoice(choice: string) { 25 | this.currentFolder = choice; 26 | } 27 | } 28 | -------------------------------------------------------------------------------- /front-end/src/app/admin/admin.component.spec.ts: -------------------------------------------------------------------------------- 1 | import { async, ComponentFixture, TestBed } from '@angular/core/testing'; 2 | 3 | import { AdminComponent } from './admin.component'; 4 | 5 | describe('AdminComponent', () => { 6 | let component: AdminComponent; 7 | let fixture: ComponentFixture; 8 | 9 | beforeEach(async(() => { 10 | TestBed.configureTestingModule({ 11 | declarations: [ AdminComponent ] 12 | }) 13 | .compileComponents(); 14 | })); 15 | 16 | beforeEach(() => { 17 | fixture = TestBed.createComponent(AdminComponent); 18 | component = fixture.componentInstance; 19 | fixture.detectChanges(); 20 | }); 21 | 22 | it('should create', () => { 23 | expect(component).toBeTruthy(); 24 | }); 25 | }); 26 | -------------------------------------------------------------------------------- /front-end/src/app/email/email.component.spec.ts: -------------------------------------------------------------------------------- 1 | import { async, ComponentFixture, TestBed } from '@angular/core/testing'; 2 | 3 | import { EmailComponent } from './email.component'; 4 | 5 | describe('EmailComponent', () => { 6 | let component: EmailComponent; 7 | let fixture: ComponentFixture; 8 | 9 | beforeEach(async(() => { 10 | TestBed.configureTestingModule({ 11 | declarations: [ EmailComponent ] 12 | }) 13 | .compileComponents(); 14 | })); 15 | 16 | beforeEach(() => { 17 | fixture = TestBed.createComponent(EmailComponent); 18 | component = fixture.componentInstance; 19 | fixture.detectChanges(); 20 | }); 21 | 22 | it('should create', () => { 23 | expect(component).toBeTruthy(); 24 | }); 25 | }); 26 | -------------------------------------------------------------------------------- /front-end/src/app/landing/landing.component.spec.ts: -------------------------------------------------------------------------------- 1 | import { async, ComponentFixture, TestBed } from '@angular/core/testing'; 2 | 3 | import { LandingComponent } from './landing.component'; 4 | 5 | describe('LandingComponent', () => { 6 | let component: LandingComponent; 7 | let fixture: ComponentFixture; 8 | 9 | beforeEach(async(() => { 10 | TestBed.configureTestingModule({ 11 | declarations: [ LandingComponent ] 12 | }) 13 | .compileComponents(); 14 | })); 15 | 16 | beforeEach(() => { 17 | fixture = TestBed.createComponent(LandingComponent); 18 | component = fixture.componentInstance; 19 | fixture.detectChanges(); 20 | }); 21 | 22 | it('should create', () => { 23 | expect(component).toBeTruthy(); 24 | }); 25 | }); 26 | -------------------------------------------------------------------------------- /front-end/src/app/register/register.component.spec.ts: -------------------------------------------------------------------------------- 1 | import { async, ComponentFixture, TestBed } from '@angular/core/testing'; 2 | 3 | import { RegisterComponent } from './register.component'; 4 | 5 | describe('RegisterComponent', () => { 6 | let component: RegisterComponent; 7 | let fixture: ComponentFixture; 8 | 9 | beforeEach(async(() => { 10 | TestBed.configureTestingModule({ 11 | declarations: [ RegisterComponent ] 12 | }) 13 | .compileComponents(); 14 | })); 15 | 16 | beforeEach(() => { 17 | fixture = TestBed.createComponent(RegisterComponent); 18 | component = fixture.componentInstance; 19 | fixture.detectChanges(); 20 | }); 21 | 22 | it('should create', () => { 23 | expect(component).toBeTruthy(); 24 | }); 25 | }); 26 | -------------------------------------------------------------------------------- /front-end/src/app/user-info/user-info.component.spec.ts: -------------------------------------------------------------------------------- 1 | import { async, ComponentFixture, TestBed } from '@angular/core/testing'; 2 | 3 | import { UserInfoComponent } from './user-info.component'; 4 | 5 | describe('UserInfoComponent', () => { 6 | let component: UserInfoComponent; 7 | let fixture: ComponentFixture; 8 | 9 | beforeEach(async(() => { 10 | TestBed.configureTestingModule({ 11 | declarations: [ UserInfoComponent ] 12 | }) 13 | .compileComponents(); 14 | })); 15 | 16 | beforeEach(() => { 17 | fixture = TestBed.createComponent(UserInfoComponent); 18 | component = fixture.componentInstance; 19 | fixture.detectChanges(); 20 | }); 21 | 22 | it('should create', () => { 23 | expect(component).toBeTruthy(); 24 | }); 25 | }); 26 | -------------------------------------------------------------------------------- /front-end/src/app/_helpers/must-match.directive.ts: -------------------------------------------------------------------------------- 1 | // Must-match validation credit: https://jasonwatmore.com/post/2019/06/15/angular-8-template-driven-forms-validation-example 2 | 3 | import { Directive, Input } from '@angular/core'; 4 | import { NG_VALIDATORS, Validator, ValidationErrors, FormGroup } from '@angular/forms'; 5 | 6 | import { MustMatch } from './must-match.validator'; 7 | 8 | @Directive({ 9 | selector: '[mustMatch]', 10 | providers: [{ provide: NG_VALIDATORS, useExisting: MustMatchDirective, multi: true }] 11 | }) 12 | export class MustMatchDirective implements Validator { 13 | @Input('mustMatch') mustMatch: string[] = []; 14 | 15 | validate(formGroup: FormGroup): ValidationErrors { 16 | return MustMatch(this.mustMatch[0], this.mustMatch[1])(formGroup); 17 | } 18 | } -------------------------------------------------------------------------------- /front-end/src/environments/environment.ts: -------------------------------------------------------------------------------- 1 | // This file can be replaced during build by using the `fileReplacements` array. 2 | // `ng build --prod` replaces `environment.ts` with `environment.prod.ts`. 3 | // The list of file replacements can be found in `angular.json`. 4 | 5 | export const environment = { 6 | production: false, 7 | apiUrl: 'https://localhost:3000' 8 | }; 9 | 10 | /* 11 | * For easier debugging in development mode, you can import the following file 12 | * to ignore zone related error stack frames such as `zone.run`, `zoneDelegate.invokeTask`. 13 | * 14 | * This import should be commented out in production mode because it will have a negative impact 15 | * on performance if an error is thrown. 16 | */ 17 | // import 'zone.js/dist/zone-error'; // Included with Angular CLI. 18 | -------------------------------------------------------------------------------- /front-end/src/app/map-details/map-details.component.spec.ts: -------------------------------------------------------------------------------- 1 | import { async, ComponentFixture, TestBed } from '@angular/core/testing'; 2 | 3 | import { MapDetailsComponent } from './map-details.component'; 4 | 5 | describe('MapDetailsComponent', () => { 6 | let component: MapDetailsComponent; 7 | let fixture: ComponentFixture; 8 | 9 | beforeEach(async(() => { 10 | TestBed.configureTestingModule({ 11 | declarations: [ MapDetailsComponent ] 12 | }) 13 | .compileComponents(); 14 | })); 15 | 16 | beforeEach(() => { 17 | fixture = TestBed.createComponent(MapDetailsComponent); 18 | component = fixture.componentInstance; 19 | fixture.detectChanges(); 20 | }); 21 | 22 | it('should create', () => { 23 | expect(component).toBeTruthy(); 24 | }); 25 | }); 26 | -------------------------------------------------------------------------------- /front-end/src/app/auctionform/auctionform.component.spec.ts: -------------------------------------------------------------------------------- 1 | import { async, ComponentFixture, TestBed } from '@angular/core/testing'; 2 | 3 | import { AuctionformComponent } from './auctionform.component'; 4 | 5 | describe('AuctionformComponent', () => { 6 | let component: AuctionformComponent; 7 | let fixture: ComponentFixture; 8 | 9 | beforeEach(async(() => { 10 | TestBed.configureTestingModule({ 11 | declarations: [ AuctionformComponent ] 12 | }) 13 | .compileComponents(); 14 | })); 15 | 16 | beforeEach(() => { 17 | fixture = TestBed.createComponent(AuctionformComponent); 18 | component = fixture.componentInstance; 19 | fixture.detectChanges(); 20 | }); 21 | 22 | it('should create', () => { 23 | expect(component).toBeTruthy(); 24 | }); 25 | }); 26 | -------------------------------------------------------------------------------- /front-end/src/app/auction-info/auction-info.component.spec.ts: -------------------------------------------------------------------------------- 1 | import { async, ComponentFixture, TestBed } from '@angular/core/testing'; 2 | 3 | import { AuctionInfoComponent } from './auction-info.component'; 4 | 5 | describe('AuctionInfoComponent', () => { 6 | let component: AuctionInfoComponent; 7 | let fixture: ComponentFixture; 8 | 9 | beforeEach(async(() => { 10 | TestBed.configureTestingModule({ 11 | declarations: [ AuctionInfoComponent ] 12 | }) 13 | .compileComponents(); 14 | })); 15 | 16 | beforeEach(() => { 17 | fixture = TestBed.createComponent(AuctionInfoComponent); 18 | component = fixture.componentInstance; 19 | fixture.detectChanges(); 20 | }); 21 | 22 | it('should create', () => { 23 | expect(component).toBeTruthy(); 24 | }); 25 | }); 26 | -------------------------------------------------------------------------------- /front-end/src/app/page-not-found/page-not-found.component.spec.ts: -------------------------------------------------------------------------------- 1 | import { async, ComponentFixture, TestBed } from '@angular/core/testing'; 2 | 3 | import { PageNotFoundComponent } from './page-not-found.component'; 4 | 5 | describe('PageNotFoundComponent', () => { 6 | let component: PageNotFoundComponent; 7 | let fixture: ComponentFixture; 8 | 9 | beforeEach(async(() => { 10 | TestBed.configureTestingModule({ 11 | declarations: [ PageNotFoundComponent ] 12 | }) 13 | .compileComponents(); 14 | })); 15 | 16 | beforeEach(() => { 17 | fixture = TestBed.createComponent(PageNotFoundComponent); 18 | component = fixture.componentInstance; 19 | fixture.detectChanges(); 20 | }); 21 | 22 | it('should create', () => { 23 | expect(component).toBeTruthy(); 24 | }); 25 | }); 26 | -------------------------------------------------------------------------------- /front-end/src/app/thanks-signup/thanks-signup.component.spec.ts: -------------------------------------------------------------------------------- 1 | import { async, ComponentFixture, TestBed } from '@angular/core/testing'; 2 | 3 | import { ThanksSignupComponent } from './thanks-signup.component'; 4 | 5 | describe('ThanksSignupComponent', () => { 6 | let component: ThanksSignupComponent; 7 | let fixture: ComponentFixture; 8 | 9 | beforeEach(async(() => { 10 | TestBed.configureTestingModule({ 11 | declarations: [ ThanksSignupComponent ] 12 | }) 13 | .compileComponents(); 14 | })); 15 | 16 | beforeEach(() => { 17 | fixture = TestBed.createComponent(ThanksSignupComponent); 18 | component = fixture.componentInstance; 19 | fixture.detectChanges(); 20 | }); 21 | 22 | it('should create', () => { 23 | expect(component).toBeTruthy(); 24 | }); 25 | }); 26 | -------------------------------------------------------------------------------- /front-end/src/app/auction-detail/auction-detail.component.spec.ts: -------------------------------------------------------------------------------- 1 | import { async, ComponentFixture, TestBed } from '@angular/core/testing'; 2 | 3 | import { AuctionDetailComponent } from './auction-detail.component'; 4 | 5 | describe('AuctionDetailComponent', () => { 6 | let component: AuctionDetailComponent; 7 | let fixture: ComponentFixture; 8 | 9 | beforeEach(async(() => { 10 | TestBed.configureTestingModule({ 11 | declarations: [ AuctionDetailComponent ] 12 | }) 13 | .compileComponents(); 14 | })); 15 | 16 | beforeEach(() => { 17 | fixture = TestBed.createComponent(AuctionDetailComponent); 18 | component = fixture.componentInstance; 19 | fixture.detectChanges(); 20 | }); 21 | 22 | it('should create', () => { 23 | expect(component).toBeTruthy(); 24 | }); 25 | }); 26 | -------------------------------------------------------------------------------- /front-end/src/app/manage-auctions/manage-auctions.component.spec.ts: -------------------------------------------------------------------------------- 1 | import { async, ComponentFixture, TestBed } from '@angular/core/testing'; 2 | 3 | import { ManageAuctionsComponent } from './manage-auctions.component'; 4 | 5 | describe('ManageAuctionsComponent', () => { 6 | let component: ManageAuctionsComponent; 7 | let fixture: ComponentFixture; 8 | 9 | beforeEach(async(() => { 10 | TestBed.configureTestingModule({ 11 | declarations: [ ManageAuctionsComponent ] 12 | }) 13 | .compileComponents(); 14 | })); 15 | 16 | beforeEach(() => { 17 | fixture = TestBed.createComponent(ManageAuctionsComponent); 18 | component = fixture.componentInstance; 19 | fixture.detectChanges(); 20 | }); 21 | 22 | it('should create', () => { 23 | expect(component).toBeTruthy(); 24 | }); 25 | }); 26 | -------------------------------------------------------------------------------- /front-end/.gitignore: -------------------------------------------------------------------------------- 1 | # See http://help.github.com/ignore-files/ for more about ignoring files. 2 | 3 | # compiled output 4 | /dist 5 | /tmp 6 | /out-tsc 7 | # Only exists if Bazel was run 8 | /bazel-out 9 | 10 | # dependencies 11 | /node_modules 12 | 13 | # profiling files 14 | chrome-profiler-events*.json 15 | speed-measure-plugin*.json 16 | 17 | # IDEs and editors 18 | /.idea 19 | .project 20 | .classpath 21 | .c9/ 22 | *.launch 23 | .settings/ 24 | *.sublime-workspace 25 | 26 | # IDE - VSCode 27 | .vscode/* 28 | !.vscode/settings.json 29 | !.vscode/tasks.json 30 | !.vscode/launch.json 31 | !.vscode/extensions.json 32 | .history/* 33 | 34 | # misc 35 | /.sass-cache 36 | /connect.lock 37 | /coverage 38 | /libpeerconnection.log 39 | npm-debug.log 40 | yarn-error.log 41 | testem.log 42 | /typings 43 | 44 | # System Files 45 | .DS_Store 46 | Thumbs.db 47 | -------------------------------------------------------------------------------- /front-end/src/app/recommendations/recommendations.component.spec.ts: -------------------------------------------------------------------------------- 1 | import { async, ComponentFixture, TestBed } from '@angular/core/testing'; 2 | 3 | import { RecommendationsComponent } from './recommendations.component'; 4 | 5 | describe('RecommendationsComponent', () => { 6 | let component: RecommendationsComponent; 7 | let fixture: ComponentFixture; 8 | 9 | beforeEach(async(() => { 10 | TestBed.configureTestingModule({ 11 | declarations: [ RecommendationsComponent ] 12 | }) 13 | .compileComponents(); 14 | })); 15 | 16 | beforeEach(() => { 17 | fixture = TestBed.createComponent(RecommendationsComponent); 18 | component = fixture.componentInstance; 19 | fixture.detectChanges(); 20 | }); 21 | 22 | it('should create', () => { 23 | expect(component).toBeTruthy(); 24 | }); 25 | }); 26 | -------------------------------------------------------------------------------- /front-end/src/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | eLagoon 10 | 11 | 12 | 13 | 14 | 16 | 17 | 18 | 20 | 21 | 22 | 23 | Loading... 24 | 25 | 26 | -------------------------------------------------------------------------------- /front-end/e2e/protractor.conf.js: -------------------------------------------------------------------------------- 1 | // @ts-check 2 | // Protractor configuration file, see link for more information 3 | // https://github.com/angular/protractor/blob/master/lib/config.ts 4 | 5 | const { SpecReporter } = require('jasmine-spec-reporter'); 6 | 7 | /** 8 | * @type { import("protractor").Config } 9 | */ 10 | exports.config = { 11 | allScriptsTimeout: 11000, 12 | specs: [ 13 | './src/**/*.e2e-spec.ts' 14 | ], 15 | capabilities: { 16 | 'browserName': 'chrome' 17 | }, 18 | directConnect: true, 19 | baseUrl: 'http://localhost:4200/', 20 | framework: 'jasmine', 21 | jasmineNodeOpts: { 22 | showColors: true, 23 | defaultTimeoutInterval: 30000, 24 | print: function() {} 25 | }, 26 | onPrepare() { 27 | require('ts-node').register({ 28 | project: require('path').join(__dirname, './tsconfig.json') 29 | }); 30 | jasmine.getEnv().addReporter(new SpecReporter({ spec: { displayStacktrace: true } })); 31 | } 32 | }; -------------------------------------------------------------------------------- /front-end/src/app/user-info/user-info.component.css: -------------------------------------------------------------------------------- 1 | body{ 2 | padding-top: 68px; 3 | padding-bottom: 50px; 4 | } 5 | .image-container { 6 | position: relative; 7 | } 8 | 9 | .image { 10 | opacity: 1; 11 | display: block; 12 | width: 100%; 13 | height: auto; 14 | transition: .5s ease; 15 | backface-visibility: hidden; 16 | } 17 | 18 | .middle { 19 | transition: .5s ease; 20 | opacity: 0; 21 | position: absolute; 22 | top: 50%; 23 | left: 50%; 24 | transform: translate(-50%, -50%); 25 | -ms-transform: translate(-50%, -50%); 26 | text-align: center; 27 | } 28 | 29 | .image-container:hover .image { 30 | opacity: 0.3; 31 | } 32 | 33 | .image-container:hover .middle { 34 | opacity: 1; 35 | } -------------------------------------------------------------------------------- /front-end/src/app/_helpers/auth.guard.ts: -------------------------------------------------------------------------------- 1 | import { Injectable } from '@angular/core'; 2 | import { Router, CanActivate, ActivatedRouteSnapshot, RouterStateSnapshot } from '@angular/router'; 3 | import { AuthenticationService } from '../_services/authentication.service'; 4 | 5 | @Injectable({ providedIn: 'root' }) 6 | export class AuthGuard implements CanActivate { 7 | constructor( 8 | private router: Router, 9 | private authenticationService: AuthenticationService 10 | ) { } 11 | 12 | canActivate(route: ActivatedRouteSnapshot, state: RouterStateSnapshot) { 13 | const currentUser = this.authenticationService.currentUserValue; 14 | if (currentUser) { 15 | // logged in so return true 16 | return true; 17 | } 18 | 19 | // not logged in so redirect to login page with the return url 20 | this.router.navigate(['/login'], { queryParams: { returnUrl: state.url } }); 21 | return false; 22 | } 23 | } -------------------------------------------------------------------------------- /front-end/src/app/_helpers/jwt.interceptor.ts: -------------------------------------------------------------------------------- 1 | import { Injectable } from '@angular/core'; 2 | import { HttpRequest, HttpHandler, HttpEvent, HttpInterceptor } from '@angular/common/http'; 3 | import { Observable } from 'rxjs'; 4 | import { AuthenticationService } from '../_services/authentication.service'; 5 | 6 | @Injectable() 7 | export class JwtInterceptor implements HttpInterceptor { 8 | constructor(private authenticationService: AuthenticationService) { } 9 | 10 | intercept(request: HttpRequest, next: HttpHandler): Observable> { 11 | // add authorization header with jwt token if available 12 | let currentUser = this.authenticationService.currentUserValue; 13 | if (currentUser && currentUser.token) { 14 | request = request.clone({ 15 | setHeaders: { 16 | Authorization: `Bearer ${currentUser.token}` 17 | } 18 | }); 19 | } 20 | 21 | return next.handle(request); 22 | } 23 | } -------------------------------------------------------------------------------- /front-end/src/app/user-info/user-info.component.ts: -------------------------------------------------------------------------------- 1 | import { Component, OnInit } from '@angular/core'; 2 | import { User } from '../_models'; 3 | import { ActivatedRoute } from '@angular/router'; 4 | import { UserService } from '../_services'; 5 | import { first } from 'rxjs/operators'; 6 | 7 | @Component({ 8 | selector: 'app-user-info', 9 | templateUrl: './user-info.component.html', 10 | styleUrls: ['./user-info.component.css'] 11 | }) 12 | export class UserInfoComponent implements OnInit { 13 | user: User; 14 | id: string; 15 | 16 | constructor( 17 | private route: ActivatedRoute, 18 | private userService: UserService 19 | ) { } 20 | 21 | ngOnInit() { 22 | this.getUser(); 23 | } 24 | 25 | getUser(): void { 26 | this.route.paramMap.subscribe(params => { 27 | this.id = params.get('id'); 28 | }); 29 | this.userService.getById(this.id).pipe(first()).subscribe(newObj => { 30 | console.log(newObj); 31 | this.user = newObj.user; 32 | }); 33 | } 34 | 35 | } 36 | -------------------------------------------------------------------------------- /front-end/src/app/_helpers/error.interceptor.ts: -------------------------------------------------------------------------------- 1 | import { Injectable } from '@angular/core'; 2 | import { HttpRequest, HttpHandler, HttpEvent, HttpInterceptor } from '@angular/common/http'; 3 | import { Observable, throwError } from 'rxjs'; 4 | import { catchError } from 'rxjs/operators'; 5 | import { AuthenticationService } from '../_services/authentication.service'; 6 | 7 | @Injectable() 8 | export class ErrorInterceptor implements HttpInterceptor { 9 | constructor(private authenticationService: AuthenticationService) { } 10 | 11 | intercept(request: HttpRequest, next: HttpHandler): Observable> { 12 | return next.handle(request).pipe(catchError(err => { 13 | if (err.status === 401 || err.status === 403) { 14 | // auto logout if 401 response returned from api 15 | this.authenticationService.logout(); 16 | } 17 | 18 | const error = err.error.message || err.statusText; 19 | return throwError(error); 20 | })) 21 | } 22 | } -------------------------------------------------------------------------------- /api/models/auction.js: -------------------------------------------------------------------------------- 1 | const mongoose = require('mongoose'); 2 | 3 | const auctionSchema = mongoose.Schema({ 4 | _id: mongoose.Schema.Types.ObjectId, 5 | //product: { type: mongoose.Schema.Types.ObjectId, red: 'Product' , required: true}, 6 | name: { type: String, required: true }, 7 | category: [{ type: String }], 8 | location: { type: String, required: true }, 9 | country: { type: String, required: true }, 10 | currently: { type: Number, required: true }, 11 | first_bid: { type: Number, required: true }, 12 | no_bids: { type: Number, default: 0 }, 13 | started: { type: Date }, 14 | ends: { type: Date, required: true }, 15 | description: { type: String }, 16 | latitude: { type: Number }, 17 | longitude: { type: Number }, 18 | seller: { type: mongoose.Schema.Types.ObjectId, red: 'User', required: true }, 19 | buy_price: { type: Number }, 20 | bids: [{ type: mongoose.Schema.Types.ObjectId, red: 'Bid' }], 21 | images: [{ type: String }] 22 | 23 | }); 24 | 25 | auctionSchema.index({ '$**': 'text' }); 26 | 27 | module.exports = mongoose.model('Auction', auctionSchema); 28 | -------------------------------------------------------------------------------- /api/models/user.js: -------------------------------------------------------------------------------- 1 | const mongoose = require('mongoose'); 2 | 3 | const userSchema = mongoose.Schema({ 4 | _id: mongoose.Schema.Types.ObjectId, 5 | username: { type: String, required: true , unique: true}, 6 | password: { type: String, required: true }, 7 | firstname: { type: String, required: true }, 8 | lastname: { type: String, required: true }, 9 | email: { type: String, required: true , unique: true}, 10 | phone: { type: String, required: true , unique: true}, 11 | address: { type: String, required: true }, 12 | city: { type: String, required: true }, 13 | afm: { type: String, required: true , unique: true}, 14 | rating: { type: Number, default: 0 }, 15 | role: { type: String, emum: ["user","admin"] , required: true }, 16 | seen: [{ type: mongoose.Schema.Types.ObjectId, red: 'Auctions' }], 17 | bid: [{ type: mongoose.Schema.Types.ObjectId, red: 'Auctions' }], 18 | recommendations: [{ type: mongoose.Schema.Types.ObjectId, red: 'Auctions' }], 19 | verified: { type: Boolean, default: false } 20 | }); 21 | 22 | module.exports = mongoose.model('User', userSchema); 23 | -------------------------------------------------------------------------------- /front-end/src/app/_helpers/must-match.validator.ts: -------------------------------------------------------------------------------- 1 | import { FormGroup } from '@angular/forms'; 2 | 3 | // custom validator to check that two fields match 4 | export function MustMatch(controlName: string, matchingControlName: string) { 5 | return (formGroup: FormGroup) => { 6 | const control = formGroup.controls[controlName]; 7 | const matchingControl = formGroup.controls[matchingControlName]; 8 | 9 | // return null if controls haven't initialised yet 10 | if (!control || !matchingControl) { 11 | return null; 12 | } 13 | 14 | // return null if another validator has already found an error on the matchingControl 15 | if (matchingControl.errors && !matchingControl.errors.mustMatch) { 16 | return null; 17 | } 18 | 19 | // set error on matchingControl if validation fails 20 | if (control.value !== matchingControl.value) { 21 | matchingControl.setErrors({ mustMatch: true }); 22 | } else { 23 | matchingControl.setErrors(null); 24 | } 25 | } 26 | } 27 | -------------------------------------------------------------------------------- /post_categories.js: -------------------------------------------------------------------------------- 1 | var fs = require("fs"); 2 | const axios = require('axios'); //npm install axios 3 | 4 | var text = fs.readFileSync("./new.xml"); 5 | var textByLine = text.toString().split("\n") 6 | const url='http://localhost:3000/items/'; 7 | var array = []; 8 | 9 | var config = { 10 | headers: {'Content-Type': 'application/xml'} 11 | }; 12 | axios.post(url, text, config) 13 | .then(res => { 14 | console.log(res); 15 | }) 16 | .catch(err => { 17 | console.log(err); 18 | }); 19 | return; 20 | var lines = textByLine; 21 | for(var line = 0; line < lines.length; line++){ 22 | //console.log(lines[line]); 23 | if (!array.includes(lines[line])) 24 | { 25 | axios.post(url, lines[line], config) 26 | .then(res => { 27 | console.log(lines[line]); 28 | //console.log(res); 29 | }) 30 | .catch(err => { 31 | console.log(err); 32 | }); 33 | array.push(lines[line]); 34 | } 35 | } -------------------------------------------------------------------------------- /front-end/README.md: -------------------------------------------------------------------------------- 1 | # FrontEnd 2 | 3 | This project was generated with [Angular CLI](https://github.com/angular/angular-cli) version 8.2.1. 4 | 5 | ## Development server 6 | 7 | Run `ng serve` for a dev server. Navigate to `http://localhost:4200/`. The app will automatically reload if you change any of the source files. 8 | 9 | ## Code scaffolding 10 | 11 | Run `ng generate component component-name` to generate a new component. You can also use `ng generate directive|pipe|service|class|guard|interface|enum|module`. 12 | 13 | ## Build 14 | 15 | Run `ng build` to build the project. The build artifacts will be stored in the `dist/` directory. Use the `--prod` flag for a production build. 16 | 17 | ## Running unit tests 18 | 19 | Run `ng test` to execute the unit tests via [Karma](https://karma-runner.github.io). 20 | 21 | ## Running end-to-end tests 22 | 23 | Run `ng e2e` to execute the end-to-end tests via [Protractor](http://www.protractortest.org/). 24 | 25 | ## Further help 26 | 27 | To get more help on the Angular CLI use `ng help` or go check out the [Angular CLI README](https://github.com/angular/angular-cli/blob/master/README.md). 28 | -------------------------------------------------------------------------------- /front-end/src/app/_services/auction.service.ts: -------------------------------------------------------------------------------- 1 | import { Injectable } from '@angular/core'; 2 | import { HttpClient } from '@angular/common/http'; 3 | import { Auction } from '../_models'; 4 | import { environment } from 'src/environments/environment'; 5 | import * as jwt_decode from "jwt-decode"; 6 | 7 | @Injectable({ providedIn: 'root' }) 8 | export class AuctionService { 9 | constructor(private http: HttpClient) { } 10 | 11 | getAll() { 12 | let currentUserJSON = JSON.parse(localStorage.getItem('currentUser')); 13 | let tokenInfo = jwt_decode(currentUserJSON.token); 14 | let id = tokenInfo.userId; 15 | 16 | return this.http.get(`${environment.apiUrl}/auctions/user/${id}`); 17 | } 18 | 19 | newForm(auction: Auction) { 20 | return this.http.post(`${environment.apiUrl}/auctions`, auction); 21 | } 22 | 23 | deleteAuction(id: string) { 24 | return this.http.delete(`${environment.apiUrl}/auctions/${id}`); 25 | } 26 | 27 | updateAuction(auction: Auction) { 28 | return this.http.put(`${environment.apiUrl}/auctions/${auction._id}`, auction); 29 | } 30 | } -------------------------------------------------------------------------------- /front-end/src/app/app.component.ts: -------------------------------------------------------------------------------- 1 | import { Component } from '@angular/core'; 2 | import { Router } from '@angular/router'; 3 | 4 | import { AuthenticationService, MessagesService } from './_services'; 5 | import { User, Role } from './_models'; 6 | 7 | import * as jwt_decode from "jwt-decode"; 8 | 9 | @Component({ 10 | selector: 'app', 11 | templateUrl: 'app.component.html', 12 | styleUrls: ['./app.component.css'] 13 | }) 14 | export class AppComponent { 15 | currentUser: User; 16 | 17 | constructor( 18 | private router: Router, 19 | private authenticationService: AuthenticationService, 20 | ) { 21 | this.authenticationService.currentUser.subscribe(x => this.currentUser = x); 22 | } 23 | 24 | get isAdmin() { 25 | let currentUserJSON = JSON.parse(localStorage.getItem('currentUser')); 26 | let tokenInfo = jwt_decode(currentUserJSON.token); 27 | const tokenRole = tokenInfo.role; 28 | return this.currentUser && tokenRole === Role.admin; 29 | } 30 | 31 | logout() { 32 | this.authenticationService.logout(); 33 | this.router.navigate(['/']); 34 | } 35 | } -------------------------------------------------------------------------------- /front-end/karma.conf.js: -------------------------------------------------------------------------------- 1 | // Karma configuration file, see link for more information 2 | // https://karma-runner.github.io/1.0/config/configuration-file.html 3 | 4 | module.exports = function (config) { 5 | config.set({ 6 | basePath: '', 7 | frameworks: ['jasmine', '@angular-devkit/build-angular'], 8 | plugins: [ 9 | require('karma-jasmine'), 10 | require('karma-chrome-launcher'), 11 | require('karma-jasmine-html-reporter'), 12 | require('karma-coverage-istanbul-reporter'), 13 | require('@angular-devkit/build-angular/plugins/karma') 14 | ], 15 | client: { 16 | clearContext: false // leave Jasmine Spec Runner output visible in browser 17 | }, 18 | coverageIstanbulReporter: { 19 | dir: require('path').join(__dirname, './coverage/front-end'), 20 | reports: ['html', 'lcovonly', 'text-summary'], 21 | fixWebpackSourcePaths: true 22 | }, 23 | reporters: ['progress', 'kjhtml'], 24 | port: 9876, 25 | colors: true, 26 | logLevel: config.LOG_INFO, 27 | autoWatch: true, 28 | browsers: ['Chrome'], 29 | singleRun: false, 30 | restartOnFileChange: true 31 | }); 32 | }; 33 | -------------------------------------------------------------------------------- /LICENSE.md: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2019 Konstantinos Paschopoulos & Hector Tavoularis 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /front-end/src/app/_helpers/role-guard.service.ts: -------------------------------------------------------------------------------- 1 | import { AuthenticationService } from '../_services'; 2 | import { Injectable } from '@angular/core'; 3 | import { CanActivate, ActivatedRouteSnapshot, RouterStateSnapshot, Router, Route } from '@angular/router'; 4 | import { Observable } from 'rxjs'; 5 | import * as jwt_decode from "jwt-decode"; 6 | 7 | @Injectable({ 8 | providedIn: 'root' 9 | }) 10 | export class RoleGuard implements CanActivate { 11 | 12 | 13 | constructor(private authenticationService: AuthenticationService, private router: Router) { 14 | } 15 | 16 | canActivate(next: ActivatedRouteSnapshot, state: RouterStateSnapshot): Observable | Promise | boolean { 17 | // Return is the user is not even logged in 18 | const currentUser = this.authenticationService.currentUserValue; 19 | if (currentUser) { 20 | let currentUserJSON = JSON.parse(localStorage.getItem('currentUser')); 21 | const user = jwt_decode(currentUserJSON.token); 22 | 23 | // Checking using the jwt and the passed data if the user is an admin 24 | if (user.role === next.data.role) { 25 | return true; 26 | } 27 | } 28 | 29 | // Navigate to not found page 30 | this.router.navigate(['page-not-found']); 31 | return false; 32 | } 33 | 34 | } 35 | -------------------------------------------------------------------------------- /front-end/src/app/recommendations/recommendations.component.ts: -------------------------------------------------------------------------------- 1 | import { Component, OnInit } from '@angular/core'; 2 | import * as jwt_decode from "jwt-decode"; 3 | import { UserService } from '../_services'; 4 | import { Auction } from '../_models'; 5 | import { first } from 'rxjs/internal/operators/first'; 6 | import { AlertService } from '../_alert'; 7 | 8 | @Component({ 9 | selector: 'app-recommendations', 10 | templateUrl: './recommendations.component.html', 11 | styleUrls: ['./recommendations.component.css'] 12 | }) 13 | export class RecommendationsComponent implements OnInit { 14 | auctions: Auction[]; 15 | 16 | constructor( 17 | private userService: UserService, 18 | private alertService: AlertService 19 | ) { } 20 | 21 | ngOnInit() { 22 | let currentUserJSON = JSON.parse(localStorage.getItem('currentUser')); 23 | let tokenInfo = jwt_decode(currentUserJSON.token); 24 | let id = tokenInfo.userId; 25 | 26 | this.userService.recommendations(id) 27 | .pipe(first()) 28 | .subscribe( 29 | data => { 30 | console.log(data); 31 | var newObj: any = data; 32 | this.auctions = newObj.recommendations; 33 | }, 34 | error => { 35 | this.alertService.error(error); 36 | }); 37 | } 38 | 39 | } 40 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "ebay_clone", 3 | "version": "1.0.0", 4 | "description": "A Ebay RESTful API clone", 5 | "main": "index.js", 6 | "scripts": { 7 | "test": "echo \"Error: no test specified\" && exit 1", 8 | "start": "nodemon server.js" 9 | }, 10 | "repository": { 11 | "type": "git", 12 | "url": "git+https://github.com/hectortav/Ebay_Clone.git" 13 | }, 14 | "keywords": [ 15 | "restful", 16 | "api", 17 | "ebay" 18 | ], 19 | "author": "Paschopoulos - Tavoularis", 20 | "license": "ISC", 21 | "bugs": { 22 | "url": "https://github.com/hectortav/Ebay_Clone/issues" 23 | }, 24 | "homepage": "https://github.com/hectortav/Ebay_Clone#readme", 25 | "dependencies": { 26 | "axios": "^0.19.0", 27 | "bcrypt": "^3.0.6", 28 | "bcryptjs": "^2.4.3", 29 | "body-parser": "^1.19.0", 30 | "body-parser-xml": "^1.1.0", 31 | "cors": "^2.8.5", 32 | "crypto": "^1.0.1", 33 | "easyxml": "^2.0.1", 34 | "express": "^4.17.1", 35 | "express-force-ssl": "^0.3.2", 36 | "jsonwebtoken": "^8.5.1", 37 | "lsh": "^1.0.6", 38 | "mongoose": "^5.7.1", 39 | "morgan": "^1.9.1", 40 | "nearest-neighbor": "0.0.3", 41 | "sqlite3": "^4.1.0", 42 | "xml2js": "^0.4.22", 43 | "xmlhttprequest": "^1.8.0" 44 | }, 45 | "devDependencies": { 46 | "nodemon": "^1.19.2" 47 | } 48 | } 49 | -------------------------------------------------------------------------------- /front-end/src/app/auction-info/auction-info.component.css: -------------------------------------------------------------------------------- 1 | .bids { 2 | margin: 0; 3 | padding: 0; 4 | } 5 | 6 | .bids li { 7 | padding: 10px 0; 8 | border-bottom: 2px solid #fff; 9 | display: flex; 10 | } 11 | 12 | .card-body { 13 | font-weight: normal; 14 | } 15 | 16 | input { 17 | margin-bottom: 10px; 18 | } 19 | 20 | .btn-warning { 21 | float: right; 22 | } 23 | 24 | .btn-danger { 25 | margin-left: 15px; 26 | float: right; 27 | } 28 | 29 | .buttons-container { 30 | margin-top: 35px; 31 | } 32 | 33 | .bids-container { 34 | margin-top: 60px; 35 | } 36 | 37 | .speech-bubble { 38 | position: relative; 39 | background: #7ec9d1; 40 | border-radius: .4em; 41 | } 42 | 43 | .speech-bubble:after { 44 | content: ''; 45 | position: absolute; 46 | bottom: 0; 47 | left: 50%; 48 | width: 0; 49 | height: 0; 50 | border: 28px solid transparent; 51 | border-top-color: #7ec9d1; 52 | border-bottom: 0; 53 | margin-left: -28px; 54 | margin-bottom: -28px; 55 | } 56 | 57 | textarea { 58 | outline: none; 59 | resize: none; 60 | background: transparent; 61 | border: 0 none; 62 | width: 100%; 63 | height: 100%; 64 | box-sizing: border-box; 65 | background-color: #7EC9D2; 66 | } 67 | 68 | input[type="file"] { 69 | display: none; 70 | } 71 | 72 | .custom-file-upload { 73 | border: 1px solid #ccc; 74 | display: inline-block; 75 | padding: 6px 12px; 76 | cursor: pointer; 77 | } -------------------------------------------------------------------------------- /front-end/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "front-end", 3 | "version": "0.0.0", 4 | "scripts": { 5 | "ng": "ng", 6 | "start": "ng serve", 7 | "build": "ng build", 8 | "test": "ng test", 9 | "lint": "ng lint", 10 | "e2e": "ng e2e" 11 | }, 12 | "private": true, 13 | "dependencies": { 14 | "@angular/animations": "^8.2.7", 15 | "@angular/common": "^8.2.7", 16 | "@angular/compiler": "^8.2.7", 17 | "@angular/core": "^8.2.7", 18 | "@angular/forms": "^8.2.7", 19 | "@angular/platform-browser": "^8.2.7", 20 | "@angular/platform-browser-dynamic": "^8.2.7", 21 | "@angular/router": "^8.2.7", 22 | "jwt-decode": "^2.2.0", 23 | "ngx-pagination": "^4.1.0", 24 | "ol": "^5.3.3", 25 | "rxjs": "~6.4.0", 26 | "tslib": "^1.10.0", 27 | "zone.js": "~0.9.1" 28 | }, 29 | "devDependencies": { 30 | "@angular-devkit/build-angular": "^0.802.2", 31 | "@angular/cli": "^8.3.5", 32 | "@angular/compiler-cli": "^8.2.7", 33 | "@angular/language-service": "^8.2.7", 34 | "@types/jasmine": "~3.3.8", 35 | "@types/jasminewd2": "~2.0.3", 36 | "@types/node": "~8.9.4", 37 | "codelyzer": "^5.1.1", 38 | "jasmine-core": "~3.4.0", 39 | "jasmine-spec-reporter": "~4.2.1", 40 | "karma": "~4.1.0", 41 | "karma-chrome-launcher": "~2.2.0", 42 | "karma-coverage-istanbul-reporter": "~2.0.1", 43 | "karma-jasmine": "~2.0.1", 44 | "karma-jasmine-html-reporter": "^1.4.0", 45 | "protractor": "~5.4.0", 46 | "ts-node": "~7.0.0", 47 | "tslint": "~5.15.0", 48 | "typescript": "~3.5.3" 49 | } 50 | } 51 | -------------------------------------------------------------------------------- /front-end/src/app/manage-auctions/manage-auctions.component.html: -------------------------------------------------------------------------------- 1 |
2 |
3 | 7 | 8 |
9 | 10 |
11 |
12 | 13 |
14 |
My auctions
15 |
16 |
    17 |
  • 20 | {{ myAuction.name }} 21 |
    22 | 23 |
    24 |
  • 25 |
26 |
27 | 28 |
29 | 32 | 33 |
34 | 35 |
36 |
37 |
-------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # Logs 2 | logs 3 | *.log 4 | npm-debug.log* 5 | yarn-debug.log* 6 | yarn-error.log* 7 | lerna-debug.log* 8 | 9 | # Diagnostic reports (https://nodejs.org/api/report.html) 10 | report.[0-9]*.[0-9]*.[0-9]*.[0-9]*.json 11 | 12 | # Runtime data 13 | pids 14 | *.pid 15 | *.seed 16 | *.pid.lock 17 | 18 | # Directory for instrumented libs generated by jscoverage/JSCover 19 | lib-cov 20 | 21 | # Coverage directory used by tools like istanbul 22 | coverage 23 | *.lcov 24 | 25 | # nyc test coverage 26 | .nyc_output 27 | 28 | # Grunt intermediate storage (https://gruntjs.com/creating-plugins#storing-task-files) 29 | .grunt 30 | 31 | # Bower dependency directory (https://bower.io/) 32 | bower_components 33 | 34 | # node-waf configuration 35 | .lock-wscript 36 | 37 | # Compiled binary addons (https://nodejs.org/api/addons.html) 38 | build/Release 39 | 40 | # Dependency directories 41 | node_modules/ 42 | jspm_packages/ 43 | 44 | # TypeScript v1 declaration files 45 | typings/ 46 | 47 | # TypeScript cache 48 | *.tsbuildinfo 49 | 50 | # Optional npm cache directory 51 | .npm 52 | 53 | # Optional eslint cache 54 | .eslintcache 55 | 56 | # Optional REPL history 57 | .node_repl_history 58 | 59 | # Output of 'npm pack' 60 | *.tgz 61 | 62 | # Yarn Integrity file 63 | .yarn-integrity 64 | 65 | # dotenv environment variables file 66 | .env 67 | .env.test 68 | 69 | # parcel-bundler cache (https://parceljs.org/) 70 | .cache 71 | 72 | # next.js build output 73 | .next 74 | 75 | # nuxt.js build output 76 | .nuxt 77 | 78 | # vuepress build output 79 | .vuepress/dist 80 | 81 | # Serverless directories 82 | .serverless/ 83 | 84 | # FuseBox cache 85 | .fusebox/ 86 | 87 | # DynamoDB Local files 88 | .dynamodb/ 89 | /build/ 90 | -------------------------------------------------------------------------------- /front-end/src/app/_services/user.service.ts: -------------------------------------------------------------------------------- 1 | import { Injectable } from '@angular/core'; 2 | import { HttpClient, HttpHeaders } from '@angular/common/http'; 3 | import { User } from '../_models'; 4 | import { environment } from 'src/environments/environment'; 5 | import { Observable } from 'rxjs'; 6 | 7 | @Injectable({ providedIn: 'root' }) 8 | export class UserService { 9 | constructor(private http: HttpClient) { } 10 | 11 | seenAuction(_id: string, seen: string) { 12 | return this.http.post(`${environment.apiUrl}/users/seen`, { _id, seen }); 13 | } 14 | 15 | getAll() { 16 | return this.http.get(`${environment.apiUrl}/users`); 17 | } 18 | 19 | register(user: User) { 20 | return this.http.post(`${environment.apiUrl}/users/signup`, user); 21 | } 22 | 23 | getById(id: string) { 24 | return this.http.get(`${environment.apiUrl}/users/id/${id}`); 25 | } 26 | 27 | getUnverified() { 28 | return this.http.get(`${environment.apiUrl}/users/unverified`); 29 | } 30 | 31 | verifyUser(id: string) { 32 | return this.http.post(`${environment.apiUrl}/users/verify/${id}`, null); 33 | } 34 | 35 | unverifyUser(id: string) { 36 | return this.http.post(`${environment.apiUrl}/users/unverify/${id}`, null); 37 | } 38 | 39 | deleteUser(user: User | number): Observable { 40 | const id = typeof user === 'number' ? user : user._id; 41 | const url = `${environment.apiUrl}/users/${id}`; 42 | 43 | return this.http.delete(url); 44 | } 45 | 46 | recommendations(id: string) { 47 | return this.http.post(`${environment.apiUrl}/users/recommendations/${id}`, null); 48 | } 49 | } -------------------------------------------------------------------------------- /front-end/src/app/_services/messages.service.ts: -------------------------------------------------------------------------------- 1 | import { Injectable } from '@angular/core'; 2 | import { HttpClient } from '@angular/common/http'; 3 | import { Message } from '../_models'; 4 | import { environment } from 'src/environments/environment'; 5 | import * as jwt_decode from "jwt-decode"; 6 | 7 | @Injectable({ 8 | providedIn: 'root' 9 | }) 10 | export class MessagesService { 11 | 12 | constructor(private http: HttpClient) { } 13 | 14 | getSent() { 15 | let currentUserJSON = JSON.parse(localStorage.getItem('currentUser')); 16 | let tokenInfo = jwt_decode(currentUserJSON.token); 17 | let id = tokenInfo.userId; 18 | 19 | return this.http.get(`${environment.apiUrl}/messages/${id}/sent`); 20 | } 21 | 22 | getReceived() { 23 | let currentUserJSON = JSON.parse(localStorage.getItem('currentUser')); 24 | let tokenInfo = jwt_decode(currentUserJSON.token); 25 | let id = tokenInfo.userId; 26 | 27 | return this.http.get(`${environment.apiUrl}/messages/${id}/received`); 28 | } 29 | 30 | newMessage(message: Message) { 31 | return this.http.post(`${environment.apiUrl}/messages`, message); 32 | } 33 | 34 | updateMessage(message: Message) { 35 | return this.http.put(`${environment.apiUrl}/messages/${message._id}`, message); 36 | } 37 | 38 | deleteMessage(message: Message) { 39 | return this.http.delete(`${environment.apiUrl}/messages/${message._id}`); 40 | } 41 | 42 | getUnread() { 43 | let currentUserJSON = JSON.parse(localStorage.getItem('currentUser')); 44 | let tokenInfo = jwt_decode(currentUserJSON.token); 45 | let id = tokenInfo.userId; 46 | 47 | return this.http.get(`${environment.apiUrl}/messages/${id}/received/unread`); 48 | } 49 | } 50 | -------------------------------------------------------------------------------- /front-end/src/app/_services/authentication.service.ts: -------------------------------------------------------------------------------- 1 | import { Injectable } from '@angular/core'; 2 | import { HttpClient } from '@angular/common/http'; 3 | import { BehaviorSubject, Observable } from 'rxjs'; 4 | import { map } from 'rxjs/operators'; 5 | import { User } from '../_models'; 6 | import { environment } from 'src/environments/environment'; 7 | 8 | @Injectable({ providedIn: 'root' }) 9 | export class AuthenticationService { 10 | private currentUserSubject: BehaviorSubject; 11 | public currentUser: Observable; 12 | 13 | constructor(private http: HttpClient) { 14 | this.currentUserSubject = new BehaviorSubject(JSON.parse(localStorage.getItem('currentUser'))); 15 | this.currentUser = this.currentUserSubject.asObservable(); 16 | } 17 | 18 | public get currentUserValue(): User { 19 | return this.currentUserSubject.value; 20 | } 21 | 22 | login(email: string, password: string) { 23 | return this.http.post(`${environment.apiUrl}/users/login`, { email, password }) 24 | .pipe(map(user => { 25 | // login successful if there's a jwt token in the response 26 | if (user && user.token) { 27 | // store user details and jwt token in local storage to keep user logged in between page refreshes 28 | localStorage.setItem('currentUser', JSON.stringify(user)); 29 | this.currentUserSubject.next(user); 30 | } 31 | 32 | return user; 33 | })); 34 | } 35 | 36 | logout() { 37 | // remove user from local storage to log user out 38 | localStorage.removeItem('currentUser'); 39 | this.currentUserSubject.next(null); 40 | } 41 | } -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Ebay Clone (REST API) 2 | 3 | Auction website with a REST API. 4 | Made using MongoDB, Express.js, Angular 8 and Node.js. 5 | 6 | ## Usage 7 | 8 | ```bash 9 | npm update 10 | npm start 11 | cd ./front-end 12 | npm update 13 | ng serve –o 14 | ``` 15 | 16 | ## Contributors 17 | Front-End: [KonstantinosPaschopoulos](https://github.com/KonstantinosPaschopoulos) 18 | 19 | Back-End: [hectortav](https://github.com/hectortav) 20 | 21 | ## Screenshots 22 | Landing Page 23 | ![alt text](https://raw.githubusercontent.com/hectortav/Ebay_Clone/master/Screenshots/landing_page.png) 24 | 25 | Login Page 26 | ![alt text](https://raw.githubusercontent.com/hectortav/Ebay_Clone/master/Screenshots/long_page.png) 27 | 28 | Register Page 29 | ![alt text](https://raw.githubusercontent.com/hectortav/Ebay_Clone/master/Screenshots/register_page.png) 30 | 31 | Front Page 32 | ![alt text](https://raw.githubusercontent.com/hectortav/Ebay_Clone/master/Screenshots/front-page.png) 33 | 34 | Auction Page 35 | ![alt text](https://raw.githubusercontent.com/hectortav/Ebay_Clone/master/Screenshots/auction_page.png) 36 | 37 | Manage Auctions Page 38 | ![alt text](https://raw.githubusercontent.com/hectortav/Ebay_Clone/master/Screenshots/manage_auctions_page.png) 39 | 40 | Email Page 41 | ![alt text](https://raw.githubusercontent.com/hectortav/Ebay_Clone/master/Screenshots/email_page.png) 42 | 43 | Admin Page 44 | ![alt text](https://raw.githubusercontent.com/hectortav/Ebay_Clone/master/Screenshots/admin_page.png) 45 | 46 | User Page 47 | ![alt text](https://raw.githubusercontent.com/hectortav/Ebay_Clone/master/Screenshots/user_page.png) 48 | 49 | ## License 50 | [MIT](https://choosealicense.com/licenses/mit/) 51 | 52 | 53 | 54 | 55 | 56 | 57 | 58 | 59 | 60 | 61 | 62 | 63 | 64 | 65 | 66 | 67 | 68 | 69 | 70 | 71 | 72 | 73 | 74 | 75 | 76 | -------------------------------------------------------------------------------- /front-end/src/app/login/login.component.html: -------------------------------------------------------------------------------- 1 |
2 |
3 | logo 4 |

Login

5 |
to continue to eLagoon
6 |
7 | 8 |
9 |
10 |
11 | 12 | 14 |
15 |
Email is required
16 |
17 |
18 |
19 | 20 | 22 |
23 |
Password is required
24 |
25 |
26 |
27 | 31 | Register 32 |
33 |
34 |
35 |
-------------------------------------------------------------------------------- /front-end/src/app/_alert/alert.component.ts: -------------------------------------------------------------------------------- 1 | import { Component, OnInit, OnDestroy, Input } from '@angular/core'; 2 | import { Subscription } from 'rxjs'; 3 | 4 | import { Alert, AlertType } from './alert.model'; 5 | import { AlertService } from './alert.service'; 6 | 7 | @Component({ selector: 'alert', templateUrl: 'alert.component.html' }) 8 | export class AlertComponent implements OnInit, OnDestroy { 9 | @Input() id: string; 10 | 11 | alerts: Alert[] = []; 12 | subscription: Subscription; 13 | 14 | constructor(private alertService: AlertService) { } 15 | 16 | ngOnInit() { 17 | this.subscription = this.alertService.onAlert(this.id) 18 | .subscribe(alert => { 19 | if (!alert.message) { 20 | // clear alerts when an empty alert is received 21 | this.alerts = []; 22 | return; 23 | } 24 | 25 | // add alert to array 26 | this.alerts.push(alert); 27 | }); 28 | } 29 | 30 | ngOnDestroy() { 31 | // unsubscribe to avoid memory leaks 32 | this.subscription.unsubscribe(); 33 | } 34 | 35 | removeAlert(alert: Alert) { 36 | // remove specified alert from array 37 | this.alerts = this.alerts.filter(x => x !== alert); 38 | } 39 | 40 | cssClass(alert: Alert) { 41 | if (!alert) { 42 | return; 43 | } 44 | 45 | // return css class based on alert type 46 | switch (alert.type) { 47 | case AlertType.Success: 48 | return 'alert alert-success'; 49 | case AlertType.Error: 50 | return 'alert alert-danger'; 51 | case AlertType.Info: 52 | return 'alert alert-info'; 53 | case AlertType.Warning: 54 | return 'alert alert-warning'; 55 | } 56 | } 57 | } -------------------------------------------------------------------------------- /front-end/src/app/map-details/map-details.component.ts: -------------------------------------------------------------------------------- 1 | import { Component, AfterViewInit, Input, OnInit } from '@angular/core'; 2 | import { Auction } from '../_models'; 3 | import OlMap from 'ol/Map'; 4 | import OlXYZ from 'ol/source/XYZ'; 5 | import OlTileLayer from 'ol/layer/Tile'; 6 | import OlView from 'ol/View'; 7 | 8 | import { fromLonLat } from 'ol/proj'; 9 | 10 | import OlVectorSource from 'ol/source/Vector'; 11 | import OlVectorLayer from 'ol/layer/Vector'; 12 | import OlFeature from 'ol/Feature'; 13 | import OlPoint from 'ol/geom/Point'; 14 | 15 | @Component({ 16 | selector: 'app-map-details', 17 | templateUrl: './map-details.component.html', 18 | styleUrls: ['./map-details.component.css'] 19 | }) 20 | export class MapDetailsComponent implements AfterViewInit { 21 | @Input() auction: Auction; 22 | 23 | map: OlMap; 24 | source: OlXYZ; 25 | layer: OlTileLayer; 26 | view: OlView; 27 | 28 | vectorSource: OlVectorSource; 29 | vectorLayer: OlVectorLayer; 30 | marker: OlFeature; 31 | 32 | constructor() { } 33 | 34 | ngAfterViewInit() { 35 | // Marker and feature 36 | this.marker = new OlFeature({ 37 | // Added fromLonLat 38 | geometry: new OlPoint(fromLonLat([this.auction.longitude, this.auction.latitude])) 39 | }); 40 | 41 | this.vectorSource = new OlVectorSource({ 42 | features: [this.marker] 43 | }); 44 | 45 | this.vectorLayer = new OlVectorLayer({ 46 | source: this.vectorSource 47 | }); 48 | 49 | this.source = new OlXYZ({ 50 | url: 'https://tile.osm.org/{z}/{x}/{y}.png' 51 | }); 52 | 53 | this.layer = new OlTileLayer({ 54 | source: this.source 55 | }); 56 | 57 | this.view = new OlView({ 58 | center: fromLonLat([this.auction.longitude, this.auction.latitude]), 59 | zoom: 14 60 | }); 61 | 62 | this.map = new OlMap({ 63 | target: 'map', 64 | layers: [this.layer, this.vectorLayer], 65 | view: this.view 66 | }); 67 | } 68 | } 69 | -------------------------------------------------------------------------------- /front-end/src/app/manage-auctions/manage-auctions.component.ts: -------------------------------------------------------------------------------- 1 | import { Component, OnInit } from '@angular/core'; 2 | import { first } from 'rxjs/operators'; 3 | import { AuctionService } from '../_services'; 4 | import { Auction } from '../_models'; 5 | 6 | @Component({ 7 | selector: 'app-manage-auctions', 8 | templateUrl: './manage-auctions.component.html', 9 | styleUrls: ['./manage-auctions.component.css'] 10 | }) 11 | export class ManageAuctionsComponent implements OnInit { 12 | openform: boolean; 13 | openRecs: boolean = false; 14 | myAuctions: Auction[]; 15 | recentAuctions: Auction[]; 16 | selectedAuction: Auction; 17 | loading = false; 18 | 19 | constructor( 20 | private auctionService: AuctionService 21 | ) { } 22 | 23 | ngOnInit() { 24 | this.openform = false; 25 | 26 | this.loadAllAuctions(); 27 | } 28 | 29 | // Toggles the new auction form on click 30 | onClickToggleForm() { 31 | this.openform = !this.openform; 32 | return this.openform; 33 | } 34 | 35 | private loadAllAuctions() { 36 | this.loading = true; 37 | this.auctionService.getAll().pipe(first()).subscribe(res => { 38 | this.loading = false; 39 | let newObj: any = res; 40 | this.myAuctions = newObj.auctions; 41 | }); 42 | } 43 | 44 | onSelect(auction: Auction): void { 45 | this.selectedAuction = auction; 46 | } 47 | 48 | calculateClasses(auction: Auction) { 49 | // Calculating the color of the list item based on the date 50 | let corrEnds = new Date(auction.ends); 51 | let currDate = new Date(); 52 | let state: boolean; 53 | if (auction.started != null) { 54 | let corrStarted = new Date(auction.started); 55 | if ((currDate > corrStarted) && (currDate < corrEnds)) { 56 | state = true; 57 | } 58 | } 59 | 60 | if (currDate > corrEnds) { 61 | state = false; 62 | } 63 | 64 | return { 65 | 'list-group-item-success': state === true, 66 | 'list-group-item-danger': state === false 67 | } 68 | } 69 | 70 | getRecs() { 71 | this.openRecs = true; 72 | return this.openRecs; 73 | } 74 | } 75 | -------------------------------------------------------------------------------- /app.js: -------------------------------------------------------------------------------- 1 | const morgan = require('morgan'); 2 | const bodyParser = require('body-parser'); 3 | require('body-parser-xml')(bodyParser); 4 | const mongoose = require('mongoose'); 5 | var express = require('express') 6 | var cors = require('cors') 7 | var app = express() 8 | app.use(cors()) 9 | app.options('*', cors()) 10 | 11 | const userRoutes = require('./api/routes/users'); 12 | const productRoutes = require('./api/routes/products'); 13 | const auctionRoutes = require('./api/routes/auctions'); 14 | const bidRoutes = require('./api/routes/bids'); 15 | const categoryRoutes = require('./api/routes/categories'); 16 | const messageRoutes = require('./api/routes/messages'); 17 | const itemRoutes = require('./api/routes/items'); 18 | 19 | 20 | 21 | 22 | mongoose.connect('mongodb+srv://admin:' + process.env.MONGO_ATLAS_PW + '@cluster0-9xgg4.mongodb.net/test?retryWrites=true&w=majority', 23 | { 24 | useNewUrlParser: true, 25 | useCreateIndex: true 26 | }); 27 | mongoose.Promise = global.Promise; 28 | 29 | app.use(morgan('dev')); 30 | app.use(bodyParser.urlencoded({ extended: false, limit: '10mb' })); 31 | app.use(bodyParser.json({ limit: '10mb' })); 32 | app.use(bodyParser.xml()); 33 | 34 | 35 | app.use((req, res, next) => { 36 | res.header("Access-Control-Allow-Origin", "*"); 37 | res.header( 38 | "Access-Controll-Allow-Headers", 39 | "Origin, X-Requested-Woth, Content-Type, Accept, Authorization" 40 | ); 41 | if (req.method === 'OPTIONS') { 42 | res.header('Access-Control-Allow-Methods', 'PUT, POST, PATCH, DELETE, GET'); 43 | return res.status(200).json({}); 44 | } 45 | next(); 46 | }); 47 | 48 | app.use('/products', productRoutes); 49 | app.use('/auctions', auctionRoutes); 50 | app.use('/users', userRoutes); 51 | app.use('/bids', bidRoutes); 52 | app.use('/categories', categoryRoutes); 53 | app.use('/messages', messageRoutes); 54 | app.use('/items', itemRoutes); 55 | 56 | app.use((req, res, next) => { 57 | const error = new Error('Not Found'); 58 | error.status = 404; 59 | next(error); 60 | }); 61 | 62 | app.use((error, req, res, next) => { 63 | res.status(error.status || 500); 64 | res.json({ 65 | error: { 66 | message: error.message 67 | } 68 | }); 69 | }); 70 | 71 | module.exports = app; 72 | -------------------------------------------------------------------------------- /front-end/src/app/_services/auctions.service.ts: -------------------------------------------------------------------------------- 1 | import { Injectable } from '@angular/core'; 2 | import { HttpClient } from '@angular/common/http'; 3 | import { Auction } from '../_models'; 4 | import { environment } from 'src/environments/environment'; 5 | 6 | import { Observable, of } from 'rxjs'; 7 | import { catchError, map, tap } from 'rxjs/operators'; 8 | 9 | @Injectable({ providedIn: 'root' }) 10 | export class AuctionsService { 11 | constructor(private http: HttpClient) { } 12 | 13 | private handleError(operation = 'operation', result?: T) { 14 | return (error: any): Observable => { 15 | console.error(error); // log to console instead 16 | console.log(`${operation} failed: ${error.message}`); 17 | 18 | return of(result as T); 19 | }; 20 | } 21 | 22 | getAllAuctions() { 23 | return this.http.get(`${environment.apiUrl}/auctions`); 24 | } 25 | 26 | getPageAuctions(page: any) { 27 | return this.http.get(`${environment.apiUrl}/auctions?page=${page}`); 28 | } 29 | 30 | getAuction(id) { 31 | return this.http.get(`${environment.apiUrl}/auctions/${id}`); 32 | } 33 | 34 | getCategories() { 35 | return this.http.get(`${environment.apiUrl}/categories`); 36 | } 37 | 38 | searchAuctions(category: string, text: string, price: any, location: string, page: any) { 39 | return this.http.get(`${environment.apiUrl}/auctions?page=${page}&category=${category}&text=${text}&price=${price}&location=${location}`).pipe( 40 | tap(_ => console.log(`found matching auctions`)), 41 | catchError(this.handleError('searchAuctions', [])) 42 | ); 43 | } 44 | 45 | searchCategory(term: string, page: any) { 46 | if (!term.trim()) { 47 | // if not search term, return empty array. 48 | return of([]); 49 | } 50 | 51 | return this.http.get(`${environment.apiUrl}/auctions?page=${page}&category=${term}`).pipe( 52 | tap(_ => console.log(`found auctions matching "${term}"`)), 53 | catchError(this.handleError('searchAuctions', [])) 54 | ); 55 | } 56 | } -------------------------------------------------------------------------------- /front-end/src/app/_alert/alert.service.ts: -------------------------------------------------------------------------------- 1 | import { Injectable } from '@angular/core'; 2 | import { Router, NavigationStart } from '@angular/router'; 3 | import { Observable, Subject } from 'rxjs'; 4 | import { filter } from 'rxjs/operators'; 5 | 6 | import { Alert, AlertType } from './alert.model'; 7 | 8 | @Injectable({ providedIn: 'root' }) 9 | export class AlertService { 10 | private subject = new Subject(); 11 | private keepAfterRouteChange = false; 12 | 13 | constructor(private router: Router) { 14 | // clear alert messages on route change unless 'keepAfterRouteChange' flag is true 15 | this.router.events.subscribe(event => { 16 | if (event instanceof NavigationStart) { 17 | if (this.keepAfterRouteChange) { 18 | // only keep for a single route change 19 | this.keepAfterRouteChange = false; 20 | } else { 21 | // clear alert messages 22 | this.clear(); 23 | } 24 | } 25 | }); 26 | } 27 | 28 | // enable subscribing to alerts observable 29 | onAlert(alertId?: string): Observable { 30 | return this.subject.asObservable().pipe(filter(x => x && x.alertId === alertId)); 31 | } 32 | 33 | // convenience methods 34 | success(message: string, alertId?: string) { 35 | this.alert(new Alert({ message, type: AlertType.Success, alertId })); 36 | } 37 | 38 | error(message: string, alertId?: string) { 39 | this.alert(new Alert({ message, type: AlertType.Error, alertId })); 40 | } 41 | 42 | info(message: string, alertId?: string) { 43 | this.alert(new Alert({ message, type: AlertType.Info, alertId })); 44 | } 45 | 46 | warn(message: string, alertId?: string) { 47 | this.alert(new Alert({ message, type: AlertType.Warning, alertId })); 48 | } 49 | 50 | // main alert method 51 | alert(alert: Alert) { 52 | this.keepAfterRouteChange = alert.keepAfterRouteChange; 53 | this.subject.next(alert); 54 | } 55 | 56 | // clear alerts 57 | clear(alertId?: string) { 58 | this.subject.next(new Alert({ alertId })); 59 | } 60 | } -------------------------------------------------------------------------------- /front-end/src/app/app.routing.ts: -------------------------------------------------------------------------------- 1 | import { Routes, RouterModule } from '@angular/router'; 2 | import { HomeComponent } from './home'; 3 | import { LoginComponent } from './login'; 4 | import { AuthGuard, RoleGuard } from './_helpers'; 5 | import { RegisterComponent } from './register/register.component'; 6 | import { PageNotFoundComponent } from './page-not-found/page-not-found.component'; 7 | import { ThanksSignupComponent } from './thanks-signup/thanks-signup.component'; 8 | import { ManageAuctionsComponent } from './manage-auctions/manage-auctions.component'; 9 | import { AuctionDetailComponent } from './auction-detail/auction-detail.component'; 10 | import { LandingComponent } from './landing/landing.component'; 11 | import { AdminComponent } from './admin/admin.component'; 12 | import { EmailComponent } from './email/email.component'; 13 | import { InboxComponent } from './inbox/inbox.component'; 14 | import { SentComponent } from './sent/sent.component'; 15 | import { UserInfoComponent } from './user-info/user-info.component' 16 | 17 | const routes: Routes = [ 18 | { path: '', component: LandingComponent }, 19 | { path: 'home', component: HomeComponent }, 20 | { path: 'login', component: LoginComponent }, 21 | { path: 'register', component: RegisterComponent }, 22 | { path: 'thanks-signup', component: ThanksSignupComponent }, 23 | { path: 'manage-auctions', component: ManageAuctionsComponent, canActivate: [AuthGuard] }, 24 | { path: 'auction-detail/:id', component: AuctionDetailComponent }, 25 | { path: 'admin', component: AdminComponent, canActivate: [RoleGuard], data: { role: 'admin' } }, 26 | { path: 'admin/users/:id', component: UserInfoComponent, canActivate: [RoleGuard], data: { role: 'admin' } }, 27 | { 28 | path: 'email', component: EmailComponent, canActivate: [AuthGuard], 29 | children: [ 30 | { path: 'inbox', component: InboxComponent }, 31 | { path: 'sent', component: SentComponent }, 32 | { path: '', redirectTo: 'inbox', pathMatch: 'full' } 33 | ] 34 | }, 35 | 36 | // Should be the last route, in order to redirect to 404 page 37 | { path: '**', component: PageNotFoundComponent } 38 | ]; 39 | 40 | export const appRoutingModule = RouterModule.forRoot(routes, { scrollPositionRestoration: 'enabled' }); -------------------------------------------------------------------------------- /front-end/src/app/login/login.component.ts: -------------------------------------------------------------------------------- 1 | import { Component, OnInit } from '@angular/core'; 2 | import { Router, ActivatedRoute } from '@angular/router'; 3 | import { FormBuilder, FormGroup, Validators } from '@angular/forms'; 4 | import { first } from 'rxjs/operators'; 5 | import { AuthenticationService } from '../_services/authentication.service'; 6 | import { AlertService } from '../_alert'; 7 | 8 | @Component({ templateUrl: 'login.component.html' }) 9 | export class LoginComponent implements OnInit { 10 | loginForm: FormGroup; 11 | loading = false; 12 | submitted = false; 13 | returnUrl: string; 14 | error = ''; 15 | 16 | constructor( 17 | private formBuilder: FormBuilder, 18 | private route: ActivatedRoute, 19 | private router: Router, 20 | private authenticationService: AuthenticationService, 21 | private alertService: AlertService 22 | ) { 23 | // redirect to home if already logged in 24 | if (this.authenticationService.currentUserValue) { 25 | this.router.navigate(['/home']); 26 | } 27 | } 28 | 29 | ngOnInit() { 30 | this.loginForm = this.formBuilder.group({ 31 | email: ['', Validators.required], 32 | password: ['', Validators.required] 33 | }); 34 | 35 | // get return url from route parameters or default to '/' 36 | this.returnUrl = this.route.snapshot.queryParams['returnUrl'] || '/home'; 37 | } 38 | 39 | // convenience getter for easy access to form fields 40 | get f() { return this.loginForm.controls; } 41 | 42 | onSubmit() { 43 | this.submitted = true; 44 | 45 | // reset alerts on submit 46 | this.alertService.clear(); 47 | 48 | // stop here if form is invalid 49 | if (this.loginForm.invalid) { 50 | return; 51 | } 52 | 53 | this.loading = true; 54 | this.authenticationService.login(this.f.email.value, this.f.password.value) 55 | .pipe(first()) 56 | .subscribe( 57 | data => { 58 | this.router.navigate([this.returnUrl]); 59 | }, 60 | error => { 61 | this.alertService.error(error); 62 | this.error = error; 63 | this.loading = false; 64 | }); 65 | } 66 | } 67 | -------------------------------------------------------------------------------- /front-end/tslint.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": "tslint:recommended", 3 | "rules": { 4 | "array-type": false, 5 | "arrow-parens": false, 6 | "deprecation": { 7 | "severity": "warning" 8 | }, 9 | "component-class-suffix": true, 10 | "contextual-lifecycle": true, 11 | "directive-class-suffix": true, 12 | "directive-selector": [ 13 | true, 14 | "attribute", 15 | "app", 16 | "camelCase" 17 | ], 18 | "component-selector": [ 19 | true, 20 | "element", 21 | "app", 22 | "kebab-case" 23 | ], 24 | "import-blacklist": [ 25 | true, 26 | "rxjs/Rx" 27 | ], 28 | "interface-name": false, 29 | "max-classes-per-file": false, 30 | "max-line-length": [ 31 | true, 32 | 140 33 | ], 34 | "member-access": false, 35 | "member-ordering": [ 36 | true, 37 | { 38 | "order": [ 39 | "static-field", 40 | "instance-field", 41 | "static-method", 42 | "instance-method" 43 | ] 44 | } 45 | ], 46 | "no-consecutive-blank-lines": false, 47 | "no-console": [ 48 | true, 49 | "debug", 50 | "info", 51 | "time", 52 | "timeEnd", 53 | "trace" 54 | ], 55 | "no-empty": false, 56 | "no-inferrable-types": [ 57 | true, 58 | "ignore-params" 59 | ], 60 | "no-non-null-assertion": true, 61 | "no-redundant-jsdoc": true, 62 | "no-switch-case-fall-through": true, 63 | "no-use-before-declare": true, 64 | "no-var-requires": false, 65 | "object-literal-key-quotes": [ 66 | true, 67 | "as-needed" 68 | ], 69 | "object-literal-sort-keys": false, 70 | "ordered-imports": false, 71 | "quotemark": [ 72 | true, 73 | "single" 74 | ], 75 | "trailing-comma": false, 76 | "no-conflicting-lifecycle": true, 77 | "no-host-metadata-property": true, 78 | "no-input-rename": true, 79 | "no-inputs-metadata-property": true, 80 | "no-output-native": true, 81 | "no-output-on-prefix": true, 82 | "no-output-rename": true, 83 | "no-outputs-metadata-property": true, 84 | "template-banana-in-box": true, 85 | "template-no-negated-async": true, 86 | "use-lifecycle-interface": true, 87 | "use-pipe-transform-interface": true 88 | }, 89 | "rulesDirectory": [ 90 | "codelyzer" 91 | ] 92 | } -------------------------------------------------------------------------------- /front-end/src/app/admin/admin.component.ts: -------------------------------------------------------------------------------- 1 | import { Component, OnInit } from '@angular/core'; 2 | import { UserService } from '../_services'; 3 | import { User } from '../_models'; 4 | import { first } from 'rxjs/operators'; 5 | import { ActivatedRoute } from '@angular/router'; 6 | import { AlertService } from '../_alert'; 7 | 8 | @Component({ 9 | selector: 'app-admin', 10 | templateUrl: './admin.component.html', 11 | styleUrls: ['./admin.component.css'] 12 | }) 13 | export class AdminComponent implements OnInit { 14 | loading = false; 15 | users: User[] = []; 16 | count: number; 17 | current: string; 18 | 19 | constructor( 20 | private userService: UserService, 21 | private route: ActivatedRoute, 22 | private alertService: AlertService 23 | ) { } 24 | 25 | ngOnInit() { 26 | this.route.queryParams.subscribe(x => this.loadPage(x)); 27 | } 28 | 29 | private loadPage(params: any) { 30 | if (params.load === "unverified") { 31 | this.current = "Unverified Users"; 32 | this.loading = true; 33 | this.userService.getUnverified().pipe(first()).subscribe(newObj => { 34 | this.loading = false; 35 | this.users = newObj.users; 36 | this.count = newObj.count; 37 | }); 38 | 39 | return; 40 | } 41 | 42 | this.current = "All Users"; 43 | this.loading = true; 44 | this.userService.getAll().pipe(first()).subscribe(res => { 45 | this.loading = false; 46 | let newObj: any = res; 47 | this.users = newObj.users; 48 | this.count = newObj.count; 49 | }); 50 | } 51 | 52 | deleteUser(user: User, i: any) { 53 | this.userService.deleteUser(user).subscribe(); 54 | this.users.splice(i, 1); 55 | this.count--; 56 | } 57 | 58 | unverifyUser(user: User) { 59 | this.userService.unverifyUser(user._id) 60 | .pipe(first()) 61 | .subscribe( 62 | data => { 63 | }, 64 | error => { 65 | this.alertService.error(error); 66 | }); 67 | } 68 | 69 | verifyUser(user: User, i: any) { 70 | this.userService.verifyUser(user._id) 71 | .pipe(first()) 72 | .subscribe( 73 | data => { 74 | }, 75 | error => { 76 | this.alertService.error(error); 77 | }); 78 | if (this.current === "Unverified Users") { 79 | this.users.splice(i, 1); 80 | this.count--; 81 | } 82 | } 83 | 84 | } 85 | -------------------------------------------------------------------------------- /front-end/src/app/register/register.component.ts: -------------------------------------------------------------------------------- 1 | import { Component, OnInit } from '@angular/core'; 2 | import { Router } from '@angular/router'; 3 | import { FormBuilder, FormGroup, Validators } from '@angular/forms'; 4 | import { first } from 'rxjs/operators'; 5 | import { AuthenticationService, UserService } from '../_services'; 6 | import { AlertService } from '../_alert'; 7 | 8 | // import custom validator to validate that password and confirm password fields match 9 | import { MustMatch } from '../_helpers/must-match.validator'; 10 | 11 | @Component({ templateUrl: 'register.component.html' }) 12 | export class RegisterComponent implements OnInit { 13 | registerForm: FormGroup; 14 | loading = false; 15 | submitted = false; 16 | 17 | constructor( 18 | private formBuilder: FormBuilder, 19 | private router: Router, 20 | private authenticationService: AuthenticationService, 21 | private userService: UserService, 22 | private alertService: AlertService 23 | ) { 24 | // redirect to home if already logged in 25 | if (this.authenticationService.currentUserValue) { 26 | this.router.navigate(['/home']); 27 | } 28 | } 29 | 30 | ngOnInit() { 31 | this.registerForm = this.formBuilder.group({ 32 | firstname: ['', Validators.required], 33 | lastname: ['', Validators.required], 34 | username: ['', Validators.required], 35 | password: ['', [Validators.required, Validators.minLength(6)]], 36 | confirmPassword: ['', Validators.required], 37 | email: ['', [Validators.required, Validators.email]], 38 | phone: ['', Validators.required], 39 | address: ['', Validators.required], 40 | city: ['', Validators.required], 41 | afm: ['', Validators.required], 42 | role: ['', Validators.required] 43 | }, { 44 | validator: MustMatch('password', 'confirmPassword') 45 | }); 46 | } 47 | 48 | // convenience getter for easy access to form fields 49 | get f() { return this.registerForm.controls; } 50 | 51 | onSubmit() { 52 | this.submitted = true; 53 | 54 | // reset alerts on submit 55 | this.alertService.clear(); 56 | 57 | // stop here if form is invalid 58 | if (this.registerForm.invalid) { 59 | return; 60 | } 61 | 62 | this.loading = true; 63 | this.userService.register(this.registerForm.value) 64 | .pipe(first()) 65 | .subscribe( 66 | data => { 67 | this.router.navigate(['/thanks-signup']); 68 | }, 69 | error => { 70 | this.alertService.error(error); 71 | this.loading = false; 72 | }); 73 | } 74 | } -------------------------------------------------------------------------------- /front-end/src/app/sent/sent.component.ts: -------------------------------------------------------------------------------- 1 | import { Component, OnInit } from '@angular/core'; 2 | import { first } from 'rxjs/operators'; 3 | import { Message } from '../_models'; 4 | import { MessagesService } from '../_services'; 5 | import { FormGroup, FormBuilder, Validators } from '@angular/forms'; 6 | import { AlertService } from '../_alert'; 7 | 8 | @Component({ 9 | selector: 'app-sent', 10 | templateUrl: './sent.component.html', 11 | styleUrls: ['./sent.component.css'] 12 | }) 13 | export class SentComponent implements OnInit { 14 | messages: Message[]; 15 | selectedMessage: Message; 16 | loading: boolean = false; 17 | replyBox: boolean = false; 18 | messageForm: FormGroup; 19 | submitted: boolean = false; 20 | 21 | constructor( 22 | private messagesService: MessagesService, 23 | private formBuilder: FormBuilder, 24 | private alertService: AlertService 25 | ) { } 26 | 27 | ngOnInit() { 28 | this.loadAllSent(); 29 | 30 | this.messageForm = this.formBuilder.group({ 31 | text: ['', Validators.required], 32 | subject: ['', Validators.required], 33 | sender: [''], 34 | receiver: [''], 35 | time: [] 36 | }); 37 | } 38 | 39 | private loadAllSent() { 40 | this.loading = true; 41 | this.messagesService.getSent().pipe(first()).subscribe(res => { 42 | this.loading = false; 43 | let newObj: any = res; 44 | this.messages = newObj.messages; 45 | }); 46 | } 47 | 48 | onSelect(message: Message): void { 49 | this.selectedMessage = message; 50 | } 51 | 52 | delete(message: Message): void { 53 | this.messagesService.deleteMessage(message).subscribe(); 54 | window.location.reload(); 55 | } 56 | 57 | reply() { 58 | this.replyBox = !this.replyBox; 59 | } 60 | 61 | // convenience getter for easy access to form fields 62 | get f() { return this.messageForm.controls; } 63 | 64 | sendMessage() { 65 | this.submitted = true; 66 | 67 | // stop here if form is invalid 68 | if (this.messageForm.invalid) { 69 | return; 70 | } 71 | 72 | this.messageForm.value.sender = this.selectedMessage.sender; 73 | this.messageForm.value.receiver = this.selectedMessage.receiver; 74 | this.messageForm.value.time = new Date(); 75 | 76 | this.loading = true; 77 | this.messagesService.newMessage(this.messageForm.value) 78 | .pipe(first()) 79 | .subscribe( 80 | data => { 81 | window.location.reload(); 82 | }, 83 | error => { 84 | this.alertService.error(error); 85 | this.loading = false; 86 | }); 87 | } 88 | 89 | } 90 | -------------------------------------------------------------------------------- /api/routes/categories.js: -------------------------------------------------------------------------------- 1 | const express = require('express'); 2 | const router = express.Router(); 3 | const mongoose = require('mongoose'); 4 | 5 | const Auction = require('../models/auction'); 6 | const Product = require('../models/product'); 7 | const User = require('../models/user'); 8 | const Bid = require('../models/bid'); 9 | const Category = require('../models/category'); 10 | 11 | var xml2js = require('xml2js'); 12 | var parser = new xml2js.Parser(); 13 | 14 | 15 | router.get('/', (req, res, next) => { 16 | Category.find() 17 | .select('name') 18 | .exec() 19 | .then(docs => { 20 | const response = { 21 | count: docs.length, 22 | categories: docs.map(doc => { 23 | return { 24 | name: doc.name 25 | } 26 | }) 27 | }; 28 | res.status(200).json(response); 29 | }) 30 | .catch(err => { 31 | console.log(err); 32 | res.status(500).json({ 33 | error: err 34 | }); 35 | }) 36 | }); 37 | 38 | router.post('/', (req, res, next) => { 39 | console.log(req.body); 40 | Category.find({ name: req.body.name}) 41 | .exec() 42 | .then( category => { 43 | if (category.length >= 1) { 44 | return res.status(409).json({ 45 | message: 'Category Exists' 46 | }); 47 | } 48 | else { 49 | const category = new Category({ 50 | name: req.body.name 51 | }); 52 | category.save() 53 | .then(result => { 54 | console.log(result); 55 | res.status(201).json({ 56 | message: 'Category Created' 57 | }); 58 | }).catch(err => { 59 | console.log(err); 60 | res.status(500).json({ 61 | error: err 62 | }); 63 | }); 64 | } 65 | }); 66 | }); 67 | 68 | router.delete('/:category_name', (req, res, next) => { 69 | /*Category.find() 70 | .select('name') 71 | .exec() 72 | .then(docs => { 73 | docs.map(doc => { 74 | Category.remove({ name: doc.name}) 75 | .exec() 76 | .then(result => { 77 | res.status(200).json({ 78 | message: 'Category Deleted' 79 | }) 80 | }) 81 | .catch(err => { 82 | console.log(err); 83 | res.status(500).json({ 84 | error: err 85 | }); 86 | }) 87 | }); 88 | }) 89 | .catch(err => { 90 | console.log(err); 91 | res.status(500).json({ 92 | error: err 93 | }); 94 | })*/ 95 | 96 | Category.remove({ name: req.params.category_name}) 97 | .exec() 98 | .then(result => { 99 | res.status(200).json({ 100 | message: 'Category Deleted' 101 | }) 102 | }) 103 | .catch(err => { 104 | console.log(err); 105 | res.status(500).json({ 106 | error: err 107 | }); 108 | }); 109 | }) 110 | 111 | module.exports = router; 112 | -------------------------------------------------------------------------------- /api/models/bid.js: -------------------------------------------------------------------------------- 1 | const mongoose = require('mongoose'); 2 | 3 | const bidSchema = mongoose.Schema({ 4 | _id: mongoose.Schema.Types.ObjectId, 5 | auction: { type: mongoose.Schema.Types.ObjectId, red: 'Auction' , required: true}, 6 | bidder: { type: mongoose.Schema.Types.ObjectId, red: 'User' , required: true}, 7 | time: {type: Date, required: true }, 8 | amount: {type: Number, required: true } 9 | }); 10 | 11 | module.exports = mongoose.model('Bid', bidSchema); 12 | 13 | /* 14 | 15 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 29 | 30 | 31 | 32 | 33 | 35 | 36 | 37 | 38 | 39 | 41 | 42 | */ 43 | 44 | /* 45 | 46 | Tommy Hilfiger jeans boy's 18-24 M (months) 47 | Clothing & Accessories 48 | Infants 49 | Clothing 50 | 12-24 Months 51 | Bottoms 52 | $7.50 53 | $7.00 54 | 2 55 | 56 | 57 | 58 | Sydney 59 | Australia 60 | 61 | 62 | $7.25 63 | 64 | 65 | 66 | New York 67 | USA 68 | 69 | 70 | $7.50 71 | 72 | 73 | JOHNNA'S QUALITY BARGAINS 74 | USA 75 | Dec-08-01 22:45:26 76 | Dec-15-01 22:45:26 77 | 78 | This is a really nice pair of Tommy Hilfiger denim jeans 79 | 80 | 81 | */ 82 | -------------------------------------------------------------------------------- /front-end/src/app/inbox/inbox.component.ts: -------------------------------------------------------------------------------- 1 | import { Component, OnInit } from '@angular/core'; 2 | import { first } from 'rxjs/operators'; 3 | import { Message } from '../_models'; 4 | import { MessagesService } from '../_services'; 5 | import { FormGroup, FormBuilder, Validators } from '@angular/forms'; 6 | import { AlertService } from '../_alert'; 7 | 8 | @Component({ 9 | selector: 'app-inbox', 10 | templateUrl: './inbox.component.html', 11 | styleUrls: ['./inbox.component.css'] 12 | }) 13 | export class InboxComponent implements OnInit { 14 | messages: Message[]; 15 | selectedMessage: Message; 16 | loading: boolean = false; 17 | replyBox: boolean = false; 18 | messageForm: FormGroup; 19 | submitted: boolean = false; 20 | 21 | constructor( 22 | private messagesService: MessagesService, 23 | private formBuilder: FormBuilder, 24 | private alertService: AlertService 25 | ) { } 26 | 27 | ngOnInit() { 28 | this.loadAllInbox(); 29 | 30 | this.messageForm = this.formBuilder.group({ 31 | text: ['', Validators.required], 32 | subject: ['', Validators.required], 33 | sender: [''], 34 | receiver: [''], 35 | time: [] 36 | }); 37 | } 38 | 39 | private loadAllInbox() { 40 | this.loading = true; 41 | this.messagesService.getReceived().pipe(first()).subscribe(res => { 42 | this.loading = false; 43 | let newObj: any = res; 44 | this.messages = newObj.messages; 45 | }); 46 | } 47 | 48 | onSelect(message: Message): void { 49 | this.selectedMessage = message; 50 | message.read = true; 51 | this.messagesService.updateMessage(message).subscribe(); 52 | } 53 | 54 | delete(message: Message): void { 55 | this.messagesService.deleteMessage(message).subscribe(); 56 | window.location.reload(); 57 | } 58 | 59 | reply() { 60 | this.replyBox = !this.replyBox; 61 | } 62 | 63 | // convenience getter for easy access to form fields 64 | get f() { return this.messageForm.controls; } 65 | 66 | sendMessage() { 67 | this.submitted = true; 68 | 69 | // stop here if form is invalid 70 | if (this.messageForm.invalid) { 71 | return; 72 | } 73 | 74 | this.messageForm.value.sender = this.selectedMessage.receiver; 75 | this.messageForm.value.receiver = this.selectedMessage.sender; 76 | this.messageForm.value.time = new Date(); 77 | 78 | this.loading = true; 79 | this.messagesService.newMessage(this.messageForm.value) 80 | .pipe(first()) 81 | .subscribe( 82 | data => { 83 | window.location.reload(); 84 | }, 85 | error => { 86 | this.alertService.error(error); 87 | this.loading = false; 88 | }); 89 | } 90 | 91 | } 92 | -------------------------------------------------------------------------------- /front-end/src/app/inbox/inbox.component.html: -------------------------------------------------------------------------------- 1 |
2 |
    3 |
  • 6 |
    7 | {{ message.sender_username }} 8 | {{ message.subject }} 9 | {{ message.time | date:'short' }} 10 |
    11 |
    12 |

    {{ message.text }}

    13 |
    14 |
    15 | Reply 16 |
    17 |
    18 | Delete 19 |
    20 |
    21 |
    22 |
    23 |
    24 | 26 |
    27 |
    Subject is required
    28 |
    29 |
    30 | 31 |
    32 | 34 |
    35 |
    Body is required
    36 |
    37 |
    38 | 39 | 43 |
    44 |
    45 |
    46 |
  • 47 |
-------------------------------------------------------------------------------- /front-end/src/app/app.module.ts: -------------------------------------------------------------------------------- 1 | import { NgModule } from '@angular/core'; 2 | import { BrowserModule } from '@angular/platform-browser'; 3 | import { ReactiveFormsModule } from '@angular/forms'; 4 | import { HttpClientModule, HTTP_INTERCEPTORS } from '@angular/common/http'; 5 | import { FormsModule } from '@angular/forms'; 6 | import { AppComponent } from './app.component'; 7 | import { appRoutingModule } from './app.routing'; 8 | import { JwtInterceptor, ErrorInterceptor } from './_helpers'; 9 | import { HomeComponent } from './home'; 10 | import { LoginComponent } from './login';; 11 | import { RegisterComponent } from './register/register.component' 12 | import { PageNotFoundComponent } from './page-not-found/page-not-found.component'; 13 | import { AlertModule } from './_alert'; 14 | import { ThanksSignupComponent } from './thanks-signup/thanks-signup.component'; 15 | import { ManageAuctionsComponent } from './manage-auctions/manage-auctions.component'; 16 | import { MustMatchDirective } from './_helpers/must-match.directive'; 17 | import { AuctionDetailComponent } from './auction-detail/auction-detail.component'; 18 | import { LandingComponent } from './landing/landing.component'; 19 | import { AdminComponent } from './admin/admin.component'; 20 | import { MapDetailsComponent } from './map-details/map-details.component'; 21 | import { AuctionformComponent } from './auctionform/auctionform.component'; 22 | import { AuctionInfoComponent } from './auction-info/auction-info.component'; 23 | import { EmailComponent } from './email/email.component'; 24 | import { InboxComponent } from './inbox/inbox.component'; 25 | import { SentComponent } from './sent/sent.component'; 26 | import { UserInfoComponent } from './user-info/user-info.component'; 27 | import { RecommendationsComponent } from './recommendations/recommendations.component' 28 | 29 | @NgModule({ 30 | imports: [ 31 | BrowserModule, 32 | ReactiveFormsModule, 33 | HttpClientModule, 34 | appRoutingModule, 35 | AlertModule, 36 | FormsModule 37 | ], 38 | declarations: [ 39 | AppComponent, 40 | HomeComponent, 41 | LoginComponent, 42 | RegisterComponent, 43 | PageNotFoundComponent, 44 | ThanksSignupComponent, 45 | ManageAuctionsComponent, 46 | MustMatchDirective, 47 | AuctionDetailComponent, 48 | LandingComponent, 49 | AdminComponent, 50 | MapDetailsComponent, 51 | AuctionformComponent, 52 | AuctionInfoComponent, 53 | EmailComponent, 54 | InboxComponent, 55 | SentComponent, 56 | UserInfoComponent, 57 | RecommendationsComponent 58 | ], 59 | providers: [ 60 | { provide: HTTP_INTERCEPTORS, useClass: JwtInterceptor, multi: true }, 61 | { provide: HTTP_INTERCEPTORS, useClass: ErrorInterceptor, multi: true } 62 | ], 63 | bootstrap: [AppComponent] 64 | }) 65 | export class AppModule { } -------------------------------------------------------------------------------- /front-end/src/app/landing/landing.component.css: -------------------------------------------------------------------------------- 1 | * { 2 | box-sizing: border-box; 3 | margin: 0; 4 | padding: 0; 5 | } 6 | 7 | body { 8 | background: #000; 9 | color: #999; 10 | } 11 | 12 | h1, h2, h3, h4 { 13 | color: #fff; 14 | } 15 | 16 | a { 17 | color: #fff; 18 | text-decoration: none; 19 | } 20 | 21 | p { 22 | margin: 0.5rem 0; 23 | } 24 | 25 | .landing-main { 26 | position: relative; 27 | height: 100vh; 28 | background: url('../../assets/auction-bidding-ss-1920_zmdrak.png') no-repeat center/cover; 29 | } 30 | 31 | .landing-main::after { 32 | content: ''; 33 | position: absolute; 34 | top: 0; 35 | left: 0; 36 | width: 100%; 37 | height: 100%; 38 | background: #030608e8; 39 | } 40 | 41 | .showcase-top { 42 | position: relative; 43 | margin: auto; 44 | display: flex; 45 | z-index: 2; 46 | height: 90px; 47 | } 48 | 49 | .showcase-top a { 50 | position: absolute; 51 | top: 50%; 52 | right: 0; 53 | transform: translate(-50%, -50%); 54 | } 55 | 56 | .landing-content { 57 | position: relative; 58 | margin: auto; 59 | display: flex; 60 | flex-direction: column; 61 | justify-content: center; 62 | align-items: center; 63 | text-align: center; 64 | z-index: 2; 65 | padding-top: 240px; 66 | } 67 | 68 | .landing-content h1 { 69 | font-weight: 750; 70 | font-size: 4.5rem; 71 | line-height: 1.1; 72 | margin: 0 0 2rem; 73 | } 74 | 75 | .landing-content p { 76 | text-transform: uppercase; 77 | color: white; 78 | font-weight: 550; 79 | font-size: 2.5rem; 80 | line-height: 1.3; 81 | margin: 0 0 2rem; 82 | } 83 | 84 | .signup { 85 | color: white; 86 | font-weight: 450; 87 | font-size: 1.5rem; 88 | line-height: 1.3; 89 | margin-top: 1.5rem; 90 | } 91 | 92 | .signuplink { 93 | padding: 20px; 94 | font-size: 1.25rem; 95 | } 96 | 97 | .btn { 98 | display: inline-block; 99 | border: 2px solid #2ecc71; 100 | /* background: #2ecc71; */ 101 | color: #fff; 102 | padding: 0.4rem 1.3rem; 103 | font-size: 1rem; 104 | /* border: none; */ 105 | cursor: pointer; 106 | margin-right: 0.5rem; 107 | outline: none; 108 | border-radius: 5px; 109 | } 110 | 111 | .btn:hover { 112 | background: #2ecc71; 113 | /* opacity: 0.9; */ 114 | } 115 | 116 | .btn-xl { 117 | font-size: 2rem; 118 | padding: 1.5rem 2rem; 119 | text-transform: uppercase; 120 | } 121 | 122 | .rounded { 123 | display: inline-block; 124 | background: #2ecc71; 125 | color: #fff; 126 | padding: 0.4rem 1.3rem; 127 | font-size: 1rem; 128 | border: none; 129 | cursor: pointer; 130 | margin-right: 0.5rem; 131 | outline: none; 132 | border-radius: 20px; 133 | } 134 | 135 | .rounded:hover { 136 | opacity: 0.9; 137 | } 138 | -------------------------------------------------------------------------------- /front-end/src/app/sent/sent.component.html: -------------------------------------------------------------------------------- 1 |
2 |
    3 |
  • 6 |
    7 | To: {{ message.receiver_username }} 8 | {{ message.subject }} 9 | {{ message.time | date:'short' }} 10 |
    11 |
    12 |

    {{ message.text }}

    13 |
    14 | Seen Seen 15 |
    16 |
    17 |
    18 | Reply 19 |
    20 |
    21 | Delete 22 |
    23 |
    24 | 25 |
    26 |
    27 |
    28 | 30 |
    31 |
    Subject is required
    32 |
    33 |
    34 | 35 |
    36 | 38 |
    39 |
    Body is required
    40 |
    41 |
    42 | 43 | 47 |
    48 |
    49 |
    50 |
  • 51 |
-------------------------------------------------------------------------------- /front-end/src/polyfills.ts: -------------------------------------------------------------------------------- 1 | /** 2 | * This file includes polyfills needed by Angular and is loaded before the app. 3 | * You can add your own extra polyfills to this file. 4 | * 5 | * This file is divided into 2 sections: 6 | * 1. Browser polyfills. These are applied before loading ZoneJS and are sorted by browsers. 7 | * 2. Application imports. Files imported after ZoneJS that should be loaded before your main 8 | * file. 9 | * 10 | * The current setup is for so-called "evergreen" browsers; the last versions of browsers that 11 | * automatically update themselves. This includes Safari >= 10, Chrome >= 55 (including Opera), 12 | * Edge >= 13 on the desktop, and iOS 10 and Chrome on mobile. 13 | * 14 | * Learn more in https://angular.io/guide/browser-support 15 | */ 16 | 17 | /*************************************************************************************************** 18 | * BROWSER POLYFILLS 19 | */ 20 | 21 | /** IE10 and IE11 requires the following for NgClass support on SVG elements */ 22 | // import 'classlist.js'; // Run `npm install --save classlist.js`. 23 | 24 | /** 25 | * Web Animations `@angular/platform-browser/animations` 26 | * Only required if AnimationBuilder is used within the application and using IE/Edge or Safari. 27 | * Standard animation support in Angular DOES NOT require any polyfills (as of Angular 6.0). 28 | */ 29 | // import 'web-animations-js'; // Run `npm install --save web-animations-js`. 30 | 31 | /** 32 | * By default, zone.js will patch all possible macroTask and DomEvents 33 | * user can disable parts of macroTask/DomEvents patch by setting following flags 34 | * because those flags need to be set before `zone.js` being loaded, and webpack 35 | * will put import in the top of bundle, so user need to create a separate file 36 | * in this directory (for example: zone-flags.ts), and put the following flags 37 | * into that file, and then add the following code before importing zone.js. 38 | * import './zone-flags.ts'; 39 | * 40 | * The flags allowed in zone-flags.ts are listed here. 41 | * 42 | * The following flags will work for all browsers. 43 | * 44 | * (window as any).__Zone_disable_requestAnimationFrame = true; // disable patch requestAnimationFrame 45 | * (window as any).__Zone_disable_on_property = true; // disable patch onProperty such as onclick 46 | * (window as any).__zone_symbol__UNPATCHED_EVENTS = ['scroll', 'mousemove']; // disable patch specified eventNames 47 | * 48 | * in IE/Edge developer tools, the addEventListener will also be wrapped by zone.js 49 | * with the following flag, it will bypass `zone.js` patch for IE/Edge 50 | * 51 | * (window as any).__Zone_enable_cross_context_check = true; 52 | * 53 | */ 54 | 55 | /*************************************************************************************************** 56 | * Zone JS is required by default for Angular itself. 57 | */ 58 | import 'zone.js/dist/zone'; // Included with Angular CLI. 59 | 60 | 61 | /*************************************************************************************************** 62 | * APPLICATION IMPORTS 63 | */ 64 | -------------------------------------------------------------------------------- /front-end/src/app/app.component.html: -------------------------------------------------------------------------------- 1 |
2 | 3 | 31 | 32 | 33 | 34 | 35 | 56 | 57 | 58 | 59 |
60 | 61 | 62 |
63 |
64 | 65 | 66 | -------------------------------------------------------------------------------- /api/routes/products.js: -------------------------------------------------------------------------------- 1 | const express = require('express'); 2 | const router = express.Router(); 3 | const mongoose = require('mongoose'); 4 | 5 | const Product = require('../models/product'); 6 | 7 | router.get('/', (req, res, next) => { 8 | Product.find() 9 | .select('_id name category') 10 | .exec() 11 | .then(docs => { 12 | const response = { 13 | count: docs.length, 14 | products: docs.map(doc => { 15 | return { 16 | _id: doc._id, 17 | name: doc.name, 18 | category: doc.category, 19 | request: { 20 | type: 'GET', 21 | url: 'http://localhost:3000/products/' + doc._id 22 | } 23 | } 24 | }) 25 | }; 26 | res.status(200).json(response); 27 | }) 28 | .catch(err => { 29 | console.log(err); 30 | res.status(500).json({ 31 | error: err 32 | }); 33 | }) 34 | }); 35 | 36 | router.post('/', (req, res, next) => { 37 | const product = new Product({ 38 | _id: new mongoose.Types.ObjectId(), 39 | name: req.body.name, 40 | category: req.body.category, 41 | location: req.body.location, 42 | country: req.body.country 43 | }); 44 | product 45 | .save() 46 | .then(result => { 47 | console.log(result); 48 | res.status(201).json({ 49 | message: 'Product Created', 50 | createdProduct: { 51 | _id: result._id, 52 | name: result.name, 53 | category: result.category, 54 | location: result.location, 55 | country: result.country, 56 | request: { 57 | type: 'GET', 58 | url: 'http://localhost:3000/products/' + result._id 59 | } 60 | } 61 | }); 62 | }).catch(err => { 63 | console.log(err); 64 | res.status(500).json({ 65 | error: err 66 | }); 67 | }); 68 | }); 69 | 70 | router.get('/:productId', (req, res, next) => { 71 | const id = req.params.productId; 72 | Product.findById(id) 73 | .select('_id name category') 74 | .exec() 75 | .then(doc => { 76 | console.log("From database", doc); 77 | if (doc) 78 | { 79 | res.status(200).json({ 80 | product: doc, 81 | request: { 82 | type: 'GET', 83 | description: 'Get all products', 84 | url: 'http://localhost:3000/products' 85 | } 86 | }); 87 | } 88 | else 89 | { 90 | res.status(404).json({message: 'No entry'}); 91 | } 92 | }).catch(err => { 93 | console.log(err); 94 | res.status(500).json({error: err}); 95 | }); 96 | }); 97 | 98 | router.patch('/:productId', (req, res, next) => { 99 | const id = req.params.productId; 100 | const updateOps = {}; 101 | for (const ops of req.body) 102 | { 103 | updateOps[ops.propName] = ops.value; 104 | } 105 | 106 | Product.update({_id: id}, { $set: updateOps }) 107 | .exec() 108 | .then(result => { 109 | console.log(result); 110 | res.status(200).json({ 111 | message: 'Product Updated', 112 | type: 'GET', 113 | url: 'http://localhost:3000/products/' + id 114 | }); 115 | }) 116 | .catch(err => { 117 | console.log(err); 118 | res.status(500).json({ 119 | error: err 120 | }); 121 | }); 122 | }); 123 | 124 | router.delete('/:productId', (req, res, next) => { 125 | const id = req.params.productId; 126 | Product.remove({_id: id}) 127 | .exec() 128 | .then(result => { 129 | res.status(200).json({ 130 | message: 'Product deleted', 131 | }); 132 | }) 133 | .catch(err => { 134 | console.log(err); 135 | res.status(500).json({ 136 | error: err 137 | }); 138 | }) 139 | }); 140 | 141 | module.exports = router; 142 | -------------------------------------------------------------------------------- /front-end/src/app/auction-detail/auction-detail.component.html: -------------------------------------------------------------------------------- 1 |
2 |
3 | 4 | 5 |
6 |
7 |
    8 |
  • 9 | image 10 |
  • 11 |
12 |
13 | 14 | 15 | 16 | 17 |
18 | 19 | 20 | 21 |
22 | 23 | 24 |
25 | {{ cat }} 26 |

{{ auction.name }}

27 |

{{ auction.description }}

28 |
29 | 30 | 31 |
32 | 33 | 34 |
35 | 36 |
37 | Location: {{ auction.location }}, 38 | {{ auction.country }} 39 | 40 |
41 | 42 |
43 |
44 |
45 | 46 |
47 | Location: {{ auction.location }}, 48 | {{ auction.country }} 49 | 50 |

Unfortunately the user has not provided us with their exact 51 | coordinates.

52 |
53 |
54 |
55 | 56 | 57 |

Auction ends: {{ corrEnds | date:'medium' }}

58 | 59 |
60 | {{ auction.currently }} € 61 | [{{ auction.no_bids }} bids] 62 |
63 | 64 |
65 |
66 |
67 |
68 | 69 |
70 | 72 |
73 | 76 |
77 |
78 |
A value is required
79 |
80 |
81 |
82 |
83 |
84 |
85 |
-------------------------------------------------------------------------------- /front-end/src/app/admin/admin.component.html: -------------------------------------------------------------------------------- 1 | 21 | 22 | 23 |
24 |
25 | 26 |
27 |
28 | Dashboard 29 | All Users 30 | Unverfied Users 32 |
33 |
34 |
35 |
36 |
37 |
38 |
39 |

{{ current }}

40 |
41 |
42 | {{ count }} 43 |
44 |
45 | 46 | 47 |
48 |
49 | 50 | 51 | 52 | 53 | 54 | 55 | 56 | 57 | 58 | 59 |
60 | 61 | 62 | 67 | 68 | 69 | 70 | 75 | 76 | 77 |
UsernameFirst NameLast NameEmailEdit
63 | 64 | {{user.username}} 65 | 66 | {{user.firstname}}{{user.lastname}}{{user.email}} 71 | 72 | 73 | 74 |
78 |
79 |
80 |
81 |
82 |
83 |
84 |
-------------------------------------------------------------------------------- /front-end/src/app/auctionform/auctionform.component.ts: -------------------------------------------------------------------------------- 1 | import { Component, OnInit } from '@angular/core'; 2 | import * as jwt_decode from "jwt-decode"; 3 | import { FormBuilder, FormGroup, Validators } from '@angular/forms'; 4 | import { first } from 'rxjs/operators'; 5 | import { AuctionService, AuctionsService } from '../_services'; 6 | import { AlertService } from '../_alert'; 7 | 8 | @Component({ 9 | selector: 'app-auctionform', 10 | templateUrl: './auctionform.component.html', 11 | styleUrls: ['./auctionform.component.css'] 12 | }) 13 | export class AuctionformComponent implements OnInit { 14 | userId: any; 15 | auctionForm: FormGroup; 16 | loading = false; 17 | submitted = false; 18 | public lat: any; 19 | public lng: any; 20 | categories: string[]; 21 | images = []; 22 | 23 | constructor( 24 | private formBuilder: FormBuilder, 25 | private auctionService: AuctionService, 26 | private alertService: AlertService, 27 | private auctionsService: AuctionsService 28 | ) { } 29 | 30 | ngOnInit() { 31 | this.getLocation(); 32 | 33 | this.loadAllCategories(); 34 | 35 | let currentUserJSON = JSON.parse(localStorage.getItem('currentUser')); 36 | let tokenInfo = jwt_decode(currentUserJSON.token); 37 | this.userId = tokenInfo.userId; 38 | 39 | this.auctionForm = this.formBuilder.group({ 40 | name: ['', Validators.required], 41 | category: [[], Validators.required], 42 | location: ['', Validators.required], 43 | country: ['', Validators.required], 44 | currently: [''], 45 | first_bid: ['', Validators.required], 46 | ends: ['', Validators.required], 47 | description: ['', Validators.required], 48 | seller: [this.userId], 49 | latitude: [-1], 50 | longitude: [-1], 51 | images: [[]] 52 | }); 53 | } 54 | 55 | // convenience getter for easy access to form fields 56 | get f() { return this.auctionForm.controls; } 57 | 58 | formSubmit() { 59 | if ((this.lng == undefined) || (this.lat == undefined)) { 60 | alert("Your exact location will not be shown"); 61 | } else { 62 | this.auctionForm.value.longitude = this.lng; 63 | this.auctionForm.value.latitude = this.lat; 64 | } 65 | 66 | this.submitted = true; 67 | 68 | // reset alerts on submit 69 | this.alertService.clear(); 70 | 71 | // stop here if form is invalid 72 | if (this.auctionForm.invalid) { 73 | return; 74 | } 75 | 76 | this.auctionForm.value.currently = this.auctionForm.value.first_bid; 77 | 78 | this.loading = true; 79 | this.auctionService.newForm(this.auctionForm.value) 80 | .pipe(first()) 81 | .subscribe( 82 | data => { 83 | window.location.reload(); 84 | }, 85 | error => { 86 | this.alertService.error(error); 87 | this.loading = false; 88 | }); 89 | } 90 | 91 | addImages(event: any) { 92 | const file = event.target.files[0]; 93 | 94 | if (file) { 95 | const reader = new FileReader(); 96 | 97 | reader.onload = this.handleReaderLoaded.bind(this); 98 | reader.readAsBinaryString(file); 99 | } 100 | } 101 | 102 | handleReaderLoaded(e) { 103 | this.auctionForm.value.images.push(btoa(e.target.result)); 104 | this.images.push('data:image/png;base64,' + btoa(e.target.result)); 105 | } 106 | 107 | getLocation() { 108 | // When on pc it returns a position based on the ISP, not exact GPS data :( 109 | if (navigator.geolocation) { 110 | navigator.geolocation.getCurrentPosition((position: Position) => { 111 | if (position) { 112 | this.lat = position.coords.latitude; 113 | this.lng = position.coords.longitude; 114 | } 115 | }, 116 | (error: PositionError) => console.log(error)); 117 | } else { 118 | alert("Geolocation is not supported by this browser."); 119 | } 120 | } 121 | 122 | private loadAllCategories() { 123 | this.auctionsService.getCategories().pipe(first()).subscribe(res => { 124 | let newObj: any = res; 125 | this.categories = newObj.categories; 126 | }); 127 | } 128 | 129 | } 130 | -------------------------------------------------------------------------------- /front-end/angular.json: -------------------------------------------------------------------------------- 1 | { 2 | "$schema": "./node_modules/@angular/cli/lib/config/schema.json", 3 | "version": 1, 4 | "newProjectRoot": "projects", 5 | "projects": { 6 | "front-end": { 7 | "projectType": "application", 8 | "schematics": {}, 9 | "root": "", 10 | "sourceRoot": "src", 11 | "prefix": "app", 12 | "architect": { 13 | "build": { 14 | "builder": "@angular-devkit/build-angular:browser", 15 | "options": { 16 | "outputPath": "dist/front-end", 17 | "index": "src/index.html", 18 | "main": "src/main.ts", 19 | "polyfills": "src/polyfills.ts", 20 | "tsConfig": "tsconfig.app.json", 21 | "aot": false, 22 | "assets": [ 23 | "src/favicon.ico", 24 | "src/assets" 25 | ], 26 | "styles": [ 27 | "src/styles.css" 28 | ], 29 | "scripts": [] 30 | }, 31 | "configurations": { 32 | "production": { 33 | "fileReplacements": [ 34 | { 35 | "replace": "src/environments/environment.ts", 36 | "with": "src/environments/environment.prod.ts" 37 | } 38 | ], 39 | "optimization": true, 40 | "outputHashing": "all", 41 | "sourceMap": false, 42 | "extractCss": true, 43 | "namedChunks": false, 44 | "aot": true, 45 | "extractLicenses": true, 46 | "vendorChunk": false, 47 | "buildOptimizer": true, 48 | "budgets": [ 49 | { 50 | "type": "initial", 51 | "maximumWarning": "2mb", 52 | "maximumError": "5mb" 53 | }, 54 | { 55 | "type": "anyComponentStyle", 56 | "maximumWarning": "6kb", 57 | "maximumError": "10kb" 58 | } 59 | ] 60 | } 61 | } 62 | }, 63 | "serve": { 64 | "builder": "@angular-devkit/build-angular:dev-server", 65 | "options": { 66 | "browserTarget": "front-end:build", 67 | "ssl": true 68 | }, 69 | "configurations": { 70 | "production": { 71 | "browserTarget": "front-end:build:production" 72 | } 73 | } 74 | }, 75 | "extract-i18n": { 76 | "builder": "@angular-devkit/build-angular:extract-i18n", 77 | "options": { 78 | "browserTarget": "front-end:build" 79 | } 80 | }, 81 | "test": { 82 | "builder": "@angular-devkit/build-angular:karma", 83 | "options": { 84 | "main": "src/test.ts", 85 | "polyfills": "src/polyfills.ts", 86 | "tsConfig": "tsconfig.spec.json", 87 | "karmaConfig": "karma.conf.js", 88 | "assets": [ 89 | "src/favicon.ico", 90 | "src/assets" 91 | ], 92 | "styles": [ 93 | "src/styles.css" 94 | ], 95 | "scripts": [] 96 | } 97 | }, 98 | "lint": { 99 | "builder": "@angular-devkit/build-angular:tslint", 100 | "options": { 101 | "tsConfig": [ 102 | "tsconfig.app.json", 103 | "tsconfig.spec.json", 104 | "e2e/tsconfig.json" 105 | ], 106 | "exclude": [ 107 | "**/node_modules/**" 108 | ] 109 | } 110 | }, 111 | "e2e": { 112 | "builder": "@angular-devkit/build-angular:protractor", 113 | "options": { 114 | "protractorConfig": "e2e/protractor.conf.js", 115 | "devServerTarget": "front-end:serve" 116 | }, 117 | "configurations": { 118 | "production": { 119 | "devServerTarget": "front-end:serve:production" 120 | } 121 | } 122 | } 123 | } 124 | } 125 | }, 126 | "defaultProject": "front-end" 127 | } -------------------------------------------------------------------------------- /front-end/src/app/home/home.component.css: -------------------------------------------------------------------------------- 1 | /* Insprired by: https://codepen.io/alassetter/pen/cyrfB */ 2 | 3 | .table-fill { 4 | background: white; 5 | border-radius:3px; 6 | border-collapse: collapse; 7 | height: 320px; 8 | margin: auto; 9 | max-width: 1000px; 10 | padding:5px; 11 | width: 100%; 12 | box-shadow: 0 5px 10px rgba(0, 0, 0, 0.1); 13 | animation: float 5s infinite; 14 | } 15 | 16 | th { 17 | color:#D5DDE5;; 18 | background:#1b1e24; 19 | border-bottom:4px solid #9ea7af; 20 | border-right: 1px solid #343a45; 21 | font-size:23px; 22 | font-weight: 100; 23 | padding:24px; 24 | text-align:left; 25 | text-shadow: 0 1px 1px rgba(0, 0, 0, 0.1); 26 | vertical-align:middle; 27 | } 28 | 29 | th:first-child { 30 | border-top-left-radius:3px; 31 | padding-right: 200px; 32 | } 33 | 34 | th:last-child { 35 | border-top-right-radius:3px; 36 | border-right:none; 37 | } 38 | 39 | tr { 40 | border-top: 1px solid #C1C3D1; 41 | border-bottom: 1px solid #C1C3D1; 42 | color:#666B85; 43 | font-size:16px; 44 | font-weight:normal; 45 | text-shadow: 0 1px 1px rgba(256, 256, 256, 0.1); 46 | } 47 | 48 | tr:hover td { 49 | background:#4E5066; 50 | color:#FFFFFF; 51 | border-top: 1px solid #22262e; 52 | } 53 | 54 | tr:hover a { 55 | background:#4E5066; 56 | color:#FFFFFF; 57 | } 58 | 59 | tr:first-child { 60 | border-top:none; 61 | } 62 | 63 | tr:last-child { 64 | border-bottom:none; 65 | } 66 | 67 | tr:nth-child(odd) td { 68 | background:#EBEBEB; 69 | } 70 | 71 | tr:nth-child(odd):hover td { 72 | background:#4E5066; 73 | } 74 | 75 | tr:nth-child(odd):hover a { 76 | background:#4E5066; 77 | } 78 | 79 | tr:last-child td:first-child { 80 | border-bottom-left-radius:3px; 81 | } 82 | 83 | tr:last-child td:last-child { 84 | border-bottom-right-radius:3px; 85 | } 86 | 87 | td { 88 | background:#FFFFFF; 89 | padding:20px; 90 | text-align:left; 91 | vertical-align:middle; 92 | font-weight:300; 93 | font-size:18px; 94 | text-shadow: -1px -1px 1px rgba(0, 0, 0, 0.1); 95 | border-right: 1px solid #C1C3D1; 96 | } 97 | 98 | td:last-child { 99 | border-right: 0px; 100 | } 101 | 102 | th.text-left { 103 | text-align: left; 104 | } 105 | 106 | th.text-center { 107 | text-align: center; 108 | } 109 | 110 | th.text-right { 111 | text-align: right; 112 | } 113 | 114 | td.text-left { 115 | text-align: left; 116 | } 117 | 118 | td.text-center { 119 | text-align: center; 120 | } 121 | 122 | td.text-right { 123 | text-align: right; 124 | } 125 | 126 | .pgn { 127 | padding-top: 25px; 128 | align-items: center; 129 | display: flex; 130 | justify-content: center; 131 | } 132 | 133 | .email-notif { 134 | margin-top: 25px; 135 | margin-left: 20px; 136 | color: white; 137 | background: #ffc42d; 138 | width: 190px; 139 | height: 3rem; 140 | border-radius: 20px; 141 | position: absolute; 142 | z-index:100; 143 | } 144 | 145 | .email-notif p { 146 | font-weight: bold; 147 | width: 300px; 148 | margin-left: auto; 149 | margin-right: auto; 150 | position: relative; 151 | padding-left: 28px; 152 | top: 50%; 153 | transform: translateY(-50%); 154 | } 155 | 156 | .no-more { 157 | padding-top: 80px; 158 | padding-bottom: 30px; 159 | align-items: center; 160 | display: flex; 161 | justify-content: center; 162 | font-size: 1.8rem; 163 | } 164 | 165 | /*search box css start here*/ 166 | .search-sec{ 167 | padding: 1.5rem; 168 | } 169 | 170 | .border-line { 171 | border-bottom: 1px solid #1A1E24; 172 | width: 1200px; 173 | margin-left: 367px; 174 | margin-bottom: 37px; 175 | } 176 | 177 | .search-slt{ 178 | display: block; 179 | width: 100%; 180 | font-size: 0.875rem; 181 | line-height: 1.5; 182 | color: #55595c; 183 | background-color: #fff; 184 | background-image: none; 185 | border: 1px solid #ccc; 186 | height: calc(3rem + 2px) !important; 187 | border-radius:0; 188 | } 189 | .wrn-btn{ 190 | width: 100%; 191 | font-size: 16px; 192 | font-weight: 400; 193 | text-transform: capitalize; 194 | height: calc(3rem + 2px) !important; 195 | border-radius:0; 196 | } 197 | 198 | .advanced-search { 199 | width: 80%; 200 | position: relative; 201 | margin-left: 265px; 202 | } -------------------------------------------------------------------------------- /front-end/src/app/auction-detail/auction-detail.component.ts: -------------------------------------------------------------------------------- 1 | import { Component, OnInit, Input } from '@angular/core'; 2 | import { Auction, User } from '../_models'; 3 | import { ActivatedRoute } from '@angular/router'; 4 | import { AuctionsService, AuthenticationService, BidService, UserService } from '../_services'; 5 | import { first } from 'rxjs/operators'; 6 | import { FormBuilder, FormGroup, Validators } from '@angular/forms'; 7 | import { AlertService } from '../_alert'; 8 | import * as jwt_decode from "jwt-decode"; 9 | 10 | @Component({ 11 | selector: 'app-auction-detail', 12 | templateUrl: './auction-detail.component.html', 13 | styleUrls: ['./auction-detail.component.css'] 14 | }) 15 | export class AuctionDetailComponent implements OnInit { 16 | @Input() auction: Auction; 17 | coords: boolean = false; 18 | corrEnds: Date; 19 | currentUser: User; 20 | bidForm: FormGroup; 21 | loading = false; 22 | submitted = false; 23 | id: string; 24 | categories: string[]; 25 | images: boolean = false; 26 | 27 | constructor( 28 | private route: ActivatedRoute, 29 | private auctionsService: AuctionsService, 30 | private authenticationService: AuthenticationService, 31 | private formBuilder: FormBuilder, 32 | private bidService: BidService, 33 | private alertService: AlertService, 34 | private userService: UserService 35 | ) { 36 | this.authenticationService.currentUser.subscribe(x => this.currentUser = x); 37 | } 38 | 39 | ngOnInit() { 40 | this.getAuction(); 41 | 42 | this.bidForm = this.formBuilder.group({ 43 | auction: [], 44 | bidder: [], 45 | time: [], 46 | amount: ['', Validators.required] 47 | }); 48 | 49 | this.seenAuction(); 50 | } 51 | 52 | // convenience getter for easy access to form fields 53 | get f() { return this.bidForm.controls; } 54 | 55 | getAuction(): void { 56 | this.route.paramMap.subscribe(params => { 57 | this.id = params.get('id'); 58 | }); 59 | this.auctionsService.getAuction(this.id).pipe(first()).subscribe(res => { 60 | let newObj: any = res; 61 | this.auction = newObj.auction; 62 | this.categories = this.auction.category; 63 | if ((this.auction.longitude != -1) && (this.auction.latitude != -1) && this.auction.longitude && this.auction.latitude) { 64 | this.coords = true 65 | } 66 | for (var img in this.auction.images) { 67 | this.images = true; 68 | this.auction.images[img] = 'data:image/png;base64,' + this.auction.images[img] 69 | } 70 | 71 | this.corrEnds = new Date(this.auction.ends); 72 | }); 73 | } 74 | 75 | formSubmit() { 76 | this.submitted = true; 77 | 78 | // reset alerts on submit 79 | this.alertService.clear(); 80 | 81 | // stop here if form is invalid 82 | if (this.bidForm.invalid) { 83 | return; 84 | } 85 | 86 | // Asking the user to confirm the bid 87 | if (confirm("Are you sure you want to place " + this.bidForm.value.amount + "€ on the auction?")) { 88 | this.bidForm.value.auction = this.id; 89 | this.bidForm.value.time = new Date(); 90 | 91 | let currentUserJSON = JSON.parse(localStorage.getItem('currentUser')); 92 | let tokenInfo = jwt_decode(currentUserJSON.token); 93 | this.bidForm.value.bidder = tokenInfo.userId 94 | 95 | this.loading = true; 96 | this.bidService.newBid(this.bidForm.value) 97 | .pipe(first()) 98 | .subscribe( 99 | data => { 100 | window.location.reload(); 101 | }, 102 | error => { 103 | this.alertService.error(error); 104 | this.loading = false; 105 | }); 106 | } 107 | } 108 | 109 | seenAuction() { 110 | if (this.currentUser) { 111 | let currentUserJSON = JSON.parse(localStorage.getItem('currentUser')); 112 | let tokenInfo = jwt_decode(currentUserJSON.token); 113 | 114 | this.userService.seenAuction(tokenInfo.userId, this.id) 115 | .pipe(first()) 116 | .subscribe( 117 | data => { 118 | console.log(this.id); 119 | }, 120 | error => { 121 | this.alertService.error(error); 122 | }); 123 | } 124 | } 125 | 126 | } 127 | -------------------------------------------------------------------------------- /front-end/src/app/home/home.component.html: -------------------------------------------------------------------------------- 1 | 2 | 5 | 6 |
7 | 47 |
48 |
49 |
50 | 51 | 52 | 53 | 54 | 55 | 56 | 57 | 58 | 59 | 60 | 61 |
62 | 63 | 64 | 69 | 70 | 71 | 72 | 73 |
NameCurrent PriceBidsCountry
65 | 66 | {{auction.name}} 67 | 68 | {{auction.currently}}{{auction.no_bids}}{{auction.country}}
74 |
75 | You reached the end of the auctions. For more results try altering your criteria. 76 |
77 | 78 | 79 |
80 |
    81 |
  • 82 | 83 |
  • 84 |
  • 85 | ... 86 |
  • 87 |
  • 88 | {{pageN}} 89 |
  • 90 |
  • 91 | ... 92 |
  • 93 |
  • 94 | 95 |
  • 96 |
97 |
-------------------------------------------------------------------------------- /front-end/src/app/auctionform/auctionform.component.html: -------------------------------------------------------------------------------- 1 |
2 | 3 |
4 | 5 |
6 |
7 | 8 | 10 |
11 |
Name is required
12 |
13 |
14 | 15 |
16 | 17 | 23 |
24 |
Category is required
25 |
26 |
27 | 28 |
29 | 30 | 32 |
33 |
Location is required
34 |
35 |
36 | 37 |
38 | 39 | 41 |
42 |
Country is required
43 |
44 |
45 | 46 |
47 | 48 | 51 |
52 |
Starting Price is required
53 |
54 |
55 | 56 |
57 | 58 | 60 |
61 |
Ending Date is required
62 |
63 |
64 | 65 |
66 | 67 | 69 |
70 |
Description is required
71 |
72 |
73 | 74 | 78 | 79 |
80 | image 81 |
82 | 83 |
84 | 88 |
89 |
90 |
91 |
-------------------------------------------------------------------------------- /front-end/src/app/auction-info/auction-info.component.html: -------------------------------------------------------------------------------- 1 |
2 |
3 | 4 |
5 | 6 | 7 |
8 |
9 | 10 | 12 |
13 |
14 | 15 |

{{auction.category}}

16 | 22 |
23 |
24 | 25 | 27 |
28 |
29 | 30 | 32 |
33 |
34 | 35 | 37 |
38 |
39 | 40 | 42 |
43 |
44 | 45 | 47 |
48 |
49 | 50 |
51 | image 52 |
53 | 57 |
58 | 59 | 60 |
61 | 64 | 67 | 70 |
71 | 72 | 73 |
74 |

Current Bids

75 | 76 | 77 |
78 |
79 |
80 | 83 |
84 |
Body is required
85 |
86 |
87 | 88 | 93 |
94 |
95 | 96 |
97 |
    98 |
  • 99 | Amount: {{ bid.amount }} at: {{ bid.time | date:'medium' }} 100 |
  • 101 |
102 |
103 |
104 |
-------------------------------------------------------------------------------- /front-end/src/app/auction-info/auction-info.component.ts: -------------------------------------------------------------------------------- 1 | import { Component, Input, OnChanges } from '@angular/core'; 2 | import { Auction, Bid } from '../_models'; 3 | import { AuctionService, BidService, AuctionsService } from '../_services'; 4 | import { AlertService } from '../_alert'; 5 | import { first } from 'rxjs/operators'; 6 | import { MessagesService } from '../_services'; 7 | import { FormGroup, FormBuilder, Validators } from '@angular/forms'; 8 | import * as jwt_decode from "jwt-decode"; 9 | 10 | @Component({ 11 | selector: 'app-auction-info', 12 | templateUrl: './auction-info.component.html', 13 | styleUrls: ['./auction-info.component.css'] 14 | }) 15 | export class AuctionInfoComponent implements OnChanges { 16 | @Input() auction: Auction; 17 | bidsArray: Bid[]; 18 | loading = false; 19 | categories: string[]; 20 | messageForm: FormGroup; 21 | submitted: boolean = false; 22 | 23 | constructor( 24 | private auctionService: AuctionService, 25 | private alertService: AlertService, 26 | private bidService: BidService, 27 | private auctionsService: AuctionsService, 28 | private messagesService: MessagesService, 29 | private formBuilder: FormBuilder, 30 | ) { } 31 | 32 | ngOnChanges() { 33 | if (this.auction.no_bids != 0) { 34 | this.loadAllBids(); 35 | } 36 | 37 | this.loadAllCategories(); 38 | 39 | this.messageForm = this.formBuilder.group({ 40 | text: ['', Validators.required], 41 | subject: [''], 42 | sender: [''], 43 | receiver: [''], 44 | time: [] 45 | }); 46 | } 47 | 48 | delete(): void { 49 | if (this.auction.no_bids != 0) { 50 | this.alertService.error("You cannot delete an auction after the first bid has been placed"); 51 | } 52 | else { 53 | this.auctionService.deleteAuction(this.auction._id).subscribe(); 54 | window.location.reload(); 55 | } 56 | } 57 | 58 | edit(): void { 59 | if (this.auction.started != null) { 60 | this.alertService.error("You cannot edit an auction after it has started"); 61 | } 62 | else { 63 | this.auctionService.updateAuction(this.auction).subscribe(); 64 | this.alertService.success("Auction edited"); 65 | } 66 | } 67 | 68 | start(): void { 69 | if (this.auction.started != null) { 70 | this.alertService.error("The auction has already started") 71 | } 72 | else { 73 | this.auction.started = new Date(); 74 | this.auctionService.updateAuction(this.auction).subscribe(); 75 | window.location.reload(); 76 | } 77 | } 78 | 79 | disableEdit(auction: Auction): boolean { 80 | let state: boolean = false; 81 | if (this.auction.started != null) { 82 | state = true; 83 | } 84 | return state; 85 | } 86 | 87 | private loadAllBids() { 88 | this.loading = true; 89 | this.bidService.getAllBids(this.auction._id).pipe(first()).subscribe(res => { 90 | this.loading = false; 91 | let newObj: any = res; 92 | this.bidsArray = newObj.bids; 93 | }); 94 | } 95 | 96 | private loadAllCategories() { 97 | this.auctionsService.getCategories().pipe(first()).subscribe(res => { 98 | let newObj: any = res; 99 | this.categories = newObj.categories; 100 | }); 101 | } 102 | 103 | enableMessaging(auction: Auction): boolean { 104 | let state: boolean = false; 105 | let now = new Date(); 106 | let ends = new Date(auction.ends); 107 | 108 | if (now > ends && auction.no_bids != 0) { 109 | state = true; 110 | } 111 | 112 | return state; 113 | } 114 | 115 | // convenience getter for easy access to form fields 116 | get f() { return this.messageForm.controls; } 117 | 118 | sendMessage() { 119 | this.submitted = true; 120 | 121 | // stop here if form is invalid 122 | if (this.messageForm.invalid) { 123 | return; 124 | } 125 | 126 | this.messageForm.value.subject = "Congratulations, you won the auction: " + this.auction.name; 127 | 128 | let currentUserJSON = JSON.parse(localStorage.getItem('currentUser')); 129 | let tokenInfo = jwt_decode(currentUserJSON.token); 130 | this.messageForm.value.sender = tokenInfo.userId; 131 | this.messageForm.value.receiver = this.bidsArray[0].bidder; 132 | this.messageForm.value.time = new Date(); 133 | 134 | this.loading = true; 135 | this.messagesService.newMessage(this.messageForm.value) 136 | .pipe(first()) 137 | .subscribe( 138 | data => { 139 | window.location.reload(); 140 | }, 141 | error => { 142 | this.alertService.error(error); 143 | this.loading = false; 144 | }); 145 | } 146 | 147 | public onChange(event) { 148 | this.auction.currently = event.target.value; 149 | } 150 | 151 | addImages(event: any) { 152 | const file = event.target.files[0]; 153 | 154 | if (file) { 155 | const reader = new FileReader(); 156 | 157 | reader.onload = this.handleReaderLoaded.bind(this); 158 | reader.readAsBinaryString(file); 159 | } 160 | } 161 | 162 | handleReaderLoaded(e) { 163 | this.auction.images.push(btoa(e.target.result)); 164 | } 165 | 166 | } 167 | -------------------------------------------------------------------------------- /api/routes/bids.js: -------------------------------------------------------------------------------- 1 | const express = require('express'); 2 | const router = express.Router(); 3 | const mongoose = require('mongoose'); 4 | 5 | const Auction = require('../models/auction'); 6 | const Product = require('../models/product'); 7 | const User = require('../models/user'); 8 | const Bid = require('../models/bid'); 9 | 10 | router.get('/:bidId', (req, res, next) => { 11 | Bid.findById(req.params.bidId) 12 | .select('_id auction bidder amount time') 13 | .exec() 14 | .then(bid => { 15 | if (!bid) { 16 | return res.status(404).json({ 17 | message: 'Bid Not Found' 18 | }); 19 | } 20 | res.status(200).json({ 21 | _id: bid._id, 22 | auction: bid.auction, 23 | bidder: bid.bidder, 24 | amount: bid.amount, 25 | time: bid.time 26 | }); 27 | }) 28 | .catch(err => { 29 | res.status(500).json({ 30 | error: err 31 | }); 32 | }); 33 | }); 34 | 35 | router.post('/', (req, res, next) => { 36 | const bid = new Bid({ 37 | _id: new mongoose.Types.ObjectId(), 38 | auction: req.body.auction, 39 | bidder: req.body.bidder, 40 | time: req.body.time, 41 | amount: req.body.amount 42 | }); 43 | Auction.findById(req.body.auction) 44 | .exec() 45 | .then(auction => { 46 | if (!auction) { 47 | return res.status(404).json({ 48 | message: 'Auction Not Found' 49 | }); 50 | } 51 | else { 52 | User.findById(req.body.bidder) 53 | .then(user => { 54 | if (!user) { 55 | return res.status(404).json({ 56 | message: "User Not Found" 57 | }); 58 | } 59 | if (req.body.amount <= auction.currently) { 60 | return res.status(404).json({ 61 | message: "Amount <= Current Price" 62 | }); 63 | } 64 | if (req.body.time < auction.started) { 65 | return res.status(404).json({ 66 | message: "Time < Auction Start" 67 | }); 68 | } 69 | if (req.body.time > auction.ends) { 70 | return res.status(404).json({ 71 | message: "Time > Auction End" 72 | }); 73 | } 74 | bid.save() 75 | .then(result => { 76 | console.log(result); 77 | auction.currently = req.body.amount; 78 | auction.no_bids++; 79 | auction.bids = auction.bids || []; 80 | auction.bids.push(bid._id); 81 | auction.save(); 82 | //not tested 83 | user.bid = user.bid || []; 84 | user.bid.push(bid._id); 85 | user.save(); 86 | 87 | res.status(201).json({ 88 | message: 'Bid Created' 89 | }); 90 | }).catch(err => { 91 | console.log(err); 92 | res.status(500).json({ 93 | error: err 94 | }); 95 | }); 96 | }); 97 | } 98 | }) 99 | .catch(err => { 100 | res.status(500).json({ 101 | error: err 102 | }); 103 | }); 104 | }); 105 | //delete all 106 | /* 107 | router.delete('/', (req, res, next) => { 108 | Bid.find() 109 | .select('_id') 110 | .exec() 111 | .then(docs => { 112 | docs.map(doc => { 113 | Bid.remove({ _id: doc._id}) 114 | .exec() 115 | .then(result => { 116 | res.status(200).json({ 117 | message: 'Bid Deleted' 118 | }) 119 | }) 120 | .catch(err => { 121 | console.log(err); 122 | res.status(500).json({ 123 | error: err 124 | }); 125 | }) 126 | }); 127 | }) 128 | .catch(err => { 129 | console.log(err); 130 | res.status(500).json({ 131 | error: err 132 | }); 133 | }) 134 | }); 135 | */ 136 | 137 | //update all user bids 138 | router.post('/update', (req, res, next) => { 139 | Bid.find() 140 | .select('_id bidder auction') 141 | .exec() 142 | .then(docs => { 143 | docs.map(doc => { 144 | User.findById(doc.bidder) 145 | .exec() 146 | .then(user => { 147 | user.bid = user.bid || []; 148 | if (!user.bid.includes(doc.auction)) 149 | { 150 | user.bid.push(doc.auction); 151 | user.save(); 152 | } 153 | else 154 | { 155 | console.log(user); 156 | } 157 | }); 158 | }); 159 | }) 160 | .catch(err => { 161 | res.status(500).json({ 162 | error: err 163 | }); 164 | }); 165 | return res.status(200).json({ 166 | message: 'Complete' 167 | }); 168 | }); 169 | 170 | module.exports = router; 171 | -------------------------------------------------------------------------------- /front-end/src/app/home/home.component.ts: -------------------------------------------------------------------------------- 1 | import { Component, OnInit } from '@angular/core'; 2 | import { first } from 'rxjs/operators'; 3 | import { Auction, User } from '../_models'; 4 | import { AuctionsService, AuthenticationService, MessagesService } from '../_services'; 5 | import { ActivatedRoute, Router } from '@angular/router'; 6 | import { FormGroup, FormBuilder } from '@angular/forms'; 7 | 8 | @Component({ 9 | selector: 'app-home', 10 | templateUrl: './home.component.html', 11 | styleUrls: ['./home.component.css'] 12 | }) 13 | export class HomeComponent implements OnInit { 14 | auctions: Auction[]; 15 | loading: boolean = false; 16 | count: any; 17 | currentUser: User; 18 | unreadCount: number = 0; 19 | unread: boolean = false; 20 | pageN: any; 21 | selectedCategory: any = "Shop by category"; 22 | categories: any[]; 23 | currRouter: string; 24 | searchForm: FormGroup; 25 | 26 | constructor( 27 | private auctionsService: AuctionsService, 28 | private authenticationService: AuthenticationService, 29 | private messagesService: MessagesService, 30 | private route: ActivatedRoute, 31 | private router: Router, 32 | private formBuilder: FormBuilder 33 | ) { 34 | this.authenticationService.currentUser.subscribe(x => this.currentUser = x); 35 | this.currRouter = router.url; 36 | this.unreadMessages() 37 | } 38 | 39 | ngOnInit() { 40 | this.route.queryParams.subscribe(x => this.loadPage(x)); 41 | this.loadAllCategories(); 42 | 43 | this.searchForm = this.formBuilder.group({ 44 | text: [''], 45 | price: [''], 46 | location: [''], 47 | category: [''] 48 | }); 49 | } 50 | 51 | private loadPage(params: any) { 52 | let page = params.page || 1; 53 | 54 | if (params.text || params.price || params.location) { 55 | this.loading = true; 56 | this.auctionsService.searchAuctions(encodeURIComponent(params.category), params.text, params.price, params.location, page).pipe(first()).subscribe(newObj => { 57 | this.loading = false; 58 | this.auctions = newObj.auctions; 59 | this.count = newObj.count; 60 | this.pageN = newObj.pageN; 61 | }); 62 | 63 | return; 64 | } 65 | 66 | if (params.category) { 67 | this.loading = true; 68 | this.auctionsService.searchCategory(encodeURIComponent(params.category), page).pipe(first()).subscribe(newObj => { 69 | this.loading = false; 70 | this.auctions = newObj.auctions; 71 | this.count = newObj.count; 72 | this.pageN = newObj.pageN; 73 | }); 74 | 75 | return; 76 | } 77 | 78 | this.loading = true; 79 | this.auctionsService.getPageAuctions(page).pipe(first()).subscribe(newObj => { 80 | this.loading = false; 81 | this.auctions = newObj.auctions; 82 | this.count = newObj.count; 83 | this.pageN = newObj.pageN; 84 | }); 85 | } 86 | 87 | unreadMessages() { 88 | if (this.currentUser) { 89 | this.messagesService.getUnread().pipe(first()).subscribe(res => { 90 | this.unreadCount = res.unread; 91 | 92 | if (this.unreadCount == 0) { 93 | this.unread = false; 94 | } 95 | else { 96 | this.unread = true; 97 | } 98 | }); 99 | } 100 | } 101 | 102 | searchByCategory() { 103 | this.router.navigate( 104 | [], 105 | { 106 | relativeTo: this.route, 107 | queryParams: { 'page': '1', 'category': this.selectedCategory.name }, 108 | replaceUrl: true 109 | }); 110 | } 111 | 112 | nextPage() { 113 | let nextP = this.pageN + 1; 114 | this.router.navigate( 115 | [], 116 | { 117 | relativeTo: this.route, 118 | queryParams: { page: nextP }, 119 | replaceUrl: true, 120 | queryParamsHandling: 'merge' 121 | }); 122 | } 123 | 124 | previousPage() { 125 | let previousP = this.pageN - 1; 126 | this.router.navigate( 127 | [], 128 | { 129 | relativeTo: this.route, 130 | queryParams: { page: previousP }, 131 | replaceUrl: true, 132 | queryParamsHandling: 'merge' 133 | }); 134 | } 135 | 136 | private loadAllCategories() { 137 | this.auctionsService.getCategories().pipe(first()).subscribe(res => { 138 | let newObj: any = res; 139 | this.categories = newObj.categories; 140 | }); 141 | } 142 | 143 | searchSubmit() { 144 | // Checking if the query is empty 145 | if (this.searchForm.value.category == "" && this.searchForm.value.text == "" && this.searchForm.value.price == "" && this.searchForm.value.location == "") { 146 | return; 147 | } 148 | 149 | this.router.navigate( 150 | [], 151 | { 152 | relativeTo: this.route, 153 | queryParams: { 'page': '1', 'category': this.searchForm.value.category, 'text': this.searchForm.value.text, 'price': this.searchForm.value.price, 'location': this.searchForm.value.location }, 154 | replaceUrl: true 155 | }); 156 | } 157 | } -------------------------------------------------------------------------------- /front-end/src/app/user-info/user-info.component.html: -------------------------------------------------------------------------------- 1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
9 | 11 |
12 |
13 |

14 |

{{user.username}} 15 |

16 |
Verified: {{user.verified}}
17 |
Rating: {{user.rating}}
18 | 19 |
20 |
21 |
22 | 23 |
24 |
25 | 30 |
31 |
33 | 34 |
35 |
36 | 37 |
38 |
39 | {{user.email}} 40 |
41 |
42 |
43 | 44 |
45 |
46 | 47 |
48 |
49 | {{user.firstname}} {{user.lastname}} 50 |
51 |
52 |
53 | 54 |
55 |
56 | 57 |
58 |
59 | {{user.phone}} 60 |
61 |
62 |
63 | 64 |
65 |
66 | 67 |
68 |
69 | {{user.city}} 70 |
71 |
72 |
73 | 74 |
75 |
76 | 77 |
78 |
79 | {{user.address}} 80 |
81 |
82 |
83 | 84 |
85 |
86 | 87 |
88 |
89 | {{user.afm}} 90 |
91 |
92 |
93 | 94 |
95 |
96 |
97 |
98 |
99 |
100 |
101 |
102 |
-------------------------------------------------------------------------------- /front-end/src/app/auction-detail/auction-detail.component.css: -------------------------------------------------------------------------------- 1 | .center-title { 2 | width: 500px; 3 | display: block; 4 | margin-left: auto; 5 | margin-right: auto; 6 | padding-top: 25px; 7 | border-bottom: 2px solid #CCCCCC; 8 | text-align: center; 9 | } 10 | 11 | .center-title h3 { 12 | font-weight: bold; 13 | } 14 | 15 | .center { 16 | width: 600px; 17 | display: block; 18 | margin-left: auto; 19 | margin-right: auto; 20 | padding-top: 25px; 21 | text-align: center; 22 | } 23 | 24 | .center-price { 25 | width: 600px; 26 | display: block; 27 | margin-left: 540px; 28 | margin-right: auto; 29 | padding-top: 25px; 30 | text-align: center; 31 | } 32 | 33 | #price { 34 | font-weight: bold; 35 | position: relative; 36 | float: right; 37 | top: -43px; 38 | left: -170px; 39 | font-size: 1.3rem; 40 | } 41 | 42 | #bids { 43 | position: relative; 44 | float: right; 45 | top: -39px; 46 | left: -40px; 47 | } 48 | 49 | .info { 50 | border: 1px solid #CCCCCC; 51 | width: 1500px; 52 | display: block; 53 | margin-left: auto; 54 | margin-right: auto; 55 | margin-top: 30px; 56 | padding-top: 25px; 57 | overflow: hidden 58 | } 59 | 60 | .info h5 { 61 | padding-left: 20px; 62 | } 63 | 64 | .info p { 65 | padding-left: 20px; 66 | padding-top: 10px; 67 | width: 1000px; 68 | } 69 | 70 | .info ul { 71 | position: relative; 72 | float: right; 73 | list-style-type: none; 74 | padding-right: 40px; 75 | } 76 | 77 | .info label { 78 | font-size: 1.2rem; 79 | } 80 | 81 | .location { 82 | border: 1px solid #CCCCCC; 83 | width: 700px; 84 | display: block; 85 | margin-left: auto; 86 | margin-right: auto; 87 | margin-top: 30px; 88 | padding-bottom: 10px; 89 | padding-left: 20px; 90 | padding-right: 20px; 91 | overflow: hidden 92 | } 93 | 94 | .location p { 95 | position: relative; 96 | top: 150px; 97 | } 98 | 99 | .no-location { 100 | border: 1px solid #CCCCCC; 101 | width: 700px; 102 | display: block; 103 | margin-left: auto; 104 | margin-right: auto; 105 | margin-top: 30px; 106 | padding-left: 20px; 107 | padding-right: 20px; 108 | overflow: hidden 109 | } 110 | 111 | .centerbid { 112 | width: 600px; 113 | display: block; 114 | margin-left: auto; 115 | margin-right: auto; 116 | padding-top: 25px; 117 | padding-left: 160px; 118 | } 119 | 120 | .container { 121 | max-width: 1200px; 122 | margin: 0 auto; 123 | padding: 15px; 124 | display: flex; 125 | } 126 | 127 | .left-column { 128 | width: 65%; 129 | position: relative; 130 | } 131 | 132 | .left-column li { 133 | display: inline; 134 | vertical-align: bottom; 135 | } 136 | 137 | .right-column { 138 | width: 35%; 139 | margin-top: 60px; 140 | } 141 | 142 | .left-column img { 143 | width: 100%; 144 | padding: 20px 20px 0px 0px; 145 | } 146 | 147 | .product-description { 148 | border-bottom: 1px solid #E1E8EE; 149 | margin-bottom: 20px; 150 | } 151 | .product-description span { 152 | font-size: 12px; 153 | color: #358ED7; 154 | letter-spacing: 1px; 155 | text-transform: uppercase; 156 | text-decoration: none; 157 | padding-right: 10px; 158 | } 159 | .product-description h1 { 160 | font-weight: 300; 161 | font-size: 52px; 162 | color: #43484D; 163 | letter-spacing: -2px; 164 | } 165 | .product-description p { 166 | font-size: 16px; 167 | font-weight: 300; 168 | color: #86939E; 169 | line-height: 24px; 170 | } 171 | 172 | .product-color { 173 | margin-bottom: 30px; 174 | border-bottom: 1px solid #E1E8EE; 175 | padding-bottom: 20px; 176 | } 177 | 178 | .color-choose div { 179 | display: inline-block; 180 | } 181 | 182 | .cable-choose { 183 | margin-bottom: 20px; 184 | } 185 | 186 | .cable-choose button { 187 | border: 2px solid #E1E8EE; 188 | border-radius: 6px; 189 | padding: 13px 20px; 190 | font-size: 14px; 191 | color: #5E6977; 192 | background-color: #fff; 193 | cursor: pointer; 194 | transition: all .5s; 195 | } 196 | 197 | .cable-choose button:hover, 198 | .cable-choose button:active, 199 | .cable-choose button:focus { 200 | border: 2px solid #86939E; 201 | outline: none; 202 | } 203 | 204 | .cable-config { 205 | border-bottom: 1px solid #E1E8EE; 206 | margin-bottom: 20px; 207 | } 208 | 209 | .cable-config a { 210 | color: #358ED7; 211 | text-decoration: none; 212 | font-size: 12px; 213 | position: relative; 214 | margin: 10px 0; 215 | display: inline-block; 216 | } 217 | .cable-config a:before { 218 | height: 15px; 219 | width: 15px; 220 | border-radius: 50%; 221 | border: 2px solid rgba(53, 142, 215, 0.5); 222 | display: inline-block; 223 | text-align: center; 224 | line-height: 16px; 225 | opacity: 0.5; 226 | margin-right: 5px; 227 | } 228 | 229 | /* Product Price */ 230 | .product-price { 231 | display: flex; 232 | align-items: center; 233 | } 234 | 235 | .product-price span { 236 | font-size: 26px; 237 | font-weight: 300; 238 | color: #43474D; 239 | margin-right: 20px; 240 | } 241 | 242 | .cart-btn { 243 | display: inline-block; 244 | background-color: #7DC855; 245 | border-radius: 6px; 246 | font-size: 16px; 247 | color: #FFFFFF; 248 | text-decoration: none; 249 | padding: 12px 30px; 250 | transition: all .5s; 251 | } 252 | .cart-btn:hover { 253 | background-color: #64af3d; 254 | } -------------------------------------------------------------------------------- /api/routes/messages.js: -------------------------------------------------------------------------------- 1 | const express = require('express'); 2 | const router = express.Router(); 3 | const mongoose = require('mongoose'); 4 | const bcrypt = require('bcrypt'); 5 | const jwt = require('jsonwebtoken'); 6 | 7 | const User = require('../models/user'); 8 | const Message = require('../models/message'); 9 | 10 | router.get('/:userId/sent', (req, res, next) => { 11 | Message.find({ sender: req.params.userId }) 12 | .select('_id sender receiver sender_username receiver_username time subject text read') 13 | .exec() 14 | .then(docs => { 15 | const response = { 16 | count: docs.length, 17 | messages: docs.map(doc => { 18 | return { 19 | _id: doc._id, 20 | sender: doc.sender, 21 | receiver: doc.receiver, 22 | sender_username: doc.sender_username, 23 | receiver_username: doc.receiver_username, 24 | subject: doc.subject, 25 | time: doc.time, 26 | text: doc.text, 27 | read: doc.read 28 | } 29 | }) 30 | }; 31 | res.status(200).json(response); 32 | }) 33 | .catch(err => { 34 | console.log(err); 35 | res.status(500).json({ 36 | error: err 37 | }); 38 | }) 39 | }); 40 | 41 | router.get('/:userId/received', (req, res, next) => { 42 | Message.find({ receiver: req.params.userId }) 43 | .select('_id sender receiver sender_username receiver_username time subject text read') 44 | .exec() 45 | .then(docs => { 46 | const response = { 47 | count: docs.length, 48 | messages: docs.map(doc => { 49 | return { 50 | _id: doc._id, 51 | sender: doc.sender, 52 | receiver: doc.receiver, 53 | sender_username: doc.sender_username, 54 | receiver_username: doc.receiver_username, 55 | subject: doc.subject, 56 | time: doc.time, 57 | text: doc.text, 58 | read: doc.read 59 | } 60 | }) 61 | }; 62 | res.status(200).json(response); 63 | }) 64 | .catch(err => { 65 | console.log(err); 66 | res.status(500).json({ 67 | error: err 68 | }); 69 | }) 70 | }); 71 | 72 | router.get('/:userId/received/unread', (req, res, next) => { 73 | Message.find({ receiver: req.params.userId }) 74 | .select('_id sender receiver time subject text read') 75 | .exec() 76 | .then(docs => { 77 | let i = 0; 78 | for (var index = 0; index < docs.length; index++) { 79 | if (docs[index].read == false) { 80 | i++; 81 | } 82 | } 83 | const response = { 84 | unread: i 85 | }; 86 | res.status(200).json(response); 87 | }) 88 | .catch(err => { 89 | console.log(err); 90 | res.status(500).json({ 91 | error: err 92 | }); 93 | }) 94 | }); 95 | 96 | router.post('/', (req, res, next) => { 97 | const message = new Message({ 98 | _id: new mongoose.Types.ObjectId(), 99 | sender: req.body.sender, 100 | receiver: req.body.receiver, 101 | subject: req.body.subject, 102 | time: req.body.time, 103 | text: req.body.text, 104 | read: req.body.read 105 | }); 106 | User.findById(req.body.sender) 107 | .exec() 108 | .then(user1 => { 109 | if (!user1) { 110 | return res.status(404).json({ 111 | message: 'Sender Not Found' 112 | }); 113 | } 114 | message.sender_username = user1.username; 115 | User.findById(req.body.receiver) 116 | .exec() 117 | .then(user2 => { 118 | if (!user2) { 119 | return res.status(404).json({ 120 | message: 'Sender Not Found' 121 | }); 122 | } 123 | message.receiver_username = user2.username; 124 | message.save() 125 | .then(result => { 126 | console.log(result); 127 | res.status(201).json({ 128 | message: 'Message Created' 129 | }); 130 | }).catch(err => { 131 | console.log(err); 132 | res.status(500).json({ 133 | error: err 134 | }); 135 | }); 136 | }) 137 | .catch(err => { 138 | console.log(result); 139 | res.status(500).json({ 140 | error: err 141 | }); 142 | }); 143 | }) 144 | .catch(err => { 145 | res.status(500).json({ 146 | error: err 147 | }); 148 | }); 149 | }); 150 | 151 | router.put('/:messageId', (req, res, next) => { 152 | const id = req.params.messageId; 153 | 154 | Message.findById(req.params.messageId) 155 | .exec() 156 | .then(message => { 157 | if (!message) { 158 | return res.status(404).json({ 159 | message: 'Message Not Found' 160 | }); 161 | } 162 | const temp_message = new Message({ 163 | _id: message._id, 164 | sender: message.sender, 165 | receiver: message.receiver, 166 | subject: message.subject, 167 | time: message.time, 168 | text: message.text, 169 | read: message.read 170 | }); 171 | 172 | if (req.body.read) 173 | temp_message.read = req.body.read; 174 | 175 | Message.update({ _id: id }, { 176 | $set: { 177 | sender: temp_message.sender, 178 | receiver: temp_message.receiver, 179 | subject: temp_message.subject, 180 | time: temp_message.time, 181 | text: temp_message.text, 182 | read: temp_message.read 183 | } 184 | }) 185 | .exec() 186 | .then(result => { 187 | res.status(200).json({ 188 | message: 'Message updated' 189 | }); 190 | }) 191 | .catch(err => { 192 | console.log(err); 193 | res.status(500).json({ 194 | error: err 195 | }); 196 | }); 197 | }); 198 | }); 199 | 200 | router.delete('/:messageId', (req, res, next) => { 201 | Message.remove({ _id: req.params.messageId }) 202 | .exec() 203 | .then(result => { 204 | res.status(200).json({ 205 | message: 'Message Deleted' 206 | }) 207 | }) 208 | .catch(err => { 209 | console.log(err); 210 | res.status(500).json({ 211 | error: err 212 | }); 213 | }); 214 | }) 215 | module.exports = router; 216 | -------------------------------------------------------------------------------- /front-end/src/app/register/register.component.html: -------------------------------------------------------------------------------- 1 |
2 |
3 | logo 4 |

Create your eLagoon Account

5 |
to continue to the website
6 |
7 |
8 |
9 |
10 | 11 | 13 |
14 |
Username is required
15 |
16 |
17 | 18 |
19 | 20 | 22 |
23 |
Password is required
24 |
Password must be at least 6 characters
25 |
26 |
27 | 28 |
29 | 30 | 32 |
33 |
Confirm Password is required
34 |
Passwords must match
35 |
36 |
37 | 38 |
39 | 40 | 42 |
43 |
First Name is required
44 |
45 |
46 | 47 |
48 | 49 | 51 |
52 |
Last Name is required
53 |
54 |
55 | 56 |
57 | 58 | 60 |
61 |
E-mail is required
62 |
Enter a valid email
63 |
64 |
65 | 66 |
67 | 68 | 70 |
71 |
Phone is required
72 |
73 |
74 | 75 |
76 | 77 | 79 |
80 |
Address is required
81 |
82 |
83 | 84 |
85 | 86 | 88 |
89 |
City is required
90 |
91 |
92 | 93 |
94 | 95 | 97 |
98 |
AFM is required
99 |
100 |
101 | 102 |
103 | 104 | 110 |
111 |
Role is required
112 |
113 |
114 | 115 |
116 | 120 | Login instead 121 |
122 |
123 |
124 |
--------------------------------------------------------------------------------