From 9ebc1a57335e3b183be9a0494cdc377a19219222 Mon Sep 17 00:00:00 2001 From: Paul Duncan Date: Tue, 15 Mar 2016 00:11:28 -0400 Subject: initial session support --- src/guff/api/methods.cr | 8 ++++ src/guff/api/test.cr | 63 +++++++++++++++++----------- src/guff/handler.cr | 22 ++++++++++ src/guff/handlers.cr | 5 +++ src/guff/handlers/api.cr | 11 ++++- src/guff/handlers/session.cr | 21 ++++++++++ src/guff/handlers/test-auth.cr | 10 ----- src/guff/migrations.cr | 18 ++++++++ src/guff/models.cr | 7 ++-- src/guff/models/session.cr | 93 ++++++++++++++++++++++++++++++++++++++++++ 10 files changed, 220 insertions(+), 38 deletions(-) create mode 100644 src/guff/handlers/session.cr create mode 100644 src/guff/models/session.cr diff --git a/src/guff/api/methods.cr b/src/guff/api/methods.cr index 0247460..8df48d5 100644 --- a/src/guff/api/methods.cr +++ b/src/guff/api/methods.cr @@ -357,6 +357,14 @@ module Guff "set_user": { text: "Set mock user", + + args: { + "user_id": { + text: "ID of user", + type: :int, + required: true, + }, + }, }, } } diff --git a/src/guff/api/test.cr b/src/guff/api/test.cr index 44b5e10..79d2d2e 100644 --- a/src/guff/api/test.cr +++ b/src/guff/api/test.cr @@ -17,41 +17,56 @@ module Guff [{foo: "bar"}, {foo: "asdf"}].to_json end - MOCK_USERS = { - "users": [{ - "id": "0", - "name": "Guest", - "active": true, - }, { - "id": "1", - "name": "Admin", - "active": false, - }, { - "id": "2", - "name": "Test User 1", - "active": false, - }, { - "id": "2", - "name": "Test User 2", - "active": false, - }, { - "id": "3", - "name": "Test User<<<<>>>>>&&&&&&2", - "active": false, - }] - } + MOCK_USERS = [{ + "id": "0", + "name": "Guest", + "active": true, + }, { + "id": "1", + "name": "Admin", + "active": false, + }, { + "id": "2", + "name": "Test User 1", + "active": false, + }, { + "id": "3", + "name": "Test User 2", + "active": false, + }, { + "id": "4", + "name": "Test User<<<<>>>>>&&&&&&2", + "active": false, + }] private def do_test_get_users( context : HTTP::Server::Context, args : Hash(String, String) ) - MOCK_USERS + user_id = "0" + with_session(context) do |session| + user_id = session["user_id"]? || "0" + false + end + + # build result + users = MOCK_USERS.map { |row| + row.merge({ "active": (row["id"] == user_id) }) + } + + # return result + { "users": users } end private def do_test_set_user( context : HTTP::Server::Context, args : Hash(String, String) ) + with_session(context) do |session| + session["user_id"] = args["user_id"] + true + end + { ok: true } end diff --git a/src/guff/handler.cr b/src/guff/handler.cr index 357ed1f..52269a3 100644 --- a/src/guff/handler.cr +++ b/src/guff/handler.cr @@ -12,6 +12,28 @@ module Guff call_next(context) end + def with_session(context, &block : Hash(String, String) -> Bool) + sid, data = nil, nil + if sid = context.request.headers["x-guff-session-id"]? + data = Hash(String, String).from_json(@models.session.get(sid) || "{}") + else + sid = @models.session.add + context.request.headers["x-guff-session-id"] = sid + data = {} of String => String + end + + save = block.call(data) + + # save data + @models.session.set(sid, data.to_json) if save + end + + private def get_session(context) : Hash(String, String) + end + + private def save_session(sid, data) + end + private def redirect(context, url : String) context.response.status_code = 302 context.response.headers["location"] = url diff --git a/src/guff/handlers.cr b/src/guff/handlers.cr index e573673..1e0e6fc 100644 --- a/src/guff/handlers.cr +++ b/src/guff/handlers.cr @@ -23,6 +23,11 @@ module Guff init: ->(models : Models) { GuffStuffHandler.new(models) as HTTP::Handler }, + }, { + env: %w{development}, + init: ->(models : Models) { + SessionHandler.new(models) as HTTP::Handler + }, }, { env: %w{development}, init: ->(models : Models) { diff --git a/src/guff/handlers/api.cr b/src/guff/handlers/api.cr index 4401f75..6f5c528 100644 --- a/src/guff/handlers/api.cr +++ b/src/guff/handlers/api.cr @@ -10,8 +10,17 @@ private macro define_method_calls(hash) case method {% for method in methods %} when "{{ method }}" + params = case context.request.method + when "GET" + context.request.query_params + when "POST" + HTTP::Params.parse((context.request.body || "") as String) + else + raise "unsupported method" + end + do_{{ namespace.id }}_{{ method }}(context, get_method_args( - context.request.query_params, + params, "{{ namespace.id }}", "{{ method.id }}" )).to_json diff --git a/src/guff/handlers/session.cr b/src/guff/handlers/session.cr new file mode 100644 index 0000000..bc7a5bc --- /dev/null +++ b/src/guff/handlers/session.cr @@ -0,0 +1,21 @@ +require "json" +require "../handler" + +class Guff::Handlers::SessionHandler < Guff::Handler + def call(context : HTTP::Server::Context) + if cookie = context.request.cookies["guff_session"]? + # get session id + sid = cookie.value + puts "DEBUG: session_id = #{sid}" + context.request.headers["x-guff-session-id"] = sid + end + + call_next(context) + + if sid = context.request.headers["x-guff-session-id"]? + # FIXME: need way to delete cookie + cookie = HTTP::Cookie.new("guff_session", sid) + context.response.headers["set-cookie"] = cookie.to_set_cookie_header + end + end +end diff --git a/src/guff/handlers/test-auth.cr b/src/guff/handlers/test-auth.cr index 5cf5769..a0238f2 100644 --- a/src/guff/handlers/test-auth.cr +++ b/src/guff/handlers/test-auth.cr @@ -8,8 +8,6 @@ class Guff::Handlers::TestAuthHandler < Guff::Handler case context.request.method when "GET", "HEAD" draw_page(context) - when "POST" - set_auth(context) else raise "unknown method" end @@ -21,12 +19,4 @@ class Guff::Handlers::TestAuthHandler < Guff::Handler private def draw_page(context) TestAuthHTMLView.run(@models, context) end - - private def set_auth(context) - params = HTTP::Params.parse(context.request.body as String) - - # TODO: extract user id and set it in session - - redirect(context, "/test/blog") - end end diff --git a/src/guff/migrations.cr b/src/guff/migrations.cr index 0e50fdb..c72edde 100644 --- a/src/guff/migrations.cr +++ b/src/guff/migrations.cr @@ -126,5 +126,23 @@ module Guff }, %{ CREATE INDEX in_post_tags_post_id ON post_tags(post_id) }], + }, { + id: "5-sessions", + + sql: [%{ + CREATE TABLE sessions ( + session_id TEXT PRIMARY KEY, + + created_at TIMESTAMP WITH TIME ZONE + NOT NULL DEFAULT CURRENT_TIMESTAMP, + + updated_at TIMESTAMP WITH TIME ZONE + NOT NULL DEFAULT CURRENT_TIMESTAMP, + + data TEXT NOT NULL DEFAULT '' + ) + }, %{ + CREATE INDEX in_sessions_sid on sessions(session_id) + }], }] end diff --git a/src/guff/models.cr b/src/guff/models.cr index 52437d4..e9102b5 100644 --- a/src/guff/models.cr +++ b/src/guff/models.cr @@ -30,9 +30,10 @@ module Guff end define_model_getters({ - post: PostModel, - tag: TagModel, - site: SiteModel, + post: PostModel, + tag: TagModel, + site: SiteModel, + session: SessionModel, }) end end diff --git a/src/guff/models/session.cr b/src/guff/models/session.cr new file mode 100644 index 0000000..1b88002 --- /dev/null +++ b/src/guff/models/session.cr @@ -0,0 +1,93 @@ +require "secure_random" +require "../model" + +class Guff::SessionModel < Guff::Model + SQL = TemplateCache.new({ + add: " + INSERT INTO sessions(session_id) VALUES (:session_id) + ", + + delete: " + DELETE FROM sessions WHERE session_id = :session_id + ", + + get: " + SELECT data + + FROM sessions + + WHERE session_id = :session_id + AND strftime('%s', datetime(updated_at, :age)) >= + strftime('%s', 'now') + + ", + + set: " + UPDATE sessions + + SET data = :data, + updated_at = CURRENT_TIMESTAMP + + WHERE session_id = :session_id + AND strftime('%s', datetime(updated_at, :age)) >= + strftime('%s', 'now') + + ", + + scrub: " + DELETE FROM sessions + WHERE strftime('%s', datetime(updated_at, :age)) < + strftime('%s', 'now') + ", + }) + + # maximum session age + AGE = "24 hours" + + def initialize(models : Models) + super(models, SQL) + + # TODO: make this configurable + @age = AGE + end + + def add : String + # create sid + r = SecureRandom.hex(16) + + # add session + query(:add, { + "session_id": r + }, nil) + + # return sid + r + end + + def get(session_id : String) : String? + one(:get, { + "session_id": session_id, + "age": @age, + }, nil) + end + + def set(session_id : String, data : String) + query(:set, { + "session_id": session_id, + "age": @age, + "data": data, + }, nil) + end + + def delete(session_id : String) + query(:delete, { + "session_id": sesion_id, + }, nil) + end + + private def scrub + query(:scrub, { + "age": @age, + }, nil) + end +end -- cgit v1.2.3