feat(store): per-account folder_mapping + excluded_folders columns
This commit is contained in:
@@ -19,6 +19,8 @@ type Account struct {
|
|||||||
Skipped int64
|
Skipped int64
|
||||||
Errors int64
|
Errors int64
|
||||||
LastError string
|
LastError string
|
||||||
|
FolderMapping map[string]string
|
||||||
|
ExcludedFolders []string
|
||||||
}
|
}
|
||||||
|
|
||||||
func (s *Store) CreateAccount(ctx context.Context, a Account) (int64, error) {
|
func (s *Store) CreateAccount(ctx context.Context, a Account) (int64, error) {
|
||||||
@@ -39,7 +41,8 @@ func (s *Store) DeleteAccount(ctx context.Context, id int64) error {
|
|||||||
func (s *Store) ListAccountsByTask(ctx context.Context, taskID int64) ([]Account, error) {
|
func (s *Store) ListAccountsByTask(ctx context.Context, taskID int64) ([]Account, error) {
|
||||||
rows, err := s.Pool.Query(ctx,
|
rows, err := s.Pool.Query(ctx,
|
||||||
`SELECT id, task_id, src_login, src_pass_enc, dst_login, dst_pass_enc,
|
`SELECT id, task_id, src_login, src_pass_enc, dst_login, dst_pass_enc,
|
||||||
test_src_status, test_dst_status, status, copied_count, skipped_count, error_count, last_error
|
test_src_status, test_dst_status, status, copied_count, skipped_count,
|
||||||
|
error_count, last_error, folder_mapping, excluded_folders
|
||||||
FROM accounts WHERE task_id=$1 ORDER BY id`, taskID)
|
FROM accounts WHERE task_id=$1 ORDER BY id`, taskID)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
@@ -49,7 +52,8 @@ func (s *Store) ListAccountsByTask(ctx context.Context, taskID int64) ([]Account
|
|||||||
for rows.Next() {
|
for rows.Next() {
|
||||||
var a Account
|
var a Account
|
||||||
if err := rows.Scan(&a.ID, &a.TaskID, &a.SrcLogin, &a.SrcPassEnc, &a.DstLogin, &a.DstPassEnc,
|
if err := rows.Scan(&a.ID, &a.TaskID, &a.SrcLogin, &a.SrcPassEnc, &a.DstLogin, &a.DstPassEnc,
|
||||||
&a.TestSrcStatus, &a.TestDstStatus, &a.Status, &a.Copied, &a.Skipped, &a.Errors, &a.LastError); err != nil {
|
&a.TestSrcStatus, &a.TestDstStatus, &a.Status, &a.Copied, &a.Skipped, &a.Errors, &a.LastError,
|
||||||
|
&a.FolderMapping, &a.ExcludedFolders); err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
out = append(out, a)
|
out = append(out, a)
|
||||||
@@ -95,3 +99,19 @@ func (s *Store) IncAccountCounters(ctx context.Context, id, copied, skipped, err
|
|||||||
id, copied, skipped, errs)
|
id, copied, skipped, errs)
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// SetAccountFolderMapping persists an account's per-folder rename map and the
|
||||||
|
// set of source folders to skip. nil is normalized to empty so JSONB stays
|
||||||
|
// '{}' / '[]' rather than null.
|
||||||
|
func (s *Store) SetAccountFolderMapping(ctx context.Context, id int64, mapping map[string]string, excluded []string) error {
|
||||||
|
if mapping == nil {
|
||||||
|
mapping = map[string]string{}
|
||||||
|
}
|
||||||
|
if excluded == nil {
|
||||||
|
excluded = []string{}
|
||||||
|
}
|
||||||
|
_, err := s.Pool.Exec(ctx,
|
||||||
|
`UPDATE accounts SET folder_mapping=$2, excluded_folders=$3 WHERE id=$1`,
|
||||||
|
id, mapping, excluded)
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|||||||
@@ -64,3 +64,31 @@ func TestResetAccountCounters(t *testing.T) {
|
|||||||
a.Copied, a.Skipped, a.Errors)
|
a.Copied, a.Skipped, a.Errors)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func TestSetAccountFolderMapping(t *testing.T) {
|
||||||
|
s := testStore(t)
|
||||||
|
ctx := context.Background()
|
||||||
|
epSrc, _ := s.CreateEndpoint(ctx, Endpoint{RoleLabel: "src", Host: "a", Port: 993, TLSMode: "ssl"})
|
||||||
|
epDst, _ := s.CreateEndpoint(ctx, Endpoint{RoleLabel: "dst", Host: "b", Port: 993, TLSMode: "ssl"})
|
||||||
|
taskID, _ := s.CreateTask(ctx, Task{Name: "t", SrcEndpointID: epSrc, DstEndpointID: epDst})
|
||||||
|
accID, _ := s.CreateAccount(ctx, Account{TaskID: taskID, SrcLogin: "u", SrcPassEnc: "x", DstLogin: "u2", DstPassEnc: "y"})
|
||||||
|
|
||||||
|
// Fresh account defaults: empty map, empty exclusions (not nil after scan).
|
||||||
|
accs, _ := s.ListAccountsByTask(ctx, taskID)
|
||||||
|
if len(accs) != 1 || len(accs[0].FolderMapping) != 0 || len(accs[0].ExcludedFolders) != 0 {
|
||||||
|
t.Fatalf("defaults: map=%v excl=%v", accs[0].FolderMapping, accs[0].ExcludedFolders)
|
||||||
|
}
|
||||||
|
|
||||||
|
if err := s.SetAccountFolderMapping(ctx, accID,
|
||||||
|
map[string]string{"Спам": "Spam"}, []string{"Trash"}); err != nil {
|
||||||
|
t.Fatalf("set: %v", err)
|
||||||
|
}
|
||||||
|
accs, _ = s.ListAccountsByTask(ctx, taskID)
|
||||||
|
a := accs[0]
|
||||||
|
if a.FolderMapping["Спам"] != "Spam" {
|
||||||
|
t.Fatalf("mapping not persisted: %v", a.FolderMapping)
|
||||||
|
}
|
||||||
|
if len(a.ExcludedFolders) != 1 || a.ExcludedFolders[0] != "Trash" {
|
||||||
|
t.Fatalf("excluded not persisted: %v", a.ExcludedFolders)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|||||||
@@ -0,0 +1,2 @@
|
|||||||
|
ALTER TABLE accounts DROP COLUMN excluded_folders;
|
||||||
|
ALTER TABLE accounts DROP COLUMN folder_mapping;
|
||||||
@@ -0,0 +1,6 @@
|
|||||||
|
ALTER TABLE accounts ADD COLUMN folder_mapping JSONB NOT NULL DEFAULT '{}';
|
||||||
|
ALTER TABLE accounts ADD COLUMN excluded_folders JSONB NOT NULL DEFAULT '[]';
|
||||||
|
|
||||||
|
-- Carry the existing task-wide mapping onto its accounts so behavior is preserved.
|
||||||
|
UPDATE accounts a SET folder_mapping = t.folder_mapping
|
||||||
|
FROM tasks t WHERE a.task_id = t.id AND t.folder_mapping <> '{}'::jsonb;
|
||||||
Reference in New Issue
Block a user