30-Day Build Challenge
Sharing sensitive information (API keys, passwords, configuration files) via email, Discord, or WhatsApp is insecure. Third-party services like Pastebin or WeTransfer store your data on their servers, creating a privacy risk.
Build a lightweight, containerized Python web application that allows you to:
- Upload a file or text snippet.
- Generate a unique, secure link.
- Permanently delete the data from the server immediately after it is viewed once (or after a short timer expires).
Goal: Build a secure, one-time file sharing application within 30 days. Timeline: December 24, 2025 β January 24, 2026 Tech Stack:
- Backend: Python / Flask
- Storage: Dockerized Redis (Ephemeral storage)
- Containerization: Docker
- UI: Bootstrap
- CI/CD: GitHub Actions
Status: β Completed
- Project Initiation: Defined the core problem (secure, temporary file sharing) and the solution (OneTimeShare).
- Public Announcement: Launched the #OneTimeShare30 challenge on social media along with the GitHub repository.
- Tech Stack Confirmation: Finalized the decision to use Flask for the backend and Redis for ephemeral key-value storage (essential for the self-destruct feature).
- Repository Setup: Initialized
onetimesharegit repository and defined the project structure. - Planning: Outlined the high-level 30-day roadmap.
Status: β Completed
- Flask Setup: Create the basic Flask application factory structure (
app/__init__.py,run.py). - Docker Initialization: specific
Dockerfilefor the Flask app and adocker-compose.ymlthat includes the Redis service. - Basic Upload Route: Implement a minimal
/uploadendpoint that can accept a file via POST request. - Redis Connection: Verify connection between Flask and the Redis container.
- Health Check: Ensure the application runs locally via
docker-compose up.
Status: β Completed
- Secure File Saving: Files saved to disk with UUID-based filenames.
- Extension Validation: Only allow pdf, txt, png, jpg, jpeg, gif, env.
- File Size Validation: Max 20MB limit via MAX_FILE_SIZE config.
- Redis Metadata Storage: Store filename, content_type, upload_time using hset().
- TTL Expiration: Auto-delete after 5 hours via REDIS_TTL.
- API Endpoints:
/upload(POST),/info/<token>(GET),/list-files(GET). - Configuration: config.py with environment variable support.
import moduleβfrom module import Classos.makedirs()is NOT optional before file writesdecode_responses=Truerequired for Redis string responses
Status: β Completed
- Service Layer Pattern: Created
services/redis_service.pywith RedisService class. - LinkGenerator: Created
utils/link_generator.pyfor URL generation. - File Validation Utility: Created
utils/get_uuid.pywith custom exceptions. - Routes Refactored: Updated routes to use services instead of direct Redis calls.
- TTL Expiration: Implemented via
expire()with configurable timeout. - All Endpoints Working:
/upload,/info/<token>,/download/<token>,/list-files.
- Service Layer Pattern: Routes handle HTTP, services handle business logic
- Python β Java: No
extendsβ use composition __init__has TWO underscores on each side- Import paths:
from app.utils.xnotfrom utils.x - Custom exceptions make debugging easier
Status: β Completed
- Design System: "Industrial Urgency" theme (dark charcoal + Safety Orange).
- AI Mockups: Generated 4 mockups after Figma attempt failed.
- Base Template:
base.htmlwith JetBrains Mono, industrial navbar. - Upload Page:
index.htmlwith containment card, drag-drop, progress bar. - Download Page:
download.htmlshowing file metadata. - CSS: 540 lines implementing design system.
- JavaScript: Drag-drop, file validation, fetch upload, copy-to-clipboard.
- Route Updated:
/download/<token>now renders template with metadata.
{% include %}β{% extends %}(Jinja templating)- Event propagation: label inside div = double triggers
- AI is leverage, not replacement
- Frontend is a skill, not "easy mode"
Status: β Completed
- Download Route: Created
/d/<token>withsend_from_directory(). - Info Page Route: Created
/download/<token>to render dl.html. - Security: Path traversal protection via send_from_directory.
- Frontend: Download button in dl.html + download.js handler.
- Original Filename: Uses
download_nameparameter. - Full Flow: Upload β Link β Info Page β Download working.
- Restart Flask after code changes!
- Two-route pattern: info page + file serve
- Save Redis results:
metadata = redis_service.get(...)before using
Status: β Completed
- Atomic Deletion: Redis WATCH/MULTI/EXEC for race-safe deletion.
- File Deletion: Remove file from disk after successful download.
- Orphan Cleanup: Bidirectional sync (files β metadata).
- Startup Cleanup: Automatic cleanup on app restart.
- Custom 404: Error page for expired/deleted files.
- Download Route Updated:
/d/<token>now deletes after serving.
- directory=Config.UPLOAD_FOLDER
+ directory_path = current_app.config['UPLOAD_FOLDER']Found through testing β files were not deleting because of wrong config reference!
Config.UPLOAD_FOLDERβcurrent_app.config['UPLOAD_FOLDER']pipeline.unwatch()required after WATCH if no transaction- Test like a user, not a developer
- 29 mistakes made, 29 fixed β iteration works
Focus: Recap & Refactor
- Day 7 (Dec 31): Recap & Refactor
- Code review of the week's work.
- Manual testing of Upload β Link β Download β Delete flow.
- Write
v0.1documentation.
Focus: One-time View Enforcement & Password Protection
- Day 8 (Jan 1): Refine "One-Time" logic to strictly enforce single use (atomic operations in Redis).
- Day 9 (Jan 2): Add optional Password Protection field to the Upload UI.
- Day 10 (Jan 3): Implement backend password hashing and verification for protected files.
- Day 11 (Jan 4): Create the "Enter Password" intermediate page for protected links.
- Day 12 (Jan 5): Data validation (file size limits, allowed extensions).
- Day 13 (Jan 6): Improve Error Handling (404 pages for expired links, 500 pages).
- Day 14 (Jan 7): Week 2 Testing & Bug Fixes.
Focus: Encryption & Rate Limiting
- Day 15 (Jan 8): Research and select File Encryption method (likely symmetrical encryption like Fernet/AES).
- Day 16 (Jan 9): Implement Encryption-at-rest (encrypt file before saving to disk).
- Day 17 (Jan 10): Implement Decryption-on-fly (decrypt stream when user downloads).
- Day 18 (Jan 11): Add Flask-Limiter to prevent abuse (Rate limiting on upload/download endpoints).
- Day 19 (Jan 12): UI Polish (Animations, Copy-to-clipboard buttons, better mobile responsiveness).
- Day 20 (Jan 13): Security Audit (Dependency vulnerability scan, ensuring no secrets in code).
- Day 21 (Jan 14): Week 3 Wrap-up & Performance testing.
Focus: Production Ready & CI/CD
- Day 22 (Jan 15): Docker Production Optimization (Gunicorn vs Dev Server).
- Day 23 (Jan 16): Set up GitHub Actions for automated testing (Pytest).
- Day 24 (Jan 17): Write comprehensive Unit Tests for core logic.
- Day 25 (Jan 18): Deployment Config (Nginx reverse proxy setup).
- Day 26 (Jan 19): Deployment Dry-Run (Test on a VPS or cloud provider).
- Day 27 (Jan 20): Finalize
README.mdand documentation API. - Day 28 (Jan 21): Post-Launch Polish (Feedback widgets, analytics if privacy-compliant).
- Day 29 (Jan 22): Prepare Launch content (Screenshots, Demo Video).
- Day 30 (Jan 24): PUBLIC LAUNCH - Release v1.0.0.
Copy this into your daily log to track progress and blockers.
### Evening Review Questions (Answer daily):
- **What surprised me today?** (Technical or feedback)
-
- **What's blocking progress?**
-
- **What feedback should I act on immediately?**
-
- **What can I simplify tomorrow?**
-
- **What should I learn tonight to unblock tomorrow?**
-
### Morning Planning Questions:
- **Based on yesterday, what needs to change today?**
-
- **What's the most valuable single thing I can complete?**
-
- **Who can help me with today's challenges?**
-
- **What's the simplest implementation that works?**
-
- **How can I share today's progress compellingly?**
-