Compare commits

..

10 Commits

Author SHA1 Message Date
Quad 48be035341 Some comments 2022-05-13 17:01:32 +02:00
Quad d7f7f547e4 Add basic user creation 2022-04-13 23:42:12 +02:00
Quad 361d3ebbcb Logging tweaks 2022-04-13 22:34:26 +02:00
Quad 83d77f8865 Implement logging/formatting 2022-04-13 22:22:52 +02:00
Quad 7c484b18d3 Move stuff to data folder, so I can just gitignore that entire folder 2022-04-13 17:24:01 +02:00
Quad 88cbeee562 SQLite boilerplate 2022-04-13 17:14:13 +02:00
Quad 44cbd19a8d Initial CSS base 2022-04-11 14:36:29 +02:00
Quad a43e45b237 Very rough sample in place 2022-04-11 11:10:32 +02:00
Quad ac32fa394f Switch to somewhat more conventional view/route split 2022-04-11 09:03:53 +02:00
Quad bc2a428432 Initial support for static files 2022-04-11 08:56:19 +02:00
15 changed files with 363 additions and 19 deletions

3
.gitignore vendored
View File

@ -3,3 +3,6 @@
# Binary name built by nimble # Binary name built by nimble
nimblog nimblog
# Blog data
data/*

View File

@ -1,6 +1,6 @@
# nimblog # nimblog
Placeholder text Basic blog written in nim, mostly for learning purposes.
## How to run ## How to run

15
schema.sql Normal file
View File

@ -0,0 +1,15 @@
CREATE TABLE users (
id INTEGER PRIMARY KEY AUTOINCREMENT,
username TEXT UNIQUE NOT NULL,
displayname TEXT NOT NULL,
password TEXT NOT NULL
);
CREATE TABLE posts (
id INTEGER PRIMARY KEY AUTOINCREMENT,
author_id INTEGER NOT NULL,
created TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP,
title TEXT NOT NULL,
body TEXT NOT NULL,
FOREIGN KEY (author_id) REFERENCES users (id)
);

47
src/modules/auth.nim Normal file
View File

@ -0,0 +1,47 @@
# Handles user management. And hopefully stuff like proper password hashing at some point in the future.
import db
import formatter
proc addUser(username: string, displayname: string, password: string): bool =
# Hopefully hashing and stuff will be implemented here at some point
return createUser(username=username, displayname=displayname, password=password)
# Extremely rudimentary, must be fixed later.
proc addUserInteractive(): bool =
var userConsent: bool = false
while userConsent != true:
stdout.write "Did not find a user. Create one now? (y/n) "
stdout.flushFile()
var userInput = readChar(stdin)
stdin.flushFile()
case userInput
of 'y', 'Y':
userConsent = true
of 'n', 'N':
return false
else:
discard
stdout.write "Enter a username: "
stdout.flushFile()
var username = readLine(stdin)
stdin.flushFile()
stdout.write "Enter a display name: "
stdout.flushFile()
var displayname = readLine(stdin)
stdin.flushFile()
stdout.write "Enter a password: "
stdout.flushFile()
var password = readLine(stdin)
stdin.flushFile()
if addUser(username=username, displayname=displayname, password=password):
logPrint("User " & username & " with displayname " & displayname & " has been created", "Main")
return true
else:
return false
export addUserInteractive

View File

@ -1,17 +1,22 @@
# This is what ACTUALLY starts the blog
import prologue import prologue
import templating import prologue/middlewares/staticfile
import views
import formatter
proc getIndex*(ctx: Context) {.async.} = proc runWebsite(settings: Table[string, bool]) =
resp renderTemplate(templateName="index.nwt")
proc runWebsite(settings: Table) =
let prologueSettings = newSettings( let prologueSettings = newSettings(
debug = settings["debug"] debug = settings["debug"]
) )
let website = newApp(settings = prologueSettings)
var website = newApp(settings = prologueSettings)
website.get("/", getIndex) website.get("/", getIndex)
website.get("/about", getAbout)
website.use(staticFileMiddleware("static"))
logPrint("Press Ctrl-C to stop site", "Info")
website.run() website.run()
export runWebsite export runWebsite

57
src/modules/db.nim Normal file
View File

@ -0,0 +1,57 @@
# Everything related to database reads and writes goes in this file
import tables
import std/[os, db_sqlite, strutils, streams]
import formatter
proc connectDb(dbPath: string = "data/blog.db"): DbConn =
var db = open(dbPath, "", "", "")
return db
proc createUser(username: string, displayname: string, password: string): bool =
try:
var db = connectDb()
db.exec(sql"INSERT INTO users (username,displayname,password) VALUES (?, ?, ?)", username, displayname, password)
db.close()
return true
except:
return false
proc checkUser(): bool =
var db = connectDb()
var response = db.getRow(sql"SELECT * FROM users")
db.close()
if response[0] != "":
return true
else:
return false
proc createDb(dbSettings: Table): bool =
if not fileExists("data/blog.db"):
var userConsent: bool = false
while userConsent != true:
stdout.write "Did not find database. Create one now? (y/n) "
stdout.flushFile()
var userInput = readChar(stdin)
stdin.flushFile()
case userInput
of 'y', 'Y':
userConsent = true
of 'n', 'N':
return false
else:
discard
let
schema = readFile(dbSettings["schema_path"])
db = connectDb()
for command in schema.split(";"):
# Skip "command" if it's just a blank line
if command == "\c\n" or command == "\n":
continue
db.exec(sql(command.strip))
logPrint("Ran SQL command from schema", "Info")
db.close()
return true
export createDb, checkUser, createUser

55
src/modules/formatter.nim Normal file
View File

@ -0,0 +1,55 @@
# Handles formatting of text and outputting of log messages
import tables
import strutils
import math
proc colorEsc(color: string): string {.gcsafe.} =
# Stored in proc to be gcsafe
let colors = {
"black": 30,
"red": 31,
"green": 32,
"orange": 33,
"blue": 34,
"purple": 35,
"cyan": 36,
"reset": 0
}.toTable()
return "\e[" & intToStr(colors[color]) & "m"
proc colorize(text: string, color: string): string {.gcsafe.} =
return colorEsc(color) & text & colorEsc("reset")
proc strCenter(text: string, filler: string = "-", length: int = 8): string {.gcsafe.} =
# Simply truncate and return if it fills the entire length
if text.len() > length:
return text[0..length-1]
# Find remaining characters and how much to pad each side
var remainHalf: float = (length - text.len()) / 2
var remainLeft: int = remainHalf.floor().int
var remainRight: int = remainHalf.ceil().int
return filler.repeat(remainLeft) & text & filler.repeat(remainRight)
proc logFormat(text: string, level: string = "undef"): string {.gcsafe.} =
# Stored in proc to be gcsafe
let colorMapping = {
"Main": "green",
"Error": "red",
"Info": "purple",
"Warning": "orange",
"Web": "blue",
"undef": "cyan"
}.toTable()
var prefix = "[" & colorize(text=strCenter(level), color=colorMapping[level]) & "]"
return prefix & " " & text
proc logPrint(text: string = "Undefined error message", level: string = "undef") {.gcsafe.} =
# In separate function in case I need to for example also write it to a file in the future.
echo logFormat(text=text, level=level)
export logPrint

View File

@ -1,6 +0,0 @@
import nimja/parser
proc renderTemplate(templateName: static[string]): string =
compileTemplateFile(getScriptDir() & "/templates/" & templateName)
export renderTemplate

17
src/modules/views.nim Normal file
View File

@ -0,0 +1,17 @@
# View definition where the fuctions for prologue will all be stashed
import prologue
import nimja/parser
import formatter
proc renderTemplate(templateName: static[string]): string =
logPrint("Served template " & templateName, "Web")
compileTemplateFile(getScriptDir() & "/templates/" & templateName)
proc getIndex*(ctx: Context) {.async.} =
resp renderTemplate(templateName="index.nwt")
proc getAbout*(ctx: Context) {.async.} =
resp renderTemplate(templateName="about.nwt")
export getIndex, getAbout

View File

@ -1,15 +1,41 @@
# This module mostly handles startup/config checks and then passes itself onwards
when isMainModule: when isMainModule:
import tables import tables
import modules/blog import os
import modules/env import modules/[blog, env, db, formatter, auth]
let dataDir: string = "data"
let settings = { let settings = {
"debug": boolEnvOrDefault("DEBUG", false), "debug": boolEnvOrDefault("DEBUG", false),
"logging": boolEnvOrDefault("NIMBLOG_LOG", true) "logging": boolEnvOrDefault("NIMBLOG_LOG", true)
}.toTable() }.toTable()
if settings["debug"]:
logPrint("Debug mode is enabled", "Warning")
let dbSettings = {
"schema_path": "schema.sql"
}.toTable()
logPrint("Starting website", "Main")
if not dirExists(dataDir):
logPrint("Created data directory", "Info")
createDir(dataDir)
if not createDb(dbSettings):
logPrint("A database must be created before use", "Error")
quit(QuitFailure)
if not checkUser():
logPrint("No user in database", "Warning")
if not addUserInteractive():
logPrint("An initial user must be created before use", "Error")
quit(QuitFailure)
try: try:
echo "Starting website" runWebsite(settings=settings)
runWebsite(settings)
except: except:
echo "Could not run website" logPrint("Could not run website", "Error")

20
src/templates/about.nwt Normal file
View File

@ -0,0 +1,20 @@
{% extends templates/components/master.nwt %}
{% block styles %}<link rel="stylesheet" type="text/css" href="/static/css/article.css" />{% endblock %}
{% block content %}
<article>
<p>
Hi there! This is my blog and you're currently reading placeholder text which has been left here so I can test formatting a bit.
</p>
<h1>
Hello world
</h1>
<p>
Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua. Ut enim ad minim veniam, quis nostrud exercitation ullamco laboris nisi ut aliquip ex ea commodo consequat. Duis aute irure dolor in reprehenderit in voluptate velit esse cillum dolore eu fugiat nulla pariatur. Excepteur sint occaecat cupidatat non proident, sunt in culpa qui officia deserunt mollit anim id est laborum.
</p>
<p>
Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua. Ut enim ad minim veniam, quis nostrud exercitation ullamco laboris nisi ut aliquip ex ea commodo consequat. Duis aute irure dolor in reprehenderit in voluptate velit esse cillum dolore eu fugiat nulla pariatur. Excepteur sint occaecat cupidatat non proident, sunt in culpa qui officia deserunt mollit anim id est laborum.
</p>
</article>
{% endblock %}

View File

@ -1,8 +1,19 @@
<!DOCTYPE html>
<html> <html>
<head> <head>
<title>Example</title> <title>Example</title>
<link rel="stylesheet" type="text/css" href="/static/css/base.css" />
<link rel="stylesheet" type="text/css" href="/static/css/master.css" />
{% block styles %}{% endblock %}
</head> </head>
<body> <body>
<div id="header-block">
<h1> Example </h1>
</div>
<div id="site-block">
<div id="content-block">
{% block content %}{% endblock %} {% block content %}{% endblock %}
</div>
</div>
</body> </body>
</html> </html>

16
static/css/article.css Normal file
View File

@ -0,0 +1,16 @@
article {
font-size: 12pt;
line-height: 1.20;
}
article p {
margin-top: calc(var(--font-margin) / 5);
margin-bottom: var(--font-margin);
}
article h1 {
font-weight: 300;
font-size: 175%;
margin-top: calc(var(--font-margin) * 1.75);
margin-bottom: calc(var(--font-margin) / 5);
}

34
static/css/base.css Normal file
View File

@ -0,0 +1,34 @@
div, body {
box-sizing: border-box;
}
body, html, div, p, h1, h2, h3, h4, h5 {
margin: 0;
padding: 0;
font-family: sans-serif;
}
:root {
--site-width: 960px;
--site-font-size: 13pt;
--bgcolor-1: rgb(195,180,180);
--bgcolor-2: rgb(225,220,220);
--bgcolor-3: rgb(250,250,250);
--fgcolor-1: rgb(0,0,0);
--fgcolor-2: rgb(245,245,245);
--highlight-1: rgb(195,55,55);
--highlight-2: rgb(175,25,5);
--font-margin: 12px;
--header-height: 50px;
--header-half-height: calc(var(--header-height) / 2);
--shadow-size: 8px;
--shadow-color: rgba(0,0,0,0.35);
--border-size: 2px;
}

44
static/css/master.css Normal file
View File

@ -0,0 +1,44 @@
body {
background-color: var(--bgcolor-2);
color: var(--fgcolor-1);
}
#header-block {
display: block;
position: fixed;
width: 100vw;
color: var(--fgcolor-2);
height: var(--header-height);
padding: 5px;
padding-top: var(--header-half-height);
background-color: var(--highlight-1);
border-bottom: solid var(--border-size) var(--highlight-2);
margin-bottom: translateY(-100%);
}
#header-block h1 {
transform: translateY(-50%);
}
#site-block {
display: block;
padding-top: calc(var(--header-height) - var(--border-size));
}
#content-block {
display: block;
max-width: var(--site-width);
width: 100vw;
padding: 10px;
background-color: var(--bgcolor-3);
margin-left: auto;
margin-right: auto;
border: solid var(--border-size) var(--bgcolor-1);
}
@media only screen and (max-width: 960px) {
#content-block {
border-left: none;
border-right: none;
}
}