def get(self, args) -> Response: try: token = oauth.auth.authorize_access_token(**args) id_token = oauth.auth.parse_id_token(token) subject = id_token.get("sub") if not subject: raise OAuthError(description="No user details provided") except OAuthError as e: return make_api_response(400, e.description) user: User = User.get(id=subject) preferred_username = id_token.get("preferred_username") if not user and preferred_username: valid = get_long_validation(preferred_username) if not valid.valid: token_body = make_linking_token(subject, preferred_username, valid.reason) linking_token = create_access_token(token_body) return make_api_response(400, valid.reason, content={"token": linking_token}) # make the user an admin if they are claimed to be an admin user = create_user(subject, preferred_username, admin=id_token.get("admin", False)) if user: tokens = get_sign_in_body(user) return make_api_response(200, content=tokens) return make_api_response(400, "Unable to login using OpenID")
def delete(self, args: Dict[str, UUID]) -> Response: song = Song[args["id"]] if song in current_user.favourites: current_user.favourites.remove(song) return make_api_response( 200, f'Removed "{song.title}" from your favourites') return make_api_response(400, f'"{song.title}" is not in your favourites')
def get(self) -> Response: if current_user: content = { "username": current_user.username, "admin": current_user.admin, } return make_api_response(200, content=content) return make_api_response(500, "Failed to get user")
def put(self, args: Dict[str, UUID]) -> Response: song = Song[args["id"]] if song not in current_user.favourites: current_user.favourites.add(song) return make_api_response( 200, f'Added "{song.title}" to your favourites') return make_api_response( 400, f'"{song.title}" is already in your favourites')
def get(self) -> Response: try: redirect_uri = urljoin(request.url_root, "openid/callback") url = oauth.auth.create_authorization_url(redirect_uri) oauth.auth.save_authorize_data(request, redirect_uri=redirect_uri, **url) return make_api_response(200, content=url) except Exception as e: app.logger.exception("Unable to login using OpenID") return make_api_response(400, "Unable to login using OpenID")
def post(self, username: str, password: str) -> Response: validator = valid_username(username) if not validator.valid: return make_api_response(400, validator.reason) # if no users registered, make first one admin make_admin = User.select().count() == 0 new_user = register(username, password, admin=make_admin) if new_user: return make_api_response( 200, f'User "{username}" successfully registered') return make_api_response(500, "Failed to register user")
def post(self, username: str, token: str): decoded = decode_token(token)["identity"] valid = get_long_validation(username) if not valid.valid: decoded = make_linking_token(decoded["id"], username, valid.reason) return make_api_response(400, valid.reason, content=decoded) user = create_user(decoded["id"], username) tokens = get_sign_in_body(user) return make_api_response(200, content=tokens)
def post(self, args: Dict[str, UUID]) -> Response: if not app.config["PUBLIC_DOWNLOADS"]: if not current_user or not current_user.admin: return make_api_response(403, "Downloading is not enabled") song_id = args["id"] new_token = create_access_token({"id": str(song_id)}, expires_delta=timedelta(seconds=10)) return make_api_response(200, content={ "download_token": new_token, "id": song_id })
def post(self) -> Response: if not app.config["PUBLIC_UPLOADS"]: if not user_is_admin(): return make_api_response(403, "Uploading is not enabled") if "song" not in request.files: app.logger.warning("No file part") return make_api_response(400, "No `song` file field in request") song = request.files["song"] if song.filename == "": app.logger.warning("No selected file") return make_api_response(400, "No file selected") if allowed_file_extension(Path(song.filename)): filename = secure_filename(song.filename) if not filename: return make_api_response(400, "Filename not valid") filepath = Path(app.config["PATH_ENCODE"], filename) song.save(str(filepath)) kind = filetype.guess(str(filepath)) if not kind or kind.mime.split("/")[0] != "audio": filepath.unlink(missing_ok=True) return make_api_response(400, "File is not audio") meta = get_metadata(filepath) if not meta: filepath.unlink(missing_ok=True) return make_api_response(400, "File missing metadata") with concurrent.futures.ThreadPoolExecutor() as executor: try: future = executor.submit(encode_file, filepath, remove_original=True) final_path = future.result() song = insert_song(final_path) if song: app.logger.info(f'File "{filename}" uploaded') return make_api_response(200, f'File "{filename}" uploaded', content={"id": song.id}) except EncodeError: # delete the original app.logger.exception(f"Encode error for {filepath}") filepath.unlink(missing_ok=True) return make_api_response(400, "File could not be encoded") return make_api_response(400, "File could not be processed")
def delete(self, args: Dict[str, UUID], **kwargs) -> Response: song = get_song_or_abort(args["id"]) filepath = os.path.join(app.config["PATH_MUSIC"], song.filename) if os.path.isfile(filepath): os.remove(filepath) song.delete() app.logger.info(f'Deleted song "{song.filename}"') return make_api_response( 200, f'Successfully deleted song "{song.filename}"')
def put(self, args: Dict[str, UUID], **kwargs) -> Response: if not request.json: return make_api_response(400, "No data provided") accepted_fields = ["artist", "title"] values = { field: val for field, val in request.json.items() if field in accepted_fields } song = get_song_or_abort(args["id"]) song.set(**values) commit() # update file metadata as well metadata = mutagen.File(Path(app.config["PATH_MUSIC"], song.filename), easy=True) metadata.update(values) metadata.save() return make_api_response( 200, "Successfully updated song metadata", content=dataclasses.asdict(get_song_detailed(song)), )
def validate_response(status_code, error=None, body=None): resp = utils.make_api_response( status_code, description=body.get("description", None) if body else None, content=body, ) if not body: body = {} json = {"status_code": status_code, "error": error, **body} assert resp.get_json() == json assert resp.status_code == status_code return True
def put(self, args: Dict[str, UUID]) -> Response: def get_meta(): return SongMeta( **dataclasses.asdict(request_status(song)), favourited=False if not current_user else song in current_user.favourites, ) song = Song[args.get("id")] status = request_status(song) if status.requestable: Queue(song=song, requested=True) return make_api_response( 200, f'Requested "{song.title}" successfully', content={"meta": get_meta()}, ) return make_api_response( 400, f'"{song.title}" is not requestable at this moment. {status.reason}', content={"meta": get_meta()}, )
def get_songs_response( context: rest.Resource, page: int, query: Optional[str], limit: int, favourites: Optional[User] = None, ) -> Response: results = query_songs(query, favourites) pagination = Pagination(page=page, per_page=limit, total_count=results.count()) # report error if page does not exist if page <= 0 or page > pagination.pages: return make_api_response(404, "Page does not exist") processed_songs = list(map(get_song_detailed, results.page(page, limit))) args = partial(filter_default_webargs, args=SongQuerySchema(), query=query, limit=limit) return make_api_response( 200, content={ "_links": { "_self": api.url_for(context, **args(page=page), _external=True), "_next": api.url_for(context, **args( page=page + 1), _external=True) if pagination.has_next else None, "_prev": api.url_for(context, **args( page=page - 1), _external=True) if pagination.has_prev else None, }, "query": query, "pagination": pagination.to_json(), "songs": processed_songs, }, )
def get(self, query: str) -> Response: data = [] artists = select(s.artist for s in Song if query.lower() in s.artist.lower()) titles = select(s.title for s in Song if query.lower() in s.title.lower()) for entry in artists.limit(5): data.append({"result": entry, "type": "Artist"}) for entry in titles.limit(5): data.append({"result": entry, "type": "Title"}) return make_api_response(200, content={ "query": query, "suggestions": data })
def invalid_token_loader(error: str) -> Response: return make_api_response(400, error)
def get(self, args: Dict[str, UUID], **kwargs) -> Response: song = get_song_or_abort(args["id"]) return make_api_response(200, content=dataclasses.asdict( get_song_detailed(song)))
def wrapper(*args, **kwargs) -> Response: if not user_is_admin(): return make_api_response( 403, "This endpoint can only be accessed by admins") return fn(*args, **kwargs)
def post(self) -> Response: tokens = refresh_token(current_user) if tokens: return make_api_response(200, content=tokens) return make_api_response(500, "Failed to refresh token")
def get(self) -> Response: return make_api_response(200, content=dict(_links=get_self_links(api, self), **settings()))
def expired_token_loader(token: dict) -> Response: token_type = token["type"] return make_api_response(401, f"The {token_type} token has expired")
def get(self, token: str): decoded = decode_token(token)["identity"] return make_api_response(200, decoded["reason"], content=decoded)
def unauthorized_loader(error: str) -> Response: return make_api_response(401, error)
def user_loader(identity: str): return make_api_response(401, f"User {identity} not found")
def post(self) -> Response: redis_client.publish("skip", "True") return make_api_response(200, "Successfully skipped current playing song")
def post(self, username: str, password: str) -> Response: tokens = sign_in(username, password) if tokens: return make_api_response(200, content=tokens) return make_api_response(401, "Invalid credentials")