From 013fce2e9669c0acb0f2e5c728a430cd006b2b82 Mon Sep 17 00:00:00 2001 From: tanshu Date: Sat, 30 May 2020 23:39:02 +0530 Subject: [PATCH] Settings working now. Everything working now. Time for docker and beta test --- .env | 15 ++-- alembic.ini | 82 ------------------- alembic/env.py | 32 ++------ alembic/script.py.mako | 24 ------ brewman/config.py | 12 --- brewman/core/config.py | 44 ++++------ brewman/core/security.py | 13 +-- brewman/db/session.py | 3 + brewman/main.py | 4 +- brewman/routers/login.py | 6 +- .../account-list/account-list-datasource.ts | 1 - overlord/src/app/auth/auth.service.ts | 21 ++--- .../product-list/product-list-datasource.ts | 20 +++-- overlord/src/environments/environment.prod.ts | 3 +- overlord/src/environments/environment.ts | 3 +- requirements.txt | 4 +- 16 files changed, 73 insertions(+), 214 deletions(-) delete mode 100644 alembic/script.py.mako delete mode 100644 brewman/config.py diff --git a/.env b/.env index cfc6b753..9793fd97 100644 --- a/.env +++ b/.env @@ -1,13 +1,18 @@ HOST=0.0.0.0 PORT=9998 -LOG_LEVEL=info +LOG_LEVEL=WARN DEBUG=true -DB_URL=postgresql://postgres:123456@localhost:5432/hops +SQLALCHEMY_DATABASE_URI=postgresql://postgres:123456@localhost:5432/hops MODULE_NAME=brewman.main -SERVER_NAME=localhost -SERVER_HOST=localhost -PROJECT_NAME=bifrost +PROJECT_NAME=brewman POSTGRES_SERVER=localhost POSTGRES_USER=postgres POSTGRES_PASSWORD=123456 POSTGRES_DB=hops + +SECRET_KEY=8546a61262dab7c05ccf2e26abe30bc10966904df6dfd29259ea85dd0844a8e7 +ALGORITHM=HS256 +JWT_TOKEN_EXPIRE_MINUTES=30 + +ALEMBIC_LOG_LEVEL=INFO +ALEMBIC_SQLALCHEMY_LOG_LEVEL=WARN diff --git a/alembic.ini b/alembic.ini index 3774c9cc..defbffbb 100644 --- a/alembic.ini +++ b/alembic.ini @@ -1,85 +1,3 @@ -# A generic, single database configuration. - [alembic] # path to migration scripts script_location = alembic - -# template used to generate migration files -# file_template = %%(rev)s_%%(slug)s - -# timezone to use when rendering the date -# within the migration file as well as the filename. -# string value is passed to dateutil.tz.gettz() -# leave blank for localtime -# timezone = - -# max length of characters to apply to the -# "slug" field -# truncate_slug_length = 40 - -# set to 'true' to run the environment during -# the 'revision' command, regardless of autogenerate -# revision_environment = false - -# set to 'true' to allow .pyc and .pyo files without -# a source .py file to be detected as revisions in the -# versions/ directory -# sourceless = false - -# version location specification; this defaults -# to alembic/versions. When using multiple version -# directories, initial revisions must be specified with --version-path -# version_locations = %(here)s/bar %(here)s/bat alembic/versions - -# the output encoding used when revision files -# are written from script.py.mako -# output_encoding = utf-8 - -sqlalchemy.url = postgresql://postgres:123456@localhost:5432/hops - - -[post_write_hooks] -# post_write_hooks defines scripts or Python functions that are run -# on newly generated revision scripts. See the documentation for further -# detail and examples - -# format using "black" - use the console_scripts runner, against the "black" entrypoint -# hooks=black -# black.type=console_scripts -# black.entrypoint=black -# black.options=-l 79 - -# Logging configuration -[loggers] -keys = root,sqlalchemy,alembic - -[handlers] -keys = console - -[formatters] -keys = generic - -[logger_root] -level = WARN -handlers = console -qualname = - -[logger_sqlalchemy] -level = WARN -handlers = -qualname = sqlalchemy.engine - -[logger_alembic] -level = INFO -handlers = -qualname = alembic - -[handler_console] -class = StreamHandler -args = (sys.stderr,) -level = NOTSET -formatter = generic - -[formatter_generic] -format = %(levelname)-5.5s [%(name)s] %(message)s -datefmt = %H:%M:%S diff --git a/alembic/env.py b/alembic/env.py index 81608ff8..e50938cb 100644 --- a/alembic/env.py +++ b/alembic/env.py @@ -1,22 +1,20 @@ -import os -from logging.config import fileConfig +import logging -from sqlalchemy import engine_from_config +from sqlalchemy import create_engine from sqlalchemy import pool from alembic import context +from brewman.core.config import settings -# this is the Alembic Config object, which provides -# access to the values within the .ini file in use. -config = context.config +logging.basicConfig() +logging.getLogger('sqlalchemy.engine').setLevel(settings.ALEMBIC_LOG_LEVEL) +logging.getLogger('alembic').setLevel(settings.ALEMBIC_LOG_LEVEL) # Interpret the config file for Python logging. # This line sets up loggers basically. -fileConfig(config.config_file_name) from brewman.models.auth import User # noqa target_metadata = User.metadata -print(target_metadata) # other values from the config, defined by the needs of env.py, # can be acquired: @@ -24,14 +22,6 @@ print(target_metadata) # ... etc. -def get_url(): - user = os.getenv("POSTGRES_USER", "postgres") - password = os.getenv("POSTGRES_PASSWORD", "123456") - server = os.getenv("POSTGRES_SERVER", "localhost") - db = os.getenv("POSTGRES_DB", "hops") - return f"postgresql://{user}:{password}@{server}/{db}" - - def run_migrations_offline(): """Run migrations in 'offline' mode. @@ -44,7 +34,7 @@ def run_migrations_offline(): script output. """ - url = get_url() + url = settings.SQLALCHEMY_DATABASE_URI context.configure( url=url, target_metadata=target_metadata, @@ -64,13 +54,7 @@ def run_migrations_online(): and associate a connection with the context. """ - configuration = config.get_section(config.config_ini_section) - configuration["sqlalchemy.url"] = get_url() - connectable = engine_from_config( - config.get_section(config.config_ini_section), - prefix="sqlalchemy.", - poolclass=pool.NullPool, - ) + connectable = create_engine(settings.SQLALCHEMY_DATABASE_URI, poolclass=pool.NullPool, ) with connectable.connect() as connection: context.configure( diff --git a/alembic/script.py.mako b/alembic/script.py.mako deleted file mode 100644 index 2c015630..00000000 --- a/alembic/script.py.mako +++ /dev/null @@ -1,24 +0,0 @@ -"""${message} - -Revision ID: ${up_revision} -Revises: ${down_revision | comma,n} -Create Date: ${create_date} - -""" -from alembic import op -import sqlalchemy as sa -${imports if imports else ""} - -# revision identifiers, used by Alembic. -revision = ${repr(up_revision)} -down_revision = ${repr(down_revision)} -branch_labels = ${repr(branch_labels)} -depends_on = ${repr(depends_on)} - - -def upgrade(): - ${upgrades if upgrades else "pass"} - - -def downgrade(): - ${downgrades if downgrades else "pass"} diff --git a/brewman/config.py b/brewman/config.py deleted file mode 100644 index 2034b3fd..00000000 --- a/brewman/config.py +++ /dev/null @@ -1,12 +0,0 @@ -from environs import Env - -env = Env() -env.read_env() # read .env file, if it exists - - -class Settings: - debug: bool = env("DEBUG") - host: str = env("HOST") - port: int = env.int("PORT") - db_url: str = env("DB_URL") - log_level: str = env("LOG_LEVEL") diff --git a/brewman/core/config.py b/brewman/core/config.py index 6523e987..17c9d131 100644 --- a/brewman/core/config.py +++ b/brewman/core/config.py @@ -1,36 +1,24 @@ +from dotenv import load_dotenv import secrets -from typing import Any, Dict, List, Optional, Union +from typing import Any, Dict, Optional -from pydantic import AnyHttpUrl, BaseSettings, PostgresDsn, validator +from pydantic import BaseSettings, PostgresDsn, validator class Settings(BaseSettings): - API_V1_STR: str = "/api/v1" + # openssl rand -hex 32 SECRET_KEY: str = secrets.token_urlsafe(32) - # 60 minutes * 24 hours * 8 days = 8 days - ACCESS_TOKEN_EXPIRE_MINUTES: int = 60 * 24 * 8 - SERVER_NAME: str = "localhost" - SERVER_HOST: AnyHttpUrl = "http://localhost:9998" - # BACKEND_CORS_ORIGINS is a JSON-formatted list of origins - # e.g: '["http://localhost", "http://localhost:4200", "http://localhost:3000", \ - # "http://localhost:8080", "http://local.dockertoolbox.tiangolo.com"]' - BACKEND_CORS_ORIGINS: List[AnyHttpUrl] = [] - - @validator("BACKEND_CORS_ORIGINS", pre=True) - def assemble_cors_origins(cls, v: Union[str, List[str]]) -> Union[List[str], str]: - if isinstance(v, str) and not v.startswith("["): - return [i.strip() for i in v.split(",")] - elif isinstance(v, (list, str)): - return v - raise ValueError(v) - - PROJECT_NAME: str = "bifrost" - - POSTGRES_SERVER: str = "localhost" + ALGORITHM: str = "HS256" + JWT_TOKEN_EXPIRE_MINUTES: int = 30 + HOST: str = "0.0.0.0" + PORT: int = 80 + DEBUG: bool = False + LOG_LEVEL: str = "NOTSET" + POSTGRES_SERVER: str = "" POSTGRES_USER: str = "postgres" - POSTGRES_PASSWORD: str = "123456" - POSTGRES_DB: str = "hops" - SQLALCHEMY_DATABASE_URI: Optional[PostgresDsn] = None + POSTGRES_PASSWORD: str = "" + POSTGRES_DB: str = "" + SQLALCHEMY_DATABASE_URI: Optional[str] = None @validator("SQLALCHEMY_DATABASE_URI", pre=True) def assemble_db_connection(cls, v: Optional[str], values: Dict[str, Any]) -> Any: @@ -44,9 +32,13 @@ class Settings(BaseSettings): path=f"/{values.get('POSTGRES_DB') or ''}", ) + ALEMBIC_LOG_LEVEL: str = "INFO" + ALEMBIC_SQLALCHEMY_LOG_LEVEL: str = "WARN" + class Config: case_sensitive = True env_file = '.env' +load_dotenv() settings = Settings() diff --git a/brewman/core/security.py b/brewman/core/security.py index d353ebe5..1410354e 100644 --- a/brewman/core/security.py +++ b/brewman/core/security.py @@ -1,6 +1,6 @@ import uuid from datetime import datetime, timedelta -from typing import List, Union, Optional +from typing import List, Optional from jwt import PyJWTError from fastapi import Depends, HTTPException, status, Security @@ -10,19 +10,14 @@ from sqlalchemy.orm import Session from jose import jwt from jose.exceptions import ExpiredSignatureError +from brewman.core.config import settings from brewman.models.auth import User as UserModel, Client from ..db.session import SessionLocal # to get a string like this run: -# openssl rand -hex 32 from ..schemas.auth import UserToken -SECRET_KEY = "8546a61262dab7c05ccf2e26abe30bc10966904df6dfd29259ea85dd0844a8e7" -ALGORITHM = "HS256" -ACCESS_TOKEN_EXPIRE_MINUTES = 30 - - oauth2_scheme = OAuth2PasswordBearer(tokenUrl="/token", scopes={}) @@ -58,7 +53,7 @@ def create_access_token(*, data: dict, expires_delta: timedelta = None): else: expire = datetime.utcnow() + timedelta(minutes=15) to_encode.update({"exp": expire}) - encoded_jwt = jwt.encode(to_encode, SECRET_KEY, algorithm=ALGORITHM) + encoded_jwt = jwt.encode(to_encode, settings.SECRET_KEY, algorithm=settings.ALGORITHM) return encoded_jwt @@ -116,7 +111,7 @@ async def get_current_user( headers={"WWW-Authenticate": authenticate_value}, ) try: - payload = jwt.decode(token, SECRET_KEY, algorithms=[ALGORITHM]) + payload = jwt.decode(token, settings.SECRET_KEY, algorithms=[settings.ALGORITHM]) username: str = payload.get("sub") if username is None: raise credentials_exception diff --git a/brewman/db/session.py b/brewman/db/session.py index 07e89e47..85d781f3 100644 --- a/brewman/db/session.py +++ b/brewman/db/session.py @@ -1,7 +1,10 @@ +import logging from sqlalchemy import create_engine from sqlalchemy.orm import sessionmaker from brewman.core.config import settings +logging.basicConfig() +logging.getLogger('sqlalchemy.engine').setLevel(settings.LOG_LEVEL) engine = create_engine(settings.SQLALCHEMY_DATABASE_URI, pool_pre_ping=True) SessionLocal = sessionmaker(autocommit=False, autoflush=False, bind=engine) diff --git a/brewman/main.py b/brewman/main.py index fdfce19a..1b8f2f60 100644 --- a/brewman/main.py +++ b/brewman/main.py @@ -53,7 +53,7 @@ from .routers.reports import ( ) from .db.base_class import Base -from .config import Settings +from .core.config import settings from .db.session import engine Base.metadata.create_all(bind=engine) @@ -149,4 +149,4 @@ app.include_router(rebase.router, prefix="/api/rebase", tags=["management"]) def init(): - uvicorn.run(app, host=Settings.host, port=Settings.port) + uvicorn.run(app, host=settings.HOST, port=settings.PORT) diff --git a/brewman/routers/login.py b/brewman/routers/login.py index 67f26537..ed71bea8 100644 --- a/brewman/routers/login.py +++ b/brewman/routers/login.py @@ -7,9 +7,9 @@ from sqlalchemy.orm import Session from ..core.security import ( Token, authenticate_user, - ACCESS_TOKEN_EXPIRE_MINUTES, create_access_token, get_current_active_user, client_allowed, ) +from brewman.core.config import settings from ..db.session import SessionLocal from ..schemas.auth import UserToken @@ -52,7 +52,7 @@ async def login_for_access_token( ) not_allowed_response.set_cookie(key="client_id", value=str(c_id), max_age=10 * 365 * 24 * 60 * 60) return not_allowed_response - access_token_expires = timedelta(minutes=ACCESS_TOKEN_EXPIRE_MINUTES) + access_token_expires = timedelta(minutes=settings.JWT_TOKEN_EXPIRE_MINUTES) access_token = create_access_token( data={ "sub": user.name, @@ -78,7 +78,7 @@ async def login_for_access_token( async def refresh_token( user: UserToken = Security(get_current_active_user) ): - access_token_expires = timedelta(minutes=ACCESS_TOKEN_EXPIRE_MINUTES) + access_token_expires = timedelta(minutes=settings.JWT_TOKEN_EXPIRE_MINUTES) access_token = create_access_token( data={ "sub": user.name, diff --git a/overlord/src/app/account/account-list/account-list-datasource.ts b/overlord/src/app/account/account-list/account-list-datasource.ts index 98acb902..77fe3e61 100644 --- a/overlord/src/app/account/account-list/account-list-datasource.ts +++ b/overlord/src/app/account/account-list/account-list-datasource.ts @@ -4,7 +4,6 @@ import { MatSort } from '@angular/material/sort'; import { map, tap } from 'rxjs/operators'; import { merge, Observable, of as observableOf } from 'rxjs'; import { Account } from '../../core/account'; -import {Employee} from "../../employee/employee"; export class AccountListDataSource extends DataSource { diff --git a/overlord/src/app/auth/auth.service.ts b/overlord/src/app/auth/auth.service.ts index f95d1aed..e0f6e7d9 100644 --- a/overlord/src/app/auth/auth.service.ts +++ b/overlord/src/app/auth/auth.service.ts @@ -1,14 +1,14 @@ -import {Injectable} from '@angular/core'; -import {HttpClient} from '@angular/common/http'; -import {BehaviorSubject, Observable} from 'rxjs'; -import {map} from 'rxjs/operators'; +import { Injectable } from '@angular/core'; +import { HttpClient } from '@angular/common/http'; +import { BehaviorSubject, Observable } from 'rxjs'; +import { map } from 'rxjs/operators'; -import {User} from '../core/user'; +import { User } from '../core/user'; +import { environment } from '../../environments/environment'; const loginUrl = '/token'; const refreshUrl = '/refresh'; const JWT_USER = 'JWT_USER'; -const ACCESS_TOKEN_REFRESH_MINUTES = 10; // refresh token 10 minutes before expiry @Injectable({providedIn: 'root'}) export class AuthService { @@ -60,10 +60,7 @@ export class AuthService { } needsRefreshing(): boolean { - - // We use this line to debug token refreshing - // console.log("\n", Date.now(), ": Date.now()\n", this.user.exp * 1000, ": user.exp\n",(this.user.exp - (ACCESS_TOKEN_REFRESH_MINUTES * 60)) * 1000, ": comp"); - return Date.now() > (this.user.exp - (ACCESS_TOKEN_REFRESH_MINUTES * 60)) * 1000; + return Date.now() > (this.user.exp - (environment.ACCESS_TOKEN_REFRESH_MINUTES * 60)) * 1000; } expired(): boolean { @@ -76,10 +73,6 @@ export class AuthService { this.currentUserSubject.next(null); } - getJwtToken() { - return JSON.parse(localStorage.getItem(JWT_USER)).access_token; - } - refreshToken() { return this.http.post(refreshUrl, {}) .pipe(map(u => u.access_token)) diff --git a/overlord/src/app/product/product-list/product-list-datasource.ts b/overlord/src/app/product/product-list/product-list-datasource.ts index f4147a79..043143b5 100644 --- a/overlord/src/app/product/product-list/product-list-datasource.ts +++ b/overlord/src/app/product/product-list/product-list-datasource.ts @@ -1,13 +1,12 @@ -import {DataSource} from '@angular/cdk/collections'; +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 {merge, Observable, of as observableOf} from 'rxjs'; -import {Product} from '../../core/product'; +import { map, tap } from 'rxjs/operators'; +import { merge, Observable, of as observableOf } from 'rxjs'; +import { Product } from '../../core/product'; export class ProductListDataSource extends DataSource { - private dataObservable: Observable; private filterValue: string; constructor(private paginator: MatPaginator, private sort: MatSort, private filter: Observable, public data: Product[]) { @@ -18,9 +17,8 @@ export class ProductListDataSource extends DataSource { } connect(): Observable { - this.dataObservable = observableOf(this.data); const dataMutations = [ - this.dataObservable, + observableOf(this.data), this.filter, this.paginator.page, this.sort.sortChange @@ -28,9 +26,13 @@ export class ProductListDataSource extends DataSource { return merge(...dataMutations).pipe( map((x: any) => { - return this.getPagedData(this.getSortedData(this.getFilteredData([...this.data]))); + return this.getFilteredData([...this.data]); }), tap((x: Product[]) => this.paginator.length = x.length) + ).pipe( + map((x: any) => { + return this.getPagedData(this.getSortedData(x)); + }) ); } @@ -66,6 +68,8 @@ export class ProductListDataSource extends DataSource { switch (this.sort.active) { case 'name': return compare(a.name, b.name, isAsc); + case 'productGroup': + return compare(a.productGroup, b.productGroup, isAsc); case 'id': return compare(+a.id, +b.id, isAsc); default: diff --git a/overlord/src/environments/environment.prod.ts b/overlord/src/environments/environment.prod.ts index 3612073b..8860a3d7 100644 --- a/overlord/src/environments/environment.prod.ts +++ b/overlord/src/environments/environment.prod.ts @@ -1,3 +1,4 @@ export const environment = { - production: true + production: true, + ACCESS_TOKEN_REFRESH_MINUTES: 10 // refresh token 10 minutes before expiry }; diff --git a/overlord/src/environments/environment.ts b/overlord/src/environments/environment.ts index 7b4f817a..07981959 100644 --- a/overlord/src/environments/environment.ts +++ b/overlord/src/environments/environment.ts @@ -3,7 +3,8 @@ // The list of file replacements can be found in `angular.json`. export const environment = { - production: false + production: false, + ACCESS_TOKEN_REFRESH_MINUTES: 10 // refresh token 10 minutes before expiry }; /* diff --git a/requirements.txt b/requirements.txt index 52967007..fe22da04 100644 --- a/requirements.txt +++ b/requirements.txt @@ -2,7 +2,6 @@ setuptools wheel uvicorn fastapi -environs python-jose[cryptography] passlib[bcrypt] psycopg2-binary @@ -11,5 +10,6 @@ python-multipart pyjwt alembic itsdangerous -pydantic +python-dotenv +pydantic[dotenv] starlette