332 lines
12 KiB
Python
332 lines
12 KiB
Python
from fastapi import APIRouter, Depends, HTTPException, Query
|
|
from sqlalchemy.ext.asyncio import AsyncSession
|
|
from sqlalchemy import select, func, and_, or_
|
|
from datetime import datetime, timedelta
|
|
from typing import Optional
|
|
from app.database import get_db
|
|
from app.models import AppInfo, AppMetrics, AppRating
|
|
from app.schemas import ApiResponse
|
|
|
|
router = APIRouter(prefix="/apps", tags=["应用"])
|
|
|
|
@router.get("/search")
|
|
async def search_apps(
|
|
q: str = Query(..., min_length=1),
|
|
page: int = Query(1, ge=1),
|
|
page_size: int = Query(20, le=100),
|
|
db: AsyncSession = Depends(get_db)
|
|
):
|
|
"""搜索应用"""
|
|
subquery = (
|
|
select(AppMetrics.app_id, func.max(AppMetrics.created_at).label('max_created_at'))
|
|
.group_by(AppMetrics.app_id)
|
|
.subquery()
|
|
)
|
|
|
|
query = (
|
|
select(AppInfo, AppMetrics, AppRating)
|
|
.join(AppMetrics, AppInfo.app_id == AppMetrics.app_id)
|
|
.outerjoin(AppRating, AppInfo.app_id == AppRating.app_id)
|
|
.join(subquery, and_(
|
|
AppMetrics.app_id == subquery.c.app_id,
|
|
AppMetrics.created_at == subquery.c.max_created_at
|
|
))
|
|
.where(or_(
|
|
AppInfo.name.like(f"%{q}%"),
|
|
AppInfo.pkg_name.like(f"%{q}%"),
|
|
AppInfo.developer_name.like(f"%{q}%")
|
|
))
|
|
.order_by(AppMetrics.download_count.desc())
|
|
)
|
|
|
|
count_query = select(func.count(AppInfo.app_id)).where(or_(
|
|
AppInfo.name.like(f"%{q}%"),
|
|
AppInfo.pkg_name.like(f"%{q}%"),
|
|
AppInfo.developer_name.like(f"%{q}%")
|
|
))
|
|
|
|
total_result = await db.execute(count_query)
|
|
total = total_result.scalar()
|
|
|
|
offset = (page - 1) * page_size
|
|
query = query.offset(offset).limit(page_size)
|
|
|
|
result = await db.execute(query)
|
|
rows = result.all()
|
|
|
|
data = [{
|
|
"app_id": row[0].app_id,
|
|
"name": row[0].name,
|
|
"pkg_name": row[0].pkg_name,
|
|
"developer_name": row[0].developer_name,
|
|
"kind_name": row[0].kind_name,
|
|
"icon_url": row[0].icon_url,
|
|
"brief_desc": row[0].brief_desc,
|
|
"download_count": row[1].download_count if len(row) > 1 else 0,
|
|
"version": row[1].version if len(row) > 1 else "",
|
|
"average_rating": float(row[2].average_rating) if len(row) > 2 and row[2] else 0,
|
|
"listed_at": row[0].listed_at.isoformat()
|
|
} for row in rows]
|
|
|
|
return ApiResponse(success=True, data=data, total=total, limit=page_size)
|
|
|
|
@router.get("/category/{category}")
|
|
async def get_apps_by_category(
|
|
category: str,
|
|
page: int = Query(1, ge=1),
|
|
page_size: int = Query(20, le=100),
|
|
db: AsyncSession = Depends(get_db)
|
|
):
|
|
"""按分类获取应用"""
|
|
subquery = (
|
|
select(AppMetrics.app_id, func.max(AppMetrics.created_at).label('max_created_at'))
|
|
.group_by(AppMetrics.app_id)
|
|
.subquery()
|
|
)
|
|
|
|
query = (
|
|
select(AppInfo, AppMetrics, AppRating)
|
|
.join(AppMetrics, AppInfo.app_id == AppMetrics.app_id)
|
|
.outerjoin(AppRating, AppInfo.app_id == AppRating.app_id)
|
|
.join(subquery, and_(
|
|
AppMetrics.app_id == subquery.c.app_id,
|
|
AppMetrics.created_at == subquery.c.max_created_at
|
|
))
|
|
.where(AppInfo.kind_name == category)
|
|
.order_by(AppMetrics.download_count.desc())
|
|
)
|
|
|
|
count_query = select(func.count(AppInfo.app_id)).where(AppInfo.kind_name == category)
|
|
total_result = await db.execute(count_query)
|
|
total = total_result.scalar()
|
|
|
|
offset = (page - 1) * page_size
|
|
query = query.offset(offset).limit(page_size)
|
|
|
|
result = await db.execute(query)
|
|
rows = result.all()
|
|
|
|
data = [{
|
|
"app_id": row[0].app_id,
|
|
"name": row[0].name,
|
|
"pkg_name": row[0].pkg_name,
|
|
"developer_name": row[0].developer_name,
|
|
"kind_name": row[0].kind_name,
|
|
"icon_url": row[0].icon_url,
|
|
"brief_desc": row[0].brief_desc,
|
|
"download_count": row[1].download_count if len(row) > 1 else 0,
|
|
"version": row[1].version if len(row) > 1 else "",
|
|
"average_rating": float(row[2].average_rating) if len(row) > 2 and row[2] else 0,
|
|
"listed_at": row[0].listed_at.isoformat()
|
|
} for row in rows]
|
|
|
|
return ApiResponse(success=True, data=data, total=total, limit=page_size)
|
|
|
|
@router.get("/categories")
|
|
async def get_categories(db: AsyncSession = Depends(get_db)):
|
|
"""获取所有分类"""
|
|
result = await db.execute(
|
|
select(AppInfo.kind_name, func.count(AppInfo.app_id).label('count'))
|
|
.group_by(AppInfo.kind_name)
|
|
.order_by(func.count(AppInfo.app_id).desc())
|
|
)
|
|
rows = result.all()
|
|
|
|
data = [{"name": row[0], "count": row[1]} for row in rows]
|
|
return ApiResponse(success=True, data=data)
|
|
|
|
@router.get("/today")
|
|
async def get_today_apps(
|
|
page_size: int = Query(20, le=100),
|
|
db: AsyncSession = Depends(get_db)
|
|
):
|
|
"""获取今日上架应用"""
|
|
today = datetime.now().replace(hour=0, minute=0, second=0, microsecond=0)
|
|
|
|
subquery = (
|
|
select(AppMetrics.app_id, func.max(AppMetrics.created_at).label('max_created_at'))
|
|
.group_by(AppMetrics.app_id)
|
|
.subquery()
|
|
)
|
|
|
|
query = (
|
|
select(AppInfo, AppMetrics, AppRating)
|
|
.join(AppMetrics, AppInfo.app_id == AppMetrics.app_id)
|
|
.outerjoin(AppRating, AppInfo.app_id == AppRating.app_id)
|
|
.join(subquery, and_(
|
|
AppMetrics.app_id == subquery.c.app_id,
|
|
AppMetrics.created_at == subquery.c.max_created_at
|
|
))
|
|
.where(AppInfo.listed_at >= today)
|
|
.order_by(AppInfo.listed_at.desc())
|
|
.limit(page_size)
|
|
)
|
|
|
|
result = await db.execute(query)
|
|
rows = result.all()
|
|
|
|
data = [{
|
|
"app_id": row[0].app_id,
|
|
"name": row[0].name,
|
|
"pkg_name": row[0].pkg_name,
|
|
"developer_name": row[0].developer_name,
|
|
"kind_name": row[0].kind_name,
|
|
"icon_url": row[0].icon_url,
|
|
"brief_desc": row[0].brief_desc,
|
|
"download_count": row[1].download_count if len(row) > 1 else 0,
|
|
"version": row[1].version if len(row) > 1 else "",
|
|
"average_rating": float(row[2].average_rating) if len(row) > 2 and row[2] else 0,
|
|
"listed_at": row[0].listed_at.isoformat()
|
|
} for row in rows]
|
|
|
|
return ApiResponse(success=True, data=data, total=len(data))
|
|
|
|
@router.get("/top-downloads")
|
|
async def get_top_downloads(
|
|
limit: int = Query(100, le=100),
|
|
db: AsyncSession = Depends(get_db)
|
|
):
|
|
"""热门应用Top100"""
|
|
subquery = (
|
|
select(AppMetrics.app_id, func.max(AppMetrics.created_at).label('max_created_at'))
|
|
.group_by(AppMetrics.app_id)
|
|
.subquery()
|
|
)
|
|
|
|
query = (
|
|
select(AppInfo, AppMetrics, AppRating)
|
|
.join(AppMetrics, AppInfo.app_id == AppMetrics.app_id)
|
|
.outerjoin(AppRating, AppInfo.app_id == AppRating.app_id)
|
|
.join(subquery, and_(
|
|
AppMetrics.app_id == subquery.c.app_id,
|
|
AppMetrics.created_at == subquery.c.max_created_at
|
|
))
|
|
.order_by(AppMetrics.download_count.desc())
|
|
.limit(limit)
|
|
)
|
|
|
|
result = await db.execute(query)
|
|
rows = result.all()
|
|
|
|
data = [{
|
|
"app_id": row[0].app_id,
|
|
"name": row[0].name,
|
|
"pkg_name": row[0].pkg_name,
|
|
"developer_name": row[0].developer_name,
|
|
"kind_name": row[0].kind_name,
|
|
"icon_url": row[0].icon_url,
|
|
"brief_desc": row[0].brief_desc,
|
|
"download_count": row[1].download_count if len(row) > 1 else 0,
|
|
"version": row[1].version if len(row) > 1 else "",
|
|
"average_rating": float(row[2].average_rating) if len(row) > 2 and row[2] else 0
|
|
} for row in rows]
|
|
|
|
return ApiResponse(success=True, data=data, total=len(data))
|
|
|
|
@router.get("/top-ratings")
|
|
async def get_top_ratings(
|
|
limit: int = Query(100, le=100),
|
|
db: AsyncSession = Depends(get_db)
|
|
):
|
|
"""评分Top100"""
|
|
subquery_metric = (
|
|
select(AppMetrics.app_id, func.max(AppMetrics.created_at).label('max_created_at'))
|
|
.group_by(AppMetrics.app_id)
|
|
.subquery()
|
|
)
|
|
|
|
subquery_rating = (
|
|
select(AppRating.app_id, func.max(AppRating.created_at).label('max_created_at'))
|
|
.group_by(AppRating.app_id)
|
|
.subquery()
|
|
)
|
|
|
|
query = (
|
|
select(AppInfo, AppMetrics, AppRating)
|
|
.join(AppMetrics, AppInfo.app_id == AppMetrics.app_id)
|
|
.join(AppRating, AppInfo.app_id == AppRating.app_id)
|
|
.join(subquery_metric, and_(
|
|
AppMetrics.app_id == subquery_metric.c.app_id,
|
|
AppMetrics.created_at == subquery_metric.c.max_created_at
|
|
))
|
|
.join(subquery_rating, and_(
|
|
AppRating.app_id == subquery_rating.c.app_id,
|
|
AppRating.created_at == subquery_rating.c.max_created_at
|
|
))
|
|
.where(AppRating.total_rating_count >= 100)
|
|
.order_by(AppRating.average_rating.desc())
|
|
.limit(limit)
|
|
)
|
|
|
|
result = await db.execute(query)
|
|
rows = result.all()
|
|
|
|
data = [{
|
|
"app_id": row[0].app_id,
|
|
"name": row[0].name,
|
|
"pkg_name": row[0].pkg_name,
|
|
"developer_name": row[0].developer_name,
|
|
"kind_name": row[0].kind_name,
|
|
"icon_url": row[0].icon_url,
|
|
"brief_desc": row[0].brief_desc,
|
|
"download_count": row[1].download_count if len(row) > 1 else 0,
|
|
"version": row[1].version if len(row) > 1 else "",
|
|
"average_rating": float(row[2].average_rating) if len(row) > 2 and row[2] else 0,
|
|
"total_rating_count": row[2].total_rating_count if len(row) > 2 and row[2] else 0
|
|
} for row in rows]
|
|
|
|
return ApiResponse(success=True, data=data, total=len(data))
|
|
|
|
@router.get("/{app_id}")
|
|
async def get_app_detail(app_id: str, db: AsyncSession = Depends(get_db)):
|
|
"""获取应用详情"""
|
|
subquery = (
|
|
select(AppMetrics.app_id, func.max(AppMetrics.created_at).label('max_created_at'))
|
|
.where(AppMetrics.app_id == app_id)
|
|
.group_by(AppMetrics.app_id)
|
|
.subquery()
|
|
)
|
|
|
|
query = (
|
|
select(AppInfo, AppMetrics, AppRating)
|
|
.join(AppMetrics, AppInfo.app_id == AppMetrics.app_id)
|
|
.outerjoin(AppRating, AppInfo.app_id == AppRating.app_id)
|
|
.join(subquery, and_(
|
|
AppMetrics.app_id == subquery.c.app_id,
|
|
AppMetrics.created_at == subquery.c.max_created_at
|
|
))
|
|
.where(AppInfo.app_id == app_id)
|
|
)
|
|
|
|
result = await db.execute(query)
|
|
row = result.first()
|
|
|
|
if not row:
|
|
raise HTTPException(status_code=404, detail="应用不存在")
|
|
|
|
data = {
|
|
"app_id": row[0].app_id,
|
|
"name": row[0].name,
|
|
"pkg_name": row[0].pkg_name,
|
|
"developer_name": row[0].developer_name,
|
|
"kind_name": row[0].kind_name,
|
|
"icon_url": row[0].icon_url,
|
|
"brief_desc": row[0].brief_desc,
|
|
"description": row[0].description,
|
|
"privacy_url": row[0].privacy_url,
|
|
"is_pay": row[0].is_pay,
|
|
"listed_at": row[0].listed_at.isoformat(),
|
|
"download_count": row[1].download_count if len(row) > 1 else 0,
|
|
"version": row[1].version if len(row) > 1 else "",
|
|
"size_bytes": row[1].size_bytes if len(row) > 1 else 0,
|
|
"average_rating": float(row[2].average_rating) if len(row) > 2 and row[2] else 0,
|
|
"total_rating_count": row[2].total_rating_count if len(row) > 2 and row[2] else 0,
|
|
"star_1_count": row[2].star_1_count if len(row) > 2 and row[2] else 0,
|
|
"star_2_count": row[2].star_2_count if len(row) > 2 and row[2] else 0,
|
|
"star_3_count": row[2].star_3_count if len(row) > 2 and row[2] else 0,
|
|
"star_4_count": row[2].star_4_count if len(row) > 2 and row[2] else 0,
|
|
"star_5_count": row[2].star_5_count if len(row) > 2 and row[2] else 0
|
|
}
|
|
|
|
return ApiResponse(success=True, data=data)
|