├── .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 | 
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 | 
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 |
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 |