Fix issues in demo templates
This commit is contained in:
+374
-373
@@ -1,373 +1,374 @@
|
||||
#include <stddef.h>
|
||||
|
||||
#define CWEB_IMPLEMENTATION
|
||||
#include "../cweb.h"
|
||||
|
||||
#define USERNAME_LIMIT 64
|
||||
|
||||
bool valid_name(CWEB_String str)
|
||||
{
|
||||
(void) str; // TODO
|
||||
return true;
|
||||
}
|
||||
|
||||
bool valid_email(CWEB_String str)
|
||||
{
|
||||
(void) str; // TODO
|
||||
return true;
|
||||
}
|
||||
|
||||
bool valid_pass(CWEB_String str)
|
||||
{
|
||||
(void) str; // TODO
|
||||
return true;
|
||||
}
|
||||
|
||||
bool valid_post_title(CWEB_String str)
|
||||
{
|
||||
(void) str; // TODO
|
||||
return true;
|
||||
}
|
||||
|
||||
bool valid_post_content(CWEB_String str)
|
||||
{
|
||||
(void) str; // TODO
|
||||
return true;
|
||||
}
|
||||
|
||||
bool valid_link(CWEB_String str)
|
||||
{
|
||||
(void) str;
|
||||
return true;
|
||||
}
|
||||
|
||||
bool valid_comment_content(CWEB_String str)
|
||||
{
|
||||
(void) str;
|
||||
return true;
|
||||
}
|
||||
|
||||
// TODO: check for the correct methods at each endpoint
|
||||
|
||||
static int user_exists(CWEB *cweb, CWEB_String name, CWEB_String pass)
|
||||
{
|
||||
CWEB_QueryResult res = cweb_database_select(cweb, "SELECT id, hash FROM Users WHERE username=?", name);
|
||||
|
||||
int64_t user_id;
|
||||
CWEB_PasswordHash hash;
|
||||
int ret = cweb_next_query_row(&res, &user_id, &hash);
|
||||
if (ret < 0) {
|
||||
cweb_free_query_result(&res);
|
||||
return -1;
|
||||
}
|
||||
if (ret == 0) {
|
||||
cweb_free_query_result(&res);
|
||||
return 0;
|
||||
}
|
||||
|
||||
ret = cweb_check_password(pass, hash);
|
||||
if (ret < 0) {
|
||||
cweb_free_query_result(&res);
|
||||
return -1;
|
||||
}
|
||||
if (ret > 0) {
|
||||
cweb_free_query_result(&res);
|
||||
return 0;
|
||||
}
|
||||
|
||||
return user_id;
|
||||
}
|
||||
|
||||
static void endpoint_api_login(CWEB *cweb, CWEB_Request *req)
|
||||
{
|
||||
if (cweb_get_user_id(req) != -1) {
|
||||
cweb_respond_redirect(req, CWEB_STR("/index"));
|
||||
return;
|
||||
}
|
||||
|
||||
CWEB_String name = cweb_get_param_s(req, CWEB_STR("username"));
|
||||
CWEB_String pass = cweb_get_param_s(req, CWEB_STR("password"));
|
||||
if (!valid_name(name) || !valid_pass(pass)) {
|
||||
cweb_respond_basic(req, 400, CWEB_STR("Invalid credentials"));
|
||||
return;
|
||||
}
|
||||
|
||||
int ret = user_exists(cweb, name, pass);
|
||||
if (ret < 0) {
|
||||
cweb_respond_basic(req, 500, CWEB_STR("Internal error"));
|
||||
return;
|
||||
}
|
||||
if (ret == 0) {
|
||||
cweb_respond_basic(req, 400, CWEB_STR("Invalid credentials"));
|
||||
return;
|
||||
}
|
||||
if (cweb_set_user_id(req, ret) < 0) {
|
||||
cweb_respond_basic(req, 500, CWEB_STR("Internal error"));
|
||||
return;
|
||||
}
|
||||
|
||||
cweb_respond_redirect(req, CWEB_STR("/index"));
|
||||
}
|
||||
|
||||
static void endpoint_api_signup(CWEB *cweb, CWEB_Request *req)
|
||||
{
|
||||
if (cweb_get_user_id(req) != -1) {
|
||||
cweb_respond_redirect(req, CWEB_STR("/index"));
|
||||
return;
|
||||
}
|
||||
|
||||
CWEB_String name = cweb_get_param_s(req, CWEB_STR("username"));
|
||||
CWEB_String email = cweb_get_param_s(req, CWEB_STR("email"));
|
||||
CWEB_String pass1 = cweb_get_param_s(req, CWEB_STR("password1"));
|
||||
CWEB_String pass2 = cweb_get_param_s(req, CWEB_STR("password2"));
|
||||
|
||||
if (!valid_name(name) || !valid_email(email) || !valid_pass(pass1)) {
|
||||
cweb_respond_basic(req, 400, CWEB_STR("Invalid credentials"));
|
||||
return;
|
||||
}
|
||||
|
||||
if (!cweb_streq(pass1, pass2)) {
|
||||
cweb_respond_basic(req, 400, CWEB_STR("The password was repeated incorrectly"));
|
||||
return;
|
||||
}
|
||||
|
||||
CWEB_PasswordHash hash;
|
||||
int ret = cweb_hash_password(pass1, 12, &hash);
|
||||
if (ret) {
|
||||
cweb_respond_basic(req, 500, CWEB_STR("Internal error"));
|
||||
return;
|
||||
}
|
||||
|
||||
int64_t insert_id = cweb_database_insert(cweb, "INSERT INTO Users(username, email, hash) VALUES (?, ?, ?)", name, email, hash);
|
||||
if (insert_id < 0) {
|
||||
// TODO: What if the user exists?
|
||||
cweb_respond_basic(req, 500, CWEB_STR("Internal error"));
|
||||
return;
|
||||
}
|
||||
if (cweb_set_user_id(req, insert_id) < 0) {
|
||||
cweb_respond_basic(req, 500, CWEB_STR("Internal error"));
|
||||
return;
|
||||
}
|
||||
|
||||
cweb_respond_redirect(req, CWEB_STR("/index"));
|
||||
}
|
||||
|
||||
static void endpoint_api_logout(CWEB *cweb, CWEB_Request *req)
|
||||
{
|
||||
(void) cweb;
|
||||
|
||||
cweb_set_user_id(req, -1);
|
||||
cweb_respond_redirect(req, CWEB_STR("/index"));
|
||||
}
|
||||
|
||||
static void endpoint_api_post(CWEB *cweb, CWEB_Request *req)
|
||||
{
|
||||
int user_id = cweb_get_user_id(req);
|
||||
if (user_id == -1) {
|
||||
cweb_respond_redirect(req, CWEB_STR("/index"));
|
||||
return;
|
||||
}
|
||||
|
||||
CWEB_String title = cweb_get_param_s(req, CWEB_STR("title"));
|
||||
CWEB_String link = cweb_get_param_s(req, CWEB_STR("link"));
|
||||
CWEB_String content = cweb_get_param_s(req, CWEB_STR("content"));
|
||||
CWEB_String csrf = cweb_get_param_s(req, CWEB_STR("csrf"));
|
||||
|
||||
if (!cweb_streq(cweb_get_csrf(req), csrf)) {
|
||||
cweb_respond_basic(req, 400, CWEB_STR("Invalid request"));
|
||||
return;
|
||||
}
|
||||
if (!valid_post_title(title)) {
|
||||
cweb_respond_basic(req, 400, CWEB_STR("Invalid title"));
|
||||
return;
|
||||
}
|
||||
if (!valid_link(link)) {
|
||||
cweb_respond_basic(req, 400, CWEB_STR("Invalid link"));
|
||||
return;
|
||||
}
|
||||
if (!valid_post_content(content)) {
|
||||
cweb_respond_basic(req, 400, CWEB_STR("Invalid content"));
|
||||
return;
|
||||
}
|
||||
|
||||
bool is_link = false;
|
||||
if (link.len > 0) {
|
||||
is_link = true;
|
||||
content = link;
|
||||
}
|
||||
|
||||
int64_t insert_id = cweb_database_insert(cweb,
|
||||
"INSERT INTO Posts(author, title, is_link, content) VALUES (?, ?, ?, ?)",
|
||||
user_id, title, is_link, content);
|
||||
|
||||
if (insert_id < 0) {
|
||||
cweb_respond_basic(req, 500, CWEB_STR("Internal error"));
|
||||
return;
|
||||
}
|
||||
int post_id = (int) insert_id;
|
||||
|
||||
CWEB_String target = cweb_format(req, "/post?id={}", post_id);
|
||||
cweb_respond_redirect(req, target);
|
||||
}
|
||||
|
||||
static void endpoint_api_comment(CWEB *cweb, CWEB_Request *req)
|
||||
{
|
||||
int user_id = cweb_get_user_id(req);
|
||||
if (user_id == -1) {
|
||||
cweb_respond_basic(req, 400, CWEB_STR("You are not logged in"));
|
||||
return;
|
||||
}
|
||||
|
||||
int parent_post = cweb_get_param_i(req, CWEB_STR("parent_post"));
|
||||
int parent_comment = cweb_get_param_i(req, CWEB_STR("parent_comment"));
|
||||
CWEB_String content = cweb_get_param_s(req, CWEB_STR("content"));
|
||||
CWEB_String csrf2 = cweb_get_param_s(req, CWEB_STR("csrf"));
|
||||
|
||||
if (!cweb_streq(cweb_get_csrf(req), csrf2)) {
|
||||
cweb_respond_basic(req, 400, CWEB_STR("Invalid request"));
|
||||
return;
|
||||
}
|
||||
|
||||
content = cweb_trim(content);
|
||||
if (!valid_comment_content(content)) {
|
||||
cweb_respond_basic(req, 400, CWEB_STR("Invalid content"));
|
||||
return;
|
||||
}
|
||||
|
||||
int64_t insert_id;
|
||||
if (parent_comment == -1) insert_id = cweb_database_insert(cweb, "INSERT INTO Comments(author, content, parent_post) VALUES (?, ?, ?)", user_id, content, parent_post);
|
||||
else insert_id = cweb_database_insert(cweb, "INSERT INTO Comments(author, content, parent_post, parent_comment) VALUES (?, ?, ?, ?)", user_id, content, parent_post, parent_comment);
|
||||
if (insert_id < 0) {
|
||||
cweb_respond_basic(req, 500, CWEB_STR("Internal error"));
|
||||
return;
|
||||
}
|
||||
|
||||
CWEB_String target = cweb_format(req, "/post?id={}", parent_post);
|
||||
cweb_respond_redirect(req, target);
|
||||
}
|
||||
|
||||
static void endpoint_index(CWEB *cweb, CWEB_Request *req)
|
||||
{
|
||||
(void) cweb;
|
||||
|
||||
cweb_respond_template(req, 200, CWEB_STR("demo/pages/index.wl"), -1);
|
||||
}
|
||||
|
||||
static void endpoint_write(CWEB *cweb, CWEB_Request *req)
|
||||
{
|
||||
(void) cweb;
|
||||
|
||||
if (cweb_get_user_id(req) == -1) {
|
||||
cweb_respond_redirect(req, CWEB_STR("/index"));
|
||||
return;
|
||||
}
|
||||
|
||||
cweb_respond_template(req, 200, CWEB_STR("demo/pages/write.wl"), -1);
|
||||
}
|
||||
|
||||
static void endpoint_login(CWEB *cweb, CWEB_Request *req)
|
||||
{
|
||||
(void) cweb;
|
||||
|
||||
if (cweb_get_user_id(req) != -1) {
|
||||
cweb_respond_redirect(req, CWEB_STR("/index"));
|
||||
return;
|
||||
}
|
||||
|
||||
cweb_respond_template(req, 200, CWEB_STR("demo/pages/login.wl"), -1);
|
||||
}
|
||||
|
||||
static void endpoint_signup(CWEB *cweb, CWEB_Request *req)
|
||||
{
|
||||
(void) cweb;
|
||||
|
||||
if (cweb_get_user_id(req) != -1) {
|
||||
cweb_respond_redirect(req, CWEB_STR("/index"));
|
||||
return;
|
||||
}
|
||||
|
||||
cweb_respond_template(req, 200, CWEB_STR("demo/pages/signup.wl"), -1);
|
||||
}
|
||||
|
||||
static void endpoint_post(CWEB *cweb, CWEB_Request *req)
|
||||
{
|
||||
int post_id = cweb_get_param_i(req, CWEB_STR("id"));
|
||||
if (post_id < 0) {
|
||||
cweb_respond_template(req, 404, CWEB_STR("demo/pages/notfound.wl"), -1);
|
||||
return;
|
||||
}
|
||||
|
||||
CWEB_QueryResult res = cweb_database_select(cweb, "SELECT COUNT(*) FROM Posts WHERE id=?", post_id);
|
||||
|
||||
int num;
|
||||
if (cweb_next_query_row(&res, &num) != 1) {
|
||||
cweb_respond_basic(req, 500, CWEB_STR(""));
|
||||
cweb_free_query_result(&res);
|
||||
return;
|
||||
}
|
||||
cweb_free_query_result(&res);
|
||||
|
||||
if (num < 0) {
|
||||
cweb_respond_basic(req, 500, CWEB_STR(""));
|
||||
return;
|
||||
}
|
||||
|
||||
if (num == 0) {
|
||||
cweb_respond_template(req, 404, CWEB_STR("demo/pages/notfound.wl"), -1);
|
||||
return;
|
||||
}
|
||||
|
||||
cweb_respond_template(req, 200, CWEB_STR("demo/pages/post.wl"), post_id);
|
||||
}
|
||||
|
||||
static void endpoint_fallback(CWEB *cweb, CWEB_Request *req)
|
||||
{
|
||||
(void) cweb;
|
||||
|
||||
cweb_respond_template(req, 404, CWEB_STR("demo/pages/notfound.wl"), -1);
|
||||
}
|
||||
|
||||
int main(void)
|
||||
{
|
||||
CWEB_String addr = CWEB_STR("127.0.0.1");
|
||||
uint16_t port = 8080;
|
||||
CWEB_String database_file = CWEB_STR(":memory:");
|
||||
CWEB_String schema_file = CWEB_STR("demo/schema.sql");
|
||||
|
||||
if (cweb_global_init() < 0)
|
||||
return -1;
|
||||
|
||||
CWEB *cweb = cweb_init(addr, port);
|
||||
if (cweb == NULL) {
|
||||
cweb_global_free();
|
||||
return -1;
|
||||
}
|
||||
|
||||
if (cweb_enable_database(cweb, database_file, schema_file) < 0) {
|
||||
cweb_free(cweb);
|
||||
cweb_global_free();
|
||||
return -1;
|
||||
}
|
||||
|
||||
cweb_trace_sql(cweb, true);
|
||||
|
||||
for (;;) {
|
||||
CWEB_Request *req = cweb_wait(cweb);
|
||||
if (0) {}
|
||||
else if (cweb_match_endpoint(req, CWEB_STR("/api/login"))) endpoint_api_login(cweb, req);
|
||||
else if (cweb_match_endpoint(req, CWEB_STR("/api/signup"))) endpoint_api_signup(cweb, req);
|
||||
else if (cweb_match_endpoint(req, CWEB_STR("/api/logout"))) endpoint_api_logout(cweb, req);
|
||||
else if (cweb_match_endpoint(req, CWEB_STR("/api/post"))) endpoint_api_post(cweb, req);
|
||||
else if (cweb_match_endpoint(req, CWEB_STR("/api/comment"))) endpoint_api_comment(cweb, req);
|
||||
else if (cweb_match_endpoint(req, CWEB_STR("/index"))) endpoint_index(cweb, req);
|
||||
else if (cweb_match_endpoint(req, CWEB_STR("/write"))) endpoint_write(cweb, req);
|
||||
else if (cweb_match_endpoint(req, CWEB_STR("/login"))) endpoint_login(cweb, req);
|
||||
else if (cweb_match_endpoint(req, CWEB_STR("/signup"))) endpoint_signup(cweb, req);
|
||||
else if (cweb_match_endpoint(req, CWEB_STR("/post"))) endpoint_post(cweb, req);
|
||||
else endpoint_fallback(cweb, req);
|
||||
}
|
||||
|
||||
cweb_free(cweb);
|
||||
cweb_global_free();
|
||||
return 0;
|
||||
}
|
||||
#include <stddef.h>
|
||||
|
||||
#define CWEB_IMPLEMENTATION
|
||||
#include "../cweb.h"
|
||||
|
||||
#define USERNAME_LIMIT 64
|
||||
|
||||
bool valid_name(CWEB_String str)
|
||||
{
|
||||
(void) str; // TODO
|
||||
return true;
|
||||
}
|
||||
|
||||
bool valid_email(CWEB_String str)
|
||||
{
|
||||
(void) str; // TODO
|
||||
return true;
|
||||
}
|
||||
|
||||
bool valid_pass(CWEB_String str)
|
||||
{
|
||||
(void) str; // TODO
|
||||
return true;
|
||||
}
|
||||
|
||||
bool valid_post_title(CWEB_String str)
|
||||
{
|
||||
(void) str; // TODO
|
||||
return true;
|
||||
}
|
||||
|
||||
bool valid_post_content(CWEB_String str)
|
||||
{
|
||||
(void) str; // TODO
|
||||
return true;
|
||||
}
|
||||
|
||||
bool valid_link(CWEB_String str)
|
||||
{
|
||||
(void) str;
|
||||
return true;
|
||||
}
|
||||
|
||||
bool valid_comment_content(CWEB_String str)
|
||||
{
|
||||
(void) str;
|
||||
return true;
|
||||
}
|
||||
|
||||
// TODO: check for the correct methods at each endpoint
|
||||
|
||||
static int user_exists(CWEB *cweb, CWEB_String name, CWEB_String pass)
|
||||
{
|
||||
CWEB_QueryResult res = cweb_database_select(cweb, "SELECT id, hash FROM Users WHERE username=?", name);
|
||||
|
||||
int64_t user_id;
|
||||
CWEB_PasswordHash hash;
|
||||
int ret = cweb_next_query_row(&res, &user_id, &hash);
|
||||
if (ret < 0) {
|
||||
cweb_free_query_result(&res);
|
||||
return -1;
|
||||
}
|
||||
if (ret == 0) {
|
||||
cweb_free_query_result(&res);
|
||||
return 0;
|
||||
}
|
||||
|
||||
ret = cweb_check_password(pass, hash);
|
||||
if (ret < 0) {
|
||||
cweb_free_query_result(&res);
|
||||
return -1;
|
||||
}
|
||||
if (ret > 0) {
|
||||
cweb_free_query_result(&res);
|
||||
return 0;
|
||||
}
|
||||
|
||||
return user_id;
|
||||
}
|
||||
|
||||
static void endpoint_api_login(CWEB *cweb, CWEB_Request *req)
|
||||
{
|
||||
if (cweb_get_user_id(req) != -1) {
|
||||
cweb_respond_redirect(req, CWEB_STR("/index"));
|
||||
return;
|
||||
}
|
||||
|
||||
CWEB_String name = cweb_get_param_s(req, CWEB_STR("username"));
|
||||
CWEB_String pass = cweb_get_param_s(req, CWEB_STR("password"));
|
||||
if (!valid_name(name) || !valid_pass(pass)) {
|
||||
cweb_respond_basic(req, 400, CWEB_STR("Invalid credentials"));
|
||||
return;
|
||||
}
|
||||
|
||||
int ret = user_exists(cweb, name, pass);
|
||||
if (ret < 0) {
|
||||
cweb_respond_basic(req, 500, CWEB_STR("Internal error"));
|
||||
return;
|
||||
}
|
||||
if (ret == 0) {
|
||||
cweb_respond_basic(req, 400, CWEB_STR("Invalid credentials"));
|
||||
return;
|
||||
}
|
||||
if (cweb_set_user_id(req, ret) < 0) {
|
||||
cweb_respond_basic(req, 500, CWEB_STR("Internal error"));
|
||||
return;
|
||||
}
|
||||
|
||||
cweb_respond_redirect(req, CWEB_STR("/index"));
|
||||
}
|
||||
|
||||
static void endpoint_api_signup(CWEB *cweb, CWEB_Request *req)
|
||||
{
|
||||
if (cweb_get_user_id(req) != -1) {
|
||||
cweb_respond_redirect(req, CWEB_STR("/index"));
|
||||
return;
|
||||
}
|
||||
|
||||
CWEB_String name = cweb_get_param_s(req, CWEB_STR("username"));
|
||||
CWEB_String email = cweb_get_param_s(req, CWEB_STR("email"));
|
||||
CWEB_String pass1 = cweb_get_param_s(req, CWEB_STR("password1"));
|
||||
CWEB_String pass2 = cweb_get_param_s(req, CWEB_STR("password2"));
|
||||
|
||||
if (!valid_name(name) || !valid_email(email) || !valid_pass(pass1)) {
|
||||
cweb_respond_basic(req, 400, CWEB_STR("Invalid credentials"));
|
||||
return;
|
||||
}
|
||||
|
||||
if (!cweb_streq(pass1, pass2)) {
|
||||
cweb_respond_basic(req, 400, CWEB_STR("The password was repeated incorrectly"));
|
||||
return;
|
||||
}
|
||||
|
||||
CWEB_PasswordHash hash;
|
||||
int ret = cweb_hash_password(pass1, 12, &hash);
|
||||
if (ret) {
|
||||
cweb_respond_basic(req, 500, CWEB_STR("Internal error"));
|
||||
return;
|
||||
}
|
||||
|
||||
int64_t insert_id = cweb_database_insert(cweb, "INSERT INTO Users(username, email, hash) VALUES (?, ?, ?)", name, email, hash);
|
||||
if (insert_id < 0) {
|
||||
// TODO: What if the user exists?
|
||||
cweb_respond_basic(req, 500, CWEB_STR("Internal error"));
|
||||
return;
|
||||
}
|
||||
if (cweb_set_user_id(req, insert_id) < 0) {
|
||||
cweb_respond_basic(req, 500, CWEB_STR("Internal error"));
|
||||
return;
|
||||
}
|
||||
|
||||
cweb_respond_redirect(req, CWEB_STR("/index"));
|
||||
}
|
||||
|
||||
static void endpoint_api_logout(CWEB *cweb, CWEB_Request *req)
|
||||
{
|
||||
(void) cweb;
|
||||
|
||||
cweb_set_user_id(req, -1);
|
||||
cweb_respond_redirect(req, CWEB_STR("/index"));
|
||||
}
|
||||
|
||||
static void endpoint_api_post(CWEB *cweb, CWEB_Request *req)
|
||||
{
|
||||
int user_id = cweb_get_user_id(req);
|
||||
if (user_id == -1) {
|
||||
cweb_respond_redirect(req, CWEB_STR("/index"));
|
||||
return;
|
||||
}
|
||||
|
||||
CWEB_String title = cweb_get_param_s(req, CWEB_STR("title"));
|
||||
CWEB_String link = cweb_get_param_s(req, CWEB_STR("link"));
|
||||
CWEB_String content = cweb_get_param_s(req, CWEB_STR("content"));
|
||||
CWEB_String csrf = cweb_get_param_s(req, CWEB_STR("csrf"));
|
||||
|
||||
if (!cweb_streq(cweb_get_csrf(req), csrf)) {
|
||||
cweb_respond_basic(req, 400, CWEB_STR("Invalid request"));
|
||||
return;
|
||||
}
|
||||
if (!valid_post_title(title)) {
|
||||
cweb_respond_basic(req, 400, CWEB_STR("Invalid title"));
|
||||
return;
|
||||
}
|
||||
if (!valid_link(link)) {
|
||||
cweb_respond_basic(req, 400, CWEB_STR("Invalid link"));
|
||||
return;
|
||||
}
|
||||
if (!valid_post_content(content)) {
|
||||
cweb_respond_basic(req, 400, CWEB_STR("Invalid content"));
|
||||
return;
|
||||
}
|
||||
|
||||
bool is_link = false;
|
||||
if (link.len > 0) {
|
||||
is_link = true;
|
||||
content = link;
|
||||
}
|
||||
|
||||
int64_t insert_id = cweb_database_insert(cweb,
|
||||
"INSERT INTO Posts(author, title, is_link, content) VALUES (?, ?, ?, ?)",
|
||||
user_id, title, is_link, content);
|
||||
|
||||
if (insert_id < 0) {
|
||||
cweb_respond_basic(req, 500, CWEB_STR("Internal error"));
|
||||
return;
|
||||
}
|
||||
int post_id = (int) insert_id;
|
||||
|
||||
CWEB_String target = cweb_format(req, "/post?id={}", post_id);
|
||||
cweb_respond_redirect(req, target);
|
||||
}
|
||||
|
||||
static void endpoint_api_comment(CWEB *cweb, CWEB_Request *req)
|
||||
{
|
||||
int user_id = cweb_get_user_id(req);
|
||||
if (user_id == -1) {
|
||||
cweb_respond_basic(req, 400, CWEB_STR("You are not logged in"));
|
||||
return;
|
||||
}
|
||||
|
||||
int parent_post = cweb_get_param_i(req, CWEB_STR("parent_post"));
|
||||
int parent_comment = cweb_get_param_i(req, CWEB_STR("parent_comment"));
|
||||
CWEB_String content = cweb_get_param_s(req, CWEB_STR("content"));
|
||||
CWEB_String csrf2 = cweb_get_param_s(req, CWEB_STR("csrf"));
|
||||
|
||||
if (!cweb_streq(cweb_get_csrf(req), csrf2)) {
|
||||
cweb_respond_basic(req, 400, CWEB_STR("Invalid request"));
|
||||
return;
|
||||
}
|
||||
|
||||
content = cweb_trim(content);
|
||||
if (!valid_comment_content(content)) {
|
||||
cweb_respond_basic(req, 400, CWEB_STR("Invalid content"));
|
||||
return;
|
||||
}
|
||||
|
||||
int64_t insert_id;
|
||||
if (parent_comment == -1) insert_id = cweb_database_insert(cweb, "INSERT INTO Comments(author, content, parent_post) VALUES (?, ?, ?)", user_id, content, parent_post);
|
||||
else insert_id = cweb_database_insert(cweb, "INSERT INTO Comments(author, content, parent_post, parent_comment) VALUES (?, ?, ?, ?)", user_id, content, parent_post, parent_comment);
|
||||
if (insert_id < 0) {
|
||||
cweb_respond_basic(req, 500, CWEB_STR("Internal error"));
|
||||
return;
|
||||
}
|
||||
|
||||
CWEB_String target = cweb_format(req, "/post?id={}", parent_post);
|
||||
cweb_respond_redirect(req, target);
|
||||
}
|
||||
|
||||
static void endpoint_index(CWEB *cweb, CWEB_Request *req)
|
||||
{
|
||||
(void) cweb;
|
||||
|
||||
cweb_respond_template(req, 200, CWEB_STR("demo/pages/index.wl"), -1);
|
||||
}
|
||||
|
||||
static void endpoint_write(CWEB *cweb, CWEB_Request *req)
|
||||
{
|
||||
(void) cweb;
|
||||
|
||||
if (cweb_get_user_id(req) == -1) {
|
||||
cweb_respond_redirect(req, CWEB_STR("/index"));
|
||||
return;
|
||||
}
|
||||
|
||||
cweb_respond_template(req, 200, CWEB_STR("demo/pages/write.wl"), -1);
|
||||
}
|
||||
|
||||
static void endpoint_login(CWEB *cweb, CWEB_Request *req)
|
||||
{
|
||||
(void) cweb;
|
||||
|
||||
if (cweb_get_user_id(req) != -1) {
|
||||
cweb_respond_redirect(req, CWEB_STR("/index"));
|
||||
return;
|
||||
}
|
||||
|
||||
cweb_respond_template(req, 200, CWEB_STR("demo/pages/login.wl"), -1);
|
||||
}
|
||||
|
||||
static void endpoint_signup(CWEB *cweb, CWEB_Request *req)
|
||||
{
|
||||
(void) cweb;
|
||||
|
||||
if (cweb_get_user_id(req) != -1) {
|
||||
cweb_respond_redirect(req, CWEB_STR("/index"));
|
||||
return;
|
||||
}
|
||||
|
||||
cweb_respond_template(req, 200, CWEB_STR("demo/pages/signup.wl"), -1);
|
||||
}
|
||||
|
||||
static void endpoint_post(CWEB *cweb, CWEB_Request *req)
|
||||
{
|
||||
int post_id = cweb_get_param_i(req, CWEB_STR("id"));
|
||||
if (post_id < 0) {
|
||||
cweb_respond_template(req, 404, CWEB_STR("demo/pages/notfound.wl"), -1);
|
||||
return;
|
||||
}
|
||||
|
||||
CWEB_QueryResult res = cweb_database_select(cweb, "SELECT COUNT(*) FROM Posts WHERE id=?", post_id);
|
||||
|
||||
int num;
|
||||
if (cweb_next_query_row(&res, &num) != 1) {
|
||||
cweb_respond_basic(req, 500, CWEB_STR(""));
|
||||
cweb_free_query_result(&res);
|
||||
return;
|
||||
}
|
||||
cweb_free_query_result(&res);
|
||||
|
||||
if (num < 0) {
|
||||
cweb_respond_basic(req, 500, CWEB_STR(""));
|
||||
return;
|
||||
}
|
||||
|
||||
if (num == 0) {
|
||||
cweb_respond_template(req, 404, CWEB_STR("demo/pages/notfound.wl"), -1);
|
||||
return;
|
||||
}
|
||||
|
||||
cweb_respond_template(req, 200, CWEB_STR("demo/pages/post.wl"), post_id);
|
||||
}
|
||||
|
||||
static void endpoint_fallback(CWEB *cweb, CWEB_Request *req)
|
||||
{
|
||||
(void) cweb;
|
||||
|
||||
cweb_respond_template(req, 404, CWEB_STR("demo/pages/notfound.wl"), -1);
|
||||
}
|
||||
|
||||
int main(void)
|
||||
{
|
||||
CWEB_String addr = CWEB_STR("127.0.0.1");
|
||||
uint16_t port = 8080;
|
||||
CWEB_String database_file = CWEB_STR(":memory:");
|
||||
CWEB_String schema_file = CWEB_STR("demo/schema.sql");
|
||||
|
||||
if (cweb_global_init() < 0)
|
||||
return -1;
|
||||
|
||||
CWEB *cweb = cweb_init(addr, port);
|
||||
if (cweb == NULL) {
|
||||
cweb_global_free();
|
||||
return -1;
|
||||
}
|
||||
|
||||
if (cweb_enable_database(cweb, database_file, schema_file) < 0) {
|
||||
cweb_free(cweb);
|
||||
cweb_global_free();
|
||||
return -1;
|
||||
}
|
||||
|
||||
cweb_trace_sql(cweb, true);
|
||||
cweb_enable_template_cache(cweb, false);
|
||||
|
||||
for (;;) {
|
||||
CWEB_Request *req = cweb_wait(cweb);
|
||||
if (0) {}
|
||||
else if (cweb_match_endpoint(req, CWEB_STR("/api/login"))) endpoint_api_login(cweb, req);
|
||||
else if (cweb_match_endpoint(req, CWEB_STR("/api/signup"))) endpoint_api_signup(cweb, req);
|
||||
else if (cweb_match_endpoint(req, CWEB_STR("/api/logout"))) endpoint_api_logout(cweb, req);
|
||||
else if (cweb_match_endpoint(req, CWEB_STR("/api/post"))) endpoint_api_post(cweb, req);
|
||||
else if (cweb_match_endpoint(req, CWEB_STR("/api/comment"))) endpoint_api_comment(cweb, req);
|
||||
else if (cweb_match_endpoint(req, CWEB_STR("/index"))) endpoint_index(cweb, req);
|
||||
else if (cweb_match_endpoint(req, CWEB_STR("/write"))) endpoint_write(cweb, req);
|
||||
else if (cweb_match_endpoint(req, CWEB_STR("/login"))) endpoint_login(cweb, req);
|
||||
else if (cweb_match_endpoint(req, CWEB_STR("/signup"))) endpoint_signup(cweb, req);
|
||||
else if (cweb_match_endpoint(req, CWEB_STR("/post"))) endpoint_post(cweb, req);
|
||||
else endpoint_fallback(cweb, req);
|
||||
}
|
||||
|
||||
cweb_free(cweb);
|
||||
cweb_global_free();
|
||||
return 0;
|
||||
}
|
||||
|
||||
+66
-66
@@ -1,67 +1,67 @@
|
||||
include "page.wl"
|
||||
|
||||
let posts = $query("SELECT P.id, P.title, P.is_link, P.content, (SELECT COUNT(*) FROM Comments as C WHERE c.parent_post=P.id) as num_comments, CURRENT_TIMESTAMP as date FROM Posts as P")
|
||||
|
||||
if posts == none:
|
||||
posts = []
|
||||
|
||||
let style =
|
||||
<style>
|
||||
.item {
|
||||
overflow: auto;
|
||||
border-bottom: 1px solid #E8D4A9;
|
||||
font-size: 14px;
|
||||
}
|
||||
.item:last-child {
|
||||
border-bottom: 0;
|
||||
}
|
||||
.item span {
|
||||
color: #7A5F2A;
|
||||
text-decoration: none;
|
||||
}
|
||||
.item div {
|
||||
color: #E8D4A9;
|
||||
float: left
|
||||
}
|
||||
.item div:first-child {
|
||||
width: calc(100% - 250px);
|
||||
}
|
||||
.item div:last-child {
|
||||
width: 250px;
|
||||
text-align: right;
|
||||
}
|
||||
|
||||
#no-posts {
|
||||
margin: 60px auto;
|
||||
width: 100%;
|
||||
text-align: center;
|
||||
color: #7A5F2A;
|
||||
}
|
||||
</style>
|
||||
|
||||
let main =
|
||||
<main>
|
||||
|
||||
\if len(posts) == 0:
|
||||
<div id="no-posts">There are no posts yet!</div>
|
||||
|
||||
\for post in posts: {
|
||||
|
||||
let link
|
||||
if post.is_link != 0:
|
||||
link = post.content
|
||||
else
|
||||
link = ["/post?id=", post.id]
|
||||
|
||||
<div class="item">
|
||||
<div>
|
||||
<a href=\'"'\link\'"'>\escape(post.title)</a>
|
||||
</div>
|
||||
<div>
|
||||
<span>\escape(post.date)</span> | <span>\escape(post.num_comments) comments</span>
|
||||
</div>
|
||||
</div>
|
||||
}
|
||||
</main>
|
||||
|
||||
include "page.wl"
|
||||
|
||||
let posts = $query("SELECT P.id, P.title, P.is_link, P.content, (SELECT COUNT(*) FROM Comments as C WHERE c.parent_post=P.id) as num_comments, CURRENT_TIMESTAMP as date FROM Posts as P")
|
||||
|
||||
if posts == none:
|
||||
posts = []
|
||||
|
||||
let style =
|
||||
<style>
|
||||
.item {
|
||||
overflow: auto;
|
||||
border-bottom: 1px solid #E8D4A9;
|
||||
font-size: 14px;
|
||||
}
|
||||
.item:last-child {
|
||||
border-bottom: 0;
|
||||
}
|
||||
.item span {
|
||||
color: #7A5F2A;
|
||||
text-decoration: none;
|
||||
}
|
||||
.item div {
|
||||
color: #E8D4A9;
|
||||
float: left
|
||||
}
|
||||
.item div:first-child {
|
||||
width: calc(100% - 250px);
|
||||
}
|
||||
.item div:last-child {
|
||||
width: 250px;
|
||||
text-align: right;
|
||||
}
|
||||
|
||||
#no-posts {
|
||||
margin: 60px auto;
|
||||
width: 100%;
|
||||
text-align: center;
|
||||
color: #7A5F2A;
|
||||
}
|
||||
</style>
|
||||
|
||||
let main =
|
||||
<main>
|
||||
|
||||
\if len(posts) == 0:
|
||||
<div id="no-posts">There are no posts yet!</div>
|
||||
|
||||
\for post in posts: {
|
||||
|
||||
let link
|
||||
if post.is_link != 0:
|
||||
link = post.content
|
||||
else
|
||||
link = ["/post?id=", post.id]
|
||||
|
||||
<div class="item">
|
||||
<div>
|
||||
<a href=\'"'\link\'"'>\escape(post.title)</a>
|
||||
</div>
|
||||
<div>
|
||||
<span>\escape(post.date)</span> | <span>\escape(post.num_comments) comments</span>
|
||||
</div>
|
||||
</div>
|
||||
}
|
||||
</main>
|
||||
|
||||
page("Index", $login_user_id, style, main)
|
||||
+22
-22
@@ -1,22 +1,22 @@
|
||||
include "page.wl"
|
||||
include "login_and_signup_style.wl"
|
||||
|
||||
let main =
|
||||
<main>
|
||||
<span>Welcome back!</span>
|
||||
|
||||
<div id="response"></div>
|
||||
|
||||
<form action="/api/login" method="POST">
|
||||
<input type="text" name="username" placeholder="username" />
|
||||
<input type="password" name="password" placeholder="password" />
|
||||
<input type="submit" value="Log-In" />
|
||||
<div class="form-links">
|
||||
<a href="">forgot password?</a>
|
||||
|
|
||||
<a href="">create account</a>
|
||||
</div>
|
||||
</form>
|
||||
</main>
|
||||
|
||||
page("Log-In", none, style, main)
|
||||
include "page.wl"
|
||||
include "login_and_signup_style.wl"
|
||||
|
||||
let main =
|
||||
<main>
|
||||
<span>Welcome back!</span>
|
||||
|
||||
<div id="response"></div>
|
||||
|
||||
<form action="/api/login" method="POST">
|
||||
<input type="text" name="username" placeholder="username" />
|
||||
<input type="password" name="password" placeholder="password" />
|
||||
<input type="submit" value="Log-In" />
|
||||
<div class="form-links">
|
||||
<a href="">forgot password?</a>
|
||||
|
|
||||
<a href="">create account</a>
|
||||
</div>
|
||||
</form>
|
||||
</main>
|
||||
|
||||
page("Log-In", none, style, main)
|
||||
|
||||
@@ -1,74 +1,74 @@
|
||||
let style =
|
||||
<style>
|
||||
|
||||
span {
|
||||
text-align: center;
|
||||
color: #1D2B42;
|
||||
font-size: 18px;
|
||||
display: inline-block;
|
||||
width: 100%;
|
||||
margin-top: 30px;
|
||||
}
|
||||
|
||||
form {
|
||||
max-width: 300px;
|
||||
margin: 30px auto;
|
||||
}
|
||||
|
||||
form input {
|
||||
border: 0;
|
||||
outline: 0;
|
||||
width: 100%;
|
||||
border-radius: 3px;
|
||||
padding: 8px;
|
||||
margin-bottom: 15px;
|
||||
}
|
||||
|
||||
form input[type=text],
|
||||
form input[type=email],
|
||||
form input[type=password] {
|
||||
background: #E8D4A9;
|
||||
border: 1px solid #D4C298;
|
||||
}
|
||||
|
||||
form input[type=text]:focus,
|
||||
form input[type=email]:focus,
|
||||
form input[type=password]:focus {
|
||||
border-color: #5780C9;
|
||||
background: #fff;
|
||||
}
|
||||
|
||||
form input[type=submit] {
|
||||
cursor: pointer;
|
||||
background: #5780C9;
|
||||
color: #F7E6C0;
|
||||
}
|
||||
|
||||
form input[type=submit]:hover {
|
||||
background: #1D2B42;
|
||||
}
|
||||
|
||||
.form-links {
|
||||
text-align: center;
|
||||
font-size: 12px;
|
||||
}
|
||||
.form-links a {
|
||||
color: #7A5F2A;
|
||||
margin: 0 10px;
|
||||
}
|
||||
.form-links a:hover {
|
||||
color: #1D2B42;
|
||||
}
|
||||
|
||||
#response {
|
||||
max-width: 300px;
|
||||
margin: auto;
|
||||
}
|
||||
#response .error {
|
||||
margin-top: 30px;
|
||||
border-radius: 3px;
|
||||
border: 1px solid #E44C82;
|
||||
background: #F295B5;
|
||||
padding: 5px 10px;
|
||||
}
|
||||
let style =
|
||||
<style>
|
||||
|
||||
span {
|
||||
text-align: center;
|
||||
color: #1D2B42;
|
||||
font-size: 18px;
|
||||
display: inline-block;
|
||||
width: 100%;
|
||||
margin-top: 30px;
|
||||
}
|
||||
|
||||
form {
|
||||
max-width: 300px;
|
||||
margin: 30px auto;
|
||||
}
|
||||
|
||||
form input {
|
||||
border: 0;
|
||||
outline: 0;
|
||||
width: 100%;
|
||||
border-radius: 3px;
|
||||
padding: 8px;
|
||||
margin-bottom: 15px;
|
||||
}
|
||||
|
||||
form input[type=text],
|
||||
form input[type=email],
|
||||
form input[type=password] {
|
||||
background: #E8D4A9;
|
||||
border: 1px solid #D4C298;
|
||||
}
|
||||
|
||||
form input[type=text]:focus,
|
||||
form input[type=email]:focus,
|
||||
form input[type=password]:focus {
|
||||
border-color: #5780C9;
|
||||
background: #fff;
|
||||
}
|
||||
|
||||
form input[type=submit] {
|
||||
cursor: pointer;
|
||||
background: #5780C9;
|
||||
color: #F7E6C0;
|
||||
}
|
||||
|
||||
form input[type=submit]:hover {
|
||||
background: #1D2B42;
|
||||
}
|
||||
|
||||
.form-links {
|
||||
text-align: center;
|
||||
font-size: 12px;
|
||||
}
|
||||
.form-links a {
|
||||
color: #7A5F2A;
|
||||
margin: 0 10px;
|
||||
}
|
||||
.form-links a:hover {
|
||||
color: #1D2B42;
|
||||
}
|
||||
|
||||
#response {
|
||||
max-width: 300px;
|
||||
margin: auto;
|
||||
}
|
||||
#response .error {
|
||||
margin-top: 30px;
|
||||
border-radius: 3px;
|
||||
border: 1px solid #E44C82;
|
||||
background: #F295B5;
|
||||
padding: 5px 10px;
|
||||
}
|
||||
</style>
|
||||
+60
-60
@@ -1,60 +1,60 @@
|
||||
include "page.wl"
|
||||
|
||||
let style =
|
||||
<style>
|
||||
.not-found-container {
|
||||
text-align: center;
|
||||
padding: 60px 20px;
|
||||
}
|
||||
.error-code {
|
||||
font-size: 72px;
|
||||
font-weight: bold;
|
||||
color: #7A5F2A;
|
||||
margin-bottom: 20px;
|
||||
line-height: 100%;
|
||||
}
|
||||
.error-message {
|
||||
font-size: 24px;
|
||||
color: #1D2B42;
|
||||
margin-bottom: 15px;
|
||||
}
|
||||
.error-description {
|
||||
font-size: 14px;
|
||||
color: #7A5F2A;
|
||||
margin-bottom: 40px;
|
||||
max-width: 500px;
|
||||
margin-left: auto;
|
||||
margin-right: auto;
|
||||
line-height: 160%;
|
||||
}
|
||||
|
||||
.home-button {
|
||||
display: inline-block;
|
||||
background: #5780C9;
|
||||
color: #F7E6C0;
|
||||
padding: 12px 24px;
|
||||
text-decoration: none;
|
||||
border-radius: 5px;
|
||||
font-size: 14px;
|
||||
margin-top: 20px;
|
||||
border: none;
|
||||
cursor: pointer;
|
||||
}
|
||||
.home-button:hover {
|
||||
background: #1D2B42;
|
||||
color: #F7E6C0;
|
||||
}
|
||||
</style>
|
||||
|
||||
let main =
|
||||
<main>
|
||||
<div class="not-found-container">
|
||||
<div class="error-code">404</div>
|
||||
<div class="error-message">Page Not Found</div>
|
||||
<div class="error-description">
|
||||
Looks like this page wandered off somewhere.
|
||||
</div>
|
||||
</div>
|
||||
</main>
|
||||
|
||||
page("Not Found", $login_user_id, style, main)
|
||||
include "page.wl"
|
||||
|
||||
let style =
|
||||
<style>
|
||||
.not-found-container {
|
||||
text-align: center;
|
||||
padding: 60px 20px;
|
||||
}
|
||||
.error-code {
|
||||
font-size: 72px;
|
||||
font-weight: bold;
|
||||
color: #7A5F2A;
|
||||
margin-bottom: 20px;
|
||||
line-height: 100%;
|
||||
}
|
||||
.error-message {
|
||||
font-size: 24px;
|
||||
color: #1D2B42;
|
||||
margin-bottom: 15px;
|
||||
}
|
||||
.error-description {
|
||||
font-size: 14px;
|
||||
color: #7A5F2A;
|
||||
margin-bottom: 40px;
|
||||
max-width: 500px;
|
||||
margin-left: auto;
|
||||
margin-right: auto;
|
||||
line-height: 160%;
|
||||
}
|
||||
|
||||
.home-button {
|
||||
display: inline-block;
|
||||
background: #5780C9;
|
||||
color: #F7E6C0;
|
||||
padding: 12px 24px;
|
||||
text-decoration: none;
|
||||
border-radius: 5px;
|
||||
font-size: 14px;
|
||||
margin-top: 20px;
|
||||
border: none;
|
||||
cursor: pointer;
|
||||
}
|
||||
.home-button:hover {
|
||||
background: #1D2B42;
|
||||
color: #F7E6C0;
|
||||
}
|
||||
</style>
|
||||
|
||||
let main =
|
||||
<main>
|
||||
<div class="not-found-container">
|
||||
<div class="error-code">404</div>
|
||||
<div class="error-message">Page Not Found</div>
|
||||
<div class="error-description">
|
||||
Looks like this page wandered off somewhere.
|
||||
</div>
|
||||
</div>
|
||||
</main>
|
||||
|
||||
page("Not Found", $login_user_id, style, main)
|
||||
|
||||
+75
-75
@@ -1,75 +1,75 @@
|
||||
|
||||
procedure page(title, login_user_id, style, main)
|
||||
<html>
|
||||
<head>
|
||||
<meta charset="UTF-8" />
|
||||
<title>CN - \escape(title)</title>
|
||||
<style>
|
||||
body {
|
||||
line-height: 200%;
|
||||
font-family: monospace;
|
||||
max-width: 800px;
|
||||
margin: 20px auto;
|
||||
}
|
||||
nav {
|
||||
overflow: auto;
|
||||
background: #5780C9;
|
||||
color: #6E93D4;
|
||||
padding: 5px 10px;
|
||||
border-top-left-radius: 3px;
|
||||
border-top-right-radius: 3px;
|
||||
}
|
||||
nav div {
|
||||
float: left
|
||||
}
|
||||
nav div:last-child {
|
||||
float: right
|
||||
}
|
||||
nav a {
|
||||
font-size: 14px;
|
||||
color: #1D2B42;
|
||||
}
|
||||
nav a.current {
|
||||
font-weight: bold;
|
||||
}
|
||||
main {
|
||||
padding: 5px 10px;
|
||||
background: #F7E6C0;
|
||||
border-bottom-left-radius: 3px;
|
||||
border-bottom-right-radius: 3px;
|
||||
}
|
||||
footer {
|
||||
padding: 5px 10px;
|
||||
}
|
||||
</style>
|
||||
\(style)
|
||||
<script src="https://cdnjs.cloudflare.com/ajax/libs/htmx/1.9.2/htmx.min.js"></script>
|
||||
</head>
|
||||
<body>
|
||||
<nav>
|
||||
<div>
|
||||
<a href="/index">index</a>
|
||||
\if login_user_id != none: {
|
||||
"|\n"
|
||||
<a href="/write">write</a>
|
||||
}
|
||||
</div>
|
||||
\if login_user_id == none:
|
||||
<div>
|
||||
<a href="/login">log-in</a>
|
||||
|
|
||||
<a href="/signup">sign-up</a>
|
||||
</div>
|
||||
else
|
||||
<div>
|
||||
<a href="">settings</a>
|
||||
|
|
||||
<a href="/api/logout">log-out</a>
|
||||
</div>
|
||||
</nav>
|
||||
\main
|
||||
<footer>
|
||||
Made with love by cozis
|
||||
</footer>
|
||||
</body>
|
||||
</html>
|
||||
|
||||
procedure page(title, login_user_id, style, main)
|
||||
<html>
|
||||
<head>
|
||||
<meta charset="UTF-8" />
|
||||
<title>CN - \escape(title)</title>
|
||||
<style>
|
||||
body {
|
||||
line-height: 200%;
|
||||
font-family: monospace;
|
||||
max-width: 800px;
|
||||
margin: 20px auto;
|
||||
}
|
||||
nav {
|
||||
overflow: auto;
|
||||
background: #5780C9;
|
||||
color: #6E93D4;
|
||||
padding: 5px 10px;
|
||||
border-top-left-radius: 3px;
|
||||
border-top-right-radius: 3px;
|
||||
}
|
||||
nav div {
|
||||
float: left
|
||||
}
|
||||
nav div:last-child {
|
||||
float: right
|
||||
}
|
||||
nav a {
|
||||
font-size: 14px;
|
||||
color: #1D2B42;
|
||||
}
|
||||
nav a.current {
|
||||
font-weight: bold;
|
||||
}
|
||||
main {
|
||||
padding: 5px 10px;
|
||||
background: #F7E6C0;
|
||||
border-bottom-left-radius: 3px;
|
||||
border-bottom-right-radius: 3px;
|
||||
}
|
||||
footer {
|
||||
padding: 5px 10px;
|
||||
}
|
||||
</style>
|
||||
\(style)
|
||||
<script src="https://cdnjs.cloudflare.com/ajax/libs/htmx/1.9.2/htmx.min.js"></script>
|
||||
</head>
|
||||
<body>
|
||||
<nav>
|
||||
<div>
|
||||
<a href="/index">index</a>
|
||||
\if login_user_id != none: {
|
||||
"|\n"
|
||||
<a href="/write">write</a>
|
||||
}
|
||||
</div>
|
||||
\if login_user_id == none:
|
||||
<div>
|
||||
<a href="/login">log-in</a>
|
||||
|
|
||||
<a href="/signup">sign-up</a>
|
||||
</div>
|
||||
else
|
||||
<div>
|
||||
<a href="">settings</a>
|
||||
|
|
||||
<a href="/api/logout">log-out</a>
|
||||
</div>
|
||||
</nav>
|
||||
\main
|
||||
<footer>
|
||||
Made with love by cozis
|
||||
</footer>
|
||||
</body>
|
||||
</html>
|
||||
|
||||
+248
-245
@@ -1,246 +1,249 @@
|
||||
include "page.wl"
|
||||
|
||||
let posts = $query("SELECT U.username, P.title, P.content FROM Posts as P, Users as U WHERE P.id=? AND U.id=P.author", $resource_id)
|
||||
let comments = $query("SELECT C.id, U.username, C.content, C.parent_post, C.parent_comment FROM Comments as C, Users as U WHERE C.parent_post=? AND U.id=C.author", $resource_id)
|
||||
|
||||
let lookup = {}
|
||||
|
||||
for comment in comments: {
|
||||
comment.child = []
|
||||
lookup[comment.id] = comment
|
||||
}
|
||||
|
||||
let root_comments = []
|
||||
|
||||
for comment in comments: {
|
||||
if comment.parent_comment == none:
|
||||
root_comments << comment
|
||||
else
|
||||
lookup[comment.parent_comment].child << comment
|
||||
}
|
||||
|
||||
let post = posts[0]
|
||||
|
||||
let style =
|
||||
<style>
|
||||
.thread-header {
|
||||
padding: 15px 0;
|
||||
border-bottom: 2px solid #E8D4A9;
|
||||
margin-bottom: 20px;
|
||||
}
|
||||
.thread-title {
|
||||
font-size: 16px;
|
||||
font-weight: bold;
|
||||
margin-bottom: 8px;
|
||||
}
|
||||
.thread-title a {
|
||||
color: #1D2B42;
|
||||
text-decoration: none;
|
||||
}
|
||||
.thread-title a:hover {
|
||||
text-decoration: underline;
|
||||
}
|
||||
.thread-meta {
|
||||
font-size: 12px;
|
||||
color: #7A5F2A;
|
||||
margin-bottom: 10px;
|
||||
}
|
||||
.thread-meta a {
|
||||
color: #7A5F2A;
|
||||
}
|
||||
.thread-text {
|
||||
font-size: 14px;
|
||||
color: #1D2B42;
|
||||
line-height: 160%;
|
||||
margin-bottom: 10px;
|
||||
}
|
||||
.thread-actions {
|
||||
font-size: 12px;
|
||||
}
|
||||
.thread-actions a {
|
||||
color: #7A5F2A;
|
||||
margin-right: 10px;
|
||||
}
|
||||
|
||||
/* Comment styles */
|
||||
.comment {
|
||||
margin-bottom: 15px;
|
||||
border-left: 1px solid #E8D4A9;
|
||||
padding-left: 10px;
|
||||
}
|
||||
|
||||
.comment-meta {
|
||||
font-size: 11px;
|
||||
color: #7A5F2A;
|
||||
margin-bottom: 5px;
|
||||
}
|
||||
.comment-meta a {
|
||||
color: #7A5F2A;
|
||||
}
|
||||
.comment-text {
|
||||
font-size: 13px;
|
||||
color: #1D2B42;
|
||||
line-height: 150%;
|
||||
margin-bottom: 5px;
|
||||
}
|
||||
.comment-actions {
|
||||
font-size: 11px;
|
||||
}
|
||||
.comment-actions a {
|
||||
color: #7A5F2A;
|
||||
margin-right: 8px;
|
||||
}
|
||||
.comment-actions a:hover {
|
||||
color: #1D2B42;
|
||||
}
|
||||
|
||||
.vote-buttons {
|
||||
float: left;
|
||||
width: 15px;
|
||||
margin-right: 8px;
|
||||
font-size: 10px;
|
||||
text-align: center;
|
||||
}
|
||||
.vote-buttons a {
|
||||
display: block;
|
||||
color: #7A5F2A;
|
||||
text-decoration: none;
|
||||
line-height: 100%;
|
||||
}
|
||||
.vote-buttons a:hover {
|
||||
color: #1D2B42;
|
||||
}
|
||||
.comment-content {
|
||||
margin-left: 23px;
|
||||
}
|
||||
.comment-child {
|
||||
margin-top: 10px;
|
||||
margin-left: 10px;
|
||||
}
|
||||
.add-comment {
|
||||
}
|
||||
.add-comment form {
|
||||
margin: 0;
|
||||
overflow: auto;
|
||||
}
|
||||
.add-comment form textarea {
|
||||
width: 100%;
|
||||
height: 80px;
|
||||
font-family: monospace;
|
||||
font-size: 12px;
|
||||
background: white;
|
||||
border: 1px solid #D4C298;
|
||||
border-radius: 3px;
|
||||
padding: 8px;
|
||||
box-sizing: border-box;
|
||||
resize: vertical;
|
||||
}
|
||||
.add-comment form input[type=submit] {
|
||||
background: #5780C9;
|
||||
color: white;
|
||||
border: none;
|
||||
border-radius: 3px;
|
||||
padding: 6px 12px;
|
||||
font-family: monospace;
|
||||
font-size: 12px;
|
||||
cursor: pointer;
|
||||
margin-top: 8px;
|
||||
float: right;
|
||||
}
|
||||
.add-comment input[type=submit]:hover {
|
||||
background: #1D2B42;
|
||||
}
|
||||
summary {
|
||||
list-style: none;
|
||||
text-decoration: underline;
|
||||
color: #7A5F2A;
|
||||
cursor: pointer;
|
||||
}
|
||||
::-webkit-details-marker {
|
||||
display: none;
|
||||
}
|
||||
|
||||
#no-comments {
|
||||
margin: 30px 0;
|
||||
width: 100%;
|
||||
text-align: center;
|
||||
color: #7A5F2A;
|
||||
}
|
||||
pre {
|
||||
white-space: pre-wrap; /* Since CSS 2.1 */
|
||||
white-space: -moz-pre-wrap; /* Mozilla, since 1999 */
|
||||
white-space: -pre-wrap; /* Opera 4-6 */
|
||||
white-space: -o-pre-wrap; /* Opera 7 */
|
||||
word-wrap: break-word; /* Internet Explorer 5.5+ */
|
||||
}
|
||||
</style>
|
||||
|
||||
let main =
|
||||
<main>
|
||||
<div class="thread-header">
|
||||
<div class="thread-title">
|
||||
<span>\escape(post.title)</span>
|
||||
</div>
|
||||
<div class="thread-meta">
|
||||
submitted 3 hours ago by <a href="">\escape(post.username)</a> | <a href="">\len(comments)</a>
|
||||
</div>
|
||||
<div class="thread-text">
|
||||
<pre>\escape(post.content)</pre>
|
||||
</div>
|
||||
<details>
|
||||
<summary>
|
||||
reply
|
||||
</summary>
|
||||
<div class="add-comment">
|
||||
<form action="/api/comment" method="POST">
|
||||
<input type="hidden" name="parent_post" value=\'"'\$resource_id\'"' />
|
||||
<textarea name="content" placeholder="Add a comment..."></textarea>
|
||||
<input type="submit" vaue="Publish" />
|
||||
</form>
|
||||
</div>
|
||||
</details>
|
||||
</div>
|
||||
|
||||
\procedure render_comment(comment)
|
||||
<div class="comment">
|
||||
<div class="vote-buttons">
|
||||
<a href="">▲</a>
|
||||
<a href="">▼</a>
|
||||
</div>
|
||||
<div class="comment-content">
|
||||
<div class="comment-meta">
|
||||
<a href="">\escape(comment.username)</a> 2 hours ago
|
||||
</div>
|
||||
<div class="comment-text">
|
||||
<pre>\escape(comment.content)</pre>
|
||||
</div>
|
||||
\if $login_user_id != none:
|
||||
<details>
|
||||
<summary>
|
||||
reply
|
||||
</summary>
|
||||
<div class="add-comment">
|
||||
<form action="/api/comment" method="POST">
|
||||
<input type="hidden" name="parent_post" value=\'"'\$resource_id\'"' />
|
||||
<input type="hidden" name="parent_comment" value=\'"'\comment.id\'"' />
|
||||
<textarea name="content" placeholder="Add a comment..."></textarea>
|
||||
<input type="submit" vaue="Publish" />
|
||||
</form>
|
||||
</div>
|
||||
</details>
|
||||
</div>
|
||||
<div class="comment-child">
|
||||
\for child in comment.child:
|
||||
render_comment(child)
|
||||
</div>
|
||||
</div>
|
||||
|
||||
\if len(root_comments) == 0:
|
||||
<div id="no-comments">
|
||||
No comments
|
||||
</div>
|
||||
else for comment in root_comments:
|
||||
render_comment(comment)
|
||||
</main>
|
||||
|
||||
include "page.wl"
|
||||
|
||||
let post_id = $args(0)
|
||||
let posts = $query("SELECT U.username, P.title, P.content FROM Posts as P, Users as U WHERE P.id=? AND U.id=P.author", post_id)
|
||||
let comments = $query("SELECT C.id, U.username, C.content, C.parent_post, C.parent_comment FROM Comments as C, Users as U WHERE C.parent_post=? AND U.id=C.author", post_id)
|
||||
|
||||
let lookup = {}
|
||||
|
||||
for comment in comments: {
|
||||
comment.child = []
|
||||
lookup[comment.id] = comment
|
||||
}
|
||||
|
||||
let root_comments = []
|
||||
|
||||
for comment in comments: {
|
||||
if comment.parent_comment == none:
|
||||
root_comments << comment
|
||||
else
|
||||
lookup[comment.parent_comment].child << comment
|
||||
}
|
||||
|
||||
let post = posts[0]
|
||||
|
||||
let style =
|
||||
<style>
|
||||
.thread-header {
|
||||
padding: 15px 0;
|
||||
border-bottom: 2px solid #E8D4A9;
|
||||
margin-bottom: 20px;
|
||||
}
|
||||
.thread-title {
|
||||
font-size: 16px;
|
||||
font-weight: bold;
|
||||
margin-bottom: 8px;
|
||||
}
|
||||
.thread-title a {
|
||||
color: #1D2B42;
|
||||
text-decoration: none;
|
||||
}
|
||||
.thread-title a:hover {
|
||||
text-decoration: underline;
|
||||
}
|
||||
.thread-meta {
|
||||
font-size: 12px;
|
||||
color: #7A5F2A;
|
||||
margin-bottom: 10px;
|
||||
}
|
||||
.thread-meta a {
|
||||
color: #7A5F2A;
|
||||
}
|
||||
.thread-text {
|
||||
font-size: 14px;
|
||||
color: #1D2B42;
|
||||
line-height: 160%;
|
||||
margin-bottom: 10px;
|
||||
}
|
||||
.thread-actions {
|
||||
font-size: 12px;
|
||||
}
|
||||
.thread-actions a {
|
||||
color: #7A5F2A;
|
||||
margin-right: 10px;
|
||||
}
|
||||
|
||||
/* Comment styles */
|
||||
.comment {
|
||||
margin-bottom: 15px;
|
||||
border-left: 1px solid #E8D4A9;
|
||||
padding-left: 10px;
|
||||
}
|
||||
|
||||
.comment-meta {
|
||||
font-size: 11px;
|
||||
color: #7A5F2A;
|
||||
margin-bottom: 5px;
|
||||
}
|
||||
.comment-meta a {
|
||||
color: #7A5F2A;
|
||||
}
|
||||
.comment-text {
|
||||
font-size: 13px;
|
||||
color: #1D2B42;
|
||||
line-height: 150%;
|
||||
margin-bottom: 5px;
|
||||
}
|
||||
.comment-actions {
|
||||
font-size: 11px;
|
||||
}
|
||||
.comment-actions a {
|
||||
color: #7A5F2A;
|
||||
margin-right: 8px;
|
||||
}
|
||||
.comment-actions a:hover {
|
||||
color: #1D2B42;
|
||||
}
|
||||
|
||||
.vote-buttons {
|
||||
float: left;
|
||||
width: 15px;
|
||||
margin-right: 8px;
|
||||
font-size: 10px;
|
||||
text-align: center;
|
||||
}
|
||||
.vote-buttons a {
|
||||
display: block;
|
||||
color: #7A5F2A;
|
||||
text-decoration: none;
|
||||
line-height: 100%;
|
||||
}
|
||||
.vote-buttons a:hover {
|
||||
color: #1D2B42;
|
||||
}
|
||||
.comment-content {
|
||||
margin-left: 23px;
|
||||
}
|
||||
.comment-child {
|
||||
margin-top: 10px;
|
||||
margin-left: 10px;
|
||||
}
|
||||
.add-comment {
|
||||
}
|
||||
.add-comment form {
|
||||
margin: 0;
|
||||
overflow: auto;
|
||||
}
|
||||
.add-comment form textarea {
|
||||
width: 100%;
|
||||
height: 80px;
|
||||
font-family: monospace;
|
||||
font-size: 12px;
|
||||
background: white;
|
||||
border: 1px solid #D4C298;
|
||||
border-radius: 3px;
|
||||
padding: 8px;
|
||||
box-sizing: border-box;
|
||||
resize: vertical;
|
||||
}
|
||||
.add-comment form input[type=submit] {
|
||||
background: #5780C9;
|
||||
color: white;
|
||||
border: none;
|
||||
border-radius: 3px;
|
||||
padding: 6px 12px;
|
||||
font-family: monospace;
|
||||
font-size: 12px;
|
||||
cursor: pointer;
|
||||
margin-top: 8px;
|
||||
float: right;
|
||||
}
|
||||
.add-comment input[type=submit]:hover {
|
||||
background: #1D2B42;
|
||||
}
|
||||
summary {
|
||||
list-style: none;
|
||||
text-decoration: underline;
|
||||
color: #7A5F2A;
|
||||
cursor: pointer;
|
||||
}
|
||||
::-webkit-details-marker {
|
||||
display: none;
|
||||
}
|
||||
|
||||
#no-comments {
|
||||
margin: 30px 0;
|
||||
width: 100%;
|
||||
text-align: center;
|
||||
color: #7A5F2A;
|
||||
}
|
||||
pre {
|
||||
white-space: pre-wrap; /* Since CSS 2.1 */
|
||||
white-space: -moz-pre-wrap; /* Mozilla, since 1999 */
|
||||
white-space: -pre-wrap; /* Opera 4-6 */
|
||||
white-space: -o-pre-wrap; /* Opera 7 */
|
||||
word-wrap: break-word; /* Internet Explorer 5.5+ */
|
||||
}
|
||||
</style>
|
||||
|
||||
let main =
|
||||
<main>
|
||||
<div class="thread-header">
|
||||
<div class="thread-title">
|
||||
<span>\escape(post.title)</span>
|
||||
</div>
|
||||
<div class="thread-meta">
|
||||
submitted 3 hours ago by <a href="">\escape(post.username)</a> | <a href="">\len(comments)</a>
|
||||
</div>
|
||||
<div class="thread-text">
|
||||
<pre>\escape(post.content)</pre>
|
||||
</div>
|
||||
<details>
|
||||
<summary>
|
||||
reply
|
||||
</summary>
|
||||
<div class="add-comment">
|
||||
<form action="/api/comment" method="POST">
|
||||
<input type="hidden" name="csrf" value=\'"'\$csrf\'"' />
|
||||
<input type="hidden" name="parent_post" value=\'"'\post_id\'"' />
|
||||
<textarea name="content" placeholder="Add a comment..."></textarea>
|
||||
<input type="submit" vaue="Publish" />
|
||||
</form>
|
||||
</div>
|
||||
</details>
|
||||
</div>
|
||||
|
||||
\procedure render_comment(post_id, comment)
|
||||
<div class="comment">
|
||||
<div class="vote-buttons">
|
||||
<a href="">▲</a>
|
||||
<a href="">▼</a>
|
||||
</div>
|
||||
<div class="comment-content">
|
||||
<div class="comment-meta">
|
||||
<a href="">\escape(comment.username)</a> 2 hours ago
|
||||
</div>
|
||||
<div class="comment-text">
|
||||
<pre>\escape(comment.content)</pre>
|
||||
</div>
|
||||
\if $login_user_id != none:
|
||||
<details>
|
||||
<summary>
|
||||
reply
|
||||
</summary>
|
||||
<div class="add-comment">
|
||||
<form action="/api/comment" method="POST">
|
||||
<input type="hidden" name="csrf" value=\'"'\$csrf\'"' />
|
||||
<input type="hidden" name="parent_post" value=\'"'\post_id\'"' />
|
||||
<input type="hidden" name="parent_comment" value=\'"'\comment.id\'"' />
|
||||
<textarea name="content" placeholder="Add a comment..."></textarea>
|
||||
<input type="submit" vaue="Publish" />
|
||||
</form>
|
||||
</div>
|
||||
</details>
|
||||
</div>
|
||||
<div class="comment-child">
|
||||
\for child in comment.child:
|
||||
render_comment(post_id, child)
|
||||
</div>
|
||||
</div>
|
||||
|
||||
\if len(root_comments) == 0:
|
||||
<div id="no-comments">
|
||||
No comments
|
||||
</div>
|
||||
else for comment in root_comments:
|
||||
render_comment(post_id, comment)
|
||||
</main>
|
||||
|
||||
page(post.title, $login_user_id, style, main)
|
||||
+72
-72
@@ -1,73 +1,73 @@
|
||||
include "page.wl"
|
||||
|
||||
let posts = $query("SELECT title, content FROM Posts WHERE id=?", $post_id)
|
||||
let comments = $query("SELECT C.id, U.username, C.content, C.parent_post, C.parent_comment FROM Comments as C, Users as U WHERE C.parent_post=? AND U.id == C.author", $post_id)
|
||||
|
||||
let lookup = {}
|
||||
|
||||
for comment in comments: {
|
||||
comment.child = []
|
||||
lookup[comment.id] = comment
|
||||
}
|
||||
|
||||
let roots = []
|
||||
|
||||
for comment in comments: {
|
||||
if comment.parent_comment == none:
|
||||
roots << comment
|
||||
else
|
||||
lookup[comment.parent_comment].child << comment
|
||||
}
|
||||
|
||||
comments = roots
|
||||
|
||||
let post = posts[0]
|
||||
|
||||
let style =
|
||||
<style>
|
||||
.child {
|
||||
border-left: 3px solid #ccc;
|
||||
padding-left: 10px;
|
||||
}
|
||||
form textarea {
|
||||
width: 100%;
|
||||
}
|
||||
</style>
|
||||
|
||||
procedure render_comment(parent)
|
||||
<div>
|
||||
<a href="">\parent.username</a>
|
||||
<p>
|
||||
\parent.content
|
||||
</p>
|
||||
<div class="child">
|
||||
\if $login_user_id != none:
|
||||
<form action="/api/comment" method="POST">
|
||||
<input type="hidden" name="parent_post" value=\'"'\$post_id\'"' />
|
||||
<input type="hidden" name="parent_comment" value=\'"'\parent.id\'"' />
|
||||
<textarea name="content"></textarea>
|
||||
<input type="submit" vaue="Publish" />
|
||||
</form>
|
||||
\for child in parent.child:
|
||||
render_comment(child)
|
||||
</div>
|
||||
</div>
|
||||
|
||||
let main =
|
||||
<main>
|
||||
<h3>\post.title</h3>
|
||||
<p>\post.content</p>
|
||||
<div>
|
||||
<form action="/api/comment" method="POST">
|
||||
<input type="hidden" name="parent_post" value=\'"'\$post_id\'"' />
|
||||
<textarea name="content"></textarea>
|
||||
<input type="submit" vaue="Publish" />
|
||||
</form>
|
||||
</div>
|
||||
\if len comments == 0:
|
||||
<span>No comments yet!</span>
|
||||
else for comment in comments:
|
||||
render_comment(comment)
|
||||
</main>
|
||||
|
||||
include "page.wl"
|
||||
|
||||
let posts = $query("SELECT title, content FROM Posts WHERE id=?", $post_id)
|
||||
let comments = $query("SELECT C.id, U.username, C.content, C.parent_post, C.parent_comment FROM Comments as C, Users as U WHERE C.parent_post=? AND U.id == C.author", $post_id)
|
||||
|
||||
let lookup = {}
|
||||
|
||||
for comment in comments: {
|
||||
comment.child = []
|
||||
lookup[comment.id] = comment
|
||||
}
|
||||
|
||||
let roots = []
|
||||
|
||||
for comment in comments: {
|
||||
if comment.parent_comment == none:
|
||||
roots << comment
|
||||
else
|
||||
lookup[comment.parent_comment].child << comment
|
||||
}
|
||||
|
||||
comments = roots
|
||||
|
||||
let post = posts[0]
|
||||
|
||||
let style =
|
||||
<style>
|
||||
.child {
|
||||
border-left: 3px solid #ccc;
|
||||
padding-left: 10px;
|
||||
}
|
||||
form textarea {
|
||||
width: 100%;
|
||||
}
|
||||
</style>
|
||||
|
||||
procedure render_comment(parent)
|
||||
<div>
|
||||
<a href="">\parent.username</a>
|
||||
<p>
|
||||
\parent.content
|
||||
</p>
|
||||
<div class="child">
|
||||
\if $login_user_id != none:
|
||||
<form action="/api/comment" method="POST">
|
||||
<input type="hidden" name="parent_post" value=\'"'\$post_id\'"' />
|
||||
<input type="hidden" name="parent_comment" value=\'"'\parent.id\'"' />
|
||||
<textarea name="content"></textarea>
|
||||
<input type="submit" vaue="Publish" />
|
||||
</form>
|
||||
\for child in parent.child:
|
||||
render_comment(child)
|
||||
</div>
|
||||
</div>
|
||||
|
||||
let main =
|
||||
<main>
|
||||
<h3>\post.title</h3>
|
||||
<p>\post.content</p>
|
||||
<div>
|
||||
<form action="/api/comment" method="POST">
|
||||
<input type="hidden" name="parent_post" value=\'"'\$post_id\'"' />
|
||||
<textarea name="content"></textarea>
|
||||
<input type="submit" vaue="Publish" />
|
||||
</form>
|
||||
</div>
|
||||
\if len comments == 0:
|
||||
<span>No comments yet!</span>
|
||||
else for comment in comments:
|
||||
render_comment(comment)
|
||||
</main>
|
||||
|
||||
page(post.title, $login_user_id, style, main)
|
||||
+24
-24
@@ -1,24 +1,24 @@
|
||||
include "page.wl"
|
||||
include "login_and_signup_style.wl"
|
||||
|
||||
let main =
|
||||
<main>
|
||||
<span>Welcome!</span>
|
||||
|
||||
<div id="response"></div>
|
||||
|
||||
<form action="/api/signup" method="POST">
|
||||
<input type="text" name="username" placeholder="username" />
|
||||
<input type="email" name="email" placeholder="email" />
|
||||
<input type="password" name="password1" placeholder="password" />
|
||||
<input type="password" name="password2" placeholder="repeat password" />
|
||||
<input type="submit" value="Sign-Up" />
|
||||
<div class="form-links">
|
||||
<a href="">forgot password?</a>
|
||||
|
|
||||
<a href="">already have an account?</a>
|
||||
</div>
|
||||
</form>
|
||||
</main>
|
||||
|
||||
page("Log-In", none, style, main)
|
||||
include "page.wl"
|
||||
include "login_and_signup_style.wl"
|
||||
|
||||
let main =
|
||||
<main>
|
||||
<span>Welcome!</span>
|
||||
|
||||
<div id="response"></div>
|
||||
|
||||
<form action="/api/signup" method="POST">
|
||||
<input type="text" name="username" placeholder="username" />
|
||||
<input type="email" name="email" placeholder="email" />
|
||||
<input type="password" name="password1" placeholder="password" />
|
||||
<input type="password" name="password2" placeholder="repeat password" />
|
||||
<input type="submit" value="Sign-Up" />
|
||||
<div class="form-links">
|
||||
<a href="">forgot password?</a>
|
||||
|
|
||||
<a href="">already have an account?</a>
|
||||
</div>
|
||||
</form>
|
||||
</main>
|
||||
|
||||
page("Log-In", none, style, main)
|
||||
|
||||
+94
-94
@@ -1,95 +1,95 @@
|
||||
include "page.wl"
|
||||
|
||||
let style =
|
||||
<style>
|
||||
form {
|
||||
max-width: 400px;
|
||||
margin: 30px auto;
|
||||
}
|
||||
|
||||
form input,
|
||||
form textarea {
|
||||
border: 0;
|
||||
outline: 0;
|
||||
width: 100%;
|
||||
border-radius: 3px;
|
||||
padding: 8px;
|
||||
margin-bottom: 15px;
|
||||
background: #E8D4A9;
|
||||
border: 1px solid #D4C298;
|
||||
}
|
||||
|
||||
form input:focus,
|
||||
form textarea:focus {
|
||||
border-color: #5780C9;
|
||||
background: #fff;
|
||||
}
|
||||
|
||||
form textarea {
|
||||
height: 120px;
|
||||
resize: vertical;
|
||||
}
|
||||
|
||||
form input[type=submit] {
|
||||
cursor: pointer;
|
||||
background: #5780C9;
|
||||
color: #F7E6C0;
|
||||
}
|
||||
|
||||
form input[type=submit]:hover {
|
||||
background: #1D2B42;
|
||||
}
|
||||
|
||||
.checkbox-row {
|
||||
margin-bottom: 15px;
|
||||
}
|
||||
|
||||
.checkbox-row input[type="checkbox"] {
|
||||
width: auto;
|
||||
margin-right: 8px;
|
||||
}
|
||||
|
||||
.checkbox-row label {
|
||||
color: #1D2B42;
|
||||
font-size: 14px;
|
||||
cursor: pointer;
|
||||
}
|
||||
</style>
|
||||
|
||||
let main =
|
||||
<main>
|
||||
<form action="/api/post" method="POST">
|
||||
|
||||
<input type="hidden" name="csrf" value=\'"'\$csrf\'"' />
|
||||
<input type="text" id="title" name="title" placeholder="Title" required />
|
||||
|
||||
<div class="checkbox-row">
|
||||
<input type="checkbox" id="is_link" name="is_link" onchange="togglePostType()" />
|
||||
<label>This is a link post</label>
|
||||
</div>
|
||||
|
||||
<input type="url" id="url" name="link" placeholder="URL" style="display: none;" />
|
||||
|
||||
<textarea id="content" name="content" placeholder="Write your post here..."></textarea>
|
||||
|
||||
<input type="submit" value="Submit Post" />
|
||||
</form>
|
||||
|
||||
<script>
|
||||
function togglePostType() {
|
||||
const checkbox = document.getElementById('is_link');
|
||||
const urlInput = document.getElementById('url');
|
||||
const contentTextarea = document.getElementById('content');
|
||||
|
||||
if (checkbox.checked) {
|
||||
urlInput.style.display = 'block';
|
||||
contentTextarea.style.display = 'none';
|
||||
} else {
|
||||
urlInput.style.display = 'none';
|
||||
contentTextarea.style.display = 'block';
|
||||
}
|
||||
}
|
||||
</script>
|
||||
</main>
|
||||
|
||||
include "page.wl"
|
||||
|
||||
let style =
|
||||
<style>
|
||||
form {
|
||||
max-width: 400px;
|
||||
margin: 30px auto;
|
||||
}
|
||||
|
||||
form input,
|
||||
form textarea {
|
||||
border: 0;
|
||||
outline: 0;
|
||||
width: 100%;
|
||||
border-radius: 3px;
|
||||
padding: 8px;
|
||||
margin-bottom: 15px;
|
||||
background: #E8D4A9;
|
||||
border: 1px solid #D4C298;
|
||||
}
|
||||
|
||||
form input:focus,
|
||||
form textarea:focus {
|
||||
border-color: #5780C9;
|
||||
background: #fff;
|
||||
}
|
||||
|
||||
form textarea {
|
||||
height: 120px;
|
||||
resize: vertical;
|
||||
}
|
||||
|
||||
form input[type=submit] {
|
||||
cursor: pointer;
|
||||
background: #5780C9;
|
||||
color: #F7E6C0;
|
||||
}
|
||||
|
||||
form input[type=submit]:hover {
|
||||
background: #1D2B42;
|
||||
}
|
||||
|
||||
.checkbox-row {
|
||||
margin-bottom: 15px;
|
||||
}
|
||||
|
||||
.checkbox-row input[type="checkbox"] {
|
||||
width: auto;
|
||||
margin-right: 8px;
|
||||
}
|
||||
|
||||
.checkbox-row label {
|
||||
color: #1D2B42;
|
||||
font-size: 14px;
|
||||
cursor: pointer;
|
||||
}
|
||||
</style>
|
||||
|
||||
let main =
|
||||
<main>
|
||||
<form action="/api/post" method="POST">
|
||||
|
||||
<input type="hidden" name="csrf" value=\'"'\$csrf\'"' />
|
||||
<input type="text" id="title" name="title" placeholder="Title" required />
|
||||
|
||||
<div class="checkbox-row">
|
||||
<input type="checkbox" id="is_link" name="is_link" onchange="togglePostType()" />
|
||||
<label>This is a link post</label>
|
||||
</div>
|
||||
|
||||
<input type="url" id="url" name="link" placeholder="URL" style="display: none;" />
|
||||
|
||||
<textarea id="content" name="content" placeholder="Write your post here..."></textarea>
|
||||
|
||||
<input type="submit" value="Submit Post" />
|
||||
</form>
|
||||
|
||||
<script>
|
||||
function togglePostType() {
|
||||
const checkbox = document.getElementById('is_link');
|
||||
const urlInput = document.getElementById('url');
|
||||
const contentTextarea = document.getElementById('content');
|
||||
|
||||
if (checkbox.checked) {
|
||||
urlInput.style.display = 'block';
|
||||
contentTextarea.style.display = 'none';
|
||||
} else {
|
||||
urlInput.style.display = 'none';
|
||||
contentTextarea.style.display = 'block';
|
||||
}
|
||||
}
|
||||
</script>
|
||||
</main>
|
||||
|
||||
page("Write Post", $login_user_id, style, main)
|
||||
+29
-29
@@ -1,29 +1,29 @@
|
||||
CREATE TABLE IF NOT EXISTS Users (
|
||||
id INTEGER PRIMARY KEY,
|
||||
username TEXT NOT NULL UNIQUE,
|
||||
hash TEXT NOT NULL,
|
||||
email TEXT NOT NULL UNIQUE,
|
||||
signup_time DATETIME DEFAULT CURRENT_TIMESTAMP
|
||||
);
|
||||
|
||||
CREATE TABLE IF NOT EXISTS Posts (
|
||||
id INTEGER PRIMARY KEY,
|
||||
author INTEGER NOT NULL,
|
||||
title TEXT NOT NULL,
|
||||
is_link BOOLEAN NOT NULL,
|
||||
content TEXT NOT NULL,
|
||||
submit_time DATETIME DEFAULT CURRENT_TIMESTAMP,
|
||||
FOREIGN KEY (author) REFERENCES Users(id)
|
||||
);
|
||||
|
||||
CREATE TABLE IF NOT EXISTS Comments (
|
||||
id INTEGER PRIMARY KEY,
|
||||
parent_post INTEGER NOT NULL,
|
||||
parent_comment INTEGER,
|
||||
author INTEGER NOT NULL,
|
||||
content TEXT NOT NULL,
|
||||
submit_time DATETIME DEFAULT CURRENT_TIMESTAMP,
|
||||
FOREIGN KEY (author) REFERENCES Users(id),
|
||||
FOREIGN KEY (parent_post) REFERENCES Posts(id),
|
||||
FOREIGN KEY (parent_comment) REFERENCES Comments(id)
|
||||
);
|
||||
CREATE TABLE IF NOT EXISTS Users (
|
||||
id INTEGER PRIMARY KEY,
|
||||
username TEXT NOT NULL UNIQUE,
|
||||
hash TEXT NOT NULL,
|
||||
email TEXT NOT NULL UNIQUE,
|
||||
signup_time DATETIME DEFAULT CURRENT_TIMESTAMP
|
||||
);
|
||||
|
||||
CREATE TABLE IF NOT EXISTS Posts (
|
||||
id INTEGER PRIMARY KEY,
|
||||
author INTEGER NOT NULL,
|
||||
title TEXT NOT NULL,
|
||||
is_link BOOLEAN NOT NULL,
|
||||
content TEXT NOT NULL,
|
||||
submit_time DATETIME DEFAULT CURRENT_TIMESTAMP,
|
||||
FOREIGN KEY (author) REFERENCES Users(id)
|
||||
);
|
||||
|
||||
CREATE TABLE IF NOT EXISTS Comments (
|
||||
id INTEGER PRIMARY KEY,
|
||||
parent_post INTEGER NOT NULL,
|
||||
parent_comment INTEGER,
|
||||
author INTEGER NOT NULL,
|
||||
content TEXT NOT NULL,
|
||||
submit_time DATETIME DEFAULT CURRENT_TIMESTAMP,
|
||||
FOREIGN KEY (author) REFERENCES Users(id),
|
||||
FOREIGN KEY (parent_post) REFERENCES Posts(id),
|
||||
FOREIGN KEY (parent_comment) REFERENCES Comments(id)
|
||||
);
|
||||
|
||||
Reference in New Issue
Block a user