feat(explorer): save user's view setting to server / optionally share view setting via share link (#2232)

This commit is contained in:
Aaron Liu
2025-06-05 10:00:37 +08:00
parent c13b7365b0
commit 522fcca6af
31 changed files with 704 additions and 158 deletions

File diff suppressed because one or more lines are too long

View File

@@ -300,6 +300,7 @@ var (
{Name: "downloads", Type: field.TypeInt, Default: 0},
{Name: "expires", Type: field.TypeTime, Nullable: true, SchemaType: map[string]string{"mysql": "datetime"}},
{Name: "remain_downloads", Type: field.TypeInt, Nullable: true},
{Name: "props", Type: field.TypeJSON, Nullable: true},
{Name: "file_shares", Type: field.TypeInt, Nullable: true},
{Name: "user_shares", Type: field.TypeInt, Nullable: true},
}
@@ -311,13 +312,13 @@ var (
ForeignKeys: []*schema.ForeignKey{
{
Symbol: "shares_files_shares",
Columns: []*schema.Column{SharesColumns[9]},
Columns: []*schema.Column{SharesColumns[10]},
RefColumns: []*schema.Column{FilesColumns[0]},
OnDelete: schema.SetNull,
},
{
Symbol: "shares_users_shares",
Columns: []*schema.Column{SharesColumns[10]},
Columns: []*schema.Column{SharesColumns[11]},
RefColumns: []*schema.Column{UsersColumns[0]},
OnDelete: schema.SetNull,
},

View File

@@ -8958,6 +8958,7 @@ type ShareMutation struct {
expires *time.Time
remain_downloads *int
addremain_downloads *int
props **types.ShareProps
clearedFields map[string]struct{}
user *int
cleareduser bool
@@ -9467,6 +9468,55 @@ func (m *ShareMutation) ResetRemainDownloads() {
delete(m.clearedFields, share.FieldRemainDownloads)
}
// SetProps sets the "props" field.
func (m *ShareMutation) SetProps(tp *types.ShareProps) {
m.props = &tp
}
// Props returns the value of the "props" field in the mutation.
func (m *ShareMutation) Props() (r *types.ShareProps, exists bool) {
v := m.props
if v == nil {
return
}
return *v, true
}
// OldProps returns the old "props" field's value of the Share entity.
// If the Share 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 *ShareMutation) OldProps(ctx context.Context) (v *types.ShareProps, err error) {
if !m.op.Is(OpUpdateOne) {
return v, errors.New("OldProps is only allowed on UpdateOne operations")
}
if m.id == nil || m.oldValue == nil {
return v, errors.New("OldProps requires an ID field in the mutation")
}
oldValue, err := m.oldValue(ctx)
if err != nil {
return v, fmt.Errorf("querying old value for OldProps: %w", err)
}
return oldValue.Props, nil
}
// ClearProps clears the value of the "props" field.
func (m *ShareMutation) ClearProps() {
m.props = nil
m.clearedFields[share.FieldProps] = struct{}{}
}
// PropsCleared returns if the "props" field was cleared in this mutation.
func (m *ShareMutation) PropsCleared() bool {
_, ok := m.clearedFields[share.FieldProps]
return ok
}
// ResetProps resets all changes to the "props" field.
func (m *ShareMutation) ResetProps() {
m.props = nil
delete(m.clearedFields, share.FieldProps)
}
// SetUserID sets the "user" edge to the User entity by id.
func (m *ShareMutation) SetUserID(id int) {
m.user = &id
@@ -9579,7 +9629,7 @@ func (m *ShareMutation) Type() string {
// order to get all numeric fields that were incremented/decremented, call
// AddedFields().
func (m *ShareMutation) Fields() []string {
fields := make([]string, 0, 8)
fields := make([]string, 0, 9)
if m.created_at != nil {
fields = append(fields, share.FieldCreatedAt)
}
@@ -9604,6 +9654,9 @@ func (m *ShareMutation) Fields() []string {
if m.remain_downloads != nil {
fields = append(fields, share.FieldRemainDownloads)
}
if m.props != nil {
fields = append(fields, share.FieldProps)
}
return fields
}
@@ -9628,6 +9681,8 @@ func (m *ShareMutation) Field(name string) (ent.Value, bool) {
return m.Expires()
case share.FieldRemainDownloads:
return m.RemainDownloads()
case share.FieldProps:
return m.Props()
}
return nil, false
}
@@ -9653,6 +9708,8 @@ func (m *ShareMutation) OldField(ctx context.Context, name string) (ent.Value, e
return m.OldExpires(ctx)
case share.FieldRemainDownloads:
return m.OldRemainDownloads(ctx)
case share.FieldProps:
return m.OldProps(ctx)
}
return nil, fmt.Errorf("unknown Share field %s", name)
}
@@ -9718,6 +9775,13 @@ func (m *ShareMutation) SetField(name string, value ent.Value) error {
}
m.SetRemainDownloads(v)
return nil
case share.FieldProps:
v, ok := value.(*types.ShareProps)
if !ok {
return fmt.Errorf("unexpected type %T for field %s", value, name)
}
m.SetProps(v)
return nil
}
return fmt.Errorf("unknown Share field %s", name)
}
@@ -9799,6 +9863,9 @@ func (m *ShareMutation) ClearedFields() []string {
if m.FieldCleared(share.FieldRemainDownloads) {
fields = append(fields, share.FieldRemainDownloads)
}
if m.FieldCleared(share.FieldProps) {
fields = append(fields, share.FieldProps)
}
return fields
}
@@ -9825,6 +9892,9 @@ func (m *ShareMutation) ClearField(name string) error {
case share.FieldRemainDownloads:
m.ClearRemainDownloads()
return nil
case share.FieldProps:
m.ClearProps()
return nil
}
return fmt.Errorf("unknown Share nullable field %s", name)
}
@@ -9857,6 +9927,9 @@ func (m *ShareMutation) ResetField(name string) error {
case share.FieldRemainDownloads:
m.ResetRemainDownloads()
return nil
case share.FieldProps:
m.ResetProps()
return nil
}
return fmt.Errorf("unknown Share field %s", name)
}

View File

@@ -5,6 +5,7 @@ import (
"entgo.io/ent/dialect"
"entgo.io/ent/schema/edge"
"entgo.io/ent/schema/field"
"github.com/cloudreve/Cloudreve/v4/inventory/types"
)
// Share holds the schema definition for the Share entity.
@@ -30,6 +31,7 @@ func (Share) Fields() []ent.Field {
field.Int("remain_downloads").
Nillable().
Optional(),
field.JSON("props", &types.ShareProps{}).Optional(),
}
}

View File

@@ -3,6 +3,7 @@
package ent
import (
"encoding/json"
"fmt"
"strings"
"time"
@@ -12,6 +13,7 @@ import (
"github.com/cloudreve/Cloudreve/v4/ent/file"
"github.com/cloudreve/Cloudreve/v4/ent/share"
"github.com/cloudreve/Cloudreve/v4/ent/user"
"github.com/cloudreve/Cloudreve/v4/inventory/types"
)
// Share is the model entity for the Share schema.
@@ -35,6 +37,8 @@ type Share struct {
Expires *time.Time `json:"expires,omitempty"`
// RemainDownloads holds the value of the "remain_downloads" field.
RemainDownloads *int `json:"remain_downloads,omitempty"`
// Props holds the value of the "props" field.
Props *types.ShareProps `json:"props,omitempty"`
// Edges holds the relations/edges for other nodes in the graph.
// The values are being populated by the ShareQuery when eager-loading is set.
Edges ShareEdges `json:"edges"`
@@ -85,6 +89,8 @@ func (*Share) scanValues(columns []string) ([]any, error) {
values := make([]any, len(columns))
for i := range columns {
switch columns[i] {
case share.FieldProps:
values[i] = new([]byte)
case share.FieldID, share.FieldViews, share.FieldDownloads, share.FieldRemainDownloads:
values[i] = new(sql.NullInt64)
case share.FieldPassword:
@@ -167,6 +173,14 @@ func (s *Share) assignValues(columns []string, values []any) error {
s.RemainDownloads = new(int)
*s.RemainDownloads = int(value.Int64)
}
case share.FieldProps:
if value, ok := values[i].(*[]byte); !ok {
return fmt.Errorf("unexpected type %T for field props", values[i])
} else if value != nil && len(*value) > 0 {
if err := json.Unmarshal(*value, &s.Props); err != nil {
return fmt.Errorf("unmarshal field props: %w", err)
}
}
case share.ForeignKeys[0]:
if value, ok := values[i].(*sql.NullInt64); !ok {
return fmt.Errorf("unexpected type %T for edge-field file_shares", value)
@@ -256,6 +270,9 @@ func (s *Share) String() string {
builder.WriteString("remain_downloads=")
builder.WriteString(fmt.Sprintf("%v", *v))
}
builder.WriteString(", ")
builder.WriteString("props=")
builder.WriteString(fmt.Sprintf("%v", s.Props))
builder.WriteByte(')')
return builder.String()
}

View File

@@ -31,6 +31,8 @@ const (
FieldExpires = "expires"
// FieldRemainDownloads holds the string denoting the remain_downloads field in the database.
FieldRemainDownloads = "remain_downloads"
// FieldProps holds the string denoting the props field in the database.
FieldProps = "props"
// EdgeUser holds the string denoting the user edge name in mutations.
EdgeUser = "user"
// EdgeFile holds the string denoting the file edge name in mutations.
@@ -64,6 +66,7 @@ var Columns = []string{
FieldDownloads,
FieldExpires,
FieldRemainDownloads,
FieldProps,
}
// ForeignKeys holds the SQL foreign-keys that are owned by the "shares"

View File

@@ -480,6 +480,16 @@ func RemainDownloadsNotNil() predicate.Share {
return predicate.Share(sql.FieldNotNull(FieldRemainDownloads))
}
// PropsIsNil applies the IsNil predicate on the "props" field.
func PropsIsNil() predicate.Share {
return predicate.Share(sql.FieldIsNull(FieldProps))
}
// PropsNotNil applies the NotNil predicate on the "props" field.
func PropsNotNil() predicate.Share {
return predicate.Share(sql.FieldNotNull(FieldProps))
}
// HasUser applies the HasEdge predicate on the "user" edge.
func HasUser() predicate.Share {
return predicate.Share(func(s *sql.Selector) {

View File

@@ -14,6 +14,7 @@ import (
"github.com/cloudreve/Cloudreve/v4/ent/file"
"github.com/cloudreve/Cloudreve/v4/ent/share"
"github.com/cloudreve/Cloudreve/v4/ent/user"
"github.com/cloudreve/Cloudreve/v4/inventory/types"
)
// ShareCreate is the builder for creating a Share entity.
@@ -136,6 +137,12 @@ func (sc *ShareCreate) SetNillableRemainDownloads(i *int) *ShareCreate {
return sc
}
// SetProps sets the "props" field.
func (sc *ShareCreate) SetProps(tp *types.ShareProps) *ShareCreate {
sc.mutation.SetProps(tp)
return sc
}
// SetUserID sets the "user" edge to the User entity by ID.
func (sc *ShareCreate) SetUserID(id int) *ShareCreate {
sc.mutation.SetUserID(id)
@@ -316,6 +323,10 @@ func (sc *ShareCreate) createSpec() (*Share, *sqlgraph.CreateSpec) {
_spec.SetField(share.FieldRemainDownloads, field.TypeInt, value)
_node.RemainDownloads = &value
}
if value, ok := sc.mutation.Props(); ok {
_spec.SetField(share.FieldProps, field.TypeJSON, value)
_node.Props = value
}
if nodes := sc.mutation.UserIDs(); len(nodes) > 0 {
edge := &sqlgraph.EdgeSpec{
Rel: sqlgraph.M2O,
@@ -528,6 +539,24 @@ func (u *ShareUpsert) ClearRemainDownloads() *ShareUpsert {
return u
}
// SetProps sets the "props" field.
func (u *ShareUpsert) SetProps(v *types.ShareProps) *ShareUpsert {
u.Set(share.FieldProps, v)
return u
}
// UpdateProps sets the "props" field to the value that was provided on create.
func (u *ShareUpsert) UpdateProps() *ShareUpsert {
u.SetExcluded(share.FieldProps)
return u
}
// ClearProps clears the value of the "props" field.
func (u *ShareUpsert) ClearProps() *ShareUpsert {
u.SetNull(share.FieldProps)
return u
}
// UpdateNewValues updates the mutable fields using the new values that were set on create.
// Using this option is equivalent to using:
//
@@ -720,6 +749,27 @@ func (u *ShareUpsertOne) ClearRemainDownloads() *ShareUpsertOne {
})
}
// SetProps sets the "props" field.
func (u *ShareUpsertOne) SetProps(v *types.ShareProps) *ShareUpsertOne {
return u.Update(func(s *ShareUpsert) {
s.SetProps(v)
})
}
// UpdateProps sets the "props" field to the value that was provided on create.
func (u *ShareUpsertOne) UpdateProps() *ShareUpsertOne {
return u.Update(func(s *ShareUpsert) {
s.UpdateProps()
})
}
// ClearProps clears the value of the "props" field.
func (u *ShareUpsertOne) ClearProps() *ShareUpsertOne {
return u.Update(func(s *ShareUpsert) {
s.ClearProps()
})
}
// Exec executes the query.
func (u *ShareUpsertOne) Exec(ctx context.Context) error {
if len(u.create.conflict) == 0 {
@@ -1083,6 +1133,27 @@ func (u *ShareUpsertBulk) ClearRemainDownloads() *ShareUpsertBulk {
})
}
// SetProps sets the "props" field.
func (u *ShareUpsertBulk) SetProps(v *types.ShareProps) *ShareUpsertBulk {
return u.Update(func(s *ShareUpsert) {
s.SetProps(v)
})
}
// UpdateProps sets the "props" field to the value that was provided on create.
func (u *ShareUpsertBulk) UpdateProps() *ShareUpsertBulk {
return u.Update(func(s *ShareUpsert) {
s.UpdateProps()
})
}
// ClearProps clears the value of the "props" field.
func (u *ShareUpsertBulk) ClearProps() *ShareUpsertBulk {
return u.Update(func(s *ShareUpsert) {
s.ClearProps()
})
}
// Exec executes the query.
func (u *ShareUpsertBulk) Exec(ctx context.Context) error {
if u.create.err != nil {

View File

@@ -15,6 +15,7 @@ import (
"github.com/cloudreve/Cloudreve/v4/ent/predicate"
"github.com/cloudreve/Cloudreve/v4/ent/share"
"github.com/cloudreve/Cloudreve/v4/ent/user"
"github.com/cloudreve/Cloudreve/v4/inventory/types"
)
// ShareUpdate is the builder for updating Share entities.
@@ -165,6 +166,18 @@ func (su *ShareUpdate) ClearRemainDownloads() *ShareUpdate {
return su
}
// SetProps sets the "props" field.
func (su *ShareUpdate) SetProps(tp *types.ShareProps) *ShareUpdate {
su.mutation.SetProps(tp)
return su
}
// ClearProps clears the value of the "props" field.
func (su *ShareUpdate) ClearProps() *ShareUpdate {
su.mutation.ClearProps()
return su
}
// SetUserID sets the "user" edge to the User entity by ID.
func (su *ShareUpdate) SetUserID(id int) *ShareUpdate {
su.mutation.SetUserID(id)
@@ -313,6 +326,12 @@ func (su *ShareUpdate) sqlSave(ctx context.Context) (n int, err error) {
if su.mutation.RemainDownloadsCleared() {
_spec.ClearField(share.FieldRemainDownloads, field.TypeInt)
}
if value, ok := su.mutation.Props(); ok {
_spec.SetField(share.FieldProps, field.TypeJSON, value)
}
if su.mutation.PropsCleared() {
_spec.ClearField(share.FieldProps, field.TypeJSON)
}
if su.mutation.UserCleared() {
edge := &sqlgraph.EdgeSpec{
Rel: sqlgraph.M2O,
@@ -526,6 +545,18 @@ func (suo *ShareUpdateOne) ClearRemainDownloads() *ShareUpdateOne {
return suo
}
// SetProps sets the "props" field.
func (suo *ShareUpdateOne) SetProps(tp *types.ShareProps) *ShareUpdateOne {
suo.mutation.SetProps(tp)
return suo
}
// ClearProps clears the value of the "props" field.
func (suo *ShareUpdateOne) ClearProps() *ShareUpdateOne {
suo.mutation.ClearProps()
return suo
}
// SetUserID sets the "user" edge to the User entity by ID.
func (suo *ShareUpdateOne) SetUserID(id int) *ShareUpdateOne {
suo.mutation.SetUserID(id)
@@ -704,6 +735,12 @@ func (suo *ShareUpdateOne) sqlSave(ctx context.Context) (_node *Share, err error
if suo.mutation.RemainDownloadsCleared() {
_spec.ClearField(share.FieldRemainDownloads, field.TypeInt)
}
if value, ok := suo.mutation.Props(); ok {
_spec.SetField(share.FieldProps, field.TypeJSON, value)
}
if suo.mutation.PropsCleared() {
_spec.ClearField(share.FieldProps, field.TypeJSON)
}
if suo.mutation.UserCleared() {
edge := &sqlgraph.EdgeSpec{
Rel: sqlgraph.M2O,