feat: add user-defined id to resource

This commit is contained in:
Steven 2024-01-21 10:49:30 +08:00
parent 40bd75c725
commit 582cc6609c
12 changed files with 100 additions and 106 deletions

View File

@ -15,6 +15,7 @@ import (
"time"
"github.com/labstack/echo/v4"
"github.com/lithammer/shortuuid/v4"
"github.com/pkg/errors"
"go.uber.org/zap"
@ -139,6 +140,7 @@ func (s *APIV1Service) CreateResource(c echo.Context) error {
}
create := &store.Resource{
ResourceName: shortuuid.New(),
CreatorID: userID,
Filename: request.Filename,
ExternalLink: request.ExternalLink,
@ -215,6 +217,7 @@ func (s *APIV1Service) UploadResource(c echo.Context) error {
defer sourceFile.Close()
create := &store.Resource{
ResourceName: shortuuid.New(),
CreatorID: userID,
Filename: file.Filename,
Type: file.Header.Get("Content-Type"),

View File

@ -5,6 +5,7 @@ import (
"net/url"
"time"
"github.com/lithammer/shortuuid/v4"
"google.golang.org/grpc/codes"
"google.golang.org/grpc/status"
"google.golang.org/protobuf/types/known/timestamppb"
@ -30,6 +31,7 @@ func (s *APIV2Service) CreateResource(ctx context.Context, request *apiv2pb.Crea
}
create := &store.Resource{
ResourceName: shortuuid.New(),
CreatorID: user.ID,
Filename: request.Filename,
ExternalLink: request.ExternalLink,

View File

@ -86,6 +86,7 @@ func (t *TelegramHandler) MessageHandle(ctx context.Context, bot *telegram.Bot,
for _, attachment := range attachments {
// Fill the common field of create
create := store.Resource{
ResourceName: shortuuid.New(),
CreatorID: creatorID,
Filename: filepath.Base(attachment.FileName),
Type: attachment.GetMimeType(),

View File

@ -64,6 +64,7 @@ CREATE TABLE `memo_relation` (
-- resource
CREATE TABLE `resource` (
`id` INT NOT NULL AUTO_INCREMENT PRIMARY KEY,
`resource_name` VARCHAR(256) NOT NULL UNIQUE,
`creator_id` INT NOT NULL,
`created_ts` TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP,
`updated_ts` TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP,

View File

@ -6,15 +6,13 @@ import (
"fmt"
"strings"
"github.com/pkg/errors"
"github.com/usememos/memos/store"
)
func (d *DB) CreateResource(ctx context.Context, create *store.Resource) (*store.Resource, error) {
fields := []string{"`filename`", "`blob`", "`external_link`", "`type`", "`size`", "`creator_id`", "`internal_path`", "`memo_id`"}
placeholder := []string{"?", "?", "?", "?", "?", "?", "?", "?"}
args := []any{create.Filename, create.Blob, create.ExternalLink, create.Type, create.Size, create.CreatorID, create.InternalPath, create.MemoID}
fields := []string{"`resource_name`", "`filename`", "`blob`", "`external_link`", "`type`", "`size`", "`creator_id`", "`internal_path`", "`memo_id`"}
placeholder := []string{"?", "?", "?", "?", "?", "?", "?", "?", "?"}
args := []any{create.ResourceName, create.Filename, create.Blob, create.ExternalLink, create.Type, create.Size, create.CreatorID, create.InternalPath, create.MemoID}
stmt := "INSERT INTO `resource` (" + strings.Join(fields, ", ") + ") VALUES (" + strings.Join(placeholder, ", ") + ")"
result, err := d.db.ExecContext(ctx, stmt, args...)
@ -28,15 +26,7 @@ func (d *DB) CreateResource(ctx context.Context, create *store.Resource) (*store
}
id32 := int32(id)
list, err := d.ListResources(ctx, &store.FindResource{ID: &id32})
if err != nil {
return nil, err
}
if len(list) != 1 {
return nil, errors.Wrapf(nil, "unexpected resource count: %d", len(list))
}
return list[0], nil
return d.GetResource(ctx, &store.FindResource{ID: &id32})
}
func (d *DB) ListResources(ctx context.Context, find *store.FindResource) ([]*store.Resource, error) {
@ -45,6 +35,9 @@ func (d *DB) ListResources(ctx context.Context, find *store.FindResource) ([]*st
if v := find.ID; v != nil {
where, args = append(where, "`id` = ?"), append(args, *v)
}
if v := find.ResourceName; v != nil {
where, args = append(where, "`resource_name` = ?"), append(args, *v)
}
if v := find.CreatorID; v != nil {
where, args = append(where, "`creator_id` = ?"), append(args, *v)
}
@ -58,7 +51,7 @@ func (d *DB) ListResources(ctx context.Context, find *store.FindResource) ([]*st
where = append(where, "`memo_id` IS NOT NULL")
}
fields := []string{"`id`", "`filename`", "`external_link`", "`type`", "`size`", "`creator_id`", "UNIX_TIMESTAMP(`created_ts`)", "UNIX_TIMESTAMP(`updated_ts`)", "`internal_path`", "`memo_id`"}
fields := []string{"`id`", "`resource_name`", "`filename`", "`external_link`", "`type`", "`size`", "`creator_id`", "UNIX_TIMESTAMP(`created_ts`)", "UNIX_TIMESTAMP(`updated_ts`)", "`internal_path`", "`memo_id`"}
if find.GetBlob {
fields = append(fields, "`blob`")
}
@ -83,6 +76,7 @@ func (d *DB) ListResources(ctx context.Context, find *store.FindResource) ([]*st
var memoID sql.NullInt32
dests := []any{
&resource.ID,
&resource.ResourceName,
&resource.Filename,
&resource.ExternalLink,
&resource.Type,
@ -112,9 +106,24 @@ func (d *DB) ListResources(ctx context.Context, find *store.FindResource) ([]*st
return list, nil
}
func (d *DB) GetResource(ctx context.Context, find *store.FindResource) (*store.Resource, error) {
list, err := d.ListResources(ctx, find)
if err != nil {
return nil, err
}
if len(list) == 0 {
return nil, nil
}
return list[0], nil
}
func (d *DB) UpdateResource(ctx context.Context, update *store.UpdateResource) (*store.Resource, error) {
set, args := []string{}, []any{}
if v := update.ResourceName; v != nil {
set, args = append(set, "`resource_name` = ?"), append(args, *v)
}
if v := update.UpdatedTs; v != nil {
set, args = append(set, "`updated_ts` = FROM_UNIXTIME(?)"), append(args, *v)
}
@ -137,15 +146,7 @@ func (d *DB) UpdateResource(ctx context.Context, update *store.UpdateResource) (
return nil, err
}
list, err := d.ListResources(ctx, &store.FindResource{ID: &update.ID})
if err != nil {
return nil, err
}
if len(list) != 1 {
return nil, errors.Wrapf(nil, "unexpected resource count: %d", len(list))
}
return list[0], nil
return d.GetResource(ctx, &store.FindResource{ID: &update.ID})
}
func (d *DB) DeleteResource(ctx context.Context, delete *store.DeleteResource) error {

View File

@ -64,6 +64,7 @@ CREATE TABLE memo_relation (
-- resource
CREATE TABLE resource (
id SERIAL PRIMARY KEY,
resource_name TEXT NOT NULL UNIQUE,
creator_id INTEGER NOT NULL,
created_ts BIGINT NOT NULL DEFAULT EXTRACT(EPOCH FROM NOW()),
updated_ts BIGINT NOT NULL DEFAULT EXTRACT(EPOCH FROM NOW()),

View File

@ -10,8 +10,8 @@ import (
)
func (d *DB) CreateResource(ctx context.Context, create *store.Resource) (*store.Resource, error) {
fields := []string{"filename", "blob", "external_link", "type", "size", "creator_id", "internal_path", "memo_id"}
args := []any{create.Filename, create.Blob, create.ExternalLink, create.Type, create.Size, create.CreatorID, create.InternalPath, create.MemoID}
fields := []string{"resource_name", "filename", "blob", "external_link", "type", "size", "creator_id", "internal_path", "memo_id"}
args := []any{create.ResourceName, create.Filename, create.Blob, create.ExternalLink, create.Type, create.Size, create.CreatorID, create.InternalPath, create.MemoID}
stmt := "INSERT INTO resource (" + strings.Join(fields, ", ") + ") VALUES (" + placeholders(len(args)) + ") RETURNING id, created_ts, updated_ts"
if err := d.db.QueryRowContext(ctx, stmt, args...).Scan(&create.ID, &create.CreatedTs, &create.UpdatedTs); err != nil {
@ -26,6 +26,9 @@ func (d *DB) ListResources(ctx context.Context, find *store.FindResource) ([]*st
if v := find.ID; v != nil {
where, args = append(where, "id = "+placeholder(len(args)+1)), append(args, *v)
}
if v := find.ResourceName; v != nil {
where, args = append(where, "resource_name = "+placeholder(len(args)+1)), append(args, *v)
}
if v := find.CreatorID; v != nil {
where, args = append(where, "creator_id = "+placeholder(len(args)+1)), append(args, *v)
}
@ -39,7 +42,7 @@ func (d *DB) ListResources(ctx context.Context, find *store.FindResource) ([]*st
where = append(where, "memo_id IS NOT NULL")
}
fields := []string{"id", "filename", "external_link", "type", "size", "creator_id", "created_ts", "updated_ts", "internal_path", "memo_id"}
fields := []string{"id", "resource_name", "filename", "external_link", "type", "size", "creator_id", "created_ts", "updated_ts", "internal_path", "memo_id"}
if find.GetBlob {
fields = append(fields, "blob")
}
@ -70,6 +73,7 @@ func (d *DB) ListResources(ctx context.Context, find *store.FindResource) ([]*st
var memoID sql.NullInt32
dests := []any{
&resource.ID,
&resource.ResourceName,
&resource.Filename,
&resource.ExternalLink,
&resource.Type,
@ -102,6 +106,9 @@ func (d *DB) ListResources(ctx context.Context, find *store.FindResource) ([]*st
func (d *DB) UpdateResource(ctx context.Context, update *store.UpdateResource) (*store.Resource, error) {
set, args := []string{}, []any{}
if v := update.ResourceName; v != nil {
set, args = append(set, "resource_name = "+placeholder(len(args)+1)), append(args, *v)
}
if v := update.UpdatedTs; v != nil {
set, args = append(set, "updated_ts = "+placeholder(len(args)+1)), append(args, *v)
}
@ -118,16 +125,13 @@ func (d *DB) UpdateResource(ctx context.Context, update *store.UpdateResource) (
set, args = append(set, "blob = "+placeholder(len(args)+1)), append(args, v)
}
fields := []string{"id", "filename", "external_link", "type", "size", "creator_id", "created_ts", "updated_ts", "internal_path"}
stmt := `
UPDATE resource
SET ` + strings.Join(set, ", ") + `
WHERE id = ` + placeholder(len(args)+1) + `
RETURNING ` + strings.Join(fields, ", ")
fields := []string{"id", "resource_name", "filename", "external_link", "type", "size", "creator_id", "created_ts", "updated_ts", "internal_path"}
stmt := `UPDATE resource SET ` + strings.Join(set, ", ") + ` WHERE id = ` + placeholder(len(args)+1) + ` RETURNING ` + strings.Join(fields, ", ")
args = append(args, update.ID)
resource := store.Resource{}
dests := []any{
&resource.ID,
&resource.ResourceName,
&resource.Filename,
&resource.ExternalLink,
&resource.Type,
@ -157,11 +161,7 @@ func (d *DB) DeleteResource(ctx context.Context, delete *store.DeleteResource) e
}
func vacuumResource(ctx context.Context, tx *sql.Tx) error {
stmt := `
DELETE FROM
resource
WHERE
creator_id NOT IN (SELECT id FROM "user")`
stmt := `DELETE FROM resource WHERE creator_id NOT IN (SELECT id FROM "user")`
_, err := tx.ExecContext(ctx, stmt)
if err != nil {
return err

View File

@ -1,19 +1,3 @@
-- drop all tables first
DROP TABLE IF EXISTS migration_history;
DROP TABLE IF EXISTS system_setting;
DROP TABLE IF EXISTS user;
DROP TABLE IF EXISTS user_setting;
DROP TABLE IF EXISTS memo;
DROP TABLE IF EXISTS memo_organizer;
DROP TABLE IF EXISTS memo_relation;
DROP TABLE IF EXISTS resource;
DROP TABLE IF EXISTS tag;
DROP TABLE IF EXISTS activity;
DROP TABLE IF EXISTS storage;
DROP TABLE IF EXISTS idp;
DROP TABLE IF EXISTS inbox;
DROP TABLE IF EXISTS webhook;
-- migration_history
CREATE TABLE migration_history (
version TEXT NOT NULL PRIMARY KEY,
@ -87,6 +71,7 @@ CREATE TABLE memo_relation (
-- resource
CREATE TABLE resource (
id INTEGER PRIMARY KEY AUTOINCREMENT,
resource_name TEXT NOT NULL UNIQUE,
creator_id INTEGER NOT NULL,
created_ts BIGINT NOT NULL DEFAULT (strftime('%s', 'now')),
updated_ts BIGINT NOT NULL DEFAULT (strftime('%s', 'now')),

View File

@ -10,9 +10,9 @@ import (
)
func (d *DB) CreateResource(ctx context.Context, create *store.Resource) (*store.Resource, error) {
fields := []string{"`filename`", "`blob`", "`external_link`", "`type`", "`size`", "`creator_id`", "`internal_path`", "`memo_id`"}
placeholder := []string{"?", "?", "?", "?", "?", "?", "?", "?"}
args := []any{create.Filename, create.Blob, create.ExternalLink, create.Type, create.Size, create.CreatorID, create.InternalPath, create.MemoID}
fields := []string{"`resource_name`", "`filename`", "`blob`", "`external_link`", "`type`", "`size`", "`creator_id`", "`internal_path`", "`memo_id`"}
placeholder := []string{"?", "?", "?", "?", "?", "?", "?", "?", "?"}
args := []any{create.ResourceName, create.Filename, create.Blob, create.ExternalLink, create.Type, create.Size, create.CreatorID, create.InternalPath, create.MemoID}
stmt := "INSERT INTO `resource` (" + strings.Join(fields, ", ") + ") VALUES (" + strings.Join(placeholder, ", ") + ") RETURNING `id`, `created_ts`, `updated_ts`"
if err := d.db.QueryRowContext(ctx, stmt, args...).Scan(&create.ID, &create.CreatedTs, &create.UpdatedTs); err != nil {
@ -26,33 +26,30 @@ func (d *DB) ListResources(ctx context.Context, find *store.FindResource) ([]*st
where, args := []string{"1 = 1"}, []any{}
if v := find.ID; v != nil {
where, args = append(where, "id = ?"), append(args, *v)
where, args = append(where, "`id` = ?"), append(args, *v)
}
if v := find.ResourceName; v != nil {
where, args = append(where, "`resource_name` = ?"), append(args, *v)
}
if v := find.CreatorID; v != nil {
where, args = append(where, "creator_id = ?"), append(args, *v)
where, args = append(where, "`creator_id` = ?"), append(args, *v)
}
if v := find.Filename; v != nil {
where, args = append(where, "filename = ?"), append(args, *v)
where, args = append(where, "`filename` = ?"), append(args, *v)
}
if v := find.MemoID; v != nil {
where, args = append(where, "memo_id = ?"), append(args, *v)
where, args = append(where, "`memo_id` = ?"), append(args, *v)
}
if find.HasRelatedMemo {
where = append(where, "memo_id IS NOT NULL")
where = append(where, "`memo_id` IS NOT NULL")
}
fields := []string{"id", "filename", "external_link", "type", "size", "creator_id", "created_ts", "updated_ts", "internal_path", "memo_id"}
fields := []string{"`id`", "`resource_name`", "`filename`", "`external_link`", "`type`", "`size`", "`creator_id`", "`created_ts`", "`updated_ts`", "`internal_path`", "`memo_id`"}
if find.GetBlob {
fields = append(fields, "blob")
fields = append(fields, "`blob`")
}
query := fmt.Sprintf(`
SELECT
%s
FROM resource
WHERE %s
ORDER BY updated_ts DESC, created_ts DESC
`, strings.Join(fields, ", "), strings.Join(where, " AND "))
query := fmt.Sprintf("SELECT %s FROM `resource` WHERE %s ORDER BY `updated_ts` DESC, `created_ts` DESC", strings.Join(fields, ", "), strings.Join(where, " AND "))
if find.Limit != nil {
query = fmt.Sprintf("%s LIMIT %d", query, *find.Limit)
if find.Offset != nil {
@ -72,6 +69,7 @@ func (d *DB) ListResources(ctx context.Context, find *store.FindResource) ([]*st
var memoID sql.NullInt32
dests := []any{
&resource.ID,
&resource.ResourceName,
&resource.Filename,
&resource.ExternalLink,
&resource.Type,
@ -104,32 +102,32 @@ func (d *DB) ListResources(ctx context.Context, find *store.FindResource) ([]*st
func (d *DB) UpdateResource(ctx context.Context, update *store.UpdateResource) (*store.Resource, error) {
set, args := []string{}, []any{}
if v := update.ResourceName; v != nil {
set, args = append(set, "`resource_name` = ?"), append(args, *v)
}
if v := update.UpdatedTs; v != nil {
set, args = append(set, "updated_ts = ?"), append(args, *v)
set, args = append(set, "`updated_ts` = ?"), append(args, *v)
}
if v := update.Filename; v != nil {
set, args = append(set, "filename = ?"), append(args, *v)
set, args = append(set, "`filename` = ?"), append(args, *v)
}
if v := update.InternalPath; v != nil {
set, args = append(set, "internal_path = ?"), append(args, *v)
set, args = append(set, "`internal_path` = ?"), append(args, *v)
}
if v := update.MemoID; v != nil {
set, args = append(set, "memo_id = ?"), append(args, *v)
set, args = append(set, "`memo_id` = ?"), append(args, *v)
}
if v := update.Blob; v != nil {
set, args = append(set, "blob = ?"), append(args, v)
set, args = append(set, "`blob` = ?"), append(args, v)
}
args = append(args, update.ID)
fields := []string{"id", "filename", "external_link", "type", "size", "creator_id", "created_ts", "updated_ts", "internal_path"}
stmt := `
UPDATE resource
SET ` + strings.Join(set, ", ") + `
WHERE id = ?
RETURNING ` + strings.Join(fields, ", ")
fields := []string{"`id`", "`resource_name`", "`filename`", "`external_link`", "`type`", "`size`", "`creator_id`", "`created_ts`", "`updated_ts`", "`internal_path`"}
stmt := "UPDATE `resource` SET " + strings.Join(set, ", ") + " WHERE `id` = ? RETURNING " + strings.Join(fields, ", ")
resource := store.Resource{}
dests := []any{
&resource.ID,
&resource.ResourceName,
&resource.Filename,
&resource.ExternalLink,
&resource.Type,
@ -147,10 +145,7 @@ func (d *DB) UpdateResource(ctx context.Context, update *store.UpdateResource) (
}
func (d *DB) DeleteResource(ctx context.Context, delete *store.DeleteResource) error {
stmt := `
DELETE FROM resource
WHERE id = ?
`
stmt := "DELETE FROM `resource` WHERE `id` = ?"
result, err := d.db.ExecContext(ctx, stmt, delete.ID)
if err != nil {
return err
@ -168,16 +163,7 @@ func (d *DB) DeleteResource(ctx context.Context, delete *store.DeleteResource) e
}
func vacuumResource(ctx context.Context, tx *sql.Tx) error {
stmt := `
DELETE FROM
resource
WHERE
creator_id NOT IN (
SELECT
id
FROM
user
)`
stmt := "DELETE FROM `resource` WHERE `creator_id` NOT IN (SELECT `id` FROM `user`)"
_, err := tx.ExecContext(ctx, stmt)
if err != nil {
return err

View File

@ -18,6 +18,7 @@ const (
type Resource struct {
ID int32
ResourceName string
// Standard fields
CreatorID int32
@ -37,6 +38,7 @@ type Resource struct {
type FindResource struct {
GetBlob bool
ID *int32
ResourceName *string
CreatorID *int32
Filename *string
MemoID *int32
@ -47,6 +49,7 @@ type FindResource struct {
type UpdateResource struct {
ID int32
ResourceName *string
UpdatedTs *int64
Filename *string
InternalPath *string
@ -60,6 +63,9 @@ type DeleteResource struct {
}
func (s *Store) CreateResource(ctx context.Context, create *Resource) (*Resource, error) {
if !util.ResourceNameMatcher.MatchString(create.ResourceName) {
return nil, errors.New("invalid resource name")
}
return s.driver.CreateResource(ctx, create)
}
@ -81,6 +87,9 @@ func (s *Store) GetResource(ctx context.Context, find *FindResource) (*Resource,
}
func (s *Store) UpdateResource(ctx context.Context, update *UpdateResource) (*Resource, error) {
if update.ResourceName != nil && !util.ResourceNameMatcher.MatchString(*update.ResourceName) {
return nil, errors.New("invalid resource name")
}
return s.driver.UpdateResource(ctx, update)
}
@ -101,6 +110,7 @@ func (s *Store) DeleteResource(ctx context.Context, delete *DeleteResource) erro
}
_ = os.Remove(resourcePath)
}
// Delete the thumbnail.
if util.HasPrefixes(resource.Type, "image/png", "image/jpeg") {
ext := filepath.Ext(resource.Filename)

View File

@ -4,6 +4,7 @@ import (
"context"
"testing"
"github.com/lithammer/shortuuid/v4"
"github.com/stretchr/testify/require"
"github.com/usememos/memos/store"
@ -33,6 +34,7 @@ func TestMigrateResourceInternalPath(t *testing.T) {
for _, testCase := range testCases {
for input, expectedOutput := range testCase {
resourceCreate := &store.Resource{
ResourceName: shortuuid.New(),
CreatorID: user.ID,
InternalPath: input,
}

View File

@ -4,6 +4,7 @@ import (
"context"
"testing"
"github.com/lithammer/shortuuid/v4"
"github.com/stretchr/testify/require"
"github.com/usememos/memos/store"
@ -13,6 +14,7 @@ func TestResourceStore(t *testing.T) {
ctx := context.Background()
ts := NewTestingStore(ctx, t)
_, err := ts.CreateResource(ctx, &store.Resource{
ResourceName: shortuuid.New(),
CreatorID: 101,
Filename: "test.epub",
Blob: []byte("test"),