Compare commits

..

27 Commits

Author SHA1 Message Date
e0fa31b7a1 Merge branch 'devel' 2025-01-26 08:53:36 +01:00
344864f5b2 Correct version number 2025-01-26 08:53:06 +01:00
09350bab0e Stop trying to use docker, it sucks 2025-01-25 19:47:03 +01:00
722022faec Update atom dependency 2025-01-24 23:19:18 +01:00
586b1cdef0 Update dependencies 2025-01-24 20:26:02 +01:00
1f72891896 Fix mariadb version 2025-01-24 20:24:32 +01:00
11d836dd26 Use DB_PASS for root and regular password 2025-01-24 20:10:32 +01:00
57953b2cfd Fix htmx version 2025-01-24 20:09:58 +01:00
a93603eac0 Use generic /bin/sh in docker-start.sh 2025-01-24 20:09:47 +01:00
1b72f05add Fix golang and tailwind version in Dockerfile 2025-01-24 20:09:23 +01:00
2c6b15bc6d Make docker-start.sh executable 2025-01-24 18:52:00 +01:00
cafb158323 Bug fix 2025-01-24 18:40:58 +01:00
40fbb93732 Add docker-start.sh 2025-01-24 18:28:29 +01:00
b120341d78 Correct docker-compose.yml 2025-01-24 18:28:18 +01:00
095576a234 Add tailwindcss to Dockerfile 2025-01-24 18:13:13 +01:00
9199f202be Check atom feed for nil returns 2025-01-24 17:42:05 +01:00
3d08cc7612 Make background image check way more efficient 2025-01-20 20:25:50 +01:00
a7b6fb9705 Correct probably last occurance of manually set path delimeter 2025-01-19 20:41:48 +01:00
2f4d5d4c7c Correct comment 2025-01-19 20:35:35 +01:00
5b417ef87d Change ValidateSession() to SessionIsActive() which only returns a bool 2025-01-19 20:34:12 +01:00
04283d5917 Restore old way of managing session while keeping slimmed down version 2025-01-19 20:29:10 +01:00
d882daeb01 Correct command line flag for ImgDir 2025-01-19 20:15:56 +01:00
f99358729c Create ValidateSession() to not have unwanted side effects when validating session 2025-01-19 20:10:51 +01:00
7b04149a28 Rename PicsDir to ImgDir 2025-01-19 20:10:06 +01:00
43c1cb6d9a Use proper filepaths for docker-compose 2025-01-19 20:08:58 +01:00
9feb16a8d8 Change filepaths to use filepath.Join() where possible 2025-01-19 20:07:32 +01:00
6885dfbb38 Add the ability to run cpolis from docker 2025-01-19 15:17:38 +01:00
17 changed files with 261 additions and 336 deletions

View File

@ -12,11 +12,11 @@ args_bin = [
"-domain localhost", "-domain localhost",
"-feed tmp/cpolis.atom", "-feed tmp/cpolis.atom",
"-firebase tmp/firebase.json", "-firebase tmp/firebase.json",
"-images tmp/pics",
"-img-width 256", "-img-width 256",
"-link https://distrikt-ni-st.de", "-link https://distrikt-ni-st.de",
"-log tmp/cpolis.log", "-log tmp/cpolis.log",
"-pdfs tmp/pdfs", "-pdfs tmp/pdfs",
"-pics tmp/pics",
"-port 8080", "-port 8080",
"-title 'Freimaurer Distrikt Niedersachsen und Sachsen-Anhalt'", "-title 'Freimaurer Distrikt Niedersachsen und Sachsen-Anhalt'",
"-web web", "-web web",

View File

@ -6,6 +6,7 @@ import (
"fmt" "fmt"
"log" "log"
"os" "os"
"path/filepath"
"time" "time"
"github.com/google/uuid" "github.com/google/uuid"
@ -334,9 +335,9 @@ func (db *DB) DeleteArticle(id int64) error {
} }
func WriteArticleToFile(c *Config, articleUUID uuid.UUID, content []byte) error { func WriteArticleToFile(c *Config, articleUUID uuid.UUID, content []byte) error {
articleAbsName := fmt.Sprint(c.ArticleDir, "/", articleUUID, ".md") articlePath := filepath.Join(c.ArticleDir, fmt.Sprint(articleUUID, ".md"))
if err := os.WriteFile(articleAbsName, content, 0644); err != nil { if err := os.WriteFile(articlePath, content, 0644); err != nil {
return fmt.Errorf("error writing article %v to file: %v", articleUUID, err) return fmt.Errorf("error writing article %v to file: %v", articleUUID, err)
} }

View File

@ -1,9 +1,9 @@
package backend package backend
import ( import (
"errors"
"fmt" "fmt"
"io" "io"
"log"
"os" "os"
"git.streifling.com/jason/atom" "git.streifling.com/jason/atom"
@ -13,6 +13,9 @@ func GenerateAtomFeed(c *Config, db *DB) (*string, error) {
feed := atom.NewFeed(c.Title) feed := atom.NewFeed(c.Title)
feed.ID = atom.NewID("urn:feed:1") feed.ID = atom.NewID("urn:feed:1")
feed.Subtitle = atom.NewText("text", c.Description) feed.Subtitle = atom.NewText("text", c.Description)
if feed.Subtitle == nil {
return nil, errors.New("feed subtitle was not created")
}
linkID := feed.AddLink(atom.NewLink(c.Link)) linkID := feed.AddLink(atom.NewLink(c.Link))
feed.Links[linkID].Rel = "self" feed.Links[linkID].Rel = "self"
@ -23,81 +26,82 @@ func GenerateAtomFeed(c *Config, db *DB) (*string, error) {
articles, err := db.GetCertainArticles("published", true) articles, err := db.GetCertainArticles("published", true)
if err != nil { if err != nil {
log.Printf("Error retrieving published articles for Atom feed: %v", err) return nil, fmt.Errorf("error getting published articles for Atom feed: %v", err)
return nil, fmt.Errorf("error getting published articles for Atom feed: %w", err)
} }
for _, article := range articles { for _, article := range articles {
articleTitle, err := ConvertToPlain(article.Title) articleTitle, err := ConvertToPlain(article.Title)
if err != nil { if err != nil {
log.Printf("Error converting article title to plain text for Atom feed: %v", err) return nil, fmt.Errorf("error converting title to plain text for Atom feed: %v", err)
return nil, fmt.Errorf("error converting title to plain text for Atom feed: %w", err)
} }
entry := atom.NewEntry(articleTitle) entry := atom.NewEntry(articleTitle)
entry.ID = atom.NewID(fmt.Sprintf("urn:entry:%d", article.ID)) entry.ID = atom.NewID(fmt.Sprint("urn:entry:", article.ID))
entry.Published = atom.NewDate(article.Created) entry.Published = atom.NewDate(article.Created)
entry.Content = atom.NewContent(atom.OutOfLine, "text/html", fmt.Sprintf("%s/article/serve/%s", c.Domain, article.UUID)) entry.Content = atom.NewContent(atom.OutOfLine, "text/html", fmt.Sprint(c.Domain, "/article/serve/", article.UUID))
if entry.Content == nil {
return nil, errors.New("entry content was not created")
}
if article.AutoGenerated { if article.AutoGenerated {
entry.Summary = atom.NewText("text", "automatically generated") entry.Summary = atom.NewText("text", "automatically generated")
if entry.Summary == nil {
return nil, errors.New("entry summary was not created")
}
} else { } else {
articleSummary, err := ConvertToPlain(article.Summary) articleSummary, err := ConvertToPlain(article.Summary)
if err != nil { if err != nil {
log.Printf("Error converting article summary to plain text for Atom feed: %v", err) return nil, fmt.Errorf("error converting description to plain text for Atom feed: %v", err)
return nil, fmt.Errorf("error converting description to plain text for Atom feed: %w", err)
} }
entry.Summary = atom.NewText("text", articleSummary) entry.Summary = atom.NewText("text", articleSummary)
if entry.Summary == nil {
return nil, errors.New("entry summary was not created")
}
} }
if len(article.BannerLink) > 0 { if len(article.BannerLink) > 0 {
linkID := entry.AddLink(atom.NewLink(fmt.Sprintf("%s/image/serve/%s", c.Domain, article.BannerLink))) linkID := entry.AddLink(atom.NewLink(c.Domain + "/image/serve/" + article.BannerLink))
entry.Links[linkID].Rel = "enclosure" entry.Links[linkID].Rel = "enclosure"
entry.Links[linkID].Type = "image/webp" entry.Links[linkID].Type = "image/webp"
} }
authors, err := db.GetArticleAuthors(c, article.ID) authors, err := db.GetArticleAuthors(c, article.ID)
if err != nil { if err != nil {
log.Printf("Error retrieving authors for article ID %d for Atom feed: %v", article.ID, err) return nil, fmt.Errorf("error getting article's authors for Atom feed: %v", err)
return nil, fmt.Errorf("error getting article's authors for Atom feed: %w", err)
} }
for _, author := range authors { for _, author := range authors {
user, err := db.GetUser(c, author.ID) user, err := db.GetUser(c, author.ID)
if err != nil { if err != nil {
log.Printf("Error retrieving user info for author ID %d for Atom feed: %v", author.ID, err) return nil, fmt.Errorf("error getting user info for Atom feed: %v", err)
return nil, fmt.Errorf("error getting user info for Atom feed: %w", err)
} }
authorID := entry.AddAuthor(atom.NewPerson(fmt.Sprintf("%s %s", user.FirstName, user.LastName))) authorID := entry.AddAuthor(atom.NewPerson(user.FirstName + " " + user.LastName))
entry.Authors[authorID].URI = fmt.Sprintf("%s/image/serve/%s", c.Domain, user.ProfilePicLink) entry.Authors[authorID].URI = c.Domain + "/image/serve/" + user.ProfilePicLink
} }
contributors, err := db.GetArticleContributors(c, article.ID) contributors, err := db.GetArticleContributors(c, article.ID)
if err != nil { if err != nil {
log.Printf("Error retrieving contributors for article ID %d for Atom feed: %v", article.ID, err) return nil, fmt.Errorf("error getting article's contributors for Atom feed: %v", err)
return nil, fmt.Errorf("error getting article's contributors for Atom feed: %w", err)
} }
for _, contributor := range contributors { for _, contributor := range contributors {
user, err := db.GetUser(c, contributor.ID) user, err := db.GetUser(c, contributor.ID)
if err != nil { if err != nil {
log.Printf("Error retrieving user info for contributor ID %d for Atom feed: %v", contributor.ID, err) return nil, fmt.Errorf("error getting user info for Atom feed: %v", err)
return nil, fmt.Errorf("error getting user info for Atom feed: %w", err)
} }
contributorID := entry.AddContributor(atom.NewPerson(fmt.Sprintf("%s %s", user.FirstName, user.LastName))) contributorID := entry.AddContributor(atom.NewPerson(user.FirstName + " " + user.LastName))
entry.Contributors[contributorID].URI = fmt.Sprintf("%s/image/serve/%s", c.Domain, user.ProfilePicLink) entry.Contributors[contributorID].URI = c.Domain + "/image/serve/" + user.ProfilePicLink
} }
tags, err := db.GetArticleTags(article.ID) tags, err := db.GetArticleTags(article.ID)
if err != nil { if err != nil {
log.Printf("Error retrieving tags for article ID %d for Atom feed: %v", article.ID, err) return nil, fmt.Errorf("error getting tags for articles for Atom feed: %v", err)
return nil, fmt.Errorf("error getting tags for articles for Atom feed: %w", err)
} }
for _, tag := range tags { for _, tag := range tags {
entry.AddCategory(atom.NewCategory(tag.Name)) entry.AddCategory(atom.NewCategory(tag.Name))
} }
if article.IsInIssue || article.AutoGenerated { if article.IsInIssue || article.AutoGenerated {
entry.AddCategory(atom.NewCategory(fmt.Sprintf("Orient Express %d", article.IssueID))) entry.AddCategory(atom.NewCategory(fmt.Sprint("Orient Express ", article.IssueID)))
} }
if article.AutoGenerated { if article.AutoGenerated {
entry.AddCategory(atom.NewCategory("autogenerated")) entry.AddCategory(atom.NewCategory("autogenerated"))
@ -108,39 +112,29 @@ func GenerateAtomFeed(c *Config, db *DB) (*string, error) {
} }
if err = feed.Check(); err != nil { if err = feed.Check(); err != nil {
log.Printf("Error checking Atom feed: %v", err) return nil, fmt.Errorf("error checking Atom feed: %v", err)
return nil, fmt.Errorf("error checking Atom feed: %w", err)
} }
atomXML, err := feed.ToXML("UTF-8") atom, err := feed.ToXML("UTF-8")
if err != nil { if err != nil {
log.Printf("Error converting Atom feed to XML: %v", err) return nil, fmt.Errorf("error converting Atom feed to XML: %v", err)
return nil, fmt.Errorf("error converting Atom feed to XML: %w", err)
} }
return &atomXML, nil return &atom, nil
} }
func SaveAtomFeed(filename string, feed *string) error { func SaveAtomFeed(filename string, feed *string) error {
file, err := os.Create(filename) file, err := os.Create(filename)
if err != nil { if err != nil {
log.Printf("Error creating file for Atom feed: %v", err) return fmt.Errorf("error creating file for Atom feed: %v", err)
return fmt.Errorf("error creating file for Atom feed: %w", err)
} }
defer func() { defer file.Close()
if cerr := file.Close(); cerr != nil {
log.Printf("Error closing file for Atom feed: %v", cerr)
}
}()
if err = file.Chmod(0644); err != nil { if err = file.Chmod(0644); err != nil {
log.Printf("Error setting permissions for Atom file '%s': %v", filename, err) return fmt.Errorf("error setting permissions for Atom file: %v", err)
return fmt.Errorf("error setting permissions for Atom file: %w", err)
} }
if _, err = io.WriteString(file, *feed); err != nil { if _, err = io.WriteString(file, *feed); err != nil {
log.Printf("Error writing to Atom file '%s': %v", filename, err) return fmt.Errorf("error writing to Atom file: %v", err)
return fmt.Errorf("error writing to Atom file: %w", err)
} }
return nil return nil

View File

@ -20,10 +20,10 @@ type Config struct {
Description string Description string
Domain string Domain string
FirebaseKey string FirebaseKey string
ImgDir string
Link string Link string
LogFile string LogFile string
PDFDir string PDFDir string
PicsDir string
Port string Port string
Title string Title string
Version string Version string
@ -43,27 +43,24 @@ func newConfig() *Config {
ConfigFile: "/etc/cpolis/config.toml", ConfigFile: "/etc/cpolis/config.toml",
CookieExpiryHours: 24 * 30, CookieExpiryHours: 24 * 30,
DBName: "cpolis", DBName: "cpolis",
FirebaseKey: "/var/www/cpolis/serviceAccountKey.json", FirebaseKey: "/etc/cpolis/serviceAccountKey.json",
ImgDir: "/var/www/cpolis/images",
LogFile: "/var/log/cpolis.log", LogFile: "/var/log/cpolis.log",
MaxBannerHeight: 1080, MaxBannerHeight: 1080,
MaxBannerWidth: 1920, MaxBannerWidth: 1920,
MaxImgHeight: 1080, MaxImgHeight: 1080,
MaxImgWidth: 1920, MaxImgWidth: 1920,
PDFDir: "/var/www/cpolis/pdfs", PDFDir: "/var/www/cpolis/pdfs",
PicsDir: "/var/www/cpolis/pics", Port: ":1664",
Port: ":8080", Version: "v0.15.4",
Version: "v0.15.3",
WebDir: "/var/www/cpolis/web", WebDir: "/var/www/cpolis/web",
} }
} }
func mkDir(path string, perm fs.FileMode) (string, error) { func mkDir(path string, perm fs.FileMode) (string, error) {
var err error name := filepath.Base(path)
stringSlice := strings.Split(path, "/") path, err := filepath.Abs(path)
name := stringSlice[len(stringSlice)-1]
path, err = filepath.Abs(path)
if err != nil { if err != nil {
return "", fmt.Errorf("error finding absolute path for %v directory: %v", name, err) return "", fmt.Errorf("error finding absolute path for %v directory: %v", name, err)
} }
@ -82,20 +79,20 @@ func mkFile(path string, filePerm, dirPerm fs.FileMode) (string, error) {
return "", fmt.Errorf("error finding absolute path for %v: %v", path, err) return "", fmt.Errorf("error finding absolute path for %v: %v", path, err)
} }
stringSlice := strings.Split(path, "/")
_, err = os.Stat(path) _, err = os.Stat(path)
if os.IsNotExist(err) { if os.IsNotExist(err) {
dir := strings.Join(stringSlice[:len(stringSlice)-1], "/") dir := filepath.Dir(path)
if err = os.MkdirAll(dir, dirPerm); err != nil { if err = os.MkdirAll(dir, dirPerm); err != nil {
return "", fmt.Errorf("error creating %v: %v", dir, err) return "", fmt.Errorf("error creating %v: %v", dir, err)
} }
fileName := stringSlice[len(stringSlice)-1] fileName := filepath.Base(path)
file, err := os.Create(filepath.Join(dir, fileName)) file, err := os.Create(filepath.Join(dir, fileName))
if err != nil { if err != nil {
return "", fmt.Errorf("error creating %v: %v", fileName, err) return "", fmt.Errorf("error creating %v: %v", fileName, err)
} }
defer file.Close() defer file.Close()
if err = file.Chmod(filePerm); err != nil { if err = file.Chmod(filePerm); err != nil {
return "", fmt.Errorf("error setting permissions for %v: %v", fileName, err) return "", fmt.Errorf("error setting permissions for %v: %v", fileName, err)
} }
@ -116,10 +113,10 @@ func (c *Config) handleCliArgs() error {
flag.StringVar(&c.Description, "desc", c.Description, "channel description") flag.StringVar(&c.Description, "desc", c.Description, "channel description")
flag.StringVar(&c.Domain, "domain", c.Domain, "domain name") flag.StringVar(&c.Domain, "domain", c.Domain, "domain name")
flag.StringVar(&c.FirebaseKey, "firebase", c.FirebaseKey, "Firebase service account key file") flag.StringVar(&c.FirebaseKey, "firebase", c.FirebaseKey, "Firebase service account key file")
flag.StringVar(&c.ImgDir, "images", c.ImgDir, "images directory")
flag.StringVar(&c.Link, "link", c.Link, "channel Link") flag.StringVar(&c.Link, "link", c.Link, "channel Link")
flag.StringVar(&c.LogFile, "log", c.LogFile, "log file") flag.StringVar(&c.LogFile, "log", c.LogFile, "log file")
flag.StringVar(&c.PDFDir, "pdfs", c.PDFDir, "pdf directory") flag.StringVar(&c.PDFDir, "pdfs", c.PDFDir, "pdf directory")
flag.StringVar(&c.PicsDir, "pics", c.PicsDir, "pictures directory")
flag.StringVar(&c.Title, "title", c.Title, "channel title") flag.StringVar(&c.Title, "title", c.Title, "channel title")
flag.StringVar(&c.WebDir, "web", c.WebDir, "web directory") flag.StringVar(&c.WebDir, "web", c.WebDir, "web directory")
flag.IntVar(&c.CookieExpiryHours, "cookie-expiry-hours", c.CookieExpiryHours, "cookies expire after this amount of hours") flag.IntVar(&c.CookieExpiryHours, "cookie-expiry-hours", c.CookieExpiryHours, "cookies expire after this amount of hours")
@ -223,6 +220,18 @@ func (c *Config) setupConfig(cliConfig *Config) error {
return fmt.Errorf("error setting up file: %v", err) return fmt.Errorf("error setting up file: %v", err)
} }
if cliConfig.ImgDir != defaultConfig.ImgDir {
c.ImgDir = cliConfig.ImgDir
}
c.ImgDir, err = filepath.Abs(c.ImgDir)
if err != nil {
return fmt.Errorf("error setting absolute filepath for PicsDir: %v", err)
}
c.ImgDir, err = mkDir(c.ImgDir, 0700)
if err != nil {
return fmt.Errorf("error setting up directory: %v", err)
}
if cliConfig.Link != defaultConfig.Link { if cliConfig.Link != defaultConfig.Link {
c.Link = cliConfig.Link c.Link = cliConfig.Link
} }
@ -267,18 +276,6 @@ func (c *Config) setupConfig(cliConfig *Config) error {
return fmt.Errorf("error setting up directory: %v", err) return fmt.Errorf("error setting up directory: %v", err)
} }
if cliConfig.PicsDir != defaultConfig.PicsDir {
c.PicsDir = cliConfig.PicsDir
}
c.PicsDir, err = filepath.Abs(c.PicsDir)
if err != nil {
return fmt.Errorf("error setting absolute filepath for PicsDir: %v", err)
}
c.PicsDir, err = mkDir(c.PicsDir, 0700)
if err != nil {
return fmt.Errorf("error setting up directory: %v", err)
}
if cliConfig.Port != defaultConfig.Port { if cliConfig.Port != defaultConfig.Port {
c.Port = cliConfig.Port c.Port = cliConfig.Port
} }

View File

@ -33,7 +33,7 @@ func ConvertToMarkdown(c *Config, filename string) ([]byte, error) {
return nil, fmt.Errorf("error reading markdown file: %v", err) return nil, fmt.Errorf("error reading markdown file: %v", err)
} }
imageNames, err := filepath.Glob(filepath.Join(tmpDir, "/media/*")) imageNames, err := filepath.Glob(filepath.Join(tmpDir, "media", "*"))
if err != nil { if err != nil {
return nil, fmt.Errorf("error getting docx images from temporary directory: %v", err) return nil, fmt.Errorf("error getting docx images from temporary directory: %v", err)
} }
@ -45,7 +45,7 @@ func ConvertToMarkdown(c *Config, filename string) ([]byte, error) {
} }
defer image.Close() defer image.Close()
newImageName, err := SaveImage(image, c.MaxImgHeight, c.MaxImgWidth, c.PicsDir) newImageName, err := SaveImage(image, c.MaxImgHeight, c.MaxImgWidth, c.ImgDir)
if err != nil { if err != nil {
return nil, fmt.Errorf("error saving image %v: %v", name, err) return nil, fmt.Errorf("error saving image %v: %v", name, err)
} }

View File

@ -17,6 +17,53 @@ import (
var ErrUnsupportedFormat error = image.ErrFormat // used internally by imaging var ErrUnsupportedFormat error = image.ErrFormat // used internally by imaging
func checkImageUsage(c *Config, db *DB, name string) (bool, error) {
imageWasFound := false
if err := filepath.Walk(c.ArticleDir, func(path string, info fs.FileInfo, err error) error {
if err != nil {
return fmt.Errorf("error walking articles filepath: %v", err)
}
if !info.IsDir() {
mdFile, err := os.Open(path)
if err != nil {
return fmt.Errorf("error opening article %v: %v", info.Name(), err)
}
defer mdFile.Close()
scanner := bufio.NewScanner(mdFile)
for scanner.Scan() {
if strings.Contains(scanner.Text(), name) {
imageWasFound = true
return nil
}
}
return scanner.Err()
}
return nil
}); err != nil {
return false, fmt.Errorf("error walking articles filepath: %v", err)
}
if !imageWasFound {
users, err := db.GetAllUsers(c)
if err != nil {
return false, fmt.Errorf("error getting all users: %v", err)
}
for _, user := range users {
if name == user.ProfilePicLink {
return true, nil
}
}
}
return imageWasFound, nil
}
func SaveImage(src io.Reader, maxHeight, maxWidth int, path string) (string, error) { func SaveImage(src io.Reader, maxHeight, maxWidth int, path string) (string, error) {
img, err := imaging.Decode(src, imaging.AutoOrientation(true)) img, err := imaging.Decode(src, imaging.AutoOrientation(true))
if err != nil { if err != nil {
@ -48,7 +95,7 @@ func SaveImage(src io.Reader, maxHeight, maxWidth int, path string) (string, err
} }
func CleanUpImages(c *Config, db *DB) error { func CleanUpImages(c *Config, db *DB) error {
if err := filepath.Walk(c.PicsDir, func(path string, info fs.FileInfo, err error) error { if err := filepath.Walk(c.ImgDir, func(path string, info fs.FileInfo, err error) error {
if err != nil { if err != nil {
return fmt.Errorf("error walking images filepath: %v", err) return fmt.Errorf("error walking images filepath: %v", err)
} }
@ -56,45 +103,10 @@ func CleanUpImages(c *Config, db *DB) error {
if !info.IsDir() { if !info.IsDir() {
imageName := info.Name() imageName := info.Name()
imagePath := path imagePath := path
imageWasFound := false
if err = filepath.Walk(c.ArticleDir, func(path string, info fs.FileInfo, err error) error { imageWasFound, err := checkImageUsage(c, db, imageName)
if err != nil { if err != nil {
return fmt.Errorf("error walking articles filepath: %v", err) return fmt.Errorf("error checking image usage: %v", err)
}
if !info.IsDir() {
mdFile, err := os.Open(path)
if err != nil {
return fmt.Errorf("error opening article %v: %v", info.Name(), err)
}
defer mdFile.Close()
scanner := bufio.NewScanner(mdFile)
for scanner.Scan() {
if strings.Contains(scanner.Text(), imageName) {
imageWasFound = true
}
}
return scanner.Err()
}
return nil
}); err != nil {
return fmt.Errorf("error walking articles filepath: %v", err)
}
users, err := db.GetAllUsers(c)
if err != nil {
return fmt.Errorf("error getting all users: %v", err)
}
for _, user := range users {
if imageName == user.ProfilePicLink {
imageWasFound = true
}
} }
if !imageWasFound { if !imageWasFound {

View File

@ -37,19 +37,15 @@ type User struct {
func readKey(filename string) ([]byte, error) { func readKey(filename string) ([]byte, error) {
key, err := os.ReadFile(filename) key, err := os.ReadFile(filename)
if err != nil { if err != nil {
log.Printf("Error reading AES key file '%s': %v", filename, err) return nil, fmt.Errorf("error reading from aes key file: %v", err)
return nil, fmt.Errorf("error reading from AES key file: %v", err)
} }
if len(key) != 44 { if len(key) != 44 {
errMsg := "key is not 32 bytes long" return nil, errors.New("key is not 32 bytes long")
log.Println(errMsg)
return nil, errors.New(errMsg)
} }
key, err = base64.StdEncoding.DecodeString(string(key)) key, err = base64.StdEncoding.DecodeString(string(key))
if err != nil { if err != nil {
log.Printf("Error base64 decoding key: %v", err)
return nil, fmt.Errorf("error base64 decoding key: %v", err) return nil, fmt.Errorf("error base64 decoding key: %v", err)
} }
@ -59,17 +55,14 @@ func readKey(filename string) ([]byte, error) {
func key(c *Config) ([]byte, error) { func key(c *Config) ([]byte, error) {
key, err := readKey(c.AESKeyFile) key, err := readKey(c.AESKeyFile)
if err != nil { if err != nil {
log.Printf("Error reading key: %v", err)
key = make([]byte, 32) key = make([]byte, 32)
if _, err := rand.Read(key); err != nil { if _, err := rand.Read(key); err != nil {
log.Printf("Error generating random key: %v", err)
return nil, fmt.Errorf("error generating random key: %v", err) return nil, fmt.Errorf("error generating random key: %v", err)
} }
fileKey := make([]byte, 44) fileKey := make([]byte, 44)
base64.StdEncoding.Encode(fileKey, key) base64.StdEncoding.Encode(fileKey, key)
if err = os.WriteFile(c.AESKeyFile, fileKey, 0600); err != nil { if err = os.WriteFile(c.AESKeyFile, fileKey, 0600); err != nil {
log.Printf("Error writing key to file '%s': %v", c.AESKeyFile, err)
return nil, fmt.Errorf("error writing key to file: %v", err) return nil, fmt.Errorf("error writing key to file: %v", err)
} }
} }
@ -80,25 +73,21 @@ func key(c *Config) ([]byte, error) {
func aesEncrypt(c *Config, plaintext string) (string, error) { func aesEncrypt(c *Config, plaintext string) (string, error) {
key, err := key(c) key, err := key(c)
if err != nil { if err != nil {
log.Printf("Error retrieving key: %v", err)
return "", fmt.Errorf("error retrieving key: %v", err) return "", fmt.Errorf("error retrieving key: %v", err)
} }
block, err := aes.NewCipher(key) block, err := aes.NewCipher(key)
if err != nil { if err != nil {
log.Printf("Error creating cipher block: %v", err)
return "", fmt.Errorf("error creating cipher block: %v", err) return "", fmt.Errorf("error creating cipher block: %v", err)
} }
gcm, err := cipher.NewGCM(block) gcm, err := cipher.NewGCM(block)
if err != nil { if err != nil {
log.Printf("Error creating GCM: %v", err) return "", fmt.Errorf("error creating new gcm: %v", err)
return "", fmt.Errorf("error creating new GCM: %v", err)
} }
nonce := make([]byte, gcm.NonceSize()) nonce := make([]byte, gcm.NonceSize())
if _, err := io.ReadFull(rand.Reader, nonce); err != nil { if _, err := io.ReadFull(rand.Reader, nonce); err != nil {
log.Printf("Error creating nonce: %v", err)
return "", fmt.Errorf("error creating nonce: %v", err) return "", fmt.Errorf("error creating nonce: %v", err)
} }
@ -109,40 +98,30 @@ func aesEncrypt(c *Config, plaintext string) (string, error) {
func aesDecrypt(c *Config, ciphertext string) (string, error) { func aesDecrypt(c *Config, ciphertext string) (string, error) {
key, err := key(c) key, err := key(c)
if err != nil { if err != nil {
log.Printf("Error retrieving key: %v", err)
return "", fmt.Errorf("error retrieving key: %v", err) return "", fmt.Errorf("error retrieving key: %v", err)
} }
block, err := aes.NewCipher(key) block, err := aes.NewCipher(key)
if err != nil { if err != nil {
log.Printf("Error creating cipher block: %v", err)
return "", fmt.Errorf("error creating cipher block: %v", err) return "", fmt.Errorf("error creating cipher block: %v", err)
} }
gcm, err := cipher.NewGCM(block) gcm, err := cipher.NewGCM(block)
if err != nil { if err != nil {
log.Printf("Error creating GCM: %v", err) return "", fmt.Errorf("error creating new gcm: %v", err)
return "", fmt.Errorf("error creating new GCM: %v", err)
} }
data, err := base64.StdEncoding.DecodeString(ciphertext) data, err := base64.StdEncoding.DecodeString(ciphertext)
if err != nil { if err != nil {
log.Printf("Error base64 decoding ciphertext: %v", err)
return "", fmt.Errorf("error base64 decoding ciphertext: %v", err) return "", fmt.Errorf("error base64 decoding ciphertext: %v", err)
} }
nonceSize := gcm.NonceSize() nonceSize := gcm.NonceSize()
if len(data) < nonceSize {
errMsg := "ciphertext too short"
log.Println(errMsg)
return "", fmt.Errorf(errMsg)
}
nonce, cipherText := data[:nonceSize], data[nonceSize:] nonce, cipherText := data[:nonceSize], data[nonceSize:]
plaintext, err := gcm.Open(nil, nonce, cipherText, nil) plaintext, err := gcm.Open(nil, nonce, cipherText, nil)
if err != nil { if err != nil {
log.Printf("Error AES decoding ciphertext: %v", err) return "", fmt.Errorf("error aes decoding ciphertext: %v", err)
return "", fmt.Errorf("error AES decoding ciphertext: %v", err)
} }
return string(plaintext), nil return string(plaintext), nil
@ -151,25 +130,21 @@ func aesDecrypt(c *Config, ciphertext string) (string, error) {
func (db *DB) AddUser(c *Config, u *User, pass string) (int64, error) { func (db *DB) AddUser(c *Config, u *User, pass string) (int64, error) {
hashedPass, err := bcrypt.GenerateFromPassword([]byte(pass), bcrypt.DefaultCost) hashedPass, err := bcrypt.GenerateFromPassword([]byte(pass), bcrypt.DefaultCost)
if err != nil { if err != nil {
log.Printf("Error creating password hash: %v", err)
return 0, fmt.Errorf("error creating password hash: %v", err) return 0, fmt.Errorf("error creating password hash: %v", err)
} }
aesFirstName, err := aesEncrypt(c, u.FirstName) aesFirstName, err := aesEncrypt(c, u.FirstName)
if err != nil { if err != nil {
log.Printf("Error encrypting first name: %v", err)
return 0, fmt.Errorf("error encrypting first name: %v", err) return 0, fmt.Errorf("error encrypting first name: %v", err)
} }
aesLastName, err := aesEncrypt(c, u.LastName) aesLastName, err := aesEncrypt(c, u.LastName)
if err != nil { if err != nil {
log.Printf("Error encrypting last name: %v", err)
return 0, fmt.Errorf("error encrypting last name: %v", err) return 0, fmt.Errorf("error encrypting last name: %v", err)
} }
aesEmail, err := aesEncrypt(c, u.Email) aesEmail, err := aesEncrypt(c, u.Email)
if err != nil { if err != nil {
log.Printf("Error encrypting email: %v", err)
return 0, fmt.Errorf("error encrypting email: %v", err) return 0, fmt.Errorf("error encrypting email: %v", err)
} }
@ -180,12 +155,10 @@ func (db *DB) AddUser(c *Config, u *User, pass string) (int64, error) {
result, err := db.Exec(query, u.UserName, string(hashedPass), aesFirstName, aesLastName, aesEmail, u.ProfilePicLink, u.Role) result, err := db.Exec(query, u.UserName, string(hashedPass), aesFirstName, aesLastName, aesEmail, u.ProfilePicLink, u.Role)
if err != nil { if err != nil {
log.Printf("Error inserting new user '%v' into DB: %v", u.UserName, err)
return 0, fmt.Errorf("error inserting new user %v into DB: %v", u.UserName, err) return 0, fmt.Errorf("error inserting new user %v into DB: %v", u.UserName, err)
} }
id, err := result.LastInsertId() id, err := result.LastInsertId()
if err != nil { if err != nil {
log.Printf("Error retrieving last insert ID: %v", err)
return 0, fmt.Errorf("error inserting user into DB: %v", err) return 0, fmt.Errorf("error inserting user into DB: %v", err)
} }
@ -202,7 +175,6 @@ func (db *DB) GetID(userName string) int64 {
` `
row := db.QueryRow(query, userName) row := db.QueryRow(query, userName)
if err := row.Scan(&id); err != nil { // seems like the only possible error is ErrNoRows if err := row.Scan(&id); err != nil { // seems like the only possible error is ErrNoRows
log.Printf("Error retrieving ID for user '%v': %v", userName, err)
return 0 return 0
} }
@ -219,12 +191,10 @@ func (db *DB) CheckPassword(id int64, pass string) error {
` `
row := db.QueryRow(query, id) row := db.QueryRow(query, id)
if err := row.Scan(&queriedPass); err != nil { if err := row.Scan(&queriedPass); err != nil {
log.Printf("Error reading password from DB for ID '%v': %v", id, err)
return fmt.Errorf("error reading password from DB: %v", err) return fmt.Errorf("error reading password from DB: %v", err)
} }
if err := bcrypt.CompareHashAndPassword([]byte(queriedPass), []byte(pass)); err != nil { if err := bcrypt.CompareHashAndPassword([]byte(queriedPass), []byte(pass)); err != nil {
log.Printf("Incorrect password for ID '%v': %v", id, err)
return fmt.Errorf("incorrect password: %v", err) return fmt.Errorf("incorrect password: %v", err)
} }
@ -240,26 +210,23 @@ func (tx *Tx) ChangePassword(id int64, oldPass, newPass string) error {
` `
row := tx.QueryRow(getQuery, id) row := tx.QueryRow(getQuery, id)
if err := row.Scan(&queriedPass); err != nil { if err := row.Scan(&queriedPass); err != nil {
log.Printf("Error reading password from DB during password change for ID '%v': %v", id, err)
if rollbackErr := tx.Rollback(); rollbackErr != nil { if rollbackErr := tx.Rollback(); rollbackErr != nil {
log.Fatalf("Transaction error: %v, Rollback error: %v", err, rollbackErr) log.Fatalf("transaction error: %v, rollback error: %v", err, rollbackErr)
} }
return fmt.Errorf("error reading password from DB: %v", err) return fmt.Errorf("error reading password from DB: %v", err)
} }
if err := bcrypt.CompareHashAndPassword([]byte(queriedPass), []byte(oldPass)); err != nil { if err := bcrypt.CompareHashAndPassword([]byte(queriedPass), []byte(oldPass)); err != nil {
log.Printf("Incorrect old password for ID '%v': %v", id, err)
if rollbackErr := tx.Rollback(); rollbackErr != nil { if rollbackErr := tx.Rollback(); rollbackErr != nil {
log.Fatalf("Transaction error: %v, Rollback error: %v", err, rollbackErr) log.Fatalf("transaction error: %v, rollback error: %v", err, rollbackErr)
} }
return fmt.Errorf("incorrect password: %v", err) return fmt.Errorf("incorrect password: %v", err)
} }
newHashedPass, err := bcrypt.GenerateFromPassword([]byte(newPass), bcrypt.DefaultCost) newHashedPass, err := bcrypt.GenerateFromPassword([]byte(newPass), bcrypt.DefaultCost)
if err != nil { if err != nil {
log.Printf("Error creating new password hash for ID '%v': %v", id, err)
if rollbackErr := tx.Rollback(); rollbackErr != nil { if rollbackErr := tx.Rollback(); rollbackErr != nil {
log.Fatalf("Transaction error: %v, Rollback error: %v", err, rollbackErr) log.Fatalf("transaction error: %v, rollback error: %v", err, rollbackErr)
} }
return fmt.Errorf("error creating password hash: %v", err) return fmt.Errorf("error creating password hash: %v", err)
} }
@ -270,9 +237,8 @@ func (tx *Tx) ChangePassword(id int64, oldPass, newPass string) error {
WHERE id = ? WHERE id = ?
` `
if _, err = tx.Exec(setQuery, string(newHashedPass), id); err != nil { if _, err = tx.Exec(setQuery, string(newHashedPass), id); err != nil {
log.Printf("Error updating password in DB for ID '%v': %v", id, err)
if rollbackErr := tx.Rollback(); rollbackErr != nil { if rollbackErr := tx.Rollback(); rollbackErr != nil {
log.Fatalf("Transaction error: %v, Rollback error: %v", err, rollbackErr) log.Fatalf("transaction error: %v, rollback error: %v", err, rollbackErr)
} }
return fmt.Errorf("error updating password in DB: %v", err) return fmt.Errorf("error updating password in DB: %v", err)
} }
@ -294,25 +260,21 @@ func (db *DB) GetUser(c *Config, id int64) (*User, error) {
row := db.QueryRow(query, id) row := db.QueryRow(query, id)
if err := row.Scan(&user.ID, &user.UserName, &aesFirstName, &aesLastName, &aesEmail, &user.ProfilePicLink, &user.Role); err != nil { if err := row.Scan(&user.ID, &user.UserName, &aesFirstName, &aesLastName, &aesEmail, &user.ProfilePicLink, &user.Role); err != nil {
log.Printf("Error reading user information from DB for ID '%v': %v", id, err)
return nil, fmt.Errorf("error reading user information: %v", err) return nil, fmt.Errorf("error reading user information: %v", err)
} }
user.FirstName, err = aesDecrypt(c, aesFirstName) user.FirstName, err = aesDecrypt(c, aesFirstName)
if err != nil { if err != nil {
log.Printf("Error decrypting first name for ID '%v': %v", id, err)
return nil, fmt.Errorf("error decrypting first name: %v", err) return nil, fmt.Errorf("error decrypting first name: %v", err)
} }
user.LastName, err = aesDecrypt(c, aesLastName) user.LastName, err = aesDecrypt(c, aesLastName)
if err != nil { if err != nil {
log.Printf("Error decrypting last name for ID '%v': %v", id, err)
return nil, fmt.Errorf("error decrypting last name: %v", err) return nil, fmt.Errorf("error decrypting last name: %v", err)
} }
user.Email, err = aesDecrypt(c, aesEmail) user.Email, err = aesDecrypt(c, aesEmail)
if err != nil { if err != nil {
log.Printf("Error decrypting email for ID '%v': %v", id, err)
return nil, fmt.Errorf("error decrypting email: %v", err) return nil, fmt.Errorf("error decrypting email: %v", err)
} }
@ -328,15 +290,13 @@ func (db *DB) UpdateOwnUserAttributes(c *Config, id int64, userName, firstName,
err := func() error { err := func() error {
tx.Tx, err = db.Begin() tx.Tx, err = db.Begin()
if err != nil { if err != nil {
log.Printf("Error starting transaction: %v", err)
return fmt.Errorf("error starting transaction: %v", err) return fmt.Errorf("error starting transaction: %v", err)
} }
if !passwordEmpty { if !passwordEmpty {
if err = tx.ChangePassword(id, oldPass, newPass); err != nil { if err = tx.ChangePassword(id, oldPass, newPass); err != nil {
log.Printf("Error changing password for ID '%v': %v", id, err)
if rollbackErr := tx.Rollback(); rollbackErr != nil { if rollbackErr := tx.Rollback(); rollbackErr != nil {
log.Fatalf("Transaction error: %v, Rollback error: %v", err, rollbackErr) log.Fatalf("transaction error: %v, rollback error: %v", err, rollbackErr)
} }
return fmt.Errorf("error changing password: %v", err) return fmt.Errorf("error changing password: %v", err)
} }
@ -344,27 +304,24 @@ func (db *DB) UpdateOwnUserAttributes(c *Config, id int64, userName, firstName,
aesFirstName, err := aesEncrypt(c, firstName) aesFirstName, err := aesEncrypt(c, firstName)
if err != nil { if err != nil {
log.Printf("Error encrypting first name for ID '%v': %v", id, err)
if rollbackErr := tx.Rollback(); rollbackErr != nil { if rollbackErr := tx.Rollback(); rollbackErr != nil {
log.Fatalf("Transaction error: %v, Rollback error: %v", err, rollbackErr) log.Fatalf("transaction error: %v, rollback error: %v", err, rollbackErr)
} }
return fmt.Errorf("error encrypting first name: %v", err) return fmt.Errorf("error encrypting first name: %v", err)
} }
aesLastName, err := aesEncrypt(c, lastName) aesLastName, err := aesEncrypt(c, lastName)
if err != nil { if err != nil {
log.Printf("Error encrypting last name for ID '%v': %v", id, err)
if rollbackErr := tx.Rollback(); rollbackErr != nil { if rollbackErr := tx.Rollback(); rollbackErr != nil {
log.Fatalf("Transaction error: %v, Rollback error: %v", err, rollbackErr) log.Fatalf("transaction error: %v, rollback error: %v", err, rollbackErr)
} }
return fmt.Errorf("error encrypting last name: %v", err) return fmt.Errorf("error encrypting last name: %v", err)
} }
aesEmail, err := aesEncrypt(c, email) aesEmail, err := aesEncrypt(c, email)
if err != nil { if err != nil {
log.Printf("Error encrypting email for ID '%v': %v", id, err)
if rollbackErr := tx.Rollback(); rollbackErr != nil { if rollbackErr := tx.Rollback(); rollbackErr != nil {
log.Fatalf("Transaction error: %v, Rollback error: %v", err, rollbackErr) log.Fatalf("transaction error: %v, rollback error: %v", err, rollbackErr)
} }
return fmt.Errorf("error encrypting email: %v", err) return fmt.Errorf("error encrypting email: %v", err)
} }
@ -376,15 +333,13 @@ func (db *DB) UpdateOwnUserAttributes(c *Config, id int64, userName, firstName,
&Attribute{Table: "users", ID: id, AttName: "email", Value: aesEmail}, &Attribute{Table: "users", ID: id, AttName: "email", Value: aesEmail},
&Attribute{Table: "users", ID: id, AttName: "profile_pic_link", Value: profilePicLink}, &Attribute{Table: "users", ID: id, AttName: "profile_pic_link", Value: profilePicLink},
); err != nil { ); err != nil {
log.Printf("Error updating attributes in DB for ID '%v': %v", id, err)
if rollbackErr := tx.Rollback(); rollbackErr != nil { if rollbackErr := tx.Rollback(); rollbackErr != nil {
log.Fatalf("Transaction error: %v, Rollback error: %v", err, rollbackErr) log.Fatalf("transaction error: %v, rollback error: %v", err, rollbackErr)
} }
return fmt.Errorf("error updating attributes in DB: %v", err) return fmt.Errorf("error updating attributes in DB: %v", err)
} }
if err = tx.Commit(); err != nil { if err = tx.Commit(); err != nil {
log.Printf("Error committing transaction for ID '%v': %v", id, err)
return fmt.Errorf("error committing transaction: %v", err) return fmt.Errorf("error committing transaction: %v", err)
} }
@ -398,9 +353,7 @@ func (db *DB) UpdateOwnUserAttributes(c *Config, id int64, userName, firstName,
wait(i) wait(i)
} }
errMsg := fmt.Sprintf("error: %v unsuccessful retries for DB operation, aborting", TxMaxRetries) return fmt.Errorf("error: %v unsuccessful retries for DB operation, aborting", TxMaxRetries)
log.Println(errMsg)
return fmt.Errorf(errMsg)
} }
func (db *DB) AddFirstUser(c *Config, u *User, pass string) (int64, error) { func (db *DB) AddFirstUser(c *Config, u *User, pass string) (int64, error) {
@ -416,20 +369,17 @@ func (db *DB) AddFirstUser(c *Config, u *User, pass string) (int64, error) {
id, err := func() (int64, error) { id, err := func() (int64, error) {
tx, err := db.BeginTx(context.Background(), txOptions) tx, err := db.BeginTx(context.Background(), txOptions)
if err != nil { if err != nil {
log.Printf("Error starting transaction: %v", err)
return 0, fmt.Errorf("error starting transaction: %v", err) return 0, fmt.Errorf("error starting transaction: %v", err)
} }
if err := tx.QueryRow(selectQuery).Scan(&numUsers); err != nil { if err := tx.QueryRow(selectQuery).Scan(&numUsers); err != nil {
log.Printf("Error retrieving number of users: %v", err)
if rollbackErr := tx.Rollback(); rollbackErr != nil { if rollbackErr := tx.Rollback(); rollbackErr != nil {
log.Fatalf("Transaction error: %v, Rollback error: %v", err, rollbackErr) log.Fatalf("transaction error: %v, rollback error: %v", err, rollbackErr)
} }
return 0, fmt.Errorf("error getting ID of %v: %v", u.UserName, err) return 0, fmt.Errorf("error getting ID of %v: %v", u.UserName, err)
} }
if numUsers != 0 { if numUsers != 0 {
if err = tx.Commit(); err != nil { if err = tx.Commit(); err != nil {
log.Printf("Error committing transaction: %v", err)
return 0, fmt.Errorf("error committing transaction: %v", err) return 0, fmt.Errorf("error committing transaction: %v", err)
} }
return -1, nil return -1, nil
@ -437,60 +387,53 @@ func (db *DB) AddFirstUser(c *Config, u *User, pass string) (int64, error) {
hashedPass, err := bcrypt.GenerateFromPassword([]byte(pass), bcrypt.DefaultCost) hashedPass, err := bcrypt.GenerateFromPassword([]byte(pass), bcrypt.DefaultCost)
if err != nil { if err != nil {
log.Printf("Error creating password hash: %v", err)
if rollbackErr := tx.Rollback(); rollbackErr != nil { if rollbackErr := tx.Rollback(); rollbackErr != nil {
log.Fatalf("Transaction error: %v, Rollback error: %v", err, rollbackErr) log.Fatalf("transaction error: %v, rollback error: %v", err, rollbackErr)
} }
return 0, fmt.Errorf("error creating password hash: %v", err) return 0, fmt.Errorf("error creating password hash: %v", err)
} }
aesFirstName, err := aesEncrypt(c, u.FirstName) aesFirstName, err := aesEncrypt(c, u.FirstName)
if err != nil { if err != nil {
log.Printf("Error encrypting first name: %v", err)
if rollbackErr := tx.Rollback(); rollbackErr != nil { if rollbackErr := tx.Rollback(); rollbackErr != nil {
log.Fatalf("Transaction error: %v, Rollback error: %v", err, rollbackErr) log.Fatalf("transaction error: %v, rollback error: %v", err, rollbackErr)
} }
return 0, fmt.Errorf("error encrypting first name: %v", err) return 0, fmt.Errorf("error encrypting first name: %v", err)
} }
aesLastName, err := aesEncrypt(c, u.LastName) aesLastName, err := aesEncrypt(c, u.LastName)
if err != nil { if err != nil {
log.Printf("Error encrypting last name: %v", err)
if rollbackErr := tx.Rollback(); rollbackErr != nil { if rollbackErr := tx.Rollback(); rollbackErr != nil {
log.Fatalf("Transaction error: %v, Rollback error: %v", err, rollbackErr) log.Fatalf("transaction error: %v, rollback error: %v", err, rollbackErr)
} }
return 0, fmt.Errorf("error encrypting last name: %v", err) return 0, fmt.Errorf("error encrypting last name: %v", err)
} }
aesEmail, err := aesEncrypt(c, u.Email) aesEmail, err := aesEncrypt(c, u.Email)
if err != nil { if err != nil {
log.Printf("Error encrypting email: %v", err)
if rollbackErr := tx.Rollback(); rollbackErr != nil { if rollbackErr := tx.Rollback(); rollbackErr != nil {
log.Fatalf("Transaction error: %v, Rollback error: %v", err, rollbackErr) log.Fatalf("transaction error: %v, rollback error: %v", err, rollbackErr)
} }
return 0, fmt.Errorf("error encrypting email: %v", err) return 0, fmt.Errorf("error encrypting email: %v", err)
} }
result, err := tx.Exec(insertQuery, u.UserName, string(hashedPass), aesFirstName, aesLastName, aesEmail, u.ProfilePicLink, u.Role) result, err := tx.Exec(insertQuery, u.UserName, string(hashedPass), aesFirstName, aesLastName, aesEmail, u.ProfilePicLink, u.Role)
if err != nil { if err != nil {
log.Printf("Error inserting new user '%v' into DB: %v", u.UserName, err)
if rollbackErr := tx.Rollback(); rollbackErr != nil { if rollbackErr := tx.Rollback(); rollbackErr != nil {
log.Fatalf("Transaction error: %v, Rollback error: %v", err, rollbackErr) log.Fatalf("transaction error: %v, rollback error: %v", err, rollbackErr)
} }
return 0, fmt.Errorf("error inserting new user %v into DB: %v", u.UserName, err) return 0, fmt.Errorf("error inserting new user %v into DB: %v", u.UserName, err)
} }
id, err := result.LastInsertId() id, err := result.LastInsertId()
if err != nil { if err != nil {
log.Printf("Error retrieving last insert ID: %v", err)
if rollbackErr := tx.Rollback(); rollbackErr != nil { if rollbackErr := tx.Rollback(); rollbackErr != nil {
log.Fatalf("Transaction error: %v, Rollback error: %v", err, rollbackErr) log.Fatalf("transaction error: %v, rollback error: %v", err, rollbackErr)
} }
return 0, fmt.Errorf("error inserting user into DB: %v", err) return 0, fmt.Errorf("error inserting user into DB: %v", err)
} }
if err = tx.Commit(); err != nil { if err = tx.Commit(); err != nil {
log.Printf("Error committing transaction: %v", err)
return 0, fmt.Errorf("error committing transaction: %v", err) return 0, fmt.Errorf("error committing transaction: %v", err)
} }
return id, nil return id, nil
@ -502,9 +445,7 @@ func (db *DB) AddFirstUser(c *Config, u *User, pass string) (int64, error) {
log.Println(err) log.Println(err)
wait(i) wait(i)
} }
errMsg := fmt.Sprintf("error: %v unsuccessful retries for DB operation, aborting", TxMaxRetries) return 0, fmt.Errorf("error: %v unsuccessful retries for DB operation, aborting", TxMaxRetries)
log.Println(errMsg)
return 0, fmt.Errorf(errMsg)
} }
func (db *DB) GetAllUsers(c *Config) ([]*User, error) { func (db *DB) GetAllUsers(c *Config) ([]*User, error) {
@ -515,45 +456,34 @@ func (db *DB) GetAllUsers(c *Config) ([]*User, error) {
rows, err := db.Query(query) rows, err := db.Query(query)
if err != nil { if err != nil {
log.Printf("Error retrieving all users from DB: %v", err)
return nil, fmt.Errorf("error getting all users from DB: %v", err) return nil, fmt.Errorf("error getting all users from DB: %v", err)
} }
defer rows.Close()
users := make([]*User, 0) users := make([]*User, 0)
for rows.Next() { for rows.Next() {
user := new(User) user := new(User)
if err = rows.Scan(&user.ID, &user.UserName, &aesFirstName, &aesLastName, &aesEmail, &user.ProfilePicLink, &user.Role); err != nil { if err = rows.Scan(&user.ID, &user.UserName, &aesFirstName, &aesLastName, &aesEmail, &user.ProfilePicLink, &user.Role); err != nil {
log.Printf("Error scanning user information: %v", err)
return nil, fmt.Errorf("error getting user info: %v", err) return nil, fmt.Errorf("error getting user info: %v", err)
} }
user.FirstName, err = aesDecrypt(c, aesFirstName) user.FirstName, err = aesDecrypt(c, aesFirstName)
if err != nil { if err != nil {
log.Printf("Error decrypting first name for user ID '%v': %v", user.ID, err)
return nil, fmt.Errorf("error decrypting first name: %v", err) return nil, fmt.Errorf("error decrypting first name: %v", err)
} }
user.LastName, err = aesDecrypt(c, aesLastName) user.LastName, err = aesDecrypt(c, aesLastName)
if err != nil { if err != nil {
log.Printf("Error decrypting last name for user ID '%v': %v", user.ID, err)
return nil, fmt.Errorf("error decrypting last name: %v", err) return nil, fmt.Errorf("error decrypting last name: %v", err)
} }
user.Email, err = aesDecrypt(c, aesEmail) user.Email, err = aesDecrypt(c, aesEmail)
if err != nil { if err != nil {
log.Printf("Error decrypting email for user ID '%v': %v", user.ID, err)
return nil, fmt.Errorf("error decrypting email: %v", err) return nil, fmt.Errorf("error decrypting email: %v", err)
} }
users = append(users, user) users = append(users, user)
} }
if err = rows.Err(); err != nil {
log.Printf("Error iterating over rows: %v", err)
return nil, fmt.Errorf("error iterating over rows: %v", err)
}
return users, nil return users, nil
} }
@ -565,63 +495,50 @@ func (db *DB) GetAllUsersMap(c *Config) (map[int64]*User, error) {
rows, err := db.Query(query) rows, err := db.Query(query)
if err != nil { if err != nil {
log.Printf("Error retrieving all users from DB: %v", err)
return nil, fmt.Errorf("error getting all users from DB: %v", err) return nil, fmt.Errorf("error getting all users from DB: %v", err)
} }
defer rows.Close()
users := make(map[int64]*User) users := make(map[int64]*User, 0)
for rows.Next() { for rows.Next() {
user := new(User) user := new(User)
if err = rows.Scan(&user.ID, &user.UserName, &aesFirstName, &aesLastName, &aesEmail, &user.ProfilePicLink, &user.Role); err != nil { if err = rows.Scan(&user.ID, &user.UserName, &aesFirstName, &aesLastName, &aesEmail, &user.ProfilePicLink, &user.Role); err != nil {
log.Printf("Error scanning user information: %v", err)
return nil, fmt.Errorf("error getting user info: %v", err) return nil, fmt.Errorf("error getting user info: %v", err)
} }
user.FirstName, err = aesDecrypt(c, aesFirstName) user.FirstName, err = aesDecrypt(c, aesFirstName)
if err != nil { if err != nil {
log.Printf("Error decrypting first name for user ID '%v': %v", user.ID, err)
return nil, fmt.Errorf("error decrypting first name: %v", err) return nil, fmt.Errorf("error decrypting first name: %v", err)
} }
user.LastName, err = aesDecrypt(c, aesLastName) user.LastName, err = aesDecrypt(c, aesLastName)
if err != nil { if err != nil {
log.Printf("Error decrypting last name for user ID '%v': %v", user.ID, err)
return nil, fmt.Errorf("error decrypting last name: %v", err) return nil, fmt.Errorf("error decrypting last name: %v", err)
} }
user.Email, err = aesDecrypt(c, aesEmail) user.Email, err = aesDecrypt(c, aesEmail)
if err != nil { if err != nil {
log.Printf("Error decrypting email for user ID '%v': %v", user.ID, err)
return nil, fmt.Errorf("error decrypting email: %v", err) return nil, fmt.Errorf("error decrypting email: %v", err)
} }
users[user.ID] = user users[user.ID] = user
} }
if err = rows.Err(); err != nil {
log.Printf("Error iterating over rows: %v", err)
return nil, fmt.Errorf("error iterating over rows: %v", err)
}
return users, nil return users, nil
} }
func (tx *Tx) SetPassword(id int64, newPass string) error { func (tx *Tx) SetPassword(id int64, newPass string) error {
hashedPass, err := bcrypt.GenerateFromPassword([]byte(newPass), bcrypt.DefaultCost) hashedPass, err := bcrypt.GenerateFromPassword([]byte(newPass), bcrypt.DefaultCost)
if err != nil { if err != nil {
log.Printf("Error creating password hash for ID '%v': %v", id, err)
if rollbackErr := tx.Rollback(); rollbackErr != nil { if rollbackErr := tx.Rollback(); rollbackErr != nil {
log.Fatalf("Transaction error: %v, Rollback error: %v", err, rollbackErr) log.Fatalf("transaction error: %v, rollback error: %v", err, rollbackErr)
} }
return fmt.Errorf("error creating password hash: %v", err) return fmt.Errorf("error creating password hash: %v", err)
} }
setQuery := "UPDATE users SET password = ? WHERE id = ?" setQuery := "UPDATE users SET password = ? WHERE id = ?"
if _, err = tx.Exec(setQuery, string(hashedPass), id); err != nil { if _, err = tx.Exec(setQuery, string(hashedPass), id); err != nil {
log.Printf("Error updating password in DB for ID '%v': %v", id, err)
if rollbackErr := tx.Rollback(); rollbackErr != nil { if rollbackErr := tx.Rollback(); rollbackErr != nil {
log.Fatalf("Transaction error: %v, Rollback error: %v", err, rollbackErr) log.Fatalf("transaction error: %v, rollback error: %v", err, rollbackErr)
} }
return fmt.Errorf("error updating password in DB: %v", err) return fmt.Errorf("error updating password in DB: %v", err)
} }
@ -638,15 +555,13 @@ func (db *DB) UpdateUserAttributes(c *Config, id int64, userName, firstName, las
err := func() error { err := func() error {
tx.Tx, err = db.Begin() tx.Tx, err = db.Begin()
if err != nil { if err != nil {
log.Printf("Error starting transaction: %v", err)
return fmt.Errorf("error starting transaction: %v", err) return fmt.Errorf("error starting transaction: %v", err)
} }
if !passwordEmpty { if !passwordEmpty {
if err = tx.SetPassword(id, newPass); err != nil { if err = tx.SetPassword(id, newPass); err != nil {
log.Printf("Error setting new password for ID '%v': %v", id, err)
if rollbackErr := tx.Rollback(); rollbackErr != nil { if rollbackErr := tx.Rollback(); rollbackErr != nil {
log.Fatalf("Transaction error: %v, Rollback error: %v", err, rollbackErr) log.Fatalf("transaction error: %v, rollback error: %v", err, rollbackErr)
} }
return fmt.Errorf("error changing password: %v", err) return fmt.Errorf("error changing password: %v", err)
} }
@ -654,27 +569,24 @@ func (db *DB) UpdateUserAttributes(c *Config, id int64, userName, firstName, las
aesFirstName, err := aesEncrypt(c, firstName) aesFirstName, err := aesEncrypt(c, firstName)
if err != nil { if err != nil {
log.Printf("Error encrypting first name for ID '%v': %v", id, err)
if rollbackErr := tx.Rollback(); rollbackErr != nil { if rollbackErr := tx.Rollback(); rollbackErr != nil {
log.Fatalf("Transaction error: %v, Rollback error: %v", err, rollbackErr) log.Fatalf("transaction error: %v, rollback error: %v", err, rollbackErr)
} }
return fmt.Errorf("error encrypting first name: %v", err) return fmt.Errorf("error encrypting first name: %v", err)
} }
aesLastName, err := aesEncrypt(c, lastName) aesLastName, err := aesEncrypt(c, lastName)
if err != nil { if err != nil {
log.Printf("Error encrypting last name for ID '%v': %v", id, err)
if rollbackErr := tx.Rollback(); rollbackErr != nil { if rollbackErr := tx.Rollback(); rollbackErr != nil {
log.Fatalf("Transaction error: %v, Rollback error: %v", err, rollbackErr) log.Fatalf("transaction error: %v, rollback error: %v", err, rollbackErr)
} }
return fmt.Errorf("error encrypting last name: %v", err) return fmt.Errorf("error encrypting last name: %v", err)
} }
aesEmail, err := aesEncrypt(c, email) aesEmail, err := aesEncrypt(c, email)
if err != nil { if err != nil {
log.Printf("Error encrypting email for ID '%v': %v", id, err)
if rollbackErr := tx.Rollback(); rollbackErr != nil { if rollbackErr := tx.Rollback(); rollbackErr != nil {
log.Fatalf("Transaction error: %v, Rollback error: %v", err, rollbackErr) log.Fatalf("transaction error: %v, rollback error: %v", err, rollbackErr)
} }
return fmt.Errorf("error encrypting email: %v", err) return fmt.Errorf("error encrypting email: %v", err)
} }
@ -687,15 +599,13 @@ func (db *DB) UpdateUserAttributes(c *Config, id int64, userName, firstName, las
&Attribute{Table: "users", ID: id, AttName: "profile_pic_link", Value: profilePicLink}, &Attribute{Table: "users", ID: id, AttName: "profile_pic_link", Value: profilePicLink},
&Attribute{Table: "users", ID: id, AttName: "role", Value: role}, &Attribute{Table: "users", ID: id, AttName: "role", Value: role},
); err != nil { ); err != nil {
log.Printf("Error updating attributes in DB for ID '%v': %v", id, err)
if rollbackErr := tx.Rollback(); rollbackErr != nil { if rollbackErr := tx.Rollback(); rollbackErr != nil {
log.Fatalf("Transaction error: %v, Rollback error: %v", err, rollbackErr) log.Fatalf("transaction error: %v, rollback error: %v", err, rollbackErr)
} }
return fmt.Errorf("error updating attributes in DB: %v", err) return fmt.Errorf("error updating attributes in DB: %v", err)
} }
if err = tx.Commit(); err != nil { if err = tx.Commit(); err != nil {
log.Printf("Error committing transaction for ID '%v': %v", id, err)
return fmt.Errorf("error committing transaction: %v", err) return fmt.Errorf("error committing transaction: %v", err)
} }
@ -709,9 +619,7 @@ func (db *DB) UpdateUserAttributes(c *Config, id int64, userName, firstName, las
wait(i) wait(i)
} }
errMsg := fmt.Sprintf("error: %v unsuccessful retries for DB operation, aborting", TxMaxRetries) return fmt.Errorf("error: %v unsuccessful retries for DB operation, aborting", TxMaxRetries)
log.Println(errMsg)
return fmt.Errorf(errMsg)
} }
func (db *DB) DeleteUser(id int64) error { func (db *DB) DeleteUser(id int64) error {
@ -719,7 +627,6 @@ func (db *DB) DeleteUser(id int64) error {
_, err := db.Exec(query, id) _, err := db.Exec(query, id)
if err != nil { if err != nil {
log.Printf("Error deleting user with ID '%v' from DB: %v", id, err)
return fmt.Errorf("error deleting user %v from DB: %v", id, err) return fmt.Errorf("error deleting user %v from DB: %v", id, err)
} }

View File

@ -5,6 +5,7 @@ import (
"log" "log"
"net/http" "net/http"
"os" "os"
"path/filepath"
"github.com/google/uuid" "github.com/google/uuid"
b "streifling.com/jason/cpolis/cmd/backend" b "streifling.com/jason/cpolis/cmd/backend"
@ -56,8 +57,8 @@ func ServeArticle(c *b.Config, db *b.DB) http.HandlerFunc {
return return
} }
articleAbsName := fmt.Sprint(c.ArticleDir, "/", article.UUID, ".md") articlePath := filepath.Join(c.ArticleDir, fmt.Sprint(article.UUID, ".md"))
contentBytes, err := os.ReadFile(articleAbsName) contentBytes, err := os.ReadFile(articlePath)
if err != nil { if err != nil {
log.Println(err) log.Println(err)
http.Error(w, err.Error(), http.StatusInternalServerError) http.Error(w, err.Error(), http.StatusInternalServerError)

View File

@ -10,12 +10,12 @@ import (
func ServeImage(c *b.Config, s map[string]*f.Session) http.HandlerFunc { func ServeImage(c *b.Config, s map[string]*f.Session) http.HandlerFunc {
return func(w http.ResponseWriter, r *http.Request) { return func(w http.ResponseWriter, r *http.Request) {
if _, err := f.ManageSession(w, r, c, s); err != nil { if !f.SessionIsActive(r, s) {
if !tokenIsVerified(w, r, c) { if !tokenIsVerified(w, r, c) {
return return
} }
} }
http.ServeFile(w, r, filepath.Join(c.PicsDir, r.PathValue("pic"))) http.ServeFile(w, r, filepath.Join(c.ImgDir, r.PathValue("pic")))
} }
} }

View File

@ -5,6 +5,7 @@ import (
"log" "log"
"net/http" "net/http"
"os" "os"
"path/filepath"
b "streifling.com/jason/cpolis/cmd/backend" b "streifling.com/jason/cpolis/cmd/backend"
) )
@ -42,6 +43,6 @@ func ServePDF(c *b.Config) http.HandlerFunc {
return return
} }
http.ServeFile(w, r, c.PDFDir+"/"+r.PathValue("id")) http.ServeFile(w, r, filepath.Join(c.PDFDir, r.PathValue("id")))
} }
} }

View File

@ -456,8 +456,8 @@ func ReviewRejectedArticle(c *b.Config, db *b.DB, s map[string]*Session) http.Ha
data.Image = data.Article.BannerLink data.Image = data.Article.BannerLink
articleAbsName := fmt.Sprint(c.ArticleDir, "/", data.Article.UUID, ".md") articlePath := filepath.Join(c.ArticleDir, fmt.Sprint(data.Article.UUID, ".md"))
content, err := os.ReadFile(articleAbsName) content, err := os.ReadFile(articlePath)
if err != nil { if err != nil {
log.Println(err) log.Println(err)
http.Error(w, err.Error(), http.StatusInternalServerError) http.Error(w, err.Error(), http.StatusInternalServerError)
@ -587,7 +587,7 @@ func PublishArticle(c *b.Config, db *b.DB, s map[string]*Session) http.HandlerFu
return return
} }
if err = os.Remove(fmt.Sprint(c.ArticleDir, "/", oldArticle.UUID, ".md")); err != nil { if err = os.Remove(filepath.Join(c.ArticleDir, fmt.Sprint(oldArticle.UUID, ".md"))); err != nil {
log.Println(err) log.Println(err)
http.Error(w, err.Error(), http.StatusInternalServerError) http.Error(w, err.Error(), http.StatusInternalServerError)
return return
@ -765,8 +765,8 @@ func ReviewArticle(c *b.Config, db *b.DB, s map[string]*Session, action, title,
return return
} }
articleAbsName := fmt.Sprint(c.ArticleDir, "/", article.UUID, ".md") articlePath := filepath.Join(c.ArticleDir, fmt.Sprint(article.UUID, ".md"))
content, err := os.ReadFile(articleAbsName) content, err := os.ReadFile(articlePath)
if err != nil { if err != nil {
log.Println(err) log.Println(err)
http.Error(w, err.Error(), http.StatusInternalServerError) http.Error(w, err.Error(), http.StatusInternalServerError)
@ -840,7 +840,7 @@ func DeleteArticle(c *b.Config, db *b.DB, s map[string]*Session) http.HandlerFun
return return
} }
if err = os.Remove(fmt.Sprint(c.ArticleDir, "/", article.UUID, ".md")); err != nil { if err = os.Remove(filepath.Join(c.ArticleDir, fmt.Sprint(article.UUID, ".md"))); err != nil {
log.Println(err) log.Println(err)
http.Error(w, err.Error(), http.StatusInternalServerError) http.Error(w, err.Error(), http.StatusInternalServerError)
return return
@ -918,8 +918,8 @@ func AllowEditArticle(c *b.Config, db *b.DB, s map[string]*Session) http.Handler
return return
} }
src := fmt.Sprint(c.ArticleDir, "/", oldArticle.UUID, ".md") src := filepath.Join(c.ArticleDir, fmt.Sprint(oldArticle.UUID, ".md"))
dst := fmt.Sprint(c.ArticleDir, "/", newArticle.UUID, ".md") dst := filepath.Join(c.ArticleDir, fmt.Sprint(newArticle.UUID, ".md"))
if err = b.CopyFile(src, dst); err != nil { if err = b.CopyFile(src, dst); err != nil {
log.Println(err) log.Println(err)
http.Error(w, err.Error(), http.StatusInternalServerError) http.Error(w, err.Error(), http.StatusInternalServerError)
@ -995,7 +995,7 @@ func EditArticle(c *b.Config, db *b.DB, s map[string]*Session) http.HandlerFunc
data.Image = data.Article.BannerLink data.Image = data.Article.BannerLink
content, err := os.ReadFile(fmt.Sprint(c.ArticleDir, "/", data.Article.UUID, ".md")) content, err := os.ReadFile(filepath.Join(c.ArticleDir, fmt.Sprint(data.Article.UUID, ".md")))
if err != nil { if err != nil {
log.Println(err) log.Println(err)
http.Error(w, err.Error(), http.StatusInternalServerError) http.Error(w, err.Error(), http.StatusInternalServerError)

View File

@ -25,7 +25,7 @@ func UploadEasyMDEImage(c *b.Config, s map[string]*Session) http.HandlerFunc {
} }
defer file.Close() defer file.Close()
filename, err := b.SaveImage(file, c.MaxImgHeight, c.MaxImgWidth, c.PicsDir+"/") filename, err := b.SaveImage(file, c.MaxImgHeight, c.MaxImgWidth, c.ImgDir)
if err != nil { if err != nil {
if err == b.ErrUnsupportedFormat { if err == b.ErrUnsupportedFormat {
http.Error(w, "Das Dateiformat wird nicht unterstützt.", http.StatusBadRequest) http.Error(w, "Das Dateiformat wird nicht unterstützt.", http.StatusBadRequest)
@ -57,7 +57,7 @@ func UploadImage(c *b.Config, s map[string]*Session, fileKey, htmlFile, htmlTemp
} }
defer file.Close() defer file.Close()
filename, err := b.SaveImage(file, c.MaxBannerHeight, c.MaxBannerWidth, c.PicsDir+"/") filename, err := b.SaveImage(file, c.MaxBannerHeight, c.MaxBannerWidth, c.ImgDir)
if err != nil { if err != nil {
if err == b.ErrUnsupportedFormat { if err == b.ErrUnsupportedFormat {
http.Error(w, "Das Dateiformat wird nicht unterstützt.", http.StatusBadRequest) http.Error(w, "Das Dateiformat wird nicht unterstützt.", http.StatusBadRequest)

View File

@ -58,8 +58,8 @@ func PublishLatestIssue(c *b.Config, db *b.DB, s map[string]*Session) http.Handl
return return
} }
articleAbsName := fmt.Sprint(c.ArticleDir, "/", article.UUID, ".md") articlePath := filepath.Join(c.ArticleDir, fmt.Sprint(article.UUID, ".md"))
if err = os.WriteFile(articleAbsName, content, 0644); err != nil { if err = os.WriteFile(articlePath, content, 0644); err != nil {
log.Println(err) log.Println(err)
http.Error(w, err.Error(), http.StatusInternalServerError) http.Error(w, err.Error(), http.StatusInternalServerError)
return return

View File

@ -64,6 +64,18 @@ func StartSessions() (map[string]*Session, chan string) {
return sessions, sessionExpiryChan return sessions, sessionExpiryChan
} }
// SessionIsActive is used for verifying that the user is logged in and returns
// a bool.
func SessionIsActive(r *http.Request, s map[string]*Session) bool {
cookie, err := r.Cookie("cpolis_session")
if err != nil {
return false
}
_, ok := s[cookie.Value]
return ok
}
// ManageSession is used for verifying that the user is logged in and returns // ManageSession is used for verifying that the user is logged in and returns
// their session and an error. It also handles cases where the user is not // their session and an error. It also handles cases where the user is not
// logged in. // logged in.

36
go.mod
View File

@ -4,7 +4,7 @@ go 1.23.2
require ( require (
firebase.google.com/go/v4 v4.15.1 firebase.google.com/go/v4 v4.15.1
git.streifling.com/jason/atom v1.0.0 git.streifling.com/jason/atom v1.0.1
github.com/BurntSushi/toml v1.4.0 github.com/BurntSushi/toml v1.4.0
github.com/chai2010/webp v1.1.1 github.com/chai2010/webp v1.1.1
github.com/disintegration/imaging v1.6.2 github.com/disintegration/imaging v1.6.2
@ -15,7 +15,7 @@ require (
github.com/yuin/goldmark v1.7.8 github.com/yuin/goldmark v1.7.8
golang.org/x/crypto v0.32.0 golang.org/x/crypto v0.32.0
golang.org/x/term v0.28.0 golang.org/x/term v0.28.0
google.golang.org/api v0.216.0 google.golang.org/api v0.218.0
) )
require ( require (
@ -27,7 +27,7 @@ require (
cloud.google.com/go/firestore v1.18.0 // indirect cloud.google.com/go/firestore v1.18.0 // indirect
cloud.google.com/go/iam v1.3.1 // indirect cloud.google.com/go/iam v1.3.1 // indirect
cloud.google.com/go/longrunning v0.6.4 // indirect cloud.google.com/go/longrunning v0.6.4 // indirect
cloud.google.com/go/monitoring v1.22.1 // indirect cloud.google.com/go/monitoring v1.23.0 // indirect
cloud.google.com/go/storage v1.50.0 // indirect cloud.google.com/go/storage v1.50.0 // indirect
filippo.io/edwards25519 v1.1.0 // indirect filippo.io/edwards25519 v1.1.0 // indirect
github.com/GoogleCloudPlatform/opentelemetry-operations-go/detectors/gcp v1.25.0 // indirect github.com/GoogleCloudPlatform/opentelemetry-operations-go/detectors/gcp v1.25.0 // indirect
@ -36,9 +36,9 @@ require (
github.com/MicahParks/keyfunc v1.9.0 // indirect github.com/MicahParks/keyfunc v1.9.0 // indirect
github.com/aymerick/douceur v0.2.0 // indirect github.com/aymerick/douceur v0.2.0 // indirect
github.com/cespare/xxhash/v2 v2.3.0 // indirect github.com/cespare/xxhash/v2 v2.3.0 // indirect
github.com/cncf/xds/go v0.0.0-20241223141626-cff3c89139a3 // indirect github.com/cncf/xds/go v0.0.0-20250121191232-2f005788dc42 // indirect
github.com/envoyproxy/go-control-plane/envoy v1.32.3 // indirect github.com/envoyproxy/go-control-plane/envoy v1.32.3 // indirect
github.com/envoyproxy/protoc-gen-validate v1.1.0 // indirect github.com/envoyproxy/protoc-gen-validate v1.2.1 // indirect
github.com/felixge/httpsnoop v1.0.4 // indirect github.com/felixge/httpsnoop v1.0.4 // indirect
github.com/go-logr/logr v1.4.2 // indirect github.com/go-logr/logr v1.4.2 // indirect
github.com/go-logr/stdr v1.2.2 // indirect github.com/go-logr/stdr v1.2.2 // indirect
@ -51,14 +51,14 @@ require (
github.com/gorilla/css v1.0.1 // indirect github.com/gorilla/css v1.0.1 // indirect
github.com/planetscale/vtprotobuf v0.6.1-0.20240319094008-0393e58bdf10 // indirect github.com/planetscale/vtprotobuf v0.6.1-0.20240319094008-0393e58bdf10 // indirect
go.opentelemetry.io/auto/sdk v1.1.0 // indirect go.opentelemetry.io/auto/sdk v1.1.0 // indirect
go.opentelemetry.io/contrib/detectors/gcp v1.33.0 // indirect go.opentelemetry.io/contrib/detectors/gcp v1.34.0 // indirect
go.opentelemetry.io/contrib/instrumentation/google.golang.org/grpc/otelgrpc v0.58.0 // indirect go.opentelemetry.io/contrib/instrumentation/google.golang.org/grpc/otelgrpc v0.59.0 // indirect
go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.58.0 // indirect go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.59.0 // indirect
go.opentelemetry.io/otel v1.33.0 // indirect go.opentelemetry.io/otel v1.34.0 // indirect
go.opentelemetry.io/otel/metric v1.33.0 // indirect go.opentelemetry.io/otel/metric v1.34.0 // indirect
go.opentelemetry.io/otel/sdk v1.33.0 // indirect go.opentelemetry.io/otel/sdk v1.34.0 // indirect
go.opentelemetry.io/otel/sdk/metric v1.33.0 // indirect go.opentelemetry.io/otel/sdk/metric v1.34.0 // indirect
go.opentelemetry.io/otel/trace v1.33.0 // indirect go.opentelemetry.io/otel/trace v1.34.0 // indirect
golang.org/x/image v0.23.0 // indirect golang.org/x/image v0.23.0 // indirect
golang.org/x/net v0.34.0 // indirect golang.org/x/net v0.34.0 // indirect
golang.org/x/oauth2 v0.25.0 // indirect golang.org/x/oauth2 v0.25.0 // indirect
@ -67,9 +67,9 @@ require (
golang.org/x/text v0.21.0 // indirect golang.org/x/text v0.21.0 // indirect
golang.org/x/time v0.9.0 // indirect golang.org/x/time v0.9.0 // indirect
google.golang.org/appengine/v2 v2.0.6 // indirect google.golang.org/appengine/v2 v2.0.6 // indirect
google.golang.org/genproto v0.0.0-20250106144421-5f5ef82da422 // indirect google.golang.org/genproto v0.0.0-20250124145028-65684f501c47 // indirect
google.golang.org/genproto/googleapis/api v0.0.0-20250106144421-5f5ef82da422 // indirect google.golang.org/genproto/googleapis/api v0.0.0-20250124145028-65684f501c47 // indirect
google.golang.org/genproto/googleapis/rpc v0.0.0-20250106144421-5f5ef82da422 // indirect google.golang.org/genproto/googleapis/rpc v0.0.0-20250124145028-65684f501c47 // indirect
google.golang.org/grpc v1.69.2 // indirect google.golang.org/grpc v1.70.0 // indirect
google.golang.org/protobuf v1.36.2 // indirect google.golang.org/protobuf v1.36.4 // indirect
) )

72
go.sum
View File

@ -16,8 +16,8 @@ cloud.google.com/go/logging v1.13.0 h1:7j0HgAp0B94o1YRDqiqm26w4q1rDMH7XNRU34lJXH
cloud.google.com/go/logging v1.13.0/go.mod h1:36CoKh6KA/M0PbhPKMq6/qety2DCAErbhXT62TuXALA= cloud.google.com/go/logging v1.13.0/go.mod h1:36CoKh6KA/M0PbhPKMq6/qety2DCAErbhXT62TuXALA=
cloud.google.com/go/longrunning v0.6.4 h1:3tyw9rO3E2XVXzSApn1gyEEnH2K9SynNQjMlBi3uHLg= cloud.google.com/go/longrunning v0.6.4 h1:3tyw9rO3E2XVXzSApn1gyEEnH2K9SynNQjMlBi3uHLg=
cloud.google.com/go/longrunning v0.6.4/go.mod h1:ttZpLCe6e7EXvn9OxpBRx7kZEB0efv8yBO6YnVMfhJs= cloud.google.com/go/longrunning v0.6.4/go.mod h1:ttZpLCe6e7EXvn9OxpBRx7kZEB0efv8yBO6YnVMfhJs=
cloud.google.com/go/monitoring v1.22.1 h1:KQbnAC4IAH+5x3iWuPZT5iN9VXqKMzzOgqcYB6fqPDE= cloud.google.com/go/monitoring v1.23.0 h1:M3nXww2gn9oZ/qWN2bZ35CjolnVHM3qnSbu6srCPgjk=
cloud.google.com/go/monitoring v1.22.1/go.mod h1:AuZZXAoN0WWWfsSvET1Cpc4/1D8LXq8KRDU87fMS6XY= cloud.google.com/go/monitoring v1.23.0/go.mod h1:034NnlQPDzrQ64G2Gavhl0LUHZs9H3rRmhtnp7jiJgg=
cloud.google.com/go/storage v1.50.0 h1:3TbVkzTooBvnZsk7WaAQfOsNrdoM8QHusXA1cpk6QJs= cloud.google.com/go/storage v1.50.0 h1:3TbVkzTooBvnZsk7WaAQfOsNrdoM8QHusXA1cpk6QJs=
cloud.google.com/go/storage v1.50.0/go.mod h1:l7XeiD//vx5lfqE3RavfmU9yvk5Pp0Zhcv482poyafY= cloud.google.com/go/storage v1.50.0/go.mod h1:l7XeiD//vx5lfqE3RavfmU9yvk5Pp0Zhcv482poyafY=
cloud.google.com/go/trace v1.11.3 h1:c+I4YFjxRQjvAhRmSsmjpASUKq88chOX854ied0K/pE= cloud.google.com/go/trace v1.11.3 h1:c+I4YFjxRQjvAhRmSsmjpASUKq88chOX854ied0K/pE=
@ -26,8 +26,8 @@ filippo.io/edwards25519 v1.1.0 h1:FNf4tywRC1HmFuKW5xopWpigGjJKiJSV0Cqo0cJWDaA=
filippo.io/edwards25519 v1.1.0/go.mod h1:BxyFTGdWcka3PhytdK4V28tE5sGfRvvvRV7EaN4VDT4= filippo.io/edwards25519 v1.1.0/go.mod h1:BxyFTGdWcka3PhytdK4V28tE5sGfRvvvRV7EaN4VDT4=
firebase.google.com/go/v4 v4.15.1 h1:tR2dzKw1MIfCfG2bhAyxa5KQ57zcE7iFKmeYClET6ZM= firebase.google.com/go/v4 v4.15.1 h1:tR2dzKw1MIfCfG2bhAyxa5KQ57zcE7iFKmeYClET6ZM=
firebase.google.com/go/v4 v4.15.1/go.mod h1:eunxbsh4UXI2rA8po3sOiebvWYuW0DVxAdZFO0I6wdY= firebase.google.com/go/v4 v4.15.1/go.mod h1:eunxbsh4UXI2rA8po3sOiebvWYuW0DVxAdZFO0I6wdY=
git.streifling.com/jason/atom v1.0.0 h1:E88z4S7JeT6T+WuAaJWnGwCWTx+vzSJ6giUL51MdptI= git.streifling.com/jason/atom v1.0.1 h1:G1PtNt1+qlzxpwjlD6iDeseFmzoac1IYxdq9twofTFY=
git.streifling.com/jason/atom v1.0.0/go.mod h1:FNTYJfatYaIOQn4OKy8y+Mtohqm3MeyEGZUu4bMtZ9E= git.streifling.com/jason/atom v1.0.1/go.mod h1:FNTYJfatYaIOQn4OKy8y+Mtohqm3MeyEGZUu4bMtZ9E=
github.com/BurntSushi/toml v1.4.0 h1:kuoIxZQy2WRRk1pttg9asf+WVv6tWQuBNVmK8+nqPr0= github.com/BurntSushi/toml v1.4.0 h1:kuoIxZQy2WRRk1pttg9asf+WVv6tWQuBNVmK8+nqPr0=
github.com/BurntSushi/toml v1.4.0/go.mod h1:ukJfTF/6rtPPRCnwkur4qwRxa8vTRFBF0uk2lLoLwho= github.com/BurntSushi/toml v1.4.0/go.mod h1:ukJfTF/6rtPPRCnwkur4qwRxa8vTRFBF0uk2lLoLwho=
github.com/GoogleCloudPlatform/opentelemetry-operations-go/detectors/gcp v1.25.0 h1:3c8yed4lgqTt+oTQ+JNMDo+F4xprBf+O/il4ZC0nRLw= github.com/GoogleCloudPlatform/opentelemetry-operations-go/detectors/gcp v1.25.0 h1:3c8yed4lgqTt+oTQ+JNMDo+F4xprBf+O/il4ZC0nRLw=
@ -46,8 +46,8 @@ github.com/cespare/xxhash/v2 v2.3.0 h1:UL815xU9SqsFlibzuggzjXhog7bL6oX9BbNZnL2UF
github.com/cespare/xxhash/v2 v2.3.0/go.mod h1:VGX0DQ3Q6kWi7AoAeZDth3/j3BFtOZR5XLFGgcrjCOs= github.com/cespare/xxhash/v2 v2.3.0/go.mod h1:VGX0DQ3Q6kWi7AoAeZDth3/j3BFtOZR5XLFGgcrjCOs=
github.com/chai2010/webp v1.1.1 h1:jTRmEccAJ4MGrhFOrPMpNGIJ/eybIgwKpcACsrTEapk= github.com/chai2010/webp v1.1.1 h1:jTRmEccAJ4MGrhFOrPMpNGIJ/eybIgwKpcACsrTEapk=
github.com/chai2010/webp v1.1.1/go.mod h1:0XVwvZWdjjdxpUEIf7b9g9VkHFnInUSYujwqTLEuldU= github.com/chai2010/webp v1.1.1/go.mod h1:0XVwvZWdjjdxpUEIf7b9g9VkHFnInUSYujwqTLEuldU=
github.com/cncf/xds/go v0.0.0-20241223141626-cff3c89139a3 h1:boJj011Hh+874zpIySeApCX4GeOjPl9qhRF3QuIZq+Q= github.com/cncf/xds/go v0.0.0-20250121191232-2f005788dc42 h1:Om6kYQYDUk5wWbT0t0q6pvyM49i9XZAv9dDrkDA7gjk=
github.com/cncf/xds/go v0.0.0-20241223141626-cff3c89139a3/go.mod h1:W+zGtBO5Y1IgJhy4+A9GOqVhqLpfZi+vwmdNXUehLA8= github.com/cncf/xds/go v0.0.0-20250121191232-2f005788dc42/go.mod h1:W+zGtBO5Y1IgJhy4+A9GOqVhqLpfZi+vwmdNXUehLA8=
github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c= github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c=
github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
github.com/disintegration/imaging v1.6.2 h1:w1LecBlG2Lnp8B3jk5zSuNqd7b4DXhcjwek1ei82L+c= github.com/disintegration/imaging v1.6.2 h1:w1LecBlG2Lnp8B3jk5zSuNqd7b4DXhcjwek1ei82L+c=
@ -58,8 +58,8 @@ github.com/envoyproxy/go-control-plane/envoy v1.32.3 h1:hVEaommgvzTjTd4xCaFd+kEQ
github.com/envoyproxy/go-control-plane/envoy v1.32.3/go.mod h1:F6hWupPfh75TBXGKA++MCT/CZHFq5r9/uwt/kQYkZfE= github.com/envoyproxy/go-control-plane/envoy v1.32.3/go.mod h1:F6hWupPfh75TBXGKA++MCT/CZHFq5r9/uwt/kQYkZfE=
github.com/envoyproxy/go-control-plane/ratelimit v0.1.0 h1:/G9QYbddjL25KvtKTv3an9lx6VBE2cnb8wp1vEGNYGI= github.com/envoyproxy/go-control-plane/ratelimit v0.1.0 h1:/G9QYbddjL25KvtKTv3an9lx6VBE2cnb8wp1vEGNYGI=
github.com/envoyproxy/go-control-plane/ratelimit v0.1.0/go.mod h1:Wk+tMFAFbCXaJPzVVHnPgRKdUdwW/KdbRt94AzgRee4= github.com/envoyproxy/go-control-plane/ratelimit v0.1.0/go.mod h1:Wk+tMFAFbCXaJPzVVHnPgRKdUdwW/KdbRt94AzgRee4=
github.com/envoyproxy/protoc-gen-validate v1.1.0 h1:tntQDh69XqOCOZsDz0lVJQez/2L6Uu2PdjCQwWCJ3bM= github.com/envoyproxy/protoc-gen-validate v1.2.1 h1:DEo3O99U8j4hBFwbJfrz9VtgcDfUKS7KJ7spH3d86P8=
github.com/envoyproxy/protoc-gen-validate v1.1.0/go.mod h1:sXRDRVmzEbkM7CVcM06s9shE/m23dg3wzjl0UWqJ2q4= github.com/envoyproxy/protoc-gen-validate v1.2.1/go.mod h1:d/C80l/jxXLdfEIhX1W2TmLfsJ31lvEjwamM4DxlWXU=
github.com/felixge/httpsnoop v1.0.4 h1:NFTV2Zj1bL4mc9sqWACXbQFVBBg2W3GPvqp8/ESS2Wg= github.com/felixge/httpsnoop v1.0.4 h1:NFTV2Zj1bL4mc9sqWACXbQFVBBg2W3GPvqp8/ESS2Wg=
github.com/felixge/httpsnoop v1.0.4/go.mod h1:m8KPJKqk1gH5J9DgRY2ASl2lWCfGKXixSwevea8zH2U= github.com/felixge/httpsnoop v1.0.4/go.mod h1:m8KPJKqk1gH5J9DgRY2ASl2lWCfGKXixSwevea8zH2U=
github.com/gabriel-vasile/mimetype v1.4.8 h1:FfZ3gj38NjllZIeJAmMhr+qKL8Wu+nOoI3GqacKw1NM= github.com/gabriel-vasile/mimetype v1.4.8 h1:FfZ3gj38NjllZIeJAmMhr+qKL8Wu+nOoI3GqacKw1NM=
@ -109,24 +109,24 @@ go.opencensus.io v0.24.0 h1:y73uSU6J157QMP2kn2r30vwW1A2W2WFwSCGnAVxeaD0=
go.opencensus.io v0.24.0/go.mod h1:vNK8G9p7aAivkbmorf4v+7Hgx+Zs0yY+0fOtgBfjQKo= go.opencensus.io v0.24.0/go.mod h1:vNK8G9p7aAivkbmorf4v+7Hgx+Zs0yY+0fOtgBfjQKo=
go.opentelemetry.io/auto/sdk v1.1.0 h1:cH53jehLUN6UFLY71z+NDOiNJqDdPRaXzTel0sJySYA= go.opentelemetry.io/auto/sdk v1.1.0 h1:cH53jehLUN6UFLY71z+NDOiNJqDdPRaXzTel0sJySYA=
go.opentelemetry.io/auto/sdk v1.1.0/go.mod h1:3wSPjt5PWp2RhlCcmmOial7AvC4DQqZb7a7wCow3W8A= go.opentelemetry.io/auto/sdk v1.1.0/go.mod h1:3wSPjt5PWp2RhlCcmmOial7AvC4DQqZb7a7wCow3W8A=
go.opentelemetry.io/contrib/detectors/gcp v1.33.0 h1:FVPoXEoILwgbZUu4X7YSgsESsAmGRgoYcnXkzgQPhP4= go.opentelemetry.io/contrib/detectors/gcp v1.34.0 h1:JRxssobiPg23otYU5SbWtQC//snGVIM3Tx6QRzlQBao=
go.opentelemetry.io/contrib/detectors/gcp v1.33.0/go.mod h1:ZHrLmr4ikK2AwRj9QL+c9s2SOlgoSRyMpNVzUj2fZqI= go.opentelemetry.io/contrib/detectors/gcp v1.34.0/go.mod h1:cV4BMFcscUR/ckqLkbfQmF0PRsq8w/lMGzdbCSveBHo=
go.opentelemetry.io/contrib/instrumentation/google.golang.org/grpc/otelgrpc v0.58.0 h1:PS8wXpbyaDJQ2VDHHncMe9Vct0Zn1fEjpsjrLxGJoSc= go.opentelemetry.io/contrib/instrumentation/google.golang.org/grpc/otelgrpc v0.59.0 h1:rgMkmiGfix9vFJDcDi1PK8WEQP4FLQwLDfhp5ZLpFeE=
go.opentelemetry.io/contrib/instrumentation/google.golang.org/grpc/otelgrpc v0.58.0/go.mod h1:HDBUsEjOuRC0EzKZ1bSaRGZWUBAzo+MhAcUUORSr4D0= go.opentelemetry.io/contrib/instrumentation/google.golang.org/grpc/otelgrpc v0.59.0/go.mod h1:ijPqXp5P6IRRByFVVg9DY8P5HkxkHE5ARIa+86aXPf4=
go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.58.0 h1:yd02MEjBdJkG3uabWP9apV+OuWRIXGDuJEUJbOHmCFU= go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.59.0 h1:CV7UdSGJt/Ao6Gp4CXckLxVRRsRgDHoI8XjbL3PDl8s=
go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.58.0/go.mod h1:umTcuxiv1n/s/S6/c2AT/g2CQ7u5C59sHDNmfSwgz7Q= go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.59.0/go.mod h1:FRmFuRJfag1IZ2dPkHnEoSFVgTVPUd2qf5Vi69hLb8I=
go.opentelemetry.io/otel v1.33.0 h1:/FerN9bax5LoK51X/sI0SVYrjSE0/yUL7DpxW4K3FWw= go.opentelemetry.io/otel v1.34.0 h1:zRLXxLCgL1WyKsPVrgbSdMN4c0FMkDAskSTQP+0hdUY=
go.opentelemetry.io/otel v1.33.0/go.mod h1:SUUkR6csvUQl+yjReHu5uM3EtVV7MBm5FHKRlNx4I8I= go.opentelemetry.io/otel v1.34.0/go.mod h1:OWFPOQ+h4G8xpyjgqo4SxJYdDQ/qmRH+wivy7zzx9oI=
go.opentelemetry.io/otel/exporters/stdout/stdoutmetric v1.29.0 h1:WDdP9acbMYjbKIyJUhTvtzj601sVJOqgWdUxSdR/Ysc= go.opentelemetry.io/otel/exporters/stdout/stdoutmetric v1.29.0 h1:WDdP9acbMYjbKIyJUhTvtzj601sVJOqgWdUxSdR/Ysc=
go.opentelemetry.io/otel/exporters/stdout/stdoutmetric v1.29.0/go.mod h1:BLbf7zbNIONBLPwvFnwNHGj4zge8uTCM/UPIVW1Mq2I= go.opentelemetry.io/otel/exporters/stdout/stdoutmetric v1.29.0/go.mod h1:BLbf7zbNIONBLPwvFnwNHGj4zge8uTCM/UPIVW1Mq2I=
go.opentelemetry.io/otel/metric v1.33.0 h1:r+JOocAyeRVXD8lZpjdQjzMadVZp2M4WmQ+5WtEnklQ= go.opentelemetry.io/otel/metric v1.34.0 h1:+eTR3U0MyfWjRDhmFMxe2SsW64QrZ84AOhvqS7Y+PoQ=
go.opentelemetry.io/otel/metric v1.33.0/go.mod h1:L9+Fyctbp6HFTddIxClbQkjtubW6O9QS3Ann/M82u6M= go.opentelemetry.io/otel/metric v1.34.0/go.mod h1:CEDrp0fy2D0MvkXE+dPV7cMi8tWZwX3dmaIhwPOaqHE=
go.opentelemetry.io/otel/sdk v1.33.0 h1:iax7M131HuAm9QkZotNHEfstof92xM+N8sr3uHXc2IM= go.opentelemetry.io/otel/sdk v1.34.0 h1:95zS4k/2GOy069d321O8jWgYsW3MzVV+KuSPKp7Wr1A=
go.opentelemetry.io/otel/sdk v1.33.0/go.mod h1:A1Q5oi7/9XaMlIWzPSxLRWOI8nG3FnzHJNbiENQuihM= go.opentelemetry.io/otel/sdk v1.34.0/go.mod h1:0e/pNiaMAqaykJGKbi+tSjWfNNHMTxoC9qANsCzbyxU=
go.opentelemetry.io/otel/sdk/metric v1.33.0 h1:Gs5VK9/WUJhNXZgn8MR6ITatvAmKeIuCtNbsP3JkNqU= go.opentelemetry.io/otel/sdk/metric v1.34.0 h1:5CeK9ujjbFVL5c1PhLuStg1wxA7vQv7ce1EK0Gyvahk=
go.opentelemetry.io/otel/sdk/metric v1.33.0/go.mod h1:dL5ykHZmm1B1nVRk9dDjChwDmt81MjVp3gLkQRwKf/Q= go.opentelemetry.io/otel/sdk/metric v1.34.0/go.mod h1:jQ/r8Ze28zRKoNRdkjCZxfs6YvBTG1+YIqyFVFYec5w=
go.opentelemetry.io/otel/trace v1.33.0 h1:cCJuF7LRjUFso9LPnEAHJDB2pqzp+hbO8eu1qqW2d/s= go.opentelemetry.io/otel/trace v1.34.0 h1:+ouXS2V8Rd4hp4580a8q23bg0azF2nI8cqLYnC8mh/k=
go.opentelemetry.io/otel/trace v1.33.0/go.mod h1:uIcdVUZMpTAmz0tI1z04GoVSezK37CbGV4fr1f2nBck= go.opentelemetry.io/otel/trace v1.34.0/go.mod h1:Svm7lSjQD7kG7KJ/MUHPVXSDGz2OX4h0M2jHBhmSfRE=
golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w= golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w=
golang.org/x/crypto v0.0.0-20210921155107-089bfa567519/go.mod h1:GvvjBRRGRdwPK5ydBHafDWAxML/pGHZbMvKqRZ5+Abc= golang.org/x/crypto v0.0.0-20210921155107-089bfa567519/go.mod h1:GvvjBRRGRdwPK5ydBHafDWAxML/pGHZbMvKqRZ5+Abc=
golang.org/x/crypto v0.32.0 h1:euUpcYgM8WcP71gNpTqQCn6rC2t6ULUPiOzfWaXVVfc= golang.org/x/crypto v0.32.0 h1:euUpcYgM8WcP71gNpTqQCn6rC2t6ULUPiOzfWaXVVfc=
@ -170,21 +170,21 @@ golang.org/x/tools v0.0.0-20191119224855-298f0cb1881e/go.mod h1:b+2E5dAYhXwXZwtn
golang.org/x/tools v0.1.12/go.mod h1:hNGJHUnrk76NpqgfD5Aqm5Crs+Hm0VOH/i9J2+nxYbc= golang.org/x/tools v0.1.12/go.mod h1:hNGJHUnrk76NpqgfD5Aqm5Crs+Hm0VOH/i9J2+nxYbc=
golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
google.golang.org/api v0.216.0 h1:xnEHy+xWFrtYInWPy8OdGFsyIfWJjtVnO39g7pz2BFY= google.golang.org/api v0.218.0 h1:x6JCjEWeZ9PFCRe9z0FBrNwj7pB7DOAqT35N+IPnAUA=
google.golang.org/api v0.216.0/go.mod h1:K9wzQMvWi47Z9IU7OgdOofvZuw75Ge3PPITImZR/UyI= google.golang.org/api v0.218.0/go.mod h1:5VGHBAkxrA/8EFjLVEYmMUJ8/8+gWWQ3s4cFH0FxG2M=
google.golang.org/appengine/v2 v2.0.6 h1:LvPZLGuchSBslPBp+LAhihBeGSiRh1myRoYK4NtuBIw= google.golang.org/appengine/v2 v2.0.6 h1:LvPZLGuchSBslPBp+LAhihBeGSiRh1myRoYK4NtuBIw=
google.golang.org/appengine/v2 v2.0.6/go.mod h1:WoEXGoXNfa0mLvaH5sV3ZSGXwVmy8yf7Z1JKf3J3wLI= google.golang.org/appengine/v2 v2.0.6/go.mod h1:WoEXGoXNfa0mLvaH5sV3ZSGXwVmy8yf7Z1JKf3J3wLI=
google.golang.org/genproto v0.0.0-20250106144421-5f5ef82da422 h1:6GUHKGv2huWOHKmDXLMNE94q3fBDlEHI+oTRIZSebK0= google.golang.org/genproto v0.0.0-20250124145028-65684f501c47 h1:SI8Hf7K4+uVYchXqZiMfP44PZ83xomMWovbcFfm0P8Q=
google.golang.org/genproto v0.0.0-20250106144421-5f5ef82da422/go.mod h1:1NPAxoesyw/SgLPqaUp9u1f9PWCLAk/jVmhx7gJZStg= google.golang.org/genproto v0.0.0-20250124145028-65684f501c47/go.mod h1:qbZzneIOXSq+KFAFut9krLfRLZiFLzZL5u2t8SV83EE=
google.golang.org/genproto/googleapis/api v0.0.0-20250106144421-5f5ef82da422 h1:GVIKPyP/kLIyVOgOnTwFOrvQaQUzOzGMCxgFUOEmm24= google.golang.org/genproto/googleapis/api v0.0.0-20250124145028-65684f501c47 h1:5iw9XJTD4thFidQmFVvx0wi4g5yOHk76rNRUxz1ZG5g=
google.golang.org/genproto/googleapis/api v0.0.0-20250106144421-5f5ef82da422/go.mod h1:b6h1vNKhxaSoEI+5jc3PJUCustfli/mRab7295pY7rw= google.golang.org/genproto/googleapis/api v0.0.0-20250124145028-65684f501c47/go.mod h1:AfA77qWLcidQWywD0YgqfpJzf50w2VjzBml3TybHeJU=
google.golang.org/genproto/googleapis/rpc v0.0.0-20250106144421-5f5ef82da422 h1:3UsHvIr4Wc2aW4brOaSCmcxh9ksica6fHEr8P1XhkYw= google.golang.org/genproto/googleapis/rpc v0.0.0-20250124145028-65684f501c47 h1:91mG8dNTpkC0uChJUQ9zCiRqx3GEEFOWaRZ0mI6Oj2I=
google.golang.org/genproto/googleapis/rpc v0.0.0-20250106144421-5f5ef82da422/go.mod h1:3ENsm/5D1mzDyhpzeRi1NR784I0BcofWBoSc5QqqMK4= google.golang.org/genproto/googleapis/rpc v0.0.0-20250124145028-65684f501c47/go.mod h1:+2Yz8+CLJbIfL9z73EW45avw8Lmge3xVElCP9zEKi50=
google.golang.org/grpc v1.69.2 h1:U3S9QEtbXC0bYNvRtcoklF3xGtLViumSYxWykJS+7AU= google.golang.org/grpc v1.70.0 h1:pWFv03aZoHzlRKHWicjsZytKAiYCtNS0dHbXnIdq7jQ=
google.golang.org/grpc v1.69.2/go.mod h1:vyjdE6jLBI76dgpDojsFGNaHlxdjXN9ghpnd2o7JGZ4= google.golang.org/grpc v1.70.0/go.mod h1:ofIJqVKDXx/JiXrwr2IG4/zwdH9txy3IlF40RmcJSQw=
google.golang.org/protobuf v1.26.0-rc.1/go.mod h1:jlhhOSvTdKEhbULTjvd4ARK9grFBp09yW+WbY/TyQbw= google.golang.org/protobuf v1.26.0-rc.1/go.mod h1:jlhhOSvTdKEhbULTjvd4ARK9grFBp09yW+WbY/TyQbw=
google.golang.org/protobuf v1.30.0/go.mod h1:HV8QOd/L58Z+nl8r43ehVNZIU/HEI6OcFqwMG9pJV4I= google.golang.org/protobuf v1.30.0/go.mod h1:HV8QOd/L58Z+nl8r43ehVNZIU/HEI6OcFqwMG9pJV4I=
google.golang.org/protobuf v1.36.2 h1:R8FeyR1/eLmkutZOM5CWghmo5itiG9z0ktFlTVLuTmU= google.golang.org/protobuf v1.36.4 h1:6A3ZDJHn/eNqc1i+IdefRzy/9PokBTPvcqMySR7NNIM=
google.golang.org/protobuf v1.36.2/go.mod h1:9fA7Ob0pmnwhb644+1+CVWFRbNajQ6iRojtC/QF5bRE= google.golang.org/protobuf v1.36.4/go.mod h1:9fA7Ob0pmnwhb644+1+CVWFRbNajQ6iRojtC/QF5bRE=
gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA= gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA=
gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=

View File

@ -42,7 +42,7 @@
<p>{{.Version}} - <strong>Alpha: Drastische Änderungen und Fehler vorbehalten.</strong></p> <p>{{.Version}} - <strong>Alpha: Drastische Änderungen und Fehler vorbehalten.</strong></p>
</footer> </footer>
<script src="https://unpkg.com/htmx.org@latest"></script> <script src="https://unpkg.com/htmx.org@2.0.4"></script>
<script src="https://unpkg.com/easymde/dist/easymde.min.js"></script> <script src="https://unpkg.com/easymde/dist/easymde.min.js"></script>
<script> <script>
document.addEventListener('DOMContentLoaded', () => { document.addEventListener('DOMContentLoaded', () => {