mirror of
https://github.com/jiojciojsioe3/a3cjroijsiojiorj.git
synced 2024-12-22 17:26:13 +08:00
implement song addition/deletion
This commit is contained in:
parent
c63b5eba4b
commit
5a68978ec4
3
.gitignore
vendored
3
.gitignore
vendored
@ -49,6 +49,5 @@ public/api
|
||||
taiko.db
|
||||
version.json
|
||||
public/index.html
|
||||
config.json
|
||||
config.py
|
||||
public/assets/song_skins
|
||||
secret.txt
|
||||
|
144
app.py
144
app.py
@ -1,6 +1,7 @@
|
||||
#!/usr/bin/env python3
|
||||
|
||||
import bcrypt
|
||||
import config
|
||||
import json
|
||||
import re
|
||||
import schema
|
||||
@ -14,27 +15,18 @@ from ffmpy import FFmpeg
|
||||
from pymongo import MongoClient
|
||||
|
||||
app = Flask(__name__)
|
||||
client = MongoClient()
|
||||
|
||||
try:
|
||||
app.secret_key = open('secret.txt').read().strip()
|
||||
except FileNotFoundError:
|
||||
app.secret_key = os.urandom(24).hex()
|
||||
with open('secret.txt', 'w') as fp:
|
||||
fp.write(app.secret_key)
|
||||
fp.close()
|
||||
client = MongoClient(host=config.MONGO['host'])
|
||||
|
||||
app.secret_key = config.SECRET_KEY
|
||||
app.config['SESSION_TYPE'] = 'redis'
|
||||
app.config['SESSION_COOKIE_HTTPONLY'] = False
|
||||
app.cache = Cache(app, config={'CACHE_TYPE': 'redis'})
|
||||
app.cache = Cache(app, config=config.REDIS)
|
||||
sess = Session()
|
||||
sess.init_app(app)
|
||||
|
||||
db = client.taiko
|
||||
db = client[config.MONGO['database']]
|
||||
db.users.create_index('username', unique=True)
|
||||
|
||||
DEFAULT_URL = 'https://github.com/bui/taiko-web/'
|
||||
|
||||
db.songs.create_index('id', unique=True)
|
||||
|
||||
def api_error(message):
|
||||
return jsonify({'status': 'error', 'message': message})
|
||||
@ -49,17 +41,19 @@ def login_required(f):
|
||||
return decorated_function
|
||||
|
||||
|
||||
def admin_required(f):
|
||||
@wraps(f)
|
||||
def decorated_function(*args, **kwargs):
|
||||
if not session.get('username'):
|
||||
return abort(403)
|
||||
def admin_required(level):
|
||||
def decorated_function(f):
|
||||
@wraps(f)
|
||||
def wrapper(*args, **kwargs):
|
||||
if not session.get('username'):
|
||||
return abort(403)
|
||||
|
||||
user = db.users.find_one({'username': session.get('username')})
|
||||
if user['user_level'] < level:
|
||||
return abort(403)
|
||||
|
||||
user = db.users.find_one({'username': session.get('username')})
|
||||
if user['user_level'] < 50:
|
||||
return abort(403)
|
||||
|
||||
return f(*args, **kwargs)
|
||||
return f(*args, **kwargs)
|
||||
return wrapper
|
||||
return decorated_function
|
||||
|
||||
|
||||
@ -71,27 +65,24 @@ def before_request_func():
|
||||
|
||||
|
||||
def get_config():
|
||||
if os.path.isfile('config.json'):
|
||||
try:
|
||||
config = json.load(open('config.json', 'r'))
|
||||
except ValueError:
|
||||
print('WARNING: Invalid config.json, using default values')
|
||||
config = {}
|
||||
else:
|
||||
print('WARNING: No config.json found, using default values')
|
||||
config = {}
|
||||
config_out = {
|
||||
'songs_baseurl': config.SONGS_BASEURL,
|
||||
'assets_baseurl': config.ASSETS_BASEURL,
|
||||
'email': config.EMAIL,
|
||||
'accounts': config.ACCOUNTS
|
||||
}
|
||||
|
||||
if not config.get('songs_baseurl'):
|
||||
config['songs_baseurl'] = ''.join([request.host_url, 'songs']) + '/'
|
||||
if not config.get('assets_baseurl'):
|
||||
config['assets_baseurl'] = ''.join([request.host_url, 'assets']) + '/'
|
||||
if not config_out.get('songs_baseurl'):
|
||||
config_out['songs_baseurl'] = ''.join([request.host_url, 'songs']) + '/'
|
||||
if not config_out.get('assets_baseurl'):
|
||||
config_out['assets_baseurl'] = ''.join([request.host_url, 'assets']) + '/'
|
||||
|
||||
config['_version'] = get_version()
|
||||
return config
|
||||
config_out['_version'] = get_version()
|
||||
return config_out
|
||||
|
||||
|
||||
def get_version():
|
||||
version = {'commit': None, 'commit_short': '', 'version': None, 'url': DEFAULT_URL}
|
||||
version = {'commit': None, 'commit_short': '', 'version': None, 'url': config.URL}
|
||||
if os.path.isfile('version.json'):
|
||||
try:
|
||||
ver = json.load(open('version.json', 'r'))
|
||||
@ -114,20 +105,21 @@ def route_index():
|
||||
|
||||
|
||||
@app.route('/admin')
|
||||
@admin_required
|
||||
@admin_required(level=50)
|
||||
def route_admin():
|
||||
return redirect('/admin/songs')
|
||||
|
||||
|
||||
@app.route('/admin/songs')
|
||||
@admin_required
|
||||
@admin_required(level=50)
|
||||
def route_admin_songs():
|
||||
songs = db.songs.find({})
|
||||
return render_template('admin_songs.html', songs=list(songs))
|
||||
user = db.users.find_one({'username': session['username']})
|
||||
return render_template('admin_songs.html', songs=list(songs), admin=user)
|
||||
|
||||
|
||||
@app.route('/admin/songs/<int:id>')
|
||||
@admin_required
|
||||
@admin_required(level=50)
|
||||
def route_admin_songs_id(id):
|
||||
song = db.songs.find_one({'id': id})
|
||||
if not song:
|
||||
@ -142,8 +134,58 @@ def route_admin_songs_id(id):
|
||||
song=song, categories=categories, song_skins=song_skins, makers=makers, admin=user)
|
||||
|
||||
|
||||
@app.route('/admin/songs/new')
|
||||
@admin_required(level=100)
|
||||
def route_admin_songs_new():
|
||||
categories = list(db.categories.find({}))
|
||||
song_skins = list(db.song_skins.find({}))
|
||||
makers = list(db.makers.find({}))
|
||||
|
||||
return render_template('admin_song_new.html', categories=categories, song_skins=song_skins, makers=makers)
|
||||
|
||||
|
||||
@app.route('/admin/songs/new', methods=['POST'])
|
||||
@admin_required(level=100)
|
||||
def route_admin_songs_new_post():
|
||||
output = {'title_lang': {}, 'subtitle_lang': {}, 'courses': {}}
|
||||
output['enabled'] = True if request.form.get('enabled') else False
|
||||
output['title'] = request.form.get('title') or None
|
||||
output['subtitle'] = request.form.get('subtitle') or None
|
||||
for lang in ['ja', 'en', 'cn', 'tw', 'ko']:
|
||||
output['title_lang'][lang] = request.form.get('title_%s' % lang) or None
|
||||
output['subtitle_lang'][lang] = request.form.get('subtitle_%s' % lang) or None
|
||||
|
||||
for course in ['easy', 'normal', 'hard', 'oni', 'ura']:
|
||||
if request.form.get('course_%s' % course):
|
||||
output['courses'][course] = {'stars': int(request.form.get('course_%s' % course)),
|
||||
'branch': True if request.form.get('branch_%s' % course) else False}
|
||||
else:
|
||||
output['courses'][course] = None
|
||||
|
||||
output['category_id'] = int(request.form.get('category_id')) or None
|
||||
output['type'] = request.form.get('type')
|
||||
output['offset'] = float(request.form.get('offset')) or None
|
||||
output['skin_id'] = int(request.form.get('skin_id')) or None
|
||||
output['preview'] = float(request.form.get('preview')) or None
|
||||
output['volume'] = float(request.form.get('volume')) or None
|
||||
output['maker_id'] = int(request.form.get('maker_id')) or None
|
||||
output['hash'] = None
|
||||
|
||||
seq = db.seq.find_one({'name': 'songs'})
|
||||
seq_new = seq['value'] + 1 if seq else 1
|
||||
output['id'] = seq_new
|
||||
output['order'] = seq_new
|
||||
|
||||
db.songs.insert_one(output)
|
||||
flash('Song created.')
|
||||
|
||||
db.seq.update_one({'name': 'songs'}, {'$set': {'value': seq_new}}, upsert=True)
|
||||
|
||||
return redirect('/admin/songs/%s' % str(seq_new))
|
||||
|
||||
|
||||
@app.route('/admin/songs/<int:id>', methods=['POST'])
|
||||
@admin_required
|
||||
@admin_required(level=100)
|
||||
def route_admin_songs_id_post(id):
|
||||
song = db.songs.find_one({'id': id})
|
||||
if not song:
|
||||
@ -183,6 +225,18 @@ def route_admin_songs_id_post(id):
|
||||
return redirect('/admin/songs/%s' % id)
|
||||
|
||||
|
||||
@app.route('/admin/songs/<int:id>/delete', methods=['POST'])
|
||||
@admin_required(level=100)
|
||||
def route_admin_songs_id_delete(id):
|
||||
song = db.songs.find_one({'id': id})
|
||||
if not song:
|
||||
return abort(404)
|
||||
|
||||
db.songs.delete_one({'id': id})
|
||||
flash('Song deleted.')
|
||||
return redirect('/admin/songs')
|
||||
|
||||
|
||||
@app.route('/api/preview')
|
||||
@app.cache.cached(timeout=15, query_string=True)
|
||||
def route_api_preview():
|
||||
|
@ -1,6 +0,0 @@
|
||||
{
|
||||
"songs_baseurl": "",
|
||||
"assets_baseurl": "",
|
||||
"email": "",
|
||||
"accounts": true
|
||||
}
|
32
config.example.py
Normal file
32
config.example.py
Normal file
@ -0,0 +1,32 @@
|
||||
# The full URL base asset URL, with trailing slash.
|
||||
ASSETS_BASEURL = ''
|
||||
|
||||
# The full URL base song URL, with trailing slash.
|
||||
SONGS_BASEURL = ''
|
||||
|
||||
# The email address to display in the "About Simulator" menu.
|
||||
EMAIL = 'taiko@example.com'
|
||||
|
||||
# Whether to use the user account system.
|
||||
ACCOUNTS = True
|
||||
|
||||
# MongoDB server settings.
|
||||
MONGO = {
|
||||
'host': ['localhost:27017'],
|
||||
'database': 'taiko'
|
||||
}
|
||||
|
||||
# Redis server settings, used for sessions + cache.
|
||||
REDIS = {
|
||||
'CACHE_TYPE': 'redis',
|
||||
'CACHE_REDIS_HOST': '127.0.0.1',
|
||||
'CACHE_REDIS_PORT': 6379,
|
||||
'CACHE_REDIS_PASSWORD': None,
|
||||
'CACHE_REDIS_DB': None
|
||||
}
|
||||
|
||||
# Secret key used for sessions.
|
||||
SECRET_KEY = 'change-me'
|
||||
|
||||
# Git repository base URL.
|
||||
URL = 'https://github.com/bui/taiko-web/'
|
@ -130,3 +130,23 @@ h1 small {
|
||||
margin-bottom: 10px;
|
||||
color: white;
|
||||
}
|
||||
|
||||
.save-song {
|
||||
font-size: 22pt;
|
||||
width: 120px;
|
||||
}
|
||||
|
||||
.delete-song button {
|
||||
float: right;
|
||||
margin-top: -25px;
|
||||
font-size: 12pt;
|
||||
}
|
||||
|
||||
.side-button {
|
||||
float: right;
|
||||
background: green;
|
||||
padding: 5px 20px;
|
||||
color: white;
|
||||
text-decoration: none;
|
||||
margin-top: 25px;
|
||||
}
|
||||
|
@ -46,19 +46,19 @@
|
||||
<div class="form-field">
|
||||
<p>Courses</p>
|
||||
<label for="course_easy">Easy</label>
|
||||
<input type="number" id="course_easy" value="{{song.courses.easy.stars}}" name="course_easy" min="1" max="10">
|
||||
<input type="number" id="course_easy" value="{{song.courses.easy.stars}}" name="course_easy" min="0" max="10">
|
||||
<span class="checkbox"><input type="checkbox" name="branch_easy" id="branch_easy"{% if song.courses.easy.branch %} checked{% endif %}><label for="branch_easy"> Diverge Notes</label></span>
|
||||
<label for="course_normal">Normal</label>
|
||||
<input type="number" id="course_normal" value="{{song.courses.normal.stars}}" name="course_normal" min="1" max="10">
|
||||
<input type="number" id="course_normal" value="{{song.courses.normal.stars}}" name="course_normal" min="0" max="10">
|
||||
<span class="checkbox"><input type="checkbox" name="branch_normal" id="branch_normal"{% if song.courses.normal.branch %} checked{% endif %}><label for="branch_normal"> Diverge Notes</label></span>
|
||||
<label for="course_hard">Hard</label>
|
||||
<input type="number" id="course_hard" value="{{song.courses.hard.stars}}" name="course_hard" min="1" max="10">
|
||||
<input type="number" id="course_hard" value="{{song.courses.hard.stars}}" name="course_hard" min="0" max="10">
|
||||
<span class="checkbox"><input type="checkbox" name="branch_hard" id="branch_hard"{% if song.courses.hard.branch %} checked{% endif %}><label for="branch_hard"> Diverge Notes</label></span>
|
||||
<label for="course_oni">Oni</label>
|
||||
<input type="number" id="course_oni" value="{{song.courses.oni.stars}}" name="course_oni" min="1" max="10">
|
||||
<input type="number" id="course_oni" value="{{song.courses.oni.stars}}" name="course_oni" min="0" max="10">
|
||||
<span class="checkbox"><input type="checkbox" name="branch_oni" id="branch_oni"{% if song.courses.oni.branch %} checked{% endif %}><label for="branch_oni"> Diverge Notes</label></span>
|
||||
<label for="course_ura">Ura</label>
|
||||
<input type="number" id="course_ura" value="{{song.courses.ura.stars}}" name="course_ura" min="1" max="10">
|
||||
<input type="number" id="course_ura" value="{{song.courses.ura.stars}}" name="course_ura" min="0" max="10">
|
||||
<span class="checkbox"><input type="checkbox" name="branch_ura" id="branch_ura"{% if song.courses.ura.branch %} checked{% endif %}><label for="branch_ura"> Diverge Notes</label></span>
|
||||
</div>
|
||||
|
||||
@ -115,7 +115,12 @@
|
||||
</select>
|
||||
</div>
|
||||
|
||||
<button type="submit">Save</button>
|
||||
<button type="submit" class="save-song">Save</button>
|
||||
</form>
|
||||
{% if admin.user_level >= 100 %}
|
||||
<form class="delete-song" method="post" action="/admin/songs/{{song.id}}/delete" onsubmit="return confirm('Are you sure you wish to delete this song?');">
|
||||
<button type="submit">Delete song</button>
|
||||
</form>
|
||||
{% endif %}
|
||||
</div>
|
||||
{% endblock %}
|
||||
|
121
templates/admin_song_new.html
Normal file
121
templates/admin_song_new.html
Normal file
@ -0,0 +1,121 @@
|
||||
{% extends 'admin.html' %}
|
||||
{% block content %}
|
||||
<h1>New song</h1>
|
||||
{% for message in get_flashed_messages() %}
|
||||
<div class="message">{{ message }}</div>
|
||||
{% endfor %}
|
||||
<div class="song-form">
|
||||
<form method="post">
|
||||
|
||||
<div class="form-field">
|
||||
<span class="checkbox"><input type="checkbox" name="enabled" id="enabled" checked><label for="enabled"> Enabled</label></span>
|
||||
</div>
|
||||
|
||||
<div class="form-field">
|
||||
<p>Title</p>
|
||||
<label for="title">Original</label>
|
||||
<input type="text" id="title" value="" name="title" required>
|
||||
<label for="title_ja">Japanese</label>
|
||||
<input type="text" id="title_ja" value="" name="title_ja">
|
||||
<label for="title_en">English</label>
|
||||
<input type="text" id="title_en" value="" name="title_en">
|
||||
<label for="title_cn">Chinese (Simplified)</label>
|
||||
<input type="text" id="title_cn" value="" name="title_cn">
|
||||
<label for="title_tw">Chinese (Traditional)</label>
|
||||
<input type="text" id="title_tw" value="" name="title_tw">
|
||||
<label for="title_ko">Korean</label>
|
||||
<input type="text" id="title_ko" value="" name="title_ko">
|
||||
</div>
|
||||
|
||||
<div class="form-field">
|
||||
<p>Subtitle</p>
|
||||
<label for="subtitle">Original</label>
|
||||
<input type="text" id="subtitle" value="" name="subtitle">
|
||||
<label for="subtitle_ja">Japanese</label>
|
||||
<input type="text" id="subtitle_ja" value="" name="subtitle_ja">
|
||||
<label for="subtitle_en">English</label>
|
||||
<input type="text" id="subtitle_en" value="" name="subtitle_en">
|
||||
<label for="subtitle_cn">Chinese (Simplified)</label>
|
||||
<input type="text" id="subtitle_cn" value="" name="subtitle_cn">
|
||||
<label for="subtitle_tw">Chinese (Traditional)</label>
|
||||
<input type="text" id="subtitle_tw" value="" name="subtitle_tw">
|
||||
<label for="subtitle_ko">Korean</label>
|
||||
<input type="text" id="subtitle_ko" value="" name="subtitle_ko">
|
||||
</div>
|
||||
|
||||
<div class="form-field">
|
||||
<p>Courses</p>
|
||||
<label for="course_easy">Easy</label>
|
||||
<input type="number" id="course_easy" value="" name="course_easy" min="0" max="10">
|
||||
<span class="checkbox"><input type="checkbox" name="branch_easy" id="branch_easy"><label for="branch_easy"> Diverge Notes</label></span>
|
||||
<label for="course_normal">Normal</label>
|
||||
<input type="number" id="course_normal" value="" name="course_normal" min="0" max="10">
|
||||
<span class="checkbox"><input type="checkbox" name="branch_normal" id="branch_normal"><label for="branch_normal"> Diverge Notes</label></span>
|
||||
<label for="course_hard">Hard</label>
|
||||
<input type="number" id="course_hard" value="" name="course_hard" min="0" max="10">
|
||||
<span class="checkbox"><input type="checkbox" name="branch_hard" id="branch_hard"><label for="branch_hard"> Diverge Notes</label></span>
|
||||
<label for="course_oni">Oni</label>
|
||||
<input type="number" id="course_oni" value="" name="course_oni" min="0" max="10">
|
||||
<span class="checkbox"><input type="checkbox" name="branch_oni" id="branch_oni"><label for="branch_oni"> Diverge Notes</label></span>
|
||||
<label for="course_ura">Ura</label>
|
||||
<input type="number" id="course_ura" value="" name="course_ura" min="0" max="10">
|
||||
<span class="checkbox"><input type="checkbox" name="branch_ura" id="branch_ura"><label for="branch_ura"> Diverge Notes</label></span>
|
||||
</div>
|
||||
|
||||
<div class="form-field">
|
||||
<p><label for="category_id">Category</label></p>
|
||||
<select name="category_id" id="category_id">
|
||||
<option value="0">(none)</option>
|
||||
{% for category in categories %}
|
||||
<option value="{{ category.id }}">{{ category.title }}</option>
|
||||
{% endfor %}
|
||||
</select>
|
||||
</div>
|
||||
|
||||
<div class="form-field">
|
||||
<p><label for="type">Type</label></p>
|
||||
<select name="type" id="type">
|
||||
<option value="tja">TJA</option>
|
||||
<option value="osu">osu!taiko</option>
|
||||
</select>
|
||||
</div>
|
||||
|
||||
<div class="form-field">
|
||||
<p><label for="offset">Offset</label></p>
|
||||
<input type="text" id="offset" value="" name="offset" required>
|
||||
</div>
|
||||
|
||||
<div class="form-field">
|
||||
<p><label for="skin_id">Skin</label></p>
|
||||
<select name="skin_id" id="skin_id">
|
||||
<option value="0">(none)</option>
|
||||
{% for skin in song_skins %}
|
||||
<option value="{{ skin.id }}">{{ skin.name }}</option>
|
||||
{% endfor %}
|
||||
</select>
|
||||
</div>
|
||||
|
||||
<div class="form-field">
|
||||
<p><label for="preview">Preview</label></p>
|
||||
<input type="text" id="preview" value="" name="preview" required>
|
||||
</div>
|
||||
|
||||
<div class="form-field">
|
||||
<p><label for="volume">Volume</label></p>
|
||||
<input type="text" id="volume" value="" name="volume" required>
|
||||
</div>
|
||||
|
||||
<div class="form-field">
|
||||
<p><label for="maker_id">Maker</label></p>
|
||||
<select name="maker_id" id="maker_id">
|
||||
<option value="0">(none)</option>
|
||||
{% for maker in makers %}
|
||||
<option value="{{ maker.id }}">{{ maker.name }}</option>
|
||||
{% endfor %}
|
||||
</select>
|
||||
</div>
|
||||
|
||||
<button type="submit" class="save-song">Save</button>
|
||||
</form>
|
||||
</div>
|
||||
{% endblock %}
|
@ -1,6 +1,12 @@
|
||||
{% extends 'admin.html' %}
|
||||
{% block content %}
|
||||
{% if admin.user_level >= 100 %}
|
||||
<a href="/admin/songs/new" class="side-button">New song</a>
|
||||
{% endif %}
|
||||
<h1>Songs</h1>
|
||||
{% for message in get_flashed_messages() %}
|
||||
<div class="message">{{ message }}</div>
|
||||
{% endfor %}
|
||||
{% for song in songs %}
|
||||
<a href="/admin/songs/{{ song.id }}" class="song-link">
|
||||
<div class="song">
|
||||
|
@ -4,24 +4,30 @@
|
||||
import sqlite3
|
||||
from pymongo import MongoClient
|
||||
|
||||
client = MongoClient()
|
||||
client.drop_database('taiko')
|
||||
db = client.taiko
|
||||
import os,sys,inspect
|
||||
current_dir = os.path.dirname(os.path.abspath(inspect.getfile(inspect.currentframe())))
|
||||
parent_dir = os.path.dirname(current_dir)
|
||||
sys.path.insert(0, parent_dir)
|
||||
import config
|
||||
|
||||
client = MongoClient(config.MONGO['host'])
|
||||
client.drop_database(config.MONGO['database'])
|
||||
db = client[config.MONGO['database']]
|
||||
sqdb = sqlite3.connect('taiko.db')
|
||||
sqdb.row_factory = sqlite3.Row
|
||||
curs = sqdb.cursor()
|
||||
|
||||
def migrate_songs():
|
||||
curs.execute('select * from songs')
|
||||
curs.execute('select * from songs order by id')
|
||||
rows = curs.fetchall()
|
||||
|
||||
for row in rows:
|
||||
song = {
|
||||
'id': row['id'],
|
||||
'title': row['title'],
|
||||
'title_lang': {'ja': row['title']},
|
||||
'title_lang': {'ja': row['title'], 'en': None, 'cn': None, 'tw': None, 'ko': None},
|
||||
'subtitle': row['subtitle'],
|
||||
'subtitle_lang': {'ja': row['subtitle']},
|
||||
'subtitle_lang': {'ja': row['subtitle'], 'en': None, 'cn': None, 'tw': None, 'ko': None},
|
||||
'courses': {'easy': None, 'normal': None, 'hard': None, 'oni': None, 'ura': None},
|
||||
'enabled': True if row['enabled'] else False,
|
||||
'category_id': row['category'],
|
||||
@ -63,6 +69,9 @@ def migrate_songs():
|
||||
song['subtitle_lang']['en'] = lang
|
||||
|
||||
db.songs.insert_one(song)
|
||||
last_song = song['id']
|
||||
|
||||
db.seq.insert_one({'name': 'songs', 'value': last_song})
|
||||
|
||||
def migrate_makers():
|
||||
curs.execute('select * from makers')
|
||||
|
Loading…
Reference in New Issue
Block a user