Skip to content

Commit

Permalink
redesign how assets relate to posts
Browse files Browse the repository at this point in the history
  • Loading branch information
Southclaws committed Jul 15, 2023
1 parent 1f50f52 commit aba1cfb
Show file tree
Hide file tree
Showing 55 changed files with 1,545 additions and 735 deletions.
59 changes: 16 additions & 43 deletions api/openapi.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -576,22 +576,13 @@ 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: |
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.
tags: [assets]
parameters: [$ref: "#/components/parameters/PostIDQueryParam"]
requestBody: { $ref: "#/components/requestBodies/AssetUpload" }
responses:
default: { $ref: "#/components/responses/InternalServerError" }
Expand Down Expand Up @@ -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
Expand Down Expand Up @@ -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:
Expand Down Expand Up @@ -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
Expand All @@ -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: |
Expand Down Expand Up @@ -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
Expand Down Expand Up @@ -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
Expand Down Expand Up @@ -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
Expand All @@ -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:
Expand Down
60 changes: 60 additions & 0 deletions app/resources/asset/db.go
Original file line number Diff line number Diff line change
@@ -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
}
4 changes: 4 additions & 0 deletions app/resources/asset/dto.go
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand All @@ -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,
Expand Down
17 changes: 17 additions & 0 deletions app/resources/asset/repo.go
Original file line number Diff line number Diff line change
@@ -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
}
2 changes: 1 addition & 1 deletion app/resources/post/repo.go
Original file line number Diff line number Diff line change
Expand Up @@ -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...)
}
Expand Down
2 changes: 2 additions & 0 deletions app/resources/resources.go
Original file line number Diff line number Diff line change
Expand Up @@ -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"
Expand All @@ -22,6 +23,7 @@ func Build() fx.Option {
fx.Provide(
settings.New,
account.New,
asset.New,
authentication.New,
category.New,
post.New,
Expand Down
63 changes: 45 additions & 18 deletions app/services/asset/service.go
Original file line number Diff line number Diff line change
@@ -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"
Expand All @@ -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)
}

Expand All @@ -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

Expand All @@ -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,
Expand All @@ -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) {
Expand Down
Loading

0 comments on commit aba1cfb

Please sign in to comment.