v5.0 cleanup

This commit is contained in:
ngn
2024-10-06 17:30:25 +03:00
parent 38f51935b9
commit d5e875ae53
29 changed files with 711 additions and 427 deletions

View File

@ -7,17 +7,11 @@ COPY *.mod ./
COPY *.sum ./
COPY Makefile ./
COPY routes ./routes
COPY global ./global
COPY global ./config
COPY database ./database
COPY util ./util
EXPOSE 7001
RUN make
ARG PASSWORD
ENV PASSWORD $PASSWORD
ARG FRONTEND_URL
ENV FRONTEND_URL $FRONTEND_URL
ENTRYPOINT ["/app/server"]

View File

@ -1,10 +1,10 @@
all: server
server: *.go routes/*.go util/*.go global/*.go database/*.go
server: *.go routes/*.go database/*.go util/*.go config/*.go
go build -o $@ .
test:
FRONTEND_URL=http://localhost:5173/ PASSWORD=test ./server
API_FRONTEND_URL=http://localhost:5173/ API_PASSWORD=test ./server
format:
gofmt -s -w .

60
api/config/config.go Normal file
View File

@ -0,0 +1,60 @@
package config
import (
"fmt"
"os"
"strings"
"github.com/ngn13/website/api/util"
)
type Option struct {
Name string
Value string
Required bool
}
func (o *Option) Env() string {
return strings.ToUpper(fmt.Sprintf("API_%s", o.Name))
}
var options []Option = []Option{
{Name: "password", Value: "", Required: true},
{Name: "frontend_url", Value: "http://localhost:5173/", Required: true},
}
func Load() bool {
var val string
for i := range options {
if val = os.Getenv(options[i].Env()); val == "" {
continue
}
options[i].Value = val
options[i].Required = false
}
for i := range options {
if options[i].Required && options[i].Value == "" {
util.Fail("please specify the required config option \"%s\" (\"%s\")", options[i].Name, options[i].Env())
return false
}
if options[i].Required && options[i].Value != "" {
util.Fail("using the default value \"%s\" for required config option \"%s\" (\"%s\")", options[i].Value, options[i].Name, options[i].Env())
}
}
return true
}
func Get(name string) string {
for i := range options {
if options[i].Name != name {
continue
}
return options[i].Value
}
return ""
}

View File

@ -2,18 +2,10 @@ package database
import (
"database/sql"
"github.com/ngn13/website/api/global"
)
type Type struct {
Sql *sql.DB
Votes []global.Vote
}
func (t *Type) Setup() error {
t.Votes = []global.Vote{}
_, err := t.Sql.Exec(`
func Setup(db *sql.DB) error {
_, err := db.Exec(`
CREATE TABLE IF NOT EXISTS posts(
id TEXT NOT NULL UNIQUE,
title TEXT NOT NULL,
@ -29,7 +21,7 @@ func (t *Type) Setup() error {
return err
}
_, err = t.Sql.Exec(`
_, err = db.Exec(`
CREATE TABLE IF NOT EXISTS services(
name TEXT NOT NULL UNIQUE,
desc TEXT NOT NULL,
@ -37,19 +29,16 @@ func (t *Type) Setup() error {
);
`)
return err
}
func (t *Type) Open(p string) error {
var err error
if t.Sql, err = sql.Open("sqlite3", p); err != nil {
if err != nil {
return err
}
return t.Setup()
}
_, err = db.Exec(`
CREATE TABLE IF NOT EXISTS votes(
hash TEXT NOT NULL UNIQUE,
is_upvote INTEGER NOT NULL
);
`)
func (t *Type) Close() {
t.Sql.Close()
return err
}

71
api/database/post.go Normal file
View File

@ -0,0 +1,71 @@
package database
import (
"database/sql"
"github.com/ngn13/website/api/util"
)
type Post struct {
ID string `json:"id"`
Title string `json:"title"`
Author string `json:"author"`
Date string `json:"date"`
Content string `json:"content"`
Public int `json:"public"`
Vote int `json:"vote"`
}
func (p *Post) Load(rows *sql.Rows) error {
return rows.Scan(&p.ID, &p.Title, &p.Author, &p.Date, &p.Content, &p.Public, &p.Vote)
}
func (p *Post) Get(db *sql.DB, id string) (bool, error) {
var (
success bool
rows *sql.Rows
err error
)
if rows, err = db.Query("SELECT * FROM posts WHERE id = ?", id); err != nil {
return false, err
}
defer rows.Close()
if success = rows.Next(); !success {
return false, nil
}
if err = p.Load(rows); err != nil {
return false, err
}
return true, nil
}
func (p *Post) Remove(db *sql.DB) error {
_, err := db.Exec("DELETE FROM posts WHERE id = ?", p.ID)
return err
}
func (p *Post) Save(db *sql.DB) error {
p.ID = util.TitleToID(p.Title)
_, err := db.Exec(
"INSERT INTO posts(id, title, author, date, content, public, vote) values(?, ?, ?, ?, ?, ?, ?)",
p.ID, p.Title, p.Author, p.Date, p.Content, p.Public, p.Vote,
)
return err
}
func (p *Post) Update(db *sql.DB) error {
p.ID = util.TitleToID(p.Title)
_, err := db.Exec(
"UPDATE posts SET title = ?, author = ?, date = ?, content = ?, public = ?, vote = ? WHERE id = ?",
p.Title, p.Author, p.Date, p.Content, p.Public, p.Vote, p.ID,
)
return err
}

56
api/database/service.go Normal file
View File

@ -0,0 +1,56 @@
package database
import (
"database/sql"
)
type Service struct {
Name string `json:"name"`
Desc string `json:"desc"`
Url string `json:"url"`
}
func (s *Service) Load(rows *sql.Rows) error {
return rows.Scan(&s.Name, &s.Desc, &s.Url)
}
func (s *Service) Get(db *sql.DB, name string) (bool, error) {
var (
success bool
rows *sql.Rows
err error
)
if rows, err = db.Query("SELECT * FROM services WHERE name = ?", name); err != nil {
return false, err
}
defer rows.Close()
if success = rows.Next(); !success {
return false, nil
}
if err = s.Load(rows); err != nil {
return false, err
}
return true, nil
}
func (s *Service) Remove(db *sql.DB) error {
_, err := db.Exec(
"DELETE FROM services WHERE name = ?",
s.Name,
)
return err
}
func (s *Service) Save(db *sql.DB) error {
_, err := db.Exec(
"INSERT INTO services(name, desc, url) values(?, ?, ?)",
s.Name, s.Desc, s.Url,
)
return err
}

49
api/database/vote.go Normal file
View File

@ -0,0 +1,49 @@
package database
import "database/sql"
type Vote struct {
Hash string
IsUpvote bool
}
func (v *Vote) Load(rows *sql.Rows) error {
return rows.Scan(&v.Hash, &v.IsUpvote)
}
func (v *Vote) Get(db *sql.DB, hash string) (bool, error) {
var (
success bool
rows *sql.Rows
err error
)
if rows, err = db.Query("SELECT * FROM votes WHERE hash = ?", hash); err != nil {
return false, err
}
defer rows.Close()
if success = rows.Next(); !success {
return false, nil
}
if err = v.Load(rows); err != nil {
return false, err
}
return true, nil
}
func (v *Vote) Update(db *sql.DB) error {
_, err := db.Exec("UPDATE votes SET is_upvote = ? WHERE hash = ?", v.IsUpvote, v.Hash)
return err
}
func (v *Vote) Save(db *sql.DB) error {
_, err := db.Exec(
"INSERT INTO votes(hash, is_upvote) values(?, ?)",
v.Hash, v.IsUpvote,
)
return err
}

View File

@ -1,23 +0,0 @@
package global
type Post struct {
ID string `json:"id"`
Title string `json:"title"`
Author string `json:"author"`
Date string `json:"date"`
Content string `json:"content"`
Public int `json:"public"`
Vote int `json:"vote"`
}
type Service struct {
Name string `json:"name"`
Desc string `json:"desc"`
Url string `json:"url"`
}
type Vote struct {
Post string
Client string
Status string
}

View File

@ -1,27 +1,40 @@
package main
import (
"log"
"net/http"
"database/sql"
"github.com/gofiber/fiber/v2"
"github.com/ngn13/website/api/config"
"github.com/ngn13/website/api/database"
"github.com/ngn13/website/api/routes"
"github.com/ngn13/website/api/util"
)
var db *sql.DB
func main() {
var (
app *fiber.App
db database.Type
//db *sql.DB
err error
)
if err = db.Open("data.db"); err != nil {
log.Fatalf("Cannot access the database: %s", err.Error())
if !config.Load() {
util.Fail("failed to load the configuration")
return
}
if db, err = sql.Open("sqlite3", "data.db"); err != nil {
util.Fail("cannot access the database: %s", err.Error())
return
}
defer db.Close()
if err = database.Setup(db); err != nil {
util.Fail("cannot setup the database: %s", err.Error())
return
}
app = fiber.New(fiber.Config{
DisableStartupMessage: true,
})
@ -31,7 +44,9 @@ func main() {
c.Set("Access-Control-Allow-Headers",
"Content-Type, Content-Length, Accept-Encoding, X-CSRF-Token, Authorization, accept, origin, Cache-Control, X-Requested-With")
c.Set("Access-Control-Allow-Methods", "PUT, DELETE, GET")
c.Locals("database", &db)
return c.Next()
})
@ -42,37 +57,46 @@ func main() {
// blog routes
blog_routes := app.Group("/blog")
blog_routes.Get("/feed.atom", routes.GetAtomFeed)
blog_routes.Get("/feed.rss", routes.GetRSSFeed)
blog_routes.Get("/feed.json", routes.GetJSONFeed)
blog_routes.Get("/sum", routes.SumPost)
blog_routes.Get("/get", routes.GetPost)
blog_routes.Get("/vote/set", routes.VoteSet)
blog_routes.Get("/vote/status", routes.VoteStat)
// blog feed routes
blog_routes.Get("/feed.*", routes.GET_Feed)
// blog post routes
blog_routes.Get("/sum", routes.GET_PostSum)
blog_routes.Get("/get", routes.GET_Post)
// blog post vote routes
blog_routes.Get("/vote/set", routes.GET_VoteSet)
blog_routes.Get("/vote/get", routes.GET_VoteGet)
// service routes
service_routes := app.Group("services")
service_routes.Get("/all", routes.GetServices)
service_routes.Get("/all", routes.GET_Services)
// admin routes
admin_routes := app.Group("admin")
admin_routes.Use("*", routes.AuthMiddleware)
admin_routes.Get("/login", routes.Login)
admin_routes.Get("/logout", routes.Logout)
admin_routes.Put("/service/add", routes.AddService)
admin_routes.Delete("/service/remove", routes.RemoveService)
admin_routes.Put("/blog/add", routes.AddPost)
admin_routes.Delete("/blog/remove", routes.RemovePost)
// 404 routes
// admin auth routes
admin_routes.Get("/login", routes.GET_Login)
admin_routes.Get("/logout", routes.GET_Logout)
// admin service managment routes
admin_routes.Put("/service/add", routes.PUT_AddService)
admin_routes.Delete("/service/remove", routes.DEL_RemoveService)
// admin blog managment routes
admin_routes.Put("/blog/add", routes.PUT_AddPost)
admin_routes.Delete("/blog/remove", routes.DEL_RemovePost)
// 404 route
app.All("*", func(c *fiber.Ctx) error {
return c.Status(http.StatusNotFound).JSON(fiber.Map{
"error": "Requested endpoint not found",
})
return util.ErrNotFound(c)
})
log.Println("Starting web server at port 7001")
util.Info("starting web server at port 7001")
if err = app.Listen("0.0.0.0:7001"); err != nil {
log.Printf("Error starting the webserver: %s", err.Error())
util.Fail("error starting the webserver: %s", err.Error())
}
}

View File

@ -1,16 +1,15 @@
package routes
import (
"log"
"database/sql"
"net/http"
"os"
"strings"
"time"
"github.com/gofiber/fiber/v2"
"github.com/mattn/go-sqlite3"
"github.com/ngn13/website/api/config"
"github.com/ngn13/website/api/database"
"github.com/ngn13/website/api/global"
"github.com/ngn13/website/api/util"
)
@ -28,14 +27,12 @@ func AuthMiddleware(c *fiber.Ctx) error {
return c.Next()
}
func Login(c *fiber.Ctx) error {
if c.Query("pass") != os.Getenv("PASSWORD") {
return c.Status(http.StatusUnauthorized).JSON(fiber.Map{
"error": "Authentication failed",
})
func GET_Login(c *fiber.Ctx) error {
if c.Query("pass") != config.Get("password") {
return util.ErrAuth(c)
}
log.Printf("New login from %s", util.GetIP(c))
util.Info("new login from %s", util.GetIP(c))
return c.Status(http.StatusOK).JSON(fiber.Map{
"error": "",
@ -43,44 +40,58 @@ func Login(c *fiber.Ctx) error {
})
}
func Logout(c *fiber.Ctx) error {
func GET_Logout(c *fiber.Ctx) error {
Token = util.CreateToken()
log.Printf("Logout from %s", util.GetIP(c))
util.Info("logout from %s", util.GetIP(c))
return c.Status(http.StatusOK).JSON(fiber.Map{
"error": "",
})
}
func RemoveService(c *fiber.Ctx) error {
func DEL_RemoveService(c *fiber.Ctx) error {
var (
db *database.Type
name string
db *sql.DB
service database.Service
name string
found bool
err error
)
db = c.Locals("database").(*database.Type)
db = *(c.Locals("database").(**sql.DB))
name = c.Query("name")
if name == "" {
util.ErrBadData(c)
}
_, err := db.Sql.Exec("DELETE FROM services WHERE name = ?", name)
if util.ErrorCheck(err, c) {
if found, err = service.Get(db, name); err != nil {
util.Fail("error while searching for a service (\"%s\"): %s", name, err.Error())
return util.ErrServer(c)
}
if !found {
return util.ErrEntryNotExists(c)
}
if err = service.Remove(db); err != nil {
util.Fail("error while removing a service (\"%s\"): %s", service.Name, err.Error())
return util.ErrServer(c)
}
return util.NoError(c)
}
func AddService(c *fiber.Ctx) error {
func PUT_AddService(c *fiber.Ctx) error {
var (
service global.Service
db *database.Type
service database.Service
db *sql.DB
found bool
err error
)
db = c.Locals("database").(*database.Type)
db = *(c.Locals("database").(**sql.DB))
if c.BodyParser(&service) != nil {
return util.ErrBadJSON(c)
@ -90,58 +101,63 @@ func AddService(c *fiber.Ctx) error {
return util.ErrBadData(c)
}
rows, err := db.Sql.Query("SELECT * FROM services WHERE name = ?", service.Name)
if util.ErrorCheck(err, c) {
if found, err = service.Get(db, service.Name); err != nil {
util.Fail("error while searching for a service (\"%s\"): %s", service.Name, err.Error())
return util.ErrServer(c)
}
if rows.Next() {
rows.Close()
return util.ErrExists(c)
if found {
return util.ErrEntryExists(c)
}
rows.Close()
_, err = db.Sql.Exec(
"INSERT INTO services(name, desc, url) values(?, ?, ?)",
service.Name, service.Desc, service.Url,
)
if util.ErrorCheck(err, c) {
if err = service.Save(db); err != nil {
util.Fail("error while saving a new service (\"%s\"): %s", service.Name, err.Error())
return util.ErrServer(c)
}
return util.NoError(c)
}
func RemovePost(c *fiber.Ctx) error {
func DEL_RemovePost(c *fiber.Ctx) error {
var (
db *database.Type
id string
db *sql.DB
id string
found bool
err error
post database.Post
)
db = c.Locals("database").(*database.Type)
id = c.Query("id")
db = *(c.Locals("database").(**sql.DB))
if id == "" {
if id = c.Query("id"); id == "" {
return util.ErrBadData(c)
}
_, err := db.Sql.Exec("DELETE FROM posts WHERE id = ?", id)
if util.ErrorCheck(err, c) {
if found, err = post.Get(db, id); err != nil {
util.Fail("error while searching for a post (\"%s\"): %s", id, err.Error())
return util.ErrServer(c)
}
if !found {
return util.ErrEntryNotExists(c)
}
if err = post.Remove(db); err != nil {
util.Fail("error while removing a post (\"%s\"): %s", post.ID, err.Error())
return util.ErrServer(c)
}
return util.NoError(c)
}
func AddPost(c *fiber.Ctx) error {
func PUT_AddPost(c *fiber.Ctx) error {
var (
db *database.Type
post global.Post
db *sql.DB
post database.Post
err error
)
db = c.Locals("database").(*database.Type)
db = *(c.Locals("database").(**sql.DB))
post.Public = 1
if c.BodyParser(&post) != nil {
@ -153,19 +169,14 @@ func AddPost(c *fiber.Ctx) error {
}
post.Date = time.Now().Format("02/01/06")
post.ID = util.TitleToID(post.Title)
_, err := db.Sql.Exec(
"INSERT INTO posts(id, title, author, date, content, public, vote) values(?, ?, ?, ?, ?, ?, ?)",
post.ID, post.Title, post.Author, post.Date, post.Content, post.Public, post.Vote,
)
if err != nil && strings.Contains(err.Error(), sqlite3.ErrConstraintUnique.Error()) {
return util.ErrExists(c)
if err = post.Save(db); err != nil && strings.Contains(err.Error(), sqlite3.ErrConstraintUnique.Error()) {
return util.ErrEntryExists(c)
}
if util.ErrorCheck(err, c) {
return util.ErrExists(c)
if err != nil {
util.Fail("error while saving a new post (\"%s\"): %s", post.ID, err.Error())
return util.ErrServer(c)
}
return util.NoError(c)

View File

@ -3,164 +3,40 @@ package routes
import (
"database/sql"
"fmt"
"net/http"
"net/url"
"os"
"path"
"strings"
"time"
"github.com/gofiber/fiber/v2"
"github.com/gorilla/feeds"
"github.com/ngn13/website/api/config"
"github.com/ngn13/website/api/database"
"github.com/ngn13/website/api/global"
"github.com/ngn13/website/api/util"
)
func PostFromRow(post *global.Post, rows *sql.Rows) error {
err := rows.Scan(&post.ID, &post.Title, &post.Author, &post.Date, &post.Content, &post.Public, &post.Vote)
if err != nil {
return err
}
return nil
}
func GetPostByID(db *database.Type, id string) (global.Post, string) {
var post global.Post = global.Post{}
post.Title = "NONE"
rows, err := db.Sql.Query("SELECT * FROM posts WHERE id = ?", id)
if err != nil {
return post, "Server error"
}
success := rows.Next()
if !success {
rows.Close()
return post, "Post not found"
}
err = PostFromRow(&post, rows)
if err != nil {
rows.Close()
return post, "Server error"
}
rows.Close()
if post.Title == "NONE" {
return post, "Post not found"
}
return post, ""
}
func VoteStat(c *fiber.Ctx) error {
var (
db *database.Type
id string
)
db = c.Locals("database").(*database.Type)
id = c.Query("id")
if id == "" {
return util.ErrBadData(c)
}
for i := 0; i < len(db.Votes); i++ {
if db.Votes[i].Client == util.GetIP(c) && db.Votes[i].Post == id {
return c.JSON(fiber.Map{
"error": "",
"result": db.Votes[i].Status,
})
}
}
return c.Status(http.StatusNotFound).JSON(util.ErrorJSON("Client never voted"))
}
func VoteSet(c *fiber.Ctx) error {
func GET_Post(c *fiber.Ctx) error {
var (
post database.Post
id string
to string
voted bool
db *database.Type
db *sql.DB
found bool
err error
)
db = c.Locals("database").(*database.Type)
id = c.Query("id")
to = c.Query("to")
voted = false
db = *(c.Locals("database").(**sql.DB))
if id == "" || (to != "upvote" && to != "downvote") {
if id = c.Query("id"); id == "" {
return util.ErrBadData(c)
}
for i := 0; i < len(db.Votes); i++ {
if db.Votes[i].Client == util.GetIP(c) && db.Votes[i].Post == id && db.Votes[i].Status == to {
return c.Status(http.StatusForbidden).JSON(util.ErrorJSON("Client already voted"))
}
if db.Votes[i].Client == util.GetIP(c) && db.Votes[i].Post == id && db.Votes[i].Status != to {
voted = true
}
}
post, msg := GetPostByID(db, id)
if msg != "" {
return c.Status(http.StatusNotFound).JSON(util.ErrorJSON(msg))
}
vote := post.Vote + 1
if to == "downvote" {
vote = post.Vote - 1
}
if to == "downvote" && voted {
vote = vote - 1
}
if to == "upvote" && voted {
vote = vote + 1
}
_, err := db.Sql.Exec("UPDATE posts SET vote = ? WHERE title = ?", vote, post.Title)
if util.ErrorCheck(err, c) {
if found, err = post.Get(db, id); err != nil {
util.Fail("error while search for a post (\"%s\"): %s", id, err.Error())
return util.ErrServer(c)
}
for i := 0; i < len(db.Votes); i++ {
if db.Votes[i].Client == util.GetIP(c) && db.Votes[i].Post == id && db.Votes[i].Status != to {
db.Votes[i].Status = to
return util.NoError(c)
}
}
var entry = global.Vote{}
entry.Client = util.GetIP(c)
entry.Status = to
entry.Post = id
db.Votes = append(db.Votes, entry)
return util.NoError(c)
}
func GetPost(c *fiber.Ctx) error {
var (
id string
db *database.Type
)
id = c.Query("id")
db = c.Locals("database").(*database.Type)
if id == "" {
return util.ErrBadData(c)
}
post, msg := GetPostByID(db, id)
if msg != "" {
return c.Status(http.StatusNotFound).JSON(util.ErrorJSON(msg))
if !found {
return util.ErrEntryNotExists(c)
}
return c.JSON(fiber.Map{
@ -169,25 +45,27 @@ func GetPost(c *fiber.Ctx) error {
})
}
func SumPost(c *fiber.Ctx) error {
func GET_PostSum(c *fiber.Ctx) error {
var (
posts []global.Post
post global.Post
db *database.Type
posts []database.Post
rows *sql.Rows
db *sql.DB
err error
)
db = c.Locals("database").(*database.Type)
db = *(c.Locals("database").(**sql.DB))
rows, err := db.Sql.Query("SELECT * FROM posts")
if util.ErrorCheck(err, c) {
if rows, err = db.Query("SELECT * FROM posts"); err != nil {
util.Fail("cannot load posts: %s", err.Error())
return util.ErrServer(c)
}
defer rows.Close()
for rows.Next() {
err = PostFromRow(&post, rows)
var post database.Post
if util.ErrorCheck(err, c) {
if err = post.Load(rows); err != nil {
util.Fail("error while loading post: %s", err.Error())
return util.ErrServer(c)
}
@ -201,7 +79,6 @@ func SumPost(c *fiber.Ctx) error {
posts = append(posts, post)
}
rows.Close()
return c.JSON(fiber.Map{
"error": "",
@ -209,22 +86,21 @@ func SumPost(c *fiber.Ctx) error {
})
}
func GetFeed(db *database.Type) (*feeds.Feed, error) {
func getFeed(db *sql.DB) (*feeds.Feed, error) {
var (
posts []global.Post
post global.Post
posts []database.Post
err error
)
rows, err := db.Sql.Query("SELECT * FROM posts")
rows, err := db.Query("SELECT * FROM posts")
if err != nil {
return nil, err
}
for rows.Next() {
err := PostFromRow(&post, rows)
var post database.Post
if err != nil {
if err = post.Load(rows); err != nil {
return nil, err
}
@ -236,7 +112,10 @@ func GetFeed(db *database.Type) (*feeds.Feed, error) {
}
rows.Close()
blogurl, err := url.JoinPath(os.Getenv("FRONTEND_URL"), "/blog")
blogurl, err := url.JoinPath(
config.Get("frontend_url"), "/blog",
)
if err != nil {
return nil, fmt.Errorf("failed to create the blog URL: %s", err.Error())
}
@ -273,46 +152,52 @@ func GetFeed(db *database.Type) (*feeds.Feed, error) {
return feed, nil
}
func GetAtomFeed(c *fiber.Ctx) error {
feed, err := GetFeed(c.Locals("database").(*database.Type))
if util.ErrorCheck(err, c) {
func GET_Feed(c *fiber.Ctx) error {
var (
db *sql.DB
err error
feed *feeds.Feed
name []string
res string
ext string
)
db = *(c.Locals("database").(**sql.DB))
if name = strings.Split(path.Base(c.Path()), "."); len(name) != 2 {
return util.ErrNotFound(c)
}
ext = name[1]
if feed, err = getFeed(db); err != nil {
util.Fail("cannot obtain the feed: %s", err.Error())
return util.ErrServer(c)
}
atom, err := feed.ToAtom()
if util.ErrorCheck(err, c) {
switch ext {
case "atom":
res, err = feed.ToAtom()
c.Set("Content-Type", "application/atom+xml")
break
case "json":
res, err = feed.ToJSON()
c.Set("Content-Type", "application/feed+json")
break
case "rss":
res, err = feed.ToRss()
c.Set("Content-Type", "application/rss+xml")
break
default:
return util.ErrNotFound(c)
}
if err != nil {
util.Fail("cannot obtain the feed as the specified format: %s", err.Error())
return util.ErrServer(c)
}
c.Set("Content-Type", "application/atom+xml")
return c.Send([]byte(atom))
}
func GetRSSFeed(c *fiber.Ctx) error {
feed, err := GetFeed(c.Locals("database").(*database.Type))
if util.ErrorCheck(err, c) {
return util.ErrServer(c)
}
rss, err := feed.ToRss()
if util.ErrorCheck(err, c) {
return util.ErrServer(c)
}
c.Set("Content-Type", "application/rss+xml")
return c.Send([]byte(rss))
}
func GetJSONFeed(c *fiber.Ctx) error {
feed, err := GetFeed(c.Locals("database").(*database.Type))
if util.ErrorCheck(err, c) {
return util.ErrServer(c)
}
json, err := feed.ToJSON()
if util.ErrorCheck(err, c) {
return util.ErrServer(c)
}
c.Set("Content-Type", "application/feed+json")
return c.Send([]byte(json))
return c.Send([]byte(res))
}

View File

@ -1,39 +1,40 @@
package routes
import (
"log"
"database/sql"
"github.com/gofiber/fiber/v2"
"github.com/ngn13/website/api/database"
"github.com/ngn13/website/api/global"
"github.com/ngn13/website/api/util"
)
func GetServices(c *fiber.Ctx) error {
func GET_Services(c *fiber.Ctx) error {
var (
services []global.Service = []global.Service{}
service global.Service
db *database.Type
services []database.Service
rows *sql.Rows
db *sql.DB
err error
)
db = c.Locals("database").(*database.Type)
db = *(c.Locals("database").(**sql.DB))
rows, err := db.Sql.Query("SELECT * FROM services")
if util.ErrorCheck(err, c) {
if rows, err = db.Query("SELECT * FROM services"); err != nil {
util.Fail("cannot load services: %s", err.Error())
return util.ErrServer(c)
}
defer rows.Close()
for rows.Next() {
if err = rows.Scan(&service.Name, &service.Desc, &service.Url); err != nil {
log.Println("Error scaning services row: " + err.Error())
continue
var service database.Service
if err = service.Load(rows); err != nil {
util.Fail("error while loading service: %s", err.Error())
return util.ErrServer(c)
}
services = append(services, service)
}
rows.Close()
return c.JSON(fiber.Map{
"error": "",
"result": services,

139
api/routes/vote.go Normal file
View File

@ -0,0 +1,139 @@
package routes
import (
"database/sql"
"github.com/gofiber/fiber/v2"
"github.com/ngn13/website/api/database"
"github.com/ngn13/website/api/util"
)
func getVoteHash(id string, ip string) string {
return util.GetSHA512(id + "_" + ip)
}
func GET_VoteGet(c *fiber.Ctx) error {
var (
db *sql.DB
id string
hash string
vote database.Vote
found bool
err error
)
db = *(c.Locals("database").(**sql.DB))
if id = c.Query("id"); id == "" {
return util.ErrBadData(c)
}
hash = getVoteHash(id, util.GetIP(c))
if found, err = vote.Get(db, hash); err != nil {
util.Fail("error while searchig for a vote (\"%s\"): %s", hash, err.Error())
return util.ErrServer(c)
}
if !found {
return util.ErrEntryNotExists(c)
}
if vote.IsUpvote {
return c.JSON(fiber.Map{
"error": "",
"result": "upvote",
})
}
return c.JSON(fiber.Map{
"error": "",
"result": "downvote",
})
}
func GET_VoteSet(c *fiber.Ctx) error {
var (
db *sql.DB
id string
is_upvote bool
hash string
vote database.Vote
post database.Post
found bool
err error
)
db = *(c.Locals("database").(**sql.DB))
id = c.Query("id")
if c.Query("to") == "" || id == "" {
return util.ErrBadData(c)
}
if found, err = post.Get(db, id); err != nil {
util.Fail("error while searching for a post (\"%s\"): %s", id, err.Error())
return util.ErrServer(c)
}
if !found {
return util.ErrEntryNotExists(c)
}
is_upvote = c.Query("to") == "upvote"
hash = getVoteHash(id, util.GetIP(c))
if found, err = vote.Get(db, hash); err != nil {
util.Fail("error while searching for a vote (\"%s\"): %s", hash, err.Error())
return util.ErrServer(c)
}
if found {
if vote.IsUpvote == is_upvote {
return util.ErrEntryExists(c)
}
if vote.IsUpvote && !is_upvote {
post.Vote -= 2
}
if !vote.IsUpvote && is_upvote {
post.Vote += 2
}
vote.IsUpvote = is_upvote
if err = post.Update(db); err != nil {
util.Fail("error while updating post (\"%s\"): %s", post.ID, err.Error())
return util.ErrServer(c)
}
if err = vote.Update(db); err != nil {
util.Fail("error while updating vote (\"%s\"): %s", vote.Hash, err.Error())
return util.ErrServer(c)
}
return util.NoError(c)
}
vote.Hash = hash
vote.IsUpvote = is_upvote
if is_upvote {
post.Vote++
} else {
post.Vote--
}
if err = post.Update(db); err != nil {
util.Fail("error while updating post (\"%s\"): %s", post.ID, err.Error())
return util.ErrServer(c)
}
if err = vote.Save(db); err != nil {
util.Fail("error while updating vote (\"%s\"): %s", vote.Hash, err.Error())
return util.ErrServer(c)
}
return util.NoError(c)
}

12
api/util/log.go Normal file
View File

@ -0,0 +1,12 @@
package util
import (
"log"
"os"
)
var (
Info = log.New(os.Stdout, "\033[34m[info]\033[0m ", log.Ltime|log.Lshortfile).Printf
Warn = log.New(os.Stderr, "\033[33m[warn]\033[0m ", log.Ltime|log.Lshortfile).Printf
Fail = log.New(os.Stderr, "\033[31m[fail]\033[0m ", log.Ltime|log.Lshortfile).Printf
)

View File

@ -1,7 +1,8 @@
package util
import (
"log"
"crypto/sha512"
"fmt"
"math/rand"
"net/http"
"strings"
@ -9,6 +10,11 @@ import (
"github.com/gofiber/fiber/v2"
)
func GetSHA512(s string) string {
hasher := sha512.New()
return fmt.Sprintf("%x", hasher.Sum([]byte(s)))
}
func TitleToID(name string) string {
return strings.ToLower(strings.ReplaceAll(name, " ", ""))
}
@ -21,15 +27,6 @@ func CreateToken() string {
return string(s)
}
func ErrorCheck(err error, c *fiber.Ctx) bool {
if err != nil {
log.Printf("Server error: '%s' on %s\n", err, c.Path())
return true
}
return false
}
func ErrorJSON(error string) fiber.Map {
return fiber.Map{
"error": error,
@ -48,10 +45,14 @@ func ErrServer(c *fiber.Ctx) error {
return c.Status(http.StatusInternalServerError).JSON(ErrorJSON("Server error"))
}
func ErrExists(c *fiber.Ctx) error {
func ErrEntryExists(c *fiber.Ctx) error {
return c.Status(http.StatusConflict).JSON(ErrorJSON("Entry already exists"))
}
func ErrEntryNotExists(c *fiber.Ctx) error {
return c.Status(http.StatusNotFound).JSON(ErrorJSON("Entry does not exist"))
}
func ErrBadData(c *fiber.Ctx) error {
return c.Status(http.StatusBadRequest).JSON(ErrorJSON("Provided data is invalid"))
}
@ -64,6 +65,10 @@ func ErrAuth(c *fiber.Ctx) error {
return c.Status(http.StatusUnauthorized).JSON(ErrorJSON("Authentication failed"))
}
func ErrNotFound(c *fiber.Ctx) error {
return c.Status(http.StatusNotFound).JSON(ErrorJSON("Requested endpoint not found"))
}
func NoError(c *fiber.Ctx) error {
return c.Status(http.StatusOK).JSON(ErrorJSON(""))
}