API redesign, better styling for the blog page
This commit is contained in:
parent
51b9214a5c
commit
6400f1fa95
@ -1,7 +1,12 @@
|
|||||||
server: *.go routes/* util/*
|
all: server
|
||||||
|
|
||||||
|
server: *.go routes/* util/* global/* database/*
|
||||||
go build -o server .
|
go build -o server .
|
||||||
|
|
||||||
test:
|
test:
|
||||||
FRONTEND_URL=http://localhost:5173/ PASSWORD=test ./server
|
FRONTEND_URL=http://localhost:5173/ PASSWORD=test ./server
|
||||||
|
|
||||||
.PHONY: test
|
format:
|
||||||
|
gofmt -s -w .
|
||||||
|
|
||||||
|
.PHONY: test format
|
||||||
|
55
api/database/database.go
Normal file
55
api/database/database.go
Normal file
@ -0,0 +1,55 @@
|
|||||||
|
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(`
|
||||||
|
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
|
||||||
|
);
|
||||||
|
`)
|
||||||
|
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
_, err = t.Sql.Exec(`
|
||||||
|
CREATE TABLE IF NOT EXISTS services(
|
||||||
|
name TEXT NOT NULL UNIQUE,
|
||||||
|
desc TEXT NOT NULL,
|
||||||
|
url TEXT NOT NULL
|
||||||
|
);
|
||||||
|
`)
|
||||||
|
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
func (t *Type) Open(p string) error {
|
||||||
|
var err error
|
||||||
|
|
||||||
|
if t.Sql, err = sql.Open("sqlite3", p); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
return t.Setup()
|
||||||
|
}
|
||||||
|
|
||||||
|
func (t *Type) Close() {
|
||||||
|
t.Sql.Close()
|
||||||
|
}
|
23
api/global/global.go
Normal file
23
api/global/global.go
Normal file
@ -0,0 +1,23 @@
|
|||||||
|
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
|
||||||
|
}
|
90
api/main.go
90
api/main.go
@ -1,42 +1,78 @@
|
|||||||
package main
|
package main
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"database/sql"
|
|
||||||
"log"
|
"log"
|
||||||
|
"net/http"
|
||||||
|
|
||||||
"github.com/gofiber/fiber/v2"
|
"github.com/gofiber/fiber/v2"
|
||||||
|
"github.com/ngn13/website/api/database"
|
||||||
"github.com/ngn13/website/api/routes"
|
"github.com/ngn13/website/api/routes"
|
||||||
)
|
)
|
||||||
|
|
||||||
func CorsMiddleware(c *fiber.Ctx) error {
|
|
||||||
c.Set("Access-Control-Allow-Origin", "*")
|
|
||||||
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")
|
|
||||||
return c.Next()
|
|
||||||
}
|
|
||||||
|
|
||||||
func main() {
|
func main() {
|
||||||
app := fiber.New(fiber.Config{
|
var (
|
||||||
DisableStartupMessage: true,
|
app *fiber.App
|
||||||
})
|
db database.Type
|
||||||
app.Use(CorsMiddleware)
|
err error
|
||||||
|
)
|
||||||
|
|
||||||
db, err := sql.Open("sqlite3", "data.db")
|
if err = db.Open("data.db"); err != nil {
|
||||||
if err != nil {
|
log.Fatalf("Cannot access the database: %s", err.Error())
|
||||||
log.Fatal("Cannot connect to the database: "+err.Error())
|
return
|
||||||
}
|
}
|
||||||
|
defer db.Close()
|
||||||
|
|
||||||
log.Println("Creating tables")
|
app = fiber.New(fiber.Config{
|
||||||
routes.BlogDb(db)
|
DisableStartupMessage: true,
|
||||||
routes.ServicesDb(db)
|
})
|
||||||
routes.Setup(app, db)
|
|
||||||
|
|
||||||
log.Println("Starting web server at port 7001")
|
app.Use("*", func(c *fiber.Ctx) error {
|
||||||
err = app.Listen("0.0.0.0:7001")
|
c.Set("Access-Control-Allow-Origin", "*")
|
||||||
if err != nil {
|
c.Set("Access-Control-Allow-Headers",
|
||||||
log.Printf("Error starting the webserver: %s", err.Error())
|
"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()
|
||||||
|
})
|
||||||
|
|
||||||
defer db.Close()
|
// index route
|
||||||
|
app.Get("/", func(c *fiber.Ctx) error {
|
||||||
|
return c.Send([]byte("o/"))
|
||||||
|
})
|
||||||
|
|
||||||
|
// 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)
|
||||||
|
|
||||||
|
// service routes
|
||||||
|
service_routes := app.Group("services")
|
||||||
|
service_routes.Get("/all", routes.GetServices)
|
||||||
|
|
||||||
|
// 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
|
||||||
|
app.All("*", func(c *fiber.Ctx) error {
|
||||||
|
return c.Status(http.StatusNotFound).JSON(fiber.Map{
|
||||||
|
"error": "Requested endpoint not found",
|
||||||
|
})
|
||||||
|
})
|
||||||
|
|
||||||
|
log.Println("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())
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
@ -1,6 +1,7 @@
|
|||||||
package routes
|
package routes
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
"log"
|
||||||
"net/http"
|
"net/http"
|
||||||
"os"
|
"os"
|
||||||
"strings"
|
"strings"
|
||||||
@ -8,132 +9,164 @@ import (
|
|||||||
|
|
||||||
"github.com/gofiber/fiber/v2"
|
"github.com/gofiber/fiber/v2"
|
||||||
"github.com/mattn/go-sqlite3"
|
"github.com/mattn/go-sqlite3"
|
||||||
|
"github.com/ngn13/website/api/database"
|
||||||
|
"github.com/ngn13/website/api/global"
|
||||||
"github.com/ngn13/website/api/util"
|
"github.com/ngn13/website/api/util"
|
||||||
)
|
)
|
||||||
|
|
||||||
var Token string = util.CreateToken()
|
var Token string = util.CreateToken()
|
||||||
|
|
||||||
func AuthMiddleware(c *fiber.Ctx) error {
|
func AuthMiddleware(c *fiber.Ctx) error {
|
||||||
if c.Path() == "/admin/login" {
|
if c.Path() == "/admin/login" {
|
||||||
return c.Next()
|
return c.Next()
|
||||||
}
|
}
|
||||||
|
|
||||||
if c.Get("Authorization") != Token {
|
if c.Get("Authorization") != Token {
|
||||||
return util.ErrAuth(c)
|
return util.ErrAuth(c)
|
||||||
}
|
}
|
||||||
|
|
||||||
return c.Next()
|
return c.Next()
|
||||||
}
|
}
|
||||||
|
|
||||||
func Login(c *fiber.Ctx) error{
|
func Login(c *fiber.Ctx) error {
|
||||||
if c.Query("pass") != os.Getenv("PASSWORD") {
|
if c.Query("pass") != os.Getenv("PASSWORD") {
|
||||||
return c.Status(http.StatusUnauthorized).JSON(fiber.Map{
|
return c.Status(http.StatusUnauthorized).JSON(fiber.Map{
|
||||||
"error": "Authentication failed",
|
"error": "Authentication failed",
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
return c.Status(http.StatusOK).JSON(fiber.Map{
|
log.Printf("New login from %s", util.GetIP(c))
|
||||||
"error": "",
|
|
||||||
"token": Token,
|
return c.Status(http.StatusOK).JSON(fiber.Map{
|
||||||
})
|
"error": "",
|
||||||
|
"token": Token,
|
||||||
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
func Logout(c *fiber.Ctx) error{
|
func Logout(c *fiber.Ctx) error {
|
||||||
Token = util.CreateToken()
|
Token = util.CreateToken()
|
||||||
return c.Status(http.StatusOK).JSON(fiber.Map{
|
|
||||||
"error": "",
|
log.Printf("Logout from %s", util.GetIP(c))
|
||||||
})
|
|
||||||
|
return c.Status(http.StatusOK).JSON(fiber.Map{
|
||||||
|
"error": "",
|
||||||
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
func RemoveService(c *fiber.Ctx) error {
|
func RemoveService(c *fiber.Ctx) error {
|
||||||
name := c.Query("name")
|
var (
|
||||||
if name == "" {
|
db *database.Type
|
||||||
util.ErrBadData(c)
|
name string
|
||||||
}
|
)
|
||||||
|
|
||||||
_, err := DB.Exec("DELETE FROM services WHERE name = ?", name)
|
db = c.Locals("database").(*database.Type)
|
||||||
if util.ErrorCheck(err, c){
|
name = c.Query("name")
|
||||||
return util.ErrServer(c)
|
|
||||||
}
|
|
||||||
|
|
||||||
return util.NoError(c)
|
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 {
|
func AddService(c *fiber.Ctx) error {
|
||||||
var service Service
|
var (
|
||||||
if c.BodyParser(&service) != nil {
|
service global.Service
|
||||||
return util.ErrBadJSON(c)
|
db *database.Type
|
||||||
}
|
)
|
||||||
|
|
||||||
if service.Name == "" || service.Desc == "" || service.Url == "" {
|
db = c.Locals("database").(*database.Type)
|
||||||
return util.ErrBadData(c)
|
|
||||||
}
|
|
||||||
|
|
||||||
rows, err := DB.Query("SELECT * FROM services WHERE name = ?", service.Name)
|
if c.BodyParser(&service) != nil {
|
||||||
if util.ErrorCheck(err, c){
|
return util.ErrBadJSON(c)
|
||||||
return util.ErrServer(c)
|
}
|
||||||
}
|
|
||||||
|
|
||||||
if rows.Next() {
|
if service.Name == "" || service.Desc == "" || service.Url == "" {
|
||||||
rows.Close()
|
return util.ErrBadData(c)
|
||||||
return util.ErrExists(c)
|
}
|
||||||
}
|
|
||||||
|
|
||||||
rows.Close()
|
rows, err := db.Sql.Query("SELECT * FROM services WHERE name = ?", service.Name)
|
||||||
|
if util.ErrorCheck(err, c) {
|
||||||
|
return util.ErrServer(c)
|
||||||
|
}
|
||||||
|
|
||||||
_, err = DB.Exec(
|
if rows.Next() {
|
||||||
"INSERT INTO services(name, desc, url) values(?, ?, ?)",
|
rows.Close()
|
||||||
service.Name, service.Desc, service.Url,
|
return util.ErrExists(c)
|
||||||
)
|
}
|
||||||
|
|
||||||
if util.ErrorCheck(err, c){
|
rows.Close()
|
||||||
return util.ErrServer(c)
|
|
||||||
}
|
|
||||||
|
|
||||||
return util.NoError(c)
|
_, 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{
|
func RemovePost(c *fiber.Ctx) error {
|
||||||
var id = c.Query("id")
|
var (
|
||||||
if id == "" {
|
db *database.Type
|
||||||
return util.ErrBadData(c)
|
id string
|
||||||
}
|
)
|
||||||
|
|
||||||
_, err := DB.Exec("DELETE FROM posts WHERE id = ?", id)
|
db = c.Locals("database").(*database.Type)
|
||||||
if util.ErrorCheck(err, c){
|
id = c.Query("id")
|
||||||
return util.ErrServer(c)
|
|
||||||
}
|
|
||||||
|
|
||||||
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{
|
func AddPost(c *fiber.Ctx) error {
|
||||||
var post Post
|
var (
|
||||||
post.Public = 1
|
db *database.Type
|
||||||
|
post global.Post
|
||||||
|
)
|
||||||
|
|
||||||
if c.BodyParser(&post) != nil {
|
db = c.Locals("database").(*database.Type)
|
||||||
return util.ErrBadJSON(c)
|
post.Public = 1
|
||||||
}
|
|
||||||
|
|
||||||
if post.Title == "" || post.Author == "" || post.Content == "" {
|
if c.BodyParser(&post) != nil {
|
||||||
return util.ErrBadData(c)
|
return util.ErrBadJSON(c)
|
||||||
}
|
}
|
||||||
|
|
||||||
post.Date = time.Now().Format("02/01/06")
|
if post.Title == "" || post.Author == "" || post.Content == "" {
|
||||||
post.ID = TitleToID(post.Title)
|
return util.ErrBadData(c)
|
||||||
|
}
|
||||||
|
|
||||||
_, err := DB.Exec(
|
post.Date = time.Now().Format("02/01/06")
|
||||||
"INSERT INTO posts(id, title, author, date, content, public, vote) values(?, ?, ?, ?, ?, ?, ?)",
|
post.ID = util.TitleToID(post.Title)
|
||||||
post.ID, post.Title, post.Author, post.Date, post.Content, post.Public, post.Vote,
|
|
||||||
)
|
|
||||||
|
|
||||||
if err != nil && strings.Contains(err.Error(), sqlite3.ErrConstraintUnique.Error()) {
|
_, err := db.Sql.Exec(
|
||||||
return util.ErrExists(c)
|
"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){
|
if err != nil && strings.Contains(err.Error(), sqlite3.ErrConstraintUnique.Error()) {
|
||||||
return util.ErrExists(c)
|
return util.ErrExists(c)
|
||||||
}
|
}
|
||||||
|
|
||||||
return util.NoError(c)
|
if util.ErrorCheck(err, c) {
|
||||||
|
return util.ErrExists(c)
|
||||||
|
}
|
||||||
|
|
||||||
|
return util.NoError(c)
|
||||||
}
|
}
|
||||||
|
@ -2,274 +2,317 @@ package routes
|
|||||||
|
|
||||||
import (
|
import (
|
||||||
"database/sql"
|
"database/sql"
|
||||||
"log"
|
"fmt"
|
||||||
"net/http"
|
"net/http"
|
||||||
"net/url"
|
"net/url"
|
||||||
"fmt"
|
|
||||||
"os"
|
"os"
|
||||||
"strings"
|
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
"github.com/gofiber/fiber/v2"
|
"github.com/gofiber/fiber/v2"
|
||||||
"github.com/gorilla/feeds"
|
"github.com/gorilla/feeds"
|
||||||
|
"github.com/ngn13/website/api/database"
|
||||||
|
"github.com/ngn13/website/api/global"
|
||||||
"github.com/ngn13/website/api/util"
|
"github.com/ngn13/website/api/util"
|
||||||
)
|
)
|
||||||
|
|
||||||
func BlogDb(db *sql.DB) {
|
func PostFromRow(post *global.Post, rows *sql.Rows) error {
|
||||||
_, err := db.Exec(`
|
err := rows.Scan(&post.ID, &post.Title, &post.Author, &post.Date, &post.Content, &post.Public, &post.Vote)
|
||||||
CREATE TABLE IF NOT EXISTS posts(
|
if err != nil {
|
||||||
id TEXT NOT NULL UNIQUE,
|
return err
|
||||||
title TEXT NOT NULL,
|
}
|
||||||
author TEXT NOT NULL,
|
|
||||||
date TEXT NOT NULL,
|
return nil
|
||||||
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 GetIP(c *fiber.Ctx) string {
|
func GetPostByID(db *database.Type, id string) (global.Post, string) {
|
||||||
if c.Get("X-Real-IP") != "" {
|
var post global.Post = global.Post{}
|
||||||
return strings.Clone(c.Get("X-Real-IP"))
|
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{
|
func VoteStat(c *fiber.Ctx) error {
|
||||||
var id = c.Query("id")
|
var (
|
||||||
if id == "" {
|
db *database.Type
|
||||||
return util.ErrBadData(c)
|
id string
|
||||||
}
|
)
|
||||||
|
|
||||||
for i := 0; i < len(votelist); i++ {
|
db = c.Locals("database").(*database.Type)
|
||||||
if votelist[i].Client == GetIP(c) && votelist[i].Post == id {
|
id = c.Query("id")
|
||||||
return c.JSON(fiber.Map {
|
|
||||||
"error": "",
|
|
||||||
"result": votelist[i].Status,
|
|
||||||
})
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
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{
|
func VoteSet(c *fiber.Ctx) error {
|
||||||
var id = c.Query("id")
|
var (
|
||||||
var to = c.Query("to")
|
id string
|
||||||
voted := false
|
to string
|
||||||
|
voted bool
|
||||||
|
db *database.Type
|
||||||
|
)
|
||||||
|
|
||||||
if id == "" || (to != "upvote" && to != "downvote") {
|
db = c.Locals("database").(*database.Type)
|
||||||
return util.ErrBadData(c)
|
id = c.Query("id")
|
||||||
}
|
to = c.Query("to")
|
||||||
|
voted = false
|
||||||
|
|
||||||
for i := 0; i < len(votelist); i++ {
|
if id == "" || (to != "upvote" && to != "downvote") {
|
||||||
if votelist[i].Client == GetIP(c) && votelist[i].Post == id && votelist[i].Status == to {
|
return util.ErrBadData(c)
|
||||||
return c.Status(http.StatusForbidden).JSON(util.ErrorJSON("Client already voted"))
|
}
|
||||||
}
|
|
||||||
|
|
||||||
if votelist[i].Client == GetIP(c) && votelist[i].Post == id && votelist[i].Status != to {
|
for i := 0; i < len(db.Votes); i++ {
|
||||||
voted = true
|
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 db.Votes[i].Client == util.GetIP(c) && db.Votes[i].Post == id && db.Votes[i].Status != to {
|
||||||
if msg != ""{
|
voted = true
|
||||||
return c.Status(http.StatusNotFound).JSON(util.ErrorJSON(msg))
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
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 {
|
if to == "downvote" {
|
||||||
vote = vote-1
|
vote = post.Vote - 1
|
||||||
}
|
}
|
||||||
|
|
||||||
if to == "upvote" && voted {
|
if to == "downvote" && voted {
|
||||||
vote = vote+1
|
vote = vote - 1
|
||||||
}
|
}
|
||||||
|
|
||||||
_, err := DB.Exec("UPDATE posts SET vote = ? WHERE title = ?", vote, post.Title)
|
if to == "upvote" && voted {
|
||||||
if util.ErrorCheck(err, c){
|
vote = vote + 1
|
||||||
return util.ErrServer(c)
|
}
|
||||||
}
|
|
||||||
|
|
||||||
for i := 0; i < len(votelist); i++ {
|
_, err := db.Sql.Exec("UPDATE posts SET vote = ? WHERE title = ?", vote, post.Title)
|
||||||
if votelist[i].Client == GetIP(c) && votelist[i].Post == id && votelist[i].Status != to {
|
if util.ErrorCheck(err, c) {
|
||||||
votelist[i].Status = to
|
return util.ErrServer(c)
|
||||||
return util.NoError(c)
|
}
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
var entry = Vote{}
|
for i := 0; i < len(db.Votes); i++ {
|
||||||
entry.Client = GetIP(c)
|
if db.Votes[i].Client == util.GetIP(c) && db.Votes[i].Post == id && db.Votes[i].Status != to {
|
||||||
entry.Status = to
|
db.Votes[i].Status = to
|
||||||
entry.Post = id
|
return util.NoError(c)
|
||||||
votelist = append(votelist, entry)
|
}
|
||||||
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{
|
func GetPost(c *fiber.Ctx) error {
|
||||||
var id = c.Query("id")
|
var (
|
||||||
if id == "" {
|
id string
|
||||||
return util.ErrBadData(c)
|
db *database.Type
|
||||||
}
|
)
|
||||||
|
|
||||||
post, msg := GetPostByID(id)
|
id = c.Query("id")
|
||||||
if msg != ""{
|
db = c.Locals("database").(*database.Type)
|
||||||
return c.Status(http.StatusNotFound).JSON(util.ErrorJSON(msg))
|
|
||||||
}
|
|
||||||
|
|
||||||
return c.JSON(fiber.Map {
|
if id == "" {
|
||||||
"error": "",
|
return util.ErrBadData(c)
|
||||||
"result": post,
|
}
|
||||||
})
|
|
||||||
|
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{
|
rows, err := db.Sql.Query("SELECT * FROM posts")
|
||||||
var posts []Post = []Post{}
|
if util.ErrorCheck(err, c) {
|
||||||
rows, err := DB.Query("SELECT * FROM posts")
|
return util.ErrServer(c)
|
||||||
if util.ErrorCheck(err, c) {
|
}
|
||||||
return util.ErrServer(c)
|
|
||||||
}
|
|
||||||
|
|
||||||
for rows.Next() {
|
for rows.Next() {
|
||||||
var post Post
|
err = PostFromRow(&post, rows)
|
||||||
err := PostFromRow(&post, rows)
|
|
||||||
|
|
||||||
if util.ErrorCheck(err, c) {
|
if util.ErrorCheck(err, c) {
|
||||||
return util.ErrServer(c)
|
return util.ErrServer(c)
|
||||||
}
|
}
|
||||||
|
|
||||||
if post.Public == 0 {
|
if post.Public == 0 {
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
|
|
||||||
if len(post.Content) > 255{
|
if len(post.Content) > 255 {
|
||||||
post.Content = post.Content[0:250]
|
post.Content = post.Content[0:250]
|
||||||
}
|
}
|
||||||
|
|
||||||
posts = append(posts, post)
|
posts = append(posts, post)
|
||||||
}
|
}
|
||||||
rows.Close()
|
rows.Close()
|
||||||
|
|
||||||
return c.JSON(fiber.Map {
|
return c.JSON(fiber.Map{
|
||||||
"error": "",
|
"error": "",
|
||||||
"result": posts,
|
"result": posts,
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
func GetFeed() (*feeds.Feed, error){
|
func GetFeed(db *database.Type) (*feeds.Feed, error) {
|
||||||
var posts []Post = []Post{}
|
var (
|
||||||
rows, err := DB.Query("SELECT * FROM posts")
|
posts []global.Post
|
||||||
if err != nil {
|
post global.Post
|
||||||
return nil, err
|
err error
|
||||||
}
|
)
|
||||||
|
|
||||||
for rows.Next() {
|
rows, err := db.Sql.Query("SELECT * FROM posts")
|
||||||
var post Post
|
if err != nil {
|
||||||
err := PostFromRow(&post, rows)
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
if err != nil {
|
for rows.Next() {
|
||||||
return nil, err
|
err := PostFromRow(&post, rows)
|
||||||
}
|
|
||||||
|
|
||||||
if post.Public == 0 {
|
if err != nil {
|
||||||
continue
|
return nil, err
|
||||||
}
|
}
|
||||||
|
|
||||||
posts = append(posts, post)
|
if post.Public == 0 {
|
||||||
}
|
continue
|
||||||
rows.Close()
|
}
|
||||||
|
|
||||||
|
posts = append(posts, post)
|
||||||
|
}
|
||||||
|
rows.Close()
|
||||||
|
|
||||||
blogurl, err := url.JoinPath(os.Getenv("FRONTEND_URL"), "/blog")
|
blogurl, err := url.JoinPath(os.Getenv("FRONTEND_URL"), "/blog")
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, fmt.Errorf("failed to create the blog URL: %s", err.Error())
|
return nil, fmt.Errorf("failed to create the blog URL: %s", err.Error())
|
||||||
}
|
}
|
||||||
|
|
||||||
feed := &feeds.Feed{
|
feed := &feeds.Feed{
|
||||||
Title: "[ngn.tf] | blog",
|
Title: "[ngn.tf] | blog",
|
||||||
Link: &feeds.Link{Href: blogurl},
|
Link: &feeds.Link{Href: blogurl},
|
||||||
Description: "ngn's personal blog",
|
Description: "ngn's personal blog",
|
||||||
Author: &feeds.Author{Name: "ngn", Email: "ngn@ngn.tf"},
|
Author: &feeds.Author{Name: "ngn", Email: "ngn@ngn.tf"},
|
||||||
Created: time.Now(),
|
Created: time.Now(),
|
||||||
}
|
}
|
||||||
|
|
||||||
feed.Items = []*feeds.Item{}
|
feed.Items = []*feeds.Item{}
|
||||||
for _, p := range posts {
|
for _, p := range posts {
|
||||||
purl, err := url.JoinPath(blogurl, p.ID)
|
purl, err := url.JoinPath(blogurl, p.ID)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, fmt.Errorf("failed to create URL for '%s': %s\n", p.ID, err.Error())
|
return nil, fmt.Errorf("failed to create URL for '%s': %s\n", p.ID, err.Error())
|
||||||
}
|
}
|
||||||
|
|
||||||
parsed, err := time.Parse("02/01/06", p.Date)
|
parsed, err := time.Parse("02/01/06", p.Date)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, fmt.Errorf("failed to parse time for '%s': %s\n", p.ID, err.Error())
|
return nil, fmt.Errorf("failed to parse time for '%s': %s\n", p.ID, err.Error())
|
||||||
}
|
}
|
||||||
|
|
||||||
feed.Items = append(feed.Items, &feeds.Item{
|
feed.Items = append(feed.Items, &feeds.Item{
|
||||||
Id: p.ID,
|
Id: p.ID,
|
||||||
Title: p.Title,
|
Title: p.Title,
|
||||||
Link: &feeds.Link{Href: purl},
|
Link: &feeds.Link{Href: purl},
|
||||||
Author: &feeds.Author{Name: p.Author},
|
Author: &feeds.Author{Name: p.Author},
|
||||||
Created: parsed,
|
Created: parsed,
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
return feed, nil
|
return feed, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func GetAtomFeed(c *fiber.Ctx) error {
|
func GetAtomFeed(c *fiber.Ctx) error {
|
||||||
feed, err := GetFeed()
|
feed, err := GetFeed(c.Locals("database").(*database.Type))
|
||||||
if util.ErrorCheck(err, c){
|
if util.ErrorCheck(err, c) {
|
||||||
return util.ErrServer(c)
|
return util.ErrServer(c)
|
||||||
}
|
}
|
||||||
|
|
||||||
atom, err := feed.ToAtom()
|
atom, err := feed.ToAtom()
|
||||||
if util.ErrorCheck(err, c){
|
if util.ErrorCheck(err, c) {
|
||||||
return util.ErrServer(c)
|
return util.ErrServer(c)
|
||||||
}
|
}
|
||||||
|
|
||||||
c.Set("Content-Type", "application/atom+xml")
|
c.Set("Content-Type", "application/atom+xml")
|
||||||
return c.Send([]byte(atom))
|
return c.Send([]byte(atom))
|
||||||
}
|
}
|
||||||
|
|
||||||
func GetRSSFeed(c *fiber.Ctx) error {
|
func GetRSSFeed(c *fiber.Ctx) error {
|
||||||
feed, err := GetFeed()
|
feed, err := GetFeed(c.Locals("database").(*database.Type))
|
||||||
if util.ErrorCheck(err, c){
|
if util.ErrorCheck(err, c) {
|
||||||
return util.ErrServer(c)
|
return util.ErrServer(c)
|
||||||
}
|
}
|
||||||
|
|
||||||
rss, err := feed.ToRss()
|
rss, err := feed.ToRss()
|
||||||
if util.ErrorCheck(err, c){
|
if util.ErrorCheck(err, c) {
|
||||||
return util.ErrServer(c)
|
return util.ErrServer(c)
|
||||||
}
|
}
|
||||||
|
|
||||||
c.Set("Content-Type", "application/rss+xml")
|
c.Set("Content-Type", "application/rss+xml")
|
||||||
return c.Send([]byte(rss))
|
return c.Send([]byte(rss))
|
||||||
}
|
}
|
||||||
|
|
||||||
func GetJSONFeed(c *fiber.Ctx) error {
|
func GetJSONFeed(c *fiber.Ctx) error {
|
||||||
feed, err := GetFeed()
|
feed, err := GetFeed(c.Locals("database").(*database.Type))
|
||||||
if util.ErrorCheck(err, c){
|
if util.ErrorCheck(err, c) {
|
||||||
return util.ErrServer(c)
|
return util.ErrServer(c)
|
||||||
}
|
}
|
||||||
|
|
||||||
json, err := feed.ToJSON()
|
json, err := feed.ToJSON()
|
||||||
if util.ErrorCheck(err, c){
|
if util.ErrorCheck(err, c) {
|
||||||
return util.ErrServer(c)
|
return util.ErrServer(c)
|
||||||
}
|
}
|
||||||
c.Set("Content-Type", "application/feed+json")
|
c.Set("Content-Type", "application/feed+json")
|
||||||
return c.Send([]byte(json))
|
return c.Send([]byte(json))
|
||||||
}
|
}
|
||||||
|
@ -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"`
|
|
||||||
}
|
|
@ -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",
|
|
||||||
})
|
|
||||||
})
|
|
||||||
}
|
|
@ -1,48 +1,41 @@
|
|||||||
package routes
|
package routes
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"database/sql"
|
|
||||||
"log"
|
"log"
|
||||||
|
|
||||||
"github.com/gofiber/fiber/v2"
|
"github.com/gofiber/fiber/v2"
|
||||||
|
"github.com/ngn13/website/api/database"
|
||||||
|
"github.com/ngn13/website/api/global"
|
||||||
"github.com/ngn13/website/api/util"
|
"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 {
|
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")
|
db = c.Locals("database").(*database.Type)
|
||||||
if util.ErrorCheck(err, c) {
|
|
||||||
return util.ErrServer(c)
|
|
||||||
}
|
|
||||||
|
|
||||||
for rows.Next() {
|
rows, err := db.Sql.Query("SELECT * FROM services")
|
||||||
var service Service
|
if util.ErrorCheck(err, c) {
|
||||||
err := rows.Scan(&service.Name, &service.Desc, &service.Url)
|
return util.ErrServer(c)
|
||||||
if err != nil {
|
}
|
||||||
log.Println("Error scaning services row: "+err.Error())
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
services = append(services, service)
|
|
||||||
}
|
|
||||||
rows.Close()
|
|
||||||
|
|
||||||
return c.JSON(fiber.Map {
|
for rows.Next() {
|
||||||
"error": "",
|
if err = rows.Scan(&service.Name, &service.Desc, &service.Url); err != nil {
|
||||||
"result": services,
|
log.Println("Error scaning services row: " + err.Error())
|
||||||
})
|
continue
|
||||||
|
}
|
||||||
|
services = append(services, service)
|
||||||
|
}
|
||||||
|
|
||||||
|
rows.Close()
|
||||||
|
|
||||||
|
return c.JSON(fiber.Map{
|
||||||
|
"error": "",
|
||||||
|
"result": services,
|
||||||
|
})
|
||||||
}
|
}
|
||||||
|
@ -4,57 +4,66 @@ import (
|
|||||||
"log"
|
"log"
|
||||||
"math/rand"
|
"math/rand"
|
||||||
"net/http"
|
"net/http"
|
||||||
|
"strings"
|
||||||
|
|
||||||
"github.com/gofiber/fiber/v2"
|
"github.com/gofiber/fiber/v2"
|
||||||
)
|
)
|
||||||
|
|
||||||
var charlist []rune = []rune("abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789")
|
func TitleToID(name string) string {
|
||||||
|
return strings.ToLower(strings.ReplaceAll(name, " ", ""))
|
||||||
|
}
|
||||||
|
|
||||||
func CreateToken() string {
|
func CreateToken() string {
|
||||||
b := make([]rune, 20)
|
s := make([]byte, 32)
|
||||||
for i := range b {
|
for i := 0; i < 32; i++ {
|
||||||
b[i] = charlist[rand.Intn(len(charlist))]
|
s[i] = byte(65 + rand.Intn(25))
|
||||||
}
|
}
|
||||||
|
return string(s)
|
||||||
return string(b)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func ErrorCheck(err error, c *fiber.Ctx) bool{
|
func ErrorCheck(err error, c *fiber.Ctx) bool {
|
||||||
if err != nil {
|
if err != nil {
|
||||||
log.Printf("Server error: '%s' on %s\n", err, c.Path())
|
log.Printf("Server error: '%s' on %s\n", err, c.Path())
|
||||||
return true
|
return true
|
||||||
}
|
}
|
||||||
|
|
||||||
return false
|
return false
|
||||||
}
|
}
|
||||||
|
|
||||||
func ErrorJSON(error string) fiber.Map{
|
func ErrorJSON(error string) fiber.Map {
|
||||||
return fiber.Map {
|
return fiber.Map{
|
||||||
"error": error,
|
"error": error,
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func GetIP(c *fiber.Ctx) string {
|
||||||
|
if c.Get("X-Real-IP") != "" {
|
||||||
|
return strings.Clone(c.Get("X-Real-IP"))
|
||||||
|
}
|
||||||
|
|
||||||
|
return c.IP()
|
||||||
}
|
}
|
||||||
|
|
||||||
func ErrServer(c *fiber.Ctx) error {
|
func ErrServer(c *fiber.Ctx) error {
|
||||||
return c.Status(http.StatusInternalServerError).JSON(ErrorJSON("Server error"))
|
return c.Status(http.StatusInternalServerError).JSON(ErrorJSON("Server error"))
|
||||||
}
|
}
|
||||||
|
|
||||||
func ErrExists(c *fiber.Ctx) error {
|
func ErrExists(c *fiber.Ctx) error {
|
||||||
return c.Status(http.StatusConflict).JSON(ErrorJSON("Entry already exists"))
|
return c.Status(http.StatusConflict).JSON(ErrorJSON("Entry already exists"))
|
||||||
}
|
}
|
||||||
|
|
||||||
func ErrBadData(c *fiber.Ctx) error {
|
func ErrBadData(c *fiber.Ctx) error {
|
||||||
return c.Status(http.StatusBadRequest).JSON(ErrorJSON("Provided data is invalid"))
|
return c.Status(http.StatusBadRequest).JSON(ErrorJSON("Provided data is invalid"))
|
||||||
}
|
}
|
||||||
|
|
||||||
func ErrBadJSON(c *fiber.Ctx) error {
|
func ErrBadJSON(c *fiber.Ctx) error {
|
||||||
return c.Status(http.StatusBadRequest).JSON(ErrorJSON("Bad JSON data"))
|
return c.Status(http.StatusBadRequest).JSON(ErrorJSON("Bad JSON data"))
|
||||||
}
|
}
|
||||||
|
|
||||||
func ErrAuth(c *fiber.Ctx) error {
|
func ErrAuth(c *fiber.Ctx) error {
|
||||||
return c.Status(http.StatusUnauthorized).JSON(ErrorJSON("Authentication failed"))
|
return c.Status(http.StatusUnauthorized).JSON(ErrorJSON("Authentication failed"))
|
||||||
}
|
}
|
||||||
|
|
||||||
func NoError(c *fiber.Ctx) error {
|
func NoError(c *fiber.Ctx) error {
|
||||||
return c.Status(http.StatusOK).JSON(ErrorJSON(""))
|
return c.Status(http.StatusOK).JSON(ErrorJSON(""))
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -1,9 +1,12 @@
|
|||||||
<script></script>
|
<script>
|
||||||
|
export let subtitle = ""
|
||||||
|
</script>
|
||||||
|
|
||||||
<header>
|
<header>
|
||||||
<h1>
|
<h1>
|
||||||
<slot></slot>
|
<slot></slot>
|
||||||
</h1>
|
</h1>
|
||||||
|
<h4><c>{subtitle}</c></h4>
|
||||||
</header>
|
</header>
|
||||||
|
|
||||||
<style>
|
<style>
|
||||||
@ -20,9 +23,19 @@ h1 {
|
|||||||
font-weight: 900;
|
font-weight: 900;
|
||||||
font-size: 5.5vw;
|
font-size: 5.5vw;
|
||||||
padding: 120px;
|
padding: 120px;
|
||||||
|
padding-bottom: 0;
|
||||||
text-align: center;
|
text-align: center;
|
||||||
color: white;
|
color: white;
|
||||||
text-shadow: var(--text-shadow);
|
text-shadow: var(--text-shadow);
|
||||||
text-size-adjust: 80%;
|
text-size-adjust: 80%;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
h4 {
|
||||||
|
padding-bottom: 120px;
|
||||||
|
font-weight: 600;
|
||||||
|
font-size: 2.2vw;
|
||||||
|
text-align: center;
|
||||||
|
color: white;
|
||||||
|
text-size-adjust: 80%;
|
||||||
|
}
|
||||||
</style>
|
</style>
|
||||||
|
@ -80,7 +80,7 @@
|
|||||||
</main>
|
</main>
|
||||||
|
|
||||||
<div class="version">
|
<div class="version">
|
||||||
<p>v4.8</p>
|
<p>v4.9</p>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<style>
|
<style>
|
||||||
|
@ -99,7 +99,7 @@
|
|||||||
<link href="/markdown.css" rel="stylesheet">
|
<link href="/markdown.css" rel="stylesheet">
|
||||||
</svelte:head>
|
</svelte:head>
|
||||||
|
|
||||||
<Header>
|
<Header subtitle="{data.author} | {data.date}">
|
||||||
{data.title}
|
{data.title}
|
||||||
</Header>
|
</Header>
|
||||||
|
|
||||||
@ -107,41 +107,34 @@
|
|||||||
<audio bind:this={audio} preload="auto">
|
<audio bind:this={audio} preload="auto">
|
||||||
<source src="/click.wav" type="audio/mpeg" />
|
<source src="/click.wav" type="audio/mpeg" />
|
||||||
</audio>
|
</audio>
|
||||||
<div class="header">
|
|
||||||
<p><b>Author:</b> {data.author} <b>| Date:</b> {data.date}</p>
|
|
||||||
<div>
|
|
||||||
<button on:click={async ()=>{upvote()}} class="{upvote_status}">
|
|
||||||
<i class="nf nf-md-arrow_up_bold"></i>
|
|
||||||
</button>
|
|
||||||
<p>{data.vote}</p>
|
|
||||||
<button on:click={async ()=>{downvote()}} class="{downvote_status}">
|
|
||||||
<i class="nf nf-md-arrow_down_bold"></i>
|
|
||||||
</button>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
<div class="content markdown-body">
|
<div class="content markdown-body">
|
||||||
{@html sanitized}
|
{@html sanitized}
|
||||||
</div>
|
</div>
|
||||||
|
<div class="votes">
|
||||||
|
<button on:click={async ()=>{upvote()}} class="{upvote_status}">
|
||||||
|
<i class="nf nf-md-arrow_up_bold"></i>
|
||||||
|
</button>
|
||||||
|
<p>{data.vote}</p>
|
||||||
|
<button on:click={async ()=>{downvote()}} class="{downvote_status}">
|
||||||
|
<i class="nf nf-md-arrow_down_bold"></i>
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
</main>
|
</main>
|
||||||
|
|
||||||
|
|
||||||
<style>
|
<style>
|
||||||
p {
|
|
||||||
font-size: 30px;
|
|
||||||
}
|
|
||||||
|
|
||||||
main {
|
main {
|
||||||
padding: 50px 10% 50px 10%;
|
padding: 50px 10% 50px 10%;
|
||||||
color: white;
|
color: white;
|
||||||
display: flex;
|
display: flex;
|
||||||
flex-direction: column;
|
flex-direction: row;
|
||||||
justify-content: center;
|
align-items: start;
|
||||||
}
|
}
|
||||||
|
|
||||||
.content {
|
.content {
|
||||||
padding: 50px;
|
padding: 35px;
|
||||||
background: var(--dark-four);
|
background: var(--dark-four);
|
||||||
border-radius: 0 0 var(--radius) var(--radius);
|
border-radius: var(--radius);
|
||||||
box-shadow: var(--box-shadow);
|
box-shadow: var(--box-shadow);
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -152,38 +145,28 @@ main {
|
|||||||
}
|
}
|
||||||
|
|
||||||
.content {
|
.content {
|
||||||
padding: 30px;
|
|
||||||
background: var(--dark-four);
|
background: var(--dark-four);
|
||||||
border-radius: 0 0 var(--radius) var(--radius);
|
border-radius: 0 0 var(--radius) var(--radius);
|
||||||
box-shadow: var(--box-shadow);
|
box-shadow: var(--box-shadow);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
.header {
|
.votes {
|
||||||
font-family: Consolas,Monaco,Lucida Console,Liberation Mono,DejaVu Sans Mono,Bitstream Vera Sans Mono,Courier New, monospace;
|
|
||||||
background: var(--dark-two);
|
|
||||||
border-radius: var(--radius) var(--radius) 0 0;
|
|
||||||
padding: 30px;
|
|
||||||
box-shadow: var(--box-shadow);
|
|
||||||
display: flex;
|
display: flex;
|
||||||
align-items: center;
|
flex-direction: column;
|
||||||
justify-content: space-between;
|
|
||||||
}
|
|
||||||
|
|
||||||
.header div{
|
|
||||||
display: flex;
|
|
||||||
flex-direction: row;
|
|
||||||
text-align: center;
|
text-align: center;
|
||||||
text-shadow: var(--text-shadow);
|
text-shadow: var(--text-shadow);
|
||||||
gap: 10px;
|
gap: 10px;
|
||||||
margin-top: 10px;
|
padding: 10px;
|
||||||
|
margin-left: 10px;
|
||||||
}
|
}
|
||||||
|
|
||||||
.header p {
|
.votes p {
|
||||||
font-size: 20px;
|
font-size: 25px;
|
||||||
|
color: var(--dark-six);
|
||||||
}
|
}
|
||||||
|
|
||||||
.header div button{
|
.votes button{
|
||||||
display: flex;
|
display: flex;
|
||||||
flex-direction: row;
|
flex-direction: row;
|
||||||
gap: 10px;
|
gap: 10px;
|
||||||
@ -192,14 +175,10 @@ main {
|
|||||||
border: none;
|
border: none;
|
||||||
font-size: 30px;
|
font-size: 30px;
|
||||||
cursor: pointer;
|
cursor: pointer;
|
||||||
color: white;
|
color: var(--dark-six);
|
||||||
}
|
}
|
||||||
|
|
||||||
.header div p {
|
.votes button:hover {
|
||||||
font-size: 25px;
|
|
||||||
}
|
|
||||||
|
|
||||||
.header div button:hover {
|
|
||||||
animation-name: colorAnimation;
|
animation-name: colorAnimation;
|
||||||
animation-iteration-count: infinite;
|
animation-iteration-count: infinite;
|
||||||
animation-duration: 10s;
|
animation-duration: 10s;
|
||||||
|
@ -63,8 +63,8 @@ tr,th,td{
|
|||||||
}
|
}
|
||||||
|
|
||||||
td,th{
|
td,th{
|
||||||
border: solid 1px white;
|
border: solid 1px var(--dark-fife);
|
||||||
padding: 10px;
|
padding: 16px;
|
||||||
}
|
}
|
||||||
|
|
||||||
th {
|
th {
|
||||||
@ -75,10 +75,6 @@ th {
|
|||||||
}
|
}
|
||||||
|
|
||||||
code {
|
code {
|
||||||
background: var(--dark-two);
|
|
||||||
color: white;
|
|
||||||
border-radius: var(--radius);
|
|
||||||
text-shadow: var(--text-shadow);
|
|
||||||
word-wrap: break-word;
|
word-wrap: break-word;
|
||||||
white-space: pre-wrap;
|
white-space: pre-wrap;
|
||||||
word-break: break-word;
|
word-break: break-word;
|
||||||
|
@ -7,6 +7,7 @@
|
|||||||
--dark-three: #121212;
|
--dark-three: #121212;
|
||||||
--dark-four: #101010;
|
--dark-four: #101010;
|
||||||
--dark-fife: #3a3b3c;
|
--dark-fife: #3a3b3c;
|
||||||
|
--dark-six: #C0C0C0;
|
||||||
--radius: 8px;
|
--radius: 8px;
|
||||||
/*
|
/*
|
||||||
old shadow animation
|
old shadow animation
|
||||||
@ -37,8 +38,8 @@ body {
|
|||||||
}
|
}
|
||||||
|
|
||||||
::-webkit-scrollbar {
|
::-webkit-scrollbar {
|
||||||
border-radius: 10px;
|
border-radius: 5px;
|
||||||
width: 10px;
|
width: 1px;
|
||||||
}
|
}
|
||||||
|
|
||||||
::-webkit-scrollbar-track {
|
::-webkit-scrollbar-track {
|
||||||
|
@ -691,7 +691,7 @@
|
|||||||
}
|
}
|
||||||
|
|
||||||
.markdown-body span.float-right>span {
|
.markdown-body span.float-right>span {
|
||||||
displaydebian: block;
|
display: block;
|
||||||
margin: 13px auto 0;
|
margin: 13px auto 0;
|
||||||
overflow: hidden;
|
overflow: hidden;
|
||||||
text-align: right;
|
text-align: right;
|
||||||
@ -703,7 +703,7 @@
|
|||||||
margin: 0;
|
margin: 0;
|
||||||
font-size: 85%;
|
font-size: 85%;
|
||||||
white-space: break-spaces;
|
white-space: break-spaces;
|
||||||
background-color: var(--dark-two);
|
background: var(--dark-two);
|
||||||
border-radius: 6px;
|
border-radius: 6px;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -745,7 +745,7 @@
|
|||||||
.markdown-body .highlight pre,
|
.markdown-body .highlight pre,
|
||||||
.markdown-body pre {
|
.markdown-body pre {
|
||||||
padding: 16px;
|
padding: 16px;
|
||||||
overflow: scroll;
|
overflow: auto;
|
||||||
font-size: 85%;
|
font-size: 85%;
|
||||||
line-height: 1.45;
|
line-height: 1.45;
|
||||||
background-color: var(--dark-two);
|
background-color: var(--dark-two);
|
||||||
|
Loading…
x
Reference in New Issue
Block a user