package postgres import ( "context" "encoding/json" "fmt" "time" sq "github.com/Masterminds/squirrel" "github.com/google/uuid" "github.com/jmoiron/sqlx" "internal/datastore" "internal/entity" ) const ( queryCreateContent = `INSERT INTO content ( id, type, attributes ) VALUES ( :id, :type, :attributes )` queryUpdateContent = `UPDATE content SET type = :type, attributes = :attributes, updated_at = now() WHERE id = :id` queryDeleteContent = `DELETE FROM content where id = :id` ) var _ datastore.ContentRepo = (*ContentRepo)(nil) var psql = sq.StatementBuilder.PlaceholderFormat(sq.Dollar) type Content struct { ID uuid.UUID `json:"id" db:"id"` Type entity.ContentType `json:"type" db:"type"` Attributes []byte `json:"attributes" db:"attributes"` entity.MutableRecord } func NewContent(source entity.Content) (Content, error) { attributes, err := json.Marshal(source.Attributes) if err != nil { return Content{}, fmt.Errorf("failed to marshal attributes: %w", err) } return Content{ ID: source.ID, Type: source.Type, Attributes: attributes, MutableRecord: source.MutableRecord, }, nil } func (c Content) ToEntity() (entity.Content, error) { var attributes map[string]any if err := json.Unmarshal(c.Attributes, &attributes); err != nil { return entity.Content{}, fmt.Errorf("failed to unmarshal attributes: %w", err) } return entity.Content{ ID: c.ID, Type: c.Type, Attributes: attributes, MutableRecord: c.MutableRecord, }, nil } type ContentRepo struct { crud CRUD[entity.Content, Content, entity.ContentFilter] exec sqlx.ExtContext } func NewContentRepo(exec sqlx.ExtContext) ContentRepo { repo := ContentRepo{ exec: exec, } queries := CRUDQueries[entity.ContentFilter]{ createQuery: queryCreateContent, selectQueryFunc: repo.selectQuery, updateQuery: queryUpdateContent, deleteQuery: queryDeleteContent, } repo.crud = NewCRUD(exec, queries, NewContent) return repo } func (c ContentRepo) Create(ctx context.Context, in entity.Content) (entity.Content, error) { in.ID = uuid.New() var err error in, _, err = c.crud.Create(ctx, in) if err != nil { return in, err } return in, nil } func (c ContentRepo) selectQuery(filter entity.ContentFilter) sq.SelectBuilder { qb := psql. Select( "id", "type", "attributes", "created_at", "updated_at", ). From("content") if len(filter.IDs) > 0 { qb = qb.Where(sq.Eq{"id": filter.IDs}) } if len(filter.Types) > 0 { qb = qb.Where(sq.Eq{"type": filter.Types}) } return qb } func (c ContentRepo) All(ctx context.Context, filter entity.ContentFilter) ([]entity.Content, error) { return c.crud.All(ctx, filter) } func (c ContentRepo) One(ctx context.Context, filter entity.ContentFilter) (entity.Content, error) { return c.crud.One(ctx, filter) } func (c ContentRepo) Update(ctx context.Context, in entity.Content) (entity.Content, error) { var err error in, err = c.crud.Update(ctx, in) if err != nil { return in, err } in.UpdatedAt = time.Now().UTC() return in, nil } func (c ContentRepo) Delete(ctx context.Context, in entity.Content) error { return c.crud.Delete(ctx, in) }