move server/* to scnserver/*

This commit is contained in:
2022-12-21 12:35:56 +01:00
parent 2b4d77bab4
commit bbf7962e29
123 changed files with 0 additions and 0 deletions

View 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
}

View 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
}

View 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"`
}

View 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
View 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
View 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
}

View 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))]
}

View 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
View 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
View 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))
}