2020-07-12 17:34:25 +02:00
# ContentDB
2021-01-30 17:59:42 +01:00
# Copyright (C) 2018-21 rubenwardy
2018-05-25 18:28:32 +02:00
#
# This program is free software: you can redistribute it and/or modify
2021-01-30 17:59:42 +01:00
# it under the terms of the GNU Affero General Public License as published by
2018-05-25 18:28:32 +02:00
# the Free Software Foundation, either version 3 of the License, or
# (at your option) any later version.
#
# This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
2021-01-30 17:59:42 +01:00
# GNU Affero General Public License for more details.
2018-05-25 18:28:32 +02:00
#
2021-01-30 17:59:42 +01:00
# You should have received a copy of the GNU Affero General Public License
2018-05-25 18:28:32 +02:00
# along with this program. If not, see <https://www.gnu.org/licenses/>.
2020-12-04 03:23:04 +01:00
from celery import uuid
2018-05-25 18:28:32 +02:00
from flask import *
2020-12-04 23:05:10 +01:00
from flask_login import login_required
2020-12-15 23:40:49 +01:00
from flask_wtf import FlaskForm
2020-12-04 03:23:04 +01:00
from wtforms import *
from wtforms . ext . sqlalchemy . fields import QuerySelectField
from wtforms . validators import *
2019-11-16 00:51:42 +01:00
2019-11-18 22:42:56 +01:00
from app . rediscache import has_key , set_key , make_download_key
2021-01-29 23:20:44 +01:00
from app . tasks . importtasks import makeVCSRelease , checkZipRelease , check_update_config
2018-05-25 18:28:32 +02:00
from app . utils import *
2020-12-04 03:23:04 +01:00
from . import bp
2019-01-28 21:48:07 +01:00
2018-05-25 18:28:32 +02:00
2019-01-28 23:28:47 +01:00
def get_mt_releases ( is_max ) :
query = MinetestRelease . query . order_by ( db . asc ( MinetestRelease . id ) )
if is_max :
query = query . limit ( query . count ( ) - 1 )
else :
query = query . filter ( MinetestRelease . name != " 0.4.17 " )
return query
2018-05-25 18:28:32 +02:00
class CreatePackageReleaseForm ( FlaskForm ) :
2018-05-28 15:54:17 +02:00
title = StringField ( " Title " , [ InputRequired ( ) , Length ( 1 , 30 ) ] )
2018-05-25 18:28:32 +02:00
uploadOpt = RadioField ( " Method " , choices = [ ( " upload " , " File Upload " ) ] , default = " upload " )
2020-12-04 02:43:20 +01:00
vcsLabel = StringField ( " Git reference (ie: commit hash, branch, or tag) " , default = None )
2018-05-25 18:28:32 +02:00
fileUpload = FileField ( " File Upload " )
2019-01-28 21:48:07 +01:00
min_rel = QuerySelectField ( " Minimum Minetest Version " , [ InputRequired ( ) ] ,
2019-01-28 23:28:47 +01:00
query_factory = lambda : get_mt_releases ( False ) , get_pk = lambda a : a . id , get_label = lambda a : a . name )
2019-01-28 21:48:07 +01:00
max_rel = QuerySelectField ( " Maximum Minetest Version " , [ InputRequired ( ) ] ,
2019-01-28 23:28:47 +01:00
query_factory = lambda : get_mt_releases ( True ) , get_pk = lambda a : a . id , get_label = lambda a : a . name )
2018-05-25 18:28:32 +02:00
submit = SubmitField ( " Save " )
class EditPackageReleaseForm ( FlaskForm ) :
2018-05-28 15:54:17 +02:00
title = StringField ( " Title " , [ InputRequired ( ) , Length ( 1 , 30 ) ] )
2020-07-18 16:42:49 +02:00
url = StringField ( " URL " , [ Optional ( ) ] )
2019-08-30 22:11:38 +02:00
task_id = StringField ( " Task ID " , filters = [ lambda x : x or None ] )
2018-05-25 18:28:32 +02:00
approved = BooleanField ( " Is Approved " )
2019-01-28 21:48:07 +01:00
min_rel = QuerySelectField ( " Minimum Minetest Version " , [ InputRequired ( ) ] ,
2019-01-28 23:28:47 +01:00
query_factory = lambda : get_mt_releases ( False ) , get_pk = lambda a : a . id , get_label = lambda a : a . name )
2019-01-28 21:48:07 +01:00
max_rel = QuerySelectField ( " Maximum Minetest Version " , [ InputRequired ( ) ] ,
2019-01-28 23:28:47 +01:00
query_factory = lambda : get_mt_releases ( True ) , get_pk = lambda a : a . id , get_label = lambda a : a . name )
2018-05-25 18:28:32 +02:00
submit = SubmitField ( " Save " )
2019-11-16 00:51:42 +01:00
@bp.route ( " /packages/<author>/<name>/releases/new/ " , methods = [ " GET " , " POST " ] )
2018-05-25 18:28:32 +02:00
@login_required
@is_package_page
2019-11-16 00:51:42 +01:00
def create_release ( package ) :
2018-05-25 18:28:32 +02:00
if not package . checkPerm ( current_user , Permission . MAKE_RELEASE ) :
return redirect ( package . getDetailsURL ( ) )
# Initial form class from post data and default data
form = CreatePackageReleaseForm ( )
2018-06-06 00:13:39 +02:00
if package . repo is not None :
2020-12-04 02:43:20 +01:00
form [ " uploadOpt " ] . choices = [ ( " vcs " , " Import from Git " ) , ( " upload " , " Upload .zip file " ) ]
2021-01-30 00:12:26 +01:00
if request . method == " GET " :
2018-05-25 18:28:32 +02:00
form [ " uploadOpt " ] . data = " vcs "
2021-01-30 00:12:26 +01:00
form . vcsLabel . data = request . args . get ( " ref " )
if request . method == " GET " :
form . title . data = request . args . get ( " title " )
2018-05-25 18:28:32 +02:00
2020-12-05 00:07:19 +01:00
if form . validate_on_submit ( ) :
2018-05-25 18:28:32 +02:00
if form [ " uploadOpt " ] . data == " vcs " :
rel = PackageRelease ( )
rel . package = package
rel . title = form [ " title " ] . data
rel . url = " "
rel . task_id = uuid ( )
2019-01-28 21:48:07 +01:00
rel . min_rel = form [ " min_rel " ] . data . getActual ( )
rel . max_rel = form [ " max_rel " ] . data . getActual ( )
2018-05-25 18:28:32 +02:00
db . session . add ( rel )
db . session . commit ( )
2020-12-14 22:05:56 +01:00
makeVCSRelease . apply_async ( ( rel . id , nonEmptyOrNone ( form . vcsLabel . data ) ) , task_id = rel . task_id )
2018-05-25 18:28:32 +02:00
2021-01-30 18:10:38 +01:00
msg = " Created release {} " . format ( rel . title )
2020-12-05 04:44:34 +01:00
addNotification ( package . maintainers , current_user , NotificationType . PACKAGE_EDIT , msg , rel . getEditURL ( ) , package )
2021-01-30 18:10:38 +01:00
addAuditLog ( AuditSeverity . NORMAL , current_user , msg , package . getDetailsURL ( ) , package )
2018-05-25 18:28:32 +02:00
db . session . commit ( )
2019-11-16 00:51:42 +01:00
return redirect ( url_for ( " tasks.check " , id = rel . task_id , r = rel . getEditURL ( ) ) )
2018-05-25 18:28:32 +02:00
else :
2020-01-19 02:37:15 +01:00
uploadedUrl , uploadedPath = doFileUpload ( form . fileUpload . data , " zip " , " a zip file " )
if uploadedUrl is not None :
2018-05-25 18:28:32 +02:00
rel = PackageRelease ( )
rel . package = package
rel . title = form [ " title " ] . data
2020-01-19 02:37:15 +01:00
rel . url = uploadedUrl
rel . task_id = uuid ( )
2019-01-28 21:48:07 +01:00
rel . min_rel = form [ " min_rel " ] . data . getActual ( )
rel . max_rel = form [ " max_rel " ] . data . getActual ( )
2018-05-25 18:28:32 +02:00
db . session . add ( rel )
db . session . commit ( )
2020-01-19 02:59:00 +01:00
checkZipRelease . apply_async ( ( rel . id , uploadedPath ) , task_id = rel . task_id )
2020-01-19 02:37:15 +01:00
2021-01-30 18:10:38 +01:00
msg = " Created release {} " . format ( rel . title )
addNotification ( package . maintainers , current_user , NotificationType . PACKAGE_EDIT ,
msg , rel . getEditURL ( ) , package )
addAuditLog ( AuditSeverity . NORMAL , current_user , msg , package . getDetailsURL ( ) ,
package )
2018-05-25 18:28:32 +02:00
db . session . commit ( )
2020-01-19 02:37:15 +01:00
return redirect ( url_for ( " tasks.check " , id = rel . task_id , r = rel . getEditURL ( ) ) )
2018-05-25 18:28:32 +02:00
return render_template ( " packages/release_new.html " , package = package , form = form )
2020-07-14 04:49:30 +02:00
2019-11-16 00:51:42 +01:00
@bp.route ( " /packages/<author>/<name>/releases/<id>/download/ " )
2018-07-28 19:33:36 +02:00
@is_package_page
2019-11-16 00:51:42 +01:00
def download_release ( package , id ) :
2018-07-28 19:33:36 +02:00
release = PackageRelease . query . get ( id )
if release is None or release . package != package :
abort ( 404 )
2019-11-18 22:42:56 +01:00
ip = request . headers . get ( " X-Forwarded-For " ) or request . remote_addr
2020-07-16 15:35:12 +02:00
if ip is not None and not is_user_bot ( ) :
2019-11-18 22:42:56 +01:00
key = make_download_key ( ip , release . package )
if not has_key ( key ) :
set_key ( key , " true " )
2019-11-21 23:16:35 +01:00
bonus = 1
2019-11-18 22:42:56 +01:00
PackageRelease . query . filter_by ( id = release . id ) . update ( {
" downloads " : PackageRelease . downloads + 1
} )
2019-11-21 23:16:35 +01:00
Package . query . filter_by ( id = package . id ) . update ( {
2020-07-09 02:11:50 +02:00
" downloads " : Package . downloads + 1 ,
2020-07-09 02:26:01 +02:00
" score_downloads " : Package . score_downloads + bonus ,
2019-11-21 23:16:35 +01:00
" score " : Package . score + bonus
} )
2019-11-18 22:42:56 +01:00
db . session . commit ( )
2019-01-29 01:43:55 +01:00
2019-11-18 22:42:56 +01:00
return redirect ( release . url , code = 300 )
2018-07-28 19:33:36 +02:00
2020-07-14 04:49:30 +02:00
2019-11-16 00:51:42 +01:00
@bp.route ( " /packages/<author>/<name>/releases/<id>/ " , methods = [ " GET " , " POST " ] )
2018-05-25 18:28:32 +02:00
@login_required
@is_package_page
2019-11-16 00:51:42 +01:00
def edit_release ( package , id ) :
2021-01-30 02:19:43 +01:00
release : PackageRelease = PackageRelease . query . get ( id )
2018-05-25 18:28:32 +02:00
if release is None or release . package != package :
abort ( 404 )
canEdit = package . checkPerm ( current_user , Permission . MAKE_RELEASE )
canApprove = package . checkPerm ( current_user , Permission . APPROVE_RELEASE )
if not ( canEdit or canApprove ) :
return redirect ( package . getDetailsURL ( ) )
# Initial form class from post data and default data
form = EditPackageReleaseForm ( formdata = request . form , obj = release )
2020-01-19 21:01:06 +01:00
if request . method == " GET " :
2020-07-10 23:19:47 +02:00
# HACK: fix bug in wtforms
2020-01-19 21:01:06 +01:00
form . approved . data = release . approved
2020-12-05 00:07:19 +01:00
if form . validate_on_submit ( ) :
2018-05-25 18:28:32 +02:00
if canEdit :
release . title = form [ " title " ] . data
2019-01-28 21:48:07 +01:00
release . min_rel = form [ " min_rel " ] . data . getActual ( )
release . max_rel = form [ " max_rel " ] . data . getActual ( )
2018-05-25 18:28:32 +02:00
if package . checkPerm ( current_user , Permission . CHANGE_RELEASE_URL ) :
release . url = form [ " url " ] . data
release . task_id = form [ " task_id " ] . data
2019-08-31 19:32:59 +02:00
if release . task_id is not None :
2018-05-25 18:28:32 +02:00
release . task_id = None
2021-01-30 02:19:43 +01:00
if form . approved . data :
release . approve ( current_user )
elif canApprove :
release . approved = False
2018-05-25 18:28:32 +02:00
db . session . commit ( )
return redirect ( package . getDetailsURL ( ) )
return render_template ( " packages/release_edit.html " , package = package , release = release , form = form )
2019-01-28 22:49:29 +01:00
class BulkReleaseForm ( FlaskForm ) :
set_min = BooleanField ( " Set Min " )
min_rel = QuerySelectField ( " Minimum Minetest Version " , [ InputRequired ( ) ] ,
2019-01-28 23:28:47 +01:00
query_factory = lambda : get_mt_releases ( False ) , get_pk = lambda a : a . id , get_label = lambda a : a . name )
2019-01-28 22:49:29 +01:00
set_max = BooleanField ( " Set Max " )
max_rel = QuerySelectField ( " Maximum Minetest Version " , [ InputRequired ( ) ] ,
2019-01-28 23:28:47 +01:00
query_factory = lambda : get_mt_releases ( True ) , get_pk = lambda a : a . id , get_label = lambda a : a . name )
2019-01-29 01:24:59 +01:00
only_change_none = BooleanField ( " Only change values previously set as none " )
2019-01-28 22:49:29 +01:00
submit = SubmitField ( " Update " )
2019-11-16 00:51:42 +01:00
@bp.route ( " /packages/<author>/<name>/releases/bulk_change/ " , methods = [ " GET " , " POST " ] )
2019-01-28 22:49:29 +01:00
@login_required
@is_package_page
2019-11-16 00:51:42 +01:00
def bulk_change_release ( package ) :
2019-01-28 22:49:29 +01:00
if not package . checkPerm ( current_user , Permission . MAKE_RELEASE ) :
return redirect ( package . getDetailsURL ( ) )
# Initial form class from post data and default data
form = BulkReleaseForm ( )
2019-01-29 01:24:59 +01:00
if request . method == " GET " :
form . only_change_none . data = True
2020-12-05 00:07:19 +01:00
elif form . validate_on_submit ( ) :
2019-01-29 01:24:59 +01:00
only_change_none = form . only_change_none . data
2019-01-28 22:49:29 +01:00
for release in package . releases . all ( ) :
2019-01-29 01:24:59 +01:00
if form [ " set_min " ] . data and ( not only_change_none or release . min_rel is None ) :
2019-01-28 22:49:29 +01:00
release . min_rel = form [ " min_rel " ] . data . getActual ( )
2019-01-29 01:24:59 +01:00
if form [ " set_max " ] . data and ( not only_change_none or release . max_rel is None ) :
2019-01-28 22:49:29 +01:00
release . max_rel = form [ " max_rel " ] . data . getActual ( )
db . session . commit ( )
return redirect ( package . getDetailsURL ( ) )
return render_template ( " packages/release_bulk_change.html " , package = package , form = form )
2020-01-19 01:02:33 +01:00
@bp.route ( " /packages/<author>/<name>/releases/<id>/delete/ " , methods = [ " POST " ] )
@login_required
@is_package_page
def delete_release ( package , id ) :
release = PackageRelease . query . get ( id )
if release is None or release . package != package :
abort ( 404 )
if not release . checkPerm ( current_user , Permission . DELETE_RELEASE ) :
return redirect ( release . getEditURL ( ) )
db . session . delete ( release )
db . session . commit ( )
return redirect ( package . getDetailsURL ( ) )
2020-12-15 20:05:29 +01:00
class PackageUpdateConfigFrom ( FlaskForm ) :
2021-01-30 16:41:55 +01:00
trigger = RadioField ( " Trigger " , [ InputRequired ( ) ] , choices = PackageUpdateTrigger . choices ( ) , coerce = PackageUpdateTrigger . coerce ,
2021-01-29 23:54:14 +01:00
default = PackageUpdateTrigger . TAG )
2020-12-15 23:08:10 +01:00
ref = StringField ( " Branch name " , [ Optional ( ) ] , default = None )
2021-01-30 16:41:55 +01:00
action = RadioField ( " Action " , [ InputRequired ( ) ] , choices = [ ( " notification " , " Send notification and mark as outdated " ) , ( " make_release " , " Create release " ) ] , default = " make_release " )
2020-12-15 22:22:17 +01:00
submit = SubmitField ( " Save Settings " )
2020-12-15 23:51:06 +01:00
disable = SubmitField ( " Disable Automation " )
2020-12-15 20:05:29 +01:00
@bp.route ( " /packages/<author>/<name>/update-config/ " , methods = [ " GET " , " POST " ] )
@login_required
@is_package_page
def update_config ( package ) :
if not package . checkPerm ( current_user , Permission . MAKE_RELEASE ) :
2020-12-15 22:22:17 +01:00
abort ( 403 )
if not package . repo :
flash ( " Please add a Git repository URL in order to set up automatic releases " , " danger " )
return redirect ( package . getEditURL ( ) )
2020-12-15 20:05:29 +01:00
form = PackageUpdateConfigFrom ( obj = package . update_config )
2021-01-29 23:54:14 +01:00
if request . method == " GET " :
if package . update_config :
form . action . data = " make_release " if package . update_config . make_release else " notification "
elif request . args . get ( " action " ) == " notification " :
form . trigger . data = PackageUpdateTrigger . COMMIT
form . action . data = " notification "
2020-12-15 22:22:17 +01:00
2020-12-15 20:05:29 +01:00
if form . validate_on_submit ( ) :
2020-12-15 22:22:17 +01:00
if form . disable . data :
flash ( " Deleted update configuration " , " success " )
if package . update_config :
db . session . delete ( package . update_config )
2021-01-29 23:20:44 +01:00
db . session . commit ( )
2020-12-15 22:22:17 +01:00
else :
if package . update_config is None :
package . update_config = PackageUpdateConfig ( )
db . session . add ( package . update_config )
2020-12-15 20:05:29 +01:00
2020-12-15 22:22:17 +01:00
form . populate_obj ( package . update_config )
2020-12-15 23:08:10 +01:00
package . update_config . ref = nonEmptyOrNone ( form . ref . data )
2020-12-15 22:22:17 +01:00
package . update_config . make_release = form . action . data == " make_release "
2021-01-29 23:20:44 +01:00
if package . update_config . last_commit is None :
last_release = package . releases . first ( )
if last_release and last_release . commit_hash :
package . update_config . last_commit = last_release . commit_hash
2021-01-30 02:07:00 +01:00
package . update_config . outdated_at = None
2021-01-30 00:18:37 +01:00
package . update_config . auto_created = False
2021-01-29 23:20:44 +01:00
db . session . commit ( )
if package . update_config . last_commit is None :
check_update_config . delay ( package . id )
2020-12-15 20:05:29 +01:00
2020-12-15 22:22:17 +01:00
if not form . disable . data and package . releases . count ( ) == 0 :
flash ( " Now, please create an initial release " , " success " )
return redirect ( package . getCreateReleaseURL ( ) )
2020-12-15 20:05:29 +01:00
return redirect ( package . getDetailsURL ( ) )
return render_template ( " packages/update_config.html " , package = package , form = form )
2020-12-15 22:22:17 +01:00
@bp.route ( " /packages/<author>/<name>/setup-releases/ " )
@login_required
@is_package_page
def setup_releases ( package ) :
if not package . checkPerm ( current_user , Permission . MAKE_RELEASE ) :
abort ( 403 )
if package . update_config :
return redirect ( package . getUpdateConfigURL ( ) )
return render_template ( " packages/release_wizard.html " , package = package )
2021-01-30 02:07:00 +01:00
@bp.route ( " /users/<username>/update-configs/ " )
@login_required
def bulk_update_config ( 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 )
confs = user . maintained_packages \
. filter ( Package . state != PackageState . DELETED ,
Package . update_config . has ( ) ) \
. order_by ( db . asc ( Package . title ) ) . all ( )
return render_template ( " packages/bulk_update_conf.html " , user = user , confs = confs )