diff --git a/README.md b/README.md
index a30c837e..1eddc65c 100644
--- a/README.md
+++ b/README.md
@@ -1,283 +1,187 @@
-[](https://opensource.org/licenses/Apache-2.0)
-
-## Welcome
-
-Hello. Want to get started with Flask quickly? Good. You came to the right place. This Flask application framework is pre-configured with **Flask-SQLAlchemy**, **Flask-WTF**, **Fabric**, **Coverage**, and the **Bootstrap** frontend (among others). This will get your Flask app up and running on Heroku or PythonAnywhere quickly. Use this starter, boilerplate for all you new Flask projects. Cheers!
-
-
-
-
-
-**Designed for the [Real Python](http://www.realpython.com) course.**
-
-
-
-Preview the skeleton app here - [http://www.flaskboilerplate.com/](http://www.flaskboilerplate.com/)
-
-**EXAMPLE APP: [http://flasktaskr.herokuapp.com/](http://flasktaskr.herokuapp.com/)**
-
-**What is Flask?** Flask is a microframework for Python based on Werkzeug and Jinja2.
-
-Project Structure
---------
-
- ```sh
- ├── Procfile
- ├── Procfile.dev
- ├── README.md
- ├── app.py
- ├── config.py
- ├── error.log
- ├── forms.py
- ├── models.py
- ├── requirements.txt
- ├── static
- │ ├── css
- │ │ ├── bootstrap-3.0.0.min.css
- │ │ ├── bootstrap-theme-3.0.0.css
- │ │ ├── bootstrap-theme-3.0.0.min.css
- │ │ ├── font-awesome-3.2.1.min.css
- │ │ ├── layout.forms.css
- │ │ ├── layout.main.css
- │ │ ├── main.css
- │ │ ├── main.quickfix.css
- │ │ └── main.responsive.css
- │ ├── font
- │ │ ├── FontAwesome.otf
- │ │ ├── fontawesome-webfont.eot
- │ │ ├── fontawesome-webfont.svg
- │ │ ├── fontawesome-webfont.ttf
- │ │ └── fontawesome-webfont.woff
- │ ├── ico
- │ │ ├── apple-touch-icon-114-precomposed.png
- │ │ ├── apple-touch-icon-144-precomposed.png
- │ │ ├── apple-touch-icon-57-precomposed.png
- │ │ ├── apple-touch-icon-72-precomposed.png
- │ │ └── favicon.png
- │ ├── img
- │ └── js
- │ ├── libs
- │ │ ├── bootstrap-3.0.0.min.js
- │ │ ├── jquery-1.10.2.min.js
- │ │ ├── modernizr-2.6.2.min.js
- │ │ └── respond-1.3.0.min.js
- │ ├── plugins.js
- │ └── script.js
- └── templates
- ├── errors
- │ ├── 404.html
- │ └── 500.html
- ├── forms
- │ ├── forgot.html
- │ ├── login.html
- │ └── register.html
- ├── layouts
- │ ├── form.html
- │ └── main.html
- └── pages
- ├── placeholder.about.html
- └── placeholder.home.html
- ```
-
-### Screenshots
-
-
-
-
-
-
-### Quick Start
-
-1. Clone the repo
- ```
- $ git clone https://github.com/realpython/flask-boilerplate.git
- $ cd flask-boilerplate
- ```
-
-2. Initialize and activate a virtualenv:
- ```
- $ virtualenv --no-site-packages env
- $ source env/bin/activate
- ```
-
-3. Install the dependencies:
- ```
- $ pip install -r requirements.txt
- ```
-
-5. Run the development server:
- ```
- $ python app.py
- ```
-
-6. Navigate to [http://localhost:5000](http://localhost:5000)
-
-
-Deploying to Heroku
-------
-
-1. Signup for [Heroku](https://api.heroku.com/signup)
-2. Login to Heroku and download the [Heroku Toolbelt](https://toolbelt.heroku.com/)
-3. Once installed, open your command-line and run the following command - `heroku login`. Then follow the prompts:
-
- ```
- Enter your Heroku credentials.
- Email: michael@mherman.org
- Password (typing will be hidden):
- Could not find an existing public key.
- Would you like to generate one? [Yn]
- Generating new SSH public key.
- Uploading ssh public key /Users/michaelherman/.ssh/id_rsa.pub
- ```
-
-4. Activate your virtualenv
-5. Heroku recognizes the dependencies needed through a *requirements.txt* file. Create one using the following command: `pip freeze > requirements.txt`. Now, this will only create the dependencies from the libraries you installed using pip. If you used easy_install, you will need to add them directly to the file.
-6. Create a Procfile. Open up a text editor and save the following text in it:
-
- ```
- web: gunicorn app:app --log-file=-
- ```
-
- Then save the file in your applications root or main directory as *Procfile* (no extension). The word "web" indicates to Heroku that the application will be attached to the HTTP routing stack once deployed.
-
-7. Create a local Git repository (if necessary):
-
- ```
- $ git init
- $ git add .
- $ git commit -m "initial files"
- ```
-
-8. Create your app on Heroku:
-
- ```
- $ heroku create
- ```
-
-9. Deploy your code to Heroku:
-
- ```
- $ git push heroku master
- ```
-
-10. View the app in your browser:
-
- ```
- $ heroku open
- ```
-
-11. You app should look similar to this - [http://www.flaskboilerplate.com/](http://www.flaskboilerplate.com/)
-
-12. Having problems? Look at the Heroku error log:
-
- ```
- $ heroku logs
- ```
-
-### Deploying to PythonAnywhere
-
-1. Install [Git](http://git-scm.com/downloads) and [Python](http://install.python-guide.org/) - if you don't already have them, of course.
-
- > If you plan on working exclusively within PythonAnywhere, which you can, because it provides a cloud solution for hosting and developing your application, you can skip step one entirely. :)
-
-2. Sign up for [PythonAnywhere](https://www.pythonanywhere.com/pricing/), if you haven't already
-3. Once logged in, you should be on the Consoles tab.
-4. Clone this repo:
- ```
- $ git clone git://github.com/realpython/flask-boilerplate.git
- $ cd flask-boilerplate
- ```
-
-5. Create and activate a virtualenv:
- ```
- $ virtualenv venv --no-site-packages
- $ source venv/bin/activate
- ```
-
-6. Install requirements:
- ```
- $ pip install -r requirements.txt
- ```
-
-7. Next, back on PythonAnywhere, click Web tab.
-8. Click the "Add a new web app" link on the left; by default this will create an app at your-username.pythonanywhere.com, though if you've signed up for a paid "Web Developer" account you can also specify your own domain name here. Once you've decided on the location of the app, click the "Next" button.
-9. On the next page, click the "Flask" option, and on the next page just keep the default settings and click "Next" again.
-Once the web app has been created (it'll take 20 seconds or so), you'll see a link near the top of the page, under the "Reload web app" button, saying "It is configured via a WSGI file stored at..." and a filename. Click this, and you get to a page with a text editor.
-10. Put the following lines of code at the start of the WSGI file (changing "your-username" appropriately)
-
- ```
- activate_this = '/home/your-username/flask-boilerplate/venv/bin/activate_this.py'
- execfile(activate_this, dict(__file__=activate_this))
- ```
-
-11. Then update the following lines of code:
-
- from
-
- ```
- project_home = u'/home/your-username/mysite'
- ```
-
- to
-
- ```
- project_home = u'/home/your-username/flask-boilerplate'
- ```
-
- from
-
- ```
- from flask_app import app as application
- ```
-
- to
-
- ```
- from app import app as application
- ```
-
-12. Save the file.
-13. Go to the website http://your-username.pythonanywhere.com/ (or your own domain if you specified a different one earlier), and you should see something like this - [http://www.flaskboilerplate.com/](http://www.flaskboilerplate.com/).
-
-*Now you're ready to start developing!*
-
-***Need to PUSH your PythonAnywhere repo to Github?***
-
-1. Start a bash console
-2. Run:
-
- ```
- $ ssh-keygen -t rsa
- ```
-
-3. Just accept the defaults, then show the public key:
-
- ```
- $ cat ~/.ssh/id_rsa.pub
- ```
-
-4. Log in to GitHub.
-5. Go to the "Account settings" option at the top right (currently a wrench and a screwdriver crossed)
-6. Select "SSH Keys" from the list at the left.
-7. Click the "Add SSH key" button at top right.
-8. Enter a title (I suggest something like "From PythonAnywhere" and then paste the output of the previous "cat" command into the Key box.
-9. Click the green "Add key" button. You'll be prompted to enter your password.
-
-PUSH and PULL away!
-
-### What's next?
-
-1. Using Heroku? Make sure you deactivate your virtualenv once you're done deploying: `deactivate`
-2. Need to reactivate? (1) Unix - `source venv/bin/activate` (2) Windows - `venv\scripts\activate`
-4. Add your Google Analytics ID to the *main.html* file
-5. Add a domain name to [Heroku](https://devcenter.heroku.com/articles/custom-domains) or PythonAnywhere via a [CNAME](http://en.wikipedia.org/wiki/CNAME_record) record
-5. DEVELOP YOUR APP - need [help](http://realpython.com)?
-
-### Learn More
-
-1. [Getting Started with Python on Heroku](https://devcenter.heroku.com/articles/python)
-2. [PythonAnywhere - Help](https://www.pythonanywhere.com/help/)
-1. [Flask Documentation](http://flask.pocoo.org/docs/)
-2. [Flask Extensions](http://flask.pocoo.org/extensions/)
-1. [Real Python](http://www.realpythonfortheweb.com) :)
-
+# Note-Taking Flask Application
+
+A comprehensive note-taking web application built with Flask, featuring full CRUD operations and REST API endpoints.
+
+## Features
+
+### Core Features
+- **Create, Read, Update, Delete (CRUD)** operations for notes
+- **Tagging system** for organizing notes
+- **Search functionality** across titles and content
+- **RESTful API** with full CRUD endpoints
+- **Responsive web interface**
+- **SQLite database** for data persistence
+
+### API Endpoints
+
+#### Notes API
+| Method | Endpoint | Description |
+|--------|----------|-------------|
+| GET | `/api/notes` | Get all notes (supports search and tag filtering) |
+| GET | `/api/notes/` | Get a specific note by ID |
+| POST | `/api/notes` | Create a new note |
+| PUT | `/api/notes/` | Update an existing note |
+| DELETE | `/api/notes/` | Delete a note |
+
+#### Tags API
+| Method | Endpoint | Description |
+|--------|----------|-------------|
+| GET | `/api/tags` | Get all unique tags |
+
+### Note Structure
+```json
+{
+ "id": 1,
+ "title": "My Note Title",
+ "content": "Note content goes here...",
+ "tags": "work,important,flask",
+ "created_at": "2024-01-15T10:30:00",
+ "updated_at": "2024-01-15T14:45:00"
+}
+```
+
+## Installation
+
+1. **Clone the repository**
+ ```bash
+ git clone
+ cd note-taking_flask_app
+ ```
+
+2. **Create virtual environment**
+ ```bash
+ python -m venv venv
+ source venv/bin/activate # On Windows: venv\Scripts\activate
+ ```
+
+3. **Install dependencies**
+ ```bash
+ pip install -r requirements.txt
+ ```
+
+4. **Initialize database**
+ ```bash
+ python -c "from models import Base, engine; Base.metadata.create_all(bind=engine)"
+ ```
+
+5. **Run the application**
+ ```bash
+ python app.py
+ ```
+
+## Usage
+
+### Web Interface
+- Visit `http://localhost:5000` to access the web interface
+- Use the navigation to access different pages
+
+### API Usage Examples
+
+#### Create a new note
+```bash
+curl -X POST http://localhost:5000/api/notes \
+ -H "Content-Type: application/json" \
+ -d '{
+ "title": "My First Note",
+ "content": "This is the content of my first note",
+ "tags": "personal,important"
+ }'
+```
+
+#### Get all notes
+```bash
+curl http://localhost:5000/api/notes
+```
+
+#### Search notes
+```bash
+curl "http://localhost:5000/api/notes?search=flask"
+```
+
+#### Get notes by tag
+```bash
+curl "http://localhost:5000/api/notes?tag=work"
+```
+
+#### Update a note
+```bash
+curl -X PUT http://localhost:5000/api/notes/1 \
+ -H "Content-Type: application/json" \
+ -d '{
+ "title": "Updated Note Title",
+ "content": "Updated content"
+ }'
+```
+
+#### Delete a note
+```bash
+curl -X DELETE http://localhost:5000/api/notes/1
+```
+
+## Database Schema
+
+### Notes Table
+- `id` (INTEGER, PRIMARY KEY)
+- `title` (VARCHAR(200), NOT NULL)
+- `content` (TEXT, NOT NULL)
+- `tags` (VARCHAR(500), NULLABLE)
+- `created_at` (DATETIME, DEFAULT: current timestamp)
+- `updated_at` (DATETIME, DEFAULT: current timestamp, ON UPDATE: current timestamp)
+
+## Development
+
+### Project Structure
+```
+note-taking_flask_app/
+├── app.py # Main application file
+├── api.py # REST API endpoints
+├── models.py # Database models
+├── forms.py # WTForms definitions
+├── config.py # Configuration settings
+├── requirements.txt # Python dependencies
+├── database.db # SQLite database (created on first run)
+├── README.md # This file
+├── templates/ # HTML templates
+├── static/ # CSS, JS, and image files
+└── error.log # Application logs
+```
+
+### Adding New Features
+1. Update the database model in `models.py`
+2. Add corresponding API endpoints in `api.py`
+3. Update the web interface in `app.py`
+4. Add appropriate tests
+
+### Testing
+The application includes basic error handling and validation. For comprehensive testing:
+- Test all CRUD operations via API
+- Verify tag filtering and search functionality
+- Check error handling for invalid inputs
+- Test database transactions
+
+## Deployment
+
+### Using Heroku
+1. Create a Heroku app
+2. Set environment variables
+3. Deploy using Git
+
+### Using Docker
+```dockerfile
+FROM python:3.9-slim
+WORKDIR /app
+COPY requirements.txt .
+RUN pip install -r requirements.txt
+COPY . .
+CMD ["python", "app.py"]
+```
+
+## Contributing
+1. Fork the repository
+2. Create a feature branch
+3. Make your changes
+4. Add tests
+5. Submit a pull request
+
+## License
+This project is open source and available under the [MIT License](LICENSE).
diff --git a/api.py b/api.py
new file mode 100644
index 00000000..5784cbd6
--- /dev/null
+++ b/api.py
@@ -0,0 +1,310 @@
+#----------------------------------------------------------------------------#
+# Imports
+#----------------------------------------------------------------------------#
+from flask import Blueprint, jsonify, request
+from models import Note, db_session
+from datetime import datetime
+
+#----------------------------------------------------------------------------#
+# API Blueprint
+#----------------------------------------------------------------------------#
+api_bp = Blueprint('api', __name__, url_prefix='/api')
+
+#----------------------------------------------------------------------------#
+# Helper Functions
+#----------------------------------------------------------------------------#
+
+def validate_note_data(data):
+ """Validate note data from request"""
+ errors = []
+
+ if not data.get('title') or not data.get('title').strip():
+ errors.append('Title is required')
+
+ if not data.get('content') or not data.get('content').strip():
+ errors.append('Content is required')
+
+ return errors
+
+#----------------------------------------------------------------------------#
+# REST API Endpoints
+#----------------------------------------------------------------------------#
+
+# GET /api/notes - Get all notes
+@api_bp.route('/notes', methods=['GET'])
+def get_notes():
+ """
+ Get all notes
+
+ Query Parameters:
+ search: Search term to filter notes
+ tag: Filter notes by tag
+
+ Returns:
+ JSON array of all notes
+ """
+ try:
+ search_term = request.args.get('search')
+ tag_filter = request.args.get('tag')
+
+ if search_term:
+ notes = Note.search_notes(search_term)
+ elif tag_filter:
+ notes = Note.get_notes_by_tag(tag_filter)
+ else:
+ notes = Note.get_all_notes()
+
+ return jsonify({
+ 'success': True,
+ 'data': [note.to_dict() for note in notes],
+ 'count': len(notes)
+ }), 200
+
+ except Exception as e:
+ return jsonify({
+ 'success': False,
+ 'error': str(e)
+ }), 500
+
+# GET /api/notes/ - Get a single note
+@api_bp.route('/notes/', methods=['GET'])
+def get_note(note_id):
+ """
+ Get a single note by ID
+
+ Args:
+ note_id: The ID of the note to retrieve
+
+ Returns:
+ JSON object of the note
+ """
+ try:
+ note = Note.get_note_by_id(note_id)
+
+ if not note:
+ return jsonify({
+ 'success': False,
+ 'error': 'Note not found'
+ }), 404
+
+ return jsonify({
+ 'success': True,
+ 'data': note.to_dict()
+ }), 200
+
+ except Exception as e:
+ return jsonify({
+ 'success': False,
+ 'error': str(e)
+ }), 500
+
+# POST /api/notes - Create a new note
+@api_bp.route('/notes', methods=['POST'])
+def create_note():
+ """
+ Create a new note
+
+ Request Body:
+ title: Note title (required)
+ content: Note content (required)
+ tags: Comma-separated tags (optional)
+
+ Returns:
+ JSON object of the created note
+ """
+ try:
+ data = request.get_json()
+
+ if not data:
+ return jsonify({
+ 'success': False,
+ 'error': 'No data provided'
+ }), 400
+
+ # Validate data
+ errors = validate_note_data(data)
+ if errors:
+ return jsonify({
+ 'success': False,
+ 'errors': errors
+ }), 400
+
+ # Create note
+ note = Note.create_note(
+ title=data['title'].strip(),
+ content=data['content'].strip(),
+ tags=data.get('tags', '').strip() if data.get('tags') else None
+ )
+
+ return jsonify({
+ 'success': True,
+ 'data': note.to_dict(),
+ 'message': 'Note created successfully'
+ }), 201
+
+ except Exception as e:
+ db_session.rollback()
+ return jsonify({
+ 'success': False,
+ 'error': str(e)
+ }), 500
+
+# PUT /api/notes/ - Update a note
+@api_bp.route('/notes/', methods=['PUT'])
+def update_note(note_id):
+ """
+ Update an existing note
+
+ Args:
+ note_id: The ID of the note to update
+
+ Request Body:
+ title: Note title (optional)
+ content: Note content (optional)
+ tags: Comma-separated tags (optional)
+
+ Returns:
+ JSON object of the updated note
+ """
+ try:
+ note = Note.get_note_by_id(note_id)
+
+ if not note:
+ return jsonify({
+ 'success': False,
+ 'error': 'Note not found'
+ }), 404
+
+ data = request.get_json()
+
+ if not data:
+ return jsonify({
+ 'success': False,
+ 'error': 'No data provided'
+ }), 400
+
+ # Update fields if provided
+ title = data.get('title')
+ content = data.get('content')
+ tags = data.get('tags')
+
+ # Validate if title or content are provided
+ if title is not None and not title.strip():
+ return jsonify({
+ 'success': False,
+ 'error': 'Title cannot be empty'
+ }), 400
+
+ if content is not None and not content.strip():
+ return jsonify({
+ 'success': False,
+ 'error': 'Content cannot be empty'
+ }), 400
+
+ updated_note = Note.update_note(
+ note_id,
+ title=title.strip() if title else None,
+ content=content.strip() if content else None,
+ tags=tags.strip() if tags else None
+ )
+
+ return jsonify({
+ 'success': True,
+ 'data': updated_note.to_dict(),
+ 'message': 'Note updated successfully'
+ }), 200
+
+ except Exception as e:
+ db_session.rollback()
+ return jsonify({
+ 'success': False,
+ 'error': str(e)
+ }), 500
+
+# DELETE /api/notes/ - Delete a note
+@api_bp.route('/notes/', methods=['DELETE'])
+def delete_note(note_id):
+ """
+ Delete a note
+
+ Args:
+ note_id: The ID of the note to delete
+
+ Returns:
+ Success message
+ """
+ try:
+ note = Note.get_note_by_id(note_id)
+
+ if not note:
+ return jsonify({
+ 'success': False,
+ 'error': 'Note not found'
+ }), 404
+
+ success = Note.delete_note(note_id)
+
+ if success:
+ return jsonify({
+ 'success': True,
+ 'message': 'Note deleted successfully'
+ }), 200
+ else:
+ return jsonify({
+ 'success': False,
+ 'error': 'Failed to delete note'
+ }), 500
+
+ except Exception as e:
+ db_session.rollback()
+ return jsonify({
+ 'success': False,
+ 'error': str(e)
+ }), 500
+
+# GET /api/tags - Get all unique tags
+@api_bp.route('/tags', methods=['GET'])
+def get_tags():
+ """
+ Get all unique tags from all notes
+
+ Returns:
+ JSON array of unique tags
+ """
+ try:
+ notes = Note.get_all_notes()
+ all_tags = set()
+
+ for note in notes:
+ if note.tags:
+ tags = [tag.strip() for tag in note.tags.split(',')]
+ all_tags.update(tags)
+
+ return jsonify({
+ 'success': True,
+ 'data': sorted(list(all_tags))
+ }), 200
+
+ except Exception as e:
+ return jsonify({
+ 'success': False,
+ 'error': str(e)
+ }), 500
+
+#----------------------------------------------------------------------------#
+# Error Handlers
+#----------------------------------------------------------------------------#
+
+@api_bp.errorhandler(404)
+def not_found(error):
+ return jsonify({
+ 'success': False,
+ 'error': 'Endpoint not found'
+ }), 404
+
+@api_bp.errorhandler(500)
+def internal_error(error):
+ return jsonify({
+ 'success': False,
+ 'error': 'Internal server error'
+ }), 500
diff --git a/app.py b/app.py
index 64d2796c..1cd78077 100644
--- a/app.py
+++ b/app.py
@@ -1,41 +1,35 @@
#----------------------------------------------------------------------------#
# Imports
#----------------------------------------------------------------------------#
-
-from flask import Flask, render_template, request
-# from flask.ext.sqlalchemy import SQLAlchemy
+from flask import Flask, render_template, request, jsonify
+from flask_cors import CORS # Add CORS support for API
import logging
from logging import Formatter, FileHandler
from forms import *
import os
+# Import API and models
+from api import api_bp
+from models import Note, db_session
+
#----------------------------------------------------------------------------#
# App Config.
#----------------------------------------------------------------------------#
-
app = Flask(__name__)
app.config.from_object('config')
-#db = SQLAlchemy(app)
+CORS(app) # Enable CORS for all routes
-# Automatically tear down SQLAlchemy.
-'''
-@app.teardown_request
+# Register API blueprint
+app.register_blueprint(api_bp)
+
+#----------------------------------------------------------------------------#
+# Database Teardown
+#----------------------------------------------------------------------------#
+@app.teardown_appcontext
def shutdown_session(exception=None):
+ """Remove database session after each request"""
db_session.remove()
-'''
-# Login required decorator.
-'''
-def login_required(test):
- @wraps(test)
- def wrap(*args, **kwargs):
- if 'logged_in' in session:
- return test(*args, **kwargs)
- else:
- flash('You need to login first.')
- return redirect(url_for('login'))
- return wrap
-'''
#----------------------------------------------------------------------------#
# Controllers.
#----------------------------------------------------------------------------#
diff --git a/database.db b/database.db
new file mode 100644
index 00000000..8bf5bd15
Binary files /dev/null and b/database.db differ
diff --git a/forms.py b/forms.py
index 3be12d82..27a3ce7b 100644
--- a/forms.py
+++ b/forms.py
@@ -1,15 +1,15 @@
from flask_wtf import Form
-from wtforms import TextField, PasswordField
+from wtforms import StringField, PasswordField
from wtforms.validators import DataRequired, EqualTo, Length
# Set your classes here.
class RegisterForm(Form):
- name = TextField(
+ name = StringField(
'Username', validators=[DataRequired(), Length(min=6, max=25)]
)
- email = TextField(
+ email = StringField(
'Email', validators=[DataRequired(), Length(min=6, max=40)]
)
password = PasswordField(
@@ -23,11 +23,11 @@ class RegisterForm(Form):
class LoginForm(Form):
- name = TextField('Username', [DataRequired()])
+ name = StringField('Username', [DataRequired()])
password = PasswordField('Password', [DataRequired()])
class ForgotForm(Form):
- email = TextField(
+ email = StringField(
'Email', validators=[DataRequired(), Length(min=6, max=40)]
)
diff --git a/models.py b/models.py
index e438d665..b250d284 100644
--- a/models.py
+++ b/models.py
@@ -1,9 +1,14 @@
-from sqlalchemy import create_engine
-from sqlalchemy.orm import scoped_session, sessionmaker
+#----------------------------------------------------------------------------#
+# Imports
+#----------------------------------------------------------------------------#
+from sqlalchemy import create_engine, Column, Integer, String, Text, DateTime, ForeignKey
+from sqlalchemy.orm import scoped_session, sessionmaker, relationship
from sqlalchemy.ext.declarative import declarative_base
-# from sqlalchemy import Column, Integer, String
-# from app import db
+from datetime import datetime
+#----------------------------------------------------------------------------#
+# Database Setup
+#----------------------------------------------------------------------------#
engine = create_engine('sqlite:///database.db', echo=True)
db_session = scoped_session(sessionmaker(autocommit=False,
autoflush=False,
@@ -11,21 +16,103 @@
Base = declarative_base()
Base.query = db_session.query_property()
-# Set your classes here.
+#----------------------------------------------------------------------------#
+# Models
+#----------------------------------------------------------------------------#
-'''
-class User(Base):
- __tablename__ = 'Users'
+class Note(Base):
+ """
+ Note Model
+ Represents a note in the note-taking application
+
+ Attributes:
+ id (int): Primary key
+ title (str): Note title
+ content (str): Note content/body
+ tags (str): Comma-separated tags for categorization
+ created_at (datetime): Creation timestamp
+ updated_at (datetime): Last update timestamp
+ """
+ __tablename__ = 'notes'
+
+ id = Column(Integer, primary_key=True)
+ title = Column(String(200), nullable=False)
+ content = Column(Text, nullable=False)
+ tags = Column(String(500), nullable=True) # Comma-separated tags
+ created_at = Column(DateTime, default=datetime.utcnow)
+ updated_at = Column(DateTime, default=datetime.utcnow, onupdate=datetime.utcnow)
+
+ def __repr__(self):
+ return f''
+
+ def to_dict(self):
+ """Convert note object to dictionary for JSON serialization"""
+ return {
+ 'id': self.id,
+ 'title': self.title,
+ 'content': self.content,
+ 'tags': self.tags,
+ 'created_at': self.created_at.isoformat() if self.created_at else None,
+ 'updated_at': self.updated_at.isoformat() if self.updated_at else None
+ }
+
+ @classmethod
+ def create_note(cls, title, content, tags=None):
+ """Create a new note"""
+ note = cls(title=title, content=content, tags=tags)
+ db_session.add(note)
+ db_session.commit()
+ return note
+
+ @classmethod
+ def get_all_notes(cls):
+ """Get all notes ordered by creation date (newest first)"""
+ return cls.query.order_by(cls.created_at.desc()).all()
+
+ @classmethod
+ def get_note_by_id(cls, note_id):
+ """Get a single note by ID"""
+ return cls.query.get(note_id)
+
+ @classmethod
+ def update_note(cls, note_id, title=None, content=None, tags=None):
+ """Update an existing note"""
+ note = cls.get_note_by_id(note_id)
+ if note:
+ if title is not None:
+ note.title = title
+ if content is not None:
+ note.content = content
+ if tags is not None:
+ note.tags = tags
+ note.updated_at = datetime.utcnow()
+ db_session.commit()
+ return note
+ return None
+
+ @classmethod
+ def delete_note(cls, note_id):
+ """Delete a note by ID"""
+ note = cls.get_note_by_id(note_id)
+ if note:
+ db_session.delete(note)
+ db_session.commit()
+ return True
+ return False
+
+ @classmethod
+ def search_notes(cls, search_term):
+ """Search notes by title or content"""
+ search_pattern = f"%{search_term}%"
+ return cls.query.filter(
+ cls.title.ilike(search_pattern) | cls.content.ilike(search_pattern)
+ ).order_by(cls.created_at.desc()).all()
+
+ @classmethod
+ def get_notes_by_tag(cls, tag):
+ """Get all notes with a specific tag"""
+ tag_pattern = f"%{tag}%"
+ return cls.query.filter(cls.tags.ilike(tag_pattern)).order_by(cls.created_at.desc()).all()
- id = db.Column(db.Integer, primary_key=True)
- name = db.Column(db.String(120), unique=True)
- email = db.Column(db.String(120), unique=True)
- password = db.Column(db.String(30))
-
- def __init__(self, name=None, password=None):
- self.name = name
- self.password = password
-'''
-
-# Create tables.
+# Create tables
Base.metadata.create_all(bind=engine)
diff --git a/pytest.ini b/pytest.ini
new file mode 100644
index 00000000..c34cae2f
--- /dev/null
+++ b/pytest.ini
@@ -0,0 +1,11 @@
+[tool:pytest]
+testpaths = .
+python_files = test_*.py
+python_classes = Test*
+python_functions = test_*
+addopts = -v --tb=short --strict-markers
+markers =
+ unit: Unit tests
+ integration: Integration tests
+ api: API tests
+ model: Model tests
diff --git a/requirements.txt b/requirements.txt
index 597021e6..d90ef4de 100755
--- a/requirements.txt
+++ b/requirements.txt
@@ -1,8 +1,30 @@
-Fabric
-Flask-SQLAlchemy
-Flask-WTF
-WTForms
-coverage
-# ecdsa
-# paramiko
-# pycrypto
+# Core Flask dependencies
+Flask==2.3.3
+Flask-SQLAlchemy==3.0.5
+Flask-WTF==1.1.1
+Flask-CORS==4.0.0
+
+# Database
+SQLAlchemy==2.0.21
+
+# Forms and validation
+WTForms==3.0.1
+
+# Template engine
+Jinja2==3.1.2
+MarkupSafe==2.1.3
+
+# Security
+Werkzeug==2.3.7
+itsdangerous==2.1.2
+
+# Development utilities
+python-dotenv==1.0.0
+coverage==7.3.2
+
+# Optional: For enhanced JSON handling
+flask-marshmallow==0.15.0
+marshmallow-sqlalchemy==0.29.0
+
+# Deployment
+Fabric==3.2.2
diff --git a/templates/notes.html b/templates/notes.html
new file mode 100644
index 00000000..1587835e
--- /dev/null
+++ b/templates/notes.html
@@ -0,0 +1,267 @@
+{% extends "layouts/main.html" %}
+
+{% block content %}
+