From fdd8d4905510cbb8c7c264d49404d3b02ee5f73b Mon Sep 17 00:00:00 2001 From: Caleb Gardner Date: Mon, 17 Jun 2024 07:28:33 -0500 Subject: [PATCH] Working on blog stuff --- LICENSE | 2 +- internal/backend/header.go | 2 + internal/blog/README.md | 83 ++++++++++++++++++ internal/blog/author.go | 54 ++++++++++++ internal/blog/blog.go | 173 +++++++++++++++++++++++++++++++++++++ internal/blog/main.go | 27 ++++++ main.go | 45 ++++++++-- portfolio.go | 9 ++ 8 files changed, 388 insertions(+), 7 deletions(-) create mode 100644 internal/blog/README.md create mode 100644 internal/blog/author.go create mode 100644 internal/blog/blog.go create mode 100644 internal/blog/main.go create mode 100644 portfolio.go diff --git a/LICENSE b/LICENSE index 02776f9..6b6c897 100644 --- a/LICENSE +++ b/LICENSE @@ -1,6 +1,6 @@ MIT License -Copyright (c) 2021 Caleb Gardner +Copyright (c) 2024 Caleb Gardner Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal diff --git a/internal/backend/header.go b/internal/backend/header.go index 09ea247..6c47ab5 100644 --- a/internal/backend/header.go +++ b/internal/backend/header.go @@ -80,6 +80,8 @@ func (b *Backend) ParseHeader(r *http.Request) (*ParsedHeader, error) { // If the check if failed, ReturnError will be called and the returned *ParsedHeader will be nil. // If token is present but invalid, no error will be returned just ParsedHeader.User will be nil. // The error return will only be populated on "internal" errors and should *probably* be logged. +// +// This function does not check the Key's appID so after calling VerifyHeader it's recommended to check the Key's appID. func (b *Backend) VerifyHeader(w http.ResponseWriter, r *http.Request, keyPerm string, allowManagementKey bool) (*ParsedHeader, error) { hdr, err := b.ParseHeader(r) if hdr == nil || hdr.Key == nil { diff --git a/internal/blog/README.md b/internal/blog/README.md new file mode 100644 index 0000000..9343b9e --- /dev/null +++ b/internal/blog/README.md @@ -0,0 +1,83 @@ +# Blog module + +A simple blog module for darkstorm-backend. + +## Requests + +### Author info + +> GET /author/{authorID} + +```json +``` + +### Blog + +#### Specific blog + +> GET /blog/{blogID} + +Return: + +```json +{ + id: "blogID", + createTime: 0, // creation time in Unix format + updateTime: 0, // last update time in Unix format + author: "authorID", + favicon: "favicon url", + title: "blog title", + blog: "blog", // blog will have been converted to HTML +} +``` + +#### Latest blogs + +> GET /blog?page=0 + +Will return up to 5 blogs. `page` query is optional (implies 0 if not set). + +Return: + +```json +{ + num: 1, // Number of returned results, returns up to 5 results + blogs: [ + { + id: "blogID", + createTime: 0, // creation time in Unix format + updateTime: 0, // last update time in Unix format + author: "authorID", + favicon: "favicon url", + title: "blog title", + blog: "blog", // blog will have been converted to HTML + } + ... + ] +} +``` + +#### Blog List + +> GET /blog/list?page=0 + +Will return up to 50 IDs. `page` query is optional (implies 0 if not set). + +Return: + +```json +{ + num: 1, // Number of returned results, returns up to 50 results + blogList: [ + { + id: "blogID", + createTime: 0, // Unix format + }, + { + id: "blogID", + createTime: 0, // Unix format + }, + ... + ] +} +``` diff --git a/internal/blog/author.go b/internal/blog/author.go new file mode 100644 index 0000000..a6f87ba --- /dev/null +++ b/internal/blog/author.go @@ -0,0 +1,54 @@ +package blog + +import ( + "context" + "log" + "net/http" + + "github.com/CalebQ42/darkstorm-server/internal/backend" + "go.mongodb.org/mongo-driver/bson" + "go.mongodb.org/mongo-driver/mongo" +) + +type Author struct { + ID string `json:"id" bson:"_id"` + About string `json:"about" bson:"about"` + PicURL string `json:"picurl" bson:"picurl"` +} + +func (b *BlogApp) AboutCaleb() (*Author, error) { + res := b.authCol.FindOne(context.Background(), bson.M{"_id": "caleb_gardner"}) + if res.Err() != nil { + log.Println("error getting about me:", res.Err()) + if res.Err() == mongo.ErrNoDocuments { + return nil, backend.ErrNotFound + } + return nil, res.Err() + } + var aboutMe Author + err := res.Decode(&aboutMe) + if err != nil { + log.Println("error decoding about me:", res) + return nil, err + } + return &aboutMe, nil +} + +func (b *BlogApp) GetAuthorInfo(w http.ResponseWriter, r *http.Request) { + +} + +func (b *BlogApp) SetAuthorInfo(w http.ResponseWriter, r *http.Request) { + hdr, err := b.back.VerifyHeader(w, r, "managment", true) + if hdr == nil { + if err != nil { + log.Println("error verifying apiKey:", err) + } + return + } + if hdr.Key.AppID != "blog" { + backend.ReturnError(w, http.StatusUnauthorized, "invalidKey", "Application not authorized") + return + } + +} diff --git a/internal/blog/blog.go b/internal/blog/blog.go new file mode 100644 index 0000000..bc25db5 --- /dev/null +++ b/internal/blog/blog.go @@ -0,0 +1,173 @@ +package blog + +import ( + "context" + "encoding/json" + "log" + "net/http" + "strconv" + + "github.com/CalebQ42/darkstorm-server/internal/backend" + "go.mongodb.org/mongo-driver/bson" + "go.mongodb.org/mongo-driver/mongo" + "go.mongodb.org/mongo-driver/mongo/options" +) + +type Blog struct { + ID string `json:"id" bson:"_id"` + Author string `json:"author" bson:"author"` + Favicon string `json:"favicon" bson:"favicon"` + Title string `json:"title" bson:"title"` + Blog string `json:"blog" bson:"blog"` + CreateTime int `json:"createTime" bson:"createTime"` + UpdateTime int `json:"updateTime" bson:"updateTime"` +} + +func (b *Blog) ConvertBlog() { + //TODO: parse BBCode/Markdown from blog + //b.Blog = bbCodeConvert(b.Blog) +} + +func (b *BlogApp) GetAuthor(blog *Blog) (*Author, error) { + res := b.authCol.FindOne(context.Background(), bson.M{"_id": blog.Author}) + if res.Err() != nil { + if res.Err() == mongo.ErrNoDocuments { + return nil, backend.ErrNotFound + } + return nil, res.Err() + } + var author Author + err := res.Decode(&author) + return &author, err +} + +func (b *BlogApp) GetBlog(ID string) (*Blog, error) { + res := b.blogCol.FindOne(context.Background(), bson.M{"_id": ID}) + if res.Err() != nil { + if res.Err() == mongo.ErrNoDocuments { + return nil, backend.ErrNotFound + } + return nil, res.Err() + } + var blog Blog + err := res.Decode(blog) + if err != nil { + return nil, err + } + blog.ConvertBlog() + return &blog, nil +} + +func (b *BlogApp) Blog(w http.ResponseWriter, r *http.Request) { + blogID := r.PathValue("blogID") + if blogID == "" { + backend.ReturnError(w, http.StatusBadRequest, "badRequest", "Must provide a blogID") + return + } + blog, err := b.GetBlog(blogID) + if err != nil { + if err == backend.ErrNotFound { + backend.ReturnError(w, http.StatusNotFound, "notFound", "Not blog found with the given ID") + return + } + log.Println("error getting blog:", err) + backend.ReturnError(w, http.StatusInternalServerError, "internal", "Server error") + return + } + json.NewEncoder(w).Encode(blog) +} + +func (b *BlogApp) GetLatestBlogs(page int64) ([]Blog, error) { + res, err := b.blogCol.Find(context.Background(), bson.M{}, options.Find(). + SetSort(bson.M{"createTime": 1}). + SetLimit(5). + SetSkip(page*5)) + if err != nil { + if err == mongo.ErrNoDocuments { + return nil, backend.ErrNotFound + } + return nil, err + } + var out []Blog + err = res.All(context.Background(), &out) + if err != nil { + return nil, err + } + for i := range out { + out[i].ConvertBlog() + } + return out, nil +} + +func (b *BlogApp) LatestBlogs(w http.ResponseWriter, r *http.Request) { + var page int + var err error + pagQuery := r.URL.Query().Get("page") + if pagQuery != "" { + page, err = strconv.Atoi(pagQuery) + if err != nil { + page = 0 + } + } + blogs, err := b.GetLatestBlogs(int64(page)) + if err != nil && err != backend.ErrNotFound { + backend.ReturnError(w, http.StatusInternalServerError, "internal", "internal error") + return + } + var ret struct { + Blogs []Blog `json:"blogs"` + Num int `json:"num"` + } + ret.Num = len(blogs) + ret.Blogs = blogs + json.NewEncoder(w).Encode(ret) +} + +type BlogListResult struct { + ID string `json:"id" bson:"_id"` + CreateTime int `json:"createTime" bson:"createTime"` +} + +func (b *BlogApp) GetBlogList(page int64) ([]BlogListResult, error) { + res, err := b.blogCol.Find(context.Background(), bson.M{}, options.Find(). + SetProjection(bson.M{"_id": 1, "createTime": 1}). + SetSort(bson.M{"createTime": 1}). + SetLimit(50). + SetSkip(page*50)) + if err != nil { + if err == mongo.ErrNoDocuments { + return nil, backend.ErrNotFound + } + return nil, err + } + var out []BlogListResult + err = res.All(context.Background(), &out) + if err != nil { + return nil, err + } + return out, nil +} + +func (b *BlogApp) BlogList(w http.ResponseWriter, r *http.Request) { + var page int + var err error + pagQuery := r.URL.Query().Get("page") + if pagQuery != "" { + page, err = strconv.Atoi(pagQuery) + if err != nil { + page = 0 + } + } + blogList, err := b.GetBlogList(int64(page)) + if err != nil && err != backend.ErrNotFound { + backend.ReturnError(w, http.StatusInternalServerError, "internal", "internal error") + return + } + var ret struct { + BlogList []BlogListResult `json:"blogList"` + Num int `json:"num"` + } + ret.Num = len(blogList) + ret.BlogList = blogList + json.NewEncoder(w).Encode(ret) +} diff --git a/internal/blog/main.go b/internal/blog/main.go new file mode 100644 index 0000000..1a02a27 --- /dev/null +++ b/internal/blog/main.go @@ -0,0 +1,27 @@ +package blog + +import ( + "net/http" + + "github.com/CalebQ42/darkstorm-server/internal/backend" + "go.mongodb.org/mongo-driver/mongo" +) + +type BlogApp struct { + back *backend.Backend + blogCol *mongo.Collection + authCol *mongo.Collection +} + +func NewBlogApp(b *backend.Backend, db *mongo.Database, mux *http.ServeMux) *BlogApp { + out := &BlogApp{ + back: b, + blogCol: db.Collection("blog"), + authCol: db.Collection("author"), + } + // setup mux + mux.HandleFunc("GET /blog/", out.LatestBlogs) + mux.HandleFunc("GET /blog/{blogID}", out.Blog) + //TODO + return out +} diff --git a/main.go b/main.go index b476897..660fd99 100644 --- a/main.go +++ b/main.go @@ -1,22 +1,55 @@ package main import ( + "context" "crypto/tls" "flag" "log" + "net/http" + "path/filepath" + + "github.com/CalebQ42/darkstorm-server/internal/blog" + "go.mongodb.org/mongo-driver/mongo" + "go.mongodb.org/mongo-driver/mongo/options" +) + +var ( + mongoClient *mongo.Client + blogApp *blog.BlogApp ) func main() { - mongoURL := flag.String("mongo", "", "Enables MongoDB usage for darkstorm-backend.") + mongoURL := flag.String("mongo", "", "Enables MongoDB usage for Darkstorm backend.") webRoot := flag.String("web-root", "", "Sets root directory of web server.") flag.Parse() if flag.NArg() != 1 { log.Fatal("You must specify key directory. ex: darkstorm-server /etc/web-keys") } - if *mongoURL != "" { - } - mongoCert, err := tls.LoadX509KeyPair(flag.Arg(0)+"mongo.pem", flag.Arg(0)+"key.pem") - if err != nil { - + if *mongoURL == "" || *webRoot == "" { + log.Fatal("SPECIFY MONGO AND WEB-ROOT OR I WILL DIE (Death noises).") } + mux := http.NewServeMux() + mongoClient = setupMongo(*mongoURL) + setupBackend(mux) + setupWebsite(mux, *webRoot) + http.ListenAndServeTLS(":443", filepath.Join(flag.Arg(0), "cert.pem"), filepath.Join(flag.Arg(0), "key.pem"), mux) +} + +func setupMongo(uri string) *mongo.Client { + mongoCert, err := tls.LoadX509KeyPair(filepath.Join(flag.Arg(0), "mongo.pem"), filepath.Join(flag.Arg(0)+"key.pem")) + if err != nil { + log.Fatal("error loading mongo keys:", err) + } + client, err := mongo.Connect(context.Background(), options.Client().ApplyURI(uri).SetTLSConfig(&tls.Config{ + Certificates: []tls.Certificate{mongoCert}, + })) + if err != nil { + log.Fatal("error connecting to mongo:", err) + } + return client +} + +func setupWebsite(mux *http.ServeMux, root string) {} + +func setupBackend(mux *http.ServeMux) { } diff --git a/portfolio.go b/portfolio.go new file mode 100644 index 0000000..b12e6cf --- /dev/null +++ b/portfolio.go @@ -0,0 +1,9 @@ +package main + +import ( + "go.mongodb.org/mongo-driver/mongo" +) + +func portfolio(client *mongo.Client) { + //TODO +}