Skip to content

Commit

Permalink
Merge pull request #2 from narlock/reminders
Browse files Browse the repository at this point in the history
Reminders
  • Loading branch information
narlock authored Oct 5, 2024
2 parents 17ddd05 + 14d945c commit 5f2237d
Show file tree
Hide file tree
Showing 15 changed files with 731 additions and 9 deletions.
4 changes: 4 additions & 0 deletions api/app/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -54,5 +54,9 @@ def create_app():
# Register kanban controller
from app.controller.kanban_controller import kanban_bp
app.register_blueprint(kanban_bp)

# Register reminder controller
from app.controller.reminder_controller import reminder_bp
app.register_blueprint(reminder_bp)

return app
160 changes: 160 additions & 0 deletions api/app/controller/reminder_controller.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,160 @@
from flask import Blueprint, request, jsonify
from app.model.reminder_model import ReminderModel
from app.schema.reminder_schema import ReminderSchema
from app import db

from datetime import datetime

# Define Reminder Blueprint
reminder_bp = Blueprint('reminder_bp', __name__)

# Initialize Reminder Schema
reminder_schema = ReminderSchema()
reminders_schema = ReminderSchema(many=True)

# Endpoint to create a new reminder
@reminder_bp.route('/reminder', methods=['POST'])
def create_reminder():
"""
Creates a reminder based on the contents of
the request body for the user.
"""
data = request.get_json()

# Validate incoming request data
errors = reminder_schema.validate(data)
if errors:
return jsonify(errors), 400

# Create a new reminder instance
new_reminder = ReminderModel(
user_id=data['user_id'],
title=data['title'],
description=data.get('description'),
remind_at=data['remind_at'],
repeat_interval=data.get('repeat_interval'),
repeat_until=data.get('repeat_until'),
repeat_count=data.get('repeat_count')
)

# Add and commit the reminder to the database
db.session.add(new_reminder)
db.session.commit()

# Return the newly created reminder
return jsonify(reminder_schema.dump(new_reminder)), 201


# Endpoint to retrieve all reminders for a specific user
@reminder_bp.route('/reminder/user/<int:user_id>', methods=['GET'])
def get_user_reminders(user_id):
"""
Retrieves all reminders for the user by user_id.
"""
# Query the database for reminders by user_id
reminders = ReminderModel.query.filter_by(user_id=user_id).all()

if not reminders:
return jsonify({"message": "No reminders found for this user."}), 404

# Serialize the list of reminders and return
return jsonify(reminders_schema.dump(reminders)), 200


# Endpoint to retrieve all reminders
@reminder_bp.route('/reminder', methods=['GET'])
def get_all_reminders():
"""
Returns all reminders stored in the database.
"""
# Query the database for all reminders
reminders = ReminderModel.query.all()

# Serialize the list of reminders and return
return jsonify(reminders_schema.dump(reminders)), 200


# Endpoint to retrieve a reminder by its ID
@reminder_bp.route('/reminder/<int:id>', methods=['GET'])
def get_reminder(id):
"""
Retrieves a reminder by its ID.
"""
# Query the database for a reminder by its ID
reminder = ReminderModel.query.get(id)

if reminder is None:
return jsonify({"message": "Reminder not found."}), 404

# Serialize and return the reminder
return jsonify(reminder_schema.dump(reminder)), 200


# Endpoint to delete a reminder by its ID
@reminder_bp.route('/reminder/<int:id>', methods=['DELETE'])
def delete_reminder(id):
"""
Delete a reminder by its ID.
"""
# Query the database for a reminder by its ID
reminder = ReminderModel.query.get(id)

if reminder is None:
return jsonify({"message": "Reminder not found."}), 404

# Delete the reminder and commit the changes
db.session.delete(reminder)
db.session.commit()

# Return 204 No Content status
return '', 204

@reminder_bp.route('/reminder/<int:id>/date', methods=['PUT'])
def update_reminder_date(id):
"""
Updates the remind_at date of a reminder using the given date string (YYYY-MM-DD).
The time component of the remind_at field will remain unchanged.
"""
# Get the existing reminder by its ID
reminder = ReminderModel.query.get(id)

if reminder is None:
return jsonify({"message": "Reminder not found."}), 404

# Parse the incoming JSON data
data = request.get_json()
new_date_str = data.get('remind_at')

# Validate the date string
if not new_date_str:
return jsonify({"message": "The 'remind_at' date field is required."}), 400

try:
# Parse the new date from the string
new_date = datetime.strptime(new_date_str, "%Y-%m-%d")
except ValueError:
return jsonify({"message": "Invalid date format. Use 'YYYY-MM-DD'."}), 400

# Check if reminder.remind_at is a datetime object or string
if isinstance(reminder.remind_at, str):
try:
# Convert string to datetime if reminder.remind_at is in ISO 8601 format
current_remind_at = datetime.fromisoformat(reminder.remind_at)
except ValueError:
return jsonify({"message": "The existing remind_at field has an invalid format."}), 500
elif isinstance(reminder.remind_at, datetime):
current_remind_at = reminder.remind_at
else:
return jsonify({"message": "Unexpected format for remind_at field."}), 500

# Create a new remind_at datetime by combining the new date with the existing time
updated_remind_at = datetime.combine(new_date, current_remind_at.time())

# Update the remind_at field of the reminder
reminder.remind_at = updated_remind_at.isoformat() # Convert back to ISO format string

# Commit the changes to the database
db.session.commit()

# Return the updated reminder
return jsonify(ReminderSchema().dump(reminder)), 200
21 changes: 21 additions & 0 deletions api/app/controller/user_controller.py
Original file line number Diff line number Diff line change
Expand Up @@ -110,6 +110,27 @@ def delete_user(id):
# Return 204 No Content
return '', 204

# Set Timezone endpoint
@user_bp.route('/user/<int:id>/timezone', methods=['PUT'])
def set_user_timezone(id):
user = User.query.get(id)
if user is None:
return jsonify({'message': 'User not found'}), 404

data = request.get_json()

# Check if timezone is provided in the request body
if 'timezone' not in data:
return jsonify({'message': 'Timezone is required'}), 400

timezone = data['timezone']

# Update user's timezone if valid
user.timezone = timezone
db.session.commit()

return jsonify(user_schema.dump(user))

# Search endpoint
@user_bp.route('/user/search', methods=['POST'])
def search_users():
Expand Down
16 changes: 16 additions & 0 deletions api/app/model/reminder_model.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
from app import db
from datetime import datetime

class ReminderModel(db.Model):
__tablename__ = 'reminder'

id = db.Column(db.BigInteger, primary_key=True, autoincrement=True)
user_id = db.Column(db.BigInteger, db.ForeignKey('user.id', ondelete='CASCADE'), nullable=False)
title = db.Column(db.String(255), nullable=False)
description = db.Column(db.Text, nullable=True)
remind_at = db.Column(db.DateTime, nullable=False)
repeat_interval = db.Column(db.String(50), nullable=True)
repeat_until = db.Column(db.DateTime, nullable=True) # NULL means no end
repeat_count = db.Column(db.Integer, nullable=True) # NULL means indefinite
created_at = db.Column(db.DateTime, default=datetime.utcnow)
updated_at = db.Column(db.DateTime, default=datetime.utcnow, onupdate=datetime.utcnow)
1 change: 1 addition & 0 deletions api/app/model/user_model.py
Original file line number Diff line number Diff line change
Expand Up @@ -6,5 +6,6 @@ class User(db.Model):
id = db.Column(db.BigInteger, primary_key=True, autoincrement=False)
tokens = db.Column(db.Integer, nullable=False)
stime = db.Column(db.BigInteger, nullable=False)
timezone = db.Column(db.String(100), nullable=False, default='UTC')
hex = db.Column(db.String(7), nullable=True)
trivia = db.Column(db.Integer, nullable=True)
27 changes: 27 additions & 0 deletions api/app/schema/reminder_schema.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,27 @@
from marshmallow import Schema, fields, validate, ValidationError
import re

# Custom validator for combinations of mtwhfsu
def validate_repeat_interval(value):
# Allow standard intervals
standard_intervals = ['daily', 'weekly', 'monthly', 'yearly']
if value in standard_intervals:
return True

# Regex pattern to match any combination of mtwhfsu
if re.fullmatch(r'[mtwhfsu]+', value):
return True

raise ValidationError(f"Invalid repeat_interval: {value}. Must be a valid combination of 'mtwhfsu' or a standard interval (daily, weekly, monthly, yearly).")

class ReminderSchema(Schema):
id = fields.Int(dump_only=True)
user_id = fields.Int(required=True)
title = fields.Str(required=True, validate=validate.Length(max=255))
description = fields.Str()
remind_at = fields.DateTime(required=True, format="%Y-%m-%dT%H:%M:%S")
repeat_interval = fields.Str(validate=validate_repeat_interval) # Custom validator here
repeat_until = fields.DateTime(format="%Y-%m-%dT%H:%M:%S")
repeat_count = fields.Int(validate=validate.Range(min=0))
created_at = fields.DateTime(dump_only=True)
updated_at = fields.DateTime(dump_only=True)
1 change: 1 addition & 0 deletions api/app/schema/user_schema.py
Original file line number Diff line number Diff line change
Expand Up @@ -4,5 +4,6 @@ class UserSchema(Schema):
id = fields.Int(required=True)
tokens = fields.Int(required=True)
stime = fields.Int(required=True)
timezone = fields.Str(required=False)
hex = fields.Str(required=False)
trivia = fields.Int(required=False)
7 changes: 4 additions & 3 deletions api/tools/utils.py
Original file line number Diff line number Diff line change
@@ -1,10 +1,11 @@
from datetime import datetime, timezone
import pytz
from datetime import datetime

class DateTimeUtils():

@staticmethod
def get_utc_date_now():
now_utc = datetime.now(timezone.utc)
now_utc = datetime.now(pytz.utc)
d = now_utc.day
mth = now_utc.month
yr = now_utc.year
Expand All @@ -13,7 +14,7 @@ def get_utc_date_now():

@staticmethod
def get_utc_month_year_now():
now_utc = datetime.now(timezone.utc)
now_utc = datetime.now(pytz.utc)
mth = now_utc.month
yr = now_utc.year
return mth, yr
44 changes: 44 additions & 0 deletions bot/apps/info/timezone.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,44 @@
"""
timezone.py
author: narlock
Interface to change timezone information for a user.
"""

import cfg
import discord
import pytz

from datetime import datetime
from client.alder.interface.user_client import UserClient

class TimeZoneApp():
@staticmethod
def set_timezone(interaction: discord.Interaction, timezone: str):
"""
Sets the user's timezone.
"""
UserClient.create_user_if_dne(interaction.user.id)

# Validate the timezone string
try:
pytz.timezone(timezone)
except:
message = f'The timezone {timezone} is not a valid timezone.\nPlease try again.\n\nExample: America/New_York'
return cfg.ErrorEmbed.message(message)

# The timezone string is valid. Set it
try:
response = UserClient.set_timezone(interaction.user.id, timezone)

if response is None:
raise Exception

embed = discord.Embed(title=f'Timezone Changed', color=0xffa500)
embed.add_field(name='\u200b', value=f'Your timezone has been updated to {timezone}.', inline=False)
embed.add_field(name='\u200b', value=cfg.EMBED_FOOTER_STRING, inline=False)
return embed
except:
message = f'An unexpected error occurred'
return cfg.ErrorEmbed.message(message)

7 changes: 7 additions & 0 deletions bot/apps/productivity/reminder.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
"""
reminder.py
author: narlock
Interface for sending reminders to users
"""

Loading

0 comments on commit 5f2237d

Please sign in to comment.