Skip to content
Open
Show file tree
Hide file tree
Changes from 7 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
30 changes: 30 additions & 0 deletions .circleci/config.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,30 @@
version: 2.1

jobs:
test:
docker:
- image: circleci/python:3.12
steps:
- checkout
- run:
name: Install uv
command: |
curl -sSL https://install.uv.tools | bash
- run:
name: Install dependencies
command: uv init # Installs dependencies from pyproject.toml
- run:
name: Run pytest tests
command: pytest tests
- run:
name: Run ruff for linting
command: ruff check .
- run:
name: Run mypy for type checking
command: mypy .

workflows:
version: 2
test_and_static_analysis:
jobs:
- test
8 changes: 8 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -154,9 +154,17 @@ dmypy.json
# Cython debug symbols
cython_debug/

# Mac metadata file
.DS_Store

# PyCharm
# JetBrains specific template is maintained in a separate JetBrains.gitignore that can
# be found at https://github.com/github/gitignore/blob/main/Global/JetBrains.gitignore
# and can be added to the global gitignore or merged into this file. For a more nuclear
# option (not recommended) you can uncomment the following to ignore the entire idea folder.
#.idea/
.DS_Store

credentials.json
token.json
token_calendar.json
1 change: 1 addition & 0 deletions .python-version
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
3.12
117 changes: 116 additions & 1 deletion README.md
Original file line number Diff line number Diff line change
@@ -1 +1,116 @@
# mAIgic-nyu
# mAIgic

## Project Description
**mAIgic** is an AI-powered assistant designed to enhance productivity and organization by tracking messages and important information across multiple platforms. It identifies and reminds users about follow-ups, ensuring timely responses and efficient information management. With intelligent search capabilities, mAIgic enables users to easily locate conversations and details tied to specific individuals or tasks.

The project supports popular platforms such as Slack, Trello, Gmail, and Google Chat, providing seamless follow-up tracking, reminders, and contextual search across these communication channels. This integration leverages AI to automate the reminder process, making it easier to stay on top of essential follow-ups and tasks.

## Features
- Unit testing with `pytest`
- Static code analysis with `ruff` (linter)
- Type checking with `mypy`
- Continuous Integration (CI) pipeline with CircleCI, running both tests and static analysis automatically on every push.

---

## Setup Instructions

### 1. Clone the Repository
```bash
git clone https://github.com/your-username/mAIgic.git
cd mAIgic
```

### 2. Install Dependencies
```bash
pip install -r requirements.txt
```

Alternatively, if using the `uv` package manager:
```bash
uv sync
```

The above command will create a virtual environment `.venv` and install all the dependencies from the `uv.lock` file. Activate the environment by executing
```bash
source ./.venv/bin/activate
```

---

## Running Tests

### 1. Pytest
You can run unit tests using `pytest` by running the following command:
```bash
pytest tests
```

**Example Tests in `tests/test_samples.py`:**
- Basic arithmetic (addition, subtraction, multiplication, division)
- String operations (case-insensitive checks)
- List and dictionary operations
- Exception handling tests (checking for specific exceptions)

### 2. Ruff (Linter)
Use `ruff` to run static analysis (linting) on your code:
```bash
ruff check .
```

### 3. Mypy (Type Checking)
To perform static type checking using `mypy`, run:
```bash
mypy .
```

---

## CircleCI Configuration

The project is integrated with CircleCI for continuous integration. Every push to the repository automatically triggers the following steps:
1. **Install dependencies**: Installs `pytest`, `ruff`, and `mypy`.
2. **Run tests**: Executes all tests in the `tests/` folder using `pytest`.
3. **Run static analysis**: Runs `ruff` for linting and `mypy` for type checking.



### View CircleCI Status:
The latest CircleCI build can be viewed [here](https://app.circleci.com/pipelines/circleci/L7kpZ5X2tZyEgUBhR4SB2j/NxWta8V9bEwRzTNu9Vzc3c/9/workflows/be567b6f-0c9c-41c8-91f7-e4789784b41f).

---

## Troubleshooting

### Common Errors:
1. **Missing Dependencies**: If any dependency-related issues arise, ensure all dependencies are installed by running:
```bash
pip install -r requirements.txt
```
or
```bash
uv init
```


## License
This project is licensed under the MIT License.


### Explanation of the Sections:

1. **Project Description**: Provides an overview of what the project does.
2. **Features**: Highlights the major tools and features used.
3. **Setup Instructions**: Guides users on how to clone the repo, install dependencies, and initialize the project.
4. **Running Tests**: Explains how to run unit tests, static analysis, and type checks.
5. **CircleCI Configuration**: Outlines how CircleCI automates the process of testing and analysis, including the YAML configuration.
6. **Troubleshooting**: Offers solutions for common errors like string comparison and missing dependencies.
7. **License**: A placeholder for your project's license type.

## Teammates
- Siddharth Singh - sms10221@nyu.edu
- Adittya Mittal - am14079@nyu.edu
- Anushka Tawte - at5849@nyu.edu
- Rafael de Leon - rdl404@nyu.edu
- Alex Ying - aty2009@nyu.edu
- Mridul Mittal - mm13171@nyu.edu
13 changes: 13 additions & 0 deletions app.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
# app.py

import threading
from src.slack.slack_bot import run_slack_bot
from src.gmail.gmail_bot import run_gmail_to_slack

if __name__ == "__main__":
# Start Gmail bot in a separate thread
gmail_thread = threading.Thread(target=run_gmail_to_slack)
gmail_thread.start()

# Start Slack bot
run_slack_bot()
6 changes: 6 additions & 0 deletions hello.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
def main():
print("Hello from maigic!")


if __name__ == "__main__":
main()
3 changes: 3 additions & 0 deletions mypy.ini
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
[mypy]
python_version = 3.10
ignore_missing_imports = True
15 changes: 15 additions & 0 deletions pyproject.toml
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
[project]
name = "mAIgic"
version = "0.1.0"
description = "A project focused on testing, static analysis, and CI integration with CircleCI"
readme = "README.md"
requires-python = ">=3.12"
dependencies = [
"pytest",
"ruff",
"mypy"
]

[tool.ruff.lint]
select = ["ALL"]
ignore = ["D203", "D213", "COM812", "ISC001", "PLW0603", "FBT001", "FBT002", "SLF001", "ANN001", "ANN201", "PLR2004", "DTZ005", "SIM117", "D103", "BLE001"]
Binary file added requirements.txt
Binary file not shown.
3 changes: 3 additions & 0 deletions src/calendar/__init__.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
# src/calendar/__init__.py
from .calendar_api import authenticate_google_calendar, add_event_to_calendar, update_calendar_event, delete_calendar_event
from .calendar_sync import add_task_to_calendar, update_task_in_calendar, delete_task_from_calendar
107 changes: 107 additions & 0 deletions src/calendar/calendar_api.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,107 @@
# src/calendar/calendar_api.py

import os
from google.oauth2.credentials import Credentials
from google_auth_oauthlib.flow import InstalledAppFlow
from googleapiclient.discovery import build
from google.auth.transport.requests import Request

# Load environment variables
SCOPES = ['https://www.googleapis.com/auth/calendar']

# Authenticate and set up Google Calendar API
def authenticate_google_calendar():
"""
Authenticate and set up the Google Calendar API client.

Checks for existing credentials in `token_calendar.json`. If credentials
are invalid or do not exist, initiates the OAuth2 flow to generate new credentials.

Returns:
google.oauth2.credentials.Credentials: Authenticated credentials for the API.

Raises:
FileNotFoundError: If `credentials.json` is not found in the current directory.
"""
creds = None
if os.path.exists('token_calendar.json'):
creds = Credentials.from_authorized_user_file('token_calendar.json', SCOPES)
if not creds or not creds.valid:
if creds and creds.expired and creds.refresh_token:
creds.refresh(Request())
else:
flow = InstalledAppFlow.from_client_secrets_file('credentials.json', SCOPES)
creds = flow.run_local_server(port=0)
with open('token_calendar.json', 'w') as token:
token.write(creds.to_json())
return creds

# Add an event to Google Calendar
def add_event_to_calendar(task_name: str, due_date: str) -> str:
"""
Add a new event to the user's primary Google Calendar.

Args:
task_name (str): The name or summary of the event.
due_date (str): The start and end time of the event in ISO 8601 format
(e.g., '2024-12-04T10:00:00-07:00').

Returns:
str: The unique event ID of the created event.

Raises:
googleapiclient.errors.HttpError: If the event creation fails.
"""
service = build('calendar', 'v3', credentials=authenticate_google_calendar())
event = {
'summary': task_name,
'start': {
'dateTime': due_date,
},
'end': {
'dateTime': due_date,
},
}
event = service.events().insert(calendarId='primary', body=event).execute()
return event['id']

# Update an existing event in Google Calendar
def update_calendar_event(event_id: str, task_name: str, due_date: str) -> dict:
"""
Update an existing event in the user's primary Google Calendar.

Args:
event_id (str): The unique ID of the event to be updated.
task_name (str): The updated name or summary of the event.
due_date (str): The updated start and end time of the event in ISO 8601 format.

Returns:
dict: The updated event resource.

Raises:
googleapiclient.errors.HttpError: If the event update fails.
"""
service = build('calendar', 'v3', credentials=authenticate_google_calendar())
event = service.events().get(calendarId='primary', eventId=event_id).execute()
event['summary'] = task_name
event['start']['dateTime'] = due_date
event['end']['dateTime'] = due_date
updated_event = service.events().update(calendarId='primary', eventId=event_id, body=event).execute()
return updated_event

# Delete an event from Google Calendar
def delete_calendar_event(event_id : str) -> None:
"""
Delete an event from the user's primary Google Calendar.

Args:
event_id (str): The unique ID of the event to be deleted.

Returns:
None

Raises:
googleapiclient.errors.HttpError: If the event deletion fails.
"""
service = build('calendar', 'v3', credentials=authenticate_google_calendar())
service.events().delete(calendarId='primary', eventId=event_id).execute()
59 changes: 59 additions & 0 deletions src/calendar/calendar_sync.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,59 @@
# src/calendar/calendar_sync.py

from .calendar_api import add_event_to_calendar, update_calendar_event, delete_calendar_event

# Function to handle task addition with due date
def add_task_to_calendar(task_name: str, due_date: str) -> str:
"""
Add a task to the Google Calendar if a due date is provided.

Args:
task_name (str): The name or summary of the task.
due_date (str): The due date and time of the task in ISO 8601 format
(e.g., '2024-12-04T10:00:00-07:00').

Returns:
str: The unique event ID of the created calendar event, or None if no due date is provided.

Raises:
googleapiclient.errors.HttpError: If the event creation fails.
"""
if due_date:
event_id = add_event_to_calendar(task_name, due_date)
return event_id
return None

# Function to handle task update with a new due date
def update_task_in_calendar(event_id: str, task_name: str, new_due_date: str) -> None:
"""
Update an existing task in the Google Calendar with a new name or due date.

Args:
event_id (str): The unique ID of the calendar event to be updated.
task_name (str): The updated name or summary of the task.
new_due_date (str): The updated due date and time in ISO 8601 format.

Returns:
None

Raises:
googleapiclient.errors.HttpError: If the event update fails.
"""
if new_due_date:
update_calendar_event(event_id, task_name, new_due_date)

# Function to handle task deletion
def delete_task_from_calendar(event_id: str) -> None:
"""
Delete a task from the Google Calendar.

Args:
event_id (str): The unique ID of the calendar event to be deleted.

Returns:
None

Raises:
googleapiclient.errors.HttpError: If the event deletion fails.
"""
delete_calendar_event(event_id)
4 changes: 4 additions & 0 deletions src/gmail/__init__.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
# src/gmail/__init__.py

from .gmail_api import authenticate_gmail, fetch_unread_emails
#from .gmail_bot import fetch_and_post_emails, run_gmail_to_slack
Loading