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 info.recentPosts = m_ctrl.getRecentPosts(); 90 info.refPath = m_settings.rootDir~"posts/"~_postname; 91 info.error = _error; 92 info.diskuto = m_diskuto; 93 94 render!("vibelog.post.dt", info); 95 } 96 97 @path("posts/:postname/:filename") 98 void getPostFile(string _postname, string _filename, HTTPServerResponse res) 99 { 100 import vibe.core.stream : pipe; 101 import vibe.inet.mimetypes : getMimeTypeForFile; 102 103 auto f = m_ctrl.db.getFile(_postname, _filename); 104 if (f) { 105 res.contentType = getMimeTypeForFile(_filename); 106 f.pipe(res.bodyWriter); 107 } 108 } 109 110 @path("feed/rss") 111 void getRSSFeed(HTTPServerResponse res) 112 { 113 auto cfg = m_ctrl.config; 114 115 auto ch = new RssChannel; 116 ch.title = cfg.feedTitle; 117 ch.link = cfg.feedLink; 118 ch.description = cfg.feedDescription; 119 ch.copyright = cfg.copyrightString; 120 ch.pubDate = Clock.currTime(UTC()); 121 ch.imageTitle = cfg.feedImageTitle; 122 ch.imageUrl = cfg.feedImageUrl; 123 ch.imageLink = cfg.feedLink; 124 125 m_ctrl.db.getPostsForCategory(cfg.categories, 0, (size_t i, Post p){ 126 if( !p.isPublic ) return true; 127 auto itm = new RssEntry; 128 itm.title = p.header; 129 itm.description = p.subHeader; 130 itm.link = m_settings.siteURL.toString() ~ "posts/" ~ p.name; 131 itm.author = p.author; 132 itm.guid = "xxyyzz"; 133 itm.pubDate = p.date; 134 ch.entries ~= itm; 135 return i < 10; 136 }); 137 138 auto feed = new RssFeed; 139 feed.channels ~= ch; 140 141 res.headers["Content-Type"] = "application/rss+xml"; 142 feed.render(res.bodyWriter); 143 } 144 145 @path("/filter") 146 void getFilter(string message, string filters, HTTPServerResponse res) 147 { 148 auto p = new Post; 149 p.content = message; 150 import std.array : split; 151 p.filters = filters.split(); 152 res.writeBody(p.renderContentAsHtml(m_settings)); 153 } 154 155 @path("/sitemap.xml") 156 void getSitemap(HTTPServerResponse res) 157 { 158 res.contentType = "application/xml"; 159 res.bodyWriter.write("<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n"); 160 res.bodyWriter.write("<urlset xmlns=\"http://www.sitemaps.org/schemas/sitemap/0.9\">\n"); 161 void writeEntry(string[] parts...){ 162 res.bodyWriter.write("<url><loc>"); 163 res.bodyWriter.write(m_settings.siteURL.toString()); 164 foreach( p; parts ) 165 res.bodyWriter.write(p); 166 res.bodyWriter.write("</loc></url>\n"); 167 } 168 169 // home page 170 writeEntry(); 171 172 m_ctrl.db.getPostsForCategory(m_ctrl.config.categories, 0, (size_t i, Post p){ 173 if( p.isPublic ) writeEntry("posts/", p.name); 174 return true; 175 }); 176 177 res.bodyWriter.write("</urlset>\n"); 178 res.bodyWriter.flush(); 179 } 180 181 @errorDisplay!get 182 void postLogin(string username, string password, string redirect = null) 183 { 184 import vibelog.internal.passwordhash : validatePasswordHash; 185 186 auto usr = m_ctrl.db.getUserByName(username); 187 enforce(usr && validatePasswordHash(usr.password, password), 188 "Invalid user name or password."); 189 m_loggedInUser = username; 190 .redirect(redirect.length ? redirect : m_ctrl.settings.rootDir); 191 } 192 193 void getLogout() 194 { 195 m_loggedInUser = null; 196 redirect(m_ctrl.settings.rootDir); 197 } 198 } 199 200 import vibelog.info : VibeLogInfo; 201 struct PageInfo 202 { 203 import vibelog.controller : PostListInfo; 204 PostListInfo pli; 205 alias pli this; 206 string rootPath; 207 string refPath; 208 string loginError; 209 210 import vibelog.settings : VibeLogSettings; 211 this(VibeLogSettings settings, PostListInfo pli) 212 { 213 this.pli = pli; 214 this.rootPath = settings.siteURL.path.toString(); 215 } 216 } 217 218 struct PostInfo 219 { 220 string loginError; 221 222 import vibelog.info : VibeLogInfo; 223 VibeLogInfo vli; 224 alias vli this; 225 226 import vibelog.user : User; 227 User[string] users; 228 229 import vibelog.settings : VibeLogSettings; 230 VibeLogSettings settings; 231 232 import vibelog.post : Post; 233 Post post; 234 235 DiskutoWeb diskuto; 236 237 Post[] recentPosts; 238 string rootPath; 239 string refPath; 240 string error; 241 242 this(VibeLogSettings settings) 243 { 244 vli = VibeLogInfo(settings); 245 this.settings = settings; 246 this.rootPath = settings.siteURL.path.toString; 247 } 248 }