From 35d33cde99c9a9398797a95d03840d61d83f44bc Mon Sep 17 00:00:00 2001 From: Thinh Date: Thu, 14 Nov 2024 16:15:01 +0700 Subject: [PATCH 1/4] Revert "Revert "Restructure id logic and move private apps to plugins_data collection"" --- backend/database/apps.py | 123 +++++++++++++----------------------- backend/database/plugins.py | 50 --------------- backend/requirements.txt | 2 + backend/routers/apps.py | 111 ++++++++++++++++---------------- backend/routers/chat.py | 10 +-- backend/routers/plugins.py | 38 ++++------- backend/utils/apps.py | 18 ++++-- backend/utils/plugins.py | 7 +- 8 files changed, 138 insertions(+), 221 deletions(-) diff --git a/backend/database/apps.py b/backend/database/apps.py index 32bb01730..8f840982f 100644 --- a/backend/database/apps.py +++ b/backend/database/apps.py @@ -1,13 +1,9 @@ import os -import random -from datetime import datetime, timezone from typing import List -import requests +from google.cloud.firestore_v1.base_query import BaseCompositeFilter, FieldFilter +from ulid import ULID -from models.app import App -from models.plugin import UsageHistoryType -from utils.other.storage import storage_client from ._client import db # ***************************** @@ -17,112 +13,79 @@ omi_plugins_bucket = os.getenv('BUCKET_PLUGINS_LOGOS') -def get_app_by_id_db(app_id: str, uid: str): - if 'private' in app_id: - app_ref = db.collection('users').document(uid).collection('plugins').document(app_id) - else: - app_ref = db.collection('plugins_data').document(app_id) +def get_app_by_id_db(app_id: str): + app_ref = db.collection('plugins_data').document(app_id) doc = app_ref.get() if doc.exists: + if doc.to_dict().get('deleted', True): + return None return doc.to_dict() return None def get_private_apps_db(uid: str) -> List: - private_plugins = db.collection('users').document(uid).collection('plugins').stream() - data = [doc.to_dict() for doc in private_plugins] + filters = [FieldFilter('uid', '==', uid), FieldFilter('private', '==', True), FieldFilter('deleted', '==', False)] + private_apps = db.collection('plugins_data').where(filter=BaseCompositeFilter('AND', filters)).stream(), + data = [doc.to_dict() for doc in private_apps] return data +# This returns public unapproved apps of all users def get_unapproved_public_apps_db() -> List: - public_plugins = db.collection('plugins_data').where('approved', '==', False).stream() - return [doc.to_dict() for doc in public_plugins] + filters = [FieldFilter('approved', '==', False), FieldFilter('private', '==', False), FieldFilter('deleted', '==', False)] + public_apps = db.collection('plugins_data').where(filter=BaseCompositeFilter('AND', filters)).stream() + return [doc.to_dict() for doc in public_apps] def get_public_apps_db(uid: str) -> List: - public_plugins = db.collection('plugins_data').where('approved', '==', True).stream() + public_plugins = db.collection('plugins_data').stream() data = [doc.to_dict() for doc in public_plugins] - # Include the doc if it is not approved but uid matches - unapproved = db.collection('plugins_data').where('approved', '==', False).where('uid', '==', uid).stream() - data.extend([doc.to_dict() for doc in unapproved]) - - return data + return [plugin for plugin in data if plugin['approved'] == True or plugin['uid'] == uid] def get_public_approved_apps_db() -> List: - public_plugins = db.collection('plugins_data').where('approved', '==', True).stream() - return [doc.to_dict() for doc in public_plugins] + filters = [FieldFilter('approved', '==', True), FieldFilter('deleted', '==', False)] + public_apps = db.collection('plugins_data').where(filter=BaseCompositeFilter('AND', filters)).stream() + return [doc.to_dict() for doc in public_apps] +# This returns public unapproved apps for a user def get_public_unapproved_apps_db(uid: str) -> List: - public_plugins = db.collection('plugins_data').where('approved', '==', False).where('uid', '==', uid).stream() - return [doc.to_dict() for doc in public_plugins] - - -def public_app_id_exists_db(app_id: str) -> bool: - app_ref = db.collection('plugins_data').document(app_id) - return app_ref.get().exists - - -def private_app_id_exists_db(app_id: str, uid: str) -> bool: - app_ref = db.collection('users').document(uid).collection('plugins').document(app_id) - return app_ref.get().exists - + filters = [FieldFilter('approved', '==', False), FieldFilter('uid', '==', uid), FieldFilter('deleted', '==', False), FieldFilter('private', '==', False)] + public_apps = db.collection('plugins_data').where(filter=BaseCompositeFilter('AND', filters)).stream() + return [doc.to_dict() for doc in public_apps] -def add_public_app(plugin_data: dict): - plugin_ref = db.collection('plugins_data') - plugin_ref.add(plugin_data, plugin_data['id']) +def add_app_to_db(app_data: dict): + app_ref = db.collection('plugins_data') + app_ref.add(app_data, app_data['id']) -def add_private_app(plugin_data: dict, uid: str): - plugin_ref = db.collection('users').document(uid).collection('plugins') - plugin_ref.add(plugin_data, plugin_data['id']) +def update_app_in_db(app_data: dict): + app_ref = db.collection('plugins_data').document(app_data['id']) + app_ref.update(app_data) -def update_public_app(plugin_data: dict): - plugin_ref = db.collection('plugins_data').document(plugin_data['id']) - plugin_ref.update(plugin_data) - -def update_private_app(plugin_data: dict, uid: str): - plugin_ref = db.collection('users').document(uid).collection('plugins').document(plugin_data['id']) - plugin_ref.update(plugin_data) - - -def delete_private_app(plugin_id: str, uid: str): - plugin_ref = db.collection('users').document(uid).collection('plugins').document(plugin_id) - plugin_ref.update({'deleted': True}) +def delete_app_from_db(app_id: str): + app_ref = db.collection('plugins_data').document(app_id) + app_ref.update({'deleted': True}) -def delete_public_app(plugin_id: str): - plugin_ref = db.collection('plugins_data').document(plugin_id) - plugin_ref.update({'deleted': True}) +def update_app_visibility_in_db(app_id: str, private: bool): + app_ref = db.collection('plugins_data').document(app_id) + if 'private' in app_id and not private: + app = app_ref.get().to_dict() + app_ref.delete() + new_app_id = app_id.split('-private')[0] + str(ULID()) + app['id'] = new_app_id + app['private'] = private + app_ref = db.collection('plugins_data').document(new_app_id) + app_ref.set(app) + else: + app_ref.update({'private': private}) def change_app_approval_status(plugin_id: str, approved: bool): plugin_ref = db.collection('plugins_data').document(plugin_id) plugin_ref.update({'approved': approved, 'status': 'approved' if approved else 'rejected'}) - - -def change_app_visibility_db(app_id: str, private: bool, was_public: bool, uid: str): - if was_public and private: # public -> private - plugin_ref = db.collection('plugins_data').document(app_id) - plugin = plugin_ref.get().to_dict() - plugin_ref.delete() - new_plugin_id = f'{app_id}-private' - plugin['id'] = new_plugin_id - plugin['private'] = private - plugin_ref = db.collection('users').document(uid).collection('plugins').document(new_plugin_id) - plugin_ref.set(plugin) - elif not was_public and not private: # private -> public - plugin_ref = db.collection('users').document(uid).collection('plugins').document(app_id) - plugin = plugin_ref.get().to_dict() - plugin_ref.delete() - new_plugin_id = app_id.split('-private')[0] - plugin['id'] = new_plugin_id - plugin['private'] = private - if public_app_id_exists_db(new_plugin_id): - new_plugin_id = new_plugin_id + '-' + ''.join([str(random.randint(0, 9)) for _ in range(5)]) - plugin_ref = db.collection('plugins_data').document(new_plugin_id) - plugin_ref.set(plugin) diff --git a/backend/database/plugins.py b/backend/database/plugins.py index 9bcd386fe..63b6c9fd7 100644 --- a/backend/database/plugins.py +++ b/backend/database/plugins.py @@ -1,5 +1,4 @@ import os -import random from datetime import datetime, timezone from typing import List @@ -39,55 +38,6 @@ def get_plugin_usage_history(plugin_id: str): return [doc.to_dict() for doc in usage] -def get_plugin_by_id_db(plugin_id: str, uid: str): - if 'private' in plugin_id: - plugin_ref = db.collection('users').document(uid).collection('plugins').document(plugin_id) - else: - plugin_ref = db.collection('plugins_data').document(plugin_id) - doc = plugin_ref.get() - if doc.exists: - return doc.to_dict() - return None - - -def add_public_plugin(plugin_data: dict): - plugin_ref = db.collection('plugins_data') - plugin_ref.add(plugin_data, plugin_data['id']) - - -def add_private_plugin(plugin_data: dict, uid: str): - plugin_ref = db.collection('users').document(uid).collection('plugins') - plugin_ref.add(plugin_data, plugin_data['id']) - - -def get_private_plugins_db(uid: str) -> List: - private_plugins = db.collection('users').document(uid).collection('plugins').stream() - data = [doc.to_dict() for doc in private_plugins] - return data - - -def get_unapproved_public_plugins_db() -> List: - public_plugins = db.collection('plugins_data').where('approved', '==', False).stream() - return [doc.to_dict() for doc in public_plugins] - - -def get_public_plugins_db(uid: str) -> List: - public_plugins = db.collection('plugins_data').stream() - data = [doc.to_dict() for doc in public_plugins] - - return [plugin for plugin in data if plugin['approved'] == True or plugin['uid'] == uid] - - -def public_plugin_id_exists_db(plugin_id: str) -> bool: - plugin_ref = db.collection('plugins_data').document(plugin_id) - return plugin_ref.get().exists - - -def private_plugin_id_exists_db(plugin_id: str, uid: str) -> bool: - plugin_ref = db.collection('users').document(uid).collection('plugins').document(plugin_id) - return plugin_ref.get().exists - - def add_plugin_from_community_json(plugin_data: dict): img = requests.get("https://raw.githubusercontent.com/BasedHardware/Omi/main/" + plugin_data['image'], stream=True) bucket = storage_client.bucket(omi_plugins_bucket) diff --git a/backend/requirements.txt b/backend/requirements.txt index 7d83dd964..b03583c6e 100644 --- a/backend/requirements.txt +++ b/backend/requirements.txt @@ -178,6 +178,8 @@ pyparsing==3.1.2 python-dateutil==2.9.0.post0 python-dotenv==1.0.1 python-multipart==0.0.9 +python-slugify==8.0.4 +python-ulid==3.0.0 pytorch-lightning==2.4.0 pytorch-metric-learning==2.6.0 pytz==2024.1 diff --git a/backend/routers/apps.py b/backend/routers/apps.py index 896a44ef4..085182b10 100644 --- a/backend/routers/apps.py +++ b/backend/routers/apps.py @@ -1,18 +1,18 @@ import json import os -import random from datetime import datetime, timezone from typing import List import requests +from ulid import ULID from fastapi import APIRouter, Depends, Form, UploadFile, File, HTTPException, Header +from slugify import slugify -from database.apps import private_app_id_exists_db, public_app_id_exists_db, add_public_app, add_private_app, \ - get_app_by_id_db, update_private_app, update_public_app, delete_private_app, delete_public_app, \ - change_app_approval_status, change_app_visibility_db, get_unapproved_public_apps_db +from database.apps import change_app_approval_status, get_unapproved_public_apps_db, \ + add_app_to_db, update_app_in_db, delete_app_from_db, update_app_visibility_in_db from database.notifications import get_token_only from database.redis_db import set_plugin_review, delete_generic_cache, increase_plugin_installs_count, enable_plugin, \ disable_plugin, decrease_plugin_installs_count -from utils.apps import get_apps_data_from_db +from utils.apps import get_available_apps, get_available_app_by_id, get_approved_available_apps from utils.notifications import send_notification from utils.other import endpoints as auth from models.app import App @@ -27,12 +27,12 @@ @router.get('/v1/apps', tags=['v1'], response_model=List[App]) def get_apps(uid: str = Depends(auth.get_current_user_uid), include_reviews: bool = True): - return get_apps_data_from_db(uid, include_reviews=include_reviews) + return get_available_apps(uid, include_reviews=include_reviews) -@router.get('/v1/approved-apps', tags=['v1'], response_model=App) -def get_approved_apps(uid: str = Depends(auth.get_current_user_uid)): - return get_apps_data_from_db(uid, include_reviews=False) +@router.get('/v1/approved-apps', tags=['v1'], response_model=List[App]) +def get_approved_apps(): + return get_approved_available_apps(include_reviews=False) @router.post('/v1/apps', tags=['v1']) @@ -41,17 +41,8 @@ def submit_app(app_data: str = Form(...), file: UploadFile = File(...), uid=Depe data['approved'] = False data['status'] = 'under-review' data['name'] = data['name'].strip() - data['id'] = data['name'].replace(' ', '-').lower() - data['uid'] = uid - data['id'] = data['id'].replace(',', '-') - data['id'] = data['id'].replace("'", '') - if 'private' in data and data['private']: - data['id'] = data['id'] + '-private' - if private_app_id_exists_db(data['id'], uid): - data['id'] = data['id'] + '-' + ''.join([str(random.randint(0, 9)) for _ in range(5)]) - else: - if public_app_id_exists_db(data['id']): - data['id'] = data['id'] + '-' + ''.join([str(random.randint(0, 9)) for _ in range(5)]) + new_app_id = slugify(data['name']) + '-' + str(ULID()) + data['id'] = new_app_id if external_integration := data.get('external_integration'): # check if setup_instructions_file_path is a single url or a just a string of text if external_integration.get('setup_instructions_file_path'): @@ -68,11 +59,7 @@ def submit_app(app_data: str = Form(...), file: UploadFile = File(...), uid=Depe imgUrl = upload_plugin_logo(file_path, data['id']) data['image'] = imgUrl data['created_at'] = datetime.now(timezone.utc) - if data.get('private', True): - print("Adding private app") - add_private_app(data, data['uid']) - else: - add_public_app(data) + add_app_to_db(data) return {'status': 'ok'} @@ -80,9 +67,9 @@ def submit_app(app_data: str = Form(...), file: UploadFile = File(...), uid=Depe def update_app(app_id: str, app_data: str = Form(...), file: UploadFile = File(None), uid=Depends(auth.get_current_user_uid)): data = json.loads(app_data) - plugin = get_app_by_id_db(app_id, uid) + plugin = get_available_app_by_id(app_id, uid) if not plugin: - raise HTTPException(status_code=404, detail='Plugin not found') + raise HTTPException(status_code=404, detail='App not found') if plugin['uid'] != uid: raise HTTPException(status_code=403, detail='You are not authorized to perform this action') if file: @@ -91,36 +78,37 @@ def update_app(app_id: str, app_data: str = Form(...), file: UploadFile = File(N file_path = f"_temp/plugins/{file.filename}" with open(file_path, 'wb') as f: f.write(file.file.read()) - imgUrl = upload_plugin_logo(file_path, app_id) - data['image'] = imgUrl - if data.get('private', True): - update_private_app(data, uid) - else: - update_public_app(data) + img_url = upload_plugin_logo(file_path, app_id) + data['image'] = img_url + data['updated_at'] = datetime.now(timezone.utc) + update_app_in_db(data) return {'status': 'ok'} @router.delete('/v1/apps/{app_id}', tags=['v1']) def delete_app(app_id: str, uid: str = Depends(auth.get_current_user_uid)): - plugin = get_app_by_id_db(app_id, uid) + plugin = get_available_app_by_id(app_id, uid) if not plugin: raise HTTPException(status_code=404, detail='App not found') if plugin['uid'] != uid: raise HTTPException(status_code=403, detail='You are not authorized to perform this action') - if plugin['private']: - delete_private_app(app_id, uid) - else: - delete_public_app(app_id) - if plugin['approved']: - delete_generic_cache('get_public_approved_apps_data') + delete_app_from_db(app_id) + if plugin['approved']: + delete_generic_cache('get_public_approved_apps_data') return {'status': 'ok'} @router.get('/v1/apps/{app_id}', tags=['v1']) def get_app_details(app_id: str, uid: str = Depends(auth.get_current_user_uid)): - app = get_app_by_id_db(app_id, uid) + app = get_available_app_by_id(app_id, uid) + app = App(**app) if app else None if not app: raise HTTPException(status_code=404, detail='App not found') + if not app.approved: + raise HTTPException(status_code=404, detail='App not found') + if app.private is None: + if app.private and app.uid != uid: + raise HTTPException(status_code=403, detail='You are not authorized to view this app') return app @@ -129,9 +117,16 @@ def review_app(app_id: str, data: dict, uid: str = Depends(auth.get_current_user if 'score' not in data: raise HTTPException(status_code=422, detail='Score is required') - plugin = get_app_by_id_db(app_id, uid) - if not plugin: - raise HTTPException(status_code=404, detail='Plugin not found') + app = get_available_app_by_id(app_id, uid) + app = App(**app) if app else None + if not app: + raise HTTPException(status_code=404, detail='App not found') + + if app.uid == uid: + raise HTTPException(status_code=403, detail='You are not authorized to review your own app') + + if app.private and app.uid != uid: + raise HTTPException(status_code=403, detail='You are not authorized to review this app') score = data['score'] review = data.get('review', '') @@ -141,11 +136,13 @@ def review_app(app_id: str, data: dict, uid: str = Depends(auth.get_current_user @router.patch('/v1/apps/{app_id}/change-visibility', tags=['v1']) def change_app_visibility(app_id: str, private: bool, uid: str = Depends(auth.get_current_user_uid)): - app = get_app_by_id_db(app_id, uid) + app = get_available_app_by_id(app_id, uid) + app = App(**app) if app else None if not app: - raise HTTPException(status_code=404, detail='Plugin not found') - was_public = not app['deleted'] and not app['private'] - change_app_visibility_db(app_id, private, was_public, uid) + raise HTTPException(status_code=404, detail='App not found') + if app.uid != uid: + raise HTTPException(status_code=403, detail='You are not authorized to perform this action') + update_app_visibility_in_db(app_id, private) return {'status': 'ok'} @@ -179,10 +176,13 @@ def get_plugin_capabilities(): @router.post('/v1/apps/enable') def enable_app(app_id: str, uid: str = Depends(auth.get_current_user_uid)): - app = get_app_by_id_db(app_id, uid) - app = App(**app) + app = get_available_app_by_id(app_id, uid) + app = App(**app) if app else None if not app: raise HTTPException(status_code=404, detail='App not found') + if app.private is not None: + if app.private and app.uid != uid: + raise HTTPException(status_code=403, detail='You are not authorized to perform this action') if app.works_externally() and app.external_integration.setup_completed_url: res = requests.get(app.external_integration.setup_completed_url + f'?uid={uid}') print('enable_app_endpoint', res.status_code, res.content) @@ -195,10 +195,13 @@ def enable_app(app_id: str, uid: str = Depends(auth.get_current_user_uid)): @router.post('/v1/apps/disable') def disable_app(app_id: str, uid: str = Depends(auth.get_current_user_uid)): - app = get_app_by_id_db(app_id, uid) - app = App(**app) + app = get_available_app_by_id(app_id, uid) + app = App(**app) if app else None if not app: raise HTTPException(status_code=404, detail='App not found') + if app.private is None: + if app.private and app.uid != uid: + raise HTTPException(status_code=403, detail='You are not authorized to perform this action') disable_plugin(uid, app_id) decrease_plugin_installs_count(app_id) return {'status': 'ok'} @@ -221,7 +224,7 @@ def approve_app(app_id: str, uid: str, secret_key: str = Header(...)): if secret_key != os.getenv('ADMIN_KEY'): raise HTTPException(status_code=403, detail='You are not authorized to perform this action') change_app_approval_status(app_id, True) - app = get_app_by_id_db(app_id, uid) + app = get_available_app_by_id(app_id, uid) token = get_token_only(uid) if token: send_notification(token, 'App Approved 🎉', @@ -234,7 +237,7 @@ def reject_app(app_id: str, uid: str, secret_key: str = Header(...)): if secret_key != os.getenv('ADMIN_KEY'): raise HTTPException(status_code=403, detail='You are not authorized to perform this action') change_app_approval_status(app_id, False) - app = get_app_by_id_db(app_id, uid) + app = get_available_app_by_id(app_id, uid) token = get_token_only(uid) if token: # TODO: Add reason for rejection in payload and also redirect to the plugin page diff --git a/backend/routers/chat.py b/backend/routers/chat.py index 33c860d98..898392432 100644 --- a/backend/routers/chat.py +++ b/backend/routers/chat.py @@ -5,12 +5,12 @@ from fastapi import APIRouter, Depends, HTTPException import database.chat as chat_db -from database.apps import get_app_by_id_db from database.plugins import record_plugin_usage from models.app import App -from models.chat import Message, SendMessageRequest, MessageSender, ResponseMessage -from models.memory import Memory from models.plugin import UsageHistoryType +from models.memory import Memory +from models.chat import Message, SendMessageRequest, MessageSender, ResponseMessage +from utils.apps import get_available_app_by_id from utils.llm import initial_chat_message from utils.other import endpoints as auth from utils.retrieval.graph import execute_graph_chat @@ -39,7 +39,7 @@ def send_message( ) chat_db.add_message(uid, message.dict()) - plugin = get_app_by_id_db(plugin_id, uid) + plugin = get_available_app_by_id(plugin_id, uid) plugin = App(**plugin) if plugin else None plugin_id = plugin.id if plugin else None @@ -84,7 +84,7 @@ def clear_chat_messages(uid: str = Depends(auth.get_current_user_uid)): def initial_message_util(uid: str, plugin_id: Optional[str] = None): - plugin = get_app_by_id_db(plugin_id, uid) + plugin = get_available_app_by_id(plugin_id, uid) plugin = App(**plugin) if plugin else None text = initial_chat_message(uid, plugin) diff --git a/backend/routers/plugins.py b/backend/routers/plugins.py index 89d91ca60..134066805 100644 --- a/backend/routers/plugins.py +++ b/backend/routers/plugins.py @@ -8,14 +8,16 @@ import requests from fastapi import APIRouter, HTTPException, Depends, UploadFile from fastapi.params import File, Form +from slugify import slugify +from ulid import ULID -from database.apps import get_app_by_id_db -from database.plugins import get_plugin_usage_history, add_public_plugin, add_private_plugin, \ - public_plugin_id_exists_db, private_plugin_id_exists_db, get_plugin_by_id_db +from database.apps import add_app_to_db +from database.plugins import get_plugin_usage_history from database.redis_db import set_plugin_review, enable_plugin, disable_plugin, increase_plugin_installs_count, \ decrease_plugin_installs_count from models.app import App from models.plugin import Plugin, UsageHistoryItem, UsageHistoryType +from utils.apps import get_available_app_by_id from utils.other import endpoints as auth from utils.other.storage import upload_plugin_logo from utils.plugins import get_plugins_data, get_plugin_by_id, get_plugins_data_from_db @@ -25,8 +27,8 @@ @router.post('/v1/plugins/enable') def enable_plugin_endpoint(plugin_id: str, uid: str = Depends(auth.get_current_user_uid)): - plugin = get_app_by_id_db(plugin_id, uid) - plugin = App(**plugin) + plugin = get_available_app_by_id(plugin_id, uid) + plugin = App(**plugin) if plugin else None if not plugin: raise HTTPException(status_code=404, detail='Plugin not found') if plugin.works_externally() and plugin.external_integration.setup_completed_url: @@ -41,10 +43,10 @@ def enable_plugin_endpoint(plugin_id: str, uid: str = Depends(auth.get_current_u @router.post('/v1/plugins/disable') def disable_plugin_endpoint(plugin_id: str, uid: str = Depends(auth.get_current_user_uid)): - plugin = get_app_by_id_db(plugin_id, uid) - plugin = App(**plugin) + plugin = get_available_app_by_id(plugin_id, uid) + plugin = App(**plugin) if plugin else None if not plugin: - raise HTTPException(status_code=404, detail='Plugin not found') + raise HTTPException(status_code=404, detail='App not found') disable_plugin(uid, plugin_id) decrease_plugin_installs_count(plugin_id) return {'status': 'ok'} @@ -70,7 +72,7 @@ def review_plugin(plugin_id: str, data: dict, uid: str = Depends(auth.get_curren if 'score' not in data: raise HTTPException(status_code=422, detail='Score is required') - plugin = get_plugin_by_id_db(plugin_id, uid) + plugin = get_available_app_by_id(plugin_id, uid) if not plugin: raise HTTPException(status_code=404, detail='Plugin not found') @@ -137,15 +139,8 @@ def add_plugin(plugin_data: str = Form(...), file: UploadFile = File(...), uid=D data = json.loads(plugin_data) data['approved'] = False data['name'] = data['name'].strip() - data['id'] = data['name'].replace(' ', '-').lower() - data['uid'] = uid - if 'private' in data and data['private']: - data['id'] = data['id'] + '-private' - if private_plugin_id_exists_db(data['id'], uid): - data['id'] = data['id'] + '-' + ''.join([str(random.randint(0, 9)) for _ in range(5)]) - else: - if public_plugin_id_exists_db(data['id']): - data['id'] = data['id'] + '-' + ''.join([str(random.randint(0, 9)) for _ in range(5)]) + new_app_id = slugify(data['name']) + '-' + str(ULID()) + data['id'] = new_app_id os.makedirs(f'_temp/plugins', exist_ok=True) file_path = f"_temp/plugins/{file.filename}" with open(file_path, 'wb') as f: @@ -153,12 +148,7 @@ def add_plugin(plugin_data: str = Form(...), file: UploadFile = File(...), uid=D imgUrl = upload_plugin_logo(file_path, data['id']) data['image'] = imgUrl data['created_at'] = datetime.now(timezone.utc) - if data.get('private', True): - print("Adding private plugin") - add_private_plugin(data, data['uid']) - else: - add_public_plugin(data) - # delete_generic_cache('get_public_plugins_data') + add_app_to_db(data) return {'status': 'ok'} diff --git a/backend/utils/apps.py b/backend/utils/apps.py index 0e2018d06..0fdadc457 100644 --- a/backend/utils/apps.py +++ b/backend/utils/apps.py @@ -1,15 +1,14 @@ from typing import List -from database.apps import get_private_apps_db, get_public_apps_db, get_public_unapproved_apps_db, \ - get_public_approved_apps_db -from database.plugins import get_private_plugins_db +from database.apps import get_private_apps_db, get_public_unapproved_apps_db, \ + get_public_approved_apps_db, get_app_by_id_db from database.redis_db import get_enabled_plugins, get_plugin_installs_count, get_plugin_reviews, get_generic_cache, \ set_generic_cache from models.app import App from utils.plugins import weighted_rating -def get_apps_data_from_db(uid: str, include_reviews: bool = False) -> List[App]: +def get_available_apps(uid: str, include_reviews: bool = False) -> List[App]: private_data = [] public_approved_data = [] public_unapproved_data = [] @@ -48,7 +47,16 @@ def get_apps_data_from_db(uid: str, include_reviews: bool = False) -> List[App]: return apps -def get_approved_apps_data_from_db(include_reviews: bool = False) -> List[App]: +def get_available_app_by_id(app_id: str, uid: str | None) -> dict | None: + app = get_app_by_id_db(app_id) + if not app: + return None + if app['private'] and app['uid'] != uid: + return None + return app + + +def get_approved_available_apps(include_reviews: bool = False) -> list[App]: all_apps = [] if cached_apps := get_generic_cache('get_public_approved_apps_data'): print('get_public_approved_apps_data from cache') diff --git a/backend/utils/plugins.py b/backend/utils/plugins.py index 5a5a2fea0..8a2227aa8 100644 --- a/backend/utils/plugins.py +++ b/backend/utils/plugins.py @@ -4,8 +4,9 @@ import requests import database.notifications as notification_db +from database.apps import get_private_apps_db, get_public_apps_db from database.chat import add_plugin_message -from database.plugins import record_plugin_usage, get_private_plugins_db, get_public_plugins_db +from database.plugins import record_plugin_usage from database.redis_db import get_enabled_plugins, get_plugin_reviews, get_plugin_installs_count, get_generic_cache, \ set_generic_cache from models.memory import Memory, MemorySource @@ -88,8 +89,8 @@ def get_plugins_data_from_db(uid: str, include_reviews: bool = False) -> List[Pl # private_data = get_private_plugins_db(uid) # pass # else: - private_data = get_private_plugins_db(uid) - public_data = get_public_plugins_db(uid) + private_data = get_private_apps_db(uid) + public_data = get_public_apps_db(uid) # set_generic_cache('get_public_plugins_data', public_data, 60 * 10) # 10 minutes cached user_enabled = set(get_enabled_plugins(uid)) all_plugins = private_data + public_data From 21153bcd6052f08e32de84cb1a353b18c8291209 Mon Sep 17 00:00:00 2001 From: Mohammed Mohsin <59914433+mdmohsin7@users.noreply.github.com> Date: Thu, 14 Nov 2024 14:48:08 +0530 Subject: [PATCH 2/4] remove comma --- backend/database/apps.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/backend/database/apps.py b/backend/database/apps.py index 8f840982f..74d401793 100644 --- a/backend/database/apps.py +++ b/backend/database/apps.py @@ -25,7 +25,7 @@ def get_app_by_id_db(app_id: str): def get_private_apps_db(uid: str) -> List: filters = [FieldFilter('uid', '==', uid), FieldFilter('private', '==', True), FieldFilter('deleted', '==', False)] - private_apps = db.collection('plugins_data').where(filter=BaseCompositeFilter('AND', filters)).stream(), + private_apps = db.collection('plugins_data').where(filter=BaseCompositeFilter('AND', filters)).stream() data = [doc.to_dict() for doc in private_apps] return data From e437e330e1c41e64bf0af107a763c2b5a4fdd939 Mon Sep 17 00:00:00 2001 From: Mohammed Mohsin <59914433+mdmohsin7@users.noreply.github.com> Date: Thu, 14 Nov 2024 16:05:17 +0530 Subject: [PATCH 3/4] fix missed edge case in get_app_details and also return reviews with it --- backend/database/apps.py | 3 ++- backend/routers/apps.py | 13 +++++++------ backend/utils/apps.py | 15 +++++++++++++++ 3 files changed, 24 insertions(+), 7 deletions(-) diff --git a/backend/database/apps.py b/backend/database/apps.py index 74d401793..e1ba503c9 100644 --- a/backend/database/apps.py +++ b/backend/database/apps.py @@ -19,7 +19,8 @@ def get_app_by_id_db(app_id: str): if doc.exists: if doc.to_dict().get('deleted', True): return None - return doc.to_dict() + else: + return doc.to_dict() return None diff --git a/backend/routers/apps.py b/backend/routers/apps.py index 085182b10..f78dedfc7 100644 --- a/backend/routers/apps.py +++ b/backend/routers/apps.py @@ -12,7 +12,8 @@ from database.notifications import get_token_only from database.redis_db import set_plugin_review, delete_generic_cache, increase_plugin_installs_count, enable_plugin, \ disable_plugin, decrease_plugin_installs_count -from utils.apps import get_available_apps, get_available_app_by_id, get_approved_available_apps +from utils.apps import get_available_apps, get_available_app_by_id, get_approved_available_apps, \ + get_available_app_by_id_with_reviews from utils.notifications import send_notification from utils.other import endpoints as auth from models.app import App @@ -31,8 +32,8 @@ def get_apps(uid: str = Depends(auth.get_current_user_uid), include_reviews: boo @router.get('/v1/approved-apps', tags=['v1'], response_model=List[App]) -def get_approved_apps(): - return get_approved_available_apps(include_reviews=False) +def get_approved_apps(include_reviews: bool = False): + return get_approved_available_apps(include_reviews=include_reviews) @router.post('/v1/apps', tags=['v1']) @@ -100,13 +101,13 @@ def delete_app(app_id: str, uid: str = Depends(auth.get_current_user_uid)): @router.get('/v1/apps/{app_id}', tags=['v1']) def get_app_details(app_id: str, uid: str = Depends(auth.get_current_user_uid)): - app = get_available_app_by_id(app_id, uid) + app = get_available_app_by_id_with_reviews(app_id, uid) app = App(**app) if app else None if not app: raise HTTPException(status_code=404, detail='App not found') - if not app.approved: + if not app.approved and app.uid != uid: raise HTTPException(status_code=404, detail='App not found') - if app.private is None: + if app.private is not None: if app.private and app.uid != uid: raise HTTPException(status_code=403, detail='You are not authorized to view this app') return app diff --git a/backend/utils/apps.py b/backend/utils/apps.py index 0fdadc457..e3d413b52 100644 --- a/backend/utils/apps.py +++ b/backend/utils/apps.py @@ -56,6 +56,21 @@ def get_available_app_by_id(app_id: str, uid: str | None) -> dict | None: return app +def get_available_app_by_id_with_reviews(app_id: str, uid: str | None) -> dict | None: + app = get_app_by_id_db(app_id) + if not app: + return None + if app['private'] and app['uid'] != uid: + return None + reviews = get_plugin_reviews(app['id']) + sorted_reviews = reviews.values() + rating_avg = sum([x['score'] for x in sorted_reviews]) / len(sorted_reviews) if reviews else None + app['reviews'] = [] + app['rating_avg'] = rating_avg + app['rating_count'] = len(sorted_reviews) + return app + + def get_approved_available_apps(include_reviews: bool = False) -> list[App]: all_apps = [] if cached_apps := get_generic_cache('get_public_approved_apps_data'): From 02e1f4897f80c0a584856791026e1ed100339451 Mon Sep 17 00:00:00 2001 From: Mohammed Mohsin <59914433+mdmohsin7@users.noreply.github.com> Date: Thu, 14 Nov 2024 16:19:22 +0530 Subject: [PATCH 4/4] include hyphen if changing app id while changing visibility --- backend/database/apps.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/backend/database/apps.py b/backend/database/apps.py index e1ba503c9..7c5ad6294 100644 --- a/backend/database/apps.py +++ b/backend/database/apps.py @@ -78,7 +78,7 @@ def update_app_visibility_in_db(app_id: str, private: bool): if 'private' in app_id and not private: app = app_ref.get().to_dict() app_ref.delete() - new_app_id = app_id.split('-private')[0] + str(ULID()) + new_app_id = app_id.split('-private')[0] + '-' + str(ULID()) app['id'] = new_app_id app['private'] = private app_ref = db.collection('plugins_data').document(new_app_id)