login

local git = require "git"
local scan = require "scan"
local hash = require "hash"

local lud = require "ludweb"

local function time_fmt(sig)
    local s = os.date("%Y-%m-%d %H:%M:%S", sig.time_)
    local offset = sig.offset
    local sign
    if offset < 0 then
        offset = -offset
        sign = "-"
    else
        sign = "+"
    end
    local hours = math.floor(offset / 60)
    local mins = offset % 60
    s = s .. (" (UTC%s%02d:%02d)"):format(sign, hours, mins)
    return s
end

local function diff_cb(line_type, line)
    line = lud.template.escape(line:sub(1, #line-1))
    if line_type == " " then
        line = ' <span class="diff_ctx">' .. line .. '</span>'
    elseif line_type == "+" then
        line = '+<span class="diff_add">' .. line .. '</span>'
    elseif line_type == "-" then
        line = '-<span class="diff_del">' .. line .. '</span>'
    elseif line_type == "=" then
        line = ' <span class="diff_nonl diff_ctx_nonl">' .. line .. '</span>'
    elseif line_type == ">" then
        line = ' <span class="diff_nonl diff_old_nonl">' .. line .. '</span>'
    elseif line_type == "<" then
        line = ' <span class="diff_nonl diff_new_nonl">' .. line .. '</span>'
    elseif line_type == "F" then
        line = '\n<span class="diff_file_hdr">' .. line .. '</span>'
    elseif line_type == "H" then
        line = '<span class="diff_hunk_hdr">' .. line .. '</span>'
    elseif line_type == "B" then
        line = '<span class="diff_bin">' .. line .. '</span>'
    else
        line = line_type .. line
    end
    return line .. "\n"
end

local function allowed(user, gname)
    if gname == "public" then
        return true
    elseif user == nil then
        return false
    end
    for _, ok in ipairs(user.groups) do
        if gname == ok then
            return true
        end
    end
    return false
end

local LOG_ERROR, LOG_WARN, LOG_INFO, LOG_DEBUG = 0, 1, 2, 3

local path = arg[1]
local conf = dofile(path.."/conf.lua")
local title = conf.title

git.init()
local groups = scan.scanrepos(path)

local sessions = {}

local function get_user(cookies)
    local session_id = cookies["sid"]
    if session_id == nil then
        return nil
    end
    local uname = sessions[session_id]
    if uname == nil then
        return nil
    end
    return conf.users[uname]
end

local routes = {
    {"GET", "/?",
    function (req)
        local user = get_user(req.cookies)
        local gnames = {}
        groups = scan.scanrepos(path)
        for gname in pairs(groups) do
            if allowed(user, gname) then
                table.insert(gnames, gname)
            end
        end
        local env = {title=title, user=user, gnames=gnames}
        return lud.template.render_file("view/home.html", env)
    end},
    {"GET", "/login",
    function (req)
        local user = get_user(req.cookies)
        if user ~= nil then  -- already logged in
            return "/", 303
        else
            return lud.template.render_file("view/login.html", {title=title})
        end
    end},
    {"POST", "/login",
    function (req)
        local uname = req.form.username
        local pass = req.form.password
        local user = conf.users[uname]
        local salt, h
        if user == nil then
            -- hash something as if we're trying to login anyway
            salt = hash.get_salt()
            h = hash.hash_pass(pass, salt)
            conf.log(LOG_INFO, "invalid username")
        else
            salt = lud.crypt.b64_dec(user.salt)
            h = hash.hash_pass(pass, salt)
            if h == lud.crypt.b64_dec(user.hash) then
                local session_id = lud.crypt.b64_enc(lud.crypt.uuid4())
                sessions[session_id] = uname
                conf.log(LOG_INFO, "logged in as "..uname)
                local age = conf.session_age
                local cookie = {key="sid", val=session_id, path="/", age=age}
                return "/", 303, "See Other", {cookie}
            else
                conf.log(LOG_INFO, "invalid password")
            end
        end
        return "/login", 303
    end},
    {"GET", "/logout",
    function (req)
        local session_id = req.cookies["sid"]
        if session_id ~= nil then
            conf.log(LOG_INFO, "logged out as "..sessions[session_id])
            sessions[session_id] = nil
        end
        return "/", 303
    end},
    {"GET", "/group/([%w_-]+)",
    function (req, gname)
        local user = get_user(req.cookies)
        if not allowed(user, gname) then
            return "/login", 303
        end
        local rnames = {}
        for rname in pairs(groups[gname]) do
            table.insert(rnames, rname)
        end
        local env = {title=title, user=user, gname=gname, rnames=rnames}
        return lud.template.render_file("view/group.html", env)
    end},
    {"GET", "/group/([%w_-]+)/repo/([%w_-]+)",
    function (req, gname, rname)
        local user = get_user(req.cookies)
        if not allowed(user, gname) then
            return "/login", 303
        end
        local repo = groups[gname][rname]
        local bnames = repo:branches()
        local tnames = repo:tags()
        local env = {
            title=title, user=user, repo=repo, gname=gname,
            rname=rname, bnames=bnames, tnames=tnames,
        }
        return lud.template.render_file("view/repo.html", env)
    end},
    {"GET", "/group/([%w_-]+)/repo/([%w_-]+)/history/([%w_-]+)",
    function (req, gname, rname, first)
        local user = get_user(req.cookies)
        if not allowed(user, gname) then
            return "/login", 303
        end
        local repo = groups[gname][rname]
        local commit = repo:commit(first)
        local prev = repo:find_prev(commit:id(), conf.limit)
        local env = {
            title=title, user=user, gname=gname, rname=rname, bname=bname,
            commit=commit, limit=conf.limit, prev=prev, first=first,
        }
        return lud.template.render_file("view/history.html", env)
    end},
    {"GET", "/group/([%w_-]+)/repo/([%w_-]+)/commit/([%w_-]+)",
    function (req, gname, rname, cid)
        local user = get_user(req.cookies)
        if not allowed(user, gname) then
            return "/login", 303
        end
        local repo = groups[gname][rname]
        local commit = repo:commit(cid)
        local prev = repo:find_prev(commit:id(), 1)
        local sig = commit:signature()
        local time_str = time_fmt(sig)
        local diff = repo:diff(commit, diff_cb)
        local env = {
            title=title, user=user, gname=gname, rname=rname, bname=bname,
            commit=commit, time_str=time_str, sig=sig, cid=cid, prev=prev, diff=diff,
        }
        return lud.template.render_file("view/commit.html", env)
    end},
    {"GET", "/group/([%w_-]+)/repo/([%w_-]+)/commit/([%w_-]+)/tree/(.*)",
    function (req, gname, rname, cid, path)
        local user = get_user(req.cookies)
        if not allowed(user, gname) then
            return "/login", 303
        end
        local repo = groups[gname][rname]
        local commit = repo:commit(cid)
        local node = commit:tree_entry(path)
        if node == nil then
            return "File not found", 404, "Not found"
        end
        local parts = {}
        for part in path:gmatch("[^/]+") do
            table.insert(parts, part)
        end
        local base = req.path
        if base:sub(#base) ~= "/" then
            base = base .. "/"
        end
        local env = {
            title=title, user=user, gname=gname, rname=rname, cid=cid,
            path=path, base=base, parts=parts, node=node,
        }
        if node.type_ == "dir" then
            return lud.template.render_file("view/dir.html", env)
        elseif node.type_ == "file" then
            return lud.template.render_file("view/file.html", env)
        end
    end},
}

lud.app.new_app(routes):run(conf.port)

git.shutdown()