]> gitweb.ps.run Git - chirp/blob - src/main.c
likes, comments, write, read
[chirp] / src / main.c
1 #include <stdint.h>\r
2 #include <mongoose.h>\r
3 #define STB_DS_IMPLEMENTATION\r
4 #include <stb_ds.h>\r
5 \r
6 \r
7 #define NAME_LEN 32\r
8 #define CONTENT_LEN 1024\r
9 #define HTML_LEN 1024*8\r
10 \r
11 typedef struct Post Post;\r
12 typedef struct User User;\r
13 \r
14 User * UsersFind(struct mg_str name);\r
15 \r
16 // Post / User\r
17 \r
18 struct Post {\r
19   time_t timestamp;\r
20   User * user;\r
21   char content[CONTENT_LEN];\r
22   int likes;\r
23   Post * comments;\r
24   Post * parent;\r
25 };\r
26 \r
27 struct User {\r
28   char name[NAME_LEN];\r
29   Post * posts;\r
30   User ** following;\r
31 };\r
32 \r
33 Post\r
34 PostNew() {\r
35   Post result;\r
36   memset(&result, 0, sizeof(result));\r
37   return result;\r
38 }\r
39 \r
40 void\r
41 PostWrite(Post * post, FILE * f) {\r
42   fwrite(&post->timestamp, 8, 1, f);\r
43   fwrite(post->user->name, 1, sizeof(post->user->name), f);\r
44   fwrite(post->content, 1, sizeof(post->content), f);\r
45 }\r
46 \r
47 void\r
48 PostRead(Post * post, FILE * f) {\r
49   fread(&post->timestamp, 8, 1, f);\r
50   char name[NAME_LEN];\r
51   fread(name, 1, sizeof(name), f);\r
52   post->user = UsersFind(mg_str(name));\r
53   fread(post->content, 1, sizeof(post->content), f);\r
54 }\r
55 \r
56 User\r
57 UserNew(const char * name) {\r
58   User result;\r
59   memset(&result, 0, sizeof(result));\r
60   strcpy(result.name, name);\r
61   return result;\r
62 }\r
63 \r
64 void\r
65 UserWrite(User * user, FILE * f) {\r
66   fwrite(user->name, 1, sizeof(user->name), f);\r
67 \r
68   uint32_t numFollowing = arrlen(user->following);\r
69   uint32_t numPosts = arrlen(user->posts);\r
70   fwrite(&numFollowing, 4, 1, f);\r
71   fwrite(&numPosts, 4, 1, f);\r
72 \r
73   for (int i = 0; i < numFollowing; i++) {\r
74     fwrite(user->following[i]->name, 1, sizeof(user->following[i]->name), f);\r
75   }\r
76 \r
77   for (int i = 0; i < numPosts; i++) {\r
78     PostWrite(&user->posts[i], f);\r
79   }\r
80 }\r
81 \r
82 void\r
83 UserRead(User * user, FILE * f) {\r
84   fread(user->name, 1, sizeof(user->name), f);\r
85 \r
86   uint32_t numFollowing;\r
87   uint32_t numPosts;\r
88   fread(&numFollowing, 1, 4, f);\r
89   fread(&numPosts, 1, 4, f);\r
90 \r
91   user->following = NULL;\r
92   for (int i = 0; i < numFollowing; i++) {\r
93     char name[NAME_LEN];\r
94     fread(name, 1, sizeof(name), f);\r
95     arrput(user->following, UsersFind(mg_str(name)));\r
96   }\r
97 \r
98   user->posts = NULL;\r
99   for (int i = 0; i < numPosts; i++) {\r
100     Post post = PostNew();\r
101     PostRead(&post, f);\r
102     arrput(user->posts, post);\r
103   }\r
104 }\r
105 \r
106 \r
107 \r
108 // Users\r
109 \r
110 User ** g_users;\r
111 static User pat, yas, tof;\r
112 \r
113 void\r
114 UsersSetup() {\r
115   pat = UserNew("pat");\r
116   yas = UserNew("yas");\r
117   tof = UserNew("tof");\r
118 \r
119   arrput(pat.following, &yas);\r
120   arrput(yas.following, &pat);\r
121   arrput(tof.following, &yas);\r
122   arrput(tof.following, &pat);\r
123 \r
124   arrput(g_users, &pat);\r
125   arrput(g_users, &yas);\r
126   arrput(g_users, &tof);\r
127 }\r
128 \r
129 User *\r
130 UsersFind(struct mg_str name) {\r
131   for (int i = 0; i < arrlen(g_users); i++) {\r
132     User * user = g_users[i];\r
133     if (strncmp(user->name, name.ptr, name.len) == 0)\r
134       return user;\r
135   }\r
136   return NULL;\r
137 }\r
138 \r
139 \r
140 \r
141 // HTML\r
142 \r
143 void\r
144 http_redirect(struct mg_connection * c, struct mg_http_message * hm) {\r
145   struct mg_str *referer = mg_http_get_header(hm, "Referer");\r
146 \r
147   static char redirectHeaders[128];\r
148   snprintf(redirectHeaders, 128,\r
149     "Location: %.*s\r\n",\r
150       referer->len, referer->ptr);\r
151   mg_http_reply(c, 303, redirectHeaders, "");\r
152 }\r
153 \r
154 char *\r
155 html_404()\r
156 {\r
157   static char html[1024];\r
158   snprintf(html, 1024,\r
159           "<!DOCTYPE html>"\r
160           "<html>"\r
161           "<body>"\r
162           "<p><i>Eeeeeeh?</i> <b>The Website's Under Construction</b></p>"\r
163           "</body>"\r
164           "</html>");\r
165   return html;\r
166 }\r
167 \r
168 char *\r
169 html_user_not_found()\r
170 {\r
171   static char html[1024];\r
172   snprintf(html, 1024,\r
173           "<!DOCTYPE html>"\r
174           "<html>"\r
175           "<body>"\r
176           "<p>User was not found :/</p>"\r
177           "</body>"\r
178           "</html>");\r
179   return html;\r
180 }\r
181 \r
182 int\r
183 html_post(Post * post, char * buffer, int bufferLen) {\r
184   int result = 0;\r
185   int printed = 0;\r
186 \r
187   printed = snprintf(buffer, bufferLen,\r
188     "<div style=\"border: 2px solid black; margin: 2px; padding: 2px;\">[%s] <a href=\"/user/%s\">%s</a>: %s <br />",\r
189       ctime(&post->timestamp),\r
190       post->user->name,\r
191       post->user->name,\r
192       post->content);\r
193   buffer += printed; bufferLen -= printed; result += printed;\r
194 \r
195   if (post->likes > 0) {\r
196     printed = snprintf(buffer, bufferLen,\r
197       "Likes: %d<br />", post->likes);\r
198     buffer += printed; bufferLen -= printed; result += printed;\r
199   }\r
200 \r
201   printed = snprintf(buffer, bufferLen,\r
202     "<form action=\"/api/like/%s/%llu\" method=\"post\">"\r
203       "<input type=\"submit\" value=\"Like!\">"\r
204     "</form>",\r
205       post->user->name,\r
206       (size_t)post);\r
207   buffer += printed; bufferLen -= printed; result += printed;\r
208   \r
209   if (arrlen(post->comments) > 0) {\r
210     printed = snprintf(buffer, bufferLen,\r
211       "Comments:<br />");\r
212     buffer += printed; bufferLen -= printed; result += printed;\r
213 \r
214     for (int i = 0; i < arrlen(post->comments); i++) {\r
215       printed = html_post(&post->comments[i], buffer, bufferLen);\r
216       buffer += printed; bufferLen -= printed; result += printed;\r
217     }\r
218   }\r
219 \r
220   printed = snprintf(buffer, bufferLen,\r
221     "<form action=\"/api/comment/%s/%llu\" method=\"post\">"\r
222       "<input type=\"text\" name=\"content\" autofocus>"\r
223       "<input type=\"submit\" value=\"Comment!\">"\r
224     "</form>"\r
225     "</div>",\r
226       post->user->name,\r
227       (size_t)post);\r
228   buffer += printed; bufferLen -= printed; result += printed;\r
229 \r
230   return result;\r
231 }\r
232 \r
233 char *\r
234 html_home(User * user)\r
235 {\r
236   Post ** posts = NULL;\r
237   for (int i = 0; i < arrlen(user->following); i++) {\r
238     User * userF = user->following[i];\r
239     for (int j = 0; j < arrlen(userF->posts); j++) {\r
240       Post * post = &userF->posts[j];\r
241       bool inserted = false;\r
242       for (int k = 0; k < arrlen(posts); k++) {\r
243         if (post->timestamp > posts[k]->timestamp) {\r
244           arrins(posts, k, post);\r
245           inserted = true;\r
246           break;\r
247         }\r
248       }\r
249       if (! inserted)\r
250         arrput(posts, post);\r
251     }\r
252   }\r
253 \r
254   static char html[HTML_LEN];\r
255   snprintf(html, HTML_LEN,\r
256           "<!DOCTYPE html>"\r
257           "<html>"\r
258           "<body>"\r
259           "<form action=\"/api/post/%s\" method=\"post\">"\r
260             "<input type=\"text\" name=\"content\" autofocus><br>"\r
261             "<input type=\"submit\" value=\"Chirp!\">"\r
262           "</form>",\r
263           user->name);\r
264   snprintf(html+strlen(html), HTML_LEN-strlen(html),\r
265           "<form action=\"/api/write/%s\" method=\"post\">"\r
266             "<input type=\"submit\" value=\"Write!\">"\r
267           "</form>"\r
268           "<form action=\"/api/read/%s\" method=\"post\">"\r
269             "<input type=\"submit\" value=\"Read!\">"\r
270           "</form>",\r
271           user->name,\r
272           user->name);\r
273   for (int i = 0; i < arrlen(posts); i++) {\r
274     html_post(posts[i], html+strlen(html), HTML_LEN-strlen(html));\r
275   }\r
276   snprintf(html+strlen(html), HTML_LEN-strlen(html),\r
277           "</body>"\r
278           "</html>");\r
279   arrfree(posts);\r
280   return html;\r
281 }\r
282 \r
283 char *\r
284 html_user(User * user)\r
285 {\r
286   static char html[HTML_LEN];\r
287   snprintf(html, HTML_LEN,\r
288           "<!DOCTYPE html>"\r
289           "<html>"\r
290           "<body>"\r
291           "<form action=\"/api/post/%s\" method=\"post\">"\r
292             "<input type=\"text\" name=\"content\" autofocus><br>"\r
293             "<input type=\"submit\" value=\"Chirp!\">"\r
294           "</form>",\r
295           user->name);\r
296   snprintf(html+strlen(html), HTML_LEN-strlen(html),\r
297           "<form action=\"/api/write/%s\" method=\"post\">"\r
298             "<input type=\"submit\" value=\"Write!\">"\r
299           "</form>"\r
300           "<form action=\"/api/read/%s\" method=\"post\">"\r
301             "<input type=\"submit\" value=\"Read!\">"\r
302           "</form>",\r
303           user->name,\r
304           user->name);\r
305   for (int i = arrlen(user->posts) - 1; i >= 0; i--) {\r
306     html_post(&user->posts[i], html+strlen(html), HTML_LEN-strlen(html));\r
307   }\r
308   snprintf(html+strlen(html), HTML_LEN-strlen(html),\r
309           "</body>"\r
310           "</html>");\r
311   return html;\r
312 }\r
313 \r
314 \r
315 \r
316 static void fn(struct mg_connection *c, int ev, void *ev_data, void *fn_data)\r
317 {\r
318   if (ev == MG_EV_HTTP_MSG)\r
319   {\r
320     struct mg_http_message *hm = (struct mg_http_message *)ev_data;\r
321     \r
322     // POST\r
323     if (mg_strcmp(hm->method, mg_str("POST")) == 0)\r
324     {\r
325       // printf("POST: %.*s  %.*s\n",\r
326       //   hm->uri.len, hm->uri.ptr,\r
327       //   hm->body.len, hm->body.ptr);\r
328       // printf("Message: %.*s\n",\r
329       //   hm->message.len, hm->message.ptr);\r
330       \r
331 \r
332       // TODO: make sure this diesnt error. shhhhhh!\r
333       struct mg_str caps[10];\r
334 \r
335       if (mg_match(hm->uri, mg_str("/api/post/*"), caps)) {\r
336         User * user = UsersFind(caps[0]);\r
337         \r
338         if (user != NULL) {\r
339           Post newPost;\r
340           newPost.likes = 0;\r
341           newPost.comments = NULL;\r
342           newPost.parent = NULL;\r
343           newPost.timestamp = time(NULL);\r
344           newPost.user = user;\r
345           mg_http_get_var(&hm->body, "content", newPost.content, sizeof(newPost.content)-1);\r
346           arrput(user->posts, newPost);\r
347         }\r
348       }\r
349       else if (mg_match(hm->uri, mg_str("/api/comment/*/*"), caps)) {\r
350         User * user = UsersFind(caps[0]);\r
351         \r
352         if (user != NULL) {\r
353           Post * parent = (Post *)atoll(caps[1].ptr);\r
354           if (parent != NULL) {\r
355             Post newComment;\r
356             newComment.likes = 0;\r
357             newComment.comments = NULL;\r
358             newComment.parent = parent;\r
359             newComment.timestamp = time(NULL);\r
360             newComment.user = user;\r
361             mg_http_get_var(&hm->body, "content", newComment.content, sizeof(newComment.content)-1);\r
362             arrput(parent->comments, newComment);\r
363           }\r
364         }\r
365       }\r
366       else if (mg_match(hm->uri, mg_str("/api/like/*/*"), caps)) {\r
367         User * user = UsersFind(caps[0]);\r
368         if (user != NULL) {\r
369           Post * post = (Post *)atoll(caps[1].ptr);\r
370           if (post != NULL)\r
371             post->likes++;\r
372         }\r
373       }\r
374       else if (mg_match(hm->uri, mg_str("/api/write/*"), caps)) {\r
375         User * user = UsersFind(caps[0]);\r
376         if (user != NULL) {\r
377           char filename[128];\r
378           snprintf(filename, 128, "data/users/%s", user->name);\r
379           FILE * f = fopen(filename, "w");\r
380           UserWrite(user, f);\r
381           fclose(f);\r
382         }\r
383       }\r
384       else if (mg_match(hm->uri, mg_str("/api/read/*"), caps)) {\r
385         User * user = UsersFind(caps[0]);\r
386         if (user != NULL) {\r
387           char filename[128];\r
388           snprintf(filename, 128, "data/users/%s", user->name);\r
389           FILE * f = fopen(filename, "r");\r
390           UserRead(user, f);\r
391           fclose(f);\r
392         }\r
393       }\r
394       http_redirect(c, hm);\r
395     }\r
396     // GET\r
397     else if (mg_strcmp(hm->method, mg_str("GET")) == 0)\r
398     {\r
399       struct mg_str caps[2];\r
400 \r
401       if (mg_match(hm->uri, mg_str("/home/*"), caps)) {\r
402         User * user = UsersFind(caps[0]);\r
403 \r
404         if (user == NULL)\r
405           mg_http_reply(c, 200, "", html_user_not_found());\r
406         else\r
407           mg_http_reply(c, 200, "", html_home(user));\r
408       }\r
409       else if (mg_match(hm->uri, mg_str("/user/*"), caps)) {\r
410         User * user = UsersFind(caps[0]);\r
411 \r
412         if (user == NULL)\r
413           mg_http_reply(c, 200, "", html_user_not_found());\r
414         else\r
415           mg_http_reply(c, 200, "", html_user(user));\r
416       }\r
417       else {\r
418         mg_http_reply(c, 404, "", html_404());\r
419       }\r
420     }\r
421     else {\r
422         mg_http_reply(c, 404, "", "Unknown");\r
423     }\r
424   }\r
425 }\r
426 \r
427 int main(int argc, char *argv[])\r
428 {\r
429   UsersSetup();\r
430 \r
431   struct mg_mgr mgr;\r
432   mg_mgr_init(&mgr);\r
433   mg_http_listen(&mgr, "http://0.0.0.0:8000", fn, &mgr);\r
434   while (1)\r
435     mg_mgr_poll(&mgr, 1000);\r
436   mg_mgr_free(&mgr);\r
437   return 0;\r
438 }\r