Compare commits

..

74 Commits

Author SHA1 Message Date
7ff1f6a2aa Correct copytoclipboard 2026-02-11 12:48:27 +01:00
4fa225900f categoory doesn't work! 2026-01-28 05:39:50 +01:00
a4fe70de60 Blog section correction RSS 2026-01-26 03:38:17 +01:00
5abbf367ab Integrate htmx and dropzone for file Section 2026-01-13 01:53:13 +01:00
0e79fdcb36 Correction of spelling mistakes 2026-01-11 23:14:42 +01:00
ff5870a33c replacement of configuration variable acquisition 2026-01-10 01:27:55 +01:00
a6ce2c5647 Choice for color theme less buggy 2026-01-10 01:26:38 +01:00
3da7b70cd1 replacement of configuration variable acquisition 2026-01-10 01:06:54 +01:00
2b04397f7d replacement of configuration variable acquisition 2026-01-10 01:04:38 +01:00
c669987ae7 replacement of configuration variable acquisition 2026-01-10 01:01:26 +01:00
1f92257ca8 replacement of configuration variable acquisition and use os.path.join for files 2026-01-10 00:58:37 +01:00
3aa76bb320 replacement of configuration variable acquisition 2026-01-10 00:11:48 +01:00
b1a1a521c2 Remove list articles if not articles 2026-01-09 19:36:22 +01:00
81aabb21ab Add pyotp dependance to installation 2026-01-09 19:19:27 +01:00
7befe1a430 Artcile in fullscreen for mobile 2026-01-06 03:32:59 +01:00
f2fea7a61f Fix css style for mobile devices 2026-01-06 03:08:38 +01:00
50db0fcce2 Coorect bug inscription 2025-12-30 22:05:15 +01:00
d6b4d64390 Display blog article on landing page 2025-12-30 21:37:15 +01:00
9baeb3736b Display blog articles on landing page 2025-12-30 21:36:26 +01:00
51b016aa69 Rework invitation system and add qrcode 2025-12-27 07:16:39 +01:00
5e54c1d383 Add Draft categorie to new_article 2025-12-24 15:55:07 +01:00
7d4a374d3e rework blog theme 2025-12-22 17:51:43 +01:00
9c36563ffd Escape form for blog 2025-12-22 16:23:32 +01:00
7bd8614359 Remove link on title and correct readmore color 2025-12-22 15:08:47 +01:00
6984d7b04f work on blog link CSS 2025-12-22 14:26:59 +01:00
c8633335f3 Add font generic for blog 2025-12-22 13:47:34 +01:00
eafb8cac70 Add padding in simpleMDE edior preview 2025-12-22 13:14:57 +01:00
496d0bd059 Add border color to simpleMDE editor 2025-12-22 13:09:02 +01:00
385fe8110e Set correct option in edit article 2025-12-22 12:44:29 +01:00
af5c93a1df commit too fast ... missing return value 2025-12-22 12:16:57 +01:00
193fc29c82 Replace variable by app.config for mail_server enable 2025-12-22 12:14:08 +01:00
e3100e77b2 Add function set_mail_hostname 2025-12-22 12:11:13 +01:00
481e179833 Fix link and color in theme blogs 2025-12-22 01:58:19 +01:00
3266b02be7 Add user theme in list articles 2025-12-22 01:57:28 +01:00
89400555ed Work on list articles for make it more readable 2025-12-22 01:16:17 +01:00
b47999b1b6 Enter text more userfriendly 2025-12-22 01:02:45 +01:00
8b918dd2a7 Fix creation directory and static blog css file 2025-12-22 01:02:01 +01:00
b4a49b406c Fix name of theme blog 2025-12-22 01:01:19 +01:00
4f89b16a1c Fix bugs for blog theme 2025-12-22 01:00:43 +01:00
0803124c14 Modify utils log code for more clarity 2025-12-21 19:31:39 +01:00
kitoy
685502aec7 Modifiy wsgi for listen the Address and port specified in config.py 2025-12-21 19:29:49 +01:00
kitoy
6a4089243a Fix bug in databaseinit 2025-12-21 19:28:24 +01:00
kitoy
9a7f22f9eb Fix page lost_password 2025-12-21 19:27:56 +01:00
kitoy
3e1c9439ec Remove Variable Global incohérence in code 2025-12-21 19:27:16 +01:00
kitoy
5c1dfb1fee Add bog theme in interface 2025-12-21 19:25:42 +01:00
kitoy
c4b9eaf60a Add blog theme ... just for watch if that work 2025-12-21 19:24:06 +01:00
kitoy
f9e873f1e7 Add options in config.py file 2025-12-21 19:07:15 +01:00
Charlie Root
0ef6950a69 Fix bugs in mailsection 2025-12-21 18:38:46 +01:00
Charlie Root
c2068c1d45 Modify explain files sections 2025-12-21 18:37:09 +01:00
Charlie Root
6201e71d2c Modify landing page can personnalize the server name and make a little description 2025-12-21 18:36:19 +01:00
Charlie Root
66eff29692 remove os.system from inscription 2025-12-21 18:32:39 +01:00
kitoy
76e3cfb870 change making url for copy link files 2025-12-17 14:20:11 +01:00
3bcfc43ed5 Add Copy link button 2025-12-17 08:51:51 +01:00
kitoy
c3da453425 Work on frontend and fix the theme_switcher bug 2025-12-16 08:19:34 +01:00
kitoy
c4e1318722 fix mistakes in templates 2025-12-16 08:18:04 +01:00
kitoy
899759a2fb fix bugs in filesuploads 2025-12-16 08:14:14 +01:00
kitoy
e90834725c fix bugs in personnalize blog view 2025-12-16 08:13:20 +01:00
kitoy
89bfce21b6 Some fix in pywallter.py to run with gevent 2025-12-16 07:52:12 +01:00
f67ccfdd19 Fix for launch pywallter 2025-12-15 11:32:19 +01:00
1e4d097425 Fix for lauching pywallter 2025-12-15 11:30:55 +01:00
417bbe021f Somes FIx for datamigration and config.example 2025-12-15 11:27:30 +01:00
a985840bc8 Reworh the messagerie page 2025-12-15 10:09:25 +01:00
2f61155040 Work on mail alias 2025-12-13 04:47:17 +01:00
a5ba0b90bb Work on blog
rewrite entire blog section
2025-12-13 02:30:50 +01:00
68d798845d Work on blog
rewrite entire blog section
2025-12-13 02:29:34 +01:00
304a2f1a45 Correct variables in mailbox 2025-12-10 01:55:01 +01:00
ed7fd0d0f5 Escape argument for rmalias 2025-12-10 01:54:03 +01:00
d887acd164 addition of an author's blog consultation 2025-12-10 01:16:59 +01:00
603c19de26 Rework Files section
Work on files section to make it more user-friendly
2025-12-08 18:00:06 +01:00
57241b843b Rework blog section
Wrok in bloc sectionn
2025-12-05 23:20:36 +01:00
0e282894a6 Enable check directory 2025-12-03 22:29:44 +01:00
e14677e701 Add login_required decorator 2025-12-03 22:21:49 +01:00
4288eca551 Add login_required decorator 2025-12-01 02:19:46 +01:00
57c2fb4ce9 Add login_required decorator
Suppression des tous les tests en debut de chanque fonction pour
vérifier sur l'utilisateur est connecté. Maintenant, il suffit d'ajouter
la ligne @login_required en dessous des routes pour spécifier qu'il faut
être logger pour accéder à cette route
2025-12-01 01:53:32 +01:00
60 changed files with 3214 additions and 1228 deletions

9
.gitignore vendored
View File

@@ -1,8 +1,11 @@
__pycache__/
base.db
*__pycache__/
*.db
log.txt
config.py
users/
sys
*~
#*
*#*
.*
base.db.new
public/*

View File

@@ -13,7 +13,7 @@ on pourrait ajoute de la double authentification mais bon ...
### Sur OpenBSD (7.1)
Il faut installer FLask et quelques dépendances
pkg_add py3-werkzeug py3-jinja2 py3-Pillow py3-wtforms py3-flask-wtf py3-flask \
py3-bcrypt py3-markdown py3-gevent py3-zopeinterface py3-qrcode
py3-bcrypt py3-markdown py3-gevent py3-zopeinterface py3-qrcode py3-pyotp
Il reste malheuresment une dépendances sur flask-bcrypt a installé avec pip:
@@ -21,7 +21,7 @@ Il reste malheuresment une dépendances sur flask-bcrypt a installé avec pip:
### Sur Debian
apt install python3-flask python3-flask-bcrypt python3-wtforms python3-pil python3-markdown \
python3-gevent python3-zope.interface python3-qrcode
python3-gevent python3-zope.interface python3-qrcode python3-pyotp
## Démarrer le programme :

View File

@@ -1,14 +1,22 @@
# L'adresse de base de votre site.
# L'Adresse de base de votre site.
# example BASE_URL="https://example.com"
# BASE_URL="http://localhost:8000/" # si vous lancez pywallter avec flask run"
# Addresse et port sur lequel doit ecouté le service pywallter
HOST="127.0.0.1" #ADDRESS IP DOMAIN
PORT="5001" # A Free port who
BASE_URL="https://example.com/"
# Essentiels pour les cookies
BASE_URL="https://your-domain.com/"
# Essentiels pour authentifier les cookies changer cette valeur par une phrase de passe ou une longue suite de caractères
SECRET_KEY="CHANGE-ME"
# Dossier où seront stocker les fichiers
TITLE_SERVER="Un titre pour votre serveur "
DESC_SERVER="Une description"
BACKUP_TIME="15 jours"
# Dossier ou seront stocker les fichiers
DOSSIER_APP = "./users/"
DOSSIER_PUBLIC = "./public/"
# Fichiers sqlite
DATABASE = "./base.db"
@@ -20,12 +28,31 @@ EXT_IMG= {'.jpg', '.JPG', '.png', '.PNG', '.gif', '.GIF', '.bmp', '.BMP', '.jpeg
# XMPP = True => Le service est installé et lancer
# XMMP = False => Le service est désactivé
XMPP_SERVER = False
# URL for XMPP Web client
XMPP_WEBSERVICE = None # Si vous vous voulez proposer une interface web client pour votre service XMPP
# Service Mail
# MAIL_SERVER = True => Le service est installé et lancer
# MAIL_SERVER = False => Le service est désactivé
MAIL_SERVER = False
MAIL_SERVER = True
MAIL_DOMAIN="" # Votre domain mail . Si vous voulez fournir des adresse @votredomaine.net
IMAP_ADDRESS = "example.net" # Adresse du serveur IMAP
SMTP_ADDRESS = "example.net" # Adresse du serveur SMTP
# URL for email Web client
MAIL_WEBSERVICE = None # Si vous voulez proposer une interface web pour consulter les mails sur votre serveur
# Doas or sudo
SETUID='doas'
# Sending email for account recovery
SMTP_SERVER="mx.example.net"
SMTP_PORT="25"
SMTP_USER="smtp_user"
SMTP_PASSWD="your password"
SENDER_ADDRESS="" # l'adresse mail qui sera afficher lors d'un envoi de mail de votre application
#Activer les inscriptions
# cette option va disparaitre car elle n'a pas vraiment de sens ... si ce n'est de pouvoir fermer les inscriptions
SIGNIN_ENABLE=True

View File

@@ -18,7 +18,8 @@ from views.logs import logs
from views.loginlogout import loginlogout
from views.gallery import mygallery
from tools.databaseinit import init_db, init_dir, db_migrate
from tools.databaseinit import init_db, check_directories, db_migrate, check_directories
import glob, os, sys, time
@@ -26,22 +27,25 @@ app = Flask( 'pywallter' )
app.config.from_pyfile('config.py')
bcrypt = Bcrypt(app)
init_db()
db_migrate()
if init_dir():
print ("Le repertoire des utilisateurs a été créer")
#### Variables Globales #########################################################################
DOSSIER_PERSO= app.config['DOSSIER_APP']
DATABASE= app.config['DATABASE']
extensionimg = app.config['EXT_IMG']
MAIL_SERVER = app.config['MAIL_SERVER']
XMPP_SERVER = app.config['XMPP_SERVER']
#################################################################################################
init_db(DATABASE)
check_directories(DOSSIER_PERSO)
db_migrate(DATABASE)
xmpp_server_not_installed = system('whereis prosodyctl')
mail_server_not_installed = system('whereis set_mail_alias') + system('whereis set_mail_passwd') + \
system('whereis dovecot') + system('whereis smtpd')
@@ -77,8 +81,11 @@ def create_app():
app = Flask( 'pywallter' )
app.config.from_pyfile('config.py')
bcrypt = Bcrypt(app)
database= app.config['DATABASE']
xmpp_server_not_installed = system('whereis prosodyctl')
mail_server_not_installed = system('whereis set_mail_alias') + system('whereis set_mail_passwd') + system('whereis smtpctl')
folder_users= app.config['DOSSIER_APP']
extensionimg = app.config['EXT_IMG']
if XMPP_SERVER and xmpp_server_not_installed :
print ("Vous avez activé la prise en charge du protocole XMPP mais prosody n'est pas installé")
@@ -92,21 +99,19 @@ def create_app():
print(" Pywallter ne peut démarrer en l'état désactivé la fonction Mail ou/et installé et confiurer les programme Dovecot et opensmtpd ainsi que les scripts pour gérer les comptes mails")
exit(1)
init_db()
db_migrate()
if init_dir():
init_db(database)
db_migrate(database)
if check_directories(folder_users):
print ("Le repertoire des utilisateurs a été créer")
DOSSIER_PERSO= app.config['DOSSIER_APP']
extensionimg = app.config['EXT_IMG']
app.register_blueprint(inscription)
app.register_blueprint(blog)
app.register_blueprint(filesupload)
app.register_blueprint(mymailbox)
app.register_blueprint(profil)
app.register_blueprint(logs)
app.register_blueprint(loginlogout)

237
static/blog-blanc&noir.css Normal file
View File

@@ -0,0 +1,237 @@
/* kitoy <kitoy__at__kitoy.me> */
:root
{
--color-background: #fefefe;
--color-title: #010101;
--color-text: #0f0f0f;
--color-link: #010101;
--font-emoji : "Apple Color Emoji","Segoe UI Emoji","Segoe UI Symbol","Noto Color Emoji";
--font-basic : system-ui,"Segoe UI",Roboto,Oxygen,Ubuntu,Cantarell,Helvetica,Arial,"Helvetica Neue",sans-serif;
}
html{
height: 100%;
width: 80%;
margin-left: 10%;
}
body {
color: var(--color-text);
background-color: var(--color-background);
font-family: var(--font-basic);
font-weight: 400;
}
h1, h2, h3, h4, h5{
color: var(--color-title);
}
hr {
color: var(--color-text);
}
* {-moz-box-sizing: border-box; box-sizing: border-box;}
a {
box-shadow: inset 0 0 0 0 var(--color-text);
color: --color-link;
padding: 0 .25rem;
margin: 0 -.25rem;
transition: color .3s ease-in-out, box-shadow .3s ease-in-out;
}
a:hover {
color: var(--color-background);
box-shadow: inset 300px 0 0 0 var(--color-text);
text-decoration: none;
}
a {
color: var(--color-link);
text-decoration: underline 2px;
font-weight: 700;
line-height: 1.5;
}
.date {
margin-bottom: 0;
}
.slug {
//margin-left: 1rem;
text-align: left;
margin-bottom: 2rem;
}
.readmore {
text-align: right;
}
pre {
white-space: pre-wrap; /* css-3 */
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+ */
}
ul {
list-style-type: none;
margin: 0;
padding: 0;
}
.articles {
max-width: 80%;
margin-left: auto;
margin-right: auto;
margin-top: 5rem;
margin-bottom: 3rem;
padding:0.5em;
border: 7px double;
border-color: var(--text-color);
border-radius: 10px 10px 10px 10px;
line-height: 1.5;
letter-spacing: 0.1vw;
text-align: justify;
}
.articles ul {
list-style-type: disc;
margin: 5vw;
padding-top: 1vw;
padding-bottom: 1vw;
padding-left: 1.5vw;
}
.head-article {
text-align: center;
}
.text-article p {
padding-left: 2vw;
padding-right:2vw;
}
.text-article h2, h3, h4, h5 {
padding-left: 1vw;
padding-right:1vw;
}
.index {
text-align: left;
}
.articles img {
display: block;
margin-left: auto;
margin-right: auto;
width: 50%;
margin-bottom: 2vw;
}
.articles .description {
font-weight: 300;
font-style: italic;
font-size: 1.5vw;
padding-bottom: 30px;
color: ;
}
.pagination a {
border: 1px solid;
border-color: $color_title;
padding: 3px;
font-size: 13px;
}
.center {
margin: auto;
width: 50%;
padding: 10px;
}
.contact{
text-align:center;
}
footer {
position: relative;
bottom:0px;
width: 100%;
text-align:center;
padding-bottom:1vw;
}
@media only screen and (max-width: 980px)
{
.articles .description {
font-size: 4vw;
}
.articles {
max-width: 100%;
margin-left: auto;
position: relative;
font-size: 3.5vw;
}
footer {
font-size: 3vw;
}
.articles img {
display: block;
margin-left: auto;
margin-right: auto;
width: 80%;
}
}
@media only screen and (max-width: 980px){
html{
height: 100%;
width: 100%;
margin: 0;
}
}
@media only screen and (max-width: 768px)
{
.h1 {
font-size: 5vw;
}
.icons img {
width: 7vw;
height: 7vw;
margin: 20px;
display:inline-block;
align-items:center;
}
}

237
static/blog-noir&blanc.css Normal file
View File

@@ -0,0 +1,237 @@
/* kitoy <kitoy__at__kitoy.me> */
:root
{
--color-background: #010101;
--color-title: #fefefe;
--color-text: #fdfdfd;
--color-link: #fefefe;
--font-emoji : "Apple Color Emoji","Segoe UI Emoji","Segoe UI Symbol","Noto Color Emoji";
--font-basic : system-ui,"Segoe UI",Roboto,Oxygen,Ubuntu,Cantarell,Helvetica,Arial,"Helvetica Neue",sans-serif;
}
html{
height: 100%;
width: 80%;
margin-left: 10%;
}
body {
color: var(--color-text);
background-color: var(--color-background);
font-family: var(--font-basic);
font-weight: 400;
}
h1, h2, h3, h4, h5{
color: var(--color-title);
}
hr {
color: var(--color-text);
}
* {-moz-box-sizing: border-box; box-sizing: border-box;}
a {
box-shadow: inset 0 0 0 0 var(--color-text);
color: --color-link;
padding: 0 .25rem;
margin: 0 -.25rem;
transition: color .3s ease-in-out, box-shadow .3s ease-in-out;
}
a:hover {
color: var(--color-background);
box-shadow: inset 300px 0 0 0 var(--color-text);
text-decoration: none;
}
a {
color: var(--color-link);
text-decoration: underline 2px;
font-weight: 700;
line-height: 1.5;
}
.date {
margin-bottom: 0;
}
.slug {
//margin-left: 1rem;
text-align: left;
margin-bottom: 2rem;
}
.readmore {
text-align: right;
}
pre {
white-space: pre-wrap; /* css-3 */
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+ */
}
ul {
list-style-type: none;
margin: 0;
padding: 0;
}
.articles {
max-width: 80%;
margin-left: auto;
margin-right: auto;
margin-top: 5rem;
margin-bottom: 3rem;
padding:0.5em;
border: 7px double;
border-color: var(--text-color);
border-radius: 10px 10px 10px 10px;
line-height: 1.5;
letter-spacing: 0.1vw;
text-align: justify;
}
.articles ul {
list-style-type: disc;
margin: 5vw;
padding-top: 1vw;
padding-bottom: 1vw;
padding-left: 1.5vw;
}
.head-article {
text-align: center;
}
.text-article p {
padding-left: 2vw;
padding-right:2vw;
}
.text-article h2, h3, h4, h5 {
padding-left: 1vw;
padding-right:1vw;
}
.index {
text-align: left;
}
.articles img {
display: block;
margin-left: auto;
margin-right: auto;
width: 50%;
margin-bottom: 2vw;
}
.articles .description {
font-weight: 300;
font-style: italic;
font-size: 1.5vw;
padding-bottom: 30px;
color: ;
}
.pagination a {
border: 1px solid;
border-color: $color_title;
padding: 3px;
font-size: 13px;
}
.center {
margin: auto;
width: 50%;
padding: 10px;
}
.contact{
text-align:center;
}
footer {
position: relative;
bottom:0px;
width: 100%;
text-align:center;
padding-bottom:1vw;
}
@media only screen and (max-width: 980px)
{
.articles .description {
font-size: 4vw;
}
.articles {
max-width: 100%;
margin-left: auto;
position: relative;
font-size: 3.5vw;
}
footer {
font-size: 3vw;
}
.articles img {
display: block;
margin-left: auto;
margin-right: auto;
width: 80%;
}
}
@media only screen and (max-width: 980px){
html{
height: 100%;
width: 100%;
margin: 0;
}
}
@media only screen and (max-width: 768px)
{
.h1 {
font-size: 5vw;
}
.icons img {
width: 7vw;
height: 7vw;
margin: 20px;
display:inline-block;
align-items:center;
}
}

238
static/blog-orange&noir.css Normal file
View File

@@ -0,0 +1,238 @@
/* kitoy <kitoy__at__kitoy.me> */
:root
{
--color-background: #010101;
--color-title: #ee794c;
--color-text: #fdfdfd;
--color-link: #ee794c;
--font-emoji : "Apple Color Emoji","Segoe UI Emoji","Segoe UI Symbol","Noto Color Emoji";
--font-basic : system-ui,"Segoe UI",Roboto,Oxygen,Ubuntu,Cantarell,Helvetica,Arial,"Helvetica Neue",sans-serif;
}
html{
height: 100%;
width: 80%;
margin-left: 10%;
}
body {
color: var(--color-text);
background-color: var(--color-background);
font-family: var(--font-basic);
font-weight: 400;
}
h1, h2, h3, h4, h5{
color: var(--color-title);
}
hr {
color: var(--color-text);
}
* {
-moz-box-sizing: border-box; box-sizing: border-box;
}
a {
box-shadow: inset 0 0 0 0 var(--color-text);
color: --color-link;
padding: 0 .25rem;
margin: 0 -.25rem;
transition: color .3s ease-in-out, box-shadow .3s ease-in-out;
}
a:hover {
color: var(--color-background);
box-shadow: inset 300px 0 0 0 var(--color-text);
text-decoration: none;
}
a {
color: var(--color-link);
text-decoration: underline 2px;
font-weight: 700;
line-height: 1.5;
}
.date {
margin-bottom: 0;
}
.slug {
//margin-left: 1rem;
text-align: left;
margin-bottom: 2rem;
}
.readmore {
text-align: right;
}
pre {
white-space: pre-wrap; /* css-3 */
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+ */
}
ul {
list-style-type: none;
margin: 0;
padding: 0;
}
.articles {
max-width: 80%;
margin-left: auto;
margin-right: auto;
margin-top: 5rem;
margin-bottom: 3rem;
padding:0.5em;
border: 7px double;
border-color: var(--text-color);
border-radius: 10px 10px 10px 10px;
line-height: 1.5;
letter-spacing: 0.1vw;
text-align: justify;
}
.articles ul {
list-style-type: disc;
margin: 5vw;
padding-top: 1vw;
padding-bottom: 1vw;
padding-left: 1.5vw;
}
.head-article {
text-align: center;
}
.text-article p {
padding-left: 2vw;
padding-right:2vw;
}
.text-article h2, h3, h4, h5 {
padding-left: 1vw;
padding-right:1vw;
}
.index {
text-align: left;
}
.articles img {
display: block;
margin-left: auto;
margin-right: auto;
width: 50%;
margin-bottom: 2vw;
}
.articles .description {
font-weight: 300;
font-style: italic;
font-size: 1.5vw;
padding-bottom: 30px;
color: ;
}
.pagination a {
border: 1px solid;
border-color: $color_title;
padding: 3px;
font-size: 13px;
}
.center {
margin: auto;
width: 50%;
padding: 10px;
}
.contact{
text-align:center;
}
footer {
position: relative;
bottom:0px;
width: 100%;
text-align:center;
padding-bottom:1vw;
}
@media only screen and (max-width: 980px)
{
.articles .description {
font-size: 4vw;
}
.articles {
max-width: 100%;
margin-left: auto;
position: relative;
font-size: 3.5vw;
}
footer {
font-size: 3vw;
}
.articles img {
display: block;
margin-left: auto;
margin-right: auto;
width: 80%;
}
}
@media only screen and (max-width: 980px){
html
{
height: 100%;
width: 100%;
margin: 0;
}
}
@media only screen and (max-width: 768px)
{
.h1 {
font-size: 5vw;
}
.icons img {
width: 7vw;
height: 7vw;
margin: 20px;
display:inline-block;
align-items:center;
}
}

237
static/blog-orangina.css Normal file
View File

@@ -0,0 +1,237 @@
/* kitoy <kitoy__at__kitoy.me> */
:root
{
--color-background: #1e227b;
--color-title: #e9d43a;
--color-text: #fdfdfd;
--color-link: #e9d43a;
--font-emoji : "Apple Color Emoji","Segoe UI Emoji","Segoe UI Symbol","Noto Color Emoji";
--font-basic : system-ui,"Segoe UI",Roboto,Oxygen,Ubuntu,Cantarell,Helvetica,Arial,"Helvetica Neue",sans-serif;
}
html{
height: 100%;
width: 80%;
margin-left: 10%;
}
body {
color: var(--color-text);
background-color: var(--color-background);
font-family: var(--font-basic);
font-weight: 400;
}
h1, h2, h3, h4, h5{
color: var(--color-title);
}
hr {
color: var(--color-text);
}
* {-moz-box-sizing: border-box; box-sizing: border-box;}
a {
box-shadow: inset 0 0 0 0 var(--color-text);
color: --color-link;
padding: 0 .25rem;
margin: 0 -.25rem;
transition: color .3s ease-in-out, box-shadow .3s ease-in-out;
}
a:hover {
color: var(--color-background);
box-shadow: inset 300px 0 0 0 var(--color-text);
text-decoration: none;
}
a {
color: var(--color-link);
text-decoration: underline 2px;
font-weight: 700;
line-height: 1.5;
}
.date {
margin-bottom: 0;
}
.slug {
//margin-left: 1rem;
text-align: left;
margin-bottom: 2rem;
}
.readmore {
text-align: right;
}
pre {
white-space: pre-wrap; /* css-3 */
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+ */
}
ul {
list-style-type: none;
margin: 0;
padding: 0;
}
.articles {
max-width: 80%;
margin-left: auto;
margin-right: auto;
margin-top: 5rem;
margin-bottom: 3rem;
padding:0.5em;
border: 7px double;
border-color: var(--text-color);
border-radius: 10px 10px 10px 10px;
line-height: 1.5;
letter-spacing: 0.1vw;
text-align: justify;
}
.articles ul {
list-style-type: disc;
margin: 5vw;
padding-top: 1vw;
padding-bottom: 1vw;
padding-left: 1.5vw;
}
.head-article {
text-align: center;
}
.text-article p {
padding-left: 2vw;
padding-right:2vw;
}
.text-article h2, h3, h4, h5 {
padding-left: 1vw;
padding-right:1vw;
}
.index {
text-align: left;
}
.articles img {
display: block;
margin-left: auto;
margin-right: auto;
width: 50%;
margin-bottom: 2vw;
}
.articles .description {
font-weight: 300;
font-style: italic;
font-size: 1.5vw;
padding-bottom: 30px;
color: ;
}
.pagination a {
border: 1px solid;
border-color: $color_title;
padding: 3px;
font-size: 13px;
}
.center {
margin: auto;
width: 50%;
padding: 10px;
}
.contact{
text-align:center;
}
footer {
position: relative;
bottom:0px;
width: 100%;
text-align:center;
padding-bottom:1vw;
}
@media only screen and (max-width: 980px)
{
.articles .description {
font-size: 4vw;
}
.articles {
max-width: 100%;
margin-left: auto;
position: relative;
font-size: 3.5vw;
}
footer {
font-size: 3vw;
}
.articles img {
display: block;
margin-left: auto;
margin-right: auto;
width: 80%;
}
}
@media only screen and (max-width: 980px){
html{
height: 100%;
width: 100%;
margin: 0;
}
}
@media only screen and (max-width: 768px)
{
.h1 {
font-size: 5vw;
}
.icons img {
width: 7vw;
height: 7vw;
margin: 20px;
display:inline-block;
align-items:center;
}
}

View File

@@ -2,8 +2,13 @@
:root
{
--color-text: #fdfdfddd;
--color-background: #202020;
--color-title: #fdfdfd;
--color-text: #dddddd;
--color-link: #fdfdfd;
--font-emoji : "Apple Color Emoji","Segoe UI Emoji","Segoe UI Symbol","Noto Color Emoji";
--font-basic : system-ui,"Segoe UI",Roboto,Oxygen,Ubuntu,Cantarell,Helvetica,Arial,"Helvetica Neue",sans-serif;
}
html{
@@ -17,21 +22,43 @@
body {
color: var(--color-text);
background-color: var(--color-background);
font-family: var(--font-basic);
font-weight: 400;
}
h1, h2, h3, h4, h5{
color: var(--color-title);
}
hr {
color: var(--color-text);
}
* {-moz-box-sizing: border-box; box-sizing: border-box;}
a {
box-shadow: inset 0 0 0 0 var(--color-text);
color: --color-link;
padding: 0 .25rem;
margin: 0 -.25rem;
transition: color .3s ease-in-out, box-shadow .3s ease-in-out;
}
a {
color: var(--color-text);
text-decoration: none;
}
a:hover {
color: var(--color-background);
box-shadow: inset 300px 0 0 0 var(--color-text);
text-decoration: none;
}
a {
color: var(--color-link);
text-decoration: underline 2px;
font-weight: 700;
line-height: 1.5;
}
.date {
margin-bottom: 0;
}
@@ -46,17 +73,6 @@
text-align: right;
}
.readmore a {
color: var(--color-text);
text-decoration: underline;
}
.copyleft {
display:inline-block;
transform: rotate(180deg);
padding-bottom: -15px;
}
pre {
white-space: pre-wrap; /* css-3 */
@@ -97,17 +113,29 @@
padding-left: 1.5vw;
}
.titre {
.head-article {
text-align: center;
}
.text-article p {
padding-left: 2vw;
padding-right:2vw;
}
.text-article h2, h3, h4, h5 {
padding-left: 1vw;
padding-right:1vw;
}
.index {
text-align: left;
}
.articles img {
.articles img {
display: block;
margin-left: auto;
margin-right: auto;
@@ -125,6 +153,7 @@
}
.pagination a {
border: 1px solid;
border-color: $color_title;
@@ -147,8 +176,10 @@
bottom:0px;
width: 100%;
text-align:center;
padding-bottom:1vw;
}
@media only screen and (max-width: 980px)
{
@@ -157,7 +188,7 @@
}
.articles {
margin-left: 0;
margin-left: auto;
position: relative;
font-size: 3.5vw;

View File

@@ -330,3 +330,78 @@
margin:5px;
}
/* Share */
.social-share
{
display: inline-block;
width: 20px;
height: 20px;
--svg: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 24 24'%3E%3Cg fill='none' stroke='%23000' stroke-linecap='round' stroke-linejoin='round' stroke-width='2'%3E%3Ccircle cx='18' cy='5' r='3'/%3E%3Ccircle cx='6' cy='12' r='3'/%3E%3Ccircle cx='18' cy='19' r='3'/%3E%3Cpath d='m8.59 13.51l6.83 3.98m-.01-10.98l-6.82 3.98'/%3E%3C/g%3E%3C/svg%3E");
background-color: currentColor;
-webkit-mask-image: var(--svg);
mask-image: var(--svg);
-webkit-mask-repeat: no-repeat;
mask-repeat: no-repeat;
-webkit-mask-size: 100% 100%;
mask-size: 100% 100%;
}
.share {
display: inline-block;
width: 30px;
height: 30px;
--svg: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 24 24'%3E%3Cpath fill='%23000' fill-rule='evenodd' d='M18.8 8.2H16V7h3.001c.55 0 .999.446.999.996v13.008a1 1 0 0 1-.996.996H4.996A1 1 0 0 1 4 21.004V7.996A1 1 0 0 1 4.999 7H8v1.2H5.2v12.6h13.6zm-6.2-3.938V13.5h-1.2V4.262L9.313 6.349L8.464 5.5l2.829-2.828a1 1 0 0 1 1.414 0L15.536 5.5l-.849.849z'/%3E%3C/svg%3E");
background-color: currentColor;
-webkit-mask-image: var(--svg);
mask-image: var(--svg);
-webkit-mask-repeat: no-repeat;
mask-repeat: no-repeat;
-webkit-mask-size: 100% 100%;
mask-size: 100% 100%;
}
.unshare
{
display: inline-block;
width: 30px;
height: 30px;
--svg: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 24 24'%3E%3Cg fill='none' stroke='%23000' stroke-linecap='round' stroke-linejoin='round' stroke-width='2'%3E%3Ccircle cx='18' cy='5' r='3'/%3E%3Ccircle cx='6' cy='12' r='3'/%3E%3Ccircle cx='18' cy='19' r='3'/%3E%3Cpath d='m8.59 13.51l6.83 3.98m-.01-10.98l-6.82 3.98'/%3E%3C/g%3E%3C/svg%3E");
background-color: currentColor;
-webkit-mask-image: var(--svg);
mask-image: var(--svg);
-webkit-mask-repeat: no-repeat;
mask-repeat: no-repeat;
-webkit-mask-size: 100% 100%;
mask-size: 100% 100%;
}
.delete
{
display: inline-block;
width: 30px;
height: 30px;
--svg: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 24 24'%3E%3Cpath fill='%23000' fill-rule='evenodd' d='m6.774 6.4l.812 13.648a.8.8 0 0 0 .798.752h7.232a.8.8 0 0 0 .798-.752L17.226 6.4zm11.655 0l-.817 13.719A2 2 0 0 1 15.616 22H8.384a2 2 0 0 1-1.996-1.881L5.571 6.4H3.5v-.7a.5.5 0 0 1 .5-.5h16a.5.5 0 0 1 .5.5v.7zM14 3a.5.5 0 0 1 .5.5v.7h-5v-.7A.5.5 0 0 1 10 3zM9.5 9h1.2l.5 9H10zm3.8 0h1.2l-.5 9h-1.2z'/%3E%3C/svg%3E");
background-color: currentColor;
-webkit-mask-image: var(--svg);
mask-image: var(--svg);
-webkit-mask-repeat: no-repeat;
mask-repeat: no-repeat;
-webkit-mask-size: 100% 100%;
mask-size: 100% 100%;
}
.copy-link{
display: inline-block;
width: 30px;
height: 30px;
--svg: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 24 24'%3E%3Cg fill='none' stroke='%23000' stroke-linecap='round' stroke-linejoin='round' stroke-width='2'%3E%3Crect width='8' height='4' x='8' y='2' rx='1' ry='1'/%3E%3Cpath d='M8 4H6a2 2 0 0 0-2 2v14a2 2 0 0 0 2 2h12a2 2 0 0 0 2-2v-2M16 4h2a2 2 0 0 1 2 2v4m1 4H11'/%3E%3Cpath d='m15 10l-4 4l4 4'/%3E%3C/g%3E%3C/svg%3E");
background-color: currentColor;
-webkit-mask-image: var(--svg);
mask-image: var(--svg);
-webkit-mask-repeat: no-repeat;
mask-repeat: no-repeat;
-webkit-mask-size: 100% 100%;
mask-size: 100% 100%;
}

View File

@@ -16,17 +16,19 @@ body
visibility: hidden;
}
.hidden{
.hidden {
visibility: hidden;
}
header {
grid-area: header;
width: 100%;
}
main
body > main
{
grid-area: main;
grid-area: main;
width: 80%;
text-align: justify;
overflow-y: scroll;
}
@@ -52,6 +54,36 @@ main > nav
color: var(--pico-color-green-500);
}
@media (min-width: 1200px)
{
.list-articles{
padding-top: 2vw;
display: inline-flex;
gap:30px;
flex-wrap: wrap;
}
article {
text-align: center;
width: 30vw;
height: 35vw;
}
article > header {
height: 15vw;
}
article > .subtitle {
height: 10vw;
}
article footer{
display: block;
height: 5vw;
}
}
@media only screen and (max-width: 600px)
{
body {

View File

@@ -0,0 +1,97 @@
.editor-toolbar a {
display: inline-block;
text-align: center;
text-decoration: none !important;
color: var(--pico-primary) !important;
width: 30px;
height: 30px;
margin: 0;
border: 1px solid transparent;
border-radius: 3px;
cursor: pointer;
}
.editor-toolbar.fullscreen {
width: 100%;
height: 50px;
overflow-x: auto;
overflow-y: hidden;
white-space: nowrap;
padding-top: 10px;
padding-bottom: 10px;
box-sizing: border-box;
background: var(--pico-background-color);
border: 0;
position: fixed;
top: 0;
left: 0;
opacity: 1;
z-index: 9;
}
.CodeMirror {
background-color: var(--pico-background-color);
color: var(--pico-color) !important;
border-color: var(--pico-primary-hover) !important;
}
.editor-preview-active{
background: var(--pico-background-color);
color: var(--pico-color) !important;
padding-right : 4vw;
padding-left: 4vw;
}
.editor-preview-active-side {
background: var(--pico-background-color);
color: var(--pico-color) !important;
position: fixed;
bottom: 0;
width: 50%;
top: 50px;
right: 0;
z-index: 9;
border: 2px solid;
border-color: var(--pico-primary-hover) !important;
}
.CodeMirror .CodeMirror-code .cm-tag {
color: #63a35c;
}
.CodeMirror .CodeMirror-code .cm-attribute {
color: #795da3;
}
.CodeMirror .CodeMirror-code .cm-string {
color: #183691;
}
.CodeMirror .CodeMirror-selected {
background: var(--pico-text-selection-color) !important;
}
.CodeMirror .CodeMirror-code .cm-link {
color: #7f8c8d;
}
.CodeMirror .CodeMirror-code .cm-url {
color: #aab2b3;
}
.CodeMirror-cursor {
border-left-color: var(--pico-primary);
}
.editor-statusbar .lines:before {
color: var(--pico-primary) !important;
content: 'lignes: '
}
.editor-statusbar .words:before {
color: var(--pico-primary) !important;
content: 'Mots: '
}

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

1
static/vendors/dropzone/dropzone.css vendored Normal file
View File

@@ -0,0 +1 @@
@keyframes passing-through{0%{opacity:0;transform:translateY(40px)}30%,70%{opacity:1;transform:translateY(0px)}100%{opacity:0;transform:translateY(-40px)}}@keyframes slide-in{0%{opacity:0;transform:translateY(40px)}30%{opacity:1;transform:translateY(0px)}}@keyframes pulse{0%{transform:scale(1)}10%{transform:scale(1.1)}20%{transform:scale(1)}}.dropzone,.dropzone *{box-sizing:border-box}.dropzone{min-height:150px;border:1px solid rgba(0,0,0,.8);border-radius:5px;padding:20px 20px}.dropzone.dz-clickable{cursor:pointer}.dropzone.dz-clickable *{cursor:default}.dropzone.dz-clickable .dz-message,.dropzone.dz-clickable .dz-message *{cursor:pointer}.dropzone.dz-started .dz-message{display:none}.dropzone.dz-drag-hover{border-style:solid}.dropzone.dz-drag-hover .dz-message{opacity:.5}.dropzone .dz-message{text-align:center;margin:3em 0}.dropzone .dz-message .dz-button{background:none;color:inherit;border:none;padding:0;font:inherit;cursor:pointer;outline:inherit}.dropzone .dz-preview{position:relative;display:inline-block;vertical-align:top;margin:16px;min-height:100px}.dropzone .dz-preview:hover{z-index:1000}.dropzone .dz-preview:hover .dz-details{opacity:1}.dropzone .dz-preview.dz-file-preview .dz-image{border-radius:20px;background:#999;background:linear-gradient(to bottom, #eee, #ddd)}.dropzone .dz-preview.dz-file-preview .dz-details{opacity:1}.dropzone .dz-preview.dz-image-preview{background:#fff}.dropzone .dz-preview.dz-image-preview .dz-details{transition:opacity .2s linear}.dropzone .dz-preview .dz-remove{font-size:14px;text-align:center;display:block;cursor:pointer;border:none}.dropzone .dz-preview .dz-remove:hover{text-decoration:underline}.dropzone .dz-preview:hover .dz-details{opacity:1}.dropzone .dz-preview .dz-details{z-index:20;position:absolute;top:0;left:0;opacity:0;font-size:13px;min-width:100%;max-width:100%;padding:2em 1em;text-align:center;color:rgba(0,0,0,.9);line-height:150%}.dropzone .dz-preview .dz-details .dz-size{margin-bottom:1em;font-size:16px}.dropzone .dz-preview .dz-details .dz-filename{white-space:nowrap}.dropzone .dz-preview .dz-details .dz-filename:hover span{border:1px solid rgba(200,200,200,.8);background-color:hsla(0,0%,100%,.8)}.dropzone .dz-preview .dz-details .dz-filename:not(:hover){overflow:hidden;text-overflow:ellipsis}.dropzone .dz-preview .dz-details .dz-filename:not(:hover) span{border:1px solid rgba(0,0,0,0)}.dropzone .dz-preview .dz-details .dz-filename span,.dropzone .dz-preview .dz-details .dz-size span{background-color:hsla(0,0%,100%,.4);padding:0 .4em;border-radius:3px}.dropzone .dz-preview:hover .dz-image img{transform:scale(1.05, 1.05);filter:blur(8px)}.dropzone .dz-preview .dz-image{border-radius:20px;overflow:hidden;width:120px;height:120px;position:relative;display:block;z-index:10}.dropzone .dz-preview .dz-image img{display:block}.dropzone .dz-preview.dz-success .dz-success-mark{animation:passing-through 3s cubic-bezier(0.77, 0, 0.175, 1)}.dropzone .dz-preview.dz-error .dz-error-mark{opacity:1;animation:slide-in 3s cubic-bezier(0.77, 0, 0.175, 1)}.dropzone .dz-preview .dz-success-mark,.dropzone .dz-preview .dz-error-mark{pointer-events:none;opacity:0;z-index:500;position:absolute;display:block;top:50%;left:50%;margin-left:-27px;margin-top:-27px;background:rgba(0,0,0,.8);border-radius:50%}.dropzone .dz-preview .dz-success-mark svg,.dropzone .dz-preview .dz-error-mark svg{display:block;width:54px;height:54px;fill:#fff}.dropzone .dz-preview.dz-processing .dz-progress{opacity:1;transition:all .2s linear}.dropzone .dz-preview.dz-complete .dz-progress{opacity:0;transition:opacity .4s ease-in}.dropzone .dz-preview:not(.dz-processing) .dz-progress{animation:pulse 6s ease infinite}.dropzone .dz-preview .dz-progress{opacity:1;z-index:1000;pointer-events:none;position:absolute;height:20px;top:50%;margin-top:-10px;left:15%;right:15%;border:3px solid rgba(0,0,0,.8);background:rgba(0,0,0,.8);border-radius:10px;overflow:hidden}.dropzone .dz-preview .dz-progress .dz-upload{background:#fff;display:block;position:relative;height:100%;width:0;transition:width 300ms ease-in-out;border-radius:17px}.dropzone .dz-preview.dz-error .dz-error-message{display:block}.dropzone .dz-preview.dz-error:hover .dz-error-message{opacity:1;pointer-events:auto}.dropzone .dz-preview .dz-error-message{pointer-events:none;z-index:1000;position:absolute;display:block;display:none;opacity:0;transition:opacity .3s ease;border-radius:8px;font-size:13px;top:130px;left:-10px;width:140px;background:#b10606;padding:.5em 1em;color:#fff}.dropzone .dz-preview .dz-error-message:after{content:"";position:absolute;top:-6px;left:64px;width:0;height:0;border-left:6px solid rgba(0,0,0,0);border-right:6px solid rgba(0,0,0,0);border-bottom:6px solid #b10606}/*# sourceMappingURL=dropzone.css.map */

1
static/vendors/htmx/htmx.min.js vendored Normal file

File diff suppressed because one or more lines are too long

View File

@@ -2,34 +2,83 @@
* Minimal theme switcher
*
* Pico.css - https://picocss.com
* Copyright 2020 - Licensed under MIT
* Copyright 2019-2023 - Licensed under MIT
*/
const themeSwitcher = {
// Config
_scheme: "auto",
menuTarget: "details[role='list']",
buttonsTarget: "a[data-theme-switcher]",
buttonAttribute: "data-theme-switcher",
rootAttribute: "data-theme",
localStorageKey: "picoPreferredColorScheme",
// Init
init() {
document.querySelectorAll(this.buttonsTarget).forEach(
function (button) {
button.addEventListener(
"click",
function (event) {
event.preventDefault();
document
.querySelector("html")
.setAttribute(
this.rootAttribute,
event.target.getAttribute(this.buttonAttribute)
);
}.bind(this),
false
);
}.bind(this)
);
this.scheme = this.schemeFromLocalStorage;
this.initSwitchers();
},
// Get color scheme from local storage
get schemeFromLocalStorage() {
if (typeof window.localStorage !== "undefined") {
if (window.localStorage.getItem(this.localStorageKey) !== null) {
return window.localStorage.getItem(this.localStorageKey);
}
}
return this._scheme;
},
// Preferred color scheme
get preferredColorScheme() {
return window.matchMedia("(prefers-color-scheme: dark)").matches ? "dark" : "light";
},
// Init switchers
initSwitchers() {
const buttons = document.querySelectorAll(this.buttonsTarget);
buttons.forEach((button) => {
button.addEventListener(
"click",
(event) => {
event.preventDefault();
// Set scheme
this.scheme = button.getAttribute(this.buttonAttribute);
// Close dropdown
document.querySelector(this.menuTarget).removeAttribute("open");
},
false
);
});
},
// Set scheme
set scheme(scheme) {
if (scheme == "auto") {
this.preferredColorScheme == "dark" ? (this._scheme = "dark") : (this._scheme = "light");
} else if (scheme == "dark" || scheme == "light") {
this._scheme = scheme;
}
this.applyScheme();
this.schemeToLocalStorage();
},
// Get scheme
get scheme() {
return this._scheme;
},
// Apply scheme
applyScheme() {
document.querySelector("html").setAttribute(this.rootAttribute, this.scheme);
},
// Store scheme to local storage
schemeToLocalStorage() {
if (typeof window.localStorage !== "undefined") {
window.localStorage.setItem(this.localStorageKey, this.scheme);
}
},
};

View File

@@ -1,4 +1,4 @@
<head>
<meta charset="utf-8">
<meta http-equiv="X-UA-Compatible" content="IE=edge">
<meta name="viewport" content="width=device-width, initial-scale=1">
@@ -11,7 +11,5 @@
<link href="{{ url_for('static', filename='vendors/picocss/pico.colors.min.css') }}" rel="stylesheet">
<link href="{{ url_for('static', filename='pywallter.css') }}" rel="stylesheet">
<link href="{{ url_for('static', filename='icons.css') }}" rel="stylesheet">
<link href="{{ url_for('static', filename='vendors/simplemde/simplemde.min.css') }}" rel="stylesheet">
<link href="{{ url_for('static', filename='vendors/glightbox/glightbox.min.css') }}" rel="stylesheet">
</head>

View File

@@ -5,6 +5,15 @@
<span class="menu-icon"></span>
</button>
<details class="dropdown">
<summary role="button" class="secondary menu-header">Theme</summary>
<ul>
<li><a href="#" data-theme-switcher="auto">Auto</a></li>
<li><a href="#" data-theme-switcher="light">Clair</a></li>
<li><a href="#" data-theme-switcher="dark">Sombre</a></li>
</ul>
</details>
<a href="/logs/">
<button class="menu-header">
Mes logs <br/>
@@ -19,14 +28,6 @@
</button>
</a>
<details class="dropdown">
<summary role="button" class="secondary menu-header">Theme</summary>
<ul>
<li><a href="#" data-theme-switcher="auto">Auto</a></li>
<li><a href="#" data-theme-switcher="light">Clair</a></li>
<li><a href="#" data-theme-switcher="dark">Sombre</a></li>
</ul>
</details>
</nav>
</header>

View File

@@ -0,0 +1,26 @@
<script>
window.onload = function () {
var dropzoneOptions = {
dictDefaultMessage: 'Déposez vos fichiers ici!',
paramName: "file",
maxFilesize: 1024, // MB
url: "/upload-dropzone",
chunking: true,
forceChunking: true,
chunkSize: 1000000,
autoProcessQueue: true,
init: function () {
this.on("success", function (file) {
console.log("success > " + file.name);
setTimeout(() => { this.removeFile(file); }, 2000);
});
}
};
var uploader = document.querySelector('#uploader');
var myDropzone = new Dropzone(uploader, dropzoneOptions);
console.log("Loaded");
};
</script>

4
templates/_js_htmx.html Normal file
View File

@@ -0,0 +1,4 @@
<script src="{{ url_for('static', filename='vendors/htmx/htmx.min.js') }}"> </script>

View File

@@ -13,7 +13,7 @@
<li>
<a href="/myblog/new-article/" {% if request.path == "/myblog/new-article/" %} class="invert" {% endif %} > <span class="icons new-article-blog"></span>
Ecrire un billet </a>
Écrire un billet </a>
</li>
<li>
<a href="/myblog/list-articles/" {% if request.path == "/myblog/list-articles/" %} class="invert" {% endif %} ><span class="icons list-articles-blog"></span>
@@ -21,14 +21,21 @@
</a>
</li>
<li>
<a href="/myblog/personalize/" {% if request.path == "/myblog/personalize/" %} class="invert" {% endif %} ><span class="icons custom-blog"></span>
Configurer mon site web
<a href="/myblog/personnalize/" {% if request.path == "/myblog/personnalize/" %} class="invert" {% endif %} ><span class="icons custom-blog"></span>
Personnaliser mon blog
</a>
</li>
<li>
<a href="/myblog/view/" {% if request.path == "/myblog/view/" %} class="invert" {% endif %} ><span class="icons view-blog" aria-hidden="true"></span>
<a href="/myblog/" {% if request.path == "/myblog/" %} class="invert" {% endif %} ><span class="icons view-blog" aria-hidden="true"></span>
Voir mon blog
</a>
</li>
<li>
<a href="/private-blog/" {% if request.path == "/private-blog/" %} class="invert" {% endif %} ><span class="icons view-blog" aria-hidden="true"></span>
Blog du serveur
</a>
</li>
</ul>
@@ -38,14 +45,13 @@
<ul>
<li>
<a href="/view/" {% if request.path == "/view/" %} class="invert" {% endif %}> <span class="icons myfiles"></span> Mes dossiers personels </a>
<a href="/view/" {% if request.path == "/view/" %} class="invert" {% endif %}> <span class="icons myfiles"></span> Mon dossier personnel </a>
</li>
<li>
<a href="/gallery/" {% if request.path == "/gallery/" %} class="invert" {% endif %} > <span class="icons mygallery"></span> Ma gallerie d'images </a>
</li>
<li>
<a href="/filesupload/" {% if request.path == "/filesupload/" %} class="invert" {% endif %}> <span class="icons send-files"></span> Envoyer des fichiers </a>
<a href="/gallery/" {% if request.path == "/gallery/" %} class="invert" {% endif %} > <span class="icons mygallery"></span> Ma galerie d'images </a>
</li>
</ul>
@@ -57,16 +63,15 @@
<ul>
<li>
<a href="/mymailbox/alias" {% if request.path == "/mymailbox/messagerie" %} class="invert" {% endif %}><span class="icons infos-messaging"></span>
<a href="/mymailbox/" {% if request.path == "/mymailbox/" %} class="invert" {% endif %}><span class="icons infos-messaging"></span>
Ma messagerie
</a>
</li>
<li>
<a href="/mymailbox/alias" {% if request.path == "/mymailbox/alias" %} class="invert" {% endif %} ><span class="icons myalias"></span>
Gerer mes alias
Gérer mes alias
</a>
</li>
</ul>

View File

@@ -9,17 +9,20 @@
{% block main %}
<h1> Bienvenue sur</h1>
<h1> Bienvenue sur : "{{ server_title }}" </h1>
<h3> {{ server_desc }} </h3>
<p>Bienvenue sur Olala, un portail utilisateur libre basé sur Flask à héberger sur un petit ordinateur.
Tu peux importer des fichiers et dans l'avenir les rendres disponibles pour ton site.
Tu peux consulter ou participer au tableau des post-its pour communiquer avec les autres membres ou
simplement savoir ce qu'il se passe sur le serveur.
<p>Bienvenue sur Pywallter, un application web basé sur Flask qui se veut être la plus légère possible.
Tu peux importer des fichiers et dans l'avenir les rendres disponibles pour ton site ou ton blog.
Tu peux participer ou consulter le blog général du serveur pour te faire connaître par les autres membres ou
simplement savoir ce qu'il se passe sur le serveur. Tu peux poster sur ton blog public et te faire connaître par les internets :p
</p>
{% if mail_server %}
<p>
Tu peux gérer ton compte MAIL et XMPP si les serveurs Mail(SMTP, IMAP) et XMPP sont actifs.
Si tu as la chance d'avoir un compte ici tu as accès à une messagerie e-mail et une messagerie instantanné avec ce même compte.
</p>
{% endif %}
{% include '_flash_msgs.html' %}
@@ -38,10 +41,31 @@
<p class="center"><a href="{{ url_for('loginlogout.lost_password') }}"> Mouarf j'ai perdu mon mot de passe </a> </p>
<button class="btn btn-default btn-primary" type="submit"> Login </button>
</form>
{% if list_posts %}
<h2> Les derniers articles de blog sur le serveur </h2>
<div class="list-articles" >
{% for article in list_posts %}
<article>
<header>
<h3> {{ article.title }} </h3>
<h5> par <a href="/blog/{{ article.author }}/">{{ article.author }}</a> </h5>
<br/>
<small> Créé le : {{ article.creation_date }} </small> <br/>
{% if article.last_updated %}
<small> Modifié le : {{ article.last_updated }}</small><br/>
{% endif %}
</header>
<div class="subtitle">
<p> {{ article.subtitle }} </p>
</div>
{% endblock %}
<footer><a href="/blog//public_unified/{{ article.author }}/{{ article.title }}"> <button> Lire la suite </button> </a></footer>
</article>
{% endfor %}
{% endif %}
{% endblock %}
</main>

View File

@@ -1,24 +1,33 @@
<!doctype html>
<html>
<head>
<title> Blog de {{ user }} </title>
<link rel="stylesheet" href="/static/blog.css" type="text/css">
<title> Article de {{ post_info.author }} </title>
<link rel="stylesheet" href="/{{ post_info.author }}/blog.css" type="text/css">
<meta name="viewport" content="width=device-width, initial-scale=1">
</head>
<body>
<div class="articles">
<h2 class="titre"> {{ post_info.title }} </h2>
<div class="head-article">
<h2> {{ post_info.title }} </h2>
<h5 class="titre">Publié le {{ post_info.time }} </h5>
<h2 class="description titre"> {{ post_info.subtitle }} </h2>
<hr/>
<h5>Publié le : {{ post_info.creation_date }} par <a href="{{ url_for('blog.view') }}/{{ post_info.author }}"> {{ post_info.author }} </h5></a>
{% if post_info.last_updated %}
<h5>Mis à jour le : {{ post_info.last_updated }} </h5>
{% endif %}
<h3 class="description"> {{ post_info.subtitle }} </h3>
</div>
<hr/>
<div class="text-article">
{{ content|safe }}
</div>
</div>
<footer> <p> pywallter </p> </footer>
</body>
</html>

View File

@@ -0,0 +1,26 @@
<!doctype html>
<html>
<head>
<title> Article de {{ post_info.author }} </title>
<link rel="stylesheet" href="/{{ post_info.author}}/blog.css" type="text/css">
<meta name="viewport" content="width=device-width, initial-scale=1">
</head>
<body>
<div class="articles">
<h2 class="titre"> {{ post_info.title }} </h2>
<h5 class="titre">Publié le : {{ post_info.creation_date }} </h5>
{% if post_info.last_updated %}
<h5 class="titre">Mis à jour le : {{ post_info.last_updated }} </h5>
{% endif %}
<h3 class="description titre"> {{ post_info.subtitle }} </h3>
<hr/>
{{ content|safe }}
</div>
</body>
</html>

35
templates/blog_rss.xml Normal file
View File

@@ -0,0 +1,35 @@
<?xml version="1.0" encoding="UTF-8" ?>
<rss version="2.0"
xmlns:content="http://purl.org/rss/1.0/modules/content/"
xmlns:dc="http://purl.org/dc/elements/1.1/"
xmlns:atom="http://www.w3.org/2005/Atom"
xmlns:sy="http://purl.org/rss/1.0/modules/syndication/"
xmlns:slash="http://purl.org/rss/1.0/modules/slash/">
<channel>
<title> Le blog de {{ blog_name }} </title>
<link> {{ base_url }}/blog/ </link>
{% if blog_description %}
<description> {{ blog_description }} </description>
{% endif %}
<language>fr</language>
<lastBuildDate> {{ last_build_date }} </lastBuildDate>
{% for post in posts %}
<item>
<title> {{ post.title }} </title>
<pubdate> {% if post.last_updated %} {{ post.last_updated }} {% else %} {{ post.time }} {% endif %} </pubdate>
<link> {{base_url}}/blog/{{ post.status }}/{{post.author}}/{{post.title}}</link>
<guid> {{base_url}}/blog/{{ post.status }}/{{post.author}}/{{post.title}}</guid>
<description>
{{ post.subtitle }}
</description>
<content:encoded>
{{ post.content | safe }}
</content:encoded>
</item>
{% endfor %}
</channel>
</rss>

View File

@@ -0,0 +1,3 @@
<script src="{{ url_for('static', filename='vendors/dropzone/dropzone-min.js') }}"></script>
<link href="{{ url_for('static', filename='vendors/dropzone/dropzone.css') }}" rel="stylesheet">

View File

@@ -0,0 +1,3 @@
<link href="{{ url_for('static', filename='vendors/simplemde/simplemde.min.css') }}" rel="stylesheet">
<link href="{{ url_for('static', filename='simplemde-custom.css') }}" rel="stylesheet">

View File

@@ -4,7 +4,7 @@
<h1> Supprimer mon compte </h1>
<p class="center"> Vous voulez supprimer votre compte pas de problèmes toutes vos données seront effacées du serveur et après un periodes de {{ time_backup }} vos données seront complètements supprimées des sauvegardes, il n'y aura aucun retour en arrière possible.
<p class="center"> Vous voulez supprimer votre compte pas de problème, toutes vos données seront effacées du serveur et après un periodes de {{ time_backup }} vos données seront complètements supprimées des sauvegardes, il n'y aura aucun retour en arrière possible.
</p>
<h3> Entrez votre mot de passe pour confirmer la suppression de votre compte </h3>

View File

@@ -1,32 +1,63 @@
{% extends 'up_squelette.html' %}
{% block main %}
{% block css %}
{% include 'css/simple_editor.html' %}
{% endblock %}
<p>Vous pouvez modifier votre article. Actuellement seule la date de première édition sera publiée. Prochainement : intégration de la date de mise à jour dans la base de donnée.</p>
{% block main %}
<h2> {{ oldpost[0] }}</h2><br />
<form action="" method="POST" id="postform" style="height: 15vw;">
<input type="text" name="subtitle" id="subtitle" placeholder="Titre" class="form-control" value="{{ oldpost[1] }}"><br />
<textarea id="editeurMarkdown" class="form-control" form="postform" name="content" id="content" placeholder="Contenu" style="height:20vw;">{{ content }}</textarea><br />
<form action="" method="POST" id="postform" >
<input type="text" name="title" id="title" placeholder="titre" class="form-control" value="{{ oldpost['title'] }}"><br />
<input type="text" name="subtitle" id="subtitle" placeholder="sous-titre" class="form-control" value="{{ oldpost['subtitle'] }}"><br />
<input type="text" name="category" id="category" placeholder="Catégorie" class="form-control" value="{{ oldpost['category'] }}"><br />
<textarea id="editeurMarkdown" class="form-control" form="postform" name="content" id="content" placeholder="Contenu" style="height:20vw;">{{ oldpost['content'] }}</textarea>
<br />
<h3> Visibilité </h3>
<div class="center">
{% if oldpost[2] == 'public' %}
<input type="radio" name="status" value="prive"> Privé
<p> Les articles brouillons ne sont visibles que par vous même</p>
<p> Les articles privés ne sont visibles que par les membres du serveur </p>
<p> Les articles public sont visibles par tout le monde </p>
{% if oldpost['status'] == 'public' or oldpost['status'] == "public_unified" %}
<input type="radio" name="status" value="draft"> Brouillon
<input type="radio" name="status" value="private"> Privé
<input type="radio" name="status" value="public" checked> Publique
{% else %}
<input type="radio" name="status" value="Privé" checked> Privé
{% elif oldpost['status'] == 'private' or oldpost['status'] == "private_unified" %}
<input type="radio" name="status" value="draft"> Brouillon
<input type="radio" name="status" value="private" checked> Privé
<input type="radio" name="status" value="public"> Publique
{% else %}
<input type="radio" name="status" value="draft" checked> Brouillon
<input type="radio" name="status" value="private"> Privé
<input type="radio" name="status" value="public"> Publique
{% endif %}
</div>
<br />
<fieldset>
<label>
{% if oldpost['status'] == "private_unified" or oldpost['status'] == "public_unified" %}
<input id="blog-unified" name="blog-unified" type="checkbox" role="switch" checked />
{% else %}
<input id="blog-unified" name="blog-unified" type="checkbox" role="switch" />
{% endif %}
Publier cet article dans le blog général
</label>
</fieldset>
<button type="submit"> Mettre à jour </button>
</form>
<br />
{% endblock %}
{% block js %}
{% include '_js_editor.html' %}
{% endblock %}

50
templates/files.html Normal file
View File

@@ -0,0 +1,50 @@
{% extends 'up_squelette.html' %}
{% block css %}
{% include 'css/dropzone.html' %}
{% endblock %}
{% block main %}
<p>Quand vous envoyez des images, elles se retrouveront directement dans <a href="/gallery/"> votre Galerie</a>. </p>
<p>
Ayez bien conscience que ce site est une expérience et qu'il est indispensable d'avoir
une sauvegarde de tous les fichiers qui vous mettrez ici. Nous ne pourrons, en aucun cas, être tenu responsable de la
perte de vos données. Merci de votre compréhension.
</p>
<br />
<form id="uploader" methods="POST" class="dropzone dz-clickable"></form>
<h2> Fichiers privés </h2>
<h5>
Vous pouvez partager des liens de vos fichiers avec les autres membres de ce serveur uniquement. Si vous partagez un lien avec une personne non inscrite elle ne pourra pas y avoir accès
</h5>
<div hx-get="/files/private/" hx-trigger="load, every 10s">
Chargement ...
</div>
<br />
<br />
<h2> Fichiers publics </h2>
<h5> Vous pouvez partager les liens de ces fichiers avec n'importe qui sur Internet ils y auront accès </h5>
<div hx-get="/files/public/" hx-trigger="load, every 10s">
Chargement ...
</div>
{% endblock %}
{% block js %}
{% include '_js_dropzone.html' %}
{% include '_js_htmx.html' %}
{% endblock %}

View File

@@ -1,10 +1,13 @@
{% extends 'up_squelette.html' %}
{% block main %}
<h3> Ma gallerie </h3>
{% if fichiers %}
@@ -24,7 +27,7 @@
{% else %}
<h2> Il n'y a aucunes images dans votre gallerie </h2>
<h2> Il n'y a aucune image dans votre galerie </h2>
{% endif %}

View File

@@ -0,0 +1,42 @@
<!doctype html>
<html>
<head>
<title> Le Blog du serveur </title>
<link rel="stylesheet" href="/{{ author }}/blog.css" type="text/css">
<meta name="viewport" content="width=device-width, initial-scale=1">
</head>
<body>
{% if not(posts) %}
<h1 style="text-align: center;"> Désolé ce blog n'existe pas encore :/ </h1>
{% else %}
<a href="/blog/{{ author }}/rss.xml"> S'abonner au flux RSS </a>
<div class="articles">
<a href="/blog/{{ author }}/rss.xml"> S'abonner au fl RSS </a>
{% for post in posts %}
<h2 class="index"> {{ post.title }} </h2>
<small>
<time datetime="{{ post.time }}">
Publié le {{ post.creation_date }}
</time>
<br/>
{% if post.last_updated %}
<time datetime="{{ post.last_updated }}">
Mis à jour le {{ post.last_updated }}
</time>
{% endif %}
</small>
<div class="slug">
<p> {{ post.subtitle }} </p>
<p class="readmore"> <a style="margin-right:2rem;" href="/blog/{{ post.status }}/{{post.author}}/{{post.title}}"> Lire la suite... </a></p>
</div>
{% endfor %}
</div>
{% endif %}
</body>
</html>

View File

@@ -1,27 +1,47 @@
<!doctype html>
<html>
<head>
<title> Blog de {{ user }} </title>
<title> Le Blog du serveur </title>
{% if author %}
<link rel="stylesheet" href="/{{author}}/blog.css" type="text/css">
{% else %}
<link rel="stylesheet" href="/static/blog.css" type="text/css">
{% endif %}
<meta name="viewport" content="width=device-width, initial-scale=1">
</head>
<body>
{% if not(posts) %}
<h1 style="text-align: center;"> Désolé ce blog n'existe pas encore :/ </h1>
{% else %}
<div class="articles">
<a href="/blog/rss.xml"> Suivre ce blog par RSS </a>
{% for post in posts %}
<h2 class="index"><a href="/blog/{{user}}/{{post.title}}"> {{ post.title }}</a></h2>
<small>
<time datetime="{{ post.time }}">
Publié le {{ post.time }}
</time>
</small>
<div class="slug">
<p> {{ post.subtitle }} </p>
<p class="readmore"> <a style="margin-right:2rem;" href="/blog/{{user}}/{{post.title}}"> Lire la suite... </a></p>
</div>
<h2 class="index"> {{ post.title }}</h2>
<small>
<time datetime="{{ post.time }}">
Publié le {{ post.creation_date }} par <a href="{{ url_for('blog.view') }}/{{ post.author }}"> {{ post.author }}</a>
</time>
<br/>
{% if post.last_updated %}
<time datetime="{{ post.last_updated }}">
Mis à jour le {{ post.last_updated }}
</time>
{% endif %}
</small>
<div class="slug">
<p> {{ post.subtitle }} </p>
<p class="readmore"> <a style="margin-right:2rem;" href="/blog/{{ post.status }}/{{post.author}}/{{post.title}}"> Lire la suite... </a></p>
</div>
{% endfor %}
</div>
{% endif %}
</body>
</html>

View File

@@ -2,69 +2,46 @@
{% block main %}
<div class="site-wrapper">
<div class="site-wrapper-inner">
<h1>Inscription</h1>
<br>
{% include '_flash_msgs.html' %}
<div class="cover-container">
{% if signin_enable %}
<form method="POST" action="{{ url_inscription }}">
<h4> Choisissez votre nom d'utilisateur pour vous connecter sur le portail pywallter </h4>
<div class="masthead clearfix">
<div class="inner">
<h3 class="masthead-brand">Pywallter</h3>
<ul class="nav masthead-nav">
<li><a href="/login/">Login</a></li>
{% if signin_enable %}
<li class="active"><a href="/inscription/">Inscription</a></li>
{% endif %}
<li><a href="#">Contact</a></li>
</ul>
</div>
</div>
<div class="inner cover">
<h1 class="cover-heading">Inscription</h1>
<br>
{% include '_flash_msgs.html' %}
{% if signin_enable %}
<form method="POST" class="form-horizontal" action="{{ url_inscription }}">
<h4> Choisissez votre nom d'utilisateur pour vous connecter sur le portail pywallter </h4>
<input type="text" name="user" id="user" placeholder="Pseudo" class="form-control"><br />
{% if MAIL_SERVER or XMPP_SERVER %}
<p>
Votre nom d'utilisateur vous servira à vous connecter à votre compte Mail et de messagerie instantanné (XMPP)
Par exemple vous souhaitez l'adresse toto@{{hostname}}; vous entrez le nom d'utilisateur toto :)
</p>
{% endif %}
<input type="password" name="passwd" id="passwd" placeholder="Mot de passe" class="form-control"><br />
<input type="password" name="passwdconfirm" id="passwdconfirm" placeholder="Confirmation du mot de passe" class="form-control"><br />
<br>
<button id="tada" class="btn btn-default btn-primary" type="submit">Créer mon compte</button>
</form>
{% else %}
<p class="lead">
Désolé les inscriptions ne sont pas activés sur le serveur
</p>
{%endif%}
{% for i in users %}
<p>{{i}}</p>
{% endfor %}
</div>
{% if MAIL_SERVER or XMPP_SERVER %}
<fieldset role="group">
<input type="text" name="user" id="user" placeholder="Votre pseudo">
<button class="outline">
@{{ hostname }}
</button>
</fieldset>
<p>
Votre pseudo vous servira à vous connecter à votre compte Mail et de messagerie instantanné (XMPP)
Par exemple vous souhaitez l'adresse toto@{{hostname}}; vous entrez le pseudo toto :)
</p>
{% else %}
<input type="text" name="user" id="user" placeholder="Votre Pseudo"><br />
{% endif %}
<input type="password" name="passwd" id="passwd" placeholder="Mot de passe"><br />
<input type="password" name="passwdconfirm" id="passwdconfirm" placeholder="Confirmation du mot de passe"><br />
<br>
<button type="submit">Créer mon compte</button>
</form>
{% else %}
<p class="lead">
Désolé les inscriptions ne sont pas activés sur le serveur
</p>
{%endif%}
{% for i in users %}
<p>{{i}}</p>
{% endfor %}
<div class="mastfoot">
<div class="inner">
<p>Cover template for <a href="http://getbootstrap.com">Bootstrap</a>, by <a href="https://twitter.com/mdo">@mdo</a>.</p>
</div>
</div>
</div>
</div>
</div>
{% endblock %}

View File

@@ -6,11 +6,10 @@
<p>
Si vous voulez vous pouvez inviter une personne à se crée un compte sur ce serveur
pour cela vous devez crée un lien d'inscription. Ce lien restera valable tant
que la personne ne s'est pas inscrite ou tant que vous ne créez pas un autre lien.
Les invitations se font une par une et sont limité à 20 personnes pour ne pas surcharger notre petit serveur :).
Une fois que la personne s'est incrite votre nombre d'invitations sera mis à jour
Si vous voulez vous pouvez inviter une personne à se créer un compte sur ce serveur.
Pour cela vous devez créer un lien d'inscription. Ce lien restera valable tant que vous ne créez pas un autre lien.
Les invitations sont limité à 20 personnes pour ne pas surcharger notre petit serveur :).
Une fois que la personne s'est inscrite votre nombre d'invitations sera mis à jour.
</p>
@@ -19,9 +18,16 @@
{% if token %}
<h3> Votre lien d'inscription en cours: </h3>
<a href="{{ url_invitation }}">
<a href="{{ url_invitation }}" id="copy-link">
{{ url_invitation }}
</a>
</a> <button class="ghost" onclick="copyLinkToClipboard()" > Copier le lien </button>
<p> ou faîtes scanner ce qrcode :)</p>
<img src="/invitation.png"/>
{% else %}
<h3> Pas d'invitation en attente </h3>
{% endif %}
@@ -29,7 +35,22 @@
<a href="/gen_token/">
<button type="submit" id="tada" class="btn btn btn-success"> Créer un nouveau lien </button></a>
<script>
function copyLinkToClipboard(){
elt = document.getElementById("copy-link");
try {
console.log(elt.outerText);
navigator.clipboard.writeText(elt.outerText);
} catch (error)
{
console.error (error.message)
}
}
</script>
{% endblock %}

View File

@@ -3,9 +3,10 @@
{% block main %}
<h2> Vos articles de blog </h2>
{% if nb_articles > 1 %}
<h2> Vos {{ nb_articles }} articles de blog </h2>
{% endif %}
<br/>
{% for article in list_posts %}
<article>
@@ -14,13 +15,29 @@
<br/>
<small> Créé le : {{ article.time }} </small> <br/>
<small> Modifié le : {{ article.last_updated }}</small>
<p> Status : {{ article.status }}</p>
<br/>
{% if article.last_updated %}
<small> Modifié le : {{ article.last_updated }}</small><br/>
{% endif %}
<br/>
{% if article.status == "private" %}
<small> Status : privé </small>
<small> (L'article n'est pas publié dans le blog général) </small>
{% elif article.status == "private_unified" %}
<small> Status : privé </small>
<small> (L'article est publié dans le blog général) </small>
{% elif article.status == "public" %}
<small> Status : public </small>
<small> (L'article n'est pas publié dans le blog général) </small>
{% elif article.status == "public_unified" %}
<small> Status : public </small>
<small> (L'article est publié dans le blog général) </small>
{% endif %}
<br/>
<br/>
<a href="{{ url_for('blog.edit', title=article.title) }}"><button type="button"> Editer </button></a>
<a href="{{ url_for('blog.delete', title=article.title) }}"><button type="button">Supprimer</button></a>
<a href="{{ url_for('blog.edit', title=article.title) }}"><button type="button"> Publier </button></a>
</article>
{% endfor %}

54
templates/list_files.html Normal file
View File

@@ -0,0 +1,54 @@
{% if listFiles %}
<table class="table">
<thead>
<tr>
<th></th>
<th>Fichier(s) <span class="badge">{{ nb_files }}</span></th>
<th>Taille (en Megaoctect)</th>
<th></th>
</tr>
</thead>
<tbody>
{% for file in listFiles %}
<tr>
<td>{{ file[0] }}</td>
<td><a href="/myfiles/{{ username }}/{{ file[1] }}">{{ file[1] }}</a></td>
<td>{{ file[2] }}</td>
<td>
{% if status == "public" %}
<td>
<a href="{{ url_for('filesupload.remove_publicFile', filename=file[1]) }}">
<button type="button" title="Supprimer"> <span class="icons delete"></span></button>
</a>
<a href="{{ url_for('filesupload.move_private', filename=file[1]) }}">
<button type="button" title="Passer ce fichier en status privé"><span class="icons share"></span></button>
</a>
<button type="button" onclick="navigator.clipboard.writeText('{{BASE_URL}}{{ url_for('filesupload.publicfiles', username=username, filename=file[1]) }}');" title="Copier le lien du fichier">
<span class="icons copy-link"> </span>
</button>
</td>
{% else %}
<td>
<a href="{{ url_for('filesupload.remove_privateFile', filename=file[1]) }}">
<button type="button" title="Supprimer"> <span class="icons delete"></span></button>
</a>
<a href="{{ url_for('filesupload.move_public', filename=file[1]) }}">
<button type="button" title="Passer ce fichier en status public"><span class="icons share"></span></button>
</a>
<button type="button" onclick="navigator.clipboard.writeText('{{BASE_URL}}{{ url_for('filesupload.publicfiles', username=username, filename=file[1]) }}');" title="Copier le lien du fichier">
<span class="icons copy-link"> </span>
</button>
</td>
{% endif %}
</tr>
{% endfor %}
</tbody>
</table>
{% else %}
<h5> Aucun fichier ici </h5>
{% endif %}

View File

@@ -36,7 +36,7 @@
{% include '_footer.html' %}
{% include '_js.html' %}
{% include '_js-core.html' %}
</body>

View File

@@ -5,85 +5,62 @@
{% block main %}
<div class="row">
{% if MAIL_SERVER %}
<div class="col-md-12">
<h3> A quoi ca sert les alias ? </h3>
<p> Les alias c'est utile quand vous ne voulez pas donner votre vrai adresse e-mail.
Vous pouvez creer une adresse que vous pouvez supprimer rapidemment, cela permet personnaliser une adresse pour un destinataire
si vous n'avez pas confiance en lui ou de trier plus facilement les e-mails venant de ce destinataire.
</p>
<p> Vous n'avez pas besoin de configurer un autre compte mail sur vos applications, tous les e-mails
arriveront sur votre adresse e-mail principale déjà configuré
</p>
<table class="table">
<thead>
<tr>
<th>Mes Alias <span class="badge">{{ i }}</span></th>
<th></th>
</tr>
</thead>
<tbody>
{% if aliases %}
{% for alias in aliases %}
<tr>
<td>{{ alias }}</td>
<td><a href="{{ url_for('profil.remove_alias', aliasrm=alias) }}"><button type="button" class="btn btn-sm btn-danger">Supprimer</button></a></td>
</tr>
{% endfor %}
{% endif %}
</tbody>
</table>
</div>
</div>
<div class="panel panel-primary">
<div class="panel-heading">
<h3 class="panel-title"> Mes identités </h3>
</div>
<div class="panel-body">
<form method="POST" action="" enctype="multipart/form-data">
<p> Votre Adresse e-mail sur ce serveur : {{ email }} </p>
<label> Nouvelle identité </label>
<br/>
<div class="col-sm-7">
<input type="text" name="alias" id="alias" placeholder="Nouvel_identité" class="form-control"><br />
</div>
<h4>@{{ hostname }}</h4>
<br/>
<br/>
<button id="tada" class="btn btn-default btn-primary" type="submit">Ajouter</button>
</form>
</div>
</div>
{% else %}
<div class="panel panel-primary">
<div class="panel-heading">
<h3 class="panel-title"> Mes identitées </h3>
</div>
<div class="panel-body">
<h2> Le serveur de mail n'est pas activé cette fonctionnalité est désactivé <h2>
</div>
</div>
{% endif %}
{# on affiche les messages d'erreur puis les messages de succes #}
{% for categorie in ['error', 'succes'] %}
{% with msgs = get_flashed_messages(category_filter=[categorie]) %}
{% if msgs %}
{% for m in msgs %}
<p>{{ m|safe }}</p>
{% endfor %}
{% endif %}
{% endwith %}
<h3> Mes identités : A quoi ça sert les alias ? </h3>
<p> Les alias d'e-mail, c'est utile quand vous ne voulez pas donner votre vrai adresse e-mail.
Vous pouvez creer une adresse que vous pouvez supprimer rapidemment, cela permet de creer une adresse pour un destinataire particulier
si vous n'avez pas confiance en lui par exemple ou de trier plus facilement les e-mails venant de ce destinataire.
</p>
<p> Vous n'avez pas besoin de configurer un autre compte mail sur vos applications mail, tous les e-mails
arriveront sur votre adresse e-mail principale déjà configurée. Faîtes attention de bien répondre avec votre mail d'alias cependant !
</p>
<table class="table">
<thead>
<tr>
<th>Mes identités <span class="badge">{{ i }}</span></th>
<th></th>
</tr>
</thead>
<tbody>
{% if aliases %}
{% for alias in aliases %}
<tr>
<td>{{ alias }}</td>
<td><a href="{{ url_for('mymailbox.remove_alias', aliasrm=alias) }}"><button type="button" class="btn btn-sm btn-danger">Supprimer</button></a></td>
</tr>
{% endfor %}
{% endif %}
</tbody>
</table>
<h3 class="panel-title"> Mes identités </h3>
<form method="POST" action="" enctype="multipart/form-data">
<h4> Votre addresse e-mail sur ce serveur : {{ email }} </h4>
<label> Nouvelle identité </label>
<br/>
<fieldset role="group">
<input type="text" name="alias" id="alias" placeholder="Nouvel_identité">
<button class="outline">
@{{ hostname }}
</button>
</fieldset>
<br/>
<br/>
<button class="btn btn-default btn-primary" type="submit">Créer cette identité</button>
</form>
{% else %}
<h3 class="panel-title"> Mes identitées </h3>
<h2> Le serveur de mail n'est pas activé cette fonctionnalitée est désactivé <h2>
{% endif %}
{% endblock %}

View File

@@ -0,0 +1,89 @@
{% extends 'up_squelette.html' %}
{% block main %}
{% if not(mail_server) or not(xmpp_server) %}
<h3 class="panel-title"> Ma Messagerie </h3>
<h2> Le service de messagerie n'est pas activé sur ce serveur <h2>
{% else %}
<h3> Bienvenue {{ username }} dans votre messagerie</h3>
<h4> Votre compte est : {{ myemail }} </h4>
<p>
Si vous êtes sur cette page, c'est que vous disposez d'un compte de messaegerie sur ce serveur.
Vous pouvez utiliser votre compte mail avec un client mail et votre compte XMPP avec un client XMPP.
</p>
<p> Voici un exemple de <a href="https://www.thunderbird.net/fr/"> client mail </a>
et <a href="https://gajim.org/">client XMPP </a> pour un ordinateur </p>
<p> un exemple de <a href="https://k9mail.app/"> client mail </a> et <a href="https://play.google.com/store/apps/details?id=org.snikket.android&hl=ln&gl=US">client XMPP</a>
pour un téléphone sous Android </p>
<p> et un un exemple de<a href="https://support.apple.com/fr-fr/mail"> client mail</a> et <a href="https://itunes.apple.com/us/app/tigase-messenger/id1153516838"> client XMPP </a>sous iOS pour un iphone ou un Ipad </p>
{% if mail_webservice %}
<p>
Vous pouvez aussi accéder à vos mails avec votre navigateur web actuel en utilisant le webmail disponible à l'adresse <a href="{{ mail_webservice }}"> {{ mail_webservice }} </a>.
</p>
{% endif %}
{% if xmpp_webservice %}
<p>
Vous pouvez aussi accéder à votre messagerie instantanné avec votre Navgateur web actuel en utilisant le tchat disponible à l'adresse <a href="{{ xmpp_webservice }}"> {{ xmpp_webservice }} </a>.
</p>
{% endif %}
{% if xmpp_server %}
<h3> Vos informations pour configurer vos clients XMPP </h3>
<p> Si vous voulez configurer votre compte XMPP dans votre, voici les informations à rentrer dans votre client XMPP </p>
<ul>
<li> identifiant : {{ myemail }} </li>
<li> mot de passse : <pre> Le même mot de passe que vous avez utilisé pour vous connecter ici </pre></li>
</ul>
{% else %}
<h4 class="alert" > La messagerie XMPP est désativé sur ce serveur </h4>
{% endif %}
{% if mail_server %}
<h3> Vos informations pour configurer vos clients Mail </h3>
<p> Si vous voulez configurer votre adresse e-mail, voici les informations à rentrer dans votre client mail </p>
<h4>Courrier entrant : </h4>
<ul>
<li> Protocol : IMAP </li>
<li> Addresse du serveur : {{ imap_address }} </li>
<li> Port : 993 SSL </li>
<li> identifiant : {{ myemail }} </li>
<li> mot de passse : <pre> Le même mot de passe que vous avez utilisé pour vous connecter ici </pre> </li>
</ul>
<h4>Courrier sortant : </h4>
<ul>
<li> Protocol : SMTP </li>
<li> Addresse du serveur : {{ smtp_address }} </li>
<li> Port : 587 STARTTLS </li>
<li> identifiant : {{ myemail }} </li>
<li> mot de passse : <pre> Le même mot de passe que vous avez utilisé pour vous connecter ici </pre> </li>
</ul>
{% else %}
<h4 class="alert" > La messagerie e-mail est désativé sur ce serveur </h4>
{% endif %}
{% endif %}
{% endblock %}

View File

@@ -20,7 +20,7 @@
<p> Voici quelques exemple de générateur de mot de passe à usage unique (OTP en anglais) pour <a href="https://getaegis.app/">Android</a>, <a href="https://apps.apple.com/us/app/totp-authenticator-fast-2fa/id1404230533">iOS</a> et <a href="https://keepassxc.org/">Linux/BSD et windows</a>
<p> Afin que le serveur et votre application génère le même code en même temps; il vous faut valider la clef sécrete partager par votre application et le serveur. Pour cela, si ce n'est pas déjà fait, scannez ou entrez la clef secrète dans votre application. Pour finir, cliquez sur valider la clef en entrant le code générer par votre application. Si le test réussi parfait c'est configuré ! </p>
<p> Afin que le serveur et votre application génèrent le même code en même temps; il vous faut valider la clef sécrete partager par votre application et le serveur. Pour cela, si ce n'est pas déjà fait, scannez ou entrez la clef secrète dans votre application. Pour finir, cliquez sur valider la clef en entrant le code généré par votre application. Si le test réussi parfait c'est configuré ! </p>
<p> Pour changer votre clef secrète vous devez d'abord supprimer votre clef actuelle pour en regénérer une nouvelle. </p>
@@ -32,7 +32,7 @@
<p class="success"> Votre clef secrète est valide et activé </p>
{% else %}
<p class="alert"> Votre clef secrète n'est pas validé et donc non active </p>
<p class="alert"> Votre clef secrète n'est pas validée et donc non active </p>
{% endif %}
<form method="POST" action="{{ url_for('profil.set_totp') }}" >

View File

@@ -6,7 +6,7 @@
<p> Hello <span id="majuscule">{{ session['username'] }} ! </span>
Bienvenue sur la création d'un nouvel article de blog. Vous pouvez créer ou importer un article de blog ici,
vous avez le choix de le rendre publique dès sa création en cochant publique ou le laisser en privé
vous avez le choix de le rendre public dès sa création en cochant public ou le laisser en privé
si vous souhaitez le modifier plus-tard avant sa publication.
Par défaut il est laissé en privé pour éviter les publications
accidentelles.
@@ -15,20 +15,40 @@
<br />
<form method="POST" action="{{ url_for('blog.new_article') }}" id="postform">
<!--<input type="text" name="category" id="category" placeholder="Catégorie" class="form-control"><br />-->
<input type="text" name="title" id="title" placeholder="Titre" class="form-control"><br />
<input type="text" name="subtitle" id="subtitle" placeholder="Sous-titre" class="form-control"><br />
<input type="text" name="category" id="category" placeholder="Catégories #hashtag" class="form-control"><br />
<hr>
<textarea id="editeurMarkdown" class="form-control" form="postform" name="content" id="content" placeholder="Contenu" style="height:20vw;"></textarea><br />
<textarea id="editeurMarkdown" class="form-control" form="postform" name="content" id="content" placeholder="Contenu" style="height:20vw; margin-right:2vw;"></textarea><br />
<h3> Visibilité </h3>
<div class="center">
<input type="radio" name="status" value="private" checked> Privé
<p> Les articles brouillons ne sont visibles que par vous même</p>
<p> Les articles privés ne sont visibles que par les membres du serveur </p>
<p> Les articles public sont visibles par tout le monde </p>
<input type="radio" name="status" value="draft"> Brouillon
<input type="radio" name="status" value="private" checked> Privé
<input type="radio" name="status" value="public">Public<br>
</div>
<br/>
<button id="tada" type="submit"> Créer l'article </button>
<fieldset>
<label>
<input id="blog-unified" name="blog-unified" type="checkbox" role="switch" />
Publier cet article dans le blog général
</label>
</fieldset>
<button type="submit"> Créer l'article </button>
</form>
{% endblock %}
{% block js %}
{% include '_js_editor.html' %}
{% endblock %}

View File

@@ -0,0 +1,34 @@
{% extends 'up_squelette.html' %}
{% block main %}
<h2> Personnalisation du blog </h2>
<form method="POST" action="" enctype="multipart/form-data">
<br />
<label for="blog-theme">Theme</label>
<select id="blog-theme" name="blog-theme">
<option selected> Default </option>
<option>orangina</option>
<option>blanc&noir</option>
<option>noir&blanc</option>
<option>orange&noir</option>
</select>
<div id="personnal-blog-theme" class="center">
<p> Vous pouvez faire le choix d'envoyer un fichier css pour personnaliser votre blog directement.
<label> Envoyer mon thème </label>
<input type="file" class="center" name="personnal-blog-theme" id="personnal-blog-theme"/>
<br />
</div>
<button class="btn btn-default btn-primary" type="submit"> Enregistrer mes modifications </button>
</form>
{% endblock %}

View File

@@ -4,9 +4,7 @@
{% block main %}
<div>
<div>
<h3> Mon profil </h3>
<p>
@@ -15,8 +13,10 @@
cela vous convient.
</p>
<p> Pour votre mail de secours il sert à vous envoyer un mail pour changer de mot de passe en cas de perte de ce dernier. Il n'y a
pas encore de validation de l'e-mail donc vérifiez bien ce que vous tapez. </p>
<p>
Pour votre mail de secours il sert à vous envoyer un mail pour changer de mot de passe en cas de perte de ce dernier. Il n'y a
pas encore de validation de l'e-mail donc vérifiez bien ce que vous tapez.
</p>
<br/>
<br/>
@@ -30,40 +30,40 @@
</div>
<br />
<label for="theme">Theme</label>
<select id="theme" name="theme" required>
<option value="" selected>Default</option>
<option>amber</option>
<option>blue</option>
<option>cyan</option>
<option>fuchsia</option>
<option>green</option>
<option>grey</option>
<option>indigo</option>
<option>jade</option>
<option>orange</option>
<option>rose</option>
<option>pumpkin</option>
<option>purple</option>
<option>red</option>
<option>sand</option>
<option>slate</option>
<option>violet</option>
<option>yellow</option>
<option>zinc</option>
</select>
<label for="theme">Thème</label>
<select id="theme" name="theme">
<option value="" selected>Choisissez votre couleur</option>
<option>amber</option>
<option>blue</option>
<option>cyan</option>
<option>fuchsia</option>
<option>green</option>
<option>grey</option>
<option>indigo</option>
<option>jade</option>
<option>orange</option>
<option>rose</option>
<option>pumpkin</option>
<option>purple</option>
<option>red</option>
<option>sand</option>
<option>slate</option>
<option>violet</option>
<option>yellow</option>
<option>zinc</option>
</select>
<br />
<br />
<label>Nom </label>
<input type="text" name="nom" id="nom" value="{% if profil['nom'] != None %}{{ profil['nom'] }}{%endif%}"><br />
<label>Prenom </label>
<input type="text" name="prenom" id="prenom" value="{% if profil['nom'] != None %}{{ profil['prenom'] }}{%endif%}"><br />
<label> Age </label>
<input type="text" name="age" value="{% if profil['age'] != None %}{{ profil['age'] }}{%endif%}"><br />
<label> Mail de secours </label>
<input type="text" name="mail_rescue" id="mail_rescue" value="{% if profil['mail_rescue'] != None %}{{ profil['mail_rescue'] }}{%endif%}"><br />
<button id="tada" class="btn btn-default btn-primary" type="submit">Envoyer</button>
<label>Nom </label>
<input type="text" name="nom" id="nom" value="{% if profil['nom'] != None %}{{ profil['nom'] }}{%endif%}"><br />
<label>Prenom </label>
<input type="text" name="prenom" id="prenom" value="{% if profil['nom'] != None %}{{ profil['prenom'] }}{%endif%}"><br />
<label> Age </label>
<input type="text" name="age" value="{% if profil['age'] != None %}{{ profil['age'] }}{%endif%}"><br />
<label> Mail de secours </label>
<input type="text" name="mail_rescue" id="mail_rescue" value="{% if profil['mail_rescue'] != None %}{{ profil['mail_rescue'] }}{%endif%}"><br />
<button id="tada" class="btn btn-default btn-primary" type="submit">Envoyer</button>
</form>

View File

@@ -1,69 +0,0 @@
{% extends 'up_squelette.html' %}
{% block main %}
<br>
<div class="row">
<div class="col-md-12">
<h2> Fichiers privés (Seul les personnes connectées et l'administrateur de l'ordinateur peuvent les voirs) </h2>
{% if listFilesPrivate %}
<table class="table">
<thead>
<tr>
<th></th>
<th>Fichier(s) <span class="badge">{{ nb_pv }}</span></th>
<th>Taille (en Megaoctect)</th>
<th></th>
</tr>
</thead>
<tbody>
{% for file in listFilesPrivate %}
<tr>
<td>{{ file[0] }}</td>
<td><a href="/myfiles/{{ username }}/{{ file[1] }}">{{ file[1] }}</a></td>
<td>{{ file[2] }}</td>
<td><a href="{{ url_for('filesupload.remove_privateFile', filename=file[1]) }}"><button type="button" class="btn btn-sm btn-danger">Supprimer</button></a></td>
<td><a href="{{ url_for('filesupload.move_public', filename=file[1]) }}"><button type="button" class="btn btn-sm btn-success"> Rendre Publique </button></a></td>
</tr>
{% endfor %}
</tbody>
</table>
{% else %}
<p> Vous n'avez aucun fichiers privés </p>
{% endif %}
<br />
<hr />
<br />
<h2> Fichiers publics (Tout le monde peut les voirs) </h2>
{% if listFilesPublic %}
<table class="table">
<thead>
<tr>
<th></th>
<th>Fichier(s) <span class="badge">{{ nb_pu }}</span></th>
<th>Taille (en Megaoctets)</th>
<th></th>
</tr>
</thead>
<tbody>
{% for file in listFilesPublic %}
<tr>
<td>{{ file[0] }}</td>
<td><a href="/public/{{ username }}/{{ file[1] }}">{{ file[1] }}</a></td>
<td>{{ file[2] }}</td>
<td><a href="{{ url_for('filesupload.remove_publicFile', filename=file[1]) }}"><button type="button" class="btn btn-sm btn-danger">Supprimer</button></a></td>
<td><a href="{{ url_for('filesupload.move_private', filename=file[1]) }}"><button type="button" class="btn btn-sm btn-success"> Rendre Privée </button></a></td>
</tr>
{% endfor %}
</tbody>
</table>
{% else %}
<p> Vous n'avez aucun fichiers publics </p>
{% endif %}
</div>
{% endblock %}

View File

@@ -1,8 +1,10 @@
<!DOCTYPE html>
<html lang="fr">
{% include '_head.html' %}
<head>
{% include '_head.html' %}
{% block css %} {% endblock %}
{% include 'css/simple_editor.html' %}
</head>
<body>
@@ -20,9 +22,13 @@
{% include '_footer.html' %}
{% include '_js-core.html' %}
{% include '_js-gallery.html' %}
</body>
{% include '_js-core.html' %}
{% block js %} {% endblock %}
</html>

View File

@@ -1,27 +0,0 @@
{% include '_nav_userlogin.html' %}
{% extends 'up_squelette.html' %}
{% block main %}
<br />
<p>Ici, vous pouvez envoyer des fichiers afin de les sauvegarder ou de les rendre accessibles à quelqu'un d'autre. Tu pourras ensuite
les consulter dans notre rubrique <a href="/view/"> Mes fichiers </a>. Les images envoyées, quand à elles se retrouveront directement
dans la <a href="/gallery/"> Gallerie</a>.<br>Ayez bien conscience que ce site est une expérience est qu'il est indispensable d'avoir
une sauvegarde de tous vos fichiers qui vous mettrez ici. Nous ne pourrons, en aucun cas, être tenu responsable de la perte de vos
données. Merci de votre compréhension.
</p>
<br />
<h3>Choisissez un ou plusieurs fichiers à téléverser </h3>
<form action="" method="post" enctype="multipart/form-data">
<input type="file" class="center" name="fic"id="fic" multiple>
<br>
<button type="submit" id="tada" class="btn btn btn-success"> Téléverser !</button>
</form>
{% endblock %}

View File

@@ -5,17 +5,12 @@ from tools.utils import gen_token
from flask_bcrypt import Bcrypt
app = Flask( 'pywallter' )
app.config.from_pyfile('config.py')
bcrypt = Bcrypt(app)
DATABASE = app.config['DATABASE']
DOSSIER_PERSO = app.config['DOSSIER_APP']
DATABASE = app.config['DATABASE']
def init_db():
conn = sqlite3.connect(DATABASE)
def init_db(database):
conn = sqlite3.connect(database)
cursor = conn.cursor()
cursor.execute("""
CREATE TABLE IF NOT EXISTS users(
@@ -37,27 +32,15 @@ def init_db():
Mail_rescue TEXT )
""")
conn.commit()
print ('table users Ok')
cursor.execute("""
CREATE TABLE IF NOT EXISTS posts(
id INTEGER PRIMARY KEY AUTOINCREMENT UNIQUE,
title TEXT,
content TEXT,
time TEXT,
category TEXT,
author TEXT,
status TEXT
)
""")
print ("table users Ok")
cursor.execute("""
CREATE TABLE IF NOT EXISTS Blog_posts(
title TEXT NOT NULL,
subtitle TEXT,
comments TEXT,
filename TEXT,
time TEXT,
content TEXT,
creation_date TEXT,
last_updated TEXT,
category TEXT,
author TEXT,
@@ -66,7 +49,7 @@ def init_db():
)
""")
conn.commit()
print ("Table Blog_posts Ok !")
cursor.execute("""select * from users""")
accounts = cursor.fetchall()
@@ -74,31 +57,38 @@ def init_db():
# pywallter qui permet la première inscription
if not(accounts) :
user = "pywallter"
token = gen_token()
token = gen_token("Invitation")
passwd_bcrypt = bcrypt.generate_password_hash(token)
cursor.execute("""INSERT INTO users(name, passwd, token) VALUES(?, ?, ?)""", (user, passwd_bcrypt, token))
conn.commit()
conn.close()
print ('table posts OK')
def init_dir():
if os.path.isdir('users'):
return False
else:
os.makedirs('./users/')
def check_directories(users_folder):
def db_migrate():
conn = sqlite3.connect(DATABASE)
if os.path.isdir(users_folder):
print("Le dossier {} existe".format(users_folder))
else:
os.makedirs(users_folder)
print("Le dossier {} a été créé".format(users_folder))
def db_migrate(database):
invitations_col = False
lost_password_token_col = False
totp_col = False
blog_unified_col = False
blog_theme_col = False
updated_col = False
creation_date_col = False
content_col = False
conn = sqlite3.connect(database)
cursor = conn.cursor()
cursor.execute("""SELECT name FROM PRAGMA_TABLE_INFO('users');""")
db_columns = cursor.fetchall()
invitations_col = False
blog_theme_col = False
updated_col = False
lost_password_token_col = False
totp_col = False
for col in db_columns:
if "invitations" == col[0]:
invitations_col = True
@@ -106,15 +96,21 @@ def db_migrate():
lost_password_token_col = True
if "totp" == col[0]:
totp_col = True
if "blog_theme" == col[0]:
blog_theme_col = True
cursor.execute("""SELECT name FROM PRAGMA_TABLE_INFO('Blog_posts');""")
db_columns = cursor.fetchall()
for col in db_columns:
if "blog_theme" == col[0]:
blog_theme_col = True
if "last_updated" == col[0]:
updated_col = True
if "content" == col[0]:
content_col = True
if "creation_date" == col[0]:
creation_date_col= True
if "category" == col[0]:
category_col= True
if not(invitations_col):
@@ -124,19 +120,18 @@ def db_migrate():
if not(lost_password_token_col):
cursor.execute("""ALTER TABLE Users ADD COLUMN Lost_password_token CHAR(64);""")
cursor.execute("""ALTER TABLE users ADD COLUMN Lost_password_token CHAR(64);""")
conn.commit()
print ("Ajout du champ Lost_password_token dans la table Users")
if not(totp_col):
cursor.execute("""ALTER TABLE Users ADD COLUMN totp CHAR(40);""")
cursor.execute("""ALTER TABLE users ADD COLUMN totp CHAR(40);""")
conn.commit()
print ("Ajout du champ totp dans la table Users")
if not(blog_theme_col):
cursor.execute("""ALTER TABLE Blog_posts ADD COLUMN blog_theme TEXT;""")
cursor.execute("""ALTER TABLE users ADD COLUMN blog_theme TEXT;""")
conn.commit()
print ("Ajout du champ blog_theme dans la table Blog")
@@ -146,5 +141,20 @@ def db_migrate():
print ("Ajout du champ updated dans la table BLog")
if not(content_col):
cursor.execute("""ALTER TABLE Blog_posts RENAME COLUMN filename TO content;""")
conn.commit()
print ("Filename renomé en content")
if not(creation_date_col):
cursor.execute("""ALTER TABLE Blog_posts RENAME COLUMN time TO creation_date;""")
conn.commit()
print ("Time renomé en creation_date")
if not(category_col):
cursor.execute("""ALTER TABLE Blog_posts ADD COLUMN category TEXT;""")
conn.commit()
print ("Ajout de la colono category")
conn.close()

View File

@@ -1,4 +1,5 @@
from flask import Flask
from flask import Flask, url_for, session, redirect, request
from functools import wraps
import sqlite3
import os
import string
@@ -14,28 +15,84 @@ DATABASE = app.config['DATABASE']
DOSSIER_PERSO = app.config['DOSSIER_APP']
DATABASE = app.config['DATABASE']
def login_required(f):
@wraps(f)
def decorated_function(*args, **kwargs):
if 'username' not in session:
return redirect(url_for('loginlogout.login', next=request.url))
return f(*args, **kwargs)
return decorated_function
def append_to_log(log_line, user):
tmp=""
log_file=os.path.join(DOSSIER_PERSO, user, "log.txt")
logs=open(log_file, "r")
tmp=logs.read()
logs.close()
if os.path.exists(log_file):
logs=open(log_file, "r")
tmp=logs.read()
logs.close()
log=open(log_file, "w")
log.write(log_line)
log.write(tmp)
log.close()
def valid_email(mail):
valid=True
# Caractères non autorisés dans la RFC #822
invalid_char = { '(', ')', '<', '>', ',', ';', ':', '"', '[', ']', '|', 'ç', '%', '&', ' ' }
mail_cut = mail.split('@')
tld = mail_cut[-1].split('.')
for character in invalid_char:
if character in mail:
valid=False
print(tld)
if len(mail_cut) > 1 and len(tld) > 1 and valid:
if len(tld[0]) < 1 or len(tld[-1]) < 2 :
valid=False
else:
valid=False
return valid
def valid_username(username):
valid=True
# Caractères non autorisés dans la RFC #822
invalid_char = { '(', ')', '<', '>', ',', ';', ':', '"', '[', ']', '|', 'ç', '%', '&', ' ' }
for character in invalid_char:
if character in username:
valid=False
conn = sqlite3.connect(DATABASE) # Connexion à la base de donnée
cursor = conn.cursor() # Création de l'objet "curseur"
cursor.execute("""SELECT name FROM users WHERE name=?""", (username,))
tmp = cursor.fetchone()
conn.close()
if tmp:
valid = True
else:
valid = False
return valid
def set_mail_domain():
mail_domain = ""
if app.config['MAIL_SERVER']:
mail_domain = app.config['MAIL_DOMAIN']
else:
mail_domain = None
return mail_domain
def email_disp(email):
disp = True
@@ -87,7 +144,7 @@ def valid_token_register(token, token_type):
cursor.execute("""SELECT name FROM users where token=?""", (token,))
tmp = cursor.fetchone()
conn.close()
print (tmp)
if tmp:
valid = True
else:
@@ -99,7 +156,6 @@ def valid_token_register(token, token_type):
def get_user_by_token(token, token_type):
if len(token) != 30 and len(token) != 64:
user = "Invalid Token"

View File

View File

@@ -7,6 +7,8 @@ import sqlite3
from markdown import markdown
from tools.filesutils import getFileSizeKo
import string
from shutil import copy
from tools.utils import login_required
blog = Blueprint('blog', __name__, template_folder='templates')
@@ -15,163 +17,294 @@ app.config.from_pyfile('config.py')
########################### Variables Globales #################################
extensionimg = app.config['EXT_IMG']
DATABASE = app.config['DATABASE']
BASE_URL= app.config['BASE_URL']
DOSSIER_PERSO= app.config['DOSSIER_APP']+'/'
DOSSIER_PUBLIC= app.config['DOSSIER_PUBLIC']+'/'
extensionimg = app.config.get('EXT_IMG')
DATABASE = app.config.get('DATABASE')
BASE_URL = app.config.get('BASE_URL')
DOSSIER_PERSO = app.config.get('DOSSIER_APP')+'/'
DOSSIER_PUBLIC = app.config.get('DOSSIER_PUBLIC')+'/'
TITLE_SERVER = app.config.get('TITLE_SERVER')
DESC_SERVER = app.config.get('DESC_SERVER')
################################################################################
@blog.route('/myblog/new-article/', methods=['GET', 'POST'])
@login_required
def new_article():
if 'username' in session:
user = '%s'% escape(session['username'])
folder_blog = DOSSIER_PERSO + user + "/blog/articles/"
if request.method == 'POST':
title = request.form['title']
subtitle = request.form['subtitle']
content = request.form['content']
status = request.form['status']
post_date = time.strftime("%d/%m/%Y %H:%M:%S")
filename = title.replace(" ", "_") + ".md"
user = '%s'% escape(session['username'])
if request.method == 'POST':
title = str(request.form['title'])
subtitle = str(request.form['subtitle'])
category = str(request.form['category'])
content = str(request.form['content'])
status = str(request.form['status'])
post_date = time.strftime("%d/%m/%Y %H:%M:%S")
if 'blog-unified' in request.form.keys():
status = status+'_unified'
conn = sqlite3.connect(DATABASE) # Connexion à la base de donnée
cursor = conn.cursor() # Création de l'objet "curseur"
cursor.execute("""INSERT INTO Blog_posts(title, subtitle, filename, time, author, status) VALUES(?, ?, ?, ?, ?, ?)""", (title, subtitle, filename, post_date, user, status)) # Insérer des valeurs
conn.commit()
## On génère le fichiers markdown
with open(folder_blog + filename, 'w') as f:
f.write(content)
return redirect(url_for('blog.list_articles_blog'))
else:
return render_template('new_article_blog.html')
else:
return redirect(BASE_URL, code=401)
@blog.route('/myblog/list-articles/', methods=['GET'])
def list_articles_blog():
if 'username' in session:
user = '%s'% escape(session['username'])
conn = sqlite3.connect(DATABASE) # Connexion à la base de donnée
cursor = conn.cursor() # Création de l'objet "curseur"
cursor.execute("""SELECT title, subtitle, time, last_updated, status FROM Blog_posts WHERE author=? """, (user,) )
list_posts=cursor.fetchall()
posts=list()
nb_articles=0
for post in list_posts:
posts.append(dict(title=post[0],
subtitle=post[1],
time=post[2],
last_updated=post[3],
status=post[4]))
nb_articles =+ 1
return render_template('list_articles.html',
section="Articles",
list_posts=posts,
nb_articles=nb_articles
)
else:
return redirect(BASE_URL, code=401)
@blog.route('/myblog/delete/<title>')
def delete(title):
if 'username' in session :
user='%s'% escape(session['username'])
folder_blog = DOSSIER_PERSO + user + "/blog/articles/"
folder_blog_public = DOSSIER_PUBLIC + user + "/blog/articles/"
filename = title.replace(" ", "_")
conn = sqlite3.connect(DATABASE) # Connexion à la base de donnée
cursor = conn.cursor() # Création de l'objet "curseur"
cursor.execute("""DELETE FROM Blog_posts WHERE title=? AND author=?""", (title, user))
cursor.execute("""INSERT INTO Blog_posts(title, subtitle, category, content, creation_date, author, status) VALUES(?, ?, ?, ?, ?, ?, ?)""", (title, subtitle, category, content, post_date, user, status)) # Insérer des valeurs
conn.commit()
conn.close()
os.remove(folder_blog+filename+".md")
os.remove(folder_blog_public+filename+".html")
return redirect(url_for('blog.list_articles_blog'))
else:
return redirect(BASE_URL, code=401) # sinon on redirige vers login
return render_template('new_article_blog.html')
@blog.route('/myblog/edit/<title>', methods=['GET', 'POST'])
@login_required
def edit(title):
if 'username' in session :
user='%s'% escape(session['username'])
filename = title.replace(" ", "_") + ".md"
folder_blog = DOSSIER_PERSO + user + "/blog/articles/"
if request.method == 'POST' :
subtitle = request.form['subtitle']
newcontent = request.form['content']
newstatus = request.form['status']
updated = time.strftime("%d/%m/%Y %H:%M:%S")
conn = sqlite3.connect(DATABASE)
cursor = conn.cursor()
cursor.execute("""UPDATE Blog_posts SET subtitle=?, last_updated=?, status=? WHERE title=? AND author=?""", (subtitle, updated, newstatus, title, user))
conn.commit()
conn.close()
with open(folder_blog + filename, 'w') as f:
f.write(newcontent)
return redirect(url_for('blog.list_articles_blog'))
else:
conn = sqlite3.connect(DATABASE) # Connexion à la base de donnée
cursor = conn.cursor() # Création de l'objet "curseur"
cursor.execute("""SELECT title, subtitle, status FROM Blog_posts WHERE title=? AND author=?""", (title, user))
oldpost = cursor.fetchone()
conn.close()
user='%s'% escape(session['username'])
folder_blog = DOSSIER_PERSO + user + "/blog/articles/"
if request.method == 'POST' :
newtitle = str(request.form['title'])
subtitle = str(request.form['subtitle'])
category = str(request.form['category'])
newcontent = str(request.form['content'])
newstatus = str(request.form['status'])
updated = time.strftime("%d/%m/%Y %H:%M:%S")
conn = sqlite3.connect(DATABASE)
cursor = conn.cursor()
if 'blog-unified' in request.form.keys():
newstatus = newstatus+'_unified'
with open(folder_blog + filename, 'r') as f:
content = f.read()
return render_template('edit_article.html',
section='Post-it',
oldpost=oldpost,
content=content)
cursor.execute("""UPDATE Blog_posts SET title=?, subtitle=?, category=?, last_updated=?, status=?, content=? WHERE title=? AND author=?""", (newtitle, subtitle, category, updated, newstatus, newcontent, title, user))
conn.commit()
conn.close()
return redirect(url_for('blog.list_articles_blog'))
else:
return redirect(BASE_URL, code=401)
conn = sqlite3.connect(DATABASE) # Connexion à la base de donnée
cursor = conn.cursor() # Création de l'objet "curseur"
cursor.execute("""SELECT title, subtitle, category, content, status FROM Blog_posts WHERE title=? AND author=?""", (title, user))
oldpost = cursor.fetchone()
conn.close()
post = dict(title=oldpost[0], subtitle=oldpost[1], category=oldpost[2], content=oldpost[3], status=oldpost[4])
return render_template('edit_article.html',
section='Post-it',
oldpost=post)
@blog.route('/blog/<username>/', methods=['GET'])
def view(username):
user = username
@blog.route('/myblog/list-articles/', methods=['GET'])
@login_required
def list_articles_blog():
user = '%s'% escape(session['username'])
conn = sqlite3.connect(DATABASE) # Connexion à la base de donnée
cursor = conn.cursor() # Création de l'objet "curseur"
cursor.execute("""SELECT title, subtitle, time, author FROM Blog_posts WHERE status='public' AND author=? """, (user,) )
cursor.execute("""SELECT title, subtitle, creation_date, last_updated, status FROM Blog_posts WHERE author=? """, (user,) )
list_posts=cursor.fetchall()
posts=list()
nb_articles=0
for post in list_posts:
posts = [dict(title=post[0],
subtitle=post[1],
time=post[2],
last_updated=post[3],
status=post[4])] + posts
nb_articles = nb_articles + 1
return render_template('list_articles.html',
section="Articles",
list_posts=posts,
nb_articles=nb_articles )
@blog.route('/myblog/delete/<title>')
@login_required
def delete(title):
title = escape(title)
user='%s'% escape(session['username'])
folder_blog = DOSSIER_PERSO + user + "/blog/articles/"
folder_blog_public = DOSSIER_PUBLIC + user + "/blog/articles/"
conn = sqlite3.connect(DATABASE) # Connexion à la base de donnée
cursor = conn.cursor() # Création de l'objet "curseur"
cursor.execute("""DELETE FROM Blog_posts WHERE title=? AND author=?""", (title, user))
conn.commit()
conn.close()
return redirect(url_for('blog.list_articles_blog'))
@blog.route('/myblog/personnalize/', methods=['GET', 'POST'])
@login_required
def personnalize_blog():
user='%s' % escape(session['username'])
conn = sqlite3.connect(DATABASE) # Connexion à la base de donnée
cursor = conn.cursor() # Création de l'objet "curseur"
cursor.execute("""SELECT blog_theme FROM users WHERE name=?""", (user,))
blog_info = cursor.fetchone()
conn.close()
blog_unified = blog_info[0]
if request.method == 'POST' :
f = request.files['personnal-blog-theme']
blog_theme = str(request.form['blog-theme'])
print(blog_theme)
if blog_theme != "Default":
copy( "static/blog-"+blog_theme+".css",
DOSSIER_PERSO+ user +'/blog.css' )
if f: # On vérifie qu'un fichier a bien été envoyé
nom = secure_filename(f.filename)
f.save(DOSSIER_PERSO + user + nom)
else:
conn = sqlite3.connect(DATABASE) # Connexion à la base de donnée
cursor = conn.cursor() # Création de l\'objet "curseur"
cursor.execute("UPDATE users SET blog_theme=? WHERE name=?", (blog_theme, user))
conn.commit()
return render_template('personnalize_blog.html', section='personnalize_blog', blog_theme=blog_info[0])
@blog.route('/myblog/', methods=['GET'])
@login_required
def viewmyblog():
user='%s' % escape(session['username'])
conn = sqlite3.connect(DATABASE) # Connexion à la base de donnée
cursor = conn.cursor() # Création de l'objet "curseur"
cursor.execute("""SELECT title, subtitle, creation_date, author, status FROM Blog_posts WHERE author=? AND status!='draft'""", (user,))
list_posts=cursor.fetchall()
posts=list()
id=0
conn.close()
print (list_posts)
if list_posts != None:
for post in list_posts:
posts=[dict(title=post[0], subtitle=post[1], creation_date=post[2], author=post[3], status=post[4])] + posts
return render_template('index_blog.html', section='Blog', posts=posts, author=user)
@blog.route('/private-blog/', methods=['GET'])
@login_required
def view_internal():
conn = sqlite3.connect(DATABASE) # Connexion à la base de donnée
cursor = conn.cursor() # Création de l'objet "curseur"
cursor.execute("""SELECT title, subtitle, content, creation_date, last_updated, author, status FROM Blog_posts WHERE status='private_unified' OR status='public_unified' """ )
list_posts=cursor.fetchall()
conn.close()
posts=list()
id=0
if list_posts != None:
for post in list_posts:
posts.append(dict(title=post[0], subtitle=post[1], time=post[2], author=post[3]))
posts = [dict(title=post[0], subtitle=post[1], content=post[2], creation_date=post[3], last_updated=post[4], author=post[5], status=post[6] )] + posts
else:
return redirect(BASE_URL, code=404)
return render_template('index_blog.html', section='Blog', posts=posts, user=user)
@blog.route('/blog/<username>/<title>', methods=['GET'])
def viewArticle(username, title):
folder_blog = DOSSIER_PERSO + username + "/blog/articles/"
filename = title.replace(" ", "_") + ".md"
user = username
return render_template('index_blog.html', section='Blog', posts=posts)
@blog.route('/blog/', methods=['GET'])
def view():
conn = sqlite3.connect(DATABASE) # Connexion à la base de donnée
cursor = conn.cursor() # Création de l'objet "curseur"
cursor.execute("""SELECT title, subtitle, time, author FROM Blog_posts WHERE author=? AND title=? """, (user, title) )
cursor.execute("""SELECT title, subtitle, creation_date, author, status FROM Blog_posts WHERE status='public_unified'""" )
list_posts=cursor.fetchall()
posts=list()
id=0
conn.close()
if list_posts != None:
for post in list_posts:
posts=[dict(title=post[0], subtitle=post[1], creation_date=post[2], author=post[3], status=post[4])] + posts
return render_template('index_blog.html', section='Blog', posts=posts)
@blog.route('/blog/<author>/', methods=['GET'])
def viewuser(author):
author = escape(author)
conn = sqlite3.connect(DATABASE) # Connexion à la base de donnée
cursor = conn.cursor() # Création de l'objet "curseur"
if 'username' in session :
cursor.execute("""SELECT title, subtitle, creation_date, last_updated, author, status FROM Blog_posts WHERE author=? AND status != 'draft' """, (author,))
else:
cursor.execute("""SELECT title, subtitle, creation_date, last_updated, author, status FROM Blog_posts WHERE author=? AND status='public' OR status='public_unified' """, (author,))
list_posts=cursor.fetchall()
posts=None
id=0
conn.close()
if list_posts != None:
posts=list()
for post in list_posts:
posts=[dict(title=post[0], subtitle=post[1], creation_date=post[2], last_updated=post[3], author=post[4], status=post[5])] + posts
return render_template('index_blog.html', section='Blog', posts=posts, author=author)
@blog.route('/blog/<author>/rss.xml', methods=['GET'])
def viewauthorrss(author):
conn = sqlite3.connect(DATABASE) # Connexion à la base de donnée
cursor = conn.cursor() # Création de l'objet "curseur"
cursor.execute("""SELECT MAX (creation_date) FROM Blog_posts WHERE author=? AND status='public_unified' OR status='public'""", (author, ) )
last_article_date = cursor.fetchone()
cursor.execute("""SELECT title, subtitle, content, creation_date, author, status FROM Blog_posts WHERE author=? AND status='public_unified' OR status='public'""", (author, ) )
list_posts=cursor.fetchall()
posts=list()
id=0
conn.close()
if list_posts != None:
last_build=last_article_date[0]
for post in list_posts:
posts=[dict(title=post[0], subtitle=post[1], content=markdown(post[2]), creation_date=post[3], author=post[4], status=post[5])] + posts
return render_template('blog_rss.xml',
base_url=BASE_URL,
blog_name=author,
last_build_date=last_build,
posts=posts)
@blog.route('/blog/private_unified/<username>/<title>', methods=['GET'])
@blog.route('/blog/private/<username>/<title>', methods=['GET'])
@login_required
def viewPrivateArticle(username, title):
user = escape(username)
title = escape(title)
conn = sqlite3.connect(DATABASE) # Connexion à la base de donnée
cursor = conn.cursor() # Création de l'objet "curseur"
cursor.execute("""SELECT title, subtitle, content, creation_date, last_updated, author, status FROM Blog_posts WHERE author=? AND title=? AND status!='draft' """, (user, title))
post = cursor.fetchone()
conn.close()
if post != None:
post_info = (dict(title=post[0], subtitle=post[1], time=post[2], author=post[3]))
with open(folder_blog + filename, 'r') as f:
content_md = f.read()
content = markdown(content_md)
post_info = (dict(title=post[0], subtitle=post[1], creation_date=post[3], last_updated=post[4],author=post[5]))
content = markdown(post[2])
return render_template('blog.html', post_info=post_info, content=content)
else:
flash(u"Cet article n'existe pas", 'error');
return redirect(url_for('blog'), code=404)
@blog.route('/blog/public_unified/<username>/<title>', methods=['GET'])
@blog.route('/blog/public/<username>/<title>', methods=['GET'])
def viewArticle(username, title):
user = username
conn = sqlite3.connect(DATABASE) # Connexion à la base de donnée
cursor = conn.cursor() # Création de l'objet "curseur"
cursor.execute("""SELECT title, subtitle, content, creation_date, last_updated, author FROM Blog_posts WHERE author=? AND title=? AND status='public_unified' """, (user, title) )
post = cursor.fetchone()
conn.close()
if post != None:
post_info = (dict(title=post[0], subtitle=post[1], creation_date=post[3], last_updated=post[4],author=post[5]))
content= markdown(post[2])
return render_template('blog.html', post_info=post_info, content=content)
else:
return redirect(url_for('blog.view'), code=404)
@blog.route('/blog/rss.xml', methods=['GET'])
def viewrss():
conn = sqlite3.connect(DATABASE) # Connexion à la base de donnée
cursor = conn.cursor() # Création de l'objet "curseur"
cursor.execute("""SELECT MAX(creation_date) FROM Blog_posts WHERE status='public_unified'""")
last_article_date = cursor.fetchone()
print (last_article_date[0])
cursor.execute("""SELECT title, subtitle, content, creation_date, author, status FROM Blog_posts WHERE status='public_unified'""" )
list_posts=cursor.fetchall()
posts=list()
id=0
conn.close()
if list_posts != None:
last_build=last_article_date[0]
for post in list_posts:
posts=[dict(title=post[0], subtitle=post[1], content=markdown(post[2]), creation_date=post[3], author=post[4], status=post[5])] + posts
return render_template('blog_rss.xml',
base_url=BASE_URL,
blog_name= TITLE_SERVER,
last_build_date=last_build,
blog_description=DESC_SERVER,
posts=posts)

View File

@@ -1,6 +1,7 @@
# -*- coding: utf-8 -*-
from flask import Blueprint, Flask, request, flash, render_template, url_for, session, redirect, abort, make_response, flash, abort, send_file, send_from_directory
from werkzeug.utils import secure_filename
from markupsafe import escape
from PIL import Image
@@ -9,187 +10,245 @@ import sqlite3
import os
from shutil import move
from tools.filesutils import getFileSizeMo, getFileSizeKo, check_and_create
from tools.utils import login_required
filesupload = Blueprint('filesupload', __name__, template_folder='templates')
app = Flask( 'pywallter' )
app.config.from_pyfile('config.py')
#### Variables ##################################################################################
#### Variables ####################################################################################
DOSSIER_PERSO= app.config.get('DOSSIER_APP')
DOSSIER_PUBLIC= app.config.get('DOSSIER_PUBLIC')
DOSSIER_PERSO= app.config['DOSSIER_APP']+'/'
DOSSIER_PUBLIC= app.config['DOSSIER_PUBLIC']+'/'
extensionimg = app.config['EXT_IMG']
DATABASE = app.config['DATABASE']
BASE_URL= app.config['BASE_URL']
extensionimg = app.config.get('EXT_IMG')
DATABASE = app.config.get('DATABASE')
BASE_URL= app.config.get('BASE_URL')
##################################################################################################
@filesupload.route( '/filesupload/', methods=['GET', 'POST'])
def uploadfiles():
if 'username' in session :
user = '%s'% escape(session['username'])
if request.method == 'POST' :
files = request.files.getlist('fic')
for f in files :
nom = secure_filename(f.filename)
check_and_create(DOSSIER_PERSO+ user + 'files')
check_and_create(DOSSIER_PERSO+ user + 'images')
if os.path.isfile(DOSSIER_PERSO + user + '/files/' + nom) or os.path.isfile(DOSSIER_PERSO + user + '/images/' + nom):
flash(u'Un fichier avec le même nom existe déjà, merci de spécifier un autre nom de fichier', 'error')
else:
file, ext = os.path.splitext(nom)
if ext in extensionimg :
f.save(DOSSIER_PERSO + user + '/images/' + nom)
image = DOSSIER_PERSO + user + '/images/' + nom
with Image.open(image) as img :
img.thumbnail((300,300))
img.save( DOSSIER_PERSO + user + '/images/thumbnails/' + nom )
TIME=time.strftime("%A %d %B %Y %H:%M:%S")
IP=request.environ['REMOTE_ADDR']
CLIENT_PLATFORM=request.headers.get('User-Agent')
log_file=os.path.join(DOSSIER_PERSO, user, "log.txt")
LOG=open(log_file, "a")
LOG.write (TIME + ' - ' + IP + ' - ' + user + ' - ' + CLIENT_PLATFORM + '\n' + '---> ' + nom + '\n')
LOG.close()
flash(u'Image envoyée et traitée avec succés', 'succes')
else:
f.save(DOSSIER_PERSO + user + '/files/' + nom)
TIME=time.strftime("%A %d %B %Y %H:%M:%S")
IP=request.environ['REMOTE_ADDR']
CLIENT_PLATFORM=request.headers.get('User-Agent')
LOG=open("log.txt", "a") # Ouvre fichier log.txt
LOG.write (TIME + ' - ' + IP + ' - ' + user + ' - ' + CLIENT_PLATFORM + '\n' + '---> ' + nom + '\n') # Écrit dans log
LOG.close() # Ferme log.txt
flash(u'Fichier envoyé avec succés', 'succes')
else:
flash(u'Error : Vous avez oublié le fichier !', 'error')
return redirect(url_for('filesupload.uploadfiles'))
resp = make_response(render_template('up_up.html', section="Upload"))
resp.set_cookie('username', session['username'])
return resp
else :
return redirect(BASE_URL, code=401)
@filesupload.route( '/filesupload/', methods=['POST'])
@login_required
def upload():
user = '%s'% escape(session['username'])
if 'fic' not in request.files:
flash(u'Mauvais format de ficher', 'error')
return redirect(request.url)
file = request.files['fic']
# If the user does not select a file, the browser submits an
# empty file without a filename.
if file.filename == '':
flash(u'Vous avez oubliez de selectionner un fichier', 'error' )
else:
files = request.files.getlist('fic')
for f in files :
nom = secure_filename(f.filename)
check_and_create(os.path.join(DOSSIER_PERSO, user, 'files'))
check_and_create(os.path.join(DOSSIER_PERSO, user, 'images'))
if os.path.isfile(os.path.join(DOSSIER_PERSO,user, 'files', nom) or
os.path.isfile(DOSSIER_PERSO, user, 'images', nom)):
alert = "Le fichier "+str(f.filename)+" avec le même nom existe déjà, merci de spécifier un autre nom de fichier \n"
flash(alert, 'error')
else:
file, ext = os.path.splitext(nom)
if ext in extensionimg :
f.save(os.path.join(DOSSIER_PERSO, user, 'images', nom))
image = os.path.join(DOSSIER_PERSO, user, 'images', nom)
with Image.open(image) as img :
img.thumbnail((300,300))
img.save(os.path.join(DOSSIER_PERSO, user, 'images','thumbnails', nom ))
time_img_create=time.strftime("%A %d %B %Y %H:%M:%S")
IP=request.environ['REMOTE_ADDR']
client_platform=request.headers.get('User-Agent')
log_file=os.path.join(DOSSIER_PERSO, user, "log.txt")
log=open(log_file, "a")
log.write (time_img_create + ' - ' + IP + ' - ' + user + ' - ' + client_platform + '\n' + '---> ' + nom + '\n')
log.close()
else:
f.save(os.path.join(DOSSIER_PERSO, user, 'files', nom))
time_file_upload=time.strftime("%A %d %B %Y %H:%M:%S")
IP=request.environ.get('REMOTE_ADDR')
client_platform=request.headers.get('User-Agent')
log=open("log.txt", "a") # Ouvre fichier log.txt
log.write (time_file_upload + ' - ' + IP + ' - ' + user + ' - ' + client_platform + '\n' + '---> ' + nom + '\n') # Écrit dans log
log.close() # Ferme log.txt
flash(u'Les fichiers envoyées ont été traitée avec succés', 'succes')
return redirect(url_for('filesupload.list'))
@filesupload.route( '/upload-dropzone', methods=['POST'])
@login_required
def drop_upload():
user = '%s'% escape(session['username'])
file = request.files['file']
check_and_create(os.path.join(DOSSIER_PERSO, user, 'files'))
check_and_create(os.path.join(DOSSIER_PERSO, user, 'images' ))
filename = secure_filename(file.filename)
ext = os.path.splitext(filename)
is_image = False
print("nom du fichier :" +filename)
if ext in extensionimg :
save_path = os.path.join(DOSSIER_PERSO, user, 'images', filename )
is_image = True
else:
save_path = os.path.join(DOSSIER_PERSO, user, 'files', filename )
current_chunk = int(request.form['dzchunkindex'])
print (current_chunk)
if (os.path.isfile(save_path) or os.path.isfile( os.path.join(DOSSIER_PERSO, user, 'images', filename ))) and current_chunk == 0:
return make_response(('Un fichier avec le même nom existe déjà', 400))
@filesupload.route('/view/')
try:
with open(save_path, 'ab') as f:
f.seek(int(request.form['dzchunkbyteoffset']))
f.write(file.stream.read())
except OSError:
return make_response(("Une erreur est survenue,"
" Impossible d'écrire le fichier sur le disque", 500))
total_chunks = int(request.form['dztotalchunkcount'])
if current_chunk + 1 == total_chunks:
# This was the last chunk, the file should be complete and the size we expect
if os.path.getsize(save_path) != int(request.form['dztotalfilesize']):
return make_response(('La taille du fichier source est différentes', 500))
else:
time_file_upload=time.strftime("%A %d %B %Y %H:%M:%S")
IP=request.environ['REMOTE_ADDR']
client_platform=request.headers.get('User-Agent')
log=open("log.txt", "a") # Ouvre fichier log.txt
log.write (time_file_upload + ' - ' + IP + ' - ' + user + ' - ' + client_platform + '\n' + '---> ' + filename + '\n') # Écrit dans log
log.close() # Ferme log.txt
if is_image :
with Image.open(save_path) as img :
img.thumbnail((300,300))
img.save(os.path.join(DOSSIER_PERSO, user, 'images', 'thumbnails', filename ) )
return make_response(('Chunk upload succesfull', 200))
@filesupload.route('/view/', methods=['GET'])
@login_required
def list():
if 'username' in session :
user = '%s'% escape(session['username'])
check_and_create(DOSSIER_PUBLIC + user + '/files/')
check_and_create(DOSSIER_PERSO + user + '/files/')
files_public = os.listdir(DOSSIER_PUBLIC + user + '/files/')
files_private = os.listdir(DOSSIER_PERSO + user + '/files/')
listFilesPublic = []
listFilesPrivate = []
nb_pv = 0
size=0
if files_private:
for fich in files_private:
nb_pv += 1
size = getFileSizeMo(DOSSIER_PERSO + user + '/files/' + fich) # size = taille des fichiers
listFilesPrivate.append([nb_pv, fich, size]) # On implémente la listeFichiers avec le num le ficier et sa taille
nb_pu = 0
if files_public:
for fich in files_public:
nb_pu += 1
size = getFileSizeMo(DOSSIER_PUBLIC + user + '/files/' + fich) # size = taille des fichiers
listFilesPublic.append([nb_pu, fich, size])
return render_template('up_list.html',
section="Files",
size=size,
username=user,
nb_pv=nb_pv,
nb_pu=nb_pu,
listFilesPrivate=listFilesPrivate,
listFilesPublic=listFilesPublic)
user = '%s'% escape(session['username'])
else :
return redirect(BASE_URL, code=401)
check_and_create(os.path.join(DOSSIER_PUBLIC, user, 'files'))
check_and_create(os.path.join(DOSSIER_PERSO, user, 'files'))
return render_template('files.html',
section="Files",
BASE_URL=BASE_URL,
username=user)
@filesupload.route('/files/<status>/', methods=['GET'])
@login_required
def list_files(status: str ):
user = '%s' % escape(session['username'])
listFiles = []
nb_files = 0
size=0
folder=""
if status == "public":
folder=DOSSIER_PUBLIC
else:
folder=DOSSIER_PERSO
files = os.listdir(os.path.join(folder, user, 'files'))
if files:
for fich in files:
nb_files += 1
size = getFileSizeMo(os.path.join(folder, user, 'files', fich)) # size = taille des fichiers
listFiles.append([nb_files, fich, size]) # On implémente la listeFichiers avec le num le ficier et sa taille
resp = "<h2> Bonjour " + user +" ça va bien putain ca marche ? </h2>"
return render_template('list_files.html',
BASE_URL=BASE_URL,
status=status,
size=size,
username=user,
nb_files=nb_files,
listFiles=listFiles)
@filesupload.route('/myfiles/<username>/<filename>')
@login_required
def myfiles(username, filename):
if 'username' in session :
user = '%s' % escape(session['username'])
return send_from_directory(
os.path.join(DOSSIER_PERSO, username, 'files'), filename )
else :
return redirect(BASE_URL, code=401)
@filesupload.route('/make_public/<filename>')
def move_public(filename):
if 'username' in session:
user = '%s' % escape(session['username'])
check_and_create(DOSSIER_PUBLIC + user + '/files/')
check_and_create(DOSSIER_PERSO + user + '/files/')
src = os.path.join(DOSSIER_PERSO, user, 'files', filename)
dst = os.path.join(DOSSIER_PUBLIC, user, 'files/')
move (src, dst)
return redirect(url_for('filesupload.list', _external=True))
else:
return redirect(BASE_URL, code=401)
@filesupload.route('/make_private/<filename>')
def move_private(filename):
if 'username' in session:
user = '%s' % escape(session['username'])
check_and_create(DOSSIER_PUBLIC + user + '/files/')
check_and_create(DOSSIER_PERSO + user + '/files/')
src = os.path.join(DOSSIER_PUBLIC, user, 'files', filename)
dst = os.path.join(DOSSIER_PERSO, user, 'files/')
move (src, dst)
return redirect(url_for('filesupload.list', _external=True))
else:
return redirect(BASE_URL, code=401)
@filesupload.route('/public/<username>/<filename>')
def publicfiles(username, filename):
user = '%s' % escape(session['username'])
return send_from_directory(
os.path.join(DOSSIER_PUBLIC, username, 'files'), filename )
os.path.join(DOSSIER_PERSO, username, 'files'), filename )
@filesupload.route('/make_public/<filename>')
@login_required
def move_public(filename):
user = '%s' % escape(session['username'])
src = os.path.join(DOSSIER_PERSO, user, 'files', filename)
dst = os.path.join(DOSSIER_PUBLIC, user, 'files')
move (src, dst)
return redirect(url_for('filesupload.list', _external=True))
@filesupload.route('/make_private/<filename>')
@login_required
def move_private(filename):
user = '%s' % escape(session['username'])
src = os.path.join(DOSSIER_PUBLIC, user, 'files', filename)
dst = os.path.join(DOSSIER_PERSO, user, 'files')
move (src, dst)
return redirect(url_for('filesupload.list', _external=True))
@filesupload.route('/remove_privateFile/<filename>')
@login_required
def remove_privateFile(filename):
if 'username' in session :
user = '%s' % escape(session['username'])
filename = secure_filename(filename)
try:
os.remove(DOSSIER_PERSO + user + '/files/' + filename) # on le supprime
except FileNotFoundError:
flash(u'Fichier {filename} inexistant.'.format(filename=filename), 'error')
return redirect(url_for('filesupload.list', _external=True))
else :
return redirect(BASE_URL, code=401)
user = '%s' % escape(session['username'])
filename = secure_filename(filename)
try:
os.remove( os.path.join(DOSSIER_PERSO, user, 'files', filename)) # on le supprime
except FileNotFoundError:
flash(u'Fichier {filename} inexistant.'.format(filename=filename), 'error')
return redirect(url_for('filesupload.list', _external=True))
@filesupload.route('/remove_publicFile/<filename>')
@login_required
def remove_publicFile(filename):
if 'username' in session :
user = '%s' % escape(session['username'])
filename = secure_filename(filename)
try:
os.remove(DOSSIER_PUBLIC + user + '/files/' + filename) # on le supprime
except FileNotFoundError:
flash(u'Fichier {filename} inexistant.'.format(filename=filename), 'error')
return redirect(url_for('filesupload.list', _external=True))
else :
return redirect(BASE_URL, code=401)
user = '%s' % escape(session['username'])
filename = secure_filename(filename)
try:
os.remove( os.path.join(DOSSIER_PUBLIC, user, 'files', filename)) # on le supprime
except FileNotFoundError:
flash(u'Fichier {filename} inexistant.'.format(filename=filename), 'error')
return redirect(url_for('filesupload.list', _external=True))
@filesupload.route('/<author>/blog.css')
def blog_theme(author):
user = author
if os.path.isfile(os.path.join(DOSSIER_PERSO, user,'blog.css')):
return send_file(os.path.join(DOSSIER_PERSO, user, 'blog.css'), mimetype='text/css')
else:
return send_file("static/blog.css", mimetype='text/css')
@filesupload.route('/theme.min.css')
def theme():
if 'username' in session:
user = '%s' % escape(session['username'])
if os.path.isfile(DOSSIER_PERSO+ user +'/theme.min.css'):
return send_file(DOSSIER_PERSO+ user +'/theme.min.css', mimetype='text/css')
else:
return send_file("static/default.min.css", mimetype='text/css')
if os.path.isfile(os.path.join(DOSSIER_PERSO, user,'theme.min.css')):
return send_file(os.path.join(DOSSIER_PERSO, user,'theme.min.css'), mimetype='text/css')
return send_file("static/default.min.css", mimetype='text/css')
@filesupload.route('/public/<username>/<filename>')
def publicfiles(username, filename):
return send_from_directory(
os.path.join(DOSSIER_PUBLIC, username, 'files'), filename )

View File

@@ -8,6 +8,7 @@ import time
import sqlite3
import os
from tools.filesutils import check_and_create
from tools.utils import login_required
mygallery = Blueprint('mygallery', __name__, template_folder='templates')
@@ -17,69 +18,66 @@ app.config.from_pyfile('config.py')
#### Variables ##################################################################################
DOSSIER_PERSO= app.config['DOSSIER_APP']
DOSSIER_PUBLIC= app.config['DOSSIER_PUBLIC']+'/'
extensionimg = app.config['EXT_IMG']
DOSSIER_PERSO = app.config.get("DOSSIER_APP")
DOSSIER_PUBLIC = app.config.get("DOSSIER_PUBLIC")
extensionimg = app.config.get("EXT_IMG")
DATABASE = app.config['DATABASE']
DATABASE = app.config.get("DATABASE")
#################################################################################################
@mygallery.route( '/gallery/')
@login_required
def gallery():
if 'username' in session :
user ='%s' % escape(session['username'])
check_and_create(DOSSIER_PUBLIC + user + '/images/')
check_and_create(DOSSIER_PUBLIC + user + '/images/thumbnails/')
check_and_create(DOSSIER_PERSO + user + '/images/')
check_and_create(DOSSIER_PERSO + user + '/images/thumbnails/')
THUMBNAILS=DOSSIER_PERSO + user + '/images/thumbnails/'
fichiers = [fich for fich in os.listdir(THUMBNAILS)]
return render_template('gallery.html',
section='Gallery',
THUMBNAILS=THUMBNAILS,
fichiers=fichiers)
else :
return redirect(url_for('loginlogout.login'), code=401)
user ='%s' % escape(session['username'])
print (DOSSIER_PUBLIC)
print (os.path.join(DOSSIER_PUBLIC, user , 'images'))
check_and_create(os.path.join(DOSSIER_PUBLIC, user , 'images'))
check_and_create(os.path.join(DOSSIER_PUBLIC, user , 'images', 'thumbnails'))
check_and_create(os.path.join(DOSSIER_PERSO , user , 'images'))
check_and_create(os.path.join(DOSSIER_PERSO, user, 'images','thumbnails'))
THUMBNAILS=os.path.join(DOSSIER_PERSO, user, 'images','thumbnails')
fichiers = [fich for fich in os.listdir(THUMBNAILS)]
return render_template('gallery.html',
section='Gallery',
THUMBNAILS=THUMBNAILS,
fichiers=fichiers)
@mygallery.route('/myfiles/images/<filename>')
@login_required
def myimg(filename):
if 'username' in session :
UTILISATEUR='%s' % escape(session['username'])
return send_from_directory(
os.path.join(DOSSIER_PERSO, UTILISATEUR, 'images'), filename )
else :
return redirect(BASE_URL, code=401)
user = '%s' % escape(session['username'])
return send_from_directory(
os.path.join(DOSSIER_PERSO, user, 'images'), filename )
@mygallery.route('/myfiles/images/thumbnails/<filename>')
@login_required
def mythumbnails(filename):
if 'username' in session :
UTILISATEUR='%s' % escape(session['username'])
return send_from_directory(
os.path.join(DOSSIER_PERSO, UTILISATEUR, 'images/thumbnails'), filename )
else :
return redirect(BASE_URL, code=401)
user='%s' % escape(session['username'])
return send_from_directory(
os.path.join(DOSSIER_PERSO, user, 'images','thumbnails'), filename )
@mygallery.route('/remove_privateImage/<filename>')
@login_required
def remove_privateImage(filename):
if 'username' in session :
user = '%s' % escape(session['username'])
filename = secure_filename(filename)
try:
os.remove(DOSSIER_PERSO + user + '/images/thumbnails/' + filename) # on le supprime
os.remove(DOSSIER_PERSO + user + '/images/' + filename) # on le supprime
except FileNotFoundError:
flash(u'Image {filename} inexistante.'.format(filename=filename), 'error')
return redirect(url_for('mygallery.gallery'))
user = '%s' % escape(session['username'])
filename = secure_filename(filename)
try:
os.remove(os.path.join(DOSSIER_PERSO, user, 'images','thumbnails', filename)) # on le supprime
os.remove(os.path.join(DOSSIER_PERSO, user, 'images', filename)) # on le supprime
except FileNotFoundError:
flash(u'Image {filename} inexistante.'.format(filename=filename), 'error')
return redirect(url_for('mygallery.gallery'))
@mygallery.route('/remove_publicImage/<filename>')
@login_required
def remove_publicImage(filename):
if 'username' in session :
user = '%s' % escape(session['username'])
filename = secure_filename(filename)
try:
os.remove(DOSSIER_PUBLIC + user + '/images/thumbnails/' + filename) # on le supprime
os.remove(DOSSIER_PUBLIC + user + '/images/' + filename) # on le supprime
except FileNotFoundError:
flash(u'Image {filename} inexistante.'.format(filename=filename), 'error')
return redirect(url_for('mygallery.gallery'))
user = '%s' % escape(session['username'])
filename = secure_filename(filename)
try:
os.remove(os.path.join(DOSSIER_PUBLIC, user, 'images','thumbnails', filename)) # on le supprime
os.remove(os.path.join(DOSSIER_PUBLIC, user, 'images', filename)) # on le supprime
except FileNotFoundError:
flash(u'Image {filename} inexistante.'.format(filename=filename), 'error')
return redirect(url_for('mygallery.gallery'))

View File

@@ -1,25 +1,29 @@
from flask import Blueprint, Flask, request, flash, render_template, url_for, session, redirect, abort, make_response, send_file
from flask_bcrypt import Bcrypt
import time
from markupsafe import escape
import sqlite3
import glob, os, sys, time
from tools.utils import email_disp, valid_token_register, valid_passwd, valid_username
from socket import gethostname
import subprocess
from tools.filesutils import check_and_create
from tools.utils import set_mail_domain
app = Flask( 'pywallter' )
app.config.from_pyfile('config.py')
bcrypt = Bcrypt(app)
#### Variables ##################################################################################
DATAS_USER = app.config.get('DOSSIER_APP')
DOSSIER_PERSO = app.config.get('DOSSIER_APP')
extensionimg = app.config.get('EXT_IMG')
DATABASE = app.config.get('DATABASE')
MAIL_SERVER = app.config.get('MAIL_SERVER')
XMPP_SERVER = app.config.get('XMPP_SERVER')
SETUID = app.config.get('SETUID')
BASE_URL = app.config.get('BASE_URL')
DATAS_USER = app.config['DOSSIER_APP']
extensionimg = app.config['EXT_IMG']
DATABASE = app.config['DATABASE']
MAIL_SERVER = app.config['MAIL_SERVER']
XMPP_SERVER = app.config['XMPP_SERVER']
SETUID = app.config['SETUID']
BASE_URL = app.config['BASE_URL']
#################################################################################################
@@ -28,24 +32,35 @@ inscription = Blueprint('inscription', __name__, template_folder='templates')
@inscription.route( '/inscription/<token>', methods=['GET','POST'] )
def signin(token) :
hostname = gethostname()
url_inscription = url_for('loginlogout.index', _external=True)+'inscription/'+token
mail_domain = set_mail_domain()
url_inscription = BASE_URL +'/'+'inscription/'+token
resp = None
if valid_token_register(token, "Invitation"):
if 'username' in session :
resp = redirect(url_for('profil.profile', _external=True))
else :
# Réponse si la requete est de type GET ou si la requete POST echoue
resp = render_template('inscription.html',
signin_enable=app.config['SIGNIN_ENABLE'],
token=token, hostname=hostname,
token=token, hostname=mail_domain,
url_inscription=url_inscription,
MAIL_SERVER=MAIL_SERVER, XMPP_SERVER=XMPP_SERVER)
if request.method == 'POST':
# Une fois que tout c'est bien passé pour l'inscription on détruit le jeton.
conn = sqlite3.connect(DATABASE) # Connexion à la base de donnée
cursor = conn.cursor() # Création de l'objet "curseur"
cursor.execute("""SELECT name, invitations FROM users where Token=?""", (token,))
tmp = cursor.fetchone()
user_invite = tmp[0]
invitations_count=tmp[1]
conn.close()
#On test si aucun champs du formulaire n'est vide.
if len(request.form['user']) == 0 or \
len(request.form['passwd']) == 0 or \
@@ -54,38 +69,31 @@ def signin(token) :
flash(u'Il faut remplir le formulaire en entier, les champs ne peuvent pas etre vide ', 'error')
return render_template('inscription.html',
signin_enable=app.config['SIGNIN_ENABLE'],
token=token, hostname=hostname,
token=token, hostname=mail_domain,
url_inscription=url_inscription,
MAIL_SERVER=MAIL_SERVER, XMPP_SERVER=XMPP_SERVER)
user = request.form['user']
passwd = request.form['passwd']
passwdconfirm = request.form['passwdconfirm']
bcrypt_passwd = bcrypt.generate_password_hash(request.form['passwd'])
user = str(request.form['user'])
passwd = str(request.form['passwd'])
passwdconfirm = str(request.form['passwdconfirm'])
bcrypt_passwd = bcrypt.generate_password_hash(passwd)
mail_passwd_change = 0
password_valid = valid_passwd(passwd)
not_error = True
conn = sqlite3.connect(DATABASE) # Connexion à la base de donnée
cursor = conn.cursor() # Création de l'objet "curseur"
cursor.execute("""SELECT name FROM users WHERE name=?""", (user,))
testuser = cursor.fetchone()
conn.close()
if testuser and valid_username(user):
flash(u'Non d\'utilisateur déjà utilisé ou contient des caractères invalides, merci d\'en choisir un autre', 'error')
if invitations_count < 1 :
flash(u'Votre lien d\'inscription est invalide', 'error')
not_error=False
if not_error and valid_username(user):
flash(u'Le Nom d\'utilisateur déjà utilisé ou contient des caractères invalides, merci d\'en choisir un autre', 'error')
flash(u'Les caractères espaces et \' ( ) < > , ; : " [ ] | ç % & ne sont pas autorisés', 'error')
not_error = False
if not(password_valid):
flash (u'Les caractère & et " ne sont pas autorisé dans les mots de passe', 'error')
not_error = False
if MAIL_SERVER:
mail = user.lower()+'@'+hostname
mail = user.lower()+'@'+mail_domain
if not(email_disp(mail)) :
flash(u'Cette Adresse email est déjà utilisée , merci d\'en choisir une autre', 'error')
not_error = False
@@ -102,38 +110,53 @@ def signin(token) :
# On change le mot de passe du compte mail
if MAIL_SERVER:
cmd = SETUID + ' set_mail_passwd ' + '"'+mail+'" ' + '"'+passwd+'"'
mail_passwd_change = os.system(cmd)
if mail_passwd_change != 0:
cmd = subprocess.run([SETUID, 'set_mail_passwd', mail, passwd],stdout=subprocess.PIPE)
if cmd.returncode != 0:
flash(u'Il y a eu une problème lors du changement de mot passe pour le compte Mail', 'error')
else:
cursor.execute("UPDATE users SET mail=? WHERE name=?", mail, user)
cursor.execute("UPDATE users SET mail=? WHERE name=?", (mail, user))
# On change le mot de passe du compte XMPP
if XMPP_SERVER:
tmp = mail.split('@')
cmd = SETUID+ ' prosodyctl register ' "'"+tmp[0]+"' " + "'"+tmp[1]+"' " + "'"+passwd+"'"
res = os.system(cmd)
if res != 0:
jid = mail.split('@')
cmd = subprocess.run([SETUID,'prosodyctl', 'register', jid[0],jid[1],passwd],stdout=subprocess.PIPE)
if cmd.returncode != 0:
flash(u'Il y a eu un problème pour la création du compte XMPP !', 'error')
check_and_create(DOSSIER_PERSO + user)
time_create_user = time.strftime("%A %d %B %Y %H:%M:%S")
ip_address=request.environ['REMOTE_ADDR']
client_plateform=request.headers.get('User-Agent')
log=time_create_user + ' - ' + ip_address + ' - ' + user + ' - ' + client_plateform + '\n' + '---> ' + 'Création du compte \n'
log_file=os.path.join(DOSSIER_PERSO,user,"log.txt")
try:
with open(log_file, 'x') as file:
file.write(log)
except FileExistsError:
print('The file '+log_file +' already exists')
# Une fois que tout c'est bien passé pour l'inscription on détruit le jeton.
cursor.execute("""SELECT name, invitations FROM users where Token=?""", (token,))
tmp = cursor.fetchone()
username = tmp[0]
invitations_count=tmp[1] - 1
if username == "pywallter":
cursor.execute("""DELETE from users where name = ?""", (username,))
if user_invite == "pywallter":
cursor.execute("""DELETE from users where name = ?""", (user_invite,))
elif invitations_count > 0:
invitations_count= invitations_count - 1
cursor.execute("""UPDATE users set invitations=? where name=?""", (invitations_count, user_invite,))
else:
cursor.execute("""UPDATE users set invitations=?, Token='' where name=?""", (invitations_count, username,))
cursor.execute("""UPDATE users set invitations=?, Token='' where name=?""", (invitations_count, user_invite,))
conn.commit()
flash(u'Inscription réalisée avec succés !', 'succes')
flash(u'Inscription réalisée avec succés !', 'success')
resp = redirect(url_for('loginlogout.login'))
else:
return render_template('inscription.html',
signin_enable=app.config['SIGNIN_ENABLE'],
token=token, hostname=mail_domain,
url_inscription=url_inscription,
MAIL_SERVER=MAIL_SERVER, XMPP_SERVER=XMPP_SERVER)
else:
resp = redirect(BASE_URL, code=401)
return resp

View File

@@ -6,6 +6,7 @@ from socket import gethostname
from os import remove, system
from tools.utils import email_disp, valid_token_register, valid_passwd, valid_username, gen_token, totp_is_valid
from tools.mailer import Mailer
from tools.utils import login_required
app = Flask( 'pywallter' )
app.config.from_pyfile('config.py')
@@ -14,31 +15,60 @@ bcrypt = Bcrypt(app)
#### Variables ####################################################################################
bcrypt = Bcrypt(app)
DATAS_USER = app.config['DOSSIER_APP']
DATAS_USER = app.config.get('DOSSIER_APP')
extensionimg = app.config['EXT_IMG']
extensionimg = app.config.get('EXT_IMG')
DATABASE = app.config['DATABASE']
BASE_URL = app.config['BASE_URL']
SETUID = app.config['SETUID']
MAIL_SERVER = app.config['MAIL_SERVER']
XMPP_SERVER = app.config['XMPP_SERVER']
BACKUP_TIME = app.config['BACKUP_TIME']
DATABASE = app.config.get('DATABASE')
BASE_URL = app.config.get('BASE_URL')
SETUID = app.config.get('SETUID')
MAIL_SERVER = app.config.get('MAIL_SERVER')
XMPP_SERVER = app.config.get('XMPP_SERVER')
BACKUP_TIME = app.config.get('BACKUP_TIME')
SERVER_TITLE = app.config.get('TITLE_SERVER')
SERVER_DESC = app.config.get('DESC_SERVER')
##################################################################################################
loginlogout = Blueprint('loginlogout', __name__, template_folder='templates')
@loginlogout.route( '/' )
def index():
conn = sqlite3.connect(DATABASE) # Connexion à la base de donnée
cursor = conn.cursor() # Création de l'objet "curseur"
cursor.execute("""SELECT token passwd FROM users where name=? """, ("pywallter", ))
tmp = cursor.fetchone()
conn.close
if tmp:
token = tmp[0]
else:
token = None
if 'username' in session :
return redirect(url_for('profil.profile'))
else :
if token:
hostname = gethostname()
url_inscription = BASE_URL+'/inscription/'+token
return render_template('inscription.html', signin_enable=app.config['SIGNIN_ENABLE'],
token=token, hostname=hostname,
url_inscription=url_inscription,
server_title=SERVER_TITLE,
mail_server=MAIL_SERVER)
else:
return redirect(url_for('loginlogout.login', _external=True))
@loginlogout.route( '/login/', methods=['GET','POST'] )
def login() :
def login():
if 'username' in session :
resp = redirect(url_for('profil.profile', _external=True))
else :
resp = redirect(url_for('loginlogout.login', _external=True))
if request.method == 'POST' :
user = request.form['user']
password = request.form['passwd']
totp = request.form['code_totp']
user = str(request.form['user'])
password = str(request.form['passwd'])
totp = str(request.form['code_totp'])
conn = sqlite3.connect(DATABASE) # Connexion à la base de donnée
cursor = conn.cursor() # Création de l'objet "curseur"
@@ -58,70 +88,87 @@ def login() :
else:
flash(u"L'utilisateur n'existe pas", 'error')
else:
resp = render_template('accueil.html', signin_enable=app.config['SIGNIN_ENABLE'])
conn = sqlite3.connect(DATABASE) # Connexion à la base de donnée
cursor = conn.cursor() # Création de l'objet "curseur"
cursor.execute("""SELECT title, subtitle, creation_date, last_updated, author FROM Blog_posts WHERE status='public_unified'""" )
list_posts=cursor.fetchall()
posts=list()
id=0
conn.close()
if list_posts != None:
for post in list_posts:
posts=[dict(title=post[0], subtitle=post[1], creation_date=post[2], last_updated=post[3], author=post[4])] + posts
resp = render_template('accueil.html',
server_title=SERVER_TITLE,
server_desc=SERVER_DESC,
mail_server=MAIL_SERVER,
list_posts=posts,
signin_enable=app.config['SIGNIN_ENABLE'])
return resp
@loginlogout.route( '/logout/' )
@login_required
def logout():
session.pop('username', None) # Supprimer username de la session s'il s'y trouve
return redirect(url_for('loginlogout.index'))
@loginlogout.route( '/delete_me/', methods=['GET','POST'])
@login_required
def delete_account():
if 'username' in session :
user='%s'% escape(session['username'])
resp = render_template('delete_account.html', time_backup=BACKUP_TIME)
if request.method == 'POST' :
conn = sqlite3.connect(DATABASE) # Connexion à la base de donnée
cursor = conn.cursor() # Création de l'objet "curseur"
cursor.execute("""SELECT passwd FROM users WHERE name=?""", (user,))
passwd = cursor.fetchone()[0].decode()
conn.close()
password = request.form['passwd']
if bcrypt.check_password_hash(passwd, password) is True:
not_error = True
user='%s'% escape(session['username'])
resp = render_template('delete_account.html', time_backup=BACKUP_TIME)
if request.method == 'POST' :
conn = sqlite3.connect(DATABASE) # Connexion à la base de donnée
cursor = conn.cursor() # Création de l'objet "curseur"
cursor.execute("""SELECT passwd FROM users WHERE name=?""", (user,))
passwd = cursor.fetchone()[0].decode()
conn.close()
password = request.form['passwd']
if bcrypt.check_password_hash(passwd, password) is True:
not_error = True
try:
cmd = 'rm -r ' + DATAS_USER + '/' + user
if system(cmd) != 0:
raise TypeError("Remove directory error")
except:
not_error = False
flash(u'Erreur lors de la suppression de votre dossier utilisateur.', 'error')
if MAIL_SERVER:
try:
cmd = 'rm -r ' + DATAS_USER + '/' + user
if system(cmd) != 0:
raise TypeError("Remove directory error")
cmd = SETUID + ' set_mail_passwd del' + '"'+mail+'"'
system(cmd)
except:
not_error = False
flash(u'Erreur lors de la suppression de votre dossier utilisateur.', 'error')
if MAIL_SERVER:
try:
cmd = SETUID + ' set_mail_passwd del' + '"'+mail+'"'
system(cmd)
except:
not_error = False
flash(u'Erreur lors de la suppression de votre compte Mail.', 'error')
flash(u'Erreur lors de la suppression de votre compte Mail.', 'error')
if XMPP_SERVER:
try:
tmp = mail.split('@')
cmd = SETUID+ ' prosodyctl deluser ' "'"+tmp[0]+"' " + "'"+tmp[1]+"'"
system(cmd)
except:
not_error = False
flash(u'Erreur lors de la suppression de votre compte XMPP.', 'error')
if XMPP_SERVER:
try:
tmp = mail.split('@')
cmd = SETUID+ ' prosodyctl deluser ' "'"+tmp[0]+"' " + "'"+tmp[1]+"'"
system(cmd)
except:
not_error = False
flash(u'Erreur lors de la suppression de votre compte XMPP.', 'error')
if not_error:
try:
conn = sqlite3.connect(DATABASE)
cursor = conn.cursor()
cursor.execute("""DELETE FROM users WHERE name=?""", (user,))
conn.commit()
conn.close()
except:
flash(u'Erreur lors de la suppression de votre compte.', 'error')
else:
flash(u'Désinscription réalisé avec succés, y\'a plus rien !', 'succes')
resp = redirect(url_for('loginlogout.logout'))
if not_error:
try:
conn = sqlite3.connect(DATABASE)
cursor = conn.cursor()
cursor.execute("""DELETE FROM users WHERE name=?""", (user,))
conn.commit()
conn.close()
except:
flash(u'Erreur lors de la suppression de votre compte.', 'error')
else:
flash(u'Mauvais mot de passe', 'error')
return resp
flash(u'Désinscription réalisé avec succés, y\'a plus rien !', 'succes')
resp = redirect(url_for('loginlogout.logout'))
else:
flash(u'Mauvais mot de passe', 'error')
return resp
@loginlogout.route( '/lost_password/', methods=['GET', 'POST'])
@@ -160,27 +207,3 @@ def lost_password():
return render_template('lost_password.html')
@loginlogout.route( '/' )
def index():
conn = sqlite3.connect(DATABASE) # Connexion à la base de donnée
cursor = conn.cursor() # Création de l'objet "curseur"
cursor.execute("""SELECT token passwd FROM users where name=? """, ("pywallter", ))
tmp = cursor.fetchone()
conn.close
if tmp:
token = tmp[0]
else:
token = None
if 'username' in session :
return redirect(url_for('profil.profile'))
else :
if token:
hostname = gethostname()
url_inscription = BASE_URL+'inscription/'+token
return render_template('inscription.html', signin_enable=app.config['SIGNIN_ENABLE'],
token=token, hostname=hostname,
url_inscription=url_inscription,
MAIL_SERVER=MAIL_SERVER)
else:
return redirect(url_for('loginlogout.login', _external=True))

View File

@@ -1,6 +1,7 @@
from flask import Blueprint, Flask, request, flash, render_template, url_for, session, redirect, abort, make_response, send_file
import glob, os, sys
from markupsafe import escape
from tools.utils import login_required
logs = Blueprint('logs', __name__, template_folder='templates')
@@ -10,22 +11,20 @@ app.config.from_pyfile('config.py')
#### Variables ####################################################################################
DOSSIER_PERSO= app.config['DOSSIER_APP']
DOSSIER_PERSO= app.config.get('DOSSIER_APP')
extensionimg = app.config['EXT_IMG']
extensionimg = app.config.get('EXT_IMG')
DATABASE = app.config['DATABASE']
DATABASE = app.config.get('DATABASE')
##################################################################################################
@logs.route('/logs/')
@login_required
def logfile():
if 'username' in session:
UTILISATEUR='%s'% escape(session['username'])
log_file=os.path.join(DOSSIER_PERSO, UTILISATEUR, "log.txt")
with open(log_file, 'r') as log:
logs=log.readlines()
log.close()
return render_template('logs.html', section="Logs", logs=logs)
else :
return redirect(url_for('loginlogout.login', _external=True), code=401)
user='%s'% escape(session['username'])
log_file=os.path.join(DOSSIER_PERSO, user, "log.txt")
with open(log_file, 'r') as log:
logs=log.readlines()
log.close()
return render_template('logs.html', section="Logs", logs=logs)

View File

@@ -7,7 +7,7 @@ import sqlite3
import os
from shutil import copy
from socket import gethostname
from tools.utils import email_disp, append_to_log, gen_token, valid_passwd
from tools.utils import email_disp, append_to_log, gen_token, valid_passwd, login_required, set_mail_domain
@@ -17,37 +17,67 @@ app = Flask( 'pywallter' )
app.config.from_pyfile('config.py')
#### Variables ####################################################################################
DOSSIER_PERSO = app.config['DOSSIER_APP']
#### Variables ###################################################################################
DOSSIER_PERSO = app.config.get('DOSSIER_APP')
extensionimg = app.config['EXT_IMG']
extensionimg = app.config.get('EXT_IMG')
DATABASE = app.config['DATABASE']
DATAS_USER = app.config['DOSSIER_APP']
MAIL_SERVER = app.config['MAIL_SERVER']
XMPP_SERVER = app.config['XMPP_SERVER']
SETUID = app.config['SETUID']
BASE_URL = app.config['BASE_URL']
BACKUP_TIME = app.config['BACKUP_TIME']
DATABASE = app.config.get('DATABASE')
DATAS_USER = app.config.get('DOSSIER_APP')
MAIL_SERVER = app.config.get('MAIL_SERVER')
XMPP_SERVER = app.config.get('XMPP_SERVER')
SETUID = app.config.get('SETUID')
BASE_URL = app.config.get('BASE_URL')
MAIL_WEBSERVICE = app.config.get('MAIL_WEBSERVICE')
XMPP_WEBSERVICE = app.config.get('XMPP_WEBSERVICE')
##################################################################################################
@mymailbox.route('/mymailbox/', methods=['GET'] )
@login_required
def mymessaging():
hostname=set_mail_domain()
user='%s' % escape(session['username'])
conn = sqlite3.connect(DATABASE) # Connexion à la base de donnée
cursor = conn.cursor() # Création de l'objet "curseur"
cursor.execute("""SELECT Mail FROM users where name=?""", (user,))
tmp = cursor.fetchone()
myemail = tmp[0]
return render_template('mymessaging.html',
section="mymessaging",
hostname=hostname,
myemail=myemail,
imap_address=app.config.get('IMAP_ADDRESS'),
smtp_address=app.config.get('SMTP_ADDRESS'),
mail_server=MAIL_SERVER,
mail_webservice=MAIL_WEBSERVICE,
xmpp_server=XMPP_SERVER,
xmpp_webservice=XMPP_WEBSERVICE,
username=user )
@mymailbox.route('/mymailbox/alias', methods=['GET', 'POST'] )
@login_required
def myalias():
hostname=gethostname()
UTILISATEUR='%s' % escape(session['username'])
hostname = set_mail_domain()
user='%s' % escape(session['username'])
conn = sqlite3.connect(DATABASE) # Connexion à la base de donnée
cursor = conn.cursor() # Création de l'objet "curseur"
if request.method == 'POST' and MAIL_SERVER:
if request.form['alias']:
alias = request.form['alias'].lower()+'@'+hostname
else:
flash(u'Addresse invalide')
flash(u'Addresse invalide', 'error')
if email_disp(alias):
cursor.execute("""SELECT Mail, alias FROM users where name=?""", (UTILISATEUR,))
cursor.execute("""SELECT Mail, alias FROM users where name=?""", (user,))
tmp = cursor.fetchone()
mail = tmp[0]
if tmp[1]:
@@ -58,23 +88,23 @@ def myalias():
cmd = SETUID+ " set_mail_alias " + "'"+mail+"'"+" add "+"'"+alias+"'"
res = os.system(cmd)
if res == 0:
cursor.execute("UPDATE users SET alias=? WHERE name=?",
(aliases, UTILISATEUR))
conn.commit()
TIME=time.strftime("%A %d %B %Y %H:%M:%S")
IP=request.environ['REMOTE_ADDR']
CLIENT_PLATFORM=request.headers.get('User-Agent')
cursor.execute("UPDATE users SET alias=? WHERE name=?",
(aliases, user))
conn.commit()
time_create_alias = time.strftime("%A %d %B %Y %H:%M:%S")
ip_address=request.environ['REMOTE_ADDR']
client_plateform=request.headers.get('User-Agent')
log=TIME + ' - ' + IP + ' - ' + UTILISATEUR + ' - ' + CLIENT_PLATFORM + '\n' + '---> ' + "Ajout de l'alias "+ alias + '\n'
append_to_log(log, UTILISATEUR)
flash(u'Votre alias a été ajouté', 'succes')
log=time_create_alias + ' - ' + ip_address + ' - ' + user + ' - ' + client_plateform + '\n' + '---> ' + "Ajout de l'alias "+ alias + '\n'
append_to_log(log, user)
flash(u'Votre alias a été ajouté', 'succes')
else:
flash(u'Adresse indisponible', 'error')
else:
flash(u'Adresse indisponible', 'error')
cursor.execute("""SELECT Mail, alias FROM users WHERE name=?""",
(UTILISATEUR,))
(user,))
tmp = cursor.fetchone()
mailbox = dict()
mailbox['Mail'] = tmp[0]
@@ -90,16 +120,18 @@ def myalias():
aliases=mailbox['alias'],
hostname=hostname,
MAIL_SERVER=MAIL_SERVER,
username=UTILISATEUR )
username=user )
@mymailbox.route('/mymailbox/rmalias/<aliasrm>')
@login_required
def remove_alias(aliasrm):
aliasrm = escape(aliasrm)
if MAIL_SERVER:
UTILISATEUR='%s' % escape(session['username'])
user='%s' % escape(session['username'])
conn = sqlite3.connect(DATABASE) # Connexion à la base de donnée
cursor = conn.cursor() # Création de l'objet "curseur"
cursor.execute("""SELECT Mail, alias FROM users WHERE name=?""", (UTILISATEUR,))
cursor.execute("""SELECT Mail, alias FROM users WHERE name=?""", (user,))
tmp = cursor.fetchone()
mail = tmp[0]
alias_list = tmp[1].split(',')
@@ -114,16 +146,16 @@ def remove_alias(aliasrm):
res = os.system(cmd)
if res == 0:
cursor.execute("UPDATE users SET alias=? WHERE name=?",
(aliases, UTILISATEUR))
(aliases, user))
conn.commit()
TIME=time.strftime("%A %d %B %Y %H:%M:%S")
IP=request.environ['REMOTE_ADDR']
CLIENT_PLATFORM=request.headers.get('User-Agent')
log = TIME + ' - ' + IP + ' - ' + UTILISATEUR + ' - ' + CLIENT_PLATFORM + '\n' + '---> ' + "Suppression de l'alias "+ alias + '\n'
append_to_log(log, UTILISATEUR)
time_del_alias=time.strftime("%A %d %B %Y %H:%M:%S")
ip_address=request.environ['REMOTE_ADDR']
client_platform=request.headers.get('User-Agent')
log = time_del_alias + ' - ' + ip_address + ' - ' + user + ' - ' + client_platform + '\n' + '---> ' + "Suppression de l'alias "+ alias + '\n'
append_to_log(log, user)
flash(u'Votre alias a été supprimé', 'succes')
else:
flash(u'Il y a eu une erreur', 'error')
return redirect(url_for('mymailbox.myalias', _external=True))
return redirect(url_for('mymailbox.myalias'))

View File

@@ -5,11 +5,13 @@ from markupsafe import escape
import time
import sqlite3
import os
import subprocess
from shutil import copy
from socket import gethostname
from flask_bcrypt import Bcrypt
from tools.utils import email_disp, append_to_log, gen_token, valid_passwd, valid_token_register, get_user_by_token, totp_is_valid
from tools.utils import email_disp, append_to_log, gen_token, valid_passwd, valid_token_register, get_user_by_token, totp_is_valid, login_required, valid_email
from pyotp import random_base32
from tools.filesutils import check_and_create
import qrcode
@@ -22,148 +24,144 @@ app.config.from_pyfile('config.py')
#### Variables ####################################################################################
bcrypt = Bcrypt(app)
DOSSIER_PERSO = app.config['DOSSIER_APP']
DOSSIER_PERSO = app.config.get('DOSSIER_APP')
extensionimg = app.config['EXT_IMG']
extensionimg = app.config.get('EXT_IMG')
DATABASE = app.config['DATABASE']
DATAS_USER = app.config['DOSSIER_APP']
MAIL_SERVER = app.config['MAIL_SERVER']
XMPP_SERVER = app.config['XMPP_SERVER']
SETUID = app.config['SETUID']
BASE_URL = app.config['BASE_URL']
BACKUP_TIME = app.config['BACKUP_TIME']
DATABASE = app.config.get('DATABASE')
DATAS_USER = app.config.get('DOSSIER_APP')
MAIL_SERVER = app.config.get('MAIL_SERVER')
XMPP_SERVER = app.config.get('XMPP_SERVER')
SETUID = app.config.get('SETUID')
BASE_URL = app.config.get('BASE_URL')
BACKUP_TIME = app.config.get('BACKUP_TIME')
##################################################################################################
@profil.route( '/profil/<user>/<img>', methods=['GET'] )
@login_required
def profil_img(user, img) :
if 'username' in session :
return send_from_directory( os.path.join(DOSSIER_PERSO, user, 'profile'), img )
else:
return redirect(BASE_URL, code=401)
return send_from_directory( os.path.join(DOSSIER_PERSO, user, 'profile'), img )
@profil.route('/profil/', methods=['GET','POST'])
@login_required
def profile() :
if 'username' in session :
user='%s' % escape(session['username'])
conn = sqlite3.connect(DATABASE) # Connexion à la base de donnée
cursor = conn.cursor() # Création de l'objet "curseur"
cursor.execute("""SELECT avatar, nom, prenom, age, Mail_rescue FROM users WHERE name=?""", (user,))
tmp = (cursor.fetchone())
profil_user = dict()
profil_user['avatar'] = tmp[0]
profil_user['nom'] = tmp[1]
profil_user['prenom'] = tmp[2]
profil_user['age'] = tmp[3]
profil_user['mail_rescue'] = tmp[4]
conn.close()
user='%s' % escape(session['username'])
check_and_create(os.path.join(DOSSIER_PERSO, user,'profile'))
conn = sqlite3.connect(DATABASE) # Connexion à la base de donnée
cursor = conn.cursor() # Création de l'objet "curseur"
cursor.execute("""SELECT avatar, nom, prenom, age, Mail_rescue FROM users WHERE name=?""", (user,))
tmp = (cursor.fetchone())
profil_user = dict()
profil_user['avatar'] = tmp[0]
profil_user['nom'] = tmp[1]
profil_user['prenom'] = tmp[2]
profil_user['age'] = tmp[3]
profil_user['mail_rescue'] = tmp[4]
conn.close()
if request.method == 'POST' :
if request.method == 'POST' :
f = request.files['fic']
f = request.files['fic']
if request.form['theme'] != "Default":
copy( "static/vendors/picocss/pico.fluid.classless."+request.form['theme']+".min.css",
DOSSIER_PERSO+ user +'/theme.min.css' )
if request.form['theme']:
copy( "static/vendors/picocss/pico.fluid.classless."+request.form['theme']+".min.css",
os.path.join(DOSSIER_PERSO, user ,'theme.min.css') )
if request.form['nom']:
profil_user['nom'] = request.form['nom']
if request.form['prenom']:
profil_user['prenom'] = request.form['prenom']
if request.form['age']:
profil_user['age'] = request.form['age']
if '@' in request.form['mail_rescue']:
if len(request.form['mail_rescue']) > 4:
profil_user['mail_rescue'] = request.form['mail_rescue']
if request.form['nom']:
profil_user['nom'] = str(request.form['nom'])
if request.form['prenom']:
profil_user['prenom'] = str(request.form['prenom'])
if request.form['age']:
profil_user['age'] = str(request.form['age'])
if request.form['mail_rescue'] :
new_mail_rescue = str(request.form['mail_rescue'])
if valid_email(new_mail_rescue):
profil_user['mail_rescue']=new_mail_rescue
else:
flash(u'Adresse de courriel invalide', 'error')
else:
flash(u'Adresse de courriel de secour invalide', 'error')
if f: # On vérifie qu'un fichier a bien été envoyé
flash(u'Adresse de courriel invalide', 'error')
if f: # On vérifie qu'un fichier a bien été envoyé
nom = secure_filename(f.filename)
f.save(DOSSIER_PERSO + user + '/profile/' + nom)
image = DOSSIER_PERSO + user + '/profile/' + nom
f.save(os.path.join(DOSSIER_PERSO, user, 'profile', nom))
image = os.path.join(DOSSIER_PERSO, user, 'profile', nom)
with Image.open(image) as img:
img.thumbnail((300,200))
img.save( DOSSIER_PERSO + user + '/profile/' + nom)
img.save(os.path.join(DOSSIER_PERSO, user, 'profile', nom))
filename = nom
conn = sqlite3.connect(DATABASE) # Connexion à la base de donnée
cursor = conn.cursor() # Création de l'objet "curseur"
cursor.execute("UPDATE users SET avatar=? WHERE name=?",
(filename, user))
conn.commit()
cursor = conn.cursor() # Création de l'objet "curseur"
conn.close()
flash(u'Image de profil mise à jour', 'success')
else:
else:
conn = sqlite3.connect(DATABASE) # Connexion à la base de donnée
cursor = conn.cursor() # Création de l\'objet "curseur"
cursor.execute("UPDATE users SET nom=?, prenom=?, age=?, mail_rescue=? WHERE name=?",
(profil_user['nom'], profil_user['prenom'], profil_user['age'], profil_user['mail_rescue'],
user))
conn.commit()
flash(u'Le profil a été mis à jour', 'succes')
flash(u'Le profil a été mis à jour', 'success')
return render_template('profil.html',
return render_template('profil.html',
section="Profil",
profil=profil_user,
username=user)
else :
return redirect(BASE_URL, code=401)
@profil.route('/profil/homepage', methods=['GET'] )
@login_required
def homepage():
if 'username' in session :
username='%s' % escape(session['username'])
return render_template('homepage.html',
section="Profil",
username=username)
username='%s' % escape(session['username'])
return render_template('homepage.html',
section="Profil",
username=username)
@profil.route('/profil/change-password/', methods=['GET','POST'] )
@login_required
def change_passwd() :
if 'username' in session:
user='%s' % escape(session['username'])
conn = sqlite3.connect(DATABASE) # Connexion à la base de donnée
cursor = conn.cursor() # Création de l'objet "curseur"
cursor.execute("""SELECT Mail, alias, xmpp, totp FROM users WHERE name=?""", (user,))
tmp = cursor.fetchone()
shared_key_validate=True
account = dict()
account['Mail'] = tmp[0]
account['alias'] = tmp[1]
account['xmpp'] = tmp[2]
account['totp'] = tmp[3]
user='%s' % escape(session['username'])
conn = sqlite3.connect(DATABASE) # Connexion à la base de donnée
cursor = conn.cursor() # Création de l'objet "curseur"
cursor.execute("""SELECT Mail, alias, xmpp, totp FROM users WHERE name=?""", (user,))
tmp = cursor.fetchone()
shared_key_validate=True
account = dict()
account['Mail'] = tmp[0]
account['alias'] = tmp[1]
account['xmpp'] = tmp[2]
account['totp'] = tmp[3]
if request.method == 'POST' :
if request.method == 'POST' :
password = request.form['password']
password_confirm = request.form['passwd_confirm']
password = request.form['password']
password_confirm = request.form['passwd_confirm']
if not(password == "") and password == password_confirm and valid_passwd(password):
if not(password == "") and password == password_confirm and valid_passwd(password):
mail_passwd_change = 0
xmpp_passwd_change = 0
passwd = request.form['password']
if MAIL_SERVER:
cmd = SETUID+ ' set_mail_passwd ' + '"'+account['Mail']+'" '+ '"'+passwd+'"'
mail_passwd_change = os.system(cmd)
cmd = SETUID+ ' set_mail_passwd ' + '"'+account['Mail']+'" '+ '"'+passwd+'"'
mail_passwd_change = os.system(cmd)
if XMPP_SERVER:
@@ -185,34 +183,32 @@ def change_passwd() :
log=TIME + ' - ' + IP + ' - ' + user + ' - ' + CLIENT_PLATFORM + '\n' + '---> ' + "Changement du mot de passe" + '\n'
append_to_log(log, user)
flash(u'Votre mot de passe a été changé', 'success')
else:
if not( valid_passwd(password) ):
flash(u'Le mot de passe ne peut pas contenir les caractères " et &', 'error')
elif password == "":
flash(u' Vous ne pouvez pas ne pas mettre de mot de passe ou un mot de passe vide', 'error')
else:
flash(u'Les mot de passes ne sont pas identiques :/ ', 'error')
else:
if not( valid_passwd(password) ):
flash(u'Le mot de passe ne peut pas contenir les caractères " et &', 'error')
elif password == "":
flash(u' Vous ne pouvez pas ne pas mettre de mot de passe ou un mot de passe vide', 'error')
else:
flash(u'Les mot de passes ne sont pas identiques :/ ', 'error')
conn.close()
conn.close()
if not(account['totp']):
account['totp'] = random_base32()
img = qrcode.make('otpauth://totp/'+BASE_URL+'?secret='+account['totp'])
img.save(DOSSIER_PERSO + user + "/totp.png")
shared_key_validate = False
if not(account['totp']):
account['totp'] = random_base32()
img = qrcode.make('otpauth://totp/'+BASE_URL+'?secret='+account['totp'])
img.save(os.path.join(DOSSIER_PERSO, user, "totp.png"))
shared_key_validate = False
return render_template('mypassword.html',
section="Profil",
address=account['Mail'],
alias=account['alias'],
totp_shared_key=account['totp'],
shared_key_validate=shared_key_validate,
username=user,
base_url=BASE_URL)
else :
return redirect(BASE_URL, code=401)
return render_template('mypassword.html',
section="Profil",
address=account['Mail'],
alias=account['alias'],
totp_shared_key=account['totp'],
shared_key_validate=shared_key_validate,
username=user,
base_url=BASE_URL)
@profil.route('/change-password-lost/<token>', methods=['GET','POST'] )
def change_passwd_lost(token) :
@@ -292,56 +288,52 @@ def change_passwd_lost(token) :
return redirect(BASE_URL, code=401)
@profil.route('/set_totp/', methods=['POST'])
@login_required
def set_totp():
if 'username' in session:
user='%s' % escape(session['username'])
conn = sqlite3.connect(DATABASE) # Connexion à la base de donnée
cursor = conn.cursor() # Création de l'objet "curseur"
user='%s' % escape(session['username'])
conn = sqlite3.connect(DATABASE) # Connexion à la base de donnée
cursor = conn.cursor() # Création de l'objet "curseur"
shared_key = request.form['shared_key']
code_totp = request.form['code_totp']
shared_key = request.form['shared_key']
code_totp = request.form['code_totp']
if totp_is_valid(shared_key, code_totp) and code_totp !="" and shared_key != "":
print("shared_key: " +shared_key)
cursor.execute("""UPDATE users SET totp=? WHERE name=?""", (shared_key, user,))
conn.commit()
img = qrcode.make('otpauth://totp/'+BASE_URL+'?secret='+shared_key)
img.save(DOSSIER_PERSO + user + "/totp.png")
flash(u'Votre mot de passe à usage unique est configuré et actif.', 'success')
else:
flash(u'Le code de validation totp n\'est pas valide.', 'error')
conn.close()
return redirect(url_for('profil.change_passwd', _external=True))
if totp_is_valid(shared_key, code_totp) and code_totp !="" and shared_key != "":
print("shared_key: " +shared_key)
cursor.execute("""UPDATE users SET totp=? WHERE name=?""", (shared_key, user,))
conn.commit()
img = qrcode.make('otpauth://totp/'+BASE_URL+'?secret='+shared_key)
img.save(os.path.join(DOSSIER_PERSO, user, "totp.png"))
flash(u'Votre mot de passe à usage unique est configuré et actif.', 'success')
else:
return redirect(BASE_URL, code=401)
flash(u'Le code de validation totp n\'est pas valide.', 'error')
conn.close()
return redirect(url_for('profil.change_passwd', _external=True))
@profil.route('/del_totp/', methods=['GET'])
@login_required
def del_totp():
if 'username' in session:
user='%s' % escape(session['username'])
conn = sqlite3.connect(DATABASE) # Connexion à la base de donnée
cursor = conn.cursor() # Création de l'objet "curseur"
cursor.execute("""UPDATE users SET totp="" WHERE name=?""", (user,))
conn.commit()
conn.close()
return redirect(url_for('profil.change_passwd', _external=True))
user='%s' % escape(session['username'])
conn = sqlite3.connect(DATABASE) # Connexion à la base de donnée
cursor = conn.cursor() # Création de l'objet "curseur"
cursor.execute("""UPDATE users SET totp="" WHERE name=?""", (user,))
conn.commit()
conn.close()
return redirect(url_for('profil.change_passwd', _external=True))
@profil.route('/totp.png', methods=['GET'])
@login_required
def totp_qrcode():
if 'username' in session :
user='%s' % escape(session['username'])
return send_file(
os.path.join(DOSSIER_PERSO, user, "totp.png"), "totp.png")
else :
return redirect(BASE_URL, code=401)
user='%s' % escape(session['username'])
return send_file(
os.path.join(DOSSIER_PERSO, user, "totp.png"), "totp.png")
@profil.route('/deltoken-password-lost/<token>', methods=['GET','POST'] )
@profil.route('/deltoken-password-lost/<token>', methods=['GET'] )
def deltoken_passwd_lost(token) :
token = escape(token)
if valid_token_register(token, "Lost password"):
user = get_user_by_token(token, "Lost password")
conn = sqlite3.connect(DATABASE) # Connexion à la base de donnée
@@ -358,106 +350,110 @@ def deltoken_passwd_lost(token) :
@profil.route('/invitation/', methods=['GET'])
@login_required
def invitation():
if 'username' in session:
UTILISATEUR='%s' % escape(session['username'])
conn = sqlite3.connect(DATABASE) # Connexion à la base de donnée
cursor = conn.cursor() # Création de l'objet "curseur"
cursor.execute("""SELECT Token, invitations FROM users WHERE name=?""", (UTILISATEUR,))
tmp = cursor.fetchone()
token = tmp[0]
if token:
url_invitation = BASE_URL + 'inscription/' + token
else:
url_invitation = ""
invitations_count = tmp[1]
conn.close()
return render_template('invitation.html',
section='Profil',
nb_invitation=invitations_count,
token=token,
url_invitation=url_invitation)
user='%s' % escape(session['username'])
conn = sqlite3.connect(DATABASE) # Connexion à la base de donnée
cursor = conn.cursor() # Création de l'objet "curseur"
cursor.execute("""SELECT Token, invitations FROM users WHERE name=?""", (user,))
tmp = cursor.fetchone()
token = tmp[0]
nb_invitations = tmp[1]
if token and nb_invitations > 0:
url_invitation = BASE_URL + '/inscription/' + token
img = qrcode.make(url_invitation)
img.save(os.path.join(DOSSIER_PERSO, user, "invitation.png"))
else:
return redirect(BASE_URL, code=401)
url_invitation = ""
conn.close()
return render_template('invitation.html',
section='Profil',
nb_invitation=nb_invitations,
token=token,
url_invitation=url_invitation)
@profil.route('/invitation.png', methods=['GET'])
@login_required
def invitation_qrcode():
user='%s' % escape(session['username'])
return send_file(
os.path.join(DOSSIER_PERSO, user, "invitation.png"))
@profil.route('/gen_token/', methods=['GET'])
@login_required
def generate_token():
if 'username' in session:
UTILISATEUR='%s' % escape(session['username'])
conn = sqlite3.connect(DATABASE) # Connexion à la base de donnée
cursor = conn.cursor() # Création de l'objet "curseur"
token = gen_token("Invitation")
cursor.execute("UPDATE users SET Token=? WHERE name=?",
(token, UTILISATEUR))
conn.commit()
conn.close()
return redirect(BASE_URL+'invitation/')
else:
return redirect(BASE_URL, code=401)
user='%s' % escape(session['username'])
conn = sqlite3.connect(DATABASE) # Connexion à la base de donnée
cursor = conn.cursor() # Création de l'objet "curseur"
cursor.execute("""SELECT invitations FROM users WHERE name=?""", (user,))
tmp = cursor.fetchone()
token = tmp[0]
token = gen_token("Invitation")
cursor.execute("UPDATE users SET Token=? WHERE name=?",
(token, user))
conn.commit()
conn.close()
return redirect(BASE_URL+'/invitation/')
@profil.route( '/delete_me/', methods=['GET','POST'])
@login_required
def delete_account():
if 'username' in session :
UTILISATEUR='%s'% escape(session['username'])
resp = render_template('delete_account.html', time_backup=BACKUP_TIME)
if request.method == 'POST' :
conn = sqlite3.connect(DATABASE) # Connexion à la base de donnée
cursor = conn.cursor() # Création de l'objet "curseur"
cursor.execute("""SELECT passwd FROM users WHERE name=?""", (UTILISATEUR,))
passwd = cursor.fetchone()[0]
cursor.execute("""SELECT mail FROM users WHERE name=?""", (UTILISATEUR,))
mail = cursor.fetchone()[0]
conn.close()
password = request.form['passwd']
if bcrypt.check_password_hash(passwd, password) is True:
not_error = True
if MAIL_SERVER:
try:
cmd = SETUID + ' set_mail_passwd del ' + '"'+mail+'"'
print(cmd)
os.system(cmd)
except:
not_error = False
flash(u'Erreur lors de la suppression de votre compte Mail.', 'error')
user='%s'% escape(session['username'])
resp = render_template('delete_account.html', time_backup=BACKUP_TIME)
if request.method == 'POST' :
conn = sqlite3.connect(DATABASE) # Connexion à la base de donnée
cursor = conn.cursor() # Création de l'objet "curseur"
cursor.execute("""SELECT passwd FROM users WHERE name=?""", (user,))
passwd = cursor.fetchone()[0]
cursor.execute("""SELECT mail FROM users WHERE name=?""", (user,))
mail = cursor.fetchone()[0]
conn.close()
password = str(request.form['passwd'])
if bcrypt.check_password_hash(passwd, password) is True:
not_error = True
# Delete mail account
if MAIL_SERVER:
try:
cmd = subprocess.run([SETUID, 'set_mail_passwd','del',mail])
except:
not_error = False
flash(u'Erreur lors de la suppression de votre compte Mail.', 'error')
if XMPP_SERVER:
try:
tmp = mail.split('@')
cmd = SETUID+ ' prosodyctl deluser ' "'"+tmp[0]+"' " + "'"+tmp[1]+"'"
os.system(cmd)
except:
not_error = False
flash(u'Erreur lors de la suppression de votre compte XMPP.', 'error')
# Delete the XMPP account
if XMPP_SERVER:
try:
cmd = subprocess.run([SETUID,'prosodyctl', 'deluser', mail])
print(str(cmd))
except:
not_error = False
flash(u'Erreur lors de la suppression de votre compte XMPP.', 'error')
# Delete files
try:
cmd = subprocess.run(['rm', '-r', DATAS_USER + '/' + user])
print(str(cmd))
if cmd.returncode != 0:
raise TypeError("Remove directory error")
except:
flash(u'Erreur lors de la suppression de votre dossier utilisateur.', 'error')
not_error = False
if not_error:
try:
cmd = 'rm -r ' + DATAS_USER + '/' + UTILISATEUR
if os.system(cmd) != 0:
raise TypeError("Remove directory error")
except:
flash(u'Erreur lors de la suppression de votre dossier utilisateur.', 'error')
try:
conn = sqlite3.connect(DATABASE)
cursor = conn.cursor()
cursor.execute("""DELETE FROM users WHERE name=?""", (UTILISATEUR,))
cursor.execute("""DELETE FROM posts WHERE author=?""", (UTILISATEUR,))
conn.commit()
conn.close()
except:
flash(u'Erreur lors de la suppression de votre compte.', 'error')
else:
flash(u'Désinscription réalisé avec succés, y\'a plus rien !', 'succes')
resp = redirect(url_for('loginlogout.logout'))
else:
flash(u'Mauvais mot de passe', 'error')
return resp
if not_error:
conn = sqlite3.connect(DATABASE)
cursor = conn.cursor()
cursor.execute("""DELETE FROM users WHERE name=?""", (user,))
cursor.execute("""DELETE FROM Blog_posts WHERE author=?""", (user,))
conn.commit()
conn.close()
flash(u'Désinscription réalisé avec succés, y\'a plus rien !', 'success')
resp = redirect(url_for('loginlogout.logout'))
else:
flash(u'Mauvais mot de passe', 'error')
return resp

View File

@@ -1,6 +1,11 @@
from gevent.pywsgi import WSGIServer
from pywallter import create_app
app = create_app()
http_server = WSGIServer(("127.0.0.1", 8000), app)
app.config.from_pyfile('config.py')
http_server = WSGIServer((app.config['HOST'], int(app.config['PORT']) ), app)
http_server.serve_forever()