]> git.r.bdr.sh - rbdr/mobius/blame - hotline/account_manager.go
Extensive refactor and clean up
[rbdr/mobius] / hotline / account_manager.go
CommitLineData
d9bc63a1
JH
1package hotline
2
3import (
4 "fmt"
5 "github.com/stretchr/testify/mock"
6 "gopkg.in/yaml.v3"
7 "os"
8 "path"
9 "path/filepath"
10 "sync"
11)
12
13type AccountManager interface {
14 Create(account Account) error
15 Update(account Account, newLogin string) error
16 Get(login string) *Account
17 List() []Account
18 Delete(login string) error
19}
20
21type YAMLAccountManager struct {
22 accounts map[string]Account
23 accountDir string
24
25 mu sync.Mutex
26}
27
28func NewYAMLAccountManager(accountDir string) (*YAMLAccountManager, error) {
29 accountMgr := YAMLAccountManager{
30 accountDir: accountDir,
31 accounts: make(map[string]Account),
32 }
33
34 matches, err := filepath.Glob(filepath.Join(accountDir, "*.yaml"))
35 if err != nil {
36 return nil, err
37 }
38
39 if len(matches) == 0 {
40 return nil, fmt.Errorf("no accounts found in directory: %s", accountDir)
41 }
42
43 for _, file := range matches {
44 var account Account
45 if err = loadFromYAMLFile(file, &account); err != nil {
46 return nil, fmt.Errorf("error loading account %s: %w", file, err)
47 }
48
49 accountMgr.accounts[account.Login] = account
50 }
51
52 return &accountMgr, nil
53}
54
55func (am *YAMLAccountManager) Create(account Account) error {
56 am.mu.Lock()
57 defer am.mu.Unlock()
58
59 // Create account file, returning an error if one already exists.
60 file, err := os.OpenFile(
61 filepath.Join(am.accountDir, path.Join("/", account.Login+".yaml")),
62 os.O_CREATE|os.O_EXCL|os.O_WRONLY, 0644,
63 )
64 if err != nil {
65 return fmt.Errorf("error creating account file: %w", err)
66 }
67 defer file.Close()
68
69 b, err := yaml.Marshal(account)
70 if err != nil {
71 return fmt.Errorf("marshal account to YAML: %v", err)
72 }
73
74 _, err = file.Write(b)
75 if err != nil {
76 return fmt.Errorf("write account file: %w", err)
77 }
78
79 am.accounts[account.Login] = account
80
81 return nil
82}
83
84func (am *YAMLAccountManager) Update(account Account, newLogin string) error {
85 am.mu.Lock()
86 defer am.mu.Unlock()
87
88 // If the login has changed, rename the account file.
89 if account.Login != newLogin {
90 err := os.Rename(
91 filepath.Join(am.accountDir, path.Join("/", account.Login)+".yaml"),
92 filepath.Join(am.accountDir, path.Join("/", newLogin)+".yaml"),
93 )
94 if err != nil {
95 return fmt.Errorf("error renaming account file: %w", err)
96 }
97
98 account.Login = newLogin
99 am.accounts[newLogin] = account
100
101 delete(am.accounts, account.Login)
102 }
103
104 out, err := yaml.Marshal(&account)
105 if err != nil {
106 return err
107 }
108
109 if err := os.WriteFile(filepath.Join(am.accountDir, newLogin+".yaml"), out, 0644); err != nil {
110 return fmt.Errorf("error writing account file: %w", err)
111 }
112
113 am.accounts[account.Login] = account
114
115 return nil
116}
117
118func (am *YAMLAccountManager) Get(login string) *Account {
119 am.mu.Lock()
120 defer am.mu.Unlock()
121
122 account, ok := am.accounts[login]
123 if !ok {
124 return nil
125 }
126
127 return &account
128}
129
130func (am *YAMLAccountManager) List() []Account {
131 am.mu.Lock()
132 defer am.mu.Unlock()
133
134 var accounts []Account
135 for _, account := range am.accounts {
136 accounts = append(accounts, account)
137 }
138
139 return accounts
140}
141
142func (am *YAMLAccountManager) Delete(login string) error {
143 am.mu.Lock()
144 defer am.mu.Unlock()
145
146 err := os.Remove(filepath.Join(am.accountDir, path.Join("/", login+".yaml")))
147 if err != nil {
148 return fmt.Errorf("delete account file: %v", err)
149 }
150
151 delete(am.accounts, login)
152
153 return nil
154}
155
156type MockAccountManager struct {
157 mock.Mock
158}
159
160func (m *MockAccountManager) Create(account Account) error {
161 args := m.Called(account)
162
163 return args.Error(0)
164}
165
166func (m *MockAccountManager) Update(account Account, newLogin string) error {
167 args := m.Called(account, newLogin)
168
169 return args.Error(0)
170}
171
172func (m *MockAccountManager) Get(login string) *Account {
173 args := m.Called(login)
174
175 return args.Get(0).(*Account)
176}
177
178func (m *MockAccountManager) List() []Account {
179 args := m.Called()
180
181 return args.Get(0).([]Account)
182}
183
184func (m *MockAccountManager) Delete(login string) error {
185 args := m.Called(login)
186
187 return args.Error(0)
188}