feat: use postgres

This commit is contained in:
Ilia Denisov
2026-04-26 20:34:39 +02:00
committed by GitHub
parent 48b0056b49
commit fe829285a6
365 changed files with 29223 additions and 24049 deletions
@@ -0,0 +1,176 @@
package mailstore
import (
"encoding/json"
"fmt"
"galaxy/mail/internal/domain/common"
"galaxy/mail/internal/service/acceptgenericdelivery"
)
// attachmentRow stores the on-disk JSONB encoding of one
// `common.AttachmentMetadata` entry. The encoding is intentionally explicit
// (named JSON keys) so the on-disk shape stays decoupled from accidental Go
// struct renames.
type attachmentRow struct {
Filename string `json:"filename"`
ContentType string `json:"content_type"`
SizeBytes int64 `json:"size_bytes"`
}
// marshalAttachments returns the JSONB bytes for the attachments column. A
// nil/empty slice round-trips as `[]` to keep the column NOT NULL across
// equality tests.
func marshalAttachments(attachments []common.AttachmentMetadata) ([]byte, error) {
rows := make([]attachmentRow, 0, len(attachments))
for _, attachment := range attachments {
rows = append(rows, attachmentRow{
Filename: attachment.Filename,
ContentType: attachment.ContentType,
SizeBytes: attachment.SizeBytes,
})
}
payload, err := json.Marshal(rows)
if err != nil {
return nil, fmt.Errorf("marshal attachments: %w", err)
}
return payload, nil
}
// unmarshalAttachments decodes the attachments JSONB column into a
// domain-friendly slice. nil/empty payloads decode to a nil slice.
func unmarshalAttachments(payload []byte) ([]common.AttachmentMetadata, error) {
if len(payload) == 0 {
return nil, nil
}
var rows []attachmentRow
if err := json.Unmarshal(payload, &rows); err != nil {
return nil, fmt.Errorf("unmarshal attachments: %w", err)
}
if len(rows) == 0 {
return nil, nil
}
out := make([]common.AttachmentMetadata, 0, len(rows))
for _, row := range rows {
out = append(out, common.AttachmentMetadata{
Filename: row.Filename,
ContentType: row.ContentType,
SizeBytes: row.SizeBytes,
})
}
return out, nil
}
// marshalTemplateVariables returns the JSONB bytes for the template_variables
// column. nil maps round-trip as SQL NULL.
func marshalTemplateVariables(variables map[string]any) ([]byte, error) {
if variables == nil {
return nil, nil
}
payload, err := json.Marshal(variables)
if err != nil {
return nil, fmt.Errorf("marshal template variables: %w", err)
}
return payload, nil
}
// unmarshalTemplateVariables decodes the template_variables JSONB column.
// SQL NULL payloads decode to a nil map.
func unmarshalTemplateVariables(payload []byte) (map[string]any, error) {
if len(payload) == 0 {
return nil, nil
}
var variables map[string]any
if err := json.Unmarshal(payload, &variables); err != nil {
return nil, fmt.Errorf("unmarshal template variables: %w", err)
}
return variables, nil
}
// payloadAttachmentRow stores the on-disk JSONB encoding of one
// `acceptgenericdelivery.AttachmentPayload`. The base64 body stays inline so
// the entire payload bundle round-trips as one JSONB value.
type payloadAttachmentRow struct {
Filename string `json:"filename"`
ContentType string `json:"content_type"`
ContentBase64 string `json:"content_base64"`
SizeBytes int64 `json:"size_bytes"`
}
// payloadRow stores the on-disk JSONB encoding of one
// `acceptgenericdelivery.DeliveryPayload`. delivery_id is intentionally
// excluded — the row is keyed by it via the `delivery_payloads` PRIMARY KEY.
type payloadRow struct {
Attachments []payloadAttachmentRow `json:"attachments"`
}
// marshalDeliveryPayload returns the JSONB bytes for the delivery_payloads
// row.
func marshalDeliveryPayload(payload acceptgenericdelivery.DeliveryPayload) ([]byte, error) {
rows := make([]payloadAttachmentRow, 0, len(payload.Attachments))
for _, attachment := range payload.Attachments {
rows = append(rows, payloadAttachmentRow{
Filename: attachment.Filename,
ContentType: attachment.ContentType,
ContentBase64: attachment.ContentBase64,
SizeBytes: attachment.SizeBytes,
})
}
encoded, err := json.Marshal(payloadRow{Attachments: rows})
if err != nil {
return nil, fmt.Errorf("marshal delivery payload: %w", err)
}
return encoded, nil
}
// unmarshalDeliveryPayload decodes the delivery_payloads row into a
// domain-friendly DeliveryPayload using deliveryID as the owning identifier.
func unmarshalDeliveryPayload(deliveryID common.DeliveryID, encoded []byte) (acceptgenericdelivery.DeliveryPayload, error) {
if len(encoded) == 0 {
return acceptgenericdelivery.DeliveryPayload{}, fmt.Errorf("unmarshal delivery payload: empty")
}
var row payloadRow
if err := json.Unmarshal(encoded, &row); err != nil {
return acceptgenericdelivery.DeliveryPayload{}, fmt.Errorf("unmarshal delivery payload: %w", err)
}
out := acceptgenericdelivery.DeliveryPayload{DeliveryID: deliveryID}
if len(row.Attachments) == 0 {
return out, nil
}
out.Attachments = make([]acceptgenericdelivery.AttachmentPayload, 0, len(row.Attachments))
for _, attachment := range row.Attachments {
out.Attachments = append(out.Attachments, acceptgenericdelivery.AttachmentPayload{
Filename: attachment.Filename,
ContentType: attachment.ContentType,
ContentBase64: attachment.ContentBase64,
SizeBytes: attachment.SizeBytes,
})
}
return out, nil
}
// marshalRawFields returns the JSONB bytes for the malformed_commands.raw_fields
// column. The map is serialised verbatim so future operator queries can match
// arbitrary keys.
func marshalRawFields(fields map[string]any) ([]byte, error) {
if fields == nil {
fields = map[string]any{}
}
payload, err := json.Marshal(fields)
if err != nil {
return nil, fmt.Errorf("marshal raw fields: %w", err)
}
return payload, nil
}
// unmarshalRawFields decodes the malformed_commands.raw_fields column.
func unmarshalRawFields(payload []byte) (map[string]any, error) {
out := map[string]any{}
if len(payload) == 0 {
return out, nil
}
if err := json.Unmarshal(payload, &out); err != nil {
return nil, fmt.Errorf("unmarshal raw fields: %w", err)
}
return out, nil
}