diff --git a/Pipfile b/Pipfile index 10a6309..a0b381f 100644 --- a/Pipfile +++ b/Pipfile @@ -12,6 +12,7 @@ peewee = "*" peewee-migrate = "*" flask = "*" flask-login = "*" +flask-vite = "*" [dev-packages] diff --git a/Pipfile.lock b/Pipfile.lock index 07c13ad..c68d368 100644 --- a/Pipfile.lock +++ b/Pipfile.lock @@ -1,7 +1,7 @@ { "_meta": { "hash": { - "sha256": "a598b3b97fc476eba41807a363b2528dfd7ec66f33b33f6b0703c004aaad22f9" + "sha256": "cbf2aa09aa805f5a444be073d6495925f6d37d04290e62140fb65bc02a170a75" }, "pipfile-spec": 6, "requires": { @@ -201,6 +201,15 @@ "markers": "python_version >= '3.7'", "version": "==0.6.3" }, + "flask-vite": { + "hashes": [ + "sha256:1681765668fa9dc7ca985f5ee395b1f7ed1d4db861407cccbcfdaf0346d4112c", + "sha256:5267abdd0bb4c8c9bb5b1a44b1260dc015a3f7e39befcf120babedc386d43558" + ], + "index": "pypi", + "markers": "python_version >= '3.9' and python_version < '4.0'", + "version": "==0.6.1" + }, "frozenlist": { "hashes": [ "sha256:0325024fe97f94c41c08872db482cf8ac4800d80e79222c6b0b7b162d5b13686", diff --git a/db/models.py b/db/models.py index 4f7430e..c0bb329 100644 --- a/db/models.py +++ b/db/models.py @@ -30,6 +30,8 @@ class DomSubUsers(BaseModel): class OrdersPool(BaseModel): name = TextField() + user = ForeignKeyField(column_name='user_id', field='id', model=User, backref='orders_pools') + class Meta: table_name = 'orders_pool' @@ -38,6 +40,8 @@ class Order(BaseModel): weight = IntegerField() repeat = FloatField() + pool = ForeignKeyField(column_name='orders_pool_id', field='id', model=OrdersPool, backref='orders') + class Meta: table_name = 'order' diff --git a/db/queries.py b/db/queries.py index 9c40a23..8084cc5 100644 --- a/db/queries.py +++ b/db/queries.py @@ -1,6 +1,6 @@ import datetime -from .models import database, User, DomSubUsers, Repeat, SkipDay, OrderStatus, PunishmentStatus +from .models import database, User, OrdersPool, DomSubUsers, Repeat, SkipDay, OrderStatus, PunishmentStatus def initdb(): database.connect() @@ -22,6 +22,9 @@ def user_get(username): telegram_username=username ) +def orders_pool_list(user_id): + return OrdersPool.select().where(OrdersPool.user_id == user_id) + def domsubusers_add(sub, dom): return DomSubUsers.create( sub=sub, @@ -34,6 +37,9 @@ def domsubusers_delete(sub, dom): ).delete() return q.execute() +def domsubusers_list(dom): + return DomSubUsers.select().where(DomSubUsers.dom == dom) + def repeat_get(): try: return Repeat.get() diff --git a/flask/api.py b/flask/api.py new file mode 100644 index 0000000..ad8a1bf --- /dev/null +++ b/flask/api.py @@ -0,0 +1,42 @@ +from flask import Blueprint, jsonify, abort +from flask_login import current_user +from db.queries import user_get, domsubusers_list, orders_pool_list + +api = Blueprint('api', __name__) + +@api.route('/subs') +def subs(): + return jsonify( + [ + {"sub_username": dsu.sub.telegram_username} + for dsu + in domsubusers_list(current_user.db_user) + ] + ) + +@api.route('/subs//orders') +def sub_orders(username): + try: + sub = user_get(username) + except: + abort(500) + return + + if sub.telegram_username not in [dsu.sub.telegram_username for dsu in domsubusers_list(current_user.db_user)]: + abort(500) + return + + return jsonify([ + { + 'id': op.id, + 'name': op.name, + 'orders': [{ + 'id': order.id, + 'name': order.name, + 'weight': order.weight, + 'repeat': order.repeat + } for order in op.orders] + } + for op + in orders_pool_list(sub.id) + ]) diff --git a/flask/app.py b/flask/app.py index c1bad62..87a752d 100644 --- a/flask/app.py +++ b/flask/app.py @@ -6,10 +6,10 @@ from flask import Flask, render_template, request, redirect, flash from flask_login import LoginManager, UserMixin, login_user, logout_user, login_required, current_user import hashlib import hmac -import base64 from settings import FLASK_SECRET_KEY, TELEGRAM_API_TOKEN, TELEGRAM_BOT_NAME, TELEGRAM_BOT_DOMAIN from db.queries import user_get +from api import api app = Flask(__name__) @@ -34,17 +34,20 @@ def load_user(user_id): except: return None +app.register_blueprint(api, url_prefix='/api') + @app.route('/') def index(): if not current_user.is_authenticated: data = {'bot_name': TELEGRAM_BOT_NAME, 'bot_damin': TELEGRAM_BOT_DOMAIN} return render_template('index.html', data = data) else: - return redirect('/dashboard') + return redirect('/dashboard/') -@app.route('/dashboard') +@app.route('/dashboard/', defaults={'path': ''}) +@app.route('/dashboard/') @login_required -def dashboard(): +def dashboard(path): return render_template('dashboard.html') @app.route('/logout') @@ -90,7 +93,7 @@ def login(): return redirect('/') - return redirect('/dashboard') + return redirect('/dashboard/') if __name__ == '__main__': app.run(host='0.0.0.0', debug=True, port=8080) \ No newline at end of file diff --git a/flask/templates/dashboard.html b/flask/templates/dashboard.html index ea976cc..1f20086 100644 --- a/flask/templates/dashboard.html +++ b/flask/templates/dashboard.html @@ -1,35 +1,16 @@ - Gear Orders Bot - + + - -
-

Dashboard

-

Successfuly logged in and the login verified

-
- - + +
+ diff --git a/flask/templates/index.html b/flask/templates/index.html index aeb52fa..f083457 100644 --- a/flask/templates/index.html +++ b/flask/templates/index.html @@ -1,7 +1,6 @@ - diff --git a/flask/vite/.gitignore b/flask/vite/.gitignore new file mode 100644 index 0000000..d856d44 --- /dev/null +++ b/flask/vite/.gitignore @@ -0,0 +1,4 @@ +/node_modules +/dist +/package-lock.json +/yarn.lock diff --git a/flask/vite/index.html b/flask/vite/index.html new file mode 100644 index 0000000..1468880 --- /dev/null +++ b/flask/vite/index.html @@ -0,0 +1,11 @@ + + + + + + Used only to please vite + + + + + diff --git a/flask/vite/package.json b/flask/vite/package.json new file mode 100644 index 0000000..6b5a83c --- /dev/null +++ b/flask/vite/package.json @@ -0,0 +1,24 @@ +{ + "name": "demo-tailwind", + "version": "0.0.1", + "description": "", + "scripts": { + "dev": "vite", + "build": "vite build", + "preview": "vite preview" + }, + "keywords": [], + "author": "", + "license": "MIT", + "devDependencies": { + "vite": "^5.0.11" + }, + "dependencies": { + "@mantine/core": "^8.3.12", + "postcss": "^8.5.6", + "react": "^19.2.3", + "react-dom": "^19.2.3", + "react-router": "^7.12.0", + "typescript": "^5.9.3" + } +} diff --git a/flask/vite/src/SubOrders.tsx b/flask/vite/src/SubOrders.tsx new file mode 100644 index 0000000..40edd44 --- /dev/null +++ b/flask/vite/src/SubOrders.tsx @@ -0,0 +1,76 @@ +import { + Container, + Accordion, + TextInput, + Divider, + Input, + Slider, +} from "@mantine/core"; +import React from "react"; +import { Params, useLoaderData, useParams } from "react-router"; + +export const subOrdersLoader = async ({ + params: { username }, +}: { + params: Params; +}) => fetch(`/api/subs/${username}/orders`).then((response) => response.json()); + +export const SubOrders: React.FC = () => { + const { username: sub_username } = useParams(); + const orders = useLoaderData< + { + id: number; + name: string; + orders: { + id: number; + name: string; + weight: number; + repeat: number; + }[]; + }[] + >(); + + return ( + +

Orders for {sub_username}

+ + {orders + ? orders.map(({ id, name, orders }) => ( + + {name} + + <> + + {orders.map( + ({ id: order_id, name: order_name, weight, repeat }) => ( + + + + + + + + + + + ) + )} + + + + )) + : null} + +
+ ); +}; diff --git a/flask/vite/src/SubsList.tsx b/flask/vite/src/SubsList.tsx new file mode 100644 index 0000000..e3adb49 --- /dev/null +++ b/flask/vite/src/SubsList.tsx @@ -0,0 +1,21 @@ +import React from "react"; +import { Container } from "@mantine/core"; +import { useLoaderData, Link } from "react-router"; + +export const subsListLoader = () => + fetch("/api/subs").then((response) => response.json()); + +export const SubsList: React.FC = () => { + const subs = useLoaderData<{ sub_username: string }[]>(); + + return ( + +

Subs

+ {subs.map(({ sub_username }) => ( + + Orders for {sub_username} + + ))} +
+ ); +}; diff --git a/flask/vite/src/main.tsx b/flask/vite/src/main.tsx new file mode 100644 index 0000000..29b0f81 --- /dev/null +++ b/flask/vite/src/main.tsx @@ -0,0 +1,42 @@ +import React from "react"; +import ReactDOM from "react-dom/client"; +import { createBrowserRouter } from "react-router"; +import { RouterProvider } from "react-router/dom"; +import { createTheme, MantineProvider, Input } from "@mantine/core"; + +import "@mantine/core/styles.css"; + +import { SubsList, subsListLoader } from "./SubsList"; +import { SubOrders, subOrdersLoader } from "./SubOrders"; + +const theme = createTheme({ + components: { + InputWrapper: Input.Wrapper.extend({ + defaultProps: { + inputWrapperOrder: ["label", "input", "description", "error"], + my: "md", + }, + }), + }, +}); + +const router = createBrowserRouter([ + { + path: "dashboard", + Component: SubsList, + loader: subsListLoader, + }, + { + path: "dashboard/subs/:username", + Component: SubOrders, + loader: subOrdersLoader, + }, +]); + +ReactDOM.createRoot(document.getElementById("root")!).render( + + + + + +); diff --git a/flask/vite/vite.config.js b/flask/vite/vite.config.js new file mode 100644 index 0000000..2fd93cd --- /dev/null +++ b/flask/vite/vite.config.js @@ -0,0 +1,9 @@ +export default { + base: '/v/', + + server: { + host: 'orders-dev.johnnygear.net', + port: 3000, + strictPort: true + } +}; diff --git a/migrations/001_init.py b/migrations/001_init.py index 78bbdb6..ff5a2c9 100644 --- a/migrations/001_init.py +++ b/migrations/001_init.py @@ -63,24 +63,26 @@ def migrate(migrator: Migrator, database: pw.Database, *, fake=False): table_name = "dom_sub" indexes = [(('dom_id', 'sub_id'), True)] + @migrator.create_model + class OrdersPool(pw.Model): + id = pw.AutoField() + name = pw.TextField() + user = pw.ForeignKeyField(column_name='user_id', field='id', model=migrator.orm['user']) + + class Meta: + table_name = "orders_pool" + @migrator.create_model class Order(pw.Model): id = pw.AutoField() name = pw.TextField() weight = pw.IntegerField() repeat = pw.FloatField() + pool = pw.ForeignKeyField(column_name='orders_pool_id', field='id', model=migrator.orm['orders_pool']) class Meta: table_name = "order" - @migrator.create_model - class OrdersPool(pw.Model): - id = pw.AutoField() - name = pw.TextField() - - class Meta: - table_name = "orders_pool" - @migrator.create_model class PunishmentStatus(pw.Model): id = pw.AutoField() @@ -135,10 +137,10 @@ def rollback(migrator: Migrator, database: pw.Database, *, fake=False): migrator.remove_model('punishment_status') - migrator.remove_model('orders_pool') - migrator.remove_model('order') + migrator.remove_model('orders_pool') + migrator.remove_model('dom_sub') migrator.remove_model('user')