diff --git a/server/version/version.go b/server/version/version.go index 2e81fc47..92a9c0b8 100644 --- a/server/version/version.go +++ b/server/version/version.go @@ -9,10 +9,10 @@ import ( // Version is the service current released version. // Semantic versioning: https://semver.org/ -var Version = "0.16.1" +var Version = "0.17.0" // DevVersion is the service current development version. -var DevVersion = "0.16.1" +var DevVersion = "0.17.0" func GetCurrentVersion(mode string) string { if mode == "dev" || mode == "demo" { diff --git a/store/db/mysql/inbox.go b/store/db/mysql/inbox.go index 10be4278..ad80e0a8 100644 --- a/store/db/mysql/inbox.go +++ b/store/db/mysql/inbox.go @@ -2,26 +2,133 @@ package mysql import ( "context" + "strings" + "github.com/pkg/errors" + "google.golang.org/protobuf/encoding/protojson" + + storepb "github.com/usememos/memos/proto/gen/store" "github.com/usememos/memos/store" ) -// nolint func (d *DB) CreateInbox(ctx context.Context, create *store.Inbox) (*store.Inbox, error) { - return nil, nil + messageString := "{}" + if create.Message != nil { + bytes, err := protojson.Marshal(create.Message) + if err != nil { + return nil, errors.Wrap(err, "failed to marshal inbox message") + } + messageString = string(bytes) + } + + fields := []string{"`sender_id`", "`receiver_id`", "`status`", "`message`"} + placeholder := []string{"?", "?", "?", "?"} + args := []any{create.SenderID, create.ReceiverID, create.Status, messageString} + + stmt := "INSERT INTO `inbox` (" + strings.Join(fields, ", ") + ") VALUES (" + strings.Join(placeholder, ", ") + ")" + result, err := d.db.ExecContext(ctx, stmt, args...) + if err != nil { + return nil, err + } + + id, err := result.LastInsertId() + if err != nil { + return nil, err + } + + id32 := int32(id) + inbox, err := d.GetInbox(ctx, &store.FindInbox{ID: &id32}) + if err != nil { + return nil, err + } + return inbox, nil } -// nolint func (d *DB) ListInboxes(ctx context.Context, find *store.FindInbox) ([]*store.Inbox, error) { - return nil, nil + where, args := []string{"1 = 1"}, []any{} + + if find.ID != nil { + where, args = append(where, "`id` = ?"), append(args, *find.ID) + } + if find.SenderID != nil { + where, args = append(where, "`sender_id` = ?"), append(args, *find.SenderID) + } + if find.ReceiverID != nil { + where, args = append(where, "`receiver_id` = ?"), append(args, *find.ReceiverID) + } + if find.Status != nil { + where, args = append(where, "`status` = ?"), append(args, *find.Status) + } + + query := "SELECT `id`, UNIX_TIMESTAMP(`created_ts`), `sender_id`, `receiver_id`, `status`, `message` FROM `inbox` WHERE " + strings.Join(where, " AND ") + " ORDER BY `created_ts` DESC" + rows, err := d.db.QueryContext(ctx, query, args...) + if err != nil { + return nil, err + } + defer rows.Close() + + list := []*store.Inbox{} + for rows.Next() { + inbox := &store.Inbox{} + var messageBytes []byte + if err := rows.Scan( + &inbox.ID, + &inbox.CreatedTs, + &inbox.SenderID, + &inbox.ReceiverID, + &inbox.Status, + &messageBytes, + ); err != nil { + return nil, err + } + + message := &storepb.InboxMessage{} + if err := protojsonUnmarshaler.Unmarshal(messageBytes, message); err != nil { + return nil, err + } + inbox.Message = message + list = append(list, inbox) + } + + if err := rows.Err(); err != nil { + return nil, err + } + + return list, nil +} + +func (d *DB) GetInbox(ctx context.Context, find *store.FindInbox) (*store.Inbox, error) { + list, err := d.ListInboxes(ctx, find) + if err != nil { + return nil, errors.Wrap(err, "failed to get inbox") + } + if len(list) != 1 { + return nil, errors.Wrapf(nil, "unexpected inbox count: %d", len(list)) + } + return list[0], nil } -// nolint func (d *DB) UpdateInbox(ctx context.Context, update *store.UpdateInbox) (*store.Inbox, error) { - return nil, nil + set, args := []string{"`status` = ?"}, []any{update.Status.String()} + args = append(args, update.ID) + query := "UPDATE `inbox` SET " + strings.Join(set, ", ") + " WHERE `id` = ?" + if _, err := d.db.ExecContext(ctx, query, args...); err != nil { + return nil, errors.Wrap(err, "failed to update inbox") + } + inbox, err := d.GetInbox(ctx, &store.FindInbox{ID: &update.ID}) + if err != nil { + return nil, err + } + return inbox, nil } -// nolint func (d *DB) DeleteInbox(ctx context.Context, delete *store.DeleteInbox) error { + result, err := d.db.ExecContext(ctx, "DELETE FROM `inbox` WHERE `id` = ?", delete.ID) + if err != nil { + return errors.Wrap(err, "failed to delete inbox") + } + if _, err := result.RowsAffected(); err != nil { + return err + } return nil } diff --git a/store/db/mysql/migration/prod/0.17/00__inbox.sql b/store/db/mysql/migration/prod/0.17/00__inbox.sql new file mode 100644 index 00000000..57635f46 --- /dev/null +++ b/store/db/mysql/migration/prod/0.17/00__inbox.sql @@ -0,0 +1,9 @@ +-- inbox +CREATE TABLE `inbox` ( + `id` INT NOT NULL AUTO_INCREMENT PRIMARY KEY, + `created_ts` TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP, + `sender_id` INT NOT NULL, + `receiver_id` INT NOT NULL, + `status` TEXT NOT NULL, + `message` TEXT NOT NULL +); diff --git a/store/db/mysql/migration/prod/0.17/01__delete_activity.sql b/store/db/mysql/migration/prod/0.17/01__delete_activity.sql new file mode 100644 index 00000000..9ea4e0f1 --- /dev/null +++ b/store/db/mysql/migration/prod/0.17/01__delete_activity.sql @@ -0,0 +1 @@ +DELETE FROM `activity`; diff --git a/store/db/mysql/migration/prod/LATEST__SCHEMA.sql b/store/db/mysql/migration/prod/LATEST__SCHEMA.sql index 74e5daed..74767c3a 100644 --- a/store/db/mysql/migration/prod/LATEST__SCHEMA.sql +++ b/store/db/mysql/migration/prod/LATEST__SCHEMA.sql @@ -11,6 +11,7 @@ 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`; -- migration_history CREATE TABLE `migration_history` ( @@ -122,3 +123,13 @@ CREATE TABLE `idp` ( `identifier_filter` VARCHAR(256) NOT NULL DEFAULT '', `config` TEXT NOT NULL ); + +-- inbox +CREATE TABLE `inbox` ( + `id` INT NOT NULL AUTO_INCREMENT PRIMARY KEY, + `created_ts` TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP, + `sender_id` INT NOT NULL, + `receiver_id` INT NOT NULL, + `status` TEXT NOT NULL, + `message` TEXT NOT NULL +); diff --git a/store/db/mysql/user.go b/store/db/mysql/user.go index 224cf9b6..882a0db4 100644 --- a/store/db/mysql/user.go +++ b/store/db/mysql/user.go @@ -49,8 +49,8 @@ func (d *DB) CreateUser(ctx context.Context, create *store.User) (*store.User, e return nil, err } - id64 := int32(id) - list, err := d.ListUsers(ctx, &store.FindUser{ID: &id64}) + id32 := int32(id) + list, err := d.ListUsers(ctx, &store.FindUser{ID: &id32}) if err != nil { return nil, err } @@ -91,23 +91,10 @@ func (d *DB) UpdateUser(ctx context.Context, update *store.UpdateUser) (*store.U return nil, err } - user := &store.User{} - query = "SELECT `id`, `username`, `role`, `email`, `nickname`, `password_hash`, `avatar_url`, UNIX_TIMESTAMP(`created_ts`), UNIX_TIMESTAMP(`updated_ts`), `row_status` FROM `user` WHERE `id` = ?" - if err := d.db.QueryRowContext(ctx, query, update.ID).Scan( - &user.ID, - &user.Username, - &user.Role, - &user.Email, - &user.Nickname, - &user.PasswordHash, - &user.AvatarURL, - &user.CreatedTs, - &user.UpdatedTs, - &user.RowStatus, - ); err != nil { + user, err := d.GetUser(ctx, &store.FindUser{ID: &update.ID}) + if err != nil { return nil, err } - return user, nil } @@ -164,6 +151,17 @@ func (d *DB) ListUsers(ctx context.Context, find *store.FindUser) ([]*store.User return list, nil } +func (d *DB) GetUser(ctx context.Context, find *store.FindUser) (*store.User, error) { + list, err := d.ListUsers(ctx, find) + if err != nil { + return nil, err + } + if len(list) != 1 { + return nil, errors.Wrapf(nil, "unexpected user count: %d", len(list)) + } + return list[0], nil +} + func (d *DB) DeleteUser(ctx context.Context, delete *store.DeleteUser) error { result, err := d.db.ExecContext(ctx, "DELETE FROM `user` WHERE `id` = ?", delete.ID) if err != nil { diff --git a/store/db/sqlite/inbox.go b/store/db/sqlite/inbox.go index 1ea3e4b9..97331c5d 100644 --- a/store/db/sqlite/inbox.go +++ b/store/db/sqlite/inbox.go @@ -25,7 +25,7 @@ func (d *DB) CreateInbox(ctx context.Context, create *store.Inbox) (*store.Inbox placeholder := []string{"?", "?", "?", "?"} args := []any{create.SenderID, create.ReceiverID, create.Status, messageString} - stmt := "INSERT INTO inbox (" + strings.Join(fields, ", ") + ") VALUES (" + strings.Join(placeholder, ", ") + ") RETURNING `id`, `created_ts`" + stmt := "INSERT INTO `inbox` (" + strings.Join(fields, ", ") + ") VALUES (" + strings.Join(placeholder, ", ") + ") RETURNING `id`, `created_ts`" if err := d.db.QueryRowContext(ctx, stmt, args...).Scan( &create.ID, &create.CreatedTs, @@ -52,7 +52,7 @@ func (d *DB) ListInboxes(ctx context.Context, find *store.FindInbox) ([]*store.I where, args = append(where, "`status` = ?"), append(args, *find.Status) } - query := "SELECT `id`, `created_ts`, `sender_id`, `receiver_id`, `status`, `message` FROM `inbox` WHERE " + strings.Join(where, " AND ") + " ORDER BY created_ts DESC" + query := "SELECT `id`, `created_ts`, `sender_id`, `receiver_id`, `status`, `message` FROM `inbox` WHERE " + strings.Join(where, " AND ") + " ORDER BY `created_ts` DESC" rows, err := d.db.QueryContext(ctx, query, args...) if err != nil { return nil, err @@ -90,9 +90,9 @@ func (d *DB) ListInboxes(ctx context.Context, find *store.FindInbox) ([]*store.I } func (d *DB) UpdateInbox(ctx context.Context, update *store.UpdateInbox) (*store.Inbox, error) { - set, args := []string{"status"}, []any{update.Status.String()} + set, args := []string{"`status` = ?"}, []any{update.Status.String()} args = append(args, update.ID) - query := "UPDATE inbox SET " + strings.Join(set, " = ?, ") + " = ? WHERE id = ? RETURNING `id`, `created_ts`, `sender_id`, `receiver_id`, `status`, `message`" + query := "UPDATE `inbox` SET " + strings.Join(set, ", ") + " WHERE `id` = ? RETURNING `id`, `created_ts`, `sender_id`, `receiver_id`, `status`, `message`" inbox := &store.Inbox{} var messageBytes []byte if err := d.db.QueryRowContext(ctx, query, args...).Scan( @@ -114,7 +114,7 @@ func (d *DB) UpdateInbox(ctx context.Context, update *store.UpdateInbox) (*store } func (d *DB) DeleteInbox(ctx context.Context, delete *store.DeleteInbox) error { - result, err := d.db.ExecContext(ctx, "DELETE FROM inbox WHERE id = ?", delete.ID) + result, err := d.db.ExecContext(ctx, "DELETE FROM `inbox` WHERE `id` = ?", delete.ID) if err != nil { return err } diff --git a/store/db/sqlite/migration/prod/0.17/00__inbox.sql b/store/db/sqlite/migration/prod/0.17/00__inbox.sql new file mode 100644 index 00000000..ed5f309e --- /dev/null +++ b/store/db/sqlite/migration/prod/0.17/00__inbox.sql @@ -0,0 +1,9 @@ +-- inbox +CREATE TABLE inbox ( + id INTEGER PRIMARY KEY AUTOINCREMENT, + created_ts BIGINT NOT NULL DEFAULT (strftime('%s', 'now')), + sender_id INTEGER NOT NULL, + receiver_id INTEGER NOT NULL, + status TEXT NOT NULL, + message TEXT NOT NULL DEFAULT '{}' +); diff --git a/store/db/sqlite/migration/prod/0.17/01__delete_activities.sql b/store/db/sqlite/migration/prod/0.17/01__delete_activities.sql new file mode 100644 index 00000000..24593d47 --- /dev/null +++ b/store/db/sqlite/migration/prod/0.17/01__delete_activities.sql @@ -0,0 +1 @@ +DELETE FROM activity; diff --git a/store/db/sqlite/migration/prod/LATEST__SCHEMA.sql b/store/db/sqlite/migration/prod/LATEST__SCHEMA.sql index 9b43e16a..597d6317 100644 --- a/store/db/sqlite/migration/prod/LATEST__SCHEMA.sql +++ b/store/db/sqlite/migration/prod/LATEST__SCHEMA.sql @@ -11,6 +11,7 @@ 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; -- migration_history CREATE TABLE migration_history ( @@ -133,3 +134,13 @@ CREATE TABLE idp ( identifier_filter TEXT NOT NULL DEFAULT '', config TEXT NOT NULL DEFAULT '{}' ); + +-- inbox +CREATE TABLE inbox ( + id INTEGER PRIMARY KEY AUTOINCREMENT, + created_ts BIGINT NOT NULL DEFAULT (strftime('%s', 'now')), + sender_id INTEGER NOT NULL, + receiver_id INTEGER NOT NULL, + status TEXT NOT NULL, + message TEXT NOT NULL DEFAULT '{}' +);