feat(desktop): push publish tasks via AMQP topic dispatch WebSocket
Introduce a desktop.task.dispatch topic exchange with per-client routing keys. Tenant-api publishes a task_available frame keyed by target client ID when a publish job is created; every instance binds its own transient queue and forwards to the matching WebSocket (/api/desktop/dispatch) it owns. Desktop-client now prefers this push channel and only falls back to HTTP /lease polling when the socket is down. Also drop admin-web monitoring-plugin remnants and scope the publish modal account list to publish platforms. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
This commit is contained in:
@@ -46,6 +46,16 @@ func New(_ context.Context, cfg config.RabbitMQConfig) (*Client, error) {
|
||||
return client, nil
|
||||
}
|
||||
|
||||
// Config returns a copy of the effective rabbitmq configuration. Services that
|
||||
// need to derive routing keys (e.g. desktop dispatch) consult this instead of
|
||||
// hard-coding string constants.
|
||||
func (c *Client) Config() config.RabbitMQConfig {
|
||||
if c == nil {
|
||||
return config.RabbitMQConfig{}
|
||||
}
|
||||
return c.cfg
|
||||
}
|
||||
|
||||
func (c *Client) Close() error {
|
||||
if c == nil {
|
||||
return nil
|
||||
@@ -102,6 +112,91 @@ func (c *Client) PublishDesktopTaskEvent(ctx context.Context, body []byte) error
|
||||
return c.publishFanout(ctx, c.cfg.DesktopTaskEventExchange, body)
|
||||
}
|
||||
|
||||
func (c *Client) PublishDesktopDispatch(ctx context.Context, routingKey string, body []byte) error {
|
||||
return c.publish(ctx, c.cfg.DesktopDispatchExchange, routingKey, body)
|
||||
}
|
||||
|
||||
// ConsumeDesktopDispatch declares a transient per-process queue bound to the
|
||||
// desktop dispatch topic exchange using bindingKeys, and begins consuming from
|
||||
// it. Every tenant-api instance gets its own queue so every instance sees every
|
||||
// dispatch message; the hub inside the instance then filters by target client
|
||||
// presence. Messages are auto-ack (transient delivery is OK: if the instance
|
||||
// dies, the client reconnects and the server re-drives the task via the
|
||||
// existing lease/recovery path).
|
||||
func (c *Client) ConsumeDesktopDispatch(consumerName string, bindingKeys []string) (<-chan amqp.Delivery, *amqp.Channel, error) {
|
||||
if len(bindingKeys) == 0 {
|
||||
return nil, nil, fmt.Errorf("consume rabbitmq desktop dispatch: bindingKeys is required")
|
||||
}
|
||||
|
||||
for attempt := 0; attempt < 2; attempt++ {
|
||||
conn, ch, err := c.openChannel()
|
||||
if err != nil {
|
||||
return nil, nil, err
|
||||
}
|
||||
|
||||
queue, err := ch.QueueDeclare(
|
||||
"",
|
||||
false,
|
||||
true,
|
||||
true,
|
||||
false,
|
||||
nil,
|
||||
)
|
||||
if err != nil {
|
||||
_ = ch.Close()
|
||||
c.invalidateConnection(conn)
|
||||
if attempt == 1 {
|
||||
return nil, nil, fmt.Errorf("declare rabbitmq desktop dispatch queue: %w", err)
|
||||
}
|
||||
continue
|
||||
}
|
||||
|
||||
bindFailed := false
|
||||
for _, key := range bindingKeys {
|
||||
if err := ch.QueueBind(
|
||||
queue.Name,
|
||||
key,
|
||||
c.cfg.DesktopDispatchExchange,
|
||||
false,
|
||||
nil,
|
||||
); err != nil {
|
||||
_ = ch.Close()
|
||||
c.invalidateConnection(conn)
|
||||
if attempt == 1 {
|
||||
return nil, nil, fmt.Errorf("bind rabbitmq desktop dispatch queue: %w", err)
|
||||
}
|
||||
bindFailed = true
|
||||
break
|
||||
}
|
||||
}
|
||||
if bindFailed {
|
||||
continue
|
||||
}
|
||||
|
||||
deliveries, err := ch.Consume(
|
||||
queue.Name,
|
||||
consumerName,
|
||||
true,
|
||||
true,
|
||||
false,
|
||||
false,
|
||||
nil,
|
||||
)
|
||||
if err != nil {
|
||||
_ = ch.Close()
|
||||
c.invalidateConnection(conn)
|
||||
if attempt == 1 {
|
||||
return nil, nil, fmt.Errorf("consume rabbitmq desktop dispatch queue: %w", err)
|
||||
}
|
||||
continue
|
||||
}
|
||||
|
||||
return deliveries, ch, nil
|
||||
}
|
||||
|
||||
return nil, nil, fmt.Errorf("consume rabbitmq desktop dispatch queue: retry exhausted")
|
||||
}
|
||||
|
||||
func (c *Client) PublishTemplateAssistTask(ctx context.Context, body []byte) error {
|
||||
return c.publish(ctx, c.cfg.TemplateAssistExchange, c.cfg.TemplateAssistRoutingKey, body)
|
||||
}
|
||||
@@ -578,6 +673,18 @@ func (c *Client) ensureTopology(conn *amqp.Connection) error {
|
||||
return fmt.Errorf("declare rabbitmq exchange %s: %w", c.cfg.DesktopTaskEventExchange, err)
|
||||
}
|
||||
|
||||
if err := ch.ExchangeDeclare(
|
||||
c.cfg.DesktopDispatchExchange,
|
||||
"topic",
|
||||
true,
|
||||
false,
|
||||
false,
|
||||
false,
|
||||
nil,
|
||||
); err != nil {
|
||||
return fmt.Errorf("declare rabbitmq exchange %s: %w", c.cfg.DesktopDispatchExchange, err)
|
||||
}
|
||||
|
||||
if err := c.ensureWorkQueue(
|
||||
ch,
|
||||
c.cfg.TemplateAssistExchange,
|
||||
|
||||
Reference in New Issue
Block a user