This commit is contained in:
Devi-priya-dp 2025-11-25 14:30:38 +05:30
parent 9713ad5a55
commit 6584c2c8c5
6 changed files with 1397 additions and 1569 deletions

View File

@ -1 +1 @@
7550
3856

View File

@ -1,7 +1,10 @@
from flask import Blueprint, request, jsonify
from admin_login import jwt_required # import decorator
from admin_login import get_db # reuse DB helper from admin_signin
# dashboard.py
import base64
import re
from functools import wraps
from flask import Blueprint, request, jsonify, current_app
from werkzeug.security import check_password_hash
from admin_login import get_db # <-- corrected import (was admin_login)
dashboard_bp = Blueprint("dashboard", __name__)
@ -13,7 +16,6 @@ def validate_product_name(name):
return False, "product_name is required"
if len(name) > 255:
return False, "product_name too long"
# allow letters, numbers, space, dash and apostrophe
if not re.match(r"^[A-Za-z0-9\s\-']+$", name):
return False, "product_name contains invalid characters"
return True, None
@ -45,20 +47,78 @@ def validate_order_id(order_id):
except (ValueError, TypeError):
return False, "order_id must be a valid integer"
def validate_status(status):
valid_statuses = ['pending', 'processing', 'completed', 'cancelled']
if status and status not in valid_statuses:
return False, f"status must be one of: {', '.join(valid_statuses)}"
return True, None
# -------------------------
# Basic auth helper & decorator
# -------------------------
def _auth_from_basic_header(auth_header):
if not auth_header:
return None, None
parts = auth_header.split(None, 1)
if len(parts) != 2:
return None, None
scheme, creds = parts
if scheme.lower() != "basic":
return None, None
try:
decoded = base64.b64decode(creds).decode("utf-8")
except Exception:
return None, None
if ":" not in decoded:
return None, None
username, password = decoded.split(":", 1)
return username, password
def basic_auth_required(f):
@wraps(f)
def wrapper(*args, **kwargs):
auth_header = request.headers.get("Authorization", "")
username, password = _auth_from_basic_header(auth_header)
# fallback: JSON body credentials
if not username:
try:
body = request.get_json(silent=True) or {}
username = (body.get("username") or "").strip()
password = (body.get("password") or "")
except Exception:
username = None
password = None
if not username or not password:
return jsonify({"error": "authentication required (Basic auth header or JSON username/password)"}), 401
cnx = None
cur = None
try:
cnx = get_db()
cur = cnx.cursor(dictionary=True)
cur.execute("SELECT id, username, password FROM admins_cred WHERE username = %s", (username,))
admin = cur.fetchone()
if not admin or not check_password_hash(admin["password"], password):
return jsonify({"error": "invalid credentials"}), 401
request.admin_id = int(admin["id"])
request.admin_username = admin["username"]
return f(*args, **kwargs)
except Exception:
current_app.logger.exception("auth verify error")
return jsonify({"error": "internal server error"}), 500
finally:
try:
if cur: cur.close()
except: pass
try:
if cnx: cnx.close()
except: pass
return wrapper
# -------------------------
# Create order item
# -------------------------
@dashboard_bp.route("/order-item", methods=["POST"])
@jwt_required
@basic_auth_required
def create_order_item():
admin_id = request.admin_id # set by decorator
admin_id = getattr(request, "admin_id", None)
if not request.is_json:
return jsonify({"error": "expected JSON body"}), 400
@ -68,158 +128,138 @@ def create_order_item():
quantity = body.get("quantity", 1)
price = body.get("price")
# Validations
ok, err = validate_order_id(order_id)
if not ok:
return jsonify({"error": err}), 400
ok, err = validate_product_name(product_name)
if not ok:
return jsonify({"error": err}), 400
ok, err = validate_quantity(quantity)
if not ok:
return jsonify({"error": err}), 400
ok, err = validate_price(price)
if not ok:
return jsonify({"error": err}), 400
order_id_int = int(order_id)
qty_int = int(quantity)
price_f = float(price)
cnx = get_db()
cur = cnx.cursor(dictionary=True)
try:
# 🔍 1. CHECK IF ITEM ALREADY EXISTS (same product in same order by same admin)
cur.execute(
"SELECT id FROM order_items WHERE product_name = %s AND order_id = %s AND admin_id = %s",
(product_name, order_id, admin_id)
(product_name, order_id_int, admin_id)
)
existing = cur.fetchone()
if existing:
return jsonify({"error": "order item already exists"}), 400
# 🔥 2. INSERT NEW ORDER ITEM
cur2 = cnx.cursor()
cur2.execute(
"INSERT INTO order_items (admin_id, order_id, product_name, quantity, price) VALUES (%s, %s, %s, %s, %s)",
(admin_id, order_id, product_name, quantity, price)
)
cnx.commit()
new_id = cur2.lastrowid
try:
cur2.execute(
"INSERT INTO order_items (admin_id, order_id, product_name, quantity, price, status, created_at) "
"VALUES (%s, %s, %s, %s, %s, %s, NOW())",
(admin_id, order_id_int, product_name, qty_int, price_f, "pending")
)
cnx.commit()
new_id = cur2.lastrowid
finally:
try: cur2.close()
except: pass
return jsonify({
"message": "order item created",
"id": new_id,
"admin_id": admin_id,
"order_id": order_id,
"order_id": order_id_int,
"product_name": product_name,
"quantity": quantity,
"price": float(price)
"quantity": qty_int,
"price": price_f,
"status": "pending"
}), 201
except Exception as e:
cnx.rollback()
current_app.logger.exception("create_order_item error")
return jsonify({"error": str(e)}), 500
finally:
try:
cur.close()
try: cur.close()
except: pass
try:
cnx.close()
try: cnx.close()
except: pass
# -------------------------
# List / Get order item(s)
# -------------------------
@dashboard_bp.route("/order-item", methods=["GET"])
@jwt_required
@basic_auth_required
def list_or_get_order_item():
admin_id = request.admin_id
admin_id = getattr(request, "admin_id", None)
id_param = request.args.get("id", "").strip()
order_id_param = request.args.get("order_id", "").strip()
cnx = get_db()
cur = cnx.cursor(dictionary=True)
try:
if id_param == "" and order_id_param == "":
# List all order items for this admin
cur.execute(
"SELECT id, admin_id, order_id, product_name, quantity, price, total, status, created_at FROM order_items WHERE admin_id = %s",
"SELECT id, admin_id, order_id, product_name, quantity, price, status, created_at "
"FROM order_items WHERE admin_id = %s",
(admin_id,)
)
rows = cur.fetchall()
return jsonify({
"rows": rows
}), 200
return jsonify({"rows": rows}), 200
elif order_id_param != "":
# List all items for a specific order
try:
order_id_int = int(order_id_param)
except ValueError:
return jsonify({"error": "order_id must be integer"}), 400
cur.execute(
"SELECT id, admin_id, order_id, product_name, quantity, price, total, status, created_at FROM order_items WHERE order_id = %s AND admin_id = %s",
"SELECT id, admin_id, order_id, product_name, quantity, price, status, created_at "
"FROM order_items WHERE order_id = %s AND admin_id = %s",
(order_id_int, admin_id)
)
rows = cur.fetchall()
return jsonify({"rows": rows}), 200
else:
# Get specific order item by id
try:
id_int = int(id_param)
except ValueError:
return jsonify({"error": "id must be integer"}), 400
cur.execute(
"SELECT id, admin_id, order_id, product_name, quantity, price, total, status, created_at FROM order_items WHERE id = %s AND admin_id = %s",
"SELECT id, admin_id, order_id, product_name, quantity, price, status, created_at "
"FROM order_items WHERE id = %s AND admin_id = %s",
(id_int, admin_id)
)
row = cur.fetchone()
if not row:
return jsonify({"error": "order item not found"}), 404
return jsonify({
"rows": rows
}), 200
return jsonify({"row": row}), 200
except Exception as e:
current_app.logger.exception("list_or_get_order_item error")
return jsonify({"error": str(e)}), 500
finally:
try:
cur.close()
except Exception:
pass
try:
cnx.close()
except Exception:
pass
# -------------------------
# Update order item
# -------------------------
try: cur.close()
except: pass
try: cnx.close()
except: pass
# -------------------------
# Delete order item
# -------------------------
from flask import current_app, jsonify, request
# optionally: from flask_jwt_extended import jwt_required, get_jwt_identity
@dashboard_bp.route("/order-item/<int:id>", methods=["DELETE"])
@jwt_required
@basic_auth_required
def delete_order_item(id):
# get admin id from jwt (adjust according to your JWT middleware)
admin_id = getattr(request, "admin_id", None)
# or if you use flask_jwt_extended: admin_id = get_jwt_identity()
if admin_id is None:
current_app.logger.warning("delete_order_item: missing admin_id on request")
return jsonify({"error": "unauthenticated"}), 401
try:
id_int = int(id)
except (ValueError, TypeError):
@ -232,31 +272,33 @@ def delete_order_item(id):
row = cur.fetchone()
if not row:
return jsonify({"error": "order item not found"}), 404
if int(row["admin_id"]) != int(admin_id):
return jsonify({"error": "not authorized to delete this order item"}), 403
cur.execute("DELETE FROM order_items WHERE id = %s", (id_int,))
cnx.commit()
return jsonify({"message": "order item deleted", "id": id_int}), 200
cur2 = cnx.cursor()
try:
cur2.execute("DELETE FROM order_items WHERE id = %s", (id_int,))
cnx.commit()
finally:
try: cur2.close()
except: pass
return jsonify({"message": "order item deleted", "id": id_int}), 200
except Exception:
cnx.rollback()
current_app.logger.exception("Error deleting order_item id=%s", id_int)
return jsonify({"error": "internal server error"}), 500
finally:
try: cur.close()
except: pass
try: cnx.close()
except: pass
# -------------------------
# Demo endpoint: UNSAFE SQL (for educational purposes only)
# Unsafe demo (intentional SQL injection vulnerability)
# -------------------------
@dashboard_bp.route("/order-item/unsafe", methods=["GET"])
@jwt_required
@basic_auth_required
def get_order_item_unsafe():
"""
UNSAFE VERSION - DO NOT USE IN PRODUCTION

View File

@ -1,15 +1,12 @@
# admin_signin.py
import os
import uuid
from datetime import datetime, timedelta
from functools import wraps
from flask import Blueprint, request, jsonify
import mysql.connector
from werkzeug.security import generate_password_hash, check_password_hash
import jwt
# -------------------------
# Config from env
# Config from env (adjust if needed)
# -------------------------
DB_HOST = os.getenv("MYSQL_HOST", "127.0.0.1")
DB_USER = os.getenv("MYSQL_USER", "root")
@ -17,11 +14,6 @@ DB_PASS = os.getenv("MYSQL_PASSWORD", "12345!")
DB_NAME = os.getenv("MYSQL_DB", "sqlilab")
DB_PORT = int(os.getenv("MYSQL_PORT", 3306))
JWT_SECRET = os.getenv("JWT_SECRET", "SUPER_SECRET_KEY_SHOULD_CHANGE")
JWT_ALGO = "HS256"
ACCESS_TOKEN_EXPIRES_MINUTES = int(os.getenv("ACCESS_TOKEN_EXPIRES_MINUTES", 15))
REFRESH_TOKEN_EXPIRES_DAYS = int(os.getenv("REFRESH_TOKEN_EXPIRES_DAYS", 7))
auth_bp = Blueprint("auth", __name__)
# -------------------------
@ -39,173 +31,46 @@ def get_db():
return mysql.connector.connect(**conn_args)
# -------------------------
# Ensure admins table has refresh token columns (runs at import)
# Ensure admins table has refresh columns (best-effort, safe)
# -------------------------
def ensure_admin_refresh_columns():
"""
Adds columns refresh_token and refresh_expires_at to admins table if missing.
Safe to call at app startup.
Best-effort: ensure admins_cred table exists and has common columns.
This will not throw if table missing; useful at startup.
"""
cnx = None
cur = None
try:
cnx = get_db()
cur = cnx.cursor(dictionary=True)
# check if 'admins' table exists and columns exist
cur.execute("""
SELECT COLUMN_NAME
FROM INFORMATION_SCHEMA.COLUMNS
WHERE TABLE_SCHEMA = %s AND TABLE_NAME = 'admins_cred' AND COLUMN_NAME IN ('refresh_token','refresh_expires_at')
""", (DB_NAME,))
cur = cnx.cursor()
# Try a harmless check; if table doesn't exist this will raise and we ignore
cur.execute(
"SELECT COLUMN_NAME FROM INFORMATION_SCHEMA.COLUMNS "
"WHERE TABLE_SCHEMA = %s AND TABLE_NAME = 'admins_cred' AND COLUMN_NAME IN ('refresh_token','refresh_expires_at')",
(DB_NAME,)
)
rows = cur.fetchall()
existing = {r["COLUMN_NAME"] for r in rows}
to_add = []
if "refresh_token" not in existing:
to_add.append("ADD COLUMN refresh_token VARCHAR(255) DEFAULT NULL")
if "refresh_expires_at" not in existing:
to_add.append("ADD COLUMN refresh_expires_at DATETIME DEFAULT NULL")
if to_add:
alter_sql = "ALTER TABLE admins " + ", ".join(to_add)
cur.execute(alter_sql)
# add an index on refresh_token (if not exists)
try:
cur.execute("CREATE INDEX idx_admins_refresh_token ON admins (refresh_token)")
except Exception:
# index may already exist or DB may throw; ignore silently
pass
cnx.commit()
except mysql.connector.Error:
# If admins table doesn't exist or other DB error, don't crash import.
# The app should create 'admins' table via your normal migrations/SQL.
if cnx:
cnx.rollback()
# If you want ALTER logic, paste here — keeping minimal to avoid accidental schema changes.
except Exception:
# ignore errors (table may not exist in dev)
pass
finally:
if cur:
cur.close()
if cnx:
cnx.close()
try:
if cur:
cur.close()
except:
pass
try:
if cnx:
cnx.close()
except:
pass
# Try to ensure columns exist (best-effort, won't raise on missing table)
# call it so import won't fail
ensure_admin_refresh_columns()
# -------------------------
# Token helpers (use admins table for single refresh token per-admin)
# -------------------------
def create_access_token(admin_id):
exp = datetime.utcnow() + timedelta(minutes=ACCESS_TOKEN_EXPIRES_MINUTES)
payload = {"admin_id": admin_id, "exp": exp, "typ": "access"}
token = jwt.encode(payload, JWT_SECRET, algorithm=JWT_ALGO)
# pyjwt may return bytes in some versions
return token if isinstance(token, str) else token.decode("utf-8")
def create_refresh_token_str():
return str(uuid.uuid4())
def store_refresh_token(admin_id, token_str):
"""Store (or replace) the refresh token for the given admin id."""
expires_at = datetime.utcnow() + timedelta(days=REFRESH_TOKEN_EXPIRES_DAYS)
cnx = get_db()
cur = cnx.cursor()
try:
cur.execute(
"UPDATE admins_cred SET refresh_token = %s, refresh_expires_at = %s WHERE id = %s",
(token_str, expires_at, admin_id),
)
# If no row was updated maybe admin doesn't exist; commit anyway
cnx.commit()
except Exception:
cnx.rollback()
raise
finally:
cur.close()
cnx.close()
def find_refresh_token(token_str):
"""Find the admin row by refresh token. Returns dict with admin_id and refresh_expires_at or None."""
cnx = get_db()
cur = cnx.cursor(dictionary=True)
try:
cur.execute(
"SELECT id AS admin_id, refresh_token, refresh_expires_at FROM admins WHERE refresh_token = %s",
(token_str,),
)
return cur.fetchone()
finally:
cur.close()
cnx.close()
def delete_refresh_token(token_str):
"""Remove refresh token by clearing fields on admins table. Returns rowcount."""
cnx = get_db()
cur = cnx.cursor()
try:
cur.execute(
"UPDATE admins_cred SET refresh_token = NULL, refresh_expires_at = NULL WHERE refresh_token = %s",
(token_str,),
)
rowcount = cur.rowcount
cnx.commit()
return rowcount
except Exception:
cnx.rollback()
raise
finally:
cur.close()
cnx.close()
def rotate_refresh_token(old_token, admin_id):
"""
Atomically rotate the refresh token for the admin.
This updates the admins row only if it currently belongs to that admin (or is NULL).
Returns new token string on success, or None if rotation failed.
"""
new_token = create_refresh_token_str()
expires_at = datetime.utcnow() + timedelta(days=REFRESH_TOKEN_EXPIRES_DAYS)
cnx = get_db()
cur = cnx.cursor()
try:
cur.execute(
"UPDATE admins_cred SET refresh_token = %s, refresh_expires_at = %s "
"WHERE id = %s AND (refresh_token = %s OR refresh_token IS NULL)",
(new_token, expires_at, admin_id, old_token),
)
cnx.commit()
if cur.rowcount == 0:
# rotation failed (token mismatch or admin doesn't have that token)
return None
return new_token
except Exception:
cnx.rollback()
raise
finally:
cur.close()
cnx.close()
# -------------------------
# jwt_required decorator
# -------------------------
def jwt_required(f):
@wraps(f)
def wrapper(*args, **kwargs):
auth = request.headers.get("Authorization", "")
if not auth or not auth.startswith("Bearer "):
return jsonify({"error": "Missing Authorization header"}), 401
token = auth.split(" ", 1)[1]
try:
payload = jwt.decode(token, JWT_SECRET, algorithms=[JWT_ALGO])
if payload.get("typ") != "access":
return jsonify({"error": "Invalid token type"}), 401
# attach admin_id for handlers to use
request.admin_id = int(payload["admin_id"])
except jwt.ExpiredSignatureError:
return jsonify({"error": "Access token expired"}), 401
except jwt.InvalidTokenError:
return jsonify({"error": "Invalid access token"}), 401
return f(*args, **kwargs)
return wrapper
# -------------------------
# Routes: signup, login, refresh, logout
# Signup & Login endpoints (simple)
# -------------------------
@auth_bp.route("/admin_signup", methods=["POST"])
def admin_signup():
@ -218,98 +83,59 @@ def admin_signup():
return jsonify({"error": "username and password required"}), 400
hashed = generate_password_hash(password)
cnx = get_db()
cur = cnx.cursor()
cnx = None
cur = None
try:
cur.execute("INSERT INTO admins_cred (username, password) VALUES (%s, %s)", (username, hashed))
cnx = get_db()
cur = cnx.cursor()
cur.execute("INSERT INTO admins_cred (username, password, created_at) VALUES (%s, %s, %s)",
(username, hashed, datetime.utcnow()))
cnx.commit()
new_id = cur.lastrowid
return jsonify({"message": "admin signed up", "id": new_id}), 201
except mysql.connector.IntegrityError:
cnx.rollback()
if cnx:
cnx.rollback()
return jsonify({"error": "username already exists"}), 400
except Exception as e:
cnx.rollback()
if cnx:
cnx.rollback()
return jsonify({"error": str(e)}), 500
finally:
cur.close()
cnx.close()
try:
if cur: cur.close()
except: pass
try:
if cnx: cnx.close()
except: pass
@auth_bp.route("/safe/admin/login", methods=["POST"])
def safe_admin_login():
data = request.get_json() or {}
@auth_bp.route("/admin_login", methods=["POST"])
def admin_login():
if not request.is_json:
return jsonify({"error": "expected JSON body"}), 400
data = request.get_json()
username = (data.get("username") or "").strip()
password = (data.get("password") or "").strip()
if not username or not password:
return jsonify({"error": "username and password required"}), 400
cnx = get_db()
cur = cnx.cursor(dictionary=True)
cnx = None
cur = None
try:
cnx = get_db()
cur = cnx.cursor(dictionary=True)
cur.execute("SELECT id, username, password FROM admins_cred WHERE username = %s", (username,))
admin = cur.fetchone()
if not admin or not check_password_hash(admin["password"], password):
return jsonify({"error": "invalid credentials"}), 401
admin_id = int(admin["id"])
access_token = create_access_token(admin_id)
refresh_token = create_refresh_token_str()
store_refresh_token(admin_id, refresh_token)
return jsonify({
"message": "login success",
"access_token": access_token,
"refresh_token": refresh_token,
"token_type": "Bearer",
"expires_in": ACCESS_TOKEN_EXPIRES_MINUTES * 60
}), 200
return jsonify({"message": "login success", "admin": {"id": int(admin["id"]), "username": admin["username"]}}), 200
except Exception as e:
return jsonify({"error": str(e)}), 500
finally:
cur.close()
cnx.close()
@auth_bp.route("/token/refresh", methods=["POST"])
def token_refresh():
body = request.get_json() or {}
old_refresh = (body.get("refresh_token") or "").strip()
if not old_refresh:
return jsonify({"error": "refresh_token required"}), 400
row = find_refresh_token(old_refresh)
if not row:
return jsonify({"error": "invalid refresh token"}), 401
expires_at = row.get("refresh_expires_at")
if expires_at and datetime.utcnow() > expires_at:
# expired - remove it and return error
delete_refresh_token(old_refresh)
return jsonify({"error": "refresh token expired"}), 401
admin_id = int(row["admin_id"])
# rotate (atomic update). rotate_refresh_token returns None on failure.
new_refresh = rotate_refresh_token(old_refresh, admin_id)
if not new_refresh:
# rotation failed - treat as invalid
return jsonify({"error": "invalid refresh token"}), 401
new_access = create_access_token(admin_id)
return jsonify({
"access_token": new_access,
"refresh_token": new_refresh,
"token_type": "Bearer",
"expires_in": ACCESS_TOKEN_EXPIRES_MINUTES * 60
}), 200
@auth_bp.route("/safe/admin/logout", methods=["POST"])
def admin_logout():
body = request.get_json() or {}
refresh = (body.get("refresh_token") or "").strip()
if refresh:
try:
delete_refresh_token(refresh)
except Exception:
# ignore errors during logout - still respond success to client
pass
return jsonify({"message": "logged out"}), 200
if cur: cur.close()
except: pass
try:
if cnx: cnx.close()
except: pass

View File

@ -266,32 +266,32 @@ else
BACKEND_PIDFILE="$ROOT_DIR/backend.pid"
start_bg "$BACKEND_CMD" "$ROOT_DIR/$BACKEND_LOG" "$BACKEND_PIDFILE"
fi
# ---------- FRONTEND ----------
info "Starting frontend..."
if [ -f "$ROOT_DIR/$FRONTEND_DIR/package.json" ]; then
cd "$ROOT_DIR/$FRONTEND_DIR"
if [[ "$(pwd)" == /mnt/* ]]; then
info "Detected WSL/Windows FS. Cleaning node_modules..."
rm -rf node_modules package-lock.json
npm install
else
if [ -f "package-lock.json" ]; then
if ! npm ci; then
rm -rf node_modules
npm install
fi
else
npm install
fi
fi
# # ---------- FRONTEND ----------
# info "Starting frontend..."
# if [ -f "$ROOT_DIR/$FRONTEND_DIR/package.json" ]; then
# cd "$ROOT_DIR/$FRONTEND_DIR"
# if [[ "$(pwd)" == /mnt/* ]]; then
# info "Detected WSL/Windows FS. Cleaning node_modules..."
# rm -rf node_modules package-lock.json
# npm install
# else
# if [ -f "package-lock.json" ]; then
# if ! npm ci; then
# rm -rf node_modules
# npm install
# fi
# else
# npm install
# fi
# fi
FRONTEND_START_CMD=${FRONTEND_START_CMD:-"npm run dev"}
FRONTEND_PIDFILE="$ROOT_DIR/frontend.pid"
start_bg "$FRONTEND_START_CMD" "$ROOT_DIR/$FRONTEND_LOG" "$FRONTEND_PIDFILE"
cd "$ROOT_DIR"
else
warn "No package.json in frontend/ — skipping frontend start."
fi
# FRONTEND_START_CMD=${FRONTEND_START_CMD:-"npm run dev"}
# FRONTEND_PIDFILE="$ROOT_DIR/frontend.pid"
# start_bg "$FRONTEND_START_CMD" "$ROOT_DIR/$FRONTEND_LOG" "$FRONTEND_PIDFILE"
# cd "$ROOT_DIR"
# else
# warn "No package.json in frontend/ — skipping frontend start."
# fi
# ---------- FINISH ----------
echo ""
@ -306,14 +306,14 @@ echo "$(timestamp) Backend → http://localhost:5000"
echo " Log: $BACKEND_LOG"
echo " PID: $(cat backend.pid 2>/dev/null || echo 'N/A')"
echo ""
echo "$(timestamp) Frontend → http://localhost:5173"
echo " Log: $FRONTEND_LOG"
echo " PID: $(cat frontend.pid 2>/dev/null || echo 'N/A')"
# echo "$(timestamp) Frontend → http://localhost:5173"
# echo " Log: $FRONTEND_LOG"
# echo " PID: $(cat frontend.pid 2>/dev/null || echo 'N/A')"
echo "========================================="
echo ""
echo "Useful commands:"
echo " tail -f $BACKEND_LOG"
echo " tail -f $FRONTEND_LOG"
# echo " tail -f $FRONTEND_LOG"
echo " $DOCKER_CMD logs -f $MYSQL_CONTAINER"
echo " $DOCKER_CMD exec -it $MYSQL_CONTAINER mysql -uroot -p$MYSQL_ROOT_PASSWORD $MYSQL_DATABASE"
echo " kill \$(cat backend.pid 2>/dev/null || echo '') && kill \$(cat frontend.pid 2>/dev/null || echo '') && $DOCKER_CMD stop $MYSQL_CONTAINER"

View File

@ -1 +1 @@
7587
3920

File diff suppressed because it is too large Load Diff