Compare commits
10 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
9c2c8c21f1 | ||
|
|
e40b247a97 | ||
|
|
a79cc758ed | ||
|
|
bafd426eaf | ||
|
|
36f9572cbb | ||
|
|
2586a11bcf | ||
|
|
d36138d5e1 | ||
|
|
7810bb54e0 | ||
|
|
2844773e4d | ||
|
|
23c406bff9 |
@@ -2,4 +2,5 @@ title: Help
|
|||||||
|
|
||||||
* [Package Tags](package_tags)
|
* [Package Tags](package_tags)
|
||||||
* [Ranks and Permissions](ranks_permissions)
|
* [Ranks and Permissions](ranks_permissions)
|
||||||
|
* [Content Ratings and Flags](content_flags)
|
||||||
* [Reporting Content](reporting)
|
* [Reporting Content](reporting)
|
||||||
|
|||||||
26
app/flatpages/help/content_flags.md
Normal file
26
app/flatpages/help/content_flags.md
Normal file
@@ -0,0 +1,26 @@
|
|||||||
|
title: Content Flags
|
||||||
|
|
||||||
|
Content flags allow you to hide content based on your preferences.
|
||||||
|
The filtering is done server-side, which means that you don't need to update
|
||||||
|
your client to use new flags.
|
||||||
|
|
||||||
|
## Flags
|
||||||
|
|
||||||
|
* `nonfree` - can be used to hide packages which do not qualify as
|
||||||
|
'free software', as defined by the Free Software Foundation.
|
||||||
|
* A content rating, given below.
|
||||||
|
|
||||||
|
|
||||||
|
## Ratings
|
||||||
|
|
||||||
|
Content ratings aren't currently supported by ContentDB.
|
||||||
|
Instead, mature content isn't allowed at all for now.
|
||||||
|
|
||||||
|
In the future, more mature content will be allowed but labelled with
|
||||||
|
content ratings which may contain the following:
|
||||||
|
|
||||||
|
* android_default - meta-rating which includes gore and drugs.
|
||||||
|
* desktop_default - meta-rating which won't include anything for now.
|
||||||
|
* gore - more than just blood
|
||||||
|
* drugs
|
||||||
|
* swearing
|
||||||
@@ -15,18 +15,29 @@
|
|||||||
# along with this program. If not, see <https://www.gnu.org/licenses/>.
|
# along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||||
|
|
||||||
|
|
||||||
from flask import Flask, url_for
|
|
||||||
from flask_sqlalchemy import SQLAlchemy
|
|
||||||
from flask_migrate import Migrate
|
|
||||||
from urllib.parse import urlparse
|
|
||||||
from app import app, gravatar
|
|
||||||
from sqlalchemy.orm import validates
|
|
||||||
from flask_user import login_required, UserManager, UserMixin, SQLAlchemyAdapter
|
|
||||||
import enum, datetime
|
import enum, datetime
|
||||||
|
|
||||||
|
from app import app, gravatar
|
||||||
|
from urllib.parse import urlparse
|
||||||
|
|
||||||
|
from flask import Flask, url_for
|
||||||
|
from flask_sqlalchemy import SQLAlchemy, BaseQuery
|
||||||
|
from flask_migrate import Migrate
|
||||||
|
from flask_user import login_required, UserManager, UserMixin, SQLAlchemyAdapter
|
||||||
|
from sqlalchemy.orm import validates
|
||||||
|
from sqlalchemy_searchable import SearchQueryMixin
|
||||||
|
from sqlalchemy_utils.types import TSVectorType
|
||||||
|
from sqlalchemy_searchable import make_searchable
|
||||||
|
|
||||||
|
|
||||||
# Initialise database
|
# Initialise database
|
||||||
db = SQLAlchemy(app)
|
db = SQLAlchemy(app)
|
||||||
migrate = Migrate(app, db)
|
migrate = Migrate(app, db)
|
||||||
|
make_searchable(db.metadata)
|
||||||
|
|
||||||
|
|
||||||
|
class ArticleQuery(BaseQuery, SearchQueryMixin):
|
||||||
|
pass
|
||||||
|
|
||||||
|
|
||||||
class UserRank(enum.Enum):
|
class UserRank(enum.Enum):
|
||||||
@@ -246,7 +257,7 @@ class PackageType(enum.Enum):
|
|||||||
class PackagePropertyKey(enum.Enum):
|
class PackagePropertyKey(enum.Enum):
|
||||||
name = "Name"
|
name = "Name"
|
||||||
title = "Title"
|
title = "Title"
|
||||||
shortDesc = "Short Description"
|
short_desc = "Short Description"
|
||||||
desc = "Description"
|
desc = "Description"
|
||||||
type = "Type"
|
type = "Type"
|
||||||
license = "License"
|
license = "License"
|
||||||
@@ -343,19 +354,22 @@ class Dependency(db.Model):
|
|||||||
return retval
|
return retval
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
class Package(db.Model):
|
class Package(db.Model):
|
||||||
|
query_class = ArticleQuery
|
||||||
|
|
||||||
id = db.Column(db.Integer, primary_key=True)
|
id = db.Column(db.Integer, primary_key=True)
|
||||||
|
|
||||||
# Basic details
|
# Basic details
|
||||||
author_id = db.Column(db.Integer, db.ForeignKey("user.id"))
|
author_id = db.Column(db.Integer, db.ForeignKey("user.id"))
|
||||||
name = db.Column(db.String(100), nullable=False)
|
name = db.Column(db.String(100), nullable=False)
|
||||||
title = db.Column(db.String(100), nullable=False)
|
title = db.Column(db.Unicode(100), nullable=False)
|
||||||
shortDesc = db.Column(db.String(200), nullable=False)
|
short_desc = db.Column(db.Unicode(200), nullable=False)
|
||||||
desc = db.Column(db.Text, nullable=True)
|
desc = db.Column(db.UnicodeText, nullable=True)
|
||||||
type = db.Column(db.Enum(PackageType))
|
type = db.Column(db.Enum(PackageType))
|
||||||
created_at = db.Column(db.DateTime, nullable=False, default=datetime.datetime.utcnow)
|
created_at = db.Column(db.DateTime, nullable=False, default=datetime.datetime.utcnow)
|
||||||
|
|
||||||
|
search_vector = db.Column(TSVectorType("title", "short_desc", "desc"))
|
||||||
|
|
||||||
license_id = db.Column(db.Integer, db.ForeignKey("license.id"), nullable=False, default=1)
|
license_id = db.Column(db.Integer, db.ForeignKey("license.id"), nullable=False, default=1)
|
||||||
license = db.relationship("License", foreign_keys=[license_id])
|
license = db.relationship("License", foreign_keys=[license_id])
|
||||||
media_license_id = db.Column(db.Integer, db.ForeignKey("license.id"), nullable=False, default=1)
|
media_license_id = db.Column(db.Integer, db.ForeignKey("license.id"), nullable=False, default=1)
|
||||||
@@ -403,26 +417,26 @@ class Package(db.Model):
|
|||||||
for e in PackagePropertyKey:
|
for e in PackagePropertyKey:
|
||||||
setattr(self, e.name, getattr(package, e.name))
|
setattr(self, e.name, getattr(package, e.name))
|
||||||
|
|
||||||
def getAsDictionaryShort(self, base_url):
|
def getAsDictionaryShort(self, base_url, protonum=None):
|
||||||
tnurl = self.getThumbnailURL(1)
|
tnurl = self.getThumbnailURL(1)
|
||||||
return {
|
return {
|
||||||
"name": self.name,
|
"name": self.name,
|
||||||
"title": self.title,
|
"title": self.title,
|
||||||
"author": self.author.display_name,
|
"author": self.author.display_name,
|
||||||
"short_description": self.shortDesc,
|
"short_description": self.short_desc,
|
||||||
"type": self.type.toName(),
|
"type": self.type.toName(),
|
||||||
"release": self.getDownloadRelease().id if self.getDownloadRelease() is not None else None,
|
"release": self.getDownloadRelease(protonum).id if self.getDownloadRelease(protonum) is not None else None,
|
||||||
"thumbnail": (base_url + tnurl) if tnurl is not None else None,
|
"thumbnail": (base_url + tnurl) if tnurl is not None else None,
|
||||||
"score": round(self.score * 10) / 10
|
"score": round(self.score * 10) / 10
|
||||||
}
|
}
|
||||||
|
|
||||||
def getAsDictionary(self, base_url):
|
def getAsDictionary(self, base_url, protonum=None):
|
||||||
tnurl = self.getThumbnailURL(1)
|
tnurl = self.getThumbnailURL(1)
|
||||||
return {
|
return {
|
||||||
"author": self.author.display_name,
|
"author": self.author.display_name,
|
||||||
"name": self.name,
|
"name": self.name,
|
||||||
"title": self.title,
|
"title": self.title,
|
||||||
"short_description": self.shortDesc,
|
"short_description": self.short_desc,
|
||||||
"desc": self.desc,
|
"desc": self.desc,
|
||||||
"type": self.type.toName(),
|
"type": self.type.toName(),
|
||||||
"created_at": self.created_at,
|
"created_at": self.created_at,
|
||||||
@@ -440,7 +454,7 @@ class Package(db.Model):
|
|||||||
"screenshots": [base_url + ss.url for ss in self.screenshots],
|
"screenshots": [base_url + ss.url for ss in self.screenshots],
|
||||||
|
|
||||||
"url": base_url + self.getDownloadURL(),
|
"url": base_url + self.getDownloadURL(),
|
||||||
"release": self.getDownloadRelease().id if self.getDownloadRelease() is not None else None,
|
"release": self.getDownloadRelease(protonum).id if self.getDownloadRelease(protonum) is not None else None,
|
||||||
|
|
||||||
"score": round(self.score * 10) / 10
|
"score": round(self.score * 10) / 10
|
||||||
}
|
}
|
||||||
@@ -489,13 +503,30 @@ class Package(db.Model):
|
|||||||
return url_for("package_download_page",
|
return url_for("package_download_page",
|
||||||
author=self.author.username, name=self.name)
|
author=self.author.username, name=self.name)
|
||||||
|
|
||||||
def getDownloadRelease(self):
|
def getDownloadRelease(self, protonum=None):
|
||||||
|
version = None
|
||||||
|
if protonum is not None:
|
||||||
|
version = MinetestRelease.query.filter(MinetestRelease.protocol >= int(protonum)).first()
|
||||||
|
if version is not None:
|
||||||
|
version = version.id
|
||||||
|
else:
|
||||||
|
version = 10000000
|
||||||
|
|
||||||
|
|
||||||
for rel in self.releases:
|
for rel in self.releases:
|
||||||
if rel.approved:
|
if rel.approved and (protonum is None or
|
||||||
|
((rel.min_rel is None or rel.min_rel_id <= version) and \
|
||||||
|
(rel.max_rel is None or rel.max_rel_id >= version))):
|
||||||
return rel
|
return rel
|
||||||
|
|
||||||
return None
|
return None
|
||||||
|
|
||||||
|
def getDownloadCount(self):
|
||||||
|
counter = 0
|
||||||
|
for release in self.releases:
|
||||||
|
counter += release.downloads
|
||||||
|
return counter
|
||||||
|
|
||||||
def checkPerm(self, user, perm):
|
def checkPerm(self, user, perm):
|
||||||
if not user.is_authenticated:
|
if not user.is_authenticated:
|
||||||
return False
|
return False
|
||||||
@@ -639,6 +670,7 @@ class PackageRelease(db.Model):
|
|||||||
approved = db.Column(db.Boolean, nullable=False, default=False)
|
approved = db.Column(db.Boolean, nullable=False, default=False)
|
||||||
task_id = db.Column(db.String(37), nullable=True)
|
task_id = db.Column(db.String(37), nullable=True)
|
||||||
commit_hash = db.Column(db.String(41), nullable=True, default=None)
|
commit_hash = db.Column(db.String(41), nullable=True, default=None)
|
||||||
|
downloads = db.Column(db.Integer, nullable=False, default=0)
|
||||||
|
|
||||||
min_rel_id = db.Column(db.Integer, db.ForeignKey("minetest_release.id"), nullable=True, server_default=None)
|
min_rel_id = db.Column(db.Integer, db.ForeignKey("minetest_release.id"), nullable=True, server_default=None)
|
||||||
min_rel = db.relationship("MinetestRelease", foreign_keys=[min_rel_id])
|
min_rel = db.relationship("MinetestRelease", foreign_keys=[min_rel_id])
|
||||||
|
|||||||
9
app/public/static/opensearch.xml
Normal file
9
app/public/static/opensearch.xml
Normal file
@@ -0,0 +1,9 @@
|
|||||||
|
<?xml version="1.0" encoding="UTF-8"?>
|
||||||
|
<OpenSearchDescription xmlns="http://a9.com/-/spec/opensearch/1.1/">
|
||||||
|
<ShortName>ContentDB</ShortName>
|
||||||
|
<LongName>ContentDB</LongName>
|
||||||
|
<InputEncoding>UTF-8</InputEncoding>
|
||||||
|
<Description>Search mods, games, and textures for Minetest.</Description>
|
||||||
|
<Tags>Minetest Mod Game Subgame Search</Tags>
|
||||||
|
<Url type="text/html" method="get" template="https://content.minetest.net/packages?q={searchTerms}"/>
|
||||||
|
</OpenSearchDescription>
|
||||||
@@ -35,10 +35,10 @@ $(function() {
|
|||||||
setField("#repo", result.repo || repoURL);
|
setField("#repo", result.repo || repoURL);
|
||||||
setField("#issueTracker", result.issueTracker);
|
setField("#issueTracker", result.issueTracker);
|
||||||
setField("#desc", result.description);
|
setField("#desc", result.description);
|
||||||
setField("#shortDesc", result.short_description);
|
setField("#short_desc", result.short_description);
|
||||||
setField("#harddep_str", result.depends);
|
setField("#harddep_str", result.depends);
|
||||||
setField("#softdep_str", result.optional_depends);
|
setField("#softdep_str", result.optional_depends);
|
||||||
setField("#shortDesc", result.short_description);
|
setField("#short_desc", result.short_description);
|
||||||
setField("#forums", result.forumId);
|
setField("#forums", result.forumId);
|
||||||
if (result.type && result.type.length > 2) {
|
if (result.type && result.type.length > 2) {
|
||||||
$("#type").val(result.type);
|
$("#type").val(result.type);
|
||||||
|
|||||||
@@ -41,7 +41,7 @@ $(function() {
|
|||||||
It's obvious that this adds something to Minetest,
|
It's obvious that this adds something to Minetest,
|
||||||
there's no need to use phrases such as \"adds X to the game\".`
|
there's no need to use phrases such as \"adds X to the game\".`
|
||||||
|
|
||||||
$("#shortDesc").on("change paste keyup", function() {
|
$("#short_desc").on("change paste keyup", function() {
|
||||||
var val = $(this).val().toLowerCase();
|
var val = $(this).val().toLowerCase();
|
||||||
if (val.indexOf("minetest") >= 0 || val.indexOf("mod") >= 0 ||
|
if (val.indexOf("minetest") >= 0 || val.indexOf("mod") >= 0 ||
|
||||||
val.indexOf("modpack") >= 0 || val.indexOf("mod pack") >= 0) {
|
val.indexOf("modpack") >= 0 || val.indexOf("mod pack") >= 0) {
|
||||||
|
|||||||
@@ -19,12 +19,14 @@ class QueryBuilder:
|
|||||||
if len(types) > 0:
|
if len(types) > 0:
|
||||||
title = ", ".join([type.value + "s" for type in types])
|
title = ", ".join([type.value + "s" for type in types])
|
||||||
|
|
||||||
|
hide_flags = args.getlist("hide")
|
||||||
|
|
||||||
self.title = title
|
self.title = title
|
||||||
self.types = types
|
self.types = types
|
||||||
self.search = args.get("q")
|
self.search = args.get("q")
|
||||||
self.random = "random" in args
|
self.random = "random" in args
|
||||||
self.lucky = self.random or "lucky" in args
|
self.lucky = self.random or "lucky" in args
|
||||||
self.hide_nonfree = isNo(args.get("nonfree"))
|
self.hide_nonfree = "nonfree" in hide_flags
|
||||||
self.limit = 1 if self.lucky else None
|
self.limit = 1 if self.lucky else None
|
||||||
self.order_by = args.get("sort") or "score"
|
self.order_by = args.get("sort") or "score"
|
||||||
self.order_dir = args.get("order") or "desc"
|
self.order_dir = args.get("order") or "desc"
|
||||||
@@ -40,7 +42,7 @@ class QueryBuilder:
|
|||||||
query = query.filter(Package.type.in_(self.types))
|
query = query.filter(Package.type.in_(self.types))
|
||||||
|
|
||||||
if self.search:
|
if self.search:
|
||||||
query = query.filter(Package.title.ilike('%' + self.search + '%'))
|
query = query.search(self.search)
|
||||||
|
|
||||||
if self.random:
|
if self.random:
|
||||||
query = query.order_by(func.random())
|
query = query.order_by(func.random())
|
||||||
|
|||||||
@@ -8,6 +8,7 @@
|
|||||||
<title>{% block title %}title{% endblock %} - {{ config.USER_APP_NAME }}</title>
|
<title>{% block title %}title{% endblock %} - {{ config.USER_APP_NAME }}</title>
|
||||||
<link rel="stylesheet" type="text/css" href="/static/bootstrap.css">
|
<link rel="stylesheet" type="text/css" href="/static/bootstrap.css">
|
||||||
<link rel="stylesheet" type="text/css" href="/static/custom.css?v=6">
|
<link rel="stylesheet" type="text/css" href="/static/custom.css?v=6">
|
||||||
|
<link rel="search" type="application/opensearchdescription+xml" href="/static/opensearch.xml" title="ContentDB" />
|
||||||
{% block headextra %}{% endblock %}
|
{% block headextra %}{% endblock %}
|
||||||
</head>
|
</head>
|
||||||
|
|
||||||
|
|||||||
@@ -4,6 +4,21 @@
|
|||||||
Welcome
|
Welcome
|
||||||
{% endblock %}
|
{% endblock %}
|
||||||
|
|
||||||
|
{% block scriptextra %}
|
||||||
|
<script type="application/ld+json">
|
||||||
|
{
|
||||||
|
"@context": "https://schema.org",
|
||||||
|
"@type": "WebSite",
|
||||||
|
"url": "https://content.minetest.net/",
|
||||||
|
"potentialAction": {
|
||||||
|
"@type": "SearchAction",
|
||||||
|
"target": "https://content.minetest.net/packages?q={search_term_string}",
|
||||||
|
"query-input": "required name=search_term_string"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
</script>
|
||||||
|
{% endblock %}
|
||||||
|
|
||||||
{% block content %}
|
{% block content %}
|
||||||
<!-- <header class="jumbotron">
|
<!-- <header class="jumbotron">
|
||||||
<div class="container">
|
<div class="container">
|
||||||
@@ -51,7 +66,7 @@ Welcome
|
|||||||
|
|
||||||
<div class="text-center">
|
<div class="text-center">
|
||||||
<small>
|
<small>
|
||||||
CDB has {{ count }} packages available to download.
|
CDB has {{ count }} packages, with a total of {{ downloads }} downloads.
|
||||||
</small>
|
</small>
|
||||||
</div>
|
</div>
|
||||||
<!-- </main> -->
|
<!-- </main> -->
|
||||||
|
|||||||
@@ -12,7 +12,7 @@
|
|||||||
</h3>
|
</h3>
|
||||||
|
|
||||||
<p>
|
<p>
|
||||||
{{ package.shortDesc }}
|
{{ package.short_desc }}
|
||||||
</p>
|
</p>
|
||||||
|
|
||||||
|
|
||||||
|
|||||||
@@ -49,7 +49,7 @@
|
|||||||
{{ render_field(form.title, class_="pkg_meta col-sm-7") }}
|
{{ render_field(form.title, class_="pkg_meta col-sm-7") }}
|
||||||
{{ render_field(form.name, class_="pkg_meta col-sm-3") }}
|
{{ render_field(form.name, class_="pkg_meta col-sm-3") }}
|
||||||
</div>
|
</div>
|
||||||
{{ render_field(form.shortDesc, class_="pkg_meta") }}
|
{{ render_field(form.short_desc, class_="pkg_meta") }}
|
||||||
{{ render_multiselect_field(form.tags, class_="pkg_meta") }}
|
{{ render_multiselect_field(form.tags, class_="pkg_meta") }}
|
||||||
<div class="pkg_meta row">
|
<div class="pkg_meta row">
|
||||||
{{ render_field(form.license, class_="not_txp col-sm-6") }}
|
{{ render_field(form.license, class_="not_txp col-sm-6") }}
|
||||||
|
|||||||
@@ -17,7 +17,7 @@
|
|||||||
{{ render_field(form.type) }}
|
{{ render_field(form.type) }}
|
||||||
{{ render_field(form.name) }}
|
{{ render_field(form.name) }}
|
||||||
{{ render_field(form.title) }}
|
{{ render_field(form.title) }}
|
||||||
{{ render_field(form.shortDesc) }}
|
{{ render_field(form.short_desc) }}
|
||||||
{{ render_field(form.desc) }}
|
{{ render_field(form.desc) }}
|
||||||
{{ render_multiselect_field(form.tags) }}
|
{{ render_multiselect_field(form.tags) }}
|
||||||
|
|
||||||
|
|||||||
@@ -19,11 +19,12 @@
|
|||||||
</h1>
|
</h1>
|
||||||
|
|
||||||
<p class="lead">
|
<p class="lead">
|
||||||
{{ package.shortDesc }}
|
{{ package.short_desc }}
|
||||||
</p>
|
</p>
|
||||||
|
|
||||||
<div class="row" style="margin-top: 2rem;">
|
<div class="row" style="margin-top: 2rem;">
|
||||||
<div class="col">
|
<div class="col">
|
||||||
|
{{ package.getDownloadCount() }} downloads
|
||||||
</div>
|
</div>
|
||||||
<div class="btn-group-horizontal col-md-auto">
|
<div class="btn-group-horizontal col-md-auto">
|
||||||
{% if package.repo %}<a class="btn btn-secondary" href="{{ package.repo }}">View Source</a>{% endif %}
|
{% if package.repo %}<a class="btn btn-secondary" href="{{ package.repo }}">View Source</a>{% endif %}
|
||||||
@@ -111,11 +112,22 @@
|
|||||||
{% endif %}
|
{% endif %}
|
||||||
|
|
||||||
<aside class="float-right ml-4" style="width: 18rem;">
|
<aside class="float-right ml-4" style="width: 18rem;">
|
||||||
{% if package.getDownloadRelease() %}
|
{% set release = package.getDownloadRelease() %}
|
||||||
<a class="btn btn-download btn-lg btn-block"
|
{% if release %}
|
||||||
|
<a class="btn btn-download btn-lg btn-block" rel="nofollow"
|
||||||
href="{{ package.getDownloadURL() }}" class="btn_green">
|
href="{{ package.getDownloadURL() }}" class="btn_green">
|
||||||
Download
|
Download
|
||||||
</a>
|
</a>
|
||||||
|
|
||||||
|
<p class="text-center m-2" style="font-size: 80%;">
|
||||||
|
{% if release.min_rel and release.max_rel %}
|
||||||
|
Minetest {{ release.min_rel.name }} - {{ release.max_rel.name }}
|
||||||
|
{% elif release.min_rel %}
|
||||||
|
Supports Minetest {{ release.min_rel.name }} and above.
|
||||||
|
{% elif release.max_rel %}
|
||||||
|
Supports Minetest {{ release.max_rel.name }} and below.
|
||||||
|
{% endif %}
|
||||||
|
</p>
|
||||||
{% else %}
|
{% else %}
|
||||||
No download available.
|
No download available.
|
||||||
{% endif %}
|
{% endif %}
|
||||||
@@ -260,10 +272,28 @@
|
|||||||
|
|
||||||
{% if not rel.approved %}<i>{% endif %}
|
{% if not rel.approved %}<i>{% endif %}
|
||||||
|
|
||||||
<a href="{{ rel.getDownloadURL() }}">{{ rel.title }}</a>{% if rel.commit_hash %}
|
<a href="{{ rel.getDownloadURL() }}" rel="nofollow">{{ rel.title }}</a>
|
||||||
[{{ rel.commit_hash | truncate(5, end='') }}]{% endif %}<br>
|
|
||||||
<small>created {{ rel.releaseDate | datetime }}.</small>
|
<span style="color:#ddd;">
|
||||||
{% if rel.task_id %}
|
{% if rel.min_rel and rel.max_rel %}
|
||||||
|
[MT {{ rel.min_rel.name }}-{{ rel.max_rel.name }}]
|
||||||
|
{% elif rel.min_rel %}
|
||||||
|
[MT {{ rel.min_rel.name }}+]
|
||||||
|
{% elif rel.max_rel %}
|
||||||
|
[MT ≤{{ rel.max_rel.name }}]
|
||||||
|
{% endif %}
|
||||||
|
</span>
|
||||||
|
|
||||||
|
<br>
|
||||||
|
|
||||||
|
<small style="color:#999;">
|
||||||
|
{% if rel.commit_hash %}
|
||||||
|
[{{ rel.commit_hash | truncate(5, end='') }}]
|
||||||
|
{% endif %}
|
||||||
|
|
||||||
|
created {{ rel.releaseDate | date }}.
|
||||||
|
</small>
|
||||||
|
{% if (package.checkPerm(current_user, "MAKE_RELEASE") or package.checkPerm(current_user, "APPROVE_RELEASE")) and rel.task_id %}
|
||||||
<a href="{{ url_for('check_task', id=rel.task_id, r=package.getDetailsURL()) }}">Importing...</a>
|
<a href="{{ url_for('check_task', id=rel.task_id, r=package.getDetailsURL()) }}">Importing...</a>
|
||||||
{% elif not rel.approved %}
|
{% elif not rel.approved %}
|
||||||
Waiting for approval.
|
Waiting for approval.
|
||||||
|
|||||||
@@ -22,6 +22,7 @@ from app.models import *
|
|||||||
import flask_menu as menu
|
import flask_menu as menu
|
||||||
from werkzeug.contrib.cache import SimpleCache
|
from werkzeug.contrib.cache import SimpleCache
|
||||||
from urllib.parse import urlparse
|
from urllib.parse import urlparse
|
||||||
|
from sqlalchemy.sql.expression import func
|
||||||
cache = SimpleCache()
|
cache = SimpleCache()
|
||||||
|
|
||||||
@app.template_filter()
|
@app.template_filter()
|
||||||
@@ -53,7 +54,8 @@ def home_page():
|
|||||||
pop_mod = query.filter_by(type=PackageType.MOD).order_by(db.desc(Package.score)).limit(8).all()
|
pop_mod = query.filter_by(type=PackageType.MOD).order_by(db.desc(Package.score)).limit(8).all()
|
||||||
pop_gam = query.filter_by(type=PackageType.GAME).order_by(db.desc(Package.score)).limit(4).all()
|
pop_gam = query.filter_by(type=PackageType.GAME).order_by(db.desc(Package.score)).limit(4).all()
|
||||||
pop_txp = query.filter_by(type=PackageType.TXP).order_by(db.desc(Package.score)).limit(4).all()
|
pop_txp = query.filter_by(type=PackageType.TXP).order_by(db.desc(Package.score)).limit(4).all()
|
||||||
return render_template("index.html", count=count, \
|
downloads = db.session.query(func.sum(PackageRelease.downloads)).first()[0]
|
||||||
|
return render_template("index.html", count=count, downloads=downloads, \
|
||||||
new=new, pop_mod=pop_mod, pop_txp=pop_txp, pop_gam=pop_gam)
|
new=new, pop_mod=pop_mod, pop_txp=pop_txp, pop_gam=pop_gam)
|
||||||
|
|
||||||
from . import users, packages, meta, threads, api
|
from . import users, packages, meta, threads, api
|
||||||
|
|||||||
@@ -27,7 +27,7 @@ def api_packages_page():
|
|||||||
qb = QueryBuilder(request.args)
|
qb = QueryBuilder(request.args)
|
||||||
query = qb.buildPackageQuery()
|
query = qb.buildPackageQuery()
|
||||||
|
|
||||||
pkgs = [package.getAsDictionaryShort(app.config["BASE_URL"]) \
|
pkgs = [package.getAsDictionaryShort(app.config["BASE_URL"], request.args.get("protocol_version")) \
|
||||||
for package in query.all() if package.getDownloadRelease() is not None]
|
for package in query.all() if package.getDownloadRelease() is not None]
|
||||||
return jsonify(pkgs)
|
return jsonify(pkgs)
|
||||||
|
|
||||||
|
|||||||
@@ -160,13 +160,18 @@ def package_download_page(package):
|
|||||||
flash("No download available.", "error")
|
flash("No download available.", "error")
|
||||||
return redirect(package.getDetailsURL())
|
return redirect(package.getDetailsURL())
|
||||||
else:
|
else:
|
||||||
|
PackageRelease.query.filter_by(id=release.id).update({
|
||||||
|
"downloads": PackageRelease.downloads + 1
|
||||||
|
})
|
||||||
|
db.session.commit()
|
||||||
|
|
||||||
return redirect(release.url, code=302)
|
return redirect(release.url, code=302)
|
||||||
|
|
||||||
|
|
||||||
class PackageForm(FlaskForm):
|
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")])
|
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)])
|
title = StringField("Title (Human-readable)", [InputRequired(), Length(3, 50)])
|
||||||
shortDesc = StringField("Short Description (Plaintext)", [InputRequired(), Length(1,200)])
|
short_desc = StringField("Short Description (Plaintext)", [InputRequired(), Length(1,200)])
|
||||||
desc = TextAreaField("Long Description (Markdown)", [Optional(), Length(0,10000)])
|
desc = TextAreaField("Long Description (Markdown)", [Optional(), Length(0,10000)])
|
||||||
type = SelectField("Type", [InputRequired()], choices=PackageType.choices(), coerce=PackageType.coerce, default=PackageType.MOD)
|
type = SelectField("Type", [InputRequired()], choices=PackageType.choices(), coerce=PackageType.coerce, default=PackageType.MOD)
|
||||||
license = QuerySelectField("License", [InputRequired()], query_factory=lambda: License.query.order_by(db.asc(License.name)), get_pk=lambda a: a.id, get_label=lambda a: a.name)
|
license = QuerySelectField("License", [InputRequired()], query_factory=lambda: License.query.order_by(db.asc(License.name)), get_pk=lambda a: a.id, get_label=lambda a: a.name)
|
||||||
|
|||||||
@@ -85,6 +85,7 @@ def create_release_page(package):
|
|||||||
rel.task_id = uuid()
|
rel.task_id = uuid()
|
||||||
rel.min_rel = form["min_rel"].data.getActual()
|
rel.min_rel = form["min_rel"].data.getActual()
|
||||||
rel.max_rel = form["max_rel"].data.getActual()
|
rel.max_rel = form["max_rel"].data.getActual()
|
||||||
|
rel.approved = package.checkPerm(current_user, Permission.APPROVE_RELEASE)
|
||||||
db.session.add(rel)
|
db.session.add(rel)
|
||||||
db.session.commit()
|
db.session.commit()
|
||||||
|
|
||||||
@@ -104,6 +105,7 @@ def create_release_page(package):
|
|||||||
rel.url = uploadedPath
|
rel.url = uploadedPath
|
||||||
rel.min_rel = form["min_rel"].data.getActual()
|
rel.min_rel = form["min_rel"].data.getActual()
|
||||||
rel.max_rel = form["max_rel"].data.getActual()
|
rel.max_rel = form["max_rel"].data.getActual()
|
||||||
|
rel.approved = package.checkPerm(current_user, Permission.APPROVE_RELEASE)
|
||||||
db.session.add(rel)
|
db.session.add(rel)
|
||||||
db.session.commit()
|
db.session.commit()
|
||||||
|
|
||||||
@@ -129,6 +131,11 @@ def download_release_page(package, id):
|
|||||||
flash("No download available.", "error")
|
flash("No download available.", "error")
|
||||||
return redirect(package.getDetailsURL())
|
return redirect(package.getDetailsURL())
|
||||||
else:
|
else:
|
||||||
|
PackageRelease.query.filter_by(id=release.id).update({
|
||||||
|
"downloads": PackageRelease.downloads + 1
|
||||||
|
})
|
||||||
|
db.session.commit()
|
||||||
|
|
||||||
return redirect(release.url, code=300)
|
return redirect(release.url, code=300)
|
||||||
|
|
||||||
@app.route("/packages/<author>/<name>/releases/<id>/", methods=["GET", "POST"])
|
@app.route("/packages/<author>/<name>/releases/<id>/", methods=["GET", "POST"])
|
||||||
|
|||||||
@@ -53,9 +53,10 @@ def create_screenshot_page(package, id=None):
|
|||||||
"a PNG or JPG image file")
|
"a PNG or JPG image file")
|
||||||
if uploadedPath is not None:
|
if uploadedPath is not None:
|
||||||
ss = PackageScreenshot()
|
ss = PackageScreenshot()
|
||||||
ss.package = package
|
ss.package = package
|
||||||
ss.title = form["title"].data or "Untitled"
|
ss.title = form["title"].data or "Untitled"
|
||||||
ss.url = uploadedPath
|
ss.url = uploadedPath
|
||||||
|
ss.approved = package.checkPerm(current_user, Permission.APPROVE_SCREENSHOT)
|
||||||
db.session.add(ss)
|
db.session.add(ss)
|
||||||
|
|
||||||
msg = "{}: Screenshot added {}" \
|
msg = "{}: Screenshot added {}" \
|
||||||
|
|||||||
36
migrations/versions/2f3c3597c78d_.py
Normal file
36
migrations/versions/2f3c3597c78d_.py
Normal file
@@ -0,0 +1,36 @@
|
|||||||
|
"""empty message
|
||||||
|
|
||||||
|
Revision ID: 2f3c3597c78d
|
||||||
|
Revises: 9ec17b558413
|
||||||
|
Create Date: 2019-01-29 02:43:08.865695
|
||||||
|
|
||||||
|
"""
|
||||||
|
from alembic import op
|
||||||
|
import sqlalchemy as sa
|
||||||
|
from sqlalchemy.dialects import postgresql
|
||||||
|
from sqlalchemy_utils.types import TSVectorType
|
||||||
|
from sqlalchemy_searchable import sync_trigger
|
||||||
|
|
||||||
|
# revision identifiers, used by Alembic.
|
||||||
|
revision = '2f3c3597c78d'
|
||||||
|
down_revision = '9ec17b558413'
|
||||||
|
branch_labels = None
|
||||||
|
depends_on = None
|
||||||
|
|
||||||
|
|
||||||
|
def upgrade():
|
||||||
|
# ### commands auto generated by Alembic - please adjust! ###
|
||||||
|
op.alter_column('package', 'shortDesc', nullable=False, new_column_name='short_desc')
|
||||||
|
op.add_column('package', sa.Column('search_vector', TSVectorType("title", "short_desc", "desc"), nullable=True))
|
||||||
|
op.create_index('ix_package_search_vector', 'package', ['search_vector'], unique=False, postgresql_using='gin')
|
||||||
|
|
||||||
|
conn = op.get_bind()
|
||||||
|
sync_trigger(conn, 'package', 'search_vector', ["title", "short_desc", "desc"])
|
||||||
|
# ### end Alembic commands ###
|
||||||
|
|
||||||
|
|
||||||
|
def downgrade():
|
||||||
|
# ### commands auto generated by Alembic - please adjust! ###
|
||||||
|
op.drop_index('ix_package_search_vector', table_name='package')
|
||||||
|
op.drop_column('package', 'search_vector')
|
||||||
|
# ### end Alembic commands ###
|
||||||
249
migrations/versions/7ff57806ffd5_.py
Normal file
249
migrations/versions/7ff57806ffd5_.py
Normal file
@@ -0,0 +1,249 @@
|
|||||||
|
"""empty message
|
||||||
|
|
||||||
|
Revision ID: 7ff57806ffd5
|
||||||
|
Revises: 2f3c3597c78d
|
||||||
|
Create Date: 2019-01-29 02:57:50.279918
|
||||||
|
|
||||||
|
"""
|
||||||
|
from alembic import op
|
||||||
|
import sqlalchemy as sa
|
||||||
|
from sqlalchemy.dialects import postgresql
|
||||||
|
|
||||||
|
# revision identifiers, used by Alembic.
|
||||||
|
revision = '7ff57806ffd5'
|
||||||
|
down_revision = '2f3c3597c78d'
|
||||||
|
branch_labels = None
|
||||||
|
depends_on = None
|
||||||
|
|
||||||
|
|
||||||
|
def upgrade():
|
||||||
|
# ### commands auto generated by Alembic - please adjust! ###
|
||||||
|
op.execute("""
|
||||||
|
DROP TYPE IF EXISTS tsq_state CASCADE;
|
||||||
|
|
||||||
|
CREATE TYPE tsq_state AS (
|
||||||
|
search_query text,
|
||||||
|
parentheses_stack int,
|
||||||
|
skip_for int,
|
||||||
|
current_token text,
|
||||||
|
current_index int,
|
||||||
|
current_char text,
|
||||||
|
previous_char text,
|
||||||
|
tokens text[]
|
||||||
|
);
|
||||||
|
|
||||||
|
CREATE OR REPLACE FUNCTION tsq_append_current_token(state tsq_state)
|
||||||
|
RETURNS tsq_state AS $$
|
||||||
|
BEGIN
|
||||||
|
IF state.current_token != '' THEN
|
||||||
|
state.tokens := array_append(state.tokens, state.current_token);
|
||||||
|
state.current_token := '';
|
||||||
|
END IF;
|
||||||
|
RETURN state;
|
||||||
|
END;
|
||||||
|
$$ LANGUAGE plpgsql IMMUTABLE;
|
||||||
|
|
||||||
|
|
||||||
|
CREATE OR REPLACE FUNCTION tsq_tokenize_character(state tsq_state)
|
||||||
|
RETURNS tsq_state AS $$
|
||||||
|
BEGIN
|
||||||
|
IF state.current_char = '(' THEN
|
||||||
|
state.tokens := array_append(state.tokens, '(');
|
||||||
|
state.parentheses_stack := state.parentheses_stack + 1;
|
||||||
|
state := tsq_append_current_token(state);
|
||||||
|
ELSIF state.current_char = ')' THEN
|
||||||
|
IF (state.parentheses_stack > 0 AND state.current_token != '') THEN
|
||||||
|
state := tsq_append_current_token(state);
|
||||||
|
state.tokens := array_append(state.tokens, ')');
|
||||||
|
state.parentheses_stack := state.parentheses_stack - 1;
|
||||||
|
END IF;
|
||||||
|
ELSIF state.current_char = '"' THEN
|
||||||
|
state.skip_for := position('"' IN substring(
|
||||||
|
state.search_query FROM state.current_index + 1
|
||||||
|
));
|
||||||
|
|
||||||
|
IF state.skip_for > 1 THEN
|
||||||
|
state.tokens = array_append(
|
||||||
|
state.tokens,
|
||||||
|
substring(
|
||||||
|
state.search_query
|
||||||
|
FROM state.current_index FOR state.skip_for + 1
|
||||||
|
)
|
||||||
|
);
|
||||||
|
ELSIF state.skip_for = 0 THEN
|
||||||
|
state.current_token := state.current_token || state.current_char;
|
||||||
|
END IF;
|
||||||
|
ELSIF (
|
||||||
|
state.current_char = '-' AND
|
||||||
|
(state.current_index = 1 OR state.previous_char = ' ')
|
||||||
|
) THEN
|
||||||
|
state.tokens := array_append(state.tokens, '-');
|
||||||
|
ELSIF state.current_char = ' ' THEN
|
||||||
|
state := tsq_append_current_token(state);
|
||||||
|
IF substring(
|
||||||
|
state.search_query FROM state.current_index FOR 4
|
||||||
|
) = ' or ' THEN
|
||||||
|
state.skip_for := 2;
|
||||||
|
|
||||||
|
-- remove duplicate OR tokens
|
||||||
|
IF state.tokens[array_length(state.tokens, 1)] != ' | ' THEN
|
||||||
|
state.tokens := array_append(state.tokens, ' | ');
|
||||||
|
END IF;
|
||||||
|
END IF;
|
||||||
|
ELSE
|
||||||
|
state.current_token = state.current_token || state.current_char;
|
||||||
|
END IF;
|
||||||
|
RETURN state;
|
||||||
|
END;
|
||||||
|
$$ LANGUAGE plpgsql IMMUTABLE;
|
||||||
|
|
||||||
|
|
||||||
|
CREATE OR REPLACE FUNCTION tsq_tokenize(search_query text) RETURNS text[] AS $$
|
||||||
|
DECLARE
|
||||||
|
state tsq_state;
|
||||||
|
BEGIN
|
||||||
|
SELECT
|
||||||
|
search_query::text AS search_query,
|
||||||
|
0::int AS parentheses_stack,
|
||||||
|
0 AS skip_for,
|
||||||
|
''::text AS current_token,
|
||||||
|
0 AS current_index,
|
||||||
|
''::text AS current_char,
|
||||||
|
''::text AS previous_char,
|
||||||
|
'{}'::text[] AS tokens
|
||||||
|
INTO state;
|
||||||
|
|
||||||
|
state.search_query := lower(trim(
|
||||||
|
regexp_replace(search_query, '""+', '""', 'g')
|
||||||
|
));
|
||||||
|
|
||||||
|
FOR state.current_index IN (
|
||||||
|
SELECT generate_series(1, length(state.search_query))
|
||||||
|
) LOOP
|
||||||
|
state.current_char := substring(
|
||||||
|
search_query FROM state.current_index FOR 1
|
||||||
|
);
|
||||||
|
|
||||||
|
IF state.skip_for > 0 THEN
|
||||||
|
state.skip_for := state.skip_for - 1;
|
||||||
|
CONTINUE;
|
||||||
|
END IF;
|
||||||
|
|
||||||
|
state := tsq_tokenize_character(state);
|
||||||
|
state.previous_char := state.current_char;
|
||||||
|
END LOOP;
|
||||||
|
state := tsq_append_current_token(state);
|
||||||
|
|
||||||
|
state.tokens := array_nremove(state.tokens, '(', -state.parentheses_stack);
|
||||||
|
|
||||||
|
RETURN state.tokens;
|
||||||
|
END;
|
||||||
|
$$ LANGUAGE plpgsql IMMUTABLE;
|
||||||
|
|
||||||
|
|
||||||
|
-- Processes an array of text search tokens and returns a tsquery
|
||||||
|
CREATE OR REPLACE FUNCTION tsq_process_tokens(config regconfig, tokens text[])
|
||||||
|
RETURNS tsquery AS $$
|
||||||
|
DECLARE
|
||||||
|
result_query text;
|
||||||
|
previous_value text;
|
||||||
|
value text;
|
||||||
|
BEGIN
|
||||||
|
result_query := '';
|
||||||
|
FOREACH value IN ARRAY tokens LOOP
|
||||||
|
IF value = '"' THEN
|
||||||
|
CONTINUE;
|
||||||
|
END IF;
|
||||||
|
|
||||||
|
IF left(value, 1) = '"' AND right(value, 1) = '"' THEN
|
||||||
|
value := phraseto_tsquery(config, value);
|
||||||
|
ELSIF value NOT IN ('(', ' | ', ')', '-') THEN
|
||||||
|
value := quote_literal(value) || ':*';
|
||||||
|
END IF;
|
||||||
|
|
||||||
|
IF previous_value = '-' THEN
|
||||||
|
IF value = '(' THEN
|
||||||
|
value := '!' || value;
|
||||||
|
ELSE
|
||||||
|
value := '!(' || value || ')';
|
||||||
|
END IF;
|
||||||
|
END IF;
|
||||||
|
|
||||||
|
SELECT
|
||||||
|
CASE
|
||||||
|
WHEN result_query = '' THEN value
|
||||||
|
WHEN (
|
||||||
|
previous_value IN ('!(', '(', ' | ') OR
|
||||||
|
value IN (')', ' | ')
|
||||||
|
) THEN result_query || value
|
||||||
|
ELSE result_query || ' & ' || value
|
||||||
|
END
|
||||||
|
INTO result_query;
|
||||||
|
previous_value := value;
|
||||||
|
END LOOP;
|
||||||
|
|
||||||
|
RETURN to_tsquery(config, result_query);
|
||||||
|
END;
|
||||||
|
$$ LANGUAGE plpgsql IMMUTABLE;
|
||||||
|
|
||||||
|
|
||||||
|
CREATE OR REPLACE FUNCTION tsq_process_tokens(tokens text[])
|
||||||
|
RETURNS tsquery AS $$
|
||||||
|
SELECT tsq_process_tokens(get_current_ts_config(), tokens);
|
||||||
|
$$ LANGUAGE SQL IMMUTABLE;
|
||||||
|
|
||||||
|
|
||||||
|
CREATE OR REPLACE FUNCTION tsq_parse(config regconfig, search_query text)
|
||||||
|
RETURNS tsquery AS $$
|
||||||
|
SELECT tsq_process_tokens(config, tsq_tokenize(search_query));
|
||||||
|
$$ LANGUAGE SQL IMMUTABLE;
|
||||||
|
|
||||||
|
|
||||||
|
CREATE OR REPLACE FUNCTION tsq_parse(config text, search_query text)
|
||||||
|
RETURNS tsquery AS $$
|
||||||
|
SELECT tsq_parse(config::regconfig, search_query);
|
||||||
|
$$ LANGUAGE SQL IMMUTABLE;
|
||||||
|
|
||||||
|
|
||||||
|
CREATE OR REPLACE FUNCTION tsq_parse(search_query text) RETURNS tsquery AS $$
|
||||||
|
SELECT tsq_parse(get_current_ts_config(), search_query);
|
||||||
|
$$ LANGUAGE SQL IMMUTABLE;
|
||||||
|
|
||||||
|
|
||||||
|
-- remove first N elements equal to the given value from the array (array
|
||||||
|
-- must be one-dimensional)
|
||||||
|
--
|
||||||
|
-- If negative value is given as the third argument the removal of elements
|
||||||
|
-- starts from the last array element.
|
||||||
|
CREATE OR REPLACE FUNCTION array_nremove(anyarray, anyelement, int)
|
||||||
|
RETURNS ANYARRAY AS $$
|
||||||
|
WITH replaced_positions AS (
|
||||||
|
SELECT UNNEST(
|
||||||
|
CASE
|
||||||
|
WHEN $2 IS NULL THEN
|
||||||
|
'{}'::int[]
|
||||||
|
WHEN $3 > 0 THEN
|
||||||
|
(array_positions($1, $2))[1:$3]
|
||||||
|
WHEN $3 < 0 THEN
|
||||||
|
(array_positions($1, $2))[
|
||||||
|
(cardinality(array_positions($1, $2)) + $3 + 1):
|
||||||
|
]
|
||||||
|
ELSE
|
||||||
|
'{}'::int[]
|
||||||
|
END
|
||||||
|
) AS position
|
||||||
|
)
|
||||||
|
SELECT COALESCE((
|
||||||
|
SELECT array_agg(value)
|
||||||
|
FROM unnest($1) WITH ORDINALITY AS t(value, index)
|
||||||
|
WHERE index NOT IN (SELECT position FROM replaced_positions)
|
||||||
|
), $1[1:0]);
|
||||||
|
$$ LANGUAGE SQL IMMUTABLE;
|
||||||
|
""")
|
||||||
|
# ### end Alembic commands ###
|
||||||
|
|
||||||
|
|
||||||
|
def downgrade():
|
||||||
|
# ### commands auto generated by Alembic - please adjust! ###
|
||||||
|
pass
|
||||||
|
# ### end Alembic commands ###
|
||||||
28
migrations/versions/9ec17b558413_.py
Normal file
28
migrations/versions/9ec17b558413_.py
Normal file
@@ -0,0 +1,28 @@
|
|||||||
|
"""empty message
|
||||||
|
|
||||||
|
Revision ID: 9ec17b558413
|
||||||
|
Revises: 97a9c461bc2d
|
||||||
|
Create Date: 2019-01-29 00:37:49.507631
|
||||||
|
|
||||||
|
"""
|
||||||
|
from alembic import op
|
||||||
|
import sqlalchemy as sa
|
||||||
|
from sqlalchemy.dialects import postgresql
|
||||||
|
|
||||||
|
# revision identifiers, used by Alembic.
|
||||||
|
revision = '9ec17b558413'
|
||||||
|
down_revision = '97a9c461bc2d'
|
||||||
|
branch_labels = None
|
||||||
|
depends_on = None
|
||||||
|
|
||||||
|
|
||||||
|
def upgrade():
|
||||||
|
# ### commands auto generated by Alembic - please adjust! ###
|
||||||
|
op.add_column('package_release', sa.Column('downloads', sa.Integer(), nullable=False, server_default="0"))
|
||||||
|
# ### end Alembic commands ###
|
||||||
|
|
||||||
|
|
||||||
|
def downgrade():
|
||||||
|
# ### commands auto generated by Alembic - please adjust! ###
|
||||||
|
op.drop_column('package_release', 'downloads')
|
||||||
|
# ### end Alembic commands ###
|
||||||
@@ -8,6 +8,7 @@ Flask-Migrate~=2.3
|
|||||||
Flask-SQLAlchemy~=2.3
|
Flask-SQLAlchemy~=2.3
|
||||||
Flask-User~=0.6
|
Flask-User~=0.6
|
||||||
GitHub-Flask~=3.2
|
GitHub-Flask~=3.2
|
||||||
|
SQLAlchemy-Searchable==1.0.3
|
||||||
|
|
||||||
beautifulsoup4~=4.6
|
beautifulsoup4~=4.6
|
||||||
celery~=4.2
|
celery~=4.2
|
||||||
|
|||||||
18
setup.py
18
setup.py
@@ -55,7 +55,7 @@ def defineDummyData(licenses, tags, ruben):
|
|||||||
mod.repo = "https://github.com/ezhh/other_worlds"
|
mod.repo = "https://github.com/ezhh/other_worlds"
|
||||||
mod.issueTracker = "https://github.com/ezhh/other_worlds/issues"
|
mod.issueTracker = "https://github.com/ezhh/other_worlds/issues"
|
||||||
mod.forums = 16015
|
mod.forums = 16015
|
||||||
mod.shortDesc = "The content library should not be used yet as it is still in alpha"
|
mod.short_desc = "The content library should not be used yet as it is still in alpha"
|
||||||
mod.desc = "This is the long desc"
|
mod.desc = "This is the long desc"
|
||||||
db.session.add(mod)
|
db.session.add(mod)
|
||||||
|
|
||||||
@@ -77,7 +77,7 @@ def defineDummyData(licenses, tags, ruben):
|
|||||||
mod1.repo = "https://github.com/rubenwardy/awards"
|
mod1.repo = "https://github.com/rubenwardy/awards"
|
||||||
mod1.issueTracker = "https://github.com/rubenwardy/awards/issues"
|
mod1.issueTracker = "https://github.com/rubenwardy/awards/issues"
|
||||||
mod1.forums = 4870
|
mod1.forums = 4870
|
||||||
mod1.shortDesc = "Adds achievements and an API to register new ones."
|
mod1.short_desc = "Adds achievements and an API to register new ones."
|
||||||
mod1.desc = """
|
mod1.desc = """
|
||||||
Majority of awards are back ported from Calinou's old fork in Carbone, under same license.
|
Majority of awards are back ported from Calinou's old fork in Carbone, under same license.
|
||||||
|
|
||||||
@@ -112,7 +112,7 @@ awards.register_achievement("award_mesefind",{
|
|||||||
mod2.repo = "https://github.com/minetest-mods/mesecons/"
|
mod2.repo = "https://github.com/minetest-mods/mesecons/"
|
||||||
mod2.issueTracker = "https://github.com/minetest-mods/mesecons/issues"
|
mod2.issueTracker = "https://github.com/minetest-mods/mesecons/issues"
|
||||||
mod2.forums = 628
|
mod2.forums = 628
|
||||||
mod2.shortDesc = "Mesecons adds everything digital, from all kinds of sensors, switches, solar panels, detectors, pistons, lamps, sound blocks to advanced digital circuitry like logic gates and programmable blocks."
|
mod2.short_desc = "Mesecons adds everything digital, from all kinds of sensors, switches, solar panels, detectors, pistons, lamps, sound blocks to advanced digital circuitry like logic gates and programmable blocks."
|
||||||
mod2.desc = """
|
mod2.desc = """
|
||||||
########################################################################
|
########################################################################
|
||||||
## __ __ _____ _____ _____ _____ _____ _ _ _____ ##
|
## __ __ _____ _____ _____ _____ _____ _ _ _____ ##
|
||||||
@@ -210,7 +210,7 @@ No warranty is provided, express or implied, for any part of the project.
|
|||||||
mod.repo = "https://github.com/ezhh/handholds"
|
mod.repo = "https://github.com/ezhh/handholds"
|
||||||
mod.issueTracker = "https://github.com/ezhh/handholds/issues"
|
mod.issueTracker = "https://github.com/ezhh/handholds/issues"
|
||||||
mod.forums = 17069
|
mod.forums = 17069
|
||||||
mod.shortDesc = "Adds hand holds and climbing thingies"
|
mod.short_desc = "Adds hand holds and climbing thingies"
|
||||||
mod.desc = "This is the long desc"
|
mod.desc = "This is the long desc"
|
||||||
db.session.add(mod)
|
db.session.add(mod)
|
||||||
|
|
||||||
@@ -233,7 +233,7 @@ No warranty is provided, express or implied, for any part of the project.
|
|||||||
mod.repo = "https://github.com/ezhh/other_worlds"
|
mod.repo = "https://github.com/ezhh/other_worlds"
|
||||||
mod.issueTracker = "https://github.com/ezhh/other_worlds/issues"
|
mod.issueTracker = "https://github.com/ezhh/other_worlds/issues"
|
||||||
mod.forums = 16015
|
mod.forums = 16015
|
||||||
mod.shortDesc = "Adds space with asteroids and comets"
|
mod.short_desc = "Adds space with asteroids and comets"
|
||||||
mod.desc = "This is the long desc"
|
mod.desc = "This is the long desc"
|
||||||
db.session.add(mod)
|
db.session.add(mod)
|
||||||
|
|
||||||
@@ -248,7 +248,7 @@ No warranty is provided, express or implied, for any part of the project.
|
|||||||
mod.repo = "https://github.com/rubenwardy/food/"
|
mod.repo = "https://github.com/rubenwardy/food/"
|
||||||
mod.issueTracker = "https://github.com/rubenwardy/food/issues/"
|
mod.issueTracker = "https://github.com/rubenwardy/food/issues/"
|
||||||
mod.forums = 2960
|
mod.forums = 2960
|
||||||
mod.shortDesc = "Adds lots of food and an API to manage ingredients"
|
mod.short_desc = "Adds lots of food and an API to manage ingredients"
|
||||||
mod.desc = "This is the long desc"
|
mod.desc = "This is the long desc"
|
||||||
food = mod
|
food = mod
|
||||||
db.session.add(mod)
|
db.session.add(mod)
|
||||||
@@ -264,7 +264,7 @@ No warranty is provided, express or implied, for any part of the project.
|
|||||||
mod.repo = "https://github.com/rubenwardy/food_sweet/"
|
mod.repo = "https://github.com/rubenwardy/food_sweet/"
|
||||||
mod.issueTracker = "https://github.com/rubenwardy/food_sweet/issues/"
|
mod.issueTracker = "https://github.com/rubenwardy/food_sweet/issues/"
|
||||||
mod.forums = 9039
|
mod.forums = 9039
|
||||||
mod.shortDesc = "Adds sweet food"
|
mod.short_desc = "Adds sweet food"
|
||||||
mod.desc = "This is the long desc"
|
mod.desc = "This is the long desc"
|
||||||
food_sweet = mod
|
food_sweet = mod
|
||||||
db.session.add(mod)
|
db.session.add(mod)
|
||||||
@@ -282,7 +282,7 @@ No warranty is provided, express or implied, for any part of the project.
|
|||||||
game1.repo = "https://github.com/rubenwardy/capturetheflag"
|
game1.repo = "https://github.com/rubenwardy/capturetheflag"
|
||||||
game1.issueTracker = "https://github.com/rubenwardy/capturetheflag/issues"
|
game1.issueTracker = "https://github.com/rubenwardy/capturetheflag/issues"
|
||||||
game1.forums = 12835
|
game1.forums = 12835
|
||||||
game1.shortDesc = "Two teams battle to snatch and return the enemy's flag, before the enemy takes their own!"
|
game1.short_desc = "Two teams battle to snatch and return the enemy's flag, before the enemy takes their own!"
|
||||||
game1.desc = """
|
game1.desc = """
|
||||||
As seen on the Capture the Flag server (minetest.rubenwardy.com:30000)
|
As seen on the Capture the Flag server (minetest.rubenwardy.com:30000)
|
||||||
|
|
||||||
@@ -307,7 +307,7 @@ Uses the CTF PvP Engine.
|
|||||||
mod.type = PackageType.TXP
|
mod.type = PackageType.TXP
|
||||||
mod.author = ruben
|
mod.author = ruben
|
||||||
mod.forums = 14132
|
mod.forums = 14132
|
||||||
mod.shortDesc = "This is an update of the original PixelBOX texture pack by the brillant artist Gambit"
|
mod.short_desc = "This is an update of the original PixelBOX texture pack by the brillant artist Gambit"
|
||||||
mod.desc = "This is the long desc"
|
mod.desc = "This is the long desc"
|
||||||
db.session.add(mod)
|
db.session.add(mod)
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user