diff --git a/.gitignore b/.gitignore
index 2f5ac37..bb73101 100644
--- a/.gitignore
+++ b/.gitignore
@@ -5,3 +5,4 @@ env
*/__pycache__/
.idea/
*.egg-info/
+DB/*.sql
diff --git a/DB/db.cmd b/DB/db.cmd
index c218ee9..bb62057 100644
--- a/DB/db.cmd
+++ b/DB/db.cmd
@@ -1,28 +1,28 @@
-"\Program Files\PostgreSQL\9.5\bin\dropdb.exe" -U postgres petty
-"\Program Files\PostgreSQL\9.5\bin\createdb.exe" -U postgres petty
+"\Program Files\PostgreSQL\10\bin\dropdb.exe" -U postgres petty
+"\Program Files\PostgreSQL\10\bin\createdb.exe" -U postgres petty
..\env\Scripts\initdb.exe ..\development.ini
..\env\Scripts\fixtures.exe ..\development.ini
-"\Program Files\PostgreSQL\9.5\bin\psql.exe" -U postgres petty < a-Roles.sql
-"\Program Files\PostgreSQL\9.5\bin\psql.exe" -U postgres petty < b-Permissions.sql
-"\Program Files\PostgreSQL\9.5\bin\psql.exe" -U postgres petty < c-RolePermissions.sql
-"\Program Files\PostgreSQL\9.5\bin\psql.exe" -U postgres petty < d-Users.sql
-"\Program Files\PostgreSQL\9.5\bin\psql.exe" -U postgres petty < e-UserRoles.sql
-"\Program Files\PostgreSQL\9.5\bin\psql.exe" -U postgres petty < f-Customers.sql
-"\Program Files\PostgreSQL\9.5\bin\psql.exe" -U postgres petty < g-FoodTables.sql
-"\Program Files\PostgreSQL\9.5\bin\psql.exe" -U postgres petty < h-Taxes.sql
-"\Program Files\PostgreSQL\9.5\bin\psql.exe" -U postgres petty < i-ProductGroups.sql
-"\Program Files\PostgreSQL\9.5\bin\psql.exe" -U postgres petty < j-Products.sql
-"\Program Files\PostgreSQL\9.5\bin\psql.exe" -U postgres petty < k-Vouchers.sql
-"\Program Files\PostgreSQL\9.5\bin\psql.exe" -U postgres petty < l-Voucher-Food-Table.sql
-"\Program Files\PostgreSQL\9.5\bin\psql.exe" -U postgres petty < m-Kots.sql
-"\Program Files\PostgreSQL\9.5\bin\psql.exe" -U postgres petty < n-Inventories.sql
-"\Program Files\PostgreSQL\9.5\bin\psql.exe" -U postgres petty < o-Modifiers.sql
-"\Program Files\PostgreSQL\9.5\bin\psql.exe" -U postgres petty < p-InventoryModifiers.sql
-"\Program Files\PostgreSQL\9.5\bin\psql.exe" -U postgres petty < q-Locations.sql
-"\Program Files\PostgreSQL\9.5\bin\psql.exe" -U postgres petty < r-MachineLocations.sql
-"\Program Files\PostgreSQL\9.5\bin\psql.exe" -U postgres petty < s-Printers.sql
-"\Program Files\PostgreSQL\9.5\bin\psql.exe" -U postgres petty < t-PrintLocations.sql
-"\Program Files\PostgreSQL\9.5\bin\psql.exe" -U postgres petty < u-ProductGroupModifiers.sql
-"\Program Files\PostgreSQL\9.5\bin\psql.exe" -U postgres petty < v-Reprints.sql
-"\Program Files\PostgreSQL\9.5\bin\psql.exe" -U postgres petty < w-Settings.sql
-"\Program Files\PostgreSQL\9.5\bin\psql.exe" -U postgres petty < x-VoucherSettlements.sql
\ No newline at end of file
+"\Program Files\PostgreSQL\10\bin\psql.exe" -U postgres petty < a-Roles.sql
+"\Program Files\PostgreSQL\10\bin\psql.exe" -U postgres petty < b-Permissions.sql
+"\Program Files\PostgreSQL\10\bin\psql.exe" -U postgres petty < c-RolePermissions.sql
+"\Program Files\PostgreSQL\10\bin\psql.exe" -U postgres petty < d-Users.sql
+"\Program Files\PostgreSQL\10\bin\psql.exe" -U postgres petty < e-UserRoles.sql
+"\Program Files\PostgreSQL\10\bin\psql.exe" -U postgres petty < f-Customers.sql
+"\Program Files\PostgreSQL\10\bin\psql.exe" -U postgres petty < g-FoodTables.sql
+"\Program Files\PostgreSQL\10\bin\psql.exe" -U postgres petty < h-Taxes.sql
+"\Program Files\PostgreSQL\10\bin\psql.exe" -U postgres petty < i-ProductGroups.sql
+"\Program Files\PostgreSQL\10\bin\psql.exe" -U postgres petty < j-Products.sql
+"\Program Files\PostgreSQL\10\bin\psql.exe" -U postgres petty < k-Vouchers.sql
+"\Program Files\PostgreSQL\10\bin\psql.exe" -U postgres petty < l-Voucher-Food-Table.sql
+"\Program Files\PostgreSQL\10\bin\psql.exe" -U postgres petty < m-Kots.sql
+"\Program Files\PostgreSQL\10\bin\psql.exe" -U postgres petty < n-Inventories.sql
+"\Program Files\PostgreSQL\10\bin\psql.exe" -U postgres petty < o-Modifiers.sql
+"\Program Files\PostgreSQL\10\bin\psql.exe" -U postgres petty < p-InventoryModifiers.sql
+"\Program Files\PostgreSQL\10\bin\psql.exe" -U postgres petty < q-Locations.sql
+"\Program Files\PostgreSQL\10\bin\psql.exe" -U postgres petty < r-MachineLocations.sql
+"\Program Files\PostgreSQL\10\bin\psql.exe" -U postgres petty < s-Printers.sql
+"\Program Files\PostgreSQL\10\bin\psql.exe" -U postgres petty < t-PrintLocations.sql
+"\Program Files\PostgreSQL\10\bin\psql.exe" -U postgres petty < u-ProductGroupModifiers.sql
+"\Program Files\PostgreSQL\10\bin\psql.exe" -U postgres petty < v-Reprints.sql
+"\Program Files\PostgreSQL\10\bin\psql.exe" -U postgres petty < w-Settings.sql
+"\Program Files\PostgreSQL\10\bin\psql.exe" -U postgres petty < x-VoucherSettlements.sql
\ No newline at end of file
diff --git a/barker/models/__init__.py b/barker/models/__init__.py
index c3f3d26..b349963 100644
--- a/barker/models/__init__.py
+++ b/barker/models/__init__.py
@@ -12,7 +12,8 @@ from .voucher import (
Reprint,
Settlement,
Voucher,
- VoucherFoodTable,
+ GuestBook,
+ Overview,
VoucherType
)
from .master import (
diff --git a/barker/models/auth.py b/barker/models/auth.py
index be2ebb7..c72c669 100644
--- a/barker/models/auth.py
+++ b/barker/models/auth.py
@@ -76,7 +76,6 @@ class User(Base):
name = Column('name', Unicode(255), unique=True, nullable=False)
_password = Column('password', Unicode(60), nullable=False)
locked_out = Column('locked_out', Boolean, nullable=False)
- msr_string = Column('msr_string', Unicode(255), unique=True)
roles = relationship("Role", secondary=user_roles, order_by="Role.name")
login_history = relationship('LoginHistory', backref='user')
@@ -94,11 +93,10 @@ class User(Base):
def __name__(self):
return self.name
- def __init__(self, name=None, password=None, locked_out=None, msr_string=None, id=None):
+ def __init__(self, name=None, password=None, locked_out=None, id=None):
self.name = name
self.password = password
self.locked_out = locked_out
- self.msr_string = msr_string
self.id = id
@classmethod
@@ -113,14 +111,6 @@ class User(Base):
else:
return True, user
- @classmethod
- def auth_msr(cls, msr, dbsession):
- user = dbsession.query(User).filter(User.msr_string == msr).first()
- if not user:
- return False, None
- else:
- return True, user
-
class LoginHistory(Base):
__tablename__ = 'login_history'
diff --git a/barker/models/master.py b/barker/models/master.py
index 60275c3..d5b6484 100644
--- a/barker/models/master.py
+++ b/barker/models/master.py
@@ -18,26 +18,27 @@ class Customer(Base):
__tablename__ = 'customers'
id = Column('id', GUID(), primary_key=True, default=uuid.uuid4)
- name = Column('name', Unicode(255), nullable=False, unique=True)
- phone = Column('phone', Unicode(255), nullable=False)
+ company = Column('company', Unicode(255), nullable=False)
+ name = Column('name', Unicode(255), nullable=False)
+ phone = Column('phone', Unicode(255), nullable=False, unique=True)
address = Column('address', Unicode(255), nullable=False)
- is_important = Column('is_important', Boolean, nullable=False)
@property
def __name__(self):
return self.name
- def __init__(self, name=None, phone=None, address=None, is_important=None, id=None):
+ def __init__(self, company=None, name=None, phone=None, address=None, id=None):
+ self.company = company
self.name = name
self.phone = phone
self.address = address
- self.is_important = is_important
self.id = id
@classmethod
def cash(cls):
return uuid.UUID('2c716f4b-0736-429a-ad51-610d7c47cb5e')
+
class FoodTable(Base):
__tablename__ = 'food_tables'
@@ -52,11 +53,11 @@ class FoodTable(Base):
def __name__(self):
return self.name
- def __init__(self, name=None, phone=None, address=None, is_important=None, id=None):
+ def __init__(self, name=None, location=None, is_active=None, sort_order=None, id=None):
self.name = name
- self.phone = phone
- self.address = address
- self.is_important = is_important
+ self.location = location
+ self.is_active = is_active
+ self.sort_order = sort_order
self.id = id
@@ -105,8 +106,6 @@ class Product(Base):
product_group_id = Column('product_group_id', GUID(), ForeignKey('product_groups.id'), nullable=False)
vat_id = Column('vat_id', GUID(), ForeignKey('taxes.id'), nullable=False)
service_tax_id = Column('service_tax_id', GUID(), ForeignKey('taxes.id'), nullable=False)
- service_charge = Column('service_charge', Numeric, nullable=False)
- is_sc_taxable = Column('is_sc_taxable', Boolean, nullable=False)
price = Column('price', Numeric, nullable=False)
has_happy_hour = Column('has_happy_hour', Boolean, nullable=False)
is_not_available = Column('is_not_available', Boolean, nullable=False)
@@ -119,16 +118,13 @@ class Product(Base):
vat = relationship('Tax', foreign_keys=vat_id)
service_tax = relationship('Tax', foreign_keys=service_tax_id)
- def __init__(self, name=None, units=None, product_group_id=None, vat_id=None, service_tax_id=None,
- service_charge=None, is_sc_taxable=None, price=None, has_happy_hour=None, is_not_available=None,
- quantity=None, is_active=None, sort_order=None, id=None):
+ def __init__(self, name=None, units=None, product_group_id=None, vat_id=None, service_tax_id=None, price=None,
+ has_happy_hour=None, is_not_available=None, quantity=None, is_active=None, sort_order=None, id=None):
self.name = name
self.units = units
self.product_group_id = product_group_id
self.vat_id = vat_id
self.service_tax_id = service_tax_id
- self.service_charge = service_charge
- self.is_sc_taxable = is_sc_taxable
self.price = price
self.has_happy_hour = has_happy_hour
self.is_not_available = is_not_available
@@ -268,14 +264,14 @@ class SettleOption(Base):
name = Column('name', Unicode(255), unique=True, nullable=False)
show_in_choices = Column('show_in_choices', Boolean, nullable=False)
group = Column('display_group', Integer, nullable=False)
- print = Column('print', Boolean, nullable=False)
+ is_print = Column('is_print', Boolean, nullable=False)
- def __init__(self, name, show_in_choices, group, print, id):
+ def __init__(self, name, show_in_choices, group, is_print, id):
self.id = id
self.name = name
self.show_in_choices = show_in_choices
self.group = group
- self.print = print
+ self.is_print = is_print
@classmethod
def UNSETTLED(cls):
diff --git a/barker/models/voucher.py b/barker/models/voucher.py
index 6d51abc..3c5ee3c 100644
--- a/barker/models/voucher.py
+++ b/barker/models/voucher.py
@@ -17,19 +17,38 @@ class VoucherType(IntEnum):
STAFF = 4
-class VoucherFoodTable(Base):
- __tablename__ = 'voucher_food_table'
+class GuestBook(Base):
+ __tablename__ = 'guest_book'
id = Column('id', GUID(), primary_key=True, default=uuid.uuid4)
- voucher_id = Column('voucher_id', GUID(), ForeignKey('vouchers.id'), nullable=False, unique=True)
- food_table_id = Column('food_table_id', GUID(), ForeignKey('food_tables.id'), nullable=False, unique=True)
+ customer_id = Column('customer_id', GUID(), ForeignKey('customers.id'), nullable=False)
+ pax = Column('pax', Numeric, nullable=False)
+ date = Column('creation_date', DateTime(timezone=True), nullable=False)
+
+ customer = relationship('Customer')
+
+ def __init__(self, customer_id=None, pax=None, id_=None):
+ self.customer_id = customer_id
+ self.pax = pax
+ self.id = id_
+ self.date = datetime.utcnow()
+
+
+class Overview(Base):
+ __tablename__ = 'overview'
+ id = Column('id', GUID(), primary_key=True, default=uuid.uuid4)
+ voucher_id = Column('voucher_id', GUID(), ForeignKey('vouchers.id'), unique=True)
+ food_table_id = Column('food_table_id', GUID(), ForeignKey('food_tables.id'), unique=True)
+ guest_book_id = Column('guest_book_id', GUID(), ForeignKey('guest_book.id'), unique=True)
status = Column('status', Unicode(255), nullable=False)
- food_table = relationship('FoodTable', backref=backref('status', uselist=False))
voucher = relationship('Voucher', backref=backref('status', uselist=False))
+ food_table = relationship('FoodTable', backref=backref('status', uselist=False))
+ guest = relationship('GuestBook', backref=backref('status', uselist=False))
- def __init__(self, voucher_id, food_table_id, status):
+ def __init__(self, voucher_id, food_table_id, guest_book_id, status):
self.voucher_id = voucher_id
self.food_table_id = food_table_id
+ self.guest_book_id = guest_book_id
self.status = status
@@ -190,8 +209,6 @@ class Inventory(Base):
quantity = Column('quantity', Numeric)
price = Column('price', Numeric)
is_happy_hour = Column('is_happy_hour', Boolean, nullable=False)
- service_charge = Column('service_charge', Numeric, nullable=False)
- is_sc_taxable = Column('is_sc_taxable', Boolean, nullable=False)
service_tax_rate = Column('service_tax_rate', Numeric)
service_tax_id = Column('service_tax_id', GUID(), ForeignKey('taxes.id'), nullable=False)
vat_rate = Column('vat_rate', Numeric)
@@ -204,16 +221,14 @@ class Inventory(Base):
service_tax = relationship('Tax', foreign_keys=service_tax_id)
product = relationship('Product', backref='inventories')
- def __init__(self, kot_id, product_id, quantity, price, discount, is_hh, sc, is_sc_taxable, st_id, st_rate, vat_id,
- vat_rate, sort_order):
+ def __init__(self, kot_id, product_id, quantity, price, discount, is_hh, st_id, st_rate, vat_id, vat_rate,
+ sort_order):
self.kot_id = kot_id
self.product_id = product_id
self.quantity = quantity
self.price = price
self.discount = discount
self.is_happy_hour = is_hh
- self.service_charge = sc
- self.is_sc_taxable = is_sc_taxable
self.service_tax_id = st_id
self.service_tax_rate = st_rate
self.vat_id = vat_id
@@ -235,21 +250,6 @@ class Inventory(Base):
def net(self):
return self.effective_price * self.quantity * (1 - self.discount)
- @hybrid_property
- def net_taxable(self):
- return self.net * (1 + self.service_charge if self.is_sc_taxable else 0)
-
- @net_taxable.expression
- def net_taxable(cls):
- return cls.net * (1 + case(
- [(cls.is_sc_taxable == True, cls.service_charge)],
- else_=0
- ))
-
- @hybrid_property
- def sc_amount(self):
- return self.net * self.service_charge
-
@hybrid_property
def st_amount(self):
return self.net_taxable * self.service_tax_rate
@@ -260,13 +260,8 @@ class Inventory(Base):
@hybrid_property
def amount(self):
- if self.is_sc_taxable:
- return Decimal(self.net * (1 + self.service_charge) * (1 + self.service_tax_rate + self.vat_rate))
- return Decimal(self.net * (1 + self.service_charge + self.service_tax_rate + self.vat_rate))
+ return Decimal(self.net * (1 + self.service_tax_rate + self.vat_rate))
@amount.expression
def amount(cls):
- return case([
- (cls.is_sc_taxable == True, cls.net * (1 + cls.service_charge) * (1 + cls.service_tax_rate + cls.vat_rate))
- ],
- else_=cls.net * (1 + cls.service_charge + cls.service_tax_rate + cls.vat_rate))
+ return cls.net * (1 + cls.service_tax_rate + cls.vat_rate)
diff --git a/barker/routes.py b/barker/routes.py
index 6526e1e..57046fb 100644
--- a/barker/routes.py
+++ b/barker/routes.py
@@ -1,7 +1,9 @@
def includeme(config):
- config.add_route('msr', '/Msr.json')
- config.add_route('login', '/Login.json')
- config.add_route('logout', '/Logout.json')
+ config.add_route('home', '/')
+ config.add_route('login', '/login')
+ config.add_route('v1_login', '/v1/login')
+ config.add_route('logout', '/logout')
+ config.add_route('v1_auth', '/v1/auth')
config.add_route('active_cashiers', '/Cashiers.json')
config.add_route('checkout', '/Checkout/{id}.json')
@@ -16,10 +18,6 @@ def includeme(config):
config.add_route('discount_report', '/DiscountReport.json')
- config.add_route('food_table', '/FoodTable.json')
- config.add_route('food_table_list', '/FoodTables.json')
- config.add_route('food_table_id', '/FoodTable/{id}.json')
-
config.add_route('location_list', '/Locations.json')
config.add_route('machine_location', '/MachineLocation.json')
@@ -57,16 +55,11 @@ def includeme(config):
config.add_route('reprint', '/Reprint.json')
config.add_route('reprint_report', '/ReprintReport.json')
- config.add_route('role', '/Role.json')
- config.add_route('role_list', '/Roles.json')
- config.add_route('role_id', '/Role/{id}.json')
-
config.add_route('setting', '/Setting.json')
config.add_route('setting_list', '/Settings.json')
config.add_route('setting_id', '/Setting/{id}.json')
config.add_route('sa_sale', '/SaleAnalysis/Sale.json')
- config.add_route('sa_sc', '/SaleAnalysis/ServiceCharge.json')
config.add_route('sa_settlements', '/SaleAnalysis/Settlements.json')
config.add_route('sa_st', '/SaleAnalysis/ServiceTax.json')
config.add_route('sa_vat', '/SaleAnalysis/Vat.json')
@@ -75,9 +68,30 @@ def includeme(config):
config.add_route('tax_list', '/Taxes.json')
config.add_route('tax_id', '/Tax/{id}.json')
- config.add_route('user', '/User.json')
- config.add_route('user_list', '/Users.json')
- config.add_route('user_id', '/User/{id}.json')
+ config.add_route('v1_users_new', '/v1/users/new')
+ config.add_route('v1_users_list', '/v1/users/list')
+ config.add_route('v1_users_id', '/v1/users/{id}')
+ config.add_route('users_new', '/users/new')
+ config.add_route('users_list', '/users/list')
+ config.add_route('users_id', '/users/{id}')
+
+ config.add_route('v1_roles_new', '/v1/roles/new')
+ config.add_route('v1_roles_list', '/v1/roles/list')
+ config.add_route('v1_roles_id', '/v1/roles/{id}')
+ config.add_route('roles_new', '/roles/new')
+ config.add_route('roles_list', '/roles/list')
+ config.add_route('roles_id', '/roles/{id}')
+
+ config.add_route('v1_guest_book_new', '/v1/guest-book/new')
+ config.add_route('v1_guest_book_list', '/v1/guest-book/list')
+ config.add_route('v1_guest_book_id', '/v1/guest-book/{id}')
+ config.add_route('guest_book_new', '/guest-book/new')
+ config.add_route('guest_book_list', '/guest-book/list')
+ config.add_route('guest_book_id', '/guest-book/{id}')
+
+ config.add_route('food_table', '/table/new.json')
+ config.add_route('food_table_list', '/table/list.json')
+ config.add_route('food_table_id', '/table/{id}.json')
config.add_route('voucher', '/Voucher.json')
config.add_route('voucher_id', '/Voucher/{id}.json')
diff --git a/barker/scripts/fixtures.py b/barker/scripts/fixtures.py
index 310c348..eaa1ff3 100644
--- a/barker/scripts/fixtures.py
+++ b/barker/scripts/fixtures.py
@@ -12,11 +12,13 @@ from pyramid.scripts.common import parse_vars
from barker.models import (
Customer,
DbSetting,
+ Permission,
+ User,
get_engine,
get_session_factory,
get_tm_session,
- SettleOption
-)
+ SettleOption,
+ Role)
def usage(argv):
@@ -40,6 +42,72 @@ def main(argv=sys.argv):
with transaction.manager:
dbsession = get_tm_session(session_factory, transaction.manager)
+ permissions = [Permission('Accounts Audit', uuid.UUID('f30fd1fb-df09-46f5-8c5d-181fd46f38de')),
+ Permission('Beer Consumption', uuid.UUID('efbb8f31-9631-4491-92f4-17cc98e6a0c0')),
+ Permission('Bill Details', uuid.UUID('612bb529-b50d-4653-a1c0-ebb725c7d728')),
+ Permission('Cashier Checkout', uuid.UUID('a86f8bcf-66f4-4c44-89e1-b714488b8331')),
+ Permission('Change Rate', uuid.UUID('10c63aae-0e48-4e54-b3b8-dd8a80b88fbf')),
+ Permission('Customers', uuid.UUID('e5fef133-cdbe-441f-bb54-1f0db0c5db79')),
+ Permission('Discount', uuid.UUID('1f66f131-0105-4466-8f8e-21e0ccc2ac27')),
+ Permission('Discount Report', uuid.UUID('0d8ba1d5-6731-417c-ab0e-be03cfdc96db')),
+ Permission('Edit Printed Bill', uuid.UUID('4ff6cb0f-93cb-4760-8219-4de280eaa957')),
+ Permission('Edit Printed Product', uuid.UUID('959713be-d753-4e14-8ecd-e27f33587499')),
+ Permission('Guest Book', uuid.UUID('7669dfc9-cc75-4e48-b267-145c8832a83c')),
+ Permission('Machines', uuid.UUID('f12b573f-edcb-490d-91c3-fa76f6502ffd')),
+ Permission('Merge Kots', uuid.UUID('bed774e9-4857-43b0-a4af-40230c9eb5db')),
+ Permission('Merge Tables', uuid.UUID('dfc493fb-ac14-4602-8596-f93f47b617de')),
+ Permission('Modifiers', uuid.UUID('9dc82529-1e86-41b4-a152-eaea9f775fea')),
+ Permission('Move Kot to New Table', uuid.UUID('5ed5796a-ca99-437f-8f67-483b1ade81ed')),
+ Permission('Move Table', uuid.UUID('cfdb69c9-d37a-40af-bef2-e29c04602543')),
+ Permission('NC Product', uuid.UUID('54263587-773e-4cbb-b1e4-4e814141158e')),
+ Permission('Open Bill', uuid.UUID('5811d233-a1ae-4d32-af52-cebbf46d274a')),
+ Permission('Owner', uuid.UUID('6d109b66-8452-42aa-bbe9-d4cef440b7a1')),
+ Permission('Print Bill', uuid.UUID('92242eae-bd38-4223-829b-2f981933b7f2')),
+ Permission('Print Kot', uuid.UUID('0eef56fb-7741-49fd-8b84-5069cccae767')),
+ Permission('Products', uuid.UUID('f1f892ed-34aa-4c50-be12-dbaf48be7d4c')),
+ Permission('Roles', uuid.UUID('d39f86f6-14e4-4fc3-b07e-01e2501a6f65')),
+ Permission('Sales', uuid.UUID('92ba45c4-2d34-4cbd-b5b8-d9164c3595d5')),
+ Permission('Sales Analysis', uuid.UUID('0c1acb7c-9116-46e2-a3be-9d632e811330')),
+ Permission('Sales Detail', uuid.UUID('e6476396-ef83-4c47-bc20-9f3b936813f9')),
+ Permission('Settle Bill', uuid.UUID('9f7b658a-25bc-4a53-8560-2dde1c7a188a')),
+ Permission('Split Bill', uuid.UUID('0a8cfaf4-624e-478f-be65-c3b6392078c7')),
+ Permission('Tables', uuid.UUID('ac4512e8-efdd-429e-a7ae-a34d18782663')),
+ Permission('Tax Analysis', uuid.UUID('bab36396-53dd-44ea-ab3c-426efa7830c8')),
+ Permission('Taxes', uuid.UUID('5c8fcdde-460d-4047-810f-e34fb899fadc')),
+ Permission('Users', uuid.UUID('243447b8-b403-47e6-8b3d-8e76f4df44a9')),
+ Permission('Void Bill', uuid.UUID('e3c76262-adc0-4936-8b4d-217c6292298b')),
+ Permission('Void or Reprinted Bill Report', uuid.UUID('30c8e743-c710-42d7-843a-0b75543b3516'))]
+
+ for permission in permissions:
+ dbsession.add(permission)
+
+ roles = [Role('Owner', uuid.UUID('06ec0ddb-949c-4357-aefb-65e5e55a9ae7')),
+ Role('Accountant', uuid.UUID('57d4538c-257a-4bf4-9a42-342992cb6af3')),
+ Role('Bar Captain', uuid.UUID('5a8ebb05-4817-45a5-866f-e523c30bfa25')),
+ Role('Captain', uuid.UUID('8db7001f-0964-4607-84f5-b6f4d9183fef')),
+ Role('Cashier', uuid.UUID('f1f0b112-1404-4b55-b121-f07ee6e08d12')),
+ Role('Controller', uuid.UUID('d9a5a478-1fe4-4847-84da-63cfba1a094a')),
+ Role('Manager', uuid.UUID('90ea7b14-9057-4bfd-a7cf-0ee7a3bb2463')),
+ Role('Senior Captain', uuid.UUID('d9b1b433-1ed5-4109-8ab2-cbd48ff010cd')),
+ Role('Server', uuid.UUID('6b378b71-f091-4485-a589-8db94ff1d6a4'))]
+
+ roles[0].permissions = permissions
+ roles[1].permissions = list(permissions[i] for i in [2, 3, 11, 18, 24, 25, 26, 30, 34])
+ roles[2].permissions = list(permissions[i] for i in [26])
+ roles[3].permissions = list(permissions[i] for i in [16])
+ roles[4].permissions = list(permissions[i] for i in [20, 24, 27])
+ roles[5].permissions = list(permissions[i] for i in [0, 2, 7, 11, 18, 22, 24, 25, 26, 34])
+ roles[6].permissions = list(permissions[i] for i in [2, 3, 4, 5, 6, 8, 11, 14, 18, 22, 25, 26, 28, 29, 33, 34])
+ roles[7].permissions = list(permissions[i] for i in [9, 12, 13, 15, 16])
+ roles[8].permissions = list(permissions[i] for i in [21, 24])
+
+ for role in roles:
+ dbsession.add(role)
+
+ admin = User("Admin", "123456", False)
+ admin.roles.append(roles[0])
+ dbsession.add(admin)
+
options = [
SettleOption('Unsettled', False, 1, True, 1),
SettleOption('Cash', True, 2, False, 2),
@@ -54,8 +122,7 @@ def main(argv=sys.argv):
]
for option in options:
dbsession.add(option)
- dbsession.add(DbSetting(None, 'Service Charge', {'Enabled': False}))
- dbsession.add(Customer("Cash", "", "", False, uuid.UUID('2c716f4b-0736-429a-ad51-610d7c47cb5e')))
+ dbsession.add(Customer("", "Cash", "", "", uuid.UUID('2c716f4b-0736-429a-ad51-610d7c47cb5e')))
dbsession.add(DbSetting(
uuid.UUID('fb738ba2-a3c9-40ed-891c-b930e6454974'),
'Header',
diff --git a/barker/scripts/initializedb.py b/barker/scripts/initializedb.py
index 343e436..9077650 100644
--- a/barker/scripts/initializedb.py
+++ b/barker/scripts/initializedb.py
@@ -42,7 +42,8 @@ from barker.models import (
User,
user_roles,
Voucher,
- VoucherFoodTable,
+ GuestBook,
+ Overview,
VoucherType,
)
diff --git a/barker/views/customer.py b/barker/views/customer.py
index a36bcd4..0a1e674 100644
--- a/barker/views/customer.py
+++ b/barker/views/customer.py
@@ -9,7 +9,7 @@ from barker.models import Customer
@view_config(request_method='PUT', route_name='customer', renderer='json', permission='Customers', trans=True)
def save(request):
json = request.json_body
- item = Customer(json['Name'], json['Phone'], json['Address'], json['Important'])
+ item = Customer(json['company'], json['name'], json['phone'], json['address'], json['important'])
request.dbsession.add(item)
transaction.commit()
item = request.dbsession.query(Customer).filter(Customer.id == item.id).first()
@@ -20,10 +20,11 @@ def save(request):
def update(request):
json = request.json_body
item = request.dbsession.query(Customer).filter(Customer.id == uuid.UUID(request.matchdict['id'])).first()
- item.name = json['Name']
- item.phone = json['Phone']
- item.address = json['Address']
- item.is_important = json['Important']
+ item.company = json['company']
+ item.name = json['name']
+ item.phone = json['phone']
+ item.address = json['address']
+ item.is_important = json['important']
transaction.commit()
item = request.dbsession.query(Customer).filter(Customer.id == item.id).first()
return customer_info(item)
@@ -50,22 +51,24 @@ def show_list(request):
customers = []
for item in list:
customers.append({
- 'CustomerID': item.id,
- 'Name': item.name,
- 'Address': item.address,
- 'Important': item.is_important,
- 'Phone': item.phone,
- 'Remarks': ''
+ 'id': item.id,
+ 'company': item.company,
+ 'name': item.name,
+ 'address': item.address,
+ 'important': item.is_important,
+ 'phone': item.phone,
+ 'remarks': ''
})
return customers
def customer_info(item):
return {
- 'CustomerID': item.id,
- 'Name': item.name,
- 'Address': item.address,
- 'Important': item.is_important,
- 'Phone': item.phone,
- 'Remarks': ''
+ 'id': item.id,
+ 'company': item.company,
+ 'name': item.name,
+ 'address': item.address,
+ 'important': item.is_important,
+ 'phone': item.phone,
+ 'remarks': ''
}
diff --git a/barker/views/food_table.py b/barker/views/food_table.py
index 61c89e3..fc88530 100644
--- a/barker/views/food_table.py
+++ b/barker/views/food_table.py
@@ -4,13 +4,13 @@ import uuid
import transaction
from pyramid.view import view_config
-from barker.models import FoodTable, VoucherFoodTable
+from barker.models import FoodTable, Overview
@view_config(request_method='PUT', route_name='food_table', renderer='json', permission='Tables', trans=True)
def save(request):
json = request.json_body
- item = FoodTable(json['Name'], json['Phone'], json['Address'], json['Important'])
+ item = FoodTable(json['name'], json['location'], json['isActive'], json['sortOrder'])
request.dbsession.add(item)
transaction.commit()
item = request.dbsession.query(FoodTable).filter(FoodTable.id == item.id).first()
@@ -21,10 +21,10 @@ def save(request):
def update(request):
json = request.json_body
item = request.dbsession.query(FoodTable).filter(FoodTable.id == uuid.UUID(request.matchdict['id'])).first()
- item.name = json['Name']
- item.phone = json['Phone']
- item.address = json['Address']
- item.is_important = json['Important']
+ item.name = json['name']
+ item.location = json['location']
+ item.is_active = json['isActive']
+ item.sort_order = json['sortOrder']
transaction.commit()
item = request.dbsession.query(FoodTable).filter(FoodTable.id == item.id).first()
return food_table_info(item)
@@ -40,20 +40,20 @@ def delete(request):
@view_config(request_method='GET', route_name='food_table_id', renderer='json', permission='Authenticated')
def show_id(request):
- id = request.matchdict['id']
+ id_ = request.matchdict['id']
p = re.compile('^[A-Za-z0-9]{8}-[A-Za-z0-9]{4}-[A-Za-z0-9]{4}-[A-Za-z0-9]{4}-[A-Za-z0-9]{12}$')
- if p.match(id):
- id = uuid.UUID(id)
- item = request.dbsession.query(FoodTable).filter(FoodTable.id == id).first()
+ if p.match(id_):
+ id_ = uuid.UUID(id_)
+ item = request.dbsession.query(FoodTable).filter(FoodTable.id == id_).first()
else:
- item = request.dbsession.query(FoodTable).filter(FoodTable.name == id).first()
+ item = request.dbsession.query(FoodTable).filter(FoodTable.name == id_).first()
return food_table_info(item)
@view_config(request_method='GET', route_name='food_table', renderer='json', request_param='v', permission='Tables')
def show_blank(request):
voucher_id = request.GET['v']
- voucher = request.dbsession.query(VoucherFoodTable).filter(VoucherFoodTable.voucher_id == voucher_id).first()
+ voucher = request.dbsession.query(Overview).filter(Overview.voucher_id == voucher_id).first()
if voucher is not None:
return food_table_info(voucher.food_table)
return {}
@@ -62,21 +62,21 @@ def show_blank(request):
@view_config(request_method='GET', route_name='food_table_list', renderer='json', permission='Authenticated')
def show_list(request):
active = request.GET.get('a', None)
- list = request.dbsession.query(FoodTable)
+ list_ = request.dbsession.query(FoodTable)
if active is not None:
- list = list.filter(FoodTable.is_active == active)
- list = list.order_by(FoodTable.sort_order).all()
+ list_ = list_.filter(FoodTable.is_active == active)
+ list_ = list_.order_by(FoodTable.sort_order).all()
food_tables = []
- for item in list:
+ for item in list_:
food_tables.append({
- 'FoodTableID': item.id,
- 'Name': item.name,
- 'IsActive': item.is_active,
- 'Location': item.location,
- 'Status': '' if item.status is None else item.status.status,
- 'SortOrder': item.sort_order,
- 'VoucherID': None if item.status is None else item.status.voucher_id
+ 'id': item.id,
+ 'name': item.name,
+ 'location': item.location,
+ 'isActive': item.is_active,
+ 'sortOrder': item.sort_order,
+ 'status': '' if item.status is None else item.status.status,
+ 'voucherId': None if item.status is None else item.status.voucher_id
})
return food_tables
@@ -97,11 +97,11 @@ def sort_order(request):
def food_table_info(item):
return {
- 'FoodTableID': item.id,
- 'Name': item.name,
- 'IsActive': item.is_active,
- 'Location': item.location,
- 'Status': '' if item.status is None else item.status.status,
- 'SortOrder': item.sort_order,
- 'VoucherID': None if item.status is None else item.status.voucher_id
+ 'id': item.id,
+ 'name': item.name,
+ 'isActive': item.is_active,
+ 'location': item.location,
+ 'status': '' if item.status is None else item.status.status,
+ 'sortOrder': item.sort_order,
+ 'voucherId': None if item.status is None else item.status.voucher_id
}
diff --git a/barker/views/guest_book.py b/barker/views/guest_book.py
new file mode 100644
index 0000000..a1b8c87
--- /dev/null
+++ b/barker/views/guest_book.py
@@ -0,0 +1,96 @@
+import re
+import uuid
+from datetime import datetime, date, timedelta
+
+import transaction
+from pyramid.view import view_config
+from sqlalchemy import desc
+
+from barker.models import FoodTable, Overview, GuestBook, Customer
+
+
+@view_config(request_method='PUT', route_name='v1_guest_book_new', renderer='json', permission='Users', trans=True)
+def save(request):
+ json = request.json_body
+ company = json['company'].strip() if json['company'] is not None else ''
+ name = json['name'].strip()
+ phone = str(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(request_method='GET', route_name='v1_guest_book_id', renderer='json', permission='Authenticated')
+def show_id(request):
+ id_ = request.matchdict['id']
+ item = request.dbsession.query(GuestBook).filter(GuestBook.id == uuid.UUID(id_)).one()
+ return guest_book_info(item)
+
+
+@view_config(request_method='GET', route_name='v1_guest_book_new', renderer='json', permission='Authenticated')
+def show_blank(request):
+ return guest_book_info(None)
+
+
+@view_config(request_method='GET', route_name='v1_guest_book_list', renderer='json', permission='Authenticated')
+def show_list(request):
+ date_ = request.GET.get('q', None)
+ if date_ is None or date_ == '':
+ date_ = date.today().strftime('%d-%b-%Y')
+ list_ = request.dbsession.query(
+ GuestBook
+ ).filter(
+ GuestBook.date >= datetime.strptime(date_, '%d-%b-%Y') - timedelta(minutes=30) # hack for timezone and 5 am
+ ).filter(
+ GuestBook.date < datetime.strptime(date_, '%d-%b-%Y') - timedelta(minutes=30) + timedelta(days=1)
+ ).order_by(
+ GuestBook.date
+ ).all()
+
+ guest_book = []
+ for i, item in enumerate(list_):
+ guest_book.insert(0, {
+ 'id': item.id,
+ 'serial': i + 1,
+ 'company': item.customer.company,
+ 'name': item.customer.name,
+ 'phone': item.customer.phone,
+ 'pax': item.pax,
+ 'status': '' if item.status is None else item.status.status
+ })
+ return guest_book
+
+
+def guest_book_info(item):
+ if item is not None:
+ return {
+ 'id': item.id,
+ 'company': item.customer.company,
+ 'name': item.customer.name,
+ 'phone': item.customer.phone,
+ 'pax': item.pax,
+ 'address': item.customer.address
+ }
+ else:
+ return {
+ 'company': '',
+ 'name': '',
+ 'phone': '',
+ 'pax': 0,
+ 'address': ''
+ }
diff --git a/barker/views/login.py b/barker/views/login.py
index eca2688..569cf8e 100644
--- a/barker/views/login.py
+++ b/barker/views/login.py
@@ -1,3 +1,5 @@
+import uuid
+
import transaction
from pyramid.httpexceptions import HTTPFound
@@ -6,6 +8,7 @@ from pyramid.security import forget, remember
from pyramid.view import view_config
from barker.models import User
+from barker.security import f7
@view_config(route_name='logout')
@@ -15,49 +18,27 @@ def logout(request):
return HTTPFound(location=request.route_url('home'), headers=headers)
-@view_config(request_method='POST', route_name='login', renderer='json', trans=True)
+@view_config(request_method="POST", route_name="logout", renderer="json")
+def api_logout(request):
+ request.session.invalidate()
+ request.response.headers = forget(request)
+ return {"isAuthenticated": False, "perms": {}}
+
+
+@view_config(request_method='POST', route_name='v1_login', renderer='json', trans=True)
def login(request):
- username = request.json_body.get('Name', None)
- password = request.json_body.get('Password', None)
+ username = request.json_body.get('name', None)
+ password = request.json_body.get('password', None)
found, user = User.auth(username, password, request.dbsession)
if found:
headers = remember(request, str(user.id))
auth = {
- 'UserID': str(user.id),
- 'Name': user.name,
- 'Password': '',
- 'LockedOut': user.locked_out,
- 'MsrString': user.msr_string,
- 'Permissions': get_permissions(user)
- }
- request.response.headers = headers
- request.response.content_type = 'application/json'
- request.response.json = auth
- return request.response
- elif not found:
- response = Response()
- response.status_int = 401
- response.content_type = 'application/json'
- response.json = {'Error': 'Login Failed'}
- transaction.abort()
- return response
-
-
-@view_config(request_method='POST', route_name='msr', renderer='json', trans=True)
-def msr(request):
- msr = request.json_body.get('Msr', None)
- found, user = User.auth_msr(msr, request.dbsession)
-
- if found:
- headers = remember(request, str(user.id))
- auth = {
- 'UserID': str(user.id),
- 'Name': user.name,
- 'Password': '',
- 'LockedOut': user.locked_out,
- 'MsrString': user.msr_string,
- 'Permissions': get_permissions(user)
+ 'id': str(user.id),
+ 'name': user.name,
+ 'lockedOut': user.locked_out,
+ 'isAuthenticated': True,
+ 'perms': sorted(f7([p.name for r in user.roles for p in r.permissions]))
}
request.response.headers = headers
request.response.content_type = 'application/json'
@@ -78,3 +59,22 @@ def get_permissions(user):
for perm in role.permissions:
perms.append({'PermissionID': str(perm.id), 'Name': perm.name})
return sorted({p['PermissionID']: p for p in perms}.values(), key=lambda i: i['Name'])
+
+
+@view_config(route_name='v1_auth', renderer='json')
+def user_permission(request):
+ user_id = request.authenticated_userid
+ if user_id is None:
+ auth = {'isAuthenticated': False, 'perms': {}}
+ elif 'auth' in request.session:
+ auth = request.session['auth']
+ else:
+ user = request.dbsession.query(User).filter(User.id == uuid.UUID(user_id)).one()
+ auth = {
+ 'id': user.id,
+ 'name': user.name,
+ 'isAuthenticated': True,
+ 'perms': sorted(f7([p.name for r in user.roles for p in r.permissions]))
+ }
+ request.session['auth'] = auth
+ return auth
diff --git a/barker/views/merge_move.py b/barker/views/merge_move.py
index 5fb1c9d..6e4346f 100644
--- a/barker/views/merge_move.py
+++ b/barker/views/merge_move.py
@@ -3,7 +3,7 @@ import uuid
import transaction
from pyramid.view import view_config
-from barker.models import Kot, Voucher, VoucherFoodTable
+from barker.models import Kot, Voucher, Overview
@view_config(request_method='POST', route_name='merge_kot', renderer='json', permission='Merge Kots', trans=True)
@@ -30,7 +30,7 @@ def move_kot(request):
request.dbsession.add(item)
item.kots.append(kot)
status = "printed" if item.is_printed else "running"
- item.status = VoucherFoodTable(voucher_id=None, food_table_id=item.food_table_id, status=status)
+ item.status = Overview(voucher_id=None, food_table_id=item.food_table_id, status=status)
request.dbsession.add(item.status)
transaction.commit()
return item.id
@@ -43,10 +43,10 @@ def move_table(request):
food_table_id = uuid.UUID(json['FoodTableID'])
request.dbsession.query(
- VoucherFoodTable
+ Overview
).filter(
- VoucherFoodTable.voucher_id == voucher_id
- ).update({VoucherFoodTable.food_table_id: food_table_id})
+ Overview.voucher_id == voucher_id
+ ).update({Overview.food_table_id: food_table_id})
request.dbsession.query(
Voucher
diff --git a/barker/views/product.py b/barker/views/product.py
index a309057..de30d3e 100644
--- a/barker/views/product.py
+++ b/barker/views/product.py
@@ -11,8 +11,8 @@ from barker.models import Product
def save(request):
json = request.json_body
item = Product(json['Name'], json['Units'], json['ProductGroupID'], json['VatID'], json['ServiceTaxID'],
- json['ServiceCharge'], json['IsScTaxable'], json['Price'], json['HasHappyHour'],
- json['IsNotAvailable'], json['Quantity'], json['IsActive'], json['SortOrder'])
+ json['Price'], json['HasHappyHour'], json['IsNotAvailable'], json['Quantity'], json['IsActive'],
+ json['SortOrder'])
request.dbsession.add(item)
transaction.commit()
item = request.dbsession.query(Product).filter(Product.id == item.id).first()
@@ -28,8 +28,6 @@ def update(request):
item.product_group_id = json['ProductGroupID']
item.vat_id = json['VatID']
item.service_tax_id = json['ServiceTaxID']
- item.service_charge = json['ServiceCharge']
- item.is_sc_taxable = json['IsScTaxable']
item.price = json['Price']
item.has_happy_hour = json['HasHappyHour']
item.is_not_available = json['IsNotAvailable']
@@ -102,8 +100,6 @@ def product_info(item):
'ProductGroup': {'ProductGroupID': item.product_group_id, 'Name': item.product_group.name},
'Vat': {'TaxID': item.vat_id, 'Name': item.vat.name, 'Rate': item.vat.rate},
'ServiceTax': {'TaxID': item.service_tax_id, 'Name': item.service_tax.name, 'Rate': item.service_tax.rate},
- 'ServiceCharge': item.service_charge,
- 'IsScTaxable': item.is_sc_taxable,
'Price': item.price,
'HasHappyHour': item.has_happy_hour,
'IsNotAvailable': item.is_not_available,
diff --git a/barker/views/product_group.py b/barker/views/product_group.py
index cf0d7d6..67bcd89 100644
--- a/barker/views/product_group.py
+++ b/barker/views/product_group.py
@@ -89,8 +89,6 @@ def sale_list(request):
'Units': p.units,
'Vat': {'TaxID': p.vat.id, 'Name': p.vat.name, 'Rate': p.vat.rate},
'ServiceTax': {'TaxID': p.service_tax.id, 'Name': p.service_tax.name, 'Rate': p.service_tax.rate},
- 'ServiceCharge': p.service_charge,
- 'IsScTaxable': p.is_sc_taxable,
'Price': p.price,
'HasHappyHour': p.has_happy_hour,
'IsActive': p.is_active,
diff --git a/barker/views/reports/sale_analysis.py b/barker/views/reports/sale_analysis.py
index 7f4892b..8658dbc 100644
--- a/barker/views/reports/sale_analysis.py
+++ b/barker/views/reports/sale_analysis.py
@@ -4,6 +4,13 @@ from pyramid.httpexceptions import HTTPForbidden
from pyramid.view import view_config
from sqlalchemy import func
+from barker.models import Inventory, Kot, Product, ProductGroup, Settlement, SettleOption, Tax, Voucher
+import datetime
+
+from pyramid.httpexceptions import HTTPForbidden
+from pyramid.view import view_config
+from sqlalchemy import func
+
from barker.models import Inventory, Kot, Product, ProductGroup, Settlement, SettleOption, Tax, Voucher
@@ -65,26 +72,6 @@ def get_settlements(request):
return info + [{'GroupType': 'Total', 'Amount': total}]
-@view_config(request_method='GET', route_name='sa_sc', renderer='json', permission='Sales Analysis',
- request_param=('s', 'f'))
-def get_sc(request):
- start_date = datetime.datetime.strptime(request.GET['s'], '%d-%b-%Y %H:%M')
- finish_date = datetime.datetime.strptime(request.GET['f'], '%d-%b-%Y %H:%M')
-
- if (datetime.date.today() - start_date.date()).days > 5 and 'Accounts Audit' not in request.effective_principals:
- raise HTTPForbidden("Accounts Audit")
-
- amount = request.dbsession.query(
- func.coalesce(func.sum(Inventory.sc_amount), 0)
- ).join(Voucher.kots).join(Kot.inventories).filter(
- Voucher.date >= start_date,
- Voucher.date <= finish_date,
- Voucher.is_void == False,
- Voucher.settlements.any(~Settlement.settled.in_([1, 4, 7, 8, 9, 10]))
- ).scalar()
- return {'GroupType': 'Service Charge', 'Amount': amount}
-
-
@view_config(request_method='GET', route_name='sa_st', renderer='json', permission=('Tax Analysis', 'Sales Analysis'),
request_param=('s', 'f'))
def get_st(request):
diff --git a/barker/views/role.py b/barker/views/role.py
index 7d2915c..2532d03 100644
--- a/barker/views/role.py
+++ b/barker/views/role.py
@@ -7,42 +7,45 @@ from pyramid.view import view_config
from barker.models import Permission, Role
-@view_config(request_method='PUT', route_name='role', renderer='json', permission='Users', trans=True)
+@view_config(request_method='PUT', route_name='v1_roles_new', renderer='json', permission='Users', trans=True)
def save(request):
item = Role(request.json_body['name'])
request.dbsession.add(item)
- add_permissions(item, request.json_body['Permissions'], request.dbsession)
+ add_permissions(item, request.json_body['permissions'], request.dbsession)
transaction.commit()
item = request.dbsession.query(Role).filter(Role.id == item.id).first()
- return role_info(item)
+ permissions = request.dbsession.query(Permission).order_by(Permission.name).all()
+ return role_info(item, permissions)
-@view_config(request_method='POST', route_name='role_id', renderer='json', permission='Users', trans=True)
+@view_config(request_method='POST', route_name='v1_roles_id', renderer='json', permission='Users', trans=True)
def update(request):
- id = request.matchdict.get('id', None)
- item = request.dbsession.query(Role).filter(Role.id == uuid.UUID(id)).one()
- item.name = request.json_body['Name']
- add_permissions(item, request.json_body['Roles'], request.dbsession)
+ id_ = request.matchdict['id']
+ item = request.dbsession.query(Role).filter(Role.id == uuid.UUID(id_)).one()
+ item.name = request.json_body['name']
+ add_permissions(item, request.json_body['permissions'], request.dbsession)
transaction.commit()
item = request.dbsession.query(Role).filter(Role.id == item.id).first()
- return role_info(item)
+ permissions = request.dbsession.query(Permission).order_by(Permission.name).all()
+ return role_info(item, permissions)
def add_permissions(role, permissions, dbsession):
- new = {uuid.UUID(p['PermissionID']) for p in permissions}
- old = {p.id for p in role.permissions}
- for perm_id in new - old:
- role.permissions.append(dbsession.query(Permission).filter(Permission.id == perm_id).one())
-
- gp = [p for p in role.permissions if p.id in (old - new)]
- for item in gp:
- role.permissions.remove(item)
+ for permission in permissions:
+ id_ = uuid.UUID(permission['id'])
+ rp = [p for p in role.permissions if p.id == id_]
+ rp = None if len(rp) == 0 else rp[0]
+ if permission['enabled'] and rp is None:
+ permission_object = dbsession.query(Permission).filter(Permission.id == id_).one()
+ role.permissions.append(permission_object)
+ elif not permission['enabled'] and rp:
+ role.permissions.remove(rp)
-@view_config(request_method='DELETE', route_name='role_id', renderer='json', permission='Users', trans=True)
+@view_config(request_method='DELETE', route_name='v1_roles_id', renderer='json', permission='Users', trans=True)
def delete(request):
- id = request.matchdict.get('id', None)
- if id is None:
+ id_ = request.matchdict['id']
+ if id_ is None:
response = Response("Role is Null")
response.status_int = 500
return response
@@ -52,42 +55,38 @@ def delete(request):
return response
-@view_config(request_method='GET', route_name='role_id', renderer='json', permission='Authenticated')
+@view_config(request_method='GET', route_name='v1_roles_id', renderer='json', permission='Authenticated')
def show_id(request):
- id = uuid.UUID(request.matchdict['id'])
- item = request.dbsession.query(Role).filter(Role.id == id).first()
- return role_info(item)
+ id_ = uuid.UUID(request.matchdict['id'])
+ item = request.dbsession.query(Role).filter(Role.id == id_).first()
+ permissions = request.dbsession.query(Permission).order_by(Permission.name).all()
+ return role_info(item, permissions)
-@view_config(request_method='GET', route_name='role_list', renderer='json', permission='Authenticated')
+@view_config(request_method='GET', route_name='v1_roles_new', renderer='json', permission='Authenticated')
+def show_blank(request):
+ permissions = request.dbsession.query(Permission).order_by(Permission.name).all()
+ return role_info(None, permissions)
+
+
+@view_config(request_method='GET', route_name='v1_roles_list', renderer='json', permission='Authenticated')
def show_list(request):
- list = request.dbsession.query(Role).order_by(Role.name).all()
-
- roles = []
- for item in list:
- role = {'RoleID': item.id, 'Name': item.name, 'Permissions': []}
- for permission in sorted(item.permissions, key=lambda p: p.name):
- role['Permissions'].append({'PermissionID': permission.id, 'Name': permission.name})
- roles.append(role)
- return roles
+ list_ = request.dbsession.query(Role).order_by(Role.name).all()
+ return [
+ {'id': item.id, 'name': item.name, 'permissions': sorted(p.name for p in item.permissions)} for item in list_
+ ]
-@view_config(request_method='GET', route_name='permission_list', renderer='json', permission='Authenticated')
-def permission_list(request):
- list = request.dbsession.query(Permission).order_by(Permission.name).all()
-
- permissions = []
- for item in list:
- permission = {'PermissionID': item.id, 'Name': item.name}
- permissions.append(permission)
- return permissions
-
-
-def role_info(item):
- return {
- 'RoleID': item.id,
- 'Name': item.name,
- 'Permissions': [{
- 'PermissionID': p.id, 'Name': p.name
- } for p in sorted(item.permissions, key=lambda p: p.name)]
- }
+def role_info(item, permissions):
+ if item is not None:
+ return {
+ 'id': item.id,
+ 'name': item.name,
+ 'permissions': [{'id': p.id, 'name': p.name, 'enabled': True if p in item.permissions else False}
+ for p in permissions]
+ }
+ else:
+ return {
+ 'name': '',
+ 'permissions': [{'id': p.id, 'name': p.name, 'enabled': False} for p in permissions]
+ }
diff --git a/barker/views/user.py b/barker/views/user.py
index bcab857..350438e 100644
--- a/barker/views/user.py
+++ b/barker/views/user.py
@@ -9,18 +9,19 @@ from barker.exceptions import ValidationFailure
from barker.models import Role, User
-@view_config(request_method='PUT', route_name='user', renderer='json', permission='Users', trans=True)
+@view_config(request_method='PUT', route_name='v1_users_new', renderer='json', permission='Users', trans=True)
def save(request):
json = request.json_body
- item = User(json['Name'], json['Password'], json['LockedOut'], json['MsrString'])
+ item = User(json['name'], json['password'], json['lockedOut'])
request.dbsession.add(item)
- add_roles(item, json['Roles'], request.dbsession)
+ add_roles(item, json['roles'], request.dbsession)
transaction.commit()
item = request.dbsession.query(User).filter(User.id == item.id).first()
- return user_info(item)
+ roles = request.dbsession.query(Role).order_by(Role.name).all() if request.has_permission('Users') else []
+ return user_info(item, roles)
-@view_config(request_method='POST', route_name='user_id', renderer='json', permission='Users', trans=True)
+@view_config(request_method='POST', route_name='v1_users_id', renderer='json', permission='Users', trans=True)
def update(request):
json = request.json_body
id = request.matchdict['id']
@@ -31,22 +32,23 @@ def update(request):
item = request.dbsession.query(User).filter(User.name.ilike(id)).first()
if item is None:
raise ValidationFailure('User name / id not found')
- if 'Users' not in request.effective_principals:
- item.name = json['Name']
- item.locked_out = json['LockedOut']
- add_roles(item, json['Roles'], request.dbsession)
+ if request.has_permission('Users'):
+ item.name = json['name']
+ item.locked_out = json['lockedOut']
+ add_roles(item, json['roles'], request.dbsession)
- if json['Password'] != '' and json['Password'] != item.password:
- item.password = json['Password']
+ if json['password'] != '' and json['password'] != item.password:
+ item.password = json['password']
transaction.commit()
item = request.dbsession.query(User).filter(User.id == item.id).first()
- return user_info(item)
+ roles = request.dbsession.query(Role).order_by(Role.name).all() if request.has_permission('Users') else []
+ return user_info(item, roles)
-@view_config(request_method='DELETE', route_name='user_id', renderer='json', permission='Users', trans=True)
+@view_config(request_method='DELETE', route_name='v1_users_id', renderer='json', permission='Users', trans=True)
def delete(request):
- id = request.matchdict['id']
- if id is None:
+ id_ = request.matchdict['id']
+ if id_ is None:
response = Response("User is Null")
response.status_int = 500
return response
@@ -56,13 +58,13 @@ def delete(request):
return response
-@view_config(request_method='POST', route_name='user_id', renderer='json', request_param='p',
+@view_config(request_method='POST', route_name='v1_users_id', renderer='json', request_param='p',
permission='Authenticated', trans=True)
def update_password(request):
json = request.json_body
name = request.matchdict['id']
- old_password = json['OldPassword']
- new_password = json['NewPassword']
+ old_password = json['oldPassword']
+ new_password = json['newPassword']
found, user = User.auth(name, old_password, request.dbsession)
if found:
user.password = new_password
@@ -70,71 +72,73 @@ def update_password(request):
return found
-@view_config(request_method='POST', route_name='user_id', renderer='json', request_param='m',
- permission='Authenticated', trans=True)
-def update_msr(request):
- json = request.json_body
- name = request.matchdict['id']
- old_password = json['OldPassword']
- new_msr = json['NewMsr']
- found, user = User.auth(name, old_password, request.dbsession)
- if found:
- user.msr_string = new_msr
- transaction.commit()
- return found
-
-
-@view_config(request_method='GET', route_name='user_id', renderer='json', permission='Authenticated')
+@view_config(request_method='GET', route_name='v1_users_id', renderer='json', permission='Authenticated')
def show_id(request):
- id = request.matchdict['id']
+ id_ = request.matchdict['id']
p = re.compile('^[A-Za-z0-9]{8}-[A-Za-z0-9]{4}-[A-Za-z0-9]{4}-[A-Za-z0-9]{4}-[A-Za-z0-9]{12}$')
- if p.match(id):
- item = request.dbsession.query(User).filter(User.id == uuid.UUID(id)).one()
+ if p.match(id_):
+ item = request.dbsession.query(User).filter(User.id == uuid.UUID(id_)).one()
else:
- item = request.dbsession.query(User).filter(User.name.ilike(id)).first()
+ item = request.dbsession.query(User).filter(User.name.ilike(id_)).first()
if item is None:
raise ValidationFailure('User name / id not found')
- return user_info(item)
+ roles = request.dbsession.query(Role).order_by(Role.name).all() if request.has_permission('Users') else []
+ return user_info(item, roles)
-@view_config(request_method='GET', route_name='user_list', renderer='json', permission='Users')
+@view_config(request_method='GET', route_name='v1_users_new', renderer='json', permission='Authenticated')
+def show_blank(request):
+ roles = request.dbsession.query(Role).order_by(Role.name).all() if request.has_permission('Users') else []
+ return user_info(None, roles)
+
+
+@view_config(request_method='GET', route_name='v1_users_list', renderer='json', permission='Users')
def show_list(request):
- list = request.dbsession.query(User).order_by(User.name).all()
+ list_ = request.dbsession.query(User).order_by(User.name).all()
return [{
- 'UserID': item.id,
- 'Name': item.name,
- 'LockedOut': item.locked_out,
- 'Roles': [{
- 'RoleID': r.id,
- 'Name': r.name
- } for r in item.roles]
- } for item in list]
+ 'id': item.id,
+ 'name': item.name,
+ 'lockedOut': item.locked_out,
+ 'roles': sorted(r.name for r in item.roles)
+ } for item in list_]
-@view_config(request_method='GET', route_name='user', renderer='json', request_param='names',
+@view_config(request_method='GET', route_name='v1_users_list', renderer='json', request_param='names',
permission='Authenticated')
def show_name(request):
list = request.dbsession.query(User).filter(User.locked_out == False).order_by(User.name).all()
- return [{'Name': item.name} for item in list]
+ return [{'name': item.name} for item in list]
-def user_info(item):
- return {
- 'UserID': item.id,
- 'Name': item.name,
- 'Password': '',
- 'LockedOut': item.locked_out,
- 'Roles': [{
- 'RoleID': r.id,
- 'name': r.name
- } for r in item.roles]}
+def user_info(item, roles):
+ if item is not None:
+ return {
+ 'id': item.id,
+ 'name': item.name,
+ 'password': '',
+ 'lockedOut': item.locked_out,
+ 'roles': [
+ {'id': role.id, 'name': role.name, 'enabled': True if role in item.roles else False} for role in roles
+ ]
+ }
+ else:
+ return {
+ 'name': '',
+ 'password': '',
+ 'lockedOut': False,
+ 'roles': [
+ {'id': role.id, 'name': role.name, 'enabled': False} for role in roles
+ ]
+ }
def add_roles(user, roles, dbsession):
- new = {uuid.UUID(r['RoleID']) for r in roles}
- old = {r.id for r in user.roles}
- for role_id in new - old:
- user.roles.append(dbsession.query(Role).filter(Role.id == role_id).one())
- ur = [r for r in user.roles if r.id in (old - new)]
- for item in ur:
- user.roles.remove(item)
+ for role in roles:
+ id_ = uuid.UUID(role['id'])
+ ur = [r for r in user.roles if r.id == id_]
+ ur = None if len(ur) == 0 else ur[0]
+ if role['enabled'] and ur is None:
+ role_object = dbsession.query(Role).filter(Role.id == id_).one()
+ user.roles.append(role_object)
+ elif not role['enabled'] and ur:
+ user.roles.remove(ur)
diff --git a/barker/views/voucher.py b/barker/views/voucher.py
index 415b48c..8fd3023 100644
--- a/barker/views/voucher.py
+++ b/barker/views/voucher.py
@@ -16,7 +16,7 @@ from barker.models import (
Settlement,
SettleOption,
Voucher,
- VoucherFoodTable,
+ Overview,
VoucherType,
)
@@ -35,7 +35,7 @@ def save(request):
item = save_voucher(json, request.dbsession)
if update_table:
status = "printed" if item.is_printed else "running"
- item.status = VoucherFoodTable(voucher_id=None, food_table_id=item.food_table_id, status=status)
+ item.status = Overview(voucher_id=None, food_table_id=item.food_table_id, status=status)
request.dbsession.add(item.status)
transaction.commit()
item = request.dbsession.query(Voucher).filter(Voucher.id == item.id).first()
@@ -89,8 +89,8 @@ def update(request):
item.last_edit_date = now
for k in item.kots:
for i in k.inventories:
- i.service_charge, i.service_tax_rate, i.vat_rate = get_sc_st_vat(
- i.service_charge, i.service_tax_rate, i.vat_rate, item.voucher_type
+ i.service_tax_rate, i.vat_rate = get_st_vat(
+ i.service_tax_rate, i.vat_rate, item.voucher_type
)
i.discount = next(
Decimal(inv['Discount']) for ko in json['Kots'] for inv in ko['Inventories'] if
@@ -101,9 +101,9 @@ def update(request):
item.kots.append(kot)
request.dbsession.add(kot)
for index, i in enumerate(k['Inventories']):
- sc, st, vat = get_sc_st_vat(i['ServiceCharge'], i['ServiceTaxRate'], i['VatRate'], json['VoucherType'])
+ st, vat = get_st_vat(i['ServiceTaxRate'], i['VatRate'], json['VoucherType'])
inv = Inventory(kot.id, uuid.UUID(i['ProductID']), i['Quantity'], i['Price'], i['Discount'],
- i['IsHappyHour'], sc, i['IsScTaxable'], i['ServiceTaxID'], st, i['VatID'], vat, index)
+ i['IsHappyHour'], i['ServiceTaxID'], st, i['VatID'], vat, index)
kot.inventories.append(inv)
request.dbsession.add(inv)
for m in i['Modifiers']:
@@ -112,10 +112,10 @@ def update(request):
request.dbsession.add(mod)
get_settlements(item, request.dbsession)
if update_table:
- vft = request.dbsession.query(VoucherFoodTable).filter(VoucherFoodTable.voucher_id == item.id).first()
+ vft = request.dbsession.query(Overview).filter(Overview.voucher_id == item.id).first()
status = "printed" if item.is_printed else "running"
if vft is None:
- item.status = VoucherFoodTable(voucher_id=None, food_table_id=item.food_table_id, status=status)
+ item.status = Overview(voucher_id=None, food_table_id=item.food_table_id, status=status)
request.dbsession.add(item.status)
else:
vft.status = status
@@ -150,13 +150,14 @@ def settle_voucher(request):
unsettled = get_settlements(item, request.dbsession)
if unsettled == 0 and update_table:
- request.dbsession.query(VoucherFoodTable).filter(VoucherFoodTable.voucher_id == item.id).delete()
+ request.dbsession.query(Overview).filter(Overview.voucher_id == item.id).delete()
transaction.commit()
item = request.dbsession.query(Voucher).filter(Voucher.id == item.id).first()
return voucher_info(item)
-@view_config(request_method='POST', route_name='voucher_reprint', renderer='json', permission='Edit Printed Bill', trans=True) #Permission error
+@view_config(request_method='POST', route_name='voucher_reprint', renderer='json', permission='Edit Printed Bill',
+ trans=True) # Permission error
def voucher_change(request):
json = request.json_body
id = uuid.UUID(request.matchdict['id'])
@@ -167,14 +168,14 @@ def voucher_change(request):
# TODO: Set the Void Settlement
if old.status is None:
- item.status = VoucherFoodTable(voucher_id=None, food_table_id=item.food_table_id, status="printed")
+ item.status = Overview(voucher_id=None, food_table_id=item.food_table_id, status="printed")
request.dbsession.add(item.status)
else:
request.dbsession.query(
- VoucherFoodTable
+ Overview
).filter(
- VoucherFoodTable.voucher_id == old.id
- ).update({VoucherFoodTable.voucher_id: item.id})
+ Overview.voucher_id == old.id
+ ).update({Overview.voucher_id: item.id})
transaction.commit()
item = request.dbsession.query(Voucher).filter(Voucher.id == item.id).first()
return voucher_info(item)
@@ -220,14 +221,14 @@ def split_voucher(request):
new_two = save_voucher(json['Two'], request.dbsession)
status = "printed" if item.is_printed else "running"
- new_one.status = VoucherFoodTable(voucher_id=None, food_table_id=new_one.food_table_id, status=status)
+ new_one.status = Overview(voucher_id=None, food_table_id=new_one.food_table_id, status=status)
request.dbsession.add(new_one.status)
request.dbsession.query(
- VoucherFoodTable
+ Overview
).filter(
- VoucherFoodTable.voucher_id == item.id
- ).update({VoucherFoodTable.voucher_id: new_two.id})
+ Overview.voucher_id == item.id
+ ).update({Overview.voucher_id: new_two.id})
transaction.commit()
@@ -306,8 +307,6 @@ def voucher_info(item):
'Quantity': i.quantity,
'Price': i.price,
'IsHappyHour': i.is_happy_hour,
- 'ServiceCharge': i.service_charge,
- 'IsScTaxable': i.is_sc_taxable,
'ServiceTaxRate': i.service_tax_rate,
'VatRate': i.vat_rate,
'ServiceTax': {'TaxID': i.service_tax_id, 'Name': i.service_tax.name},
@@ -328,13 +327,13 @@ def voucher_info(item):
}
-def get_sc_st_vat(sc, st, vat, voucher_type):
+def get_st_vat(st, vat, voucher_type):
if voucher_type == VoucherType.STAFF.value:
- return 0, 0, 0
+ return 0, 0
elif voucher_type == VoucherType.NO_CHARGE.value:
- return 0, 0, 0
+ return 0, 0
else: # voucher_type in [REGULAR_BILL, TAKE_AWAY]:
- return sc, st, vat
+ return st, vat
def get_settlements(voucher, dbsession):
@@ -382,9 +381,9 @@ def save_voucher(json, dbsession):
item.kots.append(kot)
dbsession.add(kot)
for index, i in enumerate(k['Inventories']):
- sc, st, vat = get_sc_st_vat(i['ServiceCharge'], i['ServiceTaxRate'], i['VatRate'], json['VoucherType'])
+ st, vat = get_st_vat(i['ServiceTaxRate'], i['VatRate'], json['VoucherType'])
inv = Inventory(kot.id, uuid.UUID(i['ProductID']), i['Quantity'], i['Price'], i['Discount'],
- i['IsHappyHour'], sc, i['IsScTaxable'], i['ServiceTaxID'], st, i['VatID'], vat, index)
+ i['IsHappyHour'], i['ServiceTaxID'], st, i['VatID'], vat, index)
kot.inventories.append(inv)
dbsession.add(inv)
for m in i['Modifiers']:
diff --git a/bookie/.editorconfig b/bookie/.editorconfig
new file mode 100644
index 0000000..e89330a
--- /dev/null
+++ b/bookie/.editorconfig
@@ -0,0 +1,13 @@
+# Editor configuration, see https://editorconfig.org
+root = true
+
+[*]
+charset = utf-8
+indent_style = space
+indent_size = 2
+insert_final_newline = true
+trim_trailing_whitespace = true
+
+[*.md]
+max_line_length = off
+trim_trailing_whitespace = false
diff --git a/bookie/.gitignore b/bookie/.gitignore
new file mode 100644
index 0000000..ee5c9d8
--- /dev/null
+++ b/bookie/.gitignore
@@ -0,0 +1,39 @@
+# See http://help.github.com/ignore-files/ for more about ignoring files.
+
+# compiled output
+/dist
+/tmp
+/out-tsc
+
+# dependencies
+/node_modules
+
+# IDEs and editors
+/.idea
+.project
+.classpath
+.c9/
+*.launch
+.settings/
+*.sublime-workspace
+
+# IDE - VSCode
+.vscode/*
+!.vscode/settings.json
+!.vscode/tasks.json
+!.vscode/launch.json
+!.vscode/extensions.json
+
+# misc
+/.sass-cache
+/connect.lock
+/coverage
+/libpeerconnection.log
+npm-debug.log
+yarn-error.log
+testem.log
+/typings
+
+# System Files
+.DS_Store
+Thumbs.db
diff --git a/bookie/README.md b/bookie/README.md
new file mode 100644
index 0000000..db1e06a
--- /dev/null
+++ b/bookie/README.md
@@ -0,0 +1,27 @@
+# Bookie
+
+This project was generated with [Angular CLI](https://github.com/angular/angular-cli) version 7.0.6.
+
+## Development server
+
+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.
+
+## Code scaffolding
+
+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`.
+
+## Build
+
+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.
+
+## Running unit tests
+
+Run `ng test` to execute the unit tests via [Karma](https://karma-runner.github.io).
+
+## Running end-to-end tests
+
+Run `ng e2e` to execute the end-to-end tests via [Protractor](http://www.protractortest.org/).
+
+## Further help
+
+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).
diff --git a/bookie/angular.json b/bookie/angular.json
new file mode 100644
index 0000000..3517734
--- /dev/null
+++ b/bookie/angular.json
@@ -0,0 +1,138 @@
+{
+ "$schema": "./node_modules/@angular/cli/lib/config/schema.json",
+ "version": 1,
+ "newProjectRoot": "projects",
+ "projects": {
+ "bookie": {
+ "root": "",
+ "sourceRoot": "src",
+ "projectType": "application",
+ "prefix": "app",
+ "schematics": {},
+ "architect": {
+ "build": {
+ "builder": "@angular-devkit/build-angular:browser",
+ "options": {
+ "outputPath": "dist/bookie",
+ "index": "src/index.html",
+ "main": "src/main.ts",
+ "polyfills": "src/polyfills.ts",
+ "tsConfig": "src/tsconfig.app.json",
+ "assets": [
+ "src/favicon.ico",
+ "src/assets"
+ ],
+ "styles": [
+ "./node_modules/@angular/material/prebuilt-themes/indigo-pink.css",
+ "src/styles.css"
+ ],
+ "scripts": []
+ },
+ "configurations": {
+ "production": {
+ "fileReplacements": [
+ {
+ "replace": "src/environments/environment.ts",
+ "with": "src/environments/environment.prod.ts"
+ }
+ ],
+ "optimization": true,
+ "outputHashing": "all",
+ "sourceMap": false,
+ "extractCss": true,
+ "namedChunks": false,
+ "aot": true,
+ "extractLicenses": true,
+ "vendorChunk": false,
+ "buildOptimizer": true,
+ "budgets": [
+ {
+ "type": "initial",
+ "maximumWarning": "2mb",
+ "maximumError": "5mb"
+ }
+ ]
+ }
+ }
+ },
+ "serve": {
+ "builder": "@angular-devkit/build-angular:dev-server",
+ "options": {
+ "browserTarget": "bookie:build",
+ "proxyConfig": "proxy.conf.json"
+ },
+ "configurations": {
+ "production": {
+ "browserTarget": "bookie:build:production"
+ }
+ }
+ },
+ "extract-i18n": {
+ "builder": "@angular-devkit/build-angular:extract-i18n",
+ "options": {
+ "browserTarget": "bookie:build"
+ }
+ },
+ "test": {
+ "builder": "@angular-devkit/build-angular:karma",
+ "options": {
+ "main": "src/test.ts",
+ "polyfills": "src/polyfills.ts",
+ "tsConfig": "src/tsconfig.spec.json",
+ "karmaConfig": "src/karma.conf.js",
+ "styles": [
+ "./node_modules/@angular/material/prebuilt-themes/indigo-pink.css",
+ "src/styles.css"
+ ],
+ "scripts": [],
+ "assets": [
+ "src/favicon.ico",
+ "src/assets"
+ ]
+ }
+ },
+ "lint": {
+ "builder": "@angular-devkit/build-angular:tslint",
+ "options": {
+ "tsConfig": [
+ "src/tsconfig.app.json",
+ "src/tsconfig.spec.json"
+ ],
+ "exclude": [
+ "**/node_modules/**"
+ ]
+ }
+ }
+ }
+ },
+ "bookie-e2e": {
+ "root": "e2e/",
+ "projectType": "application",
+ "prefix": "",
+ "architect": {
+ "e2e": {
+ "builder": "@angular-devkit/build-angular:protractor",
+ "options": {
+ "protractorConfig": "e2e/protractor.conf.js",
+ "devServerTarget": "bookie:serve"
+ },
+ "configurations": {
+ "production": {
+ "devServerTarget": "bookie:serve:production"
+ }
+ }
+ },
+ "lint": {
+ "builder": "@angular-devkit/build-angular:tslint",
+ "options": {
+ "tsConfig": "e2e/tsconfig.e2e.json",
+ "exclude": [
+ "**/node_modules/**"
+ ]
+ }
+ }
+ }
+ }
+ },
+ "defaultProject": "bookie"
+}
diff --git a/bookie/browserslist b/bookie/browserslist
new file mode 100644
index 0000000..37371cb
--- /dev/null
+++ b/bookie/browserslist
@@ -0,0 +1,11 @@
+# This file is currently used by autoprefixer to adjust CSS to support the below specified browsers
+# For additional information regarding the format and rule options, please see:
+# https://github.com/browserslist/browserslist#queries
+#
+# For IE 9-11 support, please remove 'not' from the last line of the file and adjust as needed
+
+> 0.5%
+last 2 versions
+Firefox ESR
+not dead
+not IE 9-11
\ No newline at end of file
diff --git a/bookie/e2e/protractor.conf.js b/bookie/e2e/protractor.conf.js
new file mode 100644
index 0000000..86776a3
--- /dev/null
+++ b/bookie/e2e/protractor.conf.js
@@ -0,0 +1,28 @@
+// Protractor configuration file, see link for more information
+// https://github.com/angular/protractor/blob/master/lib/config.ts
+
+const { SpecReporter } = require('jasmine-spec-reporter');
+
+exports.config = {
+ allScriptsTimeout: 11000,
+ specs: [
+ './src/**/*.e2e-spec.ts'
+ ],
+ capabilities: {
+ 'browserName': 'chrome'
+ },
+ directConnect: true,
+ baseUrl: 'http://localhost:4200/',
+ framework: 'jasmine',
+ jasmineNodeOpts: {
+ showColors: true,
+ defaultTimeoutInterval: 30000,
+ print: function() {}
+ },
+ onPrepare() {
+ require('ts-node').register({
+ project: require('path').join(__dirname, './tsconfig.e2e.json')
+ });
+ jasmine.getEnv().addReporter(new SpecReporter({ spec: { displayStacktrace: true } }));
+ }
+};
\ No newline at end of file
diff --git a/bookie/e2e/src/app.e2e-spec.ts b/bookie/e2e/src/app.e2e-spec.ts
new file mode 100644
index 0000000..3fefda1
--- /dev/null
+++ b/bookie/e2e/src/app.e2e-spec.ts
@@ -0,0 +1,14 @@
+import { AppPage } from './app.po';
+
+describe('workspace-project App', () => {
+ let page: AppPage;
+
+ beforeEach(() => {
+ page = new AppPage();
+ });
+
+ it('should display welcome message', () => {
+ page.navigateTo();
+ expect(page.getTitleText()).toEqual('Welcome to bookie!');
+ });
+});
diff --git a/bookie/e2e/src/app.po.ts b/bookie/e2e/src/app.po.ts
new file mode 100644
index 0000000..72e463a
--- /dev/null
+++ b/bookie/e2e/src/app.po.ts
@@ -0,0 +1,11 @@
+import { browser, by, element } from 'protractor';
+
+export class AppPage {
+ navigateTo() {
+ return browser.get('/');
+ }
+
+ getTitleText() {
+ return element(by.css('app-root h1')).getText();
+ }
+}
diff --git a/bookie/e2e/tsconfig.e2e.json b/bookie/e2e/tsconfig.e2e.json
new file mode 100644
index 0000000..a6dd622
--- /dev/null
+++ b/bookie/e2e/tsconfig.e2e.json
@@ -0,0 +1,13 @@
+{
+ "extends": "../tsconfig.json",
+ "compilerOptions": {
+ "outDir": "../out-tsc/app",
+ "module": "commonjs",
+ "target": "es5",
+ "types": [
+ "jasmine",
+ "jasminewd2",
+ "node"
+ ]
+ }
+}
\ No newline at end of file
diff --git a/bookie/package.json b/bookie/package.json
new file mode 100644
index 0000000..101a44b
--- /dev/null
+++ b/bookie/package.json
@@ -0,0 +1,59 @@
+{
+ "name": "bookie",
+ "version": "0.0.0",
+ "scripts": {
+ "ng": "ng",
+ "start": "ng serve",
+ "build": "ng build",
+ "test": "ng test",
+ "lint": "ng lint",
+ "e2e": "ng e2e"
+ },
+ "private": true,
+ "dependencies": {
+ "@angular/animations": "^8.0.0",
+ "@angular/cdk": "^8.0.1",
+ "@angular/common": "^8.0.0",
+ "@angular/compiler": "^8.0.0",
+ "@angular/core": "^8.0.0",
+ "@angular/flex-layout": "^8.0.0-beta.26",
+ "@angular/forms": "^8.0.0",
+ "@angular/material": "^8.0.1",
+ "@angular/material-moment-adapter": "^8.0.1",
+ "@angular/platform-browser": "^8.0.0",
+ "@angular/platform-browser-dynamic": "^8.0.0",
+ "@angular/router": "^8.0.0",
+ "@ngx-loading-bar/http-client": "^3.0.0",
+ "@ngx-loading-bar/router": "^3.0.0",
+ "angular2-hotkeys": "^2.1.4",
+ "core-js": "^3.1.1",
+ "hammerjs": "^2.0.8",
+ "mathjs": "^5.10.3",
+ "moment": "^2.24.0",
+ "rxjs": "^6.5.2",
+ "rxjs-tslint": "^0.1.5",
+ "zone.js": "~0.9.1"
+ },
+ "devDependencies": {
+ "@angular-devkit/build-angular": "~0.800.0",
+ "@angular/cli": "^8.0.2",
+ "@angular/compiler-cli": "^8.0.0",
+ "@angular/language-service": "^8.0.0",
+ "@types/jasmine": "^3.3.0",
+ "@types/jasminewd2": "^2.0.3",
+ "@types/node": "^12.0.2",
+ "codelyzer": "^5.0.1",
+ "jasmine-core": "^3.1.0",
+ "jasmine-spec-reporter": "^4.2.1",
+ "karma": "^4.1.0",
+ "karma-chrome-launcher": "^2.2.0",
+ "karma-coverage-istanbul-reporter": "^2.0.1",
+ "karma-jasmine": "^2.0.1",
+ "karma-jasmine-html-reporter": "^1.4.2",
+ "protractor": "^5.4.0",
+ "standard-version": "^6.0.1",
+ "ts-node": "^8.1.0",
+ "tslint": "^5.16.0",
+ "typescript": "3.4.5"
+ }
+}
diff --git a/bookie/proxy.conf.json b/bookie/proxy.conf.json
new file mode 100644
index 0000000..e27caf9
--- /dev/null
+++ b/bookie/proxy.conf.json
@@ -0,0 +1,7 @@
+{
+ "/v1": {
+ "target": "http://localhost:6543",
+ "secure": false,
+ "logLevel": "debug"
+ }
+}
diff --git a/bookie/src/app/app-routing.module.ts b/bookie/src/app/app-routing.module.ts
new file mode 100644
index 0000000..382b271
--- /dev/null
+++ b/bookie/src/app/app-routing.module.ts
@@ -0,0 +1,23 @@
+import { NgModule } from '@angular/core';
+import { Routes, RouterModule } from '@angular/router';
+import {LoginComponent} from "./auth/login/login.component";
+import {LogoutComponent} from "./auth/logout/logout.component";
+import {HomeComponent} from "./home/home.component";
+
+const routes: Routes = [
+ {
+ path: 'guest-book',
+ loadChildren: () => import('./guest-book/guest-book.module').then(mod => mod.GuestBookModule)
+ },
+ {path: 'login', component: LoginComponent},
+ {path: 'logout', component: LogoutComponent},
+ {path: '', component: HomeComponent},
+];
+@NgModule({
+ imports: [RouterModule.forRoot(routes,
+ {
+ onSameUrlNavigation: 'reload',
+ })],
+ exports: [RouterModule]
+})
+export class AppRoutingModule { }
diff --git a/bookie/src/app/app.component.css b/bookie/src/app/app.component.css
new file mode 100644
index 0000000..e69de29
diff --git a/bookie/src/app/app.component.html b/bookie/src/app/app.component.html
new file mode 100644
index 0000000..4da6fa8
--- /dev/null
+++ b/bookie/src/app/app.component.html
@@ -0,0 +1,4 @@
+
+
+
+
diff --git a/bookie/src/app/app.component.ts b/bookie/src/app/app.component.ts
new file mode 100644
index 0000000..e4b5a1e
--- /dev/null
+++ b/bookie/src/app/app.component.ts
@@ -0,0 +1,10 @@
+import { Component } from '@angular/core';
+
+@Component({
+ selector: 'app-root',
+ templateUrl: './app.component.html',
+ styleUrls: ['./app.component.css']
+})
+export class AppComponent {
+ title = 'bookie';
+}
diff --git a/bookie/src/app/app.module.ts b/bookie/src/app/app.module.ts
new file mode 100644
index 0000000..808323d
--- /dev/null
+++ b/bookie/src/app/app.module.ts
@@ -0,0 +1,58 @@
+import { BrowserModule } from '@angular/platform-browser';
+import { NgModule } from '@angular/core';
+
+import { AppRoutingModule } from './app-routing.module';
+import { AppComponent } from './app.component';
+import { BrowserAnimationsModule } from '@angular/platform-browser/animations';
+import { MatButtonModule } from '@angular/material/button';
+import { MatCardModule } from '@angular/material/card';
+import { MatDialogModule } from '@angular/material/dialog';
+import { MatDividerModule } from '@angular/material/divider';
+import { MatFormFieldModule } from '@angular/material/form-field';
+import { MatGridListModule } from '@angular/material/grid-list';
+import { MatIconModule } from '@angular/material/icon';
+import { MatInputModule } from '@angular/material/input';
+import { MatMenuModule } from '@angular/material/menu';
+import { MatSnackBarModule } from '@angular/material/snack-bar';
+import { LayoutModule } from '@angular/cdk/layout';
+import { LogoutComponent } from "./auth/logout/logout.component";
+import { LoginComponent } from "./auth/login/login.component";
+import { HomeComponent } from "./home/home.component";
+import {CoreModule} from "./core/core.module";
+import {ReactiveFormsModule} from "@angular/forms";
+import {SharedModule} from "./shared/shared.module";
+import {UserModule} from "./user/user.module";
+import {RoleModule} from "./role/role.module";
+
+@NgModule({
+ declarations: [
+ AppComponent,
+ LoginComponent,
+ LogoutComponent,
+ HomeComponent
+ ],
+ imports: [
+ BrowserModule,
+ AppRoutingModule,
+ BrowserAnimationsModule,
+ MatGridListModule,
+ MatCardModule,
+ MatMenuModule,
+ MatIconModule,
+ MatButtonModule,
+ MatFormFieldModule,
+ MatDividerModule,
+ MatDialogModule,
+ MatSnackBarModule,
+ MatInputModule,
+ LayoutModule,
+ ReactiveFormsModule,
+ CoreModule,
+ SharedModule,
+ RoleModule,
+ UserModule
+ ],
+ providers: [],
+ bootstrap: [AppComponent]
+})
+export class AppModule { }
diff --git a/bookie/src/app/auth/auth-guard.service.ts b/bookie/src/app/auth/auth-guard.service.ts
new file mode 100644
index 0000000..85c748b
--- /dev/null
+++ b/bookie/src/app/auth/auth-guard.service.ts
@@ -0,0 +1,41 @@
+import {Injectable} from '@angular/core';
+import {ActivatedRouteSnapshot, CanActivate, Router, RouterStateSnapshot} from '@angular/router';
+import {AuthService} from './auth.service';
+import {Observable} from 'rxjs/internal/Observable';
+import {map} from 'rxjs/operators';
+import {ToasterService} from '../core/toaster.service';
+import {User} from '../user/user';
+
+@Injectable({
+ providedIn: 'root'
+})
+export class AuthGuard implements CanActivate {
+
+ constructor(private auth: AuthService, private router: Router, private toaster: ToasterService) {
+ }
+
+ static checkUser(permission: string, user: User, router: Router, state: RouterStateSnapshot, toaster: ToasterService): boolean {
+ if (!user.isAuthenticated) {
+ router.navigate(['login'], {queryParams: {returnUrl: state.url}});
+ toaster.show('Danger', 'User is not authenticated');
+ return false;
+ }
+ const hasPermission = permission === undefined || user.perms.indexOf(permission) !== -1;
+ if (!hasPermission) {
+ toaster.show('Danger', 'You do not have the permission to access this area.');
+ }
+ return hasPermission;
+ }
+
+ canActivate(route: ActivatedRouteSnapshot, state: RouterStateSnapshot): Observable | boolean {
+ if (this.auth.user === undefined) {
+ return this.auth.userObservable
+ .pipe(
+ map((value: User) => AuthGuard.checkUser(
+ route.data['permission'], value, this.router, state, this.toaster
+ ))
+ );
+ }
+ return AuthGuard.checkUser(route.data['permission'], this.auth.user, this.router, state, this.toaster);
+ }
+}
diff --git a/bookie/src/app/auth/auth.service.ts b/bookie/src/app/auth/auth.service.ts
new file mode 100644
index 0000000..eeeaeb1
--- /dev/null
+++ b/bookie/src/app/auth/auth.service.ts
@@ -0,0 +1,73 @@
+import {Injectable} from '@angular/core';
+import {HttpClient, HttpHeaders} from '@angular/common/http';
+import {Observable} from 'rxjs/internal/Observable';
+import {catchError, tap} from 'rxjs/operators';
+import {Subject} from 'rxjs/internal/Subject';
+import {ErrorLoggerService} from '../core/error-logger.service';
+import {User} from '../user/user';
+
+
+const httpOptions = {
+ headers: new HttpHeaders({'Content-Type': 'application/json'})
+};
+
+const loginUrl = '/v1/login';
+const logoutUrl = '/logout';
+const checkUrl = '/v1/auth';
+
+@Injectable({
+ providedIn: 'root'
+})
+export class AuthService {
+ public userObservable = new Subject();
+
+ constructor(private http: HttpClient, private log: ErrorLoggerService) {
+ this.check().subscribe();
+ }
+
+ private _user: User;
+
+ get user() {
+ return this._user;
+ }
+
+ set user(user: User) {
+ this._user = user;
+ this.log.handleError('AuthService','Set User', user);
+ this.userObservable.next(user);
+ }
+
+ login(name: string, password: string, otp?: string, clientName?: string): Observable {
+ const data = {name: name, password: password};
+ if (otp) {
+ data['otp'] = otp;
+ }
+
+ if (clientName) {
+ data['clientName'] = clientName;
+ }
+
+ return this.http.post(loginUrl, data, httpOptions)
+ .pipe(
+ tap((user: User) => this.user = user),
+ catchError(this.log.handleError('AuthService', 'login'))
+ );
+ }
+
+ logout(): Observable {
+ return this.http.post(logoutUrl, {}, httpOptions)
+ .pipe(
+ tap((user: User) => this.user = user),
+ catchError(this.log.handleError('AuthService', 'logout'))
+ );
+ }
+
+ check(): Observable {
+ return this.http.get(checkUrl, httpOptions)
+ .pipe(
+ tap((user: User) => this.user = user),
+ catchError(this.log.handleError('AuthService', 'check'))
+ );
+ }
+
+}
diff --git a/bookie/src/app/auth/login/login.component.css b/bookie/src/app/auth/login/login.component.css
new file mode 100644
index 0000000..cfd4505
--- /dev/null
+++ b/bookie/src/app/auth/login/login.component.css
@@ -0,0 +1,8 @@
+.example-container {
+ display: flex;
+ flex-direction: column;
+}
+
+.example-container > * {
+ width: 100%;
+}
diff --git a/bookie/src/app/auth/login/login.component.html b/bookie/src/app/auth/login/login.component.html
new file mode 100644
index 0000000..9ab26e4
--- /dev/null
+++ b/bookie/src/app/auth/login/login.component.html
@@ -0,0 +1,40 @@
+
+
+ Login
+
+
+
+
+
+
+
+
diff --git a/bookie/src/app/auth/login/login.component.ts b/bookie/src/app/auth/login/login.component.ts
new file mode 100644
index 0000000..09150c8
--- /dev/null
+++ b/bookie/src/app/auth/login/login.component.ts
@@ -0,0 +1,69 @@
+import {AfterViewInit, Component, ElementRef, OnInit, ViewChild} from '@angular/core';
+import {AuthService} from '../auth.service';
+import {ActivatedRoute, Router} from '@angular/router';
+import {ToasterService} from '../../core/toaster.service';
+import {FormBuilder, FormGroup} from '@angular/forms';
+import {CookieService} from '../../shared/cookie.service';
+
+@Component({
+ selector: 'app-login',
+ templateUrl: './login.component.html',
+ styleUrls: ['./login.component.css']
+})
+export class LoginComponent implements OnInit, AfterViewInit {
+ @ViewChild('nameElement', { static: true }) nameElement: ElementRef;
+ form: FormGroup;
+ hide: boolean;
+ showOtp: boolean;
+ clientID: string;
+ private returnUrl: string;
+
+ constructor(private route: ActivatedRoute,
+ private auth: AuthService,
+ private router: Router,
+ private toaster: ToasterService,
+ private cs: CookieService,
+ private fb: FormBuilder
+ ) {
+ this.hide = true;
+ this.showOtp = false;
+ this.createForm();
+ }
+
+ createForm() {
+ this.form = this.fb.group({
+ username: '',
+ password: '',
+ otp: '',
+ clientName: ''
+ });
+ }
+
+ ngOnInit() {
+ this.returnUrl = this.route.snapshot.queryParams['returnUrl'] || '/';
+ }
+
+ ngAfterViewInit() {
+ setTimeout(() => {
+ this.nameElement.nativeElement.focus();
+ }, 0);
+ }
+
+ login(): void {
+ const formModel = this.form.value;
+ const username = formModel.username;
+ const password = formModel.password;
+ this.auth.login(username, password).subscribe(
+ (result) => {
+ this.router.navigateByUrl(this.returnUrl);
+ },
+ (error) => {
+ if (error.status === 403 && ['Unknown Client', 'OTP not supplied', 'OTP is wrong'].indexOf(error.error) !== -1) {
+ this.showOtp = true;
+ this.clientID = this.cs.getCookie('ClientID');
+ }
+ this.toaster.show('Danger', error.error);
+ }
+ );
+ }
+}
diff --git a/bookie/src/app/auth/logout/logout.component.ts b/bookie/src/app/auth/logout/logout.component.ts
new file mode 100644
index 0000000..7be331b
--- /dev/null
+++ b/bookie/src/app/auth/logout/logout.component.ts
@@ -0,0 +1,25 @@
+import {Component, OnInit} from '@angular/core';
+import {Router} from '@angular/router';
+import {AuthService} from '../auth.service';
+import {ToasterService} from '../../core/toaster.service';
+
+@Component({
+ selector: 'app-logout',
+ template: ''
+})
+export class LogoutComponent implements OnInit {
+
+ constructor(private auth: AuthService, private router: Router, private toaster: ToasterService) {
+ }
+
+ ngOnInit() {
+ this.auth.logout().subscribe(
+ (result) => {
+ this.toaster.show('Success', 'Logged Out');
+ this.router.navigateByUrl('/');
+ },
+ (error) => this.toaster.show('Danger', error.error)
+ );
+ }
+
+}
diff --git a/bookie/src/app/core/core.module.ts b/bookie/src/app/core/core.module.ts
new file mode 100644
index 0000000..570836a
--- /dev/null
+++ b/bookie/src/app/core/core.module.ts
@@ -0,0 +1,37 @@
+import {NgModule} from '@angular/core';
+import {CommonModule} from '@angular/common';
+import { MatButtonModule } from '@angular/material/button';
+import { MatIconModule } from '@angular/material/icon';
+import { MatMenuModule } from '@angular/material/menu';
+import { MatToolbarModule } from '@angular/material/toolbar';
+import {RouterModule} from '@angular/router';
+import {HTTP_INTERCEPTORS} from '@angular/common/http';
+import {HttpAuthInterceptor} from './http-auth-interceptor';
+import {LoadingBarHttpClientModule} from '@ngx-loading-bar/http-client';
+import {LoadingBarRouterModule} from '@ngx-loading-bar/router';
+
+@NgModule({
+ imports: [
+ CommonModule,
+ LoadingBarHttpClientModule,
+ LoadingBarRouterModule,
+ MatButtonModule,
+ MatIconModule,
+ MatMenuModule,
+ MatToolbarModule,
+ RouterModule
+ ],
+ declarations: [
+ ],
+ exports: [
+ LoadingBarHttpClientModule,
+ LoadingBarRouterModule
+ ],
+ providers: [[{
+ provide: HTTP_INTERCEPTORS,
+ useClass: HttpAuthInterceptor,
+ multi: true
+ }]]
+})
+export class CoreModule {
+}
diff --git a/bookie/src/app/core/error-logger.service.ts b/bookie/src/app/core/error-logger.service.ts
new file mode 100644
index 0000000..1bba940
--- /dev/null
+++ b/bookie/src/app/core/error-logger.service.ts
@@ -0,0 +1,36 @@
+import {Injectable} from '@angular/core';
+import {Observable} from 'rxjs/internal/Observable';
+import {throwError} from 'rxjs';
+
+@Injectable({
+ providedIn: 'root'
+})
+export class ErrorLoggerService {
+ constructor() {
+ }
+
+ /**
+ * Handle Http operation that failed.
+ * Let the app continue.
+ * @param operation - name of the operation that failed
+ * @param result - optional value to return as the observable result
+ */
+ public handleError(serviceName = 'error-logger', operation = 'operation', result?: T) {
+ return (error: any): Observable => {
+
+ // TODO: send the error to remote logging infrastructure
+ console.error(error); // log to console instead
+
+ // TODO: better job of transforming error for user consumption
+ this.log(serviceName, `${operation} failed: ${error.message}`);
+
+ // // Let the app keep running by returning an empty result.
+ // return of(result as T);
+ return throwError(error);
+ };
+ }
+
+ public log(serviceName = 'error-logger', message: string) {
+ console.log(serviceName + 'Service: ' + message);
+ }
+}
diff --git a/bookie/src/app/core/http-auth-interceptor.ts b/bookie/src/app/core/http-auth-interceptor.ts
new file mode 100644
index 0000000..2d8b99f
--- /dev/null
+++ b/bookie/src/app/core/http-auth-interceptor.ts
@@ -0,0 +1,42 @@
+import {Injectable} from '@angular/core';
+import {HttpEvent, HttpHandler, HttpInterceptor, HttpRequest} from '@angular/common/http';
+
+import {Observable, throwError} from 'rxjs';
+import {catchError} from 'rxjs/operators';
+import {ToasterService} from './toaster.service';
+import {Router} from '@angular/router';
+import {ConfirmDialogComponent} from '../shared/confirm-dialog/confirm-dialog.component';
+import { MatDialog } from '@angular/material/dialog';
+
+@Injectable()
+export class HttpAuthInterceptor implements HttpInterceptor {
+
+ constructor(private router: Router, private dialog: MatDialog, private toaster: ToasterService) {
+ }
+
+ intercept(req: HttpRequest, next: HttpHandler):
+ Observable> {
+ return next.handle(req)
+ .pipe(
+ catchError((error: any) => {
+ if (error.status === 401) {
+ this.toaster.show('Danger', 'User has been logged out');
+ const dialogRef = this.dialog.open(ConfirmDialogComponent, {
+ width: '250px',
+ data: {
+ title: 'Logged out!',
+ content: 'You have been logged out.\nYou can press Cancel to stay on page and login in another tab to resume here, or you can press Ok to navigate to the login page.'
+ }
+ });
+ dialogRef.afterClosed().subscribe((result: boolean) => {
+ if (result) {
+ this.router.navigate(['login']);
+ }
+ });
+ }
+ return throwError(error);
+ }
+ )
+ );
+ }
+}
diff --git a/bookie/src/app/core/toaster.service.ts b/bookie/src/app/core/toaster.service.ts
new file mode 100644
index 0000000..28a21a6
--- /dev/null
+++ b/bookie/src/app/core/toaster.service.ts
@@ -0,0 +1,17 @@
+import {Injectable} from '@angular/core';
+import { MatSnackBar } from '@angular/material/snack-bar';
+
+@Injectable({
+ providedIn: 'root'
+})
+export class ToasterService {
+
+ constructor(private snackBar: MatSnackBar) {
+ }
+
+ show(type: string, msg: string) {
+ this.snackBar.open(msg, type, {
+ duration: 3000,
+ });
+ }
+}
diff --git a/bookie/src/app/guest-book/guest-book-detail-resolver.service.ts b/bookie/src/app/guest-book/guest-book-detail-resolver.service.ts
new file mode 100644
index 0000000..8bb8c4c
--- /dev/null
+++ b/bookie/src/app/guest-book/guest-book-detail-resolver.service.ts
@@ -0,0 +1,19 @@
+import {Injectable} from '@angular/core';
+import {ActivatedRouteSnapshot, Resolve, Router, RouterStateSnapshot} from '@angular/router';
+import {GuestBookService} from './guest-book.service';
+import {GuestBook} from './guest-book';
+import {Observable} from 'rxjs/internal/Observable';
+
+@Injectable({
+ providedIn: 'root'
+})
+export class GuestBookDetailResolver implements Resolve {
+
+ constructor(private ser: GuestBookService, private router: Router) {
+ }
+
+ resolve(route: ActivatedRouteSnapshot, state: RouterStateSnapshot): Observable {
+ const id = route.paramMap.get('id');
+ return this.ser.get(id);
+ }
+}
diff --git a/bookie/src/app/guest-book/guest-book-detail/guest-book-detail.component.css b/bookie/src/app/guest-book/guest-book-detail/guest-book-detail.component.css
new file mode 100644
index 0000000..194f9ec
--- /dev/null
+++ b/bookie/src/app/guest-book/guest-book-detail/guest-book-detail.component.css
@@ -0,0 +1,27 @@
+.full-width {
+ width: 100%;
+}
+
+.shipping-card {
+ min-width: 120px;
+ margin: 20px auto;
+}
+
+.mat-radio-button {
+ display: block;
+ margin: 5px 0;
+}
+
+.row {
+ display: flex;
+ flex-direction: row;
+}
+
+.col {
+ flex: 1;
+ margin-right: 20px;
+}
+
+.col:last-child {
+ margin-right: 0;
+}
diff --git a/bookie/src/app/guest-book/guest-book-detail/guest-book-detail.component.html b/bookie/src/app/guest-book/guest-book-detail/guest-book-detail.component.html
new file mode 100644
index 0000000..ba77ba0
--- /dev/null
+++ b/bookie/src/app/guest-book/guest-book-detail/guest-book-detail.component.html
@@ -0,0 +1,51 @@
+
diff --git a/bookie/src/app/guest-book/guest-book-detail/guest-book-detail.component.ts b/bookie/src/app/guest-book/guest-book-detail/guest-book-detail.component.ts
new file mode 100644
index 0000000..be4b520
--- /dev/null
+++ b/bookie/src/app/guest-book/guest-book-detail/guest-book-detail.component.ts
@@ -0,0 +1,78 @@
+import {Component, OnInit} from '@angular/core';
+import {FormArray, FormBuilder, FormGroup, Validators} from '@angular/forms';
+import {RoleService} from "../../role/role.service";
+import {GuestBookService} from "../guest-book.service";
+import {ToasterService} from "../../core/toaster.service";
+import {Role} from "../../role/role";
+import {GuestBook} from "../guest-book";
+import {ActivatedRoute, Router} from "@angular/router";
+import {User} from "../../user/user";
+
+@Component({
+ selector: 'app-guest-book-detail',
+ templateUrl: './guest-book-detail.component.html',
+ styleUrls: ['./guest-book-detail.component.css']
+})
+export class GuestBookDetailComponent implements OnInit {
+ form: FormGroup;
+ item: GuestBook;
+
+ constructor(
+ private fb: FormBuilder,
+ private route: ActivatedRoute,
+ private router: Router,
+ private toaster: ToasterService,
+ private ser: GuestBookService
+ ) {
+ this.createForm();
+ }
+
+ createForm() {
+ this.form = this.fb.group({
+ company: null,
+ name: [null, Validators.required],
+ phone: [null, Validators.required],
+ pax: ['0', Validators.required],
+ address: null
+ });
+ }
+
+ ngOnInit() {
+ this.route.data
+ .subscribe((data: { item: GuestBook }) => {
+ this.showItem(data.item);
+ });
+ }
+
+ showItem(item: GuestBook) {
+ this.item = item;
+ this.form.get('company').setValue(item.company);
+ this.form.get('name').setValue(item.name);
+ this.form.get('phone').setValue(item.phone);
+ this.form.get('pax').setValue('' + item.pax);
+ this.form.get('address').setValue(item.address);
+ }
+
+ onSubmit() {
+ this.ser.saveOrUpdate(this.getItem())
+ .subscribe(
+ (result) => {
+ this.toaster.show('Success', '');
+ this.router.navigateByUrl('/guest-book/list');
+ },
+ (error) => {
+ this.toaster.show('Danger', error.error);
+ }
+ );
+ }
+
+ getItem(): GuestBook {
+ const formModel = this.form.value;
+ this.item.company = formModel.company;
+ this.item.name = formModel.name;
+ this.item.phone = formModel.phone;
+ this.item.pax = parseInt(formModel.pax);
+ this.item.address = formModel.address;
+ return this.item;
+ }
+}
diff --git a/bookie/src/app/guest-book/guest-book-list-resolver.service.ts b/bookie/src/app/guest-book/guest-book-list-resolver.service.ts
new file mode 100644
index 0000000..cdbbf95
--- /dev/null
+++ b/bookie/src/app/guest-book/guest-book-list-resolver.service.ts
@@ -0,0 +1,18 @@
+import {Injectable} from '@angular/core';
+import {ActivatedRouteSnapshot, Resolve, RouterStateSnapshot} from '@angular/router';
+import {GuestBook} from "./guest-book";
+import {Observable} from 'rxjs/internal/Observable';
+import {GuestBookService} from './guest-book.service';
+
+@Injectable({
+ providedIn: 'root'
+})
+export class GuestBookListResolver implements Resolve {
+
+ constructor(private ser: GuestBookService) {
+ }
+
+ resolve(route: ActivatedRouteSnapshot, state: RouterStateSnapshot): Observable {
+ return this.ser.list(null);
+ }
+}
diff --git a/bookie/src/app/guest-book/guest-book-list/guest-book-list-datasource.ts b/bookie/src/app/guest-book/guest-book-list/guest-book-list-datasource.ts
new file mode 100644
index 0000000..4aeb8d3
--- /dev/null
+++ b/bookie/src/app/guest-book/guest-book-list/guest-book-list-datasource.ts
@@ -0,0 +1,74 @@
+import {DataSource} from '@angular/cdk/collections';
+import { MatPaginator } from '@angular/material/paginator';
+import { MatSort } from '@angular/material/sort';
+import {map, tap} from 'rxjs/operators';
+import {Observable, of as observableOf, merge} from 'rxjs';
+import {GuestBook} from "../guest-book";
+
+/**
+ * Data source for the GuestBookList view. This class should
+ * encapsulate all logic for fetching and manipulating the displayed data
+ * (including sorting, pagination, and filtering).
+ */
+export class GuestBookListDataSource extends DataSource {
+ private dataObservable: Observable;
+
+ constructor(
+ private paginator: MatPaginator,
+ private sort: MatSort,
+ public data: GuestBook[]
+ ) {
+ super();
+ }
+
+ connect(): Observable {
+ this.dataObservable = observableOf(this.data);
+ const dataMutations = [
+ this.dataObservable,
+ this.paginator.page,
+ this.sort.sortChange
+ ];
+
+ return merge(...dataMutations).pipe(
+ map((x: any) => {
+ return this.getPagedData(this.getSortedData([...this.data]));
+ }),
+ tap((x: GuestBook[]) => this.paginator.length = x.length)
+ );
+
+ }
+
+ /**
+ * Called when the table is being destroyed. Use this function, to clean up
+ * any open connections or free any held resources that were set up during connect.
+ */
+ disconnect() {
+ }
+
+ private getPagedData(data: GuestBook[]) {
+ const startIndex = this.paginator.pageIndex * this.paginator.pageSize;
+ return data.splice(startIndex, this.paginator.pageSize);
+ }
+
+ private getSortedData(data: GuestBook[]) {
+ if (!this.sort.active || this.sort.direction === '') {
+ return data;
+ }
+
+ return data.sort((a, b) => {
+ const isAsc = this.sort.direction === 'asc';
+ switch (this.sort.active) {
+ case 'name':
+ return compare(a.name, b.name, isAsc);
+ case 'id':
+ return compare(+a.id, +b.id, isAsc);
+ default:
+ return 0;
+ }
+ });
+ }
+}
+
+function compare(a, b, isAsc) {
+ return (a < b ? -1 : 1) * (isAsc ? 1 : -1);
+}
diff --git a/bookie/src/app/guest-book/guest-book-list/guest-book-list.component.css b/bookie/src/app/guest-book/guest-book-list/guest-book-list.component.css
new file mode 100644
index 0000000..5050fb6
--- /dev/null
+++ b/bookie/src/app/guest-book/guest-book-list/guest-book-list.component.css
@@ -0,0 +1,3 @@
+.full-width-table {
+ width: 100%;
+}
diff --git a/bookie/src/app/guest-book/guest-book-list/guest-book-list.component.html b/bookie/src/app/guest-book/guest-book-list/guest-book-list.component.html
new file mode 100644
index 0000000..8313222
--- /dev/null
+++ b/bookie/src/app/guest-book/guest-book-list/guest-book-list.component.html
@@ -0,0 +1,70 @@
+
+
+
+
+
+
+ | S. No |
+ {{row.serial}} |
+
+
+
+
+ Name |
+ {{row.name}} |
+
+
+
+
+ Phone |
+ {{row.phone}} |
+
+
+
+
+ Pax |
+ {{row.pax}} |
+
+
+
+
+ Action
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/bookie/src/app/guest-book/guest-book-list/guest-book-list.component.ts b/bookie/src/app/guest-book/guest-book-list/guest-book-list.component.ts
new file mode 100644
index 0000000..720af3a
--- /dev/null
+++ b/bookie/src/app/guest-book/guest-book-list/guest-book-list.component.ts
@@ -0,0 +1,58 @@
+import {AfterViewInit, Component, ElementRef, OnInit, ViewChild} from '@angular/core';
+import { MatPaginator } from '@angular/material/paginator';
+import { MatSort } from '@angular/material/sort';
+import {GuestBookListDataSource} from './guest-book-list-datasource';
+import {ActivatedRoute} from "@angular/router";
+import {FormBuilder, FormGroup} from "@angular/forms";
+import {debounceTime, distinctUntilChanged, map} from "rxjs/operators";
+import {GuestBook} from "../guest-book";
+import {Observable} from "rxjs";
+import * as moment from 'moment';
+import {GuestBookService} from "../guest-book.service";
+
+@Component({
+ selector: 'app-guest-book-list',
+ templateUrl: './guest-book-list.component.html',
+ styleUrls: ['./guest-book-list.component.css']
+})
+export class GuestBookListComponent implements OnInit {
+ @ViewChild(MatPaginator, { static: true }) paginator: MatPaginator;
+ @ViewChild(MatSort, { static: true }) sort: MatSort;
+ dataSource: GuestBookListDataSource;
+ filter: Observable;
+ form: FormGroup;
+ list: GuestBook[];
+ /** Columns displayed in the table. Columns IDs can be added, removed, or reordered. */
+ displayedColumns = ['sno', 'name', 'phone', 'pax', 'action'];
+
+ constructor(private route: ActivatedRoute, private fb: FormBuilder, private ser: GuestBookService) {
+ this.createForm();
+ this.listenToDateChange();
+ }
+
+ createForm() {
+ this.form = this.fb.group({
+ date: ''
+ });
+ }
+
+ listenToDateChange(): void {
+ this.form.get('date').valueChanges.pipe(
+ map(x => moment(x).format('DD-MMM-YYYY'))
+ ).subscribe(x => {
+ return this.ser.list(x)
+ .subscribe((list: GuestBook[]) => {
+ this.list = list;
+ this.dataSource = new GuestBookListDataSource(this.paginator, this.sort, this.list);
+ });
+ });
+ }
+
+ ngOnInit() {
+ this.route.data
+ .subscribe((data: { list: GuestBook[] }) => {
+ this.list = data.list;
+ });
+ this.dataSource = new GuestBookListDataSource(this.paginator, this.sort, this.list);
+ }
+}
diff --git a/bookie/src/app/guest-book/guest-book-routing.module.ts b/bookie/src/app/guest-book/guest-book-routing.module.ts
new file mode 100644
index 0000000..dcc3627
--- /dev/null
+++ b/bookie/src/app/guest-book/guest-book-routing.module.ts
@@ -0,0 +1,54 @@
+import { NgModule } from '@angular/core';
+import { Routes, RouterModule } from '@angular/router';
+import { GuestBookListComponent } from "./guest-book-list/guest-book-list.component";
+import { GuestBookDetailComponent } from "./guest-book-detail/guest-book-detail.component";
+import { AuthGuard } from "../auth/auth-guard.service";
+import { GuestBookListResolver } from "./guest-book-list-resolver.service";
+import { GuestBookDetailResolver } from "./guest-book-detail-resolver.service";
+
+const routes: Routes = [
+ {
+ path: '',
+ component: GuestBookListComponent,
+ canActivate: [AuthGuard],
+ data: {
+ permission: 'Guest Book'
+ },
+ resolve: {
+ list: GuestBookListResolver
+ }
+ },
+ {
+ path: 'new',
+ component: GuestBookDetailComponent,
+ canActivate: [AuthGuard],
+ data: {
+ permission: 'Guest Book'
+ },
+ resolve: {
+ item: GuestBookDetailResolver
+ }
+ },
+ {
+ path: ':id',
+ component: GuestBookDetailComponent,
+ canActivate: [AuthGuard],
+ data: {
+ permission: 'Guest Book'
+ },
+ resolve: {
+ item: GuestBookDetailResolver
+ }
+ }
+];
+
+@NgModule({
+ declarations: [],
+ imports: [RouterModule.forChild(routes)],
+ exports: [RouterModule],
+ providers: [
+ GuestBookListResolver,
+ GuestBookDetailResolver
+ ]
+})
+export class GuestBookRoutingModule { }
diff --git a/bookie/src/app/guest-book/guest-book.module.ts b/bookie/src/app/guest-book/guest-book.module.ts
new file mode 100644
index 0000000..507e5b1
--- /dev/null
+++ b/bookie/src/app/guest-book/guest-book.module.ts
@@ -0,0 +1,55 @@
+import { NgModule } from '@angular/core';
+import { CommonModule } from '@angular/common';
+import { MatButtonModule } from '@angular/material/button';
+import { MatCardModule } from '@angular/material/card';
+import { MatNativeDateModule, MAT_DATE_FORMATS, MAT_DATE_LOCALE, DateAdapter } from '@angular/material/core';
+import { MatDatepickerModule } from '@angular/material/datepicker';
+import { MatIconModule } from '@angular/material/icon';
+import { MatInputModule } from '@angular/material/input';
+import { MatPaginatorModule } from '@angular/material/paginator';
+import { MatRadioModule } from '@angular/material/radio';
+import { MatSelectModule } from '@angular/material/select';
+import { MatSortModule } from '@angular/material/sort';
+import { MatTableModule } from '@angular/material/table';
+import {MomentDateAdapter} from '@angular/material-moment-adapter';
+import { GuestBookDetailComponent } from './guest-book-detail/guest-book-detail.component';
+import { ReactiveFormsModule } from '@angular/forms';
+import { GuestBookListComponent } from './guest-book-list/guest-book-list.component';
+import { GuestBookRoutingModule } from './guest-book-routing.module';
+
+export const MY_FORMATS = {
+ parse: {
+ dateInput: 'DD-MMM-YYYY',
+ },
+ display: {
+ dateInput: 'DD-MMM-YYYY',
+ monthYearLabel: 'MMM YYYY',
+ dateA11yLabel: 'DD-MMM-YYYY',
+ monthYearA11yLabel: 'MMM YYYY',
+ },
+};
+
+@NgModule({
+ declarations: [GuestBookDetailComponent, GuestBookListComponent],
+ imports: [
+ CommonModule,
+ MatTableModule,
+ MatPaginatorModule,
+ MatSortModule,
+ MatInputModule,
+ MatButtonModule,
+ MatSelectModule,
+ MatRadioModule,
+ MatCardModule,
+ MatIconModule,
+ MatDatepickerModule,
+ MatNativeDateModule,
+ ReactiveFormsModule,
+ GuestBookRoutingModule
+ ],
+ providers: [
+ {provide: DateAdapter, useClass: MomentDateAdapter, deps: [MAT_DATE_LOCALE]},
+ {provide: MAT_DATE_FORMATS, useValue: MY_FORMATS},
+ ]
+})
+export class GuestBookModule { }
diff --git a/bookie/src/app/guest-book/guest-book.service.ts b/bookie/src/app/guest-book/guest-book.service.ts
new file mode 100644
index 0000000..e5114be
--- /dev/null
+++ b/bookie/src/app/guest-book/guest-book.service.ts
@@ -0,0 +1,66 @@
+import {Injectable} from '@angular/core';
+import {Observable} from 'rxjs/internal/Observable';
+import {catchError} from 'rxjs/operators';
+import {HttpClient, HttpHeaders, HttpParams} from '@angular/common/http';
+import {GuestBook} from './guest-book';
+import {ErrorLoggerService} from '../core/error-logger.service';
+
+const httpOptions = {
+ headers: new HttpHeaders({'Content-Type': 'application/json'})
+};
+
+const url = '/v1/guest-book';
+const serviceName = 'GuestBookService';
+
+@Injectable({providedIn: 'root'})
+export class GuestBookService {
+
+ constructor(private http: HttpClient, private log: ErrorLoggerService) {
+ }
+
+ get(id: string): Observable {
+ const getUrl: string = (id === null) ? `${url}/new` : `${url}/${id}`;
+ return >this.http.get(getUrl)
+ .pipe(
+ catchError(this.log.handleError(serviceName, `get id=${id}`))
+ );
+ }
+
+ list(date: string): Observable {
+ const options = {params: new HttpParams().set('q', (date === null) ? '' : date)};
+ const listUrl: string = `${url}/list`;
+ return >this.http.get(listUrl, options)
+ .pipe(
+ catchError(this.log.handleError(serviceName, 'list'))
+ );
+ }
+
+ save(guestBook: GuestBook): Observable {
+ return >this.http.put(`${url}/new`, guestBook, httpOptions)
+ .pipe(
+ catchError(this.log.handleError(serviceName, 'save'))
+ );
+ }
+
+ update(guestBook: GuestBook): Observable {
+ return >this.http.post(`${url}/${guestBook.id}`, guestBook, httpOptions)
+ .pipe(
+ catchError(this.log.handleError(serviceName, 'update'))
+ );
+ }
+
+ saveOrUpdate(guestBook: GuestBook): Observable {
+ if (!guestBook.id) {
+ return this.save(guestBook);
+ } else {
+ return this.update(guestBook);
+ }
+ }
+
+ delete(id: string): Observable {
+ return >this.http.delete(`${url}/${id}`, httpOptions)
+ .pipe(
+ catchError(this.log.handleError(serviceName, 'delete'))
+ );
+ }
+}
diff --git a/bookie/src/app/guest-book/guest-book.ts b/bookie/src/app/guest-book/guest-book.ts
new file mode 100644
index 0000000..7d1a8e8
--- /dev/null
+++ b/bookie/src/app/guest-book/guest-book.ts
@@ -0,0 +1,13 @@
+export class GuestBook {
+ id: string;
+ serial: number;
+ company: string;
+ name: string;
+ phone: string;
+ pax: number;
+ address: string;
+
+ public constructor(init?: Partial) {
+ Object.assign(this, init);
+ }
+}
diff --git a/bookie/src/app/home/home.component.css b/bookie/src/app/home/home.component.css
new file mode 100644
index 0000000..e69de29
diff --git a/bookie/src/app/home/home.component.html b/bookie/src/app/home/home.component.html
new file mode 100644
index 0000000..ebf0418
--- /dev/null
+++ b/bookie/src/app/home/home.component.html
@@ -0,0 +1,16 @@
+
+ account_box
+ Login
+
+ Guest Book
+
+
+ Sales
+
+
+ Tables
+
+
+ account_box
+ Logout {{name}}
+
diff --git a/bookie/src/app/home/home.component.spec.ts b/bookie/src/app/home/home.component.spec.ts
new file mode 100644
index 0000000..482c23c
--- /dev/null
+++ b/bookie/src/app/home/home.component.spec.ts
@@ -0,0 +1,25 @@
+import {async, ComponentFixture, TestBed} from '@angular/core/testing';
+
+import {HomeComponent} from './home.component';
+
+describe('HomeComponent', () => {
+ let component: HomeComponent;
+ let fixture: ComponentFixture;
+
+ beforeEach(async(() => {
+ TestBed.configureTestingModule({
+ declarations: [HomeComponent]
+ })
+ .compileComponents();
+ }));
+
+ beforeEach(() => {
+ fixture = TestBed.createComponent(HomeComponent);
+ component = fixture.componentInstance;
+ fixture.detectChanges();
+ });
+
+ it('should create', () => {
+ expect(component).toBeTruthy();
+ });
+});
diff --git a/bookie/src/app/home/home.component.ts b/bookie/src/app/home/home.component.ts
new file mode 100644
index 0000000..88ed597
--- /dev/null
+++ b/bookie/src/app/home/home.component.ts
@@ -0,0 +1,25 @@
+import {Component, OnInit} from '@angular/core';
+import {Subject} from "rxjs";
+import {AuthService} from "../auth/auth.service";
+
+@Component({
+ selector: 'app-home',
+ templateUrl: './home.component.html',
+ styleUrls: ['./home.component.css']
+})
+export class HomeComponent implements OnInit {
+ public nameObject = new Subject();
+
+ constructor(private auth: AuthService) {
+ }
+
+ ngOnInit() {
+ this.auth.userObservable.subscribe((user) => {
+ if (user.isAuthenticated) {
+ this.nameObject.next(user.name);
+ } else {
+ this.nameObject.next(null);
+ }
+ });
+ }
+}
diff --git a/bookie/src/app/products/products-routing.module.ts b/bookie/src/app/products/products-routing.module.ts
new file mode 100644
index 0000000..4b34bbe
--- /dev/null
+++ b/bookie/src/app/products/products-routing.module.ts
@@ -0,0 +1,10 @@
+import { NgModule } from '@angular/core';
+import { Routes, RouterModule } from '@angular/router';
+
+const routes: Routes = [];
+
+@NgModule({
+ imports: [RouterModule.forChild(routes)],
+ exports: [RouterModule]
+})
+export class ProductsRoutingModule { }
diff --git a/bookie/src/app/products/products.module.ts b/bookie/src/app/products/products.module.ts
new file mode 100644
index 0000000..75623ee
--- /dev/null
+++ b/bookie/src/app/products/products.module.ts
@@ -0,0 +1,13 @@
+import { NgModule } from '@angular/core';
+import { CommonModule } from '@angular/common';
+
+import { ProductsRoutingModule } from './products-routing.module';
+
+@NgModule({
+ declarations: [],
+ imports: [
+ CommonModule,
+ ProductsRoutingModule
+ ]
+})
+export class ProductsModule { }
diff --git a/bookie/src/app/role/role-detail/role-detail.component.css b/bookie/src/app/role/role-detail/role-detail.component.css
new file mode 100644
index 0000000..82c7afd
--- /dev/null
+++ b/bookie/src/app/role/role-detail/role-detail.component.css
@@ -0,0 +1,3 @@
+.example-card {
+ max-width: 400px;
+}
diff --git a/bookie/src/app/role/role-detail/role-detail.component.html b/bookie/src/app/role/role-detail/role-detail.component.html
new file mode 100644
index 0000000..690a677
--- /dev/null
+++ b/bookie/src/app/role/role-detail/role-detail.component.html
@@ -0,0 +1,30 @@
+
+
+
+ Role
+
+
+
+
+
+
+
+
+
+
diff --git a/bookie/src/app/role/role-detail/role-detail.component.ts b/bookie/src/app/role/role-detail/role-detail.component.ts
new file mode 100644
index 0000000..c3a782f
--- /dev/null
+++ b/bookie/src/app/role/role-detail/role-detail.component.ts
@@ -0,0 +1,108 @@
+import {AfterViewInit, Component, ElementRef, OnInit, ViewChild} from '@angular/core';
+import {ActivatedRoute, Router} from '@angular/router';
+
+import {RoleService} from '../role.service';
+import {Role} from '../role';
+import {ToasterService} from '../../core/toaster.service';
+import {ConfirmDialogComponent} from '../../shared/confirm-dialog/confirm-dialog.component';
+import { MatDialog } from '@angular/material/dialog';
+import {FormArray, FormBuilder, FormGroup} from '@angular/forms';
+
+@Component({
+ selector: 'app-role-detail',
+ templateUrl: './role-detail.component.html',
+ styleUrls: ['./role-detail.component.css']
+})
+export class RoleDetailComponent implements OnInit, AfterViewInit {
+ @ViewChild('nameElement', { static: true }) nameElement: ElementRef;
+ form: FormGroup;
+ item: Role;
+
+ constructor(
+ private route: ActivatedRoute,
+ private router: Router,
+ private fb: FormBuilder,
+ private toaster: ToasterService,
+ private dialog: MatDialog,
+ private ser: RoleService
+ ) {
+ this.createForm();
+ }
+
+ createForm() {
+ this.form = this.fb.group({
+ name: '',
+ permissions: this.fb.array([])
+ });
+ }
+
+ ngOnInit() {
+ this.route.data
+ .subscribe((data: { item: Role }) => {
+ this.item = data.item;
+ this.form.get('name').setValue(this.item.name);
+ this.form.setControl('permissions', this.fb.array(
+ this.item.permissions.map(
+ x => this.fb.group({
+ permission: x.enabled
+ })
+ )
+ ));
+ });
+ }
+
+ ngAfterViewInit() {
+ setTimeout(() => {
+ this.nameElement.nativeElement.focus();
+ }, 0);
+ }
+
+ save() {
+ this.ser.saveOrUpdate(this.getItem())
+ .subscribe(
+ (result) => {
+ this.toaster.show('Success', '');
+ this.router.navigateByUrl('/roles/list');
+ },
+ (error) => {
+ this.toaster.show('Danger', error.error);
+ }
+ );
+ }
+
+ delete() {
+ this.ser.delete(this.item.id)
+ .subscribe(
+ (result) => {
+ this.toaster.show('Success', '');
+ this.router.navigateByUrl('/Roles');
+ },
+ (error) => {
+ this.toaster.show('Danger', error.error);
+ }
+ );
+ }
+
+ confirmDelete(): void {
+ const dialogRef = this.dialog.open(ConfirmDialogComponent, {
+ width: '250px',
+ data: {title: 'Delete Role?', content: 'Are you sure? This cannot be undone.'}
+ });
+
+ dialogRef.afterClosed().subscribe((result: boolean) => {
+ if (result) {
+ this.delete();
+ }
+ });
+ }
+
+ getItem(): Role {
+ const formModel = this.form.value;
+ this.item.name = formModel.name;
+ const array = this.form.get('permissions') as FormArray;
+ this.item.permissions.forEach((item, index) => {
+ item.enabled = array.controls[index].value.permission;
+ });
+ return this.item;
+ }
+}
diff --git a/bookie/src/app/role/role-list-resolver.service.ts b/bookie/src/app/role/role-list-resolver.service.ts
new file mode 100644
index 0000000..88e98f9
--- /dev/null
+++ b/bookie/src/app/role/role-list-resolver.service.ts
@@ -0,0 +1,18 @@
+import {Injectable} from '@angular/core';
+import {ActivatedRouteSnapshot, Resolve, Router, RouterStateSnapshot} from '@angular/router';
+import {Role} from './role';
+import {Observable} from 'rxjs/internal/Observable';
+import {RoleService} from './role.service';
+
+@Injectable({
+ providedIn: 'root'
+})
+export class RoleListResolver implements Resolve {
+
+ constructor(private ser: RoleService, private router: Router) {
+ }
+
+ resolve(route: ActivatedRouteSnapshot, state: RouterStateSnapshot): Observable {
+ return this.ser.list();
+ }
+}
diff --git a/bookie/src/app/role/role-list/role-list-datasource.ts b/bookie/src/app/role/role-list/role-list-datasource.ts
new file mode 100644
index 0000000..8d7a833
--- /dev/null
+++ b/bookie/src/app/role/role-list/role-list-datasource.ts
@@ -0,0 +1,59 @@
+import {DataSource} from '@angular/cdk/collections';
+import { MatPaginator } from '@angular/material/paginator';
+import { MatSort } from '@angular/material/sort';
+import {map} from 'rxjs/operators';
+import {merge, Observable, of as observableOf} from 'rxjs';
+import {Role} from '../role';
+
+export class RoleListDataSource extends DataSource {
+
+ constructor(private paginator: MatPaginator, private sort: MatSort, public data: Role[]) {
+ super();
+ }
+
+ connect(): Observable {
+ const dataMutations = [
+ observableOf(this.data),
+ this.paginator.page,
+ this.sort.sortChange
+ ];
+
+ // Set the paginators length
+ this.paginator.length = this.data.length;
+
+ return merge(...dataMutations).pipe(map(() => {
+ return this.getPagedData(this.getSortedData([...this.data]));
+ }));
+ }
+
+ disconnect() {
+ }
+
+ private getPagedData(data: Role[]) {
+ const startIndex = this.paginator.pageIndex * this.paginator.pageSize;
+ return data.splice(startIndex, this.paginator.pageSize);
+ }
+
+ private getSortedData(data: Role[]) {
+ if (!this.sort.active || this.sort.direction === '') {
+ return data;
+ }
+
+ return data.sort((a, b) => {
+ const isAsc = this.sort.direction === 'asc';
+ switch (this.sort.active) {
+ case 'name':
+ return compare(a.name, b.name, isAsc);
+ case 'id':
+ return compare(+a.id, +b.id, isAsc);
+ default:
+ return 0;
+ }
+ });
+ }
+}
+
+/** Simple sort comparator for example ID/Name columns (for user-side sorting). */
+function compare(a, b, isAsc) {
+ return (a < b ? -1 : 1) * (isAsc ? 1 : -1);
+}
diff --git a/bookie/src/app/role/role-list/role-list.component.css b/bookie/src/app/role/role-list/role-list.component.css
new file mode 100644
index 0000000..e69de29
diff --git a/bookie/src/app/role/role-list/role-list.component.html b/bookie/src/app/role/role-list/role-list.component.html
new file mode 100644
index 0000000..c0b1826
--- /dev/null
+++ b/bookie/src/app/role/role-list/role-list.component.html
@@ -0,0 +1,39 @@
+
+
+ Roles
+
+ add_box
+ Add
+
+
+
+
+
+
+
+ Name
+ {{row.name}}
+
+
+
+
+ Permissions
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/bookie/src/app/role/role-list/role-list.component.ts b/bookie/src/app/role/role-list/role-list.component.ts
new file mode 100644
index 0000000..bfd1621
--- /dev/null
+++ b/bookie/src/app/role/role-list/role-list.component.ts
@@ -0,0 +1,31 @@
+import {Component, OnInit, ViewChild} from '@angular/core';
+import { MatPaginator } from '@angular/material/paginator';
+import { MatSort } from '@angular/material/sort';
+import {RoleListDataSource} from './role-list-datasource';
+import {Role} from '../role';
+import {ActivatedRoute} from '@angular/router';
+
+@Component({
+ selector: 'app-role-list',
+ templateUrl: './role-list.component.html',
+ styleUrls: ['./role-list.component.css']
+})
+export class RoleListComponent implements OnInit {
+ @ViewChild(MatPaginator, { static: true }) paginator: MatPaginator;
+ @ViewChild(MatSort, { static: true }) sort: MatSort;
+ dataSource: RoleListDataSource;
+ list: Role[];
+ /** Columns displayed in the table. Columns IDs can be added, removed, or reordered. */
+ displayedColumns = ['name', 'permissions'];
+
+ constructor(private route: ActivatedRoute) {
+ }
+
+ ngOnInit() {
+ this.route.data
+ .subscribe((data: { list: Role[] }) => {
+ this.list = data.list;
+ });
+ this.dataSource = new RoleListDataSource(this.paginator, this.sort, this.list);
+ }
+}
diff --git a/bookie/src/app/role/role-resolver.service.ts b/bookie/src/app/role/role-resolver.service.ts
new file mode 100644
index 0000000..c996cad
--- /dev/null
+++ b/bookie/src/app/role/role-resolver.service.ts
@@ -0,0 +1,19 @@
+import {Injectable} from '@angular/core';
+import {ActivatedRouteSnapshot, Resolve, Router, RouterStateSnapshot} from '@angular/router';
+import {Role} from './role';
+import {Observable} from 'rxjs/internal/Observable';
+import {RoleService} from './role.service';
+
+@Injectable({
+ providedIn: 'root'
+})
+export class RoleResolver implements Resolve {
+
+ constructor(private ser: RoleService, private router: Router) {
+ }
+
+ resolve(route: ActivatedRouteSnapshot, state: RouterStateSnapshot): Observable {
+ const id = route.paramMap.get('id');
+ return this.ser.get(id);
+ }
+}
diff --git a/bookie/src/app/role/role-routing.module.ts b/bookie/src/app/role/role-routing.module.ts
new file mode 100644
index 0000000..db0fc8c
--- /dev/null
+++ b/bookie/src/app/role/role-routing.module.ts
@@ -0,0 +1,61 @@
+import {NgModule} from '@angular/core';
+import {CommonModule} from '@angular/common';
+import {RouterModule, Routes} from '@angular/router';
+import {RoleListResolver} from './role-list-resolver.service';
+import {RoleResolver} from './role-resolver.service';
+import {RoleListComponent} from './role-list/role-list.component';
+import {RoleDetailComponent} from './role-detail/role-detail.component';
+import {AuthGuard} from '../auth/auth-guard.service';
+
+const roleRoutes: Routes = [
+ {
+ path: 'roles/list',
+ component: RoleListComponent,
+ canActivate: [AuthGuard],
+ data: {
+ permission: 'Users'
+ },
+ resolve: {
+ list: RoleListResolver
+ }
+ },
+ {
+ path: 'roles/new',
+ component: RoleDetailComponent,
+ canActivate: [AuthGuard],
+ data: {
+ permission: 'Users'
+ },
+ resolve: {
+ item: RoleResolver,
+ }
+ },
+ {
+ path: 'roles/:id',
+ component: RoleDetailComponent,
+ canActivate: [AuthGuard],
+ data: {
+ permission: 'Users'
+ },
+ resolve: {
+ item: RoleResolver
+ }
+ }
+];
+
+@NgModule({
+ imports: [
+ CommonModule,
+ RouterModule.forChild(roleRoutes)
+
+ ],
+ exports: [
+ RouterModule
+ ],
+ providers: [
+ RoleListResolver,
+ RoleResolver
+ ]
+})
+export class RoleRoutingModule {
+}
diff --git a/bookie/src/app/role/role.module.ts b/bookie/src/app/role/role.module.ts
new file mode 100644
index 0000000..efaf017
--- /dev/null
+++ b/bookie/src/app/role/role.module.ts
@@ -0,0 +1,47 @@
+import {NgModule} from '@angular/core';
+import {CommonModule} from '@angular/common';
+
+import {RoleListComponent} from './role-list/role-list.component';
+import {RoleDetailComponent} from './role-detail/role-detail.component';
+import {RoleRoutingModule} from './role-routing.module';
+import { MatButtonModule } from '@angular/material/button';
+import { MatCardModule } from '@angular/material/card';
+import { MatCheckboxModule } from '@angular/material/checkbox';
+import { MatDividerModule } from '@angular/material/divider';
+import { MatIconModule } from '@angular/material/icon';
+import { MatInputModule } from '@angular/material/input';
+import { MatPaginatorModule } from '@angular/material/paginator';
+import { MatProgressSpinnerModule } from '@angular/material/progress-spinner';
+import { MatSortModule } from '@angular/material/sort';
+import { MatTableModule } from '@angular/material/table';
+import {CdkTableModule} from '@angular/cdk/table';
+import {ReactiveFormsModule} from '@angular/forms';
+import {SharedModule} from '../shared/shared.module';
+import {FlexLayoutModule} from '@angular/flex-layout';
+
+@NgModule({
+ imports: [
+ CommonModule,
+ CdkTableModule,
+ FlexLayoutModule,
+ MatButtonModule,
+ MatCardModule,
+ MatCheckboxModule,
+ MatDividerModule,
+ MatIconModule,
+ MatInputModule,
+ MatPaginatorModule,
+ MatProgressSpinnerModule,
+ MatSortModule,
+ MatTableModule,
+ ReactiveFormsModule,
+ SharedModule,
+ RoleRoutingModule
+ ],
+ declarations: [
+ RoleListComponent,
+ RoleDetailComponent
+ ]
+})
+export class RoleModule {
+}
diff --git a/bookie/src/app/role/role.service.ts b/bookie/src/app/role/role.service.ts
new file mode 100644
index 0000000..bc9e575
--- /dev/null
+++ b/bookie/src/app/role/role.service.ts
@@ -0,0 +1,65 @@
+import {Injectable} from '@angular/core';
+import {HttpClient, HttpHeaders, HttpParams} from '@angular/common/http';
+import {ErrorLoggerService} from '../core/error-logger.service';
+import {catchError} from 'rxjs/operators';
+import {Observable} from 'rxjs/internal/Observable';
+import {Role} from './role';
+
+const httpOptions = {
+ headers: new HttpHeaders({'Content-Type': 'application/json'})
+};
+const url = '/v1/roles';
+const serviceName = 'RoleService';
+
+@Injectable({
+ providedIn: 'root'
+})
+export class RoleService {
+ constructor(private http: HttpClient, private log: ErrorLoggerService) {
+ }
+
+ get(id: string): Observable {
+ const getUrl: string = (id === null) ? `${url}/new` : `${url}/${id}`;
+ return >this.http.get(getUrl)
+ .pipe(
+ catchError(this.log.handleError(serviceName, `get id=${id}`))
+ );
+ }
+
+ list(): Observable {
+ const options = {params: new HttpParams().set('l', '')};
+ return >this.http.get(`${url}/list`, options)
+ .pipe(
+ catchError(this.log.handleError(serviceName, 'list'))
+ );
+ }
+
+ save(role: Role): Observable {
+ return >this.http.put(`${url}/new`, role, httpOptions)
+ .pipe(
+ catchError(this.log.handleError(serviceName, 'save'))
+ );
+ }
+
+ update(role: Role): Observable {
+ return >this.http.post(`${url}/${role.id}`, role, httpOptions)
+ .pipe(
+ catchError(this.log.handleError(serviceName, 'update'))
+ );
+ }
+
+ saveOrUpdate(role: Role): Observable {
+ if (!role.id) {
+ return this.save(role);
+ } else {
+ return this.update(role);
+ }
+ }
+
+ delete(id: string): Observable {
+ return >this.http.delete(`${url}/${id}`, httpOptions)
+ .pipe(
+ catchError(this.log.handleError(serviceName, 'delete'))
+ );
+ }
+}
diff --git a/bookie/src/app/role/role.ts b/bookie/src/app/role/role.ts
new file mode 100644
index 0000000..5e4908b
--- /dev/null
+++ b/bookie/src/app/role/role.ts
@@ -0,0 +1,11 @@
+export class Role {
+ id: string;
+ name: string;
+ permissions: Permission[];
+}
+
+export class Permission {
+ id: string;
+ name: string;
+ enabled: boolean;
+}
diff --git a/bookie/src/app/shared/accounting.pipe.ts b/bookie/src/app/shared/accounting.pipe.ts
new file mode 100644
index 0000000..33136c4
--- /dev/null
+++ b/bookie/src/app/shared/accounting.pipe.ts
@@ -0,0 +1,20 @@
+import {Pipe, PipeTransform} from '@angular/core';
+
+@Pipe({
+ name: 'accounting'
+})
+export class AccountingPipe implements PipeTransform {
+
+ constructor() {
+
+ }
+
+ transform(value: string): string {
+ if (value === null) {
+ return '';
+ }
+ const amount = +(value.replace(new RegExp('(₹ )|(,)', 'g'), ''));
+ return value.replace('-', '') + ((amount < 0) ? '\u00A0Cr' : '\u00A0Dr');
+ }
+
+}
diff --git a/bookie/src/app/shared/clear.pipe.ts b/bookie/src/app/shared/clear.pipe.ts
new file mode 100644
index 0000000..e6a9c43
--- /dev/null
+++ b/bookie/src/app/shared/clear.pipe.ts
@@ -0,0 +1,12 @@
+import {Pipe, PipeTransform} from '@angular/core';
+
+@Pipe({
+ name: 'clear'
+})
+export class ClearPipe implements PipeTransform {
+
+ transform(value: any, args?: any): any {
+ return value === '₹ 0.00' || value === '0.00' ? '' : value;
+ }
+
+}
diff --git a/bookie/src/app/shared/confirm-dialog/confirm-dialog.component.css b/bookie/src/app/shared/confirm-dialog/confirm-dialog.component.css
new file mode 100644
index 0000000..e69de29
diff --git a/bookie/src/app/shared/confirm-dialog/confirm-dialog.component.html b/bookie/src/app/shared/confirm-dialog/confirm-dialog.component.html
new file mode 100644
index 0000000..1942c33
--- /dev/null
+++ b/bookie/src/app/shared/confirm-dialog/confirm-dialog.component.html
@@ -0,0 +1,8 @@
+{{data.title}}
+
+ {{data.content}}
+
+
+
+
+
diff --git a/bookie/src/app/shared/confirm-dialog/confirm-dialog.component.ts b/bookie/src/app/shared/confirm-dialog/confirm-dialog.component.ts
new file mode 100644
index 0000000..0d7ca32
--- /dev/null
+++ b/bookie/src/app/shared/confirm-dialog/confirm-dialog.component.ts
@@ -0,0 +1,16 @@
+import {Component, Inject} from '@angular/core';
+import { MAT_DIALOG_DATA, MatDialogRef } from '@angular/material/dialog';
+
+@Component({
+ selector: 'app-confirm-dialog',
+ templateUrl: './confirm-dialog.component.html',
+ styleUrls: ['./confirm-dialog.component.css']
+})
+export class ConfirmDialogComponent {
+
+ constructor(
+ public dialogRef: MatDialogRef,
+ @Inject(MAT_DIALOG_DATA) public data: any) {
+ }
+
+}
diff --git a/bookie/src/app/shared/cookie.service.ts b/bookie/src/app/shared/cookie.service.ts
new file mode 100644
index 0000000..ea308d7
--- /dev/null
+++ b/bookie/src/app/shared/cookie.service.ts
@@ -0,0 +1,36 @@
+import {Injectable} from '@angular/core';
+
+@Injectable({
+ providedIn: 'root'
+})
+export class CookieService {
+
+ constructor() {
+ }
+
+ public getCookie(name: string) {
+ const ca: Array = document.cookie.split(';');
+ const caLen: number = ca.length;
+ const cookieName = `${name}=`;
+ let c: string;
+
+ for (let i = 0; i < caLen; i += 1) {
+ c = ca[i].replace(/^\s+/g, '');
+ if (c.indexOf(cookieName) === 0) {
+ return c.substring(cookieName.length, c.length);
+ }
+ }
+ return '';
+ }
+
+ public deleteCookie(name) {
+ this.setCookie(name, '', -1);
+ }
+
+ public setCookie(name: string, value: string, expireDays: number, path: string = '') {
+ const d: Date = new Date();
+ d.setTime(d.getTime() + expireDays * 24 * 60 * 60 * 1000);
+ const expires: string = 'expires=' + d.toUTCString();
+ document.cookie = name + '=' + value + '; ' + expires + (path.length > 0 ? '; path=' + path : '');
+ }
+}
diff --git a/bookie/src/app/shared/image-dialog/image-dialog.component.css b/bookie/src/app/shared/image-dialog/image-dialog.component.css
new file mode 100644
index 0000000..e2718ba
--- /dev/null
+++ b/bookie/src/app/shared/image-dialog/image-dialog.component.css
@@ -0,0 +1,3 @@
+img {
+ max-width: 100%;
+}
diff --git a/bookie/src/app/shared/image-dialog/image-dialog.component.html b/bookie/src/app/shared/image-dialog/image-dialog.component.html
new file mode 100644
index 0000000..1343473
--- /dev/null
+++ b/bookie/src/app/shared/image-dialog/image-dialog.component.html
@@ -0,0 +1 @@
+
diff --git a/bookie/src/app/shared/image-dialog/image-dialog.component.ts b/bookie/src/app/shared/image-dialog/image-dialog.component.ts
new file mode 100644
index 0000000..22ab1e5
--- /dev/null
+++ b/bookie/src/app/shared/image-dialog/image-dialog.component.ts
@@ -0,0 +1,20 @@
+import {Component, Inject} from '@angular/core';
+import { MAT_DIALOG_DATA, MatDialogRef } from '@angular/material/dialog';
+
+@Component({
+ selector: 'app-image-dialog',
+ templateUrl: './image-dialog.component.html',
+ styleUrls: ['./image-dialog.component.css']
+})
+export class ImageDialogComponent {
+
+ constructor(
+ public dialogRef: MatDialogRef,
+ @Inject(MAT_DIALOG_DATA) public data: any) {
+ }
+
+ close(): void {
+ this.dialogRef.close();
+ }
+
+}
diff --git a/bookie/src/app/shared/local-time.pipe.ts b/bookie/src/app/shared/local-time.pipe.ts
new file mode 100644
index 0000000..acd61a2
--- /dev/null
+++ b/bookie/src/app/shared/local-time.pipe.ts
@@ -0,0 +1,16 @@
+import {Pipe, PipeTransform} from '@angular/core';
+import * as moment from 'moment';
+
+@Pipe({
+ name: 'localTime'
+})
+export class LocalTimePipe implements PipeTransform {
+
+ transform(value: string): string {
+ if (value === undefined) {
+ return '';
+ }
+ return moment(value, 'DD-MMM-YYYY HH:mm').subtract(new Date().getTimezoneOffset(), 'minutes').format('DD-MMM-YYYY HH:mm');
+ }
+
+}
diff --git a/bookie/src/app/shared/shared.module.ts b/bookie/src/app/shared/shared.module.ts
new file mode 100644
index 0000000..2a6b0aa
--- /dev/null
+++ b/bookie/src/app/shared/shared.module.ts
@@ -0,0 +1,35 @@
+import {NgModule} from '@angular/core';
+import {CommonModule} from '@angular/common';
+import {ConfirmDialogComponent} from './confirm-dialog/confirm-dialog.component';
+import { MatButtonModule } from '@angular/material/button';
+import { MatDialogModule } from '@angular/material/dialog';
+import {LocalTimePipe} from './local-time.pipe';
+import {ClearPipe} from './clear.pipe';
+import {AccountingPipe} from './accounting.pipe';
+import {ImageDialogComponent} from './image-dialog/image-dialog.component';
+
+@NgModule({
+ imports: [
+ CommonModule,
+ MatButtonModule,
+ MatDialogModule
+ ],
+ declarations: [
+ ConfirmDialogComponent,
+ ImageDialogComponent,
+ AccountingPipe,
+ ClearPipe,
+ LocalTimePipe
+ ],
+ entryComponents: [
+ ConfirmDialogComponent,
+ ImageDialogComponent
+ ],
+ exports: [
+ AccountingPipe,
+ ClearPipe,
+ LocalTimePipe
+ ]
+})
+export class SharedModule {
+}
diff --git a/bookie/src/app/shared/to-csv.service.ts b/bookie/src/app/shared/to-csv.service.ts
new file mode 100644
index 0000000..2ef1a00
--- /dev/null
+++ b/bookie/src/app/shared/to-csv.service.ts
@@ -0,0 +1,18 @@
+import {Injectable} from '@angular/core';
+
+@Injectable({
+ providedIn: 'root'
+})
+export class ToCsvService {
+
+ constructor() {
+ }
+
+ toCsv(headers: any, data: any[]): string {
+ const header = Object.keys(headers);
+ const replacer = (key, value) => value === null ? '' : value;
+ const csv = data.map(row => header.map(fieldName => JSON.stringify(row[headers[fieldName]], replacer)).join(','));
+ csv.unshift(header.join(','));
+ return csv.join('\r\n');
+ }
+}
diff --git a/bookie/src/app/shared/tokenizer.service.ts b/bookie/src/app/shared/tokenizer.service.ts
new file mode 100644
index 0000000..8396bc8
--- /dev/null
+++ b/bookie/src/app/shared/tokenizer.service.ts
@@ -0,0 +1,50 @@
+import {Injectable} from '@angular/core';
+
+const re = /((('[^']+'|"[^"]+"|[^\s]+)\s*:\s*('[^']+'|"[^"]+"|[^\s]+))|('[^']+'|"[^"]+"|[^\s]+))/g;
+
+@Injectable({
+ providedIn: 'root'
+})
+export class TokenizerService {
+
+ constructor() {
+ }
+
+ getMatches(input: string): string[] {
+ input = input ? input : '';
+ return input.match(re);
+ }
+
+ getPairs(input: string[]): any[] {
+ input = input ? input : [];
+ return input.reduce((accumulator, item) => {
+ let key,
+ value;
+ if (item.indexOf(':') === -1) {
+ key = '';
+ value = item;
+ } else {
+ key = item.substr(0, item.indexOf(':')).trim();
+ value = item.substr(item.indexOf(':') + 1, item.length).trim();
+ }
+ if (key.indexOf('\'') !== -1 || key.indexOf('"') !== -1) {
+ key = key.substring(1, key.length - 1).trim();
+ }
+ if (value.indexOf('\'') !== -1 || value.indexOf('"') !== -1) {
+ value = value.substring(1, value.length - 1).trim();
+ }
+ if (value !== '') {
+ accumulator.push({Key: key, Value: value});
+ }
+ return accumulator;
+ }, []);
+ }
+
+ isSort(key: string, value: string, sorter?: any): boolean {
+ const isSort = (key === '' && value.length > 1 && '+-'.indexOf(value.charAt(0)) !== -1);
+ return sorter !== null ? (isSort && value.substr(1) in sorter) : isSort;
+ }
+ getFilters(input: string[]) {
+ // input.
+ }
+}
diff --git a/bookie/src/app/tables/table-list/table-list.component.css b/bookie/src/app/tables/table-list/table-list.component.css
new file mode 100644
index 0000000..49a8146
--- /dev/null
+++ b/bookie/src/app/tables/table-list/table-list.component.css
@@ -0,0 +1,21 @@
+.grid-container {
+ margin: 20px;
+}
+
+.dashboard-card {
+ position: absolute;
+ top: 15px;
+ left: 15px;
+ right: 15px;
+ bottom: 15px;
+}
+
+.more-button {
+ position: absolute;
+ top: 5px;
+ right: 10px;
+}
+
+.dashboard-card-content {
+ text-align: center;
+}
diff --git a/bookie/src/app/tables/table-list/table-list.component.html b/bookie/src/app/tables/table-list/table-list.component.html
new file mode 100644
index 0000000..91a8a3f
--- /dev/null
+++ b/bookie/src/app/tables/table-list/table-list.component.html
@@ -0,0 +1,7 @@
+
+
+
+
+
+
+
diff --git a/bookie/src/app/tables/table-list/table-list.component.ts b/bookie/src/app/tables/table-list/table-list.component.ts
new file mode 100644
index 0000000..a0778d7
--- /dev/null
+++ b/bookie/src/app/tables/table-list/table-list.component.ts
@@ -0,0 +1,33 @@
+import { Component } from '@angular/core';
+import { map } from 'rxjs/operators';
+import { Breakpoints, BreakpointObserver } from '@angular/cdk/layout';
+
+@Component({
+ selector: 'app-table-list',
+ templateUrl: './table-list.component.html',
+ styleUrls: ['./table-list.component.css']
+})
+export class TableListComponent {
+ /** Based on the screen size, switch from standard to one column per row */
+ cards = this.breakpointObserver.observe(Breakpoints.Handset).pipe(
+ map(({ matches }) => {
+ if (matches) {
+ return [
+ { title: 'Card 1', cols: 1, rows: 1 },
+ { title: 'Card 2', cols: 1, rows: 1 },
+ { title: 'Card 3', cols: 1, rows: 1 },
+ { title: 'Card 4', cols: 1, rows: 1 }
+ ];
+ }
+
+ return [
+ { title: 'Card 1', cols: 2, rows: 1 },
+ { title: 'Card 2', cols: 1, rows: 1 },
+ { title: 'Card 3', cols: 1, rows: 2 },
+ { title: 'Card 4', cols: 1, rows: 1 }
+ ];
+ })
+ );
+
+ constructor(private breakpointObserver: BreakpointObserver) {}
+}
diff --git a/bookie/src/app/tables/tables.module.ts b/bookie/src/app/tables/tables.module.ts
new file mode 100644
index 0000000..e90288c
--- /dev/null
+++ b/bookie/src/app/tables/tables.module.ts
@@ -0,0 +1,23 @@
+import { NgModule } from '@angular/core';
+import { CommonModule } from '@angular/common';
+import { TableListComponent } from './table-list/table-list.component';
+import { MatButtonModule } from '@angular/material/button';
+import { MatCardModule } from '@angular/material/card';
+import { MatGridListModule } from '@angular/material/grid-list';
+import { MatIconModule } from '@angular/material/icon';
+import { MatMenuModule } from '@angular/material/menu';
+import { LayoutModule } from '@angular/cdk/layout';
+
+@NgModule({
+ declarations: [TableListComponent],
+ imports: [
+ CommonModule,
+ MatGridListModule,
+ MatCardModule,
+ MatMenuModule,
+ MatIconModule,
+ MatButtonModule,
+ LayoutModule
+ ]
+})
+export class TablesModule { }
diff --git a/bookie/src/app/user/user-detail/user-detail.component.css b/bookie/src/app/user/user-detail/user-detail.component.css
new file mode 100644
index 0000000..82c7afd
--- /dev/null
+++ b/bookie/src/app/user/user-detail/user-detail.component.css
@@ -0,0 +1,3 @@
+.example-card {
+ max-width: 400px;
+}
diff --git a/bookie/src/app/user/user-detail/user-detail.component.html b/bookie/src/app/user/user-detail/user-detail.component.html
new file mode 100644
index 0000000..2baf013
--- /dev/null
+++ b/bookie/src/app/user/user-detail/user-detail.component.html
@@ -0,0 +1,42 @@
+
diff --git a/bookie/src/app/user/user-detail/user-detail.component.ts b/bookie/src/app/user/user-detail/user-detail.component.ts
new file mode 100644
index 0000000..289c494
--- /dev/null
+++ b/bookie/src/app/user/user-detail/user-detail.component.ts
@@ -0,0 +1,121 @@
+import {AfterViewInit, Component, ElementRef, OnInit, ViewChild} from '@angular/core';
+import {ActivatedRoute, Router} from '@angular/router';
+
+import {UserService} from '../user.service';
+import {User} from '../user';
+import {ToasterService} from '../../core/toaster.service';
+import {ConfirmDialogComponent} from '../../shared/confirm-dialog/confirm-dialog.component';
+import { MatDialog } from '@angular/material/dialog';
+import {FormArray, FormBuilder, FormGroup} from '@angular/forms';
+
+@Component({
+ selector: 'app-user-detail',
+ templateUrl: './user-detail.component.html',
+ styleUrls: ['./user-detail.component.css']
+})
+export class UserDetailComponent implements OnInit, AfterViewInit {
+ @ViewChild('nameElement', { static: true }) nameElement: ElementRef;
+ form: FormGroup;
+ item: User;
+ hide: boolean;
+
+ constructor(
+ private route: ActivatedRoute,
+ private router: Router,
+ private fb: FormBuilder,
+ private toaster: ToasterService,
+ private dialog: MatDialog,
+ private ser: UserService
+ ) {
+ this.hide = true;
+ this.createForm();
+ }
+
+ createForm() {
+ this.form = this.fb.group({
+ name: '',
+ password: '',
+ lockedOut: '',
+ roles: this.fb.array([])
+ });
+ }
+
+ ngOnInit() {
+ this.route.data
+ .subscribe((data: { item: User }) => {
+ this.showItem(data.item);
+ });
+ }
+
+ showItem(item: User) {
+ this.item = item;
+ this.form.get('name').setValue(item.name);
+ this.form.get('password').setValue('');
+ this.form.get('lockedOut').setValue(item.lockedOut);
+ this.form.setControl('roles', this.fb.array(
+ item.roles.map(
+ x => this.fb.group({
+ role: x.enabled
+ })
+ )
+ ));
+ }
+
+
+ ngAfterViewInit() {
+ setTimeout(() => {
+ this.nameElement.nativeElement.focus();
+ }, 0);
+ }
+
+ save() {
+ this.ser.saveOrUpdate(this.getItem())
+ .subscribe(
+ (result) => {
+ this.toaster.show('Success', '');
+ this.router.navigateByUrl('/users/list');
+ },
+ (error) => {
+ this.toaster.show('Danger', error.error);
+ }
+ );
+ }
+
+ delete() {
+ this.ser.delete(this.item.id)
+ .subscribe(
+ (result) => {
+ this.toaster.show('Success', '');
+ this.router.navigateByUrl('/Users');
+ },
+ (error) => {
+ this.toaster.show('Danger', error.error);
+ }
+ );
+ }
+
+ confirmDelete(): void {
+ const dialogRef = this.dialog.open(ConfirmDialogComponent, {
+ width: '250px',
+ data: {title: 'Delete User?', content: 'Are you sure? This cannot be undone.'}
+ });
+
+ dialogRef.afterClosed().subscribe((result: boolean) => {
+ if (result) {
+ this.delete();
+ }
+ });
+ }
+
+ getItem(): User {
+ const formModel = this.form.value;
+ this.item.name = formModel.name;
+ this.item.password = formModel.password;
+ this.item.lockedOut = formModel.lockedOut;
+ const array = this.form.get('roles') as FormArray;
+ this.item.roles.forEach((item, index) => {
+ item.enabled = array.controls[index].value.role;
+ });
+ return this.item;
+ }
+}
diff --git a/bookie/src/app/user/user-list-resolver.service.ts b/bookie/src/app/user/user-list-resolver.service.ts
new file mode 100644
index 0000000..44ddc1a
--- /dev/null
+++ b/bookie/src/app/user/user-list-resolver.service.ts
@@ -0,0 +1,18 @@
+import {Injectable} from '@angular/core';
+import {ActivatedRouteSnapshot, Resolve, Router, RouterStateSnapshot} from '@angular/router';
+import {User} from './user';
+import {Observable} from 'rxjs/internal/Observable';
+import {UserService} from './user.service';
+
+@Injectable({
+ providedIn: 'root'
+})
+export class UserListResolver implements Resolve {
+
+ constructor(private ser: UserService, private router: Router) {
+ }
+
+ resolve(route: ActivatedRouteSnapshot, state: RouterStateSnapshot): Observable {
+ return this.ser.list();
+ }
+}
diff --git a/bookie/src/app/user/user-list/user-list-datasource.ts b/bookie/src/app/user/user-list/user-list-datasource.ts
new file mode 100644
index 0000000..9ea6b54
--- /dev/null
+++ b/bookie/src/app/user/user-list/user-list-datasource.ts
@@ -0,0 +1,59 @@
+import {DataSource} from '@angular/cdk/collections';
+import { MatPaginator } from '@angular/material/paginator';
+import { MatSort } from '@angular/material/sort';
+import {map} from 'rxjs/operators';
+import {merge, Observable, of as observableOf} from 'rxjs';
+import {User} from '../user';
+
+export class UserListDataSource extends DataSource {
+
+ constructor(private paginator: MatPaginator, private sort: MatSort, public data: User[]) {
+ super();
+ }
+
+ connect(): Observable {
+ const dataMutations = [
+ observableOf(this.data),
+ this.paginator.page,
+ this.sort.sortChange
+ ];
+
+ // Set the paginators length
+ this.paginator.length = this.data.length;
+
+ return merge(...dataMutations).pipe(map(() => {
+ return this.getPagedData(this.getSortedData([...this.data]));
+ }));
+ }
+
+ disconnect() {
+ }
+
+ private getPagedData(data: User[]) {
+ const startIndex = this.paginator.pageIndex * this.paginator.pageSize;
+ return data.splice(startIndex, this.paginator.pageSize);
+ }
+
+ private getSortedData(data: User[]) {
+ if (!this.sort.active || this.sort.direction === '') {
+ return data;
+ }
+
+ return data.sort((a, b) => {
+ const isAsc = this.sort.direction === 'asc';
+ switch (this.sort.active) {
+ case 'name':
+ return compare(a.name, b.name, isAsc);
+ case 'id':
+ return compare(+a.id, +b.id, isAsc);
+ default:
+ return 0;
+ }
+ });
+ }
+}
+
+/** Simple sort comparator for example ID/Name columns (for user-side sorting). */
+function compare(a, b, isAsc) {
+ return (a < b ? -1 : 1) * (isAsc ? 1 : -1);
+}
diff --git a/bookie/src/app/user/user-list/user-list.component.css b/bookie/src/app/user/user-list/user-list.component.css
new file mode 100644
index 0000000..e69de29
diff --git a/bookie/src/app/user/user-list/user-list.component.html b/bookie/src/app/user/user-list/user-list.component.html
new file mode 100644
index 0000000..547613f
--- /dev/null
+++ b/bookie/src/app/user/user-list/user-list.component.html
@@ -0,0 +1,45 @@
+
+
+ Users
+
+ add_box
+ Add
+
+
+
+
+
+
+
+ Name
+ {{row.name}}
+
+
+
+
+ Is Locked Out?
+ {{row.lockedOut}}
+
+
+
+
+ Roles
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/bookie/src/app/user/user-list/user-list.component.ts b/bookie/src/app/user/user-list/user-list.component.ts
new file mode 100644
index 0000000..253889b
--- /dev/null
+++ b/bookie/src/app/user/user-list/user-list.component.ts
@@ -0,0 +1,31 @@
+import {Component, OnInit, ViewChild} from '@angular/core';
+import { MatPaginator } from '@angular/material/paginator';
+import { MatSort } from '@angular/material/sort';
+import {UserListDataSource} from './user-list-datasource';
+import {User} from '../user';
+import {ActivatedRoute} from '@angular/router';
+
+@Component({
+ selector: 'app-user-list',
+ templateUrl: './user-list.component.html',
+ styleUrls: ['./user-list.component.css']
+})
+export class UserListComponent implements OnInit {
+ @ViewChild(MatPaginator, { static: true }) paginator: MatPaginator;
+ @ViewChild(MatSort, { static: true }) sort: MatSort;
+ dataSource: UserListDataSource;
+ list: User[];
+ /** Columns displayed in the table. Columns IDs can be added, removed, or reordered. */
+ displayedColumns = ['name', 'lockedOut', 'roles'];
+
+ constructor(private route: ActivatedRoute) {
+ }
+
+ ngOnInit() {
+ this.route.data
+ .subscribe((data: { list: User[] }) => {
+ this.list = data.list;
+ });
+ this.dataSource = new UserListDataSource(this.paginator, this.sort, this.list);
+ }
+}
diff --git a/bookie/src/app/user/user-resolver.service.ts b/bookie/src/app/user/user-resolver.service.ts
new file mode 100644
index 0000000..e9dbbac
--- /dev/null
+++ b/bookie/src/app/user/user-resolver.service.ts
@@ -0,0 +1,19 @@
+import {Injectable} from '@angular/core';
+import {ActivatedRouteSnapshot, Resolve, Router, RouterStateSnapshot} from '@angular/router';
+import {User} from './user';
+import {Observable} from 'rxjs/internal/Observable';
+import {UserService} from './user.service';
+
+@Injectable({
+ providedIn: 'root'
+})
+export class UserResolver implements Resolve {
+
+ constructor(private ser: UserService, private router: Router) {
+ }
+
+ resolve(route: ActivatedRouteSnapshot, state: RouterStateSnapshot): Observable {
+ const id = route.paramMap.get('id');
+ return this.ser.get(id);
+ }
+}
diff --git a/bookie/src/app/user/user-routing.module.ts b/bookie/src/app/user/user-routing.module.ts
new file mode 100644
index 0000000..d36d4fe
--- /dev/null
+++ b/bookie/src/app/user/user-routing.module.ts
@@ -0,0 +1,58 @@
+import {NgModule} from '@angular/core';
+import {CommonModule} from '@angular/common';
+import {RouterModule, Routes} from '@angular/router';
+import {UserListResolver} from './user-list-resolver.service';
+import {UserResolver} from './user-resolver.service';
+import {UserListComponent} from './user-list/user-list.component';
+import {UserDetailComponent} from './user-detail/user-detail.component';
+import {AuthGuard} from '../auth/auth-guard.service';
+
+const userRoutes: Routes = [
+ {
+ path: 'users/list',
+ component: UserListComponent,
+ canActivate: [AuthGuard],
+ data: {
+ permission: 'Users'
+ },
+ resolve: {
+ list: UserListResolver
+ }
+ },
+ {
+ path: 'users/new',
+ component: UserDetailComponent,
+ canActivate: [AuthGuard],
+ data: {
+ permission: 'Users'
+ },
+ resolve: {
+ item: UserResolver,
+ }
+ },
+ {
+ path: 'users/:id',
+ component: UserDetailComponent,
+ canActivate: [AuthGuard],
+ resolve: {
+ item: UserResolver
+ }
+ }
+];
+
+@NgModule({
+ imports: [
+ CommonModule,
+ RouterModule.forChild(userRoutes)
+
+ ],
+ exports: [
+ RouterModule
+ ],
+ providers: [
+ UserListResolver,
+ UserResolver
+ ]
+})
+export class UserRoutingModule {
+}
diff --git a/bookie/src/app/user/user.module.ts b/bookie/src/app/user/user.module.ts
new file mode 100644
index 0000000..467f5f5
--- /dev/null
+++ b/bookie/src/app/user/user.module.ts
@@ -0,0 +1,47 @@
+import {NgModule} from '@angular/core';
+import {CommonModule} from '@angular/common';
+
+import {UserListComponent} from './user-list/user-list.component';
+import {UserDetailComponent} from './user-detail/user-detail.component';
+import {UserRoutingModule} from './user-routing.module';
+import { MatButtonModule } from '@angular/material/button';
+import { MatCardModule } from '@angular/material/card';
+import { MatCheckboxModule } from '@angular/material/checkbox';
+import { MatDividerModule } from '@angular/material/divider';
+import { MatIconModule } from '@angular/material/icon';
+import { MatInputModule } from '@angular/material/input';
+import { MatPaginatorModule } from '@angular/material/paginator';
+import { MatProgressSpinnerModule } from '@angular/material/progress-spinner';
+import { MatSortModule } from '@angular/material/sort';
+import { MatTableModule } from '@angular/material/table';
+import {CdkTableModule} from '@angular/cdk/table';
+import {ReactiveFormsModule} from '@angular/forms';
+import {SharedModule} from '../shared/shared.module';
+import {FlexLayoutModule} from '@angular/flex-layout';
+
+@NgModule({
+ imports: [
+ CommonModule,
+ CdkTableModule,
+ FlexLayoutModule,
+ MatButtonModule,
+ MatCardModule,
+ MatCheckboxModule,
+ MatDividerModule,
+ MatIconModule,
+ MatInputModule,
+ MatPaginatorModule,
+ MatProgressSpinnerModule,
+ MatSortModule,
+ MatTableModule,
+ ReactiveFormsModule,
+ SharedModule,
+ UserRoutingModule
+ ],
+ declarations: [
+ UserListComponent,
+ UserDetailComponent
+ ]
+})
+export class UserModule {
+}
diff --git a/bookie/src/app/user/user.service.ts b/bookie/src/app/user/user.service.ts
new file mode 100644
index 0000000..9b6a4ae
--- /dev/null
+++ b/bookie/src/app/user/user.service.ts
@@ -0,0 +1,73 @@
+import {Injectable} from '@angular/core';
+import {HttpClient, HttpHeaders, HttpParams} from '@angular/common/http';
+import {ErrorLoggerService} from '../core/error-logger.service';
+import {catchError} from 'rxjs/operators';
+import {Observable} from 'rxjs/internal/Observable';
+import {User} from './user';
+
+const httpOptions = {
+ headers: new HttpHeaders({'Content-Type': 'application/json'})
+};
+const url = '/v1/users';
+const serviceName = 'UserService';
+
+@Injectable({
+ providedIn: 'root'
+})
+export class UserService {
+ constructor(private http: HttpClient, private log: ErrorLoggerService) {
+ }
+
+ get(id: string): Observable {
+ const getUrl: string = (id === null) ? `${url}/new` : `${url}/${id}`;
+ return >this.http.get(getUrl)
+ .pipe(
+ catchError(this.log.handleError(serviceName, `get id=${id}`))
+ );
+ }
+
+ list(): Observable {
+ const options = {params: new HttpParams().set('l', '')};
+ return >this.http.get(`${url}/list`, options)
+ .pipe(
+ catchError(this.log.handleError(serviceName, 'list'))
+ );
+ }
+
+ listOfNames(): Observable {
+ const options = {params: new HttpParams().set('n', '')};
+ return >this.http.get(`${url}/list`, options)
+ .pipe(
+ catchError(this.log.handleError(serviceName, 'list'))
+ );
+ }
+
+ save(user: User): Observable {
+ return >this.http.put(`${url}/new`, user, httpOptions)
+ .pipe(
+ catchError(this.log.handleError(serviceName, 'save'))
+ );
+ }
+
+ update(user: User): Observable {
+ return >this.http.post(`${url}/${user.id}`, user, httpOptions)
+ .pipe(
+ catchError(this.log.handleError(serviceName, 'update'))
+ );
+ }
+
+ saveOrUpdate(user: User): Observable {
+ if (!user.id) {
+ return this.save(user);
+ } else {
+ return this.update(user);
+ }
+ }
+
+ delete(id: string): Observable {
+ return >this.http.delete(`${url}/${id}`, httpOptions)
+ .pipe(
+ catchError(this.log.handleError(serviceName, 'delete'))
+ );
+ }
+}
diff --git a/bookie/src/app/user/user.ts b/bookie/src/app/user/user.ts
new file mode 100644
index 0000000..44d3bb2
--- /dev/null
+++ b/bookie/src/app/user/user.ts
@@ -0,0 +1,19 @@
+export class User {
+ id: string;
+ name: string;
+ password: string;
+ lockedOut: boolean;
+ roles: UserRole[];
+ perms: string[];
+ isAuthenticated: boolean;
+
+ public constructor(init?: Partial) {
+ Object.assign(this, init);
+ }
+}
+
+export class UserRole {
+ id: string;
+ name: string;
+ enabled: boolean;
+}
diff --git a/bookie/src/assets/.gitkeep b/bookie/src/assets/.gitkeep
new file mode 100644
index 0000000..e69de29
diff --git a/bookie/src/environments/environment.prod.ts b/bookie/src/environments/environment.prod.ts
new file mode 100644
index 0000000..3612073
--- /dev/null
+++ b/bookie/src/environments/environment.prod.ts
@@ -0,0 +1,3 @@
+export const environment = {
+ production: true
+};
diff --git a/bookie/src/environments/environment.ts b/bookie/src/environments/environment.ts
new file mode 100644
index 0000000..7b4f817
--- /dev/null
+++ b/bookie/src/environments/environment.ts
@@ -0,0 +1,16 @@
+// This file can be replaced during build by using the `fileReplacements` array.
+// `ng build --prod` replaces `environment.ts` with `environment.prod.ts`.
+// The list of file replacements can be found in `angular.json`.
+
+export const environment = {
+ production: false
+};
+
+/*
+ * For easier debugging in development mode, you can import the following file
+ * to ignore zone related error stack frames such as `zone.run`, `zoneDelegate.invokeTask`.
+ *
+ * This import should be commented out in production mode because it will have a negative impact
+ * on performance if an error is thrown.
+ */
+// import 'zone.js/dist/zone-error'; // Included with Angular CLI.
diff --git a/bookie/src/favicon.ico b/bookie/src/favicon.ico
new file mode 100644
index 0000000..8081c7c
Binary files /dev/null and b/bookie/src/favicon.ico differ
diff --git a/bookie/src/index.html b/bookie/src/index.html
new file mode 100644
index 0000000..56ff27e
--- /dev/null
+++ b/bookie/src/index.html
@@ -0,0 +1,16 @@
+
+
+
+
+ Bookie
+
+
+
+
+
+
+
+
+
+
+
diff --git a/bookie/src/karma.conf.js b/bookie/src/karma.conf.js
new file mode 100644
index 0000000..b6e0042
--- /dev/null
+++ b/bookie/src/karma.conf.js
@@ -0,0 +1,31 @@
+// Karma configuration file, see link for more information
+// https://karma-runner.github.io/1.0/config/configuration-file.html
+
+module.exports = function (config) {
+ config.set({
+ basePath: '',
+ frameworks: ['jasmine', '@angular-devkit/build-angular'],
+ plugins: [
+ require('karma-jasmine'),
+ require('karma-chrome-launcher'),
+ require('karma-jasmine-html-reporter'),
+ require('karma-coverage-istanbul-reporter'),
+ require('@angular-devkit/build-angular/plugins/karma')
+ ],
+ client: {
+ clearContext: false // leave Jasmine Spec Runner output visible in browser
+ },
+ coverageIstanbulReporter: {
+ dir: require('path').join(__dirname, '../coverage'),
+ reports: ['html', 'lcovonly'],
+ fixWebpackSourcePaths: true
+ },
+ reporters: ['progress', 'kjhtml'],
+ port: 9876,
+ colors: true,
+ logLevel: config.LOG_INFO,
+ autoWatch: true,
+ browsers: ['Chrome'],
+ singleRun: false
+ });
+};
\ No newline at end of file
diff --git a/bookie/src/main.ts b/bookie/src/main.ts
new file mode 100644
index 0000000..3b2b7d0
--- /dev/null
+++ b/bookie/src/main.ts
@@ -0,0 +1,13 @@
+import 'hammerjs';
+import { enableProdMode } from '@angular/core';
+import { platformBrowserDynamic } from '@angular/platform-browser-dynamic';
+
+import { AppModule } from './app/app.module';
+import { environment } from './environments/environment';
+
+if (environment.production) {
+ enableProdMode();
+}
+
+platformBrowserDynamic().bootstrapModule(AppModule)
+ .catch(err => console.error(err));
diff --git a/bookie/src/polyfills.ts b/bookie/src/polyfills.ts
new file mode 100644
index 0000000..aa665d6
--- /dev/null
+++ b/bookie/src/polyfills.ts
@@ -0,0 +1,63 @@
+/**
+ * This file includes polyfills needed by Angular and is loaded before the app.
+ * You can add your own extra polyfills to this file.
+ *
+ * This file is divided into 2 sections:
+ * 1. Browser polyfills. These are applied before loading ZoneJS and are sorted by browsers.
+ * 2. Application imports. Files imported after ZoneJS that should be loaded before your main
+ * file.
+ *
+ * The current setup is for so-called "evergreen" browsers; the last versions of browsers that
+ * automatically update themselves. This includes Safari >= 10, Chrome >= 55 (including Opera),
+ * Edge >= 13 on the desktop, and iOS 10 and Chrome on mobile.
+ *
+ * Learn more in https://angular.io/guide/browser-support
+ */
+
+/***************************************************************************************************
+ * BROWSER POLYFILLS
+ */
+
+/** IE10 and IE11 requires the following for NgClass support on SVG elements */
+// import 'classlist.js'; // Run `npm install --save classlist.js`.
+
+/**
+ * Web Animations `@angular/platform-browser/animations`
+ * Only required if AnimationBuilder is used within the application and using IE/Edge or Safari.
+ * Standard animation support in Angular DOES NOT require any polyfills (as of Angular 6.0).
+ */
+// import 'web-animations-js'; // Run `npm install --save web-animations-js`.
+
+/**
+ * By default, zone.js will patch all possible macroTask and DomEvents
+ * user can disable parts of macroTask/DomEvents patch by setting following flags
+ * because those flags need to be set before `zone.js` being loaded, and webpack
+ * will put import in the top of bundle, so user need to create a separate file
+ * in this directory (for example: zone-flags.ts), and put the following flags
+ * into that file, and then add the following code before importing zone.js.
+ * import './zone-flags.ts';
+ *
+ * The flags allowed in zone-flags.ts are listed here.
+ *
+ * The following flags will work for all browsers.
+ *
+ * (window as any).__Zone_disable_requestAnimationFrame = true; // disable patch requestAnimationFrame
+ * (window as any).__Zone_disable_on_property = true; // disable patch onProperty such as onclick
+ * (window as any).__zone_symbol__UNPATCHED_EVENTS = ['scroll', 'mousemove']; // disable patch specified eventNames
+ *
+ * in IE/Edge developer tools, the addEventListener will also be wrapped by zone.js
+ * with the following flag, it will bypass `zone.js` patch for IE/Edge
+ *
+ * (window as any).__Zone_enable_cross_context_check = true;
+ *
+ */
+
+/***************************************************************************************************
+ * Zone JS is required by default for Angular itself.
+ */
+import 'zone.js/dist/zone'; // Included with Angular CLI.
+
+
+/***************************************************************************************************
+ * APPLICATION IMPORTS
+ */
diff --git a/bookie/src/styles.css b/bookie/src/styles.css
new file mode 100644
index 0000000..7e7239a
--- /dev/null
+++ b/bookie/src/styles.css
@@ -0,0 +1,4 @@
+/* You can add global styles to this file, and also import other style files */
+
+html, body { height: 100%; }
+body { margin: 0; font-family: Roboto, "Helvetica Neue", sans-serif; }
diff --git a/bookie/src/test.ts b/bookie/src/test.ts
new file mode 100644
index 0000000..1631789
--- /dev/null
+++ b/bookie/src/test.ts
@@ -0,0 +1,20 @@
+// This file is required by karma.conf.js and loads recursively all the .spec and framework files
+
+import 'zone.js/dist/zone-testing';
+import { getTestBed } from '@angular/core/testing';
+import {
+ BrowserDynamicTestingModule,
+ platformBrowserDynamicTesting
+} from '@angular/platform-browser-dynamic/testing';
+
+declare const require: any;
+
+// First, initialize the Angular testing environment.
+getTestBed().initTestEnvironment(
+ BrowserDynamicTestingModule,
+ platformBrowserDynamicTesting()
+);
+// Then we find all the tests.
+const context = require.context('./', true, /\.spec\.ts$/);
+// And load the modules.
+context.keys().map(context);
diff --git a/bookie/src/tsconfig.app.json b/bookie/src/tsconfig.app.json
new file mode 100644
index 0000000..190fd30
--- /dev/null
+++ b/bookie/src/tsconfig.app.json
@@ -0,0 +1,11 @@
+{
+ "extends": "../tsconfig.json",
+ "compilerOptions": {
+ "outDir": "../out-tsc/app",
+ "types": []
+ },
+ "exclude": [
+ "test.ts",
+ "**/*.spec.ts"
+ ]
+}
diff --git a/bookie/src/tsconfig.spec.json b/bookie/src/tsconfig.spec.json
new file mode 100644
index 0000000..de77336
--- /dev/null
+++ b/bookie/src/tsconfig.spec.json
@@ -0,0 +1,18 @@
+{
+ "extends": "../tsconfig.json",
+ "compilerOptions": {
+ "outDir": "../out-tsc/spec",
+ "types": [
+ "jasmine",
+ "node"
+ ]
+ },
+ "files": [
+ "test.ts",
+ "polyfills.ts"
+ ],
+ "include": [
+ "**/*.spec.ts",
+ "**/*.d.ts"
+ ]
+}
diff --git a/bookie/src/tslint.json b/bookie/src/tslint.json
new file mode 100644
index 0000000..52e2c1a
--- /dev/null
+++ b/bookie/src/tslint.json
@@ -0,0 +1,17 @@
+{
+ "extends": "../tslint.json",
+ "rules": {
+ "directive-selector": [
+ true,
+ "attribute",
+ "app",
+ "camelCase"
+ ],
+ "component-selector": [
+ true,
+ "element",
+ "app",
+ "kebab-case"
+ ]
+ }
+}
diff --git a/bookie/tsconfig.json b/bookie/tsconfig.json
new file mode 100644
index 0000000..c6b0b5a
--- /dev/null
+++ b/bookie/tsconfig.json
@@ -0,0 +1,21 @@
+{
+ "compileOnSave": false,
+ "compilerOptions": {
+ "baseUrl": "./",
+ "outDir": "./dist/out-tsc",
+ "sourceMap": true,
+ "declaration": false,
+ "module": "esnext",
+ "moduleResolution": "node",
+ "emitDecoratorMetadata": true,
+ "experimentalDecorators": true,
+ "target": "es2015",
+ "typeRoots": [
+ "node_modules/@types"
+ ],
+ "lib": [
+ "es2018",
+ "dom"
+ ]
+ }
+}
diff --git a/bookie/tslint.json b/bookie/tslint.json
new file mode 100644
index 0000000..ea31655
--- /dev/null
+++ b/bookie/tslint.json
@@ -0,0 +1,131 @@
+{
+ "rulesDirectory": [
+ "node_modules/codelyzer"
+ ],
+ "rules": {
+ "arrow-return-shorthand": true,
+ "callable-types": true,
+ "class-name": true,
+ "comment-format": [
+ true,
+ "check-space"
+ ],
+ "curly": true,
+ "deprecation": {
+ "severity": "warn"
+ },
+ "eofline": true,
+ "forin": true,
+ "import-blacklist": [
+ true,
+ "rxjs/Rx"
+ ],
+ "import-spacing": true,
+ "indent": [
+ true,
+ "spaces"
+ ],
+ "interface-over-type-literal": true,
+ "label-position": true,
+ "max-line-length": [
+ true,
+ 140
+ ],
+ "member-access": false,
+ "member-ordering": [
+ true,
+ {
+ "order": [
+ "static-field",
+ "instance-field",
+ "static-method",
+ "instance-method"
+ ]
+ }
+ ],
+ "no-arg": true,
+ "no-bitwise": true,
+ "no-console": [
+ true,
+ "debug",
+ "info",
+ "time",
+ "timeEnd",
+ "trace"
+ ],
+ "no-construct": true,
+ "no-debugger": true,
+ "no-duplicate-super": true,
+ "no-empty": false,
+ "no-empty-interface": true,
+ "no-eval": true,
+ "no-inferrable-types": [
+ true,
+ "ignore-params"
+ ],
+ "no-misused-new": true,
+ "no-non-null-assertion": true,
+ "no-redundant-jsdoc": true,
+ "no-shadowed-variable": true,
+ "no-string-literal": false,
+ "no-string-throw": true,
+ "no-switch-case-fall-through": true,
+ "no-trailing-whitespace": true,
+ "no-unnecessary-initializer": true,
+ "no-unused-expression": true,
+ "no-use-before-declare": true,
+ "no-var-keyword": true,
+ "object-literal-sort-keys": false,
+ "one-line": [
+ true,
+ "check-open-brace",
+ "check-catch",
+ "check-else",
+ "check-whitespace"
+ ],
+ "prefer-const": true,
+ "quotemark": [
+ true,
+ "single"
+ ],
+ "radix": true,
+ "semicolon": [
+ true,
+ "always"
+ ],
+ "triple-equals": [
+ true,
+ "allow-null-check"
+ ],
+ "typedef-whitespace": [
+ true,
+ {
+ "call-signature": "nospace",
+ "index-signature": "nospace",
+ "parameter": "nospace",
+ "property-declaration": "nospace",
+ "variable-declaration": "nospace"
+ }
+ ],
+ "unified-signatures": true,
+ "variable-name": false,
+ "whitespace": [
+ true,
+ "check-branch",
+ "check-decl",
+ "check-operator",
+ "check-separator",
+ "check-type"
+ ],
+ "no-output-on-prefix": true,
+ "no-inputs-metadata-property": true,
+ "no-outputs-metadata-property": true,
+ "no-host-metadata-property": true,
+ "no-input-rename": true,
+ "no-output-rename": true,
+ "use-lifecycle-interface": true,
+ "use-pipe-transform-interface": true,
+ "component-class-suffix": true,
+ "directive-class-suffix": true
+ }
+}