move server/* to scnserver/*
This commit is contained in:
84
scnserver/models/channel.go
Normal file
84
scnserver/models/channel.go
Normal file
@@ -0,0 +1,84 @@
|
||||
package models
|
||||
|
||||
import (
|
||||
"github.com/jmoiron/sqlx"
|
||||
"gogs.mikescher.com/BlackForestBytes/goext/langext"
|
||||
"gogs.mikescher.com/BlackForestBytes/goext/sq"
|
||||
"time"
|
||||
)
|
||||
|
||||
type Channel struct {
|
||||
ChannelID ChannelID
|
||||
OwnerUserID UserID
|
||||
Name string
|
||||
SubscribeKey string
|
||||
SendKey string
|
||||
TimestampCreated time.Time
|
||||
TimestampLastSent *time.Time
|
||||
MessagesSent int
|
||||
}
|
||||
|
||||
func (c Channel) JSON(includeKey bool) ChannelJSON {
|
||||
return ChannelJSON{
|
||||
ChannelID: c.ChannelID,
|
||||
OwnerUserID: c.OwnerUserID,
|
||||
Name: c.Name,
|
||||
SubscribeKey: langext.Conditional(includeKey, langext.Ptr(c.SubscribeKey), nil),
|
||||
SendKey: langext.Conditional(includeKey, langext.Ptr(c.SendKey), nil),
|
||||
TimestampCreated: c.TimestampCreated.Format(time.RFC3339Nano),
|
||||
TimestampLastSent: timeOptFmt(c.TimestampLastSent, time.RFC3339Nano),
|
||||
MessagesSent: c.MessagesSent,
|
||||
}
|
||||
}
|
||||
|
||||
type ChannelJSON struct {
|
||||
ChannelID ChannelID `json:"channel_id"`
|
||||
OwnerUserID UserID `json:"owner_user_id"`
|
||||
Name string `json:"name"`
|
||||
SubscribeKey *string `json:"subscribe_key"` // can be nil, depending on endpoint
|
||||
SendKey *string `json:"send_key"` // can be nil, depending on endpoint
|
||||
TimestampCreated string `json:"timestamp_created"`
|
||||
TimestampLastSent *string `json:"timestamp_lastsent"`
|
||||
MessagesSent int `json:"messages_sent"`
|
||||
}
|
||||
|
||||
type ChannelDB struct {
|
||||
ChannelID ChannelID `db:"channel_id"`
|
||||
OwnerUserID UserID `db:"owner_user_id"`
|
||||
Name string `db:"name"`
|
||||
SubscribeKey string `db:"subscribe_key"`
|
||||
SendKey string `db:"send_key"`
|
||||
TimestampCreated int64 `db:"timestamp_created"`
|
||||
TimestampLastRead *int64 `db:"timestamp_lastread"`
|
||||
TimestampLastSent *int64 `db:"timestamp_lastsent"`
|
||||
MessagesSent int `db:"messages_sent"`
|
||||
}
|
||||
|
||||
func (c ChannelDB) Model() Channel {
|
||||
return Channel{
|
||||
ChannelID: c.ChannelID,
|
||||
OwnerUserID: c.OwnerUserID,
|
||||
Name: c.Name,
|
||||
SubscribeKey: c.SubscribeKey,
|
||||
SendKey: c.SendKey,
|
||||
TimestampCreated: time.UnixMilli(c.TimestampCreated),
|
||||
TimestampLastSent: timeOptFromMilli(c.TimestampLastSent),
|
||||
MessagesSent: c.MessagesSent,
|
||||
}
|
||||
}
|
||||
|
||||
func DecodeChannel(r *sqlx.Rows) (Channel, error) {
|
||||
data, err := sq.ScanSingle[ChannelDB](r, true)
|
||||
if err != nil {
|
||||
return Channel{}, err
|
||||
}
|
||||
return data.Model(), nil
|
||||
}
|
||||
|
||||
func DecodeChannels(r *sqlx.Rows) ([]Channel, error) {
|
||||
data, err := sq.ScanAll[ChannelDB](r, true)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return langext.ArrMap(data, func(v ChannelDB) Channel { return v.Model() }), nil
|
||||
}
|
85
scnserver/models/client.go
Normal file
85
scnserver/models/client.go
Normal file
@@ -0,0 +1,85 @@
|
||||
package models
|
||||
|
||||
import (
|
||||
"github.com/jmoiron/sqlx"
|
||||
"gogs.mikescher.com/BlackForestBytes/goext/langext"
|
||||
"gogs.mikescher.com/BlackForestBytes/goext/sq"
|
||||
"time"
|
||||
)
|
||||
|
||||
type ClientType string
|
||||
|
||||
const (
|
||||
ClientTypeAndroid ClientType = "ANDROID"
|
||||
ClientTypeIOS ClientType = "IOS"
|
||||
)
|
||||
|
||||
type Client struct {
|
||||
ClientID ClientID
|
||||
UserID UserID
|
||||
Type ClientType
|
||||
FCMToken *string
|
||||
TimestampCreated time.Time
|
||||
AgentModel string
|
||||
AgentVersion string
|
||||
}
|
||||
|
||||
func (c Client) JSON() ClientJSON {
|
||||
return ClientJSON{
|
||||
ClientID: c.ClientID,
|
||||
UserID: c.UserID,
|
||||
Type: c.Type,
|
||||
FCMToken: c.FCMToken,
|
||||
TimestampCreated: c.TimestampCreated.Format(time.RFC3339Nano),
|
||||
AgentModel: c.AgentModel,
|
||||
AgentVersion: c.AgentVersion,
|
||||
}
|
||||
}
|
||||
|
||||
type ClientJSON struct {
|
||||
ClientID ClientID `json:"client_id"`
|
||||
UserID UserID `json:"user_id"`
|
||||
Type ClientType `json:"type"`
|
||||
FCMToken *string `json:"fcm_token"`
|
||||
TimestampCreated string `json:"timestamp_created"`
|
||||
AgentModel string `json:"agent_model"`
|
||||
AgentVersion string `json:"agent_version"`
|
||||
}
|
||||
|
||||
type ClientDB struct {
|
||||
ClientID ClientID `db:"client_id"`
|
||||
UserID UserID `db:"user_id"`
|
||||
Type ClientType `db:"type"`
|
||||
FCMToken *string `db:"fcm_token"`
|
||||
TimestampCreated int64 `db:"timestamp_created"`
|
||||
AgentModel string `db:"agent_model"`
|
||||
AgentVersion string `db:"agent_version"`
|
||||
}
|
||||
|
||||
func (c ClientDB) Model() Client {
|
||||
return Client{
|
||||
ClientID: c.ClientID,
|
||||
UserID: c.UserID,
|
||||
Type: c.Type,
|
||||
FCMToken: c.FCMToken,
|
||||
TimestampCreated: time.UnixMilli(c.TimestampCreated),
|
||||
AgentModel: c.AgentModel,
|
||||
AgentVersion: c.AgentVersion,
|
||||
}
|
||||
}
|
||||
|
||||
func DecodeClient(r *sqlx.Rows) (Client, error) {
|
||||
data, err := sq.ScanSingle[ClientDB](r, true)
|
||||
if err != nil {
|
||||
return Client{}, err
|
||||
}
|
||||
return data.Model(), nil
|
||||
}
|
||||
|
||||
func DecodeClients(r *sqlx.Rows) ([]Client, error) {
|
||||
data, err := sq.ScanAll[ClientDB](r, true)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return langext.ArrMap(data, func(v ClientDB) Client { return v.Model() }), nil
|
||||
}
|
11
scnserver/models/compat.go
Normal file
11
scnserver/models/compat.go
Normal file
@@ -0,0 +1,11 @@
|
||||
package models
|
||||
|
||||
type CompatMessage struct {
|
||||
Title string `json:"title"`
|
||||
Body string `json:"body"`
|
||||
Priority int `json:"priority"`
|
||||
Timestamp int64 `json:"timestamp"`
|
||||
UserMessageID *string `json:"usr_msg_id"`
|
||||
SCNMessageID int64 `json:"scn_msg_id"`
|
||||
Trimmed *bool `json:"trimmed"`
|
||||
}
|
105
scnserver/models/delivery.go
Normal file
105
scnserver/models/delivery.go
Normal file
@@ -0,0 +1,105 @@
|
||||
package models
|
||||
|
||||
import (
|
||||
"github.com/jmoiron/sqlx"
|
||||
"gogs.mikescher.com/BlackForestBytes/goext/langext"
|
||||
"gogs.mikescher.com/BlackForestBytes/goext/sq"
|
||||
"time"
|
||||
)
|
||||
|
||||
type DeliveryStatus string
|
||||
|
||||
const (
|
||||
DeliveryStatusRetry DeliveryStatus = "RETRY"
|
||||
DeliveryStatusSuccess DeliveryStatus = "SUCCESS"
|
||||
DeliveryStatusFailed DeliveryStatus = "FAILED"
|
||||
)
|
||||
|
||||
type Delivery struct {
|
||||
DeliveryID DeliveryID
|
||||
SCNMessageID SCNMessageID
|
||||
ReceiverUserID UserID
|
||||
ReceiverClientID ClientID
|
||||
TimestampCreated time.Time
|
||||
TimestampFinalized *time.Time
|
||||
Status DeliveryStatus
|
||||
RetryCount int
|
||||
NextDelivery *time.Time
|
||||
FCMMessageID *string
|
||||
}
|
||||
|
||||
func (d Delivery) JSON() DeliveryJSON {
|
||||
return DeliveryJSON{
|
||||
DeliveryID: d.DeliveryID,
|
||||
SCNMessageID: d.SCNMessageID,
|
||||
ReceiverUserID: d.ReceiverUserID,
|
||||
ReceiverClientID: d.ReceiverClientID,
|
||||
TimestampCreated: d.TimestampCreated.Format(time.RFC3339Nano),
|
||||
TimestampFinalized: timeOptFmt(d.TimestampFinalized, time.RFC3339Nano),
|
||||
Status: d.Status,
|
||||
RetryCount: d.RetryCount,
|
||||
NextDelivery: timeOptFmt(d.NextDelivery, time.RFC3339Nano),
|
||||
FCMMessageID: d.FCMMessageID,
|
||||
}
|
||||
}
|
||||
|
||||
func (d Delivery) MaxRetryCount() int {
|
||||
return 5
|
||||
}
|
||||
|
||||
type DeliveryJSON struct {
|
||||
DeliveryID DeliveryID `json:"delivery_id"`
|
||||
SCNMessageID SCNMessageID `json:"scn_message_id"`
|
||||
ReceiverUserID UserID `json:"receiver_user_id"`
|
||||
ReceiverClientID ClientID `json:"receiver_client_id"`
|
||||
TimestampCreated string `json:"timestamp_created"`
|
||||
TimestampFinalized *string `json:"tiestamp_finalized"`
|
||||
Status DeliveryStatus `json:"status"`
|
||||
RetryCount int `json:"retry_count"`
|
||||
NextDelivery *string `json:"next_delivery"`
|
||||
FCMMessageID *string `json:"fcm_message_id"`
|
||||
}
|
||||
|
||||
type DeliveryDB struct {
|
||||
DeliveryID DeliveryID `db:"delivery_id"`
|
||||
SCNMessageID SCNMessageID `db:"scn_message_id"`
|
||||
ReceiverUserID UserID `db:"receiver_user_id"`
|
||||
ReceiverClientID ClientID `db:"receiver_client_id"`
|
||||
TimestampCreated int64 `db:"timestamp_created"`
|
||||
TimestampFinalized *int64 `db:"tiestamp_finalized"`
|
||||
Status DeliveryStatus `db:"status"`
|
||||
RetryCount int `db:"retry_count"`
|
||||
NextDelivery *int64 `db:"next_delivery"`
|
||||
FCMMessageID *string `db:"fcm_message_id"`
|
||||
}
|
||||
|
||||
func (d DeliveryDB) Model() Delivery {
|
||||
return Delivery{
|
||||
DeliveryID: d.DeliveryID,
|
||||
SCNMessageID: d.SCNMessageID,
|
||||
ReceiverUserID: d.ReceiverUserID,
|
||||
ReceiverClientID: d.ReceiverClientID,
|
||||
TimestampCreated: time.UnixMilli(d.TimestampCreated),
|
||||
TimestampFinalized: timeOptFromMilli(d.TimestampFinalized),
|
||||
Status: d.Status,
|
||||
RetryCount: d.RetryCount,
|
||||
NextDelivery: timeOptFromMilli(d.NextDelivery),
|
||||
FCMMessageID: d.FCMMessageID,
|
||||
}
|
||||
}
|
||||
|
||||
func DecodeDelivery(r *sqlx.Rows) (Delivery, error) {
|
||||
data, err := sq.ScanSingle[DeliveryDB](r, true)
|
||||
if err != nil {
|
||||
return Delivery{}, err
|
||||
}
|
||||
return data.Model(), nil
|
||||
}
|
||||
|
||||
func DecodeDeliveries(r *sqlx.Rows) ([]Delivery, error) {
|
||||
data, err := sq.ScanAll[DeliveryDB](r, true)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return langext.ArrMap(data, func(v DeliveryDB) Delivery { return v.Model() }), nil
|
||||
}
|
68
scnserver/models/ids.go
Normal file
68
scnserver/models/ids.go
Normal file
@@ -0,0 +1,68 @@
|
||||
package models
|
||||
|
||||
import "strconv"
|
||||
|
||||
type EntityID interface {
|
||||
IntID() int64
|
||||
String() string
|
||||
}
|
||||
|
||||
type UserID int64
|
||||
|
||||
func (id UserID) IntID() int64 {
|
||||
return int64(id)
|
||||
}
|
||||
|
||||
func (id UserID) String() string {
|
||||
return strconv.FormatInt(int64(id), 10)
|
||||
}
|
||||
|
||||
type ChannelID int64
|
||||
|
||||
func (id ChannelID) IntID() int64 {
|
||||
return int64(id)
|
||||
}
|
||||
|
||||
func (id ChannelID) String() string {
|
||||
return strconv.FormatInt(int64(id), 10)
|
||||
}
|
||||
|
||||
type DeliveryID int64
|
||||
|
||||
func (id DeliveryID) IntID() int64 {
|
||||
return int64(id)
|
||||
}
|
||||
|
||||
func (id DeliveryID) String() string {
|
||||
return strconv.FormatInt(int64(id), 10)
|
||||
}
|
||||
|
||||
type SCNMessageID int64
|
||||
|
||||
func (id SCNMessageID) IntID() int64 {
|
||||
return int64(id)
|
||||
}
|
||||
|
||||
func (id SCNMessageID) String() string {
|
||||
return strconv.FormatInt(int64(id), 10)
|
||||
}
|
||||
|
||||
type SubscriptionID int64
|
||||
|
||||
func (id SubscriptionID) IntID() int64 {
|
||||
return int64(id)
|
||||
}
|
||||
|
||||
func (id SubscriptionID) String() string {
|
||||
return strconv.FormatInt(int64(id), 10)
|
||||
}
|
||||
|
||||
type ClientID int64
|
||||
|
||||
func (id ClientID) IntID() int64 {
|
||||
return int64(id)
|
||||
}
|
||||
|
||||
func (id ClientID) String() string {
|
||||
return strconv.FormatInt(int64(id), 10)
|
||||
}
|
162
scnserver/models/message.go
Normal file
162
scnserver/models/message.go
Normal file
@@ -0,0 +1,162 @@
|
||||
package models
|
||||
|
||||
import (
|
||||
"github.com/jmoiron/sqlx"
|
||||
"gogs.mikescher.com/BlackForestBytes/goext/langext"
|
||||
"gogs.mikescher.com/BlackForestBytes/goext/sq"
|
||||
"time"
|
||||
)
|
||||
|
||||
const (
|
||||
ContentLengthTrim = 1900
|
||||
ContentLengthShort = 200
|
||||
)
|
||||
|
||||
type Message struct {
|
||||
SCNMessageID SCNMessageID
|
||||
SenderUserID UserID
|
||||
OwnerUserID UserID
|
||||
ChannelName string
|
||||
ChannelID ChannelID
|
||||
SenderName *string
|
||||
SenderIP string
|
||||
TimestampReal time.Time
|
||||
TimestampClient *time.Time
|
||||
Title string
|
||||
Content *string
|
||||
Priority int
|
||||
UserMessageID *string
|
||||
Deleted bool
|
||||
}
|
||||
|
||||
func (m Message) FullJSON() MessageJSON {
|
||||
return MessageJSON{
|
||||
SCNMessageID: m.SCNMessageID,
|
||||
SenderUserID: m.SenderUserID,
|
||||
OwnerUserID: m.OwnerUserID,
|
||||
ChannelName: m.ChannelName,
|
||||
ChannelID: m.ChannelID,
|
||||
SenderName: m.SenderName,
|
||||
SenderIP: m.SenderIP,
|
||||
Timestamp: m.Timestamp().Format(time.RFC3339Nano),
|
||||
Title: m.Title,
|
||||
Content: m.Content,
|
||||
Priority: m.Priority,
|
||||
UserMessageID: m.UserMessageID,
|
||||
Trimmed: false,
|
||||
}
|
||||
}
|
||||
|
||||
func (m Message) TrimmedJSON() MessageJSON {
|
||||
return MessageJSON{
|
||||
SCNMessageID: m.SCNMessageID,
|
||||
SenderUserID: m.SenderUserID,
|
||||
OwnerUserID: m.OwnerUserID,
|
||||
ChannelName: m.ChannelName,
|
||||
ChannelID: m.ChannelID,
|
||||
SenderName: m.SenderName,
|
||||
SenderIP: m.SenderIP,
|
||||
Timestamp: m.Timestamp().Format(time.RFC3339Nano),
|
||||
Title: m.Title,
|
||||
Content: m.TrimmedContent(),
|
||||
Priority: m.Priority,
|
||||
UserMessageID: m.UserMessageID,
|
||||
Trimmed: m.NeedsTrim(),
|
||||
}
|
||||
}
|
||||
|
||||
func (m Message) Timestamp() time.Time {
|
||||
return langext.Coalesce(m.TimestampClient, m.TimestampReal)
|
||||
}
|
||||
|
||||
func (m Message) NeedsTrim() bool {
|
||||
return m.Content != nil && len(*m.Content) > ContentLengthTrim
|
||||
}
|
||||
|
||||
func (m Message) TrimmedContent() *string {
|
||||
if m.Content == nil {
|
||||
return nil
|
||||
}
|
||||
if !m.NeedsTrim() {
|
||||
return m.Content
|
||||
}
|
||||
return langext.Ptr(langext.Coalesce(m.Content, "")[0:ContentLengthTrim-3] + "...")
|
||||
}
|
||||
|
||||
func (m Message) ShortContent() string {
|
||||
if m.Content == nil {
|
||||
return ""
|
||||
}
|
||||
if len(*m.Content) < ContentLengthShort {
|
||||
return *m.Content
|
||||
}
|
||||
return (*m.Content)[0:ContentLengthShort-3] + "..."
|
||||
}
|
||||
|
||||
type MessageJSON struct {
|
||||
SCNMessageID SCNMessageID `json:"scn_message_id"`
|
||||
SenderUserID UserID `json:"sender_user_id"`
|
||||
OwnerUserID UserID `json:"owner_user_id"`
|
||||
ChannelName string `json:"channel_name"`
|
||||
ChannelID ChannelID `json:"channel_id"`
|
||||
SenderName *string `json:"sender_name"`
|
||||
SenderIP string `json:"sender_ip"`
|
||||
Timestamp string `json:"timestamp"`
|
||||
Title string `json:"title"`
|
||||
Content *string `json:"content"`
|
||||
Priority int `json:"priority"`
|
||||
UserMessageID *string `json:"usr_message_id"`
|
||||
Trimmed bool `json:"trimmed"`
|
||||
}
|
||||
|
||||
type MessageDB struct {
|
||||
SCNMessageID SCNMessageID `db:"scn_message_id"`
|
||||
SenderUserID UserID `db:"sender_user_id"`
|
||||
OwnerUserID UserID `db:"owner_user_id"`
|
||||
ChannelName string `db:"channel_name"`
|
||||
ChannelID ChannelID `db:"channel_id"`
|
||||
SenderName *string `db:"sender_name"`
|
||||
SenderIP string `db:"sender_ip"`
|
||||
TimestampReal int64 `db:"timestamp_real"`
|
||||
TimestampClient *int64 `db:"timestamp_client"`
|
||||
Title string `db:"title"`
|
||||
Content *string `db:"content"`
|
||||
Priority int `db:"priority"`
|
||||
UserMessageID *string `db:"usr_message_id"`
|
||||
Deleted int `db:"deleted"`
|
||||
}
|
||||
|
||||
func (m MessageDB) Model() Message {
|
||||
return Message{
|
||||
SCNMessageID: m.SCNMessageID,
|
||||
SenderUserID: m.SenderUserID,
|
||||
OwnerUserID: m.OwnerUserID,
|
||||
ChannelName: m.ChannelName,
|
||||
ChannelID: m.ChannelID,
|
||||
SenderName: m.SenderName,
|
||||
SenderIP: m.SenderIP,
|
||||
TimestampReal: time.UnixMilli(m.TimestampReal),
|
||||
TimestampClient: timeOptFromMilli(m.TimestampClient),
|
||||
Title: m.Title,
|
||||
Content: m.Content,
|
||||
Priority: m.Priority,
|
||||
UserMessageID: m.UserMessageID,
|
||||
Deleted: m.Deleted != 0,
|
||||
}
|
||||
}
|
||||
|
||||
func DecodeMessage(r *sqlx.Rows) (Message, error) {
|
||||
data, err := sq.ScanSingle[MessageDB](r, true)
|
||||
if err != nil {
|
||||
return Message{}, err
|
||||
}
|
||||
return data.Model(), nil
|
||||
}
|
||||
|
||||
func DecodeMessages(r *sqlx.Rows) ([]Message, error) {
|
||||
data, err := sq.ScanAll[MessageDB](r, true)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return langext.ArrMap(data, func(v MessageDB) Message { return v.Model() }), nil
|
||||
}
|
239
scnserver/models/messagefilter.go
Normal file
239
scnserver/models/messagefilter.go
Normal file
@@ -0,0 +1,239 @@
|
||||
package models
|
||||
|
||||
import (
|
||||
"crypto/sha512"
|
||||
"encoding/hex"
|
||||
"fmt"
|
||||
"gogs.mikescher.com/BlackForestBytes/goext/dataext"
|
||||
"gogs.mikescher.com/BlackForestBytes/goext/langext"
|
||||
"gogs.mikescher.com/BlackForestBytes/goext/mathext"
|
||||
"gogs.mikescher.com/BlackForestBytes/goext/sq"
|
||||
"strconv"
|
||||
"strings"
|
||||
"time"
|
||||
)
|
||||
|
||||
type MessageFilter struct {
|
||||
ConfirmedSubscriptionBy *UserID
|
||||
SearchString *[]string
|
||||
Sender *[]UserID
|
||||
Owner *[]UserID
|
||||
ChannelNameCS *[]string // case-sensitive
|
||||
ChannelNameCI *[]string // case-insensitive
|
||||
ChannelID *[]ChannelID
|
||||
SenderNameCS *[]string // case-sensitive
|
||||
SenderNameCI *[]string // case-insensitive
|
||||
SenderIP *[]string
|
||||
TimestampCoalesce *time.Time
|
||||
TimestampCoalesceAfter *time.Time
|
||||
TimestampCoalesceBefore *time.Time
|
||||
TimestampReal *time.Time
|
||||
TimestampRealAfter *time.Time
|
||||
TimestampRealBefore *time.Time
|
||||
TimestampClient *time.Time
|
||||
TimestampClientAfter *time.Time
|
||||
TimestampClientBefore *time.Time
|
||||
TitleCS *string // case-sensitive
|
||||
TitleCI *string // case-insensitive
|
||||
Priority *[]int
|
||||
UserMessageID *[]string
|
||||
OnlyDeleted bool
|
||||
IncludeDeleted bool
|
||||
}
|
||||
|
||||
func (f MessageFilter) SQL() (string, string, sq.PP, error) {
|
||||
|
||||
joinClause := ""
|
||||
if f.ConfirmedSubscriptionBy != nil {
|
||||
joinClause += " LEFT JOIN subscriptions subs on messages.channel_id = subs.channel_id "
|
||||
}
|
||||
if f.SearchString != nil {
|
||||
joinClause += " JOIN messages_fts mfts on (mfts.rowid = messages.scn_message_id) "
|
||||
}
|
||||
|
||||
sqlClauses := make([]string, 0)
|
||||
|
||||
params := sq.PP{}
|
||||
|
||||
if f.OnlyDeleted {
|
||||
sqlClauses = append(sqlClauses, "(deleted=1)")
|
||||
} else if f.IncludeDeleted {
|
||||
// nothing, return all
|
||||
} else {
|
||||
sqlClauses = append(sqlClauses, "(deleted=0)") // default
|
||||
}
|
||||
|
||||
if f.ConfirmedSubscriptionBy != nil {
|
||||
sqlClauses = append(sqlClauses, "(subs.subscriber_user_id = :sub_uid AND subs.confirmed = 1)")
|
||||
params["sub_uid"] = *f.ConfirmedSubscriptionBy
|
||||
}
|
||||
|
||||
if f.Sender != nil {
|
||||
filter := make([]string, 0)
|
||||
for i, v := range *f.Sender {
|
||||
filter = append(filter, fmt.Sprintf("(sender_user_id = :sender_%d)", i))
|
||||
params[fmt.Sprintf("sender_%d", i)] = v
|
||||
}
|
||||
sqlClauses = append(sqlClauses, "("+strings.Join(filter, " OR ")+")")
|
||||
}
|
||||
|
||||
if f.Owner != nil {
|
||||
filter := make([]string, 0)
|
||||
for i, v := range *f.Sender {
|
||||
filter = append(filter, fmt.Sprintf("(owner_user_id = :owner_%d)", i))
|
||||
params[fmt.Sprintf("owner_%d", i)] = v
|
||||
}
|
||||
sqlClauses = append(sqlClauses, "("+strings.Join(filter, " OR ")+")")
|
||||
}
|
||||
|
||||
if f.ChannelNameCI != nil {
|
||||
filter := make([]string, 0)
|
||||
for i, v := range *f.ChannelNameCI {
|
||||
filter = append(filter, fmt.Sprintf("(channel_name = :channelnameci_%d COLLATE NOCASE)", i))
|
||||
params[fmt.Sprintf("channelnameci_%d", i)] = v
|
||||
}
|
||||
sqlClauses = append(sqlClauses, "("+strings.Join(filter, " OR ")+")")
|
||||
}
|
||||
|
||||
if f.ChannelNameCS != nil {
|
||||
filter := make([]string, 0)
|
||||
for i, v := range *f.ChannelNameCS {
|
||||
filter = append(filter, fmt.Sprintf("(channel_name = :channelnamecs_%d COLLATE BINARY)", i))
|
||||
params[fmt.Sprintf("channelnamecs_%d", i)] = v
|
||||
}
|
||||
sqlClauses = append(sqlClauses, "("+strings.Join(filter, " OR ")+")")
|
||||
}
|
||||
|
||||
if f.ChannelID != nil {
|
||||
filter := make([]string, 0)
|
||||
for i, v := range *f.ChannelID {
|
||||
filter = append(filter, fmt.Sprintf("(channel_id = :channelid_%d)", i))
|
||||
params[fmt.Sprintf("channelid_%d", i)] = v
|
||||
}
|
||||
sqlClauses = append(sqlClauses, "("+strings.Join(filter, " OR ")+")")
|
||||
}
|
||||
|
||||
if f.SenderNameCI != nil {
|
||||
filter := make([]string, 0)
|
||||
for i, v := range *f.ChannelNameCI {
|
||||
filter = append(filter, fmt.Sprintf("(sender_name = :sendernameci_%d COLLATE NOCASE)", i))
|
||||
params[fmt.Sprintf("sendernameci_%d", i)] = v
|
||||
}
|
||||
sqlClauses = append(sqlClauses, "(sender_name IS NOT NULL AND ("+strings.Join(filter, " OR ")+"))")
|
||||
}
|
||||
|
||||
if f.SenderNameCS != nil {
|
||||
filter := make([]string, 0)
|
||||
for i, v := range *f.ChannelNameCS {
|
||||
filter = append(filter, fmt.Sprintf("(sender_name = :sendernamecs_%d COLLATE BINARY)", i))
|
||||
params[fmt.Sprintf("sendernamecs_%d", i)] = v
|
||||
}
|
||||
sqlClauses = append(sqlClauses, "(sender_name IS NOT NULL AND ("+strings.Join(filter, " OR ")+"))")
|
||||
}
|
||||
|
||||
if f.SenderIP != nil {
|
||||
filter := make([]string, 0)
|
||||
for i, v := range *f.SenderIP {
|
||||
filter = append(filter, fmt.Sprintf("(sender_ip = :senderip_%d)", i))
|
||||
params[fmt.Sprintf("senderip_%d", i)] = v
|
||||
}
|
||||
sqlClauses = append(sqlClauses, "("+strings.Join(filter, " OR ")+")")
|
||||
}
|
||||
|
||||
if f.TimestampCoalesce != nil {
|
||||
sqlClauses = append(sqlClauses, "(COALESCE(timestamp_client, timestamp_real) = :ts_equals)")
|
||||
params["ts_equals"] = (*f.TimestampCoalesce).UnixMilli()
|
||||
}
|
||||
|
||||
if f.TimestampCoalesceAfter != nil {
|
||||
sqlClauses = append(sqlClauses, "(COALESCE(timestamp_client, timestamp_real) > :ts_after)")
|
||||
params["ts_after"] = (*f.TimestampCoalesceAfter).UnixMilli()
|
||||
}
|
||||
|
||||
if f.TimestampCoalesceBefore != nil {
|
||||
sqlClauses = append(sqlClauses, "(COALESCE(timestamp_client, timestamp_real) < :ts_before)")
|
||||
params["ts_before"] = (*f.TimestampCoalesceBefore).UnixMilli()
|
||||
}
|
||||
|
||||
if f.TimestampReal != nil {
|
||||
sqlClauses = append(sqlClauses, "(timestamp_real = :ts_real_equals)")
|
||||
params["ts_real_equals"] = (*f.TimestampRealAfter).UnixMilli()
|
||||
}
|
||||
|
||||
if f.TimestampRealAfter != nil {
|
||||
sqlClauses = append(sqlClauses, "(timestamp_real > :ts_real_after)")
|
||||
params["ts_real_after"] = (*f.TimestampRealAfter).UnixMilli()
|
||||
}
|
||||
|
||||
if f.TimestampRealBefore != nil {
|
||||
sqlClauses = append(sqlClauses, "(timestamp_real < :ts_real_before)")
|
||||
params["ts_real_before"] = (*f.TimestampRealAfter).UnixMilli()
|
||||
}
|
||||
|
||||
if f.TimestampClient != nil {
|
||||
sqlClauses = append(sqlClauses, "(timestamp_client IS NOT NULL AND timestamp_client = :ts_client_equals)")
|
||||
params["ts_client_equals"] = (*f.TimestampClient).UnixMilli()
|
||||
}
|
||||
|
||||
if f.TimestampClientAfter != nil {
|
||||
sqlClauses = append(sqlClauses, "(timestamp_client IS NOT NULL AND timestamp_client > :ts_client_after)")
|
||||
params["ts_client_after"] = (*f.TimestampClientAfter).UnixMilli()
|
||||
}
|
||||
|
||||
if f.TimestampClientBefore != nil {
|
||||
sqlClauses = append(sqlClauses, "(timestamp_client IS NOT NULL AND timestamp_client < :ts_client_before)")
|
||||
params["ts_client_before"] = (*f.TimestampClientBefore).UnixMilli()
|
||||
}
|
||||
|
||||
if f.TitleCI != nil {
|
||||
sqlClauses = append(sqlClauses, "(title = :titleci COLLATE NOCASE)")
|
||||
params["titleci"] = *f.TitleCI
|
||||
}
|
||||
|
||||
if f.TitleCS != nil {
|
||||
sqlClauses = append(sqlClauses, "(title = :titleci COLLATE BINARY)")
|
||||
params["titleci"] = *f.TitleCI
|
||||
}
|
||||
|
||||
if f.Priority != nil {
|
||||
prioList := "(" + strings.Join(langext.ArrMap(*f.Priority, func(p int) string { return strconv.Itoa(p) }), ", ") + ")"
|
||||
sqlClauses = append(sqlClauses, "(priority IN "+prioList+")")
|
||||
}
|
||||
|
||||
if f.UserMessageID != nil {
|
||||
filter := make([]string, 0)
|
||||
for i, v := range *f.UserMessageID {
|
||||
filter = append(filter, fmt.Sprintf("(usr_message_id = :usermessageid_%d)", i))
|
||||
params[fmt.Sprintf("usermessageid_%d", i)] = v
|
||||
}
|
||||
sqlClauses = append(sqlClauses, "(usr_message_id IS NOT NULL AND ("+strings.Join(filter, " OR ")+"))")
|
||||
}
|
||||
|
||||
if f.SearchString != nil {
|
||||
filter := make([]string, 0)
|
||||
for i, v := range *f.SearchString {
|
||||
filter = append(filter, fmt.Sprintf("(messages_fts match :searchstring_%d)", i))
|
||||
params[fmt.Sprintf("searchstring_%d", i)] = v
|
||||
}
|
||||
sqlClauses = append(sqlClauses, "("+strings.Join(filter, " OR ")+")")
|
||||
}
|
||||
|
||||
sqlClause := ""
|
||||
if len(sqlClauses) > 0 {
|
||||
sqlClause = strings.Join(sqlClauses, " AND ")
|
||||
} else {
|
||||
sqlClause = "1=1"
|
||||
}
|
||||
|
||||
return sqlClause, joinClause, params, nil
|
||||
}
|
||||
|
||||
func (f MessageFilter) Hash() string {
|
||||
bh, err := dataext.StructHash(f, dataext.StructHashOptions{HashAlgo: sha512.New()})
|
||||
if err != nil {
|
||||
return "00000000"
|
||||
}
|
||||
|
||||
str := hex.EncodeToString(bh)
|
||||
return str[0:mathext.Min(8, len(bh))]
|
||||
}
|
78
scnserver/models/subscription.go
Normal file
78
scnserver/models/subscription.go
Normal file
@@ -0,0 +1,78 @@
|
||||
package models
|
||||
|
||||
import (
|
||||
"github.com/jmoiron/sqlx"
|
||||
"gogs.mikescher.com/BlackForestBytes/goext/langext"
|
||||
"gogs.mikescher.com/BlackForestBytes/goext/sq"
|
||||
"time"
|
||||
)
|
||||
|
||||
type Subscription struct {
|
||||
SubscriptionID SubscriptionID
|
||||
SubscriberUserID UserID
|
||||
ChannelOwnerUserID UserID
|
||||
ChannelID ChannelID
|
||||
ChannelName string
|
||||
TimestampCreated time.Time
|
||||
Confirmed bool
|
||||
}
|
||||
|
||||
func (s Subscription) JSON() SubscriptionJSON {
|
||||
return SubscriptionJSON{
|
||||
SubscriptionID: s.SubscriptionID,
|
||||
SubscriberUserID: s.SubscriberUserID,
|
||||
ChannelOwnerUserID: s.ChannelOwnerUserID,
|
||||
ChannelID: s.ChannelID,
|
||||
ChannelName: s.ChannelName,
|
||||
TimestampCreated: s.TimestampCreated.Format(time.RFC3339Nano),
|
||||
Confirmed: s.Confirmed,
|
||||
}
|
||||
}
|
||||
|
||||
type SubscriptionJSON struct {
|
||||
SubscriptionID SubscriptionID `json:"subscription_id"`
|
||||
SubscriberUserID UserID `json:"subscriber_user_id"`
|
||||
ChannelOwnerUserID UserID `json:"channel_owner_user_id"`
|
||||
ChannelID ChannelID `json:"channel_id"`
|
||||
ChannelName string `json:"channel_name"`
|
||||
TimestampCreated string `json:"timestamp_created"`
|
||||
Confirmed bool `json:"confirmed"`
|
||||
}
|
||||
|
||||
type SubscriptionDB struct {
|
||||
SubscriptionID SubscriptionID `db:"subscription_id"`
|
||||
SubscriberUserID UserID `db:"subscriber_user_id"`
|
||||
ChannelOwnerUserID UserID `db:"channel_owner_user_id"`
|
||||
ChannelID ChannelID `db:"channel_id"`
|
||||
ChannelName string `db:"channel_name"`
|
||||
TimestampCreated int64 `db:"timestamp_created"`
|
||||
Confirmed int `db:"confirmed"`
|
||||
}
|
||||
|
||||
func (s SubscriptionDB) Model() Subscription {
|
||||
return Subscription{
|
||||
SubscriptionID: s.SubscriptionID,
|
||||
SubscriberUserID: s.SubscriberUserID,
|
||||
ChannelOwnerUserID: s.ChannelOwnerUserID,
|
||||
ChannelID: s.ChannelID,
|
||||
ChannelName: s.ChannelName,
|
||||
TimestampCreated: time.UnixMilli(s.TimestampCreated),
|
||||
Confirmed: s.Confirmed != 0,
|
||||
}
|
||||
}
|
||||
|
||||
func DecodeSubscription(r *sqlx.Rows) (Subscription, error) {
|
||||
data, err := sq.ScanSingle[SubscriptionDB](r, true)
|
||||
if err != nil {
|
||||
return Subscription{}, err
|
||||
}
|
||||
return data.Model(), nil
|
||||
}
|
||||
|
||||
func DecodeSubscriptions(r *sqlx.Rows) ([]Subscription, error) {
|
||||
data, err := sq.ScanAll[SubscriptionDB](r, true)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return langext.ArrMap(data, func(v SubscriptionDB) Subscription { return v.Model() }), nil
|
||||
}
|
179
scnserver/models/user.go
Normal file
179
scnserver/models/user.go
Normal file
@@ -0,0 +1,179 @@
|
||||
package models
|
||||
|
||||
import (
|
||||
scn "blackforestbytes.com/simplecloudnotifier"
|
||||
"github.com/jmoiron/sqlx"
|
||||
"gogs.mikescher.com/BlackForestBytes/goext/langext"
|
||||
"gogs.mikescher.com/BlackForestBytes/goext/sq"
|
||||
"time"
|
||||
)
|
||||
|
||||
type User struct {
|
||||
UserID UserID
|
||||
Username *string
|
||||
SendKey string
|
||||
ReadKey string
|
||||
AdminKey string
|
||||
TimestampCreated time.Time
|
||||
TimestampLastRead *time.Time
|
||||
TimestampLastSent *time.Time
|
||||
MessagesSent int
|
||||
QuotaUsed int
|
||||
QuotaUsedDay *string
|
||||
IsPro bool
|
||||
ProToken *string
|
||||
}
|
||||
|
||||
func (u User) JSON() UserJSON {
|
||||
return UserJSON{
|
||||
UserID: u.UserID,
|
||||
Username: u.Username,
|
||||
ReadKey: u.ReadKey,
|
||||
SendKey: u.SendKey,
|
||||
AdminKey: u.AdminKey,
|
||||
TimestampCreated: u.TimestampCreated.Format(time.RFC3339Nano),
|
||||
TimestampLastRead: timeOptFmt(u.TimestampLastRead, time.RFC3339Nano),
|
||||
TimestampLastSent: timeOptFmt(u.TimestampLastSent, time.RFC3339Nano),
|
||||
MessagesSent: u.MessagesSent,
|
||||
QuotaUsed: u.QuotaUsedToday(),
|
||||
QuotaPerDay: u.QuotaPerDay(),
|
||||
QuotaRemaining: u.QuotaRemainingToday(),
|
||||
IsPro: u.IsPro,
|
||||
DefaultChannel: u.DefaultChannel(),
|
||||
}
|
||||
}
|
||||
|
||||
func (u User) JSONWithClients(clients []Client) UserJSONWithClients {
|
||||
return UserJSONWithClients{
|
||||
UserJSON: u.JSON(),
|
||||
Clients: langext.ArrMap(clients, func(v Client) ClientJSON { return v.JSON() }),
|
||||
}
|
||||
}
|
||||
|
||||
func (u User) MaxContentLength() int {
|
||||
if u.IsPro {
|
||||
return 16384
|
||||
} else {
|
||||
return 2048
|
||||
}
|
||||
}
|
||||
|
||||
func (u User) MaxTitleLength() int {
|
||||
return 120
|
||||
}
|
||||
|
||||
func (u User) QuotaPerDay() int {
|
||||
if u.IsPro {
|
||||
return 1000
|
||||
} else {
|
||||
return 50
|
||||
}
|
||||
}
|
||||
|
||||
func (u User) QuotaUsedToday() int {
|
||||
now := scn.QuotaDayString()
|
||||
if u.QuotaUsedDay != nil && *u.QuotaUsedDay == now {
|
||||
return u.QuotaUsed
|
||||
} else {
|
||||
return 0
|
||||
}
|
||||
}
|
||||
|
||||
func (u User) QuotaRemainingToday() int {
|
||||
return u.QuotaPerDay() - u.QuotaUsedToday()
|
||||
}
|
||||
|
||||
func (u User) DefaultChannel() string {
|
||||
return "main"
|
||||
}
|
||||
|
||||
func (u User) DefaultPriority() int {
|
||||
return 1
|
||||
}
|
||||
|
||||
func (u User) MaxChannelNameLength() int {
|
||||
return 120
|
||||
}
|
||||
|
||||
func (u User) MaxSenderName() int {
|
||||
return 120
|
||||
}
|
||||
|
||||
func (u User) MaxUserMessageID() int {
|
||||
return 64
|
||||
}
|
||||
|
||||
func (u User) MaxTimestampDiffHours() int {
|
||||
return 24
|
||||
}
|
||||
|
||||
type UserJSON struct {
|
||||
UserID UserID `json:"user_id"`
|
||||
Username *string `json:"username"`
|
||||
ReadKey string `json:"read_key"`
|
||||
SendKey string `json:"send_key"`
|
||||
AdminKey string `json:"admin_key"`
|
||||
TimestampCreated string `json:"timestamp_created"`
|
||||
TimestampLastRead *string `json:"timestamp_lastread"`
|
||||
TimestampLastSent *string `json:"timestamp_lastsent"`
|
||||
MessagesSent int `json:"messages_sent"`
|
||||
QuotaUsed int `json:"quota_used"`
|
||||
QuotaRemaining int `json:"quota_remaining"`
|
||||
QuotaPerDay int `json:"quota_max"`
|
||||
IsPro bool `json:"is_pro"`
|
||||
DefaultChannel string `json:"default_channel"`
|
||||
}
|
||||
|
||||
type UserJSONWithClients struct {
|
||||
UserJSON
|
||||
Clients []ClientJSON `json:"clients"`
|
||||
}
|
||||
|
||||
type UserDB struct {
|
||||
UserID UserID `db:"user_id"`
|
||||
Username *string `db:"username"`
|
||||
SendKey string `db:"send_key"`
|
||||
ReadKey string `db:"read_key"`
|
||||
AdminKey string `db:"admin_key"`
|
||||
TimestampCreated int64 `db:"timestamp_created"`
|
||||
TimestampLastRead *int64 `db:"timestamp_lastread"`
|
||||
TimestampLastSent *int64 `db:"timestamp_lastsent"`
|
||||
MessagesSent int `db:"messages_sent"`
|
||||
QuotaUsed int `db:"quota_used"`
|
||||
QuotaUsedDay *string `db:"quota_used_day"`
|
||||
IsPro bool `db:"is_pro"`
|
||||
ProToken *string `db:"pro_token"`
|
||||
}
|
||||
|
||||
func (u UserDB) Model() User {
|
||||
return User{
|
||||
UserID: u.UserID,
|
||||
Username: u.Username,
|
||||
SendKey: u.SendKey,
|
||||
ReadKey: u.ReadKey,
|
||||
AdminKey: u.AdminKey,
|
||||
TimestampCreated: time.UnixMilli(u.TimestampCreated),
|
||||
TimestampLastRead: timeOptFromMilli(u.TimestampLastRead),
|
||||
TimestampLastSent: timeOptFromMilli(u.TimestampLastSent),
|
||||
MessagesSent: u.MessagesSent,
|
||||
QuotaUsed: u.QuotaUsed,
|
||||
QuotaUsedDay: u.QuotaUsedDay,
|
||||
IsPro: u.IsPro,
|
||||
}
|
||||
}
|
||||
|
||||
func DecodeUser(r *sqlx.Rows) (User, error) {
|
||||
data, err := sq.ScanSingle[UserDB](r, true)
|
||||
if err != nil {
|
||||
return User{}, err
|
||||
}
|
||||
return data.Model(), nil
|
||||
}
|
||||
|
||||
func DecodeUsers(r *sqlx.Rows) ([]User, error) {
|
||||
data, err := sq.ScanAll[UserDB](r, true)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return langext.ArrMap(data, func(v UserDB) User { return v.Model() }), nil
|
||||
}
|
21
scnserver/models/utils.go
Normal file
21
scnserver/models/utils.go
Normal file
@@ -0,0 +1,21 @@
|
||||
package models
|
||||
|
||||
import (
|
||||
"gogs.mikescher.com/BlackForestBytes/goext/langext"
|
||||
"time"
|
||||
)
|
||||
|
||||
func timeOptFmt(t *time.Time, fmt string) *string {
|
||||
if t == nil {
|
||||
return nil
|
||||
} else {
|
||||
return langext.Ptr(t.Format(fmt))
|
||||
}
|
||||
}
|
||||
|
||||
func timeOptFromMilli(millis *int64) *time.Time {
|
||||
if millis == nil {
|
||||
return nil
|
||||
}
|
||||
return langext.Ptr(time.UnixMilli(*millis))
|
||||
}
|
Reference in New Issue
Block a user