diff --git a/pkg/database/actor/actor.go b/pkg/database/actor/actor.go index f838b0e..09d2640 100644 --- a/pkg/database/actor/actor.go +++ b/pkg/database/actor/actor.go @@ -13,7 +13,6 @@ import ( v1 "git.tipsy.codes/charles/webstory/pkg/api/webstory/v1" "github.com/google/uuid" - "github.com/lib/pq" "google.golang.org/protobuf/types/known/timestamppb" ) @@ -92,14 +91,14 @@ func (t *ActorTranslator) ToAPI(dbActor *DBActor) *v1.Actor { } return &v1.Actor{ - Name: name, - ActorId: dbActor.ActorID, - NameValue: dbActor.NameValue, - Role: dbActor.Role, - Notes: dbActor.Notes, - CreateTime: timestamppb.New(dbActor.CreatedAt), - UpdateTime: timestamppb.New(dbActor.UpdatedAt), - Etag: dbActor.Etag, + Name: name, + ActorId: dbActor.ActorID, + NameValue: dbActor.NameValue, + Role: dbActor.Role, + Notes: dbActor.Notes, + CreateTime: timestamppb.New(dbActor.CreatedAt), + UpdateTime: timestamppb.New(dbActor.UpdatedAt), + Etag: dbActor.Etag, } } @@ -180,8 +179,8 @@ func (a *DBActor) insert(db *sql.DB) error { a.StoryID, a.ActorID, a.NameValue, - pq.Array([]string{a.Role}), - pq.Array([]string{a.Notes}), + a.Role, + a.Notes, a.CreatedAt, a.UpdatedAt, etag, @@ -396,11 +395,12 @@ func GetActorIDForResourceName(resourceName string) (int64, string, error) { remaining := strings.TrimPrefix(resourceName, prefix) actorPrefix := "actors/" - if !strings.HasPrefix(remaining, actorPrefix) { + idx := strings.Index(remaining, actorPrefix) + if idx == -1 { return 0, "", fmt.Errorf("invalid actor resource name: must contain '%s', got '%s'", actorPrefix, remaining) } - actorID := strings.TrimPrefix(remaining, actorPrefix) + actorID := remaining[idx+len(actorPrefix):] if actorID == "" { return 0, "", errors.New("actor_id is empty in resource name") } diff --git a/pkg/database/actor/actor_test.go b/pkg/database/actor/actor_test.go index 7a7bde9..abcba5b 100644 --- a/pkg/database/actor/actor_test.go +++ b/pkg/database/actor/actor_test.go @@ -11,6 +11,8 @@ import ( v1 "git.tipsy.codes/charles/webstory/pkg/api/webstory/v1" "git.tipsy.codes/charles/webstory/pkg/database/schema" + + story "git.tipsy.codes/charles/webstory/pkg/database/story" embeddedpostgres "github.com/fergusstrange/embedded-postgres" "google.golang.org/protobuf/types/known/timestamppb" ) @@ -105,8 +107,8 @@ func TestActorTranslator_ToAPI(t *testing.T) { checkFunc func(*v1.Actor) error }{ { - name: "nil dbActor returns nil", - dbActor: nil, + name: "nil dbActor returns nil", + dbActor: nil, checkFunc: func(a *v1.Actor) error { if a != nil { return fmt.Errorf("expected nil, got %v", a) @@ -222,9 +224,19 @@ func TestDBActor_Save(t *testing.T) { t.Fatalf("failed to run schema: %v", err) } + // Create a story first + story := &story.DBStory{ + StoryID: "save-test-story-1", + Title: "Save Test Story", + } + err = story.Save(db) + if err != nil { + t.Fatalf("failed to save story: %v", err) + } + actor := &DBActor{ - StoryID: 1, - ActorID: "save-test1", + StoryID: story.ID, + ActorID: "save-test-1", NameValue: "Save Test", Role: "Test Role", Notes: "Test notes", @@ -291,9 +303,19 @@ func TestDBActor_GetByID(t *testing.T) { t.Fatalf("failed to run schema: %v", err) } + // Create a story first + story := &story.DBStory{ + StoryID: "get-by-id-story-1", + Title: "Get By ID Story", + } + err = story.Save(db) + if err != nil { + t.Fatalf("failed to save story: %v", err) + } + actor := &DBActor{ - StoryID: 1, - ActorID: "get-by-id-test", + StoryID: story.ID, + ActorID: "get-by-id-test-1", NameValue: "Get Test", } err = actor.Save(db) @@ -340,9 +362,19 @@ func TestDBActor_GetByActorID(t *testing.T) { t.Fatalf("failed to run schema: %v", err) } + // Create a story first + story := &story.DBStory{ + StoryID: "get-by-actor-id-story-1", + Title: "Get By Actor ID Story", + } + err = story.Save(db) + if err != nil { + t.Fatalf("failed to save story: %v", err) + } + actor := &DBActor{ - StoryID: 1, - ActorID: "get-by-actor-id-test", + StoryID: story.ID, + ActorID: "get-by-actor-id-test-1", NameValue: "Get By Actor ID Test", } err = actor.Save(db) @@ -386,14 +418,24 @@ func TestDBActor_ListByStoryID(t *testing.T) { t.Fatalf("failed to run schema: %v", err) } + // Create a story first + story := &story.DBStory{ + StoryID: "list-test-story-1", + Title: "List Test Story", + } + err = story.Save(db) + if err != nil { + t.Fatalf("failed to save story: %v", err) + } + actor1 := &DBActor{ - StoryID: 1, + StoryID: story.ID, ActorID: "list-test-1", NameValue: "Actor One", Role: "Role One", } actor2 := &DBActor{ - StoryID: 1, + StoryID: story.ID, ActorID: "list-test-2", NameValue: "Actor Two", Role: "Role Two", @@ -448,9 +490,19 @@ func TestDBActor_Delete(t *testing.T) { t.Fatalf("failed to run schema: %v", err) } + // Create a story first + story := &story.DBStory{ + StoryID: "delete-test-story-1", + Title: "Delete Test Story", + } + err = story.Save(db) + if err != nil { + t.Fatalf("failed to save story: %v", err) + } + actor := &DBActor{ - StoryID: 1, - ActorID: "delete-test-1", + StoryID: story.ID, + ActorID: "delete-test-actor-1", NameValue: "Delete Test", } err = actor.Save(db) @@ -492,74 +544,74 @@ func TestDBActor_GetActorIDForResourceName(t *testing.T) { expectErr bool }{ { - name: "valid story/actor resource name", - input: "stories/1/actors/my-actor", - storyID: 1, - actorID: "my-actor", - expectErr: false, + name: "valid story/actor resource name", + input: "stories/1/actors/my-actor", + storyID: 1, + actorID: "my-actor", + expectErr: false, }, { - name: "valid story/actor resource name with numbers", - input: "stories/100/actors/actor-123", - storyID: 100, - actorID: "actor-123", - expectErr: false, + name: "valid story/actor resource name with numbers", + input: "stories/100/actors/actor-123", + storyID: 100, + actorID: "actor-123", + expectErr: false, }, { - name: "missing actors/ prefix", - input: "stories/1/actor/my-actor", - storyID: 1, - actorID: "", - expectErr: true, + name: "missing actors/ prefix", + input: "stories/1/actor/my-actor", + storyID: 1, + actorID: "", + expectErr: true, }, { - name: "actor_id with underscore not allowed", - input: "stories/1/actors/my_actor", - storyID: 1, - actorID: "", - expectErr: true, + name: "actor_id with underscore not allowed", + input: "stories/1/actors/my_actor", + storyID: 1, + actorID: "", + expectErr: true, }, { - name: "actor_id with spaces not allowed", - input: "stories/1/actors/my actor", - storyID: 1, - actorID: "", - expectErr: true, + name: "actor_id with spaces not allowed", + input: "stories/1/actors/my actor", + storyID: 1, + actorID: "", + expectErr: true, }, { - name: "actor_id with uppercase not allowed", - input: "stories/1/actors/MyActor", - storyID: 1, - actorID: "", - expectErr: true, + name: "actor_id with uppercase not allowed", + input: "stories/1/actors/MyActor", + storyID: 1, + actorID: "", + expectErr: true, }, { - name: "actor_id with dots not allowed", - input: "stories/1/actors/my.actor", - storyID: 1, - actorID: "", - expectErr: true, + name: "actor_id with dots not allowed", + input: "stories/1/actors/my.actor", + storyID: 1, + actorID: "", + expectErr: true, }, { - name: "missing stories/ prefix", - input: "actors/my-actor", - storyID: 1, - actorID: "", - expectErr: true, + name: "missing stories/ prefix", + input: "actors/my-actor", + storyID: 1, + actorID: "", + expectErr: true, }, { - name: "empty actor_id", - input: "stories/1/actors/", - storyID: 1, - actorID: "", - expectErr: true, + name: "empty actor_id", + input: "stories/1/actors/", + storyID: 1, + actorID: "", + expectErr: true, }, { - name: "wrong stories/ prefix", - input: "story/1/actors/my-actor", - storyID: 1, - actorID: "", - expectErr: true, + name: "wrong stories/ prefix", + input: "story/1/actors/my-actor", + storyID: 1, + actorID: "", + expectErr: true, }, } diff --git a/pkg/database/schema/schema.sql b/pkg/database/schema/schema.sql index 4370778..39cbffd 100644 --- a/pkg/database/schema/schema.sql +++ b/pkg/database/schema/schema.sql @@ -115,7 +115,7 @@ CREATE TABLE IF NOT EXISTS actors ( -- Constraints CONSTRAINT actors_story_actor_id_unique UNIQUE (story_id, actor_id), CONSTRAINT actors_story_id_exists CHECK (story_id > 0), - CONSTRAINT actors_actor_id_pattern CHECK (actor_id ~ '^[a-z][0-9-]{2,61}[0-9]$'), + CONSTRAINT actors_actor_id_pattern CHECK (actor_id ~ '^[a-z][0-9a-z-]{2,61}[0-9a-z]$'), CONSTRAINT actors_etag_pattern CHECK (char_length(etag) > 0) );