added media fixes, search, push updates, pwa, metrics. preparing release 1.0
CI / Go (build + vet + test) (push) Canceled after 0s
CI / Web (build + lint) (push) Canceled after 0s

This commit is contained in:
vantablackTea
2026-09-27 23:26:15 +03:00
parent 855a62755c
commit 6fc6d977bf
59 changed files with 6823 additions and 113 deletions
+26
View File
@@ -7,6 +7,7 @@ package main
import (
"context"
"errors"
"log/slog"
"net/http"
"os"
"os/signal"
@@ -17,6 +18,8 @@ import (
"messenger/internal/media"
"messenger/internal/platform/config"
"messenger/internal/platform/logger"
"messenger/internal/platform/metrics"
"messenger/internal/platform/version"
"messenger/internal/realtime"
"messenger/internal/server"
"messenger/internal/store"
@@ -33,6 +36,9 @@ func main() {
os.Exit(1)
}
log.Info("starting messenger", "version", version.Version)
metrics.Init(version.Version)
ctx, stop := signal.NotifyContext(context.Background(), os.Interrupt, syscall.SIGTERM)
defer stop()
@@ -59,6 +65,8 @@ func main() {
os.Exit(1)
}
go cleanupLoop(ctx, st, log)
httpServer := server.New(cfg, log, st, tm, hub, mediaStore)
// Run the server in a goroutine so that shutdown can be triggered
@@ -88,3 +96,21 @@ func main() {
}
log.Info("server stopped cleanly")
}
// cleanupLoop periodically purges expired sessions and revocations.
func cleanupLoop(ctx context.Context, st *store.Store, log *slog.Logger) {
ticker := time.NewTicker(time.Hour)
defer ticker.Stop()
for {
select {
case <-ctx.Done():
return
case <-ticker.C:
if n, err := st.CleanupSessions(ctx); err != nil {
log.Error("session cleanup failed", "error", err)
} else if n > 0 {
log.Info("cleaned up sessions", "count", n)
}
}
}
}
+19
View File
@@ -0,0 +1,19 @@
// Command keygen prints fresh VAPID keys for Web Push configuration.
package main
import (
"fmt"
"github.com/SherClockHolmes/webpush-go"
)
func main() {
private, public, err := webpush.GenerateVAPIDKeys()
if err != nil {
fmt.Println("failed to generate keys:", err)
return
}
fmt.Println("MESSENGER_VAPID_PUBLIC_KEY=" + public)
fmt.Println("MESSENGER_VAPID_PRIVATE_KEY=" + private)
fmt.Println("MESSENGER_VAPID_SUBJECT=mailto:you@example.com")
}
+118
View File
@@ -0,0 +1,118 @@
// Command seed creates demo users, a family group and a few messages.
// It is idempotent: existing usernames are left untouched.
package main
import (
"context"
"fmt"
"os"
"messenger/internal/auth"
"messenger/internal/platform/config"
"messenger/internal/platform/logger"
"messenger/internal/store"
"github.com/jackc/pgx/v5/pgxpool"
)
type seedUser struct {
username string
displayName string
password string
}
var demoUsers = []seedUser{
{"papa", "Папа", "password123"},
{"mama", "Мама", "password123"},
{"syn", "Сын", "password123"},
{"doch", "Дочь", "password123"},
}
func main() {
log := logger.New()
cfg, err := config.Load()
if err != nil {
log.Error("invalid configuration", "error", err)
os.Exit(1)
}
ctx := context.Background()
pool, err := pgxpool.New(ctx, cfg.DatabaseURL)
if err != nil {
log.Error("failed to connect", "error", err)
os.Exit(1)
}
defer pool.Close()
st := store.New(pool)
if err := st.Migrate(ctx); err != nil {
log.Error("migration failed", "error", err)
os.Exit(1)
}
created, err := ensureUsers(ctx, st)
if err != nil {
log.Error("seed users failed", "error", err)
os.Exit(1)
}
fmt.Printf("users created: %d\n", created)
group, err := ensureGroup(ctx, st)
if err != nil {
log.Error("seed group failed", "error", err)
os.Exit(1)
}
fmt.Printf("group ready: %s (id=%d)\n", group.Name, group.ID)
}
func ensureUsers(ctx context.Context, st *store.Store) (int, error) {
created := 0
for _, u := range demoUsers {
if _, err := st.GetUserByUsername(ctx, u.username); err == nil {
continue
}
hash, err := auth.HashPassword(u.password)
if err != nil {
return created, err
}
if _, err := st.CreateUser(ctx, u.username, u.displayName, hash); err != nil {
return created, err
}
created++
}
return created, nil
}
func ensureGroup(ctx context.Context, st *store.Store) (*store.Conversation, error) {
users := make([]int64, 0, len(demoUsers))
var ownerID int64
for _, u := range demoUsers {
user, err := st.GetUserByUsername(ctx, u.username)
if err != nil {
return nil, fmt.Errorf("user %s missing: %w", u.username, err)
}
if ownerID == 0 {
ownerID = user.ID
}
users = append(users, user.ID)
}
// Reuse an existing group with the same name if present.
conversations, err := st.ListConversationsForUser(ctx, ownerID)
if err != nil {
return nil, err
}
for i := range conversations {
if conversations[i].Type == "group" && conversations[i].Name == "Семья" {
return &conversations[i].Conversation, nil
}
}
group, err := st.CreateGroup(ctx, ownerID, "Семья", users[1:])
if err != nil {
return nil, err
}
if _, err := st.InsertMessage(ctx, group.ID, ownerID, "Всем привет! Это наша семейная группа.", nil); err != nil {
return nil, err
}
return group, nil
}