1 module vibelog.post;
2 
3 import vibe.data.bson;
4 import vibe.textfilter.markdown;
5 import vibe.textfilter.html;
6 
7 import std.array;
8 import std.conv;
9 public import std.datetime;
10 
11 
12 class Post {
13 	BsonObjectID id;
14 	bool isPublic;
15 	bool commentsAllowed;
16 	string slug; // url entity to identify this post - generated from the header by default
17 	string author;  // user name
18 	string category; // can be hierarchical using dotted.syntax.format
19 	SysTime date;
20 	string header;
21 	string subHeader;
22 	string content;
23 	string headerImage;
24 	string[] tags;
25 	string[] trackbacks;
26 
27 	this()
28 	{
29 		id  = BsonObjectID.generate();
30 		date = Clock.currTime().toUTC();
31 	}
32 
33 	@property string name() const { return slug.length ? slug : id.toString(); }
34 	
35 	static Post fromBson(Bson bson)
36 	{
37 		auto ret = new Post;
38 		ret.id = cast(BsonObjectID)bson["_id"];
39 		ret.isPublic = cast(bool)bson["isPublic"];
40 		ret.commentsAllowed = cast(bool)bson["commentsAllowed"];
41 		ret.slug = cast(string)bson["slug"];
42 		ret.author = cast(string)bson["author"];
43 		ret.category = cast(string)bson["category"];
44 		ret.date = SysTime.fromISOExtString(cast(string)bson["date"]);
45 		ret.header = cast(string)bson["header"];
46 		ret.subHeader = cast(string)bson["subHeader"];
47 		ret.headerImage = cast(string)bson["headerImage"];
48 		ret.content = cast(string)bson["content"];
49 		foreach( t; cast(Bson[])bson["tags"] )
50 			ret.tags ~= cast(string)t;
51 		return ret;
52 	}
53 	
54 	Bson toBson()
55 	const {
56 		Bson[] btags;
57 		foreach( t; tags )
58 			btags ~= Bson(t);
59 
60 		Bson[string] ret;
61 		ret["_id"] = Bson(id);
62 		ret["isPublic"] = Bson(isPublic);
63 		ret["commentsAllowed"] = Bson(commentsAllowed);
64 		ret["slug"] = Bson(slug);
65 		ret["author"] = Bson(author);
66 		ret["category"] = Bson(category);
67 		ret["date"] = Bson(date.toISOExtString());
68 		ret["header"] = Bson(header);
69 		ret["subHeader"] = Bson(subHeader);
70 		ret["headerImage"] = Bson(headerImage);
71 		ret["content"] = Bson(content);
72 		ret["tags"] = Bson(btags);
73 
74 		return Bson(ret);
75 	}
76 
77 	string renderSubHeaderAsHtml()
78 	const {
79 		auto ret = appender!string();
80 		filterMarkdown(ret, subHeader);
81 		return ret.data;
82 	}
83 
84 	string renderContentAsHtml(string function(string)[] filters)
85 	const {
86 		auto html = filterMarkdown(content);
87 		foreach( flt; filters )
88 			html = flt(html);
89 		return html;
90 	}
91 }
92 
93 class Comment {
94 	BsonObjectID id;
95 	BsonObjectID postId;
96 	bool isPublic;
97 	SysTime date;
98 	int answerTo;
99 	string authorName;
100 	string authorMail;
101 	string authorHomepage;
102 	string authorIP;
103 	string header;
104 	string content;
105 
106 	static Comment fromBson(Bson bson)
107 	{
108 		auto ret = new Comment;
109 		ret.id = cast(BsonObjectID)bson["_id"];
110 		ret.postId = cast(BsonObjectID)bson["postId"];
111 		ret.isPublic = cast(bool)bson["isPublic"];
112 		ret.date = SysTime.fromISOExtString(cast(string)bson["date"]);
113 		ret.answerTo = cast(int)bson["answerTo"];
114 		ret.authorName = cast(string)bson["authorName"];
115 		ret.authorMail = cast(string)bson["authorMail"];
116 		ret.authorHomepage = cast(string)bson["authorHomepage"];
117 		ret.authorIP = bson["authorIP"].opt!string();
118 		ret.header = cast(string)bson["header"];
119 		ret.content = cast(string)bson["content"];
120 		return ret;
121 	}
122 	
123 	Bson toBson()
124 	const {
125 		Bson[string] ret;
126 		ret["_id"] = Bson(id);
127 		ret["postId"] = Bson(postId);
128 		ret["isPublic"] = Bson(isPublic);
129 		ret["date"] = Bson(date.toISOExtString());
130 		ret["answerTo"] = Bson(answerTo);
131 		ret["authorName"] = Bson(authorName);
132 		ret["authorMail"] = Bson(authorMail);
133 		ret["authorHomepage"] = Bson(authorHomepage);
134 		ret["authorIP"] = Bson(authorIP);
135 		ret["header"] = Bson(header);
136 		ret["content"] = Bson(content);
137 		return Bson(ret);
138 	}
139 
140 	string renderContentAsHtml()
141 	const {
142 		auto ret = appender!string();
143 		filterMarkdown(ret, htmlEscape(content));
144 		return ret.data;
145 	}
146 }
147 
148 string makeSlugFromHeader(string header)
149 {
150 	Appender!string ret;
151 	foreach( dchar ch; header ){
152 		switch( ch ){
153 			default:
154 				ret.put('-');
155 				break;
156 			case '"', '\'', '´', '`', '.', ',', ';', '!', '?':
157 				break;
158 			case 'A': .. case 'Z'+1:
159 				ret.put(cast(dchar)(ch - 'A' + 'a'));
160 				break;
161 			case 'a': .. case 'z'+1:
162 			case '0': .. case '9'+1:
163 				ret.put(ch);
164 				break;
165 		}
166 	}
167 	return ret.data;
168 }