User and Client lists show the last logins.

This commit is contained in:
Amritanshu Agrawal 2020-12-05 09:03:11 +05:30
parent c2e8c0382a
commit 9aeb71d566
13 changed files with 102 additions and 61 deletions

View File

@ -1,11 +1,22 @@
from __future__ import annotations
import random import random
import string import string
import uuid import uuid
from datetime import datetime from datetime import datetime
from hashlib import md5 from hashlib import md5
from typing import List, Optional
from sqlalchemy import Boolean, Column, DateTime, Integer, Unicode, UniqueConstraint from sqlalchemy import (
Boolean,
Column,
DateTime,
Integer,
Unicode,
UniqueConstraint,
desc,
)
from sqlalchemy.dialects.postgresql import UUID from sqlalchemy.dialects.postgresql import UUID
from sqlalchemy.orm import Session, relationship, synonym from sqlalchemy.orm import Session, relationship, synonym
from sqlalchemy.schema import ForeignKey, Table from sqlalchemy.schema import ForeignKey, Table
@ -17,6 +28,26 @@ def encrypt(val):
return md5(val.encode("utf-8") + "Salt".encode("utf-8")).hexdigest() return md5(val.encode("utf-8") + "Salt".encode("utf-8")).hexdigest()
class LoginHistory(Base):
__tablename__ = "auth_login_history"
__table_args__ = (UniqueConstraint("user_id", "client_id", "date"),)
id = Column("login_history_id", UUID(as_uuid=True), primary_key=True, default=uuid.uuid4)
user_id = Column("user_id", UUID(as_uuid=True), ForeignKey("auth_users.id"), nullable=False)
client_id = Column(
"client_id",
UUID(as_uuid=True),
ForeignKey("auth_clients.client_id"),
nullable=False,
)
date = Column("date", DateTime(timezone=True), nullable=False)
def __init__(self, user_id=None, client_id=None, date=None, id_=None):
self.user_id = user_id
self.client_id = client_id
self.date = datetime.utcnow() if date is None else date
self.id = id_
class Client(Base): class Client(Base):
__tablename__ = "auth_clients" __tablename__ = "auth_clients"
@ -27,7 +58,7 @@ class Client(Base):
otp = Column("otp", Integer) otp = Column("otp", Integer)
creation_date = Column("creation_date", DateTime(timezone=True), nullable=False) creation_date = Column("creation_date", DateTime(timezone=True), nullable=False)
login_history = relationship("LoginHistory", backref="client") login_history: List[LoginHistory] = relationship("LoginHistory", order_by=desc(LoginHistory.date), backref="client")
def __init__( def __init__(
self, self,
@ -80,8 +111,8 @@ class User(Base):
_password = Column("password", Unicode(60)) _password = Column("password", Unicode(60))
locked_out = Column("disabled", Boolean) locked_out = Column("disabled", Boolean)
roles = relationship("Role", secondary=user_role) roles: List[Role] = relationship("Role", secondary=user_role)
login_history = relationship("LoginHistory", backref="user") login_history: List[LoginHistory] = relationship("LoginHistory", order_by=desc(LoginHistory.date), backref="user")
def _get_password(self): def _get_password(self):
return self._password return self._password
@ -103,7 +134,7 @@ class User(Base):
self.id = id_ self.id = id_
@classmethod @classmethod
def auth(cls, name, password, db) -> any: def auth(cls, name: str, password: str, db: Session) -> Optional[User]:
if password is None: if password is None:
return None return None
user = db.query(User).filter(User.name.ilike(name)).first() user = db.query(User).filter(User.name.ilike(name)).first()
@ -115,26 +146,6 @@ class User(Base):
return user return user
class LoginHistory(Base):
__tablename__ = "auth_login_history"
__table_args__ = (UniqueConstraint("user_id", "client_id", "date"),)
id = Column("login_history_id", UUID(as_uuid=True), primary_key=True, default=uuid.uuid4)
user_id = Column("user_id", UUID(as_uuid=True), ForeignKey("auth_users.id"), nullable=False)
client_id = Column(
"client_id",
UUID(as_uuid=True),
ForeignKey("auth_clients.client_id"),
nullable=False,
)
date = Column("date", DateTime(timezone=True), nullable=False)
def __init__(self, user_id=None, client_id=None, date=None, id_=None):
self.user_id = user_id
self.client_id = client_id
self.date = datetime.utcnow() if date is None else date
self.id = id_
class Role(Base): class Role(Base):
__tablename__ = "auth_roles" __tablename__ = "auth_roles"
@ -152,7 +163,7 @@ class Permission(Base):
id = Column("id", UUID(as_uuid=True), primary_key=True, default=uuid.uuid4) id = Column("id", UUID(as_uuid=True), primary_key=True, default=uuid.uuid4)
name = Column("name", Unicode(255), unique=True) name = Column("name", Unicode(255), unique=True)
roles = relationship("Role", secondary=role_permission, backref="permissions") roles: List[Role] = relationship("Role", secondary=role_permission, backref="permissions")
def __init__(self, name=None, id_=None): def __init__(self, name=None, id_=None):
self.name = name self.name = name

View File

@ -1,5 +1,7 @@
import uuid import uuid
from typing import List
import brewman.schemas.client as schemas import brewman.schemas.client as schemas
from fastapi import APIRouter, Depends, HTTPException, Security, status from fastapi import APIRouter, Depends, HTTPException, Security, status
@ -74,28 +76,25 @@ def delete(
raise raise
@router.get("/list") @router.get("/list", response_model=List[schemas.ClientList])
async def show_list( async def show_list(
db: Session = Depends(get_db), db: Session = Depends(get_db),
user: UserToken = Security(get_user, scopes=["clients"]), user: UserToken = Security(get_user, scopes=["clients"]),
): ) -> List[schemas.ClientList]:
list_ = db.query(Client).order_by(Client.name).all() list_ = db.query(Client).order_by(Client.name).all()
clients = [] clients = []
for item in list_: for item in list_:
last_login = (
db.query(LoginHistory).filter(LoginHistory.client_id == item.id).order_by(desc(LoginHistory.date)).first()
)
last_login = "Never" if last_login is None else last_login.date.strftime("%d-%b-%Y %H:%M")
clients.append( clients.append(
{ schemas.ClientList(
"id": item.id, id=item.id,
"code": item.code, code=item.code,
"name": item.name, name=item.name,
"enabled": item.enabled, enabled=item.enabled,
"otp": item.otp, otp=item.otp,
"creationDate": item.creation_date.strftime("%d-%b-%Y %H:%M"), creationDate=item.creation_date,
"lastLogin": last_login, lastUser=item.login_history[0].user.name if len(item.login_history) else "None",
} lastDate=item.login_history[0].date if len(item.login_history) else None,
)
) )
return clients return clients

View File

@ -164,6 +164,8 @@ async def show_list(
name=item.name, name=item.name,
lockedOut=item.locked_out, lockedOut=item.locked_out,
roles=[p.name for p in sorted(item.roles, key=lambda p: p.name)], roles=[p.name for p in sorted(item.roles, key=lambda p: p.name)],
lastDevice=item.login_history[0].client.name if len(item.login_history) else "None",
lastDate=item.login_history[0].date if len(item.login_history) else None,
) )
for item in db.query(User).order_by(User.name).all() for item in db.query(User).order_by(User.name).all()
] ]

View File

@ -3,6 +3,7 @@ import uuid
from datetime import datetime from datetime import datetime
from typing import Optional from typing import Optional
from brewman.schemas import to_camel
from pydantic.main import BaseModel from pydantic.main import BaseModel
@ -16,3 +17,13 @@ class Client(ClientIn):
id_: uuid.UUID id_: uuid.UUID
code: int code: int
creation_date: datetime creation_date: datetime
class ClientList(Client):
last_user: str
last_date: Optional[datetime]
class Config:
anystr_strip_whitespace = True
alias_generator = to_camel
json_encoders = {datetime: lambda v: v.strftime("%d-%b-%Y %H:%M")}

View File

@ -19,7 +19,7 @@ class ProductLink(BaseModel):
class ProductIn(BaseModel): class ProductIn(BaseModel):
name: str = Field(..., min_length=1) name: str = Field(..., min_length=1)
units: str units: str
fraction: Decimal = Field(le=1, default=1) fraction: Decimal = Field(ge=1, default=1)
fraction_units: str fraction_units: str
product_yield: Decimal = Field(gt=0, le=1, default=1) product_yield: Decimal = Field(gt=0, le=1, default=1)
product_group: ProductGroupLink = Field(...) product_group: ProductGroupLink = Field(...)

View File

@ -1,6 +1,7 @@
import uuid import uuid
from typing import List from datetime import datetime
from typing import List, Optional
from brewman.schemas import to_camel from brewman.schemas import to_camel
from brewman.schemas.role import RoleItem from brewman.schemas.role import RoleItem
@ -31,10 +32,13 @@ class UserList(BaseModel):
id_: uuid.UUID id_: uuid.UUID
name: str name: str
roles: List[str] roles: List[str]
last_device: str
last_date: Optional[datetime]
class Config: class Config:
fields = {"id_": "id"}
anystr_strip_whitespace = True anystr_strip_whitespace = True
alias_generator = to_camel
json_encoders = {datetime: lambda v: v.strftime("%d-%b-%Y %H:%M")}
class UserToken(BaseModel): class UserToken(BaseModel):

View File

@ -33,13 +33,15 @@
<!-- Created Column --> <!-- Created Column -->
<ng-container matColumnDef="created"> <ng-container matColumnDef="created">
<mat-header-cell *matHeaderCellDef mat-sort-header>Created</mat-header-cell> <mat-header-cell *matHeaderCellDef mat-sort-header>Created</mat-header-cell>
<mat-cell *matCellDef="let row">{{ row.created | localTime }}</mat-cell> <mat-cell *matCellDef="let row">{{ row.creationDate | localTime }}</mat-cell>
</ng-container> </ng-container>
<!-- Last Login Column --> <!-- Last Login Column -->
<ng-container matColumnDef="lastLogin"> <ng-container matColumnDef="last">
<mat-header-cell *matHeaderCellDef mat-sort-header>Last Login</mat-header-cell> <mat-header-cell *matHeaderCellDef mat-sort-header>Last Login</mat-header-cell>
<mat-cell *matCellDef="let row">{{ row.lastLogin | localTime }}</mat-cell> <mat-cell *matCellDef="let row"
>{{ row.lastUser }} @ {{ row.lastDate ? (row.lastDate | localTime) : 'Never' }}</mat-cell
>
</ng-container> </ng-container>
<mat-header-row *matHeaderRowDef="displayedColumns"></mat-header-row> <mat-header-row *matHeaderRowDef="displayedColumns"></mat-header-row>

View File

@ -18,7 +18,7 @@ export class ClientListComponent implements OnInit {
list: Client[] = []; list: Client[] = [];
dataSource: ClientListDataSource = new ClientListDataSource(this.list); dataSource: ClientListDataSource = new ClientListDataSource(this.list);
/** Columns displayed in the table. Columns IDs can be added, removed, or reordered. */ /** Columns displayed in the table. Columns IDs can be added, removed, or reordered. */
displayedColumns = ['code', 'name', 'enabled', 'otp', 'created', 'lastLogin']; displayedColumns = ['code', 'name', 'enabled', 'otp', 'created', 'last'];
constructor(private route: ActivatedRoute) {} constructor(private route: ActivatedRoute) {}

View File

@ -3,18 +3,18 @@ export class Client {
name: string; name: string;
code: number; code: number;
enabled: boolean; enabled: boolean;
otp: number; otp?: number;
creationDate: string; creationDate: string;
lastLogin: string; lastUser: string;
lastDate?: string;
public constructor(init?: Partial<Client>) { public constructor(init?: Partial<Client>) {
this.id = ''; this.id = '';
this.name = ''; this.name = '';
this.code = 0; this.code = 0;
this.enabled = true; this.enabled = true;
this.otp = 0;
this.creationDate = ''; this.creationDate = '';
this.lastLogin = ''; this.lastUser = '';
Object.assign(this, init); Object.assign(this, init);
} }
} }

View File

@ -1,9 +1,9 @@
export class UserGroup { export class UserRole {
id: string | undefined; id: string | undefined;
name: string; name: string;
enabled: boolean; enabled: boolean;
public constructor(init?: Partial<UserGroup>) { public constructor(init?: Partial<UserRole>) {
this.name = ''; this.name = '';
this.enabled = true; this.enabled = true;
Object.assign(this, init); Object.assign(this, init);

View File

@ -1,16 +1,18 @@
import { UserGroup } from './user-group'; import { UserRole } from './user-role';
export class User { export class User {
id: string | undefined; id: string | undefined;
name: string; name: string;
password: string; password: string;
lockedOut: boolean; lockedOut: boolean;
roles: UserGroup[]; roles: UserRole[];
perms: string[]; perms: string[];
isAuthenticated: boolean; isAuthenticated: boolean;
access_token?: string; access_token?: string;
exp: number; exp: number;
ver: string; ver: string;
lastDevice: string;
lastDate?: string;
public constructor(init?: Partial<User>) { public constructor(init?: Partial<User>) {
this.name = ''; this.name = '';
@ -21,6 +23,7 @@ export class User {
this.isAuthenticated = false; this.isAuthenticated = false;
this.exp = 0; this.exp = 0;
this.ver = ''; this.ver = '';
this.lastDevice = '';
Object.assign(this, init); Object.assign(this, init);
} }
} }

View File

@ -22,16 +22,25 @@
<mat-cell *matCellDef="let row">{{ row.lockedOut }}</mat-cell> <mat-cell *matCellDef="let row">{{ row.lockedOut }}</mat-cell>
</ng-container> </ng-container>
<!-- Groups Column --> <!-- Roles Column -->
<ng-container matColumnDef="groups"> <ng-container matColumnDef="roles">
<mat-header-cell *matHeaderCellDef mat-sort-header>Groups</mat-header-cell> <mat-header-cell *matHeaderCellDef>Roles</mat-header-cell>
<mat-cell *matCellDef="let row"> <mat-cell *matCellDef="let row">
<ul> <ul>
<li *ngFor="let group of row.groups">{{ group }}</li> <li *ngFor="let role of row.roles">{{ role }}</li>
</ul> </ul>
</mat-cell> </mat-cell>
</ng-container> </ng-container>
<!-- Last Login Column -->
<ng-container matColumnDef="last">
<mat-header-cell *matHeaderCellDef>Last Login</mat-header-cell>
<mat-cell *matCellDef="let row"
>{{ row.lastDevice }} @
{{ row.lastDate ? (row.lastDate | localTime) : 'Never' }}</mat-cell
>
</ng-container>
<mat-header-row *matHeaderRowDef="displayedColumns"></mat-header-row> <mat-header-row *matHeaderRowDef="displayedColumns"></mat-header-row>
<mat-row *matRowDef="let row; columns: displayedColumns"></mat-row> <mat-row *matRowDef="let row; columns: displayedColumns"></mat-row>
</mat-table> </mat-table>

View File

@ -18,7 +18,7 @@ export class UserListComponent implements OnInit {
list: User[] = []; list: User[] = [];
dataSource: UserListDataSource = new UserListDataSource(this.list); dataSource: UserListDataSource = new UserListDataSource(this.list);
/** Columns displayed in the table. Columns IDs can be added, removed, or reordered. */ /** Columns displayed in the table. Columns IDs can be added, removed, or reordered. */
displayedColumns = ['name', 'lockedOut', 'groups']; displayedColumns = ['name', 'lockedOut', 'roles', 'last'];
constructor(private route: ActivatedRoute) {} constructor(private route: ActivatedRoute) {}