diff --git a/.github/copilot-instructions.md b/.github/copilot-instructions.md new file mode 100644 index 000000000..032664329 --- /dev/null +++ b/.github/copilot-instructions.md @@ -0,0 +1,273 @@ +# LinkAce Developer Instructions + +LinkAce is a self-hosted bookmark manager built with Laravel PHP framework. It supports multiple databases, features advanced tagging and categorization, and provides both web interface and API access. + +**ALWAYS follow these instructions first and fallback to search or bash commands only when you encounter unexpected information that does not match the info here.** + +## Working Effectively + +### Bootstrap, Build, and Test the Repository + +1. **Environment Setup (Required for all development):** + ```bash + # Copy environment file and set up basic configuration + cp .env.example .env + + # Modify .env for local development: + # - Set APP_ENV=local + # - Set APP_DEBUG=true + # - For SQLite: Set DB_CONNECTION=sqlite and DB_DATABASE=/absolute/path/to/database.sqlite + ``` + +2. **Install Dependencies:** + ```bash + # Install PHP dependencies - takes 2-3 minutes + composer install --no-interaction + + # Install Node.js dependencies - takes 30 seconds + npm install + ``` + +3. **Application Configuration:** + ```bash + # Generate Laravel application key + php artisan key:generate + + # Create database file for SQLite (if using SQLite) + mkdir -p database && touch database/database.sqlite + + # Create testing database + touch database/testing.sqlite + + # Run database migrations + php artisan migrate --force + ``` + +4. **Build Assets:** + ```bash + # Development build (for active development) + npm run dev + + # Production build - takes 15 seconds. NEVER CANCEL. + npm run production + ``` + +5. **Run Tests and Linting:** + ```bash + # Run linting - takes 5 seconds. NEVER CANCEL. + composer run lint + + # Run test suite - takes 50 seconds. NEVER CANCEL. Set timeout to 90+ seconds. + composer run test + ``` + +### Run the Application + +**ALWAYS complete the bootstrapping steps above first.** + +#### Local Development Server: +```bash +# Start PHP development server on port 8000 +php artisan serve --host=0.0.0.0 --port=8000 +# Application will be available at http://localhost:8000 +``` + +#### Docker Development (Alternative): +```bash +# IMPORTANT: Docker setup may fail due to package availability issues +# Use native PHP setup (above) if Docker build fails + +# Copy Docker development environment +cp .env.dev .env + +# Start Docker services - takes 6+ minutes, may fail. NEVER CANCEL. Set timeout to 15+ minutes. +docker compose up -d --build + +# If successful, run inside PHP container: +docker compose exec -it php composer install +docker compose exec -it php php artisan key:generate +docker compose exec -it php php artisan migrate +``` + +## Validation + +### Manual Testing Requirements +- **ALWAYS test complete user workflows after making changes** +- **ALWAYS run both linting and tests before committing** +- Test the application setup process by accessing http://localhost:8000 +- Verify the setup wizard redirects properly (should redirect to /setup/start) +- Create a test user: `php artisan registeruser --admin testuser test@example.com` +- Test login functionality through the web interface + +### Required Validation Steps +```bash +# Before any commit, ALWAYS run: +composer run lint # Must pass without errors +composer run test # 438 tests, may have warnings about coverage (acceptable) +npm run production # Must complete successfully +``` + +## Common Tasks + +### User Management +```bash +# Create new admin user +php artisan registeruser --admin username email@example.com + +# Create regular user +php artisan registeruser username email@example.com + +# Complete setup if wizard fails +php artisan setup:complete +``` + +### Database Operations +```bash +# Run migrations +php artisan migrate + +# Reset database completely +php artisan migrate:fresh + +# Check migration status +php artisan migrate:status +``` + +### Asset Management +```bash +# Watch for changes during development +npm run watch + +# One-time development build +npm run dev + +# Production build (for deployment) +npm run production +``` + +## Build and Test Timing + +- **Composer install**: 2-3 minutes (production), 3-4 minutes (with dev dependencies) +- **npm install**: 30 seconds +- **npm run production**: 15 seconds - NEVER CANCEL +- **composer run lint**: 5 seconds - NEVER CANCEL +- **composer run test**: 50 seconds - NEVER CANCEL. Set timeout to 90+ seconds +- **Docker build**: 6+ minutes, may fail - NEVER CANCEL. Set timeout to 15+ minutes + +## Technology Stack + +- **Backend**: Laravel 10.x (PHP 8.1+) +- **Frontend**: Laravel Mix, Bootstrap 5, TomSelect +- **Database**: MySQL/MariaDB, PostgreSQL, SQLite +- **Cache**: Redis (optional) +- **Node.js**: Version 22 LTS (minimum 20 LTS) +- **Development**: Docker Compose (optional), PHP built-in server + +## Project Structure + +### Key Directories +- `app/` - Laravel application code (Models, Controllers, Commands) +- `resources/assets/` - Frontend assets (JS, SCSS) +- `public/assets/dist/` - Compiled assets +- `database/` - Migrations, factories, seeds +- `tests/` - PHPUnit test suite (438 tests) +- `config/` - Laravel configuration files +- `resources/docker/` - Docker configuration files + +### Important Files +- `composer.json` - PHP dependencies and scripts +- `package.json` - Node.js dependencies and build scripts +- `webpack.mix.js` - Asset compilation configuration +- `phpunit.xml` - Test configuration +- `phpcs.xml` - Code style configuration +- `.env.example` - Environment configuration template +- `CONTRIBUTING.md` - Development setup guide + +## Environment Configuration + +### Required Environment Variables +```bash +APP_ENV=local # Set to local for development +APP_DEBUG=true # Enable debug mode for development +APP_KEY= # Generated by artisan key:generate +DB_CONNECTION=sqlite # or mysql/pgsql +DB_DATABASE=/path/to/database # Absolute path for SQLite +``` + +### Database Configuration Examples +```bash +# SQLite (recommended for development) +DB_CONNECTION=sqlite +DB_DATABASE=/home/user/LinkAce/database/database.sqlite + +# MySQL +DB_CONNECTION=mysql +DB_HOST=127.0.0.1 +DB_PORT=3306 +DB_DATABASE=linkace +DB_USERNAME=linkace +DB_PASSWORD=password + +# PostgreSQL +DB_CONNECTION=pgsql +DB_HOST=127.0.0.1 +DB_PORT=5432 +DB_DATABASE=linkace +DB_USERNAME=linkace +DB_PASSWORD=password +``` + +## Common Issues and Solutions + +### Docker Build Failures +- **Issue**: Package availability errors in Alpine Linux +- **Solution**: Use native PHP setup instead of Docker + +### Test Failures +- **Issue**: Database file does not exist +- **Solution**: `touch database/testing.sqlite` + +### Permission Errors +- **Issue**: SQLite database not writable +- **Solution**: `chmod 664 database/database.sqlite && chmod 775 database/` + +### Asset Build Issues +- **Issue**: Mix compilation errors +- **Solution**: Delete `node_modules/` and run `npm install` again + +## CI/CD Pipeline + +The repository uses GitHub Actions for testing: +- **Node.js asset build**: Tests on Node 22, uploads build artifacts +- **PHP testing**: Tests on PHP 8.1, 8.2, 8.3, 8.4 across Ubuntu +- **Artifacts**: Built assets are shared between build and test jobs + +### CI Commands (matches local development) +```bash +npm ci && npm run production # Asset building +composer install --prefer-dist # Dependency installation +composer run lint # Code style checking +composer run test # Test suite execution +``` + +## Setup Process Flow + +1. Fresh clone → Copy `.env.example` to `.env` +2. Install Composer dependencies → `composer install` +3. Generate app key → `php artisan key:generate` +4. Install Node dependencies → `npm install` +5. Build assets → `npm run production` +6. Set up database → Create database file/connection +7. Run migrations → `php artisan migrate` +8. Create user → `php artisan registeruser --admin` +9. Start server → `php artisan serve` +10. Test application → Access setup wizard at http://localhost:8000 + +## Important Notes + +- **NEVER CANCEL long-running commands** - builds and tests can take up to several minutes +- **ALWAYS use absolute paths** for SQLite database configuration +- **Docker setup is optional** - native PHP setup is more reliable +- **Test database required** - `database/testing.sqlite` must exist for tests +- **Admin user creation** - Use `--admin` flag for admin privileges +- **Setup wizard** - Application redirects to `/setup/start` until configuration is complete \ No newline at end of file diff --git a/.github/workflows/build-docker.yml b/.github/workflows/build-docker.yml index 7a13cad8f..3fccae2a1 100644 --- a/.github/workflows/build-docker.yml +++ b/.github/workflows/build-docker.yml @@ -13,7 +13,7 @@ jobs: steps: - name: Checkout Code - uses: actions/checkout@v4 + uses: actions/checkout@v5 - name: Set up QEMU uses: docker/setup-qemu-action@v3 diff --git a/.github/workflows/build-package.yml b/.github/workflows/build-package.yml index c426faaf6..a199feeae 100644 --- a/.github/workflows/build-package.yml +++ b/.github/workflows/build-package.yml @@ -10,7 +10,7 @@ jobs: name: Build final dist package runs-on: ubuntu-latest steps: - - uses: actions/checkout@v4 + - uses: actions/checkout@v5 - name: Use Node.js uses: actions/setup-node@v4 @@ -69,7 +69,7 @@ jobs: name: Build final dist package for Docker setup runs-on: ubuntu-latest steps: - - uses: actions/checkout@v4 + - uses: actions/checkout@v5 - name: Rename files run: | diff --git a/.github/workflows/test.yml b/.github/workflows/test.yml index cd1fb0f66..ace6a2583 100644 --- a/.github/workflows/test.yml +++ b/.github/workflows/test.yml @@ -13,7 +13,7 @@ jobs: runs-on: ubuntu-latest steps: - - uses: actions/checkout@v4 + - uses: actions/checkout@v5 - name: Use Node.js uses: actions/setup-node@v4 @@ -48,14 +48,14 @@ jobs: php-versions: [ '8.1', '8.2', '8.3', '8.4' ] steps: - - uses: actions/checkout@v4 + - uses: actions/checkout@v5 - - uses: actions/download-artifact@v4 + - uses: actions/download-artifact@v5 with: name: assets path: public/assets/dist - - uses: actions/download-artifact@v4 + - uses: actions/download-artifact@v5 with: name: mix-manifest path: public diff --git a/app/Actions/Settings/SetDefaultSettingsForUser.php b/app/Actions/Settings/SetDefaultSettingsForUser.php index 1eba8f51b..6ceb51f0e 100644 --- a/app/Actions/Settings/SetDefaultSettingsForUser.php +++ b/app/Actions/Settings/SetDefaultSettingsForUser.php @@ -49,7 +49,6 @@ public function up(): void $this->migrator->add($group . '.share_linkedin', $defaults['share_services']); $this->migrator->add($group . '.share_mastodon', $defaults['share_services']); $this->migrator->add($group . '.share_pinterest', $defaults['share_services']); - $this->migrator->add($group . '.share_pocket', $defaults['share_services']); $this->migrator->add($group . '.share_reddit', $defaults['share_services']); $this->migrator->add($group . '.share_skype', $defaults['share_services']); $this->migrator->add($group . '.share_sms', $defaults['share_services']); diff --git a/app/Models/Link.php b/app/Models/Link.php index 55f55e718..980c443de 100644 --- a/app/Models/Link.php +++ b/app/Models/Link.php @@ -164,6 +164,7 @@ public function getFormattedDescriptionAttribute(): string return ''; } + // this explicit test for false allows us to automatically enable markdown for guest users, as their setting is null if (usersettings('markdown_for_text') === false) { return htmlentities($this->description); } diff --git a/app/Settings/GuestSettings.php b/app/Settings/GuestSettings.php index fd29a262f..f885462b8 100644 --- a/app/Settings/GuestSettings.php +++ b/app/Settings/GuestSettings.php @@ -22,7 +22,6 @@ class GuestSettings extends Settings public bool $share_linkedin; public bool $share_mastodon; public bool $share_pinterest; - public bool $share_pocket; public bool $share_reddit; public bool $share_skype; public bool $share_sms; diff --git a/app/Settings/UserSettings.php b/app/Settings/UserSettings.php index f7d6cc195..dcae8a700 100644 --- a/app/Settings/UserSettings.php +++ b/app/Settings/UserSettings.php @@ -39,7 +39,6 @@ class UserSettings extends Settings public bool $share_linkedin; public bool $share_mastodon; public bool $share_pinterest; - public bool $share_pocket; public bool $share_reddit; public bool $share_skype; public bool $share_sms; diff --git a/config/mail.php b/config/mail.php index e33c4bd0d..a4f5614fb 100644 --- a/config/mail.php +++ b/config/mail.php @@ -1,7 +1,6 @@ env('MAIL_USERNAME'), 'password' => env('MAIL_PASSWORD'), 'timeout' => null, + // TLS certificate verification options + // Set MAIL_VERIFY_TLS=false in your .env to disable verification (not recommended for production) + 'stream' => [ + 'ssl' => [ + 'verify_peer' => env('MAIL_VERIFY_TLS', true), + 'verify_peer_name' => env('MAIL_VERIFY_TLS', true), + ], + ], ], 'ses' => [ @@ -113,5 +120,4 @@ resource_path('views/vendor/mail'), ], ], - ]; diff --git a/config/sharing.php b/config/sharing.php index 7bd3c1bbb..b9c22cfc7 100644 --- a/config/sharing.php +++ b/config/sharing.php @@ -77,10 +77,6 @@ 'action' => 'web+mastodon://share?text=#E-SHARETEXT#', 'icon' => 'icon.brand.mastodon', ], - 'pocket' => [ - 'action' => 'https://getpocket.com/save?url=#URL#', - 'icon' => 'icon.brand.get-pocket', - ], 'flipboard' => [ 'action' => 'https://share.flipboard.com/bookmarklet/popout?v=#SUBJECT#&url=#E-URL#', 'icon' => 'icon.brand.flipboard', diff --git a/database/settings/2025_08_31_100933_remove_pocket_share_setting.php b/database/settings/2025_08_31_100933_remove_pocket_share_setting.php new file mode 100644 index 000000000..1a87f36c0 --- /dev/null +++ b/database/settings/2025_08_31_100933_remove_pocket_share_setting.php @@ -0,0 +1,19 @@ +migrator->exists('guest.share_pocket')) { + $this->migrator->delete('guest.share_pocket'); + } + + foreach (DB::table('users')->pluck('id') as $userId) { + $id = 'user-' . $userId; + if ($this->migrator->exists($id . '.share_pocket')) { + $this->migrator->delete($id . '.share_pocket'); + } + } + } +}; diff --git a/docker-compose.production.yml b/docker-compose.production.yml index 420d8c731..f0fbb536a 100644 --- a/docker-compose.production.yml +++ b/docker-compose.production.yml @@ -19,9 +19,8 @@ services: # --- Database db: - image: docker.io/library/mariadb:11.5 + image: docker.io/library/mariadb:12.0 restart: unless-stopped - command: mariadbd --character-set-server=utf8mb4 --collation-server=utf8mb4_bin environment: - MYSQL_ROOT_PASSWORD=${DB_PASSWORD} - MYSQL_USER=${DB_USERNAME} @@ -32,10 +31,9 @@ services: # --- Cache redis: - image: docker.io/bitnami/redis:7.4 + image: docker.io/library/redis:8.2 + command: "redis-server --requirepass ${REDIS_PASSWORD}" restart: unless-stopped - environment: - - REDIS_PASSWORD=${REDIS_PASSWORD} volumes: db: diff --git a/docker-compose.yml b/docker-compose.yml index 0e9888a44..28ed74894 100644 --- a/docker-compose.yml +++ b/docker-compose.yml @@ -4,8 +4,7 @@ services: # --- MariaDB db: - image: docker.io/library/mariadb:11.5 - command: mariadbd --character-set-server=utf8mb4 --collation-server=utf8mb4_bin + image: docker.io/library/mariadb:12.0 environment: - MYSQL_ROOT_PASSWORD=${DB_PASSWORD} - MYSQL_USER=${DB_USERNAME} @@ -17,7 +16,7 @@ services: - linkace-db:/var/lib/mysql pg-db: - image: docker.io/library/postgres:16 + image: docker.io/library/postgres:17 environment: - POSTGRES_PASSWORD=${DB_PASSWORD} - POSTGRES_USER=${DB_USERNAME} @@ -51,9 +50,8 @@ services: # --- Redis redis: - image: docker.io/bitnami/redis:7.4 - environment: - - REDIS_PASSWORD=${REDIS_PASSWORD} + image: docker.io/library/redis:8.2 + command: "redis-server --requirepass ${REDIS_PASSWORD}" ports: - "127.0.0.1:6379:6379" diff --git a/package-lock.json b/package-lock.json index 863268a88..36d6e3f9d 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1,12 +1,12 @@ { "name": "linkace", - "version": "2.1.9", + "version": "2.2.0", "lockfileVersion": 3, "requires": true, "packages": { "": { "name": "linkace", - "version": "2.1.9", + "version": "2.2.0", "license": "GPL-3.0-or-later", "dependencies": { "bootstrap": "^5.3.3", @@ -15,7 +15,7 @@ "devDependencies": { "laravel-mix": "^6.0.31", "postcss": "^8.4.35", - "sass": "^1.85.0", + "sass": "^1.90.0", "sass-loader": "^16.0.5" } }, @@ -8606,9 +8606,9 @@ "license": "MIT" }, "node_modules/sass": { - "version": "1.89.2", - "resolved": "https://registry.npmjs.org/sass/-/sass-1.89.2.tgz", - "integrity": "sha512-xCmtksBKd/jdJ9Bt9p7nPKiuqrlBMBuuGkQlkhZjjQk3Ty48lv93k5Dq6OPkKt4XwxDJ7tvlfrTa1MPA9bf+QA==", + "version": "1.90.0", + "resolved": "https://registry.npmjs.org/sass/-/sass-1.90.0.tgz", + "integrity": "sha512-9GUyuksjw70uNpb1MTYWsH9MQHOHY6kwfnkafC24+7aOMZn9+rVMBxRbLvw756mrBFbIsFg6Xw9IkR2Fnn3k+Q==", "dev": true, "license": "MIT", "dependencies": { diff --git a/package.json b/package.json index 5afcbb3e5..4da67f524 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "linkace", - "version": "2.1.9", + "version": "2.2.0", "description": "A small, selfhosted bookmark manager with advanced features, built with Laravel and Docker", "homepage": "https://github.com/Kovah/LinkAce", "repository": { @@ -15,7 +15,7 @@ "devDependencies": { "laravel-mix": "^6.0.31", "postcss": "^8.4.35", - "sass": "^1.85.0", + "sass": "^1.90.0", "sass-loader": "^16.0.5" }, "dependencies": { diff --git a/resources/assets/js/components/TagsSelect.js b/resources/assets/js/components/TagsSelect.js index be9ff0b16..80f7591ab 100644 --- a/resources/assets/js/components/TagsSelect.js +++ b/resources/assets/js/components/TagsSelect.js @@ -27,7 +27,6 @@ export default class TagsSelect { delimiter: ',', persist: false, create: this.selectAllowsCreation(), - addPrecedence: true, valueField: 'id', labelField: 'name', searchField: 'name', diff --git a/resources/views/guest/links/partials/single-detailed.blade.php b/resources/views/guest/links/partials/single-detailed.blade.php index 59d7bae41..bdbd445d0 100644 --- a/resources/views/guest/links/partials/single-detailed.blade.php +++ b/resources/views/guest/links/partials/single-detailed.blade.php @@ -12,7 +12,7 @@ {{ $link->url }} @if($link->description) -
{{ $link->description }}
+
{!! $link->formatted_description !!}
@endif diff --git a/resources/views/guest/partials/nav.blade.php b/resources/views/guest/partials/nav.blade.php index 7ad9290a5..22a7308ce 100644 --- a/resources/views/guest/partials/nav.blade.php +++ b/resources/views/guest/partials/nav.blade.php @@ -5,8 +5,12 @@