Add various deleted flags to entities | Add active to subscriptions | Add DeleteUser && DeleteChannel endpoints [skip-tests]
All checks were successful
Build Docker and Deploy / Run Unit-Tests (push) Has been skipped
Build Docker and Deploy / Build Docker Container (push) Successful in 43s
Build Docker and Deploy / Deploy to Server (push) Successful in 16s

This commit is contained in:
2025-04-13 16:12:15 +02:00
parent aac34ef738
commit 8c0f0e3e8f
47 changed files with 2453 additions and 243 deletions

View File

@@ -182,7 +182,7 @@ func (db *Database) migrateSingle(tctx *simplectx.SimpleContext, tx sq.Tx, schem
if schemaFrom == schemaTo-1 {
migSQL := db.schema[schemaTo].MigScript
if migSQL == "" {
if len(migSQL) == 0 {
return exerr.New(exerr.TypeInternal, fmt.Sprintf("missing %s migration from %d to %d", db.name, schemaFrom, schemaTo)).Build()
}
@@ -192,7 +192,7 @@ func (db *Database) migrateSingle(tctx *simplectx.SimpleContext, tx sq.Tx, schem
return exerr.New(exerr.TypeInternal, fmt.Sprintf("missing %s migration from %d to %d", db.name, schemaFrom, schemaTo)).Build()
}
func (db *Database) migrateBySQL(tctx *simplectx.SimpleContext, tx sq.Tx, stmts string, currSchemaVers int, resultSchemVers int, resultHash string, post func(tctx *simplectx.SimpleContext, tx sq.Tx) error) error {
func (db *Database) migrateBySQL(tctx *simplectx.SimpleContext, tx sq.Tx, stmts []string, currSchemaVers int, resultSchemVers int, resultHash string, post func(tctx *simplectx.SimpleContext, tx sq.Tx) error) error {
schemaHashMeta, err := db.ReadMetaString(tctx, "schema_hash")
if err != nil {
@@ -215,9 +215,13 @@ func (db *Database) migrateBySQL(tctx *simplectx.SimpleContext, tx sq.Tx, stmts
log.Info().Msgf("Upgrade schema from %d -> %d", currSchemaVers, resultSchemVers)
_, err = tx.Exec(tctx, stmts, sq.PP{})
if err != nil {
return err
for i, stmt := range stmts {
log.Info().Msgf("SQL-Migration of [%s]: %d/%d", db.name, i+1, len(stmts))
_, err := tx.Exec(tctx, stmt, sq.PP{})
if err != nil {
return err
}
}
schemHashDBAfter, err := sq.HashSqliteDatabase(tctx, tx)

View File

@@ -13,7 +13,7 @@ func (db *Database) GetChannelByName(ctx db.TxContext, userid models.UserID, cha
return nil, err
}
return sq.QuerySingleOpt[models.Channel](ctx, tx, "SELECT * FROM channels WHERE owner_user_id = :uid AND internal_name = :nam LIMIT 1", sq.PP{"uid": userid, "nam": chanName}, sq.SModeExtended, sq.Safe)
return sq.QuerySingleOpt[models.Channel](ctx, tx, "SELECT * FROM channels WHERE owner_user_id = :uid AND internal_name = :nam AND deleted=0 LIMIT 1", sq.PP{"uid": userid, "nam": chanName}, sq.SModeExtended, sq.Safe)
}
func (db *Database) GetChannelByID(ctx db.TxContext, chanid models.ChannelID) (*models.Channel, error) {
@@ -22,7 +22,7 @@ func (db *Database) GetChannelByID(ctx db.TxContext, chanid models.ChannelID) (*
return nil, err
}
return sq.QuerySingleOpt[models.Channel](ctx, tx, "SELECT * FROM channels WHERE channel_id = :cid LIMIT 1", sq.PP{"cid": chanid}, sq.SModeExtended, sq.Safe)
return sq.QuerySingleOpt[models.Channel](ctx, tx, "SELECT * FROM channels WHERE channel_id = :cid AND deleted=0 LIMIT 1", sq.PP{"cid": chanid}, sq.SModeExtended, sq.Safe)
}
type CreateChanel struct {
@@ -49,6 +49,7 @@ func (db *Database) CreateChannel(ctx db.TxContext, userid models.UserID, dispNa
TimestampCreated: models.NowSCNTime(),
TimestampLastSent: nil,
MessagesSent: 0,
Deleted: false,
}
_, err = sq.InsertSingle(ctx, tx, "channels", entity)
@@ -67,7 +68,7 @@ func (db *Database) ListChannelsByOwner(ctx db.TxContext, userid models.UserID,
order := " ORDER BY channels.timestamp_created ASC, channels.channel_id ASC "
sql := "SELECT channels.*, sub.* FROM channels LEFT JOIN subscriptions AS sub ON channels.channel_id = sub.channel_id AND sub.subscriber_user_id = :subuid WHERE owner_user_id = :ouid" + order
sql := "SELECT channels.*, sub.* FROM channels LEFT JOIN subscriptions AS sub ON channels.channel_id = sub.channel_id AND sub.subscriber_user_id = :subuid WHERE channels.deleted=0 AND owner_user_id = :ouid" + order
pp := sq.PP{
"ouid": userid,
@@ -90,9 +91,11 @@ func (db *Database) ListChannelsBySubscriber(ctx db.TxContext, userid models.Use
confCond = " AND sub.confirmed = 0"
}
cond := "channels.deleted=0 AND sub.subscription_id IS NOT NULL " + confCond
order := " ORDER BY channels.timestamp_created ASC, channels.channel_id ASC "
sql := "SELECT channels.*, sub.* FROM channels LEFT JOIN subscriptions AS sub on channels.channel_id = sub.channel_id AND sub.subscriber_user_id = :subuid WHERE sub.subscription_id IS NOT NULL " + confCond + order
sql := "SELECT channels.*, sub.* " + "FROM channels LEFT JOIN subscriptions AS sub on channels.channel_id = sub.channel_id AND sub.subscriber_user_id = :subuid AND sub.deleted=0 WHERE " + cond + order
pp := sq.PP{
"subuid": userid,
@@ -114,9 +117,11 @@ func (db *Database) ListChannelsByAccess(ctx db.TxContext, userid models.UserID,
confCond = "OR (sub.subscription_id IS NOT NULL AND sub.confirmed = 0)"
}
cond := "channels.deleted=0 AND (owner_user_id = :ouid " + confCond + ")"
order := " ORDER BY channels.timestamp_created ASC, channels.channel_id ASC "
sql := "SELECT channels.*, sub.* FROM channels LEFT JOIN subscriptions AS sub on channels.channel_id = sub.channel_id AND sub.subscriber_user_id = :subuid WHERE owner_user_id = :ouid " + confCond + order
sql := "SELECT channels.*, sub.* " + "FROM channels LEFT JOIN subscriptions AS sub on channels.channel_id = sub.channel_id AND sub.subscriber_user_id = :subuid AND sub.deleted=0 WHERE " + cond + order
pp := sq.PP{
"ouid": userid,
@@ -139,13 +144,14 @@ func (db *Database) GetChannel(ctx db.TxContext, userid models.UserID, channelid
selectors := "channels.*, sub.*"
join := "LEFT JOIN subscriptions AS sub on channels.channel_id = sub.channel_id AND sub.subscriber_user_id = :subuid"
join := "LEFT JOIN subscriptions AS sub on channels.channel_id = sub.channel_id AND sub.subscriber_user_id = :subuid AND sub.deleted=0"
cond := "channels.channel_id = :cid"
if enforceOwner {
cond = "owner_user_id = :ouid AND channels.channel_id = :cid"
params["ouid"] = userid
}
cond += " AND channels.deleted=0"
sql := "SELECT " + selectors + " FROM channels " + join + " WHERE " + cond + " LIMIT 1"
@@ -160,7 +166,7 @@ func (db *Database) IncChannelMessageCounter(ctx db.TxContext, channel *models.C
now := time.Now()
_, err = tx.Exec(ctx, "UPDATE channels SET messages_sent = messages_sent+1, timestamp_lastsent = :ts WHERE channel_id = :cid", sq.PP{
_, err = tx.Exec(ctx, "UPDATE channels SET messages_sent = messages_sent+1, timestamp_lastsent = :ts WHERE channel_id = :cid AND deleted=0", sq.PP{
"ts": time2DB(now),
"cid": channel.ChannelID,
})
@@ -180,7 +186,7 @@ func (db *Database) UpdateChannelSubscribeKey(ctx db.TxContext, channelid models
return err
}
_, err = tx.Exec(ctx, "UPDATE channels SET subscribe_key = :key WHERE channel_id = :cid", sq.PP{
_, err = tx.Exec(ctx, "UPDATE channels SET subscribe_key = :key WHERE channel_id = :cid AND deleted=0", sq.PP{
"key": newkey,
"cid": channelid,
})
@@ -197,7 +203,7 @@ func (db *Database) UpdateChannelDisplayName(ctx db.TxContext, channelid models.
return err
}
_, err = tx.Exec(ctx, "UPDATE channels SET display_name = :nam WHERE channel_id = :cid", sq.PP{
_, err = tx.Exec(ctx, "UPDATE channels SET display_name = :nam WHERE channel_id = :cid AND deleted=0", sq.PP{
"nam": dispname,
"cid": channelid,
})
@@ -214,7 +220,7 @@ func (db *Database) UpdateChannelDescriptionName(ctx db.TxContext, channelid mod
return err
}
_, err = tx.Exec(ctx, "UPDATE channels SET description_name = :nam WHERE channel_id = :cid", sq.PP{
_, err = tx.Exec(ctx, "UPDATE channels SET description_name = :nam WHERE channel_id = :cid AND deleted=0", sq.PP{
"nam": descname,
"cid": channelid,
})
@@ -224,3 +230,31 @@ func (db *Database) UpdateChannelDescriptionName(ctx db.TxContext, channelid mod
return nil
}
func (db *Database) DeleteChannelsOfUser(ctx db.TxContext, userid models.UserID) error {
tx, err := ctx.GetOrCreateTransaction(db)
if err != nil {
return err
}
_, err = tx.Exec(ctx, "UPDATE channels SET deleted=1 WHERE owner_user_id = :uid AND deleted=0", sq.PP{"uid": userid})
if err != nil {
return err
}
return nil
}
func (db *Database) DeleteChannel(ctx db.TxContext, channelid models.ChannelID) error {
tx, err := ctx.GetOrCreateTransaction(db)
if err != nil {
return err
}
_, err = tx.Exec(ctx, "UPDATE channels SET deleted=1 WHERE channel_id = :cid AND deleted=0", sq.PP{"cid": channelid})
if err != nil {
return err
}
return nil
}

View File

@@ -21,6 +21,7 @@ func (db *Database) CreateClient(ctx db.TxContext, userid models.UserID, ctype m
AgentModel: agentModel,
AgentVersion: agentVersion,
Name: name,
Deleted: false,
}
_, err = sq.InsertSingle(ctx, tx, "clients", entity)
@@ -159,3 +160,17 @@ func (db *Database) UpdateClientDescriptionName(ctx db.TxContext, clientid model
return nil
}
func (db *Database) DeleteClientsOfUser(ctx db.TxContext, userid models.UserID) error {
tx, err := ctx.GetOrCreateTransaction(db)
if err != nil {
return err
}
_, err = tx.Exec(ctx, "UPDATE clients SET deleted=1 WHERE user_id = :uid AND deleted=0", sq.PP{"uid": userid})
if err != nil {
return err
}
return nil
}

View File

@@ -182,7 +182,7 @@ func (db *Database) migrateSingle(tctx *simplectx.SimpleContext, tx sq.Tx, schem
if schemaFrom == schemaTo-1 {
migSQL := db.schema[schemaTo].MigScript
if migSQL == "" {
if len(migSQL) == 0 {
return exerr.New(exerr.TypeInternal, fmt.Sprintf("missing %s migration from %d to %d", db.name, schemaFrom, schemaTo)).Build()
}
@@ -192,7 +192,7 @@ func (db *Database) migrateSingle(tctx *simplectx.SimpleContext, tx sq.Tx, schem
return exerr.New(exerr.TypeInternal, fmt.Sprintf("missing %s migration from %d to %d", db.name, schemaFrom, schemaTo)).Build()
}
func (db *Database) migrateBySQL(tctx *simplectx.SimpleContext, tx sq.Tx, stmts string, currSchemaVers int, resultSchemVers int, resultHash string, post func(tctx *simplectx.SimpleContext, tx sq.Tx) error) error {
func (db *Database) migrateBySQL(tctx *simplectx.SimpleContext, tx sq.Tx, stmts []string, currSchemaVers int, resultSchemVers int, resultHash string, post func(tctx *simplectx.SimpleContext, tx sq.Tx) error) error {
schemaHashMeta, err := db.ReadMetaString(tctx, "schema_hash")
if err != nil {
@@ -215,9 +215,13 @@ func (db *Database) migrateBySQL(tctx *simplectx.SimpleContext, tx sq.Tx, stmts
log.Info().Msgf("Upgrade schema from %d -> %d", currSchemaVers, resultSchemVers)
_, err = tx.Exec(tctx, stmts, sq.PP{})
if err != nil {
return err
for i, stmt := range stmts {
log.Info().Msgf("SQL-Migration of [%s]: %d/%d", db.name, i+1, len(stmts))
_, err := tx.Exec(tctx, stmt, sq.PP{})
if err != nil {
return err
}
}
schemHashDBAfter, err := sq.HashSqliteDatabase(tctx, tx)

View File

@@ -29,6 +29,7 @@ func (db *Database) CreateRetryDelivery(ctx db.TxContext, client models.Client,
RetryCount: 0,
NextDelivery: models.NewSCNTimePtr(&next),
FCMMessageID: nil,
Deleted: false,
}
_, err = sq.InsertSingle(ctx, tx, "deliveries", entity)
@@ -58,6 +59,7 @@ func (db *Database) CreateSuccessDelivery(ctx db.TxContext, client models.Client
RetryCount: 0,
NextDelivery: nil,
FCMMessageID: langext.Ptr(fcmDelivID),
Deleted: false,
}
_, err = sq.InsertSingle(ctx, tx, "deliveries", entity)
@@ -74,7 +76,7 @@ func (db *Database) ListRetrieableDeliveries(ctx db.TxContext, pageSize int) ([]
return nil, err
}
return sq.QueryAll[models.Delivery](ctx, tx, "SELECT * FROM deliveries WHERE status = 'RETRY' AND next_delivery < :next ORDER BY next_delivery ASC LIMIT :lim", sq.PP{
return sq.QueryAll[models.Delivery](ctx, tx, "SELECT * FROM deliveries WHERE status = 'RETRY' AND next_delivery < :next AND deleted=0 ORDER BY next_delivery ASC LIMIT :lim", sq.PP{
"next": time2DB(time.Now()),
"lim": pageSize,
}, sq.SModeExtended, sq.Safe)
@@ -86,7 +88,7 @@ func (db *Database) SetDeliverySuccess(ctx db.TxContext, delivery models.Deliver
return err
}
_, err = tx.Exec(ctx, "UPDATE deliveries SET status = 'SUCCESS', next_delivery = NULL, retry_count = :rc, timestamp_finalized = :ts, fcm_message_id = :fcm WHERE delivery_id = :did", sq.PP{
_, err = tx.Exec(ctx, "UPDATE deliveries SET status = 'SUCCESS', next_delivery = NULL, retry_count = :rc, timestamp_finalized = :ts, fcm_message_id = :fcm WHERE delivery_id = :did AND deleted=0", sq.PP{
"rc": delivery.RetryCount + 1,
"ts": time2DB(time.Now()),
"fcm": fcmDelivID,
@@ -105,7 +107,7 @@ func (db *Database) SetDeliveryFailed(ctx db.TxContext, delivery models.Delivery
return err
}
_, err = tx.Exec(ctx, "UPDATE deliveries SET status = 'FAILED', next_delivery = NULL, retry_count = :rc, timestamp_finalized = :ts WHERE delivery_id = :did",
_, err = tx.Exec(ctx, "UPDATE deliveries SET status = 'FAILED', next_delivery = NULL, retry_count = :rc, timestamp_finalized = :ts WHERE delivery_id = :did AND deleted=0",
sq.PP{
"rc": delivery.RetryCount + 1,
"ts": time2DB(time.Now()),
@@ -124,7 +126,7 @@ func (db *Database) SetDeliveryRetry(ctx db.TxContext, delivery models.Delivery)
return err
}
_, err = tx.Exec(ctx, "UPDATE deliveries SET status = 'RETRY', next_delivery = :next, retry_count = :rc WHERE delivery_id = :did", sq.PP{
_, err = tx.Exec(ctx, "UPDATE deliveries SET status = 'RETRY', next_delivery = :next, retry_count = :rc WHERE delivery_id = :did AND deleted=0", sq.PP{
"next": time2DB(scn.NextDeliveryTimestamp(time.Now())),
"rc": delivery.RetryCount + 1,
"did": delivery.DeliveryID,
@@ -142,7 +144,7 @@ func (db *Database) CancelPendingDeliveries(ctx db.TxContext, messageID models.M
return err
}
_, err = tx.Exec(ctx, "UPDATE deliveries SET status = 'FAILED', next_delivery = NULL, timestamp_finalized = :ts WHERE message_id = :mid AND status = 'RETRY'", sq.PP{
_, err = tx.Exec(ctx, "UPDATE deliveries SET status = 'FAILED', next_delivery = NULL, timestamp_finalized = :ts WHERE message_id = :mid AND status = 'RETRY' AND deleted=0", sq.PP{
"ts": time.Now(),
"mid": messageID,
})
@@ -152,3 +154,31 @@ func (db *Database) CancelPendingDeliveries(ctx db.TxContext, messageID models.M
return nil
}
func (db *Database) DeleteDeliveriesOfUser(ctx db.TxContext, userid models.UserID) error {
tx, err := ctx.GetOrCreateTransaction(db)
if err != nil {
return err
}
_, err = tx.Exec(ctx, "UPDATE deliveries SET deleted=1 WHERE receiver_user_id = :uid AND deleted=0", sq.PP{"uid": userid})
if err != nil {
return err
}
return nil
}
func (db *Database) DeleteDeliveriesOfChannel(ctx db.TxContext, channelid models.ChannelID) error {
tx, err := ctx.GetOrCreateTransaction(db)
if err != nil {
return err
}
_, err = tx.Exec(ctx, "UPDATE deliveries SET deleted=1 WHERE (SELECT channel_id FROM messages WHERE messages.message_id = deliveries.message_id) = :cid AND deleted=0", sq.PP{"cid": channelid})
if err != nil {
return err
}
return nil
}

View File

@@ -26,6 +26,7 @@ func (db *Database) CreateKeyToken(ctx db.TxContext, name string, owner models.U
Token: token,
Permissions: permissions,
MessagesSent: 0,
Deleted: false,
}
_, err = sq.InsertSingle(ctx, tx, "keytokens", entity)
@@ -42,7 +43,7 @@ func (db *Database) ListKeyTokens(ctx db.TxContext, ownerID models.UserID) ([]mo
return nil, err
}
return sq.QueryAll[models.KeyToken](ctx, tx, "SELECT * FROM keytokens WHERE owner_user_id = :uid ORDER BY keytokens.timestamp_created DESC, keytokens.keytoken_id ASC", sq.PP{"uid": ownerID}, sq.SModeExtended, sq.Safe)
return sq.QueryAll[models.KeyToken](ctx, tx, "SELECT * FROM keytokens WHERE owner_user_id = :uid AND deleted=0 ORDER BY keytokens.timestamp_created DESC, keytokens.keytoken_id ASC", sq.PP{"uid": ownerID}, sq.SModeExtended, sq.Safe)
}
func (db *Database) GetKeyToken(ctx db.TxContext, userid models.UserID, keyTokenid models.KeyTokenID) (models.KeyToken, error) {
@@ -51,7 +52,7 @@ func (db *Database) GetKeyToken(ctx db.TxContext, userid models.UserID, keyToken
return models.KeyToken{}, err
}
return sq.QuerySingle[models.KeyToken](ctx, tx, "SELECT * FROM keytokens WHERE owner_user_id = :uid AND keytoken_id = :cid LIMIT 1", sq.PP{
return sq.QuerySingle[models.KeyToken](ctx, tx, "SELECT * FROM keytokens WHERE owner_user_id = :uid AND keytoken_id = :cid AND deleted=0 LIMIT 1", sq.PP{
"uid": userid,
"cid": keyTokenid,
}, sq.SModeExtended, sq.Safe)
@@ -63,7 +64,7 @@ func (db *Database) GetKeyTokenByID(ctx db.TxContext, keyTokenid models.KeyToken
return models.KeyToken{}, err
}
return sq.QuerySingle[models.KeyToken](ctx, tx, "SELECT * FROM keytokens WHERE keytoken_id = :cid LIMIT 1", sq.PP{"cid": keyTokenid}, sq.SModeExtended, sq.Safe)
return sq.QuerySingle[models.KeyToken](ctx, tx, "SELECT * FROM keytokens WHERE keytoken_id = :cid AND deleted=0 LIMIT 1", sq.PP{"cid": keyTokenid}, sq.SModeExtended, sq.Safe)
}
func (db *Database) GetKeyTokenByToken(ctx db.TxContext, key string) (*models.KeyToken, error) {
@@ -72,7 +73,7 @@ func (db *Database) GetKeyTokenByToken(ctx db.TxContext, key string) (*models.Ke
return nil, err
}
return sq.QuerySingleOpt[models.KeyToken](ctx, tx, "SELECT * FROM keytokens WHERE token = :key LIMIT 1", sq.PP{"key": key}, sq.SModeExtended, sq.Safe)
return sq.QuerySingleOpt[models.KeyToken](ctx, tx, "SELECT * FROM keytokens WHERE token = :key AND deleted=0 LIMIT 1", sq.PP{"key": key}, sq.SModeExtended, sq.Safe)
}
func (db *Database) DeleteKeyToken(ctx db.TxContext, keyTokenid models.KeyTokenID) error {
@@ -81,7 +82,7 @@ func (db *Database) DeleteKeyToken(ctx db.TxContext, keyTokenid models.KeyTokenI
return err
}
_, err = tx.Exec(ctx, "DELETE FROM keytokens WHERE keytoken_id = :tid", sq.PP{"tid": keyTokenid})
_, err = tx.Exec(ctx, "UPDATE keytokens SET deleted=1 WHERE keytoken_id = :tid AND deleted=0", sq.PP{"tid": keyTokenid})
if err != nil {
return err
}
@@ -95,7 +96,7 @@ func (db *Database) UpdateKeyTokenName(ctx db.TxContext, keyTokenid models.KeyTo
return err
}
_, err = tx.Exec(ctx, "UPDATE keytokens SET name = :nam WHERE keytoken_id = :tid", sq.PP{
_, err = tx.Exec(ctx, "UPDATE keytokens SET name = :nam WHERE keytoken_id = :tid AND deleted=0", sq.PP{
"nam": name,
"tid": keyTokenid,
})
@@ -112,7 +113,7 @@ func (db *Database) UpdateKeyTokenPermissions(ctx db.TxContext, keyTokenid model
return err
}
_, err = tx.Exec(ctx, "UPDATE keytokens SET permissions = :prm WHERE keytoken_id = :tid", sq.PP{
_, err = tx.Exec(ctx, "UPDATE keytokens SET permissions = :prm WHERE keytoken_id = :tid AND deleted=0", sq.PP{
"tid": keyTokenid,
"prm": perm.String(),
})
@@ -129,7 +130,7 @@ func (db *Database) UpdateKeyTokenAllChannels(ctx db.TxContext, keyTokenid model
return err
}
_, err = tx.Exec(ctx, "UPDATE keytokens SET all_channels = :all WHERE keytoken_id = :tid", sq.PP{
_, err = tx.Exec(ctx, "UPDATE keytokens SET all_channels = :all WHERE keytoken_id = :tid AND deleted=0", sq.PP{
"tid": keyTokenid,
"all": bool2DB(allChannels),
})
@@ -146,7 +147,7 @@ func (db *Database) UpdateKeyTokenChannels(ctx db.TxContext, keyTokenid models.K
return err
}
_, err = tx.Exec(ctx, "UPDATE keytokens SET channels = :cha WHERE keytoken_id = :tid", sq.PP{
_, err = tx.Exec(ctx, "UPDATE keytokens SET channels = :cha WHERE keytoken_id = :tid AND deleted=0", sq.PP{
"tid": keyTokenid,
"cha": strings.Join(langext.ArrMap(channels, func(v models.ChannelID) string { return v.String() }), ";"),
})
@@ -165,7 +166,7 @@ func (db *Database) IncKeyTokenMessageCounter(ctx db.TxContext, keyToken *models
now := time.Now()
_, err = tx.Exec(ctx, "UPDATE keytokens SET messages_sent = messages_sent+1, timestamp_lastused = :ts WHERE keytoken_id = :tid", sq.PP{
_, err = tx.Exec(ctx, "UPDATE keytokens SET messages_sent = messages_sent+1, timestamp_lastused = :ts WHERE keytoken_id = :tid AND deleted=0", sq.PP{
"ts": time2DB(now),
"tid": keyToken.KeyTokenID,
})
@@ -185,7 +186,7 @@ func (db *Database) UpdateKeyTokenLastUsed(ctx db.TxContext, keyTokenid models.K
return err
}
_, err = tx.Exec(ctx, "UPDATE keytokens SET timestamp_lastused = :ts WHERE keytoken_id = :tid", sq.PP{
_, err = tx.Exec(ctx, "UPDATE keytokens SET timestamp_lastused = :ts WHERE keytoken_id = :tid AND deleted=0", sq.PP{
"ts": time2DB(time.Now()),
"tid": keyTokenid,
})
@@ -195,3 +196,46 @@ func (db *Database) UpdateKeyTokenLastUsed(ctx db.TxContext, keyTokenid models.K
return nil
}
func (db *Database) DeleteKeyTokensOfUser(ctx db.TxContext, userid models.UserID) error {
tx, err := ctx.GetOrCreateTransaction(db)
if err != nil {
return err
}
_, err = tx.Exec(ctx, "UPDATE keytokens SET deleted=1 WHERE owner_user_id = :uid AND deleted=0", sq.PP{"uid": userid})
if err != nil {
return err
}
return nil
}
func (db *Database) DeleteChannelFromKeyTokens(ctx db.TxContext, channelid models.ChannelID) error {
tx, err := ctx.GetOrCreateTransaction(db)
if err != nil {
return err
}
tok, err := sq.QueryAll[models.KeyToken](ctx, tx, "SELECT * FROM keytokens WHERE instr(channels, :cid) AND deleted=0", sq.PP{"cid": channelid}, sq.SModeExtended, sq.Safe)
if err != nil {
return err
}
for _, t := range tok {
newChanList := models.ChannelIDArr(langext.ArrRemove(t.Channels, channelid))
chanListDB, err := newChanList.MarshalToDB(newChanList)
if err != nil {
return err
}
_, err = tx.Exec(ctx, "UPDATE keytokens SET channels=:clist WHERE owner_user_id = :uid AND deleted=0", sq.PP{"clist": chanListDB})
if err != nil {
return err
}
}
return nil
}

View File

@@ -15,7 +15,7 @@ func (db *Database) GetMessageByUserMessageID(ctx db.TxContext, usrMsgId string)
return nil, err
}
return sq.QuerySingleOpt[models.Message](ctx, tx, "SELECT * FROM messages WHERE usr_message_id = :umid LIMIT 1", sq.PP{"umid": usrMsgId}, sq.SModeExtended, sq.Safe)
return sq.QuerySingleOpt[models.Message](ctx, tx, "SELECT * FROM messages WHERE usr_message_id = :umid LIMIT 1", sq.PP{"umid": usrMsgId}, sq.SModeExtended, sq.Safe) // no deleted=0 check!
}
func (db *Database) GetMessage(ctx db.TxContext, scnMessageID models.MessageID, allowDeleted bool) (models.Message, error) {
@@ -45,6 +45,7 @@ func (db *Database) CreateMessage(ctx db.TxContext, senderUserID models.UserID,
SenderUserID: senderUserID,
ChannelInternalName: channel.InternalName,
ChannelID: channel.ChannelID,
ChannelOwnerUserID: channel.OwnerUserID,
SenderIP: senderIP,
SenderName: senderName,
TimestampReal: models.NowSCNTime(),
@@ -164,3 +165,45 @@ func (db *Database) CountMessages(ctx db.TxContext, filter models.MessageFilter)
return countRes, nil
}
func (db *Database) DeleteMessagesOfUserBySenderUserID(ctx db.TxContext, userid models.UserID) error {
tx, err := ctx.GetOrCreateTransaction(db)
if err != nil {
return err
}
_, err = tx.Exec(ctx, "UPDATE messages SET deleted=1 WHERE sender_user_id = :uid AND deleted=0", sq.PP{"uid": userid})
if err != nil {
return err
}
return nil
}
func (db *Database) DeleteMessagesOfUserByChannelOwnerUserID(ctx db.TxContext, userid models.UserID) error {
tx, err := ctx.GetOrCreateTransaction(db)
if err != nil {
return err
}
_, err = tx.Exec(ctx, "UPDATE messages SET deleted=1 WHERE channel_owner_user_id = :uid AND deleted=0", sq.PP{"uid": userid})
if err != nil {
return err
}
return nil
}
func (db *Database) DeleteMessagesOfChannel(ctx db.TxContext, channelid models.ChannelID) error {
tx, err := ctx.GetOrCreateTransaction(db)
if err != nil {
return err
}
_, err = tx.Exec(ctx, "UPDATE messages SET deleted=1 WHERE channel_id = :cid AND deleted=0", sq.PP{"cid": channelid})
if err != nil {
return err
}
return nil
}

View File

@@ -17,7 +17,7 @@ func (db *Database) ListSenderNames(ctx db.TxContext, userid models.UserID, incl
prepParams := sq.PP{"uid": userid}
if includeForeignSubscribed {
sqlStr = "SELECT sender_name AS name, MAX(timestamp_real) AS ts_last, MIN(timestamp_real) AS ts_first, COUNT(*) AS count FROM messages LEFT JOIN subscriptions AS subs on messages.channel_id = subs.channel_id WHERE (subs.subscriber_user_id = :uid AND subs.confirmed = 1) AND sender_NAME NOT NULL GROUP BY sender_name ORDER BY ts_last DESC"
sqlStr = "SELECT sender_name AS name, MAX(timestamp_real) AS ts_last, MIN(timestamp_real) AS ts_first, COUNT(*) AS count FROM messages LEFT JOIN subscriptions AS subs ON messages.channel_id = subs.channel_id AND subs.deleted=0 WHERE (subs.subscriber_user_id = :uid AND subs.confirmed = 1) AND sender_NAME NOT NULL GROUP BY sender_name ORDER BY ts_last DESC"
} else {
sqlStr = "SELECT sender_name AS name, MAX(timestamp_real) AS ts_last, MIN(timestamp_real) AS ts_first, COUNT(*) AS count FROM messages WHERE sender_user_id = :uid AND sender_NAME NOT NULL GROUP BY sender_name ORDER BY ts_last DESC"
}

View File

@@ -20,6 +20,8 @@ func (db *Database) CreateSubscription(ctx db.TxContext, subscriberUID models.Us
ChannelInternalName: channel.InternalName,
TimestampCreated: models.NowSCNTime(),
Confirmed: confirmed,
Active: true,
Deleted: false,
}
_, err = sq.InsertSingle(ctx, tx, "subscriptions", entity)
@@ -40,7 +42,7 @@ func (db *Database) ListSubscriptions(ctx db.TxContext, filter models.Subscripti
orderClause := " ORDER BY subscriptions.timestamp_created DESC, subscriptions.subscription_id DESC "
sqlQuery := "SELECT " + "subscriptions.*" + " FROM subscriptions " + filterJoin + " WHERE ( " + filterCond + " ) " + orderClause
sqlQuery := "SELECT " + "subscriptions.*" + " FROM subscriptions " + filterJoin + " WHERE ( " + filterCond + " ) AND subscriptions.deleted=0 " + orderClause
return sq.QueryAll[models.Subscription](ctx, tx, sqlQuery, prepParams, sq.SModeExtended, sq.Safe)
}
@@ -51,7 +53,7 @@ func (db *Database) GetSubscription(ctx db.TxContext, subid models.SubscriptionI
return models.Subscription{}, err
}
return sq.QuerySingle[models.Subscription](ctx, tx, "SELECT * FROM subscriptions WHERE subscription_id = :sid LIMIT 1", sq.PP{"sid": subid}, sq.SModeExtended, sq.Safe)
return sq.QuerySingle[models.Subscription](ctx, tx, "SELECT * FROM subscriptions WHERE subscription_id = :sid AND deleted=0 LIMIT 1", sq.PP{"sid": subid}, sq.SModeExtended, sq.Safe)
}
func (db *Database) GetSubscriptionBySubscriber(ctx db.TxContext, subscriberId models.UserID, channelId models.ChannelID) (*models.Subscription, error) {
@@ -60,7 +62,7 @@ func (db *Database) GetSubscriptionBySubscriber(ctx db.TxContext, subscriberId m
return nil, err
}
return sq.QuerySingleOpt[models.Subscription](ctx, tx, "SELECT * FROM subscriptions WHERE subscriber_user_id = :suid AND channel_id = :cid LIMIT 1", sq.PP{
return sq.QuerySingleOpt[models.Subscription](ctx, tx, "SELECT * FROM subscriptions WHERE subscriber_user_id = :suid AND channel_id = :cid AND deleted=0 LIMIT 1", sq.PP{
"suid": subscriberId,
"cid": channelId,
}, sq.SModeExtended, sq.Safe)
@@ -72,7 +74,7 @@ func (db *Database) DeleteSubscription(ctx db.TxContext, subid models.Subscripti
return err
}
_, err = tx.Exec(ctx, "DELETE FROM subscriptions WHERE subscription_id = :sid", sq.PP{"sid": subid})
_, err = tx.Exec(ctx, "UPDATE subscriptions SET deleted=1 WHERE subscription_id = :sid AND deleted=0", sq.PP{"sid": subid})
if err != nil {
return err
}
@@ -86,7 +88,7 @@ func (db *Database) UpdateSubscriptionConfirmed(ctx db.TxContext, subscriptionID
return err
}
_, err = tx.Exec(ctx, "UPDATE subscriptions SET confirmed = :conf WHERE subscription_id = :sid", sq.PP{
_, err = tx.Exec(ctx, "UPDATE subscriptions SET confirmed = :conf WHERE subscription_id = :sid AND deleted=0", sq.PP{
"conf": confirmed,
"sid": subscriptionID,
})
@@ -96,3 +98,62 @@ func (db *Database) UpdateSubscriptionConfirmed(ctx db.TxContext, subscriptionID
return nil
}
func (db *Database) UpdateSubscriptionActive(ctx db.TxContext, subscriptionID models.SubscriptionID, active bool) error {
tx, err := ctx.GetOrCreateTransaction(db)
if err != nil {
return err
}
_, err = tx.Exec(ctx, "UPDATE subscriptions SET active = :act WHERE subscription_id = :sid AND deleted=0", sq.PP{
"act": active,
"sid": subscriptionID,
})
if err != nil {
return err
}
return nil
}
func (db *Database) DeleteSubscriptionsOfUserByChannelOwner(ctx db.TxContext, userid models.UserID) error {
tx, err := ctx.GetOrCreateTransaction(db)
if err != nil {
return err
}
_, err = tx.Exec(ctx, "UPDATE subscriptions SET deleted=1 WHERE channel_owner_user_id = :uid AND deleted=0", sq.PP{"uid": userid})
if err != nil {
return err
}
return nil
}
func (db *Database) DeleteSubscriptionsOfUserBySubscriber(ctx db.TxContext, userid models.UserID) error {
tx, err := ctx.GetOrCreateTransaction(db)
if err != nil {
return err
}
_, err = tx.Exec(ctx, "UPDATE subscriptions SET deleted=1 WHERE subscriber_user_id = :uid AND deleted=0", sq.PP{"uid": userid})
if err != nil {
return err
}
return nil
}
func (db *Database) DeleteSubscriptionsByChannel(ctx db.TxContext, channelid models.ChannelID) error {
tx, err := ctx.GetOrCreateTransaction(db)
if err != nil {
return err
}
_, err = tx.Exec(ctx, "UPDATE subscriptions SET deleted=1 WHERE channel_id = :cid AND deleted=0", sq.PP{"cid": channelid})
if err != nil {
return err
}
return nil
}

View File

@@ -27,6 +27,7 @@ func (db *Database) CreateUser(ctx db.TxContext, protoken *string, username *str
IsPro: protoken != nil,
ProToken: protoken,
UserExtra: models.UserExtra{},
Deleted: false,
}
entity.PreMarshal()
@@ -45,7 +46,7 @@ func (db *Database) ClearProTokens(ctx db.TxContext, protoken string) error {
return err
}
_, err = tx.Exec(ctx, "UPDATE users SET is_pro=0, pro_token=NULL WHERE pro_token = :tok", sq.PP{"tok": protoken})
_, err = tx.Exec(ctx, "UPDATE users SET is_pro=0, pro_token=NULL WHERE pro_token = :tok AND deleted=0", sq.PP{"tok": protoken})
if err != nil {
return err
}
@@ -59,7 +60,7 @@ func (db *Database) GetUser(ctx db.TxContext, userid models.UserID) (models.User
return models.User{}, err
}
return sq.QuerySingle[models.User](ctx, tx, "SELECT * FROM users WHERE user_id = :uid LIMIT 1", sq.PP{"uid": userid}, sq.SModeExtended, sq.Safe)
return sq.QuerySingle[models.User](ctx, tx, "SELECT * FROM users WHERE user_id = :uid AND deleted=0 LIMIT 1", sq.PP{"uid": userid}, sq.SModeExtended, sq.Safe)
}
func (db *Database) GetUserOpt(ctx db.TxContext, userid models.UserID) (*models.User, error) {
@@ -68,7 +69,7 @@ func (db *Database) GetUserOpt(ctx db.TxContext, userid models.UserID) (*models.
return nil, err
}
return sq.QuerySingleOpt[models.User](ctx, tx, "SELECT * FROM users WHERE user_id = :uid LIMIT 1", sq.PP{"uid": userid}, sq.SModeExtended, sq.Safe)
return sq.QuerySingleOpt[models.User](ctx, tx, "SELECT * FROM users WHERE user_id = :uid AND deleted=0 LIMIT 1", sq.PP{"uid": userid}, sq.SModeExtended, sq.Safe)
}
func (db *Database) UpdateUserUsername(ctx db.TxContext, userid models.UserID, username *string) error {
@@ -77,7 +78,7 @@ func (db *Database) UpdateUserUsername(ctx db.TxContext, userid models.UserID, u
return err
}
_, err = tx.Exec(ctx, "UPDATE users SET username = :nam WHERE user_id = :uid", sq.PP{
_, err = tx.Exec(ctx, "UPDATE users SET username = :nam WHERE user_id = :uid AND deleted=0", sq.PP{
"nam": username,
"uid": userid,
})
@@ -94,7 +95,7 @@ func (db *Database) UpdateUserProToken(ctx db.TxContext, userid models.UserID, p
return err
}
_, err = tx.Exec(ctx, "UPDATE users SET pro_token = :tok, is_pro = :pro WHERE user_id = :uid", sq.PP{
_, err = tx.Exec(ctx, "UPDATE users SET pro_token = :tok, is_pro = :pro WHERE user_id = :uid AND deleted=0", sq.PP{
"tok": protoken,
"pro": bool2DB(protoken != nil),
"uid": userid,
@@ -119,7 +120,7 @@ func (db *Database) IncUserMessageCounter(ctx db.TxContext, user *models.User) e
user.QuotaUsed = quota
user.QuotaUsedDay = langext.Ptr(scn.QuotaDayString())
_, err = tx.Exec(ctx, "UPDATE users SET timestamp_lastsent = :ts, messages_sent = messages_sent+1, quota_used = :qu, quota_used_day = :qd WHERE user_id = :uid", sq.PP{
_, err = tx.Exec(ctx, "UPDATE users SET timestamp_lastsent = :ts, messages_sent = messages_sent+1, quota_used = :qu, quota_used_day = :qd WHERE user_id = :uid AND deleted=0", sq.PP{
"ts": time2DB(now),
"qu": user.QuotaUsed,
"qd": user.QuotaUsedDay,
@@ -141,7 +142,7 @@ func (db *Database) UpdateUserLastRead(ctx db.TxContext, userid models.UserID) e
return err
}
_, err = tx.Exec(ctx, "UPDATE users SET timestamp_lastread = :ts WHERE user_id = :uid", sq.PP{
_, err = tx.Exec(ctx, "UPDATE users SET timestamp_lastread = :ts WHERE user_id = :uid AND deleted=0", sq.PP{
"ts": time2DB(time.Now()),
"uid": userid,
})
@@ -151,3 +152,17 @@ func (db *Database) UpdateUserLastRead(ctx db.TxContext, userid models.UserID) e
return nil
}
func (db *Database) DeleteUser(ctx db.TxContext, userid models.UserID) error {
tx, err := ctx.GetOrCreateTransaction(db)
if err != nil {
return err
}
_, err = tx.Exec(ctx, "UPDATE users SET deleted=1 WHERE user_id = :uid AND deleted=0", sq.PP{"uid": userid})
if err != nil {
return err
}
return nil
}

View File

@@ -182,7 +182,7 @@ func (db *Database) migrateSingle(tctx *simplectx.SimpleContext, tx sq.Tx, schem
if schemaFrom == schemaTo-1 {
migSQL := db.schema[schemaTo].MigScript
if migSQL == "" {
if len(migSQL) == 0 {
return exerr.New(exerr.TypeInternal, fmt.Sprintf("missing %s migration from %d to %d", db.name, schemaFrom, schemaTo)).Build()
}
@@ -192,7 +192,7 @@ func (db *Database) migrateSingle(tctx *simplectx.SimpleContext, tx sq.Tx, schem
return exerr.New(exerr.TypeInternal, fmt.Sprintf("missing %s migration from %d to %d", db.name, schemaFrom, schemaTo)).Build()
}
func (db *Database) migrateBySQL(tctx *simplectx.SimpleContext, tx sq.Tx, stmts string, currSchemaVers int, resultSchemVers int, resultHash string, post func(tctx *simplectx.SimpleContext, tx sq.Tx) error) error {
func (db *Database) migrateBySQL(tctx *simplectx.SimpleContext, tx sq.Tx, stmts []string, currSchemaVers int, resultSchemVers int, resultHash string, post func(tctx *simplectx.SimpleContext, tx sq.Tx) error) error {
schemaHashMeta, err := db.ReadMetaString(tctx, "schema_hash")
if err != nil {
@@ -215,9 +215,13 @@ func (db *Database) migrateBySQL(tctx *simplectx.SimpleContext, tx sq.Tx, stmts
log.Info().Msgf("Upgrade schema from %d -> %d", currSchemaVers, resultSchemVers)
_, err = tx.Exec(tctx, stmts, sq.PP{})
if err != nil {
return err
for i, stmt := range stmts {
log.Info().Msgf("SQL-Migration of [%s]: %d/%d", db.name, i+1, len(stmts))
_, err := tx.Exec(tctx, stmt, sq.PP{})
if err != nil {
return err
}
}
schemHashDBAfter, err := sq.HashSqliteDatabase(tctx, tx)

View File

@@ -3,12 +3,13 @@ package schema
import (
"embed"
_ "embed"
"strings"
)
type Def struct {
SQL string
Hash string
MigScript string
MigScript []string
}
//go:embed *.ddl
@@ -16,29 +17,30 @@ type Def struct {
var assets embed.FS
var PrimarySchema = map[int]Def{
0: {"", "", ""},
1: {readDDL("primary_1.ddl"), "f2b2847f32681a7178e405553beea4a324034915a0c5a5dc70b3c6abbcc852f2", ""},
2: {readDDL("primary_2.ddl"), "07ed1449114416ed043084a30e0722a5f97bf172161338d2f7106a8dfd387d0a", ""},
3: {readDDL("primary_3.ddl"), "65c2125ad0e12d02490cf2275f0067ef3c62a8522edf9a35ee8aa3f3c09b12e8", ""},
0: {"", "", nil},
1: {readDDL("primary_1.ddl"), "f2b2847f32681a7178e405553beea4a324034915a0c5a5dc70b3c6abbcc852f2", nil},
2: {readDDL("primary_2.ddl"), "07ed1449114416ed043084a30e0722a5f97bf172161338d2f7106a8dfd387d0a", nil},
3: {readDDL("primary_3.ddl"), "65c2125ad0e12d02490cf2275f0067ef3c62a8522edf9a35ee8aa3f3c09b12e8", nil},
4: {readDDL("primary_4.ddl"), "cb022156ab0e7aea39dd0c985428c43cae7d60e41ca8e9e5a84c774b3019d2ca", readMig("primary_migration_3_4.sql")},
5: {readDDL("primary_5.ddl"), "9d6217ba4a3503cfe090f72569367f95a413bb14e9effe49ffeabbf255bce8dd", readMig("primary_migration_4_5.sql")},
6: {readDDL("primary_6.ddl"), "8e83d20bcd008082713f248ae8cd558335a37a37ce90bd8c86e782da640ee160", readMig("primary_migration_5_6.sql")},
7: {readDDL("primary_7.ddl"), "90d8dbc460afe025f9b74cda5c16bb8e58b178df275223bd2531907a8d8c36c3", readMig("primary_migration_6_7.sql")},
8: {readDDL("primary_8.ddl"), "746f6005c7a573b8816e5993ecd1d949fe2552b0134ba63bab8b4d5b2b5058ad", readMig("primary_migration_7_8.sql")},
9: {readDDL("primary_9.ddl"), "0fd1eacb03364153b2a2096106b1a11cc48ae764db52c2896dab9725b55ed188", readMig("primary_migration_8_9.sql")},
}
var PrimarySchemaVersion = len(PrimarySchema) - 1
var RequestsSchema = map[int]Def{
0: {"", "", ""},
1: {readDDL("requests_1.ddl"), "ebb0a5748b605e8215437413b738279670190ca8159b6227cfc2aa13418b41e9", ""},
0: {"", "", nil},
1: {readDDL("requests_1.ddl"), "ebb0a5748b605e8215437413b738279670190ca8159b6227cfc2aa13418b41e9", nil},
}
var RequestsSchemaVersion = len(RequestsSchema) - 1
var LogsSchema = map[int]Def{
0: {"", "", ""},
1: {readDDL("logs_1.ddl"), "65fba477c04095effc3a8e1bb79fe7547b8e52e983f776f156266eddc4f201d7", ""},
0: {"", "", nil},
1: {readDDL("logs_1.ddl"), "65fba477c04095effc3a8e1bb79fe7547b8e52e983f776f156266eddc4f201d7", nil},
}
var LogsSchemaVersion = len(LogsSchema) - 1
@@ -51,10 +53,51 @@ func readDDL(name string) string {
return string(data)
}
func readMig(name string) string {
func readMig(name string) []string {
data, err := assets.ReadFile(name)
if err != nil {
panic(err)
}
return string(data)
return splitMigration(string(data))
}
func splitMigration(input string) []string {
arr := make([]string, 0)
acc := ""
waitForEnd := false
for _, line := range strings.Split(input, "\n") {
if strings.TrimSpace(line) == "" {
continue
}
if strings.HasPrefix(strings.TrimSpace(line), "--") {
continue
}
acc += line
if strings.HasSuffix(strings.TrimSpace(line), "BEGIN") || strings.HasPrefix(strings.TrimSpace(line), "BEGIN") {
waitForEnd = true
}
if strings.HasPrefix(strings.TrimSpace(line), "END") {
waitForEnd = false
}
if strings.HasSuffix(acc, ";") && !waitForEnd {
arr = append(arr, acc)
acc = ""
continue
} else {
acc += "\n"
}
}
if strings.TrimSpace(acc) != "" {
arr = append(arr, acc)
}
return arr
}

View File

@@ -0,0 +1,249 @@
CREATE TABLE users
(
user_id TEXT NOT NULL,
username TEXT NULL DEFAULT NULL,
timestamp_created INTEGER NOT NULL,
timestamp_lastread INTEGER NULL DEFAULT NULL,
timestamp_lastsent INTEGER NULL DEFAULT NULL,
messages_sent INTEGER NOT NULL DEFAULT '0',
quota_used INTEGER NOT NULL DEFAULT '0',
quota_used_day TEXT NULL DEFAULT NULL,
is_pro INTEGER CHECK(is_pro IN (0, 1)) NOT NULL DEFAULT 0,
pro_token TEXT NULL DEFAULT NULL,
deleted INTEGER CHECK(deleted IN (0, 1)) NOT NULL DEFAULT '0',
PRIMARY KEY (user_id)
) STRICT;
CREATE UNIQUE INDEX "idx_users_protoken" ON users (pro_token) WHERE pro_token IS NOT NULL AND deleted=0;
CREATE TABLE keytokens
(
keytoken_id TEXT NOT NULL,
timestamp_created INTEGER NOT NULL,
timestamp_lastused INTEGER NULL DEFAULT NULL,
name TEXT NOT NULL,
owner_user_id TEXT NOT NULL,
all_channels INTEGER CHECK(all_channels IN (0, 1)) NOT NULL,
channels TEXT NOT NULL,
token TEXT NOT NULL,
permissions TEXT NOT NULL,
messages_sent INTEGER NOT NULL DEFAULT '0',
deleted INTEGER CHECK(deleted IN (0, 1)) NOT NULL DEFAULT '0',
PRIMARY KEY (keytoken_id)
) STRICT;
CREATE UNIQUE INDEX "idx_keytokens_token" ON keytokens (token);
CREATE TABLE clients
(
client_id TEXT NOT NULL,
user_id TEXT NOT NULL,
type TEXT CHECK(type IN ('ANDROID','IOS','LINUX','MACOS','WINDOWS')) NOT NULL,
fcm_token TEXT NOT NULL,
name TEXT NULL,
timestamp_created INTEGER NOT NULL,
agent_model TEXT NOT NULL,
agent_version TEXT NOT NULL,
deleted INTEGER CHECK(deleted IN (0, 1)) NOT NULL DEFAULT '0',
PRIMARY KEY (client_id)
) STRICT;
CREATE INDEX "idx_clients_userid" ON clients (user_id);
CREATE INDEX "idx_clients_deleted" ON clients (deleted);
CREATE UNIQUE INDEX "idx_clients_fcmtoken" ON clients (fcm_token) WHERE deleted=0;
CREATE TABLE channels
(
channel_id TEXT NOT NULL,
owner_user_id TEXT NOT NULL,
internal_name TEXT NOT NULL,
display_name TEXT NOT NULL,
description_name TEXT NULL,
subscribe_key TEXT NOT NULL,
timestamp_created INTEGER NOT NULL,
timestamp_lastsent INTEGER NULL DEFAULT NULL,
messages_sent INTEGER NOT NULL DEFAULT '0',
deleted INTEGER CHECK(deleted IN (0, 1)) NOT NULL DEFAULT '0',
PRIMARY KEY (channel_id)
) STRICT;
CREATE UNIQUE INDEX "idx_channels_identity" ON channels (owner_user_id, internal_name) WHERE deleted=0;
CREATE TABLE subscriptions
(
subscription_id TEXT NOT NULL,
subscriber_user_id TEXT NOT NULL,
channel_owner_user_id TEXT NOT NULL,
channel_internal_name TEXT NOT NULL,
channel_id TEXT NOT NULL,
timestamp_created INTEGER NOT NULL,
confirmed INTEGER CHECK(confirmed IN (0, 1)) NOT NULL,
active INTEGER CHECK(active IN (0, 1)) NOT NULL,
deleted INTEGER CHECK(deleted IN (0, 1)) NOT NULL DEFAULT '0',
PRIMARY KEY (subscription_id)
) STRICT;
CREATE UNIQUE INDEX "idx_subscriptions_ref" ON subscriptions (subscriber_user_id, channel_owner_user_id, channel_internal_name) WHERE deleted=0;
CREATE INDEX "idx_subscriptions_chan" ON subscriptions (channel_id);
CREATE INDEX "idx_subscriptions_subuser" ON subscriptions (subscriber_user_id);
CREATE INDEX "idx_subscriptions_ownuser" ON subscriptions (channel_owner_user_id);
CREATE INDEX "idx_subscriptions_tsc" ON subscriptions (timestamp_created);
CREATE INDEX "idx_subscriptions_conf" ON subscriptions (confirmed);
CREATE TABLE messages
(
message_id TEXT NOT NULL,
sender_user_id TEXT NOT NULL,
channel_internal_name TEXT NOT NULL,
channel_id TEXT NOT NULL,
channel_owner_user_id TEXT NOT NULL,
sender_ip TEXT NOT NULL,
sender_name TEXT NULL,
timestamp_real INTEGER NOT NULL,
timestamp_client INTEGER NULL,
title TEXT NOT NULL,
content TEXT NULL,
priority INTEGER CHECK(priority IN (0, 1, 2)) NOT NULL,
usr_message_id TEXT NULL,
used_key_id TEXT NOT NULL,
deleted INTEGER CHECK(deleted IN (0, 1)) NOT NULL DEFAULT '0',
PRIMARY KEY (message_id)
) STRICT;
CREATE INDEX "idx_messages_channel" ON messages (channel_internal_name COLLATE BINARY);
CREATE INDEX "idx_messages_channel_nc" ON messages (channel_internal_name COLLATE NOCASE);
CREATE UNIQUE INDEX "idx_messages_idempotency" ON messages (sender_user_id, usr_message_id COLLATE BINARY);
CREATE INDEX "idx_messages_senderip" ON messages (sender_ip COLLATE BINARY);
CREATE INDEX "idx_messages_sendername" ON messages (sender_name COLLATE BINARY);
CREATE INDEX "idx_messages_sendername_nc" ON messages (sender_name COLLATE NOCASE);
CREATE INDEX "idx_messages_title" ON messages (title COLLATE BINARY);
CREATE INDEX "idx_messages_title_nc" ON messages (title COLLATE NOCASE);
CREATE INDEX "idx_messages_usedkey" ON messages (sender_user_id, used_key_id);
CREATE INDEX "idx_messages_deleted" ON messages (deleted);
CREATE VIRTUAL TABLE messages_fts USING fts5
(
channel_internal_name,
sender_name,
title,
content,
tokenize = unicode61,
content = 'messages',
content_rowid = 'rowid'
);
CREATE TRIGGER fts_insert AFTER INSERT ON messages BEGIN
INSERT INTO messages_fts (rowid, channel_internal_name, sender_name, title, content) VALUES (new.rowid, new.channel_internal_name, new.sender_name, new.title, new.content);
END;
CREATE TRIGGER fts_update AFTER UPDATE ON messages BEGIN
INSERT INTO messages_fts (messages_fts, rowid, channel_internal_name, sender_name, title, content) VALUES ('delete', old.rowid, old.channel_internal_name, old.sender_name, old.title, old.content);
INSERT INTO messages_fts ( rowid, channel_internal_name, sender_name, title, content) VALUES ( new.rowid, new.channel_internal_name, new.sender_name, new.title, new.content);
END;
CREATE TRIGGER fts_delete AFTER DELETE ON messages BEGIN
INSERT INTO messages_fts (messages_fts, rowid, channel_internal_name, sender_name, title, content) VALUES ('delete', old.rowid, old.channel_internal_name, old.sender_name, old.title, old.content);
END;
CREATE TABLE deliveries
(
delivery_id TEXT NOT NULL,
message_id TEXT NOT NULL,
receiver_user_id TEXT NOT NULL,
receiver_client_id TEXT NOT NULL,
timestamp_created INTEGER NOT NULL,
timestamp_finalized INTEGER NULL,
status TEXT CHECK(status IN ('RETRY','SUCCESS','FAILED')) NOT NULL,
retry_count INTEGER NOT NULL DEFAULT 0,
next_delivery INTEGER NULL DEFAULT NULL,
fcm_message_id TEXT NULL,
deleted INTEGER CHECK(deleted IN (0, 1)) NOT NULL DEFAULT '0',
PRIMARY KEY (delivery_id)
) STRICT;
CREATE INDEX "idx_deliveries_receiver" ON deliveries (message_id, receiver_client_id);
CREATE TABLE compat_ids
(
old INTEGER NOT NULL,
new TEXT NOT NULL,
type TEXT NOT NULL
) STRICT;
CREATE UNIQUE INDEX "idx_compatids_new" ON compat_ids (new);
CREATE UNIQUE INDEX "idx_compatids_old" ON compat_ids (old, type);
CREATE TABLE compat_acks
(
user_id TEXT NOT NULL,
message_id TEXT NOT NULL
) STRICT;
CREATE INDEX "idx_compatacks_userid" ON compat_acks (user_id);
CREATE UNIQUE INDEX "idx_compatacks_messageid" ON compat_acks (message_id);
CREATE UNIQUE INDEX "idx_compatacks_userid_messageid" ON compat_acks (user_id, message_id);
CREATE TABLE compat_clients
(
client_id TEXT NOT NULL
) STRICT;
CREATE UNIQUE INDEX "idx_compatclient_clientid" ON compat_clients (client_id);
CREATE TABLE `meta`
(
meta_key TEXT NOT NULL,
value_int INTEGER NULL,
value_txt TEXT NULL,
value_real REAL NULL,
value_blob BLOB NULL,
PRIMARY KEY (meta_key)
) STRICT;
INSERT INTO meta (meta_key, value_int) VALUES ('schema', 3)

View File

@@ -0,0 +1,320 @@
-- Add deleted to channels ( migrate existing as '0' )
-- Add deleted to keytokens ( migrate existing as '0' )
-- Add deleted to subscriptions ( migrate existing as '0' )
-- Add deleted to users ( migrate existing as '0' )
-- Add deleted to deliveries ( migrate existing as '0' )
--
-- Add active to subcsriptions ( migrate existing as '1' )
--
-- Add channel_owner_id to messages ( migrate existing by looking up channel )
--
------------------------------------------------------------------------------------------------------------------------
DROP INDEX "idx_users_protoken";
CREATE TABLE __new_users
(
user_id TEXT NOT NULL,
username TEXT NULL DEFAULT NULL,
timestamp_created INTEGER NOT NULL,
timestamp_lastread INTEGER NULL DEFAULT NULL,
timestamp_lastsent INTEGER NULL DEFAULT NULL,
messages_sent INTEGER NOT NULL DEFAULT '0',
quota_used INTEGER NOT NULL DEFAULT '0',
quota_used_day TEXT NULL DEFAULT NULL,
is_pro INTEGER CHECK(is_pro IN (0, 1)) NOT NULL DEFAULT 0,
pro_token TEXT NULL DEFAULT NULL,
deleted INTEGER CHECK(deleted IN (0, 1)) NOT NULL DEFAULT '0',
PRIMARY KEY (user_id)
) STRICT;
INSERT INTO __new_users
SELECT
user_id,
username,
timestamp_created,
timestamp_lastread,
timestamp_lastsent,
messages_sent,
quota_used,
quota_used_day,
is_pro,
pro_token,
0 AS deleted
FROM users;
DROP TABLE users;
ALTER TABLE __new_users RENAME TO users;
CREATE UNIQUE INDEX "idx_users_protoken" ON users (pro_token) WHERE pro_token IS NOT NULL AND deleted=0;
------------------------------------------------------------------------------------------------------------------------
DROP INDEX "idx_keytokens_token";
CREATE TABLE __new_keytokens
(
keytoken_id TEXT NOT NULL,
timestamp_created INTEGER NOT NULL,
timestamp_lastused INTEGER NULL DEFAULT NULL,
name TEXT NOT NULL,
owner_user_id TEXT NOT NULL,
all_channels INTEGER CHECK(all_channels IN (0, 1)) NOT NULL,
channels TEXT NOT NULL,
token TEXT NOT NULL,
permissions TEXT NOT NULL,
messages_sent INTEGER NOT NULL DEFAULT '0',
deleted INTEGER CHECK(deleted IN (0, 1)) NOT NULL DEFAULT '0',
PRIMARY KEY (keytoken_id)
) STRICT;
INSERT INTO __new_keytokens
SELECT
keytoken_id,
timestamp_created,
timestamp_lastused,
name,
owner_user_id,
all_channels,
channels,
token,
permissions,
messages_sent,
0 AS deleted
FROM keytokens;
DROP TABLE keytokens;
ALTER TABLE __new_keytokens RENAME TO keytokens;
CREATE UNIQUE INDEX "idx_keytokens_token" ON keytokens (token);
------------------------------------------------------------------------------------------------------------------------
DROP INDEX "idx_deliveries_receiver";
CREATE TABLE __new_deliveries
(
delivery_id TEXT NOT NULL,
message_id TEXT NOT NULL,
receiver_user_id TEXT NOT NULL,
receiver_client_id TEXT NOT NULL,
timestamp_created INTEGER NOT NULL,
timestamp_finalized INTEGER NULL,
status TEXT CHECK(status IN ('RETRY','SUCCESS','FAILED')) NOT NULL,
retry_count INTEGER NOT NULL DEFAULT 0,
next_delivery INTEGER NULL DEFAULT NULL,
fcm_message_id TEXT NULL,
deleted INTEGER CHECK(deleted IN (0, 1)) NOT NULL DEFAULT '0',
PRIMARY KEY (delivery_id)
) STRICT;
INSERT INTO __new_deliveries
SELECT delivery_id,
message_id,
receiver_user_id,
receiver_client_id,
timestamp_created,
timestamp_finalized,
status,
retry_count,
next_delivery,
fcm_message_id,
0 as deleted
FROM deliveries;
DROP TABLE deliveries;
ALTER TABLE __new_deliveries RENAME TO deliveries;
CREATE INDEX "idx_deliveries_receiver" ON deliveries (message_id, receiver_client_id);
------------------------------------------------------------------------------------------------------------------------
DROP INDEX "idx_messages_channel";
DROP INDEX "idx_messages_channel_nc";
DROP INDEX "idx_messages_idempotency";
DROP INDEX "idx_messages_senderip";
DROP INDEX "idx_messages_sendername";
DROP INDEX "idx_messages_sendername_nc";
DROP INDEX "idx_messages_title";
DROP INDEX "idx_messages_title_nc";
DROP INDEX "idx_messages_usedkey";
DROP INDEX "idx_messages_deleted";
CREATE TABLE __new_messages
(
message_id TEXT NOT NULL,
sender_user_id TEXT NOT NULL,
channel_internal_name TEXT NOT NULL,
channel_id TEXT NOT NULL,
channel_owner_user_id TEXT NOT NULL,
sender_ip TEXT NOT NULL,
sender_name TEXT NULL,
timestamp_real INTEGER NOT NULL,
timestamp_client INTEGER NULL,
title TEXT NOT NULL,
content TEXT NULL,
priority INTEGER CHECK(priority IN (0, 1, 2)) NOT NULL,
usr_message_id TEXT NULL,
used_key_id TEXT NOT NULL,
deleted INTEGER CHECK(deleted IN (0, 1)) NOT NULL DEFAULT '0',
PRIMARY KEY (message_id)
) STRICT;
INSERT INTO __new_messages
SELECT
m.message_id,
m.sender_user_id,
m.channel_internal_name,
m.channel_id,
c.owner_user_id,
m.sender_ip,
m.sender_name,
m.timestamp_real,
m.timestamp_client,
m.title,
m.content,
m.priority,
m.usr_message_id,
m.used_key_id,
m.deleted
FROM messages m
JOIN channels c ON m.channel_id = c.channel_id;
DROP TABLE messages;
ALTER TABLE __new_messages RENAME TO messages;
CREATE INDEX "idx_messages_channel" ON messages (channel_internal_name COLLATE BINARY);
CREATE INDEX "idx_messages_channel_nc" ON messages (channel_internal_name COLLATE NOCASE);
CREATE UNIQUE INDEX "idx_messages_idempotency" ON messages (sender_user_id, usr_message_id COLLATE BINARY);
CREATE INDEX "idx_messages_senderip" ON messages (sender_ip COLLATE BINARY);
CREATE INDEX "idx_messages_sendername" ON messages (sender_name COLLATE BINARY);
CREATE INDEX "idx_messages_sendername_nc" ON messages (sender_name COLLATE NOCASE);
CREATE INDEX "idx_messages_title" ON messages (title COLLATE BINARY);
CREATE INDEX "idx_messages_title_nc" ON messages (title COLLATE NOCASE);
CREATE INDEX "idx_messages_usedkey" ON messages (sender_user_id, used_key_id);
CREATE INDEX "idx_messages_deleted" ON messages (deleted);
DROP TRIGGER IF EXISTS fts_insert;
DROP TRIGGER IF EXISTS fts_update;
DROP TRIGGER IF EXISTS fts_delete;
DROP TABLE IF EXISTS messages_fts;
CREATE VIRTUAL TABLE messages_fts USING fts5
(
channel_internal_name,
sender_name,
title,
content,
tokenize = unicode61,
content = 'messages',
content_rowid = 'rowid'
);
CREATE TRIGGER fts_insert AFTER INSERT ON messages BEGIN
INSERT INTO messages_fts (rowid, channel_internal_name, sender_name, title, content) VALUES (new.rowid, new.channel_internal_name, new.sender_name, new.title, new.content);
END;
CREATE TRIGGER fts_update AFTER UPDATE ON messages BEGIN
INSERT INTO messages_fts (messages_fts, rowid, channel_internal_name, sender_name, title, content) VALUES ('delete', old.rowid, old.channel_internal_name, old.sender_name, old.title, old.content);
INSERT INTO messages_fts ( rowid, channel_internal_name, sender_name, title, content) VALUES ( new.rowid, new.channel_internal_name, new.sender_name, new.title, new.content);
END;
CREATE TRIGGER fts_delete AFTER DELETE ON messages BEGIN
INSERT INTO messages_fts (messages_fts, rowid, channel_internal_name, sender_name, title, content) VALUES ('delete', old.rowid, old.channel_internal_name, old.sender_name, old.title, old.content);
END;
INSERT INTO messages_fts (rowid, channel_internal_name, sender_name, title, content)
SELECT rowid, channel_internal_name, sender_name, title, content FROM messages;
------------------------------------------------------------------------------------------------------------------------
DROP INDEX "idx_channels_identity";
CREATE TABLE __new_channels
(
channel_id TEXT NOT NULL,
owner_user_id TEXT NOT NULL,
internal_name TEXT NOT NULL,
display_name TEXT NOT NULL,
description_name TEXT NULL,
subscribe_key TEXT NOT NULL,
timestamp_created INTEGER NOT NULL,
timestamp_lastsent INTEGER NULL DEFAULT NULL,
messages_sent INTEGER NOT NULL DEFAULT '0',
deleted INTEGER CHECK(deleted IN (0, 1)) NOT NULL DEFAULT '0',
PRIMARY KEY (channel_id)
) STRICT;
INSERT INTO __new_channels
SELECT
channel_id,
owner_user_id,
internal_name,
display_name,
description_name,
subscribe_key,
timestamp_created,
timestamp_lastsent,
messages_sent,
0 AS deleted
FROM channels;
DROP TABLE channels;
ALTER TABLE __new_channels RENAME TO channels;
CREATE UNIQUE INDEX "idx_channels_identity" ON channels (owner_user_id, internal_name) WHERE deleted=0;
------------------------------------------------------------------------------------------------------------------------
DROP INDEX "idx_subscriptions_ref";
DROP INDEX "idx_subscriptions_chan";
DROP INDEX "idx_subscriptions_subuser";
DROP INDEX "idx_subscriptions_ownuser";
DROP INDEX "idx_subscriptions_tsc";
DROP INDEX "idx_subscriptions_conf";
CREATE TABLE __new_subscriptions
(
subscription_id TEXT NOT NULL,
subscriber_user_id TEXT NOT NULL,
channel_owner_user_id TEXT NOT NULL,
channel_internal_name TEXT NOT NULL,
channel_id TEXT NOT NULL,
timestamp_created INTEGER NOT NULL,
confirmed INTEGER CHECK(confirmed IN (0, 1)) NOT NULL,
active INTEGER CHECK(active IN (0, 1)) NOT NULL,
deleted INTEGER CHECK(deleted IN (0, 1)) NOT NULL DEFAULT '0',
PRIMARY KEY (subscription_id)
) STRICT;
INSERT INTO __new_subscriptions
SELECT
subscription_id,
subscriber_user_id,
channel_owner_user_id,
channel_internal_name,
channel_id,
timestamp_created,
confirmed,
1 AS active,
0 AS deleted
FROM subscriptions;
DROP TABLE subscriptions;
ALTER TABLE __new_subscriptions RENAME TO subscriptions;
CREATE UNIQUE INDEX "idx_subscriptions_ref" ON subscriptions (subscriber_user_id, channel_owner_user_id, channel_internal_name) WHERE deleted=0;
CREATE INDEX "idx_subscriptions_chan" ON subscriptions (channel_id);
CREATE INDEX "idx_subscriptions_subuser" ON subscriptions (subscriber_user_id);
CREATE INDEX "idx_subscriptions_ownuser" ON subscriptions (channel_owner_user_id);
CREATE INDEX "idx_subscriptions_tsc" ON subscriptions (timestamp_created);
CREATE INDEX "idx_subscriptions_conf" ON subscriptions (confirmed);
------------------------------------------------------------------------------------------------------------------------