1
0
mirror of https://github.com/lxsang/antd-web-apps synced 2024-12-26 01:18:21 +01:00

working silk mvc framework

This commit is contained in:
Xuan Sang LE 2018-08-22 19:38:36 +02:00
parent 1f369c61ec
commit 0ee4a9314f
19 changed files with 586 additions and 91 deletions

View File

@ -1,5 +1,5 @@
BUILDDIR = ./build
projs = grs info blog
projs = grs info blog apps
copyfiles = index.ls
main: clean copy
for f in $(projs); do make -C "$${f}" ; done

View File

@ -1,6 +1,6 @@
BUILDDIR = ../build/apps
copyfiles = index.ls style.css
copyfiles = ./*
main:
- mkdir $(BUILDDIR)

View File

@ -0,0 +1,37 @@
PostController = BaseController:extends{
class = "PostController",
registry = {},
models = { "post" }
}
function PostController:index(...)
local args = {...}
self.template:set("index", args[1])
self.template:set("dummy", "This is a dummy string")
self:setSession("postsession", "Huehuehue")
return true
end
function PostController:edit(...)
if self:getSession("postsession") then
self.template:set("auth", true)
else
self.template:set("auth", false)
end
--self:redirect("/category/put/1")
return true
end
function PostController:add(...)
local args = {...}
local m = {
cid = tonumber(args[1]),
content = "This is the content for #cid="..args[1]
}
if(self.post:create(m)) then
self.template:set("status", "Post created")
else
self.template:set("status", "Cannot create post")
end
return true
end

View File

@ -1,18 +0,0 @@
<html>
<head>
<title>Application pages</title>
<link rel="stylesheet" type="text/css" href="grs/ubuntu-regular.css" />
<link rel="stylesheet" type="text/css" href="style.css" />
<link rel="stylesheet" type="text/css" href="rst/font-awesome.css" />
<script type="text/javascript" src="rst/gscripts/riot.min.js"> </script>
<script type="text/javascript" src="rst/resources/antos_tags.js"></script>
<script type="text/javascript" src="rst/gscripts/jquery-3.2.1.min.js"> </script>
<script type="text/javascript" src="rst/main.js"></script>
<script type="text/javascript" src="rst/gscripts/showdown.min.js"></script>
</head>
<body>
<div id="desktop">
</div>
</body>
</html>

View File

@ -0,0 +1,27 @@
<html>
<head>
<title>Application pages</title>
<!--link rel="stylesheet" type="text/css" href="<?=HTTP_ROOT?>/asset/get/grs/ubuntu-regular.css" />
<link rel="stylesheet" type="text/css" href="<?=HTTP_ROOT?>/asset/get/style.css" />
<link rel="stylesheet" type="text/css" href="<?=HTTP_ROOT?>/asset/get/rst/font-awesome.css" />
<script type="text/javascript" src="<?=HTTP_ROOT?>/asset/get/rst/gscripts/riot.min.js"> </script>
<script type="text/javascript" src="<?=HTTP_ROOT?>/asset/get/rst/resources/antos_tags.js"></script>
<script type="text/javascript" src="<?=HTTP_ROOT?>/asset/get/rst/gscripts/jquery-3.2.1.min.js"> </script>
<script type="text/javascript" src="<?=HTTP_ROOT?>/asset/get/rst/main.js"></script>
<script type="text/javascript" src="<?=HTTP_ROOT?>/asset/get/rst/gscripts/showdown.min.js"></script-->
</head>
<body>
<div id="desktop">
<?lua
local args = {...}
local views = args[1]
for k, v in pairs(views) do
echo(k.." -> ")
v:render()
echo("<br/>")
end
--views.__main__:render()
?>
</div>
</body>
</html>

View File

@ -1,29 +0,0 @@
-- some global variables
DIR_SEP = ""
WWW_ROOT = ""
HTTP_ROOT = ""
MODEL_ROOT = ""
CONTROLLER_ROOT = ""
VIEW_ROOT = ""
BASE_FRW = "apps."
-- require needed library
require("silk.api")
-- need to define this
-- basically it initialize an session object
session_start()
-- registry object store global variables
local REGISTRY = {}
-- set logging level
REGISTRY.logger = Logger{ levels = {INFO = true, ERROR = true, DEBUG = true}}
REGISTRY.db = nil
REGISTRY.layout = 'default'
local router = Router{registry = REGISTRY}
REGISTRY.router = router
router.setPath(CONTROLLER_ROOT)
router.delegate()
if REGISTRY.db then REGISTRY.db:close() end

View File

@ -0,0 +1,8 @@
PostModel = BaseModel:extends{
registry = {},
name = "post",
fields = {
cid = "NUMERIC",
content = "TEXT"
}
}

71
apps/router.lua Normal file
View File

@ -0,0 +1,71 @@
-- some global variables
DIR_SEP = "/"
WWW_ROOT = "/opt/www/htdocs/apps"
HTTP_ROOT = "https://apps.localhost:9195/"
BASE_FRW = "apps."
LOG_ROOT = WWW_ROOT..DIR_SEP.."logs"
CONTROLLER_ROOT = BASE_FRW.."controllers"
MODEL_ROOT = BASE_FRW.."models"
VIEW_ROOT = WWW_ROOT..DIR_SEP.."views"
-- require needed library
require(BASE_FRW.."silk.api")
-- need to define this
-- basically it initialize an session object
-- session_start()
-- registry object store global variables
local REGISTRY = {}
-- set logging level
REGISTRY.logger = Logger:new{ levels = {INFO = true, ERROR = true, DEBUG = true}}
REGISTRY.db = DBHelper:new{db="iosapps"}
REGISTRY.layout = 'default'
-- mime type allows
-- this will bypass the default server security
-- the default list is from the server setting
REGISTRY.mimes = {
["application/javascript"] = true,
["image/bmp"] = true,
["image/jpeg"] = true,
["text/css"] = true,
["text/markdown"] = true,
["text/csv"] = true,
["application/pdf"] = true,
["image/gif"] = true,
["text/html"] = true,
["application/json"] = true,
["application/javascript"] = true,
["image/x-portable-pixmap"] = true,
["application/x-rar-compressed"] = true,
["image/tiff"] = true,
["application/x-tar"] = true,
["text/plain"] = true,
["application/x-font-ttf"] = true,
["application/xhtml+xml"] = true,
["application/xml"] = true,
["application/zip"] = true,
["image/svg+xml"] = true,
["application/vnd.ms-fontobject"] = true,
["application/x-font-woff"] = true,
["application/x-font-otf"] = true,
["audio/mpeg"] = true,
}
REGISTRY.db:open()
local router = Router:new{registry = REGISTRY}
REGISTRY.router = router
router:setPath(CONTROLLER_ROOT)
--router:route('edit', 'post/edit', "ALL" )
router:route('edit', 'post/edit', {
shown = true,
routes = {
["post/index"] = true
}
} )
router:delegate()
if REGISTRY.db then REGISTRY.db:close() end

View File

@ -1,22 +1,83 @@
-- create class
BaseController = Object:extends{registry = {}, models = {}}
BaseController = BaseObject:extends{class="BaseController", registry = {}, models = {}}
-- set the name here in each subclasses
function BaseController:initialize()
-- create model object here
--- infer the class here
for k, v in pairs(self.models) do
--- infer the class here
local modelname = firstToUpper(v).."Model"
local path = MODEL_ROOT.."."..modelname
-- require it
pcall(require, path)
--require(controller_path)
if not _G[modelname] then
self:modelnotfound(v)
else
-- create new model object
self[v] = _G[modelname]:new{registry = self.registry}
end
end
-- create template
self.template = Template:new{registry = self.registry}
end
function BaseController:redirect()
function BaseController:redirect(url)
std.status(301, "Moved Permanently")
std.custom_header("Content-Type","text/html")
std.custom_header("Location", url)
std.header_flush()
end
function BaseController:checkSession(key)
end
function BaseController:setSession(key)
function BaseController:setSession(key, value)
SESSION[key] = value
end
function BaseController:getSession(key)
return SESSION[key]
end
function BaseController:removeSession(key)
self:setSession(key, nil)
end
function BaseController:index()
error("#index: subclasses responsibility")
function BaseController:index(...)
self:error("#index: subclasses responsibility")
end
-- not found action
function BaseController:actionnotfound(...)
local args = {...}
self:error("#action "..args[1].." is not found in controller "..self.class)
end
-- not found model
function BaseController:modelnotfound(...)
local args = {...}
self:error("Model "..firstToUpper(args[1]).."Model is not found in controller "..self.class)
end
-- The not found controller
NotfoundController = BaseController:extends{ registry = {}, models = {} }
function NotfoundController:index(...)
local args = {...}
self:error("404: Controller "..args[1].." not found")
return false
end
-- The asset controller for the static file
AssetController = BaseController:extends{name= "AssetController",registry={}, models={}}
function AssetController:index(...)
local args = {...}
return self:get(table.unpack(args))
end
function AssetController:get(...)
local path = WWW_ROOT..DIR_SEP..implode({...}, DIR_SEP)
local mime = std.mimeOf(path)
if self.registry.mimes[mime] then
std.header(mime)
if std.isBinary(path) then
std.f(path)
else
std.fb(path)
end
end
return false
end

View File

@ -1,21 +1,46 @@
-- This is the base model class
require("OOP")
require("sqlite")
-- create class
BaseModel = Object:extends{registry = {}, name = ""}
BaseModel = BaseObject:extends{class="BaseModel",registry = {}}
function BaseModel:initialize()
self.db = self.registry.db
if self.db and self.name and self.name ~= "" and self.fields and not self.db:available(self.name) then
self.db:createTable(self.name, self.fields)
end
end
function BaseModel:create()
function BaseModel:create(m)
if self.db and m then
return self.db:insert(self.name,m)
end
return false
end
function BaseModel:update()
function BaseModel:update(m)
if self.db and m then
return self.db:update(self.name,m)
end
return false
end
function BaseModel:delete()
function BaseModel:delete(cond)
if self.db and m then
return self.db:delete(self.name,cond)
end
return false
end
function BaseModel:findAll(cond)
function BaseModel:find(cond)
if self.db and m then
return self.db:find(self.name, cond)
end
return false
end
function BaseModel:findAll()
if self.db and m then
return self.db:getAll(self.name)
end
return false
end

25
apps/silk/BaseObject.lua Normal file
View File

@ -0,0 +1,25 @@
BaseObject = Object:extends{registry = {}, class="BaseObject"}
function BaseObject:log(msg, level)
level = level or "INFO"
if self.registry.logger then
self.registry.logger:log(msg,level)
end
end
function BaseObject:debug(msg)
self:log(msg, "DEBUG")
end
function BaseObject:error(msg, trace)
html()
echo(msg)
self:log(msg,"ERROR")
if trace then
debug.traceback=nil
error(msg)
else
error(msg)
end
end

150
apps/silk/DBHelper.lua Normal file
View File

@ -0,0 +1,150 @@
sqlite = modules.sqlite()
if sqlite == nil then return 0 end
-- create class
DBHelper = BaseObject:extends{db=nil, class='DBHelper'}
function DBHelper:createTable(tbl, m)
if self:available(tbl) then return true end
local sql = "CREATE TABLE "..tbl.."(id INTEGER PRIMARY KEY"
for k, v in pairs(m) do
if k ~= "id" then
sql = sql..","..k.." "..v
end
end
sql = sql..");"
return sqlite.query(self.db,sql) == 1
end
function DBHelper:insert(tbl, m)
local keys = {}
local values = {}
for k,v in pairs(m) do
if k ~= "id" then
table.insert(keys,k)
if type(v) == "number" then
table.insert(values, v)
else
local t = "\""..v:gsub('"', '""').."\""
table.insert(values,t)
end
end
end
local sql = "INSERT INTO "..tbl.." ("..table.concat(keys,',')..') VALUES ('
sql = sql..table.concat(values,',')..');'
return sqlite.query(self.db, sql) == 1
end
function DBHelper:get(tbl, id)
return sqlite.select(self.db, tbl, "*","id="..id)[1]
end
function DBHelper:getAll(tbl)
local data = sqlite.select(self.db, tbl, "*", "1=1")
if data == nil then return nil end
local a = {}
for n in pairs(data) do table.insert(a, n) end
table.sort(a)
return data, a
end
function DBHelper:find(tbl, cond)
local cnd = "1=1"
local sel = "*"
if cond.exp then
cnd = self:gencond(cond.exp)
end
if cond.order then
cnd = cnd.." ORDER BY "
local l = {}
local i = 1
for k,v in pairs(cond.order) do
l[i] = k.." "..v
i = i+1
end
cnd = cnd..table.concat(l, ",")
end
if cond.limit then
cnd = cnd.." LIMIT "..cond.limit
end
if cond.fields then
sel = table.concat(cond.fields, ",")
--print(sel)
end
--print(cnd)
local data = sqlite.select(self.db, tbl, sel, cnd)
if data == nil then return nil end
local a = {}
for n in pairs(data) do table.insert(a, n) end
table.sort(a)
return data, a
end
function DBHelper:query(sql)
return sqlite.query(self.db, sql) == 1
end
function DBHelper:update(tbl, m)
local id = m['id']
if id ~= nil then
local lst = {}
for k,v in pairs(m) do
if(type(v)== "number") then
table.insert(lst,k.."="..v)
else
table.insert(lst,k.."=\""..v:gsub('"', '""').."\"")
end
end
local sql = "UPDATE "..tbl.." SET "..table.concat(lst,",").." WHERE id="..id..";"
return sqlite.query(self.db, sql) == 1
end
return false
end
function DBHelper:available(tbl)
return sqlite.hasTable(self.db, tbl) == 1
end
function DBHelper:deleteByID(tbl,id)
local sql = "DELETE FROM "..tbl.." WHERE id="..id..";"
return sqlite.query(self.db, sql) == 1
end
function DBHelper:gencond(o)
for k,v in pairs(o) do
if k == "and" or k == "or" then
local cnd = {}
local i = 1
for k1,v1 in pairs(v) do
cnd[i] = self:gencond(v1)
i = i + 1
end
return " ("..table.concat(cnd, " "..k.." ")..") "
else
for k1,v1 in pairs(v) do
local t = type(v1)
if(t == "string") then
return " ("..k1.." "..k..' "'..v1:gsub('"','""')..'") '
end
return " ("..k1.." "..k.." "..v1..") "
end
end
end
end
function DBHelper:delete(tbl, cond)
local sql = "DELETE FROM "..tbl.." WHERE "..self:gencond(cond)..";"
return sqlite.query(self.db, sql) == 1
end
function DBHelper:lastInsertID()
return sqlite.lastInsertID(self.db)
end
function DBHelper:close()
if self.db then
sqlite.dbclose(self.db)
end
end
function DBHelper:open()
if self.db ~= nil then
self.db = sqlite.getdb(self.db)
end
end

View File

@ -4,17 +4,27 @@ function Logger:initialize()
end
function Logger:log(msg,level)
if self.levels[level] and ulib.exists(LOG_ROOT) then
local path = LOG_ROOT..DIR_SEP..level..'.txt'
local f = io.open(path, 'a')
local text = '['..level.."]: "..msg
if f then
f:write(text..'\n')
f:close()
end
print(text)
end
end
function Logger:info(msg)
self.log(msg, "INFO")
self:log(msg, "INFO")
end
function Logger:debug(msg)
self.log(msg, "DEBUG")
self:log(msg, "DEBUG")
end
function Logger:error(msg)
self.log(msg, "ERROR")
self:log(msg, "ERROR")
end

View File

@ -1,35 +1,126 @@
--define the class
Router = Object:extends{registry = {}}
Router = BaseObject:extends{class="Router",registry = {}}
function Router:setPath(path)
self.path = path
end
function Router:initialize()
self.args = {}
self.routes = {}
end
function Router:setArgs(args)
self.args = args
--function Router:setArgs(args)
-- self.args = args
--end
--function Router:arg(name)
-- return self.args[name]
--end
function Router:infer(url)
-- a controller is like this /a/b/c/d/e
-- a is controller name
-- b is action
-- c,d,e is parameters
-- if user dont provice the url, try to infer it
-- from the REQUEST
url = url or REQUEST.query.r
url = std.trim(url,"/")
local args = explode(url,"/")
local data = {
name = 'index',
action = 'index',
args = {}
}
if args and #args > 0 and args[1] ~= "" then
data.name = args[1]
if args[2] then data.action = args[2] end
for i = 3, #args do table.insert( data.args, args[i] ) end
end
self:log('Controller: '..JSON.encode(data))
-- find the controller class and init it
local controller_name = firstToUpper(data.name).."Controller"
local controller_path = self.path.."."..controller_name
-- require the controller module
-- ignore the error
pcall(require, controller_path)
--require(controller_path)
if not _G[controller_name] then
data.controller = NotfoundController:new{ registry = self.registry }
data.args = {controller_name}
data.action = "index"
data.name = "notfound"
else
data.controller = _G[controller_name]:new{ registry = self.registry }
if not data.controller[data.action] then
data.args = {data.action}
data.action = "actionnotfound"
end
end
return data
end
function Router:arg(name)
return self.args[name]
end
function Router:getController(url)
-- TODO
end
function Router:delegate()
-- TODO
local views = {}
local data = self:infer()
views.__main__ = self:call(data)
if not views.__main__ then return end
-- get all visible routes
local routes = self:visibleRoutes(data.name.."/"..data.action)
for k, v in pairs(routes) do
data = self:infer(v)
views[k] = self:call(data)
end
-- now require the main page to put the view
local fn, e = loadscript(WWW_ROOT..DIR_SEP.."index.ls")
html()
if fn then
local r,o = pcall(fn, views)
if not r then
self:error(o)
end
else
self:error(e)
end
end
function Router:visibleRoutes(url)
local list = {}
--self:log("comparing "..url)
for k,v in pairs(self.routes) do
if v.visibility == "ALL" then
list[k] = v.url
elseif v.visibility.routes then
if v.visibility.shown == true or v.visibility.shown == nil then
if v.visibility.routes[url] then
list[k] = v.url
end
else
if not v.visibility.routes[url] then
list[k] = v.url
end
end
end
end
return list
end
function Router:setInitRoute(name, url, visibility)
function Router:call(data)
local obj = data.controller[data.action](data.controller,table.unpack(data.args))
if obj then
data.controller.template:setView(data.action, data.name)
return data.controller.template
else
return false
end
end
function Router:route(name, url, visibility)
self.routes[name] = {
url = url,
url = std.trim(url,"/"),
visibility = visibility
}
end

View File

@ -1,21 +1,37 @@
-- This is the base model class
require("OOP")
-- create class
Template = Object:extends{registry = {}}
Template = BaseObject:extends{class="Template",registry = {}}
function Template:initialize()
self.vars = {}
end
function Template:set(k, v, ow)
if not self.vars[k] or (self.vars[k] and ow) then
self.vars[k] = v
end
end
function Template:remove(k)
self.vars[k] = nil
end
-- infer view path
function setView(name, controller)
function Template:setView(name, controller)
self.path = VIEW_ROOT..DIR_SEP..self.registry.layout..DIR_SEP..controller..DIR_SEP..name..".ls"
if ulib.exists(self.path) then
else
self:error("View not found: "..self.path)
end
end
-- render the page
function render()
function Template:render()
local fn, e = loadscript(self.path)
if fn then
local r,o = pcall(fn, self.vars)
if not r then
self:error(o)
end
else
self:error(e)
end
end

View File

@ -1,11 +1,18 @@
require("OOP")
require("sqlite")
ulib = require("ulib")
require(BASE_FRW.."silk.BaseObject")
require(BASE_FRW.."silk.DBHelper")
require(BASE_FRW.."silk.Router")
require(BASE_FRW.."silk.BaseController")
require(BASE_FRW.."silk.BaseModel")
require(BASE_FRW.."silk.Logger")
require(BASE_FRW.."silk.Template")
function Object:extends(o)
return self:inherit(o)
HEADER_FLAG = false
function html()
if not HEADER_FLAG then
std.chtml(SESSION)
HEADER_FLAG = true
end
end

View File

@ -0,0 +1,5 @@
<?lua
local args = {...}
echo("add page : <br/>")
echo(JSON.encode(args))
?>

View File

@ -0,0 +1,5 @@
<?lua
local args = {...}
echo("edit page <br/>")
echo(JSON.encode(args))
?>

View File

@ -0,0 +1,4 @@
<?lua
local args = {...}
echo(JSON.encode(args))
?>