2026-06-26 08:37:04 +00:00
|
|
|
package repository
|
|
|
|
|
|
|
|
|
|
import (
|
|
|
|
|
"context"
|
|
|
|
|
|
|
|
|
|
"haixun-backend/internal/library/clock"
|
2026-06-26 16:02:06 +00:00
|
|
|
"haixun-backend/internal/library/crypto"
|
2026-06-26 08:37:04 +00:00
|
|
|
app "haixun-backend/internal/library/errors"
|
|
|
|
|
"haixun-backend/internal/library/errors/code"
|
|
|
|
|
"haixun-backend/internal/model/threads_account/domain/entity"
|
|
|
|
|
domrepo "haixun-backend/internal/model/threads_account/domain/repository"
|
|
|
|
|
|
|
|
|
|
"go.mongodb.org/mongo-driver/bson"
|
|
|
|
|
"go.mongodb.org/mongo-driver/mongo"
|
|
|
|
|
"go.mongodb.org/mongo-driver/mongo/options"
|
|
|
|
|
)
|
|
|
|
|
|
|
|
|
|
type secretsMongoRepository struct {
|
|
|
|
|
collection *mongo.Collection
|
2026-06-26 16:02:06 +00:00
|
|
|
cipher *crypto.Cipher
|
2026-06-26 08:37:04 +00:00
|
|
|
}
|
|
|
|
|
|
2026-06-26 16:02:06 +00:00
|
|
|
func NewSecretsMongoRepository(db *mongo.Database, cipher *crypto.Cipher) domrepo.SecretsRepository {
|
2026-06-26 08:37:04 +00:00
|
|
|
if db == nil {
|
2026-06-26 16:02:06 +00:00
|
|
|
return &secretsMongoRepository{cipher: cipher}
|
2026-06-26 08:37:04 +00:00
|
|
|
}
|
2026-06-26 16:02:06 +00:00
|
|
|
return &secretsMongoRepository{collection: db.Collection(entity.SecretsCollectionName), cipher: cipher}
|
2026-06-26 08:37:04 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
func (r *secretsMongoRepository) EnsureIndexes(ctx context.Context) error {
|
|
|
|
|
if r.collection == nil {
|
|
|
|
|
return nil
|
|
|
|
|
}
|
|
|
|
|
_, err := r.collection.Indexes().CreateOne(ctx, mongo.IndexModel{
|
|
|
|
|
Keys: bson.D{{Key: "_id", Value: 1}},
|
|
|
|
|
})
|
|
|
|
|
return err
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
func (r *secretsMongoRepository) FindByAccountID(ctx context.Context, accountID string) (*entity.Secrets, error) {
|
|
|
|
|
if r.collection == nil {
|
|
|
|
|
return nil, app.For(code.ThreadsAccount).DBUnavailable("Mongo is not configured")
|
|
|
|
|
}
|
|
|
|
|
var out entity.Secrets
|
|
|
|
|
err := r.collection.FindOne(ctx, bson.M{"_id": accountID}).Decode(&out)
|
|
|
|
|
if err == mongo.ErrNoDocuments {
|
|
|
|
|
return nil, nil
|
|
|
|
|
}
|
|
|
|
|
if err != nil {
|
|
|
|
|
return nil, err
|
|
|
|
|
}
|
2026-06-26 16:02:06 +00:00
|
|
|
if r.cipher != nil && out.BrowserStorageState != "" {
|
|
|
|
|
plain, decErr := r.cipher.Decrypt(out.BrowserStorageState)
|
|
|
|
|
if decErr != nil {
|
|
|
|
|
return nil, decErr
|
|
|
|
|
}
|
|
|
|
|
out.BrowserStorageState = plain
|
|
|
|
|
}
|
2026-06-26 08:37:04 +00:00
|
|
|
return &out, nil
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
func (r *secretsMongoRepository) SaveBrowserStorageState(ctx context.Context, accountID, storageState string) (*entity.Secrets, error) {
|
|
|
|
|
if r.collection == nil {
|
|
|
|
|
return nil, app.For(code.ThreadsAccount).DBUnavailable("Mongo is not configured")
|
|
|
|
|
}
|
|
|
|
|
now := clock.NowUnixNano()
|
2026-06-26 16:02:06 +00:00
|
|
|
storedValue := storageState
|
|
|
|
|
if r.cipher != nil {
|
|
|
|
|
enc, encErr := r.cipher.Encrypt(storageState)
|
|
|
|
|
if encErr != nil {
|
|
|
|
|
return nil, encErr
|
|
|
|
|
}
|
|
|
|
|
storedValue = enc
|
|
|
|
|
}
|
2026-06-26 08:37:04 +00:00
|
|
|
var out entity.Secrets
|
|
|
|
|
err := r.collection.FindOneAndUpdate(
|
|
|
|
|
ctx,
|
|
|
|
|
bson.M{"_id": accountID},
|
|
|
|
|
bson.M{
|
|
|
|
|
"$set": bson.M{
|
2026-06-26 16:02:06 +00:00
|
|
|
"browser_storage_state": storedValue,
|
2026-06-26 08:37:04 +00:00
|
|
|
"update_at": now,
|
|
|
|
|
},
|
|
|
|
|
"$setOnInsert": bson.M{"_id": accountID},
|
|
|
|
|
},
|
|
|
|
|
options.FindOneAndUpdate().SetUpsert(true).SetReturnDocument(options.After),
|
|
|
|
|
).Decode(&out)
|
|
|
|
|
if err != nil {
|
|
|
|
|
return nil, err
|
|
|
|
|
}
|
2026-06-26 16:02:06 +00:00
|
|
|
// Return plaintext to callers regardless of at-rest encryption.
|
|
|
|
|
out.BrowserStorageState = storageState
|
2026-06-26 08:37:04 +00:00
|
|
|
return &out, nil
|
|
|
|
|
}
|