-
Notifications
You must be signed in to change notification settings - Fork 52
Feature Panel - Backend #283
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Merged
yammesicka
merged 54 commits into
PythonFreeCourse:develop
from
LiranCaduri:feature/feature-panel
Feb 25, 2021
Merged
Changes from 30 commits
Commits
Show all changes
54 commits
Select commit
Hold shift + click to select a range
d09b5fd
add models to db
LiranCaduri 013c6d5
figure out delete.
LiranCaduri 894f234
Merge branch 'develop' of https://github.com/PythonFreeCourse/calenda…
LiranCaduri cb7947c
first structure of route with middlewere
LiranCaduri 523c01e
Merge branch 'develop' of https://github.com/PythonFreeCourse/calenda…
LiranCaduri 572036e
try middleware
LiranCaduri 397cee3
add middleware filtering requests
LiranCaduri 1da1dda
add: get_user_disabled_features and get_user_enabled_features
LiranCaduri d160605
modify middleware
LiranCaduri b51cd21
replace testclient, add on startup event, feature folder, is_feature_…
LiranCaduri 9df777b
Merge branch 'develop' of https://github.com/PythonFreeCourse/calenda…
LiranCaduri e4a817a
improved is_feature_enabled and middleware code.
LiranCaduri 0514121
add_feature_to_user function
LiranCaduri c715299
add checking for duplicates in association table
LiranCaduri 6380a8c
renaming some files, and adding on and off routes
LiranCaduri 7f50cc0
Merge branch 'develop' of https://github.com/PythonFreeCourse/calenda…
LiranCaduri 2ea61cb
.example
LiranCaduri b2b839f
split to internal
LiranCaduri 36af9b7
Merge branch 'develop' of https://github.com/PythonFreeCourse/calenda…
LiranCaduri 8c4929c
add config
LiranCaduri b999bc6
add tests
LiranCaduri e97f29b
Merge branch 'develop' of https://github.com/PythonFreeCourse/calenda…
LiranCaduri 6220ad0
access decorator, back to fastapi testclient, fix tests, new document…
LiranCaduri f6a6630
Merge branch 'develop' of https://github.com/PythonFreeCourse/calenda…
LiranCaduri 0b27713
fix flake8 issue
LiranCaduri 2572153
requested changes
LiranCaduri 10c7d88
Merge branch 'develop' of https://github.com/PythonFreeCourse/calenda…
LiranCaduri 7f0ab27
.example
LiranCaduri 1c1bbe7
requested changes
LiranCaduri a8e8725
Merge branch 'develop' of https://github.com/PythonFreeCourse/calenda…
LiranCaduri dfe02c7
Merge branch 'develop' of https://github.com/PythonFreeCourse/calenda…
LiranCaduri 2081329
Merge branch 'develop' of https://github.com/PythonFreeCourse/calenda…
LiranCaduri 107d246
changes
LiranCaduri 182b6be
before cache
LiranCaduri d6b26a8
remove redundant things
LiranCaduri 4618b06
Merge branch 'develop' of https://github.com/PythonFreeCourse/calenda…
LiranCaduri c4f6cbe
Merge branch 'develop' of https://github.com/PythonFreeCourse/calenda…
LiranCaduri 3c88d35
Merge branch 'develop' of https://github.com/PythonFreeCourse/calenda…
LiranCaduri 096d6fa
remove redundant things
LiranCaduri 4696df2
pre-commit change some stuff
LiranCaduri 105d253
Merge branch 'develop' of https://github.com/PythonFreeCourse/calenda…
LiranCaduri dc9fcce
remove blank lines
LiranCaduri d677ccd
requested changes
LiranCaduri 7712b07
update .gitignore
LiranCaduri ecd5cb8
Merge branch 'develop' of https://github.com/PythonFreeCourse/calenda…
LiranCaduri a9f9001
fix semantics
LiranCaduri 5b92634
fix logic
LiranCaduri 572e992
Merge branch 'develop' of https://github.com/PythonFreeCourse/calenda…
LiranCaduri 571fe1a
Merge branch 'develop' of https://github.com/PythonFreeCourse/calenda…
LiranCaduri 36f841b
requested changes
LiranCaduri c181946
Merge branch 'develop' of https://github.com/PythonFreeCourse/calenda…
LiranCaduri 93b0b02
use pre-commit on google-connect
LiranCaduri 3bd43b2
requested changes
LiranCaduri 69ed31b
Merge branch 'develop' of https://github.com/PythonFreeCourse/calenda…
LiranCaduri File filter
Filter by extension
Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
There are no files selected for viewing
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Empty file.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,51 @@ | ||
''' | ||
This file purpose is for developers to add their features to the database | ||
in one convenient place, every time the system loads up it's adding and | ||
updating the features in the features table in the database. | ||
|
||
To update a feature, The developer needs to change the name or the route | ||
and let the system load, but not change both at the same time otherwise | ||
it will create junk and unnecessary duplicates. | ||
|
||
* IMPORTANT - To enable features panel functionlity the developer must * | ||
* add the feature_access_filter decorator to ALL the feature routes * | ||
* Please see the example below. * | ||
|
||
Enjoy and good luck :) | ||
''' | ||
|
||
''' | ||
Example to feature stracture: | ||
|
||
{ | ||
"name": "<feature name - str>", | ||
"route": "/<the route like: /features - str>", | ||
"description": "<description - str>", | ||
"creator": "<creator name or nickname - str>" | ||
} | ||
''' | ||
|
||
''' | ||
* IMPORTANT * | ||
|
||
Example to decorator placement: | ||
|
||
@router.get("/<my-route>") | ||
@feature_access_filter <---- just above def keyword! | ||
def my_cool_feature_route(): | ||
.... | ||
... | ||
some code. | ||
.. | ||
. | ||
|
||
''' | ||
|
||
features = [ | ||
{ | ||
"name": "Google Sync", | ||
"route": "/google/sync", | ||
"description": "Sync Google Calendar events with Pylender", | ||
"creator": "Liran Caduri" | ||
}, | ||
] | ||
LiranCaduri marked this conversation as resolved.
Show resolved
Hide resolved
|
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,34 @@ | ||
from functools import wraps | ||
from starlette.responses import RedirectResponse | ||
|
||
from app.internal.features import is_feature_enabled | ||
|
||
|
||
def feature_access_filter(call_next): | ||
|
||
@wraps(call_next) | ||
async def wrapper(*args, **kwargs): | ||
request = kwargs['request'] | ||
|
||
if request.headers['user-agent'] == 'testclient': | ||
# in case it's a unit test. | ||
return await call_next(*args, **kwargs) | ||
|
||
# getting the url route path for matching with the database. | ||
route = '/' + str(request.url).replace(str(request.base_url), '') | ||
|
||
# getting access status. | ||
is_enabled = is_feature_enabled(route=route) | ||
|
||
if is_enabled: | ||
# in case the feature is enabled or access is allowed. | ||
return await call_next(*args, **kwargs) | ||
|
||
elif 'referer' not in request.headers: | ||
# in case request come straight from address bar in browser. | ||
return RedirectResponse(url='/') | ||
|
||
# in case the feature is disabled or access isn't allowed. | ||
return RedirectResponse(url=request.headers['referer']) | ||
|
||
return wrapper |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,146 @@ | ||
from fastapi import Depends | ||
from typing import List | ||
|
||
from app.database.models import UserFeature, Feature | ||
from app.dependencies import get_db, SessionLocal | ||
from app.features.index import features | ||
from app.internal.utils import create_model, get_current_user | ||
|
||
|
||
def create_features_at_startup(session: SessionLocal) -> bool: | ||
for feat in features: | ||
if not is_feature_exists_in_db(feature=feat, session=session): | ||
create_feature(**feat, db=session) | ||
|
||
return True | ||
|
||
|
||
def is_association_exists_in_db(form: dict, session: SessionLocal) -> bool: | ||
db_association = session.query(UserFeature).filter_by( | ||
feature_id=form['feature_id'], | ||
user_id=form['user_id'] | ||
).first() | ||
|
||
return db_association is not None | ||
|
||
|
||
def delete_feature( | ||
feature: Feature, session: SessionLocal = Depends(get_db) | ||
) -> None: | ||
session.query(UserFeature).filter_by(feature_id=feature.id).delete() | ||
session.query(Feature).filter_by(id=feature.id).delete() | ||
session.commit() | ||
|
||
|
||
def is_feature_exists_in_db(feature: dict, session: SessionLocal) -> bool: | ||
db_feature = session.query(Feature).filter( | ||
(Feature.name == feature['name']) | | ||
(Feature.route == feature['route'])).first() | ||
|
||
if db_feature is None: | ||
return False | ||
|
||
# Update if found | ||
update_feature( | ||
feature=db_feature, new_feature_obj=feature, session=session) | ||
LiranCaduri marked this conversation as resolved.
Show resolved
Hide resolved
|
||
return True | ||
|
||
|
||
LiranCaduri marked this conversation as resolved.
Show resolved
Hide resolved
|
||
def update_feature(feature: Feature, new_feature_obj: dict, | ||
session: SessionLocal = Depends(get_db)) -> Feature: | ||
feature.name = new_feature_obj['name'] | ||
feature.route = new_feature_obj['route'] | ||
feature.description = new_feature_obj['description'] | ||
feature.creator = new_feature_obj['creator'] | ||
session.commit() | ||
|
||
return feature | ||
|
||
|
||
def is_feature_exists_in_enabled( | ||
LiranCaduri marked this conversation as resolved.
Show resolved
Hide resolved
|
||
feature: Feature, session: SessionLocal = Depends(get_db) | ||
) -> bool: | ||
enabled_features = get_user_enabled_features(session=session) | ||
return any(ef['feature'].id == feature.id for ef in enabled_features) | ||
|
||
|
||
def is_feature_exists_in_disabled( | ||
feature: Feature, session: SessionLocal = Depends(get_db) | ||
) -> bool: | ||
disable_features = get_user_disabled_features(session=session) | ||
return any(ef['feature'].id == feature.id for ef in disable_features) | ||
|
||
|
||
def is_feature_enabled(route: str) -> bool: | ||
LiranCaduri marked this conversation as resolved.
Show resolved
Hide resolved
|
||
session = SessionLocal() | ||
user = get_current_user(session=session) | ||
feature = session.query(Feature).filter_by(route=route).first() | ||
|
||
if feature is None: | ||
# in case there is no feature exists in the database that match the | ||
# route that gived by to the request. | ||
return True | ||
|
||
user_pref = session.query(UserFeature).filter_by( | ||
feature_id=feature.id, | ||
user_id=user.id | ||
).first() | ||
|
||
return user_pref is not None and user_pref.is_enable | ||
|
||
|
||
def create_feature(name: str, route: str, | ||
description: str, creator: str = None, | ||
db: SessionLocal = Depends()) -> Feature: | ||
"""Creates a feature.""" | ||
|
||
LiranCaduri marked this conversation as resolved.
Show resolved
Hide resolved
|
||
db = SessionLocal() | ||
return create_model( | ||
db, Feature, | ||
name=name, | ||
route=route, | ||
creator=creator, | ||
description=description | ||
) | ||
|
||
|
||
def create_association( | ||
LiranCaduri marked this conversation as resolved.
Show resolved
Hide resolved
|
||
db: SessionLocal, feature_id: int, user_id: int, is_enable: bool | ||
) -> UserFeature: | ||
"""Creates an association.""" | ||
return create_model( | ||
db, UserFeature, | ||
user_id=user_id, | ||
feature_id=feature_id, | ||
is_enable=is_enable | ||
) | ||
|
||
|
||
def get_user_enabled_features(session: SessionLocal = Depends(get_db)) -> List: | ||
user = get_current_user(session=session) | ||
data = [] | ||
user_prefs = session.query(UserFeature).filter_by(user_id=user.id).all() | ||
|
||
for pref in user_prefs: | ||
if pref.is_enable: | ||
feature = session.query(Feature).filter_by( | ||
id=pref.feature_id).first() | ||
data.append({'feature': feature, 'is_enabled': pref.is_enable}) | ||
|
||
return data | ||
|
||
|
||
def get_user_disabled_features( | ||
session: SessionLocal = Depends(get_db) | ||
) -> List: | ||
user = get_current_user(session=session) | ||
data = [] | ||
LiranCaduri marked this conversation as resolved.
Show resolved
Hide resolved
|
||
user_prefs = session.query(UserFeature).filter_by(user_id=user.id).all() | ||
|
||
for pref in user_prefs: | ||
if not pref.is_enable: | ||
feature = session.query(Feature).filter_by( | ||
id=pref.feature_id).first() | ||
data.append({'feature': feature, 'is_enabled': pref.is_enable}) | ||
|
||
return data |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Oops, something went wrong.
Add this suggestion to a batch that can be applied as a single commit.
This suggestion is invalid because no changes were made to the code.
Suggestions cannot be applied while the pull request is closed.
Suggestions cannot be applied while viewing a subset of changes.
Only one suggestion per line can be applied in a batch.
Add this suggestion to a batch that can be applied as a single commit.
Applying suggestions on deleted lines is not supported.
You must change the existing code in this line in order to create a valid suggestion.
Outdated suggestions cannot be applied.
This suggestion has been applied or marked resolved.
Suggestions cannot be applied from pending reviews.
Suggestions cannot be applied on multi-line comments.
Suggestions cannot be applied while the pull request is queued to merge.
Suggestion cannot be applied right now. Please check back later.
Uh oh!
There was an error while loading. Please reload this page.