Skip to content

Added Features: CRUD API for Tags and Comments #68

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Open
wants to merge 1 commit into
base: master
Choose a base branch
from
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
25 changes: 25 additions & 0 deletions app/__init__.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
from flask import Flask
from sqlalchemy import create_engine
from sqlalchemy.orm import scoped_session, sessionmaker
from sqlalchemy.ext.declarative import declarative_base

engine = create_engine('sqlite:///database.db', echo=True)
db_session = scoped_session(sessionmaker(bind=engine))

Base = declarative_base()
Base.query = db_session.query_property()

def create_app():
app = Flask(__name__)

from .routes import notes_bp, tags_bp, comments_bp
app.register_blueprint(notes_bp)
app.register_blueprint(tags_bp)
app.register_blueprint(comments_bp)

@app.teardown_appcontext
def shutdown_session(exception=None):
db_session.remove()

Base.metadata.create_all(bind=engine)
return app
31 changes: 31 additions & 0 deletions app/models.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,31 @@
from . import Base
from sqlalchemy import Column, Integer, String, Text, Table, ForeignKey, DateTime
from sqlalchemy.orm import relationship
import datetime

note_tag = Table(
'note_tag', Base.metadata,
Column('note_id', Integer, ForeignKey('notes.id')),
Column('tag_id', Integer, ForeignKey('tags.id'))
)

class Note(Base):
__tablename__ = 'notes'
id = Column(Integer, primary_key=True)
title = Column(String(100))
content = Column(Text)
tags = relationship('Tag', secondary=note_tag, back_populates='notes')

class Tag(Base):
__tablename__ = 'tags'
id = Column(Integer, primary_key=True)
name = Column(String(50), unique=True)
notes = relationship('Note', secondary=note_tag, back_populates='tags')

class Comment(Base):
__tablename__ = 'comments'
id = Column(Integer, primary_key=True)
content = Column(Text, nullable=False)
post_id = Column(Integer, ForeignKey('notes.id'), nullable=False)
created_at = Column(DateTime, default=datetime.datetime.utcnow)
note = relationship('Note', backref='comments')
111 changes: 111 additions & 0 deletions app/routes.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,111 @@
from flask import Blueprint, request, jsonify
from .models import Note, Tag, note_tag, Comment
from . import db_session

notes_bp = Blueprint('notes', __name__, url_prefix='/api/notes')
tags_bp = Blueprint('tags', __name__, url_prefix='/api/tags')
comments_bp = Blueprint('comments', __name__, url_prefix='/api/comments')

@notes_bp.route('/', methods=['GET'])
def get_notes():
notes = db_session.query(Note).all()
return jsonify([{'id': n.id, 'title': n.title, 'content': n.content} for n in notes])

@notes_bp.route('/', methods=['POST'])
def create_note():
data = request.json
new_note = Note(title=data['title'], content=data['content'])
db_session.add(new_note)
db_session.commit()
return jsonify({'message': 'Note created'}), 201

@notes_bp.route('/<int:note_id>', methods=['PUT'])
def update_note(note_id):
data = request.json
note = db_session.query(Note).get(note_id)
if not note:
return jsonify({'error': 'Note not found'}), 404
note.title = data['title']
note.content = data['content']
db_session.commit()
return jsonify({'message': 'Note updated'})

@notes_bp.route('/<int:note_id>', methods=['DELETE'])
def delete_note(note_id):
note = db_session.query(Note).get(note_id)
if not note:
return jsonify({'error': 'Note not found'}), 404
db_session.delete(note)
db_session.commit()
return jsonify({'message': 'Note deleted'})

@tags_bp.route('/', methods=['POST'])
def create_tag():
data = request.json
tag = Tag(name=data['name'])
db_session.add(tag)
db_session.commit()
return jsonify({'message': 'Tag created', 'id': tag.id}), 201

@tags_bp.route('/', methods=['GET'])
def get_tags():
tags = db_session.query(Tag).all()
return jsonify([{'id': t.id, 'name': t.name} for t in tags])

@tags_bp.route('/<int:tag_id>', methods=['PUT'])
def update_tag(tag_id):
data = request.json
tag = db_session.query(Tag).get(tag_id)
if not tag:
return jsonify({'error': 'Tag not found'}), 404
tag.name = data['name']
db_session.commit()
return jsonify({'message': 'Tag updated'})

@tags_bp.route('/<int:tag_id>', methods=['DELETE'])
def delete_tag(tag_id):
tag = db_session.query(Tag).get(tag_id)
if not tag:
return jsonify({'error': 'Tag not found'}), 404
db_session.delete(tag)
db_session.commit()
return jsonify({'message': 'Tag deleted'})

@comments_bp.route('/', methods=['POST'])
def create_comment():
data = request.json
comment = Comment(content=data['content'], post_id=data['post_id'])
db_session.add(comment)
db_session.commit()
return jsonify({'message': 'Comment created', 'id': comment.id}), 201

@comments_bp.route('/', methods=['GET'])
def get_comments():
post_id = request.args.get('post_id')
query = db_session.query(Comment)
if post_id:
query = query.filter_by(post_id=post_id)
comments = query.all()
return jsonify([
{'id': c.id, 'content': c.content, 'post_id': c.post_id, 'created_at': c.created_at.isoformat()}
for c in comments
])

@comments_bp.route('/<int:comment_id>', methods=['PUT'])
def update_comment(comment_id):
data = request.json
comment = db_session.query(Comment).get(comment_id)
if not comment:
return jsonify({'error': 'Comment not found'}), 404
comment.content = data['content']
db_session.commit()
return jsonify({'message': 'Comment updated'})

@comments_bp.route('/<int:comment_id>', methods=['DELETE'])
def delete_comment(comment_id):
comment = db_session.query(Comment).get(comment_id)
if not comment:
return jsonify({'error': 'Comment not found'}), 404
db_session.delete(comment)
db_session.commit()
return jsonify({'message': 'Comment deleted'})
Binary file added database.db
Binary file not shown.
File renamed without changes.
6 changes: 6 additions & 0 deletions run.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
from app import create_app

app = create_app()

if __name__ == '__main__':
app.run(debug=True)
43 changes: 43 additions & 0 deletions test_api.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,43 @@
import pytest
import json
import sys
import os

sys.path.insert(0, os.path.abspath(os.path.join(os.path.dirname(__file__), '..')))
from app import create_app

@pytest.fixture
def client():
app = create_app()
app.config['TESTING'] = True
app.config['SQLALCHEMY_DATABASE_URI'] = 'sqlite:///:memory:' # Use in-memory DB for tests
with app.test_client() as client:
with app.app_context():
yield client

def test_post_note(client):
payload = {"title": "Test Note", "content": "This is a test note."}
response = client.post("/api/notes/", data=json.dumps(payload), content_type="application/json")
assert response.status_code == 201
assert b'Note created' in response.data

def test_get_notes(client):
# Ensure at least one note exists
client.post("/api/notes/", data=json.dumps({"title": "Temp", "content": "Temp"}), content_type="application/json")
response = client.get("/api/notes/")
assert response.status_code == 200
assert isinstance(response.get_json(), list)

def test_update_note(client):
# Create a note first
client.post("/api/notes/", data=json.dumps({"title": "Temp", "content": "Temp"}), content_type="application/json")
response = client.put("/api/notes/1", data=json.dumps({"title": "Updated", "content": "Updated"}), content_type="application/json")
assert response.status_code == 200
assert b'Note updated' in response.data

def test_delete_note(client):
# Create a note first
client.post("/api/notes/", data=json.dumps({"title": "Temp", "content": "Temp"}), content_type="application/json")
response = client.delete("/api/notes/1")
assert response.status_code == 200
assert b'Note deleted' in response.data
Empty file added tests/__init__.py
Empty file.
40 changes: 40 additions & 0 deletions tests/test_api.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,40 @@
import pytest
import json
import sys
import os

sys.path.insert(0, os.path.abspath(os.path.join(os.path.dirname(__file__), '..')))
from app import create_app()

@pytest.fixture
def client():
app = create_app()
app.config['TESTING'] = True
app.config['SQLALCHEMY_DATABASE_URI'] = 'sqlite:///:memory:' # Use in-memory DB for tests
with app.test_client() as client:
with app.app_context():
yield client

def test_post_note(client):
payload = {"title": "Test Note", "content": "This is a test note."}
response = client.post("/api/notes/", data=json.dumps(payload), content_type="application/json")
assert response.status_code == 201
assert b'Note created' in response.data

def test_get_notes(client):
client.post("/api/notes/", data=json.dumps({"title": "Temp", "content": "Temp"}), content_type="application/json")
response = client.get("/api/notes/")
assert response.status_code == 200
assert isinstance(response.get_json(), list)

def test_update_note(client):
client.post("/api/notes/", data=json.dumps({"title": "Temp", "content": "Temp"}), content_type="application/json")
response = client.put("/api/notes/1", data=json.dumps({"title": "Updated", "content": "Updated"}), content_type="application/json")
assert response.status_code == 200
assert b'Note updated' in response.data

def test_delete_note(client):
client.post("/api/notes/", data=json.dumps({"title": "Temp", "content": "Temp"}), content_type="application/json")
response = client.delete("/api/notes/1")
assert response.status_code == 200
assert b'Note deleted' in response.data