Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Featured Mods reorder Merge from Beta #506

Merged
merged 4 commits into from
Jan 4, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
20 changes: 11 additions & 9 deletions KerbalStuff/blueprints/anonymous.py
Original file line number Diff line number Diff line change
Expand Up @@ -144,10 +144,11 @@ def browse_top() -> str:

@anonymous.route("/browse/featured")
def browse_featured() -> str:
mods = Featured.query.order_by(Featured.created.desc())
mods, page, total_pages = paginate_query(mods)
mods = [f.mod for f in mods]
return render_template("browse-list.html", mods=mods, page=page, total_pages=total_pages,
featured = Featured.query.order_by(Featured.priority.desc())
featured, page, total_pages = paginate_query(featured)
mods = [f.mod for f in featured]
return render_template("browse-list.html", mods=mods, featured=featured,
page=page, total_pages=total_pages,
url="/browse/featured", name="Featured Mods", rss="/browse/featured.rss")


Expand Down Expand Up @@ -250,11 +251,12 @@ def singlegame_browse_top(gameshort: str) -> str:
@anonymous.route("/<gameshort>/browse/featured")
def singlegame_browse_featured(gameshort: str) -> str:
ga = get_game_info(short=gameshort)
mods = Featured.query.outerjoin(Mod).filter(
Mod.game_id == ga.id).order_by(Featured.created.desc())
mods, page, total_pages = paginate_query(mods)
mods = [f.mod for f in mods]
return render_template("browse-list.html", mods=mods, page=page, total_pages=total_pages, ga=ga,
featured = Featured.query.outerjoin(Mod)\
.filter(Mod.game_id == ga.id)\
.order_by(Featured.priority.desc())
featured, page, total_pages = paginate_query(featured)
mods = [f.mod for f in featured]
return render_template("browse-list.html", mods=mods, featured=featured, page=page, total_pages=total_pages, ga=ga,
url="/browse/featured", name="Featured Mods", rss="/browse/featured.rss")


Expand Down
2 changes: 1 addition & 1 deletion KerbalStuff/blueprints/api.py
Original file line number Diff line number Diff line change
Expand Up @@ -406,7 +406,7 @@ def browse_top() -> Iterable[Dict[str, Any]]:
@api.route("/api/browse/featured")
@json_output
def browse_featured() -> Iterable[Dict[str, Any]]:
mods = Featured.query.order_by(Featured.created.desc())
mods = Featured.query.order_by(Featured.priority.desc())
mods, page, total_pages = paginate_query(mods)
return serialize_mod_list((f.mod for f in mods))

Expand Down
61 changes: 57 additions & 4 deletions KerbalStuff/blueprints/mods.py
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@
from shutil import rmtree
from typing import Any, Dict, Tuple, Optional, Union

from sqlalchemy import func
import werkzeug.wrappers
import user_agents

Expand Down Expand Up @@ -477,9 +478,12 @@ def feature(mod_id: int) -> Dict[str, Any]:
mod, game = _get_mod_game_info(mod_id)
if any(Featured.query.filter(Featured.mod_id == mod_id).all()):
abort(409)
featured = Featured()
featured.mod = mod
db.add(featured)
max_prio = db.query(func.max(Featured.priority))\
.outerjoin(Mod)\
.filter(Mod.game_id == game.id)\
.scalar()
db.add(Featured(mod=mod,
priority=max_prio + 1 if max_prio is not None else 0))
return {"success": True}


Expand All @@ -488,14 +492,63 @@ def feature(mod_id: int) -> Dict[str, Any]:
@json_output
@with_session
def unfeature(mod_id: int) -> Dict[str, Any]:
_get_mod_game_info(mod_id)
_, game = _get_mod_game_info(mod_id)
featured = Featured.query.filter(Featured.mod_id == mod_id).first()
if not featured:
abort(404)
for other in Featured.query.outerjoin(Mod)\
.filter(Mod.game_id == game.id,
Featured.priority > featured.priority)\
.all():
other.priority -= 1
db.delete(featured)
return {"success": True}


@mods.route('/mod/<int:mod_id>/feature-down', methods=['POST'])
@adminrequired
@json_output
@with_session
def feature_down(mod_id: int) -> Dict[str, Any]:
_, game = _get_mod_game_info(mod_id)
featured = Featured.query.filter(Featured.mod_id == mod_id).first()
if not featured:
abort(404)
if featured.priority > 0:
other = Featured.query.outerjoin(Mod)\
.filter(Mod.game_id == game.id,
Featured.priority == featured.priority - 1)\
.first()
featured.priority -= 1
if other:
other.priority += 1
return {"success": True}


@mods.route('/mod/<int:mod_id>/feature-up', methods=['POST'])
@adminrequired
@json_output
@with_session
def feature_up(mod_id: int) -> Dict[str, Any]:
_, game = _get_mod_game_info(mod_id)
featured = Featured.query.filter(Featured.mod_id == mod_id).first()
if not featured:
abort(404)
max_prio = db.query(func.max(Featured.priority))\
.outerjoin(Mod)\
.filter(Mod.game_id == game.id)\
.scalar()
if featured.priority < max_prio:
other = Featured.query.outerjoin(Mod)\
.filter(Mod.game_id == game.id,
Featured.priority == featured.priority + 1)\
.first()
featured.priority += 1
if other:
other.priority -= 1
return {"success": True}


@mods.route('/mod/<int:mod_id>/<path:mod_name>/publish')
@with_session
@loginrequired
Expand Down
2 changes: 1 addition & 1 deletion KerbalStuff/common.py
Original file line number Diff line number Diff line change
Expand Up @@ -174,7 +174,7 @@ def get_paginated_mods(ga: Optional[Game] = None, query: str = '', page_size: in


def get_featured_mods(game_id: Optional[int], limit: int) -> List[Mod]:
mods = Featured.query.outerjoin(Mod).filter(Mod.published).order_by(Featured.created.desc())
mods = Featured.query.outerjoin(Mod).filter(Mod.published).order_by(Featured.priority.desc())
if game_id:
mods = mods.filter(Mod.game_id == game_id)
return mods.limit(limit).all()
Expand Down
4 changes: 3 additions & 1 deletion KerbalStuff/objects.py
Original file line number Diff line number Diff line change
Expand Up @@ -27,7 +27,8 @@ class Following(Base): # type: ignore
send_autoupdate = Column(Boolean(), default=True, nullable=False)

def __init__(self, mod: Optional['Mod'] = None, user: Optional['User'] = None,
send_update: Optional[bool] = True, send_autoupdate: Optional[bool] = True) -> None:
send_update: Optional[bool] = True,
send_autoupdate: Optional[bool] = True) -> None:
self.mod = mod
self.user = user
self.send_update = send_update
Expand All @@ -40,6 +41,7 @@ class Featured(Base): # type: ignore
mod_id = Column(Integer, ForeignKey('mod.id', ondelete='CASCADE'))
mod = relationship('Mod', backref=backref('featured', passive_deletes=True, order_by=id))
created = Column(DateTime, default=datetime.now, index=True)
priority = Column(Integer, nullable=False, index=True)

def __repr__(self) -> str:
return '<Featured %r>' % self.id
Expand Down
63 changes: 63 additions & 0 deletions alembic/versions/2024_01_02_14_56_29-f5a5d29ec765.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,63 @@
"""Add featured.priority

Revision ID: f5a5d29ec765
Revises: ba0c9afb6cb0
Create Date: 2024-01-02 20:57:00.647417

"""

# revision identifiers, used by Alembic.
revision = 'f5a5d29ec765'
down_revision = 'ba0c9afb6cb0'

from datetime import datetime
from alembic import op
import sqlalchemy as sa
from sqlalchemy.orm import relationship, backref

Base = sa.orm.declarative_base()

class Featured(Base): # type: ignore
__tablename__ = 'featured'
id = sa.Column(sa.Integer, primary_key=True)
mod_id = sa.Column(sa.Integer, sa.ForeignKey('mod.id', ondelete='CASCADE'))
mod = relationship('Mod', backref=backref('featured', passive_deletes=True, order_by=id))
created = sa.Column(sa.DateTime, default=datetime.now, index=True)
priority = sa.Column(sa.Integer, nullable=False, index=True)


class Mod(Base): # type: ignore
__tablename__ = 'mod'
id = sa.Column(sa.Integer, primary_key=True)
game_id = sa.Column(sa.Integer, sa.ForeignKey('game.id', ondelete='CASCADE'))
game = relationship('Game', backref=backref('mods', passive_deletes=True))


class Game(Base): # type: ignore
__tablename__ = 'game'
id = sa.Column(sa.Integer, primary_key=True)


def upgrade() -> None:
op.add_column('featured', sa.Column('priority', sa.Integer(), nullable=True))
op.create_index('ix_featured_priority', 'featured', [sa.text('priority DESC')], unique=False)

bind = op.get_bind()
session = sa.orm.Session(bind=bind)
prio = 0
game_id = None
for feature in session.query(Featured)\
.outerjoin(Mod)\
.order_by(Mod.game_id, Featured.created)\
.all():
prio = prio + 1 if game_id == feature.mod.game_id else 0
game_id = feature.mod.game_id
feature.priority = prio
session.commit()

op.alter_column('featured', 'priority', nullable=False)


def downgrade() -> None:
op.drop_index('ix_featured_priority', table_name='featured')
op.drop_column('featured', 'priority')
53 changes: 45 additions & 8 deletions frontend/coffee/global.coffee
Original file line number Diff line number Diff line change
Expand Up @@ -109,19 +109,56 @@ link.addEventListener('click', (e) ->
xhr = new XMLHttpRequest()
if e.target.classList.contains('feature-button')
xhr.open('POST', "/mod/#{e.target.dataset.mod}/feature")
xhr.setRequestHeader('Accept', 'application/json')
e.target.classList.remove('feature-button')
e.target.classList.add('unfeature-button')
e.target.textContent = 'Unfeature this mod'
else
xhr.open('POST', "/mod/#{e.target.dataset.mod}/unfeature")
xhr.setRequestHeader('Accept', 'application/json')
e.target.classList.remove('unfeature-button')
e.target.classList.add('feature-button')
e.target.textContent = 'Feature this mod'
xhr.setRequestHeader('Accept', 'application/json')
xhr.onload = () ->
response = JSON.parse this.responseText
if response.success
if e.target.classList.contains('feature-button')
e.target.classList.remove('feature-button')
e.target.classList.add('unfeature-button')
e.target.textContent = 'Unfeature this mod'
else
e.target.classList.remove('unfeature-button')
e.target.classList.add('feature-button')
e.target.textContent = 'Feature this mod'
else
$('#alert-error-text').text response.reason
$('#alert-error').removeClass 'hidden'
xhr.send()
, false) for link in document.querySelectorAll('.feature-button, .unfeature-button')

link.addEventListener('click', (e) ->
xhr = new XMLHttpRequest()
mod_id = e.target.dataset.mod
xhr.open 'POST', "/mod/#{mod_id}/feature-down"
xhr.setRequestHeader 'Accept', 'application/json'
xhr.onload = () ->
response = JSON.parse this.responseText
if response.success
window.location.reload()
else
$('#alert-error-text').text response.reason
$('#alert-error').removeClass 'hidden'
xhr.send()
, false) for link in document.querySelectorAll('.lower-feature-priority-button')

link.addEventListener('click', (e) ->
xhr = new XMLHttpRequest()
mod_id = e.target.dataset.mod
xhr.open 'POST', "/mod/#{mod_id}/feature-up"
xhr.setRequestHeader 'Accept', 'application/json'
xhr.onload = () ->
response = JSON.parse this.responseText
if response.success
window.location.reload()
else
$('#alert-error-text').text response.reason
$('#alert-error').removeClass 'hidden'
xhr.send()
, false) for link in document.querySelectorAll('.raise-feature-priority-button')

readCookie = (name) ->
nameEQ = name + "="
ca = document.cookie.split(';')
Expand Down
4 changes: 3 additions & 1 deletion frontend/styles/listing.scss
Original file line number Diff line number Diff line change
Expand Up @@ -79,7 +79,9 @@
border-radius: 0;
}

.follow-mod-button, .unfollow-mod-button, .download-link, .following-mod-indicator, .locked-mod-indicator {
.follow-mod-button, .unfollow-mod-button, .following-mod-indicator,
.download-link, .locked-mod-indicator,
.lower-feature-priority-button, .raise-feature-priority-button {
text-decoration: none;
font-size: 20px;
margin-top: -2px;
Expand Down
23 changes: 22 additions & 1 deletion generate_revision.sh
Original file line number Diff line number Diff line change
@@ -1,6 +1,21 @@
#!/usr/bin/env bash
set -e

case "$OSTYPE" in
linux*)
;;
msys | cygwin)
# Turn off db's volume mounting because we can't make the owners match,
# by mounting it at /var/lib/postgresql/data_dummy instead.
# The db will not persist between restarts, but that's better than
# failing to start and can still be used to investigate some issues.
echo 'Windows OS detected, disabling db volume mounting.'
echo 'NOTE: Database will NOT persist across restarts!'
echo
export DISABLE_DB_VOLUME=_dummy
;;
esac

docker-compose build backend

docker-compose up -d db
Expand All @@ -16,4 +31,10 @@ chmod g+rw alembic/versions/* &&
alembic history --verbose
"""

sudo chown "${USER}":"${USER}" alembic/versions/*
case "$OSTYPE" in
linux*)
sudo chown "${USER}":"${USER}" alembic/versions/*
;;
msys | cygwin)
;;
esac
13 changes: 10 additions & 3 deletions templates/browse-list.html
Original file line number Diff line number Diff line change
Expand Up @@ -47,9 +47,16 @@ <h3>{{ name }}</h3>
<p>Nothing to see here. If you're looking for a specific mod, why not ask the modder to upload it here?</p>
{% endif %}
<div class="row">
{% for mod in mods %}
{% include "mod-box.html" %}
{% endfor %}
{% if featured %}
{% for feature in featured %}
{% set mod = feature.mod %}
{% include "mod-box.html" %}
{% endfor %}
{% else %}
{% for mod in mods %}
{% include "mod-box.html" %}
{% endfor %}
{% endif %}
</div>
{%- if total_pages > 1 -%}
<div style="margin-top: 5mm" class="row vertical-centered" style="margin-bottom:2.5mm;">
Expand Down
6 changes: 4 additions & 2 deletions templates/game.html
Original file line number Diff line number Diff line change
Expand Up @@ -48,6 +48,7 @@ <h3>Followed Mods <small>Recently updated mods you follow</small></h3>
</div>
{% endif %}

{% if featured %}
<div class="well" style="margin-bottom: 0;">
<div class="container main-cat">
<a href="{% if ga %}/{{ ga.short }}{% endif %}/browse/featured" class="btn btn-primary pull-right">
Expand All @@ -60,11 +61,12 @@ <h3>Featured Mods <small>Hand-picked by {{ site_name }} admins</small></h3>
<div class="container">
<div class="row">
{% for feature in featured[:6] %}
{% set mod = feature.mod %}
{% include "mod-box.html" %}
{% set mod = feature.mod %}
{% include "mod-box.html" %}
{% endfor %}
</div>
</div>
{% endif %}
<div class="well" style="margin-bottom: 0;margin-top: 2.5mm;">
<div class="container main-cat">
<a href="{% if ga %}/{{ ga.short }}{% endif %}/browse/new" class="btn btn-primary pull-right">
Expand Down
8 changes: 8 additions & 0 deletions templates/mod-box.html
Original file line number Diff line number Diff line change
Expand Up @@ -43,6 +43,14 @@ <h2 class="group inner list-group-item-heading">
<a href="#" title="Unfollow" class="unfollow-mod-button glyphicon glyphicon-star" data-mod="{{ mod.id }}" data-id="{{ mod.id }}"></a>
<a href="#" title="Follow" class="follow-mod-button glyphicon glyphicon-star-empty" data-mod="{{ mod.id }}" data-id="{{ mod.id }}"></a>

{% if admin and feature and ga %}

<a href="#" title="Raise feature priority" class="raise-feature-priority-button glyphicon glyphicon-chevron-left" data-mod="{{ mod.id }}" data-id="{{ mod.id }}"></a>

<a href="#" title="Lower feature priority" class="lower-feature-priority-button glyphicon glyphicon-chevron-right" data-mod="{{ mod.id }}" data-id="{{ mod.id }}"></a>

{% endif %}

<a href='{{url_for("mods.download", mod_id=mod.id, mod_name=mod.name)}}' title="Download" class="download-link glyphicon glyphicon-save-file"></a>
</div>
<div class="caption">
Expand Down
Loading