diff --git a/imports.py b/imports.py index a8e4f6a..fc02171 100644 --- a/imports.py +++ b/imports.py @@ -20,4 +20,4 @@ import bcrypt import random -rate_limit_exceeded_handler = _rate_limit_exceeded_handler \ No newline at end of file +rate_limit_exceeded_handler = _rate_limit_exceeded_handler diff --git a/main.py b/main.py index 14a1878..4894e4f 100644 --- a/main.py +++ b/main.py @@ -4,8 +4,9 @@ from imports import * + def custom_key_func(request: Request): - if get_remote_address(request) == os.getenv('SERVER_IP'): + if get_remote_address(request) == os.getenv("SERVER_IP"): return "bots" return "user" @@ -37,104 +38,181 @@ def dynamic_limit_provider_upload(key: str): allow_headers=["*"], ) + @app.on_event("startup") async def startup_event(): await db.connect() # Connecting to database print("Connected to Data Base") -@app.get("/api") # root api endpoint +@app.get("/api") # root api endpoint @limiter.limit(dynamic_limit_provider) async def api(request: Request): - return JSONResponse(content={"status": "success", "message": "File uploader RESTful API", - "docs": "https://github.com/Andcool-Systems/File-uploader/blob/main/README.md"}, status_code=200) + return JSONResponse( + content={ + "status": "success", + "message": "File uploader RESTful API", + "docs": "https://github.com/Andcool-Systems/File-uploader/blob/main/README.md", + }, + status_code=200, + ) async def check_token(Authorization): if not Authorization: # If token doesn't provided return None, {"message": "No Authorization header provided", "errorId": -1} - + token_header = Authorization.split(" ") if len(token_header) != 2: # If token have unsupported format - return None, {"message": "Authorization header must have `Bearer ` format", "errorId": -2} - + return None, { + "message": "Authorization header must have `Bearer ` format", + "errorId": -2, + } + try: - token = jwt.decode(token_header[1], "accessTokenSecret", algorithms=["HS256"]) # Decode token + token = jwt.decode( + token_header[1], "accessTokenSecret", algorithms=["HS256"] + ) # Decode token except jwt.exceptions.DecodeError: return None, {"message": "Invalid access token", "errorId": -4} - - token_db = await db.token.find_first(where={"accessToken": token_header[1]}, include={"user": True}) # Find token in db + + token_db = await db.token.find_first( + where={"accessToken": token_header[1]}, include={"user": True} + ) # Find token in db if not token_db: # If not found return None, {"message": "Token not found", "errorId": -5} - + if token["ExpiresAt"] < time.time(): # If token expired await db.token.delete(where={"id": token_db.id}) return None, {"message": "Access token expired", "errorId": -3} - + return token_db, {} -@app.post("/api/upload") # File upload handler +@app.post("/api/upload/{group_id}") # File upload handler @limiter.limit(dynamic_limit_provider_upload) -async def upload_file(file: UploadFile, request: Request, include_ext: bool = False, max_uses: int = 0, - Authorization: Annotated[Union[str, None], Header(convert_underscores=False)] = None): +async def upload_file( + group_id: str, + file: UploadFile, + request: Request, + include_ext: bool = False, + max_uses: int = 0, + Authorization: Annotated[ + Union[str, None], Header(convert_underscores=False) + ] = None, +): + if not file: # Check, if the file is uploaded - return JSONResponse(content={"status": "error", "message": "No file uploaded"}, status_code=400) + return JSONResponse( + content={"status": "error", "message": "No file uploaded"}, status_code=400 + ) if file.filename.find(".") == -1: # Check, if the file has a extension - return JSONResponse(content={"status": "error", "message": "Bad file extension"}, status_code=400) + return JSONResponse( + content={"status": "error", "message": "Bad file extension"}, + status_code=400, + ) + + if file.size > 100 * 1024 * 1024: # 100MB limit + return JSONResponse( + content={ + "status": "error", + "message": "File size exceeds the limit (100MB)", + }, + status_code=413, + ) - if file.size > 100 * 1024 * 1024: # 100MB limit - return JSONResponse(content={"status": "error", "message": "File size exceeds the limit (100MB)"}, status_code=413) - if max_uses > 10000: - return JSONResponse(content={"status": "error", "message": "Invalid max_uses parameter"}, status_code=400) - + return JSONResponse( + content={"status": "error", "message": "Invalid max_uses parameter"}, + status_code=400, + ) + saved_to_account = False user_id = -1 token_db, auth_error = await check_token(Authorization) # Check token if token_db: # If token is okay - user_id = token_db.user_id saved_to_account = True + user_id = token_db.user.id + + if group_id != "private": + if not token_db: # If token is not valid + return JSONResponse(content={ + "status": "error", + "message": "Auth error", + "auth_error": auth_error, + }, + status_code=401, + ) + if not group_id.isnumeric(): + return JSONResponse(content={"status": "error", "message": "Invalid group id"}, + status_code=400) + + group = await db.group.find_first(where={"group_id": group_id}, include={"members": True} + ) + if not group: + return JSONResponse(content={"status": "error", "message": "Group not found"}, + status_code=404 + ) + if token_db.user not in group.members: + return JSONResponse(content={"status": "error", "message": "You are not in the group"}, + status_code=400) + else: + group_id = -1 key = str(uuid.uuid4()) # Generate unique delete key ext = "." + file.filename.split(".")[-1].lower() # Get file extension fid = utils.generate_token(10) + (ext if include_ext else "") # Generate file url - fn = str(uuid.uuid4()) + ext # Generate file name + fn = str(uuid.uuid4()) + ext # Generate file name async with aiofiles.open(f"uploads/{fn}", "wb") as f: # Save file locally await f.write(file.file.read()) now = datetime.now() - created = await db.file.create({ # Creating a file record - "user_id": user_id, - "created_date": f"{now.day}.{now.month}.{now.year} {now.hour}:{now.minute}:{now.second}", - "url": fid, - "filename": f"uploads/{fn}", - "craeted_at": time.time(), - "last_watched": time.time(), - "key": key, - "type": filetypes.get(ext[1:], default) if ext.lower()[1:] in filetypes else "download", - "ext": ext, - "size": file.size, - "user_filename": file.filename, - "max_uses": max_uses - }) - - user_filename = created.user_filename[:50] + ("..." if len(created.user_filename) > 50 else "") - return JSONResponse(content={"status": "success", - "message": "File uploaded successfully", - "file_url": created.url, - "file_url_full": "https://fu.andcool.ru/file/" + created.url, - "key": created.key, - "ext": created.ext, - "size": utils.calculate_size(file.size), - "user_filename": user_filename, - "craeted_at": created.craeted_at, - "synced": saved_to_account, - "auth_error": auth_error}, status_code=200) + created = await db.file.create( + { # Creating a file record + "user_id": user_id, + "group_id": int(group_id), + "created_date": f"{now.day}.{now.month}.{now.year} {now.hour}:{now.minute}:{now.second}", + "url": fid, + "filename": f"uploads/{fn}", + "craeted_at": time.time(), + "last_watched": time.time(), + "key": key, + "type": ( + filetypes.get(ext[1:], default) + if ext.lower()[1:] in filetypes + else "download" + ), + "ext": ext, + "size": file.size, + "user_filename": file.filename, + "max_uses": max_uses, + } + ) + + user_filename = created.user_filename[:50] + ( + "..." if len(created.user_filename) > 50 else "" + ) + return JSONResponse( + content={ + "status": "success", + "message": "File uploaded successfully", + "file_url": created.url, + "file_url_full": "https://fu.andcool.ru/file/" + created.url, + "key": created.key, + "ext": created.ext, + "size": utils.calculate_size(file.size), + "user_filename": user_filename, + "username": None if not token_db else token_db.user.username, + "craeted_at": created.craeted_at, + "synced": saved_to_account, + "auth_error": auth_error, + }, + status_code=200, + ) @app.get("/file/{url}") # Get file handler @@ -142,159 +220,288 @@ async def upload_file(file: UploadFile, request: Request, include_ext: bool = Fa @limiter.limit(dynamic_limit_provider) async def send_file(url: str, request: Request): result = await db.file.find_first(where={"url": url}) # Get file by url - if not result: + if not result: async with aiofiles.open("404.html", mode="rb") as f: - return Response(await f.read(), media_type="text/html", status_code=404) # if file does'n exists - - print(request.headers.get('CF-IPCountry')) - - if 'sec-fetch-dest' in request.headers: - if request.headers.get('sec-fetch-dest') == 'document': - result = await db.file.update(where={"id": result.id}, - data={"last_watched": time.time(), "uses_number": result.uses_number + 1}) # Update last watched record + return Response( + await f.read(), media_type="text/html", status_code=404 + ) # if file does'n exists + + print(request.headers.get("CF-IPCountry")) + + if "sec-fetch-dest" in request.headers: + if request.headers.get("sec-fetch-dest") == "document": + result = await db.file.update( + where={"id": result.id}, + data={ + "last_watched": time.time(), + "uses_number": result.uses_number + 1, + }, + ) # Update last watched record else: - result = await db.file.update(where={"id": result.id}, - data={"last_watched": time.time(), "uses_number": result.uses_number + 1}) + result = await db.file.update( + where={"id": result.id}, + data={"last_watched": time.time(), "uses_number": result.uses_number + 1}, + ) if result.max_uses < result.uses_number and result.max_uses != 0: await delete_file(result.url, result.key) return JSONResponse(content="File not found!", status_code=404) - + if result.type != "download": # If File extension recognized async with aiofiles.open(result.filename, mode="rb") as f: - return Response(await f.read(), media_type=result.type) # Send file with "Content-type" header + return Response( + await f.read(), media_type=result.type + ) # Send file with "Content-type" header else: # If file extension doesn't recognized - return FileResponse(path=result.filename, filename=result.user_filename, media_type=result.type) # Send file as FileResponse + return FileResponse( + path=result.filename, filename=result.user_filename, media_type=result.type + ) # Send file as FileResponse @app.get("/api/delete/{url}") # File delete handler async def delete_file(url: str, key: str = ""): result = await db.file.find_first(where={"url": url}) # Get file record by url - if not result: return JSONResponse(content={"status": "error", "message": "File not found"}, status_code=200) # if file does'n exists + if not result: + return JSONResponse( + content={"status": "error", "message": "File not found"}, status_code=200 + ) # if file does'n exists if result.key == key: # If provided key matched with key from database record os.remove(result.filename) # Delete file - await db.file.delete(where={"id": result.id}) # Delete file record from database - - async with aiohttp.ClientSession("https://api.cloudflare.com") as session: # Clear file cache from CloudFlare - async with session.post(f"/client/v4/zones/{os.getenv('ZONE_ID')}/purge_cache", - json={"files": ["https://fu.andcool.ru/file/" + result.url]}, - headers={"Authorization": "Bearer " + os.getenv('KEY')}): pass - - return JSONResponse(content={"status": "success", "message": "deleted"}, status_code=200) + await db.file.delete( + where={"id": result.id} + ) # Delete file record from database + + async with aiohttp.ClientSession( + "https://api.cloudflare.com" + ) as session: # Clear file cache from CloudFlare + async with session.post( + f"/client/v4/zones/{os.getenv('ZONE_ID')}/purge_cache", + json={"files": ["https://fu.andcool.ru/file/" + result.url]}, + headers={"Authorization": "Bearer " + os.getenv("KEY")}, + ): + pass + + return JSONResponse( + content={"status": "success", "message": "deleted"}, status_code=200 + ) else: # If provided key doesn't matched with key from database record - return JSONResponse(content={"status": "error", "message": "invalid unique key"}, status_code=400) + return JSONResponse( + content={"status": "error", "message": "invalid unique key"}, + status_code=400, + ) @app.get("/api/getFiles/{group_id}") # get files handler @app.get("/api/get_files/{group_id}") # get files handler @limiter.limit(dynamic_limit_provider) -async def getFiles(group_id: str, request: Request, - Authorization: Annotated[Union[str, None], Header(convert_underscores=False)] = None): +async def getFiles( + group_id: str, + request: Request, + Authorization: Annotated[ + Union[str, None], Header(convert_underscores=False) + ] = None, +): token_db, auth_error = await check_token(Authorization) # Check token validity if not token_db: # If token is not valid - return JSONResponse(content={"status": "error", "message": "Auth error", "auth_error": auth_error}, status_code=401) - - user = await db.user.find_first(where={"id": token_db.user_id}) # Get user files from db + return JSONResponse( + content={ + "status": "error", + "message": "Auth error", + "auth_error": auth_error, + }, + status_code=401, + ) + + user = await db.user.find_first( + where={"id": token_db.user_id} + ) # Get user files from db if group_id == "private": - user_id = user.id + files = await db.file.find_many(where={"user_id": user.id} + ) # Get all user files from db else: if not group_id.isnumeric(): - return JSONResponse(content={"status": "error", "message": "Invalid group id"}, status_code=400) - - group = await db.group.find_first(where={"group_id": group_id}, include={"members": True}) + return JSONResponse( + content={"status": "error", "message": "Invalid group id"}, + status_code=400, + ) + + group = await db.group.find_first( + where={"group_id": group_id}, include={"members": True} + ) if not group: - return JSONResponse(content={"status": "error", "message": "Group not found"}, status_code=404) + return JSONResponse( + content={"status": "error", "message": "Group not found"}, + status_code=404, + ) if user not in group.members: - return JSONResponse({"status": "error", "message": "You are not in the group"}, status_code=400) - - user_id = -int(group_id) - files = await db.file.find_many(where={"user_id": user_id}) # Get all user files from db + return JSONResponse( + {"status": "error", "message": "You are not in the group"}, + status_code=400, + ) + + files = await db.file.find_many(where={"group_id": group_id}) + files_response = [] for file in files: - user_filename = file.user_filename[:50] + ("..." if len(file.user_filename) > 50 else "") - files_response.append({ - "file_url": file.url, - "file_url_full": "https://fu.andcool.ru/file/" + file.url, - "key": file.key, - "ext": file.ext, - "user_filename": user_filename, - "creation_date": file.created_date, - "craeted_at": file.craeted_at, - "size": utils.calculate_size(file.size), - "synced": True - }) - return JSONResponse(content={"status": "success", "message": "messages got successfully", "username": user.username, "data": files_response}, status_code=200) + user_filename = file.user_filename[:50] + ( + "..." if len(file.user_filename) > 50 else "" + ) + files_response.append( + { + "file_url": file.url, + "file_url_full": "https://fu.andcool.ru/file/" + file.url, + "key": file.key, + "ext": file.ext, + "user_filename": user_filename, + "creation_date": file.created_date, + "craeted_at": file.craeted_at, + "size": utils.calculate_size(file.size), + "username": token_db.user.username if group_id != "private" else None, + "synced": True, + } + ) + return JSONResponse( + content={ + "status": "success", + "message": "messages got successfully", + "username": user.username, + "is_group_owner": False if group_id == "private" else group.admin_id == token_db.user_id, + "data": files_response, + }, + status_code=200, + ) @app.post("/api/register") # Registartion handler @limiter.limit(dynamic_limit_provider) async def register(request: Request, bot: bool = False): body = await request.json() - if "username" not in body or \ - "password" not in body: # If request body doesn't have username and password field - return JSONResponse({"status": "error", "message": "No username/password provided", "errorId": 2}, status_code=400) - - user = await db.user.find_first(where={'username': body['username']}) # Find same username in db + if ( + "username" not in body or "password" not in body + ): # If request body doesn't have username and password field + return JSONResponse( + { + "status": "error", + "message": "No username/password provided", + "errorId": 2, + }, + status_code=400, + ) + + user = await db.user.find_first( + where={"username": body["username"]} + ) # Find same username in db if user: # If iser already exists - return JSONResponse({"status": "error", "message": "An account with this name is already registered", "errorId": 1}, status_code=400) + return JSONResponse( + { + "status": "error", + "message": "An account with this name is already registered", + "errorId": 1, + }, + status_code=400, + ) salt = bcrypt.gensalt() # Encrypt password - hashed = bcrypt.hashpw(bytes(str(body['password']), "utf-8"), salt) + hashed = bcrypt.hashpw(bytes(str(body["password"]), "utf-8"), salt) user = await db.user.create( # Create user record in db - { - "username": str(body['username']), - "password": str(hashed.decode('utf-8')) - } + {"username": str(body["username"]), "password": str(hashed.decode("utf-8"))} ) - access = jwt.encode({"user_id": int(user.id), "ExpiresAt": time.time() + (accesLifeTime if not bot else accesLifeTimeBot)}, - "accessTokenSecret", algorithm="HS256") # Generate token - - await db.token.create({ # Create token record in db - "accessToken": access, - 'user': { - 'connect': { - 'id': user.id, + access = jwt.encode( + { + "user_id": int(user.id), + "ExpiresAt": time.time() + (accesLifeTime if not bot else accesLifeTimeBot), + }, + "accessTokenSecret", + algorithm="HS256", + ) # Generate token + + await db.token.create( + { # Create token record in db + "accessToken": access, + "user": { + "connect": { + "id": user.id, + }, }, } - }) - return JSONResponse({"status": "success", "accessToken": access, "username": body['username'], "message": "registred"}, status_code=200) + ) + return JSONResponse( + { + "status": "success", + "accessToken": access, + "username": body["username"], + "message": "registred", + }, + status_code=200, + ) @app.post("/api/login") # login handler @limiter.limit(dynamic_limit_provider) async def login(request: Request, bot: bool = False): body = await request.json() - if "username" not in body or \ - "password" not in body: # If request body doesn't have username and password field - return JSONResponse({"status": "error", "message": "No username/password provided", "errorId": 2}, status_code=400) - - user = await db.user.find_first(where={'username': body['username']}, include={"tokens": True}) # Find same username in db - if not user: # If user doesn't exists - return JSONResponse({"status": "error", "message": "User not found", "errorId": 4}, status_code=404) + if ( + "username" not in body or "password" not in body + ): # If request body doesn't have username and password field + return JSONResponse( + { + "status": "error", + "message": "No username/password provided", + "errorId": 2, + }, + status_code=400, + ) - if bcrypt.checkpw(bytes(body["password"], "utf-8"), bytes(user.password, "utf-8")): # If password is correct - access = jwt.encode({"user_id": int(user.id), "ExpiresAt": time.time() + (accesLifeTime if not bot else accesLifeTimeBot)}, "accessTokenSecret", algorithm="HS256") + user = await db.user.find_first( + where={"username": body["username"]}, include={"tokens": True} + ) # Find same username in db + if not user: # If user doesn't exists + return JSONResponse( + {"status": "error", "message": "User not found", "errorId": 4}, + status_code=404, + ) + + if bcrypt.checkpw( + bytes(body["password"], "utf-8"), bytes(user.password, "utf-8") + ): # If password is correct + access = jwt.encode( + { + "user_id": int(user.id), + "ExpiresAt": time.time() + + (accesLifeTime if not bot else accesLifeTimeBot), + }, + "accessTokenSecret", + algorithm="HS256", + ) if len(user.tokens) > 10: # If user have more than 10 tokens await db.token.delete_many(where={"user_id": user.id}) - - await db.token.create({ # Create token record in db - "accessToken": access, - 'user': { - 'connect': { - 'id': user.id, - } - }}) - return {"status": "success", + await db.token.create( + { # Create token record in db "accessToken": access, - "username": user.username, - "message": "logged in with password"} + "user": { + "connect": { + "id": user.id, + } + }, + } + ) + + return { + "status": "success", + "accessToken": access, + "username": user.username, + "message": "logged in with password", + } else: # If password doesn't match - return JSONResponse({"status": "error", "message": "Wrong password", "errorId": 3}, status_code=400) + return JSONResponse( + {"status": "error", "message": "Wrong password", "errorId": 3}, + status_code=400, + ) @app.post("/api/refresh_token") # refresh token handler @@ -302,54 +509,95 @@ async def login(request: Request, bot: bool = False): async def login(request: Request): body = await request.json() if "accessToken" not in body: # If token doesn't provided - return JSONResponse({"status": "error", "message": "No access token provided", "errorId": 5}, status_code=400) - - token_db, auth_error = await check_token(body["accessToken"]) # Check token validity + return JSONResponse( + {"status": "error", "message": "No access token provided", "errorId": 5}, + status_code=400, + ) + + token_db, auth_error = await check_token( + body["accessToken"] + ) # Check token validity if not token_db: # If token is not valid - return JSONResponse(content={"status": "error", "message": "Auth error", "auth_error": auth_error}, status_code=401) - - access = jwt.encode({"user_id": int(token_db.user_id), "ExpiresAt": time.time() + accesLifeTime}, - "accessTokenSecret", algorithm="HS256") # Generate new token - await db.token.update(where={"id": token_db.id}, # Replace old token - data={"accessToken": access}) - - return {"status": "success", - "accessToken": access, - "message": "token refreshed"} + return JSONResponse( + content={ + "status": "error", + "message": "Auth error", + "auth_error": auth_error, + }, + status_code=401, + ) + + access = jwt.encode( + {"user_id": int(token_db.user_id), "ExpiresAt": time.time() + accesLifeTime}, + "accessTokenSecret", + algorithm="HS256", + ) # Generate new token + await db.token.update( + where={"id": token_db.id}, data={"accessToken": access} # Replace old token + ) + + return {"status": "success", "accessToken": access, "message": "token refreshed"} @app.post("/api/logout") # logout handler @limiter.limit(dynamic_limit_provider) -async def login(request: Request, - Authorization: Annotated[Union[str, None], Header(convert_underscores=False)] = None): - +async def login( + request: Request, + Authorization: Annotated[ + Union[str, None], Header(convert_underscores=False) + ] = None, +): + token_db, auth_error = await check_token(Authorization) # Check token validity if not token_db: # If token is not valid - return JSONResponse(content={"status": "error", "message": "Auth error", "auth_error": auth_error}, status_code=401) + return JSONResponse( + content={ + "status": "error", + "message": "Auth error", + "auth_error": auth_error, + }, + status_code=401, + ) await db.token.delete(where={"id": token_db.id}) # Delete token record from db - return {"status": "success", - "message": "logged out"} + return {"status": "success", "message": "logged out"} @app.post("/api/transfer") # logout handler @limiter.limit(dynamic_limit_provider) -async def transfer(request: Request, - Authorization: Annotated[Union[str, None], Header(convert_underscores=False)] = None): - +async def transfer( + request: Request, + Authorization: Annotated[ + Union[str, None], Header(convert_underscores=False) + ] = None, +): + token_db, auth_error = await check_token(Authorization) # Check token validity if not token_db: # If token is not valid - return JSONResponse(content={"status": "error", "message": "Auth error", "auth_error": auth_error}, status_code=401) - + return JSONResponse( + content={ + "status": "error", + "message": "Auth error", + "auth_error": auth_error, + }, + status_code=401, + ) + try: body = await request.json() except: - return JSONResponse(content={"status": "error", "message": "Couldn't parse request body"}, status_code=400) - + return JSONResponse( + content={"status": "error", "message": "Couldn't parse request body"}, + status_code=400, + ) + if "data" not in body: - return JSONResponse(content={"status": "error", "message": "No `data` field in request body"}, status_code=400) - + return JSONResponse( + content={"status": "error", "message": "No `data` field in request body"}, + status_code=400, + ) + non_success = [] for requested_file in body["data"]: try: @@ -358,213 +606,242 @@ async def transfer(request: Request, non_success.append(requested_file) continue - await db.file.update(where={"id": file.id}, data={"user_id": token_db.user_id}) + await db.file.update( + where={"id": file.id}, data={"user_id": token_db.user_id} + ) except: non_success.append(requested_file) - return {"status": "success", - "message": "transfered", - "unsuccess": non_success} + return {"status": "success", "message": "transfered", "unsuccess": non_success} # --------------------------------------Groups------------------------------------------ + @app.post("/api/create_group") # create_group handler @limiter.limit(dynamic_limit_provider) -async def create_group(request: Request, - Authorization: Annotated[Union[str, None], Header(convert_underscores=False)] = None): - +async def create_group( + request: Request, + Authorization: Annotated[ + Union[str, None], Header(convert_underscores=False) + ] = None, +): + body = await request.json() if "group_name" not in body: # If token doesn't provided - return JSONResponse({"status": "error", "message": "No `group_name` provided"}, status_code=400) - + return JSONResponse( + {"status": "error", "message": "No `group_name` provided"}, status_code=400 + ) + token_db, auth_error = await check_token(Authorization) # Check token validity if not token_db: # If token is not valid - return JSONResponse(content={"status": "error", "message": "Auth error", "auth_error": auth_error}, status_code=401) - + return JSONResponse( + content={ + "status": "error", + "message": "Auth error", + "auth_error": auth_error, + }, + status_code=401, + ) + if len(body["group_name"]) > 50: - return JSONResponse(content={"status": "error", "message": "Group name length exceeded (50 chars)"}, status_code=400) - group = await db.group.create(data={ - "name": body["group_name"], - "group_id": random.randint(10000000, 99999999), - "admin_id": token_db.user_id, - "invite_string": utils.generate_token(15), - 'members': { - 'connect': { - 'id': token_db.user_id - }, - } - }) - return {"status": "success", - "message": "created", - "name": group.name, - "invite_string": group.invite_string, - "group_id": group.group_id} + return JSONResponse( + content={ + "status": "error", + "message": "Group name length exceeded (50 chars)", + }, + status_code=400, + ) + group = await db.group.create( + data={ + "name": body["group_name"], + "group_id": random.randint(10000000, 99999999), + "admin_id": token_db.user_id, + "invite_string": utils.generate_token(15), + "members": { + "connect": {"id": token_db.user_id}, + }, + } + ) + return { + "status": "success", + "message": "created", + "name": group.name, + "invite_string": group.invite_string, + "group_id": group.group_id, + } @app.delete("/api/delete_group/{group_id}") # delete_group handler @limiter.limit(dynamic_limit_provider) -async def delete_group(group_id: int, request: Request, - Authorization: Annotated[Union[str, None], Header(convert_underscores=False)] = None): - +async def delete_group( + group_id: int, + request: Request, + Authorization: Annotated[ + Union[str, None], Header(convert_underscores=False) + ] = None, +): + token_db, auth_error = await check_token(Authorization) # Check token validity if not token_db: # If token is not valid - return JSONResponse(content={"status": "error", "message": "Auth error", "auth_error": auth_error}, status_code=401) - + return JSONResponse( + content={ + "status": "error", + "message": "Auth error", + "auth_error": auth_error, + }, + status_code=401, + ) + group = await db.group.find_first(where={"group_id": group_id}) if not group: - return JSONResponse({"status": "error", "message": "Group not found"}, status_code=404) - + return JSONResponse( + {"status": "error", "message": "Group not found"}, status_code=404 + ) + if group.admin_id != token_db.user_id: - return JSONResponse({"status": "error", "message": "You dont have any permissions to delete this group"}, status_code=403) - + return JSONResponse( + { + "status": "error", + "message": "You dont have any permissions to delete this group", + }, + status_code=403, + ) + await db.group.delete(where={"id": group.id}) - return {"status": "success", - "message": "deleted"} + return {"status": "success", "message": "deleted"} @app.post("/api/join/{invite_link}") # join handler @limiter.limit(dynamic_limit_provider) -async def delete_group(invite_link: str, request: Request, - Authorization: Annotated[Union[str, None], Header(convert_underscores=False)] = None): +async def delete_group( + invite_link: str, + request: Request, + Authorization: Annotated[ + Union[str, None], Header(convert_underscores=False) + ] = None, +): token_db, auth_error = await check_token(Authorization) # Check token validity if not token_db: # If token is not valid - return JSONResponse(content={"status": "error", "message": "Auth error", "auth_error": auth_error}, status_code=401) - - group = await db.group.find_first(where={"invite_string": invite_link}, include={"members": True}) + return JSONResponse( + content={ + "status": "error", + "message": "Auth error", + "auth_error": auth_error, + }, + status_code=401, + ) + + group = await db.group.find_first( + where={"invite_string": invite_link}, include={"members": True} + ) if not group: - return JSONResponse({"status": "error", "message": "Invite link not found"}, status_code=404) - + return JSONResponse( + {"status": "error", "message": "Invite link not found"}, status_code=404 + ) + if token_db.user in group.members: - return JSONResponse({"status": "error", "message": "You are already in the group"}, status_code=400) - - await db.group.update(data={'members': { - 'connect': { - 'id': token_db.user_id - }, - } - }, - - where={"id": group.id} - ) - - return {"status": "success", - "message": "Joined", - "name": group.name, - "invite_string": group.invite_string, - "group_id": group.group_id} + return JSONResponse( + {"status": "error", "message": "You are already in the group"}, + status_code=400, + ) + + await db.group.update( + data={ + "members": { + "connect": {"id": token_db.user_id}, + } + }, + where={"id": group.id}, + ) + + return { + "status": "success", + "message": "Joined", + "name": group.name, + "invite_string": group.invite_string, + "group_id": group.group_id, + } @app.post("/api/leave/{group_id}") # leave handler @limiter.limit(dynamic_limit_provider) -async def delete_group(group_id: int, request: Request, - Authorization: Annotated[Union[str, None], Header(convert_underscores=False)] = None): +async def delete_group( + group_id: int, + request: Request, + Authorization: Annotated[ + Union[str, None], Header(convert_underscores=False) + ] = None, +): token_db, auth_error = await check_token(Authorization) # Check token validity if not token_db: # If token is not valid - return JSONResponse(content={"status": "error", "message": "Auth error", "auth_error": auth_error}, status_code=401) - - group = await db.group.find_first(where={"group_id": group_id}, include={"members": True}) - if not group: - return JSONResponse({"status": "error", "message": "Group not found"}, status_code=404) - - if token_db.user not in group.members: - return JSONResponse({"status": "error", "message": "You are not in the group"}, status_code=400) - - await db.group.update(data={'members': {'disconnect': {'id': token_db.user_id}}}, where={"id": group.id}) - - return {"status": "success", - "message": "leaved"} - - -@app.post("/api/group/{group_id}/upload") # leave handler -@limiter.limit(dynamic_limit_provider) -async def upload_group(group_id: int, file: UploadFile, request: Request, include_ext: bool = False, max_uses: int = 0, - Authorization: Annotated[Union[str, None], Header(convert_underscores=False)] = None): + return JSONResponse( + content={ + "status": "error", + "message": "Auth error", + "auth_error": auth_error, + }, + status_code=401, + ) - token_db, auth_error = await check_token(Authorization) # Check token validity - if not token_db: # If token is not valid - return JSONResponse(content={"status": "error", "message": "Auth error", "auth_error": auth_error}, status_code=401) - - group = await db.group.find_first(where={"group_id": group_id}, include={"members": True}) + group = await db.group.find_first( + where={"group_id": group_id}, include={"members": True} + ) if not group: - return JSONResponse({"status": "error", "message": "Group not found"}, status_code=404) - - if token_db.user not in group.members: - return JSONResponse({"status": "error", "message": "You are not in the group"}, status_code=400) - - if not file: # Check, if the file is uploaded - return JSONResponse(content={"status": "error", "message": "No file uploaded"}, status_code=400) + return JSONResponse( + {"status": "error", "message": "Group not found"}, status_code=404 + ) - if file.filename.find(".") == -1: # Check, if the file has a extension - return JSONResponse(content={"status": "error", "message": "Bad file extension"}, status_code=400) - - if file.size > 100 * 1024 * 1024: # 100MB limit - return JSONResponse(content={"status": "error", "message": "File size exceeds the limit (100MB)"}, status_code=413) - - if max_uses > 10000: - return JSONResponse(content={"status": "error", "message": "Invalid max_uses parameter"}, status_code=400) - - key = str(uuid.uuid4()) # Generate unique delete key - ext = "." + file.filename.split(".")[-1].lower() # Get file extension - fid = utils.generate_token(10) + (ext if include_ext else "") # Generate file url - fn = str(uuid.uuid4()) + ext # Generate file name + if token_db.user not in group.members: + return JSONResponse( + {"status": "error", "message": "You are not in the group"}, status_code=400 + ) - async with aiofiles.open(f"uploads/{fn}", "wb") as f: # Save file locally - await f.write(file.file.read()) + await db.group.update( + data={"members": {"disconnect": {"id": token_db.user_id}}}, + where={"id": group.id}, + ) - now = datetime.now() - created = await db.file.create({ # Creating a file record - "user_id": group.group_id * -1, - "created_date": f"{now.day}.{now.month}.{now.year} {now.hour}:{now.minute}:{now.second}", - "url": fid, - "filename": f"uploads/{fn}", - "craeted_at": time.time(), - "last_watched": time.time(), - "key": key, - "type": filetypes.get(ext[1:], default) if ext.lower()[1:] in filetypes else "download", - "ext": ext, - "size": file.size, - "user_filename": file.filename, - "max_uses": max_uses - }) - - user_filename = created.user_filename[:50] + ("..." if len(created.user_filename) > 50 else "") - return JSONResponse(content={"status": "success", - "message": "File uploaded successfully", - "file_url": created.url, - "file_url_full": "https://fu.andcool.ru/file/" + created.url, - "key": created.key, - "ext": created.ext, - "size": utils.calculate_size(file.size), - "user_filename": user_filename, - "craeted_at": created.craeted_at, - "synced": False, - "auth_error": auth_error}, status_code=200) + return {"status": "success", "message": "leaved"} @app.get("/api/get_groups") # leave handler @limiter.limit(dynamic_limit_provider) -async def get_groups(request: Request, Authorization: Annotated[Union[str, None], Header(convert_underscores=False)] = None): - +async def get_groups( + request: Request, + Authorization: Annotated[ + Union[str, None], Header(convert_underscores=False) + ] = None, +): + token_db, auth_error = await check_token(Authorization) # Check token validity if not token_db: # If token is not valid - return JSONResponse(content={"status": "error", "message": "Auth error", "auth_error": auth_error}, status_code=401) - - user = await db.user.find_first(where={"id": token_db.user_id}, include={"groups": True}) + return JSONResponse( + content={ + "status": "error", + "message": "Auth error", + "auth_error": auth_error, + }, + status_code=401, + ) + + user = await db.user.find_first( + where={"id": token_db.user_id}, include={"groups": True} + ) groups = [] for group in user.groups: - groups.append({ - "name": group.name, - "group_id": group.group_id, - "invite_string": group.invite_string - }) - - return {"status": "success", - "message": "groups got successfully", - "groups": groups} + groups.append( + { + "name": group.name, + "group_id": group.group_id, + "invite_string": group.invite_string, + } + ) + + return {"status": "success", "message": "groups got successfully", "groups": groups} if __name__ == "__main__": # Start program diff --git a/schema.prisma b/schema.prisma index 61c52ad..dfe15b3 100644 --- a/schema.prisma +++ b/schema.prisma @@ -13,6 +13,7 @@ generator db { model file { id Int @id @default(autoincrement()) user_id Int @default(-1) + group_id BigInt @default(-1) created_date String @default("") url String @default("") filename String @default("") diff --git a/utils.py b/utils.py index 1bdf7b6..1ea0942 100644 --- a/utils.py +++ b/utils.py @@ -1,9 +1,11 @@ import random + def generate_token(length): base = "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ123456789-+=" return "".join([random.choice(base) for x in range(length)]) + def calculate_size(size: int): units = ["B", "KB", "MB", "GB", "TB"] unit_iteration = 0 @@ -14,4 +16,3 @@ def calculate_size(size: int): unit_iteration += 1 return f"{round(calculated_size, 2)}{units[unit_iteration]}" - diff --git a/web/api.js b/web/api.js index 0196f15..2938f15 100644 --- a/web/api.js +++ b/web/api.js @@ -2,14 +2,13 @@ let api_upload_url = "/api/upload"; let api_file_url = "/file/"; //let api_url = "https://fu.andcool.ru"; let api_url = "http://127.0.0.1:8080"; -let groups = []; -async function delete_file(data, id){ +async function delete_file(data, id) { let confirmed = confirm("Delete it? It will be impossible to restore the file!"); - if (confirmed){ + if (confirmed) { let response = await axios.get(api + "/api/delete/" + data.file_url + "?key=" + data.key); - if (response.status == 200){ - if (!data.synced){ + if (response.status == 200) { + if (!data.synced) { let old_data = JSON.parse(localStorage.getItem("file_history") || "[]"); old_data.splice(id, 1); localStorage.setItem("file_history", JSON.stringify(old_data)); @@ -20,45 +19,45 @@ async function delete_file(data, id){ } -function append_to_files_arr(data, id){ - let table = document.getElementById('files_table'); +function append_to_files_arr(data, id) { + let table = document.getElementById('files_table'); - // Insert a row at the start of table - let newRow = table.insertRow(0); + // Insert a row at the start of table + let newRow = table.insertRow(0); newRow.id = "row_" + id; newRow.className = "tr"; - // Insert a cell at the end of the row - let newCell = newRow.insertCell(); - let newCell2 = newRow.insertCell(); - // Append a text node to the cell - let url = document.createElement("p"); - url.innerHTML = data['file_url_full']; + // Insert a cell at the end of the row + let newCell = newRow.insertCell(); + let newCell2 = newRow.insertCell(); + // Append a text node to the cell + let url = document.createElement("p"); + url.innerHTML = data['file_url_full']; url.id = "url"; - url.onclick = function(){navigator.clipboard.writeText(data['file_url_full']);} + url.onclick = function () { navigator.clipboard.writeText(data['file_url_full']); } let filename = document.createElement("p"); - filename.innerHTML = data['user_filename']; + filename.innerHTML = data['user_filename']; filename.id = "filename"; let creation_date_div = document.createElement("div"); creation_date_div.id = "creation_date_div"; let cr_time = document.createElement("p"); - cr_time.innerHTML = data['creation_date'] + " " + (!data['size'] || data['size'] == "0B"? "" : data['size']); + cr_time.innerHTML = data['creation_date'] + " " + (!data['size'] || data['size'] == "0B" ? "" : data['size']); cr_time.id = "cr_time"; - const button = document.createElement('button'); - button.innerHTML = 'Delete'; + const button = document.createElement('button'); + button.innerHTML = 'Delete'; button.className = "button" - button.onclick = function(){delete_file(data, id);} + button.onclick = function () { delete_file(data, id); } let online = document.createElement("img"); online.className = "online"; - if (data.synced){ + if (data.synced) { online.src = "./res/globe_on.png"; online.title = "Synchronized with the server"; - }else{ + } else { online.src = "./res/globe_off.png"; online.title = "Stored on local browser"; } @@ -71,9 +70,12 @@ function append_to_files_arr(data, id){ let href_img = document.createElement("img"); href_img.src = "./res/external-link.png"; - a_btn.appendChild(href_img); + let username = document.createElement("p"); + username.innerHTML = data['username'] ? data['username'] + "'s file" : ""; + username.id = "username"; + let urls_div = document.createElement("div"); let url_link_div = document.createElement("div"); url_link_div.className = "url_link_div"; @@ -82,57 +84,56 @@ function append_to_files_arr(data, id){ urls_div.appendChild(url_link_div); urls_div.appendChild(filename); - + creation_date_div.appendChild(cr_time); creation_date_div.appendChild(online); urls_div.appendChild(creation_date_div); + if (data["username"]) urls_div.appendChild(username); - newCell.appendChild(urls_div); - newCell2.appendChild(button); + newCell.appendChild(urls_div); + newCell2.appendChild(button); } -async function get_new_tokens(accessToken){ - try{ - let response = await axios.post(api_url + "/api/refresh_token", {'accessToken': "Bearer " + accessToken}, {}) +async function get_new_tokens(accessToken) { + try { + let response = await axios.post(api_url + "/api/refresh_token", { 'accessToken': "Bearer " + accessToken }, {}) if (!response) return false; if (response.status != 200) return false; return response.data.accessToken; - }catch{ + } catch { return false; } } -async function transfer_func(){ +async function transfer_func() { accessToken = localStorage.getItem("accessToken"); if (!accessToken) return []; - if (!checkAccess(accessToken)){ + if (!checkAccess(accessToken)) { let new_access = await get_new_tokens(accessToken); - if (!new_access){ + if (!new_access) { localStorage.removeItem("accessToken"); return []; } - console.log(new_access); accessToken = new_access; localStorage.setItem("accessToken", new_access); } - try{ - let response = await axios.post(api_url + "/api/transfer", {'data': JSON.parse(localStorage.getItem("file_history") || "[]")}, { + try { + let response = await axios.post(api_url + "/api/transfer", { 'data': JSON.parse(localStorage.getItem("file_history") || "[]") }, { headers: { 'Authorization': 'Bearer ' + accessToken } }) if (!response) return; - if (response.status == 200){ + if (response.status == 200) { localStorage.setItem("file_history", JSON.stringify(response.data.unsuccess)); location.reload(); } - - }catch (e){ - console.log(e); - if (e.response.status == 401){ + + } catch (e) { + if (e.response && e.response.status == 401) { localStorage.removeItem("accessToken"); return []; } @@ -140,20 +141,19 @@ async function transfer_func(){ } } -async function fetch_groups(){ +async function fetch_groups() { let accessToken = localStorage.getItem("accessToken"); if (!accessToken) return []; - if (!checkAccess(accessToken)){ + if (!checkAccess(accessToken)) { let new_access = await get_new_tokens(accessToken); - if (!new_access){ + if (!new_access) { localStorage.removeItem("accessToken"); return []; } - console.log(new_access); accessToken = new_access; localStorage.setItem("accessToken", new_access); } - try{ + try { let response = await axios.get(api_url + "/api/get_groups", { headers: { 'Authorization': 'Bearer ' + accessToken @@ -161,17 +161,25 @@ async function fetch_groups(){ }) if (!response) return; + document.getElementById('groups_selector').style.display = "block"; + let prev_group = localStorage.getItem("prev_group"); let groups = document.getElementById('groups'); - for (const group of response.data.groups){ + let value_finded = false; + for (const group of response.data.groups) { let groupel = document.createElement("option"); groupel.innerHTML = group.name; groupel.value = group.group_id; groups.appendChild(groupel); + if (group.group_id == prev_group){ + groups.value = prev_group; + value_finded = true; + } } + if (!value_finded) localStorage.removeItem("prev_group"); + fetch_files(localStorage.getItem("accessToken")); - }catch (e){ - console.log(e); - if (e.response.status == 401){ + } catch (e) { + if (e.response && e.response.status == 401) { localStorage.removeItem("accessToken"); return []; } @@ -179,19 +187,19 @@ async function fetch_groups(){ } } -async function fetch_files(accessToken, group){ +async function fetch_files(accessToken) { if (!accessToken) return []; - if (!checkAccess(accessToken)){ + if (!checkAccess(accessToken)) { let new_access = await get_new_tokens(accessToken); - if (!new_access){ + if (!new_access) { localStorage.removeItem("accessToken"); return []; } - console.log(new_access); accessToken = new_access; localStorage.setItem("accessToken", new_access); } - try{ + let group = document.getElementById('groups').value; + try { let response = await axios.get(api_url + "/api/get_files/" + group, { headers: { 'Authorization': 'Bearer ' + accessToken @@ -202,18 +210,19 @@ async function fetch_files(accessToken, group){ let logim_page_btn = document.getElementById('login_page_a'); logim_page_btn.textContent = "Logout"; logim_page_btn.href = "/"; - logim_page_btn.onclick = function() {if (confirm("Log out?")) {logout()}}; + logim_page_btn.onclick = function () { if (confirm("Log out?")) { logout() } }; document.title = "File uploader ยท " + response.data.username; document.getElementById('login_mess').textContent = "Logged as " + response.data.username; + document.getElementById('invite_users').disabled = !response.data.is_group_owner; let len = 0; let table = document.getElementById('files_table'); - table.innerHTML = ""; - if (group == "private"){ + table.innerHTML = ""; + if (group == "private") { let file_history = JSON.parse(localStorage.getItem("file_history") || "[]"); - if (file_history != []){ - for (const file of file_history){ + if (file_history != []) { + for (const file of file_history) { append_to_files_arr(file, len); len++; } @@ -227,18 +236,18 @@ async function fetch_files(accessToken, group){ let transfer = document.createElement("button"); transfer.id = "trensfer"; transfer.innerHTML = "Transfer local files to an account" - transfer.onclick = function(){if (confirm("Transfer local files to an active account?")) transfer_func()} + transfer.onclick = function () { if (confirm("Transfer local files to an active account?")) transfer_func() } if (len > 0) newCell.appendChild(transfer); } let it = 0; - for (const file of response.data.data){ + for (const file of response.data.data) { append_to_files_arr(file, len + it); it++; } - }catch (e){ + } catch (e) { console.log(e); - if (e.response.status == 401){ + if (e.response && e.response.status == 401) { localStorage.removeItem("accessToken"); return []; } @@ -246,35 +255,33 @@ async function fetch_files(accessToken, group){ } } -async function logout(){ +async function logout() { let accessToken = localStorage.getItem("accessToken"); - console.log(accessToken); if (!accessToken) return []; - if (!checkAccess(accessToken)){ + if (!checkAccess(accessToken)) { let new_access = await get_new_tokens(accessToken); - if (!new_access){ + if (!new_access) { localStorage.removeItem("accessToken"); return []; } - console.log(new_access); accessToken = new_access; localStorage.setItem("accessToken", new_access); } - try{ + try { let response = await axios.post(api_url + "/api/logout", {}, { headers: { 'Authorization': 'Bearer ' + accessToken } }) if (!response) return []; - if (response.status == 401 || response.status == 200){ + if (response.status == 401 || response.status == 200) { localStorage.removeItem("accessToken"); let logim_page_btn = document.getElementById('login_page_a'); logim_page_btn.textContent = "Login"; logim_page_btn.href = "https://fu.andcool.ru/login/"; } - }catch{ + } catch { localStorage.removeItem("accessToken"); return []; } @@ -282,10 +289,11 @@ async function logout(){ addEventListener("DOMContentLoaded", (event) => { document.getElementById('groups').addEventListener("change", (event) => { - fetch_files(localStorage.getItem("accessToken"), - event.target.value); - }); - document.getElementById('input_file').addEventListener('change', function(e) { + localStorage.setItem("prev_group", event.target.value); + fetch_files(localStorage.getItem("accessToken"), + event.target.value); + }); + document.getElementById('input_file').addEventListener('change', function (e) { let fileInput = document.getElementById('input_file'); let load_mess = document.getElementById('load_mess'); @@ -293,35 +301,42 @@ addEventListener("DOMContentLoaded", (event) => { if (file) { let reader = new FileReader(); - reader.onprogress = function(e) { - if (e.lengthComputable) { - let percentage = (e.loaded / e.total) * 100; - load_mess.textContent = "Uploading file... " + percentage.toFixed(1) + '%'; - } - }; + reader.onprogress = function (e) { + if (e.lengthComputable) { + let percentage = (e.loaded / e.total) * 100; + load_mess.textContent = "Uploading file... " + percentage.toFixed(1) + '%'; + } + }; - reader.onload = function() { - console.log('File loaded successfully'); - if (file.size > 100 * 1024 * 1024){ - load_mess.textContent = "File size exceeds the limit (100MB)"; - return; - } - upload(file); - }; + reader.onload = function () { + console.log('File loaded successfully'); + if (file.size > 100 * 1024 * 1024) { + load_mess.textContent = "File size exceeds the limit (100MB)"; + return; + } + upload(file); + }; - reader.readAsDataURL(file); + reader.readAsDataURL(file); } }); - + let len = 0; + let file_history = JSON.parse(localStorage.getItem("file_history") || "[]"); + if (file_history != []) { + for (const file of file_history) { + append_to_files_arr(file, len); + len++; + } + } fetch_groups(); - fetch_files(localStorage.getItem("accessToken"), document.getElementById('groups').value); let dropContainer = document.getElementById('dropContainer') - dropContainer.ondragover = dropContainer.ondragenter = function(evt) { + dropContainer.ondragover = dropContainer.ondragenter = function (evt) { evt.preventDefault(); - }; - - dropContainer.ondrop = function(evt) { + }; + + + dropContainer.ondrop = function (evt) { const dT = new DataTransfer(); dT.items.add(evt.dataTransfer.files[0]); document.getElementById('input_file').files = dT.files; @@ -331,59 +346,64 @@ addEventListener("DOMContentLoaded", (event) => { }); -async function upload(file){ - - let imagefile = document.querySelector('#input_file'); - let load_mess = document.getElementById('load_mess'); +async function upload(file) { + + let imagefile = document.querySelector('#input_file'); + let load_mess = document.getElementById('load_mess'); let file_ext = document.getElementById('include_ext'); - load_mess.textContent = "Uploading to the server..."; - - try{ - let response = await axios.post(api_url + api_upload_url + "?include_ext=" + file_ext.checked, {'file': file}, { - headers: { - 'Content-Type': 'multipart/form-data', - 'Authorization': 'Bearer ' + localStorage.getItem("accessToken") - } - }) - - imagefile.value = ""; - if (response){ - if (response.status == 200){ - let response1 = response.data; - var currentdate = new Date(); + let group_id = document.getElementById('groups').value; + load_mess.textContent = "Uploading to the server..."; + + try { + let response = await axios.post(`${api_url}${api_upload_url}/${group_id}?include_ext=${file_ext.checked}`, { 'file': file }, { + headers: { + 'Content-Type': 'multipart/form-data', + 'Authorization': 'Bearer ' + localStorage.getItem("accessToken") + } + }) + + imagefile.value = ""; + if (response) { + if (response.status == 200) { + let response1 = response.data; + var currentdate = new Date(); var datetime = currentdate.getDate() + "." - + (currentdate.getMonth()+1) + "." - + currentdate.getFullYear() + " " - + currentdate.getHours() + ":" - + currentdate.getMinutes() + ":" - + currentdate.getSeconds(); - + + (currentdate.getMonth() + 1) + "." + + currentdate.getFullYear() + " " + + currentdate.getHours() + ":" + + currentdate.getMinutes() + ":" + + currentdate.getSeconds(); + response1["creation_date"] = datetime; load_mess.textContent = "Uploaded!"; - + let old_data = JSON.parse(localStorage.getItem("file_history") || "[]"); append_to_files_arr(response1, old_data.length) navigator.clipboard.writeText(response1['file_url_full']); - - if (!response1.synced){ + + if (!response1.synced) { old_data.push(response1); localStorage.setItem("file_history", JSON.stringify(old_data)); } - } - else if (response.status == 429){ - load_mess.textContent = "Too many requests (2 per minute)"; - }else{ - load_mess.textContent = response.data.message; - } - } - }catch(err){ - console.log(err); - load_mess.textContent = "Error while uploading file. See console for more information."; + } + } + } catch (err) { + console.log(err); + if(err.response){ + if(err.response.status == 429){ + load_mess.textContent = "Too many requests (2 per minute)"; + return; + }else{ + load_mess.textContent = err.response.data.message; + return + } + } + load_mess.textContent = "Error while uploading file. See console for more information."; imagefile.value = ""; - } - + } + } diff --git a/web/index.html b/web/index.html index 7a08adf..673e10b 100644 --- a/web/index.html +++ b/web/index.html @@ -79,11 +79,17 @@

File uploader

-

Select file group:

- +

diff --git a/web/res/user_plus.svg b/web/res/user_plus.svg new file mode 100644 index 0000000..d4be380 --- /dev/null +++ b/web/res/user_plus.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/web/snowflakes.js b/web/snowflakes.js index 3cc756f..15df132 100644 --- a/web/snowflakes.js +++ b/web/snowflakes.js @@ -1,9 +1,9 @@ -function getRandomInt(min, max){ return Math.floor(Math.random() * (max - min + 1)) + min; } +function getRandomInt(min, max) { return Math.floor(Math.random() * (max - min + 1)) + min; } let object_array = []; -class Circle{ - constructor(object){ +class Circle { + constructor(object) { this.object = object; this.cnavas_width = 200; this.vw = window.innerWidth; @@ -22,21 +22,21 @@ class Circle{ this.last_scroll = document.documentElement.scrollTop || document.body.scrollTop; } - tick(){ + tick() { let now_time = Date.now(); const circle = this.object; this.posY += this.speedY; this.posX += Math.sin(now_time / 1000) * this.random_sin_mod; - if (this.posY > this.vh){ this.posY = getRandomInt(-this.vw, -50); } - if (this.posX > this.vw){ + if (this.posY > this.vh) { this.posY = getRandomInt(-this.vw, -50); } + if (this.posX > this.vw) { this.posX = -10; } - if (this.posX < -10){ this.posX = this.vw; } + if (this.posX < -10) { this.posX = this.vw; } this.posY -= (document.documentElement.scrollTop || document.body.scrollTop) - this.last_scroll; - if (now_time - this.last_time > this.random_time){ + if (now_time - this.last_time > this.random_time) { this.last_time = now_time; this.random_time = getRandomInt(1000, 5000); this.random_sin_mod = getRandomInt(1, 3); @@ -48,7 +48,7 @@ class Circle{ } } -function run_anim(){ +function run_anim() { let table = document.getElementById('canvas'); let vw = Math.max(document.documentElement.clientWidth || 0, window.innerWidth || 0); let count = 30 @@ -57,24 +57,24 @@ function run_anim(){ let day = date.getDate(); let month = date.getMonth() + 1; if (!((month == 12 && day > 20) || (month == 1 && day < 5)) || vw < 900) return; - - if( localStorage.getItem("disable_snow"))return; - + + if (localStorage.getItem("disable_snow")) return; + let snow_btn = document.getElementById('snow_btn'); snow_btn.style.display = "block"; table.style.display = "block"; - for (let x = 0; x < count; x++){ + for (let x = 0; x < count; x++) { var snowflake = document.createElement("p"); snowflake.innerHTML = "*"; snowflake.className = "snowflake"; table.appendChild(snowflake); object_array.push(new Circle(snowflake)); - + } } -function tick(){ - for (const snowflake of object_array){ +function tick() { + for (const snowflake of object_array) { snowflake.tick(); } } @@ -82,7 +82,7 @@ function tick(){ setInterval(tick, 16); -function stop_anim(){ +function stop_anim() { object_array = []; let table = document.getElementById('canvas'); table.innerHTML = ""; diff --git a/web/style.css b/web/style.css index 1b3da62..4e60040 100644 --- a/web/style.css +++ b/web/style.css @@ -5,8 +5,8 @@ body{ justify-content: center; background-color: #222222; color: #eeeeee; - height: 100%; overflow-x: hidden; + margin: 0; } main{ @@ -286,6 +286,41 @@ nav a:hover{ padding: 5px; max-width: 100%; } + +.group_btn{ + background-color: #222222; + color: #eeeeee; + border: 2px rgb(105, 105, 105) solid; + border-radius: 10px; + width: 31px; + height: 31px; + font-family: 'Roboto Mono', monospace; + font-weight: 600; + font-size: 0.9rem; + padding: 0px; + margin-left: 5px; + transition: background-color 0.2s; + cursor: pointer; +} + +.group_btn:hover{ + background-color: rgb(109, 109, 109); + transition: background-color 0.2s; +} + +#username{ + font-size: 70%; + color: rgb(200, 200, 200) +} + +#invite_users:disabled{ + cursor: default; + border: 2px rgb(68, 68, 68) solid; +} +#invite_users:disabled:hover{ + background-color: #222222; +} + @media(max-width: 425px){ main{width: 90%;} h1{font-size: 130%;}