Compare commits

..

11 Commits

Author SHA1 Message Date
rubenwardy
9cc3eba009 Fix email sign up 2020-04-11 17:56:35 +01:00
rubenwardy
54a636d79e Fix access token not being shown after creation
Fixes #190
2020-04-11 17:45:25 +01:00
rubenwardy
0087c1ef9d Allow unlimited API tokens in GitHub webhooks 2020-04-11 15:24:44 +01:00
rubenwardy
39881e0d04 Improve error messages 2020-04-11 14:51:10 +01:00
rubenwardy
39a09c5d92 Add ability to search by tag 2020-04-07 18:23:06 +01:00
rubenwardy
663a9ba07b Fix exposing abs_url_for to templates 2020-03-28 19:01:39 +00:00
rubenwardy
144ae69f5c Fix case-insensitive comparison bug 2020-03-28 18:15:15 +00:00
rubenwardy
3e07bed51b Add ability to search packages by author 2020-03-28 18:13:03 +00:00
rubenwardy
9de219fd80 Increase package name and title length limits in form validation 2020-03-27 15:30:08 +00:00
rubenwardy
4a25435f7a Fix release validation for repos with submodules 2020-03-27 15:23:18 +00:00
rubenwardy
b0f32affcb Fix scores not degrading due to missing session.commit() 2020-03-22 19:47:52 +00:00
13 changed files with 84 additions and 24 deletions

View File

@@ -60,7 +60,6 @@ if not app.debug and app.config["MAIL_UTILS_ERROR_SEND_TO"]:
from .markdown import init_app
init_app(app)
# @babel.localeselector
# def get_locale():
# return request.accept_languages.best_match(app.config['LANGUAGES'].keys())

View File

@@ -69,7 +69,7 @@ def create_edit_token(username, id=None):
elif token.owner != user:
abort(403)
access_token = session.pop("token_" + str(id), None)
access_token = session.pop("token_" + str(token.id), None)
form = CreateAPIToken(formdata=request.form, obj=token)
form.package.query_factory = lambda: Package.query.filter_by(author=user).all()
@@ -80,13 +80,14 @@ def create_edit_token(username, id=None):
token.owner = user
token.access_token = randomString(32)
# Store token so it can be shown in the edit page
session["token_" + str(token.id)] = token.access_token
form.populate_obj(token)
db.session.add(token)
db.session.commit() # save
if is_new:
# Store token so it can be shown in the edit page
session["token_" + str(token.id)] = token.access_token
return redirect(url_for("api.create_edit_token", username=username, id=token.id))
return render_template("api/create_edit_token.html", user=user, form=form, token=token, access_token=access_token)
@@ -102,8 +103,6 @@ def reset_token(username, id):
if not user.checkPerm(current_user, Permission.CREATE_TOKEN):
abort(403)
is_new = id is None
token = APIToken.query.get(id)
if token is None:
abort(404)

View File

@@ -20,7 +20,7 @@ bp = Blueprint("github", __name__)
from flask import redirect, url_for, request, flash, abort, render_template, jsonify, current_app
from flask_user import current_user, login_required
from sqlalchemy import func
from sqlalchemy import func, or_, and_
from flask_github import GitHub
from app import github, csrf
from app.models import db, User, APIToken, Package, Permission
@@ -92,10 +92,13 @@ def webhook():
github_url = "github.com/" + json["repository"]["full_name"]
package = Package.query.filter(Package.repo.like("%{}%".format(github_url))).first()
if package is None:
return error(400, "Unknown package")
return error(400, "Could not find package, did you set the VCS repo in CDB correctly?")
# Get all tokens for package
possible_tokens = APIToken.query.filter_by(package=package).all()
tokens_query = APIToken.query.filter(or_(APIToken.package==package,
and_(APIToken.package==None, APIToken.owner==package.author)))
possible_tokens = tokens_query.all()
actual_token = None
#
@@ -118,7 +121,7 @@ def webhook():
break
if actual_token is None:
return error(403, "Invalid authentication")
return error(403, "Invalid authentication, couldn't validate API token")
if not package.checkPerm(actual_token.owner, Permission.APPROVE_RELEASE):
return error(403, "Only trusted members can use webhooks")

View File

@@ -30,7 +30,7 @@ from flask_wtf import FlaskForm
from wtforms import *
from wtforms.validators import *
from wtforms.ext.sqlalchemy.fields import QuerySelectField, QuerySelectMultipleField
from sqlalchemy import or_
from sqlalchemy import or_, func
@menu.register_menu(bp, ".mods", "Mods", order=11, endpoint_arguments_constructor=lambda: { 'type': 'mod' })
@@ -64,6 +64,14 @@ def list_all():
prev_url = url_for("packages.list_all", type=type_name, q=search, page=query.prev_num) \
if query.has_prev else None
authors = []
if search:
authors = User.query \
.filter(or_(*[func.lower(User.username) == name.lower().strip() for name in search.split(" ")])) \
.all()
authors = [(author.username, search.lower().replace(author.username.lower(), "")) for author in authors]
topics = None
if qb.search and not query.has_next:
qb.show_discarded = True
@@ -73,6 +81,7 @@ def list_all():
return render_template("packages/list.html", \
title=title, packages=query.items, topics=topics, \
query=search, tags=tags, type=type_name, \
authors = authors, \
next_url=next_url, prev_url=prev_url, page=page, page_max=query.pages, packages_count=query.total)
@@ -168,8 +177,8 @@ def download(package):
class PackageForm(FlaskForm):
name = StringField("Name (Technical)", [InputRequired(), Length(1, 20), Regexp("^[a-z0-9_]+$", 0, "Lower case letters (a-z), digits (0-9), and underscores (_) only")])
title = StringField("Title (Human-readable)", [InputRequired(), Length(3, 50)])
name = StringField("Name (Technical)", [InputRequired(), Length(1, 100), Regexp("^[a-z0-9_]+$", 0, "Lower case letters (a-z), digits (0-9), and underscores (_) only")])
title = StringField("Title (Human-readable)", [InputRequired(), Length(3, 100)])
short_desc = StringField("Short Description (Plaintext)", [InputRequired(), Length(1,200)])
desc = TextAreaField("Long Description (Markdown)", [Optional(), Length(0,10000)])
type = SelectField("Type", [InputRequired()], choices=PackageType.choices(), coerce=PackageType.coerce, default=PackageType.MOD)

View File

@@ -214,6 +214,9 @@ class User(db.Model, UserMixin):
.filter(Thread.created_at > hour_ago).count() < 2
def __eq__(self, other):
if other is None:
return False
if not self.is_authenticated or not other.is_authenticated:
return False

View File

@@ -1,4 +1,5 @@
from .models import db, PackageType, Package, ForumTopic, License, MinetestRelease, PackageRelease
from .models import db, PackageType, Package, ForumTopic, License, MinetestRelease, PackageRelease, User, Tag
from .models import tags as Tags
from .utils import isNo, isYes
from sqlalchemy.sql.expression import func
from flask import abort
@@ -19,18 +20,30 @@ class QueryBuilder:
if len(types) > 0:
title = ", ".join([type.value + "s" for type in types])
# Get tags types
tags = args.getlist("tag")
tags = [Tag.query.filter_by(name=tname).first() for tname in tags]
tags = [tag for tag in tags if tag is not None]
# Hid
hide_flags = args.getlist("hide")
self.title = title
self.types = types
self.search = args.get("q")
self.tags = tags
self.random = "random" in args
self.lucky = "lucky" in args
self.hide_nonfree = "nonfree" in hide_flags
self.limit = 1 if self.lucky else None
self.order_by = args.get("sort")
self.order_dir = args.get("order") or "desc"
# Filters
self.search = args.get("q")
self.protocol_version = args.get("protocol_version")
self.author = args.get("author")
self.show_discarded = isYes(args.get("show_discarded"))
self.show_added = args.get("show_added")
@@ -84,6 +97,16 @@ class QueryBuilder:
query = query.order_by(to_order)
if self.author:
author = User.query.filter_by(username=self.author).first()
if not author:
abort(404)
query = query.filter_by(author=author)
for tag in self.tags:
query = query.filter(Package.tags.any(Tag.id == tag.id))
if self.hide_nonfree:
query = query.filter(Package.license.has(License.is_foss == True))
query = query.filter(Package.media_license.has(License.is_foss == True))

View File

@@ -162,6 +162,9 @@ def cloneRepo(urlstr, ref=None, recursive=False):
origin.fetch()
origin.pull(ref)
for submodule in repo.submodules:
submodule.update(init=True)
return gitDir, repo
except GitCommandError as e:

View File

@@ -15,9 +15,10 @@
# along with this program. If not, see <https://www.gnu.org/licenses/>.
from app.models import Package
from app.models import Package, db
from app.tasks import celery
@celery.task()
def updatePackageScores():
Package.query.update({ "score": Package.score * 0.8 })
Package.query.update({ "score": Package.score * 0.95 })
db.session.commit()

View File

@@ -1,10 +1,16 @@
from . import app
from .utils import abs_url_for
from urllib.parse import urlparse
@app.context_processor
def inject_debug():
return dict(debug=app.debug)
@app.context_processor
def inject_functions():
return dict(abs_url_for=abs_url_for)
@app.template_filter()
def throw(err):
raise Exception(err)

View File

@@ -129,7 +129,7 @@
{% endblock %}
<footer class="container footer-copyright my-5 page-footer font-small text-center">
ContentDB &copy; 2018-9 to <a href="https://rubenwardy.com/">rubenwardy</a> |
ContentDB &copy; 2018-20 to <a href="https://rubenwardy.com/">rubenwardy</a> |
<a href="https://github.com/minetest/contentdb">GitHub</a> |
<a href="{{ url_for('flatpage', path='help') }}">{{ _("Help") }}</a> |
<a href="{{ url_for('flatpage', path='policy_and_guidance') }}">{{ _("Policy and Guidance") }}</a> |

View File

@@ -5,6 +5,21 @@
{% endblock %}
{% block content %}
{% if authors %}
<p class="alert alert-primary">
Did you mean to search for packages by
{% for author in authors %}
<a href="{{ url_for('packages.list_all', type=type, author=author[0], q=author[1]) }}">{{ author[0] }}</a>
{% if not loop.last %}
,
{% endif %}
{% endfor %}
?
</p>
{% endif %}
{% from "macros/packagegridtile.html" import render_pkggrid %}
{{ render_pkggrid(packages) }}

View File

@@ -38,7 +38,8 @@
</span>
{% endif %}
{% for t in package.tags %}
<span class="badge badge-primary">{{ t.title }}</span>
<a class="badge badge-primary"
href="{{ url_for('packages.list_all', tag=t.name) }}">{{ t.title }}</a>
{% endfor %}
</p>

View File

@@ -18,12 +18,10 @@
from flask import request, flash, abort, redirect
from flask_user import *
from flask_login import login_user, logout_user
from app.models import *
from app import app
from .models import *
from . import app
import random, string, os, imghdr
@app.template_filter()
def abs_url_for(path, **kwargs):
scheme = "https" if app.config["BASE_URL"][:5] == "https" else "http"
return url_for(path, _external=True, _scheme=scheme, **kwargs)