From 1f50f52d2994a4441b682628c2689f56f2799ea3 Mon Sep 17 00:00:00 2001 From: Barnaby Keene Date: Fri, 14 Jul 2023 22:58:47 +0100 Subject: [PATCH 1/5] define a more robust asset schema and expose media assets on thread/post APIs --- api/openapi.yaml | 42 +- app/resources/asset/dto.go | 21 + app/resources/post/db.go | 3 + app/resources/post/dto.go | 3 + app/resources/post/repo.go | 6 + app/resources/thread/dto.go | 3 + app/services/asset/service.go | 103 ++ app/transports/openapi/bindings/assets.go | 35 +- app/transports/openapi/bindings/utils.go | 13 + internal/ent/asset.go | 196 ++++ internal/ent/asset/asset.go | 76 ++ internal/ent/asset/where.go | 511 ++++++++++ internal/ent/asset_create.go | 877 ++++++++++++++++++ internal/ent/asset_delete.go | 88 ++ internal/ent/asset_query.go | 625 +++++++++++++ internal/ent/asset_update.go | 456 +++++++++ internal/ent/client.go | 174 +++- internal/ent/ent.go | 2 + internal/ent/hook/hook.go | 12 + internal/ent/migrate/schema.go | 27 + internal/ent/mutation.go | 869 ++++++++++++++++- internal/ent/post.go | 18 +- internal/ent/post/post.go | 9 + internal/ent/post/where.go | 27 + internal/ent/post_create.go | 32 + internal/ent/post_query.go | 73 +- internal/ent/post_update.go | 163 ++++ internal/ent/predicate/predicate.go | 3 + internal/ent/runtime.go | 40 + internal/ent/schema/asset.go | 39 + internal/ent/schema/post.go | 2 + internal/ent/tx.go | 3 + internal/openapi/generated.go | 308 +++--- web/src/api/openapi/assets.ts | 7 +- .../api/openapi/schemas/assetUploadParams.ts | 15 + web/src/api/openapi/schemas/index.ts | 4 + web/src/api/openapi/schemas/mediaItem.ts | 14 + web/src/api/openapi/schemas/mediaItemList.ts | 10 + .../api/openapi/schemas/postCommonProps.ts | 2 + .../schemas/postIDQueryParamParameter.ts | 12 + .../openapi/schemas/threadReferenceAllOf.ts | 2 + 41 files changed, 4758 insertions(+), 167 deletions(-) create mode 100644 app/resources/asset/dto.go create mode 100644 app/services/asset/service.go create mode 100644 internal/ent/asset.go create mode 100644 internal/ent/asset/asset.go create mode 100644 internal/ent/asset/where.go create mode 100644 internal/ent/asset_create.go create mode 100644 internal/ent/asset_delete.go create mode 100644 internal/ent/asset_query.go create mode 100644 internal/ent/asset_update.go create mode 100644 internal/ent/schema/asset.go create mode 100644 web/src/api/openapi/schemas/assetUploadParams.ts create mode 100644 web/src/api/openapi/schemas/mediaItem.ts create mode 100644 web/src/api/openapi/schemas/mediaItemList.ts create mode 100644 web/src/api/openapi/schemas/postIDQueryParamParameter.ts diff --git a/api/openapi.yaml b/api/openapi.yaml index 0633fb2d6..0fa638974 100644 --- a/api/openapi.yaml +++ b/api/openapi.yaml @@ -591,6 +591,7 @@ paths: equivalent of S3's pre-signed upload URL. Files uploaded to this endpoint will be stored on the local filesystem instead of the cloud. tags: [assets] + parameters: [$ref: "#/components/parameters/PostIDQueryParam"] requestBody: { $ref: "#/components/requestBodies/AssetUpload" } responses: default: { $ref: "#/components/responses/InternalServerError" } @@ -647,6 +648,14 @@ components: schema: $ref: "#/components/schemas/Identifier" + PostIDQueryParam: + description: Unique post ID. + name: post_id + in: query + required: true + schema: + $ref: "#/components/schemas/Identifier" + OAuthProvider: description: The identifier for an OAuth2 provider such as "twitter". name: oauth_provider @@ -1635,6 +1644,7 @@ components: - category - reacts - meta + - media properties: title: type: string @@ -1663,6 +1673,7 @@ components: reacts: $ref: "#/components/schemas/ReactList" meta: { $ref: "#/components/schemas/Metadata" } + media: { $ref: "#/components/schemas/MediaItemList" } ThreadList: type: array @@ -1708,7 +1719,7 @@ components: PostCommonProps: type: object - required: [root_id, root_slug, body, author, reacts] + required: [root_id, root_slug, body, author, reacts, media] properties: root_id: { $ref: "#/components/schemas/Identifier" } root_slug: { $ref: "#/components/schemas/ThreadMark" } @@ -1717,6 +1728,7 @@ components: meta: { $ref: "#/components/schemas/Metadata" } reacts: { $ref: "#/components/schemas/ReactList" } reply_to: { $ref: "#/components/schemas/Identifier" } + media: { $ref: "#/components/schemas/MediaItemList" } PostInitialProps: type: object @@ -1770,6 +1782,17 @@ components: url: type: string + # + # 888b d888 888 d8b + # 8888b d8888 888 Y8P + # 88888b.d88888 888 + # 888Y88888P888 .d88b. .d88888 888 8888b. + # 888 Y888P 888 d8P Y8b d88" 888 888 "88b + # 888 Y8P 888 88888888 888 888 888 .d888888 + # 888 " 888 Y8b. Y88b 888 888 888 888 + # 888 888 "Y8888 "Y88888 888 "Y888888 + # + Asset: type: object required: [url] @@ -1777,6 +1800,23 @@ components: url: type: string + MediaItemList: + type: array + items: { $ref: "#/components/schemas/MediaItem" } + + MediaItem: + type: object + required: [url, mime_type, width, height] + properties: + url: + type: string + mime_type: + type: string + width: + type: number + height: + type: number + securitySchemes: browser: type: apiKey diff --git a/app/resources/asset/dto.go b/app/resources/asset/dto.go new file mode 100644 index 000000000..46b8dac66 --- /dev/null +++ b/app/resources/asset/dto.go @@ -0,0 +1,21 @@ +package asset + +import ( + "github.com/Southclaws/storyden/internal/ent" +) + +type Asset struct { + URL string + MIMEType string + Width int + Height int +} + +func FromModel(a *ent.Asset) *Asset { + return &Asset{ + URL: a.URL, + MIMEType: a.Mimetype, + Width: a.Width, + Height: a.Height, + } +} diff --git a/app/resources/post/db.go b/app/resources/post/db.go index 38e7b1ce6..c0d465c2b 100644 --- a/app/resources/post/db.go +++ b/app/resources/post/db.go @@ -79,6 +79,7 @@ func (d *database) Create( WithRoot(func(pq *ent.PostQuery) { pq.WithAuthor() }). + WithAssets(). Only(ctx) if err != nil { if ent.IsNotFound(err) { @@ -99,6 +100,7 @@ func (d *database) Get(ctx context.Context, id PostID) (*Post, error) { WithRoot(func(pq *ent.PostQuery) { pq.WithAuthor() }). + WithAssets(). Only(ctx) if err != nil { return nil, fault.Wrap(err, fctx.With(ctx), ftag.With(ftag.Internal)) @@ -127,6 +129,7 @@ func (d *database) Update(ctx context.Context, id PostID, opts ...Option) (*Post WithRoot(func(pq *ent.PostQuery) { pq.WithAuthor() }). + WithAssets(). Only(ctx) if err != nil { return nil, fault.Wrap(err, fctx.With(ctx), ftag.With(ftag.Internal)) diff --git a/app/resources/post/dto.go b/app/resources/post/dto.go index 39a047bc5..7369535cf 100644 --- a/app/resources/post/dto.go +++ b/app/resources/post/dto.go @@ -9,6 +9,7 @@ import ( "github.com/rs/xid" "github.com/Southclaws/storyden/app/resources/account" + "github.com/Southclaws/storyden/app/resources/asset" "github.com/Southclaws/storyden/app/resources/react" "github.com/Southclaws/storyden/internal/ent" ) @@ -28,6 +29,7 @@ type Post struct { ReplyTo opt.Optional[PostID] Reacts []*react.React Meta map[string]any + Assets []*asset.Asset CreatedAt time.Time UpdatedAt time.Time @@ -74,6 +76,7 @@ func FromModel(m *ent.Post) (w *Post) { ReplyTo: replyTo, Reacts: dt.Map(m.Edges.Reacts, react.FromModel), Meta: m.Metadata, + Assets: dt.Map(m.Edges.Assets, asset.FromModel), CreatedAt: m.CreatedAt, UpdatedAt: m.UpdatedAt, diff --git a/app/resources/post/repo.go b/app/resources/post/repo.go index 30e490e42..16691bb38 100644 --- a/app/resources/post/repo.go +++ b/app/resources/post/repo.go @@ -47,3 +47,9 @@ func WithMeta(meta map[string]any) Option { m.SetMetadata(meta) } } + +func WithAssets(ids ...xid.ID) Option { + return func(m *ent.PostMutation) { + m.AddAssetIDs(ids...) + } +} diff --git a/app/resources/thread/dto.go b/app/resources/thread/dto.go index 8bbd79d8d..772916115 100644 --- a/app/resources/thread/dto.go +++ b/app/resources/thread/dto.go @@ -7,6 +7,7 @@ import ( "github.com/Southclaws/opt" "github.com/Southclaws/storyden/app/resources/account" + "github.com/Southclaws/storyden/app/resources/asset" "github.com/Southclaws/storyden/app/resources/category" "github.com/Southclaws/storyden/app/resources/post" "github.com/Southclaws/storyden/app/resources/react" @@ -31,6 +32,7 @@ type Thread struct { Posts []*post.Post Reacts []*react.React Meta map[string]any + Assets []*asset.Asset } func (*Thread) GetResourceName() string { return "thread" } @@ -75,5 +77,6 @@ func FromModel(m *ent.Post) *Thread { Posts: posts, Reacts: dt.Map(m.Edges.Reacts, react.FromModel), Meta: m.Metadata, + Assets: dt.Map(m.Edges.Assets, asset.FromModel), } } diff --git a/app/services/asset/service.go b/app/services/asset/service.go new file mode 100644 index 000000000..a2e0611d5 --- /dev/null +++ b/app/services/asset/service.go @@ -0,0 +1,103 @@ +package asset + +import ( + "context" + "fmt" + "io" + "path/filepath" + + "github.com/Southclaws/fault" + "github.com/Southclaws/fault/fctx" + "github.com/rs/xid" + "go.uber.org/fx" + "go.uber.org/zap" + + "github.com/Southclaws/storyden/app/resources/account" + "github.com/Southclaws/storyden/app/resources/post" + "github.com/Southclaws/storyden/app/resources/rbac" + "github.com/Southclaws/storyden/app/resources/thread" + "github.com/Southclaws/storyden/app/services/authentication" + "github.com/Southclaws/storyden/internal/config" + "github.com/Southclaws/storyden/internal/object" +) + +const assetsSubdirectory = "assets" + +type Service interface { + Upload(ctx context.Context, pid post.PostID, r io.Reader) (string, error) + Read(ctx context.Context, path string) (io.Reader, error) +} + +func Build() fx.Option { + return fx.Provide(New) +} + +type service struct { + l *zap.Logger + rbac rbac.AccessManager + + account_repo account.Repository + thread_repo thread.Repository + post_repo post.Repository + + os object.Storer + + address string +} + +func New( + l *zap.Logger, + rbac rbac.AccessManager, + + account_repo account.Repository, + thread_repo thread.Repository, + post_repo post.Repository, + + os object.Storer, + cfg config.Config, +) Service { + return &service{ + l: l.With(zap.String("service", "post")), + rbac: rbac, + account_repo: account_repo, + thread_repo: thread_repo, + post_repo: post_repo, + os: os, + address: cfg.PublicWebAddress, + } +} + +func (s *service) Upload(ctx context.Context, pid post.PostID, r io.Reader) (string, error) { + accountID, err := authentication.GetAccountID(ctx) + if err != nil { + return "", fault.Wrap(err, fctx.With(ctx)) + } + + assetID := fmt.Sprintf("%s-%s", accountID.String(), xid.New().String()) + path := filepath.Join(assetsSubdirectory, assetID) + + if err := s.os.Write(ctx, path, r); err != nil { + return "", fault.Wrap(err, fctx.With(ctx)) + } + + url := fmt.Sprintf("%s/api/v1/assets/%s", s.address, assetID) + + _, err = s.post_repo.Update(ctx, pid, post.WithAssets( + // TODO: Insert asset record and append to post. + )) + if err != nil { + return "", fault.Wrap(err, fctx.With(ctx)) + } + + return url, nil +} + +func (s *service) Read(ctx context.Context, assetID string) (io.Reader, error) { + path := filepath.Join(assetsSubdirectory, assetID) + r, err := s.os.Read(ctx, path) + if err != nil { + return nil, fault.Wrap(err, fctx.With(ctx)) + } + + return r, nil +} diff --git a/app/transports/openapi/bindings/assets.go b/app/transports/openapi/bindings/assets.go index b9bf3819c..9e533dc90 100644 --- a/app/transports/openapi/bindings/assets.go +++ b/app/transports/openapi/bindings/assets.go @@ -3,33 +3,27 @@ package bindings import ( "context" "fmt" - "path/filepath" "github.com/Southclaws/fault" "github.com/Southclaws/fault/fctx" - "github.com/rs/xid" - "github.com/Southclaws/storyden/app/services/authentication" - "github.com/Southclaws/storyden/internal/config" - "github.com/Southclaws/storyden/internal/object" + "github.com/Southclaws/storyden/app/resources/post" + "github.com/Southclaws/storyden/app/services/asset" "github.com/Southclaws/storyden/internal/openapi" ) type Assets struct { - os object.Storer - address string + a asset.Service } -func NewAssets(cfg config.Config, os object.Storer) Assets { - return Assets{os, cfg.PublicWebAddress} +func NewAssets(a asset.Service) Assets { + return Assets{a} } -const assetsSubdirectory = "assets" - func (i *Assets) AssetGetUploadURL(ctx context.Context, request openapi.AssetGetUploadURLRequestObject) (openapi.AssetGetUploadURLResponseObject, error) { // TODO: Check if S3 is available and create a pre-signed upload URL if so. - url := fmt.Sprintf("%s/api/v1/assets", i.address) + url := fmt.Sprintf("%s/api/v1/assets", "i.address") return openapi.AssetGetUploadURL200JSONResponse{ AssetGetUploadURLOKJSONResponse: openapi.AssetGetUploadURLOKJSONResponse{ @@ -39,9 +33,7 @@ func (i *Assets) AssetGetUploadURL(ctx context.Context, request openapi.AssetGet } func (i *Assets) AssetGet(ctx context.Context, request openapi.AssetGetRequestObject) (openapi.AssetGetResponseObject, error) { - path := filepath.Join(assetsSubdirectory, request.Id) - - r, err := i.os.Read(ctx, path) + r, err := i.a.Read(ctx, request.Id) if err != nil { return nil, fault.Wrap(err, fctx.With(ctx)) } @@ -54,20 +46,13 @@ func (i *Assets) AssetGet(ctx context.Context, request openapi.AssetGetRequestOb } func (i *Assets) AssetUpload(ctx context.Context, request openapi.AssetUploadRequestObject) (openapi.AssetUploadResponseObject, error) { - accountID, err := authentication.GetAccountID(ctx) - if err != nil { - return nil, fault.Wrap(err, fctx.With(ctx)) - } - - assetID := fmt.Sprintf("%s-%s", accountID.String(), xid.New().String()) - path := filepath.Join(assetsSubdirectory, assetID) + postID := openapi.ParseID(request.Params.PostId) - if err := i.os.Write(ctx, path, request.Body); err != nil { + url, err := i.a.Upload(ctx, post.PostID(postID), request.Body) + if err != nil { return nil, fault.Wrap(err, fctx.With(ctx)) } - url := fmt.Sprintf("%s/api/v1/assets/%s", i.address, assetID) - return openapi.AssetUpload200JSONResponse{ AssetUploadOKJSONResponse: openapi.AssetUploadOKJSONResponse{ Url: url, diff --git a/app/transports/openapi/bindings/utils.go b/app/transports/openapi/bindings/utils.go index 2b4f94efd..bba9a8ce5 100644 --- a/app/transports/openapi/bindings/utils.go +++ b/app/transports/openapi/bindings/utils.go @@ -6,6 +6,7 @@ import ( "github.com/rs/xid" "github.com/Southclaws/storyden/app/resources/account" + "github.com/Southclaws/storyden/app/resources/asset" "github.com/Southclaws/storyden/app/resources/category" "github.com/Southclaws/storyden/app/resources/post" "github.com/Southclaws/storyden/app/resources/react" @@ -69,6 +70,7 @@ func serialiseThread(t *thread.Thread) (*openapi.Thread, error) { Posts: posts, Title: t.Title, UpdatedAt: t.UpdatedAt, + Media: dt.Map(t.Assets, serialiseAssetReference), }, nil } @@ -83,6 +85,8 @@ func serialisePost(p *post.Post) (openapi.PostProps, error) { Body: p.Body, Author: serialiseProfileReference(p.Author), Reacts: dt.Map(p.Reacts, serialiseReact), + Meta: (*openapi.Metadata)(&p.Meta), + Media: dt.Map(p.Assets, serialiseAssetReference), }, nil } @@ -124,6 +128,15 @@ func serialiseReact(r *react.React) openapi.React { } } +func serialiseAssetReference(a *asset.Asset) openapi.MediaItem { + return openapi.MediaItem{ + Url: a.URL, + MimeType: a.MIMEType, + Width: float32(a.Width), + Height: float32(a.Height), + } +} + func serialiseTag(t tag.Tag) openapi.Tag { return openapi.Tag{ Id: openapi.Identifier(t.ID), diff --git a/internal/ent/asset.go b/internal/ent/asset.go new file mode 100644 index 000000000..7d644095d --- /dev/null +++ b/internal/ent/asset.go @@ -0,0 +1,196 @@ +// Code generated by ent, DO NOT EDIT. + +package ent + +import ( + "fmt" + "strings" + "time" + + "entgo.io/ent/dialect/sql" + "github.com/Southclaws/storyden/internal/ent/asset" + "github.com/Southclaws/storyden/internal/ent/post" + "github.com/rs/xid" +) + +// Asset is the model entity for the Asset schema. +type Asset struct { + config `json:"-"` + // ID of the ent. + ID xid.ID `json:"id,omitempty"` + // CreatedAt holds the value of the "created_at" field. + CreatedAt time.Time `json:"created_at,omitempty"` + // UpdatedAt holds the value of the "updated_at" field. + UpdatedAt time.Time `json:"updated_at,omitempty"` + // URL holds the value of the "url" field. + URL string `json:"url,omitempty"` + // Mimetype holds the value of the "mimetype" field. + Mimetype string `json:"mimetype,omitempty"` + // Width holds the value of the "width" field. + Width int `json:"width,omitempty"` + // Height holds the value of the "height" field. + Height int `json:"height,omitempty"` + // PostID holds the value of the "post_id" field. + PostID xid.ID `json:"post_id,omitempty"` + // Edges holds the relations/edges for other nodes in the graph. + // The values are being populated by the AssetQuery when eager-loading is set. + Edges AssetEdges `json:"edges"` +} + +// AssetEdges holds the relations/edges for other nodes in the graph. +type AssetEdges struct { + // Post holds the value of the post edge. + Post *Post `json:"post,omitempty"` + // loadedTypes holds the information for reporting if a + // type was loaded (or requested) in eager-loading or not. + loadedTypes [1]bool +} + +// PostOrErr returns the Post value or an error if the edge +// was not loaded in eager-loading, or loaded but was not found. +func (e AssetEdges) PostOrErr() (*Post, error) { + if e.loadedTypes[0] { + if e.Post == nil { + // Edge was loaded but was not found. + return nil, &NotFoundError{label: post.Label} + } + return e.Post, nil + } + return nil, &NotLoadedError{edge: "post"} +} + +// scanValues returns the types for scanning values from sql.Rows. +func (*Asset) scanValues(columns []string) ([]any, error) { + values := make([]any, len(columns)) + for i := range columns { + switch columns[i] { + case asset.FieldWidth, asset.FieldHeight: + values[i] = new(sql.NullInt64) + case asset.FieldURL, asset.FieldMimetype: + values[i] = new(sql.NullString) + case asset.FieldCreatedAt, asset.FieldUpdatedAt: + values[i] = new(sql.NullTime) + case asset.FieldID, asset.FieldPostID: + values[i] = new(xid.ID) + default: + return nil, fmt.Errorf("unexpected column %q for type Asset", columns[i]) + } + } + return values, nil +} + +// assignValues assigns the values that were returned from sql.Rows (after scanning) +// to the Asset fields. +func (a *Asset) assignValues(columns []string, values []any) error { + if m, n := len(values), len(columns); m < n { + return fmt.Errorf("mismatch number of scan values: %d != %d", m, n) + } + for i := range columns { + switch columns[i] { + case asset.FieldID: + if value, ok := values[i].(*xid.ID); !ok { + return fmt.Errorf("unexpected type %T for field id", values[i]) + } else if value != nil { + a.ID = *value + } + case asset.FieldCreatedAt: + if value, ok := values[i].(*sql.NullTime); !ok { + return fmt.Errorf("unexpected type %T for field created_at", values[i]) + } else if value.Valid { + a.CreatedAt = value.Time + } + case asset.FieldUpdatedAt: + if value, ok := values[i].(*sql.NullTime); !ok { + return fmt.Errorf("unexpected type %T for field updated_at", values[i]) + } else if value.Valid { + a.UpdatedAt = value.Time + } + case asset.FieldURL: + if value, ok := values[i].(*sql.NullString); !ok { + return fmt.Errorf("unexpected type %T for field url", values[i]) + } else if value.Valid { + a.URL = value.String + } + case asset.FieldMimetype: + if value, ok := values[i].(*sql.NullString); !ok { + return fmt.Errorf("unexpected type %T for field mimetype", values[i]) + } else if value.Valid { + a.Mimetype = value.String + } + case asset.FieldWidth: + if value, ok := values[i].(*sql.NullInt64); !ok { + return fmt.Errorf("unexpected type %T for field width", values[i]) + } else if value.Valid { + a.Width = int(value.Int64) + } + case asset.FieldHeight: + if value, ok := values[i].(*sql.NullInt64); !ok { + return fmt.Errorf("unexpected type %T for field height", values[i]) + } else if value.Valid { + a.Height = int(value.Int64) + } + case asset.FieldPostID: + if value, ok := values[i].(*xid.ID); !ok { + return fmt.Errorf("unexpected type %T for field post_id", values[i]) + } else if value != nil { + a.PostID = *value + } + } + } + return nil +} + +// QueryPost queries the "post" edge of the Asset entity. +func (a *Asset) QueryPost() *PostQuery { + return NewAssetClient(a.config).QueryPost(a) +} + +// Update returns a builder for updating this Asset. +// Note that you need to call Asset.Unwrap() before calling this method if this Asset +// was returned from a transaction, and the transaction was committed or rolled back. +func (a *Asset) Update() *AssetUpdateOne { + return NewAssetClient(a.config).UpdateOne(a) +} + +// Unwrap unwraps the Asset entity that was returned from a transaction after it was closed, +// so that all future queries will be executed through the driver which created the transaction. +func (a *Asset) Unwrap() *Asset { + _tx, ok := a.config.driver.(*txDriver) + if !ok { + panic("ent: Asset is not a transactional entity") + } + a.config.driver = _tx.drv + return a +} + +// String implements the fmt.Stringer. +func (a *Asset) String() string { + var builder strings.Builder + builder.WriteString("Asset(") + builder.WriteString(fmt.Sprintf("id=%v, ", a.ID)) + builder.WriteString("created_at=") + builder.WriteString(a.CreatedAt.Format(time.ANSIC)) + builder.WriteString(", ") + builder.WriteString("updated_at=") + builder.WriteString(a.UpdatedAt.Format(time.ANSIC)) + builder.WriteString(", ") + builder.WriteString("url=") + builder.WriteString(a.URL) + builder.WriteString(", ") + builder.WriteString("mimetype=") + builder.WriteString(a.Mimetype) + builder.WriteString(", ") + builder.WriteString("width=") + builder.WriteString(fmt.Sprintf("%v", a.Width)) + builder.WriteString(", ") + builder.WriteString("height=") + builder.WriteString(fmt.Sprintf("%v", a.Height)) + builder.WriteString(", ") + builder.WriteString("post_id=") + builder.WriteString(fmt.Sprintf("%v", a.PostID)) + builder.WriteByte(')') + return builder.String() +} + +// Assets is a parsable slice of Asset. +type Assets []*Asset diff --git a/internal/ent/asset/asset.go b/internal/ent/asset/asset.go new file mode 100644 index 000000000..c599c329d --- /dev/null +++ b/internal/ent/asset/asset.go @@ -0,0 +1,76 @@ +// Code generated by ent, DO NOT EDIT. + +package asset + +import ( + "time" + + "github.com/rs/xid" +) + +const ( + // Label holds the string label denoting the asset type in the database. + Label = "asset" + // FieldID holds the string denoting the id field in the database. + FieldID = "id" + // FieldCreatedAt holds the string denoting the created_at field in the database. + FieldCreatedAt = "created_at" + // FieldUpdatedAt holds the string denoting the updated_at field in the database. + FieldUpdatedAt = "updated_at" + // FieldURL holds the string denoting the url field in the database. + FieldURL = "url" + // FieldMimetype holds the string denoting the mimetype field in the database. + FieldMimetype = "mimetype" + // FieldWidth holds the string denoting the width field in the database. + FieldWidth = "width" + // FieldHeight holds the string denoting the height field in the database. + FieldHeight = "height" + // FieldPostID holds the string denoting the post_id field in the database. + FieldPostID = "post_id" + // EdgePost holds the string denoting the post edge name in mutations. + EdgePost = "post" + // Table holds the table name of the asset in the database. + Table = "assets" + // PostTable is the table that holds the post relation/edge. + PostTable = "assets" + // PostInverseTable is the table name for the Post entity. + // It exists in this package in order to avoid circular dependency with the "post" package. + PostInverseTable = "posts" + // PostColumn is the table column denoting the post relation/edge. + PostColumn = "post_id" +) + +// Columns holds all SQL columns for asset fields. +var Columns = []string{ + FieldID, + FieldCreatedAt, + FieldUpdatedAt, + FieldURL, + FieldMimetype, + FieldWidth, + FieldHeight, + FieldPostID, +} + +// ValidColumn reports if the column name is valid (part of the table columns). +func ValidColumn(column string) bool { + for i := range Columns { + if column == Columns[i] { + return true + } + } + return false +} + +var ( + // DefaultCreatedAt holds the default value on creation for the "created_at" field. + DefaultCreatedAt func() time.Time + // DefaultUpdatedAt holds the default value on creation for the "updated_at" field. + DefaultUpdatedAt func() time.Time + // UpdateDefaultUpdatedAt holds the default value on update for the "updated_at" field. + UpdateDefaultUpdatedAt func() time.Time + // DefaultID holds the default value on creation for the "id" field. + DefaultID func() xid.ID + // IDValidator is a validator for the "id" field. It is called by the builders before save. + IDValidator func(string) error +) diff --git a/internal/ent/asset/where.go b/internal/ent/asset/where.go new file mode 100644 index 000000000..5a13696a4 --- /dev/null +++ b/internal/ent/asset/where.go @@ -0,0 +1,511 @@ +// Code generated by ent, DO NOT EDIT. + +package asset + +import ( + "time" + + "entgo.io/ent/dialect/sql" + "entgo.io/ent/dialect/sql/sqlgraph" + "github.com/Southclaws/storyden/internal/ent/predicate" + "github.com/rs/xid" +) + +// ID filters vertices based on their ID field. +func ID(id xid.ID) predicate.Asset { + return predicate.Asset(sql.FieldEQ(FieldID, id)) +} + +// IDEQ applies the EQ predicate on the ID field. +func IDEQ(id xid.ID) predicate.Asset { + return predicate.Asset(sql.FieldEQ(FieldID, id)) +} + +// IDNEQ applies the NEQ predicate on the ID field. +func IDNEQ(id xid.ID) predicate.Asset { + return predicate.Asset(sql.FieldNEQ(FieldID, id)) +} + +// IDIn applies the In predicate on the ID field. +func IDIn(ids ...xid.ID) predicate.Asset { + return predicate.Asset(sql.FieldIn(FieldID, ids...)) +} + +// IDNotIn applies the NotIn predicate on the ID field. +func IDNotIn(ids ...xid.ID) predicate.Asset { + return predicate.Asset(sql.FieldNotIn(FieldID, ids...)) +} + +// IDGT applies the GT predicate on the ID field. +func IDGT(id xid.ID) predicate.Asset { + return predicate.Asset(sql.FieldGT(FieldID, id)) +} + +// IDGTE applies the GTE predicate on the ID field. +func IDGTE(id xid.ID) predicate.Asset { + return predicate.Asset(sql.FieldGTE(FieldID, id)) +} + +// IDLT applies the LT predicate on the ID field. +func IDLT(id xid.ID) predicate.Asset { + return predicate.Asset(sql.FieldLT(FieldID, id)) +} + +// IDLTE applies the LTE predicate on the ID field. +func IDLTE(id xid.ID) predicate.Asset { + return predicate.Asset(sql.FieldLTE(FieldID, id)) +} + +// CreatedAt applies equality check predicate on the "created_at" field. It's identical to CreatedAtEQ. +func CreatedAt(v time.Time) predicate.Asset { + return predicate.Asset(sql.FieldEQ(FieldCreatedAt, v)) +} + +// UpdatedAt applies equality check predicate on the "updated_at" field. It's identical to UpdatedAtEQ. +func UpdatedAt(v time.Time) predicate.Asset { + return predicate.Asset(sql.FieldEQ(FieldUpdatedAt, v)) +} + +// URL applies equality check predicate on the "url" field. It's identical to URLEQ. +func URL(v string) predicate.Asset { + return predicate.Asset(sql.FieldEQ(FieldURL, v)) +} + +// Mimetype applies equality check predicate on the "mimetype" field. It's identical to MimetypeEQ. +func Mimetype(v string) predicate.Asset { + return predicate.Asset(sql.FieldEQ(FieldMimetype, v)) +} + +// Width applies equality check predicate on the "width" field. It's identical to WidthEQ. +func Width(v int) predicate.Asset { + return predicate.Asset(sql.FieldEQ(FieldWidth, v)) +} + +// Height applies equality check predicate on the "height" field. It's identical to HeightEQ. +func Height(v int) predicate.Asset { + return predicate.Asset(sql.FieldEQ(FieldHeight, v)) +} + +// PostID applies equality check predicate on the "post_id" field. It's identical to PostIDEQ. +func PostID(v xid.ID) predicate.Asset { + return predicate.Asset(sql.FieldEQ(FieldPostID, v)) +} + +// CreatedAtEQ applies the EQ predicate on the "created_at" field. +func CreatedAtEQ(v time.Time) predicate.Asset { + return predicate.Asset(sql.FieldEQ(FieldCreatedAt, v)) +} + +// CreatedAtNEQ applies the NEQ predicate on the "created_at" field. +func CreatedAtNEQ(v time.Time) predicate.Asset { + return predicate.Asset(sql.FieldNEQ(FieldCreatedAt, v)) +} + +// CreatedAtIn applies the In predicate on the "created_at" field. +func CreatedAtIn(vs ...time.Time) predicate.Asset { + return predicate.Asset(sql.FieldIn(FieldCreatedAt, vs...)) +} + +// CreatedAtNotIn applies the NotIn predicate on the "created_at" field. +func CreatedAtNotIn(vs ...time.Time) predicate.Asset { + return predicate.Asset(sql.FieldNotIn(FieldCreatedAt, vs...)) +} + +// CreatedAtGT applies the GT predicate on the "created_at" field. +func CreatedAtGT(v time.Time) predicate.Asset { + return predicate.Asset(sql.FieldGT(FieldCreatedAt, v)) +} + +// CreatedAtGTE applies the GTE predicate on the "created_at" field. +func CreatedAtGTE(v time.Time) predicate.Asset { + return predicate.Asset(sql.FieldGTE(FieldCreatedAt, v)) +} + +// CreatedAtLT applies the LT predicate on the "created_at" field. +func CreatedAtLT(v time.Time) predicate.Asset { + return predicate.Asset(sql.FieldLT(FieldCreatedAt, v)) +} + +// CreatedAtLTE applies the LTE predicate on the "created_at" field. +func CreatedAtLTE(v time.Time) predicate.Asset { + return predicate.Asset(sql.FieldLTE(FieldCreatedAt, v)) +} + +// UpdatedAtEQ applies the EQ predicate on the "updated_at" field. +func UpdatedAtEQ(v time.Time) predicate.Asset { + return predicate.Asset(sql.FieldEQ(FieldUpdatedAt, v)) +} + +// UpdatedAtNEQ applies the NEQ predicate on the "updated_at" field. +func UpdatedAtNEQ(v time.Time) predicate.Asset { + return predicate.Asset(sql.FieldNEQ(FieldUpdatedAt, v)) +} + +// UpdatedAtIn applies the In predicate on the "updated_at" field. +func UpdatedAtIn(vs ...time.Time) predicate.Asset { + return predicate.Asset(sql.FieldIn(FieldUpdatedAt, vs...)) +} + +// UpdatedAtNotIn applies the NotIn predicate on the "updated_at" field. +func UpdatedAtNotIn(vs ...time.Time) predicate.Asset { + return predicate.Asset(sql.FieldNotIn(FieldUpdatedAt, vs...)) +} + +// UpdatedAtGT applies the GT predicate on the "updated_at" field. +func UpdatedAtGT(v time.Time) predicate.Asset { + return predicate.Asset(sql.FieldGT(FieldUpdatedAt, v)) +} + +// UpdatedAtGTE applies the GTE predicate on the "updated_at" field. +func UpdatedAtGTE(v time.Time) predicate.Asset { + return predicate.Asset(sql.FieldGTE(FieldUpdatedAt, v)) +} + +// UpdatedAtLT applies the LT predicate on the "updated_at" field. +func UpdatedAtLT(v time.Time) predicate.Asset { + return predicate.Asset(sql.FieldLT(FieldUpdatedAt, v)) +} + +// UpdatedAtLTE applies the LTE predicate on the "updated_at" field. +func UpdatedAtLTE(v time.Time) predicate.Asset { + return predicate.Asset(sql.FieldLTE(FieldUpdatedAt, v)) +} + +// URLEQ applies the EQ predicate on the "url" field. +func URLEQ(v string) predicate.Asset { + return predicate.Asset(sql.FieldEQ(FieldURL, v)) +} + +// URLNEQ applies the NEQ predicate on the "url" field. +func URLNEQ(v string) predicate.Asset { + return predicate.Asset(sql.FieldNEQ(FieldURL, v)) +} + +// URLIn applies the In predicate on the "url" field. +func URLIn(vs ...string) predicate.Asset { + return predicate.Asset(sql.FieldIn(FieldURL, vs...)) +} + +// URLNotIn applies the NotIn predicate on the "url" field. +func URLNotIn(vs ...string) predicate.Asset { + return predicate.Asset(sql.FieldNotIn(FieldURL, vs...)) +} + +// URLGT applies the GT predicate on the "url" field. +func URLGT(v string) predicate.Asset { + return predicate.Asset(sql.FieldGT(FieldURL, v)) +} + +// URLGTE applies the GTE predicate on the "url" field. +func URLGTE(v string) predicate.Asset { + return predicate.Asset(sql.FieldGTE(FieldURL, v)) +} + +// URLLT applies the LT predicate on the "url" field. +func URLLT(v string) predicate.Asset { + return predicate.Asset(sql.FieldLT(FieldURL, v)) +} + +// URLLTE applies the LTE predicate on the "url" field. +func URLLTE(v string) predicate.Asset { + return predicate.Asset(sql.FieldLTE(FieldURL, v)) +} + +// URLContains applies the Contains predicate on the "url" field. +func URLContains(v string) predicate.Asset { + return predicate.Asset(sql.FieldContains(FieldURL, v)) +} + +// URLHasPrefix applies the HasPrefix predicate on the "url" field. +func URLHasPrefix(v string) predicate.Asset { + return predicate.Asset(sql.FieldHasPrefix(FieldURL, v)) +} + +// URLHasSuffix applies the HasSuffix predicate on the "url" field. +func URLHasSuffix(v string) predicate.Asset { + return predicate.Asset(sql.FieldHasSuffix(FieldURL, v)) +} + +// URLEqualFold applies the EqualFold predicate on the "url" field. +func URLEqualFold(v string) predicate.Asset { + return predicate.Asset(sql.FieldEqualFold(FieldURL, v)) +} + +// URLContainsFold applies the ContainsFold predicate on the "url" field. +func URLContainsFold(v string) predicate.Asset { + return predicate.Asset(sql.FieldContainsFold(FieldURL, v)) +} + +// MimetypeEQ applies the EQ predicate on the "mimetype" field. +func MimetypeEQ(v string) predicate.Asset { + return predicate.Asset(sql.FieldEQ(FieldMimetype, v)) +} + +// MimetypeNEQ applies the NEQ predicate on the "mimetype" field. +func MimetypeNEQ(v string) predicate.Asset { + return predicate.Asset(sql.FieldNEQ(FieldMimetype, v)) +} + +// MimetypeIn applies the In predicate on the "mimetype" field. +func MimetypeIn(vs ...string) predicate.Asset { + return predicate.Asset(sql.FieldIn(FieldMimetype, vs...)) +} + +// MimetypeNotIn applies the NotIn predicate on the "mimetype" field. +func MimetypeNotIn(vs ...string) predicate.Asset { + return predicate.Asset(sql.FieldNotIn(FieldMimetype, vs...)) +} + +// MimetypeGT applies the GT predicate on the "mimetype" field. +func MimetypeGT(v string) predicate.Asset { + return predicate.Asset(sql.FieldGT(FieldMimetype, v)) +} + +// MimetypeGTE applies the GTE predicate on the "mimetype" field. +func MimetypeGTE(v string) predicate.Asset { + return predicate.Asset(sql.FieldGTE(FieldMimetype, v)) +} + +// MimetypeLT applies the LT predicate on the "mimetype" field. +func MimetypeLT(v string) predicate.Asset { + return predicate.Asset(sql.FieldLT(FieldMimetype, v)) +} + +// MimetypeLTE applies the LTE predicate on the "mimetype" field. +func MimetypeLTE(v string) predicate.Asset { + return predicate.Asset(sql.FieldLTE(FieldMimetype, v)) +} + +// MimetypeContains applies the Contains predicate on the "mimetype" field. +func MimetypeContains(v string) predicate.Asset { + return predicate.Asset(sql.FieldContains(FieldMimetype, v)) +} + +// MimetypeHasPrefix applies the HasPrefix predicate on the "mimetype" field. +func MimetypeHasPrefix(v string) predicate.Asset { + return predicate.Asset(sql.FieldHasPrefix(FieldMimetype, v)) +} + +// MimetypeHasSuffix applies the HasSuffix predicate on the "mimetype" field. +func MimetypeHasSuffix(v string) predicate.Asset { + return predicate.Asset(sql.FieldHasSuffix(FieldMimetype, v)) +} + +// MimetypeEqualFold applies the EqualFold predicate on the "mimetype" field. +func MimetypeEqualFold(v string) predicate.Asset { + return predicate.Asset(sql.FieldEqualFold(FieldMimetype, v)) +} + +// MimetypeContainsFold applies the ContainsFold predicate on the "mimetype" field. +func MimetypeContainsFold(v string) predicate.Asset { + return predicate.Asset(sql.FieldContainsFold(FieldMimetype, v)) +} + +// WidthEQ applies the EQ predicate on the "width" field. +func WidthEQ(v int) predicate.Asset { + return predicate.Asset(sql.FieldEQ(FieldWidth, v)) +} + +// WidthNEQ applies the NEQ predicate on the "width" field. +func WidthNEQ(v int) predicate.Asset { + return predicate.Asset(sql.FieldNEQ(FieldWidth, v)) +} + +// WidthIn applies the In predicate on the "width" field. +func WidthIn(vs ...int) predicate.Asset { + return predicate.Asset(sql.FieldIn(FieldWidth, vs...)) +} + +// WidthNotIn applies the NotIn predicate on the "width" field. +func WidthNotIn(vs ...int) predicate.Asset { + return predicate.Asset(sql.FieldNotIn(FieldWidth, vs...)) +} + +// WidthGT applies the GT predicate on the "width" field. +func WidthGT(v int) predicate.Asset { + return predicate.Asset(sql.FieldGT(FieldWidth, v)) +} + +// WidthGTE applies the GTE predicate on the "width" field. +func WidthGTE(v int) predicate.Asset { + return predicate.Asset(sql.FieldGTE(FieldWidth, v)) +} + +// WidthLT applies the LT predicate on the "width" field. +func WidthLT(v int) predicate.Asset { + return predicate.Asset(sql.FieldLT(FieldWidth, v)) +} + +// WidthLTE applies the LTE predicate on the "width" field. +func WidthLTE(v int) predicate.Asset { + return predicate.Asset(sql.FieldLTE(FieldWidth, v)) +} + +// HeightEQ applies the EQ predicate on the "height" field. +func HeightEQ(v int) predicate.Asset { + return predicate.Asset(sql.FieldEQ(FieldHeight, v)) +} + +// HeightNEQ applies the NEQ predicate on the "height" field. +func HeightNEQ(v int) predicate.Asset { + return predicate.Asset(sql.FieldNEQ(FieldHeight, v)) +} + +// HeightIn applies the In predicate on the "height" field. +func HeightIn(vs ...int) predicate.Asset { + return predicate.Asset(sql.FieldIn(FieldHeight, vs...)) +} + +// HeightNotIn applies the NotIn predicate on the "height" field. +func HeightNotIn(vs ...int) predicate.Asset { + return predicate.Asset(sql.FieldNotIn(FieldHeight, vs...)) +} + +// HeightGT applies the GT predicate on the "height" field. +func HeightGT(v int) predicate.Asset { + return predicate.Asset(sql.FieldGT(FieldHeight, v)) +} + +// HeightGTE applies the GTE predicate on the "height" field. +func HeightGTE(v int) predicate.Asset { + return predicate.Asset(sql.FieldGTE(FieldHeight, v)) +} + +// HeightLT applies the LT predicate on the "height" field. +func HeightLT(v int) predicate.Asset { + return predicate.Asset(sql.FieldLT(FieldHeight, v)) +} + +// HeightLTE applies the LTE predicate on the "height" field. +func HeightLTE(v int) predicate.Asset { + return predicate.Asset(sql.FieldLTE(FieldHeight, v)) +} + +// PostIDEQ applies the EQ predicate on the "post_id" field. +func PostIDEQ(v xid.ID) predicate.Asset { + return predicate.Asset(sql.FieldEQ(FieldPostID, v)) +} + +// PostIDNEQ applies the NEQ predicate on the "post_id" field. +func PostIDNEQ(v xid.ID) predicate.Asset { + return predicate.Asset(sql.FieldNEQ(FieldPostID, v)) +} + +// PostIDIn applies the In predicate on the "post_id" field. +func PostIDIn(vs ...xid.ID) predicate.Asset { + return predicate.Asset(sql.FieldIn(FieldPostID, vs...)) +} + +// PostIDNotIn applies the NotIn predicate on the "post_id" field. +func PostIDNotIn(vs ...xid.ID) predicate.Asset { + return predicate.Asset(sql.FieldNotIn(FieldPostID, vs...)) +} + +// PostIDGT applies the GT predicate on the "post_id" field. +func PostIDGT(v xid.ID) predicate.Asset { + return predicate.Asset(sql.FieldGT(FieldPostID, v)) +} + +// PostIDGTE applies the GTE predicate on the "post_id" field. +func PostIDGTE(v xid.ID) predicate.Asset { + return predicate.Asset(sql.FieldGTE(FieldPostID, v)) +} + +// PostIDLT applies the LT predicate on the "post_id" field. +func PostIDLT(v xid.ID) predicate.Asset { + return predicate.Asset(sql.FieldLT(FieldPostID, v)) +} + +// PostIDLTE applies the LTE predicate on the "post_id" field. +func PostIDLTE(v xid.ID) predicate.Asset { + return predicate.Asset(sql.FieldLTE(FieldPostID, v)) +} + +// PostIDContains applies the Contains predicate on the "post_id" field. +func PostIDContains(v xid.ID) predicate.Asset { + vc := v.String() + return predicate.Asset(sql.FieldContains(FieldPostID, vc)) +} + +// PostIDHasPrefix applies the HasPrefix predicate on the "post_id" field. +func PostIDHasPrefix(v xid.ID) predicate.Asset { + vc := v.String() + return predicate.Asset(sql.FieldHasPrefix(FieldPostID, vc)) +} + +// PostIDHasSuffix applies the HasSuffix predicate on the "post_id" field. +func PostIDHasSuffix(v xid.ID) predicate.Asset { + vc := v.String() + return predicate.Asset(sql.FieldHasSuffix(FieldPostID, vc)) +} + +// PostIDEqualFold applies the EqualFold predicate on the "post_id" field. +func PostIDEqualFold(v xid.ID) predicate.Asset { + vc := v.String() + return predicate.Asset(sql.FieldEqualFold(FieldPostID, vc)) +} + +// PostIDContainsFold applies the ContainsFold predicate on the "post_id" field. +func PostIDContainsFold(v xid.ID) predicate.Asset { + vc := v.String() + return predicate.Asset(sql.FieldContainsFold(FieldPostID, vc)) +} + +// HasPost applies the HasEdge predicate on the "post" edge. +func HasPost() predicate.Asset { + return predicate.Asset(func(s *sql.Selector) { + step := sqlgraph.NewStep( + sqlgraph.From(Table, FieldID), + sqlgraph.Edge(sqlgraph.M2O, true, PostTable, PostColumn), + ) + sqlgraph.HasNeighbors(s, step) + }) +} + +// HasPostWith applies the HasEdge predicate on the "post" edge with a given conditions (other predicates). +func HasPostWith(preds ...predicate.Post) predicate.Asset { + return predicate.Asset(func(s *sql.Selector) { + step := sqlgraph.NewStep( + sqlgraph.From(Table, FieldID), + sqlgraph.To(PostInverseTable, FieldID), + sqlgraph.Edge(sqlgraph.M2O, true, PostTable, PostColumn), + ) + sqlgraph.HasNeighborsWith(s, step, func(s *sql.Selector) { + for _, p := range preds { + p(s) + } + }) + }) +} + +// And groups predicates with the AND operator between them. +func And(predicates ...predicate.Asset) predicate.Asset { + return predicate.Asset(func(s *sql.Selector) { + s1 := s.Clone().SetP(nil) + for _, p := range predicates { + p(s1) + } + s.Where(s1.P()) + }) +} + +// Or groups predicates with the OR operator between them. +func Or(predicates ...predicate.Asset) predicate.Asset { + return predicate.Asset(func(s *sql.Selector) { + s1 := s.Clone().SetP(nil) + for i, p := range predicates { + if i > 0 { + s1.Or() + } + p(s1) + } + s.Where(s1.P()) + }) +} + +// Not applies the not operator on the given predicate. +func Not(p predicate.Asset) predicate.Asset { + return predicate.Asset(func(s *sql.Selector) { + p(s.Not()) + }) +} diff --git a/internal/ent/asset_create.go b/internal/ent/asset_create.go new file mode 100644 index 000000000..618c90bde --- /dev/null +++ b/internal/ent/asset_create.go @@ -0,0 +1,877 @@ +// Code generated by ent, DO NOT EDIT. + +package ent + +import ( + "context" + "errors" + "fmt" + "time" + + "entgo.io/ent/dialect" + "entgo.io/ent/dialect/sql" + "entgo.io/ent/dialect/sql/sqlgraph" + "entgo.io/ent/schema/field" + "github.com/Southclaws/storyden/internal/ent/asset" + "github.com/Southclaws/storyden/internal/ent/post" + "github.com/rs/xid" +) + +// AssetCreate is the builder for creating a Asset entity. +type AssetCreate struct { + config + mutation *AssetMutation + hooks []Hook + conflict []sql.ConflictOption +} + +// SetCreatedAt sets the "created_at" field. +func (ac *AssetCreate) SetCreatedAt(t time.Time) *AssetCreate { + ac.mutation.SetCreatedAt(t) + return ac +} + +// SetNillableCreatedAt sets the "created_at" field if the given value is not nil. +func (ac *AssetCreate) SetNillableCreatedAt(t *time.Time) *AssetCreate { + if t != nil { + ac.SetCreatedAt(*t) + } + return ac +} + +// SetUpdatedAt sets the "updated_at" field. +func (ac *AssetCreate) SetUpdatedAt(t time.Time) *AssetCreate { + ac.mutation.SetUpdatedAt(t) + return ac +} + +// SetNillableUpdatedAt sets the "updated_at" field if the given value is not nil. +func (ac *AssetCreate) SetNillableUpdatedAt(t *time.Time) *AssetCreate { + if t != nil { + ac.SetUpdatedAt(*t) + } + return ac +} + +// SetURL sets the "url" field. +func (ac *AssetCreate) SetURL(s string) *AssetCreate { + ac.mutation.SetURL(s) + return ac +} + +// SetMimetype sets the "mimetype" field. +func (ac *AssetCreate) SetMimetype(s string) *AssetCreate { + ac.mutation.SetMimetype(s) + return ac +} + +// SetWidth sets the "width" field. +func (ac *AssetCreate) SetWidth(i int) *AssetCreate { + ac.mutation.SetWidth(i) + return ac +} + +// SetHeight sets the "height" field. +func (ac *AssetCreate) SetHeight(i int) *AssetCreate { + ac.mutation.SetHeight(i) + return ac +} + +// SetPostID sets the "post_id" field. +func (ac *AssetCreate) SetPostID(x xid.ID) *AssetCreate { + ac.mutation.SetPostID(x) + return ac +} + +// SetID sets the "id" field. +func (ac *AssetCreate) SetID(x xid.ID) *AssetCreate { + ac.mutation.SetID(x) + return ac +} + +// SetNillableID sets the "id" field if the given value is not nil. +func (ac *AssetCreate) SetNillableID(x *xid.ID) *AssetCreate { + if x != nil { + ac.SetID(*x) + } + return ac +} + +// SetPost sets the "post" edge to the Post entity. +func (ac *AssetCreate) SetPost(p *Post) *AssetCreate { + return ac.SetPostID(p.ID) +} + +// Mutation returns the AssetMutation object of the builder. +func (ac *AssetCreate) Mutation() *AssetMutation { + return ac.mutation +} + +// Save creates the Asset in the database. +func (ac *AssetCreate) Save(ctx context.Context) (*Asset, error) { + ac.defaults() + return withHooks[*Asset, AssetMutation](ctx, ac.sqlSave, ac.mutation, ac.hooks) +} + +// SaveX calls Save and panics if Save returns an error. +func (ac *AssetCreate) SaveX(ctx context.Context) *Asset { + v, err := ac.Save(ctx) + if err != nil { + panic(err) + } + return v +} + +// Exec executes the query. +func (ac *AssetCreate) Exec(ctx context.Context) error { + _, err := ac.Save(ctx) + return err +} + +// ExecX is like Exec, but panics if an error occurs. +func (ac *AssetCreate) ExecX(ctx context.Context) { + if err := ac.Exec(ctx); err != nil { + panic(err) + } +} + +// defaults sets the default values of the builder before save. +func (ac *AssetCreate) defaults() { + if _, ok := ac.mutation.CreatedAt(); !ok { + v := asset.DefaultCreatedAt() + ac.mutation.SetCreatedAt(v) + } + if _, ok := ac.mutation.UpdatedAt(); !ok { + v := asset.DefaultUpdatedAt() + ac.mutation.SetUpdatedAt(v) + } + if _, ok := ac.mutation.ID(); !ok { + v := asset.DefaultID() + ac.mutation.SetID(v) + } +} + +// check runs all checks and user-defined validators on the builder. +func (ac *AssetCreate) check() error { + if _, ok := ac.mutation.CreatedAt(); !ok { + return &ValidationError{Name: "created_at", err: errors.New(`ent: missing required field "Asset.created_at"`)} + } + if _, ok := ac.mutation.UpdatedAt(); !ok { + return &ValidationError{Name: "updated_at", err: errors.New(`ent: missing required field "Asset.updated_at"`)} + } + if _, ok := ac.mutation.URL(); !ok { + return &ValidationError{Name: "url", err: errors.New(`ent: missing required field "Asset.url"`)} + } + if _, ok := ac.mutation.Mimetype(); !ok { + return &ValidationError{Name: "mimetype", err: errors.New(`ent: missing required field "Asset.mimetype"`)} + } + if _, ok := ac.mutation.Width(); !ok { + return &ValidationError{Name: "width", err: errors.New(`ent: missing required field "Asset.width"`)} + } + if _, ok := ac.mutation.Height(); !ok { + return &ValidationError{Name: "height", err: errors.New(`ent: missing required field "Asset.height"`)} + } + if _, ok := ac.mutation.PostID(); !ok { + return &ValidationError{Name: "post_id", err: errors.New(`ent: missing required field "Asset.post_id"`)} + } + if v, ok := ac.mutation.ID(); ok { + if err := asset.IDValidator(v.String()); err != nil { + return &ValidationError{Name: "id", err: fmt.Errorf(`ent: validator failed for field "Asset.id": %w`, err)} + } + } + if _, ok := ac.mutation.PostID(); !ok { + return &ValidationError{Name: "post", err: errors.New(`ent: missing required edge "Asset.post"`)} + } + return nil +} + +func (ac *AssetCreate) sqlSave(ctx context.Context) (*Asset, error) { + if err := ac.check(); err != nil { + return nil, err + } + _node, _spec := ac.createSpec() + if err := sqlgraph.CreateNode(ctx, ac.driver, _spec); err != nil { + if sqlgraph.IsConstraintError(err) { + err = &ConstraintError{msg: err.Error(), wrap: err} + } + return nil, err + } + if _spec.ID.Value != nil { + if id, ok := _spec.ID.Value.(*xid.ID); ok { + _node.ID = *id + } else if err := _node.ID.Scan(_spec.ID.Value); err != nil { + return nil, err + } + } + ac.mutation.id = &_node.ID + ac.mutation.done = true + return _node, nil +} + +func (ac *AssetCreate) createSpec() (*Asset, *sqlgraph.CreateSpec) { + var ( + _node = &Asset{config: ac.config} + _spec = sqlgraph.NewCreateSpec(asset.Table, sqlgraph.NewFieldSpec(asset.FieldID, field.TypeString)) + ) + _spec.OnConflict = ac.conflict + if id, ok := ac.mutation.ID(); ok { + _node.ID = id + _spec.ID.Value = &id + } + if value, ok := ac.mutation.CreatedAt(); ok { + _spec.SetField(asset.FieldCreatedAt, field.TypeTime, value) + _node.CreatedAt = value + } + if value, ok := ac.mutation.UpdatedAt(); ok { + _spec.SetField(asset.FieldUpdatedAt, field.TypeTime, value) + _node.UpdatedAt = value + } + if value, ok := ac.mutation.URL(); ok { + _spec.SetField(asset.FieldURL, field.TypeString, value) + _node.URL = value + } + if value, ok := ac.mutation.Mimetype(); ok { + _spec.SetField(asset.FieldMimetype, field.TypeString, value) + _node.Mimetype = value + } + if value, ok := ac.mutation.Width(); ok { + _spec.SetField(asset.FieldWidth, field.TypeInt, value) + _node.Width = value + } + if value, ok := ac.mutation.Height(); ok { + _spec.SetField(asset.FieldHeight, field.TypeInt, value) + _node.Height = value + } + if nodes := ac.mutation.PostIDs(); len(nodes) > 0 { + edge := &sqlgraph.EdgeSpec{ + Rel: sqlgraph.M2O, + Inverse: true, + Table: asset.PostTable, + Columns: []string{asset.PostColumn}, + Bidi: false, + Target: &sqlgraph.EdgeTarget{ + IDSpec: sqlgraph.NewFieldSpec(post.FieldID, field.TypeString), + }, + } + for _, k := range nodes { + edge.Target.Nodes = append(edge.Target.Nodes, k) + } + _node.PostID = nodes[0] + _spec.Edges = append(_spec.Edges, edge) + } + return _node, _spec +} + +// OnConflict allows configuring the `ON CONFLICT` / `ON DUPLICATE KEY` clause +// of the `INSERT` statement. For example: +// +// client.Asset.Create(). +// SetCreatedAt(v). +// OnConflict( +// // Update the row with the new values +// // the was proposed for insertion. +// sql.ResolveWithNewValues(), +// ). +// // Override some of the fields with custom +// // update values. +// Update(func(u *ent.AssetUpsert) { +// SetCreatedAt(v+v). +// }). +// Exec(ctx) +func (ac *AssetCreate) OnConflict(opts ...sql.ConflictOption) *AssetUpsertOne { + ac.conflict = opts + return &AssetUpsertOne{ + create: ac, + } +} + +// OnConflictColumns calls `OnConflict` and configures the columns +// as conflict target. Using this option is equivalent to using: +// +// client.Asset.Create(). +// OnConflict(sql.ConflictColumns(columns...)). +// Exec(ctx) +func (ac *AssetCreate) OnConflictColumns(columns ...string) *AssetUpsertOne { + ac.conflict = append(ac.conflict, sql.ConflictColumns(columns...)) + return &AssetUpsertOne{ + create: ac, + } +} + +type ( + // AssetUpsertOne is the builder for "upsert"-ing + // one Asset node. + AssetUpsertOne struct { + create *AssetCreate + } + + // AssetUpsert is the "OnConflict" setter. + AssetUpsert struct { + *sql.UpdateSet + } +) + +// SetUpdatedAt sets the "updated_at" field. +func (u *AssetUpsert) SetUpdatedAt(v time.Time) *AssetUpsert { + u.Set(asset.FieldUpdatedAt, v) + return u +} + +// UpdateUpdatedAt sets the "updated_at" field to the value that was provided on create. +func (u *AssetUpsert) UpdateUpdatedAt() *AssetUpsert { + u.SetExcluded(asset.FieldUpdatedAt) + return u +} + +// SetURL sets the "url" field. +func (u *AssetUpsert) SetURL(v string) *AssetUpsert { + u.Set(asset.FieldURL, v) + return u +} + +// UpdateURL sets the "url" field to the value that was provided on create. +func (u *AssetUpsert) UpdateURL() *AssetUpsert { + u.SetExcluded(asset.FieldURL) + return u +} + +// SetMimetype sets the "mimetype" field. +func (u *AssetUpsert) SetMimetype(v string) *AssetUpsert { + u.Set(asset.FieldMimetype, v) + return u +} + +// UpdateMimetype sets the "mimetype" field to the value that was provided on create. +func (u *AssetUpsert) UpdateMimetype() *AssetUpsert { + u.SetExcluded(asset.FieldMimetype) + return u +} + +// SetWidth sets the "width" field. +func (u *AssetUpsert) SetWidth(v int) *AssetUpsert { + u.Set(asset.FieldWidth, v) + return u +} + +// UpdateWidth sets the "width" field to the value that was provided on create. +func (u *AssetUpsert) UpdateWidth() *AssetUpsert { + u.SetExcluded(asset.FieldWidth) + return u +} + +// AddWidth adds v to the "width" field. +func (u *AssetUpsert) AddWidth(v int) *AssetUpsert { + u.Add(asset.FieldWidth, v) + return u +} + +// SetHeight sets the "height" field. +func (u *AssetUpsert) SetHeight(v int) *AssetUpsert { + u.Set(asset.FieldHeight, v) + return u +} + +// UpdateHeight sets the "height" field to the value that was provided on create. +func (u *AssetUpsert) UpdateHeight() *AssetUpsert { + u.SetExcluded(asset.FieldHeight) + return u +} + +// AddHeight adds v to the "height" field. +func (u *AssetUpsert) AddHeight(v int) *AssetUpsert { + u.Add(asset.FieldHeight, v) + return u +} + +// SetPostID sets the "post_id" field. +func (u *AssetUpsert) SetPostID(v xid.ID) *AssetUpsert { + u.Set(asset.FieldPostID, v) + return u +} + +// UpdatePostID sets the "post_id" field to the value that was provided on create. +func (u *AssetUpsert) UpdatePostID() *AssetUpsert { + u.SetExcluded(asset.FieldPostID) + return u +} + +// UpdateNewValues updates the mutable fields using the new values that were set on create except the ID field. +// Using this option is equivalent to using: +// +// client.Asset.Create(). +// OnConflict( +// sql.ResolveWithNewValues(), +// sql.ResolveWith(func(u *sql.UpdateSet) { +// u.SetIgnore(asset.FieldID) +// }), +// ). +// Exec(ctx) +func (u *AssetUpsertOne) UpdateNewValues() *AssetUpsertOne { + u.create.conflict = append(u.create.conflict, sql.ResolveWithNewValues()) + u.create.conflict = append(u.create.conflict, sql.ResolveWith(func(s *sql.UpdateSet) { + if _, exists := u.create.mutation.ID(); exists { + s.SetIgnore(asset.FieldID) + } + if _, exists := u.create.mutation.CreatedAt(); exists { + s.SetIgnore(asset.FieldCreatedAt) + } + })) + return u +} + +// Ignore sets each column to itself in case of conflict. +// Using this option is equivalent to using: +// +// client.Asset.Create(). +// OnConflict(sql.ResolveWithIgnore()). +// Exec(ctx) +func (u *AssetUpsertOne) Ignore() *AssetUpsertOne { + u.create.conflict = append(u.create.conflict, sql.ResolveWithIgnore()) + return u +} + +// DoNothing configures the conflict_action to `DO NOTHING`. +// Supported only by SQLite and PostgreSQL. +func (u *AssetUpsertOne) DoNothing() *AssetUpsertOne { + u.create.conflict = append(u.create.conflict, sql.DoNothing()) + return u +} + +// Update allows overriding fields `UPDATE` values. See the AssetCreate.OnConflict +// documentation for more info. +func (u *AssetUpsertOne) Update(set func(*AssetUpsert)) *AssetUpsertOne { + u.create.conflict = append(u.create.conflict, sql.ResolveWith(func(update *sql.UpdateSet) { + set(&AssetUpsert{UpdateSet: update}) + })) + return u +} + +// SetUpdatedAt sets the "updated_at" field. +func (u *AssetUpsertOne) SetUpdatedAt(v time.Time) *AssetUpsertOne { + return u.Update(func(s *AssetUpsert) { + s.SetUpdatedAt(v) + }) +} + +// UpdateUpdatedAt sets the "updated_at" field to the value that was provided on create. +func (u *AssetUpsertOne) UpdateUpdatedAt() *AssetUpsertOne { + return u.Update(func(s *AssetUpsert) { + s.UpdateUpdatedAt() + }) +} + +// SetURL sets the "url" field. +func (u *AssetUpsertOne) SetURL(v string) *AssetUpsertOne { + return u.Update(func(s *AssetUpsert) { + s.SetURL(v) + }) +} + +// UpdateURL sets the "url" field to the value that was provided on create. +func (u *AssetUpsertOne) UpdateURL() *AssetUpsertOne { + return u.Update(func(s *AssetUpsert) { + s.UpdateURL() + }) +} + +// SetMimetype sets the "mimetype" field. +func (u *AssetUpsertOne) SetMimetype(v string) *AssetUpsertOne { + return u.Update(func(s *AssetUpsert) { + s.SetMimetype(v) + }) +} + +// UpdateMimetype sets the "mimetype" field to the value that was provided on create. +func (u *AssetUpsertOne) UpdateMimetype() *AssetUpsertOne { + return u.Update(func(s *AssetUpsert) { + s.UpdateMimetype() + }) +} + +// SetWidth sets the "width" field. +func (u *AssetUpsertOne) SetWidth(v int) *AssetUpsertOne { + return u.Update(func(s *AssetUpsert) { + s.SetWidth(v) + }) +} + +// AddWidth adds v to the "width" field. +func (u *AssetUpsertOne) AddWidth(v int) *AssetUpsertOne { + return u.Update(func(s *AssetUpsert) { + s.AddWidth(v) + }) +} + +// UpdateWidth sets the "width" field to the value that was provided on create. +func (u *AssetUpsertOne) UpdateWidth() *AssetUpsertOne { + return u.Update(func(s *AssetUpsert) { + s.UpdateWidth() + }) +} + +// SetHeight sets the "height" field. +func (u *AssetUpsertOne) SetHeight(v int) *AssetUpsertOne { + return u.Update(func(s *AssetUpsert) { + s.SetHeight(v) + }) +} + +// AddHeight adds v to the "height" field. +func (u *AssetUpsertOne) AddHeight(v int) *AssetUpsertOne { + return u.Update(func(s *AssetUpsert) { + s.AddHeight(v) + }) +} + +// UpdateHeight sets the "height" field to the value that was provided on create. +func (u *AssetUpsertOne) UpdateHeight() *AssetUpsertOne { + return u.Update(func(s *AssetUpsert) { + s.UpdateHeight() + }) +} + +// SetPostID sets the "post_id" field. +func (u *AssetUpsertOne) SetPostID(v xid.ID) *AssetUpsertOne { + return u.Update(func(s *AssetUpsert) { + s.SetPostID(v) + }) +} + +// UpdatePostID sets the "post_id" field to the value that was provided on create. +func (u *AssetUpsertOne) UpdatePostID() *AssetUpsertOne { + return u.Update(func(s *AssetUpsert) { + s.UpdatePostID() + }) +} + +// Exec executes the query. +func (u *AssetUpsertOne) Exec(ctx context.Context) error { + if len(u.create.conflict) == 0 { + return errors.New("ent: missing options for AssetCreate.OnConflict") + } + return u.create.Exec(ctx) +} + +// ExecX is like Exec, but panics if an error occurs. +func (u *AssetUpsertOne) ExecX(ctx context.Context) { + if err := u.create.Exec(ctx); err != nil { + panic(err) + } +} + +// Exec executes the UPSERT query and returns the inserted/updated ID. +func (u *AssetUpsertOne) ID(ctx context.Context) (id xid.ID, err error) { + if u.create.driver.Dialect() == dialect.MySQL { + // In case of "ON CONFLICT", there is no way to get back non-numeric ID + // fields from the database since MySQL does not support the RETURNING clause. + return id, errors.New("ent: AssetUpsertOne.ID is not supported by MySQL driver. Use AssetUpsertOne.Exec instead") + } + node, err := u.create.Save(ctx) + if err != nil { + return id, err + } + return node.ID, nil +} + +// IDX is like ID, but panics if an error occurs. +func (u *AssetUpsertOne) IDX(ctx context.Context) xid.ID { + id, err := u.ID(ctx) + if err != nil { + panic(err) + } + return id +} + +// AssetCreateBulk is the builder for creating many Asset entities in bulk. +type AssetCreateBulk struct { + config + builders []*AssetCreate + conflict []sql.ConflictOption +} + +// Save creates the Asset entities in the database. +func (acb *AssetCreateBulk) Save(ctx context.Context) ([]*Asset, error) { + specs := make([]*sqlgraph.CreateSpec, len(acb.builders)) + nodes := make([]*Asset, len(acb.builders)) + mutators := make([]Mutator, len(acb.builders)) + for i := range acb.builders { + func(i int, root context.Context) { + builder := acb.builders[i] + builder.defaults() + var mut Mutator = MutateFunc(func(ctx context.Context, m Mutation) (Value, error) { + mutation, ok := m.(*AssetMutation) + if !ok { + return nil, fmt.Errorf("unexpected mutation type %T", m) + } + if err := builder.check(); err != nil { + return nil, err + } + builder.mutation = mutation + nodes[i], specs[i] = builder.createSpec() + var err error + if i < len(mutators)-1 { + _, err = mutators[i+1].Mutate(root, acb.builders[i+1].mutation) + } else { + spec := &sqlgraph.BatchCreateSpec{Nodes: specs} + spec.OnConflict = acb.conflict + // Invoke the actual operation on the latest mutation in the chain. + if err = sqlgraph.BatchCreate(ctx, acb.driver, spec); err != nil { + if sqlgraph.IsConstraintError(err) { + err = &ConstraintError{msg: err.Error(), wrap: err} + } + } + } + if err != nil { + return nil, err + } + mutation.id = &nodes[i].ID + mutation.done = true + return nodes[i], nil + }) + for i := len(builder.hooks) - 1; i >= 0; i-- { + mut = builder.hooks[i](mut) + } + mutators[i] = mut + }(i, ctx) + } + if len(mutators) > 0 { + if _, err := mutators[0].Mutate(ctx, acb.builders[0].mutation); err != nil { + return nil, err + } + } + return nodes, nil +} + +// SaveX is like Save, but panics if an error occurs. +func (acb *AssetCreateBulk) SaveX(ctx context.Context) []*Asset { + v, err := acb.Save(ctx) + if err != nil { + panic(err) + } + return v +} + +// Exec executes the query. +func (acb *AssetCreateBulk) Exec(ctx context.Context) error { + _, err := acb.Save(ctx) + return err +} + +// ExecX is like Exec, but panics if an error occurs. +func (acb *AssetCreateBulk) ExecX(ctx context.Context) { + if err := acb.Exec(ctx); err != nil { + panic(err) + } +} + +// OnConflict allows configuring the `ON CONFLICT` / `ON DUPLICATE KEY` clause +// of the `INSERT` statement. For example: +// +// client.Asset.CreateBulk(builders...). +// OnConflict( +// // Update the row with the new values +// // the was proposed for insertion. +// sql.ResolveWithNewValues(), +// ). +// // Override some of the fields with custom +// // update values. +// Update(func(u *ent.AssetUpsert) { +// SetCreatedAt(v+v). +// }). +// Exec(ctx) +func (acb *AssetCreateBulk) OnConflict(opts ...sql.ConflictOption) *AssetUpsertBulk { + acb.conflict = opts + return &AssetUpsertBulk{ + create: acb, + } +} + +// OnConflictColumns calls `OnConflict` and configures the columns +// as conflict target. Using this option is equivalent to using: +// +// client.Asset.Create(). +// OnConflict(sql.ConflictColumns(columns...)). +// Exec(ctx) +func (acb *AssetCreateBulk) OnConflictColumns(columns ...string) *AssetUpsertBulk { + acb.conflict = append(acb.conflict, sql.ConflictColumns(columns...)) + return &AssetUpsertBulk{ + create: acb, + } +} + +// AssetUpsertBulk is the builder for "upsert"-ing +// a bulk of Asset nodes. +type AssetUpsertBulk struct { + create *AssetCreateBulk +} + +// UpdateNewValues updates the mutable fields using the new values that +// were set on create. Using this option is equivalent to using: +// +// client.Asset.Create(). +// OnConflict( +// sql.ResolveWithNewValues(), +// sql.ResolveWith(func(u *sql.UpdateSet) { +// u.SetIgnore(asset.FieldID) +// }), +// ). +// Exec(ctx) +func (u *AssetUpsertBulk) UpdateNewValues() *AssetUpsertBulk { + u.create.conflict = append(u.create.conflict, sql.ResolveWithNewValues()) + u.create.conflict = append(u.create.conflict, sql.ResolveWith(func(s *sql.UpdateSet) { + for _, b := range u.create.builders { + if _, exists := b.mutation.ID(); exists { + s.SetIgnore(asset.FieldID) + } + if _, exists := b.mutation.CreatedAt(); exists { + s.SetIgnore(asset.FieldCreatedAt) + } + } + })) + return u +} + +// Ignore sets each column to itself in case of conflict. +// Using this option is equivalent to using: +// +// client.Asset.Create(). +// OnConflict(sql.ResolveWithIgnore()). +// Exec(ctx) +func (u *AssetUpsertBulk) Ignore() *AssetUpsertBulk { + u.create.conflict = append(u.create.conflict, sql.ResolveWithIgnore()) + return u +} + +// DoNothing configures the conflict_action to `DO NOTHING`. +// Supported only by SQLite and PostgreSQL. +func (u *AssetUpsertBulk) DoNothing() *AssetUpsertBulk { + u.create.conflict = append(u.create.conflict, sql.DoNothing()) + return u +} + +// Update allows overriding fields `UPDATE` values. See the AssetCreateBulk.OnConflict +// documentation for more info. +func (u *AssetUpsertBulk) Update(set func(*AssetUpsert)) *AssetUpsertBulk { + u.create.conflict = append(u.create.conflict, sql.ResolveWith(func(update *sql.UpdateSet) { + set(&AssetUpsert{UpdateSet: update}) + })) + return u +} + +// SetUpdatedAt sets the "updated_at" field. +func (u *AssetUpsertBulk) SetUpdatedAt(v time.Time) *AssetUpsertBulk { + return u.Update(func(s *AssetUpsert) { + s.SetUpdatedAt(v) + }) +} + +// UpdateUpdatedAt sets the "updated_at" field to the value that was provided on create. +func (u *AssetUpsertBulk) UpdateUpdatedAt() *AssetUpsertBulk { + return u.Update(func(s *AssetUpsert) { + s.UpdateUpdatedAt() + }) +} + +// SetURL sets the "url" field. +func (u *AssetUpsertBulk) SetURL(v string) *AssetUpsertBulk { + return u.Update(func(s *AssetUpsert) { + s.SetURL(v) + }) +} + +// UpdateURL sets the "url" field to the value that was provided on create. +func (u *AssetUpsertBulk) UpdateURL() *AssetUpsertBulk { + return u.Update(func(s *AssetUpsert) { + s.UpdateURL() + }) +} + +// SetMimetype sets the "mimetype" field. +func (u *AssetUpsertBulk) SetMimetype(v string) *AssetUpsertBulk { + return u.Update(func(s *AssetUpsert) { + s.SetMimetype(v) + }) +} + +// UpdateMimetype sets the "mimetype" field to the value that was provided on create. +func (u *AssetUpsertBulk) UpdateMimetype() *AssetUpsertBulk { + return u.Update(func(s *AssetUpsert) { + s.UpdateMimetype() + }) +} + +// SetWidth sets the "width" field. +func (u *AssetUpsertBulk) SetWidth(v int) *AssetUpsertBulk { + return u.Update(func(s *AssetUpsert) { + s.SetWidth(v) + }) +} + +// AddWidth adds v to the "width" field. +func (u *AssetUpsertBulk) AddWidth(v int) *AssetUpsertBulk { + return u.Update(func(s *AssetUpsert) { + s.AddWidth(v) + }) +} + +// UpdateWidth sets the "width" field to the value that was provided on create. +func (u *AssetUpsertBulk) UpdateWidth() *AssetUpsertBulk { + return u.Update(func(s *AssetUpsert) { + s.UpdateWidth() + }) +} + +// SetHeight sets the "height" field. +func (u *AssetUpsertBulk) SetHeight(v int) *AssetUpsertBulk { + return u.Update(func(s *AssetUpsert) { + s.SetHeight(v) + }) +} + +// AddHeight adds v to the "height" field. +func (u *AssetUpsertBulk) AddHeight(v int) *AssetUpsertBulk { + return u.Update(func(s *AssetUpsert) { + s.AddHeight(v) + }) +} + +// UpdateHeight sets the "height" field to the value that was provided on create. +func (u *AssetUpsertBulk) UpdateHeight() *AssetUpsertBulk { + return u.Update(func(s *AssetUpsert) { + s.UpdateHeight() + }) +} + +// SetPostID sets the "post_id" field. +func (u *AssetUpsertBulk) SetPostID(v xid.ID) *AssetUpsertBulk { + return u.Update(func(s *AssetUpsert) { + s.SetPostID(v) + }) +} + +// UpdatePostID sets the "post_id" field to the value that was provided on create. +func (u *AssetUpsertBulk) UpdatePostID() *AssetUpsertBulk { + return u.Update(func(s *AssetUpsert) { + s.UpdatePostID() + }) +} + +// Exec executes the query. +func (u *AssetUpsertBulk) Exec(ctx context.Context) error { + for i, b := range u.create.builders { + if len(b.conflict) != 0 { + return fmt.Errorf("ent: OnConflict was set for builder %d. Set it on the AssetCreateBulk instead", i) + } + } + if len(u.create.conflict) == 0 { + return errors.New("ent: missing options for AssetCreateBulk.OnConflict") + } + return u.create.Exec(ctx) +} + +// ExecX is like Exec, but panics if an error occurs. +func (u *AssetUpsertBulk) ExecX(ctx context.Context) { + if err := u.create.Exec(ctx); err != nil { + panic(err) + } +} diff --git a/internal/ent/asset_delete.go b/internal/ent/asset_delete.go new file mode 100644 index 000000000..8fb313d85 --- /dev/null +++ b/internal/ent/asset_delete.go @@ -0,0 +1,88 @@ +// Code generated by ent, DO NOT EDIT. + +package ent + +import ( + "context" + + "entgo.io/ent/dialect/sql" + "entgo.io/ent/dialect/sql/sqlgraph" + "entgo.io/ent/schema/field" + "github.com/Southclaws/storyden/internal/ent/asset" + "github.com/Southclaws/storyden/internal/ent/predicate" +) + +// AssetDelete is the builder for deleting a Asset entity. +type AssetDelete struct { + config + hooks []Hook + mutation *AssetMutation +} + +// Where appends a list predicates to the AssetDelete builder. +func (ad *AssetDelete) Where(ps ...predicate.Asset) *AssetDelete { + ad.mutation.Where(ps...) + return ad +} + +// Exec executes the deletion query and returns how many vertices were deleted. +func (ad *AssetDelete) Exec(ctx context.Context) (int, error) { + return withHooks[int, AssetMutation](ctx, ad.sqlExec, ad.mutation, ad.hooks) +} + +// ExecX is like Exec, but panics if an error occurs. +func (ad *AssetDelete) ExecX(ctx context.Context) int { + n, err := ad.Exec(ctx) + if err != nil { + panic(err) + } + return n +} + +func (ad *AssetDelete) sqlExec(ctx context.Context) (int, error) { + _spec := sqlgraph.NewDeleteSpec(asset.Table, sqlgraph.NewFieldSpec(asset.FieldID, field.TypeString)) + if ps := ad.mutation.predicates; len(ps) > 0 { + _spec.Predicate = func(selector *sql.Selector) { + for i := range ps { + ps[i](selector) + } + } + } + affected, err := sqlgraph.DeleteNodes(ctx, ad.driver, _spec) + if err != nil && sqlgraph.IsConstraintError(err) { + err = &ConstraintError{msg: err.Error(), wrap: err} + } + ad.mutation.done = true + return affected, err +} + +// AssetDeleteOne is the builder for deleting a single Asset entity. +type AssetDeleteOne struct { + ad *AssetDelete +} + +// Where appends a list predicates to the AssetDelete builder. +func (ado *AssetDeleteOne) Where(ps ...predicate.Asset) *AssetDeleteOne { + ado.ad.mutation.Where(ps...) + return ado +} + +// Exec executes the deletion query. +func (ado *AssetDeleteOne) Exec(ctx context.Context) error { + n, err := ado.ad.Exec(ctx) + switch { + case err != nil: + return err + case n == 0: + return &NotFoundError{asset.Label} + default: + return nil + } +} + +// ExecX is like Exec, but panics if an error occurs. +func (ado *AssetDeleteOne) ExecX(ctx context.Context) { + if err := ado.Exec(ctx); err != nil { + panic(err) + } +} diff --git a/internal/ent/asset_query.go b/internal/ent/asset_query.go new file mode 100644 index 000000000..d876bc439 --- /dev/null +++ b/internal/ent/asset_query.go @@ -0,0 +1,625 @@ +// Code generated by ent, DO NOT EDIT. + +package ent + +import ( + "context" + "fmt" + "math" + + "entgo.io/ent/dialect/sql" + "entgo.io/ent/dialect/sql/sqlgraph" + "entgo.io/ent/schema/field" + "github.com/Southclaws/storyden/internal/ent/asset" + "github.com/Southclaws/storyden/internal/ent/post" + "github.com/Southclaws/storyden/internal/ent/predicate" + "github.com/rs/xid" +) + +// AssetQuery is the builder for querying Asset entities. +type AssetQuery struct { + config + ctx *QueryContext + order []OrderFunc + inters []Interceptor + predicates []predicate.Asset + withPost *PostQuery + modifiers []func(*sql.Selector) + // intermediate query (i.e. traversal path). + sql *sql.Selector + path func(context.Context) (*sql.Selector, error) +} + +// Where adds a new predicate for the AssetQuery builder. +func (aq *AssetQuery) Where(ps ...predicate.Asset) *AssetQuery { + aq.predicates = append(aq.predicates, ps...) + return aq +} + +// Limit the number of records to be returned by this query. +func (aq *AssetQuery) Limit(limit int) *AssetQuery { + aq.ctx.Limit = &limit + return aq +} + +// Offset to start from. +func (aq *AssetQuery) Offset(offset int) *AssetQuery { + aq.ctx.Offset = &offset + return aq +} + +// Unique configures the query builder to filter duplicate records on query. +// By default, unique is set to true, and can be disabled using this method. +func (aq *AssetQuery) Unique(unique bool) *AssetQuery { + aq.ctx.Unique = &unique + return aq +} + +// Order specifies how the records should be ordered. +func (aq *AssetQuery) Order(o ...OrderFunc) *AssetQuery { + aq.order = append(aq.order, o...) + return aq +} + +// QueryPost chains the current query on the "post" edge. +func (aq *AssetQuery) QueryPost() *PostQuery { + query := (&PostClient{config: aq.config}).Query() + query.path = func(ctx context.Context) (fromU *sql.Selector, err error) { + if err := aq.prepareQuery(ctx); err != nil { + return nil, err + } + selector := aq.sqlQuery(ctx) + if err := selector.Err(); err != nil { + return nil, err + } + step := sqlgraph.NewStep( + sqlgraph.From(asset.Table, asset.FieldID, selector), + sqlgraph.To(post.Table, post.FieldID), + sqlgraph.Edge(sqlgraph.M2O, true, asset.PostTable, asset.PostColumn), + ) + fromU = sqlgraph.SetNeighbors(aq.driver.Dialect(), step) + return fromU, nil + } + return query +} + +// First returns the first Asset entity from the query. +// Returns a *NotFoundError when no Asset was found. +func (aq *AssetQuery) First(ctx context.Context) (*Asset, error) { + nodes, err := aq.Limit(1).All(setContextOp(ctx, aq.ctx, "First")) + if err != nil { + return nil, err + } + if len(nodes) == 0 { + return nil, &NotFoundError{asset.Label} + } + return nodes[0], nil +} + +// FirstX is like First, but panics if an error occurs. +func (aq *AssetQuery) FirstX(ctx context.Context) *Asset { + node, err := aq.First(ctx) + if err != nil && !IsNotFound(err) { + panic(err) + } + return node +} + +// FirstID returns the first Asset ID from the query. +// Returns a *NotFoundError when no Asset ID was found. +func (aq *AssetQuery) FirstID(ctx context.Context) (id xid.ID, err error) { + var ids []xid.ID + if ids, err = aq.Limit(1).IDs(setContextOp(ctx, aq.ctx, "FirstID")); err != nil { + return + } + if len(ids) == 0 { + err = &NotFoundError{asset.Label} + return + } + return ids[0], nil +} + +// FirstIDX is like FirstID, but panics if an error occurs. +func (aq *AssetQuery) FirstIDX(ctx context.Context) xid.ID { + id, err := aq.FirstID(ctx) + if err != nil && !IsNotFound(err) { + panic(err) + } + return id +} + +// Only returns a single Asset entity found by the query, ensuring it only returns one. +// Returns a *NotSingularError when more than one Asset entity is found. +// Returns a *NotFoundError when no Asset entities are found. +func (aq *AssetQuery) Only(ctx context.Context) (*Asset, error) { + nodes, err := aq.Limit(2).All(setContextOp(ctx, aq.ctx, "Only")) + if err != nil { + return nil, err + } + switch len(nodes) { + case 1: + return nodes[0], nil + case 0: + return nil, &NotFoundError{asset.Label} + default: + return nil, &NotSingularError{asset.Label} + } +} + +// OnlyX is like Only, but panics if an error occurs. +func (aq *AssetQuery) OnlyX(ctx context.Context) *Asset { + node, err := aq.Only(ctx) + if err != nil { + panic(err) + } + return node +} + +// OnlyID is like Only, but returns the only Asset ID in the query. +// Returns a *NotSingularError when more than one Asset ID is found. +// Returns a *NotFoundError when no entities are found. +func (aq *AssetQuery) OnlyID(ctx context.Context) (id xid.ID, err error) { + var ids []xid.ID + if ids, err = aq.Limit(2).IDs(setContextOp(ctx, aq.ctx, "OnlyID")); err != nil { + return + } + switch len(ids) { + case 1: + id = ids[0] + case 0: + err = &NotFoundError{asset.Label} + default: + err = &NotSingularError{asset.Label} + } + return +} + +// OnlyIDX is like OnlyID, but panics if an error occurs. +func (aq *AssetQuery) OnlyIDX(ctx context.Context) xid.ID { + id, err := aq.OnlyID(ctx) + if err != nil { + panic(err) + } + return id +} + +// All executes the query and returns a list of Assets. +func (aq *AssetQuery) All(ctx context.Context) ([]*Asset, error) { + ctx = setContextOp(ctx, aq.ctx, "All") + if err := aq.prepareQuery(ctx); err != nil { + return nil, err + } + qr := querierAll[[]*Asset, *AssetQuery]() + return withInterceptors[[]*Asset](ctx, aq, qr, aq.inters) +} + +// AllX is like All, but panics if an error occurs. +func (aq *AssetQuery) AllX(ctx context.Context) []*Asset { + nodes, err := aq.All(ctx) + if err != nil { + panic(err) + } + return nodes +} + +// IDs executes the query and returns a list of Asset IDs. +func (aq *AssetQuery) IDs(ctx context.Context) (ids []xid.ID, err error) { + if aq.ctx.Unique == nil && aq.path != nil { + aq.Unique(true) + } + ctx = setContextOp(ctx, aq.ctx, "IDs") + if err = aq.Select(asset.FieldID).Scan(ctx, &ids); err != nil { + return nil, err + } + return ids, nil +} + +// IDsX is like IDs, but panics if an error occurs. +func (aq *AssetQuery) IDsX(ctx context.Context) []xid.ID { + ids, err := aq.IDs(ctx) + if err != nil { + panic(err) + } + return ids +} + +// Count returns the count of the given query. +func (aq *AssetQuery) Count(ctx context.Context) (int, error) { + ctx = setContextOp(ctx, aq.ctx, "Count") + if err := aq.prepareQuery(ctx); err != nil { + return 0, err + } + return withInterceptors[int](ctx, aq, querierCount[*AssetQuery](), aq.inters) +} + +// CountX is like Count, but panics if an error occurs. +func (aq *AssetQuery) CountX(ctx context.Context) int { + count, err := aq.Count(ctx) + if err != nil { + panic(err) + } + return count +} + +// Exist returns true if the query has elements in the graph. +func (aq *AssetQuery) Exist(ctx context.Context) (bool, error) { + ctx = setContextOp(ctx, aq.ctx, "Exist") + switch _, err := aq.FirstID(ctx); { + case IsNotFound(err): + return false, nil + case err != nil: + return false, fmt.Errorf("ent: check existence: %w", err) + default: + return true, nil + } +} + +// ExistX is like Exist, but panics if an error occurs. +func (aq *AssetQuery) ExistX(ctx context.Context) bool { + exist, err := aq.Exist(ctx) + if err != nil { + panic(err) + } + return exist +} + +// Clone returns a duplicate of the AssetQuery builder, including all associated steps. It can be +// used to prepare common query builders and use them differently after the clone is made. +func (aq *AssetQuery) Clone() *AssetQuery { + if aq == nil { + return nil + } + return &AssetQuery{ + config: aq.config, + ctx: aq.ctx.Clone(), + order: append([]OrderFunc{}, aq.order...), + inters: append([]Interceptor{}, aq.inters...), + predicates: append([]predicate.Asset{}, aq.predicates...), + withPost: aq.withPost.Clone(), + // clone intermediate query. + sql: aq.sql.Clone(), + path: aq.path, + } +} + +// WithPost tells the query-builder to eager-load the nodes that are connected to +// the "post" edge. The optional arguments are used to configure the query builder of the edge. +func (aq *AssetQuery) WithPost(opts ...func(*PostQuery)) *AssetQuery { + query := (&PostClient{config: aq.config}).Query() + for _, opt := range opts { + opt(query) + } + aq.withPost = query + return aq +} + +// GroupBy is used to group vertices by one or more fields/columns. +// It is often used with aggregate functions, like: count, max, mean, min, sum. +// +// Example: +// +// var v []struct { +// CreatedAt time.Time `json:"created_at,omitempty"` +// Count int `json:"count,omitempty"` +// } +// +// client.Asset.Query(). +// GroupBy(asset.FieldCreatedAt). +// Aggregate(ent.Count()). +// Scan(ctx, &v) +func (aq *AssetQuery) GroupBy(field string, fields ...string) *AssetGroupBy { + aq.ctx.Fields = append([]string{field}, fields...) + grbuild := &AssetGroupBy{build: aq} + grbuild.flds = &aq.ctx.Fields + grbuild.label = asset.Label + grbuild.scan = grbuild.Scan + return grbuild +} + +// Select allows the selection one or more fields/columns for the given query, +// instead of selecting all fields in the entity. +// +// Example: +// +// var v []struct { +// CreatedAt time.Time `json:"created_at,omitempty"` +// } +// +// client.Asset.Query(). +// Select(asset.FieldCreatedAt). +// Scan(ctx, &v) +func (aq *AssetQuery) Select(fields ...string) *AssetSelect { + aq.ctx.Fields = append(aq.ctx.Fields, fields...) + sbuild := &AssetSelect{AssetQuery: aq} + sbuild.label = asset.Label + sbuild.flds, sbuild.scan = &aq.ctx.Fields, sbuild.Scan + return sbuild +} + +// Aggregate returns a AssetSelect configured with the given aggregations. +func (aq *AssetQuery) Aggregate(fns ...AggregateFunc) *AssetSelect { + return aq.Select().Aggregate(fns...) +} + +func (aq *AssetQuery) prepareQuery(ctx context.Context) error { + for _, inter := range aq.inters { + if inter == nil { + return fmt.Errorf("ent: uninitialized interceptor (forgotten import ent/runtime?)") + } + if trv, ok := inter.(Traverser); ok { + if err := trv.Traverse(ctx, aq); err != nil { + return err + } + } + } + for _, f := range aq.ctx.Fields { + if !asset.ValidColumn(f) { + return &ValidationError{Name: f, err: fmt.Errorf("ent: invalid field %q for query", f)} + } + } + if aq.path != nil { + prev, err := aq.path(ctx) + if err != nil { + return err + } + aq.sql = prev + } + return nil +} + +func (aq *AssetQuery) sqlAll(ctx context.Context, hooks ...queryHook) ([]*Asset, error) { + var ( + nodes = []*Asset{} + _spec = aq.querySpec() + loadedTypes = [1]bool{ + aq.withPost != nil, + } + ) + _spec.ScanValues = func(columns []string) ([]any, error) { + return (*Asset).scanValues(nil, columns) + } + _spec.Assign = func(columns []string, values []any) error { + node := &Asset{config: aq.config} + nodes = append(nodes, node) + node.Edges.loadedTypes = loadedTypes + return node.assignValues(columns, values) + } + if len(aq.modifiers) > 0 { + _spec.Modifiers = aq.modifiers + } + for i := range hooks { + hooks[i](ctx, _spec) + } + if err := sqlgraph.QueryNodes(ctx, aq.driver, _spec); err != nil { + return nil, err + } + if len(nodes) == 0 { + return nodes, nil + } + if query := aq.withPost; query != nil { + if err := aq.loadPost(ctx, query, nodes, nil, + func(n *Asset, e *Post) { n.Edges.Post = e }); err != nil { + return nil, err + } + } + return nodes, nil +} + +func (aq *AssetQuery) loadPost(ctx context.Context, query *PostQuery, nodes []*Asset, init func(*Asset), assign func(*Asset, *Post)) error { + ids := make([]xid.ID, 0, len(nodes)) + nodeids := make(map[xid.ID][]*Asset) + for i := range nodes { + fk := nodes[i].PostID + if _, ok := nodeids[fk]; !ok { + ids = append(ids, fk) + } + nodeids[fk] = append(nodeids[fk], nodes[i]) + } + if len(ids) == 0 { + return nil + } + query.Where(post.IDIn(ids...)) + neighbors, err := query.All(ctx) + if err != nil { + return err + } + for _, n := range neighbors { + nodes, ok := nodeids[n.ID] + if !ok { + return fmt.Errorf(`unexpected foreign-key "post_id" returned %v`, n.ID) + } + for i := range nodes { + assign(nodes[i], n) + } + } + return nil +} + +func (aq *AssetQuery) sqlCount(ctx context.Context) (int, error) { + _spec := aq.querySpec() + if len(aq.modifiers) > 0 { + _spec.Modifiers = aq.modifiers + } + _spec.Node.Columns = aq.ctx.Fields + if len(aq.ctx.Fields) > 0 { + _spec.Unique = aq.ctx.Unique != nil && *aq.ctx.Unique + } + return sqlgraph.CountNodes(ctx, aq.driver, _spec) +} + +func (aq *AssetQuery) querySpec() *sqlgraph.QuerySpec { + _spec := sqlgraph.NewQuerySpec(asset.Table, asset.Columns, sqlgraph.NewFieldSpec(asset.FieldID, field.TypeString)) + _spec.From = aq.sql + if unique := aq.ctx.Unique; unique != nil { + _spec.Unique = *unique + } else if aq.path != nil { + _spec.Unique = true + } + if fields := aq.ctx.Fields; len(fields) > 0 { + _spec.Node.Columns = make([]string, 0, len(fields)) + _spec.Node.Columns = append(_spec.Node.Columns, asset.FieldID) + for i := range fields { + if fields[i] != asset.FieldID { + _spec.Node.Columns = append(_spec.Node.Columns, fields[i]) + } + } + } + if ps := aq.predicates; len(ps) > 0 { + _spec.Predicate = func(selector *sql.Selector) { + for i := range ps { + ps[i](selector) + } + } + } + if limit := aq.ctx.Limit; limit != nil { + _spec.Limit = *limit + } + if offset := aq.ctx.Offset; offset != nil { + _spec.Offset = *offset + } + if ps := aq.order; len(ps) > 0 { + _spec.Order = func(selector *sql.Selector) { + for i := range ps { + ps[i](selector) + } + } + } + return _spec +} + +func (aq *AssetQuery) sqlQuery(ctx context.Context) *sql.Selector { + builder := sql.Dialect(aq.driver.Dialect()) + t1 := builder.Table(asset.Table) + columns := aq.ctx.Fields + if len(columns) == 0 { + columns = asset.Columns + } + selector := builder.Select(t1.Columns(columns...)...).From(t1) + if aq.sql != nil { + selector = aq.sql + selector.Select(selector.Columns(columns...)...) + } + if aq.ctx.Unique != nil && *aq.ctx.Unique { + selector.Distinct() + } + for _, m := range aq.modifiers { + m(selector) + } + for _, p := range aq.predicates { + p(selector) + } + for _, p := range aq.order { + p(selector) + } + if offset := aq.ctx.Offset; offset != nil { + // limit is mandatory for offset clause. We start + // with default value, and override it below if needed. + selector.Offset(*offset).Limit(math.MaxInt32) + } + if limit := aq.ctx.Limit; limit != nil { + selector.Limit(*limit) + } + return selector +} + +// Modify adds a query modifier for attaching custom logic to queries. +func (aq *AssetQuery) Modify(modifiers ...func(s *sql.Selector)) *AssetSelect { + aq.modifiers = append(aq.modifiers, modifiers...) + return aq.Select() +} + +// AssetGroupBy is the group-by builder for Asset entities. +type AssetGroupBy struct { + selector + build *AssetQuery +} + +// Aggregate adds the given aggregation functions to the group-by query. +func (agb *AssetGroupBy) Aggregate(fns ...AggregateFunc) *AssetGroupBy { + agb.fns = append(agb.fns, fns...) + return agb +} + +// Scan applies the selector query and scans the result into the given value. +func (agb *AssetGroupBy) Scan(ctx context.Context, v any) error { + ctx = setContextOp(ctx, agb.build.ctx, "GroupBy") + if err := agb.build.prepareQuery(ctx); err != nil { + return err + } + return scanWithInterceptors[*AssetQuery, *AssetGroupBy](ctx, agb.build, agb, agb.build.inters, v) +} + +func (agb *AssetGroupBy) sqlScan(ctx context.Context, root *AssetQuery, v any) error { + selector := root.sqlQuery(ctx).Select() + aggregation := make([]string, 0, len(agb.fns)) + for _, fn := range agb.fns { + aggregation = append(aggregation, fn(selector)) + } + if len(selector.SelectedColumns()) == 0 { + columns := make([]string, 0, len(*agb.flds)+len(agb.fns)) + for _, f := range *agb.flds { + columns = append(columns, selector.C(f)) + } + columns = append(columns, aggregation...) + selector.Select(columns...) + } + selector.GroupBy(selector.Columns(*agb.flds...)...) + if err := selector.Err(); err != nil { + return err + } + rows := &sql.Rows{} + query, args := selector.Query() + if err := agb.build.driver.Query(ctx, query, args, rows); err != nil { + return err + } + defer rows.Close() + return sql.ScanSlice(rows, v) +} + +// AssetSelect is the builder for selecting fields of Asset entities. +type AssetSelect struct { + *AssetQuery + selector +} + +// Aggregate adds the given aggregation functions to the selector query. +func (as *AssetSelect) Aggregate(fns ...AggregateFunc) *AssetSelect { + as.fns = append(as.fns, fns...) + return as +} + +// Scan applies the selector query and scans the result into the given value. +func (as *AssetSelect) Scan(ctx context.Context, v any) error { + ctx = setContextOp(ctx, as.ctx, "Select") + if err := as.prepareQuery(ctx); err != nil { + return err + } + return scanWithInterceptors[*AssetQuery, *AssetSelect](ctx, as.AssetQuery, as, as.inters, v) +} + +func (as *AssetSelect) sqlScan(ctx context.Context, root *AssetQuery, v any) error { + selector := root.sqlQuery(ctx) + aggregation := make([]string, 0, len(as.fns)) + for _, fn := range as.fns { + aggregation = append(aggregation, fn(selector)) + } + switch n := len(*as.selector.flds); { + case n == 0 && len(aggregation) > 0: + selector.Select(aggregation...) + case n != 0 && len(aggregation) > 0: + selector.AppendSelect(aggregation...) + } + rows := &sql.Rows{} + query, args := selector.Query() + if err := as.driver.Query(ctx, query, args, rows); err != nil { + return err + } + defer rows.Close() + return sql.ScanSlice(rows, v) +} + +// Modify adds a query modifier for attaching custom logic to queries. +func (as *AssetSelect) Modify(modifiers ...func(s *sql.Selector)) *AssetSelect { + as.modifiers = append(as.modifiers, modifiers...) + return as +} diff --git a/internal/ent/asset_update.go b/internal/ent/asset_update.go new file mode 100644 index 000000000..7b087cf5f --- /dev/null +++ b/internal/ent/asset_update.go @@ -0,0 +1,456 @@ +// Code generated by ent, DO NOT EDIT. + +package ent + +import ( + "context" + "errors" + "fmt" + "time" + + "entgo.io/ent/dialect/sql" + "entgo.io/ent/dialect/sql/sqlgraph" + "entgo.io/ent/schema/field" + "github.com/Southclaws/storyden/internal/ent/asset" + "github.com/Southclaws/storyden/internal/ent/post" + "github.com/Southclaws/storyden/internal/ent/predicate" + "github.com/rs/xid" +) + +// AssetUpdate is the builder for updating Asset entities. +type AssetUpdate struct { + config + hooks []Hook + mutation *AssetMutation + modifiers []func(*sql.UpdateBuilder) +} + +// Where appends a list predicates to the AssetUpdate builder. +func (au *AssetUpdate) Where(ps ...predicate.Asset) *AssetUpdate { + au.mutation.Where(ps...) + return au +} + +// SetUpdatedAt sets the "updated_at" field. +func (au *AssetUpdate) SetUpdatedAt(t time.Time) *AssetUpdate { + au.mutation.SetUpdatedAt(t) + return au +} + +// SetURL sets the "url" field. +func (au *AssetUpdate) SetURL(s string) *AssetUpdate { + au.mutation.SetURL(s) + return au +} + +// SetMimetype sets the "mimetype" field. +func (au *AssetUpdate) SetMimetype(s string) *AssetUpdate { + au.mutation.SetMimetype(s) + return au +} + +// SetWidth sets the "width" field. +func (au *AssetUpdate) SetWidth(i int) *AssetUpdate { + au.mutation.ResetWidth() + au.mutation.SetWidth(i) + return au +} + +// AddWidth adds i to the "width" field. +func (au *AssetUpdate) AddWidth(i int) *AssetUpdate { + au.mutation.AddWidth(i) + return au +} + +// SetHeight sets the "height" field. +func (au *AssetUpdate) SetHeight(i int) *AssetUpdate { + au.mutation.ResetHeight() + au.mutation.SetHeight(i) + return au +} + +// AddHeight adds i to the "height" field. +func (au *AssetUpdate) AddHeight(i int) *AssetUpdate { + au.mutation.AddHeight(i) + return au +} + +// SetPostID sets the "post_id" field. +func (au *AssetUpdate) SetPostID(x xid.ID) *AssetUpdate { + au.mutation.SetPostID(x) + return au +} + +// SetPost sets the "post" edge to the Post entity. +func (au *AssetUpdate) SetPost(p *Post) *AssetUpdate { + return au.SetPostID(p.ID) +} + +// Mutation returns the AssetMutation object of the builder. +func (au *AssetUpdate) Mutation() *AssetMutation { + return au.mutation +} + +// ClearPost clears the "post" edge to the Post entity. +func (au *AssetUpdate) ClearPost() *AssetUpdate { + au.mutation.ClearPost() + return au +} + +// Save executes the query and returns the number of nodes affected by the update operation. +func (au *AssetUpdate) Save(ctx context.Context) (int, error) { + au.defaults() + return withHooks[int, AssetMutation](ctx, au.sqlSave, au.mutation, au.hooks) +} + +// SaveX is like Save, but panics if an error occurs. +func (au *AssetUpdate) SaveX(ctx context.Context) int { + affected, err := au.Save(ctx) + if err != nil { + panic(err) + } + return affected +} + +// Exec executes the query. +func (au *AssetUpdate) Exec(ctx context.Context) error { + _, err := au.Save(ctx) + return err +} + +// ExecX is like Exec, but panics if an error occurs. +func (au *AssetUpdate) ExecX(ctx context.Context) { + if err := au.Exec(ctx); err != nil { + panic(err) + } +} + +// defaults sets the default values of the builder before save. +func (au *AssetUpdate) defaults() { + if _, ok := au.mutation.UpdatedAt(); !ok { + v := asset.UpdateDefaultUpdatedAt() + au.mutation.SetUpdatedAt(v) + } +} + +// check runs all checks and user-defined validators on the builder. +func (au *AssetUpdate) check() error { + if _, ok := au.mutation.PostID(); au.mutation.PostCleared() && !ok { + return errors.New(`ent: clearing a required unique edge "Asset.post"`) + } + return nil +} + +// Modify adds a statement modifier for attaching custom logic to the UPDATE statement. +func (au *AssetUpdate) Modify(modifiers ...func(u *sql.UpdateBuilder)) *AssetUpdate { + au.modifiers = append(au.modifiers, modifiers...) + return au +} + +func (au *AssetUpdate) sqlSave(ctx context.Context) (n int, err error) { + if err := au.check(); err != nil { + return n, err + } + _spec := sqlgraph.NewUpdateSpec(asset.Table, asset.Columns, sqlgraph.NewFieldSpec(asset.FieldID, field.TypeString)) + if ps := au.mutation.predicates; len(ps) > 0 { + _spec.Predicate = func(selector *sql.Selector) { + for i := range ps { + ps[i](selector) + } + } + } + if value, ok := au.mutation.UpdatedAt(); ok { + _spec.SetField(asset.FieldUpdatedAt, field.TypeTime, value) + } + if value, ok := au.mutation.URL(); ok { + _spec.SetField(asset.FieldURL, field.TypeString, value) + } + if value, ok := au.mutation.Mimetype(); ok { + _spec.SetField(asset.FieldMimetype, field.TypeString, value) + } + if value, ok := au.mutation.Width(); ok { + _spec.SetField(asset.FieldWidth, field.TypeInt, value) + } + if value, ok := au.mutation.AddedWidth(); ok { + _spec.AddField(asset.FieldWidth, field.TypeInt, value) + } + if value, ok := au.mutation.Height(); ok { + _spec.SetField(asset.FieldHeight, field.TypeInt, value) + } + if value, ok := au.mutation.AddedHeight(); ok { + _spec.AddField(asset.FieldHeight, field.TypeInt, value) + } + if au.mutation.PostCleared() { + edge := &sqlgraph.EdgeSpec{ + Rel: sqlgraph.M2O, + Inverse: true, + Table: asset.PostTable, + Columns: []string{asset.PostColumn}, + Bidi: false, + Target: &sqlgraph.EdgeTarget{ + IDSpec: sqlgraph.NewFieldSpec(post.FieldID, field.TypeString), + }, + } + _spec.Edges.Clear = append(_spec.Edges.Clear, edge) + } + if nodes := au.mutation.PostIDs(); len(nodes) > 0 { + edge := &sqlgraph.EdgeSpec{ + Rel: sqlgraph.M2O, + Inverse: true, + Table: asset.PostTable, + Columns: []string{asset.PostColumn}, + Bidi: false, + Target: &sqlgraph.EdgeTarget{ + IDSpec: sqlgraph.NewFieldSpec(post.FieldID, field.TypeString), + }, + } + for _, k := range nodes { + edge.Target.Nodes = append(edge.Target.Nodes, k) + } + _spec.Edges.Add = append(_spec.Edges.Add, edge) + } + _spec.AddModifiers(au.modifiers...) + if n, err = sqlgraph.UpdateNodes(ctx, au.driver, _spec); err != nil { + if _, ok := err.(*sqlgraph.NotFoundError); ok { + err = &NotFoundError{asset.Label} + } else if sqlgraph.IsConstraintError(err) { + err = &ConstraintError{msg: err.Error(), wrap: err} + } + return 0, err + } + au.mutation.done = true + return n, nil +} + +// AssetUpdateOne is the builder for updating a single Asset entity. +type AssetUpdateOne struct { + config + fields []string + hooks []Hook + mutation *AssetMutation + modifiers []func(*sql.UpdateBuilder) +} + +// SetUpdatedAt sets the "updated_at" field. +func (auo *AssetUpdateOne) SetUpdatedAt(t time.Time) *AssetUpdateOne { + auo.mutation.SetUpdatedAt(t) + return auo +} + +// SetURL sets the "url" field. +func (auo *AssetUpdateOne) SetURL(s string) *AssetUpdateOne { + auo.mutation.SetURL(s) + return auo +} + +// SetMimetype sets the "mimetype" field. +func (auo *AssetUpdateOne) SetMimetype(s string) *AssetUpdateOne { + auo.mutation.SetMimetype(s) + return auo +} + +// SetWidth sets the "width" field. +func (auo *AssetUpdateOne) SetWidth(i int) *AssetUpdateOne { + auo.mutation.ResetWidth() + auo.mutation.SetWidth(i) + return auo +} + +// AddWidth adds i to the "width" field. +func (auo *AssetUpdateOne) AddWidth(i int) *AssetUpdateOne { + auo.mutation.AddWidth(i) + return auo +} + +// SetHeight sets the "height" field. +func (auo *AssetUpdateOne) SetHeight(i int) *AssetUpdateOne { + auo.mutation.ResetHeight() + auo.mutation.SetHeight(i) + return auo +} + +// AddHeight adds i to the "height" field. +func (auo *AssetUpdateOne) AddHeight(i int) *AssetUpdateOne { + auo.mutation.AddHeight(i) + return auo +} + +// SetPostID sets the "post_id" field. +func (auo *AssetUpdateOne) SetPostID(x xid.ID) *AssetUpdateOne { + auo.mutation.SetPostID(x) + return auo +} + +// SetPost sets the "post" edge to the Post entity. +func (auo *AssetUpdateOne) SetPost(p *Post) *AssetUpdateOne { + return auo.SetPostID(p.ID) +} + +// Mutation returns the AssetMutation object of the builder. +func (auo *AssetUpdateOne) Mutation() *AssetMutation { + return auo.mutation +} + +// ClearPost clears the "post" edge to the Post entity. +func (auo *AssetUpdateOne) ClearPost() *AssetUpdateOne { + auo.mutation.ClearPost() + return auo +} + +// Where appends a list predicates to the AssetUpdate builder. +func (auo *AssetUpdateOne) Where(ps ...predicate.Asset) *AssetUpdateOne { + auo.mutation.Where(ps...) + return auo +} + +// Select allows selecting one or more fields (columns) of the returned entity. +// The default is selecting all fields defined in the entity schema. +func (auo *AssetUpdateOne) Select(field string, fields ...string) *AssetUpdateOne { + auo.fields = append([]string{field}, fields...) + return auo +} + +// Save executes the query and returns the updated Asset entity. +func (auo *AssetUpdateOne) Save(ctx context.Context) (*Asset, error) { + auo.defaults() + return withHooks[*Asset, AssetMutation](ctx, auo.sqlSave, auo.mutation, auo.hooks) +} + +// SaveX is like Save, but panics if an error occurs. +func (auo *AssetUpdateOne) SaveX(ctx context.Context) *Asset { + node, err := auo.Save(ctx) + if err != nil { + panic(err) + } + return node +} + +// Exec executes the query on the entity. +func (auo *AssetUpdateOne) Exec(ctx context.Context) error { + _, err := auo.Save(ctx) + return err +} + +// ExecX is like Exec, but panics if an error occurs. +func (auo *AssetUpdateOne) ExecX(ctx context.Context) { + if err := auo.Exec(ctx); err != nil { + panic(err) + } +} + +// defaults sets the default values of the builder before save. +func (auo *AssetUpdateOne) defaults() { + if _, ok := auo.mutation.UpdatedAt(); !ok { + v := asset.UpdateDefaultUpdatedAt() + auo.mutation.SetUpdatedAt(v) + } +} + +// check runs all checks and user-defined validators on the builder. +func (auo *AssetUpdateOne) check() error { + if _, ok := auo.mutation.PostID(); auo.mutation.PostCleared() && !ok { + return errors.New(`ent: clearing a required unique edge "Asset.post"`) + } + return nil +} + +// Modify adds a statement modifier for attaching custom logic to the UPDATE statement. +func (auo *AssetUpdateOne) Modify(modifiers ...func(u *sql.UpdateBuilder)) *AssetUpdateOne { + auo.modifiers = append(auo.modifiers, modifiers...) + return auo +} + +func (auo *AssetUpdateOne) sqlSave(ctx context.Context) (_node *Asset, err error) { + if err := auo.check(); err != nil { + return _node, err + } + _spec := sqlgraph.NewUpdateSpec(asset.Table, asset.Columns, sqlgraph.NewFieldSpec(asset.FieldID, field.TypeString)) + id, ok := auo.mutation.ID() + if !ok { + return nil, &ValidationError{Name: "id", err: errors.New(`ent: missing "Asset.id" for update`)} + } + _spec.Node.ID.Value = id + if fields := auo.fields; len(fields) > 0 { + _spec.Node.Columns = make([]string, 0, len(fields)) + _spec.Node.Columns = append(_spec.Node.Columns, asset.FieldID) + for _, f := range fields { + if !asset.ValidColumn(f) { + return nil, &ValidationError{Name: f, err: fmt.Errorf("ent: invalid field %q for query", f)} + } + if f != asset.FieldID { + _spec.Node.Columns = append(_spec.Node.Columns, f) + } + } + } + if ps := auo.mutation.predicates; len(ps) > 0 { + _spec.Predicate = func(selector *sql.Selector) { + for i := range ps { + ps[i](selector) + } + } + } + if value, ok := auo.mutation.UpdatedAt(); ok { + _spec.SetField(asset.FieldUpdatedAt, field.TypeTime, value) + } + if value, ok := auo.mutation.URL(); ok { + _spec.SetField(asset.FieldURL, field.TypeString, value) + } + if value, ok := auo.mutation.Mimetype(); ok { + _spec.SetField(asset.FieldMimetype, field.TypeString, value) + } + if value, ok := auo.mutation.Width(); ok { + _spec.SetField(asset.FieldWidth, field.TypeInt, value) + } + if value, ok := auo.mutation.AddedWidth(); ok { + _spec.AddField(asset.FieldWidth, field.TypeInt, value) + } + if value, ok := auo.mutation.Height(); ok { + _spec.SetField(asset.FieldHeight, field.TypeInt, value) + } + if value, ok := auo.mutation.AddedHeight(); ok { + _spec.AddField(asset.FieldHeight, field.TypeInt, value) + } + if auo.mutation.PostCleared() { + edge := &sqlgraph.EdgeSpec{ + Rel: sqlgraph.M2O, + Inverse: true, + Table: asset.PostTable, + Columns: []string{asset.PostColumn}, + Bidi: false, + Target: &sqlgraph.EdgeTarget{ + IDSpec: sqlgraph.NewFieldSpec(post.FieldID, field.TypeString), + }, + } + _spec.Edges.Clear = append(_spec.Edges.Clear, edge) + } + if nodes := auo.mutation.PostIDs(); len(nodes) > 0 { + edge := &sqlgraph.EdgeSpec{ + Rel: sqlgraph.M2O, + Inverse: true, + Table: asset.PostTable, + Columns: []string{asset.PostColumn}, + Bidi: false, + Target: &sqlgraph.EdgeTarget{ + IDSpec: sqlgraph.NewFieldSpec(post.FieldID, field.TypeString), + }, + } + for _, k := range nodes { + edge.Target.Nodes = append(edge.Target.Nodes, k) + } + _spec.Edges.Add = append(_spec.Edges.Add, edge) + } + _spec.AddModifiers(auo.modifiers...) + _node = &Asset{config: auo.config} + _spec.Assign = _node.assignValues + _spec.ScanValues = _node.scanValues + if err = sqlgraph.UpdateNode(ctx, auo.driver, _spec); err != nil { + if _, ok := err.(*sqlgraph.NotFoundError); ok { + err = &NotFoundError{asset.Label} + } else if sqlgraph.IsConstraintError(err) { + err = &ConstraintError{msg: err.Error(), wrap: err} + } + return nil, err + } + auo.mutation.done = true + return _node, nil +} diff --git a/internal/ent/client.go b/internal/ent/client.go index eae7499f2..6c43cb257 100644 --- a/internal/ent/client.go +++ b/internal/ent/client.go @@ -16,6 +16,7 @@ import ( "entgo.io/ent/dialect/sql" "entgo.io/ent/dialect/sql/sqlgraph" "github.com/Southclaws/storyden/internal/ent/account" + "github.com/Southclaws/storyden/internal/ent/asset" "github.com/Southclaws/storyden/internal/ent/authentication" "github.com/Southclaws/storyden/internal/ent/category" "github.com/Southclaws/storyden/internal/ent/notification" @@ -33,6 +34,8 @@ type Client struct { Schema *migrate.Schema // Account is the client for interacting with the Account builders. Account *AccountClient + // Asset is the client for interacting with the Asset builders. + Asset *AssetClient // Authentication is the client for interacting with the Authentication builders. Authentication *AuthenticationClient // Category is the client for interacting with the Category builders. @@ -63,6 +66,7 @@ func NewClient(opts ...Option) *Client { func (c *Client) init() { c.Schema = migrate.NewSchema(c.driver) c.Account = NewAccountClient(c.config) + c.Asset = NewAssetClient(c.config) c.Authentication = NewAuthenticationClient(c.config) c.Category = NewCategoryClient(c.config) c.Notification = NewNotificationClient(c.config) @@ -154,6 +158,7 @@ func (c *Client) Tx(ctx context.Context) (*Tx, error) { ctx: ctx, config: cfg, Account: NewAccountClient(cfg), + Asset: NewAssetClient(cfg), Authentication: NewAuthenticationClient(cfg), Category: NewCategoryClient(cfg), Notification: NewNotificationClient(cfg), @@ -182,6 +187,7 @@ func (c *Client) BeginTx(ctx context.Context, opts *sql.TxOptions) (*Tx, error) ctx: ctx, config: cfg, Account: NewAccountClient(cfg), + Asset: NewAssetClient(cfg), Authentication: NewAuthenticationClient(cfg), Category: NewCategoryClient(cfg), Notification: NewNotificationClient(cfg), @@ -219,8 +225,8 @@ func (c *Client) Close() error { // In order to add hooks to a specific client, call: `client.Node.Use(...)`. func (c *Client) Use(hooks ...Hook) { for _, n := range []interface{ Use(...Hook) }{ - c.Account, c.Authentication, c.Category, c.Notification, c.Post, c.React, - c.Role, c.Setting, c.Tag, + c.Account, c.Asset, c.Authentication, c.Category, c.Notification, c.Post, + c.React, c.Role, c.Setting, c.Tag, } { n.Use(hooks...) } @@ -230,8 +236,8 @@ func (c *Client) Use(hooks ...Hook) { // In order to add interceptors to a specific client, call: `client.Node.Intercept(...)`. func (c *Client) Intercept(interceptors ...Interceptor) { for _, n := range []interface{ Intercept(...Interceptor) }{ - c.Account, c.Authentication, c.Category, c.Notification, c.Post, c.React, - c.Role, c.Setting, c.Tag, + c.Account, c.Asset, c.Authentication, c.Category, c.Notification, c.Post, + c.React, c.Role, c.Setting, c.Tag, } { n.Intercept(interceptors...) } @@ -242,6 +248,8 @@ func (c *Client) Mutate(ctx context.Context, m Mutation) (Value, error) { switch m := m.(type) { case *AccountMutation: return c.Account.mutate(ctx, m) + case *AssetMutation: + return c.Asset.mutate(ctx, m) case *AuthenticationMutation: return c.Authentication.mutate(ctx, m) case *CategoryMutation: @@ -461,6 +469,140 @@ func (c *AccountClient) mutate(ctx context.Context, m *AccountMutation) (Value, } } +// AssetClient is a client for the Asset schema. +type AssetClient struct { + config +} + +// NewAssetClient returns a client for the Asset from the given config. +func NewAssetClient(c config) *AssetClient { + return &AssetClient{config: c} +} + +// Use adds a list of mutation hooks to the hooks stack. +// A call to `Use(f, g, h)` equals to `asset.Hooks(f(g(h())))`. +func (c *AssetClient) Use(hooks ...Hook) { + c.hooks.Asset = append(c.hooks.Asset, hooks...) +} + +// Intercept adds a list of query interceptors to the interceptors stack. +// A call to `Intercept(f, g, h)` equals to `asset.Intercept(f(g(h())))`. +func (c *AssetClient) Intercept(interceptors ...Interceptor) { + c.inters.Asset = append(c.inters.Asset, interceptors...) +} + +// Create returns a builder for creating a Asset entity. +func (c *AssetClient) Create() *AssetCreate { + mutation := newAssetMutation(c.config, OpCreate) + return &AssetCreate{config: c.config, hooks: c.Hooks(), mutation: mutation} +} + +// CreateBulk returns a builder for creating a bulk of Asset entities. +func (c *AssetClient) CreateBulk(builders ...*AssetCreate) *AssetCreateBulk { + return &AssetCreateBulk{config: c.config, builders: builders} +} + +// Update returns an update builder for Asset. +func (c *AssetClient) Update() *AssetUpdate { + mutation := newAssetMutation(c.config, OpUpdate) + return &AssetUpdate{config: c.config, hooks: c.Hooks(), mutation: mutation} +} + +// UpdateOne returns an update builder for the given entity. +func (c *AssetClient) UpdateOne(a *Asset) *AssetUpdateOne { + mutation := newAssetMutation(c.config, OpUpdateOne, withAsset(a)) + return &AssetUpdateOne{config: c.config, hooks: c.Hooks(), mutation: mutation} +} + +// UpdateOneID returns an update builder for the given id. +func (c *AssetClient) UpdateOneID(id xid.ID) *AssetUpdateOne { + mutation := newAssetMutation(c.config, OpUpdateOne, withAssetID(id)) + return &AssetUpdateOne{config: c.config, hooks: c.Hooks(), mutation: mutation} +} + +// Delete returns a delete builder for Asset. +func (c *AssetClient) Delete() *AssetDelete { + mutation := newAssetMutation(c.config, OpDelete) + return &AssetDelete{config: c.config, hooks: c.Hooks(), mutation: mutation} +} + +// DeleteOne returns a builder for deleting the given entity. +func (c *AssetClient) DeleteOne(a *Asset) *AssetDeleteOne { + return c.DeleteOneID(a.ID) +} + +// DeleteOneID returns a builder for deleting the given entity by its id. +func (c *AssetClient) DeleteOneID(id xid.ID) *AssetDeleteOne { + builder := c.Delete().Where(asset.ID(id)) + builder.mutation.id = &id + builder.mutation.op = OpDeleteOne + return &AssetDeleteOne{builder} +} + +// Query returns a query builder for Asset. +func (c *AssetClient) Query() *AssetQuery { + return &AssetQuery{ + config: c.config, + ctx: &QueryContext{Type: TypeAsset}, + inters: c.Interceptors(), + } +} + +// Get returns a Asset entity by its id. +func (c *AssetClient) Get(ctx context.Context, id xid.ID) (*Asset, error) { + return c.Query().Where(asset.ID(id)).Only(ctx) +} + +// GetX is like Get, but panics if an error occurs. +func (c *AssetClient) GetX(ctx context.Context, id xid.ID) *Asset { + obj, err := c.Get(ctx, id) + if err != nil { + panic(err) + } + return obj +} + +// QueryPost queries the post edge of a Asset. +func (c *AssetClient) QueryPost(a *Asset) *PostQuery { + query := (&PostClient{config: c.config}).Query() + query.path = func(context.Context) (fromV *sql.Selector, _ error) { + id := a.ID + step := sqlgraph.NewStep( + sqlgraph.From(asset.Table, asset.FieldID, id), + sqlgraph.To(post.Table, post.FieldID), + sqlgraph.Edge(sqlgraph.M2O, true, asset.PostTable, asset.PostColumn), + ) + fromV = sqlgraph.Neighbors(a.driver.Dialect(), step) + return fromV, nil + } + return query +} + +// Hooks returns the client hooks. +func (c *AssetClient) Hooks() []Hook { + return c.hooks.Asset +} + +// Interceptors returns the client interceptors. +func (c *AssetClient) Interceptors() []Interceptor { + return c.inters.Asset +} + +func (c *AssetClient) mutate(ctx context.Context, m *AssetMutation) (Value, error) { + switch m.Op() { + case OpCreate: + return (&AssetCreate{config: c.config, hooks: c.Hooks(), mutation: m}).Save(ctx) + case OpUpdate: + return (&AssetUpdate{config: c.config, hooks: c.Hooks(), mutation: m}).Save(ctx) + case OpUpdateOne: + return (&AssetUpdateOne{config: c.config, hooks: c.Hooks(), mutation: m}).Save(ctx) + case OpDelete, OpDeleteOne: + return (&AssetDelete{config: c.config, hooks: c.Hooks(), mutation: m}).Exec(ctx) + default: + return nil, fmt.Errorf("ent: unknown Asset mutation op: %q", m.Op()) + } +} + // AuthenticationClient is a client for the Authentication schema. type AuthenticationClient struct { config @@ -1068,6 +1210,22 @@ func (c *PostClient) QueryReacts(po *Post) *ReactQuery { return query } +// QueryAssets queries the assets edge of a Post. +func (c *PostClient) QueryAssets(po *Post) *AssetQuery { + query := (&AssetClient{config: c.config}).Query() + query.path = func(context.Context) (fromV *sql.Selector, _ error) { + id := po.ID + step := sqlgraph.NewStep( + sqlgraph.From(post.Table, post.FieldID, id), + sqlgraph.To(asset.Table, asset.FieldID), + sqlgraph.Edge(sqlgraph.O2M, false, post.AssetsTable, post.AssetsColumn), + ) + fromV = sqlgraph.Neighbors(po.driver.Dialect(), step) + return fromV, nil + } + return query +} + // Hooks returns the client hooks. func (c *PostClient) Hooks() []Hook { return c.hooks.Post @@ -1648,11 +1806,11 @@ func (c *TagClient) mutate(ctx context.Context, m *TagMutation) (Value, error) { // hooks and interceptors per client, for fast access. type ( hooks struct { - Account, Authentication, Category, Notification, Post, React, Role, Setting, - Tag []ent.Hook + Account, Asset, Authentication, Category, Notification, Post, React, Role, + Setting, Tag []ent.Hook } inters struct { - Account, Authentication, Category, Notification, Post, React, Role, Setting, - Tag []ent.Interceptor + Account, Asset, Authentication, Category, Notification, Post, React, Role, + Setting, Tag []ent.Interceptor } ) diff --git a/internal/ent/ent.go b/internal/ent/ent.go index ad6ecd212..d9f2c7932 100644 --- a/internal/ent/ent.go +++ b/internal/ent/ent.go @@ -12,6 +12,7 @@ import ( "entgo.io/ent/dialect/sql" "entgo.io/ent/dialect/sql/sqlgraph" "github.com/Southclaws/storyden/internal/ent/account" + "github.com/Southclaws/storyden/internal/ent/asset" "github.com/Southclaws/storyden/internal/ent/authentication" "github.com/Southclaws/storyden/internal/ent/category" "github.com/Southclaws/storyden/internal/ent/notification" @@ -74,6 +75,7 @@ type OrderFunc func(*sql.Selector) func columnChecker(table string) func(string) error { checks := map[string]func(string) bool{ account.Table: account.ValidColumn, + asset.Table: asset.ValidColumn, authentication.Table: authentication.ValidColumn, category.Table: category.ValidColumn, notification.Table: notification.ValidColumn, diff --git a/internal/ent/hook/hook.go b/internal/ent/hook/hook.go index be5f21119..6ad9551be 100644 --- a/internal/ent/hook/hook.go +++ b/internal/ent/hook/hook.go @@ -21,6 +21,18 @@ func (f AccountFunc) Mutate(ctx context.Context, m ent.Mutation) (ent.Value, err return nil, fmt.Errorf("unexpected mutation type %T. expect *ent.AccountMutation", m) } +// The AssetFunc type is an adapter to allow the use of ordinary +// function as Asset mutator. +type AssetFunc func(context.Context, *ent.AssetMutation) (ent.Value, error) + +// Mutate calls f(ctx, m). +func (f AssetFunc) Mutate(ctx context.Context, m ent.Mutation) (ent.Value, error) { + if mv, ok := m.(*ent.AssetMutation); ok { + return f(ctx, mv) + } + return nil, fmt.Errorf("unexpected mutation type %T. expect *ent.AssetMutation", m) +} + // The AuthenticationFunc type is an adapter to allow the use of ordinary // function as Authentication mutator. type AuthenticationFunc func(context.Context, *ent.AuthenticationMutation) (ent.Value, error) diff --git a/internal/ent/migrate/schema.go b/internal/ent/migrate/schema.go index 00b71ce37..d3fa2dc35 100644 --- a/internal/ent/migrate/schema.go +++ b/internal/ent/migrate/schema.go @@ -25,6 +25,31 @@ var ( Columns: AccountsColumns, PrimaryKey: []*schema.Column{AccountsColumns[0]}, } + // AssetsColumns holds the columns for the "assets" table. + AssetsColumns = []*schema.Column{ + {Name: "id", Type: field.TypeString, Size: 20}, + {Name: "created_at", Type: field.TypeTime}, + {Name: "updated_at", Type: field.TypeTime}, + {Name: "url", Type: field.TypeString}, + {Name: "mimetype", Type: field.TypeString}, + {Name: "width", Type: field.TypeInt}, + {Name: "height", Type: field.TypeInt}, + {Name: "post_id", Type: field.TypeString, Size: 20}, + } + // AssetsTable holds the schema information for the "assets" table. + AssetsTable = &schema.Table{ + Name: "assets", + Columns: AssetsColumns, + PrimaryKey: []*schema.Column{AssetsColumns[0]}, + ForeignKeys: []*schema.ForeignKey{ + { + Symbol: "assets_posts_assets", + Columns: []*schema.Column{AssetsColumns[7]}, + RefColumns: []*schema.Column{PostsColumns[0]}, + OnDelete: schema.NoAction, + }, + }, + } // AuthenticationsColumns holds the columns for the "authentications" table. AuthenticationsColumns = []*schema.Column{ {Name: "id", Type: field.TypeString, Size: 20}, @@ -282,6 +307,7 @@ var ( // Tables holds all the tables in the schema. Tables = []*schema.Table{ AccountsTable, + AssetsTable, AuthenticationsTable, CategoriesTable, NotificationsTable, @@ -297,6 +323,7 @@ var ( ) func init() { + AssetsTable.ForeignKeys[0].RefTable = PostsTable AuthenticationsTable.ForeignKeys[0].RefTable = AccountsTable PostsTable.ForeignKeys[0].RefTable = AccountsTable PostsTable.ForeignKeys[1].RefTable = CategoriesTable diff --git a/internal/ent/mutation.go b/internal/ent/mutation.go index 9658855de..ac22c2a7e 100644 --- a/internal/ent/mutation.go +++ b/internal/ent/mutation.go @@ -12,6 +12,7 @@ import ( "entgo.io/ent" "entgo.io/ent/dialect/sql" "github.com/Southclaws/storyden/internal/ent/account" + "github.com/Southclaws/storyden/internal/ent/asset" "github.com/Southclaws/storyden/internal/ent/authentication" "github.com/Southclaws/storyden/internal/ent/category" "github.com/Southclaws/storyden/internal/ent/notification" @@ -34,6 +35,7 @@ const ( // Node types. TypeAccount = "Account" + TypeAsset = "Asset" TypeAuthentication = "Authentication" TypeCategory = "Category" TypeNotification = "Notification" @@ -1166,6 +1168,784 @@ func (m *AccountMutation) ResetEdge(name string) error { return fmt.Errorf("unknown Account edge %s", name) } +// AssetMutation represents an operation that mutates the Asset nodes in the graph. +type AssetMutation struct { + config + op Op + typ string + id *xid.ID + created_at *time.Time + updated_at *time.Time + url *string + mimetype *string + width *int + addwidth *int + height *int + addheight *int + clearedFields map[string]struct{} + post *xid.ID + clearedpost bool + done bool + oldValue func(context.Context) (*Asset, error) + predicates []predicate.Asset +} + +var _ ent.Mutation = (*AssetMutation)(nil) + +// assetOption allows management of the mutation configuration using functional options. +type assetOption func(*AssetMutation) + +// newAssetMutation creates new mutation for the Asset entity. +func newAssetMutation(c config, op Op, opts ...assetOption) *AssetMutation { + m := &AssetMutation{ + config: c, + op: op, + typ: TypeAsset, + clearedFields: make(map[string]struct{}), + } + for _, opt := range opts { + opt(m) + } + return m +} + +// withAssetID sets the ID field of the mutation. +func withAssetID(id xid.ID) assetOption { + return func(m *AssetMutation) { + var ( + err error + once sync.Once + value *Asset + ) + m.oldValue = func(ctx context.Context) (*Asset, error) { + once.Do(func() { + if m.done { + err = errors.New("querying old values post mutation is not allowed") + } else { + value, err = m.Client().Asset.Get(ctx, id) + } + }) + return value, err + } + m.id = &id + } +} + +// withAsset sets the old Asset of the mutation. +func withAsset(node *Asset) assetOption { + return func(m *AssetMutation) { + m.oldValue = func(context.Context) (*Asset, error) { + return node, nil + } + m.id = &node.ID + } +} + +// Client returns a new `ent.Client` from the mutation. If the mutation was +// executed in a transaction (ent.Tx), a transactional client is returned. +func (m AssetMutation) Client() *Client { + client := &Client{config: m.config} + client.init() + return client +} + +// Tx returns an `ent.Tx` for mutations that were executed in transactions; +// it returns an error otherwise. +func (m AssetMutation) Tx() (*Tx, error) { + if _, ok := m.driver.(*txDriver); !ok { + return nil, errors.New("ent: mutation is not running in a transaction") + } + tx := &Tx{config: m.config} + tx.init() + return tx, nil +} + +// SetID sets the value of the id field. Note that this +// operation is only accepted on creation of Asset entities. +func (m *AssetMutation) SetID(id xid.ID) { + m.id = &id +} + +// ID returns the ID value in the mutation. Note that the ID is only available +// if it was provided to the builder or after it was returned from the database. +func (m *AssetMutation) ID() (id xid.ID, exists bool) { + if m.id == nil { + return + } + return *m.id, true +} + +// IDs queries the database and returns the entity ids that match the mutation's predicate. +// That means, if the mutation is applied within a transaction with an isolation level such +// as sql.LevelSerializable, the returned ids match the ids of the rows that will be updated +// or updated by the mutation. +func (m *AssetMutation) IDs(ctx context.Context) ([]xid.ID, error) { + switch { + case m.op.Is(OpUpdateOne | OpDeleteOne): + id, exists := m.ID() + if exists { + return []xid.ID{id}, nil + } + fallthrough + case m.op.Is(OpUpdate | OpDelete): + return m.Client().Asset.Query().Where(m.predicates...).IDs(ctx) + default: + return nil, fmt.Errorf("IDs is not allowed on %s operations", m.op) + } +} + +// SetCreatedAt sets the "created_at" field. +func (m *AssetMutation) SetCreatedAt(t time.Time) { + m.created_at = &t +} + +// CreatedAt returns the value of the "created_at" field in the mutation. +func (m *AssetMutation) CreatedAt() (r time.Time, exists bool) { + v := m.created_at + if v == nil { + return + } + return *v, true +} + +// OldCreatedAt returns the old "created_at" field's value of the Asset entity. +// If the Asset 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 *AssetMutation) OldCreatedAt(ctx context.Context) (v time.Time, err error) { + if !m.op.Is(OpUpdateOne) { + return v, errors.New("OldCreatedAt is only allowed on UpdateOne operations") + } + if m.id == nil || m.oldValue == nil { + return v, errors.New("OldCreatedAt requires an ID field in the mutation") + } + oldValue, err := m.oldValue(ctx) + if err != nil { + return v, fmt.Errorf("querying old value for OldCreatedAt: %w", err) + } + return oldValue.CreatedAt, nil +} + +// ResetCreatedAt resets all changes to the "created_at" field. +func (m *AssetMutation) ResetCreatedAt() { + m.created_at = nil +} + +// SetUpdatedAt sets the "updated_at" field. +func (m *AssetMutation) SetUpdatedAt(t time.Time) { + m.updated_at = &t +} + +// UpdatedAt returns the value of the "updated_at" field in the mutation. +func (m *AssetMutation) UpdatedAt() (r time.Time, exists bool) { + v := m.updated_at + if v == nil { + return + } + return *v, true +} + +// OldUpdatedAt returns the old "updated_at" field's value of the Asset entity. +// If the Asset 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 *AssetMutation) OldUpdatedAt(ctx context.Context) (v time.Time, err error) { + if !m.op.Is(OpUpdateOne) { + return v, errors.New("OldUpdatedAt is only allowed on UpdateOne operations") + } + if m.id == nil || m.oldValue == nil { + return v, errors.New("OldUpdatedAt requires an ID field in the mutation") + } + oldValue, err := m.oldValue(ctx) + if err != nil { + return v, fmt.Errorf("querying old value for OldUpdatedAt: %w", err) + } + return oldValue.UpdatedAt, nil +} + +// ResetUpdatedAt resets all changes to the "updated_at" field. +func (m *AssetMutation) ResetUpdatedAt() { + m.updated_at = nil +} + +// SetURL sets the "url" field. +func (m *AssetMutation) SetURL(s string) { + m.url = &s +} + +// URL returns the value of the "url" field in the mutation. +func (m *AssetMutation) URL() (r string, exists bool) { + v := m.url + if v == nil { + return + } + return *v, true +} + +// OldURL returns the old "url" field's value of the Asset entity. +// If the Asset 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 *AssetMutation) OldURL(ctx context.Context) (v string, err error) { + if !m.op.Is(OpUpdateOne) { + return v, errors.New("OldURL is only allowed on UpdateOne operations") + } + if m.id == nil || m.oldValue == nil { + return v, errors.New("OldURL requires an ID field in the mutation") + } + oldValue, err := m.oldValue(ctx) + if err != nil { + return v, fmt.Errorf("querying old value for OldURL: %w", err) + } + return oldValue.URL, nil +} + +// ResetURL resets all changes to the "url" field. +func (m *AssetMutation) ResetURL() { + m.url = nil +} + +// SetMimetype sets the "mimetype" field. +func (m *AssetMutation) SetMimetype(s string) { + m.mimetype = &s +} + +// Mimetype returns the value of the "mimetype" field in the mutation. +func (m *AssetMutation) Mimetype() (r string, exists bool) { + v := m.mimetype + if v == nil { + return + } + return *v, true +} + +// OldMimetype returns the old "mimetype" field's value of the Asset entity. +// If the Asset 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 *AssetMutation) OldMimetype(ctx context.Context) (v string, err error) { + if !m.op.Is(OpUpdateOne) { + return v, errors.New("OldMimetype is only allowed on UpdateOne operations") + } + if m.id == nil || m.oldValue == nil { + return v, errors.New("OldMimetype requires an ID field in the mutation") + } + oldValue, err := m.oldValue(ctx) + if err != nil { + return v, fmt.Errorf("querying old value for OldMimetype: %w", err) + } + return oldValue.Mimetype, nil +} + +// ResetMimetype resets all changes to the "mimetype" field. +func (m *AssetMutation) ResetMimetype() { + m.mimetype = nil +} + +// SetWidth sets the "width" field. +func (m *AssetMutation) SetWidth(i int) { + m.width = &i + m.addwidth = nil +} + +// Width returns the value of the "width" field in the mutation. +func (m *AssetMutation) Width() (r int, exists bool) { + v := m.width + if v == nil { + return + } + return *v, true +} + +// OldWidth returns the old "width" field's value of the Asset entity. +// If the Asset 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 *AssetMutation) OldWidth(ctx context.Context) (v int, err error) { + if !m.op.Is(OpUpdateOne) { + return v, errors.New("OldWidth is only allowed on UpdateOne operations") + } + if m.id == nil || m.oldValue == nil { + return v, errors.New("OldWidth requires an ID field in the mutation") + } + oldValue, err := m.oldValue(ctx) + if err != nil { + return v, fmt.Errorf("querying old value for OldWidth: %w", err) + } + return oldValue.Width, nil +} + +// AddWidth adds i to the "width" field. +func (m *AssetMutation) AddWidth(i int) { + if m.addwidth != nil { + *m.addwidth += i + } else { + m.addwidth = &i + } +} + +// AddedWidth returns the value that was added to the "width" field in this mutation. +func (m *AssetMutation) AddedWidth() (r int, exists bool) { + v := m.addwidth + if v == nil { + return + } + return *v, true +} + +// ResetWidth resets all changes to the "width" field. +func (m *AssetMutation) ResetWidth() { + m.width = nil + m.addwidth = nil +} + +// SetHeight sets the "height" field. +func (m *AssetMutation) SetHeight(i int) { + m.height = &i + m.addheight = nil +} + +// Height returns the value of the "height" field in the mutation. +func (m *AssetMutation) Height() (r int, exists bool) { + v := m.height + if v == nil { + return + } + return *v, true +} + +// OldHeight returns the old "height" field's value of the Asset entity. +// If the Asset 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 *AssetMutation) OldHeight(ctx context.Context) (v int, err error) { + if !m.op.Is(OpUpdateOne) { + return v, errors.New("OldHeight is only allowed on UpdateOne operations") + } + if m.id == nil || m.oldValue == nil { + return v, errors.New("OldHeight requires an ID field in the mutation") + } + oldValue, err := m.oldValue(ctx) + if err != nil { + return v, fmt.Errorf("querying old value for OldHeight: %w", err) + } + return oldValue.Height, nil +} + +// AddHeight adds i to the "height" field. +func (m *AssetMutation) AddHeight(i int) { + if m.addheight != nil { + *m.addheight += i + } else { + m.addheight = &i + } +} + +// AddedHeight returns the value that was added to the "height" field in this mutation. +func (m *AssetMutation) AddedHeight() (r int, exists bool) { + v := m.addheight + if v == nil { + return + } + return *v, true +} + +// ResetHeight resets all changes to the "height" field. +func (m *AssetMutation) ResetHeight() { + m.height = nil + m.addheight = nil +} + +// SetPostID sets the "post_id" field. +func (m *AssetMutation) SetPostID(x xid.ID) { + m.post = &x +} + +// PostID returns the value of the "post_id" field in the mutation. +func (m *AssetMutation) PostID() (r xid.ID, exists bool) { + v := m.post + if v == nil { + return + } + return *v, true +} + +// OldPostID returns the old "post_id" field's value of the Asset entity. +// If the Asset 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 *AssetMutation) OldPostID(ctx context.Context) (v xid.ID, err error) { + if !m.op.Is(OpUpdateOne) { + return v, errors.New("OldPostID is only allowed on UpdateOne operations") + } + if m.id == nil || m.oldValue == nil { + return v, errors.New("OldPostID requires an ID field in the mutation") + } + oldValue, err := m.oldValue(ctx) + if err != nil { + return v, fmt.Errorf("querying old value for OldPostID: %w", err) + } + return oldValue.PostID, nil +} + +// ResetPostID resets all changes to the "post_id" field. +func (m *AssetMutation) ResetPostID() { + m.post = nil +} + +// ClearPost clears the "post" edge to the Post entity. +func (m *AssetMutation) ClearPost() { + m.clearedpost = true +} + +// PostCleared reports if the "post" edge to the Post entity was cleared. +func (m *AssetMutation) PostCleared() bool { + return m.clearedpost +} + +// PostIDs returns the "post" edge IDs in the mutation. +// Note that IDs always returns len(IDs) <= 1 for unique edges, and you should use +// PostID instead. It exists only for internal usage by the builders. +func (m *AssetMutation) PostIDs() (ids []xid.ID) { + if id := m.post; id != nil { + ids = append(ids, *id) + } + return +} + +// ResetPost resets all changes to the "post" edge. +func (m *AssetMutation) ResetPost() { + m.post = nil + m.clearedpost = false +} + +// Where appends a list predicates to the AssetMutation builder. +func (m *AssetMutation) Where(ps ...predicate.Asset) { + m.predicates = append(m.predicates, ps...) +} + +// WhereP appends storage-level predicates to the AssetMutation builder. Using this method, +// users can use type-assertion to append predicates that do not depend on any generated package. +func (m *AssetMutation) WhereP(ps ...func(*sql.Selector)) { + p := make([]predicate.Asset, len(ps)) + for i := range ps { + p[i] = ps[i] + } + m.Where(p...) +} + +// Op returns the operation name. +func (m *AssetMutation) Op() Op { + return m.op +} + +// SetOp allows setting the mutation operation. +func (m *AssetMutation) SetOp(op Op) { + m.op = op +} + +// Type returns the node type of this mutation (Asset). +func (m *AssetMutation) Type() string { + return m.typ +} + +// Fields returns all fields that were changed during this mutation. Note that in +// order to get all numeric fields that were incremented/decremented, call +// AddedFields(). +func (m *AssetMutation) Fields() []string { + fields := make([]string, 0, 7) + if m.created_at != nil { + fields = append(fields, asset.FieldCreatedAt) + } + if m.updated_at != nil { + fields = append(fields, asset.FieldUpdatedAt) + } + if m.url != nil { + fields = append(fields, asset.FieldURL) + } + if m.mimetype != nil { + fields = append(fields, asset.FieldMimetype) + } + if m.width != nil { + fields = append(fields, asset.FieldWidth) + } + if m.height != nil { + fields = append(fields, asset.FieldHeight) + } + if m.post != nil { + fields = append(fields, asset.FieldPostID) + } + return fields +} + +// Field returns the value of a field with the given name. The second boolean +// return value indicates that this field was not set, or was not defined in the +// schema. +func (m *AssetMutation) Field(name string) (ent.Value, bool) { + switch name { + case asset.FieldCreatedAt: + return m.CreatedAt() + case asset.FieldUpdatedAt: + return m.UpdatedAt() + case asset.FieldURL: + return m.URL() + case asset.FieldMimetype: + return m.Mimetype() + case asset.FieldWidth: + return m.Width() + case asset.FieldHeight: + return m.Height() + case asset.FieldPostID: + return m.PostID() + } + return nil, false +} + +// OldField returns the old value of the field from the database. An error is +// returned if the mutation operation is not UpdateOne, or the query to the +// database failed. +func (m *AssetMutation) OldField(ctx context.Context, name string) (ent.Value, error) { + switch name { + case asset.FieldCreatedAt: + return m.OldCreatedAt(ctx) + case asset.FieldUpdatedAt: + return m.OldUpdatedAt(ctx) + case asset.FieldURL: + return m.OldURL(ctx) + case asset.FieldMimetype: + return m.OldMimetype(ctx) + case asset.FieldWidth: + return m.OldWidth(ctx) + case asset.FieldHeight: + return m.OldHeight(ctx) + case asset.FieldPostID: + return m.OldPostID(ctx) + } + return nil, fmt.Errorf("unknown Asset field %s", name) +} + +// SetField sets the value of a field with the given name. It returns an error if +// the field is not defined in the schema, or if the type mismatched the field +// type. +func (m *AssetMutation) SetField(name string, value ent.Value) error { + switch name { + case asset.FieldCreatedAt: + v, ok := value.(time.Time) + if !ok { + return fmt.Errorf("unexpected type %T for field %s", value, name) + } + m.SetCreatedAt(v) + return nil + case asset.FieldUpdatedAt: + v, ok := value.(time.Time) + if !ok { + return fmt.Errorf("unexpected type %T for field %s", value, name) + } + m.SetUpdatedAt(v) + return nil + case asset.FieldURL: + v, ok := value.(string) + if !ok { + return fmt.Errorf("unexpected type %T for field %s", value, name) + } + m.SetURL(v) + return nil + case asset.FieldMimetype: + v, ok := value.(string) + if !ok { + return fmt.Errorf("unexpected type %T for field %s", value, name) + } + m.SetMimetype(v) + return nil + case asset.FieldWidth: + v, ok := value.(int) + if !ok { + return fmt.Errorf("unexpected type %T for field %s", value, name) + } + m.SetWidth(v) + return nil + case asset.FieldHeight: + v, ok := value.(int) + if !ok { + return fmt.Errorf("unexpected type %T for field %s", value, name) + } + m.SetHeight(v) + return nil + case asset.FieldPostID: + v, ok := value.(xid.ID) + if !ok { + return fmt.Errorf("unexpected type %T for field %s", value, name) + } + m.SetPostID(v) + return nil + } + return fmt.Errorf("unknown Asset field %s", name) +} + +// AddedFields returns all numeric fields that were incremented/decremented during +// this mutation. +func (m *AssetMutation) AddedFields() []string { + var fields []string + if m.addwidth != nil { + fields = append(fields, asset.FieldWidth) + } + if m.addheight != nil { + fields = append(fields, asset.FieldHeight) + } + return fields +} + +// AddedField returns the numeric value that was incremented/decremented on a field +// with the given name. The second boolean return value indicates that this field +// was not set, or was not defined in the schema. +func (m *AssetMutation) AddedField(name string) (ent.Value, bool) { + switch name { + case asset.FieldWidth: + return m.AddedWidth() + case asset.FieldHeight: + return m.AddedHeight() + } + return nil, false +} + +// AddField adds the value to the field with the given name. It returns an error if +// the field is not defined in the schema, or if the type mismatched the field +// type. +func (m *AssetMutation) AddField(name string, value ent.Value) error { + switch name { + case asset.FieldWidth: + v, ok := value.(int) + if !ok { + return fmt.Errorf("unexpected type %T for field %s", value, name) + } + m.AddWidth(v) + return nil + case asset.FieldHeight: + v, ok := value.(int) + if !ok { + return fmt.Errorf("unexpected type %T for field %s", value, name) + } + m.AddHeight(v) + return nil + } + return fmt.Errorf("unknown Asset numeric field %s", name) +} + +// ClearedFields returns all nullable fields that were cleared during this +// mutation. +func (m *AssetMutation) ClearedFields() []string { + return nil +} + +// FieldCleared returns a boolean indicating if a field with the given name was +// cleared in this mutation. +func (m *AssetMutation) FieldCleared(name string) bool { + _, ok := m.clearedFields[name] + return ok +} + +// ClearField clears the value of the field with the given name. It returns an +// error if the field is not defined in the schema. +func (m *AssetMutation) ClearField(name string) error { + return fmt.Errorf("unknown Asset nullable field %s", name) +} + +// ResetField resets all changes in the mutation for the field with the given name. +// It returns an error if the field is not defined in the schema. +func (m *AssetMutation) ResetField(name string) error { + switch name { + case asset.FieldCreatedAt: + m.ResetCreatedAt() + return nil + case asset.FieldUpdatedAt: + m.ResetUpdatedAt() + return nil + case asset.FieldURL: + m.ResetURL() + return nil + case asset.FieldMimetype: + m.ResetMimetype() + return nil + case asset.FieldWidth: + m.ResetWidth() + return nil + case asset.FieldHeight: + m.ResetHeight() + return nil + case asset.FieldPostID: + m.ResetPostID() + return nil + } + return fmt.Errorf("unknown Asset field %s", name) +} + +// AddedEdges returns all edge names that were set/added in this mutation. +func (m *AssetMutation) AddedEdges() []string { + edges := make([]string, 0, 1) + if m.post != nil { + edges = append(edges, asset.EdgePost) + } + return edges +} + +// AddedIDs returns all IDs (to other nodes) that were added for the given edge +// name in this mutation. +func (m *AssetMutation) AddedIDs(name string) []ent.Value { + switch name { + case asset.EdgePost: + if id := m.post; id != nil { + return []ent.Value{*id} + } + } + return nil +} + +// RemovedEdges returns all edge names that were removed in this mutation. +func (m *AssetMutation) RemovedEdges() []string { + edges := make([]string, 0, 1) + return edges +} + +// RemovedIDs returns all IDs (to other nodes) that were removed for the edge with +// the given name in this mutation. +func (m *AssetMutation) RemovedIDs(name string) []ent.Value { + return nil +} + +// ClearedEdges returns all edge names that were cleared in this mutation. +func (m *AssetMutation) ClearedEdges() []string { + edges := make([]string, 0, 1) + if m.clearedpost { + edges = append(edges, asset.EdgePost) + } + return edges +} + +// EdgeCleared returns a boolean which indicates if the edge with the given name +// was cleared in this mutation. +func (m *AssetMutation) EdgeCleared(name string) bool { + switch name { + case asset.EdgePost: + return m.clearedpost + } + return false +} + +// ClearEdge clears the value of the edge with the given name. It returns an error +// if that edge is not defined in the schema. +func (m *AssetMutation) ClearEdge(name string) error { + switch name { + case asset.EdgePost: + m.ClearPost() + return nil + } + return fmt.Errorf("unknown Asset unique edge %s", name) +} + +// ResetEdge resets all changes to the edge with the given name in this mutation. +// It returns an error if the edge is not defined in the schema. +func (m *AssetMutation) ResetEdge(name string) error { + switch name { + case asset.EdgePost: + m.ResetPost() + return nil + } + return fmt.Errorf("unknown Asset edge %s", name) +} + // AuthenticationMutation represents an operation that mutates the Authentication nodes in the graph. type AuthenticationMutation struct { config @@ -3174,6 +3954,9 @@ type PostMutation struct { reacts map[xid.ID]struct{} removedreacts map[xid.ID]struct{} clearedreacts bool + assets map[xid.ID]struct{} + removedassets map[xid.ID]struct{} + clearedassets bool done bool oldValue func(context.Context) (*Post, error) predicates []predicate.Post @@ -4237,6 +5020,60 @@ func (m *PostMutation) ResetReacts() { m.removedreacts = nil } +// AddAssetIDs adds the "assets" edge to the Asset entity by ids. +func (m *PostMutation) AddAssetIDs(ids ...xid.ID) { + if m.assets == nil { + m.assets = make(map[xid.ID]struct{}) + } + for i := range ids { + m.assets[ids[i]] = struct{}{} + } +} + +// ClearAssets clears the "assets" edge to the Asset entity. +func (m *PostMutation) ClearAssets() { + m.clearedassets = true +} + +// AssetsCleared reports if the "assets" edge to the Asset entity was cleared. +func (m *PostMutation) AssetsCleared() bool { + return m.clearedassets +} + +// RemoveAssetIDs removes the "assets" edge to the Asset entity by IDs. +func (m *PostMutation) RemoveAssetIDs(ids ...xid.ID) { + if m.removedassets == nil { + m.removedassets = make(map[xid.ID]struct{}) + } + for i := range ids { + delete(m.assets, ids[i]) + m.removedassets[ids[i]] = struct{}{} + } +} + +// RemovedAssets returns the removed IDs of the "assets" edge to the Asset entity. +func (m *PostMutation) RemovedAssetsIDs() (ids []xid.ID) { + for id := range m.removedassets { + ids = append(ids, id) + } + return +} + +// AssetsIDs returns the "assets" edge IDs in the mutation. +func (m *PostMutation) AssetsIDs() (ids []xid.ID) { + for id := range m.assets { + ids = append(ids, id) + } + return +} + +// ResetAssets resets all changes to the "assets" edge. +func (m *PostMutation) ResetAssets() { + m.assets = nil + m.clearedassets = false + m.removedassets = nil +} + // Where appends a list predicates to the PostMutation builder. func (m *PostMutation) Where(ps ...predicate.Post) { m.predicates = append(m.predicates, ps...) @@ -4636,7 +5473,7 @@ func (m *PostMutation) ResetField(name string) error { // AddedEdges returns all edge names that were set/added in this mutation. func (m *PostMutation) AddedEdges() []string { - edges := make([]string, 0, 8) + edges := make([]string, 0, 9) if m.author != nil { edges = append(edges, post.EdgeAuthor) } @@ -4661,6 +5498,9 @@ func (m *PostMutation) AddedEdges() []string { if m.reacts != nil { edges = append(edges, post.EdgeReacts) } + if m.assets != nil { + edges = append(edges, post.EdgeAssets) + } return edges } @@ -4708,13 +5548,19 @@ func (m *PostMutation) AddedIDs(name string) []ent.Value { ids = append(ids, id) } return ids + case post.EdgeAssets: + ids := make([]ent.Value, 0, len(m.assets)) + for id := range m.assets { + ids = append(ids, id) + } + return ids } return nil } // RemovedEdges returns all edge names that were removed in this mutation. func (m *PostMutation) RemovedEdges() []string { - edges := make([]string, 0, 8) + edges := make([]string, 0, 9) if m.removedtags != nil { edges = append(edges, post.EdgeTags) } @@ -4727,6 +5573,9 @@ func (m *PostMutation) RemovedEdges() []string { if m.removedreacts != nil { edges = append(edges, post.EdgeReacts) } + if m.removedassets != nil { + edges = append(edges, post.EdgeAssets) + } return edges } @@ -4758,13 +5607,19 @@ func (m *PostMutation) RemovedIDs(name string) []ent.Value { ids = append(ids, id) } return ids + case post.EdgeAssets: + ids := make([]ent.Value, 0, len(m.removedassets)) + for id := range m.removedassets { + ids = append(ids, id) + } + return ids } return nil } // ClearedEdges returns all edge names that were cleared in this mutation. func (m *PostMutation) ClearedEdges() []string { - edges := make([]string, 0, 8) + edges := make([]string, 0, 9) if m.clearedauthor { edges = append(edges, post.EdgeAuthor) } @@ -4789,6 +5644,9 @@ func (m *PostMutation) ClearedEdges() []string { if m.clearedreacts { edges = append(edges, post.EdgeReacts) } + if m.clearedassets { + edges = append(edges, post.EdgeAssets) + } return edges } @@ -4812,6 +5670,8 @@ func (m *PostMutation) EdgeCleared(name string) bool { return m.clearedreplies case post.EdgeReacts: return m.clearedreacts + case post.EdgeAssets: + return m.clearedassets } return false } @@ -4864,6 +5724,9 @@ func (m *PostMutation) ResetEdge(name string) error { case post.EdgeReacts: m.ResetReacts() return nil + case post.EdgeAssets: + m.ResetAssets() + return nil } return fmt.Errorf("unknown Post edge %s", name) } diff --git a/internal/ent/post.go b/internal/ent/post.go index 717688f2e..f94a6359b 100644 --- a/internal/ent/post.go +++ b/internal/ent/post.go @@ -72,9 +72,11 @@ type PostEdges struct { Replies []*Post `json:"replies,omitempty"` // Reacts holds the value of the reacts edge. Reacts []*React `json:"reacts,omitempty"` + // Assets holds the value of the assets edge. + Assets []*Asset `json:"assets,omitempty"` // loadedTypes holds the information for reporting if a // type was loaded (or requested) in eager-loading or not. - loadedTypes [8]bool + loadedTypes [9]bool } // AuthorOrErr returns the Author value or an error if the edge @@ -165,6 +167,15 @@ func (e PostEdges) ReactsOrErr() ([]*React, error) { return nil, &NotLoadedError{edge: "reacts"} } +// AssetsOrErr returns the Assets value or an error if the edge +// was not loaded in eager-loading. +func (e PostEdges) AssetsOrErr() ([]*Asset, error) { + if e.loadedTypes[8] { + return e.Assets, nil + } + return nil, &NotLoadedError{edge: "assets"} +} + // scanValues returns the types for scanning values from sql.Rows. func (*Post) scanValues(columns []string) ([]any, error) { values := make([]any, len(columns)) @@ -342,6 +353,11 @@ func (po *Post) QueryReacts() *ReactQuery { return NewPostClient(po.config).QueryReacts(po) } +// QueryAssets queries the "assets" edge of the Post entity. +func (po *Post) QueryAssets() *AssetQuery { + return NewPostClient(po.config).QueryAssets(po) +} + // Update returns a builder for updating this Post. // Note that you need to call Post.Unwrap() before calling this method if this Post // was returned from a transaction, and the transaction was committed or rolled back. diff --git a/internal/ent/post/post.go b/internal/ent/post/post.go index e6135de22..45e046f1d 100644 --- a/internal/ent/post/post.go +++ b/internal/ent/post/post.go @@ -58,6 +58,8 @@ const ( EdgeReplies = "replies" // EdgeReacts holds the string denoting the reacts edge name in mutations. EdgeReacts = "reacts" + // EdgeAssets holds the string denoting the assets edge name in mutations. + EdgeAssets = "assets" // Table holds the table name of the post in the database. Table = "posts" // AuthorTable is the table that holds the author relation/edge. @@ -102,6 +104,13 @@ const ( ReactsInverseTable = "reacts" // ReactsColumn is the table column denoting the reacts relation/edge. ReactsColumn = "post_id" + // AssetsTable is the table that holds the assets relation/edge. + AssetsTable = "assets" + // AssetsInverseTable is the table name for the Asset entity. + // It exists in this package in order to avoid circular dependency with the "asset" package. + AssetsInverseTable = "assets" + // AssetsColumn is the table column denoting the assets relation/edge. + AssetsColumn = "post_id" ) // Columns holds all SQL columns for post fields. diff --git a/internal/ent/post/where.go b/internal/ent/post/where.go index 1fff88d09..bbb89b088 100644 --- a/internal/ent/post/where.go +++ b/internal/ent/post/where.go @@ -1032,6 +1032,33 @@ func HasReactsWith(preds ...predicate.React) predicate.Post { }) } +// HasAssets applies the HasEdge predicate on the "assets" edge. +func HasAssets() predicate.Post { + return predicate.Post(func(s *sql.Selector) { + step := sqlgraph.NewStep( + sqlgraph.From(Table, FieldID), + sqlgraph.Edge(sqlgraph.O2M, false, AssetsTable, AssetsColumn), + ) + sqlgraph.HasNeighbors(s, step) + }) +} + +// HasAssetsWith applies the HasEdge predicate on the "assets" edge with a given conditions (other predicates). +func HasAssetsWith(preds ...predicate.Asset) predicate.Post { + return predicate.Post(func(s *sql.Selector) { + step := sqlgraph.NewStep( + sqlgraph.From(Table, FieldID), + sqlgraph.To(AssetsInverseTable, FieldID), + sqlgraph.Edge(sqlgraph.O2M, false, AssetsTable, AssetsColumn), + ) + sqlgraph.HasNeighborsWith(s, step, func(s *sql.Selector) { + for _, p := range preds { + p(s) + } + }) + }) +} + // And groups predicates with the AND operator between them. func And(predicates ...predicate.Post) predicate.Post { return predicate.Post(func(s *sql.Selector) { diff --git a/internal/ent/post_create.go b/internal/ent/post_create.go index 17f42ed9b..53c20092a 100644 --- a/internal/ent/post_create.go +++ b/internal/ent/post_create.go @@ -13,6 +13,7 @@ import ( "entgo.io/ent/dialect/sql/sqlgraph" "entgo.io/ent/schema/field" "github.com/Southclaws/storyden/internal/ent/account" + "github.com/Southclaws/storyden/internal/ent/asset" "github.com/Southclaws/storyden/internal/ent/category" "github.com/Southclaws/storyden/internal/ent/post" "github.com/Southclaws/storyden/internal/ent/react" @@ -320,6 +321,21 @@ func (pc *PostCreate) AddReacts(r ...*React) *PostCreate { return pc.AddReactIDs(ids...) } +// AddAssetIDs adds the "assets" edge to the Asset entity by IDs. +func (pc *PostCreate) AddAssetIDs(ids ...xid.ID) *PostCreate { + pc.mutation.AddAssetIDs(ids...) + return pc +} + +// AddAssets adds the "assets" edges to the Asset entity. +func (pc *PostCreate) AddAssets(a ...*Asset) *PostCreate { + ids := make([]xid.ID, len(a)) + for i := range a { + ids[i] = a[i].ID + } + return pc.AddAssetIDs(ids...) +} + // Mutation returns the PostMutation object of the builder. func (pc *PostCreate) Mutation() *PostMutation { return pc.mutation @@ -625,6 +641,22 @@ func (pc *PostCreate) createSpec() (*Post, *sqlgraph.CreateSpec) { } _spec.Edges = append(_spec.Edges, edge) } + if nodes := pc.mutation.AssetsIDs(); len(nodes) > 0 { + edge := &sqlgraph.EdgeSpec{ + Rel: sqlgraph.O2M, + Inverse: false, + Table: post.AssetsTable, + Columns: []string{post.AssetsColumn}, + Bidi: false, + Target: &sqlgraph.EdgeTarget{ + IDSpec: sqlgraph.NewFieldSpec(asset.FieldID, field.TypeString), + }, + } + for _, k := range nodes { + edge.Target.Nodes = append(edge.Target.Nodes, k) + } + _spec.Edges = append(_spec.Edges, edge) + } return _node, _spec } diff --git a/internal/ent/post_query.go b/internal/ent/post_query.go index 6817582b8..ff8def4c7 100644 --- a/internal/ent/post_query.go +++ b/internal/ent/post_query.go @@ -12,6 +12,7 @@ import ( "entgo.io/ent/dialect/sql/sqlgraph" "entgo.io/ent/schema/field" "github.com/Southclaws/storyden/internal/ent/account" + "github.com/Southclaws/storyden/internal/ent/asset" "github.com/Southclaws/storyden/internal/ent/category" "github.com/Southclaws/storyden/internal/ent/post" "github.com/Southclaws/storyden/internal/ent/predicate" @@ -35,6 +36,7 @@ type PostQuery struct { withReplyTo *PostQuery withReplies *PostQuery withReacts *ReactQuery + withAssets *AssetQuery withFKs bool modifiers []func(*sql.Selector) // intermediate query (i.e. traversal path). @@ -249,6 +251,28 @@ func (pq *PostQuery) QueryReacts() *ReactQuery { return query } +// QueryAssets chains the current query on the "assets" edge. +func (pq *PostQuery) QueryAssets() *AssetQuery { + query := (&AssetClient{config: pq.config}).Query() + query.path = func(ctx context.Context) (fromU *sql.Selector, err error) { + if err := pq.prepareQuery(ctx); err != nil { + return nil, err + } + selector := pq.sqlQuery(ctx) + if err := selector.Err(); err != nil { + return nil, err + } + step := sqlgraph.NewStep( + sqlgraph.From(post.Table, post.FieldID, selector), + sqlgraph.To(asset.Table, asset.FieldID), + sqlgraph.Edge(sqlgraph.O2M, false, post.AssetsTable, post.AssetsColumn), + ) + fromU = sqlgraph.SetNeighbors(pq.driver.Dialect(), step) + return fromU, nil + } + return query +} + // First returns the first Post entity from the query. // Returns a *NotFoundError when no Post was found. func (pq *PostQuery) First(ctx context.Context) (*Post, error) { @@ -449,6 +473,7 @@ func (pq *PostQuery) Clone() *PostQuery { withReplyTo: pq.withReplyTo.Clone(), withReplies: pq.withReplies.Clone(), withReacts: pq.withReacts.Clone(), + withAssets: pq.withAssets.Clone(), // clone intermediate query. sql: pq.sql.Clone(), path: pq.path, @@ -543,6 +568,17 @@ func (pq *PostQuery) WithReacts(opts ...func(*ReactQuery)) *PostQuery { return pq } +// WithAssets tells the query-builder to eager-load the nodes that are connected to +// the "assets" edge. The optional arguments are used to configure the query builder of the edge. +func (pq *PostQuery) WithAssets(opts ...func(*AssetQuery)) *PostQuery { + query := (&AssetClient{config: pq.config}).Query() + for _, opt := range opts { + opt(query) + } + pq.withAssets = query + return pq +} + // GroupBy is used to group vertices by one or more fields/columns. // It is often used with aggregate functions, like: count, max, mean, min, sum. // @@ -622,7 +658,7 @@ func (pq *PostQuery) sqlAll(ctx context.Context, hooks ...queryHook) ([]*Post, e nodes = []*Post{} withFKs = pq.withFKs _spec = pq.querySpec() - loadedTypes = [8]bool{ + loadedTypes = [9]bool{ pq.withAuthor != nil, pq.withCategory != nil, pq.withTags != nil, @@ -631,6 +667,7 @@ func (pq *PostQuery) sqlAll(ctx context.Context, hooks ...queryHook) ([]*Post, e pq.withReplyTo != nil, pq.withReplies != nil, pq.withReacts != nil, + pq.withAssets != nil, } ) if pq.withAuthor != nil || pq.withCategory != nil || pq.withRoot != nil || pq.withReplyTo != nil { @@ -712,6 +749,13 @@ func (pq *PostQuery) sqlAll(ctx context.Context, hooks ...queryHook) ([]*Post, e return nil, err } } + if query := pq.withAssets; query != nil { + if err := pq.loadAssets(ctx, query, nodes, + func(n *Post) { n.Edges.Assets = []*Asset{} }, + func(n *Post, e *Asset) { n.Edges.Assets = append(n.Edges.Assets, e) }); err != nil { + return nil, err + } + } return nodes, nil } @@ -978,6 +1022,33 @@ func (pq *PostQuery) loadReacts(ctx context.Context, query *ReactQuery, nodes [] } return nil } +func (pq *PostQuery) loadAssets(ctx context.Context, query *AssetQuery, nodes []*Post, init func(*Post), assign func(*Post, *Asset)) error { + fks := make([]driver.Value, 0, len(nodes)) + nodeids := make(map[xid.ID]*Post) + for i := range nodes { + fks = append(fks, nodes[i].ID) + nodeids[nodes[i].ID] = nodes[i] + if init != nil { + init(nodes[i]) + } + } + query.Where(predicate.Asset(func(s *sql.Selector) { + s.Where(sql.InValues(post.AssetsColumn, fks...)) + })) + neighbors, err := query.All(ctx) + if err != nil { + return err + } + for _, n := range neighbors { + fk := n.PostID + node, ok := nodeids[fk] + if !ok { + return fmt.Errorf(`unexpected foreign-key "post_id" returned %v for node %v`, fk, n.ID) + } + assign(node, n) + } + return nil +} func (pq *PostQuery) sqlCount(ctx context.Context) (int, error) { _spec := pq.querySpec() diff --git a/internal/ent/post_update.go b/internal/ent/post_update.go index b4e2c10d6..4d0ff36f7 100644 --- a/internal/ent/post_update.go +++ b/internal/ent/post_update.go @@ -12,6 +12,7 @@ import ( "entgo.io/ent/dialect/sql/sqlgraph" "entgo.io/ent/schema/field" "github.com/Southclaws/storyden/internal/ent/account" + "github.com/Southclaws/storyden/internal/ent/asset" "github.com/Southclaws/storyden/internal/ent/category" "github.com/Southclaws/storyden/internal/ent/post" "github.com/Southclaws/storyden/internal/ent/predicate" @@ -332,6 +333,21 @@ func (pu *PostUpdate) AddReacts(r ...*React) *PostUpdate { return pu.AddReactIDs(ids...) } +// AddAssetIDs adds the "assets" edge to the Asset entity by IDs. +func (pu *PostUpdate) AddAssetIDs(ids ...xid.ID) *PostUpdate { + pu.mutation.AddAssetIDs(ids...) + return pu +} + +// AddAssets adds the "assets" edges to the Asset entity. +func (pu *PostUpdate) AddAssets(a ...*Asset) *PostUpdate { + ids := make([]xid.ID, len(a)) + for i := range a { + ids[i] = a[i].ID + } + return pu.AddAssetIDs(ids...) +} + // Mutation returns the PostMutation object of the builder. func (pu *PostUpdate) Mutation() *PostMutation { return pu.mutation @@ -445,6 +461,27 @@ func (pu *PostUpdate) RemoveReacts(r ...*React) *PostUpdate { return pu.RemoveReactIDs(ids...) } +// ClearAssets clears all "assets" edges to the Asset entity. +func (pu *PostUpdate) ClearAssets() *PostUpdate { + pu.mutation.ClearAssets() + return pu +} + +// RemoveAssetIDs removes the "assets" edge to Asset entities by IDs. +func (pu *PostUpdate) RemoveAssetIDs(ids ...xid.ID) *PostUpdate { + pu.mutation.RemoveAssetIDs(ids...) + return pu +} + +// RemoveAssets removes "assets" edges to Asset entities. +func (pu *PostUpdate) RemoveAssets(a ...*Asset) *PostUpdate { + ids := make([]xid.ID, len(a)) + for i := range a { + ids[i] = a[i].ID + } + return pu.RemoveAssetIDs(ids...) +} + // Save executes the query and returns the number of nodes affected by the update operation. func (pu *PostUpdate) Save(ctx context.Context) (int, error) { pu.defaults() @@ -850,6 +887,51 @@ func (pu *PostUpdate) sqlSave(ctx context.Context) (n int, err error) { } _spec.Edges.Add = append(_spec.Edges.Add, edge) } + if pu.mutation.AssetsCleared() { + edge := &sqlgraph.EdgeSpec{ + Rel: sqlgraph.O2M, + Inverse: false, + Table: post.AssetsTable, + Columns: []string{post.AssetsColumn}, + Bidi: false, + Target: &sqlgraph.EdgeTarget{ + IDSpec: sqlgraph.NewFieldSpec(asset.FieldID, field.TypeString), + }, + } + _spec.Edges.Clear = append(_spec.Edges.Clear, edge) + } + if nodes := pu.mutation.RemovedAssetsIDs(); len(nodes) > 0 && !pu.mutation.AssetsCleared() { + edge := &sqlgraph.EdgeSpec{ + Rel: sqlgraph.O2M, + Inverse: false, + Table: post.AssetsTable, + Columns: []string{post.AssetsColumn}, + Bidi: false, + Target: &sqlgraph.EdgeTarget{ + IDSpec: sqlgraph.NewFieldSpec(asset.FieldID, field.TypeString), + }, + } + for _, k := range nodes { + edge.Target.Nodes = append(edge.Target.Nodes, k) + } + _spec.Edges.Clear = append(_spec.Edges.Clear, edge) + } + if nodes := pu.mutation.AssetsIDs(); len(nodes) > 0 { + edge := &sqlgraph.EdgeSpec{ + Rel: sqlgraph.O2M, + Inverse: false, + Table: post.AssetsTable, + Columns: []string{post.AssetsColumn}, + Bidi: false, + Target: &sqlgraph.EdgeTarget{ + IDSpec: sqlgraph.NewFieldSpec(asset.FieldID, field.TypeString), + }, + } + for _, k := range nodes { + edge.Target.Nodes = append(edge.Target.Nodes, k) + } + _spec.Edges.Add = append(_spec.Edges.Add, edge) + } _spec.AddModifiers(pu.modifiers...) if n, err = sqlgraph.UpdateNodes(ctx, pu.driver, _spec); err != nil { if _, ok := err.(*sqlgraph.NotFoundError); ok { @@ -1170,6 +1252,21 @@ func (puo *PostUpdateOne) AddReacts(r ...*React) *PostUpdateOne { return puo.AddReactIDs(ids...) } +// AddAssetIDs adds the "assets" edge to the Asset entity by IDs. +func (puo *PostUpdateOne) AddAssetIDs(ids ...xid.ID) *PostUpdateOne { + puo.mutation.AddAssetIDs(ids...) + return puo +} + +// AddAssets adds the "assets" edges to the Asset entity. +func (puo *PostUpdateOne) AddAssets(a ...*Asset) *PostUpdateOne { + ids := make([]xid.ID, len(a)) + for i := range a { + ids[i] = a[i].ID + } + return puo.AddAssetIDs(ids...) +} + // Mutation returns the PostMutation object of the builder. func (puo *PostUpdateOne) Mutation() *PostMutation { return puo.mutation @@ -1283,6 +1380,27 @@ func (puo *PostUpdateOne) RemoveReacts(r ...*React) *PostUpdateOne { return puo.RemoveReactIDs(ids...) } +// ClearAssets clears all "assets" edges to the Asset entity. +func (puo *PostUpdateOne) ClearAssets() *PostUpdateOne { + puo.mutation.ClearAssets() + return puo +} + +// RemoveAssetIDs removes the "assets" edge to Asset entities by IDs. +func (puo *PostUpdateOne) RemoveAssetIDs(ids ...xid.ID) *PostUpdateOne { + puo.mutation.RemoveAssetIDs(ids...) + return puo +} + +// RemoveAssets removes "assets" edges to Asset entities. +func (puo *PostUpdateOne) RemoveAssets(a ...*Asset) *PostUpdateOne { + ids := make([]xid.ID, len(a)) + for i := range a { + ids[i] = a[i].ID + } + return puo.RemoveAssetIDs(ids...) +} + // Where appends a list predicates to the PostUpdate builder. func (puo *PostUpdateOne) Where(ps ...predicate.Post) *PostUpdateOne { puo.mutation.Where(ps...) @@ -1718,6 +1836,51 @@ func (puo *PostUpdateOne) sqlSave(ctx context.Context) (_node *Post, err error) } _spec.Edges.Add = append(_spec.Edges.Add, edge) } + if puo.mutation.AssetsCleared() { + edge := &sqlgraph.EdgeSpec{ + Rel: sqlgraph.O2M, + Inverse: false, + Table: post.AssetsTable, + Columns: []string{post.AssetsColumn}, + Bidi: false, + Target: &sqlgraph.EdgeTarget{ + IDSpec: sqlgraph.NewFieldSpec(asset.FieldID, field.TypeString), + }, + } + _spec.Edges.Clear = append(_spec.Edges.Clear, edge) + } + if nodes := puo.mutation.RemovedAssetsIDs(); len(nodes) > 0 && !puo.mutation.AssetsCleared() { + edge := &sqlgraph.EdgeSpec{ + Rel: sqlgraph.O2M, + Inverse: false, + Table: post.AssetsTable, + Columns: []string{post.AssetsColumn}, + Bidi: false, + Target: &sqlgraph.EdgeTarget{ + IDSpec: sqlgraph.NewFieldSpec(asset.FieldID, field.TypeString), + }, + } + for _, k := range nodes { + edge.Target.Nodes = append(edge.Target.Nodes, k) + } + _spec.Edges.Clear = append(_spec.Edges.Clear, edge) + } + if nodes := puo.mutation.AssetsIDs(); len(nodes) > 0 { + edge := &sqlgraph.EdgeSpec{ + Rel: sqlgraph.O2M, + Inverse: false, + Table: post.AssetsTable, + Columns: []string{post.AssetsColumn}, + Bidi: false, + Target: &sqlgraph.EdgeTarget{ + IDSpec: sqlgraph.NewFieldSpec(asset.FieldID, field.TypeString), + }, + } + for _, k := range nodes { + edge.Target.Nodes = append(edge.Target.Nodes, k) + } + _spec.Edges.Add = append(_spec.Edges.Add, edge) + } _spec.AddModifiers(puo.modifiers...) _node = &Post{config: puo.config} _spec.Assign = _node.assignValues diff --git a/internal/ent/predicate/predicate.go b/internal/ent/predicate/predicate.go index e348a2e6d..25b6636cb 100644 --- a/internal/ent/predicate/predicate.go +++ b/internal/ent/predicate/predicate.go @@ -9,6 +9,9 @@ import ( // Account is the predicate function for account builders. type Account func(*sql.Selector) +// Asset is the predicate function for asset builders. +type Asset func(*sql.Selector) + // Authentication is the predicate function for authentication builders. type Authentication func(*sql.Selector) diff --git a/internal/ent/runtime.go b/internal/ent/runtime.go index 01fdf9e34..816d14115 100644 --- a/internal/ent/runtime.go +++ b/internal/ent/runtime.go @@ -6,6 +6,7 @@ import ( "time" "github.com/Southclaws/storyden/internal/ent/account" + "github.com/Southclaws/storyden/internal/ent/asset" "github.com/Southclaws/storyden/internal/ent/authentication" "github.com/Southclaws/storyden/internal/ent/category" "github.com/Southclaws/storyden/internal/ent/notification" @@ -73,6 +74,45 @@ func init() { return nil } }() + assetMixin := schema.Asset{}.Mixin() + assetMixinFields0 := assetMixin[0].Fields() + _ = assetMixinFields0 + assetMixinFields1 := assetMixin[1].Fields() + _ = assetMixinFields1 + assetMixinFields2 := assetMixin[2].Fields() + _ = assetMixinFields2 + assetFields := schema.Asset{}.Fields() + _ = assetFields + // assetDescCreatedAt is the schema descriptor for created_at field. + assetDescCreatedAt := assetMixinFields1[0].Descriptor() + // asset.DefaultCreatedAt holds the default value on creation for the created_at field. + asset.DefaultCreatedAt = assetDescCreatedAt.Default.(func() time.Time) + // assetDescUpdatedAt is the schema descriptor for updated_at field. + assetDescUpdatedAt := assetMixinFields2[0].Descriptor() + // asset.DefaultUpdatedAt holds the default value on creation for the updated_at field. + asset.DefaultUpdatedAt = assetDescUpdatedAt.Default.(func() time.Time) + // asset.UpdateDefaultUpdatedAt holds the default value on update for the updated_at field. + asset.UpdateDefaultUpdatedAt = assetDescUpdatedAt.UpdateDefault.(func() time.Time) + // assetDescID is the schema descriptor for id field. + assetDescID := assetMixinFields0[0].Descriptor() + // asset.DefaultID holds the default value on creation for the id field. + asset.DefaultID = assetDescID.Default.(func() xid.ID) + // asset.IDValidator is a validator for the "id" field. It is called by the builders before save. + asset.IDValidator = func() func(string) error { + validators := assetDescID.Validators + fns := [...]func(string) error{ + validators[0].(func(string) error), + validators[1].(func(string) error), + } + return func(id string) error { + for _, fn := range fns { + if err := fn(id); err != nil { + return err + } + } + return nil + } + }() authenticationMixin := schema.Authentication{}.Mixin() authenticationMixinFields0 := authenticationMixin[0].Fields() _ = authenticationMixinFields0 diff --git a/internal/ent/schema/asset.go b/internal/ent/schema/asset.go new file mode 100644 index 000000000..a15a2480a --- /dev/null +++ b/internal/ent/schema/asset.go @@ -0,0 +1,39 @@ +package schema + +import ( + "entgo.io/ent" + "entgo.io/ent/schema/edge" + "entgo.io/ent/schema/field" + "github.com/rs/xid" +) + +type Asset struct { + ent.Schema +} + +func (Asset) Mixin() []ent.Mixin { + return []ent.Mixin{Identifier{}, CreatedAt{}, UpdatedAt{}} +} + +func (Asset) Fields() []ent.Field { + return []ent.Field{ + field.String("url"), + field.String("mimetype"), + field.Int("width"), + field.Int("height"), + + // Edges + field.String("post_id").GoType(xid.ID{}), + } +} + +// Edges of Asset. +func (Asset) Edges() []ent.Edge { + return []ent.Edge{ + edge.From("post", Post.Type). + Field("post_id"). + Ref("assets"). + Unique(). + Required(), + } +} diff --git a/internal/ent/schema/post.go b/internal/ent/schema/post.go index cde4191f3..3e4e7929b 100644 --- a/internal/ent/schema/post.go +++ b/internal/ent/schema/post.go @@ -75,5 +75,7 @@ func (Post) Edges() []ent.Edge { Comment("A many-to-many recursive self reference. The replyTo post is an optional post that this post is in reply to."), edge.To("reacts", React.Type), + + edge.To("assets", Asset.Type), } } diff --git a/internal/ent/tx.go b/internal/ent/tx.go index 87495dd30..b4b57b1ac 100644 --- a/internal/ent/tx.go +++ b/internal/ent/tx.go @@ -14,6 +14,8 @@ type Tx struct { config // Account is the client for interacting with the Account builders. Account *AccountClient + // Asset is the client for interacting with the Asset builders. + Asset *AssetClient // Authentication is the client for interacting with the Authentication builders. Authentication *AuthenticationClient // Category is the client for interacting with the Category builders. @@ -162,6 +164,7 @@ func (tx *Tx) Client() *Client { func (tx *Tx) init() { tx.Account = NewAccountClient(tx.config) + tx.Asset = NewAssetClient(tx.config) tx.Authentication = NewAuthenticationClient(tx.config) tx.Category = NewCategoryClient(tx.config) tx.Notification = NewNotificationClient(tx.config) diff --git a/internal/openapi/generated.go b/internal/openapi/generated.go index 79446d0c2..1bd019d67 100644 --- a/internal/openapi/generated.go +++ b/internal/openapi/generated.go @@ -327,6 +327,17 @@ type Info struct { Title string `json:"title"` } +// MediaItem defines model for MediaItem. +type MediaItem struct { + Height float32 `json:"height"` + MimeType string `json:"mime_type"` + Url string `json:"url"` + Width float32 `json:"width"` +} + +// MediaItemList defines model for MediaItemList. +type MediaItemList = []MediaItem + // Metadata Arbitrary metadata for the resource. type Metadata map[string]interface{} @@ -359,7 +370,8 @@ type PostCommonProps struct { // an object, depending on what was used during creation. Strings can be // used for basic plain text or markdown content and objects are used for // more complex types such as Slate.js editor documents. - Body PostContent `json:"body"` + Body PostContent `json:"body"` + Media MediaItemList `json:"media"` // Meta Arbitrary metadata for the resource. Meta *Metadata `json:"meta,omitempty"` @@ -437,7 +449,8 @@ type PostProps struct { DeletedAt *time.Time `json:"deletedAt,omitempty"` // Id A unique identifier for this resource. - Id Identifier `json:"id"` + Id Identifier `json:"id"` + Media MediaItemList `json:"media"` // Meta Arbitrary metadata for the resource. Meta *Metadata `json:"meta,omitempty"` @@ -647,7 +660,8 @@ type Thread struct { DeletedAt *time.Time `json:"deletedAt,omitempty"` // Id A unique identifier for this resource. - Id Identifier `json:"id"` + Id Identifier `json:"id"` + Media MediaItemList `json:"media"` // Meta Arbitrary metadata for the resource. Meta Metadata `json:"meta"` @@ -761,7 +775,8 @@ type ThreadReference struct { DeletedAt *time.Time `json:"deletedAt,omitempty"` // Id A unique identifier for this resource. - Id Identifier `json:"id"` + Id Identifier `json:"id"` + Media MediaItemList `json:"media"` // Meta Arbitrary metadata for the resource. Meta Metadata `json:"meta"` @@ -827,6 +842,9 @@ type OAuthProvider = string // PostIDParam A unique identifier for this resource. type PostIDParam = Identifier +// PostIDQueryParam A unique identifier for this resource. +type PostIDQueryParam = Identifier + // ThreadMarkParam A thread's ID and optional slug separated by a dash = it's unique mark. // This allows endpoints to respond to varying forms of a thread's ID. // @@ -942,6 +960,12 @@ type WebAuthnMakeAssertion = PublicKeyCredential // WebAuthnMakeCredential https://www.w3.org/TR/webauthn-2/#iface-pkcredential type WebAuthnMakeCredential = PublicKeyCredential +// AssetUploadParams defines parameters for AssetUpload. +type AssetUploadParams struct { + // PostId Unique post ID. + PostId PostIDQueryParam `form:"post_id" json:"post_id"` +} + // PostSearchParams defines parameters for PostSearch. type PostSearchParams struct { // Body A text query to search for in post content. @@ -1099,7 +1123,7 @@ type ClientInterface interface { AssetGetUploadURL(ctx context.Context, reqEditors ...RequestEditorFn) (*http.Response, error) // AssetUpload request with any body - AssetUploadWithBody(ctx context.Context, contentType string, body io.Reader, reqEditors ...RequestEditorFn) (*http.Response, error) + AssetUploadWithBody(ctx context.Context, params *AssetUploadParams, contentType string, body io.Reader, reqEditors ...RequestEditorFn) (*http.Response, error) // AssetGet request AssetGet(ctx context.Context, id AssetPath, reqEditors ...RequestEditorFn) (*http.Response, error) @@ -1288,8 +1312,8 @@ func (c *Client) AssetGetUploadURL(ctx context.Context, reqEditors ...RequestEdi return c.Client.Do(req) } -func (c *Client) AssetUploadWithBody(ctx context.Context, contentType string, body io.Reader, reqEditors ...RequestEditorFn) (*http.Response, error) { - req, err := NewAssetUploadRequestWithBody(c.Server, contentType, body) +func (c *Client) AssetUploadWithBody(ctx context.Context, params *AssetUploadParams, contentType string, body io.Reader, reqEditors ...RequestEditorFn) (*http.Response, error) { + req, err := NewAssetUploadRequestWithBody(c.Server, params, contentType, body) if err != nil { return nil, err } @@ -1941,7 +1965,7 @@ func NewAssetGetUploadURLRequest(server string) (*http.Request, error) { } // NewAssetUploadRequestWithBody generates requests for AssetUpload with any type of body -func NewAssetUploadRequestWithBody(server string, contentType string, body io.Reader) (*http.Request, error) { +func NewAssetUploadRequestWithBody(server string, params *AssetUploadParams, contentType string, body io.Reader) (*http.Request, error) { var err error serverURL, err := url.Parse(server) @@ -1959,6 +1983,22 @@ func NewAssetUploadRequestWithBody(server string, contentType string, body io.Re return nil, err } + queryValues := queryURL.Query() + + if queryFrag, err := runtime.StyleParamWithLocation("form", true, "post_id", runtime.ParamLocationQuery, params.PostId); err != nil { + return nil, err + } else if parsed, err := url.ParseQuery(queryFrag); err != nil { + return nil, err + } else { + for k, v := range parsed { + for _, v2 := range v { + queryValues.Add(k, v2) + } + } + } + + queryURL.RawQuery = queryValues.Encode() + req, err := http.NewRequest("POST", queryURL.String(), body) if err != nil { return nil, err @@ -3086,7 +3126,7 @@ type ClientWithResponsesInterface interface { AssetGetUploadURLWithResponse(ctx context.Context, reqEditors ...RequestEditorFn) (*AssetGetUploadURLResponse, error) // AssetUpload request with any body - AssetUploadWithBodyWithResponse(ctx context.Context, contentType string, body io.Reader, reqEditors ...RequestEditorFn) (*AssetUploadResponse, error) + AssetUploadWithBodyWithResponse(ctx context.Context, params *AssetUploadParams, contentType string, body io.Reader, reqEditors ...RequestEditorFn) (*AssetUploadResponse, error) // AssetGet request AssetGetWithResponse(ctx context.Context, id AssetPath, reqEditors ...RequestEditorFn) (*AssetGetResponse, error) @@ -4007,8 +4047,8 @@ func (c *ClientWithResponses) AssetGetUploadURLWithResponse(ctx context.Context, } // AssetUploadWithBodyWithResponse request with arbitrary body returning *AssetUploadResponse -func (c *ClientWithResponses) AssetUploadWithBodyWithResponse(ctx context.Context, contentType string, body io.Reader, reqEditors ...RequestEditorFn) (*AssetUploadResponse, error) { - rsp, err := c.AssetUploadWithBody(ctx, contentType, body, reqEditors...) +func (c *ClientWithResponses) AssetUploadWithBodyWithResponse(ctx context.Context, params *AssetUploadParams, contentType string, body io.Reader, reqEditors ...RequestEditorFn) (*AssetUploadResponse, error) { + rsp, err := c.AssetUploadWithBody(ctx, params, contentType, body, reqEditors...) if err != nil { return nil, err } @@ -5384,7 +5424,7 @@ type ServerInterface interface { AssetGetUploadURL(ctx echo.Context) error // (POST /v1/assets) - AssetUpload(ctx echo.Context) error + AssetUpload(ctx echo.Context, params AssetUploadParams) error // (GET /v1/assets/{id}) AssetGet(ctx echo.Context, id AssetPath) error @@ -5545,8 +5585,17 @@ func (w *ServerInterfaceWrapper) AssetUpload(ctx echo.Context) error { ctx.Set(BrowserScopes, []string{""}) + // Parameter object where we will unmarshal all parameters from the context + var params AssetUploadParams + // ------------- Required query parameter "post_id" ------------- + + err = runtime.BindQueryParameter("form", true, true, "post_id", ctx.QueryParams(), ¶ms.PostId) + if err != nil { + return echo.NewHTTPError(http.StatusBadRequest, fmt.Sprintf("Invalid format for parameter post_id: %s", err)) + } + // Invoke the callback with all the unmarshalled arguments - err = w.Handler.AssetUpload(ctx) + err = w.Handler.AssetUpload(ctx, params) return err } @@ -6326,7 +6375,8 @@ func (response AssetGetUploadURLdefaultJSONResponse) VisitAssetGetUploadURLRespo } type AssetUploadRequestObject struct { - Body io.Reader + Params AssetUploadParams + Body io.Reader } type AssetUploadResponseObject interface { @@ -7666,9 +7716,11 @@ func (sh *strictHandler) AssetGetUploadURL(ctx echo.Context) error { } // AssetUpload operation middleware -func (sh *strictHandler) AssetUpload(ctx echo.Context) error { +func (sh *strictHandler) AssetUpload(ctx echo.Context, params AssetUploadParams) error { var request AssetUploadRequestObject + request.Params = params + request.Body = ctx.Request().Body handler := func(ctx echo.Context, request interface{}) (interface{}, error) { @@ -8393,118 +8445,120 @@ func (sh *strictHandler) GetVersion(ctx echo.Context) error { // Base64 encoded, gzipped, json marshaled Swagger object var swaggerSpec = []string{ - "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", + "vaOeAAA=", } // GetSwagger returns the content of the embedded swagger specification file diff --git a/web/src/api/openapi/assets.ts b/web/src/api/openapi/assets.ts index c07118824..88d6b5784 100644 --- a/web/src/api/openapi/assets.ts +++ b/web/src/api/openapi/assets.ts @@ -15,6 +15,7 @@ import type { AssetGetUploadURLOKResponse, AssetUploadBody, AssetUploadOKResponse, + AssetUploadParams, InternalServerErrorResponse, NotFoundResponse, UnauthorisedResponse, @@ -73,12 +74,16 @@ equivalent of S3's pre-signed upload URL. Files uploaded to this endpoint will be stored on the local filesystem instead of the cloud. */ -export const assetUpload = (assetUploadBody: AssetUploadBody) => { +export const assetUpload = ( + assetUploadBody: AssetUploadBody, + params: AssetUploadParams +) => { return fetcher({ url: `/v1/assets`, method: "post", headers: { "Content-Type": "application/octet-stream" }, data: assetUploadBody, + params, }); }; diff --git a/web/src/api/openapi/schemas/assetUploadParams.ts b/web/src/api/openapi/schemas/assetUploadParams.ts new file mode 100644 index 000000000..38cf068f0 --- /dev/null +++ b/web/src/api/openapi/schemas/assetUploadParams.ts @@ -0,0 +1,15 @@ +/** + * Generated by orval v6.15.0 🍺 + * Do not edit manually. + * storyden + * Storyden social API for building community driven platforms. + * OpenAPI spec version: 1 + */ +import type { PostIDQueryParamParameter } from "./postIDQueryParamParameter"; + +export type AssetUploadParams = { + /** + * Unique post ID. + */ + post_id: PostIDQueryParamParameter; +}; diff --git a/web/src/api/openapi/schemas/index.ts b/web/src/api/openapi/schemas/index.ts index 84302d754..0c30b3c4c 100644 --- a/web/src/api/openapi/schemas/index.ts +++ b/web/src/api/openapi/schemas/index.ts @@ -23,6 +23,7 @@ export * from "./assetGetOKResponse"; export * from "./assetGetUploadURLOKResponse"; export * from "./assetUploadBody"; export * from "./assetUploadOKResponse"; +export * from "./assetUploadParams"; export * from "./assetUploadURL"; export * from "./attestationConveyancePreference"; export * from "./authPair"; @@ -55,6 +56,8 @@ export * from "./getInfoOKResponse"; export * from "./identifier"; export * from "./info"; export * from "./internalServerErrorResponse"; +export * from "./mediaItem"; +export * from "./mediaItemList"; export * from "./metadata"; export * from "./notFoundResponse"; export * from "./oAuthCallback"; @@ -67,6 +70,7 @@ export * from "./postCommonProps"; export * from "./postContent"; export * from "./postCreateBody"; export * from "./postCreateOKResponse"; +export * from "./postIDQueryParamParameter"; export * from "./postInitialProps"; export * from "./postMetadata"; export * from "./postMutableProps"; diff --git a/web/src/api/openapi/schemas/mediaItem.ts b/web/src/api/openapi/schemas/mediaItem.ts new file mode 100644 index 000000000..8a29dd431 --- /dev/null +++ b/web/src/api/openapi/schemas/mediaItem.ts @@ -0,0 +1,14 @@ +/** + * Generated by orval v6.15.0 🍺 + * Do not edit manually. + * storyden + * Storyden social API for building community driven platforms. + * OpenAPI spec version: 1 + */ + +export interface MediaItem { + url: string; + mime_type: string; + width: number; + height: number; +} diff --git a/web/src/api/openapi/schemas/mediaItemList.ts b/web/src/api/openapi/schemas/mediaItemList.ts new file mode 100644 index 000000000..951d42d90 --- /dev/null +++ b/web/src/api/openapi/schemas/mediaItemList.ts @@ -0,0 +1,10 @@ +/** + * Generated by orval v6.15.0 🍺 + * Do not edit manually. + * storyden + * Storyden social API for building community driven platforms. + * OpenAPI spec version: 1 + */ +import type { MediaItem } from "./mediaItem"; + +export type MediaItemList = MediaItem[]; diff --git a/web/src/api/openapi/schemas/postCommonProps.ts b/web/src/api/openapi/schemas/postCommonProps.ts index 233e6acdc..04c933e60 100644 --- a/web/src/api/openapi/schemas/postCommonProps.ts +++ b/web/src/api/openapi/schemas/postCommonProps.ts @@ -6,6 +6,7 @@ * OpenAPI spec version: 1 */ import type { Identifier } from "./identifier"; +import type { MediaItemList } from "./mediaItemList"; import type { Metadata } from "./metadata"; import type { PostContent } from "./postContent"; import type { ProfileReference } from "./profileReference"; @@ -20,4 +21,5 @@ export interface PostCommonProps { meta?: Metadata; reacts: ReactList; reply_to?: Identifier; + media: MediaItemList; } diff --git a/web/src/api/openapi/schemas/postIDQueryParamParameter.ts b/web/src/api/openapi/schemas/postIDQueryParamParameter.ts new file mode 100644 index 000000000..4e215d097 --- /dev/null +++ b/web/src/api/openapi/schemas/postIDQueryParamParameter.ts @@ -0,0 +1,12 @@ +/** + * Generated by orval v6.15.0 🍺 + * Do not edit manually. + * storyden + * Storyden social API for building community driven platforms. + * OpenAPI spec version: 1 + */ + +/** + * Unique post ID. + */ +export type PostIDQueryParamParameter = string; diff --git a/web/src/api/openapi/schemas/threadReferenceAllOf.ts b/web/src/api/openapi/schemas/threadReferenceAllOf.ts index 0441ae078..fa2b5bc3c 100644 --- a/web/src/api/openapi/schemas/threadReferenceAllOf.ts +++ b/web/src/api/openapi/schemas/threadReferenceAllOf.ts @@ -6,6 +6,7 @@ * OpenAPI spec version: 1 */ import type { CategoryReference } from "./categoryReference"; +import type { MediaItemList } from "./mediaItemList"; import type { Metadata } from "./metadata"; import type { ProfileReference } from "./profileReference"; import type { ReactList } from "./reactList"; @@ -28,4 +29,5 @@ export type ThreadReferenceAllOf = { category: CategoryReference; reacts: ReactList; meta: Metadata; + media: MediaItemList; }; From aba1cfb481f76054f6b8307db018353982408a65 Mon Sep 17 00:00:00 2001 From: Barnaby Keene Date: Sat, 15 Jul 2023 20:22:23 +0100 Subject: [PATCH 2/5] redesign how assets relate to posts --- api/openapi.yaml | 59 +- app/resources/asset/db.go | 60 ++ app/resources/asset/dto.go | 4 + app/resources/asset/repo.go | 17 + app/resources/post/repo.go | 2 +- app/resources/resources.go | 2 + app/services/asset/service.go | 63 ++- app/services/services.go | 2 + app/transports/openapi/bindings/assets.go | 22 +- app/transports/openapi/bindings/threads.go | 4 + app/transports/openapi/bindings/utils.go | 5 +- go.mod | 1 + go.sum | 6 + internal/ent/account.go | 18 +- internal/ent/account/account.go | 9 + internal/ent/account/where.go | 27 + internal/ent/account_create.go | 32 ++ internal/ent/account_query.go | 73 ++- internal/ent/account_update.go | 163 ++++++ internal/ent/asset.go | 46 +- internal/ent/asset/asset.go | 16 +- internal/ent/asset/where.go | 140 ++++- internal/ent/asset_create.go | 139 ++++- internal/ent/asset_query.go | 90 ++- internal/ent/asset_update.go | 141 ++++- internal/ent/client.go | 40 +- internal/ent/migrate/schema.go | 18 +- internal/ent/mutation.go | 263 ++++++++- internal/ent/post_create.go | 4 +- internal/ent/post_update.go | 16 +- internal/ent/runtime.go | 26 +- internal/ent/schema/account.go | 2 + internal/ent/schema/asset.go | 15 +- internal/openapi/generated.go | 530 +++++------------- web/src/api/openapi/assets.ts | 55 +- web/src/api/openapi/schemas/asset.ts | 5 + .../schemas/assetGetUploadURLOKResponse.ts | 13 - ...ostIDQueryParamParameter.ts => assetID.ts} | 6 +- .../{assetUploadURL.ts => assetList.ts} | 5 +- ...mediaItemList.ts => assetReferenceList.ts} | 4 +- .../api/openapi/schemas/assetUploadParams.ts | 15 - web/src/api/openapi/schemas/index.ts | 9 +- web/src/api/openapi/schemas/mediaItem.ts | 14 - .../api/openapi/schemas/postCommonProps.ts | 4 +- .../api/openapi/schemas/threadInitialProps.ts | 2 + .../api/openapi/schemas/threadMutableProps.ts | 2 + .../openapi/schemas/threadReferenceAllOf.ts | 4 +- .../ContentComposer/ContentComposer.tsx | 2 +- .../components/FileDrop/FileDrop.tsx | 6 +- .../components/FileDrop/useFileDrop.ts | 25 +- .../ContentComposer/useContentComposer.ts | 3 + .../components/BodyInput/BodyInput.tsx | 13 +- .../components/ComposeForm/ComposeForm.tsx | 5 +- .../components/ComposeForm/useComposeForm.ts | 31 +- .../thread/components/PostView/PostView.tsx | 2 +- 55 files changed, 1545 insertions(+), 735 deletions(-) create mode 100644 app/resources/asset/db.go create mode 100644 app/resources/asset/repo.go delete mode 100644 web/src/api/openapi/schemas/assetGetUploadURLOKResponse.ts rename web/src/api/openapi/schemas/{postIDQueryParamParameter.ts => assetID.ts} (67%) rename web/src/api/openapi/schemas/{assetUploadURL.ts => assetList.ts} (70%) rename web/src/api/openapi/schemas/{mediaItemList.ts => assetReferenceList.ts} (66%) delete mode 100644 web/src/api/openapi/schemas/assetUploadParams.ts delete mode 100644 web/src/api/openapi/schemas/mediaItem.ts diff --git a/api/openapi.yaml b/api/openapi.yaml index 0fa638974..8f5f08474 100644 --- a/api/openapi.yaml +++ b/api/openapi.yaml @@ -576,14 +576,6 @@ paths: # /v1/assets: - get: - operationId: AssetGetUploadURL - description: Get an upload URL for a new asset. - tags: [assets] - responses: - default: { $ref: "#/components/responses/InternalServerError" } - "401": { $ref: "#/components/responses/Unauthorised" } - "200": { $ref: "#/components/responses/AssetGetUploadURLOK" } post: operationId: AssetUpload description: | @@ -591,7 +583,6 @@ paths: equivalent of S3's pre-signed upload URL. Files uploaded to this endpoint will be stored on the local filesystem instead of the cloud. tags: [assets] - parameters: [$ref: "#/components/parameters/PostIDQueryParam"] requestBody: { $ref: "#/components/requestBodies/AssetUpload" } responses: default: { $ref: "#/components/responses/InternalServerError" } @@ -648,14 +639,6 @@ components: schema: $ref: "#/components/schemas/Identifier" - PostIDQueryParam: - description: Unique post ID. - name: post_id - in: query - required: true - schema: - $ref: "#/components/schemas/Identifier" - OAuthProvider: description: The identifier for an OAuth2 provider such as "twitter". name: oauth_provider @@ -933,13 +916,6 @@ components: schema: $ref: "#/components/schemas/React" - AssetGetUploadURLOK: - description: A pre-authorised upload URL. - content: - application/json: - schema: - $ref: "#/components/schemas/AssetUploadURL" - AssetUploadOK: description: The new URL of an uploaded file. content: @@ -1614,6 +1590,7 @@ components: meta: { $ref: "#/components/schemas/Metadata" } category: { $ref: "#/components/schemas/Identifier" } status: { $ref: "#/components/schemas/ThreadStatus" } + assets: { $ref: "#/components/schemas/AssetReferenceList" } ThreadMutableProps: type: object @@ -1624,6 +1601,7 @@ components: meta: { $ref: "#/components/schemas/Metadata" } category: { $ref: "#/components/schemas/Identifier" } status: { $ref: "#/components/schemas/ThreadStatus" } + assets: { $ref: "#/components/schemas/AssetReferenceList" } ThreadReference: description: | @@ -1673,7 +1651,7 @@ components: reacts: $ref: "#/components/schemas/ReactList" meta: { $ref: "#/components/schemas/Metadata" } - media: { $ref: "#/components/schemas/MediaItemList" } + media: { $ref: "#/components/schemas/AssetList" } ThreadList: type: array @@ -1728,7 +1706,7 @@ components: meta: { $ref: "#/components/schemas/Metadata" } reacts: { $ref: "#/components/schemas/ReactList" } reply_to: { $ref: "#/components/schemas/Identifier" } - media: { $ref: "#/components/schemas/MediaItemList" } + media: { $ref: "#/components/schemas/AssetList" } PostInitialProps: type: object @@ -1775,13 +1753,6 @@ components: items: $ref: "#/components/schemas/PostProps" - AssetUploadURL: - type: object - required: [url] - properties: - url: - type: string - # # 888b d888 888 d8b # 8888b d8888 888 Y8P @@ -1793,21 +1764,23 @@ components: # 888 888 "Y8888 "Y88888 888 "Y888888 # - Asset: - type: object - required: [url] - properties: - url: - type: string + AssetID: + $ref: "#/components/schemas/Identifier" - MediaItemList: + AssetReferenceList: type: array - items: { $ref: "#/components/schemas/MediaItem" } + items: { $ref: "#/components/schemas/AssetID" } - MediaItem: + AssetList: + type: array + items: { $ref: "#/components/schemas/Asset" } + + Asset: type: object - required: [url, mime_type, width, height] + required: [id, url, mime_type, width, height] properties: + id: + $ref: "#/components/schemas/AssetID" url: type: string mime_type: diff --git a/app/resources/asset/db.go b/app/resources/asset/db.go new file mode 100644 index 000000000..796b11ff0 --- /dev/null +++ b/app/resources/asset/db.go @@ -0,0 +1,60 @@ +package asset + +import ( + "context" + + "github.com/Southclaws/fault" + "github.com/Southclaws/fault/fctx" + "github.com/Southclaws/fault/ftag" + "github.com/rs/xid" + + "github.com/Southclaws/storyden/app/resources/account" + "github.com/Southclaws/storyden/internal/ent" +) + +type database struct { + db *ent.Client +} + +func New(db *ent.Client) Repository { + return &database{db} +} + +func (d *database) Add(ctx context.Context, + accountID account.AccountID, + id, url, mt string, + width, height int, +) (*Asset, error) { + asset, err := d.db.Asset.Get(ctx, id) + if err != nil && !ent.IsNotFound(err) { + return nil, fault.Wrap(err, fctx.With(ctx)) + } + + if asset == nil { + asset, err = d.db.Asset. + Create(). + SetID(id). + SetURL(url). + SetWidth(width). + SetHeight(height). + SetMimetype(mt). + SetAccountID(xid.ID(accountID)). + Save(ctx) + if err != nil { + return nil, fault.Wrap(err, fctx.With(ctx), ftag.With(ftag.Internal)) + } + } + + return FromModel(asset), nil +} + +func (d *database) Remove(ctx context.Context, accountID account.AccountID, id AssetID) error { + q := d.db.Asset. + DeleteOneID(string(id)) + + if err := q.Exec(ctx); err != nil { + return fault.Wrap(err, fctx.With(ctx), ftag.With(ftag.Internal)) + } + + return nil +} diff --git a/app/resources/asset/dto.go b/app/resources/asset/dto.go index 46b8dac66..302d482b6 100644 --- a/app/resources/asset/dto.go +++ b/app/resources/asset/dto.go @@ -4,7 +4,10 @@ import ( "github.com/Southclaws/storyden/internal/ent" ) +type AssetID string + type Asset struct { + ID AssetID URL string MIMEType string Width int @@ -13,6 +16,7 @@ type Asset struct { func FromModel(a *ent.Asset) *Asset { return &Asset{ + ID: AssetID(a.ID), URL: a.URL, MIMEType: a.Mimetype, Width: a.Width, diff --git a/app/resources/asset/repo.go b/app/resources/asset/repo.go new file mode 100644 index 000000000..998e52f8d --- /dev/null +++ b/app/resources/asset/repo.go @@ -0,0 +1,17 @@ +package asset + +import ( + "context" + + "github.com/Southclaws/storyden/app/resources/account" +) + +type Repository interface { + Add(ctx context.Context, + owner account.AccountID, + id, url, mt string, + width, height int, + ) (*Asset, error) + + Remove(ctx context.Context, owner account.AccountID, id AssetID) error +} diff --git a/app/resources/post/repo.go b/app/resources/post/repo.go index 16691bb38..511dfc6fa 100644 --- a/app/resources/post/repo.go +++ b/app/resources/post/repo.go @@ -48,7 +48,7 @@ func WithMeta(meta map[string]any) Option { } } -func WithAssets(ids ...xid.ID) Option { +func WithAssets(ids ...string) Option { return func(m *ent.PostMutation) { m.AddAssetIDs(ids...) } diff --git a/app/resources/resources.go b/app/resources/resources.go index ec4e1e2c6..968eed5a2 100644 --- a/app/resources/resources.go +++ b/app/resources/resources.go @@ -4,6 +4,7 @@ import ( "go.uber.org/fx" "github.com/Southclaws/storyden/app/resources/account" + "github.com/Southclaws/storyden/app/resources/asset" "github.com/Southclaws/storyden/app/resources/authentication" "github.com/Southclaws/storyden/app/resources/category" "github.com/Southclaws/storyden/app/resources/notification" @@ -22,6 +23,7 @@ func Build() fx.Option { fx.Provide( settings.New, account.New, + asset.New, authentication.New, category.New, post.New, diff --git a/app/services/asset/service.go b/app/services/asset/service.go index a2e0611d5..ed743b1f7 100644 --- a/app/services/asset/service.go +++ b/app/services/asset/service.go @@ -1,19 +1,24 @@ package asset import ( + "bytes" "context" + "crypto/sha1" + "encoding/hex" "fmt" "io" + "path" "path/filepath" + "strings" "github.com/Southclaws/fault" "github.com/Southclaws/fault/fctx" - "github.com/rs/xid" + "github.com/gabriel-vasile/mimetype" "go.uber.org/fx" "go.uber.org/zap" "github.com/Southclaws/storyden/app/resources/account" - "github.com/Southclaws/storyden/app/resources/post" + "github.com/Southclaws/storyden/app/resources/asset" "github.com/Southclaws/storyden/app/resources/rbac" "github.com/Southclaws/storyden/app/resources/thread" "github.com/Southclaws/storyden/app/services/authentication" @@ -24,7 +29,7 @@ import ( const assetsSubdirectory = "assets" type Service interface { - Upload(ctx context.Context, pid post.PostID, r io.Reader) (string, error) + Upload(ctx context.Context, r io.Reader) (*asset.Asset, error) Read(ctx context.Context, path string) (io.Reader, error) } @@ -37,8 +42,8 @@ type service struct { rbac rbac.AccessManager account_repo account.Repository + asset_repo asset.Repository thread_repo thread.Repository - post_repo post.Repository os object.Storer @@ -50,8 +55,8 @@ func New( rbac rbac.AccessManager, account_repo account.Repository, + asset_repo asset.Repository, thread_repo thread.Repository, - post_repo post.Repository, os object.Storer, cfg config.Config, @@ -60,36 +65,58 @@ func New( l: l.With(zap.String("service", "post")), rbac: rbac, account_repo: account_repo, + asset_repo: asset_repo, thread_repo: thread_repo, - post_repo: post_repo, os: os, address: cfg.PublicWebAddress, } } -func (s *service) Upload(ctx context.Context, pid post.PostID, r io.Reader) (string, error) { +func (s *service) Upload(ctx context.Context, r io.Reader) (*asset.Asset, error) { accountID, err := authentication.GetAccountID(ctx) if err != nil { - return "", fault.Wrap(err, fctx.With(ctx)) + return nil, fault.Wrap(err, fctx.With(ctx)) } - assetID := fmt.Sprintf("%s-%s", accountID.String(), xid.New().String()) - path := filepath.Join(assetsSubdirectory, assetID) + // NOTE: We load the whole file into memory in order to compute a hash first + // which isn't the most optimal route as it means 5 people uploading a 100MB + // file to a 512MB server would result in a crash but this can be optimised. + // There are a few alternatives, one is to upload the whole file now by just + // streaming it to its destination then computing hashes and resizes another + // time, another way is by using a rolling hash on the stream during upload. + + buf, err := io.ReadAll(r) + if err != nil { + return nil, fault.Wrap(err, fctx.With(ctx)) + } + r = bytes.NewReader(buf) - if err := s.os.Write(ctx, path, r); err != nil { - return "", fault.Wrap(err, fctx.With(ctx)) + mt := mimetype.Detect(buf) + + hash := sha1.Sum(buf) + assetID := hex.EncodeToString(hash[:]) + slug := fmt.Sprintf("%s-%s", assetID, accountID.String()) + filePath := filepath.Join(assetsSubdirectory, slug) + + if err := s.os.Write(ctx, filePath, r); err != nil { + return nil, fault.Wrap(err, fctx.With(ctx)) } - url := fmt.Sprintf("%s/api/v1/assets/%s", s.address, assetID) + apiPath := path.Join("api/v1/assets", slug) + url := fmt.Sprintf("%s/%s", s.address, apiPath) + mime := mt.String() - _, err = s.post_repo.Update(ctx, pid, post.WithAssets( - // TODO: Insert asset record and append to post. - )) + if strings.HasPrefix(mime, "image") { + // TODO: figure out width and height + fmt.Println("IS AN IMAGE") + } + + ast, err := s.asset_repo.Add(ctx, accountID, assetID, url, mime, 0, 0) if err != nil { - return "", fault.Wrap(err, fctx.With(ctx)) + return nil, fault.Wrap(err, fctx.With(ctx)) } - return url, nil + return ast, nil } func (s *service) Read(ctx context.Context, assetID string) (io.Reader, error) { diff --git a/app/services/services.go b/app/services/services.go index d84ce0950..68a7018bc 100644 --- a/app/services/services.go +++ b/app/services/services.go @@ -4,6 +4,7 @@ import ( "go.uber.org/fx" "github.com/Southclaws/storyden/app/services/account" + "github.com/Southclaws/storyden/app/services/asset" "github.com/Southclaws/storyden/app/services/authentication" "github.com/Southclaws/storyden/app/services/avatar" "github.com/Southclaws/storyden/app/services/avatar_gen" @@ -23,6 +24,7 @@ func Build() fx.Option { react.Build(), search.Build(), avatar.Build(), + asset.Build(), thread_mark.Build(), fx.Provide(avatar_gen.New), ) diff --git a/app/transports/openapi/bindings/assets.go b/app/transports/openapi/bindings/assets.go index 9e533dc90..d87d8f7b3 100644 --- a/app/transports/openapi/bindings/assets.go +++ b/app/transports/openapi/bindings/assets.go @@ -2,12 +2,10 @@ package bindings import ( "context" - "fmt" "github.com/Southclaws/fault" "github.com/Southclaws/fault/fctx" - "github.com/Southclaws/storyden/app/resources/post" "github.com/Southclaws/storyden/app/services/asset" "github.com/Southclaws/storyden/internal/openapi" ) @@ -20,18 +18,6 @@ func NewAssets(a asset.Service) Assets { return Assets{a} } -func (i *Assets) AssetGetUploadURL(ctx context.Context, request openapi.AssetGetUploadURLRequestObject) (openapi.AssetGetUploadURLResponseObject, error) { - // TODO: Check if S3 is available and create a pre-signed upload URL if so. - - url := fmt.Sprintf("%s/api/v1/assets", "i.address") - - return openapi.AssetGetUploadURL200JSONResponse{ - AssetGetUploadURLOKJSONResponse: openapi.AssetGetUploadURLOKJSONResponse{ - Url: url, - }, - }, nil -} - func (i *Assets) AssetGet(ctx context.Context, request openapi.AssetGetRequestObject) (openapi.AssetGetResponseObject, error) { r, err := i.a.Read(ctx, request.Id) if err != nil { @@ -46,16 +32,12 @@ func (i *Assets) AssetGet(ctx context.Context, request openapi.AssetGetRequestOb } func (i *Assets) AssetUpload(ctx context.Context, request openapi.AssetUploadRequestObject) (openapi.AssetUploadResponseObject, error) { - postID := openapi.ParseID(request.Params.PostId) - - url, err := i.a.Upload(ctx, post.PostID(postID), request.Body) + a, err := i.a.Upload(ctx, request.Body) if err != nil { return nil, fault.Wrap(err, fctx.With(ctx)) } return openapi.AssetUpload200JSONResponse{ - AssetUploadOKJSONResponse: openapi.AssetUploadOKJSONResponse{ - Url: url, - }, + AssetUploadOKJSONResponse: openapi.AssetUploadOKJSONResponse(serialiseAssetReference(a)), }, nil } diff --git a/app/transports/openapi/bindings/threads.go b/app/transports/openapi/bindings/threads.go index ef0dd5f8f..759b125d9 100644 --- a/app/transports/openapi/bindings/threads.go +++ b/app/transports/openapi/bindings/threads.go @@ -51,6 +51,8 @@ func (i *Threads) ThreadCreate(ctx context.Context, request openapi.ThreadCreate meta = *request.Body.Meta } + request.Body.Assets + tags := opt.NewPtr(request.Body.Tags) thread, err := i.thread_svc.Create(ctx, @@ -87,6 +89,8 @@ func (i *Threads) ThreadUpdate(ctx context.Context, request openapi.ThreadUpdate return nil, fault.Wrap(err, fctx.With(ctx)) } + request.Body.Assets + thread, err := i.thread_svc.Update(ctx, postID, thread_service.Partial{ Title: opt.NewPtr(request.Body.Title), Body: opt.NewPtr(request.Body.Body), diff --git a/app/transports/openapi/bindings/utils.go b/app/transports/openapi/bindings/utils.go index bba9a8ce5..197aa8c36 100644 --- a/app/transports/openapi/bindings/utils.go +++ b/app/transports/openapi/bindings/utils.go @@ -128,8 +128,9 @@ func serialiseReact(r *react.React) openapi.React { } } -func serialiseAssetReference(a *asset.Asset) openapi.MediaItem { - return openapi.MediaItem{ +func serialiseAssetReference(a *asset.Asset) openapi.Asset { + return openapi.Asset{ + Id: string(a.ID), Url: a.URL, MimeType: a.MIMEType, Width: float32(a.Width), diff --git a/go.mod b/go.mod index a3d19e3a8..ff1b12f19 100644 --- a/go.mod +++ b/go.mod @@ -53,6 +53,7 @@ require ( github.com/dave/jennifer v1.6.0 // indirect github.com/dlclark/regexp2 v1.8.0 // indirect github.com/fatih/color v1.14.1 // indirect + github.com/gabriel-vasile/mimetype v1.4.2 // indirect github.com/ghodss/yaml v1.0.0 // indirect github.com/go-faster/yamlx v0.4.1 // indirect github.com/go-logr/logr v1.2.3 // indirect diff --git a/go.sum b/go.sum index 8c632be0b..f67e72321 100644 --- a/go.sum +++ b/go.sum @@ -89,6 +89,8 @@ github.com/forPelevin/gomoji v1.1.8/go.mod h1:8+Z3KNGkdslmeGZBC3tCrwMrcPy5GRzAD+ github.com/fsnotify/fsnotify v1.4.7/go.mod h1:jwhsz4b93w/PPRr/qN1Yymfu8t87LnFCMoQvtojpjFo= github.com/fxamacker/cbor/v2 v2.4.0 h1:ri0ArlOR+5XunOP8CRUowT0pSJOwhW098ZCUyskZD88= github.com/fxamacker/cbor/v2 v2.4.0/go.mod h1:TA1xS00nchWmaBnEIxPSE5oHLuJBAVvqrtAnWBwBCVo= +github.com/gabriel-vasile/mimetype v1.4.2 h1:w5qFW6JKBz9Y393Y4q372O9A7cUSequkh1Q7OhCmWKU= +github.com/gabriel-vasile/mimetype v1.4.2/go.mod h1:zApsH/mKG4w07erKIaJPFiX0Tsq9BFQgN3qGY5GnNgA= github.com/getkin/kin-openapi v0.114.0 h1:ar7QiJpDdlR+zSyPjrLf8mNnpoFP/lI90XcywMCFNe8= github.com/getkin/kin-openapi v0.114.0/go.mod h1:l5e9PaFUo9fyLJCPGQeXI2ML8c3P8BHOEV2VaAVf/pc= github.com/ghodss/yaml v1.0.0 h1:wQHKEahhL6wmXdzwWG11gIVCkOv05bNOh+Rxn0yngAk= @@ -306,6 +308,7 @@ github.com/mattn/go-isatty v0.0.14/go.mod h1:7GGIvUiUoEMVVmxf/4nioHXj79iQHKdU27k github.com/mattn/go-isatty v0.0.16/go.mod h1:kYGgaQfpe5nmfYZH+SKPsOc2e4SrIfOl2e/yFXSvRLM= github.com/mattn/go-isatty v0.0.17 h1:BTarxUcIeDqL27Mc+vyvdWYSL28zpIhv3RoTdsLMPng= github.com/mattn/go-isatty v0.0.17/go.mod h1:kYGgaQfpe5nmfYZH+SKPsOc2e4SrIfOl2e/yFXSvRLM= +github.com/mattn/go-runewidth v0.0.9 h1:Lm995f3rfxdpd6TSmuVCHVb/QhupuXlYr8sCI/QdE+0= github.com/mattn/go-sqlite3 v1.14.16 h1:yOQRA0RpS5PFz/oikGwBEqvAWhWg5ufRz4ETLjwpU1Y= github.com/matttproud/golang_protobuf_extensions v1.0.1/go.mod h1:D8He9yQNgCq6Z5Ld7szi9bcBfOoFv/3dc6xSMkL2PC0= github.com/mazznoer/colorgrad v0.9.1 h1:MB80JYVndKWSMEM1beNqnuOowWGhoQc3DXWXkFp6JlM= @@ -338,6 +341,7 @@ github.com/niemeyer/pretty v0.0.0-20200227124842-a10e7caefd8e/go.mod h1:zD1mROLA github.com/ogen-go/ogen v0.59.0 h1:9aSSZ1KCLJIcRyjkO7IHrG0vAI6l1BO877LwTbMcX+k= github.com/ogen-go/ogen v0.59.0/go.mod h1:0MHLcWEbxwdvR+R9E05paQSRh/2vHtVSJgKqmwYyW8M= github.com/oklog/ulid v1.3.1/go.mod h1:CirwcVhetQ6Lv90oh/F+FBtV6XMibvdAFo93nm5qn4U= +github.com/olekukonko/tablewriter v0.0.5 h1:P2Ga83D34wi1o9J6Wh1mRuqd4mF/x/lgBS7N7AbDhec= github.com/pelletier/go-toml v1.2.0/go.mod h1:5z9KED0ma1S8pY6P1sdut58dfprrGBbd/94hg7ilaic= github.com/perimeterx/marshmallow v1.1.4 h1:pZLDH9RjlLGGorbXhcaQLhfuV0pFMNfPO55FuFkxqLw= github.com/perimeterx/marshmallow v1.1.4/go.mod h1:dsXbUu8CRzfYP5a87xpp0xq9S3u0Vchtcl8we9tYaXw= @@ -394,8 +398,10 @@ github.com/spf13/afero v1.1.2/go.mod h1:j4pytiNVoe2o6bmDsKpLACNPDBIoEAkihy7loJ1B github.com/spf13/cast v1.3.0/go.mod h1:Qx5cxh0v+4UWYiBimWS+eyWzqEqokIECu5etghLkUJE= github.com/spf13/cobra v0.0.5/go.mod h1:3K3wKZymM7VvHMDS9+Akkh4K60UwM26emMESw8tLCHU= github.com/spf13/cobra v1.0.0/go.mod h1:/6GTrnGXV9HjY+aR4k0oJ5tcvakLuG6EuKReYlHNrgE= +github.com/spf13/cobra v1.6.1 h1:o94oiPyS4KD1mPy2fmcYYHHfCxLqYjJOhGsCHFZtEzA= github.com/spf13/jwalterweatherman v1.0.0/go.mod h1:cQK4TGJAtQXfYWX+Ddv3mKDzgVb68N+wFjFa4jdeBTo= github.com/spf13/pflag v1.0.3/go.mod h1:DYY7MBk1bdzusC3SYhjObp+wFpr4gzcvqqNjLnInEg4= +github.com/spf13/pflag v1.0.5 h1:iy+VFUOCP1a+8yFto/drg2CJ5u0yRoB7fZw3DKv/JXA= github.com/spf13/viper v1.3.2/go.mod h1:ZiWeW+zYFKm7srdB9IoDzzZXaJaI5eL9QjNiN/DMA2s= github.com/spf13/viper v1.4.0/go.mod h1:PTJ7Z/lr49W6bUbkmS1V3by4uWynFiR9p7+dSq/yZzE= github.com/spkg/bom v0.0.0-20160624110644-59b7046e48ad/go.mod h1:qLr4V1qq6nMqFKkMo8ZTx3f+BZEkzsRUY10Xsm2mwU0= diff --git a/internal/ent/account.go b/internal/ent/account.go index 235894232..a6633f5de 100644 --- a/internal/ent/account.go +++ b/internal/ent/account.go @@ -48,9 +48,11 @@ type AccountEdges struct { Authentication []*Authentication `json:"authentication,omitempty"` // Tags holds the value of the tags edge. Tags []*Tag `json:"tags,omitempty"` + // Assets holds the value of the assets edge. + Assets []*Asset `json:"assets,omitempty"` // loadedTypes holds the information for reporting if a // type was loaded (or requested) in eager-loading or not. - loadedTypes [5]bool + loadedTypes [6]bool } // PostsOrErr returns the Posts value or an error if the edge @@ -98,6 +100,15 @@ func (e AccountEdges) TagsOrErr() ([]*Tag, error) { return nil, &NotLoadedError{edge: "tags"} } +// AssetsOrErr returns the Assets value or an error if the edge +// was not loaded in eager-loading. +func (e AccountEdges) AssetsOrErr() ([]*Asset, error) { + if e.loadedTypes[5] { + return e.Assets, nil + } + return nil, &NotLoadedError{edge: "assets"} +} + // scanValues returns the types for scanning values from sql.Rows. func (*Account) scanValues(columns []string) ([]any, error) { values := make([]any, len(columns)) @@ -205,6 +216,11 @@ func (a *Account) QueryTags() *TagQuery { return NewAccountClient(a.config).QueryTags(a) } +// QueryAssets queries the "assets" edge of the Account entity. +func (a *Account) QueryAssets() *AssetQuery { + return NewAccountClient(a.config).QueryAssets(a) +} + // Update returns a builder for updating this Account. // Note that you need to call Account.Unwrap() before calling this method if this Account // was returned from a transaction, and the transaction was committed or rolled back. diff --git a/internal/ent/account/account.go b/internal/ent/account/account.go index a525369c2..9f2fb043b 100644 --- a/internal/ent/account/account.go +++ b/internal/ent/account/account.go @@ -37,6 +37,8 @@ const ( EdgeAuthentication = "authentication" // EdgeTags holds the string denoting the tags edge name in mutations. EdgeTags = "tags" + // EdgeAssets holds the string denoting the assets edge name in mutations. + EdgeAssets = "assets" // Table holds the table name of the account in the database. Table = "accounts" // PostsTable is the table that holds the posts relation/edge. @@ -70,6 +72,13 @@ const ( // TagsInverseTable is the table name for the Tag entity. // It exists in this package in order to avoid circular dependency with the "tag" package. TagsInverseTable = "tags" + // AssetsTable is the table that holds the assets relation/edge. + AssetsTable = "assets" + // AssetsInverseTable is the table name for the Asset entity. + // It exists in this package in order to avoid circular dependency with the "asset" package. + AssetsInverseTable = "assets" + // AssetsColumn is the table column denoting the assets relation/edge. + AssetsColumn = "account_id" ) // Columns holds all SQL columns for account fields. diff --git a/internal/ent/account/where.go b/internal/ent/account/where.go index 22d2a6d23..b0ef7039f 100644 --- a/internal/ent/account/where.go +++ b/internal/ent/account/where.go @@ -571,6 +571,33 @@ func HasTagsWith(preds ...predicate.Tag) predicate.Account { }) } +// HasAssets applies the HasEdge predicate on the "assets" edge. +func HasAssets() predicate.Account { + return predicate.Account(func(s *sql.Selector) { + step := sqlgraph.NewStep( + sqlgraph.From(Table, FieldID), + sqlgraph.Edge(sqlgraph.O2M, false, AssetsTable, AssetsColumn), + ) + sqlgraph.HasNeighbors(s, step) + }) +} + +// HasAssetsWith applies the HasEdge predicate on the "assets" edge with a given conditions (other predicates). +func HasAssetsWith(preds ...predicate.Asset) predicate.Account { + return predicate.Account(func(s *sql.Selector) { + step := sqlgraph.NewStep( + sqlgraph.From(Table, FieldID), + sqlgraph.To(AssetsInverseTable, FieldID), + sqlgraph.Edge(sqlgraph.O2M, false, AssetsTable, AssetsColumn), + ) + sqlgraph.HasNeighborsWith(s, step, func(s *sql.Selector) { + for _, p := range preds { + p(s) + } + }) + }) +} + // And groups predicates with the AND operator between them. func And(predicates ...predicate.Account) predicate.Account { return predicate.Account(func(s *sql.Selector) { diff --git a/internal/ent/account_create.go b/internal/ent/account_create.go index fef95f712..b8b34c444 100644 --- a/internal/ent/account_create.go +++ b/internal/ent/account_create.go @@ -13,6 +13,7 @@ import ( "entgo.io/ent/dialect/sql/sqlgraph" "entgo.io/ent/schema/field" "github.com/Southclaws/storyden/internal/ent/account" + "github.com/Southclaws/storyden/internal/ent/asset" "github.com/Southclaws/storyden/internal/ent/authentication" "github.com/Southclaws/storyden/internal/ent/post" "github.com/Southclaws/storyden/internal/ent/react" @@ -200,6 +201,21 @@ func (ac *AccountCreate) AddTags(t ...*Tag) *AccountCreate { return ac.AddTagIDs(ids...) } +// AddAssetIDs adds the "assets" edge to the Asset entity by IDs. +func (ac *AccountCreate) AddAssetIDs(ids ...string) *AccountCreate { + ac.mutation.AddAssetIDs(ids...) + return ac +} + +// AddAssets adds the "assets" edges to the Asset entity. +func (ac *AccountCreate) AddAssets(a ...*Asset) *AccountCreate { + ids := make([]string, len(a)) + for i := range a { + ids[i] = a[i].ID + } + return ac.AddAssetIDs(ids...) +} + // Mutation returns the AccountMutation object of the builder. func (ac *AccountCreate) Mutation() *AccountMutation { return ac.mutation @@ -429,6 +445,22 @@ func (ac *AccountCreate) createSpec() (*Account, *sqlgraph.CreateSpec) { } _spec.Edges = append(_spec.Edges, edge) } + if nodes := ac.mutation.AssetsIDs(); len(nodes) > 0 { + edge := &sqlgraph.EdgeSpec{ + Rel: sqlgraph.O2M, + Inverse: false, + Table: account.AssetsTable, + Columns: []string{account.AssetsColumn}, + Bidi: false, + Target: &sqlgraph.EdgeTarget{ + IDSpec: sqlgraph.NewFieldSpec(asset.FieldID, field.TypeString), + }, + } + for _, k := range nodes { + edge.Target.Nodes = append(edge.Target.Nodes, k) + } + _spec.Edges = append(_spec.Edges, edge) + } return _node, _spec } diff --git a/internal/ent/account_query.go b/internal/ent/account_query.go index bc7ffe07e..b4fd7f3c5 100644 --- a/internal/ent/account_query.go +++ b/internal/ent/account_query.go @@ -12,6 +12,7 @@ import ( "entgo.io/ent/dialect/sql/sqlgraph" "entgo.io/ent/schema/field" "github.com/Southclaws/storyden/internal/ent/account" + "github.com/Southclaws/storyden/internal/ent/asset" "github.com/Southclaws/storyden/internal/ent/authentication" "github.com/Southclaws/storyden/internal/ent/post" "github.com/Southclaws/storyden/internal/ent/predicate" @@ -33,6 +34,7 @@ type AccountQuery struct { withRoles *RoleQuery withAuthentication *AuthenticationQuery withTags *TagQuery + withAssets *AssetQuery modifiers []func(*sql.Selector) // intermediate query (i.e. traversal path). sql *sql.Selector @@ -180,6 +182,28 @@ func (aq *AccountQuery) QueryTags() *TagQuery { return query } +// QueryAssets chains the current query on the "assets" edge. +func (aq *AccountQuery) QueryAssets() *AssetQuery { + query := (&AssetClient{config: aq.config}).Query() + query.path = func(ctx context.Context) (fromU *sql.Selector, err error) { + if err := aq.prepareQuery(ctx); err != nil { + return nil, err + } + selector := aq.sqlQuery(ctx) + if err := selector.Err(); err != nil { + return nil, err + } + step := sqlgraph.NewStep( + sqlgraph.From(account.Table, account.FieldID, selector), + sqlgraph.To(asset.Table, asset.FieldID), + sqlgraph.Edge(sqlgraph.O2M, false, account.AssetsTable, account.AssetsColumn), + ) + fromU = sqlgraph.SetNeighbors(aq.driver.Dialect(), step) + return fromU, nil + } + return query +} + // First returns the first Account entity from the query. // Returns a *NotFoundError when no Account was found. func (aq *AccountQuery) First(ctx context.Context) (*Account, error) { @@ -377,6 +401,7 @@ func (aq *AccountQuery) Clone() *AccountQuery { withRoles: aq.withRoles.Clone(), withAuthentication: aq.withAuthentication.Clone(), withTags: aq.withTags.Clone(), + withAssets: aq.withAssets.Clone(), // clone intermediate query. sql: aq.sql.Clone(), path: aq.path, @@ -438,6 +463,17 @@ func (aq *AccountQuery) WithTags(opts ...func(*TagQuery)) *AccountQuery { return aq } +// WithAssets tells the query-builder to eager-load the nodes that are connected to +// the "assets" edge. The optional arguments are used to configure the query builder of the edge. +func (aq *AccountQuery) WithAssets(opts ...func(*AssetQuery)) *AccountQuery { + query := (&AssetClient{config: aq.config}).Query() + for _, opt := range opts { + opt(query) + } + aq.withAssets = query + return aq +} + // GroupBy is used to group vertices by one or more fields/columns. // It is often used with aggregate functions, like: count, max, mean, min, sum. // @@ -516,12 +552,13 @@ func (aq *AccountQuery) sqlAll(ctx context.Context, hooks ...queryHook) ([]*Acco var ( nodes = []*Account{} _spec = aq.querySpec() - loadedTypes = [5]bool{ + loadedTypes = [6]bool{ aq.withPosts != nil, aq.withReacts != nil, aq.withRoles != nil, aq.withAuthentication != nil, aq.withTags != nil, + aq.withAssets != nil, } ) _spec.ScanValues = func(columns []string) ([]any, error) { @@ -580,6 +617,13 @@ func (aq *AccountQuery) sqlAll(ctx context.Context, hooks ...queryHook) ([]*Acco return nil, err } } + if query := aq.withAssets; query != nil { + if err := aq.loadAssets(ctx, query, nodes, + func(n *Account) { n.Edges.Assets = []*Asset{} }, + func(n *Account, e *Asset) { n.Edges.Assets = append(n.Edges.Assets, e) }); err != nil { + return nil, err + } + } return nodes, nil } @@ -794,6 +838,33 @@ func (aq *AccountQuery) loadTags(ctx context.Context, query *TagQuery, nodes []* } return nil } +func (aq *AccountQuery) loadAssets(ctx context.Context, query *AssetQuery, nodes []*Account, init func(*Account), assign func(*Account, *Asset)) error { + fks := make([]driver.Value, 0, len(nodes)) + nodeids := make(map[xid.ID]*Account) + for i := range nodes { + fks = append(fks, nodes[i].ID) + nodeids[nodes[i].ID] = nodes[i] + if init != nil { + init(nodes[i]) + } + } + query.Where(predicate.Asset(func(s *sql.Selector) { + s.Where(sql.InValues(account.AssetsColumn, fks...)) + })) + neighbors, err := query.All(ctx) + if err != nil { + return err + } + for _, n := range neighbors { + fk := n.AccountID + node, ok := nodeids[fk] + if !ok { + return fmt.Errorf(`unexpected foreign-key "account_id" returned %v for node %v`, fk, n.ID) + } + assign(node, n) + } + return nil +} func (aq *AccountQuery) sqlCount(ctx context.Context) (int, error) { _spec := aq.querySpec() diff --git a/internal/ent/account_update.go b/internal/ent/account_update.go index 063810e74..248fe903f 100644 --- a/internal/ent/account_update.go +++ b/internal/ent/account_update.go @@ -12,6 +12,7 @@ import ( "entgo.io/ent/dialect/sql/sqlgraph" "entgo.io/ent/schema/field" "github.com/Southclaws/storyden/internal/ent/account" + "github.com/Southclaws/storyden/internal/ent/asset" "github.com/Southclaws/storyden/internal/ent/authentication" "github.com/Southclaws/storyden/internal/ent/post" "github.com/Southclaws/storyden/internal/ent/predicate" @@ -182,6 +183,21 @@ func (au *AccountUpdate) AddTags(t ...*Tag) *AccountUpdate { return au.AddTagIDs(ids...) } +// AddAssetIDs adds the "assets" edge to the Asset entity by IDs. +func (au *AccountUpdate) AddAssetIDs(ids ...string) *AccountUpdate { + au.mutation.AddAssetIDs(ids...) + return au +} + +// AddAssets adds the "assets" edges to the Asset entity. +func (au *AccountUpdate) AddAssets(a ...*Asset) *AccountUpdate { + ids := make([]string, len(a)) + for i := range a { + ids[i] = a[i].ID + } + return au.AddAssetIDs(ids...) +} + // Mutation returns the AccountMutation object of the builder. func (au *AccountUpdate) Mutation() *AccountMutation { return au.mutation @@ -292,6 +308,27 @@ func (au *AccountUpdate) RemoveTags(t ...*Tag) *AccountUpdate { return au.RemoveTagIDs(ids...) } +// ClearAssets clears all "assets" edges to the Asset entity. +func (au *AccountUpdate) ClearAssets() *AccountUpdate { + au.mutation.ClearAssets() + return au +} + +// RemoveAssetIDs removes the "assets" edge to Asset entities by IDs. +func (au *AccountUpdate) RemoveAssetIDs(ids ...string) *AccountUpdate { + au.mutation.RemoveAssetIDs(ids...) + return au +} + +// RemoveAssets removes "assets" edges to Asset entities. +func (au *AccountUpdate) RemoveAssets(a ...*Asset) *AccountUpdate { + ids := make([]string, len(a)) + for i := range a { + ids[i] = a[i].ID + } + return au.RemoveAssetIDs(ids...) +} + // Save executes the query and returns the number of nodes affected by the update operation. func (au *AccountUpdate) Save(ctx context.Context) (int, error) { au.defaults() @@ -610,6 +647,51 @@ func (au *AccountUpdate) sqlSave(ctx context.Context) (n int, err error) { } _spec.Edges.Add = append(_spec.Edges.Add, edge) } + if au.mutation.AssetsCleared() { + edge := &sqlgraph.EdgeSpec{ + Rel: sqlgraph.O2M, + Inverse: false, + Table: account.AssetsTable, + Columns: []string{account.AssetsColumn}, + Bidi: false, + Target: &sqlgraph.EdgeTarget{ + IDSpec: sqlgraph.NewFieldSpec(asset.FieldID, field.TypeString), + }, + } + _spec.Edges.Clear = append(_spec.Edges.Clear, edge) + } + if nodes := au.mutation.RemovedAssetsIDs(); len(nodes) > 0 && !au.mutation.AssetsCleared() { + edge := &sqlgraph.EdgeSpec{ + Rel: sqlgraph.O2M, + Inverse: false, + Table: account.AssetsTable, + Columns: []string{account.AssetsColumn}, + Bidi: false, + Target: &sqlgraph.EdgeTarget{ + IDSpec: sqlgraph.NewFieldSpec(asset.FieldID, field.TypeString), + }, + } + for _, k := range nodes { + edge.Target.Nodes = append(edge.Target.Nodes, k) + } + _spec.Edges.Clear = append(_spec.Edges.Clear, edge) + } + if nodes := au.mutation.AssetsIDs(); len(nodes) > 0 { + edge := &sqlgraph.EdgeSpec{ + Rel: sqlgraph.O2M, + Inverse: false, + Table: account.AssetsTable, + Columns: []string{account.AssetsColumn}, + Bidi: false, + Target: &sqlgraph.EdgeTarget{ + IDSpec: sqlgraph.NewFieldSpec(asset.FieldID, field.TypeString), + }, + } + for _, k := range nodes { + edge.Target.Nodes = append(edge.Target.Nodes, k) + } + _spec.Edges.Add = append(_spec.Edges.Add, edge) + } _spec.AddModifiers(au.modifiers...) if n, err = sqlgraph.UpdateNodes(ctx, au.driver, _spec); err != nil { if _, ok := err.(*sqlgraph.NotFoundError); ok { @@ -779,6 +861,21 @@ func (auo *AccountUpdateOne) AddTags(t ...*Tag) *AccountUpdateOne { return auo.AddTagIDs(ids...) } +// AddAssetIDs adds the "assets" edge to the Asset entity by IDs. +func (auo *AccountUpdateOne) AddAssetIDs(ids ...string) *AccountUpdateOne { + auo.mutation.AddAssetIDs(ids...) + return auo +} + +// AddAssets adds the "assets" edges to the Asset entity. +func (auo *AccountUpdateOne) AddAssets(a ...*Asset) *AccountUpdateOne { + ids := make([]string, len(a)) + for i := range a { + ids[i] = a[i].ID + } + return auo.AddAssetIDs(ids...) +} + // Mutation returns the AccountMutation object of the builder. func (auo *AccountUpdateOne) Mutation() *AccountMutation { return auo.mutation @@ -889,6 +986,27 @@ func (auo *AccountUpdateOne) RemoveTags(t ...*Tag) *AccountUpdateOne { return auo.RemoveTagIDs(ids...) } +// ClearAssets clears all "assets" edges to the Asset entity. +func (auo *AccountUpdateOne) ClearAssets() *AccountUpdateOne { + auo.mutation.ClearAssets() + return auo +} + +// RemoveAssetIDs removes the "assets" edge to Asset entities by IDs. +func (auo *AccountUpdateOne) RemoveAssetIDs(ids ...string) *AccountUpdateOne { + auo.mutation.RemoveAssetIDs(ids...) + return auo +} + +// RemoveAssets removes "assets" edges to Asset entities. +func (auo *AccountUpdateOne) RemoveAssets(a ...*Asset) *AccountUpdateOne { + ids := make([]string, len(a)) + for i := range a { + ids[i] = a[i].ID + } + return auo.RemoveAssetIDs(ids...) +} + // Where appends a list predicates to the AccountUpdate builder. func (auo *AccountUpdateOne) Where(ps ...predicate.Account) *AccountUpdateOne { auo.mutation.Where(ps...) @@ -1237,6 +1355,51 @@ func (auo *AccountUpdateOne) sqlSave(ctx context.Context) (_node *Account, err e } _spec.Edges.Add = append(_spec.Edges.Add, edge) } + if auo.mutation.AssetsCleared() { + edge := &sqlgraph.EdgeSpec{ + Rel: sqlgraph.O2M, + Inverse: false, + Table: account.AssetsTable, + Columns: []string{account.AssetsColumn}, + Bidi: false, + Target: &sqlgraph.EdgeTarget{ + IDSpec: sqlgraph.NewFieldSpec(asset.FieldID, field.TypeString), + }, + } + _spec.Edges.Clear = append(_spec.Edges.Clear, edge) + } + if nodes := auo.mutation.RemovedAssetsIDs(); len(nodes) > 0 && !auo.mutation.AssetsCleared() { + edge := &sqlgraph.EdgeSpec{ + Rel: sqlgraph.O2M, + Inverse: false, + Table: account.AssetsTable, + Columns: []string{account.AssetsColumn}, + Bidi: false, + Target: &sqlgraph.EdgeTarget{ + IDSpec: sqlgraph.NewFieldSpec(asset.FieldID, field.TypeString), + }, + } + for _, k := range nodes { + edge.Target.Nodes = append(edge.Target.Nodes, k) + } + _spec.Edges.Clear = append(_spec.Edges.Clear, edge) + } + if nodes := auo.mutation.AssetsIDs(); len(nodes) > 0 { + edge := &sqlgraph.EdgeSpec{ + Rel: sqlgraph.O2M, + Inverse: false, + Table: account.AssetsTable, + Columns: []string{account.AssetsColumn}, + Bidi: false, + Target: &sqlgraph.EdgeTarget{ + IDSpec: sqlgraph.NewFieldSpec(asset.FieldID, field.TypeString), + }, + } + for _, k := range nodes { + edge.Target.Nodes = append(edge.Target.Nodes, k) + } + _spec.Edges.Add = append(_spec.Edges.Add, edge) + } _spec.AddModifiers(auo.modifiers...) _node = &Account{config: auo.config} _spec.Assign = _node.assignValues diff --git a/internal/ent/asset.go b/internal/ent/asset.go index 7d644095d..d94372f57 100644 --- a/internal/ent/asset.go +++ b/internal/ent/asset.go @@ -8,6 +8,7 @@ import ( "time" "entgo.io/ent/dialect/sql" + "github.com/Southclaws/storyden/internal/ent/account" "github.com/Southclaws/storyden/internal/ent/asset" "github.com/Southclaws/storyden/internal/ent/post" "github.com/rs/xid" @@ -17,7 +18,7 @@ import ( type Asset struct { config `json:"-"` // ID of the ent. - ID xid.ID `json:"id,omitempty"` + ID string `json:"id,omitempty"` // CreatedAt holds the value of the "created_at" field. CreatedAt time.Time `json:"created_at,omitempty"` // UpdatedAt holds the value of the "updated_at" field. @@ -32,6 +33,8 @@ type Asset struct { Height int `json:"height,omitempty"` // PostID holds the value of the "post_id" field. PostID xid.ID `json:"post_id,omitempty"` + // AccountID holds the value of the "account_id" field. + AccountID xid.ID `json:"account_id,omitempty"` // Edges holds the relations/edges for other nodes in the graph. // The values are being populated by the AssetQuery when eager-loading is set. Edges AssetEdges `json:"edges"` @@ -41,9 +44,11 @@ type Asset struct { type AssetEdges struct { // Post holds the value of the post edge. Post *Post `json:"post,omitempty"` + // Owner holds the value of the owner edge. + Owner *Account `json:"owner,omitempty"` // loadedTypes holds the information for reporting if a // type was loaded (or requested) in eager-loading or not. - loadedTypes [1]bool + loadedTypes [2]bool } // PostOrErr returns the Post value or an error if the edge @@ -59,6 +64,19 @@ func (e AssetEdges) PostOrErr() (*Post, error) { return nil, &NotLoadedError{edge: "post"} } +// OwnerOrErr returns the Owner value or an error if the edge +// was not loaded in eager-loading, or loaded but was not found. +func (e AssetEdges) OwnerOrErr() (*Account, error) { + if e.loadedTypes[1] { + if e.Owner == nil { + // Edge was loaded but was not found. + return nil, &NotFoundError{label: account.Label} + } + return e.Owner, nil + } + return nil, &NotLoadedError{edge: "owner"} +} + // scanValues returns the types for scanning values from sql.Rows. func (*Asset) scanValues(columns []string) ([]any, error) { values := make([]any, len(columns)) @@ -66,11 +84,11 @@ func (*Asset) scanValues(columns []string) ([]any, error) { switch columns[i] { case asset.FieldWidth, asset.FieldHeight: values[i] = new(sql.NullInt64) - case asset.FieldURL, asset.FieldMimetype: + case asset.FieldID, asset.FieldURL, asset.FieldMimetype: values[i] = new(sql.NullString) case asset.FieldCreatedAt, asset.FieldUpdatedAt: values[i] = new(sql.NullTime) - case asset.FieldID, asset.FieldPostID: + case asset.FieldPostID, asset.FieldAccountID: values[i] = new(xid.ID) default: return nil, fmt.Errorf("unexpected column %q for type Asset", columns[i]) @@ -88,10 +106,10 @@ func (a *Asset) assignValues(columns []string, values []any) error { for i := range columns { switch columns[i] { case asset.FieldID: - if value, ok := values[i].(*xid.ID); !ok { + if value, ok := values[i].(*sql.NullString); !ok { return fmt.Errorf("unexpected type %T for field id", values[i]) - } else if value != nil { - a.ID = *value + } else if value.Valid { + a.ID = value.String } case asset.FieldCreatedAt: if value, ok := values[i].(*sql.NullTime); !ok { @@ -135,6 +153,12 @@ func (a *Asset) assignValues(columns []string, values []any) error { } else if value != nil { a.PostID = *value } + case asset.FieldAccountID: + if value, ok := values[i].(*xid.ID); !ok { + return fmt.Errorf("unexpected type %T for field account_id", values[i]) + } else if value != nil { + a.AccountID = *value + } } } return nil @@ -145,6 +169,11 @@ func (a *Asset) QueryPost() *PostQuery { return NewAssetClient(a.config).QueryPost(a) } +// QueryOwner queries the "owner" edge of the Asset entity. +func (a *Asset) QueryOwner() *AccountQuery { + return NewAssetClient(a.config).QueryOwner(a) +} + // Update returns a builder for updating this Asset. // Note that you need to call Asset.Unwrap() before calling this method if this Asset // was returned from a transaction, and the transaction was committed or rolled back. @@ -188,6 +217,9 @@ func (a *Asset) String() string { builder.WriteString(", ") builder.WriteString("post_id=") builder.WriteString(fmt.Sprintf("%v", a.PostID)) + builder.WriteString(", ") + builder.WriteString("account_id=") + builder.WriteString(fmt.Sprintf("%v", a.AccountID)) builder.WriteByte(')') return builder.String() } diff --git a/internal/ent/asset/asset.go b/internal/ent/asset/asset.go index c599c329d..3ff72d9bb 100644 --- a/internal/ent/asset/asset.go +++ b/internal/ent/asset/asset.go @@ -4,8 +4,6 @@ package asset import ( "time" - - "github.com/rs/xid" ) const ( @@ -27,8 +25,12 @@ const ( FieldHeight = "height" // FieldPostID holds the string denoting the post_id field in the database. FieldPostID = "post_id" + // FieldAccountID holds the string denoting the account_id field in the database. + FieldAccountID = "account_id" // EdgePost holds the string denoting the post edge name in mutations. EdgePost = "post" + // EdgeOwner holds the string denoting the owner edge name in mutations. + EdgeOwner = "owner" // Table holds the table name of the asset in the database. Table = "assets" // PostTable is the table that holds the post relation/edge. @@ -38,6 +40,13 @@ const ( PostInverseTable = "posts" // PostColumn is the table column denoting the post relation/edge. PostColumn = "post_id" + // OwnerTable is the table that holds the owner relation/edge. + OwnerTable = "assets" + // OwnerInverseTable is the table name for the Account entity. + // It exists in this package in order to avoid circular dependency with the "account" package. + OwnerInverseTable = "accounts" + // OwnerColumn is the table column denoting the owner relation/edge. + OwnerColumn = "account_id" ) // Columns holds all SQL columns for asset fields. @@ -50,6 +59,7 @@ var Columns = []string{ FieldWidth, FieldHeight, FieldPostID, + FieldAccountID, } // ValidColumn reports if the column name is valid (part of the table columns). @@ -69,8 +79,6 @@ var ( DefaultUpdatedAt func() time.Time // UpdateDefaultUpdatedAt holds the default value on update for the "updated_at" field. UpdateDefaultUpdatedAt func() time.Time - // DefaultID holds the default value on creation for the "id" field. - DefaultID func() xid.ID // IDValidator is a validator for the "id" field. It is called by the builders before save. IDValidator func(string) error ) diff --git a/internal/ent/asset/where.go b/internal/ent/asset/where.go index 5a13696a4..38af181fb 100644 --- a/internal/ent/asset/where.go +++ b/internal/ent/asset/where.go @@ -12,50 +12,60 @@ import ( ) // ID filters vertices based on their ID field. -func ID(id xid.ID) predicate.Asset { +func ID(id string) predicate.Asset { return predicate.Asset(sql.FieldEQ(FieldID, id)) } // IDEQ applies the EQ predicate on the ID field. -func IDEQ(id xid.ID) predicate.Asset { +func IDEQ(id string) predicate.Asset { return predicate.Asset(sql.FieldEQ(FieldID, id)) } // IDNEQ applies the NEQ predicate on the ID field. -func IDNEQ(id xid.ID) predicate.Asset { +func IDNEQ(id string) predicate.Asset { return predicate.Asset(sql.FieldNEQ(FieldID, id)) } // IDIn applies the In predicate on the ID field. -func IDIn(ids ...xid.ID) predicate.Asset { +func IDIn(ids ...string) predicate.Asset { return predicate.Asset(sql.FieldIn(FieldID, ids...)) } // IDNotIn applies the NotIn predicate on the ID field. -func IDNotIn(ids ...xid.ID) predicate.Asset { +func IDNotIn(ids ...string) predicate.Asset { return predicate.Asset(sql.FieldNotIn(FieldID, ids...)) } // IDGT applies the GT predicate on the ID field. -func IDGT(id xid.ID) predicate.Asset { +func IDGT(id string) predicate.Asset { return predicate.Asset(sql.FieldGT(FieldID, id)) } // IDGTE applies the GTE predicate on the ID field. -func IDGTE(id xid.ID) predicate.Asset { +func IDGTE(id string) predicate.Asset { return predicate.Asset(sql.FieldGTE(FieldID, id)) } // IDLT applies the LT predicate on the ID field. -func IDLT(id xid.ID) predicate.Asset { +func IDLT(id string) predicate.Asset { return predicate.Asset(sql.FieldLT(FieldID, id)) } // IDLTE applies the LTE predicate on the ID field. -func IDLTE(id xid.ID) predicate.Asset { +func IDLTE(id string) predicate.Asset { return predicate.Asset(sql.FieldLTE(FieldID, id)) } +// IDEqualFold applies the EqualFold predicate on the ID field. +func IDEqualFold(id string) predicate.Asset { + return predicate.Asset(sql.FieldEqualFold(FieldID, id)) +} + +// IDContainsFold applies the ContainsFold predicate on the ID field. +func IDContainsFold(id string) predicate.Asset { + return predicate.Asset(sql.FieldContainsFold(FieldID, id)) +} + // CreatedAt applies equality check predicate on the "created_at" field. It's identical to CreatedAtEQ. func CreatedAt(v time.Time) predicate.Asset { return predicate.Asset(sql.FieldEQ(FieldCreatedAt, v)) @@ -91,6 +101,11 @@ func PostID(v xid.ID) predicate.Asset { return predicate.Asset(sql.FieldEQ(FieldPostID, v)) } +// AccountID applies equality check predicate on the "account_id" field. It's identical to AccountIDEQ. +func AccountID(v xid.ID) predicate.Asset { + return predicate.Asset(sql.FieldEQ(FieldAccountID, v)) +} + // CreatedAtEQ applies the EQ predicate on the "created_at" field. func CreatedAtEQ(v time.Time) predicate.Asset { return predicate.Asset(sql.FieldEQ(FieldCreatedAt, v)) @@ -439,6 +454,16 @@ func PostIDHasSuffix(v xid.ID) predicate.Asset { return predicate.Asset(sql.FieldHasSuffix(FieldPostID, vc)) } +// PostIDIsNil applies the IsNil predicate on the "post_id" field. +func PostIDIsNil() predicate.Asset { + return predicate.Asset(sql.FieldIsNull(FieldPostID)) +} + +// PostIDNotNil applies the NotNil predicate on the "post_id" field. +func PostIDNotNil() predicate.Asset { + return predicate.Asset(sql.FieldNotNull(FieldPostID)) +} + // PostIDEqualFold applies the EqualFold predicate on the "post_id" field. func PostIDEqualFold(v xid.ID) predicate.Asset { vc := v.String() @@ -451,6 +476,76 @@ func PostIDContainsFold(v xid.ID) predicate.Asset { return predicate.Asset(sql.FieldContainsFold(FieldPostID, vc)) } +// AccountIDEQ applies the EQ predicate on the "account_id" field. +func AccountIDEQ(v xid.ID) predicate.Asset { + return predicate.Asset(sql.FieldEQ(FieldAccountID, v)) +} + +// AccountIDNEQ applies the NEQ predicate on the "account_id" field. +func AccountIDNEQ(v xid.ID) predicate.Asset { + return predicate.Asset(sql.FieldNEQ(FieldAccountID, v)) +} + +// AccountIDIn applies the In predicate on the "account_id" field. +func AccountIDIn(vs ...xid.ID) predicate.Asset { + return predicate.Asset(sql.FieldIn(FieldAccountID, vs...)) +} + +// AccountIDNotIn applies the NotIn predicate on the "account_id" field. +func AccountIDNotIn(vs ...xid.ID) predicate.Asset { + return predicate.Asset(sql.FieldNotIn(FieldAccountID, vs...)) +} + +// AccountIDGT applies the GT predicate on the "account_id" field. +func AccountIDGT(v xid.ID) predicate.Asset { + return predicate.Asset(sql.FieldGT(FieldAccountID, v)) +} + +// AccountIDGTE applies the GTE predicate on the "account_id" field. +func AccountIDGTE(v xid.ID) predicate.Asset { + return predicate.Asset(sql.FieldGTE(FieldAccountID, v)) +} + +// AccountIDLT applies the LT predicate on the "account_id" field. +func AccountIDLT(v xid.ID) predicate.Asset { + return predicate.Asset(sql.FieldLT(FieldAccountID, v)) +} + +// AccountIDLTE applies the LTE predicate on the "account_id" field. +func AccountIDLTE(v xid.ID) predicate.Asset { + return predicate.Asset(sql.FieldLTE(FieldAccountID, v)) +} + +// AccountIDContains applies the Contains predicate on the "account_id" field. +func AccountIDContains(v xid.ID) predicate.Asset { + vc := v.String() + return predicate.Asset(sql.FieldContains(FieldAccountID, vc)) +} + +// AccountIDHasPrefix applies the HasPrefix predicate on the "account_id" field. +func AccountIDHasPrefix(v xid.ID) predicate.Asset { + vc := v.String() + return predicate.Asset(sql.FieldHasPrefix(FieldAccountID, vc)) +} + +// AccountIDHasSuffix applies the HasSuffix predicate on the "account_id" field. +func AccountIDHasSuffix(v xid.ID) predicate.Asset { + vc := v.String() + return predicate.Asset(sql.FieldHasSuffix(FieldAccountID, vc)) +} + +// AccountIDEqualFold applies the EqualFold predicate on the "account_id" field. +func AccountIDEqualFold(v xid.ID) predicate.Asset { + vc := v.String() + return predicate.Asset(sql.FieldEqualFold(FieldAccountID, vc)) +} + +// AccountIDContainsFold applies the ContainsFold predicate on the "account_id" field. +func AccountIDContainsFold(v xid.ID) predicate.Asset { + vc := v.String() + return predicate.Asset(sql.FieldContainsFold(FieldAccountID, vc)) +} + // HasPost applies the HasEdge predicate on the "post" edge. func HasPost() predicate.Asset { return predicate.Asset(func(s *sql.Selector) { @@ -478,6 +573,33 @@ func HasPostWith(preds ...predicate.Post) predicate.Asset { }) } +// HasOwner applies the HasEdge predicate on the "owner" edge. +func HasOwner() predicate.Asset { + return predicate.Asset(func(s *sql.Selector) { + step := sqlgraph.NewStep( + sqlgraph.From(Table, FieldID), + sqlgraph.Edge(sqlgraph.M2O, true, OwnerTable, OwnerColumn), + ) + sqlgraph.HasNeighbors(s, step) + }) +} + +// HasOwnerWith applies the HasEdge predicate on the "owner" edge with a given conditions (other predicates). +func HasOwnerWith(preds ...predicate.Account) predicate.Asset { + return predicate.Asset(func(s *sql.Selector) { + step := sqlgraph.NewStep( + sqlgraph.From(Table, FieldID), + sqlgraph.To(OwnerInverseTable, FieldID), + sqlgraph.Edge(sqlgraph.M2O, true, OwnerTable, OwnerColumn), + ) + sqlgraph.HasNeighborsWith(s, step, func(s *sql.Selector) { + for _, p := range preds { + p(s) + } + }) + }) +} + // And groups predicates with the AND operator between them. func And(predicates ...predicate.Asset) predicate.Asset { return predicate.Asset(func(s *sql.Selector) { diff --git a/internal/ent/asset_create.go b/internal/ent/asset_create.go index 618c90bde..a33f90d59 100644 --- a/internal/ent/asset_create.go +++ b/internal/ent/asset_create.go @@ -12,6 +12,7 @@ import ( "entgo.io/ent/dialect/sql" "entgo.io/ent/dialect/sql/sqlgraph" "entgo.io/ent/schema/field" + "github.com/Southclaws/storyden/internal/ent/account" "github.com/Southclaws/storyden/internal/ent/asset" "github.com/Southclaws/storyden/internal/ent/post" "github.com/rs/xid" @@ -83,17 +84,23 @@ func (ac *AssetCreate) SetPostID(x xid.ID) *AssetCreate { return ac } -// SetID sets the "id" field. -func (ac *AssetCreate) SetID(x xid.ID) *AssetCreate { - ac.mutation.SetID(x) +// SetNillablePostID sets the "post_id" field if the given value is not nil. +func (ac *AssetCreate) SetNillablePostID(x *xid.ID) *AssetCreate { + if x != nil { + ac.SetPostID(*x) + } return ac } -// SetNillableID sets the "id" field if the given value is not nil. -func (ac *AssetCreate) SetNillableID(x *xid.ID) *AssetCreate { - if x != nil { - ac.SetID(*x) - } +// SetAccountID sets the "account_id" field. +func (ac *AssetCreate) SetAccountID(x xid.ID) *AssetCreate { + ac.mutation.SetAccountID(x) + return ac +} + +// SetID sets the "id" field. +func (ac *AssetCreate) SetID(s string) *AssetCreate { + ac.mutation.SetID(s) return ac } @@ -102,6 +109,17 @@ func (ac *AssetCreate) SetPost(p *Post) *AssetCreate { return ac.SetPostID(p.ID) } +// SetOwnerID sets the "owner" edge to the Account entity by ID. +func (ac *AssetCreate) SetOwnerID(id xid.ID) *AssetCreate { + ac.mutation.SetOwnerID(id) + return ac +} + +// SetOwner sets the "owner" edge to the Account entity. +func (ac *AssetCreate) SetOwner(a *Account) *AssetCreate { + return ac.SetOwnerID(a.ID) +} + // Mutation returns the AssetMutation object of the builder. func (ac *AssetCreate) Mutation() *AssetMutation { return ac.mutation @@ -145,10 +163,6 @@ func (ac *AssetCreate) defaults() { v := asset.DefaultUpdatedAt() ac.mutation.SetUpdatedAt(v) } - if _, ok := ac.mutation.ID(); !ok { - v := asset.DefaultID() - ac.mutation.SetID(v) - } } // check runs all checks and user-defined validators on the builder. @@ -171,16 +185,16 @@ func (ac *AssetCreate) check() error { if _, ok := ac.mutation.Height(); !ok { return &ValidationError{Name: "height", err: errors.New(`ent: missing required field "Asset.height"`)} } - if _, ok := ac.mutation.PostID(); !ok { - return &ValidationError{Name: "post_id", err: errors.New(`ent: missing required field "Asset.post_id"`)} + if _, ok := ac.mutation.AccountID(); !ok { + return &ValidationError{Name: "account_id", err: errors.New(`ent: missing required field "Asset.account_id"`)} } if v, ok := ac.mutation.ID(); ok { - if err := asset.IDValidator(v.String()); err != nil { + if err := asset.IDValidator(v); err != nil { return &ValidationError{Name: "id", err: fmt.Errorf(`ent: validator failed for field "Asset.id": %w`, err)} } } - if _, ok := ac.mutation.PostID(); !ok { - return &ValidationError{Name: "post", err: errors.New(`ent: missing required edge "Asset.post"`)} + if _, ok := ac.mutation.OwnerID(); !ok { + return &ValidationError{Name: "owner", err: errors.New(`ent: missing required edge "Asset.owner"`)} } return nil } @@ -197,10 +211,10 @@ func (ac *AssetCreate) sqlSave(ctx context.Context) (*Asset, error) { return nil, err } if _spec.ID.Value != nil { - if id, ok := _spec.ID.Value.(*xid.ID); ok { - _node.ID = *id - } else if err := _node.ID.Scan(_spec.ID.Value); err != nil { - return nil, err + if id, ok := _spec.ID.Value.(string); ok { + _node.ID = id + } else { + return nil, fmt.Errorf("unexpected Asset.ID type: %T", _spec.ID.Value) } } ac.mutation.id = &_node.ID @@ -216,7 +230,7 @@ func (ac *AssetCreate) createSpec() (*Asset, *sqlgraph.CreateSpec) { _spec.OnConflict = ac.conflict if id, ok := ac.mutation.ID(); ok { _node.ID = id - _spec.ID.Value = &id + _spec.ID.Value = id } if value, ok := ac.mutation.CreatedAt(); ok { _spec.SetField(asset.FieldCreatedAt, field.TypeTime, value) @@ -259,6 +273,23 @@ func (ac *AssetCreate) createSpec() (*Asset, *sqlgraph.CreateSpec) { _node.PostID = nodes[0] _spec.Edges = append(_spec.Edges, edge) } + if nodes := ac.mutation.OwnerIDs(); len(nodes) > 0 { + edge := &sqlgraph.EdgeSpec{ + Rel: sqlgraph.M2O, + Inverse: true, + Table: asset.OwnerTable, + Columns: []string{asset.OwnerColumn}, + Bidi: false, + Target: &sqlgraph.EdgeTarget{ + IDSpec: sqlgraph.NewFieldSpec(account.FieldID, field.TypeString), + }, + } + for _, k := range nodes { + edge.Target.Nodes = append(edge.Target.Nodes, k) + } + _node.AccountID = nodes[0] + _spec.Edges = append(_spec.Edges, edge) + } return _node, _spec } @@ -395,6 +426,24 @@ func (u *AssetUpsert) UpdatePostID() *AssetUpsert { return u } +// ClearPostID clears the value of the "post_id" field. +func (u *AssetUpsert) ClearPostID() *AssetUpsert { + u.SetNull(asset.FieldPostID) + return u +} + +// SetAccountID sets the "account_id" field. +func (u *AssetUpsert) SetAccountID(v xid.ID) *AssetUpsert { + u.Set(asset.FieldAccountID, v) + return u +} + +// UpdateAccountID sets the "account_id" field to the value that was provided on create. +func (u *AssetUpsert) UpdateAccountID() *AssetUpsert { + u.SetExcluded(asset.FieldAccountID) + return u +} + // UpdateNewValues updates the mutable fields using the new values that were set on create except the ID field. // Using this option is equivalent to using: // @@ -544,6 +593,27 @@ func (u *AssetUpsertOne) UpdatePostID() *AssetUpsertOne { }) } +// ClearPostID clears the value of the "post_id" field. +func (u *AssetUpsertOne) ClearPostID() *AssetUpsertOne { + return u.Update(func(s *AssetUpsert) { + s.ClearPostID() + }) +} + +// SetAccountID sets the "account_id" field. +func (u *AssetUpsertOne) SetAccountID(v xid.ID) *AssetUpsertOne { + return u.Update(func(s *AssetUpsert) { + s.SetAccountID(v) + }) +} + +// UpdateAccountID sets the "account_id" field to the value that was provided on create. +func (u *AssetUpsertOne) UpdateAccountID() *AssetUpsertOne { + return u.Update(func(s *AssetUpsert) { + s.UpdateAccountID() + }) +} + // Exec executes the query. func (u *AssetUpsertOne) Exec(ctx context.Context) error { if len(u.create.conflict) == 0 { @@ -560,7 +630,7 @@ func (u *AssetUpsertOne) ExecX(ctx context.Context) { } // Exec executes the UPSERT query and returns the inserted/updated ID. -func (u *AssetUpsertOne) ID(ctx context.Context) (id xid.ID, err error) { +func (u *AssetUpsertOne) ID(ctx context.Context) (id string, err error) { if u.create.driver.Dialect() == dialect.MySQL { // In case of "ON CONFLICT", there is no way to get back non-numeric ID // fields from the database since MySQL does not support the RETURNING clause. @@ -574,7 +644,7 @@ func (u *AssetUpsertOne) ID(ctx context.Context) (id xid.ID, err error) { } // IDX is like ID, but panics if an error occurs. -func (u *AssetUpsertOne) IDX(ctx context.Context) xid.ID { +func (u *AssetUpsertOne) IDX(ctx context.Context) string { id, err := u.ID(ctx) if err != nil { panic(err) @@ -856,6 +926,27 @@ func (u *AssetUpsertBulk) UpdatePostID() *AssetUpsertBulk { }) } +// ClearPostID clears the value of the "post_id" field. +func (u *AssetUpsertBulk) ClearPostID() *AssetUpsertBulk { + return u.Update(func(s *AssetUpsert) { + s.ClearPostID() + }) +} + +// SetAccountID sets the "account_id" field. +func (u *AssetUpsertBulk) SetAccountID(v xid.ID) *AssetUpsertBulk { + return u.Update(func(s *AssetUpsert) { + s.SetAccountID(v) + }) +} + +// UpdateAccountID sets the "account_id" field to the value that was provided on create. +func (u *AssetUpsertBulk) UpdateAccountID() *AssetUpsertBulk { + return u.Update(func(s *AssetUpsert) { + s.UpdateAccountID() + }) +} + // Exec executes the query. func (u *AssetUpsertBulk) Exec(ctx context.Context) error { for i, b := range u.create.builders { diff --git a/internal/ent/asset_query.go b/internal/ent/asset_query.go index d876bc439..0623e9c44 100644 --- a/internal/ent/asset_query.go +++ b/internal/ent/asset_query.go @@ -10,6 +10,7 @@ import ( "entgo.io/ent/dialect/sql" "entgo.io/ent/dialect/sql/sqlgraph" "entgo.io/ent/schema/field" + "github.com/Southclaws/storyden/internal/ent/account" "github.com/Southclaws/storyden/internal/ent/asset" "github.com/Southclaws/storyden/internal/ent/post" "github.com/Southclaws/storyden/internal/ent/predicate" @@ -24,6 +25,7 @@ type AssetQuery struct { inters []Interceptor predicates []predicate.Asset withPost *PostQuery + withOwner *AccountQuery modifiers []func(*sql.Selector) // intermediate query (i.e. traversal path). sql *sql.Selector @@ -83,6 +85,28 @@ func (aq *AssetQuery) QueryPost() *PostQuery { return query } +// QueryOwner chains the current query on the "owner" edge. +func (aq *AssetQuery) QueryOwner() *AccountQuery { + query := (&AccountClient{config: aq.config}).Query() + query.path = func(ctx context.Context) (fromU *sql.Selector, err error) { + if err := aq.prepareQuery(ctx); err != nil { + return nil, err + } + selector := aq.sqlQuery(ctx) + if err := selector.Err(); err != nil { + return nil, err + } + step := sqlgraph.NewStep( + sqlgraph.From(asset.Table, asset.FieldID, selector), + sqlgraph.To(account.Table, account.FieldID), + sqlgraph.Edge(sqlgraph.M2O, true, asset.OwnerTable, asset.OwnerColumn), + ) + fromU = sqlgraph.SetNeighbors(aq.driver.Dialect(), step) + return fromU, nil + } + return query +} + // First returns the first Asset entity from the query. // Returns a *NotFoundError when no Asset was found. func (aq *AssetQuery) First(ctx context.Context) (*Asset, error) { @@ -107,8 +131,8 @@ func (aq *AssetQuery) FirstX(ctx context.Context) *Asset { // FirstID returns the first Asset ID from the query. // Returns a *NotFoundError when no Asset ID was found. -func (aq *AssetQuery) FirstID(ctx context.Context) (id xid.ID, err error) { - var ids []xid.ID +func (aq *AssetQuery) FirstID(ctx context.Context) (id string, err error) { + var ids []string if ids, err = aq.Limit(1).IDs(setContextOp(ctx, aq.ctx, "FirstID")); err != nil { return } @@ -120,7 +144,7 @@ func (aq *AssetQuery) FirstID(ctx context.Context) (id xid.ID, err error) { } // FirstIDX is like FirstID, but panics if an error occurs. -func (aq *AssetQuery) FirstIDX(ctx context.Context) xid.ID { +func (aq *AssetQuery) FirstIDX(ctx context.Context) string { id, err := aq.FirstID(ctx) if err != nil && !IsNotFound(err) { panic(err) @@ -158,8 +182,8 @@ func (aq *AssetQuery) OnlyX(ctx context.Context) *Asset { // OnlyID is like Only, but returns the only Asset ID in the query. // Returns a *NotSingularError when more than one Asset ID is found. // Returns a *NotFoundError when no entities are found. -func (aq *AssetQuery) OnlyID(ctx context.Context) (id xid.ID, err error) { - var ids []xid.ID +func (aq *AssetQuery) OnlyID(ctx context.Context) (id string, err error) { + var ids []string if ids, err = aq.Limit(2).IDs(setContextOp(ctx, aq.ctx, "OnlyID")); err != nil { return } @@ -175,7 +199,7 @@ func (aq *AssetQuery) OnlyID(ctx context.Context) (id xid.ID, err error) { } // OnlyIDX is like OnlyID, but panics if an error occurs. -func (aq *AssetQuery) OnlyIDX(ctx context.Context) xid.ID { +func (aq *AssetQuery) OnlyIDX(ctx context.Context) string { id, err := aq.OnlyID(ctx) if err != nil { panic(err) @@ -203,7 +227,7 @@ func (aq *AssetQuery) AllX(ctx context.Context) []*Asset { } // IDs executes the query and returns a list of Asset IDs. -func (aq *AssetQuery) IDs(ctx context.Context) (ids []xid.ID, err error) { +func (aq *AssetQuery) IDs(ctx context.Context) (ids []string, err error) { if aq.ctx.Unique == nil && aq.path != nil { aq.Unique(true) } @@ -215,7 +239,7 @@ func (aq *AssetQuery) IDs(ctx context.Context) (ids []xid.ID, err error) { } // IDsX is like IDs, but panics if an error occurs. -func (aq *AssetQuery) IDsX(ctx context.Context) []xid.ID { +func (aq *AssetQuery) IDsX(ctx context.Context) []string { ids, err := aq.IDs(ctx) if err != nil { panic(err) @@ -276,6 +300,7 @@ func (aq *AssetQuery) Clone() *AssetQuery { inters: append([]Interceptor{}, aq.inters...), predicates: append([]predicate.Asset{}, aq.predicates...), withPost: aq.withPost.Clone(), + withOwner: aq.withOwner.Clone(), // clone intermediate query. sql: aq.sql.Clone(), path: aq.path, @@ -293,6 +318,17 @@ func (aq *AssetQuery) WithPost(opts ...func(*PostQuery)) *AssetQuery { return aq } +// WithOwner tells the query-builder to eager-load the nodes that are connected to +// the "owner" edge. The optional arguments are used to configure the query builder of the edge. +func (aq *AssetQuery) WithOwner(opts ...func(*AccountQuery)) *AssetQuery { + query := (&AccountClient{config: aq.config}).Query() + for _, opt := range opts { + opt(query) + } + aq.withOwner = query + return aq +} + // GroupBy is used to group vertices by one or more fields/columns. // It is often used with aggregate functions, like: count, max, mean, min, sum. // @@ -371,8 +407,9 @@ func (aq *AssetQuery) sqlAll(ctx context.Context, hooks ...queryHook) ([]*Asset, var ( nodes = []*Asset{} _spec = aq.querySpec() - loadedTypes = [1]bool{ + loadedTypes = [2]bool{ aq.withPost != nil, + aq.withOwner != nil, } ) _spec.ScanValues = func(columns []string) ([]any, error) { @@ -402,6 +439,12 @@ func (aq *AssetQuery) sqlAll(ctx context.Context, hooks ...queryHook) ([]*Asset, return nil, err } } + if query := aq.withOwner; query != nil { + if err := aq.loadOwner(ctx, query, nodes, nil, + func(n *Asset, e *Account) { n.Edges.Owner = e }); err != nil { + return nil, err + } + } return nodes, nil } @@ -434,6 +477,35 @@ func (aq *AssetQuery) loadPost(ctx context.Context, query *PostQuery, nodes []*A } return nil } +func (aq *AssetQuery) loadOwner(ctx context.Context, query *AccountQuery, nodes []*Asset, init func(*Asset), assign func(*Asset, *Account)) error { + ids := make([]xid.ID, 0, len(nodes)) + nodeids := make(map[xid.ID][]*Asset) + for i := range nodes { + fk := nodes[i].AccountID + if _, ok := nodeids[fk]; !ok { + ids = append(ids, fk) + } + nodeids[fk] = append(nodeids[fk], nodes[i]) + } + if len(ids) == 0 { + return nil + } + query.Where(account.IDIn(ids...)) + neighbors, err := query.All(ctx) + if err != nil { + return err + } + for _, n := range neighbors { + nodes, ok := nodeids[n.ID] + if !ok { + return fmt.Errorf(`unexpected foreign-key "account_id" returned %v`, n.ID) + } + for i := range nodes { + assign(nodes[i], n) + } + } + return nil +} func (aq *AssetQuery) sqlCount(ctx context.Context) (int, error) { _spec := aq.querySpec() diff --git a/internal/ent/asset_update.go b/internal/ent/asset_update.go index 7b087cf5f..b8d37e1d6 100644 --- a/internal/ent/asset_update.go +++ b/internal/ent/asset_update.go @@ -11,6 +11,7 @@ import ( "entgo.io/ent/dialect/sql" "entgo.io/ent/dialect/sql/sqlgraph" "entgo.io/ent/schema/field" + "github.com/Southclaws/storyden/internal/ent/account" "github.com/Southclaws/storyden/internal/ent/asset" "github.com/Southclaws/storyden/internal/ent/post" "github.com/Southclaws/storyden/internal/ent/predicate" @@ -81,11 +82,42 @@ func (au *AssetUpdate) SetPostID(x xid.ID) *AssetUpdate { return au } +// SetNillablePostID sets the "post_id" field if the given value is not nil. +func (au *AssetUpdate) SetNillablePostID(x *xid.ID) *AssetUpdate { + if x != nil { + au.SetPostID(*x) + } + return au +} + +// ClearPostID clears the value of the "post_id" field. +func (au *AssetUpdate) ClearPostID() *AssetUpdate { + au.mutation.ClearPostID() + return au +} + +// SetAccountID sets the "account_id" field. +func (au *AssetUpdate) SetAccountID(x xid.ID) *AssetUpdate { + au.mutation.SetAccountID(x) + return au +} + // SetPost sets the "post" edge to the Post entity. func (au *AssetUpdate) SetPost(p *Post) *AssetUpdate { return au.SetPostID(p.ID) } +// SetOwnerID sets the "owner" edge to the Account entity by ID. +func (au *AssetUpdate) SetOwnerID(id xid.ID) *AssetUpdate { + au.mutation.SetOwnerID(id) + return au +} + +// SetOwner sets the "owner" edge to the Account entity. +func (au *AssetUpdate) SetOwner(a *Account) *AssetUpdate { + return au.SetOwnerID(a.ID) +} + // Mutation returns the AssetMutation object of the builder. func (au *AssetUpdate) Mutation() *AssetMutation { return au.mutation @@ -97,6 +129,12 @@ func (au *AssetUpdate) ClearPost() *AssetUpdate { return au } +// ClearOwner clears the "owner" edge to the Account entity. +func (au *AssetUpdate) ClearOwner() *AssetUpdate { + au.mutation.ClearOwner() + return au +} + // Save executes the query and returns the number of nodes affected by the update operation. func (au *AssetUpdate) Save(ctx context.Context) (int, error) { au.defaults() @@ -135,8 +173,8 @@ func (au *AssetUpdate) defaults() { // check runs all checks and user-defined validators on the builder. func (au *AssetUpdate) check() error { - if _, ok := au.mutation.PostID(); au.mutation.PostCleared() && !ok { - return errors.New(`ent: clearing a required unique edge "Asset.post"`) + if _, ok := au.mutation.OwnerID(); au.mutation.OwnerCleared() && !ok { + return errors.New(`ent: clearing a required unique edge "Asset.owner"`) } return nil } @@ -209,6 +247,35 @@ func (au *AssetUpdate) sqlSave(ctx context.Context) (n int, err error) { } _spec.Edges.Add = append(_spec.Edges.Add, edge) } + if au.mutation.OwnerCleared() { + edge := &sqlgraph.EdgeSpec{ + Rel: sqlgraph.M2O, + Inverse: true, + Table: asset.OwnerTable, + Columns: []string{asset.OwnerColumn}, + Bidi: false, + Target: &sqlgraph.EdgeTarget{ + IDSpec: sqlgraph.NewFieldSpec(account.FieldID, field.TypeString), + }, + } + _spec.Edges.Clear = append(_spec.Edges.Clear, edge) + } + if nodes := au.mutation.OwnerIDs(); len(nodes) > 0 { + edge := &sqlgraph.EdgeSpec{ + Rel: sqlgraph.M2O, + Inverse: true, + Table: asset.OwnerTable, + Columns: []string{asset.OwnerColumn}, + Bidi: false, + Target: &sqlgraph.EdgeTarget{ + IDSpec: sqlgraph.NewFieldSpec(account.FieldID, field.TypeString), + }, + } + for _, k := range nodes { + edge.Target.Nodes = append(edge.Target.Nodes, k) + } + _spec.Edges.Add = append(_spec.Edges.Add, edge) + } _spec.AddModifiers(au.modifiers...) if n, err = sqlgraph.UpdateNodes(ctx, au.driver, _spec); err != nil { if _, ok := err.(*sqlgraph.NotFoundError); ok { @@ -281,11 +348,42 @@ func (auo *AssetUpdateOne) SetPostID(x xid.ID) *AssetUpdateOne { return auo } +// SetNillablePostID sets the "post_id" field if the given value is not nil. +func (auo *AssetUpdateOne) SetNillablePostID(x *xid.ID) *AssetUpdateOne { + if x != nil { + auo.SetPostID(*x) + } + return auo +} + +// ClearPostID clears the value of the "post_id" field. +func (auo *AssetUpdateOne) ClearPostID() *AssetUpdateOne { + auo.mutation.ClearPostID() + return auo +} + +// SetAccountID sets the "account_id" field. +func (auo *AssetUpdateOne) SetAccountID(x xid.ID) *AssetUpdateOne { + auo.mutation.SetAccountID(x) + return auo +} + // SetPost sets the "post" edge to the Post entity. func (auo *AssetUpdateOne) SetPost(p *Post) *AssetUpdateOne { return auo.SetPostID(p.ID) } +// SetOwnerID sets the "owner" edge to the Account entity by ID. +func (auo *AssetUpdateOne) SetOwnerID(id xid.ID) *AssetUpdateOne { + auo.mutation.SetOwnerID(id) + return auo +} + +// SetOwner sets the "owner" edge to the Account entity. +func (auo *AssetUpdateOne) SetOwner(a *Account) *AssetUpdateOne { + return auo.SetOwnerID(a.ID) +} + // Mutation returns the AssetMutation object of the builder. func (auo *AssetUpdateOne) Mutation() *AssetMutation { return auo.mutation @@ -297,6 +395,12 @@ func (auo *AssetUpdateOne) ClearPost() *AssetUpdateOne { return auo } +// ClearOwner clears the "owner" edge to the Account entity. +func (auo *AssetUpdateOne) ClearOwner() *AssetUpdateOne { + auo.mutation.ClearOwner() + return auo +} + // Where appends a list predicates to the AssetUpdate builder. func (auo *AssetUpdateOne) Where(ps ...predicate.Asset) *AssetUpdateOne { auo.mutation.Where(ps...) @@ -348,8 +452,8 @@ func (auo *AssetUpdateOne) defaults() { // check runs all checks and user-defined validators on the builder. func (auo *AssetUpdateOne) check() error { - if _, ok := auo.mutation.PostID(); auo.mutation.PostCleared() && !ok { - return errors.New(`ent: clearing a required unique edge "Asset.post"`) + if _, ok := auo.mutation.OwnerID(); auo.mutation.OwnerCleared() && !ok { + return errors.New(`ent: clearing a required unique edge "Asset.owner"`) } return nil } @@ -439,6 +543,35 @@ func (auo *AssetUpdateOne) sqlSave(ctx context.Context) (_node *Asset, err error } _spec.Edges.Add = append(_spec.Edges.Add, edge) } + if auo.mutation.OwnerCleared() { + edge := &sqlgraph.EdgeSpec{ + Rel: sqlgraph.M2O, + Inverse: true, + Table: asset.OwnerTable, + Columns: []string{asset.OwnerColumn}, + Bidi: false, + Target: &sqlgraph.EdgeTarget{ + IDSpec: sqlgraph.NewFieldSpec(account.FieldID, field.TypeString), + }, + } + _spec.Edges.Clear = append(_spec.Edges.Clear, edge) + } + if nodes := auo.mutation.OwnerIDs(); len(nodes) > 0 { + edge := &sqlgraph.EdgeSpec{ + Rel: sqlgraph.M2O, + Inverse: true, + Table: asset.OwnerTable, + Columns: []string{asset.OwnerColumn}, + Bidi: false, + Target: &sqlgraph.EdgeTarget{ + IDSpec: sqlgraph.NewFieldSpec(account.FieldID, field.TypeString), + }, + } + for _, k := range nodes { + edge.Target.Nodes = append(edge.Target.Nodes, k) + } + _spec.Edges.Add = append(_spec.Edges.Add, edge) + } _spec.AddModifiers(auo.modifiers...) _node = &Asset{config: auo.config} _spec.Assign = _node.assignValues diff --git a/internal/ent/client.go b/internal/ent/client.go index 6c43cb257..17d533386 100644 --- a/internal/ent/client.go +++ b/internal/ent/client.go @@ -444,6 +444,22 @@ func (c *AccountClient) QueryTags(a *Account) *TagQuery { return query } +// QueryAssets queries the assets edge of a Account. +func (c *AccountClient) QueryAssets(a *Account) *AssetQuery { + query := (&AssetClient{config: c.config}).Query() + query.path = func(context.Context) (fromV *sql.Selector, _ error) { + id := a.ID + step := sqlgraph.NewStep( + sqlgraph.From(account.Table, account.FieldID, id), + sqlgraph.To(asset.Table, asset.FieldID), + sqlgraph.Edge(sqlgraph.O2M, false, account.AssetsTable, account.AssetsColumn), + ) + fromV = sqlgraph.Neighbors(a.driver.Dialect(), step) + return fromV, nil + } + return query +} + // Hooks returns the client hooks. func (c *AccountClient) Hooks() []Hook { return c.hooks.Account @@ -515,7 +531,7 @@ func (c *AssetClient) UpdateOne(a *Asset) *AssetUpdateOne { } // UpdateOneID returns an update builder for the given id. -func (c *AssetClient) UpdateOneID(id xid.ID) *AssetUpdateOne { +func (c *AssetClient) UpdateOneID(id string) *AssetUpdateOne { mutation := newAssetMutation(c.config, OpUpdateOne, withAssetID(id)) return &AssetUpdateOne{config: c.config, hooks: c.Hooks(), mutation: mutation} } @@ -532,7 +548,7 @@ func (c *AssetClient) DeleteOne(a *Asset) *AssetDeleteOne { } // DeleteOneID returns a builder for deleting the given entity by its id. -func (c *AssetClient) DeleteOneID(id xid.ID) *AssetDeleteOne { +func (c *AssetClient) DeleteOneID(id string) *AssetDeleteOne { builder := c.Delete().Where(asset.ID(id)) builder.mutation.id = &id builder.mutation.op = OpDeleteOne @@ -549,12 +565,12 @@ func (c *AssetClient) Query() *AssetQuery { } // Get returns a Asset entity by its id. -func (c *AssetClient) Get(ctx context.Context, id xid.ID) (*Asset, error) { +func (c *AssetClient) Get(ctx context.Context, id string) (*Asset, error) { return c.Query().Where(asset.ID(id)).Only(ctx) } // GetX is like Get, but panics if an error occurs. -func (c *AssetClient) GetX(ctx context.Context, id xid.ID) *Asset { +func (c *AssetClient) GetX(ctx context.Context, id string) *Asset { obj, err := c.Get(ctx, id) if err != nil { panic(err) @@ -578,6 +594,22 @@ func (c *AssetClient) QueryPost(a *Asset) *PostQuery { return query } +// QueryOwner queries the owner edge of a Asset. +func (c *AssetClient) QueryOwner(a *Asset) *AccountQuery { + query := (&AccountClient{config: c.config}).Query() + query.path = func(context.Context) (fromV *sql.Selector, _ error) { + id := a.ID + step := sqlgraph.NewStep( + sqlgraph.From(asset.Table, asset.FieldID, id), + sqlgraph.To(account.Table, account.FieldID), + sqlgraph.Edge(sqlgraph.M2O, true, asset.OwnerTable, asset.OwnerColumn), + ) + fromV = sqlgraph.Neighbors(a.driver.Dialect(), step) + return fromV, nil + } + return query +} + // Hooks returns the client hooks. func (c *AssetClient) Hooks() []Hook { return c.hooks.Asset diff --git a/internal/ent/migrate/schema.go b/internal/ent/migrate/schema.go index d3fa2dc35..42c535192 100644 --- a/internal/ent/migrate/schema.go +++ b/internal/ent/migrate/schema.go @@ -27,14 +27,15 @@ var ( } // AssetsColumns holds the columns for the "assets" table. AssetsColumns = []*schema.Column{ - {Name: "id", Type: field.TypeString, Size: 20}, + {Name: "id", Type: field.TypeString, Unique: true}, {Name: "created_at", Type: field.TypeTime}, {Name: "updated_at", Type: field.TypeTime}, {Name: "url", Type: field.TypeString}, {Name: "mimetype", Type: field.TypeString}, {Name: "width", Type: field.TypeInt}, {Name: "height", Type: field.TypeInt}, - {Name: "post_id", Type: field.TypeString, Size: 20}, + {Name: "account_id", Type: field.TypeString, Size: 20}, + {Name: "post_id", Type: field.TypeString, Nullable: true, Size: 20}, } // AssetsTable holds the schema information for the "assets" table. AssetsTable = &schema.Table{ @@ -43,11 +44,17 @@ var ( PrimaryKey: []*schema.Column{AssetsColumns[0]}, ForeignKeys: []*schema.ForeignKey{ { - Symbol: "assets_posts_assets", + Symbol: "assets_accounts_assets", Columns: []*schema.Column{AssetsColumns[7]}, - RefColumns: []*schema.Column{PostsColumns[0]}, + RefColumns: []*schema.Column{AccountsColumns[0]}, OnDelete: schema.NoAction, }, + { + Symbol: "assets_posts_assets", + Columns: []*schema.Column{AssetsColumns[8]}, + RefColumns: []*schema.Column{PostsColumns[0]}, + OnDelete: schema.SetNull, + }, }, } // AuthenticationsColumns holds the columns for the "authentications" table. @@ -323,7 +330,8 @@ var ( ) func init() { - AssetsTable.ForeignKeys[0].RefTable = PostsTable + AssetsTable.ForeignKeys[0].RefTable = AccountsTable + AssetsTable.ForeignKeys[1].RefTable = PostsTable AuthenticationsTable.ForeignKeys[0].RefTable = AccountsTable PostsTable.ForeignKeys[0].RefTable = AccountsTable PostsTable.ForeignKeys[1].RefTable = CategoriesTable diff --git a/internal/ent/mutation.go b/internal/ent/mutation.go index ac22c2a7e..95bc1d582 100644 --- a/internal/ent/mutation.go +++ b/internal/ent/mutation.go @@ -75,6 +75,9 @@ type AccountMutation struct { tags map[xid.ID]struct{} removedtags map[xid.ID]struct{} clearedtags bool + assets map[string]struct{} + removedassets map[string]struct{} + clearedassets bool done bool oldValue func(context.Context) (*Account, error) predicates []predicate.Account @@ -732,6 +735,60 @@ func (m *AccountMutation) ResetTags() { m.removedtags = nil } +// AddAssetIDs adds the "assets" edge to the Asset entity by ids. +func (m *AccountMutation) AddAssetIDs(ids ...string) { + if m.assets == nil { + m.assets = make(map[string]struct{}) + } + for i := range ids { + m.assets[ids[i]] = struct{}{} + } +} + +// ClearAssets clears the "assets" edge to the Asset entity. +func (m *AccountMutation) ClearAssets() { + m.clearedassets = true +} + +// AssetsCleared reports if the "assets" edge to the Asset entity was cleared. +func (m *AccountMutation) AssetsCleared() bool { + return m.clearedassets +} + +// RemoveAssetIDs removes the "assets" edge to the Asset entity by IDs. +func (m *AccountMutation) RemoveAssetIDs(ids ...string) { + if m.removedassets == nil { + m.removedassets = make(map[string]struct{}) + } + for i := range ids { + delete(m.assets, ids[i]) + m.removedassets[ids[i]] = struct{}{} + } +} + +// RemovedAssets returns the removed IDs of the "assets" edge to the Asset entity. +func (m *AccountMutation) RemovedAssetsIDs() (ids []string) { + for id := range m.removedassets { + ids = append(ids, id) + } + return +} + +// AssetsIDs returns the "assets" edge IDs in the mutation. +func (m *AccountMutation) AssetsIDs() (ids []string) { + for id := range m.assets { + ids = append(ids, id) + } + return +} + +// ResetAssets resets all changes to the "assets" edge. +func (m *AccountMutation) ResetAssets() { + m.assets = nil + m.clearedassets = false + m.removedassets = nil +} + // Where appends a list predicates to the AccountMutation builder. func (m *AccountMutation) Where(ps ...predicate.Account) { m.predicates = append(m.predicates, ps...) @@ -982,7 +1039,7 @@ func (m *AccountMutation) ResetField(name string) error { // AddedEdges returns all edge names that were set/added in this mutation. func (m *AccountMutation) AddedEdges() []string { - edges := make([]string, 0, 5) + edges := make([]string, 0, 6) if m.posts != nil { edges = append(edges, account.EdgePosts) } @@ -998,6 +1055,9 @@ func (m *AccountMutation) AddedEdges() []string { if m.tags != nil { edges = append(edges, account.EdgeTags) } + if m.assets != nil { + edges = append(edges, account.EdgeAssets) + } return edges } @@ -1035,13 +1095,19 @@ func (m *AccountMutation) AddedIDs(name string) []ent.Value { ids = append(ids, id) } return ids + case account.EdgeAssets: + ids := make([]ent.Value, 0, len(m.assets)) + for id := range m.assets { + ids = append(ids, id) + } + return ids } return nil } // RemovedEdges returns all edge names that were removed in this mutation. func (m *AccountMutation) RemovedEdges() []string { - edges := make([]string, 0, 5) + edges := make([]string, 0, 6) if m.removedposts != nil { edges = append(edges, account.EdgePosts) } @@ -1057,6 +1123,9 @@ func (m *AccountMutation) RemovedEdges() []string { if m.removedtags != nil { edges = append(edges, account.EdgeTags) } + if m.removedassets != nil { + edges = append(edges, account.EdgeAssets) + } return edges } @@ -1094,13 +1163,19 @@ func (m *AccountMutation) RemovedIDs(name string) []ent.Value { ids = append(ids, id) } return ids + case account.EdgeAssets: + ids := make([]ent.Value, 0, len(m.removedassets)) + for id := range m.removedassets { + ids = append(ids, id) + } + return ids } return nil } // ClearedEdges returns all edge names that were cleared in this mutation. func (m *AccountMutation) ClearedEdges() []string { - edges := make([]string, 0, 5) + edges := make([]string, 0, 6) if m.clearedposts { edges = append(edges, account.EdgePosts) } @@ -1116,6 +1191,9 @@ func (m *AccountMutation) ClearedEdges() []string { if m.clearedtags { edges = append(edges, account.EdgeTags) } + if m.clearedassets { + edges = append(edges, account.EdgeAssets) + } return edges } @@ -1133,6 +1211,8 @@ func (m *AccountMutation) EdgeCleared(name string) bool { return m.clearedauthentication case account.EdgeTags: return m.clearedtags + case account.EdgeAssets: + return m.clearedassets } return false } @@ -1164,6 +1244,9 @@ func (m *AccountMutation) ResetEdge(name string) error { case account.EdgeTags: m.ResetTags() return nil + case account.EdgeAssets: + m.ResetAssets() + return nil } return fmt.Errorf("unknown Account edge %s", name) } @@ -1173,7 +1256,7 @@ type AssetMutation struct { config op Op typ string - id *xid.ID + id *string created_at *time.Time updated_at *time.Time url *string @@ -1185,6 +1268,8 @@ type AssetMutation struct { clearedFields map[string]struct{} post *xid.ID clearedpost bool + owner *xid.ID + clearedowner bool done bool oldValue func(context.Context) (*Asset, error) predicates []predicate.Asset @@ -1210,7 +1295,7 @@ func newAssetMutation(c config, op Op, opts ...assetOption) *AssetMutation { } // withAssetID sets the ID field of the mutation. -func withAssetID(id xid.ID) assetOption { +func withAssetID(id string) assetOption { return func(m *AssetMutation) { var ( err error @@ -1262,13 +1347,13 @@ func (m AssetMutation) Tx() (*Tx, error) { // SetID sets the value of the id field. Note that this // operation is only accepted on creation of Asset entities. -func (m *AssetMutation) SetID(id xid.ID) { +func (m *AssetMutation) SetID(id string) { m.id = &id } // ID returns the ID value in the mutation. Note that the ID is only available // if it was provided to the builder or after it was returned from the database. -func (m *AssetMutation) ID() (id xid.ID, exists bool) { +func (m *AssetMutation) ID() (id string, exists bool) { if m.id == nil { return } @@ -1279,12 +1364,12 @@ func (m *AssetMutation) ID() (id xid.ID, exists bool) { // That means, if the mutation is applied within a transaction with an isolation level such // as sql.LevelSerializable, the returned ids match the ids of the rows that will be updated // or updated by the mutation. -func (m *AssetMutation) IDs(ctx context.Context) ([]xid.ID, error) { +func (m *AssetMutation) IDs(ctx context.Context) ([]string, error) { switch { case m.op.Is(OpUpdateOne | OpDeleteOne): id, exists := m.ID() if exists { - return []xid.ID{id}, nil + return []string{id}, nil } fallthrough case m.op.Is(OpUpdate | OpDelete): @@ -1581,9 +1666,58 @@ func (m *AssetMutation) OldPostID(ctx context.Context) (v xid.ID, err error) { return oldValue.PostID, nil } +// ClearPostID clears the value of the "post_id" field. +func (m *AssetMutation) ClearPostID() { + m.post = nil + m.clearedFields[asset.FieldPostID] = struct{}{} +} + +// PostIDCleared returns if the "post_id" field was cleared in this mutation. +func (m *AssetMutation) PostIDCleared() bool { + _, ok := m.clearedFields[asset.FieldPostID] + return ok +} + // ResetPostID resets all changes to the "post_id" field. func (m *AssetMutation) ResetPostID() { m.post = nil + delete(m.clearedFields, asset.FieldPostID) +} + +// SetAccountID sets the "account_id" field. +func (m *AssetMutation) SetAccountID(x xid.ID) { + m.owner = &x +} + +// AccountID returns the value of the "account_id" field in the mutation. +func (m *AssetMutation) AccountID() (r xid.ID, exists bool) { + v := m.owner + if v == nil { + return + } + return *v, true +} + +// OldAccountID returns the old "account_id" field's value of the Asset entity. +// If the Asset 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 *AssetMutation) OldAccountID(ctx context.Context) (v xid.ID, err error) { + if !m.op.Is(OpUpdateOne) { + return v, errors.New("OldAccountID is only allowed on UpdateOne operations") + } + if m.id == nil || m.oldValue == nil { + return v, errors.New("OldAccountID requires an ID field in the mutation") + } + oldValue, err := m.oldValue(ctx) + if err != nil { + return v, fmt.Errorf("querying old value for OldAccountID: %w", err) + } + return oldValue.AccountID, nil +} + +// ResetAccountID resets all changes to the "account_id" field. +func (m *AssetMutation) ResetAccountID() { + m.owner = nil } // ClearPost clears the "post" edge to the Post entity. @@ -1593,7 +1727,7 @@ func (m *AssetMutation) ClearPost() { // PostCleared reports if the "post" edge to the Post entity was cleared. func (m *AssetMutation) PostCleared() bool { - return m.clearedpost + return m.PostIDCleared() || m.clearedpost } // PostIDs returns the "post" edge IDs in the mutation. @@ -1612,6 +1746,45 @@ func (m *AssetMutation) ResetPost() { m.clearedpost = false } +// SetOwnerID sets the "owner" edge to the Account entity by id. +func (m *AssetMutation) SetOwnerID(id xid.ID) { + m.owner = &id +} + +// ClearOwner clears the "owner" edge to the Account entity. +func (m *AssetMutation) ClearOwner() { + m.clearedowner = true +} + +// OwnerCleared reports if the "owner" edge to the Account entity was cleared. +func (m *AssetMutation) OwnerCleared() bool { + return m.clearedowner +} + +// OwnerID returns the "owner" edge ID in the mutation. +func (m *AssetMutation) OwnerID() (id xid.ID, exists bool) { + if m.owner != nil { + return *m.owner, true + } + return +} + +// OwnerIDs returns the "owner" edge IDs in the mutation. +// Note that IDs always returns len(IDs) <= 1 for unique edges, and you should use +// OwnerID instead. It exists only for internal usage by the builders. +func (m *AssetMutation) OwnerIDs() (ids []xid.ID) { + if id := m.owner; id != nil { + ids = append(ids, *id) + } + return +} + +// ResetOwner resets all changes to the "owner" edge. +func (m *AssetMutation) ResetOwner() { + m.owner = nil + m.clearedowner = false +} + // Where appends a list predicates to the AssetMutation builder. func (m *AssetMutation) Where(ps ...predicate.Asset) { m.predicates = append(m.predicates, ps...) @@ -1646,7 +1819,7 @@ func (m *AssetMutation) Type() string { // order to get all numeric fields that were incremented/decremented, call // AddedFields(). func (m *AssetMutation) Fields() []string { - fields := make([]string, 0, 7) + fields := make([]string, 0, 8) if m.created_at != nil { fields = append(fields, asset.FieldCreatedAt) } @@ -1668,6 +1841,9 @@ func (m *AssetMutation) Fields() []string { if m.post != nil { fields = append(fields, asset.FieldPostID) } + if m.owner != nil { + fields = append(fields, asset.FieldAccountID) + } return fields } @@ -1690,6 +1866,8 @@ func (m *AssetMutation) Field(name string) (ent.Value, bool) { return m.Height() case asset.FieldPostID: return m.PostID() + case asset.FieldAccountID: + return m.AccountID() } return nil, false } @@ -1713,6 +1891,8 @@ func (m *AssetMutation) OldField(ctx context.Context, name string) (ent.Value, e return m.OldHeight(ctx) case asset.FieldPostID: return m.OldPostID(ctx) + case asset.FieldAccountID: + return m.OldAccountID(ctx) } return nil, fmt.Errorf("unknown Asset field %s", name) } @@ -1771,6 +1951,13 @@ func (m *AssetMutation) SetField(name string, value ent.Value) error { } m.SetPostID(v) return nil + case asset.FieldAccountID: + v, ok := value.(xid.ID) + if !ok { + return fmt.Errorf("unexpected type %T for field %s", value, name) + } + m.SetAccountID(v) + return nil } return fmt.Errorf("unknown Asset field %s", name) } @@ -1827,7 +2014,11 @@ func (m *AssetMutation) AddField(name string, value ent.Value) error { // ClearedFields returns all nullable fields that were cleared during this // mutation. func (m *AssetMutation) ClearedFields() []string { - return nil + var fields []string + if m.FieldCleared(asset.FieldPostID) { + fields = append(fields, asset.FieldPostID) + } + return fields } // FieldCleared returns a boolean indicating if a field with the given name was @@ -1840,6 +2031,11 @@ func (m *AssetMutation) FieldCleared(name string) bool { // ClearField clears the value of the field with the given name. It returns an // error if the field is not defined in the schema. func (m *AssetMutation) ClearField(name string) error { + switch name { + case asset.FieldPostID: + m.ClearPostID() + return nil + } return fmt.Errorf("unknown Asset nullable field %s", name) } @@ -1868,16 +2064,22 @@ func (m *AssetMutation) ResetField(name string) error { case asset.FieldPostID: m.ResetPostID() return nil + case asset.FieldAccountID: + m.ResetAccountID() + return nil } return fmt.Errorf("unknown Asset field %s", name) } // AddedEdges returns all edge names that were set/added in this mutation. func (m *AssetMutation) AddedEdges() []string { - edges := make([]string, 0, 1) + edges := make([]string, 0, 2) if m.post != nil { edges = append(edges, asset.EdgePost) } + if m.owner != nil { + edges = append(edges, asset.EdgeOwner) + } return edges } @@ -1889,13 +2091,17 @@ func (m *AssetMutation) AddedIDs(name string) []ent.Value { if id := m.post; id != nil { return []ent.Value{*id} } + case asset.EdgeOwner: + if id := m.owner; id != nil { + return []ent.Value{*id} + } } return nil } // RemovedEdges returns all edge names that were removed in this mutation. func (m *AssetMutation) RemovedEdges() []string { - edges := make([]string, 0, 1) + edges := make([]string, 0, 2) return edges } @@ -1907,10 +2113,13 @@ func (m *AssetMutation) RemovedIDs(name string) []ent.Value { // ClearedEdges returns all edge names that were cleared in this mutation. func (m *AssetMutation) ClearedEdges() []string { - edges := make([]string, 0, 1) + edges := make([]string, 0, 2) if m.clearedpost { edges = append(edges, asset.EdgePost) } + if m.clearedowner { + edges = append(edges, asset.EdgeOwner) + } return edges } @@ -1920,6 +2129,8 @@ func (m *AssetMutation) EdgeCleared(name string) bool { switch name { case asset.EdgePost: return m.clearedpost + case asset.EdgeOwner: + return m.clearedowner } return false } @@ -1931,6 +2142,9 @@ func (m *AssetMutation) ClearEdge(name string) error { case asset.EdgePost: m.ClearPost() return nil + case asset.EdgeOwner: + m.ClearOwner() + return nil } return fmt.Errorf("unknown Asset unique edge %s", name) } @@ -1942,6 +2156,9 @@ func (m *AssetMutation) ResetEdge(name string) error { case asset.EdgePost: m.ResetPost() return nil + case asset.EdgeOwner: + m.ResetOwner() + return nil } return fmt.Errorf("unknown Asset edge %s", name) } @@ -3954,8 +4171,8 @@ type PostMutation struct { reacts map[xid.ID]struct{} removedreacts map[xid.ID]struct{} clearedreacts bool - assets map[xid.ID]struct{} - removedassets map[xid.ID]struct{} + assets map[string]struct{} + removedassets map[string]struct{} clearedassets bool done bool oldValue func(context.Context) (*Post, error) @@ -5021,9 +5238,9 @@ func (m *PostMutation) ResetReacts() { } // AddAssetIDs adds the "assets" edge to the Asset entity by ids. -func (m *PostMutation) AddAssetIDs(ids ...xid.ID) { +func (m *PostMutation) AddAssetIDs(ids ...string) { if m.assets == nil { - m.assets = make(map[xid.ID]struct{}) + m.assets = make(map[string]struct{}) } for i := range ids { m.assets[ids[i]] = struct{}{} @@ -5041,9 +5258,9 @@ func (m *PostMutation) AssetsCleared() bool { } // RemoveAssetIDs removes the "assets" edge to the Asset entity by IDs. -func (m *PostMutation) RemoveAssetIDs(ids ...xid.ID) { +func (m *PostMutation) RemoveAssetIDs(ids ...string) { if m.removedassets == nil { - m.removedassets = make(map[xid.ID]struct{}) + m.removedassets = make(map[string]struct{}) } for i := range ids { delete(m.assets, ids[i]) @@ -5052,7 +5269,7 @@ func (m *PostMutation) RemoveAssetIDs(ids ...xid.ID) { } // RemovedAssets returns the removed IDs of the "assets" edge to the Asset entity. -func (m *PostMutation) RemovedAssetsIDs() (ids []xid.ID) { +func (m *PostMutation) RemovedAssetsIDs() (ids []string) { for id := range m.removedassets { ids = append(ids, id) } @@ -5060,7 +5277,7 @@ func (m *PostMutation) RemovedAssetsIDs() (ids []xid.ID) { } // AssetsIDs returns the "assets" edge IDs in the mutation. -func (m *PostMutation) AssetsIDs() (ids []xid.ID) { +func (m *PostMutation) AssetsIDs() (ids []string) { for id := range m.assets { ids = append(ids, id) } diff --git a/internal/ent/post_create.go b/internal/ent/post_create.go index 53c20092a..79620d60e 100644 --- a/internal/ent/post_create.go +++ b/internal/ent/post_create.go @@ -322,14 +322,14 @@ func (pc *PostCreate) AddReacts(r ...*React) *PostCreate { } // AddAssetIDs adds the "assets" edge to the Asset entity by IDs. -func (pc *PostCreate) AddAssetIDs(ids ...xid.ID) *PostCreate { +func (pc *PostCreate) AddAssetIDs(ids ...string) *PostCreate { pc.mutation.AddAssetIDs(ids...) return pc } // AddAssets adds the "assets" edges to the Asset entity. func (pc *PostCreate) AddAssets(a ...*Asset) *PostCreate { - ids := make([]xid.ID, len(a)) + ids := make([]string, len(a)) for i := range a { ids[i] = a[i].ID } diff --git a/internal/ent/post_update.go b/internal/ent/post_update.go index 4d0ff36f7..535e34836 100644 --- a/internal/ent/post_update.go +++ b/internal/ent/post_update.go @@ -334,14 +334,14 @@ func (pu *PostUpdate) AddReacts(r ...*React) *PostUpdate { } // AddAssetIDs adds the "assets" edge to the Asset entity by IDs. -func (pu *PostUpdate) AddAssetIDs(ids ...xid.ID) *PostUpdate { +func (pu *PostUpdate) AddAssetIDs(ids ...string) *PostUpdate { pu.mutation.AddAssetIDs(ids...) return pu } // AddAssets adds the "assets" edges to the Asset entity. func (pu *PostUpdate) AddAssets(a ...*Asset) *PostUpdate { - ids := make([]xid.ID, len(a)) + ids := make([]string, len(a)) for i := range a { ids[i] = a[i].ID } @@ -468,14 +468,14 @@ func (pu *PostUpdate) ClearAssets() *PostUpdate { } // RemoveAssetIDs removes the "assets" edge to Asset entities by IDs. -func (pu *PostUpdate) RemoveAssetIDs(ids ...xid.ID) *PostUpdate { +func (pu *PostUpdate) RemoveAssetIDs(ids ...string) *PostUpdate { pu.mutation.RemoveAssetIDs(ids...) return pu } // RemoveAssets removes "assets" edges to Asset entities. func (pu *PostUpdate) RemoveAssets(a ...*Asset) *PostUpdate { - ids := make([]xid.ID, len(a)) + ids := make([]string, len(a)) for i := range a { ids[i] = a[i].ID } @@ -1253,14 +1253,14 @@ func (puo *PostUpdateOne) AddReacts(r ...*React) *PostUpdateOne { } // AddAssetIDs adds the "assets" edge to the Asset entity by IDs. -func (puo *PostUpdateOne) AddAssetIDs(ids ...xid.ID) *PostUpdateOne { +func (puo *PostUpdateOne) AddAssetIDs(ids ...string) *PostUpdateOne { puo.mutation.AddAssetIDs(ids...) return puo } // AddAssets adds the "assets" edges to the Asset entity. func (puo *PostUpdateOne) AddAssets(a ...*Asset) *PostUpdateOne { - ids := make([]xid.ID, len(a)) + ids := make([]string, len(a)) for i := range a { ids[i] = a[i].ID } @@ -1387,14 +1387,14 @@ func (puo *PostUpdateOne) ClearAssets() *PostUpdateOne { } // RemoveAssetIDs removes the "assets" edge to Asset entities by IDs. -func (puo *PostUpdateOne) RemoveAssetIDs(ids ...xid.ID) *PostUpdateOne { +func (puo *PostUpdateOne) RemoveAssetIDs(ids ...string) *PostUpdateOne { puo.mutation.RemoveAssetIDs(ids...) return puo } // RemoveAssets removes "assets" edges to Asset entities. func (puo *PostUpdateOne) RemoveAssets(a ...*Asset) *PostUpdateOne { - ids := make([]xid.ID, len(a)) + ids := make([]string, len(a)) for i := range a { ids[i] = a[i].ID } diff --git a/internal/ent/runtime.go b/internal/ent/runtime.go index 816d14115..ddb74f530 100644 --- a/internal/ent/runtime.go +++ b/internal/ent/runtime.go @@ -79,40 +79,22 @@ func init() { _ = assetMixinFields0 assetMixinFields1 := assetMixin[1].Fields() _ = assetMixinFields1 - assetMixinFields2 := assetMixin[2].Fields() - _ = assetMixinFields2 assetFields := schema.Asset{}.Fields() _ = assetFields // assetDescCreatedAt is the schema descriptor for created_at field. - assetDescCreatedAt := assetMixinFields1[0].Descriptor() + assetDescCreatedAt := assetMixinFields0[0].Descriptor() // asset.DefaultCreatedAt holds the default value on creation for the created_at field. asset.DefaultCreatedAt = assetDescCreatedAt.Default.(func() time.Time) // assetDescUpdatedAt is the schema descriptor for updated_at field. - assetDescUpdatedAt := assetMixinFields2[0].Descriptor() + assetDescUpdatedAt := assetMixinFields1[0].Descriptor() // asset.DefaultUpdatedAt holds the default value on creation for the updated_at field. asset.DefaultUpdatedAt = assetDescUpdatedAt.Default.(func() time.Time) // asset.UpdateDefaultUpdatedAt holds the default value on update for the updated_at field. asset.UpdateDefaultUpdatedAt = assetDescUpdatedAt.UpdateDefault.(func() time.Time) // assetDescID is the schema descriptor for id field. - assetDescID := assetMixinFields0[0].Descriptor() - // asset.DefaultID holds the default value on creation for the id field. - asset.DefaultID = assetDescID.Default.(func() xid.ID) + assetDescID := assetFields[0].Descriptor() // asset.IDValidator is a validator for the "id" field. It is called by the builders before save. - asset.IDValidator = func() func(string) error { - validators := assetDescID.Validators - fns := [...]func(string) error{ - validators[0].(func(string) error), - validators[1].(func(string) error), - } - return func(id string) error { - for _, fn := range fns { - if err := fn(id); err != nil { - return err - } - } - return nil - } - }() + asset.IDValidator = assetDescID.Validators[0].(func(string) error) authenticationMixin := schema.Authentication{}.Mixin() authenticationMixinFields0 := authenticationMixin[0].Fields() _ = authenticationMixinFields0 diff --git a/internal/ent/schema/account.go b/internal/ent/schema/account.go index 860c3705b..f01372dc1 100644 --- a/internal/ent/schema/account.go +++ b/internal/ent/schema/account.go @@ -35,5 +35,7 @@ func (Account) Edges() []ent.Edge { edge.To("authentication", Authentication.Type), edge.To("tags", Tag.Type), + + edge.To("assets", Asset.Type), } } diff --git a/internal/ent/schema/asset.go b/internal/ent/schema/asset.go index a15a2480a..8b6c147a7 100644 --- a/internal/ent/schema/asset.go +++ b/internal/ent/schema/asset.go @@ -12,18 +12,24 @@ type Asset struct { } func (Asset) Mixin() []ent.Mixin { - return []ent.Mixin{Identifier{}, CreatedAt{}, UpdatedAt{}} + return []ent.Mixin{CreatedAt{}, UpdatedAt{}} } func (Asset) Fields() []ent.Field { return []ent.Field{ + field.String("id"). + NotEmpty(). + Immutable(). + Unique(), + field.String("url"), field.String("mimetype"), field.Int("width"), field.Int("height"), // Edges - field.String("post_id").GoType(xid.ID{}), + field.String("post_id").GoType(xid.ID{}).Optional(), + field.String("account_id").GoType(xid.ID{}), } } @@ -33,6 +39,11 @@ func (Asset) Edges() []ent.Edge { edge.From("post", Post.Type). Field("post_id"). Ref("assets"). + Unique(), + + edge.From("owner", Account.Type). + Field("account_id"). + Ref("assets"). Unique(). Required(), } diff --git a/internal/openapi/generated.go b/internal/openapi/generated.go index 1bd019d67..e84908a6f 100644 --- a/internal/openapi/generated.go +++ b/internal/openapi/generated.go @@ -156,14 +156,24 @@ type AccountName = string // Asset defines model for Asset. type Asset struct { - Url string `json:"url"` -} + Height float32 `json:"height"` -// AssetUploadURL defines model for AssetUploadURL. -type AssetUploadURL struct { - Url string `json:"url"` + // Id A unique identifier for this resource. + Id AssetID `json:"id"` + MimeType string `json:"mime_type"` + Url string `json:"url"` + Width float32 `json:"width"` } +// AssetID A unique identifier for this resource. +type AssetID = Identifier + +// AssetList defines model for AssetList. +type AssetList = []Asset + +// AssetReferenceList defines model for AssetReferenceList. +type AssetReferenceList = []AssetID + // AttestationConveyancePreference https://www.w3.org/TR/webauthn-2/#enum-attestation-convey type AttestationConveyancePreference string @@ -327,17 +337,6 @@ type Info struct { Title string `json:"title"` } -// MediaItem defines model for MediaItem. -type MediaItem struct { - Height float32 `json:"height"` - MimeType string `json:"mime_type"` - Url string `json:"url"` - Width float32 `json:"width"` -} - -// MediaItemList defines model for MediaItemList. -type MediaItemList = []MediaItem - // Metadata Arbitrary metadata for the resource. type Metadata map[string]interface{} @@ -370,8 +369,8 @@ type PostCommonProps struct { // an object, depending on what was used during creation. Strings can be // used for basic plain text or markdown content and objects are used for // more complex types such as Slate.js editor documents. - Body PostContent `json:"body"` - Media MediaItemList `json:"media"` + Body PostContent `json:"body"` + Media AssetList `json:"media"` // Meta Arbitrary metadata for the resource. Meta *Metadata `json:"meta,omitempty"` @@ -449,8 +448,8 @@ type PostProps struct { DeletedAt *time.Time `json:"deletedAt,omitempty"` // Id A unique identifier for this resource. - Id Identifier `json:"id"` - Media MediaItemList `json:"media"` + Id Identifier `json:"id"` + Media AssetList `json:"media"` // Meta Arbitrary metadata for the resource. Meta *Metadata `json:"meta,omitempty"` @@ -660,8 +659,8 @@ type Thread struct { DeletedAt *time.Time `json:"deletedAt,omitempty"` // Id A unique identifier for this resource. - Id Identifier `json:"id"` - Media MediaItemList `json:"media"` + Id Identifier `json:"id"` + Media AssetList `json:"media"` // Meta Arbitrary metadata for the resource. Meta Metadata `json:"meta"` @@ -703,6 +702,8 @@ type Thread struct { // ThreadInitialProps defines model for ThreadInitialProps. type ThreadInitialProps struct { + Assets *AssetReferenceList `json:"assets,omitempty"` + // Body The body text of a post within a thread. The type is either a string or // an object, depending on what was used during creation. Strings can be // used for basic plain text or markdown content and objects are used for @@ -740,6 +741,8 @@ type ThreadMark = string // ThreadMutableProps defines model for ThreadMutableProps. type ThreadMutableProps struct { + Assets *AssetReferenceList `json:"assets,omitempty"` + // Body The body text of a post within a thread. The type is either a string or // an object, depending on what was used during creation. Strings can be // used for basic plain text or markdown content and objects are used for @@ -775,8 +778,8 @@ type ThreadReference struct { DeletedAt *time.Time `json:"deletedAt,omitempty"` // Id A unique identifier for this resource. - Id Identifier `json:"id"` - Media MediaItemList `json:"media"` + Id Identifier `json:"id"` + Media AssetList `json:"media"` // Meta Arbitrary metadata for the resource. Meta Metadata `json:"meta"` @@ -842,9 +845,6 @@ type OAuthProvider = string // PostIDParam A unique identifier for this resource. type PostIDParam = Identifier -// PostIDQueryParam A unique identifier for this resource. -type PostIDQueryParam = Identifier - // ThreadMarkParam A thread's ID and optional slug separated by a dash = it's unique mark. // This allows endpoints to respond to varying forms of a thread's ID. // @@ -861,9 +861,6 @@ type AccountGetOK = Account // AccountUpdateOK defines model for AccountUpdateOK. type AccountUpdateOK = Account -// AssetGetUploadURLOK defines model for AssetGetUploadURLOK. -type AssetGetUploadURLOK = AssetUploadURL - // AssetUploadOK defines model for AssetUploadOK. type AssetUploadOK = Asset @@ -960,12 +957,6 @@ type WebAuthnMakeAssertion = PublicKeyCredential // WebAuthnMakeCredential https://www.w3.org/TR/webauthn-2/#iface-pkcredential type WebAuthnMakeCredential = PublicKeyCredential -// AssetUploadParams defines parameters for AssetUpload. -type AssetUploadParams struct { - // PostId Unique post ID. - PostId PostIDQueryParam `form:"post_id" json:"post_id"` -} - // PostSearchParams defines parameters for PostSearch. type PostSearchParams struct { // Body A text query to search for in post content. @@ -1119,11 +1110,8 @@ type ClientInterface interface { // AccountGetAvatar request AccountGetAvatar(ctx context.Context, accountHandle AccountHandleParam, reqEditors ...RequestEditorFn) (*http.Response, error) - // AssetGetUploadURL request - AssetGetUploadURL(ctx context.Context, reqEditors ...RequestEditorFn) (*http.Response, error) - // AssetUpload request with any body - AssetUploadWithBody(ctx context.Context, params *AssetUploadParams, contentType string, body io.Reader, reqEditors ...RequestEditorFn) (*http.Response, error) + AssetUploadWithBody(ctx context.Context, contentType string, body io.Reader, reqEditors ...RequestEditorFn) (*http.Response, error) // AssetGet request AssetGet(ctx context.Context, id AssetPath, reqEditors ...RequestEditorFn) (*http.Response, error) @@ -1300,20 +1288,8 @@ func (c *Client) AccountGetAvatar(ctx context.Context, accountHandle AccountHand return c.Client.Do(req) } -func (c *Client) AssetGetUploadURL(ctx context.Context, reqEditors ...RequestEditorFn) (*http.Response, error) { - req, err := NewAssetGetUploadURLRequest(c.Server) - if err != nil { - return nil, err - } - req = req.WithContext(ctx) - if err := c.applyEditors(ctx, req, reqEditors); err != nil { - return nil, err - } - return c.Client.Do(req) -} - -func (c *Client) AssetUploadWithBody(ctx context.Context, params *AssetUploadParams, contentType string, body io.Reader, reqEditors ...RequestEditorFn) (*http.Response, error) { - req, err := NewAssetUploadRequestWithBody(c.Server, params, contentType, body) +func (c *Client) AssetUploadWithBody(ctx context.Context, contentType string, body io.Reader, reqEditors ...RequestEditorFn) (*http.Response, error) { + req, err := NewAssetUploadRequestWithBody(c.Server, contentType, body) if err != nil { return nil, err } @@ -1937,35 +1913,8 @@ func NewAccountGetAvatarRequest(server string, accountHandle AccountHandleParam) return req, nil } -// NewAssetGetUploadURLRequest generates requests for AssetGetUploadURL -func NewAssetGetUploadURLRequest(server string) (*http.Request, error) { - var err error - - serverURL, err := url.Parse(server) - if err != nil { - return nil, err - } - - operationPath := fmt.Sprintf("/v1/assets") - if operationPath[0] == '/' { - operationPath = "." + operationPath - } - - queryURL, err := serverURL.Parse(operationPath) - if err != nil { - return nil, err - } - - req, err := http.NewRequest("GET", queryURL.String(), nil) - if err != nil { - return nil, err - } - - return req, nil -} - // NewAssetUploadRequestWithBody generates requests for AssetUpload with any type of body -func NewAssetUploadRequestWithBody(server string, params *AssetUploadParams, contentType string, body io.Reader) (*http.Request, error) { +func NewAssetUploadRequestWithBody(server string, contentType string, body io.Reader) (*http.Request, error) { var err error serverURL, err := url.Parse(server) @@ -1983,22 +1932,6 @@ func NewAssetUploadRequestWithBody(server string, params *AssetUploadParams, con return nil, err } - queryValues := queryURL.Query() - - if queryFrag, err := runtime.StyleParamWithLocation("form", true, "post_id", runtime.ParamLocationQuery, params.PostId); err != nil { - return nil, err - } else if parsed, err := url.ParseQuery(queryFrag); err != nil { - return nil, err - } else { - for k, v := range parsed { - for _, v2 := range v { - queryValues.Add(k, v2) - } - } - } - - queryURL.RawQuery = queryValues.Encode() - req, err := http.NewRequest("POST", queryURL.String(), body) if err != nil { return nil, err @@ -3122,11 +3055,8 @@ type ClientWithResponsesInterface interface { // AccountGetAvatar request AccountGetAvatarWithResponse(ctx context.Context, accountHandle AccountHandleParam, reqEditors ...RequestEditorFn) (*AccountGetAvatarResponse, error) - // AssetGetUploadURL request - AssetGetUploadURLWithResponse(ctx context.Context, reqEditors ...RequestEditorFn) (*AssetGetUploadURLResponse, error) - // AssetUpload request with any body - AssetUploadWithBodyWithResponse(ctx context.Context, params *AssetUploadParams, contentType string, body io.Reader, reqEditors ...RequestEditorFn) (*AssetUploadResponse, error) + AssetUploadWithBodyWithResponse(ctx context.Context, contentType string, body io.Reader, reqEditors ...RequestEditorFn) (*AssetUploadResponse, error) // AssetGet request AssetGetWithResponse(ctx context.Context, id AssetPath, reqEditors ...RequestEditorFn) (*AssetGetResponse, error) @@ -3342,29 +3272,6 @@ func (r AccountGetAvatarResponse) StatusCode() int { return 0 } -type AssetGetUploadURLResponse struct { - Body []byte - HTTPResponse *http.Response - JSON200 *AssetUploadURL - JSONDefault *APIError -} - -// Status returns HTTPResponse.Status -func (r AssetGetUploadURLResponse) Status() string { - if r.HTTPResponse != nil { - return r.HTTPResponse.Status - } - return http.StatusText(0) -} - -// StatusCode returns HTTPResponse.StatusCode -func (r AssetGetUploadURLResponse) StatusCode() int { - if r.HTTPResponse != nil { - return r.HTTPResponse.StatusCode - } - return 0 -} - type AssetUploadResponse struct { Body []byte HTTPResponse *http.Response @@ -4037,18 +3944,9 @@ func (c *ClientWithResponses) AccountGetAvatarWithResponse(ctx context.Context, return ParseAccountGetAvatarResponse(rsp) } -// AssetGetUploadURLWithResponse request returning *AssetGetUploadURLResponse -func (c *ClientWithResponses) AssetGetUploadURLWithResponse(ctx context.Context, reqEditors ...RequestEditorFn) (*AssetGetUploadURLResponse, error) { - rsp, err := c.AssetGetUploadURL(ctx, reqEditors...) - if err != nil { - return nil, err - } - return ParseAssetGetUploadURLResponse(rsp) -} - // AssetUploadWithBodyWithResponse request with arbitrary body returning *AssetUploadResponse -func (c *ClientWithResponses) AssetUploadWithBodyWithResponse(ctx context.Context, params *AssetUploadParams, contentType string, body io.Reader, reqEditors ...RequestEditorFn) (*AssetUploadResponse, error) { - rsp, err := c.AssetUploadWithBody(ctx, params, contentType, body, reqEditors...) +func (c *ClientWithResponses) AssetUploadWithBodyWithResponse(ctx context.Context, contentType string, body io.Reader, reqEditors ...RequestEditorFn) (*AssetUploadResponse, error) { + rsp, err := c.AssetUploadWithBody(ctx, contentType, body, reqEditors...) if err != nil { return nil, err } @@ -4519,39 +4417,6 @@ func ParseAccountGetAvatarResponse(rsp *http.Response) (*AccountGetAvatarRespons return response, nil } -// ParseAssetGetUploadURLResponse parses an HTTP response from a AssetGetUploadURLWithResponse call -func ParseAssetGetUploadURLResponse(rsp *http.Response) (*AssetGetUploadURLResponse, error) { - bodyBytes, err := io.ReadAll(rsp.Body) - defer func() { _ = rsp.Body.Close() }() - if err != nil { - return nil, err - } - - response := &AssetGetUploadURLResponse{ - Body: bodyBytes, - HTTPResponse: rsp, - } - - switch { - case strings.Contains(rsp.Header.Get("Content-Type"), "json") && rsp.StatusCode == 200: - var dest AssetUploadURL - if err := json.Unmarshal(bodyBytes, &dest); err != nil { - return nil, err - } - response.JSON200 = &dest - - case strings.Contains(rsp.Header.Get("Content-Type"), "json") && true: - var dest APIError - if err := json.Unmarshal(bodyBytes, &dest); err != nil { - return nil, err - } - response.JSONDefault = &dest - - } - - return response, nil -} - // ParseAssetUploadResponse parses an HTTP response from a AssetUploadWithResponse call func ParseAssetUploadResponse(rsp *http.Response) (*AssetUploadResponse, error) { bodyBytes, err := io.ReadAll(rsp.Body) @@ -5420,11 +5285,8 @@ type ServerInterface interface { // (GET /v1/accounts/{account_handle}/avatar) AccountGetAvatar(ctx echo.Context, accountHandle AccountHandleParam) error - // (GET /v1/assets) - AssetGetUploadURL(ctx echo.Context) error - // (POST /v1/assets) - AssetUpload(ctx echo.Context, params AssetUploadParams) error + AssetUpload(ctx echo.Context) error // (GET /v1/assets/{id}) AssetGet(ctx echo.Context, id AssetPath) error @@ -5568,34 +5430,14 @@ func (w *ServerInterfaceWrapper) AccountGetAvatar(ctx echo.Context) error { return err } -// AssetGetUploadURL converts echo context to params. -func (w *ServerInterfaceWrapper) AssetGetUploadURL(ctx echo.Context) error { - var err error - - ctx.Set(BrowserScopes, []string{""}) - - // Invoke the callback with all the unmarshalled arguments - err = w.Handler.AssetGetUploadURL(ctx) - return err -} - // AssetUpload converts echo context to params. func (w *ServerInterfaceWrapper) AssetUpload(ctx echo.Context) error { var err error ctx.Set(BrowserScopes, []string{""}) - // Parameter object where we will unmarshal all parameters from the context - var params AssetUploadParams - // ------------- Required query parameter "post_id" ------------- - - err = runtime.BindQueryParameter("form", true, true, "post_id", ctx.QueryParams(), ¶ms.PostId) - if err != nil { - return echo.NewHTTPError(http.StatusBadRequest, fmt.Sprintf("Invalid format for parameter post_id: %s", err)) - } - // Invoke the callback with all the unmarshalled arguments - err = w.Handler.AssetUpload(ctx, params) + err = w.Handler.AssetUpload(ctx) return err } @@ -6023,7 +5865,6 @@ func RegisterHandlersWithBaseURL(router EchoRouter, si ServerInterface, baseURL router.PATCH(baseURL+"/v1/accounts", wrapper.AccountUpdate) router.POST(baseURL+"/v1/accounts/self/avatar", wrapper.AccountSetAvatar) router.GET(baseURL+"/v1/accounts/:account_handle/avatar", wrapper.AccountGetAvatar) - router.GET(baseURL+"/v1/assets", wrapper.AssetGetUploadURL) router.POST(baseURL+"/v1/assets", wrapper.AssetUpload) router.GET(baseURL+"/v1/assets/:id", wrapper.AssetGet) router.GET(baseURL+"/v1/auth", wrapper.AuthProviderList) @@ -6071,8 +5912,6 @@ type AssetGetOKAsteriskResponse struct { ContentLength int64 } -type AssetGetUploadURLOKJSONResponse AssetUploadURL - type AssetUploadOKJSONResponse Asset type AuthProviderListOKJSONResponse struct { @@ -6337,46 +6176,8 @@ func (response AccountGetAvatardefaultJSONResponse) VisitAccountGetAvatarRespons return json.NewEncoder(w).Encode(response.Body) } -type AssetGetUploadURLRequestObject struct { -} - -type AssetGetUploadURLResponseObject interface { - VisitAssetGetUploadURLResponse(w http.ResponseWriter) error -} - -type AssetGetUploadURL200JSONResponse struct { - AssetGetUploadURLOKJSONResponse -} - -func (response AssetGetUploadURL200JSONResponse) VisitAssetGetUploadURLResponse(w http.ResponseWriter) error { - w.Header().Set("Content-Type", "application/json") - w.WriteHeader(200) - - return json.NewEncoder(w).Encode(response) -} - -type AssetGetUploadURL401Response = UnauthorisedResponse - -func (response AssetGetUploadURL401Response) VisitAssetGetUploadURLResponse(w http.ResponseWriter) error { - w.WriteHeader(401) - return nil -} - -type AssetGetUploadURLdefaultJSONResponse struct { - Body APIError - StatusCode int -} - -func (response AssetGetUploadURLdefaultJSONResponse) VisitAssetGetUploadURLResponse(w http.ResponseWriter) error { - w.Header().Set("Content-Type", "application/json") - w.WriteHeader(response.StatusCode) - - return json.NewEncoder(w).Encode(response.Body) -} - type AssetUploadRequestObject struct { - Params AssetUploadParams - Body io.Reader + Body io.Reader } type AssetUploadResponseObject interface { @@ -7469,9 +7270,6 @@ type StrictServerInterface interface { // (GET /v1/accounts/{account_handle}/avatar) AccountGetAvatar(ctx context.Context, request AccountGetAvatarRequestObject) (AccountGetAvatarResponseObject, error) - // (GET /v1/assets) - AssetGetUploadURL(ctx context.Context, request AssetGetUploadURLRequestObject) (AssetGetUploadURLResponseObject, error) - // (POST /v1/assets) AssetUpload(ctx context.Context, request AssetUploadRequestObject) (AssetUploadResponseObject, error) @@ -7692,35 +7490,10 @@ func (sh *strictHandler) AccountGetAvatar(ctx echo.Context, accountHandle Accoun return nil } -// AssetGetUploadURL operation middleware -func (sh *strictHandler) AssetGetUploadURL(ctx echo.Context) error { - var request AssetGetUploadURLRequestObject - - handler := func(ctx echo.Context, request interface{}) (interface{}, error) { - return sh.ssi.AssetGetUploadURL(ctx.Request().Context(), request.(AssetGetUploadURLRequestObject)) - } - for _, middleware := range sh.middlewares { - handler = middleware(handler, "AssetGetUploadURL") - } - - response, err := handler(ctx, request) - - if err != nil { - return err - } else if validResponse, ok := response.(AssetGetUploadURLResponseObject); ok { - return validResponse.VisitAssetGetUploadURLResponse(ctx.Response()) - } else if response != nil { - return fmt.Errorf("Unexpected response type: %T", response) - } - return nil -} - // AssetUpload operation middleware -func (sh *strictHandler) AssetUpload(ctx echo.Context, params AssetUploadParams) error { +func (sh *strictHandler) AssetUpload(ctx echo.Context) error { var request AssetUploadRequestObject - request.Params = params - request.Body = ctx.Request().Body handler := func(ctx echo.Context, request interface{}) (interface{}, error) { @@ -8445,120 +8218,119 @@ func (sh *strictHandler) GetVersion(ctx echo.Context) error { // Base64 encoded, gzipped, json marshaled Swagger object var swaggerSpec = []string{ - "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", - "vaOeAAA=", + "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", + "u/v/AAAA///mzbJs0JwAAA==", } // GetSwagger returns the content of the embedded swagger specification file diff --git a/web/src/api/openapi/assets.ts b/web/src/api/openapi/assets.ts index 88d6b5784..f0428f754 100644 --- a/web/src/api/openapi/assets.ts +++ b/web/src/api/openapi/assets.ts @@ -12,78 +12,25 @@ import { fetcher } from "../client"; import type { AssetGetOKResponse, - AssetGetUploadURLOKResponse, AssetUploadBody, AssetUploadOKResponse, - AssetUploadParams, InternalServerErrorResponse, NotFoundResponse, UnauthorisedResponse, } from "./schemas"; -/** - * Get an upload URL for a new asset. - */ -export const assetGetUploadURL = () => { - return fetcher({ - url: `/v1/assets`, - method: "get", - }); -}; - -export const getAssetGetUploadURLKey = () => [`/v1/assets`] as const; - -export type AssetGetUploadURLQueryResult = NonNullable< - Awaited> ->; -export type AssetGetUploadURLQueryError = - | UnauthorisedResponse - | InternalServerErrorResponse; - -export const useAssetGetUploadURL = < - TError = UnauthorisedResponse | InternalServerErrorResponse ->(options?: { - swr?: SWRConfiguration< - Awaited>, - TError - > & { swrKey?: Key; enabled?: boolean }; -}) => { - const { swr: swrOptions } = options ?? {}; - - const isEnabled = swrOptions?.enabled !== false; - const swrKey = - swrOptions?.swrKey ?? - (() => (isEnabled ? getAssetGetUploadURLKey() : null)); - const swrFn = () => assetGetUploadURL(); - - const query = useSwr>, TError>( - swrKey, - swrFn, - swrOptions - ); - - return { - swrKey, - ...query, - }; -}; - /** * If Storyden is not using S3 for file uploads, this is the fallback equivalent of S3's pre-signed upload URL. Files uploaded to this endpoint will be stored on the local filesystem instead of the cloud. */ -export const assetUpload = ( - assetUploadBody: AssetUploadBody, - params: AssetUploadParams -) => { +export const assetUpload = (assetUploadBody: AssetUploadBody) => { return fetcher({ url: `/v1/assets`, method: "post", headers: { "Content-Type": "application/octet-stream" }, data: assetUploadBody, - params, }); }; diff --git a/web/src/api/openapi/schemas/asset.ts b/web/src/api/openapi/schemas/asset.ts index e5b9706b1..43367ca74 100644 --- a/web/src/api/openapi/schemas/asset.ts +++ b/web/src/api/openapi/schemas/asset.ts @@ -5,7 +5,12 @@ * Storyden social API for building community driven platforms. * OpenAPI spec version: 1 */ +import type { AssetID } from "./assetID"; export interface Asset { + id: AssetID; url: string; + mime_type: string; + width: number; + height: number; } diff --git a/web/src/api/openapi/schemas/assetGetUploadURLOKResponse.ts b/web/src/api/openapi/schemas/assetGetUploadURLOKResponse.ts deleted file mode 100644 index 9810eda9e..000000000 --- a/web/src/api/openapi/schemas/assetGetUploadURLOKResponse.ts +++ /dev/null @@ -1,13 +0,0 @@ -/** - * Generated by orval v6.15.0 🍺 - * Do not edit manually. - * storyden - * Storyden social API for building community driven platforms. - * OpenAPI spec version: 1 - */ -import type { AssetUploadURL } from "./assetUploadURL"; - -/** - * A pre-authorised upload URL. - */ -export type AssetGetUploadURLOKResponse = AssetUploadURL; diff --git a/web/src/api/openapi/schemas/postIDQueryParamParameter.ts b/web/src/api/openapi/schemas/assetID.ts similarity index 67% rename from web/src/api/openapi/schemas/postIDQueryParamParameter.ts rename to web/src/api/openapi/schemas/assetID.ts index 4e215d097..d4173de7a 100644 --- a/web/src/api/openapi/schemas/postIDQueryParamParameter.ts +++ b/web/src/api/openapi/schemas/assetID.ts @@ -5,8 +5,6 @@ * Storyden social API for building community driven platforms. * OpenAPI spec version: 1 */ +import type { Identifier } from "./identifier"; -/** - * Unique post ID. - */ -export type PostIDQueryParamParameter = string; +export type AssetID = Identifier; diff --git a/web/src/api/openapi/schemas/assetUploadURL.ts b/web/src/api/openapi/schemas/assetList.ts similarity index 70% rename from web/src/api/openapi/schemas/assetUploadURL.ts rename to web/src/api/openapi/schemas/assetList.ts index f19822a26..5a0018ec5 100644 --- a/web/src/api/openapi/schemas/assetUploadURL.ts +++ b/web/src/api/openapi/schemas/assetList.ts @@ -5,7 +5,6 @@ * Storyden social API for building community driven platforms. * OpenAPI spec version: 1 */ +import type { Asset } from "./asset"; -export interface AssetUploadURL { - url: string; -} +export type AssetList = Asset[]; diff --git a/web/src/api/openapi/schemas/mediaItemList.ts b/web/src/api/openapi/schemas/assetReferenceList.ts similarity index 66% rename from web/src/api/openapi/schemas/mediaItemList.ts rename to web/src/api/openapi/schemas/assetReferenceList.ts index 951d42d90..23a94ccbd 100644 --- a/web/src/api/openapi/schemas/mediaItemList.ts +++ b/web/src/api/openapi/schemas/assetReferenceList.ts @@ -5,6 +5,6 @@ * Storyden social API for building community driven platforms. * OpenAPI spec version: 1 */ -import type { MediaItem } from "./mediaItem"; +import type { AssetID } from "./assetID"; -export type MediaItemList = MediaItem[]; +export type AssetReferenceList = AssetID[]; diff --git a/web/src/api/openapi/schemas/assetUploadParams.ts b/web/src/api/openapi/schemas/assetUploadParams.ts deleted file mode 100644 index 38cf068f0..000000000 --- a/web/src/api/openapi/schemas/assetUploadParams.ts +++ /dev/null @@ -1,15 +0,0 @@ -/** - * Generated by orval v6.15.0 🍺 - * Do not edit manually. - * storyden - * Storyden social API for building community driven platforms. - * OpenAPI spec version: 1 - */ -import type { PostIDQueryParamParameter } from "./postIDQueryParamParameter"; - -export type AssetUploadParams = { - /** - * Unique post ID. - */ - post_id: PostIDQueryParamParameter; -}; diff --git a/web/src/api/openapi/schemas/index.ts b/web/src/api/openapi/schemas/index.ts index 0c30b3c4c..896a52310 100644 --- a/web/src/api/openapi/schemas/index.ts +++ b/web/src/api/openapi/schemas/index.ts @@ -20,11 +20,11 @@ export * from "./accountUpdateBody"; export * from "./accountUpdateOKResponse"; export * from "./asset"; export * from "./assetGetOKResponse"; -export * from "./assetGetUploadURLOKResponse"; +export * from "./assetID"; +export * from "./assetList"; +export * from "./assetReferenceList"; export * from "./assetUploadBody"; export * from "./assetUploadOKResponse"; -export * from "./assetUploadParams"; -export * from "./assetUploadURL"; export * from "./attestationConveyancePreference"; export * from "./authPair"; export * from "./authPasswordBody"; @@ -56,8 +56,6 @@ export * from "./getInfoOKResponse"; export * from "./identifier"; export * from "./info"; export * from "./internalServerErrorResponse"; -export * from "./mediaItem"; -export * from "./mediaItemList"; export * from "./metadata"; export * from "./notFoundResponse"; export * from "./oAuthCallback"; @@ -70,7 +68,6 @@ export * from "./postCommonProps"; export * from "./postContent"; export * from "./postCreateBody"; export * from "./postCreateOKResponse"; -export * from "./postIDQueryParamParameter"; export * from "./postInitialProps"; export * from "./postMetadata"; export * from "./postMutableProps"; diff --git a/web/src/api/openapi/schemas/mediaItem.ts b/web/src/api/openapi/schemas/mediaItem.ts deleted file mode 100644 index 8a29dd431..000000000 --- a/web/src/api/openapi/schemas/mediaItem.ts +++ /dev/null @@ -1,14 +0,0 @@ -/** - * Generated by orval v6.15.0 🍺 - * Do not edit manually. - * storyden - * Storyden social API for building community driven platforms. - * OpenAPI spec version: 1 - */ - -export interface MediaItem { - url: string; - mime_type: string; - width: number; - height: number; -} diff --git a/web/src/api/openapi/schemas/postCommonProps.ts b/web/src/api/openapi/schemas/postCommonProps.ts index 04c933e60..60d0b59c8 100644 --- a/web/src/api/openapi/schemas/postCommonProps.ts +++ b/web/src/api/openapi/schemas/postCommonProps.ts @@ -5,8 +5,8 @@ * Storyden social API for building community driven platforms. * OpenAPI spec version: 1 */ +import type { AssetList } from "./assetList"; import type { Identifier } from "./identifier"; -import type { MediaItemList } from "./mediaItemList"; import type { Metadata } from "./metadata"; import type { PostContent } from "./postContent"; import type { ProfileReference } from "./profileReference"; @@ -21,5 +21,5 @@ export interface PostCommonProps { meta?: Metadata; reacts: ReactList; reply_to?: Identifier; - media: MediaItemList; + media: AssetList; } diff --git a/web/src/api/openapi/schemas/threadInitialProps.ts b/web/src/api/openapi/schemas/threadInitialProps.ts index f2e55cc59..633c60785 100644 --- a/web/src/api/openapi/schemas/threadInitialProps.ts +++ b/web/src/api/openapi/schemas/threadInitialProps.ts @@ -5,6 +5,7 @@ * Storyden social API for building community driven platforms. * OpenAPI spec version: 1 */ +import type { AssetReferenceList } from "./assetReferenceList"; import type { Identifier } from "./identifier"; import type { Metadata } from "./metadata"; import type { PostContent } from "./postContent"; @@ -19,4 +20,5 @@ export interface ThreadInitialProps { meta?: Metadata; category: Identifier; status: ThreadStatus; + assets?: AssetReferenceList; } diff --git a/web/src/api/openapi/schemas/threadMutableProps.ts b/web/src/api/openapi/schemas/threadMutableProps.ts index d9a1eb01d..53be2af9d 100644 --- a/web/src/api/openapi/schemas/threadMutableProps.ts +++ b/web/src/api/openapi/schemas/threadMutableProps.ts @@ -5,6 +5,7 @@ * Storyden social API for building community driven platforms. * OpenAPI spec version: 1 */ +import type { AssetReferenceList } from "./assetReferenceList"; import type { Identifier } from "./identifier"; import type { Metadata } from "./metadata"; import type { PostContent } from "./postContent"; @@ -19,4 +20,5 @@ export interface ThreadMutableProps { meta?: Metadata; category?: Identifier; status?: ThreadStatus; + assets?: AssetReferenceList; } diff --git a/web/src/api/openapi/schemas/threadReferenceAllOf.ts b/web/src/api/openapi/schemas/threadReferenceAllOf.ts index fa2b5bc3c..724265e53 100644 --- a/web/src/api/openapi/schemas/threadReferenceAllOf.ts +++ b/web/src/api/openapi/schemas/threadReferenceAllOf.ts @@ -5,8 +5,8 @@ * Storyden social API for building community driven platforms. * OpenAPI spec version: 1 */ +import type { AssetList } from "./assetList"; import type { CategoryReference } from "./categoryReference"; -import type { MediaItemList } from "./mediaItemList"; import type { Metadata } from "./metadata"; import type { ProfileReference } from "./profileReference"; import type { ReactList } from "./reactList"; @@ -29,5 +29,5 @@ export type ThreadReferenceAllOf = { category: CategoryReference; reacts: ReactList; meta: Metadata; - media: MediaItemList; + media: AssetList; }; diff --git a/web/src/components/ContentComposer/ContentComposer.tsx b/web/src/components/ContentComposer/ContentComposer.tsx index a5215ec0e..4ec2c6442 100644 --- a/web/src/components/ContentComposer/ContentComposer.tsx +++ b/web/src/components/ContentComposer/ContentComposer.tsx @@ -23,7 +23,7 @@ export function ContentComposer({ {children} - + ) { + const { onDragStart, onDragEnd, onDrop, dragging } = useFileDrop(props); return ( void; +}; + +export function useFileDrop(props: Props) { const [dragging, setDragging] = useState(false); const editor = useSlate(); const toast = useToast(); @@ -23,23 +27,16 @@ export function useFileDrop() { } async function upload(f: File) { - const { url } = await assetGetUploadURL(); - // TODO: Upload progress indicator... - const response = await fetch(url, { - credentials: "include", - method: "POST", - headers: { "Content-Type": "application/octet-stream" }, - body: f, - }); + const asset = await assetUpload(f); - const json = (await response.json()) as AssetGetUploadURLOKResponse; + props.onComplete(asset); - return json.url; + return asset; } async function process(f: File) { - const url = await upload(f); + const { url } = await upload(f); if (!isSupportedImage(f.type)) { throw new Error("Unsupported image format"); diff --git a/web/src/components/ContentComposer/useContentComposer.ts b/web/src/components/ContentComposer/useContentComposer.ts index ee2debcd6..fb9d9a92d 100644 --- a/web/src/components/ContentComposer/useContentComposer.ts +++ b/web/src/components/ContentComposer/useContentComposer.ts @@ -8,6 +8,8 @@ import { } from "slate"; import { ReactEditor, withReact } from "slate-react"; +import { Asset } from "src/api/openapi/schemas"; + import { deserialise, serialise } from "./serialisation"; export type Props = { @@ -16,6 +18,7 @@ export type Props = { minHeight?: string; initialValue?: string; onChange: (value: string) => void; + onAssetUpload: (asset: Asset) => void; }; const defaultValue: Descendant[] = [ diff --git a/web/src/screens/compose/components/BodyInput/BodyInput.tsx b/web/src/screens/compose/components/BodyInput/BodyInput.tsx index 773603bc0..fa5b204d2 100644 --- a/web/src/screens/compose/components/BodyInput/BodyInput.tsx +++ b/web/src/screens/compose/components/BodyInput/BodyInput.tsx @@ -2,11 +2,19 @@ import { FormControl } from "@chakra-ui/react"; import { PropsWithChildren } from "react"; import { Controller } from "react-hook-form"; +import { Asset } from "src/api/openapi/schemas"; import { ContentComposer } from "src/components/ContentComposer/ContentComposer"; import { useBodyInput } from "./useBodyInput"; -export function BodyInput({ children }: PropsWithChildren) { +type Props = { + onAssetUpload: (asset: Asset) => void; +}; + +export function BodyInput({ + children, + onAssetUpload, +}: PropsWithChildren) { const { control } = useBodyInput(); return ( @@ -15,7 +23,8 @@ export function BodyInput({ children }: PropsWithChildren) { render={({ field, formState }) => ( diff --git a/web/src/screens/compose/components/ComposeForm/ComposeForm.tsx b/web/src/screens/compose/components/ComposeForm/ComposeForm.tsx index ee7885ee4..255fae3c7 100644 --- a/web/src/screens/compose/components/ComposeForm/ComposeForm.tsx +++ b/web/src/screens/compose/components/ComposeForm/ComposeForm.tsx @@ -14,7 +14,8 @@ import { TitleInput } from "../TitleInput/TitleInput"; import { Props, useComposeForm } from "./useComposeForm"; export function ComposeForm(props: Props) { - const { formContext, onBack, onPublish, onSave } = useComposeForm(props); + const { formContext, onBack, onPublish, onSave, onAssetUpload } = + useComposeForm(props); return ( @@ -54,7 +55,7 @@ export function ComposeForm(props: Props) { - + diff --git a/web/src/screens/compose/components/ComposeForm/useComposeForm.ts b/web/src/screens/compose/components/ComposeForm/useComposeForm.ts index 3d73f6491..676fa38ab 100644 --- a/web/src/screens/compose/components/ComposeForm/useComposeForm.ts +++ b/web/src/screens/compose/components/ComposeForm/useComposeForm.ts @@ -6,6 +6,7 @@ import { z } from "zod"; import { useCategoryList } from "src/api/openapi/categories"; import { + Asset, Thread, ThreadCreateOKResponse, ThreadStatus, @@ -15,20 +16,21 @@ import { errorToast } from "src/components/ErrorBanner"; export type Props = { editing?: string; draft?: Thread }; -export const ThreadCreateSchema = z.object({ +export const ThreadMutationSchema = z.object({ title: z.string().min(1), body: z.string().min(1), category: z.string(), tags: z.string().array().optional(), + assets: z.array(z.string()), }); -export type ThreadCreate = z.infer; +export type ThreadMutation = z.infer; export function useComposeForm({ draft, editing }: Props) { const router = useRouter(); const toast = useToast(); const { data } = useCategoryList(); - const formContext = useForm({ - resolver: zodResolver(ThreadCreateSchema), + const formContext = useForm({ + resolver: zodResolver(ThreadMutationSchema), reValidateMode: "onChange", defaultValues: draft ? { @@ -46,13 +48,14 @@ export function useComposeForm({ draft, editing }: Props) { router.back(); } - const onSave = formContext.handleSubmit(async (props: ThreadCreate) => { + const doSave = async (props: ThreadMutation) => { const payload = { title: props.title, category: props.category, body: props.body, tags: [], status: ThreadStatus.draft, + assets: props.assets, }; if (editing) { @@ -64,14 +67,23 @@ export function useComposeForm({ draft, editing }: Props) { .then((thread: ThreadCreateOKResponse) => thread.id) .catch(errorToast(toast)); - if (!id) return; - router.push(`/new?id=${id}`); } - }); + }; + + const onAssetUpload = async (asset: Asset) => { + const state: ThreadMutation = formContext.getValues(); + + return await doSave({ + ...state, + assets: [...state.assets, asset.id], + }); + }; + + const onSave = formContext.handleSubmit(doSave); const onPublish = formContext.handleSubmit( - async ({ title, body, category }: ThreadCreate) => { + async ({ title, body, category }: ThreadMutation) => { if (editing) { threadUpdate(editing, { status: ThreadStatus.published }) .then((thread: ThreadCreateOKResponse) => @@ -98,6 +110,7 @@ export function useComposeForm({ draft, editing }: Props) { onBack, onSave, onPublish, + onAssetUpload, formContext, }; } diff --git a/web/src/screens/thread/components/PostView/PostView.tsx b/web/src/screens/thread/components/PostView/PostView.tsx index 91f200545..81f5d0ef4 100644 --- a/web/src/screens/thread/components/PostView/PostView.tsx +++ b/web/src/screens/thread/components/PostView/PostView.tsx @@ -21,7 +21,7 @@ export function PostView(props: Props) { onContentChange, onPublishEdit, onCancelEdit, - } = usePostView(props); + } = usePostView(props); return ( From f0add1041c524ffd93e0a920e494e2cec8376b4c Mon Sep 17 00:00:00 2001 From: Barnaby Keene Date: Tue, 18 Jul 2023 08:53:20 +0100 Subject: [PATCH 3/5] editing image upload working --- api/openapi.yaml | 8 +- app/resources/asset/db.go | 12 + app/resources/asset/repo.go | 2 + app/resources/thread/repo.go | 8 + app/services/asset/service.go | 20 +- app/services/thread/service.go | 2 + app/services/thread/update.go | 2 + app/transports/openapi/bindings/assets.go | 6 +- app/transports/openapi/bindings/threads.go | 10 +- app/transports/openapi/bindings/utils.go | 8 +- internal/ent/asset.go | 38 +-- internal/ent/asset/asset.go | 23 +- internal/ent/asset/where.go | 99 +------ internal/ent/asset_create.go | 99 ++----- internal/ent/asset_query.go | 92 ++++--- internal/ent/asset_update.go | 178 ++++++++----- internal/ent/client.go | 8 +- internal/ent/migrate/schema.go | 36 ++- internal/ent/mutation.go | 182 ++++++------- internal/ent/post/post.go | 9 +- internal/ent/post/where.go | 4 +- internal/ent/post_create.go | 4 +- internal/ent/post_query.go | 64 +++-- internal/ent/post_update.go | 24 +- internal/ent/schema/asset.go | 8 +- internal/openapi/generated.go | 246 +++++++++--------- web/src/api/client.ts | 14 +- .../api/openapi/schemas/postCommonProps.ts | 2 +- .../openapi/schemas/threadReferenceAllOf.ts | 2 +- .../ContentComposer/useContentComposer.ts | 4 - web/src/screens/compose/ComposeScreen.tsx | 2 +- .../components/BodyInput/useBodyInput.ts | 4 +- .../CategorySelect/useCategorySelect.ts | 4 +- .../components/ComposeForm/useComposeForm.ts | 115 ++++---- .../components/TitleInput/TitleInput.tsx | 4 +- .../components/TitleInput/useTitleInput.ts | 6 +- 36 files changed, 662 insertions(+), 687 deletions(-) diff --git a/api/openapi.yaml b/api/openapi.yaml index 8f5f08474..6671c39fd 100644 --- a/api/openapi.yaml +++ b/api/openapi.yaml @@ -1622,7 +1622,7 @@ components: - category - reacts - meta - - media + - assets properties: title: type: string @@ -1651,7 +1651,7 @@ components: reacts: $ref: "#/components/schemas/ReactList" meta: { $ref: "#/components/schemas/Metadata" } - media: { $ref: "#/components/schemas/AssetList" } + assets: { $ref: "#/components/schemas/AssetList" } ThreadList: type: array @@ -1697,7 +1697,7 @@ components: PostCommonProps: type: object - required: [root_id, root_slug, body, author, reacts, media] + required: [root_id, root_slug, body, author, reacts, assets] properties: root_id: { $ref: "#/components/schemas/Identifier" } root_slug: { $ref: "#/components/schemas/ThreadMark" } @@ -1706,7 +1706,7 @@ components: meta: { $ref: "#/components/schemas/Metadata" } reacts: { $ref: "#/components/schemas/ReactList" } reply_to: { $ref: "#/components/schemas/Identifier" } - media: { $ref: "#/components/schemas/AssetList" } + assets: { $ref: "#/components/schemas/AssetList" } PostInitialProps: type: object diff --git a/app/resources/asset/db.go b/app/resources/asset/db.go index 796b11ff0..e1c654546 100644 --- a/app/resources/asset/db.go +++ b/app/resources/asset/db.go @@ -48,6 +48,18 @@ func (d *database) Add(ctx context.Context, return FromModel(asset), nil } +func (d *database) Get(ctx context.Context, id string) (*Asset, error) { + asset, err := d.db.Asset.Get(ctx, id) + if err != nil { + if ent.IsNotFound(err) { + return nil, fault.Wrap(err, fctx.With(ctx), ftag.With(ftag.NotFound)) + } + return nil, fault.Wrap(err, fctx.With(ctx)) + } + + return FromModel(asset), nil +} + func (d *database) Remove(ctx context.Context, accountID account.AccountID, id AssetID) error { q := d.db.Asset. DeleteOneID(string(id)) diff --git a/app/resources/asset/repo.go b/app/resources/asset/repo.go index 998e52f8d..5c82e1f21 100644 --- a/app/resources/asset/repo.go +++ b/app/resources/asset/repo.go @@ -13,5 +13,7 @@ type Repository interface { width, height int, ) (*Asset, error) + Get(ctx context.Context, id string) (*Asset, error) + Remove(ctx context.Context, owner account.AccountID, id AssetID) error } diff --git a/app/resources/thread/repo.go b/app/resources/thread/repo.go index f7f835ebb..f181640ac 100644 --- a/app/resources/thread/repo.go +++ b/app/resources/thread/repo.go @@ -4,9 +4,11 @@ import ( "context" "time" + "github.com/Southclaws/dt" "github.com/rs/xid" account_resource "github.com/Southclaws/storyden/app/resources/account" + "github.com/Southclaws/storyden/app/resources/asset" category_resource "github.com/Southclaws/storyden/app/resources/category" post_resource "github.com/Southclaws/storyden/app/resources/post" "github.com/Southclaws/storyden/internal/ent" @@ -95,6 +97,12 @@ func WithMeta(meta map[string]any) Option { } } +func WithAssets(a []asset.AssetID) Option { + return func(m *ent.PostMutation) { + m.AddAssetIDs(dt.Map(a, func(id asset.AssetID) string { return string(id) })...) + } +} + type Query func(q *ent.PostQuery) func HasAuthor(id account_resource.AccountID) Query { diff --git a/app/services/asset/service.go b/app/services/asset/service.go index ed743b1f7..b4e59362f 100644 --- a/app/services/asset/service.go +++ b/app/services/asset/service.go @@ -30,7 +30,7 @@ const assetsSubdirectory = "assets" type Service interface { Upload(ctx context.Context, r io.Reader) (*asset.Asset, error) - Read(ctx context.Context, path string) (io.Reader, error) + Get(ctx context.Context, path string) (*asset.Asset, io.Reader, error) } func Build() fx.Option { @@ -95,14 +95,13 @@ func (s *service) Upload(ctx context.Context, r io.Reader) (*asset.Asset, error) hash := sha1.Sum(buf) assetID := hex.EncodeToString(hash[:]) - slug := fmt.Sprintf("%s-%s", assetID, accountID.String()) - filePath := filepath.Join(assetsSubdirectory, slug) + filePath := filepath.Join(assetsSubdirectory, assetID) if err := s.os.Write(ctx, filePath, r); err != nil { return nil, fault.Wrap(err, fctx.With(ctx)) } - apiPath := path.Join("api/v1/assets", slug) + apiPath := path.Join("api/v1/assets", assetID) url := fmt.Sprintf("%s/%s", s.address, apiPath) mime := mt.String() @@ -119,12 +118,19 @@ func (s *service) Upload(ctx context.Context, r io.Reader) (*asset.Asset, error) return ast, nil } -func (s *service) Read(ctx context.Context, assetID string) (io.Reader, error) { +func (s *service) Get(ctx context.Context, assetID string) (*asset.Asset, io.Reader, error) { + a, err := s.asset_repo.Get(ctx, assetID) + if err != nil { + return nil, nil, fault.Wrap(err, fctx.With(ctx)) + } + path := filepath.Join(assetsSubdirectory, assetID) + ctx = fctx.WithMeta(ctx, "path", path, "asset_id", assetID) + r, err := s.os.Read(ctx, path) if err != nil { - return nil, fault.Wrap(err, fctx.With(ctx)) + return nil, nil, fault.Wrap(err, fctx.With(ctx)) } - return r, nil + return a, r, nil } diff --git a/app/services/thread/service.go b/app/services/thread/service.go index ab3d7d145..cb12f6683 100644 --- a/app/services/thread/service.go +++ b/app/services/thread/service.go @@ -12,6 +12,7 @@ import ( "go.uber.org/zap" "github.com/Southclaws/storyden/app/resources/account" + "github.com/Southclaws/storyden/app/resources/asset" "github.com/Southclaws/storyden/app/resources/category" "github.com/Southclaws/storyden/app/resources/post" "github.com/Southclaws/storyden/app/resources/rbac" @@ -58,6 +59,7 @@ type Partial struct { Category opt.Optional[xid.ID] Status opt.Optional[thread.Status] Meta opt.Optional[map[string]any] + Assets opt.Optional[[]asset.AssetID] } func Build() fx.Option { diff --git a/app/services/thread/update.go b/app/services/thread/update.go index 86e4a25be..8d7492a1d 100644 --- a/app/services/thread/update.go +++ b/app/services/thread/update.go @@ -9,6 +9,7 @@ import ( "github.com/el-mike/restrict" "github.com/rs/xid" + "github.com/Southclaws/storyden/app/resources/asset" "github.com/Southclaws/storyden/app/resources/post" "github.com/Southclaws/storyden/app/resources/rbac" "github.com/Southclaws/storyden/app/resources/thread" @@ -47,6 +48,7 @@ func (s *service) Update(ctx context.Context, threadID post.PostID, partial Part partial.Category.Call(func(v xid.ID) { opts = append(opts, thread.WithCategory(xid.ID(v))) }) partial.Status.Call(func(v thread.Status) { opts = append(opts, thread.WithStatus(v)) }) partial.Meta.Call(func(v map[string]any) { opts = append(opts, thread.WithMeta(v)) }) + partial.Assets.Call(func(v []asset.AssetID) { opts = append(opts, thread.WithAssets(v)) }) thr, err = s.thread_repo.Update(ctx, threadID, opts...) if err != nil { diff --git a/app/transports/openapi/bindings/assets.go b/app/transports/openapi/bindings/assets.go index d87d8f7b3..d653a2ba1 100644 --- a/app/transports/openapi/bindings/assets.go +++ b/app/transports/openapi/bindings/assets.go @@ -19,14 +19,16 @@ func NewAssets(a asset.Service) Assets { } func (i *Assets) AssetGet(ctx context.Context, request openapi.AssetGetRequestObject) (openapi.AssetGetResponseObject, error) { - r, err := i.a.Read(ctx, request.Id) + a, r, err := i.a.Get(ctx, request.Id) if err != nil { return nil, fault.Wrap(err, fctx.With(ctx)) } return openapi.AssetGet200AsteriskResponse{ AssetGetOKAsteriskResponse: openapi.AssetGetOKAsteriskResponse{ - Body: r, + Body: r, + ContentType: a.MIMEType, + // ContentLength: , }, }, nil } diff --git a/app/transports/openapi/bindings/threads.go b/app/transports/openapi/bindings/threads.go index 759b125d9..2a4fae532 100644 --- a/app/transports/openapi/bindings/threads.go +++ b/app/transports/openapi/bindings/threads.go @@ -51,7 +51,11 @@ func (i *Threads) ThreadCreate(ctx context.Context, request openapi.ThreadCreate meta = *request.Body.Meta } - request.Body.Assets + opts := []thread.Option{} + + if request.Body.Assets != nil { + opts = append(opts, thread.WithAssets(deserialiseAssetIDs(*request.Body.Assets))) + } tags := opt.NewPtr(request.Body.Tags) @@ -63,6 +67,7 @@ func (i *Threads) ThreadCreate(ctx context.Context, request openapi.ThreadCreate status, tags.OrZero(), meta, + opts..., ) if err != nil { return nil, fault.Wrap(err, fctx.With(ctx)) @@ -89,14 +94,13 @@ func (i *Threads) ThreadUpdate(ctx context.Context, request openapi.ThreadUpdate return nil, fault.Wrap(err, fctx.With(ctx)) } - request.Body.Assets - thread, err := i.thread_svc.Update(ctx, postID, thread_service.Partial{ Title: opt.NewPtr(request.Body.Title), Body: opt.NewPtr(request.Body.Body), Tags: opt.NewPtrMap(request.Body.Tags, tagsIDs), Category: opt.NewPtrMap(request.Body.Category, deserialiseID), Status: status, + Assets: opt.NewPtrMap(request.Body.Assets, deserialiseAssetIDs), }) if err != nil { return nil, fault.Wrap(err, fctx.With(ctx)) diff --git a/app/transports/openapi/bindings/utils.go b/app/transports/openapi/bindings/utils.go index 197aa8c36..b16c16093 100644 --- a/app/transports/openapi/bindings/utils.go +++ b/app/transports/openapi/bindings/utils.go @@ -70,7 +70,7 @@ func serialiseThread(t *thread.Thread) (*openapi.Thread, error) { Posts: posts, Title: t.Title, UpdatedAt: t.UpdatedAt, - Media: dt.Map(t.Assets, serialiseAssetReference), + Assets: dt.Map(t.Assets, serialiseAssetReference), }, nil } @@ -86,7 +86,7 @@ func serialisePost(p *post.Post) (openapi.PostProps, error) { Author: serialiseProfileReference(p.Author), Reacts: dt.Map(p.Reacts, serialiseReact), Meta: (*openapi.Metadata)(&p.Meta), - Media: dt.Map(p.Assets, serialiseAssetReference), + Assets: dt.Map(p.Assets, serialiseAssetReference), }, nil } @@ -145,6 +145,10 @@ func serialiseTag(t tag.Tag) openapi.Tag { } } +func deserialiseAssetIDs(ids openapi.AssetReferenceList) []asset.AssetID { + return dt.Map(ids, func(i string) asset.AssetID { return asset.AssetID(i) }) +} + func deserialiseID(t openapi.Identifier) xid.ID { return openapi.ParseID(t) } diff --git a/internal/ent/asset.go b/internal/ent/asset.go index d94372f57..221f6b998 100644 --- a/internal/ent/asset.go +++ b/internal/ent/asset.go @@ -10,7 +10,6 @@ import ( "entgo.io/ent/dialect/sql" "github.com/Southclaws/storyden/internal/ent/account" "github.com/Southclaws/storyden/internal/ent/asset" - "github.com/Southclaws/storyden/internal/ent/post" "github.com/rs/xid" ) @@ -31,8 +30,6 @@ type Asset struct { Width int `json:"width,omitempty"` // Height holds the value of the "height" field. Height int `json:"height,omitempty"` - // PostID holds the value of the "post_id" field. - PostID xid.ID `json:"post_id,omitempty"` // AccountID holds the value of the "account_id" field. AccountID xid.ID `json:"account_id,omitempty"` // Edges holds the relations/edges for other nodes in the graph. @@ -42,8 +39,8 @@ type Asset struct { // AssetEdges holds the relations/edges for other nodes in the graph. type AssetEdges struct { - // Post holds the value of the post edge. - Post *Post `json:"post,omitempty"` + // Posts holds the value of the posts edge. + Posts []*Post `json:"posts,omitempty"` // Owner holds the value of the owner edge. Owner *Account `json:"owner,omitempty"` // loadedTypes holds the information for reporting if a @@ -51,17 +48,13 @@ type AssetEdges struct { loadedTypes [2]bool } -// PostOrErr returns the Post value or an error if the edge -// was not loaded in eager-loading, or loaded but was not found. -func (e AssetEdges) PostOrErr() (*Post, error) { +// PostsOrErr returns the Posts value or an error if the edge +// was not loaded in eager-loading. +func (e AssetEdges) PostsOrErr() ([]*Post, error) { if e.loadedTypes[0] { - if e.Post == nil { - // Edge was loaded but was not found. - return nil, &NotFoundError{label: post.Label} - } - return e.Post, nil + return e.Posts, nil } - return nil, &NotLoadedError{edge: "post"} + return nil, &NotLoadedError{edge: "posts"} } // OwnerOrErr returns the Owner value or an error if the edge @@ -88,7 +81,7 @@ func (*Asset) scanValues(columns []string) ([]any, error) { values[i] = new(sql.NullString) case asset.FieldCreatedAt, asset.FieldUpdatedAt: values[i] = new(sql.NullTime) - case asset.FieldPostID, asset.FieldAccountID: + case asset.FieldAccountID: values[i] = new(xid.ID) default: return nil, fmt.Errorf("unexpected column %q for type Asset", columns[i]) @@ -147,12 +140,6 @@ func (a *Asset) assignValues(columns []string, values []any) error { } else if value.Valid { a.Height = int(value.Int64) } - case asset.FieldPostID: - if value, ok := values[i].(*xid.ID); !ok { - return fmt.Errorf("unexpected type %T for field post_id", values[i]) - } else if value != nil { - a.PostID = *value - } case asset.FieldAccountID: if value, ok := values[i].(*xid.ID); !ok { return fmt.Errorf("unexpected type %T for field account_id", values[i]) @@ -164,9 +151,9 @@ func (a *Asset) assignValues(columns []string, values []any) error { return nil } -// QueryPost queries the "post" edge of the Asset entity. -func (a *Asset) QueryPost() *PostQuery { - return NewAssetClient(a.config).QueryPost(a) +// QueryPosts queries the "posts" edge of the Asset entity. +func (a *Asset) QueryPosts() *PostQuery { + return NewAssetClient(a.config).QueryPosts(a) } // QueryOwner queries the "owner" edge of the Asset entity. @@ -215,9 +202,6 @@ func (a *Asset) String() string { builder.WriteString("height=") builder.WriteString(fmt.Sprintf("%v", a.Height)) builder.WriteString(", ") - builder.WriteString("post_id=") - builder.WriteString(fmt.Sprintf("%v", a.PostID)) - builder.WriteString(", ") builder.WriteString("account_id=") builder.WriteString(fmt.Sprintf("%v", a.AccountID)) builder.WriteByte(')') diff --git a/internal/ent/asset/asset.go b/internal/ent/asset/asset.go index 3ff72d9bb..28686b514 100644 --- a/internal/ent/asset/asset.go +++ b/internal/ent/asset/asset.go @@ -23,23 +23,19 @@ const ( FieldWidth = "width" // FieldHeight holds the string denoting the height field in the database. FieldHeight = "height" - // FieldPostID holds the string denoting the post_id field in the database. - FieldPostID = "post_id" // FieldAccountID holds the string denoting the account_id field in the database. FieldAccountID = "account_id" - // EdgePost holds the string denoting the post edge name in mutations. - EdgePost = "post" + // EdgePosts holds the string denoting the posts edge name in mutations. + EdgePosts = "posts" // EdgeOwner holds the string denoting the owner edge name in mutations. EdgeOwner = "owner" // Table holds the table name of the asset in the database. Table = "assets" - // PostTable is the table that holds the post relation/edge. - PostTable = "assets" - // PostInverseTable is the table name for the Post entity. + // PostsTable is the table that holds the posts relation/edge. The primary key declared below. + PostsTable = "post_assets" + // PostsInverseTable is the table name for the Post entity. // It exists in this package in order to avoid circular dependency with the "post" package. - PostInverseTable = "posts" - // PostColumn is the table column denoting the post relation/edge. - PostColumn = "post_id" + PostsInverseTable = "posts" // OwnerTable is the table that holds the owner relation/edge. OwnerTable = "assets" // OwnerInverseTable is the table name for the Account entity. @@ -58,10 +54,15 @@ var Columns = []string{ FieldMimetype, FieldWidth, FieldHeight, - FieldPostID, FieldAccountID, } +var ( + // PostsPrimaryKey and PostsColumn2 are the table columns denoting the + // primary key for the posts relation (M2M). + PostsPrimaryKey = []string{"post_id", "asset_id"} +) + // ValidColumn reports if the column name is valid (part of the table columns). func ValidColumn(column string) bool { for i := range Columns { diff --git a/internal/ent/asset/where.go b/internal/ent/asset/where.go index 38af181fb..496fbe12f 100644 --- a/internal/ent/asset/where.go +++ b/internal/ent/asset/where.go @@ -96,11 +96,6 @@ func Height(v int) predicate.Asset { return predicate.Asset(sql.FieldEQ(FieldHeight, v)) } -// PostID applies equality check predicate on the "post_id" field. It's identical to PostIDEQ. -func PostID(v xid.ID) predicate.Asset { - return predicate.Asset(sql.FieldEQ(FieldPostID, v)) -} - // AccountID applies equality check predicate on the "account_id" field. It's identical to AccountIDEQ. func AccountID(v xid.ID) predicate.Asset { return predicate.Asset(sql.FieldEQ(FieldAccountID, v)) @@ -396,86 +391,6 @@ func HeightLTE(v int) predicate.Asset { return predicate.Asset(sql.FieldLTE(FieldHeight, v)) } -// PostIDEQ applies the EQ predicate on the "post_id" field. -func PostIDEQ(v xid.ID) predicate.Asset { - return predicate.Asset(sql.FieldEQ(FieldPostID, v)) -} - -// PostIDNEQ applies the NEQ predicate on the "post_id" field. -func PostIDNEQ(v xid.ID) predicate.Asset { - return predicate.Asset(sql.FieldNEQ(FieldPostID, v)) -} - -// PostIDIn applies the In predicate on the "post_id" field. -func PostIDIn(vs ...xid.ID) predicate.Asset { - return predicate.Asset(sql.FieldIn(FieldPostID, vs...)) -} - -// PostIDNotIn applies the NotIn predicate on the "post_id" field. -func PostIDNotIn(vs ...xid.ID) predicate.Asset { - return predicate.Asset(sql.FieldNotIn(FieldPostID, vs...)) -} - -// PostIDGT applies the GT predicate on the "post_id" field. -func PostIDGT(v xid.ID) predicate.Asset { - return predicate.Asset(sql.FieldGT(FieldPostID, v)) -} - -// PostIDGTE applies the GTE predicate on the "post_id" field. -func PostIDGTE(v xid.ID) predicate.Asset { - return predicate.Asset(sql.FieldGTE(FieldPostID, v)) -} - -// PostIDLT applies the LT predicate on the "post_id" field. -func PostIDLT(v xid.ID) predicate.Asset { - return predicate.Asset(sql.FieldLT(FieldPostID, v)) -} - -// PostIDLTE applies the LTE predicate on the "post_id" field. -func PostIDLTE(v xid.ID) predicate.Asset { - return predicate.Asset(sql.FieldLTE(FieldPostID, v)) -} - -// PostIDContains applies the Contains predicate on the "post_id" field. -func PostIDContains(v xid.ID) predicate.Asset { - vc := v.String() - return predicate.Asset(sql.FieldContains(FieldPostID, vc)) -} - -// PostIDHasPrefix applies the HasPrefix predicate on the "post_id" field. -func PostIDHasPrefix(v xid.ID) predicate.Asset { - vc := v.String() - return predicate.Asset(sql.FieldHasPrefix(FieldPostID, vc)) -} - -// PostIDHasSuffix applies the HasSuffix predicate on the "post_id" field. -func PostIDHasSuffix(v xid.ID) predicate.Asset { - vc := v.String() - return predicate.Asset(sql.FieldHasSuffix(FieldPostID, vc)) -} - -// PostIDIsNil applies the IsNil predicate on the "post_id" field. -func PostIDIsNil() predicate.Asset { - return predicate.Asset(sql.FieldIsNull(FieldPostID)) -} - -// PostIDNotNil applies the NotNil predicate on the "post_id" field. -func PostIDNotNil() predicate.Asset { - return predicate.Asset(sql.FieldNotNull(FieldPostID)) -} - -// PostIDEqualFold applies the EqualFold predicate on the "post_id" field. -func PostIDEqualFold(v xid.ID) predicate.Asset { - vc := v.String() - return predicate.Asset(sql.FieldEqualFold(FieldPostID, vc)) -} - -// PostIDContainsFold applies the ContainsFold predicate on the "post_id" field. -func PostIDContainsFold(v xid.ID) predicate.Asset { - vc := v.String() - return predicate.Asset(sql.FieldContainsFold(FieldPostID, vc)) -} - // AccountIDEQ applies the EQ predicate on the "account_id" field. func AccountIDEQ(v xid.ID) predicate.Asset { return predicate.Asset(sql.FieldEQ(FieldAccountID, v)) @@ -546,24 +461,24 @@ func AccountIDContainsFold(v xid.ID) predicate.Asset { return predicate.Asset(sql.FieldContainsFold(FieldAccountID, vc)) } -// HasPost applies the HasEdge predicate on the "post" edge. -func HasPost() predicate.Asset { +// HasPosts applies the HasEdge predicate on the "posts" edge. +func HasPosts() predicate.Asset { return predicate.Asset(func(s *sql.Selector) { step := sqlgraph.NewStep( sqlgraph.From(Table, FieldID), - sqlgraph.Edge(sqlgraph.M2O, true, PostTable, PostColumn), + sqlgraph.Edge(sqlgraph.M2M, true, PostsTable, PostsPrimaryKey...), ) sqlgraph.HasNeighbors(s, step) }) } -// HasPostWith applies the HasEdge predicate on the "post" edge with a given conditions (other predicates). -func HasPostWith(preds ...predicate.Post) predicate.Asset { +// HasPostsWith applies the HasEdge predicate on the "posts" edge with a given conditions (other predicates). +func HasPostsWith(preds ...predicate.Post) predicate.Asset { return predicate.Asset(func(s *sql.Selector) { step := sqlgraph.NewStep( sqlgraph.From(Table, FieldID), - sqlgraph.To(PostInverseTable, FieldID), - sqlgraph.Edge(sqlgraph.M2O, true, PostTable, PostColumn), + sqlgraph.To(PostsInverseTable, FieldID), + sqlgraph.Edge(sqlgraph.M2M, true, PostsTable, PostsPrimaryKey...), ) sqlgraph.HasNeighborsWith(s, step, func(s *sql.Selector) { for _, p := range preds { diff --git a/internal/ent/asset_create.go b/internal/ent/asset_create.go index a33f90d59..bace799c4 100644 --- a/internal/ent/asset_create.go +++ b/internal/ent/asset_create.go @@ -78,20 +78,6 @@ func (ac *AssetCreate) SetHeight(i int) *AssetCreate { return ac } -// SetPostID sets the "post_id" field. -func (ac *AssetCreate) SetPostID(x xid.ID) *AssetCreate { - ac.mutation.SetPostID(x) - return ac -} - -// SetNillablePostID sets the "post_id" field if the given value is not nil. -func (ac *AssetCreate) SetNillablePostID(x *xid.ID) *AssetCreate { - if x != nil { - ac.SetPostID(*x) - } - return ac -} - // SetAccountID sets the "account_id" field. func (ac *AssetCreate) SetAccountID(x xid.ID) *AssetCreate { ac.mutation.SetAccountID(x) @@ -104,9 +90,19 @@ func (ac *AssetCreate) SetID(s string) *AssetCreate { return ac } -// SetPost sets the "post" edge to the Post entity. -func (ac *AssetCreate) SetPost(p *Post) *AssetCreate { - return ac.SetPostID(p.ID) +// AddPostIDs adds the "posts" edge to the Post entity by IDs. +func (ac *AssetCreate) AddPostIDs(ids ...xid.ID) *AssetCreate { + ac.mutation.AddPostIDs(ids...) + return ac +} + +// AddPosts adds the "posts" edges to the Post entity. +func (ac *AssetCreate) AddPosts(p ...*Post) *AssetCreate { + ids := make([]xid.ID, len(p)) + for i := range p { + ids[i] = p[i].ID + } + return ac.AddPostIDs(ids...) } // SetOwnerID sets the "owner" edge to the Account entity by ID. @@ -256,12 +252,12 @@ func (ac *AssetCreate) createSpec() (*Asset, *sqlgraph.CreateSpec) { _spec.SetField(asset.FieldHeight, field.TypeInt, value) _node.Height = value } - if nodes := ac.mutation.PostIDs(); len(nodes) > 0 { + if nodes := ac.mutation.PostsIDs(); len(nodes) > 0 { edge := &sqlgraph.EdgeSpec{ - Rel: sqlgraph.M2O, + Rel: sqlgraph.M2M, Inverse: true, - Table: asset.PostTable, - Columns: []string{asset.PostColumn}, + Table: asset.PostsTable, + Columns: asset.PostsPrimaryKey, Bidi: false, Target: &sqlgraph.EdgeTarget{ IDSpec: sqlgraph.NewFieldSpec(post.FieldID, field.TypeString), @@ -270,7 +266,6 @@ func (ac *AssetCreate) createSpec() (*Asset, *sqlgraph.CreateSpec) { for _, k := range nodes { edge.Target.Nodes = append(edge.Target.Nodes, k) } - _node.PostID = nodes[0] _spec.Edges = append(_spec.Edges, edge) } if nodes := ac.mutation.OwnerIDs(); len(nodes) > 0 { @@ -414,24 +409,6 @@ func (u *AssetUpsert) AddHeight(v int) *AssetUpsert { return u } -// SetPostID sets the "post_id" field. -func (u *AssetUpsert) SetPostID(v xid.ID) *AssetUpsert { - u.Set(asset.FieldPostID, v) - return u -} - -// UpdatePostID sets the "post_id" field to the value that was provided on create. -func (u *AssetUpsert) UpdatePostID() *AssetUpsert { - u.SetExcluded(asset.FieldPostID) - return u -} - -// ClearPostID clears the value of the "post_id" field. -func (u *AssetUpsert) ClearPostID() *AssetUpsert { - u.SetNull(asset.FieldPostID) - return u -} - // SetAccountID sets the "account_id" field. func (u *AssetUpsert) SetAccountID(v xid.ID) *AssetUpsert { u.Set(asset.FieldAccountID, v) @@ -579,27 +556,6 @@ func (u *AssetUpsertOne) UpdateHeight() *AssetUpsertOne { }) } -// SetPostID sets the "post_id" field. -func (u *AssetUpsertOne) SetPostID(v xid.ID) *AssetUpsertOne { - return u.Update(func(s *AssetUpsert) { - s.SetPostID(v) - }) -} - -// UpdatePostID sets the "post_id" field to the value that was provided on create. -func (u *AssetUpsertOne) UpdatePostID() *AssetUpsertOne { - return u.Update(func(s *AssetUpsert) { - s.UpdatePostID() - }) -} - -// ClearPostID clears the value of the "post_id" field. -func (u *AssetUpsertOne) ClearPostID() *AssetUpsertOne { - return u.Update(func(s *AssetUpsert) { - s.ClearPostID() - }) -} - // SetAccountID sets the "account_id" field. func (u *AssetUpsertOne) SetAccountID(v xid.ID) *AssetUpsertOne { return u.Update(func(s *AssetUpsert) { @@ -912,27 +868,6 @@ func (u *AssetUpsertBulk) UpdateHeight() *AssetUpsertBulk { }) } -// SetPostID sets the "post_id" field. -func (u *AssetUpsertBulk) SetPostID(v xid.ID) *AssetUpsertBulk { - return u.Update(func(s *AssetUpsert) { - s.SetPostID(v) - }) -} - -// UpdatePostID sets the "post_id" field to the value that was provided on create. -func (u *AssetUpsertBulk) UpdatePostID() *AssetUpsertBulk { - return u.Update(func(s *AssetUpsert) { - s.UpdatePostID() - }) -} - -// ClearPostID clears the value of the "post_id" field. -func (u *AssetUpsertBulk) ClearPostID() *AssetUpsertBulk { - return u.Update(func(s *AssetUpsert) { - s.ClearPostID() - }) -} - // SetAccountID sets the "account_id" field. func (u *AssetUpsertBulk) SetAccountID(v xid.ID) *AssetUpsertBulk { return u.Update(func(s *AssetUpsert) { diff --git a/internal/ent/asset_query.go b/internal/ent/asset_query.go index 0623e9c44..d0b6a84fc 100644 --- a/internal/ent/asset_query.go +++ b/internal/ent/asset_query.go @@ -4,6 +4,7 @@ package ent import ( "context" + "database/sql/driver" "fmt" "math" @@ -24,7 +25,7 @@ type AssetQuery struct { order []OrderFunc inters []Interceptor predicates []predicate.Asset - withPost *PostQuery + withPosts *PostQuery withOwner *AccountQuery modifiers []func(*sql.Selector) // intermediate query (i.e. traversal path). @@ -63,8 +64,8 @@ func (aq *AssetQuery) Order(o ...OrderFunc) *AssetQuery { return aq } -// QueryPost chains the current query on the "post" edge. -func (aq *AssetQuery) QueryPost() *PostQuery { +// QueryPosts chains the current query on the "posts" edge. +func (aq *AssetQuery) QueryPosts() *PostQuery { query := (&PostClient{config: aq.config}).Query() query.path = func(ctx context.Context) (fromU *sql.Selector, err error) { if err := aq.prepareQuery(ctx); err != nil { @@ -77,7 +78,7 @@ func (aq *AssetQuery) QueryPost() *PostQuery { step := sqlgraph.NewStep( sqlgraph.From(asset.Table, asset.FieldID, selector), sqlgraph.To(post.Table, post.FieldID), - sqlgraph.Edge(sqlgraph.M2O, true, asset.PostTable, asset.PostColumn), + sqlgraph.Edge(sqlgraph.M2M, true, asset.PostsTable, asset.PostsPrimaryKey...), ) fromU = sqlgraph.SetNeighbors(aq.driver.Dialect(), step) return fromU, nil @@ -299,7 +300,7 @@ func (aq *AssetQuery) Clone() *AssetQuery { order: append([]OrderFunc{}, aq.order...), inters: append([]Interceptor{}, aq.inters...), predicates: append([]predicate.Asset{}, aq.predicates...), - withPost: aq.withPost.Clone(), + withPosts: aq.withPosts.Clone(), withOwner: aq.withOwner.Clone(), // clone intermediate query. sql: aq.sql.Clone(), @@ -307,14 +308,14 @@ func (aq *AssetQuery) Clone() *AssetQuery { } } -// WithPost tells the query-builder to eager-load the nodes that are connected to -// the "post" edge. The optional arguments are used to configure the query builder of the edge. -func (aq *AssetQuery) WithPost(opts ...func(*PostQuery)) *AssetQuery { +// WithPosts tells the query-builder to eager-load the nodes that are connected to +// the "posts" edge. The optional arguments are used to configure the query builder of the edge. +func (aq *AssetQuery) WithPosts(opts ...func(*PostQuery)) *AssetQuery { query := (&PostClient{config: aq.config}).Query() for _, opt := range opts { opt(query) } - aq.withPost = query + aq.withPosts = query return aq } @@ -408,7 +409,7 @@ func (aq *AssetQuery) sqlAll(ctx context.Context, hooks ...queryHook) ([]*Asset, nodes = []*Asset{} _spec = aq.querySpec() loadedTypes = [2]bool{ - aq.withPost != nil, + aq.withPosts != nil, aq.withOwner != nil, } ) @@ -433,9 +434,10 @@ func (aq *AssetQuery) sqlAll(ctx context.Context, hooks ...queryHook) ([]*Asset, if len(nodes) == 0 { return nodes, nil } - if query := aq.withPost; query != nil { - if err := aq.loadPost(ctx, query, nodes, nil, - func(n *Asset, e *Post) { n.Edges.Post = e }); err != nil { + if query := aq.withPosts; query != nil { + if err := aq.loadPosts(ctx, query, nodes, + func(n *Asset) { n.Edges.Posts = []*Post{} }, + func(n *Asset, e *Post) { n.Edges.Posts = append(n.Edges.Posts, e) }); err != nil { return nil, err } } @@ -448,31 +450,63 @@ func (aq *AssetQuery) sqlAll(ctx context.Context, hooks ...queryHook) ([]*Asset, return nodes, nil } -func (aq *AssetQuery) loadPost(ctx context.Context, query *PostQuery, nodes []*Asset, init func(*Asset), assign func(*Asset, *Post)) error { - ids := make([]xid.ID, 0, len(nodes)) - nodeids := make(map[xid.ID][]*Asset) - for i := range nodes { - fk := nodes[i].PostID - if _, ok := nodeids[fk]; !ok { - ids = append(ids, fk) +func (aq *AssetQuery) loadPosts(ctx context.Context, query *PostQuery, nodes []*Asset, init func(*Asset), assign func(*Asset, *Post)) error { + edgeIDs := make([]driver.Value, len(nodes)) + byID := make(map[string]*Asset) + nids := make(map[xid.ID]map[*Asset]struct{}) + for i, node := range nodes { + edgeIDs[i] = node.ID + byID[node.ID] = node + if init != nil { + init(node) } - nodeids[fk] = append(nodeids[fk], nodes[i]) } - if len(ids) == 0 { - return nil + query.Where(func(s *sql.Selector) { + joinT := sql.Table(asset.PostsTable) + s.Join(joinT).On(s.C(post.FieldID), joinT.C(asset.PostsPrimaryKey[0])) + s.Where(sql.InValues(joinT.C(asset.PostsPrimaryKey[1]), edgeIDs...)) + columns := s.SelectedColumns() + s.Select(joinT.C(asset.PostsPrimaryKey[1])) + s.AppendSelect(columns...) + s.SetDistinct(false) + }) + if err := query.prepareQuery(ctx); err != nil { + return err } - query.Where(post.IDIn(ids...)) - neighbors, err := query.All(ctx) + qr := QuerierFunc(func(ctx context.Context, q Query) (Value, error) { + return query.sqlAll(ctx, func(_ context.Context, spec *sqlgraph.QuerySpec) { + assign := spec.Assign + values := spec.ScanValues + spec.ScanValues = func(columns []string) ([]any, error) { + values, err := values(columns[1:]) + if err != nil { + return nil, err + } + return append([]any{new(sql.NullString)}, values...), nil + } + spec.Assign = func(columns []string, values []any) error { + outValue := values[0].(*sql.NullString).String + inValue := *values[1].(*xid.ID) + if nids[inValue] == nil { + nids[inValue] = map[*Asset]struct{}{byID[outValue]: {}} + return assign(columns[1:], values[1:]) + } + nids[inValue][byID[outValue]] = struct{}{} + return nil + } + }) + }) + neighbors, err := withInterceptors[[]*Post](ctx, query, qr, query.inters) if err != nil { return err } for _, n := range neighbors { - nodes, ok := nodeids[n.ID] + nodes, ok := nids[n.ID] if !ok { - return fmt.Errorf(`unexpected foreign-key "post_id" returned %v`, n.ID) + return fmt.Errorf(`unexpected "posts" node returned %v`, n.ID) } - for i := range nodes { - assign(nodes[i], n) + for kn := range nodes { + assign(kn, n) } } return nil diff --git a/internal/ent/asset_update.go b/internal/ent/asset_update.go index b8d37e1d6..afdd5557b 100644 --- a/internal/ent/asset_update.go +++ b/internal/ent/asset_update.go @@ -76,35 +76,25 @@ func (au *AssetUpdate) AddHeight(i int) *AssetUpdate { return au } -// SetPostID sets the "post_id" field. -func (au *AssetUpdate) SetPostID(x xid.ID) *AssetUpdate { - au.mutation.SetPostID(x) - return au -} - -// SetNillablePostID sets the "post_id" field if the given value is not nil. -func (au *AssetUpdate) SetNillablePostID(x *xid.ID) *AssetUpdate { - if x != nil { - au.SetPostID(*x) - } - return au -} - -// ClearPostID clears the value of the "post_id" field. -func (au *AssetUpdate) ClearPostID() *AssetUpdate { - au.mutation.ClearPostID() - return au -} - // SetAccountID sets the "account_id" field. func (au *AssetUpdate) SetAccountID(x xid.ID) *AssetUpdate { au.mutation.SetAccountID(x) return au } -// SetPost sets the "post" edge to the Post entity. -func (au *AssetUpdate) SetPost(p *Post) *AssetUpdate { - return au.SetPostID(p.ID) +// AddPostIDs adds the "posts" edge to the Post entity by IDs. +func (au *AssetUpdate) AddPostIDs(ids ...xid.ID) *AssetUpdate { + au.mutation.AddPostIDs(ids...) + return au +} + +// AddPosts adds the "posts" edges to the Post entity. +func (au *AssetUpdate) AddPosts(p ...*Post) *AssetUpdate { + ids := make([]xid.ID, len(p)) + for i := range p { + ids[i] = p[i].ID + } + return au.AddPostIDs(ids...) } // SetOwnerID sets the "owner" edge to the Account entity by ID. @@ -123,12 +113,27 @@ func (au *AssetUpdate) Mutation() *AssetMutation { return au.mutation } -// ClearPost clears the "post" edge to the Post entity. -func (au *AssetUpdate) ClearPost() *AssetUpdate { - au.mutation.ClearPost() +// ClearPosts clears all "posts" edges to the Post entity. +func (au *AssetUpdate) ClearPosts() *AssetUpdate { + au.mutation.ClearPosts() + return au +} + +// RemovePostIDs removes the "posts" edge to Post entities by IDs. +func (au *AssetUpdate) RemovePostIDs(ids ...xid.ID) *AssetUpdate { + au.mutation.RemovePostIDs(ids...) return au } +// RemovePosts removes "posts" edges to Post entities. +func (au *AssetUpdate) RemovePosts(p ...*Post) *AssetUpdate { + ids := make([]xid.ID, len(p)) + for i := range p { + ids[i] = p[i].ID + } + return au.RemovePostIDs(ids...) +} + // ClearOwner clears the "owner" edge to the Account entity. func (au *AssetUpdate) ClearOwner() *AssetUpdate { au.mutation.ClearOwner() @@ -218,12 +223,12 @@ func (au *AssetUpdate) sqlSave(ctx context.Context) (n int, err error) { if value, ok := au.mutation.AddedHeight(); ok { _spec.AddField(asset.FieldHeight, field.TypeInt, value) } - if au.mutation.PostCleared() { + if au.mutation.PostsCleared() { edge := &sqlgraph.EdgeSpec{ - Rel: sqlgraph.M2O, + Rel: sqlgraph.M2M, Inverse: true, - Table: asset.PostTable, - Columns: []string{asset.PostColumn}, + Table: asset.PostsTable, + Columns: asset.PostsPrimaryKey, Bidi: false, Target: &sqlgraph.EdgeTarget{ IDSpec: sqlgraph.NewFieldSpec(post.FieldID, field.TypeString), @@ -231,12 +236,28 @@ func (au *AssetUpdate) sqlSave(ctx context.Context) (n int, err error) { } _spec.Edges.Clear = append(_spec.Edges.Clear, edge) } - if nodes := au.mutation.PostIDs(); len(nodes) > 0 { + if nodes := au.mutation.RemovedPostsIDs(); len(nodes) > 0 && !au.mutation.PostsCleared() { edge := &sqlgraph.EdgeSpec{ - Rel: sqlgraph.M2O, + Rel: sqlgraph.M2M, + Inverse: true, + Table: asset.PostsTable, + Columns: asset.PostsPrimaryKey, + Bidi: false, + Target: &sqlgraph.EdgeTarget{ + IDSpec: sqlgraph.NewFieldSpec(post.FieldID, field.TypeString), + }, + } + for _, k := range nodes { + edge.Target.Nodes = append(edge.Target.Nodes, k) + } + _spec.Edges.Clear = append(_spec.Edges.Clear, edge) + } + if nodes := au.mutation.PostsIDs(); len(nodes) > 0 { + edge := &sqlgraph.EdgeSpec{ + Rel: sqlgraph.M2M, Inverse: true, - Table: asset.PostTable, - Columns: []string{asset.PostColumn}, + Table: asset.PostsTable, + Columns: asset.PostsPrimaryKey, Bidi: false, Target: &sqlgraph.EdgeTarget{ IDSpec: sqlgraph.NewFieldSpec(post.FieldID, field.TypeString), @@ -342,35 +363,25 @@ func (auo *AssetUpdateOne) AddHeight(i int) *AssetUpdateOne { return auo } -// SetPostID sets the "post_id" field. -func (auo *AssetUpdateOne) SetPostID(x xid.ID) *AssetUpdateOne { - auo.mutation.SetPostID(x) - return auo -} - -// SetNillablePostID sets the "post_id" field if the given value is not nil. -func (auo *AssetUpdateOne) SetNillablePostID(x *xid.ID) *AssetUpdateOne { - if x != nil { - auo.SetPostID(*x) - } - return auo -} - -// ClearPostID clears the value of the "post_id" field. -func (auo *AssetUpdateOne) ClearPostID() *AssetUpdateOne { - auo.mutation.ClearPostID() - return auo -} - // SetAccountID sets the "account_id" field. func (auo *AssetUpdateOne) SetAccountID(x xid.ID) *AssetUpdateOne { auo.mutation.SetAccountID(x) return auo } -// SetPost sets the "post" edge to the Post entity. -func (auo *AssetUpdateOne) SetPost(p *Post) *AssetUpdateOne { - return auo.SetPostID(p.ID) +// AddPostIDs adds the "posts" edge to the Post entity by IDs. +func (auo *AssetUpdateOne) AddPostIDs(ids ...xid.ID) *AssetUpdateOne { + auo.mutation.AddPostIDs(ids...) + return auo +} + +// AddPosts adds the "posts" edges to the Post entity. +func (auo *AssetUpdateOne) AddPosts(p ...*Post) *AssetUpdateOne { + ids := make([]xid.ID, len(p)) + for i := range p { + ids[i] = p[i].ID + } + return auo.AddPostIDs(ids...) } // SetOwnerID sets the "owner" edge to the Account entity by ID. @@ -389,12 +400,27 @@ func (auo *AssetUpdateOne) Mutation() *AssetMutation { return auo.mutation } -// ClearPost clears the "post" edge to the Post entity. -func (auo *AssetUpdateOne) ClearPost() *AssetUpdateOne { - auo.mutation.ClearPost() +// ClearPosts clears all "posts" edges to the Post entity. +func (auo *AssetUpdateOne) ClearPosts() *AssetUpdateOne { + auo.mutation.ClearPosts() + return auo +} + +// RemovePostIDs removes the "posts" edge to Post entities by IDs. +func (auo *AssetUpdateOne) RemovePostIDs(ids ...xid.ID) *AssetUpdateOne { + auo.mutation.RemovePostIDs(ids...) return auo } +// RemovePosts removes "posts" edges to Post entities. +func (auo *AssetUpdateOne) RemovePosts(p ...*Post) *AssetUpdateOne { + ids := make([]xid.ID, len(p)) + for i := range p { + ids[i] = p[i].ID + } + return auo.RemovePostIDs(ids...) +} + // ClearOwner clears the "owner" edge to the Account entity. func (auo *AssetUpdateOne) ClearOwner() *AssetUpdateOne { auo.mutation.ClearOwner() @@ -514,12 +540,12 @@ func (auo *AssetUpdateOne) sqlSave(ctx context.Context) (_node *Asset, err error if value, ok := auo.mutation.AddedHeight(); ok { _spec.AddField(asset.FieldHeight, field.TypeInt, value) } - if auo.mutation.PostCleared() { + if auo.mutation.PostsCleared() { edge := &sqlgraph.EdgeSpec{ - Rel: sqlgraph.M2O, + Rel: sqlgraph.M2M, Inverse: true, - Table: asset.PostTable, - Columns: []string{asset.PostColumn}, + Table: asset.PostsTable, + Columns: asset.PostsPrimaryKey, Bidi: false, Target: &sqlgraph.EdgeTarget{ IDSpec: sqlgraph.NewFieldSpec(post.FieldID, field.TypeString), @@ -527,12 +553,28 @@ func (auo *AssetUpdateOne) sqlSave(ctx context.Context) (_node *Asset, err error } _spec.Edges.Clear = append(_spec.Edges.Clear, edge) } - if nodes := auo.mutation.PostIDs(); len(nodes) > 0 { + if nodes := auo.mutation.RemovedPostsIDs(); len(nodes) > 0 && !auo.mutation.PostsCleared() { edge := &sqlgraph.EdgeSpec{ - Rel: sqlgraph.M2O, + Rel: sqlgraph.M2M, + Inverse: true, + Table: asset.PostsTable, + Columns: asset.PostsPrimaryKey, + Bidi: false, + Target: &sqlgraph.EdgeTarget{ + IDSpec: sqlgraph.NewFieldSpec(post.FieldID, field.TypeString), + }, + } + for _, k := range nodes { + edge.Target.Nodes = append(edge.Target.Nodes, k) + } + _spec.Edges.Clear = append(_spec.Edges.Clear, edge) + } + if nodes := auo.mutation.PostsIDs(); len(nodes) > 0 { + edge := &sqlgraph.EdgeSpec{ + Rel: sqlgraph.M2M, Inverse: true, - Table: asset.PostTable, - Columns: []string{asset.PostColumn}, + Table: asset.PostsTable, + Columns: asset.PostsPrimaryKey, Bidi: false, Target: &sqlgraph.EdgeTarget{ IDSpec: sqlgraph.NewFieldSpec(post.FieldID, field.TypeString), diff --git a/internal/ent/client.go b/internal/ent/client.go index 17d533386..154a83ec2 100644 --- a/internal/ent/client.go +++ b/internal/ent/client.go @@ -578,15 +578,15 @@ func (c *AssetClient) GetX(ctx context.Context, id string) *Asset { return obj } -// QueryPost queries the post edge of a Asset. -func (c *AssetClient) QueryPost(a *Asset) *PostQuery { +// QueryPosts queries the posts edge of a Asset. +func (c *AssetClient) QueryPosts(a *Asset) *PostQuery { query := (&PostClient{config: c.config}).Query() query.path = func(context.Context) (fromV *sql.Selector, _ error) { id := a.ID step := sqlgraph.NewStep( sqlgraph.From(asset.Table, asset.FieldID, id), sqlgraph.To(post.Table, post.FieldID), - sqlgraph.Edge(sqlgraph.M2O, true, asset.PostTable, asset.PostColumn), + sqlgraph.Edge(sqlgraph.M2M, true, asset.PostsTable, asset.PostsPrimaryKey...), ) fromV = sqlgraph.Neighbors(a.driver.Dialect(), step) return fromV, nil @@ -1250,7 +1250,7 @@ func (c *PostClient) QueryAssets(po *Post) *AssetQuery { step := sqlgraph.NewStep( sqlgraph.From(post.Table, post.FieldID, id), sqlgraph.To(asset.Table, asset.FieldID), - sqlgraph.Edge(sqlgraph.O2M, false, post.AssetsTable, post.AssetsColumn), + sqlgraph.Edge(sqlgraph.M2M, false, post.AssetsTable, post.AssetsPrimaryKey...), ) fromV = sqlgraph.Neighbors(po.driver.Dialect(), step) return fromV, nil diff --git a/internal/ent/migrate/schema.go b/internal/ent/migrate/schema.go index 42c535192..e4b97c208 100644 --- a/internal/ent/migrate/schema.go +++ b/internal/ent/migrate/schema.go @@ -35,7 +35,6 @@ var ( {Name: "width", Type: field.TypeInt}, {Name: "height", Type: field.TypeInt}, {Name: "account_id", Type: field.TypeString, Size: 20}, - {Name: "post_id", Type: field.TypeString, Nullable: true, Size: 20}, } // AssetsTable holds the schema information for the "assets" table. AssetsTable = &schema.Table{ @@ -49,12 +48,6 @@ var ( RefColumns: []*schema.Column{AccountsColumns[0]}, OnDelete: schema.NoAction, }, - { - Symbol: "assets_posts_assets", - Columns: []*schema.Column{AssetsColumns[8]}, - RefColumns: []*schema.Column{PostsColumns[0]}, - OnDelete: schema.SetNull, - }, }, } // AuthenticationsColumns holds the columns for the "authentications" table. @@ -261,6 +254,31 @@ var ( }, }, } + // PostAssetsColumns holds the columns for the "post_assets" table. + PostAssetsColumns = []*schema.Column{ + {Name: "post_id", Type: field.TypeString, Size: 20}, + {Name: "asset_id", Type: field.TypeString}, + } + // PostAssetsTable holds the schema information for the "post_assets" table. + PostAssetsTable = &schema.Table{ + Name: "post_assets", + Columns: PostAssetsColumns, + PrimaryKey: []*schema.Column{PostAssetsColumns[0], PostAssetsColumns[1]}, + ForeignKeys: []*schema.ForeignKey{ + { + Symbol: "post_assets_post_id", + Columns: []*schema.Column{PostAssetsColumns[0]}, + RefColumns: []*schema.Column{PostsColumns[0]}, + OnDelete: schema.Cascade, + }, + { + Symbol: "post_assets_asset_id", + Columns: []*schema.Column{PostAssetsColumns[1]}, + RefColumns: []*schema.Column{AssetsColumns[0]}, + OnDelete: schema.Cascade, + }, + }, + } // RoleAccountsColumns holds the columns for the "role_accounts" table. RoleAccountsColumns = []*schema.Column{ {Name: "role_id", Type: field.TypeString, Size: 20}, @@ -324,6 +342,7 @@ var ( SettingsTable, TagsTable, AccountTagsTable, + PostAssetsTable, RoleAccountsTable, TagPostsTable, } @@ -331,7 +350,6 @@ var ( func init() { AssetsTable.ForeignKeys[0].RefTable = AccountsTable - AssetsTable.ForeignKeys[1].RefTable = PostsTable AuthenticationsTable.ForeignKeys[0].RefTable = AccountsTable PostsTable.ForeignKeys[0].RefTable = AccountsTable PostsTable.ForeignKeys[1].RefTable = CategoriesTable @@ -341,6 +359,8 @@ func init() { ReactsTable.ForeignKeys[1].RefTable = PostsTable AccountTagsTable.ForeignKeys[0].RefTable = AccountsTable AccountTagsTable.ForeignKeys[1].RefTable = TagsTable + PostAssetsTable.ForeignKeys[0].RefTable = PostsTable + PostAssetsTable.ForeignKeys[1].RefTable = AssetsTable RoleAccountsTable.ForeignKeys[0].RefTable = RolesTable RoleAccountsTable.ForeignKeys[1].RefTable = AccountsTable TagPostsTable.ForeignKeys[0].RefTable = TagsTable diff --git a/internal/ent/mutation.go b/internal/ent/mutation.go index 95bc1d582..a92d5ef60 100644 --- a/internal/ent/mutation.go +++ b/internal/ent/mutation.go @@ -1266,8 +1266,9 @@ type AssetMutation struct { height *int addheight *int clearedFields map[string]struct{} - post *xid.ID - clearedpost bool + posts map[xid.ID]struct{} + removedposts map[xid.ID]struct{} + clearedposts bool owner *xid.ID clearedowner bool done bool @@ -1635,55 +1636,6 @@ func (m *AssetMutation) ResetHeight() { m.addheight = nil } -// SetPostID sets the "post_id" field. -func (m *AssetMutation) SetPostID(x xid.ID) { - m.post = &x -} - -// PostID returns the value of the "post_id" field in the mutation. -func (m *AssetMutation) PostID() (r xid.ID, exists bool) { - v := m.post - if v == nil { - return - } - return *v, true -} - -// OldPostID returns the old "post_id" field's value of the Asset entity. -// If the Asset 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 *AssetMutation) OldPostID(ctx context.Context) (v xid.ID, err error) { - if !m.op.Is(OpUpdateOne) { - return v, errors.New("OldPostID is only allowed on UpdateOne operations") - } - if m.id == nil || m.oldValue == nil { - return v, errors.New("OldPostID requires an ID field in the mutation") - } - oldValue, err := m.oldValue(ctx) - if err != nil { - return v, fmt.Errorf("querying old value for OldPostID: %w", err) - } - return oldValue.PostID, nil -} - -// ClearPostID clears the value of the "post_id" field. -func (m *AssetMutation) ClearPostID() { - m.post = nil - m.clearedFields[asset.FieldPostID] = struct{}{} -} - -// PostIDCleared returns if the "post_id" field was cleared in this mutation. -func (m *AssetMutation) PostIDCleared() bool { - _, ok := m.clearedFields[asset.FieldPostID] - return ok -} - -// ResetPostID resets all changes to the "post_id" field. -func (m *AssetMutation) ResetPostID() { - m.post = nil - delete(m.clearedFields, asset.FieldPostID) -} - // SetAccountID sets the "account_id" field. func (m *AssetMutation) SetAccountID(x xid.ID) { m.owner = &x @@ -1720,30 +1672,58 @@ func (m *AssetMutation) ResetAccountID() { m.owner = nil } -// ClearPost clears the "post" edge to the Post entity. -func (m *AssetMutation) ClearPost() { - m.clearedpost = true +// AddPostIDs adds the "posts" edge to the Post entity by ids. +func (m *AssetMutation) AddPostIDs(ids ...xid.ID) { + if m.posts == nil { + m.posts = make(map[xid.ID]struct{}) + } + for i := range ids { + m.posts[ids[i]] = struct{}{} + } } -// PostCleared reports if the "post" edge to the Post entity was cleared. -func (m *AssetMutation) PostCleared() bool { - return m.PostIDCleared() || m.clearedpost +// ClearPosts clears the "posts" edge to the Post entity. +func (m *AssetMutation) ClearPosts() { + m.clearedposts = true } -// PostIDs returns the "post" edge IDs in the mutation. -// Note that IDs always returns len(IDs) <= 1 for unique edges, and you should use -// PostID instead. It exists only for internal usage by the builders. -func (m *AssetMutation) PostIDs() (ids []xid.ID) { - if id := m.post; id != nil { - ids = append(ids, *id) +// PostsCleared reports if the "posts" edge to the Post entity was cleared. +func (m *AssetMutation) PostsCleared() bool { + return m.clearedposts +} + +// RemovePostIDs removes the "posts" edge to the Post entity by IDs. +func (m *AssetMutation) RemovePostIDs(ids ...xid.ID) { + if m.removedposts == nil { + m.removedposts = make(map[xid.ID]struct{}) + } + for i := range ids { + delete(m.posts, ids[i]) + m.removedposts[ids[i]] = struct{}{} + } +} + +// RemovedPosts returns the removed IDs of the "posts" edge to the Post entity. +func (m *AssetMutation) RemovedPostsIDs() (ids []xid.ID) { + for id := range m.removedposts { + ids = append(ids, id) } return } -// ResetPost resets all changes to the "post" edge. -func (m *AssetMutation) ResetPost() { - m.post = nil - m.clearedpost = false +// PostsIDs returns the "posts" edge IDs in the mutation. +func (m *AssetMutation) PostsIDs() (ids []xid.ID) { + for id := range m.posts { + ids = append(ids, id) + } + return +} + +// ResetPosts resets all changes to the "posts" edge. +func (m *AssetMutation) ResetPosts() { + m.posts = nil + m.clearedposts = false + m.removedposts = nil } // SetOwnerID sets the "owner" edge to the Account entity by id. @@ -1819,7 +1799,7 @@ func (m *AssetMutation) Type() string { // order to get all numeric fields that were incremented/decremented, call // AddedFields(). func (m *AssetMutation) Fields() []string { - fields := make([]string, 0, 8) + fields := make([]string, 0, 7) if m.created_at != nil { fields = append(fields, asset.FieldCreatedAt) } @@ -1838,9 +1818,6 @@ func (m *AssetMutation) Fields() []string { if m.height != nil { fields = append(fields, asset.FieldHeight) } - if m.post != nil { - fields = append(fields, asset.FieldPostID) - } if m.owner != nil { fields = append(fields, asset.FieldAccountID) } @@ -1864,8 +1841,6 @@ func (m *AssetMutation) Field(name string) (ent.Value, bool) { return m.Width() case asset.FieldHeight: return m.Height() - case asset.FieldPostID: - return m.PostID() case asset.FieldAccountID: return m.AccountID() } @@ -1889,8 +1864,6 @@ func (m *AssetMutation) OldField(ctx context.Context, name string) (ent.Value, e return m.OldWidth(ctx) case asset.FieldHeight: return m.OldHeight(ctx) - case asset.FieldPostID: - return m.OldPostID(ctx) case asset.FieldAccountID: return m.OldAccountID(ctx) } @@ -1944,13 +1917,6 @@ func (m *AssetMutation) SetField(name string, value ent.Value) error { } m.SetHeight(v) return nil - case asset.FieldPostID: - v, ok := value.(xid.ID) - if !ok { - return fmt.Errorf("unexpected type %T for field %s", value, name) - } - m.SetPostID(v) - return nil case asset.FieldAccountID: v, ok := value.(xid.ID) if !ok { @@ -2014,11 +1980,7 @@ func (m *AssetMutation) AddField(name string, value ent.Value) error { // ClearedFields returns all nullable fields that were cleared during this // mutation. func (m *AssetMutation) ClearedFields() []string { - var fields []string - if m.FieldCleared(asset.FieldPostID) { - fields = append(fields, asset.FieldPostID) - } - return fields + return nil } // FieldCleared returns a boolean indicating if a field with the given name was @@ -2031,11 +1993,6 @@ func (m *AssetMutation) FieldCleared(name string) bool { // ClearField clears the value of the field with the given name. It returns an // error if the field is not defined in the schema. func (m *AssetMutation) ClearField(name string) error { - switch name { - case asset.FieldPostID: - m.ClearPostID() - return nil - } return fmt.Errorf("unknown Asset nullable field %s", name) } @@ -2061,9 +2018,6 @@ func (m *AssetMutation) ResetField(name string) error { case asset.FieldHeight: m.ResetHeight() return nil - case asset.FieldPostID: - m.ResetPostID() - return nil case asset.FieldAccountID: m.ResetAccountID() return nil @@ -2074,8 +2028,8 @@ func (m *AssetMutation) ResetField(name string) error { // AddedEdges returns all edge names that were set/added in this mutation. func (m *AssetMutation) AddedEdges() []string { edges := make([]string, 0, 2) - if m.post != nil { - edges = append(edges, asset.EdgePost) + if m.posts != nil { + edges = append(edges, asset.EdgePosts) } if m.owner != nil { edges = append(edges, asset.EdgeOwner) @@ -2087,10 +2041,12 @@ func (m *AssetMutation) AddedEdges() []string { // name in this mutation. func (m *AssetMutation) AddedIDs(name string) []ent.Value { switch name { - case asset.EdgePost: - if id := m.post; id != nil { - return []ent.Value{*id} + case asset.EdgePosts: + ids := make([]ent.Value, 0, len(m.posts)) + for id := range m.posts { + ids = append(ids, id) } + return ids case asset.EdgeOwner: if id := m.owner; id != nil { return []ent.Value{*id} @@ -2102,20 +2058,31 @@ func (m *AssetMutation) AddedIDs(name string) []ent.Value { // RemovedEdges returns all edge names that were removed in this mutation. func (m *AssetMutation) RemovedEdges() []string { edges := make([]string, 0, 2) + if m.removedposts != nil { + edges = append(edges, asset.EdgePosts) + } return edges } // RemovedIDs returns all IDs (to other nodes) that were removed for the edge with // the given name in this mutation. func (m *AssetMutation) RemovedIDs(name string) []ent.Value { + switch name { + case asset.EdgePosts: + ids := make([]ent.Value, 0, len(m.removedposts)) + for id := range m.removedposts { + ids = append(ids, id) + } + return ids + } return nil } // ClearedEdges returns all edge names that were cleared in this mutation. func (m *AssetMutation) ClearedEdges() []string { edges := make([]string, 0, 2) - if m.clearedpost { - edges = append(edges, asset.EdgePost) + if m.clearedposts { + edges = append(edges, asset.EdgePosts) } if m.clearedowner { edges = append(edges, asset.EdgeOwner) @@ -2127,8 +2094,8 @@ func (m *AssetMutation) ClearedEdges() []string { // was cleared in this mutation. func (m *AssetMutation) EdgeCleared(name string) bool { switch name { - case asset.EdgePost: - return m.clearedpost + case asset.EdgePosts: + return m.clearedposts case asset.EdgeOwner: return m.clearedowner } @@ -2139,9 +2106,6 @@ func (m *AssetMutation) EdgeCleared(name string) bool { // if that edge is not defined in the schema. func (m *AssetMutation) ClearEdge(name string) error { switch name { - case asset.EdgePost: - m.ClearPost() - return nil case asset.EdgeOwner: m.ClearOwner() return nil @@ -2153,8 +2117,8 @@ func (m *AssetMutation) ClearEdge(name string) error { // It returns an error if the edge is not defined in the schema. func (m *AssetMutation) ResetEdge(name string) error { switch name { - case asset.EdgePost: - m.ResetPost() + case asset.EdgePosts: + m.ResetPosts() return nil case asset.EdgeOwner: m.ResetOwner() diff --git a/internal/ent/post/post.go b/internal/ent/post/post.go index 45e046f1d..94ff49bb8 100644 --- a/internal/ent/post/post.go +++ b/internal/ent/post/post.go @@ -104,13 +104,11 @@ const ( ReactsInverseTable = "reacts" // ReactsColumn is the table column denoting the reacts relation/edge. ReactsColumn = "post_id" - // AssetsTable is the table that holds the assets relation/edge. - AssetsTable = "assets" + // AssetsTable is the table that holds the assets relation/edge. The primary key declared below. + AssetsTable = "post_assets" // AssetsInverseTable is the table name for the Asset entity. // It exists in this package in order to avoid circular dependency with the "asset" package. AssetsInverseTable = "assets" - // AssetsColumn is the table column denoting the assets relation/edge. - AssetsColumn = "post_id" ) // Columns holds all SQL columns for post fields. @@ -142,6 +140,9 @@ var ( // TagsPrimaryKey and TagsColumn2 are the table columns denoting the // primary key for the tags relation (M2M). TagsPrimaryKey = []string{"tag_id", "post_id"} + // AssetsPrimaryKey and AssetsColumn2 are the table columns denoting the + // primary key for the assets relation (M2M). + AssetsPrimaryKey = []string{"post_id", "asset_id"} ) // ValidColumn reports if the column name is valid (part of the table columns). diff --git a/internal/ent/post/where.go b/internal/ent/post/where.go index bbb89b088..c26ea8495 100644 --- a/internal/ent/post/where.go +++ b/internal/ent/post/where.go @@ -1037,7 +1037,7 @@ func HasAssets() predicate.Post { return predicate.Post(func(s *sql.Selector) { step := sqlgraph.NewStep( sqlgraph.From(Table, FieldID), - sqlgraph.Edge(sqlgraph.O2M, false, AssetsTable, AssetsColumn), + sqlgraph.Edge(sqlgraph.M2M, false, AssetsTable, AssetsPrimaryKey...), ) sqlgraph.HasNeighbors(s, step) }) @@ -1049,7 +1049,7 @@ func HasAssetsWith(preds ...predicate.Asset) predicate.Post { step := sqlgraph.NewStep( sqlgraph.From(Table, FieldID), sqlgraph.To(AssetsInverseTable, FieldID), - sqlgraph.Edge(sqlgraph.O2M, false, AssetsTable, AssetsColumn), + sqlgraph.Edge(sqlgraph.M2M, false, AssetsTable, AssetsPrimaryKey...), ) sqlgraph.HasNeighborsWith(s, step, func(s *sql.Selector) { for _, p := range preds { diff --git a/internal/ent/post_create.go b/internal/ent/post_create.go index 79620d60e..38436ea9e 100644 --- a/internal/ent/post_create.go +++ b/internal/ent/post_create.go @@ -643,10 +643,10 @@ func (pc *PostCreate) createSpec() (*Post, *sqlgraph.CreateSpec) { } if nodes := pc.mutation.AssetsIDs(); len(nodes) > 0 { edge := &sqlgraph.EdgeSpec{ - Rel: sqlgraph.O2M, + Rel: sqlgraph.M2M, Inverse: false, Table: post.AssetsTable, - Columns: []string{post.AssetsColumn}, + Columns: post.AssetsPrimaryKey, Bidi: false, Target: &sqlgraph.EdgeTarget{ IDSpec: sqlgraph.NewFieldSpec(asset.FieldID, field.TypeString), diff --git a/internal/ent/post_query.go b/internal/ent/post_query.go index ff8def4c7..970da9967 100644 --- a/internal/ent/post_query.go +++ b/internal/ent/post_query.go @@ -265,7 +265,7 @@ func (pq *PostQuery) QueryAssets() *AssetQuery { step := sqlgraph.NewStep( sqlgraph.From(post.Table, post.FieldID, selector), sqlgraph.To(asset.Table, asset.FieldID), - sqlgraph.Edge(sqlgraph.O2M, false, post.AssetsTable, post.AssetsColumn), + sqlgraph.Edge(sqlgraph.M2M, false, post.AssetsTable, post.AssetsPrimaryKey...), ) fromU = sqlgraph.SetNeighbors(pq.driver.Dialect(), step) return fromU, nil @@ -1023,29 +1023,63 @@ func (pq *PostQuery) loadReacts(ctx context.Context, query *ReactQuery, nodes [] return nil } func (pq *PostQuery) loadAssets(ctx context.Context, query *AssetQuery, nodes []*Post, init func(*Post), assign func(*Post, *Asset)) error { - fks := make([]driver.Value, 0, len(nodes)) - nodeids := make(map[xid.ID]*Post) - for i := range nodes { - fks = append(fks, nodes[i].ID) - nodeids[nodes[i].ID] = nodes[i] + edgeIDs := make([]driver.Value, len(nodes)) + byID := make(map[xid.ID]*Post) + nids := make(map[string]map[*Post]struct{}) + for i, node := range nodes { + edgeIDs[i] = node.ID + byID[node.ID] = node if init != nil { - init(nodes[i]) + init(node) } } - query.Where(predicate.Asset(func(s *sql.Selector) { - s.Where(sql.InValues(post.AssetsColumn, fks...)) - })) - neighbors, err := query.All(ctx) + query.Where(func(s *sql.Selector) { + joinT := sql.Table(post.AssetsTable) + s.Join(joinT).On(s.C(asset.FieldID), joinT.C(post.AssetsPrimaryKey[1])) + s.Where(sql.InValues(joinT.C(post.AssetsPrimaryKey[0]), edgeIDs...)) + columns := s.SelectedColumns() + s.Select(joinT.C(post.AssetsPrimaryKey[0])) + s.AppendSelect(columns...) + s.SetDistinct(false) + }) + if err := query.prepareQuery(ctx); err != nil { + return err + } + qr := QuerierFunc(func(ctx context.Context, q Query) (Value, error) { + return query.sqlAll(ctx, func(_ context.Context, spec *sqlgraph.QuerySpec) { + assign := spec.Assign + values := spec.ScanValues + spec.ScanValues = func(columns []string) ([]any, error) { + values, err := values(columns[1:]) + if err != nil { + return nil, err + } + return append([]any{new(xid.ID)}, values...), nil + } + spec.Assign = func(columns []string, values []any) error { + outValue := *values[0].(*xid.ID) + inValue := values[1].(*sql.NullString).String + if nids[inValue] == nil { + nids[inValue] = map[*Post]struct{}{byID[outValue]: {}} + return assign(columns[1:], values[1:]) + } + nids[inValue][byID[outValue]] = struct{}{} + return nil + } + }) + }) + neighbors, err := withInterceptors[[]*Asset](ctx, query, qr, query.inters) if err != nil { return err } for _, n := range neighbors { - fk := n.PostID - node, ok := nodeids[fk] + nodes, ok := nids[n.ID] if !ok { - return fmt.Errorf(`unexpected foreign-key "post_id" returned %v for node %v`, fk, n.ID) + return fmt.Errorf(`unexpected "assets" node returned %v`, n.ID) + } + for kn := range nodes { + assign(kn, n) } - assign(node, n) } return nil } diff --git a/internal/ent/post_update.go b/internal/ent/post_update.go index 535e34836..f39eee225 100644 --- a/internal/ent/post_update.go +++ b/internal/ent/post_update.go @@ -889,10 +889,10 @@ func (pu *PostUpdate) sqlSave(ctx context.Context) (n int, err error) { } if pu.mutation.AssetsCleared() { edge := &sqlgraph.EdgeSpec{ - Rel: sqlgraph.O2M, + Rel: sqlgraph.M2M, Inverse: false, Table: post.AssetsTable, - Columns: []string{post.AssetsColumn}, + Columns: post.AssetsPrimaryKey, Bidi: false, Target: &sqlgraph.EdgeTarget{ IDSpec: sqlgraph.NewFieldSpec(asset.FieldID, field.TypeString), @@ -902,10 +902,10 @@ func (pu *PostUpdate) sqlSave(ctx context.Context) (n int, err error) { } if nodes := pu.mutation.RemovedAssetsIDs(); len(nodes) > 0 && !pu.mutation.AssetsCleared() { edge := &sqlgraph.EdgeSpec{ - Rel: sqlgraph.O2M, + Rel: sqlgraph.M2M, Inverse: false, Table: post.AssetsTable, - Columns: []string{post.AssetsColumn}, + Columns: post.AssetsPrimaryKey, Bidi: false, Target: &sqlgraph.EdgeTarget{ IDSpec: sqlgraph.NewFieldSpec(asset.FieldID, field.TypeString), @@ -918,10 +918,10 @@ func (pu *PostUpdate) sqlSave(ctx context.Context) (n int, err error) { } if nodes := pu.mutation.AssetsIDs(); len(nodes) > 0 { edge := &sqlgraph.EdgeSpec{ - Rel: sqlgraph.O2M, + Rel: sqlgraph.M2M, Inverse: false, Table: post.AssetsTable, - Columns: []string{post.AssetsColumn}, + Columns: post.AssetsPrimaryKey, Bidi: false, Target: &sqlgraph.EdgeTarget{ IDSpec: sqlgraph.NewFieldSpec(asset.FieldID, field.TypeString), @@ -1838,10 +1838,10 @@ func (puo *PostUpdateOne) sqlSave(ctx context.Context) (_node *Post, err error) } if puo.mutation.AssetsCleared() { edge := &sqlgraph.EdgeSpec{ - Rel: sqlgraph.O2M, + Rel: sqlgraph.M2M, Inverse: false, Table: post.AssetsTable, - Columns: []string{post.AssetsColumn}, + Columns: post.AssetsPrimaryKey, Bidi: false, Target: &sqlgraph.EdgeTarget{ IDSpec: sqlgraph.NewFieldSpec(asset.FieldID, field.TypeString), @@ -1851,10 +1851,10 @@ func (puo *PostUpdateOne) sqlSave(ctx context.Context) (_node *Post, err error) } if nodes := puo.mutation.RemovedAssetsIDs(); len(nodes) > 0 && !puo.mutation.AssetsCleared() { edge := &sqlgraph.EdgeSpec{ - Rel: sqlgraph.O2M, + Rel: sqlgraph.M2M, Inverse: false, Table: post.AssetsTable, - Columns: []string{post.AssetsColumn}, + Columns: post.AssetsPrimaryKey, Bidi: false, Target: &sqlgraph.EdgeTarget{ IDSpec: sqlgraph.NewFieldSpec(asset.FieldID, field.TypeString), @@ -1867,10 +1867,10 @@ func (puo *PostUpdateOne) sqlSave(ctx context.Context) (_node *Post, err error) } if nodes := puo.mutation.AssetsIDs(); len(nodes) > 0 { edge := &sqlgraph.EdgeSpec{ - Rel: sqlgraph.O2M, + Rel: sqlgraph.M2M, Inverse: false, Table: post.AssetsTable, - Columns: []string{post.AssetsColumn}, + Columns: post.AssetsPrimaryKey, Bidi: false, Target: &sqlgraph.EdgeTarget{ IDSpec: sqlgraph.NewFieldSpec(asset.FieldID, field.TypeString), diff --git a/internal/ent/schema/asset.go b/internal/ent/schema/asset.go index 8b6c147a7..642f9bfd2 100644 --- a/internal/ent/schema/asset.go +++ b/internal/ent/schema/asset.go @@ -28,18 +28,14 @@ func (Asset) Fields() []ent.Field { field.Int("height"), // Edges - field.String("post_id").GoType(xid.ID{}).Optional(), field.String("account_id").GoType(xid.ID{}), } } -// Edges of Asset. func (Asset) Edges() []ent.Edge { return []ent.Edge{ - edge.From("post", Post.Type). - Field("post_id"). - Ref("assets"). - Unique(), + edge.From("posts", Post.Type). + Ref("assets"), edge.From("owner", Account.Type). Field("account_id"). diff --git a/internal/openapi/generated.go b/internal/openapi/generated.go index e84908a6f..fabb22da3 100644 --- a/internal/openapi/generated.go +++ b/internal/openapi/generated.go @@ -362,6 +362,8 @@ type PhoneSubmitCodeProps struct { // PostCommonProps defines model for PostCommonProps. type PostCommonProps struct { + Assets AssetList `json:"assets"` + // Author A minimal reference to an account. Author ProfileReference `json:"author"` @@ -369,8 +371,7 @@ type PostCommonProps struct { // an object, depending on what was used during creation. Strings can be // used for basic plain text or markdown content and objects are used for // more complex types such as Slate.js editor documents. - Body PostContent `json:"body"` - Media AssetList `json:"media"` + Body PostContent `json:"body"` // Meta Arbitrary metadata for the resource. Meta *Metadata `json:"meta,omitempty"` @@ -432,6 +433,8 @@ type PostMutableProps struct { // PostProps defines model for PostProps. type PostProps struct { + Assets AssetList `json:"assets"` + // Author A minimal reference to an account. Author ProfileReference `json:"author"` @@ -448,8 +451,7 @@ type PostProps struct { DeletedAt *time.Time `json:"deletedAt,omitempty"` // Id A unique identifier for this resource. - Id Identifier `json:"id"` - Media AssetList `json:"media"` + Id Identifier `json:"id"` // Meta Arbitrary metadata for the resource. Meta *Metadata `json:"meta,omitempty"` @@ -648,6 +650,8 @@ type TagName = string // Thread defines model for Thread. type Thread struct { + Assets AssetList `json:"assets"` + // Author A minimal reference to an account. Author ProfileReference `json:"author"` Category CategoryReference `json:"category"` @@ -659,8 +663,7 @@ type Thread struct { DeletedAt *time.Time `json:"deletedAt,omitempty"` // Id A unique identifier for this resource. - Id Identifier `json:"id"` - Media AssetList `json:"media"` + Id Identifier `json:"id"` // Meta Arbitrary metadata for the resource. Meta Metadata `json:"meta"` @@ -767,6 +770,8 @@ type ThreadMutableProps struct { // ThreadReference defines model for ThreadReference. type ThreadReference struct { + Assets AssetList `json:"assets"` + // Author A minimal reference to an account. Author ProfileReference `json:"author"` Category CategoryReference `json:"category"` @@ -778,8 +783,7 @@ type ThreadReference struct { DeletedAt *time.Time `json:"deletedAt,omitempty"` // Id A unique identifier for this resource. - Id Identifier `json:"id"` - Media AssetList `json:"media"` + Id Identifier `json:"id"` // Meta Arbitrary metadata for the resource. Meta Metadata `json:"meta"` @@ -8218,119 +8222,119 @@ func (sh *strictHandler) GetVersion(ctx echo.Context) error { // Base64 encoded, gzipped, json marshaled Swagger object var swaggerSpec = []string{ - "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", - "u/v/AAAA///mzbJs0JwAAA==", + "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", + "AP//t/L69dScAAA=", } // GetSwagger returns the content of the embedded swagger specification file diff --git a/web/src/api/client.ts b/web/src/api/client.ts index ec26c3f47..f4d846715 100644 --- a/web/src/api/client.ts +++ b/web/src/api/client.ts @@ -25,7 +25,7 @@ export const fetcher = async ({ mode: "cors", credentials: "include", ...(headers ? { headers } : {}), - ...(data ? { body: JSON.stringify(data) } : {}), + body: buildPayload(data), }); const response = await fetch(req); @@ -50,6 +50,18 @@ export const fetcher = async ({ return response.json(); }; +const buildPayload = (data: unknown) => { + if (!data) { + return undefined; + } + + if (data instanceof File) { + return data; + } + + return JSON.stringify(data); +}; + export default fetcher; const removeEmpty = omitBy(isNil); diff --git a/web/src/api/openapi/schemas/postCommonProps.ts b/web/src/api/openapi/schemas/postCommonProps.ts index 60d0b59c8..20cf59b02 100644 --- a/web/src/api/openapi/schemas/postCommonProps.ts +++ b/web/src/api/openapi/schemas/postCommonProps.ts @@ -21,5 +21,5 @@ export interface PostCommonProps { meta?: Metadata; reacts: ReactList; reply_to?: Identifier; - media: AssetList; + assets: AssetList; } diff --git a/web/src/api/openapi/schemas/threadReferenceAllOf.ts b/web/src/api/openapi/schemas/threadReferenceAllOf.ts index 724265e53..e4722be3e 100644 --- a/web/src/api/openapi/schemas/threadReferenceAllOf.ts +++ b/web/src/api/openapi/schemas/threadReferenceAllOf.ts @@ -29,5 +29,5 @@ export type ThreadReferenceAllOf = { category: CategoryReference; reacts: ReactList; meta: Metadata; - media: AssetList; + assets: AssetList; }; diff --git a/web/src/components/ContentComposer/useContentComposer.ts b/web/src/components/ContentComposer/useContentComposer.ts index fb9d9a92d..8d9dca6a6 100644 --- a/web/src/components/ContentComposer/useContentComposer.ts +++ b/web/src/components/ContentComposer/useContentComposer.ts @@ -52,10 +52,6 @@ export function useContentComposer(props: Props) { focus: Editor.end(editor, []), }, }); - - // Disable this error because, despite not using the "resetKey" prop, we're - // using the behaviour of useMemo to clear the input when the value changes. - // eslint-disable-next-line react-hooks/exhaustive-deps }, [props.resetKey, editor]); function onChange(value: Descendant[]) { diff --git a/web/src/screens/compose/ComposeScreen.tsx b/web/src/screens/compose/ComposeScreen.tsx index 21a4b9945..5556174bc 100644 --- a/web/src/screens/compose/ComposeScreen.tsx +++ b/web/src/screens/compose/ComposeScreen.tsx @@ -18,7 +18,7 @@ export function ComposeScreen(props: Props) { return ( - + ); } diff --git a/web/src/screens/compose/components/BodyInput/useBodyInput.ts b/web/src/screens/compose/components/BodyInput/useBodyInput.ts index 2fba645ba..4724bb48a 100644 --- a/web/src/screens/compose/components/BodyInput/useBodyInput.ts +++ b/web/src/screens/compose/components/BodyInput/useBodyInput.ts @@ -1,9 +1,9 @@ import { useFormContext } from "react-hook-form"; -import { ThreadCreate } from "../ComposeForm/useComposeForm"; +import { FormShape } from "../ComposeForm/useComposeForm"; export function useBodyInput() { - const ctx = useFormContext(); + const ctx = useFormContext(); return { control: ctx.control, diff --git a/web/src/screens/compose/components/CategorySelect/useCategorySelect.ts b/web/src/screens/compose/components/CategorySelect/useCategorySelect.ts index f21c3c729..c6e5b107e 100644 --- a/web/src/screens/compose/components/CategorySelect/useCategorySelect.ts +++ b/web/src/screens/compose/components/CategorySelect/useCategorySelect.ts @@ -3,10 +3,10 @@ import { useFormContext } from "react-hook-form"; import { useCategoryList } from "src/api/openapi/categories"; -import { ThreadCreate } from "../ComposeForm/useComposeForm"; +import { FormShape } from "../ComposeForm/useComposeForm"; export function useCategorySelect() { - const ctx = useFormContext(); + const ctx = useFormContext(); const { data, error } = useCategoryList(); useEffect(() => { diff --git a/web/src/screens/compose/components/ComposeForm/useComposeForm.ts b/web/src/screens/compose/components/ComposeForm/useComposeForm.ts index 676fa38ab..ab5f2f048 100644 --- a/web/src/screens/compose/components/ComposeForm/useComposeForm.ts +++ b/web/src/screens/compose/components/ComposeForm/useComposeForm.ts @@ -5,105 +5,100 @@ import { useForm } from "react-hook-form"; import { z } from "zod"; import { useCategoryList } from "src/api/openapi/categories"; -import { - Asset, - Thread, - ThreadCreateOKResponse, - ThreadStatus, -} from "src/api/openapi/schemas"; +import { Asset, Thread, ThreadStatus } from "src/api/openapi/schemas"; import { threadCreate, threadUpdate } from "src/api/openapi/threads"; import { errorToast } from "src/components/ErrorBanner"; -export type Props = { editing?: string; draft?: Thread }; +export type Props = { editing?: string; initialDraft?: Thread }; -export const ThreadMutationSchema = z.object({ +export const FormShapeSchema = z.object({ title: z.string().min(1), body: z.string().min(1), category: z.string(), tags: z.string().array().optional(), assets: z.array(z.string()), }); -export type ThreadMutation = z.infer; +export type FormShape = z.infer; -export function useComposeForm({ draft, editing }: Props) { +export function useComposeForm({ initialDraft, editing }: Props) { const router = useRouter(); const toast = useToast(); const { data } = useCategoryList(); - const formContext = useForm({ - resolver: zodResolver(ThreadMutationSchema), + const formContext = useForm({ + resolver: zodResolver(FormShapeSchema), reValidateMode: "onChange", - defaultValues: draft + defaultValues: initialDraft ? { - title: draft.title, - body: draft.posts[0]?.body, - tags: draft.tags, + title: initialDraft.title, + body: initialDraft.posts[0]?.body, + tags: initialDraft.tags, + assets: initialDraft.assets.map((v) => v.id), } : { // hack: the underlying category list select component can't do this. category: data?.categories[0]?.id, + assets: [], }, }); - function onBack() { - router.back(); - } - - const doSave = async (props: ThreadMutation) => { + const doSave = async (data: FormShape) => { const payload = { - title: props.title, - category: props.category, - body: props.body, - tags: [], + ...data, status: ThreadStatus.draft, - assets: props.assets, }; if (editing) { - await threadUpdate(editing, payload) - .then((thread: ThreadCreateOKResponse) => thread.id) - .catch(errorToast(toast)); + await threadUpdate(editing, payload); } else { - const id = await threadCreate(payload) - .then((thread: ThreadCreateOKResponse) => thread.id) - .catch(errorToast(toast)); + const { id } = await threadCreate(payload); router.push(`/new?id=${id}`); } }; + const doPublish = async ({ title, body, category }: FormShape) => { + if (editing) { + const { slug } = await threadUpdate(editing, { + status: ThreadStatus.published, + }); + router.push(`/t/${slug}`); + } else { + const { slug } = await threadCreate({ + title, + body, + category, + status: ThreadStatus.published, + tags: [], + }); + router.push(`/t/${slug}`); + } + }; + const onAssetUpload = async (asset: Asset) => { - const state: ThreadMutation = formContext.getValues(); + const state: FormShape = formContext.getValues(); + + const newAssets = [...state.assets, asset.id]; - return await doSave({ + const newState = { ...state, - assets: [...state.assets, asset.id], - }); + assets: newAssets, + }; + + await doSave(newState).catch(errorToast(toast)); + + formContext.setValue("assets", newAssets); }; - const onSave = formContext.handleSubmit(doSave); - - const onPublish = formContext.handleSubmit( - async ({ title, body, category }: ThreadMutation) => { - if (editing) { - threadUpdate(editing, { status: ThreadStatus.published }) - .then((thread: ThreadCreateOKResponse) => - router.push(`/t/${thread.slug}`) - ) - .catch(errorToast(toast)); - } else { - await threadCreate({ - title, - body, - category, - status: ThreadStatus.published, - tags: [], - }) - .then((thread: ThreadCreateOKResponse) => - router.push(`/t/${thread.slug}`) - ) - .catch(errorToast(toast)); - } - } + function onBack() { + router.back(); + } + + const onSave = formContext.handleSubmit((data) => + doSave(data).catch(errorToast(toast)) + ); + + const onPublish = formContext.handleSubmit((data) => + doPublish(data).catch(errorToast(toast)) ); return { diff --git a/web/src/screens/compose/components/TitleInput/TitleInput.tsx b/web/src/screens/compose/components/TitleInput/TitleInput.tsx index acd777189..c8eba67fa 100644 --- a/web/src/screens/compose/components/TitleInput/TitleInput.tsx +++ b/web/src/screens/compose/components/TitleInput/TitleInput.tsx @@ -43,7 +43,7 @@ export function TitleInput() { onInput={onInput} {...field} > - {formState.defaultValues?.title} + {formState.defaultValues?.["title"]} ); }} @@ -51,7 +51,7 @@ export function TitleInput() { name="title" /> - {fieldError?.message} + {fieldError?.message?.toString()}