Update Models and Schema
This commit is contained in:
108
migrations/versions/938218f911e8_.py
Normal file
108
migrations/versions/938218f911e8_.py
Normal file
@@ -0,0 +1,108 @@
|
||||
"""empty message
|
||||
|
||||
Revision ID: 938218f911e8
|
||||
Revises:
|
||||
Create Date: 2022-07-12 06:31:18.835124
|
||||
|
||||
"""
|
||||
from alembic import op
|
||||
import sqlalchemy as sa
|
||||
|
||||
|
||||
# revision identifiers, used by Alembic.
|
||||
revision = '938218f911e8'
|
||||
down_revision = None
|
||||
branch_labels = None
|
||||
depends_on = None
|
||||
|
||||
|
||||
def upgrade():
|
||||
# ### commands auto generated by Alembic - please adjust! ###
|
||||
op.create_table('access_keys',
|
||||
sa.Column('key', sa.String(length=64), nullable=False),
|
||||
sa.Column('clientId', sa.String(length=64), nullable=False),
|
||||
sa.Column('clientIP', sa.String(length=46), nullable=False),
|
||||
sa.Column('userAgent', sa.String(length=255), nullable=False),
|
||||
sa.Column('suspended', sa.Boolean(), nullable=False),
|
||||
sa.Column('revoked', sa.Boolean(), nullable=False),
|
||||
sa.Column('createdAt', sa.DateTime(), nullable=True),
|
||||
sa.Column('suspendedAt', sa.DateTime(), nullable=True),
|
||||
sa.Column('revokedAt', sa.DateTime(), nullable=True),
|
||||
sa.Column('restoredAt', sa.DateTime(), nullable=True),
|
||||
sa.PrimaryKeyConstraint('key')
|
||||
)
|
||||
op.create_table('access_tokens',
|
||||
sa.Column('token', sa.String(length=64), nullable=False),
|
||||
sa.Column('clientIP', sa.String(length=46), nullable=False),
|
||||
sa.Column('userAgent', sa.String(length=255), nullable=False),
|
||||
sa.Column('expired', sa.Boolean(), nullable=False),
|
||||
sa.Column('revoked', sa.Boolean(), nullable=False),
|
||||
sa.Column('issuedAt', sa.DateTime(), nullable=True),
|
||||
sa.Column('expiresAt', sa.DateTime(), nullable=True),
|
||||
sa.Column('revokedAt', sa.DateTime(), nullable=True),
|
||||
sa.PrimaryKeyConstraint('token')
|
||||
)
|
||||
op.create_table('lists',
|
||||
sa.Column('id', sa.Integer(), nullable=False),
|
||||
sa.Column('accessKey', sa.String(length=64), nullable=True),
|
||||
sa.Column('name', sa.String(length=128), nullable=False),
|
||||
sa.Column('notes', sa.String(), nullable=True),
|
||||
sa.Column('createdAt', sa.DateTime(), nullable=True),
|
||||
sa.Column('modifiedAt', sa.DateTime(), nullable=True),
|
||||
sa.PrimaryKeyConstraint('id')
|
||||
)
|
||||
op.create_table('products',
|
||||
sa.Column('id', sa.Integer(), nullable=False),
|
||||
sa.Column('accessKey', sa.String(length=64), nullable=True),
|
||||
sa.Column('name', sa.String(length=128), nullable=False),
|
||||
sa.Column('category', sa.String(length=128), nullable=False),
|
||||
sa.Column('notes', sa.String(), nullable=True),
|
||||
sa.Column('createdAt', sa.DateTime(), nullable=True),
|
||||
sa.Column('modifiedAt', sa.DateTime(), nullable=True),
|
||||
sa.PrimaryKeyConstraint('id')
|
||||
)
|
||||
op.create_index(op.f('ix_products_category'), 'products', ['category'], unique=False)
|
||||
op.create_table('sigl_config',
|
||||
sa.Column('key', sa.String(), nullable=False),
|
||||
sa.Column('value', sa.String(length=128), nullable=True),
|
||||
sa.PrimaryKeyConstraint('key')
|
||||
)
|
||||
op.create_table('list_entries',
|
||||
sa.Column('id', sa.Integer(), nullable=False),
|
||||
sa.Column('list_id', sa.Integer(), nullable=False),
|
||||
sa.Column('product_id', sa.Integer(), nullable=False),
|
||||
sa.Column('quantity', sa.String(length=128), nullable=True),
|
||||
sa.Column('crossedOff', sa.Boolean(), nullable=True),
|
||||
sa.Column('deleted', sa.Boolean(), nullable=True),
|
||||
sa.Column('notes', sa.String(), nullable=True),
|
||||
sa.Column('createdAt', sa.DateTime(), nullable=True),
|
||||
sa.Column('modifiedAt', sa.DateTime(), nullable=True),
|
||||
sa.ForeignKeyConstraint(['list_id'], ['lists.id'], ),
|
||||
sa.ForeignKeyConstraint(['product_id'], ['products.id'], ),
|
||||
sa.PrimaryKeyConstraint('id')
|
||||
)
|
||||
op.create_table('product_locations',
|
||||
sa.Column('product_id', sa.Integer(), nullable=False),
|
||||
sa.Column('store', sa.String(length=128), nullable=False),
|
||||
sa.Column('aisle', sa.String(length=64), nullable=False),
|
||||
sa.Column('bin', sa.String(length=64), nullable=True),
|
||||
sa.Column('notes', sa.String(), nullable=True),
|
||||
sa.Column('createdAt', sa.DateTime(), nullable=True),
|
||||
sa.Column('modifiedAt', sa.DateTime(), nullable=True),
|
||||
sa.ForeignKeyConstraint(['product_id'], ['products.id'], ),
|
||||
sa.PrimaryKeyConstraint('product_id', 'store')
|
||||
)
|
||||
# ### end Alembic commands ###
|
||||
|
||||
|
||||
def downgrade():
|
||||
# ### commands auto generated by Alembic - please adjust! ###
|
||||
op.drop_table('product_locations')
|
||||
op.drop_table('list_entries')
|
||||
op.drop_table('sigl_config')
|
||||
op.drop_index(op.f('ix_products_category'), table_name='products')
|
||||
op.drop_table('products')
|
||||
op.drop_table('lists')
|
||||
op.drop_table('access_tokens')
|
||||
op.drop_table('access_keys')
|
||||
# ### end Alembic commands ###
|
||||
@@ -1,56 +0,0 @@
|
||||
"""empty message
|
||||
|
||||
Revision ID: b3639db87e06
|
||||
Revises:
|
||||
Create Date: 2022-07-11 17:09:20.502213
|
||||
|
||||
"""
|
||||
from alembic import op
|
||||
import sqlalchemy as sa
|
||||
|
||||
|
||||
# revision identifiers, used by Alembic.
|
||||
revision = 'b3639db87e06'
|
||||
down_revision = None
|
||||
branch_labels = None
|
||||
depends_on = None
|
||||
|
||||
|
||||
def upgrade():
|
||||
# ### commands auto generated by Alembic - please adjust! ###
|
||||
op.create_table('products',
|
||||
sa.Column('id', sa.Integer(), nullable=False),
|
||||
sa.Column('name', sa.String(length=128), nullable=False),
|
||||
sa.Column('category', sa.String(length=128), nullable=False),
|
||||
sa.Column('notes', sa.String(), nullable=True),
|
||||
sa.Column('created_at', sa.DateTime(), nullable=True),
|
||||
sa.Column('modified_at', sa.DateTime(), nullable=True),
|
||||
sa.PrimaryKeyConstraint('id')
|
||||
)
|
||||
op.create_index(op.f('ix_products_category'), 'products', ['category'], unique=False)
|
||||
op.create_table('sigl_config',
|
||||
sa.Column('key', sa.String(), nullable=False),
|
||||
sa.Column('value', sa.String(length=128), nullable=True),
|
||||
sa.PrimaryKeyConstraint('key')
|
||||
)
|
||||
op.create_table('product_locations',
|
||||
sa.Column('product_id', sa.Integer(), nullable=False),
|
||||
sa.Column('store', sa.String(length=128), nullable=False),
|
||||
sa.Column('aisle', sa.String(length=64), nullable=False),
|
||||
sa.Column('bin', sa.String(length=64), nullable=True),
|
||||
sa.Column('notes', sa.String(), nullable=True),
|
||||
sa.Column('created_at', sa.DateTime(), nullable=True),
|
||||
sa.Column('modified_at', sa.DateTime(), nullable=True),
|
||||
sa.ForeignKeyConstraint(['product_id'], ['products.id'], ),
|
||||
sa.PrimaryKeyConstraint('product_id', 'store')
|
||||
)
|
||||
# ### end Alembic commands ###
|
||||
|
||||
|
||||
def downgrade():
|
||||
# ### commands auto generated by Alembic - please adjust! ###
|
||||
op.drop_table('product_locations')
|
||||
op.drop_table('sigl_config')
|
||||
op.drop_index(op.f('ix_products_category'), table_name='products')
|
||||
op.drop_table('products')
|
||||
# ### end Alembic commands ###
|
||||
@@ -15,6 +15,91 @@ sigl_config = db.Table(
|
||||
db.Column('value', db.String(128)),
|
||||
)
|
||||
|
||||
#: Access Key Table
|
||||
access_keys = db.Table(
|
||||
'access_keys',
|
||||
|
||||
# Primary Key
|
||||
db.Column('key', db.String(64), primary_key=True),
|
||||
|
||||
# Client Attributes
|
||||
db.Column('clientId', db.String(64), nullable=False),
|
||||
db.Column('clientIP', db.String(46), nullable=False),
|
||||
db.Column('userAgent', db.String(255), nullable=False),
|
||||
|
||||
# Key Validity
|
||||
db.Column('suspended', db.Boolean, nullable=False, default=False),
|
||||
db.Column('revoked', db.Boolean, nullable=False, default=False),
|
||||
|
||||
# Timestamps
|
||||
db.Column('createdAt', db.DateTime(), default=None),
|
||||
db.Column('suspendedAt', db.DateTime(), default=None),
|
||||
db.Column('revokedAt', db.DateTime(), default=None),
|
||||
db.Column('restoredAt', db.DateTime(), default=None),
|
||||
)
|
||||
|
||||
#: Access Token Table
|
||||
access_tokens = db.Table(
|
||||
'access_tokens',
|
||||
|
||||
# Primary Key
|
||||
db.Column('token', db.String(64), primary_key=True),
|
||||
|
||||
# Client Attributes
|
||||
db.Column('clientIP', db.String(46), nullable=False),
|
||||
db.Column('userAgent', db.String(255), nullable=False),
|
||||
|
||||
# Key Validity
|
||||
db.Column('expired', db.Boolean, nullable=False, default=False),
|
||||
db.Column('revoked', db.Boolean, nullable=False, default=False),
|
||||
|
||||
# Timestamps
|
||||
db.Column('issuedAt', db.DateTime(), default=None),
|
||||
db.Column('expiresAt', db.DateTime(), default=None),
|
||||
db.Column('revokedAt', db.DateTime(), default=None),
|
||||
)
|
||||
|
||||
#: Shopping List Table
|
||||
lists = db.Table(
|
||||
'lists',
|
||||
|
||||
# Primary Key
|
||||
db.Column('id', db.Integer, primary_key=True),
|
||||
|
||||
# Access Key
|
||||
db.Column('accessKey', db.String(64), default=None),
|
||||
|
||||
# List Attributes
|
||||
db.Column('name', db.String(128), nullable=False),
|
||||
|
||||
# Mixin Columns
|
||||
db.Column('notes', db.String(), default=None),
|
||||
db.Column('createdAt', db.DateTime(), default=None),
|
||||
db.Column('modifiedAt', db.DateTime(), default=None),
|
||||
)
|
||||
|
||||
#: List Entry Table
|
||||
list_entries = db.Table(
|
||||
'list_entries',
|
||||
|
||||
# Primary Key
|
||||
db.Column('id', db.Integer, primary_key=True),
|
||||
|
||||
# Shopping List and Product Link
|
||||
db.Column('list_id', db.ForeignKey('lists.id'), nullable=False),
|
||||
db.Column('product_id', db.ForeignKey('products.id'), nullable=False),
|
||||
|
||||
# Entry Attributes
|
||||
db.Column('quantity', db.String(128), default=None),
|
||||
db.Column('crossedOff', db.Boolean, default=False),
|
||||
db.Column('deleted', db.Boolean, default=False),
|
||||
|
||||
# Mixin Columns
|
||||
db.Column('notes', db.String(), default=None),
|
||||
db.Column('createdAt', db.DateTime(), default=None),
|
||||
db.Column('modifiedAt', db.DateTime(), default=None),
|
||||
)
|
||||
|
||||
#: Product Table
|
||||
products = db.Table(
|
||||
'products',
|
||||
@@ -22,14 +107,17 @@ products = db.Table(
|
||||
# Primary Key
|
||||
db.Column('id', db.Integer, primary_key=True),
|
||||
|
||||
# Access Key
|
||||
db.Column('accessKey', db.String(64), default=None),
|
||||
|
||||
# Product Attributes
|
||||
db.Column('name', db.String(128), nullable=False),
|
||||
db.Column('category', db.String(128), nullable=False, index=True),
|
||||
|
||||
# Mixin Columns
|
||||
db.Column('notes', db.String(), default=None),
|
||||
db.Column('created_at', db.DateTime(), default=None),
|
||||
db.Column('modified_at', db.DateTime(), default=None),
|
||||
db.Column('createdAt', db.DateTime(), default=None),
|
||||
db.Column('modifiedAt', db.DateTime(), default=None),
|
||||
)
|
||||
|
||||
#: Product Location Table
|
||||
@@ -51,6 +139,6 @@ product_locations = db.Table(
|
||||
|
||||
# Mixin Columns
|
||||
db.Column('notes', db.String(), default=None),
|
||||
db.Column('created_at', db.DateTime(), default=None),
|
||||
db.Column('modified_at', db.DateTime(), default=None),
|
||||
db.Column('createdAt', db.DateTime(), default=None),
|
||||
db.Column('modifiedAt', db.DateTime(), default=None),
|
||||
)
|
||||
|
||||
67
sigl/domain/models/accessKey.py
Normal file
67
sigl/domain/models/accessKey.py
Normal file
@@ -0,0 +1,67 @@
|
||||
"""Sigl Access Key Model.
|
||||
|
||||
Simple Grocery List (Sigl) | sigl.app
|
||||
Copyright (c) 2022 Asymworks, LLC. All Rights Reserved.
|
||||
"""
|
||||
|
||||
from dataclasses import dataclass
|
||||
from datetime import datetime
|
||||
|
||||
__all__ = ('AccessKey', 'AccessToken')
|
||||
|
||||
|
||||
@dataclass
|
||||
class AccessKey:
|
||||
"""Sigl Access Key Class.
|
||||
|
||||
The Access Key represents a client or group of clients authorized to
|
||||
interact with their specific Sigl shopping lists and products. Access Keys
|
||||
are generally not revoked or deleted and remain for the lifetime of the
|
||||
server.
|
||||
|
||||
When an Access Key is created, client details including the IP address and
|
||||
User Agent string are logged for future auditing purposes. The IP address
|
||||
may also be used for throttling.
|
||||
"""
|
||||
key: str = None
|
||||
clientId: str = None
|
||||
clientIP: str = None
|
||||
userAgent: str = None
|
||||
|
||||
# Key Suspension/Revocation
|
||||
suspended: bool = False
|
||||
revoked: bool = False
|
||||
|
||||
# Timestamps
|
||||
createdAt: datetime = datetime.utcnow()
|
||||
suspendedAt: datetime = None
|
||||
revokedAt: datetime = None
|
||||
restoredAt: datetime = None
|
||||
|
||||
|
||||
@dataclass
|
||||
class AccessToken:
|
||||
"""Sigl Access Token Class.
|
||||
|
||||
The Access Token represents authorization for a client (identified by an
|
||||
Access Key) to interact with the Sigl server. The token string, issue time,
|
||||
and expiry time must match the client-provided JWT for access to be granted.
|
||||
Access Tokens are short-lived tokens expected to expire or be revoked, and
|
||||
new tokens requested by the client.
|
||||
|
||||
When an Access Key is created, client details including the IP address and
|
||||
User Agent string are logged for future auditing purposes.
|
||||
"""
|
||||
token: str = None
|
||||
accessKey: str = None
|
||||
clientIP: str = None
|
||||
userAgent: str = None
|
||||
|
||||
# Key Suspension/Revocation
|
||||
expired: bool = False
|
||||
revoked: bool = False
|
||||
|
||||
# Timestamps
|
||||
issuedAt: datetime = datetime.utcnow()
|
||||
expiresAt: datetime = None
|
||||
revokedAt: datetime = None
|
||||
44
sigl/domain/models/list.py
Normal file
44
sigl/domain/models/list.py
Normal file
@@ -0,0 +1,44 @@
|
||||
"""Sigl Shopping List Domain Model.
|
||||
|
||||
Simple Grocery List (Sigl) | sigl.app
|
||||
Copyright (c) 2022 Asymworks, LLC. All Rights Reserved.
|
||||
"""
|
||||
|
||||
from dataclasses import dataclass
|
||||
from typing import List
|
||||
|
||||
from .mixins import NotesMixin, TimestampMixin
|
||||
from .product import Product
|
||||
|
||||
__all__ = ('ShoppingList', 'ListEntry')
|
||||
|
||||
|
||||
@dataclass
|
||||
class ListEntry(NotesMixin, TimestampMixin):
|
||||
"""Information about a Product on a Shopping List.
|
||||
|
||||
This class contains information about a Product that is on a shopping
|
||||
list, including the quantity to be purchased, notes about the entry, and
|
||||
whether the Product has been crossed off the list or deleted.
|
||||
"""
|
||||
quantity: str = None
|
||||
crossedOff: bool = False
|
||||
deleted: bool = False
|
||||
|
||||
# Relationship Fields
|
||||
shoppingList: 'ShoppingList' = None
|
||||
product: Product = None
|
||||
|
||||
|
||||
@dataclass
|
||||
class ShoppingList(NotesMixin, TimestampMixin):
|
||||
"""Top-Level Shopping List.
|
||||
|
||||
Contains a collection of `ListEntry` items which are intended to be
|
||||
purchased.
|
||||
"""
|
||||
accessKey: str = None
|
||||
name: str = None
|
||||
|
||||
# Populated by ORM
|
||||
# entries: List[ListEntry] = None
|
||||
@@ -34,8 +34,8 @@ class TimestampMixin:
|
||||
Adds a ``modified_at`` column to the database object with type
|
||||
:py:class:`datetime.datetime`
|
||||
'''
|
||||
created_at: datetime = datetime.utcnow()
|
||||
modified_at: datetime = None
|
||||
createdAt: datetime = datetime.utcnow()
|
||||
modifiedAt: datetime = None
|
||||
|
||||
def set_modified_at(self):
|
||||
self.modified_at = datetime.utcnow()
|
||||
self.modifiedAt = datetime.utcnow()
|
||||
|
||||
@@ -23,6 +23,9 @@ class Product(NotesMixin, TimestampMixin):
|
||||
name: str = None
|
||||
category: str = None
|
||||
|
||||
# Access Control
|
||||
accessKey: str = None
|
||||
|
||||
# Populated by ORM
|
||||
# locations: List['ProductLocation'] = []
|
||||
|
||||
|
||||
Reference in New Issue
Block a user