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.
159 lines
4.5 KiB
Go
159 lines
4.5 KiB
Go
package sqlite
|
|
|
|
import (
|
|
"bytes"
|
|
"context"
|
|
"database/sql"
|
|
"excalidraw-complete/core"
|
|
"fmt"
|
|
"log"
|
|
"time"
|
|
|
|
"github.com/oklog/ulid/v2"
|
|
"github.com/sirupsen/logrus"
|
|
_ "modernc.org/sqlite"
|
|
)
|
|
|
|
type sqliteStore struct {
|
|
db *sql.DB
|
|
}
|
|
|
|
// NewStore creates a new SQLite-based store.
|
|
func NewStore(dataSourceName string) *sqliteStore {
|
|
db, err := sql.Open("sqlite", dataSourceName)
|
|
if err != nil {
|
|
log.Fatalf("failed to open sqlite database: %v", err)
|
|
}
|
|
|
|
// Initialize table for anonymous documents
|
|
docTableStmt := `CREATE TABLE IF NOT EXISTS documents (id TEXT PRIMARY KEY, data BLOB);`
|
|
if _, err = db.Exec(docTableStmt); err != nil {
|
|
log.Fatalf("failed to create documents table: %v", err)
|
|
}
|
|
|
|
// Initialize table for user-owned canvases
|
|
canvasTableStmt := `
|
|
CREATE TABLE IF NOT EXISTS canvases (
|
|
id TEXT NOT NULL,
|
|
user_id TEXT NOT NULL,
|
|
name TEXT,
|
|
thumbnail TEXT,
|
|
data BLOB,
|
|
created_at DATETIME,
|
|
updated_at DATETIME,
|
|
PRIMARY KEY (user_id, id)
|
|
);`
|
|
if _, err = db.Exec(canvasTableStmt); err != nil {
|
|
log.Fatalf("failed to create canvases table: %v", err)
|
|
}
|
|
|
|
return &sqliteStore{db}
|
|
}
|
|
|
|
// DocumentStore implementation
|
|
func (s *sqliteStore) FindID(ctx context.Context, id string) (*core.Document, error) {
|
|
log := logrus.WithField("document_id", id)
|
|
log.Debug("Retrieving document by ID")
|
|
var data []byte
|
|
err := s.db.QueryRowContext(ctx, "SELECT data FROM documents WHERE id = ?", id).Scan(&data)
|
|
if err != nil {
|
|
if err == sql.ErrNoRows {
|
|
log.WithField("error", "document not found").Warn("Document with specified ID not found")
|
|
return nil, fmt.Errorf("document with id %s not found", id)
|
|
}
|
|
log.WithError(err).Error("Failed to retrieve document")
|
|
return nil, err
|
|
}
|
|
document := core.Document{
|
|
Data: *bytes.NewBuffer(data),
|
|
}
|
|
log.Info("Document retrieved successfully")
|
|
return &document, nil
|
|
}
|
|
|
|
func (s *sqliteStore) Create(ctx context.Context, document *core.Document) (string, error) {
|
|
id := ulid.Make().String()
|
|
data := document.Data.Bytes()
|
|
log := logrus.WithFields(logrus.Fields{
|
|
"document_id": id,
|
|
"data_length": len(data),
|
|
})
|
|
|
|
_, err := s.db.ExecContext(ctx, "INSERT INTO documents (id, data) VALUES (?, ?)", id, data)
|
|
if err != nil {
|
|
log.WithError(err).Error("Failed to create document")
|
|
return "", err
|
|
}
|
|
log.Info("Document created successfully")
|
|
return id, nil
|
|
}
|
|
|
|
// CanvasStore implementation
|
|
func (s *sqliteStore) List(ctx context.Context, userID string) ([]*core.Canvas, error) {
|
|
rows, err := s.db.QueryContext(ctx, "SELECT id, name, updated_at, thumbnail FROM canvases WHERE user_id = ?", userID)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
defer rows.Close()
|
|
|
|
var canvases []*core.Canvas
|
|
for rows.Next() {
|
|
var canvas core.Canvas
|
|
canvas.UserID = userID
|
|
if err := rows.Scan(&canvas.ID, &canvas.Name, &canvas.UpdatedAt, &canvas.Thumbnail); err != nil {
|
|
return nil, err
|
|
}
|
|
canvases = append(canvases, &canvas)
|
|
}
|
|
return canvases, nil
|
|
}
|
|
|
|
func (s *sqliteStore) Get(ctx context.Context, userID, id string) (*core.Canvas, error) {
|
|
var canvas core.Canvas
|
|
canvas.UserID = userID
|
|
canvas.ID = id
|
|
err := s.db.QueryRowContext(ctx, "SELECT name, data, created_at, updated_at, thumbnail FROM canvases WHERE user_id = ? AND id = ?", userID, id).Scan(&canvas.Name, &canvas.Data, &canvas.CreatedAt, &canvas.UpdatedAt, &canvas.Thumbnail)
|
|
if err != nil {
|
|
if err == sql.ErrNoRows {
|
|
return nil, fmt.Errorf("canvas not found")
|
|
}
|
|
return nil, err
|
|
}
|
|
return &canvas, nil
|
|
}
|
|
|
|
func (s *sqliteStore) Save(ctx context.Context, canvas *core.Canvas) error {
|
|
tx, err := s.db.BeginTx(ctx, nil)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
defer tx.Rollback() // Rollback on any error
|
|
|
|
var exists bool
|
|
err = tx.QueryRowContext(ctx, "SELECT 1 FROM canvases WHERE user_id = ? AND id = ?", canvas.UserID, canvas.ID).Scan(&exists)
|
|
|
|
now := time.Now()
|
|
if err != nil && err != sql.ErrNoRows {
|
|
return err
|
|
}
|
|
|
|
if exists {
|
|
// Update
|
|
_, err = tx.ExecContext(ctx, "UPDATE canvases SET name = ?, data = ?, updated_at = ?, thumbnail = ? WHERE user_id = ? AND id = ?", canvas.Name, canvas.Data, now, canvas.Thumbnail, canvas.UserID, canvas.ID)
|
|
} else {
|
|
// Insert
|
|
_, err = tx.ExecContext(ctx, "INSERT INTO canvases (id, user_id, name, data, created_at, updated_at, thumbnail) VALUES (?, ?, ?, ?, ?, ?, ?)", canvas.ID, canvas.UserID, canvas.Name, canvas.Data, now, now, canvas.Thumbnail)
|
|
}
|
|
|
|
if err != nil {
|
|
return err
|
|
}
|
|
|
|
return tx.Commit()
|
|
}
|
|
|
|
func (s *sqliteStore) Delete(ctx context.Context, userID, id string) error {
|
|
_, err := s.db.ExecContext(ctx, "DELETE FROM canvases WHERE user_id = ? AND id = ?", userID, id)
|
|
return err
|
|
}
|