♻️ Better notification system

This commit is contained in:
LittleSheep 2024-06-07 20:05:56 +08:00
parent b925d54000
commit 332557778d
9 changed files with 120 additions and 106 deletions

60
.idea/workspace.xml generated
View File

@ -5,15 +5,15 @@
</component> </component>
<component name="ChangeListManager"> <component name="ChangeListManager">
<list default="true" id="3fefb2c4-b6f9-466b-a523-53352e8d6f95" name="更改" comment=":sparkles: Apple push notification services"> <list default="true" id="3fefb2c4-b6f9-466b-a523-53352e8d6f95" name="更改" comment=":sparkles: Apple push notification services">
<change afterPath="$PROJECT_DIR$/pkg/services/external_apns.go" afterDir="false" />
<change beforePath="$PROJECT_DIR$/.idea/workspace.xml" beforeDir="false" afterPath="$PROJECT_DIR$/.idea/workspace.xml" afterDir="false" /> <change beforePath="$PROJECT_DIR$/.idea/workspace.xml" beforeDir="false" afterPath="$PROJECT_DIR$/.idea/workspace.xml" afterDir="false" />
<change beforePath="$PROJECT_DIR$/go.mod" beforeDir="false" afterPath="$PROJECT_DIR$/go.mod" afterDir="false" /> <change beforePath="$PROJECT_DIR$/pkg/grpc/notify.go" beforeDir="false" afterPath="$PROJECT_DIR$/pkg/grpc/notify.go" afterDir="false" />
<change beforePath="$PROJECT_DIR$/go.sum" beforeDir="false" afterPath="$PROJECT_DIR$/go.sum" afterDir="false" /> <change beforePath="$PROJECT_DIR$/pkg/grpc/proto/notify.pb.go" beforeDir="false" afterPath="$PROJECT_DIR$/pkg/grpc/proto/notify.pb.go" afterDir="false" />
<change beforePath="$PROJECT_DIR$/pkg/cmd/main.go" beforeDir="false" afterPath="$PROJECT_DIR$/pkg/cmd/main.go" afterDir="false" /> <change beforePath="$PROJECT_DIR$/pkg/grpc/proto/notify.proto" beforeDir="false" afterPath="$PROJECT_DIR$/pkg/grpc/proto/notify.proto" afterDir="false" />
<change beforePath="$PROJECT_DIR$/pkg/grpc/proto/notify_grpc.pb.go" beforeDir="false" afterPath="$PROJECT_DIR$/pkg/grpc/proto/notify_grpc.pb.go" afterDir="false" />
<change beforePath="$PROJECT_DIR$/pkg/models/notifications.go" beforeDir="false" afterPath="$PROJECT_DIR$/pkg/models/notifications.go" afterDir="false" /> <change beforePath="$PROJECT_DIR$/pkg/models/notifications.go" beforeDir="false" afterPath="$PROJECT_DIR$/pkg/models/notifications.go" afterDir="false" />
<change beforePath="$PROJECT_DIR$/pkg/services/external_firebase.go" beforeDir="false" afterPath="$PROJECT_DIR$/pkg/services/external_firebase.go" afterDir="false" /> <change beforePath="$PROJECT_DIR$/pkg/server/notifications_api.go" beforeDir="false" afterPath="$PROJECT_DIR$/pkg/server/notifications_api.go" afterDir="false" />
<change beforePath="$PROJECT_DIR$/pkg/server/notify_api.go" beforeDir="false" afterPath="$PROJECT_DIR$/pkg/server/notify_api.go" afterDir="false" />
<change beforePath="$PROJECT_DIR$/pkg/services/notifications.go" beforeDir="false" afterPath="$PROJECT_DIR$/pkg/services/notifications.go" afterDir="false" /> <change beforePath="$PROJECT_DIR$/pkg/services/notifications.go" beforeDir="false" afterPath="$PROJECT_DIR$/pkg/services/notifications.go" afterDir="false" />
<change beforePath="$PROJECT_DIR$/settings.toml" beforeDir="false" afterPath="$PROJECT_DIR$/settings.toml" afterDir="false" />
</list> </list>
<option name="SHOW_DIALOG" value="false" /> <option name="SHOW_DIALOG" value="false" />
<option name="HIGHLIGHT_CONFLICTS" value="true" /> <option name="HIGHLIGHT_CONFLICTS" value="true" />
@ -48,33 +48,33 @@
<option name="hideEmptyMiddlePackages" value="true" /> <option name="hideEmptyMiddlePackages" value="true" />
<option name="showLibraryContents" value="true" /> <option name="showLibraryContents" value="true" />
</component> </component>
<component name="PropertiesComponent"><![CDATA[{ <component name="PropertiesComponent">{
"keyToString": { &quot;keyToString&quot;: {
"DefaultGoTemplateProperty": "Go File", &quot;DefaultGoTemplateProperty&quot;: &quot;Go File&quot;,
"Go Build.Backend.executor": "Run", &quot;Go Build.Backend.executor&quot;: &quot;Run&quot;,
"Go 构建.Backend.executor": "Run", &quot;Go 构建.Backend.executor&quot;: &quot;Run&quot;,
"RunOnceActivity.ShowReadmeOnStart": "true", &quot;RunOnceActivity.ShowReadmeOnStart&quot;: &quot;true&quot;,
"RunOnceActivity.go.formatter.settings.were.checked": "true", &quot;RunOnceActivity.go.formatter.settings.were.checked&quot;: &quot;true&quot;,
"RunOnceActivity.go.migrated.go.modules.settings": "true", &quot;RunOnceActivity.go.migrated.go.modules.settings&quot;: &quot;true&quot;,
"RunOnceActivity.go.modules.automatic.dependencies.download": "true", &quot;RunOnceActivity.go.modules.automatic.dependencies.download&quot;: &quot;true&quot;,
"RunOnceActivity.go.modules.go.list.on.any.changes.was.set": "true", &quot;RunOnceActivity.go.modules.go.list.on.any.changes.was.set&quot;: &quot;true&quot;,
"git-widget-placeholder": "master", &quot;git-widget-placeholder&quot;: &quot;master&quot;,
"go.import.settings.migrated": "true", &quot;go.import.settings.migrated&quot;: &quot;true&quot;,
"go.sdk.automatically.set": "true", &quot;go.sdk.automatically.set&quot;: &quot;true&quot;,
"last_opened_file_path": "/Users/littlesheep/Documents/Projects/Hydrogen/Passport/pkg/services", &quot;last_opened_file_path&quot;: &quot;/Users/littlesheep/Documents/Projects/Hydrogen/Passport/pkg/services&quot;,
"node.js.detected.package.eslint": "true", &quot;node.js.detected.package.eslint&quot;: &quot;true&quot;,
"node.js.selected.package.eslint": "(autodetect)", &quot;node.js.selected.package.eslint&quot;: &quot;(autodetect)&quot;,
"nodejs_package_manager_path": "npm", &quot;nodejs_package_manager_path&quot;: &quot;npm&quot;,
"run.code.analysis.last.selected.profile": "pProject Default", &quot;run.code.analysis.last.selected.profile&quot;: &quot;pProject Default&quot;,
"settings.editor.selected.configurable": "preferences.pluginManager", &quot;settings.editor.selected.configurable&quot;: &quot;preferences.pluginManager&quot;,
"vue.rearranger.settings.migration": "true" &quot;vue.rearranger.settings.migration&quot;: &quot;true&quot;
}, },
"keyToStringList": { &quot;keyToStringList&quot;: {
"DatabaseDriversLRU": [ &quot;DatabaseDriversLRU&quot;: [
"postgresql" &quot;postgresql&quot;
] ]
} }
}]]></component> }</component>
<component name="RecentsManager"> <component name="RecentsManager">
<key name="CopyFile.RECENT_KEYS"> <key name="CopyFile.RECENT_KEYS">
<recent name="$PROJECT_DIR$/pkg/services" /> <recent name="$PROJECT_DIR$/pkg/services" />

View File

@ -2,6 +2,7 @@ package grpc
import ( import (
"context" "context"
jsoniter "github.com/json-iterator/go"
"git.solsynth.dev/hydrogen/passport/pkg/grpc/proto" "git.solsynth.dev/hydrogen/passport/pkg/grpc/proto"
"git.solsynth.dev/hydrogen/passport/pkg/models" "git.solsynth.dev/hydrogen/passport/pkg/models"
@ -20,6 +21,9 @@ func (v *Server) NotifyUser(_ context.Context, in *proto.NotifyRequest) (*proto.
return nil, err return nil, err
} }
var metadata map[string]any
_ = jsoniter.Unmarshal(in.GetMetadata(), &metadata)
links := lo.Map(in.GetLinks(), func(item *proto.NotifyLink, index int) models.NotificationLink { links := lo.Map(in.GetLinks(), func(item *proto.NotifyLink, index int) models.NotificationLink {
return models.NotificationLink{ return models.NotificationLink{
Label: item.Label, Label: item.Label,
@ -28,12 +32,13 @@ func (v *Server) NotifyUser(_ context.Context, in *proto.NotifyRequest) (*proto.
}) })
notification := models.Notification{ notification := models.Notification{
Type: in.GetType(),
Subject: in.GetSubject(), Subject: in.GetSubject(),
Content: in.GetContent(), Content: in.GetContent(),
Metadata: metadata,
Links: links, Links: links,
IsImportant: in.GetIsImportant(),
IsRealtime: in.GetIsRealtime(), IsRealtime: in.GetIsRealtime(),
ReadAt: nil, IsForcePush: in.GetIsForcePush(),
RecipientID: user.ID, RecipientID: user.ID,
SenderID: &client.ID, SenderID: &client.ID,
} }

View File

@ -1,7 +1,7 @@
// Code generated by protoc-gen-go. DO NOT EDIT. // Code generated by protoc-gen-go. DO NOT EDIT.
// versions: // versions:
// protoc-gen-go v1.33.0 // protoc-gen-go v1.33.0
// protoc v4.25.3 // protoc v5.26.1
// source: notify.proto // source: notify.proto
package proto package proto
@ -80,14 +80,16 @@ type NotifyRequest struct {
sizeCache protoimpl.SizeCache sizeCache protoimpl.SizeCache
unknownFields protoimpl.UnknownFields unknownFields protoimpl.UnknownFields
Subject string `protobuf:"bytes,1,opt,name=subject,proto3" json:"subject,omitempty"` Type string `protobuf:"bytes,1,opt,name=type,proto3" json:"type,omitempty"`
Content string `protobuf:"bytes,2,opt,name=content,proto3" json:"content,omitempty"` Subject string `protobuf:"bytes,2,opt,name=subject,proto3" json:"subject,omitempty"`
Links []*NotifyLink `protobuf:"bytes,3,rep,name=links,proto3" json:"links,omitempty"` Content string `protobuf:"bytes,3,opt,name=content,proto3" json:"content,omitempty"`
IsImportant bool `protobuf:"varint,4,opt,name=is_important,json=isImportant,proto3" json:"is_important,omitempty"` Metadata []byte `protobuf:"bytes,4,opt,name=metadata,proto3" json:"metadata,omitempty"`
RecipientId uint64 `protobuf:"varint,5,opt,name=recipient_id,json=recipientId,proto3" json:"recipient_id,omitempty"` Links []*NotifyLink `protobuf:"bytes,5,rep,name=links,proto3" json:"links,omitempty"`
ClientId string `protobuf:"bytes,6,opt,name=client_id,json=clientId,proto3" json:"client_id,omitempty"` RecipientId uint64 `protobuf:"varint,6,opt,name=recipient_id,json=recipientId,proto3" json:"recipient_id,omitempty"`
ClientSecret string `protobuf:"bytes,7,opt,name=client_secret,json=clientSecret,proto3" json:"client_secret,omitempty"` ClientId string `protobuf:"bytes,7,opt,name=client_id,json=clientId,proto3" json:"client_id,omitempty"`
IsRealtime bool `protobuf:"varint,8,opt,name=is_realtime,json=isRealtime,proto3" json:"is_realtime,omitempty"` ClientSecret string `protobuf:"bytes,8,opt,name=client_secret,json=clientSecret,proto3" json:"client_secret,omitempty"`
IsRealtime bool `protobuf:"varint,9,opt,name=is_realtime,json=isRealtime,proto3" json:"is_realtime,omitempty"`
IsForcePush bool `protobuf:"varint,10,opt,name=is_force_push,json=isForcePush,proto3" json:"is_force_push,omitempty"`
} }
func (x *NotifyRequest) Reset() { func (x *NotifyRequest) Reset() {
@ -122,6 +124,13 @@ func (*NotifyRequest) Descriptor() ([]byte, []int) {
return file_notify_proto_rawDescGZIP(), []int{1} return file_notify_proto_rawDescGZIP(), []int{1}
} }
func (x *NotifyRequest) GetType() string {
if x != nil {
return x.Type
}
return ""
}
func (x *NotifyRequest) GetSubject() string { func (x *NotifyRequest) GetSubject() string {
if x != nil { if x != nil {
return x.Subject return x.Subject
@ -136,6 +145,13 @@ func (x *NotifyRequest) GetContent() string {
return "" return ""
} }
func (x *NotifyRequest) GetMetadata() []byte {
if x != nil {
return x.Metadata
}
return nil
}
func (x *NotifyRequest) GetLinks() []*NotifyLink { func (x *NotifyRequest) GetLinks() []*NotifyLink {
if x != nil { if x != nil {
return x.Links return x.Links
@ -143,13 +159,6 @@ func (x *NotifyRequest) GetLinks() []*NotifyLink {
return nil return nil
} }
func (x *NotifyRequest) GetIsImportant() bool {
if x != nil {
return x.IsImportant
}
return false
}
func (x *NotifyRequest) GetRecipientId() uint64 { func (x *NotifyRequest) GetRecipientId() uint64 {
if x != nil { if x != nil {
return x.RecipientId return x.RecipientId
@ -178,6 +187,13 @@ func (x *NotifyRequest) GetIsRealtime() bool {
return false return false
} }
func (x *NotifyRequest) GetIsForcePush() bool {
if x != nil {
return x.IsForcePush
}
return false
}
type NotifyReply struct { type NotifyReply struct {
state protoimpl.MessageState state protoimpl.MessageState
sizeCache protoimpl.SizeCache sizeCache protoimpl.SizeCache
@ -232,33 +248,36 @@ var file_notify_proto_rawDesc = []byte{
0x70, 0x72, 0x6f, 0x74, 0x6f, 0x22, 0x34, 0x0a, 0x0a, 0x4e, 0x6f, 0x74, 0x69, 0x66, 0x79, 0x4c, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x22, 0x34, 0x0a, 0x0a, 0x4e, 0x6f, 0x74, 0x69, 0x66, 0x79, 0x4c,
0x69, 0x6e, 0x6b, 0x12, 0x14, 0x0a, 0x05, 0x6c, 0x61, 0x62, 0x65, 0x6c, 0x18, 0x01, 0x20, 0x01, 0x69, 0x6e, 0x6b, 0x12, 0x14, 0x0a, 0x05, 0x6c, 0x61, 0x62, 0x65, 0x6c, 0x18, 0x01, 0x20, 0x01,
0x28, 0x09, 0x52, 0x05, 0x6c, 0x61, 0x62, 0x65, 0x6c, 0x12, 0x10, 0x0a, 0x03, 0x75, 0x72, 0x6c, 0x28, 0x09, 0x52, 0x05, 0x6c, 0x61, 0x62, 0x65, 0x6c, 0x12, 0x10, 0x0a, 0x03, 0x75, 0x72, 0x6c,
0x18, 0x02, 0x20, 0x01, 0x28, 0x09, 0x52, 0x03, 0x75, 0x72, 0x6c, 0x22, 0x95, 0x02, 0x0a, 0x0d, 0x18, 0x02, 0x20, 0x01, 0x28, 0x09, 0x52, 0x03, 0x75, 0x72, 0x6c, 0x22, 0xc6, 0x02, 0x0a, 0x0d,
0x4e, 0x6f, 0x74, 0x69, 0x66, 0x79, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x12, 0x18, 0x0a, 0x4e, 0x6f, 0x74, 0x69, 0x66, 0x79, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x12, 0x12, 0x0a,
0x07, 0x73, 0x75, 0x62, 0x6a, 0x65, 0x63, 0x74, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x07, 0x04, 0x74, 0x79, 0x70, 0x65, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x04, 0x74, 0x79, 0x70,
0x73, 0x75, 0x62, 0x6a, 0x65, 0x63, 0x74, 0x12, 0x18, 0x0a, 0x07, 0x63, 0x6f, 0x6e, 0x74, 0x65, 0x65, 0x12, 0x18, 0x0a, 0x07, 0x73, 0x75, 0x62, 0x6a, 0x65, 0x63, 0x74, 0x18, 0x02, 0x20, 0x01,
0x6e, 0x74, 0x18, 0x02, 0x20, 0x01, 0x28, 0x09, 0x52, 0x07, 0x63, 0x6f, 0x6e, 0x74, 0x65, 0x6e, 0x28, 0x09, 0x52, 0x07, 0x73, 0x75, 0x62, 0x6a, 0x65, 0x63, 0x74, 0x12, 0x18, 0x0a, 0x07, 0x63,
0x74, 0x12, 0x27, 0x0a, 0x05, 0x6c, 0x69, 0x6e, 0x6b, 0x73, 0x18, 0x03, 0x20, 0x03, 0x28, 0x0b, 0x6f, 0x6e, 0x74, 0x65, 0x6e, 0x74, 0x18, 0x03, 0x20, 0x01, 0x28, 0x09, 0x52, 0x07, 0x63, 0x6f,
0x6e, 0x74, 0x65, 0x6e, 0x74, 0x12, 0x1a, 0x0a, 0x08, 0x6d, 0x65, 0x74, 0x61, 0x64, 0x61, 0x74,
0x61, 0x18, 0x04, 0x20, 0x01, 0x28, 0x0c, 0x52, 0x08, 0x6d, 0x65, 0x74, 0x61, 0x64, 0x61, 0x74,
0x61, 0x12, 0x27, 0x0a, 0x05, 0x6c, 0x69, 0x6e, 0x6b, 0x73, 0x18, 0x05, 0x20, 0x03, 0x28, 0x0b,
0x32, 0x11, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x2e, 0x4e, 0x6f, 0x74, 0x69, 0x66, 0x79, 0x4c, 0x32, 0x11, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x2e, 0x4e, 0x6f, 0x74, 0x69, 0x66, 0x79, 0x4c,
0x69, 0x6e, 0x6b, 0x52, 0x05, 0x6c, 0x69, 0x6e, 0x6b, 0x73, 0x12, 0x21, 0x0a, 0x0c, 0x69, 0x73, 0x69, 0x6e, 0x6b, 0x52, 0x05, 0x6c, 0x69, 0x6e, 0x6b, 0x73, 0x12, 0x21, 0x0a, 0x0c, 0x72, 0x65,
0x5f, 0x69, 0x6d, 0x70, 0x6f, 0x72, 0x74, 0x61, 0x6e, 0x74, 0x18, 0x04, 0x20, 0x01, 0x28, 0x08, 0x63, 0x69, 0x70, 0x69, 0x65, 0x6e, 0x74, 0x5f, 0x69, 0x64, 0x18, 0x06, 0x20, 0x01, 0x28, 0x04,
0x52, 0x0b, 0x69, 0x73, 0x49, 0x6d, 0x70, 0x6f, 0x72, 0x74, 0x61, 0x6e, 0x74, 0x12, 0x21, 0x0a, 0x52, 0x0b, 0x72, 0x65, 0x63, 0x69, 0x70, 0x69, 0x65, 0x6e, 0x74, 0x49, 0x64, 0x12, 0x1b, 0x0a,
0x0c, 0x72, 0x65, 0x63, 0x69, 0x70, 0x69, 0x65, 0x6e, 0x74, 0x5f, 0x69, 0x64, 0x18, 0x05, 0x20, 0x09, 0x63, 0x6c, 0x69, 0x65, 0x6e, 0x74, 0x5f, 0x69, 0x64, 0x18, 0x07, 0x20, 0x01, 0x28, 0x09,
0x01, 0x28, 0x04, 0x52, 0x0b, 0x72, 0x65, 0x63, 0x69, 0x70, 0x69, 0x65, 0x6e, 0x74, 0x49, 0x64, 0x52, 0x08, 0x63, 0x6c, 0x69, 0x65, 0x6e, 0x74, 0x49, 0x64, 0x12, 0x23, 0x0a, 0x0d, 0x63, 0x6c,
0x12, 0x1b, 0x0a, 0x09, 0x63, 0x6c, 0x69, 0x65, 0x6e, 0x74, 0x5f, 0x69, 0x64, 0x18, 0x06, 0x20, 0x69, 0x65, 0x6e, 0x74, 0x5f, 0x73, 0x65, 0x63, 0x72, 0x65, 0x74, 0x18, 0x08, 0x20, 0x01, 0x28,
0x01, 0x28, 0x09, 0x52, 0x08, 0x63, 0x6c, 0x69, 0x65, 0x6e, 0x74, 0x49, 0x64, 0x12, 0x23, 0x0a, 0x09, 0x52, 0x0c, 0x63, 0x6c, 0x69, 0x65, 0x6e, 0x74, 0x53, 0x65, 0x63, 0x72, 0x65, 0x74, 0x12,
0x0d, 0x63, 0x6c, 0x69, 0x65, 0x6e, 0x74, 0x5f, 0x73, 0x65, 0x63, 0x72, 0x65, 0x74, 0x18, 0x07, 0x1f, 0x0a, 0x0b, 0x69, 0x73, 0x5f, 0x72, 0x65, 0x61, 0x6c, 0x74, 0x69, 0x6d, 0x65, 0x18, 0x09,
0x20, 0x01, 0x28, 0x09, 0x52, 0x0c, 0x63, 0x6c, 0x69, 0x65, 0x6e, 0x74, 0x53, 0x65, 0x63, 0x72, 0x20, 0x01, 0x28, 0x08, 0x52, 0x0a, 0x69, 0x73, 0x52, 0x65, 0x61, 0x6c, 0x74, 0x69, 0x6d, 0x65,
0x65, 0x74, 0x12, 0x1f, 0x0a, 0x0b, 0x69, 0x73, 0x5f, 0x72, 0x65, 0x61, 0x6c, 0x74, 0x69, 0x6d, 0x12, 0x22, 0x0a, 0x0d, 0x69, 0x73, 0x5f, 0x66, 0x6f, 0x72, 0x63, 0x65, 0x5f, 0x70, 0x75, 0x73,
0x65, 0x18, 0x08, 0x20, 0x01, 0x28, 0x08, 0x52, 0x0a, 0x69, 0x73, 0x52, 0x65, 0x61, 0x6c, 0x74, 0x68, 0x18, 0x0a, 0x20, 0x01, 0x28, 0x08, 0x52, 0x0b, 0x69, 0x73, 0x46, 0x6f, 0x72, 0x63, 0x65,
0x69, 0x6d, 0x65, 0x22, 0x26, 0x0a, 0x0b, 0x4e, 0x6f, 0x74, 0x69, 0x66, 0x79, 0x52, 0x65, 0x70, 0x50, 0x75, 0x73, 0x68, 0x22, 0x26, 0x0a, 0x0b, 0x4e, 0x6f, 0x74, 0x69, 0x66, 0x79, 0x52, 0x65,
0x6c, 0x79, 0x12, 0x17, 0x0a, 0x07, 0x69, 0x73, 0x5f, 0x73, 0x65, 0x6e, 0x74, 0x18, 0x01, 0x20, 0x70, 0x6c, 0x79, 0x12, 0x17, 0x0a, 0x07, 0x69, 0x73, 0x5f, 0x73, 0x65, 0x6e, 0x74, 0x18, 0x01,
0x01, 0x28, 0x08, 0x52, 0x06, 0x69, 0x73, 0x53, 0x65, 0x6e, 0x74, 0x32, 0x42, 0x0a, 0x06, 0x4e, 0x20, 0x01, 0x28, 0x08, 0x52, 0x06, 0x69, 0x73, 0x53, 0x65, 0x6e, 0x74, 0x32, 0x42, 0x0a, 0x06,
0x6f, 0x74, 0x69, 0x66, 0x79, 0x12, 0x38, 0x0a, 0x0a, 0x4e, 0x6f, 0x74, 0x69, 0x66, 0x79, 0x55, 0x4e, 0x6f, 0x74, 0x69, 0x66, 0x79, 0x12, 0x38, 0x0a, 0x0a, 0x4e, 0x6f, 0x74, 0x69, 0x66, 0x79,
0x73, 0x65, 0x72, 0x12, 0x14, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x2e, 0x4e, 0x6f, 0x74, 0x69, 0x55, 0x73, 0x65, 0x72, 0x12, 0x14, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x2e, 0x4e, 0x6f, 0x74,
0x66, 0x79, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x12, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x69, 0x66, 0x79, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x12, 0x2e, 0x70, 0x72, 0x6f,
0x6f, 0x2e, 0x4e, 0x6f, 0x74, 0x69, 0x66, 0x79, 0x52, 0x65, 0x70, 0x6c, 0x79, 0x22, 0x00, 0x42, 0x74, 0x6f, 0x2e, 0x4e, 0x6f, 0x74, 0x69, 0x66, 0x79, 0x52, 0x65, 0x70, 0x6c, 0x79, 0x22, 0x00,
0x09, 0x5a, 0x07, 0x2e, 0x3b, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x62, 0x06, 0x70, 0x72, 0x6f, 0x74, 0x42, 0x09, 0x5a, 0x07, 0x2e, 0x3b, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x62, 0x06, 0x70, 0x72, 0x6f,
0x6f, 0x33, 0x74, 0x6f, 0x33,
} }
var ( var (

View File

@ -14,14 +14,16 @@ message NotifyLink {
} }
message NotifyRequest { message NotifyRequest {
string subject = 1; string type = 1;
string content = 2; string subject = 2;
repeated NotifyLink links = 3; string content = 3;
bool is_important = 4; bytes metadata = 4;
uint64 recipient_id = 5; repeated NotifyLink links = 5;
string client_id = 6; uint64 recipient_id = 6;
string client_secret = 7; string client_id = 7;
bool is_realtime = 8; string client_secret = 8;
bool is_realtime = 9;
bool is_force_push = 10;
} }
message NotifyReply { message NotifyReply {

View File

@ -1,7 +1,7 @@
// Code generated by protoc-gen-go-grpc. DO NOT EDIT. // Code generated by protoc-gen-go-grpc. DO NOT EDIT.
// versions: // versions:
// - protoc-gen-go-grpc v1.3.0 // - protoc-gen-go-grpc v1.3.0
// - protoc v4.25.3 // - protoc v5.26.1
// source: notify.proto // source: notify.proto
package proto package proto

View File

@ -1,8 +1,6 @@
package models package models
import ( import (
"time"
"gorm.io/datatypes" "gorm.io/datatypes"
) )
@ -14,9 +12,8 @@ type Notification struct {
Content string `json:"content"` Content string `json:"content"`
Metadata datatypes.JSONMap `json:"metadata"` Metadata datatypes.JSONMap `json:"metadata"`
Links datatypes.JSONSlice[NotificationLink] `json:"links"` Links datatypes.JSONSlice[NotificationLink] `json:"links"`
IsImportant bool `json:"is_important"`
IsRealtime bool `json:"is_realtime" gorm:"-"` IsRealtime bool `json:"is_realtime" gorm:"-"`
ReadAt *time.Time `json:"read_at"` IsForcePush bool `json:"is_force_push" gorm:"-"`
SenderID *uint `json:"sender_id"` SenderID *uint `json:"sender_id"`
RecipientID uint `json:"recipient_id"` RecipientID uint `json:"recipient_id"`
} }

View File

@ -1,14 +1,11 @@
package server package server
import ( import (
"git.solsynth.dev/hydrogen/passport/pkg/utils"
"time"
"git.solsynth.dev/hydrogen/passport/pkg/database" "git.solsynth.dev/hydrogen/passport/pkg/database"
"git.solsynth.dev/hydrogen/passport/pkg/models" "git.solsynth.dev/hydrogen/passport/pkg/models"
"git.solsynth.dev/hydrogen/passport/pkg/services" "git.solsynth.dev/hydrogen/passport/pkg/services"
"git.solsynth.dev/hydrogen/passport/pkg/utils"
"github.com/gofiber/fiber/v2" "github.com/gofiber/fiber/v2"
"github.com/samber/lo"
) )
func getNotifications(c *fiber.Ctx) error { func getNotifications(c *fiber.Ctx) error {
@ -16,12 +13,7 @@ func getNotifications(c *fiber.Ctx) error {
take := c.QueryInt("take", 0) take := c.QueryInt("take", 0)
offset := c.QueryInt("offset", 0) offset := c.QueryInt("offset", 0)
only_unread := !c.QueryBool("past", false)
tx := database.C.Where(&models.Notification{RecipientID: user.ID}).Model(&models.Notification{}) tx := database.C.Where(&models.Notification{RecipientID: user.ID}).Model(&models.Notification{})
if only_unread {
tx = tx.Where("read_at IS NULL")
}
var count int64 var count int64
var notifications []models.Notification var notifications []models.Notification
@ -33,7 +25,6 @@ func getNotifications(c *fiber.Ctx) error {
if err := tx. if err := tx.
Limit(take). Limit(take).
Offset(offset). Offset(offset).
Order("read_at desc").
Find(&notifications).Error; err != nil { Find(&notifications).Error; err != nil {
return fiber.NewError(fiber.StatusInternalServerError, err.Error()) return fiber.NewError(fiber.StatusInternalServerError, err.Error())
} }
@ -56,9 +47,7 @@ func markNotificationRead(c *fiber.Ctx) error {
return fiber.NewError(fiber.StatusNotFound, err.Error()) return fiber.NewError(fiber.StatusNotFound, err.Error())
} }
notify.ReadAt = lo.ToPtr(time.Now()) if err := database.C.Delete(&notify).Error; err != nil {
if err := database.C.Save(&notify).Error; err != nil {
return fiber.NewError(fiber.StatusInternalServerError, err.Error()) return fiber.NewError(fiber.StatusInternalServerError, err.Error())
} else { } else {
return c.SendStatus(fiber.StatusOK) return c.SendStatus(fiber.StatusOK)
@ -78,7 +67,7 @@ func markNotificationReadBatch(c *fiber.Ctx) error {
if err := database.C.Model(&models.Notification{}). if err := database.C.Model(&models.Notification{}).
Where("recipient_id = ? AND id IN ?", user.ID, data.MessageIDs). Where("recipient_id = ? AND id IN ?", user.ID, data.MessageIDs).
Updates(&models.Notification{ReadAt: lo.ToPtr(time.Now())}).Error; err != nil { Delete(&models.Notification{}).Error; err != nil {
return fiber.NewError(fiber.StatusInternalServerError, err.Error()) return fiber.NewError(fiber.StatusInternalServerError, err.Error())
} else { } else {
return c.SendStatus(fiber.StatusOK) return c.SendStatus(fiber.StatusOK)

View File

@ -11,10 +11,12 @@ func notifyUser(c *fiber.Ctx) error {
var data struct { var data struct {
ClientID string `json:"client_id" validate:"required"` ClientID string `json:"client_id" validate:"required"`
ClientSecret string `json:"client_secret" validate:"required"` ClientSecret string `json:"client_secret" validate:"required"`
Type string `json:"type" validate:"required"`
Subject string `json:"subject" validate:"required,max=1024"` Subject string `json:"subject" validate:"required,max=1024"`
Content string `json:"content" validate:"required,max=3072"` Content string `json:"content" validate:"required,max=4096"`
Metadata map[string]any `json:"metadata"`
Links []models.NotificationLink `json:"links"` Links []models.NotificationLink `json:"links"`
IsImportant bool `json:"is_important"` IsForcePush bool `json:"is_force_push"`
IsRealtime bool `json:"is_realtime"` IsRealtime bool `json:"is_realtime"`
UserID uint `json:"user_id" validate:"required"` UserID uint `json:"user_id" validate:"required"`
} }
@ -34,12 +36,12 @@ func notifyUser(c *fiber.Ctx) error {
} }
notification := models.Notification{ notification := models.Notification{
Type: data.Type,
Subject: data.Subject, Subject: data.Subject,
Content: data.Content, Content: data.Content,
Links: data.Links, Links: data.Links,
IsImportant: data.IsImportant,
IsRealtime: data.IsRealtime, IsRealtime: data.IsRealtime,
ReadAt: nil, IsForcePush: data.IsForcePush,
RecipientID: user.ID, RecipientID: user.ID,
SenderID: &client.ID, SenderID: &client.ID,
} }

View File

@ -67,7 +67,7 @@ func PushNotification(notification models.Notification) error {
} }
// Skip push notification when frontend notify is available // Skip push notification when frontend notify is available
if frontendAvailable { if frontendAvailable && !notification.IsForcePush {
return nil return nil
} }