| 
									
										
										
										
											2024-04-12 16:22:24 +08:00
										 |  |  | from datetime import datetime, timezone | 
					
						
							| 
									
										
										
										
											2023-05-25 15:54:45 +08:00
										 |  |  | 
 | 
					
						
							| 
									
										
										
										
											2024-02-06 13:21:13 +08:00
										 |  |  | from flask_login import current_user | 
					
						
							|  |  |  | from flask_restful import Resource, inputs, marshal_with, reqparse | 
					
						
							|  |  |  | from sqlalchemy import and_ | 
					
						
							|  |  |  | from werkzeug.exceptions import BadRequest, Forbidden, NotFound | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2023-05-25 15:54:45 +08:00
										 |  |  | from controllers.console import api | 
					
						
							|  |  |  | from controllers.console.explore.wraps import InstalledAppResource | 
					
						
							| 
									
										
										
										
											2024-01-12 12:34:01 +08:00
										 |  |  | from controllers.console.wraps import account_initialization_required, cloud_edition_billing_resource_check | 
					
						
							| 
									
										
										
										
											2023-05-25 15:54:45 +08:00
										 |  |  | from extensions.ext_database import db | 
					
						
							| 
									
										
										
										
											2023-09-27 16:06:32 +08:00
										 |  |  | from fields.installed_app_fields import installed_app_list_fields | 
					
						
							| 
									
										
										
										
											2024-01-12 12:34:01 +08:00
										 |  |  | from libs.login import login_required | 
					
						
							| 
									
										
										
										
											2023-05-25 15:54:45 +08:00
										 |  |  | from models.model import App, InstalledApp, RecommendedApp | 
					
						
							|  |  |  | from services.account_service import TenantService | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | class InstalledAppsListApi(Resource): | 
					
						
							|  |  |  |     @login_required | 
					
						
							|  |  |  |     @account_initialization_required | 
					
						
							|  |  |  |     @marshal_with(installed_app_list_fields) | 
					
						
							|  |  |  |     def get(self): | 
					
						
							|  |  |  |         current_tenant_id = current_user.current_tenant_id | 
					
						
							|  |  |  |         installed_apps = db.session.query(InstalledApp).filter( | 
					
						
							|  |  |  |             InstalledApp.tenant_id == current_tenant_id | 
					
						
							|  |  |  |         ).all() | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |         current_user.role = TenantService.get_user_role(current_user, current_user.current_tenant) | 
					
						
							|  |  |  |         installed_apps = [ | 
					
						
							|  |  |  |             { | 
					
						
							|  |  |  |                 'id': installed_app.id, | 
					
						
							|  |  |  |                 'app': installed_app.app, | 
					
						
							|  |  |  |                 'app_owner_tenant_id': installed_app.app_owner_tenant_id, | 
					
						
							|  |  |  |                 'is_pinned': installed_app.is_pinned, | 
					
						
							|  |  |  |                 'last_used_at': installed_app.last_used_at, | 
					
						
							| 
									
										
										
										
											2024-01-23 19:58:23 +08:00
										 |  |  |                 'editable': current_user.role in ["owner", "admin"], | 
					
						
							| 
									
										
										
										
											2024-04-08 18:51:46 +08:00
										 |  |  |                 'uninstallable': current_tenant_id == installed_app.app_owner_tenant_id | 
					
						
							| 
									
										
										
										
											2023-05-25 15:54:45 +08:00
										 |  |  |             } | 
					
						
							|  |  |  |             for installed_app in installed_apps | 
					
						
							|  |  |  |         ] | 
					
						
							| 
									
										
										
										
											2023-11-13 22:05:46 +08:00
										 |  |  |         installed_apps.sort(key=lambda app: (-app['is_pinned'], | 
					
						
							|  |  |  |                                              app['last_used_at'] is None, | 
					
						
							|  |  |  |                                              -app['last_used_at'].timestamp() if app['last_used_at'] is not None else 0)) | 
					
						
							| 
									
										
										
										
											2023-05-25 15:54:45 +08:00
										 |  |  | 
 | 
					
						
							|  |  |  |         return {'installed_apps': installed_apps} | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |     @login_required | 
					
						
							|  |  |  |     @account_initialization_required | 
					
						
							| 
									
										
										
										
											2023-12-05 16:53:55 +08:00
										 |  |  |     @cloud_edition_billing_resource_check('apps') | 
					
						
							| 
									
										
										
										
											2023-05-25 15:54:45 +08:00
										 |  |  |     def post(self): | 
					
						
							|  |  |  |         parser = reqparse.RequestParser() | 
					
						
							|  |  |  |         parser.add_argument('app_id', type=str, required=True, help='Invalid app_id') | 
					
						
							|  |  |  |         args = parser.parse_args() | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |         recommended_app = RecommendedApp.query.filter(RecommendedApp.app_id == args['app_id']).first() | 
					
						
							|  |  |  |         if recommended_app is None: | 
					
						
							|  |  |  |             raise NotFound('App not found') | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |         current_tenant_id = current_user.current_tenant_id | 
					
						
							|  |  |  |         app = db.session.query(App).filter( | 
					
						
							|  |  |  |             App.id == args['app_id'] | 
					
						
							|  |  |  |         ).first() | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |         if app is None: | 
					
						
							|  |  |  |             raise NotFound('App not found') | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |         if not app.is_public: | 
					
						
							|  |  |  |             raise Forbidden('You can\'t install a non-public app') | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |         installed_app = InstalledApp.query.filter(and_( | 
					
						
							|  |  |  |             InstalledApp.app_id == args['app_id'], | 
					
						
							|  |  |  |             InstalledApp.tenant_id == current_tenant_id | 
					
						
							|  |  |  |         )).first() | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |         if installed_app is None: | 
					
						
							|  |  |  |             # todo: position | 
					
						
							|  |  |  |             recommended_app.install_count += 1 | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |             new_installed_app = InstalledApp( | 
					
						
							|  |  |  |                 app_id=args['app_id'], | 
					
						
							|  |  |  |                 tenant_id=current_tenant_id, | 
					
						
							|  |  |  |                 app_owner_tenant_id=app.tenant_id, | 
					
						
							|  |  |  |                 is_pinned=False, | 
					
						
							| 
									
										
										
										
											2024-04-12 16:22:24 +08:00
										 |  |  |                 last_used_at=datetime.now(timezone.utc).replace(tzinfo=None) | 
					
						
							| 
									
										
										
										
											2023-05-25 15:54:45 +08:00
										 |  |  |             ) | 
					
						
							|  |  |  |             db.session.add(new_installed_app) | 
					
						
							|  |  |  |             db.session.commit() | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |         return {'message': 'App installed successfully'} | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | class InstalledAppApi(InstalledAppResource): | 
					
						
							|  |  |  |     """
 | 
					
						
							|  |  |  |     update and delete an installed app | 
					
						
							|  |  |  |     use InstalledAppResource to apply default decorators and get installed_app | 
					
						
							|  |  |  |     """
 | 
					
						
							|  |  |  |     def delete(self, installed_app): | 
					
						
							|  |  |  |         if installed_app.app_owner_tenant_id == current_user.current_tenant_id: | 
					
						
							|  |  |  |             raise BadRequest('You can\'t uninstall an app owned by the current tenant') | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |         db.session.delete(installed_app) | 
					
						
							|  |  |  |         db.session.commit() | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |         return {'result': 'success', 'message': 'App uninstalled successfully'} | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |     def patch(self, installed_app): | 
					
						
							|  |  |  |         parser = reqparse.RequestParser() | 
					
						
							|  |  |  |         parser.add_argument('is_pinned', type=inputs.boolean) | 
					
						
							|  |  |  |         args = parser.parse_args() | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |         commit_args = False | 
					
						
							|  |  |  |         if 'is_pinned' in args: | 
					
						
							|  |  |  |             installed_app.is_pinned = args['is_pinned'] | 
					
						
							|  |  |  |             commit_args = True | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |         if commit_args: | 
					
						
							|  |  |  |             db.session.commit() | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |         return {'result': 'success', 'message': 'App info updated successfully'} | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | api.add_resource(InstalledAppsListApi, '/installed-apps') | 
					
						
							|  |  |  | api.add_resource(InstalledAppApi, '/installed-apps/<uuid:installed_app_id>') |