diff --git a/api/resource.go b/api/resource.go index c69a8557..d730fbb1 100644 --- a/api/resource.go +++ b/api/resource.go @@ -9,11 +9,12 @@ type Resource struct { UpdatedTs int64 `json:"updatedTs"` // Domain specific fields - Filename string `json:"filename"` - Blob []byte `json:"-"` - ExternalLink string `json:"externalLink"` - Type string `json:"type"` - Size int64 `json:"size"` + Filename string `json:"filename"` + Blob []byte `json:"-"` + ExternalLink string `json:"externalLink"` + Type string `json:"type"` + Size int64 `json:"size"` + Visibility Visibility `json:"visibility"` // Related fields LinkedMemoAmount int `json:"linkedMemoAmount"` @@ -24,11 +25,12 @@ type ResourceCreate struct { CreatorID int `json:"-"` // Domain specific fields - Filename string `json:"filename"` - Blob []byte `json:"-"` - ExternalLink string `json:"externalLink"` - Type string `json:"type"` - Size int64 `json:"-"` + Filename string `json:"filename"` + Blob []byte `json:"-"` + ExternalLink string `json:"externalLink"` + Type string `json:"type"` + Size int64 `json:"-"` + Visibility Visibility `json:"visibility"` } type ResourceFind struct { @@ -50,7 +52,8 @@ type ResourcePatch struct { UpdatedTs *int64 // Domain specific fields - Filename *string `json:"filename"` + Filename *string `json:"filename"` + Visibility *Visibility `json:"visibility"` } type ResourceDelete struct { diff --git a/plugin/storage/s3/s3.go b/plugin/storage/s3/s3.go index b9f5fd7e..d7b93697 100644 --- a/plugin/storage/s3/s3.go +++ b/plugin/storage/s3/s3.go @@ -35,7 +35,7 @@ func NewClient(ctx context.Context, config *Config) (*Client, error) { }, nil }) - cfg, err := s3config.LoadDefaultConfig(ctx, + awsConfig, err := s3config.LoadDefaultConfig(ctx, s3config.WithEndpointResolverWithOptions(resolver), s3config.WithCredentialsProvider(credentials.NewStaticCredentialsProvider(config.AccessKey, config.SecretKey, "")), ) @@ -43,7 +43,7 @@ func NewClient(ctx context.Context, config *Config) (*Client, error) { return nil, err } - client := awss3.NewFromConfig(cfg) + client := awss3.NewFromConfig(awsConfig) return &Client{ Client: client, @@ -53,7 +53,7 @@ func NewClient(ctx context.Context, config *Config) (*Client, error) { func (client *Client) UploadFile(ctx context.Context, filename string, fileType string, src io.Reader) (string, error) { uploader := manager.NewUploader(client.Client) - resp, err := uploader.Upload(ctx, &awss3.PutObjectInput{ + uploadOutput, err := uploader.Upload(ctx, &awss3.PutObjectInput{ Bucket: aws.String(client.Config.Bucket), Key: aws.String(filename), Body: src, @@ -63,10 +63,12 @@ func (client *Client) UploadFile(ctx context.Context, filename string, fileType if err != nil { return "", err } - var link string - if client.Config.URLPrefix == "" { - link = resp.Location - } else { + + link := uploadOutput.Location + if link == "" { + if client.Config.URLPrefix == "" { + return "", fmt.Errorf("url prefix is empty") + } link = fmt.Sprintf("%s/%s", client.Config.URLPrefix, filename) } return link, nil diff --git a/server/profile/profile.go b/server/profile/profile.go index 0e80194e..1d0541b6 100644 --- a/server/profile/profile.go +++ b/server/profile/profile.go @@ -24,6 +24,10 @@ type Profile struct { Version string `json:"version"` } +func (p *Profile) IsDev() bool { + return p.Mode != "prod" +} + func checkDSN(dataDir string) (string, error) { // Convert to absolute path if relative path is supplied. if !filepath.IsAbs(dataDir) { diff --git a/server/resource.go b/server/resource.go index d816f4f8..593707ef 100644 --- a/server/resource.go +++ b/server/resource.go @@ -42,6 +42,7 @@ func (s *Server) registerResourceRoutes(g *echo.Group) { if resourceCreate.ExternalLink != "" && !strings.HasPrefix(resourceCreate.ExternalLink, "http") { return echo.NewHTTPError(http.StatusBadRequest, "Invalid external link") } + resourceCreate.Visibility = api.Private resource, err := s.Store.CreateResource(ctx, resourceCreate) if err != nil { return echo.NewHTTPError(http.StatusInternalServerError, "Failed to create resource").SetInternal(err) @@ -99,11 +100,12 @@ func (s *Server) registerResourceRoutes(g *echo.Group) { return echo.NewHTTPError(http.StatusInternalServerError, "Failed to read file").SetInternal(err) } resourceCreate = &api.ResourceCreate{ - CreatorID: userID, - Filename: filename, - Type: filetype, - Size: size, - Blob: fileBytes, + CreatorID: userID, + Filename: filename, + Type: filetype, + Size: size, + Blob: fileBytes, + Visibility: api.Private, } } else { storage, err := s.Store.FindStorage(ctx, &api.StorageFind{ID: &storageServiceID}) @@ -134,6 +136,7 @@ func (s *Server) registerResourceRoutes(g *echo.Group) { Filename: filename, Type: filetype, ExternalLink: link, + Visibility: api.Private, } } else { return echo.NewHTTPError(http.StatusInternalServerError, "Unsupported storage type") diff --git a/store/db/migration/dev/LATEST__SCHEMA.sql b/store/db/migration/dev/LATEST__SCHEMA.sql index b2448a1e..64e1bacf 100644 --- a/store/db/migration/dev/LATEST__SCHEMA.sql +++ b/store/db/migration/dev/LATEST__SCHEMA.sql @@ -76,7 +76,8 @@ CREATE TABLE resource ( blob BLOB DEFAULT NULL, external_link TEXT NOT NULL DEFAULT '', type TEXT NOT NULL DEFAULT '', - size INTEGER NOT NULL DEFAULT 0 + size INTEGER NOT NULL DEFAULT 0, + visibility TEXT NOT NULL CHECK (visibility IN ('PUBLIC', 'PROTECTED', 'PRIVATE')) DEFAULT 'PRIVATE' ); -- memo_resource diff --git a/store/resource.go b/store/resource.go index 39986c29..81f2bd38 100644 --- a/store/resource.go +++ b/store/resource.go @@ -27,6 +27,7 @@ type resourceRaw struct { ExternalLink string Type string Size int64 + Visibility api.Visibility } func (raw *resourceRaw) toResource() *api.Resource { @@ -44,6 +45,7 @@ func (raw *resourceRaw) toResource() *api.Resource { ExternalLink: raw.ExternalLink, Type: raw.Type, Size: raw.Size, + Visibility: raw.Visibility, } } @@ -88,7 +90,7 @@ func (s *Store) CreateResource(ctx context.Context, create *api.ResourceCreate) } defer tx.Rollback() - resourceRaw, err := createResource(ctx, tx, create) + resourceRaw, err := s.createResourceImpl(ctx, tx, create) if err != nil { return nil, err } @@ -109,7 +111,7 @@ func (s *Store) FindResourceList(ctx context.Context, find *api.ResourceFind) ([ } defer tx.Rollback() - resourceRawList, err := findResourceList(ctx, tx, find) + resourceRawList, err := s.findResourceListImpl(ctx, tx, find) if err != nil { return nil, err } @@ -129,7 +131,7 @@ func (s *Store) FindResource(ctx context.Context, find *api.ResourceFind) (*api. } defer tx.Rollback() - list, err := findResourceList(ctx, tx, find) + list, err := s.findResourceListImpl(ctx, tx, find) if err != nil { return nil, err } @@ -172,7 +174,7 @@ func (s *Store) PatchResource(ctx context.Context, patch *api.ResourcePatch) (*a } defer tx.Rollback() - resourceRaw, err := patchResource(ctx, tx, patch) + resourceRaw, err := s.patchResourceImpl(ctx, tx, patch) if err != nil { return nil, err } @@ -186,21 +188,25 @@ func (s *Store) PatchResource(ctx context.Context, patch *api.ResourcePatch) (*a return resource, nil } -func createResource(ctx context.Context, tx *sql.Tx, create *api.ResourceCreate) (*resourceRaw, error) { +func (s *Store) createResourceImpl(ctx context.Context, tx *sql.Tx, create *api.ResourceCreate) (*resourceRaw, error) { + fields := []string{"filename", "blob", "external_link", "type", "size", "creator_id"} + values := []interface{}{create.Filename, create.Blob, create.ExternalLink, create.Type, create.Size, create.CreatorID} + placeholders := []string{"?", "?", "?", "?", "?", "?"} + if s.profile.IsDev() { + fields = append(fields, "visibility") + values = append(values, create.Visibility) + placeholders = append(placeholders, "?") + } + query := ` INSERT INTO resource ( - filename, - blob, - external_link, - type, - size, - creator_id + ` + strings.Join(fields, ",") + ` ) - VALUES (?, ?, ?, ?, ?, ?) - RETURNING id, filename, blob, external_link, type, size, creator_id, created_ts, updated_ts + VALUES (` + strings.Join(placeholders, ",") + `) + RETURNING id, ` + strings.Join(fields, ",") + `, created_ts, updated_ts ` var resourceRaw resourceRaw - if err := tx.QueryRowContext(ctx, query, create.Filename, create.Blob, create.ExternalLink, create.Type, create.Size, create.CreatorID).Scan( + dests := []interface{}{ &resourceRaw.ID, &resourceRaw.Filename, &resourceRaw.Blob, @@ -208,16 +214,19 @@ func createResource(ctx context.Context, tx *sql.Tx, create *api.ResourceCreate) &resourceRaw.Type, &resourceRaw.Size, &resourceRaw.CreatorID, - &resourceRaw.CreatedTs, - &resourceRaw.UpdatedTs, - ); err != nil { + } + if s.profile.IsDev() { + dests = append(dests, &resourceRaw.Visibility) + } + dests = append(dests, []interface{}{&resourceRaw.CreatedTs, &resourceRaw.UpdatedTs}...) + if err := tx.QueryRowContext(ctx, query, values...).Scan(dests...); err != nil { return nil, FormatError(err) } return &resourceRaw, nil } -func patchResource(ctx context.Context, tx *sql.Tx, patch *api.ResourcePatch) (*resourceRaw, error) { +func (s *Store) patchResourceImpl(ctx context.Context, tx *sql.Tx, patch *api.ResourcePatch) (*resourceRaw, error) { set, args := []string{}, []interface{}{} if v := patch.UpdatedTs; v != nil { @@ -226,34 +235,46 @@ func patchResource(ctx context.Context, tx *sql.Tx, patch *api.ResourcePatch) (* if v := patch.Filename; v != nil { set, args = append(set, "filename = ?"), append(args, *v) } + if s.profile.IsDev() { + if v := patch.Visibility; v != nil { + set, args = append(set, "visibility = ?"), append(args, *v) + } + } args = append(args, patch.ID) + fields := []string{"id", "filename", "external_link", "type", "size", "creator_id", "created_ts", "updated_ts"} + if s.profile.IsDev() { + fields = append(fields, "visibility") + } + query := ` UPDATE resource SET ` + strings.Join(set, ", ") + ` WHERE id = ? - RETURNING id, filename, blob, external_link, type, size, creator_id, created_ts, updated_ts - ` + RETURNING ` + strings.Join(fields, ", ") var resourceRaw resourceRaw - if err := tx.QueryRowContext(ctx, query, args...).Scan( + dests := []interface{}{ &resourceRaw.ID, &resourceRaw.Filename, - &resourceRaw.Blob, &resourceRaw.ExternalLink, &resourceRaw.Type, &resourceRaw.Size, &resourceRaw.CreatorID, &resourceRaw.CreatedTs, &resourceRaw.UpdatedTs, - ); err != nil { + } + if s.profile.IsDev() { + dests = append(dests, &resourceRaw.Visibility) + } + if err := tx.QueryRowContext(ctx, query, args...).Scan(dests...); err != nil { return nil, FormatError(err) } return &resourceRaw, nil } -func findResourceList(ctx context.Context, tx *sql.Tx, find *api.ResourceFind) ([]*resourceRaw, error) { +func (s *Store) findResourceListImpl(ctx context.Context, tx *sql.Tx, find *api.ResourceFind) ([]*resourceRaw, error) { where, args := []string{"1 = 1"}, []interface{}{} if v := find.ID; v != nil { @@ -273,6 +294,9 @@ func findResourceList(ctx context.Context, tx *sql.Tx, find *api.ResourceFind) ( if find.GetBlob { fields = append(fields, "blob") } + if s.profile.IsDev() { + fields = append(fields, "visibility") + } query := fmt.Sprintf(` SELECT @@ -290,7 +314,7 @@ func findResourceList(ctx context.Context, tx *sql.Tx, find *api.ResourceFind) ( resourceRawList := make([]*resourceRaw, 0) for rows.Next() { var resourceRaw resourceRaw - dest := []interface{}{ + dests := []interface{}{ &resourceRaw.ID, &resourceRaw.Filename, &resourceRaw.ExternalLink, @@ -301,11 +325,12 @@ func findResourceList(ctx context.Context, tx *sql.Tx, find *api.ResourceFind) ( &resourceRaw.UpdatedTs, } if find.GetBlob { - dest = append(dest, &resourceRaw.Blob) + dests = append(dests, &resourceRaw.Blob) } - if err := rows.Scan( - dest..., - ); err != nil { + if s.profile.IsDev() { + dests = append(dests, &resourceRaw.Visibility) + } + if err := rows.Scan(dests...); err != nil { return nil, FormatError(err) }