diff --git a/api/ent/downloadconfig.go b/api/ent/downloadconfig.go index e614fd5..ada007e 100644 --- a/api/ent/downloadconfig.go +++ b/api/ent/downloadconfig.go @@ -41,6 +41,8 @@ type DownloadConfig struct { DeltaSubfolder bool `json:"deltaSubfolder"` // GroupByDate holds the value of the "groupByDate" field. GroupByDate bool `json:"groupByDate"` + // FolderStructure holds the value of the "folder_structure" field. + FolderStructure string `json:"folderStructure"` // LastDownloadAt holds the value of the "lastDownloadAt" field. LastDownloadAt *time.Time `json:"lastDownloadAt"` // ProjectID holds the value of the "project_id" field. @@ -97,7 +99,7 @@ func (*DownloadConfig) scanValues(columns []string) ([]any, error) { values[i] = new([]byte) case downloadconfig.FieldDeltaSubfolder, downloadconfig.FieldGroupByDate: values[i] = new(sql.NullBool) - case downloadconfig.FieldID, downloadconfig.FieldName, downloadconfig.FieldProjectID: + case downloadconfig.FieldID, downloadconfig.FieldName, downloadconfig.FieldFolderStructure, downloadconfig.FieldProjectID: values[i] = new(sql.NullString) case downloadconfig.FieldCreatedAt, downloadconfig.FieldUpdatedAt, downloadconfig.FieldLastDownloadAt: values[i] = new(sql.NullTime) @@ -192,6 +194,12 @@ func (_m *DownloadConfig) assignValues(columns []string, values []any) error { } else if value.Valid { _m.GroupByDate = value.Bool } + case downloadconfig.FieldFolderStructure: + if value, ok := values[i].(*sql.NullString); !ok { + return fmt.Errorf("unexpected type %T for field folder_structure", values[i]) + } else if value.Valid { + _m.FolderStructure = value.String + } case downloadconfig.FieldLastDownloadAt: if value, ok := values[i].(*sql.NullTime); !ok { return fmt.Errorf("unexpected type %T for field lastDownloadAt", values[i]) @@ -291,6 +299,9 @@ func (_m *DownloadConfig) String() string { builder.WriteString("groupByDate=") builder.WriteString(fmt.Sprintf("%v", _m.GroupByDate)) builder.WriteString(", ") + builder.WriteString("folder_structure=") + builder.WriteString(_m.FolderStructure) + builder.WriteString(", ") if v := _m.LastDownloadAt; v != nil { builder.WriteString("lastDownloadAt=") builder.WriteString(v.Format(time.ANSIC)) diff --git a/api/ent/downloadconfig/downloadconfig.go b/api/ent/downloadconfig/downloadconfig.go index 3c26bdc..c366e37 100644 --- a/api/ent/downloadconfig/downloadconfig.go +++ b/api/ent/downloadconfig/downloadconfig.go @@ -34,6 +34,8 @@ const ( FieldDeltaSubfolder = "delta_subfolder" // FieldGroupByDate holds the string denoting the groupbydate field in the database. FieldGroupByDate = "group_by_date" + // FieldFolderStructure holds the string denoting the folder_structure field in the database. + FieldFolderStructure = "folder_structure" // FieldLastDownloadAt holds the string denoting the lastdownloadat field in the database. FieldLastDownloadAt = "last_download_at" // FieldProjectID holds the string denoting the project_id field in the database. @@ -75,6 +77,7 @@ var Columns = []string{ FieldBlockedImageIds, FieldDeltaSubfolder, FieldGroupByDate, + FieldFolderStructure, FieldLastDownloadAt, FieldProjectID, FieldUserID, @@ -109,6 +112,8 @@ var ( DefaultDeltaSubfolder bool // DefaultGroupByDate holds the default value on creation for the "groupByDate" field. DefaultGroupByDate bool + // DefaultFolderStructure holds the default value on creation for the "folder_structure" field. + DefaultFolderStructure string // DefaultID holds the default value on creation for the "id" field. DefaultID func() string // IDValidator is a validator for the "id" field. It is called by the builders before save. @@ -158,6 +163,11 @@ func ByGroupByDate(opts ...sql.OrderTermOption) OrderOption { return sql.OrderByField(FieldGroupByDate, opts...).ToFunc() } +// ByFolderStructure orders the results by the folder_structure field. +func ByFolderStructure(opts ...sql.OrderTermOption) OrderOption { + return sql.OrderByField(FieldFolderStructure, opts...).ToFunc() +} + // ByLastDownloadAt orders the results by the lastDownloadAt field. func ByLastDownloadAt(opts ...sql.OrderTermOption) OrderOption { return sql.OrderByField(FieldLastDownloadAt, opts...).ToFunc() diff --git a/api/ent/downloadconfig/where.go b/api/ent/downloadconfig/where.go index 0851e3c..08d1c36 100644 --- a/api/ent/downloadconfig/where.go +++ b/api/ent/downloadconfig/where.go @@ -101,6 +101,11 @@ func GroupByDate(v bool) predicate.DownloadConfig { return predicate.DownloadConfig(sql.FieldEQ(FieldGroupByDate, v)) } +// FolderStructure applies equality check predicate on the "folder_structure" field. It's identical to FolderStructureEQ. +func FolderStructure(v string) predicate.DownloadConfig { + return predicate.DownloadConfig(sql.FieldEQ(FieldFolderStructure, v)) +} + // LastDownloadAt applies equality check predicate on the "lastDownloadAt" field. It's identical to LastDownloadAtEQ. func LastDownloadAt(v time.Time) predicate.DownloadConfig { return predicate.DownloadConfig(sql.FieldEQ(FieldLastDownloadAt, v)) @@ -411,6 +416,71 @@ func GroupByDateNEQ(v bool) predicate.DownloadConfig { return predicate.DownloadConfig(sql.FieldNEQ(FieldGroupByDate, v)) } +// FolderStructureEQ applies the EQ predicate on the "folder_structure" field. +func FolderStructureEQ(v string) predicate.DownloadConfig { + return predicate.DownloadConfig(sql.FieldEQ(FieldFolderStructure, v)) +} + +// FolderStructureNEQ applies the NEQ predicate on the "folder_structure" field. +func FolderStructureNEQ(v string) predicate.DownloadConfig { + return predicate.DownloadConfig(sql.FieldNEQ(FieldFolderStructure, v)) +} + +// FolderStructureIn applies the In predicate on the "folder_structure" field. +func FolderStructureIn(vs ...string) predicate.DownloadConfig { + return predicate.DownloadConfig(sql.FieldIn(FieldFolderStructure, vs...)) +} + +// FolderStructureNotIn applies the NotIn predicate on the "folder_structure" field. +func FolderStructureNotIn(vs ...string) predicate.DownloadConfig { + return predicate.DownloadConfig(sql.FieldNotIn(FieldFolderStructure, vs...)) +} + +// FolderStructureGT applies the GT predicate on the "folder_structure" field. +func FolderStructureGT(v string) predicate.DownloadConfig { + return predicate.DownloadConfig(sql.FieldGT(FieldFolderStructure, v)) +} + +// FolderStructureGTE applies the GTE predicate on the "folder_structure" field. +func FolderStructureGTE(v string) predicate.DownloadConfig { + return predicate.DownloadConfig(sql.FieldGTE(FieldFolderStructure, v)) +} + +// FolderStructureLT applies the LT predicate on the "folder_structure" field. +func FolderStructureLT(v string) predicate.DownloadConfig { + return predicate.DownloadConfig(sql.FieldLT(FieldFolderStructure, v)) +} + +// FolderStructureLTE applies the LTE predicate on the "folder_structure" field. +func FolderStructureLTE(v string) predicate.DownloadConfig { + return predicate.DownloadConfig(sql.FieldLTE(FieldFolderStructure, v)) +} + +// FolderStructureContains applies the Contains predicate on the "folder_structure" field. +func FolderStructureContains(v string) predicate.DownloadConfig { + return predicate.DownloadConfig(sql.FieldContains(FieldFolderStructure, v)) +} + +// FolderStructureHasPrefix applies the HasPrefix predicate on the "folder_structure" field. +func FolderStructureHasPrefix(v string) predicate.DownloadConfig { + return predicate.DownloadConfig(sql.FieldHasPrefix(FieldFolderStructure, v)) +} + +// FolderStructureHasSuffix applies the HasSuffix predicate on the "folder_structure" field. +func FolderStructureHasSuffix(v string) predicate.DownloadConfig { + return predicate.DownloadConfig(sql.FieldHasSuffix(FieldFolderStructure, v)) +} + +// FolderStructureEqualFold applies the EqualFold predicate on the "folder_structure" field. +func FolderStructureEqualFold(v string) predicate.DownloadConfig { + return predicate.DownloadConfig(sql.FieldEqualFold(FieldFolderStructure, v)) +} + +// FolderStructureContainsFold applies the ContainsFold predicate on the "folder_structure" field. +func FolderStructureContainsFold(v string) predicate.DownloadConfig { + return predicate.DownloadConfig(sql.FieldContainsFold(FieldFolderStructure, v)) +} + // LastDownloadAtEQ applies the EQ predicate on the "lastDownloadAt" field. func LastDownloadAtEQ(v time.Time) predicate.DownloadConfig { return predicate.DownloadConfig(sql.FieldEQ(FieldLastDownloadAt, v)) diff --git a/api/ent/downloadconfig_create.go b/api/ent/downloadconfig_create.go index a3c4c84..7ad20a4 100644 --- a/api/ent/downloadconfig_create.go +++ b/api/ent/downloadconfig_create.go @@ -131,6 +131,20 @@ func (_c *DownloadConfigCreate) SetNillableGroupByDate(v *bool) *DownloadConfigC return _c } +// SetFolderStructure sets the "folder_structure" field. +func (_c *DownloadConfigCreate) SetFolderStructure(v string) *DownloadConfigCreate { + _c.mutation.SetFolderStructure(v) + return _c +} + +// SetNillableFolderStructure sets the "folder_structure" field if the given value is not nil. +func (_c *DownloadConfigCreate) SetNillableFolderStructure(v *string) *DownloadConfigCreate { + if v != nil { + _c.SetFolderStructure(*v) + } + return _c +} + // SetLastDownloadAt sets the "lastDownloadAt" field. func (_c *DownloadConfigCreate) SetLastDownloadAt(v time.Time) *DownloadConfigCreate { _c.mutation.SetLastDownloadAt(v) @@ -244,6 +258,10 @@ func (_c *DownloadConfigCreate) defaults() { v := downloadconfig.DefaultGroupByDate _c.mutation.SetGroupByDate(v) } + if _, ok := _c.mutation.FolderStructure(); !ok { + v := downloadconfig.DefaultFolderStructure + _c.mutation.SetFolderStructure(v) + } if _, ok := _c.mutation.ID(); !ok { v := downloadconfig.DefaultID() _c.mutation.SetID(v) @@ -272,6 +290,9 @@ func (_c *DownloadConfigCreate) check() error { if _, ok := _c.mutation.GroupByDate(); !ok { return &ValidationError{Name: "groupByDate", err: errors.New(`ent: missing required field "DownloadConfig.groupByDate"`)} } + if _, ok := _c.mutation.FolderStructure(); !ok { + return &ValidationError{Name: "folder_structure", err: errors.New(`ent: missing required field "DownloadConfig.folder_structure"`)} + } if _, ok := _c.mutation.ProjectID(); !ok { return &ValidationError{Name: "project_id", err: errors.New(`ent: missing required field "DownloadConfig.project_id"`)} } @@ -364,6 +385,10 @@ func (_c *DownloadConfigCreate) createSpec() (*DownloadConfig, *sqlgraph.CreateS _spec.SetField(downloadconfig.FieldGroupByDate, field.TypeBool, value) _node.GroupByDate = value } + if value, ok := _c.mutation.FolderStructure(); ok { + _spec.SetField(downloadconfig.FieldFolderStructure, field.TypeString, value) + _node.FolderStructure = value + } if value, ok := _c.mutation.LastDownloadAt(); ok { _spec.SetField(downloadconfig.FieldLastDownloadAt, field.TypeTime, value) _node.LastDownloadAt = &value diff --git a/api/ent/downloadconfig_update.go b/api/ent/downloadconfig_update.go index 1ec628e..dd41ecf 100644 --- a/api/ent/downloadconfig_update.go +++ b/api/ent/downloadconfig_update.go @@ -154,6 +154,20 @@ func (_u *DownloadConfigUpdate) SetNillableGroupByDate(v *bool) *DownloadConfigU return _u } +// SetFolderStructure sets the "folder_structure" field. +func (_u *DownloadConfigUpdate) SetFolderStructure(v string) *DownloadConfigUpdate { + _u.mutation.SetFolderStructure(v) + return _u +} + +// SetNillableFolderStructure sets the "folder_structure" field if the given value is not nil. +func (_u *DownloadConfigUpdate) SetNillableFolderStructure(v *string) *DownloadConfigUpdate { + if v != nil { + _u.SetFolderStructure(*v) + } + return _u +} + // SetLastDownloadAt sets the "lastDownloadAt" field. func (_u *DownloadConfigUpdate) SetLastDownloadAt(v time.Time) *DownloadConfigUpdate { _u.mutation.SetLastDownloadAt(v) @@ -347,6 +361,9 @@ func (_u *DownloadConfigUpdate) sqlSave(ctx context.Context) (_node int, err err if value, ok := _u.mutation.GroupByDate(); ok { _spec.SetField(downloadconfig.FieldGroupByDate, field.TypeBool, value) } + if value, ok := _u.mutation.FolderStructure(); ok { + _spec.SetField(downloadconfig.FieldFolderStructure, field.TypeString, value) + } if value, ok := _u.mutation.LastDownloadAt(); ok { _spec.SetField(downloadconfig.FieldLastDownloadAt, field.TypeTime, value) } @@ -553,6 +570,20 @@ func (_u *DownloadConfigUpdateOne) SetNillableGroupByDate(v *bool) *DownloadConf return _u } +// SetFolderStructure sets the "folder_structure" field. +func (_u *DownloadConfigUpdateOne) SetFolderStructure(v string) *DownloadConfigUpdateOne { + _u.mutation.SetFolderStructure(v) + return _u +} + +// SetNillableFolderStructure sets the "folder_structure" field if the given value is not nil. +func (_u *DownloadConfigUpdateOne) SetNillableFolderStructure(v *string) *DownloadConfigUpdateOne { + if v != nil { + _u.SetFolderStructure(*v) + } + return _u +} + // SetLastDownloadAt sets the "lastDownloadAt" field. func (_u *DownloadConfigUpdateOne) SetLastDownloadAt(v time.Time) *DownloadConfigUpdateOne { _u.mutation.SetLastDownloadAt(v) @@ -776,6 +807,9 @@ func (_u *DownloadConfigUpdateOne) sqlSave(ctx context.Context) (_node *Download if value, ok := _u.mutation.GroupByDate(); ok { _spec.SetField(downloadconfig.FieldGroupByDate, field.TypeBool, value) } + if value, ok := _u.mutation.FolderStructure(); ok { + _spec.SetField(downloadconfig.FieldFolderStructure, field.TypeString, value) + } if value, ok := _u.mutation.LastDownloadAt(); ok { _spec.SetField(downloadconfig.FieldLastDownloadAt, field.TypeTime, value) } diff --git a/api/ent/migrate/schema.go b/api/ent/migrate/schema.go index 6c78c8a..932a2aa 100644 --- a/api/ent/migrate/schema.go +++ b/api/ent/migrate/schema.go @@ -130,6 +130,7 @@ var ( {Name: "blocked_image_ids", Type: field.TypeJSON, Nullable: true}, {Name: "delta_subfolder", Type: field.TypeBool, Default: false}, {Name: "group_by_date", Type: field.TypeBool, Default: false}, + {Name: "folder_structure", Type: field.TypeString, Default: "default"}, {Name: "last_download_at", Type: field.TypeTime, Nullable: true}, {Name: "project_id", Type: field.TypeString, Size: 15}, {Name: "user_id", Type: field.TypeUUID}, @@ -142,13 +143,13 @@ var ( ForeignKeys: []*schema.ForeignKey{ { Symbol: "download_configs_projects_downloadConfigs", - Columns: []*schema.Column{DownloadConfigsColumns[12]}, + Columns: []*schema.Column{DownloadConfigsColumns[13]}, RefColumns: []*schema.Column{ProjectsColumns[0]}, OnDelete: schema.Cascade, }, { Symbol: "download_configs_users_downloadConfigs", - Columns: []*schema.Column{DownloadConfigsColumns[13]}, + Columns: []*schema.Column{DownloadConfigsColumns[14]}, RefColumns: []*schema.Column{UsersColumns[0]}, OnDelete: schema.Cascade, }, @@ -157,12 +158,12 @@ var ( { Name: "downloadconfig_project_id", Unique: false, - Columns: []*schema.Column{DownloadConfigsColumns[12]}, + Columns: []*schema.Column{DownloadConfigsColumns[13]}, }, { Name: "downloadconfig_user_id_project_id", Unique: false, - Columns: []*schema.Column{DownloadConfigsColumns[13], DownloadConfigsColumns[12]}, + Columns: []*schema.Column{DownloadConfigsColumns[14], DownloadConfigsColumns[13]}, }, }, } diff --git a/api/ent/mutation.go b/api/ent/mutation.go index 42d9b54..f23daa7 100644 --- a/api/ent/mutation.go +++ b/api/ent/mutation.go @@ -2911,6 +2911,7 @@ type DownloadConfigMutation struct { appendblockedImageIds []string deltaSubfolder *bool groupByDate *bool + folder_structure *string lastDownloadAt *time.Time clearedFields map[string]struct{} project *string @@ -3499,6 +3500,42 @@ func (m *DownloadConfigMutation) ResetGroupByDate() { m.groupByDate = nil } +// SetFolderStructure sets the "folder_structure" field. +func (m *DownloadConfigMutation) SetFolderStructure(s string) { + m.folder_structure = &s +} + +// FolderStructure returns the value of the "folder_structure" field in the mutation. +func (m *DownloadConfigMutation) FolderStructure() (r string, exists bool) { + v := m.folder_structure + if v == nil { + return + } + return *v, true +} + +// OldFolderStructure returns the old "folder_structure" field's value of the DownloadConfig entity. +// If the DownloadConfig object wasn't provided to the builder, the object is fetched from the database. +// An error is returned if the mutation operation is not UpdateOne, or the database query fails. +func (m *DownloadConfigMutation) OldFolderStructure(ctx context.Context) (v string, err error) { + if !m.op.Is(OpUpdateOne) { + return v, errors.New("OldFolderStructure is only allowed on UpdateOne operations") + } + if m.id == nil || m.oldValue == nil { + return v, errors.New("OldFolderStructure requires an ID field in the mutation") + } + oldValue, err := m.oldValue(ctx) + if err != nil { + return v, fmt.Errorf("querying old value for OldFolderStructure: %w", err) + } + return oldValue.FolderStructure, nil +} + +// ResetFolderStructure resets all changes to the "folder_structure" field. +func (m *DownloadConfigMutation) ResetFolderStructure() { + m.folder_structure = nil +} + // SetLastDownloadAt sets the "lastDownloadAt" field. func (m *DownloadConfigMutation) SetLastDownloadAt(t time.Time) { m.lastDownloadAt = &t @@ -3708,7 +3745,7 @@ func (m *DownloadConfigMutation) Type() string { // order to get all numeric fields that were incremented/decremented, call // AddedFields(). func (m *DownloadConfigMutation) Fields() []string { - fields := make([]string, 0, 13) + fields := make([]string, 0, 14) if m.createdAt != nil { fields = append(fields, downloadconfig.FieldCreatedAt) } @@ -3739,6 +3776,9 @@ func (m *DownloadConfigMutation) Fields() []string { if m.groupByDate != nil { fields = append(fields, downloadconfig.FieldGroupByDate) } + if m.folder_structure != nil { + fields = append(fields, downloadconfig.FieldFolderStructure) + } if m.lastDownloadAt != nil { fields = append(fields, downloadconfig.FieldLastDownloadAt) } @@ -3776,6 +3816,8 @@ func (m *DownloadConfigMutation) Field(name string) (ent.Value, bool) { return m.DeltaSubfolder() case downloadconfig.FieldGroupByDate: return m.GroupByDate() + case downloadconfig.FieldFolderStructure: + return m.FolderStructure() case downloadconfig.FieldLastDownloadAt: return m.LastDownloadAt() case downloadconfig.FieldProjectID: @@ -3811,6 +3853,8 @@ func (m *DownloadConfigMutation) OldField(ctx context.Context, name string) (ent return m.OldDeltaSubfolder(ctx) case downloadconfig.FieldGroupByDate: return m.OldGroupByDate(ctx) + case downloadconfig.FieldFolderStructure: + return m.OldFolderStructure(ctx) case downloadconfig.FieldLastDownloadAt: return m.OldLastDownloadAt(ctx) case downloadconfig.FieldProjectID: @@ -3896,6 +3940,13 @@ func (m *DownloadConfigMutation) SetField(name string, value ent.Value) error { } m.SetGroupByDate(v) return nil + case downloadconfig.FieldFolderStructure: + v, ok := value.(string) + if !ok { + return fmt.Errorf("unexpected type %T for field %s", value, name) + } + m.SetFolderStructure(v) + return nil case downloadconfig.FieldLastDownloadAt: v, ok := value.(time.Time) if !ok { @@ -4035,6 +4086,9 @@ func (m *DownloadConfigMutation) ResetField(name string) error { case downloadconfig.FieldGroupByDate: m.ResetGroupByDate() return nil + case downloadconfig.FieldFolderStructure: + m.ResetFolderStructure() + return nil case downloadconfig.FieldLastDownloadAt: m.ResetLastDownloadAt() return nil diff --git a/api/ent/runtime.go b/api/ent/runtime.go index 86b228c..3f0d15c 100644 --- a/api/ent/runtime.go +++ b/api/ent/runtime.go @@ -158,6 +158,10 @@ func init() { downloadconfigDescGroupByDate := downloadconfigFields[5].Descriptor() // downloadconfig.DefaultGroupByDate holds the default value on creation for the groupByDate field. downloadconfig.DefaultGroupByDate = downloadconfigDescGroupByDate.Default.(bool) + // downloadconfigDescFolderStructure is the schema descriptor for folder_structure field. + downloadconfigDescFolderStructure := downloadconfigFields[6].Descriptor() + // downloadconfig.DefaultFolderStructure holds the default value on creation for the folder_structure field. + downloadconfig.DefaultFolderStructure = downloadconfigDescFolderStructure.Default.(string) // downloadconfigDescID is the schema descriptor for id field. downloadconfigDescID := downloadconfigMixinFields0[0].Descriptor() // downloadconfig.DefaultID holds the default value on creation for the id field. diff --git a/api/ent/schema/download_config.go b/api/ent/schema/download_config.go index 2b0fd10..0d22acd 100644 --- a/api/ent/schema/download_config.go +++ b/api/ent/schema/download_config.go @@ -33,6 +33,7 @@ func (DownloadConfig) Fields() []ent.Field { // (PR #40 "upload" mode). field.Bool("deltaSubfolder").Default(false).StructTag(`json:"deltaSubfolder"`), field.Bool("groupByDate").Default(false).StructTag(`json:"groupByDate"`), + field.String("folder_structure").Default("default").StructTag(`json:"folderStructure"`), field.Time("lastDownloadAt").Optional().Nillable().StructTag(`json:"lastDownloadAt"`), field.String("project_id").StructTag(`json:"-"`), field.UUID("user_id", uuid.UUID{}).StructTag(`json:"-"`), @@ -51,4 +52,4 @@ func (DownloadConfig) Indexes() []ent.Index { index.Fields("project_id"), index.Fields("user_id", "project_id"), } -} +} \ No newline at end of file diff --git a/api/internal/repository/download_config.go b/api/internal/repository/download_config.go index a691ea2..642cc0e 100644 --- a/api/internal/repository/download_config.go +++ b/api/internal/repository/download_config.go @@ -42,6 +42,7 @@ type CreateDownloadConfigParameters struct { BlockedImageIds []string DeltaSubfolder bool GroupByDate bool + FolderStructure string } func (r *Repository) CreateDownloadConfig(ctx context.Context, parameters *CreateDownloadConfigParameters) (*ent.DownloadConfig, error) { @@ -60,6 +61,7 @@ func (r *Repository) CreateDownloadConfig(ctx context.Context, parameters *Creat SetGroupByDate(parameters.GroupByDate). SetCreatedBy(util.GetActorID(ctx)). SetUpdatedBy(util.GetActorID(ctx)). + SetFolderStructure(parameters.FolderStructure). Save(ctx) if err != nil { log.Error().Err(err).Msg("error creating download config") @@ -81,6 +83,7 @@ type UpdateDownloadConfigParameters struct { BlockedImageIds *[]string DeltaSubfolder *bool GroupByDate *bool + FolderStructure *string LastDownloadAt *time.Time } @@ -118,6 +121,9 @@ func (r *Repository) UpdateDownloadConfig(ctx context.Context, id string, parame if parameters.GroupByDate != nil { update.SetGroupByDate(*parameters.GroupByDate) } + if parameters.FolderStructure != nil { + update.SetFolderStructure(*parameters.FolderStructure) + } if parameters.LastDownloadAt != nil { update.SetLastDownloadAt(*parameters.LastDownloadAt) } @@ -125,6 +131,7 @@ func (r *Repository) UpdateDownloadConfig(ctx context.Context, id string, parame log.Error().Err(err).Msg("error updating download config") return nil, err } + safeGo(func() { r.CreateAuditLog(context.WithoutCancel(ctx), &CreateAuditLogParameters{ Action: "update", ObjectType: util.StringPointer("download_config"), ObjectId: util.StringPointer(id), diff --git a/api/internal/server/download_configs_controller.go b/api/internal/server/download_configs_controller.go index a842265..b71eedc 100644 --- a/api/internal/server/download_configs_controller.go +++ b/api/internal/server/download_configs_controller.go @@ -28,6 +28,7 @@ func downloadConfigResponse(cfg *ent.DownloadConfig) gin.H { "projectId": cfg.ProjectID, "createdAt": cfg.CreatedAt, "updatedAt": cfg.UpdatedAt, + "folderStructure": cfg.FolderStructure, } } @@ -76,6 +77,7 @@ type createDownloadConfigPayload struct { BlockedImageIds []string `json:"blockedImageIds"` DeltaSubfolder bool `json:"deltaSubfolder"` GroupByDate bool `json:"groupByDate"` + FolderStructure string `json:"folderStructure"` } func (s *Server) createDownloadConfig(c *gin.Context) { @@ -95,6 +97,7 @@ func (s *Server) createDownloadConfig(c *gin.Context) { BlockedImageIds: payload.BlockedImageIds, DeltaSubfolder: payload.DeltaSubfolder, GroupByDate: payload.GroupByDate, + FolderStructure: payload.FolderStructure, }) if abortDownloadConfigMutationError(c, err) { return @@ -128,6 +131,7 @@ func (s *Server) updateDownloadConfig(c *gin.Context) { DeltaSubfolder *bool `json:"deltaSubfolder"` GroupByDate *bool `json:"groupByDate"` LastDownloadAt *time.Time `json:"lastDownloadAt"` + FolderStructure *string `json:"folderStructure"` } if !bindJSON(c, &payload) { return @@ -144,6 +148,7 @@ func (s *Server) updateDownloadConfig(c *gin.Context) { DeltaSubfolder: payload.DeltaSubfolder, GroupByDate: payload.GroupByDate, LastDownloadAt: payload.LastDownloadAt, + FolderStructure: payload.FolderStructure, }) if abortDownloadConfigMutationError(c, err) { return diff --git a/ui/src/api/downloadConfigs.ts b/ui/src/api/downloadConfigs.ts index 89111a4..9862267 100644 --- a/ui/src/api/downloadConfigs.ts +++ b/ui/src/api/downloadConfigs.ts @@ -9,6 +9,7 @@ export interface DownloadConfigCreate { blockedImageIds?: string[]; deltaSubfolder?: boolean; groupByDate?: boolean; + folderStructure?: 'weekday' | 'default' | undefined; } export interface DownloadConfigUpdate { @@ -21,6 +22,28 @@ export interface DownloadConfigUpdate { lastDownloadAt?: string; } +export interface DownloadConfigCreate { + name: string; + projectId: string; + whitelistTagIds?: string[]; + blacklistTagIds?: string[]; + blockedImageIds?: string[]; + deltaSubfolder?: boolean; + groupByDate?: boolean; + folderStructure?: "default" | "weekday"; +} + +export interface DownloadConfigUpdate { + name?: string; + whitelistTagIds?: string[]; + blacklistTagIds?: string[]; + blockedImageIds?: string[]; + deltaSubfolder?: boolean; + groupByDate?: boolean; + folderStructure?: "default" | "weekday"; + lastDownloadAt?: string; +} + export async function list(projectId: string): Promise> { const { data } = await http.get>("/download-configs", { params: { projectId } }); return data; diff --git a/ui/src/components/download/DownloadConfigDialog.vue b/ui/src/components/download/DownloadConfigDialog.vue index c61bb85..050a212 100644 --- a/ui/src/components/download/DownloadConfigDialog.vue +++ b/ui/src/components/download/DownloadConfigDialog.vue @@ -130,27 +130,25 @@ One file name (or image id) per line — never downloaded by this config. - -
- - -
+
+ + +
-
@@ -108,8 +108,175 @@ + +
+
+
+

+ Sync läuft — {{ syncProgress.configName }} +

- +

+ + + + + +

+
+ + + + + + +
+ +
+
+ +
+
+ +

+ + +

+
+ + +
+
+
+

+ Sync abgeschlossen +

+ +

+ Config: {{ syncResult.configName }} + · {{ formatDateTime(syncResult.syncedAt.toISOString()) }} +

+
+ + +
+ +
+
+

+ {{ syncResult.result.deletedCount }} +

+

+ nach _deleted verschoben +

+
+ +
+

+ {{ syncResult.result.blacklistedCount }} +

+

+ nach _blacklist verschoben +

+
+ +
+

+ {{ syncResult.result.movedFiles.length }} +

+

+ Dateien insgesamt verschoben +

+
+
+ +
+

+ Verschobene Dateien +

+ +
+ + + + + + + + + + + + + + + + +
+ Dateiname + + Grund + + Neuer Speicherort +
+ {{ moved.basename }} + + {{ moved.reason }} + + {{ moved.toPath }} +
+
+
+
+ +

+ + + +

last download {{ formatDateTime(config.lastDownloadAt) }} never downloaded

- - + - @@ -196,7 +376,11 @@ @save="saveConfig" @deleted="deleteConfig" /> - +
@@ -226,8 +410,27 @@ import { RETRY_COUNT, RunProgress, runDownload, + reconcileLocalFiles, + ReconcileResult, } from "src/util/downloadRunner"; +interface SyncResultState { + configName: string; + result: ReconcileResult; + syncedAt: Date; +} + +interface SyncProgress { + configName: string; + current: number; + total: number; + fileName?: string; + phase: "scanning" | "moving" | "finished"; +} + +const syncProgress = ref(null); + +const syncResult = ref(null); const userStore = useUserStore(); const { activeProjectId } = storeToRefs(userStore); @@ -238,13 +441,16 @@ const matchCounts = ref>({}); const folderNames = ref>({}); const loaded = ref(false); -const unexpectedError = ref(null); +const unexpectedError = ref(undefined); const showUnexpectedErrorMessage = ref(false); + const fail = (error: any) => { - unexpectedError.value = error; + unexpectedError.value = error instanceof Error ? error : new Error(String(error)); showUnexpectedErrorMessage.value = true; }; + + async function loadData() { if (!activeProjectId.value) return; try { @@ -422,7 +628,8 @@ async function openPreview(config: DownloadConfig) { try { const directory = await getDirectory(config); if (!directory) return; - const [images, existing] = await Promise.all([fetchAllImages(config), collectExistingFiles(directory)]); + const images = await fetchAllImages(config); + const existing = await collectExistingFiles(directory); preview.value = { config, images, plan: planDownload(images, config, existing, { delta: true }), directory }; } catch (error: any) { fail(error); @@ -488,6 +695,57 @@ async function startRun(config: DownloadConfig, delta: boolean) { fail(error); } } + +// ---- sync local files ---- +async function syncLocalFiles(config: DownloadConfig) { + const directory = await getDirectory(config); + if (!directory) return; + + syncResult.value = null; + + syncProgress.value = { + configName: config.name, + current: 0, + total: 0, + phase: "scanning", + }; + + try { + const images = await fetchAllImages(config); + + const rc = await reconcileLocalFiles( + directory, + config, + images, + (progress) => { + syncProgress.value = { + configName: config.name, + ...progress, + }; + }, + ); + + syncResult.value = { + configName: config.name, + result: rc, + syncedAt: new Date(), + }; + + showNotificationToast({ + headline: + rc.deletedCount || rc.blacklistedCount + ? `Synced: ${rc.deletedCount} deleted, ${rc.blacklistedCount} blacklisted` + : "No changes to sync", + type: rc.deletedCount || rc.blacklistedCount ? "success" : "info", + }); + } catch (error: any) { + fail(error); + } finally { + syncProgress.value = null; + } +} + +