565 lines
13 KiB
Go
565 lines
13 KiB
Go
package guilddb
|
|
|
|
import (
|
|
"database/sql"
|
|
"encoding/json"
|
|
"errors"
|
|
"fmt"
|
|
"slices"
|
|
"strings"
|
|
|
|
"forge.capytal.company/capytal/dislate/translator"
|
|
|
|
_ "github.com/tursodatabase/go-libsql"
|
|
)
|
|
|
|
type SQLiteDB[C any] struct {
|
|
sql *sql.DB
|
|
}
|
|
|
|
func NewSQLiteDB[C any](file string) (*SQLiteDB[C], error) {
|
|
db, err := sql.Open("libsql", file)
|
|
if err != nil {
|
|
return &SQLiteDB[C]{}, err
|
|
}
|
|
db.SetMaxOpenConns(1)
|
|
return &SQLiteDB[C]{db}, nil
|
|
}
|
|
|
|
func (db *SQLiteDB[C]) Close() error {
|
|
return db.sql.Close()
|
|
}
|
|
|
|
func (db *SQLiteDB[C]) Prepare() error {
|
|
if _, err := db.sql.Exec(`
|
|
CREATE TABLE IF NOT EXISTS guilds (
|
|
ID text NOT NULL,
|
|
Config text NOT NULL,
|
|
PRIMARY KEY(ID)
|
|
);
|
|
`); err != nil {
|
|
return errors.Join(ErrInternal, err)
|
|
}
|
|
|
|
if _, err := db.sql.Exec(`
|
|
CREATE TABLE IF NOT EXISTS channels (
|
|
GuildID text NOT NULL,
|
|
ID text NOT NULL,
|
|
Language text NOT NULL,
|
|
PRIMARY KEY(ID, GuildID),
|
|
FOREIGN KEY(GuildID) REFERENCES guilds(ID)
|
|
);
|
|
`); err != nil {
|
|
return errors.Join(ErrInternal, err)
|
|
}
|
|
|
|
if _, err := db.sql.Exec(`
|
|
CREATE TABLE IF NOT EXISTS channelGroups (
|
|
GuildID text NOT NULL,
|
|
Channels text NOT NULL,
|
|
PRIMARY KEY(Channels, GuildID),
|
|
FOREIGN KEY(GuildID) REFERENCES guilds(ID)
|
|
);
|
|
`); err != nil {
|
|
return errors.Join(ErrInternal, err)
|
|
}
|
|
|
|
if _, err := db.sql.Exec(`
|
|
CREATE TABLE IF NOT EXISTS messages (
|
|
GuildID text NOT NULL,
|
|
ChannelID text NOT NULL,
|
|
ID text NOT NULL,
|
|
Language text NOT NULL,
|
|
OriginChannelID text,
|
|
OriginID text,
|
|
PRIMARY KEY(ID, ChannelID, GuildID),
|
|
FOREIGN KEY(GuildID, ChannelID) REFERENCES channels(GuildID, ID),
|
|
FOREIGN KEY(GuildID, OriginChannelID, OriginID) REFERENCES messages(GuildID, ChannelID, ID)
|
|
);
|
|
`); err != nil {
|
|
return errors.Join(ErrInternal, err)
|
|
}
|
|
|
|
return nil
|
|
}
|
|
|
|
func (db *SQLiteDB[C]) Message(guildID, channelID, messageID string) (Message, error) {
|
|
return db.selectMessage(`
|
|
WHERE "GuildID" = $1 AND "ChannelID" = $2 AND "ID" = $3
|
|
`, guildID, channelID, messageID)
|
|
}
|
|
|
|
func (db *SQLiteDB[C]) MessagesWithOrigin(
|
|
guildID, originChannelID, originID string,
|
|
) ([]Message, error) {
|
|
return db.selectMessages(`
|
|
WHERE "GuildID" = $1 AND "OriginChannelID" = $2 AND "OriginID" = $3
|
|
`, guildID, originChannelID, originID)
|
|
}
|
|
|
|
func (db *SQLiteDB[C]) MessageWithOriginByLang(
|
|
guildID, originChannelID, originID string,
|
|
language translator.Language,
|
|
) (Message, error) {
|
|
return db.selectMessage(`
|
|
WHERE "GuildID" = $1 AND "OriginChannelID" = $2 AND "OriginID" = $3 AND "Language" = $4
|
|
`, guildID, originChannelID, originID, language)
|
|
}
|
|
|
|
func (db *SQLiteDB[C]) MessageInsert(m Message) error {
|
|
_, err := db.Channel(m.GuildID, m.ChannelID)
|
|
if errors.Is(err, ErrNotFound) {
|
|
return errors.Join(
|
|
ErrPreconditionFailed,
|
|
fmt.Errorf("Channel %s doesn't exists in the database", m.ChannelID),
|
|
)
|
|
} else if err != nil {
|
|
return errors.Join(
|
|
ErrInternal,
|
|
errors.New("Failed to check if Channel exists in the database"),
|
|
err,
|
|
)
|
|
}
|
|
|
|
r, err := db.sql.Exec(`
|
|
INSERT OR IGNORE INTO messages (GuildID, ChannelID, ID, Language, OriginChannelID, OriginID)
|
|
VALUES ($1, $2, $3, $4, $5, $6)
|
|
`, m.GuildID, m.ChannelID, m.ID, m.Language, m.OriginChannelID, m.OriginID)
|
|
|
|
if err != nil {
|
|
return errors.Join(ErrInternal, err)
|
|
} else if rows, _ := r.RowsAffected(); rows == 0 {
|
|
return ErrNoAffect
|
|
}
|
|
|
|
return nil
|
|
}
|
|
|
|
func (db *SQLiteDB[C]) MessageUpdate(m Message) error {
|
|
r, err := db.sql.Exec(`
|
|
UPDATE messages
|
|
SET Language = $1, OriginChannelID = $2, OriginID = $3
|
|
WHERE "GuildID" = $4 AND "ChannelID" = $5 AND "ID" = $6
|
|
`, m.Language,
|
|
m.OriginChannelID,
|
|
m.OriginID,
|
|
m.GuildID,
|
|
m.ChannelID,
|
|
m.ID,
|
|
)
|
|
|
|
if err != nil {
|
|
return errors.Join(ErrInternal, err)
|
|
} else if rows, _ := r.RowsAffected(); rows == 0 {
|
|
return ErrNoAffect
|
|
}
|
|
|
|
return nil
|
|
}
|
|
|
|
func (db *SQLiteDB[C]) MessageDelete(m Message) error {
|
|
_, err := db.sql.Exec(`
|
|
DELETE FROM messages
|
|
WHERE "GuildID" = $1 AND "OriginChannelID" = $2 AND "OriginID" = $3
|
|
`, m.GuildID, m.ChannelID, m.ID)
|
|
if err != nil && !errors.Is(err, ErrNoAffect) {
|
|
return errors.Join(ErrInternal, err)
|
|
}
|
|
|
|
r, err := db.sql.Exec(`
|
|
DELETE FROM messages
|
|
WHERE "GuildID" = $1 AND "ChannelID" = $2 AND "ID" = $3
|
|
`, m.GuildID, m.ChannelID, m.ID)
|
|
|
|
if err != nil {
|
|
return errors.Join(ErrInternal, err)
|
|
} else if rows, _ := r.RowsAffected(); rows == 0 {
|
|
return ErrNoAffect
|
|
}
|
|
|
|
return nil
|
|
}
|
|
|
|
func (db *SQLiteDB[C]) MessageDeleteFromChannel(c Channel) error {
|
|
r, err := db.sql.Exec(`
|
|
DELETE FROM messages
|
|
WHERE "GuildID" = $1 AND "ChannelID" = $2
|
|
`, c.GuildID, c.ID)
|
|
if err != nil && !errors.Is(err, ErrNoAffect) {
|
|
return errors.Join(ErrInternal, err)
|
|
} else if rows, _ := r.RowsAffected(); rows == 0 {
|
|
return ErrNoAffect
|
|
}
|
|
|
|
return nil
|
|
}
|
|
|
|
func (db *SQLiteDB[C]) selectMessage(query string, args ...any) (Message, error) {
|
|
var m Message
|
|
err := db.sql.QueryRow(fmt.Sprintf(`
|
|
SELECT GuildID, ChannelID, ID, Language, OriginChannelID, OriginID FROM messages
|
|
%s
|
|
`, query), args...).
|
|
Scan(&m.GuildID, &m.ChannelID, &m.ID, &m.Language, &m.OriginChannelID, &m.OriginID)
|
|
|
|
if errors.Is(err, sql.ErrNoRows) {
|
|
return m, errors.Join(ErrNotFound, err)
|
|
} else if err != nil {
|
|
return m, errors.Join(ErrInternal, err)
|
|
}
|
|
|
|
return m, nil
|
|
}
|
|
|
|
func (db *SQLiteDB[C]) selectMessages(query string, args ...any) ([]Message, error) {
|
|
r, err := db.sql.Query(fmt.Sprintf(`
|
|
SELECT GuildID, ChannelID, ID, Language, OriginChannelID, OriginID FROM messages
|
|
%s
|
|
`, query), args...)
|
|
defer r.Close()
|
|
|
|
if err != nil {
|
|
return []Message{}, errors.Join(ErrInternal, err)
|
|
}
|
|
|
|
var ms []Message
|
|
for r.Next() {
|
|
var m Message
|
|
|
|
err = r.Scan(&m.GuildID, &m.ChannelID, &m.ID, &m.Language, &m.OriginChannelID, &m.OriginID)
|
|
if err != nil {
|
|
return ms, errors.Join(
|
|
ErrInternal,
|
|
fmt.Errorf("Query: %s\nArguments: %v", query, args),
|
|
err,
|
|
)
|
|
}
|
|
|
|
ms = append(ms, m)
|
|
}
|
|
|
|
if len(ms) == 0 {
|
|
return ms, errors.Join(
|
|
ErrNotFound,
|
|
fmt.Errorf("Query: %s\nArguments: %v", query, args),
|
|
)
|
|
}
|
|
return ms, err
|
|
}
|
|
|
|
func (db *SQLiteDB[C]) Channel(guildID, ID string) (Channel, error) {
|
|
return db.selectChannel(`
|
|
WHERE "GuildID" = $1 AND "ID" = $2
|
|
`, guildID, ID)
|
|
}
|
|
|
|
func (db *SQLiteDB[C]) ChannelInsert(c Channel) error {
|
|
r, err := db.sql.Exec(`
|
|
INSERT OR IGNORE INTO channels (GuildID, ID, Language)
|
|
VALUES ($1, $2, $3)
|
|
`, c.GuildID, c.ID, c.Language)
|
|
|
|
if err != nil {
|
|
return errors.Join(ErrInternal, err)
|
|
} else if rows, _ := r.RowsAffected(); rows == 0 {
|
|
return ErrNoAffect
|
|
}
|
|
|
|
return nil
|
|
}
|
|
|
|
func (db *SQLiteDB[C]) ChannelUpdate(c Channel) error {
|
|
r, err := db.sql.Exec(`
|
|
UPDATE channels
|
|
SET Language = $1
|
|
WHERE "GuildID" = $2 AND "ID" = $3
|
|
`, c.Language, c.GuildID, c.ID)
|
|
|
|
if err != nil {
|
|
return errors.Join(ErrInternal, err)
|
|
} else if rows, _ := r.RowsAffected(); rows == 0 {
|
|
return ErrNoAffect
|
|
}
|
|
|
|
return nil
|
|
}
|
|
|
|
func (db *SQLiteDB[C]) ChannelDelete(c Channel) error {
|
|
r, err := db.sql.Exec(`
|
|
DELETE FROM channels
|
|
WHERE "GuildID" = $1 AND "ID" = $2
|
|
`, c.ID, c.ID)
|
|
|
|
if err != nil {
|
|
return errors.Join(ErrInternal, err)
|
|
} else if rows, _ := r.RowsAffected(); rows == 0 {
|
|
return ErrNoAffect
|
|
}
|
|
|
|
return nil
|
|
}
|
|
|
|
func (db *SQLiteDB[C]) ChannelGroup(guildID, channelID string) (ChannelGroup, error) {
|
|
var j string
|
|
err := db.sql.QueryRow(fmt.Sprintf(`
|
|
SELECT Channels FROM channelGroups, json_each(Channels)
|
|
WHERE "GuildID" = $1 AND json_each.value='%s';
|
|
`, channelID), guildID).Scan(&j)
|
|
|
|
if errors.Is(err, sql.ErrNoRows) {
|
|
return ChannelGroup{}, errors.Join(ErrNotFound, err)
|
|
} else if err != nil {
|
|
return ChannelGroup{}, errors.Join(ErrInternal, err)
|
|
}
|
|
|
|
var ids []string
|
|
err = json.Unmarshal([]byte(j), &ids)
|
|
if err != nil {
|
|
return ChannelGroup{}, errors.Join(ErrInternal, err)
|
|
}
|
|
for i, v := range ids {
|
|
ids[i] = fmt.Sprintf("\"ID\" = %s", v)
|
|
}
|
|
|
|
cs, err := db.selectChannels(fmt.Sprintf(`
|
|
WHERE %s AND "GuildID" = $1
|
|
`, strings.Join(ids, " OR ")), guildID)
|
|
|
|
if errors.Is(err, ErrNotFound) || len(cs) != len(ids) {
|
|
return ChannelGroup{}, errors.Join(
|
|
ErrPreconditionFailed,
|
|
fmt.Errorf(
|
|
"ChannelGroup has Channels that doesn't exist in the database, group: %s",
|
|
ids,
|
|
),
|
|
err,
|
|
)
|
|
} else if err != nil {
|
|
return ChannelGroup{}, errors.Join(ErrInternal, err)
|
|
}
|
|
|
|
return cs, nil
|
|
}
|
|
|
|
func (db *SQLiteDB[C]) ChannelGroupInsert(g ChannelGroup) error {
|
|
if len(g) == 0 {
|
|
return ErrNoAffect
|
|
}
|
|
|
|
var ids []string
|
|
for _, c := range g {
|
|
ids = append(ids, c.ID)
|
|
}
|
|
slices.Sort(ids)
|
|
|
|
j, err := json.Marshal(ids)
|
|
if err != nil {
|
|
return errors.Join(ErrInternal, err)
|
|
}
|
|
|
|
r, err := db.sql.Exec(fmt.Sprintf(`
|
|
INSERT OR IGNORE INTO channelGroups (GuildID, Channels)
|
|
VALUES ($1, json('%s'))
|
|
`, string(j)), g[0].GuildID)
|
|
|
|
if err != nil {
|
|
return errors.Join(ErrInternal, err)
|
|
} else if rows, _ := r.RowsAffected(); rows == 0 {
|
|
return ErrNoAffect
|
|
}
|
|
|
|
return nil
|
|
}
|
|
|
|
func (db *SQLiteDB[C]) ChannelGroupUpdate(g ChannelGroup) error {
|
|
if len(g) != 0 {
|
|
return nil
|
|
}
|
|
|
|
var ids, idsq []string
|
|
for _, c := range g {
|
|
ids = append(ids, c.ID)
|
|
idsq = append(idsq, "json_each.value='"+c.ID+"'")
|
|
}
|
|
slices.Sort(ids)
|
|
|
|
r, err := db.sql.Exec(
|
|
fmt.Sprintf(`
|
|
UPDATE channelGroups, json_each(Channels)
|
|
SET Channels = $1
|
|
WHERE %s AND "GuildID" = $2
|
|
`, strings.Join(idsq, " OR ")),
|
|
strings.Join(ids, ","),
|
|
g[0].GuildID,
|
|
)
|
|
|
|
if err != nil {
|
|
return errors.Join(ErrInternal, err)
|
|
} else if rows, _ := r.RowsAffected(); rows == 0 {
|
|
return ErrNoAffect
|
|
}
|
|
|
|
return nil
|
|
}
|
|
|
|
func (db *SQLiteDB[C]) ChannelGroupDelete(g ChannelGroup) error {
|
|
if len(g) != 0 {
|
|
return nil
|
|
}
|
|
|
|
var ids, idsq []string
|
|
for _, c := range g {
|
|
ids = append(ids, c.ID)
|
|
idsq = append(idsq, "json_each.value='"+c.ID+"'")
|
|
}
|
|
slices.Sort(ids)
|
|
|
|
r, err := db.sql.Exec(
|
|
fmt.Sprintf(`
|
|
DELETE FROM channelGroups, json_each(Channels)
|
|
WHERE %s AND "GuildID" = $2
|
|
`, strings.Join(idsq, " OR ")),
|
|
g[0].GuildID,
|
|
)
|
|
|
|
if err != nil {
|
|
return errors.Join(ErrInternal, err)
|
|
} else if rows, _ := r.RowsAffected(); rows == 0 {
|
|
return ErrNoAffect
|
|
}
|
|
|
|
return nil
|
|
}
|
|
|
|
func (db *SQLiteDB[C]) selectChannel(query string, args ...any) (Channel, error) {
|
|
var c Channel
|
|
err := db.sql.QueryRow(fmt.Sprintf(`
|
|
SELECT GuildID, ID, Language FROM channels
|
|
%s
|
|
`, query), args...).Scan(&c.GuildID, &c.ID, &c.Language)
|
|
|
|
if errors.Is(err, sql.ErrNoRows) {
|
|
return c, errors.Join(ErrNotFound, err)
|
|
} else if err != nil {
|
|
return c, errors.Join(ErrInternal, err)
|
|
}
|
|
|
|
return c, nil
|
|
}
|
|
|
|
func (db *SQLiteDB[C]) selectChannels(query string, args ...any) ([]Channel, error) {
|
|
r, err := db.sql.Query(fmt.Sprintf(`
|
|
SELECT GuildID, ID, Language FROM channels
|
|
%s
|
|
`, query), args...)
|
|
defer r.Close()
|
|
|
|
if err != nil {
|
|
return []Channel{}, errors.Join(ErrInternal, err)
|
|
}
|
|
|
|
var cs []Channel
|
|
for r.Next() {
|
|
var c Channel
|
|
|
|
err = r.Scan(&c.GuildID, &c.ID, &c.Language)
|
|
if err != nil {
|
|
return cs, errors.Join(
|
|
ErrInternal,
|
|
fmt.Errorf("Query: %s\nArguments: %v", query, args),
|
|
err,
|
|
)
|
|
}
|
|
|
|
cs = append(cs, c)
|
|
}
|
|
|
|
if len(cs) == 0 {
|
|
return cs, errors.Join(
|
|
ErrNotFound,
|
|
fmt.Errorf("Query: %s\nArguments: %v", query, args),
|
|
)
|
|
}
|
|
return cs, err
|
|
}
|
|
|
|
func (db *SQLiteDB[C]) Guild(ID string) (Guild[C], error) {
|
|
var g struct {
|
|
ID string
|
|
Config string
|
|
}
|
|
|
|
if err := db.sql.QueryRow(`
|
|
SELECT "ID", "Config" FROM guilds
|
|
WHERE "ID" = $1
|
|
`, ID).Scan(&g.ID, &g.Config); errors.Is(err, sql.ErrNoRows) {
|
|
return Guild[C]{}, errors.Join(ErrNotFound, err)
|
|
} else if err != nil {
|
|
return Guild[C]{}, errors.Join(ErrInternal, err)
|
|
}
|
|
|
|
var c C
|
|
err := json.Unmarshal([]byte(g.Config), &c)
|
|
if err != nil {
|
|
return Guild[C]{}, errors.Join(ErrConfigParsing, err)
|
|
}
|
|
|
|
return Guild[C]{g.ID, c}, nil
|
|
}
|
|
|
|
func (db *SQLiteDB[C]) GuildInsert(g Guild[C]) error {
|
|
j, err := json.Marshal(g.Config)
|
|
if err != nil {
|
|
return errors.Join(ErrConfigParsing, err)
|
|
}
|
|
|
|
r, err := db.sql.Exec(`
|
|
INSERT OR IGNORE INTO guilds (ID, Config)
|
|
VALUES ($1, $2)
|
|
`, g.ID, string(j))
|
|
|
|
if err != nil {
|
|
return errors.Join(ErrInternal, err)
|
|
} else if rows, _ := r.RowsAffected(); rows == 0 {
|
|
return ErrNoAffect
|
|
}
|
|
|
|
return nil
|
|
}
|
|
|
|
func (db *SQLiteDB[C]) GuildUpdate(g Guild[C]) error {
|
|
j, err := json.Marshal(g.Config)
|
|
if err != nil {
|
|
return errors.Join(ErrConfigParsing, err)
|
|
}
|
|
|
|
r, err := db.sql.Exec(fmt.Sprintf(`
|
|
UPDATE guilds
|
|
SET "Config" = '%s'
|
|
WHERE "ID" = '%s'
|
|
`, string(j), g.ID))
|
|
|
|
if err != nil {
|
|
return errors.Join(ErrInternal, err)
|
|
} else if rows, _ := r.RowsAffected(); rows == 0 {
|
|
return ErrNoAffect
|
|
}
|
|
|
|
return nil
|
|
}
|
|
|
|
func (db *SQLiteDB[C]) GuildDelete(g Guild[C]) error {
|
|
r, err := db.sql.Exec(`
|
|
DELETE FROM guilds
|
|
WHERE "ID" = $1
|
|
`, g.ID)
|
|
|
|
if err != nil {
|
|
return errors.Join(ErrInternal, err)
|
|
} else if rows, _ := r.RowsAffected(); rows == 0 {
|
|
return ErrNoAffect
|
|
}
|
|
|
|
return nil
|
|
}
|