Commit 981bfe04 authored by Steven's avatar Steven

feat: add version checker

parent 695fb1e0
......@@ -6,6 +6,7 @@
- [store/activity.proto](#store_activity-proto)
- [ActivityMemoCommentPayload](#memos-store-ActivityMemoCommentPayload)
- [ActivityPayload](#memos-store-ActivityPayload)
- [ActivityVersionUpdatePayload](#memos-store-ActivityVersionUpdatePayload)
- [store/common.proto](#store_common-proto)
- [store/inbox.proto](#store_inbox-proto)
......@@ -61,6 +62,22 @@
| Field | Type | Label | Description |
| ----- | ---- | ----- | ----------- |
| memo_comment | [ActivityMemoCommentPayload](#memos-store-ActivityMemoCommentPayload) | | |
| version_update | [ActivityVersionUpdatePayload](#memos-store-ActivityVersionUpdatePayload) | | |
<a name="memos-store-ActivityVersionUpdatePayload"></a>
### ActivityVersionUpdatePayload
| Field | Type | Label | Description |
| ----- | ---- | ----- | ----------- |
| version | [string](#string) | | |
......@@ -126,6 +143,7 @@
| ---- | ------ | ----------- |
| TYPE_UNSPECIFIED | 0 | |
| TYPE_MEMO_COMMENT | 1 | |
| TYPE_VERSION_UPDATE | 2 | |
......
This diff is collapsed.
......@@ -23,8 +23,9 @@ const (
type InboxMessage_Type int32
const (
InboxMessage_TYPE_UNSPECIFIED InboxMessage_Type = 0
InboxMessage_TYPE_MEMO_COMMENT InboxMessage_Type = 1
InboxMessage_TYPE_UNSPECIFIED InboxMessage_Type = 0
InboxMessage_TYPE_MEMO_COMMENT InboxMessage_Type = 1
InboxMessage_TYPE_VERSION_UPDATE InboxMessage_Type = 2
)
// Enum value maps for InboxMessage_Type.
......@@ -32,10 +33,12 @@ var (
InboxMessage_Type_name = map[int32]string{
0: "TYPE_UNSPECIFIED",
1: "TYPE_MEMO_COMMENT",
2: "TYPE_VERSION_UPDATE",
}
InboxMessage_Type_value = map[string]int32{
"TYPE_UNSPECIFIED": 0,
"TYPE_MEMO_COMMENT": 1,
"TYPE_UNSPECIFIED": 0,
"TYPE_MEMO_COMMENT": 1,
"TYPE_VERSION_UPDATE": 2,
}
)
......@@ -126,27 +129,29 @@ var File_store_inbox_proto protoreflect.FileDescriptor
var file_store_inbox_proto_rawDesc = []byte{
0x0a, 0x11, 0x73, 0x74, 0x6f, 0x72, 0x65, 0x2f, 0x69, 0x6e, 0x62, 0x6f, 0x78, 0x2e, 0x70, 0x72,
0x6f, 0x74, 0x6f, 0x12, 0x0b, 0x6d, 0x65, 0x6d, 0x6f, 0x73, 0x2e, 0x73, 0x74, 0x6f, 0x72, 0x65,
0x22, 0xad, 0x01, 0x0a, 0x0c, 0x49, 0x6e, 0x62, 0x6f, 0x78, 0x4d, 0x65, 0x73, 0x73, 0x61, 0x67,
0x22, 0xc6, 0x01, 0x0a, 0x0c, 0x49, 0x6e, 0x62, 0x6f, 0x78, 0x4d, 0x65, 0x73, 0x73, 0x61, 0x67,
0x65, 0x12, 0x32, 0x0a, 0x04, 0x74, 0x79, 0x70, 0x65, 0x18, 0x01, 0x20, 0x01, 0x28, 0x0e, 0x32,
0x1e, 0x2e, 0x6d, 0x65, 0x6d, 0x6f, 0x73, 0x2e, 0x73, 0x74, 0x6f, 0x72, 0x65, 0x2e, 0x49, 0x6e,
0x62, 0x6f, 0x78, 0x4d, 0x65, 0x73, 0x73, 0x61, 0x67, 0x65, 0x2e, 0x54, 0x79, 0x70, 0x65, 0x52,
0x04, 0x74, 0x79, 0x70, 0x65, 0x12, 0x24, 0x0a, 0x0b, 0x61, 0x63, 0x74, 0x69, 0x76, 0x69, 0x74,
0x79, 0x5f, 0x69, 0x64, 0x18, 0x02, 0x20, 0x01, 0x28, 0x05, 0x48, 0x00, 0x52, 0x0a, 0x61, 0x63,
0x74, 0x69, 0x76, 0x69, 0x74, 0x79, 0x49, 0x64, 0x88, 0x01, 0x01, 0x22, 0x33, 0x0a, 0x04, 0x54,
0x74, 0x69, 0x76, 0x69, 0x74, 0x79, 0x49, 0x64, 0x88, 0x01, 0x01, 0x22, 0x4c, 0x0a, 0x04, 0x54,
0x79, 0x70, 0x65, 0x12, 0x14, 0x0a, 0x10, 0x54, 0x59, 0x50, 0x45, 0x5f, 0x55, 0x4e, 0x53, 0x50,
0x45, 0x43, 0x49, 0x46, 0x49, 0x45, 0x44, 0x10, 0x00, 0x12, 0x15, 0x0a, 0x11, 0x54, 0x59, 0x50,
0x45, 0x5f, 0x4d, 0x45, 0x4d, 0x4f, 0x5f, 0x43, 0x4f, 0x4d, 0x4d, 0x45, 0x4e, 0x54, 0x10, 0x01,
0x42, 0x0e, 0x0a, 0x0c, 0x5f, 0x61, 0x63, 0x74, 0x69, 0x76, 0x69, 0x74, 0x79, 0x5f, 0x69, 0x64,
0x42, 0x95, 0x01, 0x0a, 0x0f, 0x63, 0x6f, 0x6d, 0x2e, 0x6d, 0x65, 0x6d, 0x6f, 0x73, 0x2e, 0x73,
0x74, 0x6f, 0x72, 0x65, 0x42, 0x0a, 0x49, 0x6e, 0x62, 0x6f, 0x78, 0x50, 0x72, 0x6f, 0x74, 0x6f,
0x50, 0x01, 0x5a, 0x29, 0x67, 0x69, 0x74, 0x68, 0x75, 0x62, 0x2e, 0x63, 0x6f, 0x6d, 0x2f, 0x75,
0x73, 0x65, 0x6d, 0x65, 0x6d, 0x6f, 0x73, 0x2f, 0x6d, 0x65, 0x6d, 0x6f, 0x73, 0x2f, 0x70, 0x72,
0x6f, 0x74, 0x6f, 0x2f, 0x67, 0x65, 0x6e, 0x2f, 0x73, 0x74, 0x6f, 0x72, 0x65, 0xa2, 0x02, 0x03,
0x4d, 0x53, 0x58, 0xaa, 0x02, 0x0b, 0x4d, 0x65, 0x6d, 0x6f, 0x73, 0x2e, 0x53, 0x74, 0x6f, 0x72,
0x65, 0xca, 0x02, 0x0b, 0x4d, 0x65, 0x6d, 0x6f, 0x73, 0x5c, 0x53, 0x74, 0x6f, 0x72, 0x65, 0xe2,
0x02, 0x17, 0x4d, 0x65, 0x6d, 0x6f, 0x73, 0x5c, 0x53, 0x74, 0x6f, 0x72, 0x65, 0x5c, 0x47, 0x50,
0x42, 0x4d, 0x65, 0x74, 0x61, 0x64, 0x61, 0x74, 0x61, 0xea, 0x02, 0x0c, 0x4d, 0x65, 0x6d, 0x6f,
0x73, 0x3a, 0x3a, 0x53, 0x74, 0x6f, 0x72, 0x65, 0x62, 0x06, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x33,
0x12, 0x17, 0x0a, 0x13, 0x54, 0x59, 0x50, 0x45, 0x5f, 0x56, 0x45, 0x52, 0x53, 0x49, 0x4f, 0x4e,
0x5f, 0x55, 0x50, 0x44, 0x41, 0x54, 0x45, 0x10, 0x02, 0x42, 0x0e, 0x0a, 0x0c, 0x5f, 0x61, 0x63,
0x74, 0x69, 0x76, 0x69, 0x74, 0x79, 0x5f, 0x69, 0x64, 0x42, 0x95, 0x01, 0x0a, 0x0f, 0x63, 0x6f,
0x6d, 0x2e, 0x6d, 0x65, 0x6d, 0x6f, 0x73, 0x2e, 0x73, 0x74, 0x6f, 0x72, 0x65, 0x42, 0x0a, 0x49,
0x6e, 0x62, 0x6f, 0x78, 0x50, 0x72, 0x6f, 0x74, 0x6f, 0x50, 0x01, 0x5a, 0x29, 0x67, 0x69, 0x74,
0x68, 0x75, 0x62, 0x2e, 0x63, 0x6f, 0x6d, 0x2f, 0x75, 0x73, 0x65, 0x6d, 0x65, 0x6d, 0x6f, 0x73,
0x2f, 0x6d, 0x65, 0x6d, 0x6f, 0x73, 0x2f, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x2f, 0x67, 0x65, 0x6e,
0x2f, 0x73, 0x74, 0x6f, 0x72, 0x65, 0xa2, 0x02, 0x03, 0x4d, 0x53, 0x58, 0xaa, 0x02, 0x0b, 0x4d,
0x65, 0x6d, 0x6f, 0x73, 0x2e, 0x53, 0x74, 0x6f, 0x72, 0x65, 0xca, 0x02, 0x0b, 0x4d, 0x65, 0x6d,
0x6f, 0x73, 0x5c, 0x53, 0x74, 0x6f, 0x72, 0x65, 0xe2, 0x02, 0x17, 0x4d, 0x65, 0x6d, 0x6f, 0x73,
0x5c, 0x53, 0x74, 0x6f, 0x72, 0x65, 0x5c, 0x47, 0x50, 0x42, 0x4d, 0x65, 0x74, 0x61, 0x64, 0x61,
0x74, 0x61, 0xea, 0x02, 0x0c, 0x4d, 0x65, 0x6d, 0x6f, 0x73, 0x3a, 0x3a, 0x53, 0x74, 0x6f, 0x72,
0x65, 0x62, 0x06, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x33,
}
var (
......
......@@ -9,6 +9,11 @@ message ActivityMemoCommentPayload {
int32 related_memo_id = 2;
}
message ActivityVersionUpdatePayload {
string version = 1;
}
message ActivityPayload {
ActivityMemoCommentPayload memo_comment = 1;
ActivityVersionUpdatePayload version_update = 2;
}
......@@ -8,6 +8,7 @@ message InboxMessage {
enum Type {
TYPE_UNSPECIFIED = 0;
TYPE_MEMO_COMMENT = 1;
TYPE_VERSION_UPDATE = 2;
}
Type type = 1;
optional int32 activity_id = 2;
......
......@@ -20,6 +20,7 @@ import (
"github.com/usememos/memos/server/profile"
"github.com/usememos/memos/server/service/backup"
"github.com/usememos/memos/server/service/metric"
versionchecker "github.com/usememos/memos/server/service/version_checker"
"github.com/usememos/memos/store"
)
......@@ -109,6 +110,7 @@ func NewServer(ctx context.Context, profile *profile.Profile, store *store.Store
}
func (s *Server) Start(ctx context.Context) error {
go versionchecker.NewVersionChecker(s.Store).Start(ctx)
go s.telegramBot.Start(ctx)
go s.backupRunner.Run(ctx)
......
package versionchecker
import (
"bytes"
"context"
"encoding/json"
"fmt"
"net/http"
"time"
"github.com/pkg/errors"
storepb "github.com/usememos/memos/proto/gen/store"
"github.com/usememos/memos/server/version"
"github.com/usememos/memos/store"
)
// nolint
type VersionChecker struct {
Store *store.Store
}
func NewVersionChecker(store *store.Store) *VersionChecker {
return &VersionChecker{
Store: store,
}
}
func (*VersionChecker) GetLatestVersion() (string, error) {
response, err := http.Get("https://www.usememos.com/api/version")
if err != nil {
return "", errors.Wrap(err, "failed to make http request")
}
defer response.Body.Close()
buf := &bytes.Buffer{}
_, err = buf.ReadFrom(response.Body)
if err != nil {
return "", errors.Wrap(err, "fail to read response body")
}
version := ""
if err = json.Unmarshal(buf.Bytes(), &version); err != nil {
return "", errors.Wrap(err, "fail to unmarshal get version response")
}
return version, nil
}
func (c *VersionChecker) Check(ctx context.Context) {
migrationHistories, err := c.Store.FindMigrationHistoryList(ctx, &store.FindMigrationHistory{})
if err != nil {
return
}
if len(migrationHistories) == 0 {
return
}
lastVersion := migrationHistories[0].Version
latestVersion, err := c.GetLatestVersion()
if err != nil {
return
}
if !version.IsVersionGreaterThan(latestVersion, lastVersion) {
return
}
versionUpdateActivityType := store.ActivityTypeVersionUpdate
list, err := c.Store.ListActivities(ctx, &store.FindActivity{
Type: &versionUpdateActivityType,
})
if err != nil {
return
}
if len(list) == 0 {
return
}
latestVersionUpdateActivity := list[0]
if latestVersionUpdateActivity.Payload == nil {
return
}
if version.IsVersionGreaterOrEqualThan(latestVersionUpdateActivity.Payload.VersionUpdate.Version, latestVersion) {
return
}
// Create version update activity and inbox message.
activity := &store.Activity{
CreatorID: store.SystemBotID,
Type: store.ActivityTypeVersionUpdate,
Level: store.ActivityLevelInfo,
Payload: &storepb.ActivityPayload{
VersionUpdate: &storepb.ActivityVersionUpdatePayload{
Version: latestVersion,
},
},
}
if _, err := c.Store.CreateActivity(ctx, activity); err != nil {
return
}
hostUserRole := store.RoleHost
users, err := c.Store.ListUsers(ctx, &store.FindUser{
Role: &hostUserRole,
})
if err != nil {
return
}
if len(users) == 0 {
return
}
hostUser := users[0]
if _, err := c.Store.CreateInbox(ctx, &store.Inbox{
SenderID: store.SystemBotID,
ReceiverID: hostUser.ID,
Status: store.UNREAD,
Message: &storepb.InboxMessage{
Type: storepb.InboxMessage_TYPE_VERSION_UPDATE,
ActivityId: &activity.ID,
},
}); err != nil {
fmt.Printf("failed to create inbox: %s\n", err)
}
}
func (c *VersionChecker) Start(ctx context.Context) {
c.Check(ctx)
// Schedule checker every 8 hours.
ticker := time.NewTicker(8 * time.Hour)
defer ticker.Stop()
for {
select {
case <-ctx.Done():
return
case <-ticker.C:
}
c.Check(ctx)
}
}
package versionchecker
import (
"testing"
"github.com/stretchr/testify/require"
)
func TestGetLatestVersion(t *testing.T) {
_, err := NewVersionChecker(nil).GetLatestVersion()
require.NoError(t, err)
}
......@@ -9,7 +9,8 @@ import (
type ActivityType string
const (
ActivityTypeMemoComment ActivityType = "MEMO_COMMENT"
ActivityTypeMemoComment ActivityType = "MEMO_COMMENT"
ActivityTypeVersionUpdate ActivityType = "VERSION_UPDATE"
)
func (t ActivityType) String() string {
......@@ -40,7 +41,8 @@ type Activity struct {
}
type FindActivity struct {
ID *int32
ID *int32
Type *ActivityType
}
func (s *Store) CreateActivity(ctx context.Context, create *Activity) (*Activity, error) {
......
......@@ -63,8 +63,11 @@ func (d *DB) ListActivities(ctx context.Context, find *store.FindActivity) ([]*s
if find.ID != nil {
where, args = append(where, "`id` = ?"), append(args, *find.ID)
}
if find.Type != nil {
where, args = append(where, "`type` = ?"), append(args, find.Type.String())
}
query := "SELECT `id`, `creator_id`, `type`, `level`, `payload`, UNIX_TIMESTAMP(`created_ts`) FROM `activity` WHERE " + strings.Join(where, " AND ")
query := "SELECT `id`, `creator_id`, `type`, `level`, `payload`, UNIX_TIMESTAMP(`created_ts`) FROM `activity` WHERE " + strings.Join(where, " AND ") + " ORDER BY `created_ts` DESC"
rows, err := d.db.QueryContext(ctx, query, args...)
if err != nil {
return nil, err
......
......@@ -54,8 +54,11 @@ func (d *DB) ListActivities(ctx context.Context, find *store.FindActivity) ([]*s
if find.ID != nil {
where, args = append(where, "`id` = ?"), append(args, *find.ID)
}
if find.Type != nil {
where, args = append(where, "`type` = ?"), append(args, find.Type.String())
}
query := "SELECT `id`, `creator_id`, `type`, `level`, `payload`, `created_ts` FROM `activity` WHERE " + strings.Join(where, " AND ")
query := "SELECT `id`, `creator_id`, `type`, `level`, `payload`, `created_ts` FROM `activity` WHERE " + strings.Join(where, " AND ") + " ORDER BY `created_ts` DESC"
rows, err := d.db.QueryContext(ctx, query, args...)
if err != nil {
return nil, err
......
......@@ -28,6 +28,20 @@ func (e Role) String() string {
return "USER"
}
const (
SystemBotID int32 = 0
)
var (
SystemBot = &User{
ID: SystemBotID,
Username: "system_bot",
Role: RoleAdmin,
Email: "",
Nickname: "Bot",
}
)
type User struct {
ID int32
......@@ -106,6 +120,10 @@ func (s *Store) ListUsers(ctx context.Context, find *FindUser) ([]*User, error)
func (s *Store) GetUser(ctx context.Context, find *FindUser) (*User, error) {
if find.ID != nil {
if *find.ID == SystemBotID {
return SystemBot, nil
}
if cache, ok := s.userCache.Load(*find.ID); ok {
return cache.(*User), nil
}
......
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment