1
0
mirror of https://github.com/lxsang/antd-web-apps synced 2024-11-20 02:18:20 +01:00

WIP: make code compatible with new SILK API
All checks were successful
gitea-sync/antd-web-apps/pipeline/head This commit looks good

This commit is contained in:
DanyLE 2023-04-26 18:51:03 +02:00
parent 93b6ca18ad
commit a76942f2f3
60 changed files with 1527 additions and 2845 deletions

View File

@ -1,12 +1,13 @@
BUILDDIR?=./build BUILDDIR?=./build
PROJS?=grs info blog os doc talk get PROJS?=grs blog talk get info
copyfiles = index.ls mimes.json # info blog doc talk get
copyfiles = layout.ls index.lua mimes.json
main: copy main: copy
for f in $(PROJS); do BUILDDIR=$(BUILDDIR)/"$${f}" make -C "$${f}" ; done for f in $(PROJS); do BUILDDIR=$(BUILDDIR)/"$${f}" make -C "$${f}" ; done
copy: copy:
cp -rfv $(copyfiles) $(BUILDDIR) cp -rfv $(copyfiles) $(BUILDDIR)
cp -rv silk $(BUILDDIR) # cp -rv silk $(BUILDDIR)
ar: ar:
-[ -d /tmp/antd_web_apps ] && rm -r /tmp/antd_web_apps -[ -d /tmp/antd_web_apps ] && rm -r /tmp/antd_web_apps
@ -19,4 +20,3 @@ ar:
clean: clean:
-for f in $(PROJS); do rm -r $(BUILDDIR)/"$${f}"; done -for f in $(PROJS); do rm -r $(BUILDDIR)/"$${f}"; done
-for f in $(copyfiles); do rm -r $(BUILDDIR)/"$${f}"; done -for f in $(copyfiles); do rm -r $(BUILDDIR)/"$${f}"; done
-rm -r $(BUILDDIR)/silk

View File

@ -1,345 +0,0 @@
local doclassify = {}
local st = require("stmr")
doclassify.bow = function(data, stopwords)
-- first step get a table of worlds that contain
-- world: occurences
local bag = {}
for w in data:gmatch('%w+') do
local word = w:lower()
if not stopwords[word] then
word = st.stmr(word)
if bag[word] then
bag[word].count = bag[word].count + 1
else
bag[word] = {count=0, tf=0, tfidf=0.0}
bag[word].count = 1
end
end
end
-- now calculate the tf of the bag
for k,v in pairs(bag) do
bag[k].tf = math.log(1 + bag[k].count)
end
return bag
end
doclassify.len = function(table)
local cnt = 0
for k,v in pairs(table) do cnt = cnt+1 end
return cnt
end
doclassify.tfidf = function(documents)
-- now for each term in a bag, calculate
-- the inverse document frequency, which
-- is a measure of how much information
-- the word provides, that is, whether the
-- term is common or rare across all documents
local ndoc = doclassify.len(documents)
for k,bag in pairs(documents) do
-- for eacht term in bag
-- calculate its idf across all documents
for term,b in pairs(bag) do
local n = 0
for id,doc in pairs(documents) do
if doc[term] then n = n+1 end
end
--echo("term:"..term.." appears in"..n.." documents")
b.tfidf = b.tf*math.log(ndoc/n)
end
end
end
doclassify.search = function(term, documents)
local r = {}
for id, doc in pairs(documents) do
if doc[term:lower()] then
r[id] = doc[term].tfidf
end
end
return r
end
doclassify.get_vectors = function(documents)
-- get a list of vector from documents
local index = 0
local vectors = {}
local maps = {}
local terms = {}
local maxv = 0
for id in pairs(documents) do
maps[id] = {}
vectors[id] = {}
end
-- first loop, get the term
for id, doc in pairs(documents) do
for k,v in pairs(doc) do
-- get max value
if v.tfidf > maxv then
maxv = v.tfidf
end
-- get the term
if not terms[k] then
index = index + 1
terms[k] = index
end
for pid in pairs(documents) do
if not maps[pid][k] then
if id == pid then
maps[pid][k] = v.tfidf
else
maps[pid][k] = 0
end
else
if maps[pid][k] == 0 and id == pid then
maps[pid][k] = v.tfidf
end
end
end
end
end
-- reindexing the vectors
for id in pairs(documents) do
for k,v in pairs(maps[id]) do
vectors[id][terms[k]] = v
end
end
--echo("Max tfidf "..maxv.." in document #"..maxid.." of term "..term)
return vectors, maxv, index, terms
end
doclassify.similarity = function(va, vb)
-- using cosin similarity
local dotp = 0
local maga = 0
local magb = 0
for k = 1,#va do
dotp = dotp + va[k]*vb[k]
maga = maga + va[k]*va[k]
magb = magb + vb[k]*vb[k]
end
maga = math.sqrt(maga)
magb = math.sqrt(magb)
local d = 0
if maga ~= 0 and magb ~= 0 then
d = dotp/ (magb*maga)
end
return d
end
doclassify.similarities = function(v1, collection)
local similarities = {}
assert(#v1 == #(collection[1]), "Incorrect vectors size")
for i=1,#collection do
similarities[i] = doclassify.similarity(v1, collection[i])
end
return similarities
end
doclassify.mean_similarity = function(v1, v2)
assert(#v1 == #v2, "Incorrect vectors size")
local similarities = {}
for i = 1,#v1 do similarities[i] = doclassify.similarity(v1[i], v2[i]) end
return doclassify.mean(similarities)
end
doclassify.similarity_chart = function(id, vectors)
local vs = {}
local cnt = 0
local lut = {}
for k,v in pairs(vectors) do
if k ~= id then
cnt = cnt + 1
vs[cnt] = v
lut[cnt] = k
end
end
if not vs[1] then return {} end
return doclassify.similarities(vectors[id], vs), lut
end
doclassify.top_similarity = function(id, vectors, n, th)
local chart,lut = doclassify.similarity_chart(id,vectors)
--echo(JSON.encode(chart))
--echo(JSON.encode(lut))
if not lut or #lut <= 0 then return nil end
local top = {}
local j=0
local goon = true
if not th then
goon = false
end
while j < n or goon
do
local i,maxv = doclassify.argmax(chart)
top[lut[i]] = maxv
chart[i] = 0.0
j=j+1
if maxv < th and goon then
goon = false
end
end
--for j=1,n do
-- local i,maxv = doclassify.argmax(chart)
-- top[lut[i]] = maxv
-- chart[i] = 0.0
--end
return top
end
doclassify.save_vectors = function(vectors, name)
local f = io.open(name,"w")
if f == nil then return false end
for id, v in pairs(vectors) do
f:write(id)
for i=1,#v do f:write(","..v[i]) end
f:write("\n")
end
f:close()
return true
end
doclassify.save_topchart = function(vectors, name,n)
local f = io.open(name,"w")
if f == nil then return false end
for k,v in pairs(vectors) do
local top = doclassify.top_similarity(k,vectors,n, 0.1)
for a,b in pairs(top) do
f:write(k.." "..a.." "..b.."\n")
end
end
f:close()
return true
end
doclassify.kmean = function(nclass, documents, maxstep, ids)
-- now
local vectors, maxv, size = doclassify.get_vectors(documents)
-- random centroids
local centroids = {}
local old_centroids = {}
local clusters = {}
--for pid in pairs(documents) do clusters[pid] = 0 end
-- add noise to mean_vector
for i = 1,nclass do
if ids == nil then
centroids[i] = doclassify.random(size,math.floor(maxv))
else
centroids[i] = vectors[ids[i]]
end
old_centroids[i] = doclassify.zeros(size)
end
-- loop until convergence or maxstep reached
local similarity = doclassify.mean_similarity(centroids, old_centroids)
local step = maxstep
while 1.0-similarity > 1e-9 and step > 0 do
clusters = {}
--echo(JSON.encode(centroids))
for id,v in pairs(vectors) do
local similarities = doclassify.similarities(v, centroids)
--echo(JSON.encode(similarities))
local cluster, maxvalue = doclassify.argmax(similarities)
--echo("doc #"..id.." is in clusters #"..cluster.." max value is "..maxvalue)
clusters[id] = cluster
end
-- storing the old centroids
old_centroids = centroids
-- calculate new centroids
local new_centroids = {}
for class in pairs(centroids) do
local cnt = 0
local cvectors = {}
for id,v in pairs(vectors) do
if clusters[id] == class then
cnt = cnt + 1
cvectors[cnt] = v
end
end
new_centroids[class] = doclassify.mean_vector(cvectors, size)
end
centroids = new_centroids
--echo(JSON.encode(centroids))
--echo(JSON.encode(old_centroids))
similarity = doclassify.mean_similarity(centroids, old_centroids)
echo("step #"..step..", similarity "..similarity)
step = step - 1
end
local results = {}
for i = 1,nclass do
local list = {}
local cnt = 0
for id,c in pairs(clusters) do
if c == i then
cnt = cnt + 1
list[cnt] = id
end
end
results[i] = list
end
return results, clusters, centroids
end
doclassify.zeros = function(n)
local vector = {}
for i = 1,n do vector[i] = 0.0 end
return vector
end
doclassify.random = function(n,maxv)
local vector = {}
for i=1,n do
vector[i] = math.random() + math.random(0, maxv)
end
return vector
end
doclassify.sum = function(v)
local sum = 0.0
for i=1,#v do sum = sum + v[i] end
return sum
end
doclassify.mean = function(v)
return doclassify.sum(v)/#v
end
doclassify.mean_vector = function(vectors, size)
local means = doclassify.zeros(size)
if not vectors or #vectors == 0 then return means end
--local size = #(vectors[1])
local times = 0
for k,v in pairs(vectors) do
for i=1,#v do means[i] = means[i] + v[i] end
times = times + 1
end
for i = 1,size do means[i] = means[i]/times end
return means
end
doclassify.argmin = function(v)
local minv = 0.0
local mini = 0.0
for i = 1,#v do
if v[i] <= minv then
mini = i
minv = v[i]
end
end
--echo("min index"..mini.." val "..minv)
return mini, minv
end
doclassify.argmax = function(v)
local maxv = 0.0
local maxi = 0.0
for i = 1,#v do
if v[i] >= maxv then
maxi = i
maxv = v[i]
end
end
return maxi,maxv
end
return doclassify

View File

@ -1,29 +0,0 @@
local gettext = {}
require("sqlite")
gettext.get = function(q)
local db = require("os.libs.dbmodel").get("mrsang","blogs",nil)
if not db then return nil end
local exp = {["="] =q}
local cond = {
exp = exp,
fields = {"id", "content"}
}
local data, sort = db:find(cond)
db:close()
if not data or #data == 0 then return nil end
--for k,v in pairs(data) do
-- data[k].content = bytes.__tostring(std.b64decode(data[k].content)):gsub("%%","%%%%")
--end
return data
end
gettext.stopwords = function(ospath)
--local ospath = require("fs/vfs").ospath(path)
local words = {}
for line in io.lines(ospath) do
words[line] = true
end
return words
end
return gettext

View File

@ -1,151 +0,0 @@
i
me
my
myself
we
our
ours
ourselves
you
your
yours
yourself
yourselves
he
him
his
himself
she
her
hers
herself
it
its
itself
they
them
their
theirs
themselves
what
which
who
whom
this
that
these
those
am
is
are
was
were
be
been
being
have
has
had
having
do
does
did
doing
a
an
the
and
but
if
or
because
as
until
while
of
at
by
for
with
about
against
between
into
through
during
before
after
above
below
to
from
up
down
in
out
on
off
over
under
again
further
then
once
here
there
when
where
why
how
all
any
both
each
few
more
most
other
some
such
no
nor
not
only
own
same
so
than
too
very
s
t
can
will
just
don
should
now
a
b
c
d
e
f
g
h
i
j
k
l
m
n
o
p
q
w
r
s
t
x
y
z

View File

@ -1,50 +0,0 @@
local path = require("fs/vfs").ospath("home://aiws/blog-clustering")
local gettext = loadfile(path.."/gettext.lua")()
local cluster = loadfile(path.."/cluster.lua")()
local refresh = false
local file = "/home/mrsang/test.csv"
if refresh then
local data = gettext.get({publish=1})
local documents = {}
if data then
local sw = gettext.stopwords("home://aiws/blog-clustering/stopwords.txt")
for k,v in pairs(data) do
local bag = cluster.bow(data[k].content, sw)
documents[data[k].id] = bag
end
cluster.tfidf(documents)
--local v = cluster.search("arm", documents)
--echo(JSON.encode(v))
local vectors, maxv, size = cluster.get_vectors(documents)
local s = cluster.save_topchart(vectors,file, 3)
if s then echo("file saved") else echo("error save file") end
--echo(JSON.encode(r))
--r = cluster.similarity(vectors["14"],vectors["16"])
--echo("Similarity "..r)
--local c,l = cluster.kmean(3, documents, 10)
--echo(JSON.encode(c))
--echo(JSON.encode(l))
else
echo("Data missing")
end
else
local f = io.open(file,"r")
local result = {}
for line in f:lines() do
local arr = {}
local cnt = 0
for i in line:gmatch( "%S+") do
cnt = cnt + 1
arr[cnt] = i
end
if not result[arr[1]] then result[arr[1]] = {} end
result[arr[1]][arr[2]] = tonumber(arr[3])
end
f:close()
echo(JSON.encode(result))
--local r = cluster.top_similarity("2",vectors, 3)
--echo(JSON.encode(r))
end

1135
blog/assets/afx.css Normal file

File diff suppressed because it is too large Load Diff

1
blog/assets/afx.js Normal file

File diff suppressed because one or more lines are too long

View File

@ -260,10 +260,43 @@ button {
white-space: -o-pre-wrap; /* Opera 7 */ white-space: -o-pre-wrap; /* Opera 7 */
word-wrap: break-word; /* Internet Explorer 5.5+ */ word-wrap: break-word; /* Internet Explorer 5.5+ */
} }
#container .blogentry a { .search-result {
color: #24292e;
}
.search-result ul {
list-style: none;
margin: 0;
padding: 0;
}
.search-result ul li{
margin: 0;
}
.search-result ul li b {
color: #878887;
}
.search-result ul li p.title
{
font-size: 16px;
}
.search-result ul li p.preview {
margin: 0;
padding: 0;
padding-left: 20px;
}
#container .blogentry a,
.search-result a {
text-decoration: none; text-decoration: none;
color: #3170b2; color: #3170b2;
} }
.search-result h2 {
font-size: 18px;
text-align: left;
padding-bottom: 0.3em;
border-bottom: 1px solid #eaecef;
margin-bottom: 16px;
font-weight: 600;
line-height: 1.25;
}
#container .blogentry hr { #container .blogentry hr {
display: block; display: block;
height: 1px; height: 1px;
@ -330,6 +363,9 @@ button {
display: block; display: block;
margin: 0 auto; margin: 0 auto;
} }
form.search-form {
display: contents;
}
input.search-box { input.search-box {
flex: 1; flex: 1;
padding: 0; padding: 0;

View File

@ -6,6 +6,34 @@ BaseController:subclass(
} }
) )
local tools = {}
tools.sum = function(v)
local sum = 0.0
for i=1,#v do sum = sum + v[i] end
return sum
end
tools.mean = function(v)
return tools.sum(v)/#v
end
tools.argmax = function(v)
local maxv = 0.0
local maxi = 0.0
for i = 1,#v do
if v[i] >= maxv then
maxi = i
maxv = v[i]
end
end
return maxi,maxv
end
tools.cmp = function(a,b)
return a[2] > b[2]
end
function PostController:index(...) function PostController:index(...)
return self:top(table.unpack({...})) return self:top(table.unpack({...}))
end end
@ -22,7 +50,7 @@ end
function PostController:afterof(id, limit) function PostController:afterof(id, limit)
limit = limit or POST_LIMIT limit = limit or POST_LIMIT
local data, order = self.blog:fetch({[">"] = {id = id}}, limit, {ctime = "ASC"}) local data, order = self.blog:fetch({["id$gt"] = tonumber(id)}, limit, { "ctime$asc"})
if not data or #order == 0 then if not data or #order == 0 then
return self:notfound("No entry found") return self:notfound("No entry found")
end end
@ -36,9 +64,68 @@ function PostController:afterof(id, limit)
return true return true
end end
function PostController:search(...)
local index_file = DB_FILE..".index.json"
local st = require("stmr")
local indexes, err_code = JSON.decodeFile(index_file)
local terms = REQUEST.q
if not err_code then
-- prepare the vectors
local docs = {}
local tid = 1
local tokens = {}
local search_vector = {}
for word in string.gmatch(terms,'%w+') do
local token = st.stmr(word:lower())
local index = indexes[token]
if index then
for id,v in pairs(index) do
if not docs[id] then
docs[id] = {}
end
docs[id][token] = v
end
tokens[tid] = token
tid = tid + 1
end
end
--echo(JSON.encode(docs))
--echo(JSON.encode(tokens))
-- now create one vector for each documents
local mean_tfidf = {}
for id,doc in pairs(docs) do
local vector = {}
for i,token in ipairs(tokens) do
if doc[token] then
vector[i] = doc[token]
else
vector[i] = 0
end
end
local data, order = self.blog:find({
where = {id = tonumber(id)},
fields = {"id", "title", "utime", "ctime", "content"}
})
if data and data[1] then
data[1].content = data[1].content:sub(1,255)
table.insert(mean_tfidf, {id, tools.mean(vector), data[1]})
end
end
table.sort(mean_tfidf, tools.cmp)
self.template:setView("search")
self.template:set("result", mean_tfidf)
self.template:set("title", "Search result")
return true
else
LOG_ERROR("Unable to parse file %s", index_file)
return self:notfound("Internal search error")
end
end
function PostController:beforeof(id, limit) function PostController:beforeof(id, limit)
limit = limit or POST_LIMIT limit = limit or POST_LIMIT
local data, order = self.blog:fetch({["<"] = {id = id}}, limit) local data, order = self.blog:fetch({["id$lt"] = tonumber(id)}, limit)
if not data or #order == 0 then if not data or #order == 0 then
return self:notfound("No entry found") return self:notfound("No entry found")
end end
@ -58,15 +145,15 @@ function PostController:list(data, order)
end end
function PostController:bytag(b64tag, limit, action, id) function PostController:bytag(b64tag, limit, action, id)
local tag = bytes.__tostring(std.b64decode(b64tag .. "==")) local tag = tostring(enc.b64decode(b64tag .. "=="))
local cond = {["LIKE"] = {tags = "%%" .. tag .. "%%"}} local cond = {["tags$like"] = "%%"..tag.."%%"}
local order = nil local order = nil
limit = limit or POST_LIMIT limit = limit or POST_LIMIT
if action == "before" then if action == "before" then
cond = {["and"] = {cond, {["<"] = {id = id}}}} cond["id$lt"] = tonumber(id)
elseif action == "after" then elseif action == "after" then
cond = {["and"] = {cond, {[">"] = {id = id}}}} cond["id$gt"] = tonumber(id)
order = {ctime = "ASC"} order = {"ctime$asc"}
end end
local data, sort = self.blog:fetch(cond, limit, order) local data, sort = self.blog:fetch(cond, limit, order)
if not data or #sort == 0 then if not data or #sort == 0 then
@ -93,7 +180,7 @@ function PostController:json(id)
error = false, error = false,
result = false result = false
} }
local data, order = self.blog:fetch({["="] = {id = id}}) local data, order = self.blog:fetch({id = tonumber(id)})
if not data or #order == 0 then if not data or #order == 0 then
obj.error = "No data found" obj.error = "No data found"
else else
@ -126,7 +213,7 @@ function PostController:json(id)
end end
function PostController:id(pid) function PostController:id(pid)
local data, order = self.blog:fetch({["="] = {id = pid}}) local data, order = self.blog:fetch({id = tonumber(pid)})
if not data or #order == 0 then if not data or #order == 0 then
return self:notfound("No post found") return self:notfound("No post found")
end end
@ -149,7 +236,8 @@ function PostController:id(pid)
self.template:set("similar_posts", similar_posts) self.template:set("similar_posts", similar_posts)
self.template:set("render", true) self.template:set("render", true)
self.template:set("tags", data.tags) self.template:set("tags", data.tags)
self.template:set("url", HTTP_ROOT .. "/post/id/" .. pid) self.template:set("url", string.format(HTTP_ROOT .. "/post/id/%d",pid))
-- self.template:set("url", string.format("https://blog.lxsang.me/post/id/%d",pid))
self.template:setView("detail") self.template:setView("detail")
return true return true
end end
@ -168,7 +256,7 @@ function PostController:actionnotfound(...)
end end
function PostController:graph_json(...) function PostController:graph_json(...)
local nodes = self.blog:find({exp= { ["="] = { publish = 1}}, fields = {"id", "title"}}) local nodes = self.blog:find({ where = {publish = 1}, fields = {"id", "title"}})
local output = { error = false, result = false } local output = { error = false, result = false }
local lut = {} local lut = {}
std.json() std.json()
@ -195,7 +283,7 @@ function PostController:graph_json(...)
else else
key = v.sid..v.pid key = v.sid..v.pid
end end
key = std.sha1(key) key = enc.sha1(key)
if not lut[key] then if not lut[key] then
output.result.links[i] = link output.result.links[i] = link
i = i + 1 i = i + 1
@ -212,40 +300,3 @@ function PostController:graph(...)
self.template:set("d3", true) self.template:set("d3", true)
return true return true
end end
function PostController:analyse(n)
if not n then
n = 5
end
local path = WWW_ROOT..DIR_SEP.."ai"
local gettext = loadfile(path .. "/gettext.lua")()
local cluster = loadfile(path .. "/cluster.lua")()
local data = gettext.get({publish = 1})
local documents = {}
if data then
local sw = gettext.stopwords(path .. "/stopwords.txt")
for k, v in pairs(data) do
local bag = cluster.bow(data[k].content, sw)
documents[data[k].id] = bag
end
cluster.tfidf(documents)
--local v = cluster.search("arm", documents)
--echo(JSON.encode(v))
local vectors, maxv, size = cluster.get_vectors(documents)
-- purge the table
self.analytical:delete({["="] = {["1"] = 1}})
-- get similarity and put to the table
for id, v in pairs(vectors) do
local top = cluster.top_similarity(id, vectors, tonumber(n), 0.1)
for a, b in pairs(top) do
local record = {pid = id, sid = a, score = b}
self.analytical:create(record)
end
end
self.template:set("message", "Analyse complete")
else
self.template:set("message", "Cannot analyse")
end
self.template:set("title", "TFIDF-analyse")
return true
end

View File

@ -21,7 +21,7 @@ function ServiceController:sendmail()
fail("unknown request") fail("unknown request")
end end
local rq = (JSON.decodeString(REQUEST.json)) local rq = (JSON.decodeString(REQUEST.json))
local to = "mrsang@lxsang.me" local to = "mrsang@iohub.dev"
local from = "From: " .. rq.email .. "\n" local from = "From: " .. rq.email .. "\n"
local suject = "Subject: " .. rq.subject .. "\n" local suject = "Subject: " .. rq.subject .. "\n"
local content = "Contact request from:" .. rq.name .. "\n Email: " .. rq.email .. "\n" .. rq.content .. "\n" local content = "Contact request from:" .. rq.name .. "\n Email: " .. rq.email .. "\n" .. rq.content .. "\n"
@ -45,7 +45,7 @@ function ServiceController:subscribe()
end end
local rq = (JSON.decodeString(REQUEST.json)) local rq = (JSON.decodeString(REQUEST.json))
-- check if email is exist -- check if email is exist
local data = self.subscribers:find({exp = {["="] = {email = rq.email}}}) local data = self.subscribers:find({where = {email = rq.email}})
if data and #data > 0 then if data and #data > 0 then
fail("You are already/previously subscribed") fail("You are already/previously subscribed")
else else

View File

@ -9,5 +9,8 @@ BaseModel:subclass("AnalyticalModel",{
}) })
function AnalyticalModel:similarof(id) function AnalyticalModel:similarof(id)
return self:find({ exp = {["="] = {pid = id}}, order = {score = "DESC"}}) return self:find({
where = {pid = id},
order = { "score$desc"}
})
end end

View File

@ -15,28 +15,27 @@ BaseModel:subclass("BlogModel",{
}) })
function BlogModel:fetch(cnd, limit, order) function BlogModel:fetch(cnd, limit, order)
local exp = {} local filter = {
exp[1] = {["="] = { publish = 1 }} order = { "ctime$desc" },
if cnd then
exp[2] = cnd
else
end
local cond = {
exp = {["and"] = exp },
order = { ctime = "DESC" },
fields = { fields = {
"id", "title", "utime", "ctime", "utimestr", "content", "ctimestr", "rendered", "tags" "id", "title", "utime", "ctime", "utimestr", "content", "ctimestr", "rendered", "tags"
} }
} }
if limit then if limit then
cond.limit = limit filter.limit = limit
end end
if order then if order then
cond.order = order filter.order = order
end end
return self:find(cond)
filter.where = {}
if cnd then
filter.where = cnd
end
filter.where.publish = 1
return self:find(filter)
end end
function BlogModel:minid() function BlogModel:minid()

View File

@ -1,26 +1,30 @@
-- the rewrite rule for the framework -- the rewrite rule for the framework
-- should be something like this -- should be something like this
-- ^\/apps\/+(.*)$ = /apps/router.lua?r=<1>&<query> -- ^\/apps\/+(.*)$ = /apps/router.lua?r=<1>&<query>
-- some global variables -- some global variables
DIR_SEP = "/" package.path = _SERVER["LIB_DIR"].."/lua/?.lua"
require("silk.api")
-- crypto lib
enc = require("enc")
WWW_ROOT = __ROOT__.."/blog" WWW_ROOT = __ROOT__.."/blog"
-- TODO: change me
DB_FILE = "/home/dany/databases/mrsang.db"
-- add aditional paths
package.path = package.path..";"..WWW_ROOT .. '/?.lua'
DIR_SEP = "/"
if HEADER.Host then if HEADER.Host then
HTTP_ROOT= "https://"..HEADER.Host HTTP_ROOT= "https://"..HEADER.Host
else else
HTTP_ROOT = "https://blog.lxsang.me" HTTP_ROOT = "https://blog.iohub.dev"
end end
-- class path: path.to.class -- TODO remove me
BASE_FRW = "" HTTP_ROOT = HTTP_ROOT.."/next/blog"
-- class path: path.to.class CONTROLLER_ROOT = "blog.controllers"
CONTROLLER_ROOT = BASE_FRW.."blog.controllers" MODEL_ROOT = "blog.models"
MODEL_ROOT = BASE_FRW.."blog.models"
-- file path: path/to/file -- file path: path/to/file
VIEW_ROOT = WWW_ROOT..DIR_SEP.."views" VIEW_ROOT = WWW_ROOT..DIR_SEP.."views"
LOG_ROOT = WWW_ROOT..DIR_SEP.."logs" POST_LIMIT = 3
POST_LIMIT = 10
-- require needed library
require(BASE_FRW.."silk.api")
if REQUEST.r then if REQUEST.r then
REQUEST.r = REQUEST.r:gsub("%:", "/") REQUEST.r = REQUEST.r:gsub("%:", "/")
@ -29,8 +33,8 @@ end
-- registry object store global variables -- registry object store global variables
local REGISTRY = {} local REGISTRY = {}
-- set logging level -- set logging level
REGISTRY.logger = Logger:new{ levels = {INFO = false, ERROR = true, DEBUG = false}} REGISTRY.logger = Logger:new{ level = Logger.INFO}
REGISTRY.db = DBHelper:new{db="mrsang"} REGISTRY.db = DBModel:new{db=DB_FILE}
REGISTRY.layout = 'default' REGISTRY.layout = 'default'
REGISTRY.fileaccess = true REGISTRY.fileaccess = true

View File

@ -1,6 +1,7 @@
<?lua <?lua
local chat_uri="https://chat.iohub.dev/comment"
local title = __main__:get("title") local title = __main__:get("title")
local render = __main__:get("render") local render = __main__:get("render")
local url = __main__:get("url") local url = __main__:get("url")
@ -18,17 +19,20 @@
<meta charset="UTF-8"> <meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1"> <meta name="viewport" content="width=device-width, initial-scale=1">
<link rel="stylesheet" type="text/css" href="<?=HTTP_ROOT?>/rst/ubuntu-regular.css" /> <link rel="stylesheet" type="text/css" href="https://fonts.googleapis.com/css?family=Ubuntu:regular,bold&subset=Latin" />
<link rel="stylesheet" type="text/css" href="<?=HTTP_ROOT?>/rst/font-awesome.css" /> <link rel="stylesheet" type="text/css" href="https://cdnjs.cloudflare.com/ajax/libs/font-awesome/4.7.0/css/font-awesome.css" />
<link rel="stylesheet" type="text/css" href="<?=HTTP_ROOT?>/rst/afx.css" /> <link rel="stylesheet" type="text/css" href="<?=HTTP_ROOT?>/assets/afx.css" />
<link rel="stylesheet" type="text/css" href="<?=HTTP_ROOT?>/assets/style.css" /> <link rel="stylesheet" type="text/css" href="<?=HTTP_ROOT?>/assets/style.css" />
<link rel="stylesheet" type="text/css" href="<?=HTTP_ROOT?>/assets/github-markdown.css" /> <link rel="stylesheet" type="text/css" href="<?=HTTP_ROOT?>/assets/github-markdown.css" />
<link rel="stylesheet" type="text/css" href="https://chat.iohub.dev/assets/quicktalk.css" /> <link rel="stylesheet" type="text/css" href="https://chat.iohub.dev/assets/quicktalk.css" />
<script src="https://chat.iohub.dev/assets/quicktalk.js"> </script> <script src="https://chat.iohub.dev/assets/quicktalk.js"> </script>
<script src="<?=HTTP_ROOT?>/rst/afx.js"> </script> <!--link rel="stylesheet" type="text/css" href="https://app.iohub.dev/next/talk/assets/quicktalk.css" />
<script src="<?=HTTP_ROOT?>/rst/gscripts/jquery-3.4.1.min.js"> </script> <script src="https://app.iohub.dev/next/talk/assets/quicktalk.js"> </script-->
<script src="<?=HTTP_ROOT?>/assets/afx.js"> </script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/jquery/3.4.1/jquery.min.js"> </script>
<script src="<?=HTTP_ROOT?>/assets/main.js"></script> <script src="<?=HTTP_ROOT?>/assets/main.js"></script>
<?lua if d3 then ?> <?lua if d3 then ?>
<script src="https://cdnjs.cloudflare.com/ajax/libs/d3/6.5.0/d3.min.js" ></script> <script src="https://cdnjs.cloudflare.com/ajax/libs/d3/6.5.0/d3.min.js" ></script>
@ -41,8 +45,8 @@
<meta property="og:image" content="" /> <meta property="og:image" content="" />
<?lua if render then ?> <?lua if render then ?>
<meta name="twitter:card" content="summary" /> <meta name="twitter:card" content="summary" />
<meta name="twitter:site" content="@blog.lxsang.me" /> <meta name="twitter:site" content="@blog.iohub.dev" />
<meta name="twitter:creator" content="@lexsang" /> <meta name="twitter:creator" content="@DanyLE" />
<meta property="og:url" content="<?=url?>" /> <meta property="og:url" content="<?=url?>" />
<meta property="og:type" content="article" /> <meta property="og:type" content="article" />
<meta property="og:title" content="<?=title?>" /> <meta property="og:title" content="<?=title?>" />
@ -54,9 +58,9 @@
<script src="<?=HTTP_ROOT?>/rst/katex/katex.min.js"> </script> <script src="<?=HTTP_ROOT?>/rst/katex/katex.min.js"> </script>
<script src="<?=HTTP_ROOT?>/rst/katex/auto-render.min.js"> </script> <script src="<?=HTTP_ROOT?>/rst/katex/auto-render.min.js"> </script>
<?lua else ?> <?lua else ?>
<meta property="og:url" content="https://blog.lxsang.me" /> <meta property="og:url" content="https://blog.iohub.dev" />
<meta property="og:type" content="article" /> <meta property="og:type" content="article" />
<meta property="og:title" content="Xuan Sang LE's blog" /> <meta property="og:title" content="Dany LE's blog" />
<meta property="og:description" content="Blog Home" /> <meta property="og:description" content="Blog Home" />
<?lua end ?> <?lua end ?>
<script> <script>
@ -66,7 +70,7 @@
var options = { var options = {
target: "quick_talk_comment_thread", target: "quick_talk_comment_thread",
page: "desktop", page: "desktop",
api_uri: "https://chat.iohub.dev/comment", api_uri: "<?=chat_uri?>",
uri: "<?=url?>", uri: "<?=url?>",
author: { author: {
first: "mrsang", first: "mrsang",
@ -122,22 +126,25 @@
<div id = "top"> <div id = "top">
<div id = "navbar" class = "<?=cls?>"> <div id = "navbar" class = "<?=cls?>">
<div class = "logo"><a href = "https://lxsang.me"></a></div> <div class = "logo"><a href = "https://iohub.dev"></a></div>
<ul> <ul>
<li><i class = "fa fa-home"></i><a href="<?=HTTP_ROOT?>">Home</a></li> <li><i class = "fa fa-home"></i><a href="<?=HTTP_ROOT?>">Home</a></li>
<?lua <?lua
if not HEADER.mobile then if not HEADER.mobile then
?> ?>
<li > <i class = "fa fa-globe"></i><a href = "/post/graph">Explore</a></li> <li > <i class = "fa fa-globe"></i><a href = "<?=HTTP_ROOT?>/post/graph">Explore</a></li>
<li> <i class = "fa fa-paper-plane"></i><a href="#" onclick="subscribe('<?=HTTP_ROOT?>')">Subscribe</a></li> <li> <i class = "fa fa-paper-plane"></i><a href="#" onclick="subscribe('<?=HTTP_ROOT?>')">Subscribe</a></li>
<?lua end ?> <?lua end ?>
<li ><i class = "fa fa-address-card"></i><a href="https://info.lxsang.me" >Portfolio</a></li> <li ><i class = "fa fa-address-card"></i><a href="https://info.iohub.dev" >Portfolio</a></li>
<li><i class = "fa fa-envelope"></i><a href="#" onclick="mailtoMe('<?=HTTP_ROOT?>')" >Contact</a></li> <li><i class = "fa fa-envelope"></i><a href="#" onclick="mailtoMe('<?=HTTP_ROOT?>')" >Contact</a></li>
</ul> </ul>
<?lua <?lua
if not HEADER.mobile then if not HEADER.mobile then
?> ?>
<input type = "text" class = "search-box"></input> <form class="search-form" method="get" action="<?=HTTP_ROOT?>/post/search">
<input type = "text" class = "search-box" name="q"></input>
<input type="submit" hidden ></input>
</form>
<div class= "search-icon"></div> <div class= "search-icon"></div>
<?lua <?lua
end end

View File

@ -14,9 +14,9 @@
local atags = {} local atags = {}
local i = 1 local i = 1
for tag in data.tags:gmatch(",*([^,]+)") do for tag in data.tags:gmatch(",*([^,]+)") do
tag = std.trim(tag, " ") tag = ulib.trim(tag, " ")
if tag ~= "" then if tag ~= "" then
local b64tag = std.b64encode(tag) local b64tag = enc.b64encode(tag)
atags[i] = '<a href = "'..HTTP_ROOT..'/post/bytag/'..b64tag:gsub("=","")..'/'..POST_LIMIT..'">'..tag.."</a>" atags[i] = '<a href = "'..HTTP_ROOT..'/post/bytag/'..b64tag:gsub("=","")..'/'..POST_LIMIT..'">'..tag.."</a>"
i = i+ 1 i = i+ 1
end end

View File

@ -23,16 +23,16 @@
local atags = {} local atags = {}
local i = 1 local i = 1
for tag in data.tags:gmatch(",*([^,]+)") do for tag in data.tags:gmatch(",*([^,]+)") do
tag = std.trim(tag, " ") tag = ulib.trim(tag, " ")
if tag ~= "" then if tag ~= "" then
local b64tag = std.b64encode(tag) local b64tag = enc.b64encode(tag)
atags[i] = '<a href = "'..HTTP_ROOT..'/post/bytag/'..b64tag:gsub("=","")..'/'..POST_LIMIT..'">'..tag.."</a>" atags[i] = '<a href = "'..HTTP_ROOT..'/post/bytag/'..b64tag:gsub("=","")..'/'..POST_LIMIT..'">'..tag.."</a>"
i = i+ 1 i = i+ 1
end end
end end
echo(table.concat(atags, ", ")) echo(table.concat(atags, ", "))
local url = HTTP_ROOT.."/post/id/"..data.id local url = HTTP_ROOT.."/post/id/"..string.format("%d",data.id)
local old_url = HTTP_ROOT.."/r:id:"..data.id local old_url = HTTP_ROOT.."/r:id:"..string.format("%d",data.id)
?> ?>
</span> </span>
<!--div class="fb-like" data-href="<?=old_url?>" data-layout="button_count" data-action="like" data-size="small" data-show-faces="true" data-share="true"></div--> <!--div class="fb-like" data-href="<?=old_url?>" data-layout="button_count" data-action="like" data-size="small" data-show-faces="true" data-share="true"></div-->
@ -58,7 +58,7 @@
end end
if title then if title then
echo(content:sub(0, b)) echo(content:sub(0, b))
echo("<a class = 'title_link' href='"..HTTP_ROOT.."/post/id/"..data.id.."'>"..title.."</a>") echo("<a class = 'title_link' href='"..url.."'>"..title.."</a>")
echo(content:sub(c)) echo(content:sub(c))
else else
echo(content) echo(content)
@ -67,18 +67,18 @@
</div> </div>
<div class = "detail"> <div class = "detail">
<span></span> <span></span>
<?='<a href="'..HTTP_ROOT..'/post/id/'..data.id..'" ></a>'?> <?='<a href="'..url..'" ></a>'?>
<span></span> <span></span>
</div> </div>
</div> </div>
</div> </div>
<?lua <?lua
end end
local beforelk = HTTP_ROOT.."/post/beforeof/"..first_id.."/"..POST_LIMIT local beforelk = HTTP_ROOT.."/post/beforeof/"..string.format("%d",first_id).."/"..POST_LIMIT
local afterlk = HTTP_ROOT.."/post/afterof/"..last_id.."/"..POST_LIMIT local afterlk = HTTP_ROOT.."/post/afterof/"..string.format("%d",last_id).."/"..POST_LIMIT
if action == "bytag" or action == "search" then if action == "bytag" or action == "search" then
beforelk = HTTP_ROOT.."/post/"..action.."/"..query.."/"..POST_LIMIT.."/before/"..first_id beforelk = HTTP_ROOT.."/post/"..action.."/"..query.."/"..POST_LIMIT.."/before/"..string.format("%d",first_id)
afterlk = HTTP_ROOT.."/post/"..action.."/"..query.."/"..POST_LIMIT.."/after/"..last_id afterlk = HTTP_ROOT.."/post/"..action.."/"..query.."/"..POST_LIMIT.."/after/"..string.format("%d",last_id)
end end
?> ?>
<div class = "time-travel"> <div class = "time-travel">

View File

@ -0,0 +1,17 @@
<div class="search-result">
<h2>Posts matched for query: <?=REQUEST.q?></h2>
<ul>
<?lua
for i,v in ipairs(result) do
?>
<li>
<p class="title">
<b>Score <?=string.format("%.3f",v[2])?></b> <a href="<?=HTTP_ROOT?>/post/id/<?=v[3].id?>"><?=v[3].title?></a>
</p>
<p class="preview">
<?=v[3].content?>...
</p>
</li>
<?lua end ?>
</ul>
</div>

View File

@ -1,7 +1,8 @@
BaseController:subclass("IndexController") BaseController:subclass("IndexController")
function IndexController:index(...) function IndexController:index(...)
local file = io.open("/home/mrsang/doc/library.md", "r") -- TODO: add description to router
local file = io.open(DOC_COVER, "r")
if file then if file then
local content = "" local content = ""
local md = require("md") local md = require("md")

View File

@ -107,8 +107,8 @@ function DocController:loadTOC()
if line then if line then
local file = local file =
{ {
name = std.trim( name = ulib.trim(
std.trim(line, "#"), " "), ulib.trim(line, "#"), " "),
path = vf.path, path = vf.path,
tpath = vf.path, tpath = vf.path,
parent = section, parent = section,
@ -145,14 +145,14 @@ function DocController:index(...)
if args[1] then if args[1] then
local b64text = args[1] local b64text = args[1]
if b64text then if b64text then
local p = bytes.__tostring(std.b64decode(b64text .. "==")) local p = tostring(enc.b64decode(b64text .. "=="))
if p then if p then
toc.cpath = p toc.cpath = p
path = getpath(p, self) path = getpath(p, self)
if path and ulib.exists(path) then if path and ulib.exists(path) then
self.template:set("url", HTTP_ROOT .. '/' .. self.name .. self.template:set("url", HTTP_ROOT .. '/' .. self.name ..
'/' .. '/' ..
std.b64encode(toc.cpath):gsub("=", "")) enc.b64encode(toc.cpath):gsub("=", ""))
end end
end end
end end
@ -188,7 +188,7 @@ function DocController:search(...)
local arr = explode(query, " ") local arr = explode(query, " ")
local patterns = {} local patterns = {}
for k, v in ipairs(arr) do for k, v in ipairs(arr) do
local world = std.trim(v, " ") local world = ulib.trim(v, " ")
if v and v ~= "" then if v and v ~= "" then
cmd = cmd .. " -e '" .. v .. "' " cmd = cmd .. " -e '" .. v .. "' "
table.insert(patterns, v:lower()) table.insert(patterns, v:lower())

View File

@ -3,24 +3,27 @@
-- should be something like this -- should be something like this
-- ^\/apps\/+(.*)$ = /apps/router.lua?r=<1>&<query> -- ^\/apps\/+(.*)$ = /apps/router.lua?r=<1>&<query>
-- some global variables -- some global variables
DIR_SEP = "/" package.path = _SERVER["LIB_DIR"].."/lua/?.lua"
require("silk.api")
-- crypto lib
enc = require("enc")
WWW_ROOT = __ROOT__.."/doc" WWW_ROOT = __ROOT__.."/doc"
DIR_SEP = "/"
if HEADER.Host then if HEADER.Host then
HTTP_ROOT= "https://"..HEADER.Host HTTP_ROOT= "https://"..HEADER.Host
else else
HTTP_ROOT = "https://doc.iohub.dev" HTTP_ROOT = "https://doc.iohub.dev"
end end
-- class path: path.to.class -- class path: path.to.class
BASE_FRW = "" CONTROLLER_ROOT = ."doc.controllers"
-- class path: path.to.class MODEL_ROOT = "doc.models"
CONTROLLER_ROOT = BASE_FRW.."doc.controllers"
MODEL_ROOT = BASE_FRW.."doc.models"
-- file path: path/to/file -- file path: path/to/file
VIEW_ROOT = WWW_ROOT..DIR_SEP.."views" VIEW_ROOT = WWW_ROOT..DIR_SEP.."views"
LOG_ROOT = WWW_ROOT..DIR_SEP.."logs"
POST_LIMIT = 10 -- TODO change me
-- require needed library DOC_DIR = "/home/mrsang/doc"
require(BASE_FRW.."silk.api") DOC_COVER = DOC_DIR.."/library.md"
package.path = package.path..";"..__ROOT__.."/os/libs/?.lua" package.path = package.path..";"..__ROOT__.."/os/libs/?.lua"
@ -33,7 +36,7 @@ end
-- registry object store global variables -- registry object store global variables
local REGISTRY = {} local REGISTRY = {}
-- set logging level -- set logging level
REGISTRY.logger = Logger:new{ levels = {INFO = false, ERROR = true, DEBUG = false}} REGISTRY.logger = Logger:new{ level = Logger.INFO}
REGISTRY.layout = 'default' REGISTRY.layout = 'default'
REGISTRY.fileaccess = true REGISTRY.fileaccess = true

View File

@ -76,12 +76,12 @@
<?lua <?lua
if prev_entry then if prev_entry then
echo("<a class = 'go_prev' href="..HTTP_ROOT..'/'..toc.controller..'/'..std.b64encode(prev_entry.path):gsub("=","")..'/'..prev_entry.name:gsub(" ", "_")..".md?show_toc=false".." >") echo("<a class = 'go_prev' href="..HTTP_ROOT..'/'..toc.controller..'/'..enc.b64encode(prev_entry.path):gsub("=","")..'/'..prev_entry.name:gsub(" ", "_")..".md?show_toc=false".." >")
echo(prev_entry.name) echo(prev_entry.name)
echo("</a>") echo("</a>")
end end
if next_entry then if next_entry then
echo("<a class = 'go_next' href="..HTTP_ROOT..'/'..toc.controller..'/'..std.b64encode(next_entry.path):gsub("=","")..'/'..next_entry.name:gsub(" ", "_")..".md?show_toc=false".." >") echo("<a class = 'go_next' href="..HTTP_ROOT..'/'..toc.controller..'/'..enc.b64encode(next_entry.path):gsub("=","")..'/'..next_entry.name:gsub(" ", "_")..".md?show_toc=false".." >")
echo(next_entry.name) echo(next_entry.name)
echo("</a>") echo("</a>")
end end
@ -108,12 +108,12 @@ The comment editor supports <b>Markdown</b> syntax. Your email is necessary to n
<div class = "pagenav"> <div class = "pagenav">
<?lua <?lua
if prev_entry then if prev_entry then
echo("<a class = 'go_prev' href="..HTTP_ROOT..'/'..toc.controller..'/'..std.b64encode(prev_entry.path):gsub("=","")..'/'..prev_entry.name:gsub(" ", "_")..".md?show_toc=false".." >") echo("<a class = 'go_prev' href="..HTTP_ROOT..'/'..toc.controller..'/'..enc.b64encode(prev_entry.path):gsub("=","")..'/'..prev_entry.name:gsub(" ", "_")..".md?show_toc=false".." >")
echo(prev_entry.name) echo(prev_entry.name)
echo("</a>") echo("</a>")
end end
if next_entry then if next_entry then
echo("<a class = 'go_next' href="..HTTP_ROOT..'/'..toc.controller..'/'..std.b64encode(next_entry.path):gsub("=","")..'/'..next_entry.name:gsub(" ", "_")..".md?show_toc=false".." >") echo("<a class = 'go_next' href="..HTTP_ROOT..'/'..toc.controller..'/'..enc.b64encode(next_entry.path):gsub("=","")..'/'..next_entry.name:gsub(" ", "_")..".md?show_toc=false".." >")
echo(next_entry.name) echo(next_entry.name)
echo("</a>") echo("</a>")
end end

View File

@ -7,9 +7,9 @@
local title = io.read() local title = io.read()
io.close() io.close()
file = file:gsub(map.local_path, map.vfs_path) file = file:gsub(map.local_path, map.vfs_path)
title = std.trim(std.trim(title, "#"), " ") title = ulib.trim(ulib.trim(title, "#"), " ")
echo("<div>") echo("<div>")
echo("<p class= 'result-header'><a href='"..HTTP_ROOT..'/'..controller..'/'..std.b64encode(file):gsub("=","")..'/'..title:gsub(" ", "_")..".md'>") echo("<p class= 'result-header'><a href='"..HTTP_ROOT..'/'..controller..'/'..enc.b64encode(file):gsub("=","")..'/'..title:gsub(" ", "_")..".md'>")
echo(title) echo(title)
echo("</a></p>") echo("</a></p>")
for i,content in ipairs(arr) do for i,content in ipairs(arr) do

View File

@ -20,7 +20,7 @@ gentree = function(data, controller, cpath)
selected = "class = 'selected'" selected = "class = 'selected'"
end end
end end
local markup = '<li '..selected..'>'..caret..'<a '..highlight..' href="'..HTTP_ROOT..'/'..controller..'/'..std.b64encode(data.path):gsub("=","")..'/'..data.name:gsub(" ", "_")..'.md">'..data.name.."</a>" local markup = '<li '..selected..'>'..caret..'<a '..highlight..' href="'..HTTP_ROOT..'/'..controller..'/'..enc.b64encode(data.path):gsub("=","")..'/'..data.name:gsub(" ", "_")..'.md">'..data.name.."</a>"
if data.entries then if data.entries then
markup = markup.."<ul class='nested "..active.."'>" markup = markup.."<ul class='nested "..active.."'>"
for k,v in pairs(data.entries) do for k,v in pairs(data.entries) do

View File

@ -3,7 +3,8 @@
-- should be something like this -- should be something like this
-- ^\/apps\/+(.*)$ = /apps/router.lua?r=<1>&<query> -- ^\/apps\/+(.*)$ = /apps/router.lua?r=<1>&<query>
-- some global variables -- some global variables
DIR_SEP = "/" package.path = _SERVER["LIB_DIR"].."/lua/?.lua"
require("silk.api")
WWW_ROOT = __ROOT__.."/get" WWW_ROOT = __ROOT__.."/get"
if HEADER.Host then if HEADER.Host then
HTTP_ROOT= "https://"..HEADER.Host HTTP_ROOT= "https://"..HEADER.Host
@ -11,16 +12,11 @@ else
HTTP_ROOT = "https://get.iohub.dev" HTTP_ROOT = "https://get.iohub.dev"
end end
-- class path: path.to.class -- class path: path.to.class
BASE_FRW = "" CONTROLLER_ROOT = "get.controllers"
-- class path: path.to.class MODEL_ROOT = "get.models"
CONTROLLER_ROOT = BASE_FRW.."get.controllers"
MODEL_ROOT = BASE_FRW.."get.models"
-- file path: path/to/file -- file path: path/to/file
VIEW_ROOT = WWW_ROOT..DIR_SEP.."views" VIEW_ROOT = WWW_ROOT..DIR_SEP.."views"
LOG_ROOT = WWW_ROOT..DIR_SEP.."logs"
-- require needed library
require(BASE_FRW.."silk.api")
function NotfoundController:index(...) function NotfoundController:index(...)
local args = {...} local args = {...}
@ -32,7 +28,6 @@ function NotfoundController:index(...)
local path = WWW_ROOT..DIR_SEP.."shs"..DIR_SEP..name..".sh" local path = WWW_ROOT..DIR_SEP.."shs"..DIR_SEP..name..".sh"
if ulib.exists(path) then if ulib.exists(path) then
std.header("text/plain")
std.sendFile(path) std.sendFile(path)
else else
self:error("No script found: "..path) self:error("No script found: "..path)
@ -44,8 +39,7 @@ end
-- registry object store global variables -- registry object store global variables
local REGISTRY = {} local REGISTRY = {}
-- set logging level -- set logging level
REGISTRY.logger = Logger:new{ levels = {INFO = false, ERROR = false, DEBUG = false}} REGISTRY.logger = Logger:new{ level = Logger.INFO}
REGISTRY.layout = 'default' REGISTRY.layout = 'default'
REGISTRY.fileaccess = false REGISTRY.fileaccess = false

View File

@ -16,7 +16,7 @@
</afx-hbox> </afx-hbox>
<div data-height="5"></div> <div data-height="5"></div>
<afx-hbox data-height="20" class="inputbox"> <afx-hbox data-height="20" class="inputbox">
<div data-width="45" class="label">From:</div> <div data-width="45" class="label">Email:</div>
<input data-class="data" type="text" name="email" /> <input data-class="data" type="text" name="email" />
</afx-hbox> </afx-hbox>
<div data-height="5"></div> <div data-height="5"></div>

View File

@ -1,39 +0,0 @@
/*! Generated by Font Squirrel (https://www.fontsquirrel.com) on August 6, 2017 */
@font-face {
font-family: 'Ubuntu';
src: url('resources/themes/system/fonts//ubuntu-regular-webfont.woff2') format('woff2'),
url('resources/themes/system/fonts//ubuntu-regular-webfont.woff') format('woff');
font-weight: normal;
font-style: normal;
}
@font-face {
font-family: 'Ubuntu';
src: url('resources/themes/system/fonts//ubuntu-bold-webfont.woff2') format('woff2'),
url('resources/themes/system/fonts//ubuntu-bold-webfont.woff') format('woff');
font-weight: bold;
font-style: normal;
}
@font-face {
font-family: 'Ubuntu';
src: url('resources/themes/system/fonts//ubuntu-bolditalic-webfont.woff2') format('woff2'),
url('resources/themes/system/fonts//ubuntu-bolditalic-webfont.woff') format('woff');
font-weight: bold;
font-style: italic;
}
@font-face {
font-family: 'Ubuntu';
src: url('resources/themes/system/fonts//ubuntu-italic-webfont.woff2') format('woff2'),
url('resources/themes/system/fonts//ubuntu-italic-webfont.woff') format('woff');
font-weight: normal;
font-style: italic;
}

14
index.lua Normal file
View File

@ -0,0 +1,14 @@
package.path = _SERVER["LIB_DIR"].."/lua/?.lua"
require("silk.api")
local args = {}
local argv = {}
local fn, e = loadscript(__ROOT__.."/layout.ls", args)
if fn then
local r,o = pcall(fn, table.unpack(argv))
if not r then
std.error(500, o)
end
else
std.error(500, e)
end

View File

@ -7,8 +7,13 @@ BaseController:subclass(
) )
local sectionsByCid = function(db, id) local sectionsByCid = function(db, id)
local cond = {exp = { ["and"] = {{["="] = {cid = id}}, {["= "] = {publish = 1}} }}, order = {start = "DESC"}} local data, a = db:find({
local data, a = db:find(cond) where = {
cid = id,
publish = 1
},
order = {"start$desc"}
})
return data, a return data, a
end end
@ -16,8 +21,12 @@ function IndexController:index(...)
local args = {...} local args = {...}
-- now read all the data -- now read all the data
-- get all root sections as the toc -- get all root sections as the toc
local cond = {exp = {["="] = {pid = 0}}, order = {name = "ASC"}} local data, a = self.category:find({
local data, a = self.category:find(cond) where = {
pid = 0
},
order = {"name$asc"}
})
local toc = {} local toc = {}
if not data then if not data then
return self:error("Cannot query the ToC") return self:error("Cannot query the ToC")
@ -26,8 +35,12 @@ function IndexController:index(...)
for key, cat in pairs(data) do for key, cat in pairs(data) do
cat.name = cat.name:gsub("^%d+%.", "") cat.name = cat.name:gsub("^%d+%.", "")
table.insert(toc, {cat.name, cat.id}) table.insert(toc, {cat.name, cat.id})
cond = {exp = {["="] = {pid = cat.id}}, order = {name = "ASC"}} local children, b = self.category:find({
local children, b = self.category:find(cond) where = {
pid = cat.id
},
order = {"name$asc"}
})
if children and #children > 0 then if children and #children > 0 then
for k, v in pairs(children) do for k, v in pairs(children) do
v.sections = sectionsByCid(self.sections, v.id) v.sections = sectionsByCid(self.sections, v.id)

View File

@ -28,7 +28,7 @@ function UserController:photo(...)
local prefix = data[1].photo:match("%a+://") local prefix = data[1].photo:match("%a+://")
local suffix = data[1].photo:gsub(prefix,"") local suffix = data[1].photo:gsub(prefix,"")
local path = string.format("/home/%s/", self.registry.user)..suffix local path = string.format("/home/%s/", self.registry.user)..suffix
print(path) LOG_DEBUG("Photo path: %s", path)
if ulib.exists(path) then if ulib.exists(path) then
local mime = std.mimeOf(path) local mime = std.mimeOf(path)
std.sendFile(path) std.sendFile(path)

View File

@ -3,32 +3,33 @@
-- should be something like this -- should be something like this
-- ^\/apps\/+(.*)$ = /apps/router.lua?r=<1>&<query> -- ^\/apps\/+(.*)$ = /apps/router.lua?r=<1>&<query>
-- some global variables -- some global variables
DIR_SEP = "/" package.path = _SERVER["LIB_DIR"].."/lua/?.lua"
require("silk.api")
WWW_ROOT = __ROOT__.."/info" WWW_ROOT = __ROOT__.."/info"
if HEADER.Host then if HEADER.Host then
HTTP_ROOT= "https://"..HEADER.Host HTTP_ROOT= "https://"..HEADER.Host
else else
HTTP_ROOT = "https://info.lxsang.me" HTTP_ROOT = "https://info.iohub.dev"
end end
-- class path: path.to.class -- TODO remove me
BASE_FRW = "" HTTP_ROOT = HTTP_ROOT.."/next/info"
-- class path: path.to.class
CONTROLLER_ROOT = BASE_FRW.."info.controllers" CONTROLLER_ROOT = "info.controllers"
MODEL_ROOT = BASE_FRW.."info.models" MODEL_ROOT = "info.models"
-- file path: path/to/file -- file path: path/to/file
VIEW_ROOT = WWW_ROOT..DIR_SEP.."views" VIEW_ROOT = WWW_ROOT..DIR_SEP.."views"
LOG_ROOT = WWW_ROOT..DIR_SEP.."logs"
-- require needed library
require(BASE_FRW.."silk.api")
-- registry object store global variables -- registry object store global variables
local REGISTRY = {} local REGISTRY = {}
-- set logging level -- set logging level
REGISTRY.logger = Logger:new{ levels = {INFO = false, ERROR = true, DEBUG = false}} REGISTRY.logger = Logger:new{ level = Logger.INFO}
REGISTRY.users_allowed = { phuong = true, mrsang = true, dany = true } REGISTRY.users_allowed = { phuong = true, mrsang = true, dany = true }
REGISTRY.user = "mrsang" -- TODO change me
REGISTRY.db = DBHelper:new{db=REGISTRY.user} REGISTRY.user = "dany"
REGISTRY.dbfile = "/home/"..REGISTRY.user.."/databases/"..REGISTRY.user..".db"
REGISTRY.db = DBModel:new{db=REGISTRY.dbfile}
REGISTRY.layout = 'default' REGISTRY.layout = 'default'
REGISTRY.fileaccess = true REGISTRY.fileaccess = true
@ -67,10 +68,12 @@ function NotfoundController:index(...)
self:error("404: Controller "..args[1].." not found : "..args[2]) self:error("404: Controller "..args[1].." not found : "..args[2])
return return
end end
REQUEST.r = std.trim(REQUEST.r:gsub(user, ""), "/") LOG_DEBUG("Request: %s", REQUEST.r)
REQUEST.r = ulib.trim(REQUEST.r:gsub(user, ""), "/")
if REGISTRY.db then REGISTRY.db:close() end if REGISTRY.db then REGISTRY.db:close() end
REGISTRY.user = user REGISTRY.user = user
REGISTRY.db = DBHelper:new{db=REGISTRY.user} REGISTRY.dbfile = "/home/"..REGISTRY.user.."/databases/"..REGISTRY.user..".db"
REGISTRY.db = DBModel:new{db=REGISTRY.dbfile}
REGISTRY.db:open() REGISTRY.db:open()
router:delegate() router:delegate()
end end

View File

@ -29,8 +29,8 @@
<span><?=entry.subtitle?></span> <span><?=entry.subtitle?></span>
<span class="date"> <span class="date">
<?lua <?lua
if entry["start"]:match("^20%d.*") and entry['end']:match("^20%d.*") then if tostring(entry["start"]):match("^20%d.*") and tostring(entry['end']):match("^20%d.*") then
echo(entry.start.."-"..entry['end']) echo("%d-%d",entry["start"],entry['end'])
end end
?> ?>
</span> </span>
@ -71,8 +71,8 @@
<span><?=entry.subtitle?></span> <span><?=entry.subtitle?></span>
<span class="date"> <span class="date">
<?lua <?lua
if entry["start"]:match("^20%d.*") and entry['end']:match("^20%d.*") then if tostring(entry["start"]):match("^20%d.*") and tostring(entry['end']):match("^20%d.*") then
echo(entry.start.."-"..entry['end']) echo("%d-%d",entry["start"],entry['end'])
end end
?> ?>
</span> </span>

View File

@ -3,11 +3,12 @@
<head> <head>
<script type="text/javascript" src="<?=HTTP_ROOT?>/rst/gscripts/showdown.min.js"></script> <script type="text/javascript" src="<?=HTTP_ROOT?>/rst/gscripts/showdown.min.js"></script>
<link rel="stylesheet" type="text/css" href="<?=HTTP_ROOT?>/style.css" /> <link rel="stylesheet" type="text/css" href="<?=HTTP_ROOT?>/style.css" />
<link rel="stylesheet" type="text/css" href="<?=HTTP_ROOT?>/rst/font-awesome.css" /> <link rel="stylesheet" type="text/css" href="https://cdnjs.cloudflare.com/ajax/libs/font-awesome/4.7.0/css/font-awesome.css" />
<?lua <?lua
if not toc then if not toc then
?> ?>
<link rel="stylesheet" type="text/css" href="<?=HTTP_ROOT?>/rst/ubuntu-regular.css" /> <link rel="stylesheet" type="text/css" href="https://fonts.googleapis.com/css?family=Ubuntu:regular,bold&subset=Latin" />
<?lua <?lua
end end
?> ?>

View File

@ -1,6 +1,6 @@
<div class="header_container"> <div class="header_container">
<?lua if data.photo and data.photo ~= "" and data.user ~= "mrsang" then ?> <?lua if data.photo and data.photo ~= "" and data.user ~= "mrsang" then ?>
<img src="/<?=data.user?>/user/photo"></img> <img src="<?=HTTP_ROOT?>/<?=data.user?>/user/photo"></img>
<?lua end ?> <?lua end ?>
<h1> <h1>
<span class="name"><?=data.fullname?></span> <span class="name"><?=data.fullname?></span>

View File

@ -1,7 +1,5 @@
<?lua <?lua
std.html() std.html()
require("sqlite")
local user = "mrsang"
local die = function(m) local die = function(m)
echo(m) echo(m)
debug.traceback=nil debug.traceback=nil
@ -9,9 +7,10 @@
end end
local mobilecls = "" local mobilecls = ""
if HEADER.mobile then mobilecls = "mobile" end if HEADER.mobile then mobilecls = "mobile" end
local db = require("os.libs.dbmodel").get(user,"user",nil) local db = DBModel:new{db="/home/dany/databases/mrsang.db"}
if db == nil then die("cannot get db data") end if db == nil then die("cannot get db data") end
local data, a = db:getAll() db:open()
local data, a = db:getAll("user")
db:close() db:close()
if data == nil or data[1] == nil then die("Cannot fetch user info") end if data == nil or data[1] == nil then die("Cannot fetch user info") end
data = data[1] data = data[1]
@ -22,8 +21,8 @@
<title>Hi, I'm <?=data.fullname?></title> <title>Hi, I'm <?=data.fullname?></title>
<meta charset="UTF-8"> <meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1"> <meta name="viewport" content="width=device-width, initial-scale=1">
<link rel="stylesheet" type="text/css" href="grs/ubuntu-regular.css" /> <link rel="stylesheet" type="text/css" href="https://fonts.googleapis.com/css?family=Ubuntu:regular,bold&subset=Latin">
<link rel="stylesheet" type="text/css" href="grs/font-awesome.css" /> <link rel="stylesheet" type="text/css" href="https://cdnjs.cloudflare.com/ajax/libs/font-awesome/4.7.0/css/font-awesome.css" />
<link rel="stylesheet" type="text/css" href="grs/mainsite.css" /> <link rel="stylesheet" type="text/css" href="grs/mainsite.css" />
</head> </head>
<body> <body>

View File

@ -74,5 +74,9 @@
"map":{ "map":{
"mime": "application/json", "mime": "application/json",
"binary": false "binary": false
},
"sh":{
"mime": "text/plain",
"binary": false
} }
} }

View File

@ -1,6 +0,0 @@
copyfiles = controllers libs router.lua
main:
- mkdir -p $(BUILDDIR)
cp -rfv $(copyfiles) $(BUILDDIR)
- cd $(BUILDDIR) && ln -s ../grs ./rst

View File

@ -1,33 +0,0 @@
BaseController:subclass(
"IndexController",
{
registry = {},
models = {}
}
)
function IndexController:actionnotfound(...)
return self:index(table.unpack({...}))
end
function IndexController:index(...)
html()
std.f(WWW_ROOT..DIR_SEP.."index.html")
end
function IndexController:doc(...)
local api = {
author = "Xuan Sang LE",
email = "xsang.le@gmail.com",
api_name = "AntOS API",
version = "0.2.4 a",
documents = {
vfs = HTTP_ROOT.."/VFS",
vdb = HTTP_ROOT.."/VDB",
user = HTTP_ROOT.."/user",
system = HTTP_ROOT.."/system"
}
}
result(api)
return false
end

View File

@ -1,257 +0,0 @@
BaseController:subclass(
"SystemController",
{
registry = {},
models = {}
}
)
function SystemController:actionnotfound(...)
return self:index(table.unpack({...}))
end
function SystemController:index(...)
local api = {
description = "This api handle system operations",
actions = {
["/packages"] = "Handle all operation relate to package: list, install, cache, uninstall",
["/settings"] = "Save user setting",
["/application"] = "Call a specific server side application api",
["/apigateway"] = "Gateway for executing custom server side code"
}
}
result(api)
return false
end
function SystemController:packages(...)
auth_or_die("User unauthorized. Please login")
local rq = (JSON.decodeString(REQUEST.json))
local packages = require("packages")
packages.init(rq.args.paths)
if rq ~= nil then
-- check user command here
if (rq.command == "install") then
packages.install(rq.args)
elseif rq.command == "cache" then
packages.cache(rq.args)
elseif rq.command == "list" then
packages.list(rq.args.paths)
elseif rq.command == "uninstall" then
packages.uninstall(rq.args.path)
else
fail("Uknown packages command")
end
else
fail("Uknown request")
end
end
function SystemController:settings(...)
auth_or_die("User unauthorized. Please login")
local user = SESSION.user
if user then
local ospath = require("vfs").ospath("home:///", user)
if REQUEST and REQUEST.json then
local file_path = ospath .. "/" .. ".settings.json"
local f = io.open(file_path, "w")
if f then
f:write(REQUEST.json)
f:close()
os.execute("chmod o-r "..file_path)
result(true)
else
fail("Cannot save setting")
end
else
fail("No setting founds")
end
else
fail("User not found")
end
end
function SystemController:application(...)
auth_or_die("User unauthorized. Please login")
local rq = nil
if REQUEST.json ~= nil then
rq = (JSON.decodeString(REQUEST.json))
else
rq = REQUEST
end
if rq.path ~= nil then
local pkg = require("vfs").ospath(rq.path)
if pkg == nil then
pkg = WWW_ROOT .. "/packages/" .. rq.path
--die("unkown request path:"..rq.path)
end
pkg = pkg .. "/api.lua"
if ulib.exists(pkg) then
dofile(pkg).exec(rq.method, rq.arguments)
else
fail("Uknown application handler: " .. pkg)
end
else
fail("Uknown request")
end
end
function SystemController:apigateway(...)
local args={...}
local use_ws = false
if REQUEST and REQUEST.ws == "1" then
-- override the global echo command
echo = std.ws.swrite
use_ws = true
--else
-- std.json()
end
local exec_with_user_priv = function(data)
local uid = ulib.uid(SESSION.user)
-- disable unused modules
package.loaded["silk.Router"] = nil
package.loaded["silk.BaseController"] = nil
package.loaded["silk.DBHelper"] = nil
package.loaded["silk.Template"] = nil
package.loaded["silk.api"] = nil
package.loaded["silk.Logger"] = nil
package.loaded["silk.BaseModel"] = nil
package.loaded["silk.BaseObject"] = nil
package.loaded["os.controllers.SystemController"] = nil
-- user only allowed to load module in the following paths
package.path = __api__.apiroot.."/?.lua;"..WWW_ROOT .. '/libs/?.lua'
if not ulib.setgid(uid.gid) or not ulib.setuid(uid.id) then
echo("Cannot set permission to execute the code")
return
end
local r, e
e = "{'error': 'Unknow function'}"
-- set env var
local home = ulib.home_dir(uid.id)
ulib.setenv("USER", SESSION.user, 1)
ulib.setenv("LOGNAME", SESSION.user, 1)
if home then
ulib.setenv("HOME", home, 1)
ulib.setenv("PWD", home,1)
local paths = ""
if ulib.exists(home.."/bin") then
paths = home.."/bin:"
end
if ulib.exists(home.."/.local/bin") then
paths = paths..home.."/.local/bin:"
end
local envar = ulib.getenv("PATH")
if envar then
paths = paths..envar
end
ulib.setenv("PATH", paths,1)
end
-- run the code as user
if data.code then
r, e = load(data.code)
if r then
local status, result = pcall(r)
if result then
if (status) then
echo(JSON.encode(result))
else
echo(result)
end
end
else
echo(e)
end
elseif data.path then
local ospath = require("vfs").ospath(data.path)
r, e = loadfile(ospath)
if r then
local status, result = pcall(r, data.parameters)
if result then
if (status) then
echo(JSON.encode(result))
else
echo(result)
end
end
else
echo(e)
end
else
echo(e)
end
end
if (is_auth()) then
local pid = ulib.fork()--std.pfork(HTTP_REQUEST.id)
if (pid == -1) then
echo("{'error':'Cannot create process'}")
elseif pid > 0 then -- parent
-- wait for the child exit or websocket exit
ulib.waitpid(pid, 0)
--ulib.kill(pid)
print("Parent exit")
else -- child
if use_ws then
if std.ws.enable() then
-- read header
local header = nil
-- wait until we receive request
while(not header) do header = std.ws.header() end
if header then
if header.mask == 0 then
print("Data is not masked")
std.ws.close(1012)
elseif header.opcode == std.ws.CLOSE then
print("Connection closed")
-- std.ws.close(1000)
elseif header.opcode == std.ws.TEXT then
-- read the file
local data = std.ws.read(header)
if data then
data = (JSON.decodeString(data))
exec_with_user_priv(data)
std.ws.close(1011)
else
print("Error: Invalid request")
std.ws.close(1011)
end
end
else
std.ws.close(1011)
end
else
print("Web socket is not available.")
end
else
if REQUEST.path then
exec_with_user_priv(REQUEST)
elseif REQUEST.json then
data = JSON.decodeString(REQUEST.json)
exec_with_user_priv(data)
elseif args and #args > 0 then
-- data is encoded in url safe base64
local encoded = args[1]:gsub('_', '/'):gsub('-', '+')
if #encoded % 4 == 2 then
encoded = encoded.."=="
elseif #encoded %4 == 3 then
encoded = encoded.."="
end
local decoded = std.b64decode(encoded)
data = JSON.decodeString(bytes.__tostring(decoded))
if data and data.path then
exec_with_user_priv(data)
else
fail("Unknown request")
end
else
fail("Unkown request")
end
end
print("Child exit")
ulib.kill(-1)
end
else
echo('{"error":"User unauthorized. Please login"}')
end
end

View File

@ -1,97 +0,0 @@
BaseController:subclass(
"UserController",
{
registry = {},
models = {}
}
)
function UserController:actionnotfound(...)
return self:index(table.unpack({...}))
end
function UserController:index(...)
local api = {
description = "This api handle the user authentification",
actions = {
["/auth"] = "Return user information if a user is alreay logged in",
["/login"] = "Perform a login operation",
["/logout"] = "Perform a logout operation"
}
}
result(api)
return false
end
--[[
request query: none
return:
]]
function UserController:auth(...)
auth_or_die("User unauthorized. Please login")
local user = require("uman").userinfo(SESSION.user)
result(user)
return false
end
--[[ request:
{"username":"mrsang", "password":"pass"}
return:
{} ]]
function UserController:login(...)
if REQUEST.json ~= nil then
local request = JSON.decodeString(REQUEST.json)
local r = ulib.auth(request.username,request.password)
if r == true then
local salt = utils.generate_salt(20)
local cookie = {sessionid=std.sha1(request.username..request.password..salt)} -- iotos_user = request.username
local db = sysdb();
if db == nil then return fail("Cannot setup session") end
local cond = {exp= {["="] = { sessionid = cookie.sessionid }}}
local data = db:find(cond)
--print(data)
if data == nil or data[1] == nil then
--print("insert new data")
data = {sessionid = cookie.sessionid, username=request.username, stamp=os.time(os.date("!*t"))}
else
data = data[1]
--print("Update old data")
data.stamp = os.time(os.date("!*t"))
end
if data.id == nil then
db:insert(data)
else
db:update(data)
end
db:close()
std.cjson(cookie)
SESSION.user = request.username
local user = {
result = require("uman").userinfo(request.username),
error = false
}
std.t(JSON.encode(user))
else
fail("Invalid login")
end
else
fail("Invalid request")
end
return false
end
function UserController:logout(...)
if SESSION.sessionid ~= nil and SESSION.sessionid ~= '0' then
local cookie = {sessionid='0'}
local db = sysdb()
if db ~= nil then
local cond = {["="] = { sessionid = SESSION.sessionid }}
db:delete(cond)
db:close()
end
std.cjson(cookie)
else
std.json()
end
std.t(JSON.encode({error=false,result=true}))
end

View File

@ -1,130 +0,0 @@
BaseController:subclass(
"VDBController",
{
registry = {},
models = {}
}
)
function VDBController:actionnotfound(...)
return self:index(table.unpack({...}))
end
function VDBController:index(...)
local api = {
description = "This api handle database operation",
actions = {
["/save"] = "Save a record to a table",
["/get"] = "Get all records or Get a record by id",
["/select"] = "Select records by a condition",
["/delete"] = "Delete record(s) by condition or by id"
}
}
result(api)
return false
end
function VDBController:save(...)
auth_or_die("User unauthorized. Please login")
local rq = (JSON.decodeString(REQUEST.json))
if (rq ~= nil and rq.table ~= nil) then
local model = require("dbmodel").get(SESSION.user, rq.table, rq.data)
local ret
if model == nil then
fail("Cannot get table metadata:" .. rq.table)
else
if (rq.data.id ~= nil) then
rq.data.id = tonumber(rq.data.id)
ret = model:update(rq.data)
else
ret = model:insert(rq.data)
end
model:close()
if ret == true then
result(ret)
else
fail("Cannot modify/update table " .. rq.table)
end
end
else
fail("Unknown database request")
end
return false
end
function VDBController:get(...)
auth_or_die("User unauthorized. Please login")
local rq = (JSON.decodeString(REQUEST.json))
if (rq ~= nil and rq.table ~= nil) then
local model = require("dbmodel").get(SESSION.user, rq.table, nil)
local ret
if model == nil then
fail("Cannot get table metadata:" .. rq.table)
else
if (rq.id == nil) then
ret = model:getAll()
else
ret = model:get(rq.id)
end
model:close()
result(ret)
end
else
fail("Unknown database request")
end
end
function VDBController:select(...)
auth_or_die("User unauthorized. Please login")
local rq = (JSON.decodeString(REQUEST.json))
if (rq ~= nil and rq.table ~= nil) then
local model = require("dbmodel").get(SESSION.user, rq.table, nil)
local ret
if model == nil then
fail("Cannot get table metadata:" .. rq.table)
else
if (rq.cond == nil) then
model:close()
return fail("Unknow condition")
else
ret = model:find(rq.cond)
end
model:close()
result(ret)
end
else
fail("Unknown database request")
end
end
function VDBController:delete(...)
auth_or_die("User unauthorized. Please login")
local rq = (JSON.decodeString(REQUEST.json))
if (rq ~= nil and rq.table ~= nil) then
local model = require("dbmodel").get(SESSION.user, rq.table, nil)
local ret
if model == nil then
fail("Cannot get table metadata:" .. rq.table)
else
if (rq.id == nil) then
if (rq.cond) then
ret = model:delete(rq.cond)
model:close()
else
model:close()
return fail("Unknow element to delete")
end
else
ret = model:deleteByID(rq.id)
model:close()
end
if ret then
result(ret)
else
fail("Querry error or database is locked")
end
end
else
fail("Unknown database request")
end
end

View File

@ -1,222 +0,0 @@
BaseController:subclass(
"VFSController",
{
registry = {},
models = {}
}
)
function VFSController:actionnotfound(...)
return self:index(table.unpack({...}))
end
function VFSController:index(...)
local api = {
description = "This api handle file operations",
actions = {
["/fileinfo"] = "Get file information",
["/exists"] = "Check if file exists",
["/delete"] = "Delete a file",
["/get"] = "Get a file content",
["/mkdir"] = "Create directory",
["/move"] = "Move file to a new destination",
["/publish"] = "Share a file to all users",
["/scandir"] = "List all files and folders",
["/write"] = "Write data to file",
["/shared"] = "Get shared file content"
}
}
result(api)
return false
end
function VFSController:fileinfo(...)
auth_or_die("User unauthorized. Please login")
local vfspath = (JSON.decodeString(REQUEST.json)).path
local r, m = require("vfs").fileinfo(vfspath)
if r then
result(m)
else
fail(m)
end
return false
end
function VFSController:exists(...)
auth_or_die("User unauthorized. Please login")
local vfs = require("vfs")
local rq = (JSON.decodeString(REQUEST.json))
if rq ~= nil then
result(vfs.exists(rq.path))
else
fail("Uknown request")
end
return false
end
function VFSController:delete(...)
auth_or_die("User unauthorized. Please login")
local vfs = require("vfs")
local rq = (JSON.decodeString(REQUEST.json))
if rq ~= nil then
local r, e = vfs.delete(rq.path)
if r then
result(r)
else
fail(e)
end
else
fail("Uknown request")
end
return false
end
function VFSController:get(...)
local args = {...}
local uri = implode(args, "/")
auth_or_die("User unauthorized. Please login")
local vfsfile = utils.decodeURI(uri)
local r, m = require("vfs").checkperm(vfsfile, "read")
if r then
std.sendFile(m)
else
fail(m)
end
return false
end
function VFSController:mkdir(...)
auth_or_die("User unauthorized. Please login")
local rq = (JSON.decodeString(REQUEST.json))
if rq ~= nil then
local r, m = require("vfs").mkdir(rq.path)
if r then
result(r)
else
fail(m)
end
else
fail("Uknown request")
end
return false
end
function VFSController:move(...)
auth_or_die("User unauthorized. Please login")
local rq = (JSON.decodeString(REQUEST.json))
if rq ~= nil then
local r, m = require("vfs").move(rq.src, rq.dest)
if r then
result(r)
else
fail(m)
end
else
fail("Uknown request")
end
return false
end
function VFSController:publish(...)
auth_or_die("User unauthorized. Please login")
local rq = (JSON.decodeString(REQUEST.json))
if rq ~= nil then
local p = nil
if rq.publish then
p = require("vfs").ospath(rq.path)
else
p = require("shared").ospath(rq.path)
end
local user = SESSION.user
local uid = ulib.uid(user)
local st = ulib.file_stat(p)
if uid.id ~= st.uid then
die("Only the owner can share or unshare this file")
end
local entry = {sid = std.sha1(p), user = SESSION.user, path = p, uid = uid.id}
local db = require("dbmodel").get("sysdb", "shared", entry)
if db == nil then
die("Cannot get system database")
end
local cond = nil
if rq.publish then
cond = {exp = {["="] = {path = p}}}
local data = db:find(cond)
if data == nil or data[0] == nil then
-- insert entry
db:insert(entry)
end
else
cond = {["="] = {sid = rq.path}}
db:delete(cond)
end
db:close()
result(entry.sid)
else
fail("Uknown request")
end
return false
end
function VFSController:scandir(...)
auth_or_die("User unauthorized. Please login")
local rq = JSON.decodeString(REQUEST.json)
local vfspath = rq.path
local r = require("vfs").readDir(vfspath)
if r == nil then
fail("Resource not found: " .. rq.path)
else
--print(JSON.encode(readDir(ospath, vfspath)))
result(r)
end
return false
end
function VFSController:upload(...)
auth_or_die("User unauthorized. Please login")
local vfs = require("vfs")
if REQUEST and REQUEST.path then
local r, m = require("vfs").upload(REQUEST.path)
if r then
result(r)
else
self:error(m)
fail(m)
end
else
fail("Invalid query")
end
return false
end
function VFSController:write(...)
auth_or_die("User unauthorized. Please login")
local rq = (JSON.decodeString(REQUEST.json))
if rq ~= nil then
local r, m = require("vfs").write(rq.path, rq.data)
if r then
result(r)
else
fail(m)
end
else
fail("Uknown request")
end
return false
end
function VFSController:shared(sid)
require("shared").get(sid)
end

View File

@ -1,66 +0,0 @@
require("sqlite")
local TUNNEL_KEYCHAIN = __api__.tmpdir.."/channels/antunnel_keychain"
function fail(msg)
std.custom_header("Connection","close")
std.json()
std.t(JSON.encode({error=msg}))
end
function result(obj)
std.custom_header("Connection","close")
std.json()
std.t(JSON.encode({result=obj, error=false}))
end
function die (msg)
fail(msg)
debug.traceback=nil
error("Permission denied")
return false
end
-- check if the sysdb is create, otherwise create the table
function sysdb()
local meta = {}
meta.sessionid = ""
meta.username = ""
meta.stamp = 0
return require("dbmodel").get("sysdb", "sessions", meta)
end
function is_auth()
local sessionid = nil
if SESSION.sessionid and SESSION.sessionid ~= '0' then
sessionid = SESSION.sessionid
-- should be used only by API call
elseif REQUEST.sessionid and REQUEST.sessionid ~= '0' then
sessionid = REQUEST.sessionid
elseif REQUEST.access_token and REQUEST.access_token ~= '0' then
sessionid = REQUEST.access_token
end
if sessionid == nil then
return false
end
-- query session id from database
local db = sysdb()
if db == nil then return false end
local cond = {exp= {["="] = { sessionid = sessionid }}}
local data = db:find(cond)
--print(JSON.encode(data))
db:close()
if data == nil or data[1] == nil then return die("No user data found") end
-- next time check the stamp
SESSION.user = data[1].username
local f = io.open(TUNNEL_KEYCHAIN, "w")
if f then
f:write(sessionid..SESSION.user)
f:close()
end
return true
end
function auth_or_die(msg)
if(is_auth() == false) then
die(msg)
end
end

View File

@ -1,20 +0,0 @@
local model = {}
model.get = function(name, tbl, data)
local db = DBModel:new{db = name, name=tbl}
db:open()
if db:available() then return db end
if data == nil then return nil end
local meta = {}
--print(JSON.encode(data))
for k,v in pairs(data) do
if type(v) == "number" or type(v) == "boolean" then
meta[k] = "NUMERIC"
else
meta[k] = "TEXT"
end
end
db:createTable(meta)
return db
end
return model

View File

@ -1,125 +0,0 @@
local packages={}
local vfs = require("vfs")
local uid = ulib.uid(SESSION.user)
packages._cache = function(y)
local p = vfs.ospath(y).."/packages.cache"
if y:find("^os:/") then
p = __api__.tmpdir.."/packages.cache"
end
local f = io.open(p, "w")
local has_cache = false
local i = 1
local meta = {}
if f then
local files = vfs.readDir(y)
for k,v in pairs(files) do
if v.type == "dir" then
local f1 = io.open(vfs.ospath(v.path.."/package.json"))
if f1 then
local name = std.basename(v.path)
local mt = JSON.decodeString(f1:read("*all"))
mt.path = v.path
meta[i] ='"'..name..'":'..JSON.encode(mt)
i = i+1
f1:close()
has_cache = true;
end
end
end
f:write(table.concat(meta, ","))
f:close()
if has_cache == false then
ulib.delete(p);
end
end
end
-- we will change this later
packages.list = function(paths)
std.json()
std.t("{\"result\" : { ")
local first = true
--std.f(__ROOT__.."/system/packages.json")
for k,v in pairs(paths) do
local osp = vfs.ospath(v.."/packages.cache")
if v:find("^os:/") then
osp = __api__.tmpdir.."/packages.cache"
end
if ulib.exists(osp) == false then
packages._cache(v)
end
if ulib.exists(osp) then
if first == false then
std.t(",")
else
first = false
end
std.f(osp)
end
end
std.t("}, \"error\":false}")
end
-- generate the packages caches
packages.cache = function(args)
-- perform a packages caches
for x,y in pairs(args.paths) do
packages._cache(y)
end
result(true)
end
-- install a function from zip file
packages.install = function(args)
local path = vfs.ospath(args.dest)
local zip = vfs.ospath(args.zip)
if(ulib.exists(path) == false) then
-- create directory if not exist
ulib.mkdir(path)
-- change permission
ulib.chown(path, uid.id, uid.gid)
end
-- extract the zip file to it
if(ulib.unzip(zip, path)) then
-- read metadata
local meta = JSON.decodeFile(path.."/metadata.json")
meta.path = args.dest
meta.scope = "user"
local f=io.open(path.."/package.json","w")
if f then
f:write(JSON.encode(meta))
f:close()
end
result(true)
else
fail("Problem extracting zip file")
end
end
-- uninstall the package
packages.uninstall = function(path)
local osf = vfs.ospath(path)
if(osf and ulib.exists(osf) ) then
--remove it
ulib.delete(osf)
result(true)
else
fail("Cannot find package")
end
end
-- set user packages environment
packages.init = function(paths)
if(paths) then
for k,v in pairs(paths) do
local p = vfs.ospath(v)
if p and (ulib.exists(p) == false) then
ulib.mkdir(p)
-- change permission
ulib.chown(p, uid.id, uid.gid)
end
end
end
end
return packages

View File

@ -1,47 +0,0 @@
local shared = {}
shared.get = function(sharedid)
if sharedid == "all" then
-- get all shared files
local db = require("dbmodel").get("sysdb", "shared", nil)
if db == nil then die("Cannot get shared database") end
local data = db:getAll()
if data == nil then die("No file found") end
local i = 1
local ret = {}
for k,v in pairs(data) do
if(ulib.exists(v.path)) then
local r = ulib.file_stat(v.path)
if(r.error == nil) then
r.path = "shared://"..v.sid
r.filename = std.basename(v.path)
if r.mime == "application/octet-stream" then
r.mime = std.extra_mime(r.filename)
end
ret[i] = r
i = i+1
end
else
local cond = { ["="] = { sid = v.sid } }
db:delete(cond)
end
end
db:close()
--std.json()
result(ret)
else
local p = shared.ospath(sharedid)
std.sendFile(p)
end
end
shared.ospath = function(sharedid)
local db = require("dbmodel").get("sysdb", "shared", nil)
if db == nil then die("Cannot get shared database") end
local cond = { exp = { ["="] = { sid = sharedid } } }
local data = db:find(cond)
db:close()
if data == nil or data[1] == nil then die("Cannot get shared file with: "..sharedid) end
return data[1].path
end
return shared;

View File

@ -1,27 +0,0 @@
local uman={}
uman.userinfo = function(user)
local info = {}
local uid = ulib.uid(user)
if uid then
-- read the setting
-- use the decodeFile function of JSON instead
local file = require('vfs').ospath("home:///").."/.settings.json"
local st = JSON.decodeFile(file)
if(st) then
info = st
end
info.user = {
username = user,
id = uid.id,
name = user,
groups = uid.groups
}
--print(JSON.encode(info))
return info
else
return {}
end
end
return uman

View File

@ -1,234 +0,0 @@
local vfs = {}
vfs.ospath = function(path)
local user = SESSION.user
local prefix = string.match(path, "%a+:/")
if(prefix ~= nil) then
local suffix = string.gsub(path,prefix,"")
if prefix == "home:/" then
return string.format(VFS_HOME,user)..'/'..suffix
elseif prefix == "desktop:/" then
return string.format(VFS_HOME,user).."/.desktop/"..suffix
elseif prefix == "shared:/" then
return require("shared").ospath(std.trim(suffix,"/"))
elseif prefix == "os:/" then
return WWW_ROOT.."/"..suffix
else
return nil
end
else
return nil;
end
end
vfs.delete = function(path)
local r,m = vfs.checkperm(path,"write")
if r then
if ulib.delete(m) then
-- change permission
return true,nil
else
return false,"Cant not delete the file"
end
else
return r,m
end
end
vfs.exists = function(path)
local osfile = vfs.ospath(path)
return ulib.exists(osfile)
end
vfs.fileinfo = function(vfspath)
local ospath = vfs.ospath(vfspath)
if ospath then
if(ulib.exists(ospath) == false) then return false,"File not found" end
local r = ulib.file_stat(ospath)
if(r.error ~= nil) then return false,r.error end
r.path = vfspath
r.name = std.basename(vfspath)
if r.mime == "application/octet-stream" then
r.mime = std.extra_mime(r.name)
end
return true,r
else
return false,"Resource not found"
end
end
vfs.mkdir = function(path)
local file = std.basename(path)
local folder = string.gsub(path, utils.escape_pattern(file).."$","")
local r,m = vfs.checkperm(folder,"write")
if r then
local osfile = m.."/"..file
local uid = ulib.uid(SESSION.user)
ulib.mkdir(osfile)
-- change permission
ulib.chown(osfile, uid.id, uid.gid)
return true,nil
else
return r,m
end
end
vfs.move = function(src,dest)
local file = std.basename(dest)
local folder = string.gsub(dest, utils.escape_pattern(file),"")
local sp,sm = vfs.checkperm(src,"write")
if sp then
local dp,dm = vfs.checkperm(folder,"write")
if dp then
ulib.move(sm,dm.."/"..file)
-- change permission
return true,nil
else
return dp,dm
end
else
return sp,sm
end
end
vfs.write = function(path,data)
local file = std.basename(path)
local folder = string.gsub(path, utils.escape_pattern(file),"")
local r,m = vfs.checkperm(folder,"write")
if r then
local osfile = m.."/"..file
if ulib.exists(osfile) then
local r1,m1 = vfs.checkperm(path,"write")
if not r1 then
return r1, m1..": "..path
end
end
local uid = ulib.uid(SESSION.user)
--
if data ~= "" then
local header = string.match(data, "^data%:[%w%.-%+]+%/[%w%.-%+]+;base64,")
if header ~= nil then
local b64data = string.gsub(data, utils.escape_pattern(header),"")
local barr = std.b64decode(b64data)
bytes.write(barr,osfile)
--[[ if std.isBinary(osfile) then
else
local f = io.open(osfile, "w")
f:write(bytes.__tostring(barr))
f:close()
end ]]
else
return false, "Wrong data format"
end
else
bytes.write(bytes.new(0),osfile)
end
--f:close()
-- change permission
ulib.chown(osfile, uid.id, uid.gid)
return true,nil
else
return r,m..": "..folder
end
end
vfs.upload = function(path)
if(not path) then
return false, "Unknown upload destination, abort!"
end
local r,m = vfs.checkperm(path,"write")
if(r) then
local uid = ulib.uid(SESSION.user)
local index = 0
while(REQUEST["upload-"..index..".tmp"] ~= nil) do
local file = m.."/"..REQUEST["upload-"..index..".file"]
local ret = ulib.move(REQUEST["upload-"..index..".tmp"], file)
if not ret then
ret = ulib.send_file(REQUEST["upload-"..index..".tmp"], file)
ulib.delete(REQUEST["upload-"..index..".tmp"])
end
if not ret then
return false, "Unable to copy file"
end
ulib.chown(file, uid.id, uid.gid)
index = index + 1
end
if(index == 0) then
return false, "No file is uploaded"
end
return true, index
else
return r,m
end
end
vfs.checkperm = function(path, right)
local osfile = vfs.ospath(path)
local perm = vfs.perm(osfile)
--print(osfile)
if not ulib.exists(osfile) then
return false,"Resource does not exist"
end
-- check if user own the file
if perm ~= nil then
if perm[right] == true then
--print("Permission granted")
return true,osfile
else
print("Permission denie")
return false,"You dont have "..right.." permission on this file"
end
else
return false,"User is unrecognized"
end
end
vfs.perm = function(file)
local user = SESSION.user
local uid = ulib.uid(user)
local st = ulib.file_stat(file)
-- check if user own the file
if uid ~= nil and st ~= nil and st.perm ~= nil then
--print(JSON.encode({uid, st}))
if(uid.id == st.uid) then -- the user owned the file
--print("file belong to user")
return st.perm.owner
elseif uid.groups and uid.groups[st.gid] then
--print("User belong to this group")
return st.perm.group
else
--print("User belong to other")
return st.perm.other
end
else
return nil
end
end
vfs.readDir = function(vfspath)
if(string.sub(vfspath,-1) == "/") then
prefix = string.sub(vfspath,1,-2)
else
prefix = vfspath
end
local ospath = vfs.ospath(vfspath,SESSION.user)
local r = ulib.read_dir(ospath, prefix)
if(r.error ~= nil) then return nil end
-- add extra mime type
for k,v in pairs(r) do
if v.mime == "application/octet-stream" then
v.mime = std.extra_mime(v.filename)
end
end
return r
end
return vfs

View File

@ -1,45 +0,0 @@
-- the rewrite rule for the framework
-- should be something like this
-- ^\/apps\/+(.*)$ = /apps/router.lua?r=<1>&<query>
-- some global variables
DIR_SEP = "/"
WWW_ROOT = __ROOT__.."/os"
if HEADER.Host then
HTTP_ROOT= "https://"..HEADER.Host
else
HTTP_ROOT = "https://os.lxsang.me"
end
-- class path: path.to.class
BASE_FRW = ""
-- class path: path.to.class
CONTROLLER_ROOT = BASE_FRW.."os.controllers"
MODEL_ROOT = BASE_FRW.."os.models"
-- file path: path/to/file
VIEW_ROOT = WWW_ROOT..DIR_SEP.."views"
LOG_ROOT = WWW_ROOT..DIR_SEP.."logs"
VFS_HOME = "/home/%s"
package.path = package.path..";"..WWW_ROOT .. '/libs/?.lua'
-- require needed library
require(BASE_FRW.."silk.api")
require("common")
-- registry object store global variables
local REGISTRY = {}
-- set logging level
REGISTRY.logger = Logger:new{ levels = {INFO = false, ERROR = true, DEBUG = false}}
--REGISTRY.db = DBHelper:new{db="sysdb"}
REGISTRY.layout = 'default'
REGISTRY.fileaccess = true
--REGISTRY.db:open()
local router = Router:new{registry = REGISTRY}
REGISTRY.router = router
router:setPath(CONTROLLER_ROOT)
router:delegate()
--if REGISTRY.db then REGISTRY.db:close() end

View File

@ -1,108 +0,0 @@
-- create class
BaseObject:subclass("BaseController",
{
registry = {},
models = {},
main = false
})
-- set the name here in each subclasses
function BaseController:initialize()
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:print(...)
return self:actionnotfound("print")
end
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:switchLayout(name)
if self.main then
self.registry.layout = name
else
self:log("Cannot switch layout since the controller "..self.class.." is not the main controller")
end
end
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(...)
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
BaseController:subclass("NotfoundController",{ registry = {}, models = {} })
function NotfoundController:index(...)
local args = {...}
local error = args[2] or ""
if self.template:path() then
self.template:set("error", error)
self.template:set("title", "404 not found")
return true
end
self:error("404: Controller "..args[1].." not found : "..error)
return false
end
-- The asset controller for the static file
BaseController:subclass("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)
if self.registry.fileaccess and ulib.exists(path) then
local mime = std.mimeOf(path)
if POLICY.mimes[mime] then
std.sendFile(path)
else
self:error("Access forbidden: "..path)
end
else
self:error("Asset file not found or access forbidden: "..path)
end
return false
end

View File

@ -1,51 +0,0 @@
-- create class
BaseObject:subclass("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(m)
if self.db and m then return self.db:insert(self.name, m) end
return false
end
function BaseModel:update(m)
if self.db and m then return self.db:update(self.name, m) end
return false
end
function BaseModel:delete(cond)
if self.db and cond then return self.db:delete(self.name, cond) end
return false
end
function BaseModel:find(cond)
if self.db and cond then return self.db:find(self.name, cond) end
return false
end
function BaseModel:get(id)
local data, order = self:find({exp = {["="] = {id = id}}})
if not data or #order == 0 then return false end
return data[1]
end
function BaseModel:findAll()
if self.db then return self.db:getAll(self.name) end
return false
end
function BaseModel:query(sql)
if self.db then return self.db:query(sql) end
return false
end
function BaseModel:select(sel, sql_cnd)
if self.db then return self.db:select(self.name, sel, sql_cnd) end
return nil
end

View File

@ -1,34 +0,0 @@
BaseObject = Object:extends{registry = {}, class="BaseObject"}
function BaseObject:subclass(name, args)
_G[name] = self:extends(args)
_G[name].class = name
end
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:print()
print(self.class)
end
function BaseObject:error(msg, trace)
html()
--local line = debug.getinfo(1).currentline
echo(msg)
self:log(msg,"ERROR")
if trace then
debug.traceback=nil
error(msg)
else
error(msg)
end
return false
end

View File

@ -1,144 +0,0 @@
sqlite = modules.sqlite()
if sqlite == nil then return 0 end
-- create class
BaseObject:subclass("DBHelper", {db = {}})
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
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:select(tbl, sel, 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

@ -1,30 +0,0 @@
Logger = Object:extends{levels = {}}
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")
end
function Logger:debug(msg)
self:log(msg, "DEBUG")
end
function Logger:error(msg)
self:log(msg, "ERROR")
end

View File

@ -1,164 +0,0 @@
--define the class
BaseObject:subclass("Router", {registry = {}})
function Router:setPath(path)
self.path = path
end
function Router:initialize()
self.routes = {}
self.remaps = {}
end
--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 provide the url, try to infer it
-- from the REQUEST
url = url or REQUEST.r or ""
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]:gsub("%.", "")
if args[2] then
data.action = args[2]:gsub("%.", "")
end
for i = 3, #args do
table.insert(data.args, args[i])
end
end
-- remap if needed
if self.remaps[data.name] ~= nil then
data.name = self.remaps[data.name]
end
-- 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
local r, e = pcall(require, controller_path)
--require(controller_path)
if not _G[controller_name] then
-- verify if it is an asset
url = url:gsub("/", DIR_SEP)
local filepath = WWW_ROOT..DIR_SEP..url
if ulib.exists(filepath) then -- and not std.is_dir(filepath)
data.controller = AssetController:new {registry = self.registry}
data.action = "get"
data.name = "asset"
data.args ={url}
else
-- let the notfound controller handle the error
data.controller = NotfoundController:new {registry = self.registry}
data.args = {controller_name, e}
data.action = "index"
data.name = "notfound"
end
else
-- create the coresponding controller
data.controller = _G[controller_name]:new {registry = self.registry}
if not data.controller[data.action] then
--data.args = {data.action}
table.insert(data.args, 1, data.action)
data.action = "actionnotfound"
end
end
self:log("Controller: " .. data.controller.class .. ", action: "..data.action..", args: ".. JSON.encode(data.args))
return data
end
function Router:delegate()
local views = {}
local data = self:infer()
-- set the controller to the main controller
data.controller.main = true
views.__main__ = self:call(data)
if not views.__main__ then
--self:error("No view available for this action")
return
end
-- get all visible routes
local routes = self:dependencies(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 view_args = {}
local view_argv = {}
for k,v in pairs(views) do
table.insert( view_args, k )
table.insert( view_argv, v )
end
local fn, e = loadscript(VIEW_ROOT .. DIR_SEP .. self.registry.layout .. DIR_SEP .. "layout.ls", view_args)
html()
if fn then
local r, o = pcall(fn, table.unpack(view_argv))
if not r then
self:error(o)
end
else
e = e or ""
self:error("The index page is not found for layout: " .. self.registry.layout..": "..e)
end
end
function Router:dependencies(url)
if not self.routes[self.registry.layout] then
return {}
end
local list = {}
--self:log("comparing "..url)
for k, v in pairs(self.routes[self.registry.layout]) do
v.url = std.trim(v.url, "/")
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:call(data)
data.controller.template:setView(data.action, data.name)
local obj = data.controller[data.action](data.controller, table.unpack(data.args))
if obj then
return data.controller.template
else
return false
end
end
function Router:remap(from, to)
self.remaps[from] = to
end
function Router:route(layout, dependencies)
self.routes[layout] = dependencies
end

View File

@ -1,58 +0,0 @@
-- create class
BaseObject:subclass("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:get(k)
return self.vars[k]
end
function Template:remove(k)
self.vars[k] = nil
end
-- infer view path
function Template:setView(name, controller)
self.name = name
if controller then
self.controller = controller
end
end
function Template:path()
local path = VIEW_ROOT..DIR_SEP..self.registry.layout..DIR_SEP..self.controller..DIR_SEP..self.name..".ls"
if ulib.exists(path) then
return path
else
return false, path
end
end
-- render the page
function Template:render()
local path, err = self:path()
if not path then
return self:error("View not found: "..err)
end
local args = {}
local argv = {}
for k, v in pairs(self.vars) do
table.insert( args, k )
table.insert( argv,v )
end
local fn, e = loadscript(self:path(), args)
if fn then
local r,o = pcall(fn, table.unpack(argv))
if not r then
self:error(o)
end
else
self:error(e)
end
end

View File

@ -1,57 +0,0 @@
require("OOP")
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")
-- mime type allows
-- this will bypass the default server security
-- the default list is from the server setting
POLICY = {}
POLICY.mimes = {
["application/javascript"] = true,
["image/bmp"] = true,
["image/jpeg"] = true,
["image/png"] = 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,
}
HEADER_FLAG = false
function html()
if not HEADER_FLAG then
std.chtml(SESSION)
HEADER_FLAG = true
end
end
function import(module)
return require(BASE_FRW.."silk.api."..module)
end

View File

@ -1,54 +0,0 @@
-- the rewrite rule for the framework
-- should be something like this
-- ^\/apps\/+(.*)$ = /apps/router.lua?r=<1>&<query>
-- some global variables
DIR_SEP = "/"
WWW_ROOT = "/opt/www/htdocs/apps"
HTTP_ROOT = "https://apps.localhost:9195/"
-- class path: path.to.class
BASE_FRW = ""
-- class path: path.to.class
CONTROLLER_ROOT = BASE_FRW.."apps.controllers"
MODEL_ROOT = BASE_FRW.."apps.models"
-- file path: path/to/file
VIEW_ROOT = WWW_ROOT..DIR_SEP.."views"
LOG_ROOT = WWW_ROOT..DIR_SEP.."logs"
-- require needed library
require(BASE_FRW.."silk.api")
-- 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'
REGISTRY.db:open()
local router = Router:new{registry = REGISTRY}
REGISTRY.router = router
router:setPath(CONTROLLER_ROOT)
--router:route('edit', 'post/edit', "ALL" )
-- example of depedencies to the current main route
-- each layout may have different dependencies
local default_routes_dependencies = {
edit = {
url = "post/edit",
visibility = {
shown = true,
routes = {
["post/index"] = true
}
}
},
--category = {
-- url = "cat/index",
-- visibility = "ALL"
--}
}
router:route('default', default_routes_dependencies )
router:delegate()
if REGISTRY.db then REGISTRY.db:close() end

View File

@ -21,7 +21,15 @@ class QuickTalk {
this.instant_compose.parentNode.removeChild(this.instant_compose); this.instant_compose.parentNode.removeChild(this.instant_compose);
} }
this.instant_compose = this.compose(editor, 0, true, (data) => { this.instant_compose = this.compose(editor, 0, true, (data) => {
this.show_comment(container, data, true).scrollIntoView(); this.show_comment(container, data, true);
if(this.options.page)
{
this.options.page.scrollTop = this.options.page.scrollHeight;
}
else
{
window.scrollTo(0, document.body.scrollHeight);
}
}); });
}); });
} }
@ -199,9 +207,9 @@ class QuickTalk {
container.appendChild(preview); container.appendChild(preview);
container.appendChild(footer); container.appendChild(footer);
at.appendChild(container); at.appendChild(container);
if (this.options.page) { //if (this.options.page) {
this.options.page.scrollTop = container.offsetTop - this.options.page.offsetTop; // this.options.page.scrollTop = container.offsetTop - this.options.page.offsetTop;
} //}
//container.scrollIntoView(); //container.scrollIntoView();
return container; return container;
@ -228,6 +236,7 @@ class QuickTalk {
show_comment(at, comment, show_footer) { show_comment(at, comment, show_footer) {
let container = document.createElement("div"); let container = document.createElement("div");
container.setAttribute("class", "quick-talk-comment"); container.setAttribute("class", "quick-talk-comment");
container.setAttribute("id", "comment-" + comment.id);
let header = document.createElement("div"); let header = document.createElement("div");
header.setAttribute("class", "quick-talk-comment-header"); header.setAttribute("class", "quick-talk-comment-header");
let username = document.createElement("span"); let username = document.createElement("span");
@ -265,7 +274,8 @@ class QuickTalk {
this.instant_compose.parentNode.removeChild(this.instant_compose); this.instant_compose.parentNode.removeChild(this.instant_compose);
} }
this.instant_compose = this.compose(editor, parseInt(comment.id), true, (data) => { this.instant_compose = this.compose(editor, parseInt(comment.id), true, (data) => {
this.show_comment(sub_comments, data, false).scrollIntoView(); this.show_comment(sub_comments, data, false);
//.scrollIntoView();
}); });
}); });
container.appendChild(footer); container.appendChild(footer);

View File

@ -10,6 +10,7 @@ local function process_md(input)
end end
local function sendmail(to, subject, content) local function sendmail(to, subject, content)
LOG_DEBUG("Sending email to %s", to)
local from = "From: contact@iohub.dev\nTo: " .. to .. "\n" local from = "From: contact@iohub.dev\nTo: " .. to .. "\n"
local suject = "Subject: " .. subject .. "\n" local suject = "Subject: " .. subject .. "\n"
@ -31,17 +32,17 @@ function CommentController:index(...)
end end
local rq = (JSON.decodeString(REQUEST.json)) local rq = (JSON.decodeString(REQUEST.json))
if (rq) then if (rq) then
local pages, order = self.pages:find({exp = {["="] = {uri = rq.page}}}) local pages, order = self.pages:find({where = {uri = rq.page}})
if not pages or #order == 0 then if not pages or #order == 0 then
fail("Be the first to comment") fail("Be the first to comment")
else else
local pid = pages[1].id local pid = pages[1].id
local comments, order = self.comment:find( local comments, order = self.comment:find({
{ where = {
exp = { pid = pid,
["and"] = {{["="] = {pid = pid}}, {[" = "] = {rid = 0}}} rid = 0
}, },
order = {time = "ASC"}, order = {"time$asc"},
fields = {"id", "time", "name", "rid", "pid", "content"} fields = {"id", "time", "name", "rid", "pid", "content"}
}) })
if not comments or #order == 0 then if not comments or #order == 0 then
@ -55,13 +56,11 @@ function CommentController:index(...)
local sub_comments, suborder = local sub_comments, suborder =
self.comment:find( self.comment:find(
{ {
exp = { where = {
["and"] = { pid = pid,
{["="] = {pid = pid}}, rid = data.id
{[" = "] = {rid = data.id}}
}
}, },
order = {time = "ASC"} order = {"time$asc"}
}) })
if sub_comments and #suborder ~= 0 then if sub_comments and #suborder ~= 0 then
@ -92,7 +91,7 @@ function CommentController:post(...)
end end
local rq = (JSON.decodeString(REQUEST.json)) local rq = (JSON.decodeString(REQUEST.json))
if rq then if rq then
local pages, order = self.pages:find({exp = {["="] = rq.page}}) local pages, order = self.pages:find({where = rq.page})
if not pages or #order == 0 then if not pages or #order == 0 then
-- insert data -- insert data
if self.pages:create(rq.page) then if self.pages:create(rq.page) then
@ -120,19 +119,27 @@ function CommentController:post(...)
".\nBest regards,\nEmail automatically sent by QuickTalk API") ".\nBest regards,\nEmail automatically sent by QuickTalk API")
end end
-- send mail to all users of current page -- send mail to all users of current page
local cmts, cmti = self.comment:select("MIN(id) as id,email", local cmts, cmti = self.comment:find(
"pid=" .. rq.comment.pid .. {
" AND email != '" .. where = {
rq.comment.email .. pid = rq.comment.pid,
"' GROUP BY email") ["email$ne"] = rq.comment.email
},
fields = {"id", "email"}
})
-- check duplicate email
if cmts and #cmti > 0 then if cmts and #cmti > 0 then
local sent = {}
for idx, v in pairs(cmti) do for idx, v in pairs(cmti) do
sendmail(cmts[v].email, rq.comment.name .. if not sent[cmts[v].email] then
sendmail(cmts[v].email, rq.comment.name ..
" has written something on a page that you've commented on", " has written something on a page that you've commented on",
rq.comment.name .. rq.comment.name ..
" has written something on a page that you've commented. \nPlease visit this page: " .. " has written something on a page that you've commented. \nPlease visit this page: " ..
rq.page.uri .. rq.page.uri..
" for updates on the discussion.\nBest regards,\nEmail automatically sent by QuickTalk API") " for updates on the discussion.\nBest regards,\nEmail automatically sent by QuickTalk API")
sent[cmts[v].email] = true
end
end end
end end
rq.comment.email = "" rq.comment.email = ""

View File

@ -2,6 +2,13 @@
-- should be something like this -- should be something like this
-- ^\/apps\/+(.*)$ = /apps/router.lua?r=<1>&<query> -- ^\/apps\/+(.*)$ = /apps/router.lua?r=<1>&<query>
-- some global variables -- some global variables
package.path = _SERVER["LIB_DIR"].."/lua/?.lua"
require("silk.api")
-- crypto lib
enc = require("enc")
WWW_ROOT = __ROOT__.."/talk"
-- TODO change me
DB_FILE = "/home/dany/databases/quicktalk.db"
function fail(msg) function fail(msg)
std.json() std.json()
std.t(JSON.encode({error = msg})) std.t(JSON.encode({error = msg}))
@ -12,34 +19,30 @@ function result(obj)
std.t(JSON.encode({result = obj, error = false})) std.t(JSON.encode({result = obj, error = false}))
end end
DIR_SEP = "/" DIR_SEP = "/"
WWW_ROOT = __ROOT__ .. "/talk"
if HEADER.Host then if HEADER.Host then
HTTP_ROOT = "https://" .. HEADER.Host HTTP_ROOT = "https://" .. HEADER.Host
else else
HTTP_ROOT = "https://talk.iohub.dev" HTTP_ROOT = "https://talk.iohub.dev"
end end
-- class path: path.to.class
BASE_FRW = ""
-- class path: path.to.class
CONTROLLER_ROOT = BASE_FRW .. "talk.controllers"
MODEL_ROOT = BASE_FRW .. "talk.models"
-- file path: path/to/file
VIEW_ROOT = WWW_ROOT .. DIR_SEP .. "views"
LOG_ROOT = WWW_ROOT .. DIR_SEP .. "logs"
-- require needed library -- TODO remove me
require(BASE_FRW .. "silk.api") HTTP_ROOT = HTTP_ROOT.."/next/talk"
-- class path: path.to.class
CONTROLLER_ROOT = "talk.controllers"
MODEL_ROOT = "talk.models"
-- file path: path/to/file
VIEW_ROOT = WWW_ROOT..DIR_SEP.."views"
-- registry object store global variables -- registry object store global variables
local REGISTRY = {} local REGISTRY = {}
-- set logging level -- set logging level
REGISTRY.logger = Logger:new{ REGISTRY.logger = Logger:new{ level = Logger.INFO}
levels = {INFO = false, ERROR = false, DEBUG = false}
}
REGISTRY.layout = 'default' REGISTRY.layout = 'default'
REGISTRY.fileaccess = true REGISTRY.fileaccess = true
REGISTRY.db = DBHelper:new{db = "quicktalk"} REGISTRY.db = DBModel:new{db = DB_FILE}
REGISTRY.db:open() REGISTRY.db:open()
local router = Router:new{registry = REGISTRY} local router = Router:new{registry = REGISTRY}