Skip to content
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
16 changes: 13 additions & 3 deletions appstore/api.py
Original file line number Diff line number Diff line change
Expand Up @@ -27,6 +27,10 @@ def generate_app_response(results, sort_override=None):
sorting = sort_override or request.args.get('sort', 'updated')
if sorting == 'hearts':
results = results.order_by(App.hearts.desc())
elif sorting == 'recent_hearts':
results = results.order_by(App.recent_hearts.desc())
elif sorting == 'random_weekly':
results = results.order_by(App.random_weekly.desc())
else:
results = results.order_by(App.id.desc())
# This is slow-ish, but over our appstore size we don't really care.
Expand Down Expand Up @@ -148,11 +152,12 @@ def apps_by_collection(slug, app_type):
apps = App.query.filter(App.type == app_type, ~generated_filter(), global_filter(hw))
elif slug == 'most-loved':
apps = App.query.filter(App.type == app_type, global_filter(hw))
sort_override = 'hearts'
sort_override = 'recent_hearts'
elif slug == 'all-generated':
apps = App.query.filter(App.type == app_type, generated_filter(), global_filter(hw))
else:
collection = Collection.query.filter_by(slug=slug).one_or_none()
sort_override = 'random_weekly'
if collection is None:
abort(404)
apps = collection.apps.filter_by(type=app_type).filter(global_filter(hw))
Expand Down Expand Up @@ -247,7 +252,12 @@ def home(home_type):
'collections': [*({
'name': collection.name,
'slug': collection.slug,
'application_ids': [x.id for x in collection.apps.distinct().limit(7)],
'application_ids': [
x.id for x in collection.apps
.filter(App.type == app_type, global_filter(hw))
.order_by(App.random_weekly.desc())
.distinct()
.limit(7)],
'links': {
'apps': url_for('api.apps_by_collection', slug=collection.slug, app_type=home_type)
},
Expand All @@ -257,7 +267,7 @@ def home(home_type):
'application_ids': [
x.id for x in App.query
.filter(App.type == app_type, global_filter(hw))
.order_by(App.hearts.desc())
.order_by(App.recent_hearts.desc())
.distinct()
.limit(7)],
'links': {
Expand Down
8 changes: 8 additions & 0 deletions appstore/commands.py
Original file line number Diff line number Diff line change
Expand Up @@ -771,5 +771,13 @@ def export_archive(output, upload, test):
db.session.commit()


@apps.command('random-weekly')
def random_weekly():
App.generate_random_weekly()

@apps.command('daily-hearts')
def daily_hearts():
App.generate_recent_hearts()

def init_app(app):
app.cli.add_command(apps)
44 changes: 43 additions & 1 deletion appstore/models.py
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
from flask_sqlalchemy import SQLAlchemy
from flask_migrate import Migrate
from sqlalchemy import Table, desc
from sqlalchemy import Table, desc, Date
from sqlalchemy.dialects.postgresql import ARRAY, UUID
from sqlalchemy.orm.collections import attribute_mapped_collection

Expand Down Expand Up @@ -66,6 +66,8 @@ class App(db.Model):
developer_id = db.Column(db.String(24), db.ForeignKey('developers.id'))
developer = db.relationship('Developer', lazy='joined')
hearts = db.Column(db.Integer, index=True)
recent_hearts = db.Column(db.Float, index=True)
random_weekly = db.Column(db.Integer, index=True)
releases = db.relationship('Release', order_by=lambda: desc(Release.published_date), back_populates='app', lazy='selectin')
icon_large = db.Column(db.String)
icon_small = db.Column(db.String)
Expand All @@ -81,6 +83,46 @@ class App(db.Model):
discourse_topic_id = db.Column(db.Integer)
preview_image = db.Column(db.String)

@classmethod
def generate_random_weekly(cls):
seed = db.func.concat(
db.func.extract("year", db.func.current_date()),
db.func.extract("week", db.func.current_date()),
cast(cls.id, String)
)

random_value = (
("x" + db.func.substr(db.func.md5(seed), 1, 8)).cast(Integer)
)

random_update = db.update(cls).values(random_weekly=random_value)
db.session.execute(random_update)
db.session.commit()

@classmethod
def generate_recent_hearts(cls):
decay_cte = (
db.select(
UserLike.app_id,
db.func.sum(
db.func.pow(
0.9,
db.func.cast(db.func.current_date(), Date) - db.func.cast(UserLike.created_at, Date)
)
).label("decay_sum")
)
.where(UserLike.created_at != None)
.group_by(UserLike.app_id)
).cte("decay_cte")

decay_update = (
db.update(cls)
.values(recent_hearts=decay_cte.c.decay_sum)
.where(cls.id == decay_cte.c.app_id)
)

db.session.execute(decay_update)
db.session.commit()

category_banner_apps = Table('category_banner_apps', db.Model.metadata,
db.Column('category_id', db.String(24), db.ForeignKey('categories.id', ondelete='cascade')),
Expand Down
34 changes: 34 additions & 0 deletions migrations/versions/3edc55c9f541_introduce_app_sorting_columns.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,34 @@
"""Introduce app sorting columns

Revision ID: 3edc55c9f541
Revises: 0c3a1cfa6af5
Create Date: 2025-12-14 04:31:04.072470

"""
from alembic import op
import sqlalchemy as sa


# revision identifiers, used by Alembic.
revision = '3edc55c9f541'
down_revision = '0c3a1cfa6af5'
branch_labels = None
depends_on = None


def upgrade():
# ### commands auto generated by Alembic - please adjust! ###
op.add_column('apps', sa.Column('recent_hearts', sa.Float(), nullable=True))
op.add_column('apps', sa.Column('random_weekly', sa.Integer(), nullable=True))
op.create_index(op.f('ix_apps_recent_hearts'), 'apps', ['recent_hearts'], unique=False)
op.create_index(op.f('ix_apps_random_weekly'), 'apps', ['random_weekly'], unique=False)
# ### end Alembic commands ###


def downgrade():
# ### commands auto generated by Alembic - please adjust! ###
op.drop_index(op.f('ix_apps_random_weekly'), table_name='apps')
op.drop_index(op.f('ix_apps_recent_hearts'), table_name='apps')
op.drop_column('apps', 'random_weekly')
op.drop_column('apps', 'recent_hearts')
# ### end Alembic commands ###