├── .gitignore ├── .swiftlint.yml ├── Package.resolved ├── Package.swift ├── Public └── .gitkeep ├── README.md ├── Sources ├── App │ ├── Controllers │ │ ├── .gitkeep │ │ └── API │ │ │ ├── AppRouteControllers │ │ │ ├── BookRouteController.swift │ │ │ ├── NewsRouteController.swift │ │ │ ├── SearchRouteController.swift │ │ │ ├── UserRouteController.swift │ │ │ └── WishBookRouteController.swift │ │ │ └── RouteControllers │ │ │ ├── AccountRouteController.swift │ │ │ ├── AuthenticationRouteController.swift │ │ │ ├── ProtectedRoutesController.swift │ │ │ └── SysRouteController.swift │ ├── Middlewares │ │ ├── APIErrorMiddleware │ │ │ ├── APIErrorMiddleware.swift │ │ │ └── Specialization │ │ │ │ └── Specialization.swift │ │ └── read.md │ ├── Models │ │ ├── .gitkeep │ │ ├── Entities │ │ │ ├── ActiveCode.swift │ │ │ ├── Authentication │ │ │ │ ├── Token │ │ │ │ │ ├── AccessToken.swift │ │ │ │ │ └── RefreshToken.swift │ │ │ │ └── UserAuth.swift │ │ │ ├── Book.swift │ │ │ ├── BookClassify+Populate.swift │ │ │ ├── BookClassify.swift │ │ │ ├── Collect.swift │ │ │ ├── Comment.swift │ │ │ ├── Feedback.swift │ │ │ ├── Friend.swift │ │ │ ├── MessageBoard.swift │ │ │ ├── News │ │ │ │ ├── Notify.swift │ │ │ │ ├── Subscription.swift │ │ │ │ └── UserNotify.swift │ │ │ ├── PriceUnit+Populate.swift │ │ │ ├── PriceUnit.swift │ │ │ ├── Recommend.swift │ │ │ ├── Sys │ │ │ │ ├── Group.swift │ │ │ │ ├── GroupRoleRelation.swift │ │ │ │ ├── Menu+Populate.swift │ │ │ │ ├── Menu.swift │ │ │ │ ├── MenuRoleRelation.swift │ │ │ │ ├── Move │ │ │ │ │ ├── GroupRightRelation.swift │ │ │ │ │ └── UserRightRelation.swift │ │ │ │ ├── OpLog.swift │ │ │ │ ├── Organization+Populate.swift │ │ │ │ ├── Organization.swift │ │ │ │ ├── Resusivable.swift │ │ │ │ ├── Right.swift │ │ │ │ ├── Role.swift │ │ │ │ ├── RoleRightRelation.swift │ │ │ │ ├── User.swift │ │ │ │ ├── UserGroupRelation.swift │ │ │ │ └── UserRoleRelation.swift │ │ │ ├── WishBook.swift │ │ │ └── WishBookComment.swift │ │ ├── Requests │ │ │ ├── Account │ │ │ │ ├── AuthenticationContainer.swift │ │ │ │ ├── EmailLoginContainer.swift │ │ │ │ ├── NewPasswordContainer.swift │ │ │ │ ├── RefreshTokenContainer.swift │ │ │ │ ├── RegisteCodeContainer.swift │ │ │ │ ├── UserEmailContainer.swift │ │ │ │ ├── UserRegisterContainer.swift │ │ │ │ ├── UserUpdateContainer.swift │ │ │ │ └── UserWxAppOauthContainer.swift │ │ │ ├── Book │ │ │ │ ├── BookCommentListContainer.swift │ │ │ │ ├── BookCreateContainer.swift │ │ │ │ ├── BookIsbnContainer.swift │ │ │ │ └── BookListContainer.swift │ │ │ ├── DeleteIDContainer.swift │ │ │ ├── Sys │ │ │ │ └── TestContainer.swift │ │ │ └── WishBook │ │ │ │ └── WishBookCreateContainer.swift │ │ └── Responses │ │ │ ├── JSONContainer.swift │ │ │ └── WxAppUserInfoContainer.swift │ ├── Setup │ │ ├── app.swift │ │ ├── boot.swift │ │ ├── commands.swift │ │ ├── configure.swift │ │ ├── content.swift │ │ ├── databases.swift │ │ ├── middlewares.swift │ │ ├── migrate.swift │ │ ├── repositories.swift │ │ └── routes.swift │ └── Utils │ │ ├── Base64 │ │ └── Base64.swift │ │ ├── Either │ │ ├── Either.swift │ │ └── EitherProtocol.swift │ │ ├── Email │ │ ├── EmailSender.swift │ │ └── RouteCollection+Email.swift │ │ ├── Error │ │ └── ApiError.swift │ │ ├── Ext │ │ ├── Model+Empty.swift │ │ └── String+Ext.swift │ │ ├── Service │ │ ├── Authentication │ │ │ └── AuthenticationService.swift │ │ └── Notify │ │ │ └── NotifyService.swift │ │ └── Validation │ │ └── Validator+Password.swift └── Run │ └── main.swift ├── Tests ├── .gitkeep ├── AppTests │ └── AppTests.swift └── LinuxMain.swift ├── api-template.paw ├── circle.yml ├── cloud.yml ├── license └── slide2.gif /.gitignore: -------------------------------------------------------------------------------- 1 | 2 | # Created by https://www.gitignore.io/api/vapor 3 | 4 | ### Vapor ### 5 | Config/secrets 6 | 7 | ### Vapor Patch ### 8 | Packages 9 | .build 10 | xcuserdata 11 | *.xcodeproj 12 | DerivedData/ 13 | .DS_Store 14 | 15 | # End of https://www.gitignore.io/api/vapor 16 | -------------------------------------------------------------------------------- /.swiftlint.yml: -------------------------------------------------------------------------------- 1 | included: 2 | - Sources 3 | - Tests 4 | 5 | nesting: 6 | type_level: 2 7 | 8 | opt_in_rules: 9 | - closure_end_indentation 10 | - literal_expression_end_indentation 11 | 12 | identifier_name: 13 | excluded: 14 | - id 15 | -------------------------------------------------------------------------------- /Package.resolved: -------------------------------------------------------------------------------- 1 | { 2 | "object": { 3 | "pins": [ 4 | { 5 | "package": "Auth", 6 | "repositoryURL": "https://github.com/vapor/auth.git", 7 | "state": { 8 | "branch": null, 9 | "revision": "90868627c7587ea207c0b6d4054265e68f6a33ef", 10 | "version": "2.0.1" 11 | } 12 | }, 13 | { 14 | "package": "Cryptor", 15 | "repositoryURL": "https://github.com/IBM-Swift/BlueCryptor.git", 16 | "state": { 17 | "branch": null, 18 | "revision": "efdb770164a7d3b222d6dcb78f253633c38a2ec2", 19 | "version": "1.0.10" 20 | } 21 | }, 22 | { 23 | "package": "Socket", 24 | "repositoryURL": "https://github.com/IBM-Swift/BlueSocket.git", 25 | "state": { 26 | "branch": null, 27 | "revision": "57092aba30b32ab6925ab2202672897ee5f34c40", 28 | "version": "1.0.17" 29 | } 30 | }, 31 | { 32 | "package": "SSLService", 33 | "repositoryURL": "https://github.com/IBM-Swift/BlueSSLService.git", 34 | "state": { 35 | "branch": null, 36 | "revision": "763b3d88d07d3e7eb84ff537900f6db2b70fffdd", 37 | "version": "1.0.17" 38 | } 39 | }, 40 | { 41 | "package": "CommonCrypto", 42 | "repositoryURL": "https://github.com/IBM-Swift/CommonCrypto.git", 43 | "state": { 44 | "branch": null, 45 | "revision": "9156d238dbc4c15455b77b45e721b2bb0b995e31", 46 | "version": "1.0.0" 47 | } 48 | }, 49 | { 50 | "package": "Console", 51 | "repositoryURL": "https://github.com/vapor/console.git", 52 | "state": { 53 | "branch": null, 54 | "revision": "5b9796d39f201b3dd06800437abd9d774a455e57", 55 | "version": "3.0.2" 56 | } 57 | }, 58 | { 59 | "package": "Core", 60 | "repositoryURL": "https://github.com/vapor/core.git", 61 | "state": { 62 | "branch": null, 63 | "revision": "eb876a758733166a4fb20f3f0a17b480c5ea563e", 64 | "version": "3.4.3" 65 | } 66 | }, 67 | { 68 | "package": "Crypto", 69 | "repositoryURL": "https://github.com/vapor/crypto.git", 70 | "state": { 71 | "branch": null, 72 | "revision": "5605334590affd4785a5839806b4504407e054ac", 73 | "version": "3.3.0" 74 | } 75 | }, 76 | { 77 | "package": "DatabaseKit", 78 | "repositoryURL": "https://github.com/vapor/database-kit.git", 79 | "state": { 80 | "branch": null, 81 | "revision": "3a17dbbe9be5f8c37703e4b7982c1332ad6b00c4", 82 | "version": "1.3.1" 83 | } 84 | }, 85 | { 86 | "package": "Fluent", 87 | "repositoryURL": "https://github.com/vapor/fluent.git", 88 | "state": { 89 | "branch": null, 90 | "revision": "270b6fa372f03809b9795e8f8b9d1c31267a0ff3", 91 | "version": "3.0.0" 92 | } 93 | }, 94 | { 95 | "package": "FluentPostgreSQL", 96 | "repositoryURL": "https://github.com/vapor/fluent-postgresql.git", 97 | "state": { 98 | "branch": null, 99 | "revision": "8e3eb9d24d54ac58c8d04c194ad6b24f0b1b667e", 100 | "version": "1.0.0" 101 | } 102 | }, 103 | { 104 | "package": "FluentSQLite", 105 | "repositoryURL": "https://github.com/vapor/fluent-sqlite.git", 106 | "state": { 107 | "branch": null, 108 | "revision": "c32f5bda84bf4ea691d19afe183d40044f579e11", 109 | "version": "3.0.0" 110 | } 111 | }, 112 | { 113 | "package": "HTTP", 114 | "repositoryURL": "https://github.com/vapor/http.git", 115 | "state": { 116 | "branch": null, 117 | "revision": "9e3eff9dfa4df7fc282bf27f801c72b3ffbfd984", 118 | "version": "3.1.4" 119 | } 120 | }, 121 | { 122 | "package": "LoggerAPI", 123 | "repositoryURL": "https://github.com/IBM-Swift/LoggerAPI.git", 124 | "state": { 125 | "branch": null, 126 | "revision": "5041f2673aa75d6e973d9b6bd3956bc5068387c8", 127 | "version": "1.7.3" 128 | } 129 | }, 130 | { 131 | "package": "Multipart", 132 | "repositoryURL": "https://github.com/vapor/multipart.git", 133 | "state": { 134 | "branch": null, 135 | "revision": "e57007c23a52b68e44ebdfc70cbe882a7c4f1ec3", 136 | "version": "3.0.2" 137 | } 138 | }, 139 | { 140 | "package": "Pagination", 141 | "repositoryURL": "https://github.com/vapor-community/pagination.git", 142 | "state": { 143 | "branch": null, 144 | "revision": "d018e2a02ed94536ad494ff750057990e8ae1acf", 145 | "version": "1.0.7" 146 | } 147 | }, 148 | { 149 | "package": "PostgreSQL", 150 | "repositoryURL": "https://github.com/vapor/postgresql.git", 151 | "state": { 152 | "branch": null, 153 | "revision": "c8b01a35efae6737e8d896440f5eec9b219571d9", 154 | "version": "1.1.0" 155 | } 156 | }, 157 | { 158 | "package": "Routing", 159 | "repositoryURL": "https://github.com/vapor/routing.git", 160 | "state": { 161 | "branch": null, 162 | "revision": "3219e328491b0853b8554c5a694add344d2c6cfb", 163 | "version": "3.0.1" 164 | } 165 | }, 166 | { 167 | "package": "Service", 168 | "repositoryURL": "https://github.com/vapor/service.git", 169 | "state": { 170 | "branch": null, 171 | "revision": "281a70b69783891900be31a9e70051b6fe19e146", 172 | "version": "1.0.0" 173 | } 174 | }, 175 | { 176 | "package": "SQL", 177 | "repositoryURL": "https://github.com/vapor/sql.git", 178 | "state": { 179 | "branch": null, 180 | "revision": "a35cf1dced4ddd32bb2dc8b6e765aea7bcf8d6e0", 181 | "version": "2.1.0" 182 | } 183 | }, 184 | { 185 | "package": "SQLite", 186 | "repositoryURL": "https://github.com/vapor/sqlite.git", 187 | "state": { 188 | "branch": null, 189 | "revision": "ad2e9bc9f0ed00ef2c6a05f89c1cec605467c90f", 190 | "version": "3.1.0" 191 | } 192 | }, 193 | { 194 | "package": "swift-nio", 195 | "repositoryURL": "https://github.com/apple/swift-nio.git", 196 | "state": { 197 | "branch": null, 198 | "revision": "5d8148c8b45dfb449276557f22120694567dd1d2", 199 | "version": "1.9.5" 200 | } 201 | }, 202 | { 203 | "package": "swift-nio-ssl", 204 | "repositoryURL": "https://github.com/apple/swift-nio-ssl.git", 205 | "state": { 206 | "branch": null, 207 | "revision": "8380fa29a2af784b067d8ee01c956626ca29f172", 208 | "version": "1.3.1" 209 | } 210 | }, 211 | { 212 | "package": "swift-nio-ssl-support", 213 | "repositoryURL": "https://github.com/apple/swift-nio-ssl-support.git", 214 | "state": { 215 | "branch": null, 216 | "revision": "c02eec4e0e6d351cd092938cf44195a8e669f555", 217 | "version": "1.0.0" 218 | } 219 | }, 220 | { 221 | "package": "swift-nio-zlib-support", 222 | "repositoryURL": "https://github.com/apple/swift-nio-zlib-support.git", 223 | "state": { 224 | "branch": null, 225 | "revision": "37760e9a52030bb9011972c5213c3350fa9d41fd", 226 | "version": "1.0.0" 227 | } 228 | }, 229 | { 230 | "package": "SwiftSMTP", 231 | "repositoryURL": "https://github.com/IBM-Swift/Swift-SMTP", 232 | "state": { 233 | "branch": null, 234 | "revision": "496f779a073991609cf3745214e49449d9b5db24", 235 | "version": "5.1.0" 236 | } 237 | }, 238 | { 239 | "package": "TemplateKit", 240 | "repositoryURL": "https://github.com/vapor/template-kit.git", 241 | "state": { 242 | "branch": null, 243 | "revision": "db35b1c35aabd0f5db3abca0cfda7becfe9c43e2", 244 | "version": "1.1.0" 245 | } 246 | }, 247 | { 248 | "package": "URLEncodedForm", 249 | "repositoryURL": "https://github.com/vapor/url-encoded-form.git", 250 | "state": { 251 | "branch": null, 252 | "revision": "932024f363ee5ff59059cf7d67194a1c271d3d0c", 253 | "version": "1.0.5" 254 | } 255 | }, 256 | { 257 | "package": "Validation", 258 | "repositoryURL": "https://github.com/vapor/validation.git", 259 | "state": { 260 | "branch": null, 261 | "revision": "156f8adeac3440e868da3757777884efbc6ec0cc", 262 | "version": "2.1.0" 263 | } 264 | }, 265 | { 266 | "package": "Vapor", 267 | "repositoryURL": "https://github.com/vapor/vapor.git", 268 | "state": { 269 | "branch": null, 270 | "revision": "157d3b15336caa882662cc75024dd04b2e225246", 271 | "version": "3.1.0" 272 | } 273 | }, 274 | { 275 | "package": "WebSocket", 276 | "repositoryURL": "https://github.com/vapor/websocket.git", 277 | "state": { 278 | "branch": null, 279 | "revision": "149af03348f60ac8b84defdf277112d62fd8c704", 280 | "version": "1.0.2" 281 | } 282 | } 283 | ] 284 | }, 285 | "version": 1 286 | } 287 | -------------------------------------------------------------------------------- /Package.swift: -------------------------------------------------------------------------------- 1 | // swift-tools-version:4.0 2 | import PackageDescription 3 | 4 | let package = Package( 5 | name: "Hello", 6 | dependencies: [ 7 | .package(url: "https://github.com/vapor/vapor.git", from: "3.1.0"), 8 | .package(url: "https://github.com/vapor/fluent-postgresql.git", from: "1.0.0"), 9 | .package(url: "https://github.com/vapor/auth.git", from: "2.0.1"), 10 | .package(url: "https://github.com/vapor-community/pagination.git", from: "1.0.7"), 11 | .package(url: "https://github.com/IBM-Swift/Swift-SMTP", .upToNextMinor(from: "5.1.0")) 12 | ], 13 | targets: [ 14 | .target(name: "App", dependencies: [ 15 | "Vapor", 16 | "Authentication", 17 | "SwiftSMTP", 18 | "Pagination", 19 | "FluentPostgreSQL" 20 | ]), 21 | .target(name: "Run", dependencies: ["App"]), 22 | .testTarget(name: "AppTests", dependencies: ["App"]) 23 | ] 24 | ) 25 | 26 | -------------------------------------------------------------------------------- /Public/.gitkeep: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/OHeroJ/BookVapor/b97b43d545d2d35f48006400814860e5d9fd0385/Public/.gitkeep -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 |

2 | API Template 3 | 4 | 5 | 6 | ## 环境 7 | 8 | * Vapor3.1.0 9 | * Swift4.2 10 | 11 | ## 项目规范 12 | 13 | * [规范](https://github.com/vapor-community/styleguide) 14 | 15 | 16 | 17 | ## 功能 18 | 19 | * [x] 用户注册、登入、密码找回, 适用于第三方登录 20 | * [x] 支持微信小程序 21 | * [x] 角色管理 22 | * [x] 权限管理 23 | * [x] 用户管理 24 | * [x] 资源管理 25 | * [ ] 其他, 完善中 26 | 27 | ## 安装 28 | 29 | ### 安装 swift & vapor 30 | 31 | ``` 32 | sudo apt-get install swift vapor 33 | 34 | swift --version # 查看 swift 版本 35 | ``` 36 | 37 | ### Postgresql 38 | 39 | ``` 40 | sudo apt-get install postgresql # 安装 Postgresql 41 | 42 | createuser root -P # 创建一个用户,密码 lai12345 43 | createdb book -O root -E UTF8 -e # 创建数据库 44 | ``` 45 | 46 | ### vapor fetch 问题 47 | 48 | 如果在 vapor fetch 的时候很慢,建议先运行 vapor clean, 然后执行 vapor run 如果你想看进度的话。可以 49 | 50 | ``` 51 | vapor clean 52 | vapor fetch 53 | ls -all #看是否有.build 目录 54 | cd .build 55 | du -sh * # 这里就可以进行看.build 文件的大小的变化 56 | ``` 57 | 58 | 59 | ## 预览 60 | 61 | ![](https://github.com/OHeroJ/BookCoin/blob/master/slide2.gif) 62 | 63 | ### 环境配置 64 | 65 | #### 数据库配置 66 | 67 | ``` 68 | brew install postgresql # 安装 psql 69 | createuser root -P lai12345 # 创建数据库用户 70 | createdb book -O root -E UTF8 -e # 创建数据库 71 | ``` 72 | 73 | #### 运行 vapor 74 | 75 | ``` 76 | brew install vapor/tap/vapor 77 | vapor run 78 | ``` 79 | 80 | 81 | ### 下载 demo 82 | 83 | ``` 84 | git clone https://github.com/OHeroJ/book.git 85 | ``` 86 | 87 | 然后切到项目目录下 88 | 89 | ``` 90 | npm run dev 91 | ``` 92 | 93 | 94 | 95 | 96 | -------------------------------------------------------------------------------- /Sources/App/Controllers/.gitkeep: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/OHeroJ/BookVapor/b97b43d545d2d35f48006400814860e5d9fd0385/Sources/App/Controllers/.gitkeep -------------------------------------------------------------------------------- /Sources/App/Controllers/API/AppRouteControllers/BookRouteController.swift: -------------------------------------------------------------------------------- 1 | // 2 | // BookController.swift 3 | // App 4 | // 5 | // Created by laijihua on 2018/6/27. 6 | // 7 | 8 | import Vapor 9 | import FluentPostgreSQL 10 | import Pagination 11 | import Fluent 12 | 13 | final class BookRouteController: RouteCollection { 14 | func boot(router: Router) throws { 15 | let group = router.grouped("api", "book") 16 | let guardAuthMiddleware = User.guardAuthMiddleware() 17 | let tokenAuthMiddleware = User.tokenAuthMiddleware() 18 | let authGroup = group.grouped([tokenAuthMiddleware, guardAuthMiddleware]) 19 | authGroup.post(BookCreateContainer.self, at:"create", use: createBookHandler) // 创建书籍 20 | authGroup.post(BookUpdateContainer.self, at:"update", use: updateBookHandler) // 编辑书籍 21 | authGroup.post(Comment.self, at:"comment", use: commentBookHandle) // 评论 22 | authGroup.post(BookCheckContainer.self, at:"check", use: checkBookHandle) // 书籍审核 23 | /// 获取全部书籍 24 | group.get("list", use: listBooksHandle) 25 | 26 | /// 获取书本的评论 27 | group.get("comments", use: listCommentsHandle) 28 | 29 | /// 获取豆瓣书籍信息?isbn= 30 | group.get("douban", use: getDoubanInfo) 31 | 32 | } 33 | } 34 | 35 | extension BookRouteController { 36 | 37 | func getDoubanInfo(_ request: Request) throws -> Future { 38 | let isbn = try request.query.decode(BookIsbnContainer.self) 39 | let url = "https://api.douban.com/v2/book/isbn/\(isbn.isbn)" 40 | return try request.client().get(url) 41 | } 42 | 43 | /// 书籍审核 44 | func checkBookHandle(_ request: Request, container: BookCheckContainer) throws -> Future { 45 | // 审核成功与失败需要给到消息系统 46 | let _ = try request.requireAuthenticated(User.self) 47 | return Book 48 | .find(container.id, on: request) 49 | .unwrap(or: ApiError(code: .bookNotExist)) 50 | .flatMap { book in 51 | book.state = container.state 52 | return try book.update(on: request).makeJson(on: request) 53 | } 54 | } 55 | 56 | 57 | /// 评论列表 58 | func listCommentsHandle(_ request: Request) throws -> Future { 59 | let container = try request.query.decode(BookCommentListContainer.self) 60 | return try Comment 61 | .query(on: request) 62 | .filter(\.bookId == container.bookId) 63 | .paginate(for: request) 64 | .map {$0.response()} 65 | .makeJson(on: request) 66 | } 67 | 68 | /// 创建评论 69 | func commentBookHandle(_ request: Request, container: Comment) throws -> Future { 70 | let user = try request.requireAuthenticated(User.self) 71 | guard let userId = user.id , userId == container.userId else { 72 | throw ApiError(code: .userNotExist) 73 | } 74 | let comment = Comment(bookId: container.bookId, userId: userId, content: container.content) 75 | // TODO: 推送 + 消息 76 | return try comment.create(on: request).makeJson(on: request) 77 | } 78 | 79 | /// 获取书籍列表, page=1&per=10 80 | func listBooksHandle(_ request: Request) throws -> Future { 81 | let filters = try request.query.decode(BookListContainer.self) 82 | var orderBys:[PostgreSQLOrderBy] = [.orderBy(PostgreSQLExpression.column(PostgreSQLColumnIdentifier.keyPath(\Book.createdAt)), .ascending)] 83 | 84 | switch filters.bType { 85 | case .hot: 86 | orderBys = [.orderBy(PostgreSQLExpression.column(PostgreSQLColumnIdentifier.keyPath(\Book.commentCount)), .ascending)] 87 | case .new: 88 | break 89 | } 90 | 91 | return try Book 92 | .query(on: request) 93 | .filter(\.state ~~ [.putaway, .soldout]) 94 | .paginate(for: request, orderBys) 95 | .map {$0.response()} 96 | .makeJson(on: request) 97 | } 98 | 99 | 100 | /// 书籍的编辑, 只有是用户的书籍才能编辑 101 | func updateBookHandler(_ request: Request, container: BookUpdateContainer) throws -> Future { 102 | let _ = try request.requireAuthenticated(User.self) 103 | return Book 104 | .find(container.id, on: request) 105 | .unwrap(or: ApiError(code: .bookNotExist)) 106 | .flatMap { book in 107 | book.covers = container.convers ?? book.covers 108 | book.detail = container.detail ?? book.detail 109 | book.price = container.price ?? book.price 110 | return try book.update(on: request).makeJson(on: request) 111 | } 112 | } 113 | 114 | /// 创建书籍 115 | func createBookHandler(_ request: Request, container: BookCreateContainer) throws -> Future { 116 | let user = try request.requireAuthenticated(User.self) 117 | let userId = try user.requireID() 118 | let book = Book(isbn: container.isbn, 119 | name: container.name, 120 | author: container.author, 121 | price: container.price, 122 | detail: container.detail, 123 | covers: container.convers, 124 | repCount: 0, 125 | comCount: 0, 126 | collCount: 0, 127 | state: Book.State.check, 128 | doubanPrice: container.doubanPrice, 129 | doubanGrade: container.doubanGrade, 130 | createId: userId, 131 | classifyId: container.classifyId, 132 | priceUintId: container.priceUintId) 133 | return try book 134 | .create(on:request) 135 | .makeJson(on: request) 136 | } 137 | } 138 | 139 | 140 | 141 | -------------------------------------------------------------------------------- /Sources/App/Controllers/API/AppRouteControllers/NewsRouteController.swift: -------------------------------------------------------------------------------- 1 | // 2 | // NewsRouteController.swift 3 | // App 4 | // 5 | // Created by laijihua on 2018/6/27. 6 | // 7 | 8 | import Vapor 9 | import FluentPostgreSQL 10 | 11 | final class NewsRouteController: RouteCollection { 12 | 13 | let notifyService = NotifyService() 14 | 15 | 16 | func boot(router: Router) throws { 17 | let group = router.grouped("api", "news") 18 | let guardAuthMiddleware = User.guardAuthMiddleware() 19 | let tokenAuthMiddleware = User.tokenAuthMiddleware() 20 | let _ = group.grouped([tokenAuthMiddleware, guardAuthMiddleware]) 21 | 22 | // tokenAuthGroup.get("list", use: listNews) 23 | // tokenAuthGroup.get("newer", use: hasNewerNews) 24 | } 25 | } 26 | 27 | extension NewsRouteController { 28 | 29 | // func listNews(_ request: Request) throws -> Future { 30 | // 31 | // } 32 | 33 | // func hasNewerNews(_ request: Request) throws -> Future { 34 | // return try News.query(on: request).all() 35 | // } 36 | } 37 | -------------------------------------------------------------------------------- /Sources/App/Controllers/API/AppRouteControllers/SearchRouteController.swift: -------------------------------------------------------------------------------- 1 | // 2 | // SearchRouteController.swift 3 | // App 4 | // 5 | // Created by laijihua on 2018/6/27. 6 | // 7 | import Vapor 8 | import FluentPostgreSQL 9 | 10 | final class SearchRouteController: RouteCollection { 11 | func boot(router: Router) throws { 12 | 13 | } 14 | } 15 | 16 | extension SearchRouteController { 17 | 18 | } 19 | -------------------------------------------------------------------------------- /Sources/App/Controllers/API/AppRouteControllers/UserRouteController.swift: -------------------------------------------------------------------------------- 1 | // 2 | // UserRouteController.swift 3 | // App 4 | // 5 | // Created by laijihua on 2018/6/16. 6 | // 7 | 8 | import Vapor 9 | import Crypto 10 | import FluentPostgreSQL 11 | import CNIOOpenSSL 12 | 13 | final class UserRouteController: RouteCollection { 14 | private let authService = AuthenticationService() 15 | 16 | func boot(router: Router) throws { 17 | let group = router.grouped("api", "users") 18 | 19 | group.post(EmailLoginContainer.self, at: "login", use: loginUserHandler) 20 | group.post(UserRegisterContainer.self, at: "register", use: registerUserHandler) 21 | /// 修改密码 22 | group.post(NewsPasswordContainer.self, at:"newPassword", use: newPassword) 23 | 24 | /// 发送修改密码验证码 25 | group.post(UserEmailContainer.self, at:"changePwdCode", use: sendPwdCode) 26 | 27 | /// 激活校验码 28 | group.get("activate", use: activeRegisteEmailCode) 29 | 30 | // 微信小程序 31 | // /oauth/token 通过小程序提供的验证信息获取服务器自己的 token 32 | group.post(UserWxAppOauthContainer.self, at: "/oauth/token", use: wxappOauthToken) 33 | } 34 | } 35 | 36 | //MARK: Helper 37 | private extension UserRouteController { 38 | /// 小程序调用wx.login() 获取 临时登录凭证code ,并回传到开发者服务器。 39 | // 开发者服务器以code换取用户唯一标识openid 和 会话密钥session_key。 40 | func wxappOauthToken(_ request: Request, container: UserWxAppOauthContainer) throws -> Future { 41 | 42 | let appId = "wx295f34d030798e48" 43 | let secret = "39a549d066a34c56c8f1d34d606e3a95" 44 | let url = "https://api.weixin.qq.com/sns/jscode2session?appid=\(appId)&secret=\(secret)&js_code=\(container.code)&grant_type=authorization_code" 45 | return try request 46 | .make(Client.self) 47 | .get(url) 48 | .flatMap { response in 49 | guard let res = response.http.body.data else { 50 | throw ApiError(code:.custom) 51 | } 52 | let resContainer = try JSONDecoder().decode(WxAppCodeResContainer.self,from: res) 53 | let sessionKey = try resContainer.session_key.base64decode() 54 | let encryptedData = try container.encryptedData.base64decode() 55 | let iv = try container.iv.base64decode() 56 | 57 | let cipherAlgorithm = CipherAlgorithm(c: OpaquePointer(EVP_aes_128_cbc())) 58 | let shiper = Cipher(algorithm: cipherAlgorithm) 59 | 60 | let decrypted = try shiper.decrypt(encryptedData, key: sessionKey, iv: iv) 61 | let data = try JSONDecoder().decode(WxAppUserInfoContainer.self, from: decrypted) 62 | 63 | if data.watermark.appid == appId { 64 | /// 通过 resContainer.session_key 和 data.openid 65 | /// 66 | return UserAuth 67 | .query(on: request) 68 | .filter(\.identityType == UserAuth.AuthType.wxapp.rawValue) 69 | .filter(\.identifier == data.openId) 70 | .first() 71 | .flatMap { userauth in 72 | if let userAuth = userauth { // 该用户已授权过, 更新 73 | var userAu = userAuth 74 | let digest = try request.make(BCryptDigest.self) 75 | userAu.credential = try digest.hash(resContainer.session_key) 76 | return userAu 77 | .update(on: request) 78 | .flatMap { _ in 79 | return try self.authService.authenticationContainer(for: userAuth.userId, on: request) 80 | } 81 | } else { // 注册 82 | var userAuth = UserAuth(userId: nil, identityType: .wxapp, identifier: data.openId, credential: resContainer.session_key) 83 | let newUser = User(name: data.nickName, 84 | avator: data.avatarUrl) 85 | return newUser 86 | .create(on: request) 87 | .flatMap { user in 88 | userAuth.userId = try user.requireID() 89 | return try userAuth 90 | .userAuth(with: request.make(BCryptDigest.self)) 91 | .create(on: request) 92 | .flatMap { _ in 93 | return try self.authService.authenticationContainer(for: user.requireID(), on: request) 94 | } 95 | } 96 | } 97 | } 98 | } else { 99 | throw ApiError(code: .custom) 100 | } 101 | } 102 | } 103 | 104 | // 激活注册校验码 105 | func activeRegisteEmailCode(_ request: Request) throws -> Future { 106 | // 获取到参数 107 | let filters = try request.query.decode(RegisteCodeContainer.self) 108 | return ActiveCode 109 | .query(on: request) 110 | .filter(\ActiveCode.codeType == ActiveCode.CodeType.activeAccount.rawValue) 111 | .filter(\ActiveCode.userId == filters.userId) 112 | .filter(\ActiveCode.code == filters.code) 113 | .first() 114 | .unwrap(or: ApiError(code: .modelNotExist)) 115 | .flatMap { code in 116 | code.state = true 117 | return try code 118 | .save(on: request) 119 | .map(to: Void.self, {_ in return }) 120 | .makeJson(request: request) 121 | } 122 | } 123 | 124 | 125 | /// 发送修改密码的验证码 126 | func sendPwdCode(_ request: Request, container: UserEmailContainer) throws -> Future { 127 | return UserAuth 128 | .query(on: request) 129 | .filter(\.identityType == UserAuth.AuthType.email.rawValue) 130 | .filter(\.identifier == container.email) 131 | .first() 132 | .unwrap(or: ApiError(code: .modelNotExist)) 133 | .flatMap { existAuth in 134 | let codeStr: String = try String.random(length: 4) 135 | let activeCode = ActiveCode(userId: existAuth.userId, code: codeStr, type: .changePwd) 136 | return try activeCode 137 | .create(on: request) 138 | .flatMap {acode in 139 | let content = EmailSender.Content.changePwd(emailTo: container.email, code: codeStr) 140 | return try self.sendMail(request: request, content: content) 141 | }.makeJson(request: request) 142 | } 143 | 144 | } 145 | 146 | func loginUserHandler(_ request: Request, container: EmailLoginContainer) throws -> Future { 147 | return UserAuth 148 | .query(on: request) 149 | .filter(\UserAuth.identityType == UserAuth.AuthType.email.rawValue) 150 | .filter(\UserAuth.identifier == container.email) 151 | .first() 152 | .unwrap(or: ApiError(code: .modelNotExist)) 153 | .flatMap { existingAuth in 154 | let digest = try request.make(BCryptDigest.self) 155 | guard try digest.verify(container.password, created: existingAuth.credential) else { 156 | throw ApiError(code: .authFail) 157 | } 158 | return try self.authService.authenticationContainer(for: existingAuth.userId, on: request) 159 | } 160 | } 161 | 162 | // TODO: send email has some error , wait 163 | func newPassword(_ request: Request, container: NewsPasswordContainer) throws -> Future { 164 | 165 | return UserAuth 166 | .query(on: request) 167 | .filter(\UserAuth.identityType == UserAuth.AuthType.email.rawValue) 168 | .filter(\UserAuth.identifier == container.email) 169 | .first() 170 | .unwrap(or: ApiError(code: .modelNotExist)) 171 | .flatMap{ userAuth in 172 | return userAuth 173 | .user 174 | .query(on: request) 175 | .first() 176 | .unwrap(or: ApiError(code: .modelNotExist)) 177 | .flatMap { user in 178 | return try user 179 | .codes 180 | .query(on: request) 181 | .filter(\ActiveCode.codeType == ActiveCode.CodeType.changePwd.rawValue) 182 | .filter(\ActiveCode.code == container.code) 183 | .first() 184 | .flatMap { code in 185 | // 只有激活的用户才可以修改密码 186 | guard let code = code, code.state else { 187 | throw ApiError(code: .codeFail) 188 | } 189 | var tmpUserAuth = userAuth 190 | tmpUserAuth.credential = container.password 191 | return try tmpUserAuth 192 | .userAuth(with: request.make(BCryptDigest.self)) 193 | .save(on: request) 194 | .map(to: Void.self, {_ in return }) 195 | .makeJson(request: request) 196 | } 197 | } 198 | 199 | } 200 | } 201 | 202 | func registerUserHandler(_ request: Request, container: UserRegisterContainer) throws -> Future { 203 | return UserAuth 204 | .query(on: request) 205 | .filter(\.identityType == UserAuth.AuthType.email.rawValue) 206 | .filter(\.identifier == container.email) 207 | .first() 208 | .flatMap{ existAuth in 209 | guard existAuth == nil else { 210 | throw ApiError(code: .modelExisted) 211 | } 212 | var userAuth = UserAuth(userId: nil, identityType: .email, identifier: container.email, credential: container.password) 213 | try userAuth.validate() 214 | let newUser = User(name: container.name, 215 | email: container.email, 216 | organizId: container.organizId) 217 | return newUser 218 | .create(on: request) 219 | .flatMap { user in 220 | userAuth.userId = try user.requireID() 221 | return try userAuth 222 | .userAuth(with: request.make(BCryptDigest.self)) 223 | .create(on: request) 224 | .flatMap { _ in 225 | return try self.sendRegisteMail(user: user, request: request) 226 | }.flatMap { _ in 227 | return try self.authService.authenticationContainer(for: user.requireID(), on: request) 228 | } 229 | } 230 | } 231 | } 232 | } 233 | 234 | extension UserAuth { 235 | func userAuth(with digest: BCryptDigest) throws -> UserAuth { 236 | return try UserAuth(userId: userId, identityType: .type(identityType), identifier: identifier, credential: digest.hash(credential)) 237 | } 238 | } 239 | 240 | 241 | -------------------------------------------------------------------------------- /Sources/App/Controllers/API/AppRouteControllers/WishBookRouteController.swift: -------------------------------------------------------------------------------- 1 | // 2 | // WishBookController.swift 3 | // App 4 | // 5 | // Created by laijihua on 2018/6/27. 6 | // 7 | 8 | import Foundation 9 | 10 | import Vapor 11 | import FluentPostgreSQL 12 | 13 | final class WishBookRouteController: RouteCollection { 14 | func boot(router: Router) throws { 15 | let group = router.grouped("api", "wishbook") 16 | let guardAuthMiddleware = User.guardAuthMiddleware() 17 | let tokenAuthMiddleware = User.tokenAuthMiddleware() 18 | let authGroup = group.grouped([tokenAuthMiddleware, guardAuthMiddleware]) 19 | 20 | authGroup.post(WishBookCreateContainer.self, at:"create", use: createWishBook) 21 | authGroup.post(WishBookComment.self, at:"comment", use: commnetWishBook) 22 | 23 | } 24 | } 25 | 26 | extension WishBookRouteController { 27 | 28 | func commnetWishBook(_ request: Request, container: WishBookComment) throws -> Future { 29 | return try container.create(on: request).makeJson(on: request) 30 | } 31 | 32 | func createWishBook(_ request: Request, container: WishBookCreateContainer) throws -> Future { 33 | let wishBook = WishBook(title: container.title, 34 | content: container.content, 35 | userId: container.userId, 36 | commentCount: 0) 37 | return try wishBook.save(on: request).makeJson(on: request) 38 | } 39 | 40 | } 41 | -------------------------------------------------------------------------------- /Sources/App/Controllers/API/RouteControllers/AccountRouteController.swift: -------------------------------------------------------------------------------- 1 | // 2 | // AccountRouteController.swift 3 | // App 4 | // 5 | // Created by laijihua on 2018/6/20. 6 | // 7 | 8 | import Vapor 9 | import FluentPostgreSQL 10 | 11 | final class AccountRouteController: RouteCollection { 12 | 13 | func boot(router: Router) throws { 14 | let group = router.grouped("api", "account") 15 | let guardAuthMiddleware = User.guardAuthMiddleware() 16 | let tokenAuthMiddleware = User.tokenAuthMiddleware() 17 | let tokenAuthGroup = group.grouped([tokenAuthMiddleware, guardAuthMiddleware]) 18 | tokenAuthGroup.get("info", use: getAcccountInfo) 19 | tokenAuthGroup.post(UserUpdateContainer.self, at:"update", use: updateAccountInfo) 20 | } 21 | } 22 | 23 | extension AccountRouteController { 24 | 25 | func updateAccountInfo(_ request: Request, container: UserUpdateContainer) throws -> Future { 26 | let user = try request.requireAuthenticated(User.self) 27 | user.avator = container.avator ?? user.avator 28 | user.name = container.name ?? user.name 29 | user.phone = container.phone ?? user.phone 30 | user.organizId = container.organizId ?? user.organizId 31 | user.info = container.info ?? user.info 32 | return try user.update(on: request).makeJson(on: request) 33 | } 34 | 35 | func getAcccountInfo(_ request: Request) throws -> Future { 36 | let user = try request 37 | .requireAuthenticated(User.self) 38 | return try request.makeJson(user) 39 | } 40 | } 41 | 42 | -------------------------------------------------------------------------------- /Sources/App/Controllers/API/RouteControllers/AuthenticationRouteController.swift: -------------------------------------------------------------------------------- 1 | // 2 | // AuthenticationRouteController.swift 3 | // App 4 | // 5 | // Created by laijihua on 2018/6/16. 6 | // 7 | 8 | import Foundation 9 | import Vapor 10 | import Crypto 11 | import Authentication 12 | 13 | // 由于access_token默认有效时间为一小时, 所以每隔一小时需要点击从而刷新令牌,就是使用refresh_token 换取了一个新的 access_token. 14 | // 由于access_token默认有效时间为一小时, refreshToken 有效期为三年,所以需要先获取refreshToken, 然后将其保存, 以后每次就可以不用去阿里云认证就可以用 refreshToken 换取 AccessToken 15 | 16 | final class AuthenticationRouteController: RouteCollection { 17 | 18 | private let authService = AuthenticationService() 19 | 20 | func boot(router: Router) throws { 21 | let group = router.grouped("api", "token") 22 | group.post(RefreshTokenContainer.self, at: "refresh", use: refreshAccessTokenHandler) 23 | let basicAuthMiddleware = UserAuth.basicAuthMiddleware(using: BCrypt) 24 | let guardAuthMiddleware = User.guardAuthMiddleware() 25 | let basicAuthGroup = group.grouped([basicAuthMiddleware, guardAuthMiddleware]) 26 | basicAuthGroup.post(UserEmailContainer.self, at: "revoke", use: accessTokenRevocationhandler) 27 | } 28 | } 29 | 30 | //MARK: Helper 31 | extension AuthenticationRouteController { 32 | func refreshAccessTokenHandler(_ request: Request, container: RefreshTokenContainer) throws -> Future { 33 | return try authService.authenticationContainer(for: container.refreshToken, on: request) 34 | } 35 | 36 | func accessTokenRevocationhandler(_ request: Request, container: UserEmailContainer) throws -> Future { 37 | return try authService.revokeTokens(forEmail: container.email, on: request).transform(to: .noContent) 38 | } 39 | } 40 | -------------------------------------------------------------------------------- /Sources/App/Controllers/API/RouteControllers/ProtectedRoutesController.swift: -------------------------------------------------------------------------------- 1 | // 2 | // ProtectedRoutesController.swift 3 | // App 4 | // 5 | // Created by laijihua on 2018/6/16. 6 | // 7 | 8 | import Vapor 9 | import Authentication 10 | 11 | final class ProtectedRoutesController: RouteCollection { 12 | func boot(router: Router) throws { 13 | let group = router.grouped("api", "protected") 14 | 15 | let basicAuthMiddleware = UserAuth.basicAuthMiddleware(using: BCrypt) 16 | let guardAuthMiddleware = UserAuth.guardAuthMiddleware() 17 | let basicAuthGroup = group.grouped([basicAuthMiddleware, guardAuthMiddleware]) 18 | basicAuthGroup.get("basic", use: basicAuthRouteHandler) 19 | 20 | /// App 采用这个 21 | let tokenAuthMiddleware = User.tokenAuthMiddleware() 22 | let tokenAuthGroup = group.grouped([tokenAuthMiddleware, guardAuthMiddleware]) 23 | tokenAuthGroup.get("token", use: tokenAuthRouteHandler) 24 | } 25 | } 26 | 27 | //MARK: Helper 28 | private extension ProtectedRoutesController { 29 | 30 | /// 用 basic 获取用户信息 31 | func basicAuthRouteHandler(_ request: Request) throws -> Future { 32 | let user = try request 33 | .requireAuthenticated(User.self) 34 | return try request.makeJson(user) 35 | } 36 | 37 | /// 用 token 获取用户信息 38 | func tokenAuthRouteHandler(_ request: Request) throws -> Future { 39 | let user = try request 40 | .requireAuthenticated(User.self) 41 | return try request.makeJson(user) 42 | } 43 | } 44 | 45 | 46 | 47 | -------------------------------------------------------------------------------- /Sources/App/Controllers/API/RouteControllers/SysRouteController.swift: -------------------------------------------------------------------------------- 1 | // 2 | // SysRouteController.swift 3 | // App 4 | // 5 | // Created by laijihua on 2018/7/6. 6 | // 7 | 8 | import Foundation 9 | import Crypto 10 | 11 | import Vapor 12 | import FluentPostgreSQL 13 | import Pagination 14 | 15 | final class SysRouteController: RouteCollection { 16 | 17 | private let authService = AuthenticationService() 18 | 19 | func boot(router: Router) throws { 20 | let group = router.grouped("api", "sys") 21 | let guardAuthMiddleware = User.guardAuthMiddleware() 22 | let tokenAuthMiddleware = User.tokenAuthMiddleware() 23 | let tokenAuthGroup = group.grouped([tokenAuthMiddleware, guardAuthMiddleware]) 24 | 25 | /// 菜单管理 26 | let menuGroup = tokenAuthGroup.grouped("menu") 27 | menuGroup.get("list", use: getMenuList) 28 | menuGroup.post(Menu.self, at:"add", use: createMenu) 29 | menuGroup.post(DeleteIDContainer

.self, at:"delete", use: deleteMenu) 30 | menuGroup.post(Menu.self, at:"update", use: updateMenu) 31 | 32 | /// 角色管理 33 | let roleGroup = tokenAuthGroup.grouped("role") // 34 | roleGroup.post(Role.self, at: "add", use: createRole) 35 | roleGroup.post(DeleteIDContainer.self, at: "delete", use: deleteRole) 36 | roleGroup.get("list", use: getRoleList) 37 | roleGroup.post(Role.self,at:"update", use: updateRole) 38 | 39 | /// 用户管理 40 | let userGroup = group.grouped("user") 41 | userGroup.post(DeleteIDContainer.self, at: "delete", use: deleteUser) 42 | userGroup.get("page", use: listUser) 43 | userGroup.post(UserRegisterContainer.self, at:"add", use: addUser) 44 | 45 | /// 权限(资源)管理 46 | let rightGroup = group.grouped("resource") 47 | rightGroup.get("list", use: listRight) 48 | rightGroup.post(Right.self,at:"update", use: updateRight) 49 | rightGroup.post(Right.self, at:"add", use: addRight) 50 | rightGroup.post(DeleteIDContainer.self, at:"delete", use: deleteRight) 51 | } 52 | } 53 | 54 | //MARK: - Right 55 | extension SysRouteController { 56 | 57 | func updateRight(_ request: Request, container: Right) throws -> Future { 58 | let _ = try request.requireAuthenticated(User.self) 59 | return Right 60 | .query(on: request) 61 | .filter(\.id == container.id) 62 | .first() 63 | .unwrap(or: ApiError(code: .modelNotExist)) 64 | .flatMap { right in 65 | right.parentId = container.parentId 66 | right.name = container.name 67 | right.remarks = container.remarks 68 | right.code = container.code 69 | right.type = container.type 70 | return try right.update(on: request).makeJson(on: request) 71 | } 72 | } 73 | 74 | func addRight(_ request: Request, container: Right) throws -> Future { 75 | let _ = try request.requireAuthenticated(User.self) 76 | return Right 77 | .query(on: request) 78 | .filter(\Right.name == container.name) 79 | .first() 80 | .flatMap { exisRole in 81 | guard exisRole == nil else { 82 | throw ApiError(code: .modelExisted) 83 | } 84 | container.id = nil 85 | return try container.create(on: request).makeJson(on: request) 86 | } 87 | } 88 | 89 | func deleteRight(_ request: Request, container: DeleteIDContainer) throws -> Future { 90 | let _ = try request.requireAuthenticated(User.self) 91 | return Right 92 | .find(container.id, on: request) 93 | .unwrap(or: ApiError(code: .modelNotExist)) 94 | .flatMap { user in 95 | return try user 96 | .delete(on: request) 97 | .makeJson(request: request) 98 | } 99 | } 100 | 101 | func listRight(_ request: Request) throws -> Future { 102 | let _ = try request.requireAuthenticated(User.self) 103 | return try Right 104 | .query(on: request) 105 | .paginate(for: request) 106 | .map {$0.response()} 107 | .makeJson(on: request) 108 | } 109 | 110 | } 111 | 112 | //MARK: - User 113 | extension SysRouteController { 114 | func addUser(_ request: Request, container: UserRegisterContainer) throws -> Future { 115 | return UserAuth 116 | .query(on: request) 117 | .filter(\.identityType == UserAuth.AuthType.email.rawValue) 118 | .filter(\.identifier == container.email) 119 | .first() 120 | .flatMap{ existAuth in 121 | guard existAuth == nil else { 122 | throw ApiError(code: .modelExisted) 123 | } 124 | 125 | var userAuth = UserAuth(userId: nil, identityType: .email, identifier: container.email, credential: container.password) 126 | try userAuth.validate() 127 | let newUser = User(name: container.name, 128 | email: container.email, 129 | organizId: container.organizId) 130 | 131 | return newUser 132 | .create(on: request) 133 | .flatMap { user in 134 | userAuth.userId = try user.requireID() 135 | return try userAuth 136 | .userAuth(with: request.make(BCryptDigest.self)) 137 | .create(on: request) 138 | .flatMap { _ in 139 | return try self.sendRegisteMail(user: user, request: request) 140 | }.flatMap { _ in 141 | return try self.authService.authenticationContainer(for: user.requireID(), on: request) 142 | } 143 | } 144 | } 145 | } 146 | 147 | func listUser(_ request: Request) throws -> Future { 148 | return try User 149 | .query(on: request) 150 | .paginate(for: request) 151 | .map {$0.response()} 152 | .makeJson(on: request) 153 | } 154 | 155 | func deleteUser(_ request: Request, container: DeleteIDContainer) throws -> Future { 156 | let _ = try request.requireAuthenticated(User.self) 157 | return User 158 | .find(container.id, on: request) 159 | .unwrap(or: ApiError(code: .modelNotExist)) 160 | .flatMap { user in 161 | return try user 162 | .delete(on: request) 163 | .makeJson(request: request) 164 | } 165 | } 166 | } 167 | 168 | //MARK: - Role 169 | extension SysRouteController { 170 | func updateRole(_ request: Request, container: Role) throws -> Future { 171 | let _ = try request.requireAuthenticated(User.self) 172 | return Role 173 | .query(on: request) 174 | .filter(\.id == container.id) 175 | .first() 176 | .unwrap(or: ApiError(code: .modelNotExist)) 177 | .flatMap { role in 178 | role.parentId = container.parentId 179 | role.name = container.name 180 | role.sort = container.sort 181 | role.usable = container.usable 182 | return try role.update(on: request).makeJson(on: request) 183 | } 184 | } 185 | 186 | func createRole(_ request: Request, role: Role) throws -> Future { 187 | let _ = try request.requireAuthenticated(User.self) 188 | return Role 189 | .query(on: request) 190 | .filter(\Role.name == role.name) 191 | .first() 192 | .flatMap { exisRole in 193 | guard exisRole == nil else { 194 | throw ApiError(code: .modelExisted) 195 | } 196 | role.id = nil 197 | return try role.create(on: request).makeJson(on: request) 198 | } 199 | } 200 | 201 | func deleteRole(_ request: Request, container: DeleteIDContainer) throws -> Future { 202 | let _ = try request.requireAuthenticated(User.self) 203 | return Role 204 | .find(container.id, on: request) 205 | .unwrap(or: ApiError(code: .modelNotExist)) 206 | .flatMap { role in 207 | return try role 208 | .delete(on: request) 209 | .makeJson(request: request) 210 | } 211 | } 212 | 213 | func getRoleList(_ request: Request) throws -> Future { 214 | let _ = try request.requireAuthenticated(User.self) 215 | return try Role 216 | .query(on: request) 217 | .all() 218 | .map{ roles in 219 | return self.generateModelTree(parentId: 0, originArray: roles) 220 | }.makeJson(on: request) 221 | } 222 | 223 | } 224 | 225 | //MARK: - Menu 226 | extension SysRouteController { 227 | 228 | func updateMenu(_ request: Request, container: Menu) throws -> Future { 229 | let _ = try request.requireAuthenticated(User.self) 230 | return Menu 231 | .query(on: request) 232 | .filter(\.id == container.id) 233 | .first() 234 | .unwrap(or: ApiError(code: .modelNotExist)) 235 | .flatMap { menu in 236 | menu.parentId = container.parentId 237 | menu.name = container.name 238 | menu.href = container.href 239 | menu.sort = container.sort 240 | menu.icon = container.icon 241 | menu.isShow = container.isShow 242 | return try menu.update(on: request).makeJson(on: request) 243 | } 244 | } 245 | 246 | func deleteMenu(_ request: Request, container: DeleteIDContainer) throws -> Future { 247 | let _ = try request.requireAuthenticated(User.self) 248 | return Menu 249 | .find(container.id, on: request) 250 | .unwrap(or: ApiError(code: .modelNotExist)) 251 | .flatMap { menu in 252 | return try menu 253 | .delete(on: request) 254 | .makeJson(request: request) 255 | } 256 | } 257 | 258 | /// 创建一个菜单代表一个新的功能注入, 那么也需要为其生成一个操作权限 259 | func createMenu(_ request: Request, menu: Menu) throws -> Future { 260 | let _ = try request.requireAuthenticated(User.self) 261 | return Menu 262 | .query(on: request) 263 | .filter(\Menu.name == menu.name) 264 | .first() 265 | .flatMap { exisMenu in 266 | guard exisMenu == nil else { 267 | throw ApiError(code: .modelExisted) 268 | } 269 | menu.id = nil; 270 | // menu&role 271 | return try menu.create(on: request).makeJson(on: request) 272 | } 273 | } 274 | 275 | func getMenuList(_ request: Request) throws -> Future { 276 | let _ = try request.requireAuthenticated(User.self) 277 | return try Menu 278 | .query(on: request) 279 | .all() 280 | .map{ menus in 281 | return self.generateModelTree(parentId: 0, originArray: menus) 282 | }.makeJson(on: request) 283 | } 284 | } 285 | 286 | 287 | 288 | 289 | 290 | -------------------------------------------------------------------------------- /Sources/App/Middlewares/APIErrorMiddleware/APIErrorMiddleware.swift: -------------------------------------------------------------------------------- 1 | import Vapor 2 | import Foundation 3 | 4 | /// Catches errors thrown from route handlers or middleware 5 | /// further down the responder chain and converts it to 6 | /// a JSON response. 7 | /// 8 | /// Errors with an identifier of `modelNotFound` get 9 | /// a 404 status code. 10 | public final class APIErrorMiddleware: Middleware, Service, ServiceType { 11 | 12 | /// Specializations for converting specific errors 13 | /// to `ErrorResult` objects. 14 | public var specializations: [ErrorCatchingSpecialization] 15 | 16 | /// The current environemnt that the application is in. 17 | public let environment: Environment 18 | 19 | /// Create an instance if `APIErrorMiddleware`. 20 | public init(environment: Environment, specializations: [ErrorCatchingSpecialization] = []) { 21 | self.specializations = specializations 22 | self.environment = environment 23 | } 24 | 25 | /// Creates a service instance. Used by a `ServiceFactory`. 26 | public static func makeService(for worker: Container) throws -> APIErrorMiddleware { 27 | #if canImport(Fluent) 28 | return APIErrorMiddleware(environment: worker.environment, specializations: [ModelNotFound()]) 29 | #else 30 | return APIErrorMiddleware(environment: worker.environment, specializations: []) 31 | #endif 32 | } 33 | 34 | /// Catch all errors thrown by the route handler or 35 | /// middleware futher down the responder chain and 36 | /// convert it to a JSON response. 37 | public func respond(to request: Request, chainingTo next: Responder) throws -> Future { 38 | 39 | // Call the next responder in the reponse chain. 40 | // If the future returned contains an error, or if 41 | // the next responder throws an error, catch it and 42 | // convert it to a JSON response. 43 | return Future.flatMap(on: request) { 44 | return try next.respond(to: request) 45 | }.mapIfError { error in 46 | return self.response(for: error, with: request) 47 | } 48 | } 49 | 50 | /// Creates a response with a JSON body. 51 | /// 52 | /// - Parameters: 53 | /// - error: The error that will be the value of the 54 | /// `error` key in the responses JSON body. 55 | /// - request: The request we wil get a container from 56 | /// to create the resulting reponse in. 57 | /// 58 | /// - Returns: A response with a JSON body with a `{"error":}` structure. 59 | private func response(for error: Error, with request: Request) -> Response { 60 | 61 | // The error message and status code 62 | // for the response returned by the 63 | // middleware. 64 | var result: ErrorResult! 65 | 66 | // The HTTP headers to send with the error 67 | var headers: HTTPHeaders = ["Content-Type": "application/json"] 68 | 69 | 70 | // Loop through the specializations, running 71 | // the error converter on each one. 72 | for converter in self.specializations { 73 | if let formatted = converter.convert(error: error, on: request) { 74 | 75 | // Found a non-nil response. Save it and break 76 | // from the loop so we don't override it. 77 | result = formatted 78 | break 79 | } 80 | } 81 | var status: UInt? = nil 82 | if result == nil { 83 | switch error { 84 | case let apiError as ApiError: 85 | result = ErrorResult(message: apiError.reason, status: apiError.status) 86 | apiError.headers.forEach { name, value in 87 | headers.add(name: name, value: value) 88 | } 89 | status = apiError.code.rawValue 90 | 91 | case let abort as AbortError: 92 | // We have an `AbortError` which has both a 93 | // status code and error message. 94 | // Assign the data to the correct varaibles. 95 | result = ErrorResult(message: abort.reason, status: abort.status) 96 | 97 | abort.headers.forEach { name, value in 98 | headers.add(name: name, value: value) 99 | } 100 | status = result.status?.code 101 | case let debuggable as Debuggable where !self.environment.isRelease: 102 | // Since we are not in a production environment and we 103 | // have a error conforming to `Debuggable`, we get the 104 | // data about the error and create a result with it. 105 | // We don't do this in a production env because the error 106 | // might container sensetive information 107 | let reason = debuggable.debuggableHelp(format: .short) 108 | result = ErrorResult(message: reason, status: .internalServerError) 109 | status = result.status?.code 110 | default: 111 | // We use a compiler OS check because `Error` can be directly 112 | // convertred to `CustomStringConvertible` on macOS, but not 113 | // on Linux. 114 | #if !os(macOS) 115 | if let error = error as? CustomStringConvertible { 116 | result = ErrorResult(message: error.description, status: nil) 117 | } else { 118 | result = ErrorResult(message: "Unknown error.", status: nil) 119 | } 120 | #else 121 | result = ErrorResult(message: (error as CustomStringConvertible).description, status: nil) 122 | #endif 123 | status = result.status?.code 124 | } 125 | } 126 | 127 | let json: Data 128 | do { 129 | // Create JSON with an `error` key with the `message` constant as its value. 130 | let container = ApiErrorContainer(status: status ?? 10086, message: result.message) 131 | json = try JSONEncoder().encode(container) 132 | } catch { 133 | // Creating JSON data from error failed, so create a generic response message 134 | // because we can't have any Swift errors leaving the middleware. 135 | json = Data("{\"message\": \"Unable to encode error to JSON\", \"status\":10086}".utf8) 136 | } 137 | 138 | // Create an HTTPResponse with 139 | // - The detected status code, using 140 | // 400 (Bad Request) if one does not exist. 141 | // - A `application/json` Content Type header. 142 | // A body with the JSON we created. 143 | let httpResponse = HTTPResponse( 144 | status: result.status ?? .badRequest, 145 | headers: headers, 146 | body: HTTPBody(data: json) 147 | ) 148 | 149 | // Create the response and return it. 150 | return Response(http: httpResponse, using: request.sharedContainer) 151 | } 152 | } 153 | -------------------------------------------------------------------------------- /Sources/App/Middlewares/APIErrorMiddleware/Specialization/Specialization.swift: -------------------------------------------------------------------------------- 1 | import Vapor 2 | 3 | /// The data used to create a response 4 | /// from a Swift error. 5 | public struct ErrorResult { 6 | 7 | /// The value of the 'error' key in 8 | /// the JSON returned by the middleware. 9 | public let message: String 10 | 11 | /// The status code for the response 12 | /// returned by the middleware 13 | public let status: HTTPStatus? 14 | 15 | /// Creates an instance with a 'message' and 'status'. 16 | public init(message: String, status: HTTPStatus?) { 17 | self.message = message 18 | self.status = status 19 | } 20 | } 21 | 22 | /// Converts a Swift error, along with data from a request, 23 | /// to a `ErroResult` instance, which can be used to create 24 | /// a JSON response 25 | public protocol ErrorCatchingSpecialization { 26 | 27 | /// Converts a Swift error, along with data from a request, 28 | /// to a `ErroResult` instance, which can be used to create 29 | /// a JSON response 30 | /// 31 | /// - Parameters: 32 | /// - error: The error to convert to a message. 33 | /// - request: The request that the error originated from. 34 | /// 35 | /// - Returns: An `ErrorResult` instance. The result should be `nil` 36 | /// if the specialization doesn't convert the kind of the error 37 | /// passed in. 38 | func convert(error: Error, on request: Request) -> ErrorResult? 39 | } 40 | 41 | // MARK: - ErrorCatchingSpecialization implementations 42 | 43 | #if canImport(Fluent) 44 | import Fluent 45 | 46 | /// Catches Fluent's `modelNotFound` error and returns a 404 status code. 47 | public struct ModelNotFound: ErrorCatchingSpecialization { 48 | public init() {} 49 | 50 | public func convert(error: Error, on request: Request) -> ErrorResult? { 51 | if 52 | let wrappingError = error as? NotFound, 53 | let error = wrappingError.rootCause as? FluentError 54 | { 55 | return ErrorResult(message: error.reason, status: .notFound) 56 | } 57 | 58 | return nil 59 | } 60 | } 61 | #endif 62 | -------------------------------------------------------------------------------- /Sources/App/Middlewares/read.md: -------------------------------------------------------------------------------- 1 | # <#Title#> 2 | 3 | -------------------------------------------------------------------------------- /Sources/App/Models/.gitkeep: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/OHeroJ/BookVapor/b97b43d545d2d35f48006400814860e5d9fd0385/Sources/App/Models/.gitkeep -------------------------------------------------------------------------------- /Sources/App/Models/Entities/ActiveCode.swift: -------------------------------------------------------------------------------- 1 | // 2 | // ActiveCode.swift 3 | // App 4 | // 5 | // Created by laijihua on 2018/6/23. 6 | // 7 | 8 | import Vapor 9 | import FluentPostgreSQL 10 | 11 | /// 邮箱验证码 12 | final class ActiveCode: Content { 13 | var id: Int? 14 | var userId: User.ID 15 | var state: Bool // 是否激活, 使用过 16 | var code: String 17 | var codeType: String // 验证码类型 18 | 19 | var createdAt: Date? 20 | var updatedAt: Date? 21 | var deletedAt: Date? 22 | 23 | static var createdAtKey: TimestampKey? { return \.createdAt } 24 | static var updatedAtKey: TimestampKey? { return \.updatedAt } 25 | static var deletedAtKey: TimestampKey? { return \.deletedAt } 26 | 27 | init(userId: User.ID, code: String, type: CodeType, state: Bool = false) { 28 | self.userId = userId 29 | self.code = code 30 | self.state = state 31 | self.codeType = type.rawValue 32 | } 33 | } 34 | 35 | extension ActiveCode { 36 | 37 | /// 验证码类型 38 | enum CodeType: String { 39 | case changePwd = "changePwd" // 修改密码的时候的邮件验证码 40 | case activeAccount = "activeAccount" // 判断用户注册的邮箱是否激活过 41 | } 42 | } 43 | 44 | extension ActiveCode: PostgreSQLModel {} 45 | extension ActiveCode: Migration { 46 | 47 | static func prepare(on connection: PostgreSQLConnection) -> Future { 48 | return Database.create(self, on: connection) { builder in 49 | try addProperties(to: builder) 50 | builder.reference(from: \.userId, to: \User.id) 51 | } 52 | } 53 | } 54 | 55 | extension ActiveCode { 56 | var user: Parent { 57 | return parent(\.userId) 58 | } 59 | } 60 | 61 | 62 | -------------------------------------------------------------------------------- /Sources/App/Models/Entities/Authentication/Token/AccessToken.swift: -------------------------------------------------------------------------------- 1 | // 2 | // AccessToken.swift 3 | // App 4 | // 5 | // Created by laijihua on 2018/6/15. 6 | // 7 | 8 | import Vapor 9 | import FluentPostgreSQL 10 | import Crypto 11 | import Authentication 12 | 13 | 14 | struct AccessToken: Content { 15 | struct Const { 16 | static let expirationInterval: TimeInterval = 3600 17 | } 18 | typealias Token = String 19 | var id: Int? 20 | private(set) var token: Token 21 | private(set) var userId: Int 22 | let expiryTime: Date 23 | 24 | var createdAt: Date? 25 | var updatedAt: Date? 26 | var deletedAt: Date? 27 | static var createdAtKey: TimestampKey? { return \.createdAt } 28 | static var updatedAtKey: TimestampKey? { return \.updatedAt } 29 | static var deletedAtKey: TimestampKey? { return \.deletedAt } 30 | 31 | init(userId: Int) throws { 32 | self.token = try CryptoRandom().generateData(count: 32).base64URLEncodedString() 33 | self.userId = userId 34 | self.expiryTime = Date().addingTimeInterval(Const.expirationInterval) 35 | } 36 | } 37 | 38 | extension AccessToken: PostgreSQLModel {} 39 | extension AccessToken: Migration {} 40 | 41 | 42 | extension AccessToken: BearerAuthenticatable { 43 | static var tokenKey: WritableKeyPath = \.token 44 | static func authenticate(using bearer: BearerAuthorization, on connection: DatabaseConnectable) -> Future { 45 | return Future.flatMap(on: connection) { 46 | return AccessToken.query(on: connection) 47 | .filter(tokenKey == bearer.token) 48 | .first() 49 | .map{ token in 50 | guard let token = token, token.expiryTime > Date() else { 51 | return nil 52 | } 53 | return token 54 | } 55 | } 56 | } 57 | } 58 | 59 | extension AccessToken: Token { 60 | typealias UserType = User 61 | static var userIDKey: WritableKeyPath = \.userId 62 | } 63 | 64 | -------------------------------------------------------------------------------- /Sources/App/Models/Entities/Authentication/Token/RefreshToken.swift: -------------------------------------------------------------------------------- 1 | // 2 | // RefreshToken.swift 3 | // App 4 | // 5 | // Created by laijihua on 2018/6/15. 6 | // 7 | 8 | import Vapor 9 | import FluentPostgreSQL 10 | import Crypto 11 | 12 | struct RefreshToken: Content { 13 | typealias Token = String 14 | 15 | var id: Int? 16 | let token: Token 17 | let userId: Int 18 | 19 | var createdAt: Date? 20 | var updatedAt: Date? 21 | var deletedAt: Date? 22 | 23 | static var createdAtKey: TimestampKey? { return \.createdAt } 24 | static var updatedAtKey: TimestampKey? { return \.updatedAt } 25 | static var deletedAtKey: TimestampKey? { return \.deletedAt } 26 | 27 | init(userId: Int) throws { 28 | self.token = try CryptoRandom().generateData(count: 32).base64URLEncodedString() 29 | self.userId = userId 30 | } 31 | } 32 | 33 | extension RefreshToken: PostgreSQLModel {} 34 | extension RefreshToken: Migration {} 35 | 36 | 37 | 38 | 39 | 40 | -------------------------------------------------------------------------------- /Sources/App/Models/Entities/Authentication/UserAuth.swift: -------------------------------------------------------------------------------- 1 | // 2 | // UserAuth.swift 3 | // App 4 | // 5 | // Created by laijihua on 2018/9/4. 6 | // 7 | 8 | import Vapor 9 | import FluentPostgreSQL 10 | import Crypto 11 | import Authentication 12 | 13 | /// 用该信息获取到 token 14 | struct UserAuth: Content { 15 | var id: Int? 16 | var userId: User.ID 17 | var identityType: String // 登录类型 18 | var identifier: String // 标志 (手机号,邮箱,用户名或第三方应用的唯一标识) 19 | var credential: String // 密码凭证(站内的保存密码, 站外的不保存或保存 token) 20 | 21 | var createdAt: Date? 22 | var updatedAt: Date? 23 | var deletedAt: Date? 24 | 25 | static var createdAtKey: TimestampKey? { return \.createdAt } 26 | static var updatedAtKey: TimestampKey? { return \.updatedAt } 27 | static var deletedAtKey: TimestampKey? { return \.deletedAt } 28 | 29 | init(userId: User.ID?, identityType: AuthType, identifier: String, credential: String) { 30 | self.userId = userId ?? 0 31 | self.identityType = identityType.rawValue 32 | self.identifier = identifier 33 | self.credential = credential 34 | } 35 | } 36 | 37 | extension UserAuth { 38 | enum AuthType: String { 39 | case email = "email" 40 | case wxapp = "wxapp" // 微信小程序 41 | 42 | static func type(_ val: String) -> AuthType { 43 | return AuthType(rawValue: val) ?? .email 44 | } 45 | } 46 | } 47 | 48 | extension UserAuth { 49 | var user: Parent { 50 | return parent(\.userId) 51 | } 52 | } 53 | 54 | extension UserAuth: BasicAuthenticatable { 55 | static var usernameKey: WritableKeyPath = \.identifier 56 | static var passwordKey: WritableKeyPath = \.credential 57 | } 58 | 59 | extension UserAuth: PostgreSQLModel {} 60 | extension UserAuth: Migration {} 61 | 62 | extension UserAuth: Validatable { 63 | /// 只针对 email 的校验 64 | static func validations() throws -> Validations { 65 | var validations = Validations(UserAuth.self) 66 | try validations.add(\.identifier, .email) 67 | try validations.add(\.credential, .password) 68 | return validations 69 | } 70 | } 71 | -------------------------------------------------------------------------------- /Sources/App/Models/Entities/Book.swift: -------------------------------------------------------------------------------- 1 | // 2 | // Book.swift 3 | // App 4 | // 5 | // Created by laijihua on 2018/6/22. 6 | // 7 | 8 | import Vapor 9 | import FluentPostgreSQL 10 | import Pagination 11 | 12 | final class Book: Content { 13 | var id:Int? 14 | var covers: [String] 15 | var name: String 16 | var isbn: String 17 | var author: String 18 | var price: Double 19 | var detail: String 20 | var commentCount: Int 21 | var collectCount: Int 22 | var reportCount: Int 23 | var state: State 24 | var doubanPrice: Double 25 | var doubanGrade: Double // 豆瓣评分 26 | 27 | var createId: User.ID // 创建者 id 多-1 28 | var classifyId: BookClassify.ID // 分类 id 多-1 29 | var priceUintId: PriceUnit.ID // 货币 id 多对1 30 | 31 | var createdAt: Date? 32 | var updatedAt: Date? 33 | var deletedAt: Date? 34 | 35 | static var createdAtKey: TimestampKey? { return \.createdAt } 36 | static var updatedAtKey: TimestampKey? { return \.updatedAt } 37 | static var deletedAtKey: TimestampKey? { return \.deletedAt } 38 | 39 | init(isbn: String, 40 | name: String, 41 | author: String, 42 | price: Double, 43 | detail: String, 44 | covers: [String], 45 | repCount: Int, 46 | comCount: Int, 47 | collCount: Int, 48 | state: Book.State, 49 | doubanPrice: Double, 50 | doubanGrade: Double, 51 | createId: User.ID, 52 | classifyId: BookClassify.ID, 53 | priceUintId: PriceUnit.ID) { 54 | self.author = author 55 | self.isbn = isbn 56 | self.name = name 57 | self.price = price 58 | self.detail = detail 59 | self.covers = covers 60 | self.commentCount = comCount 61 | self.collectCount = collCount 62 | self.state = state 63 | self.doubanPrice = doubanPrice 64 | self.doubanGrade = doubanGrade 65 | self.createId = createId 66 | self.classifyId = classifyId 67 | self.priceUintId = priceUintId 68 | self.reportCount = repCount 69 | } 70 | } 71 | 72 | extension Book { 73 | var creator:Parent { // 多 - 1 74 | return parent(\.createId) 75 | } 76 | 77 | var classify:Parent { 78 | return parent(\.classifyId) 79 | } 80 | 81 | var unit:Parent { 82 | return parent(\.priceUintId) 83 | } 84 | 85 | var collectors: Siblings { // 收藏者 86 | return siblings() 87 | } 88 | } 89 | 90 | extension Book: Paginatable {} 91 | 92 | extension Book { 93 | enum State: Int, Codable { 94 | case check = 0 // 审核状态 95 | case putaway = 1 // 发布状态 96 | case soldout = 2 // 下架 97 | case deleted = 3 // 删除 98 | } 99 | } 100 | 101 | extension Book: PostgreSQLModel {} 102 | extension Book: Migration { 103 | 104 | static func prepare(on connection: PostgreSQLConnection) -> Future { 105 | return Database.create(self, on: connection) { builder in 106 | try addProperties(to: builder) 107 | builder.reference(from: \.createId, to: \User.id) 108 | builder.reference(from: \.classifyId, to: \BookClassify.id) 109 | builder.reference(from: \.priceUintId, to: \PriceUnit.id) 110 | } 111 | } 112 | } 113 | 114 | 115 | 116 | 117 | -------------------------------------------------------------------------------- /Sources/App/Models/Entities/BookClassify+Populate.swift: -------------------------------------------------------------------------------- 1 | // 2 | // BookClassify+Populate.swift 3 | // App 4 | // 5 | // Created by laijihua on 2018/9/13. 6 | // 7 | import Vapor 8 | import FluentPostgreSQL 9 | 10 | /// 数据填充 11 | final class PopulateBookClassifyForms: Migration { 12 | typealias Database = PostgreSQLDatabase 13 | 14 | static let categories = [ 15 | "文学小说": [ 16 | "恐怖/惊悚小说", 17 | "悬疑推理小说", 18 | "翻译文学", 19 | "科幻小说", 20 | "历史武侠", 21 | "古典文学", 22 | "言情小说", 23 | "现代文学", 24 | "现代小说", 25 | "儿童文学" 26 | ], 27 | "轻小说/漫画": [ 28 | "历史战役漫画","图文书/绘本","华文轻小说", "奇幻/魔法漫书", "动作冒险漫画", "悬疑推理漫画", "BL/GL", "日本轻小说", "运动/竞技漫画", "科幻漫画", "灵异漫画"], 29 | "心理/宗教": ["励志/散文", "人际关系", "两性/家庭关系", "宗教命理", "心理学", "个人成长"], 30 | "知识学习": ["韩语", "中文", "日语", "外语", "英语", "语言能力", "电脑资讯", "音乐"], 31 | "商业理财": ["电子商务", "成功法", "管理", "经济", "传记", "投资理财", "广告/业务", "职场"], 32 | "人文史地": ["中国历史", "哲学", "当代", "世界历史", "逻辑/思考"], 33 | "社会科学": ["社会议题", "文化研究", "新闻学", "报道文学", "性别研究", "政治", "军事"], 34 | "艺术设计": ["室内设计", "电影", "摄影", "戏剧", "设计", "绘图", "建筑", "收藏鉴赏"], 35 | "生活风格/饮食": ["休闲", "居家生活", "个人护理", "宠物", "户外", "手作", "食谱", "饮食文化"], 36 | "教科读物": ["小学", "初中", "高中", "大学"], 37 | "旅游": ["中国", "旅游文学", "美洲", "欧洲", "非洲/大洋洲", "亚洲", "主题旅游"], 38 | "自然科普":["应用科学", "工程", "天文学"], 39 | "计算机": ["程序设计", "期刊", "操作系统", "基础知识"] 40 | ] 41 | 42 | static func getHeadId(on connection: PostgreSQLConnection, title: String) -> Future { 43 | let cat = BookClassify(name: title, parentId: 0, path: "0") 44 | return cat.create(on: connection).map { classi in 45 | return classi.id! 46 | } 47 | } 48 | 49 | static func creatSubCategory(headId:BookClassify.ID, title: String, conn: PostgreSQLConnection) -> EventLoopFuture { 50 | let subs = categories[title] ?? [] 51 | let subfutures = subs.compactMap { item in 52 | return BookClassify(name: item, parentId: headId, path: "\(headId)").create(on: conn) 53 | .map(to:Void.self, {_ in return}) 54 | } 55 | return Future.andAll(subfutures, eventLoop: conn.eventLoop) 56 | } 57 | 58 | 59 | 60 | static func searchCateId(title: String, conn: PostgreSQLConnection) -> Future { 61 | return BookClassify.query(on: conn) 62 | .filter(\BookClassify.name == title) 63 | .first() 64 | .unwrap(or: FluentError(identifier: "PopulateBookClassifyForms_noSuchHeat", reason: "PopulateBookClassifyForms_noSuchHeat")) 65 | .map { return $0.id! } 66 | } 67 | 68 | static func deleteCates(on conn:PostgreSQLConnection, heatName: String, subcates: [String]) -> EventLoopFuture { 69 | return searchCateId(title: heatName, conn: conn).flatMap(to: Void.self) { heatId in 70 | let futures = subcates.map { name in 71 | return BookClassify.query(on: conn).filter(\.name == name).delete() 72 | } 73 | return Future.andAll(futures, eventLoop: conn.eventLoop) 74 | } 75 | } 76 | 77 | static func prepare(on conn: PostgreSQLConnection) -> Future { 78 | let keys = categories.keys 79 | 80 | let futures = keys.map { title -> EventLoopFuture in 81 | let future = getHeadId(on: conn, title: title) 82 | .flatMap { headId -> EventLoopFuture in 83 | return creatSubCategory(headId: headId, title: title, conn: conn) 84 | } 85 | return future 86 | } 87 | return Future.andAll(futures, eventLoop: conn.eventLoop) 88 | } 89 | 90 | 91 | static func revert(on conn: PostgreSQLConnection) -> EventLoopFuture { 92 | let futures = categories.map { (arg) -> EventLoopFuture in 93 | let (name, touples) = arg 94 | let allFut = deleteCates(on: conn, heatName: name, subcates: touples).always { 95 | _ = BookClassify.query(on: conn).filter(\.name == name).delete() 96 | } 97 | return allFut 98 | } 99 | return Future.andAll(futures, eventLoop: conn.eventLoop) 100 | } 101 | 102 | } 103 | -------------------------------------------------------------------------------- /Sources/App/Models/Entities/BookClassify.swift: -------------------------------------------------------------------------------- 1 | // 2 | // BookClassify.swift 3 | // App 4 | // 5 | // Created by laijihua on 2018/6/23. 6 | // 7 | 8 | import Vapor 9 | import FluentPostgreSQL 10 | 11 | final class BookClassify: Content { 12 | var id: Int? 13 | var name: String 14 | var parentId: BookClassify.ID 15 | var path: String 16 | 17 | var createdAt: Date? 18 | var updatedAt: Date? 19 | var deletedAt: Date? 20 | 21 | static var createdAtKey: TimestampKey? { return \.createdAt } 22 | static var updatedAtKey: TimestampKey? { return \.updatedAt } 23 | static var deletedAtKey: TimestampKey? { return \.deletedAt } 24 | 25 | init(name: String, parentId: BookClassify.ID, path: String) { 26 | self.name = name 27 | self.parentId = parentId 28 | self.path = path 29 | } 30 | } 31 | 32 | extension BookClassify: Migration { 33 | static func prepare(on connection: PostgreSQLConnection) -> Future { 34 | return Database.create(self, on: connection) { builder in 35 | try addProperties(to: builder) 36 | // builder.reference(from: \.parentId, to: \BookClassify.id) 37 | } 38 | } 39 | } 40 | extension BookClassify: PostgreSQLModel {} 41 | 42 | extension BookClassify { 43 | var books: Children { 44 | return children(\Book.classifyId) 45 | } 46 | } 47 | -------------------------------------------------------------------------------- /Sources/App/Models/Entities/Collect.swift: -------------------------------------------------------------------------------- 1 | // 2 | // Collect.swift 3 | // App 4 | // 5 | // Created by laijihua on 2018/6/23. 6 | // 7 | 8 | import Vapor 9 | import FluentPostgreSQL 10 | 11 | /// 收藏表 12 | final class Collect: PostgreSQLPivot { 13 | var id: Int? 14 | var userId: User.ID 15 | var bookId: Book.ID 16 | 17 | typealias Left = User 18 | typealias Right = Book 19 | 20 | static let leftIDKey: LeftIDKey = \.userId 21 | static let rightIDKey: RightIDKey = \.bookId 22 | 23 | var createdAt: Date? 24 | var updatedAt: Date? 25 | var deletedAt: Date? 26 | 27 | static var createdAtKey: TimestampKey? { return \.createdAt } 28 | static var updatedAtKey: TimestampKey? { return \.updatedAt } 29 | static var deletedAtKey: TimestampKey? { return \.deletedAt } 30 | 31 | init(userId: User.ID, bookId: Book.ID) { 32 | self.userId = userId 33 | self.bookId = bookId 34 | } 35 | 36 | } 37 | 38 | extension Collect: Migration { 39 | static func prepare(on connection: PostgreSQLConnection) -> Future { 40 | return Database.create(self, on: connection) { builder in 41 | try addProperties(to: builder) 42 | builder.reference(from: \.userId, to: \User.id) 43 | builder.reference(from: \.bookId, to: \Book.id) 44 | } 45 | } 46 | } 47 | 48 | -------------------------------------------------------------------------------- /Sources/App/Models/Entities/Comment.swift: -------------------------------------------------------------------------------- 1 | // 2 | // Comment.swift 3 | // App 4 | // 5 | // Created by laijihua on 2018/6/23. 6 | // 7 | 8 | import Vapor 9 | import FluentPostgreSQL 10 | import Pagination 11 | 12 | /// 评论表 13 | final class Comment: Content { 14 | 15 | var id: Int? 16 | var bookId: Book.ID // 评论书籍 ID 17 | var userId: User.ID // 评论者 18 | var content: String 19 | var reportCount: Int? 20 | 21 | var createdAt: Date? 22 | var updatedAt: Date? 23 | var deletedAt: Date? 24 | 25 | static var createdAtKey: TimestampKey? { return \.createdAt } 26 | static var updatedAtKey: TimestampKey? { return \.updatedAt } 27 | static var deletedAtKey: TimestampKey? { return \.deletedAt } 28 | 29 | init(bookId: Book.ID, userId: User.ID, content: String, reportCount: Int = 0) { 30 | self.bookId = bookId 31 | self.userId = userId 32 | self.content = content 33 | self.reportCount = reportCount 34 | } 35 | } 36 | 37 | extension Comment: Paginatable {} 38 | 39 | extension Comment: Migration { 40 | static func prepare(on connection: PostgreSQLConnection) -> Future { 41 | return Database.create(self, on: connection) { builder in 42 | try addProperties(to: builder) 43 | builder.reference(from: \.userId, to: \User.id) 44 | builder.reference(from: \.bookId, to: \Book.id) 45 | } 46 | } 47 | } 48 | extension Comment: PostgreSQLModel {} 49 | 50 | extension Comment { 51 | var book: Parent { 52 | return parent(\.bookId) 53 | } 54 | var creater: Parent { 55 | return parent(\.userId) 56 | } 57 | } 58 | -------------------------------------------------------------------------------- /Sources/App/Models/Entities/Feedback.swift: -------------------------------------------------------------------------------- 1 | // 2 | // Feedback.swift 3 | // App 4 | // 5 | // Created by laijihua on 2018/6/23. 6 | // 7 | 8 | import Vapor 9 | import FluentPostgreSQL 10 | 11 | /// 意见反馈 12 | 13 | final class Feedback: Content { 14 | var id: Int? 15 | var content: String 16 | var userId: User.ID 17 | 18 | var createdAt: Date? 19 | var updatedAt: Date? 20 | var deletedAt: Date? 21 | 22 | static var createdAtKey: TimestampKey? { return \.createdAt } 23 | static var updatedAtKey: TimestampKey? { return \.updatedAt } 24 | static var deletedAtKey: TimestampKey? { return \.deletedAt } 25 | 26 | init(userId: User.ID, content: String) { 27 | self.userId = userId 28 | self.content = content 29 | } 30 | } 31 | 32 | extension Feedback: Migration { 33 | static func prepare(on connection: PostgreSQLConnection) -> Future { 34 | return Database.create(self, on: connection) { builder in 35 | try addProperties(to: builder) 36 | builder.reference(from: \.userId, to: \User.id) 37 | } 38 | } 39 | } 40 | extension Feedback: PostgreSQLModel {} 41 | 42 | extension Feedback { 43 | var user: Parent { 44 | return parent(\.userId) 45 | } 46 | } 47 | -------------------------------------------------------------------------------- /Sources/App/Models/Entities/Friend.swift: -------------------------------------------------------------------------------- 1 | // 2 | // Friend.swift 3 | // App 4 | // 5 | // Created by laijihua on 2018/5/31. 6 | // 7 | 8 | import Vapor 9 | import FluentPostgreSQL 10 | 11 | final class Friend: Content { 12 | var id: Int? 13 | var userId: User.ID 14 | var friendId: User.ID 15 | 16 | var createdAt: Date? 17 | var updatedAt: Date? 18 | var deletedAt: Date? 19 | 20 | static var createdAtKey: TimestampKey? { return \.createdAt } 21 | static var updatedAtKey: TimestampKey? { return \.updatedAt } 22 | static var deletedAtKey: TimestampKey? { return \.deletedAt } 23 | 24 | init(userId: User.ID, friendId: User.ID) { 25 | self.userId = userId 26 | self.friendId = friendId 27 | } 28 | } 29 | 30 | extension Friend: PostgreSQLModel { 31 | static func prepare(on connection: PostgreSQLConnection) -> Future { 32 | return Database.create(self, on: connection) { builder in 33 | try addProperties(to: builder) 34 | builder.reference(from: \.userId, to: \User.id) 35 | builder.reference(from: \.friendId, to: \User.id) 36 | } 37 | } 38 | } 39 | extension Friend: Migration {} 40 | 41 | 42 | -------------------------------------------------------------------------------- /Sources/App/Models/Entities/MessageBoard.swift: -------------------------------------------------------------------------------- 1 | // 2 | // MessageBoard.swift 3 | // App 4 | // 5 | // Created by laijihua on 2018/6/23. 6 | // 7 | 8 | import Vapor 9 | import FluentPostgreSQL 10 | 11 | // 用户留言板块 12 | final class MessageBoard: Content { 13 | var id: Int? 14 | var content: String 15 | var userId: User.ID 16 | var senderId: User.ID 17 | 18 | var createdAt: Date? 19 | var updatedAt: Date? 20 | var deletedAt: Date? 21 | 22 | static var createdAtKey: TimestampKey? { return \.createdAt } 23 | static var updatedAtKey: TimestampKey? { return \.updatedAt } 24 | static var deletedAtKey: TimestampKey? { return \.deletedAt } 25 | 26 | init(userId: User.ID, senderId: User.ID, content: String) { 27 | self.userId = userId 28 | self.senderId = senderId 29 | self.content = content 30 | } 31 | } 32 | 33 | extension MessageBoard: Migration { 34 | static func prepare(on connection: PostgreSQLConnection) -> Future { 35 | return Database.create(self, on: connection) { builder in 36 | try addProperties(to: builder) 37 | builder.reference(from: \.userId, to: \User.id) 38 | builder.reference(from: \.senderId, to: \User.id) 39 | } 40 | } 41 | } 42 | extension MessageBoard: PostgreSQLModel {} 43 | -------------------------------------------------------------------------------- /Sources/App/Models/Entities/News/Notify.swift: -------------------------------------------------------------------------------- 1 | // 2 | // Notify.swift 3 | // App 4 | // 5 | // Created by laijihua on 2018/8/27. 6 | // 7 | 8 | import Vapor 9 | import FluentPostgreSQL 10 | import Pagination 11 | 12 | final class Notify: Content { 13 | var id: Int? 14 | var content: String? 15 | var type: Int // 消息的类型,1: 公告 Announce,2: 提醒 Remind,3:信息 Message 16 | var target: Int? 17 | var targetType: String? 18 | var action: String? 19 | var senderId: User.ID? 20 | 21 | var createdAt: Date? 22 | var updatedAt: Date? 23 | var deletedAt: Date? 24 | static var createdAtKey: TimestampKey? { return \.createdAt } 25 | static var updatedAtKey: TimestampKey? { return \.updatedAt } 26 | static var deletedAtKey: TimestampKey? { return \.deletedAt } 27 | 28 | init(type: Int, 29 | target:Int? = nil, 30 | targetType: String? = nil, 31 | action: String? = nil, 32 | sender: User.ID? = nil, 33 | content: String? = nil){ 34 | self.type = type 35 | self.target = target 36 | self.targetType = targetType 37 | self.action = action 38 | self.senderId = sender 39 | self.content = content 40 | } 41 | } 42 | 43 | extension Notify { 44 | static var announce: Int {return 1} 45 | static var remind: Int {return 2} 46 | static var message: Int {return 3} 47 | 48 | static var targetTypes: [String] { 49 | return ["topic", "reply", "comment"] 50 | } 51 | 52 | static var actionTypes: [String] { 53 | return ["like", "collect", "comment"] 54 | } 55 | } 56 | 57 | extension Notify { 58 | var userNotifis: Children { 59 | return children(\UserNotify.notifyId) 60 | } 61 | } 62 | 63 | extension Notify: Paginatable {} 64 | extension Notify: Migration {} 65 | extension Notify: PostgreSQLModel {} 66 | 67 | 68 | 69 | -------------------------------------------------------------------------------- /Sources/App/Models/Entities/News/Subscription.swift: -------------------------------------------------------------------------------- 1 | // 2 | // Subscription.swift 3 | // App 4 | // 5 | // Created by laijihua on 2018/8/27. 6 | // 7 | import Vapor 8 | import FluentPostgreSQL 9 | 10 | final class Subscription: Content { 11 | var id: Int? 12 | var target: Int 13 | var targetType: String 14 | var userId: User.ID 15 | var action: String 16 | 17 | var createdAt: Date? 18 | var updatedAt: Date? 19 | var deletedAt: Date? 20 | static var createdAtKey: TimestampKey? { return \.createdAt } 21 | static var updatedAtKey: TimestampKey? { return \.updatedAt } 22 | static var deletedAtKey: TimestampKey? { return \.deletedAt } 23 | 24 | init(target: Int, targetType: String, userId: User.ID, action: String) { 25 | self.target = target 26 | self.targetType = targetType 27 | self.userId = userId 28 | self.action = action 29 | } 30 | } 31 | 32 | extension Subscription: Migration {} 33 | extension Subscription: PostgreSQLModel {} 34 | -------------------------------------------------------------------------------- /Sources/App/Models/Entities/News/UserNotify.swift: -------------------------------------------------------------------------------- 1 | // 2 | // UserNotify.swift 3 | // App 4 | // 5 | // Created by laijihua on 2018/8/27. 6 | // 7 | import Vapor 8 | import FluentPostgreSQL 9 | import Pagination 10 | 11 | /// 遍历订阅(Subscription)表拉取公告(Announce)和提醒(Remind)的时候创建 12 | /// 新建信息(Message)之后,立刻创建。 13 | final class UserNotify: Content { 14 | var id: Int? 15 | var userId: User.ID 16 | var notifyId: Notify.ID 17 | var notifyType: Int 18 | var isRead: Bool 19 | 20 | var createdAt: Date? 21 | var updatedAt: Date? 22 | var deletedAt: Date? 23 | static var createdAtKey: TimestampKey? { return \.createdAt } 24 | static var updatedAtKey: TimestampKey? { return \.updatedAt } 25 | static var deletedAtKey: TimestampKey? { return \.deletedAt } 26 | 27 | init(userId: User.ID, notifyId: Notify.ID, notifyType: Int, isRead: Bool = false) { 28 | self.userId = userId 29 | self.notifyId = notifyId 30 | self.isRead = isRead 31 | self.notifyType = notifyType 32 | } 33 | } 34 | 35 | extension UserNotify { 36 | var notify: Parent { 37 | return parent(\.notifyId) 38 | } 39 | } 40 | 41 | extension UserNotify: Paginatable {} 42 | 43 | extension UserNotify: Migration {} 44 | extension UserNotify: PostgreSQLModel {} 45 | -------------------------------------------------------------------------------- /Sources/App/Models/Entities/PriceUnit+Populate.swift: -------------------------------------------------------------------------------- 1 | // 2 | // PriceUnit+Populate.swift 3 | // App 4 | // 5 | // Created by laijihua on 2018/9/13. 6 | // 7 | 8 | import Foundation 9 | 10 | import Vapor 11 | import FluentPostgreSQL 12 | 13 | /// 数据填充 14 | final class PopulatePriceUnitForms: Migration { 15 | typealias Database = PostgreSQLDatabase 16 | 17 | static let units = [ 18 | "元", 19 | "美元" 20 | ] 21 | 22 | static func prepare(on conn: PostgreSQLConnection) -> EventLoopFuture { 23 | let futures = units.map { name in 24 | return PriceUnit(unit: name).create(on: conn).map(to: Void.self, { _ in 25 | return 26 | }) 27 | } 28 | return Future.andAll(futures, eventLoop: conn.eventLoop) 29 | } 30 | 31 | static func revert(on conn: PostgreSQLConnection) -> EventLoopFuture { 32 | let futures = units.map { name in 33 | return PriceUnit.query(on: conn).filter(\PriceUnit.unit == name).delete() 34 | } 35 | return Future.andAll(futures, eventLoop: conn.eventLoop) 36 | } 37 | 38 | } 39 | -------------------------------------------------------------------------------- /Sources/App/Models/Entities/PriceUnit.swift: -------------------------------------------------------------------------------- 1 | // 2 | // PriceUnit.swift 3 | // App 4 | // 5 | // Created by laijihua on 2018/6/23. 6 | // 7 | 8 | import Vapor 9 | import FluentPostgreSQL 10 | 11 | final class PriceUnit: Content { 12 | var id: Int? 13 | var unit: String // 单位名字 14 | 15 | var createdAt: Date? 16 | var updatedAt: Date? 17 | var deletedAt: Date? 18 | 19 | static var createdAtKey: TimestampKey? { return \.createdAt } 20 | static var updatedAtKey: TimestampKey? { return \.updatedAt } 21 | static var deletedAtKey: TimestampKey? { return \.deletedAt } 22 | 23 | init(unit: String) { 24 | self.unit = unit 25 | } 26 | } 27 | 28 | extension PriceUnit: Migration {} 29 | extension PriceUnit: PostgreSQLModel {} 30 | 31 | extension PriceUnit { 32 | var books: Children { 33 | return children(\Book.priceUintId) 34 | } 35 | } 36 | 37 | 38 | -------------------------------------------------------------------------------- /Sources/App/Models/Entities/Recommend.swift: -------------------------------------------------------------------------------- 1 | // 2 | // Recommend.swift 3 | // App 4 | // 5 | // Created by laijihua on 2018/6/23. 6 | // 7 | 8 | import Foundation 9 | 10 | // 推荐 11 | -------------------------------------------------------------------------------- /Sources/App/Models/Entities/Sys/Group.swift: -------------------------------------------------------------------------------- 1 | // 2 | // Group.swift 3 | // APIErrorMiddleware 4 | // 5 | // Created by laijihua on 2018/7/12. 6 | // 7 | 8 | import Vapor 9 | import FluentPostgreSQL 10 | 11 | /// 组表 12 | final class Group: Content { 13 | var id: Int? 14 | var parentId: Group.ID 15 | var name: String 16 | var remarks: String? 17 | 18 | var createdAt: Date? 19 | var updatedAt: Date? 20 | var deletedAt: Date? 21 | static var createdAtKey: TimestampKey? { return \.createdAt } 22 | static var updatedAtKey: TimestampKey? { return \.updatedAt } 23 | static var deletedAtKey: TimestampKey? { return \.deletedAt } 24 | 25 | init(parentId: Group.ID, name: String, remarks: String? = nil) { 26 | self.parentId = parentId 27 | self.name = name 28 | self.remarks = remarks 29 | } 30 | } 31 | 32 | extension Group: Migration { 33 | static func prepare(on connection: PostgreSQLConnection) -> Future { 34 | return Database.create(self, on: connection) { builder in 35 | try addProperties(to: builder) 36 | builder.reference(from: \.parentId, to: \Group.id) 37 | } 38 | } 39 | } 40 | extension Group: PostgreSQLModel {} 41 | 42 | -------------------------------------------------------------------------------- /Sources/App/Models/Entities/Sys/GroupRoleRelation.swift: -------------------------------------------------------------------------------- 1 | // 2 | // GroupRoleRelation.swift 3 | // APIErrorMiddleware 4 | // 5 | // Created by laijihua on 2018/7/12. 6 | // 7 | 8 | import Vapor 9 | import FluentPostgreSQL 10 | 11 | // 组角色表 12 | final class GroupRole: PostgreSQLPivot { 13 | var id: Int? 14 | var groupId: Group.ID 15 | var roleId: Role.ID 16 | 17 | var createdAt: Date? 18 | var updatedAt: Date? 19 | var deletedAt: Date? 20 | 21 | typealias Left = Group 22 | typealias Right = Role 23 | 24 | static var leftIDKey: LeftIDKey = \.groupId 25 | static var rightIDKey: RightIDKey = \.roleId 26 | 27 | static var createdAtKey: TimestampKey? { return \.createdAt } 28 | static var updatedAtKey: TimestampKey? { return \.updatedAt } 29 | static var deletedAtKey: TimestampKey? { return \.deletedAt } 30 | 31 | init(groupId: Group.ID, roleId: Role.ID) { 32 | self.groupId = groupId 33 | self.roleId = roleId 34 | } 35 | } 36 | 37 | extension GroupRole: Migration { 38 | static func prepare(on connection: PostgreSQLConnection) -> Future { 39 | return Database.create(self, on: connection) { builder in 40 | try addProperties(to: builder) 41 | builder.reference(from: \.roleId, to: \Role.id) 42 | builder.reference(from: \.groupId, to: \Group.id) 43 | } 44 | } 45 | } 46 | 47 | 48 | -------------------------------------------------------------------------------- /Sources/App/Models/Entities/Sys/Menu+Populate.swift: -------------------------------------------------------------------------------- 1 | // 2 | // Menu+Populate.swift 3 | // App 4 | // 5 | // Created by laijihua on 2018/9/4. 6 | // 7 | 8 | import Vapor 9 | import FluentPostgreSQL 10 | 11 | /// 数据填充 12 | final class PopulateMenuForms: Migration { 13 | typealias Database = PostgreSQLDatabase 14 | 15 | static var menus = [ 16 | (name: "菜单管理", href: "/sys/menuList"), 17 | (name: "权限管理", href: "/sys/roleList"), 18 | (name: "用户管理", href: "/sys/userList"), 19 | (name: "资源管理", href: "/sys/resource") 20 | ] 21 | 22 | static func getHeadId(on connection: PostgreSQLConnection) -> Future { 23 | let sysMenu = Menu(sort: 0, name: "系统管理", href: "/sys/menuList", icon: "", isShow: true) 24 | return sysMenu.create(on: connection).map {return $0.id! } 25 | } 26 | 27 | static func prepare(on conn: PostgreSQLConnection) -> EventLoopFuture { 28 | 29 | return getHeadId(on: conn) 30 | .flatMap(to: Void.self) { headId in 31 | let fetures = menus.map { touple -> EventLoopFuture in 32 | let name = touple.0 33 | let path = touple.href 34 | return Menu(sort: 0, name: name, href: path, icon: "", isShow: true, parentId: headId) 35 | .create(on: conn).map(to: Void.self, {_ in return}) 36 | } 37 | return Future.andAll(fetures, eventLoop: conn.eventLoop) 38 | } 39 | } 40 | 41 | static func revert(on conn: PostgreSQLConnection) -> EventLoopFuture { 42 | let futures = menus.map { menu in 43 | return Menu.query(on: conn).filter(\Menu.name == menu.name).delete() 44 | } 45 | return Future.andAll(futures, eventLoop: conn.eventLoop) 46 | } 47 | 48 | } 49 | -------------------------------------------------------------------------------- /Sources/App/Models/Entities/Sys/Menu.swift: -------------------------------------------------------------------------------- 1 | // 2 | // Menu.swift 3 | // App 4 | // 5 | // Created by laijihua on 2018/7/6. 6 | // 7 | 8 | import Vapor 9 | import FluentPostgreSQL 10 | 11 | final class Menu: Content { 12 | var id: Int? 13 | var parentId: Menu.ID 14 | var sort: Int // 第几个位置 15 | var name: String 16 | var href: String 17 | var icon: String 18 | var isShow: Bool 19 | 20 | var createdAt: Date? 21 | var updatedAt: Date? 22 | var deletedAt: Date? 23 | 24 | static var createdAtKey: TimestampKey? { return \.createdAt } 25 | static var updatedAtKey: TimestampKey? { return \.updatedAt } 26 | static var deletedAtKey: TimestampKey? { return \.deletedAt } 27 | 28 | init(sort: Int, 29 | name: String, 30 | href: String, 31 | icon: String, 32 | isShow: Bool, 33 | parentId: Int = 0) { 34 | self.sort = sort 35 | self.name = name 36 | self.href = href 37 | self.icon = icon 38 | self.isShow = isShow 39 | self.parentId = parentId 40 | } 41 | } 42 | 43 | extension Menu: ModelResusivable { 44 | 45 | struct Public: ModelPublicable { 46 | var id: Int? 47 | var parentId: Menu.ID 48 | var sort: Int // 第几个位置 49 | var name: String 50 | var href: String 51 | var icon: String 52 | var isShow: Bool 53 | var children: [Public] 54 | } 55 | 56 | func convertToPublic(childrens: [Public]) -> Public { 57 | return Public(id: self.id, 58 | parentId: self.parentId, 59 | sort: self.sort, 60 | name: self.name, 61 | href: self.href, 62 | icon: self.icon, 63 | isShow: self.isShow, 64 | children: childrens) 65 | } 66 | } 67 | 68 | extension Menu { 69 | var children: Children{ 70 | return children(\Menu.parentId) 71 | } 72 | } 73 | 74 | extension Menu: PostgreSQLModel {} 75 | extension Menu: Migration { 76 | static func prepare(on connection: PostgreSQLConnection) -> Future { 77 | return Database.create(self, on: connection) { builder in 78 | try addProperties(to: builder) 79 | // builder.reference(from: \.parentId, to: \Menu.id) 80 | } 81 | } 82 | } 83 | -------------------------------------------------------------------------------- /Sources/App/Models/Entities/Sys/MenuRoleRelation.swift: -------------------------------------------------------------------------------- 1 | // 2 | // MenuRoleRelation.swift 3 | // App 4 | // 5 | // Created by laijihua on 2018/7/16. 6 | // 7 | 8 | import Vapor 9 | import FluentPostgreSQL 10 | 11 | // 菜单权限表 12 | final class MenuRole: PostgreSQLPivot { 13 | var id: Int? 14 | var menuId: Menu.ID 15 | var roleId: Role.ID 16 | 17 | var createdAt: Date? 18 | var updatedAt: Date? 19 | var deletedAt: Date? 20 | 21 | typealias Left = Menu 22 | typealias Right = Role 23 | 24 | static var leftIDKey: LeftIDKey = \.menuId 25 | static var rightIDKey: RightIDKey = \.roleId 26 | 27 | static var createdAtKey: TimestampKey? { return \.createdAt } 28 | static var updatedAtKey: TimestampKey? { return \.updatedAt } 29 | static var deletedAtKey: TimestampKey? { return \.deletedAt } 30 | 31 | init(menuId: Menu.ID, roleId: Role.ID) { 32 | self.menuId = menuId 33 | self.roleId = roleId 34 | } 35 | } 36 | 37 | extension MenuRole: Migration { 38 | 39 | static func prepare(on connection: PostgreSQLConnection) -> Future { 40 | return Database.create(self, on: connection) { builder in 41 | try addProperties(to: builder) 42 | builder.reference(from: \.menuId, to: \Menu.id) 43 | builder.reference(from: \.roleId, to: \Role.id) 44 | } 45 | } 46 | } 47 | -------------------------------------------------------------------------------- /Sources/App/Models/Entities/Sys/Move/GroupRightRelation.swift: -------------------------------------------------------------------------------- 1 | // 2 | // GroupRightRelation.swift 3 | // APIErrorMiddleware 4 | // 5 | // Created by laijihua on 2018/7/12. 6 | // 7 | 8 | import Vapor 9 | import FluentPostgreSQL 10 | 11 | /// 组权限表,为了方便多个操作, 该表可能呢不需要 12 | final class GroupRight: PostgreSQLPivot { 13 | var id: Int? 14 | var groupId: Group.ID 15 | var rightId: Right.ID 16 | 17 | typealias Left = Group 18 | typealias Right = App.Right 19 | 20 | static var leftIDKey: LeftIDKey = \.groupId 21 | static var rightIDKey: RightIDKey = \.rightId 22 | 23 | var createdAt: Date? 24 | var updatedAt: Date? 25 | var deletedAt: Date? 26 | 27 | static var createdAtKey: TimestampKey? { return \.createdAt } 28 | static var updatedAtKey: TimestampKey? { return \.updatedAt } 29 | static var deletedAtKey: TimestampKey? { return \.deletedAt } 30 | 31 | init(groupId: Group.ID, rightId: Right.ID) { 32 | self.groupId = groupId 33 | self.rightId = rightId 34 | } 35 | } 36 | 37 | extension GroupRight: Migration { 38 | static func prepare(on connection: PostgreSQLConnection) -> Future { 39 | return Database.create(self, on: connection) { builder in 40 | try addProperties(to: builder) 41 | builder.reference(from: \.groupId, to: \Group.id) 42 | builder.reference(from: \.rightId, to: \Right.id) 43 | } 44 | } 45 | } 46 | 47 | -------------------------------------------------------------------------------- /Sources/App/Models/Entities/Sys/Move/UserRightRelation.swift: -------------------------------------------------------------------------------- 1 | // 2 | // UserRightRelation.swift 3 | // APIErrorMiddleware 4 | // 5 | // Created by laijihua on 2018/7/12. 6 | // 7 | 8 | import Vapor 9 | import FluentPostgreSQL 10 | 11 | // 用户权限表, 该表可能不需要 12 | final class UserRight: PostgreSQLPivot { 13 | var id: Int? 14 | var userId: User.ID 15 | var rightId: Right.ID 16 | 17 | typealias Left = User 18 | typealias Right = App.Right 19 | 20 | static var leftIDKey: LeftIDKey = \.userId 21 | static var rightIDKey: RightIDKey = \.rightId 22 | 23 | var createdAt: Date? 24 | var updatedAt: Date? 25 | var deletedAt: Date? 26 | 27 | static var createdAtKey: TimestampKey? { return \.createdAt } 28 | static var updatedAtKey: TimestampKey? { return \.updatedAt } 29 | static var deletedAtKey: TimestampKey? { return \.deletedAt } 30 | 31 | init(userId: User.ID, rightId: Right.ID) { 32 | self.userId = userId 33 | self.rightId = rightId 34 | } 35 | } 36 | 37 | extension UserRight: Migration { 38 | static func prepare(on connection: PostgreSQLConnection) -> Future { 39 | return Database.create(self, on: connection) { builder in 40 | try addProperties(to: builder) 41 | builder.reference(from: \.userId, to: \User.id) 42 | builder.reference(from: \.rightId, to: \Right.id) 43 | } 44 | } 45 | } 46 | 47 | -------------------------------------------------------------------------------- /Sources/App/Models/Entities/Sys/OpLog.swift: -------------------------------------------------------------------------------- 1 | // 2 | // Log.swift 3 | // App 4 | // 5 | // Created by laijihua on 2018/7/12. 6 | // 7 | 8 | import Vapor 9 | import FluentPostgreSQL 10 | 11 | // 操作日志表 12 | final class OpLog: Content { 13 | var id: Int? 14 | var type: Int 15 | var content: String 16 | var userId: User.ID 17 | 18 | var createdAt: Date? 19 | static var createdAtKey: TimestampKey? { return \.createdAt } 20 | 21 | init(type:Int, content: String, userId: User.ID) { 22 | self.type = type 23 | self.content = content 24 | self.userId = userId 25 | } 26 | } 27 | 28 | extension OpLog { 29 | var user: Parent { 30 | return parent(\.userId) 31 | } 32 | } 33 | 34 | extension OpLog: Migration { 35 | 36 | static func prepare(on connection: PostgreSQLConnection) -> Future { 37 | return Database.create(self, on: connection) { builder in 38 | try addProperties(to: builder) 39 | builder.reference(from: \.userId, to: \User.id) 40 | } 41 | } 42 | } 43 | extension OpLog: PostgreSQLModel {} 44 | -------------------------------------------------------------------------------- /Sources/App/Models/Entities/Sys/Organization+Populate.swift: -------------------------------------------------------------------------------- 1 | // 2 | // Organization+Populate.swift 3 | // App 4 | // 5 | // Created by laijihua on 2018/8/30. 6 | // 7 | 8 | import Vapor 9 | import FluentPostgreSQL 10 | 11 | /// 数据填充 12 | final class PopulateOrganizationForms: Migration { 13 | typealias Database = PostgreSQLDatabase 14 | 15 | static let organizations = [ 16 | "再书" 17 | ] 18 | 19 | static func prepare(on conn: PostgreSQLConnection) -> EventLoopFuture { 20 | let futures = organizations.map { name in 21 | return Organization(parentId: 0, name: name, remarks: name).create(on: conn).map(to: Void.self, { _ in 22 | return 23 | }) 24 | } 25 | return Future.andAll(futures, eventLoop: conn.eventLoop) 26 | } 27 | 28 | static func revert(on conn: PostgreSQLConnection) -> EventLoopFuture { 29 | let futures = organizations.map { name in 30 | return Organization.query(on: conn).filter(\.name == name).delete() 31 | } 32 | return Future.andAll(futures, eventLoop: conn.eventLoop) 33 | } 34 | 35 | } 36 | -------------------------------------------------------------------------------- /Sources/App/Models/Entities/Sys/Organization.swift: -------------------------------------------------------------------------------- 1 | // 2 | // Organization.swift 3 | // App 4 | // 5 | // Created by laijihua on 2018/7/12. 6 | // 7 | 8 | import Vapor 9 | import FluentPostgreSQL 10 | // 组织表 11 | 12 | final class Organization: Content { 13 | var id: Int? 14 | var parentId: Organization.ID 15 | var name: String 16 | var remarks: String? 17 | 18 | var createdAt: Date? 19 | var updatedAt: Date? 20 | var deletedAt: Date? 21 | 22 | static var createdAtKey: TimestampKey? { return \.createdAt } 23 | static var updatedAtKey: TimestampKey? { return \.updatedAt } 24 | static var deletedAtKey: TimestampKey? { return \.deletedAt } 25 | 26 | init(parentId: Organization.ID, name: String, remarks: String?) { 27 | self.parentId = parentId 28 | self.name = name 29 | self.remarks = remarks 30 | } 31 | } 32 | 33 | extension Organization { 34 | var users: Children { 35 | return children(\User.organizId) 36 | } 37 | } 38 | 39 | extension Organization: Migration {} 40 | extension Organization: PostgreSQLModel {} 41 | -------------------------------------------------------------------------------- /Sources/App/Models/Entities/Sys/Resusivable.swift: -------------------------------------------------------------------------------- 1 | // 2 | // Resusivable.swift 3 | // App 4 | // 5 | // Created by laijihua on 2018/7/15. 6 | // 7 | 8 | import Vapor 9 | import FluentPostgreSQL 10 | 11 | protocol ModelPublicable: Content { 12 | var children: [Self] {get set} 13 | } 14 | 15 | protocol ModelResusivable: PostgreSQLModel { 16 | associatedtype Public: ModelPublicable 17 | var parentId:Self.ID {get} 18 | func convertToPublic(childrens: [Public]) -> Public 19 | } 20 | 21 | extension RouteCollection { 22 | func generateModelTree(parentId: T.ID, originArray:[T]) -> [T.Public] where T: ModelResusivable { 23 | let firstParents = originArray.filter({ $0.parentId == parentId && $0.parentId != $0.id }) 24 | let originArr = Array(originArray.drop(while: {$0.parentId == parentId})) 25 | if (firstParents.count > 0) { 26 | return firstParents.map { (menu) -> T.Public in 27 | return menu.convertToPublic(childrens: generateModelTree(parentId: menu.id!, originArray: originArr)) 28 | } 29 | } else { 30 | return [] 31 | } 32 | } 33 | } 34 | -------------------------------------------------------------------------------- /Sources/App/Models/Entities/Sys/Right.swift: -------------------------------------------------------------------------------- 1 | // 2 | // Right.swift 3 | // APIErrorMiddleware 4 | // 5 | // Created by laijihua on 2018/7/12. 6 | // 7 | 8 | import Vapor 9 | import FluentPostgreSQL 10 | import Pagination 11 | 12 | // 权限表 13 | final class Right: Content { 14 | var id: Int? 15 | var parentId: Right.ID 16 | var remarks: String? // 备注 17 | var name: String 18 | var code: String // 代码 19 | var type: String // 类型 菜单 按钮 工能 20 | 21 | var createdAt: Date? 22 | var updatedAt: Date? 23 | var deletedAt: Date? 24 | 25 | static var createdAtKey: TimestampKey? { return \.createdAt } 26 | static var updatedAtKey: TimestampKey? { return \.updatedAt } 27 | static var deletedAtKey: TimestampKey? { return \.deletedAt } 28 | 29 | init(parentId: Right.ID, 30 | remarks:String? = nil, 31 | name: String, 32 | code: String, 33 | type: String) { 34 | self.parentId = parentId 35 | self.remarks = remarks 36 | self.name = name 37 | self.code = code 38 | self.type = type 39 | } 40 | } 41 | 42 | extension Right: PostgreSQLModel {} 43 | extension Right: Migration { 44 | static func prepare(on connection: PostgreSQLConnection) -> Future { 45 | return Database.create(self, on: connection) { builder in 46 | try addProperties(to: builder) 47 | builder.reference(from: \.parentId, to: \Right.id) 48 | } 49 | } 50 | } 51 | 52 | extension Right: Paginatable {} 53 | 54 | -------------------------------------------------------------------------------- /Sources/App/Models/Entities/Sys/Role.swift: -------------------------------------------------------------------------------- 1 | // 2 | // Role.swift 3 | // App 4 | // 5 | // Created by laijihua on 2018/7/10. 6 | // 7 | 8 | import Vapor 9 | import FluentPostgreSQL 10 | 11 | /// 角色表 12 | final class Role: Content { 13 | var id: Int? 14 | var delFlag: Bool 15 | var parentId: Role.ID 16 | var sort: Int 17 | var name: String 18 | var remaks: String? 19 | var usable: Bool 20 | 21 | var createdAt: Date? 22 | var updatedAt: Date? 23 | var deletedAt: Date? 24 | 25 | static var createdAtKey: TimestampKey? { return \.createdAt } 26 | static var updatedAtKey: TimestampKey? { return \.updatedAt } 27 | static var deletedAtKey: TimestampKey? { return \.deletedAt } 28 | 29 | init(parentId: Role.ID, 30 | sort: Int, 31 | name:String, 32 | remarks: String? = nil, 33 | usable: Bool = true, 34 | delFlag: Bool = false) { 35 | self.parentId = parentId 36 | self.sort = sort 37 | self.name = name 38 | self.remaks = remarks 39 | self.usable = usable 40 | self.delFlag = delFlag 41 | } 42 | } 43 | 44 | extension Role: ModelResusivable { 45 | struct Public: ModelPublicable { 46 | var id: Int? 47 | var delFlag: Bool 48 | var parentId: Role.ID 49 | var sort: Int 50 | var name: String 51 | var remaks: String? 52 | var usable: Bool 53 | var children: [Public] 54 | } 55 | 56 | func convertToPublic(childrens: [Public]) -> Public { 57 | return Public(id: self.id, 58 | delFlag: self.delFlag, 59 | parentId: self.parentId, 60 | sort: self.sort, 61 | name: self.name, 62 | remaks: self.remaks, 63 | usable: self.usable, 64 | children: childrens) 65 | } 66 | } 67 | 68 | extension Role: Migration {} 69 | extension Role: PostgreSQLModel { 70 | static func prepare(on connection: PostgreSQLConnection) -> Future { 71 | return Database.create(self, on: connection) { builder in 72 | try addProperties(to: builder) 73 | builder.reference(from: \.parentId, to: \Role.id) 74 | } 75 | } 76 | } 77 | -------------------------------------------------------------------------------- /Sources/App/Models/Entities/Sys/RoleRightRelation.swift: -------------------------------------------------------------------------------- 1 | // 2 | // RoleRightRelation.swift 3 | // APIErrorMiddleware 4 | // 5 | // Created by laijihua on 2018/7/12. 6 | // 7 | 8 | import Vapor 9 | import FluentPostgreSQL 10 | 11 | /// 角色权限表 12 | final class RoleRight: PostgreSQLPivot { 13 | 14 | var id: Int? 15 | var roleId: Role.ID 16 | var rightId: Right.ID 17 | 18 | typealias Left = Role 19 | typealias Right = App.Right 20 | 21 | static var leftIDKey: LeftIDKey = \.roleId 22 | static var rightIDKey: RightIDKey = \.rightId 23 | 24 | var createdAt: Date? 25 | var updatedAt: Date? 26 | var deletedAt: Date? 27 | 28 | static var createdAtKey: TimestampKey? { return \.createdAt } 29 | static var updatedAtKey: TimestampKey? { return \.updatedAt } 30 | static var deletedAtKey: TimestampKey? { return \.deletedAt } 31 | 32 | init(roleId: Role.ID, rightId: Right.ID) { 33 | self.roleId = roleId 34 | self.rightId = rightId 35 | } 36 | } 37 | 38 | extension RoleRight: Migration { 39 | static func prepare(on connection: PostgreSQLConnection) -> Future { 40 | return Database.create(self, on: connection) { builder in 41 | try addProperties(to: builder) 42 | builder.reference(from: \.roleId, to: \Role.id) 43 | builder.reference(from: \.rightId, to: \Right.id) 44 | } 45 | } 46 | } 47 | 48 | 49 | -------------------------------------------------------------------------------- /Sources/App/Models/Entities/Sys/User.swift: -------------------------------------------------------------------------------- 1 | // 2 | // User.swift 3 | // App 4 | // 5 | // Created by laijihua on 2018/5/31. 6 | // 7 | 8 | import Vapor 9 | import FluentPostgreSQL 10 | import Authentication 11 | import Pagination 12 | 13 | /// 用户表 14 | final class User: Content { 15 | var id: Int? 16 | var organizId: Organization.ID // 公司 17 | var name: String 18 | var email: String? 19 | var avator: String? 20 | var info: String? // 简介 21 | 22 | var phone: String? 23 | var wechat: String? // 微信账号 24 | var qq: String? // qq 账号 25 | 26 | var createdAt: Date? 27 | var updatedAt: Date? 28 | var deletedAt: Date? 29 | static var createdAtKey: TimestampKey? { return \.createdAt } 30 | static var updatedAtKey: TimestampKey? { return \.updatedAt } 31 | static var deletedAtKey: TimestampKey? { return \.deletedAt } 32 | 33 | init(name: String, 34 | phone: String? = nil, 35 | email: String? = nil, 36 | avator: String? = nil, 37 | organizId: Organization.ID? = nil, 38 | info: String? = nil) { 39 | self.name = name 40 | self.phone = phone 41 | self.email = email 42 | self.avator = avator 43 | self.organizId = organizId ?? 1 44 | self.info = info ?? "暂无简介" 45 | } 46 | } 47 | 48 | extension User: PostgreSQLModel {} 49 | extension User: Migration { 50 | static func prepare(on connection: PostgreSQLConnection) -> Future { 51 | return Database.create(self, on: connection) { builder in 52 | try addProperties(to: builder) 53 | builder.reference(from: \.organizId, to: \Organization.id) 54 | } 55 | } 56 | } 57 | 58 | extension User { 59 | var publishedBooks: Children { // 发布的书 60 | return children(\.createId) 61 | } 62 | 63 | var codes: Children { 64 | return children(\.userId) 65 | } 66 | 67 | var collectedBooks: Siblings { // 收藏的书 68 | return siblings() 69 | } 70 | 71 | var organization: Parent { // 组织 72 | return parent(\.organizId) 73 | } 74 | } 75 | 76 | extension User: Paginatable {} 77 | 78 | 79 | //MARK: TOkenAuthenticatable 80 | extension User: TokenAuthenticatable { 81 | typealias TokenType = AccessToken 82 | } 83 | 84 | -------------------------------------------------------------------------------- /Sources/App/Models/Entities/Sys/UserGroupRelation.swift: -------------------------------------------------------------------------------- 1 | // 2 | // UserGroupRelation.swift 3 | // APIErrorMiddleware 4 | // 5 | // Created by laijihua on 2018/7/12. 6 | // 7 | 8 | import Vapor 9 | import FluentPostgreSQL 10 | 11 | // 用户组表 12 | final class UserGroup: PostgreSQLPivot { 13 | var id: Int? 14 | var userId: User.ID 15 | var groupId: Group.ID 16 | 17 | typealias Left = User 18 | typealias Right = Group 19 | 20 | static var leftIDKey: LeftIDKey = \.userId 21 | static var rightIDKey: RightIDKey = \.groupId 22 | 23 | var createdAt: Date? 24 | var updatedAt: Date? 25 | var deletedAt: Date? 26 | 27 | static var createdAtKey: TimestampKey? { return \.createdAt } 28 | static var updatedAtKey: TimestampKey? { return \.updatedAt } 29 | static var deletedAtKey: TimestampKey? { return \.deletedAt } 30 | 31 | init(userId: User.ID, groupId: Group.ID) { 32 | self.userId = userId 33 | self.groupId = groupId 34 | } 35 | } 36 | 37 | extension UserGroup: Migration { 38 | static func prepare(on connection: PostgreSQLConnection) -> Future { 39 | return Database.create(self, on: connection) { builder in 40 | try addProperties(to: builder) 41 | builder.reference(from: \.userId, to: \User.id) 42 | builder.reference(from: \.groupId, to: \Group.id) 43 | } 44 | } 45 | } 46 | 47 | -------------------------------------------------------------------------------- /Sources/App/Models/Entities/Sys/UserRoleRelation.swift: -------------------------------------------------------------------------------- 1 | // 2 | // UserRoleRelation.swift 3 | // APIErrorMiddleware 4 | // 5 | // Created by laijihua on 2018/7/12. 6 | // 7 | 8 | import Vapor 9 | import FluentPostgreSQL 10 | 11 | // 用户角色表 12 | final class UserRole: PostgreSQLPivot { 13 | var id: Int? 14 | var userId: User.ID 15 | var roleId: Role.ID 16 | 17 | typealias Left = User 18 | typealias Right = Role 19 | 20 | static var leftIDKey: LeftIDKey = \.userId 21 | static var rightIDKey: RightIDKey = \.roleId 22 | 23 | var createdAt: Date? 24 | var updatedAt: Date? 25 | var deletedAt: Date? 26 | 27 | static var createdAtKey: TimestampKey? { return \.createdAt } 28 | static var updatedAtKey: TimestampKey? { return \.updatedAt } 29 | static var deletedAtKey: TimestampKey? { return \.deletedAt } 30 | 31 | init(userId: User.ID, roleId: Role.ID) { 32 | self.userId = userId 33 | self.roleId = roleId 34 | } 35 | } 36 | 37 | extension UserRole: Migration { 38 | static func prepare(on connection: PostgreSQLConnection) -> Future { 39 | return Database.create(self, on: connection) { builder in 40 | try addProperties(to: builder) 41 | builder.reference(from: \.userId, to: \User.id) 42 | builder.reference(from: \.roleId, to: \Role.id) 43 | } 44 | } 45 | } 46 | 47 | -------------------------------------------------------------------------------- /Sources/App/Models/Entities/WishBook.swift: -------------------------------------------------------------------------------- 1 | // 2 | // WishBook.swift 3 | // App 4 | // 5 | // Created by laijihua on 2018/6/23. 6 | // 7 | 8 | import Vapor 9 | import FluentPostgreSQL 10 | 11 | /// 心愿书单 12 | final class WishBook: Content { 13 | var id: Int? 14 | var title: String 15 | var content: String //> 评论内容 16 | var userId: User.ID 17 | var commentCount: Int //> 评论数 18 | var photos: [String] // 上传的图片数组 19 | 20 | var createdAt: Date? 21 | var updatedAt: Date? 22 | var deletedAt: Date? 23 | 24 | static var createdAtKey: TimestampKey? { return \.createdAt } 25 | static var updatedAtKey: TimestampKey? { return \.updatedAt } 26 | static var deletedAtKey: TimestampKey? { return \.deletedAt } 27 | 28 | init(title: String, content: String, userId: User.ID, commentCount: Int = 0, photos:[String] = []) { 29 | self.title = title 30 | self.content = content 31 | self.userId = userId 32 | self.commentCount = commentCount 33 | self.photos = photos 34 | } 35 | } 36 | 37 | extension WishBook: Migration { 38 | static func prepare(on connection: PostgreSQLConnection) -> Future { 39 | return Database.create(self, on: connection) { builder in 40 | try addProperties(to: builder) 41 | builder.reference(from: \.userId, to: \User.id) 42 | } 43 | } 44 | } 45 | extension WishBook: PostgreSQLModel {} 46 | 47 | 48 | 49 | 50 | -------------------------------------------------------------------------------- /Sources/App/Models/Entities/WishBookComment.swift: -------------------------------------------------------------------------------- 1 | // 2 | // WishBookComment.swift 3 | // App 4 | // 5 | // Created by laijihua on 2018/6/23. 6 | // 7 | 8 | import Vapor 9 | import FluentPostgreSQL 10 | 11 | /// 心愿书单评论 12 | final class WishBookComment: Content { 13 | var id: Int? 14 | var wishBookId: WishBook.ID 15 | var userId: User.ID 16 | var comment: String 17 | var reportCount: Int 18 | 19 | var createdAt: Date? 20 | var updatedAt: Date? 21 | var deletedAt: Date? 22 | static var createdAtKey: TimestampKey? { return \.createdAt } 23 | static var updatedAtKey: TimestampKey? { return \.updatedAt } 24 | static var deletedAtKey: TimestampKey? { return \.deletedAt } 25 | 26 | init(wishBookId: WishBook.ID, userId: User.ID, comment: String, reportCount: Int = 0) { 27 | self.wishBookId = wishBookId 28 | self.userId = userId 29 | self.comment = comment 30 | self.reportCount = reportCount 31 | } 32 | } 33 | 34 | extension WishBookComment: Migration { 35 | static func prepare(on connection: PostgreSQLConnection) -> Future { 36 | return Database.create(self, on: connection) { builder in 37 | try addProperties(to: builder) 38 | builder.reference(from: \.userId, to: \User.id) 39 | builder.reference(from: \.wishBookId, to: \WishBook.id) 40 | } 41 | } 42 | } 43 | extension WishBookComment: PostgreSQLModel {} 44 | 45 | extension WishBookComment { 46 | var wishBook: Parent { 47 | return parent(\.wishBookId) 48 | } 49 | 50 | var creater: Parent { 51 | return parent(\.userId) 52 | } 53 | } 54 | -------------------------------------------------------------------------------- /Sources/App/Models/Requests/Account/AuthenticationContainer.swift: -------------------------------------------------------------------------------- 1 | // 2 | // AuthenticationContainer.swift 3 | // App 4 | // 5 | // Created by laijihua on 2018/6/16. 6 | // 7 | 8 | import Vapor 9 | 10 | struct AuthenticationContainer: Content { 11 | 12 | //MARK: Properties 13 | let accessToken: AccessToken.Token 14 | let expiresIn: TimeInterval 15 | let refreshToken: RefreshToken.Token 16 | 17 | //MARK: Initializers 18 | init(accessToken: AccessToken, refreshToken: RefreshToken) { 19 | self.accessToken = accessToken.token 20 | self.expiresIn = accessToken.expiryTime.timeIntervalSince1970 //Not honored, just an estimate 21 | self.refreshToken = refreshToken.token 22 | } 23 | 24 | //MARK: Codable 25 | private enum CodingKeys: String, CodingKey { 26 | case accessToken = "access_token" 27 | case expiresIn = "expires_in" 28 | case refreshToken = "refresh_token" 29 | } 30 | } 31 | -------------------------------------------------------------------------------- /Sources/App/Models/Requests/Account/EmailLoginContainer.swift: -------------------------------------------------------------------------------- 1 | 2 | // 3 | // UserLoginContainer.swift 4 | // App 5 | // 6 | // Created by laijihua on 2018/6/16. 7 | // 8 | 9 | import Vapor 10 | 11 | struct EmailLoginContainer: Content { 12 | let email: String 13 | let password: String 14 | } 15 | -------------------------------------------------------------------------------- /Sources/App/Models/Requests/Account/NewPasswordContainer.swift: -------------------------------------------------------------------------------- 1 | // 2 | // NewPasswordContainer.swift 3 | // App 4 | // 5 | // Created by laijihua on 2018/6/25. 6 | // 7 | 8 | import Vapor 9 | 10 | struct NewsPasswordContainer: Content { 11 | let email: String 12 | let password: String 13 | let newPassword: String 14 | let code: String 15 | } 16 | -------------------------------------------------------------------------------- /Sources/App/Models/Requests/Account/RefreshTokenContainer.swift: -------------------------------------------------------------------------------- 1 | // 2 | // RefreshTokenContainer.swift 3 | // App 4 | // 5 | // Created by laijihua on 2018/6/16. 6 | // 7 | 8 | import Vapor 9 | 10 | struct RefreshTokenContainer: Content { 11 | let refreshToken: RefreshToken.Token 12 | 13 | private enum CodingKeys: String, CodingKey { 14 | case refreshToken = "refresh_token" 15 | } 16 | } 17 | -------------------------------------------------------------------------------- /Sources/App/Models/Requests/Account/RegisteCodeContainer.swift: -------------------------------------------------------------------------------- 1 | // 2 | // RegisteCodeContainer.swift 3 | // App 4 | // 5 | // Created by laijihua on 2018/9/5. 6 | // 7 | import Vapor 8 | 9 | struct RegisteCodeContainer: Content { 10 | var code: String 11 | var userId: User.ID 12 | } 13 | -------------------------------------------------------------------------------- /Sources/App/Models/Requests/Account/UserEmailContainer.swift: -------------------------------------------------------------------------------- 1 | // 2 | // UserEmailContainer.swift 3 | // App 4 | // 5 | // Created by laijihua on 2018/6/16. 6 | // 7 | 8 | import Vapor 9 | 10 | struct UserEmailContainer: Content { 11 | 12 | //MARK: Properties 13 | let email: String 14 | } 15 | -------------------------------------------------------------------------------- /Sources/App/Models/Requests/Account/UserRegisterContainer.swift: -------------------------------------------------------------------------------- 1 | // 2 | // UserRegisterContainer.swift 3 | // App 4 | // 5 | // Created by laijihua on 2018/7/13. 6 | // 7 | import Vapor 8 | 9 | struct UserRegisterContainer: Content { 10 | let email: String 11 | let password: String 12 | let name: String 13 | let organizId: Organization.ID? 14 | } 15 | -------------------------------------------------------------------------------- /Sources/App/Models/Requests/Account/UserUpdateContainer.swift: -------------------------------------------------------------------------------- 1 | // 2 | // UserUpdateContainer.swift 3 | // App 4 | // 5 | // Created by laijihua on 2018/7/16. 6 | // 7 | 8 | import Vapor 9 | 10 | struct UserUpdateContainer: Content { 11 | var organizId: Organization.ID? 12 | var phone: String? 13 | var name: String? 14 | var avator: String? 15 | var info: String? 16 | } 17 | -------------------------------------------------------------------------------- /Sources/App/Models/Requests/Account/UserWxAppOauthContainer.swift: -------------------------------------------------------------------------------- 1 | // 2 | // UserWxAppOauthContainer.swift 3 | // App 4 | // 5 | // Created by laijihua on 2018/9/10. 6 | // 7 | import Vapor 8 | 9 | struct UserWxAppOauthContainer: Content { 10 | let encryptedData: String // encryptedData 11 | let iv: String // iv 12 | let code: String 13 | } 14 | -------------------------------------------------------------------------------- /Sources/App/Models/Requests/Book/BookCommentListContainer.swift: -------------------------------------------------------------------------------- 1 | // 2 | // BookCommentListContainer.swift 3 | // App 4 | // 5 | // Created by laijihua on 2018/6/29. 6 | // 7 | 8 | import Vapor 9 | 10 | struct BookCommentListContainer: Content { 11 | var bookId: Book.ID 12 | } 13 | -------------------------------------------------------------------------------- /Sources/App/Models/Requests/Book/BookCreateContainer.swift: -------------------------------------------------------------------------------- 1 | // 2 | // BookCreateContainer.swift 3 | // App 4 | // 5 | // Created by laijihua on 2018/6/27. 6 | // 7 | 8 | import Vapor 9 | 10 | struct BookCreateContainer: Content { 11 | var isbn: String 12 | var name: String 13 | var author: String 14 | var price: Double 15 | var detail: String 16 | var convers: [String] 17 | var doubanPrice: Double // 豆瓣价格 18 | var doubanGrade: Double // 豆瓣等级 19 | var classifyId: BookClassify.ID 20 | var priceUintId: PriceUnit.ID 21 | } 22 | 23 | struct BookUpdateContainer: Content { 24 | var id: Book.ID 25 | var price: Double? 26 | var detail: String? 27 | var convers: [String]? 28 | } 29 | 30 | struct BookCheckContainer: Content { 31 | var id: Book.ID 32 | var state: Book.State 33 | var remarks: String // 审核的标注 34 | } 35 | -------------------------------------------------------------------------------- /Sources/App/Models/Requests/Book/BookIsbnContainer.swift: -------------------------------------------------------------------------------- 1 | // 2 | // BookIsbnContainer.swift 3 | // App 4 | // 5 | // Created by laijihua on 2018/9/18. 6 | // 7 | 8 | import Vapor 9 | 10 | struct BookIsbnContainer: Content { 11 | var isbn: String 12 | } 13 | -------------------------------------------------------------------------------- /Sources/App/Models/Requests/Book/BookListContainer.swift: -------------------------------------------------------------------------------- 1 | // 2 | // BookListContainer.swift 3 | // App 4 | // 5 | // Created by laijihua on 2018/7/16. 6 | // 7 | 8 | import Vapor 9 | 10 | struct BookListContainer: Content { 11 | var type: Int? // 类型 12 | var sort: Int? // 排序 13 | } 14 | 15 | extension BookListContainer { 16 | enum BookListType: Int { 17 | case hot = 0 // 热榜 18 | case new = 1 // 新书 19 | } 20 | 21 | var bType: BookListType { 22 | guard let tt = type else {return .hot} 23 | switch tt { 24 | case 0: return .hot 25 | case 1: return .new 26 | default: return .hot 27 | } 28 | } 29 | } 30 | -------------------------------------------------------------------------------- /Sources/App/Models/Requests/DeleteIDContainer.swift: -------------------------------------------------------------------------------- 1 | // 2 | // DeleteIDContainer.swift 3 | // APIErrorMiddleware 4 | // 5 | // Created by laijihua on 2018/7/15. 6 | // 7 | 8 | 9 | import Vapor 10 | import FluentPostgreSQL 11 | 12 | struct DeleteIDContainer: Content { 13 | var id: Model.ID 14 | } 15 | -------------------------------------------------------------------------------- /Sources/App/Models/Requests/Sys/TestContainer.swift: -------------------------------------------------------------------------------- 1 | // 2 | // TestContainer.swift 3 | // App 4 | // 5 | // Created by laijihua on 2018/7/15. 6 | // 7 | 8 | import Foundation 9 | -------------------------------------------------------------------------------- /Sources/App/Models/Requests/WishBook/WishBookCreateContainer.swift: -------------------------------------------------------------------------------- 1 | // 2 | // WishBookCreateContainer.swift 3 | // App 4 | // 5 | // Created by laijihua on 2018/7/17. 6 | // 7 | 8 | import Vapor 9 | 10 | struct WishBookCreateContainer: Content { 11 | var title: String 12 | var content: String 13 | var userId: User.ID 14 | } 15 | 16 | -------------------------------------------------------------------------------- /Sources/App/Models/Responses/JSONContainer.swift: -------------------------------------------------------------------------------- 1 | // 2 | // JSONContainer.swift 3 | // App 4 | // 5 | // Created by laijihua on 2018/6/20. 6 | // 7 | 8 | import Vapor 9 | 10 | enum ResponseStatus: UInt, Content { 11 | case ok = 0 // 请求成功状态 12 | 13 | /// 接口失败 14 | case userExist = 20 15 | case userNotExist = 21 16 | case passwordError = 22 17 | case emailNotExist = 23 18 | case bookNotExist = 24 19 | case modelNotExist = 25 20 | case modelExisted = 26 21 | case authFail = 27 22 | case codeFail = 28 23 | case resonNotExist = 29 24 | case base64DecodeError = 30 25 | 26 | case custom = 31 27 | case refreshTokenNotExist = 32 28 | 29 | var desc: String { 30 | switch self { 31 | case .ok: 32 | return "请求成功" 33 | case .userExist: 34 | return "用户已经存在" 35 | case .userNotExist: 36 | return "用户不存在" 37 | case .passwordError: 38 | return "密码错误" 39 | case .emailNotExist: 40 | return "邮箱不存在" 41 | case .bookNotExist: 42 | return "书籍不存在" 43 | case .modelNotExist: 44 | return "对象不存在" 45 | case .modelExisted: 46 | return "对象已存在" 47 | case .authFail: 48 | return "认证失败" 49 | case .codeFail: 50 | return "验证码错误" 51 | case .resonNotExist: 52 | return "不存在reason" 53 | case .base64DecodeError: 54 | return "base64 decode 失败" 55 | case .custom: 56 | return "出错了" 57 | case .refreshTokenNotExist: 58 | return "refreshToken 不存在" 59 | } 60 | } 61 | } 62 | 63 | struct Empty: Content {} 64 | 65 | struct JSONContainer: Content { 66 | private var status: ResponseStatus 67 | private var message: String 68 | private var data: D? 69 | 70 | static var successEmpty: JSONContainer { 71 | return JSONContainer() 72 | } 73 | 74 | init(data:D? = nil) { 75 | self.status = .ok 76 | self.message = self.status.desc 77 | self.data = data 78 | } 79 | 80 | init(data: D) { 81 | self.status = .ok 82 | self.message = status.desc 83 | self.data = data 84 | } 85 | 86 | static func success(data: D) -> JSONContainer { 87 | return JSONContainer(data:data) 88 | } 89 | } 90 | 91 | extension Future where T: Content { 92 | func makeJson(on request: Request) throws -> Future { 93 | return try self.map { data in 94 | return JSONContainer(data: data) 95 | }.encode(for: request) 96 | } 97 | } 98 | 99 | extension Future where T == Void { 100 | func makeJson(request: Request) throws -> Future { 101 | return try self.transform(to: JSONContainer.successEmpty).encode(for: request) 102 | } 103 | } 104 | 105 | extension Future where T == Either { 106 | func makeJson(on request: Request) throws -> Future { 107 | return try self.makeJson(on: request) 108 | } 109 | } 110 | 111 | extension Request { 112 | func makeJson(_ content: T) throws -> Future { 113 | return try JSONContainer(data: content).encode(for: self) 114 | } 115 | 116 | /// Void json 117 | func makeJson() throws -> Future { 118 | return try JSONContainer(data: nil).encode(for: self) 119 | } 120 | } 121 | -------------------------------------------------------------------------------- /Sources/App/Models/Responses/WxAppUserInfoContainer.swift: -------------------------------------------------------------------------------- 1 | // 2 | // WxAppUserInfoContainer.swift 3 | // App 4 | // 5 | // Created by laijihua on 2018/9/11. 6 | // 7 | 8 | import Vapor 9 | 10 | /// openId : 用户在当前小程序的唯一标识 11 | struct WxAppUserInfoContainer: Content { 12 | var openId: String 13 | var nickName: String 14 | var city: String 15 | var province: String 16 | var country: String 17 | var avatarUrl: String 18 | var unionId: String? // 如果开发者拥有多个移动应用、网站应用、和公众帐号(包括小程序),可通过unionid来区分用户的唯一性,因为只要是同一个微信开放平台帐号下的移动应用、网站应用和公众帐号(包括小程序),用户的unionid是唯一的。换句话说,同一用户,对同一个微信开放平台下的不同应用,unionId是相同的 19 | var watermark: WaterMark 20 | 21 | struct WaterMark: Content { 22 | var appid: String 23 | var timestamp: TimeInterval 24 | } 25 | } 26 | 27 | struct WxAppCodeResContainer: Content { 28 | var session_key: String 29 | var expires_in: TimeInterval 30 | var openid: String 31 | } 32 | -------------------------------------------------------------------------------- /Sources/App/Setup/app.swift: -------------------------------------------------------------------------------- 1 | // 2 | // app.swift 3 | // App 4 | // 5 | // Created by laijihua on 2018/8/29. 6 | // 7 | 8 | import Vapor 9 | 10 | public func app(_ env: Environment) throws -> Application { 11 | var config = Config.default() 12 | var env = env 13 | var services = Services.default() 14 | try configure(&config, &env, &services) 15 | let app = try Application(config: config, environment: env, services: services) 16 | try boot(app) 17 | 18 | return app 19 | } 20 | -------------------------------------------------------------------------------- /Sources/App/Setup/boot.swift: -------------------------------------------------------------------------------- 1 | import Routing 2 | import Vapor 3 | 4 | /// Called after your application has initialized. 5 | /// 6 | /// [Learn More →](https://docs.vapor.codes/3.0/getting-started/structure/#bootswift) 7 | public func boot(_ app: Application) throws { 8 | // your code here 9 | } 10 | -------------------------------------------------------------------------------- /Sources/App/Setup/commands.swift: -------------------------------------------------------------------------------- 1 | // 2 | // command.swift 3 | // App 4 | // 5 | // Created by laijihua on 2018/8/29. 6 | // 7 | import Vapor 8 | 9 | public func commands(config: inout CommandConfig) { 10 | config.useFluentCommands() 11 | } 12 | -------------------------------------------------------------------------------- /Sources/App/Setup/configure.swift: -------------------------------------------------------------------------------- 1 | 2 | import Vapor 3 | import FluentPostgreSQL 4 | /// Called before your application initializes. 5 | /// 6 | /// https://docs.vapor.codes/3.0/getting-started/structure/#configureswift 7 | public func configure( 8 | _ config: inout Config, 9 | _ env: inout Environment, 10 | _ services: inout Services 11 | ) throws { 12 | let router = EngineRouter.default() 13 | try routes(router) 14 | services.register(router, as: Router.self) 15 | 16 | let serverConfig = NIOServerConfig.default(hostname: "0.0.0.0", 17 | port: 8988) 18 | services.register(serverConfig) 19 | 20 | /// 配置全局的 middleware 21 | var middlewaresConfig = MiddlewareConfig() 22 | try middlewares(config: &middlewaresConfig, env:&env) 23 | services.register(middlewaresConfig) 24 | 25 | var commandConfig = CommandConfig.default() 26 | commands(config: &commandConfig) 27 | services.register(commandConfig) 28 | 29 | /// Register Content Config 30 | var contentConfig = ContentConfig.default() 31 | try content(config: &contentConfig) 32 | services.register(contentConfig) 33 | 34 | var databasesConfig = DatabasesConfig() 35 | try databases(config: &databasesConfig, services: &services) 36 | services.register(databasesConfig) 37 | 38 | services.register { (container) -> MigrationConfig in 39 | var migrationConfig = MigrationConfig() 40 | try migrate(migrations: &migrationConfig) 41 | return migrationConfig 42 | } 43 | } 44 | 45 | -------------------------------------------------------------------------------- /Sources/App/Setup/content.swift: -------------------------------------------------------------------------------- 1 | // 2 | // content.swift 3 | // App 4 | // 5 | // Created by laijihua on 2018/8/29. 6 | // 7 | 8 | import Vapor 9 | 10 | public func content(config: inout ContentConfig) throws { 11 | let encoder = JSONEncoder() 12 | let decoder = JSONDecoder() 13 | 14 | encoder.dateEncodingStrategy = .millisecondsSince1970 15 | decoder.dateDecodingStrategy = .millisecondsSince1970 16 | 17 | config.use(encoder: encoder, for: .json) 18 | config.use(decoder: decoder, for: .json) 19 | } 20 | -------------------------------------------------------------------------------- /Sources/App/Setup/databases.swift: -------------------------------------------------------------------------------- 1 | // 2 | // databases.swift 3 | // App 4 | // 5 | // Created by laijihua on 2018/8/29. 6 | // 7 | 8 | import Vapor 9 | import FluentPostgreSQL 10 | import Authentication 11 | 12 | public func databases(config: inout DatabasesConfig, services: inout Services) throws { 13 | 14 | try services.register(FluentPostgreSQLProvider()) 15 | try services.register(AuthenticationProvider()) 16 | 17 | let psqlConfig = PostgreSQLDatabaseConfig(hostname: "localhost", 18 | port: 5432, 19 | username: "root", 20 | database: "book", 21 | password: "lai12345") 22 | config.add(database: PostgreSQLDatabase(config: psqlConfig), as: .psql) 23 | } 24 | -------------------------------------------------------------------------------- /Sources/App/Setup/middlewares.swift: -------------------------------------------------------------------------------- 1 | // 2 | // middlewares.swift 3 | // App 4 | // 5 | // Created by laijihua on 2018/8/29. 6 | // 7 | 8 | import Vapor 9 | import Authentication 10 | 11 | public func middlewares(config: inout MiddlewareConfig, env: inout Environment) throws { 12 | 13 | config.use(APIErrorMiddleware(environment: env, specializations: [ 14 | ModelNotFound() 15 | ])) 16 | 17 | let corsConfig = CORSMiddleware.Configuration( 18 | allowedOrigin: .originBased, 19 | allowedMethods: [.GET, .POST, .PUT, .OPTIONS, .DELETE, .PATCH], 20 | allowedHeaders: [.accept, .authorization, .contentType, .origin, .xRequestedWith, .userAgent], 21 | exposedHeaders: [ 22 | HTTPHeaderName.authorization.description, 23 | HTTPHeaderName.contentLength.description, 24 | HTTPHeaderName.contentType.description, 25 | HTTPHeaderName.contentDisposition.description, 26 | HTTPHeaderName.cacheControl.description, 27 | HTTPHeaderName.expires.description 28 | ] 29 | ) 30 | config.use(CORSMiddleware(configuration: corsConfig)) 31 | } 32 | -------------------------------------------------------------------------------- /Sources/App/Setup/migrate.swift: -------------------------------------------------------------------------------- 1 | // 2 | // migrate.swift 3 | // App 4 | // 5 | // Created by laijihua on 2018/8/29. 6 | // 7 | 8 | import Vapor 9 | import FluentPostgreSQL //use your database driver here 10 | 11 | public func migrate(migrations: inout MigrationConfig) throws { 12 | migrations.add(model: Organization.self, database: .psql) 13 | migrations.add(model: User.self, database: .psql) 14 | migrations.add(model: Menu.self, database: .psql) 15 | migrations.add(model: Role.self, database: .psql) 16 | migrations.add(model: OpLog.self, database: .psql) 17 | migrations.add(model: Right.self, database: .psql) 18 | migrations.add(model: Group.self, database: .psql) 19 | migrations.add(model: RoleRight.self, database: .psql) 20 | migrations.add(model: GroupRight.self, database: .psql) 21 | migrations.add(model: GroupRole.self, database: .psql) 22 | migrations.add(model: UserRight.self, database: .psql) 23 | migrations.add(model: UserRole.self, database: .psql) 24 | migrations.add(model: UserGroup.self, database: .psql) 25 | migrations.add(model: AccessToken.self, database: .psql) 26 | migrations.add(model: RefreshToken.self, database: .psql) 27 | migrations.add(model: Friend.self, database: .psql) 28 | migrations.add(model: PriceUnit.self, database: .psql) 29 | migrations.add(model: BookClassify.self, database: .psql) 30 | migrations.add(model: Book.self, database: .psql) 31 | migrations.add(model: ActiveCode.self, database: .psql) 32 | migrations.add(model: Feedback.self, database: .psql) 33 | migrations.add(model: MessageBoard.self, database: .psql) 34 | migrations.add(model: WishBook.self, database: .psql) 35 | migrations.add(model: WishBookComment.self, database: .psql) 36 | migrations.add(model: PriceUnit.self, database: .psql) 37 | migrations.add(model: Notify.self, database: .psql) 38 | migrations.add(model: UserNotify.self, database: .psql) 39 | migrations.add(model: Subscription.self, database: .psql) 40 | migrations.add(model: UserAuth.self, database: .psql) 41 | 42 | // Populate 43 | migrations.add(migration: PopulateOrganizationForms.self, database: .psql) 44 | migrations.add(migration: PopulateMenuForms.self, database: .psql) 45 | migrations.add(migration: PopulateBookClassifyForms.self, database: .psql) 46 | migrations.add(migration: PopulatePriceUnitForms.self, database: .psql) 47 | } 48 | -------------------------------------------------------------------------------- /Sources/App/Setup/repositories.swift: -------------------------------------------------------------------------------- 1 | // 2 | // repositories.swift 3 | // App 4 | // 5 | // Created by laijihua on 2018/8/29. 6 | // 7 | 8 | import Foundation 9 | -------------------------------------------------------------------------------- /Sources/App/Setup/routes.swift: -------------------------------------------------------------------------------- 1 | import Routing 2 | import Vapor 3 | 4 | 5 | public func routes(_ router: Router) throws { 6 | 7 | router.get("welcome") { req in 8 | return "welcome" 9 | } 10 | 11 | router.get("senderEmail") { request in 12 | return try EmailSender.sendEmail(request, content: .accountActive(emailTo: "1164258202@qq.com", url: "https://baidu.com")).transform(to: HTTPStatus.ok) 13 | } 14 | 15 | router.get("scheme") { request in 16 | return request 17 | .http 18 | .headers 19 | .firstValue(name: HTTPHeaderName.host) ?? "" 20 | } 21 | 22 | do { 23 | let aa = try "c2RmanNvb2pvc2Rmam9qbw==".base64decode() 24 | print(aa) 25 | } catch { 26 | print("error") 27 | } 28 | 29 | func handleTestPost(_ request: Request) throws -> Future { 30 | return try request.content.decode(User.self) 31 | } 32 | 33 | router.post("test", use: handleTestPost) 34 | 35 | let authRouteController = AuthenticationRouteController() 36 | try router.register(collection: authRouteController) 37 | 38 | let userRouteController = UserRouteController() 39 | try router.register(collection: userRouteController) 40 | 41 | let sysRouteController = SysRouteController() 42 | try router.register(collection: sysRouteController) 43 | 44 | let protectedRouteController = ProtectedRoutesController() 45 | try router.register(collection: protectedRouteController) 46 | 47 | let accountRouteController = AccountRouteController() 48 | try router.register(collection: accountRouteController) 49 | 50 | let bookRouteController = BookRouteController() 51 | try router.register(collection: bookRouteController) 52 | 53 | let wishBookRouteController = WishBookRouteController() 54 | try router.register(collection: wishBookRouteController) 55 | 56 | let searchRouteController = SearchRouteController() 57 | try router.register(collection: searchRouteController) 58 | 59 | let newsRouteController = NewsRouteController() 60 | try router.register(collection: newsRouteController) 61 | } 62 | -------------------------------------------------------------------------------- /Sources/App/Utils/Base64/Base64.swift: -------------------------------------------------------------------------------- 1 | // 2 | // base64.swift 3 | // unchained 4 | // 5 | // Created by Johannes Schriewer on 13/12/15. 6 | // Copyright © 2015 Johannes Schriewer. All rights reserved. 7 | // 8 | 9 | /// Base64 alphabet 10 | public enum Base64Alphabet: String { 11 | /// default alphabet 12 | case `default` = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/" 13 | 14 | /// URL type alphabet 15 | case url = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789-_" 16 | 17 | /// XML name alphabet 18 | case xmlName = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789.-" 19 | 20 | /// XML identifier alphabet 21 | case xmlIdentifier = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789_:" 22 | 23 | /// alphabet for file names 24 | case filename = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+-" 25 | 26 | /// alphabet for regular expressions 27 | case regEx = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789!-" 28 | } 29 | 30 | /// Base64 decoding and encoding 31 | public class Base64 { 32 | 33 | /// Encode data with Base64 encoding 34 | /// 35 | /// - parameter data: data to encode 36 | /// - parameter linebreak: (optional) number of characters after which to insert a linebreak. (Value rounded to multiple of 4) 37 | /// - parameter alphabet: (optional) Base64 alphabet to use 38 | /// - returns: Base64 string with padding 39 | public class func encode(data: [UInt8], linebreak: Int? = nil, alphabet: Base64Alphabet = .default) -> String { 40 | 41 | // Build lookup table from alphabet 42 | let lookup = alphabet.rawValue.utf8.map { c -> UnicodeScalar in 43 | return UnicodeScalar(c) 44 | } 45 | 46 | // round linebreak value to quads 47 | var breakQuads: Int = 0 48 | if let linebreak = linebreak { 49 | breakQuads = (linebreak / 4) * 4 50 | } 51 | 52 | // encode full triplets to quads 53 | var outData = "" 54 | outData.reserveCapacity(data.count / 3 * 4 + 4) 55 | for i in 0..<(data.count / 3) { 56 | if (breakQuads > 0) && (i > 0) && ((i * 4) % breakQuads == 0) { 57 | outData.append("\r\n") 58 | } 59 | 60 | let d1 = data[i * 3] 61 | let d2 = data[i * 3 + 1] 62 | let d3 = data[i * 3 + 2] 63 | 64 | let o1 = (d1 >> 2) & 0x3f 65 | let o2 = ((d1 << 4) + (d2 >> 4)) & 0x3f 66 | let o3 = ((d2 << 2) + (d3 >> 6)) & 0x3f 67 | let o4 = d3 & 0x3f 68 | 69 | outData.unicodeScalars.append(lookup[Int(o1)]) 70 | outData.unicodeScalars.append(lookup[Int(o2)]) 71 | outData.unicodeScalars.append(lookup[Int(o3)]) 72 | outData.unicodeScalars.append(lookup[Int(o4)]) 73 | } 74 | 75 | // calculate leftover bytes 76 | let overhang = data.count - ((data.count / 3) * 3) 77 | if overhang > 0 { 78 | if (breakQuads > 0) && (((data.count / 3) * 4) % breakQuads == 0) { 79 | outData.append("\r\n") 80 | } 81 | } 82 | 83 | if overhang == 1 { 84 | // one byte left, pad with two equal signs 85 | let d1 = data[data.count - 1] 86 | 87 | let o1 = (d1 >> 2) & 0x3f 88 | let o2 = (d1 << 4) & 0x3f 89 | 90 | outData.unicodeScalars.append(lookup[Int(o1)]) 91 | outData.unicodeScalars.append(lookup[Int(o2)]) 92 | outData.unicodeScalars.append(UnicodeScalar(61)) // = 93 | outData.unicodeScalars.append(UnicodeScalar(61)) // = 94 | } else if overhang == 2 { 95 | // two bytes left, pad with one equal sign 96 | let d1 = data[data.count - 2] 97 | let d2 = data[data.count - 1] 98 | 99 | let o1 = (d1 >> 2) & 0x3f 100 | let o2 = ((d1 << 4) + (d2 >> 4)) & 0x3f 101 | let o3 = (d2 << 2) & 0x3f 102 | 103 | outData.unicodeScalars.append(lookup[Int(o1)]) 104 | outData.unicodeScalars.append(lookup[Int(o2)]) 105 | outData.unicodeScalars.append(lookup[Int(o3)]) 106 | outData.unicodeScalars.append(UnicodeScalar(61)) // = 107 | } 108 | 109 | return outData 110 | } 111 | 112 | /// Decode Base64 encoded data 113 | /// 114 | /// - parameter data: data to decode 115 | /// - parameter alphabet: (optional) Base64 alphabet to use 116 | /// - returns: decoded data 117 | public class func decode(data: [UInt8], alphabet: Base64Alphabet = .default) -> [UInt8] { 118 | // build lookup table for decoding 119 | var lookup = [UInt8](repeating: 64, count: 255) 120 | let alpha = alphabet.rawValue.utf8 121 | var idx = alpha.startIndex 122 | for i in 0..> 4) 145 | let o2 = (din[1] << 4) + (din[2] >> 2) 146 | let o3 = (din[2] << 6) + din[3] 147 | outData.append(o1) 148 | outData.append(o2) 149 | outData.append(o3) 150 | din.removeAll() 151 | } 152 | } 153 | 154 | // if data was not a full quad, assume padding 155 | if din.count > 0 { 156 | if din.count == 2 { 157 | let o1 = (din[0] << 2) + (din[1] >> 4) 158 | let o2 = (din[1] << 4) 159 | 160 | outData.append(o1) 161 | outData.append(o2) 162 | } else if din.count == 3 { 163 | let o1 = (din[0] << 2) + (din[1] >> 4) 164 | let o2 = (din[1] << 4) + (din[2] >> 2) 165 | let o3 = (din[2] << 6) 166 | 167 | outData.append(o1) 168 | outData.append(o2) 169 | outData.append(o3) 170 | } 171 | } 172 | 173 | return outData 174 | } 175 | 176 | /// Encode string with Base64 encoding 177 | /// 178 | /// - parameter string: string to encode 179 | /// - parameter linebreak: (optional) number of characters after which to insert a linebreak. (Value rounded to multiple of 4) 180 | /// - parameter alphabet: (optional) Base64 alphabet to use 181 | /// - returns: Base64 string with padding 182 | public class func encode(string: String, linebreak: Int? = nil, alphabet: Base64Alphabet = .default) -> String { 183 | return Base64.encode(data: string.utf8.map({ c -> UInt8 in 184 | return c 185 | }), linebreak: linebreak, alphabet: alphabet) 186 | } 187 | 188 | /// Decode Base64 encoded string 189 | /// 190 | /// - parameter string: string to decode 191 | /// - parameter alphabet: (optional) Base64 alphabet to use 192 | /// - returns: decoded data 193 | public class func decode(string: String, alphabet: Base64Alphabet = .default) -> [UInt8] { 194 | return Base64.decode(data: string.utf8.map({ c -> UInt8 in 195 | return c 196 | }), alphabet: alphabet) 197 | } 198 | } 199 | 200 | -------------------------------------------------------------------------------- /Sources/App/Utils/Either/Either.swift: -------------------------------------------------------------------------------- 1 | // 2 | // Either.swift 3 | // App 4 | // 5 | // Created by laijihua on 2018/9/5. 6 | // 7 | 8 | import Foundation 9 | 10 | enum Either { 11 | case left(T) 12 | case right(U) 13 | } 14 | 15 | extension Either: EitherProtocol { 16 | static func toLeft(_ value: T) -> Either { 17 | return .left(value) 18 | } 19 | 20 | static func toRight(_ value: U) -> Either { 21 | return .right(value) 22 | } 23 | 24 | func either(ifLeft: (T) throws -> Result, ifRight: (U) throws -> Result) rethrows -> Result { 25 | switch self { 26 | case let .left(x): 27 | return try ifLeft(x) 28 | case let .right(x): 29 | return try ifRight(x) 30 | } 31 | } 32 | } 33 | 34 | 35 | extension Either { 36 | func map(_ transform: (U) -> V) -> Either { 37 | return flatMap { .right(transform($0)) } 38 | } 39 | 40 | /// Returns the result of applying `transform` to `Right` values, or re-wrapping `Left` values. 41 | func flatMap(_ transform: (U) -> Either) -> Either { 42 | return either( 43 | ifLeft: Either.left, 44 | ifRight: transform) 45 | } 46 | 47 | /// Maps `Left` values with `transform`, and re-wraps `Right` values. 48 | func mapLeft(_ transform: (T) -> V) -> Either { 49 | return flatMapLeft { .left(transform($0)) } 50 | } 51 | 52 | /// Returns the result of applying `transform` to `Left` values, or re-wrapping `Right` values. 53 | func flatMapLeft(_ transform: (T) -> Either) -> Either { 54 | return either( 55 | ifLeft: transform, 56 | ifRight: Either.right) 57 | } 58 | 59 | public func bimap(leftBy lf: (T) -> V, rightBy rf: (U) -> W) -> Either { 60 | return either( 61 | ifLeft: { .left(lf($0)) }, 62 | ifRight: { .right(rf($0)) }) 63 | } 64 | } 65 | 66 | extension Sequence where Iterator.Element: EitherProtocol { 67 | /// Select only `Right` instances. 68 | public var rights: [Iterator.Element.Right] { 69 | return compactMap { $0.right } 70 | } 71 | 72 | /// Select only `Left` instances. 73 | public var lefts: [Iterator.Element.Left] { 74 | return compactMap { $0.left } 75 | } 76 | } 77 | 78 | extension Either: Codable where T: Codable, U: Codable { 79 | enum CodingKeys: CodingKey { 80 | case left 81 | case right 82 | } 83 | 84 | func encode(to encoder: Encoder) throws { 85 | var container = encoder.container(keyedBy: CodingKeys.self) 86 | switch self { 87 | case .left(let value): 88 | try container.encode(value, forKey: .left) 89 | case .right(let value): 90 | try container.encode(value, forKey: .right) 91 | } 92 | } 93 | 94 | init(from decoder: Decoder) throws { 95 | let container = try decoder.container(keyedBy: CodingKeys.self) 96 | do { 97 | let leftValue = try container.decode(T.self, forKey: .left) 98 | self = .left(leftValue) 99 | } catch { 100 | let rightValue = try container.decode(U.self, forKey: .right) 101 | self = .right(rightValue) 102 | } 103 | } 104 | 105 | } 106 | 107 | 108 | 109 | precedencegroup Bind { 110 | associativity: left 111 | higherThan: DefaultPrecedence 112 | } 113 | infix operator >>- : Bind 114 | func >>- (either: Either, transform: (U) -> Either) -> Either { 115 | return either.flatMap(transform) 116 | } 117 | 118 | 119 | -------------------------------------------------------------------------------- /Sources/App/Utils/Either/EitherProtocol.swift: -------------------------------------------------------------------------------- 1 | // 2 | // EitherProtocol.swift 3 | // App 4 | // 5 | // Created by laijihua on 2018/9/5. 6 | // 7 | 8 | import Foundation 9 | 10 | private func const(_ x: T) -> (U) -> T { 11 | return { _ in x } 12 | } 13 | 14 | 15 | /// Same associativity and precedence as &&. 16 | infix operator &&& : LogicalConjunctionPrecedence 17 | private func &&& (left: T?, right: @autoclosure () -> U?) -> (T, U)? { 18 | if let x = left, let y = right() { 19 | return (x, y) 20 | } 21 | return nil 22 | } 23 | 24 | public protocol EitherProtocol { 25 | associatedtype Left 26 | associatedtype Right 27 | 28 | /// Constructs a `Left` instance. 29 | static func toLeft(_ value: Left) -> Self 30 | 31 | /// Constructs a `Right` instance. 32 | static func toRight(_ value: Right) -> Self 33 | 34 | /// Returns the result of applying `f` to `Left` values, or `g` to `Right` values. 35 | func either(ifLeft: (Left) throws -> Result, ifRight: (Right) throws -> Result) rethrows -> Result 36 | } 37 | 38 | 39 | extension EitherProtocol { 40 | /// Returns the value of `Left` instances, or `nil` for `Right` instances. 41 | var left: Left? { 42 | return either(ifLeft: Optional.some, ifRight: const(nil)) 43 | } 44 | 45 | /// Returns the value of `Right` instances, or `nil` for `Left` instances. 46 | var right: Right? { 47 | return either(ifLeft: const(nil), ifRight: Optional.some) 48 | } 49 | 50 | /// Returns true of `Left` instances, or false for `Right` instances. 51 | var isLeft: Bool { 52 | return either(ifLeft: const(true), ifRight: const(false)) 53 | } 54 | 55 | /// Returns true of `Right` instances, or false for `Left` instances. 56 | var isRight: Bool { 57 | return either(ifLeft: const(false), ifRight: const(true)) 58 | } 59 | } 60 | 61 | extension EitherProtocol where Left: Equatable, Right: Equatable { 62 | /// Equality (tho not `Equatable`) over `EitherType` where `Left` & `Right` : `Equatable`. 63 | static func == (lhs: Self, rhs: Self) -> Bool { 64 | return Self.equivalence(left: ==, right: ==)(lhs, rhs) 65 | } 66 | 67 | /// Inequality over `EitherType` where `Left` & `Right` : `Equatable`. 68 | static func != (lhs: Self, rhs: Self) -> Bool { 69 | return !(lhs == rhs) 70 | } 71 | } 72 | 73 | extension EitherProtocol { 74 | /// Given equivalent functions for `Left` and `Right`, returns an equivalent function for `EitherProtocol`. 75 | static func equivalence(left: @escaping (Left, Left) -> Bool, right: @escaping (Right, Right) -> Bool) -> (Self, Self) -> Bool { 76 | return { a, b in 77 | (a.left &&& b.left).map(left) 78 | ?? (a.right &&& b.right).map(right) 79 | ?? false 80 | } 81 | } 82 | } 83 | -------------------------------------------------------------------------------- /Sources/App/Utils/Email/EmailSender.swift: -------------------------------------------------------------------------------- 1 | // 2 | // EmailSender.swift 3 | // App 4 | // 5 | // Created by laijihua on 2018/8/19. 6 | // 7 | 8 | import Vapor 9 | import SwiftSMTP 10 | 11 | final class EmailSender { 12 | enum Content { 13 | case register(emailTo: String, code: String) 14 | case accountActive(emailTo: String, url: String) 15 | case changePwd(emailTo: String, code: String) 16 | 17 | var emailTo: Mail.User { 18 | switch self { 19 | case let .register(emailTo, _), 20 | let .accountActive(emailTo,_), 21 | let .changePwd(emailTo, _): 22 | 23 | return Mail.User(name: "EMgamean", email: emailTo) 24 | } 25 | } 26 | 27 | var subject: String { 28 | switch self { 29 | case .register: 30 | return "注册验证码" 31 | case .changePwd: 32 | return "修改密码验证码" 33 | case .accountActive: 34 | return "激活账号" 35 | } 36 | } 37 | 38 | var text: String { 39 | switch self { 40 | case let .register(_, code): 41 | return "注册验证码是:\(code)" 42 | case let .changePwd(_, code): 43 | return "修改密码的验证码是: \(code)" 44 | case let .accountActive(emailTo: _, url): 45 | return "点击此链接激活账号:\(url)" 46 | } 47 | } 48 | } 49 | 50 | static func sendEmail(_ req: Request, content: EmailSender.Content) throws -> Future { 51 | let promise = req.eventLoop.newPromise(Void.self) 52 | let emailUser = Mail.User(name: "再书", email: "13576051334@163.com") 53 | let emailTo = content.emailTo 54 | let mail = Mail(from: emailUser, to: [emailTo], subject: content.subject, text: content.text) 55 | 56 | let smtp = SMTP(hostname: "smtp.163.com", email: "13576051334@163.com", password: "laijihua12345", port: 465, tlsMode: .requireTLS, domainName:"book.twicebook.top") 57 | DispatchQueue.global().async { 58 | smtp.send(mail) 59 | promise.succeed() 60 | } 61 | return promise.futureResult 62 | } 63 | } 64 | -------------------------------------------------------------------------------- /Sources/App/Utils/Email/RouteCollection+Email.swift: -------------------------------------------------------------------------------- 1 | // 2 | // RouteCollection+Email.swift 3 | // App 4 | // 5 | // Created by laijihua on 2018/8/21. 6 | // 7 | 8 | import Vapor 9 | import Crypto 10 | 11 | extension RouteCollection { 12 | func sendRegisteMail(user: User, request: Request) throws -> Future { 13 | let userId = try user.requireID() 14 | let codeStr = try MD5.hash(Data(Date().description.utf8)).hexEncodedString().lowercased() 15 | let code = ActiveCode(userId: userId, code: codeStr, type: ActiveCode.CodeType.activeAccount) 16 | return code.save(on: request) 17 | .flatMap{ code in 18 | let scheme = request.http.headers.firstValue(name: .host) ?? "" 19 | let linkUrl = "https://\(scheme)/api/users/activate?userId=\(userId)&code=\(code.code)" 20 | guard let email = user.email else { 21 | throw ApiError(code: .emailNotExist) 22 | } 23 | let emailContent = EmailSender.Content.accountActive(emailTo: email, url: linkUrl) 24 | return try self.sendMail(request: request, content: emailContent) 25 | } 26 | } 27 | 28 | func sendMail(request: Request, content: EmailSender.Content) throws -> Future { 29 | return try EmailSender.sendEmail(request, content: content) 30 | } 31 | } 32 | -------------------------------------------------------------------------------- /Sources/App/Utils/Error/ApiError.swift: -------------------------------------------------------------------------------- 1 | // 2 | // ApiError.swift 3 | // App 4 | // 5 | // Created by laijihua on 2018/9/6. 6 | // 7 | 8 | import Foundation 9 | import Vapor 10 | 11 | /// 将错误200, 错误信息由接口体现 12 | struct ApiError: Debuggable { 13 | var identifier: String 14 | var reason: String 15 | var code: Code 16 | 17 | init(code: Code, message: String? = nil) { 18 | self.identifier = "api error: \(code.rawValue)" 19 | self.reason = message ?? code.desc 20 | self.code = code 21 | } 22 | } 23 | 24 | struct ApiErrorContainer: Content { 25 | let status: UInt 26 | let message: String 27 | } 28 | 29 | extension ApiError { 30 | typealias Code = ResponseStatus 31 | } 32 | 33 | extension ApiError: AbortError { 34 | var status: HTTPResponseStatus { 35 | return .ok 36 | } 37 | } 38 | -------------------------------------------------------------------------------- /Sources/App/Utils/Ext/Model+Empty.swift: -------------------------------------------------------------------------------- 1 | // 2 | // Model+Empty.swift 3 | // App 4 | // 5 | // Created by laijihua on 2018/9/3. 6 | // 7 | 8 | import Vapor 9 | 10 | public extension Future where Expectation: OptionalType { 11 | 12 | } 13 | -------------------------------------------------------------------------------- /Sources/App/Utils/Ext/String+Ext.swift: -------------------------------------------------------------------------------- 1 | // 2 | // String+Ext.swift 3 | // App 4 | // 5 | // Created by laijihua on 2018/9/5. 6 | // 7 | 8 | import Foundation 9 | import Random 10 | 11 | extension String { 12 | static func random(length: Int = 20) throws -> String { 13 | let base = "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789" 14 | var randomString: String = "" 15 | for _ in 0.. Data { 24 | guard let data = Data(base64Encoded: self) else { 25 | throw ApiError(code: .base64DecodeError) 26 | } 27 | return data 28 | } 29 | } 30 | 31 | -------------------------------------------------------------------------------- /Sources/App/Utils/Service/Authentication/AuthenticationService.swift: -------------------------------------------------------------------------------- 1 | // 2 | // AuthenticationController.swift 3 | // App 4 | // 5 | // Created by laijihua on 2018/6/16. 6 | // 7 | 8 | import Vapor 9 | import Fluent 10 | import FluentPostgreSQL 11 | import Crypto 12 | 13 | 14 | final class AuthenticationService { 15 | 16 | //MARK: Actions 17 | func authenticationContainer(for refreshToken: RefreshToken.Token, on connection: Request) throws -> Future { 18 | return try existingUser(matchingTokenString: refreshToken, on: connection) 19 | .unwrap(or: ApiError(code: .userNotExist)) 20 | .flatMap { user in 21 | return try self.authenticationContainer(for: user.requireID(), on: connection) 22 | } 23 | } 24 | 25 | func authenticationContainer(for userId: User.ID, on connection: Request) throws -> Future { 26 | return try removeAllTokens(for: userId, on: connection) 27 | .flatMap { _ in 28 | return try map(to: AuthenticationContainer.self, 29 | self.accessToken(for: userId, on: connection), 30 | self.refreshToken(for: userId, on: connection)) { access, refresh in 31 | return AuthenticationContainer(accessToken: access, refreshToken: refresh) 32 | }.makeJson(on: connection) 33 | } 34 | } 35 | 36 | func revokeTokens(forEmail email: String, on connection: DatabaseConnectable) throws -> Future { 37 | return User 38 | .query(on: connection) 39 | .filter(\.email == email) 40 | .first() 41 | .flatMap { user in 42 | guard let user = user else { return Future.map(on: connection) { Void() } } 43 | return try self.removeAllTokens(for: user.requireID(), on: connection) 44 | } 45 | } 46 | } 47 | 48 | //MARK: Helper 49 | private extension AuthenticationService { 50 | 51 | //MARK: Queries 52 | func existingUser(matchingTokenString tokenString: RefreshToken.Token, on connection: DatabaseConnectable) throws -> Future { 53 | return RefreshToken 54 | .query(on: connection) 55 | .filter(\RefreshToken.token == tokenString) 56 | .first() 57 | .unwrap(or: ApiError(code: .refreshTokenNotExist)) 58 | .flatMap { token in 59 | return User 60 | .query(on: connection) 61 | .filter(\.id == token.userId) 62 | .first() 63 | } 64 | } 65 | 66 | func existingUser(matching user: User, on connection: DatabaseConnectable) throws -> Future { 67 | return User 68 | .query(on: connection) 69 | .filter(\.email == user.email) 70 | .first() 71 | } 72 | 73 | //MARK: Cleanup 74 | func removeAllTokens(for userId: User.ID?, on connection: DatabaseConnectable) throws -> Future { 75 | guard let userId = userId else { throw ApiError(code: .userNotExist) } 76 | 77 | let accessTokens = AccessToken 78 | .query(on: connection) 79 | .filter(\.userId == userId) 80 | .delete() 81 | 82 | let refreshToken = RefreshToken 83 | .query(on: connection) 84 | .filter(\.userId == userId) 85 | .delete() 86 | 87 | return map(to: Void.self, accessTokens, refreshToken) { _, _ in Void() } 88 | } 89 | 90 | //MARK: Generation 91 | func accessToken(for userId: User.ID, on connection: DatabaseConnectable) throws -> Future { 92 | return try AccessToken(userId: userId) 93 | .save(on: connection) 94 | } 95 | 96 | func refreshToken(for userId: User.ID, on connection: DatabaseConnectable) throws -> Future { 97 | return try RefreshToken(userId: userId) 98 | .save(on: connection) 99 | } 100 | 101 | func accessToken(for refreshToken: RefreshToken, on connection: DatabaseConnectable) throws -> Future { 102 | return try AccessToken(userId: refreshToken.userId) 103 | .save(on: connection) 104 | } 105 | } 106 | -------------------------------------------------------------------------------- /Sources/App/Utils/Service/Notify/NotifyService.swift: -------------------------------------------------------------------------------- 1 | // 2 | // NotifyService.swift 3 | // App 4 | // 5 | // Created by laijihua on 2018/8/27. 6 | // 7 | 8 | import Foundation 9 | import Vapor 10 | 11 | import Fluent 12 | import FluentPostgreSQL 13 | import Pagination 14 | 15 | final class NotifyService { 16 | 17 | /// 往Notify表中插入一条公告记录 18 | func createAnnouce(content: String, sender: User.ID, on reqeust: Request) throws -> Future { 19 | let notify = Notify(type: Notify.announce, target: nil, targetType: nil, action: nil, sender: sender, content: content) 20 | return try notify.create(on: reqeust).makeJson(on: reqeust) 21 | } 22 | 23 | /// 往Notify表中插入一条提醒记录 24 | func createRemind(target: Int, targetType: String, action: String, sender: User.ID, content: String, on reqeust: Request) throws -> Future { 25 | let notify = Notify(type: Notify.remind, target: target, targetType: targetType, action: action, sender: sender, content: content) 26 | return try notify.create(on: reqeust).makeJson(on: reqeust) 27 | } 28 | 29 | /// 往Notify表中插入一条信息记录 30 | /// 往UserNotify表中插入一条记录,并关联新建的Notify 31 | func createMessage(content: String, sender: User.ID, receiver: User.ID, on reqeust: Request) throws -> Future { 32 | let notify = Notify(type: Notify.message, target: nil, targetType: nil, action: nil, sender: sender, content: content) 33 | return notify 34 | .create(on: reqeust) 35 | .flatMap { (noti) in 36 | let notid = try noti.requireID() 37 | let userNotify = UserNotify(userId: sender, notifyId: notid, notifyType: noti.type) 38 | let _ = userNotify.create(on: reqeust) 39 | return try reqeust.makeJson(noti) 40 | } 41 | } 42 | 43 | /// 从UserNotify中获取最近的一条公告信息的创建时间 44 | /// 用lastTime作为过滤条件,查询Notify的公告信息 45 | /// 新建UserNotify并关联查询出来的公告信息 46 | func pullAnnounce(userId: User.ID, on request: Request) throws -> Future { 47 | return UserNotify 48 | .query(on: request) 49 | .filter(\.userId == userId) 50 | .filter(\UserNotify.notifyType == Notify.announce) 51 | .sort(\UserNotify.createdAt, .descending) 52 | .first() 53 | .flatMap { usernoti in 54 | guard let existUsernoti = usernoti, 55 | let lastTime = existUsernoti.createdAt else { 56 | return try request.makeJson() 57 | } 58 | 59 | return Notify 60 | .query(on: request) 61 | .filter(\.type == Notify.announce) 62 | .filter(\.createdAt > lastTime) 63 | .all() 64 | .flatMap{ noties in 65 | noties.forEach({ (notify) in 66 | guard let notiyId = notify.id else {return} 67 | let userNoti = UserNotify(userId: userId, notifyId: notiyId, notifyType: notify.type) 68 | _ = userNoti.create(on: request) 69 | }) 70 | return try request.makeJson(noties) 71 | } 72 | } 73 | 74 | } 75 | 76 | /// 查询用户的订阅表,得到用户的一系列订阅记录 77 | /// 通过每一条的订阅记录的target、targetType、action、createdAt去查询Notify表,获取订阅的Notify记录。(注意订阅时间必须早于提醒创建时间) 78 | /// 查询用户的配置文件SubscriptionConfig,如果没有则使用默认的配置DefaultSubscriptionConfig 79 | /// 使用订阅配置,过滤查询出来的Notify 80 | /// 使用过滤好的Notify作为关联新建UserNotify 81 | func pullRemind(userId: User.ID, on request: Request) throws -> Future { 82 | return Subscription 83 | .query(on: request) 84 | .filter(\.userId == userId) 85 | .all() 86 | .flatMap { subs in 87 | // 二维数组 88 | let noties = subs.compactMap { sub in 89 | return Notify 90 | .query(on: request) 91 | .filter(\Notify.type == sub.target) 92 | .filter(\Notify.targetType == sub.targetType) 93 | .filter(\Notify.action == sub.action) 94 | .filter(\Notify.createdAt > sub.createdAt) 95 | .all() 96 | } 97 | 98 | var notifyArr = [Notify]() 99 | noties.forEach({ (notifyF) in 100 | let _ = notifyF.flatMap { notis -> EventLoopFuture in 101 | notis.forEach({ (notify) in 102 | guard let notiyId = notify.id else {return} 103 | notifyArr.append(notify) 104 | let userNoti = UserNotify(userId: userId, notifyId: notiyId, notifyType: notify.type) 105 | let _ = userNoti.create(on: request) 106 | }) 107 | return try request.makeJson() 108 | } 109 | }) 110 | return try request.makeJson(notifyArr) 111 | } 112 | } 113 | 114 | 115 | /// 通过reason,查询reasonAction,获取对应的动作组:actions 116 | /// 遍历动作组,每一个动作新建一则Subscription记录 117 | func subscribe(user: User.ID, target: Int, targetType: String, reason: String, on reqeust: Request) throws -> Future{ 118 | let reasonAction: [String: [String]] = [ 119 | "create_topic": ["like", "comment"], 120 | "like_replay": ["comment"] 121 | ] 122 | guard let actions = reasonAction[reason] else {throw ApiError(code: .resonNotExist)} 123 | actions.forEach { action in 124 | let subscribe = Subscription(target: target, targetType: targetType, userId: user, action: action) 125 | let _ = subscribe.create(on: reqeust).map(to: Void.self, { _ in return}) 126 | } 127 | return try reqeust.makeJson() 128 | } 129 | 130 | //// 删除user、target、targetType对应的一则或多则记录 131 | func cancelSubscription(userId: User.ID, target: Int, targetType: String, on reqeust: Request) throws -> Future { 132 | return try Subscription.query(on: reqeust) 133 | .filter(\.userId == userId) 134 | .filter(\.target == target) 135 | .filter(\.targetType == targetType) 136 | .delete() 137 | .makeJson(request: reqeust) 138 | } 139 | 140 | //// 查询SubscriptionConfig表,获取用户的订阅配置 141 | func getSubscriptionConfig(userId: User.ID, on reqeust: Request) throws -> Future { 142 | return try Subscription.query(on: reqeust) 143 | .filter(\.userId == userId) 144 | .all() 145 | .makeJson(on: reqeust) 146 | } 147 | 148 | /// 获取用户的消息列表 149 | func getUserNotify(userId: User.ID, on reqeust: Request) throws -> Future{ 150 | return try UserNotify 151 | .query(on: reqeust) 152 | .filter(\UserNotify.userId == userId) 153 | .sort(\UserNotify.createdAt) 154 | .paginate(for: reqeust) 155 | .map {$0.response()} 156 | .makeJson(on: reqeust) 157 | } 158 | 159 | /// 更新指定的notify,把isRead属性设置为true 160 | func read(user: User, notifyIds:[Notify.ID], on reqeust: Request) throws -> Future{ 161 | return UserNotify 162 | .query(on: reqeust) 163 | .filter(\UserNotify.notifyId ~~ notifyIds) 164 | .update(\UserNotify.isRead, to: true) 165 | .all() 166 | .map(to: Void.self, { _ in Void()}) 167 | } 168 | } 169 | -------------------------------------------------------------------------------- /Sources/App/Utils/Validation/Validator+Password.swift: -------------------------------------------------------------------------------- 1 | // 2 | // Validator+Password.swift 3 | // App 4 | // 5 | // Created by laijihua on 2018/6/16. 6 | // 7 | 8 | import Foundation 9 | import Validation 10 | 11 | fileprivate struct PasswordValidator: ValidatorType { 12 | private var asciiValidator: Validator = .ascii 13 | private var lengthValidator: Validator = Validator.count(8...) 14 | private var numberValidator: Validator = Validator.containsCharacterFrom(set: .decimalDigits) 15 | private var lowercaseValidator: Validator = Validator.containsCharacterFrom(set: .lowercaseLetters) 16 | private var uppercaseValidator: Validator = Validator.containsCharacterFrom(set: .uppercaseLetters) 17 | 18 | var validatorReadable: String { return "a valid password of 8 or more ASCII characters" } 19 | 20 | init() {} 21 | 22 | func validate(_ s: String) throws { 23 | try asciiValidator.validate(s) 24 | try lengthValidator.validate(s) 25 | try numberValidator.validate(s) 26 | try lowercaseValidator.validate(s) 27 | try uppercaseValidator.validate(s) 28 | } 29 | } 30 | 31 | fileprivate struct ContainsCharacterFromSetValidator: ValidatorType { 32 | private let characterSet: CharacterSet 33 | var validatorReadable: String { return "a valid string consisting of at least one character from a given set"} 34 | 35 | init(characterSet: CharacterSet) { 36 | self.characterSet = characterSet 37 | } 38 | 39 | func validate(_ data: String) throws { 40 | guard let _ = data.rangeOfCharacter(from: characterSet) else { 41 | throw BasicValidationError("does not contain a member of character set: \(characterSet.description)") 42 | } 43 | } 44 | } 45 | 46 | extension Validator where T == String { 47 | static var password: Validator { 48 | return PasswordValidator().validator() 49 | } 50 | 51 | static func containsCharacterFrom(set: CharacterSet) -> Validator { 52 | return ContainsCharacterFromSetValidator(characterSet: set).validator() 53 | } 54 | } 55 | -------------------------------------------------------------------------------- /Sources/Run/main.swift: -------------------------------------------------------------------------------- 1 | import App 2 | 3 | try app(.detect()).run() 4 | -------------------------------------------------------------------------------- /Tests/.gitkeep: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/OHeroJ/BookVapor/b97b43d545d2d35f48006400814860e5d9fd0385/Tests/.gitkeep -------------------------------------------------------------------------------- /Tests/AppTests/AppTests.swift: -------------------------------------------------------------------------------- 1 | import App 2 | import Dispatch 3 | import XCTest 4 | 5 | final class AppTests: XCTestCase { 6 | func testNothing() throws { 7 | XCTAssert(true) 8 | } 9 | 10 | static let allTests = [ 11 | ("testNothing", testNothing) 12 | ] 13 | } 14 | -------------------------------------------------------------------------------- /Tests/LinuxMain.swift: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/OHeroJ/BookVapor/b97b43d545d2d35f48006400814860e5d9fd0385/Tests/LinuxMain.swift -------------------------------------------------------------------------------- /api-template.paw: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/OHeroJ/BookVapor/b97b43d545d2d35f48006400814860e5d9fd0385/api-template.paw -------------------------------------------------------------------------------- /circle.yml: -------------------------------------------------------------------------------- 1 | version: 2 2 | 3 | jobs: 4 | macos: 5 | macos: 6 | xcode: "9.2" 7 | steps: 8 | - checkout 9 | - run: swift build 10 | - run: swift test 11 | 12 | linux: 13 | docker: 14 | - image: codevapor/swift:4.1 15 | steps: 16 | - checkout 17 | - run: 18 | name: Compile code 19 | command: swift build 20 | - run: 21 | name: Run unit tests 22 | command: swift test 23 | - run: 24 | name: Compile code with optimizations 25 | command: swift build -c release 26 | 27 | swiftlint: 28 | docker: 29 | - image: norionomura/swiftlint 30 | steps: 31 | - checkout 32 | - run: swiftlint 33 | workflows: 34 | version: 2 35 | tests: 36 | jobs: 37 | - linux 38 | - swiftlint 39 | # - macos 40 | 41 | nightly: 42 | triggers: 43 | - schedule: 44 | cron: "0 0 * * *" 45 | filters: 46 | branches: 47 | only: 48 | - master 49 | jobs: 50 | - linux 51 | # - macos 52 | 53 | -------------------------------------------------------------------------------- /cloud.yml: -------------------------------------------------------------------------------- 1 | type: "vapor" 2 | swift_version: "4.1.0" 3 | run_parameters: "serve --port 8080 --hostname 0.0.0.0" 4 | -------------------------------------------------------------------------------- /license: -------------------------------------------------------------------------------- 1 | The MIT License (MIT) 2 | 3 | Copyright (c) 2017 Qutheory, LLC 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /slide2.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/OHeroJ/BookVapor/b97b43d545d2d35f48006400814860e5d9fd0385/slide2.gif --------------------------------------------------------------------------------