diff --git a/api/v1/pages/extension_manage/__init__.py b/api/v1/pages/extension_manage/__init__.py index 5feb9eb3c..64d623d86 100644 --- a/api/v1/pages/extension_manage/__init__.py +++ b/api/v1/pages/extension_manage/__init__.py @@ -1,6 +1,6 @@ from arkid.core import routers from arkid.core.translation import gettext_default as _ -from . import extension_admin,extension_manage,extension_psc +from . import extension_admin,extension_manage,extension_psc,extension_buy,extension_rent router = routers.FrontRouter( path='extension_manage', @@ -10,5 +10,7 @@ extension_manage.router, extension_admin.router, extension_psc.router, + extension_buy.router, + extension_rent.router ] ) \ No newline at end of file diff --git a/api/v1/pages/extension_manage/extension_buy.py b/api/v1/pages/extension_manage/extension_buy.py new file mode 100644 index 000000000..b63d62fb2 --- /dev/null +++ b/api/v1/pages/extension_manage/extension_buy.py @@ -0,0 +1,69 @@ +from arkid.core import routers, pages, actions +from arkid.core.translation import gettext_default as _ + +tag = 'extension_buy' +name = '插件购买' + +page = pages.StepPage(tag=tag, name=name) +price_page = pages.CardsPage(name='选择价格') +copies_page = pages.FormPage(name='人天份数') +payment_page = pages.FormPage(name='支付') + +router = routers.FrontRouter( + path=tag, + name=name, + page=page, + icon='extension_buy', + hidden=True +) + +pages.register_front_pages(page) +pages.register_front_pages(price_page) +pages.register_front_pages(copies_page) +pages.register_front_pages(payment_page) + +page.add_pages([ + price_page, + copies_page, + payment_page +]) + +price_page.create_actions( + init_action=actions.DirectAction( + path='/api/v1/tenant/{tenant_id}/arkstore/order/extensions/{uuid}/', + method=actions.FrontActionMethod.GET + ), + local_actions={ + 'next': actions.NextAction( + name="选择价格" + ), + } +) + +copies_page.create_actions( + init_action=actions.DirectAction( + path="/api/v1/tenant/{tenant_id}/arkstore/order/extensions/{uuid}/set_copies/", + method=actions.FrontActionMethod.POST + ), + global_actions={ + 'next': actions.NextAction( + name="创建订单", + path="/api/v1/tenant/{tenant_id}/arkstore/order/extensions/{uuid}/", + method=actions.FrontActionMethod.POST + ), + } +) + +payment_page.create_actions( + init_action=actions.DirectAction( + path="/api/v1/tenant/{tenant_id}/arkstore/order/{order_no}/payment/", + method=actions.FrontActionMethod.GET + ), + global_actions={ + 'next': actions.NextAction( + name="已支付", + path="/api/v1/tenant/{tenant_id}/arkstore/purchase/order/{order_no}/payment_status/extensions/{uuid}/", + method=actions.FrontActionMethod.GET + ), + } +) \ No newline at end of file diff --git a/api/v1/pages/extension_manage/extension_psc.py b/api/v1/pages/extension_manage/extension_psc.py index 69a4713ce..3864ea8c5 100644 --- a/api/v1/pages/extension_manage/extension_psc.py +++ b/api/v1/pages/extension_manage/extension_psc.py @@ -2,20 +2,24 @@ from arkid.core import routers, pages, actions from arkid.core.translation import gettext_default as _ from .extension_admin import profile_page -from .extension_manage import setting_page,config_page +from .extension_manage import setting_page tag = 'extension_psc' name = '插件配置' page = pages.TabsPage(tag=tag, name=name) +config_page = pages.TablePage(name='插件运行时配置') router = routers.FrontRouter( path=tag, name=name, page=page, icon='extension_psc', + hidden=True ) pages.register_front_pages(page) +pages.register_front_pages(config_page) + page.add_pages( [profile_page, @@ -28,4 +32,20 @@ path='/api/v1/extensions/{id}/', method=actions.FrontActionMethod.GET, ), +) + +config_page.create_actions( + init_action=actions.DirectAction( + path='/api/v1/tenant/{tenant_id}/extension/{extension_id}/config/', + method=actions.FrontActionMethod.GET + ), + global_actions={ + 'new': actions.CreateAction( + path="/api/v1/tenant/{tenant_id}/extension/{extension_id}/config/", + init_data={ + 'package': 'package', + # 'type': 'type' + } + ), + }, ) \ No newline at end of file diff --git a/api/v1/pages/extension_manage/extension_rent.py b/api/v1/pages/extension_manage/extension_rent.py new file mode 100644 index 000000000..8ab8bbf90 --- /dev/null +++ b/api/v1/pages/extension_manage/extension_rent.py @@ -0,0 +1,69 @@ +from arkid.core import routers, pages, actions +from arkid.core.translation import gettext_default as _ + +tag = 'extension_rent' +name = '插件租赁' + +page = pages.StepPage(tag=tag, name=name) +price_page = pages.CardsPage(name='选择价格') +copies_page = pages.FormPage(name='人天份数') +payment_page = pages.FormPage(name='支付') + +router = routers.FrontRouter( + path=tag, + name=name, + page=page, + icon='extension_rent', + hidden=True +) + +pages.register_front_pages(page) +pages.register_front_pages(price_page) +pages.register_front_pages(copies_page) +pages.register_front_pages(payment_page) + +page.add_pages([ + price_page, + copies_page, + payment_page +]) + +price_page.create_actions( + init_action=actions.DirectAction( + path='/api/v1/tenant/{tenant_id}/arkstore/rent/extensions/{package}/', + method=actions.FrontActionMethod.GET + ), + local_actions={ + 'next': actions.NextAction( + name="选择价格" + ), + } +) + +copies_page.create_actions( + init_action=actions.DirectAction( + path="/api/v1/tenant/{tenant_id}/arkstore/order/extensions/{uuid}/set_copies/", + method=actions.FrontActionMethod.POST + ), + global_actions={ + 'next': actions.NextAction( + name="创建订单", + path="/api/v1/tenant/{tenant_id}/arkstore/rent/extensions/{package}/", + method=actions.FrontActionMethod.POST + ), + } +) + +payment_page.create_actions( + init_action=actions.DirectAction( + path="/api/v1/tenant/{tenant_id}/arkstore/order/{order_no}/payment/", + method=actions.FrontActionMethod.GET + ), + global_actions={ + 'next': actions.NextAction( + name="已支付", + path="/api/v1/tenant/{tenant_id}/arkstore/rent/order/{order_no}/payment_status/extensions/{package}/", + method=actions.FrontActionMethod.GET + ), + } +) diff --git a/api/v1/schema/mine.py b/api/v1/schema/mine.py index e3b0645b3..47d01ac04 100644 --- a/api/v1/schema/mine.py +++ b/api/v1/schema/mine.py @@ -127,7 +127,9 @@ class Config: model_fields = ['id', 'logo', 'name','url','description','type'] class MineAppListOut(ResponseSchema): - data:List[MineAppListItemOut] + data:List[MineAppListItemOut] = Field( + default=[] + ) class MessageSenderItemOut(Schema): diff --git a/api/v1/views/arkstore.py b/api/v1/views/arkstore.py index 7a50ba07e..433f3df2e 100644 --- a/api/v1/views/arkstore.py +++ b/api/v1/views/arkstore.py @@ -8,6 +8,7 @@ from arkid.common.arkstore import ( check_arkstore_purcahsed_extension_expired, check_arkstore_rented_extension_expired, + check_time_and_user_valid, get_arkstore_access_token, get_arkstore_extension_detail_by_package, purcharse_arkstore_extension, @@ -46,6 +47,7 @@ from pydantic import condecimal, conint from arkid.core.schema import ResponseSchema from django.conf import settings +from arkid.core import actions def get_arkstore_list(request, purchased, type, rented=False, all=False, extra_params={}): @@ -90,7 +92,7 @@ class ArkstoreItemSchemaOut(Schema): version: str = Field(readonly=True, title=_('Version', '版本')) author: str = Field(readonly=True, title=_('Author', '作者')) logo: str = Field(readonly=True, default="") - description: str = Field(readonly=True) + description: Optional[str] = Field(readonly=True) category: Optional[str] = Field(title=_('Category', '分类'), readonly=True) labels: Optional[str] = Field(title=_('Labels', '标签'), readonly=True) homepage: str = Field(readonly=True, title=_('Homepage', '官方网站')) @@ -140,6 +142,22 @@ class OnShelveExtensionPurchaseOut(ArkstoreExtensionItemSchemaOut): ) install: Optional[bool] = Field(title=_("Install", "安装"), default=False, hidden=True) upgrade: Optional[bool] = Field(title=_("Upgrade", "升级"), default=False, hidden=True) + lease_state: Optional[str] = Field(title=_('Lease State', '租赁状态')) + lease_useful_life: Optional[List[str]] = Field(title=_('Lease Useful Life', '有效期')) + is_active: Optional[bool] = Field( + title='是否启动', + item_action={ + "path":"/api/v1/extensions/{id}/toggle/", + "method":actions.FrontActionMethod.POST.value + } + ) + is_active_tenant: Optional[bool] = Field( + title='是否使用', + item_action={ + "path":"/api/v1/tenant/{tenant_id}/tenant/extensions/{id}/active/", + "method":actions.FrontActionMethod.POST.value + } + ) class OrderStatusSchema(Schema): @@ -258,6 +276,10 @@ class ArkstoreExtensionQueryIn(Schema): default="", title=_("应用分类") ) + search:str = Field( + default="", + title=_("任一字段") + ) class ArkstoreAppQueryIn(Schema): @@ -399,34 +421,96 @@ def list_arkstore_purchased_extensions(request, tenant_id: str, category_id: str return get_arkstore_list(request, True, 'extension', extra_params=extra_params) -@api.get("/tenant/{tenant_id}/arkstore/not_installed/extensions/", tags=['方舟商店'], response=List[OnShelveExtensionPurchaseOut]) +class ArkstoreStatusFilterIn(Schema): + install: Optional[bool] = Field( + title=_("未安装") + ) + upgrade: Optional[bool] = Field( + title=_("有升级") + ) + +@api.get("/tenant/{tenant_id}/arkstore/purchased_and_installed/extensions/", tags=['方舟商店'], response=List[OnShelveExtensionPurchaseOut]) @operation(List[ArkstoreItemSchemaOut], roles=[TENANT_ADMIN, PLATFORM_ADMIN]) @paginate(CustomPagination) -def list_arkstore_not_installed_extensions(request, tenant_id: str, category_id: str = None): +def list_arkstore_purchased_and_installed_extensions(request, tenant_id: str, filter: ArkstoreStatusFilterIn=Query(...)): extra_params = {} - if category_id and category_id != "" and category_id != "0": - extra_params['category_id'] = category_id - installed_exts = Extension.valid_objects.filter() - installed_ext_packages = set(str(ext.package) for ext in installed_exts) + installed_exts = Extension.valid_objects.filter().all() + + tenant_extension_ids = TenantExtension.valid_objects.filter(tenant_id=tenant_id, is_active=True).values('extension_id') + tenant_active_extension_ids_set = {x['extension_id'] for x in tenant_extension_ids} + for ext in installed_exts: + ext.is_active_tenant = True if ext.id in tenant_active_extension_ids_set else False + + installed_ext_packages = {ext.package: ext for ext in installed_exts} purchased_exts = get_arkstore_list(request, True, 'extension', all=True, extra_params=extra_params)['items'] - return [ext for ext in purchased_exts if ext['package'] not in installed_ext_packages] + for ext in purchased_exts: + if ext['package'] in installed_ext_packages: + ext['is_active'] = installed_ext_packages[ext['package']].is_active + ext['is_active_tenant'] = installed_ext_packages[ext['package']].is_active_tenant + if installed_ext_packages[ext['package']].version < ext['version']: + ext['upgrade'] = True + else: + ext['install'] = True + + purchased_exts_packages = {ext['package']: ext for ext in purchased_exts} + local_exts = [ext for ext in installed_exts if ext.package not in purchased_exts_packages] + for ext in local_exts: + ext.uuid = str(ext.id) + ext.labels = " ".join(ext.labels) if ext.labels else "" + if filter.upgrade == True: + return [ext for ext in purchased_exts if ext.get('upgrade') == True] -@api.get("/tenant/{tenant_id}/arkstore/not_upgraded/extensions/", tags=['方舟商店'], response=List[OnShelveExtensionPurchaseOut]) + if filter.install == True: + return [ext for ext in purchased_exts if ext.get('install') == True] + elif filter.install == False: + return local_exts + [ext for ext in purchased_exts if ext.get('install') == False] + + return local_exts + purchased_exts + + +@api.get("/tenant/{tenant_id}/arkstore/rented/extensions/", tags=['方舟商店'], response=List[OnShelveExtensionPurchaseOut]) @operation(List[ArkstoreItemSchemaOut], roles=[TENANT_ADMIN, PLATFORM_ADMIN]) @paginate(CustomPagination) -def list_arkstore_not_upgraded_extensions(request, tenant_id: str, category_id: str = None): - extra_params = {} - if category_id and category_id != "" and category_id != "0": - extra_params['category_id'] = category_id - installed_exts = Extension.valid_objects.filter() - installed_ext_packages = {ext.package: ext for ext in installed_exts} - purchased_exts = get_arkstore_list(request, True, 'extension', all=True, extra_params=extra_params)['items'] - exts = [] - for ext in purchased_exts: - if ext['package'] in installed_ext_packages and installed_ext_packages[ext['package']].version < ext['version']: - exts.append(ext) - return exts +def list_arkstore_rented_extensions(request, tenant_id: str): + tenant = Tenant.objects.get(id=tenant_id) + extension_ids = TenantExtension.valid_objects.filter(tenant_id=tenant_id, is_rented=True).values('extension_id') + extensions = Extension.valid_objects.filter().all() + + tenant_extension_ids = TenantExtension.valid_objects.filter(tenant_id=tenant_id, is_active=True).values('extension_id') + tenant_active_extension_ids_set = {x['extension_id'] for x in tenant_extension_ids} + for ext in extensions: + ext.is_active_tenant = True if ext.id in tenant_active_extension_ids_set else False + + if settings.IS_CENTRAL_ARKID: + return extensions + + for ext in extensions: + ext.uuid = str(ext.id) + ext.labels = " ".join(ext.labels) if ext.labels else "" + + if tenant.is_platform_tenant: + for ext in extensions: + ext.lease_useful_life = ["不限天数,不限人数"] + ext.lease_state = '已租赁' + return extensions + + resp = get_arkstore_list(request, None, 'extension', rented=True, all=True)['items'] + extensions_rented = {ext['package']: ext for ext in resp} + for ext in extensions: + if ext.package in extensions_rented: + ext.lease_useful_life = extensions_rented[ext.package]['lease_useful_life'] + ext.lease_state = '已租赁' + lease_records = extensions_rented[ext.package].get('lease_records') or [] + # check_lease_records_expired + if check_time_and_user_valid(lease_records, tenant): + tenant_extension, created = TenantExtension.objects.update_or_create( + tenant_id=tenant_id, + extension=ext, + defaults={"is_rented": True} + ) + + return extensions @api.get("/tenant/{tenant_id}/arkstore/purchased/apps/", tags=['方舟商店'], response=List[ArkstoreAppItemSchemaOut]) diff --git a/api/v1/views/mine.py b/api/v1/views/mine.py index 41a748039..e3d8d62ec 100644 --- a/api/v1/views/mine.py +++ b/api/v1/views/mine.py @@ -2,6 +2,7 @@ from ninja import Query from django.urls import reverse from django.shortcuts import render +from arkid.common.utils import deep_merge from arkid.config import get_app_config from arkid.core.api import GlobalAuth, api, operation from arkid.core.error import ErrorCode, ErrorDict, SuccessDict @@ -457,7 +458,7 @@ def get_mine_apps_with_group_all(request, tenant_id: str, app_group_id:str=None, apps = apps.order_by(order) apps = apps.all() - return SuccessDict(data=list(apps) if apps else []) + return SuccessDict(data=list(apps)) @api.get("/mine/unread_messages/",response=List[MineMessageListItemOut],tags=["我的"],auth=GlobalAuth()) @operation(MineMessageListOut,roles=[NORMAL_USER, TENANT_ADMIN, PLATFORM_ADMIN]) @@ -560,7 +561,7 @@ def post_personal_settings(request,tenant_id:str,data:MinePersonalSettingsIn): user=request.user ) - setting.settings.update(data.dict()) + setting.settings = deep_merge(setting.settings,data.dict()) setting.save() return SuccessDict( diff --git a/arkid/core/pagenation.py b/arkid/core/pagenation.py index 2a69174c6..e126d9d77 100644 --- a/arkid/core/pagenation.py +++ b/arkid/core/pagenation.py @@ -83,6 +83,21 @@ def paginate_queryset(self, queryset, pagination: CustomPagination.Input, reques else: ext['install'] = True + tenant = request.tenant + if tenant.is_platform_tenant: + # for ext in items: + # ext["lease_useful_life"] = ["不限天数,不限人数"] + # ext["lease_state"] = '已租赁' + pass + else: + from api.v1.views.arkstore import get_arkstore_list + resp = get_arkstore_list(request, None, 'extension', rented=True, all=True)['items'] + extensions_rented = {ext['package']: ext for ext in resp} + for ext in items: + if ext["package"] in extensions_rented: + ext["lease_useful_life"] = extensions_rented[ext.package]['lease_useful_life'] + ext["lease_state"] = '已租赁' + return { 'items': items, 'count': count,