Only soft-delete messages

This commit is contained in:
Mike Schwörer 2022-12-14 12:29:55 +01:00
parent 98b1e8bd80
commit 66ecad27a7
Signed by: Mikescher
GPG Key ID: D3C7172E0A70F8CF
10 changed files with 238 additions and 46 deletions

View File

@ -1237,7 +1237,7 @@ func (h APIHandler) GetMessage(g *gin.Context) ginresp.HTTPResponse {
return *permResp return *permResp
} }
msg, err := h.database.GetMessage(ctx, u.MessageID) msg, err := h.database.GetMessage(ctx, u.MessageID, false)
if err == sql.ErrNoRows { if err == sql.ErrNoRows {
return ginresp.APIError(g, 404, apierr.MESSAGE_NOT_FOUND, "message not found", err) return ginresp.APIError(g, 404, apierr.MESSAGE_NOT_FOUND, "message not found", err)
} }
@ -1307,7 +1307,7 @@ func (h APIHandler) DeleteMessage(g *gin.Context) ginresp.HTTPResponse {
return *permResp return *permResp
} }
msg, err := h.database.GetMessage(ctx, u.MessageID) msg, err := h.database.GetMessage(ctx, u.MessageID, false)
if err == sql.ErrNoRows { if err == sql.ErrNoRows {
return ginresp.APIError(g, 404, apierr.MESSAGE_NOT_FOUND, "message not found", err) return ginresp.APIError(g, 404, apierr.MESSAGE_NOT_FOUND, "message not found", err)
} }

View File

@ -533,7 +533,7 @@ func (h CompatHandler) Expand(g *gin.Context) ginresp.HTTPResponse {
return ginresp.CompatAPIError(204, "Authentification failed") return ginresp.CompatAPIError(204, "Authentification failed")
} }
msg, err := h.database.GetMessage(ctx, models.SCNMessageID(*data.MessageID)) msg, err := h.database.GetMessage(ctx, models.SCNMessageID(*data.MessageID), false)
if err == sql.ErrNoRows { if err == sql.ErrNoRows {
return ginresp.CompatAPIError(301, "Message not found") return ginresp.CompatAPIError(301, "Message not found")
} }

View File

@ -196,6 +196,7 @@ func (h MessageHandler) sendMessageInternal(g *gin.Context, ctx *logic.AppContex
return ginresp.SendAPIError(g, 500, apierr.DATABASE_ERROR, hl.NONE, "Failed to query existing message", err) return ginresp.SendAPIError(g, 500, apierr.DATABASE_ERROR, hl.NONE, "Failed to query existing message", err)
} }
if msg != nil { if msg != nil {
//the found message can be deleted (!), but we still return NO_ERROR here...
return ctx.FinishSuccess(ginresp.JSON(http.StatusOK, response{ return ctx.FinishSuccess(ginresp.JSON(http.StatusOK, response{
Success: true, Success: true,
ErrorID: apierr.NO_ERROR, ErrorID: apierr.NO_ERROR,

View File

@ -30,13 +30,20 @@ func (db *Database) GetMessageByUserMessageID(ctx TxContext, usrMsgId string) (*
return &msg, nil return &msg, nil
} }
func (db *Database) GetMessage(ctx TxContext, scnMessageID models.SCNMessageID) (models.Message, error) { func (db *Database) GetMessage(ctx TxContext, scnMessageID models.SCNMessageID, allowDeleted bool) (models.Message, error) {
tx, err := ctx.GetOrCreateTransaction(db) tx, err := ctx.GetOrCreateTransaction(db)
if err != nil { if err != nil {
return models.Message{}, err return models.Message{}, err
} }
rows, err := tx.Query(ctx, "SELECT * FROM messages WHERE scn_message_id = :mid LIMIT 1", sq.PP{"mid": scnMessageID}) var sqlcmd string
if allowDeleted {
sqlcmd = "SELECT * FROM messages WHERE scn_message_id = :mid LIMIT 1"
} else {
sqlcmd = "SELECT * FROM messages WHERE scn_message_id = :mid AND deleted=0 LIMIT 1"
}
rows, err := tx.Query(ctx, sqlcmd, sq.PP{"mid": scnMessageID})
if err != nil { if err != nil {
return models.Message{}, err return models.Message{}, err
} }
@ -103,7 +110,7 @@ func (db *Database) DeleteMessage(ctx TxContext, scnMessageID models.SCNMessageI
return err return err
} }
_, err = tx.Exec(ctx, "DELETE FROM messages WHERE scn_message_id = :mid", sq.PP{"mid": scnMessageID}) _, err = tx.Exec(ctx, "UPDATE messages SET deleted=1 WHERE scn_message_id = :mid AND deleted=0", sq.PP{"mid": scnMessageID})
if err != nil { if err != nil {
return err return err
} }

View File

@ -90,7 +90,9 @@ CREATE TABLE messages
title TEXT NOT NULL, title TEXT NOT NULL,
content TEXT NULL, content TEXT NULL,
priority INTEGER CHECK(priority IN (0, 1, 2)) NOT NULL, priority INTEGER CHECK(priority IN (0, 1, 2)) NOT NULL,
usr_message_id TEXT NULL usr_message_id TEXT NULL,
deleted INTEGER CHECK(deleted IN (0, 1)) NOT NULL DEFAULT '0'
) STRICT; ) STRICT;
CREATE INDEX "idx_messages_owner_channel" ON messages (owner_user_id, channel_name COLLATE BINARY); CREATE INDEX "idx_messages_owner_channel" ON messages (owner_user_id, channel_name COLLATE BINARY);
CREATE INDEX "idx_messages_owner_channel_nc" ON messages (owner_user_id, channel_name COLLATE NOCASE); CREATE INDEX "idx_messages_owner_channel_nc" ON messages (owner_user_id, channel_name COLLATE NOCASE);
@ -102,6 +104,7 @@ CREATE INDEX "idx_messages_sendername" ON messages (sender_name COL
CREATE INDEX "idx_messages_sendername_nc" ON messages (sender_name COLLATE NOCASE); 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" ON messages (title COLLATE BINARY);
CREATE INDEX "idx_messages_title_nc" ON messages (title COLLATE NOCASE); CREATE INDEX "idx_messages_title_nc" ON messages (title COLLATE NOCASE);
CREATE INDEX "idx_messages_deleted" ON messages (deleted);
CREATE VIRTUAL TABLE messages_fts USING fts5 CREATE VIRTUAL TABLE messages_fts USING fts5

View File

@ -93,13 +93,22 @@ func (j *DeliveryRetryJob) redeliver(ctx *logic.SimpleContext, delivery models.D
return return
} }
msg, err := j.app.Database.GetMessage(ctx, delivery.SCNMessageID) msg, err := j.app.Database.GetMessage(ctx, delivery.SCNMessageID, true)
if err != nil { if err != nil {
log.Err(err).Int64("SCNMessageID", delivery.SCNMessageID.IntID()).Msg("Failed to get message") log.Err(err).Int64("SCNMessageID", delivery.SCNMessageID.IntID()).Msg("Failed to get message")
ctx.RollbackTransaction() ctx.RollbackTransaction()
return return
} }
if msg.Deleted {
err = j.app.Database.SetDeliveryFailed(ctx, delivery)
if err != nil {
log.Err(err).Int64("SCNMessageID", delivery.SCNMessageID.IntID()).Int64("DeliveryID", delivery.DeliveryID.IntID()).Msg("Failed to update delivery")
ctx.RollbackTransaction()
return
}
} else {
fcmDelivID, err := j.app.DeliverMessage(ctx, client, msg) fcmDelivID, err := j.app.DeliverMessage(ctx, client, msg)
if err == nil { if err == nil {
err = j.app.Database.SetDeliverySuccess(ctx, delivery, *fcmDelivID) err = j.app.Database.SetDeliverySuccess(ctx, delivery, *fcmDelivID)
@ -125,6 +134,8 @@ func (j *DeliveryRetryJob) redeliver(ctx *logic.SimpleContext, delivery models.D
} }
} }
}
err = ctx.CommitTransaction() err = ctx.CommitTransaction()
} }

View File

@ -26,6 +26,7 @@ type Message struct {
Content *string Content *string
Priority int Priority int
UserMessageID *string UserMessageID *string
Deleted bool
} }
func (m Message) FullJSON() MessageJSON { func (m Message) FullJSON() MessageJSON {
@ -122,6 +123,7 @@ type MessageDB struct {
Content *string `db:"content"` Content *string `db:"content"`
Priority int `db:"priority"` Priority int `db:"priority"`
UserMessageID *string `db:"usr_message_id"` UserMessageID *string `db:"usr_message_id"`
Deleted int `db:"deleted"`
} }
func (m MessageDB) Model() Message { func (m MessageDB) Model() Message {
@ -139,6 +141,7 @@ func (m MessageDB) Model() Message {
Content: m.Content, Content: m.Content,
Priority: m.Priority, Priority: m.Priority,
UserMessageID: m.UserMessageID, UserMessageID: m.UserMessageID,
Deleted: m.Deleted != 0,
} }
} }

View File

@ -37,6 +37,8 @@ type MessageFilter struct {
TitleCI *string // case-insensitive TitleCI *string // case-insensitive
Priority *[]int Priority *[]int
UserMessageID *[]string UserMessageID *[]string
OnlyDeleted bool
IncludeDeleted bool
} }
func (f MessageFilter) SQL() (string, string, sq.PP, error) { func (f MessageFilter) SQL() (string, string, sq.PP, error) {
@ -53,6 +55,14 @@ func (f MessageFilter) SQL() (string, string, sq.PP, error) {
params := sq.PP{} 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 { if f.ConfirmedSubscriptionBy != nil {
sqlClauses = append(sqlClauses, "(subs.subscriber_user_id = :sub_uid AND subs.confirmed = 1)") sqlClauses = append(sqlClauses, "(subs.subscriber_user_id = :sub_uid AND subs.confirmed = 1)")
params["sub_uid"] = *f.ConfirmedSubscriptionBy params["sub_uid"] = *f.ConfirmedSubscriptionBy

View File

@ -1,12 +1,15 @@
package test package test
import ( import (
"blackforestbytes.com/simplecloudnotifier/api/apierr"
tt "blackforestbytes.com/simplecloudnotifier/test/util" tt "blackforestbytes.com/simplecloudnotifier/test/util"
"fmt" "fmt"
"github.com/gin-gonic/gin" "github.com/gin-gonic/gin"
"gogs.mikescher.com/BlackForestBytes/goext/langext" "gogs.mikescher.com/BlackForestBytes/goext/langext"
"gogs.mikescher.com/BlackForestBytes/goext/timeext"
"net/url" "net/url"
"testing" "testing"
"time"
) )
func TestSearchMessageFTSSimple(t *testing.T) { func TestSearchMessageFTSSimple(t *testing.T) {
@ -31,4 +34,154 @@ func TestSearchMessageFTSMulti(t *testing.T) {
//TODO search for messages by FTS //TODO search for messages by FTS
} }
//TODO test missing message-xx methods //TODO more search/list/filter message tests
//TODO list messages by chan_key
//TODO list messages from channel that you cannot see
func TestDeleteMessage(t *testing.T) {
ws, stop := tt.StartSimpleWebserver(t)
defer stop()
baseUrl := "http://127.0.0.1:" + ws.Port
r0 := tt.RequestPost[gin.H](t, baseUrl, "/api/users", gin.H{
"agent_model": "DUMMY_PHONE",
"agent_version": "4X",
"client_type": "ANDROID",
"fcm_token": "DUMMY_FCM",
})
uid := int(r0["user_id"].(float64))
sendtok := r0["send_key"].(string)
admintok := r0["admin_key"].(string)
msg1 := tt.RequestPost[gin.H](t, baseUrl, "/", gin.H{
"user_key": sendtok,
"user_id": uid,
"title": "Message_1",
})
tt.RequestAuthGet[tt.Void](t, admintok, baseUrl, "/api/messages/"+fmt.Sprintf("%v", msg1["scn_msg_id"]))
tt.RequestAuthDelete[tt.Void](t, admintok, baseUrl, "/api/messages/"+fmt.Sprintf("%v", msg1["scn_msg_id"]), gin.H{})
tt.RequestAuthGetShouldFail(t, admintok, baseUrl, "/api/messages/"+fmt.Sprintf("%v", msg1["scn_msg_id"]), 404, apierr.MESSAGE_NOT_FOUND)
}
func TestDeleteMessageAndResendUsrMsgId(t *testing.T) {
ws, stop := tt.StartSimpleWebserver(t)
defer stop()
baseUrl := "http://127.0.0.1:" + ws.Port
r0 := tt.RequestPost[gin.H](t, baseUrl, "/api/users", gin.H{
"agent_model": "DUMMY_PHONE",
"agent_version": "4X",
"client_type": "ANDROID",
"fcm_token": "DUMMY_FCM",
})
uid := int(r0["user_id"].(float64))
sendtok := r0["send_key"].(string)
admintok := r0["admin_key"].(string)
msg1 := tt.RequestPost[gin.H](t, baseUrl, "/", gin.H{
"user_key": sendtok,
"user_id": uid,
"title": "Message_1",
"msg_id": "bef8dd3d-078e-4f89-abf4-5258ad22a2e4",
})
tt.AssertEqual(t, "suppress_send", false, msg1["suppress_send"])
tt.RequestAuthGet[tt.Void](t, admintok, baseUrl, "/api/messages/"+fmt.Sprintf("%v", msg1["scn_msg_id"]))
msg2 := tt.RequestPost[gin.H](t, baseUrl, "/", gin.H{
"user_key": sendtok,
"user_id": uid,
"title": "Message_1",
"msg_id": "bef8dd3d-078e-4f89-abf4-5258ad22a2e4",
})
tt.AssertEqual(t, "suppress_send", true, msg2["suppress_send"])
tt.RequestAuthDelete[tt.Void](t, admintok, baseUrl, "/api/messages/"+fmt.Sprintf("%v", msg1["scn_msg_id"]), gin.H{})
// even though message is deleted, we still get a `suppress_send` on send_message
msg3 := tt.RequestPost[gin.H](t, baseUrl, "/", gin.H{
"user_key": sendtok,
"user_id": uid,
"title": "Message_1",
"msg_id": "bef8dd3d-078e-4f89-abf4-5258ad22a2e4",
})
tt.AssertEqual(t, "suppress_send", true, msg3["suppress_send"])
}
func TestGetMessageSimple(t *testing.T) {
ws, stop := tt.StartSimpleWebserver(t)
defer stop()
baseUrl := "http://127.0.0.1:" + ws.Port
data := tt.InitDefaultData(t, ws)
msgOut := tt.RequestPost[gin.H](t, baseUrl, "/", gin.H{
"user_key": data.User[0].SendKey,
"user_id": data.User[0].UID,
"title": "Message_1",
})
msgIn := tt.RequestAuthGet[gin.H](t, data.User[0].AdminKey, baseUrl, "/api/messages/"+fmt.Sprintf("%v", msgOut["scn_msg_id"]))
tt.AssertEqual(t, "msg.title", "Message_1", msgIn["title"])
}
func TestGetMessageNotFound(t *testing.T) {
ws, stop := tt.StartSimpleWebserver(t)
defer stop()
baseUrl := "http://127.0.0.1:" + ws.Port
data := tt.InitDefaultData(t, ws)
tt.RequestAuthGetShouldFail(t, data.User[0].AdminKey, baseUrl, "/api/messages/8963586", 404, apierr.MESSAGE_NOT_FOUND)
}
func TestGetMessageFull(t *testing.T) {
ws, stop := tt.StartSimpleWebserver(t)
defer stop()
baseUrl := "http://127.0.0.1:" + ws.Port
data := tt.InitDefaultData(t, ws)
ts := time.Now().Unix() - 735
content := tt.Lipsum0(2)
msgOut := tt.RequestPost[gin.H](t, baseUrl, "/", gin.H{
"user_key": data.User[0].SendKey,
"user_id": data.User[0].UID,
"title": "Message_1",
"content": content,
"channel": "demo-channel-007",
"msg_id": "580b5055-a9b5-4cee-b53c-28cf304d25b0",
"priority": 0,
"sender_name": "unit-test-[TestGetMessageFull]",
"timestamp": ts,
})
msgIn := tt.RequestAuthGet[gin.H](t, data.User[0].AdminKey, baseUrl, "/api/messages/"+fmt.Sprintf("%v", msgOut["scn_msg_id"]))
tt.AssertEqual(t, "msg.title", "Message_1", msgIn["title"])
tt.AssertEqual(t, "msg.content", content, msgIn["content"])
tt.AssertEqual(t, "msg.channel", "demo-channel-007", msgIn["channel_name"])
tt.AssertEqual(t, "msg.msg_id", "580b5055-a9b5-4cee-b53c-28cf304d25b0", msgIn["usr_message_id"])
tt.AssertStrRepEqual(t, "msg.priority", 0, msgIn["priority"])
tt.AssertEqual(t, "msg.sender_name", "unit-test-[TestGetMessageFull]", msgIn["sender_name"])
tt.AssertEqual(t, "msg.timestamp", time.Unix(ts, 0).In(timeext.TimezoneBerlin).Format(time.RFC3339Nano), msgIn["timestamp"])
}

View File

@ -112,7 +112,7 @@ var messageExamples = []msgex{
{0, "", "", P0, SKEY, "Congratulations", "You have been selected as Employee of the Month. Please come to the front desk to pick up your prize", 0}, {0, "", "", P0, SKEY, "Congratulations", "You have been selected as Employee of the Month. Please come to the front desk to pick up your prize", 0},
{0, "", "", PX, AKEY, "Attention", "The water cooler is empty. Could someone please refill it?", timeext.FromHours(-11.29)}, {0, "", "", PX, AKEY, "Attention", "The water cooler is empty. Could someone please refill it?", timeext.FromHours(-11.29)},
{0, "Chatting Chamber", "Mobile Mate", P2, SKEY, "Important", "All employees are required to complete a safety training course by the end of the month", 0}, {0, "Chatting Chamber", "Mobile Mate", P2, SKEY, "Important", "All employees are required to complete a safety training course by the end of the month", 0},
{0, "", "", P1, AKEY, "FAQ Update", lipsum(10001, 1), 0}, {0, "", "", P1, AKEY, "FAQ Update", Lipsum(10001, 1), 0},
{0, "", "", PX, AKEY, "Notice", "There will be a fire drill at 10:00am tomorrow. Please follow the instructions of the fire marshal", 0}, {0, "", "", PX, AKEY, "Notice", "There will be a fire drill at 10:00am tomorrow. Please follow the instructions of the fire marshal", 0},
{0, "", "Cellular Confidant", P2, SKEY, "Invitation", "You are invited to a celebration in honor of our 10-year anniversary. The party will be held on Friday at 7:00pm", 0}, {0, "", "Cellular Confidant", P2, SKEY, "Invitation", "You are invited to a celebration in honor of our 10-year anniversary. The party will be held on Friday at 7:00pm", 0},
{0, "", "", P0, SKEY, "Deadline reminder", "Please remember to submit your project proposal by the end of the day \U0001f638", 0}, {0, "", "", P0, SKEY, "Deadline reminder", "Please remember to submit your project proposal by the end of the day \U0001f638", 0},
@ -156,9 +156,9 @@ var messageExamples = []msgex{
{3, "", "", PX, AKEY, "Payment confirmation", "Your payment of $100 has been successfully processed. Thank you for your business.", 0}, {3, "", "", PX, AKEY, "Payment confirmation", "Your payment of $100 has been successfully processed. Thank you for your business.", 0},
{3, "", "", P2, SKEY, "Task completed", "Your task \"Update website content\" has been completed and is ready for review.", 0}, {3, "", "", P2, SKEY, "Task completed", "Your task \"Update website content\" has been completed and is ready for review.", 0},
{3, "Innovations", "", PX, AKEY, "Invitation to join a group", "You have been invited to join the \"Marketing Team\" group on our collaboration platform.", 0}, {3, "Innovations", "", PX, AKEY, "Invitation to join a group", "You have been invited to join the \"Marketing Team\" group on our collaboration platform.", 0},
{3, "", "", P2, SKEY, "Password reset", lipsum(10002, 1), 0}, {3, "", "", P2, SKEY, "Password reset", Lipsum(10002, 1), 0},
{3, "", "", P2, SKEY, "Low battery alert", lipsum(10003, 2), 0}, {3, "", "", P2, SKEY, "Low battery alert", Lipsum(10003, 2), 0},
{3, "Innovations", "", P2, SKEY, "System update available", lipsum(10004, 5), 0}, {3, "Innovations", "", P2, SKEY, "System update available", Lipsum(10004, 5), 0},
{3, "", "", P2, SKEY, "Appointment confirmation", "Your appointment for a physical exam on Monday, March 15th at 10 AM has been confirmed.", 0}, {3, "", "", P2, SKEY, "Appointment confirmation", "Your appointment for a physical exam on Monday, March 15th at 10 AM has been confirmed.", 0},
{3, "\U0001f5ff", "", P2, SKEY, "Order shipped", "Your order #123456 has been shipped and is on its way to your address.", 0}, {3, "\U0001f5ff", "", P2, SKEY, "Order shipped", "Your order #123456 has been shipped and is on its way to your address.", 0},
{3, "", "", P2, SKEY, "Order cancelled", "Your order #123456 has been cancelled. We apologize for any inconvenience this may have caused.", 0}, {3, "", "", P2, SKEY, "Order cancelled", "Your order #123456 has been cancelled. We apologize for any inconvenience this may have caused.", 0},
@ -166,7 +166,7 @@ var messageExamples = []msgex{
{3, "Reminders", "", PX, AKEY, "Account verification", "", timeext.FromHours(1.15)}, {3, "Reminders", "", PX, AKEY, "Account verification", "", timeext.FromHours(1.15)},
{3, "Reminders", "", PX, AKEY, "Overdue payment", "", 0}, {3, "Reminders", "", PX, AKEY, "Overdue payment", "", 0},
{3, "Reminders", "", P2, SKEY, "Security alert", "We have detected suspicious activity on your account. Please take the necessary steps to secure your account.", timeext.FromHours(0.80)}, {3, "Reminders", "", P2, SKEY, "Security alert", "We have detected suspicious activity on your account. Please take the necessary steps to secure your account.", timeext.FromHours(0.80)},
{3, "Reminders", "", PX, AKEY, "Product back in stock", lipsum(10001, 6), 0}, {3, "Reminders", "", PX, AKEY, "Product back in stock", Lipsum(10001, 6), 0},
{3, "", "", PX, AKEY, "Connection lost", "Your device has lost its connection to the internet. Please check your network settings and try again.", 0}, {3, "", "", PX, AKEY, "Connection lost", "Your device has lost its connection to the internet. Please check your network settings and try again.", 0},
{3, "", "", P2, SKEY, "Subscription renewal", "Your subscription is set to renew in one week. Please update your payment information to avoid any interruption in service.", 0}, {3, "", "", P2, SKEY, "Subscription renewal", "Your subscription is set to renew in one week. Please update your payment information to avoid any interruption in service.", 0},
{3, "", "", PX, AKEY, "Work order assigned", "You have been assigned a new work order #123456. Please review the details and complete the task as soon as possible.", 0}, {3, "", "", PX, AKEY, "Work order assigned", "You have been assigned a new work order #123456. Please review the details and complete the task as soon as possible.", 0},
@ -202,22 +202,22 @@ var messageExamples = []msgex{
{6, "", "server1", P2, SKEY, "Server performance improvement", "Thanks to recent upgrades, the server is now performing better than ever", 0}, {6, "", "server1", P2, SKEY, "Server performance improvement", "Thanks to recent upgrades, the server is now performing better than ever", 0},
{6, "", "server1", PX, AKEY, "Server security update", "The server has been updated with the latest security patches and enhancements", 0}, {6, "", "server1", PX, AKEY, "Server security update", "The server has been updated with the latest security patches and enhancements", 0},
{6, "", "server1", P1, AKEY, "Server downtime schedule change", "The server downtime schedule has been changed to every other Friday at 8am EST", 0}, {6, "", "server1", P1, AKEY, "Server downtime schedule change", "The server downtime schedule has been changed to every other Friday at 8am EST", 0},
{6, "Lipsum", "", P2, SKEY, "Lorem Ipsum", lipsum(20001, 1), 0}, {6, "Lipsum", "", P2, SKEY, "Lorem Ipsum", Lipsum(20001, 1), 0},
{6, "Lipsum", "", P0, SKEY, "Lorem Ipsum", lipsum(20002, 1), 0}, {6, "Lipsum", "", P0, SKEY, "Lorem Ipsum", Lipsum(20002, 1), 0},
{6, "Lipsum", "", P2, SKEY, "Lorem Ipsum", lipsum(20003, 1), 0}, {6, "Lipsum", "", P2, SKEY, "Lorem Ipsum", Lipsum(20003, 1), 0},
{6, "Lipsum", "", P0, SKEY, "Lorem Ipsum", lipsum(20004, 1), 0}, {6, "Lipsum", "", P0, SKEY, "Lorem Ipsum", Lipsum(20004, 1), 0},
{6, "Lipsum", "", P2, SKEY, "Lorem Ipsum", lipsum(20005, 1), 0}, {6, "Lipsum", "", P2, SKEY, "Lorem Ipsum", Lipsum(20005, 1), 0},
{6, "Lipsum", "", P1, AKEY, "Lorem Ipsum", lipsum(20006, 1), 0}, {6, "Lipsum", "", P1, AKEY, "Lorem Ipsum", Lipsum(20006, 1), 0},
{6, "Lipsum", "", P1, AKEY, "Lorem Ipsum", lipsum(20007, 1), timeext.FromHours(-3.39)}, {6, "Lipsum", "", P1, AKEY, "Lorem Ipsum", Lipsum(20007, 1), timeext.FromHours(-3.39)},
{6, "Lipsum", "", P0, SKEY, "Lorem Ipsum", lipsum(20008, 1), 0}, {6, "Lipsum", "", P0, SKEY, "Lorem Ipsum", Lipsum(20008, 1), 0},
{6, "Lipsum", "", PX, AKEY, "Lorem Ipsum", lipsum(20009, 1), 0}, {6, "Lipsum", "", PX, AKEY, "Lorem Ipsum", Lipsum(20009, 1), 0},
{6, "Lipsum", "", P0, SKEY, "Lorem Ipsum", lipsum(20010, 1), 0}, {6, "Lipsum", "", P0, SKEY, "Lorem Ipsum", Lipsum(20010, 1), 0},
{6, "Lipsum", "", P2, SKEY, "Lorem Ipsum", lipsum(20011, 1), 0}, {6, "Lipsum", "", P2, SKEY, "Lorem Ipsum", Lipsum(20011, 1), 0},
{6, "Lipsum", "", PX, AKEY, "Lorem Ipsum", lipsum(20012, 1), 0}, {6, "Lipsum", "", PX, AKEY, "Lorem Ipsum", Lipsum(20012, 1), 0},
{6, "Lipsum", "", P2, SKEY, "Lorem Ipsum", lipsum(20013, 1), 0}, {6, "Lipsum", "", P2, SKEY, "Lorem Ipsum", Lipsum(20013, 1), 0},
{6, "Lipsum", "", P2, SKEY, "Lorem Ipsum", lipsum(20014, 1), timeext.FromHours(-2.33)}, {6, "Lipsum", "", P2, SKEY, "Lorem Ipsum", Lipsum(20014, 1), timeext.FromHours(-2.33)},
{6, "Lipsum", "", P0, SKEY, "Lorem Ipsum", lipsum(20015, 1), 0}, {6, "Lipsum", "", P0, SKEY, "Lorem Ipsum", Lipsum(20015, 1), 0},
{6, "Lipsum", "", P0, SKEY, "Lorem Ipsum", lipsum(20016, 1), 0}, {6, "Lipsum", "", P0, SKEY, "Lorem Ipsum", Lipsum(20016, 1), 0},
{7, "", "localhost", P2, SKEY, "Server outage resolution update", "We are still working on resolving the server outage and will provide updates as soon as possible", 0}, {7, "", "localhost", P2, SKEY, "Server outage resolution update", "We are still working on resolving the server outage and will provide updates as soon as possible", 0},
{7, "", "localhost", P0, SKEY, "New server release update", "A new update for the server has been released. Please update to the latest version for optimal performance", 0}, {7, "", "localhost", P0, SKEY, "New server release update", "A new update for the server has been released. Please update to the latest version for optimal performance", 0},
@ -369,6 +369,10 @@ func InitDefaultData(t *testing.T, ws *logic.Application) DefData {
return DefData{User: users} return DefData{User: users}
} }
func lipsum(seed int64, paracount int) string { func Lipsum(seed int64, paracount int) string {
return loremipsum.NewWithSeed(seed).Paragraphs(paracount) return loremipsum.NewWithSeed(seed).Paragraphs(paracount)
} }
func Lipsum0(paracount int) string {
return loremipsum.NewWithSeed(0).Paragraphs(paracount)
}