{"id":28713894,"url":"https://github.com/markbosire/google_sheets_db_gdrive_storage","last_synced_at":"2026-04-14T15:32:04.671Z","repository":{"id":296299240,"uuid":"992911235","full_name":"markbosire/google_sheets_db_gdrive_storage","owner":"markbosire","description":"A REST API using Google Sheets as a database and Google Drive for file storage.","archived":false,"fork":false,"pushed_at":"2025-05-29T23:04:46.000Z","size":12,"stargazers_count":0,"open_issues_count":0,"forks_count":0,"subscribers_count":0,"default_branch":"main","last_synced_at":"2025-06-15T01:01:45.888Z","etag":null,"topics":["api","database","google-drive-api","google-sheets-api","nodejs","postman"],"latest_commit_sha":null,"homepage":"","language":"JavaScript","has_issues":true,"has_wiki":null,"has_pages":null,"mirror_url":null,"source_name":null,"license":null,"status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/markbosire.png","metadata":{"files":{"readme":"README.md","changelog":null,"contributing":null,"funding":null,"license":null,"code_of_conduct":null,"threat_model":null,"audit":null,"citation":null,"codeowners":null,"security":null,"support":null,"governance":null,"roadmap":null,"authors":null,"dei":null,"publiccode":null,"codemeta":null,"zenodo":null}},"created_at":"2025-05-29T22:56:59.000Z","updated_at":"2025-05-29T23:05:44.000Z","dependencies_parsed_at":"2025-05-30T00:29:06.514Z","dependency_job_id":null,"html_url":"https://github.com/markbosire/google_sheets_db_gdrive_storage","commit_stats":null,"previous_names":["markbosire/google_sheets_db_gdrive_storage"],"tags_count":0,"template":false,"template_full_name":null,"purl":"pkg:github/markbosire/google_sheets_db_gdrive_storage","repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/markbosire%2Fgoogle_sheets_db_gdrive_storage","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/markbosire%2Fgoogle_sheets_db_gdrive_storage/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/markbosire%2Fgoogle_sheets_db_gdrive_storage/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/markbosire%2Fgoogle_sheets_db_gdrive_storage/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/markbosire","download_url":"https://codeload.github.com/markbosire/google_sheets_db_gdrive_storage/tar.gz/refs/heads/main","sbom_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/markbosire%2Fgoogle_sheets_db_gdrive_storage/sbom","scorecard":null,"host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":286080680,"owners_count":31803233,"icon_url":"https://github.com/github.png","version":null,"created_at":"2022-05-30T11:31:42.601Z","updated_at":"2026-04-14T11:13:53.975Z","status":"ssl_error","status_checked_at":"2026-04-14T11:13:53.299Z","response_time":153,"last_error":"SSL_connect returned=1 errno=0 peeraddr=140.82.121.5:443 state=error: unexpected eof while reading","robots_txt_status":"success","robots_txt_updated_at":"2025-07-24T06:49:26.215Z","robots_txt_url":"https://github.com/robots.txt","online":false,"can_crawl_api":true,"host_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub","repositories_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories","repository_names_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repository_names","owners_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners"}},"keywords":["api","database","google-drive-api","google-sheets-api","nodejs","postman"],"created_at":"2025-06-15T01:00:27.893Z","updated_at":"2026-04-14T15:32:04.654Z","avatar_url":"https://github.com/markbosire.png","language":"JavaScript","funding_links":[],"categories":[],"sub_categories":[],"readme":"# Google Sheets + Drive Backend Setup\n\nThis guide explains how to set up a Node.js backend using Google Sheets as a database and Google Drive for file storage.\n\n## Table of Contents\n\n1. [Prerequisites](#prerequisites)\n2. [Google Cloud Setup](#1-google-cloud-setup)\n   - [Create a Google Cloud Project](#create-a-google-cloud-project)\n   - [Create Service Account](#create-service-account)\n   - [Share Resources with Service Account](#share-resources-with-service-account)\n3. [Prepare Google Sheet](#2-prepare-google-sheet)\n   - [Sheet 1: \"Todos\"](#sheet-1-todos)\n   - [Sheet 2: \"Users\"](#sheet-2-users)\n4. [Node.js Server Setup](#3-nodejs-server-setup)\n5. [Environment Configuration](#4-environment-configuration)\n6. [Starting the Server](#5-starting-the-server)\n7. [API Endpoints](#6-api-endpoints)\n   - [Authentication Endpoints](#authentication-endpoints)\n   - [Default Admin User](#default-admin-user)\n   - [Todos](#todos)\n8. [User Roles and Permissions](#7-user-roles-and-permissions)\n   - [User Roles](#user-roles)\n   - [Permission System](#permission-system)\n9. [Testing with Postman](#8-testing-with-postman)\n   - [Testing Steps](#testing-steps)\n   - [Setting Variables in Postman](#setting-variables-in-postman)\n10. [Important Notes](#9-important-notes)\n    - [Security Considerations](#security-considerations)\n    - [User Isolation](#user-isolation)\n    - [File Management](#file-management)\n    - [Error Handling](#error-handling)\n\n---\n\n## Prerequisites\n\n- Node.js (v14+)\n- Google account\n- Basic knowledge of Google Sheets and Drive\n\n## 1. Google Cloud Setup\n\n### Create a Google Cloud Project\n\n1. Go to [Google Cloud Console](https://console.cloud.google.com/)\n2. Create a new project\n3. Enable Google Sheets API and Google Drive API:\n   - Navigation Menu \u003e APIs \u0026 Services \u003e Library\n   - Search for \"Google Sheets API\" and enable it\n   - Search for \"Google Drive API\" and enable it\n\n### Create Service Account\n\n1. Navigation Menu \u003e IAM \u0026 Admin \u003e Service Accounts\n2. Click \"Create Service Account\"\n3. Fill in details and create\n4. Under \"Keys\" tab, click \"Add Key\" \u003e \"Create new key\" \u003e JSON\n5. Save the JSON file securely (this is your service account credentials rename it to something memorable it will be used later)\n\n### Share Resources with Service Account\n\n1. **Google Sheet**:\n   - Create a new Google Sheet\n   - Share it with your service account email as \"Editor\" (the service account email is located in the JSON file under the client_email field)\n   - Note the Sheet ID from the URL (`https://docs.google.com/spreadsheets/d/[SHEET_ID]/edit`)\n\n2. **Google Drive Folder**:\n   - Create a new folder in Drive\n   - Share it with your service account email as \"Editor\"\n   - Note the Folder ID from the URL (`https://drive.google.com/drive/folders/[FOLDER_ID]`)\n\n## 2. Prepare Google Sheet\n\nCreate two sheets in your Google Spreadsheet:\n\n### Sheet 1: \"Todos\"\nCreate a sheet named \"Todos\" with these headers in the first row:\n```\nid, title, description, imageId, imageLink, createdAt, updatedAt, completed, userId\n```\n\n### Sheet 2: \"Users\"\nCreate a sheet named \"Users\" with these headers in the first row:\n```\nid, username, password, role, createdAt\n```\n\nMake sure there are no whitespaces in the headers.\n\n## 3. Node.js Server Setup\n\n1. Clone this repository\n2. Install the required dependencies:\n\n```bash\nnpm install express googleapis bcryptjs jsonwebtoken multer dotenv cors uuid\n```\n\n## 4. Environment Configuration\n\nCreate a `.env` file in the root directory with the following variables:\n\n```env\n# Server Configuration\nPORT=3000\n\n# Google Sheets Configuration\nSPREADSHEET_ID=your_sheet_id\nDRIVE_FOLDER_ID=your_folder_id\nGOOGLE_APPLICATION_CREDENTIALS=./path/to/your/service-account.json\n\n# JWT Authentication\nJWT_SECRET=your_jwt_secret_key\nJWT_EXPIRES_IN=1h\n```\nYou can generate a jwt secret here:\n[https://jwtsecret.com/generate](https://jwtsecret.com/generate)\n\n## 5. Starting the Server\n\n```bash\nnode server.js\n```\n\nThe server will automatically:\n- Initialize Google APIs\n- Create a default admin user (username: `admin`, password: `admin123`)\n\n---\n\n## 6. API Endpoints\n\n## Authentication Endpoints\n\n**POST** `/api/auth/register`\n\n- Register a new user\n- **Body:**\n  ```json\n  {\n    \"username\": \"newuser\",\n    \"password\": \"password123\"\n  }\n  ```\n- **Success Response (201):**\n  ```json\n  {\n    \"id\": \"user-uuid\",\n    \"username\": \"newuser\",\n    \"role\": \"user\"\n  }\n  ```\n- **Error Responses:**\n  - 400: Username already exists or validation error\n  - 500: Server error\n\n**POST** `/api/auth/login`\n\n- Login existing user\n- **Body:**\n  ```json\n  {\n    \"username\": \"newuser\",\n    \"password\": \"password123\"\n  }\n  ```\n- **Success Response:**\n  ```json\n  {\n    \"token\": \"jwt.token.here\",\n    \"user\": {\n      \"id\": \"user-uuid\",\n      \"username\": \"newuser\",\n      \"role\": \"user\"\n    }\n  }\n  ```\n- **Error Responses:**\n  - 401: Invalid credentials\n  - 400: Validation error\n\n### Default Admin User\nThe system automatically creates an admin user on first startup:\n- **Username:** `admin`\n- **Password:** `admin123`\n- **Role:** `admin`\n\n\u003e **Important:** Change the admin password after first login for security.\n\n### Todos\n\n\u003e **Note:** All todo endpoints require JWT token in `Authorization` header as:  \n\u003e `Authorization: Bearer \u003cyour_token\u003e`\n\n#### **GET** `/api/todos`\n- List all todos (admin only - sees all todos from all users)\n- **Success Response:**\n  ```json\n  [\n    {\n      \"id\": \"todo-123\",\n      \"title\": \"Task 1\",\n      \"description\": \"Description\",\n      \"imageId\": \"drive-file-id\",\n      \"imageLink\": \"https://drive.google.com/uc?id=drive-file-id\",\n      \"createdAt\": \"2023-01-01T00:00:00Z\",\n      \"updatedAt\": \"2023-01-01T00:00:00Z\",\n      \"completed\": false,\n      \"userId\": \"user-456\"\n    }\n  ]\n  ```\n\n#### **GET** `/api/todos/user`\n- List todos for the current authenticated user only\n- **Success Response:**\n  ```json\n  [\n    {\n      \"id\": \"todo-123\",\n      \"title\": \"My Task\",\n      \"description\": \"My Description\",\n      \"imageId\": \"drive-file-id\",\n      \"imageLink\": \"https://drive.google.com/uc?id=drive-file-id\",\n      \"createdAt\": \"2023-01-01T00:00:00Z\",\n      \"updatedAt\": \"2023-01-01T00:00:00Z\",\n      \"completed\": false,\n      \"userId\": \"current-user-id\"\n    }\n  ]\n  ```\n\n#### **POST** `/api/todos`\n- Create new todo (automatically assigned to current user)\n- **Headers:**\n  - `Content-Type: multipart/form-data`\n- **Body:**\n  - Form-data fields:\n    - `title` (string, required)\n    - `description` (string, optional)\n    - `completed` (boolean, optional, default false)\n    - `image` (file, optional)\n- **Success Response:**\n  ```json\n  {\n    \"id\": \"todo-123\",\n    \"title\": \"Task 1\",\n    \"description\": \"Description\",\n    \"imageId\": \"drive-file-id\",\n    \"imageLink\": \"https://drive.google.com/uc?id=drive-file-id\",\n    \"createdAt\": \"2023-01-01T00:00:00Z\",\n    \"updatedAt\": \"2023-01-01T00:00:00Z\",\n    \"completed\": false,\n    \"userId\": \"current-user-id\"\n  }\n  ```\n\n#### **GET** `/api/todos/:id`\n- Get single todo by ID (users can only access their own todos)\n- **Success Response:**\n  ```json\n  {\n    \"id\": \"todo-123\",\n    \"title\": \"Task 1\",\n    \"description\": \"Description\",\n    \"imageId\": \"drive-file-id\",\n    \"imageLink\": \"https://drive.google.com/uc?id=drive-file-id\",\n    \"createdAt\": \"2023-01-01T00:00:00Z\",\n    \"updatedAt\": \"2023-01-01T00:00:00Z\",\n    \"completed\": false,\n    \"userId\": \"current-user-id\"\n  }\n  ```\n- **Error Response (404):**\n  ```json\n  {\n    \"message\": \"Todo not found\"\n  }\n  ```\n\n#### **PUT** `/api/todos/:id`\n- Update todo (users can only update their own todos)\n- **Headers:**\n  - `Content-Type: multipart/form-data`\n- **Body:**\n  - Form-data fields:\n    - `title` (string, optional)\n    - `description` (string, optional)\n    - `completed` (boolean, optional)\n    - `image` (file, optional - replaces existing image if any)\n- **Success Response:**\n  ```json\n  {\n    \"id\": \"todo-123\",\n    \"title\": \"Updated Task\",\n    \"description\": \"Updated Description\",\n    \"imageId\": \"new-drive-file-id\",\n    \"imageLink\": \"https://drive.google.com/uc?id=new-drive-file-id\",\n    \"createdAt\": \"2023-01-01T00:00:00Z\",\n    \"updatedAt\": \"2023-01-02T00:00:00Z\",\n    \"completed\": true,\n    \"userId\": \"current-user-id\"\n  }\n  ```\n\n#### **DELETE** `/api/todos/:id`\n- Delete todo (users can only delete their own todos, also deletes associated image)\n- **Success Response:**\n  ```json\n  {\n    \"id\": \"todo-123\",\n    \"message\": \"Todo deleted successfully\"\n  }\n  ```\n\n---\n\n## 7. User Roles and Permissions\n\n### User Roles\n- **admin**: Can view all todos from all users via `/api/todos`\n- **user**: Can only access their own todos via `/api/todos/user`\n\n### Permission System\n- Users can only create, read, update, and delete their own todos\n- Admins have full access to all todos\n- Authentication is required for all todo operations\n\n---\n\n## 8. Testing with Postman\n\nImport this collection into Postman:\n\n```json\n{\n  \"info\": {\n    \"_postman_id\": \"a1b2c3d4-e5f6-7890\",\n    \"name\": \"Todo API with Authentication\",\n    \"schema\": \"https://schema.getpostman.com/json/collection/v2.1.0/collection.json\"\n  },\n  \"item\": [\n    {\n      \"name\": \"Register User\",\n      \"request\": {\n        \"method\": \"POST\",\n        \"header\": [\n          {\n            \"key\": \"Content-Type\",\n            \"value\": \"application/json\"\n          }\n        ],\n        \"body\": {\n          \"mode\": \"raw\",\n          \"raw\": \"{\\n    \\\"username\\\": \\\"testuser\\\",\\n    \\\"password\\\": \\\"password123\\\"\\n}\",\n          \"options\": {\n            \"raw\": {\n              \"language\": \"json\"\n            }\n          }\n        },\n        \"url\": {\n          \"raw\": \"http://localhost:3000/api/auth/register\",\n          \"protocol\": \"http\",\n          \"host\": [\"localhost\"],\n          \"port\": \"3000\",\n          \"path\": [\"api\",\"auth\",\"register\"]\n        }\n      }\n    },\n    {\n      \"name\": \"Login User\",\n      \"request\": {\n        \"method\": \"POST\",\n        \"header\": [\n          {\n            \"key\": \"Content-Type\",\n            \"value\": \"application/json\"\n          }\n        ],\n        \"body\": {\n          \"mode\": \"raw\",\n          \"raw\": \"{\\n    \\\"username\\\": \\\"testuser\\\",\\n    \\\"password\\\": \\\"password123\\\"\\n}\",\n          \"options\": {\n            \"raw\": {\n              \"language\": \"json\"\n            }\n          }\n        },\n        \"url\": {\n          \"raw\": \"http://localhost:3000/api/auth/login\",\n          \"protocol\": \"http\",\n          \"host\": [\"localhost\"],\n          \"port\": \"3000\",\n          \"path\": [\"api\",\"auth\",\"login\"]\n        }\n      }\n    },\n    {\n      \"name\": \"Login Admin\",\n      \"request\": {\n        \"method\": \"POST\",\n        \"header\": [\n          {\n            \"key\": \"Content-Type\",\n            \"value\": \"application/json\"\n          }\n        ],\n        \"body\": {\n          \"mode\": \"raw\",\n          \"raw\": \"{\\n    \\\"username\\\": \\\"admin\\\",\\n    \\\"password\\\": \\\"admin123\\\"\\n}\",\n          \"options\": {\n            \"raw\": {\n              \"language\": \"json\"\n            }\n          }\n        },\n        \"url\": {\n          \"raw\": \"http://localhost:3000/api/auth/login\",\n          \"protocol\": \"http\",\n          \"host\": [\"localhost\"],\n          \"port\": \"3000\",\n          \"path\": [\"api\",\"auth\",\"login\"]\n        }\n      }\n    },\n    {\n      \"name\": \"Get User's Todos\",\n      \"request\": {\n        \"method\": \"GET\",\n        \"header\": [\n          {\n            \"key\": \"Authorization\",\n            \"value\": \"Bearer {{token}}\"\n          }\n        ],\n        \"url\": {\n          \"raw\": \"http://localhost:3000/api/todos/user\",\n          \"protocol\": \"http\",\n          \"host\": [\"localhost\"],\n          \"port\": \"3000\",\n          \"path\": [\"api\",\"todos\",\"user\"]\n        }\n      }\n    },\n    {\n      \"name\": \"Get All Todos (Admin Only)\",\n      \"request\": {\n        \"method\": \"GET\",\n        \"header\": [\n          {\n            \"key\": \"Authorization\",\n            \"value\": \"Bearer {{admin_token}}\"\n          }\n        ],\n        \"url\": {\n          \"raw\": \"http://localhost:3000/api/todos\",\n          \"protocol\": \"http\",\n          \"host\": [\"localhost\"],\n          \"port\": \"3000\",\n          \"path\": [\"api\",\"todos\"]\n        }\n      }\n    },\n    {\n      \"name\": \"Get Single Todo\",\n      \"request\": {\n        \"method\": \"GET\",\n        \"header\": [\n          {\n            \"key\": \"Authorization\",\n            \"value\": \"Bearer {{token}}\"\n          }\n        ],\n        \"url\": {\n          \"raw\": \"http://localhost:3000/api/todos/{{todo_id}}\",\n          \"protocol\": \"http\",\n          \"host\": [\"localhost\"],\n          \"port\": \"3000\",\n          \"path\": [\"api\",\"todos\",\"{{todo_id}}\"]\n        }\n      }\n    },\n    {\n      \"name\": \"Create Todo (with image)\",\n      \"request\": {\n        \"method\": \"POST\",\n        \"header\": [\n          {\n            \"key\": \"Authorization\",\n            \"value\": \"Bearer {{token}}\"\n          }\n        ],\n        \"body\": {\n          \"mode\": \"formdata\",\n          \"formdata\": [\n            {\n              \"key\": \"title\",\n              \"value\": \"Task with image\",\n              \"type\": \"text\"\n            },\n            {\n              \"key\": \"description\",\n              \"value\": \"Description here\",\n              \"type\": \"text\"\n            },\n            {\n              \"key\": \"completed\",\n              \"value\": \"false\",\n              \"type\": \"text\"\n            },\n            {\n              \"key\": \"image\",\n              \"type\": \"file\",\n              \"src\": \"/path/to/image.jpg\"\n            }\n          ]\n        },\n        \"url\": {\n          \"raw\": \"http://localhost:3000/api/todos\",\n          \"protocol\": \"http\",\n          \"host\": [\"localhost\"],\n          \"port\": \"3000\",\n          \"path\": [\"api\",\"todos\"]\n        }\n      }\n    },\n    {\n      \"name\": \"Create Todo (without image)\",\n      \"request\": {\n        \"method\": \"POST\",\n        \"header\": [\n          {\n            \"key\": \"Authorization\",\n            \"value\": \"Bearer {{token}}\"\n          }\n        ],\n        \"body\": {\n          \"mode\": \"formdata\",\n          \"formdata\": [\n            {\n              \"key\": \"title\",\n              \"value\": \"Simple task\",\n              \"type\": \"text\"\n            },\n            {\n              \"key\": \"description\",\n              \"value\": \"Just a simple todo\",\n              \"type\": \"text\"\n            },\n            {\n              \"key\": \"completed\",\n              \"value\": \"false\",\n              \"type\": \"text\"\n            }\n          ]\n        },\n        \"url\": {\n          \"raw\": \"http://localhost:3000/api/todos\",\n          \"protocol\": \"http\",\n          \"host\": [\"localhost\"],\n          \"port\": \"3000\",\n          \"path\": [\"api\",\"todos\"]\n        }\n      }\n    },\n    {\n      \"name\": \"Update Todo (with new image)\",\n      \"request\": {\n        \"method\": \"PUT\",\n        \"header\": [\n          {\n            \"key\": \"Authorization\",\n            \"value\": \"Bearer {{token}}\"\n          }\n        ],\n        \"body\": {\n          \"mode\": \"formdata\",\n          \"formdata\": [\n            {\n              \"key\": \"title\",\n              \"value\": \"Updated Task\",\n              \"type\": \"text\"\n            },\n            {\n              \"key\": \"description\",\n              \"value\": \"Updated description\",\n              \"type\": \"text\"\n            },\n            {\n              \"key\": \"completed\",\n              \"value\": \"true\",\n              \"type\": \"text\"\n            },\n            {\n              \"key\": \"image\",\n              \"type\": \"file\",\n              \"src\": \"/path/to/new-image.jpg\"\n            }\n          ]\n        },\n        \"url\": {\n          \"raw\": \"http://localhost:3000/api/todos/{{todo_id}}\",\n          \"protocol\": \"http\",\n          \"host\": [\"localhost\"],\n          \"port\": \"3000\",\n          \"path\": [\"api\",\"todos\",\"{{todo_id}}\"]\n        }\n      }\n    },\n    {\n      \"name\": \"Delete Todo\",\n      \"request\": {\n        \"method\": \"DELETE\",\n        \"header\": [\n          {\n            \"key\": \"Authorization\",\n            \"value\": \"Bearer {{token}}\"\n          }\n        ],\n        \"url\": {\n          \"raw\": \"http://localhost:3000/api/todos/{{todo_id}}\",\n          \"protocol\": \"http\",\n          \"host\": [\"localhost\"],\n          \"port\": \"3000\",\n          \"path\": [\"api\",\"todos\",\"{{todo_id}}\"]\n        }\n      }\n    }\n  ],\n  \"variable\": [\n    {\n      \"key\": \"token\",\n      \"value\": \"\",\n      \"description\": \"User JWT token\"\n    },\n    {\n      \"key\": \"admin_token\",\n      \"value\": \"\",\n      \"description\": \"Admin JWT token\"\n    },\n    {\n      \"key\": \"todo_id\",\n      \"value\": \"\",\n      \"description\": \"Todo ID for testing\"\n    }\n  ]\n}\n```\n\n### Testing Steps \n\n1. **Register** a new user account\n2. **Login** as the user to get your JWT token\n3. **Login** as admin (username: `admin`, password: `admin123`) to get admin token\n4. **Create** a new todo as the user (note the ID in the response)\n5. **Get User's Todos** to see only your todos\n6. **Get All Todos** as admin to see all todos from all users\n7. **Get Single Todo** using the ID from step 4\n8. **Update** the todo (try with/without new image)\n9. **Delete** the todo when done\n\n### Setting Variables in Postman\n- After login, copy the JWT token from the response\n- Set it as the `token` variable in your Postman environment\n- For admin testing, set the admin token as `admin_token` variable\n- Set `todo_id` variable with actual todo IDs from your responses\n\n---\n\n## 9. Important Notes\n\n### Security Considerations\n- Change the default admin password immediately after setup\n- Use strong JWT secrets in production\n- Consider implementing rate limiting for authentication endpoints\n- Validate file uploads (size, type) in production\n\n### User Isolation\n- Each user can only access their own todos\n- User IDs are automatically assigned from JWT tokens\n- Admin users can view all todos for administrative purposes\n\n### File Management\n- Images are stored in Google Drive\n- Old images are automatically deleted when replaced or when todos are deleted\n- Image links are public (anyone with the link can view)\n\n### Error Handling\n- All endpoints return appropriate HTTP status codes\n- Detailed error messages are provided for debugging\n- Authentication failures return 401 status\n- Authorization failures return 403 status\n","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fmarkbosire%2Fgoogle_sheets_db_gdrive_storage","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Fmarkbosire%2Fgoogle_sheets_db_gdrive_storage","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fmarkbosire%2Fgoogle_sheets_db_gdrive_storage/lists"}