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.textfilter.markdown; 18 import vibe.web.web; 19 20 import std.conv; 21 import std.datetime; 22 import std.exception; 23 import std.string; 24 25 26 void registerVibeLogWeb(URLRouter router, VibeLogController controller) 27 { 28 import vibelog.internal.diskuto; 29 30 string sub_path = controller.settings.rootDir; 31 assert(sub_path.endsWith("/"), "Blog site URL must end with '/'."); 32 33 if (sub_path.length > 1) router.get(sub_path[0 .. $-1], staticRedirect(sub_path)); 34 35 auto diskuto = router.registerDiskuto(controller); 36 37 auto web = new VibeLogWeb(controller, diskuto); 38 39 auto websettings = new WebInterfaceSettings; 40 websettings.urlPrefix = sub_path; 41 router.registerWebInterface(web, websettings); 42 43 auto fsettings = new HTTPFileServerSettings; 44 fsettings.serverPathPrefix = sub_path; 45 router.get(sub_path ~ "*", serveStaticFiles("public", fsettings)); 46 } 47 48 49 /// private 50 /*private*/ final class VibeLogWeb { 51 private { 52 VibeLogController m_ctrl; 53 VibeLogSettings m_settings; 54 DiskutoWeb m_diskuto; 55 SessionVar!(string, "vibelog.loggedInUser") m_loggedInUser; 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, string _error = null) 72 { 73 auto info = PageInfo(m_settings, m_ctrl.getPostListInfo(page - 1)); 74 info.refPath = m_settings.rootDir; 75 info.loginError = _error; 76 render!("vibelog.postlist.dt", info); 77 } 78 79 @errorDisplay!getPost 80 @path("posts/:postname") 81 void getPost(string _postname, string _error) 82 { 83 m_diskuto.setupRequest(); 84 85 auto info = PostInfo(m_settings); 86 info.users = m_ctrl.db.getAllUsers(); 87 try info.post = m_ctrl.db.getPost(_postname); 88 catch(Exception e){ return; } // -> gives 404 error 89 if (m_settings.enableBackButton) 90 info.postPage = m_ctrl.getPostPage(info.post.id); 91 info.recentPosts = m_ctrl.getRecentPosts(); 92 info.refPath = m_settings.rootDir~"posts/"~_postname; 93 info.error = _error; 94 info.diskuto = m_diskuto; 95 96 render!("vibelog.post.dt", info); 97 } 98 99 @path("posts/:postname/:filename") 100 void getPostFile(string _postname, string _filename, HTTPServerResponse res) 101 { 102 import vibe.core.stream : pipe; 103 import vibe.inet.mimetypes : getMimeTypeForFile; 104 105 auto f = m_ctrl.db.getFile(_postname, _filename); 106 if (f) { 107 res.contentType = getMimeTypeForFile(_filename); 108 f.pipe(res.bodyWriter); 109 } 110 } 111 112 @path("feed/rss") 113 void getRSSFeed(HTTPServerResponse res) 114 { 115 auto cfg = m_ctrl.config; 116 117 auto ch = new RssChannel; 118 ch.title = cfg.feedTitle; 119 ch.link = cfg.feedLink; 120 ch.description = cfg.feedDescription; 121 ch.copyright = cfg.copyrightString; 122 ch.pubDate = Clock.currTime(UTC()); 123 ch.imageTitle = cfg.feedImageTitle; 124 ch.imageUrl = cfg.feedImageUrl; 125 ch.imageLink = cfg.feedLink; 126 127 m_ctrl.db.getPostsForCategory(cfg.categories, 0, (size_t i, Post p){ 128 if( !p.isPublic ) return true; 129 130 auto usr = m_ctrl.db.getUserByName(p.author); 131 132 auto itm = new RssEntry; 133 itm.title = p.header; 134 itm.description = p.subHeader; 135 itm.link = m_settings.siteURL.toString() ~ "posts/" ~ p.name; 136 itm.author = usr 137 ? usr.email ~ " (" ~ usr.name ~ ")" 138 : "unknown@unknown.unknown (Unknown)"; 139 itm.guid = p.id.toString; 140 itm.pubDate = p.date; 141 ch.entries ~= itm; 142 return i < 10; 143 }); 144 145 auto feed = new RssFeed; 146 feed.channels ~= ch; 147 148 res.headers["Content-Type"] = "application/rss+xml"; 149 feed.render(res.bodyWriter); 150 } 151 152 @path("/filter") 153 void getFilter(string message, string filters, HTTPServerResponse res) 154 { 155 auto p = new Post; 156 p.content = message; 157 import std.array : split; 158 p.filters = filters.split(); 159 res.writeBody(p.renderContentAsHtml(m_settings)); 160 } 161 162 @path("/sitemap.xml") 163 void getSitemap(HTTPServerResponse res) 164 { 165 res.contentType = "application/xml"; 166 res.bodyWriter.write("<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n"); 167 res.bodyWriter.write("<urlset xmlns=\"http://www.sitemaps.org/schemas/sitemap/0.9\">\n"); 168 void writeEntry(string[] parts...){ 169 res.bodyWriter.write("<url><loc>"); 170 res.bodyWriter.write(m_settings.siteURL.toString()); 171 foreach( p; parts ) 172 res.bodyWriter.write(p); 173 res.bodyWriter.write("</loc></url>\n"); 174 } 175 176 // home page 177 writeEntry(); 178 179 m_ctrl.db.getPostsForCategory(m_ctrl.config.categories, 0, (size_t i, Post p){ 180 if( p.isPublic ) writeEntry("posts/", p.name); 181 return true; 182 }); 183 184 res.bodyWriter.write("</urlset>\n"); 185 res.bodyWriter.flush(); 186 } 187 188 @errorDisplay!get 189 void postLogin(string username, string password, string redirect = null) 190 { 191 import vibelog.internal.passwordhash : validatePasswordHash; 192 193 auto usr = m_ctrl.db.getUserByName(username); 194 enforce(usr && validatePasswordHash(usr.password, password), 195 "Invalid user name or password."); 196 m_loggedInUser = username; 197 .redirect(redirect.length ? redirect : m_ctrl.settings.rootDir); 198 } 199 200 void getLogout() 201 { 202 m_loggedInUser = null; 203 redirect(m_ctrl.settings.rootDir); 204 } 205 } 206 207 import vibelog.info : VibeLogInfo; 208 struct PageInfo 209 { 210 import vibelog.controller : PostListInfo; 211 PostListInfo pli; 212 alias pli this; 213 string rootPath; 214 string refPath; 215 string loginError; 216 217 import vibelog.settings : VibeLogSettings; 218 this(VibeLogSettings settings, PostListInfo pli) 219 { 220 this.pli = pli; 221 this.rootPath = settings.siteURL.path.toString(); 222 } 223 } 224 225 struct PostInfo 226 { 227 string loginError; 228 229 import vibelog.info : VibeLogInfo; 230 VibeLogInfo vli; 231 alias vli this; 232 233 import vibelog.user : User; 234 User[string] users; 235 236 import vibelog.settings : VibeLogSettings; 237 VibeLogSettings settings; 238 239 import vibelog.post : Post; 240 Post post; 241 242 int postPage; 243 244 DiskutoWeb diskuto; 245 246 Post[] recentPosts; 247 string rootPath; 248 string refPath; 249 string error; 250 251 this(VibeLogSettings settings) 252 { 253 vli = VibeLogInfo(settings); 254 this.settings = settings; 255 this.rootPath = settings.siteURL.path.toString; 256 } 257 }