Minor restructuring

This commit is contained in:
n* 2025-03-08 00:07:37 -03:00
parent 5316db3dcb
commit 22cb36a23d
27 changed files with 320 additions and 272 deletions

2
.gitignore vendored
View file

@ -1,3 +1,5 @@
deps/*
!deps/weblit-server.lua
!deps/weblit-cookie.lua
!deps/radon.lua
!deps/foppy.lua

View file

@ -1,4 +1,4 @@
![Napkin Logo](./logo.svg)
![Napkin Logo](./cool-stuff/logo.svg)
Napkin is a simple blogging platform built on Luvit for the Fediverse
it's absolutely not finished yet, so stay tuned.

View file

@ -0,0 +1 @@
return "rainbows!"

View file

@ -1,150 +0,0 @@
local authors = {}
local foppy = require "./foppy"
local fs = require "fs"
local json = require "json"
local utils = require "./utils"
authors.from_id = function(id, writeable)
return foppy.open("database/authors/" .. id .. ".json", writeable)
end
authors.id_from_token = function(token, writeable)
local data = foppy.open("database/tokens/" .. token .. ".json", writeable)
if not data then
print("token doesnt exist", token)
return nil, 404
end
if data.expires <= os.time() then
data.dead = true
return nil, 401
end
if data.dead then
return nil, 401
end
local exists = fs.existsSync("database/authors/" .. data.of .. ".json")
if not exists then
data.dead = true
return nil, 404
end
return data.of
end
authors.id_from_blog = function(blog, writeable)
local data = foppy.open("database/blogs/" .. blog .. ".json")
if not data then
return nil, 404
end
return data.owner
end
local is_right_password = function(id, password)
local author, code = authors.from_id(id, true)
if not author then
return nil, 404
end
-- check salted password with openssl
local hashed = utils.hash(author.salt .. password)
if author.password ~= hashed then
return nil, 401
end
return author
end
authors.auth = function(id, password)
local author, code = is_right_password(id, password)
if not author then
return nil, code
end
local hash, filename
repeat
hash = utils.uuid()
filename = "database/tokens/" .. hash .. ".json"
until not fs.existsSync(hash)
local token_data = json.encode {
of = id,
expires = os.time() + 2628000, -- 4 months, approximately.
dead = false
}
fs.writeFileSync(filename, token_data)
print(#author.tokens, hash)
author.tokens[#author.tokens + 1] = hash
foppy.sync("database/authors/" .. id .. ".json")
return hash
end
authors.revoke_all_tokens = function(id, password)
local author, code = is_right_password(id, password)
if not author then
return nil, code
end
for _, v in ipairs(author.tokens) do
local f = "database/tokens/" .. v .. ".json"
local token = foppy.open(f, true)
token.dead = true
foppy.sync(f, true)
end
author.tokens = {}
foppy.sync("database/authors/" .. id .. ".json")
end
authors.change_password = function(id, password, new_password)
local author, code = is_right_password(id, password)
if not author then
return nil, code
end
author.salt = utils.uuid()
author.password = utils.data(author.salt .. new_password)
foppy.sync("database/authors/" .. id .. ".json")
end
authors.get_available_id = function()
local id
repeat
id = utils.pin()
until not fs.existsSync("database/authors/" .. id .. ".json")
return id
end
authors.create_account = function(password)
local id = authors.get_available_id()
local salt = utils.uuid()
local account_data = json.encode {
blogs = {},
password = utils.hash(salt .. password),
salt = salt,
tokens = {}
}
fs.writeFileSync(
"database/authors/" .. id .. ".json",
account_data
)
return id
end
package.loaded["./authors"] = authors
return authors

View file

@ -1,46 +0,0 @@
local blogs = {}
local json = require "json"
local fs = require "fs"
local foppy = require "./foppy"
local authors = require "./authors"
blogs.from_handle = function(handle)
return foppy.open(
"database/blogs/" .. handle .. ".json"
)
end
blogs.new_blog = function(id, handle, title, description)
assert(handle)
if blogs.from_handle(handle) then
return nil, "blog_already_exists"
end
local author = authors.from_id(id, true)
assert(author)
local blog = {
owner = id,
title = title or handle,
handle = handle,
description = description,
banner = { source = nil, alt = nil },
index = {},
created_at = os.time(),
updated_at = os.time()
}
fs.writeFileSync(
"database/blogs/" .. handle .. ".json",
json.encode(blog)
)
author.blogs[#author.blogs + 1] = handle
return blog
end
package.loaded["./blogs"] = blogs
return blogs

View file

@ -1,8 +0,0 @@
local config = {
base_url = "test0.at.cyrneko.eu",
max_blogs_per_user = 3
}
package.loaded["./config"] = config
return config

View file

Before

Width:  |  Height:  |  Size: 2.7 KiB

After

Width:  |  Height:  |  Size: 2.7 KiB

Before After
Before After

View file

@ -1 +0,0 @@
-- retrieves a buncha useful data!

198
database.lua Normal file
View file

@ -0,0 +1,198 @@
local fs = require "fs"
local json = require "json"
local utils = require "./utils"
local foppy = require "./foppy"
local authors = {}
package.loaded["authors"] = authors
do
authors.from_id = function(id, writeable)
return foppy.open("database/authors/" .. id .. ".json", writeable)
end
authors.id_from_token = function(token, writeable)
local data = foppy.open("database/tokens/" .. token .. ".json", writeable)
if not data then
print("token doesnt exist", token)
return nil, 404
end
if data.expires <= os.time() then
data.dead = true
return nil, 401
end
if data.dead then
return nil, 401
end
local exists = fs.existsSync("database/authors/" .. data.of .. ".json")
if not exists then
data.dead = true
return nil, 404
end
return data.of
end
authors.id_from_blog = function(blog, writeable)
local data = foppy.open("database/blogs/" .. blog .. ".json")
if not data then
return nil, 404
end
return data.owner
end
local is_right_password = function(id, password)
local author, code = authors.from_id(id, true)
if not author then
return nil, 404
end
-- check salted password with openssl
local hashed = utils.hash(author.salt .. password)
if author.password ~= hashed then
return nil, 401
end
return author
end
authors.auth = function(id, password)
local author, code = is_right_password(id, password)
if not author then
return nil, code
end
local hash, filename
repeat
hash = utils.uuid()
filename = "database/tokens/" .. hash .. ".json"
until not fs.existsSync(hash)
local token_data = json.encode {
of = id,
expires = os.time() + 2628000, -- 4 months, approximately.
dead = false
}
fs.writeFileSync(filename, token_data)
print(#author.tokens, hash)
author.tokens[#author.tokens + 1] = hash
foppy.sync("database/authors/" .. id .. ".json")
return hash
end
authors.revoke_all_tokens = function(id, password)
local author, code = is_right_password(id, password)
if not author then
return nil, code
end
for _, v in ipairs(author.tokens) do
local f = "database/tokens/" .. v .. ".json"
local token = foppy.open(f, true)
token.dead = true
foppy.sync(f, true)
end
author.tokens = {}
foppy.sync("database/authors/" .. id .. ".json")
end
authors.change_password = function(id, password, new_password)
local author, code = is_right_password(id, password)
if not author then
return nil, code
end
author.salt = utils.uuid()
author.password = utils.data(author.salt .. new_password)
foppy.sync("database/authors/" .. id .. ".json")
end
authors.get_available_id = function()
local id
repeat
id = utils.pin()
until not fs.existsSync("database/authors/" .. id .. ".json")
return id
end
authors.create_account = function(password)
local id = authors.get_available_id()
local salt = utils.uuid()
local account_data = json.encode {
blogs = {},
password = utils.hash(salt .. password),
salt = salt,
tokens = {}
}
fs.writeFileSync(
"database/authors/" .. id .. ".json",
account_data
)
return id
end
end
local blogs = {}
package.loaded["blogs"] = blogs
do
blogs.from_handle = function(handle)
return foppy.open(
"database/blogs/" .. handle .. ".json"
)
end
blogs.new_blog = function(id, handle, title, description)
assert(handle)
if blogs.from_handle(handle) then
return nil, "blog_already_exists"
end
local author = authors.from_id(id, true)
assert(author)
local blog = {
owner = id,
title = title or handle,
handle = handle,
description = description,
banner = { source = nil, alt = nil },
index = {},
created_at = os.time(),
updated_at = os.time()
}
fs.writeFileSync(
"database/blogs/" .. handle .. ".json",
json.encode(blog)
)
author.blogs[#author.blogs + 1] = handle
return blog
end
end
return {
authors = authors,
blogs = blogs,
}

View file

@ -0,0 +1 @@
{"blogs":[],"salt":"2b2c8b55-e968-4834-939d-1367d4ac0692","tokens":["9937f5d8-4d87-4d1e-9881-1a9f4df186ac"],"password":"c78e906904ecb7b832742a50c448f91180a2a539b2eca7d35d822bf9ea914710"}

View file

@ -0,0 +1,2 @@
{"_pleroma_theme_version":2,"theme":{"fonts":{},"shadows":{"button":[{"x":0,"y":0,"blur":"0","spread":"1","color":"#707070","alpha":1},{"x":0,"y":"0","blur":0,"spread":"1","color":"#ffffff","alpha":"0.6","inset":true},{"x":0,"y":"16","blur":"0","spread":"0","color":"#ffffff","alpha":"0.4","inset":true},{"x":0,"y":"-20","blur":"20","spread":"-10","color":"#cfcfcf","alpha":"1","inset":true}],"buttonHover":[{"x":0,"y":0,"blur":"0","spread":"1","color":"#3c7fb1","alpha":1},{"x":0,"y":"0","blur":"3","spread":0,"color":"#38d5fd","alpha":"1","inset":true},{"x":0,"y":"16","blur":"0","spread":"0","color":"#ffffff","alpha":"0.4","inset":true},{"x":0,"y":"-20","blur":"20","spread":"-10","color":"#c1d9e6","alpha":"1","inset":true}],"panel":[{"x":"0","y":"0","blur":"0","spread":"4","color":"#b7d3eb","alpha":"1","inset":true},{"x":"1","y":"1","blur":"0","spread":"0","color":"#28d1e4","alpha":"1","inset":false},{"x":"0","y":"0","blur":"0","spread":"1","color":"#f8fffe","alpha":"1"},{"x":"0","y":"0","blur":"0","spread":"2","color":"#040003","alpha":"1"}],"panelHeader":[],"buttonPressed":[{"x":0,"y":0,"blur":"0","spread":"1","color":"#2c628b","alpha":1,"inset":false},{"x":0,"y":0,"blur":"1","spread":"0","color":"#000000","alpha":"0.3","inset":true},{"x":0,"y":"16","blur":"0","spread":"0","color":"#ffffff","alpha":"0.3","inset":true},{"x":0,"y":"-20","blur":"20","spread":"-10","color":"#68b2da","alpha":"1","inset":true},{"x":0,"y":"20","blur":"20","spread":"-10","color":"#c4e5f6","alpha":"1","inset":true}],"input":[{"x":0,"y":"1","blur":0,"spread":0,"color":"#adadb5","alpha":"1","inset":true},{"x":"1","y":"0","blur":0,"spread":0,"color":"#e6e6ef","alpha":"1","inset":true},{"x":"-1","y":"0","blur":0,"spread":0,"color":"#e6e6ef","alpha":"1","inset":true},{"x":"0","y":"1","blur":0,"spread":0,"color":"#e6efef","alpha":"1","inset":true}],"topBar":[{"x":0,"y":"1","blur":"0","spread":0,"color":"#545351","alpha":"1","inset":true},{"x":0,"y":"20","blur":"20","spread":"-10","color":"#ffffff","alpha":"0.2","inset":true},{"x":0,"y":"20","blur":"0","spread":"0","color":"#ffffff","alpha":"0.2","inset":true},{"x":0,"y":"2","blur":"0","spread":"0","color":"#ffffff","alpha":"0.2","inset":true}]},"opacity":{"input":"1"},"colors":{"bg":"#f7f7f7","text":"#000000","link":"#0a63cb","fg":"#dddddd","panel":"#b9d1ea","input":"#ffffff","inputText":"#000000","topBar":"#131416","topBarLink":"#ffffff","badgeNotification":"#2c333a","border":"#cccccc","cRed":"#cb0a0a","cBlue":"#0a63cb","cGreen":"#0dcb0a","cOrange":"#d0c000"},"radii":{"btn":"2","input":"1","checkbox":"0","panel":"2","avatar":"2","avatarAlt":"2","tooltip":"0","attachment":"0"}},"name":"Aero Basic (@Feuerfuchs)"}
--------------------------GsdEAWaeP9OSgyWdJnggDm--

View file

@ -0,0 +1 @@
{"dead":false,"expires":1744030167,"of":"3076"}

View file

@ -1,3 +1,28 @@
--[[
zlib License
Copyright (c) 2025 Nelson "N*" Lopez <darltrash@icloud.com>
This software is provided 'as-is', without any express or implied
warranty. In no event will the authors be held liable for any damages
arising from the use of this software.
Permission is granted to anyone to use this software for any purpose,
including commercial applications, and to alter it and redistribute it
freely, subject to the following restrictions:
1. The origin of this software must not be misrepresented; you must not
claim that you wrote the original software. If you use this software
in a product, an acknowledgment in the product documentation would be
appreciated but is not required.
2. Altered source versions must be plainly marked as such, and must not be
misrepresented as being the original software.
3. This notice may not be removed or altered from any source distribution.
]]
-- THIS IS A WIP LIBRARY, DO NOT USE YET UNLESS
-- YOU WANNA FIDDLE WITH THE MESS THAT IT IS.
local tag = function(name)
return function(content)
local data = ""

View file

@ -12,6 +12,15 @@
homepage = "https://github.com/creationix/weblit/blob/master/libs/weblit-app.lua"
]]
--[[
Hello, it's me! I'm Nelson, Napkin's core developer.
This version of weblit was modified to do the following things:
- Transform /paths/like/these/ into /paths/like/these
- Print full stacktrace at program failure
- If request body is bigger than 10k, it provides a reader (req.read)
]]
local uv = require('uv')
local createServer = require('coro-net').createServer
local httpCodec = require('http-codec')
@ -75,6 +84,7 @@ local function newServer(run)
headers = setmetatable({}, headerMeta),
version = head.version,
keepAlive = head.keepAlive,
read = read,
body = ""
}
@ -105,14 +115,12 @@ local function newServer(run)
expectedSize = tonumber(value) or expectedSize
end
end
req.expectedSize = expectedSize
if expectedSize then
local bodySize = 0
if expectedSize > maxSize then
req.read = read
req.expectedSize = expectedSize
else
if expectedSize <= maxSize then
local parts = {}
for chunk in read do
if #chunk == 0 then

View file

@ -19,6 +19,14 @@ local errors = {
invalid_token = "invalid authentication, please log back in..."
}
errors.handle = function(data, code, redirect)
data.response.setCookie("error-card", code)
if redirect then
data.response.headers["Location"] = redirect
data.response.code = 302
end
end
package.loaded["./errors"] = errors
return errors

View file

@ -1,3 +1,28 @@
--[[
zlib License
Copyright (c) 2025 Nelson "N*" Lopez <darltrash@icloud.com>
This software is provided 'as-is', without any express or implied
warranty. In no event will the authors be held liable for any damages
arising from the use of this software.
Permission is granted to anyone to use this software for any purpose,
including commercial applications, and to alter it and redistribute it
freely, subject to the following restrictions:
1. The origin of this software must not be misrepresented; you must not
claim that you wrote the original software. If you use this software
in a product, an acknowledgment in the product documentation would be
appreciated but is not required.
2. Altered source versions must be plainly marked as such, and must not be
misrepresented as being the original software.
3. This notice may not be removed or altered from any source distribution.
]]
-- THIS IS A WIP LIBRARY, DO NOT USE YET UNLESS
-- YOU WANNA FIDDLE WITH THE MESS THAT IT IS.
local json = require "json"
local fs = require "fs"

View file

@ -1,15 +1,14 @@
local weblit = require('weblit')
local cookie = require('weblit-cookie')
local json = require("json")
local radon = require("./radon")
local foppy = require("./foppy")
local config = require("./config")
local authors = require("./authors")
local blogs = require("./blogs")
local utils = require("./utils")
local multipart = require('weblit-multipart')
local timer = require("timer")
local radon = require("radon")
local foppy = require("./foppy")
local database = require("./database")
local errors = require("./errors")
local utils = require("./utils")
local error_handler = require("template.error_handler")
local templater = function(template, rate)
return function(req, res)
@ -47,7 +46,7 @@ weblit.app
.use(cookie)
.route(
{ path = "/static/:path:" },
{ path = "/static/:path" },
weblit.static("static")
)
@ -56,6 +55,11 @@ weblit.app
weblit.static("database/media")
)
.route(
{ path = "/media", method = "POST" },
multipart { writeTo = "database/media" }
)
-- TODO: CLONE ALL OF THIS AS A REST API EVENTUALLY,
.route({ path = "/signup" }, templater("signup"))
.route({ path = "/signup2" }, templater("signup2", 3000))

View file

@ -1,10 +1,10 @@
---@diagnostic disable:undefined-global
local wrapper = require "template.wrapper"
local error_handler = require "template.error_handler"
local phosphor = require "template.phosphor"
local blogs = require "./blogs"
local blogs = require "blogs"
local errors = require "./errors"
return function(data)
local handle = data.request.params.blog
@ -14,7 +14,7 @@ return function(data)
-- could find no darn blog!!!
if not blog then
return error_handler(data, "blog_not_found", "/")
return errors.handle(data, "blog_not_found", "/")
end
-- our little post renderer

View file

@ -1,7 +0,0 @@
return function(data, code, redirect)
-- create an error card cookie for displaying an error card, then redirect
data.response.setCookie("error-card", code)
data.response.headers["Location"] = redirect
data.response.code = 302
end

View file

@ -1,10 +1,10 @@
---@diagnostic disable:undefined-global
local wrapper = require "template.wrapper"
local utils = require "./utils"
local authors = require "./authors"
local phosphor = require "template.phosphor"
local error_handler = require "template.error_handler"
local authors = require "authors"
local utils = require "./utils"
local errors = require "./errors"
return function(data)
local token = data.request.cookies.auth_token
@ -15,7 +15,7 @@ return function(data)
token = authors.auth(form.id, form.password)
if not token then
return error_handler(data, "wrong_credentials", "/login")
return errors.handle(data, "wrong_credentials", "/login")
end
data.response.setCookie(

View file

@ -2,20 +2,20 @@
local wrapper = require "template.wrapper"
local phosphor = require "template.phosphor"
local error_handler = require "template.error_handler"
local authors = require "./authors"
local blogs = require "./blogs"
local authors = require "authors"
local blogs = require "blogs"
local errors = require "./errors"
return function(data)
local token = data.request.cookies.auth_token
if not token then
return error_handler(data, "not_logged_in", "/login")
return errors.handle(data, "not_logged_in", "/login")
end
local author_id = authors.id_from_token(token)
if not author_id then
return error_handler(data, "invalid_token", "/login")
return errors.handle(data, "invalid_token", "/login")
end
return wrapper(data) {

View file

@ -1,19 +1,20 @@
local authors = require "./authors"
local blogs = require "./blogs"
local utils = require "./utils"
local error_handler = require "template.error_handler"
local logout = require "template.logout"
local authors = require "authors"
local blogs = require "blogs"
local utils = require "./utils"
local errors = require "./errors"
return function(data)
local token = data.request.cookies.auth_token
if not token then
return error_handler(data, "not_logged_in", "/login")
return errors.handle(data, "not_logged_in", "/login")
end
local author_id = authors.id_from_token(token)
if not author_id then
return error_handler(data, "invalid_token", "/login")
return errors.handle(data, "invalid_token", "/login")
end
local form = data.request.form
@ -25,7 +26,7 @@ return function(data)
print("@line: " .. debug.getinfo(1).currentline)
if not utils.is_valid_handle(form.handle) then
return error_handler(data, "blog_handle_invalid", "/new-blog")
return errors.handle(data, "blog_handle_invalid", "/new-blog")
end
print("@line: " .. debug.getinfo(1).currentline)
@ -37,7 +38,7 @@ return function(data)
form.about
)
if not ok then
return error_handler(data, err, "/new-blog")
return errors.handle(data, err, "/new-blog")
end
print("@line: " .. debug.getinfo(1).currentline)

View file

@ -3,19 +3,7 @@
local wrapper = require "template.wrapper"
local phosphor = require "template.phosphor"
local placeholders = {
{ "CoolBlog abOUT cats!!!!", "kool.katzz._", "i LOVE cats SO MUCH i could TALK about them all DAY" },
{ "Videodude99's Reviews", "videodude99", "hello everyone i'm videodude99 i do reviews of movies that suck and stuff" },
{ "The Passionate Gamer", "pressurevessel", "hi! i'm the passionate gamer, fan of Full Death and Pressure Vessel, i own a Vessel Switch and am a Bintux user" },
{ "blue hedgehog fanfiction", "123-sonicfan-123", "sonami isnt even that good." },
{ "Happy Videogame Geek", "anticinematic", "Anticinematic creates reviews and comedy shows about videogames and movies." },
{ "Look Dad All Computers!", "LDAC", "i dont make things that make music, i just use a daw tbh." },
{ "Jackerlope's Creepy Blog", "not-wendigoon", "bad vibes and awful content." }
}
return function(data)
local placeholder = placeholders[math.random(#placeholders)]
return wrapper(data) {
h1 {
phosphor("book"), " create a new blog!"

View file

@ -1,11 +1,8 @@
---@diagnostic disable:undefined-global
local wrapper = require "template.wrapper"
local error_handler = require "template.error_handler"
local phosphor = require "template.phosphor"
local foppy = require "./foppy"
return function(data)
local post_name = data.request.params.post
local blog_name = data.request.params.blog
@ -16,7 +13,7 @@ return function(data)
"database/blogs/" .. blog_name .. ".json"
)
if not blog then
return error_handler(data, "blog_not_found", "/")
return errors.handle(data, "blog_not_found", "/")
end
-- attempt to read the post
@ -24,7 +21,7 @@ return function(data)
"database/blogs/" .. blog_name .. "/" .. post_name .. ".json"
)
if not post then
return error_handler(data, "post_not_found", "/" .. handle)
return errors.handle(data, "post_not_found", "/" .. handle)
end
local latest_version = post.versions[#post.versions]

View file

@ -1,11 +1,11 @@
---@diagnostic disable:undefined-global
local wrapper = require "template.wrapper"
local utils = require "./utils"
local authors = require "./authors"
local phosphor = require "template.phosphor"
local error_handler = require "template.error_handler"
local authors = require "authors"
local utils = require "./utils"
local errors = require "./errors"
return function(data)
-- do we already have a token?
@ -25,12 +25,12 @@ return function(data)
-- if passwords dont match
if form.password ~= form.confirm_password then
return error_handler(data, "password_not_matching", "/signup") -- error out
return errors.handle(data, "password_not_matching", "/signup") -- error out
end
-- if password is not as secure as it should be
if not utils.is_valid_password(form.password) then
return error_handler(data, "password_not_secure", "/signup") -- error out!
return errors.handle(data, "password_not_secure", "/signup") -- error out!
end
local id = authors.create_account(form.password)

View file

@ -1,15 +1,14 @@
---@diagnostic disable:undefined-global
local config = require "./config"
local authors = require "./authors"
local phosphor = require "template.phosphor"
local authors = require "authors"
local errors = require "./errors"
return function(data)
-- create top navigation element
local left_side = {
class = "top-navigator-url",
a { href = "/", config.base_url .. "/" }
a { href = "/", data.request.headers["host"] .. "/" }
}
local url = data.request.path