diff --git a/backend/.gen/haystack/haystack/model/image.go b/backend/.gen/haystack/haystack/model/image.go index c99a329..b0df734 100644 --- a/backend/.gen/haystack/haystack/model/image.go +++ b/backend/.gen/haystack/haystack/model/image.go @@ -9,11 +9,15 @@ package model import ( "github.com/google/uuid" + "time" ) type Image struct { ID uuid.UUID `sql:"primary_key"` + UserID *uuid.UUID ImageName string Description string + Status Progress Image []byte + CreatedAt *time.Time } diff --git a/backend/.gen/haystack/haystack/model/image_lists.go b/backend/.gen/haystack/haystack/model/image_stacks.go similarity index 85% rename from backend/.gen/haystack/haystack/model/image_lists.go rename to backend/.gen/haystack/haystack/model/image_stacks.go index 955858f..69046fc 100644 --- a/backend/.gen/haystack/haystack/model/image_lists.go +++ b/backend/.gen/haystack/haystack/model/image_stacks.go @@ -11,8 +11,8 @@ import ( "github.com/google/uuid" ) -type ImageLists struct { +type ImageStacks struct { ID uuid.UUID `sql:"primary_key"` ImageID uuid.UUID - ListID uuid.UUID + StackID uuid.UUID } diff --git a/backend/.gen/haystack/haystack/model/logs.go b/backend/.gen/haystack/haystack/model/logs.go deleted file mode 100644 index 7f2ebe5..0000000 --- a/backend/.gen/haystack/haystack/model/logs.go +++ /dev/null @@ -1,19 +0,0 @@ -// -// Code generated by go-jet DO NOT EDIT. -// -// WARNING: Changes to this file may cause incorrect behavior -// and will be lost if the code is regenerated -// - -package model - -import ( - "github.com/google/uuid" - "time" -) - -type Logs struct { - Log string - ImageID uuid.UUID - CreatedAt *time.Time -} diff --git a/backend/.gen/haystack/haystack/model/processing_lists.go b/backend/.gen/haystack/haystack/model/processing_lists.go deleted file mode 100644 index 684ba6a..0000000 --- a/backend/.gen/haystack/haystack/model/processing_lists.go +++ /dev/null @@ -1,22 +0,0 @@ -// -// Code generated by go-jet DO NOT EDIT. -// -// WARNING: Changes to this file may cause incorrect behavior -// and will be lost if the code is regenerated -// - -package model - -import ( - "github.com/google/uuid" - "time" -) - -type ProcessingLists struct { - ID uuid.UUID `sql:"primary_key"` - UserID uuid.UUID - Title string - Fields string - Status Progress - CreatedAt *time.Time -} diff --git a/backend/.gen/haystack/haystack/model/schema_items.go b/backend/.gen/haystack/haystack/model/schema_items.go index 330ce55..cdc0390 100644 --- a/backend/.gen/haystack/haystack/model/schema_items.go +++ b/backend/.gen/haystack/haystack/model/schema_items.go @@ -16,5 +16,5 @@ type SchemaItems struct { Item string Value string Description string - SchemaID uuid.UUID + StackID uuid.UUID } diff --git a/backend/.gen/haystack/haystack/model/schemas.go b/backend/.gen/haystack/haystack/model/schemas.go deleted file mode 100644 index f4c04b1..0000000 --- a/backend/.gen/haystack/haystack/model/schemas.go +++ /dev/null @@ -1,17 +0,0 @@ -// -// Code generated by go-jet DO NOT EDIT. -// -// WARNING: Changes to this file may cause incorrect behavior -// and will be lost if the code is regenerated -// - -package model - -import ( - "github.com/google/uuid" -) - -type Schemas struct { - ID uuid.UUID `sql:"primary_key"` - ListID uuid.UUID -} diff --git a/backend/.gen/haystack/haystack/model/lists.go b/backend/.gen/haystack/haystack/model/stacks.go similarity index 89% rename from backend/.gen/haystack/haystack/model/lists.go rename to backend/.gen/haystack/haystack/model/stacks.go index ac24c1c..388eb23 100644 --- a/backend/.gen/haystack/haystack/model/lists.go +++ b/backend/.gen/haystack/haystack/model/stacks.go @@ -12,9 +12,10 @@ import ( "time" ) -type Lists struct { +type Stacks struct { ID uuid.UUID `sql:"primary_key"` UserID uuid.UUID + Status Progress Name string Description string CreatedAt *time.Time diff --git a/backend/.gen/haystack/haystack/model/user_images.go b/backend/.gen/haystack/haystack/model/user_images.go deleted file mode 100644 index b7dddc4..0000000 --- a/backend/.gen/haystack/haystack/model/user_images.go +++ /dev/null @@ -1,20 +0,0 @@ -// -// Code generated by go-jet DO NOT EDIT. -// -// WARNING: Changes to this file may cause incorrect behavior -// and will be lost if the code is regenerated -// - -package model - -import ( - "github.com/google/uuid" - "time" -) - -type UserImages struct { - ID uuid.UUID `sql:"primary_key"` - ImageID uuid.UUID - UserID uuid.UUID - CreatedAt *time.Time -} diff --git a/backend/.gen/haystack/haystack/model/user_images_to_process.go b/backend/.gen/haystack/haystack/model/user_images_to_process.go deleted file mode 100644 index 323c803..0000000 --- a/backend/.gen/haystack/haystack/model/user_images_to_process.go +++ /dev/null @@ -1,19 +0,0 @@ -// -// Code generated by go-jet DO NOT EDIT. -// -// WARNING: Changes to this file may cause incorrect behavior -// and will be lost if the code is regenerated -// - -package model - -import ( - "github.com/google/uuid" -) - -type UserImagesToProcess struct { - ID uuid.UUID `sql:"primary_key"` - Status Progress - ImageID uuid.UUID - UserID uuid.UUID -} diff --git a/backend/.gen/haystack/haystack/model/users.go b/backend/.gen/haystack/haystack/model/users.go index 2f417d8..9bea14e 100644 --- a/backend/.gen/haystack/haystack/model/users.go +++ b/backend/.gen/haystack/haystack/model/users.go @@ -9,9 +9,11 @@ package model import ( "github.com/google/uuid" + "time" ) type Users struct { - ID uuid.UUID `sql:"primary_key"` - Email string + ID uuid.UUID `sql:"primary_key"` + Email string + CreatedAt *time.Time } diff --git a/backend/.gen/haystack/haystack/table/image.go b/backend/.gen/haystack/haystack/table/image.go index c7467a6..098c596 100644 --- a/backend/.gen/haystack/haystack/table/image.go +++ b/backend/.gen/haystack/haystack/table/image.go @@ -18,13 +18,15 @@ type imageTable struct { // Columns ID postgres.ColumnString + UserID postgres.ColumnString ImageName postgres.ColumnString Description postgres.ColumnString - Image postgres.ColumnBytea + Status postgres.ColumnString + Image postgres.ColumnString + CreatedAt postgres.ColumnTimestampz AllColumns postgres.ColumnList MutableColumns postgres.ColumnList - DefaultColumns postgres.ColumnList } type ImageTable struct { @@ -63,12 +65,14 @@ func newImageTable(schemaName, tableName, alias string) *ImageTable { func newImageTableImpl(schemaName, tableName, alias string) imageTable { var ( IDColumn = postgres.StringColumn("id") + UserIDColumn = postgres.StringColumn("user_id") ImageNameColumn = postgres.StringColumn("image_name") DescriptionColumn = postgres.StringColumn("description") - ImageColumn = postgres.ByteaColumn("image") - allColumns = postgres.ColumnList{IDColumn, ImageNameColumn, DescriptionColumn, ImageColumn} - mutableColumns = postgres.ColumnList{ImageNameColumn, DescriptionColumn, ImageColumn} - defaultColumns = postgres.ColumnList{IDColumn} + StatusColumn = postgres.StringColumn("status") + ImageColumn = postgres.StringColumn("image") + CreatedAtColumn = postgres.TimestampzColumn("created_at") + allColumns = postgres.ColumnList{IDColumn, UserIDColumn, ImageNameColumn, DescriptionColumn, StatusColumn, ImageColumn, CreatedAtColumn} + mutableColumns = postgres.ColumnList{UserIDColumn, ImageNameColumn, DescriptionColumn, StatusColumn, ImageColumn, CreatedAtColumn} ) return imageTable{ @@ -76,12 +80,14 @@ func newImageTableImpl(schemaName, tableName, alias string) imageTable { //Columns ID: IDColumn, + UserID: UserIDColumn, ImageName: ImageNameColumn, Description: DescriptionColumn, + Status: StatusColumn, Image: ImageColumn, + CreatedAt: CreatedAtColumn, AllColumns: allColumns, MutableColumns: mutableColumns, - DefaultColumns: defaultColumns, } } diff --git a/backend/.gen/haystack/haystack/table/image_lists.go b/backend/.gen/haystack/haystack/table/image_lists.go deleted file mode 100644 index d4e9332..0000000 --- a/backend/.gen/haystack/haystack/table/image_lists.go +++ /dev/null @@ -1,84 +0,0 @@ -// -// Code generated by go-jet DO NOT EDIT. -// -// WARNING: Changes to this file may cause incorrect behavior -// and will be lost if the code is regenerated -// - -package table - -import ( - "github.com/go-jet/jet/v2/postgres" -) - -var ImageLists = newImageListsTable("haystack", "image_lists", "") - -type imageListsTable struct { - postgres.Table - - // Columns - ID postgres.ColumnString - ImageID postgres.ColumnString - ListID postgres.ColumnString - - AllColumns postgres.ColumnList - MutableColumns postgres.ColumnList - DefaultColumns postgres.ColumnList -} - -type ImageListsTable struct { - imageListsTable - - EXCLUDED imageListsTable -} - -// AS creates new ImageListsTable with assigned alias -func (a ImageListsTable) AS(alias string) *ImageListsTable { - return newImageListsTable(a.SchemaName(), a.TableName(), alias) -} - -// Schema creates new ImageListsTable with assigned schema name -func (a ImageListsTable) FromSchema(schemaName string) *ImageListsTable { - return newImageListsTable(schemaName, a.TableName(), a.Alias()) -} - -// WithPrefix creates new ImageListsTable with assigned table prefix -func (a ImageListsTable) WithPrefix(prefix string) *ImageListsTable { - return newImageListsTable(a.SchemaName(), prefix+a.TableName(), a.TableName()) -} - -// WithSuffix creates new ImageListsTable with assigned table suffix -func (a ImageListsTable) WithSuffix(suffix string) *ImageListsTable { - return newImageListsTable(a.SchemaName(), a.TableName()+suffix, a.TableName()) -} - -func newImageListsTable(schemaName, tableName, alias string) *ImageListsTable { - return &ImageListsTable{ - imageListsTable: newImageListsTableImpl(schemaName, tableName, alias), - EXCLUDED: newImageListsTableImpl("", "excluded", ""), - } -} - -func newImageListsTableImpl(schemaName, tableName, alias string) imageListsTable { - var ( - IDColumn = postgres.StringColumn("id") - ImageIDColumn = postgres.StringColumn("image_id") - ListIDColumn = postgres.StringColumn("list_id") - allColumns = postgres.ColumnList{IDColumn, ImageIDColumn, ListIDColumn} - mutableColumns = postgres.ColumnList{ImageIDColumn, ListIDColumn} - defaultColumns = postgres.ColumnList{IDColumn} - ) - - return imageListsTable{ - Table: postgres.NewTable(schemaName, tableName, alias, allColumns...), - - //Columns - ID: IDColumn, - ImageID: ImageIDColumn, - ListID: ListIDColumn, - - AllColumns: allColumns, - MutableColumns: mutableColumns, - DefaultColumns: defaultColumns, - } -} diff --git a/backend/.gen/haystack/haystack/table/image_schema_items.go b/backend/.gen/haystack/haystack/table/image_schema_items.go index e1e1a19..8d3b344 100644 --- a/backend/.gen/haystack/haystack/table/image_schema_items.go +++ b/backend/.gen/haystack/haystack/table/image_schema_items.go @@ -24,7 +24,6 @@ type imageSchemaItemsTable struct { AllColumns postgres.ColumnList MutableColumns postgres.ColumnList - DefaultColumns postgres.ColumnList } type ImageSchemaItemsTable struct { @@ -68,7 +67,6 @@ func newImageSchemaItemsTableImpl(schemaName, tableName, alias string) imageSche ImageIDColumn = postgres.StringColumn("image_id") allColumns = postgres.ColumnList{IDColumn, ValueColumn, SchemaItemIDColumn, ImageIDColumn} mutableColumns = postgres.ColumnList{ValueColumn, SchemaItemIDColumn, ImageIDColumn} - defaultColumns = postgres.ColumnList{IDColumn} ) return imageSchemaItemsTable{ @@ -82,6 +80,5 @@ func newImageSchemaItemsTableImpl(schemaName, tableName, alias string) imageSche AllColumns: allColumns, MutableColumns: mutableColumns, - DefaultColumns: defaultColumns, } } diff --git a/backend/.gen/haystack/haystack/table/image_stacks.go b/backend/.gen/haystack/haystack/table/image_stacks.go new file mode 100644 index 0000000..e4bc250 --- /dev/null +++ b/backend/.gen/haystack/haystack/table/image_stacks.go @@ -0,0 +1,81 @@ +// +// Code generated by go-jet DO NOT EDIT. +// +// WARNING: Changes to this file may cause incorrect behavior +// and will be lost if the code is regenerated +// + +package table + +import ( + "github.com/go-jet/jet/v2/postgres" +) + +var ImageStacks = newImageStacksTable("haystack", "image_stacks", "") + +type imageStacksTable struct { + postgres.Table + + // Columns + ID postgres.ColumnString + ImageID postgres.ColumnString + StackID postgres.ColumnString + + AllColumns postgres.ColumnList + MutableColumns postgres.ColumnList +} + +type ImageStacksTable struct { + imageStacksTable + + EXCLUDED imageStacksTable +} + +// AS creates new ImageStacksTable with assigned alias +func (a ImageStacksTable) AS(alias string) *ImageStacksTable { + return newImageStacksTable(a.SchemaName(), a.TableName(), alias) +} + +// Schema creates new ImageStacksTable with assigned schema name +func (a ImageStacksTable) FromSchema(schemaName string) *ImageStacksTable { + return newImageStacksTable(schemaName, a.TableName(), a.Alias()) +} + +// WithPrefix creates new ImageStacksTable with assigned table prefix +func (a ImageStacksTable) WithPrefix(prefix string) *ImageStacksTable { + return newImageStacksTable(a.SchemaName(), prefix+a.TableName(), a.TableName()) +} + +// WithSuffix creates new ImageStacksTable with assigned table suffix +func (a ImageStacksTable) WithSuffix(suffix string) *ImageStacksTable { + return newImageStacksTable(a.SchemaName(), a.TableName()+suffix, a.TableName()) +} + +func newImageStacksTable(schemaName, tableName, alias string) *ImageStacksTable { + return &ImageStacksTable{ + imageStacksTable: newImageStacksTableImpl(schemaName, tableName, alias), + EXCLUDED: newImageStacksTableImpl("", "excluded", ""), + } +} + +func newImageStacksTableImpl(schemaName, tableName, alias string) imageStacksTable { + var ( + IDColumn = postgres.StringColumn("id") + ImageIDColumn = postgres.StringColumn("image_id") + StackIDColumn = postgres.StringColumn("stack_id") + allColumns = postgres.ColumnList{IDColumn, ImageIDColumn, StackIDColumn} + mutableColumns = postgres.ColumnList{ImageIDColumn, StackIDColumn} + ) + + return imageStacksTable{ + Table: postgres.NewTable(schemaName, tableName, alias, allColumns...), + + //Columns + ID: IDColumn, + ImageID: ImageIDColumn, + StackID: StackIDColumn, + + AllColumns: allColumns, + MutableColumns: mutableColumns, + } +} diff --git a/backend/.gen/haystack/haystack/table/lists.go b/backend/.gen/haystack/haystack/table/lists.go deleted file mode 100644 index dc70a72..0000000 --- a/backend/.gen/haystack/haystack/table/lists.go +++ /dev/null @@ -1,90 +0,0 @@ -// -// Code generated by go-jet DO NOT EDIT. -// -// WARNING: Changes to this file may cause incorrect behavior -// and will be lost if the code is regenerated -// - -package table - -import ( - "github.com/go-jet/jet/v2/postgres" -) - -var Lists = newListsTable("haystack", "lists", "") - -type listsTable struct { - postgres.Table - - // Columns - ID postgres.ColumnString - UserID postgres.ColumnString - Name postgres.ColumnString - Description postgres.ColumnString - CreatedAt postgres.ColumnTimestampz - - AllColumns postgres.ColumnList - MutableColumns postgres.ColumnList - DefaultColumns postgres.ColumnList -} - -type ListsTable struct { - listsTable - - EXCLUDED listsTable -} - -// AS creates new ListsTable with assigned alias -func (a ListsTable) AS(alias string) *ListsTable { - return newListsTable(a.SchemaName(), a.TableName(), alias) -} - -// Schema creates new ListsTable with assigned schema name -func (a ListsTable) FromSchema(schemaName string) *ListsTable { - return newListsTable(schemaName, a.TableName(), a.Alias()) -} - -// WithPrefix creates new ListsTable with assigned table prefix -func (a ListsTable) WithPrefix(prefix string) *ListsTable { - return newListsTable(a.SchemaName(), prefix+a.TableName(), a.TableName()) -} - -// WithSuffix creates new ListsTable with assigned table suffix -func (a ListsTable) WithSuffix(suffix string) *ListsTable { - return newListsTable(a.SchemaName(), a.TableName()+suffix, a.TableName()) -} - -func newListsTable(schemaName, tableName, alias string) *ListsTable { - return &ListsTable{ - listsTable: newListsTableImpl(schemaName, tableName, alias), - EXCLUDED: newListsTableImpl("", "excluded", ""), - } -} - -func newListsTableImpl(schemaName, tableName, alias string) listsTable { - var ( - IDColumn = postgres.StringColumn("id") - UserIDColumn = postgres.StringColumn("user_id") - NameColumn = postgres.StringColumn("name") - DescriptionColumn = postgres.StringColumn("description") - CreatedAtColumn = postgres.TimestampzColumn("created_at") - allColumns = postgres.ColumnList{IDColumn, UserIDColumn, NameColumn, DescriptionColumn, CreatedAtColumn} - mutableColumns = postgres.ColumnList{UserIDColumn, NameColumn, DescriptionColumn, CreatedAtColumn} - defaultColumns = postgres.ColumnList{IDColumn, CreatedAtColumn} - ) - - return listsTable{ - Table: postgres.NewTable(schemaName, tableName, alias, allColumns...), - - //Columns - ID: IDColumn, - UserID: UserIDColumn, - Name: NameColumn, - Description: DescriptionColumn, - CreatedAt: CreatedAtColumn, - - AllColumns: allColumns, - MutableColumns: mutableColumns, - DefaultColumns: defaultColumns, - } -} diff --git a/backend/.gen/haystack/haystack/table/logs.go b/backend/.gen/haystack/haystack/table/logs.go deleted file mode 100644 index 2539860..0000000 --- a/backend/.gen/haystack/haystack/table/logs.go +++ /dev/null @@ -1,84 +0,0 @@ -// -// Code generated by go-jet DO NOT EDIT. -// -// WARNING: Changes to this file may cause incorrect behavior -// and will be lost if the code is regenerated -// - -package table - -import ( - "github.com/go-jet/jet/v2/postgres" -) - -var Logs = newLogsTable("haystack", "logs", "") - -type logsTable struct { - postgres.Table - - // Columns - Log postgres.ColumnString - ImageID postgres.ColumnString - CreatedAt postgres.ColumnTimestampz - - AllColumns postgres.ColumnList - MutableColumns postgres.ColumnList - DefaultColumns postgres.ColumnList -} - -type LogsTable struct { - logsTable - - EXCLUDED logsTable -} - -// AS creates new LogsTable with assigned alias -func (a LogsTable) AS(alias string) *LogsTable { - return newLogsTable(a.SchemaName(), a.TableName(), alias) -} - -// Schema creates new LogsTable with assigned schema name -func (a LogsTable) FromSchema(schemaName string) *LogsTable { - return newLogsTable(schemaName, a.TableName(), a.Alias()) -} - -// WithPrefix creates new LogsTable with assigned table prefix -func (a LogsTable) WithPrefix(prefix string) *LogsTable { - return newLogsTable(a.SchemaName(), prefix+a.TableName(), a.TableName()) -} - -// WithSuffix creates new LogsTable with assigned table suffix -func (a LogsTable) WithSuffix(suffix string) *LogsTable { - return newLogsTable(a.SchemaName(), a.TableName()+suffix, a.TableName()) -} - -func newLogsTable(schemaName, tableName, alias string) *LogsTable { - return &LogsTable{ - logsTable: newLogsTableImpl(schemaName, tableName, alias), - EXCLUDED: newLogsTableImpl("", "excluded", ""), - } -} - -func newLogsTableImpl(schemaName, tableName, alias string) logsTable { - var ( - LogColumn = postgres.StringColumn("log") - ImageIDColumn = postgres.StringColumn("image_id") - CreatedAtColumn = postgres.TimestampzColumn("created_at") - allColumns = postgres.ColumnList{LogColumn, ImageIDColumn, CreatedAtColumn} - mutableColumns = postgres.ColumnList{LogColumn, ImageIDColumn, CreatedAtColumn} - defaultColumns = postgres.ColumnList{CreatedAtColumn} - ) - - return logsTable{ - Table: postgres.NewTable(schemaName, tableName, alias, allColumns...), - - //Columns - Log: LogColumn, - ImageID: ImageIDColumn, - CreatedAt: CreatedAtColumn, - - AllColumns: allColumns, - MutableColumns: mutableColumns, - DefaultColumns: defaultColumns, - } -} diff --git a/backend/.gen/haystack/haystack/table/processing_lists.go b/backend/.gen/haystack/haystack/table/processing_lists.go deleted file mode 100644 index 3b3af88..0000000 --- a/backend/.gen/haystack/haystack/table/processing_lists.go +++ /dev/null @@ -1,93 +0,0 @@ -// -// Code generated by go-jet DO NOT EDIT. -// -// WARNING: Changes to this file may cause incorrect behavior -// and will be lost if the code is regenerated -// - -package table - -import ( - "github.com/go-jet/jet/v2/postgres" -) - -var ProcessingLists = newProcessingListsTable("haystack", "processing_lists", "") - -type processingListsTable struct { - postgres.Table - - // Columns - ID postgres.ColumnString - UserID postgres.ColumnString - Title postgres.ColumnString - Fields postgres.ColumnString - Status postgres.ColumnString - CreatedAt postgres.ColumnTimestampz - - AllColumns postgres.ColumnList - MutableColumns postgres.ColumnList - DefaultColumns postgres.ColumnList -} - -type ProcessingListsTable struct { - processingListsTable - - EXCLUDED processingListsTable -} - -// AS creates new ProcessingListsTable with assigned alias -func (a ProcessingListsTable) AS(alias string) *ProcessingListsTable { - return newProcessingListsTable(a.SchemaName(), a.TableName(), alias) -} - -// Schema creates new ProcessingListsTable with assigned schema name -func (a ProcessingListsTable) FromSchema(schemaName string) *ProcessingListsTable { - return newProcessingListsTable(schemaName, a.TableName(), a.Alias()) -} - -// WithPrefix creates new ProcessingListsTable with assigned table prefix -func (a ProcessingListsTable) WithPrefix(prefix string) *ProcessingListsTable { - return newProcessingListsTable(a.SchemaName(), prefix+a.TableName(), a.TableName()) -} - -// WithSuffix creates new ProcessingListsTable with assigned table suffix -func (a ProcessingListsTable) WithSuffix(suffix string) *ProcessingListsTable { - return newProcessingListsTable(a.SchemaName(), a.TableName()+suffix, a.TableName()) -} - -func newProcessingListsTable(schemaName, tableName, alias string) *ProcessingListsTable { - return &ProcessingListsTable{ - processingListsTable: newProcessingListsTableImpl(schemaName, tableName, alias), - EXCLUDED: newProcessingListsTableImpl("", "excluded", ""), - } -} - -func newProcessingListsTableImpl(schemaName, tableName, alias string) processingListsTable { - var ( - IDColumn = postgres.StringColumn("id") - UserIDColumn = postgres.StringColumn("user_id") - TitleColumn = postgres.StringColumn("title") - FieldsColumn = postgres.StringColumn("fields") - StatusColumn = postgres.StringColumn("status") - CreatedAtColumn = postgres.TimestampzColumn("created_at") - allColumns = postgres.ColumnList{IDColumn, UserIDColumn, TitleColumn, FieldsColumn, StatusColumn, CreatedAtColumn} - mutableColumns = postgres.ColumnList{UserIDColumn, TitleColumn, FieldsColumn, StatusColumn, CreatedAtColumn} - defaultColumns = postgres.ColumnList{IDColumn, StatusColumn, CreatedAtColumn} - ) - - return processingListsTable{ - Table: postgres.NewTable(schemaName, tableName, alias, allColumns...), - - //Columns - ID: IDColumn, - UserID: UserIDColumn, - Title: TitleColumn, - Fields: FieldsColumn, - Status: StatusColumn, - CreatedAt: CreatedAtColumn, - - AllColumns: allColumns, - MutableColumns: mutableColumns, - DefaultColumns: defaultColumns, - } -} diff --git a/backend/.gen/haystack/haystack/table/schema_items.go b/backend/.gen/haystack/haystack/table/schema_items.go index f7eb9f4..7b560ce 100644 --- a/backend/.gen/haystack/haystack/table/schema_items.go +++ b/backend/.gen/haystack/haystack/table/schema_items.go @@ -21,11 +21,10 @@ type schemaItemsTable struct { Item postgres.ColumnString Value postgres.ColumnString Description postgres.ColumnString - SchemaID postgres.ColumnString + StackID postgres.ColumnString AllColumns postgres.ColumnList MutableColumns postgres.ColumnList - DefaultColumns postgres.ColumnList } type SchemaItemsTable struct { @@ -67,10 +66,9 @@ func newSchemaItemsTableImpl(schemaName, tableName, alias string) schemaItemsTab ItemColumn = postgres.StringColumn("item") ValueColumn = postgres.StringColumn("value") DescriptionColumn = postgres.StringColumn("description") - SchemaIDColumn = postgres.StringColumn("schema_id") - allColumns = postgres.ColumnList{IDColumn, ItemColumn, ValueColumn, DescriptionColumn, SchemaIDColumn} - mutableColumns = postgres.ColumnList{ItemColumn, ValueColumn, DescriptionColumn, SchemaIDColumn} - defaultColumns = postgres.ColumnList{IDColumn} + StackIDColumn = postgres.StringColumn("stack_id") + allColumns = postgres.ColumnList{IDColumn, ItemColumn, ValueColumn, DescriptionColumn, StackIDColumn} + mutableColumns = postgres.ColumnList{ItemColumn, ValueColumn, DescriptionColumn, StackIDColumn} ) return schemaItemsTable{ @@ -81,10 +79,9 @@ func newSchemaItemsTableImpl(schemaName, tableName, alias string) schemaItemsTab Item: ItemColumn, Value: ValueColumn, Description: DescriptionColumn, - SchemaID: SchemaIDColumn, + StackID: StackIDColumn, AllColumns: allColumns, MutableColumns: mutableColumns, - DefaultColumns: defaultColumns, } } diff --git a/backend/.gen/haystack/haystack/table/schemas.go b/backend/.gen/haystack/haystack/table/schemas.go deleted file mode 100644 index 847e015..0000000 --- a/backend/.gen/haystack/haystack/table/schemas.go +++ /dev/null @@ -1,81 +0,0 @@ -// -// Code generated by go-jet DO NOT EDIT. -// -// WARNING: Changes to this file may cause incorrect behavior -// and will be lost if the code is regenerated -// - -package table - -import ( - "github.com/go-jet/jet/v2/postgres" -) - -var Schemas = newSchemasTable("haystack", "schemas", "") - -type schemasTable struct { - postgres.Table - - // Columns - ID postgres.ColumnString - ListID postgres.ColumnString - - AllColumns postgres.ColumnList - MutableColumns postgres.ColumnList - DefaultColumns postgres.ColumnList -} - -type SchemasTable struct { - schemasTable - - EXCLUDED schemasTable -} - -// AS creates new SchemasTable with assigned alias -func (a SchemasTable) AS(alias string) *SchemasTable { - return newSchemasTable(a.SchemaName(), a.TableName(), alias) -} - -// Schema creates new SchemasTable with assigned schema name -func (a SchemasTable) FromSchema(schemaName string) *SchemasTable { - return newSchemasTable(schemaName, a.TableName(), a.Alias()) -} - -// WithPrefix creates new SchemasTable with assigned table prefix -func (a SchemasTable) WithPrefix(prefix string) *SchemasTable { - return newSchemasTable(a.SchemaName(), prefix+a.TableName(), a.TableName()) -} - -// WithSuffix creates new SchemasTable with assigned table suffix -func (a SchemasTable) WithSuffix(suffix string) *SchemasTable { - return newSchemasTable(a.SchemaName(), a.TableName()+suffix, a.TableName()) -} - -func newSchemasTable(schemaName, tableName, alias string) *SchemasTable { - return &SchemasTable{ - schemasTable: newSchemasTableImpl(schemaName, tableName, alias), - EXCLUDED: newSchemasTableImpl("", "excluded", ""), - } -} - -func newSchemasTableImpl(schemaName, tableName, alias string) schemasTable { - var ( - IDColumn = postgres.StringColumn("id") - ListIDColumn = postgres.StringColumn("list_id") - allColumns = postgres.ColumnList{IDColumn, ListIDColumn} - mutableColumns = postgres.ColumnList{ListIDColumn} - defaultColumns = postgres.ColumnList{IDColumn} - ) - - return schemasTable{ - Table: postgres.NewTable(schemaName, tableName, alias, allColumns...), - - //Columns - ID: IDColumn, - ListID: ListIDColumn, - - AllColumns: allColumns, - MutableColumns: mutableColumns, - DefaultColumns: defaultColumns, - } -} diff --git a/backend/.gen/haystack/haystack/table/stacks.go b/backend/.gen/haystack/haystack/table/stacks.go new file mode 100644 index 0000000..f423be5 --- /dev/null +++ b/backend/.gen/haystack/haystack/table/stacks.go @@ -0,0 +1,90 @@ +// +// Code generated by go-jet DO NOT EDIT. +// +// WARNING: Changes to this file may cause incorrect behavior +// and will be lost if the code is regenerated +// + +package table + +import ( + "github.com/go-jet/jet/v2/postgres" +) + +var Stacks = newStacksTable("haystack", "stacks", "") + +type stacksTable struct { + postgres.Table + + // Columns + ID postgres.ColumnString + UserID postgres.ColumnString + Status postgres.ColumnString + Name postgres.ColumnString + Description postgres.ColumnString + CreatedAt postgres.ColumnTimestampz + + AllColumns postgres.ColumnList + MutableColumns postgres.ColumnList +} + +type StacksTable struct { + stacksTable + + EXCLUDED stacksTable +} + +// AS creates new StacksTable with assigned alias +func (a StacksTable) AS(alias string) *StacksTable { + return newStacksTable(a.SchemaName(), a.TableName(), alias) +} + +// Schema creates new StacksTable with assigned schema name +func (a StacksTable) FromSchema(schemaName string) *StacksTable { + return newStacksTable(schemaName, a.TableName(), a.Alias()) +} + +// WithPrefix creates new StacksTable with assigned table prefix +func (a StacksTable) WithPrefix(prefix string) *StacksTable { + return newStacksTable(a.SchemaName(), prefix+a.TableName(), a.TableName()) +} + +// WithSuffix creates new StacksTable with assigned table suffix +func (a StacksTable) WithSuffix(suffix string) *StacksTable { + return newStacksTable(a.SchemaName(), a.TableName()+suffix, a.TableName()) +} + +func newStacksTable(schemaName, tableName, alias string) *StacksTable { + return &StacksTable{ + stacksTable: newStacksTableImpl(schemaName, tableName, alias), + EXCLUDED: newStacksTableImpl("", "excluded", ""), + } +} + +func newStacksTableImpl(schemaName, tableName, alias string) stacksTable { + var ( + IDColumn = postgres.StringColumn("id") + UserIDColumn = postgres.StringColumn("user_id") + StatusColumn = postgres.StringColumn("status") + NameColumn = postgres.StringColumn("name") + DescriptionColumn = postgres.StringColumn("description") + CreatedAtColumn = postgres.TimestampzColumn("created_at") + allColumns = postgres.ColumnList{IDColumn, UserIDColumn, StatusColumn, NameColumn, DescriptionColumn, CreatedAtColumn} + mutableColumns = postgres.ColumnList{UserIDColumn, StatusColumn, NameColumn, DescriptionColumn, CreatedAtColumn} + ) + + return stacksTable{ + Table: postgres.NewTable(schemaName, tableName, alias, allColumns...), + + //Columns + ID: IDColumn, + UserID: UserIDColumn, + Status: StatusColumn, + Name: NameColumn, + Description: DescriptionColumn, + CreatedAt: CreatedAtColumn, + + AllColumns: allColumns, + MutableColumns: mutableColumns, + } +} diff --git a/backend/.gen/haystack/haystack/table/table_use_schema.go b/backend/.gen/haystack/haystack/table/table_use_schema.go index 67b0d9f..5f72152 100644 --- a/backend/.gen/haystack/haystack/table/table_use_schema.go +++ b/backend/.gen/haystack/haystack/table/table_use_schema.go @@ -11,14 +11,9 @@ package table // this method only once at the beginning of the program. func UseSchema(schema string) { Image = Image.FromSchema(schema) - ImageLists = ImageLists.FromSchema(schema) ImageSchemaItems = ImageSchemaItems.FromSchema(schema) - Lists = Lists.FromSchema(schema) - Logs = Logs.FromSchema(schema) - ProcessingLists = ProcessingLists.FromSchema(schema) + ImageStacks = ImageStacks.FromSchema(schema) SchemaItems = SchemaItems.FromSchema(schema) - Schemas = Schemas.FromSchema(schema) - UserImages = UserImages.FromSchema(schema) - UserImagesToProcess = UserImagesToProcess.FromSchema(schema) + Stacks = Stacks.FromSchema(schema) Users = Users.FromSchema(schema) } diff --git a/backend/.gen/haystack/haystack/table/user_images.go b/backend/.gen/haystack/haystack/table/user_images.go deleted file mode 100644 index bd5cb49..0000000 --- a/backend/.gen/haystack/haystack/table/user_images.go +++ /dev/null @@ -1,87 +0,0 @@ -// -// Code generated by go-jet DO NOT EDIT. -// -// WARNING: Changes to this file may cause incorrect behavior -// and will be lost if the code is regenerated -// - -package table - -import ( - "github.com/go-jet/jet/v2/postgres" -) - -var UserImages = newUserImagesTable("haystack", "user_images", "") - -type userImagesTable struct { - postgres.Table - - // Columns - ID postgres.ColumnString - ImageID postgres.ColumnString - UserID postgres.ColumnString - CreatedAt postgres.ColumnTimestampz - - AllColumns postgres.ColumnList - MutableColumns postgres.ColumnList - DefaultColumns postgres.ColumnList -} - -type UserImagesTable struct { - userImagesTable - - EXCLUDED userImagesTable -} - -// AS creates new UserImagesTable with assigned alias -func (a UserImagesTable) AS(alias string) *UserImagesTable { - return newUserImagesTable(a.SchemaName(), a.TableName(), alias) -} - -// Schema creates new UserImagesTable with assigned schema name -func (a UserImagesTable) FromSchema(schemaName string) *UserImagesTable { - return newUserImagesTable(schemaName, a.TableName(), a.Alias()) -} - -// WithPrefix creates new UserImagesTable with assigned table prefix -func (a UserImagesTable) WithPrefix(prefix string) *UserImagesTable { - return newUserImagesTable(a.SchemaName(), prefix+a.TableName(), a.TableName()) -} - -// WithSuffix creates new UserImagesTable with assigned table suffix -func (a UserImagesTable) WithSuffix(suffix string) *UserImagesTable { - return newUserImagesTable(a.SchemaName(), a.TableName()+suffix, a.TableName()) -} - -func newUserImagesTable(schemaName, tableName, alias string) *UserImagesTable { - return &UserImagesTable{ - userImagesTable: newUserImagesTableImpl(schemaName, tableName, alias), - EXCLUDED: newUserImagesTableImpl("", "excluded", ""), - } -} - -func newUserImagesTableImpl(schemaName, tableName, alias string) userImagesTable { - var ( - IDColumn = postgres.StringColumn("id") - ImageIDColumn = postgres.StringColumn("image_id") - UserIDColumn = postgres.StringColumn("user_id") - CreatedAtColumn = postgres.TimestampzColumn("created_at") - allColumns = postgres.ColumnList{IDColumn, ImageIDColumn, UserIDColumn, CreatedAtColumn} - mutableColumns = postgres.ColumnList{ImageIDColumn, UserIDColumn, CreatedAtColumn} - defaultColumns = postgres.ColumnList{IDColumn, CreatedAtColumn} - ) - - return userImagesTable{ - Table: postgres.NewTable(schemaName, tableName, alias, allColumns...), - - //Columns - ID: IDColumn, - ImageID: ImageIDColumn, - UserID: UserIDColumn, - CreatedAt: CreatedAtColumn, - - AllColumns: allColumns, - MutableColumns: mutableColumns, - DefaultColumns: defaultColumns, - } -} diff --git a/backend/.gen/haystack/haystack/table/user_images_to_process.go b/backend/.gen/haystack/haystack/table/user_images_to_process.go deleted file mode 100644 index 1da2f3e..0000000 --- a/backend/.gen/haystack/haystack/table/user_images_to_process.go +++ /dev/null @@ -1,87 +0,0 @@ -// -// Code generated by go-jet DO NOT EDIT. -// -// WARNING: Changes to this file may cause incorrect behavior -// and will be lost if the code is regenerated -// - -package table - -import ( - "github.com/go-jet/jet/v2/postgres" -) - -var UserImagesToProcess = newUserImagesToProcessTable("haystack", "user_images_to_process", "") - -type userImagesToProcessTable struct { - postgres.Table - - // Columns - ID postgres.ColumnString - Status postgres.ColumnString - ImageID postgres.ColumnString - UserID postgres.ColumnString - - AllColumns postgres.ColumnList - MutableColumns postgres.ColumnList - DefaultColumns postgres.ColumnList -} - -type UserImagesToProcessTable struct { - userImagesToProcessTable - - EXCLUDED userImagesToProcessTable -} - -// AS creates new UserImagesToProcessTable with assigned alias -func (a UserImagesToProcessTable) AS(alias string) *UserImagesToProcessTable { - return newUserImagesToProcessTable(a.SchemaName(), a.TableName(), alias) -} - -// Schema creates new UserImagesToProcessTable with assigned schema name -func (a UserImagesToProcessTable) FromSchema(schemaName string) *UserImagesToProcessTable { - return newUserImagesToProcessTable(schemaName, a.TableName(), a.Alias()) -} - -// WithPrefix creates new UserImagesToProcessTable with assigned table prefix -func (a UserImagesToProcessTable) WithPrefix(prefix string) *UserImagesToProcessTable { - return newUserImagesToProcessTable(a.SchemaName(), prefix+a.TableName(), a.TableName()) -} - -// WithSuffix creates new UserImagesToProcessTable with assigned table suffix -func (a UserImagesToProcessTable) WithSuffix(suffix string) *UserImagesToProcessTable { - return newUserImagesToProcessTable(a.SchemaName(), a.TableName()+suffix, a.TableName()) -} - -func newUserImagesToProcessTable(schemaName, tableName, alias string) *UserImagesToProcessTable { - return &UserImagesToProcessTable{ - userImagesToProcessTable: newUserImagesToProcessTableImpl(schemaName, tableName, alias), - EXCLUDED: newUserImagesToProcessTableImpl("", "excluded", ""), - } -} - -func newUserImagesToProcessTableImpl(schemaName, tableName, alias string) userImagesToProcessTable { - var ( - IDColumn = postgres.StringColumn("id") - StatusColumn = postgres.StringColumn("status") - ImageIDColumn = postgres.StringColumn("image_id") - UserIDColumn = postgres.StringColumn("user_id") - allColumns = postgres.ColumnList{IDColumn, StatusColumn, ImageIDColumn, UserIDColumn} - mutableColumns = postgres.ColumnList{StatusColumn, ImageIDColumn, UserIDColumn} - defaultColumns = postgres.ColumnList{IDColumn, StatusColumn} - ) - - return userImagesToProcessTable{ - Table: postgres.NewTable(schemaName, tableName, alias, allColumns...), - - //Columns - ID: IDColumn, - Status: StatusColumn, - ImageID: ImageIDColumn, - UserID: UserIDColumn, - - AllColumns: allColumns, - MutableColumns: mutableColumns, - DefaultColumns: defaultColumns, - } -} diff --git a/backend/.gen/haystack/haystack/table/users.go b/backend/.gen/haystack/haystack/table/users.go index 2880683..d3823b3 100644 --- a/backend/.gen/haystack/haystack/table/users.go +++ b/backend/.gen/haystack/haystack/table/users.go @@ -17,12 +17,12 @@ type usersTable struct { postgres.Table // Columns - ID postgres.ColumnString - Email postgres.ColumnString + ID postgres.ColumnString + Email postgres.ColumnString + CreatedAt postgres.ColumnTimestampz AllColumns postgres.ColumnList MutableColumns postgres.ColumnList - DefaultColumns postgres.ColumnList } type UsersTable struct { @@ -60,22 +60,22 @@ func newUsersTable(schemaName, tableName, alias string) *UsersTable { func newUsersTableImpl(schemaName, tableName, alias string) usersTable { var ( - IDColumn = postgres.StringColumn("id") - EmailColumn = postgres.StringColumn("email") - allColumns = postgres.ColumnList{IDColumn, EmailColumn} - mutableColumns = postgres.ColumnList{EmailColumn} - defaultColumns = postgres.ColumnList{IDColumn} + IDColumn = postgres.StringColumn("id") + EmailColumn = postgres.StringColumn("email") + CreatedAtColumn = postgres.TimestampzColumn("created_at") + allColumns = postgres.ColumnList{IDColumn, EmailColumn, CreatedAtColumn} + mutableColumns = postgres.ColumnList{EmailColumn, CreatedAtColumn} ) return usersTable{ Table: postgres.NewTable(schemaName, tableName, alias, allColumns...), //Columns - ID: IDColumn, - Email: EmailColumn, + ID: IDColumn, + Email: EmailColumn, + CreatedAt: CreatedAtColumn, AllColumns: allColumns, MutableColumns: mutableColumns, - DefaultColumns: defaultColumns, } } diff --git a/backend/agents/create_list_agent.go b/backend/agents/create_list_agent.go index e1f7447..78011e1 100644 --- a/backend/agents/create_list_agent.go +++ b/backend/agents/create_list_agent.go @@ -76,7 +76,7 @@ type createNewListArguments struct { type CreateListAgent struct { client client.AgentClient - listModel models.ListModel + listModel models.StackModel } func (agent *CreateListAgent) CreateList(log *log.Logger, userID uuid.UUID, userReq string) error { @@ -120,12 +120,20 @@ func (agent *CreateListAgent) CreateList(log *log.Logger, userID uuid.UUID, user }) } - agent.listModel.Save(ctx, userID, createListArgs.Title, createListArgs.Description, schemaItems) + _, err = agent.listModel.Save(ctx, userID, createListArgs.Title, createListArgs.Description) + if err != nil { + return fmt.Errorf("creating list agent, saving list: %w", err) + } + + err = agent.listModel.SaveItems(ctx, schemaItems) + if err != nil { + return fmt.Errorf("creating list agent, saving items: %w", err) + } return nil } -func NewCreateListAgent(log *log.Logger, listModel models.ListModel) CreateListAgent { +func NewCreateListAgent(log *log.Logger, listModel models.StackModel) CreateListAgent { client := client.CreateAgentClient(client.CreateAgentClientOptions{ SystemPrompt: createListAgentPrompt, Log: log, diff --git a/backend/agents/description_agent.go b/backend/agents/description_agent.go index bab10fd..3587483 100644 --- a/backend/agents/description_agent.go +++ b/backend/agents/description_agent.go @@ -3,6 +3,7 @@ package agents import ( "context" "fmt" + "screenmark/screenmark/.gen/haystack/haystack/model" "screenmark/screenmark/agents/client" "screenmark/screenmark/models" @@ -26,7 +27,7 @@ type DescriptionAgent struct { imageModel models.ImageModel } -func (agent DescriptionAgent) Describe(log *log.Logger, imageId uuid.UUID, imageName string, imageData []byte) error { +func (agent DescriptionAgent) Describe(log *log.Logger, imageID uuid.UUID, imageName string, imageData []byte) error { request := client.AgentRequestBody{ Model: "policy/images", Temperature: 0.3, @@ -51,7 +52,11 @@ func (agent DescriptionAgent) Describe(log *log.Logger, imageId uuid.UUID, image markdown := resp.Choices[0].Message.Content - err = agent.imageModel.AddDescription(ctx, imageId, markdown) + _, err = agent.imageModel.Update(ctx, model.Image{ + ID: imageID, + Description: markdown, + }) + if err != nil { return err } diff --git a/backend/agents/list_agent.go b/backend/agents/list_agent.go index 49db7aa..215ffc5 100644 --- a/backend/agents/list_agent.go +++ b/backend/agents/list_agent.go @@ -176,7 +176,7 @@ type addToListArguments struct { Schema []models.IDValue } -func NewListAgent(log *log.Logger, listModel models.ListModel, limitsMethods limits.LimitsManagerMethods) client.AgentClient { +func NewListAgent(log *log.Logger, listModel models.StackModel, limitsMethods limits.LimitsManagerMethods) client.AgentClient { agentClient := client.CreateAgentClient(client.CreateAgentClientOptions{ SystemPrompt: listPrompt, JsonTools: listTools, @@ -206,10 +206,19 @@ func NewListAgent(log *log.Logger, listModel models.ListModel, limitsMethods lim } ctx := context.Background() - savedList, err := listModel.Save(ctx, info.UserId, args.Name, args.Desription, args.Schema) - + savedList, err := listModel.Save(ctx, info.UserId, args.Name, args.Desription) if err != nil { - log.Error(err) + log.Error("saving list", "err", err) + return "", err + } + + for i := range args.Schema { + args.Schema[i].StackID = savedList.ID + } + + err = listModel.SaveItems(ctx, args.Schema) + if err != nil { + log.Error("saving items", "err", err) return "", err } @@ -229,12 +238,12 @@ func NewListAgent(log *log.Logger, listModel models.ListModel, limitsMethods lim ctx := context.Background() - listUuid, err := uuid.Parse(args.ListID) + listUUID, err := uuid.Parse(args.ListID) if err != nil { return "", err } - if err := listModel.SaveInto(ctx, listUuid, info.ImageId, args.Schema); err != nil { + if err := listModel.SaveImage(ctx, info.ImageId, listUUID); err != nil { return "", err } diff --git a/backend/events.go b/backend/events.go index c4f8382..09c0c82 100644 --- a/backend/events.go +++ b/backend/events.go @@ -1,23 +1,13 @@ package main import ( - "context" - "database/sql" "encoding/json" "fmt" "net/http" - "os" - "screenmark/screenmark/agents" - "screenmark/screenmark/limits" "screenmark/screenmark/middleware" - "screenmark/screenmark/models" "strconv" - "sync" - "time" - "github.com/charmbracelet/log" "github.com/google/uuid" - "github.com/lib/pq" ) const ( @@ -76,229 +66,6 @@ func (n *Notification) UnmarshalJSON(data []byte) error { return fmt.Errorf("unimplemented") } -func ProcessImage(log *log.Logger, db *sql.DB) func(imageID uuid.UUID) { - imageModel := models.NewImageModel(db) - listModel := models.NewListModel(db) - limits := limits.CreateLimitsManager(db) - - ctx := context.Background() - - return func(imageID uuid.UUID) { - log.Debug("Starting processing image", "ImageID", imageID) - - go func() { - image, err := imageModel.GetToProcessWithData(ctx, imageID) - if err != nil { - log.Error("Failed to GetToProcessWithData", "error", err) - return - } - - splitWriter := createDbStdoutWriter(db, image.ImageID) - - if err := imageModel.StartProcessing(ctx, image.ID); err != nil { - log.Error("Failed to FinishProcessing", "error", err) - return - } - - descriptionAgent := agents.NewDescriptionAgent(createLogger("Description 📝", splitWriter), imageModel) - listAgent := agents.NewListAgent(createLogger("Lists 🖋️", splitWriter), listModel, limits) - - var wg sync.WaitGroup - wg.Add(2) - - go func() { - defer wg.Done() - - descriptionAgent.Describe(createLogger("Description 📓", splitWriter), image.Image.ID, image.Image.ImageName, image.Image.Image) - }() - - go func() { - defer wg.Done() - - listAgent.RunAgent(image.UserID, image.ImageID, image.Image.ImageName, image.Image.Image) - }() - - wg.Wait() - - _, err = imageModel.FinishProcessing(ctx, image.ID) - if err != nil { - log.Error("Failed to finish processing", "ImageID", imageID, "error", err) - return - } - - log.Debug("Finished processing image", "ImageID", imageID) - }() - } -} - -func ListenNewImageEvents(db *sql.DB) { - listener := pq.NewListener(os.Getenv("DB_CONNECTION"), time.Second, time.Second, func(event pq.ListenerEventType, err error) { - if err != nil { - panic(err) - } - }) - defer listener.Close() - - databaseEventLog := createLogger("Database Events 🤖", os.Stdout) - databaseEventLog.SetLevel(log.DebugLevel) - - err := listener.Listen("new_image") - if err != nil { - panic(err) - } - - for parameters := range listener.Notify { - imageID := uuid.MustParse(parameters.Extra) - ProcessImage(databaseEventLog, db)(imageID) - } -} - -func ListenProcessingImageStatus(db *sql.DB, images models.ImageModel, notifier *Notifier[Notification]) { - listener := pq.NewListener(os.Getenv("DB_CONNECTION"), time.Second, time.Second, func(event pq.ListenerEventType, err error) { - if err != nil { - panic(err) - } - }) - defer listener.Close() - - logger := createLogger("Image Status 📊", os.Stdout) - - if err := listener.Listen("new_processing_image_status"); err != nil { - panic(err) - } - - for data := range listener.Notify { - imageStringUuid := data.Extra[0:36] - status := data.Extra[36:] - - imageUuid, err := uuid.Parse(imageStringUuid) - if err != nil { - logger.Error(err) - continue - } - - processingImage, err := images.GetToProcess(context.Background(), imageUuid) - if err != nil { - logger.Error("GetToProcess failed", "err", err) - continue - } - - logger.Info("Update", "id", imageStringUuid, "status", status) - - notification := getImageNotification(imageNotification{ - Type: IMAGE_TYPE, - ImageID: processingImage.ImageID, - ImageName: processingImage.Image.ImageName, - Status: status, - }) - - if err := notifier.SendAndCreate(processingImage.UserID.String(), notification); err != nil { - logger.Error(err) - } - } -} - -func ListenNewStackEvents(db *sql.DB) { - listener := pq.NewListener(os.Getenv("DB_CONNECTION"), time.Second, time.Second, func(event pq.ListenerEventType, err error) { - if err != nil { - panic(err) - } - }) - defer listener.Close() - - stackModel := models.NewListModel(db) - - newStacksLogger := createLogger("New Stacks 🤖", os.Stdout) - newStacksLogger.SetLevel(log.DebugLevel) - - err := listener.Listen("new_stack") - if err != nil { - panic(err) - } - - for parameters := range listener.Notify { - stackID := uuid.MustParse(parameters.Extra) - - newStacksLogger.Debug("Starting processing stack", "StackID", stackID) - - ctx := context.Background() - - go func() { - stack, err := stackModel.GetProcessing(ctx, stackID) - if err != nil { - newStacksLogger.Error("failed to get processing", "error", err) - return - } - - if err := stackModel.StartProcessing(ctx, stackID); err != nil { - newStacksLogger.Error("failed to start processing", "error", err) - return - } - - listAgent := agents.NewCreateListAgent(newStacksLogger, stackModel) - userListRequest := fmt.Sprintf("title=%s,fields=%s", stack.Title, stack.Fields) - - err = listAgent.CreateList(newStacksLogger, stack.UserID, userListRequest) - if err != nil { - newStacksLogger.Error("running agent", "err", err) - return - } - - if err := stackModel.EndProcessing(ctx, stackID); err != nil { - newStacksLogger.Error("failed to finish processing", "error", err) - return - } - - newStacksLogger.Debug("Finished processing stack", "StackID", stackID) - }() - } -} - -func ListenProcessingStackStatus(db *sql.DB, stacks models.ListModel, notifier *Notifier[Notification]) { - listener := pq.NewListener(os.Getenv("DB_CONNECTION"), time.Second, time.Second, func(event pq.ListenerEventType, err error) { - if err != nil { - panic(err) - } - }) - defer listener.Close() - - logger := createLogger("Stack Status 📊", os.Stdout) - - if err := listener.Listen("new_processing_stack_status"); err != nil { - panic(err) - } - - for data := range listener.Notify { - stackStringUUID := data.Extra[0:36] - status := data.Extra[36:] - - stackUUID, err := uuid.Parse(stackStringUUID) - if err != nil { - logger.Error(err) - continue - } - - processingStack, err := stacks.GetToProcess(context.Background(), stackUUID) - if err != nil { - logger.Error("GetToProcess failed", "err", err) - continue - } - - logger.Info("Update", "id", stackStringUUID, "status", status) - - notification := getListNotification(listNotification{ - Type: LIST_TYPE, - Name: processingStack.Title, - ListID: stackUUID, - Status: status, - }) - - if err := notifier.SendAndCreate(processingStack.UserID.String(), notification); err != nil { - logger.Error(err) - } - } -} - /* * TODO: We have channels open every a user sends an image. * We never close these channels. diff --git a/backend/images/handler.go b/backend/images/handler.go index befde10..ba14b17 100644 --- a/backend/images/handler.go +++ b/backend/images/handler.go @@ -9,7 +9,6 @@ import ( "net/http" "os" "path/filepath" - "screenmark/screenmark/.gen/haystack/haystack/model" "screenmark/screenmark/limits" "screenmark/screenmark/middleware" "screenmark/screenmark/models" @@ -27,15 +26,12 @@ type ImageHandler struct { limitsManager limits.LimitsManagerMethods - processImage func(imageID uuid.UUID) - jwtManager *middleware.JwtManager } type ImagesReturn struct { - UserImages []models.UserImageWithImage `json:"userImages"` - ProcessingImages []models.UserProcessingImage `json:"processingImages"` - Lists []models.ListsWithImages `json:"lists"` + UserImages []models.UserImageWithImage `json:"userImages"` + Lists []models.ListsWithImages `json:"lists"` } func (h *ImageHandler) serveImage(w http.ResponseWriter, r *http.Request) { @@ -52,19 +48,19 @@ func (h *ImageHandler) serveImage(w http.ResponseWriter, r *http.Request) { return } - isAuthorized := h.imageModel.IsUserAuthorized(ctx, imageID, userID) - if !isAuthorized { - w.WriteHeader(http.StatusUnauthorized) - return - } - - image, err := h.imageModel.Get(r.Context(), imageID) + image, exists, err := h.imageModel.Get(r.Context(), imageID) if err != nil { w.WriteHeader(http.StatusNotFound) fmt.Fprintf(w, "Could not get image") return } + // Do not leak that this ID exists. + if !exists || *image.UserID != userID { + w.WriteHeader(http.StatusNotFound) + return + } + // TODO: this could be part of the db table extension := filepath.Ext(image.ImageName) if len(extension) == 0 { @@ -89,12 +85,6 @@ func (h *ImageHandler) listImages(w http.ResponseWriter, r *http.Request) { return } - processingImages, err := h.imageModel.GetProcessing(r.Context(), userId) - if err != nil { - middleware.WriteErrorInternal(h.logger, "could not get processing images", w) - return - } - listsWithImages, err := h.userModel.ListWithImages(r.Context(), userId) if err != nil { middleware.WriteErrorInternal(h.logger, "could not get lists with images", w) @@ -102,9 +92,8 @@ func (h *ImageHandler) listImages(w http.ResponseWriter, r *http.Request) { } imagesReturn := ImagesReturn{ - UserImages: images, - ProcessingImages: processingImages, - Lists: listsWithImages, + UserImages: images, + Lists: listsWithImages, } middleware.WriteJsonOrError(h.logger, imagesReturn, w) @@ -117,7 +106,7 @@ func (h *ImageHandler) uploadImage(w http.ResponseWriter, r *http.Request) { return } - userId, err := middleware.GetUserID(r.Context(), h.logger, w) + userID, err := middleware.GetUserID(r.Context(), h.logger, w) if err != nil { return } @@ -151,17 +140,16 @@ func (h *ImageHandler) uploadImage(w http.ResponseWriter, r *http.Request) { return } - userImage, err := h.imageModel.Process(r.Context(), userId, model.Image{ - Image: image, - ImageName: imageName, - }) + ctx := r.Context() + + err = h.imageModel.Save(ctx, imageName, image, userID) if err != nil { middleware.WriteErrorInternal(h.logger, "could not save image to DB", w) return } - middleware.WriteJsonOrError(h.logger, userImage, w) + w.WriteHeader(http.StatusOK) } func (h *ImageHandler) deleteImage(w http.ResponseWriter, r *http.Request) { @@ -180,71 +168,19 @@ func (h *ImageHandler) deleteImage(w http.ResponseWriter, r *http.Request) { return } - isAuthorized := h.imageModel.IsUserAuthorized(ctx, imageID, userID) - if !isAuthorized { - w.WriteHeader(http.StatusUnauthorized) - return - } - - err = h.imageModel.Delete(ctx, imageID) + exists, err := h.imageModel.Delete(ctx, imageID, userID) if err != nil { h.logger.Warn("cannot delete image", "error", err) w.WriteHeader(http.StatusBadRequest) return } - w.WriteHeader(http.StatusOK) -} - -// This feature is actually stupid -func (h *ImageHandler) reprocessImage(w http.ResponseWriter, r *http.Request) { - stringImageID := chi.URLParam(r, "image-id") - imageID, err := uuid.Parse(stringImageID) - if err != nil { - w.WriteHeader(http.StatusBadRequest) + // Don't leak if the image exists or not + if !exists { + w.WriteHeader(http.StatusNotFound) return } - ctx := r.Context() - - userID, err := middleware.GetUserID(ctx, h.logger, w) - if err != nil { - w.WriteHeader(http.StatusInternalServerError) - return - } - - isAuthorized := h.imageModel.IsUserAuthorized(ctx, imageID, userID) - if !isAuthorized { - w.WriteHeader(http.StatusUnauthorized) - return - } - - imageToProcessID, err := h.imageModel.GetImageToProcessID(ctx, imageID) - if err != nil { - h.logger.Error("get image to process", "err", err) - w.WriteHeader(http.StatusInternalServerError) - return - } - - // The whole way in which I do this event driven stuff is stupid. - // It's so messy now - - err = h.imageModel.DeleteUserImage(ctx, imageID) - if err != nil { - h.logger.Error("delete user image", "err", err) - w.WriteHeader(http.StatusInternalServerError) - return - } - - err = h.imageModel.SetNotStarted(ctx, imageToProcessID) - if err != nil { - h.logger.Error("set not started", "err", err) - w.WriteHeader(http.StatusInternalServerError) - return - } - - h.processImage(imageToProcessID) - w.WriteHeader(http.StatusOK) } @@ -266,7 +202,7 @@ func (h *ImageHandler) CreateRoutes(r chi.Router) { }) } -func CreateImageHandler(db *sql.DB, limitsManager limits.LimitsManagerMethods, processImage func(imageID uuid.UUID), jwtManager *middleware.JwtManager) ImageHandler { +func CreateImageHandler(db *sql.DB, limitsManager limits.LimitsManagerMethods, jwtManager *middleware.JwtManager) ImageHandler { imageModel := models.NewImageModel(db) userModel := models.NewUserModel(db) logger := log.New(os.Stdout).WithPrefix("Images") @@ -276,7 +212,6 @@ func CreateImageHandler(db *sql.DB, limitsManager limits.LimitsManagerMethods, p imageModel: imageModel, userModel: userModel, limitsManager: limitsManager, - processImage: processImage, jwtManager: jwtManager, } } diff --git a/backend/limits/limits.go b/backend/limits/limits.go index 3190d1e..b502038 100644 --- a/backend/limits/limits.go +++ b/backend/limits/limits.go @@ -12,7 +12,7 @@ import ( const ( LISTS_LIMIT = 10 - IMAGE_LIMIT = 50 + IMAGE_LIMIT = 10 ) type LimitsManager struct { @@ -29,9 +29,9 @@ type listCount struct { } func (m *LimitsManager) HasReachedStackLimit(userID uuid.UUID) (bool, error) { - getStacks := Lists. - SELECT(COUNT(Lists.UserID).AS("listCount.ListCount")). - WHERE(Lists.UserID.EQ(UUID(userID))) + getStacks := Stacks. + SELECT(COUNT(Stacks.UserID).AS("listCount.ListCount")). + WHERE(Stacks.UserID.EQ(UUID(userID))) var count listCount err := getStacks.Query(m.dbPool, &count) @@ -44,9 +44,9 @@ type imageCount struct { } func (m *LimitsManager) HasReachedImageLimit(userID uuid.UUID) (bool, error) { - getStacks := UserImages. - SELECT(COUNT(UserImages.UserID).AS("imageCount.ImageCount")). - WHERE(UserImages.UserID.EQ(UUID(userID))) + getStacks := Image. + SELECT(COUNT(Image.UserID).AS("imageCount.ImageCount")). + WHERE(Image.UserID.EQ(UUID(userID))) var count imageCount err := getStacks.Query(m.dbPool, &count) diff --git a/backend/logs.go b/backend/logs.go index 80a4f70..2c1045e 100644 --- a/backend/logs.go +++ b/backend/logs.go @@ -1,21 +1,11 @@ package main import ( - "context" "database/sql" - "fmt" "io" - "net/http" "os" "time" - "screenmark/screenmark/.gen/haystack/haystack/model" - . "screenmark/screenmark/.gen/haystack/haystack/table" - - "github.com/go-chi/chi/v5" - . "github.com/go-jet/jet/v2/postgres" - "github.com/robert-nix/ansihtml" - "github.com/charmbracelet/log" "github.com/google/uuid" "github.com/muesli/termenv" @@ -31,12 +21,6 @@ func (w *DatabaseWriter) Write(p []byte) (n int, err error) { return 0, nil } - insertLogStmt := Logs. - INSERT(Logs.Log, Logs.ImageID). - VALUES(string(p), w.imageId) - - _, err = insertLogStmt.Exec(w.dbPool) - if err != nil { return 0, err } else { @@ -44,85 +28,6 @@ func (w *DatabaseWriter) Write(p []byte) (n int, err error) { } } -func (w *DatabaseWriter) GetImageLogs(ctx context.Context, imageId uuid.UUID) ([]string, error) { - getImageLogsStmt := Logs. - SELECT(Logs.Log). - WHERE(Logs.ImageID.EQ(UUID(imageId))) - - logs := []model.Logs{} - err := getImageLogsStmt.QueryContext(ctx, w.dbPool, &logs) - - if err != nil { - return []string{}, err - } - - stringLogs := make([]string, len(logs)) - for i, log := range logs { - stringLogs[i] = log.Log - } - - return stringLogs, nil -} - -func createLogHandler(logWriter *DatabaseWriter) func(r chi.Router) { - return func(r chi.Router) { - r.Get("/{imageId}", func(w http.ResponseWriter, r *http.Request) { - stringImageId := r.PathValue("imageId") - imageId, err := uuid.Parse(stringImageId) - if err != nil { - w.WriteHeader(http.StatusBadGateway) - return - } - - logs, err := logWriter.GetImageLogs(r.Context(), imageId) - if err != nil { - w.WriteHeader(http.StatusInternalServerError) - return - } - - html := "" - - imageTag := fmt.Sprintf(``, stringImageId) - - for _, log := range logs { - html += fmt.Sprintf("
%s
", string(ansihtml.ConvertToHTML([]byte(log)))+"\n") - } - - css := ` - - ` - - fullHtml := fmt.Sprintf("Logs%s%s%s", css, imageTag, html) - - w.Header().Add("Content-Type", "text/html") - w.Write([]byte(fullHtml)) - w.WriteHeader(http.StatusOK) - }) - } -} - func newDatabaseWriter(dbPool *sql.DB, imageId uuid.UUID) *DatabaseWriter { return &DatabaseWriter{ dbPool: dbPool, diff --git a/backend/models/image.go b/backend/models/image.go index 0681c37..c5add01 100644 --- a/backend/models/image.go +++ b/backend/models/image.go @@ -4,7 +4,6 @@ import ( "context" "database/sql" "fmt" - "screenmark/screenmark/.gen/haystack/haystack/enum" "screenmark/screenmark/.gen/haystack/haystack/model" . "screenmark/screenmark/.gen/haystack/haystack/table" @@ -18,248 +17,50 @@ type ImageModel struct { dbPool *sql.DB } -type ImageData struct { - model.UserImages +func (m ImageModel) Save(ctx context.Context, name string, image []byte, userID uuid.UUID) error { + saveImageStmt := Image.INSERT(Image.ImageName, Image.Image, Image.UserID). + VALUES(name, image, userID) - Image model.Image -} - -type ProcessingImageData struct { - model.UserImagesToProcess - - Image model.Image -} - -type UserProcessingImage struct { - model.UserImagesToProcess - - Image model.Image -} - -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{}, fmt.Errorf("Failed to begin transaction: %w", err) - } - - insertImageStmt := Image. - INSERT(Image.ImageName, Image.Image, Image.Description). - VALUES(image.ImageName, image.Image, image.Description). - RETURNING(Image.ID) - - insertedImage := model.Image{} - err = insertImageStmt.QueryContext(ctx, tx, &insertedImage) - if err != nil { - return model.UserImagesToProcess{}, fmt.Errorf("Could not insert/query new image. SQL %s: %w", insertImageStmt.DebugSql(), err) - } - - 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{}, fmt.Errorf("Could not insert user_image: %w", err) - } - - err = tx.Commit() - - return userImage, err -} - -func (m ImageModel) GetToProcess(ctx context.Context, imageId uuid.UUID) (UserProcessingImage, error) { - getToProcessStmt := SELECT(UserImagesToProcess.AllColumns, Image.ID, Image.ImageName). - FROM( - UserImagesToProcess.INNER_JOIN( - Image, Image.ID.EQ(UserImagesToProcess.ImageID), - ), - ). - WHERE(UserImagesToProcess.ID.EQ(UUID(imageId))) - - images := []UserProcessingImage{} - err := getToProcessStmt.QueryContext(ctx, m.dbPool, &images) - - if len(images) != 1 { - return UserProcessingImage{}, fmt.Errorf("Expected 1, got %d\n", len(images)) - } - - return images[0], err -} - -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))) - - images := []ProcessingImageData{} - err := stmt.QueryContext(ctx, m.dbPool, &images) - - if len(images) != 1 { - return ProcessingImageData{}, fmt.Errorf("Expected 1, got %d\n", len(images)) - } - - return images[0], err -} - -func (m ImageModel) FinishProcessing(ctx context.Context, imageId uuid.UUID) (model.UserImages, error) { - imageToProcess, err := m.GetToProcess(ctx, imageId) - if err != nil { - return model.UserImages{}, err - } - - tx, err := m.dbPool.Begin() - if err != nil { - return model.UserImages{}, err - } - - insertImageStmt := UserImages. - INSERT(UserImages.UserID, UserImages.ImageID). - VALUES(imageToProcess.UserID, imageToProcess.ImageID). - RETURNING(UserImages.ID, UserImages.UserID, UserImages.ImageID) - - userImage := model.UserImages{} - err = insertImageStmt.QueryContext(ctx, tx, &userImage) - if err != nil { - return model.UserImages{}, err - } - - // Hacky. Update the status before removing so we can get our regular triggers - // to work. - - updateStatusStmt := UserImagesToProcess. - UPDATE(UserImagesToProcess.Status). - SET(model.Progress_Complete). - WHERE(UserImagesToProcess.ID.EQ(UUID(imageToProcess.ID))) - - _, err = updateStatusStmt.ExecContext(ctx, tx) - if err != nil { - return model.UserImages{}, err - } - - // TODO: - // We cannot delete the image to process because our events rely on it. - // This indicates our DB structure with the two tables might need some adjusting. - // Or re-doing all together perhaps. - // (switching to a one table (user_images) could work) - // But for now, we can just not delete the images to process and set them to complete - - // removeProcessingStmt := UserImagesToProcess. - // DELETE(). - // WHERE(UserImagesToProcess.ID.EQ(UUID(imageToProcess.ID))) - // - // _, err = removeProcessingStmt.ExecContext(ctx, tx) - // if err != nil { - // return model.UserImages{}, err - // } - - err = tx.Commit() - return userImage, err -} - -func (m ImageModel) GetImageToProcessID(ctx context.Context, imageID uuid.UUID) (uuid.UUID, error) { - getImageToProcessIDStmt := UserImagesToProcess. - SELECT(UserImagesToProcess.ID). - WHERE(UserImagesToProcess.ImageID.EQ(UUID(imageID))) - - imageToProcess := model.UserImagesToProcess{} - err := getImageToProcessIDStmt.QueryContext(ctx, m.dbPool, &imageToProcess) - - return imageToProcess.ID, err -} - -func (m ImageModel) SetNotStarted(ctx context.Context, processingImageId uuid.UUID) error { - startProcessingStmt := UserImagesToProcess. - UPDATE(UserImagesToProcess.Status). - SET(model.Progress_NotStarted). - WHERE(UserImagesToProcess.ID.EQ(UUID(processingImageId))) - - _, err := startProcessingStmt.ExecContext(ctx, m.dbPool) + _, err := saveImageStmt.ExecContext(ctx, m.dbPool) return err } -func (m ImageModel) StartProcessing(ctx context.Context, processingImageId uuid.UUID) error { - startProcessingStmt := UserImagesToProcess. - UPDATE(UserImagesToProcess.Status). - SET(model.Progress_InProgress). - WHERE(UserImagesToProcess.ID.EQ(UUID(processingImageId))) - - _, err := startProcessingStmt.ExecContext(ctx, m.dbPool) - - return err -} - -func (m ImageModel) Get(ctx context.Context, imageId uuid.UUID) (model.Image, error) { - getImageStmt := Image.SELECT(Image.AllColumns). - WHERE(Image.ID.EQ(UUID(imageId))) +func (m ImageModel) Get(ctx context.Context, imageID uuid.UUID) (model.Image, bool, error) { + getImageStmt := Image.SELECT(Image.AllColumns.Except(Image.Image)).WHERE(Image.ID.EQ(UUID(imageID))) image := model.Image{} err := getImageStmt.QueryContext(ctx, m.dbPool, &image) - return image, err + return image, err != qrm.ErrNoRows, err } -func (m ImageModel) GetProcessing(ctx context.Context, userId uuid.UUID) ([]UserProcessingImage, error) { - getProcessingStmt := SELECT(UserImagesToProcess.AllColumns, Image.ID, Image.ImageName). - FROM( - UserImagesToProcess.INNER_JOIN( - Image, Image.ID.EQ(UserImagesToProcess.ImageID), - ), - ).WHERE( - UserImagesToProcess.UserID.EQ(UUID(userId)). - AND(UserImagesToProcess.Status.NOT_EQ(enum.Progress.Complete)), - ) +func (m ImageModel) Update(ctx context.Context, image model.Image) (model.Image, error) { + updateImageStmt := Image.UPDATE(Image.MutableColumns.Except(Image.Image)). + MODEL(image). + WHERE(Image.ID.EQ(UUID(image.ID))). + RETURNING(Image.AllColumns.Except(Image.Image)) - images := []UserProcessingImage{} - err := getProcessingStmt.QueryContext(ctx, m.dbPool, &images) + updatedImage := model.Image{} + err := updateImageStmt.QueryContext(ctx, m.dbPool, &updatedImage) - return images, err + return updatedImage, err } -func (m ImageModel) AddDescription(ctx context.Context, imageId uuid.UUID, description string) error { - updateImageStmt := Image.UPDATE(Image.Description). - SET(description). - WHERE(Image.ID.EQ(UUID(imageId))) +func (m ImageModel) Delete(ctx context.Context, imageID, userID uuid.UUID) (bool, error) { + deleteImageStmt := Image.DELETE().WHERE(Image.ID.EQ(UUID(imageID)).AND(Image.UserID.EQ(UUID(userID)))) - _, err := updateImageStmt.ExecContext(ctx, m.dbPool) + r, err := deleteImageStmt.ExecContext(ctx, m.dbPool) + if err != nil { + return false, fmt.Errorf("deleting image: %w", err) + } - return err -} + rowsAffected, err := r.RowsAffected() + if err != nil { + return false, fmt.Errorf("unreachable: %w", err) + } -func (m ImageModel) DeleteUserImage(ctx context.Context, imageID uuid.UUID) error { - deleteImageStmt := UserImages.DELETE(). - WHERE(UserImages.ImageID.EQ(UUID(imageID))) - - _, err := deleteImageStmt.ExecContext(ctx, m.dbPool) - - return err -} - -func (m ImageModel) Delete(ctx context.Context, imageID uuid.UUID) error { - deleteImageStmt := Image.DELETE(). - WHERE(Image.ID.EQ(UUID(imageID))) - - _, err := deleteImageStmt.ExecContext(ctx, m.dbPool) - - return err -} - -func (m ImageModel) IsUserAuthorized(ctx context.Context, imageId uuid.UUID, userId uuid.UUID) bool { - getImageUserId := UserImages.SELECT(UserImages.UserID).WHERE(UserImages.ImageID.EQ(UUID(imageId))) - getProcessingImageUserId := UserImagesToProcess.SELECT(UserImagesToProcess.UserID).WHERE(UserImagesToProcess.ImageID.EQ(UUID(imageId))) - - userImage := model.UserImages{} - userProcessingImage := model.UserImagesToProcess{} - - err1 := getImageUserId.QueryContext(ctx, m.dbPool, &userImage) - err2 := getProcessingImageUserId.QueryContext(ctx, m.dbPool, &userProcessingImage) - - return (err1 == nil || err1 == qrm.ErrNoRows) && (err2 == nil || err2 == qrm.ErrNoRows) && (userImage.UserID.String() == userId.String() || userProcessingImage.UserID.String() == userId.String()) + return rowsAffected > 0, nil } func NewImageModel(db *sql.DB) ImageModel { diff --git a/backend/models/lists.go b/backend/models/lists.go index 529af2e..78dd01c 100644 --- a/backend/models/lists.go +++ b/backend/models/lists.go @@ -3,7 +3,6 @@ package models import ( "context" "database/sql" - "fmt" "screenmark/screenmark/.gen/haystack/haystack/model" . "screenmark/screenmark/.gen/haystack/haystack/table" @@ -12,22 +11,18 @@ import ( "github.com/google/uuid" ) -type ListModel struct { +type StackModel struct { dbPool *sql.DB } -type ListWithItems struct { - model.Lists +type StackWithItems struct { + model.Stacks - Schema struct { - model.Schemas - - SchemaItems []model.SchemaItems - } + SchemaItems []model.SchemaItems } type ImageWithSchema struct { - model.ImageLists + model.ImageStacks Items []model.ImageSchemaItems } @@ -41,35 +36,33 @@ type IDValue struct { // SELECT for lists // ======================================== -func (m ListModel) List(ctx context.Context, userId uuid.UUID) ([]ListWithItems, error) { - getListsWithItems := SELECT( - Lists.AllColumns, - Schemas.AllColumns, +func (m StackModel) List(ctx context.Context, userId uuid.UUID) ([]StackWithItems, error) { + getStacksWithItems := SELECT( + Stacks.AllColumns, SchemaItems.AllColumns, ). FROM( - Lists. - INNER_JOIN(Schemas, Schemas.ListID.EQ(Lists.ID)). - INNER_JOIN(SchemaItems, SchemaItems.SchemaID.EQ(Schemas.ID)), + Stacks. + INNER_JOIN(SchemaItems, SchemaItems.StackID.EQ(Stacks.ID)), ). - WHERE(Lists.UserID.EQ(UUID(userId))) + WHERE(Stacks.UserID.EQ(UUID(userId))) - lists := []ListWithItems{} - err := getListsWithItems.QueryContext(ctx, m.dbPool, &lists) + lists := []StackWithItems{} + err := getStacksWithItems.QueryContext(ctx, m.dbPool, &lists) return lists, err } -func (m ListModel) ListItems(ctx context.Context, listID uuid.UUID) ([]ImageWithSchema, error) { +func (m StackModel) ListItems(ctx context.Context, listID uuid.UUID) ([]ImageWithSchema, error) { getListItems := SELECT( - ImageLists.AllColumns, + ImageStacks.AllColumns, ImageSchemaItems.AllColumns, ). FROM( - ImageLists. - INNER_JOIN(ImageSchemaItems, ImageSchemaItems.ImageID.EQ(ImageLists.ImageID)), + ImageStacks. + INNER_JOIN(ImageSchemaItems, ImageSchemaItems.ImageID.EQ(ImageStacks.ImageID)), ). - WHERE(ImageLists.ListID.EQ(UUID(listID))) + WHERE(ImageStacks.StackID.EQ(UUID(listID))) listItems := make([]ImageWithSchema, 0) err := getListItems.QueryContext(ctx, m.dbPool, &listItems) @@ -77,174 +70,45 @@ func (m ListModel) ListItems(ctx context.Context, listID uuid.UUID) ([]ImageWith return listItems, err } -// ======================================== -// SELECT for specific items -// ======================================== +func (m StackModel) Get(ctx context.Context, listID uuid.UUID) (model.Stacks, error) { + getStackStmt := Stacks.SELECT(Stacks.AllColumns).WHERE(Stacks.ID.EQ(UUID(listID))) -func (m ListModel) GetProcessing(ctx context.Context, processingListID uuid.UUID) (model.ProcessingLists, error) { - getProcessingListStmt := ProcessingLists. - SELECT(ProcessingLists.AllColumns). - WHERE(ProcessingLists.ID.EQ(UUID(processingListID))) + stack := model.Stacks{} + err := getStackStmt.QueryContext(ctx, m.dbPool, &stack) - list := model.ProcessingLists{} - err := getProcessingListStmt.QueryContext(ctx, m.dbPool, &list) - - return list, err -} - -func (m ListModel) GetToProcess(ctx context.Context, listID uuid.UUID) (model.ProcessingLists, error) { - getToProcessStmt := ProcessingLists. - SELECT(ProcessingLists.AllColumns). - WHERE(ProcessingLists.ID.EQ(UUID(listID))) - - stack := []model.ProcessingLists{} - err := getToProcessStmt.QueryContext(ctx, m.dbPool, &stack) - - if len(stack) != 1 { - return model.ProcessingLists{}, fmt.Errorf("Expected 1, got %d\n", len(stack)) - } - - return stack[0], err -} - -// ======================================== -// UPDATE -// ======================================== - -func (m ListModel) StartProcessing(ctx context.Context, processingListID uuid.UUID) error { - startProcessingStmt := ProcessingLists. - UPDATE(ProcessingLists.Status). - SET(model.Progress_InProgress). - WHERE(ProcessingLists.ID.EQ(UUID(processingListID))) - - _, err := startProcessingStmt.ExecContext(ctx, m.dbPool) - return err -} - -func (m ListModel) EndProcessing(ctx context.Context, processingListID uuid.UUID) error { - startProcessingStmt := ProcessingLists. - UPDATE(ProcessingLists.Status). - SET(model.Progress_Complete). - WHERE(ProcessingLists.ID.EQ(UUID(processingListID))) - - _, err := startProcessingStmt.ExecContext(ctx, m.dbPool) - return err + return stack, err } // ======================================== // INSERT methods // ======================================== -func (m ListModel) Save(ctx context.Context, userId uuid.UUID, name string, description string, schemaItems []model.SchemaItems) (ListWithItems, error) { - tx, err := m.dbPool.BeginTx(ctx, nil) +func (m StackModel) Save(ctx context.Context, userID uuid.UUID, name string, description string) (model.Stacks, error) { + saveListStmt := Stacks. + INSERT(Stacks.UserID, Stacks.Name, Stacks.Description). + VALUES(userID, name, description). + RETURNING(Stacks.ID, Stacks.UserID, Stacks.Name, Stacks.Description, Stacks.CreatedAt) - stmt := Lists.INSERT(Lists.UserID, Lists.Name, Lists.Description). - VALUES(userId, name, description). - RETURNING(Lists.ID, Lists.Name, Lists.Description) + list := model.Stacks{} + err := saveListStmt.QueryContext(ctx, m.dbPool, &list) - newList := model.Lists{} - err = stmt.QueryContext(ctx, tx, &newList) - - if err != nil { - tx.Rollback() - return ListWithItems{}, fmt.Errorf("Could not save new list. %s", err) - } - - insertSchemaStmt := Schemas.INSERT(Schemas.ListID). - VALUES(newList.ID). - RETURNING(Schemas.ID) - - newSchema := model.Schemas{} - err = insertSchemaStmt.QueryContext(ctx, tx, &newSchema) - - if err != nil { - tx.Rollback() - return ListWithItems{}, fmt.Errorf("Could not save new schema. %s", err) - } - - // This is very interesting... - for i := range schemaItems { - schemaItems[i].SchemaID = newSchema.ID - } - - insertSchemaItemsStmt := SchemaItems.INSERT(SchemaItems.Item, SchemaItems.Value, SchemaItems.Description, SchemaItems.SchemaID). - MODELS(schemaItems) - _, err = insertSchemaItemsStmt.ExecContext(ctx, tx) - - if err != nil { - tx.Rollback() - return ListWithItems{}, fmt.Errorf("Could not save schema items. %s", err) - } - - err = tx.Commit() - if err != nil { - return ListWithItems{}, fmt.Errorf("Could not commit transaction. %s", err) - } - - getListAndItems := SELECT(Lists.AllColumns, Schemas.AllColumns, SchemaItems.AllColumns). - FROM( - Lists. - INNER_JOIN(Schemas, Schemas.ListID.EQ(Lists.ID)). - INNER_JOIN(SchemaItems, SchemaItems.SchemaID.EQ(Schemas.ID)), - ). - WHERE(Lists.ID.EQ(UUID(newList.ID))) - - listWithItems := ListWithItems{} - err = getListAndItems.QueryContext(ctx, m.dbPool, &listWithItems) - - return listWithItems, err + return list, err } -func (m ListModel) SaveInto(ctx context.Context, listID uuid.UUID, imageID uuid.UUID, schemaValues []IDValue) error { - tx, err := m.dbPool.BeginTx(ctx, nil) - if err != nil { - return err - } +func (m StackModel) SaveItems(ctx context.Context, items []model.SchemaItems) error { + saveItemsStmt := SchemaItems.INSERT(SchemaItems.AllColumns).MODELS(items) - var imageList model.ImageLists - stmt := ImageLists.INSERT(ImageLists.ListID, ImageLists.ImageID). - VALUES(listID, imageID). - RETURNING(ImageLists.ID) + _, err := saveItemsStmt.ExecContext(ctx, m.dbPool) - err = stmt.QueryContext(ctx, m.dbPool, &imageList) - if err != nil { - tx.Rollback() - return fmt.Errorf("Could not insert new list. %s", err) - } - - imageSchemaItems := make([]model.ImageSchemaItems, len(schemaValues)) - - for i, v := range schemaValues { - parsedId, err := uuid.Parse(v.ID) - if err != nil { - return err - } - - imageSchemaItems[i].SchemaItemID = parsedId - imageSchemaItems[i].ImageID = imageList.ID - imageSchemaItems[i].Value = &v.Value - } - - insertSchemaItemsStmt := ImageSchemaItems. - INSERT(ImageSchemaItems.Value, ImageSchemaItems.SchemaItemID, ImageSchemaItems.ImageID). - MODELS(imageSchemaItems) - - _, err = insertSchemaItemsStmt.ExecContext(ctx, tx) - if err != nil { - tx.Rollback() - return fmt.Errorf("Could not insert schema items. %s", err) - } - - err = tx.Commit() return err } -func (m ListModel) SaveProcessing(ctx context.Context, userID uuid.UUID, title string, fields string) error { - insertListToProcess := ProcessingLists. - INSERT(ProcessingLists.UserID, ProcessingLists.Title, ProcessingLists.Fields). - VALUES(userID, title, fields) +func (m StackModel) SaveImage(ctx context.Context, imageID uuid.UUID, stackID uuid.UUID) error { + saveImageStmt := ImageStacks. + INSERT(ImageStacks.ImageID, ImageStacks.StackID). + VALUES(imageID, stackID) - _, err := insertListToProcess.ExecContext(ctx, m.dbPool) + _, err := saveImageStmt.ExecContext(ctx, m.dbPool) return err } @@ -253,11 +117,11 @@ func (m ListModel) SaveProcessing(ctx context.Context, userID uuid.UUID, title s // DELETE methods // ======================================== -func (m ListModel) DeleteImage(ctx context.Context, listID uuid.UUID, imageID uuid.UUID) error { - deleteImageListStmt := ImageLists.DELETE(). +func (m StackModel) DeleteImage(ctx context.Context, listID uuid.UUID, imageID uuid.UUID) error { + deleteImageListStmt := ImageStacks.DELETE(). WHERE( - ImageLists.ListID.EQ(UUID(listID)). - AND(ImageLists.ImageID.EQ(UUID(imageID))), + ImageStacks.StackID.EQ(UUID(listID)). + AND(ImageStacks.ImageID.EQ(UUID(imageID))), ) _, err := deleteImageListStmt.ExecContext(ctx, m.dbPool) @@ -265,60 +129,14 @@ func (m ListModel) DeleteImage(ctx context.Context, listID uuid.UUID, imageID uu return err } -func (m ListModel) Delete(ctx context.Context, listID uuid.UUID, userID uuid.UUID) error { - // First verify the list belongs to the user - checkOwnershipStmt := Lists. - SELECT(Lists.ID). - WHERE(Lists.ID.EQ(UUID(listID)).AND(Lists.UserID.EQ(UUID(userID)))) +func (m StackModel) Delete(ctx context.Context, listID uuid.UUID, userID uuid.UUID) error { + deleteStackStmt := Stacks.DELETE().WHERE(Stacks.ID.EQ(UUID(listID)).AND(Stacks.UserID.EQ(UUID(userID)))) - var existingList model.Lists - err := checkOwnershipStmt.QueryContext(ctx, m.dbPool, &existingList) - if err != nil { - return fmt.Errorf("could not verify list ownership: %w", err) - } + _, err := deleteStackStmt.ExecContext(ctx, m.dbPool) - // Start a transaction to ensure all deletions happen atomically - tx, err := m.dbPool.BeginTx(ctx, nil) - if err != nil { - return fmt.Errorf("could not start transaction: %w", err) - } - defer tx.Rollback() - - // Delete in reverse order of dependencies: - // 1. Delete schema items first - deleteSchemaItemsStmt := SchemaItems.DELETE(). - WHERE(SchemaItems.SchemaID.IN( - Schemas.SELECT(Schemas.ID). - WHERE(Schemas.ListID.EQ(UUID(listID))), - )) - _, err = deleteSchemaItemsStmt.ExecContext(ctx, tx) - if err != nil { - return fmt.Errorf("could not delete schema items: %w", err) - } - - // 2. Delete schemas - deleteSchemasStmt := Schemas.DELETE().WHERE(Schemas.ListID.EQ(UUID(listID))) - _, err = deleteSchemasStmt.ExecContext(ctx, tx) - if err != nil { - return fmt.Errorf("could not delete schemas: %w", err) - } - - // 3. Delete the list itself - deleteListStmt := Lists.DELETE().WHERE(Lists.ID.EQ(UUID(listID))) - _, err = deleteListStmt.ExecContext(ctx, tx) - if err != nil { - return fmt.Errorf("could not delete list: %w", err) - } - - // Commit the transaction - err = tx.Commit() - if err != nil { - return fmt.Errorf("could not commit transaction: %w", err) - } - - return nil + return err } -func NewListModel(db *sql.DB) ListModel { - return ListModel{dbPool: db} +func NewListModel(db *sql.DB) StackModel { + return StackModel{dbPool: db} } diff --git a/backend/models/user.go b/backend/models/user.go index 4af2999..7e380a8 100644 --- a/backend/models/user.go +++ b/backend/models/user.go @@ -49,28 +49,20 @@ func (m UserModel) Save(ctx context.Context, user model.Users) (model.Users, err } type UserImageWithImage struct { - model.UserImages - - Image struct { - model.Image - ImageLists []model.ImageLists - } + model.Image + ImageStacks []model.ImageStacks } func (m UserModel) GetUserImages(ctx context.Context, userId uuid.UUID) ([]UserImageWithImage, error) { getUserImagesStmt := SELECT( - UserImages.AllColumns, - Image.ID, - Image.ImageName, - Image.Description, - ImageLists.AllColumns, + Image.AllColumns.Except(Image.Image), + ImageStacks.AllColumns, ). FROM( - UserImages. - INNER_JOIN(Image, Image.ID.EQ(UserImages.ImageID)). - LEFT_JOIN(ImageLists, ImageLists.ImageID.EQ(UserImages.ImageID)), + Image. + LEFT_JOIN(ImageStacks, ImageStacks.ImageID.EQ(ImageStacks.ID)), ). - WHERE(UserImages.UserID.EQ(UUID(userId))) + WHERE(Image.UserID.EQ(UUID(userId))) userImages := []UserImageWithImage{} err := getUserImagesStmt.QueryContext(ctx, m.dbPool, &userImages) @@ -79,16 +71,12 @@ func (m UserModel) GetUserImages(ctx context.Context, userId uuid.UUID) ([]UserI } type ListsWithImages struct { - model.Lists + model.Stacks - Schema struct { - model.Schemas - - SchemaItems []model.SchemaItems - } + SchemaItems []model.SchemaItems Images []struct { - model.ImageLists + model.ImageStacks Items []model.ImageSchemaItems } @@ -96,20 +84,18 @@ type ListsWithImages struct { func (m UserModel) ListWithImages(ctx context.Context, userId uuid.UUID) ([]ListsWithImages, error) { stmt := SELECT( - Lists.AllColumns, - ImageLists.AllColumns, - Schemas.AllColumns, + Stacks.AllColumns, + ImageStacks.AllColumns, SchemaItems.AllColumns, ImageSchemaItems.AllColumns, ). FROM( - Lists. - INNER_JOIN(Schemas, Schemas.ListID.EQ(Lists.ID)). - INNER_JOIN(SchemaItems, SchemaItems.SchemaID.EQ(Schemas.ID)). - LEFT_JOIN(ImageLists, ImageLists.ListID.EQ(Lists.ID)). - LEFT_JOIN(ImageSchemaItems, ImageSchemaItems.ImageID.EQ(ImageLists.ID)), + Stacks. + INNER_JOIN(SchemaItems, SchemaItems.StackID.EQ(Stacks.ID)). + LEFT_JOIN(ImageStacks, ImageStacks.StackID.EQ(Stacks.ID)). + LEFT_JOIN(ImageSchemaItems, ImageSchemaItems.ImageID.EQ(ImageStacks.ID)), ). - WHERE(Lists.UserID.EQ(UUID(userId))) + WHERE(Stacks.UserID.EQ(UUID(userId))) lists := []ListsWithImages{} err := stmt.QueryContext(ctx, m.dbPool, &lists) diff --git a/backend/processor/image.go b/backend/processor/image.go new file mode 100644 index 0000000..e44ce80 --- /dev/null +++ b/backend/processor/image.go @@ -0,0 +1,95 @@ +package processor + +import ( + "context" + "screenmark/screenmark/.gen/haystack/haystack/model" + "screenmark/screenmark/agents" + "screenmark/screenmark/agents/client" + "screenmark/screenmark/models" + "sync" + + "github.com/charmbracelet/log" +) + +const IMAGE_PROCESS_AT_A_TIME = 10 + +type ImageProcessor struct { + imageModel models.ImageModel + logger *log.Logger + + descriptionAgent agents.DescriptionAgent + listAgent client.AgentClient + + // TODO: add the notifier here + + processor *Processor[model.Image] +} + +func (p *ImageProcessor) setImageToProcess(ctx context.Context, image model.Image) { + updatedImage := model.Image{ + ID: image.ID, + Status: model.Progress_InProgress, + } + + _, err := p.imageModel.Update(ctx, updatedImage) + if err != nil { + // TODO: what can we actually do here for the errors? + // We can't stop the work for the others + + p.logger.Error("failed to update image", "err", err) + + // TODO: we can use context here to actually pass some information through + return + } +} + +func (p *ImageProcessor) describe(ctx context.Context, image model.Image) { + descriptionSubLogger := p.logger.With("describe image", image.ID) + + err := p.descriptionAgent.Describe(descriptionSubLogger, image.ID, image.ImageName, image.Image) + if err != nil { + // Again, wtf do we do? + // Although i think the agent actually returns an error when it's finished + p.logger.Error("failed to describe image", "err", err) + return + } +} + +func (p *ImageProcessor) extractInfo(ctx context.Context, image model.Image) { + err := p.listAgent.RunAgent(*image.UserID, image.ID, image.ImageName, image.Image) + if err != nil { + // Again, wtf do we do? + // Although i think the agent actually returns an error when it's finished + p.logger.Error("failed to process image", "err", err) + return + } +} + +func (p *ImageProcessor) processImage(image model.Image) { + ctx := context.Background() + + p.setImageToProcess(ctx, image) + + var wg sync.WaitGroup + wg.Add(2) + + go func() { + p.describe(ctx, image) + wg.Done() + }() + + go func() { + p.extractInfo(ctx, image) + wg.Done() + }() + + wg.Wait() +} + +func CreateImageProcessor(logger *log.Logger, imageModel models.ImageModel) ImageProcessor { + imageProcessor := ImageProcessor{imageModel: imageModel, logger: logger} + + imageProcessor.processor = NewProcessor(int(IMAGE_PROCESS_AT_A_TIME), imageProcessor.processImage) + + return imageProcessor +} diff --git a/backend/processor/processor.go b/backend/processor/processor.go new file mode 100644 index 0000000..635e3f9 --- /dev/null +++ b/backend/processor/processor.go @@ -0,0 +1,19 @@ +package processor + +type Processor[TMessage any] struct { + queue chan TMessage + process func(message TMessage) +} + +func (p *Processor[TMessage]) Work() { + for msg := range p.queue { + p.process(msg) + } +} + +func NewProcessor[TMessage any](bufferSize int, process func(message TMessage)) *Processor[TMessage] { + return &Processor[TMessage]{ + queue: make(chan TMessage, bufferSize), + process: process, + } +} diff --git a/backend/router.go b/backend/router.go index faf3ecf..2943540 100644 --- a/backend/router.go +++ b/backend/router.go @@ -2,12 +2,10 @@ package main import ( "database/sql" - "os" "screenmark/screenmark/agents/client" "screenmark/screenmark/auth" "screenmark/screenmark/images" "screenmark/screenmark/limits" - "screenmark/screenmark/models" "screenmark/screenmark/stacks" ourmiddleware "screenmark/screenmark/middleware" @@ -25,33 +23,14 @@ func (client TestAiClient) GetImageInfo(imageName string, imageData []byte) (cli } func setupRouter(db *sql.DB, jwtManager *ourmiddleware.JwtManager) chi.Router { - imageModel := models.NewImageModel(db) - stackModel := models.NewListModel(db) - limitsManager := limits.CreateLimitsManager(db) - processImageLogger := createLogger("Process Image", os.Stdout) - processImage := ProcessImage(processImageLogger, db) - stackHandler := stacks.CreateStackHandler(db, limitsManager, jwtManager) authHandler := auth.CreateAuthHandler(db, jwtManager) - imageHandler := images.CreateImageHandler(db, limitsManager, processImage, jwtManager) + imageHandler := images.CreateImageHandler(db, limitsManager, jwtManager) notifier := NewNotifier[Notification](10) - // Only start event listeners if not in test environment - if os.Getenv("GO_TEST_ENVIRONMENT") != "true" { - - // TODO: should extract these into a notification manager - // And actually make them the same code. - // The events are basically the same. - - go ListenNewImageEvents(db) - go ListenProcessingImageStatus(db, imageModel, ¬ifier) - go ListenNewStackEvents(db) - go ListenProcessingStackStatus(db, stackModel, ¬ifier) - } - r := chi.NewRouter() r.Use(middleware.Logger) @@ -67,11 +46,5 @@ func setupRouter(db *sql.DB, jwtManager *ourmiddleware.JwtManager) chi.Router { r.Get("/", CreateEventsHandler(¬ifier)) }) - logWriter := DatabaseWriter{ - dbPool: db, - } - - r.Route("/logs", createLogHandler(&logWriter)) - return r } diff --git a/backend/schema.sql b/backend/schema.sql index 82ad9d1..5c972e9 100644 --- a/backend/schema.sql +++ b/backend/schema.sql @@ -9,72 +9,43 @@ CREATE TYPE haystack.progress AS ENUM('not-started','in-progress', 'complete'); /* -----| Schema tables |----- */ CREATE TABLE haystack.users ( - id uuid PRIMARY KEY DEFAULT gen_random_uuid(), - email TEXT NOT NULL + id UUID PRIMARY KEY DEFAULT gen_random_uuid(), + email TEXT NOT NULL, + + created_at TIMESTAMP WITH TIME ZONE DEFAULT now() ); CREATE TABLE haystack.image ( - id uuid PRIMARY KEY DEFAULT gen_random_uuid(), + id UUID PRIMARY KEY DEFAULT gen_random_uuid(), + user_id UUID REFERENCES haystack.users (id), + image_name TEXT NOT NULL, description TEXT NOT NULL, - image BYTEA NOT NULL -); -CREATE TABLE haystack.user_images_to_process ( - id uuid PRIMARY KEY DEFAULT gen_random_uuid(), status haystack.progress NOT NULL DEFAULT 'not-started', - image_id uuid NOT NULL UNIQUE REFERENCES haystack.image (id) ON DELETE CASCADE, - user_id uuid NOT NULL REFERENCES haystack.users (id) -); -CREATE TABLE haystack.user_images ( - id uuid PRIMARY KEY DEFAULT gen_random_uuid(), - image_id uuid NOT NULL UNIQUE REFERENCES haystack.image (id) ON DELETE CASCADE, - user_id uuid NOT NULL REFERENCES haystack.users (id), + image BYTEA NOT NULL, created_at TIMESTAMP WITH TIME ZONE DEFAULT now() ); -CREATE TABLE haystack.logs ( - log TEXT NOT NULL, - image_id UUID NOT NULL REFERENCES haystack.image (id) ON DELETE CASCADE, - - created_at TIMESTAMP WITH TIME ZONE DEFAULT now() -); - -CREATE TABLE haystack.lists ( +CREATE TABLE haystack.stacks ( id UUID PRIMARY KEY DEFAULT gen_random_uuid(), user_id UUID NOT NULL REFERENCES haystack.users (id), + status haystack.progress NOT NULL DEFAULT 'not-started', + name TEXT NOT NULL, description TEXT NOT NULL, created_at TIMESTAMP WITH TIME ZONE DEFAULT now() ); -CREATE TABLE haystack.processing_lists ( - id UUID PRIMARY KEY DEFAULT gen_random_uuid(), - user_id UUID NOT NULL REFERENCES haystack.users (id), - - title TEXT NOT NULL, - fields TEXT NOT NULL, - - status haystack.progress NOT NULL DEFAULT 'not-started', - - created_at TIMESTAMP WITH TIME ZONE DEFAULT now() -); - -CREATE TABLE haystack.image_lists ( +CREATE TABLE haystack.image_stacks ( id UUID PRIMARY KEY DEFAULT gen_random_uuid(), image_id UUID NOT NULL REFERENCES haystack.image (id) ON DELETE CASCADE, - list_id UUID NOT NULL REFERENCES haystack.lists (id) ON DELETE CASCADE -); - -CREATE TABLE haystack.schemas ( - id UUID PRIMARY KEY DEFAULT gen_random_uuid(), - - list_id UUID NOT NULL REFERENCES haystack.lists (id) ON DELETE CASCADE + stack_id UUID NOT NULL REFERENCES haystack.stacks (id) ON DELETE CASCADE ); CREATE TABLE haystack.schema_items ( @@ -84,7 +55,7 @@ CREATE TABLE haystack.schema_items ( value TEXT NOT NULL, description TEXT NOT NULL, - schema_id UUID NOT NULL REFERENCES haystack.schemas (id) + stack_id UUID NOT NULL REFERENCES haystack.stacks (id) ON DELETE CASCADE ); CREATE TABLE haystack.image_schema_items ( @@ -92,68 +63,6 @@ CREATE TABLE haystack.image_schema_items ( value TEXT, - schema_item_id UUID NOT NULL REFERENCES haystack.schema_items (id), - image_id UUID NOT NULL REFERENCES haystack.image_lists (id) ON DELETE CASCADE + schema_item_id UUID NOT NULL REFERENCES haystack.schema_items (id) ON DELETE CASCADE, + image_id UUID NOT NULL REFERENCES haystack.image_stacks (id) ON DELETE CASCADE ); - -/* -----| Indexes |----- */ - -/* -----| Stored Procedures |----- */ - -CREATE OR REPLACE FUNCTION notify_new_image() -RETURNS TRIGGER AS $$ -BEGIN - PERFORM pg_notify('new_image', NEW.id::texT); - RETURN NEW; -END -$$ LANGUAGE plpgsql; - -CREATE OR REPLACE FUNCTION notify_new_processing_image_status() -RETURNS TRIGGER AS $$ -BEGIN - IF NEW.status <> 'not-started' THEN - PERFORM pg_notify('new_processing_image_status', NEW.id::text || NEW.status::text); - END IF; - RETURN NEW; -END -$$ LANGUAGE plpgsql; - -CREATE OR REPLACE FUNCTION notify_new_stacks() -RETURNS TRIGGER AS $$ -BEGIN -PERFORM pg_notify('new_stack', NEW.id::text); - RETURN NEW; -END -$$ LANGUAGE plpgsql; - -CREATE OR REPLACE FUNCTION notify_new_processing_stack_status() -RETURNS TRIGGER AS $$ -BEGIN -PERFORM pg_notify('new_processing_stack_status', NEW.id::text || NEW.status::text); - RETURN NEW; -END -$$ LANGUAGE plpgsql; - -/* -----| Triggers |----- */ - -CREATE OR REPLACE TRIGGER on_new_image AFTER INSERT -ON haystack.user_images_to_process -FOR EACH ROW -EXECUTE PROCEDURE notify_new_image(); - -CREATE OR REPLACE TRIGGER on_update_image_progress -AFTER UPDATE OF status -ON haystack.user_images_to_process -FOR EACH ROW -EXECUTE PROCEDURE notify_new_processing_image_status(); - -CREATE OR REPLACE TRIGGER on_new_image AFTER INSERT -ON haystack.processing_lists -FOR EACH ROW -EXECUTE PROCEDURE notify_new_stacks(); - -CREATE OR REPLACE TRIGGER on_update_stack_progress -AFTER UPDATE OF status -ON haystack.processing_lists -FOR EACH ROW -EXECUTE PROCEDURE notify_new_processing_stack_status(); diff --git a/backend/stacks/handler.go b/backend/stacks/handler.go index 850b7de..0f18dbf 100644 --- a/backend/stacks/handler.go +++ b/backend/stacks/handler.go @@ -2,14 +2,11 @@ package stacks import ( "database/sql" - "fmt" "net/http" "os" - . "screenmark/screenmark/.gen/haystack/haystack/model" "screenmark/screenmark/limits" "screenmark/screenmark/middleware" "screenmark/screenmark/models" - "strings" "github.com/charmbracelet/log" "github.com/go-chi/chi/v5" @@ -20,7 +17,7 @@ type StackHandler struct { logger *log.Logger imageModel models.ImageModel - stackModel models.ListModel + stackModel models.StackModel limitsManager limits.LimitsManagerMethods @@ -125,8 +122,13 @@ func (h *StackHandler) deleteImageFromStack(w http.ResponseWriter, r *http.Reque return } - isAuthorized := h.imageModel.IsUserAuthorized(ctx, imageID, userID) - if !isAuthorized { + stack, err := h.stackModel.Get(ctx, listID) + if err != nil { + w.WriteHeader(http.StatusInternalServerError) + return + } + + if stack.UserID != userID { w.WriteHeader(http.StatusUnauthorized) return } @@ -142,10 +144,8 @@ func (h *StackHandler) deleteImageFromStack(w http.ResponseWriter, r *http.Reque } type CreateStackBody struct { - Title string `json:"title"` - - // We want a regular string because AI will take care of creating these for us. - Fields string `json:"fields"` + Title string `json:"title"` + Description string `json:"description"` } func (h *StackHandler) createStack(body CreateStackBody, w http.ResponseWriter, r *http.Request) { @@ -155,25 +155,7 @@ func (h *StackHandler) createStack(body CreateStackBody, w http.ResponseWriter, return } - // Convert fields string to basic schema items - // For now, create a simple schema item for each field - var schemaItems []SchemaItems - if body.Fields != "" { - fields := strings.Split(body.Fields, ",") - for i, field := range fields { - field = strings.TrimSpace(field) - if field != "" { - schemaItems = append(schemaItems, SchemaItems{ - Item: field, - Value: "", - Description: fmt.Sprintf("Field %d: %s", i+1, field), - }) - } - } - } - - // Use empty description for now since the API doesn't provide one - _, err = h.stackModel.Save(ctx, userID, body.Title, "", schemaItems) + _, err = h.stackModel.Save(ctx, userID, body.Title, body.Description) if err != nil { h.logger.Warn("could not save stack", "err", err) w.WriteHeader(http.StatusInternalServerError) diff --git a/frontend/src/components/image/index.tsx b/frontend/src/components/image/index.tsx index bab59d6..af20c67 100644 --- a/frontend/src/components/image/index.tsx +++ b/frontend/src/components/image/index.tsx @@ -67,7 +67,7 @@ export const ImageComponentFullHeight: Component = (props) const [accessToken] = createResource(getAccessToken); return ( - <> +