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