1 module vibelog.dbcontroller; 2 3 public import vibelog.config; 4 public import vibelog.post; 5 public import vibelog.user; 6 7 import vibe.db.mongo.mongo; 8 import vibe.core.log; 9 import vibe.data.bson; 10 import vibe.mail.smtp; 11 import vibe.stream.memory; 12 import vibe.templ.diet; 13 14 import std.exception; 15 import std.variant; 16 17 18 class DBController { 19 private { 20 MongoDB m_db; 21 string m_dbname; 22 MongoCollection m_configs; 23 MongoCollection m_users; 24 MongoCollection m_posts; 25 MongoCollection m_comments; 26 } 27 28 this(string host, ushort port, string dbname) 29 { 30 m_db = connectMongoDB(host, port); 31 m_dbname = dbname; 32 m_configs = m_db[m_dbname~".configs"]; 33 m_users = m_db[m_dbname~".users"]; 34 m_posts = m_db[m_dbname~".posts"]; 35 m_comments = m_db[m_dbname~".comments"]; 36 37 // Upgrade post contained comments to their collection 38 foreach( p; m_posts.find(["comments": ["$exists": true]], ["comments": 1]) ){ 39 foreach( c; p.comments ){ 40 c["_id"] = BsonObjectID.generate(); 41 c["postId"] = p._id; 42 m_comments.insert(c); 43 } 44 m_posts.update(["_id": p._id], ["$unset": ["comments": 1]]); 45 } 46 } 47 48 Config getConfig(string name, bool createdefault = false) 49 { 50 auto configbson = m_configs.findOne(["name": Bson(name)]); 51 if( !configbson.isNull() ) 52 return Config.fromBson(configbson); 53 enforce(createdefault, "Configuration does not exist."); 54 auto cfg = new Config; 55 cfg.name = name; 56 m_configs.insert(cfg.toBson()); 57 return cfg; 58 } 59 60 void setConfig(Config cfg) 61 { 62 Bson update = cfg.toBson(); 63 m_configs.update(["name": Bson(cfg.name)], update); 64 } 65 66 void deleteConfig(string name) 67 { 68 m_configs.remove(["name": Bson(name)]); 69 } 70 71 Config[] getAllConfigs() 72 { 73 Bson[string] query; 74 Config[] ret; 75 foreach( config; m_configs.find(query) ){ 76 auto c = Config.fromBson(config); 77 ret ~= c; 78 } 79 return ret; 80 } 81 82 User[string] getAllUsers() 83 { 84 Bson[string] query; 85 User[string] ret; 86 foreach( user; m_users.find(query) ){ 87 auto u = User.fromBson(user); 88 ret[u.username] = u; 89 } 90 if( ret.length == 0 ){ 91 auto initial_admin = new User; 92 initial_admin.username = "admin"; 93 initial_admin.password = generatePasswordHash("admin"); 94 initial_admin.name = "Default Administrator"; 95 initial_admin.groups ~= "admin"; 96 m_users.insert(initial_admin); 97 ret["admin"] = initial_admin; 98 } 99 return ret; 100 } 101 102 User getUser(BsonObjectID userid) 103 { 104 auto userbson = m_users.findOne(["_id": Bson(userid)]); 105 return User.fromBson(userbson); 106 } 107 108 User getUser(string name) 109 { 110 auto userbson = m_users.findOne(["username": Bson(name)]); 111 if( userbson.isNull() ){ 112 auto id = BsonObjectID.fromHexString(name); 113 logDebug("%s <-> %s", name, id.toString()); 114 assert(id.toString() == name); 115 userbson = m_users.findOne(["_id": Bson(id)]); 116 } 117 //auto userbson = m_users.findOne(Bson(["name" : Bson(name)])); 118 return User.fromBson(userbson); 119 } 120 121 BsonObjectID addUser(User user) 122 { 123 auto id = BsonObjectID.generate(); 124 Bson userbson = user.toBson(); 125 userbson["_id"] = Bson(id); 126 m_users.insert(userbson); 127 return id; 128 } 129 130 void modifyUser(User user) 131 { 132 assert(user._id.valid); 133 Bson update = user.toBson(); 134 m_users.update(["_id": Bson(user._id)], update); 135 } 136 137 void deleteUser(BsonObjectID id) 138 { 139 assert(id.valid); 140 m_users.remove(["_id": Bson(id)]); 141 } 142 143 int countPostsForCategory(string[] categories) 144 { 145 int cnt; 146 getPostsForCategory(categories, 0, (size_t, Post p){ if( p.isPublic ) cnt++; return true; }); 147 return cnt; 148 } 149 150 void getPostsForCategory(string[] categories, int nskip, bool delegate(size_t idx, Post post) del) 151 { 152 auto cats = new Bson[categories.length]; 153 foreach( i; 0 .. categories.length ) cats[i] = Bson(categories[i]); 154 Bson category = Bson(["$in" : Bson(cats)]); 155 Bson[string] query = ["query" : Bson(["category" : category]), "orderby" : Bson(["_id" : Bson(-1)])]; 156 foreach( idx, post; m_posts.find(query, null, QueryFlags.None, nskip) ){ 157 if( !del(idx, Post.fromBson(post)) ) 158 break; 159 } 160 } 161 162 void getPublicPostsForCategory(string[] categories, int nskip, bool delegate(size_t idx, Post post) del) 163 { 164 auto cats = new Bson[categories.length]; 165 foreach( i; 0 .. categories.length ) cats[i] = Bson(categories[i]); 166 Bson category = Bson(["$in" : Bson(cats)]); 167 Bson[string] query = ["query" : Bson(["category" : category, "isPublic": Bson(true)]), "orderby" : Bson(["_id" : Bson(-1)])]; 168 foreach( idx, post; m_posts.find(query, null, QueryFlags.None, nskip) ){ 169 if( !del(idx, Post.fromBson(post)) ) 170 break; 171 } 172 } 173 174 void getAllPosts(int nskip, bool delegate(size_t idx, Post post) del) 175 { 176 Bson[string] query; 177 Bson[string] extquery = ["query" : Bson(query), "orderby" : Bson(["_id" : Bson(-1)])]; 178 foreach( idx, post; m_posts.find(extquery, null, QueryFlags.None, nskip) ){ 179 if( !del(idx, Post.fromBson(post)) ) 180 break; 181 } 182 } 183 184 185 Post getPost(BsonObjectID postid) 186 { 187 auto postbson = m_posts.findOne(["_id": Bson(postid)]); 188 return Post.fromBson(postbson); 189 } 190 191 Post getPost(string name) 192 { 193 auto postbson = m_posts.findOne(["slug": Bson(name)]); 194 if( postbson.isNull() ) 195 postbson = m_posts.findOne(["_id" : Bson(BsonObjectID.fromHexString(name))]); 196 return Post.fromBson(postbson); 197 } 198 199 bool hasPost(string name) 200 { 201 return !m_posts.findOne(["slug": Bson(name)]).isNull(); 202 203 } 204 205 BsonObjectID addPost(Post post) 206 { 207 auto id = BsonObjectID.generate(); 208 Bson postbson = post.toBson(); 209 postbson["_id"] = Bson(id); 210 m_posts.insert(postbson); 211 return id; 212 } 213 214 void modifyPost(Post post) 215 { 216 assert(post.id.valid); 217 Bson update = post.toBson(); 218 m_posts.update(["_id": Bson(post.id)], update); 219 } 220 221 void deletePost(BsonObjectID id) 222 { 223 assert(id.valid); 224 m_posts.remove(["_id": Bson(id)]); 225 } 226 227 Comment[] getComments(BsonObjectID post_id, bool allow_inactive = false) 228 { 229 Comment[] ret; 230 foreach( c; m_comments.find(["postId": post_id]) ) 231 if( allow_inactive || c.isPublic.get!bool ) 232 ret ~= Comment.fromBson(c); 233 return ret; 234 } 235 236 long getCommentCount(BsonObjectID post_id) 237 { 238 return m_comments.count(["postId": Bson(post_id), "isPublic": Bson(true)]); 239 } 240 241 242 void addComment(BsonObjectID post_id, Comment comment) 243 { 244 Bson cmtbson = comment.toBson(); 245 comment.id = BsonObjectID.generate(); 246 comment.postId = post_id; 247 m_comments.insert(comment.toBson()); 248 249 try { 250 auto p = m_posts.findOne(["_id": post_id]); 251 auto u = m_users.findOne(["username": p.author]); 252 auto msg = new MemoryOutputStream; 253 254 auto post = Post.fromBson(p); 255 256 parseDietFileCompat!("mail.new_comment.dt", 257 Comment, "comment", 258 Post, "post")(msg, Variant(comment), Variant(post)); 259 260 auto mail = new Mail; 261 mail.headers["From"] = comment.authorName ~ " <" ~ comment.authorMail ~ ">"; 262 mail.headers["To"] = u.email.get!string; 263 mail.headers["Subject"] = "[VibeLog] New comment"; 264 mail.headers["Content-Type"] = "text/html"; 265 mail.bodyText = cast(string)msg.data(); 266 267 auto settings = new SmtpClientSettings; 268 //settings.host = m_settings.mailServer; 269 sendMail(settings, mail); 270 } catch(Exception e){ 271 logWarn("Failed to send comment mail: %s", e.msg); 272 } 273 } 274 275 void setCommentPublic(BsonObjectID comment_id, bool is_public) 276 { 277 m_comments.update(["_id": comment_id], ["$set": ["isPublic": is_public]]); 278 } 279 280 void deleteNonPublicComments(BsonObjectID post_id) 281 { 282 m_posts.remove(["postId": Bson(post_id), "isPublic": Bson(false)]); 283 } 284 }