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 .
|
||||
|
||||
test:
|
||||
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
|
||||
|
||||
import (
|
||||
"database/sql"
|
||||
"log"
|
||||
"net/http"
|
||||
|
||||
"github.com/gofiber/fiber/v2"
|
||||
"github.com/ngn13/website/api/database"
|
||||
"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() {
|
||||
app := fiber.New(fiber.Config{
|
||||
DisableStartupMessage: true,
|
||||
})
|
||||
app.Use(CorsMiddleware)
|
||||
var (
|
||||
app *fiber.App
|
||||
db database.Type
|
||||
err error
|
||||
)
|
||||
|
||||
db, err := sql.Open("sqlite3", "data.db")
|
||||
if err != nil {
|
||||
log.Fatal("Cannot connect to the database: "+err.Error())
|
||||
}
|
||||
if err = db.Open("data.db"); err != nil {
|
||||
log.Fatalf("Cannot access the database: %s", err.Error())
|
||||
return
|
||||
}
|
||||
defer db.Close()
|
||||
|
||||
log.Println("Creating tables")
|
||||
routes.BlogDb(db)
|
||||
routes.ServicesDb(db)
|
||||
routes.Setup(app, db)
|
||||
app = fiber.New(fiber.Config{
|
||||
DisableStartupMessage: true,
|
||||
})
|
||||
|
||||
log.Println("Starting web server at port 7001")
|
||||
err = app.Listen("0.0.0.0:7001")
|
||||
if err != nil {
|
||||
log.Printf("Error starting the webserver: %s", err.Error())
|
||||
}
|
||||
app.Use("*", func(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")
|
||||
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
|
||||
|
||||
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)
|
||||
}
|
||||
|
@ -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))
|
||||
}
|
||||
|
@ -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
|
||||
|
||||
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,
|
||||
})
|
||||
}
|
||||
|
@ -4,57 +4,66 @@ import (
|
||||
"log"
|
||||
"math/rand"
|
||||
"net/http"
|
||||
"strings"
|
||||
|
||||
"github.com/gofiber/fiber/v2"
|
||||
)
|
||||
|
||||
var charlist []rune = []rune("abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789")
|
||||
func TitleToID(name string) string {
|
||||
return strings.ToLower(strings.ReplaceAll(name, " ", ""))
|
||||
}
|
||||
|
||||
func CreateToken() string {
|
||||
b := make([]rune, 20)
|
||||
for i := range b {
|
||||
b[i] = charlist[rand.Intn(len(charlist))]
|
||||
}
|
||||
|
||||
return string(b)
|
||||
s := make([]byte, 32)
|
||||
for i := 0; i < 32; i++ {
|
||||
s[i] = byte(65 + rand.Intn(25))
|
||||
}
|
||||
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
|
||||
}
|
||||
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
|
||||
return false
|
||||
}
|
||||
|
||||
func ErrorJSON(error string) fiber.Map{
|
||||
return fiber.Map {
|
||||
"error": error,
|
||||
}
|
||||
func ErrorJSON(error string) fiber.Map {
|
||||
return fiber.Map{
|
||||
"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 {
|
||||
return c.Status(http.StatusInternalServerError).JSON(ErrorJSON("Server error"))
|
||||
return c.Status(http.StatusInternalServerError).JSON(ErrorJSON("Server 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 {
|
||||
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 {
|
||||
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 {
|
||||
return c.Status(http.StatusUnauthorized).JSON(ErrorJSON("Authentication failed"))
|
||||
return c.Status(http.StatusUnauthorized).JSON(ErrorJSON("Authentication failed"))
|
||||
}
|
||||
|
||||
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>
|
||||
<h1>
|
||||
<slot></slot>
|
||||
</h1>
|
||||
<h4><c>{subtitle}</c></h4>
|
||||
</header>
|
||||
|
||||
<style>
|
||||
@ -20,9 +23,19 @@ h1 {
|
||||
font-weight: 900;
|
||||
font-size: 5.5vw;
|
||||
padding: 120px;
|
||||
padding-bottom: 0;
|
||||
text-align: center;
|
||||
color: white;
|
||||
text-shadow: var(--text-shadow);
|
||||
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>
|
||||
|
@ -80,7 +80,7 @@
|
||||
</main>
|
||||
|
||||
<div class="version">
|
||||
<p>v4.8</p>
|
||||
<p>v4.9</p>
|
||||
</div>
|
||||
|
||||
<style>
|
||||
|
@ -99,7 +99,7 @@
|
||||
<link href="/markdown.css" rel="stylesheet">
|
||||
</svelte:head>
|
||||
|
||||
<Header>
|
||||
<Header subtitle="{data.author} | {data.date}">
|
||||
{data.title}
|
||||
</Header>
|
||||
|
||||
@ -107,41 +107,34 @@
|
||||
<audio bind:this={audio} preload="auto">
|
||||
<source src="/click.wav" type="audio/mpeg" />
|
||||
</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">
|
||||
{@html sanitized}
|
||||
</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>
|
||||
|
||||
|
||||
<style>
|
||||
p {
|
||||
font-size: 30px;
|
||||
}
|
||||
|
||||
main {
|
||||
padding: 50px 10% 50px 10%;
|
||||
color: white;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
justify-content: center;
|
||||
flex-direction: row;
|
||||
align-items: start;
|
||||
}
|
||||
|
||||
.content {
|
||||
padding: 50px;
|
||||
padding: 35px;
|
||||
background: var(--dark-four);
|
||||
border-radius: 0 0 var(--radius) var(--radius);
|
||||
border-radius: var(--radius);
|
||||
box-shadow: var(--box-shadow);
|
||||
}
|
||||
|
||||
@ -152,38 +145,28 @@ main {
|
||||
}
|
||||
|
||||
.content {
|
||||
padding: 30px;
|
||||
background: var(--dark-four);
|
||||
border-radius: 0 0 var(--radius) var(--radius);
|
||||
box-shadow: var(--box-shadow);
|
||||
}
|
||||
}
|
||||
|
||||
.header {
|
||||
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);
|
||||
.votes {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: space-between;
|
||||
}
|
||||
|
||||
.header div{
|
||||
display: flex;
|
||||
flex-direction: row;
|
||||
flex-direction: column;
|
||||
text-align: center;
|
||||
text-shadow: var(--text-shadow);
|
||||
gap: 10px;
|
||||
margin-top: 10px;
|
||||
padding: 10px;
|
||||
margin-left: 10px;
|
||||
}
|
||||
|
||||
.header p {
|
||||
font-size: 20px;
|
||||
.votes p {
|
||||
font-size: 25px;
|
||||
color: var(--dark-six);
|
||||
}
|
||||
|
||||
.header div button{
|
||||
.votes button{
|
||||
display: flex;
|
||||
flex-direction: row;
|
||||
gap: 10px;
|
||||
@ -192,14 +175,10 @@ main {
|
||||
border: none;
|
||||
font-size: 30px;
|
||||
cursor: pointer;
|
||||
color: white;
|
||||
color: var(--dark-six);
|
||||
}
|
||||
|
||||
.header div p {
|
||||
font-size: 25px;
|
||||
}
|
||||
|
||||
.header div button:hover {
|
||||
.votes button:hover {
|
||||
animation-name: colorAnimation;
|
||||
animation-iteration-count: infinite;
|
||||
animation-duration: 10s;
|
||||
|
@ -63,8 +63,8 @@ tr,th,td{
|
||||
}
|
||||
|
||||
td,th{
|
||||
border: solid 1px white;
|
||||
padding: 10px;
|
||||
border: solid 1px var(--dark-fife);
|
||||
padding: 16px;
|
||||
}
|
||||
|
||||
th {
|
||||
@ -75,10 +75,6 @@ th {
|
||||
}
|
||||
|
||||
code {
|
||||
background: var(--dark-two);
|
||||
color: white;
|
||||
border-radius: var(--radius);
|
||||
text-shadow: var(--text-shadow);
|
||||
word-wrap: break-word;
|
||||
white-space: pre-wrap;
|
||||
word-break: break-word;
|
||||
|
@ -7,6 +7,7 @@
|
||||
--dark-three: #121212;
|
||||
--dark-four: #101010;
|
||||
--dark-fife: #3a3b3c;
|
||||
--dark-six: #C0C0C0;
|
||||
--radius: 8px;
|
||||
/*
|
||||
old shadow animation
|
||||
@ -37,8 +38,8 @@ body {
|
||||
}
|
||||
|
||||
::-webkit-scrollbar {
|
||||
border-radius: 10px;
|
||||
width: 10px;
|
||||
border-radius: 5px;
|
||||
width: 1px;
|
||||
}
|
||||
|
||||
::-webkit-scrollbar-track {
|
||||
|
@ -691,7 +691,7 @@
|
||||
}
|
||||
|
||||
.markdown-body span.float-right>span {
|
||||
displaydebian: block;
|
||||
display: block;
|
||||
margin: 13px auto 0;
|
||||
overflow: hidden;
|
||||
text-align: right;
|
||||
@ -703,7 +703,7 @@
|
||||
margin: 0;
|
||||
font-size: 85%;
|
||||
white-space: break-spaces;
|
||||
background-color: var(--dark-two);
|
||||
background: var(--dark-two);
|
||||
border-radius: 6px;
|
||||
}
|
||||
|
||||
@ -745,7 +745,7 @@
|
||||
.markdown-body .highlight pre,
|
||||
.markdown-body pre {
|
||||
padding: 16px;
|
||||
overflow: scroll;
|
||||
overflow: auto;
|
||||
font-size: 85%;
|
||||
line-height: 1.45;
|
||||
background-color: var(--dark-two);
|
||||
|
Loading…
x
Reference in New Issue
Block a user