Add user rating support!

It took me too long to complete it, I hope this thing will work for more
than 10 minutes. It also breaks when user rated a level that was already
rated.
This commit is contained in:
magmaus3 2022-12-09 19:35:07 +01:00
parent 270cae46b2
commit 44b79518a5
Signed by: magmaus3
GPG key ID: 966755D3F4A9B251
3 changed files with 113 additions and 24 deletions

View file

@ -53,6 +53,10 @@ class User(BaseModel):
Username: str = "magmaus3" Username: str = "magmaus3"
Email: str = "user@example.com" Email: str = "user@example.com"
# Ratings have to be stored in the following format: {mapID: rating},
# where rating is either 1 or 5, or None.
Ratings: dict = {}
class Notification(BaseModel): class Notification(BaseModel):
"""Pydantic model for Notifications """Pydantic model for Notifications

View file

@ -37,6 +37,8 @@ def auth_check(Authorization):
- wrongpass = wrong password - wrongpass = wrong password
- [dictionary] = query - [dictionary] = query
""" """
if Authorization is None:
return False, "noauth"
username, password = Authorization.split(":") username, password = Authorization.split(":")
query = user_collection.find_one({"Username": username}) query = user_collection.find_one({"Username": username})

View file

@ -2,6 +2,7 @@ from datetime import datetime
from fastapi import FastAPI, Form, File, UploadFile, Header, HTTPException, Body from fastapi import FastAPI, Form, File, UploadFile, Header, HTTPException, Body
from fastapi.responses import PlainTextResponse from fastapi.responses import PlainTextResponse
from fastapi.exceptions import RequestValidationError from fastapi.exceptions import RequestValidationError
from pydantic import BaseModel
from starlette.exceptions import HTTPException as StarletteHTTPException from starlette.exceptions import HTTPException as StarletteHTTPException
from typing import Union, Optional, Any from typing import Union, Optional, Any
@ -166,23 +167,33 @@ async def getMap(mapID: int):
@app.post("/api/v1/map/{mapID}/start") @app.post("/api/v1/map/{mapID}/start")
async def startMap(mapID: int): async def startMap(
query = db.maps_collection.find_one({"ID": mapID}) mapID: int,
del query["_id"] Authorization: Union[str, None] = Header(default=None)
):
authcheck = db.auth_check(Authorization)
if not authcheck[0] and authcheck[1] == "nouser":
raise HTTPException(404, detail="User not found")
elif not authcheck[0] and authcheck[1] == "wrongpass":
raise HTTPException(403, detail="Wrong password")
elif authcheck[0]:
userData = types.User(**authcheck[1])
query = db.maps_collection.find_one({"ID": mapID})
del query["_id"]
returned_resp = { returned_resp = {
"BestDeaths": 0, "BestDeaths": 0,
"BestPlaytime": 0, "BestPlaytime": 0,
"Clear": False, "Clear": False,
"CurMap": query, "CurMap": query,
"Difficulty": 0, "Difficulty": 0,
"Followed": False, "Followed": False,
"Played": True, "Played": True,
"Rating": 5, "Rating": userData.Ratings[str(mapID)] if str(mapID) in userData.Ratings else -1,
"TagIDs": "1,8,9", "TagIDs": query["TagIDs"],
"TagNames": "Boss/Avoidance,Music,Art", "TagNames": query["TagNames"],
} }
return returned_resp return returned_resp
@app.post("/api/v1/map/{mapID}/stop") @app.post("/api/v1/map/{mapID}/stop")
@ -213,12 +224,10 @@ async def stopMapPlay(
) )
if query is not None: if query is not None:
del query["_id"] del query["_id"]
print(__import__("json").dumps(query, indent=4))
BestUserTime = None BestUserTime = None
BestTime = None BestTime = None
if query is not None and "Leaderboard" in query: if query is not None and "Leaderboard" in query:
print("-" * 3)
for i in query["Leaderboard"]: for i in query["Leaderboard"]:
if i["UserID"] == userData.ID: if i["UserID"] == userData.ID:
if BestUserTime is None or BestUserTime > i["BestPlaytime"]: if BestUserTime is None or BestUserTime > i["BestPlaytime"]:
@ -227,10 +236,7 @@ async def stopMapPlay(
BestTime = i["BestPlaytime"] BestTime = i["BestPlaytime"]
if len(query["Leaderboard"]) <= 1 and i["UserID"] != userData.ID: if len(query["Leaderboard"]) <= 1 and i["UserID"] != userData.ID:
FirstClear = True FirstClear = True
print(BestUserTime, BestTime)
if BestUserTime is None or playtime < BestUserTime: if BestUserTime is None or playtime < BestUserTime:
print(BestUserTime)
updateQuery = db.maps_collection.update_one( updateQuery = db.maps_collection.update_one(
{"ID": mapID}, {"ID": mapID},
{"$pull": {"Leaderboard": {"UserID": userData.ID}}, {"$pull": {"Leaderboard": {"UserID": userData.ID}},
@ -271,7 +277,6 @@ async def stopMapPlay(
}, },
) )
if BestTime is None or playtime < BestTime: if BestTime is None or playtime < BestTime:
print(BestTime, playtime)
NewMapRecord = True NewMapRecord = True
hook.execute_hooks( hook.execute_hooks(
@ -368,6 +373,84 @@ async def upload_map(
return {"MapCode": MapCode} return {"MapCode": MapCode}
# raise HTTPException(501) # raise HTTPException(501)
class Rating(BaseModel):
Rating: int
@app.post("/api/v1/map/{mapID}/rating")
async def rateMap(mapID: int, Rating: Rating, Authorization: Union[str, None] = Header(default=None)):
authcheck = db.auth_check(Authorization)
if not authcheck[0] and authcheck[1] == "nouser":
raise HTTPException(404, detail="User not found")
elif not authcheck[0] and authcheck[1] == "wrongpass":
raise HTTPException(403, detail="Wrong password")
elif authcheck[0]:
userData = types.User(**authcheck[1])
userRating = userData.Ratings[str(mapID)] if str(mapID) in userData.Ratings else 0
print(userRating, type(userRating))
if Rating.Rating == 5:
if userRating is not None and userRating == 5:
raise HTTPException(400, detail="Map already rated! Set rating in request to -1 to unrate.")
additional = {}
if userRating == 1:
additional = {"NumThumbsDown": -1}
query = db.maps_collection.update_one({"ID": mapID}, {"$inc": {"NumThumbsUp":1, "NumRatings": 1, **additional}})
userRating = 5
elif Rating.Rating == 1:
if userRating is not None and userRating == 1:
raise HTTPException(400, detail="Map already rated! Set rating in request to -1 to unrate.")
additional = {}
if userRating == 5:
additional = {"NumThumbsUp": -1}
query = db.maps_collection.update_one({"ID": mapID}, {"$inc": {"NumThumbsDown": 1, "NumRatings": 1, **additional}})
userRating = 1
elif Rating.Rating == -1:
if userRating is None or userRating == -1:
raise HTTPException(400, detail="Map is not rated!")
key = ""
if userRating == 5:
key = "NumThumbsUp"
elif userRating == 1:
key = "NumThumbsDown"
else:
raise HTTPException(400, detail=f"Previous rating was not 5 or 1. It was {userRating} [{repr(type(userRating))}]")
query = db.maps_collection.update_one({"ID": mapID}, {"$inc": {"NumRatings": -1, key: -1}})
userRating = -1
query = db.maps_collection.find_one({"ID": mapID})
print(userRating, "+")
userQuery = db.user_collection.update_one({"ID": userData.ID}, {"$set": {f"Ratings.{mapID}": userRating} })
print(userQuery.raw_result)
return {
"Exists": True,
"TagIDs": query["TagIDs"],
"TagNames": query["TagNames"],
"UserMap": {
"BestDeaths": 0,
"BestFullPlaytime": 0,
"BestFullPlaytimeTime": None,
"BestPlaytime": 0,
"BestPlaytimeTime": None,
"BestReplay": "",
"Clear": False,
"Difficulty": 0,
"FirstClearInvalid": False,
"FirstClearPlaytime": 0,
"FirstClearTime": False,
"FirstDeathTimeValid": False,
"FirstDeaths": -1,
"FirstPlayRecorded": True,
"FirstPlaytime": -1,
"FullClear": False,
"MapID": mapID,
"Played": True,
"Rating": userRating,
"UserID": userData.ID
}
}
@app.get("/api/v1/map/{mapID}/besttimes/{maxEntries}") @app.get("/api/v1/map/{mapID}/besttimes/{maxEntries}")
async def getMapLeaderboard(mapID: int, maxEntries: int = 5): async def getMapLeaderboard(mapID: int, maxEntries: int = 5):
@ -497,14 +580,14 @@ async def reportContent(
async def featuredlist(): async def featuredlist():
"""Returns the list id of the weekly levels list.""" """Returns the list id of the weekly levels list."""
# FIXME Add playlists # FIXME Add playlists
return raise HTTPException(404, detail="Not available due to lack of playlist support.")
@app.get("/api/v1/followcheck") @app.get("/api/v1/followcheck")
async def followcheck(): async def followcheck():
"""Check, if creators that the user follows uploaded new levels.""" """Check, if creators that the user follows uploaded new levels."""
# FIXME: Stub # FIXME: Stub
return 1 raise HTTPException(404, detail="Not available due to follows not being implemented.")
def start(): def start():