Compare commits
8
Commits
e1780fad99
..
main
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
d72f11a525 | ||
|
|
85185e1438 | ||
|
|
19135364ff | ||
|
|
0e6f6058a0 | ||
|
|
5ff7354dc5 | ||
|
|
99666d40d6 | ||
|
|
2be582db57 | ||
|
|
4031860921 |
@@ -0,0 +1,104 @@
|
|||||||
|
# ICPC-WEBPRINT
|
||||||
|
|
||||||
|
This platform helps contest participants to print their code using no drivers, only web application in any browser. Supports authorization, printing PDF for administrators and teachers.
|
||||||
|
|
||||||
|
Platform was already used on some events, like "The FPMI Cup : February, 2025" and "MIPT Open Programming Championship : April, 2025".
|
||||||
|
|
||||||
|
## Instalation
|
||||||
|
### Environment and Service
|
||||||
|
Both RX and TX servers have the same way to install dependencies. First, be sure to have venv globally installed on your system:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
apt install python3-venv
|
||||||
|
# or
|
||||||
|
python3 -m pip install venv
|
||||||
|
```
|
||||||
|
|
||||||
|
After that create virtual environment:
|
||||||
|
|
||||||
|
```
|
||||||
|
cd print-tx # and then print-rx
|
||||||
|
python3 -m venv .venv
|
||||||
|
```
|
||||||
|
|
||||||
|
Enter environment and install dependencies from `requirements.txt`:
|
||||||
|
|
||||||
|
```
|
||||||
|
source .venv/bin/activate
|
||||||
|
python3 -m pip install -r requirements.txt
|
||||||
|
```
|
||||||
|
Great! Now you have working system. You can start your project either running
|
||||||
|
```
|
||||||
|
python3 -m gunicorn wsgi_back:app -c gunicorn.py
|
||||||
|
```
|
||||||
|
in venv, or creating services on your Linux. Template could be found in `example.serivce` file. After that you can start your web app with running:
|
||||||
|
```
|
||||||
|
systemctl start example.service
|
||||||
|
```
|
||||||
|
|
||||||
|
### Setting Secrets
|
||||||
|
|
||||||
|
Change secret code, that is used to access RX server from TX in `server-tx.json` and `server-rx.json`.
|
||||||
|
Change salt and session secret key in `server-tx/app/config.py`:
|
||||||
|
```
|
||||||
|
_SALT = '$2b$12$DE09JKsqy6Ii/zAxEAA5n.' #edit
|
||||||
|
_SECRET_SESSION_KEY = "GAZAKBAYEV-AHMET-PROJECT" #edit
|
||||||
|
```
|
||||||
|
You can also to set super admin user (it can add and remove others):
|
||||||
|
```
|
||||||
|
_SUPER_ADMINS = ( #edit
|
||||||
|
"god"
|
||||||
|
)
|
||||||
|
```
|
||||||
|
|
||||||
|
Accounts with login:password like "god:god" will be checked and created if not exist, when application start.
|
||||||
|
|
||||||
|
### Setting ports
|
||||||
|
|
||||||
|
You can set port of TX part in file `gunicorn.py`, and RX part in config file `server-rx.json`. Using TX interface, in admin panel, you can configure RX server and get `server-rx.json` to load it.
|
||||||
|
|
||||||
|
### Setting default printer
|
||||||
|
|
||||||
|
Learn more about `lp` service in Linux ([Wiki](https://ru.wikipedia.org/wiki/Lp)).
|
||||||
|
|
||||||
|
## Features
|
||||||
|
|
||||||
|
- Four groups: Super Admin, Admin (create and remove users, set configuration), Tutor (print PDF and code), Team Member (default, print only code)
|
||||||
|
- Print code & PDF
|
||||||
|
- Create & Delete users
|
||||||
|
- Statistic of printed pages & last printing time of member
|
||||||
|
- Setting RX server using UI
|
||||||
|
- Setting TX configuration: PDF Watermark and limits.
|
||||||
|
|
||||||
|
## Setting user group
|
||||||
|
|
||||||
|
Groups are just team names! Use `$root` for Admins, `$tutor$` for Teachers and other names for members.
|
||||||
|
|
||||||
|
## Interface
|
||||||
|
|
||||||
|
### Team Member
|
||||||
|

|
||||||
|
|
||||||
|

|
||||||
|
|
||||||
|
### Tutor
|
||||||
|

|
||||||
|
|
||||||
|
### Админ Панель
|
||||||
|

|
||||||
|
|
||||||
|

|
||||||
|
|
||||||
|

|
||||||
|
|
||||||
|

|
||||||
|
|
||||||
|
### Создание и Редактирование пользователя
|
||||||
|
|
||||||
|

|
||||||
|
|
||||||
|

|
||||||
|
|
||||||
|

|
||||||
|
|
||||||
|
Просмотреть данную галерею можно на [NextCloud](https://cloud.gazakbayev.net/index.php/s/gqzmo9S8KFGcQLi?dir=/) автора.
|
||||||
@@ -0,0 +1,20 @@
|
|||||||
|
#
|
||||||
|
# NOTICE! Replace PATH_TO_SCRIPT with required directory!
|
||||||
|
# Paste content of this file into /etc/systemd/system/YOUR_NAME.service
|
||||||
|
#
|
||||||
|
|
||||||
|
# Suggested names for service: print-rx.service & print-tx.server
|
||||||
|
# REMEMBER to create virtual environment before using script (read README.md)
|
||||||
|
|
||||||
|
[Unit]
|
||||||
|
Description=Print Server TX/RX server script.
|
||||||
|
After=network.target
|
||||||
|
|
||||||
|
[Service]
|
||||||
|
User=www-data
|
||||||
|
Group=www-data
|
||||||
|
WorkingDirectory=PATH_TO_SCRIPT
|
||||||
|
ExecStart=PATH_TO_SCRIPT/.venv/bin/gunicorn wsgi_back:app -c gunicorn.py
|
||||||
|
|
||||||
|
[Install]
|
||||||
|
WantedBy=multi-user.target
|
||||||
@@ -0,0 +1,8 @@
|
|||||||
|
import json
|
||||||
|
|
||||||
|
with open("server-rx.json", 'r', encoding = "utf-8") as file:
|
||||||
|
server_data = json.load(file)
|
||||||
|
|
||||||
|
bind = f"{server_data['ip']}:{server_data['port']}"
|
||||||
|
workers = 2
|
||||||
|
timeout = 120
|
||||||
@@ -0,0 +1,9 @@
|
|||||||
|
blinker==1.9.0
|
||||||
|
click==8.1.8
|
||||||
|
Flask==3.1.0
|
||||||
|
gunicorn==23.0.0
|
||||||
|
itsdangerous==2.2.0
|
||||||
|
Jinja2==3.1.5
|
||||||
|
MarkupSafe==3.0.2
|
||||||
|
packaging==24.2
|
||||||
|
Werkzeug==3.1.3
|
||||||
@@ -0,0 +1,5 @@
|
|||||||
|
{
|
||||||
|
"ip": "127.0.0.1",
|
||||||
|
"port": 33333,
|
||||||
|
"secret": "PROJECT_BY_GAZAKBAYEV_AHMET"
|
||||||
|
}
|
||||||
@@ -0,0 +1,37 @@
|
|||||||
|
import os
|
||||||
|
import json
|
||||||
|
|
||||||
|
from flask import Flask, request
|
||||||
|
|
||||||
|
app = Flask(__name__)
|
||||||
|
SECRET = None
|
||||||
|
|
||||||
|
with open("server-rx.json", 'r', encoding = "utf-8") as file:
|
||||||
|
server_data = json.load(file)
|
||||||
|
SECRET = server_data['secret']
|
||||||
|
|
||||||
|
def go_to_print(file_path, samples):
|
||||||
|
os.system(f"lp -n {samples} {file_path}")
|
||||||
|
|
||||||
|
@app.route('/printfile', methods=['POST'])
|
||||||
|
def print_file():
|
||||||
|
if 'file' not in request.files:
|
||||||
|
return 'Файл не был предоставлен.', 400
|
||||||
|
if 'secret-key' not in request.form:
|
||||||
|
return 'Секретный ключ не был предоставлен.', 400
|
||||||
|
if request.form['secret-key'] != SECRET:
|
||||||
|
return 'Авторизация не удалась.', 400
|
||||||
|
file = request.files['file']
|
||||||
|
filename = "files/" + file.filename
|
||||||
|
with open(filename, 'wb') as f:
|
||||||
|
f.write(file.read())
|
||||||
|
samples = 1
|
||||||
|
if b'samples' in request.data:
|
||||||
|
samples = request.data[b'samples']
|
||||||
|
try:
|
||||||
|
go_to_print(filename, samples)
|
||||||
|
return 'Файл отправлен на печать.', 200
|
||||||
|
except Exception as e:
|
||||||
|
return f'Ошибка при печати файла: {str(e)}.', 500
|
||||||
|
finally:
|
||||||
|
return 'OK', 200
|
||||||
@@ -29,6 +29,8 @@ def get_app():
|
|||||||
from app._tools.database import DataManager
|
from app._tools.database import DataManager
|
||||||
from app._tools.database import SessionManager
|
from app._tools.database import SessionManager
|
||||||
DataManager.init_table()
|
DataManager.init_table()
|
||||||
|
DataManager.check_admins()
|
||||||
SessionManager.init_table()
|
SessionManager.init_table()
|
||||||
|
|
||||||
|
|
||||||
return app
|
return app
|
||||||
@@ -4,12 +4,15 @@ import json
|
|||||||
|
|
||||||
from app._tools.models.user import User
|
from app._tools.models.user import User
|
||||||
from app._tools.models.session import Session
|
from app._tools.models.session import Session
|
||||||
|
from app._tools import passhash as Hash
|
||||||
|
|
||||||
from app.config import _USER_DATABASE as U_DB_PATH
|
from app.config import _USER_DATABASE as U_DB_PATH
|
||||||
from app.config import _TOKEN_DATABASE as T_DB_PATH
|
from app.config import _TOKEN_DATABASE as T_DB_PATH
|
||||||
from app.config import _SERVER_CONFIG as S_CFG_PATH
|
from app.config import _SERVER_CONFIG as S_CFG_PATH
|
||||||
from app.config import _SCHEMA_USERS as SCHEMA_USERS
|
from app.config import _SCHEMA_USERS as SCHEMA_USERS
|
||||||
from app.config import _SCHEMA_TOKENS as SCHEMA_TOKENS
|
from app.config import _SCHEMA_TOKENS as SCHEMA_TOKENS
|
||||||
|
from app.config import _SUPER_ADMINS as SUPER_ADMINS
|
||||||
|
from app.config import _SALT as SALT
|
||||||
|
|
||||||
class ConnectManager:
|
class ConnectManager:
|
||||||
def __init__(self, db_file : str):
|
def __init__(self, db_file : str):
|
||||||
@@ -96,6 +99,16 @@ class DataManager:
|
|||||||
c_man = ConnectManager(U_DB_PATH)
|
c_man = ConnectManager(U_DB_PATH)
|
||||||
c_man.execute(SCHEMA_USERS, close_connection = False)
|
c_man.execute(SCHEMA_USERS, close_connection = False)
|
||||||
c_man.execute("VACUUM;")
|
c_man.execute("VACUUM;")
|
||||||
|
def check_admins():
|
||||||
|
for k in SUPER_ADMINS:
|
||||||
|
if DataManager.get_user_by_name(k) == None:
|
||||||
|
DataManager.add_user(
|
||||||
|
k,
|
||||||
|
"$root$",
|
||||||
|
"Super Admin",
|
||||||
|
Hash.hashPassword(k, SALT)
|
||||||
|
)
|
||||||
|
print(f"Create super admin {k} with password {k}")
|
||||||
def get_user_by_name(username : str) -> User:
|
def get_user_by_name(username : str) -> User:
|
||||||
c_man = ConnectManager(U_DB_PATH)
|
c_man = ConnectManager(U_DB_PATH)
|
||||||
data = c_man.execute(
|
data = c_man.execute(
|
||||||
|
|||||||
+42
-42
@@ -1,42 +1,42 @@
|
|||||||
|
#
|
||||||
#
|
# Web-site platform settings
|
||||||
# Web-site platform settings
|
# It's better to edit only #edit fields!
|
||||||
# It's better to edit only #edit fields!
|
#
|
||||||
#
|
|
||||||
|
_USER_DATABASE = "databases/users.db" #default
|
||||||
_USER_DATABASE = "databases/users.db" #default
|
_TOKEN_DATABASE = "databases/tokens.db" #default
|
||||||
_TOKEN_DATABASE = "databases/tokens.db" #default
|
_SERVER_CONFIG = "server-tx.json" #default
|
||||||
_SERVER_CONFIG = "server-tx.json" #default
|
_SALT = '$2b$12$DE09JKsqy6Ii/zAxEAA5n.' #edit
|
||||||
_SALT = '$2b$12$DE09JKsqy6Ii/zAxEAA5n.' #edit
|
_SECRET_SESSION_KEY = "GAZAKBAYEV-AHMET-PROJECT" #edit
|
||||||
_SECRET_SESSION_KEY = "fpmi_cup_event_2025_by_MMCFPMI_developed_by_gallahad_ru_Welcomeee!!!" #edit
|
_SUPER_ADMINS = ( #edit
|
||||||
_SUPER_ADMINS = ( #edit
|
"god",
|
||||||
"god"
|
"admin"
|
||||||
)
|
)
|
||||||
|
|
||||||
#
|
#
|
||||||
# !!! WARNING !!!
|
# !!! WARNING !!!
|
||||||
# Please, don't edit this
|
# Please, don't edit this
|
||||||
# w/o understanding!
|
# w/o understanding!
|
||||||
#
|
#
|
||||||
|
|
||||||
_SCHEMA_TOKENS = """
|
_SCHEMA_TOKENS = """
|
||||||
CREATE TABLE IF NOT EXISTS tokens (
|
CREATE TABLE IF NOT EXISTS tokens (
|
||||||
token TEXT,
|
token TEXT,
|
||||||
user_id INTEGER,
|
user_id INTEGER,
|
||||||
expires INTEGER
|
expires INTEGER
|
||||||
);
|
);
|
||||||
"""
|
"""
|
||||||
_SCHEMA_USERS = """
|
_SCHEMA_USERS = """
|
||||||
CREATE TABLE IF NOT EXISTS users (
|
CREATE TABLE IF NOT EXISTS users (
|
||||||
id INTEGER PRIMARY KEY
|
id INTEGER PRIMARY KEY
|
||||||
UNIQUE
|
UNIQUE
|
||||||
NOT NULL,
|
NOT NULL,
|
||||||
username TEXT,
|
username TEXT,
|
||||||
team TEXT,
|
team TEXT,
|
||||||
loc TEXT,
|
loc TEXT,
|
||||||
passkey TEXT,
|
passkey TEXT,
|
||||||
reg_ts INTEGER,
|
reg_ts INTEGER,
|
||||||
lastprint_ts INTEGER DEFAULT (0),
|
lastprint_ts INTEGER DEFAULT (0),
|
||||||
pages INTEGER DEFAULT (0)
|
pages INTEGER DEFAULT (0)
|
||||||
);
|
);
|
||||||
"""
|
"""
|
||||||
|
|||||||
Reference in New Issue
Block a user