Compare commits

...

54 Commits

Author SHA1 Message Date
737a9ec314 Merge branch 'devel' 2024-10-27 15:29:50 +01:00
4b75fc61cd Update version number 2024-10-27 15:29:42 +01:00
1ebe0380ee Merge branch 'devel' 2024-10-27 15:28:22 +01:00
c439e048c1 Update dependencies 2024-10-27 15:28:07 +01:00
f86f2ba146 Get rid of RSS completely 2024-10-27 15:27:57 +01:00
d62d71b5d1 Merge branch 'devel' 2024-10-27 15:14:11 +01:00
07e1983fcc Add call to serve atom feed 2024-10-27 15:12:30 +01:00
878f57af08 Split up max value for img height and with into values for img and banner 2024-10-27 15:05:11 +01:00
d86b9027bf Correct lengths for username and names 2024-10-27 14:53:37 +01:00
31484dd44a Add encrypted email to user info 2024-10-27 14:43:38 +01:00
3b4e1e01d2 Make version part of config 2024-10-27 13:58:19 +01:00
03425fc2c6 Make action-btn and btn clicky 2024-10-27 13:58:03 +01:00
0b9bae3bf8 Get rid of rss 2024-10-27 13:29:58 +01:00
8ed0676e51 Encrypt sensitive user data with aes256 2024-10-27 13:29:46 +01:00
d7cbb34814 Prepare sql for encrypted user info, email address and profile pic 2024-10-27 07:34:44 +01:00
23f0c6f4a4 Make banner_link and content_link NOT NULL 2024-10-27 07:29:51 +01:00
cb322c57f9 Delete unnecessary enc_url, enc_length and enc_type from sql 2024-10-27 07:25:53 +01:00
b36e0ea503 Merge branch 'devel' 2024-10-04 16:06:57 +02:00
bc4d8fa37e Merge branch 'devel' 2024-09-28 13:59:34 +02:00
d2b21e7405 Merge branch 'devel' 2024-09-28 12:36:46 +02:00
e3c192359f Merge branch 'devel' 2024-09-11 18:15:31 +02:00
46532e4c85 Merge branch 'devel' 2024-09-11 18:15:07 +02:00
c722135a56 Merge branch 'devel' 2024-09-11 17:18:42 +02:00
887fa863bc Merge branch 'devel' 2024-09-10 19:43:22 +02:00
74d71cfb6a Merge branch 'devel' 2024-09-09 22:03:43 +02:00
ca7e7cddd3 Merge branch 'devel' 2024-09-08 16:22:59 +02:00
94431a2aa9 Merge branch 'devel' 2024-09-08 16:21:38 +02:00
5b1f20c5bc Merge branch 'devel' 2024-09-08 13:36:22 +02:00
d0c566f8df Merge branch 'devel' 2024-09-01 21:14:05 +02:00
5e586aa49a Merge branch 'devel' 2024-09-01 18:51:33 +02:00
66b2743d3d Merge branch 'devel' 2024-09-01 18:48:26 +02:00
3723b2b5e6 Merge branch 'devel' 2024-09-01 18:18:18 +02:00
ce788bfd50 Merge branch 'devel' 2024-09-01 12:54:12 +02:00
230a6278cc Merge branch 'devel' 2024-09-01 12:49:30 +02:00
42d6e0c198 Merge branch 'devel' 2024-08-31 12:17:41 +02:00
e1af2979af Merge branch 'devel' 2024-08-31 11:27:15 +02:00
f6dedc6f10 Merge branch 'devel' 2024-08-31 01:43:53 +02:00
cdf0a49550 Merge branch 'devel' 2024-08-31 01:38:28 +02:00
c3c0650210 Merge branch 'devel' 2024-08-31 01:00:55 +02:00
d077f700d8 Merge branch 'devel' 2024-08-31 00:36:48 +02:00
ec752b1c66 Merge branch 'devel' 2024-08-30 23:43:12 +02:00
46aef4f12f Merge branch 'devel' 2024-08-25 10:51:35 +02:00
1b29e328cf Merge branch 'devel' 2024-08-25 06:38:55 +02:00
e50cb819f3 Merge branch 'devel' 2024-08-23 21:45:30 +02:00
c32e38ca10 Merge branch 'devel' 2024-08-23 20:57:11 +02:00
d7c8c7a43a Merge branch 'devel' 2024-08-18 17:31:00 +02:00
1cd3edc90c Merge branch 'devel' 2024-08-18 12:06:29 +02:00
0e768c9f61 Merge branch 'devel' 2024-08-08 21:27:07 +02:00
1fcd775cc5 Merge branch 'devel' 2024-08-08 21:14:24 +02:00
203a1ed147 Implemented EasyMDE 2024-08-08 21:13:25 +02:00
ef1914ee5c Implemented article preview 2024-08-08 21:13:25 +02:00
084b101e31 Register f.ArticlePreviewHtmlData in init() 2024-08-08 21:13:25 +02:00
b2db128aa9 Shorten lines by referencing frontend as f and backend as b 2024-08-08 21:13:25 +02:00
081e880fb6 Change structure of code tor frontend and backend one 2024-08-08 21:13:25 +02:00
28 changed files with 636 additions and 411 deletions

View File

@ -4,22 +4,23 @@ tmp_dir = "tmp"
[build] [build]
args_bin = [ args_bin = [
"-aes tmp/cpolis.aes",
"-articles tmp/articles", "-articles tmp/articles",
"-banner-width 512",
"-config tmp/config.toml", "-config tmp/config.toml",
"-desc 'Freiheit, Gleichheit, Brüderlichkeit, Toleranz und Humanität'", "-desc 'Freiheit, Gleichheit, Brüderlichkeit, Toleranz und Humanität'",
"-domain localhost", "-domain localhost",
"-feed tmp/cpolis.atom", "-feed tmp/cpolis.atom",
"-firebase tmp/firebase.json", "-firebase tmp/firebase.json",
"-key tmp/key.gob", "-gob tmp/cpolis.gob",
"-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", "-pics tmp/pics",
"-port 8080", "-port 8080",
"-rss tmp/orientexpress_alle.rss",
"-title 'Freimaurer Distrikt Niedersachsen und Sachsen-Anhalt'", "-title 'Freimaurer Distrikt Niedersachsen und Sachsen-Anhalt'",
"-web web", "-web web",
"-width 1024",
] ]
bin = "./tmp/main" bin = "./tmp/main"
cmd = "go build -o ./tmp/main ./cmd/main.go" cmd = "go build -o ./tmp/main ./cmd/main.go"

View File

@ -18,17 +18,17 @@ func GenerateAtomFeed(c *Config, db *DB) (*string, error) {
feed.Generator = atom.NewGenerator("cpolis") feed.Generator = atom.NewGenerator("cpolis")
feed.Generator.URI = "https://git.streifling.com/jason/cpolis" feed.Generator.URI = "https://git.streifling.com/jason/cpolis"
feed.Generator.Version = "0.13" feed.Generator.Version = c.Version
articles, err := db.GetCertainArticles("published", true) articles, err := db.GetCertainArticles("published", true)
if err != nil { if err != nil {
return nil, fmt.Errorf("error getting published articles for RSS feed: %v", err) return nil, fmt.Errorf("error getting published articles for Atom feed: %v", 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 {
return nil, fmt.Errorf("error converting title to plain text for RSS feed: %v", err) return nil, fmt.Errorf("error converting title to plain text for Atom feed: %v", err)
} }
entry := atom.NewEntry(articleTitle) entry := atom.NewEntry(articleTitle)
entry.ID = atom.NewID(fmt.Sprint("urn:entry:", article.ID)) entry.ID = atom.NewID(fmt.Sprint("urn:entry:", article.ID))
@ -39,15 +39,15 @@ func GenerateAtomFeed(c *Config, db *DB) (*string, error) {
entry.Links[linkID].Rel = "enclosure" entry.Links[linkID].Rel = "enclosure"
entry.Links[linkID].Type = "image/webp" entry.Links[linkID].Type = "image/webp"
user, err := db.GetUser(article.AuthorID) user, err := db.GetUser(c, article.AuthorID)
if err != nil { if err != nil {
return nil, fmt.Errorf("error getting user user info for RSS feed: %v", err) return nil, fmt.Errorf("error getting user user info for Atom feed: %v", err)
} }
entry.AddAuthor(atom.NewPerson(user.FirstName + " " + user.LastName)) entry.AddAuthor(atom.NewPerson(user.FirstName + " " + user.LastName))
articleSummary, err := ConvertToPlain(article.Summary) articleSummary, err := ConvertToPlain(article.Summary)
if err != nil { if err != nil {
return nil, fmt.Errorf("error converting description to plain text for RSS feed: %v", err) return nil, fmt.Errorf("error converting description to plain text for Atom feed: %v", err)
} }
if article.AutoGenerated { if article.AutoGenerated {
articleSummary = "auto generated" articleSummary = "auto generated"
@ -56,7 +56,7 @@ func GenerateAtomFeed(c *Config, db *DB) (*string, error) {
tags, err := db.GetArticleTags(article.ID) tags, err := db.GetArticleTags(article.ID)
if err != nil { if err != nil {
return nil, fmt.Errorf("error getting tags for articles for RSS feed: %v", err) return nil, fmt.Errorf("error getting tags for articles for Atom feed: %v", err)
} }
for _, tag := range tags { for _, tag := range tags {
entry.AddCategory(atom.NewCategory(tag.Name)) entry.AddCategory(atom.NewCategory(tag.Name))
@ -79,7 +79,7 @@ func GenerateAtomFeed(c *Config, db *DB) (*string, error) {
atom, err := feed.ToXML("UTF-8") atom, err := feed.ToXML("UTF-8")
if err != nil { if err != nil {
return nil, fmt.Errorf("error converting RSS feed to XML: %v", err) return nil, fmt.Errorf("error converting Atom feed to XML: %v", err)
} }
return &atom, nil return &atom, nil

View File

@ -12,42 +12,48 @@ import (
) )
type Config struct { type Config struct {
ArticleDir string AESKeyFile string
ConfigFile string ArticleDir string
DBName string ConfigFile string
Description string DBName string
Domain string Description string
AtomFeed string Domain string
FirebaseKey string AtomFeed string
KeyFile string FirebaseKey string
Link string GOBKeyFile string
LogFile string Link string
PDFDir string LogFile string
PicsDir string PDFDir string
Port string PicsDir string
RSSFile string Port string
Title string Title string
WebDir string Version string
MaxImgHeight int WebDir string
MaxImgWidth int MaxBannerHeight int
MaxBannerWidth int
MaxImgHeight int
MaxImgWidth int
} }
func newConfig() *Config { func newConfig() *Config {
return &Config{ return &Config{
ArticleDir: "/var/www/cpolis/articles", AESKeyFile: "/var/www/cpolis/aes.key",
ConfigFile: "/etc/cpolis/config.toml", ArticleDir: "/var/www/cpolis/articles",
DBName: "cpolis", AtomFeed: "/var/www/cpolis/cpolis.atom",
AtomFeed: "/var/www/cpolis/cpolis.atom", ConfigFile: "/etc/cpolis/config.toml",
FirebaseKey: "/var/www/cpolis/serviceAccountKey.json", DBName: "cpolis",
KeyFile: "/var/www/cpolis/cpolis.key", FirebaseKey: "/var/www/cpolis/serviceAccountKey.json",
LogFile: "/var/log/cpolis.log", GOBKeyFile: "/var/www/cpolis/gob.key",
MaxImgHeight: 1080, LogFile: "/var/log/cpolis.log",
MaxImgWidth: 1920, MaxBannerHeight: 1080,
PDFDir: "/var/www/cpolis/pdfs", MaxBannerWidth: 1920,
PicsDir: "/var/www/cpolis/pics", MaxImgHeight: 1080,
Port: ":8080", MaxImgWidth: 1920,
RSSFile: "/var/www/cpolis/cpolis.rss", PDFDir: "/var/www/cpolis/pdfs",
WebDir: "/var/www/cpolis/web", PicsDir: "/var/www/cpolis/pics",
Port: ":8080",
Version: "v0.13.1",
WebDir: "/var/www/cpolis/web",
} }
} }
@ -102,6 +108,7 @@ func (c *Config) handleCliArgs() error {
var port int var port int
var err error var err error
flag.StringVar(&c.AESKeyFile, "aes", c.AESKeyFile, "aes key file")
flag.StringVar(&c.ArticleDir, "articles", c.ArticleDir, "articles directory") flag.StringVar(&c.ArticleDir, "articles", c.ArticleDir, "articles directory")
flag.StringVar(&c.AtomFeed, "feed", c.AtomFeed, "atom feed file") flag.StringVar(&c.AtomFeed, "feed", c.AtomFeed, "atom feed file")
flag.StringVar(&c.ConfigFile, "config", c.ConfigFile, "config file") flag.StringVar(&c.ConfigFile, "config", c.ConfigFile, "config file")
@ -109,16 +116,17 @@ 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.KeyFile, "key", c.KeyFile, "key file") flag.StringVar(&c.GOBKeyFile, "gob", c.GOBKeyFile, "gob key file")
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.PicsDir, "pics", c.PicsDir, "pictures directory")
flag.StringVar(&c.RSSFile, "rss", c.RSSFile, "RSS file")
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.MaxImgHeight, "height", c.MaxImgHeight, "maximum image height") flag.IntVar(&c.MaxBannerHeight, "banner-height", c.MaxBannerHeight, "maximum banner height")
flag.IntVar(&c.MaxImgWidth, "width", c.MaxImgWidth, "maximum image width") flag.IntVar(&c.MaxBannerWidth, "banner-width", c.MaxBannerWidth, "maximum banner width")
flag.IntVar(&c.MaxImgHeight, "img-height", c.MaxImgHeight, "maximum image height")
flag.IntVar(&c.MaxImgWidth, "img-width", c.MaxImgWidth, "maximum image width")
flag.IntVar(&port, "port", port, "port") flag.IntVar(&port, "port", port, "port")
flag.Parse() flag.Parse()
@ -147,6 +155,14 @@ func (c *Config) setupConfig(cliConfig *Config) error {
var err error var err error
defaultConfig := newConfig() defaultConfig := newConfig()
if cliConfig.AESKeyFile != defaultConfig.AESKeyFile {
c.AESKeyFile = cliConfig.AESKeyFile
}
c.AESKeyFile, err = mkFile(c.AESKeyFile, 0600, 0700)
if err != nil {
return fmt.Errorf("error setting up file: %v", err)
}
if cliConfig.ArticleDir != defaultConfig.ArticleDir { if cliConfig.ArticleDir != defaultConfig.ArticleDir {
c.ArticleDir = cliConfig.ArticleDir c.ArticleDir = cliConfig.ArticleDir
} }
@ -187,10 +203,10 @@ 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.KeyFile != defaultConfig.KeyFile { if cliConfig.GOBKeyFile != defaultConfig.GOBKeyFile {
c.KeyFile = cliConfig.KeyFile c.GOBKeyFile = cliConfig.GOBKeyFile
} }
c.KeyFile, err = mkFile(c.KeyFile, 0600, 0700) c.GOBKeyFile, err = mkFile(c.GOBKeyFile, 0600, 0700)
if err != nil { if err != nil {
return fmt.Errorf("error setting up file: %v", err) return fmt.Errorf("error setting up file: %v", err)
} }
@ -207,6 +223,14 @@ 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.MaxBannerHeight != defaultConfig.MaxBannerHeight {
c.MaxBannerHeight = cliConfig.MaxBannerHeight
}
if cliConfig.MaxBannerWidth != defaultConfig.MaxBannerWidth {
c.MaxBannerWidth = cliConfig.MaxBannerWidth
}
if cliConfig.MaxImgHeight != defaultConfig.MaxImgHeight { if cliConfig.MaxImgHeight != defaultConfig.MaxImgHeight {
c.MaxImgHeight = cliConfig.MaxImgHeight c.MaxImgHeight = cliConfig.MaxImgHeight
} }
@ -235,14 +259,6 @@ func (c *Config) setupConfig(cliConfig *Config) error {
c.Port = cliConfig.Port c.Port = cliConfig.Port
} }
if cliConfig.RSSFile != defaultConfig.RSSFile {
c.RSSFile = cliConfig.RSSFile
}
c.RSSFile, err = mkFile(c.RSSFile, 0600, 0700)
if err != nil {
return fmt.Errorf("error setting up file: %v", err)
}
if cliConfig.Title != defaultConfig.Title { if cliConfig.Title != defaultConfig.Title {
c.Title = cliConfig.Title c.Title = cliConfig.Title
} }

View File

@ -1,6 +1,7 @@
package backend package backend
import ( import (
"encoding/base64"
"fmt" "fmt"
"image" "image"
"io" "io"
@ -13,7 +14,7 @@ import (
var ErrUnsupportedFormat error = image.ErrFormat // used internally by imaging var ErrUnsupportedFormat error = image.ErrFormat // used internally by imaging
func SaveImage(c *Config, src io.Reader) (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 {
if err == ErrUnsupportedFormat { if err == ErrUnsupportedFormat {
@ -22,15 +23,15 @@ func SaveImage(c *Config, src io.Reader) (string, error) {
return "", fmt.Errorf("error decoding image: %v", err) return "", fmt.Errorf("error decoding image: %v", err)
} }
if img.Bounds().Dy() > c.MaxImgHeight { if img.Bounds().Dy() > maxHeight {
img = imaging.Resize(img, 0, c.MaxImgHeight, imaging.Lanczos) img = imaging.Resize(img, 0, maxHeight, imaging.Lanczos)
} }
if img.Bounds().Dx() > c.MaxImgWidth { if img.Bounds().Dx() > maxWidth {
img = imaging.Resize(img, c.MaxImgWidth, 0, imaging.Lanczos) img = imaging.Resize(img, maxWidth, 0, imaging.Lanczos)
} }
filename := fmt.Sprint(uuid.New(), ".webp") filename := fmt.Sprint(uuid.New(), ".webp")
file, err := os.Create(c.PicsDir + "/" + filename) file, err := os.Create(path + filename)
if err != nil { if err != nil {
return "", fmt.Errorf("error creating new image file: %v", err) return "", fmt.Errorf("error creating new image file: %v", err)
} }
@ -42,3 +43,20 @@ func SaveImage(c *Config, src io.Reader) (string, error) {
return filename, nil return filename, nil
} }
func ServeBase64Image(c *Config, filename string) (string, error) {
file := c.PicsDir + "/" + filename
img, err := os.Open(file)
if err != nil {
return "", fmt.Errorf("error opening file %v: %v", file, err)
}
defer img.Close()
imgBytes, err := io.ReadAll(img)
if err != nil {
return "", fmt.Errorf("error turning %v into bytes: %v", file, err)
}
return base64.StdEncoding.EncodeToString(imgBytes), nil
}

View File

@ -1,103 +0,0 @@
package backend
import (
"fmt"
"io"
"os"
"time"
"git.streifling.com/jason/rss"
)
func GenerateRSS(c *Config, db *DB) (*string, error) {
channel := &rss.Channel{
Title: c.Title,
Link: c.Link,
Description: c.Description,
Items: make([]*rss.Item, 0),
}
articles, err := db.GetCertainArticles("published", true)
if err != nil {
return nil, fmt.Errorf("error getting published articles for RSS feed: %v", err)
}
for _, article := range articles {
tags, err := db.GetArticleTags(article.ID)
if err != nil {
return nil, fmt.Errorf("error getting tags for articles for RSS feed: %v", err)
}
tagNames := make([]string, 0)
for _, tag := range tags {
tagNames = append(tagNames, tag.Name)
}
if article.IsInIssue || article.AutoGenerated {
tagNames = append(tagNames, fmt.Sprint("Orient Express ", article.IssueID))
}
if article.AutoGenerated {
tagNames = append(tagNames, "autogenerated")
}
user, err := db.GetUser(article.AuthorID)
if err != nil {
return nil, fmt.Errorf("error getting user user info for RSS feed: %v", err)
}
articleTitle, err := ConvertToPlain(article.Title)
if err != nil {
return nil, fmt.Errorf("error converting title to plain text for RSS feed: %v", err)
}
articleDescription, err := ConvertToPlain(article.Summary)
if err != nil {
return nil, fmt.Errorf("error converting description to plain text for RSS feed: %v", err)
}
item := &rss.Item{
Author: user.FirstName + " " + user.LastName,
Categories: tagNames,
Description: articleDescription,
Guid: string(article.ID),
Link: article.ContentLink,
PubDate: article.Created.Format(time.RFC1123Z),
Title: articleTitle,
}
// if article.AutoGenerated {
// item.Enclosure = &rss.Enclosure{
// Url: article.EncURL,
// Lenght: article.EncLength,
// Type: article.EncType,
// }
// }
//
channel.Items = append(channel.Items, item)
}
feed := rss.NewFeed()
feed.Channels = append(feed.Channels, channel)
rss, err := feed.ToXML("UTF-8")
if err != nil {
return nil, fmt.Errorf("error converting RSS feed to XML: %v", err)
}
return &rss, nil
}
func SaveRSS(filename string, feed *string) error {
file, err := os.Create(filename)
if err != nil {
return fmt.Errorf("error creating file for RSS feed: %v", err)
}
defer file.Close()
if err = file.Chmod(0644); err != nil {
return fmt.Errorf("error setting permissions for RSS file: %v", err)
}
if _, err = io.WriteString(file, *feed); err != nil {
return fmt.Errorf("error writing to RSS file: %v", err)
}
return nil
}

View File

@ -2,9 +2,16 @@ package backend
import ( import (
"context" "context"
"crypto/aes"
"crypto/cipher"
"crypto/rand"
"database/sql" "database/sql"
"encoding/base64"
"errors"
"fmt" "fmt"
"io"
"log" "log"
"os"
"golang.org/x/crypto/bcrypt" "golang.org/x/crypto/bcrypt"
) )
@ -18,24 +25,135 @@ const (
) )
type User struct { type User struct {
UserName string UserName string
FirstName string FirstName string
LastName string LastName string
ID int64 Email string
Role int ProfilePicLink string
ID int64
Role int
} }
func (db *DB) AddUser(u *User, pass string) (int64, error) { func readKey(filename string) ([]byte, error) {
key, err := os.ReadFile(filename)
if err != nil {
return nil, fmt.Errorf("error reading from aes key file: %v", err)
}
if len(key) != 44 {
return nil, errors.New("key is not 32 bytes long")
}
key, err = base64.StdEncoding.DecodeString(string(key))
if err != nil {
return nil, fmt.Errorf("error base64 decoding key: %v", err)
}
return key, nil
}
func key(c *Config) ([]byte, error) {
key, err := readKey(c.AESKeyFile)
if err != nil {
key = make([]byte, 32)
if _, err := rand.Read(key); err != nil {
return nil, fmt.Errorf("error generating random key: %v", err)
}
fileKey := make([]byte, 44)
base64.StdEncoding.Encode(fileKey, key)
if err = os.WriteFile(c.AESKeyFile, fileKey, 0600); err != nil {
return nil, fmt.Errorf("error writing key to file: %v", err)
}
}
return key, nil
}
func aesEncrypt(c *Config, plaintext string) (string, error) {
key, err := key(c)
if err != nil {
return "", fmt.Errorf("error retrieving key: %v", err)
}
block, err := aes.NewCipher(key)
if err != nil {
return "", fmt.Errorf("error creating cipher block: %v", err)
}
gcm, err := cipher.NewGCM(block)
if err != nil {
return "", fmt.Errorf("error creating new gcm: %v", err)
}
nonce := make([]byte, gcm.NonceSize())
if _, err := io.ReadFull(rand.Reader, nonce); err != nil {
return "", fmt.Errorf("error creating nonce: %v", err)
}
cipherText := gcm.Seal(nonce, nonce, []byte(plaintext), nil)
return base64.StdEncoding.EncodeToString(cipherText), nil
}
func aesDecrypt(c *Config, ciphertext string) (string, error) {
key, err := key(c)
if err != nil {
return "", fmt.Errorf("error retrieving key: %v", err)
}
block, err := aes.NewCipher(key)
if err != nil {
return "", fmt.Errorf("error creating cipher block: %v", err)
}
gcm, err := cipher.NewGCM(block)
if err != nil {
return "", fmt.Errorf("error creating new gcm: %v", err)
}
data, err := base64.StdEncoding.DecodeString(ciphertext)
if err != nil {
return "", fmt.Errorf("error base64 decoding ciphertext: %v", err)
}
nonceSize := gcm.NonceSize()
nonce, cipherText := data[:nonceSize], data[nonceSize:]
plaintext, err := gcm.Open(nil, nonce, cipherText, nil)
if err != nil {
return "", fmt.Errorf("error aes decoding ciphertext: %v", err)
}
return string(plaintext), nil
}
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 {
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)
if err != nil {
return 0, fmt.Errorf("error encrypting first name: %v", err)
}
aesLastName, err := aesEncrypt(c, u.LastName)
if err != nil {
return 0, fmt.Errorf("error encrypting last name: %v", err)
}
aesEmail, err := aesEncrypt(c, u.Email)
if err != nil {
return 0, fmt.Errorf("error encrypting email: %v", err)
}
query := ` query := `
INSERT INTO users (username, password, first_name, last_name, role) INSERT INTO users (username, password, first_name, last_name, email, role)
VALUES (?, ?, ?, ?, ?) VALUES (?, ?, ?, ?, ?, ?)
` `
result, err := db.Exec(query, u.UserName, string(hashedPass), u.FirstName, u.LastName, u.Role)
result, err := db.Exec(query, u.UserName, string(hashedPass), aesFirstName, aesLastName, aesEmail, u.Role)
if err != nil { if err != nil {
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)
} }
@ -129,34 +247,44 @@ func (tx *Tx) ChangePassword(id int64, oldPass, newPass string) error {
} }
// TODO: No need for ID field in general // TODO: No need for ID field in general
func (db *DB) GetUser(id int64) (*User, error) { func (db *DB) GetUser(c *Config, id int64) (*User, error) {
var aesFirstName, aesLastName, aesEmail string
var err error
user := new(User) user := new(User)
query := ` query := `
SELECT id, username, first_name, last_name, role SELECT id, username, first_name, last_name, email, role
FROM users FROM users
WHERE id = ? WHERE id = ?
` `
row := db.QueryRow(query, id) row := db.QueryRow(query, id)
if err := row.Scan(&user.ID, &user.UserName, &user.FirstName, if err := row.Scan(&user.ID, &user.UserName, &aesFirstName, &aesLastName, &aesEmail, &user.Role); err != nil {
&user.LastName, &user.Role); err != nil {
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)
if err != nil {
return nil, fmt.Errorf("error decrypting first name: %v", err)
}
user.LastName, err = aesDecrypt(c, aesLastName)
if err != nil {
return nil, fmt.Errorf("error decrypting last name: %v", err)
}
user.Email, err = aesDecrypt(c, aesEmail)
if err != nil {
return nil, fmt.Errorf("error decrypting email: %v", err)
}
return user, nil return user, nil
} }
func (db *DB) UpdateOwnUserAttributes(id int64, user, first, last, oldPass, newPass, newPass2 string) error { func (db *DB) UpdateOwnUserAttributes(c *Config, id int64, userName, firstName, lastName, email, oldPass, newPass string) error {
passwordEmpty := true
if len(newPass) > 0 || len(newPass2) > 0 {
if newPass != newPass2 {
return fmt.Errorf("error: passwords do not match")
}
passwordEmpty = false
}
tx := new(Tx)
var err error var err error
tx := new(Tx)
passwordEmpty := len(newPass) > 0
for i := 0; i < TxMaxRetries; i++ { for i := 0; i < TxMaxRetries; i++ {
err := func() error { err := func() error {
@ -174,10 +302,35 @@ func (db *DB) UpdateOwnUserAttributes(id int64, user, first, last, oldPass, newP
} }
} }
aesFirstName, err := aesEncrypt(c, firstName)
if err != nil {
if rollbackErr := tx.Rollback(); rollbackErr != nil {
log.Fatalf("transaction error: %v, rollback error: %v", err, rollbackErr)
}
return fmt.Errorf("error encrypting first name: %v", err)
}
aesLastName, err := aesEncrypt(c, lastName)
if err != nil {
if rollbackErr := tx.Rollback(); rollbackErr != nil {
log.Fatalf("transaction error: %v, rollback error: %v", err, rollbackErr)
}
return fmt.Errorf("error encrypting last name: %v", err)
}
aesEmail, err := aesEncrypt(c, email)
if err != nil {
if rollbackErr := tx.Rollback(); rollbackErr != nil {
log.Fatalf("transaction error: %v, rollback error: %v", err, rollbackErr)
}
return fmt.Errorf("error encrypting email: %v", err)
}
if err = tx.UpdateAttributes( if err = tx.UpdateAttributes(
&Attribute{Table: "users", ID: id, AttName: "username", Value: user}, &Attribute{Table: "users", ID: id, AttName: "username", Value: userName},
&Attribute{Table: "users", ID: id, AttName: "first_name", Value: first}, &Attribute{Table: "users", ID: id, AttName: "first_name", Value: aesFirstName},
&Attribute{Table: "users", ID: id, AttName: "last_name", Value: last}, &Attribute{Table: "users", ID: id, AttName: "last_name", Value: aesLastName},
&Attribute{Table: "users", ID: id, AttName: "email", Value: aesEmail},
); err != nil { ); err != nil {
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)
@ -202,13 +355,13 @@ func (db *DB) UpdateOwnUserAttributes(id int64, user, first, last, oldPass, newP
return fmt.Errorf("error: %v unsuccessful retries for DB operation, aborting", TxMaxRetries) return fmt.Errorf("error: %v unsuccessful retries for DB operation, aborting", TxMaxRetries)
} }
func (db *DB) AddFirstUser(u *User, pass string) (int64, error) { func (db *DB) AddFirstUser(c *Config, u *User, pass string) (int64, error) {
var numUsers int64 var numUsers int64
txOptions := &sql.TxOptions{Isolation: sql.LevelSerializable} txOptions := &sql.TxOptions{Isolation: sql.LevelSerializable}
selectQuery := "SELECT COUNT(*) FROM users" selectQuery := "SELECT COUNT(*) FROM users"
insertQuery := ` insertQuery := `
INSERT INTO users (username, password, first_name, last_name, role) INSERT INTO users (username, password, first_name, last_name, email, role)
VALUES (?, ?, ?, ?, ?) VALUES (?, ?, ?, ?, ?, ?)
` `
for i := 0; i < TxMaxRetries; i++ { for i := 0; i < TxMaxRetries; i++ {
@ -239,7 +392,31 @@ func (db *DB) AddFirstUser(u *User, pass string) (int64, error) {
return 0, fmt.Errorf("error creating password hash: %v", err) return 0, fmt.Errorf("error creating password hash: %v", err)
} }
result, err := tx.Exec(insertQuery, u.UserName, string(hashedPass), u.FirstName, u.LastName, u.Role) aesFirstName, err := aesEncrypt(c, u.FirstName)
if err != nil {
if rollbackErr := tx.Rollback(); rollbackErr != nil {
log.Fatalf("transaction error: %v, rollback error: %v", err, rollbackErr)
}
return 0, fmt.Errorf("error encrypting first name: %v", err)
}
aesLastName, err := aesEncrypt(c, u.LastName)
if err != nil {
if rollbackErr := tx.Rollback(); rollbackErr != nil {
log.Fatalf("transaction error: %v, rollback error: %v", err, rollbackErr)
}
return 0, fmt.Errorf("error encrypting last name: %v", err)
}
aesEmail, err := aesEncrypt(c, u.Email)
if err != nil {
if rollbackErr := tx.Rollback(); rollbackErr != nil {
log.Fatalf("transaction error: %v, rollback error: %v", err, rollbackErr)
}
return 0, fmt.Errorf("error encrypting email: %v", err)
}
result, err := tx.Exec(insertQuery, u.UserName, string(hashedPass), aesFirstName, aesLastName, aesEmail, u.Role)
if err != nil { if err != nil {
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)
@ -270,8 +447,11 @@ func (db *DB) AddFirstUser(u *User, pass string) (int64, error) {
return 0, fmt.Errorf("error: %v unsuccessful retries for DB operation, aborting", TxMaxRetries) return 0, fmt.Errorf("error: %v unsuccessful retries for DB operation, aborting", TxMaxRetries)
} }
func (db *DB) GetAllUsers() (map[int64]*User, error) { func (db *DB) GetAllUsers(c *Config) (map[int64]*User, error) {
query := "SELECT id, username, first_name, last_name, role FROM users" var aesFirstName, aesLastName, aesEmail string
var err error
query := "SELECT id, username, first_name, last_name, email, role FROM users"
rows, err := db.Query(query) rows, err := db.Query(query)
if err != nil { if err != nil {
@ -281,10 +461,25 @@ func (db *DB) GetAllUsers() (map[int64]*User, error) {
users := make(map[int64]*User, 0) 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, &user.FirstName, if err = rows.Scan(&user.ID, &user.UserName, &aesFirstName, &aesLastName, &aesEmail, &user.Role); err != nil {
&user.LastName, &user.Role); err != nil {
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)
if err != nil {
return nil, fmt.Errorf("error decrypting first name: %v", err)
}
user.LastName, err = aesDecrypt(c, aesLastName)
if err != nil {
return nil, fmt.Errorf("error decrypting last name: %v", err)
}
user.Email, err = aesDecrypt(c, aesEmail)
if err != nil {
return nil, fmt.Errorf("error decrypting email: %v", err)
}
users[user.ID] = user users[user.ID] = user
} }
@ -311,17 +506,10 @@ func (tx *Tx) SetPassword(id int64, newPass string) error {
return nil return nil
} }
func (db *DB) UpdateUserAttributes(id int64, user, first, last, newPass, newPass2 string, role int) error { func (db *DB) UpdateUserAttributes(c *Config, id int64, userName, firstName, lastName, email, newPass string, role int) error {
passwordEmpty := true
if len(newPass) > 0 || len(newPass2) > 0 {
if newPass != newPass2 {
return fmt.Errorf("error: passwords do not match")
}
passwordEmpty = false
}
tx := new(Tx)
var err error var err error
tx := new(Tx)
passwordEmpty := len(newPass) > 0
for i := 0; i < TxMaxRetries; i++ { for i := 0; i < TxMaxRetries; i++ {
err := func() error { err := func() error {
@ -339,10 +527,35 @@ func (db *DB) UpdateUserAttributes(id int64, user, first, last, newPass, newPass
} }
} }
aesFirstName, err := aesEncrypt(c, firstName)
if err != nil {
if rollbackErr := tx.Rollback(); rollbackErr != nil {
log.Fatalf("transaction error: %v, rollback error: %v", err, rollbackErr)
}
return fmt.Errorf("error encrypting first name: %v", err)
}
aesLastName, err := aesEncrypt(c, lastName)
if err != nil {
if rollbackErr := tx.Rollback(); rollbackErr != nil {
log.Fatalf("transaction error: %v, rollback error: %v", err, rollbackErr)
}
return fmt.Errorf("error encrypting last name: %v", err)
}
aesEmail, err := aesEncrypt(c, email)
if err != nil {
if rollbackErr := tx.Rollback(); rollbackErr != nil {
log.Fatalf("transaction error: %v, rollback error: %v", err, rollbackErr)
}
return fmt.Errorf("error encrypting email: %v", err)
}
if err = tx.UpdateAttributes( if err = tx.UpdateAttributes(
&Attribute{Table: "users", ID: id, AttName: "username", Value: user}, &Attribute{Table: "users", ID: id, AttName: "username", Value: userName},
&Attribute{Table: "users", ID: id, AttName: "first_name", Value: first}, &Attribute{Table: "users", ID: id, AttName: "first_name", Value: aesFirstName},
&Attribute{Table: "users", ID: id, AttName: "last_name", Value: last}, &Attribute{Table: "users", ID: id, AttName: "last_name", Value: aesLastName},
&Attribute{Table: "users", ID: id, AttName: "email", Value: aesEmail},
&Attribute{Table: "users", ID: id, AttName: "role", Value: role}, &Attribute{Table: "users", ID: id, AttName: "role", Value: role},
); err != nil { ); err != nil {
if rollbackErr := tx.Rollback(); rollbackErr != nil { if rollbackErr := tx.Rollback(); rollbackErr != nil {

26
cmd/calls/atom.go Normal file
View File

@ -0,0 +1,26 @@
package calls
import (
"log"
"net/http"
"path/filepath"
b "streifling.com/jason/cpolis/cmd/backend"
)
func ServeAtomFeed(c *b.Config) http.HandlerFunc {
return func(w http.ResponseWriter, r *http.Request) {
if !tokenIsVerified(w, r, c) {
return
}
absFilepath, err := filepath.Abs(c.AtomFeed)
if err != nil {
log.Println(err)
http.Error(w, err.Error(), http.StatusInternalServerError)
return
}
http.ServeFile(w, r, absFilepath)
}
}

View File

@ -8,7 +8,7 @@ import (
b "streifling.com/jason/cpolis/cmd/backend" b "streifling.com/jason/cpolis/cmd/backend"
) )
func ServeImage(c *b.Config, s *b.CookieStore) http.HandlerFunc { func ServeImage(c *b.Config) http.HandlerFunc {
return func(w http.ResponseWriter, r *http.Request) { return func(w http.ResponseWriter, r *http.Request) {
if !tokenIsVerified(w, r, c) { if !tokenIsVerified(w, r, c) {
return return

View File

@ -1,17 +0,0 @@
package calls
import (
"net/http"
b "streifling.com/jason/cpolis/cmd/backend"
)
func ServeRSS(c *b.Config) http.HandlerFunc {
return func(w http.ResponseWriter, r *http.Request) {
if !tokenIsVerified(w, r, c) {
return
}
http.ServeFile(w, r, c.RSSFile)
}
}

View File

@ -146,9 +146,12 @@ func SubmitArticle(c *b.Config, db *b.DB, s *b.CookieStore) http.HandlerFunc {
return return
} }
data := new(struct{ Role int })
data.Role = session.Values["role"].(int)
tmpl, err := template.ParseFiles(c.WebDir + "/templates/hub.html") tmpl, err := template.ParseFiles(c.WebDir + "/templates/hub.html")
tmpl = template.Must(tmpl, err) tmpl = template.Must(tmpl, err)
if err = tmpl.ExecuteTemplate(w, "page-content", session.Values["role"]); err != nil { if err = tmpl.ExecuteTemplate(w, "page-content", data); err != nil {
log.Println(err) log.Println(err)
http.Error(w, err.Error(), http.StatusInternalServerError) http.Error(w, err.Error(), http.StatusInternalServerError)
return return
@ -225,9 +228,12 @@ func ResubmitArticle(c *b.Config, db *b.DB, s *b.CookieStore) http.HandlerFunc {
return return
} }
data := new(struct{ Role int })
data.Role = session.Values["role"].(int)
tmpl, err := template.ParseFiles(c.WebDir + "/templates/hub.html") tmpl, err := template.ParseFiles(c.WebDir + "/templates/hub.html")
tmpl = template.Must(tmpl, err) tmpl = template.Must(tmpl, err)
if err = tmpl.ExecuteTemplate(w, "page-content", session.Values["role"]); err != nil { if err = tmpl.ExecuteTemplate(w, "page-content", data); err != nil {
log.Println(err) log.Println(err)
http.Error(w, err.Error(), http.StatusInternalServerError) http.Error(w, err.Error(), http.StatusInternalServerError)
return return
@ -342,7 +348,7 @@ func ReviewRejectedArticle(c *b.Config, db *b.DB, s *b.CookieStore) http.Handler
imgURL := strings.Split(data.Article.BannerLink, "/") imgURL := strings.Split(data.Article.BannerLink, "/")
imgFileName := imgURL[len(imgURL)-1] imgFileName := imgURL[len(imgURL)-1]
data.BannerImage, err = serveBase64Image(c, imgFileName) data.BannerImage, err = b.ServeBase64Image(c, imgFileName)
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)
@ -469,9 +475,12 @@ func PublishArticle(c *b.Config, db *b.DB, s *b.CookieStore) http.HandlerFunc {
return return
} }
data := new(struct{ Role int })
data.Role = session.Values["role"].(int)
tmpl, err := template.ParseFiles(c.WebDir + "/templates/hub.html") tmpl, err := template.ParseFiles(c.WebDir + "/templates/hub.html")
tmpl = template.Must(tmpl, err) tmpl = template.Must(tmpl, err)
if err = tmpl.ExecuteTemplate(w, "page-content", session.Values["role"]); err != nil { if err = tmpl.ExecuteTemplate(w, "page-content", data); err != nil {
log.Println(err) log.Println(err)
http.Error(w, err.Error(), http.StatusInternalServerError) http.Error(w, err.Error(), http.StatusInternalServerError)
return return
@ -501,9 +510,12 @@ func RejectArticle(c *b.Config, db *b.DB, s *b.CookieStore) http.HandlerFunc {
return return
} }
data := new(struct{ Role int })
data.Role = session.Values["role"].(int)
tmpl, err := template.ParseFiles(c.WebDir + "/templates/hub.html") tmpl, err := template.ParseFiles(c.WebDir + "/templates/hub.html")
tmpl = template.Must(tmpl, err) tmpl = template.Must(tmpl, err)
if err = tmpl.ExecuteTemplate(w, "page-content", session.Values["role"]); err != nil { if err = tmpl.ExecuteTemplate(w, "page-content", data); err != nil {
log.Println(err) log.Println(err)
http.Error(w, err.Error(), http.StatusInternalServerError) http.Error(w, err.Error(), http.StatusInternalServerError)
return return
@ -613,7 +625,7 @@ func ReviewArticle(c *b.Config, db *b.DB, s *b.CookieStore, action, title, butto
imgURL := strings.Split(article.BannerLink, "/") imgURL := strings.Split(article.BannerLink, "/")
imgFileName := imgURL[len(imgURL)-1] imgFileName := imgURL[len(imgURL)-1]
data.BannerImage, err = serveBase64Image(c, imgFileName) data.BannerImage, err = b.ServeBase64Image(c, imgFileName)
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)
@ -698,9 +710,12 @@ func DeleteArticle(c *b.Config, db *b.DB, s *b.CookieStore) http.HandlerFunc {
return return
} }
data := new(struct{ Role int })
data.Role = session.Values["role"].(int)
tmpl, err := template.ParseFiles(c.WebDir + "/templates/hub.html") tmpl, err := template.ParseFiles(c.WebDir + "/templates/hub.html")
tmpl = template.Must(tmpl, err) tmpl = template.Must(tmpl, err)
if err = tmpl.ExecuteTemplate(w, "page-content", session.Values["role"].(int)); err != nil { if err = tmpl.ExecuteTemplate(w, "page-content", data); err != nil {
log.Println(err) log.Println(err)
http.Error(w, err.Error(), http.StatusInternalServerError) http.Error(w, err.Error(), http.StatusInternalServerError)
return return
@ -755,8 +770,11 @@ func AllowEditArticle(c *b.Config, db *b.DB, s *b.CookieStore) http.HandlerFunc
return return
} }
data := new(struct{ Role int })
data.Role = session.Values["role"].(int)
tmpl := template.Must(template.ParseFiles(c.WebDir + "/templates/hub.html")) tmpl := template.Must(template.ParseFiles(c.WebDir + "/templates/hub.html"))
if err = tmpl.ExecuteTemplate(w, "page-content", session.Values["role"].(int)); err != nil { if err = tmpl.ExecuteTemplate(w, "page-content", data); err != nil {
log.Println(err) log.Println(err)
http.Error(w, err.Error(), http.StatusInternalServerError) http.Error(w, err.Error(), http.StatusInternalServerError)
return return
@ -790,7 +808,7 @@ func EditArticle(c *b.Config, db *b.DB, s *b.CookieStore) http.HandlerFunc {
imgURL := strings.Split(data.Article.BannerLink, "/") imgURL := strings.Split(data.Article.BannerLink, "/")
imgFileName := imgURL[len(imgURL)-1] imgFileName := imgURL[len(imgURL)-1]
data.BannerImage, err = serveBase64Image(c, imgFileName) data.BannerImage, err = b.ServeBase64Image(c, imgFileName)
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

@ -1,35 +1,14 @@
package frontend package frontend
import ( import (
"encoding/base64"
"encoding/json" "encoding/json"
"fmt"
"html/template" "html/template"
"io"
"log" "log"
"net/http" "net/http"
"os"
b "streifling.com/jason/cpolis/cmd/backend" b "streifling.com/jason/cpolis/cmd/backend"
) )
func serveBase64Image(c *b.Config, filename string) (string, error) {
file := c.PicsDir + "/" + filename
img, err := os.Open(file)
if err != nil {
return "", fmt.Errorf("error opening file %v: %v", file, err)
}
defer img.Close()
imgBytes, err := io.ReadAll(img)
if err != nil {
return "", fmt.Errorf("error turning %v into bytes: %v", file, err)
}
return base64.StdEncoding.EncodeToString(imgBytes), nil
}
func UploadImage(c *b.Config, s *b.CookieStore) http.HandlerFunc { func UploadImage(c *b.Config, s *b.CookieStore) http.HandlerFunc {
return func(w http.ResponseWriter, r *http.Request) { return func(w http.ResponseWriter, r *http.Request) {
if _, err := getSession(w, r, c, s); err != nil { if _, err := getSession(w, r, c, s); err != nil {
@ -52,7 +31,7 @@ func UploadImage(c *b.Config, s *b.CookieStore) http.HandlerFunc {
} }
defer file.Close() defer file.Close()
filename, err := b.SaveImage(c, file) filename, err := b.SaveImage(file, c.MaxImgHeight, c.MaxImgWidth, c.PicsDir+"/")
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)
@ -69,51 +48,6 @@ func UploadImage(c *b.Config, s *b.CookieStore) http.HandlerFunc {
} }
} }
func UploadIssueImage(c *b.Config, s *b.CookieStore) http.HandlerFunc {
return func(w http.ResponseWriter, r *http.Request) {
session, err := getSession(w, r, c, s)
if err != nil {
log.Println(err)
http.Error(w, err.Error(), http.StatusInternalServerError)
return
}
if err := r.ParseMultipartForm(10 << 20); err != nil {
log.Println(err)
http.Error(w, err.Error(), http.StatusBadRequest)
return
}
file, _, err := r.FormFile("issue-image")
if err != nil {
log.Println(err)
http.Error(w, err.Error(), http.StatusInternalServerError)
return
}
defer file.Close()
filename, err := b.SaveImage(c, file)
if err != nil {
if err == b.ErrUnsupportedFormat {
http.Error(w, "Das Dateiformat wird nicht unterstützt.", http.StatusBadRequest)
return
}
log.Println(err)
http.Error(w, err.Error(), http.StatusInternalServerError)
return
}
session.Values["issue-image"] = filename
if err = session.Save(r, w); err != nil {
log.Println(err)
http.Error(w, err.Error(), http.StatusInternalServerError)
return
}
w.WriteHeader(http.StatusOK)
}
}
func UploadBanner(c *b.Config, s *b.CookieStore, fileKey, htmlFile, htmlTemplate string) http.HandlerFunc { func UploadBanner(c *b.Config, s *b.CookieStore, fileKey, htmlFile, htmlTemplate string) http.HandlerFunc {
return func(w http.ResponseWriter, r *http.Request) { return func(w http.ResponseWriter, r *http.Request) {
if _, err := getSession(w, r, c, s); err != nil { if _, err := getSession(w, r, c, s); err != nil {
@ -130,7 +64,7 @@ func UploadBanner(c *b.Config, s *b.CookieStore, fileKey, htmlFile, htmlTemplate
} }
defer file.Close() defer file.Close()
filename, err := b.SaveImage(c, file) filename, err := b.SaveImage(file, c.MaxBannerHeight, c.MaxBannerWidth, c.PicsDir+"/")
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)
@ -141,7 +75,7 @@ func UploadBanner(c *b.Config, s *b.CookieStore, fileKey, htmlFile, htmlTemplate
return return
} }
base64Img, err := serveBase64Image(c, filename) base64Img, err := b.ServeBase64Image(c, filename)
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

@ -110,9 +110,12 @@ func PublishLatestIssue(c *b.Config, db *b.DB, s *b.CookieStore) http.HandlerFun
return return
} }
data := new(struct{ Role int })
data.Role = session.Values["role"].(int)
tmpl, err := template.ParseFiles(c.WebDir + "/templates/hub.html") tmpl, err := template.ParseFiles(c.WebDir + "/templates/hub.html")
tmpl = template.Must(tmpl, err) tmpl = template.Must(tmpl, err)
if err = tmpl.ExecuteTemplate(w, "page-content", session.Values["role"]); err != nil { if err = tmpl.ExecuteTemplate(w, "page-content", data); 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

@ -56,12 +56,18 @@ func HomePage(c *b.Config, db *b.DB, s *b.CookieStore) http.HandlerFunc {
log.Fatalln(err) log.Fatalln(err)
} }
data := new(struct {
Version string
Role int
})
data.Version = c.Version
files := make([]string, 2) files := make([]string, 2)
files[0] = c.WebDir + "/templates/index.html" files[0] = c.WebDir + "/templates/index.html"
if numRows == 0 { if numRows == 0 {
files[1] = c.WebDir + "/templates/first-user.html" files[1] = c.WebDir + "/templates/first-user.html"
tmpl, err := template.ParseFiles(files...) tmpl, err := template.ParseFiles(files...)
if err = template.Must(tmpl, err).Execute(w, nil); err != nil { if err = template.Must(tmpl, err).Execute(w, data); err != nil {
log.Println(err) log.Println(err)
http.Error(w, err.Error(), http.StatusInternalServerError) http.Error(w, err.Error(), http.StatusInternalServerError)
return return
@ -74,9 +80,10 @@ func HomePage(c *b.Config, db *b.DB, s *b.CookieStore) http.HandlerFunc {
return return
} }
if auth, ok := session.Values["authenticated"].(bool); auth && ok { if auth, ok := session.Values["authenticated"].(bool); auth && ok {
data.Role = session.Values["role"].(int)
files[1] = c.WebDir + "/templates/hub.html" files[1] = c.WebDir + "/templates/hub.html"
tmpl, err := template.ParseFiles(files...) tmpl, err := template.ParseFiles(files...)
if err = template.Must(tmpl, err).Execute(w, session.Values["role"]); err != nil { if err = template.Must(tmpl, err).Execute(w, data); err != nil {
log.Println(err) log.Println(err)
http.Error(w, err.Error(), http.StatusInternalServerError) http.Error(w, err.Error(), http.StatusInternalServerError)
return return
@ -84,7 +91,7 @@ func HomePage(c *b.Config, db *b.DB, s *b.CookieStore) http.HandlerFunc {
} else { } else {
files[1] = c.WebDir + "/templates/login.html" files[1] = c.WebDir + "/templates/login.html"
tmpl, err := template.ParseFiles(files...) tmpl, err := template.ParseFiles(files...)
if err = template.Must(tmpl, err).Execute(w, nil); err != nil { if err = template.Must(tmpl, err).Execute(w, data); err != nil {
log.Println(err) log.Println(err)
http.Error(w, err.Error(), http.StatusInternalServerError) http.Error(w, err.Error(), http.StatusInternalServerError)
return return
@ -111,7 +118,7 @@ func Login(c *b.Config, db *b.DB, s *b.CookieStore) http.HandlerFunc {
return return
} }
user, err := db.GetUser(id) user, err := db.GetUser(c, id)
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)
@ -125,7 +132,7 @@ func Login(c *b.Config, db *b.DB, s *b.CookieStore) http.HandlerFunc {
} }
tmpl, err := template.ParseFiles(c.WebDir + "/templates/hub.html") tmpl, err := template.ParseFiles(c.WebDir + "/templates/hub.html")
if err = template.Must(tmpl, err).ExecuteTemplate(w, "page-content", user.Role); err != nil { if err = template.Must(tmpl, err).ExecuteTemplate(w, "page-content", user); err != nil {
log.Println(err) log.Println(err)
http.Error(w, err.Error(), http.StatusInternalServerError) http.Error(w, err.Error(), http.StatusInternalServerError)
return return
@ -174,8 +181,11 @@ func ShowHub(c *b.Config, db *b.DB, s *b.CookieStore) http.HandlerFunc {
return return
} }
data := new(struct{ Role int })
data.Role = session.Values["role"].(int)
tmpl, err := template.ParseFiles(c.WebDir + "/templates/hub.html") tmpl, err := template.ParseFiles(c.WebDir + "/templates/hub.html")
if err = template.Must(tmpl, err).ExecuteTemplate(w, "page-content", session.Values["role"].(int)); err != nil { if err = template.Must(tmpl, err).ExecuteTemplate(w, "page-content", data); 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

@ -41,9 +41,12 @@ func AddTag(c *b.Config, db *b.DB, s *b.CookieStore) http.HandlerFunc {
} }
db.AddTag(tag) db.AddTag(tag)
data := new(struct{ Role int })
data.Role = session.Values["role"].(int)
tmpl, err := template.ParseFiles(c.WebDir + "/templates/hub.html") tmpl, err := template.ParseFiles(c.WebDir + "/templates/hub.html")
tmpl = template.Must(tmpl, err) tmpl = template.Must(tmpl, err)
if err = tmpl.ExecuteTemplate(w, "page-content", session.Values["role"]); err != nil { if err = tmpl.ExecuteTemplate(w, "page-content", data); 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

@ -11,8 +11,8 @@ import (
) )
func checkUserStrings(user *b.User) (string, int, bool) { func checkUserStrings(user *b.User) (string, int, bool) {
userLen := 15 userLen := 63 // max value for utf-8 at 255 bytes
nameLen := 50 nameLen := 56 // max value when aes encrypting utf-8 at up to 255 bytes
if len(user.UserName) > userLen { if len(user.UserName) > userLen {
return "Benutzername", userLen, false return "Benutzername", userLen, false
@ -55,12 +55,13 @@ func AddUser(c *b.Config, db *b.DB, s *b.CookieStore) http.HandlerFunc {
UserName: r.PostFormValue("username"), UserName: r.PostFormValue("username"),
FirstName: r.PostFormValue("first-name"), FirstName: r.PostFormValue("first-name"),
LastName: r.PostFormValue("last-name"), LastName: r.PostFormValue("last-name"),
Email: r.PostFormValue("email"),
} }
pass := r.PostFormValue("password") pass := r.PostFormValue("password")
pass2 := r.PostFormValue("password2") pass2 := r.PostFormValue("password2")
email2 := r.PostFormValue("email2")
if len(user.UserName) == 0 || len(user.FirstName) == 0 || if len(user.UserName) == 0 || len(user.FirstName) == 0 || len(user.LastName) == 0 || len(user.Email) == 0 || len(email2) == 0 || len(pass) == 0 || len(pass2) == 0 {
len(user.LastName) == 0 || len(pass) == 0 || len(pass2) == 0 {
http.Error(w, "Bitte alle Felder ausfüllen.", http.StatusBadRequest) http.Error(w, "Bitte alle Felder ausfüllen.", http.StatusBadRequest)
return return
} }
@ -76,6 +77,11 @@ func AddUser(c *b.Config, db *b.DB, s *b.CookieStore) http.HandlerFunc {
return return
} }
if user.Email != email2 {
http.Error(w, "Die Emailadressen stimmen nicht überein.", http.StatusBadRequest)
return
}
if pass != pass2 { if pass != pass2 {
http.Error(w, "Die Passwörter stimmen nicht überein.", http.StatusBadRequest) http.Error(w, "Die Passwörter stimmen nicht überein.", http.StatusBadRequest)
return return
@ -94,16 +100,19 @@ func AddUser(c *b.Config, db *b.DB, s *b.CookieStore) http.HandlerFunc {
return return
} }
_, err = db.AddUser(user, pass) _, err = db.AddUser(c, user, pass)
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)
return return
} }
data := new(struct{ Role int })
data.Role = session.Values["role"].(int)
tmpl, err := template.ParseFiles(c.WebDir + "/templates/hub.html") tmpl, err := template.ParseFiles(c.WebDir + "/templates/hub.html")
tmpl = template.Must(tmpl, err) tmpl = template.Must(tmpl, err)
if err = tmpl.ExecuteTemplate(w, "page-content", session.Values["role"].(int)); err != nil { if err = tmpl.ExecuteTemplate(w, "page-content", data); err != nil {
log.Println(err) log.Println(err)
http.Error(w, err.Error(), http.StatusInternalServerError) http.Error(w, err.Error(), http.StatusInternalServerError)
return return
@ -120,7 +129,7 @@ func EditSelf(c *b.Config, db *b.DB, s *b.CookieStore) http.HandlerFunc {
return return
} }
user, err := db.GetUser(session.Values["id"].(int64)) user, err := db.GetUser(c, session.Values["id"].(int64))
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)
@ -150,11 +159,13 @@ func UpdateSelf(c *b.Config, db *b.DB, s *b.CookieStore) http.HandlerFunc {
UserName: r.PostFormValue("username"), UserName: r.PostFormValue("username"),
FirstName: r.PostFormValue("first-name"), FirstName: r.PostFormValue("first-name"),
LastName: r.PostFormValue("last-name"), LastName: r.PostFormValue("last-name"),
Email: r.PostFormValue("email"),
} }
oldPass := r.PostFormValue("old-password") oldPass := r.PostFormValue("old-password")
newPass := r.PostFormValue("password") newPass := r.PostFormValue("password")
newPass2 := r.PostFormValue("password2") newPass2 := r.PostFormValue("password2")
email2 := r.PostFormValue("email2")
if len(user.UserName) == 0 { if len(user.UserName) == 0 {
http.Error(w, "Bitte den Benutzernamen ausfüllen.", http.StatusBadRequest) http.Error(w, "Bitte den Benutzernamen ausfüllen.", http.StatusBadRequest)
@ -166,6 +177,20 @@ func UpdateSelf(c *b.Config, db *b.DB, s *b.CookieStore) http.HandlerFunc {
return return
} }
if len(user.Email) == 0 || len(email2) == 0 {
http.Error(w, "Bitte die Emailadresse ausfüllen.", http.StatusBadRequest)
return
}
if user.Email != email2 {
http.Error(w, "Die Emailadressen stimmen nicht überein", http.StatusBadRequest)
return
}
if newPass != newPass2 {
http.Error(w, "Die Passwörter stimmen nicht überein", http.StatusBadRequest)
return
}
userString, stringLen, ok := checkUserStrings(user) userString, stringLen, ok := checkUserStrings(user)
if !ok { if !ok {
http.Error(w, fmt.Sprint(userString, " ist zu lang. Maximal ", stringLen, " Zeichen erlaubt."), http.StatusBadRequest) http.Error(w, fmt.Sprint(userString, " ist zu lang. Maximal ", stringLen, " Zeichen erlaubt."), http.StatusBadRequest)
@ -177,15 +202,18 @@ func UpdateSelf(c *b.Config, db *b.DB, s *b.CookieStore) http.HandlerFunc {
return return
} }
if err = db.UpdateOwnUserAttributes(user.ID, user.UserName, user.FirstName, user.LastName, oldPass, newPass, newPass2); err != nil { if err = db.UpdateOwnUserAttributes(c, user.ID, user.UserName, user.FirstName, user.LastName, user.Email, oldPass, newPass); err != nil {
log.Println("error: user:", user.ID, err) log.Println("error: user:", user.ID, err)
http.Error(w, "Benutzerdaten konnten nicht aktualisiert werden.", http.StatusInternalServerError) http.Error(w, "Benutzerdaten konnten nicht aktualisiert werden.", http.StatusInternalServerError)
return return
} }
data := new(struct{ Role int })
data.Role = session.Values["role"].(int)
tmpl, err := template.ParseFiles(c.WebDir + "/templates/hub.html") tmpl, err := template.ParseFiles(c.WebDir + "/templates/hub.html")
tmpl = template.Must(tmpl, err) tmpl = template.Must(tmpl, err)
if err = tmpl.ExecuteTemplate(w, "page-content", session.Values["role"].(int)); err != nil { if err = tmpl.ExecuteTemplate(w, "page-content", data); err != nil {
log.Println(err) log.Println(err)
http.Error(w, err.Error(), http.StatusInternalServerError) http.Error(w, err.Error(), http.StatusInternalServerError)
return return
@ -200,13 +228,14 @@ func AddFirstUser(c *b.Config, db *b.DB, s *b.CookieStore) http.HandlerFunc {
UserName: r.PostFormValue("username"), UserName: r.PostFormValue("username"),
FirstName: r.PostFormValue("first-name"), FirstName: r.PostFormValue("first-name"),
LastName: r.PostFormValue("last-name"), LastName: r.PostFormValue("last-name"),
Email: r.PostFormValue("email"),
Role: b.Admin, Role: b.Admin,
} }
pass := r.PostFormValue("password") pass := r.PostFormValue("password")
pass2 := r.PostFormValue("password2") pass2 := r.PostFormValue("password2")
email2 := r.PostFormValue("email2")
if len(user.UserName) == 0 || len(user.FirstName) == 0 || if len(user.UserName) == 0 || len(user.FirstName) == 0 || len(user.LastName) == 0 || len(user.Email) == 0 || len(email2) == 0 || len(pass) == 0 || len(pass2) == 0 {
len(user.LastName) == 0 || len(pass) == 0 || len(pass2) == 0 {
http.Error(w, "Bitte alle Felder ausfüllen.", http.StatusBadRequest) http.Error(w, "Bitte alle Felder ausfüllen.", http.StatusBadRequest)
return return
} }
@ -217,12 +246,17 @@ func AddFirstUser(c *b.Config, db *b.DB, s *b.CookieStore) http.HandlerFunc {
return return
} }
if user.Email != email2 {
http.Error(w, "Die Emailadressen stimmen nicht überein.", http.StatusBadRequest)
return
}
if pass != pass2 { if pass != pass2 {
http.Error(w, "Die Passwörter stimmen nicht überein.", http.StatusBadRequest) http.Error(w, "Die Passwörter stimmen nicht überein.", http.StatusBadRequest)
return return
} }
user.ID, err = db.AddFirstUser(user, pass) user.ID, err = db.AddFirstUser(c, user, pass)
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)
@ -245,8 +279,11 @@ func AddFirstUser(c *b.Config, db *b.DB, s *b.CookieStore) http.HandlerFunc {
return return
} }
data := new(struct{ Role int })
data.Role = 0
tmpl, err := template.ParseFiles(c.WebDir + "/templates/hub.html") tmpl, err := template.ParseFiles(c.WebDir + "/templates/hub.html")
if err = template.Must(tmpl, err).ExecuteTemplate(w, "page-content", 0); err != nil { if err = template.Must(tmpl, err).ExecuteTemplate(w, "page-content", data); err != nil {
log.Println(err) log.Println(err)
http.Error(w, err.Error(), http.StatusInternalServerError) http.Error(w, err.Error(), http.StatusInternalServerError)
return return
@ -269,7 +306,7 @@ func ShowAllUsers(c *b.Config, db *b.DB, s *b.CookieStore, action string) http.H
}) })
data.Action = action data.Action = action
data.Users, err = db.GetAllUsers() data.Users, err = db.GetAllUsers(c)
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)
@ -301,7 +338,7 @@ func EditUser(c *b.Config, db *b.DB, s *b.CookieStore) http.HandlerFunc {
return return
} }
user, err := db.GetUser(id) user, err := db.GetUser(c, id)
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)
@ -343,19 +380,34 @@ func UpdateUser(c *b.Config, db *b.DB, s *b.CookieStore) http.HandlerFunc {
user.UserName = r.PostFormValue("username") user.UserName = r.PostFormValue("username")
if len(user.UserName) == 0 { if len(user.UserName) == 0 {
http.Error(w, "Bitte den Benutzernamen ausfüllen.", http.StatusInternalServerError) http.Error(w, "Bitte den Benutzernamen ausfüllen.", http.StatusBadRequest)
return return
} }
user.FirstName = r.PostFormValue("first-name") user.FirstName = r.PostFormValue("first-name")
user.LastName = r.PostFormValue("last-name") user.LastName = r.PostFormValue("last-name")
if len(user.FirstName) == 0 || len(user.LastName) == 0 { if len(user.FirstName) == 0 || len(user.LastName) == 0 {
http.Error(w, "Bitte den vollständigen Namen ausfüllen.", http.StatusInternalServerError) http.Error(w, "Bitte den vollständigen Namen ausfüllen.", http.StatusBadRequest)
return
}
user.Email = r.PostFormValue("email")
email2 := r.PostFormValue("email2")
if len(user.Email) == 0 || len(email2) == 0 {
http.Error(w, "Bitte die Emailadresse ausfüllen.", http.StatusBadRequest)
return
}
if user.Email != email2 {
http.Error(w, "Die Emailadressen stimmen nicht überein.", http.StatusBadRequest)
return return
} }
newPass := r.PostFormValue("password") newPass := r.PostFormValue("password")
newPass2 := r.PostFormValue("password2") newPass2 := r.PostFormValue("password2")
if newPass != newPass2 {
http.Error(w, "Die Passwörter stimmen nicht überein.", http.StatusBadRequest)
return
}
userString, stringLen, ok := checkUserStrings(user) userString, stringLen, ok := checkUserStrings(user)
if !ok { if !ok {
@ -368,14 +420,17 @@ func UpdateUser(c *b.Config, db *b.DB, s *b.CookieStore) http.HandlerFunc {
return return
} }
if err = db.UpdateUserAttributes(user.ID, user.UserName, user.FirstName, user.LastName, newPass, newPass2, user.Role); err != nil { if err = db.UpdateUserAttributes(c, user.ID, user.UserName, user.FirstName, user.LastName, user.Email, newPass, user.Role); err != nil {
log.Println("error: user:", user.ID, err) log.Println("error: user:", user.ID, err)
http.Error(w, "Benutzerdaten konnten nicht aktualisiert werden.", http.StatusInternalServerError) http.Error(w, "Benutzerdaten konnten nicht aktualisiert werden.", http.StatusInternalServerError)
return return
} }
data := new(struct{ Role int })
data.Role = session.Values["role"].(int)
tmpl := template.Must(template.ParseFiles(c.WebDir + "/templates/hub.html")) tmpl := template.Must(template.ParseFiles(c.WebDir + "/templates/hub.html"))
if err = tmpl.ExecuteTemplate(w, "page-content", session.Values["role"].(int)); err != nil { if err = tmpl.ExecuteTemplate(w, "page-content", data); err != nil {
log.Println(err) log.Println(err)
http.Error(w, err.Error(), http.StatusInternalServerError) http.Error(w, err.Error(), http.StatusInternalServerError)
return return
@ -405,9 +460,12 @@ func DeleteUser(c *b.Config, db *b.DB, s *b.CookieStore) http.HandlerFunc {
return return
} }
data := new(struct{ Role int })
data.Role = session.Values["role"].(int)
tmpl, err := template.ParseFiles(c.WebDir + "/templates/hub.html") tmpl, err := template.ParseFiles(c.WebDir + "/templates/hub.html")
tmpl = template.Must(tmpl, err) tmpl = template.Must(tmpl, err)
if err = tmpl.ExecuteTemplate(w, "page-content", session.Values["role"].(int)); err != nil { if err = tmpl.ExecuteTemplate(w, "page-content", data); 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

@ -34,13 +34,13 @@ func main() {
} }
defer db.Close() defer db.Close()
key, err := b.LoadKey(config.KeyFile) key, err := b.LoadKey(config.GOBKeyFile)
if err != nil { if err != nil {
key, err = b.NewKey() key, err = b.NewKey()
if err != nil { if err != nil {
log.Fatalln(err) log.Fatalln(err)
} }
if err = b.SaveKey(key, config.KeyFile); err != nil { if err = b.SaveKey(key, config.GOBKeyFile); err != nil {
log.Fatalln(err) log.Fatalln(err)
} }
} }
@ -66,13 +66,13 @@ func main() {
mux.HandleFunc("GET /article/review-unpublished/{id}", f.ReviewArticle(config, db, store, "publish", "Artikel veröffentlichen", "Veröffentlichen")) mux.HandleFunc("GET /article/review-unpublished/{id}", f.ReviewArticle(config, db, store, "publish", "Artikel veröffentlichen", "Veröffentlichen"))
mux.HandleFunc("GET /article/serve/{id}", c.ServeArticle(config, db)) mux.HandleFunc("GET /article/serve/{id}", c.ServeArticle(config, db))
mux.HandleFunc("GET /article/write", f.WriteArticle(config, db, store)) mux.HandleFunc("GET /article/write", f.WriteArticle(config, db, store))
mux.HandleFunc("GET /atom/serve", c.ServeAtomFeed(config))
mux.HandleFunc("GET /hub", f.ShowHub(config, db, store)) mux.HandleFunc("GET /hub", f.ShowHub(config, db, store))
mux.HandleFunc("GET /image/serve/{pic}", c.ServeImage(config, store)) mux.HandleFunc("GET /image/serve/{pic}", c.ServeImage(config))
mux.HandleFunc("GET /issue/this", f.ShowCurrentIssue(config, db, store)) mux.HandleFunc("GET /issue/this", f.ShowCurrentIssue(config, db, store))
mux.HandleFunc("GET /logout", f.Logout(config, store)) mux.HandleFunc("GET /logout", f.Logout(config, store))
mux.HandleFunc("GET /pdf/get-list", c.ServePDFList(config)) mux.HandleFunc("GET /pdf/get-list", c.ServePDFList(config))
mux.HandleFunc("GET /pdf/serve/{id}", c.ServePDF(config)) mux.HandleFunc("GET /pdf/serve/{id}", c.ServePDF(config))
mux.HandleFunc("GET /rss/serve", c.ServeRSS(config))
mux.HandleFunc("GET /tag/create", f.CreateTag(config, store)) mux.HandleFunc("GET /tag/create", f.CreateTag(config, store))
mux.HandleFunc("GET /user/create", f.CreateUser(config, store)) mux.HandleFunc("GET /user/create", f.CreateUser(config, store))
mux.HandleFunc("GET /user/delete/{id}", f.DeleteUser(config, db, store)) mux.HandleFunc("GET /user/delete/{id}", f.DeleteUser(config, db, store))

View File

@ -5,12 +5,14 @@ DROP TABLE IF EXISTS issues;
DROP TABLE IF EXISTS users; DROP TABLE IF EXISTS users;
CREATE TABLE users ( CREATE TABLE users (
id INT AUTO_INCREMENT, id INT AUTO_INCREMENT,
username VARCHAR(15) NOT NULL UNIQUE, username VARCHAR(255) NOT NULL UNIQUE,
password VARCHAR(60) NOT NULL, password VARCHAR(60) NOT NULL,
first_name VARCHAR(50) NOT NULL, first_name VARCHAR(255) NOT NULL,
last_name VARCHAR(50) NOT NULL, last_name VARCHAR(255) NOT NULL,
role INT NOT NULL, email VARCHAR(255) NOT NULL,
-- profile_pic_link VARCHAR(255) NOT NULL,
role INT NOT NULL,
PRIMARY KEY (id) PRIMARY KEY (id)
); );
@ -24,12 +26,9 @@ CREATE TABLE articles (
id INT AUTO_INCREMENT, id INT AUTO_INCREMENT,
title VARCHAR(255) NOT NULL, title VARCHAR(255) NOT NULL,
created TIMESTAMP DEFAULT CURRENT_TIMESTAMP, created TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
banner_link VARCHAR(255), banner_link VARCHAR(255) NOT NULL,
summary TEXT NOT NULL, summary TEXT NOT NULL,
content_link VARCHAR(255), content_link VARCHAR(255) NOT NULL,
enc_url VARCHAR(255),
enc_length INT,
enc_type VARCHAR(255),
published BOOL NOT NULL, published BOOL NOT NULL,
rejected BOOL NOT NULL, rejected BOOL NOT NULL,
author_id INT NOT NULL, author_id INT NOT NULL,

23
go.mod
View File

@ -3,9 +3,8 @@ module streifling.com/jason/cpolis
go 1.23.2 go 1.23.2
require ( require (
firebase.google.com/go/v4 v4.14.1 firebase.google.com/go/v4 v4.15.0
git.streifling.com/jason/atom v1.0.0 git.streifling.com/jason/atom v1.0.0
git.streifling.com/jason/rss v0.1.3
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
@ -16,19 +15,19 @@ require (
github.com/yuin/goldmark v1.7.8 github.com/yuin/goldmark v1.7.8
golang.org/x/crypto v0.28.0 golang.org/x/crypto v0.28.0
golang.org/x/term v0.25.0 golang.org/x/term v0.25.0
google.golang.org/api v0.201.0 google.golang.org/api v0.203.0
) )
require ( require (
cel.dev/expr v0.16.2 // indirect cel.dev/expr v0.18.0 // indirect
cloud.google.com/go v0.116.0 // indirect cloud.google.com/go v0.116.0 // indirect
cloud.google.com/go/auth v0.9.8 // indirect cloud.google.com/go/auth v0.9.9 // indirect
cloud.google.com/go/auth/oauth2adapt v0.2.4 // indirect cloud.google.com/go/auth/oauth2adapt v0.2.4 // indirect
cloud.google.com/go/compute/metadata v0.5.2 // indirect cloud.google.com/go/compute/metadata v0.5.2 // indirect
cloud.google.com/go/firestore v1.17.0 // indirect cloud.google.com/go/firestore v1.17.0 // indirect
cloud.google.com/go/iam v1.2.1 // indirect cloud.google.com/go/iam v1.2.2 // indirect
cloud.google.com/go/longrunning v0.6.1 // indirect cloud.google.com/go/longrunning v0.6.2 // indirect
cloud.google.com/go/monitoring v1.21.1 // indirect cloud.google.com/go/monitoring v1.21.2 // indirect
cloud.google.com/go/storage v1.45.0 // indirect cloud.google.com/go/storage v1.45.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.24.3 // indirect github.com/GoogleCloudPlatform/opentelemetry-operations-go/detectors/gcp v1.24.3 // indirect
@ -70,10 +69,10 @@ require (
golang.org/x/text v0.19.0 // indirect golang.org/x/text v0.19.0 // indirect
golang.org/x/time v0.7.0 // indirect golang.org/x/time v0.7.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-20241015192408-796eee8c2d53 // indirect google.golang.org/genproto v0.0.0-20241021214115-324edc3d5d38 // indirect
google.golang.org/genproto/googleapis/api v0.0.0-20241015192408-796eee8c2d53 // indirect google.golang.org/genproto/googleapis/api v0.0.0-20241021214115-324edc3d5d38 // indirect
google.golang.org/genproto/googleapis/rpc v0.0.0-20241015192408-796eee8c2d53 // indirect google.golang.org/genproto/googleapis/rpc v0.0.0-20241021214115-324edc3d5d38 // indirect
google.golang.org/grpc v1.67.1 // indirect google.golang.org/grpc v1.67.1 // indirect
google.golang.org/grpc/stats/opentelemetry v0.0.0-20241018153737-98959d9a4904 // indirect google.golang.org/grpc/stats/opentelemetry v0.0.0-20241025232817-cb329375b14e // indirect
google.golang.org/protobuf v1.35.1 // indirect google.golang.org/protobuf v1.35.1 // indirect
) )

50
go.sum
View File

@ -1,36 +1,34 @@
cel.dev/expr v0.16.2 h1:RwRhoH17VhAu9U5CMvMhH1PDVgf0tuz9FT+24AfMLfU= cel.dev/expr v0.18.0 h1:CJ6drgk+Hf96lkLikr4rFf19WrU0BOWEihyZnI2TAzo=
cel.dev/expr v0.16.2/go.mod h1:gXngZQMkWJoSbE8mOzehJlXQyubn/Vg0vR9/F3W7iw8= cel.dev/expr v0.18.0/go.mod h1:MrpN08Q+lEBs+bGYdLxxHkZoUSsCp0nSKTs0nTymJgw=
cloud.google.com/go v0.26.0/go.mod h1:aQUYkXzVsufM+DwF1aE+0xfcU+56JwCaLick0ClmMTw= cloud.google.com/go v0.26.0/go.mod h1:aQUYkXzVsufM+DwF1aE+0xfcU+56JwCaLick0ClmMTw=
cloud.google.com/go v0.116.0 h1:B3fRrSDkLRt5qSHWe40ERJvhvnQwdZiHu0bJOpldweE= cloud.google.com/go v0.116.0 h1:B3fRrSDkLRt5qSHWe40ERJvhvnQwdZiHu0bJOpldweE=
cloud.google.com/go v0.116.0/go.mod h1:cEPSRWPzZEswwdr9BxE6ChEn01dWlTaF05LiC2Xs70U= cloud.google.com/go v0.116.0/go.mod h1:cEPSRWPzZEswwdr9BxE6ChEn01dWlTaF05LiC2Xs70U=
cloud.google.com/go/auth v0.9.8 h1:+CSJ0Gw9iVeSENVCKJoLHhdUykDgXSc4Qn+gu2BRtR8= cloud.google.com/go/auth v0.9.9 h1:BmtbpNQozo8ZwW2t7QJjnrQtdganSdmqeIBxHxNkEZQ=
cloud.google.com/go/auth v0.9.8/go.mod h1:xxA5AqpDrvS+Gkmo9RqrGGRh6WSNKKOXhY3zNOr38tI= cloud.google.com/go/auth v0.9.9/go.mod h1:xxA5AqpDrvS+Gkmo9RqrGGRh6WSNKKOXhY3zNOr38tI=
cloud.google.com/go/auth/oauth2adapt v0.2.4 h1:0GWE/FUsXhf6C+jAkWgYm7X9tK8cuEIfy19DBn6B6bY= cloud.google.com/go/auth/oauth2adapt v0.2.4 h1:0GWE/FUsXhf6C+jAkWgYm7X9tK8cuEIfy19DBn6B6bY=
cloud.google.com/go/auth/oauth2adapt v0.2.4/go.mod h1:jC/jOpwFP6JBxhB3P5Rr0a9HLMC/Pe3eaL4NmdvqPtc= cloud.google.com/go/auth/oauth2adapt v0.2.4/go.mod h1:jC/jOpwFP6JBxhB3P5Rr0a9HLMC/Pe3eaL4NmdvqPtc=
cloud.google.com/go/compute/metadata v0.5.2 h1:UxK4uu/Tn+I3p2dYWTfiX4wva7aYlKixAHn3fyqngqo= cloud.google.com/go/compute/metadata v0.5.2 h1:UxK4uu/Tn+I3p2dYWTfiX4wva7aYlKixAHn3fyqngqo=
cloud.google.com/go/compute/metadata v0.5.2/go.mod h1:C66sj2AluDcIqakBq/M8lw8/ybHgOZqin2obFxa/E5k= cloud.google.com/go/compute/metadata v0.5.2/go.mod h1:C66sj2AluDcIqakBq/M8lw8/ybHgOZqin2obFxa/E5k=
cloud.google.com/go/firestore v1.17.0 h1:iEd1LBbkDZTFsLw3sTH50eyg4qe8eoG6CjocmEXO9aQ= cloud.google.com/go/firestore v1.17.0 h1:iEd1LBbkDZTFsLw3sTH50eyg4qe8eoG6CjocmEXO9aQ=
cloud.google.com/go/firestore v1.17.0/go.mod h1:69uPx1papBsY8ZETooc71fOhoKkD70Q1DwMrtKuOT/Y= cloud.google.com/go/firestore v1.17.0/go.mod h1:69uPx1papBsY8ZETooc71fOhoKkD70Q1DwMrtKuOT/Y=
cloud.google.com/go/iam v1.2.1 h1:QFct02HRb7H12J/3utj0qf5tobFh9V4vR6h9eX5EBRU= cloud.google.com/go/iam v1.2.2 h1:ozUSofHUGf/F4tCNy/mu9tHLTaxZFLOUiKzjcgWHGIA=
cloud.google.com/go/iam v1.2.1/go.mod h1:3VUIJDPpwT6p/amXRC5GY8fCCh70lxPygguVtI0Z4/g= cloud.google.com/go/iam v1.2.2/go.mod h1:0Ys8ccaZHdI1dEUilwzqng/6ps2YB6vRsjIe00/+6JY=
cloud.google.com/go/logging v1.11.0 h1:v3ktVzXMV7CwHq1MBF65wcqLMA7i+z3YxbUsoK7mOKs= cloud.google.com/go/logging v1.12.0 h1:ex1igYcGFd4S/RZWOCU51StlIEuey5bjqwH9ZYjHibk=
cloud.google.com/go/logging v1.11.0/go.mod h1:5LDiJC/RxTt+fHc1LAt20R9TKiUTReDg6RuuFOZ67+A= cloud.google.com/go/logging v1.12.0/go.mod h1:wwYBt5HlYP1InnrtYI0wtwttpVU1rifnMT7RejksUAM=
cloud.google.com/go/longrunning v0.6.1 h1:lOLTFxYpr8hcRtcwWir5ITh1PAKUD/sG2lKrTSYjyMc= cloud.google.com/go/longrunning v0.6.2 h1:xjDfh1pQcWPEvnfjZmwjKQEcHnpz6lHjfy7Fo0MK+hc=
cloud.google.com/go/longrunning v0.6.1/go.mod h1:nHISoOZpBcmlwbJmiVk5oDRz0qG/ZxPynEGs1iZ79s0= cloud.google.com/go/longrunning v0.6.2/go.mod h1:k/vIs83RN4bE3YCswdXC5PFfWVILjm3hpEUlSko4PiI=
cloud.google.com/go/monitoring v1.21.1 h1:zWtbIoBMnU5LP9A/fz8LmWMGHpk4skdfeiaa66QdFGc= cloud.google.com/go/monitoring v1.21.2 h1:FChwVtClH19E7pJ+e0xUhJPGksctZNVOk2UhMmblmdU=
cloud.google.com/go/monitoring v1.21.1/go.mod h1:Rj++LKrlht9uBi8+Eb530dIrzG/cU/lB8mt+lbeFK1c= cloud.google.com/go/monitoring v1.21.2/go.mod h1:hS3pXvaG8KgWTSz+dAdyzPrGUYmi2Q+WFX8g2hqVEZU=
cloud.google.com/go/storage v1.45.0 h1:5av0QcIVj77t+44mV4gffFC/LscFRUhto6UBMB5SimM= cloud.google.com/go/storage v1.45.0 h1:5av0QcIVj77t+44mV4gffFC/LscFRUhto6UBMB5SimM=
cloud.google.com/go/storage v1.45.0/go.mod h1:wpPblkIuMP5jCB/E48Pz9zIo2S/zD8g+ITmxKkPCITE= cloud.google.com/go/storage v1.45.0/go.mod h1:wpPblkIuMP5jCB/E48Pz9zIo2S/zD8g+ITmxKkPCITE=
cloud.google.com/go/trace v1.11.1 h1:UNqdP+HYYtnm6lb91aNA5JQ0X14GnxkABGlfz2PzPew= cloud.google.com/go/trace v1.11.1 h1:UNqdP+HYYtnm6lb91aNA5JQ0X14GnxkABGlfz2PzPew=
cloud.google.com/go/trace v1.11.1/go.mod h1:IQKNQuBzH72EGaXEodKlNJrWykGZxet2zgjtS60OtjA= cloud.google.com/go/trace v1.11.1/go.mod h1:IQKNQuBzH72EGaXEodKlNJrWykGZxet2zgjtS60OtjA=
filippo.io/edwards25519 v1.1.0 h1:FNf4tywRC1HmFuKW5xopWpigGjJKiJSV0Cqo0cJWDaA= 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.14.1 h1:4qiUETaFRWoFGE1XP5VbcEdtPX93Qs+8B/7KvP2825g= firebase.google.com/go/v4 v4.15.0 h1:k27M+cHbyN1YpBI2Cf4NSjeHnnYRB9ldXwpqA5KikN0=
firebase.google.com/go/v4 v4.14.1/go.mod h1:fgk2XshgNDEKaioKco+AouiegSI9oTWVqRaBdTTGBoM= firebase.google.com/go/v4 v4.15.0/go.mod h1:S/4MJqVZn1robtXkHhpRUbwOC4gdYtgsiMMJQ4x+xmQ=
git.streifling.com/jason/atom v1.0.0 h1:E88z4S7JeT6T+WuAaJWnGwCWTx+vzSJ6giUL51MdptI= git.streifling.com/jason/atom v1.0.0 h1:E88z4S7JeT6T+WuAaJWnGwCWTx+vzSJ6giUL51MdptI=
git.streifling.com/jason/atom v1.0.0/go.mod h1:FNTYJfatYaIOQn4OKy8y+Mtohqm3MeyEGZUu4bMtZ9E= git.streifling.com/jason/atom v1.0.0/go.mod h1:FNTYJfatYaIOQn4OKy8y+Mtohqm3MeyEGZUu4bMtZ9E=
git.streifling.com/jason/rss v0.1.3 h1:fd3j4ZtcLehapcmmroo3AP3X34gRHC4xzpfV6bDV1ZU=
git.streifling.com/jason/rss v0.1.3/go.mod h1:gpZF0nZbQSstMpyHD9DTAvlQEG7v4pjO5c7aIMWM4Jg=
github.com/BurntSushi/toml v0.3.1/go.mod h1:xHWCNGjB5oqiDr8zfno3MHue2Ht5sIBksp03qcyfWMU= github.com/BurntSushi/toml v0.3.1/go.mod h1:xHWCNGjB5oqiDr8zfno3MHue2Ht5sIBksp03qcyfWMU=
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=
@ -227,8 +225,8 @@ 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.201.0 h1:+7AD9JNM3tREtawRMu8sOjSbb8VYcYXJG/2eEOmfDu0= google.golang.org/api v0.203.0 h1:SrEeuwU3S11Wlscsn+LA1kb/Y5xT8uggJSkIhD08NAU=
google.golang.org/api v0.201.0/go.mod h1:HVY0FCHVs89xIW9fzf/pBvOEm+OolHa86G/txFezyq4= google.golang.org/api v0.203.0/go.mod h1:BuOVyCSYEPwJb3npWvDnNmFI92f3GeRnHNkETneT3SI=
google.golang.org/appengine v1.1.0/go.mod h1:EbEs0AVv82hx2wNQdGPgUI5lhzA/G0D9YwlJXL52JkM= google.golang.org/appengine v1.1.0/go.mod h1:EbEs0AVv82hx2wNQdGPgUI5lhzA/G0D9YwlJXL52JkM=
google.golang.org/appengine v1.4.0/go.mod h1:xpcJRLb0r/rnEns0DIKYYv+WjYCduHsrkT7/EB5XEv4= google.golang.org/appengine v1.4.0/go.mod h1:xpcJRLb0r/rnEns0DIKYYv+WjYCduHsrkT7/EB5XEv4=
google.golang.org/appengine/v2 v2.0.6 h1:LvPZLGuchSBslPBp+LAhihBeGSiRh1myRoYK4NtuBIw= google.golang.org/appengine/v2 v2.0.6 h1:LvPZLGuchSBslPBp+LAhihBeGSiRh1myRoYK4NtuBIw=
@ -236,12 +234,12 @@ google.golang.org/appengine/v2 v2.0.6/go.mod h1:WoEXGoXNfa0mLvaH5sV3ZSGXwVmy8yf7
google.golang.org/genproto v0.0.0-20180817151627-c66870c02cf8/go.mod h1:JiN7NxoALGmiZfu7CAH4rXhgtRTLTxftemlI0sWmxmc= google.golang.org/genproto v0.0.0-20180817151627-c66870c02cf8/go.mod h1:JiN7NxoALGmiZfu7CAH4rXhgtRTLTxftemlI0sWmxmc=
google.golang.org/genproto v0.0.0-20190819201941-24fa4b261c55/go.mod h1:DMBHOl98Agz4BDEuKkezgsaosCRResVns1a3J2ZsMNc= google.golang.org/genproto v0.0.0-20190819201941-24fa4b261c55/go.mod h1:DMBHOl98Agz4BDEuKkezgsaosCRResVns1a3J2ZsMNc=
google.golang.org/genproto v0.0.0-20200526211855-cb27e3aa2013/go.mod h1:NbSheEEYHJ7i3ixzK3sjbqSGDJWnxyFXZblF3eUsNvo= google.golang.org/genproto v0.0.0-20200526211855-cb27e3aa2013/go.mod h1:NbSheEEYHJ7i3ixzK3sjbqSGDJWnxyFXZblF3eUsNvo=
google.golang.org/genproto v0.0.0-20241015192408-796eee8c2d53 h1:Df6WuGvthPzc+JiQ/G+m+sNX24kc0aTBqoDN/0yyykE= google.golang.org/genproto v0.0.0-20241021214115-324edc3d5d38 h1:Q3nlH8iSQSRUwOskjbcSMcF2jiYMNiQYZ0c2KEJLKKU=
google.golang.org/genproto v0.0.0-20241015192408-796eee8c2d53/go.mod h1:fheguH3Am2dGp1LfXkrvwqC/KlFq8F0nLq3LryOMrrE= google.golang.org/genproto v0.0.0-20241021214115-324edc3d5d38/go.mod h1:xBI+tzfqGGN2JBeSebfKXFSdBpWVQ7sLW40PTupVRm4=
google.golang.org/genproto/googleapis/api v0.0.0-20241015192408-796eee8c2d53 h1:fVoAXEKA4+yufmbdVYv+SE73+cPZbbbe8paLsHfkK+U= google.golang.org/genproto/googleapis/api v0.0.0-20241021214115-324edc3d5d38 h1:2oV8dfuIkM1Ti7DwXc0BJfnwr9csz4TDXI9EmiI+Rbw=
google.golang.org/genproto/googleapis/api v0.0.0-20241015192408-796eee8c2d53/go.mod h1:riSXTwQ4+nqmPGtobMFyW5FqVAmIs0St6VPp4Ug7CE4= google.golang.org/genproto/googleapis/api v0.0.0-20241021214115-324edc3d5d38/go.mod h1:vuAjtvlwkDKF6L1GQ0SokiRLCGFfeBUXWr/aFFkHACc=
google.golang.org/genproto/googleapis/rpc v0.0.0-20241015192408-796eee8c2d53 h1:X58yt85/IXCx0Y3ZwN6sEIKZzQtDEYaBWrDvErdXrRE= google.golang.org/genproto/googleapis/rpc v0.0.0-20241021214115-324edc3d5d38 h1:zciRKQ4kBpFgpfC5QQCVtnnNAcLIqweL7plyZRQHVpI=
google.golang.org/genproto/googleapis/rpc v0.0.0-20241015192408-796eee8c2d53/go.mod h1:GX3210XPVPUjJbTUbvwI8f2IpZDMZuPJWDzDuebbviI= google.golang.org/genproto/googleapis/rpc v0.0.0-20241021214115-324edc3d5d38/go.mod h1:GX3210XPVPUjJbTUbvwI8f2IpZDMZuPJWDzDuebbviI=
google.golang.org/grpc v1.19.0/go.mod h1:mqu4LbDTu4XGKhr4mRzUsmM4RtVoemTSY81AxZiDr8c= google.golang.org/grpc v1.19.0/go.mod h1:mqu4LbDTu4XGKhr4mRzUsmM4RtVoemTSY81AxZiDr8c=
google.golang.org/grpc v1.23.0/go.mod h1:Y5yQAOtifL1yxbo5wqy6BxZv8vAUGQwXBOALyacEbxg= google.golang.org/grpc v1.23.0/go.mod h1:Y5yQAOtifL1yxbo5wqy6BxZv8vAUGQwXBOALyacEbxg=
google.golang.org/grpc v1.25.1/go.mod h1:c3i+UQWmh7LiEpx4sFZnkU36qjEYZ0imhYfXVyQciAY= google.golang.org/grpc v1.25.1/go.mod h1:c3i+UQWmh7LiEpx4sFZnkU36qjEYZ0imhYfXVyQciAY=
@ -249,8 +247,8 @@ google.golang.org/grpc v1.27.0/go.mod h1:qbnxyOmOxrQa7FizSgH+ReBfzJrCY1pSN7KXBS8
google.golang.org/grpc v1.33.2/go.mod h1:JMHMWHQWaTccqQQlmk3MJZS+GWXOdAesneDmEnv2fbc= google.golang.org/grpc v1.33.2/go.mod h1:JMHMWHQWaTccqQQlmk3MJZS+GWXOdAesneDmEnv2fbc=
google.golang.org/grpc v1.67.1 h1:zWnc1Vrcno+lHZCOofnIMvycFcc0QRGIzm9dhnDX68E= google.golang.org/grpc v1.67.1 h1:zWnc1Vrcno+lHZCOofnIMvycFcc0QRGIzm9dhnDX68E=
google.golang.org/grpc v1.67.1/go.mod h1:1gLDyUQU7CTLJI90u3nXZ9ekeghjeM7pTDZlqFNg2AA= google.golang.org/grpc v1.67.1/go.mod h1:1gLDyUQU7CTLJI90u3nXZ9ekeghjeM7pTDZlqFNg2AA=
google.golang.org/grpc/stats/opentelemetry v0.0.0-20241018153737-98959d9a4904 h1:Lplo3VKrYtWeryBkDI4SZ4kJTFaWO4qUGs+xX7N2bFc= google.golang.org/grpc/stats/opentelemetry v0.0.0-20241025232817-cb329375b14e h1:SoMI+r+Qsp379U9BlVzrHtqAqYP3NEv9vNhYqUaAWOg=
google.golang.org/grpc/stats/opentelemetry v0.0.0-20241018153737-98959d9a4904/go.mod h1:jzYlkSMbKypzuu6xoAEijsNVo9ZeDF1u/zCfFgsx7jg= google.golang.org/grpc/stats/opentelemetry v0.0.0-20241025232817-cb329375b14e/go.mod h1:jzYlkSMbKypzuu6xoAEijsNVo9ZeDF1u/zCfFgsx7jg=
google.golang.org/protobuf v0.0.0-20200109180630-ec00e32a8dfd/go.mod h1:DFci5gLYBciE7Vtevhsrf46CRTquxDuWsQurQQe4oz8= google.golang.org/protobuf v0.0.0-20200109180630-ec00e32a8dfd/go.mod h1:DFci5gLYBciE7Vtevhsrf46CRTquxDuWsQurQQe4oz8=
google.golang.org/protobuf v0.0.0-20200221191635-4d8936d0db64/go.mod h1:kwYJMbMJ01Woi6D6+Kah6886xMZcty6N08ah7+eCXa0= google.golang.org/protobuf v0.0.0-20200221191635-4d8936d0db64/go.mod h1:kwYJMbMJ01Woi6D6+Kah6886xMZcty6N08ah7+eCXa0=
google.golang.org/protobuf v0.0.0-20200228230310-ab0ca4ff8a60/go.mod h1:cfTl7dwQJ+fmap5saPgwCLgHXTUD7jkjRqWcaiX5VyM= google.golang.org/protobuf v0.0.0-20200228230310-ab0ca4ff8a60/go.mod h1:cfTl7dwQJ+fmap5saPgwCLgHXTUD7jkjRqWcaiX5VyM=

View File

@ -32,11 +32,11 @@ textarea {
} }
.btn { .btn {
@apply bg-slate-200 dark:bg-slate-800 hover:bg-slate-100 dark:hover:bg-slate-900 border border-slate-200 dark:border-slate-800 my-2 px-3 py-2 rounded-md w-full; @apply bg-slate-200 dark:bg-slate-800 hover:bg-slate-100 dark:hover:bg-slate-900 border border-slate-200 dark:border-slate-800 cursor-pointer my-2 px-3 py-2 rounded-md w-full;
} }
.action-btn { .action-btn {
@apply bg-slate-800 dark:bg-slate-200 hover:bg-slate-700 dark:hover:bg-slate-300 my-2 px-3 py-2 rounded-md text-slate-50 dark:text-slate-950 w-full; @apply bg-slate-800 dark:bg-slate-200 hover:bg-slate-700 dark:hover:bg-slate-300 cursor-pointer my-2 px-3 py-2 rounded-md text-slate-50 dark:text-slate-950 w-full;
} }
.EasyMDEContainer .CodeMirror { .EasyMDEContainer .CodeMirror {

View File

@ -7,22 +7,36 @@
<label for="username">Benutzername</label> <label for="username">Benutzername</label>
<input class="w-full" required name="username" type="text" value="{{.UserName}}" /> <input class="w-full" required name="username" type="text" value="{{.UserName}}" />
</div> </div>
<div> <div>
<label for="password">Passwort</label> <label for="password">Passwort</label>
<input class="w-full" required name="password" placeholder="***" type="password" /> <input class="w-full" required name="password" placeholder="***" type="password" />
</div> </div>
<div> <div>
<label for="password2">Passwort wiederholen</label> <label for="password2">Passwort wiederholen</label>
<input class="w-full" required name="password2" placeholder="***" type="password" /> <input class="w-full" required name="password2" placeholder="***" type="password" />
</div> </div>
<div> <div>
<label for="first-name">Vorname</label> <label for="first-name">Vorname</label>
<input class="w-full" required name="first-name" type="text" value="{{.FirstName}}" /> <input class="w-full" required name="first-name" type="text" value="{{.FirstName}}" />
</div> </div>
<div> <div>
<label for="last-name">Nachname</label> <label for="last-name">Nachname</label>
<input class="w-full" required name="last-name" type="text" value="{{.LastName}}" /> <input class="w-full" required name="last-name" type="text" value="{{.LastName}}" />
</div> </div>
<div>
<label for="email">Emailadresse</label>
<input class="w-full" required name="email" type="text" value="{{.Email}}" />
</div>
<div>
<label for="email2">Emailadresse wiederholen</label>
<input class="w-full" required name="email2" type="text" value="{{.Email}}" />
</div>
</div> </div>
<div class="flex flex-wrap gap-4"> <div class="flex flex-wrap gap-4">

View File

@ -13,7 +13,7 @@
</div> </div>
<div class="grid grid-cols-1 items-center"> <div class="grid grid-cols-1 items-center">
<label class="btn cursor-pointer text-center" for="issue-banner-upload">Titelbild</label> <label class="btn text-center" for="issue-banner-upload">Titelbild</label>
<input class="hidden" id="issue-banner-upload" name="issue-banner" type="file" required <input class="hidden" id="issue-banner-upload" name="issue-banner" type="file" required
hx-post="/issue/upload-banner" hx-target="#issue-banner-container" /> hx-post="/issue/upload-banner" hx-target="#issue-banner-container" />
</div> </div>

View File

@ -32,6 +32,16 @@
<label for="password2">Passwort wiederholen</label> <label for="password2">Passwort wiederholen</label>
<input class="w-full" name="password2" placeholder="***" type="password" /> <input class="w-full" name="password2" placeholder="***" type="password" />
</div> </div>
<div>
<label for="email">Emailadresse</label>
<input class="w-full" required name="email" type="text" value="{{.Email}}" />
</div>
<div>
<label for="email2">Emailadresse wiederholen</label>
<input class="w-full" required name="email2" type="text" value="{{.Email}}" />
</div>
</div> </div>
<div class="btn-area"> <div class="btn-area">

View File

@ -27,6 +27,16 @@
<label for="password2">Passwort wiederholen</label> <label for="password2">Passwort wiederholen</label>
<input class="w-full" name="password2" placeholder="***" type="password" /> <input class="w-full" name="password2" placeholder="***" type="password" />
</div> </div>
<div>
<label for="email">Emailadresse</label>
<input class="w-full" required name="email" type="text" value="{{.Email}}" />
</div>
<div>
<label for="email2">Emailadresse wiederholen</label>
<input class="w-full" required name="email2" type="text" value="{{.Email}}" />
</div>
</div> </div>
<div class="flex flex-wrap gap-4"> <div class="flex flex-wrap gap-4">

View File

@ -15,7 +15,7 @@
</div> </div>
<div class="grid grid-cols-1 items-center"> <div class="grid grid-cols-1 items-center">
<label class="btn cursor-pointer text-center" for="article-banner">Titelbild</label> <label class="btn text-center" for="article-banner">Titelbild</label>
<input class="hidden" id="article-banner" name="article-banner" type="file" required <input class="hidden" id="article-banner" name="article-banner" type="file" required
hx-post="/article/upload-banner" hx-target="#article-banner-container" /> hx-post="/article/upload-banner" hx-target="#article-banner-container" />
</div> </div>

View File

@ -5,23 +5,37 @@
<div class="grid grid-cols-3 gap-4"> <div class="grid grid-cols-3 gap-4">
<div> <div>
<label for="username">Benutzername</label> <label for="username">Benutzername</label>
<input class="w-full" required name="username" type="text" value="{{.UserName}}" /> <input class="w-full" required name="username" type="text" />
</div> </div>
<div> <div>
<label for="password">Passwort</label> <label for="password">Passwort</label>
<input class="w-full" required name="password" placeholder="***" type="password" /> <input class="w-full" required name="password" placeholder="***" type="password" />
</div> </div>
<div> <div>
<label for="password2">Passwort wiederholen</label> <label for="password2">Passwort wiederholen</label>
<input class="w-full" required name="password2" placeholder="***" type="password" /> <input class="w-full" required name="password2" placeholder="***" type="password" />
</div> </div>
<div> <div>
<label for="first-name">Vorname</label> <label for="first-name">Vorname</label>
<input class="w-full" required name="first-name" type="text" value="{{.FirstName}}" /> <input class="w-full" required name="first-name" type="text" />
</div> </div>
<div> <div>
<label for="last-name">Nachname</label> <label for="last-name">Nachname</label>
<input class="w-full" required name="last-name" type="text" value="{{.LastName}}" /> <input class="w-full" required name="last-name" type="text" />
</div>
<div>
<label for="email">Emailadresse</label>
<input class="w-full" required name="email" type="text" />
</div>
<div>
<label for="email2">Emailadresse wiederholen</label>
<input class="w-full" required name="email2" type="text" />
</div> </div>
</div> </div>

View File

@ -2,30 +2,31 @@
<div class="flex flex-col gap-4"> <div class="flex flex-col gap-4">
<button class="btn" hx-get="/logout" hx-target="#page-content">Abmelden</button> <button class="btn" hx-get="/logout" hx-target="#page-content">Abmelden</button>
{{if lt . 4}} {{if lt .Role 4}}
<div class="mb-3"> <div class="mb-3">
<h2>Artikel</h2> <h2>Artikel</h2>
<div class="grid grid-cols-1 md:grid-cols-2 gap-x-4 gap-y-2"> <div class="grid grid-cols-1 md:grid-cols-2 gap-x-4 gap-y-2">
<button class="btn" hx-get="/article/write" hx-target="#page-content">Artikel schreiben</button> <button class="btn" hx-get="/article/write" hx-target="#page-content">Artikel schreiben</button>
<button class="btn" hx-get="/article/all-rejected" hx-target="#page-content">Artikel bearbeiten</button> <button class="btn" hx-get="/article/all-rejected" hx-target="#page-content">Artikel bearbeiten</button>
{{if lt . 3}}<button class="btn" hx-get="/article/all-unpublished-unrejected-and-published-rejected" {{if lt .Role 3}}<button class="btn" hx-get="/article/all-unpublished-unrejected-and-published-rejected"
hx-target="#page-content">Artikel veröffentlichen</button>{{end}} hx-target="#page-content">Artikel veröffentlichen</button>{{end}}
{{if lt . 2}}<button class="btn" hx-get="/article/all-published/delete" hx-target="#page-content">Artikel {{if lt .Role 2}}<button class="btn" hx-get="/article/all-published/delete"
löschen</button>{{end}} hx-target="#page-content">Artikel löschen</button>{{end}}
{{if lt . 2}}<button class="btn" hx-get="/article/all-published/review-edit" {{if lt .Role 2}}<button class="btn" hx-get="/article/all-published/review-edit"
hx-target="#page-content">Artikel bearbeiten lassen</button>{{end}} hx-target="#page-content">Artikel bearbeiten lassen</button>{{end}}
{{if lt . 3}}<button class="btn" hx-get="/tag/create" hx-target="#page-content">Neuer Tag</button>{{end}} {{if lt .Role 3}}<button class="btn" hx-get="/tag/create" hx-target="#page-content">Neuer
Tag</button>{{end}}
</div> </div>
</div> </div>
{{end}} {{end}}
{{if lt . 2}} {{if lt .Role 2}}
<div class="mb-3"> <div class="mb-3">
<h2>Ausgabe</h2> <h2>Ausgabe</h2>
<div class="grid grid-cols-1 md:grid-cols-2 gap-x-4 gap-y-2"> <div class="grid grid-cols-1 md:grid-cols-2 gap-x-4 gap-y-2">
<button class="btn" hx-get="/issue/this" hx-target="#page-content">Diese Ausgabe</button> <button class="btn" hx-get="/issue/this" hx-target="#page-content">Diese Ausgabe</button>
<form class="flex" hx-encoding="multipart/form-data"> <form class="flex" hx-encoding="multipart/form-data">
<label class="btn cursor-pointer text-center" for="pdf-upload">PDF hochladen</label> <label class="btn text-center" for="pdf-upload">PDF hochladen</label>
<input accept=".pdf" class="hidden" id="pdf-upload" name="pdf-upload" type="file" <input accept=".pdf" class="hidden" id="pdf-upload" name="pdf-upload" type="file"
hx-post="/pdf/upload" /> hx-post="/pdf/upload" />
</form> </form>
@ -33,16 +34,16 @@
{{end}} {{end}}
</div> </div>
{{if lt . 4}} {{if lt .Role 4}}
<div class="mb-3"> <div class="mb-3">
<h2>Benutzer</h2> <h2>Benutzer</h2>
<div class="grid grid-cols-1 md:grid-cols-2 gap-x-4 gap-y-2"> <div class="grid grid-cols-1 md:grid-cols-2 gap-x-4 gap-y-2">
<button class="btn" hx-get="/user/edit/self" hx-target="#page-content">Mein Profil bearbeiten</button> <button class="btn" hx-get="/user/edit/self" hx-target="#page-content">Mein Profil bearbeiten</button>
{{if eq . 0}}<button class="btn" hx-get="/user/create" hx-target="#page-content">Benutzer {{if eq .Role 0}}<button class="btn" hx-get="/user/create" hx-target="#page-content">Benutzer
hinzufügen</button>{{end}} hinzufügen</button>{{end}}
{{if eq . 0}}<button class="btn" hx-get="/user/show-all/edit" hx-target="#page-content">Benutzer {{if eq .Role 0}}<button class="btn" hx-get="/user/show-all/edit" hx-target="#page-content">Benutzer
bearbeiten</button>{{end}} bearbeiten</button>{{end}}
{{if eq . 0}}<button class="btn" hx-get="/user/show-all/delete" hx-target="#page-content">Benutzer {{if eq .Role 0}}<button class="btn" hx-get="/user/show-all/delete" hx-target="#page-content">Benutzer
löschen</button>{{end}} löschen</button>{{end}}
</div> </div>
</div> </div>

View File

@ -39,7 +39,7 @@
<footer class="text-center text-gray-500 my-8"> <footer class="text-center text-gray-500 my-8">
<p>&copy; 2024 Jason Streifling. Alle Rechte vorbehalten.</p> <p>&copy; 2024 Jason Streifling. Alle Rechte vorbehalten.</p>
<p>v0.13.0 - <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@2.0.2"></script> <script src="https://unpkg.com/htmx.org@2.0.2"></script>