Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 3 additions & 0 deletions .agents/skills/commit/SKILL.md
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,9 @@ description: 提交並推送程式碼變更。用於每個開發階段完成後
- 要把程式碼推送到 GitHub,讓組員可以取得最新版本
- 遇到 Git 要求設定 `user.name` 或 `user.email` 的提示

## 檢查 .gitignore
在推送之前檢查 .gitignore,確保虛擬環境、環境設定與非必要檔案有正確被排除在版本控制之外。

## ⚠️ 設定 Git 使用者身份

如果 Git 顯示以下提示,需要先設定身份才能 commit:
Expand Down
3 changes: 3 additions & 0 deletions .env.example
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
FLASK_APP=app.py
FLASK_DEBUG=1
SECRET_KEY=super-secret-key-change-in-production
22 changes: 22 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
# Byte-compiled / optimized / DLL files
__pycache__/
*.py[cod]
*$py.class

# Environment
.env
.venv
env/
venv/
ENV/
env.bak/
venv.bak/

# SQLite database and instance directory
instance/
*.db
*.sqlite3

# OS generated files
.DS_Store
Thumbs.db
11 changes: 3 additions & 8 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@

- [系統簡介](#系統簡介)
- [技術棧](#技術棧)
- [組員與分工](#組員與分工)
- [系統截圖](#系統截圖)
- [個人心得](#個人心得)

---
Expand All @@ -25,13 +25,8 @@

---

## 組員與分工(先寫自己就好)

**第 X 組**

| 姓名 | 學號 | 負責部分 |
| ---- | ---- | -------- |
| | | |
## 系統截圖
請在此貼上系統的截圖畫面, 選擇一個功能邊操作邊截圖

---

Expand Down
30 changes: 30 additions & 0 deletions app.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,30 @@
import os
from flask import Flask
from dotenv import load_dotenv

# 載入環境變數
load_dotenv()

def create_app():
app = Flask(__name__, template_folder='app/templates')
app.config['SECRET_KEY'] = os.environ.get('SECRET_KEY', 'default-dev-key')

# 載入並註冊 Blueprints
from app.routes.main import main_bp
from app.routes.transaction import transaction_bp
from app.routes.fixed_deduction import fixed_deduction_bp

app.register_blueprint(main_bp)
app.register_blueprint(transaction_bp)
app.register_blueprint(fixed_deduction_bp)

# 初始化資料庫
from app.models.db import init_db
init_db()

return app

app = create_app()

if __name__ == '__main__':
app.run(debug=True)
1 change: 1 addition & 0 deletions app/__init__.py
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
# 標記 app 為 Python 套件
1 change: 1 addition & 0 deletions app/models/__init__.py
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
# initialize app/models module
21 changes: 21 additions & 0 deletions app/models/db.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
import sqlite3
import os

DB_PATH = os.path.join(os.path.dirname(os.path.dirname(os.path.dirname(__file__))), 'instance', 'database.db')

def get_db_connection():
"""建立並回傳資料庫連線,設定 row_factory 讓結果可透過字典的 key 來存取。"""
os.makedirs(os.path.dirname(DB_PATH), exist_ok=True)
conn = sqlite3.connect(DB_PATH)
conn.row_factory = sqlite3.Row
return conn

def init_db():
"""初始化資料庫實體,執行 schema.sql。"""
schema_path = os.path.join(os.path.dirname(os.path.dirname(os.path.dirname(__file__))), 'database', 'schema.sql')
if not os.path.exists(schema_path):
return
with open(schema_path, 'r', encoding='utf-8') as f:
schema = f.read()
with get_db_connection() as conn:
conn.executescript(schema)
107 changes: 107 additions & 0 deletions app/models/fixed_deduction.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,107 @@
import sqlite3
from app.models.db import get_db_connection

class FixedDeduction:
@staticmethod
def create(amount, category, deduct_day):
"""
新增一筆記錄
:param amount: 扣款金額
:param category: 扣款分類
:param deduct_day: 每月固定扣款日 (1-31)
:return: 新增成功的紀錄 ID,若失敗則回傳 None
"""
try:
with get_db_connection() as conn:
cursor = conn.cursor()
cursor.execute(
"INSERT INTO fixed_deductions (amount, category, deduct_day) VALUES (?, ?, ?)",
(amount, category, deduct_day)
)
conn.commit()
return cursor.lastrowid
except sqlite3.Error as e:
print(f"Database error in FixedDeduction.create: {e}")
return None

@staticmethod
def get_all():
"""
取得所有記錄
:return: 紀錄列表 (sqlite3.Row 格式)
"""
try:
with get_db_connection() as conn:
return conn.execute("SELECT * FROM fixed_deductions ORDER BY deduct_day ASC").fetchall()
except sqlite3.Error as e:
print(f"Database error in FixedDeduction.get_all: {e}")
return []

@staticmethod
def get_by_id(deduction_id):
"""
取得單筆記錄
:param deduction_id: FixedDeduction ID
:return: 該筆記錄 (sqlite3.Row) 或 None
"""
try:
with get_db_connection() as conn:
return conn.execute("SELECT * FROM fixed_deductions WHERE id = ?", (deduction_id,)).fetchone()
except sqlite3.Error as e:
print(f"Database error in FixedDeduction.get_by_id: {e}")
return None

@staticmethod
def update(deduction_id, amount, category, deduct_day):
"""
更新記錄
:param deduction_id: FixedDeduction ID
:return: 成功與否的布林值
"""
try:
with get_db_connection() as conn:
conn.execute(
"UPDATE fixed_deductions SET amount=?, category=?, deduct_day=? WHERE id=?",
(amount, category, deduct_day, deduction_id)
)
conn.commit()
return True
except sqlite3.Error as e:
print(f"Database error in FixedDeduction.update: {e}")
return False

@staticmethod
def update_last_processed(deduction_id, processed_month):
"""
更新記錄最後被處理的月份
:param deduction_id: FixedDeduction ID
:param processed_month: 處理的月份字串 (YYYY-MM)
:return: 成功與否的布林值
"""
try:
with get_db_connection() as conn:
conn.execute(
"UPDATE fixed_deductions SET last_processed_month = ? WHERE id = ?",
(processed_month, deduction_id)
)
conn.commit()
return True
except sqlite3.Error as e:
print(f"Database error in FixedDeduction.update_last_processed: {e}")
return False

@staticmethod
def delete(deduction_id):
"""
刪除記錄
:param deduction_id: FixedDeduction ID
:return: 成功與否的布林值
"""
try:
with get_db_connection() as conn:
conn.execute("DELETE FROM fixed_deductions WHERE id = ?", (deduction_id,))
conn.commit()
return True
except sqlite3.Error as e:
print(f"Database error in FixedDeduction.delete: {e}")
return False
73 changes: 73 additions & 0 deletions app/models/recipe.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,73 @@
import sqlite3
import os
from contextlib import contextmanager

# 取得資料庫檔案存放的目錄 instance/ (位於專案根目錄)
DB_DIR = os.path.join(os.path.dirname(os.path.dirname(os.path.dirname(__file__))), 'instance')
DB_PATH = os.path.join(DB_DIR, 'database.db')

@contextmanager
def get_db_connection():
# 若目錄不存在則建立一個
os.makedirs(DB_DIR, exist_ok=True)
conn = sqlite3.connect(DB_PATH)
# 將回傳結果設為 dict-like 的 Row 物件
conn.row_factory = sqlite3.Row
try:
yield conn
finally:
conn.commit()
conn.close()

class Recipe:
@staticmethod
def create(data):
with get_db_connection() as conn:
cursor = conn.cursor()
cursor.execute('''
INSERT INTO recipes (title, description, ingredients, steps)
VALUES (?, ?, ?, ?)
''', (data['title'], data.get('description', ''), data['ingredients'], data['steps']))
return cursor.lastrowid

@staticmethod
def get_all(query=None):
with get_db_connection() as conn:
cursor = conn.cursor()
if query:
# 簡單支援對 title 或 ingredients 的 LIKE 搜尋 (搜尋推薦食譜)
search_term = f"%{query}%"
cursor.execute('''
SELECT * FROM recipes
WHERE title LIKE ? OR ingredients LIKE ?
ORDER BY created_at DESC
''', (search_term, search_term))
else:
cursor.execute('SELECT * FROM recipes ORDER BY created_at DESC')
return [dict(row) for row in cursor.fetchall()]

@staticmethod
def get_by_id(recipe_id):
with get_db_connection() as conn:
cursor = conn.cursor()
cursor.execute('SELECT * FROM recipes WHERE id = ?', (recipe_id,))
row = cursor.fetchone()
return dict(row) if row else None

@staticmethod
def update(recipe_id, data):
with get_db_connection() as conn:
cursor = conn.cursor()
cursor.execute('''
UPDATE recipes
SET title = ?, description = ?, ingredients = ?, steps = ?, updated_at = CURRENT_TIMESTAMP
WHERE id = ?
''', (data['title'], data.get('description', ''), data['ingredients'], data['steps'], recipe_id))
return cursor.rowcount > 0

@staticmethod
def delete(recipe_id):
with get_db_connection() as conn:
cursor = conn.cursor()
cursor.execute('DELETE FROM recipes WHERE id = ?', (recipe_id,))
return cursor.rowcount > 0
Loading