Browse Source

format

theme
npmrun 2 years ago
parent
commit
994c7d6013
  1. 171
      packages/hapi-router/src/index.ts
  2. 67
      packages/hapi-router/src/util/index.ts
  3. 10
      public/js/common/main.js
  4. 2
      public/js/page/login.js
  5. 6
      public/style/common/normalize.css
  6. 2
      public/style/common/style.css
  7. 4
      route.txt
  8. 18
      source/auth/index.ts
  9. 4
      source/log4js_config.ts
  10. 30
      source/main.ts
  11. 12
      source/models/Color.ts
  12. 12
      source/models/Constant.ts
  13. 55
      source/models/User.ts
  14. 18
      source/models/ha/user.ts
  15. 22
      source/models/ha/user_info.ts
  16. 14
      source/plugins/file-plugin.ts
  17. 84
      source/plugins/index.ts
  18. 6
      source/route/api/index.ts
  19. 79
      source/route/api/v1/upload/_upload.ts
  20. 22
      source/route/api/v1/upload/index.ts
  21. 82
      source/route/api/v1/user/index.ts
  22. 2
      source/route/htmx/index.ts
  23. 78
      source/route/views/index.ts
  24. 49
      source/route/views/index/index.ts
  25. 6
      source/route/views/nav/index.ts
  26. 64
      source/run.ts
  27. 16
      source/schema/index.ts
  28. 16
      source/util/index.ts
  29. 8
      source/util/res-helper.ts
  30. 54
      source/util/util.ts
  31. 13
      template/ui/header.pug
  32. 12
      template/views/index.pug
  33. 25
      types/global.d.ts

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

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

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

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

10
public/js/common/main.js

@ -15,9 +15,12 @@ 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) => { $messages.forEach((el, index) => {
let timeID; let timeID
function click() { function click() {
// Get the target from the "data-target" attribute // Get the target from the "data-target" attribute
const target = el.dataset.target const target = el.dataset.target
@ -31,8 +34,7 @@ document.addEventListener("DOMContentLoaded", () => {
const $target = document.getElementById(target) const $target = document.getElementById(target)
el.removeEventListener("click", click) el.removeEventListener("click", click)
$target.remove() $target.remove()
}, (index + 1) * 6000); }, (index + 1) * 6000)
el.addEventListener("click", click) 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") let url = new URLSearchParams(window.location.search).get("next")
referer.value = url ? url : "" referer.value = url ? url : ""
} }
console.log(referer); console.log(referer)

6
public/style/common/normalize.css

@ -174,7 +174,8 @@ textarea {
*/ */
button, button,
input { /* 1 */ input {
/* 1 */
overflow: visible; overflow: visible;
} }
@ -184,7 +185,8 @@ input { /* 1 */
*/ */
button, button,
select { /* 1 */ select {
/* 1 */
text-transform: none; text-transform: none;
} }

2
public/style/common/style.css

@ -1,5 +1,5 @@
html { html {
overflow-y: auto overflow-y: auto;
} }
.message-container { .message-container {

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 不需权限 : GET /htmx/clicked
D:\1XYX\demo\hapi-demo\source\route\views对应路径: /home/topuser/桌面/demo/hapi-demo/source/route/views对应路径:
不需权限(提供无需验证): GET /404 不需权限(提供无需验证): GET /404
不需权限 : GET /css 不需权限 : GET /css
不需权限(提供无需验证): GET / 不需权限(提供无需验证): GET /

18
source/auth/index.ts

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

4
source/log4js_config.ts

@ -1,4 +1,4 @@
import path from "path"; import path from "path"
export default function () { export default function () {
return { return {
@ -33,5 +33,5 @@ export default function () {
level: "debug", level: "debug",
}, },
}, },
}; }
} }

30
source/main.ts

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

12
source/models/Color.ts

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

12
source/models/Constant.ts

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

55
source/models/User.ts

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

18
source/models/ha/user.ts

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

22
source/models/ha/user_info.ts

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

14
source/plugins/file-plugin.ts

@ -1,5 +1,5 @@
import { publicDir } from "@/util"; import { publicDir } from "@/util"
const Inert = require("@hapi/inert"); const Inert = require("@hapi/inert")
const filePlugin = { const filePlugin = {
name: "filePlugin", name: "filePlugin",
@ -9,8 +9,8 @@ const filePlugin = {
files: { files: {
relativeTo: publicDir, relativeTo: publicDir,
}, },
}; }
await server.register(Inert); await server.register(Inert)
server.route({ server.route({
method: "GET", method: "GET",
path: "/public/{param*}", path: "/public/{param*}",
@ -22,8 +22,8 @@ const filePlugin = {
redirectToSlash: true, redirectToSlash: true,
}, },
}, },
}); })
}, },
}; }
export default filePlugin; export default filePlugin

84
source/plugins/index.ts

@ -1,15 +1,15 @@
import filePlugin from "./file-plugin"; import filePlugin from "./file-plugin"
import path from "path"; import path from "path"
import { baseDir, sourceDir } from "@/util"; import { baseDir, sourceDir } from "@/util"
import { plugin as routePlugin } from "@noderun/hapi-router"; import { plugin as routePlugin } from "@noderun/hapi-router"
import { ServerRegisterPluginObject, Plugin, Server, Request, ResponseObject } from "@hapi/hapi" import { ServerRegisterPluginObject, Plugin, Server, Request, ResponseObject } from "@hapi/hapi"
import Hoek from "@hapi/hoek" import Hoek from "@hapi/hoek"
import HapiYar from "@hapi/yar"; import HapiYar from "@hapi/yar"
import HapiCrumb from "@hapi/crumb" import HapiCrumb from "@hapi/crumb"
import { Stream } from "stream"; import { Stream } from "stream"
import HapiPino from "hapi-pino"; import HapiPino from "hapi-pino"
const pino = require('pino') const pino = require("pino")
const transport = pino.transport({ const transport = pino.transport({
targets: [ targets: [
// process.env.NODE_ENV !== 'production' ? { // process.env.NODE_ENV !== 'production' ? {
@ -19,19 +19,23 @@ const transport = pino.transport({
// translateTime: 'HH:MM:ss.l mmmm dS yyyy "UTC"' // translateTime: 'HH:MM:ss.l mmmm dS yyyy "UTC"'
// }, // },
// level: 'info' } : { target: 'pino/file', level: 'info' }, // level: 'info' } : { target: 'pino/file', level: 'info' },
{ target: 'pino-pretty', level: 'trace', options: { {
target: "pino-pretty",
level: "trace",
options: {
destination: path.resolve(baseDir, "./log/pino.log"), destination: path.resolve(baseDir, "./log/pino.log"),
colorize: true, colorize: true,
translateTime: 'HH:MM:ss.l mmmm dS yyyy "UTC"', translateTime: 'HH:MM:ss.l mmmm dS yyyy "UTC"',
mkdir: true, mkdir: true,
// append: false // append: false
}} },
},
// { target: 'pino/file', level: 'trace', options: { // { target: 'pino/file', level: 'trace', options: {
// destination: path.resolve(baseDir, "./log/pino.log"), // destination: path.resolve(baseDir, "./log/pino.log"),
// mkdir: true, // mkdir: true,
// // append: false // // append: false
// }} // }}
] ],
}) })
export default [ export default [
@ -45,13 +49,13 @@ export default [
// logRequestStart: true, // logRequestStart: true,
instance: pino(transport), instance: pino(transport),
ignoreFunc: (options, request) => { ignoreFunc: (options, request) => {
return request.path.startsWith('/public') || request.path.startsWith('/404') return request.path.startsWith("/public") || request.path.startsWith("/404")
}, },
// stream: fs.createWriteStream(path.resolve(baseDir, "./log/pino.log"), { encoding: "utf-8" }) // stream: fs.createWriteStream(path.resolve(baseDir, "./log/pino.log"), { encoding: "utf-8" })
// prettyPrint: process.env.NODE_ENV !== 'production', // prettyPrint: process.env.NODE_ENV !== 'production',
// Redact Authorization headers, see https://getpino.io/#/docs/redaction // Redact Authorization headers, see https://getpino.io/#/docs/redaction
// redact: ['req.headers.cookie'] // redact: ['req.headers.cookie']
} },
}, },
{ {
plugin: filePlugin as unknown as Plugin<any>, plugin: filePlugin as unknown as Plugin<any>,
@ -59,7 +63,7 @@ export default [
{ {
plugin: routePlugin as Plugin<any>, plugin: routePlugin as Plugin<any>,
options: { options: {
auth: ['/api'], auth: ["/api"],
sourceDir: [ sourceDir: [
// { // {
// dir: path.resolve(sourceDir, "route/api"), // dir: path.resolve(sourceDir, "route/api"),
@ -67,14 +71,14 @@ export default [
// }, // },
{ {
dir: path.resolve(sourceDir, "route/htmx"), dir: path.resolve(sourceDir, "route/htmx"),
prefix: "htmx" prefix: "htmx",
}, },
{ {
dir: path.resolve(sourceDir, "route/views"), dir: path.resolve(sourceDir, "route/views"),
prefix: "" prefix: "",
}, },
], ],
type: "session" type: "session",
}, },
}, },
{ {
@ -83,12 +87,12 @@ export default [
version: "0.0.1", version: "0.0.1",
// https://github.com/hks-epod/paydash/blob/master/lib/flash.js // https://github.com/hks-epod/paydash/blob/master/lib/flash.js
register: function (server: Server, options) { register: function (server: Server, options) {
server.ext('onPreResponse', async function(request: Request, h) { server.ext("onPreResponse", async function (request: Request, h) {
// @ts-ignore // @ts-ignore
if(request.response.variety === "file") return h.continue; // 返回文件时不处理 if (request.response.variety === "file") return h.continue // 返回文件时不处理
if(request.path.startsWith("/api") || request.path.startsWith("/htmx")) return h.continue; if (request.path.startsWith("/api") || request.path.startsWith("/htmx")) return h.continue
// 需要设置auth是try或者true才行 // 需要设置auth是try或者true才行
const isLogin = request.auth.isAuthenticated; const isLogin = request.auth.isAuthenticated
loggerSite.debug(`是否登录:${isLogin}, 请求路径:${request.path}, 请求方法:${request.method}`) loggerSite.debug(`是否登录:${isLogin}, 请求路径:${request.path}, 请求方法:${request.method}`)
// @ts-ignore // @ts-ignore
@ -102,9 +106,9 @@ export default [
// delete user.password; // delete user.password;
// } // }
// @ts-ignore // @ts-ignore
if (request.yar && request.yar.flash && request.response.variety === 'view') { if (request.yar && request.yar.flash && request.response.variety === "view") {
var flash = request.yar.flash(); var flash = request.yar.flash()
request.yar.set('_flash', {}); request.yar.set("_flash", {})
// @ts-ignore // @ts-ignore
request.response.source.context = Hoek.applyToDefaults( request.response.source.context = Hoek.applyToDefaults(
{ {
@ -113,14 +117,14 @@ export default [
user: isLogin ? request.auth.credentials : false, user: isLogin ? request.auth.credentials : false,
}, },
// @ts-ignore // @ts-ignore
request.response.source.context request.response.source.context,
); )
// @ts-ignore // @ts-ignore
} }
return h.continue; return h.continue
}); })
} },
} as Plugin<any> } as Plugin<any>,
}, },
{ {
plugin: HapiYar, plugin: HapiYar,
@ -129,10 +133,10 @@ export default [
storeBlank: false, storeBlank: false,
cookieOptions: { cookieOptions: {
password: "dsRhw1Y5UZqB8SjfClbkrX9PF7yuDMV3JItcW0G4vgpaxONo6mzenHLQET2AiKyPUjjdgjo10amjfghy", password: "dsRhw1Y5UZqB8SjfClbkrX9PF7yuDMV3JItcW0G4vgpaxONo6mzenHLQET2AiKyPUjjdgjo10amjfghy",
path: '/', path: "/",
isSecure: false isSecure: false,
} },
} },
}, },
{ {
plugin: HapiCrumb, plugin: HapiCrumb,
@ -144,12 +148,12 @@ export default [
if (request.payload instanceof Stream) { if (request.payload instanceof Stream) {
return true return true
} }
return false; return false
}, },
cookieOptions: { cookieOptions: {
path: '/', path: "/",
isSecure: false isSecure: false,
} },
} },
}, },
] as unknown as ServerRegisterPluginObject<any>; ] as unknown as ServerRegisterPluginObject<any>

6
source/route/api/index.ts

@ -1,5 +1,5 @@
import { Req, Res, ReturnValue } from "#/global"; import { Req, Res, ReturnValue } from "#/global"
import { auth, route } from "@noderun/hapi-router"; import { auth, route } from "@noderun/hapi-router"
export default class { export default class {
@route("/{path*}") @route("/{path*}")
@ -8,6 +8,6 @@ export default class {
return { return {
code: 404, code: 404,
data: null, data: null,
}; }
} }
} }

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

@ -1,32 +1,33 @@
import path from "path"; import path from "path"
import { gSuccess, gFail, uploadDir, uploadPath } from "@/util"; import { gSuccess, gFail, uploadDir, uploadPath } from "@/util"
import { dateTimeFormat } from "@/util/util" import { dateTimeFormat } from "@/util/util"
const fs = require("fs"); const fs = require("fs")
const multiparty = require("multiparty"); const multiparty = require("multiparty")
const FileType = require("file-type"); const FileType = require("file-type")
function saveFile(file) { function saveFile(file) {
return new Promise(async (resolve, reject) => { return new Promise(async (resolve, reject) => {
const filename = file.originalFilename; const filename = file.originalFilename
const uploadedPath = file.path; const uploadedPath = file.path
const filetype = await FileType.fromFile(uploadedPath); const filetype = await FileType.fromFile(uploadedPath)
const _file = path.parse(filename); const _file = path.parse(filename)
if (filetype && (filetype.ext == "jpg" || filetype.ext == "png")) { if (filetype && (filetype.ext == "jpg" || filetype.ext == "png")) {
let _name = _file.name + "_" + dateTimeFormat(new Date(), "yyyy_MM_dd") + "_" + new Date().getTime() + _file.ext let _name =
const dstPath = path.resolve(uploadDir, _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) { fs.rename(uploadedPath, dstPath, function (err) {
if (err) { if (err) {
console.log("rename error: " + err); console.log("rename error: " + err)
reject(); reject()
} else { } else {
resolve(dstPath); resolve(dstPath)
} }
}); })
} else { } else {
fs.unlinkSync(uploadedPath); fs.unlinkSync(uploadedPath)
reject(new Error(filename + "文件不是图片")); reject(new Error(filename + "文件不是图片"))
} }
}); })
} }
export default function (req, h) { export default function (req, h) {
@ -39,45 +40,43 @@ export default function (req, h) {
/*设置文件最大值 20MB */ /*设置文件最大值 20MB */
keepExtensions: true, keepExtensions: true,
/*保留后缀*/ /*保留后缀*/
}); })
return new Promise(async (resolve, reject) => { return new Promise(async (resolve, reject) => {
form.on("part", function (part) { form.on("part", function (part) {
console.log(part.filename); console.log(part.filename)
}); })
form.on("progress", function (bytesReceived, bytesExpected) { form.on("progress", function (bytesReceived, bytesExpected) {
if (bytesExpected === null) { if (bytesExpected === null) {
return; return
} }
var percentComplete = (bytesReceived / bytesExpected) * 100; var percentComplete = (bytesReceived / bytesExpected) * 100
console.log( console.log("the form is " + Math.floor(percentComplete) + "%" + " complete")
"the form is " + Math.floor(percentComplete) + "%" + " complete" })
);
});
form.parse(req.payload, async function (err, fields, files) { form.parse(req.payload, async function (err, fields, files) {
// console.log(err, fields, files); // console.log(err, fields, files);
if (err) { if (err) {
resolve(err.message); resolve(err.message)
return; return
} }
const errList = []; const errList = []
const fileList = []; const fileList = []
for (let i = 0; i < files.file.length; i++) { for (let i = 0; i < files.file.length; i++) {
const file = files.file[i]; const file = files.file[i]
try { try {
const dstPath = await saveFile(file); const dstPath = await saveFile(file)
fileList.push(dstPath); fileList.push(dstPath)
} catch (error) { } catch (error) {
errList.push(error.message); errList.push(error.message)
} }
} }
if (errList.length) { if (errList.length) {
resolve(gFail(null, errList.join("\n"))); resolve(gFail(null, errList.join("\n")))
return; return
} }
// resolve(h.view("views/upload.ejs")); // resolve(h.view("views/upload.ejs"));
resolve([...new Set(fileList)]); resolve([...new Set(fileList)])
}); })
}); })
} }

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

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

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

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

2
source/route/htmx/index.ts

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

78
source/route/views/index.ts

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

49
source/route/views/index/index.ts

@ -1,20 +1,13 @@
import { import { auth, config, method, route, swagger, validate } from "@noderun/hapi-router"
auth, import { Req, Res, ReturnValue } from "#/global"
config, import path from "path"
method, import fs from "fs-extra"
route, import { baseDir } from "@/util"
swagger, import MarkdownIt from "markdown-it"
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 { export default class Index {
async css(request: Req, h: Res): ReturnValue { async css(request: Req, h: Res): ReturnValue {
return h.view("views/css.pug"); return h.view("views/css.pug")
} }
@auth("try") @auth("try")
@ -24,7 +17,7 @@ export default class Index {
} else { } else {
// 未登录 // 未登录
} }
return h.view("views/index.pug", { isLogin: request.auth.isAuthenticated }); return h.view("views/index.pug", { isLogin: request.auth.isAuthenticated })
} }
@route("/about") @route("/about")
@ -41,11 +34,11 @@ export default class Index {
// console.log(error); // console.log(error);
// } // }
// console.log(2); // console.log(2);
const md = new MarkdownIt(); const md = new MarkdownIt()
var result = md.render('# markdown-it rulezz!'); var result = md.render("# markdown-it rulezz!")
return h.view("views/about.pug", { return h.view("views/about.pug", {
md: result md: result,
}); })
} }
@route("/docs/{path*}") @route("/docs/{path*}")
@ -65,22 +58,22 @@ export default class Index {
const mdPath = path.resolve(baseDir, "docs/" + req.params.path) const mdPath = path.resolve(baseDir, "docs/" + req.params.path)
if (fs.existsSync(mdPath)) { if (fs.existsSync(mdPath)) {
const str = fs.readFileSync(mdPath, "utf8") const str = fs.readFileSync(mdPath, "utf8")
console.log("---->", mdPath); console.log("---->", mdPath)
return h.view("views/css.pug", { return h.view("views/css.pug", {
content: str.toString() content: str.toString(),
}); })
} }
// 解析文档 // 解析文档
return h.view("views/css.pug"); return h.view("views/css.pug")
} }
// 404页面 // 404页面
return h.redirect("/404"); return h.redirect("/404")
} }
@route("/{path*}") @route("/{path*}")
async any(req: Req, h: Res): ReturnValue { async any(req: Req, h: Res): ReturnValue {
console.log('404: ',req.raw.req.url); console.log("404: ", req.raw.req.url)
return h.redirect("/404?r="+encodeURIComponent(req.raw.req.url)); return h.redirect("/404?r=" + encodeURIComponent(req.raw.req.url))
} }
@route("/404") @route("/404")
@auth("try") @auth("try")
@ -94,7 +87,7 @@ export default class Index {
// 可重定向返回 // 可重定向返回
} }
return h.view("404.pug", { return h.view("404.pug", {
rollback: request.query?.r rollback: request.query?.r,
}); })
} }
} }

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

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

64
source/run.ts

@ -1,22 +1,22 @@
"use strict"; "use strict"
import plugins from "@/plugins"; import plugins from "@/plugins"
import path from "path"; import path from "path"
import { baseDir, templateDir } from "@/util"; import { baseDir, templateDir } from "@/util"
import { validateJwt, validateSession } from "./auth"; import { validateJwt, validateSession } from "./auth"
import Hapi, { Server } from "@hapi/hapi"; import Hapi, { Server } from "@hapi/hapi"
import { Sequelize } from "sequelize"; import { Sequelize } from "sequelize"
import { Req } from "#/global"; import { Req } from "#/global"
// const Hapi = require("@hapi/hapi"); // const Hapi = require("@hapi/hapi");
// const HapiSwagger = require("hapi-swagger"); // const HapiSwagger = require("hapi-swagger");
// const HapiSwagger = require("hapi-swaggered-ui"); // swagger v2版本 // const HapiSwagger = require("hapi-swaggered-ui"); // swagger v2版本
const pugPluginAlias = require('pug-alias'); const pugPluginAlias = require("pug-alias")
const run = async (): Promise<Server> => { const run = async (): Promise<Server> => {
const server = Hapi.server({ const server = Hapi.server({
port: 3388, port: 3388,
host: "localhost", host: "localhost",
}); })
await server.register([ await server.register([
{ {
plugin: require("hapi-sequelizejs"), plugin: require("hapi-sequelizejs"),
@ -36,7 +36,7 @@ const run = async (): Promise<Server> => {
}, },
], ],
}, },
]); ])
//===== JWT ===== Start //===== JWT ===== Start
// await server.register(require("hapi-auth-jwt2")); // await server.register(require("hapi-auth-jwt2"));
@ -48,34 +48,34 @@ const run = async (): Promise<Server> => {
//===== JWT ===== End //===== JWT ===== End
//===== session ===== Start //===== session ===== Start
// https://hapi.dev/module/cookie/api?v=11.0.2 // https://hapi.dev/module/cookie/api?v=11.0.2
await server.register(require("@hapi/cookie")); await server.register(require("@hapi/cookie"))
server.auth.strategy("session", "cookie", { server.auth.strategy("session", "cookie", {
cookie: { cookie: {
ttl: 1000 * 60 * 60 * 24, ttl: 1000 * 60 * 60 * 24,
path: '/', // 测试退出时set-cookie失效,加上这个好了 path: "/", // 测试退出时set-cookie失效,加上这个好了
name: "sid", //cookie的名字 name: "sid", //cookie的名字
password: process.env.KEY, password: process.env.KEY,
isSecure: false, // false: 允许 Cookie 通过不安全的连接传输,这会使其受到攻击 isSecure: false, // false: 允许 Cookie 通过不安全的连接传输,这会使其受到攻击
}, },
redirectTo(request: Req) { redirectTo(request: Req) {
if (request.path.startsWith('/api')) { if (request.path.startsWith("/api")) {
return false return false
} }
return "/login" return "/login"
}, },
appendNext: true, appendNext: true,
validateFunc: validateSession, validateFunc: validateSession,
}); })
server.auth.default("session"); server.auth.default("session")
//===== session ===== End //===== session ===== End
await server.register(plugins as any); await server.register(plugins as any)
/** /**
* *
*/ */
// https://hapi.dev/module/vision/api/?v=6.1.0 // https://hapi.dev/module/vision/api/?v=6.1.0
await server.register(require("@hapi/vision")); await server.register(require("@hapi/vision"))
server.views({ server.views({
engines: { engines: {
ejs: require("ejs"), ejs: require("ejs"),
@ -95,11 +95,11 @@ const run = async (): Promise<Server> => {
plugins: [ plugins: [
pugPluginAlias({ pugPluginAlias({
// as Function // as Function
'@': fn => fn.replace(/^@/, 'template') "@": fn => fn.replace(/^@/, "template"),
}) }),
] ],
}, },
}); })
// http://localhost:3000/documentation // http://localhost:3000/documentation
await server.register([ await server.register([
@ -132,15 +132,15 @@ const run = async (): Promise<Server> => {
], ],
}, },
}, },
]); ])
await server.start(); await server.start()
logger.trace("Server running on %s", server.info.uri); logger.trace("Server running on %s", server.info.uri)
return server; return server
}; }
process.on("unhandledRejection", (err) => { process.on("unhandledRejection", err => {
console.log("unhandledRejection:", err); console.log("unhandledRejection:", err)
process.exit(1); process.exit(1)
}); })
export { run }; export { run }

16
source/schema/index.ts

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

16
source/util/index.ts

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

8
source/util/res-helper.ts

@ -4,13 +4,13 @@ export function gSuccess(data = null, message = "success") {
code: 200, code: 200,
message: data, message: data,
data: null, data: null,
}; }
} }
return { return {
code: 200, code: 200,
message: message, message: message,
data: data, data: data,
}; }
} }
export function gFail(data = null, message = "fail") { export function gFail(data = null, message = "fail") {
@ -19,11 +19,11 @@ export function gFail(data = null, message = "fail") {
code: 400, code: 400,
message: data, message: data,
data: null, data: null,
}; }
} }
return { return {
code: 400, code: 400,
message: message, message: message,
data: data, data: data,
}; }
} }

54
source/util/util.ts

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

13
template/ui/header.pug

@ -31,9 +31,14 @@ nav.is-fixed-top.navbar(role='navigation', aria-label='main navigation', style="
a.button.is-light(href="/login") a.button.is-light(href="/login")
| 登录 | 登录
else else
.navbar-item .navbar-item.has-dropdown.is-hoverable
.buttons a.navbar-link
button.button.is-white
| #{user.nickname} | #{user.nickname}
a.button.is-danger.is-light(href="/logout") .navbar-dropdown
a.navbar-item
| 用户资料
hr.navbar-divider
a.navbar-item
| 退出 | 退出
//- a.button.is-danger.is-light(href="/logout")
//- | 退出

12
template/views/index.pug

@ -9,16 +9,6 @@ block head
block content block content
section.section section.section
.container .container
h1.title div sda
| Hello World
p.subtitle
| My first website with
strong Bulma
| !
if isLogin if isLogin
button(hx-get="/htmx/clicked" hx-push-url="/about" hx-trigger="click" hx-target="this" hx-swap="outerHTML") Click Me! 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>

25
types/global.d.ts

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

Loading…
Cancel
Save