1
0

nightly commit

This commit is contained in:
2021-01-24 14:38:08 +08:00
parent e4bc3f686f
commit b83b19364c
21 changed files with 223 additions and 107 deletions

View File

@@ -3,6 +3,7 @@ import sqlite3
import json
import utils
import threading
import logging
def SafeDatabaseOperation(func):
def wrapper(self, *args, **kwargs):
@@ -11,22 +12,31 @@ def SafeDatabaseOperation(func):
try:
self.check_database()
self.cursor = self.db.cursor()
except:
except Exception as e:
self.cursor = None
return (False, None)
if config.CustomConfig['debug']:
logging.exception(e)
return (False, str(e), None)
# do real data work
try:
result = (True, func(self, *args, **kwargs))
if self.isFirstRun:
self.isFirstRun = False
print('Cleaning outdated token...')
self.tokenOper_clean()
result = (True, '', func(self, *args, **kwargs))
self.cursor.close()
self.cursor = None
self.db.commit()
return result
except:
except Exception as e:
self.cursor.close()
self.cursor = None
self.db.rollback()
return (False, None)
if config.CustomConfig['debug']:
logging.exception(e)
return (False, str(e), None)
return wrapper
@@ -35,6 +45,7 @@ class CalendarDatabase(object):
self.db = None
self.cursor = None
self.mutex = threading.Lock()
self.isFirstRun = True
def open(self):
if (self.is_database_valid()):
@@ -49,7 +60,7 @@ class CalendarDatabase(object):
def init(self, username, password):
if (self.is_database_valid()):
raise Exception('Databade is opened')
raise Exception('Database is opened')
# establish tables
self.open()
@@ -58,13 +69,11 @@ class CalendarDatabase(object):
cursor.executescript(fsql.read())
# finish init
cursor.execute('INSERT INTO user VALUES (?, ?, ?, ?, ?, ?);', (
cursor.execute('INSERT INTO user VALUES (?, ?, ?, ?);', (
username,
utils.ComputePasswordHash(password),
1,
utils.GenerateSalt(),
utils.GenerateToken(username),
0
utils.GenerateSalt()
))
cursor.close()
self.db.commit()
@@ -81,8 +90,27 @@ class CalendarDatabase(object):
def is_database_valid(self):
return not (self.db == None)
def get_username_from_token(self, token):
self.cursor.execute('SELECT [ccn_name] FROM user WHERE [ccn_token] = ? AND [ccn_tokenExpireOn] > ?;',(
# ======================= token related internal operation
def tokenOper_clean(self):
# remove outdated token
self.cursor.execute('DELETE FROM token WHERE [ccn_tokenExpireOn] <= ?',(utils.GetCurrentTimestamp(), ))
def tokenOper_postpone_expireOn(self, token):
self.cursor.execute('UPDATE token SET [ccn_tokenExpireOn] = ? WHERE [ccn_token] = ?;', (
utils.GetTokenExpireOn(),
token
))
def tokenOper_check_valid(self, token):
self.tokenOper_get_username(token)
def tokenOper_is_admin(self, username):
self.cursor.execute('SELECT [ccn_isAdmin] FROM user WHERE [ccn_name] = ?;',(username, ))
cache = self.cursor.fetchone()[0]
return True if cache == 1 else False
def tokenOper_get_username(self, token):
self.cursor.execute('SELECT [ccn_user] FROM token WHERE [ccn_token] = ? AND [ccn_tokenExpireOn] > ?;',(
token,
utils.GetCurrentTimestamp()
))
@@ -106,16 +134,19 @@ class CalendarDatabase(object):
if password == utils.ComputePasswordHashWithSalt(gotten_password, gotten_salt):
token = utils.GenerateToken(username)
self.cursor.execute('UPDATE user SET [ccn_token] = ?, [ccn_tokenExpireOn] = ?, [ccn_salt] = ? WHERE [ccn_name] = ?;', (
token,
utils.GetCurrentTimestamp() + 60 * 60 * 24 * 2, # add 2 day from now
self.cursor.execute('UPDATE user SET [ccn_salt] = ? WHERE [ccn_name] = ?;', (
utils.GenerateSalt(), # regenerate a new slat to prevent re-login try
username
))
self.cursor.execute('INSERT INTO token VALUES (?, ?, ?);', (
username,
token,
utils.GetTokenExpireOn(), # add 2 day from now
))
return token
else:
# throw a exception to indicate fail to login
raise Exception()
raise Exception('Login authentication failed')
@SafeDatabaseOperation
def common_webLogin(self, username, password):
@@ -123,38 +154,35 @@ class CalendarDatabase(object):
if len(self.cursor.fetchall()) != 0:
token = utils.GenerateToken(username)
self.cursor.execute('UPDATE user SET [ccn_token] = ?, [ccn_tokenExpireOn] = ? WHERE [ccn_name] = ?;', (
self.cursor.execute('INSERT INTO token VALUES (?, ?, ?);', (
username,
token,
utils.GetCurrentTimestamp() + 60 * 60 * 24 * 2, # add 2 day from now
username
utils.GetTokenExpireOn(), # add 2 day from now
))
return token
else:
# throw a exception to indicate fail to login
raise Exception()
raise Exception('Login authentication failed')
@SafeDatabaseOperation
def common_logout(self, token):
username = self.get_username_from_token(token)
self.cursor.execute('UPDATE user SET [ccn_tokenExpireOn] = 0 WHERE [ccn_name] = ?;', (username, ))
self.tokenOper_check_valid(token)
self.cursor.execute('DELETE FROM token WHERE [ccn_token] = ?;', (token, ))
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)
self.tokenOper_check_valid(token)
return None
@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
username = self.tokenOper_get_username(token)
return self.tokenOper_is_admin(username)
@SafeDatabaseOperation
def common_changePassword(self, token, newpassword):
username = self.get_username_from_token(token)
username = self.tokenOper_get_username(token)
self.cursor.execute('UPDATE user SET [ccn_password] = ? WHERE [ccn_name] = ?;', (
newpassword,
username
@@ -170,25 +198,25 @@ class CalendarDatabase(object):
# =============================== todo
@SafeDatabaseOperation
def todo_getFull(self, token):
username = self.get_username_from_token(token)
username = self.tokenOper_get_username(token)
self.cursor.execute('SELECT * FROM todo WHERE [ccn_belongTo] = ?;', (username, ))
return self.cursor.fetchall()
@SafeDatabaseOperation
def todo_getList(self, token):
username = self.get_username_from_token(token)
username = self.tokenOper_get_username(token)
self.cursor.execute('SELECT [ccn_uuid] FROM todo WHERE [ccn_belongTo] = ?;', (username, ))
return tuple(map(lambda x: x[0], self.cursor.fetchall()))
@SafeDatabaseOperation
def todo_getDetail(self, token, uuid):
username = self.get_username_from_token(token)
username = self.tokenOper_get_username(token)
self.cursor.execute('SELECT * FROM todo WHERE [ccn_belongTo] = ? AND [ccn_uuid] = ?;', (username, uuid))
return self.cursor.fetchone()
@SafeDatabaseOperation
def todo_add(self, token):
username = self.get_username_from_token(token)
username = self.tokenOper_get_username(token)
newuuid = utils.GenerateUUID()
lastupdate = utils.GenerateUUID()
returnedData = (
@@ -203,7 +231,7 @@ class CalendarDatabase(object):
@SafeDatabaseOperation
def todo_update(self, token, uuid, data, lastChange):
# check valid token
self.get_username_from_token(token)
self.tokenOper_check_valid(token)
# check sync conflict
self.cursor.execute('SELECT [ccn_uuid] FROM todo WHERE [ccn_uuid] = ? AND [ccn_lastChange] = ?;', (
uuid,
@@ -224,7 +252,7 @@ class CalendarDatabase(object):
@SafeDatabaseOperation
def todo_delete(self, token, uuid, lastChange):
# check valid token
self.get_username_from_token(token)
self.tokenOper_check_valid(token)
# check sync conflict
self.cursor.execute('SELECT [ccn_uuid] FROM todo WHERE [ccn_uuid] = ? AND [ccn_lastChange] = ?;', (
uuid,

View File

@@ -302,8 +302,8 @@ def CheckParameter(paramList):
def ConstructResponseBody(returnedTuple):
return {
'success': returnedTuple[0],
'error': '',
'data': returnedTuple[1]
'error': returnedTuple[1],
'data': returnedTuple[2]
}
def run():

View File

@@ -6,10 +6,16 @@ CREATE TABLE user(
[ccn_password] TEXT NOT NULL,
[ccn_isAdmin] TINYINT NOT NULL CHECK(ccn_isAdmin = 1 OR ccn_isAdmin = 0),
[ccn_salt] INTEGER NOT NULL,
PRIMARY KEY (ccn_name)
);
CREATE TABLE token(
[ccn_user] TEXT NOT NULL,
[ccn_token] TEXT UNIQUE NOT NULL,
[ccn_tokenExpireOn] BIGINT NOT NULL,
PRIMARY KEY (ccn_name)
FOREIGN KEY (ccn_user) REFERENCES user(ccn_name) ON DELETE CASCADE
);
CREATE TABLE collection(

View File

@@ -24,10 +24,10 @@ ccn-login-form-login=Login
ccn-todo-todoList=Todo list
ccn-calendar-jump=Jump
ccn-calendar-today=Today
ccn-calendar-add=Add...
ccn-calendar-scheduleList=Schedule
ccn-calendar-calendar-jump=Jump
ccn-calendar-calendar-today=Today
ccn-calendar-calendar-add=Add...
ccn-calendar-calendar-scheduleList=Schedule
ccn-calendar-tabcontrol-tabCalendar=Calendar
ccn-calendar-tabcontrol-tabShared=Shared
ccn-calendar-tabcontrol-tabSharing=Sharing
@@ -38,3 +38,7 @@ ccn-calendar-week-thursday=Thursday
ccn-calendar-week-friday=Friday
ccn-calendar-week-saturday=Saturday
ccn-calendar-week-sunday=Sunday
ccn-calendar-shared-list=Shared
ccn-calendar-sharing-ownedList=Owned
ccn-calendar-sharing-sharingTargetList=Sharing target
ccn-calendar-sharing-sharingTargetEditing=Editing:

View File

@@ -24,10 +24,10 @@ ccn-login-form-login=登录
ccn-todo-todoList=待办列表
ccn-calendar-jump=转到
ccn-calendar-today=今天
ccn-calendar-add=添加...
ccn-calendar-scheduleList=日程安排
ccn-calendar-calendar-jump=转到
ccn-calendar-calendar-today=今天
ccn-calendar-calendar-add=添加...
ccn-calendar-calendar-scheduleList=日程安排
ccn-calendar-tabcontrol-tabCalendar=日历
ccn-calendar-tabcontrol-tabShared=被共享的
ccn-calendar-tabcontrol-tabSharing=共享给其他人
@@ -38,3 +38,7 @@ ccn-calendar-week-thursday=星期四
ccn-calendar-week-friday=星期五
ccn-calendar-week-saturday=星期六
ccn-calendar-week-sunday=星期日
ccn-calendar-shared-list=被共享的集合
ccn-calendar-sharing-ownedList=我的集合
ccn-calendar-sharing-sharingTargetList=分享目标
ccn-calendar-sharing-sharingTargetEditing=正在编辑集合:

View File

@@ -1,13 +1,5 @@
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());
}
});
$('body').prepend(ccn_template_headerNav.render());
}
function cnn_headerNav_LoggedRefresh() {
@@ -57,10 +49,10 @@ function cnn_headerNav_BindEvents() {
// 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");
// Toggle the "is-active" class on both the "navbar-burger" and the "navbar-menu"
$(".navbar-burger").toggleClass("is-active");
$(".navbar-menu").toggleClass("is-active");
});
});
}

View File

@@ -43,19 +43,19 @@ function ccn_i18n_ApplyLanguage() {
//set title
switch(ccn_pages_currentPage) {
case ccn_pages_enumPages.home:
$('#ccn-pageName').html($.i18n.prop('ccn-pageName-home'))
$('#ccn-pageName').html($.i18n.prop('ccn-pageName-home'));
break;
case ccn_pages_enumPages.calendar:
$('#ccn-pageName').html($.i18n.prop('ccn-pageName-calendar'))
$('#ccn-pageName').html($.i18n.prop('ccn-pageName-calendar'));
break;
case ccn_pages_enumPages.todo:
$('#ccn-pageName').html($.i18n.prop('ccn-pageName-todo'))
$('#ccn-pageName').html($.i18n.prop('ccn-pageName-todo'));
break;
case ccn_pages_enumPages.admin:
$('#ccn-pageName').html($.i18n.prop('ccn-pageName-admin'))
$('#ccn-pageName').html($.i18n.prop('ccn-pageName-admin'));
break;
case ccn_pages_enumPages.login:
$('#ccn-pageName').html($.i18n.prop('ccn-pageName-login'))
$('#ccn-pageName').html($.i18n.prop('ccn-pageName-login'));
break;
}
}

View File

@@ -0,0 +1,14 @@
$(document).ready(function() {
ccn_pages_currentPage = ccn_pages_enumPages.admin;
// template process
ccn_template_Load();
// nav process
cnn_headerNav_Insert();
cnn_headerNav_BindEvents();
cnn_headerNav_LoggedRefresh();
// apply i18n
ccn_i18n_ApplyLanguage();
});

View File

@@ -1,6 +1,10 @@
$(document).ready(function() {
// nav process
ccn_pages_currentPage = ccn_pages_enumPages.calendar;
// template process
ccn_template_Load();
// nav process
cnn_headerNav_Insert();
cnn_headerNav_BindEvents();
cnn_headerNav_LoggedRefresh();

View File

@@ -1,6 +1,10 @@
$(document).ready(function() {
// nav process
ccn_pages_currentPage = ccn_pages_enumPages.home;
// template process
ccn_template_Load();
// nav process
cnn_headerNav_Insert();
cnn_headerNav_BindEvents();
cnn_headerNav_LoggedRefresh();

View File

@@ -1,6 +1,10 @@
$(document).ready(function() {
// nav process
ccn_pages_currentPage = ccn_pages_enumPages.login;
// template process
ccn_template_Load();
// nav process
cnn_headerNav_Insert();
cnn_headerNav_BindEvents();
cnn_headerNav_LoggedRefresh();

View File

@@ -1,8 +1,12 @@
var ccn_todo_todoListCache = [];
$(document).ready(function() {
ccn_pages_currentPage = ccn_pages_enumPages.todo;
// template process
ccn_template_Load();
// nav process
ccn_pages_currentPage = ccn_pages_enumPages.login;
cnn_headerNav_Insert();
cnn_headerNav_BindEvents();
cnn_headerNav_LoggedRefresh();
@@ -40,25 +44,15 @@ function ccn_todo_RenderCacheList() {
data: undefined
};
var templates = undefined;
$.ajax({
url: $("#jsrender-tmpl-todoItem").attr('src'),
type: "GET",
async: false,
success: function (data) {
templates = $.templates(data);
}
});
var listDOM = $("#ccn-todo-todoList");
for(var index in ccn_todo_todoListCache) {
// update render data
var item = ccn_todo_todoListCache[index];
renderdata.uuid = item[0];
renderdata.data = item[2];
renderdata.data = LineBreaker2Br(item[2]);
// render
listDOM.append(templates.render(renderdata));
listDOM.append(ccn_template_todoItem.render(renderdata));
// set mode
var uuid = renderdata.uuid;
@@ -110,22 +104,11 @@ function ccn_todo_Add() {
// add into cache
ccn_todo_todoListCache[result[0]] = result;
// render
var templates = undefined;
$.ajax({
url: $("#jsrender-tmpl-todoItem").attr('src'),
type: "GET",
async: false,
success: function (data) {
templates = $.templates(data);
}
});
// render
var listDOM = $("#ccn-todo-todoList");
listDOM.append(templates.render({
listDOM.append(ccn_template_todoItem.render({
uuid: result[0],
data: result[2]
data: LineBreaker2Br(result[2])
}));
// set mode
@@ -145,7 +128,7 @@ function ccn_todo_ItemEdit() {
// copy current data to textarea
$("#ccn-todo-todoItem-textarea-" + uuid).val(
$("#ccn-todo-todoItem-p-" + uuid).text()
ccn_todo_todoListCache[uuid][2]
);
// switch to edit mode
@@ -186,7 +169,7 @@ function ccn_todo_ItemUpdate() {
// safely update data & lastChanged and control
ccn_todo_todoListCache[uuid][2] = newData;
ccn_todo_todoListCache[uuid][3] = result;
$("#ccn-todo-todoItem-p-" + uuid).text(newData);
$("#ccn-todo-todoItem-p-" + uuid).html(LineBreaker2Br(newData));
// switch to normal mode
ccn_todo_ChangeDisplayMode(uuid, false);

61
src/static/js/template.js Normal file
View File

@@ -0,0 +1,61 @@
var ccn_template_headerNav = undefined;
var ccn_template_calendarItem = undefined;
var ccn_template_scheduleItem = undefined;
var ccn_template_userItem = undefined;
var ccn_template_todoItem = undefined;
function ccn_template_Load() {
$.ajax({
url: $("#jsrender-tmpl-headerNav").attr('src'),
type: "GET",
async: false,
success: function (data) {
ccn_template_headerNav = $.templates(data);
}
});
switch(ccn_pages_currentPage) {
case ccn_pages_enumPages.home:
break;
case ccn_pages_enumPages.calendar:
$.ajax({
url: $("#jsrender-tmpl-calendarItem").attr('src'),
type: "GET",
async: false,
success: function (data) {
ccn_template_calendarItem = $.templates(data);
}
});
$.ajax({
url: $("#jsrender-tmpl-scheduleItem").attr('src'),
type: "GET",
async: false,
success: function (data) {
ccn_template_scheduleItem = $.templates(data);
}
});
break;
case ccn_pages_enumPages.todo:
$.ajax({
url: $("#jsrender-tmpl-todoItem").attr('src'),
type: "GET",
async: false,
success: function (data) {
ccn_template_todoItem = $.templates(data);
}
});
break;
case ccn_pages_enumPages.admin:
$.ajax({
url: $("#jsrender-tmpl-userItem").attr('src'),
type: "GET",
async: false,
success: function (data) {
ccn_template_userItem = $.templates(data);
}
});
break;
case ccn_pages_enumPages.login:
break;
}
}

View File

@@ -36,3 +36,7 @@ function GetApiToken() {
function SetApiToken(value) {
ccn_localstorageAssist_Set('ccn-token', value);
}
function LineBreaker2Br(strl) {
return $('<div>').text(strl).html().replace(/\n/g,'<br />');
}

View File

@@ -1,6 +1,6 @@
<div id="ccn-todo-todoItem-{{:uuid}}" class="todo-item card">
<div class="todo-item-words">
<p id="ccn-todo-todoItem-p-{{:uuid}}">{{>data}}</p>
<p id="ccn-todo-todoItem-p-{{:uuid}}">{{:data}}</p>
<textarea id="ccn-todo-todoItem-textarea-{{:uuid}}" class="textarea"></textarea>
</div>

View File

@@ -18,6 +18,7 @@
<script type="text/javascript" src="/static/js/i18n.js"></script>
<script type="text/javascript" src="/static/js/utils.js"></script>
<script type="text/javascript" src="/static/js/api.js"></script>
<script type="text/javascript" src="/static/js/template.js"></script>
<script type="text/javascript" src="/static/js/headerNav.js"></script>
<script type="text/javascript" src="/static/js/page/admin.js"></script>

View File

@@ -19,6 +19,7 @@
<script type="text/javascript" src="/static/js/i18n.js"></script>
<script type="text/javascript" src="/static/js/utils.js"></script>
<script type="text/javascript" src="/static/js/api.js"></script>
<script type="text/javascript" src="/static/js/template.js"></script>
<script type="text/javascript" src="/static/js/headerNav.js"></script>
<script type="text/javascript" src="/static/js/tabcontrol.js"></script>
@@ -46,16 +47,16 @@
<input class="input" type="date">
</div>
<div class="control">
<button i18n-name="ccn-calendar-jump" class="button is-info"></button>
<button i18n-name="ccn-calendar-calendar-jump" class="button is-info"></button>
</div>
</div>
</div>
<div class="level-item control">
<a i18n-name="ccn-calendar-today" class="button is-info"></a>
<a i18n-name="ccn-calendar-calendar-today" class="button is-info"></a>
</div>
<div class="level-item control">
<a i18n-name="ccn-calendar-add" class="button is-primary"></a>
<a i18n-name="ccn-calendar-calendar-add" class="button is-primary"></a>
</div>
</nav>
@@ -72,7 +73,7 @@
</div>
<div class="container" style="padding: 1.25rem; display: flex; flex-flow: column; margin-top: 1.25rem;">
<h1 i18n-name="ccn-calendar-scheduleList" class="title"></h1>
<h1 i18n-name="ccn-calendar-calendar-scheduleList" class="title"></h1>
<div id="schedule-list">
<div class="schedule-day container">
@@ -174,7 +175,7 @@
<div id="tabcontrol-panel-1-2" class="container tabcontrol-panel-1" style="margin-top: 20px;">
<div class="container" style="display: flex; flex-flow: column;">
<h1 class="title">Shared</h1>
<h1 i18n-name="ccn-calendar-shared-list" class="title"></h1>
<div class="field">
<div class="control">
<a class="button is-primary">
@@ -215,7 +216,7 @@
<div id="tabcontrol-panel-1-3" class="container tabcontrol-panel-1" style="margin-top: 20px;">
<div class="container" style="display: flex; flex-flow: column;">
<h1 class="title">Owned</h1>
<h1 i18n-name="ccn-calendar-sharing-ownedList" class="title"></h1>
<div class="control-list">
<div class="field has-addons">
<div class="control">
@@ -272,8 +273,8 @@
</div>
<div class="container" style="display: flex; flex-flow: column;">
<h1 class="title">Sharing target</h1>
<label class="label"><span>Editing: </span><span>xxx</span></label>
<h1 i18n-name="ccn-calendar-sharing-sharingTargetList" class="title"></h1>
<label class="label"><span i18n-name="ccn-calendar-sharing-sharingTargetEditing"></span><span>xxx</span></label>
<div class="control-list">
<div class="field has-addons">

View File

@@ -17,6 +17,7 @@
<script type="text/javascript" src="/static/js/i18n.js"></script>
<script type="text/javascript" src="/static/js/utils.js"></script>
<script type="text/javascript" src="/static/js/api.js"></script>
<script type="text/javascript" src="/static/js/template.js"></script>
<script type="text/javascript" src="/static/js/headerNav.js"></script>
<script type="text/javascript" src="/static/js/page/home.js"></script>

View File

@@ -17,6 +17,7 @@
<script type="text/javascript" src="/static/js/i18n.js"></script>
<script type="text/javascript" src="/static/js/utils.js"></script>
<script type="text/javascript" src="/static/js/api.js"></script>
<script type="text/javascript" src="/static/js/template.js"></script>
<script type="text/javascript" src="/static/js/headerNav.js"></script>
<script type="text/javascript" src="/static/js/page/login.js"></script>

View File

@@ -18,6 +18,7 @@
<script type="text/javascript" src="/static/js/i18n.js"></script>
<script type="text/javascript" src="/static/js/utils.js"></script>
<script type="text/javascript" src="/static/js/api.js"></script>
<script type="text/javascript" src="/static/js/template.js"></script>
<script type="text/javascript" src="/static/js/headerNav.js"></script>
<script type="text/javascript" src="/static/js/page/todo.js"></script>

View File

@@ -23,7 +23,7 @@ def GenerateUUID():
def GenerateToken(username):
s = hashlib.sha256()
s.update(username.encode('utf-8'))
s.update((str(GenerateSalt())).encode('utf-8'))
s.update(GenerateUUID().encode('utf-8'))
return s.hexdigest()
def GenerateSalt():
@@ -36,3 +36,6 @@ def ComputePasswordHashWithSalt(passwordHashed, salt):
def GetCurrentTimestamp():
return int(time.time())
def GetTokenExpireOn():
return GetCurrentTimestamp() + 60 * 60 * 24 * 2 # add 2 day from now