Example #1
0
def set_lock(id):
    thread = Thread.query.get(id)
    if thread is None or not thread.checkPerm(current_user,
                                              Permission.LOCK_THREAD):
        abort(404)

    thread.locked = isYes(request.args.get("lock"))
    if thread.locked is None:
        abort(400)

    msg = None
    if thread.locked:
        msg = "Locked thread '{}'".format(thread.title)
        flash("Locked thread", "success")
    else:
        msg = "Unlocked thread '{}'".format(thread.title)
        flash("Unlocked thread", "success")

    addNotification(thread.watchers, current_user, NotificationType.OTHER, msg,
                    thread.getViewURL(), thread.package)
    addAuditLog(AuditSeverity.MODERATION, current_user, NotificationType.OTHER,
                msg, thread.getViewURL(), thread.package)

    db.session.commit()

    return redirect(thread.getViewURL())
Example #2
0
def delete_review(package):
    review = PackageReview.query.filter_by(package=package,
                                           author=current_user).first()
    if review is None or review.package != package:
        abort(404)

    thread = review.thread

    reply = ThreadReply()
    reply.thread = thread
    reply.author = current_user
    reply.comment = "_converted review into a thread_"
    db.session.add(reply)

    thread.review = None

    notif_msg = "Deleted review '{}', comments were kept as a thread".format(
        thread.title)
    addNotification(package.maintainers, current_user, notif_msg,
                    url_for("threads.view", id=thread.id), package)

    db.session.delete(review)
    db.session.commit()

    return redirect(thread.getViewURL())
Example #3
0
def do_create_screenshot(user: User, package: Package, title: str, file, reason: str = None):
	thirty_minutes_ago = datetime.datetime.now() - datetime.timedelta(minutes=30)
	count = package.screenshots.filter(PackageScreenshot.created_at > thirty_minutes_ago).count()
	if count >= 20:
		raise LogicError(429, "Too many requests, please wait before trying again")

	uploaded_url, uploaded_path = upload_file(file, "image", "a PNG or JPG image file")

	counter = 1
	for screenshot in package.screenshots.all():
		screenshot.order = counter
		counter += 1

	ss = PackageScreenshot()
	ss.package  = package
	ss.title    = title or "Untitled"
	ss.url      = uploaded_url
	ss.approved = package.checkPerm(user, Permission.APPROVE_SCREENSHOT)
	ss.order    = counter
	db.session.add(ss)

	if reason is None:
		msg = "Created screenshot {}".format(ss.title)
	else:
		msg = "Created screenshot {} ({})".format(ss.title, reason)

	addNotification(package.maintainers, user, NotificationType.PACKAGE_EDIT, msg, package.getURL("packages.view"), package)
	addAuditLog(AuditSeverity.NORMAL, user, msg, package.getURL("packages.view"), package)

	db.session.commit()

	return ss
Example #4
0
def send_bulk_notification():
    form = SendNotificationForm(request.form)
    if form.validate_on_submit():
        addAuditLog(AuditSeverity.MODERATION, current_user,
                    "Sent bulk notification", None, None, form.title.data)

        users = User.query.filter(User.rank >= UserRank.NEW_MEMBER).all()
        addNotification(users, current_user, NotificationType.OTHER,
                        form.title.data, form.url.data, None)
        db.session.commit()

        return redirect(url_for("admin.admin_page"))

    return render_template("admin/send_bulk_notification.html", form=form)
Example #5
0
def apply_all_updates(username):
    user: User = User.query.filter_by(username=username).first()
    if not user:
        abort(404)

    if current_user != user and not current_user.rank.atLeast(UserRank.EDITOR):
        abort(403)

    outdated_packages = user.maintained_packages \
     .filter(Package.state != PackageState.DELETED,
      Package.update_config.has(PackageUpdateConfig.outdated_at.isnot(None))) \
     .order_by(db.asc(Package.title)).all()

    for package in outdated_packages:
        if not package.checkPerm(current_user, Permission.MAKE_RELEASE):
            continue

        if package.releases.filter(
                or_(
                    PackageRelease.task_id.isnot(None),
                    PackageRelease.commit_hash
                    == package.update_config.last_commit)).count() > 0:
            continue

        title = package.update_config.get_title()
        ref = package.update_config.get_ref()

        rel = PackageRelease()
        rel.package = package
        rel.title = title
        rel.url = ""
        rel.task_id = uuid()
        db.session.add(rel)
        db.session.commit()

        makeVCSRelease.apply_async((rel.id, ref), task_id=rel.task_id)

        msg = "Created release {} (Applied all Git Update Detection)".format(
            rel.title)
        addNotification(package.maintainers,
                        current_user, NotificationType.PACKAGE_EDIT, msg,
                        rel.getEditURL(), package)
        addAuditLog(AuditSeverity.NORMAL, current_user, msg,
                    package.getDetailsURL(), package)
        db.session.commit()

    return redirect(url_for("todo.view_user", username=username))
Example #6
0
def view(id):
    thread: Thread = Thread.query.get(id)
    if thread is None or not thread.checkPerm(current_user,
                                              Permission.SEE_THREAD):
        abort(404)

    if current_user.is_authenticated and request.method == "POST":
        comment = request.form["comment"]

        if not thread.checkPerm(current_user, Permission.COMMENT_THREAD):
            flash("You cannot comment on this thread", "danger")
            return redirect(thread.getViewURL())

        if not current_user.canCommentRL():
            flash("Please wait before commenting again", "danger")
            return redirect(thread.getViewURL())

        if 2000 >= len(comment) > 3:
            reply = ThreadReply()
            reply.author = current_user
            reply.comment = comment
            db.session.add(reply)

            thread.replies.append(reply)
            if not current_user in thread.watchers:
                thread.watchers.append(current_user)

            msg = "New comment on '{}'".format(thread.title)
            addNotification(thread.watchers, current_user,
                            NotificationType.THREAD_REPLY, msg,
                            thread.getViewURL(), thread.package)

            if thread.author == get_system_user():
                editors = User.query.filter(User.rank >= UserRank.EDITOR).all()
                addNotification(editors, current_user,
                                NotificationType.EDITOR_MISC, msg,
                                thread.getViewURL(), thread.package)

            db.session.commit()

            return redirect(thread.getViewURL())

        else:
            flash("Comment needs to be between 3 and 2000 characters.")

    return render_template("threads/view.html", thread=thread)
Example #7
0
def edit_reply(id):
    thread = Thread.query.get(id)
    if thread is None:
        abort(404)

    reply_id = request.args.get("reply")
    if reply_id is None:
        abort(404)

    reply = ThreadReply.query.get(reply_id)
    if reply is None or reply.thread != thread:
        abort(404)

    if not reply.checkPerm(current_user, Permission.EDIT_REPLY):
        abort(403)

    form = CommentForm(formdata=request.form, obj=reply)
    if form.validate_on_submit():
        comment = form.comment.data

        msg = "Edited reply by {}".format(reply.author.display_name)
        severity = AuditSeverity.NORMAL if current_user == reply.author else AuditSeverity.MODERATION
        addNotification(reply.author, current_user, NotificationType.OTHER,
                        msg, thread.getViewURL(), thread.package)
        addAuditLog(severity, current_user, msg, thread.getViewURL(),
                    thread.package, reply.comment)

        reply.comment = comment

        db.session.commit()

        return redirect(thread.getViewURL())

    return render_template("threads/edit_reply.html",
                           thread=thread,
                           reply=reply,
                           form=form)
Example #8
0
def review(package):
    if current_user in package.maintainers:
        flash("You can't review your own package!", "danger")
        return redirect(package.getDetailsURL())

    review = PackageReview.query.filter_by(package=package,
                                           author=current_user).first()

    form = ReviewForm(formdata=request.form, obj=review)

    # Set default values
    if request.method == "GET" and review:
        form.title.data = review.thread.title
        form.recommends.data = "yes" if review.recommends else "no"
        form.comment.data = review.thread.replies[0].comment

    # Validate and submit
    elif request.method == "POST" and form.validate():
        was_new = False
        if not review:
            was_new = True
            review = PackageReview()
            review.package = package
            review.author = current_user
            db.session.add(review)

        review.recommends = form.recommends.data == "yes"

        thread = review.thread
        if not thread:
            thread = Thread()
            thread.author = current_user
            thread.private = False
            thread.package = package
            thread.review = review
            db.session.add(thread)

            thread.watchers.append(current_user)

            reply = ThreadReply()
            reply.thread = thread
            reply.author = current_user
            reply.comment = form.comment.data
            db.session.add(reply)

            thread.replies.append(reply)
        else:
            reply = thread.replies[0]
            reply.comment = form.comment.data

        thread.title = form.title.data

        db.session.commit()

        package.recalcScore()

        notif_msg = None
        if was_new:
            notif_msg = "New review '{}'".format(form.title.data)
        else:
            notif_msg = "Updated review '{}'".format(form.title.data)

        addNotification(package.maintainers, current_user, notif_msg,
                        url_for("threads.view", id=thread.id), package)

        db.session.commit()

        return redirect(package.getDetailsURL())

    return render_template("packages/review_create_edit.html", \
      form=form, package=package, review=review)
Example #9
0
def new():
    form = ThreadForm(formdata=request.form)

    package = None
    if "pid" in request.args:
        package = Package.query.get(int(request.args.get("pid")))
        if package is None:
            flash("Unable to find that package!", "danger")

    # Don't allow making orphan threads on approved packages for now
    if package is None:
        abort(403)

    def_is_private = request.args.get("private") or False
    if package is None:
        def_is_private = True
    allow_change = package and package.approved
    is_review_thread = package and not package.approved

    # Check that user can make the thread
    if not package.checkPerm(current_user, Permission.CREATE_THREAD):
        flash("Unable to create thread!", "danger")
        return redirect(url_for("homepage.home"))

    # Only allow creating one thread when not approved
    elif is_review_thread and package.review_thread is not None:
        flash("A review thread already exists!", "danger")
        return redirect(package.review_thread.getViewURL())

    elif not current_user.canOpenThreadRL():
        flash("Please wait before opening another thread", "danger")

        if package:
            return redirect(package.getDetailsURL())
        else:
            return redirect(url_for("homepage.home"))

    # Set default values
    elif request.method == "GET":
        form.private.data = def_is_private
        form.title.data = request.args.get("title") or ""

    # Validate and submit
    elif form.validate_on_submit():
        thread = Thread()
        thread.author = current_user
        thread.title = form.title.data
        thread.private = form.private.data if allow_change else def_is_private
        thread.package = package
        db.session.add(thread)

        thread.watchers.append(current_user)
        if package is not None and package.author != current_user:
            thread.watchers.append(package.author)

        reply = ThreadReply()
        reply.thread = thread
        reply.author = current_user
        reply.comment = form.comment.data
        db.session.add(reply)

        thread.replies.append(reply)

        db.session.commit()

        if is_review_thread:
            package.review_thread = thread

            if package.state == PackageState.READY_FOR_REVIEW and current_user not in package.maintainers:
                package.state = PackageState.CHANGES_NEEDED

        notif_msg = "New thread '{}'".format(thread.title)
        if package is not None:
            addNotification(package.maintainers, current_user,
                            NotificationType.NEW_THREAD, notif_msg,
                            thread.getViewURL(), package)

        editors = User.query.filter(User.rank >= UserRank.EDITOR).all()
        addNotification(editors, current_user, NotificationType.EDITOR_MISC,
                        notif_msg, thread.getViewURL(), package)

        db.session.commit()

        return redirect(thread.getViewURL())

    return render_template("threads/new.html",
                           form=form,
                           allow_private_change=allow_change,
                           package=package)
Example #10
0
def admin_page():
    if request.method == "POST":
        action = request.form["action"]

        if action == "delstuckreleases":
            PackageRelease.query.filter(
                PackageRelease.task_id != None).delete()
            db.session.commit()
            return redirect(url_for("admin.admin_page"))

        elif action == "checkreleases":
            releases = PackageRelease.query.filter(
                PackageRelease.url.like("/uploads/%")).all()

            tasks = []
            for release in releases:
                zippath = release.url.replace("/uploads/",
                                              app.config["UPLOAD_DIR"])
                tasks.append(checkZipRelease.s(release.id, zippath))

            result = group(tasks).apply_async()

            while not result.ready():
                import time
                time.sleep(0.1)

            return redirect(url_for("todo.view_editor"))

        elif action == "reimportpackages":
            tasks = []
            for package in Package.query.filter(
                    Package.state != PackageState.DELETED).all():
                release = package.releases.first()
                if release:
                    zippath = release.url.replace("/uploads/",
                                                  app.config["UPLOAD_DIR"])
                    tasks.append(checkZipRelease.s(release.id, zippath))

            result = group(tasks).apply_async()

            while not result.ready():
                import time
                time.sleep(0.1)

            return redirect(url_for("todo.view_editor"))

        elif action == "importmodlist":
            task = importTopicList.delay()
            return redirect(
                url_for("tasks.check", id=task.id, r=url_for("todo.topics")))

        elif action == "checkusers":
            task = checkAllForumAccounts.delay()
            return redirect(
                url_for("tasks.check",
                        id=task.id,
                        r=url_for("admin.admin_page")))

        elif action == "importscreenshots":
            packages = Package.query \
             .filter(Package.state!=PackageState.DELETED) \
             .outerjoin(PackageScreenshot, Package.id==PackageScreenshot.package_id) \
             .filter(PackageScreenshot.id==None) \
             .all()
            for package in packages:
                importRepoScreenshot.delay(package.id)

            return redirect(url_for("admin.admin_page"))

        elif action == "restore":
            package = Package.query.get(request.form["package"])
            if package is None:
                flash("Unknown package", "danger")
            else:
                package.state = PackageState.READY_FOR_REVIEW
                db.session.commit()
                return redirect(url_for("admin.admin_page"))

        elif action == "recalcscores":
            for p in Package.query.all():
                p.recalcScore()

            db.session.commit()
            return redirect(url_for("admin.admin_page"))

        elif action == "cleanuploads":
            upload_dir = app.config['UPLOAD_DIR']

            (_, _, filenames) = next(os.walk(upload_dir))
            existing_uploads = set(filenames)

            if len(existing_uploads) != 0:

                def getURLsFromDB(column):
                    results = db.session.query(column).filter(
                        column != None, column != "").all()
                    return set([os.path.basename(x[0]) for x in results])

                release_urls = getURLsFromDB(PackageRelease.url)
                screenshot_urls = getURLsFromDB(PackageScreenshot.url)

                db_urls = release_urls.union(screenshot_urls)
                unreachable = existing_uploads.difference(db_urls)

                import sys
                print("On Disk: ", existing_uploads, file=sys.stderr)
                print("In DB: ", db_urls, file=sys.stderr)
                print("Unreachable: ", unreachable, file=sys.stderr)

                for filename in unreachable:
                    os.remove(os.path.join(upload_dir, filename))

                flash(
                    "Deleted " + str(len(unreachable)) +
                    " unreachable uploads", "success")
            else:
                flash("No downloads to create", "danger")

            return redirect(url_for("admin.admin_page"))

        elif action == "delmetapackages":
            query = MetaPackage.query.filter(~MetaPackage.dependencies.any(),
                                             ~MetaPackage.packages.any())
            count = query.count()
            query.delete(synchronize_session=False)
            db.session.commit()

            flash("Deleted " + str(count) + " unused meta packages", "success")
            return redirect(url_for("admin.admin_page"))

        elif action == "delremovedpackages":
            query = Package.query.filter_by(state=PackageState.DELETED)
            count = query.count()
            for pkg in query.all():
                pkg.review_thread = None
                db.session.delete(pkg)
            db.session.commit()

            flash("Deleted {} soft deleted packages packages".format(count),
                  "success")
            return redirect(url_for("admin.admin_page"))

        elif action == "addupdateconfig":
            added = 0
            for pkg in Package.query.filter(
                    Package.repo != None, Package.releases.any(),
                    Package.update_config == None).all():
                pkg.update_config = PackageUpdateConfig()
                pkg.update_config.auto_created = True

                release: PackageRelease = pkg.releases.first()
                if release and release.commit_hash:
                    pkg.update_config.last_commit = release.commit_hash

                db.session.add(pkg.update_config)
                added += 1

            db.session.commit()

            flash("Added {} update configs".format(added), "success")
            return redirect(url_for("admin.admin_page"))

        elif action == "runupdateconfig":
            check_for_updates.delay()

            flash("Started update configs", "success")
            return redirect(url_for("admin.admin_page"))

        elif action == "remindwip":
            users = User.query.filter(
                User.packages.any(
                    or_(Package.state == PackageState.WIP,
                        Package.state == PackageState.CHANGES_NEEDED)))
            system_user = get_system_user()
            for user in users:
                packages = db.session.query(Package.title).filter(
                  Package.author_id==user.id,
                  or_(Package.state==PackageState.WIP, Package.state==PackageState.CHANGES_NEEDED)) \
                 .all()

                # Who needs translations?
                packages = [pkg[0] for pkg in packages]
                if len(packages) >= 3:
                    packages[len(packages) -
                             1] = "and " + packages[len(packages) - 1]
                    packages_list = ", ".join(packages)
                else:
                    packages_list = "and ".join(packages)

                havent = "haven't" if len(packages) > 1 else "hasn't"
                if len(packages_list) + 54 > 100:
                    packages_list = packages_list[0:(100 - 54 - 1)] + "…"

                addNotification(
                    user, system_user, NotificationType.PACKAGE_APPROVAL,
                    f"Did you forget? {packages_list} {havent} been submitted for review yet",
                    url_for('todo.view_user', username=user.username))
            db.session.commit()

        else:
            flash("Unknown action: " + action, "danger")

    deleted_packages = Package.query.filter(
        Package.state == PackageState.DELETED).all()
    return render_template("admin/list.html",
                           deleted_packages=deleted_packages)