Browse Source

Can show the right follow link; Formatting

pull/1/head
Dashie der otter 1 year ago
parent
commit
124c9263ca
43 changed files with 274 additions and 755 deletions
  1. 1
    1
      .isort.cfg
  2. 11
    35
      activitypub/backend.py
  3. 2
    5
      activitypub/utils.py
  4. 10
    46
      app.py
  5. 1
    10
      controllers/admin.py
  6. 13
    56
      controllers/albums.py
  7. 11
    50
      controllers/api/v1/activitypub.py
  8. 1
    3
      controllers/api/v1/nodeinfo.py
  9. 7
    24
      controllers/api/v1/well_known.py
  10. 53
    42
      controllers/search.py
  11. 18
    74
      controllers/sound.py
  12. 13
    60
      controllers/users.py
  13. 1
    5
      dbseed.py
  14. 2
    6
      forms.py
  15. 3
    7
      migrations/env.py
  16. 2
    6
      migrations/versions/00_795db5a5e99b_.py
  17. 1
    3
      migrations/versions/01_da3273ca0f0f_.py
  18. 1
    3
      migrations/versions/06_14ecb77e9482_.py
  19. 1
    3
      migrations/versions/08_fc50eb4e9b34_.py
  20. 1
    3
      migrations/versions/09_aeb6c1345690_.py
  21. 1
    3
      migrations/versions/10_a901f35d5686_.py
  22. 1
    3
      migrations/versions/11_f41ac4617ef6_.py
  23. 2
    7
      migrations/versions/17_01e6d591fe6f_.py
  24. 1
    3
      migrations/versions/19_98b863ae920c_.py
  25. 2
    12
      migrations/versions/20_691eaff10a88_.py
  26. 2
    12
      migrations/versions/21_abf095d9c9f3_.py
  27. 2
    6
      migrations/versions/22_835be4f73770_.py
  28. 2
    7
      migrations/versions/23_2ec399782f52_.py
  29. 2
    6
      migrations/versions/25_32f48de123f3_.py
  30. 2
    10
      migrations/versions/27_687d40646d63_.py
  31. 5
    26
      migrations/versions/28_2e83f405e9a4_.py
  32. 1
    4
      migrations/versions/31_7ce00beb5b0a_.py
  33. 39
    101
      models.py
  34. 2
    0
      pyproject.toml
  35. 1
    0
      setup.cfg
  36. 25
    0
      templates/search/local_users_auth.jinja2
  37. 0
    0
      templates/search/local_users_unauth.jinja2
  38. 6
    1
      templates/search/remote_user.jinja2
  39. 1
    3
      tests/helpers.py
  40. 3
    13
      tests/test_c_users.py
  41. 6
    21
      tests/test_wellknown.py
  42. 7
    48
      utils.py
  43. 8
    27
      workers.py

+ 1
- 1
.isort.cfg View File

@@ -1,3 +1,3 @@
[settings]
line_length=120
force_single_line=true
force_single_line=false

+ 11
- 35
activitypub/backend.py View File

@@ -45,9 +45,7 @@ class Reel2BitsBackend(ap.Backend):
def note_url(self, obj_id: str):
return f"{self.base_url()}/note/{obj_id}"

def new_follower(
self, activity: ap.BaseActivity, as_actor: ap.Person, follow: ap.Follow
) -> None:
def new_follower(self, activity: ap.BaseActivity, as_actor: ap.Person, follow: ap.Follow) -> None:
current_app.logger.info("new follower")

db_actor = Actor.query.filter(Actor.url == as_actor.id).first()
@@ -59,9 +57,7 @@ class Reel2BitsBackend(ap.Backend):
current_app.logger.error(f"cannot find follow {follow!r}")
return

current_app.logger.info(
f"{db_actor.name} wanted " f"to follow {db_follow.name}"
)
current_app.logger.info(f"{db_actor.name} wanted " f"to follow {db_follow.name}")

db_actor.follow(activity.id, db_follow)
db.session.commit()
@@ -100,9 +96,7 @@ class Reel2BitsBackend(ap.Backend):
# fetch the activity
activity = Activity.query.filter(Activity.url == undo_activity).first()
if not activity:
current_app.logger.error(
f"cannot find activity" f" to undo: {undo_activity}"
)
current_app.logger.error(f"cannot find activity" f" to undo: {undo_activity}")
return

# Parse the activity
@@ -171,13 +165,9 @@ class Reel2BitsBackend(ap.Backend):
domain = urlparse(ap_actor.id)
current_app.logger.debug(f"actor.id=={ap_actor.__dict__}")

current_app.logger.debug(
f"actor domain {domain.netloc} and " f"name {ap_actor.preferredUsername}"
)
current_app.logger.debug(f"actor domain {domain.netloc} and " f"name {ap_actor.preferredUsername}")

actor = Actor.query.filter(
Actor.domain == domain.netloc, Actor.name == ap_actor.preferredUsername
).first()
actor = Actor.query.filter(Actor.domain == domain.netloc, Actor.name == ap_actor.preferredUsername).first()

if not actor:
actor = create_remote_actor(ap_actor)
@@ -256,9 +246,7 @@ def process_new_activity(activity: ap.BaseActivity) -> None:
if note.inReplyTo:
try:
reply = ap.fetch_remote_activity(note.inReplyTo)
if (
reply.id.startswith(id) or reply.has_mention(id)
) and activity.is_public():
if (reply.id.startswith(id) or reply.has_mention(id)) and activity.is_public():
# The reply is public "local reply", forward the
# reply (i.e. the original activity) to the
# original recipients
@@ -284,9 +272,7 @@ def process_new_activity(activity: ap.BaseActivity) -> None:
should_forward = False

elif activity.has_type(ap.ActivityType.DELETE):
note = Activity.query.filter(
Activity.id == activity.get_object().id
).first()
note = Activity.query.filter(Activity.id == activity.get_object().id).first()
if note and note["meta"].get("forwarded", False):
# If the activity was originally forwarded, forward the
# delete too
@@ -319,13 +305,9 @@ def process_new_activity(activity: ap.BaseActivity) -> None:
current_app.logger.info(f"new activity {activity.id} processed")

except (ActivityGoneError, ActivityNotFoundError):
current_app.logger.exception(
f"failed to process new activity" f" {activity.id}"
)
current_app.logger.exception(f"failed to process new activity" f" {activity.id}")
except Exception as err:
current_app.logger.exception(
f"failed to process new activity" f" {activity.id}"
)
current_app.logger.exception(f"failed to process new activity" f" {activity.id}")


# TODO, this must move to Dramatiq queueing
@@ -371,9 +353,7 @@ def finish_inbox_processing(activity: ap.BaseActivity) -> None:
except (ActivityGoneError, ActivityNotFoundError, NotAnActivityError):
current_app.logger.exception(f"no retry")
except Exception as err:
current_app.logger.exception(
f"failed to cache attachments for" f" {activity.id}"
)
current_app.logger.exception(f"failed to cache attachments for" f" {activity.id}")


def post_to_outbox(activity: ap.BaseActivity) -> str:
@@ -468,11 +448,7 @@ def post_to_remote_inbox(payload: str, to: str) -> None:
to,
data=json.dumps(signed_payload),
auth=signature_auth,
headers={
"Content-Type": HEADERS[1],
"Accept": HEADERS[1],
"User-Agent": backend.user_agent(),
},
headers={"Content-Type": HEADERS[1], "Accept": HEADERS[1], "User-Agent": backend.user_agent()},
)
current_app.logger.info("resp=%s", resp)
current_app.logger.info("resp_body=%s", resp.text)

+ 2
- 5
activitypub/utils.py View File

@@ -47,13 +47,10 @@ def add_extra_collection(item: Dict[str, Any]) -> Dict[str, Any]:
return item

item["object"]["replies"] = embed_collection(
item.get("meta", {}).get("count_direct_reply", 0),
f'{item["remote_id"]}/replies',
item.get("meta", {}).get("count_direct_reply", 0), f'{item["remote_id"]}/replies'
)

item["object"]["likes"] = embed_collection(
item.get("meta", {}).get("count_like", 0), f'{item["remote_id"]}/likes'
)
item["object"]["likes"] = embed_collection(item.get("meta", {}).get("count_like", 0), f'{item["remote_id"]}/likes')

item["object"]["shares"] = embed_collection(
item.get("meta", {}).get("count_boost", 0), f'{item["remote_id"]}/shares'

+ 10
- 46
app.py View File

@@ -4,16 +4,7 @@ import os
import subprocess
from logging.handlers import RotatingFileHandler
from flask_babelex import gettext, Babel
from flask import (
Flask,
render_template,
g,
send_from_directory,
jsonify,
safe_join,
request,
flash,
)
from flask import Flask, render_template, g, send_from_directory, jsonify, safe_join, request, flash
from flask_bootstrap import Bootstrap
from flask_mail import Mail
from flask_migrate import Migrate
@@ -35,13 +26,7 @@ from controllers.api.v1.activitypub import bp_ap

from forms import ExtendedRegisterForm
from models import db, Config, user_datastore, Role, create_actor
from utils import (
InvalidUsage,
is_admin,
duration_elapsed_human,
duration_song_human,
add_user_log,
)
from utils import InvalidUsage, is_admin, duration_elapsed_human, duration_song_human, add_user_log

import texttable
from flask_debugtoolbar import DebugToolbarExtension
@@ -92,12 +77,8 @@ def create_app(config_filename="config.py"):

# Logging
if not app.debug:
formatter = logging.Formatter(
"%(asctime)s %(levelname)s: %(message)s " "[in %(pathname)s:%(lineno)d]"
)
file_handler = RotatingFileHandler(
"%s/errors_app.log" % os.getcwd(), "a", 1000000, 1
)
formatter = logging.Formatter("%(asctime)s %(levelname)s: %(message)s " "[in %(pathname)s:%(lineno)d]")
file_handler = RotatingFileHandler("%s/errors_app.log" % os.getcwd(), "a", 1000000, 1)
file_handler.setLevel(logging.DEBUG)
file_handler.setFormatter(formatter)
app.logger.addHandler(file_handler)
@@ -115,10 +96,7 @@ def create_app(config_filename="config.py"):

# Setup Flask-Security
security = Security( # noqa: F841
app,
user_datastore,
register_form=ExtendedRegisterForm,
confirm_register_form=ExtendedRegisterForm,
app, user_datastore, register_form=ExtendedRegisterForm, confirm_register_form=ExtendedRegisterForm
)

@FlaskSecuritySignals.password_reset.connect_via(app)
@@ -126,17 +104,13 @@ def create_app(config_filename="config.py"):
def log_password_reset(sender, user):
if not user:
return
add_user_log(
user.id, user.id, "user", "info", "Your password has been changed !"
)
add_user_log(user.id, user.id, "user", "info", "Your password has been changed !")

@FlaskSecuritySignals.reset_password_instructions_sent.connect_via(app)
def log_reset_password_instr(sender, user, token):
if not user:
return
add_user_log(
user.id, user.id, "user", "info", "Password reset instructions sent."
)
add_user_log(user.id, user.id, "user", "info", "Password reset instructions sent.")

@FlaskSecuritySignals.user_registered.connect_via(app)
def create_actor_for_registered_user(app, user, confirm_token):
@@ -236,12 +210,7 @@ def create_app(config_filename="config.py"):

@app.errorhandler(410)
def err_gone(msg):
pcfg = {
"title": gettext("Whoops, something failed."),
"error": 410,
"message": gettext("Gone"),
"e": msg,
}
pcfg = {"title": gettext("Whoops, something failed."), "error": 410, "message": gettext("Gone"), "e": msg}
return render_template("error_page.jinja2", pcfg=pcfg), 410

if not app.debug:
@@ -289,9 +258,7 @@ def create_app(config_filename="config.py"):
"""Create an user"""
username = click.prompt("Username", type=str)
email = click.prompt("Email", type=str)
password = click.prompt(
"Password", type=str, hide_input=True, confirmation_prompt=True
)
password = click.prompt("Password", type=str, hide_input=True, confirmation_prompt=True)
while True:
role = click.prompt("Role [admin/user]", type=str)
if role == "admin" or role == "user":
@@ -302,10 +269,7 @@ def create_app(config_filename="config.py"):
if not role:
raise click.UsageError("Roles not present in database")
u = user_datastore.create_user(
name=username,
email=email,
password=encrypt_password(password),
roles=[role],
name=username, email=email, password=encrypt_password(password), roles=[role]
)

actor = create_actor(u)

+ 1
- 10
controllers/admin.py View File

@@ -1,13 +1,4 @@
from flask import (
Blueprint,
render_template,
request,
redirect,
url_for,
flash,
Response,
json,
)
from flask import Blueprint, render_template, request, redirect, url_for, flash, Response, json
from flask_babelex import gettext
from flask_security import login_required


+ 13
- 56
controllers/albums.py View File

@@ -1,13 +1,4 @@
from flask import (
Blueprint,
render_template,
request,
redirect,
url_for,
flash,
Response,
json,
)
from flask import Blueprint, render_template, request, redirect, url_for, flash, Response, json
from flask_babelex import gettext
from flask_security import login_required, current_user

@@ -36,20 +27,12 @@ def new(username):
db.session.commit()

# log
add_user_log(
rec.id,
rec.user_id,
"albums",
"info",
"Created {0} -- {1}".format(rec.id, rec.title),
)
add_user_log(rec.id, rec.user_id, "albums", "info", "Created {0} -- {1}".format(rec.id, rec.title))

flash(gettext("Created !"), "success")
else:
return render_template("album/new.jinja2", pcfg=pcfg, form=form)
return redirect(
url_for("bp_albums.show", username=current_user.name, setslug=rec.slug)
)
return redirect(url_for("bp_albums.show", username=current_user.name, setslug=rec.slug))


@bp_albums.route("/user/<string:username>/sets/<string:setslug>", methods=["GET"])
@@ -83,14 +66,10 @@ def show(username, setslug):
)


@bp_albums.route(
"/user/<string:username>/sets/<string:setslug>/edit", methods=["GET", "POST"]
)
@bp_albums.route("/user/<string:username>/sets/<string:setslug>/edit", methods=["GET", "POST"])
@login_required
def edit(username, setslug):
album = Album.query.filter(
Album.user_id == current_user.id, Album.slug == setslug
).first()
album = Album.query.filter(Album.user_id == current_user.id, Album.slug == setslug).first()
if not album:
flash(gettext("Album not found"), "error")
return redirect(url_for("bp_users.profile", name=username))
@@ -99,7 +78,7 @@ def edit(username, setslug):
flash(gettext("Forbidden"), "error")
return redirect(url_for("bp_users.profile", name=username))

pcfg = {"title": gettext(u"Edit %(title)s", title=album.title)}
pcfg = {"title": gettext("Edit %(title)s", title=album.title)}

form = AlbumForm(request.form, obj=album)

@@ -118,26 +97,15 @@ def edit(username, setslug):
db.session.commit()

# log
add_user_log(
album.id,
album.user.id,
"albums",
"info",
"Edited {0} -- {1}".format(album.id, album.title),
)
add_user_log(album.id, album.user.id, "albums", "info", "Edited {0} -- {1}".format(album.id, album.title))

return redirect(
url_for("bp_albums.show", username=username, setslug=album.slug)
)
return redirect(url_for("bp_albums.show", username=username, setslug=album.slug))
else:
flash(gettext("Public album cannot have private sounds"), "error")
return render_template("album/edit.jinja2", pcfg=pcfg, form=form, album=album)


@bp_albums.route(
"/user/<string:username>/sets/<string:setslug>/delete",
methods=["GET", "DELETE", "PUT"],
)
@bp_albums.route("/user/<string:username>/sets/<string:setslug>/delete", methods=["GET", "DELETE", "PUT"])
def delete(username, setslug):
user = User.query.filter(User.name == username).first()
if not user:
@@ -157,20 +125,12 @@ def delete(username, setslug):
db.session.commit()

# log
add_user_log(
album.id,
user.id,
"albums",
"info",
"Deleted {0} -- {1}".format(album.id, album.title),
)
add_user_log(album.id, user.id, "albums", "info", "Deleted {0} -- {1}".format(album.id, album.title))

return redirect(url_for("bp_users.profile", name=username))


@bp_albums.route(
"/user/<string:username>/sets/<string:setslug>/reorder.json", methods=["POST"]
)
@bp_albums.route("/user/<string:username>/sets/<string:setslug>/reorder.json", methods=["POST"])
def reorder_json(username, setslug):
user = User.query.filter(User.name == username).first()
if not user:
@@ -199,16 +159,13 @@ def reorder_json(username, setslug):
raise InvalidUsage("Invalid json", status_code=500)

for snd in request.get_json()["data"]:
sound = Sound.query.filter(
Sound.id == int(snd["soundid"]), Sound.album_id == album.id
).first()
sound = Sound.query.filter(Sound.id == int(snd["soundid"]), Sound.album_id == album.id).first()
if not sound:
raise InvalidUsage("Sound not found", status_code=404)

if sound.album_order != int(snd["oldPosition"]):
raise InvalidUsage(
"Old position %s doesn't match bdd one %s"
% (int(snd["oldPosition"]), sound.album_order)
"Old position %s doesn't match bdd one %s" % (int(snd["oldPosition"]), sound.album_order)
)
sound.album_order = int(snd["newPosition"])


+ 11
- 50
controllers/api/v1/activitypub.py View File

@@ -1,15 +1,4 @@
from flask import (
Blueprint,
request,
abort,
current_app,
Response,
jsonify,
flash,
render_template,
redirect,
url_for,
)
from flask import Blueprint, request, abort, current_app, Response, jsonify, flash, render_template, redirect, url_for
from little_boxes import activitypub
from little_boxes.httpsig import verify_request
from activitypub.backend import post_to_inbox, Box
@@ -34,22 +23,15 @@ def user_inbox(name):
current_app.logger.debug(f"raw_data={data}")

try:
if not verify_request(
request.method, request.path, request.headers, request.data
):
if not verify_request(request.method, request.path, request.headers, request.data):
raise Exception("failed to verify request")
except Exception:
current_app.logger.exception("failed to verify request")
try:
data = be.fetch_iri(data["id"])
except Exception:
current_app.logger.exception(
f"failed to fetch remote id " f"at {data['id']}"
)
resp = {
"error": "failed to verify request "
"(using HTTP signatures or fetching the IRI)"
}
current_app.logger.exception(f"failed to fetch remote id " f"at {data['id']}")
resp = {"error": "failed to verify request " "(using HTTP signatures or fetching the IRI)"}
response = jsonify(resp)
response.mimetype = "application/json; charset=utf-8"
response.status = 422
@@ -87,13 +69,7 @@ def followings(name):

followings = user.actor[0].followings

return render_template(
"users/followings.jinja2",
pcfg=pcfg,
user=user,
actor=user.actor[0],
followings=followings,
)
return render_template("users/followings.jinja2", pcfg=pcfg, user=user, actor=user.actor[0], followings=followings)


@bp_ap.route("/user/<string:name>/followers", methods=["GET"])
@@ -107,11 +83,7 @@ def followers(name):
return redirect(url_for("bp_main.home"))

return render_template(
"users/followers.jinja2",
pcfg=pcfg,
user=user,
actor=user.actor[0],
followers=user.actor[0].followers,
"users/followers.jinja2", pcfg=pcfg, user=user, actor=user.actor[0], followers=user.actor[0].followers
)


@@ -134,9 +106,7 @@ def user_followers(name):
actor = user.actor[0]
followers = actor.followers

return jsonify(
**build_ordered_collection(followers, actor.url, request.args.get("page"))
)
return jsonify(**build_ordered_collection(followers, actor.url, request.args.get("page")))


@bp_ap.route("/inbox", methods=["GET", "POST"])
@@ -152,22 +122,15 @@ def inbox():
current_app.logger.debug(f"raw_data={data}")

try:
if not verify_request(
request.method, request.path, request.headers, request.data
):
if not verify_request(request.method, request.path, request.headers, request.data):
raise Exception("failed to verify request")
except Exception:
current_app.logger.exception("failed to verify request")
try:
data = be.fetch_iri(data["id"])
except Exception:
current_app.logger.exception(
f"failed to fetch remote id " f"at {data['id']}"
)
resp = {
"error": "failed to verify request "
"(using HTTP signatures or fetching the IRI)"
}
current_app.logger.exception(f"failed to fetch remote id " f"at {data['id']}")
resp = {"error": "failed to verify request " "(using HTTP signatures or fetching the IRI)"}
response = jsonify(resp)
response.mimetype = "application/json; charset=utf-8"
response.status = 422
@@ -206,9 +169,7 @@ def outbox_item(item_id):

current_app.logger.debug(f"activity url {be.activity_url(item_id)}")

item = Activity.query.filter(
Activity.box == Box.OUTBOX.value, Activity.url == be.activity_url(item_id)
).first()
item = Activity.query.filter(Activity.box == Box.OUTBOX.value, Activity.url == be.activity_url(item_id)).first()
if not item:
abort(404)


+ 1
- 3
controllers/api/v1/nodeinfo.py View File

@@ -9,9 +9,7 @@ bp_nodeinfo = Blueprint("bp_nodeinfo", __name__, url_prefix="/nodeinfo")
def nodeinfo():
_config = Config.query.one()
if not _config:
return Response(
"", status=500, content_type="application/jrd+json; charset=utf-8"
)
return Response("", status=500, content_type="application/jrd+json; charset=utf-8")

resp = {
"version": "2.0",

+ 7
- 24
controllers/api/v1/well_known.py View File

@@ -8,36 +8,26 @@ bp_wellknown = Blueprint("bp_wellknown", __name__, url_prefix="/.well-known")
def webfinger():
resource = request.args.get("resource")
if not resource:
return Response(
"", status=400, content_type="application/jrd+json; charset=utf-8"
)
return Response("", status=400, content_type="application/jrd+json; charset=utf-8")

id_list = resource.split(":")
if len(id_list) < 2:
return Response(
"", status=400, content_type="application/jrd+json; charset=utf-8"
)
return Response("", status=400, content_type="application/jrd+json; charset=utf-8")

try:
user_id, domain = id_list[1].split("@")
except ValueError:
return Response(
"", status=400, content_type="application/jrd+json; charset=utf-8"
)
return Response("", status=400, content_type="application/jrd+json; charset=utf-8")

if len(id_list) == 3:
domain += f":{id_list[2]}"

if not (domain == current_app.config["AP_DOMAIN"]):
return Response(
"", status=404, content_type="application/jrd+json; charset=utf-8"
)
return Response("", status=404, content_type="application/jrd+json; charset=utf-8")

user = db.session.query(User).filter_by(name_insensitive=user_id).first()
if not user:
return Response(
"", status=404, content_type="application/jrd+json; charset=utf-8"
)
return Response("", status=404, content_type="application/jrd+json; charset=utf-8")

method = "https"

@@ -45,11 +35,7 @@ def webfinger():
"subject": f"acct:{user.name}@{domain}",
"aliases": [f"{method}://{domain}/user/{user.name}"],
"links": [
{
"rel": "self",
"type": "application/activity+json",
"href": f"{method}://{domain}/user/{user.name}",
}
{"rel": "self", "type": "application/activity+json", "href": f"{method}://{domain}/user/{user.name}"}
],
}

@@ -64,10 +50,7 @@ def nodeinfo():
domain = current_app.config["AP_DOMAIN"]
resp = {
"links": [
{
"rel": "http://nodeinfo.diaspora.software/ns/schema/2.0",
"href": f"{method}://{domain}/nodeinfo/2.0",
}
{"rel": "http://nodeinfo.diaspora.software/ns/schema/2.0", "href": f"{method}://{domain}/nodeinfo/2.0"}
]
}
response = jsonify(resp)

+ 53
- 42
controllers/search.py View File

@@ -1,19 +1,12 @@
from flask import (
Blueprint,
render_template,
request,
redirect,
url_for,
flash,
current_app,
)
from flask import Blueprint, render_template, request, redirect, url_for, flash, current_app
from flask_babelex import gettext

from models import User
from models import db, User, Follower, Actor
from little_boxes.webfinger import get_actor_url
from little_boxes.urlutils import InvalidURLError
from little_boxes import activitypub as ap
from urllib.parse import urlparse
from flask_security import current_user

bp_search = Blueprint("bp_search", __name__, url_prefix="/search")

@@ -23,42 +16,60 @@ def users():
who = request.args.get("who")
pcfg = {"title": gettext("Search user")}

# Search is to be done in two steps:
# 1. Search from local User
# 2. If not found, webfinger it
# No follow status for unauthenticated user search
if not current_user.is_authenticated:
local_users = User.query.filter(User.name.contains(who)).all()

local_users = User.query.filter(User.name.like(who)).all()

if len(local_users) > 0:
return render_template(
"search/local_user.jinja2", pcfg=pcfg, who=who, users=local_users
if len(local_users) > 0:
return render_template("search/local_users_unauth.jinja2", pcfg=pcfg, who=who, users=local_users)
else:
# (user, actor, follower) tuple
local_users = (
db.session.query(User, Actor, Follower)
.join(Actor, User.id == Actor.user_id)
.outerjoin(Follower, Actor.id == Follower.target_id)
.filter(User.name.contains(who))
.all()
)

if not local_users:
try:
remote_actor_url = get_actor_url(who, debug=current_app.debug)
except InvalidURLError:
current_app.logger.exception(f"Invalid webfinger URL: {who}")
remote_actor_url = None
if len(local_users) > 0:
return render_template("search/local_users_auth.jinja2", pcfg=pcfg, who=who, users=local_users)

if not remote_actor_url:
flash(gettext("User not found"), "error")
return redirect(url_for("bp_main.home"))
if not local_users:
current_app.logger.debug(f"searching for {who}")
try:
remote_actor_url = get_actor_url(who, debug=current_app.debug)
except (InvalidURLError, ValueError):
current_app.logger.exception(f"Invalid webfinger URL: {who}")
remote_actor_url = None

# We need to get the remote Actor
backend = ap.get_backend()
iri = backend.fetch_iri(remote_actor_url)
if not iri:
flash(gettext("User not found"), "error")
return redirect(url_for("bp_main.home"))
if not remote_actor_url:
flash(gettext("User not found"), "error")
return redirect(url_for("bp_main.home"))

domain = urlparse(iri["url"])
user = {
"name": iri["preferredUsername"],
"instance": domain.netloc,
"url": iri["url"],
}
# We need to get the remote Actor
backend = ap.get_backend()
iri = backend.fetch_iri(remote_actor_url)
if not iri:
flash(gettext("User not found"), "error")
return redirect(url_for("bp_main.home"))

return render_template(
"search/remote_user.jinja2", pcfg=pcfg, who=who, user=user
)
current_app.logger.debug(f"got remote actor URL {remote_actor_url}")

follow_rel = (
db.session.query(Actor.id, Follower.id)
.outerjoin(Follower, Actor.id == Follower.target_id)
.filter(Actor.url == remote_actor_url)
.first()
)
follow_status = follow_rel[1] is not None

domain = urlparse(iri["url"])
user = {
"name": iri["preferredUsername"],
"instance": domain.netloc,
"url": iri["url"],
"follow": follow_status,
}

return render_template("search/remote_user.jinja2", pcfg=pcfg, who=who, user=user)

+ 18
- 74
controllers/sound.py View File

@@ -1,14 +1,4 @@
from flask import (
Blueprint,
render_template,
request,
redirect,
url_for,
flash,
Response,
abort,
json,
)
from flask import Blueprint, render_template, request, redirect, url_for, flash, Response, abort, json
from flask_babelex import gettext
from flask_security import login_required, current_user
from flask_uploads import UploadSet, AUDIO
@@ -29,14 +19,10 @@ def show(username, soundslug):
flash(gettext("User not found"), "error")
return redirect(url_for("bp_main.home"))
if current_user.is_authenticated and user.id == current_user.id:
sound = Sound.query.filter(
Sound.slug == soundslug, Sound.user_id == user.id
).first()
sound = Sound.query.filter(Sound.slug == soundslug, Sound.user_id == user.id).first()
else:
sound = Sound.query.filter(
Sound.slug == soundslug,
Sound.user_id == user.id,
Sound.transcode_state == Sound.TRANSCODE_DONE,
Sound.slug == soundslug, Sound.user_id == user.id, Sound.transcode_state == Sound.TRANSCODE_DONE
).first()

if not sound:
@@ -63,21 +49,15 @@ def show(username, soundslug):
# if si and si.type == "FLAC":
# flash(gettext("No HTML5 player supported actually"), 'info')

return render_template(
"sound/show.jinja2", pcfg=pcfg, user=user, sound=sound, waveform=si_w
)
return render_template("sound/show.jinja2", pcfg=pcfg, user=user, sound=sound, waveform=si_w)


@bp_sound.route(
"/user/<string:username>/track/<string:soundslug>/waveform.json", methods=["GET"]
)
@bp_sound.route("/user/<string:username>/track/<string:soundslug>/waveform.json", methods=["GET"])
def waveform_json(username, soundslug):
user = User.query.filter(User.name == username).first()
if not user:
raise InvalidUsage("User not found", status_code=404)
sound = Sound.query.filter(
Sound.slug == soundslug, Sound.user_id == user.id
).first()
sound = Sound.query.filter(Sound.slug == soundslug, Sound.user_id == user.id).first()
if not sound:
raise InvalidUsage("Sound not found", status_code=404)

@@ -131,10 +111,7 @@ def upload():
rec.title = form.title.data
rec.private = form.private.data

if (
"flac" in request.files["sound"].mimetype
or "ogg" in request.files["sound"].mimetype
):
if "flac" in request.files["sound"].mimetype or "ogg" in request.files["sound"].mimetype:
rec.transcode_state = Sound.TRANSCODE_WAITING
rec.transcode_needed = True

@@ -147,35 +124,21 @@ def upload():
upload_workflow.send(rec.id)

# log
add_user_log(
rec.id,
user.id,
"sounds",
"info",
"Uploaded {0} -- {1}".format(rec.id, rec.title),
)
add_user_log(rec.id, user.id, "sounds", "info", "Uploaded {0} -- {1}".format(rec.id, rec.title))

flash(gettext("Uploaded ! Processing will now follow."), "success")
else:
return render_template(
"sound/upload.jinja2", pcfg=pcfg, form=form, flash="Error with the file"
)
return redirect(
url_for("bp_sound.show", username=current_user.name, soundslug=rec.slug)
)
return render_template("sound/upload.jinja2", pcfg=pcfg, form=form, flash="Error with the file")
return redirect(url_for("bp_sound.show", username=current_user.name, soundslug=rec.slug))

# GET
return render_template("sound/upload.jinja2", pcfg=pcfg, form=form)


@bp_sound.route(
"/user/<string:username>/track/<string:soundslug>/edit", methods=["GET", "POST"]
)
@bp_sound.route("/user/<string:username>/track/<string:soundslug>/edit", methods=["GET", "POST"])
@login_required
def edit(username, soundslug):
sound = Sound.query.filter(
Sound.user_id == current_user.id, Sound.slug == soundslug
).first()
sound = Sound.query.filter(Sound.user_id == current_user.id, Sound.slug == soundslug).first()
if not sound:
flash(gettext("Sound not found"), "error")
return redirect(url_for("bp_users.profile", name=username))
@@ -184,7 +147,7 @@ def edit(username, soundslug):
flash(gettext("Forbidden"), "error")
return redirect(url_for("bp_users.profile", name=username))

pcfg = {"title": gettext(u"Edit %(title)s", title=sound.title)}
pcfg = {"title": gettext("Edit %(title)s", title=sound.title)}

form = SoundEditForm(request.form, obj=sound)

@@ -203,29 +166,16 @@ def edit(username, soundslug):

db.session.commit()
# log
add_user_log(
sound.id,
sound.user.id,
"sounds",
"info",
"Edited {0} -- {1}".format(sound.id, sound.title),
)
return redirect(
url_for("bp_sound.show", username=username, soundslug=sound.slug)
)
add_user_log(sound.id, sound.user.id, "sounds", "info", "Edited {0} -- {1}".format(sound.id, sound.title))
return redirect(url_for("bp_sound.show", username=username, soundslug=sound.slug))

return render_template("sound/edit.jinja2", pcfg=pcfg, form=form, sound=sound)


@bp_sound.route(
"/user/<string:username>/track/<string:soundslug>/delete",
methods=["GET", "DELETE", "PUT"],
)
@bp_sound.route("/user/<string:username>/track/<string:soundslug>/delete", methods=["GET", "DELETE", "PUT"])
@login_required
def delete(username, soundslug):
sound = Sound.query.filter(
Sound.user_id == current_user.id, Sound.slug == soundslug
).first()
sound = Sound.query.filter(Sound.user_id == current_user.id, Sound.slug == soundslug).first()
if not sound:
flash(gettext("Sound not found"), "error")
return redirect(url_for("bp_users.profile", name=username))
@@ -238,12 +188,6 @@ def delete(username, soundslug):
db.session.commit()

# log
add_user_log(
sound.id,
sound.user.id,
"sounds",
"info",
"Deleted {0} -- {1}".format(sound.id, sound.title),
)
add_user_log(sound.id, sound.user.id, "sounds", "info", "Deleted {0} -- {1}".format(sound.id, sound.title))

return redirect(url_for("bp_users.profile", name=username))

+ 13
- 60
controllers/users.py View File

@@ -1,31 +1,10 @@
import pytz
from flask import (
Blueprint,
render_template,
request,
redirect,
url_for,
flash,
Response,
json,
jsonify,
current_app,
)
from flask import Blueprint, render_template, request, redirect, url_for, flash, Response, json, jsonify, current_app
from flask_babelex import gettext
from flask_security import login_required, current_user

from forms import UserProfileForm
from models import (
db,
User,
UserLogging,
Sound,
Album,
Follower,
Actor,
Activity,
create_remote_actor,
)
from models import db, User, UserLogging, Sound, Album, Follower, Actor, Activity, create_remote_actor
from utils import add_user_log
from flask_accept import accept_fallback
from little_boxes.webfinger import get_actor_url
@@ -43,28 +22,19 @@ def logs():
pcfg = {"title": gettext("User Logs")}
if level:
_logs = (
UserLogging.query.filter(
UserLogging.level == level.upper(),
UserLogging.user_id == current_user.id,
)
UserLogging.query.filter(UserLogging.level == level.upper(), UserLogging.user_id == current_user.id)
.limit(100)
.all()
)
else:
_logs = (
UserLogging.query.filter(UserLogging.user_id == current_user.id)
.limit(100)
.all()
)
_logs = UserLogging.query.filter(UserLogging.user_id == current_user.id).limit(100).all()
return render_template("users/user_logs.jinja2", pcfg=pcfg, logs=_logs)


@bp_users.route("/account/logs/<int:log_id>/delete", methods=["GET", "DELETE", "PUT"])
@login_required
def logs_delete(log_id):
log = UserLogging.query.filter(
UserLogging.id == log_id, UserLogging.user_id == current_user.id
).first()
log = UserLogging.query.filter(UserLogging.id == log_id, UserLogging.user_id == current_user.id).first()
if not log:
_datas = {"status": "error", "id": log_id}
else:
@@ -88,9 +58,7 @@ def profile(name):
sounds = Sound.query.filter(Sound.user_id == user.id)
else:
sounds = Sound.query.filter(
Sound.user_id == user.id,
Sound.private.is_(False),
Sound.transcode_state == Sound.TRANSCODE_DONE,
Sound.user_id == user.id, Sound.private.is_(False), Sound.transcode_state == Sound.TRANSCODE_DONE
)

# FIXME better SQL for counting thoses relations
@@ -98,12 +66,7 @@ def profile(name):
followers = len(user.actor[0].followers)

return render_template(
"users/profile.jinja2",
pcfg=pcfg,
user=user,
sounds=sounds,
followings=followings,
followers=followers,
"users/profile.jinja2", pcfg=pcfg, user=user, sounds=sounds, followings=followings, followers=followers
)


@@ -136,9 +99,7 @@ def profile_albums(name):
else:
albums = Album.query.filter(Album.user_id == user.id, Album.private.is_(False))

return render_template(
"users/profile_albums.jinja2", pcfg=pcfg, user=user, albums=albums
)
return render_template("users/profile_albums.jinja2", pcfg=pcfg, user=user, albums=albums)


@bp_users.route("/account/edit", methods=["GET", "POST"])
@@ -183,7 +144,7 @@ def follow():

if local_user:
# Process local follow
actor_me.follow(local_user.actor[0])
actor_me.follow(None, local_user.actor[0])
flash(gettext("Follow successful"), "success")
else:
# Might be a remote follow
@@ -275,26 +236,18 @@ def unfollow():
return redirect(url_for("bp_users.profile", name=current_user.name))

# 3. Fetch the Activity of the Follow
accept_activity = Activity.query.filter(
Activity.url == follow_relation.activity_url
).first()
accept_activity = Activity.query.filter(Activity.url == follow_relation.activity_url).first()
if not accept_activity:
current_app.logger.error(
f"cannot find accept activity {follow_relation.activity_url}"
)
current_app.logger.error(f"cannot find accept activity {follow_relation.activity_url}")
flash(gettext("Whoops, something went wrong"))
return redirect(url_for("bp_users.profile", name=current_user.name))
# Then the Activity ID of the Accept will be the object id
activity = ap.parse_activity(payload=accept_activity.payload)

# Get the final activity (the Follow one)
follow_activity = Activity.query.filter(
Activity.url == activity.get_object_id()
).first()
follow_activity = Activity.query.filter(Activity.url == activity.get_object_id()).first()
if not follow_activity:
current_app.logger.error(
f"cannot find follow activity {activity.get_object_id()}"
)
current_app.logger.error(f"cannot find follow activity {activity.get_object_id()}")
flash(gettext("Whoops, something went wrong"))
return redirect(url_for("bp_users.profile", name=current_user.name))


+ 1
- 5
dbseed.py View File

@@ -41,11 +41,7 @@ def seed_users(db):
db.session.add(role_adm)

user_datastore.create_user(
email="dashie@sigpipe.me",
password="fluttershy",
name="toto",
timezone="UTC",
roles=[role_adm],
email="dashie@sigpipe.me", password="fluttershy", name="toto", timezone="UTC", roles=[role_adm]
)
db.session.commit()
return

+ 2
- 6
forms.py View File

@@ -36,9 +36,7 @@ class ModelForm(BaseModelForm):


class ExtendedRegisterForm(RegisterForm):
name = StringField(
"Username", [DataRequired(), Regexp(regex="^\w+$"), Length(max=150)]
)
name = StringField("Username", [DataRequired(), Regexp(regex="^\w+$"), Length(max=150)])

def validate_name(form, field):
if len(field.data) <= 0:
@@ -59,9 +57,7 @@ class UserProfileForm(ModelForm):
firstname = StringField(gettext("Firstname"), [Length(max=32)])
lastname = StringField(gettext("Lastname"), [Length(max=32)])
timezone = SelectField(coerce=str, label=gettext("Timezone"), default="UTC")
locale = SelectField(
gettext("Locale"), default="en", choices=[["en", "English"], ["fr", "French"]]
)
locale = SelectField(gettext("Locale"), default="en", choices=[["en", "English"], ["fr", "French"]])
submit = SubmitField(gettext("Update profile"))



+ 3
- 7
migrations/env.py View File

@@ -22,9 +22,7 @@ logger = logging.getLogger("alembic.env")
# from myapp import mymodel
# target_metadata = mymodel.Base.metadata

config.set_main_option(
"sqlalchemy.url", current_app.config.get("SQLALCHEMY_DATABASE_URI")
)
config.set_main_option("sqlalchemy.url", current_app.config.get("SQLALCHEMY_DATABASE_URI"))
target_metadata = current_app.extensions["migrate"].db.metadata


@@ -72,9 +70,7 @@ def run_migrations_online():
logger.info("No changes in schema detected.")

engine = engine_from_config(
config.get_section(config.config_ini_section),
prefix="sqlalchemy.",
poolclass=pool.NullPool,
config.get_section(config.config_ini_section), prefix="sqlalchemy.", poolclass=pool.NullPool
)

connection = engine.connect()
@@ -83,7 +79,7 @@ def run_migrations_online():
target_metadata=target_metadata,
process_revision_directives=process_revision_directives,
transaction_per_migration=True,
**current_app.extensions["migrate"].configure_args
**current_app.extensions["migrate"].configure_args,
)

try:

+ 2
- 6
migrations/versions/00_795db5a5e99b_.py View File

@@ -63,9 +63,7 @@ def upgrade():
sa.Column("category", sa.String(length=255), nullable=False),
sa.Column("level", sa.String(length=255), nullable=False),
sa.Column("message", sa.Text(), nullable=False),
sa.Column(
"timestamp", sa.DateTime(), server_default=sa.text("now()"), nullable=True
),
sa.Column("timestamp", sa.DateTime(), server_default=sa.text("now()"), nullable=True),
sa.Column("user_id", sa.Integer(), nullable=True),
sa.ForeignKeyConstraint(["user_id"], ["user.id"]),
sa.PrimaryKeyConstraint("id"),
@@ -83,9 +81,7 @@ def upgrade():
sa.Column("category", sa.String(length=255), nullable=False),
sa.Column("level", sa.String(length=255), nullable=False),
sa.Column("message", sa.Text(), nullable=False),
sa.Column(
"timestamp", sa.DateTime(), server_default=sa.text("now()"), nullable=True
),
sa.Column("timestamp", sa.DateTime(), server_default=sa.text("now()"), nullable=True),
sa.Column("user_id", sa.Integer(), nullable=False),
sa.ForeignKeyConstraint(["user_id"], ["user.id"]),
sa.PrimaryKeyConstraint("id"),

+ 1
- 3
migrations/versions/01_da3273ca0f0f_.py View File

@@ -19,9 +19,7 @@ def upgrade():
"sound",
sa.Column("id", sa.Integer(), nullable=False),
sa.Column("title", sa.String(length=255), nullable=True),
sa.Column(
"uploaded", sa.DateTime(), server_default=sa.text("now()"), nullable=True
),
sa.Column("uploaded", sa.DateTime(), server_default=sa.text("now()"), nullable=True),
sa.Column("description", sa.UnicodeText(), nullable=True),
sa.Column("public", sa.Boolean(), nullable=False),
sa.Column("slug", sa.String(length=255), nullable=True),

+ 1
- 3
migrations/versions/06_14ecb77e9482_.py View File

@@ -16,9 +16,7 @@ from alembic import op # noqa: E402

def upgrade():
op.add_column("sound_info", sa.Column("bitrate", sa.Integer(), nullable=True))
op.add_column(
"sound_info", sa.Column("bitrate_mode", sa.String(length=10), nullable=True)
)
op.add_column("sound_info", sa.Column("bitrate_mode", sa.String(length=10), nullable=True))


def downgrade():

+ 1
- 3
migrations/versions/08_fc50eb4e9b34_.py View File

@@ -15,9 +15,7 @@ from alembic import op # noqa: E402


def upgrade():
op.add_column(
"sound_info", sa.Column("type_human", sa.String(length=20), nullable=True)
)
op.add_column("sound_info", sa.Column("type_human", sa.String(length=20), nullable=True))


def downgrade():

+ 1
- 3
migrations/versions/09_aeb6c1345690_.py View File

@@ -15,9 +15,7 @@ from alembic import op # noqa: E402


def upgrade():
op.add_column(
"sound_info", sa.Column("waveform_error", sa.Boolean(), nullable=True)
)
op.add_column("sound_info", sa.Column("waveform_error", sa.Boolean(), nullable=True))


def downgrade():

+ 1
- 3
migrations/versions/10_a901f35d5686_.py View File

@@ -15,9 +15,7 @@ from alembic import op # noqa: E402


def upgrade():
op.add_column(
"sound", sa.Column("filename_orig", sa.String(length=255), nullable=True)
)
op.add_column("sound", sa.Column("filename_orig", sa.String(length=255), nullable=True))


def downgrade():

+ 1
- 3
migrations/versions/11_f41ac4617ef6_.py View File

@@ -20,7 +20,5 @@ def upgrade():


def downgrade():
op.add_column(
"sound", sa.Column("public", sa.BOOLEAN(), autoincrement=False, nullable=False)
)
op.add_column("sound", sa.Column("public", sa.BOOLEAN(), autoincrement=False, nullable=False))
op.drop_column("sound", "private")

+ 2
- 7
migrations/versions/17_01e6d591fe6f_.py View File

@@ -15,14 +15,9 @@ from alembic import op # noqa: E402


def upgrade():
op.add_column(
"sound", sa.Column("filename_transcoded", sa.String(length=255), nullable=True)
)
op.add_column("sound", sa.Column("filename_transcoded", sa.String(length=255), nullable=True))
op.add_column("sound", sa.Column("transcode_needed", sa.Boolean(), nullable=True))
op.add_column(
"sound",
sa.Column("transcode_state", sa.Integer(), server_default="0", nullable=False),
)
op.add_column("sound", sa.Column("transcode_state", sa.Integer(), server_default="0", nullable=False))


def downgrade():

+ 1
- 3
migrations/versions/19_98b863ae920c_.py View File

@@ -15,9 +15,7 @@ import sqlalchemy as sa # noqa: E402


def upgrade():
op.add_column(
"sound", sa.Column("licence", sa.Integer(), nullable=False, server_default="0")
)
op.add_column("sound", sa.Column("licence", sa.Integer(), nullable=False, server_default="0"))


def downgrade():

+ 2
- 12
migrations/versions/20_691eaff10a88_.py View File

@@ -15,20 +15,10 @@ import sqlalchemy as sa # noqa: E402


def upgrade():
op.alter_column(
"sound",
"licence",
existing_type=sa.INTEGER(),
nullable=True,
existing_server_default=sa.text("0"),
)
op.alter_column("sound", "licence", existing_type=sa.INTEGER(), nullable=True, existing_server_default=sa.text("0"))


def downgrade():
op.alter_column(
"sound",
"licence",
existing_type=sa.INTEGER(),
nullable=False,
existing_server_default=sa.text("0"),
"sound", "licence", existing_type=sa.INTEGER(), nullable=False, existing_server_default=sa.text("0")
)

+ 2
- 12
migrations/versions/21_abf095d9c9f3_.py View File

@@ -16,19 +16,9 @@ import sqlalchemy as sa # noqa: E402

def upgrade():
op.alter_column(
"sound",
"licence",
existing_type=sa.INTEGER(),
nullable=False,
existing_server_default=sa.text("0"),
"sound", "licence", existing_type=sa.INTEGER(), nullable=False, existing_server_default=sa.text("0")
)


def downgrade():
op.alter_column(
"sound",
"licence",
existing_type=sa.INTEGER(),
nullable=True,
existing_server_default=sa.text("0"),
)
op.alter_column("sound", "licence", existing_type=sa.INTEGER(), nullable=True, existing_server_default=sa.text("0"))

+ 2
- 6
migrations/versions/22_835be4f73770_.py View File

@@ -15,12 +15,8 @@ from alembic import op # noqa: E402


def upgrade():
op.alter_column(
"user_logging", "sound_id", existing_type=sa.INTEGER(), nullable=True
)
op.alter_column("user_logging", "sound_id", existing_type=sa.INTEGER(), nullable=True)


def downgrade():
op.alter_column(
"user_logging", "sound_id", existing_type=sa.INTEGER(), nullable=False
)
op.alter_column("user_logging", "sound_id", existing_type=sa.INTEGER(), nullable=False)

+ 2
- 7
migrations/versions/23_2ec399782f52_.py View File

@@ -21,11 +21,6 @@ def upgrade():


def downgrade():
op.add_column(
"user_logging",
sa.Column("sound_id", sa.INTEGER(), autoincrement=False, nullable=True),
)
op.create_foreign_key(
"user_logging_sound_id_fkey", "user_logging", "sound", ["sound_id"], ["id"]
)
op.add_column("user_logging", sa.Column("sound_id", sa.INTEGER(), autoincrement=False, nullable=True))
op.create_foreign_key("user_logging_sound_id_fkey", "user_logging", "sound", ["sound_id"], ["id"])
op.drop_column("user_logging", "item_id")

+ 2
- 6
migrations/versions/25_32f48de123f3_.py View File

@@ -25,9 +25,7 @@ def upgrade():
sa.Column("inbox_url", sqlalchemy_utils.types.url.URLType(), nullable=True),
sa.Column("following_url", sqlalchemy_utils.types.url.URLType(), nullable=True),
sa.Column("followers_url", sqlalchemy_utils.types.url.URLType(), nullable=True),
sa.Column(
"shared_inbox_url", sqlalchemy_utils.types.url.URLType(), nullable=True
),
sa.Column("shared_inbox_url", sqlalchemy_utils.types.url.URLType(), nullable=True),
sa.Column(
"type",
sqlalchemy_utils.types.choice.ChoiceType(choices=ACTOR_TYPE_CHOICES),
@@ -46,9 +44,7 @@ def upgrade():
sa.Column("user_id", sa.Integer(), nullable=True),
sa.ForeignKeyConstraint(["user_id"], ["user.id"]),
sa.PrimaryKeyConstraint("id"),
sa.UniqueConstraint(
"domain", "preferred_username", name="_domain_pref_username_uc"
),
sa.UniqueConstraint("domain", "preferred_username", name="_domain_pref_username_uc"),
)
op.create_index(op.f("ix_actor_url"), "actor", ["url"], unique=True)


+ 2
- 10
migrations/versions/27_687d40646d63_.py View File

@@ -18,20 +18,12 @@ from sqlalchemy.dialects import postgresql # noqa: E402
def upgrade():
print("This migration may fails if you don't already have the " "extension created")
print("If you get an error about 'function uuid_generate_v4() " "does not exist'")
print(
"please run 'CREATE EXTENSION IF NOT EXISTS \"uuid-ossp\";' "
"from postgresql shell"
)
print("please run 'CREATE EXTENSION IF NOT EXISTS \"uuid-ossp\";' " "from postgresql shell")
print("with a valid superuser on your database.")
op.create_table(
"follow",
sa.Column("id", sa.Integer(), nullable=False),
sa.Column(
"uuid",
postgresql.UUID(as_uuid=True),
server_default=sa.text("uuid_generate_v4()"),
nullable=True,
),
sa.Column("uuid", postgresql.UUID(as_uuid=True), server_default=sa.text("uuid_generate_v4()"), nullable=True),
sa.Column("creation_date", sa.DateTime(), nullable=True),
sa.Column("modification_date", sa.DateTime(), nullable=True),
sa.PrimaryKeyConstraint("id"),

+ 5
- 26
migrations/versions/28_2e83f405e9a4_.py View File

@@ -21,12 +21,7 @@ def upgrade():
"activity",
sa.Column("id", sa.Integer(), nullable=False),
sa.Column("actor", sa.Integer(), nullable=True),
sa.Column(
"uuid",
postgresql.UUID(as_uuid=True),
server_default=sa.text("uuid_generate_v4()"),
nullable=True,
),
sa.Column("uuid", postgresql.UUID(as_uuid=True), server_default=sa.text("uuid_generate_v4()"), nullable=True),
sa.Column("url", sqlalchemy_utils.types.url.URLType(), nullable=True),
sa.Column("type", sa.String(length=100), nullable=True),
sa.Column("payload", sqlalchemy_utils.types.json.JSONType(), nullable=True),
@@ -42,12 +37,7 @@ def upgrade():
op.create_table(
"followers",
sa.Column("id", sa.Integer(), nullable=False),
sa.Column(
"uuid",
postgresql.UUID(as_uuid=True),
server_default=sa.text("uuid_generate_v4()"),
nullable=True,
),
sa.Column("uuid", postgresql.UUID(as_uuid=True), server_default=sa.text("uuid_generate_v4()"), nullable=True),
sa.Column("actor_id", sa.Integer(), nullable=True),
sa.Column("target_id", sa.Integer(), nullable=True),
sa.Column("creation_date", sa.DateTime(), nullable=True),
@@ -66,21 +56,10 @@ def downgrade():
"follow",
sa.Column("id", sa.INTEGER(), autoincrement=True, nullable=False),
sa.Column(
"uuid",
postgresql.UUID(),
server_default=sa.text("uuid_generate_v4()"),
autoincrement=False,
nullable=True,
),
sa.Column(
"creation_date", postgresql.TIMESTAMP(), autoincrement=False, nullable=True
),
sa.Column(
"modification_date",
postgresql.TIMESTAMP(),
autoincrement=False,
nullable=True,
"uuid", postgresql.UUID(), server_default=sa.text("uuid_generate_v4()"), autoincrement=False, nullable=True
),
sa.Column("creation_date", postgresql.TIMESTAMP(), autoincrement=False, nullable=True),
sa.Column("modification_date", postgresql.TIMESTAMP(), autoincrement=False, nullable=True),
sa.PrimaryKeyConstraint("id", name="follow_pkey"),
sa.UniqueConstraint("uuid", name="follow_uuid_key"),
)

+ 1
- 4
migrations/versions/31_7ce00beb5b0a_.py View File

@@ -16,10 +16,7 @@ import sqlalchemy_utils # noqa: E402


def upgrade():
op.add_column(
"followers",
sa.Column("activity_url", sqlalchemy_utils.types.url.URLType(), nullable=True),
)
op.add_column("followers", sa.Column("activity_url", sqlalchemy_utils.types.url.URLType(), nullable=True))
op.create_unique_constraint(None, "followers", ["activity_url"])



+ 39
- 101
models.py View File

@@ -60,12 +60,8 @@ class Role(db.Model, RoleMixin):

class User(db.Model, UserMixin):
id = db.Column(db.Integer, primary_key=True)
email = db.Column(
db.String(255), unique=True, nullable=False, info={"label": "Email"}
)
name = db.Column(
db.String(255), unique=True, nullable=False, info={"label": "Username"}
)
email = db.Column(db.String(255), unique=True, nullable=False, info={"label": "Email"})
name = db.Column(db.String(255), unique=True, nullable=False, info={"label": "Username"})
password = db.Column(db.String(255), nullable=False, info={"label": "Password"})
active = db.Column(db.Boolean())
confirmed_at = db.Column(db.DateTime())
@@ -75,25 +71,15 @@ class User(db.Model, UserMixin):

locale = db.Column(db.String(5), default="en")

timezone = db.Column(
db.String(255), nullable=False, default="UTC"
) # Managed and fed by pytz
timezone = db.Column(db.String(255), nullable=False, default="UTC") # Managed and fed by pytz

slug = db.Column(db.String(255), unique=True, nullable=True)

roles = db.relationship(
"Role", secondary=roles_users, backref=db.backref("users", lazy="dynamic")
)
apitokens = db.relationship(
"Apitoken", backref="user", lazy="dynamic", cascade="delete"
)
roles = db.relationship("Role", secondary=roles_users, backref=db.backref("users", lazy="dynamic"))
apitokens = db.relationship("Apitoken", backref="user", lazy="dynamic", cascade="delete")

user_loggings = db.relationship(
"UserLogging", backref="user", lazy="dynamic", cascade="delete"
)
loggings = db.relationship(
"Logging", backref="user", lazy="dynamic", cascade="delete"
)
user_loggings = db.relationship("UserLogging", backref="user", lazy="dynamic", cascade="delete")
loggings = db.relationship("Logging", backref="user", lazy="dynamic", cascade="delete")

sounds = db.relationship("Sound", backref="user", lazy="dynamic", cascade="delete")
albums = db.relationship("Album", backref="user", lazy="dynamic", cascade="delete")
@@ -116,6 +102,9 @@ class User(db.Model, UserMixin):
def name_insensitive(cls):
return CaseInsensitiveComparator(cls.name)

def __repr__(self):
return f"<User(id='{self.id}', name='{self.name}')>"


event.listen(User.name, "set", User.generate_slug, retval=False)

@@ -123,12 +112,8 @@ event.listen(User.name, "set", User.generate_slug, retval=False)
class Apitoken(db.Model):
id = db.Column(db.Integer, primary_key=True)
user_id = db.Column(db.Integer(), db.ForeignKey("user.id"), nullable=False)
token = db.Column(
db.String(255), unique=True, nullable=False, info={"label": "Token"}
)
secret = db.Column(
db.String(255), unique=True, nullable=False, info={"label": "Secret"}
)
token = db.Column(db.String(255), unique=True, nullable=False, info={"label": "Token"})
secret = db.Column(db.String(255), unique=True, nullable=False, info={"label": "Secret"})


user_datastore = SQLAlchemyUserDatastore(db, User, Role)
@@ -144,9 +129,7 @@ class Logging(db.Model):
category = db.Column(db.String(255), nullable=False, default="General")
level = db.Column(db.String(255), nullable=False, default="INFO")
message = db.Column(db.Text, nullable=False)
timestamp = db.Column(
db.DateTime(timezone=False), server_default=func.now(), onupdate=func.now()
)
timestamp = db.Column(db.DateTime(timezone=False), server_default=func.now(), onupdate=func.now())

user_id = db.Column(db.Integer(), db.ForeignKey("user.id"), nullable=True)

@@ -160,9 +143,7 @@ class UserLogging(db.Model):
category = db.Column(db.String(255), nullable=False, default="General")
level = db.Column(db.String(255), nullable=False, default="INFO")
message = db.Column(db.Text, nullable=False)
timestamp = db.Column(
db.DateTime(timezone=False), server_default=func.now(), onupdate=func.now()
)
timestamp = db.Column(db.DateTime(timezone=False), server_default=func.now(), onupdate=func.now())
item_id = db.Column(db.Integer(), nullable=True)

user_id = db.Column(db.Integer(), db.ForeignKey("user.id"), nullable=False)
@@ -206,9 +187,7 @@ class Sound(db.Model):
id = db.Column(db.Integer, primary_key=True)
title = db.Column(db.String(255), nullable=True)
uploaded = db.Column(db.DateTime(timezone=False), default=func.now())
updated = db.Column(
db.DateTime(timezone=False), default=func.now(), onupdate=func.now()
)
updated = db.Column(db.DateTime(timezone=False), default=func.now(), onupdate=func.now())
# TODO genre
# TODO tags
# TODO picture ?
@@ -228,9 +207,7 @@ class Sound(db.Model):

user_id = db.Column(db.Integer(), db.ForeignKey("user.id"), nullable=False)
album_id = db.Column(db.Integer(), db.ForeignKey("album.id"), nullable=True)
sound_infos = db.relationship(
"SoundInfo", backref="sound_info", lazy="dynamic", cascade="delete"
)
sound_infos = db.relationship("SoundInfo", backref="sound_info", lazy="dynamic", cascade="delete")

timeline = db.relationship("Timeline", uselist=False, back_populates="sound")

@@ -245,11 +222,7 @@ class Sound(db.Model):
return os.path.join(self.user.slug, "{0}.png".format(self.filename))

def path_sound(self, orig=False):
if (
self.transcode_needed
and self.transcode_state == self.TRANSCODE_DONE
and not orig
):
if self.transcode_needed and self.transcode_state == self.TRANSCODE_DONE and not orig:
return os.path.join(self.user.slug, self.filename_transcoded)
else:
return os.path.join(self.user.slug, self.filename)
@@ -273,9 +246,7 @@ class Album(db.Model):
title = db.Column(db.String(255), nullable=True)
created = db.Column(db.DateTime(timezone=False), default=datetime.datetime.utcnow)
updated = db.Column(
db.DateTime(timezone=False),
default=datetime.datetime.utcnow,
onupdate=datetime.datetime.utcnow,
db.DateTime(timezone=False), default=datetime.datetime.utcnow, onupdate=datetime.datetime.utcnow
)
# TODO tags
description = db.Column(db.UnicodeText(), nullable=True)
@@ -365,11 +336,7 @@ def make_sound_slug(mapper, connection, target):
title = "{0} {1}".format(target.id, target.title)

slug = slugify(title[:255])
connection.execute(
Sound.__table__.update()
.where(Sound.__table__.c.id == target.id)
.values(slug=slug)
)
connection.execute(Sound.__table__.update().where(Sound.__table__.c.id == target.id).values(slug=slug))


@event.listens_for(Album, "after_update")
@@ -377,11 +344,7 @@ def make_sound_slug(mapper, connection, target):
def make_album_slug(mapper, connection, target):
title = "{0} {1}".format(target.id, target.title)
slug = slugify(title[:255])
connection.execute(
Album.__table__.update()
.where(Album.__table__.c.id == target.id)
.values(slug=slug)
)
connection.execute(Album.__table__.update().where(Album.__table__.c.id == target.id).values(slug=slug))


# #### Federation ####
@@ -402,20 +365,17 @@ class Follower(db.Model):
__tablename__ = "followers"

id = db.Column(db.Integer, primary_key=True)
uuid = db.Column(
UUID(as_uuid=True), server_default=sa_text("uuid_generate_v4()"), unique=True
)
uuid = db.Column(UUID(as_uuid=True), server_default=sa_text("uuid_generate_v4()"), unique=True)
actor_id = db.Column(db.Integer, db.ForeignKey("actor.id"))
target_id = db.Column(db.Integer, db.ForeignKey("actor.id"))
activity_url = db.Column(URLType(), unique=True, nullable=True)
creation_date = db.Column(db.DateTime(timezone=False), default=func.now())
modification_date = db.Column(
db.DateTime(timezone=False), onupdate=datetime.datetime.now
)
modification_date = db.Column(db.DateTime(timezone=False), onupdate=datetime.datetime.now)

__table_args__ = (
UniqueConstraint("actor_id", "target_id", name="unique_following"),
)
__table_args__ = (UniqueConstraint("actor_id", "target_id", name="unique_following"),)

def __repr__(self):
return f"<Follower(id='{self.id}', actor_id='{self.actor_id}', target_id='{self.target_id}')>"


class Actor(db.Model):
@@ -439,17 +399,11 @@ class Actor(db.Model):
private_key = db.Column(db.String(5000), nullable=True)
creation_date = db.Column(db.DateTime(timezone=False), default=func.now())
last_fetch_date = db.Column(db.DateTime(timezone=False), default=func.now())
manually_approves_followers = db.Column(
db.Boolean, nullable=True, server_default=None
)
manually_approves_followers = db.Column(db.Boolean, nullable=True, server_default=None)
# Who follows self
followers = db.relationship(
"Follower", backref="followers", primaryjoin=id == Follower.target_id
)
followers = db.relationship("Follower", backref="followers", primaryjoin=id == Follower.target_id)
# Who self follows
followings = db.relationship(
"Follower", backref="followings", primaryjoin=id == Follower.actor_id
)
followings = db.relationship("Follower", backref="followings", primaryjoin=id == Follower.actor_id)
# Relation on itself, intermediary with actor and target
# By using an Association Object, which isn't possible except by using
# two relations. This may be better than only one, and some hackish things
@@ -458,11 +412,10 @@ class Actor(db.Model):
user_id = db.Column(db.Integer, db.ForeignKey("user.id"), nullable=True)
user = db.relationship("User", backref=db.backref("actor"))

__table_args__ = (
UniqueConstraint(
"domain", "preferred_username", name="_domain_pref_username_uc"
),
)
__table_args__ = (UniqueConstraint("domain", "preferred_username", name="_domain_pref_username_uc"),)

def __repr__(self):
return f"<Actor(id='{self.id}', user_id='{self.user_id}', preferredUsername='{self.preferred_username}', domain='{self.domain}')>"

def webfinger_subject(self):
return f"{self.preferred_username}@{self.domain}"
@@ -479,9 +432,7 @@ class Actor(db.Model):
def follow(self, activity_url, target):
current_app.logger.debug(f"saving: {self.id} following {target.id}")

rel = Follower.query.filter(
Follower.actor_id == self.id, Follower.target_id == target.id
).first()
rel = Follower.query.filter(Follower.actor_id == self.id, Follower.target_id == target.id).first()

if not rel:
rel = Follower()
@@ -494,30 +445,21 @@ class Actor(db.Model):
def unfollow(self, target):
current_app.logger.debug(f"saving: {self.id} unfollowing {target.id}")

rel = Follower.query.filter(
Follower.actor_id == self.id, Follower.target_id == target.id
).first()
rel = Follower.query.filter(Follower.actor_id == self.id, Follower.target_id == target.id).first()
if rel:
db.session.delete(rel)
db.session.commit()

def to_dict(self):
return {
"@context": [
"https://www.w3.org/ns/activitystreams",
"https://w3id.org/security/v1",
],
"@context": ["https://www.w3.org/ns/activitystreams", "https://w3id.org/security/v1"],
"id": self.url,
"type": self.type.code,
"preferredUsername": self.preferred_username,
"inbox": self.inbox_url,
"outbox": self.outbox_url,
"manuallyApprovesFollowers": self.manually_approves_followers,
"publicKey": {
"id": self.private_key_id(),
"owner": self.url,
"publicKeyPem": self.public_key,
},
"publicKey": {"id": self.private_key_id(), "owner": self.url, "publicKeyPem": self.public_key},
"endpoints": {"sharedInbox": self.shared_inbox_url},
}

@@ -528,9 +470,7 @@ class Activity(db.Model):

actor = db.Column(db.Integer, db.ForeignKey("actor.id"))

uuid = db.Column(
UUID(as_uuid=True), server_default=sa_text("uuid_generate_v4()"), unique=True
)
uuid = db.Column(UUID(as_uuid=True), server_default=sa_text("uuid_generate_v4()"), unique=True)
url = db.Column(URLType(), unique=True, nullable=True)
type = db.Column(db.String(100), index=True)
box = db.Column(db.String(100))
@@ -581,9 +521,7 @@ def create_remote_actor(activity_actor: ap.BaseActivity):
actor.name = activity_actor.preferredUsername # mastodon don't have .name
actor.manually_approves_followers = False
actor.url = activity_actor.id # FIXME: or .id ??? [cf backend.py:52-53]
actor.shared_inbox_url = activity_actor._data.get("endpoints", {}).get(
"sharedInbox"
)
actor.shared_inbox_url = activity_actor._data.get("endpoints", {}).get("sharedInbox")
actor.inbox_url = activity_actor.inbox
actor.outbox_url = activity_actor.outbot
actor.public_key = activity_actor.get_key().pubkey_pem

+ 2
- 0
pyproject.toml View File

@@ -1,4 +1,6 @@
[tool.black]
line-length = 120
py36 = true
exclude = '''
/(
\.git

+ 1
- 0
setup.cfg View File

@@ -7,3 +7,4 @@ python_files = tests/*.py

[flake8]
max-line-length = 120
ignore = E501

+ 25
- 0
templates/search/local_users_auth.jinja2 View File

@@ -0,0 +1,25 @@
{% extends "layout.jinja2" %}

{% block content %}
<div class="row">
<div class="col-lg-10"><h3>{{ gettext("Searching for: <i>%(username)s</i>", username=who) }}</h3></div>
</div>

<br/>

<div class="row">
<div class="col-lg-10">
{% for user in users %}
<div class="row_user">
{{ gettext("Local user:") }} <a href="{{ url_for('bp_users.profile', name=user[0].name) }}">{{ user[0].name }}</a> -
{% if user[2] %}
<a href="{{ url_for("bp_users.unfollow", user=user[0].name) }}">unfollow</a>
{% else %}
<a href="{{ url_for("bp_users.follow", user=user[0].name) }}">follow</a>
{% endif %}
</div>
{% endfor %}
</div>
</div>

{% endblock %}

templates/search/local_user.jinja2 → templates/search/local_users_unauth.jinja2 View File


+ 6
- 1
templates/search/remote_user.jinja2 View File

@@ -10,7 +10,12 @@
<div class="row">
<div class="col-lg-10">
<div class="row_user">
{{ gettext("Remote user:") }} <a href="{{ user['url'] }}">{{ user['name'] }}@{{ user['instance'] }}</a> - <a href="{{ url_for("bp_users.follow", user=user['url']) }}">follow</a> - <a href="{{ url_for("bp_users.unfollow", user=user['url']) }}">unfollow</a>
{{ gettext("Remote user:") }} <a href="{{ user['url'] }}">{{ user['name'] }}@{{ user['instance'] }}</a> -
{% if user.follow %}
<a href="{{ url_for("bp_users.unfollow", user=user['url']) }}">unfollow</a>
{% else %}
<a href="{{ url_for("bp_users.follow", user=user['url']) }}">follow</a>
{% endif %}
</div>
</div>
</div>

+ 1
- 3
tests/helpers.py View File

@@ -4,9 +4,7 @@ from jsonschema import validate


def login(client, email, password):
return client.post(
"/login", data=dict(email=email, password=password), follow_redirects=True
)
return client.post("/login", data=dict(email=email, password=password), follow_redirects=True)


def logout(client):

+ 3
- 13
tests/test_c_users.py View File

@@ -34,19 +34,13 @@ def test_register_two_identical_users(client, session):
resp = client.post(
"/register",
data=dict(
email="dashie+imunique@sigpipe.me",
password="fluttershy",
password_confirm="fluttershy",
name="ImUnique",
email="dashie+imunique@sigpipe.me", password="fluttershy", password_confirm="fluttershy", name="ImUnique"
),
follow_redirects=True,
)
# should error
assert b"Logged as" not in resp.data
assert (
b"dashie+imunique@sigpipe.me is already associated "
b"with an account." in resp.data
)
assert b"dashie+imunique@sigpipe.me is already associated " b"with an account." in resp.data
assert b"Username already taken" in resp.data
assert resp.status_code == 200

@@ -64,11 +58,7 @@ def test_change_password(client, session):
# Change password
resp = client.post(
"/change",
data=dict(
password=init_password,
new_password=new_password,
new_password_confirm=new_password,
),
data=dict(password=init_password, new_password=new_password, new_password_confirm=new_password),
follow_redirects=True,
)


+ 6
- 21
tests/test_wellknown.py View File

@@ -14,21 +14,14 @@ def test_webfinger(client, session):
datas = rv.json

assert "aliases" in datas
assert (
f"https://{current_app.config['AP_DOMAIN']}/user/TestWebfinger"
in datas["aliases"]
)
assert f"https://{current_app.config['AP_DOMAIN']}/user/TestWebfinger" in datas["aliases"]
assert "links" in datas
assert "subject" in datas
assert (
datas["subject"] == f"acct:TestWebfinger@" f"{current_app.config['AP_DOMAIN']}"
)
assert datas["subject"] == f"acct:TestWebfinger@" f"{current_app.config['AP_DOMAIN']}"


def test_webfinger_case(client, session):
register(
client, "dashie+webfingercase@sigpipe.me", "fluttershy", "TestWebfingerCase"
)
register(client, "dashie+webfingercase@sigpipe.me", "fluttershy", "TestWebfingerCase")
logout(client)

rv = client.get("/.well-known/webfinger?resource=acct:testwebfingercase@localhost")
@@ -39,22 +32,14 @@ def test_webfinger_case(client, session):
datas = rv.json

assert "aliases" in datas
assert (
f"https://{current_app.config['AP_DOMAIN']}"
f"/user/TestWebfingerCase" in datas["aliases"]
)
assert f"https://{current_app.config['AP_DOMAIN']}" f"/user/TestWebfingerCase" in datas["aliases"]
assert "links" in datas
assert "subject" in datas
assert (
datas["subject"] == f"acct:TestWebfingerCase@"
f"{current_app.config['AP_DOMAIN']}"
)
assert datas["subject"] == f"acct:TestWebfingerCase@" f"{current_app.config['AP_DOMAIN']}"


def test_unknown_webfinger(client, session):
rv = client.get(
"/.well-known/webfinger?resource=acct:TestWebfinger83294289@localhost"
)
rv = client.get("/.well-known/webfinger?resource=acct:TestWebfinger83294289@localhost")
assert rv.headers["Content-Type"] == "application/jrd+json; charset=utf-8"
assert rv.status_code == 404


+ 7
- 48
utils.py View File

@@ -61,18 +61,8 @@ def add_log(category, level, message):
def add_user_log(item, user, category, level, message):
if not category or not level or not message or not item:
print("!! Fatal error in add_user_log() one of three variables not set")
print(
"[LOG][{0}][{1}][u:{2}i:{3}] {4}".format(
level.upper(), category, user, item, message
)
)
a = UserLogging(
category=category,
level=level.upper(),
message=message,
item_id=item,
user_id=user,
)
print("[LOG][{0}][{1}][u:{2}i:{3}] {4}".format(level.upper(), category, user, item, message))
a = UserLogging(category=category, level=level.upper(), message=message, item_id=item, user_id=user)
db.session.add(a)
db.session.commit()

@@ -132,26 +122,12 @@ def duration_song_human(seconds):
def get_waveform(filename):
binary = current_app.config["AUDIOWAVEFORM_BIN"]
if not os.path.exists(binary) or not os.path.exists(filename):
add_log(
"AUDIOWAVEFORM",
"ERROR",
"Filename {0} or binary {1} invalid".format(filename, binary),
)
add_log("AUDIOWAVEFORM", "ERROR", "Filename {0} or binary {1} invalid".format(filename, binary))
return None

tmpjson = "{0}.json".format(filename)

cmd = [
binary,
"-i",
filename,
"--pixels-per-second",
"10",
"-b",
"8",
"-o",
tmpjson,
]
cmd = [binary, "-i", filename, "--pixels-per-second", "10", "-b", "8", "-o", tmpjson]

"""
Failed: Can't generate "xxx" from "xxx"
@@ -200,26 +176,11 @@ def get_waveform(filename):
def create_png_waveform(fn_audio, fn_png):
binary = current_app.config["AUDIOWAVEFORM_BIN"]
if not os.path.exists(binary) or not os.path.exists(fn_audio):
add_log(
"AUDIOWAVEFORM_PNG",
"ERROR",
"Filename {0} or binary {1} invalid".format(fn_audio, binary),
)
add_log("AUDIOWAVEFORM_PNG", "ERROR", "Filename {0} or binary {1} invalid".format(fn_audio, binary))
return None

pngwf = "{0}.png".format(fn_png)
cmd = [
binary,
"-i",
fn_audio,
"--width",
"384",
"--height",
"64",
"--no-axis-labels",
"-o",
pngwf,
]
cmd = [binary, "-i", fn_audio, "--width", "384", "--height", "64", "--no-axis-labels", "-o", pngwf]

try:
process = subprocess.run(cmd, stdout=subprocess.PIPE, stderr=subprocess.PIPE)
@@ -233,9 +194,7 @@ def create_png_waveform(fn_audio, fn_png):
print("- Command ran with: {0}".format(process.args))

if process.stderr.startswith(b"Can't generate"):
add_log(
"AUDIOWAVEFORM_PNG", "ERROR", "Process error: {0}".format(process.stderr)
)
add_log("AUDIOWAVEFORM_PNG", "ERROR", "Process error: {0}".format(process.stderr))
return None

return True

+ 8
- 27
workers.py View File

@@ -23,9 +23,7 @@ app = create_app()
ctx = app.app_context()

redis_broker = RedisBroker(
host=app.config["BROKER_REDIS_HOST"],
port=app.config["BROKER_REDIS_PORT"],
db=app.config["BROKER_REDIS_DB"],
host=app.config["BROKER_REDIS_HOST"], port=app.config["BROKER_REDIS_PORT"], db=app.config["BROKER_REDIS_DB"]
)
dramatiq.set_broker(redis_broker)

@@ -141,16 +139,10 @@ def work_transcode(sound_id):

print("File: {0}: {1}".format(sound.id, sound.title))
add_user_log(
sound.id,
sound.user.id,
"sounds",
"info",
"Transcoding started for: {0} -- {1}".format(sound.id, sound.title),
sound.id, sound.user.id, "sounds", "info", "Transcoding started for: {0} -- {1}".format(sound.id, sound.title)
)

fname = os.path.join(
app.config["UPLOADED_SOUNDS_DEST"], sound.user.slug, sound.filename
)
fname = os.path.join(app.config["UPLOADED_SOUNDS_DEST"], sound.user.slug, sound.filename)
_file, _ext = splitext(fname)

_start = time.time()
@@ -173,11 +165,7 @@ def work_transcode(sound_id):
db.session.commit()

add_user_log(
sound.id,
sound.user.id,
"sounds",
"info",
"Transcoding finished for: {0} -- {1}".format(sound.id, sound.title),
sound.id, sound.user.id, "sounds", "info", "Transcoding finished for: {0} -- {1}".format(sound.id, sound.title)
)


@@ -203,9 +191,7 @@ def work_metadatas(sound_id, force=False):

# Generate Basic infos