API redesign, better styling for the blog page

This commit is contained in:
ngn
2024-07-24 01:15:37 +03:00
parent 51b9214a5c
commit 6400f1fa95
16 changed files with 622 additions and 558 deletions

View File

@ -1,6 +1,7 @@
package routes
import (
"log"
"net/http"
"os"
"strings"
@ -8,132 +9,164 @@ import (
"github.com/gofiber/fiber/v2"
"github.com/mattn/go-sqlite3"
"github.com/ngn13/website/api/database"
"github.com/ngn13/website/api/global"
"github.com/ngn13/website/api/util"
)
var Token string = util.CreateToken()
var Token string = util.CreateToken()
func AuthMiddleware(c *fiber.Ctx) error {
if c.Path() == "/admin/login" {
return c.Next()
}
if c.Path() == "/admin/login" {
return c.Next()
}
if c.Get("Authorization") != Token {
return util.ErrAuth(c)
}
if c.Get("Authorization") != Token {
return util.ErrAuth(c)
}
return c.Next()
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 Login(c *fiber.Ctx) error {
if c.Query("pass") != os.Getenv("PASSWORD") {
return c.Status(http.StatusUnauthorized).JSON(fiber.Map{
"error": "Authentication failed",
})
}
return c.Status(http.StatusOK).JSON(fiber.Map{
"error": "",
"token": Token,
})
log.Printf("New login from %s", util.GetIP(c))
return c.Status(http.StatusOK).JSON(fiber.Map{
"error": "",
"token": Token,
})
}
func Logout(c *fiber.Ctx) error{
Token = util.CreateToken()
return c.Status(http.StatusOK).JSON(fiber.Map{
"error": "",
})
func Logout(c *fiber.Ctx) error {
Token = util.CreateToken()
log.Printf("Logout from %s", util.GetIP(c))
return c.Status(http.StatusOK).JSON(fiber.Map{
"error": "",
})
}
func RemoveService(c *fiber.Ctx) error {
name := c.Query("name")
if name == "" {
util.ErrBadData(c)
}
var (
db *database.Type
name string
)
_, err := DB.Exec("DELETE FROM services WHERE name = ?", name)
if util.ErrorCheck(err, c){
return util.ErrServer(c)
}
return util.NoError(c)
db = c.Locals("database").(*database.Type)
name = c.Query("name")
if name == "" {
util.ErrBadData(c)
}
_, err := db.Sql.Exec("DELETE FROM services WHERE name = ?", name)
if util.ErrorCheck(err, c) {
return util.ErrServer(c)
}
return util.NoError(c)
}
func AddService(c *fiber.Ctx) error {
var service Service
if c.BodyParser(&service) != nil {
return util.ErrBadJSON(c)
}
var (
service global.Service
db *database.Type
)
if service.Name == "" || service.Desc == "" || service.Url == "" {
return util.ErrBadData(c)
}
db = c.Locals("database").(*database.Type)
rows, err := DB.Query("SELECT * FROM services WHERE name = ?", service.Name)
if util.ErrorCheck(err, c){
return util.ErrServer(c)
}
if rows.Next() {
rows.Close()
return util.ErrExists(c)
}
if c.BodyParser(&service) != nil {
return util.ErrBadJSON(c)
}
rows.Close()
if service.Name == "" || service.Desc == "" || service.Url == "" {
return util.ErrBadData(c)
}
_, err = DB.Exec(
"INSERT INTO services(name, desc, url) values(?, ?, ?)",
service.Name, service.Desc, service.Url,
)
rows, err := db.Sql.Query("SELECT * FROM services WHERE name = ?", service.Name)
if util.ErrorCheck(err, c) {
return util.ErrServer(c)
}
if util.ErrorCheck(err, c){
return util.ErrServer(c)
}
if rows.Next() {
rows.Close()
return util.ErrExists(c)
}
return util.NoError(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) {
return util.ErrServer(c)
}
return util.NoError(c)
}
func RemovePost(c *fiber.Ctx) error{
var id = c.Query("id")
if id == "" {
return util.ErrBadData(c)
}
func RemovePost(c *fiber.Ctx) error {
var (
db *database.Type
id string
)
_, err := DB.Exec("DELETE FROM posts WHERE id = ?", id)
if util.ErrorCheck(err, c){
return util.ErrServer(c)
}
db = c.Locals("database").(*database.Type)
id = c.Query("id")
return util.NoError(c)
if id == "" {
return util.ErrBadData(c)
}
_, err := db.Sql.Exec("DELETE FROM posts WHERE id = ?", id)
if util.ErrorCheck(err, c) {
return util.ErrServer(c)
}
return util.NoError(c)
}
func AddPost(c *fiber.Ctx) error{
var post Post
post.Public = 1
func AddPost(c *fiber.Ctx) error {
var (
db *database.Type
post global.Post
)
if c.BodyParser(&post) != nil {
return util.ErrBadJSON(c)
}
db = c.Locals("database").(*database.Type)
post.Public = 1
if post.Title == "" || post.Author == "" || post.Content == "" {
return util.ErrBadData(c)
}
if c.BodyParser(&post) != nil {
return util.ErrBadJSON(c)
}
post.Date = time.Now().Format("02/01/06")
post.ID = TitleToID(post.Title)
if post.Title == "" || post.Author == "" || post.Content == "" {
return util.ErrBadData(c)
}
_, err := DB.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,
)
post.Date = time.Now().Format("02/01/06")
post.ID = util.TitleToID(post.Title)
if err != nil && strings.Contains(err.Error(), sqlite3.ErrConstraintUnique.Error()) {
return util.ErrExists(c)
}
_, 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 util.ErrorCheck(err, c){
return util.ErrExists(c)
}
if err != nil && strings.Contains(err.Error(), sqlite3.ErrConstraintUnique.Error()) {
return util.ErrExists(c)
}
return util.NoError(c)
if util.ErrorCheck(err, c) {
return util.ErrExists(c)
}
return util.NoError(c)
}

View File

@ -2,274 +2,317 @@ package routes
import (
"database/sql"
"log"
"fmt"
"net/http"
"net/url"
"fmt"
"os"
"strings"
"time"
"github.com/gofiber/fiber/v2"
"github.com/gorilla/feeds"
"github.com/ngn13/website/api/database"
"github.com/ngn13/website/api/global"
"github.com/ngn13/website/api/util"
)
func BlogDb(db *sql.DB) {
_, err := db.Exec(`
CREATE TABLE IF NOT EXISTS posts(
id TEXT NOT NULL UNIQUE,
title TEXT NOT NULL,
author TEXT NOT NULL,
date TEXT NOT NULL,
content TEXT NOT NULL,
public INTEGER NOT NULL,
vote INTEGER NOT NULL
);
`)
DB = db
if err != nil {
log.Fatal("Error creating table: "+err.Error())
}
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 GetIP(c *fiber.Ctx) string {
if c.Get("X-Real-IP") != "" {
return strings.Clone(c.Get("X-Real-IP"))
}
func GetPostByID(db *database.Type, id string) (global.Post, string) {
var post global.Post = global.Post{}
post.Title = "NONE"
return c.IP()
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 id = c.Query("id")
if id == "" {
return util.ErrBadData(c)
}
func VoteStat(c *fiber.Ctx) error {
var (
db *database.Type
id string
)
for i := 0; i < len(votelist); i++ {
if votelist[i].Client == GetIP(c) && votelist[i].Post == id {
return c.JSON(fiber.Map {
"error": "",
"result": votelist[i].Status,
})
}
}
db = c.Locals("database").(*database.Type)
id = c.Query("id")
return c.Status(http.StatusNotFound).JSON(util.ErrorJSON("Client never voted"))
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{
var id = c.Query("id")
var to = c.Query("to")
voted := false
func VoteSet(c *fiber.Ctx) error {
var (
id string
to string
voted bool
db *database.Type
)
if id == "" || (to != "upvote" && to != "downvote") {
return util.ErrBadData(c)
}
db = c.Locals("database").(*database.Type)
id = c.Query("id")
to = c.Query("to")
voted = false
for i := 0; i < len(votelist); i++ {
if votelist[i].Client == GetIP(c) && votelist[i].Post == id && votelist[i].Status == to {
return c.Status(http.StatusForbidden).JSON(util.ErrorJSON("Client already voted"))
}
if id == "" || (to != "upvote" && to != "downvote") {
return util.ErrBadData(c)
}
if votelist[i].Client == GetIP(c) && votelist[i].Post == id && votelist[i].Status != to {
voted = true
}
}
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"))
}
post, msg := GetPostByID(id)
if msg != ""{
return c.Status(http.StatusNotFound).JSON(util.ErrorJSON(msg))
}
if db.Votes[i].Client == util.GetIP(c) && db.Votes[i].Post == id && db.Votes[i].Status != to {
voted = true
}
}
vote := post.Vote+1
post, msg := GetPostByID(db, id)
if msg != "" {
return c.Status(http.StatusNotFound).JSON(util.ErrorJSON(msg))
}
if to == "downvote" {
vote = post.Vote-1
}
vote := post.Vote + 1
if to == "downvote" && voted {
vote = vote-1
}
if to == "downvote" {
vote = post.Vote - 1
}
if to == "upvote" && voted {
vote = vote+1
}
if to == "downvote" && voted {
vote = vote - 1
}
_, err := DB.Exec("UPDATE posts SET vote = ? WHERE title = ?", vote, post.Title)
if util.ErrorCheck(err, c){
return util.ErrServer(c)
}
if to == "upvote" && voted {
vote = vote + 1
}
for i := 0; i < len(votelist); i++ {
if votelist[i].Client == GetIP(c) && votelist[i].Post == id && votelist[i].Status != to {
votelist[i].Status = to
return util.NoError(c)
}
}
_, err := db.Sql.Exec("UPDATE posts SET vote = ? WHERE title = ?", vote, post.Title)
if util.ErrorCheck(err, c) {
return util.ErrServer(c)
}
var entry = Vote{}
entry.Client = GetIP(c)
entry.Status = to
entry.Post = id
votelist = append(votelist, entry)
return util.NoError(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 = c.Query("id")
if id == "" {
return util.ErrBadData(c)
}
func GetPost(c *fiber.Ctx) error {
var (
id string
db *database.Type
)
post, msg := GetPostByID(id)
if msg != ""{
return c.Status(http.StatusNotFound).JSON(util.ErrorJSON(msg))
}
id = c.Query("id")
db = c.Locals("database").(*database.Type)
return c.JSON(fiber.Map {
"error": "",
"result": post,
})
if id == "" {
return util.ErrBadData(c)
}
post, msg := GetPostByID(db, id)
if msg != "" {
return c.Status(http.StatusNotFound).JSON(util.ErrorJSON(msg))
}
return c.JSON(fiber.Map{
"error": "",
"result": post,
})
}
func SumPost(c *fiber.Ctx) error {
var (
posts []global.Post
post global.Post
db *database.Type
err error
)
db = c.Locals("database").(*database.Type)
func SumPost(c *fiber.Ctx) error{
var posts []Post = []Post{}
rows, err := DB.Query("SELECT * FROM posts")
if util.ErrorCheck(err, c) {
return util.ErrServer(c)
}
rows, err := db.Sql.Query("SELECT * FROM posts")
if util.ErrorCheck(err, c) {
return util.ErrServer(c)
}
for rows.Next() {
var post Post
err := PostFromRow(&post, rows)
for rows.Next() {
err = PostFromRow(&post, rows)
if util.ErrorCheck(err, c) {
return util.ErrServer(c)
}
if post.Public == 0 {
continue
}
if util.ErrorCheck(err, c) {
return util.ErrServer(c)
}
if len(post.Content) > 255{
post.Content = post.Content[0:250]
}
if post.Public == 0 {
continue
}
posts = append(posts, post)
}
rows.Close()
if len(post.Content) > 255 {
post.Content = post.Content[0:250]
}
return c.JSON(fiber.Map {
"error": "",
"result": posts,
})
posts = append(posts, post)
}
rows.Close()
return c.JSON(fiber.Map{
"error": "",
"result": posts,
})
}
func GetFeed() (*feeds.Feed, error){
var posts []Post = []Post{}
rows, err := DB.Query("SELECT * FROM posts")
if err != nil {
return nil, err
}
func GetFeed(db *database.Type) (*feeds.Feed, error) {
var (
posts []global.Post
post global.Post
err error
)
for rows.Next() {
var post Post
err := PostFromRow(&post, rows)
rows, err := db.Sql.Query("SELECT * FROM posts")
if err != nil {
return nil, err
}
if err != nil {
return nil, err
}
if post.Public == 0 {
continue
}
for rows.Next() {
err := PostFromRow(&post, rows)
posts = append(posts, post)
}
rows.Close()
if err != nil {
return nil, err
}
if post.Public == 0 {
continue
}
blogurl, err := url.JoinPath(os.Getenv("FRONTEND_URL"), "/blog")
if err != nil {
return nil, fmt.Errorf("failed to create the blog URL: %s", err.Error())
}
posts = append(posts, post)
}
rows.Close()
feed := &feeds.Feed{
Title: "[ngn.tf] | blog",
Link: &feeds.Link{Href: blogurl},
Description: "ngn's personal blog",
Author: &feeds.Author{Name: "ngn", Email: "ngn@ngn.tf"},
Created: time.Now(),
}
blogurl, err := url.JoinPath(os.Getenv("FRONTEND_URL"), "/blog")
if err != nil {
return nil, fmt.Errorf("failed to create the blog URL: %s", err.Error())
}
feed.Items = []*feeds.Item{}
for _, p := range posts {
purl, err := url.JoinPath(blogurl, p.ID)
if err != nil {
return nil, fmt.Errorf("failed to create URL for '%s': %s\n", p.ID, err.Error())
}
feed := &feeds.Feed{
Title: "[ngn.tf] | blog",
Link: &feeds.Link{Href: blogurl},
Description: "ngn's personal blog",
Author: &feeds.Author{Name: "ngn", Email: "ngn@ngn.tf"},
Created: time.Now(),
}
parsed, err := time.Parse("02/01/06", p.Date)
if err != nil {
return nil, fmt.Errorf("failed to parse time for '%s': %s\n", p.ID, err.Error())
}
feed.Items = []*feeds.Item{}
for _, p := range posts {
purl, err := url.JoinPath(blogurl, p.ID)
if err != nil {
return nil, fmt.Errorf("failed to create URL for '%s': %s\n", p.ID, err.Error())
}
feed.Items = append(feed.Items, &feeds.Item{
Id: p.ID,
Title: p.Title,
Link: &feeds.Link{Href: purl},
Author: &feeds.Author{Name: p.Author},
Created: parsed,
})
}
parsed, err := time.Parse("02/01/06", p.Date)
if err != nil {
return nil, fmt.Errorf("failed to parse time for '%s': %s\n", p.ID, err.Error())
}
return feed, nil
feed.Items = append(feed.Items, &feeds.Item{
Id: p.ID,
Title: p.Title,
Link: &feeds.Link{Href: purl},
Author: &feeds.Author{Name: p.Author},
Created: parsed,
})
}
return feed, nil
}
func GetAtomFeed(c *fiber.Ctx) error {
feed, err := GetFeed()
if util.ErrorCheck(err, c){
return util.ErrServer(c)
}
feed, err := GetFeed(c.Locals("database").(*database.Type))
if util.ErrorCheck(err, c) {
return util.ErrServer(c)
}
atom, err := feed.ToAtom()
if util.ErrorCheck(err, c){
return util.ErrServer(c)
}
atom, err := feed.ToAtom()
if util.ErrorCheck(err, c) {
return util.ErrServer(c)
}
c.Set("Content-Type", "application/atom+xml")
return c.Send([]byte(atom))
c.Set("Content-Type", "application/atom+xml")
return c.Send([]byte(atom))
}
func GetRSSFeed(c *fiber.Ctx) error {
feed, err := GetFeed()
if util.ErrorCheck(err, c){
return util.ErrServer(c)
}
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)
}
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))
c.Set("Content-Type", "application/rss+xml")
return c.Send([]byte(rss))
}
func GetJSONFeed(c *fiber.Ctx) error {
feed, err := GetFeed()
if util.ErrorCheck(err, c){
return util.ErrServer(c)
}
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))
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))
}

View File

@ -1,74 +0,0 @@
package routes
import (
"database/sql"
"strings"
)
// ############### BLOG ###############
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"`
}
var votelist = []Vote{}
type Vote struct {
Post string
Client string
Status string
}
func PostFromRow(post *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(id string) (Post, string) {
var post Post = Post{}
post.Title = "NONE"
rows, err := DB.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 TitleToID(name string) string{
return strings.ToLower(strings.ReplaceAll(name, " ", ""))
}
// ############### SERVICES ###############
type Service struct {
Name string `json:"name"`
Desc string `json:"desc"`
Url string `json:"url"`
}

View File

@ -1,48 +0,0 @@
package routes
import (
"database/sql"
"net/http"
"github.com/gofiber/fiber/v2"
)
var DB *sql.DB
func Setup(app *fiber.App, db *sql.DB){
// database init
DB = db
// index route
app.Get("/", func(c *fiber.Ctx) error {
return c.Send([]byte("o/"))
})
// blog routes
app.Get("/blog/feed.atom", GetAtomFeed)
app.Get("/blog/feed.rss", GetRSSFeed)
app.Get("/blog/feed.json", GetJSONFeed)
app.Get("/blog/sum", SumPost)
app.Get("/blog/get", GetPost)
app.Get("/blog/vote/set", VoteSet)
app.Get("/blog/vote/status", VoteStat)
// service routes
app.Get("/services/all", GetServices)
// admin routes
app.Use("/admin*", AuthMiddleware)
app.Get("/admin/login", Login)
app.Get("/admin/logout", Logout)
app.Put("/admin/service/add", AddService)
app.Delete("/admin/service/remove", RemoveService)
app.Put("/admin/blog/add", AddPost)
app.Delete("/admin/blog/remove", RemovePost)
// 404 page
app.All("*", func(c *fiber.Ctx) error {
return c.Status(http.StatusNotFound).JSON(fiber.Map {
"error": "Requested endpoint not found",
})
})
}

View File

@ -1,48 +1,41 @@
package routes
import (
"database/sql"
"log"
"github.com/gofiber/fiber/v2"
"github.com/ngn13/website/api/database"
"github.com/ngn13/website/api/global"
"github.com/ngn13/website/api/util"
)
func ServicesDb(db *sql.DB) {
_, err := db.Exec(`
CREATE TABLE IF NOT EXISTS services(
name TEXT NOT NULL UNIQUE,
desc TEXT NOT NULL,
url TEXT NOT NULL
);
`)
if err != nil {
log.Fatal("Error creating table: "+err.Error())
}
}
func GetServices(c *fiber.Ctx) error {
var services []Service = []Service{}
var (
services []global.Service = []global.Service{}
service global.Service
db *database.Type
err error
)
rows, err := DB.Query("SELECT * FROM services")
if util.ErrorCheck(err, c) {
return util.ErrServer(c)
}
db = c.Locals("database").(*database.Type)
for rows.Next() {
var service Service
err := rows.Scan(&service.Name, &service.Desc, &service.Url)
if err != nil {
log.Println("Error scaning services row: "+err.Error())
continue
}
services = append(services, service)
}
rows.Close()
rows, err := db.Sql.Query("SELECT * FROM services")
if util.ErrorCheck(err, c) {
return util.ErrServer(c)
}
return c.JSON(fiber.Map {
"error": "",
"result": services,
})
for rows.Next() {
if err = rows.Scan(&service.Name, &service.Desc, &service.Url); err != nil {
log.Println("Error scaning services row: " + err.Error())
continue
}
services = append(services, service)
}
rows.Close()
return c.JSON(fiber.Map{
"error": "",
"result": services,
})
}