diff --git a/MYREADME.md b/MYREADME.md new file mode 100644 index 00000000..544a4e8c --- /dev/null +++ b/MYREADME.md @@ -0,0 +1,21 @@ +## Features Added + +### Notes CRUD API (`/api/notes`) +- Create Note (POST `/api/notes`) +- Read All Notes (GET `/api/notes`) +- Read One Note (GET `/api/notes/`) +- Update Note (PUT `/api/notes/`) +- Delete Note (DELETE `/api/notes/`) + +All responses are in JSON format and use proper status codes and error messages. + +## Frontend +- HTML form added inside `templates/pages/placeholder.home.html` +- Dynamically loads notes using JavaScript +- Supports adding, editing, and deleting notes inline + +## Unit Tests +- All CRUD operations are tested with `pytest` in `tests/test_notes_api.py` +- Includes both **positive** and **negative** test cases +- Covers edge cases like missing fields, invalid note IDs, and empty content +- Achieves **100% test coverage** for the Notes API \ No newline at end of file diff --git a/app.py b/app.py index 64d2796c..5341ac0c 100644 --- a/app.py +++ b/app.py @@ -8,6 +8,12 @@ from logging import Formatter, FileHandler from forms import * import os +from routes.notes_api import notes_bp +from flasgger import Swagger + + + + #----------------------------------------------------------------------------# # App Config. @@ -15,6 +21,26 @@ app = Flask(__name__) app.config.from_object('config') + +app.register_blueprint(notes_bp) + + +swagger = Swagger(app, template={ + "components": { + "schemas": { + "Note": { + "type": "object", + "properties": { + "id": {"type": "integer"}, + "title": {"type": "string"}, + "content": {"type": "string"} + } + } + } + } +}) + + #db = SQLAlchemy(app) # Automatically tear down SQLAlchemy. @@ -91,6 +117,11 @@ def not_found_error(error): app.logger.addHandler(file_handler) app.logger.info('errors') +@app.route('/notes') +def notes_page(): + return render_template('pages/notes.html') + + #----------------------------------------------------------------------------# # Launch. #----------------------------------------------------------------------------# diff --git a/models.py b/models.py index e438d665..590295be 100644 --- a/models.py +++ b/models.py @@ -1,6 +1,10 @@ -from sqlalchemy import create_engine +from sqlalchemy import create_engine, Column, Integer, String, Text, DateTime from sqlalchemy.orm import scoped_session, sessionmaker -from sqlalchemy.ext.declarative import declarative_base +from sqlalchemy.orm import declarative_base +from datetime import datetime, timezone +created_at = Column(DateTime, default=lambda: datetime.now(timezone.utc)) + + # from sqlalchemy import Column, Integer, String # from app import db @@ -11,6 +15,22 @@ Base = declarative_base() Base.query = db_session.query_property() +class Note(Base): + __tablename__ = 'notes' + + id = Column(Integer, primary_key=True) + title = Column(String(100), nullable=False) + content = Column(Text, nullable=True) + created_at = Column(DateTime, default=datetime.utcnow) + + def to_dict(self): + return { + "id": self.id, + "title": self.title, + "content": self.content, + "created_at": self.created_at.isoformat() + } + # Set your classes here. ''' diff --git a/routes/notes_api.py b/routes/notes_api.py new file mode 100644 index 00000000..8cc2ea92 --- /dev/null +++ b/routes/notes_api.py @@ -0,0 +1,74 @@ +from flask import Blueprint, request, jsonify +from models import Note, db_session + +notes_bp = Blueprint('notes', __name__, url_prefix='/api/notes') + +@notes_bp.route('', methods=['POST']) +def create_note(): + """ + Create a new note + --- + tags: + - Notes + requestBody: + required: true + content: + application/json: + schema: + type: object + required: + - title + properties: + title: + type: string + content: + type: string + responses: + 201: + description: Note created successfully + content: + application/json: + schema: + $ref: '#/components/schemas/Note' + 400: + description: Missing required field + """ + data = request.get_json() + if not data or not data.get('title'): + return jsonify({'error': 'Title is required'}), 400 + note = Note(title=data['title'], content=data.get('content')) + db_session.add(note) + db_session.commit() + return jsonify(note.to_dict()), 201 + +@notes_bp.route('', methods=['GET']) +def get_notes(): + notes = db_session.query(Note).all() + return jsonify([note.to_dict() for note in notes]), 200 + +@notes_bp.route('/', methods=['GET']) +def get_note(note_id): + note = db_session.query(Note).get(note_id) + if not note: + return jsonify({'error': 'Note not found'}), 404 + return jsonify(note.to_dict()), 200 + +@notes_bp.route('/', methods=['PUT']) +def update_note(note_id): + note = db_session.query(Note).get(note_id) + if not note: + return jsonify({'error': 'Note not found'}), 404 + data = request.get_json() + note.title = data.get('title', note.title) + note.content = data.get('content', note.content) + db_session.commit() + return jsonify(note.to_dict()), 200 + +@notes_bp.route('/', 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'}), 200 diff --git a/templates/pages/placeholder.home.html b/templates/pages/placeholder.home.html index 45355d68..27dc6b5b 100644 --- a/templates/pages/placeholder.home.html +++ b/templates/pages/placeholder.home.html @@ -2,9 +2,111 @@ {% block title %}Home{% endblock %} {% block content %} -