aboutsummaryrefslogtreecommitdiff
path: root/src/guff.cr
diff options
context:
space:
mode:
Diffstat (limited to 'src/guff.cr')
-rw-r--r--src/guff.cr948
1 files changed, 0 insertions, 948 deletions
diff --git a/src/guff.cr b/src/guff.cr
index 9696e5d..f0e2671 100644
--- a/src/guff.cr
+++ b/src/guff.cr
@@ -11,14 +11,6 @@ end
require "./guff/**"
-private macro define_lazy_getters(hash)
- {% for name, klass in hash %}
- def {{ name.id }} : {{ klass.id }}
- (@cached_{{ name.id }} ||= {{ klass.id }}.new(@context))
- end
- {% end %}
-end
-
private macro include_api_modules(modules)
{% for mod in modules.resolve %}
include {{ mod.id }}
@@ -46,946 +38,6 @@ private macro api_method_dispatch(modules)
end
module Guff
- module Models
- class PageModel < Model
- SQL = {
- get_id: "
- SELECT b.post_id
-
- FROM sites a
- JOIN posts b
- ON (b.site_id = a.site_id)
- JOIN pages c
- ON (c.post_id = b.post_id)
- JOIN states d
- ON (d.state_id = b.state_id)
-
- WHERE a.site_id = ?
- AND b.slug = ?
- AND d.state = 'public'
-
- ORDER BY b.created_at DESC
- LIMIT 1
- ",
-
- add: "
- INSERT INTO pages(post_id, layout_id)
- VALUES (?, (SELECT layout_id FROM layouts WHERE layout = 'default'))
- ",
-
- set: "
- UPDATE pages
- SET layout_id = (SELECT layout_id FROM layouts WHERE layout = ?)
- WHERE post_id = ?
- ",
-
- get: "
- SELECT a.post_id,
- a.site_id,
- c.state,
- a.posted_at,
- a.expires_at,
- a.slug,
- a.slug_lock,
- a.name,
- a.body,
- d.layout
-
- FROM posts a
- JOIN pages b
- ON (b.post_id = a.post_id)
- JOIN states c
- ON (c.state_id = a.state_id)
- JOIN layouts d
- ON (d.layout_id = b.layout_id)
-
- WHERE a.post_id = ?
- ",
- }
-
- def get_id(
- site_id : Int64,
- slug : String
- ) : Int64?
- r = @context.dbs.ro.one(SQL[:get_id], [site_id.to_s, slug])
- r ? r.to_i64 : nil
- end
-
- def add(
- site_id : Int64,
- user_id : Int64,
- ) : Int64
- db = @context.dbs.rw
- post_id = -1_i64
-
- db.transaction do
- post_id = @context.models.post.add(site_id, user_id)
- db.query(SQL[:add], [post_id.to_s])
- end
-
- post_id
- end
-
- def set(
- post_id : Int64,
-
- site_id : Int64? = nil,
- state : String? = nil,
-
- have_posted_at : Bool = false,
- posted_at : Time? = nil,
-
- have_expires_at : Bool = false,
- expires_at : Time? = nil,
-
- slug : String? = nil,
- slug_lock : Bool? = nil,
-
- name : String? = nil,
- body : String? = nil,
-
- layout : String? = nil,
- )
- db = @context.dbs.rw
-
- db.transaction do
- @context.models.post.set(
- post_id: post_id,
-
- site_id: site_id,
- state: state,
-
- have_posted_at: have_posted_at,
- posted_at: posted_at,
-
- have_expires_at: have_expires_at,
- expires_at: expires_at,
-
- slug: slug,
- slug_lock: slug_lock,
-
- name: name,
- body: body,
- )
-
- if layout
- db.query(SQL[:set], [layout, post_id.to_s])
- end
- end
- end
-
- def get(post_id : Int64)
- @context.dbs.ro.row(SQL[:get], [post_id.to_s]).not_nil!
- end
- end
-
- class ProjectModel < Model
- SQL = {
- get_id: "
- SELECT b.post_id
-
- FROM sites a
- JOIN posts b
- ON (b.site_id = a.site_id)
- JOIN projects c
- ON (c.post_id = b.post_id)
- JOIN states d
- ON (d.state_id = b.state_id)
-
- WHERE a.site_id = ?
- AND b.slug = ?
- AND d.state = 'public'
-
- ORDER BY b.created_at DESC
- LIMIT 1
- ",
-
- add: "
- INSERT INTO projects(post_id) VALUES (?)
- ",
-
- set: "
- UPDATE projects
- SET repo_url = ?
- WHERE post_id = ?
- ",
-
- get: "
- SELECT a.post_id,
- a.site_id,
- c.state,
- a.posted_at,
- a.expires_at,
- a.slug,
- a.slug_lock,
- a.name,
- a.body,
- b.repo_url
-
- FROM posts a
- JOIN projects b
- ON (b.post_id = a.post_id)
- JOIN states c
- ON (c.state_id = a.state_id)
-
- WHERE a.post_id = ?
- ",
- }
-
- def get_id(
- site_id : Int64,
- slug : String
- ) : Int64?
- r = @context.dbs.ro.one(SQL[:get_id], [site_id.to_s, slug])
- r ? r.to_i64 : nil
- end
-
- def add(
- site_id : Int64,
- user_id : Int64,
- ) : Int64
- db = @context.dbs.rw
- post_id = -1_i64
-
- db.transaction do
- post_id = @context.models.post.add(site_id, user_id)
- db.query(SQL[:add], [post_id.to_s])
- end
-
- post_id
- end
-
- def set(
- post_id : Int64,
-
- site_id : Int64? = nil,
- state : String? = nil,
-
- have_posted_at : Bool = false,
- posted_at : Time? = nil,
-
- have_expires_at : Bool = false,
- expires_at : Time? = nil,
-
- slug : String? = nil,
- slug_lock : Bool? = nil,
-
- name : String? = nil,
- body : String? = nil,
-
- repo_url : String? = nil,
- )
- db = @context.dbs.rw
-
- db.transaction do
- @context.models.post.set(
- post_id: post_id,
-
- site_id: site_id,
- state: state,
-
- have_posted_at: have_posted_at,
- posted_at: posted_at,
-
- have_expires_at: have_expires_at,
- expires_at: expires_at,
-
- slug: slug,
- slug_lock: slug_lock,
-
- name: name,
- body: body,
- )
-
- if repo_url
- db.query(SQL[:set], [repo_url, post_id.to_s])
- end
- end
- end
-
- def get(post_id : Int64)
- @context.dbs.ro.row(SQL[:get], [post_id.to_s]).not_nil!
- end
- end
-
- class BlogModel < Model
- SQL = {
- get_ids: "
- SELECT b.post_id
-
- FROM sites a
- JOIN posts b
- ON (b.site_id = a.site_id)
- JOIN blogs c
- ON (c.post_id = b.post_id)
- JOIN states d
- ON (d.state_id = b.state_id)
-
- WHERE a.site_id = ?
- AND %s
- AND d.state = 'public'
- -- TODO: handle posted_at and expired_at
-
- ORDER BY COALESCE(b.posted_at, b.created_at) DESC
-
- LIMIT ? OFFSET ?
- ",
-
- add: "
- INSERT INTO blogs(post_id) VALUES (?)
- ",
-
- get: "
- SELECT a.post_id,
- a.site_id,
- c.state,
- a.posted_at,
- a.expires_at,
- a.slug,
- a.slug_lock,
- a.name,
- a.body
-
- FROM posts a
- JOIN blogs b
- ON (b.post_id = a.post_id)
- JOIN states c
- ON (c.state_id = a.state_id)
-
- WHERE a.post_id = ?
- ",
- }
-
- def get_id(
- site_id : Int64,
- year : Int32,
- month : Int32,
- day : Int32,
- slug : String
- ) : Int64?
- ids = get_ids(site_id, year, month, day, slug)
- (ids.size > 0) ? ids.first : nil
- end
-
- # TODO: make this configurable
- LIMIT = 50
-
- def get_ids(
- site_id : Int64,
- year : Int32? = nil,
- month : Int32? = nil,
- day : Int32? = nil,
- slug : String? = nil,
- page : Int32? = nil
- ) : Array(Int64)
- sql = ["1"]
- args = [site_id.to_s]
-
- # STDERR.puts "DEBUG: site_id = #{site_id}, year = #{year}, month = #{month}, day = #{day}, slug = \"#{slug}\""
-
- if year
- # add year filter
- sql << "strftime('%Y', b.posted_at) + 0 = ? + 0"
- args << year.to_s
-
- if month
- # add month filter
- sql << "strftime('%m', b.posted_at) + 0 = ? + 0"
- args << month.to_s
-
- if day
- # add day filter
- sql << "strftime('%d', b.posted_at) + 0 = ? + 0"
- args << day.to_s
- end
- end
- end
-
- if slug
- # add slug filter
- sql << "b.slug = ?"
- args << slug
- end
-
- page ||= 1
- raise "invalid page: #{page}" if page < 1
-
- args << LIMIT.to_s
- args << ((page - 1) * LIMIT).to_s
-
- # STDERR.puts "DEBUG: args = #{args.to_json}"
-
- # exec query, build result
- r = [] of Int64
- @context.dbs.ro.all(SQL[:get_ids] % sql.join(" AND "), args) do |row|
- p row
- r << row["post_id"] as Int64
- end
-
- # return results
- r
- end
-
- def add(
- site_id : Int64,
- user_id : Int64,
- ) : Int64
- db = @context.dbs.rw
- post_id = -1_i64
-
- db.transaction do
- post_id = @context.models.post.add(site_id, user_id)
- db.query(SQL[:add], [post_id.to_s])
- end
-
- post_id
- end
-
- def set(
- post_id : Int64,
-
- site_id : Int64? = nil,
- state : String? = nil,
-
- have_posted_at : Bool = false,
- posted_at : Time? = nil,
-
- have_expires_at : Bool = false,
- expires_at : Time? = nil,
-
- slug : String? = nil,
- slug_lock : Bool? = nil,
-
- name : String? = nil,
- body : String? = nil,
-
- repo_url : String? = nil,
- )
- @context.models.post.set(
- post_id: post_id,
-
- site_id: site_id,
- state: state,
-
- have_posted_at: have_posted_at,
- posted_at: posted_at,
-
- have_expires_at: have_expires_at,
- expires_at: expires_at,
-
- slug: slug,
- slug_lock: slug_lock,
-
- name: name,
- body: body,
- )
- end
-
- def get(post_id : Int64)
- @context.dbs.ro.row(SQL[:get], [post_id.to_s]).not_nil!
- end
- end
-
- class UserModel < Model
- SQL = {
- login: "
- SELECT user_id,
- password
-
- FROM users
-
- WHERE is_active
- AND email = ?
- ",
-
- has_role: "
- SELECT 1
-
- FROM users
-
- WHERE is_active
- AND user_id = ?
- AND role_id IN (SELECT role_id FROM roles WHERE role IN (%s))
- ",
-
- add: "
- INSERT INTO users(role_id, name, email, password, is_active) VALUES
- ((SELECT role_id FROM roles where role = ?), ?, ?, ?, ?)
- ",
-
- set: "
- UPDATE users
- SET %s
- WHERE user_id = ?
- ",
-
- get_users: "
- SELECT a.user_id,
- a.name,
- a.email,
- a.is_active,
- b.role,
- b.name AS role_name
-
- FROM users a
- JOIN roles b
- ON (b.role_id = a.role_id)
-
- ORDER BY LOWER(a.name)
- ",
-
- get: "
- SELECT a.user_id,
- a.name,
- a.email,
- a.is_active,
- b.role,
- b.name AS role_name
-
- FROM users a
- JOIN roles b
- ON (b.role_id = a.role_id)
-
- WHERE a.user_id = ?
- ",
- }
-
- def login(
- email : String,
- password : String
- ) : Int64?
- r = nil
-
- if row = @context.dbs.ro.row(SQL[:login], [email])
- if Password.test(row["password"] as String, password)
- # given email and password matches active user
- r = row["user_id"] as Int64
- end
- end
-
- # return result
- r
- end
-
- def has_role?(user_id : Int64, roles : Array(String))
- raise "empty role list" unless roles.size > 0
-
- !!@context.dbs.ro.one(SQL[:has_role] % [
- (["?"] * roles.size).join(",")
- ], [user_id.to_s].concat(roles))
- end
-
- def add(
- name : String,
- email : String,
- password : String,
- role : String,
- active : Bool,
- ) : Int64
- @context.dbs.rw.query(SQL[:add], [
- role,
- name,
- email,
- Password.create(password),
- active ? "1" : "0",
- ])
-
- @context.dbs.rw.last_insert_row_id.to_i64
- end
-
- def set(
- user_id : Int64,
- name : String? = nil,
- email : String? = nil,
- password : String? = nil,
- role : String? = nil,
- active : Bool? = nil,
- )
- sets = [] of String
- args = [] of String
-
- if name
- sets << "name = ?"
- args << name
- end
-
- if email
- sets << "email = ?"
- args << email
- end
-
- if role
- sets << "role_id = (SELECT role_id FROM roles WHERE role = ?)"
- args << role
- end
-
- if password
- sets << "password = ?"
- args << Password.create(password)
- end
-
- if active != nil
- sets << "is_active = ?"
- args << (active ? "1" : "0")
- end
-
- if sets.size > 0
- args << user_id.to_s
- @context.dbs.rw.query(SQL[:set] % sets.join(", "), args)
- end
- end
-
- def get_users
- rows = [] of Hash(String, String)
-
- @context.dbs.ro.all(SQL[:get_users]) do |row|
- # append row to result
- rows << row.reduce({} of String => String) do |r, kv|
- r[kv[0]] = kv[1].to_s
- r
- end
- end
-
- rows
- end
-
- def get(user_id : Int64)
- row = @context.dbs.ro.row(SQL[:get], [user_id.to_s])
- raise "unknown user: #{user_id}" unless row
-
- row.reduce({} of String => String) do |r, kv|
- r[kv[0]] = kv[1].to_s
- r
- end
- end
- end
-
- # TODO: handle session expiration
- class SessionModel < Model
- SQL = {
- load: "
- SELECT data
-
- FROM sessions
-
- WHERE id = ?
- -- TODO:
- -- AND strftime('%s', created_at, '1 week') > strftime('%s')
- -- AND strftime('%s', updated_at, '2 hours') > strftime('%s')
- ",
-
- save: "
- UPDATE sessions
-
- SET updated_at = CURRENT_TIMESTAMP,
- data = ?
-
- WHERE id = ?
- ",
-
- delete: "
- DELETE FROM sessions WHERE id = ?
- ",
-
- create: "
- INSERT INTO sessions(id, data) VALUES (?, ?)
- ",
- }
-
- def load(id : String) : String?
- @context.dbs.ro.one(SQL[:load], [id])
- end
-
- def save(id : String, data : String)
- @context.dbs.rw.query(SQL[:save], [data, id])
- nil
- end
-
- def delete(id : String?)
- @context.dbs.rw.query(SQL[:delete], [id]) if id
- nil
- end
-
- def create(data : String) : String
- # generate id
- r = SecureRandom.hex(32)
-
- # save session
- @context.dbs.rw.query(SQL[:create], [r, data])
-
- # return session id
- r
- end
- end
-
- class CSRFModel < Model
- getter :minutes
-
- def initialize(context : Context)
- super(context)
- @cache = {} of String => Int64
-
- # expire form after 5 minutes
- # TODO: make this configurable
- @minutes = 5
- end
-
- def create_token
- remove_expired_tokens
-
- # generate and cache new token
- r = SecureRandom.hex(16)
- @cache[r] = Time.now.epoch + 60 * @minutes
-
- # return token
- r
- end
-
- def use_token(id : String)
- remove_expired_tokens
-
- if @cache.has_key?(id)
- # remove token, return success
- @cache.delete(id)
- true
- else
- # return failure
- false
- end
- end
-
- private def remove_expired_tokens
- now = Time.now.epoch
-
- # FIXME: limit the size of the cache
- # to prevent insane memory use
-
- # remove expired entries
- @cache.delete_if do |key, val|
- val < now
- end
- end
- end
-
- class StateModel < Model
- SQL = {
- get_states: "
- SELECT state_id,
- state,
- name,
- icon
-
- FROM states
-
- ORDER BY sort
- ",
- }
-
- def get_states
- rows = [] of Hash(String, String)
-
- @context.dbs.ro.all(SQL[:get_states]) do |row|
- rows << row.reduce({} of String => String) do |r, kv|
- r[kv[0]] = kv[1].to_s
- r
- end
- end
-
- rows
- end
- end
-
- class SiteModel < Model
- SQL = {
- get_id: "
- SELECT site_id
-
- FROM (
- SELECT a.site_id
-
- FROM site_domains a
- JOIN sites b
- ON (b.site_id = a.site_id)
-
- WHERE b.is_active
- AND a.domain = $1
-
- UNION ALL
-
- SELECT site_id
-
- FROM sites
-
- WHERE is_active
- AND is_default
- ) a
-
- LIMIT 1
- ",
-
- get_default_id: "
- SELECT site_id
-
- FROM sites
-
- WHERE is_active
- AND is_default
- ",
-
- get_sites: "
- SELECT site_id,
- name,
- is_active,
- is_default
-
- FROM sites
-
- ORDER BY LOWER(name)
-
- ",
- }
-
- def get_id(host : String?) : Int64?
- r = @context.dbs.ro.one(SQL[:get_id], [host || ""])
- r ? r.to_i64 : nil
- end
-
- def get_default_id : Int64
- @context.dbs.ro.one(SQL[:get_default_id]).not_nil!.to_i64
- end
-
- def get_sites
- rows = [] of Hash(String, String)
-
- @context.dbs.ro.all(SQL[:get_sites]) do |row|
- rows << row.reduce({} of String => String) do |r, kv|
- r[kv[0]] = kv[1].to_s
- r
- end
- end
-
- rows
- end
- end
-
- class RoleModel < Model
- SQL = {
- get_roles: "
- SELECT role,
- name
-
- FROM roles
-
- ORDER BY sort
- ",
- }
-
- def get_roles : Array(Hash(String, String))
- r = [] of Hash(String, String)
-
- @context.dbs.ro.all(SQL[:get_roles]) do |row|
- r << {
- "role" => row["role"] as String,
- "name" => row["name"] as String,
- }
- end
-
- r
- end
- end
- end
-
- class ModelSet
- def initialize(@context : Context)
- end
-
- define_lazy_getters({
- user: Models::UserModel,
- session: Models::SessionModel,
- csrf: Models::CSRFModel,
- post: Models::PostModel,
- page: Models::PageModel,
- project: Models::ProjectModel,
- blog: Models::BlogModel,
- site: Models::SiteModel,
- role: Models::RoleModel,
- state: Models::StateModel,
- })
- end
-
- class Session < Hash(String, String)
- getter :session_id
-
- # session cookie name
- # FIXME: does this belong here?
- COOKIE = "guff_session"
-
- def initialize(@context : Context)
- super()
- @session_id = nil
- end
-
- def load(id : String)
- begin
- # clear existing session
- clear
-
- # load session values
- JSON.parse(@context.models.session.load(id).not_nil!).each do |key, val|
- self[key.as_s] = val.as_s
- end
-
- # save session id
- @session_id = id
-
- # return success
- true
- rescue err
- STDERR.puts "session load failed: #{err}"
- # invalid session id, return failure
- false
- end
- end
-
- def create(hash : Hash(String, String)) : String
- clear
- merge!(hash)
- @session_id = @context.models.session.create(hash.to_json)
- end
-
- def save
- if valid?
- @context.models.session.save(@session_id.not_nil!, to_json)
-
- # return success
- true
- else
- # no session, return failure
- false
- end
- end
-
- def clear
- super
- @session_id = nil
- end
-
- def delete : String?
- r = @session_id
-
- if valid?
- @context.models.session.delete(r)
- clear
- end
-
- r
- end
-
- def valid?
- @session_id != nil
- end
- end
-
module Views
abstract class View
def initialize(@context : Context)