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
File renamed without changes.
27 changes: 27 additions & 0 deletions .github/workflows/docker-ci.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,27 @@
name: Build and Push Docker Image

on: [push, pull_request]

jobs:
build:
runs-on: ubuntu-latest

steps:
- name: Checkout code
uses: actions/checkout@v4

- name: Set up Docker Buildx
uses: docker/setup-buildx-action@v3

- name: Log in to Docker Hub
uses: docker/login-action@v3
with:
username: ${{ secrets.DOCKER_USERNAME }}
password: ${{ secrets.DOCKER_PASSWORD }}

- name: Build and push Docker image
uses: docker/build-push-action@v5
with:
context: .
push: true
tags: ${{ secrets.DOCKER_USERNAME }}/restful-api:${{ github.sha }}
24 changes: 24 additions & 0 deletions .github/workflows/python-app.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
name: Python CI

on: [push, pull_request]

jobs:
build:
runs-on: ubuntu-latest

steps:
- name: Check out repository code
uses: actions/checkout@v3

- name: Set up Python
uses: actions/setup-python@v4
with:
python-version: '3.x' # Specify the Python version you are using

- name: Install dependencies
run: |
python -m pip install --upgrade pip
pip install -r requirements.txt

- name: Run tests
run: pytest tests/unit/test_student.py
4 changes: 2 additions & 2 deletions Dockerfile
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@
FROM python:3.11

# Set the working directory
WORKDIR /app
WORKDIR /

# Copy the requirements and the application code
COPY requirements.txt requirements.txt
Expand All @@ -15,4 +15,4 @@ COPY . .
EXPOSE 8888

# Set the entrypoint to run the application
CMD ["python", "app/app.py"]
CMD ["python", "app.py"]
67 changes: 67 additions & 0 deletions _script/add_init_student.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,67 @@
import os
from dotenv import load_dotenv
from sqlalchemy import create_engine
from sqlalchemy.orm import sessionmaker
from sqlalchemy.orm import DeclarativeBase
from sqlalchemy import (
Column,
Integer,
String,
)

class Base(DeclarativeBase):
def to_dict(self):
return {c.name: getattr(self, c.name) for c in self.__table__.columns}

class Student(Base):
__tablename__ = 'student'

id = Column(Integer, primary_key=True, unique=True, nullable=False)
name = Column(String(100), nullable=False)
age = Column(Integer, nullable=False)
major = Column(String(100), nullable=False)

# 載入環境變數
load_dotenv()
DATABASE_HOST=os.getenv('DATABASE_HOST', 'postgres')
DATABASE_PORT=os.getenv('DATABASE_PORT', '5432')
DATABASE_PASS=os.getenv('DATABASE_PASS', 'postgres')
DATABASE_USER=os.getenv('DATABASE_USER', 'postgres')
DATABASE_NAME=os.getenv('DATABASE_NAME', 'postgres')

# 建立連線 URL
DATABASE_URL = f'postgresql://{DATABASE_USER}:{DATABASE_PASS}@{DATABASE_HOST}:{DATABASE_PORT}/{DATABASE_NAME}'

# 創建資料庫引擎
engine = create_engine(DATABASE_URL)

# 創建 session
Session = sessionmaker(bind=engine)
session = Session()

# 插入四個學生資料
def insert_students():
try:
# 創建四個學生實例
student1 = Student(name="Alice", age=20, major="Computer Science")
student2 = Student(name="Bob", age=22, major="Mathematics")
student3 = Student(name="Charlie", age=21, major="Physics")
student4 = Student(name="David", age=23, major="Engineering")

# 將學生物件加入 session
session.add_all([student1, student2, student3, student4])

# 提交事務到資料庫
session.commit()

print("Students inserted successfully!")

except Exception as e:
session.rollback()
print(f"An error occurred: {e}")
finally:
session.close()

if __name__ == "__main__":
Base.metadata.create_all(engine)
insert_students()
102 changes: 102 additions & 0 deletions app.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,102 @@
from flask import Flask, jsonify, request
from sqlalchemy.orm import scoped_session

from orm.models import Student
from orm.database import Session, init_db

app = Flask(__name__)

@app.route('/api/student', methods=['GET'])
def get_student():
session = scoped_session(Session)
try:
student_id = request.args.get("id", type=int)

if student_id:
student = session.get(Student, student_id)
if not student:
return jsonify({"error": f"Student with ID {student_id} was not found"}), 404
return jsonify(student.to_dict()), 200
else:
students = session.query(Student).all()
if not students:
return jsonify({"error": "No students found"}), 404
return jsonify([student.to_dict() for student in students]), 200
except Exception as e:
session.rollback()
return jsonify({"error": str(e)}), 500
finally:
session.remove()


# Endpoint to add a student
@app.route('/api/student', methods=['POST'])
def add_student():
session = scoped_session(Session)
try:
student_data = request.get_json()
name = student_data.get('name')
age = student_data.get('age')
major = student_data.get('major')

if not all([name, age, major]):
return jsonify({"error": "Missing required fields"}), 400

new_student = Student(name=name, age=age, major=major)
session.add(new_student)
except Exception as e:
return jsonify({"error": str(e)}), 500

try:
session.commit()
except Exception as e:
session.rollback()
return jsonify({"error": str(e)}), 500
finally:
session.remove()

return jsonify({"message": "Student added successfully"}), 201


@app.route('/api/student/<int:id>', methods=['DELETE'])
def delete_student(id):
session = scoped_session(Session)
try:
student = session.get(Student, id)
if student:
session.delete(student)
session.commit()
return jsonify({"message": "Student deleted successfully"}), 200
else:
return jsonify({"error": "Student not found"}), 404
except Exception as e:
session.rollback()
return jsonify({"error": str(e)}), 500
finally:
session.remove()

@app.route('/api/student/<int:id>', methods=['PUT'])
def update_student(id):
session = scoped_session(Session)
try:
student = session.get(Student, id)
if not student:
return jsonify({"error": "Student not found"}), 404

student_data = request.get_json()
student.name = student_data.get('name', student.name)
student.age = student_data.get('age', student.age)
student.major = student_data.get('major', student.major)

session.commit()
return jsonify({"message": "Student updated successfully"}), 200
except Exception as e:
session.rollback()
return jsonify({"error": str(e)}), 500
finally:
session.remove()


if __name__ == '__main__':
init_db()
app.run(host='0.0.0.0', port=8787)
82 changes: 0 additions & 82 deletions app/app.py

This file was deleted.

24 changes: 19 additions & 5 deletions docker-compose.yml
Original file line number Diff line number Diff line change
Expand Up @@ -6,28 +6,42 @@ services:
POSTGRES_USER: postgres
POSTGRES_PASSWORD: postgres
POSTGRES_DB: postgres

ports:
- "5432:5432"
volumes:
- postgres_data:/var/lib/postgresql/data
# volumes:
# - postgres_data:/var/lib/postgresql/data
restart: always
healthcheck:
test: ["CMD-SHELL", "pg_isready -U postgres"]
interval: 10s
retries: 5
networks:
- app-network

app:
build:
context: .
container_name: flask_app
environment:
DATABASE_URL: "postgresql://postgres:postgres@postgres:5432/mydatabase"
DATABASE_HOST: postgres
DATABASE_PORT: 5432
DATABASE_PASS: postgres
DATABASE_USER: postgres
DATABASE_NAME: postgres
ports:
- "8888:8888"
depends_on:
postgres:
condition: service_healthy
restart: always
networks:
- app-network

networks:
app-network:
driver: bridge


volumes:
postgres_data:
# volumes:
# postgres_data:
Empty file added orm/__init__.py
Empty file.
21 changes: 21 additions & 0 deletions orm/database.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
import os

from dotenv import load_dotenv
from sqlalchemy import create_engine
from sqlalchemy.orm import sessionmaker

from orm.models import Base

load_dotenv()
DATABASE_HOST=os.getenv('DATABASE_HOST', 'postgres')
DATABASE_PORT=os.getenv('DATABASE_PORT', '5432')
DATABASE_PASS=os.getenv('DATABASE_PASS', 'postgres')
DATABASE_USER=os.getenv('DATABASE_USER', 'postgres')
DATABASE_NAME=os.getenv('DATABASE_NAME', 'postgres')

DATABASE_URL = f'postgresql://{DATABASE_USER}:{DATABASE_PASS}@{DATABASE_HOST}:{DATABASE_PORT}/{DATABASE_NAME}'
engine = create_engine(DATABASE_URL) # Connect to database
Session = sessionmaker(bind=engine)

def init_db():
Base.metadata.create_all(engine)
Loading