æ¯æ¥ã®æ°åãèšé²ã§ããæ¥œããWeb APIãäœã£ãŠã¿ãŸãããïŒ çµµæåã䜿ã£ãŠããã®æ¥ã®æ°åã5段éã§èšé²ã§ããã·ã³ãã«ãªAPIã§ãã ããã«ãChromeéçºè ããŒã«ã䜿ã£ãŠãããã¯ãŒã¯éä¿¡ã®ä»çµã¿ãåŠã³ãŸãïŒ
mood-tracker/
âââ docker-compose.yml
âââ Dockerfile
âââ app.py
âââ requirements.txt
âââ static/
â âââ index.html
âââ templates/requirements.txtFlask==3.0.0
flask-cors==4.0.0DockerfileFROM python:3.11-slim
WORKDIR /app
COPY requirements.txt .
RUN pip install -r requirements.txt
COPY . .
CMD ["python", "app.py"]docker-compose.ymlversion: '3.8'
services:
mood-api:
build: .
ports:
- "5000:5000"
volumes:
- .:/app
environment:
- FLASK_ENV=developmentapp.pyfrom flask import Flask, jsonify, request, send_from_directory
from flask_cors import CORS
from datetime import datetime
app = Flask(__name__)
CORS(app) # ãã©ãŠã¶ããã®ã¢ã¯ã»ã¹ãèš±å¯
# æ°åã®ããŒã¿ãä¿åãããªã¹ãïŒæ¬æ¥ã¯ããŒã¿ããŒã¹ã䜿ããŸãïŒ
moods = []
# çµµæåãšæ°åã®å¯Ÿå¿
MOOD_EMOJIS = {
5: "ð æé«ïŒ",
4: "ð è¯ãæã",
3: "ð ãŸããŸã",
2: "ð ã¡ãã£ãšç²ãã",
1: "ð¢ ã€ãã..."
}
# HTMLããŒãžã衚瀺
@app.route('/')
def index():
return send_from_directory('static', 'index.html')
# APIæ
å ±
@app.route('/api')
def api_info():
return jsonify({
"message": "ð æ°åãã©ãã«ãŒAPIãžããããïŒ",
"endpoints": {
"GET /api/moods": "ãã¹ãŠã®æ°åèšé²ãèŠã",
"POST /api/moods": "æ°ããæ°åãèšé²ãã",
"GET /api/today": "仿¥ã®æ°åãèŠã"
}
})
# ãã¹ãŠã®æ°åèšé²ãååŸ
@app.route('/api/moods', methods=['GET'])
def get_all_moods():
return jsonify({
"moods": moods,
"total": len(moods)
})
# æ°ããæ°åãèšé²
@app.route('/api/moods', methods=['POST'])
def add_mood():
data = request.get_json()
# ã¹ã³ã¢ã®ãã§ãã¯
score = data.get('score')
if not score or score not in range(1, 6):
return jsonify({"error": "ã¹ã³ã¢ã¯1ã5ã®æ°åã§å
¥åããŠãã ãã"}), 400
# æ°ããæ°åèšé²ãäœæ
new_mood = {
"id": len(moods) + 1,
"date": datetime.now().strftime("%Y-%m-%d %H:%M:%S"),
"score": score,
"emoji": MOOD_EMOJIS[score],
"note": data.get('note', '') # ã¡ã¢ïŒãªãã·ã§ã³ïŒ
}
moods.append(new_mood)
return jsonify({
"message": "æ°åãèšé²ããŸããïŒ",
"mood": new_mood
}), 201
# 仿¥ã®æ°åãååŸ
@app.route('/api/today', methods=['GET'])
def get_today_mood():
today = datetime.now().strftime("%Y-%m-%d")
today_moods = [m for m in moods if m['date'].startswith(today)]
if not today_moods:
return jsonify({"message": "仿¥ã¯ãŸã èšé²ããããŸãã"})
# 仿¥ã®å¹³åã¹ã³ã¢ãèšç®
avg_score = sum(m['score'] for m in today_moods) / len(today_moods)
return jsonify({
"date": today,
"records": today_moods,
"average_score": round(avg_score, 1),
"summary": MOOD_EMOJIS[round(avg_score)]
})
if __name__ == '__main__':
app.run(host='0.0.0.0', port=5000, debug=True)static/index.html<!DOCTYPE html>
<html lang="ja">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>æ°åãã©ãã«ãŒ</title>
<style>
body {
font-family: Arial, sans-serif;
max-width: 800px;
margin: 0 auto;
padding: 20px;
background-color: #f0f0f0;
}
.container {
background: white;
border-radius: 10px;
padding: 30px;
box-shadow: 0 2px 10px rgba(0,0,0,0.1);
}
h1 {
color: #333;
text-align: center;
}
.mood-selector {
display: flex;
justify-content: space-around;
margin: 30px 0;
}
.mood-button {
font-size: 2em;
padding: 10px 20px;
border: none;
background: #f8f8f8;
border-radius: 10px;
cursor: pointer;
transition: all 0.3s;
}
.mood-button:hover {
background: #e0e0e0;
transform: scale(1.1);
}
.mood-button.selected {
background: #4CAF50;
color: white;
}
textarea {
width: 100%;
padding: 10px;
border: 1px solid #ddd;
border-radius: 5px;
resize: vertical;
}
button {
background: #4CAF50;
color: white;
padding: 10px 20px;
border: none;
border-radius: 5px;
cursor: pointer;
font-size: 16px;
}
button:hover {
background: #45a049;
}
.records {
margin-top: 30px;
}
.record {
background: #f8f8f8;
padding: 10px;
margin: 10px 0;
border-radius: 5px;
}
.developer-tip {
background: #fff3cd;
border: 1px solid #ffeaa7;
padding: 15px;
border-radius: 5px;
margin: 20px 0;
}
</style>
</head>
<body>
<div class="container">
<h1>ð 仿¥ã®æ°åã¯ã©ãïŒ</h1>
<div class="developer-tip">
ð¡ <strong>éçºè
ããŒã«ãéããïŒ</strong><br>
F12ããŒïŒãŸãã¯Ctrl+Shift+IïŒãæŒããŠãããããã¯ãŒã¯ãã¿ããéããŠã¿ããïŒ
</div>
<div class="mood-selector">
<button class="mood-button" data-score="1">ð¢</button>
<button class="mood-button" data-score="2">ð</button>
<button class="mood-button" data-score="3">ð</button>
<button class="mood-button" data-score="4">ð</button>
<button class="mood-button" data-score="5">ð</button>
</div>
<div>
<h3>ã¡ã¢ïŒä»»æïŒ</h3>
<textarea id="note" rows="3" placeholder="仿¥ã¯ã©ããªæ¥ã ã£ãïŒ"></textarea>
</div>
<div style="text-align: center; margin: 20px 0;">
<button onclick="submitMood()">èšé²ãã</button>
<button onclick="loadMoods()">èšé²ãèŠã</button>
<button onclick="loadToday()">仿¥ã®æ°å</button>
</div>
<div id="message" style="text-align: center; color: #4CAF50; margin: 10px 0;"></div>
<div id="records" class="records"></div>
</div>
<script>
let selectedScore = null;
// æ°åãã¿ã³ã®ã¯ãªãã¯ã€ãã³ã
document.querySelectorAll('.mood-button').forEach(button => {
button.addEventListener('click', function() {
document.querySelectorAll('.mood-button').forEach(b => b.classList.remove('selected'));
this.classList.add('selected');
selectedScore = parseInt(this.dataset.score);
console.log('éžæãããã¹ã³ã¢:', selectedScore);
});
});
// æ°åãéä¿¡
async function submitMood() {
if (!selectedScore) {
alert('æ°åãéžãã§ãã ããïŒ');
return;
}
const note = document.getElementById('note').value;
console.log('éä¿¡ããŒã¿:', { score: selectedScore, note: note });
try {
const response = await fetch('/api/moods', {
method: 'POST',
headers: {
'Content-Type': 'application/json',
},
body: JSON.stringify({
score: selectedScore,
note: note
})
});
const data = await response.json();
console.log('ã¬ã¹ãã³ã¹:', data);
document.getElementById('message').textContent = data.message;
document.getElementById('note').value = '';
// éžæããªã»ãã
document.querySelectorAll('.mood-button').forEach(b => b.classList.remove('selected'));
selectedScore = null;
} catch (error) {
console.error('ãšã©ãŒ:', error);
alert('ãšã©ãŒãçºçããŸãã');
}
}
// ãã¹ãŠã®èšé²ãèªã¿èŸŒã
async function loadMoods() {
console.log('èšé²ãååŸäž...');
try {
const response = await fetch('/api/moods');
const data = await response.json();
console.log('ååŸããããŒã¿:', data);
const recordsDiv = document.getElementById('records');
recordsDiv.innerHTML = '<h3>ð ãã¹ãŠã®èšé²</h3>';
if (data.moods.length === 0) {
recordsDiv.innerHTML += '<p>ãŸã èšé²ããããŸãã</p>';
} else {
data.moods.forEach(mood => {
recordsDiv.innerHTML += `
<div class="record">
<strong>${mood.emoji}</strong> - ${mood.date}
${mood.note ? `<br>ã¡ã¢: ${mood.note}` : ''}
</div>
`;
});
}
} catch (error) {
console.error('ãšã©ãŒ:', error);
}
}
// 仿¥ã®æ°åãèªã¿èŸŒã
async function loadToday() {
console.log('仿¥ã®æ°åãååŸäž...');
try {
const response = await fetch('/api/today');
const data = await response.json();
console.log('仿¥ã®ããŒã¿:', data);
const recordsDiv = document.getElementById('records');
if (data.records) {
recordsDiv.innerHTML = `
<h3>ð 仿¥ã®æ°åãµããªãŒ</h3>
<p>å¹³åã¹ã³ã¢: ${data.average_score} - ${data.summary}</p>
<p>èšé²æ°: ${data.records.length}ä»¶</p>
`;
data.records.forEach(mood => {
recordsDiv.innerHTML += `
<div class="record">
${mood.emoji} - ${mood.date}
${mood.note ? `<br>ã¡ã¢: ${mood.note}` : ''}
</div>
`;
});
} else {
recordsDiv.innerHTML = '<p>' + data.message + '</p>';
}
} catch (error) {
console.error('ãšã©ãŒ:', error);
}
}
</script>
</body>
</html>mkdir mood-tracker
cd mood-tracker
mkdir staticäžèšã®ãã¡ã€ã«ãããããäœæã»é 眮ããŸãã
docker-compose up --buildhttp://localhost:5000 ã«ã¢ã¯ã»ã¹ããŠã¿ãŸãããïŒ
F12 ãŸã㯠Ctrl + Shift + ICmd + Option + Iäžéšã®ã¿ããããNetworkãïŒãããã¯ãŒã¯ïŒãã¯ãªãã¯
moods ïŒãªã¯ãšã¹ããããšã³ããã€ã³ãïŒ200 ïŒæåïŒfetch ãŸã㯠xhrmoods ãªã¯ãšã¹ããã¯ãªãã¯Request URL: http://localhost:5000/api/moods
Request Method: POST
Status Code: 201 Created
Content-Type: application/jsonéä¿¡ããããŒã¿ã確èªïŒ
{
"score": 5,
"note": "ããã°ã©ãã³ã°ã楜ããïŒ"
}ãµãŒããŒããã®è¿çïŒ
{
"message": "æ°åãèšé²ããŸããïŒ",
"mood": {
"id": 1,
"date": "2024-01-15 14:30:00",
"score": 5,
"emoji": "ð æé«ïŒ",
"note": "ããã°ã©ãã³ã°ã楜ããïŒ"
}
}console.log() ã®åºåã確èªéçºè ããŒã«ã§èŠãå 容ãcurlã§ã確èªïŒ
curl -X POST http://localhost:5000/api/moods \
-H "Content-Type: application/json" \
-d '{"score": 5, "note": "curlãããã¹ãïŒ"}' \
-v-v ãªãã·ã§ã³ã§ããããŒæ
å ±ã衚瀺ãããŸãïŒ
curl http://localhost:5000/api/moods -vContent-Length ãèŠã€ããŠã¿ããCtrl+Shift+C: èŠçŽ éžæã¢ãŒãCtrl+Shift+M: ã¬ã¹ãã³ã·ããã¶ã€ã³ã¢ãŒãCtrl+R: ãªããŒãïŒãã£ãã·ã¥ã¯ãªã¢: Ctrl+Shift+RïŒããã§ä»¥äžã®ã¹ãã«ã身ã«ã€ããŸããïŒ
次ã®ã¹ãããïŒ
楜ããããã°ã©ãã³ã°ã©ã€ããïŒ ð