├── resources └── views │ └── .gitkeep ├── pint.json ├── CHANGELOG.md ├── src ├── MeowPoints.php ├── Models │ ├── User.php │ └── Point.php ├── Facades │ └── MeowPoints.php ├── MeowPointsServiceProvider.php └── Traits │ └── HasPoints.php ├── config └── meow-points.php ├── database └── migrations │ └── create_meow_points_table.php ├── LICENSE.md ├── composer.json ├── README.md └── configure.php /resources/views/.gitkeep: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /pint.json: -------------------------------------------------------------------------------- 1 | { 2 | "exclude": [ 3 | "build" 4 | ] 5 | } 6 | -------------------------------------------------------------------------------- /CHANGELOG.md: -------------------------------------------------------------------------------- 1 | # Changelog 2 | 3 | All notable changes to `:package_name` will be documented in this file. 4 | -------------------------------------------------------------------------------- /src/MeowPoints.php: -------------------------------------------------------------------------------- 1 | 10.0, 9 | ]; 10 | -------------------------------------------------------------------------------- /src/Models/User.php: -------------------------------------------------------------------------------- 1 | id(); 13 | $table->morphs('owner'); 14 | $table->nullableMorphs('pointable'); 15 | $table->integer('count'); 16 | $table->enum('type', ['add', 'sub']); 17 | $table->timestamps(); 18 | }); 19 | } 20 | 21 | public function down() 22 | { 23 | Schema::dropIfExists('meow_points'); 24 | } 25 | }; 26 | -------------------------------------------------------------------------------- /src/Models/Point.php: -------------------------------------------------------------------------------- 1 | where('type', $type); 21 | } 22 | 23 | public function pointable() 24 | { 25 | return $this->morphTo(); 26 | } 27 | 28 | public function owner() 29 | { 30 | return $this->morphTo(); 31 | } 32 | 33 | public function for($pointable) 34 | { 35 | return $this->pointable()->associate($pointable); 36 | } 37 | } 38 | -------------------------------------------------------------------------------- /src/MeowPointsServiceProvider.php: -------------------------------------------------------------------------------- 1 | app->bind('meow_points', function ($app) { 13 | return new MeowPoints(); 14 | }); 15 | // $this->mergeConfigFrom(__DIR__.'/../config/meow-points.php', 'meow-points'); 16 | } 17 | 18 | public function boot() 19 | { 20 | if ($this->app->runningInConsole()) { 21 | $this->publishes([ 22 | __DIR__.'/../config/meow-points.php' => config_path('meow-points.php'), 23 | ], 'config'); 24 | $this->publishes([ 25 | __DIR__.'/../database/migrations/create_meow_points_table.php' => database_path('migrations/'.date('Y_m_d_His', time()).'_create_points_table.php'), 26 | // you can add any number of migrations here 27 | ], 'migrations'); 28 | } 29 | } 30 | } 31 | -------------------------------------------------------------------------------- /LICENSE.md: -------------------------------------------------------------------------------- 1 | The MIT License (MIT) 2 | 3 | Copyright (c) :vendor_name 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in 13 | all copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN 21 | THE SOFTWARE. 22 | -------------------------------------------------------------------------------- /src/Traits/HasPoints.php: -------------------------------------------------------------------------------- 1 | morphMany(Point::class, 'owner', 'owner_type', 'owner_id'); 12 | } 13 | 14 | public function addPoints($points) 15 | { 16 | return $this->points()->create([ 17 | 'count' => $points, 18 | 'type' => 'add', 19 | ]); 20 | } 21 | 22 | public function subPoints($points) 23 | { 24 | return $this->points()->create([ 25 | 'count' => $points, 26 | 'type' => 'sub', 27 | ]); 28 | } 29 | 30 | public function addAmount($amount) 31 | { 32 | return $this->addPoints($amount * config('meow-points.amount')); 33 | } 34 | 35 | public function subAmount($amount) 36 | { 37 | return $this->subPoints($amount * config('meow-points.amount')); 38 | } 39 | 40 | public function getCurrentPointsAttribute() 41 | { 42 | return $this->points()->type('add')->sum('count') - $this->points()->type('sub')->sum('count'); 43 | } 44 | 45 | public function getCurrentAmountAttribute() 46 | { 47 | return $this->getCurrentPointsAttribute() / config('meow-points.amount'); 48 | } 49 | } 50 | -------------------------------------------------------------------------------- /composer.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "ahmedtofaha/meow-points", 3 | "description": "Laravel Points System Package", 4 | "keywords": [ 5 | "ahmedtofaha", 6 | "laravel", 7 | "meow-points", 8 | "points", 9 | "points-system" 10 | ], 11 | "homepage": "https://github.com/ahmedtofaha10/meow-points", 12 | "license": "MIT", 13 | "authors": [ 14 | { 15 | "name": "ahmedtofaha10", 16 | "email": "ahmedtofaha999@gmail.com", 17 | "role": "Developer" 18 | } 19 | ], 20 | "require": { 21 | "php": ">=7.3" 22 | }, 23 | "require-dev": { 24 | "laravel/pint": "^1.0", 25 | "nunomaduro/collision": "^6.0", 26 | "nunomaduro/larastan": "^2.0.1", 27 | "orchestra/testbench": "^7.0", 28 | "pestphp/pest": "^1.21", 29 | "pestphp/pest-plugin-laravel": "^1.1", 30 | "phpstan/extension-installer": "^1.1", 31 | "phpstan/phpstan-deprecation-rules": "^1.0", 32 | "phpstan/phpstan-phpunit": "^1.0", 33 | "phpunit/phpunit": "^9.5", 34 | "spatie/laravel-ray": "^1.26" 35 | }, 36 | "autoload": { 37 | "psr-4": { 38 | "AhmedTofaha\\MeowPoints\\": "src", 39 | "AhmedTofaha\\MeowPoints\\Database\\Factories\\": "database/factories" 40 | } 41 | }, 42 | "autoload-dev": { 43 | "psr-4": { 44 | "AhmedTofaha\\MeowPoints\\Tests\\": "tests" 45 | } 46 | }, 47 | "scripts": { 48 | "analyse": "vendor/bin/phpstan analyse", 49 | "test": "vendor/bin/pest", 50 | "test-coverage": "vendor/bin/pest --coverage", 51 | "format": "vendor/bin/pint" 52 | }, 53 | "config": { 54 | "sort-packages": true 55 | }, 56 | "extra": { 57 | "laravel": { 58 | "providers": [ 59 | "AhmedTofaha\\MeowPoints\\MeowPointsServiceProvider" 60 | ], 61 | "aliases": { 62 | "AhmedTofaha\\MeowPoints\\Facades\\MeowPoints": "MeowPoints" 63 | } 64 | } 65 | }, 66 | "minimum-stability": "dev", 67 | "prefer-stable": true 68 | } 69 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # MEOW POINTS 2 | ### laravel package for points system 3 | [![Latest Version on Packagist](https://img.shields.io/packagist/v/ahmedtofaha/meow-points.svg?style=flat-square)](https://packagist.org/packages/ahmedtofaha/meow-points) 4 | [![GitHub Tests Action Status](https://img.shields.io/github/workflow/status/ahmedtofaha/meow-points/run-tests?label=tests)](https://github.com/ahmedtofaha/meow-points/actions?query=workflow%3Arun-tests+branch%3Amain) 5 | [![GitHub Code Style Action Status](https://img.shields.io/github/workflow/status/ahmedtofaha/meow-points/Fix%20PHP%20code%20style%20issues?label=code%20style)](https://github.com/ahmedtofaha/meow-points/actions?query=workflow%3A"Fix+PHP+code+style+issues"+branch%3Amain) 6 | [![Total Downloads](https://img.shields.io/packagist/dt/ahmedtofaha/meow-points.svg?style=flat-square)](https://packagist.org/packages/ahmedtofaha/meow-points) 7 | ## Installation 8 | 9 | You can install the package via composer: 10 | 11 | ```bash 12 | composer require ahmedtofaha/meow-points 13 | ``` 14 | 15 | You can publish all we need and run the migrations with: 16 | 17 | ```bash 18 | php artisan vendor:publish --provider="AhmedTofaha\MeowPoints\MeowPointsServiceProvider" 19 | php artisan migrate 20 | ``` 21 | 22 | This is the contents of the published config file: 23 | 24 | ```php 25 | # meow-points.php 26 | return [ 27 | /* 28 | determine how much points equal to money amount 29 | @type float 30 | */ 31 | 'amount' => 10.0, 32 | ]; 33 | ``` 34 | 35 | 36 | ## Usage 37 | ### points system 38 | ```php 39 | # use the trait in your model 40 | class User extends Model 41 | { 42 | use HasPoints; 43 | } 44 | # then you can use the following methods for this model 45 | $user = new User(); 46 | $user->addPoints(100); // add 100 points to the user 47 | $user->subPoints(100); // remove 100 points from the user 48 | # and you can use for method after add or sub points 49 | # to determine the points source or reason for the points change 50 | $user->addPoints(100)->for($prize); // add 100 points to the user for the prize 51 | $user->subPoints(100)->for($payment); // remove 100 points from the user for the payment 52 | # you can get the points history for the user 53 | $user->points; // return collection of points history 54 | # you can get the points history for the user with pagination 55 | $user->points()->paginate(10); // return collection of points history with pagination 56 | # get the points model has 57 | $user->current_points; // return the points of the user 58 | ``` 59 | ### balance and amount system 60 | ```php 61 | # config/meow-points.php 62 | return [ 63 | /* 64 | determine how much points equal to money amount 65 | @type float 66 | */ 67 | 'amount' => 12.5, 68 | ]; 69 | ```` 70 | 71 | ```php 72 | $user->addAmount(100); // add 100 amount of money to the user 73 | $user->subAmount(100); // remove 100 amount of money from the user 74 | # and you can use for method after add or sub amount 75 | # to determine the amount money source or reason for the points change 76 | $user->addAmount(100)->for($prize); // add 100 amount of money to the user for the prize 77 | $user->subAmount(100)->for($payment); // remove 100 amount of money from the user for the payment 78 | $user->current_balance; // return the balance of the model 79 | ``` 80 | 81 | 82 | ## Testing 83 | 84 | ```bash 85 | composer test 86 | ``` 87 | 88 | ## Changelog 89 | 90 | Please see [CHANGELOG](CHANGELOG.md) for more information on what has changed recently. 91 | 92 | ## Contributing 93 | 94 | Please see [CONTRIBUTING](CONTRIBUTING.md) for details. 95 | 96 | ## Security Vulnerabilities 97 | 98 | Please review [our security policy](../../security/policy) on how to report security vulnerabilities. 99 | 100 | ## Credits 101 | 102 | - [ahmedtofaha](https://github.com/ahmedtofaha10) 103 | - [All Contributors](../../contributors) 104 | 105 | ## License 106 | 107 | The MIT License (MIT). Please see [License File](LICENSE.md) for more information. 108 | -------------------------------------------------------------------------------- /configure.php: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env php 2 | $version) { 90 | if (in_array($name, $names, true)) { 91 | unset($data['require-dev'][$name]); 92 | } 93 | } 94 | 95 | file_put_contents(__DIR__.'/composer.json', json_encode($data, JSON_PRETTY_PRINT | JSON_UNESCAPED_SLASHES | JSON_UNESCAPED_UNICODE)); 96 | } 97 | 98 | function remove_composer_script($scriptName) 99 | { 100 | $data = json_decode(file_get_contents(__DIR__.'/composer.json'), true); 101 | 102 | foreach ($data['scripts'] as $name => $script) { 103 | if ($scriptName === $name) { 104 | unset($data['scripts'][$name]); 105 | break; 106 | } 107 | } 108 | 109 | file_put_contents(__DIR__.'/composer.json', json_encode($data, JSON_PRETTY_PRINT | JSON_UNESCAPED_SLASHES | JSON_UNESCAPED_UNICODE)); 110 | } 111 | 112 | function remove_readme_paragraphs(string $file): void 113 | { 114 | $contents = file_get_contents($file); 115 | 116 | file_put_contents( 117 | $file, 118 | preg_replace('/.*/s', '', $contents) ?: $contents 119 | ); 120 | } 121 | 122 | function safeUnlink(string $filename) 123 | { 124 | if (file_exists($filename) && is_file($filename)) { 125 | unlink($filename); 126 | } 127 | } 128 | 129 | function determineSeparator(string $path): string 130 | { 131 | return str_replace('/', DIRECTORY_SEPARATOR, $path); 132 | } 133 | 134 | function replaceForWindows(): array 135 | { 136 | return preg_split('/\\r\\n|\\r|\\n/', run('dir /S /B * | findstr /v /i .git\ | findstr /v /i vendor | findstr /v /i '.basename(__FILE__).' | findstr /r /i /M /F:/ ":author :vendor :package VendorName skeleton migration_table_name vendor_name vendor_slug author@domain.com"')); 137 | } 138 | 139 | function replaceForAllOtherOSes(): array 140 | { 141 | return explode(PHP_EOL, run('grep -E -r -l -i ":author|:vendor|:package|VendorName|skeleton|migration_table_name|vendor_name|vendor_slug|author@domain.com" --exclude-dir=vendor ./* ./.github/* | grep -v '.basename(__FILE__))); 142 | } 143 | 144 | $gitName = run('git config user.name'); 145 | $authorName = ask('Author name', $gitName); 146 | 147 | $gitEmail = run('git config user.email'); 148 | $authorEmail = ask('Author email', $gitEmail); 149 | 150 | $usernameGuess = explode(':', run('git config remote.origin.url'))[1]; 151 | $usernameGuess = dirname($usernameGuess); 152 | $usernameGuess = basename($usernameGuess); 153 | $authorUsername = ask('Author username', $usernameGuess); 154 | 155 | $vendorName = ask('Vendor name', $authorUsername); 156 | $vendorSlug = slugify($vendorName); 157 | $vendorNamespace = ucwords($vendorName); 158 | $vendorNamespace = ask('Vendor namespace', $vendorNamespace); 159 | 160 | $currentDirectory = getcwd(); 161 | $folderName = basename($currentDirectory); 162 | 163 | $packageName = ask('Package name', $folderName); 164 | $packageSlug = slugify($packageName); 165 | $packageSlugWithoutPrefix = remove_prefix('laravel-', $packageSlug); 166 | 167 | $className = title_case($packageName); 168 | $className = ask('Class name', $className); 169 | $variableName = lcfirst($className); 170 | $description = ask('Package description', "This is my package {$packageSlug}"); 171 | 172 | $usePhpStan = confirm('Enable PhpStan?', true); 173 | $useLaravelPint = confirm('Enable Laravel Pint?', true); 174 | $useDependabot = confirm('Enable Dependabot?', true); 175 | $useLaravelRay = confirm('Use Ray for debugging?', true); 176 | $useUpdateChangelogWorkflow = confirm('Use automatic changelog updater workflow?', true); 177 | 178 | writeln('------'); 179 | writeln("Author : {$authorName} ({$authorUsername}, {$authorEmail})"); 180 | writeln("Vendor : {$vendorName} ({$vendorSlug})"); 181 | writeln("Package : {$packageSlug} <{$description}>"); 182 | writeln("Namespace : {$vendorNamespace}\\{$className}"); 183 | writeln("Class name : {$className}"); 184 | writeln('---'); 185 | writeln('Packages & Utilities'); 186 | writeln('Use Laravel/Pint : '.($useLaravelPint ? 'yes' : 'no')); 187 | writeln('Use Larastan/PhpStan : '.($usePhpStan ? 'yes' : 'no')); 188 | writeln('Use Dependabot : '.($useDependabot ? 'yes' : 'no')); 189 | writeln('Use Ray App : '.($useLaravelRay ? 'yes' : 'no')); 190 | writeln('Use Auto-Changelog : '.($useUpdateChangelogWorkflow ? 'yes' : 'no')); 191 | writeln('------'); 192 | 193 | writeln('This script will replace the above values in all relevant files in the project directory.'); 194 | 195 | if (! confirm('Modify files?', true)) { 196 | exit(1); 197 | } 198 | 199 | $files = (str_starts_with(strtoupper(PHP_OS), 'WIN') ? replaceForWindows() : replaceForAllOtherOSes()); 200 | 201 | foreach ($files as $file) { 202 | replace_in_file($file, [ 203 | ':author_name' => $authorName, 204 | ':author_username' => $authorUsername, 205 | 'author@domain.com' => $authorEmail, 206 | ':vendor_name' => $vendorName, 207 | ':vendor_slug' => $vendorSlug, 208 | 'VendorName' => $vendorNamespace, 209 | ':package_name' => $packageName, 210 | ':package_slug' => $packageSlug, 211 | ':package_slug_without_prefix' => $packageSlugWithoutPrefix, 212 | 'Skeleton' => $className, 213 | 'skeleton' => $packageSlug, 214 | 'migration_table_name' => title_snake($packageSlug), 215 | 'variable' => $variableName, 216 | ':package_description' => $description, 217 | ]); 218 | 219 | match (true) { 220 | str_contains($file, determineSeparator('src/Skeleton.php')) => rename($file, determineSeparator('./src/'.$className.'.php')), 221 | str_contains($file, determineSeparator('src/SkeletonServiceProvider.php')) => rename($file, determineSeparator('./src/'.$className.'ServiceProvider.php')), 222 | str_contains($file, determineSeparator('src/Facades/Skeleton.php')) => rename($file, determineSeparator('./src/Facades/'.$className.'.php')), 223 | str_contains($file, determineSeparator('src/Commands/SkeletonCommand.php')) => rename($file, determineSeparator('./src/Commands/'.$className.'Command.php')), 224 | str_contains($file, determineSeparator('database/migrations/create_skeleton_table.php.stub')) => rename($file, determineSeparator('./database/migrations/create_'.title_snake($packageSlugWithoutPrefix).'_table.php.stub')), 225 | str_contains($file, determineSeparator('config/skeleton.php')) => rename($file, determineSeparator('./config/'.$packageSlugWithoutPrefix.'.php')), 226 | str_contains($file, 'README.md') => remove_readme_paragraphs($file), 227 | default => [], 228 | }; 229 | } 230 | 231 | if (! $useLaravelPint) { 232 | safeUnlink(__DIR__.'/.github/workflows/fix-php-code-style-issues.yml'); 233 | safeUnlink(__DIR__.'/pint.json'); 234 | } 235 | 236 | if (! $usePhpStan) { 237 | safeUnlink(__DIR__.'/phpstan.neon.dist'); 238 | safeUnlink(__DIR__.'/phpstan-baseline.neon'); 239 | safeUnlink(__DIR__.'/.github/workflows/phpstan.yml'); 240 | 241 | remove_composer_deps([ 242 | 'phpstan/extension-installer', 243 | 'phpstan/phpstan-deprecation-rules', 244 | 'phpstan/phpstan-phpunit', 245 | 'nunomaduro/larastan', 246 | ]); 247 | 248 | remove_composer_script('phpstan'); 249 | } 250 | 251 | if (! $useDependabot) { 252 | safeUnlink(__DIR__.'/.github/dependabot.yml'); 253 | safeUnlink(__DIR__.'/.github/workflows/dependabot-auto-merge.yml'); 254 | } 255 | 256 | if (! $useLaravelRay) { 257 | remove_composer_deps(['spatie/laravel-ray']); 258 | } 259 | 260 | if (! $useUpdateChangelogWorkflow) { 261 | safeUnlink(__DIR__.'/.github/workflows/update-changelog.yml'); 262 | } 263 | 264 | confirm('Execute `composer install` and run tests?') && run('composer install && composer test'); 265 | 266 | confirm('Let this script delete itself?', true) && unlink(__FILE__); 267 | --------------------------------------------------------------------------------