from functools import wraps from flask import request from flask_restful import Resource from werkzeug.exceptions import Unauthorized from tracker.logger import get_logger from tracker.db import Session as db from tracker import config, db as tdb logger = get_logger(__name__) from flask import Response import flask_login def execute_to_dicts(query, params=None): result_proxy = db.execute(query, params) names = [a.name for a in result_proxy.cursor.description] return (dict(zip(names, row)) for row in result_proxy.cursor) from api import version, api_errors import os class Ping(Resource): def get(self): from tracker.db import setup_session from tracker import search from tracker import dynamo setup_session(application_name="wlapi_ping") db_test = db.execute("select version_num from alembic_version").fetchone()[0] es_test = {k: v for (k,v) in search.get_es().info().items() if k in ["cluster_name", "cluster_uuid", "name"]} def table_info(table): return {k: v for (k,v) in table.describe_table().items() if k in ["CreationDateTime", "ItemCount"]} dynamo_test = { cls.__name__: table_info(cls) if cls.exists() else "Not created" for cls in [dynamo.FollowedArtist, dynamo.YtVideos] } return { "status": "OK", "version": { "semver": version, "sha": config.sha, "db": db_test, "es": es_test, "dynamo": dynamo_test, }, } class Echo(Resource): def get(self): return {"params": request.args, "method": "GET"} def post(self): return {"params": request.args, "method": "POST", "body": request.data} def add_user_xray_metadata(func): @wraps(func) def wrapper(*args, **kwargs): if config.enable_aws_xray: logged_in_user = flask_login.current_user if logged_in_user.is_authenticated: from aws_xray_sdk.core import xray_recorder xray_recorder.current_segment().set_user(logged_in_user.name) return func(*args, **kwargs) return wrapper def setup_db_session(func): # The session is torn down in the teardown context listener. @wraps(func) def wrapper(*args, **kwargs): if config.enable_aws_xray: from aws_xray_sdk.core import xray_recorder xray_recorder.begin_subsegment("db_session_setup") logged_in_user = flask_login.current_user if not logged_in_user.is_authenticated: # we dont expect to get here since the 'login_required' wrapper should ensure we are logged in. raise api_errors.SessionExpiredError() from tracker import db db.setup_session( connection_uri=logged_in_user.db_uri, echo=config.api_echo_statements, application_name="wlapi_{}".format(logged_in_user.id), ) if config.enable_aws_xray: from aws_xray_sdk.core import xray_recorder xray_recorder.end_subsegment() return func(*args, **kwargs) return wrapper @flask_login.login_required def admin_login_required(func): # The session is torn down in the teardown context listener. @wraps(func) def wrapper(*args, **kwargs): logged_in_user = flask_login.current_user if not logged_in_user.is_admin: # we dont expect to get here since the 'login_required' wrapper should ensure we are logged in. raise Unauthorized return func(*args, **kwargs) return wrapper from .login import Login, User class AuthenticatedResource(Resource): """Resource that requires a user to be logged in (using the authentication cookie stuff. Importantly this sets up the database connection based on this user. If you want to work with the db in an API call you must extend this class (or you better know what you are doing)""" method_decorators = [flask_login.login_required, setup_db_session, add_user_xray_metadata] @property def user(self) -> User: return flask_login.current_user def safe_json(self, **defaults): if not request.get_data(): # get_json will fail on empty body, I don't want to return {} return dict(defaults, **(request.json or {})) class AdminResource(AuthenticatedResource): method_decorators = [admin_login_required, setup_db_session, add_user_xray_metadata] from .youtube import YouTube, VideoDetail as YouTubeVideoDetail from .twitter_activity import TwitterActivity from .soundcloud_activity import ( SoundCloudActivity, SoundCloudTrackSearch, SoundCloudTrackDetails, ) from .instagram_activity import InstagramActivity from .spotify import Spotify, SpotifyTrack, SpotifyTrackTags, SampleSpotify from .artist import Artist, ArtistBrains, ManageArtistSources, ArtistManualCache, ArtistByIds from .influencers import Influencer from .search import Search from .my_whitelist import MyWhitelist, MyWhitelists, ManageWhitelist from .admin.manual_alerts import ManualAlerts from .admin.spy_streams import SpyStreams from .admin.soundcloud import SoundcloudAdmin from .profiles import ( ScProfile, InProfile, TwProfile, SpyProfile, YtProfile, ScRecommendedInfluencers, ) from .Users import UsersHiddenArtist, MyHiddenArtists from .soundcloud_analyzer import SoundCloudAnalyzer, SoundCloudTrackByScid, SpotifyAnalyzer, SpotifyTracksById, InstagramAnalyzer from .youtube_analyzer import YouTubeAnalyzer, YouTubeVidByYtid from .tiktoks import TikToks, TikTokSounds, TikTokScoutings from .admin.published_sheets import PublishedSpreadsheets class DbCheck(AuthenticatedResource): def get(self): return { "name": self.user.name, "db": str(db.get_bind().url).split("/")[-1], "checks": db.execute( """ with spy as (select count(*) as c from spy_artists), sc as (select count(*) as c from sc_users), checks as (select spy.c as "spy", sc.c as "sc" from spy, sc) select row_to_json(checks.*) from checks """ ).fetchone()[0], } def add_track_tags(track, tags_map): track["tags"] = tags_map.get(track["spyid"]) or [] return track class UserData(AuthenticatedResource): editable_field_mapping = {"lastClearedAlerts": "last_cleared_alerts"} read_only_field_mapping = {"username": "username"} def get(self): data = db.query(tdb.User).get(self.user.name).__json__() reverse_map = dict( (value, key) for (key, value) in list(self.read_only_field_mapping.items()) + list(self.editable_field_mapping.items()) ) return {reverse_map[attr]: data.get(attr) for attr in reverse_map.keys()} def put(self): submitted_json = request.json or {} db_data = { self.editable_field_mapping[key]: submitted_json[key] for key in self.editable_field_mapping.keys() if submitted_json.get(key) } if db_data: db.query(tdb.User).filter(tdb.User.username == self.user.name).update(db_data) db.commit() return self.get() class TestThreading(AuthenticatedResource): def get(self): from api import cache_model print("Starting thread") cache_model.executor.submit(cache_model.test_long_task, 'hello', 123) print("Submitted.") return dict(message='Two jobs was launched in background!')