Browse Source

format

theme
npmrun 2 years ago
parent
commit
994c7d6013
  1. 4
      .prettierrc
  2. 367
      packages/hapi-router/src/index.ts
  3. 20
      packages/hapi-router/src/util/decorators.ts
  4. 135
      packages/hapi-router/src/util/index.ts
  5. 30
      public/js/common/main.js
  6. 2
      public/js/page/login.js
  7. 122
      public/style/common/normalize.css
  8. 32
      public/style/common/style.css
  9. 4
      route.txt
  10. 38
      source/auth/index.ts
  11. 68
      source/log4js_config.ts
  12. 30
      source/main.ts
  13. 12
      source/models/Color.ts
  14. 12
      source/models/Constant.ts
  15. 93
      source/models/User.ts
  16. 26
      source/models/ha/user.ts
  17. 42
      source/models/ha/user_info.ts
  18. 52
      source/plugins/file-plugin.ts
  19. 286
      source/plugins/index.ts
  20. 20
      source/route/api/index.ts
  21. 143
      source/route/api/v1/upload/_upload.ts
  22. 48
      source/route/api/v1/upload/index.ts
  23. 176
      source/route/api/v1/user/index.ts
  24. 2
      source/route/htmx/index.ts
  25. 164
      source/route/views/index.ts
  26. 165
      source/route/views/index/index.ts
  27. 8
      source/route/views/nav/index.ts
  28. 262
      source/run.ts
  29. 34
      source/schema/index.ts
  30. 16
      source/util/index.ts
  31. 44
      source/util/res-helper.ts
  32. 60
      source/util/util.ts
  33. 8
      template/404.pug
  34. 120
      template/helper/flush.pug
  35. 2
      template/helper/form_security.pug
  36. 2
      template/md/about.md
  37. 79
      template/ui/header.pug
  38. 6
      template/views/about.pug
  39. 12
      template/views/index.pug
  40. 20
      template/views/login.pug
  41. 14
      template/views/user.pug
  42. 27
      types/global.d.ts

4
.prettierrc

@ -7,5 +7,5 @@
"bracketSpacing": true,
"arrowParens": "avoid",
"htmlWhitespaceSensitivity": "ignore",
"printWidth": 120
}
"printWidth": 120
}

367
packages/hapi-router/src/index.ts

@ -1,211 +1,190 @@
// @ts-nocheck
import { walkDir, removeIndex, isIndexEnd } from "./util";
import * as Joi from "joi";
const path = require("path");
const fs = require("fs");
import { walkDir, removeIndex, isIndexEnd } from "./util"
import * as Joi from "joi"
const path = require("path")
const fs = require("fs")
class routePlugin {
public name: string = "routePlugin";
public version: string = "0.0.1";
public register(server: any, opts: any) {
const sourceDir = opts.sourceDir;
const type = opts.type || "jwt";
const auth = opts.auth || [];
let array = [];
for (let i = 0; i < sourceDir.length; i++) {
const dir = sourceDir[i];
console.log(dir);
array.push(dir.dir + "对应路径:");
array = array.concat(
this.registerRoute(server, dir.dir, dir.prefix || "", auth, type)
);
public name: string = "routePlugin"
public version: string = "0.0.1"
public register(server: any, opts: any) {
const sourceDir = opts.sourceDir
const type = opts.type || "jwt"
const auth = opts.auth || []
let array = []
for (let i = 0; i < sourceDir.length; i++) {
const dir = sourceDir[i]
console.log(dir)
array.push(dir.dir + "对应路径:")
array = array.concat(this.registerRoute(server, dir.dir, dir.prefix || "", auth, type))
}
fs.writeFileSync(path.resolve(process.cwd(), "route.txt"), array.join("\n"), {
encoding: "utf-8",
})
}
fs.writeFileSync(
path.resolve(process.cwd(), "route.txt"),
array.join("\n"),
{
encoding: "utf-8",
}
);
}
registerRoute(server, sourceDir, prefix, auth, type) {
const files = walkDir(sourceDir);
const routes = [];
files.forEach((file) => {
let filename = file.relativeFileNoExt;
let array = filename.split(path.sep).slice(1);
let fileNoExt = removeIndex("/" + array.join("/"));
const moduleName = path.resolve(sourceDir, filename);
const obj = require(moduleName);
if (obj.default) {
const func = new (obj.default || obj)();
const prototype = Object.getPrototypeOf(func);
const keys = Reflect.ownKeys(prototype);
for (const key of keys) {
if (key !== "constructor") {
let ff = func[key];
let handler:()=>void = undefined
// 默认方法
const method = ff.$method || "GET";
// 路由收集规则
let route = "";
if (ff.$route) {
if (isIndexEnd(fileNoExt)) {
route = ff.$route;
} else {
route = fileNoExt + ff.$route;
}
} else {
if (isIndexEnd(fileNoExt)) {
route = fileNoExt + key.toString();
} else {
route = fileNoExt + "/" + key.toString();
}
}
route = removeIndex(route);
route = prefix ? route[0] + prefix + "/" + route.slice(1) : route;
// 配置规则
const options = ff.$options ? ff.$options : {};
if (!options.auth) {
if (ff.$auth == undefined) {
if (
auth &&
auth.length &&
auth.filter((v) => route.startsWith(v)).length
) {
options.auth = type;
} else {
options.auth = false;
}
} else if (ff.$auth) {
options.auth =
typeof ff.$auth === "boolean"
? type
: {
strategy: type,
mode: ff.$auth,
};
} else {
options.auth = false;
}
}
if (!options.validate) {
let validateObj = ff.$validate || {};
if (options.auth && type === "jwt") {
if (validateObj.headers) {
validateObj.headers = validateObj.headers.keys({
Authorization: Joi.string(),
});
} else {
validateObj.headers = Joi.object({
headers: Joi.object({
Authorization: Joi.string(),
}).unknown(), // 注意加上这个
});
}
}
if (validateObj&&!!Object.keys(validateObj).length) {
const failReason = validateObj.failReason
delete validateObj.failReason
if(validateObj.failAction === "log"){
if(!options.log) options.log = {}
options.log.collect = true
let errto = validateObj.$errto
handler = async function (...argus){
const request = argus[0]
const h = argus[1]
if(request.logs&&!!request.logs.length && errto){
// request.yar.flash('error', request.logs.map((v: any)=>v.error.message));
request.yar.flash('error', failReason);
return h.redirect(errto);
registerRoute(server, sourceDir, prefix, auth, type) {
const files = walkDir(sourceDir)
const routes = []
files.forEach(file => {
let filename = file.relativeFileNoExt
let array = filename.split(path.sep).slice(1)
let fileNoExt = removeIndex("/" + array.join("/"))
const moduleName = path.resolve(sourceDir, filename)
const obj = require(moduleName)
if (obj.default) {
const func = new (obj.default || obj)()
const prototype = Object.getPrototypeOf(func)
const keys = Reflect.ownKeys(prototype)
for (const key of keys) {
if (key !== "constructor") {
let ff = func[key]
let handler: () => void = undefined
// 默认方法
const method = ff.$method || "GET"
// 路由收集规则
let route = ""
if (ff.$route) {
if (isIndexEnd(fileNoExt)) {
route = ff.$route
} else {
route = fileNoExt + ff.$route
}
} else {
if (isIndexEnd(fileNoExt)) {
route = fileNoExt + key.toString()
} else {
route = fileNoExt + "/" + key.toString()
}
}
return await ff.call(this, ...argus)
}
}
if(validateObj.failAction === "function"){
let errto = validateObj.$errto
validateObj.failAction = async function(request, h, err){
if(err.details){
request.$joi_error = err.details.map(v=>v.message)
route = removeIndex(route)
route = prefix ? route[0] + prefix + "/" + route.slice(1) : route
// 配置规则
const options = ff.$options ? ff.$options : {}
if (!options.auth) {
if (ff.$auth == undefined) {
if (auth && auth.length && auth.filter(v => route.startsWith(v)).length) {
options.auth = type
} else {
options.auth = false
}
} else if (ff.$auth) {
options.auth =
typeof ff.$auth === "boolean"
? type
: {
strategy: type,
mode: ff.$auth,
}
} else {
options.auth = false
}
}
return h.continue;
}
handler = async function (...argus){
const request = argus[0]
const h = argus[1]
if(request.$joi_error){
loggerSite.debug('传输参数错误: ', request.$joi_error)
request.yar.flash('error', failReason);
delete request.$joi_error
return h.redirect(errto);
if (!options.validate) {
let validateObj = ff.$validate || {}
if (options.auth && type === "jwt") {
if (validateObj.headers) {
validateObj.headers = validateObj.headers.keys({
Authorization: Joi.string(),
})
} else {
validateObj.headers = Joi.object({
headers: Joi.object({
Authorization: Joi.string(),
}).unknown(), // 注意加上这个
})
}
}
if (validateObj && !!Object.keys(validateObj).length) {
const failReason = validateObj.failReason
delete validateObj.failReason
if (validateObj.failAction === "log") {
if (!options.log) options.log = {}
options.log.collect = true
let errto = validateObj.$errto
handler = async function (...argus) {
const request = argus[0]
const h = argus[1]
if (request.logs && !!request.logs.length && errto) {
// request.yar.flash('error', request.logs.map((v: any)=>v.error.message));
request.yar.flash("error", failReason)
return h.redirect(errto)
}
return await ff.call(this, ...argus)
}
}
if (validateObj.failAction === "function") {
let errto = validateObj.$errto
validateObj.failAction = async function (request, h, err) {
if (err.details) {
request.$joi_error = err.details.map(v => v.message)
}
return h.continue
}
handler = async function (...argus) {
const request = argus[0]
const h = argus[1]
if (request.$joi_error) {
loggerSite.debug("传输参数错误: ", request.$joi_error)
request.yar.flash("error", failReason)
delete request.$joi_error
return h.redirect(errto)
}
return await ff.call(this, ...argus)
}
}
options.validate = validateObj
}
}
// && route.startsWith("/api")
if (ff.$swagger) {
options.description = ff.$swagger[0]
options.notes = ff.$swagger[1]
options.tags = ff.$swagger[2]
}
let str = route
if (
(typeof options.auth === "string" && options.auth) ||
(typeof options.auth === "object" && options.auth.mode === "required")
) {
str = " 需要权限 : " + " " + full(method) + " " + str
} else if (typeof options.auth === "object" && options.auth.mode === "optional") {
str = " 不需权限(提供即需验证): " + " " + full(method) + " " + str
} else if (typeof options.auth === "object" && options.auth.mode === "try") {
str = " 不需权限(提供无需验证): " + " " + full(method) + " " + str
} else {
str = " 不需权限 : " + " " + full(method) + " " + str
}
return await ff.call(this, ...argus)
routes.push(str)
if (options.validate && options.validate.$errto) {
delete options.validate.$errto
}
if (!handler) {
handler = ff
}
server.route({
method: method,
path: route,
handler: handler,
options: options,
})
}
}
options.validate = validateObj;
}
}
// && route.startsWith("/api")
if (ff.$swagger) {
options.description = ff.$swagger[0];
options.notes = ff.$swagger[1];
options.tags = ff.$swagger[2];
}
let str = route;
if (
(typeof options.auth === "string" && options.auth) ||
(typeof options.auth === "object" &&
options.auth.mode === "required")
) {
str =
" 需要权限 : " + " " + full(method) + " " + str;
} else if (
typeof options.auth === "object" &&
options.auth.mode === "optional"
) {
str =
" 不需权限(提供即需验证): " + " " + full(method) + " " + str;
} else if (
typeof options.auth === "object" &&
options.auth.mode === "try"
) {
str =
" 不需权限(提供无需验证): " + " " + full(method) + " " + str;
} else {
str =
" 不需权限 : " + " " + full(method) + " " + str;
}
routes.push(str);
if(options.validate && options.validate.$errto){
delete options.validate.$errto
}
if(!handler){
handler = ff
}
server.route({
method: method,
path: route,
handler: handler,
options: options,
});
}
}
}
});
return routes;
}
})
return routes
}
}
function full(str: string, length = 10) {
let len = str.length;
let need = length - len;
if (need <= 0) return str;
return str + [...Array(need)].map((v, i) => " ").join("");
let len = str.length
let need = length - len
if (need <= 0) return str
return str + [...Array(need)].map((v, i) => " ").join("")
}
const plugin = new routePlugin();
const plugin = new routePlugin()
export { plugin };
export * from "./util/decorators";
export { plugin }
export * from "./util/decorators"

20
packages/hapi-router/src/util/decorators.ts

@ -1,40 +1,40 @@
// @ts-nocheck
/**
*
* @param opts
*/
*
* @param opts
*/
type TMethod = "GET" | "POST" | "PUT" | "DELETE"
export function method(opts?:TMethod|Array<TMethod>) {
export function method(opts?: TMethod | Array<TMethod>) {
return function (target, propertyKey: string, descriptor: PropertyDescriptor) {
target[propertyKey].$method = opts
}
}
export function route(route?:string) {
export function route(route?: string) {
return function (target, propertyKey: string, descriptor: PropertyDescriptor) {
target[propertyKey].$route = route
}
}
export function config(options:Object) {
export function config(options: Object) {
return function (target, propertyKey: string, descriptor: PropertyDescriptor) {
target[propertyKey].$options = options
}
}
export function auth(isAuth:boolean | "try" | "required" | "optional" = true) {
export function auth(isAuth: boolean | "try" | "required" | "optional" = true) {
return function (target, propertyKey: string, descriptor: PropertyDescriptor) {
target[propertyKey].$auth = isAuth
}
}
export function validate(validate:Object) {
export function validate(validate: Object) {
return function (target, propertyKey: string, descriptor: PropertyDescriptor) {
target[propertyKey].$validate = validate
}
}
export function swagger(desc,notes,tags) {
export function swagger(desc, notes, tags) {
return function (target, propertyKey: string, descriptor: PropertyDescriptor) {
target[propertyKey].$swagger = [desc,notes,tags]
target[propertyKey].$swagger = [desc, notes, tags]
}
}

135
packages/hapi-router/src/util/index.ts

@ -3,80 +3,79 @@
const path = require("path")
const fs = require("fs")
export function removeIndex(ss:any) {
const remove = (str:any) => {
if (str.endsWith("/index")) {
return str.slice(0, -6);
}
if (str.endsWith("index")) {
return str.slice(0, -5);
export function removeIndex(ss: any) {
const remove = (str: any) => {
if (str.endsWith("/index")) {
return str.slice(0, -6)
}
if (str.endsWith("index")) {
return str.slice(0, -5)
}
return str ? str : "/"
}
return str ? str : "/";
};
let r = true;
let rr = ss;
while (r) {
if (rr.endsWith("/index")) {
rr = remove(rr);
} else {
r = false;
let r = true
let rr = ss
while (r) {
if (rr.endsWith("/index")) {
rr = remove(rr)
} else {
r = false
}
}
}
return rr ? rr : "/";
return rr ? rr : "/"
}
export function isIndexEnd(str:any) {
return str.length == 1 && str.endsWith("/");
export function isIndexEnd(str: any) {
return str.length == 1 && str.endsWith("/")
}
export function walkDir(
filePath:any,
exclude = ["node_modules", "^_", ".git", ".idea", ".gitignore", "client","\.txt$","\.test\.js$","\.test\.ts$"]
filePath: any,
exclude = ["node_modules", "^_", ".git", ".idea", ".gitignore", "client", ".txt$", ".test.js$", ".test.ts$"],
) {
let files:any[] = [];
function Data(opts:any) {
this.relativeDir = opts.relativeDir;
this.relativeFile = opts.relativeFile;
this.filename = opts.filename;
this.file = opts.file;
this.absoluteFile = opts.absoluteFile;
this.relativeFileNoExt = opts.relativeFileNoExt;
this.absoluteDir = opts.absoluteDir;
}
function readDir(filePath, dirname = ".") {
let res = fs.readdirSync(filePath);
res.forEach((filename) => {
const filepath = path.resolve(filePath, filename);
const stat = fs.statSync(filepath);
const name = filepath.split(path.sep).slice(-1)[0];
if (typeof exclude === "string" && new RegExp(exclude).test(name)) {
return;
}
if (Array.isArray(exclude)) {
for (let i = 0; i < exclude.length; i++) {
const excludeItem = exclude[i];
if (new RegExp(excludeItem).test(name)) {
return;
}
}
}
if (!stat.isFile()) {
readDir(filepath, dirname + path.sep + name);
} else {
const data = new Data({
relativeDir: dirname,
relativeFile: dirname + path.sep + path.parse(filepath).base,
relativeFileNoExt: dirname + path.sep + path.parse(filepath).name,
file: path.parse(filepath).base,
filename: path.parse(filepath).name,
absoluteFile: filepath,
absoluteDir: path.parse(filepath).dir,
});
files.push(data);
}
});
}
readDir(filePath);
return files;
let files: any[] = []
function Data(opts: any) {
this.relativeDir = opts.relativeDir
this.relativeFile = opts.relativeFile
this.filename = opts.filename
this.file = opts.file
this.absoluteFile = opts.absoluteFile
this.relativeFileNoExt = opts.relativeFileNoExt
this.absoluteDir = opts.absoluteDir
}
function readDir(filePath, dirname = ".") {
let res = fs.readdirSync(filePath)
res.forEach(filename => {
const filepath = path.resolve(filePath, filename)
const stat = fs.statSync(filepath)
const name = filepath.split(path.sep).slice(-1)[0]
if (typeof exclude === "string" && new RegExp(exclude).test(name)) {
return
}
if (Array.isArray(exclude)) {
for (let i = 0; i < exclude.length; i++) {
const excludeItem = exclude[i]
if (new RegExp(excludeItem).test(name)) {
return
}
}
}
if (!stat.isFile()) {
readDir(filepath, dirname + path.sep + name)
} else {
const data = new Data({
relativeDir: dirname,
relativeFile: dirname + path.sep + path.parse(filepath).base,
relativeFileNoExt: dirname + path.sep + path.parse(filepath).name,
file: path.parse(filepath).base,
filename: path.parse(filepath).name,
absoluteFile: filepath,
absoluteDir: path.parse(filepath).dir,
})
files.push(data)
}
})
}
readDir(filePath)
return files
}

30
public/js/common/main.js

@ -15,24 +15,26 @@ document.addEventListener("DOMContentLoaded", () => {
})
})
const $messages = Array.prototype.slice.call(document.querySelectorAll(".message-container .message button.delete"), 0)
const $messages = Array.prototype.slice.call(
document.querySelectorAll(".message-container .message button.delete"),
0,
)
$messages.forEach((el, index) => {
let timeID;
let timeID
function click() {
// Get the target from the "data-target" attribute
const target = el.dataset.target
const $target = document.getElementById(target)
el.removeEventListener("click", click)
$target.remove()
clearTimeout(timeID)
// Get the target from the "data-target" attribute
const target = el.dataset.target
const $target = document.getElementById(target)
el.removeEventListener("click", click)
$target.remove()
clearTimeout(timeID)
}
timeID = setTimeout(() => {
const target = el.dataset.target
const $target = document.getElementById(target)
el.removeEventListener("click", click)
$target.remove()
}, (index + 1) * 6000);
const target = el.dataset.target
const $target = document.getElementById(target)
el.removeEventListener("click", click)
$target.remove()
}, (index + 1) * 6000)
el.addEventListener("click", click)
})
})

2
public/js/page/login.js

@ -14,4 +14,4 @@ if (referer) {
let url = new URLSearchParams(window.location.search).get("next")
referer.value = url ? url : ""
}
console.log(referer);
console.log(referer)

122
public/style/common/normalize.css

@ -8,9 +8,9 @@
* 2. Prevent adjustments of font size after orientation changes in iOS.
*/
html {
line-height: 1.15; /* 1 */
-webkit-text-size-adjust: 100%; /* 2 */
html {
line-height: 1.15; /* 1 */
-webkit-text-size-adjust: 100%; /* 2 */
}
/* Sections
@ -21,7 +21,7 @@
*/
body {
margin: 0;
margin: 0;
}
/**
@ -29,7 +29,7 @@ body {
*/
main {
display: block;
display: block;
}
/**
@ -38,8 +38,8 @@ main {
*/
h1 {
font-size: 2em;
margin: 0.67em 0;
font-size: 2em;
margin: 0.67em 0;
}
/* Grouping content
@ -51,9 +51,9 @@ h1 {
*/
hr {
box-sizing: content-box; /* 1 */
height: 0; /* 1 */
overflow: visible; /* 2 */
box-sizing: content-box; /* 1 */
height: 0; /* 1 */
overflow: visible; /* 2 */
}
/**
@ -62,8 +62,8 @@ hr {
*/
pre {
font-family: monospace, monospace; /* 1 */
font-size: 1em; /* 2 */
font-family: monospace, monospace; /* 1 */
font-size: 1em; /* 2 */
}
/* Text-level semantics
@ -74,7 +74,7 @@ pre {
*/
a {
background-color: transparent;
background-color: transparent;
}
/**
@ -83,9 +83,9 @@ a {
*/
abbr[title] {
border-bottom: none; /* 1 */
text-decoration: underline; /* 2 */
text-decoration: underline dotted; /* 2 */
border-bottom: none; /* 1 */
text-decoration: underline; /* 2 */
text-decoration: underline dotted; /* 2 */
}
/**
@ -94,7 +94,7 @@ abbr[title] {
b,
strong {
font-weight: bolder;
font-weight: bolder;
}
/**
@ -105,8 +105,8 @@ strong {
code,
kbd,
samp {
font-family: monospace, monospace; /* 1 */
font-size: 1em; /* 2 */
font-family: monospace, monospace; /* 1 */
font-size: 1em; /* 2 */
}
/**
@ -114,7 +114,7 @@ samp {
*/
small {
font-size: 80%;
font-size: 80%;
}
/**
@ -124,18 +124,18 @@ small {
sub,
sup {
font-size: 75%;
line-height: 0;
position: relative;
vertical-align: baseline;
font-size: 75%;
line-height: 0;
position: relative;
vertical-align: baseline;
}
sub {
bottom: -0.25em;
bottom: -0.25em;
}
sup {
top: -0.5em;
top: -0.5em;
}
/* Embedded content
@ -146,7 +146,7 @@ sup {
*/
img {
border-style: none;
border-style: none;
}
/* Forms
@ -162,10 +162,10 @@ input,
optgroup,
select,
textarea {
font-family: inherit; /* 1 */
font-size: 100%; /* 1 */
line-height: 1.15; /* 1 */
margin: 0; /* 2 */
font-family: inherit; /* 1 */
font-size: 100%; /* 1 */
line-height: 1.15; /* 1 */
margin: 0; /* 2 */
}
/**
@ -174,8 +174,9 @@ textarea {
*/
button,
input { /* 1 */
overflow: visible;
input {
/* 1 */
overflow: visible;
}
/**
@ -184,8 +185,9 @@ input { /* 1 */
*/
button,
select { /* 1 */
text-transform: none;
select {
/* 1 */
text-transform: none;
}
/**
@ -196,7 +198,7 @@ button,
[type="button"],
[type="reset"],
[type="submit"] {
-webkit-appearance: button;
-webkit-appearance: button;
}
/**
@ -207,8 +209,8 @@ button::-moz-focus-inner,
[type="button"]::-moz-focus-inner,
[type="reset"]::-moz-focus-inner,
[type="submit"]::-moz-focus-inner {
border-style: none;
padding: 0;
border-style: none;
padding: 0;
}
/**
@ -219,7 +221,7 @@ button:-moz-focusring,
[type="button"]:-moz-focusring,
[type="reset"]:-moz-focusring,
[type="submit"]:-moz-focusring {
outline: 1px dotted ButtonText;
outline: 1px dotted ButtonText;
}
/**
@ -227,7 +229,7 @@ button:-moz-focusring,
*/
fieldset {
padding: 0.35em 0.75em 0.625em;
padding: 0.35em 0.75em 0.625em;
}
/**
@ -238,12 +240,12 @@ fieldset {
*/
legend {
box-sizing: border-box; /* 1 */
color: inherit; /* 2 */
display: table; /* 1 */
max-width: 100%; /* 1 */
padding: 0; /* 3 */
white-space: normal; /* 1 */
box-sizing: border-box; /* 1 */
color: inherit; /* 2 */
display: table; /* 1 */
max-width: 100%; /* 1 */
padding: 0; /* 3 */
white-space: normal; /* 1 */
}
/**
@ -251,7 +253,7 @@ legend {
*/
progress {
vertical-align: baseline;
vertical-align: baseline;
}
/**
@ -259,7 +261,7 @@ progress {
*/
textarea {
overflow: auto;
overflow: auto;
}
/**
@ -269,8 +271,8 @@ textarea {
[type="checkbox"],
[type="radio"] {
box-sizing: border-box; /* 1 */
padding: 0; /* 2 */
box-sizing: border-box; /* 1 */
padding: 0; /* 2 */
}
/**
@ -279,7 +281,7 @@ textarea {
[type="number"]::-webkit-inner-spin-button,
[type="number"]::-webkit-outer-spin-button {
height: auto;
height: auto;
}
/**
@ -288,8 +290,8 @@ textarea {
*/
[type="search"] {
-webkit-appearance: textfield; /* 1 */
outline-offset: -2px; /* 2 */
-webkit-appearance: textfield; /* 1 */
outline-offset: -2px; /* 2 */
}
/**
@ -297,7 +299,7 @@ textarea {
*/
[type="search"]::-webkit-search-decoration {
-webkit-appearance: none;
-webkit-appearance: none;
}
/**
@ -306,8 +308,8 @@ textarea {
*/
::-webkit-file-upload-button {
-webkit-appearance: button; /* 1 */
font: inherit; /* 2 */
-webkit-appearance: button; /* 1 */
font: inherit; /* 2 */
}
/* Interactive
@ -318,7 +320,7 @@ textarea {
*/
details {
display: block;
display: block;
}
/*
@ -326,7 +328,7 @@ details {
*/
summary {
display: list-item;
display: list-item;
}
/* Misc
@ -337,7 +339,7 @@ summary {
*/
template {
display: none;
display: none;
}
/**
@ -345,5 +347,5 @@ template {
*/
[hidden] {
display: none;
display: none;
}

32
public/style/common/style.css

@ -1,21 +1,21 @@
html{
overflow-y: auto
html {
overflow-y: auto;
}
.message-container{
position: fixed;
right: 0;
top: 0;
z-index: 999;
overflow: auto;
height: 100vh;
.message-container {
position: fixed;
right: 0;
top: 0;
z-index: 999;
overflow: auto;
height: 100vh;
}
.message-container::-webkit-scrollbar{
display: none;
.message-container::-webkit-scrollbar {
display: none;
}
.message-container .message{
min-width: 250px;
max-width: 250px;
margin: 25px;
box-shadow: rgba(0, 0, 0, 0.24) 0px 3px 8px;
.message-container .message {
min-width: 250px;
max-width: 250px;
margin: 25px;
box-shadow: rgba(0, 0, 0, 0.24) 0px 3px 8px;
}

4
route.txt

@ -1,6 +1,6 @@
D:\1XYX\demo\hapi-demo\source\route\htmx对应路径:
/home/topuser/桌面/demo/hapi-demo/source/route/htmx对应路径:
不需权限 : GET /htmx/clicked
D:\1XYX\demo\hapi-demo\source\route\views对应路径:
/home/topuser/桌面/demo/hapi-demo/source/route/views对应路径:
不需权限(提供无需验证): GET /404
不需权限 : GET /css
不需权限(提供无需验证): GET /

38
source/auth/index.ts

@ -1,27 +1,27 @@
import { Req } from "#/global";
import { Req } from "#/global"
export async function validateJwt(decoded, request: Req, h) {
if (decoded.id) {
const User = request.getModel("User")
const result = await User.findOne({ where: { id: decoded.id } });
if (result == null) {
return { isValid: false };
if (decoded.id) {
const User = request.getModel("User")
const result = await User.findOne({ where: { id: decoded.id } })
if (result == null) {
return { isValid: false }
}
return { isValid: true }
} else {
return { isValid: false }
}
return { isValid: true };
} else {
return { isValid: false };
}
}
export async function validateSession(request: Req, session) {
const User = request.getModel("User")
if (session.id) {
const result = await User.findOne({ where: { id: session.id } });
if (result == null) {
return { valid: false };
const User = request.getModel("User")
if (session.id) {
const result = await User.findOne({ where: { id: session.id } })
if (result == null) {
return { valid: false }
}
return { valid: true, credentials: result }
} else {
return { valid: false }
}
return { valid: true, credentials: result };
} else {
return { valid: false };
}
}

68
source/log4js_config.ts

@ -1,37 +1,37 @@
import path from "path";
import path from "path"
export default function () {
return {
appenders: {
file: {
type: "file",
filename: path.resolve(__dirname, "../", "./log", "./Site.log"),
},
SQL: {
type: "file",
filename: path.resolve(__dirname, "../", "./log", "./SQL.log"),
},
console: {
type: "console",
},
},
categories: {
default: {
appenders: ["console"],
level: "all",
},
HAPI: {
appenders: ["console"],
level: "all",
},
Site: {
appenders: ["file", "console"],
level: "debug",
},
SQL: {
appenders: ["SQL"],
level: "debug",
},
},
};
return {
appenders: {
file: {
type: "file",
filename: path.resolve(__dirname, "../", "./log", "./Site.log"),
},
SQL: {
type: "file",
filename: path.resolve(__dirname, "../", "./log", "./SQL.log"),
},
console: {
type: "console",
},
},
categories: {
default: {
appenders: ["console"],
level: "all",
},
HAPI: {
appenders: ["console"],
level: "all",
},
Site: {
appenders: ["file", "console"],
level: "debug",
},
SQL: {
appenders: ["SQL"],
level: "debug",
},
},
}
}

30
source/main.ts

@ -1,21 +1,21 @@
// require("dotenv").config();
import { configure, getLogger } from "log4js";
import log4jsConfig from "./log4js_config";
import { configure, getLogger } from "log4js"
import log4jsConfig from "./log4js_config"
configure(log4jsConfig());
const loggerSite = getLogger("Site");
const loggerSQL = getLogger("SQL");
const logger = getLogger("HAPI");
configure(log4jsConfig())
const loggerSite = getLogger("Site")
const loggerSQL = getLogger("SQL")
const logger = getLogger("HAPI")
loggerSite.level = "debug";
loggerSQL.level = "debug";
loggerSite.level = "debug"
loggerSQL.level = "debug"
global.logger = logger;
global.loggerSite = loggerSite;
global.loggerSQL = loggerSQL;
global.logger = logger
global.loggerSite = loggerSite
global.loggerSQL = loggerSQL
import { run } from "./run";
import { run } from "./run"
run().then((server) => {
global.server = server;
});
run().then(server => {
global.server = server
})

12
source/models/Color.ts

@ -4,20 +4,20 @@ module.exports = function (sequelize, DataTypes) {
{
// 图片地址
color: {
type: DataTypes.STRING
type: DataTypes.STRING,
},
title: {
type: DataTypes.STRING,
allowNull: true
allowNull: true,
},
describe: {
type: DataTypes.STRING,
allowNull: true
allowNull: true,
},
},
{
timestamps: false,
}
);
},
)
return Color
};
}

12
source/models/Constant.ts

@ -4,21 +4,21 @@ module.exports = function (sequelize, DataTypes) {
{
// 键
key: {
type: DataTypes.STRING
type: DataTypes.STRING,
},
// 值
value: {
type: DataTypes.STRING,
allowNull: true
allowNull: true,
},
describe: {
type: DataTypes.STRING,
allowNull: true
allowNull: true,
},
},
{
timestamps: false,
}
);
},
)
return Constant
};
}

93
source/models/User.ts

@ -1,64 +1,67 @@
import { Sequelize, DataTypes, Optional, Model } from "sequelize"
interface UserAttributes {
id: number;
username: string;
password: string;
nickname: string;
email: string;
id: number
username: string
password: string
nickname: string
email: string
createdAt?: Date;
updatedAt?: Date;
deletedAt?: Date;
createdAt?: Date
updatedAt?: Date
deletedAt?: Date
}
export interface UserInput extends Optional<UserAttributes, 'id'> { }
export interface UserOuput extends Required<UserAttributes> { }
export interface UserInput extends Optional<UserAttributes, "id"> {}
export interface UserOuput extends Required<UserAttributes> {}
export type TUserModel = ReturnType<typeof UserModel>
type DT = typeof DataTypes
export default function UserModel(sequelize: Sequelize, DataTypes: DT) {
class User extends Model<UserAttributes, UserInput> implements UserAttributes {
public id: number;
public username: string;
public password: string;
public nickname: string;
public email: string;
public id: number
public username: string
public password: string
public nickname: string
public email: string
// timestamps!
public readonly createdAt!: Date;
public readonly updatedAt!: Date;
public readonly deletedAt!: Date;
public readonly createdAt!: Date
public readonly updatedAt!: Date
public readonly deletedAt!: Date
}
User.init({
id: {
type: DataTypes.INTEGER.UNSIGNED,
autoIncrement: true,
primaryKey: true,
User.init(
{
id: {
type: DataTypes.INTEGER.UNSIGNED,
autoIncrement: true,
primaryKey: true,
},
username: {
type: DataTypes.STRING,
allowNull: false,
},
password: {
type: DataTypes.STRING,
allowNull: false,
},
nickname: {
type: DataTypes.STRING,
allowNull: false,
},
email: {
type: DataTypes.STRING,
},
},
username: {
type: DataTypes.STRING,
allowNull: false
{
sequelize,
timestamps: true,
paranoid: true, // 对模型施加了一个软删除
},
password: {
type: DataTypes.STRING,
allowNull: false
},
nickname: {
type: DataTypes.STRING,
allowNull: false
},
email: {
type: DataTypes.STRING,
}
}, {
sequelize,
timestamps: true,
paranoid: true // 对模型施加了一个软删除
})
)
// 覆盖User的toJSON方法
interface User{
toJSON: ()=> UserOuput
interface User {
toJSON: () => UserOuput
}
return User
};
}

26
source/models/ha/user.ts

@ -2,17 +2,19 @@ import { Sequelize, DataTypes } from "sequelize"
type DT = typeof DataTypes
module.exports = function (sequelize: Sequelize, DataTypes: DT) {
const User = sequelize.define('ha-user', {
username: {
type: DataTypes.STRING,
allowNull: false
const User = sequelize.define(
"ha-user",
{
username: {
type: DataTypes.STRING,
allowNull: false,
},
password: {
type: DataTypes.STRING,
allowNull: false,
},
},
password: {
type: DataTypes.STRING,
allowNull: false
}
}, {
});
{},
)
return User
};
}

42
source/models/ha/user_info.ts

@ -2,28 +2,30 @@ import { Sequelize, DataTypes } from "sequelize"
type DT = typeof DataTypes
module.exports = function (sequelize: Sequelize, DataTypes: DT) {
const UserInfo = sequelize.define('ha-user_info', {
nickname: {
type: DataTypes.STRING,
allowNull: false
const UserInfo = sequelize.define(
"ha-user_info",
{
nickname: {
type: DataTypes.STRING,
allowNull: false,
},
email: {
type: DataTypes.STRING,
},
avatar: {
type: DataTypes.STRING,
},
tel: {
type: DataTypes.STRING,
},
},
email: {
type: DataTypes.STRING,
},
avatar: {
type: DataTypes.STRING,
},
tel: {
type: DataTypes.STRING,
}
}, {
});
{},
)
// @ts-ignore
UserInfo.associate = function (models) {
models['ha-user'].hasOne(models['ha-user_info']);
models['ha-user_info'].belongsTo(models['ha-user'], { foreignKey: 'user_id' });
};
models["ha-user"].hasOne(models["ha-user_info"])
models["ha-user_info"].belongsTo(models["ha-user"], { foreignKey: "user_id" })
}
return UserInfo
};
}

52
source/plugins/file-plugin.ts

@ -1,29 +1,29 @@
import { publicDir } from "@/util";
const Inert = require("@hapi/inert");
import { publicDir } from "@/util"
const Inert = require("@hapi/inert")
const filePlugin = {
name: "filePlugin",
version: "0.0.1",
register: async function (server, options) {
server.settings.routes = {
files: {
relativeTo: publicDir,
},
};
await server.register(Inert);
server.route({
method: "GET",
path: "/public/{param*}",
config: { auth: false },
handler: {
directory: {
path: publicDir,
index: true,
redirectToSlash: true,
},
},
});
},
};
name: "filePlugin",
version: "0.0.1",
register: async function (server, options) {
server.settings.routes = {
files: {
relativeTo: publicDir,
},
}
await server.register(Inert)
server.route({
method: "GET",
path: "/public/{param*}",
config: { auth: false },
handler: {
directory: {
path: publicDir,
index: true,
redirectToSlash: true,
},
},
})
},
}
export default filePlugin;
export default filePlugin

286
source/plugins/index.ts

@ -1,155 +1,159 @@
import filePlugin from "./file-plugin";
import path from "path";
import { baseDir, sourceDir } from "@/util";
import { plugin as routePlugin } from "@noderun/hapi-router";
import {ServerRegisterPluginObject, Plugin, Server, Request, ResponseObject} from "@hapi/hapi"
import filePlugin from "./file-plugin"
import path from "path"
import { baseDir, sourceDir } from "@/util"
import { plugin as routePlugin } from "@noderun/hapi-router"
import { ServerRegisterPluginObject, Plugin, Server, Request, ResponseObject } from "@hapi/hapi"
import Hoek from "@hapi/hoek"
import HapiYar from "@hapi/yar";
import HapiYar from "@hapi/yar"
import HapiCrumb from "@hapi/crumb"
import { Stream } from "stream";
import HapiPino from "hapi-pino";
import { Stream } from "stream"
import HapiPino from "hapi-pino"
const pino = require('pino')
const pino = require("pino")
const transport = pino.transport({
targets: [
// process.env.NODE_ENV !== 'production' ? {
// target: 'pino-pretty',
// options: {
// colorize: true,
// translateTime: 'HH:MM:ss.l mmmm dS yyyy "UTC"'
// },
// level: 'info' } : { target: 'pino/file', level: 'info' },
{ target: 'pino-pretty', level: 'trace', options: {
destination: path.resolve(baseDir, "./log/pino.log"),
colorize: true,
translateTime: 'HH:MM:ss.l mmmm dS yyyy "UTC"',
mkdir: true,
// append: false
}}
// { target: 'pino/file', level: 'trace', options: {
// destination: path.resolve(baseDir, "./log/pino.log"),
// mkdir: true,
// // append: false
// }}
]
targets: [
// process.env.NODE_ENV !== 'production' ? {
// target: 'pino-pretty',
// options: {
// colorize: true,
// translateTime: 'HH:MM:ss.l mmmm dS yyyy "UTC"'
// },
// level: 'info' } : { target: 'pino/file', level: 'info' },
{
target: "pino-pretty",
level: "trace",
options: {
destination: path.resolve(baseDir, "./log/pino.log"),
colorize: true,
translateTime: 'HH:MM:ss.l mmmm dS yyyy "UTC"',
mkdir: true,
// append: false
},
},
// { target: 'pino/file', level: 'trace', options: {
// destination: path.resolve(baseDir, "./log/pino.log"),
// mkdir: true,
// // append: false
// }}
],
})
export default [
{
plugin: HapiPino,
options: {
logPayload: true,
logQueryParams: true,
logPathParams: true,
logRouteTags: true,
// logRequestStart: true,
instance: pino(transport),
ignoreFunc: (options, request) => {
return request.path.startsWith('/public') || request.path.startsWith('/404')
{
plugin: HapiPino,
options: {
logPayload: true,
logQueryParams: true,
logPathParams: true,
logRouteTags: true,
// logRequestStart: true,
instance: pino(transport),
ignoreFunc: (options, request) => {
return request.path.startsWith("/public") || request.path.startsWith("/404")
},
// stream: fs.createWriteStream(path.resolve(baseDir, "./log/pino.log"), { encoding: "utf-8" })
// prettyPrint: process.env.NODE_ENV !== 'production',
// Redact Authorization headers, see https://getpino.io/#/docs/redaction
// redact: ['req.headers.cookie']
},
},
{
plugin: filePlugin as unknown as Plugin<any>,
},
{
plugin: routePlugin as Plugin<any>,
options: {
auth: ["/api"],
sourceDir: [
// {
// dir: path.resolve(sourceDir, "route/api"),
// prefix: "api"
// },
{
dir: path.resolve(sourceDir, "route/htmx"),
prefix: "htmx",
},
{
dir: path.resolve(sourceDir, "route/views"),
prefix: "",
},
],
type: "session",
},
// stream: fs.createWriteStream(path.resolve(baseDir, "./log/pino.log"), { encoding: "utf-8" })
// prettyPrint: process.env.NODE_ENV !== 'production',
// Redact Authorization headers, see https://getpino.io/#/docs/redaction
// redact: ['req.headers.cookie']
}
},
{
plugin: filePlugin as unknown as Plugin<any>,
},
{
plugin: routePlugin as Plugin<any>,
options: {
auth: ['/api'],
sourceDir: [
// {
// dir: path.resolve(sourceDir, "route/api"),
// prefix: "api"
// },
{
dir: path.resolve(sourceDir, "route/htmx"),
prefix: "htmx"
},
{
dir: path.resolve(sourceDir, "route/views"),
prefix: ""
},
],
type: "session"
},
},
{
plugin: {
name: "flash",
version: "0.0.1",
// https://github.com/hks-epod/paydash/blob/master/lib/flash.js
register: function (server: Server, options) {
server.ext('onPreResponse', async function(request: Request, h) {
// @ts-ignore
if(request.response.variety === "file") return h.continue; // 返回文件时不处理
if(request.path.startsWith("/api") || request.path.startsWith("/htmx")) return h.continue;
// 需要设置auth是try或者true才行
const isLogin = request.auth.isAuthenticated;
loggerSite.debug(`是否登录:${isLogin}, 请求路径:${request.path}, 请求方法:${request.method}`)
{
plugin: {
name: "flash",
version: "0.0.1",
// https://github.com/hks-epod/paydash/blob/master/lib/flash.js
register: function (server: Server, options) {
server.ext("onPreResponse", async function (request: Request, h) {
// @ts-ignore
if (request.response.variety === "file") return h.continue // 返回文件时不处理
if (request.path.startsWith("/api") || request.path.startsWith("/htmx")) return h.continue
// 需要设置auth是try或者true才行
const isLogin = request.auth.isAuthenticated
loggerSite.debug(`是否登录:${isLogin}, 请求路径:${request.path}, 请求方法:${request.method}`)
// @ts-ignore
// console.log(isLogin, request.path, request.response.variety);
// let user;
// if(isLogin){
// const { id } = request.auth.credentials;
// const User = request.getModel("User")
// user = <any>await User.findOne({ where: { id: id } });
// user = user.toJSON();
// delete user.password;
// }
// @ts-ignore
if (request.yar && request.yar.flash && request.response.variety === 'view') {
var flash = request.yar.flash();
request.yar.set('_flash', {});
// @ts-ignore
request.response.source.context = Hoek.applyToDefaults(
{
flash: !!Object.keys(flash).length?flash:false,
isLogin: isLogin,
user: isLogin?request.auth.credentials:false,
},
// @ts-ignore
request.response.source.context
);
// console.log(isLogin, request.path, request.response.variety);
// let user;
// if(isLogin){
// const { id } = request.auth.credentials;
// const User = request.getModel("User")
// user = <any>await User.findOne({ where: { id: id } });
// user = user.toJSON();
// delete user.password;
// }
// @ts-ignore
if (request.yar && request.yar.flash && request.response.variety === "view") {
var flash = request.yar.flash()
request.yar.set("_flash", {})
// @ts-ignore
request.response.source.context = Hoek.applyToDefaults(
{
flash: !!Object.keys(flash).length ? flash : false,
isLogin: isLogin,
user: isLogin ? request.auth.credentials : false,
},
// @ts-ignore
request.response.source.context,
)
// @ts-ignore
}
return h.continue
})
},
} as Plugin<any>,
},
{
plugin: HapiYar,
options: {
name: "yar",
storeBlank: false,
cookieOptions: {
password: "dsRhw1Y5UZqB8SjfClbkrX9PF7yuDMV3JItcW0G4vgpaxONo6mzenHLQET2AiKyPUjjdgjo10amjfghy",
path: "/",
isSecure: false,
},
},
},
{
plugin: HapiCrumb,
options: {
autoGenerate: true,
logUnauthorized: true,
skip: function (request, reply) {
// 流的话直接通过,不需要验证,主要用于传递form-data数据时这里通不过
if (request.payload instanceof Stream) {
return true
}
return h.continue;
});
}
} as Plugin<any>
},
{
plugin: HapiYar,
options: {
name: "yar",
storeBlank: false,
cookieOptions: {
password: "dsRhw1Y5UZqB8SjfClbkrX9PF7yuDMV3JItcW0G4vgpaxONo6mzenHLQET2AiKyPUjjdgjo10amjfghy",
path: '/',
isSecure: false
}
}
},
{
plugin: HapiCrumb,
options: {
autoGenerate: true,
logUnauthorized: true,
skip: function(request, reply) {
// 流的话直接通过,不需要验证,主要用于传递form-data数据时这里通不过
if(request.payload instanceof Stream){
return true
}
return false;
return false
},
cookieOptions: {
path: "/",
isSecure: false,
},
},
cookieOptions: {
path: '/',
isSecure: false
}
}
},
] as unknown as ServerRegisterPluginObject<any>;
},
] as unknown as ServerRegisterPluginObject<any>

20
source/route/api/index.ts

@ -1,13 +1,13 @@
import { Req, Res, ReturnValue } from "#/global";
import { auth, route } from "@noderun/hapi-router";
import { Req, Res, ReturnValue } from "#/global"
import { auth, route } from "@noderun/hapi-router"
export default class {
@route("/{path*}")
@auth(false)
async any(req: Req, h: Res): ReturnValue {
return {
code: 404,
data: null,
};
}
@route("/{path*}")
@auth(false)
async any(req: Req, h: Res): ReturnValue {
return {
code: 404,
data: null,
}
}
}

143
source/route/api/v1/upload/_upload.ts

@ -1,83 +1,82 @@
import path from "path";
import { gSuccess, gFail, uploadDir, uploadPath } from "@/util";
import path from "path"
import { gSuccess, gFail, uploadDir, uploadPath } from "@/util"
import { dateTimeFormat } from "@/util/util"
const fs = require("fs");
const multiparty = require("multiparty");
const FileType = require("file-type");
const fs = require("fs")
const multiparty = require("multiparty")
const FileType = require("file-type")
function saveFile(file) {
return new Promise(async (resolve, reject) => {
const filename = file.originalFilename;
const uploadedPath = file.path;
const filetype = await FileType.fromFile(uploadedPath);
const _file = path.parse(filename);
if (filetype && (filetype.ext == "jpg" || filetype.ext == "png")) {
let _name = _file.name + "_" + dateTimeFormat(new Date(), "yyyy_MM_dd") + "_" + new Date().getTime() + _file.ext
const dstPath = path.resolve(uploadDir, _name);
fs.rename(uploadedPath, dstPath, function (err) {
if (err) {
console.log("rename error: " + err);
reject();
return new Promise(async (resolve, reject) => {
const filename = file.originalFilename
const uploadedPath = file.path
const filetype = await FileType.fromFile(uploadedPath)
const _file = path.parse(filename)
if (filetype && (filetype.ext == "jpg" || filetype.ext == "png")) {
let _name =
_file.name + "_" + dateTimeFormat(new Date(), "yyyy_MM_dd") + "_" + new Date().getTime() + _file.ext
const dstPath = path.resolve(uploadDir, _name)
fs.rename(uploadedPath, dstPath, function (err) {
if (err) {
console.log("rename error: " + err)
reject()
} else {
resolve(dstPath)
}
})
} else {
resolve(dstPath);
fs.unlinkSync(uploadedPath)
reject(new Error(filename + "文件不是图片"))
}
});
} else {
fs.unlinkSync(uploadedPath);
reject(new Error(filename + "文件不是图片"));
}
});
})
}
export default function (req, h) {
const form = new multiparty.Form({
uploadDir: uploadDir, //路径需要对应自己的项目更改
/*设置文件保存路径 */
encoding: "utf-8",
/*编码设置 */
maxFilesSize: 20000 * 1024 * 1024,
/*设置文件最大值 20MB */
keepExtensions: true,
/*保留后缀*/
});
return new Promise(async (resolve, reject) => {
form.on("part", function (part) {
console.log(part.filename);
});
form.on("progress", function (bytesReceived, bytesExpected) {
if (bytesExpected === null) {
return;
}
const form = new multiparty.Form({
uploadDir: uploadDir, //路径需要对应自己的项目更改
/*设置文件保存路径 */
encoding: "utf-8",
/*编码设置 */
maxFilesSize: 20000 * 1024 * 1024,
/*设置文件最大值 20MB */
keepExtensions: true,
/*保留后缀*/
})
return new Promise(async (resolve, reject) => {
form.on("part", function (part) {
console.log(part.filename)
})
form.on("progress", function (bytesReceived, bytesExpected) {
if (bytesExpected === null) {
return
}
var percentComplete = (bytesReceived / bytesExpected) * 100;
console.log(
"the form is " + Math.floor(percentComplete) + "%" + " complete"
);
});
form.parse(req.payload, async function (err, fields, files) {
// console.log(err, fields, files);
var percentComplete = (bytesReceived / bytesExpected) * 100
console.log("the form is " + Math.floor(percentComplete) + "%" + " complete")
})
form.parse(req.payload, async function (err, fields, files) {
// console.log(err, fields, files);
if (err) {
resolve(err.message);
return;
}
const errList = [];
const fileList = [];
for (let i = 0; i < files.file.length; i++) {
const file = files.file[i];
try {
const dstPath = await saveFile(file);
fileList.push(dstPath);
} catch (error) {
errList.push(error.message);
}
}
if (errList.length) {
resolve(gFail(null, errList.join("\n")));
return;
}
// resolve(h.view("views/upload.ejs"));
resolve([...new Set(fileList)]);
});
});
if (err) {
resolve(err.message)
return
}
const errList = []
const fileList = []
for (let i = 0; i < files.file.length; i++) {
const file = files.file[i]
try {
const dstPath = await saveFile(file)
fileList.push(dstPath)
} catch (error) {
errList.push(error.message)
}
}
if (errList.length) {
resolve(gFail(null, errList.join("\n")))
return
}
// resolve(h.view("views/upload.ejs"));
resolve([...new Set(fileList)])
})
})
}

48
source/route/api/v1/upload/index.ts

@ -1,29 +1,27 @@
import { config, method, route, swagger, validate } from "@noderun/hapi-router";
import UploadFunc from "./_upload";
import Joi from "joi";
import { config, method, route, swagger, validate } from "@noderun/hapi-router"
import UploadFunc from "./_upload"
import Joi from "joi"
export default class {
index(request, h) {
return h.view("views/demo.ejs");
}
index(request, h) {
return h.view("views/demo.ejs")
}
@config({
payload: {
maxBytes: 20000 * 1024 * 1024,
output: "stream",
parse: false,
// multipart: true,
allow: "multipart/form-data",
@config({
payload: {
maxBytes: 20000 * 1024 * 1024,
output: "stream",
parse: false,
// multipart: true,
allow: "multipart/form-data",
},
})
@method("POST")
@swagger("文件上传", "文件上传a ", ["sum", "api"])
async upload(req, h) {
const startTime = new Date().getTime()
const res = await UploadFunc(req, h)
const endTime = new Date().getTime()
console.log(`该请求处理时间为:${Number(endTime - startTime).toFixed(2)}ms`)
return res
}
})
@method("POST")
@swagger("文件上传", "文件上传a ", ["sum", "api"])
async upload(req, h) {
const startTime = new Date().getTime();
const res = await UploadFunc(req, h);
const endTime = new Date().getTime();
console.log(
`该请求处理时间为:${Number(endTime - startTime).toFixed(2)}ms`
);
return res;
}
}

176
source/route/api/v1/user/index.ts

@ -1,102 +1,96 @@
import {
auth,
method,
route,
swagger,
validate,
config,
} from "@noderun/hapi-router";
import { gSuccess, gFail } from "@/util";
import * as bcrypt from "bcrypt";
import * as jwt from "jsonwebtoken";
import * as Joi from "joi";
import { UserSchema } from "@/schema";
import { ReturnValue, Req, Res } from "#/global";
import { auth, method, route, swagger, validate, config } from "@noderun/hapi-router"
import { gSuccess, gFail } from "@/util"
import * as bcrypt from "bcrypt"
import * as jwt from "jsonwebtoken"
import * as Joi from "joi"
import { UserSchema } from "@/schema"
import { ReturnValue, Req, Res } from "#/global"
export default class {
@validate({
payload: UserSchema,
})
@method("POST")
@swagger("用户注册", "返回注册用户的信息", ["api"])
@auth(false)
async register(request: Req, h: Res): ReturnValue {
let { username, password, email } = request.payload as any;
if (!username) username = email;
const User = request.getModel("User")
logger.trace(username, email);
try {
const result = await User.findOne({ where: { username: username } });
if (result != null) {
return gFail(null, "已存在该用户");
}
let salt = bcrypt.genSaltSync(10);
let pwdLock = bcrypt.hashSync(password, salt);
await User.create({ username, password: pwdLock, email });
return gSuccess("success", "you have a good heart.");
} catch (e) {
return gFail(null, "新建用户失败");
@validate({
payload: UserSchema,
})
@method("POST")
@swagger("用户注册", "返回注册用户的信息", ["api"])
@auth(false)
async register(request: Req, h: Res): ReturnValue {
let { username, password, email, } = request.payload as any
if (!username) username = email
const User = request.getModel("User")
logger.trace(username, email)
try {
const result = await User.findOne({ where: { username: username } })
if (result != null) {
return gFail(null, "已存在该用户")
}
let salt = bcrypt.genSaltSync(10)
let pwdLock = bcrypt.hashSync(password, salt)
// @ts-ignore
await User.create({ username, password: pwdLock, email })
return gSuccess("success", "you have a good heart.")
} catch (e) {
return gFail(null, "新建用户失败")
}
}
}
@method("POST")
async logout(request: Req, h: Res): ReturnValue {
request.cookieAuth.clear();
return gSuccess("success");
}
@validate({
payload: UserSchema,
})
@auth(false)
@method("POST")
@swagger("用户登录", "返回注册用户的信息", ["api"])
async login(request: Req, h: Res): ReturnValue {
let { username, password } = request.payload as any;
const User = request.getModel("User")
const result = <any>await User.findOne({ where: { username: username } });
if (result == null) {
return gFail(null, "不存在该用户");
@method("POST")
async logout(request: Req, h: Res): ReturnValue {
request.cookieAuth.clear()
return gSuccess("success")
}
const validUser = bcrypt.compareSync(password, result.password);
if (!validUser) {
return gFail(null, "密码不正确");
@validate({
payload: UserSchema,
})
@auth(false)
@method("POST")
@swagger("用户登录", "返回注册用户的信息", ["api"])
async login(request: Req, h: Res): ReturnValue {
let { username, password } = request.payload as any
const User = request.getModel("User")
const result = <any>await User.findOne({ where: { username: username } })
if (result == null) {
return gFail(null, "不存在该用户")
}
const validUser = bcrypt.compareSync(password, result.password)
if (!validUser) {
return gFail(null, "密码不正确")
}
//===== JWT ===== Start
// let token = jwt.sign({ id: result.id }, process.env.KEY);
// return gSuccess({ token: token });
//===== JWT ===== End
//===== session ===== Start
request.cookieAuth.set({ id: result.id })
//===== session ===== End
return gSuccess({})
}
//===== JWT ===== Start
// let token = jwt.sign({ id: result.id }, process.env.KEY);
// return gSuccess({ token: token });
//===== JWT ===== End
//===== session ===== Start
request.cookieAuth.set({ id: result.id });
//===== session ===== End
return gSuccess({});
}
@method("DELETE")
@auth()
@swagger("删除用户", "删除用户账号", ["sum"])
async del(request: Req, h: Res): ReturnValue {
const { id } = request.auth.credentials;
const User = request.getModel("User")
let result = await User.findOne({ where: { id: id } });
if (result == null) {
return gFail(null, "不存在该用户");
@method("DELETE")
@auth()
@swagger("删除用户", "删除用户账号", ["sum"])
async del(request: Req, h: Res): ReturnValue {
const { id } = request.auth.credentials
const User = request.getModel("User")
let result = await User.findOne({ where: { id: id } })
if (result == null) {
return gFail(null, "不存在该用户")
}
await result.destroy()
return gSuccess(null, "删除成功")
}
await result.destroy();
return gSuccess(null, "删除成功");
}
@method("GET")
@swagger("获取用户信息", "返回注册用户的信息", ["用户操作", "api"])
async userinfo(request: Req, h: Res): ReturnValue {
const { id } = request.auth.credentials;
const User = request.getModel("User")
let result = <any>await User.findOne({ where: { id: id } });
if (result == null) {
return gFail(null, "不存在该用户");
@method("GET")
@swagger("获取用户信息", "返回注册用户的信息", ["用户操作", "api"])
async userinfo(request: Req, h: Res): ReturnValue {
const { id } = request.auth.credentials
const User = request.getModel("User")
let result = <any>await User.findOne({ where: { id: id } })
if (result == null) {
return gFail(null, "不存在该用户")
}
result = result.toJSON()
delete result.password
return gSuccess(result)
}
result = result.toJSON();
delete result.password;
return gSuccess(result);
}
}

2
source/route/htmx/index.ts

@ -8,6 +8,6 @@ export default class {
@route("/clicked")
async clicked(request: Req, h: Res): ReturnValue {
// return 'aaaaaaaaaaaaaabbbbbbbbbbbbbbbb'
return h.view('htmx/a.pug')
return h.view("htmx/a.pug")
}
}

164
source/route/views/index.ts

@ -1,96 +1,96 @@
import { Req, Res, ReturnValue } from "#/global";
import { LoginUserSchema, RegisterUserSchema, UserSchema } from "@/schema";
import { gFail, gSuccess } from "@/util";
import { auth, config, method, route, validate } from "@noderun/hapi-router";
import * as bcrypt from "bcrypt";
import { Req, Res, ReturnValue } from "#/global"
import { LoginUserSchema, RegisterUserSchema, UserSchema } from "@/schema"
import { gFail, gSuccess } from "@/util"
import { auth, config, method, route, validate } from "@noderun/hapi-router"
import * as bcrypt from "bcrypt"
/**
*
*/
export default class {
@route("/login")
@auth("try")
@method("GET")
async login_GET(request: Req, h: Res): ReturnValue {
if (request.auth.isAuthenticated) {
request.yar.flash('warning', '您已经登陆');
return h.redirect("/")
} else {
logger.debug("未登录");
@route("/login")
@auth("try")
@method("GET")
async login_GET(request: Req, h: Res): ReturnValue {
if (request.auth.isAuthenticated) {
request.yar.flash("warning", "您已经登陆")
return h.redirect("/")
} else {
logger.debug("未登录")
}
return h.view("views/login.pug")
}
return h.view("views/login.pug");
}
@validate({
payload: LoginUserSchema,
$errto: '/login',
// failAction: 'log'
failAction: 'function',
failReason: '用户名或密码错误,请重试',
})
@method("POST")
@route("/login")
async login_POST(request: Req, h: Res): ReturnValue {
const { username, password, referrer } = request.payload as any;
const User = request.getModel("User");
const account = <any>await User.findOne({ where: { username: username } });
@validate({
payload: LoginUserSchema,
$errto: "/login",
// failAction: 'log'
failAction: "function",
failReason: "用户名或密码错误,请重试",
})
@method("POST")
@route("/login")
async login_POST(request: Req, h: Res): ReturnValue {
const { username, password, referrer } = request.payload as any
const User = request.getModel("User")
const account = <any>await User.findOne({ where: { username: username } })
if (!account || !(await bcrypt.compare(password, account.password))) {
request.yar.flash('error', 'Invalid username or password');
return h.redirect("/login");
if (!account || !(await bcrypt.compare(password, account.password))) {
request.yar.flash("error", "Invalid username or password")
return h.redirect("/login")
}
request.cookieAuth.set({ id: account.id, nickname: account.nickname })
request.yar.flash("success", "用户已登录")
return h.redirect(referrer ? referrer : "/")
}
request.cookieAuth.set({ id: account.id, nickname: account.nickname });
request.yar.flash('success', '用户已登录');
return h.redirect(referrer ? referrer : "/");
}
@method("GET")
@auth()
async logout(request: Req, h: Res): ReturnValue {
request.yar.flash('success', '用户已退出');
request.cookieAuth.clear();
return h.redirect('/');
}
@route("/register")
@auth("try")
@method("GET")
async registerView(request: Req, h: Res): ReturnValue {
if (request.auth.isAuthenticated) {
request.yar.flash('warning', '您已经登陆');
return h.redirect("/")
} else {
logger.debug("未登录");
@method("GET")
@auth()
async logout(request: Req, h: Res): ReturnValue {
request.yar.flash("success", "用户已退出")
request.cookieAuth.clear()
return h.redirect("/")
}
return h.view("views/login.pug");
}
@validate({
payload: RegisterUserSchema,
})
@method("POST")
async register(request: Req, h: Res): ReturnValue {
let { username, password, email, nickname } = request.payload as any;
if(!email){
request.yar.flash('error', '必须填写邮箱');
return h.redirect("/login");
@route("/register")
@auth("try")
@method("GET")
async registerView(request: Req, h: Res): ReturnValue {
if (request.auth.isAuthenticated) {
request.yar.flash("warning", "您已经登陆")
return h.redirect("/")
} else {
logger.debug("未登录")
}
return h.view("views/login.pug")
}
if (!username) username = email;
if (!nickname) nickname = username;
const User = request.getModel("User")
logger.trace(username, email);
try {
const result = await User.findOne({ where: { username: username } });
if (result != null) {
request.yar.flash('error', '已存在该用户');
return h.redirect("/login");
}
let salt = bcrypt.genSaltSync(10);
let pwdLock = bcrypt.hashSync(password, salt);
await User.create({ username, nickname, password: pwdLock, email });
return h.redirect("/")
} catch (e) {
request.yar.flash('error', '注册用户失败');
return h.redirect("/login");
@validate({
payload: RegisterUserSchema,
})
@method("POST")
async register(request: Req, h: Res): ReturnValue {
let { username, password, email, nickname } = request.payload as any
if (!email) {
request.yar.flash("error", "必须填写邮箱")
return h.redirect("/login")
}
if (!username) username = email
if (!nickname) nickname = username
const User = request.getModel("User")
logger.trace(username, email)
try {
const result = await User.findOne({ where: { username: username } })
if (result != null) {
request.yar.flash("error", "已存在该用户")
return h.redirect("/login")
}
let salt = bcrypt.genSaltSync(10)
let pwdLock = bcrypt.hashSync(password, salt)
await User.create({ username, nickname, password: pwdLock, email })
return h.redirect("/")
} catch (e) {
request.yar.flash("error", "注册用户失败")
return h.redirect("/login")
}
}
}
}

165
source/route/views/index/index.ts

@ -1,100 +1,93 @@
import {
auth,
config,
method,
route,
swagger,
validate,
} from "@noderun/hapi-router";
import { Req, Res, ReturnValue } from "#/global";
import path from "path";
import fs from "fs-extra";
import { baseDir } from "@/util";
import MarkdownIt from "markdown-it";
import { auth, config, method, route, swagger, validate } from "@noderun/hapi-router"
import { Req, Res, ReturnValue } from "#/global"
import path from "path"
import fs from "fs-extra"
import { baseDir } from "@/util"
import MarkdownIt from "markdown-it"
export default class Index {
async css(request: Req, h: Res): ReturnValue {
return h.view("views/css.pug");
}
async css(request: Req, h: Res): ReturnValue {
return h.view("views/css.pug")
}
@auth("try")
async index(request: Req, h: Res): ReturnValue {
if (request.auth.isAuthenticated) {
// 登录了
} else {
// 未登录
@auth("try")
async index(request: Req, h: Res): ReturnValue {
if (request.auth.isAuthenticated) {
// 登录了
} else {
// 未登录
}
return h.view("views/index.pug", { isLogin: request.auth.isAuthenticated })
}
return h.view("views/index.pug", { isLogin: request.auth.isAuthenticated });
}
@route("/about")
@auth("try")
async about(request: Req, h) {
// console.log(request.auth);
// console.log(1);
@route("/about")
@auth("try")
async about(request: Req, h) {
// console.log(request.auth);
// console.log(1);
// try {
// const User = request.getModel("User");
// try {
// const User = request.getModel("User");
// console.log(await User.findOne({ where: { username: "xieyaxin" } }));
// } catch (error) {
// console.log(error);
// }
// console.log(2);
const md = new MarkdownIt();
var result = md.render('# markdown-it rulezz!');
return h.view("views/about.pug", {
md: result
});
}
// console.log(await User.findOne({ where: { username: "xieyaxin" } }));
// } catch (error) {
// console.log(error);
// }
// console.log(2);
const md = new MarkdownIt()
var result = md.render("# markdown-it rulezz!")
return h.view("views/about.pug", {
md: result,
})
}
@route("/docs/{path*}")
@auth()
async docs(req: Req, h: Res): ReturnValue {
// const {id} = req.auth.credentials
// try {
// req.cookieAuth.ttl(720 * 24 * 60 * 60 * 1000)
// req.cookieAuth.set({ id: id });
// } catch (error) {
// console.log(error);
@route("/docs/{path*}")
@auth()
async docs(req: Req, h: Res): ReturnValue {
// const {id} = req.auth.credentials
// try {
// req.cookieAuth.ttl(720 * 24 * 60 * 60 * 1000)
// req.cookieAuth.set({ id: id });
// } catch (error) {
// console.log(error);
// }
if (req.params && req.params.path.endsWith(".md")) {
// console.log(path.resolve(baseDir, "docs/"+"*.md"));
// console.log(await glob("docs/"+"*.md"));
const mdPath = path.resolve(baseDir, "docs/"+req.params.path)
if(fs.existsSync(mdPath)){
const str = fs.readFileSync(mdPath, "utf8")
console.log("---->", mdPath);
// }
if (req.params && req.params.path.endsWith(".md")) {
// console.log(path.resolve(baseDir, "docs/"+"*.md"));
// console.log(await glob("docs/"+"*.md"));
const mdPath = path.resolve(baseDir, "docs/" + req.params.path)
if (fs.existsSync(mdPath)) {
const str = fs.readFileSync(mdPath, "utf8")
console.log("---->", mdPath)
return h.view("views/css.pug", {
content: str.toString()
});
}
// 解析文档
return h.view("views/css.pug");
return h.view("views/css.pug", {
content: str.toString(),
})
}
// 解析文档
return h.view("views/css.pug")
}
// 404页面
return h.redirect("/404")
}
// 404页面
return h.redirect("/404");
}
@route("/{path*}")
async any(req: Req, h: Res): ReturnValue {
console.log('404: ',req.raw.req.url);
return h.redirect("/404?r="+encodeURIComponent(req.raw.req.url));
}
@route("/404")
@auth("try")
async 404(request: Req, h: Res): ReturnValue {
if (request.auth.isAuthenticated) {
// 登录了
} else {
// 未登录
@route("/{path*}")
async any(req: Req, h: Res): ReturnValue {
console.log("404: ", req.raw.req.url)
return h.redirect("/404?r=" + encodeURIComponent(req.raw.req.url))
}
if(request.query?.r){
// 可重定向返回
@route("/404")
@auth("try")
async 404(request: Req, h: Res): ReturnValue {
if (request.auth.isAuthenticated) {
// 登录了
} else {
// 未登录
}
if (request.query?.r) {
// 可重定向返回
}
return h.view("404.pug", {
rollback: request.query?.r,
})
}
return h.view("404.pug", {
rollback: request.query?.r
});
}
}

8
source/route/views/nav/index.ts

@ -1,9 +1,9 @@
import { Req, Res, ReturnValue } from "#/global";
import { gSuccess } from "@/util";
import { Req, Res, ReturnValue } from "#/global"
import { gSuccess } from "@/util"
export default class Nav {
async index(req: Req, h: Res): ReturnValue{
const Constant = req.getModel("Constant")
async index(req: Req, h: Res): ReturnValue {
// const Constant = req.getModel("Constant")
return gSuccess("31231")
}
}

262
source/run.ts

@ -1,146 +1,146 @@
"use strict";
import plugins from "@/plugins";
import path from "path";
import { baseDir, templateDir } from "@/util";
import { validateJwt, validateSession } from "./auth";
import Hapi, { Server } from "@hapi/hapi";
import { Sequelize } from "sequelize";
import { Req } from "#/global";
"use strict"
import plugins from "@/plugins"
import path from "path"
import { baseDir, templateDir } from "@/util"
import { validateJwt, validateSession } from "./auth"
import Hapi, { Server } from "@hapi/hapi"
import { Sequelize } from "sequelize"
import { Req } from "#/global"
// const Hapi = require("@hapi/hapi");
// const HapiSwagger = require("hapi-swagger");
// const HapiSwagger = require("hapi-swaggered-ui"); // swagger v2版本
const pugPluginAlias = require('pug-alias');
const pugPluginAlias = require("pug-alias")
const run = async (): Promise<Server> => {
const server = Hapi.server({
port: 3388,
host: "localhost",
});
await server.register([
{
plugin: require("hapi-sequelizejs"),
options: [
const server = Hapi.server({
port: 3388,
host: "localhost",
})
await server.register([
{
name: "data", // identifier
models: [__dirname + "/models/**/*.ts"], // paths/globs to model files
// ignoredModels: [__dirname + "/server/models/**/*.js"], // OPTIONAL: paths/globs to ignore files
sequelize: new Sequelize({
dialect: "sqlite",
storage: path.resolve(__dirname, "./db/data.db"),
logging: false,
// logging: loggerSQL.debug.bind(loggerSQL) // Alternative way to use custom logger, displays all messages
}), // sequelize instance
sync: true, // sync models - default false
forceSync: false, // force sync (drops tables) - default false
plugin: require("hapi-sequelizejs"),
options: [
{
name: "data", // identifier
models: [__dirname + "/models/**/*.ts"], // paths/globs to model files
// ignoredModels: [__dirname + "/server/models/**/*.js"], // OPTIONAL: paths/globs to ignore files
sequelize: new Sequelize({
dialect: "sqlite",
storage: path.resolve(__dirname, "./db/data.db"),
logging: false,
// logging: loggerSQL.debug.bind(loggerSQL) // Alternative way to use custom logger, displays all messages
}), // sequelize instance
sync: true, // sync models - default false
forceSync: false, // force sync (drops tables) - default false
},
],
},
],
},
]);
])
//===== JWT ===== Start
// await server.register(require("hapi-auth-jwt2"));
// server.auth.strategy("jwt", "jwt", {
// key: process.env.KEY, // Never Share your secret key
// validate: validateJwt, // validate function defined above
// verifyOptions: { algorithms: ["HS256"] },
// });
//===== JWT ===== End
//===== session ===== Start
// https://hapi.dev/module/cookie/api?v=11.0.2
await server.register(require("@hapi/cookie"));
server.auth.strategy("session", "cookie", {
cookie: {
ttl: 1000 * 60 * 60 * 24,
path: '/', // 测试退出时set-cookie失效,加上这个好了
name: "sid", //cookie的名字
password: process.env.KEY,
isSecure: false, // false: 允许 Cookie 通过不安全的连接传输,这会使其受到攻击
},
redirectTo(request: Req){
if (request.path.startsWith('/api')) {
return false
}
return "/login"
},
appendNext: true,
validateFunc: validateSession,
});
server.auth.default("session");
//===== session ===== End
await server.register(plugins as any);
//===== JWT ===== Start
// await server.register(require("hapi-auth-jwt2"));
// server.auth.strategy("jwt", "jwt", {
// key: process.env.KEY, // Never Share your secret key
// validate: validateJwt, // validate function defined above
// verifyOptions: { algorithms: ["HS256"] },
// });
//===== JWT ===== End
//===== session ===== Start
// https://hapi.dev/module/cookie/api?v=11.0.2
await server.register(require("@hapi/cookie"))
server.auth.strategy("session", "cookie", {
cookie: {
ttl: 1000 * 60 * 60 * 24,
path: "/", // 测试退出时set-cookie失效,加上这个好了
name: "sid", //cookie的名字
password: process.env.KEY,
isSecure: false, // false: 允许 Cookie 通过不安全的连接传输,这会使其受到攻击
},
redirectTo(request: Req) {
if (request.path.startsWith("/api")) {
return false
}
return "/login"
},
appendNext: true,
validateFunc: validateSession,
})
server.auth.default("session")
//===== session ===== End
/**
*
*/
// https://hapi.dev/module/vision/api/?v=6.1.0
await server.register(require("@hapi/vision"));
server.views({
engines: {
ejs: require("ejs"),
pug: require("pug"),
},
isCached: process.env.NODE_ENV === "development" ? false : true,
compileMode: "sync", // ejs
relativeTo: baseDir,
layout: false, // ejs
layoutPath: path.resolve(templateDir, "layout"), // ejs
path: "template",
// pug
compileOptions: {
// By default Pug uses relative paths (e.g. ../root.pug), when using absolute paths (e.g. include /root.pug), basedir is prepended.
// https://pugjs.org/language/includes.html
basedir: templateDir,
plugins: [
pugPluginAlias({
// as Function
'@': fn => fn.replace(/^@/, 'template')
})
]
},
});
await server.register(plugins as any)
// http://localhost:3000/documentation
await server.register([
{
plugin: require("hapi-swagger"),
options: {
documentationPath: "/doc",
info: {
title: "Dream 文档",
version: "1.0.0",
/**
*
*/
// https://hapi.dev/module/vision/api/?v=6.1.0
await server.register(require("@hapi/vision"))
server.views({
engines: {
ejs: require("ejs"),
pug: require("pug"),
},
grouping: "tags",
tags: [
{
name: "sum",
description: "working with maths",
externalDocs: {
description: "Find out more",
url: "http://example.org",
},
},
{
name: "store",
description: "storing data",
externalDocs: {
description: "Find out more",
url: "http://example.org",
isCached: process.env.NODE_ENV === "development" ? false : true,
compileMode: "sync", // ejs
relativeTo: baseDir,
layout: false, // ejs
layoutPath: path.resolve(templateDir, "layout"), // ejs
path: "template",
// pug
compileOptions: {
// By default Pug uses relative paths (e.g. ../root.pug), when using absolute paths (e.g. include /root.pug), basedir is prepended.
// https://pugjs.org/language/includes.html
basedir: templateDir,
plugins: [
pugPluginAlias({
// as Function
"@": fn => fn.replace(/^@/, "template"),
}),
],
},
})
// http://localhost:3000/documentation
await server.register([
{
plugin: require("hapi-swagger"),
options: {
documentationPath: "/doc",
info: {
title: "Dream 文档",
version: "1.0.0",
},
grouping: "tags",
tags: [
{
name: "sum",
description: "working with maths",
externalDocs: {
description: "Find out more",
url: "http://example.org",
},
},
{
name: "store",
description: "storing data",
externalDocs: {
description: "Find out more",
url: "http://example.org",
},
},
],
},
},
],
},
},
]);
await server.start();
logger.trace("Server running on %s", server.info.uri);
return server;
};
},
])
await server.start()
logger.trace("Server running on %s", server.info.uri)
return server
}
process.on("unhandledRejection", (err) => {
console.log("unhandledRejection:", err);
process.exit(1);
});
process.on("unhandledRejection", err => {
console.log("unhandledRejection:", err)
process.exit(1)
})
export { run };
export { run }

34
source/schema/index.ts

@ -1,30 +1,32 @@
import * as Joi from "joi";
import * as Joi from "joi"
export const UserSchema = Joi.object({
username: Joi.string().alphanum().min(6).max(35),
password: Joi.string().pattern(new RegExp("^[a-zA-Z0-9]{3,30}$")).required(),
email: Joi.string().email({
minDomainSegments: 2,
tlds: { allow: ["com", "net"] },
})
}).or("username", "email");
username: Joi.string().alphanum().min(6).max(35),
password: Joi.string().pattern(new RegExp("^[a-zA-Z0-9]{3,30}$")).required(),
email: Joi.string().email({
minDomainSegments: 2,
tlds: { allow: ["com", "net"] },
}),
}).or("username", "email")
export const RegisterUserSchema = Joi.object({
username: Joi.string().alphanum().min(6).max(35),
password: Joi.string().pattern(new RegExp("^[a-zA-Z0-9]{3,30}$")).required(),
email: Joi.string().email({
minDomainSegments: 2,
tlds: { allow: ["com", "net"] },
}).required(),
email: Joi.string()
.email({
minDomainSegments: 2,
tlds: { allow: ["com", "net"] },
})
.required(),
nickname: Joi.string().alphanum().min(4).max(35),
})
export const LoginUserSchema = Joi.object({
referrer: Joi.string().allow('').optional(),
referrer: Joi.string().allow("").optional(),
username: Joi.string().min(6).max(35), //Joi.string().alphanum().min(6).max(35)
password: Joi.string().pattern(new RegExp("^[a-zA-Z0-9]{3,30}$")).required(),
email: Joi.string().email({
minDomainSegments: 2,
tlds: { allow: ["com", "net"] },
minDomainSegments: 2,
tlds: { allow: ["com", "net"] },
}),
}).or("username", "email");
}).or("username", "email")

16
source/util/index.ts

@ -1,13 +1,13 @@
import path from "path";
export * from "./res-helper";
import path from "path"
export * from "./res-helper"
export const isDev = process.env.NODE_ENV === "development"
export const isProd = process.env.NODE_ENV === "production"
export const baseDir = path.resolve(__dirname, "../../");
export const baseDir = path.resolve(__dirname, "../../")
export const sourceDir = isProd? path.resolve(__dirname, "../../dist") : path.resolve(__dirname, "../../source");
export const publicDir = path.resolve(__dirname, "../../public");
export const uploadDir = path.resolve(publicDir, "upload");
export const uploadPath = "/public/upload"; // 图片上传地址
export const templateDir = path.resolve(baseDir, "template");
export const sourceDir = isProd ? path.resolve(__dirname, "../../dist") : path.resolve(__dirname, "../../source")
export const publicDir = path.resolve(__dirname, "../../public")
export const uploadDir = path.resolve(publicDir, "upload")
export const uploadPath = "/public/upload" // 图片上传地址
export const templateDir = path.resolve(baseDir, "template")

44
source/util/res-helper.ts

@ -1,29 +1,29 @@
export function gSuccess(data = null, message = "success") {
if (typeof data === "string") {
if (typeof data === "string") {
return {
code: 200,
message: data,
data: null,
}
}
return {
code: 200,
message: data,
data: null,
};
}
return {
code: 200,
message: message,
data: data,
};
code: 200,
message: message,
data: data,
}
}
export function gFail(data = null, message = "fail") {
if (typeof data === "string") {
if (typeof data === "string") {
return {
code: 400,
message: data,
data: null,
}
}
return {
code: 400,
message: data,
data: null,
};
}
return {
code: 400,
message: message,
data: data,
};
code: 400,
message: message,
data: data,
}
}

60
source/util/util.ts

@ -1,43 +1,47 @@
export function dateTimeFormat(date, fmt = 'yyyy-MM-dd HH:mm:ss') { //日期时间格式化
export function dateTimeFormat(date, fmt = "yyyy-MM-dd HH:mm:ss") {
//日期时间格式化
if (!date) {
return ''
return ""
}
if (typeof date === 'string') {
date = date.replace('T', ' ').replace('Z', '');
date = new Date(date.replace(/-/g, '/'))
if (typeof date === "string") {
date = date.replace("T", " ").replace("Z", "")
date = new Date(date.replace(/-/g, "/"))
}
if (typeof date === 'number') {
date = new Date(date)
if (typeof date === "number") {
date = new Date(date)
}
var o = {
'M+': date.getMonth() + 1,
'd+': date.getDate(),
'h+': date.getHours() % 12 === 0 ? 12 : date.getHours() % 12,
'H+': date.getHours(),
'm+': date.getMinutes(),
's+': date.getSeconds(),
'q+': Math.floor((date.getMonth() + 3) / 3),
'S': date.getMilliseconds()
"M+": date.getMonth() + 1,
"d+": date.getDate(),
"h+": date.getHours() % 12 === 0 ? 12 : date.getHours() % 12,
"H+": date.getHours(),
"m+": date.getMinutes(),
"s+": date.getSeconds(),
"q+": Math.floor((date.getMonth() + 3) / 3),
S: date.getMilliseconds(),
}
var week = {
'0': '\u65e5',
'1': '\u4e00',
'2': '\u4e8c',
'3': '\u4e09',
'4': '\u56db',
'5': '\u4e94',
'6': '\u516d'
"0": "\u65e5",
"1": "\u4e00",
"2": "\u4e8c",
"3": "\u4e09",
"4": "\u56db",
"5": "\u4e94",
"6": "\u516d",
}
if (/(y+)/.test(fmt)) {
fmt = fmt.replace(RegExp.$1, (date.getFullYear() + '').substr(4 - RegExp.$1.length))
fmt = fmt.replace(RegExp.$1, (date.getFullYear() + "").substr(4 - RegExp.$1.length))
}
if (/(E+)/.test(fmt)) {
fmt = fmt.replace(RegExp.$1, ((RegExp.$1.length > 1) ? (RegExp.$1.length > 2 ? '\u661f\u671f' : '\u5468') : '') + week[date.getDay() + ''])
fmt = fmt.replace(
RegExp.$1,
(RegExp.$1.length > 1 ? (RegExp.$1.length > 2 ? "\u661f\u671f" : "\u5468") : "") + week[date.getDay() + ""],
)
}
for (var k in o) {
if (new RegExp('(' + k + ')').test(fmt)) {
fmt = fmt.replace(RegExp.$1, (RegExp.$1.length === 1) ? (o[k]) : (('00' + o[k]).substr(('' + o[k]).length)))
}
if (new RegExp("(" + k + ")").test(fmt)) {
fmt = fmt.replace(RegExp.$1, RegExp.$1.length === 1 ? o[k] : ("00" + o[k]).substr(("" + o[k]).length))
}
}
return fmt
}
}

8
template/404.pug

@ -1,9 +1,9 @@
extends layout/layout
block head
link(rel="stylesheet", href="/public/css/views/404.css")
link(rel="stylesheet", href="/public/css/views/404.css")
block content
div(style="text-align: center")
span.text404 404
div 重定向回:#{rollback}
div(style="text-align: center")
span.text404 404
div 重定向回:#{rollback}

120
template/helper/flush.pug

@ -1,62 +1,62 @@
//- 服务器反馈UI
if flash
.message-container
- index = 0
if flash.error
each item in flash.error
- index++
.message.is-danger(id="message"+index)
.message-header
p 错误
button.delete(aria-label='delete' data-target="message"+index)
.message-body
| #{item}
if flash.success
each item in flash.success
- index++
.message.is-success(id="message"+index)
.message-header
p 成功
button.delete(aria-label='delete' data-target="message"+index)
.message-body
| #{item}
if flash.info
each item in flash.info
- index++
.message.is-info(id="message"+index)
.message-header
p 信息
button.delete(aria-label='delete' data-target="message"+index)
.message-body
| #{item}
if flash.warning
each item in flash.warning
- index++
.message.is-warning(id="message"+index)
.message-header
p 警告
button.delete(aria-label='delete' data-target="message"+index)
.message-body
| #{item}
//- .toast-container.top-0.end-0.p-3
//- each item in flash.error
//- .toast.show(role='alert', aria-live='assertive', aria-atomic='true')
//- .toast-header
//- img.rounded.me-2(src='/public/image/icons/error.svg', alt='错误' style="width:20px;height: 20px;")
//- strong.me-auto 提示
//- //- small.text-muted just now
//- button.btn-close(type='button', data-bs-dismiss='toast', aria-label='Close')
//- .toast-body.
//- #{item}
//- .toast-container.position-fixed.bottom-0.end-0.p-3
//- #liveToast.toast(role='alert', aria-live='assertive', aria-atomic='true')
//- .toast-header
//- img.rounded.me-2(src='...', alt='...')
//- strong.me-auto Bootstrap
//- small 11 mins ago
//- button.btn-close(type='button', data-bs-dismiss='toast', aria-label='Close')
//- .toast-body.
//- Hello, world! This is a toast message.
//- ul
//- each item in flash.error
//- li #{item}
.message-container
- index = 0
if flash.error
each item in flash.error
- index++
.message.is-danger(id="message"+index)
.message-header
p 错误
button.delete(aria-label='delete' data-target="message"+index)
.message-body
| #{item}
if flash.success
each item in flash.success
- index++
.message.is-success(id="message"+index)
.message-header
p 成功
button.delete(aria-label='delete' data-target="message"+index)
.message-body
| #{item}
if flash.info
each item in flash.info
- index++
.message.is-info(id="message"+index)
.message-header
p 信息
button.delete(aria-label='delete' data-target="message"+index)
.message-body
| #{item}
if flash.warning
each item in flash.warning
- index++
.message.is-warning(id="message"+index)
.message-header
p 警告
button.delete(aria-label='delete' data-target="message"+index)
.message-body
| #{item}
//- .toast-container.top-0.end-0.p-3
//- each item in flash.error
//- .toast.show(role='alert', aria-live='assertive', aria-atomic='true')
//- .toast-header
//- img.rounded.me-2(src='/public/image/icons/error.svg', alt='错误' style="width:20px;height: 20px;")
//- strong.me-auto 提示
//- //- small.text-muted just now
//- button.btn-close(type='button', data-bs-dismiss='toast', aria-label='Close')
//- .toast-body.
//- #{item}
//- .toast-container.position-fixed.bottom-0.end-0.p-3
//- #liveToast.toast(role='alert', aria-live='assertive', aria-atomic='true')
//- .toast-header
//- img.rounded.me-2(src='...', alt='...')
//- strong.me-auto Bootstrap
//- small 11 mins ago
//- button.btn-close(type='button', data-bs-dismiss='toast', aria-label='Close')
//- .toast-body.
//- Hello, world! This is a toast message.
//- ul
//- each item in flash.error
//- li #{item}

2
template/helper/form_security.pug

@ -1,2 +1,2 @@
if crumb
<input type="hidden" name="crumb" value=#{crumb} />
<input type="hidden" name="crumb" value=#{crumb} />

2
template/md/about.md

@ -1 +1 @@
## 关于我
## 关于我

79
template/ui/header.pug

@ -1,39 +1,44 @@
nav.is-fixed-top.navbar(role='navigation', aria-label='main navigation', style="box-shadow: rgba(0, 0, 0, 0.16) 0px 1px 4px;")
.container
.navbar-brand
a.navbar-item(href='/')
img(src='https://bulma.io/images/bulma-logo.png', width='112', height='28')
a.navbar-burger(role='button', aria-label='menu', aria-expanded='false', data-target='navbarBasicExample')
span(aria-hidden='true')
span(aria-hidden='true')
span(aria-hidden='true')
#navbarBasicExample.navbar-menu
.navbar-start
a.navbar-item
| 文档
.navbar-item.has-dropdown.is-hoverable
a.navbar-link
| 更多
.navbar-dropdown
a.navbar-item(href="/about")
| 关于本站
a.navbar-item
| 关于作者
hr.navbar-divider
a.navbar-item
| 报告问题
.navbar-end
if !isLogin
.navbar-item
.buttons
a.button.is-primary(href="/register")
strong 注册
a.button.is-light(href="/login")
| 登录
else
.navbar-item
.buttons
button.button.is-white
| #{user.nickname}
a.button.is-danger.is-light(href="/logout")
| 退出
.navbar-brand
a.navbar-item(href='/')
img(src='https://bulma.io/images/bulma-logo.png', width='112', height='28')
a.navbar-burger(role='button', aria-label='menu', aria-expanded='false', data-target='navbarBasicExample')
span(aria-hidden='true')
span(aria-hidden='true')
span(aria-hidden='true')
#navbarBasicExample.navbar-menu
.navbar-start
a.navbar-item
| 文档
.navbar-item.has-dropdown.is-hoverable
a.navbar-link
| 更多
.navbar-dropdown
a.navbar-item(href="/about")
| 关于本站
a.navbar-item
| 关于作者
hr.navbar-divider
a.navbar-item
| 报告问题
.navbar-end
if !isLogin
.navbar-item
.buttons
a.button.is-primary(href="/register")
strong 注册
a.button.is-light(href="/login")
| 登录
else
.navbar-item.has-dropdown.is-hoverable
a.navbar-link
| #{user.nickname}
.navbar-dropdown
a.navbar-item
| 用户资料
hr.navbar-divider
a.navbar-item
| 退出
//- a.button.is-danger.is-light(href="/logout")
//- | 退出

6
template/views/about.pug

@ -1,12 +1,12 @@
extends /layout/layout
block var
-title="关于"
-title="关于"
block head
block content
section.section
.container.content!= md
section.section
.container.content!= md
block script

12
template/views/index.pug

@ -9,16 +9,6 @@ block head
block content
section.section
.container
h1.title
| Hello World
p.subtitle
| My first website with
strong Bulma
| !
div sda
if isLogin
button(hx-get="/htmx/clicked" hx-push-url="/about" hx-trigger="click" hx-target="this" hx-swap="outerHTML") Click Me!
<form action="/api/v1/upload/upload" method="post" enctype="multipart/form-data">
<input type="file" name="file" id="file" multiple />
+security
<button type="submit" id="upload">上传</button>
</form>

20
template/views/login.pug

@ -1,22 +1,22 @@
extends /layout/layout
block var
-title="登陆" // 网页标题
-hideHeader=true
-title="登陆" // 网页标题
-hideHeader=true
block head
+css("style/views/login.css")
block content
.login
h1.title.is-1 登录
form(action='/login' method='post')
input(id="referrer" type="text" name="referrer" class="form-control" style="display:none;")
input(type='text', name='username', placeholder='用户名', required)
input(type='password', name='password', placeholder='密码', required)
+security
button.btn.btn-primary.btn-block.btn-large(type='submit') 现在登录!
a(href="/register" style="margin-top: 8px;color: white;font-size: 14px;display: inline-block;float: right") 前往注册
h1.title.is-1 登录
form(action='/login' method='post')
input(id="referrer" type="text" name="referrer" class="form-control" style="display:none;")
input(type='text', name='username', placeholder='用户名', required)
input(type='password', name='password', placeholder='密码', required)
+security
button.btn.btn-primary.btn-block.btn-large(type='submit') 现在登录!
a(href="/register" style="margin-top: 8px;color: white;font-size: 14px;display: inline-block;float: right") 前往注册
block script
+script("js/page/login.js")

14
template/views/user.pug

@ -1,15 +1,15 @@
extends @/layout/layout
block var
-title="用户信息" // 网页标题
-title="用户信息" // 网页标题
//- -hideHeader=true
block head
block content
section.section
.container
div nickname: #{user.nickname}
div email: #{user.email}
div username: #{user.username}
div id: #{user.id}
section.section
.container
div nickname: #{user.nickname}
div email: #{user.email}
div username: #{user.username}
div id: #{user.id}

27
types/global.d.ts

@ -1,29 +1,26 @@
import { Logger } from "log4js";
import { Server } from "@hapi/hapi";
import { Request, ResponseToolkit, Lifecycle } from "@hapi/hapi";
import { Model, ModelCtor } from "Sequelize";
import { TUserModel } from "@/models/User";
import { Logger } from "log4js"
import { Server } from "@hapi/hapi"
import { Request, ResponseToolkit, Lifecycle } from "@hapi/hapi"
import { TUserModel } from "@/models/User"
declare global {
var server: Server;
var logger: Logger;
var loggerSite: Logger;
var loggerSQL: Logger;
var server: Server
var logger: Logger
var loggerSite: Logger
var loggerSQL: Logger
}
interface Models {
"User": TUserModel
User: TUserModel
}
declare module '@hapi/hapi' {
declare module "@hapi/hapi" {
interface Request {
getModel<T extends keyof Models, M extends Models[T]>(name: T): M
}
interface ResponseToolkit {
}
interface ResponseToolkit {}
}
export declare type Req = Request
export declare type Res = ResponseToolkit
export declare type ReturnValue = Promise<Lifecycle.ReturnValue>
export declare type ReturnValue = Promise<Lifecycle.ReturnValue>

Loading…
Cancel
Save