{"id":15655606,"url":"https://github.com/loftwah/linkarooie","last_synced_at":"2025-03-28T23:31:51.749Z","repository":{"id":192215401,"uuid":"685490503","full_name":"loftwah/linkarooie","owner":"loftwah","description":"Simplify your online presence with a single link.","archived":false,"fork":false,"pushed_at":"2024-10-25T10:49:47.000Z","size":41180,"stargazers_count":26,"open_issues_count":26,"forks_count":5,"subscribers_count":2,"default_branch":"main","last_synced_at":"2024-10-26T15:49:50.546Z","etag":null,"topics":["biodrop","hacktoberfest","link-in-bio","link-management","linkfree","linktree","linktree-alternative","online-presence","personal-profile","ruby","ruby-on-rails"],"latest_commit_sha":null,"homepage":"https://linkarooie.com","language":"Ruby","has_issues":true,"has_wiki":null,"has_pages":null,"mirror_url":null,"source_name":null,"license":"mit","status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/loftwah.png","metadata":{"files":{"readme":"README.md","changelog":null,"contributing":"CONTRIBUTING.md","funding":null,"license":"LICENSE","code_of_conduct":"CODE_OF_CONDUCT.md","threat_model":null,"audit":null,"citation":null,"codeowners":null,"security":"SECURITY.md","support":null,"governance":null,"roadmap":null,"authors":null,"dei":null,"publiccode":null,"codemeta":null},"funding":{"github":"loftwah"}},"created_at":"2023-08-31T10:57:01.000Z","updated_at":"2024-10-25T10:49:50.000Z","dependencies_parsed_at":"2024-10-18T05:34:02.259Z","dependency_job_id":null,"html_url":"https://github.com/loftwah/linkarooie","commit_stats":{"total_commits":160,"total_committers":2,"mean_commits":80.0,"dds":"0.012499999999999956","last_synced_commit":"3490fabc918d029c6b13987b2143b073b6500ee5"},"previous_names":["loftwah/custom-dashboard","loftwah/linkaroo"],"tags_count":74,"template":false,"template_full_name":null,"repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/loftwah%2Flinkarooie","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/loftwah%2Flinkarooie/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/loftwah%2Flinkarooie/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/loftwah%2Flinkarooie/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/loftwah","download_url":"https://codeload.github.com/loftwah/linkarooie/tar.gz/refs/heads/main","host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":222431501,"owners_count":16983386,"icon_url":"https://github.com/github.png","version":null,"created_at":"2022-05-30T11:31:42.601Z","updated_at":"2022-07-04T15:15:14.044Z","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":["biodrop","hacktoberfest","link-in-bio","link-management","linkfree","linktree","linktree-alternative","online-presence","personal-profile","ruby","ruby-on-rails"],"created_at":"2024-10-03T13:00:01.309Z","updated_at":"2024-10-31T15:02:11.326Z","avatar_url":"https://github.com/loftwah.png","language":"Ruby","readme":"# Linkarooie\n\n[![Build Status](https://github.com/loftwah/linkarooie/actions/workflows/ci.yml/badge.svg)](https://github.com/loftwah/linkarooie/actions) [![Docker Image Available](https://img.shields.io/badge/Docker%20image-available-blue?logo=docker)](https://github.com/users/loftwah/packages/container/package/linkarooie)\n\n![Judgemental Linkarooie](https://github.com/user-attachments/assets/65378fe8-d0ae-4682-9f15-64007b5b0818)\n\nLinkarooie is a robust, open-source alternative to Linktree, built with Ruby on Rails. It provides a centralized platform for managing and sharing your important links, achievements, and online presence. Created as a replacement for the archived BioDrop (LinkFree) project, Linkarooie offers a feature-rich, customizable solution designed for easy deployment and management.\n\n## Table of Contents\n\n1. [Features](#features)\n2. [Tech Stack](#tech-stack)\n3. [Getting Started](#getting-started)\n   - [Prerequisites](#prerequisites)\n   - [Local Development Setup](#local-development-setup)\n   - [Creating a New User](#creating-a-new-user)\n4. [Docker Deployment](#docker-deployment)\n5. [DigitalOcean Deployment](#digitalocean-deployment)\n6. [Configuration](#configuration)\n   - [Environment Variables](#environment-variables)\n   - [Database Configuration](#database-configuration)\n7. [Backup and Restore Process](#backup-and-restore-process)\n8. [Geolocation](#geolocation)\n9. [Customization](#customization)\n10. [Testing](#testing)\n11. [CI/CD](#cicd)\n12. [Project Structure](#project-structure)\n13. [Key Components](#key-components)\n14. [Gather Script](#gather-script)\n15. [Contributing](#contributing)\n16. [License](#license)\n17. [Support](#support)\n18. [Acknowledgements](#acknowledgements)\n\n## Video Demo\n\n[Video Demo](https://github.com/user-attachments/assets/d5dc1636-2a7f-4cbf-878c-516ebda6c47e)\n\n## Features\n\n- **Custom Links Management:**\n\n  - Add, edit, and delete links with titles, URLs, descriptions, and custom icons\n  - Toggle link visibility\n  - Pin important links to the top of your profile\n  - Organize links with custom positioning\n\n- **User Profiles:**\n\n  - Customizable profiles with avatars, banners, full names, usernames, and descriptions\n  - Tagging system for categorizing profiles\n\n- **User Achievements:**\n\n  - Create and showcase personal or professional accomplishments\n  - Include achievement titles, dates, descriptions, icons, and URLs\n\n- **Analytics:**\n\n  - Track page views, link clicks, and unique visitors\n  - View daily metrics for user engagement\n  - Geolocation tracking for visitor insights (currently mandatory)\n\n- **Open Graph Image Generation:**\n\n  - Automatic creation of social media preview images for improved sharing\n\n- **Responsive Design:**\n\n  - Ensures optimal user experience across all devices\n\n- **Background Job Processing:**\n\n  - Utilizes Sidekiq for efficient handling of background tasks\n\n- **Asset Management:**\n\n  - Implements Vite for modern, efficient frontend asset handling\n\n- **Automated Backups:**\n  - Daily backups to DigitalOcean Spaces with easy restoration process\n\n## Tech Stack\n\n- **Backend:**\n\n  - Ruby 3.3.0 (local development)\n  - Ruby 3.3.4 (production Docker image)\n  - Rails 7.1.3\n  - SQLite3 (development)\n  - PostgreSQL/MySQL (recommended for production)\n  - Sidekiq for background job processing\n  - Redis for Sidekiq and caching\n\n- **Frontend:**\n\n  - Vite for asset compilation and management\n  - Tailwind CSS for styling\n  - Stimulus.js for JavaScript sprinkles\n  - Chartkick for chart generation\n\n- **Testing:**\n\n  - RSpec for unit and integration tests\n  - Factory Bot for test data generation\n  - Shoulda Matchers for additional RSpec matchers\n\n- **Deployment \u0026 Infrastructure:**\n\n  - Docker and Docker Compose for containerization\n  - GitHub Actions for CI/CD\n  - Terraform for infrastructure as code\n  - DigitalOcean for hosting (Droplets and Spaces)\n\n- **Additional Libraries:**\n  - Devise for authentication\n  - Geocoder for geolocation services\n  - AWS SDK for S3 compatible storage interactions\n  - Font Awesome for icons\n\n## Getting Started\n\n### Prerequisites\n\n- Ruby 3.3.0 or higher\n- Rails 7.1.3 or higher\n- SQLite3\n- Node.js (v20 or higher) and npm\n- Docker and Docker Compose (for containerized deployment)\n- Git\n\n### Local Development Setup\n\n1. Clone the repository:\n\n   ```bash\n   git clone https://github.com/loftwah/linkarooie.git\n   cd linkarooie\n   ```\n\n2. Install Ruby dependencies:\n\n   ```bash\n   bundle install\n   ```\n\n3. Install JavaScript dependencies:\n\n   ```bash\n   npm install\n   ```\n\n4. Set up the database:\n\n   ```bash\n   rails db:create db:migrate db:seed\n   ```\n\n5. Start the development servers:\n\n   ```bash\n   bin/dev\n   ```\n\n   This command starts the Rails server, Vite dev server, and Tailwind CSS watcher.\n\n6. Visit `http://localhost:3000` in your browser to access the application.\n\n### Creating a New User\n\nLinkarooie provides an interactive Ruby script for creating new users:\n\n1. Run the script:\n\n   ```bash\n   ruby create_user.rb\n   ```\n\n2. Follow the prompts to enter user details, including:\n   - Email\n   - Password\n   - Username (optional)\n   - Full name\n   - Tags (comma-separated)\n   - Avatar URL\n   - Banner URL\n   - Description\n\nThis script allows for easy user creation, especially useful for setting up initial accounts or testing.\n\n## Docker Deployment\n\nLinkarooie uses Docker for easy deployment and scaling. The project includes a multi-stage Dockerfile for creating a lean production image.\n\n1. Build and start the Docker containers:\n\n   ```bash\n   docker compose -f docker-compose.prod.yml up --build\n   ```\n\n2. Access the application at `http://localhost`.\n\nThe production Docker setup includes:\n\n- Rails application container\n- Redis container for Sidekiq and caching\n- Sidekiq container for background job processing\n\nKey Dockerfile features:\n\n- Multi-stage build for a smaller final image\n- Precompilation of assets and bootsnap\n- Non-root user for improved security\n\n## DigitalOcean Deployment\n\nLinkarooie is optimized for deployment on DigitalOcean using Terraform for infrastructure management and GitHub Actions for continuous deployment.\n\n### Setting up DigitalOcean Infrastructure\n\n1. Install Terraform and set up a DigitalOcean account.\n\n2. Configure your DigitalOcean API token:\n\n   ```bash\n   export DO_TOKEN=your_digitalocean_api_token\n   ```\n\n3. Create a DigitalOcean Droplet:\n\n   ```bash\n   cd terraform/droplet\n   terraform init\n   terraform apply -var=\"do_token=$DO_TOKEN\"\n   ```\n\n4. Set up DigitalOcean Spaces for backups:\n   ```bash\n   cd ../spaces\n   terraform init\n   terraform apply\n   ```\n\n### Configuring GitHub Actions\n\n1. Set up the following secrets in your GitHub repository:\n\n   - `DROPLET_IP`: The IP address of your DigitalOcean Droplet (output from Terraform)\n   - `DROPLET_SSH_PRIVATE_KEY`: The private SSH key to access your Droplet\n   - `GH_PAT`: Your GitHub Personal Access Token\n\n2. The GitHub Actions workflows will automatically:\n   - Run tests and build Docker images on pull requests and pushes to feature branches\n   - Deploy to your DigitalOcean Droplet when changes are merged to the main branch\n\n### Manual Deployment\n\nYou can also trigger a manual deployment using the GitHub Actions workflow dispatch event.\n\n## Configuration\n\n### Environment Variables\n\nCreate a `.env` file in the root directory with the following variables:\n\n```\nSECRET_KEY_BASE=your_secret_key_base\nAXIOM_API_KEY=your_axiom_api_key\nDO_TOKEN=your_digitalocean_token\nSPACES_ACCESS_KEY_ID=your_spaces_access_key_id\nSPACES_SECRET_ACCESS_KEY=your_spaces_secret_access_key\nSPACES_REGION=your_spaces_region\nSPACES_BUCKET_NAME=your_spaces_bucket_name\nRAILS_ENV=production\nCACHE_EXPIRATION=30\n```\n\nEnsure all placeholder values are replaced with your actual API keys and tokens.\n\n### Database Configuration\n\n- Development and test environments use SQLite3.\n- For production, configure your preferred database (PostgreSQL recommended) in `config/database.yml`.\n\n## Backup and Restore Process\n\nLinkarooie includes an automated backup system utilizing DigitalOcean Spaces:\n\n### Automated Backups\n\n- The `BackupDatabaseJob` runs daily at 2 AM.\n- It creates a dump of the SQLite database and uploads it to a DigitalOcean Spaces bucket.\n- Backups are versioned for easy point-in-time recovery.\n\n### Restoring from a Backup\n\nUse the provided Rake task to restore from a backup:\n\n```bash\nrake db:restore BACKUP_FILE=path/to/your_backup_file.sql\n```\n\nFor compressed backups:\n\n```bash\nrake db:restore BACKUP_FILE=path/to/your_backup_file.sql.tar.gz\n```\n\nThe restore process:\n\n1. Drops all existing tables in the database.\n2. Loads the specified backup file.\n3. Applies any pending migrations.\n\n## Customization\n\nLinkarooie is designed to be highly customizable:\n\n- **Views:** Modify ERB templates in `app/views/`\n- **Styles:**\n  - Edit Tailwind CSS classes directly in views\n  - Customize Tailwind configuration in `config/tailwind.config.js`\n  - Add custom styles in `app/assets/stylesheets/application.css.scss`\n- **JavaScript:**\n  - Add or modify Stimulus controllers in `app/javascript/controllers/`\n  - Update the main JavaScript file at `app/javascript/application.js`\n- **Backend Logic:**\n  - Controllers are located in `app/controllers/`\n  - Models are in `app/models/`\n- **Background Jobs:** Add or modify Sidekiq jobs in `app/jobs/`\n- **Localization:** Update language files in `config/locales/`\n\n## Testing\n\nLinkarooie uses RSpec for testing. The test suite includes:\n\n- Model specs\n- Controller specs\n- Feature specs\n- Helper specs\n\nTo run the entire test suite:\n\n```bash\nbundle exec rspec\n```\n\nTo run specific tests:\n\n```bash\nbundle exec rspec spec/models\nbundle exec rspec spec/controllers\nbundle exec rspec spec/features\n```\n\n## CI/CD\n\nLinkarooie utilizes GitHub Actions for continuous integration and deployment:\n\n1. **CI Workflow** (`ci.yml`):\n\n   - Triggered on pull requests to `main` and pushes to feature branches\n   - Sets up Ruby and Node.js environments\n   - Installs dependencies\n   - Runs tests\n   - Builds and pushes Docker image to GitHub Container Registry\n\n2. **Deployment Workflow** (`deploy.yml`):\n   - Triggered on merges to `main` or manual dispatch\n   - Builds and pushes Docker image\n   - SSHs into the DigitalOcean Droplet\n   - Pulls the latest Docker image\n   - Runs database migrations\n   - Restarts the application containers\n\n## Project Structure\n\n```\nlinkarooie/\n├── app/\n│   ├── assets/\n│   ├── controllers/\n│   ├── helpers/\n│   ├── javascript/\n│   ├── jobs/\n│   ├── mailers/\n│   ├── models/\n│   ├── services/\n│   └── views/\n├── bin/\n├── config/\n├── db/\n├── lib/\n├── public/\n├── spec/\n├── storage/\n├── terraform/\n│   ├── droplet/\n│   └── spaces/\n├── .github/workflows/\n├── Dockerfile\n├── docker-compose.prod.yml\n├── Gemfile\n├── package.json\n└── ...\n```\n\n## Key Components\n\n- **User Model:** Manages user accounts, profiles, and authentication.\n- **Link Model:** Handles the creation and management of user links.\n- **Achievement Model:** Manages user achievements and milestones.\n- **Analytics:** Tracks and stores user engagement metrics.\n- **OpenGraphImageGenerator:** Service for creating social media preview images.\n- **BackupDatabaseJob:** Manages automated database backups.\n\n## Gather Script\n\n- [GRABIT.SH](https://grabit.sh) was inspired by this script.\n\nThe `gather.sh` script is a utility for collecting project information:\n\n```bash\n./gather.sh [-o output_method] [-f output_file]\n  -o, --output         Output method: stdout, clipboard, or file (default: stdout)\n  -f, --file           Output file path (required if output method is file)\n```\n\n\u003e Note: There is also a `gather.rb` that works the same.\n\nThis script is useful for quickly compiling project details for documentation or sharing.\n\n## **Useful Rails Console Commands**\n\n### **User Management**\n\n1. **Create a User:**\n\n   ```ruby\n   User.create!(\n     email: \"user@example.com\",\n     password: \"Password123\",\n     username: \"newuser\",\n     full_name: \"New User\",\n     tags: [\"Tech\", \"Music\"].to_json,  # Tags as JSON array\n     avatar: \"https://example.com/avatar.png\",\n     avatar_border: \"white\",\n     banner: \"https://example.com/banner.png\",\n     description: \"User description\",\n     community_opt_in: true,\n     public_analytics: true\n   )\n   ```\n\n2. **List Users:**\n\n   ```ruby\n   User.pluck(:email, :username, :full_name)\n   ```\n\n3. **Find and Update a User:**\n\n   ```ruby\n   user = User.find_by(email: \"user@example.com\")\n   user.update!(full_name: \"Updated Name\")\n   ```\n\n4. **Delete a User:**\n\n   ```ruby\n   user = User.find_by(email: \"user@example.com\")\n   user.destroy!\n   ```\n\n### **Managing Links**\n\n1. **List All Links for a User:**\n\n   ```ruby\n   user = User.find_by(username: \"newuser\")\n   user.links.pluck(:title, :url, :pinned, :position)\n   ```\n\n2. **Create a Link:**\n\n   ```ruby\n   user = User.find_by(username: \"newuser\")\n   user.links.create!(title: \"GitHub\", url: \"https://github.com\", icon: \"fa-brands fa-github\")\n   ```\n\n3. **Delete a Link:**\n\n   ```ruby\n   link = Link.find_by(url: \"https://github.com\")\n   link.destroy!\n   ```\n\n### **Managing Achievements**\n\n1. **List Achievements for a User:**\n\n   ```ruby\n   user = User.find_by(username: \"newuser\")\n   user.achievements.pluck(:title, :date, :description, :url)\n   ```\n\n2. **Create an Achievement:**\n\n   ```ruby\n   user = User.find_by(username: \"newuser\")\n   user.achievements.create!(title: \"Achievement\", date: Date.today, description: \"Details\")\n   ```\n\n3. **Delete an Achievement:**\n\n   ```ruby\n   achievement = Achievement.find_by(title: \"Achievement\")\n   achievement.destroy!\n   ```\n\n### **Analytics and Metrics**\n\n1. **View Total Page Views for a User:**\n\n   ```ruby\n   user = User.find_by(username: \"newuser\")\n   user.page_views.count\n   ```\n\n2. **View Detailed Page Views:**\n\n   ```ruby\n   user = User.find_by(username: \"newuser\")\n   user.page_views.pluck(:path, :visited_at, :referrer, :browser)\n   ```\n\n3. **Get Unique Visitors for a User:**\n\n   ```ruby\n   user = User.find_by(username: \"newuser\")\n   user.page_views.distinct.count(:ip_address)\n   ```\n\n### **Importing and Exporting Data**\n\n1. **Export Users to CSV:**\n\n   ```ruby\n   require 'csv'\n   CSV.open(\"users.csv\", \"wb\") do |csv|\n     csv \u003c\u003c [\"Email\", \"Username\", \"Full Name\", \"Tags\"]\n     User.all.each do |user|\n       csv \u003c\u003c [user.email, user.username, user.full_name, JSON.parse(user.tags).join(\", \")]\n     end\n   end\n   ```\n\n2. **Import Users from CSV:**\n\n   ```ruby\n   require 'csv'\n   CSV.foreach(\"path_to_users.csv\", headers: true) do |row|\n     User.create!(\n       email: row[\"Email\"],\n       username: row[\"Username\"],\n       full_name: row[\"Full Name\"],\n       tags: row[\"Tags\"].split(',').to_json,\n       password: \"Password123\",\n       password_confirmation: \"Password123\"\n     )\n   end\n   ```\n\n### **Checking User Activity**\n\n1. **Users Without Achievements:**\n\n   ```ruby\n   User.left_joins(:achievements).where(achievements: { id: nil }).pluck(:username, :email)\n   ```\n\n2. **Users with Public Analytics Enabled:**\n\n   ```ruby\n   User.where(public_analytics: true).pluck(:username, :email)\n   ```\n\n## Contributing\n\nWe welcome contributions to Linkarooie! Here's how you can help:\n\n1. Fork the repository\n2. Create your feature branch: `git checkout -b feature/AmazingFeature`\n3. Commit your changes: `git commit -m 'Add some AmazingFeature'`\n4. Push to the branch: `git push origin feature/AmazingFeature`\n5. Open a Pull Request\n\nPlease ensure your code adheres to the existing style and passes all tests.\n\n## License\n\nThis project is licensed under the MIT License - see the [LICENSE](LICENSE) file for details.\n\n## Support\n\nIf you find Linkarooie helpful, please consider:\n\n- Starring the repository on GitHub\n- Sharing the project with others\n- Contributing to the codebase\n- Reporting issues or suggesting improvements\n\n## Acknowledgements\n\n- [Ruby on Rails](https://rubyonrails.org/)\n- [Tailwind CSS](https://tailwindcss.com/)\n- [Docker](https://www.docker.com/)\n- [DigitalOcean](https://www.digitalocean.com/)\n- [Terraform](https://www.terraform.io/)\n- [Vite](https://vitejs.dev/)\n- [Sidekiq](https://sidekiq.org/)\n- [Devise](https://github.com/heartcombo/devise)\n- [Chartkick](https://chartkick.com/)\n- [Geocoder](https://github.com/alexreisner/geocoder)\n\n---\n\nLinkarooie © 2024 - Simplify Your Online Presence\n","funding_links":["https://github.com/sponsors/loftwah"],"categories":[],"sub_categories":[],"project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Floftwah%2Flinkarooie","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Floftwah%2Flinkarooie","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Floftwah%2Flinkarooie/lists"}