Compare commits

..

No commits in common. "48be0353416a9b57c874b156a71bd7c93756d362" and "e40b453118d1d389de94b6d60bd7b624c3275269" have entirely different histories.

15 changed files with 19 additions and 363 deletions

3
.gitignore vendored
View File

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

View File

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

View File

@ -1,15 +0,0 @@
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)
);

View File

@ -1,47 +0,0 @@
# 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,22 +1,17 @@
# This is what ACTUALLY starts the blog
import prologue import prologue
import prologue/middlewares/staticfile import templating
import views
import formatter
proc runWebsite(settings: Table[string, bool]) = proc getIndex*(ctx: Context) {.async.} =
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

View File

@ -1,57 +0,0 @@
# 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

View File

@ -1,55 +0,0 @@
# 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

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

View File

@ -1,17 +0,0 @@
# 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,41 +1,15 @@
# This module mostly handles startup/config checks and then passes itself onwards
when isMainModule: when isMainModule:
import tables import tables
import os import modules/blog
import modules/[blog, env, db, formatter, auth] import modules/env
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:
runWebsite(settings=settings) echo "Starting website"
runWebsite(settings)
except: except:
logPrint("Could not run website", "Error") echo "Could not run website"

View File

@ -1,20 +0,0 @@
{% 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,19 +1,8 @@
<!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>

View File

@ -1,16 +0,0 @@
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);
}

View File

@ -1,34 +0,0 @@
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;
}

View File

@ -1,44 +0,0 @@
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;
}
}