mirror of
https://gh-proxy.org/https://github.com/klinsmaya/multica.git
synced 2026-11-09 01:46:46 +08:00
Adds a DingTalk (钉钉) bot integration on the bring-your-own-app model: a workspace admin creates their own Stream-mode robot and pastes its AppKey / AppSecret, so no public webhook or OAuth redirect is required. Each agent gets its own bot identity, so several agents can be distinct, separately @-mentionable contacts in one DingTalk organization. Supports DMs, @-mentions in groups, inbound images, /issue quick-create, and /new. Built on the shared channel engine (ForceFresh/BareFresh, MediaResolver / MediaRef and the intent ledger) rather than a private implementation. Off unless MULTICA_DINGTALK_SECRET_KEY is set. Docs in en/zh/ja/ko. Closes #4791. Community-maintained: @yyclaw is the code owner for server/internal/integrations/dingtalk/.
136 lines
5.0 KiB
Go
136 lines
5.0 KiB
Go
// Code generated by sqlc. DO NOT EDIT.
|
|
// versions:
|
|
// sqlc v1.31.1
|
|
// source: dingtalk.sql
|
|
|
|
package db
|
|
|
|
import (
|
|
"context"
|
|
|
|
"github.com/jackc/pgx/v5/pgtype"
|
|
)
|
|
|
|
const deleteDingTalkInstallationForReplacement = `-- name: DeleteDingTalkInstallationForReplacement :one
|
|
WITH retired AS (
|
|
DELETE FROM channel_installation ci
|
|
WHERE ci.id = $1
|
|
AND ci.workspace_id = $2
|
|
AND ci.agent_id = $3
|
|
AND ci.channel_type = 'dingtalk'
|
|
RETURNING ci.id
|
|
),
|
|
cleared_chat_sessions AS (
|
|
DELETE FROM channel_chat_session_binding
|
|
WHERE installation_id IN (SELECT id FROM retired)
|
|
RETURNING chat_session_id
|
|
),
|
|
cleared_outbound_cards AS (
|
|
DELETE FROM channel_outbound_card_message
|
|
WHERE chat_session_id IN (SELECT chat_session_id FROM cleared_chat_sessions)
|
|
),
|
|
cleared_binding_tokens AS (
|
|
DELETE FROM channel_binding_token
|
|
WHERE installation_id IN (SELECT id FROM retired)
|
|
),
|
|
cleared_user_bindings AS (
|
|
DELETE FROM channel_user_binding
|
|
WHERE installation_id IN (SELECT id FROM retired)
|
|
),
|
|
cleared_inbound_dedup AS (
|
|
DELETE FROM channel_inbound_message_dedup
|
|
WHERE installation_id IN (SELECT id FROM retired)
|
|
),
|
|
detached_audit AS (
|
|
UPDATE channel_inbound_audit SET installation_id = NULL
|
|
WHERE installation_id IN (SELECT id FROM retired)
|
|
),
|
|
detached_media_intents AS (
|
|
UPDATE channel_media_pending_object SET installation_id = NULL
|
|
WHERE installation_id IN (SELECT id FROM retired)
|
|
)
|
|
SELECT retired.id FROM retired
|
|
`
|
|
|
|
type DeleteDingTalkInstallationForReplacementParams struct {
|
|
InstallationID pgtype.UUID `json:"installation_id"`
|
|
WorkspaceID pgtype.UUID `json:"workspace_id"`
|
|
AgentID pgtype.UUID `json:"agent_id"`
|
|
}
|
|
|
|
// Retires an installation when the same agent is connected with a DIFFERENT
|
|
// AppKey. A senderStaffId is scoped to one DingTalk organization, so none of the
|
|
// old installation's identity, token, session, dedup, or outbound state may
|
|
// cross into the new robot. The caller inserts a fresh installation in the same
|
|
// transaction, giving the replacement a new installation_id that also fences
|
|
// late writes and replies from the old connection.
|
|
//
|
|
// Chat sessions themselves remain as history, but their channel bindings and
|
|
// outbound cards are removed. Audit and media-intent rows remain useful for
|
|
// diagnostics / reconciliation, so their installation references are detached.
|
|
func (q *Queries) DeleteDingTalkInstallationForReplacement(ctx context.Context, arg DeleteDingTalkInstallationForReplacementParams) (pgtype.UUID, error) {
|
|
row := q.db.QueryRow(ctx, deleteDingTalkInstallationForReplacement, arg.InstallationID, arg.WorkspaceID, arg.AgentID)
|
|
var id pgtype.UUID
|
|
err := row.Scan(&id)
|
|
return id, err
|
|
}
|
|
|
|
const getDingTalkInstallationOwnerForUpdate = `-- name: GetDingTalkInstallationOwnerForUpdate :one
|
|
SELECT id, COALESCE(config ->> 'app_id', '')::text AS app_id
|
|
FROM channel_installation
|
|
WHERE workspace_id = $1
|
|
AND agent_id = $2
|
|
AND channel_type = 'dingtalk'
|
|
FOR UPDATE
|
|
`
|
|
|
|
type GetDingTalkInstallationOwnerForUpdateParams struct {
|
|
WorkspaceID pgtype.UUID `json:"workspace_id"`
|
|
AgentID pgtype.UUID `json:"agent_id"`
|
|
}
|
|
|
|
type GetDingTalkInstallationOwnerForUpdateRow struct {
|
|
ID pgtype.UUID `json:"id"`
|
|
AppID string `json:"app_id"`
|
|
}
|
|
|
|
// Reads the current robot identity after LockDingTalkInstallationOwner has
|
|
// serialized the logical owner slot. app_id is non-null for every DingTalk
|
|
// installation; COALESCE treats malformed legacy config as a different robot,
|
|
// which safely replaces it instead of preserving unknown identity state.
|
|
func (q *Queries) GetDingTalkInstallationOwnerForUpdate(ctx context.Context, arg GetDingTalkInstallationOwnerForUpdateParams) (GetDingTalkInstallationOwnerForUpdateRow, error) {
|
|
row := q.db.QueryRow(ctx, getDingTalkInstallationOwnerForUpdate, arg.WorkspaceID, arg.AgentID)
|
|
var i GetDingTalkInstallationOwnerForUpdateRow
|
|
err := row.Scan(&i.ID, &i.AppID)
|
|
return i, err
|
|
}
|
|
|
|
const lockDingTalkInstallationOwner = `-- name: LockDingTalkInstallationOwner :exec
|
|
|
|
SELECT pg_advisory_xact_lock(
|
|
hashtextextended(
|
|
($1::uuid)::text || ':' ||
|
|
($2::uuid)::text || ':dingtalk',
|
|
0
|
|
)
|
|
)
|
|
`
|
|
|
|
type LockDingTalkInstallationOwnerParams struct {
|
|
WorkspaceID pgtype.UUID `json:"workspace_id"`
|
|
AgentID pgtype.UUID `json:"agent_id"`
|
|
}
|
|
|
|
// DingTalk-specific installation identity operations. The underlying channel_*
|
|
// tables are shared, but these replacement semantics belong to DingTalk's BYO
|
|
// AppKey model and deliberately stay out of the shared channel query surface.
|
|
// Serializes install / replacement decisions for one logical
|
|
// (workspace, agent, channel) slot. A different-AppKey replacement deletes the
|
|
// old row and inserts a fresh installation id; the advisory lock closes the
|
|
// gap where two concurrent replacements could otherwise miss each other's new
|
|
// row and update it in place, carrying identity state across robot boundaries.
|
|
func (q *Queries) LockDingTalkInstallationOwner(ctx context.Context, arg LockDingTalkInstallationOwnerParams) error {
|
|
_, err := q.db.Exec(ctx, lockDingTalkInstallationOwner, arg.WorkspaceID, arg.AgentID)
|
|
return err
|
|
}
|