diff --git a/.gitignore b/.gitignore
index 554ce08..7b7be40 100644
--- a/.gitignore
+++ b/.gitignore
@@ -17,7 +17,6 @@
.vscode/
dist/
target/
-codegen/
__debug_bin
node_modules/
diff --git a/api/message_bus/openapi.yaml b/api/message_bus/openapi.yaml
index 3ee61f4..15f66aa 100644
--- a/api/message_bus/openapi.yaml
+++ b/api/message_bus/openapi.yaml
@@ -34,6 +34,10 @@ tags:
description: |-
Endpoint paths for subscribing to events and actions via SocketIO
+ - name: YSK Endpoints
+ description: |-
+ Endpoint paths for YSK card via RESTful API
+
- name: EventType
description: |-
@@ -53,6 +57,11 @@ tags:
- name: PropertyType
description: |-
+
+ - name: YSK methods
+ description: |-
+ methods for managing YSK card
+
x-tagGroups:
- name: Methods
@@ -61,11 +70,13 @@ x-tagGroups:
- Event methods
- ActionType methods
- Action methods
+ - YSK methods
- name: Endpoints
tags:
- WebSocket Endpoints
- SocketIO Endpoints
+ - YSK Endpoints
- name: Schemas
tags:
@@ -337,6 +348,37 @@ paths:
"200":
description: |
Polling continued from SocketIO
+ /ysk:
+ get:
+ description: |-
+ Get need display YSK card
+ operationId: getYskCard
+ tags:
+ - YSK Endpoints
+ responses:
+ "200":
+ $ref: "#/components/responses/ResponseGetYSKCardListOK"
+ "500":
+ $ref: "#/components/responses/ResponseInternalServerError"
+ /ysk/{id}:
+ delete:
+ description: |-
+ Not display YSK card
+ operationId: deleteYskCard
+ tags:
+ - YSK Endpoints
+ parameters:
+ - name: id
+ in: path
+ description: YSK card id
+ required: true
+ schema:
+ type: string
+ responses:
+ "200":
+ $ref: "#/components/responses/ResponseOK"
+ "500":
+ $ref: "#/components/responses/ResponseInternalServerError"
components:
securitySchemes:
@@ -528,6 +570,17 @@ components:
application/json:
schema:
$ref: "#/components/schemas/Action"
+
+ ResponseGetYSKCardListOK:
+ description: OK
+ content:
+ application/json:
+ schema:
+ allOf:
+ - $ref: "#/components/schemas/BaseResponse"
+ - properties:
+ data:
+ $ref: "#/components/schemas/YSKCardList"
schemas:
BaseResponse:
@@ -668,3 +721,137 @@ components:
type: string
description: timestamp this action took place
format: date-time
+
+ YSKCardList:
+ type: array
+ items:
+ $ref: "#/components/schemas/YSKCard"
+
+ YSKCard:
+ type: object
+ required:
+ - "id"
+ - "cardType"
+ - "renderType"
+ - "content"
+ properties:
+ id:
+ type: string
+ example: "1234567890"
+ cardType:
+ type: string
+ enum:
+ - "task"
+ - "long-notice"
+ - "short-notice"
+ renderType:
+ type: string
+ enum:
+ - "task"
+ - "list-notice"
+ - "icon-text-notice"
+ - "markdown-notice"
+ content:
+ $ref: "#/components/schemas/YSKCardContent"
+
+ YSKCardContent:
+ type: object
+ required:
+ - "titleIcon"
+ - "titleText"
+ properties:
+ titleIcon:
+ $ref: "#/components/schemas/YSKCardIcon"
+ titleText:
+ type: string
+ example: "CasaOS"
+ bodyProgress:
+ $ref: "#/components/schemas/YSKCardProgress"
+ bodyIconWithText:
+ $ref: "#/components/schemas/YSKCardIconWithText"
+ bodyList:
+ type: array
+ items:
+ $ref: "#/components/schemas/YSKCardListItem"
+ footerActions:
+ type: array
+ items:
+ $ref: "#/components/schemas/YSKCardFooterAction"
+
+ YSKCardProgress:
+ type: object
+ required:
+ - "label"
+ - "progress"
+ properties:
+ label:
+ type: string
+ example: "Installing jellyfin"
+ progress:
+ type: integer
+ example: 50
+
+ YSKCardIconWithText:
+ type: object
+ required:
+ - "icon"
+ - "description"
+ properties:
+ icon:
+ $ref: "#/components/schemas/YSKCardIcon"
+ description:
+ type: string
+ example: "CasaOS"
+
+ YSKCardListItem:
+ type: object
+ required:
+ - "icon"
+ - "description"
+ - "rightText"
+ properties:
+ icon:
+ $ref: "#/components/schemas/YSKCardIcon"
+ description:
+ type: string
+ example: "CasaOS"
+ rightText:
+ type: string
+ example: "4 TB"
+
+ YSKCardFooterAction:
+ type: object
+ required:
+ - "side"
+ - "style"
+ - "text"
+ - "messageBus"
+ properties:
+ side:
+ type: string
+ example: "View Details"
+ style:
+ type: string
+ example: "view-details"
+ text:
+ type: string
+ example: "https://casaos.com"
+ messageBus:
+ $ref: "#/components/schemas/YSKCardMessageBusAction"
+
+ YSKCardMessageBusAction:
+ type: object
+ required:
+ - "key"
+ - "payload"
+ properties:
+ key:
+ type: string
+ example: "open-url"
+ payload:
+ type: string
+ example: "https://casaos.com"
+
+ YSKCardIcon:
+ type: string
+ example: "https://raw.githubusercontent.com/IceWhaleTech/logo/main/casaos/casaos_banner_twilight_blue_800px.png"
diff --git a/codegen/message_bus_api.go b/codegen/message_bus_api.go
new file mode 100644
index 0000000..93d2863
--- /dev/null
+++ b/codegen/message_bus_api.go
@@ -0,0 +1,838 @@
+// Package codegen provides primitives to interact with the openapi HTTP API.
+//
+// Code generated by github.com/deepmap/oapi-codegen version v1.12.4 DO NOT EDIT.
+package codegen
+
+import (
+ "bytes"
+ "compress/gzip"
+ "encoding/base64"
+ "fmt"
+ "net/http"
+ "net/url"
+ "path"
+ "strings"
+ "time"
+
+ "github.com/deepmap/oapi-codegen/pkg/runtime"
+ "github.com/getkin/kin-openapi/openapi3"
+ "github.com/labstack/echo/v4"
+)
+
+const (
+ Access_tokenScopes = "access_token.Scopes"
+)
+
+// Defines values for YSKCardCardType.
+const (
+ YSKCardCardTypeLongNotice YSKCardCardType = "long-notice"
+ YSKCardCardTypeShortNotice YSKCardCardType = "short-notice"
+ YSKCardCardTypeTask YSKCardCardType = "task"
+)
+
+// Defines values for YSKCardRenderType.
+const (
+ YSKCardRenderTypeIconTextNotice YSKCardRenderType = "icon-text-notice"
+ YSKCardRenderTypeListNotice YSKCardRenderType = "list-notice"
+ YSKCardRenderTypeMarkdownNotice YSKCardRenderType = "markdown-notice"
+ YSKCardRenderTypeTask YSKCardRenderType = "task"
+)
+
+// Action defines model for Action.
+type Action struct {
+ // Name action name
+ Name string `json:"name"`
+
+ // Properties event properties
+ Properties map[string]string `json:"properties"`
+
+ // SourceID associated source id
+ SourceID string `json:"sourceID"`
+
+ // Timestamp timestamp this action took place
+ Timestamp *time.Time `json:"timestamp,omitempty"`
+}
+
+// ActionType defines model for ActionType.
+type ActionType struct {
+ // Name action name
+ //
+ // (there is no naming convention for action names, but it is recommended to name each as structural and descriptive as possible)
+ Name string `json:"name"`
+ PropertyTypeList []PropertyType `json:"propertyTypeList"`
+
+ // SourceID action source id to identify where the action will take
+ SourceID string `json:"sourceID"`
+}
+
+// BaseResponse defines model for BaseResponse.
+type BaseResponse struct {
+ // Message message returned by server side if there is any
+ Message *string `json:"message,omitempty"`
+}
+
+// Event defines model for Event.
+type Event struct {
+ // Name event name
+ Name string `json:"name"`
+
+ // Properties event properties
+ Properties map[string]string `json:"properties"`
+
+ // SourceID associated source id
+ SourceID string `json:"sourceID"`
+
+ // Timestamp timestamp this event took place
+ Timestamp *time.Time `json:"timestamp,omitempty"`
+
+ // Uuid event uuid
+ Uuid *string `json:"uuid,omitempty"`
+}
+
+// EventType defines model for EventType.
+type EventType struct {
+ // Name event name
+ //
+ // (there is no naming convention for event names, but it is recommended to name each as structural and descriptive as possible)
+ Name string `json:"name"`
+ PropertyTypeList []PropertyType `json:"propertyTypeList"`
+
+ // SourceID event source id to identify where the event comes from
+ SourceID string `json:"sourceID"`
+}
+
+// PropertyType defines model for PropertyType.
+type PropertyType struct {
+ Description *string `json:"description,omitempty"`
+ Example *string `json:"example,omitempty"`
+
+ // Name property name
+ //
+ // > It is recommended for a property name to be as descriptive as possible. One option is to prefix with a namespace.
+ // > - If the property is source specific, prefix with source ID. For example, `local-storage:vendor`
+ // > - Otherwise, prefix with `common:`. For example, `common:email`
+ // >
+ // > Some bad examples are `id`, `avail`, `blk`...which can be ambiguous and confusing.
+ Name string `json:"name"`
+}
+
+// YSKCard defines model for YSKCard.
+type YSKCard struct {
+ CardType YSKCardCardType `json:"cardType"`
+ Content YSKCardContent `json:"content"`
+ Id string `json:"id"`
+ RenderType YSKCardRenderType `json:"renderType"`
+}
+
+// YSKCardCardType defines model for YSKCard.CardType.
+type YSKCardCardType string
+
+// YSKCardRenderType defines model for YSKCard.RenderType.
+type YSKCardRenderType string
+
+// YSKCardContent defines model for YSKCardContent.
+type YSKCardContent struct {
+ BodyIconWithText *YSKCardIconWithText `json:"bodyIconWithText,omitempty"`
+ BodyList *[]YSKCardListItem `json:"bodyList,omitempty"`
+ BodyProgress *YSKCardProgress `json:"bodyProgress,omitempty"`
+ FooterActions *[]YSKCardFooterAction `json:"footerActions,omitempty"`
+ TitleIcon YSKCardIcon `json:"titleIcon"`
+ TitleText string `json:"titleText"`
+}
+
+// YSKCardFooterAction defines model for YSKCardFooterAction.
+type YSKCardFooterAction struct {
+ MessageBus YSKCardMessageBusAction `json:"messageBus"`
+ Side string `json:"side"`
+ Style string `json:"style"`
+ Text string `json:"text"`
+}
+
+// YSKCardIcon defines model for YSKCardIcon.
+type YSKCardIcon = string
+
+// YSKCardIconWithText defines model for YSKCardIconWithText.
+type YSKCardIconWithText struct {
+ Description string `json:"description"`
+ Icon YSKCardIcon `json:"icon"`
+}
+
+// YSKCardList defines model for YSKCardList.
+type YSKCardList = []YSKCard
+
+// YSKCardListItem defines model for YSKCardListItem.
+type YSKCardListItem struct {
+ Description string `json:"description"`
+ Icon YSKCardIcon `json:"icon"`
+ RightText string `json:"rightText"`
+}
+
+// YSKCardMessageBusAction defines model for YSKCardMessageBusAction.
+type YSKCardMessageBusAction struct {
+ Key string `json:"key"`
+ Payload string `json:"payload"`
+}
+
+// YSKCardProgress defines model for YSKCardProgress.
+type YSKCardProgress struct {
+ Label string `json:"label"`
+ Progress int `json:"progress"`
+}
+
+// ActionName defines model for ActionName.
+type ActionName = string
+
+// ActionNames defines model for ActionNames.
+type ActionNames = []string
+
+// EventName defines model for EventName.
+type EventName = string
+
+// EventNames defines model for EventNames.
+type EventNames = []string
+
+// SourceID defines model for SourceID.
+type SourceID = string
+
+// GetActionTypeOK defines model for GetActionTypeOK.
+type GetActionTypeOK = ActionType
+
+// GetActionTypesOK defines model for GetActionTypesOK.
+type GetActionTypesOK = []ActionType
+
+// GetEventTypeOK defines model for GetEventTypeOK.
+type GetEventTypeOK = EventType
+
+// GetEventTypesOK defines model for GetEventTypesOK.
+type GetEventTypesOK = []EventType
+
+// PublishEventOK defines model for PublishEventOK.
+type PublishEventOK = Event
+
+// ResponseBadRequest defines model for ResponseBadRequest.
+type ResponseBadRequest = BaseResponse
+
+// ResponseConflict defines model for ResponseConflict.
+type ResponseConflict = BaseResponse
+
+// ResponseGetYSKCardListOK defines model for ResponseGetYSKCardListOK.
+type ResponseGetYSKCardListOK struct {
+ Data *YSKCardList `json:"data,omitempty"`
+
+ // Message message returned by server side if there is any
+ Message *string `json:"message,omitempty"`
+}
+
+// ResponseInternalServerError defines model for ResponseInternalServerError.
+type ResponseInternalServerError = BaseResponse
+
+// ResponseNotFound defines model for ResponseNotFound.
+type ResponseNotFound = BaseResponse
+
+// ResponseOK defines model for ResponseOK.
+type ResponseOK = BaseResponse
+
+// TriggerActionOK defines model for TriggerActionOK.
+type TriggerActionOK = Action
+
+// PublishEvent event properties
+type PublishEvent map[string]string
+
+// RegisterActionTypes defines model for RegisterActionTypes.
+type RegisterActionTypes = []ActionType
+
+// RegisterEventTypes defines model for RegisterEventTypes.
+type RegisterEventTypes = []EventType
+
+// TriggerAction action properties
+type TriggerAction map[string]string
+
+// SubscribeActionWSParams defines parameters for SubscribeActionWS.
+type SubscribeActionWSParams struct {
+ Names *ActionNames `form:"names,omitempty" json:"names,omitempty"`
+}
+
+// TriggerActionJSONBody defines parameters for TriggerAction.
+type TriggerActionJSONBody map[string]string
+
+// RegisterActionTypesJSONBody defines parameters for RegisterActionTypes.
+type RegisterActionTypesJSONBody = []ActionType
+
+// SubscribeEventWSParams defines parameters for SubscribeEventWS.
+type SubscribeEventWSParams struct {
+ Names *EventNames `form:"names,omitempty" json:"names,omitempty"`
+}
+
+// PublishEventJSONBody defines parameters for PublishEvent.
+type PublishEventJSONBody map[string]string
+
+// RegisterEventTypesJSONBody defines parameters for RegisterEventTypes.
+type RegisterEventTypesJSONBody = []EventType
+
+// TriggerActionJSONRequestBody defines body for TriggerAction for application/json ContentType.
+type TriggerActionJSONRequestBody TriggerActionJSONBody
+
+// RegisterActionTypesJSONRequestBody defines body for RegisterActionTypes for application/json ContentType.
+type RegisterActionTypesJSONRequestBody = RegisterActionTypesJSONBody
+
+// PublishEventJSONRequestBody defines body for PublishEvent for application/json ContentType.
+type PublishEventJSONRequestBody PublishEventJSONBody
+
+// RegisterEventTypesJSONRequestBody defines body for RegisterEventTypes for application/json ContentType.
+type RegisterEventTypesJSONRequestBody = RegisterEventTypesJSONBody
+
+// ServerInterface represents all server handlers.
+type ServerInterface interface {
+ // Subscribe to actions by source ID (WebSocket)
+ // (GET /action/{source_id})
+ SubscribeActionWS(ctx echo.Context, sourceId SourceID, params SubscribeActionWSParams) error
+ // Trigger an action
+ // (POST /action/{source_id}/{name})
+ TriggerAction(ctx echo.Context, sourceId SourceID, name ActionName) error
+ // List action types
+ // (GET /action_type)
+ GetActionTypes(ctx echo.Context) error
+ // Register one or more action types
+ // (POST /action_type)
+ RegisterActionTypes(ctx echo.Context) error
+ // Get action types by source ID
+ // (GET /action_type/{source_id})
+ GetActionTypesBySourceID(ctx echo.Context, sourceId SourceID) error
+ // Get an action type by source ID and name
+ // (GET /action_type/{source_id}/{name})
+ GetActionType(ctx echo.Context, sourceId SourceID, name ActionName) error
+ // Subscribe to events by source ID (WebSocket)
+ // (GET /event/{source_id})
+ SubscribeEventWS(ctx echo.Context, sourceId SourceID, params SubscribeEventWSParams) error
+ // Publish an event
+ // (POST /event/{source_id}/{name})
+ PublishEvent(ctx echo.Context, sourceId SourceID, name EventName) error
+ // List event types
+ // (GET /event_type)
+ GetEventTypes(ctx echo.Context) error
+ // Register one or more event types
+ // (POST /event_type)
+ RegisterEventTypes(ctx echo.Context) error
+ // Get event types by source ID
+ // (GET /event_type/{source_id})
+ GetEventTypesBySourceID(ctx echo.Context, sourceId SourceID) error
+ // Get an event type by source ID and name
+ // (GET /event_type/{source_id}/{name})
+ GetEventType(ctx echo.Context, sourceId SourceID, name EventName) error
+ // Subscribe to events and actions (SocketIO)
+ // (GET /socket.io)
+ SubscribeSIO(ctx echo.Context) error
+ // Poll events and actions (SocketIO)
+ // (POST /socket.io)
+ PollSIO(ctx echo.Context) error
+ // Subscribe to events and actions (SocketIO)
+ // (GET /socket.io/)
+ SubscribeSIO2(ctx echo.Context) error
+ // Poll events and actions (SocketIO)
+ // (POST /socket.io/)
+ PollSIO2(ctx echo.Context) error
+
+ // (GET /ysk)
+ GetYskCard(ctx echo.Context) error
+
+ // (DELETE /ysk/{id})
+ DeleteYskCard(ctx echo.Context, id string) error
+}
+
+// ServerInterfaceWrapper converts echo contexts to parameters.
+type ServerInterfaceWrapper struct {
+ Handler ServerInterface
+}
+
+// SubscribeActionWS converts echo context to params.
+func (w *ServerInterfaceWrapper) SubscribeActionWS(ctx echo.Context) error {
+ var err error
+ // ------------- Path parameter "source_id" -------------
+ var sourceId SourceID
+
+ err = runtime.BindStyledParameterWithLocation("simple", false, "source_id", runtime.ParamLocationPath, ctx.Param("source_id"), &sourceId)
+ if err != nil {
+ return echo.NewHTTPError(http.StatusBadRequest, fmt.Sprintf("Invalid format for parameter source_id: %s", err))
+ }
+
+ ctx.Set(Access_tokenScopes, []string{""})
+
+ // Parameter object where we will unmarshal all parameters from the context
+ var params SubscribeActionWSParams
+ // ------------- Optional query parameter "names" -------------
+
+ err = runtime.BindQueryParameter("form", true, false, "names", ctx.QueryParams(), ¶ms.Names)
+ if err != nil {
+ return echo.NewHTTPError(http.StatusBadRequest, fmt.Sprintf("Invalid format for parameter names: %s", err))
+ }
+
+ // Invoke the callback with all the unmarshalled arguments
+ err = w.Handler.SubscribeActionWS(ctx, sourceId, params)
+ return err
+}
+
+// TriggerAction converts echo context to params.
+func (w *ServerInterfaceWrapper) TriggerAction(ctx echo.Context) error {
+ var err error
+ // ------------- Path parameter "source_id" -------------
+ var sourceId SourceID
+
+ err = runtime.BindStyledParameterWithLocation("simple", false, "source_id", runtime.ParamLocationPath, ctx.Param("source_id"), &sourceId)
+ if err != nil {
+ return echo.NewHTTPError(http.StatusBadRequest, fmt.Sprintf("Invalid format for parameter source_id: %s", err))
+ }
+
+ // ------------- Path parameter "name" -------------
+ var name ActionName
+
+ err = runtime.BindStyledParameterWithLocation("simple", false, "name", runtime.ParamLocationPath, ctx.Param("name"), &name)
+ if err != nil {
+ return echo.NewHTTPError(http.StatusBadRequest, fmt.Sprintf("Invalid format for parameter name: %s", err))
+ }
+
+ ctx.Set(Access_tokenScopes, []string{""})
+
+ // Invoke the callback with all the unmarshalled arguments
+ err = w.Handler.TriggerAction(ctx, sourceId, name)
+ return err
+}
+
+// GetActionTypes converts echo context to params.
+func (w *ServerInterfaceWrapper) GetActionTypes(ctx echo.Context) error {
+ var err error
+
+ ctx.Set(Access_tokenScopes, []string{""})
+
+ // Invoke the callback with all the unmarshalled arguments
+ err = w.Handler.GetActionTypes(ctx)
+ return err
+}
+
+// RegisterActionTypes converts echo context to params.
+func (w *ServerInterfaceWrapper) RegisterActionTypes(ctx echo.Context) error {
+ var err error
+
+ ctx.Set(Access_tokenScopes, []string{""})
+
+ // Invoke the callback with all the unmarshalled arguments
+ err = w.Handler.RegisterActionTypes(ctx)
+ return err
+}
+
+// GetActionTypesBySourceID converts echo context to params.
+func (w *ServerInterfaceWrapper) GetActionTypesBySourceID(ctx echo.Context) error {
+ var err error
+ // ------------- Path parameter "source_id" -------------
+ var sourceId SourceID
+
+ err = runtime.BindStyledParameterWithLocation("simple", false, "source_id", runtime.ParamLocationPath, ctx.Param("source_id"), &sourceId)
+ if err != nil {
+ return echo.NewHTTPError(http.StatusBadRequest, fmt.Sprintf("Invalid format for parameter source_id: %s", err))
+ }
+
+ ctx.Set(Access_tokenScopes, []string{""})
+
+ // Invoke the callback with all the unmarshalled arguments
+ err = w.Handler.GetActionTypesBySourceID(ctx, sourceId)
+ return err
+}
+
+// GetActionType converts echo context to params.
+func (w *ServerInterfaceWrapper) GetActionType(ctx echo.Context) error {
+ var err error
+ // ------------- Path parameter "source_id" -------------
+ var sourceId SourceID
+
+ err = runtime.BindStyledParameterWithLocation("simple", false, "source_id", runtime.ParamLocationPath, ctx.Param("source_id"), &sourceId)
+ if err != nil {
+ return echo.NewHTTPError(http.StatusBadRequest, fmt.Sprintf("Invalid format for parameter source_id: %s", err))
+ }
+
+ // ------------- Path parameter "name" -------------
+ var name ActionName
+
+ err = runtime.BindStyledParameterWithLocation("simple", false, "name", runtime.ParamLocationPath, ctx.Param("name"), &name)
+ if err != nil {
+ return echo.NewHTTPError(http.StatusBadRequest, fmt.Sprintf("Invalid format for parameter name: %s", err))
+ }
+
+ ctx.Set(Access_tokenScopes, []string{""})
+
+ // Invoke the callback with all the unmarshalled arguments
+ err = w.Handler.GetActionType(ctx, sourceId, name)
+ return err
+}
+
+// SubscribeEventWS converts echo context to params.
+func (w *ServerInterfaceWrapper) SubscribeEventWS(ctx echo.Context) error {
+ var err error
+ // ------------- Path parameter "source_id" -------------
+ var sourceId SourceID
+
+ err = runtime.BindStyledParameterWithLocation("simple", false, "source_id", runtime.ParamLocationPath, ctx.Param("source_id"), &sourceId)
+ if err != nil {
+ return echo.NewHTTPError(http.StatusBadRequest, fmt.Sprintf("Invalid format for parameter source_id: %s", err))
+ }
+
+ ctx.Set(Access_tokenScopes, []string{""})
+
+ // Parameter object where we will unmarshal all parameters from the context
+ var params SubscribeEventWSParams
+ // ------------- Optional query parameter "names" -------------
+
+ err = runtime.BindQueryParameter("form", true, false, "names", ctx.QueryParams(), ¶ms.Names)
+ if err != nil {
+ return echo.NewHTTPError(http.StatusBadRequest, fmt.Sprintf("Invalid format for parameter names: %s", err))
+ }
+
+ // Invoke the callback with all the unmarshalled arguments
+ err = w.Handler.SubscribeEventWS(ctx, sourceId, params)
+ return err
+}
+
+// PublishEvent converts echo context to params.
+func (w *ServerInterfaceWrapper) PublishEvent(ctx echo.Context) error {
+ var err error
+ // ------------- Path parameter "source_id" -------------
+ var sourceId SourceID
+
+ err = runtime.BindStyledParameterWithLocation("simple", false, "source_id", runtime.ParamLocationPath, ctx.Param("source_id"), &sourceId)
+ if err != nil {
+ return echo.NewHTTPError(http.StatusBadRequest, fmt.Sprintf("Invalid format for parameter source_id: %s", err))
+ }
+
+ // ------------- Path parameter "name" -------------
+ var name EventName
+
+ err = runtime.BindStyledParameterWithLocation("simple", false, "name", runtime.ParamLocationPath, ctx.Param("name"), &name)
+ if err != nil {
+ return echo.NewHTTPError(http.StatusBadRequest, fmt.Sprintf("Invalid format for parameter name: %s", err))
+ }
+
+ ctx.Set(Access_tokenScopes, []string{""})
+
+ // Invoke the callback with all the unmarshalled arguments
+ err = w.Handler.PublishEvent(ctx, sourceId, name)
+ return err
+}
+
+// GetEventTypes converts echo context to params.
+func (w *ServerInterfaceWrapper) GetEventTypes(ctx echo.Context) error {
+ var err error
+
+ ctx.Set(Access_tokenScopes, []string{""})
+
+ // Invoke the callback with all the unmarshalled arguments
+ err = w.Handler.GetEventTypes(ctx)
+ return err
+}
+
+// RegisterEventTypes converts echo context to params.
+func (w *ServerInterfaceWrapper) RegisterEventTypes(ctx echo.Context) error {
+ var err error
+
+ ctx.Set(Access_tokenScopes, []string{""})
+
+ // Invoke the callback with all the unmarshalled arguments
+ err = w.Handler.RegisterEventTypes(ctx)
+ return err
+}
+
+// GetEventTypesBySourceID converts echo context to params.
+func (w *ServerInterfaceWrapper) GetEventTypesBySourceID(ctx echo.Context) error {
+ var err error
+ // ------------- Path parameter "source_id" -------------
+ var sourceId SourceID
+
+ err = runtime.BindStyledParameterWithLocation("simple", false, "source_id", runtime.ParamLocationPath, ctx.Param("source_id"), &sourceId)
+ if err != nil {
+ return echo.NewHTTPError(http.StatusBadRequest, fmt.Sprintf("Invalid format for parameter source_id: %s", err))
+ }
+
+ ctx.Set(Access_tokenScopes, []string{""})
+
+ // Invoke the callback with all the unmarshalled arguments
+ err = w.Handler.GetEventTypesBySourceID(ctx, sourceId)
+ return err
+}
+
+// GetEventType converts echo context to params.
+func (w *ServerInterfaceWrapper) GetEventType(ctx echo.Context) error {
+ var err error
+ // ------------- Path parameter "source_id" -------------
+ var sourceId SourceID
+
+ err = runtime.BindStyledParameterWithLocation("simple", false, "source_id", runtime.ParamLocationPath, ctx.Param("source_id"), &sourceId)
+ if err != nil {
+ return echo.NewHTTPError(http.StatusBadRequest, fmt.Sprintf("Invalid format for parameter source_id: %s", err))
+ }
+
+ // ------------- Path parameter "name" -------------
+ var name EventName
+
+ err = runtime.BindStyledParameterWithLocation("simple", false, "name", runtime.ParamLocationPath, ctx.Param("name"), &name)
+ if err != nil {
+ return echo.NewHTTPError(http.StatusBadRequest, fmt.Sprintf("Invalid format for parameter name: %s", err))
+ }
+
+ ctx.Set(Access_tokenScopes, []string{""})
+
+ // Invoke the callback with all the unmarshalled arguments
+ err = w.Handler.GetEventType(ctx, sourceId, name)
+ return err
+}
+
+// SubscribeSIO converts echo context to params.
+func (w *ServerInterfaceWrapper) SubscribeSIO(ctx echo.Context) error {
+ var err error
+
+ ctx.Set(Access_tokenScopes, []string{""})
+
+ // Invoke the callback with all the unmarshalled arguments
+ err = w.Handler.SubscribeSIO(ctx)
+ return err
+}
+
+// PollSIO converts echo context to params.
+func (w *ServerInterfaceWrapper) PollSIO(ctx echo.Context) error {
+ var err error
+
+ ctx.Set(Access_tokenScopes, []string{""})
+
+ // Invoke the callback with all the unmarshalled arguments
+ err = w.Handler.PollSIO(ctx)
+ return err
+}
+
+// SubscribeSIO2 converts echo context to params.
+func (w *ServerInterfaceWrapper) SubscribeSIO2(ctx echo.Context) error {
+ var err error
+
+ ctx.Set(Access_tokenScopes, []string{""})
+
+ // Invoke the callback with all the unmarshalled arguments
+ err = w.Handler.SubscribeSIO2(ctx)
+ return err
+}
+
+// PollSIO2 converts echo context to params.
+func (w *ServerInterfaceWrapper) PollSIO2(ctx echo.Context) error {
+ var err error
+
+ ctx.Set(Access_tokenScopes, []string{""})
+
+ // Invoke the callback with all the unmarshalled arguments
+ err = w.Handler.PollSIO2(ctx)
+ return err
+}
+
+// GetYskCard converts echo context to params.
+func (w *ServerInterfaceWrapper) GetYskCard(ctx echo.Context) error {
+ var err error
+
+ ctx.Set(Access_tokenScopes, []string{""})
+
+ // Invoke the callback with all the unmarshalled arguments
+ err = w.Handler.GetYskCard(ctx)
+ return err
+}
+
+// DeleteYskCard converts echo context to params.
+func (w *ServerInterfaceWrapper) DeleteYskCard(ctx echo.Context) error {
+ var err error
+ // ------------- Path parameter "id" -------------
+ var id string
+
+ err = runtime.BindStyledParameterWithLocation("simple", false, "id", runtime.ParamLocationPath, ctx.Param("id"), &id)
+ if err != nil {
+ return echo.NewHTTPError(http.StatusBadRequest, fmt.Sprintf("Invalid format for parameter id: %s", err))
+ }
+
+ ctx.Set(Access_tokenScopes, []string{""})
+
+ // Invoke the callback with all the unmarshalled arguments
+ err = w.Handler.DeleteYskCard(ctx, id)
+ return err
+}
+
+// This is a simple interface which specifies echo.Route addition functions which
+// are present on both echo.Echo and echo.Group, since we want to allow using
+// either of them for path registration
+type EchoRouter interface {
+ CONNECT(path string, h echo.HandlerFunc, m ...echo.MiddlewareFunc) *echo.Route
+ DELETE(path string, h echo.HandlerFunc, m ...echo.MiddlewareFunc) *echo.Route
+ GET(path string, h echo.HandlerFunc, m ...echo.MiddlewareFunc) *echo.Route
+ HEAD(path string, h echo.HandlerFunc, m ...echo.MiddlewareFunc) *echo.Route
+ OPTIONS(path string, h echo.HandlerFunc, m ...echo.MiddlewareFunc) *echo.Route
+ PATCH(path string, h echo.HandlerFunc, m ...echo.MiddlewareFunc) *echo.Route
+ POST(path string, h echo.HandlerFunc, m ...echo.MiddlewareFunc) *echo.Route
+ PUT(path string, h echo.HandlerFunc, m ...echo.MiddlewareFunc) *echo.Route
+ TRACE(path string, h echo.HandlerFunc, m ...echo.MiddlewareFunc) *echo.Route
+}
+
+// RegisterHandlers adds each server route to the EchoRouter.
+func RegisterHandlers(router EchoRouter, si ServerInterface) {
+ RegisterHandlersWithBaseURL(router, si, "")
+}
+
+// Registers handlers, and prepends BaseURL to the paths, so that the paths
+// can be served under a prefix.
+func RegisterHandlersWithBaseURL(router EchoRouter, si ServerInterface, baseURL string) {
+
+ wrapper := ServerInterfaceWrapper{
+ Handler: si,
+ }
+
+ router.GET(baseURL+"/action/:source_id", wrapper.SubscribeActionWS)
+ router.POST(baseURL+"/action/:source_id/:name", wrapper.TriggerAction)
+ router.GET(baseURL+"/action_type", wrapper.GetActionTypes)
+ router.POST(baseURL+"/action_type", wrapper.RegisterActionTypes)
+ router.GET(baseURL+"/action_type/:source_id", wrapper.GetActionTypesBySourceID)
+ router.GET(baseURL+"/action_type/:source_id/:name", wrapper.GetActionType)
+ router.GET(baseURL+"/event/:source_id", wrapper.SubscribeEventWS)
+ router.POST(baseURL+"/event/:source_id/:name", wrapper.PublishEvent)
+ router.GET(baseURL+"/event_type", wrapper.GetEventTypes)
+ router.POST(baseURL+"/event_type", wrapper.RegisterEventTypes)
+ router.GET(baseURL+"/event_type/:source_id", wrapper.GetEventTypesBySourceID)
+ router.GET(baseURL+"/event_type/:source_id/:name", wrapper.GetEventType)
+ router.GET(baseURL+"/socket.io", wrapper.SubscribeSIO)
+ router.POST(baseURL+"/socket.io", wrapper.PollSIO)
+ router.GET(baseURL+"/socket.io/", wrapper.SubscribeSIO2)
+ router.POST(baseURL+"/socket.io/", wrapper.PollSIO2)
+ router.GET(baseURL+"/ysk", wrapper.GetYskCard)
+ router.DELETE(baseURL+"/ysk/:id", wrapper.DeleteYskCard)
+
+}
+
+// Base64 encoded, gzipped, json marshaled Swagger object
+var swaggerSpec = []string{
+
+ "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",
+}
+
+// GetSwagger returns the content of the embedded swagger specification file
+// or error if failed to decode
+func decodeSpec() ([]byte, error) {
+ zipped, err := base64.StdEncoding.DecodeString(strings.Join(swaggerSpec, ""))
+ if err != nil {
+ return nil, fmt.Errorf("error base64 decoding spec: %s", err)
+ }
+ zr, err := gzip.NewReader(bytes.NewReader(zipped))
+ if err != nil {
+ return nil, fmt.Errorf("error decompressing spec: %s", err)
+ }
+ var buf bytes.Buffer
+ _, err = buf.ReadFrom(zr)
+ if err != nil {
+ return nil, fmt.Errorf("error decompressing spec: %s", err)
+ }
+
+ return buf.Bytes(), nil
+}
+
+var rawSpec = decodeSpecCached()
+
+// a naive cached of a decoded swagger spec
+func decodeSpecCached() func() ([]byte, error) {
+ data, err := decodeSpec()
+ return func() ([]byte, error) {
+ return data, err
+ }
+}
+
+// Constructs a synthetic filesystem for resolving external references when loading openapi specifications.
+func PathToRawSpec(pathToFile string) map[string]func() ([]byte, error) {
+ var res = make(map[string]func() ([]byte, error))
+ if len(pathToFile) > 0 {
+ res[pathToFile] = rawSpec
+ }
+
+ return res
+}
+
+// GetSwagger returns the Swagger specification corresponding to the generated code
+// in this file. The external references of Swagger specification are resolved.
+// The logic of resolving external references is tightly connected to "import-mapping" feature.
+// Externally referenced files must be embedded in the corresponding golang packages.
+// Urls can be supported but this task was out of the scope.
+func GetSwagger() (swagger *openapi3.T, err error) {
+ var resolvePath = PathToRawSpec("")
+
+ loader := openapi3.NewLoader()
+ loader.IsExternalRefsAllowed = true
+ loader.ReadFromURIFunc = func(loader *openapi3.Loader, url *url.URL) ([]byte, error) {
+ var pathToFile = url.String()
+ pathToFile = path.Clean(pathToFile)
+ getSpec, ok := resolvePath[pathToFile]
+ if !ok {
+ err1 := fmt.Errorf("path not found: %s", pathToFile)
+ return nil, err1
+ }
+ return getSpec()
+ }
+ var specData []byte
+ specData, err = rawSpec()
+ if err != nil {
+ return
+ }
+ swagger, err = loader.LoadFromData(specData)
+ if err != nil {
+ return
+ }
+ return
+}
diff --git a/common/message.go b/common/message.go
new file mode 100644
index 0000000..d25c96e
--- /dev/null
+++ b/common/message.go
@@ -0,0 +1,51 @@
+package common
+
+import (
+ "github.com/IceWhaleTech/CasaOS-MessageBus/codegen"
+ "github.com/samber/lo"
+)
+
+const (
+ SERVICENAME = "ysk"
+)
+
+// common properties
+var (
+ PropertyTypeMessage = codegen.PropertyType{
+ Name: "message",
+ Description: lo.ToPtr("message at different levels, typically for error"),
+ }
+)
+
+// app properties
+var (
+ PropertyTypeCardID = codegen.PropertyType{
+ Name: "card:id",
+ Description: lo.ToPtr("card id"),
+ Example: lo.ToPtr("task:application:install"),
+ }
+
+ PropertyTypeCardBody = codegen.PropertyType{
+ Name: "card:body",
+ Description: lo.ToPtr("card body"),
+ Example: lo.ToPtr("{xxxxxx}"),
+ }
+)
+
+var (
+ EventTypeYSKCardUpsert = codegen.EventType{
+ SourceID: SERVICENAME,
+ Name: "ysk:card:upsert",
+ PropertyTypeList: []codegen.PropertyType{
+ PropertyTypeCardBody,
+ },
+ }
+
+ EventTypeYSKCardDelete = codegen.EventType{
+ SourceID: SERVICENAME,
+ Name: "ysk:card:delete",
+ PropertyTypeList: []codegen.PropertyType{
+ PropertyTypeCardID,
+ },
+ }
+)
diff --git a/go.mod b/go.mod
index 0360962..baf8d3b 100644
--- a/go.mod
+++ b/go.mod
@@ -52,6 +52,7 @@ require (
github.com/pierrec/lz4/v4 v4.1.17 // indirect
github.com/pkg/errors v0.9.1 // indirect
github.com/remyoudompheng/bigfft v0.0.0-20230129092748-24d4a6f8daec // indirect
+ github.com/samber/lo v1.46.0 // indirect
github.com/twitchyliquid64/golang-asm v0.15.1 // indirect
github.com/ugorji/go/codec v1.2.11 // indirect
github.com/ulikunitz/xz v0.5.11 // indirect
@@ -90,7 +91,7 @@ require (
golang.org/x/crypto v0.8.0 // indirect
golang.org/x/net v0.9.0 // indirect
golang.org/x/sys v0.7.0 // indirect
- golang.org/x/text v0.9.0 // indirect
+ golang.org/x/text v0.16.0 // indirect
gopkg.in/ini.v1 v1.67.0
gopkg.in/natefinch/lumberjack.v2 v2.2.1 // indirect
gopkg.in/yaml.v3 v3.0.1 // indirect
diff --git a/go.sum b/go.sum
index 0b9b8f8..caf8bf6 100644
--- a/go.sum
+++ b/go.sum
@@ -165,6 +165,8 @@ github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZN
github.com/remyoudompheng/bigfft v0.0.0-20200410134404-eec4a21b6bb0/go.mod h1:qqbHyh8v60DhA7CoWK5oRCqLrMHRGoxYCSS9EjAz6Eo=
github.com/remyoudompheng/bigfft v0.0.0-20230129092748-24d4a6f8daec h1:W09IVJc94icq4NjY3clb7Lk8O1qJ8BdBEF8z0ibU0rE=
github.com/remyoudompheng/bigfft v0.0.0-20230129092748-24d4a6f8daec/go.mod h1:qqbHyh8v60DhA7CoWK5oRCqLrMHRGoxYCSS9EjAz6Eo=
+github.com/samber/lo v1.46.0 h1:w8G+oaCPgz1PoCJztqymCFaKwXt+5cCXn51uPxExFfQ=
+github.com/samber/lo v1.46.0/go.mod h1:RmDH9Ct32Qy3gduHQuKJ3gW1fMHAnE/fAzQuf6He5cU=
github.com/spkg/bom v0.0.0-20160624110644-59b7046e48ad/go.mod h1:qLr4V1qq6nMqFKkMo8ZTx3f+BZEkzsRUY10Xsm2mwU0=
github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
github.com/stretchr/objx v0.4.0/go.mod h1:YvHI0jy2hoMjB+UWwv71VJQ9isScKT/TqJzVSSt89Yw=
@@ -220,6 +222,8 @@ golang.org/x/sys v0.7.0 h1:3jlCCIQZPdOYu1h8BkNvLz8Kgwtae2cagcG/VamtZRU=
golang.org/x/sys v0.7.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/text v0.9.0 h1:2sjJmO8cDvYveuX97RDLsxlyUxLl+GHoLxBiRdHllBE=
golang.org/x/text v0.9.0/go.mod h1:e1OnstbJyHTd6l/uOt8jFFHp6TRDWZR/bV3emEE/zU8=
+golang.org/x/text v0.16.0 h1:a94ExnEXNtEwYLGJSIUxnWoxoRz/ZcCsV63ROupILh4=
+golang.org/x/text v0.16.0/go.mod h1:GhwF1Be+LQoKShO3cGOHzqOgRrGaYc9AvblQOmPVHnI=
golang.org/x/time v0.3.0 h1:rg5rLMjNzMS1RkNLzCG38eapWhnYLFYXDXj2gOlr8j4=
golang.org/x/time v0.3.0/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ=
golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
diff --git a/main.go b/main.go
index 5e5620a..e7470c3 100644
--- a/main.go
+++ b/main.go
@@ -88,6 +88,7 @@ func main() {
defer cancel()
services.Start(&ctx)
+ go services.YSKService.Start(true)
// route
swagger, err := codegen.GetSwagger()
diff --git a/package.json b/package.json
index c15b4ce..4dcd872 100644
--- a/package.json
+++ b/package.json
@@ -1,12 +1,12 @@
{
- "name": "casaos-messagebus-openapi",
+ "name": "@icewhale/casaos-messagebus-openapi",
"version": "0.0.1",
"scripts": {
"build": "rm -rf dist && tsc && rm -rf generate",
"generate:local": "openapi-generator-cli generate -g typescript-axios -i ./api/message_bus/openapi.yaml -o ./generate",
"generate:npx": "npx @openapitools/openapi-generator-cli generate -g typescript-axios -i ./api/message_bus/openapi.yaml -o ./generate",
"generate:ts": "npx openapi-typescript-codegen --input ./api/message_bus/openapi.yaml --output ./generate",
- "start": "yarn generate:ts && yarn build"
+ "start": "yarn generate:local && yarn build"
},
"homepage": "https://github.com/IceWhaleTech/CasaOS-MessageBus#readme",
"description": "Casaos-MessageBus Typescript+Axios SDK",
diff --git a/pkg/ysk/adapter.go b/pkg/ysk/adapter.go
new file mode 100644
index 0000000..24ed264
--- /dev/null
+++ b/pkg/ysk/adapter.go
@@ -0,0 +1,125 @@
+package ysk
+
+import (
+ "database/sql/driver"
+ "encoding/json"
+ "errors"
+
+ "github.com/IceWhaleTech/CasaOS-MessageBus/codegen"
+)
+
+type CartType string
+
+const (
+ CardTypeTask CartType = "task"
+ CardTypeLongNote CartType = "long-notice"
+ CardTypeShortNote CartType = "short-notice"
+)
+
+type RenderType string
+
+const (
+ RenderTypeCardTask RenderType = "task"
+ RenderTypeCardListNotice RenderType = "list-notice"
+ RenderTypeCardIconTextNotice RenderType = "icon-text-notice"
+ RenderTypeCardMarkdownNotice RenderType = "markdown-notice"
+)
+
+type ActionPosition string
+
+const (
+ ActionPositionLeft ActionPosition = "left"
+ ActionPositionRight ActionPosition = "right"
+)
+
+type YSKCard struct {
+ Id string `json:"id"`
+ CardType CartType `json:"cardType"`
+ RenderType RenderType `json:"renderType"`
+ Content YSKCardContent `json:"content"`
+}
+
+func (yskCard YSKCard) WithProgress(label string, progress int) YSKCard {
+ if yskCard.Content.BodyProgress != nil {
+ yskCard.Content.BodyProgress = &YSKCardProgress{
+ Label: label,
+ Progress: progress,
+ }
+ return yskCard
+ }
+ return yskCard
+}
+
+type YSKCardContent struct {
+ TitleIcon string `json:"titleIcon" gorm:"column:title_icon"`
+ TitleText string `json:"titleText" gorm:"column:title_text"`
+ BodyProgress *YSKCardProgress `json:"bodyProgress,omitempty" gorm:"serializer:json"`
+ BodyIconWithText *YSKCardIconWithText `json:"bodyIconWithText,omitempty" gorm:"serializer:json"`
+ BodyList []YSKCardListItem `json:"bodyList,omitempty" gorm:"serializer:json"`
+ FooterActions []YSKCardFooterAction `json:"footerActions,omitempty" gorm:"serializer:json"`
+}
+
+func (p YSKCardContent) Value() (driver.Value, error) {
+ return json.Marshal(p)
+}
+
+func (p *YSKCardContent) Scan(value interface{}) error {
+ b, ok := value.([]byte)
+ if !ok {
+ return errors.New("type assertion to []byte failed")
+ }
+
+ return json.Unmarshal(b, &p)
+}
+
+type YSKCardProgress struct {
+ Label string
+ Progress int
+}
+
+type YSKCardIconWithText struct {
+ Icon string
+ Description string
+}
+
+type YSKCardListItem struct {
+ Icon string
+ Description string
+ RightText string
+}
+
+type YSKCardFooterAction struct {
+ Side ActionPosition
+ Style string
+ Text string
+ MessageBus YSKCardMessageBusAction
+}
+
+type YSKCardMessageBusAction struct {
+ Key string `json:"key"`
+ Payload string `json:"payload"`
+}
+
+type YSKCardIcon = string
+
+func ToCodegenYSKCard(card YSKCard) (codegen.YSKCard, error) {
+ jsonBody, err := json.Marshal(card)
+ if err != nil {
+ return codegen.YSKCard{}, err
+ }
+ var yskCard codegen.YSKCard
+ err = json.Unmarshal(jsonBody, &yskCard)
+
+ return yskCard, err
+}
+
+func FromCodegenYSKCard(card codegen.YSKCard) (YSKCard, error) {
+ jsonBody, err := json.Marshal(card)
+ if err != nil {
+ return YSKCard{}, err
+ }
+ var yskCard YSKCard
+ err = json.Unmarshal(jsonBody, &yskCard)
+
+ return yskCard, err
+}
diff --git a/pkg/ysk/adapter_test.go b/pkg/ysk/adapter_test.go
new file mode 100644
index 0000000..8bb0a64
--- /dev/null
+++ b/pkg/ysk/adapter_test.go
@@ -0,0 +1,55 @@
+package ysk_test
+
+import (
+ "encoding/json"
+ "reflect"
+ "testing"
+
+ "github.com/IceWhaleTech/CasaOS-MessageBus/codegen"
+ "github.com/IceWhaleTech/CasaOS-MessageBus/pkg/ysk"
+ "gotest.tools/assert"
+)
+
+func compareJSON(json1, json2 string) (bool, error) {
+ var v1, v2 interface{}
+
+ if err := json.Unmarshal([]byte(json1), &v1); err != nil {
+ return false, err
+ }
+
+ if err := json.Unmarshal([]byte(json2), &v2); err != nil {
+ return false, err
+ }
+
+ return reflect.DeepEqual(v1, v2), nil
+}
+
+func TestJsonIsSame(t *testing.T) {
+ domainObject := ysk.YSKCard{
+ Id: "test-card",
+ CardType: ysk.CardTypeTask,
+ RenderType: ysk.RenderTypeCardIconTextNotice,
+ Content: ysk.YSKCardContent{
+ TitleIcon: "test-icon",
+ TitleText: "test-title",
+ },
+ }
+ codegenObject := codegen.YSKCard{
+ Id: "test-card",
+ CardType: codegen.YSKCardCardTypeTask,
+ RenderType: codegen.YSKCardRenderTypeIconTextNotice,
+ Content: codegen.YSKCardContent{
+ TitleIcon: "test-icon",
+ TitleText: "test-title",
+ },
+ }
+
+ json1, err := json.Marshal(domainObject)
+ assert.NilError(t, err)
+ json2, err := json.Marshal(codegenObject)
+ assert.NilError(t, err)
+
+ equal, err := compareJSON(string(json1), string(json2))
+ assert.NilError(t, err)
+ assert.Equal(t, equal, true)
+}
diff --git a/pkg/ysk/ysk.go b/pkg/ysk/ysk.go
new file mode 100644
index 0000000..1ed16ec
--- /dev/null
+++ b/pkg/ysk/ysk.go
@@ -0,0 +1,35 @@
+package ysk
+
+import (
+ "context"
+ "encoding/json"
+
+ "github.com/IceWhaleTech/CasaOS-MessageBus/common"
+)
+
+func DefineCard(ctx context.Context, cardID string) YSKCard {
+ return YSKCard{}
+}
+
+func NewYSKCard(ctx context.Context, YSKCard YSKCard, publish func(context.Context, string, string, map[string]string)) error {
+ yskCardBodyJSON, _ := json.Marshal(YSKCard)
+ publish(ctx,
+ common.SERVICENAME,
+ common.EventTypeYSKCardUpsert.Name,
+ map[string]string{
+ common.PropertyTypeCardBody.Name: string(yskCardBodyJSON),
+ },
+ )
+ return nil
+}
+
+func DeleteCard(ctx context.Context, cardID string, publish func(context.Context, string, string, map[string]string)) error {
+ // do something
+ publish(ctx,
+ common.SERVICENAME,
+ common.EventTypeYSKCardDelete.Name,
+ map[string]string{
+ common.PropertyTypeCardID.Name: cardID,
+ })
+ return nil
+}
diff --git a/pkg/ysk/ysk_test.go b/pkg/ysk/ysk_test.go
new file mode 100644
index 0000000..5927e14
--- /dev/null
+++ b/pkg/ysk/ysk_test.go
@@ -0,0 +1,101 @@
+package ysk_test
+
+import (
+ "context"
+ "testing"
+ "time"
+
+ "github.com/IceWhaleTech/CasaOS-Common/utils/logger"
+ "github.com/IceWhaleTech/CasaOS-MessageBus/model"
+ "github.com/IceWhaleTech/CasaOS-MessageBus/pkg/ysk"
+ "github.com/IceWhaleTech/CasaOS-MessageBus/repository"
+ "github.com/IceWhaleTech/CasaOS-MessageBus/service"
+ "github.com/IceWhaleTech/CasaOS-MessageBus/utils"
+ "gotest.tools/assert"
+)
+
+var ws *service.EventServiceWS
+
+func setup(t *testing.T) (*service.EventServiceWS, *service.YSKService, func()) {
+ repository, err := repository.NewDatabaseRepositoryInMemory()
+ assert.NilError(t, err)
+ s := service.NewServices(&repository)
+ wsService := s.EventServiceWS
+ yskService := s.YSKService
+
+ ctx := context.Background()
+ go s.Start(&ctx)
+ return wsService, yskService, func() {
+ repository.Close()
+ }
+}
+func mockPublish(ctx context.Context, sourceID string, eventName string, body map[string]string) {
+ if ws != nil {
+ ws.Publish(model.Event{
+ SourceID: sourceID,
+ Name: eventName,
+ Properties: body,
+ })
+ }
+}
+
+func TestUpdateProgress(t *testing.T) {
+ logger.LogInitConsoleOnly()
+
+ wsService, yskService, cleanup := setup(t)
+ defer cleanup()
+ ws = wsService
+
+ yskService.Start(false)
+ // wait for the service to start
+ time.Sleep(1 * time.Second)
+
+ err := ysk.NewYSKCard(context.Background(), utils.ApplicationInstallProgress.WithProgress(
+ "Installing LinuxServer/Jellyfin", 25,
+ ), mockPublish)
+ assert.NilError(t, err)
+
+ err = ysk.NewYSKCard(context.Background(), utils.ApplicationInstallProgress.WithProgress(
+ "Installing LinuxServer/Jellyfin", 50,
+ ), mockPublish)
+ assert.NilError(t, err)
+
+ time.Sleep(1 * time.Second)
+
+ cards, err := yskService.YskCardList(context.Background())
+ assert.NilError(t, err)
+ assert.Equal(t, len(cards), 1)
+
+ assert.NilError(t, err)
+ err = ysk.DeleteCard(context.Background(), utils.ApplicationInstallProgress.Id, mockPublish)
+ assert.NilError(t, err)
+
+ time.Sleep(1 * time.Second)
+
+ cards, err = yskService.YskCardList(context.Background())
+ assert.NilError(t, err)
+ assert.Equal(t, len(cards), 0)
+}
+
+func TestLongAndShortNoticeInsert(t *testing.T) {
+ logger.LogInitConsoleOnly()
+
+ wsService, yskService, cleanup := setup(t)
+ defer cleanup()
+ ws = wsService
+
+ yskService.Start(false)
+ // wait for the service to start
+ time.Sleep(1 * time.Second)
+
+ err := ysk.NewYSKCard(context.Background(), utils.ZimaOSDataStationNotice, mockPublish)
+ assert.NilError(t, err)
+ err = ysk.NewYSKCard(context.Background(), utils.ApplicationUpdateNotice, mockPublish)
+ assert.NilError(t, err)
+
+ time.Sleep(1 * time.Second)
+
+ cards, err := yskService.YskCardList(context.Background())
+ assert.NilError(t, err)
+ assert.Equal(t, len(cards), 1)
+}
diff --git a/repository/repository.go b/repository/repository.go
index 80fb90f..5c114a5 100644
--- a/repository/repository.go
+++ b/repository/repository.go
@@ -1,6 +1,9 @@
package repository
-import "github.com/IceWhaleTech/CasaOS-MessageBus/model"
+import (
+ "github.com/IceWhaleTech/CasaOS-MessageBus/model"
+ "github.com/IceWhaleTech/CasaOS-MessageBus/pkg/ysk"
+)
type Repository interface {
GetEventTypes() ([]model.EventType, error)
@@ -13,5 +16,9 @@ type Repository interface {
GetActionTypesBySourceID(sourceID string) ([]model.ActionType, error)
GetActionType(sourceID string, name string) (*model.ActionType, error)
+ GetYSKCardList() ([]ysk.YSKCard, error)
+ UpsertYSKCard(card ysk.YSKCard) error
+ DeleteYSKCard(id string) error
+
Close()
}
diff --git a/repository/repository_db.go b/repository/repository_db.go
index bc7f3cf..760570c 100644
--- a/repository/repository_db.go
+++ b/repository/repository_db.go
@@ -5,6 +5,7 @@ import (
"github.com/IceWhaleTech/CasaOS-Common/utils/logger"
"github.com/IceWhaleTech/CasaOS-MessageBus/model"
+ "github.com/IceWhaleTech/CasaOS-MessageBus/pkg/ysk"
"github.com/glebarez/sqlite"
"go.uber.org/zap"
"gorm.io/gorm"
@@ -15,6 +16,23 @@ type DatabaseRepository struct {
db *gorm.DB
}
+func (r *DatabaseRepository) GetYSKCardList() ([]ysk.YSKCard, error) {
+ var cardList []ysk.YSKCard
+ if err := r.db.Find(&cardList).Error; err != nil {
+ return nil, err
+ }
+ return cardList, nil
+}
+
+func (r *DatabaseRepository) UpsertYSKCard(card ysk.YSKCard) error {
+ return r.db.Clauses(clause.OnConflict{
+ Columns: []clause.Column{{Name: "id"}},
+ UpdateAll: true,
+ }).Create(&card).Error
+}
+func (r *DatabaseRepository) DeleteYSKCard(id string) error {
+ return r.db.Delete(&ysk.YSKCard{Id: id}).Error
+}
func (r *DatabaseRepository) GetEventTypes() ([]model.EventType, error) {
var eventTypes []model.EventType
@@ -132,7 +150,10 @@ func NewDatabaseRepository(databaseFilePath string) (Repository, error) {
c.SetMaxOpenConns(1)
c.SetConnMaxIdleTime(1000 * time.Second)
- if err := db.AutoMigrate(&model.EventType{}, &model.ActionType{}, &model.PropertyType{}); err != nil {
+ if err := db.AutoMigrate(
+ &model.EventType{}, &model.ActionType{}, &model.PropertyType{},
+ &ysk.YSKCard{},
+ ); err != nil {
return nil, err
}
diff --git a/route/ysk.go b/route/ysk.go
new file mode 100644
index 0000000..e5af283
--- /dev/null
+++ b/route/ysk.go
@@ -0,0 +1,41 @@
+package route
+
+import (
+ "net/http"
+
+ "github.com/IceWhaleTech/CasaOS-MessageBus/codegen"
+ "github.com/IceWhaleTech/CasaOS-MessageBus/pkg/ysk"
+ "github.com/labstack/echo/v4"
+ "github.com/samber/lo"
+)
+
+func (r *APIRoute) DeleteYskCard(ctx echo.Context, id string) error {
+ err := r.services.YSKService.DeleteYSKCard(ctx.Request().Context(), id)
+ if err != nil {
+ return ctx.JSON(http.StatusInternalServerError, codegen.ResponseInternalServerError{
+ Message: lo.ToPtr(err.Error()),
+ })
+ }
+ return ctx.JSON(http.StatusOK, codegen.ResponseOK{
+ Message: lo.ToPtr("success"),
+ })
+}
+
+func (r *APIRoute) GetYskCard(ctx echo.Context) error {
+ cardList, err := r.services.YSKService.YskCardList(ctx.Request().Context())
+ if err != nil {
+ return ctx.JSON(http.StatusInternalServerError, codegen.ResponseInternalServerError{
+ Message: lo.ToPtr(err.Error()),
+ })
+ }
+
+ return ctx.JSON(http.StatusOK, codegen.ResponseGetYSKCardListOK{
+ Data: lo.ToPtr(lo.Map(cardList, func(yskCard ysk.YSKCard, _ int) codegen.YSKCard {
+ card, err := ysk.ToCodegenYSKCard(yskCard)
+ if err != nil {
+ return codegen.YSKCard{}
+ }
+ return card
+ })),
+ })
+}
diff --git a/service/services.go b/service/services.go
index 34d5e76..38cd26c 100644
--- a/service/services.go
+++ b/service/services.go
@@ -15,6 +15,8 @@ type Services struct {
ActionServiceWS *ActionServiceWS
SocketIOService *SocketIOService
+
+ YSKService *YSKService
}
var (
@@ -34,12 +36,15 @@ func NewServices(repository *repository.Repository) Services {
eventTypeService := NewEventTypeService(repository)
actionTypeService := NewActionTypeService(repository)
+ eventServiceWS := NewEventServiceWS(eventTypeService)
+
return Services{
EventTypeService: eventTypeService,
- EventServiceWS: NewEventServiceWS(eventTypeService),
SocketIOService: NewSocketIOService(),
+ EventServiceWS: eventServiceWS,
ActionTypeService: actionTypeService,
ActionServiceWS: NewActionServiceWS(actionTypeService),
+ YSKService: NewYSKService(repository, eventServiceWS, eventTypeService),
}
}
diff --git a/service/ysk.go b/service/ysk.go
new file mode 100644
index 0000000..4772f41
--- /dev/null
+++ b/service/ysk.go
@@ -0,0 +1,116 @@
+package service
+
+import (
+ "context"
+ "encoding/json"
+ "fmt"
+ "log"
+
+ "github.com/IceWhaleTech/CasaOS-Common/utils/logger"
+ "github.com/IceWhaleTech/CasaOS-MessageBus/common"
+ "github.com/IceWhaleTech/CasaOS-MessageBus/model"
+ "github.com/IceWhaleTech/CasaOS-MessageBus/pkg/ysk"
+ "github.com/IceWhaleTech/CasaOS-MessageBus/repository"
+ "github.com/IceWhaleTech/CasaOS-MessageBus/utils"
+ "go.uber.org/zap"
+)
+
+type YSKService struct {
+ repository *repository.Repository
+ ws *EventServiceWS
+ eventTypeService *EventTypeService
+}
+
+func NewYSKService(
+ repository *repository.Repository,
+ ws *EventServiceWS,
+ ets *EventTypeService,
+) *YSKService {
+ return &YSKService{
+ repository: repository,
+ ws: ws,
+ eventTypeService: ets,
+ }
+}
+
+func (s *YSKService) YskCardList(ctx context.Context) ([]ysk.YSKCard, error) {
+ cardList, err := (*s.repository).GetYSKCardList()
+ if err != nil {
+ return []ysk.YSKCard{}, err
+ }
+ return cardList, nil
+}
+
+func (s *YSKService) UpsertYSKCard(ctx context.Context, yskCard ysk.YSKCard) error {
+ // don't store short notice cards
+ if yskCard.CardType == ysk.CardTypeShortNote {
+ return nil
+ }
+ err := (*s.repository).UpsertYSKCard(yskCard)
+ return err
+}
+
+func (s *YSKService) DeleteYSKCard(ctx context.Context, id string) error {
+ return (*s.repository).DeleteYSKCard(id)
+}
+
+func (s *YSKService) Start(init bool) {
+ if init {
+
+ s.UpsertYSKCard(context.Background(), utils.ApplicationInstallProgress.WithProgress("Installing Jellyfin", 20))
+ s.UpsertYSKCard(context.Background(), utils.ZimaOSDataStationNotice)
+ s.UpsertYSKCard(context.Background(), utils.ZimaOSFileManagementNotice)
+ s.UpsertYSKCard(context.Background(), utils.ZimaOSRemoteAccessNotice)
+ s.UpsertYSKCard(context.Background(), utils.DiskInsertNotice)
+ }
+ // register event
+ s.eventTypeService.RegisterEventType(model.EventType{
+ SourceID: common.SERVICENAME,
+ Name: common.EventTypeYSKCardUpsert.Name,
+ })
+
+ s.eventTypeService.RegisterEventType(model.EventType{
+ SourceID: common.SERVICENAME,
+ Name: common.EventTypeYSKCardDelete.Name,
+ })
+
+ channel, err := s.ws.Subscribe(common.SERVICENAME, []string{
+ common.EventTypeYSKCardUpsert.Name, common.EventTypeYSKCardDelete.Name,
+ })
+ if err != nil {
+ logger.Error("failed to subscribe to event", zap.Error(err))
+ return
+ }
+
+ go func() {
+ for {
+ select {
+ case event, ok := <-channel:
+ if !ok {
+ log.Println("channel closed")
+ }
+ switch event.Name {
+ case common.EventTypeYSKCardUpsert.Name:
+ var card ysk.YSKCard
+ err := json.Unmarshal([]byte(event.Properties[common.PropertyTypeCardBody.Name]), &card)
+ if err != nil {
+ logger.Error("failed to umarshal ysk card", zap.Error(err))
+ continue
+ }
+ err = s.UpsertYSKCard(context.Background(), card)
+ if err != nil {
+ logger.Error("failed to upsert ysk card", zap.Error(err))
+ }
+ case common.EventTypeYSKCardDelete.Name:
+ err = s.DeleteYSKCard(context.Background(), event.Properties[common.PropertyTypeCardID.Name])
+ if err != nil {
+ logger.Error("failed to delete ysk card", zap.Error(err))
+ }
+ default:
+ fmt.Println(event)
+ }
+ }
+ }
+ }()
+
+}
diff --git a/service/ysk_test.go b/service/ysk_test.go
new file mode 100644
index 0000000..d4c5621
--- /dev/null
+++ b/service/ysk_test.go
@@ -0,0 +1,82 @@
+package service_test
+
+import (
+ "context"
+ "testing"
+
+ "github.com/IceWhaleTech/CasaOS-MessageBus/pkg/ysk"
+ "github.com/IceWhaleTech/CasaOS-MessageBus/repository"
+ "github.com/IceWhaleTech/CasaOS-MessageBus/service"
+ "github.com/IceWhaleTech/CasaOS-MessageBus/utils"
+ "gotest.tools/assert"
+)
+
+func setup(t *testing.T) (*service.YSKService, func()) {
+ repository, err := repository.NewDatabaseRepositoryInMemory()
+ assert.NilError(t, err)
+
+ yskService := service.NewYSKService(&repository, nil, nil)
+ return yskService, func() {
+ repository.Close()
+ }
+}
+
+func TestInsertAndGetCardList(t *testing.T) {
+ yskService, cleanup := setup(t)
+ defer cleanup()
+
+ cardList, err := yskService.YskCardList(context.Background())
+ assert.NilError(t, err)
+ assert.Equal(t, len(cardList), 0)
+
+ cardInsertQueue := []ysk.YSKCard{
+ utils.ApplicationInstallProgress, utils.ZimaOSDataStationNotice,
+ }
+
+ for _, card := range cardInsertQueue {
+ err = yskService.UpsertYSKCard(context.Background(), card)
+ assert.NilError(t, err)
+ }
+
+ cardList, err = yskService.YskCardList(context.Background())
+ assert.NilError(t, err)
+ assert.Equal(t, len(cardList), 2)
+
+ for _, card := range cardList {
+ if card.Id == utils.ApplicationInstallProgress.Id {
+ assert.DeepEqual(t, card, utils.ApplicationInstallProgress)
+ } else if card.Id == utils.ZimaOSDataStationNotice.Id {
+ assert.DeepEqual(t, card, utils.ZimaOSDataStationNotice)
+ } else {
+ t.Errorf("unexpected card: %v", card)
+ }
+ }
+}
+
+func TestInsertAllTypeCardList(t *testing.T) {
+ yskService, cleanup := setup(t)
+ defer cleanup()
+
+ cardList, err := yskService.YskCardList(context.Background())
+ assert.NilError(t, err)
+ assert.Equal(t, len(cardList), 0)
+
+ cardInsertQueue := []ysk.YSKCard{
+ utils.ApplicationInstallProgress, utils.ZimaOSDataStationNotice,
+ // the notice is short. it didn't be stored
+ utils.ApplicationUpdateNotice,
+ utils.ApplicationInstallProgress.WithProgress("Installing LinuxServer/Jellyfin", 50), utils.ApplicationInstallProgress.WithProgress("Installing LinuxServer/Jellyfin", 55),
+ utils.ApplicationInstallProgress.WithProgress("Installing LinuxServer/Jellyfin", 80), utils.ApplicationInstallProgress.WithProgress("Installing LinuxServer/Jellyfin", 99),
+ utils.ApplicationUpdateNotice,
+ }
+
+ for _, card := range cardInsertQueue {
+ err = yskService.UpsertYSKCard(context.Background(), card)
+ assert.NilError(t, err)
+ }
+
+ cardList, err = yskService.YskCardList(context.Background())
+ assert.NilError(t, err)
+ assert.Equal(t, len(cardList), 2)
+
+}
diff --git a/utils/fixtures.go b/utils/fixtures.go
new file mode 100644
index 0000000..ef3795e
--- /dev/null
+++ b/utils/fixtures.go
@@ -0,0 +1,157 @@
+package utils
+
+import (
+ "github.com/IceWhaleTech/CasaOS-MessageBus/pkg/ysk"
+)
+
+var (
+ ApplicationInstallProgress = ysk.YSKCard{
+ Id: "task:application:install",
+ CardType: ysk.CardTypeTask,
+ RenderType: ysk.RenderTypeCardTask,
+ Content: ysk.YSKCardContent{
+ TitleIcon: "jellyfin logo",
+ TitleText: "APP Installing",
+ BodyProgress: &ysk.YSKCardProgress{},
+ BodyIconWithText: nil,
+ BodyList: nil,
+ FooterActions: nil,
+ },
+ }
+
+ ZimaOSDataStationNotice = ysk.YSKCard{
+ Id: "long-notice:disk:insert",
+ CardType: ysk.CardTypeLongNote,
+ RenderType: ysk.RenderTypeCardIconTextNotice,
+ Content: ysk.YSKCardContent{
+ TitleIcon: "ZimaOS-Logo",
+ TitleText: "创建数据工作站",
+ BodyProgress: nil,
+ BodyIconWithText: &ysk.YSKCardIconWithText{
+ Icon: "disk",
+ Description: "通过添加硬盘驱动器或固态硬盘来增强你的个人主机,并建立自己的个人数据中心。",
+ },
+ BodyList: nil,
+ FooterActions: []ysk.YSKCardFooterAction{
+ {
+ Side: "Right",
+ Style: "primary",
+ Text: "创建数据工作站",
+ MessageBus: ysk.YSKCardMessageBusAction{
+ Key: "open:disk:insert",
+ Payload: "{'type':'disk'}",
+ },
+ },
+ },
+ },
+ }
+
+ ZimaOSRemoteAccessNotice = ysk.YSKCard{
+ Id: "long-notice:remote:access",
+ CardType: ysk.CardTypeLongNote,
+ RenderType: ysk.RenderTypeCardIconTextNotice,
+ Content: ysk.YSKCardContent{
+ TitleIcon: "ZimaOS-Logo",
+ TitleText: "远程访问",
+ BodyProgress: nil,
+ BodyIconWithText: &ysk.YSKCardIconWithText{
+ Icon: "remote access",
+ Description: "通过远程访问,您可以随时随地访问您的个人主机。",
+ },
+ BodyList: nil,
+ FooterActions: []ysk.YSKCardFooterAction{
+ {
+ Side: "Right",
+ Style: "primary",
+ Text: "learn more",
+ MessageBus: ysk.YSKCardMessageBusAction{
+ Key: "open:remote:access",
+ Payload: "{'type':'remote'}",
+ },
+ },
+ },
+ },
+ }
+
+ ZimaOSFileManagementNotice = ysk.YSKCard{
+ Id: "long-notice:file:management",
+ CardType: ysk.CardTypeLongNote,
+ RenderType: ysk.RenderTypeCardIconTextNotice,
+ Content: ysk.YSKCardContent{
+ TitleIcon: "ZimaOS-Logo",
+ TitleText: "文件管理",
+ BodyProgress: nil,
+ BodyIconWithText: &ysk.YSKCardIconWithText{
+ Icon: "file management",
+ Description: "使用Files来管理分散的数据,包括备份、 云存储、 NAS 或本地网络中的其他个人数据。",
+ },
+ BodyList: nil,
+ FooterActions: []ysk.YSKCardFooterAction{
+ {
+ Side: "Right",
+ Style: "primary",
+ Text: "learn more",
+ MessageBus: ysk.YSKCardMessageBusAction{
+ Key: "open:file:management",
+ Payload: "{'type':'file'}",
+ },
+ },
+ },
+ },
+ }
+
+ ApplicationUpdateNotice = ysk.YSKCard{
+ Id: "short-notice:application:update",
+ CardType: ysk.CardTypeShortNote,
+ RenderType: ysk.RenderTypeCardListNotice,
+ Content: ysk.YSKCardContent{
+ TitleIcon: "app store logo",
+ TitleText: "有应用更新",
+ BodyList: []ysk.YSKCardListItem{
+ {
+ Icon: "jellyfin logo",
+ Description: "Jellyfin 10.7.0",
+ RightText: "2 days ago",
+ },
+ {
+ Icon: "next-cloud logo",
+ Description: "NextCloud 10.7.0",
+ RightText: "2 days ago",
+ },
+ },
+ FooterActions: []ysk.YSKCardFooterAction{
+ {
+ Side: "Right",
+ Style: "primary",
+ Text: "更新所有",
+ MessageBus: ysk.YSKCardMessageBusAction{
+ Key: "open:application:update",
+ Payload: "{'type':'all'}",
+ },
+ },
+ },
+ },
+ }
+
+ DiskInsertNotice = ysk.YSKCard{
+ Id: "long-notice:disk:insert",
+ CardType: ysk.CardTypeLongNote,
+ RenderType: ysk.RenderTypeCardListNotice,
+ Content: ysk.YSKCardContent{
+ TitleIcon: "disk logo",
+ TitleText: "硬盘插入",
+ BodyList: []ysk.YSKCardListItem{
+ {
+ Icon: "disk",
+ Description: "ZimaOS-HD",
+ RightText: "2TB",
+ }, {
+ Icon: "disk",
+ Description: "Safe-Storage",
+ RightText: "2TB",
+ },
+ },
+ FooterActions: nil,
+ },
+ }
+)