From f64bf9a786dac8c126bc544759fdafcc16786e94 Mon Sep 17 00:00:00 2001 From: yyc12345 Date: Tue, 19 Jan 2021 22:20:11 +0800 Subject: [PATCH] nightly commit --- src/database.py | 129 +++++++++++++++++++++-- src/server.py | 25 +++-- src/sql/sqlite.sql | 4 +- src/static/css/calendar.css | 10 +- src/static/i18n/strings_en-US.properties | 20 ++++ src/static/i18n/strings_zh-CN.properties | 21 ++++ src/static/js/headerNav.js | 47 +++++++++ src/static/js/i18n.js | 36 ++++--- src/static/js/page/calendar.js | 25 +++++ src/static/js/page/home.js | 8 +- src/static/js/page/login.js | 10 ++ src/static/tmpl/calendarItem.tmpl | 11 ++ src/static/tmpl/headerNav.tmpl | 26 ++--- src/static/tmpl/scheduleItem.tmpl | 37 +++++++ src/static/tmpl/todoItem.tmpl | 27 +++++ src/static/tmpl/userItem.tmpl | 27 +++++ src/templates/admin.html | 55 +++------- src/templates/calendar.html | 116 +++----------------- src/templates/home.html | 23 ++-- src/templates/login.html | 53 +++------- src/templates/todo.html | 54 +++------- src/utils.py | 14 ++- 22 files changed, 485 insertions(+), 293 deletions(-) create mode 100644 src/static/i18n/strings_en-US.properties create mode 100644 src/static/i18n/strings_zh-CN.properties create mode 100644 src/static/tmpl/calendarItem.tmpl create mode 100644 src/static/tmpl/scheduleItem.tmpl create mode 100644 src/static/tmpl/todoItem.tmpl create mode 100644 src/static/tmpl/userItem.tmpl diff --git a/src/database.py b/src/database.py index a49e8cc..2459469 100644 --- a/src/database.py +++ b/src/database.py @@ -1,10 +1,40 @@ import config import sqlite3 import json +import utils +import threading + +def SafeDatabaseOperation(func): + def wrapper(self, *args, **kwargs): + with self.mutex: + # check database and acquire cursor + try: + self.check_database() + self.cursor = self.db.cursor() + except: + self.cursor = None + return (False, None) + + # do real data work + try: + result = (True, func(self, *args, **kwargs)) + self.cursor.close() + self.cursor = None + self.db.commit() + return result + except: + self.cursor.close() + self.cursor = None + self.db.rollback() + return (False, None) + + return wrapper class CalendarDatabase(object): def __init__(self): self.db = None + self.cursor = None + self.mutex = threading.Lock() def open(self): if (self.is_database_valid()): @@ -21,15 +51,23 @@ class CalendarDatabase(object): if (self.is_database_valid()): raise Exception('Databade is opened') + # establish tables self.open() + cursor = self.db.cursor() with open('sql/sqlite.sql', 'r', encoding='utf-8') as fsql: - cursor = self.db.cursor() cursor.executescript(fsql.read()) - cursor.close() - self.db.commit() - #todo: finish init - + # finish init + cursor.execute('INSERT INTO user VALUES (?, ?, ?, ?, ?, ?);', ( + username, + utils.ComputePasswordHash(password), + 1, + utils.GenerateSalt(), + utils.GenerateToken(username), + 0 + )) + cursor.close() + self.db.commit() def close(self): self.check_database() @@ -43,9 +81,80 @@ class CalendarDatabase(object): def is_database_valid(self): return not (self.db == None) - # operation function - def login_step1(self, username): - self.check_database() + def get_username_from_token(self, token): + self.cursor.execute('SELECT [ccn_name] FROM user WHERE [ccn_token] = ? AND [ccn_tokenExpireOn] > ?;',( + token, + utils.GetCurrentTimestamp() + )) + return self.cursor.fetchone()[0] + + # =============================== # =============================== operation function + # =============================== common + @SafeDatabaseOperation + def common_salt(self, username): + salt = utils.GenerateSalt() + self.cursor.execute('UPDATE user SET [ccn_salt] = ? WHERE [ccn_name] = ?;', ( + salt, + username + )) + return salt + + @SafeDatabaseOperation + def common_login(self, username, password): + self.cursor.execute('SELECT [ccn_password], [ccn_salt] FROM user WHERE [ccn_name] = ?;', (username, )) + (gotten_salt, gotten_password) = self.cursor.fetchone() + + if password == utils.ComputePasswordHashWithSalt(gotten_password, gotten_salt): + token = utils.GenerateToken(username) + self.cursor.execute('UPDATE user SET [ccn_token] = ?, [ccn_tokenExpireOn] = ? WHERE [ccn_name] = ?;', ( + token, + utils.GetCurrentTimestamp() + 60 * 60 * 24 * 2, # add 2 day from now + username + )) + return token + else: + # return empty string to indicate fail to login + return '' + + @SafeDatabaseOperation + def common_logout(self, token): + username = self.get_username_from_token(cur, token) + self.cursor.execute('UPDATE user SET [ccn_tokenExpireOn] = 0 WHERE [ccn_name] = ?;', (username, )) + return None + + @SafeDatabaseOperation + def common_tokenValid(self, token): + # get user name have check the validation, don't do anything more. + self.get_username_from_token(token) + return result + + @SafeDatabaseOperation + def common_isAdmin(self, token): + username = self.get_username_from_token(token) + self.cursor.execute('SELECT [ccn_isAdmin] FROM user WHERE [ccn_name] = ?;', (username, )) + result = self.cursor.fetchone()[0] == 1 + return result + + @SafeDatabaseOperation + def common_changePassword(self, token, newpassword): + username = self.get_username_from_token(token) + self.cursor.execute('UPDATE user SET [ccn_password] = ? WHERE [ccn_name] = ?;', ( + newpassword, + username + )) + return None + + # =============================== calendar + + + # =============================== collection + + + # =============================== todo + + + + + # =============================== admin + - def login_step2(self, username, password): - self.check_database() diff --git a/src/server.py b/src/server.py index 3b9fbf1..a801a28 100644 --- a/src/server.py +++ b/src/server.py @@ -15,7 +15,7 @@ import database app = Flask(__name__) -render_static_resources = None +# render_static_resources = None # =============================================database def get_database(): @@ -39,23 +39,28 @@ def nospecHandle(): @app.route('/web/home', methods=['GET']) def web_homeHandle(): - UpdateStaticResources() - return render_template("home.html", **render_static_resources) + # UpdateStaticResources() + return render_template("home.html") @app.route('/web/calendar', methods=['GET']) def web_calendarHandle(): - UpdateStaticResources() - return render_template("calendar.html", **render_static_resources) + # UpdateStaticResources() + return render_template("calendar.html") @app.route('/web/todo', methods=['GET']) def web_todoHandle(): - UpdateStaticResources() - return render_template("todo.html", **render_static_resources) + # UpdateStaticResources() + return render_template("todo.html") @app.route('/web/admin', methods=['GET']) def web_adminHandle(): - UpdateStaticResources() - return render_template("admin.html", **render_static_resources) + # UpdateStaticResources() + return render_template("admin.html") + +@app.route('/web/login', methods=['GET']) +def web_loginHandle(): + # UpdateStaticResources() + return render_template("login.html") # ============================================= query page route @@ -194,6 +199,7 @@ def api_admin_deleteHandle(): # =============================================main run +''' def UpdateStaticResources(): global render_static_resources if render_static_resources is not None: @@ -209,6 +215,7 @@ def UpdateStaticResources(): 'url_js_pageHome': url_for('static', filename='js/page/home.js') } +''' def run(): app.run(port=config.CustomConfig['web']['port']) diff --git a/src/sql/sqlite.sql b/src/sql/sqlite.sql index a7517b1..9f2a2da 100644 --- a/src/sql/sqlite.sql +++ b/src/sql/sqlite.sql @@ -35,7 +35,7 @@ CREATE TABLE calendar( [ccn_title] TEXT NOT NULL, [ccn_description] TEXT NOT NULL, - [ccn_lastChange] BIGINT NOT NULL, + [ccn_lastChange] TEXT NOT NULL, [ccn_eventDateTimeType] TINYINT NOT NULL, [ccn_eventDateTimeStart] BIGINT NOT NULL, @@ -54,7 +54,7 @@ CREATE TABLE todo( [ccn_belongTo] TEXT NOT NULL, [ccn_data] TEXT NOT NULL, - [ccn_lastChange] BIGINT NOT NULL, + [ccn_lastChange] TEXT NOT NULL, PRIMARY KEY (ccn_uuid), FOREIGN KEY (ccn_belongTo) REFERENCES user(ccn_name) ON DELETE CASCADE diff --git a/src/static/css/calendar.css b/src/static/css/calendar.css index 7fefd66..9a09f21 100644 --- a/src/static/css/calendar.css +++ b/src/static/css/calendar.css @@ -1,4 +1,4 @@ -#calendar-body div:nth-child(n+2) div { +#ccn-calendar-calendarBbody div:nth-child(n+2) div { border-top: 0 solid black; border-left: 0 solid black; border-right: 1px solid black; @@ -13,15 +13,15 @@ overflow: hidden; } -#calendar-body div:nth-child(n+2) div:nth-child(1) { +#ccn-calendar-calendarBbody div:nth-child(n+2) div:nth-child(1) { border-left: 1px solid black; } -#calendar-body div:nth-child(2) div { +#ccn-calendar-calendarBbody div:nth-child(2) div { border-top: 1px solid black; } -#calendar-body div div { +#ccn-calendar-calendarBbody div div { flex-grow: 1; flex-basis: 0; flex-shrink: 0; @@ -29,7 +29,7 @@ overflow: hidden; } -#calendar-body div { +#ccn-calendar-calendarBbody div { display: flex; flex-flow: row; } diff --git a/src/static/i18n/strings_en-US.properties b/src/static/i18n/strings_en-US.properties new file mode 100644 index 0000000..67a16cd --- /dev/null +++ b/src/static/i18n/strings_en-US.properties @@ -0,0 +1,20 @@ +ccn-pageName-home=coconut-leaf - A light, self-host calendar system. +ccn-pageName-calendar=coconut-leaf - Calendar +ccn-pageName-todo=coconut-leaf - Todo +ccn-pageName-admin=coconut-leaf - Admin +ccn-pageName-login=coconut-leaf - Login + +ccn-header-nav-home=Home +ccn-header-nav-calendar=Calendar +ccn-header-nav-todo=Todo +ccn-header-nav-admin=Admin +ccn-header-user-login=Login +ccn-header-user-logout=Logout +ccn-header-language=Language + +ccn-home-desc=

coconut-leaf

A light, self-host calendar system.

Originally, this app is served for yyc12345 personal use.


Pull request / issue / translation are welcomed.

Submit them in our GitHub project.

This project source code is licensed AGPL v3.

+ +ccn-login-form-username=Username +ccn-login-form-password=Password +ccn-login-form-login=Login + diff --git a/src/static/i18n/strings_zh-CN.properties b/src/static/i18n/strings_zh-CN.properties new file mode 100644 index 0000000..46fe982 --- /dev/null +++ b/src/static/i18n/strings_zh-CN.properties @@ -0,0 +1,21 @@ +ccn-pageName-home=coconut-leaf - 一个轻量的自建日历系统 +ccn-pageName-calendar=coconut-leaf - 日历 +ccn-pageName-todo=coconut-leaf - 待办 +ccn-pageName-admin=coconut-leaf - 管理 +ccn-pageName-login=coconut-leaf - 登录 + +ccn-header-nav-home=主页 +ccn-header-nav-calendar=日历 +ccn-header-nav-todo=待办 +ccn-header-nav-admin=管理 +ccn-header-user-login=登录 +ccn-header-user-logout=登出 +ccn-header-language=语言 + +ccn-home-desc=

coconut-leaf

一个轻量的自建日历系统

原本是出于yyc12345的个人使用而制作的。


欢迎提出Pull request / issue / 翻译

将他们提交到我们的GitHub项目.

本工程代码使用AGPL v3授权。

+ +ccn-login-form-username=用户名 +ccn-login-form-password=密码 +ccn-login-form-login=登录 + + diff --git a/src/static/js/headerNav.js b/src/static/js/headerNav.js index 972f810..3b2e9ed 100644 --- a/src/static/js/headerNav.js +++ b/src/static/js/headerNav.js @@ -2,6 +2,7 @@ function cnn_headerNav_Insert() { $.ajax({ url: $("#jsrender-tmpl-headerNav").attr('src'), type: "GET", + async: false, success: function (data) { var tmpl = $.templates(data); $('body').prepend(tmpl.render()); @@ -9,3 +10,49 @@ function cnn_headerNav_Insert() { }); } +function cnn_headerNav_LoggedRefresh() { + if (cnn_api_tokenValid()) { + // logged, show all nav button and logout button + $("#cnn-header-nav-home").show(); + $("#cnn-header-nav-calendar").show(); + $("#cnn-header-nav-todo").show(); + $("#cnn-header-nav-admin").show(); + + $("#cnn-header-user-login").hide(); + $("#cnn-header-user-logout").show(); + } else { + $("#cnn-header-nav-home").show(); + $("#cnn-header-nav-calendar").hide(); + $("#cnn-header-nav-todo").hide(); + $("#cnn-header-nav-admin").hide(); + + $("#cnn-header-user-login").show(); + $("#cnn-header-user-logout").hide(); + } +} + +// bind language process and internal process function such as logout and expand menu +function cnn_headerNav_BindEvents() { + // bind function + $("#cnn-header-language > *").each(function(){ + $(this).click(function(){ + ccn_i18n_ChangeLanguage($(this).attr("language")); + ccn_i18n_ApplyLanguage(); + }); + }); + + // todo: bind logout + + + // bind burger menu + // copy from bulma website + // Check for click events on the navbar burger icon + $(".navbar-burger").click(function() { + + // Toggle the "is-active" class on both the "navbar-burger" and the "navbar-menu" + $(".navbar-burger").toggleClass("is-active"); + $(".navbar-menu").toggleClass("is-active"); + +}); +} + diff --git a/src/static/js/i18n.js b/src/static/js/i18n.js index dc02d86..211bbd9 100644 --- a/src/static/js/i18n.js +++ b/src/static/js/i18n.js @@ -1,5 +1,13 @@ var ccn_i18n_i18nSupported = ['en-US', 'zh-CN']; var ccn_i18n_currentLanguage = 'en-US'; +var ccn_pages_enumPages = { + home : 0, + calendar: 1, + todo: 2, + admin: 3, + login: 4 +}; +var ccn_pages_currentPage = ccn_pages_enumPages.home; // judge current language ccn_i18n_currentLanguage = ccn_localstorageAssist_Get('ccn-i18n', 'en-US'); @@ -18,14 +26,17 @@ function ccn_i18n_ChangeLanguage(newLang) { function ccn_i18n_ApplyLanguage() { $.i18n.properties({ name: 'strings_' + ccn_i18n_currentLanguage, - path: 'i18n/', + path: '/static/i18n/', + encoding: 'utf-8', mode: 'map', + async: true, + cache: false, language: ccn_i18n_currentLanguage, callback: function() { //set usual block var cache = $(".ccn-i18n"); cache.each(function() { - $(this).html($.i18n.prop($(this).attr('name'))); + $(this).html($.i18n.prop($(this).attr('i18n-name'))); }); //set unusual block @@ -34,22 +45,19 @@ function ccn_i18n_ApplyLanguage() { case ccn_pages_enumPages.home: $('#ccn-pageName').html($.i18n.prop('ccn-pageName-home')) break; - case ccn_pages_enumPages.user: - $('#ccn-pageName').html($.i18n.prop('ccn-pageName-user')) + case ccn_pages_enumPages.calendar: + $('#ccn-pageName').html($.i18n.prop('ccn-pageName-calendar')) break; - case ccn_pages_enumPages.userinfo: - $('#ccn-pageName').html($.i18n.prop('ccn-pageName-userinfo')) + case ccn_pages_enumPages.todo: + $('#ccn-pageName').html($.i18n.prop('ccn-pageName-todo')) break; - case ccn_pages_enumPages.map: - $('#ccn-pageName').html($.i18n.prop('ccn-pageName-map')) + case ccn_pages_enumPages.admin: + $('#ccn-pageName').html($.i18n.prop('ccn-pageName-admin')) break; - case ccn_pages_enumPages.mapinfo: - $('#ccn-pageName').html($.i18n.prop('ccn-pageName-mapinfo')) - break; - case ccn_pages_enumPages.about: - $('#ccn-pageName').html($.i18n.prop('ccn-pageName-about')) + case ccn_pages_enumPages.login: + $('#ccn-pageName').html($.i18n.prop('ccn-pageName-login')) break; } } - }) + }); } diff --git a/src/static/js/page/calendar.js b/src/static/js/page/calendar.js index e69de29..83af4a5 100644 --- a/src/static/js/page/calendar.js +++ b/src/static/js/page/calendar.js @@ -0,0 +1,25 @@ +$(document).ready(function() { + // nav process + ccn_pages_currentPage = ccn_pages_enumPages.calendar; + cnn_headerNav_Insert(); + cnn_headerNav_BindEvents(); + cnn_headerNav_LoggedRefresh(); + + // process calendar it self + ccn_calendar_LoadCalendarBody(); + + // apply i18n + ccn_i18n_ApplyLanguage(); +}); + +function ccn_calendar_LoadCalendarBody() { + $.ajax({ + url: $("#jsrender-tmpl-calendarItem").attr('src'), + type: "GET", + async: false, + success: function (data) { + var tmpl = $.templates(data); + $('#ccn-calendar-calendarBbody').append(tmpl.render()); + } + }); +} diff --git a/src/static/js/page/home.js b/src/static/js/page/home.js index e9a3ade..5c79ac9 100644 --- a/src/static/js/page/home.js +++ b/src/static/js/page/home.js @@ -1,4 +1,10 @@ $(document).ready(function() { - // insert nav first + // nav process + ccn_pages_currentPage = ccn_pages_enumPages.home; cnn_headerNav_Insert(); + cnn_headerNav_BindEvents(); + cnn_headerNav_LoggedRefresh(); + + // apply i18n + ccn_i18n_ApplyLanguage(); }); \ No newline at end of file diff --git a/src/static/js/page/login.js b/src/static/js/page/login.js index e69de29..0685097 100644 --- a/src/static/js/page/login.js +++ b/src/static/js/page/login.js @@ -0,0 +1,10 @@ +$(document).ready(function() { + // nav process + ccn_pages_currentPage = ccn_pages_enumPages.login; + cnn_headerNav_Insert(); + cnn_headerNav_BindEvents(); + cnn_headerNav_LoggedRefresh(); + + // apply i18n + ccn_i18n_ApplyLanguage(); +}); \ No newline at end of file diff --git a/src/static/tmpl/calendarItem.tmpl b/src/static/tmpl/calendarItem.tmpl new file mode 100644 index 0000000..cf7cd08 --- /dev/null +++ b/src/static/tmpl/calendarItem.tmpl @@ -0,0 +1,11 @@ +{{for start=0 end=6 step=1 itemVar="~row"}} +
+ {{for start=0 end=7 step=1 itemVar="~column"}} +
+

1

+

春分

+

114514

+
+ {{/for}} +
+{{/for}} \ No newline at end of file diff --git a/src/static/tmpl/headerNav.tmpl b/src/static/tmpl/headerNav.tmpl index 65418ac..0a90789 100644 --- a/src/static/tmpl/headerNav.tmpl +++ b/src/static/tmpl/headerNav.tmpl @@ -1,7 +1,7 @@ -
+
Monday
Tuesday
@@ -92,65 +67,6 @@
Saturday
Sunday
- -
-
-

1

-

春分

-

114514

-
-
1
-
1
-
1
-
1
-
1
-
1
-
-
-
1
-
1
-
1
-
1
-
1
-
1
-
1
-
-
-
1
-
1
-
1
-
1
-
1
-
1
-
1
-
-
-
1
-
1
-
1
-
1
-
1
-
1
-
1
-
-
-
1
-
1
-
1
-
1
-
1
-
1
-
1
-
-
-
1
-
1
-
1
-
1
-
1
-
1
-
1
-
diff --git a/src/templates/home.html b/src/templates/home.html index 31be4a5..4018f28 100644 --- a/src/templates/home.html +++ b/src/templates/home.html @@ -3,7 +3,7 @@ - coconut-leaf Login + @@ -11,26 +11,19 @@ - + - - - - + + + + - +
-
-

coconut-leaf

-

A light, self-host calendar system.

-

Originally, this app is served for yyc12345's personal use.

-
-

Pull request / issue / translation are welcomed.

-

Submit them in our GitHub project.

- +
diff --git a/src/templates/login.html b/src/templates/login.html index 6d52cea..acca2ca 100644 --- a/src/templates/login.html +++ b/src/templates/login.html @@ -3,53 +3,30 @@ - coconut-leaf Login + - + + + + + + + + + + + - -
- +
@@ -58,7 +35,7 @@
- +

@@ -68,7 +45,7 @@

- +
diff --git a/src/templates/todo.html b/src/templates/todo.html index 315167b..f20c726 100644 --- a/src/templates/todo.html +++ b/src/templates/todo.html @@ -3,53 +3,27 @@ - coconut-leaf Calendar + - + + + - + + + + + + + + + + -

Todo list

diff --git a/src/utils.py b/src/utils.py index 58e3fa8..6602ab5 100644 --- a/src/utils.py +++ b/src/utils.py @@ -1,6 +1,7 @@ import hashlib import random import uuid +import time ValidUsername = set(map(lambda x:chr(x), range(48, 58, 1))) | set(map(lambda x:chr(x), range(65, 91, 1))) | set(map(lambda x:chr(x), range(97, 123, 1))) ValidPassword = set(map(lambda x:chr(x), range(33, 127, 1))) @@ -13,7 +14,7 @@ def IsValidPassword(strl): def ComputePasswordHash(password): s = hashlib.sha256() - s.update(password) + s.update(password.encode('utf-8')) return s.hexdigest() def GenerateUUID(): @@ -21,8 +22,8 @@ def GenerateUUID(): def GenerateToken(username): s = hashlib.sha256() - s.update(username) - s.update(str(GenerateSalt())) + s.update(username.encode('utf-8')) + s.update((str(GenerateSalt())).encode('utf-8')) return s.hexdigest() def GenerateSalt(): @@ -30,5 +31,8 @@ def GenerateSalt(): def ComputePasswordHashWithSalt(passwordHashed, salt): s = hashlib.sha256() - s.update(passwordHashed + str(salt)) - return s.hexdigest() \ No newline at end of file + s.update((passwordHashed + str(salt)).encode('utf-8')) + return s.hexdigest() + +def GetCurrentTimestamp(): + return int(time.time()) \ No newline at end of file