Move to string-ids for all entities (compat translation for existing data)
This commit is contained in:
@@ -17,7 +17,7 @@ const (
|
||||
|
||||
type Delivery struct {
|
||||
DeliveryID DeliveryID
|
||||
SCNMessageID SCNMessageID
|
||||
MessageID MessageID
|
||||
ReceiverUserID UserID
|
||||
ReceiverClientID ClientID
|
||||
TimestampCreated time.Time
|
||||
@@ -31,7 +31,7 @@ type Delivery struct {
|
||||
func (d Delivery) JSON() DeliveryJSON {
|
||||
return DeliveryJSON{
|
||||
DeliveryID: d.DeliveryID,
|
||||
SCNMessageID: d.SCNMessageID,
|
||||
MessageID: d.MessageID,
|
||||
ReceiverUserID: d.ReceiverUserID,
|
||||
ReceiverClientID: d.ReceiverClientID,
|
||||
TimestampCreated: d.TimestampCreated.Format(time.RFC3339Nano),
|
||||
@@ -49,7 +49,7 @@ func (d Delivery) MaxRetryCount() int {
|
||||
|
||||
type DeliveryJSON struct {
|
||||
DeliveryID DeliveryID `json:"delivery_id"`
|
||||
SCNMessageID SCNMessageID `json:"scn_message_id"`
|
||||
MessageID MessageID `json:"message_id"`
|
||||
ReceiverUserID UserID `json:"receiver_user_id"`
|
||||
ReceiverClientID ClientID `json:"receiver_client_id"`
|
||||
TimestampCreated string `json:"timestamp_created"`
|
||||
@@ -62,7 +62,7 @@ type DeliveryJSON struct {
|
||||
|
||||
type DeliveryDB struct {
|
||||
DeliveryID DeliveryID `db:"delivery_id"`
|
||||
SCNMessageID SCNMessageID `db:"scn_message_id"`
|
||||
MessageID MessageID `db:"message_id"`
|
||||
ReceiverUserID UserID `db:"receiver_user_id"`
|
||||
ReceiverClientID ClientID `db:"receiver_client_id"`
|
||||
TimestampCreated int64 `db:"timestamp_created"`
|
||||
@@ -76,7 +76,7 @@ type DeliveryDB struct {
|
||||
func (d DeliveryDB) Model() Delivery {
|
||||
return Delivery{
|
||||
DeliveryID: d.DeliveryID,
|
||||
SCNMessageID: d.SCNMessageID,
|
||||
MessageID: d.MessageID,
|
||||
ReceiverUserID: d.ReceiverUserID,
|
||||
ReceiverClientID: d.ReceiverClientID,
|
||||
TimestampCreated: time.UnixMilli(d.TimestampCreated),
|
||||
|
@@ -1,78 +1,376 @@
|
||||
package models
|
||||
|
||||
import "strconv"
|
||||
import (
|
||||
"crypto/rand"
|
||||
"errors"
|
||||
"fmt"
|
||||
"github.com/go-playground/validator/v10"
|
||||
"github.com/rs/zerolog/log"
|
||||
"gogs.mikescher.com/BlackForestBytes/goext/langext"
|
||||
"math/big"
|
||||
"reflect"
|
||||
"regexp"
|
||||
"strings"
|
||||
)
|
||||
|
||||
type EntityID interface {
|
||||
IntID() int64
|
||||
String() string
|
||||
Valid() error
|
||||
Prefix() string
|
||||
Raw() string
|
||||
CheckString() string
|
||||
Regex() *regexp.Regexp
|
||||
}
|
||||
|
||||
type UserID int64
|
||||
const idlen = 24
|
||||
|
||||
func (id UserID) IntID() int64 {
|
||||
return int64(id)
|
||||
const checklen = 1
|
||||
|
||||
const idCharset = "0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz"
|
||||
const idCharsetLen = len(idCharset)
|
||||
|
||||
var charSetReverseMap = generateCharsetMap()
|
||||
|
||||
const (
|
||||
prefixUserID = "USR"
|
||||
prefixChannelID = "CHA"
|
||||
prefixDeliveryID = "DEL"
|
||||
prefixMessageID = "MSG"
|
||||
prefixSubscriptionID = "SUB"
|
||||
prefixClientID = "CLN"
|
||||
prefixRequestID = "REQ"
|
||||
)
|
||||
|
||||
var (
|
||||
regexUserID = generateRegex(prefixUserID)
|
||||
regexChannelID = generateRegex(prefixChannelID)
|
||||
regexDeliveryID = generateRegex(prefixDeliveryID)
|
||||
regexMessageID = generateRegex(prefixMessageID)
|
||||
regexSubscriptionID = generateRegex(prefixSubscriptionID)
|
||||
regexClientID = generateRegex(prefixClientID)
|
||||
regexRequestID = generateRegex(prefixRequestID)
|
||||
)
|
||||
|
||||
func generateRegex(prefix string) *regexp.Regexp {
|
||||
return regexp.MustCompile(fmt.Sprintf("^%s[%s]{%d}[%s]{%d}$", prefix, idCharset, idlen-len(prefix)-checklen, idCharset, checklen))
|
||||
}
|
||||
|
||||
func generateCharsetMap() []int {
|
||||
result := make([]int, 128)
|
||||
for i := 0; i < len(result); i++ {
|
||||
result[i] = -1
|
||||
}
|
||||
for idx, chr := range idCharset {
|
||||
result[int(chr)] = idx
|
||||
}
|
||||
return result
|
||||
}
|
||||
|
||||
func generateID(prefix string) string {
|
||||
k := ""
|
||||
max := big.NewInt(int64(idCharsetLen))
|
||||
checksum := 0
|
||||
for i := 0; i < idlen-len(prefix)-checklen; i++ {
|
||||
v, err := rand.Int(rand.Reader, max)
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
v64 := v.Int64()
|
||||
k += string(idCharset[v64])
|
||||
checksum = (checksum + int(v64)) % (idCharsetLen)
|
||||
}
|
||||
checkstr := string(idCharset[checksum%idCharsetLen])
|
||||
return prefix + k + checkstr
|
||||
}
|
||||
|
||||
func validateID(prefix string, value string) error {
|
||||
if len(value) != idlen {
|
||||
return errors.New("id has the wrong length")
|
||||
}
|
||||
|
||||
if !strings.HasPrefix(value, prefix) {
|
||||
return errors.New("id is missing the correct prefix")
|
||||
}
|
||||
|
||||
checksum := 0
|
||||
for i := len(prefix); i < len(value)-checklen; i++ {
|
||||
ichr := int(value[i])
|
||||
if ichr < 0 || ichr >= len(charSetReverseMap) || charSetReverseMap[ichr] == -1 {
|
||||
return errors.New("id contains invalid characters")
|
||||
}
|
||||
checksum = (checksum + charSetReverseMap[ichr]) % (idCharsetLen)
|
||||
}
|
||||
|
||||
checkstr := string(idCharset[checksum%idCharsetLen])
|
||||
|
||||
if !strings.HasSuffix(value, checkstr) {
|
||||
return errors.New("id checkstring is invalid")
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func getRawData(prefix string, value string) string {
|
||||
if len(value) != idlen {
|
||||
return ""
|
||||
}
|
||||
return value[len(prefix) : idlen-checklen]
|
||||
}
|
||||
|
||||
func getCheckString(prefix string, value string) string {
|
||||
if len(value) != idlen {
|
||||
return ""
|
||||
}
|
||||
return value[idlen-checklen:]
|
||||
}
|
||||
|
||||
func ValidateEntityID(vfl validator.FieldLevel) bool {
|
||||
if !vfl.Field().CanInterface() {
|
||||
log.Error().Msgf("Failed to validate EntityID (cannot interface ?!?)")
|
||||
return false
|
||||
}
|
||||
|
||||
ifvalue := vfl.Field().Interface()
|
||||
|
||||
if value1, ok := ifvalue.(EntityID); ok {
|
||||
|
||||
if vfl.Field().Type().Kind() == reflect.Pointer && langext.IsNil(value1) {
|
||||
return true
|
||||
}
|
||||
|
||||
if err := value1.Valid(); err != nil {
|
||||
log.Debug().Msgf("Failed to validate EntityID '%s' (%s)", value1.String(), err.Error())
|
||||
return false
|
||||
} else {
|
||||
return true
|
||||
}
|
||||
|
||||
} else {
|
||||
log.Error().Msgf("Failed to validate EntityID (wrong type: %T)", ifvalue)
|
||||
return false
|
||||
}
|
||||
}
|
||||
|
||||
// ------------------------------------------------------------
|
||||
|
||||
type UserID string
|
||||
|
||||
func NewUserID() UserID {
|
||||
return UserID(generateID(prefixUserID))
|
||||
}
|
||||
|
||||
func (id UserID) Valid() error {
|
||||
return validateID(prefixUserID, string(id))
|
||||
}
|
||||
|
||||
func (id UserID) String() string {
|
||||
return strconv.FormatInt(int64(id), 10)
|
||||
return string(id)
|
||||
}
|
||||
|
||||
type ChannelID int64
|
||||
func (id UserID) Prefix() string {
|
||||
return prefixUserID
|
||||
}
|
||||
|
||||
func (id ChannelID) IntID() int64 {
|
||||
return int64(id)
|
||||
func (id UserID) Raw() string {
|
||||
return getRawData(prefixUserID, string(id))
|
||||
}
|
||||
|
||||
func (id UserID) CheckString() string {
|
||||
return getCheckString(prefixUserID, string(id))
|
||||
}
|
||||
|
||||
func (id UserID) Regex() *regexp.Regexp {
|
||||
return regexUserID
|
||||
}
|
||||
|
||||
// ------------------------------------------------------------
|
||||
|
||||
type ChannelID string
|
||||
|
||||
func NewChannelID() ChannelID {
|
||||
return ChannelID(generateID(prefixChannelID))
|
||||
}
|
||||
|
||||
func (id ChannelID) Valid() error {
|
||||
return validateID(prefixChannelID, string(id))
|
||||
}
|
||||
|
||||
func (id ChannelID) String() string {
|
||||
return strconv.FormatInt(int64(id), 10)
|
||||
return string(id)
|
||||
}
|
||||
|
||||
type DeliveryID int64
|
||||
func (id ChannelID) Prefix() string {
|
||||
return prefixChannelID
|
||||
}
|
||||
|
||||
func (id DeliveryID) IntID() int64 {
|
||||
return int64(id)
|
||||
func (id ChannelID) Raw() string {
|
||||
return getRawData(prefixChannelID, string(id))
|
||||
}
|
||||
|
||||
func (id ChannelID) CheckString() string {
|
||||
return getCheckString(prefixChannelID, string(id))
|
||||
}
|
||||
|
||||
func (id ChannelID) Regex() *regexp.Regexp {
|
||||
return regexChannelID
|
||||
}
|
||||
|
||||
// ------------------------------------------------------------
|
||||
|
||||
type DeliveryID string
|
||||
|
||||
func NewDeliveryID() DeliveryID {
|
||||
return DeliveryID(generateID(prefixDeliveryID))
|
||||
}
|
||||
|
||||
func (id DeliveryID) Valid() error {
|
||||
return validateID(prefixDeliveryID, string(id))
|
||||
}
|
||||
|
||||
func (id DeliveryID) String() string {
|
||||
return strconv.FormatInt(int64(id), 10)
|
||||
return string(id)
|
||||
}
|
||||
|
||||
type SCNMessageID int64
|
||||
|
||||
func (id SCNMessageID) IntID() int64 {
|
||||
return int64(id)
|
||||
func (id DeliveryID) Prefix() string {
|
||||
return prefixDeliveryID
|
||||
}
|
||||
|
||||
func (id SCNMessageID) String() string {
|
||||
return strconv.FormatInt(int64(id), 10)
|
||||
func (id DeliveryID) Raw() string {
|
||||
return getRawData(prefixDeliveryID, string(id))
|
||||
}
|
||||
|
||||
type SubscriptionID int64
|
||||
func (id DeliveryID) CheckString() string {
|
||||
return getCheckString(prefixDeliveryID, string(id))
|
||||
}
|
||||
|
||||
func (id SubscriptionID) IntID() int64 {
|
||||
return int64(id)
|
||||
func (id DeliveryID) Regex() *regexp.Regexp {
|
||||
return regexDeliveryID
|
||||
}
|
||||
|
||||
// ------------------------------------------------------------
|
||||
|
||||
type MessageID string
|
||||
|
||||
func NewMessageID() MessageID {
|
||||
return MessageID(generateID(prefixMessageID))
|
||||
}
|
||||
|
||||
func (id MessageID) Valid() error {
|
||||
return validateID(prefixMessageID, string(id))
|
||||
}
|
||||
|
||||
func (id MessageID) String() string {
|
||||
return string(id)
|
||||
}
|
||||
|
||||
func (id MessageID) Prefix() string {
|
||||
return prefixMessageID
|
||||
}
|
||||
|
||||
func (id MessageID) Raw() string {
|
||||
return getRawData(prefixMessageID, string(id))
|
||||
}
|
||||
|
||||
func (id MessageID) CheckString() string {
|
||||
return getCheckString(prefixMessageID, string(id))
|
||||
}
|
||||
|
||||
func (id MessageID) Regex() *regexp.Regexp {
|
||||
return regexMessageID
|
||||
}
|
||||
|
||||
// ------------------------------------------------------------
|
||||
|
||||
type SubscriptionID string
|
||||
|
||||
func NewSubscriptionID() SubscriptionID {
|
||||
return SubscriptionID(generateID(prefixSubscriptionID))
|
||||
}
|
||||
|
||||
func (id SubscriptionID) Valid() error {
|
||||
return validateID(prefixSubscriptionID, string(id))
|
||||
}
|
||||
|
||||
func (id SubscriptionID) String() string {
|
||||
return strconv.FormatInt(int64(id), 10)
|
||||
return string(id)
|
||||
}
|
||||
|
||||
type ClientID int64
|
||||
func (id SubscriptionID) Prefix() string {
|
||||
return prefixSubscriptionID
|
||||
}
|
||||
|
||||
func (id ClientID) IntID() int64 {
|
||||
return int64(id)
|
||||
func (id SubscriptionID) Raw() string {
|
||||
return getRawData(prefixSubscriptionID, string(id))
|
||||
}
|
||||
|
||||
func (id SubscriptionID) CheckString() string {
|
||||
return getCheckString(prefixSubscriptionID, string(id))
|
||||
}
|
||||
|
||||
func (id SubscriptionID) Regex() *regexp.Regexp {
|
||||
return regexSubscriptionID
|
||||
}
|
||||
|
||||
// ------------------------------------------------------------
|
||||
|
||||
type ClientID string
|
||||
|
||||
func NewClientID() ClientID {
|
||||
return ClientID(generateID(prefixClientID))
|
||||
}
|
||||
|
||||
func (id ClientID) Valid() error {
|
||||
return validateID(prefixClientID, string(id))
|
||||
}
|
||||
|
||||
func (id ClientID) String() string {
|
||||
return strconv.FormatInt(int64(id), 10)
|
||||
return string(id)
|
||||
}
|
||||
|
||||
type RequestID int64
|
||||
func (id ClientID) Prefix() string {
|
||||
return prefixClientID
|
||||
}
|
||||
|
||||
func (id RequestID) IntID() int64 {
|
||||
return int64(id)
|
||||
func (id ClientID) Raw() string {
|
||||
return getRawData(prefixClientID, string(id))
|
||||
}
|
||||
|
||||
func (id ClientID) CheckString() string {
|
||||
return getCheckString(prefixClientID, string(id))
|
||||
}
|
||||
|
||||
func (id ClientID) Regex() *regexp.Regexp {
|
||||
return regexClientID
|
||||
}
|
||||
|
||||
// ------------------------------------------------------------
|
||||
|
||||
type RequestID string
|
||||
|
||||
func NewRequestID() RequestID {
|
||||
return RequestID(generateID(prefixRequestID))
|
||||
}
|
||||
|
||||
func (id RequestID) Valid() error {
|
||||
return validateID(prefixRequestID, string(id))
|
||||
}
|
||||
|
||||
func (id RequestID) String() string {
|
||||
return strconv.FormatInt(int64(id), 10)
|
||||
return string(id)
|
||||
}
|
||||
|
||||
func (id RequestID) Prefix() string {
|
||||
return prefixRequestID
|
||||
}
|
||||
|
||||
func (id RequestID) Raw() string {
|
||||
return getRawData(prefixRequestID, string(id))
|
||||
}
|
||||
|
||||
func (id RequestID) CheckString() string {
|
||||
return getCheckString(prefixRequestID, string(id))
|
||||
}
|
||||
|
||||
func (id RequestID) Regex() *regexp.Regexp {
|
||||
return regexRequestID
|
||||
}
|
||||
|
@@ -13,7 +13,7 @@ const (
|
||||
)
|
||||
|
||||
type Message struct {
|
||||
SCNMessageID SCNMessageID
|
||||
MessageID MessageID
|
||||
SenderUserID UserID
|
||||
OwnerUserID UserID
|
||||
ChannelInternalName string
|
||||
@@ -31,7 +31,7 @@ type Message struct {
|
||||
|
||||
func (m Message) FullJSON() MessageJSON {
|
||||
return MessageJSON{
|
||||
SCNMessageID: m.SCNMessageID,
|
||||
MessageID: m.MessageID,
|
||||
SenderUserID: m.SenderUserID,
|
||||
OwnerUserID: m.OwnerUserID,
|
||||
ChannelInternalName: m.ChannelInternalName,
|
||||
@@ -49,7 +49,7 @@ func (m Message) FullJSON() MessageJSON {
|
||||
|
||||
func (m Message) TrimmedJSON() MessageJSON {
|
||||
return MessageJSON{
|
||||
SCNMessageID: m.SCNMessageID,
|
||||
MessageID: m.MessageID,
|
||||
SenderUserID: m.SenderUserID,
|
||||
OwnerUserID: m.OwnerUserID,
|
||||
ChannelInternalName: m.ChannelInternalName,
|
||||
@@ -94,41 +94,41 @@ func (m Message) ShortContent() string {
|
||||
}
|
||||
|
||||
type MessageJSON struct {
|
||||
SCNMessageID SCNMessageID `json:"scn_message_id"`
|
||||
SenderUserID UserID `json:"sender_user_id"`
|
||||
OwnerUserID UserID `json:"owner_user_id"`
|
||||
ChannelInternalName string `json:"channel_internal_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"`
|
||||
MessageID MessageID `json:"message_id"`
|
||||
SenderUserID UserID `json:"sender_user_id"`
|
||||
OwnerUserID UserID `json:"owner_user_id"`
|
||||
ChannelInternalName string `json:"channel_internal_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"`
|
||||
ChannelInternalName string `db:"channel_internal_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"`
|
||||
MessageID MessageID `db:"message_id"`
|
||||
SenderUserID UserID `db:"sender_user_id"`
|
||||
OwnerUserID UserID `db:"owner_user_id"`
|
||||
ChannelInternalName string `db:"channel_internal_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,
|
||||
MessageID: m.MessageID,
|
||||
SenderUserID: m.SenderUserID,
|
||||
OwnerUserID: m.OwnerUserID,
|
||||
ChannelInternalName: m.ChannelInternalName,
|
||||
|
@@ -48,7 +48,7 @@ func (f MessageFilter) SQL() (string, string, sq.PP, error) {
|
||||
joinClause += " LEFT JOIN subscriptions AS subs on messages.channel_id = subs.channel_id "
|
||||
}
|
||||
if f.SearchString != nil {
|
||||
joinClause += " JOIN messages_fts AS mfts on (mfts.rowid = messages.scn_message_id) "
|
||||
joinClause += " JOIN messages_fts AS mfts on (mfts.rowid = messages.rowid) "
|
||||
}
|
||||
|
||||
sqlClauses := make([]string, 0)
|
||||
|
Reference in New Issue
Block a user