84 lines
2.5 KiB
Go
84 lines
2.5 KiB
Go
|
|
package usecase
|
||
|
|
|
||
|
|
import (
|
||
|
|
"context"
|
||
|
|
"strings"
|
||
|
|
"time"
|
||
|
|
|
||
|
|
"haixun-backend/internal/library/clock"
|
||
|
|
app "haixun-backend/internal/library/errors"
|
||
|
|
"haixun-backend/internal/library/errors/code"
|
||
|
|
libthreads "haixun-backend/internal/library/threadsapi"
|
||
|
|
domusecase "haixun-backend/internal/model/threads_account/domain/usecase"
|
||
|
|
)
|
||
|
|
|
||
|
|
const defaultTokenRefreshLeadTime = 7 * 24 * time.Hour
|
||
|
|
|
||
|
|
func (u *threadsAccountUseCase) ResolveAccountAPIAccessToken(
|
||
|
|
ctx context.Context,
|
||
|
|
tenantID, ownerUID, accountID string,
|
||
|
|
) (string, error) {
|
||
|
|
account, err := u.assertOwned(ctx, tenantID, ownerUID, accountID)
|
||
|
|
if err != nil {
|
||
|
|
return "", err
|
||
|
|
}
|
||
|
|
secrets, err := u.secretsRepo.FindByAccountID(ctx, account.ID)
|
||
|
|
if err != nil {
|
||
|
|
return "", err
|
||
|
|
}
|
||
|
|
if secrets == nil || !isValidAPIToken(secrets) {
|
||
|
|
return "", app.For(code.ThreadsAccount).InputMissingRequired("此帳號尚未完成 Threads API 連線或 token 已過期")
|
||
|
|
}
|
||
|
|
return strings.TrimSpace(secrets.APIAccessToken), nil
|
||
|
|
}
|
||
|
|
|
||
|
|
func (u *threadsAccountUseCase) RefreshAccountAPIAccessToken(
|
||
|
|
ctx context.Context,
|
||
|
|
tenantID, ownerUID, accountID string,
|
||
|
|
) (*domusecase.SecretsData, error) {
|
||
|
|
token, err := u.ResolveAccountAPIAccessToken(ctx, tenantID, ownerUID, accountID)
|
||
|
|
if err != nil {
|
||
|
|
return nil, err
|
||
|
|
}
|
||
|
|
result, err := libthreads.RefreshAccessToken(ctx, token)
|
||
|
|
if err != nil {
|
||
|
|
return nil, app.For(code.ThreadsAccount).SvcThirdParty("Threads token refresh 失敗").WithCause(err)
|
||
|
|
}
|
||
|
|
expiresAt := int64(0)
|
||
|
|
if result.ExpiresIn > 0 {
|
||
|
|
expiresAt = libthreads.NsFromNow(result.ExpiresIn)
|
||
|
|
}
|
||
|
|
return u.SaveAPIAccessToken(ctx, accountID, result.AccessToken, expiresAt)
|
||
|
|
}
|
||
|
|
|
||
|
|
func (u *threadsAccountUseCase) ListTokenRefreshCandidates(
|
||
|
|
ctx context.Context,
|
||
|
|
leadTime time.Duration,
|
||
|
|
) ([]domusecase.TokenRefreshCandidate, error) {
|
||
|
|
if leadTime <= 0 {
|
||
|
|
leadTime = defaultTokenRefreshLeadTime
|
||
|
|
}
|
||
|
|
deadline := clock.NowUnixNano() + leadTime.Nanoseconds()
|
||
|
|
secretsList, err := u.secretsRepo.ListAPIAccessTokensExpiringBefore(ctx, deadline)
|
||
|
|
if err != nil {
|
||
|
|
return nil, err
|
||
|
|
}
|
||
|
|
out := make([]domusecase.TokenRefreshCandidate, 0, len(secretsList))
|
||
|
|
for _, secrets := range secretsList {
|
||
|
|
if secrets == nil || strings.TrimSpace(secrets.APIAccessToken) == "" {
|
||
|
|
continue
|
||
|
|
}
|
||
|
|
account, err := u.repo.FindByIDGlobal(ctx, secrets.AccountID)
|
||
|
|
if err != nil || account == nil {
|
||
|
|
continue
|
||
|
|
}
|
||
|
|
out = append(out, domusecase.TokenRefreshCandidate{
|
||
|
|
AccountID: account.ID,
|
||
|
|
TenantID: account.TenantID,
|
||
|
|
OwnerUID: account.OwnerUID,
|
||
|
|
ExpiresAt: secrets.APITokenExpiresAt,
|
||
|
|
})
|
||
|
|
}
|
||
|
|
return out, nil
|
||
|
|
}
|