├── .gitignore ├── Assets ├── Calatrava.png └── Calatrava.psd ├── CODE_OF_CONDUCT.md ├── CONTRIBUTING.md ├── ISSUE_TEMPLATE.md ├── LICENSE ├── Package.swift ├── README.md ├── Script ├── build_debug.sh ├── build_release.sh ├── clean_all.sh ├── clean_build.sh ├── env │ └── ubuntu_16_04.sh ├── run_debug.sh ├── run_release.sh └── xcodeproj.sh ├── Source └── Calatrava │ ├── Base │ ├── AppConfig.swift │ ├── AppDelegate.swift │ ├── ConfigModel.swift │ ├── EventHooks.swift │ ├── Extension.swift │ ├── FooterBarView.swift │ ├── Global.swift │ ├── MonitorHandle.swift │ └── NavigationBarView.swift │ ├── Error │ ├── ErrorNotFoundView.swift │ └── ErrorNotSupportView.swift │ ├── Modules │ ├── About │ │ ├── AboutView.swift │ │ ├── MessageHandle.swift │ │ └── MessageModel.swift │ ├── Base │ │ ├── Statistics │ │ │ ├── StatisticsManager.swift │ │ │ └── VisitStatisticsModel.swift │ │ └── VerificationCode │ │ │ ├── VerificationHandle.swift │ │ │ └── VerificationManager.swift │ ├── Bilibili │ │ ├── BilibiliFeedModel.swift │ │ ├── BilibiliListModel.swift │ │ └── BilibiliListView.swift │ ├── Corpus │ │ ├── CorpusListView.swift │ │ ├── CorpusModel.swift │ │ ├── CorpusPostsCommentHandle.swift │ │ ├── CorpusPostsCommentModel.swift │ │ ├── CorpusPostsListView.swift │ │ ├── CorpusPostsLoveHandle.swift │ │ ├── CorpusPostsModel.swift │ │ ├── CorpusPostsTextView.swift │ │ └── CorpusPostsView.swift │ ├── Index │ │ └── IndexView.swift │ ├── Instagram │ │ ├── InstagramCurlHandle.swift │ │ ├── InstagramFeedModel.swift │ │ ├── InstagramListView.swift │ │ ├── InstagramNet.swift │ │ └── InstagramUserModel.swift │ ├── Modules │ │ ├── ModuleListView.swift │ │ └── ModuleModel.swift │ ├── Posts │ │ ├── PostsArchiveView.swift │ │ ├── PostsCommentHandle.swift │ │ ├── PostsCommentModel.swift │ │ ├── PostsHistoryModel.swift │ │ ├── PostsListView.swift │ │ ├── PostsLoveHandle.swift │ │ ├── PostsModel.swift │ │ ├── PostsOldHandle.swift │ │ ├── PostsSearchView.swift │ │ ├── PostsTagModel.swift │ │ ├── PostsTextView.swift │ │ └── PostsView.swift │ ├── Project │ │ ├── ProjectListView.swift │ │ └── ProjectModel.swift │ ├── Report │ │ ├── ReportCache.swift │ │ ├── ReportDailyModel.swift │ │ ├── ReportDailyView.swift │ │ ├── ReportOnlyDateModel.swift │ │ └── ReportTotalView.swift │ ├── Search │ │ ├── SearchResultBilibiliView.swift │ │ ├── SearchResultCorpusModel.swift │ │ ├── SearchResultInstagramView.swift │ │ ├── SearchResultListView.swift │ │ ├── SearchResultPostsView.swift │ │ ├── SearchResultProjectView.swift │ │ └── SearchView.swift │ ├── Update │ │ ├── UpdateBilibiliModel.swift │ │ ├── UpdateCorpusModel.swift │ │ ├── UpdateInstagramModel.swift │ │ ├── UpdateListView.swift │ │ ├── UpdateModel.swift │ │ ├── UpdatePostsView.swift │ │ └── UpdateProjectView.swift │ └── VPSSSCURL │ │ └── VPSSSCURL.swift │ ├── Plugin │ ├── DailyCleanPlugin.swift │ ├── InstagramTimerPlugin.swift │ ├── NotFoundFilterPlugin.swift │ ├── ReportGeneratePlugin.swift │ └── ReportUpdatePlugin.swift │ └── main.swift └── Workspace ├── .keep ├── runtime ├── .keep └── config.json ├── static ├── .keep ├── css │ ├── bootstrap.min.css │ ├── buttons.css │ ├── font-awesome.min.css │ ├── height.css │ ├── markdown.css │ └── me.css ├── fonts │ ├── FontAwesome.otf │ ├── fontawesome-webfont.eot │ ├── fontawesome-webfont.svg │ ├── fontawesome-webfont.ttf │ ├── fontawesome-webfont.woff │ └── fontawesome-webfont.woff2 ├── img │ ├── .keep │ ├── Calatrava.png │ ├── background.jpg │ ├── background_index.jpg │ ├── background_me.jpg │ ├── bilibili │ │ └── head.jpg │ ├── corpus │ │ └── .keep │ ├── donate-alipay.jpg │ ├── donate-wechat.jpg │ ├── favicon.png │ ├── head_128.png │ ├── instagram │ │ └── .keep │ └── postman_128.png └── js │ ├── bootstrap.min.js │ ├── buttons.js │ ├── echarts.min.js │ ├── jquery.min.js │ └── me.js └── templates ├── .keep ├── about.html ├── bilibili_list.html ├── corpus └── .keep ├── corpus_list.html ├── corpus_posts.html ├── corpus_posts_list.html ├── error_404.html ├── error_notsupport.html ├── footer_bar.html ├── index.html ├── instagram_list.html ├── modules.html ├── navigation_bar.html ├── posts.html ├── posts ├── .keep └── 17001.html ├── posts_archive.html ├── posts_list.html ├── posts_search.html ├── project.html ├── report_daily.html ├── report_total.html ├── search.html ├── search_result.html ├── search_result_bilibili_cell.html ├── search_result_corpus_cell.html ├── search_result_instagram_cell.html ├── search_result_posts_cell.html ├── search_result_project_cell.html ├── update.html ├── update_bilibili_cell.html ├── update_corpus_cell.html ├── update_instagram_cell.html ├── update_posts_cell.html └── update_project_cell.html /.gitignore: -------------------------------------------------------------------------------- 1 | # Xcode 2 | # 3 | # gitignore contributors: remember to update Global/Xcode.gitignore, Objective-C.gitignore & Swift.gitignore 4 | 5 | ## OS 6 | .DS_Store 7 | .DS_Store? 8 | *.swp 9 | ._* 10 | .Spotlight-V100 11 | .Trashes 12 | Icon? 13 | ehthumbs.db 14 | Thumbs.db 15 | 16 | ## Build generated 17 | build/ 18 | DerivedData/ 19 | 20 | ## Various settings 21 | *.pbxuser 22 | !default.pbxuser 23 | *.mode1v3 24 | !default.mode1v3 25 | *.mode2v3 26 | !default.mode2v3 27 | *.perspectivev3 28 | !default.perspectivev3 29 | xcuserdata/ 30 | 31 | ## Other 32 | *.moved-aside 33 | *.xccheckout 34 | *.xcscmblueprint 35 | 36 | ## Obj-C/Swift specific 37 | *.hmap 38 | *.ipa 39 | *.dSYM.zip 40 | *.dSYM 41 | 42 | ## Playgrounds 43 | timeline.xctimeline 44 | playground.xcworkspace 45 | 46 | # Swift Package Manager 47 | # 48 | # Add this line if you want to avoid checking in source code from Swift Package Manager dependencies. 49 | # Packages/ 50 | # Package.pins 51 | .build/ 52 | 53 | # CocoaPods 54 | # 55 | # We recommend against adding the Pods directory to your .gitignore. However 56 | # you should judge for yourself, the pros and cons are mentioned at: 57 | # https://guides.cocoapods.org/using/using-cocoapods.html#should-i-check-the-pods-directory-into-source-control 58 | # 59 | # Pods/ 60 | 61 | # Carthage 62 | # 63 | # Add this line if you want to avoid checking in source code from Carthage dependencies. 64 | # Carthage/Checkouts 65 | 66 | Carthage/Build 67 | 68 | # fastlane 69 | # 70 | # It is recommended to not store the screenshots in the git repo. Instead, use fastlane to re-generate the 71 | # screenshots whenever they are needed. 72 | # For more information about the recommended setup visit: 73 | # https://docs.fastlane.tools/best-practices/source-control/#source-control 74 | 75 | fastlane/report.xml 76 | fastlane/Preview.html 77 | fastlane/screenshots 78 | fastlane/test_output 79 | Package.pins 80 | Calatrava.xcodeproj 81 | Workspace/runtime/calatrava.log 82 | Workspace/static/resume.pdf 83 | /Package.resolved 84 | /Workspace/static/_md 85 | /Workspace/static/img/post 86 | /Workspace/templates/posts/*.html 87 | /Workspace/templates/corpus/*.html 88 | /Workspace/static/_corpus 89 | /Workspace/static/_posts 90 | /Workspace/static/img/corpus/*.png 91 | /Workspace/static/img/corpus/*.mp4 92 | /Workspace/static/img/corpus/*.jpg 93 | /Workspace/static/img/corpus/*.JPG 94 | /Workspace/static/img/corpus/*.jpeg 95 | /Workspace/static/img/corpus/*.gif 96 | /Workspace/static/img/corpus/*.mov 97 | /Workspace/static/img/instagram/*.png 98 | /Workspace/static/img/bilibili/*.jpg 99 | /Workspace/static/img/bilibili/*.png 100 | /Workspace/static/*.md 101 | /Workspace/templates/_posts 102 | /Workspace/templates/_corpus 103 | /ins.json 104 | -------------------------------------------------------------------------------- /Assets/Calatrava.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/enums/Calatrava/bd02ae3dbd4ed8e86dda479534e9583b1c56b131/Assets/Calatrava.png -------------------------------------------------------------------------------- /Assets/Calatrava.psd: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/enums/Calatrava/bd02ae3dbd4ed8e86dda479534e9583b1c56b131/Assets/Calatrava.psd -------------------------------------------------------------------------------- /CODE_OF_CONDUCT.md: -------------------------------------------------------------------------------- 1 | # Contributor Covenant Code of Conduct 2 | 3 | ## Our Pledge 4 | 5 | In the interest of fostering an open and welcoming environment, we as contributors and maintainers pledge to making participation in our project and our community a harassment-free experience for everyone, regardless of age, body size, disability, ethnicity, gender identity and expression, level of experience, nationality, personal appearance, race, religion, or sexual identity and orientation. 6 | 7 | ## Our Standards 8 | 9 | Examples of behavior that contributes to creating a positive environment include: 10 | 11 | * Using welcoming and inclusive language 12 | * Being respectful of differing viewpoints and experiences 13 | * Gracefully accepting constructive criticism 14 | * Focusing on what is best for the community 15 | * Showing empathy towards other community members 16 | 17 | Examples of unacceptable behavior by participants include: 18 | 19 | * The use of sexualized language or imagery and unwelcome sexual attention or advances 20 | * Trolling, insulting/derogatory comments, and personal or political attacks 21 | * Public or private harassment 22 | * Publishing others' private information, such as a physical or electronic address, without explicit permission 23 | * Other conduct which could reasonably be considered inappropriate in a professional setting 24 | 25 | ## Our Responsibilities 26 | 27 | Project maintainers are responsible for clarifying the standards of acceptable behavior and are expected to take appropriate and fair corrective action in response to any instances of unacceptable behavior. 28 | 29 | Project maintainers have the right and responsibility to remove, edit, or reject comments, commits, code, wiki edits, issues, and other contributions that are not aligned to this Code of Conduct, or to ban temporarily or permanently any contributor for other behaviors that they deem inappropriate, threatening, offensive, or harmful. 30 | 31 | ## Scope 32 | 33 | This Code of Conduct applies both within project spaces and in public spaces when an individual is representing the project or its community. Examples of representing a project or community include using an official project e-mail address, posting via an official social media account, or acting as an appointed representative at an online or offline event. Representation of a project may be further defined and clarified by project maintainers. 34 | 35 | ## Enforcement 36 | 37 | Instances of abusive, harassing, or otherwise unacceptable behavior may be reported by contacting the project team at enum@enumsblog.com. The project team will review and investigate all complaints, and will respond in a way that it deems appropriate to the circumstances. The project team is obligated to maintain confidentiality with regard to the reporter of an incident. Further details of specific enforcement policies may be posted separately. 38 | 39 | Project maintainers who do not follow or enforce the Code of Conduct in good faith may face temporary or permanent repercussions as determined by other members of the project's leadership. 40 | 41 | ## Attribution 42 | 43 | This Code of Conduct is adapted from the [Contributor Covenant][homepage], version 1.4, available at [http://contributor-covenant.org/version/1/4][version] 44 | 45 | [homepage]: http://contributor-covenant.org 46 | [version]: http://contributor-covenant.org/version/1/4/ 47 | -------------------------------------------------------------------------------- /CONTRIBUTING.md: -------------------------------------------------------------------------------- 1 | # Contributing Guidelines 2 | 3 | This document contains information and guidelines about contributing to this project. 4 | Please read it before you start participating. 5 | 6 | **Topics** 7 | 8 | * [Asking Questions](#asking-questions) 9 | * [Reporting Issues](#reporting-issues) 10 | * [Developers Certificate of Origin](#developers-certificate-of-origin) 11 | 12 | ## Asking Questions 13 | 14 | We don't use GitHub as a support forum. 15 | For any usage questions that are not specific to the project itself, 16 | please ask on [Stack Overflow](https://stackoverflow.com) instead. 17 | By doing so, you'll be more likely to quickly solve your problem, 18 | and you'll allow anyone else with the same question to find the answer. 19 | This also allows maintainers to focus on improving the project for others. 20 | 21 | ## Reporting Issues 22 | 23 | A great way to contribute to the project 24 | is to send a detailed issue when you encounter an problem. 25 | We always appreciate a well-written, thorough bug report. 26 | 27 | Check that the project issues database 28 | doesn't already include that problem or suggestion before submitting an issue. 29 | If you find a match, add a quick "+1" or "I have this problem too." 30 | Doing this helps prioritize the most common problems and requests. 31 | 32 | When reporting issues, please include the following: 33 | 34 | * The version of Xcode you're using 35 | * The version of iOS or macOS you're targeting 36 | * The full output of any stack trace or compiler error 37 | * A code snippet that reproduces the described behavior, if applicable 38 | * Any other details that would be useful in understanding the problem 39 | 40 | This information will help us review and fix your issue faster. 41 | 42 | ## Developer's Certificate of Origin 43 | 44 | By making a contribution to this project, I certify that: 45 | 46 | - (a) The contribution was created in whole or in part by me and I 47 | have the right to submit it under the open source license 48 | indicated in the file; or 49 | 50 | - (b) The contribution is based upon previous work that, to the best 51 | of my knowledge, is covered under an appropriate open source 52 | license and I have the right under that license to submit that 53 | work with modifications, whether created in whole or in part 54 | by me, under the same open source license (unless I am 55 | permitted to submit under a different license), as indicated 56 | in the file; or 57 | 58 | - (c) The contribution was provided directly to me by some other 59 | person who certified (a), (b) or (c) and I have not modified 60 | it. 61 | 62 | - (d) I understand and agree that this project and the contribution 63 | are public and that a record of the contribution (including all 64 | personal information I submit with it, including my sign-off) is 65 | maintained indefinitely and may be redistributed consistent with 66 | this project or the open source license(s) involved. 67 | 68 | --- 69 | 70 | *Some of the ideas and wording for the statements above were based on work by the [Alamofire](https://github.com/Alamofire/Alamofire/blob/master/CONTRIBUTING.md) communities. We commend them for their efforts to facilitate collaboration in their projects.* 71 | -------------------------------------------------------------------------------- /ISSUE_TEMPLATE.md: -------------------------------------------------------------------------------- 1 | ### Check List 2 | 3 | Thanks for considering to open an issue. Before you submit your issue, please confirm these boxes are checked. 4 | 5 | - [ ] I have read the [README.md](https://github.com/enums/Calatrava/blob/master/README.md), but there is no information I need. 6 | - [ ] I have searched in [existing issues](https://github.com/enums/Calatrava/issues?utf8=%E2%9C%93&q=is%3Aissue), but did find a same one. 7 | 8 | ### Issue Description 9 | 10 | #### Description 11 | 12 | [Tell us about the issue] 13 | 14 | #### Reproduce 15 | 16 | [The steps to reproduce this issue. What are the parameters, where did you put your code, etc.] 17 | 18 | #### Other Comment 19 | 20 | [Add anything else here] 21 | -------------------------------------------------------------------------------- /Package.swift: -------------------------------------------------------------------------------- 1 | // swift-tools-version:4.2 2 | // The swift-tools-version declares the minimum version of Swift required to build this package. 3 | 4 | import PackageDescription 5 | 6 | let package = Package( 7 | name: "Calatrava", 8 | products: [ 9 | .executable( 10 | name: "Calatrava", 11 | targets: ["Calatrava"]), 12 | ], 13 | dependencies: [ 14 | .package(url:"https://github.com/enums/Pjango.git" , from: "2.1.0"), 15 | .package(url:"https://github.com/enums/Pjango-MySQL.git" , from: "2.1.0"), 16 | .package(url:"https://github.com/enums/Pjango-Postman.git" , from: "2.2.0"), 17 | ], 18 | targets: [ 19 | .target( 20 | name: "Calatrava", 21 | dependencies: [ 22 | "Pjango", 23 | "PjangoMySQL", 24 | "PjangoPostman", 25 | ]) 26 | ] 27 | ) 28 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | ![](/Assets/Calatrava.png) 2 | 3 |

4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 |

13 | 14 | 基于 [Pjango](https://github.com/enums/pjango) 的主题博客。使用 `Swift4.x` 开发,运行在 `macOS` 和 `Linux` 上。使用 Pjango,可以快速搭建你的网站。只需集成你需要的组件,而无需关心服务器和数据库具体的实现细节。 15 | 16 | Calatrava 则是对其的高度集成,它是已经完成的博客系统。使用 Calatrava,你可以迅速在你的云服务器上运行起你的博客,直接开始写作。 17 | 18 | ## 更新 19 | 20 | 注意:个人博客最新的更新已转为私有仓库,不再开源,该仓库不再维护。 21 | 22 | 2020-07-18 更新: 23 | 由于 Swift 实现的改变,在高版本的 Swift 下逻辑已经不 work 了,不要再使用这个项目了。 24 | 如有类似需求可以参考我的另一个项目:[Heze](https://github.com/enums/Heze),这个项目和 Pjango 非常相似。 25 | 26 | ## 组分 27 | 28 | 项目包含: 29 | 30 | - 前端样式和网页模板。 31 | - 基于 Pjango 的博客服务器程序。 32 | - 基于 Pjango 的可扩展插件集成。 33 | 34 | ## 安装 35 | 36 | - 克隆此仓库。 37 | - 修改 `AppDelegate.swift` 中的 `PJANGO_WORKSPACE` 路径。 38 | - 若遇到无法写入日志的错误,请手动新建日志的目录,路径同样位于 `AppDelegate.swift` 中。 39 | - 按照下文中 `准备工作` 一节中修改 `hosts` 添加本地解析。 40 | - macOS:使用下面的命令生成 Xcode 工程进行编译: 41 | 42 | ```bash 43 | $ swift package generate-xcodeproj 44 | ``` 45 | 46 | - Linux: 使用 `Swift Package Manager` 编译: 47 | 48 | ```bash 49 | $ swift build 50 | ``` 51 | 52 | - macOS 中请使用 root 用户执行,否则无法使用 80 端口。 53 | - 打开浏览器,输入 `calatravatest.com` 进入博客。 54 | 55 | ## 效果预览 56 | 57 | 参考: [我的博客](http://enumsblog.com) 58 | 59 | 如果你喜欢这个效果,可以以 Star 的方式支持我。 60 | 61 | ## 使用引导 62 | 63 | 基于 [Pjango](https://github.com/enums/Pjango) 实现,Model 和 View 分离,具体请参考 Pjango 的文档。 64 | 65 | 默认的设计下,Model 都有一分钟的内存缓存,可以修改各 Model 的 `cacheTime` 参数。 66 | 67 | 更多设置可以直接看源码后修改对应的属性。 68 | 69 | ### 1. WorkSpace 结构 70 | 71 | 默认的 WorkSpace 包含以下目录: 72 | 73 | - static:静态资源目录,发布后可通过 url 直接访问到静态资源。 74 | - templates:网页模板目录,包含未渲染的网页模板以及博文内容。 75 | - runtime:运行时目录,默认情况目录下仅产生日志文件。 76 | - filedb:文件数据驱动,默认情况下博客不关联任何数据库,数据以 json 文件方式存放在该目录下。 77 | 78 | ### 2. 准备工作 79 | 80 | 修改 hosts,添加本地解析以方便调试。 81 | 82 | ``` 83 | 127.0.0.1 www.calatravatest.com 84 | 127.0.0.1 calatravatest.com 85 | 127.0.0.1 posts.calatravatest.com 86 | 127.0.0.1 project.calatravatest.com 87 | ``` 88 | 89 | `Global.swift` 和 `AppDelegate.swift` 有部分参数需要根据实际情况修改,如路径和域名等。 90 | 91 | ### 3. 修改博客参数 92 | 93 | 在默认使用文件数据驱动的情况下,在 `Workspace/filedb/default/LocalConfig.json` 修改对应参数即可。 94 | 95 | ### 4. 发布博文 96 | 97 | 以下为 macOS 平台下使用 MacDown 编写博文的步骤: 98 | 99 | #### 4.1 编写博文内容 100 | 101 | 使用 [MacDown](http://macdown.uranusjr.com) 编写内容并导出成 HTML。 102 | 103 | #### 4.2 转换成可用的 HTML 104 | 105 | 克隆 [这个](https://github.com/enums/Calatrava-MacDown-Html-Transformation) 仓库,编译后二进制程序可自行保存。 106 | 107 | 运行这个程序,参数为输入文件和输出文件路径,剔除导出的 HTML 中的样式部分。 108 | 109 | #### 4.3 推送模板和配图到对应目录 110 | 111 | 将博文 HTML 推送至 `Workspace/templates/posts` 目录下。 112 | 113 | 将配图等静态资源推送至 `Workspace/static` 目录下。 114 | 115 | 编写时请注意静态资源的路径。本地编写时推荐将 `.md` 文稿临时放在 `static`目录下,以配合写作时的静态资源路径和发布以后的 url 访问地址一直。 116 | 117 | #### 4.4 在数据库中插入博文记录 118 | 119 | 在默认使用文件数据驱动的情况下,在 `Workspace/filedb/default/posts.json` 文件中加入相应记录即可。 120 | 121 | 博文模型: 122 | 123 | ```swift 124 | var pid = PCDataBaseField.init(name: "PID", type: .int) 125 | var title = PCDataBaseField.init(name: "TITLE", type: .string, length: 64) 126 | var date = PCDataBaseField.init(name: "DATE", type: .string, length: 16) 127 | var read = PCDataBaseField.init(name: "READ", type: .int) 128 | var love = PCDataBaseField.init(name: "LOVE", type: .int) 129 | var tag = PCDataBaseField.init(name: "TAG", type: .string, length: 64) 130 | ``` 131 | 132 | 因此,按照模型的结构,json 结构: 133 | 134 | ```json 135 | { 136 | "objs" : [ 137 | [ 138 | "id,记录的额外字段,任意整数,不可重复", 139 | "PID,博文id,整数,和博文 HTML 文件名一直。如PID = 17001,对应 HTML 文件为 17001.html", 140 | "TITLE,博文标题", 141 | "DATE,博文发布日期,YYYY-MM-dd HH:mm:ss", 142 | "READ,阅读数量", 143 | "LOVE,点赞数量", 144 | "TAG,标签列表,以|分隔", 145 | ] 146 | ] 147 | } 148 | ``` 149 | 150 | ### 5. 添加标签、项目和建设历史等 151 | 152 | 根据上面提到的规则,根据各自的模型代码直接修改对应的 json 文件记录即可。 153 | 154 | 如果改用[Pjango-MySQL](https://github.com/enums/Pjango-MySQL)组件,直接改数据库记录即可。 155 | 156 | ### 6. 修改网页 157 | 158 | 部分页面内容是直接渲染静态网页的,如 `about.html` 中的关于内容。因此这部分内容可以直接修改 HTML。 159 | 160 | ## 联系我 161 | 162 | 发邮件给我: [i@yuusann.com](mailto:i@yuusann.com) 163 | 164 | ## 协议 165 | 166 | ![](https://www.gnu.org/graphics/agplv3-155x51.png) 167 | 168 | Calatrava 基于 AGPL-3.0 协议进行分发和使用,更多信息参见协议文件。 169 | -------------------------------------------------------------------------------- /Script/build_debug.sh: -------------------------------------------------------------------------------- 1 | cd ../ 2 | swift build -------------------------------------------------------------------------------- /Script/build_release.sh: -------------------------------------------------------------------------------- 1 | cd ../ 2 | swift build -c release -------------------------------------------------------------------------------- /Script/clean_all.sh: -------------------------------------------------------------------------------- 1 | cd ../ 2 | rm -rf .build 3 | rm Package.resolved 4 | -------------------------------------------------------------------------------- /Script/clean_build.sh: -------------------------------------------------------------------------------- 1 | cd ../ 2 | swift package clean -------------------------------------------------------------------------------- /Script/env/ubuntu_16_04.sh: -------------------------------------------------------------------------------- 1 | sudo apt update 2 | sudo apt install git 3 | git clone https://github.com/enums/Perfect-Ubuntu.git 4 | cd Perfect-Ubuntu/ 5 | sudo ./install.sh --sure 6 | sudo apt install -y mysql-client-core-5.7 mysql-client-5.7 mysql-server-core-5.7 mysql-server-core-5.7 mysql-server-5.7 7 | echo "done" -------------------------------------------------------------------------------- /Script/run_debug.sh: -------------------------------------------------------------------------------- 1 | cd ../ 2 | lldb ./.build/debug/Calatrava -------------------------------------------------------------------------------- /Script/run_release.sh: -------------------------------------------------------------------------------- 1 | cd ../ 2 | while true 3 | do 4 | ./.build/release/Calatrava 5 | DATE=`date +%Y-%m-%d_%H:%M:%S` 6 | echo '['$DATE']Crash detected!' >> Workspace/runtime/crash.log 7 | done 8 | -------------------------------------------------------------------------------- /Script/xcodeproj.sh: -------------------------------------------------------------------------------- 1 | cd ../ 2 | swift package generate-xcodeproj 3 | open ./Calatrava.xcodeproj 4 | echo "Tips:" 5 | echo "1. 编辑 module.modulemap. 设置你的真实头文件路径,例: /usr/local/mysql/include/mysql.h" 6 | echo "2. Target -> PerfectMySQL -> Build Settings -> Library Search Paths 添加 /usr/local/mysql/lib" 7 | echo "3. Target -> PjangoMySQL -> Build Settings -> Other Linker Flags 添加 -L/usr/local/mysql/lib" 8 | -------------------------------------------------------------------------------- /Source/Calatrava/Base/AppConfig.swift: -------------------------------------------------------------------------------- 1 | // 2 | // AppConfig.swift 3 | // Calatrava 4 | // 5 | // Created by 郑宇琦 on 2018/12/10. 6 | // 7 | 8 | import Foundation 9 | import SwiftyJSON 10 | import PerfectLib 11 | 12 | class AppConfig { 13 | 14 | var data: JSON 15 | 16 | init(json: JSON?) { 17 | if let json = json { 18 | data = json 19 | } else { 20 | data = JSON.null 21 | } 22 | } 23 | 24 | func string(forKey key: String) -> String? { 25 | return data[key].string 26 | } 27 | 28 | func bool(forKey key: String) -> Bool? { 29 | return data[key].bool 30 | } 31 | 32 | func int(forKey key: String) -> Int? { 33 | return data[key].int 34 | } 35 | 36 | } 37 | 38 | let APP_CONFIG: AppConfig = { 39 | #if os(macOS) 40 | let configPath = "/Users/enum/Developer/Calatrava/Workspace/runtime/config.json" 41 | #else 42 | let currentPath = FileManager.default.currentDirectoryPath 43 | let configPath = "\(currentPath)/Workspace/runtime/config.json" 44 | #endif 45 | let configFile = File.init(configPath) 46 | let content = (try? configFile.readString()) ?? "" 47 | return AppConfig.init(json: JSON.init(parseJSON: content)) 48 | }() 49 | -------------------------------------------------------------------------------- /Source/Calatrava/Base/ConfigModel.swift: -------------------------------------------------------------------------------- 1 | // 2 | // ConfigModel.swift 3 | // Pjango-Dev 4 | // 5 | // Created by 郑宇琦 on 2017/7/1. 6 | // Copyright © 2017年 郑宇琦. All rights reserved. 7 | // 8 | 9 | import Foundation 10 | import Pjango 11 | 12 | enum ConfigModelKey: String { 13 | 14 | case counterIndex = "counter_index" 15 | case counterPostsList = "counter_posts_list" 16 | case counterPostsSearch = "counter_posts_search" 17 | 18 | case name = "name" 19 | case indexMessage = "index_message" 20 | case titleMessage = "title_message" 21 | case postsListMessage = "posts_list_message" 22 | 23 | case instagramMessage = "instagram_message" 24 | case bilibiliName = "bilibili_name" 25 | case bilibiliMessage = "bilibili_message" 26 | 27 | case reportDailyMessage = "report_daily_message" 28 | case reportTotalMessage = "report_total_message" 29 | } 30 | 31 | class ConfigModel: PCModel { 32 | 33 | override var tableName: String { 34 | return "LocalConfig" 35 | } 36 | 37 | var key = PCDataBaseField.init(name: "KEY", type: .string, length: 64) 38 | var value = PCDataBaseField.init(name: "VALUE", type: .string, length: 512) 39 | 40 | override func registerFields() -> [PCDataBaseField] { 41 | return [ 42 | key, value 43 | ] 44 | } 45 | 46 | override class var cacheTime: TimeInterval? { 47 | return 60 48 | } 49 | 50 | static func getValueForKey(_ key: ConfigModelKey) -> String? { 51 | let values = (self.queryObjects() as? [ConfigModel])?.filter { 52 | $0.key.strValue == key.rawValue 53 | }.map { 54 | $0.value.strValue 55 | } 56 | if values != nil, values!.count > 0 { 57 | return values![0] 58 | } else { 59 | return nil 60 | } 61 | } 62 | 63 | @discardableResult 64 | static func setValueForKey(_ key: ConfigModelKey, value: PCModelDataBaseFieldType) -> Bool { 65 | if let model = (self.queryObjects() as? [ConfigModel])?.first(where: { ($0.key.strValue) == key.rawValue }) { 66 | model.value.value = value 67 | return self.updateObject(model) 68 | } else { 69 | let model = ConfigModel.init() 70 | model.key.value = key.rawValue 71 | model.value.value = value 72 | return self.insertObject(model) 73 | } 74 | 75 | } 76 | 77 | override func initialObjects() -> [PCModel]? { 78 | return [ 79 | ConfigModelKey.postsListMessage.rawValue: "主人还没有写寄语", 80 | ConfigModelKey.titleMessage.rawValue: "主人还没有设定座右铭", 81 | ConfigModelKey.name.rawValue: "主人还没有设定名字", 82 | ConfigModelKey.indexMessage.rawValue: "主人还没有设定主页寄语", 83 | ConfigModelKey.counterIndex.rawValue: "0", 84 | ConfigModelKey.counterPostsList.rawValue: "0", 85 | ConfigModelKey.counterPostsSearch.rawValue: "0", 86 | ].map { 87 | let config = ConfigModel.init() 88 | config.key.value = $0.key 89 | config.value.value = $0.value 90 | return config 91 | } 92 | } 93 | 94 | } 95 | -------------------------------------------------------------------------------- /Source/Calatrava/Base/EventHooks.swift: -------------------------------------------------------------------------------- 1 | // 2 | // EventHooks.swift 3 | // Calatrava 4 | // 5 | // Created by 郑宇琦 on 2017/7/1. 6 | // Copyright © 2017年 郑宇琦. All rights reserved. 7 | // 8 | 9 | import Foundation 10 | import PerfectHTTP 11 | import Pjango 12 | 13 | class EventHooks { 14 | 15 | static func hookIndex(req: HTTPRequest?) { 16 | addCountForKey(.counterIndex) 17 | StatisticsManager.statisticsEvent(eventType: .visitIndex, req: req) 18 | } 19 | 20 | static func hookModuleList(req: HTTPRequest?) { 21 | StatisticsManager.statisticsEvent(eventType: .listModules, req: req) 22 | } 23 | 24 | static func hookSearchAll(req: HTTPRequest?, module: String?, keyword: String?) { 25 | addCountForKey(.counterPostsSearch) 26 | StatisticsManager.statisticsEvent(eventType: .searchAll, param: "\(module ?? "null")_\(keyword ?? "null")", req: req) 27 | } 28 | 29 | static func hookAbout(req: HTTPRequest?) { 30 | StatisticsManager.statisticsEvent(eventType: .visitAbout, req: req) 31 | } 32 | 33 | static func hookProject(req: HTTPRequest?) { 34 | StatisticsManager.statisticsEvent(eventType: .visitProject, req: req) 35 | } 36 | 37 | static func hookPostsList(req: HTTPRequest?) { 38 | addCountForKey(.counterPostsList) 39 | StatisticsManager.statisticsEvent(eventType: .listPosts, req: req) 40 | } 41 | 42 | static func hookPostsSearch(req: HTTPRequest?, keyword: String?) { 43 | addCountForKey(.counterPostsSearch) 44 | StatisticsManager.statisticsEvent(eventType: .searchPosts, param: keyword, req: req) 45 | } 46 | 47 | static func hookPostsRead(req: HTTPRequest?, pid: Int) { 48 | StatisticsManager.statisticsEvent(eventType: .readPosts, param: "\(pid)", req: req) 49 | } 50 | 51 | static func hookPostsLove(req: HTTPRequest?, pid: Int) { 52 | StatisticsManager.statisticsEvent(eventType: .lovePosts, param: "\(pid)", req: req) 53 | } 54 | 55 | static func hookPostsComment(req: HTTPRequest?, pid: Int) { 56 | StatisticsManager.statisticsEvent(eventType: .commentPosts, param: "\(pid)", req: req) 57 | } 58 | 59 | static func hookPostsArchive(req: HTTPRequest?) { 60 | StatisticsManager.statisticsEvent(eventType: .archivePosts, req: req) 61 | } 62 | 63 | static func hookCorpusList(req: HTTPRequest?) { 64 | addCountForKey(.counterPostsList) 65 | StatisticsManager.statisticsEvent(eventType: .listCorpus, req: req) 66 | } 67 | 68 | static func hookCorpusPostsList(req: HTTPRequest?, cid: Int) { 69 | addCountForKey(.counterPostsList) 70 | StatisticsManager.statisticsEvent(eventType: .listCorpusPosts, param: "\(cid)", req: req) 71 | } 72 | 73 | static func hookCorpusPostsRead(req: HTTPRequest?, cpid: Int) { 74 | StatisticsManager.statisticsEvent(eventType: .readCorpusPosts, param: "\(cpid)", req: req) 75 | } 76 | 77 | static func hookCorpusPostsLove(req: HTTPRequest?, cpid: Int) { 78 | StatisticsManager.statisticsEvent(eventType: .lovePosts, param: "\(cpid)", req: req) 79 | } 80 | 81 | static func hookCorpusPostsComment(req: HTTPRequest?, cpid: Int) { 82 | StatisticsManager.statisticsEvent(eventType: .commentPosts, param: "\(cpid)", req: req) 83 | } 84 | 85 | static func hookLeaveMessage(req: HTTPRequest?) { 86 | StatisticsManager.statisticsEvent(eventType: .leaveMessage, req: req) 87 | } 88 | 89 | static func hookBlogUpdate(req: HTTPRequest?) { 90 | StatisticsManager.statisticsEvent(eventType: .blogUpdate, req: req) 91 | } 92 | 93 | static internal func addCountForKey(_ key: ConfigModelKey) { 94 | if Int(ConfigModel.getValueForKey(key) ?? "") == nil { 95 | ConfigModel.setValueForKey(key, value: "0") 96 | } 97 | guard let oldCounter = Int(ConfigModel.getValueForKey(key) ?? "") else { 98 | return 99 | } 100 | ConfigModel.setValueForKey(key, value: "\(oldCounter + 1)") 101 | } 102 | 103 | static func hookInstagram(req: HTTPRequest?) { 104 | StatisticsManager.statisticsEvent(eventType: .visitInstagram, req: req) 105 | } 106 | 107 | static func hookBilibili(req: HTTPRequest?) { 108 | StatisticsManager.statisticsEvent(eventType: .visitBilibili, req: req) 109 | } 110 | 111 | static func hookReportDaily(req: HTTPRequest?, date: String) { 112 | StatisticsManager.statisticsEvent(eventType: .reportDaily, param: date, req: req) 113 | } 114 | 115 | static func hookReportTotal(req: HTTPRequest?, date: String) { 116 | StatisticsManager.statisticsEvent(eventType: .reportTotal, param: date, req: req) 117 | } 118 | 119 | } 120 | -------------------------------------------------------------------------------- /Source/Calatrava/Base/Extension.swift: -------------------------------------------------------------------------------- 1 | // 2 | // Extension.swift 3 | // Calatrava 4 | // 5 | // Created by 郑宇琦 on 2017/6/25. 6 | // Copyright © 2017年 郑宇琦. All rights reserved. 7 | // 8 | 9 | import Foundation 10 | 11 | 12 | var dateFormatter = { () -> DateFormatter in 13 | let that = DateFormatter.init() 14 | that.timeZone = TimeZone.init(secondsFromGMT: 8 * 3600) 15 | that.dateFormat = "yyyy-MM-dd HH:mm:ss" 16 | return that 17 | }() 18 | 19 | var dayFormatter = { () -> DateFormatter in 20 | let that = DateFormatter.init() 21 | that.timeZone = TimeZone.init(secondsFromGMT: 8 * 3600) 22 | that.dateFormat = "yyyy-MM-dd" 23 | return that 24 | }() 25 | 26 | extension Date { 27 | var stringValue: String { 28 | get { 29 | return dateFormatter.string(from: self) 30 | } 31 | } 32 | var dayStringValue: String { 33 | get { 34 | return dayFormatter.string(from: self) 35 | } 36 | } 37 | 38 | static var today: Date { 39 | get { 40 | let date = Date.init() 41 | let todayStr = dayFormatter.string(from: date) 42 | return dayFormatter.date(from: todayStr)! 43 | } 44 | } 45 | 46 | static var tomorrow: Date { 47 | return Date.init(timeInterval: 3600 * 24, since: today) 48 | } 49 | 50 | static var yestoday: Date { 51 | get { 52 | return Date.init(timeInterval: -3600 * 24, since: today) 53 | } 54 | } 55 | } 56 | 57 | extension Int { 58 | 59 | static func rand(_ to: Int) -> Int { 60 | #if os(Linux) 61 | return Int.random(in: 0.. Element { 71 | return self[Int.rand(self.count)] 72 | } 73 | 74 | } 75 | -------------------------------------------------------------------------------- /Source/Calatrava/Base/FooterBarView.swift: -------------------------------------------------------------------------------- 1 | // 2 | // FooterBarView.swift 3 | // Calatrava 4 | // 5 | // Created by 郑宇琦 on 2017/6/24. 6 | // Copyright © 2017年 郑宇琦. All rights reserved. 7 | // 8 | 9 | import Foundation 10 | import Pjango 11 | 12 | class FooterBarView: PCDetailView { 13 | 14 | override var templateName: String? { 15 | return "footer_bar.html" 16 | } 17 | 18 | static var html = FooterBarView.meta.getTemplate() 19 | } 20 | -------------------------------------------------------------------------------- /Source/Calatrava/Base/Global.swift: -------------------------------------------------------------------------------- 1 | // 2 | // Global.swift 3 | // Calatrava 4 | // 5 | // Created by 郑宇琦 on 2017/6/24. 6 | // Copyright © 2017年 郑宇琦. All rights reserved. 7 | // 8 | 9 | import Foundation 10 | import Pjango 11 | 12 | #if os(macOS) 13 | let WEBSITE_HOST = "blog.yuusanntest.com" 14 | #else 15 | let WEBSITE_HOST = "blog.yuusann.com" 16 | #endif 17 | 18 | let logger = PCLog.init(tag: "Calatrava-Blog") 19 | let statisticsLogger = PCLog.init(tag: "Statistics") 20 | 21 | //pid@ip 22 | var postsLoveDict = Set.init() 23 | 24 | //pid@ip 25 | var postsCommentLastTimeDict = Dictionary.init() 26 | //pid@ip 27 | var postsCommentDailyDict = Dictionary.init() 28 | 29 | 30 | //cpid@ip 31 | var corpusPostsLoveDict = Set.init() 32 | 33 | //cpid@ip 34 | var corpusPostsCommentLastTimeDict = Dictionary.init() 35 | //cpid@ip 36 | var corpusPostsCommentDailyDict = Dictionary.init() 37 | 38 | //pid@ip 39 | var messageLastTimeDict = Dictionary.init() 40 | //pid@ip 41 | var messageDailyDict = Dictionary.init() 42 | 43 | //pid@ip 44 | var verificationLastTimeDict = Dictionary.init() 45 | 46 | 47 | -------------------------------------------------------------------------------- /Source/Calatrava/Base/MonitorHandle.swift: -------------------------------------------------------------------------------- 1 | // 2 | // MonitorHandle.swift 3 | // Calatrava 4 | // 5 | // Created by enum on 2019/3/26. 6 | // 7 | 8 | import Foundation 9 | import Pjango 10 | 11 | func monitorHandle() -> PCUrlHandle { 12 | return pjangoHttpResponse { req, res in 13 | pjangoHttpResponse("1")(req, res) 14 | } 15 | } 16 | -------------------------------------------------------------------------------- /Source/Calatrava/Base/NavigationBarView.swift: -------------------------------------------------------------------------------- 1 | // 2 | // NavigationBarView.swift 3 | // Calatrava 4 | // 5 | // Created by 郑宇琦 on 2017/6/24. 6 | // Copyright © 2017年 郑宇琦. All rights reserved. 7 | // 8 | 9 | import Foundation 10 | import Pjango 11 | 12 | class NavigationBarView: PCDetailView { 13 | 14 | override var templateName: String? { 15 | return "navigation_bar.html" 16 | } 17 | 18 | override var viewParam: PCViewParam? { 19 | return [ 20 | "_pjango_param_host": WEBSITE_HOST, 21 | ] 22 | } 23 | 24 | static var html = NavigationBarView.meta.getTemplate() 25 | 26 | } 27 | -------------------------------------------------------------------------------- /Source/Calatrava/Error/ErrorNotFoundView.swift: -------------------------------------------------------------------------------- 1 | // 2 | // ErrorNotFoundView.swift 3 | // Calatrava 4 | // 5 | // Created by 郑宇琦 on 2017/6/24. 6 | // Copyright © 2017年 郑宇琦. All rights reserved. 7 | // 8 | 9 | import Foundation 10 | import Pjango 11 | 12 | class ErrorNotFoundView: PCDetailView { 13 | 14 | override var templateName: String? { 15 | return "error_404.html" 16 | } 17 | 18 | let titleMessage = ConfigModel.getValueForKey(.titleMessage) ?? "null" 19 | 20 | override var viewParam: PCViewParam? { 21 | return [ 22 | "_pjango_template_navigation_bar": NavigationBarView.html, 23 | "_pjango_template_footer_bar": FooterBarView.html, 24 | "_pjango_param_title_message": titleMessage, 25 | 26 | "_pjango_param_website_host": WEBSITE_HOST, 27 | ] 28 | } 29 | 30 | } 31 | -------------------------------------------------------------------------------- /Source/Calatrava/Error/ErrorNotSupportView.swift: -------------------------------------------------------------------------------- 1 | // 2 | // ErrorNotSupportView.swift 3 | // Calatrava 4 | // 5 | // Created by 郑宇琦 on 2017/7/1. 6 | // Copyright © 2017年 郑宇琦. All rights reserved. 7 | // 8 | 9 | import Foundation 10 | import Pjango 11 | 12 | class ErrorNotSupportView: PCDetailView { 13 | 14 | override var templateName: String? { 15 | return "error_notsupport.html" 16 | } 17 | 18 | override var viewParam: PCViewParam? { 19 | return [ 20 | "_pjango_template_navigation_bar": NavigationBarView.html, 21 | "_pjango_template_footer_bar": FooterBarView.html, 22 | "_pjango_param_title_message": ConfigModel.getValueForKey(.titleMessage) ?? "null", 23 | 24 | "_pjango_param_website_host": WEBSITE_HOST, 25 | ] 26 | } 27 | 28 | } 29 | -------------------------------------------------------------------------------- /Source/Calatrava/Modules/About/AboutView.swift: -------------------------------------------------------------------------------- 1 | // 2 | // AboutView.swift 3 | // Calatrava 4 | // 5 | // Created by 郑宇琦 on 2017/6/25. 6 | // Copyright © 2017年 郑宇琦. All rights reserved. 7 | // 8 | 9 | import Foundation 10 | import Pjango 11 | 12 | class AboutView: PCListView { 13 | 14 | override var templateName: String? { 15 | return "about.html" 16 | } 17 | 18 | override func listUserField(inList list: String, forModel model: PCModel) -> PCViewParam? { 19 | guard let model = model as? MessageModel else { 20 | return nil 21 | } 22 | return [ 23 | "_pjango_param_table_Message_ISADMIN": model.admin.intValue == 1 ? 1 : 0 24 | 25 | ] 26 | } 27 | 28 | override var listObjectSets: [String : [PCModel]]? { 29 | guard let history = PostsHistoryModel.queryObjects() else { 30 | return nil 31 | } 32 | let message = MessageModel.queryObjects() ?? [MessageModel]() 33 | return [ 34 | "_pjango_param_table_history": history.reversed(), 35 | "_pjango_param_table_message": message.reversed(), 36 | ] 37 | } 38 | 39 | override var viewParam: PCViewParam? { 40 | EventHooks.hookAbout(req: currentRequest) 41 | 42 | let titleMessage = ConfigModel.getValueForKey(.titleMessage) ?? "null" 43 | 44 | return [ 45 | "_pjango_template_navigation_bar": NavigationBarView.html, 46 | "_pjango_template_footer_bar": FooterBarView.html, 47 | "_pjango_param_title_message": titleMessage, 48 | 49 | ] 50 | } 51 | } 52 | -------------------------------------------------------------------------------- /Source/Calatrava/Modules/About/MessageHandle.swift: -------------------------------------------------------------------------------- 1 | // 2 | // MessageHandle.swift 3 | // Calatrava-Blog 4 | // 5 | // Created by 郑宇琦 on 2017/12/21. 6 | // 7 | 8 | import Foundation 9 | import Pjango 10 | import SwiftyJSON 11 | 12 | func messageHandle() -> PCUrlHandle { 13 | 14 | return pjangoHttpResponse { req, res in 15 | guard let jsonStr = req.postBodyString else { 16 | pjangoHttpResponse("请把内容填写完整哦!")(req, res) 17 | return 18 | } 19 | let json = JSON.init(parseJSON: jsonStr) 20 | guard json != JSON.null else { 21 | pjangoHttpResponse("请把内容填写完整哦!")(req, res) 22 | return 23 | } 24 | guard let v_id = json["v_id"].string, let v_a = json["v_a"].string else { 25 | pjangoHttpResponse("人机校验失败啦!")(req, res) 26 | return 27 | } 28 | guard VerificationManager.checkCode(identifier: v_id, answer: v_a) else { 29 | pjangoHttpResponse("人机校验失败啦!")(req, res) 30 | return 31 | } 32 | guard let name = json["name"].string, let email = json["email"].string, let comment = json["comment"].string else { 33 | pjangoHttpResponse("请把内容填写完整哦!")(req, res) 34 | return 35 | } 36 | guard name.count > 2 else { 37 | pjangoHttpResponse("昵称太短啦!")(req, res) 38 | return 39 | } 40 | guard email.contains(string: "@"), email.contains(string: "."), email.count > 5 else { 41 | pjangoHttpResponse("邮箱地址不合法!")(req, res) 42 | return 43 | } 44 | guard comment.count > 2 else { 45 | pjangoHttpResponse("评论太短啦!")(req, res) 46 | return 47 | } 48 | let ip = req.header(.custom(name: "watchdog_ip")) ?? req.remoteAddress.host 49 | if let lastTime = messageLastTimeDict[ip] { 50 | guard Date.init().timeIntervalSince1970 - lastTime > 10 else { 51 | logger.info("Message - Frequency anomaly @ \(ip): NAME: \(name), EMAIL: \(email), COMMENT: \(comment)") 52 | pjangoHttpResponse("你提交得太频繁啦!")(req, res) 53 | return 54 | } 55 | } 56 | messageLastTimeDict[ip] = Date.init().timeIntervalSince1970 57 | if let count = messageDailyDict[ip] { 58 | messageDailyDict[ip] = count + 1 59 | } else { 60 | messageDailyDict[ip] = 1 61 | } 62 | if let count = messageDailyDict[ip], count >= 40 { 63 | logger.info("Message - Quantity anomaly @ \(ip): NAME: \(name), EMAIL: \(email), COMMENT: \(comment)") 64 | pjangoHttpResponse("今天提交的次数已达上限啦!")(req, res) 65 | return 66 | } 67 | 68 | let date = Date.init().stringValue 69 | let messageModel = MessageModel.init() 70 | messageModel.mid.strValue = "\(name)#\(date)@\(ip)" 71 | messageModel.date.strValue = date 72 | messageModel.name.strValue = name 73 | messageModel.floor.intValue = (MessageModel.queryObjects()?.count ?? 0) + 1 74 | messageModel.admin.intValue = 0 75 | messageModel.email.strValue = email 76 | messageModel.comment.strValue = comment 77 | messageModel.fromIp.strValue = ip 78 | guard MessageModel.insertObject(messageModel) else { 79 | pjangoHttpResponse("居然出错了!")(req, res) 80 | return 81 | } 82 | pjangoHttpResponse("发表成功!")(req, res) 83 | EventHooks.hookLeaveMessage(req: req) 84 | } 85 | 86 | } 87 | -------------------------------------------------------------------------------- /Source/Calatrava/Modules/About/MessageModel.swift: -------------------------------------------------------------------------------- 1 | // 2 | // MessageModel.swift 3 | // Calatrava-Blog 4 | // 5 | // Created by 郑宇琦 on 2017/12/21. 6 | // 7 | 8 | import Foundation 9 | import Pjango 10 | 11 | class MessageModel: PCModel { 12 | 13 | override var tableName: String { 14 | return "Message" 15 | } 16 | 17 | var mid = PCDataBaseField.init(name: "MID", type: .string, length: 128) 18 | var floor = PCDataBaseField.init(name: "FLOOR", type: .int) 19 | var admin = PCDataBaseField.init(name: "ADMIN", type: .int) 20 | var date = PCDataBaseField.init(name: "DATE", type: .string, length: 20) 21 | var name = PCDataBaseField.init(name: "NAME", type: .string, length: 64) 22 | var email = PCDataBaseField.init(name: "EMAIL", type: .string, length: 64) 23 | var comment = PCDataBaseField.init(name: "COMMENT", type: .string, length: 2048) 24 | var fromIp = PCDataBaseField.init(name: "FROM_IP", type: .string, length: 16) 25 | 26 | override func registerFields() -> [PCDataBaseField] { 27 | return [ 28 | mid, floor, admin, date, name, email, comment, fromIp 29 | ] 30 | } 31 | 32 | } 33 | -------------------------------------------------------------------------------- /Source/Calatrava/Modules/Base/Statistics/StatisticsManager.swift: -------------------------------------------------------------------------------- 1 | // 2 | // StatisticsManager.swift 3 | // Calatrava-Blog 4 | // 5 | // Created by 郑宇琦 on 2017/12/23. 6 | // 7 | 8 | import Foundation 9 | import PerfectHTTP 10 | 11 | class StatisticsManager { 12 | 13 | static func statisticsEvent(eventType: VisitStatisticsEventType, param: String? = nil, req: HTTPRequest?) { 14 | let date = Date.init() 15 | let dateStr = Int(date.timeIntervalSince1970 * 1000) 16 | let ip = req?.header(.custom(name: "watchdog_ip")) ?? req?.remoteAddress.host ?? "unknow" 17 | let port = UInt16(req?.header(.custom(name: "watchdog_port")) ?? "") ?? 0 18 | 19 | let model = VisitStatisticsModel.init() 20 | model.vid.strValue = "\(dateStr)@\(ip):\(port)" 21 | model.date.strValue = date.stringValue 22 | model.ip.strValue = ip 23 | model.port.intValue = Int(port) 24 | model.from.strValue = req?.header(HTTPRequestHeader.Name.referer) ?? "" 25 | model.event.strValue = eventType.rawValue 26 | model.param.strValue = param ?? "" 27 | 28 | guard VisitStatisticsModel.insertObject(model) else { 29 | statisticsLogger.error("Failed: [\(ip)][\(port)][\(model.from.strValue)][\(eventType.rawValue)][\(param ?? "")]") 30 | return 31 | } 32 | statisticsLogger.info("Success: [\(ip)][\(port)][\(model.from.strValue)][\(eventType.rawValue)][\(param ?? "")]") 33 | 34 | } 35 | 36 | } 37 | -------------------------------------------------------------------------------- /Source/Calatrava/Modules/Base/Statistics/VisitStatisticsModel.swift: -------------------------------------------------------------------------------- 1 | // 2 | // VisitStatisticsModel.swift 3 | // Calatrava-Blog 4 | // 5 | // Created by 郑宇琦 on 2017/12/23. 6 | // 7 | 8 | import Foundation 9 | import Pjango 10 | 11 | enum VisitStatisticsEventType: String { 12 | case visitIndex = "VISIT_INDEX" 13 | case listModules = "VISIT_MODULES" 14 | case visitProject = "VISIT_PROJECT" 15 | case visitAbout = "VISIT_ABOUT" 16 | case listPosts = "LIST_POSTS" 17 | case searchAll = "SEARCH_ALL" 18 | case searchPosts = "SEARCH_POSTS" 19 | case readPosts = "READ_POSTS" 20 | case lovePosts = "LOVE_POSTS" 21 | case commentPosts = "COMMENT_POSTS" 22 | case archivePosts = "ARCHIVE_POSTS" 23 | case listCorpus = "LIST_CORPUS" 24 | case listCorpusPosts = "LIST_CORPUS_POSTS" 25 | case readCorpusPosts = "READ_CORPUS_POSTS" 26 | case loveCorpusPosts = "LOVE_CORPUS_POSTS" 27 | case commentCorpusPosts = "COMMENT_CORPUS_POSTS" 28 | case leaveMessage = "LEAVE_MESSAGE" 29 | case blogUpdate = "BLOG_UPDATE" 30 | case visitInstagram = "VISIT_INSTAGRAM" 31 | case visitBilibili = "VISIT_BILIBILI" 32 | case reportDaily = "REPORT_DAILY" 33 | case reportTotal = "REPORT_TOTAL" 34 | } 35 | 36 | extension VisitStatisticsEventType { 37 | var displayValue: String { 38 | switch self { 39 | case .visitIndex: return "访问主页" 40 | case .listModules: return "查看内容列表" 41 | case .visitProject: return "业余项目" 42 | case .visitAbout: return "查看关于" 43 | case .listPosts: return "查看博文列表" 44 | case .searchAll: return "搜索全站" 45 | case .searchPosts: return "搜索博文" 46 | case .readPosts: return "阅读博文" 47 | case .lovePosts: return "点赞博文" 48 | case .commentPosts: return "评论博文" 49 | case .archivePosts: return "查看博文归档" 50 | case .listCorpus: return "查看文集列表" 51 | case .listCorpusPosts: return "查看文集文章列表" 52 | case .readCorpusPosts: return "阅读文集文章" 53 | case .loveCorpusPosts: return "点赞文集文章" 54 | case .commentCorpusPosts: return "评论文集文章" 55 | case .leaveMessage: return "留言" 56 | case .blogUpdate: return "查看动态聚合" 57 | case .visitInstagram: return "访问IG抓取" 58 | case .visitBilibili: return "访问原创视频" 59 | case .reportDaily: return "数据每日报告" 60 | case .reportTotal: return "数据统计报告" 61 | } 62 | } 63 | } 64 | 65 | class VisitStatisticsModel: PCModel { 66 | 67 | override var tableName: String { 68 | return "VisitStatistics" 69 | } 70 | 71 | var vid = PCDataBaseField.init(name: "VID", type: .string, length: 128) 72 | var date = PCDataBaseField.init(name: "DATE", type: .string, length: 20) 73 | var ip = PCDataBaseField.init(name: "IP", type: .string, length: 16) 74 | var port = PCDataBaseField.init(name: "PORT", type: .int) 75 | var from = PCDataBaseField.init(name: "FROM", type: .string, length: 2048) 76 | var event = PCDataBaseField.init(name: "EVENT", type: .string, length: 64) 77 | var param = PCDataBaseField.init(name: "PARAM", type: .string, length: 1024) 78 | 79 | override func registerFields() -> [PCDataBaseField] { 80 | return [ 81 | vid, date, ip, port, from, event, param 82 | ] 83 | } 84 | 85 | 86 | } 87 | -------------------------------------------------------------------------------- /Source/Calatrava/Modules/Base/VerificationCode/VerificationHandle.swift: -------------------------------------------------------------------------------- 1 | // 2 | // VerificationHandle.swift 3 | // Calatrava 4 | // 5 | // Created by 郑宇琦 on 2017/12/23. 6 | // 7 | 8 | import Foundation 9 | import Pjango 10 | 11 | func verificationHandle() -> PCUrlHandle { 12 | 13 | return pjangoHttpResponse { req, res in 14 | let ip = req.header(.custom(name: "watchdog_ip")) ?? req.remoteAddress.host 15 | if let lastTime = verificationLastTimeDict[ip] { 16 | guard Date.init().timeIntervalSince1970 - lastTime > 10 else { 17 | logger.info("Verification - Frequency anomaly @ \(ip)") 18 | pjangoHttpResponse("你请求得太频繁啦!")(req, res) 19 | return 20 | } 21 | } 22 | let (identifier, question) = VerificationManager.generateCode() 23 | pjangoHttpResponse("\(question)@\(identifier)")(req, res) 24 | } 25 | 26 | } 27 | -------------------------------------------------------------------------------- /Source/Calatrava/Modules/Base/VerificationCode/VerificationManager.swift: -------------------------------------------------------------------------------- 1 | // 2 | // VerificationManager.swift 3 | // Calatrava 4 | // 5 | // Created by 郑宇琦 on 2017/12/23. 6 | // 7 | 8 | import Foundation 9 | 10 | struct VerificationItem { 11 | var identifier: String 12 | var timeout: TimeInterval 13 | var question: String 14 | var answer: String 15 | } 16 | 17 | class VerificationManager { 18 | 19 | static internal var verification = Dictionary.init() 20 | static internal var lastCheckTime: TimeInterval = 0 21 | 22 | static func generateCode() -> (identifier: String, question: String) { 23 | checkListIfNeed() 24 | let funcs = [generateCodeNum, generateCodeMath] 25 | return funcs.rand()() 26 | } 27 | 28 | static internal func generateCodeNum() ->(identifier: String, question: String) { 29 | let identifier = String(Int(Date.init().timeIntervalSince1970 * 1000)) 30 | var (equation, _, nums) = generateEquation() 31 | 32 | let num = rand(10) 33 | let funcs: Array<((Int)->Bool)> = [ 34 | { $0 == num }, 35 | { $0 > num }, 36 | { $0 < num }, 37 | ] 38 | let funcNum = rand(funcs.count) 39 | nums = nums.filter(funcs[funcNum]) 40 | 41 | let msg_a = ["输入", "给出", "数出"] 42 | let msg_b = ["下列", "下面", "这个"] 43 | let msg_c = ["式子", "算式", "等式", "四则运算"] 44 | let msg_d = ["等于", "大于", "小于"] 45 | let msg_e = ["的个数", "出现的次数"] 46 | let question = "请\(msg_a.rand())\(msg_b.rand())\(msg_c.rand())\(equation)中数字\(msg_d[funcNum])\(num)\(msg_e.rand())" 47 | 48 | let item = VerificationItem.init(identifier: identifier, timeout: Date.init().timeIntervalSince1970 + 180, question: question, answer: "\(nums.count)") 49 | verification[identifier] = item 50 | 51 | return (identifier, question) 52 | } 53 | 54 | static internal func generateCodeMath() -> (identifier: String, question: String) { 55 | let identifier = String(Int(Date.init().timeIntervalSince1970 * 1000)) 56 | let (equation, answer, _) = generateEquation() 57 | 58 | let msg_a = ["输入", "给出", "得出", "计算", "算出"] 59 | let msg_b = ["下列", "下面", "这个"] 60 | let msg_c = ["式子", "算式", "等式", "四则运算"] 61 | let msg_d = ["答案", "值", "结果"] 62 | let question = "请\(msg_a.rand())\(msg_b.rand())\(msg_c.rand())\(equation)的\(msg_d.rand())" 63 | 64 | let item = VerificationItem.init(identifier: identifier, timeout: Date.init().timeIntervalSince1970 + 180, question: question, answer: "\(answer)") 65 | verification[identifier] = item 66 | 67 | return (identifier, question) 68 | } 69 | 70 | static internal func generateEquation() -> (equation: String, answer: String, nums: [Int]) { 71 | let numTable = [ 72 | ["0", "1", "2", "3", "4", "5", "6", "7", "8", "9"], 73 | ["0", "1", "2", "3", "4", "5", "6", "7", "8", "9"], 74 | ["零", "一", "二", "三", "四", "五", "六", "七", "八", "九"], 75 | ["零", "壹", "贰", "叁", "肆", "伍", "陆", "柒", "捌", "玖"], 76 | ["⓪", "①", "②", "③", "④", "⑤", "⑥", "⑦", "⑧", "⑨"], 77 | ] 78 | let signTable = [ 79 | ["+", "-"], 80 | ["+", "-"], 81 | ["加", "减"], 82 | ["➕", "➖"], 83 | ] 84 | let calculateFuncs: Array<((Int, Int)->Int)> = [ 85 | { $0 + $1 }, 86 | { $0 - $1 }, 87 | { $0 * $1 }, 88 | ] 89 | // 2 ~ 4 个数 90 | let numOfNum = rand(3) + 1 91 | var answer = rand(10) 92 | var nums = [answer] 93 | var question = "\(answer)" 94 | for _ in 0.. Bool { 106 | checkListIfNeed() 107 | guard let item = verification[identifier] else { 108 | return false 109 | } 110 | verification[identifier] = nil 111 | return item.answer == answer 112 | } 113 | 114 | static internal func rand(_ to: Int) -> Int { 115 | #if os(Linux) 116 | return Int(random()) % to 117 | #else 118 | return Int(arc4random()) % to 119 | #endif 120 | } 121 | 122 | static internal func checkListIfNeed() { 123 | let now = Date.init().timeIntervalSince1970 124 | guard now > lastCheckTime * 5 else { 125 | return 126 | } 127 | lastCheckTime = now 128 | verification.forEach { 129 | if ($0.value.timeout >= now) { 130 | verification[$0.value.identifier] = nil 131 | } 132 | } 133 | } 134 | } 135 | -------------------------------------------------------------------------------- /Source/Calatrava/Modules/Bilibili/BilibiliFeedModel.swift: -------------------------------------------------------------------------------- 1 | // 2 | // BilibiliFeedModel.swift 3 | // Calatrava 4 | // 5 | // Created by 郑宇琦 on 2018/5/20. 6 | // 7 | 8 | import Foundation 9 | import Pjango 10 | 11 | class BilibiliFeedModel: PCModel { 12 | 13 | override var tableName: String { 14 | return "BilibiliFeed" 15 | } 16 | 17 | var bvid = PCDataBaseField.init(name: "BVID", type: .int) 18 | var blid = PCDataBaseField.init(name: "BLID", type: .int) 19 | var url = PCDataBaseField.init(name: "URL", type: .string, length: 1024) 20 | var player = PCDataBaseField.init(name: "PLAYER", type: .string, length: 1024) 21 | var name = PCDataBaseField.init(name: "NAME", type: .string, length: 256, notNull: true, defaultValue: "null") 22 | var memo = PCDataBaseField.init(name: "MEMO", type: .string, length: 1024) 23 | var cover = PCDataBaseField.init(name: "COVER", type: .string, length: 1024, notNull: true, defaultValue: "null") 24 | var date = PCDataBaseField.init(name: "DATE", type: .string, length: 20, notNull: true, defaultValue: "1970-01-01 08:00") 25 | 26 | override func registerFields() -> [PCDataBaseField] { 27 | return [ 28 | bvid, blid, url, player, name, memo, cover, date 29 | ] 30 | } 31 | 32 | override class var cacheTime: TimeInterval? { 33 | return 60 34 | } 35 | 36 | } 37 | -------------------------------------------------------------------------------- /Source/Calatrava/Modules/Bilibili/BilibiliListModel.swift: -------------------------------------------------------------------------------- 1 | // 2 | // BilibiliListModel.swift 3 | // Calatrava 4 | // 5 | // Created by 郑宇琦 on 2018/5/20. 6 | // 7 | 8 | import Foundation 9 | import Pjango 10 | 11 | class BilibiliListModel: PCModel { 12 | 13 | override var tableName: String { 14 | return "BilibiliList" 15 | } 16 | 17 | var blid = PCDataBaseField.init(name: "BLID", type: .int) 18 | var name = PCDataBaseField.init(name: "NAME", type: .string, length: 256, notNull: true, defaultValue: "null") 19 | var memo = PCDataBaseField.init(name: "MEMO", type: .string, length: 1024) 20 | 21 | var updateDate: String { 22 | if let comments = (BilibiliFeedModel.queryObjects() as? [BilibiliFeedModel])?.filter({ $0.blid.intValue == blid.intValue }) { 23 | guard let last = comments.last else { 24 | return "1970-01-01 08:00" 25 | } 26 | return last.date.strValue 27 | } else { 28 | return "1970-01-01 08:00" 29 | } 30 | 31 | } 32 | 33 | override func registerFields() -> [PCDataBaseField] { 34 | return [ 35 | blid, name, memo 36 | ] 37 | } 38 | 39 | override class var cacheTime: TimeInterval? { 40 | return 60 41 | } 42 | 43 | } 44 | -------------------------------------------------------------------------------- /Source/Calatrava/Modules/Corpus/CorpusListView.swift: -------------------------------------------------------------------------------- 1 | // 2 | // CorpusListView.swift 3 | // Calatrava 4 | // 5 | // Created by 郑宇琦 on 2017/12/29. 6 | // 7 | 8 | import Foundation 9 | import Pjango 10 | 11 | class CorpusListView: PCListView { 12 | 13 | override var templateName: String? { 14 | return "corpus_list.html" 15 | } 16 | 17 | override var listObjectSets: [String : [PCModel]]? { 18 | guard var corpusList = CorpusModel.queryObjects() as? [CorpusModel] else { 19 | return nil 20 | } 21 | corpusList.sort { $0.updateDate > $1.updateDate } 22 | return [ 23 | "_pjango_param_table_corpus": corpusList 24 | ] 25 | } 26 | 27 | override func listUserField(inList list: String, forModel model: PCModel) -> PCViewParam? { 28 | guard let model = model as? CorpusModel else { 29 | return nil 30 | } 31 | return [ 32 | "_pjango_param_table_Corpus_UPDATEDATE": model.updateDate, 33 | ] 34 | } 35 | 36 | 37 | override var viewParam: PCViewParam? { 38 | EventHooks.hookCorpusList(req: currentRequest) 39 | 40 | let titleMessage = ConfigModel.getValueForKey(.titleMessage) ?? "null" 41 | 42 | return [ 43 | "_pjango_template_navigation_bar": NavigationBarView.html, 44 | "_pjango_template_footer_bar": FooterBarView.html, 45 | "_pjango_param_title_message": titleMessage, 46 | "_pjango_param_host": WEBSITE_HOST 47 | 48 | ] 49 | } 50 | 51 | } 52 | -------------------------------------------------------------------------------- /Source/Calatrava/Modules/Corpus/CorpusModel.swift: -------------------------------------------------------------------------------- 1 | // 2 | // CorpusModel.swift 3 | // Calatrava 4 | // 5 | // Created by 郑宇琦 on 2017/12/29. 6 | // 7 | 8 | import Foundation 9 | import Pjango 10 | 11 | class CorpusModel: PCModel { 12 | 13 | override var tableName: String { 14 | return "Corpus" 15 | } 16 | 17 | var cid = PCDataBaseField.init(name: "CID", type: .int) 18 | var title = PCDataBaseField.init(name: "TITLE", type: .string, length: 64) 19 | var memo = PCDataBaseField.init(name: "MEMO", type: .string, length: 1024) 20 | var date = PCDataBaseField.init(name: "DATE", type: .string, length: 16) 21 | 22 | var updateDate: String { 23 | if let comments = (CorpusPostsModel.queryObjects() as? [CorpusPostsModel])?.filter({ $0.cid.intValue == cid.intValue }) { 24 | guard let last = comments.last else { 25 | return date.strValue 26 | } 27 | return last.date.strValue 28 | } else { 29 | return date.strValue 30 | } 31 | 32 | } 33 | 34 | override func registerFields() -> [PCDataBaseField] { 35 | return [ 36 | cid, title, memo, date 37 | ] 38 | } 39 | 40 | override class var cacheTime: TimeInterval? { 41 | return 60 42 | } 43 | 44 | } 45 | -------------------------------------------------------------------------------- /Source/Calatrava/Modules/Corpus/CorpusPostsCommentHandle.swift: -------------------------------------------------------------------------------- 1 | // 2 | // CorpusPostsCommentHandle.swift 3 | // Calatrava 4 | // 5 | // Created by 郑宇琦 on 2017/12/30. 6 | // 7 | 8 | import Foundation 9 | import Pjango 10 | import SwiftyJSON 11 | 12 | func corpusPostsCommentHandle() -> PCUrlHandle { 13 | 14 | func processRefer(comment: inout String, posts: CorpusPostsModel) -> Int { 15 | guard let begin = comment.range(of: "!*<引用 ")?.upperBound else { 16 | return 0 17 | } 18 | guard let end = comment.range(of: " 楼>*!")?.lowerBound, let endUpper = comment.range(of: " 楼>*!")?.upperBound else { 19 | return 0 20 | } 21 | if let floor = Int(comment[begin.. 2 else { 63 | pjangoHttpResponse("昵称太短啦!")(req, res) 64 | return 65 | } 66 | guard email.contains(string: "@"), email.contains(string: "."), email.count > 5 else { 67 | pjangoHttpResponse("邮箱地址不合法!")(req, res) 68 | return 69 | } 70 | let refer = processRefer(comment: &comment, posts: tmpPosts) 71 | guard comment.count > 2 else { 72 | pjangoHttpResponse("评论太短啦!")(req, res) 73 | return 74 | } 75 | let ip = req.header(.custom(name: "watchdog_ip")) ?? req.remoteAddress.host 76 | if let lastTime = corpusPostsCommentLastTimeDict[ip] { 77 | guard Date.init().timeIntervalSince1970 - lastTime > 1 else { 78 | logger.info("Comment - Frequency anomaly @ \(ip): NAME: \(name), EMAIL: \(email), COMMENT: \(comment)") 79 | pjangoHttpResponse("你提交得太频繁啦!")(req, res) 80 | return 81 | } 82 | } 83 | corpusPostsCommentLastTimeDict[ip] = Date.init().timeIntervalSince1970 84 | if let count = corpusPostsCommentDailyDict[ip] { 85 | corpusPostsCommentDailyDict[ip] = count + 1 86 | } else { 87 | corpusPostsCommentDailyDict[ip] = 1 88 | } 89 | if let count = corpusPostsCommentDailyDict[ip], count >= 960 { 90 | logger.info("Comment - Quantity anomaly @ \(ip): NAME: \(name), EMAIL: \(email), COMMENT: \(comment)") 91 | pjangoHttpResponse("今天提交的次数已达上限啦!")(req, res) 92 | return 93 | } 94 | 95 | let date = Date.init().stringValue 96 | let commentModel = CorpusPostsCommentModel.init() 97 | commentModel.cpcid.strValue = "\(cpid)_\(name)#\(date)@\(ip)" 98 | commentModel.cpid.intValue = cpid 99 | commentModel.floor.intValue = tmpPosts.commentsCount + 1 100 | commentModel.refer_floor.intValue = refer 101 | commentModel.admin.intValue = 0 102 | commentModel.date.strValue = date 103 | commentModel.name.strValue = name 104 | commentModel.email.strValue = email 105 | commentModel.comment.strValue = comment 106 | commentModel.fromIp.strValue = ip 107 | guard CorpusPostsCommentModel.insertObject(commentModel) else { 108 | pjangoHttpResponse("居然出错了!")(req, res) 109 | return 110 | } 111 | pjangoHttpResponse("发表成功!")(req, res) 112 | EventHooks.hookCorpusPostsComment(req: req, cpid: cpid) 113 | } 114 | 115 | } 116 | -------------------------------------------------------------------------------- /Source/Calatrava/Modules/Corpus/CorpusPostsCommentModel.swift: -------------------------------------------------------------------------------- 1 | // 2 | // CorpusPostsCommentModel.swift 3 | // Calatrava 4 | // 5 | // Created by 郑宇琦 on 2017/12/30. 6 | // 7 | 8 | import Foundation 9 | import Pjango 10 | 11 | class CorpusPostsCommentModel: PCModel { 12 | 13 | override var tableName: String { 14 | return "CorpusPostsComment" 15 | } 16 | 17 | var cpcid = PCDataBaseField.init(name: "CPCID", type: .string, length: 128) 18 | var cpid = PCDataBaseField.init(name: "CPID", type: .int) 19 | var floor = PCDataBaseField.init(name: "FLOOR", type: .int) 20 | var refer_floor = PCDataBaseField.init(name: "REFER_FLOOR", type: .int) 21 | var admin = PCDataBaseField.init(name: "ADMIN", type: .int) 22 | var date = PCDataBaseField.init(name: "DATE", type: .string, length: 20) 23 | var name = PCDataBaseField.init(name: "NAME", type: .string, length: 64) 24 | var email = PCDataBaseField.init(name: "EMAIL", type: .string, length: 64) 25 | var comment = PCDataBaseField.init(name: "COMMENT", type: .string, length: 2048) 26 | var fromIp = PCDataBaseField.init(name: "FROM_IP", type: .string, length: 16) 27 | 28 | override func registerFields() -> [PCDataBaseField] { 29 | return [ 30 | cpcid, cpid, floor, refer_floor, admin, date, name, email, comment, fromIp 31 | ] 32 | } 33 | 34 | } 35 | -------------------------------------------------------------------------------- /Source/Calatrava/Modules/Corpus/CorpusPostsListView.swift: -------------------------------------------------------------------------------- 1 | // 2 | // CorpusPostsListView.swift 3 | // Calatrava 4 | // 5 | // Created by 郑宇琦 on 2017/12/29. 6 | // 7 | 8 | import Foundation 9 | import PerfectHTTP 10 | import Pjango 11 | 12 | class CorpusPostsListView: PCListView { 13 | 14 | override var templateName: String? { 15 | return "corpus_posts_list.html" 16 | } 17 | 18 | var currentCID: Int? = nil 19 | 20 | override func requestVaild(_ req: HTTPRequest) -> Bool { 21 | guard let cid = Int(req.urlVariables["cid"] ?? "") else { 22 | return false 23 | } 24 | currentCID = cid 25 | return true 26 | } 27 | 28 | override var listObjectSets: [String : [PCModel]]? { 29 | guard var postsList = CorpusPostsModel.queryObjects() as? [CorpusPostsModel], let cid = currentCID else { 30 | return nil 31 | } 32 | postsList = postsList.filter({ 33 | $0.cid.intValue == cid 34 | }).reversed() 35 | return [ 36 | "_pjango_param_table_corpus": postsList 37 | ] 38 | 39 | } 40 | 41 | override func listUserField(inList list: String, forModel model: PCModel) -> PCViewParam? { 42 | guard let model = model as? CorpusPostsModel else { 43 | return nil 44 | } 45 | return [ 46 | "_pjango_param_table_CorpusPosts_COMMENT": model.commentsCount 47 | ] 48 | } 49 | 50 | 51 | override var viewParam: PCViewParam? { 52 | EventHooks.hookCorpusPostsList(req: currentRequest, cid: currentCID ?? -1) 53 | 54 | let titleMessage = ConfigModel.getValueForKey(.titleMessage) ?? "null" 55 | let corpusModel = (CorpusModel.queryObjects() as? [CorpusModel])?.first(where: { 56 | $0.cid.intValue == currentCID 57 | }) 58 | let postsListMessage = corpusModel?.memo.strValue ?? "null" 59 | let postsListTitle = corpusModel?.title.strValue ?? "null" 60 | 61 | return [ 62 | "_pjango_template_navigation_bar": NavigationBarView.html, 63 | "_pjango_template_footer_bar": FooterBarView.html, 64 | "_pjango_param_title_message": titleMessage, 65 | 66 | "_pjango_url_host": WEBSITE_HOST, 67 | 68 | "_pjango_param_message": postsListMessage, 69 | "_pjango_param_list_title": postsListTitle, 70 | ] 71 | } 72 | } 73 | -------------------------------------------------------------------------------- /Source/Calatrava/Modules/Corpus/CorpusPostsLoveHandle.swift: -------------------------------------------------------------------------------- 1 | // 2 | // CorpusPostsLoveHandle.swift 3 | // Calatrava 4 | // 5 | // Created by 郑宇琦 on 2017/12/30. 6 | // 7 | 8 | import Foundation 9 | import Pjango 10 | 11 | func corpusPostsLoveHandle() -> PCUrlHandle { 12 | 13 | return pjangoHttpResponse { req, res in 14 | guard let postsList = (CorpusPostsModel.queryObjects() as? [CorpusPostsModel]) else { 15 | pjangoHttpResponse("居然出错了!")(req, res) 16 | return 17 | } 18 | guard let cpid = Int(req.getUrlParam(key: "cpid") ?? "") else { 19 | pjangoHttpResponse("AI娘无法识别你的请求哦!")(req, res) 20 | return 21 | } 22 | let tmpPosts = postsList.first(where: { $0.cpid.intValue == cpid }) 23 | guard let posts = tmpPosts else { 24 | pjangoHttpResponse("目标博文不存在!")(req, res) 25 | return 26 | } 27 | let ip = req.header(.custom(name: "watchdog_ip")) ?? req.remoteAddress.host 28 | let key = "\(cpid)@\(ip)" 29 | guard !corpusPostsLoveDict.contains(key) else { 30 | pjangoHttpResponse("今天您已经赞过这篇文章啦!")(req, res) 31 | return 32 | } 33 | posts.love.intValue += 1 34 | guard CorpusPostsModel.updateObject(posts) else { 35 | pjangoHttpResponse("居然出错了!")(req, res) 36 | return 37 | } 38 | corpusPostsLoveDict.insert(key) 39 | pjangoHttpResponse("已经收到你的赞啦!")(req, res) 40 | EventHooks.hookCorpusPostsLove(req: req, cpid: cpid) 41 | } 42 | 43 | } 44 | -------------------------------------------------------------------------------- /Source/Calatrava/Modules/Corpus/CorpusPostsModel.swift: -------------------------------------------------------------------------------- 1 | // 2 | // CorpusPostsModel.swift 3 | // Calatrava 4 | // 5 | // Created by 郑宇琦 on 2017/12/29. 6 | // 7 | 8 | import Foundation 9 | import Pjango 10 | 11 | class CorpusPostsModel: PCModel { 12 | 13 | override var tableName: String { 14 | return "CorpusPosts" 15 | } 16 | 17 | var cpid = PCDataBaseField.init(name: "CPID", type: .int) 18 | var cid = PCDataBaseField.init(name: "CID", type: .int) 19 | var title = PCDataBaseField.init(name: "TITLE", type: .string, length: 64) 20 | var date = PCDataBaseField.init(name: "DATE", type: .string, length: 16) 21 | var read = PCDataBaseField.init(name: "READ", type: .int) 22 | var love = PCDataBaseField.init(name: "LOVE", type: .int) 23 | 24 | var commentsCount: Int { 25 | if let comments = (CorpusPostsCommentModel.queryObjects() as? [CorpusPostsCommentModel])?.filter({ $0.cpid.intValue == cpid.intValue }) { 26 | return comments.count 27 | } else { 28 | return 0 29 | } 30 | } 31 | 32 | override func registerFields() -> [PCDataBaseField] { 33 | return [ 34 | cpid, cid, title, date, read, love 35 | ] 36 | } 37 | 38 | override class var cacheTime: TimeInterval? { 39 | return 60 40 | } 41 | 42 | } 43 | -------------------------------------------------------------------------------- /Source/Calatrava/Modules/Corpus/CorpusPostsTextView.swift: -------------------------------------------------------------------------------- 1 | // 2 | // CorpusPostsTextView.swift 3 | // Calatrava 4 | // 5 | // Created by 郑宇琦 on 2017/12/30. 6 | // 7 | 8 | import Foundation 9 | import Pjango 10 | 11 | class CorpusTextView: PCDetailView { 12 | 13 | var cpid: Int 14 | 15 | override var templateName: String? { 16 | return "corpus/\(cpid).html" 17 | } 18 | 19 | init(cpid: Int) { 20 | self.cpid = cpid 21 | } 22 | 23 | required init() { 24 | self.cpid = -1 25 | } 26 | 27 | } 28 | -------------------------------------------------------------------------------- /Source/Calatrava/Modules/Corpus/CorpusPostsView.swift: -------------------------------------------------------------------------------- 1 | // 2 | // CorpusPostsView.swift 3 | // Calatrava 4 | // 5 | // Created by 郑宇琦 on 2017/12/30. 6 | // 7 | 8 | import Foundation 9 | import PerfectHTTP 10 | import Pjango 11 | 12 | class CorpusPostsView: PCListView { 13 | 14 | override var templateName: String? { 15 | return "corpus_posts.html" 16 | } 17 | 18 | var currentPosts: CorpusPostsModel? = nil 19 | 20 | override func requestVaild(_ req: HTTPRequest) -> Bool { 21 | guard let req = currentRequest, 22 | let cpidStr = req.urlVariables["cpid"], 23 | let cpid = Int(cpidStr) else { 24 | return false 25 | } 26 | guard let posts = (CorpusPostsModel.queryObjects() as? [CorpusPostsModel])?.first(where: { $0.cpid.intValue == cpid }) else { 27 | return false 28 | } 29 | currentPosts = posts 30 | return true 31 | } 32 | 33 | override func requestInvaildHandle() -> PCUrlHandle? { 34 | return pjangoHttpRedirect(name: "error.404") 35 | } 36 | 37 | override func listUserField(inList list: String, forModel model: PCModel) -> PCViewParam? { 38 | guard let model = model as? CorpusPostsCommentModel else { 39 | return nil 40 | } 41 | let hasRefer = model.refer_floor.intValue > 0 42 | if hasRefer, let refer = (CorpusPostsCommentModel.queryObjects() as? [CorpusPostsCommentModel])?.first(where: { $0.cpid.intValue == model.cpid.intValue && $0.floor.intValue == model.refer_floor.intValue }) { 43 | var fields: [String : Any] = [ 44 | "_pjango_param_table_CorpusPostsComment_ISADMIN": model.admin.intValue == 1 ? 1 : 0, 45 | "_pjango_param_table_CorpusPostsComment_HAVE_REFER": 1, 46 | "_pjango_param_table_CorpusPostsComment_REFER_ISADMIN": refer.admin.intValue == 1 ? 1 : 0, 47 | "_pjango_param_table_CorpusPostsComment_REFER_NAME": refer.name.strValue, 48 | "_pjango_param_table_CorpusPostsComment_REFER_DATE": refer.date.strValue, 49 | ] 50 | if refer.refer_floor.intValue > 0 { 51 | fields["_pjango_param_table_CorpusPostsComment_REFER_COMMENT"] = "[引用 \(refer.refer_floor.intValue) 楼]\n" + refer.comment.strValue 52 | } else { 53 | fields["_pjango_param_table_CorpusPostsComment_REFER_COMMENT"] = refer.comment.strValue 54 | } 55 | return fields 56 | } else { 57 | return [ 58 | "_pjango_param_table_CorpusPostsComment_ISADMIN": model.admin.intValue == 1 ? 1 : 0, 59 | "_pjango_param_table_CorpusPostsComment_HAVE_REFER": 0, 60 | "_pjango_param_table_CorpusPostsComment_REFER_ISADMIN": 0, 61 | ] 62 | } 63 | } 64 | 65 | override var listObjectSets: [String : [PCModel]]? { 66 | guard var commentList = CorpusPostsCommentModel.queryObjects() as? [CorpusPostsCommentModel] else { 67 | return nil 68 | } 69 | commentList = commentList.filter({ $0.cpid.intValue == currentPosts?.cpid.intValue }) 70 | return [ 71 | "_pjango_param_table_comment": commentList, 72 | ] 73 | } 74 | 75 | override var viewParam: PCViewParam? { 76 | guard let posts = currentPosts else { 77 | return nil 78 | } 79 | EventHooks.hookCorpusPostsRead(req: currentRequest, cpid: posts.cpid.intValue) 80 | 81 | posts.read.intValue += 1 82 | CorpusPostsModel.updateObject(posts) 83 | 84 | let title = posts.title.strValue 85 | let cpid = posts.cpid.intValue 86 | let date = posts.date.strValue 87 | let read = posts.read.intValue 88 | let comment = posts.commentsCount 89 | let love = posts.love.intValue 90 | 91 | let titleMessage = ConfigModel.getValueForKey(.titleMessage) ?? "null" 92 | let name = ConfigModel.getValueForKey(.name) ?? "null" 93 | 94 | return [ 95 | "_pjango_template_navigation_bar": NavigationBarView.html, 96 | "_pjango_template_footer_bar": FooterBarView.html, 97 | "_pjango_param_title_message": titleMessage, 98 | "_pjango_param_name": name, 99 | 100 | "_pjango_param_host": WEBSITE_HOST, 101 | 102 | "_pjango_param_posts_title": title, 103 | "_pjango_param_posts_cpid": cpid, 104 | "_pjango_param_posts_date": date, 105 | "_pjango_param_posts_read": read, 106 | "_pjango_param_posts_comment": comment, 107 | "_pjango_param_posts_love": love, 108 | "_pjango_template_posts_text": CorpusTextView.init(cpid: posts.cpid.intValue).getTemplate(), 109 | ] 110 | } 111 | } 112 | -------------------------------------------------------------------------------- /Source/Calatrava/Modules/Index/IndexView.swift: -------------------------------------------------------------------------------- 1 | // 2 | // IndexView.swift 3 | // Calatrava 4 | // 5 | // Created by 郑宇琦 on 2017/6/24. 6 | // Copyright © 2017年 郑宇琦. All rights reserved. 7 | // 8 | 9 | import Foundation 10 | import Pjango 11 | 12 | class IndexView: PCDetailView { 13 | 14 | override var templateName: String? { 15 | return "index.html" 16 | } 17 | 18 | override var viewParam: PCViewParam? { 19 | EventHooks.hookIndex(req: currentRequest) 20 | 21 | let titleMessage = ConfigModel.getValueForKey(.titleMessage) ?? "null" 22 | let name = ConfigModel.getValueForKey(.name) ?? "null" 23 | let indexMessage = ConfigModel.getValueForKey(.indexMessage) ?? "null" 24 | 25 | return [ 26 | "_pjango_template_navigation_bar": NavigationBarView.html, 27 | "_pjango_template_footer_bar": FooterBarView.html, 28 | "_pjango_param_title_message": titleMessage, 29 | "_pjango_param_name": name, 30 | "_pjango_param_message": indexMessage, 31 | 32 | "_pjango_url_posts_list": pjangoUrlReverse(host: WEBSITE_HOST, name: "posts.list") ?? "", 33 | "_pjango_url_corpus_list": pjangoUrlReverse(host: WEBSITE_HOST, name: "corpus.list") ?? "", 34 | "_pjango_url_about": pjangoUrlReverse(host: WEBSITE_HOST, name: "about") ?? "", 35 | "_pjango_url_update": pjangoUrlReverse(host: WEBSITE_HOST, name: "update") ?? "", 36 | "_pjango_url_data": pjangoUrlReverse(host: WEBSITE_HOST, name: "report.daily.today") ?? "", 37 | "_pjango_url_modules": pjangoUrlReverse(host: WEBSITE_HOST, name: "modules") ?? "", 38 | ] 39 | } 40 | } 41 | -------------------------------------------------------------------------------- /Source/Calatrava/Modules/Instagram/InstagramCurlHandle.swift: -------------------------------------------------------------------------------- 1 | // 2 | // InstagramCurlHandle.swift 3 | // Calatrava 4 | // 5 | // Created by 郑宇琦 on 2018/1/20. 6 | // 7 | 8 | import Foundation 9 | import PerfectLib 10 | import Pjango 11 | import PjangoPostman 12 | import SwiftyJSON 13 | 14 | enum InstagramCurlAction: String { 15 | case html = "html" 16 | case image = "image" 17 | } 18 | 19 | func InstagramCurlHandle() -> PCUrlHandle { 20 | return { req, res in 21 | guard let encodedStr = req.getUrlParam(key: "value"), let decodedData = encodedStr.decode(.base64url), let decryptedData = PostmanEncryptor.decode(bytes: decodedData), let paramStr = String.init(bytes: decryptedData, encoding: .utf8) else { 22 | pjangoHttpResponse("")(req, res) 23 | return 24 | } 25 | let json = JSON.init(parseJSON: paramStr) 26 | guard json != JSON.null else { 27 | pjangoHttpResponse("")(req, res) 28 | return 29 | } 30 | guard json["key"].string == PostmanConfigModel.pKey else { 31 | pjangoHttpResponse("")(req, res) 32 | return 33 | } 34 | guard let actionStr = json["action"].string, let action = InstagramCurlAction.init(rawValue: actionStr) else { 35 | pjangoHttpResponse("")(req, res) 36 | return 37 | } 38 | let ip = req.header(.custom(name: "watchdog_ip")) ?? req.remoteAddress.host 39 | let port = "\(UInt16(req.header(.custom(name: "watchdog_port")) ?? "") ?? req.remoteAddress.port)" 40 | switch action { 41 | case .html: 42 | guard let url = json["url"].string else { 43 | pjangoHttpResponse("")(req, res) 44 | return 45 | } 46 | guard let bytes = PostmanCURL.getBytes(url: url, clientIp: ip, clientPort: port) else { 47 | pjangoHttpResponse("")(req, res) 48 | return 49 | } 50 | pjangoHttpResponse(bytes)(req, res) 51 | case .image: 52 | guard let url = json["url"].string else { 53 | pjangoHttpResponse("")(req, res) 54 | return 55 | } 56 | guard let bytes = PostmanCURL.getBytes(url: url, clientIp: ip, clientPort: port) else { 57 | pjangoHttpResponse("")(req, res) 58 | return 59 | } 60 | if let base64 = Array(url.utf8).digest(.md5)?.encode(.base64url), let filename = String.init(bytes: base64, encoding: .utf8) { 61 | let path = "\(PJANGO_STATIC_URL)/img/instagram/\(filename).png" 62 | let file = File.init(path) 63 | if !file.exists { 64 | do { 65 | try file.open(.write, permissions: .rxOther) 66 | defer { 67 | file.close() 68 | } 69 | try file.write(bytes: bytes) 70 | file.close() 71 | } catch { 72 | logger.error("Cache Image Error![url: \(url)][path: \(path)]") 73 | } 74 | } 75 | } 76 | pjangoHttpResponse(bytes)(req, res) 77 | } 78 | } 79 | } 80 | -------------------------------------------------------------------------------- /Source/Calatrava/Modules/Instagram/InstagramFeedModel.swift: -------------------------------------------------------------------------------- 1 | // 2 | // InstagramFeedModel.swift 3 | // Calatrava 4 | // 5 | // Created by 郑宇琦 on 2018/1/19. 6 | // 7 | 8 | import Foundation 9 | import Pjango 10 | import PjangoPostman 11 | 12 | class InstagramFeedModel: PCModel { 13 | 14 | override var tableName: String { 15 | return "InstagramFeed" 16 | } 17 | 18 | var id = PCDataBaseField.init(name: "ID", type: .string, length: 256) 19 | var name = PCDataBaseField.init(name: "NAME", type: .string, length: 256) 20 | var full_name = PCDataBaseField.init(name: "FULL_NAME", type: .string, length: 256) 21 | var bio = PCDataBaseField.init(name: "BIO", type: .string, length: 1024) 22 | var url = PCDataBaseField.init(name: "URL", type: .string, length: 1024) 23 | var caption = PCDataBaseField.init(name: "CAPTION", type: .string, length: 1024) 24 | var head = PCDataBaseField.init(name: "HEAD", type: .string, length: 1024) 25 | var image = PCDataBaseField.init(name: "IMAGE", type: .string, length: 1024) 26 | var big_image = PCDataBaseField.init(name: "BIG_IMAGE", type: .string, length: 1024) 27 | var date = PCDataBaseField.init(name: "DATE", type: .string, length: 20) 28 | 29 | var headSource: String { 30 | return PostmanCURL.instagramImageToPostmanURL(url: head.strValue) ?? "http:///" 31 | } 32 | 33 | var imageSource: String { 34 | return PostmanCURL.instagramImageToPostmanURL(url: image.strValue) ?? "http:///" 35 | } 36 | 37 | var bigImageSource: String { 38 | return PostmanCURL.instagramImageToPostmanURL(url: big_image.strValue) ?? "http:///" 39 | } 40 | 41 | static var cacheFeed: [InstagramFeedModel]? = InstagramFeedModel.queryObjects(ext: (true, "ORDER BY date DESC")) as? [InstagramFeedModel] 42 | 43 | required init() { } 44 | 45 | init(userUrl: String, info: InstagramInfo, node: InstagramMediaNode) { 46 | super.init() 47 | id.strValue = info.id 48 | name.strValue = info.username 49 | full_name.strValue = info.full_name 50 | bio.strValue = info.biography 51 | url.strValue = userUrl 52 | caption.strValue = node.caption 53 | head.strValue = info.profile_pic_url 54 | image.strValue = node.thumbnail_src 55 | big_image.strValue = node.display_src 56 | date.strValue = Date.init(timeIntervalSince1970: TimeInterval(node.date)).stringValue 57 | } 58 | 59 | override func registerFields() -> [PCDataBaseField] { 60 | return [ 61 | id, name, full_name, bio, url, caption, head, image, big_image, date 62 | ] 63 | } 64 | 65 | override class var cacheTime: TimeInterval? { 66 | return 60 * 15 67 | } 68 | 69 | static func recache() { 70 | cacheFeed = nil 71 | guard let feed = InstagramFeedModel.queryObjects(ext: (true, "ORDER BY date DESC")) as? [InstagramFeedModel] else { 72 | return 73 | } 74 | cacheFeed = feed 75 | } 76 | } 77 | -------------------------------------------------------------------------------- /Source/Calatrava/Modules/Instagram/InstagramListView.swift: -------------------------------------------------------------------------------- 1 | // 2 | // InstagramListView.swift 3 | // Calatrava 4 | // 5 | // Created by 郑宇琦 on 2018/1/19. 6 | // 7 | 8 | import Foundation 9 | import Pjango 10 | import PjangoPostman 11 | 12 | class InstagramListView: PCListView { 13 | 14 | override var templateName: String? { 15 | return "instagram_list.html" 16 | } 17 | 18 | var displayFeed: [InstagramFeedModel]? 19 | 20 | override var listObjectSets: [String : [PCModel]]? { 21 | defer { 22 | displayFeed = nil 23 | } 24 | return [ 25 | "_pjango_param_table_instagram_feed": displayFeed ?? [], 26 | "_pjango_param_table_instagram_user": InstagramUserModel.queryObjects() ?? [], 27 | ] 28 | } 29 | 30 | override func listUserField(inList list: String, forModel model: PCModel) -> PCViewParam? { 31 | if list == "_pjango_param_table_instagram_feed" { 32 | guard let feed = model as? InstagramFeedModel else { 33 | return nil 34 | } 35 | return [ 36 | "_pjango_param_table_InstagramFeed_IMAGE_SOURCE": feed.imageSource, 37 | "_pjango_param_table_InstagramFeed_BIG_IMAGE_SOURCE": feed.bigImageSource, 38 | "_pjango_param_table_InstagramFeed_HEAD_SOURCE": feed.headSource, 39 | ] 40 | } else if list == "_pjango_param_table_instagram_user" { 41 | guard let user = model as? InstagramUserModel else { 42 | return nil 43 | } 44 | if let id = currentRequest?.getUrlParam(key: "id"), id == user.iid.strValue { 45 | return [ 46 | "_pjango_param_table_InstagramUser_HEAD_SOURCE": PostmanCURL.instagramImageToPostmanURL(url: user.head.strValue) ?? "http:///", 47 | "_pjango_param_table_InstagramUser_WATCH_BUTTON_TEXT": "取消只看TA", 48 | "_pjango_param_table_InstagramUser_WATCH_BUTTON_ID": "", 49 | ] 50 | } else { 51 | return [ 52 | "_pjango_param_table_InstagramUser_HEAD_SOURCE": PostmanCURL.instagramImageToPostmanURL(url: user.head.strValue) ?? "http:///", 53 | "_pjango_param_table_InstagramUser_WATCH_BUTTON_TEXT": "只看TA", 54 | "_pjango_param_table_InstagramUser_WATCH_BUTTON_ID": user.iid.strValue, 55 | ] 56 | } 57 | } else { 58 | return nil 59 | } 60 | } 61 | 62 | override var viewParam: PCViewParam? { 63 | guard let request = currentRequest else { 64 | return nil 65 | } 66 | guard var filteredFeed = InstagramFeedModel.cacheFeed else { 67 | return nil 68 | } 69 | filteredFeed.sort { (feedA, feedB) -> Bool in 70 | feedA.date.strValue > feedB.date.strValue 71 | } 72 | var page = 1 73 | if let pageParam = Int(request.getUrlParam(key: "page") ?? ""), pageParam > 0 { 74 | page = pageParam 75 | } 76 | var id = "" 77 | if let idParam = request.getUrlParam(key: "id"), idParam != "" { 78 | filteredFeed = filteredFeed.filter { $0.id.strValue == idParam } 79 | id = idParam 80 | } 81 | let eachPageFeedCount = 12 82 | var insFeed = [InstagramFeedModel].init() 83 | let begin = eachPageFeedCount * (page - 1) 84 | let end = eachPageFeedCount * page - 1 85 | if (begin < filteredFeed.count) { 86 | let trueEnd = min(filteredFeed.count - 1, end) 87 | insFeed = Array(filteredFeed[begin..<(trueEnd + 1)]) 88 | } 89 | displayFeed = insFeed 90 | 91 | let titleMessage = ConfigModel.getValueForKey(.titleMessage) ?? "null" 92 | let instagramMessage = ConfigModel.getValueForKey(.instagramMessage) ?? "null" 93 | 94 | EventHooks.hookInstagram(req: request) 95 | 96 | return [ 97 | "_pjango_template_navigation_bar": NavigationBarView.html, 98 | "_pjango_template_footer_bar": FooterBarView.html, 99 | "_pjango_param_title_message": titleMessage, 100 | "_pjango_url_host": WEBSITE_HOST, 101 | 102 | "_pjango_param_message": instagramMessage, 103 | "_pjango_param_param_page": page, 104 | "_pjango_param_param_id": id, 105 | "_pjango_param_param_page_total": max(0, filteredFeed.count - 1) / eachPageFeedCount + 1, 106 | "_pjango_param_param_total_count": filteredFeed.count, 107 | ] 108 | } 109 | 110 | } 111 | -------------------------------------------------------------------------------- /Source/Calatrava/Modules/Instagram/InstagramUserModel.swift: -------------------------------------------------------------------------------- 1 | // 2 | // InstagramUserModel.swift 3 | // Poster 4 | // 5 | // Created by 郑宇琦 on 2018/1/18. 6 | // 7 | 8 | import Foundation 9 | import Pjango 10 | 11 | class InstagramUserModel: PCModel { 12 | 13 | override var tableName: String { 14 | return "InstagramUser" 15 | } 16 | 17 | var iid = PCDataBaseField.init(name: "IID", type: .string, length: 64) 18 | var url = PCDataBaseField.init(name: "URL", type: .string, length: 1024) 19 | var memo = PCDataBaseField.init(name: "MEMO", type: .string, length: 1024) 20 | var name = PCDataBaseField.init(name: "NAME", type: .string, length: 256, notNull: true, defaultValue: "null") 21 | var full_name = PCDataBaseField.init(name: "FULL_NAME", type: .string, length: 256, notNull: true, defaultValue: "null") 22 | var bio = PCDataBaseField.init(name: "BIO", type: .string, length: 1024, notNull: true, defaultValue: "null") 23 | var head = PCDataBaseField.init(name: "HEAD", type: .string, length: 1024, notNull: true, defaultValue: "http:///") 24 | var updateDate = PCDataBaseField.init(name: "UPDATE_DATE", type: .string, length: 20, notNull: true, defaultValue: "1970-01-01 08:00") 25 | 26 | override func registerFields() -> [PCDataBaseField] { 27 | return [ 28 | iid, url, memo, name, full_name, bio, head, updateDate 29 | ] 30 | } 31 | 32 | override class var cacheTime: TimeInterval? { 33 | return 60 34 | } 35 | 36 | } 37 | -------------------------------------------------------------------------------- /Source/Calatrava/Modules/Modules/ModuleListView.swift: -------------------------------------------------------------------------------- 1 | // 2 | // ModuleListView.swift 3 | // Calatrava 4 | // 5 | // Created by 郑宇琦 on 2018/12/6. 6 | // Copyright © 2017年 郑宇琦. All rights reserved. 7 | // 8 | 9 | import Foundation 10 | import Pjango 11 | 12 | class ModuleListView: PCListView { 13 | 14 | override var templateName: String? { 15 | return "modules.html" 16 | } 17 | 18 | override var listObjectSets: [String : [PCModel]]? { 19 | guard let moduleList = ModuleModel.queryObjects() else { 20 | return nil 21 | } 22 | return [ 23 | "_pjango_param_table_module": moduleList 24 | ] 25 | } 26 | 27 | override func listUserField(inList list: String, forModel model: PCModel) -> PCViewParam? { 28 | guard let model = model as? ModuleModel else { 29 | return nil 30 | } 31 | return [ 32 | "_pjango_param_table_Module_REALURL": model.realUrl 33 | ] 34 | } 35 | 36 | override var viewParam: PCViewParam? { 37 | EventHooks.hookModuleList(req: currentRequest) 38 | 39 | let titleMessage = ConfigModel.getValueForKey(.titleMessage) ?? "null" 40 | let name = ConfigModel.getValueForKey(.name) ?? "null" 41 | let indexMessage = ConfigModel.getValueForKey(.indexMessage) ?? "null" 42 | 43 | return [ 44 | "_pjango_template_navigation_bar": NavigationBarView.html, 45 | "_pjango_template_footer_bar": FooterBarView.html, 46 | "_pjango_param_title_message": titleMessage, 47 | "_pjango_param_name": name, 48 | "_pjango_param_message": indexMessage, 49 | "_pjango_url_search": pjangoUrlReverse(host: WEBSITE_HOST, name: "search") ?? "" 50 | ] 51 | } 52 | } 53 | 54 | -------------------------------------------------------------------------------- /Source/Calatrava/Modules/Modules/ModuleModel.swift: -------------------------------------------------------------------------------- 1 | // 2 | // ModuleModel.swift 3 | // Calatrava 4 | // 5 | // Created by 郑宇琦 on 2018/12/6. 6 | // Copyright © 2017年 郑宇琦. All rights reserved. 7 | // 8 | 9 | import Foundation 10 | import Pjango 11 | 12 | class ModuleModel: PCModel { 13 | 14 | override var tableName: String { 15 | return "Module" 16 | } 17 | 18 | var title = PCDataBaseField.init(name: "TITLE", type: .string, length: 64) 19 | var icon = PCDataBaseField.init(name: "ICON", type: .string, length: 64) 20 | var memo = PCDataBaseField.init(name: "MEMO", type: .string, length: 128) 21 | var url = PCDataBaseField.init(name: "URL", type: .string, length: 128) 22 | var searchable = PCDataBaseField.init(name: "SEARCHABLE", type: .int) 23 | 24 | var realUrl: String { 25 | get { 26 | let urlDict = [ 27 | "_pjango_url_posts_list": pjangoUrlReverse(host: WEBSITE_HOST, name: "posts.list") ?? "", 28 | "_pjango_url_corpus_list": pjangoUrlReverse(host: WEBSITE_HOST, name: "corpus.list") ?? "", 29 | "_pjango_url_project_list": pjangoUrlReverse(host: WEBSITE_HOST, name: "project") ?? "", 30 | "_pjango_url_instagram_list": pjangoUrlReverse(host: WEBSITE_HOST, name: "instagram.list") ?? "", 31 | "_pjango_url_bilibili_list": pjangoUrlReverse(host: WEBSITE_HOST, name: "bilibili.list") ?? "", 32 | "_pjango_url_update": pjangoUrlReverse(host: WEBSITE_HOST, name: "update") ?? "", 33 | "_pjango_url_report": pjangoUrlReverse(host: WEBSITE_HOST, name: "report.daily.today") ?? "", 34 | "_pjango_url_about": pjangoUrlReverse(host: WEBSITE_HOST, name: "about") ?? "", 35 | ] 36 | return urlDict[self.url.strValue] ?? "" 37 | } 38 | } 39 | 40 | override func registerFields() -> [PCDataBaseField] { 41 | return [ 42 | title, icon, memo, url, searchable 43 | ] 44 | } 45 | 46 | override class var cacheTime: TimeInterval? { 47 | return 60 48 | } 49 | 50 | 51 | } 52 | -------------------------------------------------------------------------------- /Source/Calatrava/Modules/Posts/PostsArchiveView.swift: -------------------------------------------------------------------------------- 1 | // 2 | // PostsArchiveView.swift 3 | // Calatrava 4 | // 5 | // Created by 郑宇琦 on 2018/6/18. 6 | // Copyright © 2017年 郑宇琦. All rights reserved. 7 | // 8 | 9 | import Foundation 10 | import Pjango 11 | 12 | class PostsArchiveView: PCListView { 13 | 14 | override var templateName: String? { 15 | return "posts_archive.html" 16 | } 17 | 18 | override var listObjectSets: [String : [PCModel]]? { 19 | guard var postsList = PostsModel.queryObjects(ext: (true, "ORDER BY date DESC")) as? [PostsModel] else { 20 | return nil 21 | } 22 | postsList = postsList.map { posts in 23 | posts.date.value = posts.date.strValue.components(separatedBy: " ")[0] 24 | 25 | return posts 26 | } 27 | 28 | return [ 29 | "_pjango_param_table_posts": postsList 30 | ] 31 | } 32 | 33 | override var viewParam: PCViewParam? { 34 | EventHooks.hookPostsArchive(req: currentRequest) 35 | 36 | let titleMessage = ConfigModel.getValueForKey(.titleMessage) ?? "null" 37 | 38 | return [ 39 | "_pjango_template_navigation_bar": NavigationBarView.html, 40 | "_pjango_template_footer_bar": FooterBarView.html, 41 | "_pjango_param_title_message": titleMessage, 42 | 43 | "_pjango_url_host": WEBSITE_HOST, 44 | ] 45 | } 46 | } 47 | -------------------------------------------------------------------------------- /Source/Calatrava/Modules/Posts/PostsCommentHandle.swift: -------------------------------------------------------------------------------- 1 | // 2 | // PostsCommentHandle.swift 3 | // Calatrava-Blog 4 | // 5 | // Created by 郑宇琦 on 2017/12/20. 6 | // 7 | 8 | import Foundation 9 | import Pjango 10 | import SwiftyJSON 11 | 12 | func postsCommentHandle() -> PCUrlHandle { 13 | 14 | func processRefer(comment: inout String, posts: PostsModel) -> Int { 15 | guard let begin = comment.range(of: "!*<引用 ")?.upperBound else { 16 | return 0 17 | } 18 | guard let end = comment.range(of: " 楼>*!")?.lowerBound, let endUpper = comment.range(of: " 楼>*!")?.upperBound else { 19 | return 0 20 | } 21 | if let floor = Int(comment[begin.. 2 else { 63 | pjangoHttpResponse("昵称太短啦!")(req, res) 64 | return 65 | } 66 | guard email.contains(string: "@"), email.contains(string: "."), email.count > 5 else { 67 | pjangoHttpResponse("邮箱地址不合法!")(req, res) 68 | return 69 | } 70 | let refer = processRefer(comment: &comment, posts: tmpPosts) 71 | guard comment.count > 2 else { 72 | pjangoHttpResponse("评论太短啦!")(req, res) 73 | return 74 | } 75 | let ip = req.header(.custom(name: "watchdog_ip")) ?? req.remoteAddress.host 76 | if let lastTime = postsCommentLastTimeDict[ip] { 77 | guard Date.init().timeIntervalSince1970 - lastTime > 1 else { 78 | logger.info("Comment - Frequency anomaly @ \(ip): NAME: \(name), EMAIL: \(email), COMMENT: \(comment)") 79 | pjangoHttpResponse("你提交得太频繁啦!")(req, res) 80 | return 81 | } 82 | } 83 | postsCommentLastTimeDict[ip] = Date.init().timeIntervalSince1970 84 | if let count = postsCommentDailyDict[ip] { 85 | postsCommentDailyDict[ip] = count + 1 86 | } else { 87 | postsCommentDailyDict[ip] = 1 88 | } 89 | if let count = postsCommentDailyDict[ip], count >= 960 { 90 | logger.info("Comment - Quantity anomaly @ \(ip): NAME: \(name), EMAIL: \(email), COMMENT: \(comment)") 91 | pjangoHttpResponse("今天提交的次数已达上限啦!")(req, res) 92 | return 93 | } 94 | 95 | let date = Date.init().stringValue 96 | let commentModel = PostsCommentModel.init() 97 | commentModel.pcid.strValue = "\(pid)_\(name)#\(date)@\(ip)" 98 | commentModel.pid.intValue = pid 99 | commentModel.floor.intValue = tmpPosts.commentsCount + 1 100 | commentModel.refer_floor.intValue = refer 101 | commentModel.admin.intValue = 0 102 | commentModel.date.strValue = date 103 | commentModel.name.strValue = name 104 | commentModel.email.strValue = email 105 | commentModel.comment.strValue = comment 106 | commentModel.fromIp.strValue = ip 107 | guard PostsCommentModel.insertObject(commentModel) else { 108 | pjangoHttpResponse("居然出错了!")(req, res) 109 | return 110 | } 111 | pjangoHttpResponse("发表成功!")(req, res) 112 | EventHooks.hookPostsComment(req: req, pid: pid) 113 | } 114 | } 115 | -------------------------------------------------------------------------------- /Source/Calatrava/Modules/Posts/PostsCommentModel.swift: -------------------------------------------------------------------------------- 1 | // 2 | // PostsCommentModel.swift 3 | // Calatrava-Blog 4 | // 5 | // Created by 郑宇琦 on 2017/12/18. 6 | // 7 | 8 | import Foundation 9 | import Pjango 10 | 11 | class PostsCommentModel: PCModel { 12 | 13 | override var tableName: String { 14 | return "PostsComment" 15 | } 16 | 17 | var pcid = PCDataBaseField.init(name: "PCID", type: .string, length: 128) 18 | var pid = PCDataBaseField.init(name: "PID", type: .int) 19 | var floor = PCDataBaseField.init(name: "FLOOR", type: .int) 20 | var refer_floor = PCDataBaseField.init(name: "REFER_FLOOR", type: .int) 21 | var admin = PCDataBaseField.init(name: "ADMIN", type: .int) 22 | var date = PCDataBaseField.init(name: "DATE", type: .string, length: 20) 23 | var name = PCDataBaseField.init(name: "NAME", type: .string, length: 64) 24 | var email = PCDataBaseField.init(name: "EMAIL", type: .string, length: 64) 25 | var comment = PCDataBaseField.init(name: "COMMENT", type: .string, length: 2048) 26 | var fromIp = PCDataBaseField.init(name: "FROM_IP", type: .string, length: 16) 27 | 28 | override func registerFields() -> [PCDataBaseField] { 29 | return [ 30 | pcid, pid, floor, refer_floor, admin, date, name, email, comment, fromIp 31 | ] 32 | } 33 | 34 | } 35 | -------------------------------------------------------------------------------- /Source/Calatrava/Modules/Posts/PostsHistoryModel.swift: -------------------------------------------------------------------------------- 1 | // 2 | // PostsHistoryModel.swift 3 | // Calatrava 4 | // 5 | // Created by 郑宇琦 on 2017/6/25. 6 | // Copyright © 2017年 郑宇琦. All rights reserved. 7 | // 8 | 9 | import Foundation 10 | import Pjango 11 | 12 | class PostsHistoryModel: PCModel { 13 | 14 | override var tableName: String { 15 | return "PostsHistory" 16 | } 17 | 18 | var date = PCDataBaseField.init(name: "DATE", type: .string, length: 10) 19 | var content = PCDataBaseField.init(name: "CONTENT", type: .string, length: 64) 20 | 21 | override func registerFields() -> [PCDataBaseField] { 22 | return [ 23 | date, content 24 | ] 25 | } 26 | 27 | override class var cacheTime: TimeInterval? { 28 | return 60 29 | } 30 | 31 | } 32 | 33 | -------------------------------------------------------------------------------- /Source/Calatrava/Modules/Posts/PostsListView.swift: -------------------------------------------------------------------------------- 1 | // 2 | // PostsListView.swift 3 | // Calatrava 4 | // 5 | // Created by 郑宇琦 on 2017/6/24. 6 | // Copyright © 2017年 郑宇琦. All rights reserved. 7 | // 8 | 9 | import Foundation 10 | import Pjango 11 | 12 | class PostsListView: PCListView { 13 | 14 | override var templateName: String? { 15 | return "posts_list.html" 16 | } 17 | 18 | var displayPosts: [PostsModel]? 19 | 20 | override var listObjectSets: [String : [PCModel]]? { 21 | return [ 22 | "_pjango_param_table_posts": displayPosts ?? [] 23 | ] 24 | 25 | } 26 | 27 | override func listUserField(inList list: String, forModel model: PCModel) -> PCViewParam? { 28 | guard let model = model as? PostsModel else { 29 | return nil 30 | } 31 | return [ 32 | "_pjango_param_table_Posts_TAGHTML": model.tagModel.map { $0.toViewParam() }, 33 | "_pjango_param_table_Posts_COMMENT": model.commentsCount 34 | ] 35 | } 36 | 37 | 38 | override var viewParam: PCViewParam? { 39 | var listTitle = "博文列表" 40 | guard let req = currentRequest, var postsList = PostsModel.queryObjects(ext: (true, "ORDER BY date DESC")) as? [PostsModel] else { 41 | return nil 42 | } 43 | 44 | let tag = req.getUrlParam(key: "tag") 45 | let keyword = req.getUrlParam(key: "keyword") 46 | if tag == nil, keyword == nil { 47 | listTitle = "全部博文" 48 | } else if let tag = tag, keyword == nil { 49 | listTitle = "标签是 `\(tag)` 的博文" 50 | } else if tag == nil, let keyword = keyword { 51 | listTitle = "包含 `\(keyword)` 关键字的博文" 52 | } else if let tag = tag, let keyword = keyword { 53 | listTitle = "标签是 `\(tag)` 且包含 `\(keyword)` 关键字的博文" 54 | } 55 | 56 | var needHooks = false 57 | if let tag = tag { 58 | needHooks = true 59 | postsList = postsList.filter { posts in 60 | return posts.tag.strValue.components(separatedBy: "|").contains(tag) 61 | } 62 | } 63 | if let keyword = keyword?.lowercased() { 64 | let keywordList = keyword.split(separator: " ") 65 | if !keywordList.isEmpty { 66 | needHooks = true 67 | postsList = postsList.filter { posts in 68 | for keyword in keywordList { 69 | guard keyword != "" else { 70 | continue 71 | } 72 | guard posts.title.strValue.lowercased().contains(keyword) || 73 | posts.date.strValue.lowercased().contains(keyword) else { 74 | return false 75 | } 76 | } 77 | return true 78 | } 79 | } 80 | } 81 | if needHooks { 82 | EventHooks.hookPostsSearch(req: currentRequest, keyword: keyword) 83 | } else { 84 | EventHooks.hookPostsList(req: currentRequest) 85 | } 86 | 87 | var page = 1 88 | if let pageParam = Int(req.getUrlParam(key: "page") ?? ""), pageParam > 0 { 89 | page = pageParam 90 | } 91 | let eachPageFeedCount = 12 92 | let begin = eachPageFeedCount * (page - 1) 93 | let end = eachPageFeedCount * page - 1 94 | var posts = [PostsModel].init() 95 | if (begin < postsList.count) { 96 | let trueEnd = min(postsList.count - 1, end) 97 | posts = Array(postsList[begin..<(trueEnd + 1)]) 98 | } 99 | displayPosts = posts 100 | 101 | let titleMessage = ConfigModel.getValueForKey(.titleMessage) ?? "null" 102 | let postsListMessage = ConfigModel.getValueForKey(.postsListMessage) ?? "null" 103 | let allTags = PostsTagModel.queryObjects()?.map { $0.toViewParam() } ?? [] 104 | 105 | return [ 106 | "_pjango_template_navigation_bar": NavigationBarView.html, 107 | "_pjango_template_footer_bar": FooterBarView.html, 108 | "_pjango_param_title_message": titleMessage, 109 | 110 | "_pjango_url_host": WEBSITE_HOST, 111 | 112 | "_pjango_param_message": postsListMessage, 113 | "_pjango_param_list_title": listTitle, 114 | "_pjango_param_all_tags": allTags, 115 | "_pjango_param_param_page": page, 116 | "_pjango_param_param_page_total": max(0, postsList.count - 1) / eachPageFeedCount + 1, 117 | "_pjango_param_param_total_count": postsList.count, 118 | "_pjango_param_param_tag": tag ?? "", 119 | "_pjango_param_param_keyword": keyword ?? "", 120 | 121 | ] 122 | } 123 | } 124 | -------------------------------------------------------------------------------- /Source/Calatrava/Modules/Posts/PostsLoveHandle.swift: -------------------------------------------------------------------------------- 1 | // 2 | // PostsLoveHandle.swift 3 | // Calatrava 4 | // 5 | // Created by 郑宇琦 on 2017/6/25. 6 | // Copyright © 2017年 郑宇琦. All rights reserved. 7 | // 8 | 9 | import Foundation 10 | import Pjango 11 | 12 | func postsLoveHandle() -> PCUrlHandle { 13 | 14 | return pjangoHttpResponse { req, res in 15 | guard let postsList = (PostsModel.queryObjects() as? [PostsModel]) else { 16 | pjangoHttpResponse("居然出错了!")(req, res) 17 | return 18 | } 19 | guard let pid = Int(req.getUrlParam(key: "pid") ?? "") else { 20 | pjangoHttpResponse("AI娘无法识别你的请求哦!")(req, res) 21 | return 22 | } 23 | guard let posts = postsList.first(where: { $0.pid.intValue == pid }) else { 24 | pjangoHttpResponse("目标博文不存在!")(req, res) 25 | return 26 | } 27 | let ip = req.header(.custom(name: "watchdog_ip")) ?? req.remoteAddress.host 28 | let key = "\(pid)@\(ip)" 29 | guard !postsLoveDict.contains(key) else { 30 | pjangoHttpResponse("今天您已经赞过这篇博文啦!")(req, res) 31 | return 32 | } 33 | posts.love.intValue += 1 34 | guard PostsModel.updateObject(posts) else { 35 | pjangoHttpResponse("居然出错了!")(req, res) 36 | return 37 | } 38 | postsLoveDict.insert(key) 39 | pjangoHttpResponse("已经收到你的赞啦!")(req, res) 40 | EventHooks.hookPostsLove(req: req, pid: pid) 41 | } 42 | 43 | } 44 | 45 | -------------------------------------------------------------------------------- /Source/Calatrava/Modules/Posts/PostsModel.swift: -------------------------------------------------------------------------------- 1 | // 2 | // PostsModel.swift 3 | // Calatrava 4 | // 5 | // Created by 郑宇琦 on 2017/6/24. 6 | // Copyright © 2017年 郑宇琦. All rights reserved. 7 | // 8 | 9 | import Foundation 10 | import Pjango 11 | 12 | class PostsModel: PCModel { 13 | 14 | override var tableName: String { 15 | return "Posts" 16 | } 17 | 18 | var pid = PCDataBaseField.init(name: "PID", type: .int) 19 | var title = PCDataBaseField.init(name: "TITLE", type: .string, length: 64) 20 | var date = PCDataBaseField.init(name: "DATE", type: .string, length: 16) 21 | var read = PCDataBaseField.init(name: "READ", type: .int) 22 | var love = PCDataBaseField.init(name: "LOVE", type: .int) 23 | var tag = PCDataBaseField.init(name: "TAG", type: .string, length: 64) 24 | 25 | var tagModel: [PostsTagModel] { 26 | let tagList = tag.strValue.components(separatedBy: "|") 27 | PostsTagModel.updateHtmlDictIfNeed() 28 | 29 | return tagList.compactMap { 30 | PostsTagModel.htmlDict[$0] 31 | } 32 | } 33 | 34 | var commentsCount: Int { 35 | if let comments = (PostsCommentModel.queryObjects() as? [PostsCommentModel])?.filter({ $0.pid.intValue == pid.intValue }) { 36 | return comments.count 37 | } else { 38 | return 0 39 | } 40 | } 41 | 42 | override func registerFields() -> [PCDataBaseField] { 43 | return [ 44 | pid, title, date, read, love, tag 45 | ] 46 | } 47 | 48 | override class var cacheTime: TimeInterval? { 49 | return 60 50 | } 51 | 52 | } 53 | -------------------------------------------------------------------------------- /Source/Calatrava/Modules/Posts/PostsOldHandle.swift: -------------------------------------------------------------------------------- 1 | // 2 | // PostsOldHandle.swift 3 | // Calatrava 4 | // 5 | // Created by 郑宇琦 on 2017/6/26. 6 | // Copyright © 2017年 郑宇琦. All rights reserved. 7 | // 8 | 9 | import Foundation 10 | import Pjango 11 | 12 | func postsOldHandle() -> PCUrlHandle { 13 | return { req, res in 14 | let pid = Int(req.getUrlParam(key: "pid") ?? "-1") ?? -1 15 | pjangoHttpRedirect(url: "http://\(WEBSITE_HOST)/posts/article/\(pid)")(req, res) 16 | } 17 | } 18 | -------------------------------------------------------------------------------- /Source/Calatrava/Modules/Posts/PostsSearchView.swift: -------------------------------------------------------------------------------- 1 | // 2 | // PostsSearchView.swift 3 | // Pjango-Dev 4 | // 5 | // Created by 郑宇琦 on 2017/6/27. 6 | // Copyright © 2017年 郑宇琦. All rights reserved. 7 | // 8 | 9 | import Foundation 10 | import Pjango 11 | 12 | class PostsSearchView: PCDetailView { 13 | 14 | override var templateName: String? { 15 | return "posts_search.html" 16 | } 17 | 18 | override var viewParam: PCViewParam? { 19 | 20 | let titleMessage = ConfigModel.getValueForKey(.titleMessage) ?? "null" 21 | let allTags = PostsTagModel.queryObjects()?.map { $0.toViewParam() } ?? [] 22 | 23 | return [ 24 | "_pjango_template_navigation_bar": NavigationBarView.html, 25 | "_pjango_template_footer_bar": FooterBarView.html, 26 | "_pjango_param_title_message": titleMessage, 27 | 28 | "_pjango_param_all_tags": allTags, 29 | "_pjango_url_search": pjangoUrlReverse(host: WEBSITE_HOST, name: "search") ?? "", 30 | 31 | "_pjango_param_host": WEBSITE_HOST, 32 | ] 33 | } 34 | 35 | } 36 | -------------------------------------------------------------------------------- /Source/Calatrava/Modules/Posts/PostsTagModel.swift: -------------------------------------------------------------------------------- 1 | // 2 | // PostsTagModel.swift 3 | // Calatrava 4 | // 5 | // Created by 郑宇琦 on 2017/6/24. 6 | // Copyright © 2017年 郑宇琦. All rights reserved. 7 | // 8 | 9 | import Foundation 10 | import Pjango 11 | 12 | class PostsTagModel: PCModel { 13 | 14 | override var tableName: String { 15 | return "PostsTag" 16 | } 17 | 18 | var tag = PCDataBaseField.init(name: "TAG", type: .string, length: 16) 19 | var label = PCDataBaseField.init(name: "LABEL", type: .string, length: 16) 20 | 21 | override func registerFields() -> [PCDataBaseField] { 22 | return [ 23 | tag, label 24 | ] 25 | } 26 | 27 | override class var cacheTime: TimeInterval? { 28 | return 60 29 | } 30 | 31 | static var htmlDict = [String: PostsTagModel]() 32 | static var htmlDictUpdateTime: TimeInterval = 0 33 | 34 | static func updateHtmlDictIfNeed() { 35 | guard let tagObjs = queryObjects() as? [PostsTagModel], Date.init().timeIntervalSince1970 - htmlDictUpdateTime > cacheTime! else { 36 | return 37 | } 38 | var htmlDict = [String: PostsTagModel]() 39 | tagObjs.forEach { 40 | htmlDict[$0.tag.strValue] = $0 41 | } 42 | self.htmlDict = htmlDict 43 | 44 | } 45 | 46 | } 47 | -------------------------------------------------------------------------------- /Source/Calatrava/Modules/Posts/PostsTextView.swift: -------------------------------------------------------------------------------- 1 | // 2 | // PostsTextView.swift 3 | // Calatrava 4 | // 5 | // Created by 郑宇琦 on 2017/6/25. 6 | // Copyright © 2017年 郑宇琦. All rights reserved. 7 | // 8 | 9 | import Foundation 10 | import Pjango 11 | 12 | class PostsTextView: PCDetailView { 13 | 14 | var pid: Int 15 | 16 | override var templateName: String? { 17 | return "posts/\(pid).html" 18 | } 19 | 20 | init(pid: Int) { 21 | self.pid = pid 22 | } 23 | 24 | required init() { 25 | self.pid = -1 26 | } 27 | 28 | } 29 | -------------------------------------------------------------------------------- /Source/Calatrava/Modules/Posts/PostsView.swift: -------------------------------------------------------------------------------- 1 | // 2 | // PostsView.swift 3 | // Calatrava 4 | // 5 | // Created by 郑宇琦 on 2017/6/24. 6 | // Copyright © 2017年 郑宇琦. All rights reserved. 7 | // 8 | 9 | import Foundation 10 | import PerfectHTTP 11 | import Pjango 12 | 13 | class PostsView: PCListView { 14 | 15 | override var templateName: String? { 16 | return "posts.html" 17 | } 18 | 19 | var currentPosts: PostsModel? = nil 20 | 21 | override func requestVaild(_ req: HTTPRequest) -> Bool { 22 | guard let req = currentRequest, 23 | let pidStr = req.urlVariables["pid"], 24 | let pid = Int(pidStr) else { 25 | return false 26 | } 27 | guard let posts = (PostsModel.queryObjects() as? [PostsModel])?.first(where: { $0.pid.intValue == pid }) else { 28 | return false 29 | } 30 | currentPosts = posts 31 | return true 32 | } 33 | 34 | override func requestInvaildHandle() -> PCUrlHandle? { 35 | return pjangoHttpRedirect(name: "error.404") 36 | } 37 | 38 | override func listUserField(inList list: String, forModel model: PCModel) -> PCViewParam? { 39 | guard let model = model as? PostsCommentModel else { 40 | return nil 41 | } 42 | let hasRefer = model.refer_floor.intValue > 0 43 | if hasRefer, let refer = (PostsCommentModel.queryObjects() as? [PostsCommentModel])?.first(where: { $0.pid.intValue == model.pid.intValue && $0.floor.intValue == model.refer_floor.intValue }) { 44 | var fields: [String : Any] = [ 45 | "_pjango_param_table_PostsComment_ISADMIN": model.admin.intValue == 1 ? 1 : 0, 46 | "_pjango_param_table_PostsComment_HAVE_REFER": 1, 47 | "_pjango_param_table_PostsComment_REFER_ISADMIN": refer.admin.intValue == 1 ? 1 : 0, 48 | "_pjango_param_table_PostsComment_REFER_NAME": refer.name.strValue, 49 | "_pjango_param_table_PostsComment_REFER_DATE": refer.date.strValue, 50 | ] 51 | if refer.refer_floor.intValue > 0 { 52 | fields["_pjango_param_table_PostsComment_REFER_COMMENT"] = "[引用 \(refer.refer_floor.intValue) 楼]\n" + refer.comment.strValue 53 | } else { 54 | fields["_pjango_param_table_PostsComment_REFER_COMMENT"] = refer.comment.strValue 55 | } 56 | return fields 57 | } else { 58 | return [ 59 | "_pjango_param_table_PostsComment_ISADMIN": model.admin.intValue == 1 ? 1 : 0, 60 | "_pjango_param_table_PostsComment_HAVE_REFER": 0, 61 | "_pjango_param_table_PostsComment_REFER_ISADMIN": 0, 62 | ] 63 | } 64 | } 65 | 66 | override var listObjectSets: [String : [PCModel]]? { 67 | guard var commentList = PostsCommentModel.queryObjects() as? [PostsCommentModel] else { 68 | return nil 69 | } 70 | commentList = commentList.filter({ $0.pid.intValue == currentPosts?.pid.intValue }) 71 | return [ 72 | "_pjango_param_table_comment": commentList, 73 | ] 74 | } 75 | 76 | override var viewParam: PCViewParam? { 77 | guard let posts = currentPosts else { 78 | return nil 79 | } 80 | EventHooks.hookPostsRead(req: currentRequest, pid: posts.pid.intValue) 81 | 82 | posts.read.intValue += 1 83 | PostsModel.updateObject(posts) 84 | 85 | let title = posts.title.strValue 86 | let pid = posts.pid.intValue 87 | let tag = posts.tagModel.map { $0.toViewParam() } 88 | let date = posts.date.strValue 89 | let read = posts.read.intValue 90 | let comment = posts.commentsCount 91 | let love = posts.love.intValue 92 | 93 | let titleMessage = ConfigModel.getValueForKey(.titleMessage) ?? "null" 94 | let name = ConfigModel.getValueForKey(.name) ?? "null" 95 | 96 | return [ 97 | "_pjango_template_navigation_bar": NavigationBarView.html, 98 | "_pjango_template_footer_bar": FooterBarView.html, 99 | "_pjango_param_title_message": titleMessage, 100 | "_pjango_param_name": name, 101 | 102 | "_pjango_param_host": WEBSITE_HOST, 103 | 104 | "_pjango_param_posts_title": title, 105 | "_pjango_param_posts_pid": pid, 106 | "_pjango_param_posts_tag": tag, 107 | "_pjango_param_posts_date": date, 108 | "_pjango_param_posts_read": read, 109 | "_pjango_param_posts_comment": comment, 110 | "_pjango_param_posts_love": love, 111 | "_pjango_template_posts_text": PostsTextView.init(pid: posts.pid.intValue).getTemplate(), 112 | ] 113 | } 114 | } 115 | -------------------------------------------------------------------------------- /Source/Calatrava/Modules/Project/ProjectListView.swift: -------------------------------------------------------------------------------- 1 | // 2 | // ProjectListView.swift 3 | // Pjango-Dev 4 | // 5 | // Created by 郑宇琦 on 2017/7/1. 6 | // 7 | // 8 | 9 | import Foundation 10 | import Pjango 11 | 12 | class ProjectListView: PCListView { 13 | 14 | override var templateName: String? { 15 | return "project.html" 16 | } 17 | 18 | override var listObjectSets: [String : [PCModel]]? { 19 | guard let projectList = ProjectModel.queryObjects() else { 20 | return nil 21 | } 22 | return [ 23 | "_pjango_param_table_project": projectList.reversed() 24 | ] 25 | } 26 | 27 | override func listUserField(inList list: String, forModel model: PCModel) -> PCViewParam? { 28 | guard let model = model as? ProjectModel else { 29 | return nil 30 | } 31 | return [ 32 | "_pjango_param_table_Project_TAGHTML": model.tagModel.map { $0.toViewParam() } 33 | ] 34 | } 35 | 36 | 37 | override var viewParam: PCViewParam? { 38 | EventHooks.hookProject(req: currentRequest) 39 | 40 | let titleMessage = ConfigModel.getValueForKey(.titleMessage) ?? "null" 41 | 42 | return [ 43 | "_pjango_template_navigation_bar": NavigationBarView.html, 44 | "_pjango_template_footer_bar": FooterBarView.html, 45 | "_pjango_param_title_message": titleMessage, 46 | 47 | ] 48 | } 49 | 50 | } 51 | -------------------------------------------------------------------------------- /Source/Calatrava/Modules/Project/ProjectModel.swift: -------------------------------------------------------------------------------- 1 | // 2 | // ProjectModel.swift 3 | // Pjango-Dev 4 | // 5 | // Created by 郑宇琦 on 2017/7/1. 6 | // 7 | // 8 | 9 | import Foundation 10 | import Pjango 11 | 12 | class ProjectModel: PCModel { 13 | 14 | override var tableName: String { 15 | return "Project" 16 | } 17 | 18 | var title = PCDataBaseField.init(name: "TITLE", type: .string, length: 64) 19 | var subtitle = PCDataBaseField.init(name: "SUBTITLE", type: .string, length: 64) 20 | var tag = PCDataBaseField.init(name: "TAG", type: .string, length: 64) 21 | var date = PCDataBaseField.init(name: "DATE", type: .string, length: 10) 22 | var url = PCDataBaseField.init(name: "URL", type: .string, length: 256) 23 | var memo = PCDataBaseField.init(name: "MEMO", type: .string, length: 512) 24 | 25 | var tagModel: [PostsTagModel] { 26 | let tagList = tag.strValue.components(separatedBy: "|") 27 | PostsTagModel.updateHtmlDictIfNeed() 28 | 29 | return tagList.compactMap { 30 | PostsTagModel.htmlDict[$0] 31 | } 32 | } 33 | 34 | override func registerFields() -> [PCDataBaseField] { 35 | return [ 36 | title, subtitle, tag, date, url, memo 37 | ] 38 | } 39 | 40 | override class var cacheTime: TimeInterval? { 41 | return 60 42 | } 43 | 44 | 45 | } 46 | 47 | -------------------------------------------------------------------------------- /Source/Calatrava/Modules/Report/ReportOnlyDateModel.swift: -------------------------------------------------------------------------------- 1 | // 2 | // ReportOnlyDateModel.swift 3 | // Calatrava 4 | // 5 | // Created by 郑宇琦 on 2018/6/19. 6 | // 7 | 8 | import Foundation 9 | import Pjango 10 | 11 | class ReportOnlyDateModel: PCModel { 12 | 13 | var date: String 14 | var selected = false 15 | 16 | init(date: String) { 17 | self.date = date 18 | } 19 | 20 | required init() { 21 | fatalError("init() has not been implemented") 22 | } 23 | } 24 | -------------------------------------------------------------------------------- /Source/Calatrava/Modules/Report/ReportTotalView.swift: -------------------------------------------------------------------------------- 1 | // 2 | // ReportTotalView.swift 3 | // Calatrava 4 | // 5 | // Created by 郑宇琦 on 2018/6/19. 6 | // 7 | 8 | import Foundation 9 | import PerfectHTTP 10 | import Pjango 11 | 12 | enum ReportTotalViewOption: String { 13 | case all = "all" 14 | case week = "week" 15 | case monthOne = "month_1" 16 | case monthThree = "month_3" 17 | } 18 | 19 | class ReportTotalView: PCView { 20 | 21 | override var templateName: String? { 22 | return "report_total.html" 23 | } 24 | 25 | var currentOpt: ReportTotalViewOption? 26 | 27 | override func requestVaild(_ req: HTTPRequest) -> Bool { 28 | guard let optStr = currentRequest?.urlVariables["opt"], 29 | let opt = ReportTotalViewOption.init(rawValue: optStr) else { 30 | return false 31 | } 32 | currentOpt = opt 33 | return true 34 | } 35 | 36 | override func requestInvaildHandle() -> PCUrlHandle? { 37 | return pjangoHttpRedirect(name: "error.404") 38 | } 39 | 40 | override var viewParam: PCViewParam? { 41 | guard let opt = currentOpt else { 42 | return nil 43 | } 44 | 45 | var colorAll = "white" 46 | var colorWeek = "white" 47 | var colorMonthOne = "white" 48 | var colorMonthThree = "white" 49 | 50 | var beginDate = "2018-01-01" 51 | 52 | switch opt { 53 | case .all: 54 | colorAll = "rgba(178, 178, 178, 1)" 55 | case .week: 56 | colorWeek = "rgba(178, 178, 178, 1)" 57 | beginDate = Date.init(timeInterval: -7 * 86400, since: Date.today).dayStringValue 58 | case .monthOne: 59 | colorMonthOne = "rgba(178, 178, 178, 1)" 60 | beginDate = Date.init(timeInterval: -30 * 86400, since: Date.today).dayStringValue 61 | case .monthThree: 62 | colorMonthThree = "rgba(178, 178, 178, 1)" 63 | beginDate = Date.init(timeInterval: -3 * 30 * 86400, since: Date.today).dayStringValue 64 | } 65 | 66 | let postsList = PostsModel.queryObjects() as? [PostsModel] ?? [] 67 | let corpusPostsList = CorpusPostsModel.queryObjects() as? [CorpusPostsModel] ?? [] 68 | 69 | let readSum = postsList.reduce(0) { 70 | $0 + $1.read.intValue 71 | } + corpusPostsList.reduce(0) { 72 | $0 + $1.read.intValue 73 | } 74 | let loveSum = postsList.reduce(0) { 75 | $0 + $1.love.intValue 76 | } + corpusPostsList.reduce(0) { 77 | $0 + $1.love.intValue 78 | } 79 | let counterIndex = Int(ConfigModel.getValueForKey(.counterIndex) ?? "0") ?? 0 80 | let counterList = Int(ConfigModel.getValueForKey(.counterPostsList) ?? "0") ?? 0 81 | let counterSearch = Int(ConfigModel.getValueForKey(.counterPostsSearch) ?? "0") ?? 0 82 | 83 | let message = ConfigModel.getValueForKey(.reportTotalMessage) ?? "null" 84 | let titleMessage = ConfigModel.getValueForKey(.titleMessage) ?? "null" 85 | 86 | let dailyPvData = ReportCache.shared.cache?["_pjango_chart_daily_pv_data"] ?? "" 87 | let dailyIndexPvData = ReportCache.shared.cache?["_pjango_chart_daily_index_pv_data"] ?? "" 88 | let dailyReadData = ReportCache.shared.cache?["_pjango_chart_daily_read_data"] ?? "" 89 | let totalPvData = ReportCache.shared.cache?["_pjango_chart_total_pv_data"] ?? "" 90 | let totalReadData = ReportCache.shared.cache?["_pjango_chart_total_read_data"] ?? "" 91 | 92 | EventHooks.hookReportTotal(req: currentRequest, date: beginDate) 93 | 94 | return [ 95 | "_pjango_template_navigation_bar": NavigationBarView.html, 96 | "_pjango_template_footer_bar": FooterBarView.html, 97 | "_pjango_param_title_message": titleMessage, 98 | "_pjango_param_message": message, 99 | 100 | "_pjango_url_host": WEBSITE_HOST, 101 | 102 | "_pjango_param_color_all": colorAll, 103 | "_pjango_param_color_week": colorWeek, 104 | "_pjango_param_color_month_1": colorMonthOne, 105 | "_pjango_param_color_month_3": colorMonthThree, 106 | "_pjango_param_char_begin_date": beginDate, 107 | 108 | "_pjango_param_counter_posts": postsList.count + corpusPostsList.count, 109 | "_pjango_param_counter_read": readSum, 110 | "_pjango_param_counter_love": loveSum, 111 | "_pjango_param_counter_index": counterIndex, 112 | "_pjango_param_counter_list": counterList, 113 | "_pjango_param_counter_search": counterSearch, 114 | 115 | "_pjango_chart_daily_pv_data": dailyPvData, 116 | "_pjango_chart_daily_index_pv_data": dailyIndexPvData, 117 | "_pjango_chart_daily_read_data": dailyReadData, 118 | "_pjango_chart_total_pv_data": totalPvData, 119 | "_pjango_chart_total_read_data": totalReadData, 120 | ] 121 | } 122 | } 123 | -------------------------------------------------------------------------------- /Source/Calatrava/Modules/Search/SearchResultBilibiliView.swift: -------------------------------------------------------------------------------- 1 | // 2 | // SearchResultBilibiliView.swift 3 | // Calatrava 4 | // 5 | // Created by 郑宇琦 on 2019/2/23. 6 | // 7 | 8 | import Foundation 9 | import Pjango 10 | 11 | class SearchResultBilibiliView: PCDetailView { 12 | 13 | var model: BilibiliFeedModel? 14 | 15 | init(model: BilibiliFeedModel) { 16 | self.model = model 17 | } 18 | 19 | required init() { 20 | 21 | } 22 | 23 | override var templateName: String? { 24 | return "search_result_bilibili_cell.html" 25 | } 26 | 27 | override var viewParam: PCViewParam? { 28 | guard let model = model else { 29 | return nil 30 | } 31 | var coverName = "" 32 | if model.cover.strValue != "null" { 33 | coverName = model.cover.strValue 34 | } else { 35 | coverName = "\(model.blid.intValue)_cover.jpg" 36 | } 37 | let coverUrl = "\(WEBSITE_HOST)/img/bilibili/\(coverName)" 38 | let list = (BilibiliListModel.queryObjects() as? [BilibiliListModel])?.first (where: { $0.blid.intValue == model.blid.intValue }) 39 | 40 | let name = ConfigModel.getValueForKey(.bilibiliName) ?? "null" 41 | let listTitle = list?.name.strValue ?? "null" 42 | 43 | return [ 44 | "_pjango_param_name": name, 45 | "_pjango_param_title": model.name.strValue, 46 | "_pjango_param_list_title": listTitle, 47 | "_pjango_param_image": coverUrl, 48 | "_pjango_param_url": model.url.strValue, 49 | "_pjango_param_blid": model.blid.intValue, 50 | "_pjango_param_memo": model.memo.strValue, 51 | "_pjango_param_date": model.date.strValue, 52 | 53 | "_pjango_url_host": WEBSITE_HOST, 54 | ] 55 | } 56 | 57 | } 58 | -------------------------------------------------------------------------------- /Source/Calatrava/Modules/Search/SearchResultCorpusModel.swift: -------------------------------------------------------------------------------- 1 | // 2 | // SearchResultCorpusModel.swift 3 | // Calatrava 4 | // 5 | // Created by 郑宇琦 on 2019/2/23. 6 | // 7 | 8 | import Foundation 9 | import Pjango 10 | 11 | class SearchResultCorpusView: PCDetailView { 12 | 13 | var model: CorpusPostsModel? 14 | 15 | init(model: CorpusPostsModel) { 16 | self.model = model 17 | } 18 | 19 | required init() { 20 | 21 | } 22 | 23 | override var templateName: String? { 24 | return "search_result_corpus_cell.html" 25 | } 26 | 27 | override var viewParam: PCViewParam? { 28 | guard let model = model else { 29 | return nil 30 | } 31 | let corpus = ((CorpusModel.queryObjects() as? [CorpusModel])?.filter { $0.cid.intValue == model.cid.intValue })?.first 32 | let corpusTitle = corpus?.title.strValue ?? "null" 33 | 34 | return [ 35 | "_pjango_param_title": model.title.strValue, 36 | "_pjango_param_corpus_title": corpusTitle, 37 | "_pjango_param_cid": model.cid.intValue, 38 | "_pjango_param_cpid": model.cpid.intValue, 39 | "_pjango_param_date": model.date.strValue, 40 | 41 | "_pjango_url_host": WEBSITE_HOST, 42 | ] 43 | } 44 | 45 | } 46 | -------------------------------------------------------------------------------- /Source/Calatrava/Modules/Search/SearchResultInstagramView.swift: -------------------------------------------------------------------------------- 1 | // 2 | // SearchResultInstagramView.swift 3 | // Calatrava 4 | // 5 | // Created by 郑宇琦 on 2019/2/23. 6 | // 7 | 8 | import Foundation 9 | import Pjango 10 | 11 | class SearchResultInstagramView: PCDetailView { 12 | 13 | var model: InstagramFeedModel? 14 | 15 | init(model: InstagramFeedModel) { 16 | self.model = model 17 | } 18 | 19 | required init() { 20 | 21 | } 22 | 23 | override var templateName: String? { 24 | return "search_result_instagram_cell.html" 25 | } 26 | 27 | override var viewParam: PCViewParam? { 28 | guard let model = model else { 29 | return nil 30 | } 31 | return [ 32 | "_pjango_param_author_name": model.name.strValue, 33 | "_pjango_param_author_fullname": model.full_name.strValue, 34 | "_pjango_param_author_head": model.headSource, 35 | "_pjango_param_author_url": model.url.strValue, 36 | "_pjango_param_image": model.imageSource, 37 | "_pjango_param_image_origin": model.bigImageSource, 38 | "_pjango_param_caption": model.caption.strValue, 39 | "_pjango_param_date": model.date.strValue, 40 | ] 41 | } 42 | 43 | } 44 | -------------------------------------------------------------------------------- /Source/Calatrava/Modules/Search/SearchResultPostsView.swift: -------------------------------------------------------------------------------- 1 | // 2 | // SearchResultPostsView.swift 3 | // Calatrava 4 | // 5 | // Created by 郑宇琦 on 2019/2/23. 6 | // 7 | 8 | import Foundation 9 | import Pjango 10 | 11 | class SearchResultPostsView: PCDetailView { 12 | 13 | var model: PostsModel? 14 | 15 | init(model: PostsModel) { 16 | self.model = model 17 | } 18 | 19 | required init() { 20 | 21 | } 22 | 23 | override var templateName: String? { 24 | return "search_result_posts_cell.html" 25 | } 26 | 27 | override var viewParam: PCViewParam? { 28 | guard let model = model else { 29 | return nil 30 | } 31 | return [ 32 | "_pjango_param_title": model.title.strValue, 33 | "_pjango_param_pid": model.pid.intValue, 34 | "_pjango_param_date": model.date.strValue, 35 | "_pjango_param_table_tag": model.tagModel.map { $0.toViewParam() }, 36 | 37 | "_pjango_url_host": WEBSITE_HOST, 38 | ] 39 | } 40 | 41 | } 42 | -------------------------------------------------------------------------------- /Source/Calatrava/Modules/Search/SearchResultProjectView.swift: -------------------------------------------------------------------------------- 1 | // 2 | // SearchResultProjectView.swift 3 | // Calatrava 4 | // 5 | // Created by 郑宇琦 on 2019/2/24. 6 | // 7 | 8 | import Foundation 9 | import Pjango 10 | 11 | class SearchResultProjectView: PCDetailView { 12 | 13 | var model: ProjectModel? 14 | 15 | init(model: ProjectModel) { 16 | self.model = model 17 | } 18 | 19 | required init() { 20 | 21 | } 22 | 23 | override var templateName: String? { 24 | return "search_result_project_cell.html" 25 | } 26 | 27 | override var viewParam: PCViewParam? { 28 | guard let model = model else { 29 | return nil 30 | } 31 | return [ 32 | "_pjango_param_title": model.title.strValue, 33 | "_pjango_param_subtitle": model.subtitle.strValue, 34 | "_pjango_param_memo": model.memo.strValue, 35 | "_pjango_param_url": model.url.strValue, 36 | "_pjango_param_date": model.date.strValue, 37 | "_pjango_param_table_TAGHTML": model.tagModel.map { $0.toViewParam() } 38 | ] 39 | } 40 | 41 | } 42 | -------------------------------------------------------------------------------- /Source/Calatrava/Modules/Search/SearchView.swift: -------------------------------------------------------------------------------- 1 | // 2 | // SearchView.swift 3 | // Calatrava 4 | // 5 | // Created by 郑宇琦 on 2019/2/23. 6 | // 7 | 8 | import Foundation 9 | import Pjango 10 | 11 | class SearchView: PCDetailView { 12 | 13 | override var templateName: String? { 14 | return "search.html" 15 | } 16 | 17 | override var viewParam: PCViewParam? { 18 | 19 | let titleMessage = ConfigModel.getValueForKey(.titleMessage) ?? "null" 20 | let allTags = (ModuleModel.queryObjects() as? [ModuleModel])?.filter { $0.searchable.intValue > 0 }.map { $0.toViewParam() } ?? [] 21 | 22 | return [ 23 | "_pjango_template_navigation_bar": NavigationBarView.html, 24 | "_pjango_template_footer_bar": FooterBarView.html, 25 | "_pjango_param_title_message": titleMessage, 26 | 27 | "_pjango_param_all_tags": allTags, 28 | 29 | "_pjango_param_host": WEBSITE_HOST, 30 | ] 31 | } 32 | 33 | } 34 | -------------------------------------------------------------------------------- /Source/Calatrava/Modules/Update/UpdateBilibiliModel.swift: -------------------------------------------------------------------------------- 1 | // 2 | // UpdateBilibiliView.swift 3 | // Calatrava 4 | // 5 | // Created by 郑宇琦 on 2017/6/24. 6 | // Copyright © 2017年 郑宇琦. All rights reserved. 7 | // 8 | 9 | import Foundation 10 | import Pjango 11 | 12 | class UpdateBilibiliView: PCDetailView { 13 | 14 | var model: BilibiliFeedModel 15 | 16 | init(model: BilibiliFeedModel) { 17 | self.model = model 18 | } 19 | 20 | required init() { 21 | fatalError("init() has not been implemented") 22 | } 23 | 24 | override var templateName: String? { 25 | return "update_bilibili_cell.html" 26 | } 27 | 28 | override var viewParam: PCViewParam? { 29 | var coverName = "" 30 | if model.cover.strValue != "null" { 31 | coverName = model.cover.strValue 32 | } else { 33 | coverName = "\(model.blid.intValue)_cover.jpg" 34 | } 35 | let coverUrl = "\(WEBSITE_HOST)/img/bilibili/\(coverName)" 36 | let list = (BilibiliListModel.queryObjects() as? [BilibiliListModel])?.first(where: { $0.blid.intValue == model.blid.intValue }) 37 | 38 | let name = ConfigModel.getValueForKey(.bilibiliName) ?? "null" 39 | let listTitle = list?.name.strValue ?? "null" 40 | 41 | return [ 42 | "_pjango_param_name": name, 43 | "_pjango_param_title": model.name.strValue, 44 | "_pjango_param_list_title": listTitle, 45 | "_pjango_param_image": coverUrl, 46 | "_pjango_param_url": model.url.strValue, 47 | "_pjango_param_blid": model.blid.intValue, 48 | "_pjango_param_memo": model.memo.strValue, 49 | "_pjango_param_date": model.date.strValue, 50 | 51 | "_pjango_url_host": WEBSITE_HOST 52 | ] 53 | } 54 | 55 | } 56 | -------------------------------------------------------------------------------- /Source/Calatrava/Modules/Update/UpdateCorpusModel.swift: -------------------------------------------------------------------------------- 1 | // 2 | // UpdateCorpusView.swift 3 | // Calatrava 4 | // 5 | // Created by 郑宇琦 on 2017/6/24. 6 | // Copyright © 2017年 郑宇琦. All rights reserved. 7 | // 8 | 9 | import Foundation 10 | import Pjango 11 | 12 | class UpdateCorpusView: PCDetailView { 13 | 14 | var model: CorpusPostsModel 15 | 16 | init(model: CorpusPostsModel) { 17 | self.model = model 18 | } 19 | 20 | required init() { 21 | fatalError("init() has not been implemented") 22 | } 23 | 24 | override var templateName: String? { 25 | return "update_corpus_cell.html" 26 | } 27 | 28 | override var viewParam: PCViewParam? { 29 | let corpus = (CorpusModel.queryObjects() as? [CorpusModel])?.first(where: { $0.cid.intValue == model.cid.intValue }) 30 | let corpusTitle = corpus?.title.strValue ?? "null" 31 | 32 | return [ 33 | "_pjango_param_title": model.title.strValue, 34 | "_pjango_param_corpus_title": corpusTitle, 35 | "_pjango_param_cid": model.cid.intValue, 36 | "_pjango_param_cpid": model.cpid.intValue, 37 | "_pjango_param_date": model.date.strValue, 38 | 39 | "_pjango_url_host": WEBSITE_HOST 40 | ] 41 | } 42 | 43 | } 44 | -------------------------------------------------------------------------------- /Source/Calatrava/Modules/Update/UpdateInstagramModel.swift: -------------------------------------------------------------------------------- 1 | // 2 | // UpdateInstagramView.swift 3 | // Calatrava 4 | // 5 | // Created by 郑宇琦 on 2017/6/24. 6 | // Copyright © 2017年 郑宇琦. All rights reserved. 7 | // 8 | 9 | import Foundation 10 | import Pjango 11 | 12 | class UpdateInstagramView: PCDetailView { 13 | 14 | var model: InstagramFeedModel 15 | 16 | init(model: InstagramFeedModel) { 17 | self.model = model 18 | } 19 | 20 | required init() { 21 | fatalError("init() has not been implemented") 22 | } 23 | 24 | override var templateName: String? { 25 | return "update_instagram_cell.html" 26 | } 27 | 28 | override var viewParam: PCViewParam? { 29 | return [ 30 | "_pjango_param_author_name": model.name.strValue, 31 | "_pjango_param_author_fullname": model.full_name.strValue, 32 | "_pjango_param_author_head": model.headSource, 33 | "_pjango_param_author_url": model.url.strValue, 34 | "_pjango_param_image": model.imageSource, 35 | "_pjango_param_image_origin": model.bigImageSource, 36 | "_pjango_param_caption": model.caption.strValue, 37 | "_pjango_param_date": model.date.strValue, 38 | ] 39 | } 40 | 41 | } 42 | -------------------------------------------------------------------------------- /Source/Calatrava/Modules/Update/UpdateListView.swift: -------------------------------------------------------------------------------- 1 | // 2 | // UpdateListView.swift 3 | // Calatrava 4 | // 5 | // Created by 郑宇琦 on 2018/6/18. 6 | // Copyright © 2017年 郑宇琦. All rights reserved. 7 | // 8 | 9 | import Foundation 10 | import Pjango 11 | 12 | var UpadateListCache: [UpdateModel]? 13 | var UpadateListCacheTime: Date? 14 | 15 | class UpdateListView: PCListView { 16 | 17 | override var templateName: String? { 18 | return "update.html" 19 | } 20 | 21 | var displayUpdates: [UpdateModel]? 22 | 23 | func generateAllUpdate() -> [UpdateModel] { 24 | var needRebuild: Bool 25 | if UpadateListCache != nil, let cacheTime = UpadateListCacheTime { 26 | if Date.init().timeIntervalSince1970 - cacheTime.timeIntervalSince1970 > 60 { 27 | needRebuild = true 28 | } else { 29 | needRebuild = false 30 | } 31 | } else { 32 | needRebuild = true 33 | } 34 | 35 | var updates = [UpdateModel]() 36 | if needRebuild { 37 | if let posts = PostsModel.queryObjects() as? [PostsModel] { 38 | updates += posts.map { UpdateModel.init($0) } 39 | } 40 | 41 | if let projects = ProjectModel.queryObjects() as? [ProjectModel] { 42 | updates += projects.map { UpdateModel.init($0) } 43 | } 44 | 45 | if let corpuss = CorpusPostsModel.queryObjects() as? [CorpusPostsModel] { 46 | updates += corpuss.map { UpdateModel.init($0) } 47 | } 48 | 49 | if let instagrams = InstagramFeedModel.queryObjects() as? [InstagramFeedModel] { 50 | updates += instagrams.map { UpdateModel.init($0) } 51 | } 52 | 53 | if let bilibilis = BilibiliFeedModel.queryObjects() as? [BilibiliFeedModel] { 54 | updates += bilibilis.map { UpdateModel.init($0) } 55 | } 56 | 57 | updates.sort(by: { $0.date > $1.date }) 58 | 59 | UpadateListCache = updates 60 | UpadateListCacheTime = Date.init() 61 | } 62 | 63 | return UpadateListCache ?? [] 64 | } 65 | 66 | override var listObjectSets: [String : [PCModel]]? { 67 | guard let updates = displayUpdates else { 68 | return nil 69 | } 70 | return [ 71 | "_pjango_param_table_update": updates, 72 | ] 73 | } 74 | 75 | override func listUserField(inList list: String, forModel model: PCModel) -> PCViewParam? { 76 | guard let model = model as? UpdateModel else { 77 | return nil 78 | } 79 | return [ 80 | "_pjango_param_table_update_HTML": model.generateTemplate() 81 | ] 82 | } 83 | 84 | 85 | override var viewParam: PCViewParam? { 86 | guard let req = currentRequest else { 87 | return nil 88 | } 89 | var updateList = generateAllUpdate() 90 | 91 | var page = 1 92 | if let pageParam = Int(req.getUrlParam(key: "page") ?? ""), pageParam > 0 { 93 | page = pageParam 94 | } 95 | let ignorePostman = Bool(req.getUrlParam(key: "ignore_postman") ?? "") ?? true 96 | if ignorePostman { 97 | updateList = updateList.filter { $0.type != .instagram } 98 | } 99 | let eachPageFeedCount = 12 100 | let begin = eachPageFeedCount * (page - 1) 101 | let end = eachPageFeedCount * page - 1 102 | var updates = [UpdateModel].init() 103 | if (begin < updateList.count) { 104 | let trueEnd = min(updateList.count - 1, end) 105 | updates = Array(updateList[begin..<(trueEnd + 1)]) 106 | } 107 | 108 | displayUpdates = updates 109 | 110 | EventHooks.hookBlogUpdate(req: currentRequest) 111 | 112 | let titleMessage = ConfigModel.getValueForKey(.titleMessage) ?? "null" 113 | let ignorePostmanButtonTitle = ignorePostman ? "显示 Postman 抓取 Instagram 的动态" : "隐藏 Postman 抓取 Instagram 的动态" 114 | 115 | return [ 116 | "_pjango_template_navigation_bar": NavigationBarView.html, 117 | "_pjango_template_footer_bar": FooterBarView.html, 118 | "_pjango_param_title_message": titleMessage, 119 | 120 | "_pjango_url_host": WEBSITE_HOST, 121 | 122 | "_pjango_param_param_page": page, 123 | "_pjango_param_param_page_total": max(0, updateList.count - 1) / eachPageFeedCount + 1, 124 | "_pjango_param_param_total_count": updateList.count, 125 | "_pjango_param_param_ignore_postman": ignorePostman, 126 | "_pjango_param_param_ignore_postman_button_title": ignorePostmanButtonTitle 127 | 128 | ] 129 | } 130 | 131 | 132 | } 133 | -------------------------------------------------------------------------------- /Source/Calatrava/Modules/Update/UpdateModel.swift: -------------------------------------------------------------------------------- 1 | // 2 | // UpdateModel.swift 3 | // Calatrava 4 | // 5 | // Created by 郑宇琦 on 2018/6/18. 6 | // Copyright © 2017年 郑宇琦. All rights reserved. 7 | // 8 | 9 | import Foundation 10 | import Pjango 11 | 12 | enum UpdateModelType { 13 | case posts 14 | case project 15 | case corpus 16 | case instagram 17 | case bilibili 18 | } 19 | 20 | class UpdateModel: PCModel { 21 | 22 | var type: UpdateModelType 23 | var model: PCModel 24 | var date: String 25 | 26 | required init() { 27 | fatalError("init() has not been implemented") 28 | } 29 | 30 | init(type: UpdateModelType, model: PCModel, date: String) { 31 | self.type = type 32 | self.model = model 33 | self.date = date 34 | } 35 | 36 | convenience init(_ postsModel: PostsModel) { 37 | self.init(type: .posts, model: postsModel, date: postsModel.date.strValue) 38 | } 39 | 40 | convenience init(_ projectModel: ProjectModel) { 41 | self.init(type: .project, model: projectModel, date: projectModel.date.strValue) 42 | } 43 | 44 | convenience init(_ corpusModel: CorpusPostsModel) { 45 | self.init(type: .corpus, model: corpusModel, date: corpusModel.date.strValue) 46 | } 47 | 48 | convenience init(_ insModel: InstagramFeedModel) { 49 | self.init(type: .instagram, model: insModel, date: insModel.date.strValue) 50 | } 51 | 52 | convenience init(_ biliModel: BilibiliFeedModel) { 53 | self.init(type: .bilibili, model: biliModel, date: biliModel.date.strValue) 54 | } 55 | 56 | func generateTemplate() -> String { 57 | switch type { 58 | case .posts: return UpdatePostsView.init(model: model as! PostsModel).getTemplate() 59 | case .project: return UpdateProjectView.init(model: model as! ProjectModel).getTemplate() 60 | case .corpus: return UpdateCorpusView.init(model: model as! CorpusPostsModel).getTemplate() 61 | case .instagram: return UpdateInstagramView.init(model: model as! InstagramFeedModel).getTemplate() 62 | case .bilibili: return UpdateBilibiliView.init(model: model as! BilibiliFeedModel).getTemplate() 63 | } 64 | } 65 | 66 | } 67 | -------------------------------------------------------------------------------- /Source/Calatrava/Modules/Update/UpdatePostsView.swift: -------------------------------------------------------------------------------- 1 | // 2 | // UpdatePostsView.swift 3 | // Calatrava 4 | // 5 | // Created by 郑宇琦 on 2017/6/24. 6 | // Copyright © 2017年 郑宇琦. All rights reserved. 7 | // 8 | 9 | import Foundation 10 | import Pjango 11 | 12 | class UpdatePostsView: PCDetailView { 13 | 14 | var model: PostsModel 15 | 16 | init(model: PostsModel) { 17 | self.model = model 18 | } 19 | 20 | required init() { 21 | fatalError("init() has not been implemented") 22 | } 23 | 24 | override var templateName: String? { 25 | return "update_posts_cell.html" 26 | } 27 | 28 | override var viewParam: PCViewParam? { 29 | return [ 30 | "_pjango_param_title": model.title.strValue, 31 | "_pjango_param_pid": model.pid.intValue, 32 | "_pjango_param_date": model.date.strValue, 33 | "_pjango_param_table_tag": model.tagModel.map { $0.toViewParam() }, 34 | 35 | "_pjango_url_host": WEBSITE_HOST 36 | ] 37 | } 38 | 39 | } 40 | -------------------------------------------------------------------------------- /Source/Calatrava/Modules/Update/UpdateProjectView.swift: -------------------------------------------------------------------------------- 1 | // 2 | // UpdateProjectView.swift 3 | // Calatrava 4 | // 5 | // Created by 郑宇琦 on 2017/6/24. 6 | // Copyright © 2017年 郑宇琦. All rights reserved. 7 | // 8 | 9 | import Foundation 10 | import Pjango 11 | 12 | class UpdateProjectView: PCDetailView { 13 | 14 | var model: ProjectModel 15 | 16 | init(model: ProjectModel) { 17 | self.model = model 18 | } 19 | 20 | required init() { 21 | fatalError("init() has not been implemented") 22 | } 23 | 24 | override var templateName: String? { 25 | return "update_project_cell.html" 26 | } 27 | 28 | override var viewParam: PCViewParam? { 29 | return [ 30 | "_pjango_param_title": model.title.strValue, 31 | "_pjango_param_sub_title": model.subtitle.strValue, 32 | "_pjango_param_memo": model.memo.strValue, 33 | "_pjango_param_url": model.url.strValue, 34 | "_pjango_param_date": model.date.strValue, 35 | "_pjango_param_table_TAGHTML": model.tagModel.map { $0.toViewParam() } 36 | ] 37 | } 38 | 39 | } 40 | -------------------------------------------------------------------------------- /Source/Calatrava/Modules/VPSSSCURL/VPSSSCURL.swift: -------------------------------------------------------------------------------- 1 | // 2 | // VPSSSCURL.swift 3 | // Calatrava 4 | // 5 | // Created by 郑宇琦 on 2018/1/19. 6 | // 7 | 8 | import Foundation 9 | import Dispatch 10 | import SwiftyJSON 11 | import PerfectCURL 12 | import cURL 13 | 14 | class VPSSSCURL { 15 | 16 | static func getBytes(url: String) -> [UInt8] { 17 | 18 | let curl = CURL(url: url) 19 | curl.setOption(CURLOPT_PROXYTYPE, int: Int(CURLPROXY_SOCKS5.rawValue)) 20 | curl.setOption(CURLOPT_PROXY, s: "socks5h://127.0.0.1:1080") 21 | 22 | let (_, _, resBody) = curl.performFully() 23 | 24 | return resBody 25 | } 26 | 27 | static func toVPSSSCURL(url: String) -> String? { 28 | return "http://\(WEBSITE_HOST)/vpssscurl?url=\(url.addingPercentEncoding(withAllowedCharacters: .alphanumerics) ?? "")" 29 | } 30 | } 31 | -------------------------------------------------------------------------------- /Source/Calatrava/Plugin/DailyCleanPlugin.swift: -------------------------------------------------------------------------------- 1 | // 2 | // CustomerCleanerPlugin.swift 3 | // Calatrava 4 | // 5 | // Created by 郑宇琦 on 2017/6/25. 6 | // Copyright © 2017年 郑宇琦. All rights reserved. 7 | // 8 | 9 | import Foundation 10 | import Pjango 11 | 12 | class DailyCleanPlugin: PCTimerPlugin { 13 | 14 | override var timerDelay: TimeInterval { 15 | return Date.tomorrow.timeIntervalSince1970 - Date.init().timeIntervalSince1970 16 | } 17 | 18 | override var timerInterval: TimeInterval { 19 | return 3600 * 24 20 | } 21 | 22 | override var task: PCTask? { 23 | return { 24 | postsLoveDict.removeAll() 25 | postsCommentLastTimeDict.removeAll() 26 | postsCommentDailyDict.removeAll() 27 | corpusPostsLoveDict.removeAll() 28 | corpusPostsCommentLastTimeDict.removeAll() 29 | corpusPostsCommentDailyDict.removeAll() 30 | messageLastTimeDict.removeAll() 31 | messageDailyDict.removeAll() 32 | verificationLastTimeDict.removeAll() 33 | } 34 | } 35 | } 36 | -------------------------------------------------------------------------------- /Source/Calatrava/Plugin/InstagramTimerPlugin.swift: -------------------------------------------------------------------------------- 1 | // 2 | // InstagramTimerPlugin.swift 3 | // Poster 4 | // 5 | // Created by 郑宇琦 on 2018/1/18. 6 | // 7 | 8 | import Foundation 9 | import Pjango 10 | import PjangoPostman 11 | import SwiftyJSON 12 | 13 | class InstagramTimerPlugin: PCTimerPlugin { 14 | 15 | var isLoading = false 16 | 17 | override var timerInterval: TimeInterval { 18 | return 60 * 60 * 3 19 | } 20 | 21 | override var task: PCTask? { 22 | return { 23 | guard !self.isLoading else { 24 | return 25 | } 26 | guard let igs = InstagramUserModel.queryObjects() as? [InstagramUserModel] else { 27 | return 28 | } 29 | self.isLoading = true 30 | defer { 31 | self.isLoading = false 32 | } 33 | logger.info("[Instagram] Pull Start!") 34 | for user in igs { 35 | let url = user.url.strValue 36 | guard let htmlBytes = PostmanCURL.getBytes(url: url, clientIp: "Blog", clientPort: "0"), let html = String.init(bytes: htmlBytes, encoding: .ascii) else { 37 | continue 38 | } 39 | guard let info = self.buildInstagramInfo(html: html, to: user.updateDate.strValue) else { 40 | continue 41 | } 42 | user.name.strValue = info.username 43 | user.full_name.strValue = info.full_name 44 | user.head.strValue = info.profile_pic_url 45 | user.bio.strValue = info.biography 46 | if let first = info.mediaNodes.first { 47 | user.updateDate.strValue = Date.init(timeIntervalSince1970: TimeInterval(first.date)).stringValue 48 | } 49 | InstagramUserModel.updateObject(user) 50 | info.mediaNodes.forEach { 51 | let node = InstagramFeedModel.init(userUrl: url, info: info, node: $0) 52 | InstagramFeedModel.insertObject(node) 53 | } 54 | } 55 | InstagramFeedModel.recache() 56 | logger.info("[Instagram] Pull Done!") 57 | } 58 | } 59 | 60 | open func buildInstagramInfo(html: String, to date: String) -> InstagramInfo? { 61 | guard let jsonBegin = html.range(of: "window._sharedData = ")?.upperBound else { 62 | return nil 63 | } 64 | guard let jsonEnd = html.range(of: "};")?.lowerBound else { 65 | return nil 66 | } 67 | let jsonRange = jsonBegin.. Bool { 16 | if case .notFound = res.status { 17 | ErrorNotFoundView.asHandle()(req, res) 18 | return false 19 | } else { 20 | return true 21 | } 22 | 23 | } 24 | 25 | } 26 | -------------------------------------------------------------------------------- /Source/Calatrava/Plugin/ReportGeneratePlugin.swift: -------------------------------------------------------------------------------- 1 | // 2 | // ReportGeneratePlugin.swift 3 | // Calatrava 4 | // 5 | // Created by 郑宇琦 on 2017/6/27. 6 | // Copyright © 2017年 郑宇琦. All rights reserved. 7 | // 8 | 9 | import Foundation 10 | import Pjango 11 | 12 | class ReportGeneratePlugin: PCTimerPlugin { 13 | 14 | // 次日凌晨3点 15 | override var timerDelay: TimeInterval { 16 | return Date.tomorrow.timeIntervalSince1970 + 3 * 3600 - Date.init().timeIntervalSince1970 17 | } 18 | 19 | override var timerInterval: TimeInterval { 20 | return 24 * 3600 21 | } 22 | 23 | override var task: PCTask? { 24 | return { 25 | logger.info("[Report] Generating!") 26 | ReportDailyModel.generateAllReport() 27 | logger.info("[Report] Generated!") 28 | } 29 | } 30 | } 31 | -------------------------------------------------------------------------------- /Source/Calatrava/Plugin/ReportUpdatePlugin.swift: -------------------------------------------------------------------------------- 1 | // 2 | // ReportUpdatePlugin.swift 3 | // Calatrava 4 | // 5 | // Created by 郑宇琦 on 2017/6/25. 6 | // Copyright © 2017年 郑宇琦. All rights reserved. 7 | // 8 | 9 | import Foundation 10 | import Pjango 11 | 12 | class ReportUpdatePlugin: PCTimerPlugin { 13 | 14 | override var timerInterval: TimeInterval { 15 | return 60 * 10 16 | } 17 | 18 | override var task: PCTask? { 19 | return { 20 | logger.info("[Report] Updating!") 21 | #if os(macOS) 22 | autoreleasepool { 23 | ReportCache.shared.updateCacheData() 24 | } 25 | #else 26 | ReportCache.shared.updateCacheData() 27 | #endif 28 | logger.info("[Report] Done!") 29 | } 30 | } 31 | } 32 | -------------------------------------------------------------------------------- /Source/Calatrava/main.swift: -------------------------------------------------------------------------------- 1 | // 2 | // AppDelegate.swift 3 | // Calatrava 4 | // 5 | // Created by 郑宇琦 on 2017/6/24. 6 | // Copyright © 2017年 郑宇琦. All rights reserved. 7 | // 8 | 9 | import Foundation 10 | import Pjango 11 | 12 | PjangoRuntime.run(delegate: AppDelegate.init()) 13 | 14 | -------------------------------------------------------------------------------- /Workspace/.keep: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/enums/Calatrava/bd02ae3dbd4ed8e86dda479534e9583b1c56b131/Workspace/.keep -------------------------------------------------------------------------------- /Workspace/runtime/.keep: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/enums/Calatrava/bd02ae3dbd4ed8e86dda479534e9583b1c56b131/Workspace/runtime/.keep -------------------------------------------------------------------------------- /Workspace/runtime/config.json: -------------------------------------------------------------------------------- 1 | { 2 | "macos_workspace_path": "", 3 | "workspace_path": "", 4 | 5 | "log_path": "runtime/calatrava.log", 6 | 7 | "base_dir": "", 8 | "template_dir": "templates", 9 | "static_dir": "static", 10 | 11 | "port": 80, 12 | "log_debug": false, 13 | 14 | "mysql_schema": "Pjango_calatrava", 15 | "mysql_user": "", 16 | "mysql_password": "", 17 | "mysql_host": "127.0.0.1", 18 | "mysql_port": 3306 19 | } -------------------------------------------------------------------------------- /Workspace/static/.keep: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/enums/Calatrava/bd02ae3dbd4ed8e86dda479534e9583b1c56b131/Workspace/static/.keep -------------------------------------------------------------------------------- /Workspace/static/css/height.css: -------------------------------------------------------------------------------- 1 | body{height:100%;min-height:100%;max-height:100%}div.box{height:100%;min-height:100%;max-height:100%}div.bigbox{height:200%;min-height:200%;max-height:200%}div.height-1{height:1%;min-height:1%;max-height:1%}div.height-2{height:2%;min-height:2%;max-height:2%}div.height-3{height:3%;min-height:3%;max-height:3%}div.height_4{height:4%;min-height:4%;max-height:4%}div.height-5{height:5%;min-height:5%;max-height:5%}div.height-6{height:6%;min-height:6%;max-height:6%}div.height-7{height:7%;min-height:7%;max-height:7%}div.height-8{height:8%;min-height:8%;max-height:8%}div.height-10{height:10%;min-height:10%;max-height:10%}div.height-15{height:15%;min-height:15%;max-height:15%}div.height-20{height:20%;min-height:20%;max-height:20%}div.height-30{height:30%;min-height:30%;max-height:30%}div.height-40{height:40%;min-height:40%;max-height:40%}div.height-50{height:50%;min-height:50%;max-height:50%}div.height-60{height:60%;min-height:60%;max-height:60%}div.height-70{height:70%;min-height:70%;max-height:70%}div.height-80{height:80%;min-height:80%;max-height:80%}div.height-85{height:85%;min-height:85%;max-height:85%}div.height-90{height:90%;min-height:90%;max-height:90%}div.height-100{height:100%;min-height:100%;max-height:100%}div.height-200{height:200%;min-height:200%;max-height:200%}div.col-center{float:none;display:block;margin-left:auto;margin-right:auto}div.height-more-than-85{min-height:85%} -------------------------------------------------------------------------------- /Workspace/static/css/me.css: -------------------------------------------------------------------------------- 1 | 2 | body.main { 3 | background: url("/img/background.jpg") fixed center center no-repeat; 4 | background-size: cover; 5 | } 6 | 7 | body.main:before { 8 | content: ' '; 9 | position: fixed; 10 | z-index: -1; 11 | top: 0; 12 | right: 0; 13 | bottom: 0; 14 | left: 0; 15 | background: url("/img/background.jpg") center center no-repeat; 16 | background-size: cover; 17 | } 18 | 19 | body.main-index { 20 | background: url("/img/background_index.jpg") fixed center center no-repeat; 21 | background-size: cover; 22 | } 23 | 24 | body.main-index:before { 25 | content: ' '; 26 | position: fixed; 27 | z-index: -1; 28 | top: 0; 29 | right: 0; 30 | bottom: 0; 31 | left: 0; 32 | background: url("/img/background_index.jpg") center center no-repeat; 33 | background-size: cover; 34 | } 35 | 36 | body.main-me { 37 | background: url("/img/background_me.jpg") fixed center center no-repeat; 38 | background-size: cover; 39 | } 40 | 41 | body.main-me:before { 42 | content: ' '; 43 | position: fixed; 44 | z-index: -1; 45 | top: 0; 46 | right: 0; 47 | bottom: 0; 48 | left: 0; 49 | background: url("/img/background_me.jpg") center center no-repeat; 50 | background-size: cover; 51 | } 52 | 53 | img.head{ 54 | width: 128px; 55 | height: 128px; 56 | min-height: 128px; 57 | max-height: 128px; 58 | } 59 | 60 | div.corner-window { 61 | border-radius: 8px; 62 | background: rgba(0, 0, 0, 0.6); 63 | } 64 | 65 | div.window { 66 | background: rgba(0, 0, 0, 0.6); 67 | } 68 | 69 | div.shadow { 70 | box-shadow: 4px 4px 6px black; 71 | } 72 | 73 | div.grey-shadow { 74 | box-shadow: 4px 4px 6px grey; 75 | } 76 | 77 | div.scroll { 78 | overflow-y:auto 79 | } 80 | -------------------------------------------------------------------------------- /Workspace/static/fonts/FontAwesome.otf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/enums/Calatrava/bd02ae3dbd4ed8e86dda479534e9583b1c56b131/Workspace/static/fonts/FontAwesome.otf -------------------------------------------------------------------------------- /Workspace/static/fonts/fontawesome-webfont.eot: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/enums/Calatrava/bd02ae3dbd4ed8e86dda479534e9583b1c56b131/Workspace/static/fonts/fontawesome-webfont.eot -------------------------------------------------------------------------------- /Workspace/static/fonts/fontawesome-webfont.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/enums/Calatrava/bd02ae3dbd4ed8e86dda479534e9583b1c56b131/Workspace/static/fonts/fontawesome-webfont.ttf -------------------------------------------------------------------------------- /Workspace/static/fonts/fontawesome-webfont.woff: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/enums/Calatrava/bd02ae3dbd4ed8e86dda479534e9583b1c56b131/Workspace/static/fonts/fontawesome-webfont.woff -------------------------------------------------------------------------------- /Workspace/static/fonts/fontawesome-webfont.woff2: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/enums/Calatrava/bd02ae3dbd4ed8e86dda479534e9583b1c56b131/Workspace/static/fonts/fontawesome-webfont.woff2 -------------------------------------------------------------------------------- /Workspace/static/img/.keep: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/enums/Calatrava/bd02ae3dbd4ed8e86dda479534e9583b1c56b131/Workspace/static/img/.keep -------------------------------------------------------------------------------- /Workspace/static/img/Calatrava.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/enums/Calatrava/bd02ae3dbd4ed8e86dda479534e9583b1c56b131/Workspace/static/img/Calatrava.png -------------------------------------------------------------------------------- /Workspace/static/img/background.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/enums/Calatrava/bd02ae3dbd4ed8e86dda479534e9583b1c56b131/Workspace/static/img/background.jpg -------------------------------------------------------------------------------- /Workspace/static/img/background_index.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/enums/Calatrava/bd02ae3dbd4ed8e86dda479534e9583b1c56b131/Workspace/static/img/background_index.jpg -------------------------------------------------------------------------------- /Workspace/static/img/background_me.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/enums/Calatrava/bd02ae3dbd4ed8e86dda479534e9583b1c56b131/Workspace/static/img/background_me.jpg -------------------------------------------------------------------------------- /Workspace/static/img/bilibili/head.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/enums/Calatrava/bd02ae3dbd4ed8e86dda479534e9583b1c56b131/Workspace/static/img/bilibili/head.jpg -------------------------------------------------------------------------------- /Workspace/static/img/corpus/.keep: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/enums/Calatrava/bd02ae3dbd4ed8e86dda479534e9583b1c56b131/Workspace/static/img/corpus/.keep -------------------------------------------------------------------------------- /Workspace/static/img/donate-alipay.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/enums/Calatrava/bd02ae3dbd4ed8e86dda479534e9583b1c56b131/Workspace/static/img/donate-alipay.jpg -------------------------------------------------------------------------------- /Workspace/static/img/donate-wechat.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/enums/Calatrava/bd02ae3dbd4ed8e86dda479534e9583b1c56b131/Workspace/static/img/donate-wechat.jpg -------------------------------------------------------------------------------- /Workspace/static/img/favicon.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/enums/Calatrava/bd02ae3dbd4ed8e86dda479534e9583b1c56b131/Workspace/static/img/favicon.png -------------------------------------------------------------------------------- /Workspace/static/img/head_128.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/enums/Calatrava/bd02ae3dbd4ed8e86dda479534e9583b1c56b131/Workspace/static/img/head_128.png -------------------------------------------------------------------------------- /Workspace/static/img/instagram/.keep: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/enums/Calatrava/bd02ae3dbd4ed8e86dda479534e9583b1c56b131/Workspace/static/img/instagram/.keep -------------------------------------------------------------------------------- /Workspace/static/img/postman_128.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/enums/Calatrava/bd02ae3dbd4ed8e86dda479534e9583b1c56b131/Workspace/static/img/postman_128.png -------------------------------------------------------------------------------- /Workspace/static/js/buttons.js: -------------------------------------------------------------------------------- 1 | /*! @license 2 | * Project: Buttons 3 | * Description: A highly customizable CSS button library built with Sass and Compass 4 | * Author: Alex Wolfe and Rob Levin 5 | * License: Apache License v2.0 6 | */ 7 | (function($,window,document,undefined){var pluginName="menuButton";var menuClass=".button-dropdown";var defaults={propertyName:"value"};function Plugin(element,options){this.options=$.extend({},defaults,options);this._defaults=defaults;this._name=pluginName;this.$element=$(element);this.init()}Plugin.prototype={constructor:Plugin,init:function(){this.toggle()},toggle:function(el,options){if(this.$element.data("dropdown")==="show"){this.hideMenu()}else{this.showMenu()}},showMenu:function(){this.$element.data("dropdown","show");this.$element.find("ul").show();this.$element.find(".button:first").addClass("is-active")},hideMenu:function(){this.$element.data("dropdown","hide");this.$element.find("ul").hide();this.$element.find(".button:first").removeClass("is-active")}};$.fn[pluginName]=function(options){return this.each(function(){if($.data(this,"plugin_"+pluginName)){$.data(this,"plugin_"+pluginName).toggle()}else{$.data(this,"plugin_"+pluginName,new Plugin(this,options))}})};$(document).on("click",function(e){$.each($("[data-buttons=dropdown]"),function(i,value){if($(e.target.offsetParent)[0]!=$(this)[0]){if($.data(this,"plugin_"+pluginName)){$.data(this,"plugin_"+pluginName).hideMenu();$(this).find("ul").hide()}}})});$(document).on("click","[data-buttons=dropdown]",function(e){var $dropdown=$(e.currentTarget);$dropdown.menuButton()});$(document).on("click","[data-buttons=dropdown] > a",function(e){e.preventDefault()})})(jQuery,window,document); -------------------------------------------------------------------------------- /Workspace/static/js/me.js: -------------------------------------------------------------------------------- 1 | var currentPosition,timer; 2 | var currentPosition,timer; 3 | function toTop() { 4 | timer = setInterval("stepToTop()",0); 5 | } 6 | function stepToTop(){ 7 | currentPosition = document.documentElement.scrollTop || document.body.scrollTop; 8 | currentPosition -= document.body.scrollTop / 12; 9 | if(currentPosition>0) { 10 | window.scrollTo(0,currentPosition); 11 | } else { 12 | window.scrollTo(0,0); 13 | clearInterval(timer); 14 | } 15 | } 16 | 17 | function getMsgToDo(url, foo) { 18 | var xmlhttp; 19 | if (window.XMLHttpRequest) { 20 | xmlhttp = new XMLHttpRequest(); 21 | } else { 22 | xmlhttp = new ActiveXObject("Microsoft.XMLHTTP"); 23 | } 24 | xmlhttp.onreadystatechange = function() { 25 | if (xmlhttp.readyState == 4 && xmlhttp.status == 200) { 26 | foo(xmlhttp.responseText); 27 | } 28 | } 29 | xmlhttp.open("GET",url,true); 30 | xmlhttp.send(); 31 | } 32 | 33 | function postMsgToDo(url, body, foo) { 34 | var xmlhttp; 35 | if (window.XMLHttpRequest) { 36 | xmlhttp = new XMLHttpRequest(); 37 | } else { 38 | xmlhttp = new ActiveXObject("Microsoft.XMLHTTP"); 39 | } 40 | xmlhttp.onreadystatechange = function() { 41 | if (xmlhttp.readyState == 4 && xmlhttp.status == 200) { 42 | foo(xmlhttp.responseText); 43 | } 44 | } 45 | xmlhttp.open("POST",url,true); 46 | xmlhttp.send(body); 47 | } 48 | 49 | function getMsgToChangeHTML(url, id) { 50 | getMsgToDo(url, function(text) { 51 | get(id).innerHTML=text; 52 | }); 53 | } 54 | 55 | function _posts_love(pid) { 56 | getMsgToDo("/api/posts/love?pid=" + pid, function(text) { 57 | alert(text) 58 | }) 59 | } 60 | 61 | function _corpus_love(pid) { 62 | getMsgToDo("/api/corpus/love?cpid=" + pid, function(text) { 63 | alert(text) 64 | }) 65 | } 66 | 67 | function _posts_comment(pid, name, email, comment, v_id, v_a, onSuccess, onFailed) { 68 | var obj = { 69 | pid: pid, 70 | name: name, 71 | email: email, 72 | comment: comment, 73 | v_id: v_id, 74 | v_a: v_a, 75 | }; 76 | postMsgToDo("/api/posts/comment", JSON.stringify(obj), function(text) { 77 | alert(text) 78 | if (text == "发表成功!") { 79 | onSuccess(); 80 | } else { 81 | onFailed(); 82 | } 83 | }) 84 | } 85 | 86 | function _corpus_comment(cpid, name, email, comment, v_id, v_a, onSuccess, onFailed) { 87 | var obj = { 88 | cpid: cpid, 89 | name: name, 90 | email: email, 91 | comment: comment, 92 | v_id: v_id, 93 | v_a: v_a, 94 | }; 95 | postMsgToDo("/api/corpus/comment", JSON.stringify(obj), function(text) { 96 | alert(text) 97 | if (text == "发表成功!") { 98 | onSuccess(); 99 | } else { 100 | onFailed(); 101 | } 102 | }) 103 | } 104 | 105 | function _message(name, email, comment, v_id, v_a, onSuccess, onFailed) { 106 | var obj = { 107 | name: name, 108 | email: email, 109 | comment: comment, 110 | v_id: v_id, 111 | v_a: v_a, 112 | }; 113 | postMsgToDo("/api/message", JSON.stringify(obj), function(text) { 114 | alert(text) 115 | if (text == "发表成功!") { 116 | onSuccess(); 117 | } else { 118 | onFailed(); 119 | } 120 | }) 121 | } 122 | 123 | function _getVerification(onSuccess) { 124 | getMsgToDo("/api/verification", onSuccess) 125 | } 126 | 127 | function _setReferFloor(floor) { 128 | $("#comment").val("!*<引用 " + floor + " 楼>*!\n"); 129 | location = "#comment"; 130 | } 131 | 132 | function setCookie(cname,cvalue) 133 | { 134 | var d = new Date(); 135 | var hours = 24 * 7 * 365; 136 | d.setHours(d.getHours() + hours); 137 | document.cookie = cname + "=" + cvalue + "; expires=" + d.toGMTString(); 138 | } 139 | 140 | function getCookie(cname) 141 | { 142 | var name = cname + "="; 143 | var ca = document.cookie.split(';'); 144 | for(var i=0; i 2 | 3 | 4 | 5 | 我的文集列表 | {{_pjango_param_title_message}} 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 71 | 72 | 73 |
74 | 75 | {{{_pjango_template_navigation_bar}}} 76 | 77 |
78 |
79 |
80 |
81 |
82 |

我的文集列表

83 |
84 |
85 |
86 |
87 | {{#_pjango_param_table_corpus}} 88 |
89 |
90 |
91 |
92 |

{{_pjango_param_table_Corpus_TITLE}}

93 |

{{_pjango_param_table_Corpus_MEMO}}

94 |
95 |
96 |
97 | 98 | 最后更新于: {{_pjango_param_table_Corpus_UPDATEDATE}} 99 |
100 |
101 | 102 | 创建于: {{_pjango_param_table_Corpus_DATE}} 103 |
104 |
105 | 106 |
107 |
108 |
109 | {{/_pjango_param_table_corpus}} 110 | 111 |
112 |
113 |
114 |
115 |
116 | 回到顶部 117 |
118 |
119 |
120 | 121 |
122 | 123 | {{{_pjango_template_footer_bar}}} 124 |
125 | 126 | 127 | 128 | 129 | -------------------------------------------------------------------------------- /Workspace/templates/error_404.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 没有找到该页面 | {{_pjango_param_title_message}} 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 24 | 25 | 26 |
27 | 28 | {{{_pjango_template_navigation_bar}}} 29 | 30 |
31 |
32 |
33 |
34 |
35 |

没有找到该页面

36 |
37 | 40 |
41 |
42 |
43 |
44 |
45 | 46 | 47 | 48 | {{{_pjango_template_footer_bar}}} 49 |
50 | 51 | 52 | 53 | 54 | -------------------------------------------------------------------------------- /Workspace/templates/error_notsupport.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 已经下线的服务 | {{_pjango_param_title_message}} 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 24 | 25 | 26 |
27 | 28 | {{{_pjango_template_navigation_bar}}} 29 | 30 |
31 |
32 |

该服务已不再提供

33 | 36 |
37 |
38 | 39 | 40 | {{{_pjango_template_footer_bar}}} 41 |
42 | 43 | 44 | 45 | 46 | -------------------------------------------------------------------------------- /Workspace/templates/footer_bar.html: -------------------------------------------------------------------------------- 1 | 17 | -------------------------------------------------------------------------------- /Workspace/templates/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | {{_pjango_param_name}}的技术博客 | {{_pjango_param_title_message}} 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 22 | 23 | 24 |
25 | 26 | {{{_pjango_template_navigation_bar}}} 27 | 28 |
29 |
30 |
31 |
32 |
33 |

34 |

{{_pjango_param_name}}

35 |

{{_pjango_param_message}}

36 |
37 |

38 | 39 | 40 | 41 | 42 | 43 |

44 |

45 | 技术博文 46 | 文集文章 47 | 关于博客 48 |

49 |

50 | 动态聚合 51 | 数据统计 52 | 所有板块 53 |

54 |
55 |
56 |
57 |
58 |
59 | {{{_pjango_template_footer_bar}}} 60 |
61 | 62 | 63 | 64 | 65 | -------------------------------------------------------------------------------- /Workspace/templates/modules.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 博客包含的所有内容 | {{_pjango_param_title_message}} 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 71 | 72 | 73 |
74 | 75 | {{{_pjango_template_navigation_bar}}} 76 | 77 |
78 |
79 |
80 |
81 |
82 |

所有内容

83 |
84 |
85 |
86 |
87 |
88 | 搜索全站内容 89 |
90 |
91 | {{#_pjango_param_table_module}} 92 |
93 |
94 |
95 |
96 |

{{{_pjango_param_table_Module_ICON}}} {{_pjango_param_table_Module_TITLE}}

97 |

{{_pjango_param_table_Module_MEMO}}

98 |
99 |
100 |
101 |
102 |
103 | {{/_pjango_param_table_module}} 104 |
105 |
106 |
107 |
108 |
109 | 回到顶部 110 |
111 |
112 |
113 | 114 |
115 | 116 | {{{_pjango_template_footer_bar}}} 117 |
118 | 119 | 120 | 121 | 122 | -------------------------------------------------------------------------------- /Workspace/templates/navigation_bar.html: -------------------------------------------------------------------------------- 1 | 18 | -------------------------------------------------------------------------------- /Workspace/templates/posts/.keep: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/enums/Calatrava/bd02ae3dbd4ed8e86dda479534e9583b1c56b131/Workspace/templates/posts/.keep -------------------------------------------------------------------------------- /Workspace/templates/posts_archive.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 技术博文归档 | {{_pjango_param_title_message}} 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 67 | 68 | 69 |
70 | 71 | {{{_pjango_template_navigation_bar}}} 72 | 73 |
74 |
75 |
76 |
77 |
78 |

技术博文归档

79 |
80 |
81 |
82 |
83 | 84 |
85 |
86 |
87 |
88 | {{#_pjango_param_table_posts}} 89 | 98 | {{/_pjango_param_table_posts}} 99 |
100 |
101 |
102 |
103 |
104 |
105 |
106 |
107 |
108 | 回到顶部 109 |
110 |
111 |
112 | 113 |
114 | 115 | {{{_pjango_template_footer_bar}}} 116 |
117 | 118 | 119 | 120 | 121 | -------------------------------------------------------------------------------- /Workspace/templates/posts_search.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 搜索博文 | {{_pjango_param_title_message}} 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 37 | 38 | 39 |
40 | 41 | {{{_pjango_template_navigation_bar}}} 42 | 43 |
44 |
45 |
46 |
47 |
48 |
49 |

搜索全部博文

50 |
51 |
52 |
53 | 54 | 55 | 56 | 57 |
58 |
59 |

60 | 全部 61 | {{#_pjango_param_all_tags}} 62 | {{{_pjango_param_table_PostsTag_TAG}}} 63 | {{/_pjango_param_all_tags}} 64 |

65 |
66 |
67 |
68 | 73 |
74 | 75 |
76 | 77 | {{{_pjango_template_footer_bar}}} 78 |
79 | 103 | 104 | 105 | 106 | -------------------------------------------------------------------------------- /Workspace/templates/project.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 业余项目列表 | {{_pjango_param_title_message}} 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 71 | 72 | 73 |
74 | 75 | {{{_pjango_template_navigation_bar}}} 76 | 77 |
78 |
79 |
80 |
81 |
82 |

业余项目列表

83 |
84 |
85 |
86 |
87 | {{#_pjango_param_table_project}} 88 |
89 |
90 |
91 |
92 |

{{_pjango_param_table_Project_TITLE}}

93 |

{{_pjango_param_table_Project_SUBTITLE}}

94 |

95 | {{#_pjango_param_table_Project_TAGHTML}} 96 | {{{_pjango_param_table_PostsTag_TAG}}} 97 | {{/_pjango_param_table_Project_TAGHTML}} 98 |

99 |

{{_pjango_param_table_Project_MEMO}}

100 |
101 |
102 |
103 | 104 | 发布于: {{_pjango_param_table_Project_DATE}} 105 |
106 |
107 | 108 |
109 |
110 |
111 | {{/_pjango_param_table_project}} 112 | 113 |
114 |
115 |
116 |
117 |
118 | 回到顶部 119 |
120 |
121 |
122 | 123 |
124 | 125 | {{{_pjango_template_footer_bar}}} 126 |
127 | 128 | 129 | 130 | 131 | -------------------------------------------------------------------------------- /Workspace/templates/search.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 全站内容搜索 | {{_pjango_param_title_message}} 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 38 | 39 | 40 |
41 | 42 | {{{_pjango_template_navigation_bar}}} 43 | 44 |
45 |
46 |
47 |
48 |
49 |
50 |

搜索全站内容

51 |
52 |
53 | 54 | 55 | 56 | 57 |
58 |
59 |

60 | 全站内容 61 | {{#_pjango_param_all_tags}} 62 | {{{_pjango_param_table_Module_ICON}}} {{{_pjango_param_table_Module_TITLE}}} 63 | {{/_pjango_param_all_tags}} 64 |

65 |
66 |
67 |
68 | 69 |
70 | 71 | {{{_pjango_template_footer_bar}}} 72 |
73 | 97 | 98 | 99 | 100 | -------------------------------------------------------------------------------- /Workspace/templates/search_result_bilibili_cell.html: -------------------------------------------------------------------------------- 1 |
2 |
3 |
4 |

5 | 6 | 我 发布的【原创视频】: 7 |

8 |
9 |

10 | 11 | 12 | {{_pjango_param_title}} 13 | 14 |

15 |

16 | 17 | 18 | 收录于播单:《{{_pjango_param_list_title}}》 19 | 20 |

21 |
22 |
23 |
24 |
25 | 26 |
27 |
28 |

29 | 30 | {{_pjango_param_name}} 31 |

32 |

33 | {{_pjango_param_memo}} 34 |

35 |
36 |
37 |
38 | 39 | {{_pjango_param_date}} 40 |
41 |
42 |
43 |
44 |
-------------------------------------------------------------------------------- /Workspace/templates/search_result_corpus_cell.html: -------------------------------------------------------------------------------- 1 |
2 |
3 |
4 |

5 | 6 | 我 发表的【文集文章】: 7 |

8 |
9 |

10 | 11 | 12 | {{_pjango_param_title}} 13 | 14 |

15 |

16 | 17 | 18 | 收录于文集:《{{_pjango_param_corpus_title}}》 19 | 20 |

21 |
22 |
23 | 24 | {{_pjango_param_date}} 25 |
26 |
27 |
28 |
29 |
-------------------------------------------------------------------------------- /Workspace/templates/search_result_instagram_cell.html: -------------------------------------------------------------------------------- 1 |
2 |
3 |
4 |

5 | 6 | Postman 抓取到的【Instagram】: 7 |

8 |
9 |
10 |
11 |
12 | 13 |
14 |
15 |

16 | 17 | {{_pjango_param_author_fullname}} 18 |

19 |

20 | {{_pjango_param_caption}} 21 |

22 |
23 |
24 |
25 | 26 | {{_pjango_param_date}} 27 |
28 |
29 |
30 |
31 |
-------------------------------------------------------------------------------- /Workspace/templates/search_result_posts_cell.html: -------------------------------------------------------------------------------- 1 |
2 |
3 |
4 |

5 | 6 | 我 发表的【技术博文】: 7 |

8 | 9 | 10 | {{_pjango_param_title}} 11 | 12 |

13 | {{#_pjango_param_table_tag}} 14 | {{{_pjango_param_table_PostsTag_TAG}}} 15 | {{/_pjango_param_table_tag}} 16 |

17 |
18 |
19 | 20 | {{_pjango_param_date}} 21 |
22 |
23 |
24 |
25 |
-------------------------------------------------------------------------------- /Workspace/templates/search_result_project_cell.html: -------------------------------------------------------------------------------- 1 |
2 |
3 |
4 |
5 |
6 |

7 | 8 | 我 发布的【业余项目】: 9 |

10 |

{{_pjango_param_title}}

11 |

{{_pjango_param_subtitle}}

12 |

13 | {{#_pjango_param_table_TAGHTML}} 14 | {{{_pjango_param_table_PostsTag_TAG}}} 15 | {{/_pjango_param_table_TAGHTML}} 16 |

17 |

{{_pjango_param_memo}}

18 |
19 |
20 |
21 | 22 | 发布于: {{_pjango_param_date}} 23 |
24 |
25 | 26 |
27 |
28 |
29 | -------------------------------------------------------------------------------- /Workspace/templates/update_bilibili_cell.html: -------------------------------------------------------------------------------- 1 |
2 |
3 |
4 |

5 | 6 | 7 | 我 8 | 刚刚发布了新的 9 | 原创视频: 10 | 11 |

12 |
13 |

14 | 15 | 16 | {{_pjango_param_title}} 17 | 18 |

19 |

20 | 21 | 22 | 收录于播单:《{{_pjango_param_list_title}}》 23 | 24 |

25 |
26 |
27 |
28 |
29 | 30 |
31 |
32 |

33 | 34 | {{_pjango_param_name}} 35 |

36 |

37 | {{_pjango_param_memo}} 38 |

39 |
40 |
41 |
42 | 43 | {{_pjango_param_date}} 44 |
45 |
46 |
47 |
48 |
-------------------------------------------------------------------------------- /Workspace/templates/update_corpus_cell.html: -------------------------------------------------------------------------------- 1 |
2 |
3 |
4 |

5 | 6 | 7 | 我 8 | 刚刚发布了新的 9 | 文集文章: 10 | 11 |

12 |
13 |

14 | 15 | 16 | {{_pjango_param_title}} 17 | 18 |

19 |

20 | 21 | 22 | 收录于文集:《{{_pjango_param_corpus_title}}》 23 | 24 |

25 |
26 |
27 | 28 | {{_pjango_param_date}} 29 |
30 |
31 |
32 |
33 |
-------------------------------------------------------------------------------- /Workspace/templates/update_instagram_cell.html: -------------------------------------------------------------------------------- 1 |
2 |
3 |
4 |

5 | 6 | 7 | Postman 8 | 刚刚抓取到了的新 9 | Instagram: 10 | 11 |

12 |
13 |
14 |
15 |
16 | 17 |
18 |
19 |

20 | 21 | {{_pjango_param_author_fullname}} 22 |

23 |

24 | {{_pjango_param_caption}} 25 |

26 |
27 |
28 |
29 | 30 | {{_pjango_param_date}} 31 |
32 |
33 |
34 |
35 |
-------------------------------------------------------------------------------- /Workspace/templates/update_posts_cell.html: -------------------------------------------------------------------------------- 1 |
2 |
3 |
4 |

5 | 6 | 7 | 我 8 | 刚刚发布了新 9 | 技术博文: 10 | 11 |

12 |
13 |

14 | 15 | 16 | {{_pjango_param_title}} 17 | 18 |

19 |

20 | {{#_pjango_param_table_tag}} 21 | {{{_pjango_param_table_PostsTag_TAG}}} 22 | {{/_pjango_param_table_tag}} 23 |

24 |
25 |
26 | 27 | {{_pjango_param_date}} 28 |
29 |
30 |
31 |
32 |
-------------------------------------------------------------------------------- /Workspace/templates/update_project_cell.html: -------------------------------------------------------------------------------- 1 |
2 |
3 |
4 |

5 | 6 | 7 | 我 8 | 刚刚发布了新的 9 | 业余项目: 10 | 11 |

12 |
13 |

14 | 15 | 16 | {{_pjango_param_title}} 17 | 18 |

19 |

{{_pjango_param_sub_title}}

20 |

21 | {{#_pjango_param_table_TAGHTML}} 22 | {{{_pjango_param_table_PostsTag_TAG}}} 23 | {{/_pjango_param_table_TAGHTML}} 24 |

25 |

{{_pjango_param_memo}}

26 |

27 |
28 |
29 | 30 | {{_pjango_param_date}} 31 |
32 |
33 |
34 |
35 |
--------------------------------------------------------------------------------