Skip to content

Commit

Permalink
feat: upload resource simple
Browse files Browse the repository at this point in the history
  • Loading branch information
Miguel Ramos committed Feb 15, 2021
1 parent c380705 commit 9a4bbac
Show file tree
Hide file tree
Showing 16 changed files with 321 additions and 34 deletions.
2 changes: 1 addition & 1 deletion api/api.go
Original file line number Diff line number Diff line change
Expand Up @@ -42,7 +42,7 @@ func NewPrivateAPI(api *API, router fiber.Router) {

bucketRouter := router.Group("/bucket")
bucketRouter.Post("", api.CreateBucket)
bucketRouter.Post("/upload", api.PutObject)
bucketRouter.Post("/:bucket", api.PutObject)

userRouter := router.Group("/user")
userRouter.Post("", api.CreateUser)
Expand Down
78 changes: 75 additions & 3 deletions api/bucket.go
Original file line number Diff line number Diff line change
@@ -1,11 +1,17 @@
package api

import (
"encoding/json"
"fmt"
"net/url"
"os"
"path"
"strings"

"github.com/barasher/go-exiftool"
"github.com/gobuffalo/pop/nulls"
"github.com/gofiber/fiber/v2"
"github.com/gofrs/uuid"
"github.com/gosimple/slug"
"github.com/minio/minio-go/v7"
"github.com/websublime/barrel/config"
Expand Down Expand Up @@ -79,8 +85,74 @@ func (api *API) CreateBucket(ctx *fiber.Ctx) error {
})
}

// PutObject upload to bucket
func (api *API) PutObject(ctx *fiber.Ctx) error {
return ctx.JSON(fiber.Map{
"data": "object",
})
bucketName := ctx.Params("bucket")
medias := []*models.Media{}
//TODO: user
bucket, err := models.FindBucket(api.db, bucketName)
if err != nil {
return utils.NewException(utils.ErrorBucketMissing, fiber.StatusBadRequest, err.Error())
}

if et, err := exiftool.NewExiftool(); err == nil {
defer et.Close()

if form, err := ctx.MultipartForm(); err == nil {
files := form.File["asset"]

for _, file := range files {
ctx.SaveFile(file, fmt.Sprintf("./temp/%s", file.Filename))
data := et.ExtractMetadata(fmt.Sprintf("./temp/%s", file.Filename))

meta, err := json.Marshal(data[0].Fields)
if err != nil {
return utils.NewException(utils.ErrorResourceMetaFailure, fiber.StatusBadRequest, err.Error())
}

metafile := new(config.MetaFile)
json.Unmarshal([]byte(meta), &metafile)

bucketFile, err := api.store.FPutObject(ctx.Context(), bucketName, metafile.FileName, fmt.Sprintf("./temp/%s", metafile.FileName), minio.PutObjectOptions{
ContentType: metafile.MIMEType,
UserMetadata: map[string]string{},
})

if err != nil {
return utils.NewException(utils.ErrorResourceBucketFailure, fiber.StatusBadRequest, err.Error())
}

u, _ := url.Parse(api.config.BarrelBaseURL)
u.Path = path.Join(u.Path, bucketName, metafile.FileName)
bucketFileJSON, _ := json.Marshal(bucketFile)
metaFileJSON, _ := json.Marshal(metafile)

media, _ := models.NewMedia(u.String(), nulls.NewUUID(uuid.Nil), nulls.NewString(string(bucketFileJSON)), nulls.NewString(string(metaFileJSON)))
bucketMedia, _ := models.NewBucketMedia(bucket.ID, media.ID)

err = api.db.Transaction(func(tx *storage.Connection) error {
terr := tx.Create(media)
terr = tx.Create(bucketMedia)

return terr
})
if err != nil {
return utils.NewException(utils.ErrorResourceModelSave, fiber.StatusBadRequest, err.Error())
}

os.Remove(fmt.Sprintf("./temp/%s", file.Filename))

medias = append(medias, media)
}

return ctx.JSON(fiber.Map{
"data": medias,
})
} else {
return utils.NewException(utils.ErrorResourceInvalidForm, fiber.StatusBadRequest, err.Error())
}
} else {
return utils.NewException(utils.ErrorExifMissing, fiber.StatusBadRequest, err.Error())
}

}
25 changes: 23 additions & 2 deletions api/middleware.go
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@ import (
"github.com/sirupsen/logrus"
"github.com/tidwall/gjson"
"github.com/websublime/barrel/config"
"github.com/websublime/barrel/models"
"github.com/websublime/barrel/utils"
)

Expand Down Expand Up @@ -42,7 +43,7 @@ func (api *API) AuthorizedMiddleware(ctx *fiber.Ctx) error {
}

ctx.Locals("claims", claims)
ctx.Locals("token", auth)
ctx.Locals("token", token)
} else {
return utils.NewException(utils.ErrorOrgStatusForbidden, fiber.StatusForbidden, "Only authorized requests are permitted")
}
Expand All @@ -57,7 +58,14 @@ func (api *API) AdminMiddleware(ctx *fiber.Ctx) error {

headerKey := ctx.Get("X-BARREL-KEY")

if len(headerKey) > 0 && strings.Compare(headerKey, api.config.BarrelAdminKey) == 0 {
identity, err := models.FindIdentityByKey(api.db, headerKey)
if err != nil {
return utils.NewException(utils.ErrorOrgStatusForbidden, fiber.StatusForbidden, err.Error())
}

if identity.IsAdmin {
isAdmin = true
} else if len(headerKey) > 0 && strings.Compare(headerKey, api.config.BarrelAdminKey) == 0 {
isAdmin = true
} else {
claimer := ctx.Locals("claims").(*config.GoTrueClaims)
Expand Down Expand Up @@ -85,14 +93,27 @@ func (api *API) AdminMiddleware(ctx *fiber.Ctx) error {
//CanAccessMiddleware check if user is register to access private endpoints
func (api *API) CanAccessMiddleware(ctx *fiber.Ctx) error {
isAdmin := ctx.Locals("admin").(bool)
token := ctx.Locals("token").(*jwt.Token)
headerKey := ctx.Get("X-BARREL-KEY")

identity, err := models.FindIdentityByKey(api.db, headerKey)
if err != nil {
return utils.NewException(utils.ErrorOrgStatusForbidden, fiber.StatusForbidden, err.Error())
}

if !isAdmin {
user, err := config.UserIsRegister(api.config, headerKey)
if err != nil {
return err
}

if user.Status == "disabled" {
return utils.NewException(utils.ErrorOrgStatusForbidden, fiber.StatusForbidden, "User is disabled")
}
// TODO: we need to save user key/secret on database
client, _ := config.NewClient(api.config, identity.AccessKey, identity.SecretKey, token.Raw)
api.store = client

ctx.Locals("user", user)
}

Expand Down
16 changes: 15 additions & 1 deletion api/minio.go
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,8 @@ package api
import (
"github.com/gofiber/fiber/v2"
"github.com/websublime/barrel/config"
"github.com/websublime/barrel/models"
"github.com/websublime/barrel/storage"
"github.com/websublime/barrel/utils"
)

Expand All @@ -16,7 +18,10 @@ func (api *API) CreateUser(ctx *fiber.Ctx) error {
return utils.NewException(utils.ErrorUserCreation, fiber.StatusForbidden, "Creation permission denied")
}

identity := new(config.Identity)
identity, err := models.NewIdentity("", "", isAdmin)
if err != nil {
return utils.NewException(utils.ErrorOrgUserFailure, fiber.StatusBadRequest, err.Error())
}

if err := ctx.BodyParser(identity); err != nil {
return utils.NewException(utils.ErrorUserBodyParse, fiber.StatusPreconditionFailed, "Invalid request body parser")
Expand All @@ -30,6 +35,15 @@ func (api *API) CreateUser(ctx *fiber.Ctx) error {
return utils.NewException(utils.ErrorOrgUserFailure, fiber.StatusBadRequest, err.Error())
}

err = api.db.Transaction(func(tx *storage.Connection) error {
terr := tx.Create(identity)

return terr
})
if err != nil {
return utils.NewException(utils.ErrorResourceModelSave, fiber.StatusBadRequest, err.Error())
}

return ctx.JSON(fiber.Map{
"data": identity,
})
Expand Down
2 changes: 1 addition & 1 deletion config/minio.go
Original file line number Diff line number Diff line change
Expand Up @@ -39,7 +39,7 @@ func OpenAdminClient(conf *EnvironmentConfig) (*madmin.AdminClient, error) {
// NewClient create a new client connection
func NewClient(conf *EnvironmentConfig, key string, secret string, token string) (*minio.Client, error) {
minioClient, err := minio.New(conf.BarrelMinioURL, &minio.Options{
Creds: credentials.NewStaticV4(key, secret, token),
Creds: credentials.NewStaticV4(key, secret, ""),
Secure: false,
})

Expand Down
21 changes: 5 additions & 16 deletions config/types.go
Original file line number Diff line number Diff line change
Expand Up @@ -14,23 +14,12 @@ type GoTrueClaims struct {
UserMetaData map[string]interface{} `json:"user_metadata"`
}

type Identity struct {
AccessKey string `json:"accessKey,omitempty"`
SecretKey string `json:"secretKey,omitempty"`
}

func (identity *Identity) Validate() *validate.Errors {
return validate.Validate(
&validators.StringIsPresent{Field: identity.AccessKey, Name: "AccessKey", Message: "Identity accessKey is missing"},
&validators.StringIsPresent{Field: identity.SecretKey, Name: "SecretKey", Message: "Identity secretKey is missing"},
)
}

type GoTrueIdentity struct {
Identity
ID uuid.UUID `json:"id,omitempty"`
UserID uuid.UUID `json:"userID,omitempty"`
Token string `json:"token,omitempty"`
AccessKey string `json:"accessKey,omitempty"`
SecretKey string `json:"secretKey,omitempty"`
ID uuid.UUID `json:"id,omitempty"`
UserID uuid.UUID `json:"userID,omitempty"`
Token string `json:"token,omitempty"`
}

type CannedPolicy struct {
Expand Down
13 changes: 13 additions & 0 deletions database/00-gotrue-schema.sql
Original file line number Diff line number Diff line change
Expand Up @@ -84,6 +84,18 @@ CREATE TABLE auth.identities (
user_id uuid NOT NULL,
CONSTRAINT identities_pkey PRIMARY KEY (id)
);
-- auth.templates definition
CREATE TABLE auth.templates
(
id uuid NOT NULL,
aud varchar(255),
type varchar(50),
subject text,
url text DEFAULT '/',
base_url varchar(255),
url_template text,
CONSTRAINT templates_pkey PRIMARY KEY (id)
);
-- Gets the User ID from the request cookie
create or replace function auth.uid() returns uuid as $$
select nullif(current_setting('request.jwt.claim.sub', true), '')::uuid;
Expand All @@ -92,6 +104,7 @@ $$ language sql stable;
create or replace function auth.role() returns text as $$
select nullif(current_setting('request.jwt.claim.role', true), '')::text;
$$ language sql stable;

GRANT ALL PRIVILEGES ON SCHEMA auth TO postgres;
GRANT ALL PRIVILEGES ON ALL TABLES IN SCHEMA auth TO postgres;
GRANT ALL PRIVILEGES ON ALL SEQUENCES IN SCHEMA auth TO postgres;
Expand Down
9 changes: 8 additions & 1 deletion database/01-storage-schema.sql
Original file line number Diff line number Diff line change
Expand Up @@ -35,5 +35,12 @@ CREATE TABLE "barrel".bucket_media (
);

ALTER TABLE "barrel".bucket_media ADD CONSTRAINT fk_bucket_media_bucket FOREIGN KEY ( bucket_id ) REFERENCES "barrel".buckets( id );

ALTER TABLE "barrel".bucket_media ADD CONSTRAINT fk_bucket_media_media FOREIGN KEY ( media_id ) REFERENCES "barrel".medias( id );

CREATE TABLE "barrel".identities (
id uuid NOT NULL ,
"secret" text NOT NULL ,
"key" text NOT NULL ,
is_admin boolean DEFAULT false ,
CONSTRAINT pk_users_id PRIMARY KEY (id)
);
1 change: 1 addition & 0 deletions go.mod
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@ module github.com/websublime/barrel
go 1.15

require (
github.com/barasher/go-exiftool v1.3.2
github.com/dgrijalva/jwt-go v3.2.0+incompatible
github.com/gobuffalo/pop v4.13.1+incompatible
github.com/gobuffalo/pop/v5 v5.3.3
Expand Down
2 changes: 2 additions & 0 deletions go.sum
Original file line number Diff line number Diff line change
Expand Up @@ -72,6 +72,8 @@ github.com/aws/aws-lambda-go v1.13.3/go.mod h1:4UKl9IzQMoD+QF79YdCuzCwp8VbmG4VAQ
github.com/aws/aws-sdk-go v1.27.0/go.mod h1:KmX6BPdI08NWTb3/sm4ZGu5ShLoqVDhKgpiN924inxo=
github.com/aws/aws-sdk-go v1.35.20/go.mod h1:tlPOdRjfxPBpNIwqDj61rmsnA85v9jc0Ps9+muhnW+k=
github.com/aws/aws-sdk-go-v2 v0.18.0/go.mod h1:JWVYvqSMppoMJC0x5wdwiImzgXTI9FuZwxzkQq9wy+g=
github.com/barasher/go-exiftool v1.3.2 h1:yWUIGOsM6PLbbHxe84ASTo/eyORMTyMH/5Qv1yBcC7s=
github.com/barasher/go-exiftool v1.3.2/go.mod h1:F9s/a3uHSM8YniVfwF+sbQUtP8Gmh9nyzigNF+8vsWo=
github.com/bcicen/jstream v1.0.1 h1:BXY7Cu4rdmc0rhyTVyT3UkxAiX3bnLpKLas9btbH5ck=
github.com/bcicen/jstream v1.0.1/go.mod h1:9ielPxqFry7Y4Tg3j4BfjPocfJ3TbsRtXOAYXYmRuAQ=
github.com/beevik/ntp v0.3.0 h1:xzVrPrE4ziasFXgBVBZJDP0Wg/KpMwk2KHJ4Ba8GrDw=
Expand Down
19 changes: 10 additions & 9 deletions models/bucket.go
Original file line number Diff line number Diff line change
Expand Up @@ -16,15 +16,16 @@ import (

// Bucket model type
type Bucket struct {
ID uuid.UUID `json:"id" db:"id"`
Name string `json:"name" db:"name"`
Bucket nulls.String `json:"bucket,omitempty" db:"bucket"`
OrgID nulls.String `json:"orgId,omitempty" db:"org_id"`
IsPrivate bool `json:"isPrivate" db:"is_private"`
Policy string `json:"policy" db:"-"`
CreatedAt time.Time `json:"createdAt" db:"created_at"`
UpdatedAt time.Time `json:"updatedAt" db:"updated_at"`
DeletedAt nulls.Time `json:"deleteAt,omitempty" db:"deleted_at"`
ID uuid.UUID `json:"id" db:"id"`
Name string `json:"name" db:"name"`
Bucket nulls.String `json:"bucket,omitempty" db:"bucket"`
OrgID nulls.String `json:"orgId,omitempty" db:"org_id"`
IsPrivate bool `json:"isPrivate" db:"is_private"`
Policy string `json:"policy" db:"-"`
CreatedAt time.Time `json:"createdAt" db:"created_at"`
UpdatedAt time.Time `json:"updatedAt" db:"updated_at"`
DeletedAt nulls.Time `json:"deleteAt,omitempty" db:"deleted_at"`
BucketMedia []*BucketMedia `json:"medias,omitempty" many_to_many:"bucket_media" db:"-" fk_id:"bucket_id"`
}

// NewBucket creates new Bucket
Expand Down
38 changes: 38 additions & 0 deletions models/bucket_media.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,38 @@
package models

import (
"github.com/gobuffalo/uuid"
"github.com/pkg/errors"
"github.com/websublime/barrel/storage/namespace"
)

type BucketMedia struct {
ID uuid.UUID `json:"id" db:"id" primary_id:"id"`
BucketID uuid.UUID `json:"bucketId" db:"bucket_id"`
MediaID uuid.UUID `json:"mediaId" db:"media_id"`
}

func (BucketMedia) TableName() string {
tableName := "bucket_media"

if namespace.GetNamespace() != "" {
return namespace.GetNamespace() + "." + tableName
}

return tableName
}

func NewBucketMedia(bucket uuid.UUID, media uuid.UUID) (*BucketMedia, error) {
uid, err := uuid.NewV4()
if err != nil {
return nil, errors.Wrap(err, "Error generating unique id")
}

bucketMedia := &BucketMedia{
ID: uid,
BucketID: bucket,
MediaID: media,
}

return bucketMedia, nil
}
Loading

0 comments on commit 9a4bbac

Please sign in to comment.