main pico / pkg / apps / pgs / db / memory.go
Antonio Mika  ·  2026-10-09
  1package pgsdb
  2
  3import (
  4	"fmt"
  5	"log/slog"
  6	"slices"
  7	"time"
  8
  9	"github.com/google/uuid"
 10	"github.com/picosh/pico/pkg/db"
 11	"github.com/picosh/pico/pkg/shared"
 12)
 13
 14type MemoryDB struct {
 15	Logger      *slog.Logger
 16	Users       []*db.User
 17	Projects    []*db.Project
 18	Pubkeys     []*db.PublicKey
 19	Feature     *db.FeatureFlag
 20	Features    []*db.FeatureFlag
 21	FormEntries []*db.FormEntry
 22}
 23
 24var _ PgsDB = (*MemoryDB)(nil)
 25
 26func NewDBMemory(logger *slog.Logger) *MemoryDB {
 27	d := &MemoryDB{
 28		Logger: logger,
 29	}
 30	d.Logger.Info("connecting to our in-memory database. All data created during runtime will be lost on exit.")
 31	return d
 32}
 33
 34func (me *MemoryDB) SetupTestData() {
 35	user := &db.User{
 36		ID:   uuid.NewString(),
 37		Name: "testusr",
 38	}
 39	me.Users = append(me.Users, user)
 40	feature := db.NewFeatureFlag(
 41		user.ID,
 42		"plus",
 43		uint64(25*shared.MB),
 44		int64(10*shared.MB),
 45		int64(5*shared.KB),
 46	)
 47	expiresAt := time.Now().Add(time.Hour * 24)
 48	feature.ExpiresAt = &expiresAt
 49	me.Feature = feature
 50}
 51
 52var errNotImpl = fmt.Errorf("not implemented")
 53
 54func (me *MemoryDB) FindUsers() ([]*db.User, error) {
 55	users := []*db.User{}
 56	return users, errNotImpl
 57}
 58
 59func (me *MemoryDB) FindUserByPubkey(key string) (*db.User, error) {
 60	for _, pk := range me.Pubkeys {
 61		if pk.Key == key {
 62			return me.FindUser(pk.UserID)
 63		}
 64	}
 65	return nil, fmt.Errorf("user not found")
 66}
 67
 68func (me *MemoryDB) FindUser(userID string) (*db.User, error) {
 69	for _, user := range me.Users {
 70		if user.ID == userID {
 71			return user, nil
 72		}
 73	}
 74	return nil, fmt.Errorf("user not found")
 75}
 76
 77func (me *MemoryDB) FindUserByName(name string) (*db.User, error) {
 78	for _, user := range me.Users {
 79		if user.Name == name {
 80			return user, nil
 81		}
 82	}
 83	return nil, fmt.Errorf("user not found")
 84}
 85
 86func (me *MemoryDB) FindFeature(userID, name string) (*db.FeatureFlag, error) {
 87	for _, ff := range me.Features {
 88		if ff.UserID == userID && ff.Name == name {
 89			return ff, nil
 90		}
 91	}
 92	if me.Feature != nil && (me.Feature.Name == name || me.Feature.Name == "") {
 93		return me.Feature, nil
 94	}
 95	return nil, fmt.Errorf("feature flag not found")
 96}
 97
 98func (me *MemoryDB) Close() error {
 99	return nil
100}
101
102func (me *MemoryDB) FindTotalSizeForUser(userID string) (int, error) {
103	return 0, errNotImpl
104}
105
106func (me *MemoryDB) InsertProject(userID, name, projectDir string) (string, error) {
107	id := uuid.NewString()
108	now := time.Now()
109	me.Projects = append(me.Projects, &db.Project{
110		ID:         id,
111		UserID:     userID,
112		Name:       name,
113		ProjectDir: projectDir,
114		CreatedAt:  &now,
115		UpdatedAt:  &now,
116	})
117	return id, nil
118}
119
120func (me *MemoryDB) UpdateProject(userID, name string) error {
121	project, err := me.FindProjectByName(userID, name)
122	if err != nil {
123		return err
124	}
125
126	now := time.Now()
127	project.UpdatedAt = &now
128
129	return nil
130}
131
132func (me *MemoryDB) UpsertProject(userID, projectName, projectDir string) (*db.Project, error) {
133	project, err := me.FindProjectByName(userID, projectName)
134	if err == nil {
135		// this just updates the `createdAt` timestamp, useful for book-keeping
136		err = me.UpdateProject(userID, projectName)
137		if err != nil {
138			me.Logger.Error("could not update project", "err", err)
139			return nil, err
140		}
141		return project, nil
142	}
143
144	_, err = me.InsertProject(userID, projectName, projectName)
145	if err != nil {
146		me.Logger.Error("could not create project", "err", err)
147		return nil, err
148	}
149	return me.FindProjectByName(userID, projectName)
150}
151
152func (me *MemoryDB) LinkToProject(userID, projectID, projectDir string, commit bool) error {
153	for _, project := range me.Projects {
154		if project.ID == projectID && project.UserID == userID {
155			if commit {
156				project.ProjectDir = projectDir
157			}
158			return nil
159		}
160	}
161	return fmt.Errorf("project not found by id %s", projectID)
162}
163
164func (me *MemoryDB) RemoveProject(projectID string) error {
165	me.Projects = slices.DeleteFunc(me.Projects, func(project *db.Project) bool {
166		return project.ID == projectID
167	})
168	return nil
169}
170
171func (me *MemoryDB) FindProjectByName(userID, name string) (*db.Project, error) {
172	for _, project := range me.Projects {
173		if project.UserID != userID {
174			continue
175		}
176
177		if project.Name != name {
178			continue
179		}
180
181		return project, nil
182	}
183	return nil, fmt.Errorf("project not found by name %s", name)
184}
185
186func (me *MemoryDB) FindProjectLinks(userID, name string) ([]*db.Project, error) {
187	links := []*db.Project{}
188	for _, project := range me.Projects {
189		if project.UserID == userID && project.ProjectDir == name && project.Name != name {
190			links = append(links, project)
191		}
192	}
193	return links, nil
194}
195
196func (me *MemoryDB) FindProjectsByPrefix(userID, prefix string) ([]*db.Project, error) {
197	return []*db.Project{}, errNotImpl
198}
199
200func (me *MemoryDB) FindProjectsByUser(userID string) ([]*db.Project, error) {
201	pjs := []*db.Project{}
202	for _, project := range me.Projects {
203		if project.UserID != userID {
204			continue
205		}
206		pjs = append(pjs, project)
207	}
208	return pjs, nil
209}
210
211func (me *MemoryDB) FindProjects(userID string) ([]*db.Project, error) {
212	return []*db.Project{}, errNotImpl
213}
214
215func (me *MemoryDB) UpdateProjectAcl(userID, name string, acl db.ProjectAcl) error {
216	project, err := me.FindProjectByName(userID, name)
217	if err != nil {
218		return err
219	}
220	project.Acl = acl
221	return nil
222}
223
224func (me *MemoryDB) RegisterAdmin(username, pubkey, pubkeyName string) error {
225	return errNotImpl
226}
227
228func (me *MemoryDB) InsertAccessLog(*db.AccessLog) error {
229	return errNotImpl
230}
231
232func (me *MemoryDB) InsertFormEntry(userID, name string, data map[string]interface{}) error {
233	id := uuid.NewString()
234	now := time.Now()
235	entry := &db.FormEntry{
236		ID:        id,
237		UserID:    userID,
238		Name:      name,
239		Data:      data,
240		CreatedAt: &now,
241	}
242	me.FormEntries = append(me.FormEntries, entry)
243	return nil
244}
245
246func (me *MemoryDB) FindFormEntriesByUserAndName(userID, name string) ([]*db.FormEntry, error) {
247	entries := []*db.FormEntry{}
248	for _, entry := range me.FormEntries {
249		if entry.UserID == userID && entry.Name == name {
250			entries = append(entries, entry)
251		}
252	}
253	return entries, nil
254}
255
256func (me *MemoryDB) FindFormNamesByUser(userID string) ([]string, error) {
257	names := make(map[string]bool)
258	for _, entry := range me.FormEntries {
259		if entry.UserID == userID {
260			names[entry.Name] = true
261		}
262	}
263	result := []string{}
264	for name := range names {
265		result = append(result, name)
266	}
267	return result, nil
268}
269
270func (me *MemoryDB) RemoveFormEntriesByUserAndName(userID, name string) error {
271	filtered := []*db.FormEntry{}
272	for _, entry := range me.FormEntries {
273		if entry.UserID != userID || entry.Name != name {
274			filtered = append(filtered, entry)
275		}
276	}
277	me.FormEntries = filtered
278	return nil
279}