1 module vibelog.web;
2 
3 public import vibelog.controller;
4 
5 import vibelog.config;
6 import vibelog.post;
7 import vibelog.rss;
8 import vibelog.settings;
9 import vibelog.user;
10 
11 import diskuto.web;
12 import vibe.core.log;
13 import vibe.db.mongo.connection;
14 import vibe.http.fileserver;
15 import vibe.http.router;
16 import vibe.inet.url;
17 import vibe.templ.diet;
18 import vibe.textfilter.markdown;
19 import vibe.web.web;
20 
21 import std.conv;
22 import std.datetime;
23 import std.exception;
24 import std.string;
25 
26 
27 void registerVibeLogWeb(URLRouter router, VibeLogController controller)
28 {
29 	import vibelog.internal.diskuto;
30 
31 	string sub_path = controller.settings.rootDir;
32 	assert(sub_path.endsWith("/"), "Blog site URL must end with '/'.");
33 
34 	if (sub_path.length > 1) router.get(sub_path[0 .. $-1], staticRedirect(sub_path));
35 
36 	auto diskuto = router.registerDiskuto(controller);
37 
38 	auto web = new VibeLogWeb(controller, diskuto);
39 
40 	auto websettings = new WebInterfaceSettings;
41 	websettings.urlPrefix = sub_path;
42 	router.registerWebInterface(web, websettings);
43 
44 	auto fsettings = new HTTPFileServerSettings;
45 	fsettings.serverPathPrefix = sub_path;
46 	router.get(sub_path ~ "*", serveStaticFiles("public", fsettings));
47 }
48 
49 
50 /// private
51 /*private*/ final class VibeLogWeb {
52 	private {
53 		VibeLogController m_ctrl;
54 		VibeLogSettings m_settings;
55 		DiskutoWeb m_diskuto;
56 	}
57 
58 	this(VibeLogController controller, DiskutoWeb diskuto)
59 	{
60 		m_settings = controller.settings;
61 		m_ctrl = controller;
62 		m_diskuto = diskuto;
63 
64 		enforce(m_settings.rootDir.startsWith("/") && m_settings.rootDir.endsWith("/"), "All local URLs must start with and end with '/'.");
65 	}
66 
67 	//
68 	// public pages
69 	//
70 
71 	void get(int page = 1)
72 	{
73 		auto info = PageInfo(m_settings, m_ctrl.getPostListInfo(page - 1));
74 		info.refPath = m_settings.rootDir;
75 		render!("vibelog.postlist.dt", info);
76 	}
77 
78 	@errorDisplay!getPost
79 	@path("posts/:postname")
80 	void getPost(string _postname, string _error)
81 	{
82 		auto info = PostInfo(m_settings);
83 		info.users = m_ctrl.db.getAllUsers();
84 		try info.post = m_ctrl.db.getPost(_postname);
85 		catch(Exception e){ return; } // -> gives 404 error
86 		info.recentPosts = m_ctrl.getRecentPosts();
87 		info.refPath = m_settings.rootDir~"posts/"~_postname;
88 		info.error = _error;
89 		info.diskuto = m_diskuto;
90 
91 		render!("vibelog.post.dt", info);
92 	}
93 
94 	@path("posts/:postname/:filename")
95 	void getPostFile(string _postname, string _filename, HTTPServerResponse res)
96 	{
97 		import vibe.inet.mimetypes;
98 		auto f = m_ctrl.db.getFile(_postname, _filename);
99 		if (f) {
100 			res.contentType = getMimeTypeForFile(_filename);
101 			res.bodyWriter.write(f);
102 		}
103 	}
104 
105 	@path("feed/rss")
106 	void getRSSFeed(HTTPServerResponse res)
107 	{
108 		auto cfg = m_ctrl.config;
109 
110 		auto ch = new RssChannel;
111 		ch.title = cfg.feedTitle;
112 		ch.link = cfg.feedLink;
113 		ch.description = cfg.feedDescription;
114 		ch.copyright = cfg.copyrightString;
115 		ch.pubDate = Clock.currTime(UTC());
116 		ch.imageTitle = cfg.feedImageTitle;
117 		ch.imageUrl = cfg.feedImageUrl;
118 		ch.imageLink = cfg.feedLink;
119 
120 		m_ctrl.db.getPostsForCategory(cfg.categories, 0, (size_t i, Post p){
121 				if( !p.isPublic ) return true;
122 				auto itm = new RssEntry;
123 				itm.title = p.header;
124 				itm.description = p.subHeader;
125 				itm.link = m_settings.siteURL.toString() ~ "posts/" ~ p.name;
126 				itm.author = p.author;
127 				itm.guid = "xxyyzz";
128 				itm.pubDate = p.date;
129 				ch.entries ~= itm;
130 				return i < 10;
131 			});
132 
133 		auto feed = new RssFeed;
134 		feed.channels ~= ch;
135 
136 		res.headers["Content-Type"] = "application/rss+xml";
137 		feed.render(res.bodyWriter);
138 	}
139 
140 	@path("/filter")
141 	void getFilter(string message, string filters, HTTPServerResponse res)
142 	{
143 		auto p = new Post;
144 		p.content = message;
145 		import std.array : split;
146 		p.filters = filters.split();
147 		res.writeBody(p.renderContentAsHtml(m_settings));
148 	}
149 
150 	@path("/sitemap.xml")
151 	void getSitemap(HTTPServerResponse res)
152 	{
153 		res.contentType = "application/xml";
154 		res.bodyWriter.write("<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n");
155 		res.bodyWriter.write("<urlset xmlns=\"http://www.sitemaps.org/schemas/sitemap/0.9\">\n");
156 		void writeEntry(string[] parts...){
157 			res.bodyWriter.write("<url><loc>");
158 			res.bodyWriter.write(m_settings.siteURL.toString());
159 			foreach( p; parts )
160 				res.bodyWriter.write(p);
161 			res.bodyWriter.write("</loc></url>\n");
162 		}
163 
164 		// home page
165 		writeEntry();
166 
167 		m_ctrl.db.getPostsForCategory(m_ctrl.config.categories, 0, (size_t i, Post p){
168 				if( p.isPublic ) writeEntry("posts/", p.name);
169 				return true;
170 			});
171 		
172 		res.bodyWriter.write("</urlset>\n");
173 		res.bodyWriter.flush();
174 	}
175 }
176 
177 import vibelog.info : VibeLogInfo;
178 struct PageInfo
179 {
180 	import vibelog.controller : PostListInfo;
181 	PostListInfo pli;
182 	alias pli this;
183 	string refPath;
184 
185 	import vibelog.settings : VibeLogSettings;
186 	this(VibeLogSettings settings, PostListInfo pli)
187 	{
188 		this.pli = pli;
189 	}
190 }
191 
192 struct PostInfo
193 {
194 	import vibelog.info : VibeLogInfo;
195 	VibeLogInfo vli;
196 	alias vli this;
197 
198 	import vibelog.user : User;
199 	User[string] users;
200 
201 	import vibelog.settings : VibeLogSettings;
202 	VibeLogSettings settings;
203 
204 	import vibelog.post : Post;
205 	Post post;
206 
207 	DiskutoWeb diskuto;
208 
209 	Post[] recentPosts;
210 	string refPath;
211 	string error;
212 
213 	this(VibeLogSettings settings)
214 	{
215 		vli = VibeLogInfo(settings);
216 		this.settings = settings;
217 	}
218 }