From 435f6cf71c1b00548404eab1c79fe49d85bd7644 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Fl=C3=A1vio=20Mendes?= Date: Mon, 14 Mar 2022 10:22:27 +0000 Subject: [PATCH 01/25] Create python-app.yml --- .github/workflows/python-app.yml | 36 ++++++++++++++++++++++++++++++++ 1 file changed, 36 insertions(+) create mode 100644 .github/workflows/python-app.yml diff --git a/.github/workflows/python-app.yml b/.github/workflows/python-app.yml new file mode 100644 index 0000000..96e75c0 --- /dev/null +++ b/.github/workflows/python-app.yml @@ -0,0 +1,36 @@ +# This workflow will install Python dependencies, run tests and lint with a single version of Python +# For more information see: https://help.github.com/actions/language-and-framework-guides/using-python-with-github-actions + +name: Test & Lint + +on: + push: + branches: [ main ] + pull_request: + branches: [ main ] + +jobs: + build: + + runs-on: ubuntu-latest + + steps: + - uses: actions/checkout@v2 + - name: Set up Python 3.10 + uses: actions/setup-python@v2 + with: + python-version: "3.10" + - name: Install dependencies + run: | + python -m pip install --upgrade pip + pip install flake8 pytest + if [ -f requirements.txt ]; then pip install -r requirements.txt; fi + - name: Lint with flake8 + run: | + # stop the build if there are Python syntax errors or undefined names + flake8 . --count --select=E9,F63,F7,F82 --show-source --statistics + # exit-zero treats all errors as warnings. The GitHub editor is 127 chars wide + flake8 . --count --exit-zero --max-complexity=10 --max-line-length=127 --statistics + - name: Test with pytest + run: | + pytest From 529a12cf48c431caea4917f63fd2184097f22035 Mon Sep 17 00:00:00 2001 From: Mendes Date: Mon, 14 Mar 2022 10:26:58 +0000 Subject: [PATCH 02/25] formatting --- controller/private_api.py | 130 ++++++++++++----------- controller/public_api.py | 5 +- main.py | 31 +++--- model/initializer.py | 9 +- model/reward_model.py | 15 ++- model/user_interaction_model.py | 15 ++- model/user_model.py | 12 ++- persistence/user_dao.py | 56 ++++++---- persistence/user_interaction_dao.py | 19 +++- poc.py | 4 +- service/stats_service.py | 57 +++++++--- service/twitch_service.py | 99 +++++++++-------- view_model/stream_viewmodel.py | 2 +- view_model/tag_viewmodel.py | 4 +- view_model/user_interaction_viewmodel.py | 6 +- view_model/user_viewmodel.py | 3 +- view_model/vod_viewmodel.py | 2 +- 17 files changed, 272 insertions(+), 197 deletions(-) diff --git a/controller/private_api.py b/controller/private_api.py index 9bab0ec..03aae48 100644 --- a/controller/private_api.py +++ b/controller/private_api.py @@ -10,7 +10,12 @@ from model.user_interaction_model import UserInteraction from model.user_model import User -from persistence.user_dao import create_user_model, delete_user, get_user_by_login, update_user_model +from persistence.user_dao import ( + create_user_model, + delete_user, + get_user_by_login, + update_user_model, +) from persistence.user_interaction_dao import get_user_interactions_by_user_login from service.stats_service import compute_stat @@ -32,56 +37,55 @@ ) -AUTH0_DOMAIN = 'zapperson.us.auth0.com' +AUTH0_DOMAIN = "zapperson.us.auth0.com" API_AUDIENCE = "BrStreamersApi" ALGORITHMS = ["RS256"] def decode_jwt(token: str): - token = token.split(" ")[1] - jsonurl = urlopen("https://"+AUTH0_DOMAIN+"/.well-known/jwks.json") - jwks = json.loads(jsonurl.read()) - unverified_header = jwt.get_unverified_header(token) - rsa_key = {} - for key in jwks["keys"]: - if key["kid"] == unverified_header["kid"]: - rsa_key = { - "kty": key["kty"], - "kid": key["kid"], - "use": key["use"], - "n": key["n"], - "e": key["e"] - } - if rsa_key: - try: - payload = jwt.decode( - token, - rsa_key, - algorithms=ALGORITHMS, - audience=API_AUDIENCE, - issuer="https://"+AUTH0_DOMAIN+"/" - ) - except jwt.ExpiredSignatureError: - raise HTTPException(status_code=401, detail="token_expired") - except jwt.JWTClaimsError: - raise HTTPException(status_code=404, detail="invalid_claims") - - except Exception: - raise HTTPException(status_code=401, detail="invalid_header") - if payload is not None: - return payload - raise HTTPException(status_code=401, detail="invalid_header") + token = token.split(" ")[1] + jsonurl = urlopen("https://" + AUTH0_DOMAIN + "/.well-known/jwks.json") + jwks = json.loads(jsonurl.read()) + unverified_header = jwt.get_unverified_header(token) + rsa_key = {} + for key in jwks["keys"]: + if key["kid"] == unverified_header["kid"]: + rsa_key = { + "kty": key["kty"], + "kid": key["kid"], + "use": key["use"], + "n": key["n"], + "e": key["e"], + } + if rsa_key: + try: + payload = jwt.decode( + token, + rsa_key, + algorithms=ALGORITHMS, + audience=API_AUDIENCE, + issuer="https://" + AUTH0_DOMAIN + "/", + ) + except jwt.ExpiredSignatureError: + raise HTTPException(status_code=401, detail="token_expired") + except jwt.JWTClaimsError: + raise HTTPException(status_code=404, detail="invalid_claims") + + except Exception: + raise HTTPException(status_code=401, detail="invalid_header") + if payload is not None: + return payload + raise HTTPException(status_code=401, detail="invalid_header") @app_private.middleware("http") async def verify_user_agent(request: Request, call_next): - token = request.headers['Authorization'] + token = request.headers["Authorization"] payload = decode_jwt(token) response = await call_next(request) return response - @app_private.get("/users") async def get_users(): try: @@ -92,71 +96,71 @@ async def get_users(): @app_private.get("/user/{user_login}") -async def user(user_login: str, Authorization = Header(...)): +async def user(user_login: str, Authorization=Header(...)): try: token = decode_jwt(Authorization) - nickname = token['https://brstreamers.dev/nickname'] - if(nickname == user_login): + nickname = token["https://brstreamers.dev/nickname"] + if nickname == user_login: streamer = get_user_by_login(user_login) return streamer - + raise HTTPException(status_code=403, detail="Unauthorized") except Exception as e: raise HTTPException(status_code=404, detail=f"Streamer not found {e}") @app_private.post("/user") -async def save_user(user: UpdateUserViewModel, Authorization = Header(...)): +async def save_user(user: UpdateUserViewModel, Authorization=Header(...)): token = decode_jwt(Authorization) - nickname = token['https://brstreamers.dev/nickname'] + nickname = token["https://brstreamers.dev/nickname"] - if(nickname == user.user_login): + if nickname == user.user_login: return create_user_model(user) - raise HTTPException(status_code=403, detail="Unauthorized") - + raise HTTPException(status_code=403, detail="Unauthorized") + @app_private.put("/user") -async def update_user(user: UpdateUserViewModel, Authorization = Header(...)): +async def update_user(user: UpdateUserViewModel, Authorization=Header(...)): token = decode_jwt(Authorization) - nickname = token['https://brstreamers.dev/nickname'] + nickname = token["https://brstreamers.dev/nickname"] - if(nickname == user.user_login): + if nickname == user.user_login: res = update_user_model(user) return res - raise HTTPException(status_code=403, detail="Unauthorized") + raise HTTPException(status_code=403, detail="Unauthorized") + @app_private.delete("/user/{user_login}") -async def delete_streamer(user_login, Authorization = Header(...)): +async def delete_streamer(user_login, Authorization=Header(...)): token = decode_jwt(Authorization) - nickname = token['https://brstreamers.dev/nickname'] + nickname = token["https://brstreamers.dev/nickname"] try: - if(nickname == user_login): + if nickname == user_login: user = delete_user(user_login) return user - raise HTTPException(status_code=403, detail="Unauthorized") + raise HTTPException(status_code=403, detail="Unauthorized") except: raise HTTPException(status_code=404, detail="Streamer not found") @app_private.post("/userinteraction") -async def stats(stat: UserInteractionViewModel, Authorization = Header(...)): +async def stats(stat: UserInteractionViewModel, Authorization=Header(...)): token = decode_jwt(Authorization) - nickname = token['https://brstreamers.dev/nickname'] - if(nickname == stat.user_login): + nickname = token["https://brstreamers.dev/nickname"] + if nickname == stat.user_login: return compute_stat(stat) - raise HTTPException(status_code=403, detail="Unauthorized") + raise HTTPException(status_code=403, detail="Unauthorized") - @app_private.get("/userinteraction/{user_login}") -async def stats(user_login, Authorization = Header(...)): +async def stats(user_login, Authorization=Header(...)): token = decode_jwt(Authorization) - nickname = token['https://brstreamers.dev/nickname'] - if(nickname == user_login): + nickname = token["https://brstreamers.dev/nickname"] + if nickname == user_login: user_interactions = get_user_interactions_by_user_login(user_login) data = [] for interaction in user_interactions: data.append(interaction.__data__) return data - raise HTTPException(status_code=403, detail="Unauthorized") \ No newline at end of file + raise HTTPException(status_code=403, detail="Unauthorized") diff --git a/controller/public_api.py b/controller/public_api.py index c3d2aef..b314b5c 100644 --- a/controller/public_api.py +++ b/controller/public_api.py @@ -17,7 +17,7 @@ app_public.add_middleware( CORSMiddleware, - allow_origins=origins, + allow_origins=origins, allow_credentials=True, allow_methods=["*"], allow_headers=["*"], @@ -45,6 +45,7 @@ async def stats(): async def tags(): return get_tags() + @app_public.get("/stats/summary") async def stats_summary(): - return get_stats_summary() \ No newline at end of file + return get_stats_summary() diff --git a/main.py b/main.py index 343e4ce..c3ec46d 100644 --- a/main.py +++ b/main.py @@ -1,4 +1,3 @@ - import aioredis import uvicorn from dotenv import dotenv_values @@ -31,9 +30,12 @@ allow_headers=["*"], ) + @app.on_event("startup") async def startup(): - redis = aioredis.from_url(f"redis://{config['REDIS_HOST']}", encoding="utf8", decode_responses=True) + redis = aioredis.from_url( + f"redis://{config['REDIS_HOST']}", encoding="utf8", decode_responses=True + ) FastAPICache.init(RedisBackend(redis), prefix="fastapi-cache") @@ -44,18 +46,15 @@ async def shutdown(): app.add_middleware(GZipMiddleware) -if __name__ == '__main__': - if(config["ENV"] == 'prod'): - uvicorn.run("main:app", - host="0.0.0.0", - port=8000, - reload=True, - ssl_keyfile=config["PRIVATE_KEY"], - ssl_certfile=config["CERT"] - ) +if __name__ == "__main__": + if config["ENV"] == "prod": + uvicorn.run( + "main:app", + host="0.0.0.0", + port=8000, + reload=True, + ssl_keyfile=config["PRIVATE_KEY"], + ssl_certfile=config["CERT"], + ) else: - uvicorn.run("main:app", - host="0.0.0.0", - port=8000, - reload=True - ) + uvicorn.run("main:app", host="0.0.0.0", port=8000, reload=True) diff --git a/model/initializer.py b/model/initializer.py index 8dca34f..26067e7 100644 --- a/model/initializer.py +++ b/model/initializer.py @@ -6,8 +6,13 @@ config = dotenv_values(".env") -db = PostgresqlDatabase(config['DB_NAME'], user=config['DB_USER'], - password=config['DB_PASS'], host=config['DB_HOST'], port=config['DB_PORT']) +db = PostgresqlDatabase( + config["DB_NAME"], + user=config["DB_USER"], + password=config["DB_PASS"], + host=config["DB_HOST"], + port=config["DB_PORT"], +) def init_db(): diff --git a/model/reward_model.py b/model/reward_model.py index 7261488..35d42a3 100644 --- a/model/reward_model.py +++ b/model/reward_model.py @@ -1,15 +1,22 @@ from dotenv import dotenv_values from peewee import PostgresqlDatabase, CharField, IntegerField, Model + config = dotenv_values(".env") -db = PostgresqlDatabase(config['DB_NAME'], user=config['DB_USER'], - password=config['DB_PASS'], host=config['DB_HOST'], port=config['DB_PORT']) +db = PostgresqlDatabase( + config["DB_NAME"], + user=config["DB_USER"], + password=config["DB_PASS"], + host=config["DB_HOST"], + port=config["DB_PORT"], +) + class Reward(Model): - id= CharField(null=False) + id = CharField(null=False) description = CharField(null=False) price = IntegerField(null=False) quantity_available = IntegerField(null=False) class Meta: - database = db \ No newline at end of file + database = db diff --git a/model/user_interaction_model.py b/model/user_interaction_model.py index 83d5771..508c9e4 100644 --- a/model/user_interaction_model.py +++ b/model/user_interaction_model.py @@ -1,18 +1,23 @@ - from dotenv import dotenv_values from peewee import PostgresqlDatabase, CharField, DateField, Model config = dotenv_values(".env") -db = PostgresqlDatabase(config['DB_NAME'], user=config['DB_USER'], - password=config['DB_PASS'], host=config['DB_HOST'], port=config['DB_PORT']) +db = PostgresqlDatabase( + config["DB_NAME"], + user=config["DB_USER"], + password=config["DB_PASS"], + host=config["DB_HOST"], + port=config["DB_PORT"], +) + class UserInteraction(Model): - user_login= CharField(null=False) + user_login = CharField(null=False) target_user = CharField(null=True) date = DateField(null=False) type = CharField(null=False) interaction_fingerprint = CharField(null=False) class Meta: - database = db \ No newline at end of file + database = db diff --git a/model/user_model.py b/model/user_model.py index a3c8c31..775321a 100644 --- a/model/user_model.py +++ b/model/user_model.py @@ -3,8 +3,14 @@ config = dotenv_values(".env") -db = PostgresqlDatabase(config['DB_NAME'], user=config['DB_USER'], - password=config['DB_PASS'], host=config['DB_HOST'], port=config['DB_PORT']) +db = PostgresqlDatabase( + config["DB_NAME"], + user=config["DB_USER"], + password=config["DB_PASS"], + host=config["DB_HOST"], + port=config["DB_PORT"], +) + class User(Model): user_login = CharField(unique=True) @@ -17,4 +23,4 @@ class User(Model): bio = CharField(unique=False, null=True) class Meta: - database = db \ No newline at end of file + database = db diff --git a/persistence/user_dao.py b/persistence/user_dao.py index b6775c6..cd8d068 100644 --- a/persistence/user_dao.py +++ b/persistence/user_dao.py @@ -1,11 +1,17 @@ - from dotenv import dotenv_values + config = dotenv_values(".env") from model.user_model import User from peewee import * -db = PostgresqlDatabase(config['DB_NAME'], user=config['DB_USER'], - password=config['DB_PASS'], host=config['DB_HOST'], port=config['DB_PORT']) +db = PostgresqlDatabase( + config["DB_NAME"], + user=config["DB_USER"], + password=config["DB_PASS"], + host=config["DB_HOST"], + port=config["DB_PORT"], +) + def exception_handler(func): def inner_function(*args, **kwargs): @@ -13,14 +19,17 @@ def inner_function(*args, **kwargs): return func(*args, **kwargs) except Exception as e: print(f"Error executing {func.__name__}. Error: {e}") + return inner_function + @exception_handler def get_users_by_name(users): users = User.select().where(User.user_login << users).execute() db.close() return users + @exception_handler def get_users(): users = User.select().execute() @@ -38,34 +47,39 @@ def get_user_by_login(user_login): @exception_handler def create_user_model(user): res = User.create( - user_login=user.user_login, - email=user.email, - bio=user.bio, - discord = user.discord, - instagram = user.instagram, - linkedin = user.linkedin, - github = user.github, - twitter = user.twitter) + user_login=user.user_login, + email=user.email, + bio=user.bio, + discord=user.discord, + instagram=user.instagram, + linkedin=user.linkedin, + github=user.github, + twitter=user.twitter, + ) return res @exception_handler def update_user_model(user): - res = (User - .update({User.instagram: user.instagram, - User.linkedin: user.linkedin, - User.github: user.github, - User.twitter: user.twitter, - User.discord: user.discord, - User.bio: user.bio - }) + res = ( + User.update( + { + User.instagram: user.instagram, + User.linkedin: user.linkedin, + User.github: user.github, + User.twitter: user.twitter, + User.discord: user.discord, + User.bio: user.bio, + } + ) .where(User.user_login == user.user_login) - .execute()) + .execute() + ) return res @exception_handler def delete_user(user_login): res = User.delete().where(User.user_login == user_login).execute() - return res \ No newline at end of file + return res diff --git a/persistence/user_interaction_dao.py b/persistence/user_interaction_dao.py index ba3e504..37405e1 100644 --- a/persistence/user_interaction_dao.py +++ b/persistence/user_interaction_dao.py @@ -2,10 +2,16 @@ from model.user_interaction_model import UserInteraction from model.user_model import User from dotenv import dotenv_values + config = dotenv_values(".env") -db = PostgresqlDatabase(config['DB_NAME'], user=config['DB_USER'], - password=config['DB_PASS'], host=config['DB_HOST'], port=config['DB_PORT']) +db = PostgresqlDatabase( + config["DB_NAME"], + user=config["DB_USER"], + password=config["DB_PASS"], + host=config["DB_HOST"], + port=config["DB_PORT"], +) def exception_handler(func): @@ -14,11 +20,16 @@ def inner_function(*args, **kwargs): return func(*args, **kwargs) except Exception as e: print(f"Error executing {func.__name__}. Error: {e}") + return inner_function @exception_handler def get_user_interactions_by_user_login(user_login): - user_interactions = UserInteraction.select().where(UserInteraction.user_login == user_login).execute() + user_interactions = ( + UserInteraction.select() + .where(UserInteraction.user_login == user_login) + .execute() + ) db.close() - return user_interactions \ No newline at end of file + return user_interactions diff --git a/poc.py b/poc.py index eb5a572..d3a2763 100644 --- a/poc.py +++ b/poc.py @@ -4,9 +4,9 @@ config = dotenv_values(".env") -twitch = Twitch(config['CLIENT_ID'], config['CLIENT_SECRET']) +twitch = Twitch(config["CLIENT_ID"], config["CLIENT_SECRET"]) -users = twitch.get_users(user_ids=['227168488']) +users = twitch.get_users(user_ids=["227168488"]) print(users) diff --git a/service/stats_service.py b/service/stats_service.py index 5b42090..b70d425 100644 --- a/service/stats_service.py +++ b/service/stats_service.py @@ -8,16 +8,23 @@ config = dotenv_values(".env") -db = PostgresqlDatabase(config['DB_NAME'], user=config['DB_USER'], - password=config['DB_PASS'], host=config['DB_HOST'], port=config['DB_PORT']) +db = PostgresqlDatabase( + config["DB_NAME"], + user=config["DB_USER"], + password=config["DB_PASS"], + host=config["DB_HOST"], + port=config["DB_PORT"], +) + def get_stats() -> List[StatsViewModel]: cursor = db.execute_sql( - "SELECT distinct s.target_user, " + - "(SELECT count(*) FROM userinteraction WHERE target_user = s.target_user AND type = 'STREAM_CLICK')," + - "(SELECT count(*) FROM userinteraction WHERE target_user = s.target_user AND type = 'VOD_CLICK')," + - "(SELECT count(*) FROM userinteraction WHERE target_user = s.target_user AND type = 'PREVIEW_CLICK')" + - "FROM userinteraction s ORDER BY s.target_user") + "SELECT distinct s.target_user, " + + "(SELECT count(*) FROM userinteraction WHERE target_user = s.target_user AND type = 'STREAM_CLICK')," + + "(SELECT count(*) FROM userinteraction WHERE target_user = s.target_user AND type = 'VOD_CLICK')," + + "(SELECT count(*) FROM userinteraction WHERE target_user = s.target_user AND type = 'PREVIEW_CLICK')" + + "FROM userinteraction s ORDER BY s.target_user" + ) stats: List[StatsViewModel] = [] @@ -29,19 +36,35 @@ def get_stats() -> List[StatsViewModel]: stat.preview_clicks = row[3] stats.append(stat) return stats - + + def get_stats_summary(): - streams = UserInteraction.select().where(UserInteraction.type == 'STREAM_CLICK').count() - vods = UserInteraction.select().where(UserInteraction.type == 'VOD_CLICK').count() - previews = UserInteraction.select().where(UserInteraction.type == 'PREVIEW').count() + streams = ( + UserInteraction.select().where(UserInteraction.type == "STREAM_CLICK").count() + ) + vods = UserInteraction.select().where(UserInteraction.type == "VOD_CLICK").count() + previews = UserInteraction.select().where(UserInteraction.type == "PREVIEW").count() stats_summary = {"streams": streams, "vods": vods, "previews": previews} return stats_summary + def compute_stat(stat: UserInteraction): - db_stat = UserInteraction.select().where(UserInteraction.target_user == stat.target_user, - UserInteraction.type == stat.type, - UserInteraction.date == stat.date, - UserInteraction.interaction_fingerprint == stat.interaction_fingerprint).count() + db_stat = ( + UserInteraction.select() + .where( + UserInteraction.target_user == stat.target_user, + UserInteraction.type == stat.type, + UserInteraction.date == stat.date, + UserInteraction.interaction_fingerprint == stat.interaction_fingerprint, + ) + .count() + ) if db_stat == 0: - return UserInteraction.create(user_login=stat.user_login, date=stat.date, target_user=stat.target_user, type=stat.type, interaction_fingerprint=stat.interaction_fingerprint) - return None \ No newline at end of file + return UserInteraction.create( + user_login=stat.user_login, + date=stat.date, + target_user=stat.target_user, + type=stat.type, + interaction_fingerprint=stat.interaction_fingerprint, + ) + return None diff --git a/service/twitch_service.py b/service/twitch_service.py index 2d26a68..e6e6d60 100644 --- a/service/twitch_service.py +++ b/service/twitch_service.py @@ -12,39 +12,39 @@ from view_model.vod_viewmodel import VodViewModel config = dotenv_values(".env") -twitch = Twitch(config['CLIENT_ID'], config['CLIENT_SECRET']) +twitch = Twitch(config["CLIENT_ID"], config["CLIENT_SECRET"]) def get_streamers() -> List[StreamViewModel]: - streams = twitch.get_streams(language="pt", game_id='1469308723') + streams = twitch.get_streams(language="pt", game_id="1469308723") streams_model: List[StreamViewModel] = [] stream_users = [] - for s in streams['data']: + for s in streams["data"]: stream = StreamViewModel() - stream.id = s['id'] - stream.user_id = s['user_id'] - stream.user_name = s['user_name'] - stream.user_login = s['user_login'] - stream.title = s['title'] - stream.viewer_count = s['viewer_count'] - stream.started_at = s['started_at'] - stream.thumbnail_url = s['thumbnail_url'] - - stream.tags = s['tag_ids'] - - streamer = get_streamer(s['user_id']) - stream.profile_image_url = streamer['profile_image_url'] - stream.description = streamer['description'][:100] + '...' - - stream_users.append(s['user_login']) + stream.id = s["id"] + stream.user_id = s["user_id"] + stream.user_name = s["user_name"] + stream.user_login = s["user_login"] + stream.title = s["title"] + stream.viewer_count = s["viewer_count"] + stream.started_at = s["started_at"] + stream.thumbnail_url = s["thumbnail_url"] + + stream.tags = s["tag_ids"] + + streamer = get_streamer(s["user_id"]) + stream.profile_image_url = streamer["profile_image_url"] + stream.description = streamer["description"][:100] + "..." + + stream_users.append(s["user_login"]) streams_model.append(stream) try: streamers = get_users_by_name(stream_users) for s in streamers: for stream in streams_model: - if(stream.user_login == s.user_login): + if stream.user_login == s.user_login: stream.github_url = s.github stream.twitter_url = s.twitter stream.instagram_url = s.instagram @@ -59,40 +59,39 @@ def get_streamers() -> List[StreamViewModel]: def get_streamer(id): - return twitch.get_users(user_ids=[id])['data'][0] + return twitch.get_users(user_ids=[id])["data"][0] def get_vods() -> List[VodViewModel]: - vods = twitch.get_videos( - language="pt", game_id='1469308723', period=TimePeriod.DAY) + vods = twitch.get_videos(language="pt", game_id="1469308723", period=TimePeriod.DAY) vods_model: List[VodViewModel] = [] vod_users = [] - for s in vods['data']: - if is_long_enough(s['duration']): + for s in vods["data"]: + if is_long_enough(s["duration"]): stream = VodViewModel() - stream.id = s['id'] - stream.user_id = s['user_id'] - stream.user_name = s['user_name'] - stream.user_login = s['user_login'] - stream.title = s['title'] - stream.viewer_count = s['view_count'] - stream.started_at = s['published_at'] - stream.thumbnail_url = s['thumbnail_url'] - stream.stream_id = s['id'] - stream.duration = s['duration'] - - streamer = get_streamer(s['user_id']) - stream.profile_image_url = streamer['profile_image_url'] - stream.description = streamer['description'][:100] + '...' - - vod_users.append(s['user_login']) + stream.id = s["id"] + stream.user_id = s["user_id"] + stream.user_name = s["user_name"] + stream.user_login = s["user_login"] + stream.title = s["title"] + stream.viewer_count = s["view_count"] + stream.started_at = s["published_at"] + stream.thumbnail_url = s["thumbnail_url"] + stream.stream_id = s["id"] + stream.duration = s["duration"] + + streamer = get_streamer(s["user_id"]) + stream.profile_image_url = streamer["profile_image_url"] + stream.description = streamer["description"][:100] + "..." + + vod_users.append(s["user_login"]) vods_model.append(stream) try: streamers = get_users_by_name(vod_users) for s in streamers: for stream in vods_model: - if(stream.user_login == s.user_login): + if stream.user_login == s.user_login: stream.github_url = s.github stream.twitter_url = s.twitter stream.instagram_url = s.instagram @@ -106,25 +105,25 @@ def get_vods() -> List[VodViewModel]: def is_long_enough(duration): - return 'h' in duration + return "h" in duration def get_tags() -> List[TagViewModel]: - streams = twitch.get_streams(language="pt", game_id='1469308723') + streams = twitch.get_streams(language="pt", game_id="1469308723") tag_ids = get_tag_list_from_streams(streams) tags = twitch.get_all_stream_tags(tag_ids=tag_ids) tags_dict = {} - for tag in tags['data']: + for tag in tags["data"]: tag_model = TagViewModel( - id=tag['tag_id'], name=tag['localization_names']['pt-br']) - tags_dict[tag['tag_id']] = tag_model + id=tag["tag_id"], name=tag["localization_names"]["pt-br"] + ) + tags_dict[tag["tag_id"]] = tag_model return list(tags_dict.values()) def get_tag_list_from_streams(streams): tag_ids = [] - for s in streams['data']: - for tag in s['tag_ids']: + for s in streams["data"]: + for tag in s["tag_ids"]: tag_ids.append(tag) return tag_ids - diff --git a/view_model/stream_viewmodel.py b/view_model/stream_viewmodel.py index bcf890c..f770491 100644 --- a/view_model/stream_viewmodel.py +++ b/view_model/stream_viewmodel.py @@ -13,4 +13,4 @@ class StreamViewModel(UserOutViewModel): thumbnail_url: Optional[str] profile_image_url: Optional[str] description: Optional[str] - tags: Optional[List[str]] \ No newline at end of file + tags: Optional[List[str]] diff --git a/view_model/tag_viewmodel.py b/view_model/tag_viewmodel.py index 3cd9378..51bb13a 100644 --- a/view_model/tag_viewmodel.py +++ b/view_model/tag_viewmodel.py @@ -4,5 +4,5 @@ class TagViewModel(BaseModel): - name:Optional[str] - id:Optional[str] \ No newline at end of file + name: Optional[str] + id: Optional[str] diff --git a/view_model/user_interaction_viewmodel.py b/view_model/user_interaction_viewmodel.py index b60b75f..eb5e468 100644 --- a/view_model/user_interaction_viewmodel.py +++ b/view_model/user_interaction_viewmodel.py @@ -6,8 +6,8 @@ class UserInteractionViewModel(BaseModel): - user_login:str - target_user:Optional[str] + user_login: str + target_user: Optional[str] date: datetime type: str - interaction_fingerprint: str \ No newline at end of file + interaction_fingerprint: str diff --git a/view_model/user_viewmodel.py b/view_model/user_viewmodel.py index 6286f40..05fdcb6 100644 --- a/view_model/user_viewmodel.py +++ b/view_model/user_viewmodel.py @@ -13,6 +13,7 @@ class UpdateUserViewModel(BaseModel): github: Optional[str] twitter: Optional[str] + class UserOutViewModel(BaseModel): user_login: Optional[str] email: Optional[str] @@ -21,4 +22,4 @@ class UserOutViewModel(BaseModel): twitter_url: Optional[str] instagram_url: Optional[str] linkedin_url: Optional[str] - discord_url: Optional[str] \ No newline at end of file + discord_url: Optional[str] diff --git a/view_model/vod_viewmodel.py b/view_model/vod_viewmodel.py index 5610bd2..1c162c7 100644 --- a/view_model/vod_viewmodel.py +++ b/view_model/vod_viewmodel.py @@ -14,4 +14,4 @@ class VodViewModel(UserOutViewModel): profile_image_url: Optional[str] description: Optional[str] stream_id: Optional[str] - duration: Optional[str] \ No newline at end of file + duration: Optional[str] From ae65e3fb4687ff6d888390550705f69dd051a56a Mon Sep 17 00:00:00 2001 From: Mendes Date: Mon, 14 Mar 2022 10:39:51 +0000 Subject: [PATCH 03/25] reformatting code --- persistence/user_interaction_dao.py | 4 +--- service/stats_service.py | 4 +--- service/twitch_service.py | 4 +--- 3 files changed, 3 insertions(+), 9 deletions(-) diff --git a/persistence/user_interaction_dao.py b/persistence/user_interaction_dao.py index 37405e1..a6fb737 100644 --- a/persistence/user_interaction_dao.py +++ b/persistence/user_interaction_dao.py @@ -27,9 +27,7 @@ def inner_function(*args, **kwargs): @exception_handler def get_user_interactions_by_user_login(user_login): user_interactions = ( - UserInteraction.select() - .where(UserInteraction.user_login == user_login) - .execute() + UserInteraction.select().where(UserInteraction.user_login == user_login).execute() ) db.close() return user_interactions diff --git a/service/stats_service.py b/service/stats_service.py index b70d425..accd01e 100644 --- a/service/stats_service.py +++ b/service/stats_service.py @@ -39,9 +39,7 @@ def get_stats() -> List[StatsViewModel]: def get_stats_summary(): - streams = ( - UserInteraction.select().where(UserInteraction.type == "STREAM_CLICK").count() - ) + streams = UserInteraction.select().where(UserInteraction.type == "STREAM_CLICK").count() vods = UserInteraction.select().where(UserInteraction.type == "VOD_CLICK").count() previews = UserInteraction.select().where(UserInteraction.type == "PREVIEW").count() stats_summary = {"streams": streams, "vods": vods, "previews": previews} diff --git a/service/twitch_service.py b/service/twitch_service.py index e6e6d60..59264ca 100644 --- a/service/twitch_service.py +++ b/service/twitch_service.py @@ -114,9 +114,7 @@ def get_tags() -> List[TagViewModel]: tags = twitch.get_all_stream_tags(tag_ids=tag_ids) tags_dict = {} for tag in tags["data"]: - tag_model = TagViewModel( - id=tag["tag_id"], name=tag["localization_names"]["pt-br"] - ) + tag_model = TagViewModel(id=tag["tag_id"], name=tag["localization_names"]["pt-br"]) tags_dict[tag["tag_id"]] = tag_model return list(tags_dict.values()) From 21689f030e1e265e6ac3e3564198211974c4cfe1 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Fl=C3=A1vio=20Mendes?= Date: Mon, 14 Mar 2022 10:41:42 +0000 Subject: [PATCH 04/25] Delete python-app.yml --- .github/workflows/python-app.yml | 36 -------------------------------- 1 file changed, 36 deletions(-) delete mode 100644 .github/workflows/python-app.yml diff --git a/.github/workflows/python-app.yml b/.github/workflows/python-app.yml deleted file mode 100644 index 96e75c0..0000000 --- a/.github/workflows/python-app.yml +++ /dev/null @@ -1,36 +0,0 @@ -# This workflow will install Python dependencies, run tests and lint with a single version of Python -# For more information see: https://help.github.com/actions/language-and-framework-guides/using-python-with-github-actions - -name: Test & Lint - -on: - push: - branches: [ main ] - pull_request: - branches: [ main ] - -jobs: - build: - - runs-on: ubuntu-latest - - steps: - - uses: actions/checkout@v2 - - name: Set up Python 3.10 - uses: actions/setup-python@v2 - with: - python-version: "3.10" - - name: Install dependencies - run: | - python -m pip install --upgrade pip - pip install flake8 pytest - if [ -f requirements.txt ]; then pip install -r requirements.txt; fi - - name: Lint with flake8 - run: | - # stop the build if there are Python syntax errors or undefined names - flake8 . --count --select=E9,F63,F7,F82 --show-source --statistics - # exit-zero treats all errors as warnings. The GitHub editor is 127 chars wide - flake8 . --count --exit-zero --max-complexity=10 --max-line-length=127 --statistics - - name: Test with pytest - run: | - pytest From 7f9addf55a9451583ad3adf1af0b2c2eebf641d9 Mon Sep 17 00:00:00 2001 From: Mendes Date: Mon, 14 Mar 2022 15:35:59 +0000 Subject: [PATCH 05/25] adding first test --- Pipfile | 3 + Pipfile.lock | 132 ++++++++++++++++- brdevstreamers (1).db | Bin 32768 -> 0 bytes controller/__init__.py | 0 model/__init__.py | 0 persistence/__init__.py | 0 service/__init__.py | 0 service/twitch_service.py | 217 +++++++++++++++------------- test/__init__.py | 0 test/service/__init__.py | 0 test/service/test_twitch_service.py | 31 ++++ view_model/__init__.py | 0 12 files changed, 278 insertions(+), 105 deletions(-) delete mode 100644 brdevstreamers (1).db create mode 100644 controller/__init__.py create mode 100644 model/__init__.py create mode 100644 persistence/__init__.py create mode 100644 service/__init__.py create mode 100644 test/__init__.py create mode 100644 test/service/__init__.py create mode 100644 test/service/test_twitch_service.py create mode 100644 view_model/__init__.py diff --git a/Pipfile b/Pipfile index c796845..cfc8e23 100644 --- a/Pipfile +++ b/Pipfile @@ -58,6 +58,9 @@ PyJWT = "==2.3.0" PyNaCl = "==1.5.0" PyYAML = "==6.0" fastapi-cache2 = "*" +pytest = "*" +asyncmock = "*" +pytest-cov = "*" [requires] python_version = "3.7" diff --git a/Pipfile.lock b/Pipfile.lock index 47e541e..d0776ff 100644 --- a/Pipfile.lock +++ b/Pipfile.lock @@ -1,7 +1,7 @@ { "_meta": { "hash": { - "sha256": "4cfe2d19d9add39387cc3b062afcd9256b869443f69e2ac0308827dd23bd7c0b" + "sha256": "a0657f47e777ab57aefa8edbed8e3fc58a36b51ec4a522f16600d8decd2338c2" }, "pipfile-spec": 6, "requires": { @@ -134,6 +134,14 @@ "index": "pypi", "version": "==4.0.2" }, + "asyncmock": { + "hashes": [ + "sha256:c251889d542e98fe5f7ece2b5b8643b7d62b50a5657d34a4cbce8a1d5170d750", + "sha256:fd8bc4e7813251a8959d1140924ccba3adbbc7af885dba7047c67f73c0b664b1" + ], + "index": "pypi", + "version": "==0.4.2" + }, "asynctest": { "hashes": [ "sha256:5da6118a7e6d6b54d83a8f7197769d046922a44d2a99c21382f0a6e4fadae676", @@ -142,6 +150,14 @@ "index": "pypi", "version": "==0.13.0" }, + "atomicwrites": { + "hashes": [ + "sha256:6d1784dea7c0c8d4a5172b6c620f40b6e4cbfdf96d783691f2e1302a7b88e197", + "sha256:ae70396ad1a434f9c7046fd2dd196fc04b12f9e91ffb859164193be8b6168a7a" + ], + "markers": "sys_platform == 'win32'", + "version": "==1.4.0" + }, "attrs": { "hashes": [ "sha256:2d27e3784d7a565d36ab851fe94887c5eccd6a463168875832a1be79c82828b4", @@ -313,6 +329,55 @@ "index": "pypi", "version": "==0.4.4" }, + "coverage": { + "extras": [ + "toml" + ], + "hashes": [ + "sha256:03e2a7826086b91ef345ff18742ee9fc47a6839ccd517061ef8fa1976e652ce9", + "sha256:07e6db90cd9686c767dcc593dff16c8c09f9814f5e9c51034066cad3373b914d", + "sha256:18d520c6860515a771708937d2f78f63cc47ab3b80cb78e86573b0a760161faf", + "sha256:1ebf730d2381158ecf3dfd4453fbca0613e16eaa547b4170e2450c9707665ce7", + "sha256:21b7745788866028adeb1e0eca3bf1101109e2dc58456cb49d2d9b99a8c516e6", + "sha256:26e2deacd414fc2f97dd9f7676ee3eaecd299ca751412d89f40bc01557a6b1b4", + "sha256:2c6dbb42f3ad25760010c45191e9757e7dce981cbfb90e42feef301d71540059", + "sha256:2fea046bfb455510e05be95e879f0e768d45c10c11509e20e06d8fcaa31d9e39", + "sha256:34626a7eee2a3da12af0507780bb51eb52dca0e1751fd1471d0810539cefb536", + "sha256:37d1141ad6b2466a7b53a22e08fe76994c2d35a5b6b469590424a9953155afac", + "sha256:46191097ebc381fbf89bdce207a6c107ac4ec0890d8d20f3360345ff5976155c", + "sha256:4dd8bafa458b5c7d061540f1ee9f18025a68e2d8471b3e858a9dad47c8d41903", + "sha256:4e21876082ed887baed0146fe222f861b5815455ada3b33b890f4105d806128d", + "sha256:58303469e9a272b4abdb9e302a780072c0633cdcc0165db7eec0f9e32f901e05", + "sha256:5ca5aeb4344b30d0bec47481536b8ba1181d50dbe783b0e4ad03c95dc1296684", + "sha256:68353fe7cdf91f109fc7d474461b46e7f1f14e533e911a2a2cbb8b0fc8613cf1", + "sha256:6f89d05e028d274ce4fa1a86887b071ae1755082ef94a6740238cd7a8178804f", + "sha256:7a15dc0a14008f1da3d1ebd44bdda3e357dbabdf5a0b5034d38fcde0b5c234b7", + "sha256:8bdde1177f2311ee552f47ae6e5aa7750c0e3291ca6b75f71f7ffe1f1dab3dca", + "sha256:8ce257cac556cb03be4a248d92ed36904a59a4a5ff55a994e92214cde15c5bad", + "sha256:8cf5cfcb1521dc3255d845d9dca3ff204b3229401994ef8d1984b32746bb45ca", + "sha256:8fbbdc8d55990eac1b0919ca69eb5a988a802b854488c34b8f37f3e2025fa90d", + "sha256:9548f10d8be799551eb3a9c74bbf2b4934ddb330e08a73320123c07f95cc2d92", + "sha256:96f8a1cb43ca1422f36492bebe63312d396491a9165ed3b9231e778d43a7fca4", + "sha256:9b27d894748475fa858f9597c0ee1d4829f44683f3813633aaf94b19cb5453cf", + "sha256:9baff2a45ae1f17c8078452e9e5962e518eab705e50a0aa8083733ea7d45f3a6", + "sha256:a2a8b8bcc399edb4347a5ca8b9b87e7524c0967b335fbb08a83c8421489ddee1", + "sha256:acf53bc2cf7282ab9b8ba346746afe703474004d9e566ad164c91a7a59f188a4", + "sha256:b0be84e5a6209858a1d3e8d1806c46214e867ce1b0fd32e4ea03f4bd8b2e3359", + "sha256:b31651d018b23ec463e95cf10070d0b2c548aa950a03d0b559eaa11c7e5a6fa3", + "sha256:b78e5afb39941572209f71866aa0b206c12f0109835aa0d601e41552f9b3e620", + "sha256:c76aeef1b95aff3905fb2ae2d96e319caca5b76fa41d3470b19d4e4a3a313512", + "sha256:dd035edafefee4d573140a76fdc785dc38829fe5a455c4bb12bac8c20cfc3d69", + "sha256:dd6fe30bd519694b356cbfcaca9bd5c1737cddd20778c6a581ae20dc8c04def2", + "sha256:e5f4e1edcf57ce94e5475fe09e5afa3e3145081318e5fd1a43a6b4539a97e518", + "sha256:ec6bc7fe73a938933d4178c9b23c4e0568e43e220aef9472c4f6044bfc6dd0f0", + "sha256:f1555ea6d6da108e1999b2463ea1003fe03f29213e459145e70edbaf3e004aaa", + "sha256:f5fa5803f47e095d7ad8443d28b01d48c0359484fec1b9d8606d0e3282084bc4", + "sha256:f7331dbf301b7289013175087636bbaf5b2405e57259dd2c42fdcc9fcc47325e", + "sha256:f9987b0354b06d4df0f4d3e0ec1ae76d7ce7cbca9a2f98c25041eb79eec766f1", + "sha256:fd9e830e9d8d89b20ab1e5af09b32d33e1a08ef4c4e14411e559556fd788e6b2" + ], + "version": "==6.3.2" + }, "cryptography": { "hashes": [ "sha256:0a817b961b46894c5ca8a66b599c745b9a3d9f822725221f0e0fe49dc043a3a3", @@ -490,6 +555,20 @@ "index": "pypi", "version": "==4.11.1" }, + "iniconfig": { + "hashes": [ + "sha256:011e24c64b7f47f6ebd835bb12a743f2fbe9a26d4cecaa7f53bc4f35ee9da8b3", + "sha256:bc3af051d7d14b2ee5ef9969666def0cd1a000e121eaea580d4a313df4b37f32" + ], + "version": "==1.1.1" + }, + "mock": { + "hashes": [ + "sha256:122fcb64ee37cfad5b3f48d7a7d51875d7031aaf3d8be7c42e2bee25044eee62", + "sha256:7d3fbbde18228f4ff2f1f119a45cdffa458b4c0dee32eb4d2bb2f82554bac7bc" + ], + "version": "==4.0.3" + }, "multidict": { "hashes": [ "sha256:0327292e745a880459ef71be14e709aaea2f783f3537588fb4ed09b6c01bca60", @@ -591,6 +670,13 @@ "index": "pypi", "version": "==1.21.5" }, + "packaging": { + "hashes": [ + "sha256:dd47c42927d89ab911e606518907cc2d3a1f38bbd026385970643f9c5b8ecfeb", + "sha256:ef103e05f519cdc783ae24ea4e2e0f508a9c99b2d4969652eed6a2e1ea5bd522" + ], + "version": "==21.3" + }, "peewee": { "hashes": [ "sha256:69c1b88dc89b184231cc1ce6df241075aca5cec43e89749cc4a63108f9ceea47" @@ -624,6 +710,13 @@ ], "version": "==2.1.2" }, + "pluggy": { + "hashes": [ + "sha256:4224373bacce55f955a878bf9cfa763c1e360858e330072059e10bad68531159", + "sha256:74134bbf457f031a36d68416e1509f34bd5ccc019f0bcc952c7b909d06b37bd3" + ], + "version": "==1.0.0" + }, "psycopg2": { "hashes": [ "sha256:06f32425949bd5fe8f625c49f17ebb9784e1e4fe928b7cce72edc36fb68e4c0c", @@ -641,6 +734,13 @@ "index": "pypi", "version": "==2.9.3" }, + "py": { + "hashes": [ + "sha256:51c75c4126074b472f746a24399ad32f6053d1b34b68d2fa41e558e6f4a98719", + "sha256:607c53218732647dff4acdfcd50cb62615cedf612e72d1724fb1a0cc6405b378" + ], + "version": "==1.11.0" + }, "pyasn1": { "hashes": [ "sha256:39c7e2ec30515947ff4e87fb6f456dfc6e84857d34be479c9d4a4ba4bf46aa5d", @@ -722,6 +822,29 @@ "index": "pypi", "version": "==1.5.0" }, + "pyparsing": { + "hashes": [ + "sha256:18ee9022775d270c55187733956460083db60b37d0d0fb357445f3094eed3eea", + "sha256:a6c06a88f252e6c322f65faf8f418b16213b51bdfaece0524c1c1bc30c63c484" + ], + "version": "==3.0.7" + }, + "pytest": { + "hashes": [ + "sha256:b555252a95bbb2a37a97b5ac2eb050c436f7989993565f5e0c9128fcaacadd0e", + "sha256:f1089d218cfcc63a212c42896f1b7fbf096874d045e1988186861a1a87d27b47" + ], + "index": "pypi", + "version": "==7.1.0" + }, + "pytest-cov": { + "hashes": [ + "sha256:578d5d15ac4a25e5f961c938b85a05b09fdaae9deef3bb6de9a6e766622ca7a6", + "sha256:e7f0f5b1617d2210a2cabc266dfe2f4c75a8d32fb89eafb7ad9d06f6d076d470" + ], + "index": "pypi", + "version": "==3.0.0" + }, "python-dateutil": { "hashes": [ "sha256:0123cacc1627ae19ddf3c27a5de5bd67ee4586fbdd6440d9748f8abb483d3e86", @@ -835,6 +958,13 @@ "index": "pypi", "version": "==0.17.1" }, + "tomli": { + "hashes": [ + "sha256:939de3e7a6161af0c887ef91b7d41a53e7c5a1ca976325f429cb46ea9bc30ecc", + "sha256:de526c12914f0c550d15924c62d72abc48d6fe7364aa87328337a31007fe8a4f" + ], + "version": "==2.0.1" + }, "twitchapi": { "hashes": [ "sha256:f0ee5388911154375170a83df9a18e8a698fe382cea5d94a3e33ad27a7ce9133" diff --git a/brdevstreamers (1).db b/brdevstreamers (1).db deleted file mode 100644 index 5c23535366e16c998f06dbbfdd2b6fcce93d9900..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 32768 zcmeHQU2Gdyb{>k7XpEEzsRIZGmJLO$#(n?e?kKmqiOS4~wGhF3>I7qR2zB=tCAL z`p|xNC~}4r6}d(pHoGH0;+^|*?z!JN_ue_@9$u~7x~4Z6DVbJ{Hi#tb5ds0>EFpp* zwD7wNznz~j62qMbd>?Q5VpM89EC^UB9K;Ye@T_N(~;3id% zxV}IcX}YbN7Ub;(9rDo)yFtqqt!*i(>c%~$^~)Vi%6g;HRJPP_+}9h>-Yxl?KzsZ3 zs)C}My-jA<(}B>~p+kYkryYDOcAr{W`|nUMM(xZFO563IFqb+C^pQ&4`&2rluM!)~#Bj6+8Bj6+8Bj6+8Bj6+8Bj6+8Bj6+8 zBj6+OGmO9p#ukYG41y22>1y22>1y22>1y22>1y22>1y22>e_k z5SrmZ{d3XJ1o12JSL0uce-v92|4bZ>eil!}zb-zG9gck%H)8L^e=L3@{#UV2;``$l z#ANI*#Q%&HW7XJyiC-7b#{O0OuK3Tfk@)Y6b@67r6gwWf75^kIh-0z$el9`v2kj%^ zBj6+8Bj6+8Bj6+8Bj6+8BkA-zf zMJRc>VH#D2??|(xJ^M^>I#97y1XNdPjT$PQi3cYG_fX5CrBbn2oEC$}0tPM9M$=$- zPw@H|ber0UFSik>eQ%2A;G5PJd~2dit5s7v&Rb}VT2@n|waIAkXy6`gGTSbdCV1s? zrIM^L%b@s@fIYrHI3G}S%k2DeX361TF;H#lc8OK>I!n^3!YpvDX9~wSaTGl&*VCEQ z^J76NaMz?}-PWru3;-NdD6@}psx@k(zVA;=?$wFE|&#qiq^CL}c3a%7gv9lBTn=xmIIUGo9YYiSe-{F$f(pOgdxQ zyEix+fG{5EN!wI)S}kOsaWG(}@t|tnOYeyV#~pyty=Boj@2jP&4b$Rs#c(K%@u5|K zrI}Pspw9m`Xu}kFK2PgLh1OBC(bP=2Sd4NS5RqQJ7^@wV*?CY8TJwR)Z`E+`xAY%zE(o;6eb*(j@1Qsk%lro!Smp$%ZzxCpf*kRHZH5yjx>jqa6D`G9g6% zCiWlkZ2XtSO7v#rOk^VVLHtH^B>H3VALEC_KZyT(G#meBY%KOGvF}B`D*hz;r{W9Z z*J4Z2qmkc@Wus>Fz33IT*`vWTy~d=M z^30)N-fLL8?WVaI(ls&N{1cpBkFn{cH+3X<3YLV&@N`=q=M=jpsGC2zX|OrQsGB{( zN%feeZrM1uKRU)K|KtX$TTG4zuWw_jx|NRcp?8f|?;xHZ4_+E%z`AXZ^8WixS-1E( z&bnjNp4FIo5B85@igkoH?KgAXriUkkR|9vq8oXZq4sp)A#;>>JSq^2#5O#@< zaV6Pm61x=+as~4m$8OO9F7G}w*)84A)z)h;d%4-iyWMI$yRG-~*1cx5TlkD4pN>K8 zrMu_ZpzJZO-6nC)YoD3zmd3bP2N>LL4UyLvV0ybXqRwpUHNxG}2-ongo$cm_`A}e( zpKZHNOv`}TZ*zs%&4Jf+*Hm{K?czmUVmGwrkR#ty?BM zH=Q+Pn3wih_-@Bz5*q=tVu z{O<5v@b7|6kn(?g1is`5yg3yTr0Mag>jS){nDEu6O6{syhXXT}$;_vu`BZvk75Cg+ zUps&8%K58GL6K#N6?2(vp;%HhEt@NpSYFFeDW5B)O0OOd3G3nFtBfoOX``vFBkh z9I7fQvXqxgX^fp_m9(a+g?v`kG?vL_it5YbA>r6O2zNLAmTu^4Wm?0PI2P3(!FN3f zX04biD47DKd95Vnv@DfXRnZh#Eo4*ad|p{5pgZNI>s_^?XAz_Y#(6^gC0SuXZ zhNknfRFt)>lxK1=t7I81Q*`=4#i*BQu{VcvX2P*}eW86mAw|`xOqS9tsXnC>-NB zDH%+BCyjgU2zcg-(Ze&t#Y#qnA5y3$t5%+p@mCHH(q=vi(|MuCgc;2APKc}?0^#v3 zOeixf|AKadLm6)?$MjU!0$x<)_#cdNWVC%to7ibN(YnNNkf+Qg@iFa(H%=< zX9S9}oK+>7FJ|)CKbVqE$y7b;N=i!83Z?vw7<7O`(ZlEG;INYhydj2! zG}qa!wqH*dgHgujrj}T?fQ>_;YCgr3Y*CSld8vTCMNMaxqak7H7^r*gzq?JlQNp40 zHcN3zOHnzSV>wC9DJ+-Llw7GO(^R^YRaimIuSOubqoCO*Icy;1re4(sbE@nhwAaI+ zI=7W7KkYVg_=|;}H5%Kj@H-KIV>gKN-J_4?vX1-YsL?&Lemd6g>>8}fwpr4tWkTU_ zO8j)BS3)4o=W`$9+(o0YZKmE%XuLcMmE%OW+WntPm#RfcmNO+*!1kg-!Q?zG6q&5i zT!EEX?#>A21{YzE;y#%G^XZ%-NlIE#lq}u@b7?B2OC?z;O8FvX?A76rFw192uP36X zqrnKLGICMH+JHCXbb)E5e7;nY@@ghm$jTDBc{vEC`3l@aYv+|%*@5F%hCr8d+(WnL zVKBox5)KR^`~+_+utADck~B3V$!V!n%BTe`mu0y^T4oZ{vf@Zqm=Qi4iWg$5;%|y? zi3KsX>tCXO9{r7IY4oj;dg$E|A^LB_e?R=k!*_>Y93Bn+L-3oy2SeZA_1i;l4Hbrl zhJI({V(45bIvN=HLEzNr4+FbLJ{JB-`0$H108{v;!n8y5jL;K-)AQhUM$J!$g-KBm zMKQn66QSctx?YfUMoLY&9rSondmg+UtY4Un2*)l7$Bs=(>nmOVX{FJq+b^CxskJYz zOqRy!XovYldG;8!hJJP3H9XXwJ=d)__b(`R7%L;+hmnh6`5O|j`m1;G_D1GwJ&tUFsu$BJ6dDcmK>h@9D1-ZrAl@ zc>8-X3cYsPT@nU3vS|2%+hT6P?Z^p?eP0O}N#MPq-=uRdG&nI-hQ>H^;H= z%?gu~lc&U8G%=d5*q%9$bQ1;;vQwXITBuZmYA$!a~vM z{<;8>4~W7Xoa<6}yhn`i#cq6Dy!1|dfcMAAf@)~&G7n2U&r<`cxfE|yH{Qom#hS!G zd?4BQ7P-9fEwm&~Q?hDN1)-lN!9j;fE?SIL5zLD1#h?UHgM7o;Tn=?a31Wk}Xd&!z zei0iCBP}>zOoAxP3RX(IrW0%@6>1Zz=v5wWh%E_KkZ&Sb6bmAjLIjN(b`9~7d?BO_ zumjEzw}{S5^pR7U_EbtCP7+i)BC|4j9b3RwEkh-bcJZ(Cgo`pzS1v?!^ip>l2De1x!D00Ll5i}2uJQZ|9fChP|P zk6Ts!5w!>(wA}=?*)R#W2MIQ;_PnU`PfSfdn=l6GjbZW&-H|r%%O@cw974J6Bv}D_ z{Kk1zuhVLKVCSfM4+O}JI+-9ZpG(4?P7u7oaJHyjV~O^BGznbpxKAC}>UI;OMTobq zlH6=t=v=SWrlrFsM?*dg2Ozr19MJ5%_43Qt8&;CBlV1Hl5q7t%Zhkl6&ZCLlVPT)M zmgWT<%MLu5P3-EtcNFp7;U3U>(=J628=i@WH+Kq|zeT~_%|pN4lv{gd)UD|r84#hKqd(P{o`c8b;&jlmG+$|W9ht8}|_Z|IqFz#3um~SU_ zqlND#oV<8bzD*Y{^;nuc4>ekYyS$H}%jOL|(OJK++GF=Xgm`Dd(YKpAHy1D^Ixoo< zQ8zx;bSMJU!O6x_go)1ku#@X_C@d_9!dEX{y8Y(%2h^S6+`oT6*}1cyAH8EtCmR)t z`7=*DLG1GdoOGoN>6~=3)2w})ztdo~f+YrV?$quSZr+jkcz7By^$_ZLa9R99 zWhG*Q1jWyU*h*{)AO4SzfRBKWfRBKWfRBKWfRBKWfRBKWfRDhJ6M>=0L3}6YIxdvc zg7+Q-Z`$iaO}x;lUqpTP+b-U;YoEUJ!fZrHUlQiivy1QE-g=8dzkQ4Y(g(^*_g_6< zv>r6w>hsR63+}V?2q&SNv)1#}xun5mr0oT-dA#7Sbgy(E*Z7?diA|e6(>Chcj#aq) zmURX3h1?Bz(_-*-&6Aaz3HU&q<5L{@S)>97DgMEp%}5qLKsW^8DckglnsCb9 List[StreamViewModel]: - streams = twitch.get_streams(language="pt", game_id="1469308723") - - streams_model: List[StreamViewModel] = [] - stream_users = [] - for s in streams["data"]: - stream = StreamViewModel() - stream.id = s["id"] - stream.user_id = s["user_id"] - stream.user_name = s["user_name"] - stream.user_login = s["user_login"] - stream.title = s["title"] - stream.viewer_count = s["viewer_count"] - stream.started_at = s["started_at"] - stream.thumbnail_url = s["thumbnail_url"] - - stream.tags = s["tag_ids"] - - streamer = get_streamer(s["user_id"]) - stream.profile_image_url = streamer["profile_image_url"] - stream.description = streamer["description"][:100] + "..." - - stream_users.append(s["user_login"]) - streams_model.append(stream) - - try: - streamers = get_users_by_name(stream_users) - for s in streamers: - for stream in streams_model: - if stream.user_login == s.user_login: - stream.github_url = s.github - stream.twitter_url = s.twitter - stream.instagram_url = s.instagram - stream.linkedin_url = s.linkedin - stream.discord_url = s.discord - stream.bio = s.bio - break - except Exception as e: - print(e) - shuffle(streams_model) - return streams_model - - -def get_streamer(id): - return twitch.get_users(user_ids=[id])["data"][0] - - -def get_vods() -> List[VodViewModel]: - vods = twitch.get_videos(language="pt", game_id="1469308723", period=TimePeriod.DAY) - vods_model: List[VodViewModel] = [] - vod_users = [] - - for s in vods["data"]: - if is_long_enough(s["duration"]): - stream = VodViewModel() + + +class TwitchService: + + config, twitch = None, None + + def __init__(self): + config = dotenv_values(".env") + twitch = Twitch(config["CLIENT_ID"], config["CLIENT_SECRET"]) + + + + + def get_streamers(self) -> List[StreamViewModel]: + streams = self.twitch.get_streams(language="pt", game_id="1469308723") + streams_model: List[StreamViewModel] = [] + stream_users = [] + for s in streams["data"]: + stream = StreamViewModel() stream.id = s["id"] stream.user_id = s["user_id"] stream.user_name = s["user_name"] stream.user_login = s["user_login"] stream.title = s["title"] - stream.viewer_count = s["view_count"] - stream.started_at = s["published_at"] + stream.viewer_count = s["viewer_count"] + stream.started_at = s["started_at"] stream.thumbnail_url = s["thumbnail_url"] - stream.stream_id = s["id"] - stream.duration = s["duration"] - streamer = get_streamer(s["user_id"]) + stream.tags = s["tag_ids"] + + streamer = self.get_streamer(s["user_id"]) stream.profile_image_url = streamer["profile_image_url"] stream.description = streamer["description"][:100] + "..." - vod_users.append(s["user_login"]) - vods_model.append(stream) - try: - streamers = get_users_by_name(vod_users) - for s in streamers: - for stream in vods_model: - if stream.user_login == s.user_login: - stream.github_url = s.github - stream.twitter_url = s.twitter - stream.instagram_url = s.instagram - stream.linkedin_url = s.linkedin - stream.discord_url = s.discord - stream.bio = s.bio - break - except Exception as e: - print(e) - return vods_model - - -def is_long_enough(duration): - return "h" in duration - - -def get_tags() -> List[TagViewModel]: - streams = twitch.get_streams(language="pt", game_id="1469308723") - tag_ids = get_tag_list_from_streams(streams) - tags = twitch.get_all_stream_tags(tag_ids=tag_ids) - tags_dict = {} - for tag in tags["data"]: - tag_model = TagViewModel(id=tag["tag_id"], name=tag["localization_names"]["pt-br"]) - tags_dict[tag["tag_id"]] = tag_model - return list(tags_dict.values()) - - -def get_tag_list_from_streams(streams): - tag_ids = [] - for s in streams["data"]: - for tag in s["tag_ids"]: - tag_ids.append(tag) - return tag_ids + stream_users.append(s["user_login"]) + streams_model.append(stream) + + try: + streamers = get_users_by_name(stream_users) + for s in streamers: + for stream in streams_model: + if stream.user_login == s.user_login: + stream.github_url = s.github + stream.twitter_url = s.twitter + stream.instagram_url = s.instagram + stream.linkedin_url = s.linkedin + stream.discord_url = s.discord + stream.bio = s.bio + break + except Exception as e: + print(e) + shuffle(streams_model) + return streams_model + + + def get_streamer(self, id): + return self.twitch.get_users(user_ids=[id])["data"][0] + + + def get_vods(self) -> List[VodViewModel]: + vods = self.twitch.get_videos(language="pt", game_id="1469308723", period=TimePeriod.DAY) + vods_model: List[VodViewModel] = [] + vod_users = [] + + for s in vods["data"]: + if self.is_long_enough(s["duration"]): + stream = VodViewModel() + stream.id = s["id"] + stream.user_id = s["user_id"] + stream.user_name = s["user_name"] + stream.user_login = s["user_login"] + stream.title = s["title"] + stream.viewer_count = s["view_count"] + stream.started_at = s["published_at"] + stream.thumbnail_url = s["thumbnail_url"] + stream.stream_id = s["id"] + stream.duration = s["duration"] + + streamer = self.get_streamer(s["user_id"]) + stream.profile_image_url = streamer["profile_image_url"] + stream.description = streamer["description"][:100] + "..." + + vod_users.append(s["user_login"]) + vods_model.append(stream) + try: + streamers = get_users_by_name(vod_users) + for s in streamers: + for stream in vods_model: + if stream.user_login == s.user_login: + stream.github_url = s.github + stream.twitter_url = s.twitter + stream.instagram_url = s.instagram + stream.linkedin_url = s.linkedin + stream.discord_url = s.discord + stream.bio = s.bio + break + except Exception as e: + print(e) + return vods_model + + + def is_long_enough(self, duration): + return "h" in duration + + + def get_tags(self) -> List[TagViewModel]: + streams = self.twitch.get_streams(language="pt", game_id="1469308723") + tag_ids = self.get_tag_list_from_streams(streams) + tags = self.twitch.get_all_stream_tags(tag_ids=tag_ids) + tags_dict = {} + for tag in tags["data"]: + tag_model = TagViewModel(id=tag["tag_id"], name=tag["localization_names"]["pt-br"]) + tags_dict[tag["tag_id"]] = tag_model + return list(tags_dict.values()) + + + def get_tag_list_from_streams(self, streams): + tag_ids = [] + for s in streams["data"]: + for tag in s["tag_ids"]: + tag_ids.append(tag) + return tag_ids diff --git a/test/__init__.py b/test/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/test/service/__init__.py b/test/service/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/test/service/test_twitch_service.py b/test/service/test_twitch_service.py new file mode 100644 index 0000000..1caf4b2 --- /dev/null +++ b/test/service/test_twitch_service.py @@ -0,0 +1,31 @@ +import unittest +from unittest.mock import MagicMock, Mock, patch +import pytest +from twitchAPI.twitch import Twitch + +from service.twitch_service import TwitchService + + + + + + +class TestTwitchService(unittest.TestCase): + + + def mock_twitch(self): + twitch = Mock() + response = {"data": [{"id": "44960190524", "user_id": "166681140", "user_login": "marcobrunodev", "user_name": "MarcoBrunoDev", "game_id": "1469308723", "game_name": "Software and Game Development", "type": "live", "title": "#28 Pet Snoar | Pet Runner | !Alura", "viewer_count": 158, "started_at": "2022-03-14T11:00:48Z", "language": "pt", "thumbnail_url": "https://static-cdn.jtvnw.net/previews-ttv/live_user_marcobrunodev-{width}x{height}.jpg", "tag_ids": ["39ee8140-901a-4762-bfca-8260dea1310f", "a106f013-6e26-4f27-9a4b-01e9d76084e2", "6e23d976-33ec-47e8-b22b-3727acd41862", "f588bd74-e496-4d11-9169-3597f38a5d25", "6f86127d-6051-4a38-94bb-f7b475dde109", "c23ce252-cf78-4b98-8c11-8769801aaf3a"], "is_mature": 'false'}], "pagination": {"cursor": "eyJiIjp7IkN1cnNvciI6ImV5SnpJam94TlRndU1URXdOVEl3TlRreE9UZzFNRGdzSW1RaU9tWmhiSE5sTENKMElqcDBjblZsZlE9PSJ9LCJhIjp7IkN1cnNvciI6IiJ9fQ"}} + twitch.get_streams = MagicMock(return_value=response) + + streamer = {"data": [{"profile_image_url": '', 'description': ''}]} + twitch.get_users = MagicMock(return_value=streamer) + return twitch + + # @patch("service.twitch_service.twitch", return_value=mock_twitch()) + def test_get_streamers(self): + twitch_service = TwitchService() + twitch_service.twitch = self.mock_twitch() + streamers = twitch_service.get_streamers() + self.assertEqual(len(streamers), 1) + self.assertEqual(streamers[0].user_login, "marcobrunodev") \ No newline at end of file diff --git a/view_model/__init__.py b/view_model/__init__.py new file mode 100644 index 0000000..e69de29 From 983621b747197efff68cf0961f31029d64544e76 Mon Sep 17 00:00:00 2001 From: Mendes Date: Mon, 14 Mar 2022 15:42:19 +0000 Subject: [PATCH 06/25] adding first test --- service/twitch_service.py | 9 ------- test/service/test_twitch_service.py | 41 ++++++++++++++++++++++------- 2 files changed, 32 insertions(+), 18 deletions(-) diff --git a/service/twitch_service.py b/service/twitch_service.py index 6f281d5..cabbf5a 100644 --- a/service/twitch_service.py +++ b/service/twitch_service.py @@ -13,7 +13,6 @@ from view_model.vod_viewmodel import VodViewModel - class TwitchService: config, twitch = None, None @@ -22,9 +21,6 @@ def __init__(self): config = dotenv_values(".env") twitch = Twitch(config["CLIENT_ID"], config["CLIENT_SECRET"]) - - - def get_streamers(self) -> List[StreamViewModel]: streams = self.twitch.get_streams(language="pt", game_id="1469308723") streams_model: List[StreamViewModel] = [] @@ -66,11 +62,9 @@ def get_streamers(self) -> List[StreamViewModel]: shuffle(streams_model) return streams_model - def get_streamer(self, id): return self.twitch.get_users(user_ids=[id])["data"][0] - def get_vods(self) -> List[VodViewModel]: vods = self.twitch.get_videos(language="pt", game_id="1469308723", period=TimePeriod.DAY) vods_model: List[VodViewModel] = [] @@ -112,11 +106,9 @@ def get_vods(self) -> List[VodViewModel]: print(e) return vods_model - def is_long_enough(self, duration): return "h" in duration - def get_tags(self) -> List[TagViewModel]: streams = self.twitch.get_streams(language="pt", game_id="1469308723") tag_ids = self.get_tag_list_from_streams(streams) @@ -127,7 +119,6 @@ def get_tags(self) -> List[TagViewModel]: tags_dict[tag["tag_id"]] = tag_model return list(tags_dict.values()) - def get_tag_list_from_streams(self, streams): tag_ids = [] for s in streams["data"]: diff --git a/test/service/test_twitch_service.py b/test/service/test_twitch_service.py index 1caf4b2..142d9fa 100644 --- a/test/service/test_twitch_service.py +++ b/test/service/test_twitch_service.py @@ -6,20 +6,43 @@ from service.twitch_service import TwitchService - - - - class TestTwitchService(unittest.TestCase): - - def mock_twitch(self): twitch = Mock() - response = {"data": [{"id": "44960190524", "user_id": "166681140", "user_login": "marcobrunodev", "user_name": "MarcoBrunoDev", "game_id": "1469308723", "game_name": "Software and Game Development", "type": "live", "title": "#28 Pet Snoar | Pet Runner | !Alura", "viewer_count": 158, "started_at": "2022-03-14T11:00:48Z", "language": "pt", "thumbnail_url": "https://static-cdn.jtvnw.net/previews-ttv/live_user_marcobrunodev-{width}x{height}.jpg", "tag_ids": ["39ee8140-901a-4762-bfca-8260dea1310f", "a106f013-6e26-4f27-9a4b-01e9d76084e2", "6e23d976-33ec-47e8-b22b-3727acd41862", "f588bd74-e496-4d11-9169-3597f38a5d25", "6f86127d-6051-4a38-94bb-f7b475dde109", "c23ce252-cf78-4b98-8c11-8769801aaf3a"], "is_mature": 'false'}], "pagination": {"cursor": "eyJiIjp7IkN1cnNvciI6ImV5SnpJam94TlRndU1URXdOVEl3TlRreE9UZzFNRGdzSW1RaU9tWmhiSE5sTENKMElqcDBjblZsZlE9PSJ9LCJhIjp7IkN1cnNvciI6IiJ9fQ"}} + response = { + "data": [ + { + "id": "44960190524", + "user_id": "166681140", + "user_login": "marcobrunodev", + "user_name": "MarcoBrunoDev", + "game_id": "1469308723", + "game_name": "Software and Game Development", + "type": "live", + "title": "#28 Pet Snoar | Pet Runner | !Alura", + "viewer_count": 158, + "started_at": "2022-03-14T11:00:48Z", + "language": "pt", + "thumbnail_url": "https://static-cdn.jtvnw.net/previews-ttv/live_user_marcobrunodev-{width}x{height}.jpg", + "tag_ids": [ + "39ee8140-901a-4762-bfca-8260dea1310f", + "a106f013-6e26-4f27-9a4b-01e9d76084e2", + "6e23d976-33ec-47e8-b22b-3727acd41862", + "f588bd74-e496-4d11-9169-3597f38a5d25", + "6f86127d-6051-4a38-94bb-f7b475dde109", + "c23ce252-cf78-4b98-8c11-8769801aaf3a", + ], + "is_mature": 'false', + } + ], + "pagination": { + "cursor": "eyJiIjp7IkN1cnNvciI6ImV5SnpJam94TlRndU1URXdOVEl3TlRreE9UZzFNRGdzSW1RaU9tWmhiSE5sTENKMElqcDBjblZsZlE9PSJ9LCJhIjp7IkN1cnNvciI6IiJ9fQ" + }, + } twitch.get_streams = MagicMock(return_value=response) streamer = {"data": [{"profile_image_url": '', 'description': ''}]} - twitch.get_users = MagicMock(return_value=streamer) + twitch.get_users = MagicMock(return_value=streamer) return twitch # @patch("service.twitch_service.twitch", return_value=mock_twitch()) @@ -28,4 +51,4 @@ def test_get_streamers(self): twitch_service.twitch = self.mock_twitch() streamers = twitch_service.get_streamers() self.assertEqual(len(streamers), 1) - self.assertEqual(streamers[0].user_login, "marcobrunodev") \ No newline at end of file + self.assertEqual(streamers[0].user_login, "marcobrunodev") From bf4ed89a8e068505a839700d3ddf9f9a7a486b74 Mon Sep 17 00:00:00 2001 From: Mendes Date: Mon, 14 Mar 2022 15:46:47 +0000 Subject: [PATCH 07/25] requirements --- Pipfile | 1 + Pipfile.lock | 82 ++++++++++++++++++++++++++++++++++++++++++++++- requirements.txt | Bin 1982 -> 2754 bytes 3 files changed, 82 insertions(+), 1 deletion(-) diff --git a/Pipfile b/Pipfile index cfc8e23..354aeb9 100644 --- a/Pipfile +++ b/Pipfile @@ -61,6 +61,7 @@ fastapi-cache2 = "*" pytest = "*" asyncmock = "*" pytest-cov = "*" +black = "*" [requires] python_version = "3.7" diff --git a/Pipfile.lock b/Pipfile.lock index d0776ff..b717450 100644 --- a/Pipfile.lock +++ b/Pipfile.lock @@ -1,7 +1,7 @@ { "_meta": { "hash": { - "sha256": "a0657f47e777ab57aefa8edbed8e3fc58a36b51ec4a522f16600d8decd2338c2" + "sha256": "4d0536022e1fc40e71c59ef748efcffb715cd055c8b2c79fb07cf280f53479bc" }, "pipfile-spec": 6, "requires": { @@ -166,6 +166,35 @@ "index": "pypi", "version": "==21.4.0" }, + "black": { + "hashes": [ + "sha256:07e5c049442d7ca1a2fc273c79d1aecbbf1bc858f62e8184abe1ad175c4f7cc2", + "sha256:0e21e1f1efa65a50e3960edd068b6ae6d64ad6235bd8bfea116a03b21836af71", + "sha256:1297c63b9e1b96a3d0da2d85d11cd9bf8664251fd69ddac068b98dc4f34f73b6", + "sha256:228b5ae2c8e3d6227e4bde5920d2fc66cc3400fde7bcc74f480cb07ef0b570d5", + "sha256:2d6f331c02f0f40aa51a22e479c8209d37fcd520c77721c034517d44eecf5912", + "sha256:2ff96450d3ad9ea499fc4c60e425a1439c2120cbbc1ab959ff20f7c76ec7e866", + "sha256:3524739d76b6b3ed1132422bf9d82123cd1705086723bc3e235ca39fd21c667d", + "sha256:35944b7100af4a985abfcaa860b06af15590deb1f392f06c8683b4381e8eeaf0", + "sha256:373922fc66676133ddc3e754e4509196a8c392fec3f5ca4486673e685a421321", + "sha256:5fa1db02410b1924b6749c245ab38d30621564e658297484952f3d8a39fce7e8", + "sha256:6f2f01381f91c1efb1451998bd65a129b3ed6f64f79663a55fe0e9b74a5f81fd", + "sha256:742ce9af3086e5bd07e58c8feb09dbb2b047b7f566eb5f5bc63fd455814979f3", + "sha256:7835fee5238fc0a0baf6c9268fb816b5f5cd9b8793423a75e8cd663c48d073ba", + "sha256:8871fcb4b447206904932b54b567923e5be802b9b19b744fdff092bd2f3118d0", + "sha256:a7c0192d35635f6fc1174be575cb7915e92e5dd629ee79fdaf0dcfa41a80afb5", + "sha256:b1a5ed73ab4c482208d20434f700d514f66ffe2840f63a6252ecc43a9bc77e8a", + "sha256:c8226f50b8c34a14608b848dc23a46e5d08397d009446353dad45e04af0c8e28", + "sha256:ccad888050f5393f0d6029deea2a33e5ae371fd182a697313bdbd835d3edaf9c", + "sha256:dae63f2dbf82882fa3b2a3c49c32bffe144970a573cd68d247af6560fc493ae1", + "sha256:e2f69158a7d120fd641d1fa9a921d898e20d52e44a74a6fbbcc570a62a6bc8ab", + "sha256:efbadd9b52c060a8fc3b9658744091cb33c31f830b3f074422ed27bad2b18e8f", + "sha256:f5660feab44c2e3cb24b2419b998846cbb01c23c7fe645fee45087efa3da2d61", + "sha256:fdb8754b453fb15fad3f72cd9cad3e16776f0964d67cf30ebcbf10327a3777a3" + ], + "index": "pypi", + "version": "==22.1.0" + }, "brotli": { "hashes": [ "sha256:12effe280b8ebfd389022aa65114e30407540ccb89b177d3fbc9a4f177c4bd5d", @@ -634,6 +663,13 @@ "index": "pypi", "version": "==6.0.2" }, + "mypy-extensions": { + "hashes": [ + "sha256:090fedd75945a69ae91ce1303b5824f428daf5a028d2f6ab8a299250a846f15d", + "sha256:2d82818f5bb3e369420cb3c4060a7970edba416647068eb4c5343488a6c604a8" + ], + "version": "==0.4.3" + }, "numpy": { "hashes": [ "sha256:00c9fa73a6989895b8815d98300a20ac993c49ac36c8277e8ffeaa3631c0dbbb", @@ -677,6 +713,13 @@ ], "version": "==21.3" }, + "pathspec": { + "hashes": [ + "sha256:7d15c4ddb0b5c802d161efc417ec1a2558ea2653c2e8ad9c19098201dc1c993a", + "sha256:e564499435a2673d586f6b2130bb5b95f04a3ba06f81b8f895b651a3c76aabb1" + ], + "version": "==0.9.0" + }, "peewee": { "hashes": [ "sha256:69c1b88dc89b184231cc1ce6df241075aca5cec43e89749cc4a63108f9ceea47" @@ -710,6 +753,13 @@ ], "version": "==2.1.2" }, + "platformdirs": { + "hashes": [ + "sha256:7535e70dfa32e84d4b34996ea99c5e432fa29a708d0f4e394bbcb2a8faa4f16d", + "sha256:bcae7cab893c2d310a711b70b24efb93334febe65f8de776ee320b517471e227" + ], + "version": "==2.5.1" + }, "pluggy": { "hashes": [ "sha256:4224373bacce55f955a878bf9cfa763c1e360858e330072059e10bad68531159", @@ -972,6 +1022,36 @@ "index": "pypi", "version": "==2.5.2" }, + "typed-ast": { + "hashes": [ + "sha256:0eb77764ea470f14fcbb89d51bc6bbf5e7623446ac4ed06cbd9ca9495b62e36e", + "sha256:1098df9a0592dd4c8c0ccfc2e98931278a6c6c53cb3a3e2cf7e9ee3b06153344", + "sha256:183b183b7771a508395d2cbffd6db67d6ad52958a5fdc99f450d954003900266", + "sha256:18fe320f354d6f9ad3147859b6e16649a0781425268c4dde596093177660e71a", + "sha256:26a432dc219c6b6f38be20a958cbe1abffcc5492821d7e27f08606ef99e0dffd", + "sha256:294a6903a4d087db805a7656989f613371915fc45c8cc0ddc5c5a0a8ad9bea4d", + "sha256:31d8c6b2df19a777bc8826770b872a45a1f30cfefcfd729491baa5237faae837", + "sha256:33b4a19ddc9fc551ebabca9765d54d04600c4a50eda13893dadf67ed81d9a098", + "sha256:42c47c3b43fe3a39ddf8de1d40dbbfca60ac8530a36c9b198ea5b9efac75c09e", + "sha256:525a2d4088e70a9f75b08b3f87a51acc9cde640e19cc523c7e41aa355564ae27", + "sha256:58ae097a325e9bb7a684572d20eb3e1809802c5c9ec7108e85da1eb6c1a3331b", + "sha256:676d051b1da67a852c0447621fdd11c4e104827417bf216092ec3e286f7da596", + "sha256:74cac86cc586db8dfda0ce65d8bcd2bf17b58668dfcc3652762f3ef0e6677e76", + "sha256:8c08d6625bb258179b6e512f55ad20f9dfef019bbfbe3095247401e053a3ea30", + "sha256:90904d889ab8e81a956f2c0935a523cc4e077c7847a836abee832f868d5c26a4", + "sha256:963a0ccc9a4188524e6e6d39b12c9ca24cc2d45a71cfdd04a26d883c922b4b78", + "sha256:bbebc31bf11762b63bf61aaae232becb41c5bf6b3461b80a4df7e791fabb3aca", + "sha256:bc2542e83ac8399752bc16e0b35e038bdb659ba237f4222616b4e83fb9654985", + "sha256:c29dd9a3a9d259c9fa19d19738d021632d673f6ed9b35a739f48e5f807f264fb", + "sha256:c7407cfcad702f0b6c0e0f3e7ab876cd1d2c13b14ce770e412c0c4b9728a0f88", + "sha256:da0a98d458010bf4fe535f2d1e367a2e2060e105978873c04c04212fb20543f7", + "sha256:df05aa5b241e2e8045f5f4367a9f6187b09c4cdf8578bb219861c4e27c443db5", + "sha256:f290617f74a610849bd8f5514e34ae3d09eafd521dceaa6cf68b3f4414266d4e", + "sha256:f30ddd110634c2d7534b2d4e0e22967e88366b0d356b24de87419cc4410c41b7" + ], + "markers": "python_version < '3.8' and implementation_name == 'cpython'", + "version": "==1.5.2" + }, "typing-extensions": { "hashes": [ "sha256:1a9462dcc3347a79b1f1c0271fbe79e844580bb598bafa1ed208b94da3cdcd42", diff --git a/requirements.txt b/requirements.txt index 2a1753217f46a9410bb39fb8e1fb351d255010e0..51cfbb67693dde5eee30673319f40fd161e70575 100644 GIT binary patch delta 707 zcmZWn%Syvg5S`dkQIS$bH*T^ip@=09wH7TRF2oOTRiud+N6@H44OyN^H4g-;$05x%1k zJ|dUKQebfb%p{!-VPlG6EedEvZPfJu7NS>|YMl%q4C$I2vdK^tt-Q{4Q#u>bDp|>? zwxSt6O+XVO-G^@ij75y-QlR!x=)mmcY$4X_jLG_Z_#VFcGj*)cqE%W(^L(&WGa%Gk zuCnMsbwIItDQqpt$PBJNbLB>=jU_NcifRLo$r&?{DF>K2fXI%pGD00@s_)$S$=^M6 zffHuC<5~A$sXE@+Eg1UH!mDQvMl#iLe!Do0SZQ3f^UqD@W~{j~UI_QpGHr2osfc!D zHxoggR+;~*n5Bk(EBVSlXr*-DLuq2Mrn)IO_bzn-*9LJU85YW8*x3`nB9R%$DO%YL zStOrErz4mlmpg#hfpZN!9eGb4jSKH07Rh4Mt``2cC5~sb2~vjW@Wp0CM)FiBYrpAR Bd*1*6 delta 62 zcmV-E0Kxyl6}}I!5&@Go0U(pK0UV?50RfZ32Ntt316%=<%mgHpAO#?kgatyA4hAHX UN(P*>P6u`Yli&y_v#1Fg0eqhmr~m)} From c1dfb2be79321ffd896b01e82ce9540df7a2c381 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Fl=C3=A1vio=20Mendes?= Date: Mon, 14 Mar 2022 15:48:34 +0000 Subject: [PATCH 08/25] Update ci.yml --- .github/workflows/ci.yml | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 54b3649..cf4f73e 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -12,6 +12,8 @@ jobs: with: python-version: 3.8 - name: Install dependencies - run: pip install black + run: pip install --no-cache-dir --upgrade -r /code/requirements.txt - name: Execute the black linter run: black -l 100 -S . --check + - name: Execute Tests + run: pytest --cov=. test From 8b8a6b5345129d6604b2c54a87ec1d6a5352b7cf Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Fl=C3=A1vio=20Mendes?= Date: Mon, 14 Mar 2022 15:49:49 +0000 Subject: [PATCH 09/25] Update ci.yml --- .github/workflows/ci.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index cf4f73e..6efb03e 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -12,7 +12,7 @@ jobs: with: python-version: 3.8 - name: Install dependencies - run: pip install --no-cache-dir --upgrade -r /code/requirements.txt + run: pip install --no-cache-dir --upgrade -r requirements.txt - name: Execute the black linter run: black -l 100 -S . --check - name: Execute Tests From f5d760ec333a1d27920988222a0183b61daf8dc5 Mon Sep 17 00:00:00 2001 From: Mendes Date: Mon, 14 Mar 2022 16:55:50 +0000 Subject: [PATCH 10/25] refactoring env vars --- controller/public_api.py | 13 ++++++++----- main.py | 13 +++++++------ model/initializer.py | 15 +++++++-------- model/reward_model.py | 15 +++++++-------- model/user_interaction_model.py | 14 +++++++------- model/user_model.py | 15 +++++++-------- persistence/user_dao.py | 13 ++++++------- persistence/user_interaction_dao.py | 13 ++++++------- poc.py | 2 +- service/stats_service.py | 13 ++++++------- service/twitch_service.py | 5 ++--- 11 files changed, 64 insertions(+), 67 deletions(-) diff --git a/controller/public_api.py b/controller/public_api.py index b314b5c..9ff0bc6 100644 --- a/controller/public_api.py +++ b/controller/public_api.py @@ -5,7 +5,7 @@ from fastapi_cache.decorator import cache from service.stats_service import compute_stat, get_stats, get_stats_summary -from service.twitch_service import get_streamers, get_tags, get_vods +from service.twitch_service import TwitchService from view_model.stats_viewmodel import StatsViewModel from view_model.stream_viewmodel import StreamViewModel from view_model.tag_viewmodel import TagViewModel @@ -26,14 +26,16 @@ @app_public.get("/streams", response_model=List[StreamViewModel]) @cache(expire=60) -async def root(): - return get_streamers() +async def streams(): + twitch_service = TwitchService() + return twitch_service.get_streamers() @app_public.get("/vods", response_model=List[VodViewModel]) @cache(expire=60) async def vods(): - return get_vods() + twitch_service = TwitchService() + return twitch_service.get_vods() @app_public.get("/stats", response_model=List[StatsViewModel]) @@ -43,7 +45,8 @@ async def stats(): @app_public.get("/tags", response_model=List[TagViewModel]) async def tags(): - return get_tags() + twitch_service = TwitchService() + return twitch_service.get_tags() @app_public.get("/stats/summary") diff --git a/main.py b/main.py index c3ec46d..0ec5c8e 100644 --- a/main.py +++ b/main.py @@ -1,6 +1,7 @@ +import os import aioredis import uvicorn -from dotenv import dotenv_values +from dotenv import load_dotenv from fastapi import FastAPI from fastapi.middleware.cors import CORSMiddleware from fastapi.middleware.gzip import GZipMiddleware @@ -11,7 +12,7 @@ from controller.public_api import app_public from model.initializer import init_db -config = dotenv_values(".env") +load_dotenv() init_db() origins = ["*"] @@ -34,7 +35,7 @@ @app.on_event("startup") async def startup(): redis = aioredis.from_url( - f"redis://{config['REDIS_HOST']}", encoding="utf8", decode_responses=True + f"redis://{os.environ['REDIS_HOST']}", encoding="utf8", decode_responses=True ) FastAPICache.init(RedisBackend(redis), prefix="fastapi-cache") @@ -47,14 +48,14 @@ async def shutdown(): app.add_middleware(GZipMiddleware) if __name__ == "__main__": - if config["ENV"] == "prod": + if os.environ["ENV"] == "prod": uvicorn.run( "main:app", host="0.0.0.0", port=8000, reload=True, - ssl_keyfile=config["PRIVATE_KEY"], - ssl_certfile=config["CERT"], + ssl_keyfile=os.environ["PRIVATE_KEY"], + ssl_certfile=os.environ["CERT"], ) else: uvicorn.run("main:app", host="0.0.0.0", port=8000, reload=True) diff --git a/model/initializer.py b/model/initializer.py index 26067e7..c2bbce8 100644 --- a/model/initializer.py +++ b/model/initializer.py @@ -1,17 +1,16 @@ -from dotenv import dotenv_values + +import os from model.reward_model import Reward from model.user_interaction_model import UserInteraction from model.user_model import User from peewee import PostgresqlDatabase -config = dotenv_values(".env") - db = PostgresqlDatabase( - config["DB_NAME"], - user=config["DB_USER"], - password=config["DB_PASS"], - host=config["DB_HOST"], - port=config["DB_PORT"], + os.environ["DB_NAME"], + user=os.environ["DB_USER"], + password=os.environ["DB_PASS"], + host=os.environ["DB_HOST"], + port=os.environ["DB_PORT"], ) diff --git a/model/reward_model.py b/model/reward_model.py index 35d42a3..a091b5f 100644 --- a/model/reward_model.py +++ b/model/reward_model.py @@ -1,14 +1,13 @@ -from dotenv import dotenv_values -from peewee import PostgresqlDatabase, CharField, IntegerField, Model -config = dotenv_values(".env") +import os +from peewee import PostgresqlDatabase, CharField, IntegerField, Model db = PostgresqlDatabase( - config["DB_NAME"], - user=config["DB_USER"], - password=config["DB_PASS"], - host=config["DB_HOST"], - port=config["DB_PORT"], + os.environ["DB_NAME"], + user=os.environ["DB_USER"], + password=os.environ["DB_PASS"], + host=os.environ["DB_HOST"], + port=os.environ["DB_PORT"], ) diff --git a/model/user_interaction_model.py b/model/user_interaction_model.py index 508c9e4..1166f6e 100644 --- a/model/user_interaction_model.py +++ b/model/user_interaction_model.py @@ -1,14 +1,14 @@ -from dotenv import dotenv_values + +import os from peewee import PostgresqlDatabase, CharField, DateField, Model -config = dotenv_values(".env") db = PostgresqlDatabase( - config["DB_NAME"], - user=config["DB_USER"], - password=config["DB_PASS"], - host=config["DB_HOST"], - port=config["DB_PORT"], + os.environ["DB_NAME"], + user=os.environ["DB_USER"], + password=os.environ["DB_PASS"], + host=os.environ["DB_HOST"], + port=os.environ["DB_PORT"], ) diff --git a/model/user_model.py b/model/user_model.py index 775321a..2e05d93 100644 --- a/model/user_model.py +++ b/model/user_model.py @@ -1,14 +1,13 @@ -from dotenv import dotenv_values -from peewee import PostgresqlDatabase, CharField, Model -config = dotenv_values(".env") +import os +from peewee import PostgresqlDatabase, CharField, Model db = PostgresqlDatabase( - config["DB_NAME"], - user=config["DB_USER"], - password=config["DB_PASS"], - host=config["DB_HOST"], - port=config["DB_PORT"], + os.environ["DB_NAME"], + user=os.environ["DB_USER"], + password=os.environ["DB_PASS"], + host=os.environ["DB_HOST"], + port=os.environ["DB_PORT"], ) diff --git a/persistence/user_dao.py b/persistence/user_dao.py index cd8d068..a8bf4a0 100644 --- a/persistence/user_dao.py +++ b/persistence/user_dao.py @@ -1,15 +1,14 @@ -from dotenv import dotenv_values -config = dotenv_values(".env") +import os from model.user_model import User from peewee import * db = PostgresqlDatabase( - config["DB_NAME"], - user=config["DB_USER"], - password=config["DB_PASS"], - host=config["DB_HOST"], - port=config["DB_PORT"], + os.environ["DB_NAME"], + user=os.environ["DB_USER"], + password=os.environ["DB_PASS"], + host=os.environ["DB_HOST"], + port=os.environ["DB_PORT"], ) diff --git a/persistence/user_interaction_dao.py b/persistence/user_interaction_dao.py index a6fb737..978d0ac 100644 --- a/persistence/user_interaction_dao.py +++ b/persistence/user_interaction_dao.py @@ -1,16 +1,15 @@ +import os from peewee import * from model.user_interaction_model import UserInteraction from model.user_model import User -from dotenv import dotenv_values -config = dotenv_values(".env") db = PostgresqlDatabase( - config["DB_NAME"], - user=config["DB_USER"], - password=config["DB_PASS"], - host=config["DB_HOST"], - port=config["DB_PORT"], + os.environ["DB_NAME"], + user=os.environ["DB_USER"], + password=os.environ["DB_PASS"], + host=os.environ["DB_HOST"], + port=os.environ["DB_PORT"], ) diff --git a/poc.py b/poc.py index d3a2763..f99d79c 100644 --- a/poc.py +++ b/poc.py @@ -4,7 +4,7 @@ config = dotenv_values(".env") -twitch = Twitch(config["CLIENT_ID"], config["CLIENT_SECRET"]) +twitch = Twitch(os.environ["CLIENT_ID"], os.environ["CLIENT_SECRET"]) users = twitch.get_users(user_ids=["227168488"]) diff --git a/service/stats_service.py b/service/stats_service.py index accd01e..ef2c60c 100644 --- a/service/stats_service.py +++ b/service/stats_service.py @@ -1,19 +1,18 @@ +import os from typing import List -from dotenv import dotenv_values from peewee import * from model.user_interaction_model import UserInteraction from view_model.stats_viewmodel import StatsViewModel -config = dotenv_values(".env") db = PostgresqlDatabase( - config["DB_NAME"], - user=config["DB_USER"], - password=config["DB_PASS"], - host=config["DB_HOST"], - port=config["DB_PORT"], + os.environ["DB_NAME"], + user=os.environ["DB_USER"], + password=os.environ["DB_PASS"], + host=os.environ["DB_HOST"], + port=os.environ["DB_PORT"], ) diff --git a/service/twitch_service.py b/service/twitch_service.py index cabbf5a..a3ea934 100644 --- a/service/twitch_service.py +++ b/service/twitch_service.py @@ -1,8 +1,8 @@ import json +import os from random import shuffle from typing import List -from dotenv import dotenv_values from twitchAPI.twitch import Twitch from twitchAPI.types import TimePeriod @@ -18,8 +18,7 @@ class TwitchService: config, twitch = None, None def __init__(self): - config = dotenv_values(".env") - twitch = Twitch(config["CLIENT_ID"], config["CLIENT_SECRET"]) + twitch = Twitch(os.environ["CLIENT_ID"], os.environ["CLIENT_SECRET"]) def get_streamers(self) -> List[StreamViewModel]: streams = self.twitch.get_streams(language="pt", game_id="1469308723") From c356fd28e35ac23597112c05de81b30da12ab2fa Mon Sep 17 00:00:00 2001 From: Mendes Date: Mon, 14 Mar 2022 19:42:59 +0000 Subject: [PATCH 11/25] formatting --- main.py | 2 +- model/initializer.py | 1 - model/reward_model.py | 1 - model/user_interaction_model.py | 1 - model/user_model.py | 1 - persistence/user_dao.py | 1 - 6 files changed, 1 insertion(+), 6 deletions(-) diff --git a/main.py b/main.py index 0ec5c8e..39459a4 100644 --- a/main.py +++ b/main.py @@ -12,7 +12,7 @@ from controller.public_api import app_public from model.initializer import init_db -load_dotenv() +load_dotenv() init_db() origins = ["*"] diff --git a/model/initializer.py b/model/initializer.py index c2bbce8..e3602e1 100644 --- a/model/initializer.py +++ b/model/initializer.py @@ -1,4 +1,3 @@ - import os from model.reward_model import Reward from model.user_interaction_model import UserInteraction diff --git a/model/reward_model.py b/model/reward_model.py index a091b5f..a4aac86 100644 --- a/model/reward_model.py +++ b/model/reward_model.py @@ -1,4 +1,3 @@ - import os from peewee import PostgresqlDatabase, CharField, IntegerField, Model diff --git a/model/user_interaction_model.py b/model/user_interaction_model.py index 1166f6e..b90176b 100644 --- a/model/user_interaction_model.py +++ b/model/user_interaction_model.py @@ -1,4 +1,3 @@ - import os from peewee import PostgresqlDatabase, CharField, DateField, Model diff --git a/model/user_model.py b/model/user_model.py index 2e05d93..b6cd211 100644 --- a/model/user_model.py +++ b/model/user_model.py @@ -1,4 +1,3 @@ - import os from peewee import PostgresqlDatabase, CharField, Model diff --git a/persistence/user_dao.py b/persistence/user_dao.py index a8bf4a0..de0f28b 100644 --- a/persistence/user_dao.py +++ b/persistence/user_dao.py @@ -1,4 +1,3 @@ - import os from model.user_model import User from peewee import * From 22c68b290a83aa64defd9e327ef24f54fd8e77f6 Mon Sep 17 00:00:00 2001 From: Mendes Date: Mon, 14 Mar 2022 19:56:42 +0000 Subject: [PATCH 12/25] adding env for test --- .env.example | 25 ++++++++++++++++++------- .env.test | 18 ++++++++++++++++++ Pipfile | 1 + Pipfile.lock | 10 +++++++++- test/service/test_twitch_service.py | 1 - 5 files changed, 46 insertions(+), 9 deletions(-) create mode 100644 .env.test diff --git a/.env.example b/.env.example index 991d87a..8c3a5d1 100644 --- a/.env.example +++ b/.env.example @@ -1,7 +1,18 @@ -ENV=dev -CLIENT_ID= #client ID for your Twitch App -CLIENT_SECRET= #client Secret for your Twitch App -PRIVATE_KEY= #private key (prod env only) -CERT= #cert (prod env only) -API_TOKEN= #token for private api (you choose here) -DB= #sqlite db location \ No newline at end of file +ENV= +CLIENT_ID= +CLIENT_SECRET= +TWITTER_API_KEY= +TWITTER_API_SECRET= +TWITTER_ACCESS_TOKEN= +TWITTER_ACCESS_SECRET= +GITHUB_TOKEN= +PRIVATE_KEY= +CERT= +API_TOKEN= +DB=a +DB_NAME= +DB_USER= +DB_PASS= +DB_HOST= +DB_PORT= +REDIS_HOST= \ No newline at end of file diff --git a/.env.test b/.env.test new file mode 100644 index 0000000..6b11755 --- /dev/null +++ b/.env.test @@ -0,0 +1,18 @@ +ENV= +CLIENT_ID= +CLIENT_SECRET= +TWITTER_API_KEY= +TWITTER_API_SECRET= +TWITTER_ACCESS_TOKEN= +TWITTER_ACCESS_SECRET= +GITHUB_TOKEN= +PRIVATE_KEY= +CERT= +API_TOKEN= +DB= +DB_NAME= +DB_USER= +DB_PASS= +DB_HOST= +DB_PORT= +REDIS_HOST= \ No newline at end of file diff --git a/Pipfile b/Pipfile index 354aeb9..d33bc04 100644 --- a/Pipfile +++ b/Pipfile @@ -62,6 +62,7 @@ pytest = "*" asyncmock = "*" pytest-cov = "*" black = "*" +pytest-dotenv = "*" [requires] python_version = "3.7" diff --git a/Pipfile.lock b/Pipfile.lock index b717450..62fcf6b 100644 --- a/Pipfile.lock +++ b/Pipfile.lock @@ -1,7 +1,7 @@ { "_meta": { "hash": { - "sha256": "4d0536022e1fc40e71c59ef748efcffb715cd055c8b2c79fb07cf280f53479bc" + "sha256": "d4be9595bb50fd2af59d946bfc3f994f2d85851d369f0288d1dd93794a31e98a" }, "pipfile-spec": 6, "requires": { @@ -895,6 +895,14 @@ "index": "pypi", "version": "==3.0.0" }, + "pytest-dotenv": { + "hashes": [ + "sha256:2dc6c3ac6d8764c71c6d2804e902d0ff810fa19692e95fe138aefc9b1aa73732", + "sha256:40a2cece120a213898afaa5407673f6bd924b1fa7eafce6bda0e8abffe2f710f" + ], + "index": "pypi", + "version": "==0.5.2" + }, "python-dateutil": { "hashes": [ "sha256:0123cacc1627ae19ddf3c27a5de5bd67ee4586fbdd6440d9748f8abb483d3e86", diff --git a/test/service/test_twitch_service.py b/test/service/test_twitch_service.py index 142d9fa..2c5ac58 100644 --- a/test/service/test_twitch_service.py +++ b/test/service/test_twitch_service.py @@ -45,7 +45,6 @@ def mock_twitch(self): twitch.get_users = MagicMock(return_value=streamer) return twitch - # @patch("service.twitch_service.twitch", return_value=mock_twitch()) def test_get_streamers(self): twitch_service = TwitchService() twitch_service.twitch = self.mock_twitch() From 1505555a094ad7d4220de1055e44f22f1d238bce Mon Sep 17 00:00:00 2001 From: Mendes Date: Mon, 14 Mar 2022 20:02:00 +0000 Subject: [PATCH 13/25] using env.test --- .env.test | 36 ++++++++++++++--------------- test/service/test_twitch_service.py | 6 +++++ 2 files changed, 24 insertions(+), 18 deletions(-) diff --git a/.env.test b/.env.test index 6b11755..0a3d8ae 100644 --- a/.env.test +++ b/.env.test @@ -1,18 +1,18 @@ -ENV= -CLIENT_ID= -CLIENT_SECRET= -TWITTER_API_KEY= -TWITTER_API_SECRET= -TWITTER_ACCESS_TOKEN= -TWITTER_ACCESS_SECRET= -GITHUB_TOKEN= -PRIVATE_KEY= -CERT= -API_TOKEN= -DB= -DB_NAME= -DB_USER= -DB_PASS= -DB_HOST= -DB_PORT= -REDIS_HOST= \ No newline at end of file +ENV=a +CLIENT_ID=a +CLIENT_SECRET=a +TWITTER_API_KEY=a +TWITTER_API_SECRET=a +TWITTER_ACCESS_TOKEN=a +TWITTER_ACCESS_SECRET=a +GITHUB_TOKEN=a +PRIVATE_KEY=a +CERT=a +API_TOKEN=a +DB=a +DB_NAME=a +DB_USER=a +DB_PASS=a +DB_HOST=a +DB_PORT=a +REDIS_HOST=a \ No newline at end of file diff --git a/test/service/test_twitch_service.py b/test/service/test_twitch_service.py index 2c5ac58..3df76a5 100644 --- a/test/service/test_twitch_service.py +++ b/test/service/test_twitch_service.py @@ -1,5 +1,6 @@ import unittest from unittest.mock import MagicMock, Mock, patch +from dotenv import load_dotenv import pytest from twitchAPI.twitch import Twitch @@ -7,6 +8,11 @@ class TestTwitchService(unittest.TestCase): + + def __init__(self, methodName: str = ...) -> None: + super().__init__(methodName) + load_dotenv('.env.test') + def mock_twitch(self): twitch = Mock() response = { From a14e03fcecc9c26961df80498b09ab1ea067cb7e Mon Sep 17 00:00:00 2001 From: Mendes Date: Mon, 14 Mar 2022 20:03:08 +0000 Subject: [PATCH 14/25] using env.test --- test/service/test_twitch_service.py | 1 - 1 file changed, 1 deletion(-) diff --git a/test/service/test_twitch_service.py b/test/service/test_twitch_service.py index 3df76a5..c9e8df4 100644 --- a/test/service/test_twitch_service.py +++ b/test/service/test_twitch_service.py @@ -8,7 +8,6 @@ class TestTwitchService(unittest.TestCase): - def __init__(self, methodName: str = ...) -> None: super().__init__(methodName) load_dotenv('.env.test') From 6c589432a2f58ad974208d2147e96d2873e4baef Mon Sep 17 00:00:00 2001 From: Mendes Date: Tue, 15 Mar 2022 00:08:25 +0000 Subject: [PATCH 15/25] adding env.test --- .env.bkp | 18 ++++++++++++++++++ controller/public_api.py | 9 ++++++--- service/twitch_service.py | 4 ++-- test/service/test_twitch_service.py | 5 ++--- 4 files changed, 28 insertions(+), 8 deletions(-) create mode 100644 .env.bkp diff --git a/.env.bkp b/.env.bkp new file mode 100644 index 0000000..06aa685 --- /dev/null +++ b/.env.bkp @@ -0,0 +1,18 @@ +ENV=dev +CLIENT_ID=6zakmdaqzkjqga9fmmsq922uq3tgw0 +CLIENT_SECRET=qsgqic56t5mrip34lz5mcbr9o09ns3 +TWITTER_API_KEY=z3mn1LTX4MA0qZtmDVn7OqRCQ +TWITTER_API_SECRET=3Lj5nZyCQkxnMxJO1SCdLK7klmpeEAh9y1hwtmUQyuJFJE29PS +TWITTER_ACCESS_TOKEN=24740510-xNhELDLkbuQBqc1ksO4DZbqOiuRmoXgoJpLxFtuzO +TWITTER_ACCESS_SECRET=O5Ji4yGaX8YWb8TKw0zt50TN5uVCsQFgS6pe6kIAQgMxh +GITHUB_TOKEN=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 +PRIVATE_KEY=/etc/letsencrypt/live/brstreamers.dev/privkey.pem +CERT=/etc/letsencrypt/live/brstreamers.dev/cert.pem +API_TOKEN=1q2w3e4 +DB=a +DB_NAME=postgres +DB_USER=postgres +DB_PASS=mamaco123! +DB_HOST=104.198.13.23 +DB_PORT=5432 +REDIS_HOST=104.198.13.23 \ No newline at end of file diff --git a/controller/public_api.py b/controller/public_api.py index 9ff0bc6..4cb571e 100644 --- a/controller/public_api.py +++ b/controller/public_api.py @@ -1,3 +1,4 @@ +import os from typing import List from fastapi import FastAPI @@ -10,10 +11,12 @@ from view_model.stream_viewmodel import StreamViewModel from view_model.tag_viewmodel import TagViewModel from view_model.vod_viewmodel import VodViewModel +from twitchAPI.twitch import Twitch origins = ["*"] app_public = FastAPI(openapi_prefix="/public") +twitch = Twitch(os.environ["CLIENT_ID"], os.environ["CLIENT_SECRET"]) app_public.add_middleware( CORSMiddleware, @@ -27,14 +30,14 @@ @app_public.get("/streams", response_model=List[StreamViewModel]) @cache(expire=60) async def streams(): - twitch_service = TwitchService() + twitch_service = TwitchService(twitch) return twitch_service.get_streamers() @app_public.get("/vods", response_model=List[VodViewModel]) @cache(expire=60) async def vods(): - twitch_service = TwitchService() + twitch_service = TwitchService(twitch) return twitch_service.get_vods() @@ -45,7 +48,7 @@ async def stats(): @app_public.get("/tags", response_model=List[TagViewModel]) async def tags(): - twitch_service = TwitchService() + twitch_service = TwitchService(twitch) return twitch_service.get_tags() diff --git a/service/twitch_service.py b/service/twitch_service.py index a3ea934..5719368 100644 --- a/service/twitch_service.py +++ b/service/twitch_service.py @@ -17,8 +17,8 @@ class TwitchService: config, twitch = None, None - def __init__(self): - twitch = Twitch(os.environ["CLIENT_ID"], os.environ["CLIENT_SECRET"]) + def __init__(self, twitch): + self.twitch = twitch def get_streamers(self) -> List[StreamViewModel]: streams = self.twitch.get_streams(language="pt", game_id="1469308723") diff --git a/test/service/test_twitch_service.py b/test/service/test_twitch_service.py index c9e8df4..ba8c354 100644 --- a/test/service/test_twitch_service.py +++ b/test/service/test_twitch_service.py @@ -10,7 +10,7 @@ class TestTwitchService(unittest.TestCase): def __init__(self, methodName: str = ...) -> None: super().__init__(methodName) - load_dotenv('.env.test') + def mock_twitch(self): twitch = Mock() @@ -51,8 +51,7 @@ def mock_twitch(self): return twitch def test_get_streamers(self): - twitch_service = TwitchService() - twitch_service.twitch = self.mock_twitch() + twitch_service = TwitchService(self.mock_twitch()) streamers = twitch_service.get_streamers() self.assertEqual(len(streamers), 1) self.assertEqual(streamers[0].user_login, "marcobrunodev") From 2805298e1ac03ad693832355aeab5b2a71a45c4e Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Fl=C3=A1vio=20Mendes?= Date: Tue, 15 Mar 2022 00:09:09 +0000 Subject: [PATCH 16/25] Update ci.yml --- .github/workflows/ci.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 6efb03e..fcf36c6 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -16,4 +16,4 @@ jobs: - name: Execute the black linter run: black -l 100 -S . --check - name: Execute Tests - run: pytest --cov=. test + run: pytest --envfile .env.test --cov=. test From 777bf0222b6ae39f56719ce1288a7d9d4db9ce3e Mon Sep 17 00:00:00 2001 From: Mendes Date: Tue, 15 Mar 2022 00:14:36 +0000 Subject: [PATCH 17/25] adding env.test --- test/service/test_twitch_service.py | 1 - 1 file changed, 1 deletion(-) diff --git a/test/service/test_twitch_service.py b/test/service/test_twitch_service.py index ba8c354..0a25186 100644 --- a/test/service/test_twitch_service.py +++ b/test/service/test_twitch_service.py @@ -10,7 +10,6 @@ class TestTwitchService(unittest.TestCase): def __init__(self, methodName: str = ...) -> None: super().__init__(methodName) - def mock_twitch(self): twitch = Mock() From 1118d274b06051eecace48629ff7c9b92f69d4ca Mon Sep 17 00:00:00 2001 From: Mendes Date: Tue, 15 Mar 2022 00:21:57 +0000 Subject: [PATCH 18/25] adding env.test --- requirements.txt | Bin 2754 -> 2798 bytes 1 file changed, 0 insertions(+), 0 deletions(-) diff --git a/requirements.txt b/requirements.txt index 51cfbb67693dde5eee30673319f40fd161e70575..c4580b075454ab556708994a5681cfad16023d2d 100644 GIT binary patch delta 40 scmX>k`c8C%1-rB^LkdGaLkUAFLmopJgDnsmFz7Lu0 Date: Tue, 15 Mar 2022 00:35:17 +0000 Subject: [PATCH 19/25] code coverage --- Pipfile | 1 + Pipfile.lock | 10 +++++++++- README.md | 2 ++ coverage.svg | 21 +++++++++++++++++++++ requirements.txt | Bin 2798 -> 2844 bytes 5 files changed, 33 insertions(+), 1 deletion(-) create mode 100644 coverage.svg diff --git a/Pipfile b/Pipfile index d33bc04..33dbef2 100644 --- a/Pipfile +++ b/Pipfile @@ -63,6 +63,7 @@ asyncmock = "*" pytest-cov = "*" black = "*" pytest-dotenv = "*" +coverage-badge = "*" [requires] python_version = "3.7" diff --git a/Pipfile.lock b/Pipfile.lock index 62fcf6b..977b176 100644 --- a/Pipfile.lock +++ b/Pipfile.lock @@ -1,7 +1,7 @@ { "_meta": { "hash": { - "sha256": "d4be9595bb50fd2af59d946bfc3f994f2d85851d369f0288d1dd93794a31e98a" + "sha256": "b4c2e23acef63282a36509edb061d56e40e4d085ab3e19b46af9dcbba7d703e1" }, "pipfile-spec": 6, "requires": { @@ -407,6 +407,14 @@ ], "version": "==6.3.2" }, + "coverage-badge": { + "hashes": [ + "sha256:c824a106503e981c02821e7d32f008fb3984b2338aa8c3800ec9357e33345b78", + "sha256:e365d56e5202e923d1b237f82defd628a02d1d645a147f867ac85c58c81d7997" + ], + "index": "pypi", + "version": "==1.1.0" + }, "cryptography": { "hashes": [ "sha256:0a817b961b46894c5ca8a66b599c745b9a3d9f822725221f0e0fe49dc043a3a3", diff --git a/README.md b/README.md index c8ad5fc..0e98f3a 100644 --- a/README.md +++ b/README.md @@ -1,3 +1,5 @@ +![Coverage](./coverage.svg) + # Br Dev Streamers - Backend ![Logo](./logo.svg) diff --git a/coverage.svg b/coverage.svg new file mode 100644 index 0000000..fa9907f --- /dev/null +++ b/coverage.svg @@ -0,0 +1,21 @@ + + + + + + + + + + + + + + + + coverage + coverage + 24% + 24% + + diff --git a/requirements.txt b/requirements.txt index c4580b075454ab556708994a5681cfad16023d2d..6e528b49fe7e0e6628ed14e11873ac661f3b92d7 100644 GIT binary patch delta 28 kcmaDSI!A27A0}>Hh9rhWh7^W$hSbT7Ov0PrFfnle0D?LP;{X5v delta 12 TcmbOu_D*!eAEwP(%%9i+B;o}< From e2259818ed10238d2c1fd8eb4a160219ab467c86 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Fl=C3=A1vio=20Mendes?= Date: Mon, 14 Mar 2022 22:04:03 -0300 Subject: [PATCH 20/25] Ci cd app engine (#29) * Create python-app.yml * formatting * reformatting code * Delete python-app.yml * adding first test * adding first test * requirements * Update ci.yml * Update ci.yml * refactoring env vars * formatting * adding env for test * using env.test * using env.test * adding env.test * Update ci.yml * adding env.test * adding env.test * code coverage --- .env.bkp | 18 ++ .env.example | 25 ++- .env.test | 18 ++ .github/workflows/ci.yml | 4 +- Pipfile | 6 + Pipfile.lock | 228 +++++++++++++++++++++- README.md | 2 + brdevstreamers (1).db | Bin 32768 -> 0 bytes controller/__init__.py | 0 controller/private_api.py | 130 ++++++------- controller/public_api.py | 21 ++- coverage.svg | 21 +++ main.py | 36 ++-- model/__init__.py | 0 model/initializer.py | 13 +- model/reward_model.py | 17 +- model/user_interaction_model.py | 18 +- model/user_model.py | 14 +- persistence/__init__.py | 0 persistence/user_dao.py | 58 +++--- persistence/user_interaction_dao.py | 20 +- poc.py | 4 +- requirements.txt | Bin 1982 -> 2844 bytes service/__init__.py | 0 service/stats_service.py | 58 ++++-- service/twitch_service.py | 230 +++++++++++------------ test/__init__.py | 0 test/service/__init__.py | 0 test/service/test_twitch_service.py | 56 ++++++ view_model/__init__.py | 0 view_model/stream_viewmodel.py | 2 +- view_model/tag_viewmodel.py | 4 +- view_model/user_interaction_viewmodel.py | 6 +- view_model/user_viewmodel.py | 3 +- view_model/vod_viewmodel.py | 2 +- 35 files changed, 719 insertions(+), 295 deletions(-) create mode 100644 .env.bkp create mode 100644 .env.test delete mode 100644 brdevstreamers (1).db create mode 100644 controller/__init__.py create mode 100644 coverage.svg create mode 100644 model/__init__.py create mode 100644 persistence/__init__.py create mode 100644 service/__init__.py create mode 100644 test/__init__.py create mode 100644 test/service/__init__.py create mode 100644 test/service/test_twitch_service.py create mode 100644 view_model/__init__.py diff --git a/.env.bkp b/.env.bkp new file mode 100644 index 0000000..06aa685 --- /dev/null +++ b/.env.bkp @@ -0,0 +1,18 @@ +ENV=dev +CLIENT_ID=6zakmdaqzkjqga9fmmsq922uq3tgw0 +CLIENT_SECRET=qsgqic56t5mrip34lz5mcbr9o09ns3 +TWITTER_API_KEY=z3mn1LTX4MA0qZtmDVn7OqRCQ +TWITTER_API_SECRET=3Lj5nZyCQkxnMxJO1SCdLK7klmpeEAh9y1hwtmUQyuJFJE29PS +TWITTER_ACCESS_TOKEN=24740510-xNhELDLkbuQBqc1ksO4DZbqOiuRmoXgoJpLxFtuzO +TWITTER_ACCESS_SECRET=O5Ji4yGaX8YWb8TKw0zt50TN5uVCsQFgS6pe6kIAQgMxh +GITHUB_TOKEN=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 +PRIVATE_KEY=/etc/letsencrypt/live/brstreamers.dev/privkey.pem +CERT=/etc/letsencrypt/live/brstreamers.dev/cert.pem +API_TOKEN=1q2w3e4 +DB=a +DB_NAME=postgres +DB_USER=postgres +DB_PASS=mamaco123! +DB_HOST=104.198.13.23 +DB_PORT=5432 +REDIS_HOST=104.198.13.23 \ No newline at end of file diff --git a/.env.example b/.env.example index 991d87a..8c3a5d1 100644 --- a/.env.example +++ b/.env.example @@ -1,7 +1,18 @@ -ENV=dev -CLIENT_ID= #client ID for your Twitch App -CLIENT_SECRET= #client Secret for your Twitch App -PRIVATE_KEY= #private key (prod env only) -CERT= #cert (prod env only) -API_TOKEN= #token for private api (you choose here) -DB= #sqlite db location \ No newline at end of file +ENV= +CLIENT_ID= +CLIENT_SECRET= +TWITTER_API_KEY= +TWITTER_API_SECRET= +TWITTER_ACCESS_TOKEN= +TWITTER_ACCESS_SECRET= +GITHUB_TOKEN= +PRIVATE_KEY= +CERT= +API_TOKEN= +DB=a +DB_NAME= +DB_USER= +DB_PASS= +DB_HOST= +DB_PORT= +REDIS_HOST= \ No newline at end of file diff --git a/.env.test b/.env.test new file mode 100644 index 0000000..0a3d8ae --- /dev/null +++ b/.env.test @@ -0,0 +1,18 @@ +ENV=a +CLIENT_ID=a +CLIENT_SECRET=a +TWITTER_API_KEY=a +TWITTER_API_SECRET=a +TWITTER_ACCESS_TOKEN=a +TWITTER_ACCESS_SECRET=a +GITHUB_TOKEN=a +PRIVATE_KEY=a +CERT=a +API_TOKEN=a +DB=a +DB_NAME=a +DB_USER=a +DB_PASS=a +DB_HOST=a +DB_PORT=a +REDIS_HOST=a \ No newline at end of file diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 54b3649..fcf36c6 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -12,6 +12,8 @@ jobs: with: python-version: 3.8 - name: Install dependencies - run: pip install black + run: pip install --no-cache-dir --upgrade -r requirements.txt - name: Execute the black linter run: black -l 100 -S . --check + - name: Execute Tests + run: pytest --envfile .env.test --cov=. test diff --git a/Pipfile b/Pipfile index c796845..33dbef2 100644 --- a/Pipfile +++ b/Pipfile @@ -58,6 +58,12 @@ PyJWT = "==2.3.0" PyNaCl = "==1.5.0" PyYAML = "==6.0" fastapi-cache2 = "*" +pytest = "*" +asyncmock = "*" +pytest-cov = "*" +black = "*" +pytest-dotenv = "*" +coverage-badge = "*" [requires] python_version = "3.7" diff --git a/Pipfile.lock b/Pipfile.lock index 47e541e..977b176 100644 --- a/Pipfile.lock +++ b/Pipfile.lock @@ -1,7 +1,7 @@ { "_meta": { "hash": { - "sha256": "4cfe2d19d9add39387cc3b062afcd9256b869443f69e2ac0308827dd23bd7c0b" + "sha256": "b4c2e23acef63282a36509edb061d56e40e4d085ab3e19b46af9dcbba7d703e1" }, "pipfile-spec": 6, "requires": { @@ -134,6 +134,14 @@ "index": "pypi", "version": "==4.0.2" }, + "asyncmock": { + "hashes": [ + "sha256:c251889d542e98fe5f7ece2b5b8643b7d62b50a5657d34a4cbce8a1d5170d750", + "sha256:fd8bc4e7813251a8959d1140924ccba3adbbc7af885dba7047c67f73c0b664b1" + ], + "index": "pypi", + "version": "==0.4.2" + }, "asynctest": { "hashes": [ "sha256:5da6118a7e6d6b54d83a8f7197769d046922a44d2a99c21382f0a6e4fadae676", @@ -142,6 +150,14 @@ "index": "pypi", "version": "==0.13.0" }, + "atomicwrites": { + "hashes": [ + "sha256:6d1784dea7c0c8d4a5172b6c620f40b6e4cbfdf96d783691f2e1302a7b88e197", + "sha256:ae70396ad1a434f9c7046fd2dd196fc04b12f9e91ffb859164193be8b6168a7a" + ], + "markers": "sys_platform == 'win32'", + "version": "==1.4.0" + }, "attrs": { "hashes": [ "sha256:2d27e3784d7a565d36ab851fe94887c5eccd6a463168875832a1be79c82828b4", @@ -150,6 +166,35 @@ "index": "pypi", "version": "==21.4.0" }, + "black": { + "hashes": [ + "sha256:07e5c049442d7ca1a2fc273c79d1aecbbf1bc858f62e8184abe1ad175c4f7cc2", + "sha256:0e21e1f1efa65a50e3960edd068b6ae6d64ad6235bd8bfea116a03b21836af71", + "sha256:1297c63b9e1b96a3d0da2d85d11cd9bf8664251fd69ddac068b98dc4f34f73b6", + "sha256:228b5ae2c8e3d6227e4bde5920d2fc66cc3400fde7bcc74f480cb07ef0b570d5", + "sha256:2d6f331c02f0f40aa51a22e479c8209d37fcd520c77721c034517d44eecf5912", + "sha256:2ff96450d3ad9ea499fc4c60e425a1439c2120cbbc1ab959ff20f7c76ec7e866", + "sha256:3524739d76b6b3ed1132422bf9d82123cd1705086723bc3e235ca39fd21c667d", + "sha256:35944b7100af4a985abfcaa860b06af15590deb1f392f06c8683b4381e8eeaf0", + "sha256:373922fc66676133ddc3e754e4509196a8c392fec3f5ca4486673e685a421321", + "sha256:5fa1db02410b1924b6749c245ab38d30621564e658297484952f3d8a39fce7e8", + "sha256:6f2f01381f91c1efb1451998bd65a129b3ed6f64f79663a55fe0e9b74a5f81fd", + "sha256:742ce9af3086e5bd07e58c8feb09dbb2b047b7f566eb5f5bc63fd455814979f3", + "sha256:7835fee5238fc0a0baf6c9268fb816b5f5cd9b8793423a75e8cd663c48d073ba", + "sha256:8871fcb4b447206904932b54b567923e5be802b9b19b744fdff092bd2f3118d0", + "sha256:a7c0192d35635f6fc1174be575cb7915e92e5dd629ee79fdaf0dcfa41a80afb5", + "sha256:b1a5ed73ab4c482208d20434f700d514f66ffe2840f63a6252ecc43a9bc77e8a", + "sha256:c8226f50b8c34a14608b848dc23a46e5d08397d009446353dad45e04af0c8e28", + "sha256:ccad888050f5393f0d6029deea2a33e5ae371fd182a697313bdbd835d3edaf9c", + "sha256:dae63f2dbf82882fa3b2a3c49c32bffe144970a573cd68d247af6560fc493ae1", + "sha256:e2f69158a7d120fd641d1fa9a921d898e20d52e44a74a6fbbcc570a62a6bc8ab", + "sha256:efbadd9b52c060a8fc3b9658744091cb33c31f830b3f074422ed27bad2b18e8f", + "sha256:f5660feab44c2e3cb24b2419b998846cbb01c23c7fe645fee45087efa3da2d61", + "sha256:fdb8754b453fb15fad3f72cd9cad3e16776f0964d67cf30ebcbf10327a3777a3" + ], + "index": "pypi", + "version": "==22.1.0" + }, "brotli": { "hashes": [ "sha256:12effe280b8ebfd389022aa65114e30407540ccb89b177d3fbc9a4f177c4bd5d", @@ -313,6 +358,63 @@ "index": "pypi", "version": "==0.4.4" }, + "coverage": { + "extras": [ + "toml" + ], + "hashes": [ + "sha256:03e2a7826086b91ef345ff18742ee9fc47a6839ccd517061ef8fa1976e652ce9", + "sha256:07e6db90cd9686c767dcc593dff16c8c09f9814f5e9c51034066cad3373b914d", + "sha256:18d520c6860515a771708937d2f78f63cc47ab3b80cb78e86573b0a760161faf", + "sha256:1ebf730d2381158ecf3dfd4453fbca0613e16eaa547b4170e2450c9707665ce7", + "sha256:21b7745788866028adeb1e0eca3bf1101109e2dc58456cb49d2d9b99a8c516e6", + "sha256:26e2deacd414fc2f97dd9f7676ee3eaecd299ca751412d89f40bc01557a6b1b4", + "sha256:2c6dbb42f3ad25760010c45191e9757e7dce981cbfb90e42feef301d71540059", + "sha256:2fea046bfb455510e05be95e879f0e768d45c10c11509e20e06d8fcaa31d9e39", + "sha256:34626a7eee2a3da12af0507780bb51eb52dca0e1751fd1471d0810539cefb536", + "sha256:37d1141ad6b2466a7b53a22e08fe76994c2d35a5b6b469590424a9953155afac", + "sha256:46191097ebc381fbf89bdce207a6c107ac4ec0890d8d20f3360345ff5976155c", + "sha256:4dd8bafa458b5c7d061540f1ee9f18025a68e2d8471b3e858a9dad47c8d41903", + "sha256:4e21876082ed887baed0146fe222f861b5815455ada3b33b890f4105d806128d", + "sha256:58303469e9a272b4abdb9e302a780072c0633cdcc0165db7eec0f9e32f901e05", + "sha256:5ca5aeb4344b30d0bec47481536b8ba1181d50dbe783b0e4ad03c95dc1296684", + "sha256:68353fe7cdf91f109fc7d474461b46e7f1f14e533e911a2a2cbb8b0fc8613cf1", + "sha256:6f89d05e028d274ce4fa1a86887b071ae1755082ef94a6740238cd7a8178804f", + "sha256:7a15dc0a14008f1da3d1ebd44bdda3e357dbabdf5a0b5034d38fcde0b5c234b7", + "sha256:8bdde1177f2311ee552f47ae6e5aa7750c0e3291ca6b75f71f7ffe1f1dab3dca", + "sha256:8ce257cac556cb03be4a248d92ed36904a59a4a5ff55a994e92214cde15c5bad", + "sha256:8cf5cfcb1521dc3255d845d9dca3ff204b3229401994ef8d1984b32746bb45ca", + "sha256:8fbbdc8d55990eac1b0919ca69eb5a988a802b854488c34b8f37f3e2025fa90d", + "sha256:9548f10d8be799551eb3a9c74bbf2b4934ddb330e08a73320123c07f95cc2d92", + "sha256:96f8a1cb43ca1422f36492bebe63312d396491a9165ed3b9231e778d43a7fca4", + "sha256:9b27d894748475fa858f9597c0ee1d4829f44683f3813633aaf94b19cb5453cf", + "sha256:9baff2a45ae1f17c8078452e9e5962e518eab705e50a0aa8083733ea7d45f3a6", + "sha256:a2a8b8bcc399edb4347a5ca8b9b87e7524c0967b335fbb08a83c8421489ddee1", + "sha256:acf53bc2cf7282ab9b8ba346746afe703474004d9e566ad164c91a7a59f188a4", + "sha256:b0be84e5a6209858a1d3e8d1806c46214e867ce1b0fd32e4ea03f4bd8b2e3359", + "sha256:b31651d018b23ec463e95cf10070d0b2c548aa950a03d0b559eaa11c7e5a6fa3", + "sha256:b78e5afb39941572209f71866aa0b206c12f0109835aa0d601e41552f9b3e620", + "sha256:c76aeef1b95aff3905fb2ae2d96e319caca5b76fa41d3470b19d4e4a3a313512", + "sha256:dd035edafefee4d573140a76fdc785dc38829fe5a455c4bb12bac8c20cfc3d69", + "sha256:dd6fe30bd519694b356cbfcaca9bd5c1737cddd20778c6a581ae20dc8c04def2", + "sha256:e5f4e1edcf57ce94e5475fe09e5afa3e3145081318e5fd1a43a6b4539a97e518", + "sha256:ec6bc7fe73a938933d4178c9b23c4e0568e43e220aef9472c4f6044bfc6dd0f0", + "sha256:f1555ea6d6da108e1999b2463ea1003fe03f29213e459145e70edbaf3e004aaa", + "sha256:f5fa5803f47e095d7ad8443d28b01d48c0359484fec1b9d8606d0e3282084bc4", + "sha256:f7331dbf301b7289013175087636bbaf5b2405e57259dd2c42fdcc9fcc47325e", + "sha256:f9987b0354b06d4df0f4d3e0ec1ae76d7ce7cbca9a2f98c25041eb79eec766f1", + "sha256:fd9e830e9d8d89b20ab1e5af09b32d33e1a08ef4c4e14411e559556fd788e6b2" + ], + "version": "==6.3.2" + }, + "coverage-badge": { + "hashes": [ + "sha256:c824a106503e981c02821e7d32f008fb3984b2338aa8c3800ec9357e33345b78", + "sha256:e365d56e5202e923d1b237f82defd628a02d1d645a147f867ac85c58c81d7997" + ], + "index": "pypi", + "version": "==1.1.0" + }, "cryptography": { "hashes": [ "sha256:0a817b961b46894c5ca8a66b599c745b9a3d9f822725221f0e0fe49dc043a3a3", @@ -490,6 +592,20 @@ "index": "pypi", "version": "==4.11.1" }, + "iniconfig": { + "hashes": [ + "sha256:011e24c64b7f47f6ebd835bb12a743f2fbe9a26d4cecaa7f53bc4f35ee9da8b3", + "sha256:bc3af051d7d14b2ee5ef9969666def0cd1a000e121eaea580d4a313df4b37f32" + ], + "version": "==1.1.1" + }, + "mock": { + "hashes": [ + "sha256:122fcb64ee37cfad5b3f48d7a7d51875d7031aaf3d8be7c42e2bee25044eee62", + "sha256:7d3fbbde18228f4ff2f1f119a45cdffa458b4c0dee32eb4d2bb2f82554bac7bc" + ], + "version": "==4.0.3" + }, "multidict": { "hashes": [ "sha256:0327292e745a880459ef71be14e709aaea2f783f3537588fb4ed09b6c01bca60", @@ -555,6 +671,13 @@ "index": "pypi", "version": "==6.0.2" }, + "mypy-extensions": { + "hashes": [ + "sha256:090fedd75945a69ae91ce1303b5824f428daf5a028d2f6ab8a299250a846f15d", + "sha256:2d82818f5bb3e369420cb3c4060a7970edba416647068eb4c5343488a6c604a8" + ], + "version": "==0.4.3" + }, "numpy": { "hashes": [ "sha256:00c9fa73a6989895b8815d98300a20ac993c49ac36c8277e8ffeaa3631c0dbbb", @@ -591,6 +714,20 @@ "index": "pypi", "version": "==1.21.5" }, + "packaging": { + "hashes": [ + "sha256:dd47c42927d89ab911e606518907cc2d3a1f38bbd026385970643f9c5b8ecfeb", + "sha256:ef103e05f519cdc783ae24ea4e2e0f508a9c99b2d4969652eed6a2e1ea5bd522" + ], + "version": "==21.3" + }, + "pathspec": { + "hashes": [ + "sha256:7d15c4ddb0b5c802d161efc417ec1a2558ea2653c2e8ad9c19098201dc1c993a", + "sha256:e564499435a2673d586f6b2130bb5b95f04a3ba06f81b8f895b651a3c76aabb1" + ], + "version": "==0.9.0" + }, "peewee": { "hashes": [ "sha256:69c1b88dc89b184231cc1ce6df241075aca5cec43e89749cc4a63108f9ceea47" @@ -624,6 +761,20 @@ ], "version": "==2.1.2" }, + "platformdirs": { + "hashes": [ + "sha256:7535e70dfa32e84d4b34996ea99c5e432fa29a708d0f4e394bbcb2a8faa4f16d", + "sha256:bcae7cab893c2d310a711b70b24efb93334febe65f8de776ee320b517471e227" + ], + "version": "==2.5.1" + }, + "pluggy": { + "hashes": [ + "sha256:4224373bacce55f955a878bf9cfa763c1e360858e330072059e10bad68531159", + "sha256:74134bbf457f031a36d68416e1509f34bd5ccc019f0bcc952c7b909d06b37bd3" + ], + "version": "==1.0.0" + }, "psycopg2": { "hashes": [ "sha256:06f32425949bd5fe8f625c49f17ebb9784e1e4fe928b7cce72edc36fb68e4c0c", @@ -641,6 +792,13 @@ "index": "pypi", "version": "==2.9.3" }, + "py": { + "hashes": [ + "sha256:51c75c4126074b472f746a24399ad32f6053d1b34b68d2fa41e558e6f4a98719", + "sha256:607c53218732647dff4acdfcd50cb62615cedf612e72d1724fb1a0cc6405b378" + ], + "version": "==1.11.0" + }, "pyasn1": { "hashes": [ "sha256:39c7e2ec30515947ff4e87fb6f456dfc6e84857d34be479c9d4a4ba4bf46aa5d", @@ -722,6 +880,37 @@ "index": "pypi", "version": "==1.5.0" }, + "pyparsing": { + "hashes": [ + "sha256:18ee9022775d270c55187733956460083db60b37d0d0fb357445f3094eed3eea", + "sha256:a6c06a88f252e6c322f65faf8f418b16213b51bdfaece0524c1c1bc30c63c484" + ], + "version": "==3.0.7" + }, + "pytest": { + "hashes": [ + "sha256:b555252a95bbb2a37a97b5ac2eb050c436f7989993565f5e0c9128fcaacadd0e", + "sha256:f1089d218cfcc63a212c42896f1b7fbf096874d045e1988186861a1a87d27b47" + ], + "index": "pypi", + "version": "==7.1.0" + }, + "pytest-cov": { + "hashes": [ + "sha256:578d5d15ac4a25e5f961c938b85a05b09fdaae9deef3bb6de9a6e766622ca7a6", + "sha256:e7f0f5b1617d2210a2cabc266dfe2f4c75a8d32fb89eafb7ad9d06f6d076d470" + ], + "index": "pypi", + "version": "==3.0.0" + }, + "pytest-dotenv": { + "hashes": [ + "sha256:2dc6c3ac6d8764c71c6d2804e902d0ff810fa19692e95fe138aefc9b1aa73732", + "sha256:40a2cece120a213898afaa5407673f6bd924b1fa7eafce6bda0e8abffe2f710f" + ], + "index": "pypi", + "version": "==0.5.2" + }, "python-dateutil": { "hashes": [ "sha256:0123cacc1627ae19ddf3c27a5de5bd67ee4586fbdd6440d9748f8abb483d3e86", @@ -835,6 +1024,13 @@ "index": "pypi", "version": "==0.17.1" }, + "tomli": { + "hashes": [ + "sha256:939de3e7a6161af0c887ef91b7d41a53e7c5a1ca976325f429cb46ea9bc30ecc", + "sha256:de526c12914f0c550d15924c62d72abc48d6fe7364aa87328337a31007fe8a4f" + ], + "version": "==2.0.1" + }, "twitchapi": { "hashes": [ "sha256:f0ee5388911154375170a83df9a18e8a698fe382cea5d94a3e33ad27a7ce9133" @@ -842,6 +1038,36 @@ "index": "pypi", "version": "==2.5.2" }, + "typed-ast": { + "hashes": [ + "sha256:0eb77764ea470f14fcbb89d51bc6bbf5e7623446ac4ed06cbd9ca9495b62e36e", + "sha256:1098df9a0592dd4c8c0ccfc2e98931278a6c6c53cb3a3e2cf7e9ee3b06153344", + "sha256:183b183b7771a508395d2cbffd6db67d6ad52958a5fdc99f450d954003900266", + "sha256:18fe320f354d6f9ad3147859b6e16649a0781425268c4dde596093177660e71a", + "sha256:26a432dc219c6b6f38be20a958cbe1abffcc5492821d7e27f08606ef99e0dffd", + "sha256:294a6903a4d087db805a7656989f613371915fc45c8cc0ddc5c5a0a8ad9bea4d", + "sha256:31d8c6b2df19a777bc8826770b872a45a1f30cfefcfd729491baa5237faae837", + "sha256:33b4a19ddc9fc551ebabca9765d54d04600c4a50eda13893dadf67ed81d9a098", + "sha256:42c47c3b43fe3a39ddf8de1d40dbbfca60ac8530a36c9b198ea5b9efac75c09e", + "sha256:525a2d4088e70a9f75b08b3f87a51acc9cde640e19cc523c7e41aa355564ae27", + "sha256:58ae097a325e9bb7a684572d20eb3e1809802c5c9ec7108e85da1eb6c1a3331b", + "sha256:676d051b1da67a852c0447621fdd11c4e104827417bf216092ec3e286f7da596", + "sha256:74cac86cc586db8dfda0ce65d8bcd2bf17b58668dfcc3652762f3ef0e6677e76", + "sha256:8c08d6625bb258179b6e512f55ad20f9dfef019bbfbe3095247401e053a3ea30", + "sha256:90904d889ab8e81a956f2c0935a523cc4e077c7847a836abee832f868d5c26a4", + "sha256:963a0ccc9a4188524e6e6d39b12c9ca24cc2d45a71cfdd04a26d883c922b4b78", + "sha256:bbebc31bf11762b63bf61aaae232becb41c5bf6b3461b80a4df7e791fabb3aca", + "sha256:bc2542e83ac8399752bc16e0b35e038bdb659ba237f4222616b4e83fb9654985", + "sha256:c29dd9a3a9d259c9fa19d19738d021632d673f6ed9b35a739f48e5f807f264fb", + "sha256:c7407cfcad702f0b6c0e0f3e7ab876cd1d2c13b14ce770e412c0c4b9728a0f88", + "sha256:da0a98d458010bf4fe535f2d1e367a2e2060e105978873c04c04212fb20543f7", + "sha256:df05aa5b241e2e8045f5f4367a9f6187b09c4cdf8578bb219861c4e27c443db5", + "sha256:f290617f74a610849bd8f5514e34ae3d09eafd521dceaa6cf68b3f4414266d4e", + "sha256:f30ddd110634c2d7534b2d4e0e22967e88366b0d356b24de87419cc4410c41b7" + ], + "markers": "python_version < '3.8' and implementation_name == 'cpython'", + "version": "==1.5.2" + }, "typing-extensions": { "hashes": [ "sha256:1a9462dcc3347a79b1f1c0271fbe79e844580bb598bafa1ed208b94da3cdcd42", diff --git a/README.md b/README.md index c8ad5fc..0e98f3a 100644 --- a/README.md +++ b/README.md @@ -1,3 +1,5 @@ +![Coverage](./coverage.svg) + # Br Dev Streamers - Backend ![Logo](./logo.svg) diff --git a/brdevstreamers (1).db b/brdevstreamers (1).db deleted file mode 100644 index 5c23535366e16c998f06dbbfdd2b6fcce93d9900..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 32768 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 + + + + + + + + + + + + + + + coverage + coverage + 24% + 24% + + diff --git a/main.py b/main.py index 343e4ce..39459a4 100644 --- a/main.py +++ b/main.py @@ -1,7 +1,7 @@ - +import os import aioredis import uvicorn -from dotenv import dotenv_values +from dotenv import load_dotenv from fastapi import FastAPI from fastapi.middleware.cors import CORSMiddleware from fastapi.middleware.gzip import GZipMiddleware @@ -12,7 +12,7 @@ from controller.public_api import app_public from model.initializer import init_db -config = dotenv_values(".env") +load_dotenv() init_db() origins = ["*"] @@ -31,9 +31,12 @@ allow_headers=["*"], ) + @app.on_event("startup") async def startup(): - redis = aioredis.from_url(f"redis://{config['REDIS_HOST']}", encoding="utf8", decode_responses=True) + redis = aioredis.from_url( + f"redis://{os.environ['REDIS_HOST']}", encoding="utf8", decode_responses=True + ) FastAPICache.init(RedisBackend(redis), prefix="fastapi-cache") @@ -44,18 +47,15 @@ async def shutdown(): app.add_middleware(GZipMiddleware) -if __name__ == '__main__': - if(config["ENV"] == 'prod'): - uvicorn.run("main:app", - host="0.0.0.0", - port=8000, - reload=True, - ssl_keyfile=config["PRIVATE_KEY"], - ssl_certfile=config["CERT"] - ) +if __name__ == "__main__": + if os.environ["ENV"] == "prod": + uvicorn.run( + "main:app", + host="0.0.0.0", + port=8000, + reload=True, + ssl_keyfile=os.environ["PRIVATE_KEY"], + ssl_certfile=os.environ["CERT"], + ) else: - uvicorn.run("main:app", - host="0.0.0.0", - port=8000, - reload=True - ) + uvicorn.run("main:app", host="0.0.0.0", port=8000, reload=True) diff --git a/model/__init__.py b/model/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/model/initializer.py b/model/initializer.py index 8dca34f..e3602e1 100644 --- a/model/initializer.py +++ b/model/initializer.py @@ -1,13 +1,16 @@ -from dotenv import dotenv_values +import os from model.reward_model import Reward from model.user_interaction_model import UserInteraction from model.user_model import User from peewee import PostgresqlDatabase -config = dotenv_values(".env") - -db = PostgresqlDatabase(config['DB_NAME'], user=config['DB_USER'], - password=config['DB_PASS'], host=config['DB_HOST'], port=config['DB_PORT']) +db = PostgresqlDatabase( + os.environ["DB_NAME"], + user=os.environ["DB_USER"], + password=os.environ["DB_PASS"], + host=os.environ["DB_HOST"], + port=os.environ["DB_PORT"], +) def init_db(): diff --git a/model/reward_model.py b/model/reward_model.py index 7261488..a4aac86 100644 --- a/model/reward_model.py +++ b/model/reward_model.py @@ -1,15 +1,20 @@ -from dotenv import dotenv_values +import os from peewee import PostgresqlDatabase, CharField, IntegerField, Model -config = dotenv_values(".env") -db = PostgresqlDatabase(config['DB_NAME'], user=config['DB_USER'], - password=config['DB_PASS'], host=config['DB_HOST'], port=config['DB_PORT']) +db = PostgresqlDatabase( + os.environ["DB_NAME"], + user=os.environ["DB_USER"], + password=os.environ["DB_PASS"], + host=os.environ["DB_HOST"], + port=os.environ["DB_PORT"], +) + class Reward(Model): - id= CharField(null=False) + id = CharField(null=False) description = CharField(null=False) price = IntegerField(null=False) quantity_available = IntegerField(null=False) class Meta: - database = db \ No newline at end of file + database = db diff --git a/model/user_interaction_model.py b/model/user_interaction_model.py index 83d5771..b90176b 100644 --- a/model/user_interaction_model.py +++ b/model/user_interaction_model.py @@ -1,18 +1,22 @@ - -from dotenv import dotenv_values +import os from peewee import PostgresqlDatabase, CharField, DateField, Model -config = dotenv_values(".env") -db = PostgresqlDatabase(config['DB_NAME'], user=config['DB_USER'], - password=config['DB_PASS'], host=config['DB_HOST'], port=config['DB_PORT']) +db = PostgresqlDatabase( + os.environ["DB_NAME"], + user=os.environ["DB_USER"], + password=os.environ["DB_PASS"], + host=os.environ["DB_HOST"], + port=os.environ["DB_PORT"], +) + class UserInteraction(Model): - user_login= CharField(null=False) + user_login = CharField(null=False) target_user = CharField(null=True) date = DateField(null=False) type = CharField(null=False) interaction_fingerprint = CharField(null=False) class Meta: - database = db \ No newline at end of file + database = db diff --git a/model/user_model.py b/model/user_model.py index a3c8c31..b6cd211 100644 --- a/model/user_model.py +++ b/model/user_model.py @@ -1,10 +1,14 @@ -from dotenv import dotenv_values +import os from peewee import PostgresqlDatabase, CharField, Model -config = dotenv_values(".env") +db = PostgresqlDatabase( + os.environ["DB_NAME"], + user=os.environ["DB_USER"], + password=os.environ["DB_PASS"], + host=os.environ["DB_HOST"], + port=os.environ["DB_PORT"], +) -db = PostgresqlDatabase(config['DB_NAME'], user=config['DB_USER'], - password=config['DB_PASS'], host=config['DB_HOST'], port=config['DB_PORT']) class User(Model): user_login = CharField(unique=True) @@ -17,4 +21,4 @@ class User(Model): bio = CharField(unique=False, null=True) class Meta: - database = db \ No newline at end of file + database = db diff --git a/persistence/__init__.py b/persistence/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/persistence/user_dao.py b/persistence/user_dao.py index b6775c6..de0f28b 100644 --- a/persistence/user_dao.py +++ b/persistence/user_dao.py @@ -1,11 +1,15 @@ - -from dotenv import dotenv_values -config = dotenv_values(".env") +import os from model.user_model import User from peewee import * -db = PostgresqlDatabase(config['DB_NAME'], user=config['DB_USER'], - password=config['DB_PASS'], host=config['DB_HOST'], port=config['DB_PORT']) +db = PostgresqlDatabase( + os.environ["DB_NAME"], + user=os.environ["DB_USER"], + password=os.environ["DB_PASS"], + host=os.environ["DB_HOST"], + port=os.environ["DB_PORT"], +) + def exception_handler(func): def inner_function(*args, **kwargs): @@ -13,14 +17,17 @@ def inner_function(*args, **kwargs): return func(*args, **kwargs) except Exception as e: print(f"Error executing {func.__name__}. Error: {e}") + return inner_function + @exception_handler def get_users_by_name(users): users = User.select().where(User.user_login << users).execute() db.close() return users + @exception_handler def get_users(): users = User.select().execute() @@ -38,34 +45,39 @@ def get_user_by_login(user_login): @exception_handler def create_user_model(user): res = User.create( - user_login=user.user_login, - email=user.email, - bio=user.bio, - discord = user.discord, - instagram = user.instagram, - linkedin = user.linkedin, - github = user.github, - twitter = user.twitter) + user_login=user.user_login, + email=user.email, + bio=user.bio, + discord=user.discord, + instagram=user.instagram, + linkedin=user.linkedin, + github=user.github, + twitter=user.twitter, + ) return res @exception_handler def update_user_model(user): - res = (User - .update({User.instagram: user.instagram, - User.linkedin: user.linkedin, - User.github: user.github, - User.twitter: user.twitter, - User.discord: user.discord, - User.bio: user.bio - }) + res = ( + User.update( + { + User.instagram: user.instagram, + User.linkedin: user.linkedin, + User.github: user.github, + User.twitter: user.twitter, + User.discord: user.discord, + User.bio: user.bio, + } + ) .where(User.user_login == user.user_login) - .execute()) + .execute() + ) return res @exception_handler def delete_user(user_login): res = User.delete().where(User.user_login == user_login).execute() - return res \ No newline at end of file + return res diff --git a/persistence/user_interaction_dao.py b/persistence/user_interaction_dao.py index ba3e504..978d0ac 100644 --- a/persistence/user_interaction_dao.py +++ b/persistence/user_interaction_dao.py @@ -1,11 +1,16 @@ +import os from peewee import * from model.user_interaction_model import UserInteraction from model.user_model import User -from dotenv import dotenv_values -config = dotenv_values(".env") -db = PostgresqlDatabase(config['DB_NAME'], user=config['DB_USER'], - password=config['DB_PASS'], host=config['DB_HOST'], port=config['DB_PORT']) + +db = PostgresqlDatabase( + os.environ["DB_NAME"], + user=os.environ["DB_USER"], + password=os.environ["DB_PASS"], + host=os.environ["DB_HOST"], + port=os.environ["DB_PORT"], +) def exception_handler(func): @@ -14,11 +19,14 @@ def inner_function(*args, **kwargs): return func(*args, **kwargs) except Exception as e: print(f"Error executing {func.__name__}. Error: {e}") + return inner_function @exception_handler def get_user_interactions_by_user_login(user_login): - user_interactions = UserInteraction.select().where(UserInteraction.user_login == user_login).execute() + user_interactions = ( + UserInteraction.select().where(UserInteraction.user_login == user_login).execute() + ) db.close() - return user_interactions \ No newline at end of file + return user_interactions diff --git a/poc.py b/poc.py index eb5a572..f99d79c 100644 --- a/poc.py +++ b/poc.py @@ -4,9 +4,9 @@ config = dotenv_values(".env") -twitch = Twitch(config['CLIENT_ID'], config['CLIENT_SECRET']) +twitch = Twitch(os.environ["CLIENT_ID"], os.environ["CLIENT_SECRET"]) -users = twitch.get_users(user_ids=['227168488']) +users = twitch.get_users(user_ids=["227168488"]) print(users) diff --git a/requirements.txt b/requirements.txt index 2a1753217f46a9410bb39fb8e1fb351d255010e0..6e528b49fe7e0e6628ed14e11873ac661f3b92d7 100644 GIT binary patch delta 765 zcmZ8fJxc>Y5S=qIq9P#%B4Xh+;Rup&A8HI5L@dP8!b&8>%O{%LnMCC(3x9!ZCD>Zn z30DgK6@Q0?=$qZUL_-+%GBf+$n>YKBe$KruYndK-BM! zP+QbiJ-P+W0OdB$B38wmGXX-pCkk^tOyyG`UQ=uH(h>$2pNV``X=jPdpiph#F*#%U(uEE@ z@iBxeSs9^%FvWM`_~5@!4d4tSZ`q42gi^B0sC49#YSDICJ}*tDJ%k~}?cmgb(+8f8dQU#LCe9{uBWbz1 ZRm1 List[StatsViewModel]: cursor = db.execute_sql( - "SELECT distinct s.target_user, " + - "(SELECT count(*) FROM userinteraction WHERE target_user = s.target_user AND type = 'STREAM_CLICK')," + - "(SELECT count(*) FROM userinteraction WHERE target_user = s.target_user AND type = 'VOD_CLICK')," + - "(SELECT count(*) FROM userinteraction WHERE target_user = s.target_user AND type = 'PREVIEW_CLICK')" + - "FROM userinteraction s ORDER BY s.target_user") + "SELECT distinct s.target_user, " + + "(SELECT count(*) FROM userinteraction WHERE target_user = s.target_user AND type = 'STREAM_CLICK')," + + "(SELECT count(*) FROM userinteraction WHERE target_user = s.target_user AND type = 'VOD_CLICK')," + + "(SELECT count(*) FROM userinteraction WHERE target_user = s.target_user AND type = 'PREVIEW_CLICK')" + + "FROM userinteraction s ORDER BY s.target_user" + ) stats: List[StatsViewModel] = [] @@ -29,19 +35,33 @@ def get_stats() -> List[StatsViewModel]: stat.preview_clicks = row[3] stats.append(stat) return stats - + + def get_stats_summary(): - streams = UserInteraction.select().where(UserInteraction.type == 'STREAM_CLICK').count() - vods = UserInteraction.select().where(UserInteraction.type == 'VOD_CLICK').count() - previews = UserInteraction.select().where(UserInteraction.type == 'PREVIEW').count() + streams = UserInteraction.select().where(UserInteraction.type == "STREAM_CLICK").count() + vods = UserInteraction.select().where(UserInteraction.type == "VOD_CLICK").count() + previews = UserInteraction.select().where(UserInteraction.type == "PREVIEW").count() stats_summary = {"streams": streams, "vods": vods, "previews": previews} return stats_summary + def compute_stat(stat: UserInteraction): - db_stat = UserInteraction.select().where(UserInteraction.target_user == stat.target_user, - UserInteraction.type == stat.type, - UserInteraction.date == stat.date, - UserInteraction.interaction_fingerprint == stat.interaction_fingerprint).count() + db_stat = ( + UserInteraction.select() + .where( + UserInteraction.target_user == stat.target_user, + UserInteraction.type == stat.type, + UserInteraction.date == stat.date, + UserInteraction.interaction_fingerprint == stat.interaction_fingerprint, + ) + .count() + ) if db_stat == 0: - return UserInteraction.create(user_login=stat.user_login, date=stat.date, target_user=stat.target_user, type=stat.type, interaction_fingerprint=stat.interaction_fingerprint) - return None \ No newline at end of file + return UserInteraction.create( + user_login=stat.user_login, + date=stat.date, + target_user=stat.target_user, + type=stat.type, + interaction_fingerprint=stat.interaction_fingerprint, + ) + return None diff --git a/service/twitch_service.py b/service/twitch_service.py index 2d26a68..5719368 100644 --- a/service/twitch_service.py +++ b/service/twitch_service.py @@ -1,7 +1,8 @@ +import json +import os from random import shuffle from typing import List -from dotenv import dotenv_values from twitchAPI.twitch import Twitch from twitchAPI.types import TimePeriod @@ -11,120 +12,115 @@ from view_model.tag_viewmodel import TagViewModel from view_model.vod_viewmodel import VodViewModel -config = dotenv_values(".env") -twitch = Twitch(config['CLIENT_ID'], config['CLIENT_SECRET']) - - -def get_streamers() -> List[StreamViewModel]: - streams = twitch.get_streams(language="pt", game_id='1469308723') - - streams_model: List[StreamViewModel] = [] - stream_users = [] - for s in streams['data']: - stream = StreamViewModel() - stream.id = s['id'] - stream.user_id = s['user_id'] - stream.user_name = s['user_name'] - stream.user_login = s['user_login'] - stream.title = s['title'] - stream.viewer_count = s['viewer_count'] - stream.started_at = s['started_at'] - stream.thumbnail_url = s['thumbnail_url'] - - stream.tags = s['tag_ids'] - - streamer = get_streamer(s['user_id']) - stream.profile_image_url = streamer['profile_image_url'] - stream.description = streamer['description'][:100] + '...' - - stream_users.append(s['user_login']) - streams_model.append(stream) - - try: - streamers = get_users_by_name(stream_users) - for s in streamers: - for stream in streams_model: - if(stream.user_login == s.user_login): - stream.github_url = s.github - stream.twitter_url = s.twitter - stream.instagram_url = s.instagram - stream.linkedin_url = s.linkedin - stream.discord_url = s.discord - stream.bio = s.bio - break - except Exception as e: - print(e) - shuffle(streams_model) - return streams_model - - -def get_streamer(id): - return twitch.get_users(user_ids=[id])['data'][0] - - -def get_vods() -> List[VodViewModel]: - vods = twitch.get_videos( - language="pt", game_id='1469308723', period=TimePeriod.DAY) - vods_model: List[VodViewModel] = [] - vod_users = [] - - for s in vods['data']: - if is_long_enough(s['duration']): - stream = VodViewModel() - stream.id = s['id'] - stream.user_id = s['user_id'] - stream.user_name = s['user_name'] - stream.user_login = s['user_login'] - stream.title = s['title'] - stream.viewer_count = s['view_count'] - stream.started_at = s['published_at'] - stream.thumbnail_url = s['thumbnail_url'] - stream.stream_id = s['id'] - stream.duration = s['duration'] - - streamer = get_streamer(s['user_id']) - stream.profile_image_url = streamer['profile_image_url'] - stream.description = streamer['description'][:100] + '...' - - vod_users.append(s['user_login']) - vods_model.append(stream) - try: - streamers = get_users_by_name(vod_users) - for s in streamers: - for stream in vods_model: - if(stream.user_login == s.user_login): - stream.github_url = s.github - stream.twitter_url = s.twitter - stream.instagram_url = s.instagram - stream.linkedin_url = s.linkedin - stream.discord_url = s.discord - stream.bio = s.bio - break - except Exception as e: - print(e) - return vods_model - - -def is_long_enough(duration): - return 'h' in duration - - -def get_tags() -> List[TagViewModel]: - streams = twitch.get_streams(language="pt", game_id='1469308723') - tag_ids = get_tag_list_from_streams(streams) - tags = twitch.get_all_stream_tags(tag_ids=tag_ids) - tags_dict = {} - for tag in tags['data']: - tag_model = TagViewModel( - id=tag['tag_id'], name=tag['localization_names']['pt-br']) - tags_dict[tag['tag_id']] = tag_model - return list(tags_dict.values()) - - -def get_tag_list_from_streams(streams): - tag_ids = [] - for s in streams['data']: - for tag in s['tag_ids']: - tag_ids.append(tag) - return tag_ids +class TwitchService: + + config, twitch = None, None + + def __init__(self, twitch): + self.twitch = twitch + + def get_streamers(self) -> List[StreamViewModel]: + streams = self.twitch.get_streams(language="pt", game_id="1469308723") + streams_model: List[StreamViewModel] = [] + stream_users = [] + for s in streams["data"]: + stream = StreamViewModel() + stream.id = s["id"] + stream.user_id = s["user_id"] + stream.user_name = s["user_name"] + stream.user_login = s["user_login"] + stream.title = s["title"] + stream.viewer_count = s["viewer_count"] + stream.started_at = s["started_at"] + stream.thumbnail_url = s["thumbnail_url"] + + stream.tags = s["tag_ids"] + + streamer = self.get_streamer(s["user_id"]) + stream.profile_image_url = streamer["profile_image_url"] + stream.description = streamer["description"][:100] + "..." + + stream_users.append(s["user_login"]) + streams_model.append(stream) + + try: + streamers = get_users_by_name(stream_users) + for s in streamers: + for stream in streams_model: + if stream.user_login == s.user_login: + stream.github_url = s.github + stream.twitter_url = s.twitter + stream.instagram_url = s.instagram + stream.linkedin_url = s.linkedin + stream.discord_url = s.discord + stream.bio = s.bio + break + except Exception as e: + print(e) + shuffle(streams_model) + return streams_model + + def get_streamer(self, id): + return self.twitch.get_users(user_ids=[id])["data"][0] + + def get_vods(self) -> List[VodViewModel]: + vods = self.twitch.get_videos(language="pt", game_id="1469308723", period=TimePeriod.DAY) + vods_model: List[VodViewModel] = [] + vod_users = [] + + for s in vods["data"]: + if self.is_long_enough(s["duration"]): + stream = VodViewModel() + stream.id = s["id"] + stream.user_id = s["user_id"] + stream.user_name = s["user_name"] + stream.user_login = s["user_login"] + stream.title = s["title"] + stream.viewer_count = s["view_count"] + stream.started_at = s["published_at"] + stream.thumbnail_url = s["thumbnail_url"] + stream.stream_id = s["id"] + stream.duration = s["duration"] + + streamer = self.get_streamer(s["user_id"]) + stream.profile_image_url = streamer["profile_image_url"] + stream.description = streamer["description"][:100] + "..." + + vod_users.append(s["user_login"]) + vods_model.append(stream) + try: + streamers = get_users_by_name(vod_users) + for s in streamers: + for stream in vods_model: + if stream.user_login == s.user_login: + stream.github_url = s.github + stream.twitter_url = s.twitter + stream.instagram_url = s.instagram + stream.linkedin_url = s.linkedin + stream.discord_url = s.discord + stream.bio = s.bio + break + except Exception as e: + print(e) + return vods_model + + def is_long_enough(self, duration): + return "h" in duration + + def get_tags(self) -> List[TagViewModel]: + streams = self.twitch.get_streams(language="pt", game_id="1469308723") + tag_ids = self.get_tag_list_from_streams(streams) + tags = self.twitch.get_all_stream_tags(tag_ids=tag_ids) + tags_dict = {} + for tag in tags["data"]: + tag_model = TagViewModel(id=tag["tag_id"], name=tag["localization_names"]["pt-br"]) + tags_dict[tag["tag_id"]] = tag_model + return list(tags_dict.values()) + + def get_tag_list_from_streams(self, streams): + tag_ids = [] + for s in streams["data"]: + for tag in s["tag_ids"]: + tag_ids.append(tag) + return tag_ids diff --git a/test/__init__.py b/test/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/test/service/__init__.py b/test/service/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/test/service/test_twitch_service.py b/test/service/test_twitch_service.py new file mode 100644 index 0000000..0a25186 --- /dev/null +++ b/test/service/test_twitch_service.py @@ -0,0 +1,56 @@ +import unittest +from unittest.mock import MagicMock, Mock, patch +from dotenv import load_dotenv +import pytest +from twitchAPI.twitch import Twitch + +from service.twitch_service import TwitchService + + +class TestTwitchService(unittest.TestCase): + def __init__(self, methodName: str = ...) -> None: + super().__init__(methodName) + + def mock_twitch(self): + twitch = Mock() + response = { + "data": [ + { + "id": "44960190524", + "user_id": "166681140", + "user_login": "marcobrunodev", + "user_name": "MarcoBrunoDev", + "game_id": "1469308723", + "game_name": "Software and Game Development", + "type": "live", + "title": "#28 Pet Snoar | Pet Runner | !Alura", + "viewer_count": 158, + "started_at": "2022-03-14T11:00:48Z", + "language": "pt", + "thumbnail_url": "https://static-cdn.jtvnw.net/previews-ttv/live_user_marcobrunodev-{width}x{height}.jpg", + "tag_ids": [ + "39ee8140-901a-4762-bfca-8260dea1310f", + "a106f013-6e26-4f27-9a4b-01e9d76084e2", + "6e23d976-33ec-47e8-b22b-3727acd41862", + "f588bd74-e496-4d11-9169-3597f38a5d25", + "6f86127d-6051-4a38-94bb-f7b475dde109", + "c23ce252-cf78-4b98-8c11-8769801aaf3a", + ], + "is_mature": 'false', + } + ], + "pagination": { + "cursor": "eyJiIjp7IkN1cnNvciI6ImV5SnpJam94TlRndU1URXdOVEl3TlRreE9UZzFNRGdzSW1RaU9tWmhiSE5sTENKMElqcDBjblZsZlE9PSJ9LCJhIjp7IkN1cnNvciI6IiJ9fQ" + }, + } + twitch.get_streams = MagicMock(return_value=response) + + streamer = {"data": [{"profile_image_url": '', 'description': ''}]} + twitch.get_users = MagicMock(return_value=streamer) + return twitch + + def test_get_streamers(self): + twitch_service = TwitchService(self.mock_twitch()) + streamers = twitch_service.get_streamers() + self.assertEqual(len(streamers), 1) + self.assertEqual(streamers[0].user_login, "marcobrunodev") diff --git a/view_model/__init__.py b/view_model/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/view_model/stream_viewmodel.py b/view_model/stream_viewmodel.py index bcf890c..f770491 100644 --- a/view_model/stream_viewmodel.py +++ b/view_model/stream_viewmodel.py @@ -13,4 +13,4 @@ class StreamViewModel(UserOutViewModel): thumbnail_url: Optional[str] profile_image_url: Optional[str] description: Optional[str] - tags: Optional[List[str]] \ No newline at end of file + tags: Optional[List[str]] diff --git a/view_model/tag_viewmodel.py b/view_model/tag_viewmodel.py index 3cd9378..51bb13a 100644 --- a/view_model/tag_viewmodel.py +++ b/view_model/tag_viewmodel.py @@ -4,5 +4,5 @@ class TagViewModel(BaseModel): - name:Optional[str] - id:Optional[str] \ No newline at end of file + name: Optional[str] + id: Optional[str] diff --git a/view_model/user_interaction_viewmodel.py b/view_model/user_interaction_viewmodel.py index b60b75f..eb5e468 100644 --- a/view_model/user_interaction_viewmodel.py +++ b/view_model/user_interaction_viewmodel.py @@ -6,8 +6,8 @@ class UserInteractionViewModel(BaseModel): - user_login:str - target_user:Optional[str] + user_login: str + target_user: Optional[str] date: datetime type: str - interaction_fingerprint: str \ No newline at end of file + interaction_fingerprint: str diff --git a/view_model/user_viewmodel.py b/view_model/user_viewmodel.py index 6286f40..05fdcb6 100644 --- a/view_model/user_viewmodel.py +++ b/view_model/user_viewmodel.py @@ -13,6 +13,7 @@ class UpdateUserViewModel(BaseModel): github: Optional[str] twitter: Optional[str] + class UserOutViewModel(BaseModel): user_login: Optional[str] email: Optional[str] @@ -21,4 +22,4 @@ class UserOutViewModel(BaseModel): twitter_url: Optional[str] instagram_url: Optional[str] linkedin_url: Optional[str] - discord_url: Optional[str] \ No newline at end of file + discord_url: Optional[str] diff --git a/view_model/vod_viewmodel.py b/view_model/vod_viewmodel.py index 5610bd2..1c162c7 100644 --- a/view_model/vod_viewmodel.py +++ b/view_model/vod_viewmodel.py @@ -14,4 +14,4 @@ class VodViewModel(UserOutViewModel): profile_image_url: Optional[str] description: Optional[str] stream_id: Optional[str] - duration: Optional[str] \ No newline at end of file + duration: Optional[str] From 167c4945dd16522f1c53df8af2f3890396378670 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Fl=C3=A1vio=20Mendes?= Date: Tue, 15 Mar 2022 12:23:46 +0000 Subject: [PATCH 21/25] Update ci.yml --- .github/workflows/ci.yml | 44 +++++++++++++++++++++++++++++++++++++++- 1 file changed, 43 insertions(+), 1 deletion(-) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index fcf36c6..d878a36 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -3,7 +3,7 @@ name: Continuous Integration Tests on: [push] jobs: - execute_black_linter_check: + LintandTest: runs-on: ubuntu-latest steps: - uses: actions/checkout@v2 @@ -17,3 +17,45 @@ jobs: run: black -l 100 -S . --check - name: Execute Tests run: pytest --envfile .env.test --cov=. test + Build: + runs-on: ubuntu-latest + steps: + - + name: Checkout + uses: actions/checkout@v2 + - + name: Set up Docker Buildx + id: buildx + uses: docker/setup-buildx-action@v1 + - + name: Cache Docker layers + uses: actions/cache@v2 + with: + path: /tmp/.buildx-cache + key: ${{ runner.os }}-buildx-${{ github.sha }} + restore-keys: | + ${{ runner.os }}-buildx- + + - + name: Login to ghcr + if: github.event_name != 'pull_request' + uses: docker/login-action@v1 + with: + registry: ghcr.io + username: ${{ github.repository_owner }} + password: ${{ secrets.GITHUB_TOKEN }} + - + name: Build and push + id: docker_build + uses: docker/build-push-action@v2 + with: + context: ./ + file: ./Dockerfile + builder: ${{ steps.buildx.outputs.name }} + push: true + tags: ghcr.io/flaviojmendes/brdevstreamers:latest + cache-from: type=local,src=/tmp/.buildx-cache + cache-to: type=local,dest=/tmp/.buildx-cache + - + name: Image digest + run: echo ${{ steps.docker_build.outputs.digest }} From e11c670d6a8254392c35318e96180c038f4b1948 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Fl=C3=A1vio=20Mendes?= Date: Tue, 15 Mar 2022 12:25:42 +0000 Subject: [PATCH 22/25] Update ci.yml --- .github/workflows/ci.yml | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index d878a36..aa747bc 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -3,7 +3,7 @@ name: Continuous Integration Tests on: [push] jobs: - LintandTest: + lint-and-test: runs-on: ubuntu-latest steps: - uses: actions/checkout@v2 @@ -17,8 +17,9 @@ jobs: run: black -l 100 -S . --check - name: Execute Tests run: pytest --envfile .env.test --cov=. test - Build: + build: runs-on: ubuntu-latest + needs: lint-and-test steps: - name: Checkout From c39749abae2d7789668e7f47a2620e89b7b0c85d Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Fl=C3=A1vio=20Mendes?= Date: Tue, 15 Mar 2022 12:31:49 +0000 Subject: [PATCH 23/25] Update ci.yml --- .github/workflows/ci.yml | 56 +++++++++++----------------------------- 1 file changed, 15 insertions(+), 41 deletions(-) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index aa747bc..764870d 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -18,45 +18,19 @@ jobs: - name: Execute Tests run: pytest --envfile .env.test --cov=. test build: - runs-on: ubuntu-latest - needs: lint-and-test - steps: - - - name: Checkout - uses: actions/checkout@v2 - - - name: Set up Docker Buildx - id: buildx - uses: docker/setup-buildx-action@v1 - - - name: Cache Docker layers - uses: actions/cache@v2 - with: - path: /tmp/.buildx-cache - key: ${{ runner.os }}-buildx-${{ github.sha }} - restore-keys: | - ${{ runner.os }}-buildx- + runs-on: ubuntu-latest + + steps: + - uses: actions/checkout@v2 + + - name: Login to GitHub Container Registry + uses: docker/login-action@v1 + with: + registry: ghcr.io + username: ${{ github.actor }} + password: ${{ secrets.GITHUB_TOKEN }} - - - name: Login to ghcr - if: github.event_name != 'pull_request' - uses: docker/login-action@v1 - with: - registry: ghcr.io - username: ${{ github.repository_owner }} - password: ${{ secrets.GITHUB_TOKEN }} - - - name: Build and push - id: docker_build - uses: docker/build-push-action@v2 - with: - context: ./ - file: ./Dockerfile - builder: ${{ steps.buildx.outputs.name }} - push: true - tags: ghcr.io/flaviojmendes/brdevstreamers:latest - cache-from: type=local,src=/tmp/.buildx-cache - cache-to: type=local,dest=/tmp/.buildx-cache - - - name: Image digest - run: echo ${{ steps.docker_build.outputs.digest }} + - name: Build the brstreamers-bot Docker image + run: | + docker build -t ghcr.io/br-dev-streamers/brdevstreamers-bot:latest . + docker push ghcr.io/br-dev-streamers/brdevstreamers-bot:latest From 3020c915cbec7df409a2f2bdb5d2e477f203f9b3 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Fl=C3=A1vio=20Mendes?= Date: Tue, 15 Mar 2022 13:07:09 +0000 Subject: [PATCH 24/25] Delete main.yml --- .github/workflows/main.yml | 51 -------------------------------------- 1 file changed, 51 deletions(-) delete mode 100644 .github/workflows/main.yml diff --git a/.github/workflows/main.yml b/.github/workflows/main.yml deleted file mode 100644 index 8b4440a..0000000 --- a/.github/workflows/main.yml +++ /dev/null @@ -1,51 +0,0 @@ -name: CI - -on: - push: - branches: [ master ] - workflow_dispatch: - -jobs: - - build: - runs-on: ubuntu-latest - steps: - - - name: Checkout - uses: actions/checkout@v2 - - - name: Set up Docker Buildx - id: buildx - uses: docker/setup-buildx-action@v1 - - - name: Cache Docker layers - uses: actions/cache@v2 - with: - path: /tmp/.buildx-cache - key: ${{ runner.os }}-buildx-${{ github.sha }} - restore-keys: | - ${{ runner.os }}-buildx- - - - - name: Login to ghcr - if: github.event_name != 'pull_request' - uses: docker/login-action@v1 - with: - registry: ghcr.io - username: ${{ github.repository_owner }} - password: ${{ secrets.GITHUB_TOKEN }} - - - name: Build and push - id: docker_build - uses: docker/build-push-action@v2 - with: - context: ./ - file: ./Dockerfile - builder: ${{ steps.buildx.outputs.name }} - push: true - tags: ghcr.io/flaviojmendes/brdevstreamers:latest - cache-from: type=local,src=/tmp/.buildx-cache - cache-to: type=local,dest=/tmp/.buildx-cache - - - name: Image digest - run: echo ${{ steps.docker_build.outputs.digest }} From 038a00406ce8550bb669e138a0ba462f944b1a3b Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Fl=C3=A1vio=20Mendes?= Date: Tue, 15 Mar 2022 13:08:19 +0000 Subject: [PATCH 25/25] Update ci.yml --- .github/workflows/ci.yml | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 764870d..6f014e8 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -19,7 +19,7 @@ jobs: run: pytest --envfile .env.test --cov=. test build: runs-on: ubuntu-latest - + needs: [lint-and-test] steps: - uses: actions/checkout@v2 @@ -32,5 +32,5 @@ jobs: - name: Build the brstreamers-bot Docker image run: | - docker build -t ghcr.io/br-dev-streamers/brdevstreamers-bot:latest . - docker push ghcr.io/br-dev-streamers/brdevstreamers-bot:latest + docker build -t ghcr.io/br-dev-streamers/brdevstreamers:latest . + docker push ghcr.io/br-dev-streamers/brdevstreamers:latest