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 }