]> git.r.bdr.sh - rbdr/mobius/blob - hotline/account_manager.go
Update documentation
[rbdr/mobius] / hotline / account_manager.go
1 package hotline
2
3 import (
4 "fmt"
5 "github.com/stretchr/testify/mock"
6 "gopkg.in/yaml.v3"
7 "os"
8 "path"
9 "path/filepath"
10 "sync"
11 )
12
13 type 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
21 type YAMLAccountManager struct {
22 accounts map[string]Account
23 accountDir string
24
25 mu sync.Mutex
26 }
27
28 func 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
55 func (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
84 func (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
118 func (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
130 func (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
142 func (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
156 type MockAccountManager struct {
157 mock.Mock
158 }
159
160 func (m *MockAccountManager) Create(account Account) error {
161 args := m.Called(account)
162
163 return args.Error(0)
164 }
165
166 func (m *MockAccountManager) Update(account Account, newLogin string) error {
167 args := m.Called(account, newLogin)
168
169 return args.Error(0)
170 }
171
172 func (m *MockAccountManager) Get(login string) *Account {
173 args := m.Called(login)
174
175 return args.Get(0).(*Account)
176 }
177
178 func (m *MockAccountManager) List() []Account {
179 args := m.Called()
180
181 return args.Get(0).([]Account)
182 }
183
184 func (m *MockAccountManager) Delete(login string) error {
185 args := m.Called(login)
186
187 return args.Error(0)
188 }