From 7ce23c8e6d6ac7fe5708499ed9d7d7c15fcb2674 Mon Sep 17 00:00:00 2001 From: Ben Date: Mon, 6 Oct 2025 09:00:34 +0100 Subject: [PATCH 1/2] add support for Form Input API fields Signed-off-by: Ben --- database.go | 10 +- forminputapiconfig.go | 277 ++++++++++++++++++++++++++++++++++++ forminputapiheaders.go | 316 +++++++++++++++++++++++++++++++++++++++++ panels.go | 101 +++++++++++++ 4 files changed, 702 insertions(+), 2 deletions(-) create mode 100644 forminputapiconfig.go create mode 100644 forminputapiheaders.go diff --git a/database.go b/database.go index b405022..54207fb 100644 --- a/database.go +++ b/database.go @@ -46,6 +46,8 @@ type Database struct { FormInput *FormInputTable FormInputOption *FormInputOptionTable Forms *FormsTable + FormInputApiConfig *FormInputApiConfigTable + FormInputApiHeaders *FormInputApiHeaderTable GlobalBlacklist *GlobalBlacklist GuildLeaveTime *GuildLeaveTime GuildMetadata *GuildMetadataTable @@ -137,6 +139,8 @@ func NewDatabase(pool *pgxpool.Pool) *Database { FirstResponseTime: newFirstResponseTime(pool), FormInput: newFormInputTable(pool), Forms: newFormsTable(pool), + FormInputApiConfig: newFormInputApiConfigTable(pool), + FormInputApiHeaders: newFormInputApiHeaderTable(pool), FormInputOption: newFormInputOptionTable(pool), GlobalBlacklist: newGlobalBlacklist(pool), GuildLeaveTime: newGuildLeaveTime(pool), @@ -248,8 +252,10 @@ func (d *Database) CreateTables(ctx context.Context, pool *pgxpool.Pool) { d.SubscriptionSkus, // depends on skus d.FeedbackEnabled, d.Forms, - d.FormInput, // depends on forms - d.FormInputOption, // depends on form inputs + d.FormInput, // depends on forms + d.FormInputOption, // depends on form inputs + d.FormInputApiConfig, // depends on form inputs + d.FormInputApiHeader, // depends on form input api config d.GlobalBlacklist, d.GuildLeaveTime, d.GuildMetadata, diff --git a/forminputapiconfig.go b/forminputapiconfig.go new file mode 100644 index 0000000..d8b2296 --- /dev/null +++ b/forminputapiconfig.go @@ -0,0 +1,277 @@ +package database + +import ( + "context" + "errors" + "time" + + "github.com/jackc/pgx/v4" + "github.com/jackc/pgx/v4/pgxpool" +) + +type FormInputApiConfig struct { + Id int `json:"id"` + FormInputId int `json:"form_input_id"` + EndpointUrl string `json:"endpoint_url"` + Method string `json:"method"` + CacheDurationSeconds *int `json:"cache_duration_seconds,omitempty"` + CreatedAt time.Time `json:"created_at"` + UpdatedAt time.Time `json:"updated_at"` +} + +type FormInputApiConfigTable struct { + *pgxpool.Pool +} + +func newFormInputApiConfigTable(db *pgxpool.Pool) *FormInputApiConfigTable { + return &FormInputApiConfigTable{ + db, + } +} + +func (f FormInputApiConfigTable) Schema() string { + return ` + CREATE TABLE IF NOT EXISTS form_input_api_config( + "id" SERIAL NOT NULL UNIQUE, + "form_input_id" INT NOT NULL UNIQUE, + "endpoint_url" VARCHAR(500) NOT NULL, + "method" VARCHAR(10) NOT NULL DEFAULT 'GET', + "cache_duration_seconds" INT DEFAULT 300, + "created_at" TIMESTAMP DEFAULT CURRENT_TIMESTAMP, + "updated_at" TIMESTAMP DEFAULT CURRENT_TIMESTAMP, + FOREIGN KEY("form_input_id") REFERENCES form_input("id") ON DELETE CASCADE, + CHECK(method IN ('GET', 'POST', 'PUT', 'PATCH', 'DELETE')), + CHECK(cache_duration_seconds >= 0), + PRIMARY KEY("id") + ); + CREATE INDEX IF NOT EXISTS form_input_api_config_form_input_id ON form_input_api_config("form_input_id"); + ` +} + +func (f *FormInputApiConfigTable) Get(ctx context.Context, formInputId int) (config FormInputApiConfig, ok bool, e error) { + query := ` + SELECT "id", "form_input_id", "endpoint_url", "method", "cache_duration_seconds", "created_at", "updated_at" + FROM form_input_api_config + WHERE "form_input_id" = $1;` + + err := f.QueryRow(ctx, query, formInputId).Scan( + &config.Id, + &config.FormInputId, + &config.EndpointUrl, + &config.Method, + &config.CacheDurationSeconds, + &config.CreatedAt, + &config.UpdatedAt, + ) + + if err != nil { + if errors.Is(err, pgx.ErrNoRows) { + return FormInputApiConfig{}, false, nil + } else { + return FormInputApiConfig{}, false, err + } + } + + return config, true, nil +} + +func (f *FormInputApiConfigTable) GetById(ctx context.Context, id int) (config FormInputApiConfig, ok bool, e error) { + query := ` + SELECT "id", "form_input_id", "endpoint_url", "method", "cache_duration_seconds", "created_at", "updated_at" + FROM form_input_api_config + WHERE "id" = $1;` + + err := f.QueryRow(ctx, query, id).Scan( + &config.Id, + &config.FormInputId, + &config.EndpointUrl, + &config.Method, + &config.CacheDurationSeconds, + &config.CreatedAt, + &config.UpdatedAt, + ) + + if err != nil { + if errors.Is(err, pgx.ErrNoRows) { + return FormInputApiConfig{}, false, nil + } else { + return FormInputApiConfig{}, false, err + } + } + + return config, true, nil +} + +func (f *FormInputApiConfigTable) GetByFormId(ctx context.Context, formId int) ([]FormInputApiConfig, error) { + query := ` + SELECT c."id", c."form_input_id", c."endpoint_url", c."method", c."cache_duration_seconds", c."created_at", c."updated_at" + FROM form_input_api_config c + INNER JOIN form_input i ON c."form_input_id" = i."id" + WHERE i."form_id" = $1 + ORDER BY i."position" ASC;` + + rows, err := f.Query(ctx, query, formId) + if err != nil { + return nil, err + } + defer rows.Close() + + var configs []FormInputApiConfig + for rows.Next() { + var config FormInputApiConfig + if err := rows.Scan( + &config.Id, + &config.FormInputId, + &config.EndpointUrl, + &config.Method, + &config.CacheDurationSeconds, + &config.CreatedAt, + &config.UpdatedAt, + ); err != nil { + return nil, err + } + configs = append(configs, config) + } + + return configs, rows.Err() +} + +func (f *FormInputApiConfigTable) GetByFormInputId(ctx context.Context, formInputId int) (config FormInputApiConfig, ok bool, e error) { + query := ` + SELECT "id", "form_input_id", "endpoint_url", "method", "cache_duration_seconds", "created_at", "updated_at" + FROM form_input_api_config + WHERE "form_input_id" = $1;` + + err := f.QueryRow(ctx, query, formInputId).Scan( + &config.Id, + &config.FormInputId, + &config.EndpointUrl, + &config.Method, + &config.CacheDurationSeconds, + &config.CreatedAt, + &config.UpdatedAt, + ) + + if err != nil { + if errors.Is(err, pgx.ErrNoRows) { + return FormInputApiConfig{}, false, nil + } else { + return FormInputApiConfig{}, false, err + } + } + + return config, true, nil +} + +func (f *FormInputApiConfigTable) GetAllByGuild(ctx context.Context, guildId uint64) (map[int]FormInputApiConfig, error) { + + query := ` + SELECT c."id", c."form_input_id", c."endpoint_url", c."method", c."cache_duration_seconds", c."created_at", c."updated_at" + FROM form_input_api_config c + INNER JOIN form_input i ON c."form_input_id" = i."id" + INNER JOIN forms f ON i."form_id" = f."form_id" + WHERE f."guild_id" = $1;` + + rows, err := f.Query(ctx, query, guildId) + if err != nil { + return nil, err + } + defer rows.Close() + + configs := make(map[int]FormInputApiConfig) + for rows.Next() { + var config FormInputApiConfig + if err := rows.Scan( + &config.Id, + &config.FormInputId, + &config.EndpointUrl, + &config.Method, + &config.CacheDurationSeconds, + &config.CreatedAt, + &config.UpdatedAt, + ); err != nil { + return nil, err + } + configs[config.FormInputId] = config + } + + return configs, rows.Err() +} + +func (f *FormInputApiConfigTable) Create(ctx context.Context, formInputId int, endpointUrl string, method string, cacheDurationSeconds *int) (int, error) { + query := ` + INSERT INTO form_input_api_config("form_input_id", "endpoint_url", "method", "cache_duration_seconds") + VALUES($1, $2, $3, $4) + RETURNING "id";` + + var id int + if err := f.QueryRow(ctx, query, formInputId, endpointUrl, method, cacheDurationSeconds).Scan(&id); err != nil { + return 0, err + } + + return id, nil +} + +func (f *FormInputApiConfigTable) CreateTx(ctx context.Context, tx pgx.Tx, formInputId int, endpointUrl string, method string, cacheDurationSeconds *int) (int, error) { + query := ` + INSERT INTO form_input_api_config("form_input_id", "endpoint_url", "method", "cache_duration_seconds") + VALUES($1, $2, $3, $4) + RETURNING "id";` + + var id int + if err := tx.QueryRow(ctx, query, formInputId, endpointUrl, method, cacheDurationSeconds).Scan(&id); err != nil { + return 0, err + } + + return id, nil +} + +func (f *FormInputApiConfigTable) Update(ctx context.Context, id int, endpointUrl string, method string, cacheDurationSeconds *int) error { + query := ` + UPDATE form_input_api_config + SET "endpoint_url" = $2, + "method" = $3, + "cache_duration_seconds" = $4, + "updated_at" = CURRENT_TIMESTAMP + WHERE "id" = $1;` + + _, err := f.Exec(ctx, query, id, endpointUrl, method, cacheDurationSeconds) + return err +} + +func (f *FormInputApiConfigTable) UpdateTx(ctx context.Context, tx pgx.Tx, id int, endpointUrl string, method string, cacheDurationSeconds *int) error { + query := ` + UPDATE form_input_api_config + SET "endpoint_url" = $2, + "method" = $3, + "cache_duration_seconds" = $4, + "updated_at" = CURRENT_TIMESTAMP + WHERE "id" = $1;` + + _, err := tx.Exec(ctx, query, id, endpointUrl, method, cacheDurationSeconds) + return err +} + +func (f *FormInputApiConfigTable) Delete(ctx context.Context, id int) error { + query := `DELETE FROM form_input_api_config WHERE "id" = $1;` + _, err := f.Exec(ctx, query, id) + return err +} + +func (f *FormInputApiConfigTable) DeleteTx(ctx context.Context, tx pgx.Tx, id int) error { + query := `DELETE FROM form_input_api_config WHERE "id" = $1;` + _, err := tx.Exec(ctx, query, id) + return err +} + +func (f *FormInputApiConfigTable) DeleteByFormInput(ctx context.Context, formInputId int) error { + query := `DELETE FROM form_input_api_config WHERE "form_input_id" = $1;` + _, err := f.Exec(ctx, query, formInputId) + return err +} + +func (f *FormInputApiConfigTable) DeleteByFormInputTx(ctx context.Context, tx pgx.Tx, formInputId int) error { + query := `DELETE FROM form_input_api_config WHERE "form_input_id" = $1;` + _, err := tx.Exec(ctx, query, formInputId) + return err +} diff --git a/forminputapiheaders.go b/forminputapiheaders.go new file mode 100644 index 0000000..3357e9e --- /dev/null +++ b/forminputapiheaders.go @@ -0,0 +1,316 @@ +package database + +import ( + "context" + "errors" + + "github.com/jackc/pgx/v4" + "github.com/jackc/pgx/v4/pgxpool" +) + +type FormInputApiHeader struct { + Id int `json:"id"` + ApiConfigId int `json:"api_config_id"` + HeaderName string `json:"header_name"` + HeaderValue string `json:"header_value"` + IsSecret bool `json:"is_secret"` +} + +type FormInputApiHeaderTable struct { + *pgxpool.Pool +} + +func newFormInputApiHeaderTable(db *pgxpool.Pool) *FormInputApiHeaderTable { + return &FormInputApiHeaderTable{ + db, + } +} + +func (f FormInputApiHeaderTable) Schema() string { + return ` + CREATE TABLE IF NOT EXISTS form_input_api_headers( + "id" SERIAL NOT NULL UNIQUE, + "api_config_id" INT NOT NULL, + "header_name" VARCHAR(255) NOT NULL, + "header_value" TEXT NOT NULL, + "is_secret" BOOLEAN DEFAULT FALSE, + FOREIGN KEY("api_config_id") REFERENCES form_input_api_config("id") ON DELETE CASCADE, + UNIQUE("api_config_id", "header_name"), + PRIMARY KEY("id") + ); + CREATE INDEX IF NOT EXISTS form_input_api_headers_api_config_id ON form_input_api_headers("api_config_id"); + ` +} + +func (f *FormInputApiHeaderTable) Get(ctx context.Context, id int) (header FormInputApiHeader, ok bool, e error) { + query := ` + SELECT "id", "api_config_id", "header_name", "header_value", "is_secret" + FROM form_input_api_headers + WHERE "id" = $1;` + + err := f.QueryRow(ctx, query, id).Scan( + &header.Id, + &header.ApiConfigId, + &header.HeaderName, + &header.HeaderValue, + &header.IsSecret, + ) + + if err != nil { + if errors.Is(err, pgx.ErrNoRows) { + return FormInputApiHeader{}, false, nil + } else { + return FormInputApiHeader{}, false, err + } + } + + return header, true, nil +} + +func (f *FormInputApiHeaderTable) GetByApiConfig(ctx context.Context, apiConfigId int) ([]FormInputApiHeader, error) { + query := ` + SELECT "id", "api_config_id", "header_name", "header_value", "is_secret" + FROM form_input_api_headers + WHERE "api_config_id" = $1 + ORDER BY "header_name" ASC;` + + rows, err := f.Query(ctx, query, apiConfigId) + if err != nil { + return nil, err + } + defer rows.Close() + + var headers []FormInputApiHeader + for rows.Next() { + var header FormInputApiHeader + if err := rows.Scan( + &header.Id, + &header.ApiConfigId, + &header.HeaderName, + &header.HeaderValue, + &header.IsSecret, + ); err != nil { + return nil, err + } + headers = append(headers, header) + } + + return headers, rows.Err() +} + +func (f *FormInputApiHeaderTable) GetByFormInput(ctx context.Context, formInputId int) ([]FormInputApiHeader, error) { + query := ` + SELECT h."id", h."api_config_id", h."header_name", h."header_value", h."is_secret" + FROM form_input_api_headers h + INNER JOIN form_input_api_config c ON h."api_config_id" = c."id" + WHERE c."form_input_id" = $1 + ORDER BY h."header_name" ASC;` + + rows, err := f.Query(ctx, query, formInputId) + if err != nil { + return nil, err + } + defer rows.Close() + + var headers []FormInputApiHeader + for rows.Next() { + var header FormInputApiHeader + if err := rows.Scan( + &header.Id, + &header.ApiConfigId, + &header.HeaderName, + &header.HeaderValue, + &header.IsSecret, + ); err != nil { + return nil, err + } + headers = append(headers, header) + } + + return headers, rows.Err() +} + +func (f *FormInputApiHeaderTable) GetHeadersMap(ctx context.Context, apiConfigId int) (map[string]string, error) { + headers, err := f.GetByApiConfig(ctx, apiConfigId) + if err != nil { + return nil, err + } + + headerMap := make(map[string]string) + for _, header := range headers { + headerMap[header.HeaderName] = header.HeaderValue + } + + return headerMap, nil +} + +func (f *FormInputApiHeaderTable) GetAllByGuild(ctx context.Context, guildId uint64) (map[int][]FormInputApiHeader, error) { + query := ` + SELECT h."id", h."api_config_id", h."header_name", h."header_value", h."is_secret" + FROM form_input_api_headers h + INNER JOIN form_input_api_config c ON h."api_config_id" = c."id" + INNER JOIN form_input i ON c."form_input_id" = i."id" + INNER JOIN forms f ON i."form_id" = f."form_id" + WHERE f."guild_id" = $1 + ORDER BY h."api_config_id", h."header_name" ASC;` + + rows, err := f.Query(ctx, query, guildId) + if err != nil { + return nil, err + } + defer rows.Close() + + headers := make(map[int][]FormInputApiHeader) + for rows.Next() { + var header FormInputApiHeader + if err := rows.Scan( + &header.Id, + &header.ApiConfigId, + &header.HeaderName, + &header.HeaderValue, + &header.IsSecret, + ); err != nil { + return nil, err + } + + if _, ok := headers[header.ApiConfigId]; !ok { + headers[header.ApiConfigId] = make([]FormInputApiHeader, 0) + } + headers[header.ApiConfigId] = append(headers[header.ApiConfigId], header) + } + + return headers, rows.Err() +} + +func (f *FormInputApiHeaderTable) Create(ctx context.Context, apiConfigId int, headerName string, headerValue string, isSecret bool) (int, error) { + query := ` + INSERT INTO form_input_api_headers("api_config_id", "header_name", "header_value", "is_secret") + VALUES($1, $2, $3, $4) + RETURNING "id";` + + var id int + if err := f.QueryRow(ctx, query, apiConfigId, headerName, headerValue, isSecret).Scan(&id); err != nil { + return 0, err + } + + return id, nil +} + +func (f *FormInputApiHeaderTable) CreateTx(ctx context.Context, tx pgx.Tx, apiConfigId int, headerName string, headerValue string, isSecret bool) (int, error) { + query := ` + INSERT INTO form_input_api_headers("api_config_id", "header_name", "header_value", "is_secret") + VALUES($1, $2, $3, $4) + RETURNING "id";` + + var id int + if err := tx.QueryRow(ctx, query, apiConfigId, headerName, headerValue, isSecret).Scan(&id); err != nil { + return 0, err + } + + return id, nil +} + +func (f *FormInputApiHeaderTable) BulkCreate(ctx context.Context, apiConfigId int, headers map[string]string, secretHeaders map[string]bool) error { + tx, err := f.Begin(ctx) + if err != nil { + return err + } + defer tx.Rollback(ctx) + + for name, value := range headers { + isSecret := false + if secretHeaders != nil { + isSecret = secretHeaders[name] + } + + if _, err := f.CreateTx(ctx, tx, apiConfigId, name, value, isSecret); err != nil { + return err + } + } + + return tx.Commit(ctx) +} + +func (f *FormInputApiHeaderTable) Update(ctx context.Context, id int, headerValue string, isSecret bool) error { + query := ` + UPDATE form_input_api_headers + SET "header_value" = $2, + "is_secret" = $3 + WHERE "id" = $1;` + + _, err := f.Exec(ctx, query, id, headerValue, isSecret) + return err +} + +func (f *FormInputApiHeaderTable) UpdateTx(ctx context.Context, tx pgx.Tx, id int, headerValue string, isSecret bool) error { + query := ` + UPDATE form_input_api_headers + SET "header_value" = $2, + "is_secret" = $3 + WHERE "id" = $1;` + + _, err := tx.Exec(ctx, query, id, headerValue, isSecret) + return err +} + +func (f *FormInputApiHeaderTable) Upsert(ctx context.Context, apiConfigId int, headerName string, headerValue string, isSecret bool) error { + query := ` + INSERT INTO form_input_api_headers("api_config_id", "header_name", "header_value", "is_secret") + VALUES($1, $2, $3, $4) + ON CONFLICT("api_config_id", "header_name") + DO UPDATE SET + "header_value" = EXCLUDED.header_value, + "is_secret" = EXCLUDED.is_secret;` + + _, err := f.Exec(ctx, query, apiConfigId, headerName, headerValue, isSecret) + return err +} + +func (f *FormInputApiHeaderTable) UpsertTx(ctx context.Context, tx pgx.Tx, apiConfigId int, headerName string, headerValue string, isSecret bool) error { + query := ` + INSERT INTO form_input_api_headers("api_config_id", "header_name", "header_value", "is_secret") + VALUES($1, $2, $3, $4) + ON CONFLICT("api_config_id", "header_name") + DO UPDATE SET + "header_value" = EXCLUDED.header_value, + "is_secret" = EXCLUDED.is_secret;` + + _, err := tx.Exec(ctx, query, apiConfigId, headerName, headerValue, isSecret) + return err +} + +func (f *FormInputApiHeaderTable) Delete(ctx context.Context, id int) error { + query := `DELETE FROM form_input_api_headers WHERE "id" = $1;` + _, err := f.Exec(ctx, query, id) + return err +} + +func (f *FormInputApiHeaderTable) DeleteTx(ctx context.Context, tx pgx.Tx, id int) error { + query := `DELETE FROM form_input_api_headers WHERE "id" = $1;` + _, err := tx.Exec(ctx, query, id) + return err +} + +func (f *FormInputApiHeaderTable) DeleteByApiConfig(ctx context.Context, apiConfigId int) error { + query := `DELETE FROM form_input_api_headers WHERE "api_config_id" = $1;` + _, err := f.Exec(ctx, query, apiConfigId) + return err +} + +func (f *FormInputApiHeaderTable) DeleteByApiConfigTx(ctx context.Context, tx pgx.Tx, apiConfigId int) error { + query := `DELETE FROM form_input_api_headers WHERE "api_config_id" = $1;` + _, err := tx.Exec(ctx, query, apiConfigId) + return err +} + +func (f *FormInputApiHeaderTable) DeleteByName(ctx context.Context, apiConfigId int, headerName string) error { + query := `DELETE FROM form_input_api_headers WHERE "api_config_id" = $1 AND "header_name" = $2;` + _, err := f.Exec(ctx, query, apiConfigId, headerName) + return err +} + +func (f *FormInputApiHeaderTable) DeleteByNameTx(ctx context.Context, tx pgx.Tx, apiConfigId int, headerName string) error { + query := `DELETE FROM form_input_api_headers WHERE "api_config_id" = $1 AND "header_name" = $2;` + _, err := tx.Exec(ctx, query, apiConfigId, headerName) + return err +} diff --git a/panels.go b/panels.go index 3ba8e06..2e8c1b4 100644 --- a/panels.go +++ b/panels.go @@ -173,6 +173,107 @@ WHERE "panel_id" = $1; return } +func (p *PanelTable) GetByIdWithWelcomeMessage(ctx context.Context, guildId uint64, panelId int) (panel *PanelWithWelcomeMessage, e error) { + query := ` +SELECT + panels.panel_id, + panels.message_id, + panels.channel_id, + panels.guild_id, + panels.title, + panels.content, + panels.colour, + panels.target_category, + panels.emoji_name, + panels.emoji_id, + panels.welcome_message, + panels.default_team, + panels.custom_id, + panels.image_url, + panels.thumbnail_url, + panels.button_style, + panels.button_label, + panels.form_id, + panels.naming_scheme, + panels.force_disabled, + panels.disabled, + panels.exit_survey_form_id, + panels.pending_category, + panels.delete_mentions, + embeds.id, + embeds.guild_id, + embeds.title, + embeds.description, + embeds.url, + embeds.colour, + embeds.author_name, + embeds.author_icon_url, + embeds.author_url, + embeds.image_url, + embeds.thumbnail_url, + embeds.footer_text, + embeds.footer_icon_url, + embeds.timestamp +FROM panels +LEFT JOIN embeds +ON panels.welcome_message = embeds.id +WHERE panels.guild_id = $1 +AND panels.panel_id = $2;` + + rows, err := p.Query(ctx, query, guildId, panelId) + defer rows.Close() + if err != nil { + return nil, err + } + + for rows.Next() { + var pan Panel + var embed CustomEmbed + + // Can't scan missing values into non-nullable fields + var embedId *int + var embedGuildId *uint64 + var embedColour *uint32 + + err := rows.Scan(append(pan.fieldPtrs(), + &embedId, + &embedGuildId, + &embed.Title, + &embed.Description, + &embed.Url, + &embedColour, + &embed.AuthorName, + &embed.AuthorIconUrl, + &embed.AuthorUrl, + &embed.ImageUrl, + &embed.ThumbnailUrl, + &embed.FooterText, + &embed.FooterIconUrl, + &embed.Timestamp, + )...) + + if err != nil { + return nil, err + } + + var embedPtr *CustomEmbed + if embedId != nil { + embed.Id = *embedId + embed.GuildId = *embedGuildId + embed.Colour = *embedColour + + embedPtr = &embed + } + + panel = &PanelWithWelcomeMessage{ + Panel: pan, + WelcomeMessage: embedPtr, + } + } + + return +} + func (p *PanelTable) GetByCustomId(ctx context.Context, guildId uint64, customId string) (panel Panel, ok bool, e error) { query := ` SELECT From c6f6db92db8bc730197344fd9d0e9c8d01fdf2c6 Mon Sep 17 00:00:00 2001 From: Ben Date: Sun, 26 Oct 2025 18:32:09 +0000 Subject: [PATCH 2/2] fix Signed-off-by: Ben --- database.go | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/database.go b/database.go index 54207fb..51e923a 100644 --- a/database.go +++ b/database.go @@ -252,10 +252,10 @@ func (d *Database) CreateTables(ctx context.Context, pool *pgxpool.Pool) { d.SubscriptionSkus, // depends on skus d.FeedbackEnabled, d.Forms, - d.FormInput, // depends on forms - d.FormInputOption, // depends on form inputs - d.FormInputApiConfig, // depends on form inputs - d.FormInputApiHeader, // depends on form input api config + d.FormInput, // depends on forms + d.FormInputOption, // depends on form inputs + d.FormInputApiConfig, // depends on form inputs + d.FormInputApiHeaders, // depends on form input api config d.GlobalBlacklist, d.GuildLeaveTime, d.GuildMetadata,