This commit is contained in:
2025-03-17 21:01:15 +01:00
9 changed files with 333 additions and 240 deletions

View File

@ -2,6 +2,7 @@ package main
import (
"bytes"
"context"
"encoding/base64"
"encoding/json"
"fmt"
@ -10,11 +11,13 @@ import (
"net/http"
"os"
"path/filepath"
"screenmark/screenmark/.gen/haystack/haystack/model"
"screenmark/screenmark/models"
"time"
"github.com/go-chi/chi/v5"
"github.com/go-chi/chi/v5/middleware"
"github.com/google/uuid"
"github.com/joho/godotenv"
"github.com/lib/pq"
)
@ -51,11 +54,17 @@ func main() {
mode := os.Getenv("MODE")
log.Printf("Mode: %s\n", mode)
err = models.InitDatabase()
db, err := models.InitDatabase()
if err != nil {
panic(err)
}
imageModel := models.NewImageModel(db)
linkModel := models.NewLinkModel(db)
tagModel := models.NewTagModel(db)
textModel := models.NewTextModel(db)
userModel := models.NewUserModel(db)
listener := pq.NewListener(os.Getenv("DB_CONNECTION"), time.Second, time.Second, func(event pq.ListenerEventType, err error) {
if err != nil {
panic(err)
@ -73,9 +82,9 @@ func main() {
select {
case parameters := <-listener.Notify:
imageId := parameters.Extra
imageId := uuid.MustParse(parameters.Extra)
log.Println("received notification, new image available: " + imageId)
ctx := context.Background()
go func() {
openAiClient, err := GetAiClient()
@ -83,46 +92,34 @@ func main() {
panic(err)
}
image, err := models.GetImageToProcessWithData(imageId)
image, err := imageModel.GetToProcessWithData(ctx, imageId)
if err != nil {
log.Println("1")
log.Println(err)
return
}
imageInfo, err := openAiClient.GetImageInfo(image.Image.ImageName, image.Image.Image)
if err != nil {
log.Println("2")
log.Println(err)
return
}
savedImage, err := models.SaveImage(image.ID)
savedImage, err := imageModel.FinishProcessing(ctx, image.ID)
if err != nil {
log.Println("3")
log.Println(err)
return
}
log.Println("Finished processing image " + imageId)
log.Printf("Image attributes: %+v\n", imageInfo)
_, err = models.SaveImageTags(savedImage.ID.String(), imageInfo.Tags)
err = tagModel.SaveToImage(ctx, savedImage.ID, imageInfo.Tags)
if err != nil {
log.Println("1")
log.Println(err)
return
}
_, err = models.SaveImageLinks(savedImage.ID.String(), imageInfo.Links)
err = linkModel.Save(ctx, savedImage.ID, imageInfo.Links)
if err != nil {
log.Println("2")
log.Println(err)
return
}
_, err = models.SaveImageTexts(savedImage.ID.String(), imageInfo.Text)
err = textModel.Save(ctx, savedImage.ID, imageInfo.Text)
if err != nil {
log.Println("3")
log.Println(err)
return
}
}()
}
@ -153,7 +150,7 @@ func main() {
w.Header().Add("Access-Control-Allow-Credentials", "*")
w.Header().Add("Access-Control-Allow-Headers", "*")
images, err := models.GetUserImages(userId)
images, err := userModel.ListWithProperties(r.Context(), uuid.MustParse(userId))
if err != nil {
log.Println(err)
w.WriteHeader(http.StatusNotFound)
@ -180,7 +177,7 @@ func main() {
w.Header().Add("Access-Control-Allow-Headers", "*")
// TODO: really need authorization here!
image, err := models.GetImage(imageId)
image, err := imageModel.Get(r.Context(), uuid.MustParse(imageId))
if err != nil {
log.Println(err)
w.WriteHeader(http.StatusNotFound)
@ -259,7 +256,10 @@ func main() {
return
}
userImage, err := models.SaveImageToProcess(userId, imageName, image)
userImage, err := imageModel.Process(r.Context(), uuid.MustParse(userId), model.Image{
Image: image,
ImageName: imageName,
})
if err != nil {
log.Println("Second case")
log.Println(err)

View File

@ -8,18 +8,12 @@ import (
_ "github.com/lib/pq"
)
var db *sql.DB
func InitDatabase() error {
func InitDatabase() (*sql.DB, error) {
connection := os.Getenv("DB_CONNECTION")
if len(connection) == 0 {
return errors.New("DB_CONNECTION env was not found.")
return nil, errors.New("DB_CONNECTION env was not found.")
}
database, err := sql.Open("postgres", connection)
db = database
return err
return sql.Open("postgres", connection)
}

View File

@ -1,6 +1,8 @@
package models
import (
"context"
"database/sql"
"errors"
"fmt"
"screenmark/screenmark/.gen/haystack/haystack/model"
@ -11,76 +13,8 @@ import (
"github.com/google/uuid"
)
func SaveImageToProcess(userId string, imageName string, imageData []byte) (model.UserImagesToProcess, error) {
insertImageStmt := Image.INSERT(Image.ImageName, Image.Image).VALUES(imageName, imageData).RETURNING(Image.ID)
// TODO: should be a transaction
image := model.Image{}
err := insertImageStmt.Query(db, &image)
if err != nil {
return model.UserImagesToProcess{}, err
}
stmt := UserImagesToProcess.INSERT(UserImagesToProcess.UserID, UserImagesToProcess.ImageID).VALUES(userId, image.ID).RETURNING(UserImagesToProcess.AllColumns)
fmt.Println(stmt.DebugSql())
userImage := model.UserImagesToProcess{}
err = stmt.Query(db, &userImage)
return userImage, err
}
func removeImageToProcess(imageId string) error {
id := uuid.MustParse(imageId)
stmt := UserImagesToProcess.DELETE().WHERE(UserImagesToProcess.ID.EQ(UUID(id)))
_, err := stmt.Exec(db)
return err
}
func getUserId(imageId uuid.UUID) (uuid.UUID, error) {
stmt := UserImages.SELECT(UserImages.UserID).WHERE(UserImages.ID.EQ(UUID(imageId)))
fmt.Println(stmt.DebugSql())
userIds := make([]string, 0)
err := stmt.Query(db, &userIds)
if err != nil {
return uuid.Nil, err
}
if len(userIds) != 1 {
return uuid.Nil, errors.New("expect only one user id per image id")
}
return uuid.Parse(userIds[0])
}
func SaveImage(imageId uuid.UUID) (model.UserImages, error) {
imageToProcess, err := GetImageToProcess(imageId.String())
if err != nil {
return model.UserImages{}, err
}
stmt := UserImages.INSERT(UserImages.UserID, UserImages.ImageID).VALUES(imageToProcess.UserID, imageToProcess.ImageID).RETURNING(UserImages.ID, UserImages.UserID, UserImages.ImageID)
userImage := model.UserImages{}
err = stmt.Query(db, &userImage)
if err != nil {
return model.UserImages{}, err
}
err = removeImageToProcess(imageId.String())
if err != nil {
return model.UserImages{}, err
}
return userImage, err
type ImageModel struct {
dbPool *sql.DB
}
type ImageData struct {
@ -89,49 +23,52 @@ type ImageData struct {
Image model.Image
}
func GetImage(imageId string) (ImageData, error) {
id := uuid.MustParse(imageId)
stmt := SELECT(UserImages.AllColumns, Image.AllColumns).FROM(UserImages.INNER_JOIN(Image, Image.ID.EQ(UserImages.ImageID))).WHERE(UserImages.ID.EQ(UUID(id)))
images := []ImageData{}
err := stmt.Query(db, &images)
if len(images) != 1 {
return ImageData{}, errors.New(fmt.Sprintf("Expected 1, got %d\n", len(images)))
}
return images[0], err
}
type ImageToProcessData struct {
type ProcessingImageData struct {
model.UserImagesToProcess
Image model.Image
}
func GetImageToProcessWithData(imageId string) (ImageToProcessData, error) {
id := uuid.MustParse(imageId)
// stmt := UserImagesToProcess.SELECT(UserImages.AllColumns).WHERE(UserImages.ID.EQ(UUID(id)))
// TODO: Image should be `Images`
stmt := SELECT(UserImagesToProcess.AllColumns, Image.AllColumns).FROM(UserImagesToProcess.INNER_JOIN(Image, Image.ID.EQ(UserImagesToProcess.ImageID))).WHERE(UserImagesToProcess.ID.EQ(UUID(id)))
images := []ImageToProcessData{}
err := stmt.Query(db, &images)
if len(images) != 1 {
return ImageToProcessData{}, errors.New(fmt.Sprintf("Expected 1, got %d\n", len(images)))
func (m ImageModel) Process(ctx context.Context, userId uuid.UUID, image model.Image) (model.UserImagesToProcess, error) {
tx, err := m.dbPool.BeginTx(ctx, nil)
if err != nil {
return model.UserImagesToProcess{}, err
}
return images[0], err
insertImageStmt := Image.
INSERT(Image.ImageName, Image.Image).
VALUES(image.ImageName, image.Image).
RETURNING(Image.ID)
insertedImage := model.Image{}
err = insertImageStmt.QueryContext(ctx, tx, &insertedImage)
if err != nil {
return model.UserImagesToProcess{}, err
}
func GetImageToProcess(imageId string) (model.UserImagesToProcess, error) {
id := uuid.MustParse(imageId)
stmt := UserImagesToProcess.SELECT(UserImagesToProcess.AllColumns).WHERE(UserImagesToProcess.ID.EQ(UUID(id)))
stmt := UserImagesToProcess.
INSERT(UserImagesToProcess.UserID, UserImagesToProcess.ImageID).
VALUES(userId, insertedImage.ID).
RETURNING(UserImagesToProcess.AllColumns)
userImage := model.UserImagesToProcess{}
err = stmt.QueryContext(ctx, tx, &userImage)
if err != nil {
return model.UserImagesToProcess{}, err
}
err = tx.Commit()
return userImage, err
}
func (m ImageModel) GetToProcess(ctx context.Context, imageId uuid.UUID) (model.UserImagesToProcess, error) {
getToProcessStmt := UserImagesToProcess.
SELECT(UserImagesToProcess.AllColumns).
WHERE(UserImagesToProcess.ID.EQ(UUID(imageId)))
images := []model.UserImagesToProcess{}
err := stmt.Query(db, &images)
err := getToProcessStmt.QueryContext(ctx, m.dbPool, &images)
if len(images) != 1 {
return model.UserImagesToProcess{}, errors.New(fmt.Sprintf("Expected 1, got %d\n", len(images)))
@ -140,97 +77,72 @@ func GetImageToProcess(imageId string) (model.UserImagesToProcess, error) {
return images[0], err
}
type UserImagesWithInfo struct {
ID uuid.UUID
func (m ImageModel) GetToProcessWithData(ctx context.Context, imageId uuid.UUID) (ProcessingImageData, error) {
stmt := SELECT(UserImagesToProcess.AllColumns, Image.AllColumns).
FROM(
UserImagesToProcess.INNER_JOIN(
Image, Image.ID.EQ(UserImagesToProcess.ImageID),
),
).WHERE(UserImagesToProcess.ID.EQ(UUID(imageId)))
// TODO: this shit
Image model.Image
images := []ProcessingImageData{}
err := stmt.QueryContext(ctx, m.dbPool, &images)
Tags []model.ImageTags
Links []model.ImageLinks
Text []model.ImageText
if len(images) != 1 {
return ProcessingImageData{}, errors.New(fmt.Sprintf("Expected 1, got %d\n", len(images)))
}
func GetUserImages(userId string) ([]UserImagesWithInfo, error) {
id := uuid.MustParse(userId)
stmt := SELECT(UserImages.ID.AS("UserImagesWithInfo.ID"), Image.ID, Image.ImageName, ImageTags.AllColumns, ImageText.AllColumns, ImageLinks.AllColumns).FROM(UserImages.INNER_JOIN(Image, Image.ID.EQ(UserImages.ImageID)).LEFT_JOIN(ImageTags, ImageTags.ImageID.EQ(UserImages.ID)).LEFT_JOIN(ImageText, ImageText.ImageID.EQ(UserImages.ID)).LEFT_JOIN(ImageLinks, ImageLinks.ImageID.EQ(UserImages.ID))).WHERE(UserImages.UserID.EQ(UUID(id)))
images := []UserImagesWithInfo{}
err := stmt.Query(db, &images)
return images, err
return images[0], err
}
func SaveImageTags(imageId string, tags []string) ([]model.ImageTags, error) {
id := uuid.MustParse(imageId)
userId, err := getUserId(id)
func (m ImageModel) FinishProcessing(ctx context.Context, imageId uuid.UUID) (model.UserImages, error) {
imageToProcess, err := m.GetToProcess(ctx, imageId)
if err != nil {
return []model.ImageTags{}, err
return model.UserImages{}, err
}
err = CreateTags(userId, tags)
tx, err := m.dbPool.Begin()
if err != nil {
return []model.ImageTags{}, err
return model.UserImages{}, err
}
userTagsExpression := make([]Expression, 0)
for _, tag := range tags {
userTagsExpression = append(userTagsExpression, String(tag))
}
insertImageStmt := UserImages.
INSERT(UserImages.UserID, UserImages.ImageID).
VALUES(imageToProcess.UserID, imageToProcess.ImageID).
RETURNING(UserImages.ID, UserImages.UserID, UserImages.ImageID)
userTags := make([]model.UserTags, 0)
getTagsStmt := UserTags.SELECT(UserTags.ID, UserTags.Tag).WHERE(UserTags.Tag.IN(userTagsExpression...))
err = getTagsStmt.Query(db, &userTags)
userImage := model.UserImages{}
err = insertImageStmt.QueryContext(ctx, tx, &userImage)
if err != nil {
return []model.ImageTags{}, err
return model.UserImages{}, err
}
stmt := ImageTags.INSERT(ImageTags.ImageID, ImageTags.TagID)
removeProcessingStmt := UserImagesToProcess.
DELETE().
WHERE(UserImagesToProcess.ID.EQ(UUID(imageToProcess.ID)))
for _, t := range userTags {
stmt = stmt.VALUES(id, t.ID)
_, err = removeProcessingStmt.ExecContext(ctx, tx)
return userImage, err
}
stmt.RETURNING(ImageTags.AllColumns)
func (m ImageModel) Get(ctx context.Context, imageId uuid.UUID) (ImageData, error) {
getImageStmt := SELECT(UserImages.AllColumns, Image.AllColumns).
FROM(
UserImages.INNER_JOIN(Image, Image.ID.EQ(UserImages.ImageID)),
).
WHERE(UserImages.ID.EQ(UUID(imageId)))
imageTags := make([]model.ImageTags, 0)
err = stmt.Query(db, &imageTags)
images := []ImageData{}
err := getImageStmt.QueryContext(ctx, m.dbPool, &images)
return imageTags, err
if len(images) != 1 {
return ImageData{}, errors.New(fmt.Sprintf("Expected 1, got %d\n", len(images)))
}
func SaveImageLinks(imageId string, links []string) ([]model.ImageLinks, error) {
id := uuid.MustParse(imageId)
stmt := ImageLinks.INSERT(ImageLinks.ImageID, ImageLinks.Link)
for _, t := range links {
stmt = stmt.VALUES(id, t)
return images[0], err
}
stmt.RETURNING(ImageLinks.AllColumns)
imageLinks := []model.ImageLinks{}
err := stmt.Query(db, &imageLinks)
return imageLinks, err
}
func SaveImageTexts(imageId string, texts []string) ([]model.ImageText, error) {
id := uuid.MustParse(imageId)
stmt := ImageText.INSERT(ImageText.ImageID, ImageText.ImageText)
for _, t := range texts {
stmt = stmt.VALUES(id, t)
}
stmt.RETURNING(ImageText.AllColumns)
imageTags := []model.ImageText{}
err := stmt.Query(db, &imageTags)
return imageTags, err
func NewImageModel(db *sql.DB) ImageModel {
return ImageModel{dbPool: db}
}

29
backend/models/links.go Normal file
View File

@ -0,0 +1,29 @@
package models
import (
"context"
"database/sql"
. "screenmark/screenmark/.gen/haystack/haystack/table"
"github.com/google/uuid"
)
type LinkModel struct {
dbPool *sql.DB
}
func (m LinkModel) Save(ctx context.Context, imageId uuid.UUID, links []string) error {
stmt := ImageLinks.INSERT(ImageLinks.ImageID, ImageLinks.Link)
for _, link := range links {
stmt = stmt.VALUES(imageId, link)
}
_, err := stmt.ExecContext(ctx, m.dbPool)
return err
}
func NewLinkModel(db *sql.DB) LinkModel {
return LinkModel{dbPool: db}
}

View File

@ -1,6 +1,8 @@
package models
import (
"context"
"database/sql"
"fmt"
"screenmark/screenmark/.gen/haystack/haystack/model"
. "screenmark/screenmark/.gen/haystack/haystack/table"
@ -9,6 +11,10 @@ import (
"github.com/google/uuid"
)
type TagModel struct {
dbPool *sql.DB
}
// Raw dogging SQL is kinda based though?
//
// | nO, usE OrM!!
@ -20,7 +26,7 @@ import (
// | -- --
// | -- --
// | ---- IQ ----
func getNonExistantTags(userId uuid.UUID, tags []string) ([]string, error) {
func (m TagModel) getNonExistantTags(ctx context.Context, userId uuid.UUID, tags []string) ([]string, error) {
values := ""
counter := 1
// big big SQL injection problem here?
@ -29,20 +35,7 @@ func getNonExistantTags(userId uuid.UUID, tags []string) ([]string, error) {
}
values = values[0 : len(values)-1]
/*
WITH given_tags
AS (SELECT given_tags.tag FROM (VALUES ('c')) AS given_tags (tag)),
this_user_tags as (
SELECT id, tag
FROM haystack.user_tags
where user_tags.user_id = 'fcc22dbb-7792-4595-be8e-d0439e13990a'
)
select given_tags.tag from given_tags
LEFT OUTER JOIN this_user_tags ON this_user_tags.tag = given_tags.tag
where this_user_tags.tag is null;
*/
withStuff := fmt.Sprintf(`WITH given_tags
getNonExistingTags := fmt.Sprintf(`WITH given_tags
AS (SELECT given_tags.tag FROM (VALUES `+values+`) AS given_tags (tag)),
this_user_tags AS
(SELECT id, tag FROM haystack.user_tags WHERE user_tags.user_id = $%d)
@ -51,23 +44,20 @@ func getNonExistantTags(userId uuid.UUID, tags []string) ([]string, error) {
LEFT OUTER JOIN haystack.user_tags ON haystack.user_tags.tag = given_tags.tag
where user_tags.tag is null`, counter)
stmt, err := db.Prepare(withStuff)
fmt.Println(withStuff)
getNonExistingTagsStmt, err := m.dbPool.PrepareContext(ctx, getNonExistingTags)
defer getNonExistingTagsStmt.Close()
if err != nil {
fmt.Println("failing to prepare stmt")
return []string{}, err
}
defer stmt.Close()
args := make([]any, counter)
for i, v := range tags {
args[i] = v
}
args[counter-1] = userId.String()
rows, err := stmt.Query(args...)
rows, err := getNonExistingTagsStmt.QueryContext(ctx, args...)
if err != nil {
return []string{}, err
}
@ -84,8 +74,8 @@ func getNonExistantTags(userId uuid.UUID, tags []string) ([]string, error) {
return nonExistantTags, nil
}
func CreateTags(userId uuid.UUID, tags []string) error {
tagsToInsert, err := getNonExistantTags(userId, tags)
func (m TagModel) Save(ctx context.Context, userId uuid.UUID, tags []string) error {
tagsToInsert, err := m.getNonExistantTags(ctx, userId, tags)
if err != nil {
return err
}
@ -100,17 +90,58 @@ func CreateTags(userId uuid.UUID, tags []string) error {
stmt = stmt.VALUES(UUID(userId), tag)
}
_, err = stmt.Exec(db)
_, err = stmt.ExecContext(ctx, m.dbPool)
return err
}
func GetTags(userId uuid.UUID) ([]model.UserTags, error) {
stmt := UserTags.SELECT(UserTags.AllColumns).WHERE(UserTags.UserID.EQ(UUID(userId)))
func (m TagModel) List(ctx context.Context, userId uuid.UUID) ([]model.UserTags, error) {
listTagsStmt := UserTags.SELECT(UserTags.AllColumns).WHERE(UserTags.UserID.EQ(UUID(userId)))
userTags := []model.UserTags{}
err := stmt.Query(db, &userTags)
err := listTagsStmt.QueryContext(ctx, m.dbPool, &userTags)
return userTags, err
}
func (m TagModel) SaveToImage(ctx context.Context, imageId uuid.UUID, tags []string) error {
userId, err := getUserIdFromImage(ctx, m.dbPool, imageId)
if err != nil {
return err
}
err = m.Save(ctx, userId, tags)
if err != nil {
return err
}
userTagsExpression := make([]Expression, 0)
for _, tag := range tags {
userTagsExpression = append(userTagsExpression, String(tag))
}
userTags := make([]model.UserTags, 0)
getTagsStmt := UserTags.SELECT(
UserTags.ID, UserTags.Tag,
).WHERE(UserTags.Tag.IN(userTagsExpression...))
err = getTagsStmt.Query(m.dbPool, &userTags)
if err != nil {
return err
}
stmt := ImageTags.INSERT(ImageTags.ImageID, ImageTags.TagID)
for _, t := range userTags {
stmt = stmt.VALUES(imageId, t.ID)
}
_, err = stmt.ExecContext(ctx, m.dbPool)
return err
}
func NewTagModel(db *sql.DB) TagModel {
return TagModel{dbPool: db}
}

31
backend/models/text.go Normal file
View File

@ -0,0 +1,31 @@
package models
import (
"context"
"database/sql"
. "screenmark/screenmark/.gen/haystack/haystack/table"
"github.com/google/uuid"
)
type TextModel struct {
dbPool *sql.DB
}
func (m TextModel) Save(ctx context.Context, imageId uuid.UUID, texts []string) error {
saveImageTextStmt := ImageText.INSERT(ImageText.ImageID, ImageText.ImageText)
for _, t := range texts {
saveImageTextStmt = saveImageTextStmt.VALUES(imageId, t)
}
saveImageTextStmt.RETURNING(ImageText.AllColumns)
_, err := saveImageTextStmt.ExecContext(ctx, m.dbPool)
return err
}
func NewTextModel(db *sql.DB) TextModel {
return TextModel{dbPool: db}
}

75
backend/models/user.go Normal file
View File

@ -0,0 +1,75 @@
package models
import (
"context"
"database/sql"
"errors"
"fmt"
"screenmark/screenmark/.gen/haystack/haystack/model"
. "screenmark/screenmark/.gen/haystack/haystack/table"
. "github.com/go-jet/jet/v2/postgres"
"github.com/google/uuid"
)
type UserModel struct {
dbPool *sql.DB
}
type ImageWithProperties struct {
ID uuid.UUID
Image model.Image
Tags []struct {
model.ImageTags
Tag model.UserTags
}
Links []model.ImageLinks
Text []model.ImageText
}
func getUserIdFromImage(ctx context.Context, dbPool *sql.DB, imageId uuid.UUID) (uuid.UUID, error) {
getUserIdStmt := UserImages.SELECT(UserImages.UserID).WHERE(UserImages.ID.EQ(UUID(imageId)))
user := []model.Users{}
err := getUserIdStmt.QueryContext(ctx, dbPool, &user)
if err != nil {
return uuid.Nil, err
}
if len(user) != 1 {
return uuid.Nil, errors.New("Expected exactly one choice.")
}
return user[0].ID, nil
}
func (m UserModel) ListWithProperties(ctx context.Context, userId uuid.UUID) ([]ImageWithProperties, error) {
listWithPropertiesStmt := SELECT(
UserImages.ID.AS("ImageWithProperties.ID"),
Image.ID,
Image.ImageName,
ImageTags.AllColumns,
UserTags.AllColumns,
ImageText.AllColumns,
ImageLinks.AllColumns).
FROM(
UserImages.INNER_JOIN(Image, Image.ID.EQ(UserImages.ImageID)).
LEFT_JOIN(ImageTags, ImageTags.ImageID.EQ(UserImages.ID)).
INNER_JOIN(UserTags, UserTags.ID.EQ(ImageTags.TagID)).
LEFT_JOIN(ImageText, ImageText.ImageID.EQ(UserImages.ID)).
LEFT_JOIN(ImageLinks, ImageLinks.ImageID.EQ(UserImages.ID))).
WHERE(UserImages.UserID.EQ(UUID(userId)))
fmt.Println(listWithPropertiesStmt.DebugSql())
images := []ImageWithProperties{}
err := listWithPropertiesStmt.QueryContext(ctx, m.dbPool, &images)
return images, err
}
func NewUserModel(db *sql.DB) UserModel {
return UserModel{dbPool: db}
}

View File

@ -82,8 +82,8 @@ func TestMessageBuilderImage(t *testing.T) {
base64data := base64.StdEncoding.EncodeToString(data)
url := fmt.Sprintf("data:image/%s;base64,%s", "png", base64data)
if imageContent.ImageUrl.Url != url {
t.Logf("Expected %s, but got %s.\n", url, imageContent.ImageUrl.Url)
if imageContent.ImageUrl != url {
t.Logf("Expected %s, but got %s.\n", url, imageContent.ImageUrl)
t.FailNow()
}
}

View File

@ -1,8 +1,7 @@
import {
type InferOutput,
null as Null,
any,
array,
InferOutput,
null as Null,
nullable,
object,
parse,
@ -56,8 +55,30 @@ const getUserImagesResponseValidator = array(
ImageName: string(),
Image: Null(),
}),
Tags: any(),
Links: any(),
Tags: nullable(
array(
object({
ID: pipe(string(), uuid()),
TagID: pipe(string(), uuid()),
ImageID: pipe(string(), uuid()),
Tag: object({
ID: pipe(string(), uuid()),
Tag: string(),
UserID: pipe(string(), uuid()),
}),
}),
),
),
Links: nullable(
array(
object({
ID: pipe(string(), uuid()),
Links: string(),
ImageID: pipe(string(), uuid()),
}),
),
),
Text: nullable(
array(
object({