Guest Book

Chore:
  Added New Day Offset and Timezone Offset in settings so that they can be shared consistently and also set in runtime.
This commit is contained in:
2020-09-22 10:53:31 +05:30
parent cabdd505e0
commit b19d8cc030
13 changed files with 223 additions and 75 deletions

View File

@ -8,8 +8,11 @@ from pydantic import BaseSettings, PostgresDsn, validator
class Settings(BaseSettings): class Settings(BaseSettings):
# openssl rand -hex 32 # openssl rand -hex 32
SECRET_KEY: str = secrets.token_urlsafe(32) SECRET_KEY: str = secrets.token_urlsafe(32)
MIDDLEWARE_SECRET_KEY: str = secrets.token_urlsafe(5)
ALGORITHM: str = "HS256" ALGORITHM: str = "HS256"
JWT_TOKEN_EXPIRE_MINUTES: int = 30 JWT_TOKEN_EXPIRE_MINUTES: int = 30
NEW_DAY_OFFSET_MINUTES: int = 7 * 60
TIMEZONE_OFFSET_MINUTES: int = 330
HOST: str = "0.0.0.0" HOST: str = "0.0.0.0"
PORT: int = 80 PORT: int = 80
DEBUG: bool = False DEBUG: bool = False

View File

@ -37,11 +37,16 @@ class GuestBook(Base):
customer = relationship("Customer") customer = relationship("Customer")
def __init__(self, customer_id=None, pax=None, id_=None): def __init__(self, pax=None, id_=None, customer_id=None, customer=None):
self.customer_id = customer_id self.customer_id = customer_id
self.pax = pax self.pax = pax
self.id = id_ self.id = id_
self.date = datetime.utcnow() self.date = datetime.utcnow()
if customer is None:
self.customer_id = customer_id
else:
self.customer = customer
class Overview(Base): class Overview(Base):

View File

@ -1,69 +1,126 @@
import uuid import uuid
from datetime import datetime, date, timedelta from typing import Optional
from datetime import date, timedelta, datetime
from fastapi import APIRouter, HTTPException, status, Depends, Security
from sqlalchemy.exc import SQLAlchemyError
from sqlalchemy.orm import Session
import transaction from ..core.config import settings
from pyramid.view import view_config from ..schemas.auth import UserToken
import barker.schemas.master as schemas
from ..core.security import get_current_active_user as get_user
from ..db.session import SessionLocal
from ..models.master import Customer
from ..models.voucher import GuestBook
from barker.models import GuestBook, Customer router = APIRouter()
@view_config( # Dependency
request_method="PUT", route_name="v1_guest_book_new", renderer="json", permission="Guest Book", trans=True, def get_db():
) try:
def save(request): db = SessionLocal()
json = request.json_body yield db
company = json["company"].strip() if json["company"] is not None else "" finally:
name = json["name"].strip() db.close()
phone = json["phone"]
address = json["address"].strip()
customer = request.dbsession.query(Customer).filter(Customer.phone == phone).first()
if customer is None:
customer = Customer(company, name, phone, address)
request.dbsession.add(customer)
else:
if company != "":
customer.company = company
if name != "":
customer.name = name
if address != "":
customer.address = address
item = GuestBook(pax=json["pax"])
item.customer = customer
request.dbsession.add(item)
transaction.commit()
item = request.dbsession.query(GuestBook).filter(GuestBook.id == item.id).first()
return guest_book_info(item)
@view_config( @router.post("", response_model=schemas.GuestBook)
request_method="GET", route_name="v1_guest_book_id", renderer="json", permission="Guest Book", def save(
) data: schemas.GuestBookIn,
def show_id(request): db: Session = Depends(get_db),
id_ = request.matchdict["id"] user: UserToken = Security(get_user, scopes=["guest-book"]),
item = request.dbsession.query(GuestBook).filter(GuestBook.id == uuid.UUID(id_)).one() ):
return guest_book_info(item) try:
customer: Customer = db.query(Customer).filter(Customer.phone == data.phone).first()
if customer is None:
customer = Customer(company=data.company, name=data.name, phone=data.phone, address=data.address,)
db.add(customer)
else:
customer.name = data.name or customer.name
customer.company = data.company or customer.company
customer.address = data.address or customer.address
item = GuestBook(pax=data.pax, customer=customer)
db.add(item)
db.commit()
return guest_book_info(item)
except SQLAlchemyError as e:
db.rollback()
raise HTTPException(
status_code=status.HTTP_500_INTERNAL_SERVER_ERROR, detail=str(e),
)
except Exception:
db.rollback()
raise
@view_config( @router.put("/{id_}", response_model=schemas.GuestBook)
request_method="GET", route_name="v1_guest_book_new", renderer="json", permission="Guest Book", def update(
) id_: uuid.UUID,
def show_blank(request): data: schemas.GuestBookIn,
db: Session = Depends(get_db),
user: UserToken = Security(get_user, scopes=["guest-book"]),
):
try:
item: GuestBook = db.query(GuestBook).filter(GuestBook.id == id_).first()
item.customer.company = data.company
item.customer.name = data.name
item.customer.phone = data.phone
item.customer.address = data.address
item.pax = data.pax
db.commit()
return guest_book_info(item)
except SQLAlchemyError as e:
db.rollback()
raise HTTPException(
status_code=status.HTTP_500_INTERNAL_SERVER_ERROR, detail=str(e),
)
except Exception:
db.rollback()
raise
@router.delete("/{id_}")
def delete(
id_: uuid.UUID, db: Session = Depends(get_db), user: UserToken = Security(get_user, scopes=["guest-book"]),
):
try:
item: GuestBook = db.query(GuestBook).filter(GuestBook.id == id_).first()
db.delete(item)
db.commit()
return guest_book_info(None)
except SQLAlchemyError as e:
db.rollback()
raise HTTPException(
status_code=status.HTTP_500_INTERNAL_SERVER_ERROR, detail=str(e),
)
except Exception:
db.rollback()
raise
@router.get("")
def show_blank(
db: Session = Depends(get_db), user: UserToken = Security(get_user, scopes=["guest-book"]),
):
return guest_book_info(None) return guest_book_info(None)
@view_config( @router.get("/list")
request_method="GET", route_name="v1_guest_book_list", renderer="json", permission="Authenticated", def show_list(q: Optional[str] = None, db: Session = Depends(get_db), user: UserToken = Depends(get_user)):
) if q is None or q == "":
def show_list(request): q = date.today()
date_ = request.GET.get("q", None) else:
if date_ is None or date_ == "": q = datetime.strptime(q, "%d-%b-%Y")
date_ = date.today().strftime("%d-%b-%Y")
list_ = ( list_ = (
request.dbsession.query(GuestBook) db.query(GuestBook)
.filter( .filter(
GuestBook.date >= datetime.strptime(date_, "%d-%b-%Y") - timedelta(minutes=30) # hack for timezone and 5 am GuestBook.date >= q + timedelta(minutes=settings.NEW_DAY_OFFSET_MINUTES - settings.TIMEZONE_OFFSET_MINUTES)
)
.filter(
GuestBook.date
< q + timedelta(minutes=settings.NEW_DAY_OFFSET_MINUTES - settings.TIMEZONE_OFFSET_MINUTES, days=1)
) )
.filter(GuestBook.date < datetime.strptime(date_, "%d-%b-%Y") - timedelta(minutes=30) + timedelta(days=1))
.order_by(GuestBook.date) .order_by(GuestBook.date)
.all() .all()
) )
@ -79,13 +136,22 @@ def show_list(request):
"name": item.customer.name, "name": item.customer.name,
"phone": item.customer.phone, "phone": item.customer.phone,
"pax": item.pax, "pax": item.pax,
"date": item.date.strftime("%d-%b-%Y %H:%M"),
"status": "" if item.status is None else item.status.status, "status": "" if item.status is None else item.status.status,
}, },
) )
return {"date": date_, "list": guest_book} return {"date": q.strftime("%d-%b-%Y"), "list": guest_book}
def guest_book_info(item): @router.get("/{id_}")
def show_id(
id_: uuid.UUID, db: Session = Depends(get_db), user: UserToken = Security(get_user, scopes=["guest-book"]),
):
item: GuestBook = db.query(GuestBook).filter(GuestBook.id == id_).first()
return guest_book_info(item)
def guest_book_info(item: Optional[GuestBook]):
if item is not None: if item is not None:
return { return {
"id": item.id, "id": item.id,
@ -94,6 +160,7 @@ def guest_book_info(item):
"phone": item.customer.phone, "phone": item.customer.phone,
"pax": item.pax, "pax": item.pax,
"address": item.customer.address, "address": item.customer.address,
"date": item.date.strftime("%d-%b-%Y %H:%M"),
} }
else: else:
return {"company": "", "name": "", "phone": "", "pax": 0, "address": ""} return {"company": "", "name": "", "phone": "", "pax": 0, "address": ""}

View File

@ -0,0 +1,25 @@
import uuid
from pydantic import BaseModel, Field
from barker.schemas import to_camel
from barker.schemas.customer import CustomerIn
class GuestBookIn(CustomerIn):
pax: int = Field(ge=0)
class Config:
fields = {"id_": "id"}
anystr_strip_whitespace = True
alias_generator = to_camel
class GuestBook(GuestBookIn):
id_: uuid.UUID
date: str
class Config:
fields = {"id_": "id"}
anystr_strip_whitespace = True
alias_generator = to_camel

View File

@ -9,6 +9,7 @@ from barker.schemas import to_camel
from barker.schemas.tax import TaxLink, TaxIn, Tax # noqa: F401 from barker.schemas.tax import TaxLink, TaxIn, Tax # noqa: F401
from barker.schemas.sale_category import SaleCategoryLink, SaleCategoryIn, SaleCategory # noqa: F401 from barker.schemas.sale_category import SaleCategoryLink, SaleCategoryIn, SaleCategory # noqa: F401
from barker.schemas.customer import CustomerIn, Customer # noqa: F401 from barker.schemas.customer import CustomerIn, Customer # noqa: F401
from barker.schemas.guest_book import GuestBookIn, GuestBook # noqa: F401
from barker.schemas.menu_category import MenuCategoryLink, MenuCategoryIn, MenuCategory # noqa: F401 from barker.schemas.menu_category import MenuCategoryLink, MenuCategoryIn, MenuCategory # noqa: F401
from barker.schemas.product import ProductLink, ProductIn, Product # noqa: F401 from barker.schemas.product import ProductLink, ProductIn, Product # noqa: F401
from barker.schemas.modifier_category import ModifierCategoryLink, ModifierCategoryIn, ModifierCategory # noqa: F401 from barker.schemas.modifier_category import ModifierCategoryLink, ModifierCategoryIn, ModifierCategory # noqa: F401

View File

@ -25,7 +25,7 @@ import { CoreModule } from './core/core.module';
import { ReactiveFormsModule } from '@angular/forms'; import { ReactiveFormsModule } from '@angular/forms';
import { SharedModule } from './shared/shared.module'; import { SharedModule } from './shared/shared.module';
import { FlexLayoutModule } from '@angular/flex-layout'; import { FlexLayoutModule } from '@angular/flex-layout';
import { MatToolbarModule } from "@angular/material/toolbar"; import { MatToolbarModule } from '@angular/material/toolbar';
import { NavBarComponent } from './nav-bar/nav-bar.component'; import { NavBarComponent } from './nav-bar/nav-bar.component';
registerLocaleData(enIN); registerLocaleData(enIN);

View File

@ -43,6 +43,12 @@
<mat-cell *matCellDef="let row">{{row.pax}}</mat-cell> <mat-cell *matCellDef="let row">{{row.pax}}</mat-cell>
</ng-container> </ng-container>
<!-- Time Column -->
<ng-container matColumnDef="date">
<mat-header-cell *matHeaderCellDef>Time</mat-header-cell>
<mat-cell *matCellDef="let row">{{row.date | localTime}}</mat-cell>
</ng-container>
<!-- Action Column --> <!-- Action Column -->
<ng-container matColumnDef="action"> <ng-container matColumnDef="action">
<mat-header-cell *matHeaderCellDef class="center">Action</mat-header-cell> <mat-header-cell *matHeaderCellDef class="center">Action</mat-header-cell>
@ -53,7 +59,7 @@
<button mat-icon-button [routerLink]="['/guest-book/', row.id]"> <button mat-icon-button [routerLink]="['/guest-book/', row.id]">
<mat-icon>edit</mat-icon> <mat-icon>edit</mat-icon>
</button> </button>
<button mat-icon-button color="warn" (click)="deleteRow(row)"> <button mat-icon-button color="warn" (click)="confirmDelete(row.id)">
<mat-icon>delete</mat-icon> <mat-icon>delete</mat-icon>
</button> </button>
</mat-cell> </mat-cell>

View File

@ -1,10 +1,13 @@
import { Component, OnInit } from '@angular/core'; import { Component, OnInit } from '@angular/core';
import { ActivatedRoute } from '@angular/router'; import { ActivatedRoute, Router } from '@angular/router';
import { FormBuilder, FormGroup } from '@angular/forms'; import { FormBuilder, FormGroup } from '@angular/forms';
import { MatDialog } from '@angular/material/dialog';
import { BehaviorSubject } from 'rxjs'; import { BehaviorSubject } from 'rxjs';
import { map } from 'rxjs/operators'; import { map } from 'rxjs/operators';
import * as moment from 'moment'; import * as moment from 'moment';
import {GuestBook, GuestBookList} from '../guest-book'; import { ConfirmDialogComponent } from '../../shared/confirm-dialog/confirm-dialog.component';
import { ToasterService } from '../../core/toaster.service';
import { GuestBook, GuestBookList } from '../guest-book';
import { GuestBookService } from '../guest-book.service'; import { GuestBookService } from '../guest-book.service';
import { GuestBookListDataSource } from './guest-book-list-datasource'; import { GuestBookListDataSource } from './guest-book-list-datasource';
@ -18,9 +21,16 @@ export class GuestBookListComponent implements OnInit {
form: FormGroup; form: FormGroup;
data: BehaviorSubject<GuestBook[]>; data: BehaviorSubject<GuestBook[]>;
/** Columns displayed in the table. Columns IDs can be added, removed, or reordered. */ /** Columns displayed in the table. Columns IDs can be added, removed, or reordered. */
displayedColumns = ['sno', 'name', 'phone', 'pax', 'action']; displayedColumns = ['sno', 'name', 'phone', 'pax', 'date', 'action'];
constructor(private route: ActivatedRoute, private fb: FormBuilder, private ser: GuestBookService) { constructor(
private route: ActivatedRoute,
private router: Router,
private fb: FormBuilder,
private dialog: MatDialog,
private toaster: ToasterService,
private ser: GuestBookService
) {
this.createForm(); this.createForm();
this.data = new BehaviorSubject([]); this.data = new BehaviorSubject([]);
this.listenToDateChange(); this.listenToDateChange();
@ -51,4 +61,32 @@ export class GuestBookListComponent implements OnInit {
}); });
this.dataSource = new GuestBookListDataSource(this.data); this.dataSource = new GuestBookListDataSource(this.data);
} }
delete(id: string) {
this.ser.delete(id)
.subscribe(
(result) => {
this.toaster.show('Success', '');
this.router.navigateByUrl('/guest-book');
},
(error) => {
this.toaster.show('Danger', error);
}
);
}
confirmDelete(id: string): void {
const dialogRef = this.dialog.open(ConfirmDialogComponent, {
width: '250px',
data: {title: 'Delete Guest Book Entry?', content: 'Are you sure? This cannot be undone.'}
});
dialogRef.afterClosed().subscribe((result: boolean) => {
if (result) {
this.delete(id);
}
});
}
} }

View File

@ -16,7 +16,8 @@ const routes: Routes = [
}, },
resolve: { resolve: {
list: GuestBookListResolver list: GuestBookListResolver
} },
runGuardsAndResolvers: 'always'
}, },
{ {
path: 'new', path: 'new',

View File

@ -15,6 +15,7 @@ import { FlexLayoutModule } from '@angular/flex-layout';
import { GuestBookDetailComponent } from './guest-book-detail/guest-book-detail.component'; import { GuestBookDetailComponent } from './guest-book-detail/guest-book-detail.component';
import { GuestBookListComponent } from './guest-book-list/guest-book-list.component'; import { GuestBookListComponent } from './guest-book-list/guest-book-list.component';
import { GuestBookRoutingModule } from './guest-book-routing.module'; import { GuestBookRoutingModule } from './guest-book-routing.module';
import { SharedModule } from '../shared/shared.module';
export const MY_FORMATS = { export const MY_FORMATS = {
parse: { parse: {
@ -43,7 +44,8 @@ export const MY_FORMATS = {
MatNativeDateModule, MatNativeDateModule,
ReactiveFormsModule, ReactiveFormsModule,
FlexLayoutModule, FlexLayoutModule,
GuestBookRoutingModule GuestBookRoutingModule,
SharedModule
], ],
providers: [ providers: [
{provide: DateAdapter, useClass: MomentDateAdapter, deps: [MAT_DATE_LOCALE]}, {provide: DateAdapter, useClass: MomentDateAdapter, deps: [MAT_DATE_LOCALE]},

View File

@ -9,7 +9,7 @@ const httpOptions = {
headers: new HttpHeaders({'Content-Type': 'application/json'}) headers: new HttpHeaders({'Content-Type': 'application/json'})
}; };
const url = '/v1/guest-book'; const url = '/api/guest-book';
const serviceName = 'GuestBookService'; const serviceName = 'GuestBookService';
@Injectable({providedIn: 'root'}) @Injectable({providedIn: 'root'})
@ -19,7 +19,7 @@ export class GuestBookService {
} }
get(id: string): Observable<GuestBook> { get(id: string): Observable<GuestBook> {
const getUrl: string = (id === null) ? `${url}/new` : `${url}/${id}`; const getUrl: string = (id === null) ? url : `${url}/${id}`;
return <Observable<GuestBook>>this.http.get<GuestBook>(getUrl) return <Observable<GuestBook>>this.http.get<GuestBook>(getUrl)
.pipe( .pipe(
catchError(this.log.handleError(serviceName, `get id=${id}`)) catchError(this.log.handleError(serviceName, `get id=${id}`))
@ -28,22 +28,21 @@ export class GuestBookService {
list(date: string): Observable<GuestBookList> { list(date: string): Observable<GuestBookList> {
const options = {params: new HttpParams().set('q', (date === null) ? '' : date)}; const options = {params: new HttpParams().set('q', (date === null) ? '' : date)};
const listUrl: string = url; return <Observable<GuestBookList>>this.http.get<GuestBookList>(`${url}/list`, options)
return <Observable<GuestBookList>>this.http.get<GuestBookList>(listUrl, options)
.pipe( .pipe(
catchError(this.log.handleError(serviceName, 'list')) catchError(this.log.handleError(serviceName, 'list'))
); );
} }
save(guestBook: GuestBook): Observable<GuestBook> { save(guestBook: GuestBook): Observable<GuestBook> {
return <Observable<GuestBook>>this.http.put<GuestBook>(`${url}/new`, guestBook, httpOptions) return <Observable<GuestBook>>this.http.post<GuestBook>(url, guestBook, httpOptions)
.pipe( .pipe(
catchError(this.log.handleError(serviceName, 'save')) catchError(this.log.handleError(serviceName, 'save'))
); );
} }
update(guestBook: GuestBook): Observable<GuestBook> { update(guestBook: GuestBook): Observable<GuestBook> {
return <Observable<GuestBook>>this.http.post<GuestBook>(`${url}/${guestBook.id}`, guestBook, httpOptions) return <Observable<GuestBook>>this.http.put<GuestBook>(`${url}/${guestBook.id}`, guestBook, httpOptions)
.pipe( .pipe(
catchError(this.log.handleError(serviceName, 'update')) catchError(this.log.handleError(serviceName, 'update'))
); );

View File

@ -6,6 +6,7 @@ export class GuestBook {
phone: string; phone: string;
pax: number; pax: number;
address: string; address: string;
date: string;
public constructor(init?: Partial<GuestBook>) { public constructor(init?: Partial<GuestBook>) {
Object.assign(this, init); Object.assign(this, init);

View File

@ -1,12 +1,12 @@
import {NgModule} from '@angular/core'; import { NgModule } from '@angular/core';
import {CommonModule} from '@angular/common'; import { CommonModule } from '@angular/common';
import {ConfirmDialogComponent} from './confirm-dialog/confirm-dialog.component'; import { ConfirmDialogComponent } from './confirm-dialog/confirm-dialog.component';
import { MatButtonModule } from '@angular/material/button'; import { MatButtonModule } from '@angular/material/button';
import { MatDialogModule } from '@angular/material/dialog'; import { MatDialogModule } from '@angular/material/dialog';
import {LocalTimePipe} from './local-time.pipe'; import { LocalTimePipe } from './local-time.pipe';
import {ClearPipe} from './clear.pipe'; import { ClearPipe } from './clear.pipe';
import {AccountingPipe} from './accounting.pipe'; import { AccountingPipe } from './accounting.pipe';
import {ImageDialogComponent} from './image-dialog/image-dialog.component'; import { ImageDialogComponent } from './image-dialog/image-dialog.component';
@NgModule({ @NgModule({
imports: [ imports: [