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 }