diff --git a/backend/__pycache__/main.cpython-313.pyc b/backend/__pycache__/main.cpython-313.pyc
index 02039a9..5524bfe 100644
Binary files a/backend/__pycache__/main.cpython-313.pyc and b/backend/__pycache__/main.cpython-313.pyc differ
diff --git a/backend/alembic/versions/__pycache__/a12a2ecd70b9_.cpython-313.pyc b/backend/alembic/versions/__pycache__/a12a2ecd70b9_.cpython-313.pyc
new file mode 100644
index 0000000..07d47b2
Binary files /dev/null and b/backend/alembic/versions/__pycache__/a12a2ecd70b9_.cpython-313.pyc differ
diff --git a/backend/alembic/versions/__pycache__/ab09ab5070f7_initial.cpython-313.pyc b/backend/alembic/versions/__pycache__/ab09ab5070f7_initial.cpython-313.pyc
new file mode 100644
index 0000000..94d43be
Binary files /dev/null and b/backend/alembic/versions/__pycache__/ab09ab5070f7_initial.cpython-313.pyc differ
diff --git a/backend/alembic/versions/a12a2ecd70b9_.py b/backend/alembic/versions/a12a2ecd70b9_.py
new file mode 100644
index 0000000..5ec8cda
--- /dev/null
+++ b/backend/alembic/versions/a12a2ecd70b9_.py
@@ -0,0 +1,28 @@
+"""empty message
+
+Revision ID: a12a2ecd70b9
+Revises: 96dff3d4cf1c
+Create Date: 2026-05-17 09:46:42.181807
+
+"""
+from typing import Sequence, Union
+
+from alembic import op
+import sqlalchemy as sa
+
+
+# revision identifiers, used by Alembic.
+revision: str = 'a12a2ecd70b9'
+down_revision: Union[str, Sequence[str], None] = '96dff3d4cf1c'
+branch_labels: Union[str, Sequence[str], None] = None
+depends_on: Union[str, Sequence[str], None] = None
+
+
+def upgrade() -> None:
+ """Upgrade schema."""
+ pass
+
+
+def downgrade() -> None:
+ """Downgrade schema."""
+ pass
diff --git a/backend/alembic/versions/ab09ab5070f7_initial.py b/backend/alembic/versions/ab09ab5070f7_initial.py
new file mode 100644
index 0000000..e6efba1
--- /dev/null
+++ b/backend/alembic/versions/ab09ab5070f7_initial.py
@@ -0,0 +1,28 @@
+"""initial
+
+Revision ID: ab09ab5070f7
+Revises: a12a2ecd70b9
+Create Date: 2026-05-17 11:44:50.049279
+
+"""
+from typing import Sequence, Union
+
+from alembic import op
+import sqlalchemy as sa
+
+
+# revision identifiers, used by Alembic.
+revision: str = 'ab09ab5070f7'
+down_revision: Union[str, Sequence[str], None] = 'a12a2ecd70b9'
+branch_labels: Union[str, Sequence[str], None] = None
+depends_on: Union[str, Sequence[str], None] = None
+
+
+def upgrade() -> None:
+ """Upgrade schema."""
+ pass
+
+
+def downgrade() -> None:
+ """Downgrade schema."""
+ pass
diff --git a/backend/api/__pycache__/admin.cpython-313.pyc b/backend/api/__pycache__/admin.cpython-313.pyc
index 7fd52b4..e10c474 100644
Binary files a/backend/api/__pycache__/admin.cpython-313.pyc and b/backend/api/__pycache__/admin.cpython-313.pyc differ
diff --git a/backend/api/admin.py b/backend/api/admin.py
index 79d823f..afc31ed 100644
--- a/backend/api/admin.py
+++ b/backend/api/admin.py
@@ -5,6 +5,7 @@ from fastapi import HTTPException
from sqlalchemy.orm import Session
import schemas
+from schemas import ItemResponse
from services import crud
from app.db.session import SessionLocal
@@ -14,30 +15,26 @@ router = APIRouter()
def get_db():
-
db = SessionLocal()
-
try:
yield db
-
finally:
db.close()
+# GET ALL ITEMS
+@router.get("/items", response_model=list[ItemResponse])
+def get_items(db: Session = Depends(get_db)):
+ return crud.get_items(db)
+
+
# CREATE ITEM
-@router.post(
- "/items",
- response_model=schemas.ItemResponse
-)
+@router.post("/items", response_model=ItemResponse)
def create_item(
item: schemas.ItemCreate,
db: Session = Depends(get_db)
):
-
- return crud.create_item(
- db,
- item.title
- )
+ return crud.create_item(db, item.title)
# DELETE ITEM
@@ -46,19 +43,7 @@ def delete_item(
item_id: int,
db: Session = Depends(get_db)
):
-
- item = crud.delete_item(
- db,
- item_id
- )
-
+ item = crud.delete_item(db, item_id)
if not item:
-
- raise HTTPException(
- status_code=404,
- detail="Item not found"
- )
-
- return {
- "message": "Item deleted"
- }
\ No newline at end of file
+ raise HTTPException(status_code=404, detail="Item not found")
+ return {"message": "Item deleted"}
\ No newline at end of file
diff --git a/backend/app/db/__pycache__/session.cpython-313.pyc b/backend/app/db/__pycache__/session.cpython-313.pyc
index 20b6f71..1267946 100644
Binary files a/backend/app/db/__pycache__/session.cpython-313.pyc and b/backend/app/db/__pycache__/session.cpython-313.pyc differ
diff --git a/backend/main.py b/backend/main.py
index f394234..003c12e 100644
--- a/backend/main.py
+++ b/backend/main.py
@@ -17,6 +17,5 @@ app.add_middleware(
)
-app.include_router(admin_router)
-
-app.include_router(user_router)
\ No newline at end of file
+app.include_router(admin_router, prefix="/admin")
+app.include_router(user_router, prefix="/user")
\ No newline at end of file
diff --git a/backend/wheel.db b/backend/wheel.db
index 89e4764..36027c6 100644
Binary files a/backend/wheel.db and b/backend/wheel.db differ
diff --git a/frontend-admin/admin.html b/frontend-admin/admin.html
deleted file mode 100644
index e69de29..0000000
diff --git a/frontend-admin/app.js b/frontend-admin/app.js
deleted file mode 100644
index e69de29..0000000
diff --git a/frontend-admin/index.html b/frontend-admin/index.html
deleted file mode 100644
index e69de29..0000000
diff --git a/frontend/admin.html b/frontend/admin.html
new file mode 100644
index 0000000..0ae1a68
--- /dev/null
+++ b/frontend/admin.html
@@ -0,0 +1,20 @@
+
+
+
+
+ پنل ادمین
+
+
+
+ مدیریت آیتمها
+
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/frontend/assets/css/style.css b/frontend/assets/css/style.css
new file mode 100644
index 0000000..b183966
--- /dev/null
+++ b/frontend/assets/css/style.css
@@ -0,0 +1,61 @@
+* {
+ box-sizing: border-box;
+ margin: 0;
+ padding: 0;
+}
+
+body {
+ font-family: Tahoma, sans-serif;
+ background: #f5f5f5;
+ display: flex;
+ flex-direction: column;
+ align-items: center;
+ padding: 40px 20px;
+ gap: 20px;
+}
+
+h1 {
+ color: #2c3e50;
+ margin-bottom: 10px;
+}
+
+canvas {
+ border-radius: 50%;
+ box-shadow: 0 4px 20px rgba(0,0,0,0.2);
+}
+
+button {
+ padding: 12px 30px;
+ font-size: 18px;
+ font-family: Tahoma, sans-serif;
+ background: #e74c3c;
+ color: white;
+ border: none;
+ border-radius: 8px;
+ cursor: pointer;
+}
+
+button:hover {
+ background: #c0392b;
+}
+
+button:disabled {
+ background: #aaa;
+ cursor: not-allowed;
+}
+
+#result {
+ font-size: 24px;
+ font-weight: bold;
+ color: #2c3e50;
+}
+
+/* ادمین */
+#add-section {
+ display: flex;
+ gap: 10px;
+ margin-bottom: 20px;
+}
+
+input {
+ padding: 10px;
\ No newline at end of file
diff --git a/frontend/assets/js/admin.js b/frontend/assets/js/admin.js
new file mode 100644
index 0000000..2058645
--- /dev/null
+++ b/frontend/assets/js/admin.js
@@ -0,0 +1,33 @@
+import { adminGetItems, createItem, deleteItem } from './api.js';
+
+const list = document.getElementById('items-list');
+const input = document.getElementById('new-item');
+const addBtn = document.getElementById('add-btn');
+
+async function renderItems() {
+ const items = await adminGetItems();
+ list.innerHTML = '';
+ items.forEach(item => {
+ const li = document.createElement('li');
+ li.innerHTML = `
+ ${item.title}
+
+ `;
+ list.appendChild(li);
+ });
+}
+
+window.removeItem = async (id) => {
+ await deleteItem(id);
+ renderItems();
+};
+
+addBtn.onclick = async () => {
+ const title = input.value.trim();
+ if (!title) return;
+ await createItem(title);
+ input.value = '';
+ renderItems();
+};
+
+renderItems();
\ No newline at end of file
diff --git a/frontend/assets/js/api.js b/frontend/assets/js/api.js
new file mode 100644
index 0000000..a55795f
--- /dev/null
+++ b/frontend/assets/js/api.js
@@ -0,0 +1,20 @@
+const BASE = 'http://127.0.0.1:8000';
+
+export const getItems = () =>
+ fetch(`${BASE}/user/items`).then(r => r.json());
+
+export const spinWheel = () =>
+ fetch(`${BASE}/user/spin`, { method: 'POST' }).then(r => r.json());
+
+export const adminGetItems = () =>
+ fetch(`${BASE}/admin/items`).then(r => r.json());
+
+export const createItem = (title) =>
+ fetch(`${BASE}/admin/items`, {
+ method: 'POST',
+ headers: { 'Content-Type': 'application/json' },
+ body: JSON.stringify({ title })
+ }).then(r => r.json());
+
+export const deleteItem = (id) =>
+ fetch(`${BASE}/admin/items/${id}`, { method: 'DELETE' }).then(r => r.json());
\ No newline at end of file
diff --git a/frontend/assets/js/wheel.js b/frontend/assets/js/wheel.js
new file mode 100644
index 0000000..83cc99b
--- /dev/null
+++ b/frontend/assets/js/wheel.js
@@ -0,0 +1,82 @@
+import { getItems, spinWheel } from './api.js';
+
+const canvas = document.getElementById('wheel');
+const ctx = canvas.getContext('2d');
+const btn = document.getElementById('spin-btn');
+const result = document.getElementById('result');
+const colors = ['#e74c3c','#3498db','#2ecc71','#f39c12','#9b59b6','#1abc9c'];
+
+let items = [];
+let angle = 0;
+let spinning = false;
+
+async function init() {
+ items = await getItems();
+ drawWheel(angle);
+}
+
+function drawWheel(rotation) {
+ if (items.length === 0) return;
+ const cx = canvas.width / 2;
+ const cy = canvas.height / 2;
+ const r = cx - 10;
+ const slice = (2 * Math.PI) / items.length;
+
+ ctx.clearRect(0, 0, canvas.width, canvas.height);
+ items.forEach((item, i) => {
+ const start = rotation + i * slice;
+ const end = start + slice;
+ ctx.beginPath();
+ ctx.moveTo(cx, cy);
+ ctx.arc(cx, cy, r, start, end);
+ ctx.fillStyle = colors[i % colors.length];
+ ctx.fill();
+ ctx.stroke();
+ ctx.save();
+ ctx.translate(cx, cy);
+ ctx.rotate(start + slice / 2);
+ ctx.textAlign = 'right';
+ ctx.fillStyle = '#fff';
+ ctx.font = 'bold 16px Tahoma';
+ ctx.fillText(item.title, r - 10, 5);
+ ctx.restore();
+ });
+}
+
+btn.onclick = async () => {
+ if (spinning || items.length === 0) return;
+ spinning = true;
+ btn.disabled = true;
+ result.textContent = '';
+
+ const data = await spinWheel();
+ const winner = data.winner;
+ const winnerIdx = items.findIndex(i => i.id === winner.id);
+ const slice = (2 * Math.PI) / items.length;
+ const targetAngle = angle + (Math.PI * 2 * 5) +
+ (Math.PI * 2 - (winnerIdx * slice + slice / 2));
+
+ const duration = 4000;
+ const start = performance.now();
+ const from = angle;
+
+ function animate(now) {
+ const elapsed = now - start;
+ const t = Math.min(elapsed / duration, 1);
+ const ease = 1 - Math.pow(1 - t, 3);
+ angle = from + (targetAngle - from) * ease;
+ drawWheel(angle);
+
+ if (t < 1) {
+ requestAnimationFrame(animate);
+ } else {
+ spinning = false;
+ btn.disabled = false;
+ result.textContent = `🎉 ${winner.title}`;
+ }
+ }
+
+ requestAnimationFrame(animate);
+};
+
+init();
\ No newline at end of file
diff --git a/frontend/index.html b/frontend/index.html
new file mode 100644
index 0000000..0713603
--- /dev/null
+++ b/frontend/index.html
@@ -0,0 +1,17 @@
+
+
+
+
+ گردونه شانس
+
+
+
+ گردونه شانس
+
+
+
+
+
+
+
+
\ No newline at end of file