├── .gitignore ├── .vscode └── settings.json ├── LICENSE ├── README.md ├── RoboFile.php ├── docker-compose.yaml ├── docker └── web │ ├── dockerfile │ ├── init.sh │ ├── supervisord-proxy.conf │ └── vhost.conf ├── docs ├── .gitignore ├── .vuepress │ ├── config.js │ └── dist │ │ ├── 404.html │ │ ├── assets │ │ ├── css │ │ │ └── 0.styles.35bcedcf.css │ │ ├── img │ │ │ ├── 0990f53c83bb94a02d8885a973aa6736f1de022081d874f3556ea40ff6abad83.f8b9e053.png │ │ │ ├── 20014d5f04411184559b7a49ca1300a9b95e867acda5b7942524f035e054a28b.0f836819.png │ │ │ ├── 38d04db14246b8c3008d303da81563dab3ba4a2db6949d77951f550c23735e6d.d38183b9.png │ │ │ ├── 42b313a369564f02f6cc7afb400de46e6d08e6d25cf2df0802babdec3263866b.1caaebb8.png │ │ │ ├── 43bd995595b8e51e9fe3eb9a0bf133e348b01336310e92530319197e9fddd701.d5e94f17.png │ │ │ ├── 48fa4dbf28e3314719f9b69e246ca7bd9f397f64311a11e799e44524aaf46dee.75964ac4.png │ │ │ ├── 548d567dbab937cd2c77d9bce2e62132c91df20b2c4f027aa7871136fdf7f2ac.a95fb44a.png │ │ │ ├── 54fc0a9b336b290c16ebf4375808f1c6a15b8fe7f916073893680fce7077950c.38e22bbb.png │ │ │ ├── 554aaa554abae4a604986b38b3fbd9900e29aa433448adcb58188a351b3a1ec7.dfacdb2f.png │ │ │ ├── 5d1e579c1b1b685a759be5201f47a734ee568cc012d068cd18b6b0f533927104.18804581.png │ │ │ ├── 5d488deee54ed0b05ffdb6fd5f5642e33bbefa4a441bb81c8a072cc681598c8e.1becf902.png │ │ │ ├── 817daf704b66a572f2c3b163320abbc0508b2dd200f7dfd36ab294f58e0e19e0.e3533ad6.png │ │ │ ├── 92ade1fe984e3dded00156cbe7920d0de9d243f262b1a03c902583552ac61a03.f94908c0.png │ │ │ ├── 9a3fe6dce02bd6cc8484f711f949c16e42c35e84d13d4ea0ace536dc7122f750.969bde3b.png │ │ │ ├── a1cac133c49ff024b2024d1a78d2da451d1e58d3c827651e4dc62090d9e87183.488a5649.png │ │ │ ├── b217c0402e8f49361a24080b2c91e28f1a24c0b26760aaf89871ac00a0949c9e.01cd5750.png │ │ │ ├── e5629dd0d0718e6844a29966eaa1be70ff69f6d540b9df73fb512a8455a1faaf.5892a7f7.png │ │ │ ├── e9789275ff88333a7d004b0d1a2833fa8cc226882ea63b830e39295e1e1b5412.424e7ba4.png │ │ │ ├── f78cb444544a6985f61463396f626bde993a360412463d1726af53250fe548f4.82c20197.png │ │ │ ├── f88288b384cfd27d55d410ba9015be1d1b32ed6cf060b46f73dae23f0f497fc4.49898d8d.png │ │ │ ├── fc8073dcebc631393515945821d4c101d32b80aaea1c399e59c47eeb94ef1b68.ecb0794d.png │ │ │ ├── fcfe6c149194be94f11e7de6691f6de6e09593fe10f305c7dac2c1a3853251e2.197c7d78.png │ │ │ ├── ff792949fdb0d8359b1f20a1db6763d23430e8d94b58180f38be1342f8ccce0c.33b19f02.png │ │ │ └── search.83621669.svg │ │ └── js │ │ │ ├── 2.abbacd5f.js │ │ │ ├── 3.752c41d5.js │ │ │ ├── 4.5e253ff5.js │ │ │ ├── 5.f7ea1688.js │ │ │ ├── 6.a65e5dd2.js │ │ │ ├── 7.1eab93e4.js │ │ │ ├── 8.4e522548.js │ │ │ └── app.216dffaf.js │ │ ├── index.html │ │ └── zh │ │ └── index.html ├── README.md ├── images │ ├── 0990f53c83bb94a02d8885a973aa6736f1de022081d874f3556ea40ff6abad83.png │ ├── 20014d5f04411184559b7a49ca1300a9b95e867acda5b7942524f035e054a28b.png │ ├── 38d04db14246b8c3008d303da81563dab3ba4a2db6949d77951f550c23735e6d.png │ ├── 42b313a369564f02f6cc7afb400de46e6d08e6d25cf2df0802babdec3263866b.png │ ├── 43bd995595b8e51e9fe3eb9a0bf133e348b01336310e92530319197e9fddd701.png │ ├── 48fa4dbf28e3314719f9b69e246ca7bd9f397f64311a11e799e44524aaf46dee.png │ ├── 548d567dbab937cd2c77d9bce2e62132c91df20b2c4f027aa7871136fdf7f2ac.png │ ├── 54fc0a9b336b290c16ebf4375808f1c6a15b8fe7f916073893680fce7077950c.png │ ├── 554aaa554abae4a604986b38b3fbd9900e29aa433448adcb58188a351b3a1ec7.png │ ├── 5d1e579c1b1b685a759be5201f47a734ee568cc012d068cd18b6b0f533927104.png │ ├── 5d488deee54ed0b05ffdb6fd5f5642e33bbefa4a441bb81c8a072cc681598c8e.png │ ├── 817daf704b66a572f2c3b163320abbc0508b2dd200f7dfd36ab294f58e0e19e0.png │ ├── 92ade1fe984e3dded00156cbe7920d0de9d243f262b1a03c902583552ac61a03.png │ ├── 9a3fe6dce02bd6cc8484f711f949c16e42c35e84d13d4ea0ace536dc7122f750.png │ ├── a1cac133c49ff024b2024d1a78d2da451d1e58d3c827651e4dc62090d9e87183.png │ ├── ac6800e6f4dddef7b963b6c14346a46e0183c63186d5ed90de979f7958968105.png │ ├── b217c0402e8f49361a24080b2c91e28f1a24c0b26760aaf89871ac00a0949c9e.png │ ├── e5629dd0d0718e6844a29966eaa1be70ff69f6d540b9df73fb512a8455a1faaf.png │ ├── e9789275ff88333a7d004b0d1a2833fa8cc226882ea63b830e39295e1e1b5412.png │ ├── f78cb444544a6985f61463396f626bde993a360412463d1726af53250fe548f4.png │ ├── f88288b384cfd27d55d410ba9015be1d1b32ed6cf060b46f73dae23f0f497fc4.png │ ├── fc8073dcebc631393515945821d4c101d32b80aaea1c399e59c47eeb94ef1b68.png │ ├── fcfe6c149194be94f11e7de6691f6de6e09593fe10f305c7dac2c1a3853251e2.png │ └── ff792949fdb0d8359b1f20a1db6763d23430e8d94b58180f38be1342f8ccce0c.png ├── package.json ├── yarn.lock └── zh │ └── README.md ├── images └── 7ff8a6db94c5aa55be336a84bcab53c5c198d33919179a77df37dc7e593b4574.png ├── proxy ├── RoboFile.php ├── go.mod ├── go.sum ├── proxy │ ├── assets │ │ ├── rules.json │ │ └── settings.json │ ├── proxy.go │ ├── proxy_test.go │ └── types.go ├── rules.json ├── server ├── server-linux ├── server-mac ├── server.go └── settings.json └── web ├── .editorconfig ├── .env.example ├── .gitattributes ├── .gitignore ├── .styleci.yml ├── README.md ├── app ├── Actions │ ├── Fortify │ │ ├── CreateNewUser.php │ │ ├── PasswordValidationRules.php │ │ ├── ResetUserPassword.php │ │ ├── UpdateUserPassword.php │ │ └── UpdateUserProfileInformation.php │ └── Jetstream │ │ └── DeleteUser.php ├── Console │ ├── Commands │ │ └── Test.php │ └── Kernel.php ├── Exceptions │ └── Handler.php ├── Http │ ├── Controllers │ │ ├── ApiController.php │ │ ├── AssertController.php │ │ ├── Controller.php │ │ └── WebhookController.php │ ├── Kernel.php │ ├── Livewire │ │ └── Prism │ │ │ └── Admin │ │ │ ├── Content.php │ │ │ ├── ContentCreate.php │ │ │ ├── ContentDelete.php │ │ │ ├── ContentItem.php │ │ │ ├── ContentModify.php │ │ │ ├── Links.php │ │ │ ├── MemberItem.php │ │ │ ├── Members.php │ │ │ ├── MembersModify.php │ │ │ ├── Plan.php │ │ │ ├── PlanCreate.php │ │ │ ├── PlanDelete.php │ │ │ ├── PlanItem.php │ │ │ ├── PlanModify.php │ │ │ └── Settings.php │ ├── Middleware │ │ ├── Authenticate.php │ │ ├── CheckAdmin.php │ │ ├── CheckJwt.php │ │ ├── EncryptCookies.php │ │ ├── LoadConfig.php │ │ ├── PreventRequestsDuringMaintenance.php │ │ ├── RedirectIfAuthenticated.php │ │ ├── TrimStrings.php │ │ ├── TrustHosts.php │ │ ├── TrustProxies.php │ │ └── VerifyCsrfToken.php │ └── helpers.php ├── Models │ ├── Content.php │ ├── Payinfo.php │ ├── Plan.php │ ├── Settings.php │ └── User.php ├── Providers │ ├── AppServiceProvider.php │ ├── AuthServiceProvider.php │ ├── BroadcastServiceProvider.php │ ├── EventServiceProvider.php │ ├── FortifyServiceProvider.php │ ├── JetstreamServiceProvider.php │ └── RouteServiceProvider.php └── View │ └── Components │ ├── AppLayout.php │ ├── Detail.php │ ├── GuestLayout.php │ └── Span.php ├── artisan ├── bootstrap ├── app.php └── cache │ └── .gitignore ├── composer.json ├── composer.lock ├── config ├── app.php ├── auth.php ├── broadcasting.php ├── cache.php ├── cors.php ├── database.php ├── filesystems.php ├── fortify.php ├── hashing.php ├── jetstream.php ├── logging.php ├── mail.php ├── queue.php ├── sanctum.php ├── services.php ├── session.php └── view.php ├── database ├── .gitignore ├── factories │ └── UserFactory.php ├── migrations │ ├── 2014_10_12_000000_create_users_table.php │ ├── 2014_10_12_100000_create_password_resets_table.php │ ├── 2014_10_12_200000_add_two_factor_columns_to_users_table.php │ ├── 2019_05_03_000001_create_customer_columns.php │ ├── 2019_05_03_000002_create_subscriptions_table.php │ ├── 2019_05_03_000003_create_subscription_items_table.php │ ├── 2019_08_19_000000_create_failed_jobs_table.php │ ├── 2019_12_14_000001_create_personal_access_tokens_table.php │ ├── 2021_02_19_084258_create_sessions_table.php │ ├── 2021_02_19_090050_add_level_to_users_tables.php │ ├── 2021_02_20_065353_create_content_table.php │ ├── 2021_02_20_091827_create_plans_table.php │ ├── 2021_02_20_092033_create_content_to_plans_table.php │ ├── 2021_02_22_045143_add_subscription_info_to_users_table.php │ ├── 2021_02_22_045431_create_payinfo_table.php │ └── 2021_02_23_101504_create_settings_table.php └── seeders │ └── DatabaseSeeder.php ├── package-lock.json ├── package.json ├── phpunit.xml ├── public ├── .htaccess ├── css │ └── app.css ├── favicon.ico ├── img │ ├── brand.svg │ └── logo.svg ├── index.php ├── js │ └── app.js ├── mix-manifest.json ├── robots.txt ├── site │ ├── app.css │ └── app.js └── web.config ├── resources ├── css │ └── app.css ├── js │ ├── app.js │ └── bootstrap.js ├── lang │ └── en │ │ ├── auth.php │ │ ├── pagination.php │ │ ├── passwords.php │ │ └── validation.php ├── markdown │ ├── policy.md │ └── terms.md ├── site │ ├── .gitignore │ ├── dist │ │ ├── app.css │ │ └── app.js │ ├── headcode.html │ ├── mix-manifest.json │ ├── package-lock.json │ ├── package.json │ ├── src │ │ ├── alpine.min.js │ │ ├── app.js │ │ ├── app.scss │ │ └── app2.scss │ ├── webpack.mix.js │ └── yarn.lock └── views │ ├── admin │ ├── content │ │ ├── create.blade.php │ │ ├── list.blade.php │ │ └── modify.blade.php │ ├── links.blade.php │ ├── members │ │ ├── list.blade.php │ │ └── modify.blade.php │ ├── plans │ │ ├── create.blade.php │ │ ├── list.blade.php │ │ └── modify.blade.php │ └── settings.blade.php │ ├── api │ ├── api-token-manager.blade.php │ └── index.blade.php │ ├── auth │ ├── confirm-password.blade.php │ ├── forgot-password.blade.php │ ├── login.blade.php │ ├── register.blade.php │ ├── reset-password.blade.php │ ├── two-factor-challenge.blade.php │ └── verify-email.blade.php │ ├── components │ ├── detail.blade.php │ └── span.blade.php │ ├── dashboard.blade.php │ ├── layouts │ ├── app.blade.php │ └── guest.blade.php │ ├── livewire │ └── prism │ │ └── admin │ │ ├── content-create.blade.php │ │ ├── content-delete.blade.php │ │ ├── content-item.blade.php │ │ ├── content-modify.blade.php │ │ ├── content.blade.php │ │ ├── links.blade.php │ │ ├── member-item.blade.php │ │ ├── members-modify.blade.php │ │ ├── members.blade.php │ │ ├── plan-create.blade.php │ │ ├── plan-delete.blade.php │ │ ├── plan-item.blade.php │ │ ├── plan-modify.blade.php │ │ ├── plan.blade.php │ │ └── settings.blade.php │ ├── navigation-menu.blade.php │ ├── page │ ├── profile.blade.php │ ├── profile2.blade.php │ ├── user.blade.php │ └── user2.blade.php │ ├── policy.blade.php │ ├── profile │ ├── delete-user-form.blade.php │ ├── logout-other-browser-sessions-form.blade.php │ ├── show.blade.php │ ├── two-factor-authentication-form.blade.php │ ├── update-password-form.blade.php │ └── update-profile-information-form.blade.php │ ├── terms.blade.php │ ├── vendor │ └── jetstream │ │ ├── components │ │ ├── action-message.blade.php │ │ ├── action-section.blade.php │ │ ├── application-logo.blade.php │ │ ├── application-mark.blade.php │ │ ├── authentication-card-logo.blade.php │ │ ├── authentication-card.blade.php │ │ ├── banner.blade.php │ │ ├── button.blade.php │ │ ├── checkbox.blade.php │ │ ├── confirmation-modal.blade.php │ │ ├── confirms-password.blade.php │ │ ├── danger-button.blade.php │ │ ├── dialog-modal.blade.php │ │ ├── dropdown-link.blade.php │ │ ├── dropdown.blade.php │ │ ├── form-section.blade.php │ │ ├── input-error.blade.php │ │ ├── input.blade.php │ │ ├── label.blade.php │ │ ├── modal.blade.php │ │ ├── nav-link.blade.php │ │ ├── responsive-nav-link.blade.php │ │ ├── secondary-button.blade.php │ │ ├── section-border.blade.php │ │ ├── section-title.blade.php │ │ ├── switchable-team.blade.php │ │ ├── validation-errors.blade.php │ │ └── welcome.blade.php │ │ └── mail │ │ └── team-invitation.blade.php │ └── welcome.blade.php ├── routes ├── api.php ├── channels.php ├── console.php └── web.php ├── server.php ├── storage ├── app │ ├── .gitignore │ └── public │ │ └── .gitignore ├── framework │ ├── .gitignore │ ├── cache │ │ ├── .gitignore │ │ └── data │ │ │ └── .gitignore │ ├── sessions │ │ └── .gitignore │ ├── testing │ │ └── .gitignore │ └── views │ │ └── .gitignore └── logs │ └── .gitignore ├── tailwind.config.js ├── tests ├── CreatesApplication.php ├── Feature │ ├── ApiTokenPermissionsTest.php │ ├── AuthenticationTest.php │ ├── BrowserSessionsTest.php │ ├── CreateApiTokenTest.php │ ├── DeleteAccountTest.php │ ├── DeleteApiTokenTest.php │ ├── EmailVerificationTest.php │ ├── ExampleTest.php │ ├── PasswordConfirmationTest.php │ ├── PasswordResetTest.php │ ├── ProfileInformationTest.php │ ├── RegistrationTest.php │ ├── TwoFactorAuthenticationSettingsTest.php │ └── UpdatePasswordTest.php ├── TestCase.php └── Unit │ └── ExampleTest.php └── webpack.mix.js /.gitignore: -------------------------------------------------------------------------------- 1 | /site -------------------------------------------------------------------------------- /.vscode/settings.json: -------------------------------------------------------------------------------- 1 | { 2 | "git.ignoreLimitWarning": true 3 | } -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | This project uses the GPLv2 License with conditions. 2 | 3 | Both individuals and commercial companies can use MemberPrism to build their own member-only content application under the GPLv2 license. 4 | 5 | However, selling MemberPrism itself as a Cloud Hosting service (e.g., Selling MemberPrism as a cloud service through Docker2SaaS or other platforms) is prohibited. -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Member Prism 2 | 3 | 4 | 5 | open source alternative to memberstack / memberspace , but with both front and backend member-only content protection 6 | 7 | # Video 8 | 9 | Youtube: How to add membership management, subscriptions and content protection to a website in less than ten minutes. 10 | 11 | [![youtube](https://img.youtube.com/vi/lmhISws3UJk/0.jpg)](https://www.youtube.com/watch?v=lmhISws3UJk) 12 | 13 | Bilibili link: 14 | 15 | 16 | # Document 17 | 18 | - [English](docs/README.md) 19 | - [简体中文](docs/zh/README.md) 20 | 21 | # License 22 | 23 | This project uses the GPLv2 License with conditions. 24 | 25 | Both individuals and commercial companies can use MemberPrism to build their own member-only content application under the GPLv2 license. 26 | 27 | However, selling MemberPrism itself as a Cloud Hosting service (e.g., Selling MemberPrism as a cloud service through Docker2SaaS or other platforms) is prohibited. 28 | -------------------------------------------------------------------------------- /RoboFile.php: -------------------------------------------------------------------------------- 1 | _exec("git add . && git commit -m '$note' && git push"); 14 | $tmp_dir = '/tmp/MPT'.md5($repo); 15 | if( file_exists( $tmp_dir ) ) 16 | { 17 | $this->_exec("cd $tmp_dir && git pull"); 18 | } 19 | else 20 | { 21 | $this->_exec("git clone $repo $tmp_dir"); 22 | } 23 | $this->_exec("cd $tmp_dir && cp -rf ./* /Users/Easy/Code/gitcode/MemberPrism2/ && cp -rf ./.vscode /Users/Easy/Code/gitcode/MemberPrism2/ && cp -rf ./.vscode /Users/Easy/Code/gitcode/MemberPrism2/ && cd /Users/Easy/Code/gitcode/MemberPrism2/ && git add . && git commit -m '$note' && git push"); 24 | 25 | } 26 | } -------------------------------------------------------------------------------- /docker-compose.yaml: -------------------------------------------------------------------------------- 1 | version: '2' 2 | services: 3 | mariadb: 4 | image: 'mariadb:10.5.8-focal' 5 | volumes: 6 | - 'mariadb_data:/var/lib/mysql' 7 | environment: 8 | - MYSQL_ROOT_PASSWORD=theVeryp@ssw0rd 9 | - MYSQL_DATABASE=prism 10 | ports: 11 | - '3306:3306' 12 | app: 13 | #image: 'webdevops/php-apache:8.0-alpine' 14 | build: './docker/web/' 15 | ports: 16 | - '8801:80' 17 | - '8802:8802' 18 | - '443:443' 19 | volumes: 20 | - './:/app' 21 | depends_on: 22 | - mariadb 23 | environment: 24 | - DB_HOST=mariadb 25 | - DB_PORT=3306 26 | - DB_USERNAME=root 27 | - DB_DATABASE=prism 28 | - DB_PASSWORD=theVeryp@ssw0rd 29 | volumes: 30 | mariadb_data: -------------------------------------------------------------------------------- /docker/web/dockerfile: -------------------------------------------------------------------------------- 1 | FROM webdevops/php-apache:8.0-alpine 2 | 3 | # 首先配置 vhost 4 | COPY vhost.conf /opt/docker/etc/httpd/vhost.conf 5 | # COPY web.vhost.conf /opt/docker/etc/httpd/vhost.common.d/ 6 | 7 | # 然后运行初始化脚本 8 | # https://dockerfile.readthedocs.io/en/latest/content/Customization/provisioning.html 9 | COPY init.sh /opt/docker/provision/entrypoint.d/ 10 | #CMD chmod +x /opt/docker/provision/entrypoint.d/init.sh 11 | ADD supervisord-proxy.conf /opt/docker/etc/supervisor.d/prism-proxy.conf 12 | 13 | EXPOSE 80 14 | EXPOSE 8802 15 | 16 | -------------------------------------------------------------------------------- /docker/web/init.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | # 初始化 laravel 4 | cd /app/web && composer install && cp .env.example .env && php artisan key:generate && php artisan migrate --seed 5 | 6 | chmod -R 0777 /app/web/storage 7 | chmod -R 0777 /app/web/bootstrap/cache/ 8 | 9 | chmod -R 0777 /app/proxy/ 10 | chmod +x /app/proxy/server-linux 11 | 12 | # 启动 proxy 13 | # 已经设置为 deamon 14 | # cd /app/proxy && ./server-linux & -------------------------------------------------------------------------------- /docker/web/supervisord-proxy.conf: -------------------------------------------------------------------------------- 1 | [group:prism-proxy] 2 | programs=prism-proxy 3 | priority=20 4 | 5 | [program:prism-proxy] 6 | process_name=%(program_name)s 7 | command=/bin/bash -c 'cd /app/proxy/ && ./server-linux' 8 | autostart=true 9 | startretries=10 10 | autorestart=true 11 | priority=1 12 | redirect_stderr=true 13 | stdout_logfile=/proxy.log -------------------------------------------------------------------------------- /docker/web/vhost.conf: -------------------------------------------------------------------------------- 1 | ####################################### 2 | # Vhost 3 | ####################################### 4 | 5 | 6 | ServerName docker.vm 7 | ServerAlias *.vm 8 | DocumentRoot "/app/web/public" 9 | 10 | Options FollowSymLinks MultiViews 11 | AllowOverride All 12 | Order allow,deny 13 | allow from all 14 | 15 | 16 | 17 | 18 | ServerName docker.vm 19 | ServerAlias *.vm 20 | DocumentRoot "/app/web/public" 21 | 22 | Options FollowSymLinks MultiViews 23 | AllowOverride All 24 | Order allow,deny 25 | allow from all 26 | 27 | #SSLEngine on 28 | #SSLCertificateFile /app/ssl/server.crt 29 | #SSLCertificateKeyFile /app/ssl/server.key 30 | 31 | -------------------------------------------------------------------------------- /docs/.gitignore: -------------------------------------------------------------------------------- 1 | node_modules -------------------------------------------------------------------------------- /docs/.vuepress/config.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | locales: { 3 | '/': { 4 | lang: 'en-US', // 将会被设置为 的 lang 属性 5 | title: 'MemberPrism', 6 | description: 'Open source alternative to memberstack / memberspace , but with both front and backend member-only content protection' 7 | }, 8 | '/zh/': { 9 | lang: 'zh-CN', 10 | title: 'MemberPrism', 11 | description: '类似 memberstack 或 memberspace 的开源实现,但对会员内容同时提供了前端和后端的双重保护' 12 | } 13 | }, 14 | themeConfig: { 15 | sidebar:'auto', 16 | nav: [ 17 | { text: 'Home', link: '/' }, 18 | { text: 'GitHub', link: 'https://github.com/easychen/member-prism' }, 19 | ] 20 | } 21 | } -------------------------------------------------------------------------------- /docs/.vuepress/dist/404.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | MemberPrism 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 |

404

How did we get here?
16 | Take me home. 17 |
18 | 19 | 20 | 21 | -------------------------------------------------------------------------------- /docs/.vuepress/dist/assets/img/0990f53c83bb94a02d8885a973aa6736f1de022081d874f3556ea40ff6abad83.f8b9e053.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/easychen/MemberPrism2/6ca545d918dea8c4b6170d0ff2303883aac0ad9f/docs/.vuepress/dist/assets/img/0990f53c83bb94a02d8885a973aa6736f1de022081d874f3556ea40ff6abad83.f8b9e053.png -------------------------------------------------------------------------------- /docs/.vuepress/dist/assets/img/20014d5f04411184559b7a49ca1300a9b95e867acda5b7942524f035e054a28b.0f836819.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/easychen/MemberPrism2/6ca545d918dea8c4b6170d0ff2303883aac0ad9f/docs/.vuepress/dist/assets/img/20014d5f04411184559b7a49ca1300a9b95e867acda5b7942524f035e054a28b.0f836819.png -------------------------------------------------------------------------------- /docs/.vuepress/dist/assets/img/38d04db14246b8c3008d303da81563dab3ba4a2db6949d77951f550c23735e6d.d38183b9.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/easychen/MemberPrism2/6ca545d918dea8c4b6170d0ff2303883aac0ad9f/docs/.vuepress/dist/assets/img/38d04db14246b8c3008d303da81563dab3ba4a2db6949d77951f550c23735e6d.d38183b9.png -------------------------------------------------------------------------------- /docs/.vuepress/dist/assets/img/42b313a369564f02f6cc7afb400de46e6d08e6d25cf2df0802babdec3263866b.1caaebb8.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/easychen/MemberPrism2/6ca545d918dea8c4b6170d0ff2303883aac0ad9f/docs/.vuepress/dist/assets/img/42b313a369564f02f6cc7afb400de46e6d08e6d25cf2df0802babdec3263866b.1caaebb8.png -------------------------------------------------------------------------------- /docs/.vuepress/dist/assets/img/43bd995595b8e51e9fe3eb9a0bf133e348b01336310e92530319197e9fddd701.d5e94f17.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/easychen/MemberPrism2/6ca545d918dea8c4b6170d0ff2303883aac0ad9f/docs/.vuepress/dist/assets/img/43bd995595b8e51e9fe3eb9a0bf133e348b01336310e92530319197e9fddd701.d5e94f17.png -------------------------------------------------------------------------------- /docs/.vuepress/dist/assets/img/48fa4dbf28e3314719f9b69e246ca7bd9f397f64311a11e799e44524aaf46dee.75964ac4.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/easychen/MemberPrism2/6ca545d918dea8c4b6170d0ff2303883aac0ad9f/docs/.vuepress/dist/assets/img/48fa4dbf28e3314719f9b69e246ca7bd9f397f64311a11e799e44524aaf46dee.75964ac4.png -------------------------------------------------------------------------------- /docs/.vuepress/dist/assets/img/548d567dbab937cd2c77d9bce2e62132c91df20b2c4f027aa7871136fdf7f2ac.a95fb44a.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/easychen/MemberPrism2/6ca545d918dea8c4b6170d0ff2303883aac0ad9f/docs/.vuepress/dist/assets/img/548d567dbab937cd2c77d9bce2e62132c91df20b2c4f027aa7871136fdf7f2ac.a95fb44a.png -------------------------------------------------------------------------------- /docs/.vuepress/dist/assets/img/54fc0a9b336b290c16ebf4375808f1c6a15b8fe7f916073893680fce7077950c.38e22bbb.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/easychen/MemberPrism2/6ca545d918dea8c4b6170d0ff2303883aac0ad9f/docs/.vuepress/dist/assets/img/54fc0a9b336b290c16ebf4375808f1c6a15b8fe7f916073893680fce7077950c.38e22bbb.png -------------------------------------------------------------------------------- /docs/.vuepress/dist/assets/img/554aaa554abae4a604986b38b3fbd9900e29aa433448adcb58188a351b3a1ec7.dfacdb2f.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/easychen/MemberPrism2/6ca545d918dea8c4b6170d0ff2303883aac0ad9f/docs/.vuepress/dist/assets/img/554aaa554abae4a604986b38b3fbd9900e29aa433448adcb58188a351b3a1ec7.dfacdb2f.png -------------------------------------------------------------------------------- /docs/.vuepress/dist/assets/img/5d1e579c1b1b685a759be5201f47a734ee568cc012d068cd18b6b0f533927104.18804581.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/easychen/MemberPrism2/6ca545d918dea8c4b6170d0ff2303883aac0ad9f/docs/.vuepress/dist/assets/img/5d1e579c1b1b685a759be5201f47a734ee568cc012d068cd18b6b0f533927104.18804581.png -------------------------------------------------------------------------------- /docs/.vuepress/dist/assets/img/5d488deee54ed0b05ffdb6fd5f5642e33bbefa4a441bb81c8a072cc681598c8e.1becf902.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/easychen/MemberPrism2/6ca545d918dea8c4b6170d0ff2303883aac0ad9f/docs/.vuepress/dist/assets/img/5d488deee54ed0b05ffdb6fd5f5642e33bbefa4a441bb81c8a072cc681598c8e.1becf902.png -------------------------------------------------------------------------------- /docs/.vuepress/dist/assets/img/817daf704b66a572f2c3b163320abbc0508b2dd200f7dfd36ab294f58e0e19e0.e3533ad6.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/easychen/MemberPrism2/6ca545d918dea8c4b6170d0ff2303883aac0ad9f/docs/.vuepress/dist/assets/img/817daf704b66a572f2c3b163320abbc0508b2dd200f7dfd36ab294f58e0e19e0.e3533ad6.png -------------------------------------------------------------------------------- /docs/.vuepress/dist/assets/img/92ade1fe984e3dded00156cbe7920d0de9d243f262b1a03c902583552ac61a03.f94908c0.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/easychen/MemberPrism2/6ca545d918dea8c4b6170d0ff2303883aac0ad9f/docs/.vuepress/dist/assets/img/92ade1fe984e3dded00156cbe7920d0de9d243f262b1a03c902583552ac61a03.f94908c0.png -------------------------------------------------------------------------------- /docs/.vuepress/dist/assets/img/9a3fe6dce02bd6cc8484f711f949c16e42c35e84d13d4ea0ace536dc7122f750.969bde3b.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/easychen/MemberPrism2/6ca545d918dea8c4b6170d0ff2303883aac0ad9f/docs/.vuepress/dist/assets/img/9a3fe6dce02bd6cc8484f711f949c16e42c35e84d13d4ea0ace536dc7122f750.969bde3b.png -------------------------------------------------------------------------------- /docs/.vuepress/dist/assets/img/a1cac133c49ff024b2024d1a78d2da451d1e58d3c827651e4dc62090d9e87183.488a5649.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/easychen/MemberPrism2/6ca545d918dea8c4b6170d0ff2303883aac0ad9f/docs/.vuepress/dist/assets/img/a1cac133c49ff024b2024d1a78d2da451d1e58d3c827651e4dc62090d9e87183.488a5649.png -------------------------------------------------------------------------------- /docs/.vuepress/dist/assets/img/b217c0402e8f49361a24080b2c91e28f1a24c0b26760aaf89871ac00a0949c9e.01cd5750.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/easychen/MemberPrism2/6ca545d918dea8c4b6170d0ff2303883aac0ad9f/docs/.vuepress/dist/assets/img/b217c0402e8f49361a24080b2c91e28f1a24c0b26760aaf89871ac00a0949c9e.01cd5750.png -------------------------------------------------------------------------------- /docs/.vuepress/dist/assets/img/e5629dd0d0718e6844a29966eaa1be70ff69f6d540b9df73fb512a8455a1faaf.5892a7f7.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/easychen/MemberPrism2/6ca545d918dea8c4b6170d0ff2303883aac0ad9f/docs/.vuepress/dist/assets/img/e5629dd0d0718e6844a29966eaa1be70ff69f6d540b9df73fb512a8455a1faaf.5892a7f7.png -------------------------------------------------------------------------------- /docs/.vuepress/dist/assets/img/e9789275ff88333a7d004b0d1a2833fa8cc226882ea63b830e39295e1e1b5412.424e7ba4.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/easychen/MemberPrism2/6ca545d918dea8c4b6170d0ff2303883aac0ad9f/docs/.vuepress/dist/assets/img/e9789275ff88333a7d004b0d1a2833fa8cc226882ea63b830e39295e1e1b5412.424e7ba4.png -------------------------------------------------------------------------------- /docs/.vuepress/dist/assets/img/f78cb444544a6985f61463396f626bde993a360412463d1726af53250fe548f4.82c20197.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/easychen/MemberPrism2/6ca545d918dea8c4b6170d0ff2303883aac0ad9f/docs/.vuepress/dist/assets/img/f78cb444544a6985f61463396f626bde993a360412463d1726af53250fe548f4.82c20197.png -------------------------------------------------------------------------------- /docs/.vuepress/dist/assets/img/f88288b384cfd27d55d410ba9015be1d1b32ed6cf060b46f73dae23f0f497fc4.49898d8d.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/easychen/MemberPrism2/6ca545d918dea8c4b6170d0ff2303883aac0ad9f/docs/.vuepress/dist/assets/img/f88288b384cfd27d55d410ba9015be1d1b32ed6cf060b46f73dae23f0f497fc4.49898d8d.png -------------------------------------------------------------------------------- /docs/.vuepress/dist/assets/img/fc8073dcebc631393515945821d4c101d32b80aaea1c399e59c47eeb94ef1b68.ecb0794d.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/easychen/MemberPrism2/6ca545d918dea8c4b6170d0ff2303883aac0ad9f/docs/.vuepress/dist/assets/img/fc8073dcebc631393515945821d4c101d32b80aaea1c399e59c47eeb94ef1b68.ecb0794d.png -------------------------------------------------------------------------------- /docs/.vuepress/dist/assets/img/fcfe6c149194be94f11e7de6691f6de6e09593fe10f305c7dac2c1a3853251e2.197c7d78.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/easychen/MemberPrism2/6ca545d918dea8c4b6170d0ff2303883aac0ad9f/docs/.vuepress/dist/assets/img/fcfe6c149194be94f11e7de6691f6de6e09593fe10f305c7dac2c1a3853251e2.197c7d78.png -------------------------------------------------------------------------------- /docs/.vuepress/dist/assets/img/ff792949fdb0d8359b1f20a1db6763d23430e8d94b58180f38be1342f8ccce0c.33b19f02.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/easychen/MemberPrism2/6ca545d918dea8c4b6170d0ff2303883aac0ad9f/docs/.vuepress/dist/assets/img/ff792949fdb0d8359b1f20a1db6763d23430e8d94b58180f38be1342f8ccce0c.33b19f02.png -------------------------------------------------------------------------------- /docs/.vuepress/dist/assets/img/search.83621669.svg: -------------------------------------------------------------------------------- 1 | 2 | -------------------------------------------------------------------------------- /docs/.vuepress/dist/assets/js/5.f7ea1688.js: -------------------------------------------------------------------------------- 1 | (window.webpackJsonp=window.webpackJsonp||[]).push([[5],{359:function(t,e,n){},388:function(t,e,n){"use strict";n(359)},397:function(t,e,n){"use strict";n.r(e);var i={functional:!0,props:{type:{type:String,default:"tip"},text:String,vertical:{type:String,default:"top"}},render:function(t,e){var n=e.props,i=e.slots;return t("span",{class:["badge",n.type],style:{verticalAlign:n.vertical}},n.text||i().default)}},r=(n(388),n(40)),p=Object(r.a)(i,void 0,void 0,!1,null,"15b7b770",null);e.default=p.exports}}]); -------------------------------------------------------------------------------- /docs/.vuepress/dist/assets/js/6.a65e5dd2.js: -------------------------------------------------------------------------------- 1 | (window.webpackJsonp=window.webpackJsonp||[]).push([[6],{361:function(t,e,a){},390:function(t,e,a){"use strict";a(361)},394:function(t,e,a){"use strict";a.r(e);var n={name:"CodeBlock",props:{title:{type:String,required:!0},active:{type:Boolean,default:!1}},mounted:function(){this.$parent&&this.$parent.loadTabs&&this.$parent.loadTabs()}},i=(a(390),a(40)),s=Object(i.a)(n,(function(){var t=this.$createElement;return(this._self._c||t)("div",{staticClass:"theme-code-block",class:{"theme-code-block__active":this.active}},[this._t("default")],2)}),[],!1,null,"759a7d02",null);e.default=s.exports}}]); -------------------------------------------------------------------------------- /docs/.vuepress/dist/assets/js/7.1eab93e4.js: -------------------------------------------------------------------------------- 1 | (window.webpackJsonp=window.webpackJsonp||[]).push([[7],{360:function(e,t,a){},389:function(e,t,a){"use strict";a(360)},393:function(e,t,a){"use strict";a.r(t);a(63),a(27),a(94),a(95);var o={name:"CodeGroup",data:function(){return{codeTabs:[],activeCodeTabIndex:-1}},watch:{activeCodeTabIndex:function(e){this.activateCodeTab(e)}},mounted:function(){this.loadTabs()},methods:{changeCodeTab:function(e){this.activeCodeTabIndex=e},loadTabs:function(){var e=this;this.codeTabs=(this.$slots.default||[]).filter((function(e){return Boolean(e.componentOptions)})).map((function(t,a){return""===t.componentOptions.propsData.active&&(e.activeCodeTabIndex=a),{title:t.componentOptions.propsData.title,elm:t.elm}})),-1===this.activeCodeTabIndex&&this.codeTabs.length>0&&(this.activeCodeTabIndex=0),this.activateCodeTab(0)},activateCodeTab:function(e){this.codeTabs.forEach((function(e){e.elm&&e.elm.classList.remove("theme-code-block__active")})),this.codeTabs[e].elm&&this.codeTabs[e].elm.classList.add("theme-code-block__active")}}},n=(a(389),a(40)),c=Object(n.a)(o,(function(){var e=this,t=e.$createElement,a=e._self._c||t;return a("ClientOnly",[a("div",{staticClass:"theme-code-group"},[a("div",{staticClass:"theme-code-group__nav"},[a("ul",{staticClass:"theme-code-group__ul"},e._l(e.codeTabs,(function(t,o){return a("li",{key:t.title,staticClass:"theme-code-group__li"},[a("button",{staticClass:"theme-code-group__nav-tab",class:{"theme-code-group__nav-tab-active":o===e.activeCodeTabIndex},on:{click:function(t){return e.changeCodeTab(o)}}},[e._v("\n "+e._s(t.title)+"\n ")])])})),0)]),e._v(" "),e._t("default"),e._v(" "),e.codeTabs.length<1?a("pre",{staticClass:"pre-blank"},[e._v("// Make sure to add code blocks to your code group")]):e._e()],2)])}),[],!1,null,"deefee04",null);t.default=c.exports}}]); -------------------------------------------------------------------------------- /docs/.vuepress/dist/assets/js/8.4e522548.js: -------------------------------------------------------------------------------- 1 | (window.webpackJsonp=window.webpackJsonp||[]).push([[8],{392:function(t,e,s){"use strict";s.r(e);var n=["There's nothing here.","How did we get here?","That's a Four-Oh-Four.","Looks like we've got some broken links."],o={methods:{getMsg:function(){return n[Math.floor(Math.random()*n.length)]}}},i=s(40),h=Object(i.a)(o,(function(){var t=this.$createElement,e=this._self._c||t;return e("div",{staticClass:"theme-container"},[e("div",{staticClass:"theme-default-content"},[e("h1",[this._v("404")]),this._v(" "),e("blockquote",[this._v(this._s(this.getMsg()))]),this._v(" "),e("RouterLink",{attrs:{to:"/"}},[this._v("\n Take me home.\n ")])],1)])}),[],!1,null,null,null);e.default=h.exports}}]); -------------------------------------------------------------------------------- /docs/images/0990f53c83bb94a02d8885a973aa6736f1de022081d874f3556ea40ff6abad83.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/easychen/MemberPrism2/6ca545d918dea8c4b6170d0ff2303883aac0ad9f/docs/images/0990f53c83bb94a02d8885a973aa6736f1de022081d874f3556ea40ff6abad83.png -------------------------------------------------------------------------------- /docs/images/20014d5f04411184559b7a49ca1300a9b95e867acda5b7942524f035e054a28b.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/easychen/MemberPrism2/6ca545d918dea8c4b6170d0ff2303883aac0ad9f/docs/images/20014d5f04411184559b7a49ca1300a9b95e867acda5b7942524f035e054a28b.png -------------------------------------------------------------------------------- /docs/images/38d04db14246b8c3008d303da81563dab3ba4a2db6949d77951f550c23735e6d.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/easychen/MemberPrism2/6ca545d918dea8c4b6170d0ff2303883aac0ad9f/docs/images/38d04db14246b8c3008d303da81563dab3ba4a2db6949d77951f550c23735e6d.png -------------------------------------------------------------------------------- /docs/images/42b313a369564f02f6cc7afb400de46e6d08e6d25cf2df0802babdec3263866b.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/easychen/MemberPrism2/6ca545d918dea8c4b6170d0ff2303883aac0ad9f/docs/images/42b313a369564f02f6cc7afb400de46e6d08e6d25cf2df0802babdec3263866b.png -------------------------------------------------------------------------------- /docs/images/43bd995595b8e51e9fe3eb9a0bf133e348b01336310e92530319197e9fddd701.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/easychen/MemberPrism2/6ca545d918dea8c4b6170d0ff2303883aac0ad9f/docs/images/43bd995595b8e51e9fe3eb9a0bf133e348b01336310e92530319197e9fddd701.png -------------------------------------------------------------------------------- /docs/images/48fa4dbf28e3314719f9b69e246ca7bd9f397f64311a11e799e44524aaf46dee.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/easychen/MemberPrism2/6ca545d918dea8c4b6170d0ff2303883aac0ad9f/docs/images/48fa4dbf28e3314719f9b69e246ca7bd9f397f64311a11e799e44524aaf46dee.png -------------------------------------------------------------------------------- /docs/images/548d567dbab937cd2c77d9bce2e62132c91df20b2c4f027aa7871136fdf7f2ac.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/easychen/MemberPrism2/6ca545d918dea8c4b6170d0ff2303883aac0ad9f/docs/images/548d567dbab937cd2c77d9bce2e62132c91df20b2c4f027aa7871136fdf7f2ac.png -------------------------------------------------------------------------------- /docs/images/54fc0a9b336b290c16ebf4375808f1c6a15b8fe7f916073893680fce7077950c.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/easychen/MemberPrism2/6ca545d918dea8c4b6170d0ff2303883aac0ad9f/docs/images/54fc0a9b336b290c16ebf4375808f1c6a15b8fe7f916073893680fce7077950c.png -------------------------------------------------------------------------------- /docs/images/554aaa554abae4a604986b38b3fbd9900e29aa433448adcb58188a351b3a1ec7.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/easychen/MemberPrism2/6ca545d918dea8c4b6170d0ff2303883aac0ad9f/docs/images/554aaa554abae4a604986b38b3fbd9900e29aa433448adcb58188a351b3a1ec7.png -------------------------------------------------------------------------------- /docs/images/5d1e579c1b1b685a759be5201f47a734ee568cc012d068cd18b6b0f533927104.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/easychen/MemberPrism2/6ca545d918dea8c4b6170d0ff2303883aac0ad9f/docs/images/5d1e579c1b1b685a759be5201f47a734ee568cc012d068cd18b6b0f533927104.png -------------------------------------------------------------------------------- /docs/images/5d488deee54ed0b05ffdb6fd5f5642e33bbefa4a441bb81c8a072cc681598c8e.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/easychen/MemberPrism2/6ca545d918dea8c4b6170d0ff2303883aac0ad9f/docs/images/5d488deee54ed0b05ffdb6fd5f5642e33bbefa4a441bb81c8a072cc681598c8e.png -------------------------------------------------------------------------------- /docs/images/817daf704b66a572f2c3b163320abbc0508b2dd200f7dfd36ab294f58e0e19e0.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/easychen/MemberPrism2/6ca545d918dea8c4b6170d0ff2303883aac0ad9f/docs/images/817daf704b66a572f2c3b163320abbc0508b2dd200f7dfd36ab294f58e0e19e0.png -------------------------------------------------------------------------------- /docs/images/92ade1fe984e3dded00156cbe7920d0de9d243f262b1a03c902583552ac61a03.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/easychen/MemberPrism2/6ca545d918dea8c4b6170d0ff2303883aac0ad9f/docs/images/92ade1fe984e3dded00156cbe7920d0de9d243f262b1a03c902583552ac61a03.png -------------------------------------------------------------------------------- /docs/images/9a3fe6dce02bd6cc8484f711f949c16e42c35e84d13d4ea0ace536dc7122f750.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/easychen/MemberPrism2/6ca545d918dea8c4b6170d0ff2303883aac0ad9f/docs/images/9a3fe6dce02bd6cc8484f711f949c16e42c35e84d13d4ea0ace536dc7122f750.png -------------------------------------------------------------------------------- /docs/images/a1cac133c49ff024b2024d1a78d2da451d1e58d3c827651e4dc62090d9e87183.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/easychen/MemberPrism2/6ca545d918dea8c4b6170d0ff2303883aac0ad9f/docs/images/a1cac133c49ff024b2024d1a78d2da451d1e58d3c827651e4dc62090d9e87183.png -------------------------------------------------------------------------------- /docs/images/ac6800e6f4dddef7b963b6c14346a46e0183c63186d5ed90de979f7958968105.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/easychen/MemberPrism2/6ca545d918dea8c4b6170d0ff2303883aac0ad9f/docs/images/ac6800e6f4dddef7b963b6c14346a46e0183c63186d5ed90de979f7958968105.png -------------------------------------------------------------------------------- /docs/images/b217c0402e8f49361a24080b2c91e28f1a24c0b26760aaf89871ac00a0949c9e.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/easychen/MemberPrism2/6ca545d918dea8c4b6170d0ff2303883aac0ad9f/docs/images/b217c0402e8f49361a24080b2c91e28f1a24c0b26760aaf89871ac00a0949c9e.png -------------------------------------------------------------------------------- /docs/images/e5629dd0d0718e6844a29966eaa1be70ff69f6d540b9df73fb512a8455a1faaf.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/easychen/MemberPrism2/6ca545d918dea8c4b6170d0ff2303883aac0ad9f/docs/images/e5629dd0d0718e6844a29966eaa1be70ff69f6d540b9df73fb512a8455a1faaf.png -------------------------------------------------------------------------------- /docs/images/e9789275ff88333a7d004b0d1a2833fa8cc226882ea63b830e39295e1e1b5412.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/easychen/MemberPrism2/6ca545d918dea8c4b6170d0ff2303883aac0ad9f/docs/images/e9789275ff88333a7d004b0d1a2833fa8cc226882ea63b830e39295e1e1b5412.png -------------------------------------------------------------------------------- /docs/images/f78cb444544a6985f61463396f626bde993a360412463d1726af53250fe548f4.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/easychen/MemberPrism2/6ca545d918dea8c4b6170d0ff2303883aac0ad9f/docs/images/f78cb444544a6985f61463396f626bde993a360412463d1726af53250fe548f4.png -------------------------------------------------------------------------------- /docs/images/f88288b384cfd27d55d410ba9015be1d1b32ed6cf060b46f73dae23f0f497fc4.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/easychen/MemberPrism2/6ca545d918dea8c4b6170d0ff2303883aac0ad9f/docs/images/f88288b384cfd27d55d410ba9015be1d1b32ed6cf060b46f73dae23f0f497fc4.png -------------------------------------------------------------------------------- /docs/images/fc8073dcebc631393515945821d4c101d32b80aaea1c399e59c47eeb94ef1b68.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/easychen/MemberPrism2/6ca545d918dea8c4b6170d0ff2303883aac0ad9f/docs/images/fc8073dcebc631393515945821d4c101d32b80aaea1c399e59c47eeb94ef1b68.png -------------------------------------------------------------------------------- /docs/images/fcfe6c149194be94f11e7de6691f6de6e09593fe10f305c7dac2c1a3853251e2.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/easychen/MemberPrism2/6ca545d918dea8c4b6170d0ff2303883aac0ad9f/docs/images/fcfe6c149194be94f11e7de6691f6de6e09593fe10f305c7dac2c1a3853251e2.png -------------------------------------------------------------------------------- /docs/images/ff792949fdb0d8359b1f20a1db6763d23430e8d94b58180f38be1342f8ccce0c.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/easychen/MemberPrism2/6ca545d918dea8c4b6170d0ff2303883aac0ad9f/docs/images/ff792949fdb0d8359b1f20a1db6763d23430e8d94b58180f38be1342f8ccce0c.png -------------------------------------------------------------------------------- /docs/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "docs", 3 | "version": "1.0.0", 4 | "main": "index.js", 5 | "license": "MIT", 6 | "devDependencies": { 7 | "vuepress": "^1.8.2" 8 | }, 9 | "scripts": { 10 | "docs:dev": "vuepress dev ./", 11 | "docs:build": "vuepress build ./" 12 | } 13 | } 14 | -------------------------------------------------------------------------------- /images/7ff8a6db94c5aa55be336a84bcab53c5c198d33919179a77df37dc7e593b4574.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/easychen/MemberPrism2/6ca545d918dea8c4b6170d0ff2303883aac0ad9f/images/7ff8a6db94c5aa55be336a84bcab53c5c198d33919179a77df37dc7e593b4574.png -------------------------------------------------------------------------------- /proxy/RoboFile.php: -------------------------------------------------------------------------------- 1 | _exec("GOOS=linux GOARCH=amd64 go build -o server-linux server.go "); 13 | $this->_exec("GOOS=darwin GOARCH=amd64 go build -o server-mac server.go"); 14 | $this->_exec("cp -f server-linux /Users/Easy/Playground/prism2/proxy/server-linux"); 15 | } 16 | } -------------------------------------------------------------------------------- /proxy/go.mod: -------------------------------------------------------------------------------- 1 | module github.com/easychen/mirror 2 | 3 | go 1.15 4 | 5 | require ( 6 | github.com/dgrijalva/jwt-go v3.2.0+incompatible 7 | github.com/fsnotify/fsnotify v1.4.9 8 | github.com/gin-gonic/gin v1.6.3 9 | github.com/go-playground/assert/v2 v2.0.1 10 | github.com/spf13/viper v1.7.1 11 | ) 12 | -------------------------------------------------------------------------------- /proxy/proxy/assets/rules.json: -------------------------------------------------------------------------------- 1 | { 2 | "1": { 3 | "content_id": "1", 4 | "path": "/vip1", 5 | "match": 1, 6 | "redir": "/forbidden.html" 7 | }, 8 | "2": { 9 | "content_id": "2", 10 | "path": "/vip2", 11 | "match": 1, 12 | "redir": "/forbidden.html" 13 | }, 14 | "3": { 15 | "content_id": "3", 16 | "path": "/vip3", 17 | "match": 1, 18 | "redir": "/forbidden.html" 19 | }, 20 | "4": { 21 | "content_id": "4", 22 | "path": "/vip3", 23 | "match": 1, 24 | "redir": "/forbidden.html" 25 | } 26 | } 27 | -------------------------------------------------------------------------------- /proxy/proxy/assets/settings.json: -------------------------------------------------------------------------------- 1 | { 2 | "debug": false, 3 | "secret": "hello-im-secret", 4 | "target": "http://127.0.0.1:9000", 5 | "host": "127.0.0.1", 6 | "port": 10086, 7 | "https": false, 8 | "key": "server.key", 9 | "crt": "server.crt" 10 | } 11 | -------------------------------------------------------------------------------- /proxy/proxy/types.go: -------------------------------------------------------------------------------- 1 | package proxy 2 | 3 | import ( 4 | "net/http/httputil" 5 | 6 | "github.com/dgrijalva/jwt-go" 7 | ) 8 | 9 | // Proxy 代理 10 | type Proxy struct { 11 | httputil.ReverseProxy 12 | } 13 | 14 | // Settings 配置文件 15 | type Settings struct { 16 | Debug *bool `json:"debug,omitempty"` // 调试模式 17 | Secret string `json:"secret"` 18 | Target string `json:"target"` 19 | Host string `json:"host"` // 服务监听地址 20 | Port int `json:"int"` // 服务地址 21 | HTTPSEnable bool `json:"https_enable"` // HTTPS 证书KEY文件 22 | Key string `json:"key,omitempty"` // HTTPS 证书KEY文件 23 | Crt string `json:"crt,omitempty"` // HTTPS 证书文件 24 | } 25 | 26 | // Rule 规则配置 27 | type Rule struct { 28 | ContentID string `json:"content_id"` 29 | Path string `json:"path"` 30 | Match int `json:"match"` 31 | Redir string `json:"redir"` 32 | } 33 | 34 | // Rules 规则配置 35 | type Rules map[string]Rule 36 | 37 | // Claims JWTClaims 38 | type Claims struct { 39 | ContentIDs []string `json:"content_ids"` 40 | Level int `json:"level"` 41 | UID int `json:"uid"` 42 | Expire string `json:"expire"` 43 | jwt.StandardClaims 44 | } 45 | -------------------------------------------------------------------------------- /proxy/rules.json: -------------------------------------------------------------------------------- 1 | {"1":{"content_id":"1","path":"\/vip","match":1,"redir":"\/forbidden.html"},"2":{"content_id":"2","path":"\/gogog","match":1,"redir":"\/forbidden.html"},"3":{"content_id":"3","path":"\/vip2","match":1,"redir":"\/forbidden.html"}} -------------------------------------------------------------------------------- /proxy/server: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/easychen/MemberPrism2/6ca545d918dea8c4b6170d0ff2303883aac0ad9f/proxy/server -------------------------------------------------------------------------------- /proxy/server-linux: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/easychen/MemberPrism2/6ca545d918dea8c4b6170d0ff2303883aac0ad9f/proxy/server-linux -------------------------------------------------------------------------------- /proxy/server-mac: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/easychen/MemberPrism2/6ca545d918dea8c4b6170d0ff2303883aac0ad9f/proxy/server-mac -------------------------------------------------------------------------------- /proxy/server.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "fmt" 5 | "log" 6 | 7 | "github.com/easychen/mirror/proxy" 8 | ) 9 | 10 | func main() { 11 | 12 | r, settings, rules := proxy.Setup(proxy.ServerRouters) 13 | fmt.Println("Rules:") 14 | for i, rule := range *rules { 15 | fmt.Printf("[%s] %s %s, %d \n", i, rule.Path, rule.Redir, rule.Match) 16 | } 17 | 18 | fmt.Println("Settings:") 19 | fmt.Printf( 20 | "Host: %s:%d > TargetHost: %s, httpsEnable: %v \n", 21 | settings.Host, settings.Port, settings.Target, settings.HTTPSEnable, 22 | ) 23 | 24 | // 启动服务 25 | var err error 26 | if settings.HTTPSEnable { 27 | err = r.RunTLS(fmt.Sprintf("%s:%d", settings.Host, settings.Port), settings.Crt, settings.Key) 28 | } else { 29 | err = r.Run(fmt.Sprintf("%s:%d", settings.Host, settings.Port)) 30 | } 31 | 32 | if err != nil { 33 | log.Fatal(err) 34 | } 35 | } 36 | -------------------------------------------------------------------------------- /proxy/settings.json: -------------------------------------------------------------------------------- 1 | {"debug":false,"secret":"4137hu82RGHFE4854hgh","target":"http:\/\/localhost:8008","host":"0.0.0.0","port":"8802","https":false,"key":"\/app\/ssl\/server.key","crt":"\/app\/ssl\/server.crt"} -------------------------------------------------------------------------------- /web/.editorconfig: -------------------------------------------------------------------------------- 1 | root = true 2 | 3 | [*] 4 | charset = utf-8 5 | end_of_line = lf 6 | insert_final_newline = true 7 | indent_style = space 8 | indent_size = 4 9 | trim_trailing_whitespace = true 10 | 11 | [*.md] 12 | trim_trailing_whitespace = false 13 | 14 | [*.{yml,yaml}] 15 | indent_size = 2 16 | -------------------------------------------------------------------------------- /web/.env.example: -------------------------------------------------------------------------------- 1 | APP_NAME=MemberPrism 2 | APP_ENV=local 3 | APP_KEY= 4 | APP_DEBUG=true 5 | APP_URL=http://localhost 6 | 7 | # big logo in front page 8 | APP_LOGO_URL=/img/brand.svg 9 | # small icon left top 10 | APP_ICON_URL=/img/logo.svg 11 | 12 | APP_TITLE="Welcome to Member prism!" 13 | APP_DETAIL="MemberPrism brings plug-in membership system and subscription management to the modern Web, which uses both front-end and back-end protection to make your member-only content more secure." 14 | 15 | LOG_CHANNEL=stack 16 | LOG_LEVEL=debug 17 | 18 | DB_CONNECTION=mysql 19 | DB_HOST=mariadb 20 | DB_PORT=3306 21 | DB_DATABASE=prism 22 | DB_USERNAME=root 23 | DB_PASSWORD=theVeryp@ssw0rd 24 | 25 | BROADCAST_DRIVER=log 26 | CACHE_DRIVER=file 27 | QUEUE_CONNECTION=sync 28 | SESSION_DRIVER=database 29 | SESSION_LIFETIME=120 30 | 31 | MEMCACHED_HOST=127.0.0.1 32 | 33 | REDIS_HOST=127.0.0.1 34 | REDIS_PASSWORD=null 35 | REDIS_PORT=6379 36 | 37 | MAIL_MAILER=smtp 38 | MAIL_HOST=mailhog 39 | MAIL_PORT=1025 40 | MAIL_USERNAME=null 41 | MAIL_PASSWORD=null 42 | MAIL_ENCRYPTION=null 43 | MAIL_FROM_ADDRESS=null 44 | MAIL_FROM_NAME="${APP_NAME}" 45 | 46 | AWS_ACCESS_KEY_ID= 47 | AWS_SECRET_ACCESS_KEY= 48 | AWS_DEFAULT_REGION=us-east-1 49 | AWS_BUCKET= 50 | 51 | PUSHER_APP_ID= 52 | PUSHER_APP_KEY= 53 | PUSHER_APP_SECRET= 54 | PUSHER_APP_CLUSTER=mt1 55 | 56 | MIX_PUSHER_APP_KEY="${PUSHER_APP_KEY}" 57 | MIX_PUSHER_APP_CLUSTER="${PUSHER_APP_CLUSTER}" 58 | 59 | STRIPE_KEY=your-stripe-key 60 | STRIPE_SECRET=your-stripe-secret 61 | 62 | PRISM_JWT_KEY=just-a-demo-key 63 | # PRISM_TARGET_URL host for user visiting 64 | PRISM_TARGET_URL= 65 | # PRISM_SOURCE_URL host for admin editing 66 | PRISM_SOURCE_URL=http://ftqq.com 67 | # PRISM_PROXY_PORT local proxy service port 68 | PRISM_PROXY_PORT=8802 69 | PRISM_HTTPS=false 70 | PRISM_MEMBER_EXPIRE_DAYS=2 71 | -------------------------------------------------------------------------------- /web/.gitattributes: -------------------------------------------------------------------------------- 1 | * text=auto 2 | *.css linguist-vendored 3 | *.scss linguist-vendored 4 | *.js linguist-vendored 5 | CHANGELOG.md export-ignore 6 | -------------------------------------------------------------------------------- /web/.gitignore: -------------------------------------------------------------------------------- 1 | /node_modules 2 | /**/node_modules 3 | /web/resources/site/node_modules 4 | /public/hot 5 | /public/storage 6 | /storage/*.key 7 | /vendor 8 | .env 9 | .env.local 10 | .env.backup 11 | .phpunit.result.cache 12 | docker-compose.override.yml 13 | Homestead.json 14 | Homestead.yaml 15 | npm-debug.log 16 | yarn-error.log 17 | /storage/settings.json 18 | -------------------------------------------------------------------------------- /web/.styleci.yml: -------------------------------------------------------------------------------- 1 | php: 2 | preset: laravel 3 | disabled: 4 | - no_unused_imports 5 | finder: 6 | not-name: 7 | - index.php 8 | - server.php 9 | js: 10 | finder: 11 | not-name: 12 | - webpack.mix.js 13 | css: true 14 | -------------------------------------------------------------------------------- /web/app/Actions/Fortify/CreateNewUser.php: -------------------------------------------------------------------------------- 1 | ['required', 'string', 'max:255'], 25 | 'email' => ['required', 'string', 'email', 'max:255', 'unique:users'], 26 | 'password' => $this->passwordRules(), 27 | 'terms' => Jetstream::hasTermsAndPrivacyPolicyFeature() ? ['required', 'accepted'] : '', 28 | ])->validate(); 29 | 30 | return User::create([ 31 | 'name' => $input['name'], 32 | 'email' => $input['email'], 33 | 'password' => Hash::make($input['password']), 34 | ]); 35 | } 36 | } 37 | -------------------------------------------------------------------------------- /web/app/Actions/Fortify/PasswordValidationRules.php: -------------------------------------------------------------------------------- 1 | $this->passwordRules(), 24 | ])->validate(); 25 | 26 | $user->forceFill([ 27 | 'password' => Hash::make($input['password']), 28 | ])->save(); 29 | } 30 | } 31 | -------------------------------------------------------------------------------- /web/app/Actions/Fortify/UpdateUserPassword.php: -------------------------------------------------------------------------------- 1 | ['required', 'string'], 24 | 'password' => $this->passwordRules(), 25 | ])->after(function ($validator) use ($user, $input) { 26 | if (! isset($input['current_password']) || ! Hash::check($input['current_password'], $user->password)) { 27 | $validator->errors()->add('current_password', __('The provided password does not match your current password.')); 28 | } 29 | })->validateWithBag('updatePassword'); 30 | 31 | $user->forceFill([ 32 | 'password' => Hash::make($input['password']), 33 | ])->save(); 34 | } 35 | } 36 | -------------------------------------------------------------------------------- /web/app/Actions/Fortify/UpdateUserProfileInformation.php: -------------------------------------------------------------------------------- 1 | ['required', 'string', 'max:255'], 23 | 'email' => ['required', 'email', 'max:255', Rule::unique('users')->ignore($user->id)], 24 | 'photo' => ['nullable', 'image', 'max:1024'], 25 | ])->validateWithBag('updateProfileInformation'); 26 | 27 | if (isset($input['photo'])) { 28 | $user->updateProfilePhoto($input['photo']); 29 | } 30 | 31 | if ($input['email'] !== $user->email && 32 | $user instanceof MustVerifyEmail) { 33 | $this->updateVerifiedUser($user, $input); 34 | } else { 35 | $user->forceFill([ 36 | 'name' => $input['name'], 37 | 'email' => $input['email'], 38 | ])->save(); 39 | } 40 | } 41 | 42 | /** 43 | * Update the given verified user's profile information. 44 | * 45 | * @param mixed $user 46 | * @param array $input 47 | * @return void 48 | */ 49 | protected function updateVerifiedUser($user, array $input) 50 | { 51 | $user->forceFill([ 52 | 'name' => $input['name'], 53 | 'email' => $input['email'], 54 | 'email_verified_at' => null, 55 | ])->save(); 56 | 57 | $user->sendEmailVerificationNotification(); 58 | } 59 | } 60 | -------------------------------------------------------------------------------- /web/app/Actions/Jetstream/DeleteUser.php: -------------------------------------------------------------------------------- 1 | deleteProfilePhoto(); 18 | $user->tokens->each->delete(); 19 | $user->delete(); 20 | } 21 | } 22 | -------------------------------------------------------------------------------- /web/app/Console/Commands/Test.php: -------------------------------------------------------------------------------- 1 | content_ids = [0=>"1"]; 45 | $out->uid = 4; 46 | $out->level = 6; 47 | $out->expire = "2021-03-28"; 48 | 49 | echo JWT::encode( $out, env('PRISM_JWT_KEY') ); 50 | echo "\r\n---------------"; 51 | 52 | } 53 | } 54 | -------------------------------------------------------------------------------- /web/app/Console/Kernel.php: -------------------------------------------------------------------------------- 1 | command('inspire')->hourly(); 28 | } 29 | 30 | /** 31 | * Register the commands for the application. 32 | * 33 | * @return void 34 | */ 35 | protected function commands() 36 | { 37 | $this->load(__DIR__.'/Commands'); 38 | 39 | require base_path('routes/console.php'); 40 | } 41 | } 42 | -------------------------------------------------------------------------------- /web/app/Exceptions/Handler.php: -------------------------------------------------------------------------------- 1 | reportable(function (Throwable $e) { 37 | // 38 | }); 39 | } 40 | } 41 | -------------------------------------------------------------------------------- /web/app/Http/Controllers/Controller.php: -------------------------------------------------------------------------------- 1 | contents = $contents ? $contents->toArray():[]; 15 | } 16 | 17 | public function create() 18 | { 19 | return redirect()->route('content.create'); 20 | } 21 | 22 | public function render() 23 | { 24 | return view('livewire.prism.admin.content'); 25 | } 26 | } 27 | -------------------------------------------------------------------------------- /web/app/Http/Livewire/Prism/Admin/ContentCreate.php: -------------------------------------------------------------------------------- 1 | 'StartWith', 11 | '2' => 'ExactMatch', 12 | ]; 13 | 14 | public $state = [ 15 | 'name' => null, 16 | 'url' => null, 17 | 'match' => '1', 18 | 'redirect_url' => null, 19 | ]; 20 | 21 | protected $rules = [ 22 | 'state.name' => 'required|string', 23 | 'state.url' => 'required|string', 24 | 'state.match' => 'required|integer', 25 | 'state.redirect_url' => 'required|string', 26 | ]; 27 | 28 | public function save() 29 | { 30 | $this->validate(); 31 | Content::create( $this->state ); 32 | $this->emit('saved'); 33 | prism_save_rules(); 34 | redirect()->route('content.list'); 35 | } 36 | 37 | 38 | 39 | 40 | public function render() 41 | { 42 | return view('livewire.prism.admin.content-create'); 43 | } 44 | } 45 | -------------------------------------------------------------------------------- /web/app/Http/Livewire/Prism/Admin/ContentDelete.php: -------------------------------------------------------------------------------- 1 | confirm = true; 15 | } 16 | 17 | public function cancel() 18 | { 19 | $this->confirm = false; 20 | } 21 | 22 | public function delete() 23 | { 24 | Content::where('id',$this->content['id'])->delete(); 25 | $this->emit('saved'); 26 | redirect()->route('content.list'); 27 | } 28 | 29 | public function render() 30 | { 31 | return view('livewire.prism.admin.content-delete'); 32 | } 33 | } 34 | -------------------------------------------------------------------------------- /web/app/Http/Livewire/Prism/Admin/ContentItem.php: -------------------------------------------------------------------------------- 1 | content['id'] )->update( ['enabled' => $new_status] ); 15 | $this->content['enabled'] = $new_status; 16 | 17 | } 18 | 19 | public function edit() 20 | { 21 | redirect()->route('content.modify',['content'=>$this->content['id']]); 22 | } 23 | 24 | public function render() 25 | { 26 | return view('livewire.prism.admin.content-item'); 27 | } 28 | } 29 | -------------------------------------------------------------------------------- /web/app/Http/Livewire/Prism/Admin/ContentModify.php: -------------------------------------------------------------------------------- 1 | 'StartWith', 13 | '2' => 'ExactMatch', 14 | ]; 15 | 16 | public function mount() 17 | { 18 | $this->state = $this->content; 19 | } 20 | 21 | protected $rules = [ 22 | 'state.name' => 'required|string', 23 | 'state.url' => 'required|string', 24 | 'state.match' => 'required|integer', 25 | 'state.redirect_url' => 'required|string', 26 | ]; 27 | 28 | public function save() 29 | { 30 | $vdata = $this->validate(); 31 | Content::where('id',$this->content['id'])->update( $vdata['state'] ); 32 | $this->emit('saved'); 33 | prism_save_rules(); 34 | redirect()->route('content.list'); 35 | } 36 | 37 | public function render() 38 | { 39 | return view('livewire.prism.admin.content-modify'); 40 | } 41 | } 42 | -------------------------------------------------------------------------------- /web/app/Http/Livewire/Prism/Admin/MemberItem.php: -------------------------------------------------------------------------------- 1 | route('members.modify',['user'=>$this->member['id']]); 15 | } 16 | 17 | public function change( $status ) 18 | { 19 | $new_level = $status == 'user' ? 1 : 6; 20 | User::where( 'id' , $this->member['id'] )->update( ['level' => $new_level] ); 21 | $this->member['level'] = $new_level; 22 | 23 | } 24 | 25 | public function render() 26 | { 27 | return view('livewire.prism.admin.member-item'); 28 | } 29 | } 30 | -------------------------------------------------------------------------------- /web/app/Http/Livewire/Prism/Admin/Members.php: -------------------------------------------------------------------------------- 1 | members = User::all()->toArray(); 14 | } 15 | 16 | public function render() 17 | { 18 | return view('livewire.prism.admin.members'); 19 | } 20 | } 21 | -------------------------------------------------------------------------------- /web/app/Http/Livewire/Prism/Admin/MembersModify.php: -------------------------------------------------------------------------------- 1 | state = $this->user; 17 | } 18 | 19 | protected $rules = [ 20 | 'state.name' => 'required|string', 21 | 'state.email' => 'required|email', 22 | 'state.subscription_expire_date' => 'required|date', 23 | ]; 24 | 25 | public function save() 26 | { 27 | $vdata = $this->validate(); 28 | if( isset($this->state['password']) && strlen($this->state['password']) > 0 ) 29 | { 30 | $vdata['state']['password'] = Hash::make( $this->state['password'] ); 31 | } 32 | 33 | User::where('id',$this->user['id'])->update( $vdata['state'] ); 34 | $this->emit('saved'); 35 | redirect()->route('members'); 36 | } 37 | 38 | public function render() 39 | { 40 | return view('livewire.prism.admin.members-modify'); 41 | } 42 | } 43 | -------------------------------------------------------------------------------- /web/app/Http/Livewire/Prism/Admin/Plan.php: -------------------------------------------------------------------------------- 1 | plans = $plans ? $plans->toArray():[]; 16 | $this->plans = $plans; 17 | } 18 | 19 | public function create() 20 | { 21 | return redirect()->route('plans.create'); 22 | } 23 | 24 | public function render() 25 | { 26 | return view('livewire.prism.admin.plan'); 27 | } 28 | } 29 | -------------------------------------------------------------------------------- /web/app/Http/Livewire/Prism/Admin/PlanCreate.php: -------------------------------------------------------------------------------- 1 | null, 11 | 'stripe_price_id' => null 12 | ]; 13 | 14 | protected $rules = [ 15 | 'state.name' => 'required|string', 16 | 'state.stripe_price_id' => 'required|string', 17 | ]; 18 | 19 | public function save() 20 | { 21 | $this->validate(); 22 | Plan::create( $this->state ); 23 | $this->emit('saved'); 24 | redirect()->route('plans.list'); 25 | } 26 | 27 | public function render() 28 | { 29 | return view('livewire.prism.admin.plan-create'); 30 | } 31 | } 32 | -------------------------------------------------------------------------------- /web/app/Http/Livewire/Prism/Admin/PlanDelete.php: -------------------------------------------------------------------------------- 1 | confirm = true; 15 | } 16 | 17 | public function cancel() 18 | { 19 | $this->confirm = false; 20 | } 21 | 22 | public function delete() 23 | { 24 | if( $this->plan['member_count'] > 0 ) $this->addError('info', 'cannot remove plan which not empty.'); 25 | Plan::where('id',$this->plan['id'])->delete(); 26 | $this->emit('saved'); 27 | redirect()->route('plans.list'); 28 | } 29 | 30 | 31 | public function render() 32 | { 33 | return view('livewire.prism.admin.plan-delete'); 34 | } 35 | } 36 | -------------------------------------------------------------------------------- /web/app/Http/Livewire/Prism/Admin/PlanItem.php: -------------------------------------------------------------------------------- 1 | contents = $all->toArray(); 19 | $this->selected_contents = $this->plan->contents()->pluck('id'); 20 | } 21 | 22 | public function toggle_add( $status ) 23 | { 24 | $this->add = $status == 'true'; 25 | } 26 | 27 | public function save_content() 28 | { 29 | $plan = Plan::find($this->plan['id']); 30 | $plan->contents()->sync( $this->selected_contents ); 31 | $new_plan = Plan::find($this->plan['id']); 32 | $this->selected_contents = $new_plan->contents()->pluck('id'); 33 | $this->plan = $new_plan; 34 | prism_save_rules(); 35 | $this->add = false; 36 | //dd( $this->selected_contents ); 37 | } 38 | 39 | 40 | public function enable( $status ) 41 | { 42 | $new_status = $status == 1 ? 1 : 0; 43 | Plan::where( 'id' , $this->plan['id'] )->update( ['enabled' => $new_status] ); 44 | $this->plan['enabled'] = $new_status; 45 | 46 | } 47 | 48 | public function edit() 49 | { 50 | redirect()->route('plans.modify',['plan'=>$this->plan['id']]); 51 | } 52 | 53 | public function render() 54 | { 55 | return view('livewire.prism.admin.plan-item'); 56 | } 57 | } 58 | -------------------------------------------------------------------------------- /web/app/Http/Livewire/Prism/Admin/PlanModify.php: -------------------------------------------------------------------------------- 1 | state = $this->plan; 15 | } 16 | 17 | protected $rules = [ 18 | 'state.name' => 'required|string', 19 | 'state.stripe_price_id' => 'required|string' 20 | ]; 21 | 22 | public function save() 23 | { 24 | $vdata = $this->validate(); 25 | Plan::where('id',$this->plan['id'])->update( $vdata['state'] ); 26 | $this->emit('saved'); 27 | prism_save_rules(); 28 | redirect()->route('plans.list'); 29 | } 30 | 31 | public function render() 32 | { 33 | return view('livewire.prism.admin.plan-modify'); 34 | } 35 | } 36 | -------------------------------------------------------------------------------- /web/app/Http/Middleware/Authenticate.php: -------------------------------------------------------------------------------- 1 | expectsJson()) { 18 | return route('login'); 19 | } 20 | } 21 | } 22 | -------------------------------------------------------------------------------- /web/app/Http/Middleware/CheckAdmin.php: -------------------------------------------------------------------------------- 1 | bearerToken(); 20 | if( $jwt && prism_check_jwt( $jwt ) ) 21 | { 22 | 23 | } 24 | else 25 | { 26 | return abort(403,'Login first'); 27 | } 28 | return $next($request); 29 | } 30 | } 31 | -------------------------------------------------------------------------------- /web/app/Http/Middleware/EncryptCookies.php: -------------------------------------------------------------------------------- 1 | check()) { 26 | return redirect(RouteServiceProvider::HOME); 27 | } 28 | } 29 | 30 | return $next($request); 31 | } 32 | } 33 | -------------------------------------------------------------------------------- /web/app/Http/Middleware/TrimStrings.php: -------------------------------------------------------------------------------- 1 | allSubdomainsOfApplicationUrl(), 18 | ]; 19 | } 20 | } 21 | -------------------------------------------------------------------------------- /web/app/Http/Middleware/TrustProxies.php: -------------------------------------------------------------------------------- 1 | belongsToMany(Content::class,'content_plans','plans_id' , 'content_id'); 20 | } 21 | } 22 | -------------------------------------------------------------------------------- /web/app/Models/Settings.php: -------------------------------------------------------------------------------- 1 | 'datetime', 53 | ]; 54 | 55 | /** 56 | * The accessors to append to the model's array form. 57 | * 58 | * @var array 59 | */ 60 | protected $appends = [ 61 | 'profile_photo_url', 62 | ]; 63 | } 64 | -------------------------------------------------------------------------------- /web/app/Providers/AppServiceProvider.php: -------------------------------------------------------------------------------- 1 | 'App\Policies\ModelPolicy', 18 | ]; 19 | 20 | /** 21 | * Register any authentication / authorization services. 22 | * 23 | * @return void 24 | */ 25 | public function boot() 26 | { 27 | $this->registerPolicies(); 28 | 29 | Gate::define('saas-admin', function (User $user) { 30 | return $user->level >= 6; // 第一个注册的成为管理员 31 | }); 32 | } 33 | } 34 | -------------------------------------------------------------------------------- /web/app/Providers/BroadcastServiceProvider.php: -------------------------------------------------------------------------------- 1 | [ 19 | SendEmailVerificationNotification::class, 20 | ], 21 | ]; 22 | 23 | /** 24 | * Register any events for your application. 25 | * 26 | * @return void 27 | */ 28 | public function boot() 29 | { 30 | // 31 | } 32 | } 33 | -------------------------------------------------------------------------------- /web/app/Providers/FortifyServiceProvider.php: -------------------------------------------------------------------------------- 1 | by($request->email.$request->ip()); 41 | }); 42 | 43 | RateLimiter::for('two-factor', function (Request $request) { 44 | return Limit::perMinute(5)->by($request->session()->get('login.id')); 45 | }); 46 | } 47 | } 48 | -------------------------------------------------------------------------------- /web/app/Providers/JetstreamServiceProvider.php: -------------------------------------------------------------------------------- 1 | configurePermissions(); 29 | 30 | Jetstream::deleteUsersUsing(DeleteUser::class); 31 | } 32 | 33 | /** 34 | * Configure the permissions that are available within the application. 35 | * 36 | * @return void 37 | */ 38 | protected function configurePermissions() 39 | { 40 | Jetstream::defaultApiTokenPermissions(['read']); 41 | 42 | Jetstream::permissions([ 43 | 'create', 44 | 'read', 45 | 'update', 46 | 'delete', 47 | ]); 48 | } 49 | } 50 | -------------------------------------------------------------------------------- /web/app/Providers/RouteServiceProvider.php: -------------------------------------------------------------------------------- 1 | configureRateLimiting(); 39 | 40 | $this->routes(function () { 41 | Route::prefix('api') 42 | ->middleware('api') 43 | ->namespace($this->namespace) 44 | ->group(base_path('routes/api.php')); 45 | 46 | Route::middleware('web') 47 | ->namespace($this->namespace) 48 | ->group(base_path('routes/web.php')); 49 | }); 50 | } 51 | 52 | /** 53 | * Configure the rate limiters for the application. 54 | * 55 | * @return void 56 | */ 57 | protected function configureRateLimiting() 58 | { 59 | RateLimiter::for('api', function (Request $request) { 60 | return Limit::perMinute(60)->by(optional($request->user())->id ?: $request->ip()); 61 | }); 62 | } 63 | } 64 | -------------------------------------------------------------------------------- /web/app/View/Components/AppLayout.php: -------------------------------------------------------------------------------- 1 | content = $content; 22 | $this->color = $color; 23 | $this->bg = $bg; 24 | $this->class = $class; 25 | // dd( $color ); 26 | } 27 | 28 | /** 29 | * Get the view / contents that represent the component. 30 | * 31 | * @return \Illuminate\Contracts\View\View|string 32 | */ 33 | public function render() 34 | { 35 | return view('components.span'); 36 | } 37 | } 38 | -------------------------------------------------------------------------------- /web/artisan: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env php 2 | make(Illuminate\Contracts\Console\Kernel::class); 34 | 35 | $status = $kernel->handle( 36 | $input = new Symfony\Component\Console\Input\ArgvInput, 37 | new Symfony\Component\Console\Output\ConsoleOutput 38 | ); 39 | 40 | /* 41 | |-------------------------------------------------------------------------- 42 | | Shutdown The Application 43 | |-------------------------------------------------------------------------- 44 | | 45 | | Once Artisan has finished running, we will fire off the shutdown events 46 | | so that any final work may be done by the application before we shut 47 | | down the process. This is the last thing to happen to the request. 48 | | 49 | */ 50 | 51 | $kernel->terminate($input, $status); 52 | 53 | exit($status); 54 | -------------------------------------------------------------------------------- /web/bootstrap/app.php: -------------------------------------------------------------------------------- 1 | singleton( 30 | Illuminate\Contracts\Http\Kernel::class, 31 | App\Http\Kernel::class 32 | ); 33 | 34 | $app->singleton( 35 | Illuminate\Contracts\Console\Kernel::class, 36 | App\Console\Kernel::class 37 | ); 38 | 39 | $app->singleton( 40 | Illuminate\Contracts\Debug\ExceptionHandler::class, 41 | App\Exceptions\Handler::class 42 | ); 43 | 44 | /* 45 | |-------------------------------------------------------------------------- 46 | | Return The Application 47 | |-------------------------------------------------------------------------- 48 | | 49 | | This script returns the application instance. The instance is given to 50 | | the calling script so we can separate the building of the instances 51 | | from the actual running of the application and sending responses. 52 | | 53 | */ 54 | 55 | return $app; 56 | -------------------------------------------------------------------------------- /web/bootstrap/cache/.gitignore: -------------------------------------------------------------------------------- 1 | * 2 | !.gitignore 3 | -------------------------------------------------------------------------------- /web/composer.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "laravel/laravel", 3 | "type": "project", 4 | "description": "The Laravel Framework.", 5 | "keywords": [ 6 | "framework", 7 | "laravel" 8 | ], 9 | "license": "MIT", 10 | "require": { 11 | "php": "^7.3|^8.0", 12 | "creativeorange/gravatar": "~1.0", 13 | "fideloper/proxy": "^4.4", 14 | "firebase/php-jwt": "^5.2", 15 | "fruitcake/laravel-cors": "^2.0", 16 | "guzzlehttp/guzzle": "^7.0.1", 17 | "laravel/cashier": "^12.9", 18 | "laravel/framework": "^8.12", 19 | "laravel/jetstream": "^2.2", 20 | "laravel/sanctum": "^2.6", 21 | "laravel/tinker": "^2.5", 22 | "livewire/livewire": "^2.0" 23 | }, 24 | "require-dev": { 25 | "facade/ignition": "^2.5", 26 | "fakerphp/faker": "^1.9.1", 27 | "laravel/sail": "^1.0.1", 28 | "mockery/mockery": "^1.4.2", 29 | "nunomaduro/collision": "^5.0", 30 | "phpunit/phpunit": "^9.3.3" 31 | }, 32 | "config": { 33 | "optimize-autoloader": true, 34 | "preferred-install": "dist", 35 | "sort-packages": true 36 | }, 37 | "extra": { 38 | "laravel": { 39 | "dont-discover": [] 40 | } 41 | }, 42 | "autoload": { 43 | "psr-4": { 44 | "App\\": "app/", 45 | "Database\\Factories\\": "database/factories/", 46 | "Database\\Seeders\\": "database/seeders/" 47 | }, 48 | "files": ["app/Http/helpers.php"] 49 | }, 50 | "autoload-dev": { 51 | "psr-4": { 52 | "Tests\\": "tests/" 53 | } 54 | }, 55 | "minimum-stability": "dev", 56 | "prefer-stable": true, 57 | "scripts": { 58 | "post-autoload-dump": [ 59 | "Illuminate\\Foundation\\ComposerScripts::postAutoloadDump", 60 | "@php artisan package:discover --ansi" 61 | ], 62 | "post-root-package-install": [ 63 | "@php -r \"file_exists('.env') || copy('.env.example', '.env');\"" 64 | ], 65 | "post-create-project-cmd": [ 66 | "@php artisan key:generate --ansi" 67 | ] 68 | } 69 | } 70 | -------------------------------------------------------------------------------- /web/config/broadcasting.php: -------------------------------------------------------------------------------- 1 | env('BROADCAST_DRIVER', 'null'), 19 | 20 | /* 21 | |-------------------------------------------------------------------------- 22 | | Broadcast Connections 23 | |-------------------------------------------------------------------------- 24 | | 25 | | Here you may define all of the broadcast connections that will be used 26 | | to broadcast events to other systems or over websockets. Samples of 27 | | each available type of connection are provided inside this array. 28 | | 29 | */ 30 | 31 | 'connections' => [ 32 | 33 | 'pusher' => [ 34 | 'driver' => 'pusher', 35 | 'key' => env('PUSHER_APP_KEY'), 36 | 'secret' => env('PUSHER_APP_SECRET'), 37 | 'app_id' => env('PUSHER_APP_ID'), 38 | 'options' => [ 39 | 'cluster' => env('PUSHER_APP_CLUSTER'), 40 | 'useTLS' => true, 41 | ], 42 | ], 43 | 44 | 'ably' => [ 45 | 'driver' => 'ably', 46 | 'key' => env('ABLY_KEY'), 47 | ], 48 | 49 | 'redis' => [ 50 | 'driver' => 'redis', 51 | 'connection' => 'default', 52 | ], 53 | 54 | 'log' => [ 55 | 'driver' => 'log', 56 | ], 57 | 58 | 'null' => [ 59 | 'driver' => 'null', 60 | ], 61 | 62 | ], 63 | 64 | ]; 65 | -------------------------------------------------------------------------------- /web/config/cors.php: -------------------------------------------------------------------------------- 1 | ['api/*', 'sanctum/csrf-cookie'], 19 | 20 | 'allowed_methods' => ['*'], 21 | 22 | 'allowed_origins' => ['*'], 23 | 24 | 'allowed_origins_patterns' => [], 25 | 26 | 'allowed_headers' => ['*'], 27 | 28 | 'exposed_headers' => [], 29 | 30 | 'max_age' => 0, 31 | 32 | 'supports_credentials' => false, 33 | 34 | ]; 35 | -------------------------------------------------------------------------------- /web/config/hashing.php: -------------------------------------------------------------------------------- 1 | 'bcrypt', 19 | 20 | /* 21 | |-------------------------------------------------------------------------- 22 | | Bcrypt Options 23 | |-------------------------------------------------------------------------- 24 | | 25 | | Here you may specify the configuration options that should be used when 26 | | passwords are hashed using the Bcrypt algorithm. This will allow you 27 | | to control the amount of time it takes to hash the given password. 28 | | 29 | */ 30 | 31 | 'bcrypt' => [ 32 | 'rounds' => env('BCRYPT_ROUNDS', 10), 33 | ], 34 | 35 | /* 36 | |-------------------------------------------------------------------------- 37 | | Argon Options 38 | |-------------------------------------------------------------------------- 39 | | 40 | | Here you may specify the configuration options that should be used when 41 | | passwords are hashed using the Argon algorithm. These will allow you 42 | | to control the amount of time it takes to hash the given password. 43 | | 44 | */ 45 | 46 | 'argon' => [ 47 | 'memory' => 1024, 48 | 'threads' => 2, 49 | 'time' => 2, 50 | ], 51 | 52 | ]; 53 | -------------------------------------------------------------------------------- /web/config/sanctum.php: -------------------------------------------------------------------------------- 1 | explode(',', env( 17 | 'SANCTUM_STATEFUL_DOMAINS', 18 | 'localhost,localhost:3000,127.0.0.1,127.0.0.1:8000,::1' 19 | )), 20 | 21 | /* 22 | |-------------------------------------------------------------------------- 23 | | Expiration Minutes 24 | |-------------------------------------------------------------------------- 25 | | 26 | | This value controls the number of minutes until an issued token will be 27 | | considered expired. If this value is null, personal access tokens do 28 | | not expire. This won't tweak the lifetime of first-party sessions. 29 | | 30 | */ 31 | 32 | 'expiration' => null, 33 | 34 | /* 35 | |-------------------------------------------------------------------------- 36 | | Sanctum Middleware 37 | |-------------------------------------------------------------------------- 38 | | 39 | | When authenticating your first-party SPA with Sanctum you may need to 40 | | customize some of the middleware Sanctum uses while processing the 41 | | request. You may change the middleware listed below as required. 42 | | 43 | */ 44 | 45 | 'middleware' => [ 46 | 'verify_csrf_token' => App\Http\Middleware\VerifyCsrfToken::class, 47 | 'encrypt_cookies' => App\Http\Middleware\EncryptCookies::class, 48 | ], 49 | 50 | ]; 51 | -------------------------------------------------------------------------------- /web/config/services.php: -------------------------------------------------------------------------------- 1 | [ 18 | 'domain' => env('MAILGUN_DOMAIN'), 19 | 'secret' => env('MAILGUN_SECRET'), 20 | 'endpoint' => env('MAILGUN_ENDPOINT', 'api.mailgun.net'), 21 | ], 22 | 23 | 'postmark' => [ 24 | 'token' => env('POSTMARK_TOKEN'), 25 | ], 26 | 27 | 'ses' => [ 28 | 'key' => env('AWS_ACCESS_KEY_ID'), 29 | 'secret' => env('AWS_SECRET_ACCESS_KEY'), 30 | 'region' => env('AWS_DEFAULT_REGION', 'us-east-1'), 31 | ], 32 | 33 | ]; 34 | -------------------------------------------------------------------------------- /web/config/view.php: -------------------------------------------------------------------------------- 1 | [ 17 | resource_path('views'), 18 | ], 19 | 20 | /* 21 | |-------------------------------------------------------------------------- 22 | | Compiled View Path 23 | |-------------------------------------------------------------------------- 24 | | 25 | | This option determines where all the compiled Blade templates will be 26 | | stored for your application. Typically, this is within the storage 27 | | directory. However, as usual, you are free to change this value. 28 | | 29 | */ 30 | 31 | 'compiled' => env( 32 | 'VIEW_COMPILED_PATH', 33 | realpath(storage_path('framework/views')) 34 | ), 35 | 36 | ]; 37 | -------------------------------------------------------------------------------- /web/database/.gitignore: -------------------------------------------------------------------------------- 1 | *.sqlite 2 | *.sqlite-journal 3 | -------------------------------------------------------------------------------- /web/database/factories/UserFactory.php: -------------------------------------------------------------------------------- 1 | $this->faker->name, 27 | 'email' => $this->faker->unique()->safeEmail, 28 | 'email_verified_at' => now(), 29 | 'password' => '$2y$10$92IXUNpkjO0rOQ5byMi.Ye4oKoEa3Ro9llC/.og/at2.uheWG/igi', // password 30 | 'remember_token' => Str::random(10), 31 | ]; 32 | } 33 | 34 | /** 35 | * Indicate that the model's email address should be unverified. 36 | * 37 | * @return \Illuminate\Database\Eloquent\Factories\Factory 38 | */ 39 | public function unverified() 40 | { 41 | return $this->state(function (array $attributes) { 42 | return [ 43 | 'email_verified_at' => null, 44 | ]; 45 | }); 46 | } 47 | } 48 | -------------------------------------------------------------------------------- /web/database/migrations/2014_10_12_000000_create_users_table.php: -------------------------------------------------------------------------------- 1 | id(); 18 | $table->string('name'); 19 | $table->string('email')->unique(); 20 | $table->timestamp('email_verified_at')->nullable(); 21 | $table->string('password'); 22 | $table->rememberToken(); 23 | $table->foreignId('current_team_id')->nullable(); 24 | $table->text('profile_photo_path')->nullable(); 25 | $table->timestamps(); 26 | }); 27 | } 28 | 29 | /** 30 | * Reverse the migrations. 31 | * 32 | * @return void 33 | */ 34 | public function down() 35 | { 36 | Schema::dropIfExists('users'); 37 | } 38 | } 39 | -------------------------------------------------------------------------------- /web/database/migrations/2014_10_12_100000_create_password_resets_table.php: -------------------------------------------------------------------------------- 1 | string('email')->index(); 18 | $table->string('token'); 19 | $table->timestamp('created_at')->nullable(); 20 | }); 21 | } 22 | 23 | /** 24 | * Reverse the migrations. 25 | * 26 | * @return void 27 | */ 28 | public function down() 29 | { 30 | Schema::dropIfExists('password_resets'); 31 | } 32 | } 33 | -------------------------------------------------------------------------------- /web/database/migrations/2014_10_12_200000_add_two_factor_columns_to_users_table.php: -------------------------------------------------------------------------------- 1 | text('two_factor_secret') 18 | ->after('password') 19 | ->nullable(); 20 | 21 | $table->text('two_factor_recovery_codes') 22 | ->after('two_factor_secret') 23 | ->nullable(); 24 | }); 25 | } 26 | 27 | /** 28 | * Reverse the migrations. 29 | * 30 | * @return void 31 | */ 32 | public function down() 33 | { 34 | Schema::table('users', function (Blueprint $table) { 35 | $table->dropColumn('two_factor_secret', 'two_factor_recovery_codes'); 36 | }); 37 | } 38 | } 39 | -------------------------------------------------------------------------------- /web/database/migrations/2019_05_03_000001_create_customer_columns.php: -------------------------------------------------------------------------------- 1 | string('stripe_id')->nullable()->index(); 18 | $table->string('card_brand')->nullable(); 19 | $table->string('card_last_four', 4)->nullable(); 20 | $table->timestamp('trial_ends_at')->nullable(); 21 | }); 22 | } 23 | 24 | /** 25 | * Reverse the migrations. 26 | * 27 | * @return void 28 | */ 29 | public function down() 30 | { 31 | Schema::table('users', function (Blueprint $table) { 32 | $table->dropColumn([ 33 | 'stripe_id', 34 | 'card_brand', 35 | 'card_last_four', 36 | 'trial_ends_at', 37 | ]); 38 | }); 39 | } 40 | } 41 | -------------------------------------------------------------------------------- /web/database/migrations/2019_05_03_000002_create_subscriptions_table.php: -------------------------------------------------------------------------------- 1 | bigIncrements('id'); 18 | $table->unsignedBigInteger('user_id'); 19 | $table->string('name'); 20 | $table->string('stripe_id'); 21 | $table->string('stripe_status'); 22 | $table->string('stripe_plan')->nullable(); 23 | $table->integer('quantity')->nullable(); 24 | $table->timestamp('trial_ends_at')->nullable(); 25 | $table->timestamp('ends_at')->nullable(); 26 | $table->timestamps(); 27 | 28 | $table->index(['user_id', 'stripe_status']); 29 | }); 30 | } 31 | 32 | /** 33 | * Reverse the migrations. 34 | * 35 | * @return void 36 | */ 37 | public function down() 38 | { 39 | Schema::dropIfExists('subscriptions'); 40 | } 41 | } 42 | -------------------------------------------------------------------------------- /web/database/migrations/2019_05_03_000003_create_subscription_items_table.php: -------------------------------------------------------------------------------- 1 | bigIncrements('id'); 18 | $table->unsignedBigInteger('subscription_id'); 19 | $table->string('stripe_id')->index(); 20 | $table->string('stripe_plan'); 21 | $table->integer('quantity')->nullable(); 22 | $table->timestamps(); 23 | 24 | $table->unique(['subscription_id', 'stripe_plan']); 25 | }); 26 | } 27 | 28 | /** 29 | * Reverse the migrations. 30 | * 31 | * @return void 32 | */ 33 | public function down() 34 | { 35 | Schema::dropIfExists('subscription_items'); 36 | } 37 | } 38 | -------------------------------------------------------------------------------- /web/database/migrations/2019_08_19_000000_create_failed_jobs_table.php: -------------------------------------------------------------------------------- 1 | id(); 18 | $table->string('uuid')->unique(); 19 | $table->text('connection'); 20 | $table->text('queue'); 21 | $table->longText('payload'); 22 | $table->longText('exception'); 23 | $table->timestamp('failed_at')->useCurrent(); 24 | }); 25 | } 26 | 27 | /** 28 | * Reverse the migrations. 29 | * 30 | * @return void 31 | */ 32 | public function down() 33 | { 34 | Schema::dropIfExists('failed_jobs'); 35 | } 36 | } 37 | -------------------------------------------------------------------------------- /web/database/migrations/2019_12_14_000001_create_personal_access_tokens_table.php: -------------------------------------------------------------------------------- 1 | bigIncrements('id'); 18 | $table->morphs('tokenable'); 19 | $table->string('name'); 20 | $table->string('token', 64)->unique(); 21 | $table->text('abilities')->nullable(); 22 | $table->timestamp('last_used_at')->nullable(); 23 | $table->timestamps(); 24 | }); 25 | } 26 | 27 | /** 28 | * Reverse the migrations. 29 | * 30 | * @return void 31 | */ 32 | public function down() 33 | { 34 | Schema::dropIfExists('personal_access_tokens'); 35 | } 36 | } 37 | -------------------------------------------------------------------------------- /web/database/migrations/2021_02_19_084258_create_sessions_table.php: -------------------------------------------------------------------------------- 1 | string('id')->primary(); 18 | $table->foreignId('user_id')->nullable()->index(); 19 | $table->string('ip_address', 45)->nullable(); 20 | $table->text('user_agent')->nullable(); 21 | $table->text('payload'); 22 | $table->integer('last_activity')->index(); 23 | }); 24 | } 25 | 26 | /** 27 | * Reverse the migrations. 28 | * 29 | * @return void 30 | */ 31 | public function down() 32 | { 33 | Schema::dropIfExists('sessions'); 34 | } 35 | } 36 | -------------------------------------------------------------------------------- /web/database/migrations/2021_02_19_090050_add_level_to_users_tables.php: -------------------------------------------------------------------------------- 1 | integer('level')->nullable()->default(1); 18 | }); 19 | } 20 | 21 | /** 22 | * Reverse the migrations. 23 | * 24 | * @return void 25 | */ 26 | public function down() 27 | { 28 | Schema::table('users', function (Blueprint $table) { 29 | $table->dropColumn([ 30 | 'level', 31 | ]); 32 | }); 33 | } 34 | } 35 | -------------------------------------------------------------------------------- /web/database/migrations/2021_02_20_065353_create_content_table.php: -------------------------------------------------------------------------------- 1 | bigIncrements('id'); 18 | $table->string('name'); 19 | $table->string('url'); 20 | $table->tinyInteger('match')->default(1); 21 | $table->tinyInteger('enabled')->default(1); 22 | $table->string('redirect_url'); 23 | $table->timestamps(); 24 | }); 25 | } 26 | 27 | /** 28 | * Reverse the migrations. 29 | * 30 | * @return void 31 | */ 32 | public function down() 33 | { 34 | Schema::table('content', function (Blueprint $table) { 35 | Schema::dropIfExists('content'); 36 | }); 37 | } 38 | } 39 | -------------------------------------------------------------------------------- /web/database/migrations/2021_02_20_091827_create_plans_table.php: -------------------------------------------------------------------------------- 1 | bigIncrements('id'); 18 | $table->string('name'); 19 | $table->string('stripe_price_id'); 20 | $table->integer('member_count')->default(0); 21 | $table->tinyInteger('enabled')->default(1); 22 | $table->timestamps(); 23 | }); 24 | } 25 | 26 | /** 27 | * Reverse the migrations. 28 | * 29 | * @return void 30 | */ 31 | public function down() 32 | { 33 | Schema::table('plans', function (Blueprint $table) { 34 | Schema::dropIfExists('plans'); 35 | }); 36 | } 37 | } 38 | -------------------------------------------------------------------------------- /web/database/migrations/2021_02_20_092033_create_content_to_plans_table.php: -------------------------------------------------------------------------------- 1 | foreignId('content_id'); 18 | $table->foreignId('plans_id'); 19 | }); 20 | } 21 | 22 | /** 23 | * Reverse the migrations. 24 | * 25 | * @return void 26 | */ 27 | public function down() 28 | { 29 | Schema::table('content_plans', function (Blueprint $table) { 30 | Schema::dropIfExists('content_plans'); 31 | }); 32 | } 33 | } 34 | -------------------------------------------------------------------------------- /web/database/migrations/2021_02_22_045143_add_subscription_info_to_users_table.php: -------------------------------------------------------------------------------- 1 | date('subscription_expire_date')->nullable()->default('2021-01-23'); 18 | }); 19 | } 20 | 21 | /** 22 | * Reverse the migrations. 23 | * 24 | * @return void 25 | */ 26 | public function down() 27 | { 28 | Schema::table('users', function (Blueprint $table) { 29 | $table->dropColumn('subscription_expire_date'); 30 | }); 31 | } 32 | } 33 | -------------------------------------------------------------------------------- /web/database/migrations/2021_02_22_045431_create_payinfo_table.php: -------------------------------------------------------------------------------- 1 | bigIncrements('id'); 18 | $table->bigInteger('uid'); 19 | $table->string('stripe_event_id'); 20 | $table->string('stripe_price_id'); 21 | $table->string('stripe_charge_id')->nullable(); 22 | $table->date('old_expire_date')->nullable(); 23 | $table->string('duration'); 24 | $table->text('record'); 25 | $table->timestamps(); 26 | }); 27 | } 28 | 29 | /** 30 | * Reverse the migrations. 31 | * 32 | * @return void 33 | */ 34 | public function down() 35 | { 36 | Schema::table('payinfo', function (Blueprint $table) { 37 | Schema::dropIfExists('payinfo'); 38 | }); 39 | } 40 | } 41 | -------------------------------------------------------------------------------- /web/database/migrations/2021_02_23_101504_create_settings_table.php: -------------------------------------------------------------------------------- 1 | id(); 18 | $table->string('app_name')->nullable(); 19 | $table->string('app_url')->nullable(); 20 | $table->string('app_logo_url')->nullable(); 21 | $table->string('app_icon_url')->nullable(); 22 | $table->string('app_title')->nullable(); 23 | $table->string('app_detail')->nullable(); 24 | 25 | $table->string('mail_host')->nullable(); 26 | $table->string('mail_port')->nullable(); 27 | $table->string('mail_username')->nullable(); 28 | $table->string('mail_password')->nullable(); 29 | $table->string('mail_from_address')->nullable(); 30 | 31 | $table->string('stripe_key')->nullable(); 32 | $table->string('stripe_secret')->nullable(); 33 | 34 | $table->string('prism_jwt_key')->nullable(); 35 | $table->string('prism_target_url')->nullable(); 36 | $table->string('prism_source_url')->nullable(); 37 | 38 | 39 | $table->timestamps(); 40 | }); 41 | } 42 | 43 | /** 44 | * Reverse the migrations. 45 | * 46 | * @return void 47 | */ 48 | public function down() 49 | { 50 | Schema::dropIfExists('settings'); 51 | } 52 | } 53 | -------------------------------------------------------------------------------- /web/database/seeders/DatabaseSeeder.php: -------------------------------------------------------------------------------- 1 | create(); 21 | if( !$user = DB::table('users')->where(['name'=>'admin'])->first() ) 22 | DB::table('users')->insert([ 23 | 'name' => 'admin', 24 | 'email' => 'admin@memberprism.com', 25 | 'password' => Hash::make('admin'), 26 | 'level' => 9, 27 | 'created_at' => Carbon::now()->format('Y-m-d H:i:s'), 28 | 'updated_at' => Carbon::now()->format('Y-m-d H:i:s'), 29 | ]); 30 | 31 | if( !$settings = DB::table('settings')->where(['id'=>1])->first() ) 32 | DB::table('settings')->insert([ 33 | 'id'=>1, 34 | 'prism_jwt_key' => Str::random(16), 35 | 'created_at' => Carbon::now()->format('Y-m-d H:i:s'), 36 | 'updated_at' => Carbon::now()->format('Y-m-d H:i:s'), 37 | ]); 38 | } 39 | } 40 | -------------------------------------------------------------------------------- /web/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "private": true, 3 | "scripts": { 4 | "dev": "npm run development", 5 | "development": "mix", 6 | "watch": "mix watch", 7 | "watch-poll": "mix watch -- --watch-options-poll=1000", 8 | "hot": "mix watch --hot", 9 | "prod": "npm run production", 10 | "production": "mix --production" 11 | }, 12 | "devDependencies": { 13 | "@tailwindcss/forms": "^0.2.1", 14 | "@tailwindcss/typography": "^0.3.0", 15 | "alpinejs": "^2.7.3", 16 | "autoprefixer": "^10.0.2", 17 | "axios": "^0.21", 18 | "laravel-mix": "^6.0.6", 19 | "lodash": "^4.17.19", 20 | "postcss": "^8.1.14", 21 | "postcss-import": "^12.0.1", 22 | "resolve-url-loader": "^3.1.2", 23 | "sass": "^1.32.8", 24 | "sass-loader": "^8.0.2", 25 | "tailwindcss": "^2.0.1" 26 | } 27 | } 28 | -------------------------------------------------------------------------------- /web/phpunit.xml: -------------------------------------------------------------------------------- 1 | 2 | 7 | 8 | 9 | ./tests/Unit 10 | 11 | 12 | ./tests/Feature 13 | 14 | 15 | 16 | 17 | ./app 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | -------------------------------------------------------------------------------- /web/public/.htaccess: -------------------------------------------------------------------------------- 1 | 2 | 3 | Options -MultiViews -Indexes 4 | 5 | 6 | RewriteEngine On 7 | 8 | # Handle Authorization Header 9 | RewriteCond %{HTTP:Authorization} . 10 | RewriteRule .* - [E=HTTP_AUTHORIZATION:%{HTTP:Authorization}] 11 | 12 | # Redirect Trailing Slashes If Not A Folder... 13 | RewriteCond %{REQUEST_FILENAME} !-d 14 | RewriteCond %{REQUEST_URI} (.+)/$ 15 | RewriteRule ^ %1 [L,R=301] 16 | 17 | # Send Requests To Front Controller... 18 | RewriteCond %{REQUEST_FILENAME} !-d 19 | RewriteCond %{REQUEST_FILENAME} !-f 20 | RewriteRule ^ index.php [L] 21 | 22 | -------------------------------------------------------------------------------- /web/public/favicon.ico: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/easychen/MemberPrism2/6ca545d918dea8c4b6170d0ff2303883aac0ad9f/web/public/favicon.ico -------------------------------------------------------------------------------- /web/public/index.php: -------------------------------------------------------------------------------- 1 | make(Kernel::class); 52 | 53 | $response = tap($kernel->handle( 54 | $request = Request::capture() 55 | ))->send(); 56 | 57 | $kernel->terminate($request, $response); 58 | -------------------------------------------------------------------------------- /web/public/mix-manifest.json: -------------------------------------------------------------------------------- 1 | { 2 | "/js/app.js": "/js/app.js", 3 | "/site/app.js": "/site/app.js", 4 | "/css/app.css": "/css/app.css", 5 | "/site/app.css": "/site/app.css" 6 | } 7 | -------------------------------------------------------------------------------- /web/public/robots.txt: -------------------------------------------------------------------------------- 1 | User-agent: * 2 | Disallow: 3 | -------------------------------------------------------------------------------- /web/public/site/app.css: -------------------------------------------------------------------------------- 1 | .book { 2 | font-size: 12px; 3 | } 4 | -------------------------------------------------------------------------------- /web/public/site/app.js: -------------------------------------------------------------------------------- 1 | /******/ (() => { // webpackBootstrap 2 | var __webpack_exports__ = {}; 3 | /*!***********************************!*\ 4 | !*** ./resources/site/src/app.js ***! 5 | \***********************************/ 6 | // 7 | /******/ })() 8 | ; -------------------------------------------------------------------------------- /web/public/web.config: -------------------------------------------------------------------------------- 1 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | -------------------------------------------------------------------------------- /web/resources/css/app.css: -------------------------------------------------------------------------------- 1 | @import 'tailwindcss/base'; 2 | @import 'tailwindcss/components'; 3 | @import 'tailwindcss/utilities'; 4 | -------------------------------------------------------------------------------- /web/resources/js/app.js: -------------------------------------------------------------------------------- 1 | require('./bootstrap'); 2 | 3 | require('alpinejs'); 4 | -------------------------------------------------------------------------------- /web/resources/js/bootstrap.js: -------------------------------------------------------------------------------- 1 | window._ = require('lodash'); 2 | 3 | /** 4 | * We'll load the axios HTTP library which allows us to easily issue requests 5 | * to our Laravel back-end. This library automatically handles sending the 6 | * CSRF token as a header based on the value of the "XSRF" token cookie. 7 | */ 8 | 9 | window.axios = require('axios'); 10 | 11 | window.axios.defaults.headers.common['X-Requested-With'] = 'XMLHttpRequest'; 12 | 13 | /** 14 | * Echo exposes an expressive API for subscribing to channels and listening 15 | * for events that are broadcast by Laravel. Echo and event broadcasting 16 | * allows your team to easily build robust real-time web applications. 17 | */ 18 | 19 | // import Echo from 'laravel-echo'; 20 | 21 | // window.Pusher = require('pusher-js'); 22 | 23 | // window.Echo = new Echo({ 24 | // broadcaster: 'pusher', 25 | // key: process.env.MIX_PUSHER_APP_KEY, 26 | // cluster: process.env.MIX_PUSHER_APP_CLUSTER, 27 | // forceTLS: true 28 | // }); 29 | -------------------------------------------------------------------------------- /web/resources/lang/en/auth.php: -------------------------------------------------------------------------------- 1 | 'These credentials do not match our records.', 17 | 'password' => 'The provided password is incorrect.', 18 | 'throttle' => 'Too many login attempts. Please try again in :seconds seconds.', 19 | 20 | ]; 21 | -------------------------------------------------------------------------------- /web/resources/lang/en/pagination.php: -------------------------------------------------------------------------------- 1 | '« Previous', 17 | 'next' => 'Next »', 18 | 19 | ]; 20 | -------------------------------------------------------------------------------- /web/resources/lang/en/passwords.php: -------------------------------------------------------------------------------- 1 | 'Your password has been reset!', 17 | 'sent' => 'We have emailed your password reset link!', 18 | 'throttled' => 'Please wait before retrying.', 19 | 'token' => 'This password reset token is invalid.', 20 | 'user' => "We can't find a user with that email address.", 21 | 22 | ]; 23 | -------------------------------------------------------------------------------- /web/resources/markdown/policy.md: -------------------------------------------------------------------------------- 1 | # Privacy Policy 2 | 3 | Edit this file to define the privacy policy for your application. 4 | -------------------------------------------------------------------------------- /web/resources/markdown/terms.md: -------------------------------------------------------------------------------- 1 | # Terms of Service 2 | 3 | Edit this file to define the terms of service for your application. 4 | -------------------------------------------------------------------------------- /web/resources/site/.gitignore: -------------------------------------------------------------------------------- 1 | node_modules 2 | -------------------------------------------------------------------------------- /web/resources/site/headcode.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | -------------------------------------------------------------------------------- /web/resources/site/mix-manifest.json: -------------------------------------------------------------------------------- 1 | { 2 | "/dist/app.js": "/dist/app.js", 3 | "/dist/app.css": "/dist/app.css" 4 | } 5 | -------------------------------------------------------------------------------- /web/resources/site/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "site", 3 | "version": "1.0.0", 4 | "description": "", 5 | "main": "webpack.mix.js", 6 | "scripts": { 7 | "test": "echo \"Error: no test specified\" && exit 1" 8 | }, 9 | "keywords": [], 10 | "author": "", 11 | "license": "ISC", 12 | "devDependencies": { 13 | "laravel-mix": "^6.0.11", 14 | "postcss": "^8.2.6" 15 | }, 16 | "dependencies": { 17 | "axios": "^0.21.1", 18 | "js-cookie": "^2.2.1" 19 | } 20 | } 21 | -------------------------------------------------------------------------------- /web/resources/site/webpack.mix.js: -------------------------------------------------------------------------------- 1 | let mix = require('laravel-mix'); 2 | 3 | mix.js(['src/alpine.min.js','src/app.js'], 'dist/app.js'); 4 | mix.sass('src/app.scss', 'dist/app.css'); 5 | -------------------------------------------------------------------------------- /web/resources/views/admin/content/create.blade.php: -------------------------------------------------------------------------------- 1 | 2 | 3 |

4 | {{ __('Create Content') }} 5 |

6 |
7 | 8 |
9 |
10 | @livewire('prism.admin.content-create') 11 |
12 |
13 |
14 | -------------------------------------------------------------------------------- /web/resources/views/admin/content/list.blade.php: -------------------------------------------------------------------------------- 1 | 2 | 3 |

4 | {{ __('Content') }} 5 |

6 |
7 | 8 |
9 |
10 | @livewire('prism.admin.content') 11 |
12 |
13 |
14 | -------------------------------------------------------------------------------- /web/resources/views/admin/content/modify.blade.php: -------------------------------------------------------------------------------- 1 | 2 | 3 |

4 | {{ __('Modify Content') }} 5 |

6 |
7 | 8 |
9 |
10 | @livewire('prism.admin.content-modify', ['content'=>$content->toArray()]) 11 | 12 | @livewire('prism.admin.content-delete',['content'=>$content->toArray()]) 13 |
14 |
15 |
16 | -------------------------------------------------------------------------------- /web/resources/views/admin/links.blade.php: -------------------------------------------------------------------------------- 1 | 2 | 3 |

4 | {{ __('Links and code') }} 5 |

6 |
7 | 8 |
9 |
10 | @livewire('prism.admin.links') 11 |
12 |
13 |
14 | -------------------------------------------------------------------------------- /web/resources/views/admin/members/list.blade.php: -------------------------------------------------------------------------------- 1 | 2 | 3 |

4 | {{ __('Members') }} 5 |

6 |
7 | 8 |
9 |
10 | @livewire('prism.admin.members') 11 |
12 |
13 |
14 | -------------------------------------------------------------------------------- /web/resources/views/admin/members/modify.blade.php: -------------------------------------------------------------------------------- 1 | 2 | 3 |

4 | {{ __('Modify Member Info') }} 5 |

6 |
7 | 8 |
9 |
10 | @livewire('prism.admin.members-modify', ['user'=>$user->toArray()]) 11 |
12 |
13 |
14 | -------------------------------------------------------------------------------- /web/resources/views/admin/plans/create.blade.php: -------------------------------------------------------------------------------- 1 | 2 | 3 |

4 | {{ __('Create a plan') }} 5 |

6 |
7 | 8 |
9 |
10 | @livewire('prism.admin.plan-create') 11 |
12 |
13 |
14 | -------------------------------------------------------------------------------- /web/resources/views/admin/plans/list.blade.php: -------------------------------------------------------------------------------- 1 | 2 | 3 |

4 | {{ __('Plans') }} 5 |

6 |
7 | 8 |
9 |
10 | @livewire('prism.admin.plan') 11 |
12 |
13 |
14 | -------------------------------------------------------------------------------- /web/resources/views/admin/plans/modify.blade.php: -------------------------------------------------------------------------------- 1 | 2 | 3 |

4 | {{ __('Modify Plan') }} 5 |

6 |
7 | 8 |
9 |
10 | @livewire('prism.admin.plan-modify', ['plan'=>$plan->toArray()]) 11 | 12 | @livewire('prism.admin.plan-delete',['plan'=>$plan->toArray()]) 13 |
14 |
15 |
16 | -------------------------------------------------------------------------------- /web/resources/views/admin/settings.blade.php: -------------------------------------------------------------------------------- 1 | 2 | 3 |

4 | {{ __('Settings') }} 5 |

6 |
7 | 8 |
9 |
10 | @livewire('prism.admin.settings') 11 |
12 |
13 |
14 | -------------------------------------------------------------------------------- /web/resources/views/api/index.blade.php: -------------------------------------------------------------------------------- 1 | 2 | 3 |

4 | {{ __('API Tokens') }} 5 |

6 |
7 | 8 |
9 |
10 | @livewire('api.api-token-manager') 11 |
12 |
13 |
14 | -------------------------------------------------------------------------------- /web/resources/views/auth/confirm-password.blade.php: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 |
8 | {{ __('This is a secure area of the application. Please confirm your password before continuing.') }} 9 |
10 | 11 | 12 | 13 |
14 | @csrf 15 | 16 |
17 | 18 | 19 |
20 | 21 |
22 | 23 | {{ __('Confirm') }} 24 | 25 |
26 |
27 |
28 |
29 | -------------------------------------------------------------------------------- /web/resources/views/auth/forgot-password.blade.php: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 |
8 | {{ __('Forgot your password? No problem. Just let us know your email address and we will email you a password reset link that will allow you to choose a new one.') }} 9 |
10 | 11 | @if (session('status')) 12 |
13 | {{ session('status') }} 14 |
15 | @endif 16 | 17 | 18 | 19 |
20 | @csrf 21 | 22 |
23 | 24 | 25 |
26 | 27 |
28 | 29 | {{ __('Email Password Reset Link') }} 30 | 31 |
32 |
33 |
34 |
35 | -------------------------------------------------------------------------------- /web/resources/views/auth/login.blade.php: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | @if (session('status')) 10 |
11 | {{ session('status') }} 12 |
13 | @endif 14 | 15 |
16 | @csrf 17 | 18 |
19 | 20 | 21 |
22 | 23 |
24 | 25 | 26 |
27 | 28 |
29 | 33 |
34 | 35 |
36 | @if (Route::has('password.request')) 37 | 38 | {{ __('Forgot your password?') }} 39 | 40 | @endif 41 | 42 | 43 | {{ __('Log in') }} 44 | 45 |
46 |
47 |
48 |
49 | -------------------------------------------------------------------------------- /web/resources/views/auth/reset-password.blade.php: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 |
10 | @csrf 11 | 12 | 13 | 14 |
15 | 16 | 17 |
18 | 19 |
20 | 21 | 22 |
23 | 24 |
25 | 26 | 27 |
28 | 29 |
30 | 31 | {{ __('Reset Password') }} 32 | 33 |
34 |
35 |
36 |
37 | -------------------------------------------------------------------------------- /web/resources/views/auth/verify-email.blade.php: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 |
8 | {{ __('Thanks for signing up! Before getting started, could you verify your email address by clicking on the link we just emailed to you? If you didn\'t receive the email, we will gladly send you another.') }} 9 |
10 | 11 | @if (session('status') == 'verification-link-sent') 12 |
13 | {{ __('A new verification link has been sent to the email address you provided during registration.') }} 14 |
15 | @endif 16 | 17 |
18 |
19 | @csrf 20 | 21 |
22 | 23 | {{ __('Resend Verification Email') }} 24 | 25 |
26 |
27 | 28 |
29 | @csrf 30 | 31 | 34 |
35 |
36 |
37 |
38 | -------------------------------------------------------------------------------- /web/resources/views/components/span.blade.php: -------------------------------------------------------------------------------- 1 | 2 | 4 | {{ $slot }} 5 | 6 | -------------------------------------------------------------------------------- /web/resources/views/dashboard.blade.php: -------------------------------------------------------------------------------- 1 | 2 | 3 |

4 | {{ __('Dashboard') }} 5 |

6 |
7 | 8 |
9 |
10 |
11 | 12 |
13 |
14 |
15 |
16 | -------------------------------------------------------------------------------- /web/resources/views/layouts/app.blade.php: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | {{ config('app.name', 'Laravel') }} 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | @livewireStyles 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 |
25 | @livewire('navigation-menu') 26 | 27 | 28 | @if (isset($header)) 29 |
30 |
31 | {{ $header }} 32 |
33 |
34 | @endif 35 | 36 | 37 |
38 | {{ $slot }} 39 |
40 |
41 | 42 | @stack('modals') 43 | 44 | @livewireScripts 45 | 46 | 47 | -------------------------------------------------------------------------------- /web/resources/views/layouts/guest.blade.php: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | {{ config('app.name', 'Laravel') }} 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 |
21 | {{ $slot }} 22 |
23 | 24 | 25 | -------------------------------------------------------------------------------- /web/resources/views/livewire/prism/admin/content-create.blade.php: -------------------------------------------------------------------------------- 1 | 2 | 3 | {{ __('Create a member-only content') }} 4 | 5 | 6 | 7 | {{ __('Only members can visit it .') }} 8 | 9 | 10 | 11 |
12 | 13 | 14 | 15 |
16 | 17 |
18 | 19 | 20 | 21 |
22 | 23 |
24 | 25 | 31 | 32 |
33 | 34 |
35 | 36 | 37 | 38 |
39 | 40 |
41 | 42 | 43 | 44 | {{ __('Saved.') }} 45 | 46 | 47 | 48 | {{ __('Save') }} 49 | 50 | 51 |
52 | -------------------------------------------------------------------------------- /web/resources/views/livewire/prism/admin/content-delete.blade.php: -------------------------------------------------------------------------------- 1 | 2 | 3 | {{ __('Delete the content') }} 4 | 5 | 6 | 7 | {{ __('Cannot be revocerd .') }} 8 | 9 | 10 | 11 |
Delete?
12 |
13 | 14 | 15 | 16 | {{ __('Deleted.') }} 17 | 18 | 19 | @if( $confirm ) 20 | {{ __('Cancel') }} 21 | 22 | {{ __('Confirm') }} 23 | 24 | @else 25 | {{ __('Delete') }} 26 | @endif 27 | 28 | 29 | 30 |
31 | -------------------------------------------------------------------------------- /web/resources/views/livewire/prism/admin/content-item.blade.php: -------------------------------------------------------------------------------- 1 | 2 | 3 |

4 | {{$content['name']}} 5 |

6 | 7 | 8 | 9 |

10 | {{$content['url']}} 11 |

12 | 13 | 14 | 15 |

16 | @if( $content['match'] == 1 ) 17 | StartWith 18 | @else 19 | Exact 20 | @endif 21 |

22 | 23 | 24 | 25 |

26 | {{$content['redirect_url']}} 27 |

28 | 29 | 30 | 31 |

32 | @if( $content['enabled'] > 0 ) 33 | Enabled 34 | @else 35 | Disabled 36 | @endif 37 | 38 | 39 |

40 | 41 | 42 | 43 | 44 |

45 | 46 |

47 | 48 | 49 | 50 | 51 | 52 | -------------------------------------------------------------------------------- /web/resources/views/livewire/prism/admin/content-modify.blade.php: -------------------------------------------------------------------------------- 1 | 2 | 3 | {{ __('Modify the member-only content') }} 4 | 5 | 6 | 7 | {{ __('Only members can visit it .') }} 8 | 9 | 10 | 11 |
12 | 13 | 14 | 15 |
16 | 17 |
18 | 19 | 20 | 21 |
22 | 23 |
24 | 25 | 31 | 32 |
33 | 34 |
35 | 36 | 37 | 38 |
39 | 40 |
41 | 42 | 43 | 44 | {{ __('Saved.') }} 45 | 46 | 47 | 48 | {{ __('Save') }} 49 | 50 | 51 |
52 | -------------------------------------------------------------------------------- /web/resources/views/livewire/prism/admin/member-item.blade.php: -------------------------------------------------------------------------------- 1 | 2 | 3 |
4 |
5 | 8 | 9 |
10 |
11 |

12 | {{ $member['name'] }} 13 |

14 |
15 |
16 | 17 | 18 |

{{ $member['email'] }}

20 | 21 | 22 | @if( $member['level'] > 1 ) 23 | Admin 24 | @else 25 | User 26 | @endif 27 | 28 | 29 | 30 |

31 | 32 |

33 | 34 | 35 | 36 |

37 | 38 |

39 | 40 | 41 | 42 | 43 | 44 | -------------------------------------------------------------------------------- /web/resources/views/livewire/prism/admin/members-modify.blade.php: -------------------------------------------------------------------------------- 1 | 2 | 3 | {{ __('Modify the user') }} 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 |
12 | 13 | 14 | 15 |
16 | 17 |
18 | 19 | 20 | 21 |
22 | 23 |
24 | 25 | 26 | 27 |
28 | 29 |
30 | 31 | 32 | 33 |
34 | 35 |
36 | 37 | 38 | 39 | {{ __('Saved.') }} 40 | 41 | 42 | 43 | {{ __('Save') }} 44 | 45 | 46 |
47 | -------------------------------------------------------------------------------- /web/resources/views/livewire/prism/admin/plan-create.blade.php: -------------------------------------------------------------------------------- 1 | 2 | 3 | {{ __('Create a plan') }} 4 | 5 | 6 | 7 | {{ __('Then members can subscribe it .') }} 8 | 9 | 10 | 11 |
12 | 13 | 14 | 15 |
16 | 17 |
18 | 19 | 20 | 21 |
22 | 23 |
24 | 25 | 26 | 27 | {{ __('Saved.') }} 28 | 29 | 30 | 31 | {{ __('Save') }} 32 | 33 | 34 |
35 | -------------------------------------------------------------------------------- /web/resources/views/livewire/prism/admin/plan-delete.blade.php: -------------------------------------------------------------------------------- 1 | 2 | 3 | {{ __('Delete the plan') }} 4 | 5 | 6 | 7 | {{ __('Cannot be revocerd .') }} 8 | 9 | 10 | 11 |
Delete?
12 |
13 | 14 | 15 | 16 | 17 | {{ __('Deleted.') }} 18 | 19 | 20 | @if( $confirm ) 21 | {{ __('Cancel') }} 22 | 23 | {{ __('Confirm') }} 24 | 25 | @else 26 | {{ __('Delete') }} 27 | @endif 28 | 29 | 30 | 31 |
32 | -------------------------------------------------------------------------------- /web/resources/views/livewire/prism/admin/plan-modify.blade.php: -------------------------------------------------------------------------------- 1 | 2 | 3 | {{ __('Modify the plan') }} 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 |
12 | 13 | 14 | 15 |
16 | 17 |
18 | 19 | 20 | 21 |
22 | 23 |
24 | 25 | 26 | 27 | {{ __('Saved.') }} 28 | 29 | 30 | 31 | {{ __('Save') }} 32 | 33 | 34 |
35 | -------------------------------------------------------------------------------- /web/resources/views/policy.blade.php: -------------------------------------------------------------------------------- 1 | 2 |
3 |
4 |
5 | 6 |
7 | 8 |
9 | {!! $policy !!} 10 |
11 |
12 |
13 |
14 | -------------------------------------------------------------------------------- /web/resources/views/profile/show.blade.php: -------------------------------------------------------------------------------- 1 | 2 | 3 |

4 | {{ __('Profile') }} 5 |

6 |
7 | 8 |
9 |
10 | @if (Laravel\Fortify\Features::canUpdateProfileInformation()) 11 | @livewire('profile.update-profile-information-form') 12 | 13 | 14 | @endif 15 | 16 | @if (Laravel\Fortify\Features::enabled(Laravel\Fortify\Features::updatePasswords())) 17 |
18 | @livewire('profile.update-password-form') 19 |
20 | 21 | 22 | @endif 23 | 24 | @if (Laravel\Fortify\Features::canManageTwoFactorAuthentication()) 25 |
26 | @livewire('profile.two-factor-authentication-form') 27 |
28 | 29 | 30 | @endif 31 | 32 |
33 | @livewire('profile.logout-other-browser-sessions-form') 34 |
35 | 36 | @if (Laravel\Jetstream\Jetstream::hasAccountDeletionFeatures()) 37 | 38 | 39 |
40 | @livewire('profile.delete-user-form') 41 |
42 | @endif 43 |
44 |
45 |
46 | -------------------------------------------------------------------------------- /web/resources/views/profile/update-password-form.blade.php: -------------------------------------------------------------------------------- 1 | 2 | 3 | {{ __('Update Password') }} 4 | 5 | 6 | 7 | {{ __('Ensure your account is using a long, random password to stay secure.') }} 8 | 9 | 10 | 11 |
12 | 13 | 14 | 15 |
16 | 17 |
18 | 19 | 20 | 21 |
22 | 23 |
24 | 25 | 26 | 27 |
28 |
29 | 30 | 31 | 32 | {{ __('Saved.') }} 33 | 34 | 35 | 36 | {{ __('Save') }} 37 | 38 | 39 |
40 | -------------------------------------------------------------------------------- /web/resources/views/terms.blade.php: -------------------------------------------------------------------------------- 1 | 2 |
3 |
4 |
5 | 6 |
7 | 8 |
9 | {!! $terms !!} 10 |
11 |
12 |
13 |
14 | -------------------------------------------------------------------------------- /web/resources/views/vendor/jetstream/components/action-message.blade.php: -------------------------------------------------------------------------------- 1 | @props(['on']) 2 | 3 |
merge(['class' => 'text-sm text-gray-600']) }}> 8 | {{ $slot->isEmpty() ? 'Saved.' : $slot }} 9 |
10 | -------------------------------------------------------------------------------- /web/resources/views/vendor/jetstream/components/action-section.blade.php: -------------------------------------------------------------------------------- 1 |
2 | 3 | {{ $title }} 4 | {{ $description }} 5 | 6 | 7 |
8 |
9 | {{ $content }} 10 |
11 |
12 |
13 | -------------------------------------------------------------------------------- /web/resources/views/vendor/jetstream/components/application-mark.blade.php: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | -------------------------------------------------------------------------------- /web/resources/views/vendor/jetstream/components/authentication-card-logo.blade.php: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | -------------------------------------------------------------------------------- /web/resources/views/vendor/jetstream/components/authentication-card.blade.php: -------------------------------------------------------------------------------- 1 |
2 |
3 | {{ $logo }} 4 |
5 | 6 |
7 | {{ $slot }} 8 |
9 |
10 | -------------------------------------------------------------------------------- /web/resources/views/vendor/jetstream/components/button.blade.php: -------------------------------------------------------------------------------- 1 | 4 | -------------------------------------------------------------------------------- /web/resources/views/vendor/jetstream/components/checkbox.blade.php: -------------------------------------------------------------------------------- 1 | merge(['class' => 'rounded border-gray-300 text-indigo-600 shadow-sm focus:border-indigo-300 focus:ring focus:ring-indigo-200 focus:ring-opacity-50']) !!}> 2 | -------------------------------------------------------------------------------- /web/resources/views/vendor/jetstream/components/confirmation-modal.blade.php: -------------------------------------------------------------------------------- 1 | @props(['id' => null, 'maxWidth' => null]) 2 | 3 | 4 |
5 |
6 |
7 | 8 | 9 | 10 |
11 | 12 |
13 |

14 | {{ $title }} 15 |

16 | 17 |
18 | {{ $content }} 19 |
20 |
21 |
22 |
23 | 24 |
25 | {{ $footer }} 26 |
27 |
28 | -------------------------------------------------------------------------------- /web/resources/views/vendor/jetstream/components/confirms-password.blade.php: -------------------------------------------------------------------------------- 1 | @props(['title' => __('Confirm Password'), 'content' => __('For your security, please confirm your password to continue.'), 'button' => __('Confirm')]) 2 | 3 | @php 4 | $confirmableId = md5($attributes->wire('then')); 5 | @endphp 6 | 7 | wire('then') }} 9 | x-data 10 | x-ref="span" 11 | x-on:click="$wire.startConfirmingPassword('{{ $confirmableId }}')" 12 | x-on:password-confirmed.window="setTimeout(() => $event.detail.id === '{{ $confirmableId }}' && $refs.span.dispatchEvent(new CustomEvent('then', { bubbles: false })), 250);" 13 | > 14 | {{ $slot }} 15 | 16 | 17 | @once 18 | 19 | 20 | {{ $title }} 21 | 22 | 23 | 24 | {{ $content }} 25 | 26 |
27 | 31 | 32 | 33 |
34 |
35 | 36 | 37 | 38 | {{ __('Nevermind') }} 39 | 40 | 41 | 42 | {{ $button }} 43 | 44 | 45 |
46 | @endonce 47 | -------------------------------------------------------------------------------- /web/resources/views/vendor/jetstream/components/danger-button.blade.php: -------------------------------------------------------------------------------- 1 | 4 | -------------------------------------------------------------------------------- /web/resources/views/vendor/jetstream/components/dialog-modal.blade.php: -------------------------------------------------------------------------------- 1 | @props(['id' => null, 'maxWidth' => null]) 2 | 3 | 4 |
5 |
6 | {{ $title }} 7 |
8 | 9 |
10 | {{ $content }} 11 |
12 |
13 | 14 |
15 | {{ $footer }} 16 |
17 |
18 | -------------------------------------------------------------------------------- /web/resources/views/vendor/jetstream/components/dropdown-link.blade.php: -------------------------------------------------------------------------------- 1 | merge(['class' => 'block px-4 py-2 text-sm leading-5 text-gray-700 hover:bg-gray-100 focus:outline-none focus:bg-gray-100 transition duration-150 ease-in-out']) }}>{{ $slot }} 2 | -------------------------------------------------------------------------------- /web/resources/views/vendor/jetstream/components/dropdown.blade.php: -------------------------------------------------------------------------------- 1 | @props(['align' => 'right', 'width' => '48', 'contentClasses' => 'py-1 bg-white', 'dropdownClasses' => '']) 2 | 3 | @php 4 | switch ($align) { 5 | case 'left': 6 | $alignmentClasses = 'origin-top-left left-0'; 7 | break; 8 | case 'top': 9 | $alignmentClasses = 'origin-top'; 10 | break; 11 | case 'none': 12 | case 'false': 13 | $alignmentClasses = ''; 14 | break; 15 | case 'right': 16 | default: 17 | $alignmentClasses = 'origin-top-right right-0'; 18 | break; 19 | } 20 | 21 | switch ($width) { 22 | case '48': 23 | $width = 'w-48'; 24 | break; 25 | } 26 | @endphp 27 | 28 |
29 |
30 | {{ $trigger }} 31 |
32 | 33 | 47 |
48 | -------------------------------------------------------------------------------- /web/resources/views/vendor/jetstream/components/form-section.blade.php: -------------------------------------------------------------------------------- 1 | @props(['submit']) 2 | 3 |
merge(['class' => 'md:grid md:grid-cols-3 md:gap-6']) }}> 4 | 5 | {{ $title }} 6 | {{ $description }} 7 | 8 | 9 |
10 |
11 |
12 |
13 | {{ $form }} 14 |
15 |
16 | 17 | @if (isset($actions)) 18 |
19 | {{ $actions }} 20 |
21 | @endif 22 |
23 |
24 |
25 | -------------------------------------------------------------------------------- /web/resources/views/vendor/jetstream/components/input-error.blade.php: -------------------------------------------------------------------------------- 1 | @props(['for']) 2 | 3 | @error($for) 4 |

merge(['class' => 'text-sm text-red-600']) }}>{{ $message }}

5 | @enderror 6 | -------------------------------------------------------------------------------- /web/resources/views/vendor/jetstream/components/input.blade.php: -------------------------------------------------------------------------------- 1 | @props(['disabled' => false]) 2 | 3 | merge(['class' => 'border-gray-300 focus:border-indigo-300 focus:ring focus:ring-indigo-200 focus:ring-opacity-50 rounded-md shadow-sm']) !!}> 4 | -------------------------------------------------------------------------------- /web/resources/views/vendor/jetstream/components/label.blade.php: -------------------------------------------------------------------------------- 1 | @props(['value']) 2 | 3 | 6 | -------------------------------------------------------------------------------- /web/resources/views/vendor/jetstream/components/nav-link.blade.php: -------------------------------------------------------------------------------- 1 | @props(['active']) 2 | 3 | @php 4 | $classes = ($active ?? false) 5 | ? 'inline-flex items-center px-1 pt-1 border-b-2 border-indigo-400 text-sm font-medium leading-5 text-gray-900 focus:outline-none focus:border-indigo-700 transition duration-150 ease-in-out' 6 | : 'inline-flex items-center px-1 pt-1 border-b-2 border-transparent text-sm font-medium leading-5 text-gray-500 hover:text-gray-700 hover:border-gray-300 focus:outline-none focus:text-gray-700 focus:border-gray-300 transition duration-150 ease-in-out'; 7 | @endphp 8 | 9 | merge(['class' => $classes]) }}> 10 | {{ $slot }} 11 | 12 | -------------------------------------------------------------------------------- /web/resources/views/vendor/jetstream/components/responsive-nav-link.blade.php: -------------------------------------------------------------------------------- 1 | @props(['active']) 2 | 3 | @php 4 | $classes = ($active ?? false) 5 | ? 'block pl-3 pr-4 py-2 border-l-4 border-indigo-400 text-base font-medium text-indigo-700 bg-indigo-50 focus:outline-none focus:text-indigo-800 focus:bg-indigo-100 focus:border-indigo-700 transition duration-150 ease-in-out' 6 | : 'block pl-3 pr-4 py-2 border-l-4 border-transparent text-base font-medium text-gray-600 hover:text-gray-800 hover:bg-gray-50 hover:border-gray-300 focus:outline-none focus:text-gray-800 focus:bg-gray-50 focus:border-gray-300 transition duration-150 ease-in-out'; 7 | @endphp 8 | 9 | merge(['class' => $classes]) }}> 10 | {{ $slot }} 11 | 12 | -------------------------------------------------------------------------------- /web/resources/views/vendor/jetstream/components/secondary-button.blade.php: -------------------------------------------------------------------------------- 1 | 4 | -------------------------------------------------------------------------------- /web/resources/views/vendor/jetstream/components/section-border.blade.php: -------------------------------------------------------------------------------- 1 | 6 | -------------------------------------------------------------------------------- /web/resources/views/vendor/jetstream/components/section-title.blade.php: -------------------------------------------------------------------------------- 1 |
2 |
3 |

{{ $title }}

4 | 5 |

6 | {{ $description }} 7 |

8 |
9 |
10 | -------------------------------------------------------------------------------- /web/resources/views/vendor/jetstream/components/switchable-team.blade.php: -------------------------------------------------------------------------------- 1 | @props(['team', 'component' => 'jet-dropdown-link']) 2 | 3 |
4 | @method('PUT') 5 | @csrf 6 | 7 | 8 | 9 | 10 | 11 |
12 | @if (Auth::user()->isCurrentTeam($team)) 13 | 14 | @endif 15 | 16 |
{{ $team->name }}
17 |
18 |
19 |
20 | -------------------------------------------------------------------------------- /web/resources/views/vendor/jetstream/components/validation-errors.blade.php: -------------------------------------------------------------------------------- 1 | @if ($errors->any()) 2 |
3 |
{{ __('Whoops! Something went wrong.') }}
4 | 5 |
    6 | @foreach ($errors->all() as $error) 7 |
  • {{ $error }}
  • 8 | @endforeach 9 |
10 |
11 | @endif 12 | -------------------------------------------------------------------------------- /web/resources/views/vendor/jetstream/components/welcome.blade.php: -------------------------------------------------------------------------------- 1 |
2 |
3 | 4 |
5 | 6 |
7 | {{ env('APP_TITLE') }} 8 |
9 | 10 |
11 | {{ env('APP_DETAIL') }} 12 |
13 |
14 | 15 | 16 | -------------------------------------------------------------------------------- /web/resources/views/vendor/jetstream/mail/team-invitation.blade.php: -------------------------------------------------------------------------------- 1 | @component('mail::message') 2 | {{ __('You have been invited to join the :team team!', ['team' => $invitation->team->name]) }} 3 | 4 | {{ __('If you do not have an account, you may create one by clicking the button below. After creating an account, you may click the invitation acceptance button in this email to accept the team invitation:') }} 5 | 6 | @component('mail::button', ['url' => route('register')]) 7 | {{ __('Create Account') }} 8 | @endcomponent 9 | 10 | {{ __('If you already have an account, you may accept this invitation by clicking the button below:') }} 11 | 12 | @component('mail::button', ['url' => $acceptUrl]) 13 | {{ __('Accept Invitation') }} 14 | @endcomponent 15 | 16 | {{ __('If you did not expect to receive an invitation to this team, you may discard this email.') }} 17 | @endcomponent 18 | -------------------------------------------------------------------------------- /web/routes/api.php: -------------------------------------------------------------------------------- 1 | get('/user', function (Request $request) { 18 | return $request->user(); 19 | }); 20 | 21 | 22 | Route::post('/register' , 'Laravel\Fortify\Http\Controllers\RegisteredUserController@store' ); 23 | 24 | Route::post('/login' , 'App\Http\Controllers\ApiController@login' ); 25 | Route::get('/lock' , 'App\Http\Controllers\ApiController@lock' ); 26 | 27 | Route::middleware('jwt')->post('/subscribe' , 'App\Http\Controllers\ApiController@subscribe' ); 28 | 29 | Route::middleware('jwt')->post('/stripe_port' , 'App\Http\Controllers\ApiController@stripeport' ); 30 | 31 | Route::get('/stripe/callback' , 'App\Http\Controllers\ApiController@payback' )->name('stripe.payback'); 32 | 33 | Route::post('/forgot-password' , 'Laravel\Fortify\Http\Controllers\PasswordResetLinkController@store' ); 34 | 35 | Route::get('/page/{page_name}', 'App\Http\Controllers\AssertController@page' ); 36 | Route::get('/app.js', 'App\Http\Controllers\AssertController@js' ); 37 | Route::get('/app.css', 'App\Http\Controllers\AssertController@css' ); 38 | -------------------------------------------------------------------------------- /web/routes/channels.php: -------------------------------------------------------------------------------- 1 | id === (int) $id; 18 | }); 19 | -------------------------------------------------------------------------------- /web/routes/console.php: -------------------------------------------------------------------------------- 1 | comment(Inspiring::quote()); 19 | })->purpose('Display an inspiring quote'); 20 | -------------------------------------------------------------------------------- /web/server.php: -------------------------------------------------------------------------------- 1 | 8 | */ 9 | 10 | $uri = urldecode( 11 | parse_url($_SERVER['REQUEST_URI'], PHP_URL_PATH) 12 | ); 13 | 14 | // This file allows us to emulate Apache's "mod_rewrite" functionality from the 15 | // built-in PHP web server. This provides a convenient way to test a Laravel 16 | // application without having installed a "real" web server software here. 17 | if ($uri !== '/' && file_exists(__DIR__.'/public'.$uri)) { 18 | return false; 19 | } 20 | 21 | require_once __DIR__.'/public/index.php'; 22 | -------------------------------------------------------------------------------- /web/storage/app/.gitignore: -------------------------------------------------------------------------------- 1 | * 2 | !public/ 3 | !.gitignore 4 | -------------------------------------------------------------------------------- /web/storage/app/public/.gitignore: -------------------------------------------------------------------------------- 1 | * 2 | !.gitignore 3 | -------------------------------------------------------------------------------- /web/storage/framework/.gitignore: -------------------------------------------------------------------------------- 1 | compiled.php 2 | config.php 3 | down 4 | events.scanned.php 5 | maintenance.php 6 | routes.php 7 | routes.scanned.php 8 | schedule-* 9 | services.json 10 | -------------------------------------------------------------------------------- /web/storage/framework/cache/.gitignore: -------------------------------------------------------------------------------- 1 | * 2 | !data/ 3 | !.gitignore 4 | -------------------------------------------------------------------------------- /web/storage/framework/cache/data/.gitignore: -------------------------------------------------------------------------------- 1 | * 2 | !.gitignore 3 | -------------------------------------------------------------------------------- /web/storage/framework/sessions/.gitignore: -------------------------------------------------------------------------------- 1 | * 2 | !.gitignore 3 | -------------------------------------------------------------------------------- /web/storage/framework/testing/.gitignore: -------------------------------------------------------------------------------- 1 | * 2 | !.gitignore 3 | -------------------------------------------------------------------------------- /web/storage/framework/views/.gitignore: -------------------------------------------------------------------------------- 1 | * 2 | !.gitignore 3 | -------------------------------------------------------------------------------- /web/storage/logs/.gitignore: -------------------------------------------------------------------------------- 1 | * 2 | !.gitignore 3 | -------------------------------------------------------------------------------- /web/tailwind.config.js: -------------------------------------------------------------------------------- 1 | const defaultTheme = require('tailwindcss/defaultTheme'); 2 | 3 | module.exports = { 4 | purge: [ 5 | './vendor/laravel/framework/src/Illuminate/Pagination/resources/views/*.blade.php', 6 | './vendor/laravel/jetstream/**/*.blade.php', 7 | './storage/framework/views/*.php', 8 | './resources/views/**/*.blade.php', 9 | ], 10 | 11 | theme: { 12 | extend: { 13 | fontFamily: { 14 | sans: ['Nunito', ...defaultTheme.fontFamily.sans], 15 | }, 16 | }, 17 | }, 18 | 19 | variants: { 20 | extend: { 21 | opacity: ['disabled'], 22 | }, 23 | }, 24 | 25 | plugins: [require('@tailwindcss/forms'), require('@tailwindcss/typography')], 26 | }; 27 | -------------------------------------------------------------------------------- /web/tests/CreatesApplication.php: -------------------------------------------------------------------------------- 1 | make(Kernel::class)->bootstrap(); 19 | 20 | return $app; 21 | } 22 | } 23 | -------------------------------------------------------------------------------- /web/tests/Feature/ApiTokenPermissionsTest.php: -------------------------------------------------------------------------------- 1 | markTestSkipped('API support is not enabled.'); 21 | } 22 | 23 | $this->actingAs($user = User::factory()->withPersonalTeam()->create()); 24 | 25 | $token = $user->tokens()->create([ 26 | 'name' => 'Test Token', 27 | 'token' => Str::random(40), 28 | 'abilities' => ['create', 'read'], 29 | ]); 30 | 31 | Livewire::test(ApiTokenManager::class) 32 | ->set(['managingPermissionsFor' => $token]) 33 | ->set(['updateApiTokenForm' => [ 34 | 'permissions' => [ 35 | 'delete', 36 | 'missing-permission', 37 | ], 38 | ]]) 39 | ->call('updateApiToken'); 40 | 41 | $this->assertTrue($user->fresh()->tokens->first()->can('delete')); 42 | $this->assertFalse($user->fresh()->tokens->first()->can('read')); 43 | $this->assertFalse($user->fresh()->tokens->first()->can('missing-permission')); 44 | } 45 | } 46 | -------------------------------------------------------------------------------- /web/tests/Feature/AuthenticationTest.php: -------------------------------------------------------------------------------- 1 | get('/login'); 17 | 18 | $response->assertStatus(200); 19 | } 20 | 21 | public function test_users_can_authenticate_using_the_login_screen() 22 | { 23 | $user = User::factory()->create(); 24 | 25 | $response = $this->post('/login', [ 26 | 'email' => $user->email, 27 | 'password' => 'password', 28 | ]); 29 | 30 | $this->assertAuthenticated(); 31 | $response->assertRedirect(RouteServiceProvider::HOME); 32 | } 33 | 34 | public function test_users_can_not_authenticate_with_invalid_password() 35 | { 36 | $user = User::factory()->create(); 37 | 38 | $this->post('/login', [ 39 | 'email' => $user->email, 40 | 'password' => 'wrong-password', 41 | ]); 42 | 43 | $this->assertGuest(); 44 | } 45 | } 46 | -------------------------------------------------------------------------------- /web/tests/Feature/BrowserSessionsTest.php: -------------------------------------------------------------------------------- 1 | actingAs($user = User::factory()->create()); 18 | 19 | Livewire::test(LogoutOtherBrowserSessionsForm::class) 20 | ->set('password', 'password') 21 | ->call('logoutOtherBrowserSessions'); 22 | } 23 | } 24 | -------------------------------------------------------------------------------- /web/tests/Feature/CreateApiTokenTest.php: -------------------------------------------------------------------------------- 1 | markTestSkipped('API support is not enabled.'); 20 | } 21 | 22 | $this->actingAs($user = User::factory()->withPersonalTeam()->create()); 23 | 24 | Livewire::test(ApiTokenManager::class) 25 | ->set(['createApiTokenForm' => [ 26 | 'name' => 'Test Token', 27 | 'permissions' => [ 28 | 'read', 29 | 'update', 30 | ], 31 | ]]) 32 | ->call('createApiToken'); 33 | 34 | $this->assertCount(1, $user->fresh()->tokens); 35 | $this->assertEquals('Test Token', $user->fresh()->tokens->first()->name); 36 | $this->assertTrue($user->fresh()->tokens->first()->can('read')); 37 | $this->assertFalse($user->fresh()->tokens->first()->can('delete')); 38 | } 39 | } 40 | -------------------------------------------------------------------------------- /web/tests/Feature/DeleteAccountTest.php: -------------------------------------------------------------------------------- 1 | markTestSkipped('Account deletion is not enabled.'); 20 | } 21 | 22 | $this->actingAs($user = User::factory()->create()); 23 | 24 | $component = Livewire::test(DeleteUserForm::class) 25 | ->set('password', 'password') 26 | ->call('deleteUser'); 27 | 28 | $this->assertNull($user->fresh()); 29 | } 30 | 31 | public function test_correct_password_must_be_provided_before_account_can_be_deleted() 32 | { 33 | if (! Features::hasAccountDeletionFeatures()) { 34 | return $this->markTestSkipped('Account deletion is not enabled.'); 35 | } 36 | 37 | $this->actingAs($user = User::factory()->create()); 38 | 39 | Livewire::test(DeleteUserForm::class) 40 | ->set('password', 'wrong-password') 41 | ->call('deleteUser') 42 | ->assertHasErrors(['password']); 43 | 44 | $this->assertNotNull($user->fresh()); 45 | } 46 | } 47 | -------------------------------------------------------------------------------- /web/tests/Feature/DeleteApiTokenTest.php: -------------------------------------------------------------------------------- 1 | markTestSkipped('API support is not enabled.'); 21 | } 22 | 23 | $this->actingAs($user = User::factory()->withPersonalTeam()->create()); 24 | 25 | $token = $user->tokens()->create([ 26 | 'name' => 'Test Token', 27 | 'token' => Str::random(40), 28 | 'abilities' => ['create', 'read'], 29 | ]); 30 | 31 | Livewire::test(ApiTokenManager::class) 32 | ->set(['apiTokenIdBeingDeleted' => $token->id]) 33 | ->call('deleteApiToken'); 34 | 35 | $this->assertCount(0, $user->fresh()->tokens); 36 | } 37 | } 38 | -------------------------------------------------------------------------------- /web/tests/Feature/ExampleTest.php: -------------------------------------------------------------------------------- 1 | get('/'); 18 | 19 | $response->assertStatus(200); 20 | } 21 | } 22 | -------------------------------------------------------------------------------- /web/tests/Feature/PasswordConfirmationTest.php: -------------------------------------------------------------------------------- 1 | withPersonalTeam()->create() 18 | : User::factory()->create(); 19 | 20 | $response = $this->actingAs($user)->get('/user/confirm-password'); 21 | 22 | $response->assertStatus(200); 23 | } 24 | 25 | public function test_password_can_be_confirmed() 26 | { 27 | $user = User::factory()->create(); 28 | 29 | $response = $this->actingAs($user)->post('/user/confirm-password', [ 30 | 'password' => 'password', 31 | ]); 32 | 33 | $response->assertRedirect(); 34 | $response->assertSessionHasNoErrors(); 35 | } 36 | 37 | public function test_password_is_not_confirmed_with_invalid_password() 38 | { 39 | $user = User::factory()->create(); 40 | 41 | $response = $this->actingAs($user)->post('/user/confirm-password', [ 42 | 'password' => 'wrong-password', 43 | ]); 44 | 45 | $response->assertSessionHasErrors(); 46 | } 47 | } 48 | -------------------------------------------------------------------------------- /web/tests/Feature/ProfileInformationTest.php: -------------------------------------------------------------------------------- 1 | actingAs($user = User::factory()->create()); 18 | 19 | $component = Livewire::test(UpdateProfileInformationForm::class); 20 | 21 | $this->assertEquals($user->name, $component->state['name']); 22 | $this->assertEquals($user->email, $component->state['email']); 23 | } 24 | 25 | public function test_profile_information_can_be_updated() 26 | { 27 | $this->actingAs($user = User::factory()->create()); 28 | 29 | Livewire::test(UpdateProfileInformationForm::class) 30 | ->set('state', ['name' => 'Test Name', 'email' => 'test@example.com']) 31 | ->call('updateProfileInformation'); 32 | 33 | $this->assertEquals('Test Name', $user->fresh()->name); 34 | $this->assertEquals('test@example.com', $user->fresh()->email); 35 | } 36 | } 37 | -------------------------------------------------------------------------------- /web/tests/Feature/RegistrationTest.php: -------------------------------------------------------------------------------- 1 | get('/register'); 17 | 18 | $response->assertStatus(200); 19 | } 20 | 21 | public function test_new_users_can_register() 22 | { 23 | $response = $this->post('/register', [ 24 | 'name' => 'Test User', 25 | 'email' => 'test@example.com', 26 | 'password' => 'password', 27 | 'password_confirmation' => 'password', 28 | 'terms' => Jetstream::hasTermsAndPrivacyPolicyFeature(), 29 | ]); 30 | 31 | $this->assertAuthenticated(); 32 | $response->assertRedirect(RouteServiceProvider::HOME); 33 | } 34 | } 35 | -------------------------------------------------------------------------------- /web/tests/Feature/TwoFactorAuthenticationSettingsTest.php: -------------------------------------------------------------------------------- 1 | actingAs($user = User::factory()->create()); 18 | 19 | $this->withSession(['auth.password_confirmed_at' => time()]); 20 | 21 | Livewire::test(TwoFactorAuthenticationForm::class) 22 | ->call('enableTwoFactorAuthentication'); 23 | 24 | $user = $user->fresh(); 25 | 26 | $this->assertNotNull($user->two_factor_secret); 27 | $this->assertCount(8, $user->recoveryCodes()); 28 | } 29 | 30 | public function test_recovery_codes_can_be_regenerated() 31 | { 32 | $this->actingAs($user = User::factory()->create()); 33 | 34 | $this->withSession(['auth.password_confirmed_at' => time()]); 35 | 36 | $component = Livewire::test(TwoFactorAuthenticationForm::class) 37 | ->call('enableTwoFactorAuthentication') 38 | ->call('regenerateRecoveryCodes'); 39 | 40 | $user = $user->fresh(); 41 | 42 | $component->call('regenerateRecoveryCodes'); 43 | 44 | $this->assertCount(8, $user->recoveryCodes()); 45 | $this->assertCount(8, array_diff($user->recoveryCodes(), $user->fresh()->recoveryCodes())); 46 | } 47 | 48 | public function test_two_factor_authentication_can_be_disabled() 49 | { 50 | $this->actingAs($user = User::factory()->create()); 51 | 52 | $this->withSession(['auth.password_confirmed_at' => time()]); 53 | 54 | $component = Livewire::test(TwoFactorAuthenticationForm::class) 55 | ->call('enableTwoFactorAuthentication'); 56 | 57 | $this->assertNotNull($user->fresh()->two_factor_secret); 58 | 59 | $component->call('disableTwoFactorAuthentication'); 60 | 61 | $this->assertNull($user->fresh()->two_factor_secret); 62 | } 63 | } 64 | -------------------------------------------------------------------------------- /web/tests/TestCase.php: -------------------------------------------------------------------------------- 1 | assertTrue(true); 17 | } 18 | } 19 | -------------------------------------------------------------------------------- /web/webpack.mix.js: -------------------------------------------------------------------------------- 1 | const mix = require('laravel-mix'); 2 | 3 | /* 4 | |-------------------------------------------------------------------------- 5 | | Mix Asset Management 6 | |-------------------------------------------------------------------------- 7 | | 8 | | Mix provides a clean, fluent API for defining some Webpack build steps 9 | | for your Laravel applications. By default, we are compiling the CSS 10 | | file for the application as well as bundling up all the JS files. 11 | | 12 | */ 13 | 14 | mix.js('resources/js/app.js', 'public/js') 15 | .postCss('resources/css/app.css', 'public/css', [ 16 | require('postcss-import'), 17 | require('tailwindcss'), 18 | require('autoprefixer'), 19 | ]); 20 | 21 | if (mix.inProduction()) { 22 | mix.version(); 23 | } 24 | --------------------------------------------------------------------------------