feat: implement unified content engine to eliminate ID generation inconsistencies

- Create internal/engine module as single source of truth for content processing
- Consolidate 4 separate ID generation systems into one unified engine
- Update API handlers to use engine for consistent server-side ID generation
- Remove frontend client-side ID generation, delegate to server engine
- Ensure identical HTML markup + file path produces identical content IDs
- Resolve content persistence failures caused by ID fragmentation between manual editing and enhancement processes
This commit is contained in:
2025-09-16 15:04:27 +02:00
parent c1bc28d107
commit 84c90f428d
12 changed files with 1426 additions and 267 deletions

View File

@@ -0,0 +1,112 @@
package engine
import (
"context"
"fmt"
"github.com/insertr/insertr/internal/db"
"github.com/insertr/insertr/internal/db/postgresql"
"github.com/insertr/insertr/internal/db/sqlite"
)
// DatabaseClient implements ContentClient interface using the database
type DatabaseClient struct {
database *db.Database
}
// NewDatabaseClient creates a new database client
func NewDatabaseClient(database *db.Database) *DatabaseClient {
return &DatabaseClient{
database: database,
}
}
// GetContent retrieves a single content item
func (c *DatabaseClient) GetContent(siteID, contentID string) (*ContentItem, error) {
switch c.database.GetDBType() {
case "sqlite3":
content, err := c.database.GetSQLiteQueries().GetContent(context.Background(), sqlite.GetContentParams{
ID: contentID,
SiteID: siteID,
})
if err != nil {
return nil, err
}
return &ContentItem{
ID: content.ID,
SiteID: content.SiteID,
Value: content.Value,
Type: content.Type,
LastEditedBy: content.LastEditedBy,
}, nil
case "postgresql":
content, err := c.database.GetPostgreSQLQueries().GetContent(context.Background(), postgresql.GetContentParams{
ID: contentID,
SiteID: siteID,
})
if err != nil {
return nil, err
}
return &ContentItem{
ID: content.ID,
SiteID: content.SiteID,
Value: content.Value,
Type: content.Type,
LastEditedBy: content.LastEditedBy,
}, nil
default:
return nil, fmt.Errorf("unsupported database type: %s", c.database.GetDBType())
}
}
// GetBulkContent retrieves multiple content items
func (c *DatabaseClient) GetBulkContent(siteID string, contentIDs []string) ([]*ContentItem, error) {
switch c.database.GetDBType() {
case "sqlite3":
contents, err := c.database.GetSQLiteQueries().GetBulkContent(context.Background(), sqlite.GetBulkContentParams{
SiteID: siteID,
Ids: contentIDs,
})
if err != nil {
return nil, err
}
items := make([]*ContentItem, len(contents))
for i, content := range contents {
items[i] = &ContentItem{
ID: content.ID,
SiteID: content.SiteID,
Value: content.Value,
Type: content.Type,
LastEditedBy: content.LastEditedBy,
}
}
return items, nil
case "postgresql":
contents, err := c.database.GetPostgreSQLQueries().GetBulkContent(context.Background(), postgresql.GetBulkContentParams{
SiteID: siteID,
Ids: contentIDs,
})
if err != nil {
return nil, err
}
items := make([]*ContentItem, len(contents))
for i, content := range contents {
items[i] = &ContentItem{
ID: content.ID,
SiteID: content.SiteID,
Value: content.Value,
Type: content.Type,
LastEditedBy: content.LastEditedBy,
}
}
return items, nil
default:
return nil, fmt.Errorf("unsupported database type: %s", c.database.GetDBType())
}
}