Compare commits
10 Commits
aff3f76fc1
...
master
Author | SHA1 | Date | |
---|---|---|---|
c0d608b63f | |||
dd7031ff19 | |||
1294d1a037 | |||
546600a29e | |||
|
30f11a7c83 | ||
|
14d1ba9fa7 | ||
|
ce768d895d | ||
daa3b0ca73 | |||
673c986ff9 | |||
453c3e95dd |
@@ -30,6 +30,7 @@ async def devices_get_by_name(name: str, apikey: APIKey = Depends(get_api_key)):
|
|||||||
device = col_devices.find_one({"user": user_by_key(apikey), "name": name})
|
device = col_devices.find_one({"user": user_by_key(apikey), "name": name})
|
||||||
if device is not None:
|
if device is not None:
|
||||||
del device["_id"]
|
del device["_id"]
|
||||||
|
del device["user"]
|
||||||
return UJSONResponse(device)
|
return UJSONResponse(device)
|
||||||
else:
|
else:
|
||||||
raise HTTPException(HTTP_404_NOT_FOUND, detail="Could not find device with that name.")
|
raise HTTPException(HTTP_404_NOT_FOUND, detail="Could not find device with that name.")
|
||||||
@@ -46,7 +47,7 @@ async def devices_delete_by_name(name: str, apikey: APIKey = Depends(get_api_key
|
|||||||
raise HTTPException(HTTP_404_NOT_FOUND, detail="Could not find device with that name.")
|
raise HTTPException(HTTP_404_NOT_FOUND, detail="Could not find device with that name.")
|
||||||
|
|
||||||
@app.post("/devices", response_class=UJSONResponse, description="Create new device")
|
@app.post("/devices", response_class=UJSONResponse, description="Create new device")
|
||||||
async def devices_post(name: str, os: str, apikey: APIKey = Depends(get_api_key)):
|
async def devices_post(name: str, os: str, client: str, apikey: APIKey = Depends(get_api_key)):
|
||||||
|
|
||||||
user = user_by_key(apikey)
|
user = user_by_key(apikey)
|
||||||
|
|
||||||
@@ -62,12 +63,12 @@ async def devices_post(name: str, os: str, apikey: APIKey = Depends(get_api_key)
|
|||||||
if col_devices.find_one({"user": user, "name": name}) is not None:
|
if col_devices.find_one({"user": user, "name": name}) is not None:
|
||||||
raise HTTPException(HTTP_409_CONFLICT, detail="Device with this name already exists.")
|
raise HTTPException(HTTP_409_CONFLICT, detail="Device with this name already exists.")
|
||||||
|
|
||||||
col_devices.insert_one({"user": user, "name": name, "os": os, "last_save": 0})
|
col_devices.insert_one({"user": user, "name": name, "os": os, "client": client, "last_save": 0})
|
||||||
|
|
||||||
return Response(status_code=HTTP_204_NO_CONTENT)
|
return Response(status_code=HTTP_204_NO_CONTENT)
|
||||||
|
|
||||||
@app.patch("/devices/{name}", description="Update name of the existing device")
|
@app.patch("/devices/{name}", description="Update name of the existing device")
|
||||||
async def devices_patch(name: str, new_name: str, os: str, apikey: APIKey = Depends(get_api_key)):
|
async def devices_patch(name: str, new_name: str, os: str, client: str, apikey: APIKey = Depends(get_api_key)):
|
||||||
|
|
||||||
user = user_by_key(apikey)
|
user = user_by_key(apikey)
|
||||||
|
|
||||||
@@ -77,7 +78,7 @@ async def devices_patch(name: str, new_name: str, os: str, apikey: APIKey = Depe
|
|||||||
if col_devices.find_one({"user": user, "name": new_name}) is not None:
|
if col_devices.find_one({"user": user, "name": new_name}) is not None:
|
||||||
raise HTTPException(HTTP_409_CONFLICT, detail="Device with this name already exists.")
|
raise HTTPException(HTTP_409_CONFLICT, detail="Device with this name already exists.")
|
||||||
|
|
||||||
col_devices.find_one_and_update({"user": user, "name": name}, {"$set": {"name": new_name, "os": os}})
|
col_devices.find_one_and_update({"user": user, "name": name}, {"$set": {"name": new_name, "os": os, "client": client}})
|
||||||
col_saves.update_many({"user": user, "device": name}, {"$set": {"device": new_name}})
|
col_saves.update_many({"user": user, "device": name}, {"$set": {"device": new_name}})
|
||||||
|
|
||||||
return Response(status_code=HTTP_204_NO_CONTENT)
|
return Response(status_code=HTTP_204_NO_CONTENT)
|
@@ -1,9 +1,10 @@
|
|||||||
from datetime import datetime
|
from datetime import datetime, timezone
|
||||||
from urllib.parse import quote_plus
|
from urllib.parse import quote_plus
|
||||||
from os import remove
|
from os import remove
|
||||||
from typing import Dict, List, Union
|
from typing import Dict, List, Literal, Union
|
||||||
from xmltodict import parse
|
from xmltodict import parse
|
||||||
from models.saves import StardewSave
|
from pymongo import DESCENDING
|
||||||
|
from models.saves import StardewSave, StardewSaveBrief
|
||||||
from modules.app import app, get_api_key, user_by_key
|
from modules.app import app, get_api_key, user_by_key
|
||||||
from modules.database import col_devices, col_saves
|
from modules.database import col_devices, col_saves
|
||||||
from fastapi import HTTPException, Depends, UploadFile
|
from fastapi import HTTPException, Depends, UploadFile
|
||||||
@@ -14,8 +15,8 @@ from starlette.status import HTTP_204_NO_CONTENT, HTTP_403_FORBIDDEN, HTTP_404_N
|
|||||||
from modules.utils import configGet, zip_saves
|
from modules.utils import configGet, zip_saves
|
||||||
|
|
||||||
|
|
||||||
@app.get("/saves", response_class=UJSONResponse, response_model=Dict[str, StardewSave], description="Get all available game saves")
|
@app.get("/saves", response_class=UJSONResponse, response_model=Union[List[Dict[str, StardewSave]], List[StardewSaveBrief]], description="Get all available game saves")
|
||||||
async def saves_get(device: Union[str, None] = None, version: Union[str, None] = None, apikey: APIKey = Depends(get_api_key)):
|
async def saves_get(device: Union[str, None] = None, version: Union[str, None] = None, only_ids: bool = False, sort: Union[Literal["upload", "progress"], None] = "upload", apikey: APIKey = Depends(get_api_key)):
|
||||||
|
|
||||||
user = user_by_key(apikey)
|
user = user_by_key(apikey)
|
||||||
|
|
||||||
@@ -27,25 +28,31 @@ async def saves_get(device: Union[str, None] = None, version: Union[str, None] =
|
|||||||
if version is not None:
|
if version is not None:
|
||||||
query["data.game_version"] = version
|
query["data.game_version"] = version
|
||||||
|
|
||||||
saves_entries = list(col_saves.find(query))
|
saves_entries = list(col_saves.find(query).sort("date", DESCENDING)) if sort == "upload" else list(col_saves.find(query).sort("data.save_time", DESCENDING))
|
||||||
|
|
||||||
if len(saves_entries) == 0:
|
if len(saves_entries) == 0:
|
||||||
raise HTTPException(HTTP_404_NOT_FOUND, detail="Could not find any saves.")
|
raise HTTPException(HTTP_404_NOT_FOUND, detail="Could not find any saves.")
|
||||||
|
|
||||||
output = []
|
output = []
|
||||||
|
added = []
|
||||||
|
|
||||||
for entry in saves_entries:
|
for entry in saves_entries:
|
||||||
out_entry = entry
|
out_entry = entry
|
||||||
del out_entry["_id"]
|
del out_entry["_id"]
|
||||||
del out_entry["user"]
|
del out_entry["user"]
|
||||||
del out_entry["file"]
|
del out_entry["file"]
|
||||||
|
if only_ids is True:
|
||||||
|
if entry["id"] in added:
|
||||||
|
continue
|
||||||
|
else:
|
||||||
|
added.append(entry["id"])
|
||||||
output.append(out_entry)
|
output.append(out_entry)
|
||||||
|
|
||||||
return UJSONResponse(output)
|
return UJSONResponse(output)
|
||||||
|
|
||||||
|
|
||||||
@app.get("/saves/{id}", response_class=UJSONResponse, response_model=List[StardewSave], description="Get game saves by name")
|
@app.get("/saves/{id}", response_class=UJSONResponse, response_model=List[StardewSave], description="Get game saves by name")
|
||||||
async def saves_get_by_id(id: int, device: Union[str, None] = None, version: Union[str, None] = None, apikey: APIKey = Depends(get_api_key)):
|
async def saves_get_by_id(id: int, device: Union[str, None] = None, version: Union[str, None] = None, sort: Union[Literal["upload", "progress"], None] = "upload", apikey: APIKey = Depends(get_api_key)):
|
||||||
|
|
||||||
query = {"id": id}
|
query = {"id": id}
|
||||||
|
|
||||||
@@ -55,7 +62,7 @@ async def saves_get_by_id(id: int, device: Union[str, None] = None, version: Uni
|
|||||||
if version is not None:
|
if version is not None:
|
||||||
query["data.game_version"] = version
|
query["data.game_version"] = version
|
||||||
|
|
||||||
saves_entries = list(col_saves.find(query))
|
saves_entries = list(col_saves.find(query).sort("date", DESCENDING)) if sort == "upload" else list(col_saves.find(query).sort("data.save_time", DESCENDING))
|
||||||
|
|
||||||
if len(saves_entries) == 0:
|
if len(saves_entries) == 0:
|
||||||
raise HTTPException(HTTP_404_NOT_FOUND, detail="Could not find save with such id.")
|
raise HTTPException(HTTP_404_NOT_FOUND, detail="Could not find save with such id.")
|
||||||
@@ -72,8 +79,8 @@ async def saves_get_by_id(id: int, device: Union[str, None] = None, version: Uni
|
|||||||
return UJSONResponse(output)
|
return UJSONResponse(output)
|
||||||
|
|
||||||
|
|
||||||
@app.get("/saves/{id}/{save_date}", response_class=UJSONResponse, response_model=List[StardewSave], description="Get game saves by name")
|
@app.get("/saves/{id}/{save_date}", response_class=UJSONResponse, response_model=List[StardewSave], description="Get game saves by id and upload date")
|
||||||
async def saves_get_by_both_ids(id: int, save_date: int, device: Union[str, None] = None, apikey: APIKey = Depends(get_api_key)):
|
async def saves_get_by_both_ids(id: int, save_date: Union[int, Literal["latest"]], device: Union[str, None] = None, apikey: APIKey = Depends(get_api_key)):
|
||||||
|
|
||||||
query = {"user": user_by_key(apikey), "id": id, "date": save_date}
|
query = {"user": user_by_key(apikey), "id": id, "date": save_date}
|
||||||
|
|
||||||
@@ -90,7 +97,7 @@ async def saves_get_by_both_ids(id: int, save_date: int, device: Union[str, None
|
|||||||
raise HTTPException(HTTP_404_NOT_FOUND, detail="Could not find save with such id.")
|
raise HTTPException(HTTP_404_NOT_FOUND, detail="Could not find save with such id.")
|
||||||
|
|
||||||
|
|
||||||
@app.delete("/saves/{id}", description="Get game saves by name")
|
@app.delete("/saves/{id}", description="Delete game saves by id")
|
||||||
async def saves_delete_by_id(id: int, apikey: APIKey = Depends(get_api_key)):
|
async def saves_delete_by_id(id: int, apikey: APIKey = Depends(get_api_key)):
|
||||||
user = user_by_key(apikey)
|
user = user_by_key(apikey)
|
||||||
if col_saves.count_documents({"user": user, "id": id}) > 0:
|
if col_saves.count_documents({"user": user, "id": id}) > 0:
|
||||||
@@ -103,7 +110,7 @@ async def saves_delete_by_id(id: int, apikey: APIKey = Depends(get_api_key)):
|
|||||||
raise HTTPException(HTTP_404_NOT_FOUND, detail="Could not find save with such id.")
|
raise HTTPException(HTTP_404_NOT_FOUND, detail="Could not find save with such id.")
|
||||||
|
|
||||||
|
|
||||||
@app.delete("/saves/{id}/{save_date}", response_class=UJSONResponse, description="Get game saves by name")
|
@app.delete("/saves/{id}/{save_date}", response_class=UJSONResponse, description="Delete game saves by id and upload date")
|
||||||
async def saves_delete_by_both_ids(id: int, save_date: int, apikey: APIKey = Depends(get_api_key)):
|
async def saves_delete_by_both_ids(id: int, save_date: int, apikey: APIKey = Depends(get_api_key)):
|
||||||
saves_entry = col_saves.find_one_and_delete({"id": id, "date": save_date})
|
saves_entry = col_saves.find_one_and_delete({"id": id, "date": save_date})
|
||||||
if saves_entry is not None:
|
if saves_entry is not None:
|
||||||
@@ -113,7 +120,7 @@ async def saves_delete_by_both_ids(id: int, save_date: int, apikey: APIKey = Dep
|
|||||||
raise HTTPException(HTTP_404_NOT_FOUND, detail="Could not find save with such id.")
|
raise HTTPException(HTTP_404_NOT_FOUND, detail="Could not find save with such id.")
|
||||||
|
|
||||||
|
|
||||||
@app.get("/saves/{id}/{save_date}/download", response_class=FileResponse, description="Get game save as .svsave file by its id and save date")
|
@app.get("/saves/{id}/{save_date}/download", response_class=FileResponse, description="Get game save as .svsave file by its id and upload date")
|
||||||
async def saves_download(id: int, save_date: int, device: Union[str, None] = None, apikey: APIKey = Depends(get_api_key)):
|
async def saves_download(id: int, save_date: int, device: Union[str, None] = None, apikey: APIKey = Depends(get_api_key)):
|
||||||
saves_entry = col_saves.find_one({"user": user_by_key(apikey), "id": id, "date": save_date}) if device is None else col_saves.find_one({"user": user_by_key(apikey), "id": id, "device": device, "date": save_date})
|
saves_entry = col_saves.find_one({"user": user_by_key(apikey), "id": id, "date": save_date}) if device is None else col_saves.find_one({"user": user_by_key(apikey), "id": id, "device": device, "date": save_date})
|
||||||
if saves_entry is not None: # type: ignore
|
if saves_entry is not None: # type: ignore
|
||||||
@@ -175,7 +182,7 @@ async def saves_post(device: str, files: List[UploadFile], apikey: APIKey = Depe
|
|||||||
|
|
||||||
zipped = zip_saves(save_data_filename, save_data_file, save_info_file)
|
zipped = zip_saves(save_data_filename, save_data_file, save_info_file)
|
||||||
|
|
||||||
save_date = int(datetime.utcnow().timestamp())
|
save_date = int(datetime.now(tz=timezone.utc).timestamp())
|
||||||
|
|
||||||
index = {
|
index = {
|
||||||
"id": int(save_data["SaveGame"]["uniqueIDForThisGame"]),
|
"id": int(save_data["SaveGame"]["uniqueIDForThisGame"]),
|
||||||
|
@@ -4,4 +4,5 @@ class Device(BaseModel):
|
|||||||
user: str
|
user: str
|
||||||
name: str
|
name: str
|
||||||
os: str
|
os: str
|
||||||
|
client: str
|
||||||
last_save: int
|
last_save: int
|
@@ -15,3 +15,7 @@ class StardewSave(BaseModel):
|
|||||||
device: str
|
device: str
|
||||||
date: int
|
date: int
|
||||||
data: StardewSaveData
|
data: StardewSaveData
|
||||||
|
|
||||||
|
class StardewSaveBrief(BaseModel):
|
||||||
|
id: int
|
||||||
|
farmer: str
|
@@ -13,13 +13,11 @@ app = FastAPI(title="Stardew Sync", docs_url=None, redoc_url=None, version="0.1"
|
|||||||
|
|
||||||
api_key_query = APIKeyQuery(name="apikey", auto_error=False)
|
api_key_query = APIKeyQuery(name="apikey", auto_error=False)
|
||||||
api_key_header = APIKeyHeader(name="apikey", auto_error=False)
|
api_key_header = APIKeyHeader(name="apikey", auto_error=False)
|
||||||
api_key_cookie = APIKeyCookie(name="apikey", auto_error=False)
|
|
||||||
|
|
||||||
|
|
||||||
async def get_api_key(
|
async def get_api_key(
|
||||||
api_key_query: str = Security(api_key_query),
|
api_key_query: str = Security(api_key_query),
|
||||||
api_key_header: str = Security(api_key_header),
|
api_key_header: str = Security(api_key_header),
|
||||||
api_key_cookie: str = Security(api_key_cookie),
|
|
||||||
) -> str:
|
) -> str:
|
||||||
|
|
||||||
def is_valid(key):
|
def is_valid(key):
|
||||||
@@ -29,10 +27,8 @@ async def get_api_key(
|
|||||||
return api_key_query
|
return api_key_query
|
||||||
elif is_valid(api_key_header):
|
elif is_valid(api_key_header):
|
||||||
return api_key_header
|
return api_key_header
|
||||||
elif is_valid(api_key_cookie):
|
|
||||||
return api_key_cookie
|
|
||||||
else:
|
else:
|
||||||
if (col_expired.find_one({"hash": passEncode(api_key_query)}) is not None) or (col_expired.find_one({"hash": passEncode(api_key_header)}) is not None) or (col_expired.find_one({"hash": passEncode(api_key_cookie)}) is not None):
|
if (col_expired.find_one({"hash": passEncode(api_key_query)}) is not None) or (col_expired.find_one({"hash": passEncode(api_key_header)}) is not None):
|
||||||
raise HTTPException(status_code=HTTP_403_FORBIDDEN, detail=configGet("key_expired", "messages"))
|
raise HTTPException(status_code=HTTP_403_FORBIDDEN, detail=configGet("key_expired", "messages"))
|
||||||
else:
|
else:
|
||||||
raise HTTPException(status_code=HTTP_401_UNAUTHORIZED, detail=configGet("key_invalid", "messages"))
|
raise HTTPException(status_code=HTTP_401_UNAUTHORIZED, detail=configGet("key_invalid", "messages"))
|
||||||
|
@@ -9,7 +9,7 @@ makedirs(configGet("data", "locations"), exist_ok=True)
|
|||||||
|
|
||||||
@app.get("/check", response_class=Response, include_in_schema=False)
|
@app.get("/check", response_class=Response, include_in_schema=False)
|
||||||
async def check():
|
async def check():
|
||||||
return Response(HTTP_200_OK)
|
return Response(status_code=HTTP_200_OK)
|
||||||
|
|
||||||
@app.get("/favicon.ico", response_class=FileResponse, include_in_schema=False)
|
@app.get("/favicon.ico", response_class=FileResponse, include_in_schema=False)
|
||||||
async def favicon():
|
async def favicon():
|
||||||
|
@@ -12,7 +12,7 @@ parser = ArgumentParser(
|
|||||||
|
|
||||||
parser.add_argument("-u", "--username", help="Enter username without input prompt", action="store")
|
parser.add_argument("-u", "--username", help="Enter username without input prompt", action="store")
|
||||||
parser.add_argument("-e", "--email", help="Enter email without input prompt", action="store")
|
parser.add_argument("-e", "--email", help="Enter email without input prompt", action="store")
|
||||||
parser.add_argument("-l", "--local", help="Do not save user's email to make it completely local and unrecoverable", action="store_trues")
|
parser.add_argument("-l", "--local", help="Do not save user's email to make it completely local and unrecoverable", action="store_true")
|
||||||
parser.add_argument("-j", "--json", help="Return output as a json. Username must be provided as an argument", action="store_true")
|
parser.add_argument("-j", "--json", help="Return output as a json. Username must be provided as an argument", action="store_true")
|
||||||
|
|
||||||
args = parser.parse_args()
|
args = parser.parse_args()
|
||||||
|
@@ -4,6 +4,7 @@
|
|||||||
"user",
|
"user",
|
||||||
"name",
|
"name",
|
||||||
"os",
|
"os",
|
||||||
|
"client",
|
||||||
"last_save"
|
"last_save"
|
||||||
],
|
],
|
||||||
"properties": {
|
"properties": {
|
||||||
@@ -16,6 +17,9 @@
|
|||||||
"os": {
|
"os": {
|
||||||
"bsonType": "string"
|
"bsonType": "string"
|
||||||
},
|
},
|
||||||
|
"client": {
|
||||||
|
"bsonType": "string"
|
||||||
|
},
|
||||||
"last_save": {
|
"last_save": {
|
||||||
"bsonType": ["int", "double"]
|
"bsonType": ["int", "double"]
|
||||||
}
|
}
|
||||||
|
Reference in New Issue
Block a user