mirror of
https://github.com/Dvorinka/excalidraw-full.git
synced 2026-06-03 22:02:57 +00:00
44414af085
Introduces a 'thumbnail' field to the Canvas model and updates all storage backends (AWS S3, filesystem, memory, and SQLite) to handle storing and retrieving this field. Also updates the API handler to accept and save the thumbnail, and switches SQLite driver to modernc.org/sqlite for improved compatibility. Updates .gitignore to exclude .db files.
168 lines
4.6 KiB
Go
168 lines
4.6 KiB
Go
package memory
|
|
|
|
import (
|
|
"context"
|
|
"excalidraw-complete/core"
|
|
"fmt"
|
|
"sync"
|
|
"time"
|
|
|
|
"github.com/oklog/ulid/v2"
|
|
"github.com/sirupsen/logrus"
|
|
)
|
|
|
|
var (
|
|
savedDocuments = make(map[string]core.Document)
|
|
// savedCanvases is a map where the key is userID, and the value is another map
|
|
// where the key is canvasID and the value is the canvas itself.
|
|
savedCanvases = make(map[string]map[string]*core.Canvas)
|
|
mu sync.RWMutex
|
|
)
|
|
|
|
// memStore implements both DocumentStore and CanvasStore for in-memory storage.
|
|
type memStore struct{}
|
|
|
|
// NewStore creates a new in-memory store.
|
|
func NewStore() *memStore {
|
|
return &memStore{}
|
|
}
|
|
|
|
// FindID retrieves a document by its ID. Part of the DocumentStore interface.
|
|
func (s *memStore) FindID(ctx context.Context, id string) (*core.Document, error) {
|
|
mu.RLock()
|
|
defer mu.RUnlock()
|
|
|
|
log := logrus.WithField("document_id", id)
|
|
if val, ok := savedDocuments[id]; ok {
|
|
log.Info("Document retrieved successfully")
|
|
return &val, nil
|
|
}
|
|
log.WithField("error", "document not found").Warn("Document with specified ID not found")
|
|
return nil, fmt.Errorf("document with id %s not found", id)
|
|
}
|
|
|
|
// Create stores a new document. Part of the DocumentStore interface.
|
|
func (s *memStore) Create(ctx context.Context, document *core.Document) (string, error) {
|
|
mu.Lock()
|
|
defer mu.Unlock()
|
|
|
|
id := ulid.Make().String()
|
|
savedDocuments[id] = *document
|
|
log := logrus.WithFields(logrus.Fields{
|
|
"document_id": id,
|
|
"data_length": len(document.Data.Bytes()),
|
|
})
|
|
log.Info("Document created successfully")
|
|
|
|
return id, nil
|
|
}
|
|
|
|
// List returns metadata for all canvases owned by a user. Part of the CanvasStore interface.
|
|
func (s *memStore) List(ctx context.Context, userID string) ([]*core.Canvas, error) {
|
|
mu.RLock()
|
|
defer mu.RUnlock()
|
|
|
|
userCanvases, ok := savedCanvases[userID]
|
|
if !ok {
|
|
return []*core.Canvas{}, nil // No canvases for this user, return empty slice
|
|
}
|
|
|
|
canvases := make([]*core.Canvas, 0, len(userCanvases))
|
|
for _, canvas := range userCanvases {
|
|
// Important: create a copy without the large `Data` field for the list view
|
|
listCanvas := &core.Canvas{
|
|
ID: canvas.ID,
|
|
UserID: canvas.UserID,
|
|
Name: canvas.Name,
|
|
Thumbnail: canvas.Thumbnail,
|
|
CreatedAt: canvas.CreatedAt,
|
|
UpdatedAt: canvas.UpdatedAt,
|
|
}
|
|
canvases = append(canvases, listCanvas)
|
|
}
|
|
|
|
logrus.WithField("user_id", userID).Infof("Listed %d canvases", len(canvases))
|
|
return canvases, nil
|
|
}
|
|
|
|
// Get returns a single canvas by its ID, ensuring it belongs to the user. Part of the CanvasStore interface.
|
|
func (s *memStore) Get(ctx context.Context, userID, id string) (*core.Canvas, error) {
|
|
mu.RLock()
|
|
defer mu.RUnlock()
|
|
|
|
log := logrus.WithFields(logrus.Fields{"user_id": userID, "canvas_id": id})
|
|
|
|
userCanvases, ok := savedCanvases[userID]
|
|
if !ok {
|
|
log.Warn("User has no canvases")
|
|
return nil, fmt.Errorf("canvas with id %s not found for user %s", id, userID)
|
|
}
|
|
|
|
canvas, ok := userCanvases[id]
|
|
if !ok {
|
|
log.Warn("Canvas not found for user")
|
|
return nil, fmt.Errorf("canvas with id %s not found for user %s", id, userID)
|
|
}
|
|
|
|
log.Info("Canvas retrieved successfully")
|
|
return canvas, nil
|
|
}
|
|
|
|
// Save creates or updates a canvas for a user. Part of the CanvasStore interface.
|
|
func (s *memStore) Save(ctx context.Context, canvas *core.Canvas) error {
|
|
mu.Lock()
|
|
defer mu.Unlock()
|
|
|
|
log := logrus.WithFields(logrus.Fields{"user_id": canvas.UserID, "canvas_id": canvas.ID})
|
|
|
|
if canvas.UserID == "" {
|
|
return fmt.Errorf("UserID cannot be empty")
|
|
}
|
|
|
|
userCanvases, ok := savedCanvases[canvas.UserID]
|
|
if !ok {
|
|
userCanvases = make(map[string]*core.Canvas)
|
|
savedCanvases[canvas.UserID] = userCanvases
|
|
}
|
|
|
|
if canvas.ID == "" {
|
|
return fmt.Errorf("Canvas ID cannot be empty for save operation")
|
|
}
|
|
|
|
now := time.Now()
|
|
if existingCanvas, exists := userCanvases[canvas.ID]; exists {
|
|
canvas.CreatedAt = existingCanvas.CreatedAt
|
|
canvas.UpdatedAt = now
|
|
} else {
|
|
canvas.CreatedAt = now
|
|
canvas.UpdatedAt = now
|
|
}
|
|
|
|
userCanvases[canvas.ID] = canvas
|
|
log.Info("Canvas saved successfully")
|
|
return nil
|
|
}
|
|
|
|
// Delete removes a canvas, ensuring it belongs to the user. Part of the CanvasStore interface.
|
|
func (s *memStore) Delete(ctx context.Context, userID, id string) error {
|
|
mu.Lock()
|
|
defer mu.Unlock()
|
|
|
|
log := logrus.WithFields(logrus.Fields{"user_id": userID, "canvas_id": id})
|
|
|
|
userCanvases, ok := savedCanvases[userID]
|
|
if !ok {
|
|
log.Warn("User has no canvases to delete from")
|
|
return fmt.Errorf("user %s has no canvases", userID)
|
|
}
|
|
|
|
if _, ok := userCanvases[id]; !ok {
|
|
log.Warn("Canvas not found for deletion")
|
|
return fmt.Errorf("canvas with id %s not found for user %s", id, userID)
|
|
}
|
|
|
|
delete(userCanvases, id)
|
|
log.Info("Canvas deleted successfully")
|
|
return nil
|
|
}
|