Skip to content

Commit 7588197

Browse files
committed
Initial koa-easy project
Changes to be committed: new file: .gitignore new file: ActionTrigger.js new file: Controller.js new file: LICENSE new file: NotFoundError.js new file: README.md new file: Route.js new file: Router.js new file: Rule.js new file: SessionStore.js new file: View.js new file: decorators/paramFrom.js new file: decorators/params.js new file: index.js new file: install new file: mvc.js new file: package.json new file: reflect/Decorator.js new file: reflect/Metadata.js new file: reflect/init.js new file: session/index.js new file: session/lib/context.js new file: session/lib/session.js new file: session/lib/util.js new file: utils.js new file: yarn.lock
0 parents  commit 7588197

26 files changed

Lines changed: 1892 additions & 0 deletions

.gitignore

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,4 @@
1+
.idea
2+
.DS_Store
3+
.vscode
4+
node_modules

ActionTrigger.js

Lines changed: 89 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,89 @@
1+
const path = require('path');
2+
const Controller = require('./Controller');
3+
const reflectInit = require('./reflect/init');
4+
const NotFoundError = require('./NotFoundError');
5+
const Metadata = require('./reflect/Metadata');
6+
7+
const cache = {};
8+
9+
function findControllerByPath(p, decorator){
10+
if(!cache[p]){
11+
try{
12+
cache[p] = require(p);
13+
reflectInit(cache[p], decorator);
14+
}catch(e){
15+
if(e.message.startsWith('Cannot find module ')){
16+
cache[p] = new NotFoundError('controller not found');
17+
}
18+
cache[p] = e;
19+
}
20+
}
21+
22+
const RouteController = cache[p];
23+
if(RouteController instanceof Error) throw RouteController;
24+
if(! RouteController.prototype instanceof Controller)
25+
throw new Error('required controller class is not drive from koa-mvc/Controller');
26+
return RouteController;
27+
}
28+
29+
module.exports = class ActionTrigger{
30+
constructor({ctx, route, router, decorator, view}){
31+
this._ctx = ctx;
32+
this._route = route;
33+
this._router = router;
34+
this._view = view;
35+
36+
this._decorator = decorator;
37+
38+
this._controllerName = route.controllerFileName;
39+
this._actionName = route.actionMethodName;
40+
}
41+
build(){
42+
const RouteController = findControllerByPath(path.resolve(
43+
this._route.controllersRoot,
44+
this._controllerName
45+
), this._decorator);
46+
47+
this._controller = new RouteController({
48+
ctx: this._ctx,
49+
route: this._route,
50+
router: this._router,
51+
view: this._view
52+
});
53+
}
54+
async trigger(){
55+
if(!this._controller) this.build();
56+
57+
const controller = this._controller;
58+
if(controller[this._actionName]){
59+
const meta = Metadata.getMetadata(controller.constructor, this._actionName);
60+
let result = await controller.$beforeAction();
61+
62+
if(result!==false){
63+
const args = (meta.$$params||[]).map(key=>{
64+
let searchParamFrom = ['query', 'post'];
65+
if(meta.$$paramFrom&&meta.$$paramFrom[key]){
66+
searchParamFrom = meta.$$paramFrom[key];
67+
}
68+
for(let from of searchParamFrom){
69+
switch(from){
70+
case 'query':
71+
if(key in controller.query)
72+
return controller.query[key];
73+
break;
74+
case 'post':
75+
if(key in controller.request.body)
76+
return controller.request.body[key];
77+
break;
78+
}
79+
}
80+
81+
return undefined;
82+
});
83+
const actionResult = await controller[this._actionName](...args);
84+
}
85+
await controller.$afterAction();
86+
87+
} else throw new NotFoundError('action not found');
88+
}
89+
}

Controller.js

Lines changed: 88 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,88 @@
1+
2+
const pug = require('pug');
3+
const path = require('path');
4+
const ActionTrigger = require('./ActionTrigger');
5+
const Route = require('./Route');
6+
7+
const pugCompileOptions = {
8+
cache: true,
9+
doctype: 'html'
10+
}
11+
12+
const viewKey = Symbol('#view');
13+
14+
module.exports = class Controller {
15+
16+
$beforeAction(){}
17+
$afterAction(){}
18+
19+
constructor({ctx, route, router, view}){
20+
Object.defineProperties(this, {
21+
ctx: { value: ctx },
22+
route: { value: route },
23+
router: { value: router }
24+
});
25+
this[viewKey] = view;
26+
}
27+
get request(){
28+
return this.ctx.request;
29+
}
30+
get response(){
31+
return this.ctx.response;
32+
}
33+
get session(){
34+
return this.ctx.session;
35+
}
36+
set session(s){
37+
this.ctx.session = s;
38+
}
39+
get sessionId(){
40+
return this.ctx.sessionId;
41+
}
42+
get cookies(){
43+
return this.ctx.cookies;
44+
}
45+
get query(){
46+
return this.ctx.query;
47+
}
48+
throw(...arg){
49+
this.ctx.throw(...arg);
50+
}
51+
isAjax(){
52+
return this.ctx.headers['x-requested-with'] === 'XMLHttpRequest'
53+
}
54+
renderText(txt){
55+
this.ctx.body = txt;
56+
}
57+
render(model = {}, view){
58+
const controller = this.route.controller;
59+
view = view || this.route.action;
60+
61+
const viewPath = path.resolve(this.route.viewsRoot, `${controller}/${view}.pug`);
62+
63+
this.ctx.body = pug.compileFile(viewPath, pugCompileOptions)({
64+
model,
65+
view: this[viewKey]
66+
});
67+
}
68+
renderJSON(json = {}){
69+
this.ctx.body = json;
70+
}
71+
renderSVG(svg){
72+
this.ctx.body = svg;
73+
this.ctx.type="image/svg+xml";
74+
}
75+
redirect(params, area){
76+
const url = (typeof params === 'string') ? params : this.router.resolve(params, area);
77+
this.ctx.redirect(url);
78+
}
79+
async rewrite(params, area){
80+
const route = (typeof params === 'string') ? this.router.match(params) : new Route(params, area);
81+
const actionTrigger = new ActionTrigger({
82+
ctx: this.ctx,
83+
route,
84+
router: this.router
85+
});
86+
await actionTrigger.trigger();
87+
}
88+
}

LICENSE

Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,21 @@
1+
MIT License
2+
3+
Copyright (c) 2017 kserver
4+
5+
Permission is hereby granted, free of charge, to any person obtaining a copy
6+
of this software and associated documentation files (the "Software"), to deal
7+
in the Software without restriction, including without limitation the rights
8+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9+
copies of the Software, and to permit persons to whom the Software is
10+
furnished to do so, subject to the following conditions:
11+
12+
The above copyright notice and this permission notice shall be included in all
13+
copies or substantial portions of the Software.
14+
15+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21+
SOFTWARE.

NotFoundError.js

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
module.exports = class NotFoundError extends Error{
2+
constructor(msg){
3+
super(msg);
4+
}
5+
}

README.md

Whitespace-only changes.

Route.js

Lines changed: 53 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,53 @@
1+
const path = require('path');
2+
const { appRoot, upperFirstLetter, camelCase } = require('./utils');
3+
4+
const paramsKey = Symbol();
5+
const areaKey = Symbol();
6+
const controllersRootKey = Symbol();
7+
const viewRootKey = Symbol();
8+
const controllerFileNameKey = Symbol();
9+
const actionMethodNameKey = Symbol();
10+
11+
module.exports = class Route {
12+
constructor(params, area = ''){
13+
this[paramsKey] = params;
14+
this[areaKey] = area;
15+
16+
this[controllerFileNameKey] = upperFirstLetter(camelCase(params.controller)) + 'Controller';
17+
this[actionMethodNameKey] = camelCase(params.action);
18+
19+
let moduleRoot = appRoot;
20+
if(area){
21+
moduleRoot = path.resolve(moduleRoot, `areas/${area}`);
22+
}
23+
this[controllersRootKey] = path.resolve(moduleRoot, 'controllers');
24+
this[viewRootKey] = path.resolve(moduleRoot, 'views');
25+
}
26+
get area(){
27+
return this[areaKey];
28+
}
29+
get controller(){
30+
return this[paramsKey].controller;
31+
}
32+
get action(){
33+
return this[paramsKey].action;
34+
}
35+
get params(){
36+
return this[paramsKey]
37+
}
38+
39+
get controllerFileName(){
40+
return this[controllerFileNameKey];
41+
}
42+
get actionMethodName(){
43+
return this[actionMethodNameKey];
44+
}
45+
46+
get controllersRoot(){
47+
return this[controllersRootKey];
48+
}
49+
50+
get viewsRoot(){
51+
return this[viewRootKey];
52+
}
53+
}

Router.js

Lines changed: 63 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,63 @@
1+
2+
const Rule = require('./Rule');
3+
const Route = require('./Route');
4+
const p = require('path');
5+
6+
const rulesKey = Symbol('#rules');
7+
const areasKey = Symbol('#areas');
8+
const getMatchedResultKey = Symbol('#getMatchedResultKey');
9+
10+
module.exports = class Router{
11+
get rules(){
12+
return this[rulesKey];
13+
}
14+
get areas(){
15+
return this[areasKey];
16+
}
17+
constructor({
18+
rules = [],
19+
areas = []
20+
} = {}){
21+
if(rules.length===0){
22+
rules.push('{{controller:home}}/{{action:index}}')
23+
}
24+
this[areasKey] = areas;
25+
this[rulesKey] = [];
26+
for(let rule of rules){
27+
this[rulesKey].push(new Rule(rule));
28+
}
29+
}
30+
31+
[getMatchedResultKey](path){
32+
for(let rule of this.rules){
33+
const result = rule.match(path);
34+
if(result) return result;
35+
}
36+
return null;
37+
}
38+
39+
match(path){
40+
if(path.startsWith('/')) path = path.substr(1);
41+
let area = '';
42+
let areaPath = '';
43+
for(let areaName of this.areas){
44+
if(path.startsWith(areaName)){
45+
area = areaName;
46+
areaPath = p.relative(areaName, path);
47+
}
48+
}
49+
50+
if(area){
51+
const params = this[getMatchedResultKey](areaPath);
52+
if(params) return new Route(params, area);
53+
}
54+
55+
const params = this[getMatchedResultKey](path);
56+
if(params) return new Route(params);
57+
58+
return null;
59+
}
60+
resolve(params, area){
61+
return p.join('/'+area, this.rules[0].resolve(params));
62+
}
63+
}

0 commit comments

Comments
 (0)