├── .dockerignore ├── .editorconfig ├── .env.example ├── .gitattributes ├── .gitignore ├── Dockerfile ├── LICENSE.md ├── README.md ├── app ├── Console │ └── Kernel.php ├── Exceptions │ └── Handler.php ├── Http │ ├── Controllers │ │ └── Controller.php │ ├── Kernel.php │ └── Middleware │ │ ├── Authenticate.php │ │ ├── EncryptCookies.php │ │ ├── PreventRequestsDuringMaintenance.php │ │ ├── RedirectIfAuthenticated.php │ │ ├── TrimStrings.php │ │ ├── TrustHosts.php │ │ ├── TrustProxies.php │ │ ├── ValidateSignature.php │ │ └── VerifyCsrfToken.php ├── Modules │ ├── Admin │ │ ├── AdminRouteRegistrar.php │ │ ├── AdminServiceProvider.php │ │ ├── Endpoints │ │ │ ├── CreateGiftCard.php │ │ │ ├── CreatePrompt.php │ │ │ ├── CreateTag.php │ │ │ ├── DeleteGiftCard.php │ │ │ ├── DeletePrompt.php │ │ │ ├── DeleteTag.php │ │ │ ├── DeleteUser.php │ │ │ ├── GetStats.php │ │ │ ├── GetUser.php │ │ │ ├── ListConversations.php │ │ │ ├── ListGiftCards.php │ │ │ ├── ListMessages.php │ │ │ ├── ListPayments.php │ │ │ ├── ListPrompts.php │ │ │ ├── ListRewards.php │ │ │ ├── ListTags.php │ │ │ ├── ListUsers.php │ │ │ ├── UpdateGiftCard.php │ │ │ ├── UpdatePrompt.php │ │ │ ├── UpdateTag.php │ │ │ └── UpdateUser.php │ │ └── Middlewares │ │ │ └── MustBeAdmin.php │ ├── Auth │ │ ├── AuthRouteRegistrar.php │ │ ├── AuthServiceProvider.php │ │ ├── ClientManager.php │ │ ├── Endpoints │ │ │ ├── CreatePersonalToken.php │ │ │ ├── CreateTokenViaCode.php │ │ │ ├── CreateTokenViaSms.php │ │ │ ├── ListTokens.php │ │ │ ├── PurgeTokens.php │ │ │ ├── Redirect.php │ │ │ └── RevokeToken.php │ │ ├── Requests │ │ │ ├── CreateTokenViaCodeRequest.php │ │ │ ├── CreateTokenViaSmsRequest.php │ │ │ └── RedirectRequest.php │ │ └── Tests │ │ │ ├── CreateClientTest.php │ │ │ ├── CreateClientTokenTest.php │ │ │ ├── CreateTokenViaCodeTest.php │ │ │ ├── CreateTokenViaSmsTest.php │ │ │ ├── DeleteClientTest.php │ │ │ └── ListUserClientsTest.php │ ├── Chat │ │ ├── Actions │ │ │ └── RefreshConversation.php │ │ ├── ChatRouteRegistrar.php │ │ ├── ChatServiceProvider.php │ │ ├── Conversation.php │ │ ├── ConversationFactory.php │ │ ├── ConversationPolicy.php │ │ ├── Endpoints │ │ │ ├── CreateCompletion.php │ │ │ ├── CreateConversation.php │ │ │ ├── CreateMessage.php │ │ │ ├── DeleteConversation.php │ │ │ ├── GetConversation.php │ │ │ ├── ListConversationMessages.php │ │ │ ├── ListConversations.php │ │ │ ├── ToggleLikeMessage.php │ │ │ ├── TruncateConversation.php │ │ │ └── UpdateConversation.php │ │ ├── Enums │ │ │ └── MessageRole.php │ │ ├── Filters │ │ │ ├── ConversationFilter.php │ │ │ └── MessageFilter.php │ │ ├── Jobs │ │ │ └── SummarizeConversation.php │ │ ├── Message.php │ │ ├── MessageFactory.php │ │ ├── Requests │ │ │ ├── CreateConversationRequest.php │ │ │ ├── CreateMessageRequest.php │ │ │ └── UpdateConversationRequest.php │ │ └── Tests │ │ │ ├── CreateCompletionTest.php │ │ │ ├── CreateConversationTest.php │ │ │ ├── CreateMessageTest.php │ │ │ ├── DeleteConversationTest.php │ │ │ ├── GetConversationTest.php │ │ │ ├── ListConversationMessagesTest.php │ │ │ ├── ListConversationsTest.php │ │ │ ├── ToggleLikeMessageTest.php │ │ │ ├── TruncateConversationTest.php │ │ │ └── UpdateConversationTest.php │ ├── Common │ │ ├── Actions │ │ │ ├── Action.php │ │ │ ├── ConvertStringToArray.php │ │ │ └── FakeAction.php │ │ ├── Endpoints │ │ │ └── Endpoint.php │ │ ├── RenderWithContext.php │ │ └── Traits │ │ │ └── Sortable.php │ ├── GiftCard │ │ ├── Actions │ │ │ └── AssignGiftCardToUser.php │ │ ├── Commands │ │ │ └── MakeGiftCard.php │ │ ├── Endpoints │ │ │ └── ActivateGiftCard.php │ │ ├── Filters │ │ │ └── GiftCardFilter.php │ │ ├── GiftCard.php │ │ ├── GiftCardFactory.php │ │ ├── GiftCardRouterRegistrar.php │ │ ├── GiftCardServiceProvider.php │ │ └── Tests │ │ │ ├── ActivateGiftCardTest.php │ │ │ └── MakeGiftCardTest.php │ ├── Leaderboard │ │ ├── Endpoints │ │ │ └── ListLeaderboards.php │ │ ├── LeaderboardRouteRegistrar.php │ │ ├── LeaderboardServiceProvider.php │ │ └── Tests │ │ │ └── ListLeaderboardsTest.php │ ├── Payment │ │ ├── Actions │ │ │ ├── CreatePaymentOrderNumber.php │ │ │ ├── MarkPaymentAsExpired.php │ │ │ └── MarkPaymentAsPaid.php │ │ ├── Command │ │ │ └── CheckPayment.php │ │ ├── Endpoints │ │ │ ├── CreatePayment.php │ │ │ ├── GetPayment.php │ │ │ ├── ListPayments.php │ │ │ └── ProcessPayment.php │ │ ├── Enums │ │ │ ├── Gateway.php │ │ │ └── PaymentState.php │ │ ├── Exceptions │ │ │ ├── GatewayException.php │ │ │ └── InvalidArgumentException.php │ │ ├── Filters │ │ │ └── PaymentFilter.php │ │ ├── GatewayManager.php │ │ ├── Gateways │ │ │ ├── GatewayInterface.php │ │ │ └── Payjs │ │ │ │ └── Gateway.php │ │ ├── Jobs │ │ │ └── MarkPaymentAsExpired.php │ │ ├── Payment.php │ │ ├── PaymentFactory.php │ │ ├── PaymentPolicy.php │ │ ├── PaymentRouteRegistrar.php │ │ ├── PaymentServiceProvider.php │ │ ├── Processors │ │ │ ├── GrantQuotaProcessor.php │ │ │ └── Processor.php │ │ ├── Requests │ │ │ └── CreatePaymentRequest.php │ │ └── Tests │ │ │ ├── CreatePaymentTest.php │ │ │ ├── GetPaymentTest.php │ │ │ ├── ListPaymentsTest.php │ │ │ └── ProcessPaymentTest.php │ ├── Prompt │ │ ├── Endpoints │ │ │ ├── CreatePrompt.php │ │ │ ├── DeletePrompt.php │ │ │ ├── GetPrompt.php │ │ │ ├── ListFeaturedPrompts.php │ │ │ ├── ListPrompts.php │ │ │ ├── ListUserPrompts.php │ │ │ └── UpdatePrompt.php │ │ ├── Filters │ │ │ └── PromptFilter.php │ │ ├── Prompt.php │ │ ├── PromptFactory.php │ │ ├── PromptRouteRegistrar.php │ │ ├── PromptServiceProvider.php │ │ ├── Requests │ │ │ ├── CreatePromptRequest.php │ │ │ └── UpdatePromptRequest.php │ │ └── Tests │ │ │ ├── CreatePromptTest.php │ │ │ ├── DeletePromptTest.php │ │ │ ├── ListPromptsTest.php │ │ │ └── UpdatePromptTest.php │ ├── Quota │ │ ├── Actions │ │ │ ├── CreateQuotaUsage.php │ │ │ └── GrantUserQuota.php │ │ ├── BelongsToQuota.php │ │ ├── Commands │ │ │ └── CheckQuota.php │ │ ├── Endpoints │ │ │ ├── ActivateQuota.php │ │ │ ├── ListPricings.php │ │ │ └── ListQuotas.php │ │ ├── Enums │ │ │ └── QuotaState.php │ │ ├── Exceptions │ │ │ └── QuotaException.php │ │ ├── Filters │ │ │ └── QuotaFilter.php │ │ ├── Jobs │ │ │ └── RefreshQuota.php │ │ ├── Listeners │ │ │ └── GrantFreeQuotas.php │ │ ├── Middlewares │ │ │ └── CheckQuota.php │ │ ├── Policies │ │ │ └── QuotaPolicy.php │ │ ├── Quota.php │ │ ├── QuotaFactory.php │ │ ├── QuotaRouteRegistrar.php │ │ ├── QuotaServiceProvider.php │ │ ├── QuotaUsage.php │ │ ├── Tests │ │ │ ├── ListPricingsTest.php │ │ │ └── ListQuotasTest.php │ │ ├── Tokenizable.php │ │ └── TokenizableInterface.php │ ├── Reward │ │ ├── Actions │ │ │ ├── CreateReward.php │ │ │ └── CreateRewardFromPayment.php │ │ ├── Endpoints │ │ │ └── ListRewards.php │ │ ├── Enums │ │ │ └── RewardState.php │ │ ├── Events │ │ │ └── RewardCreated.php │ │ ├── Filters │ │ │ └── RewardFilter.php │ │ ├── Reward.php │ │ ├── RewardFactory.php │ │ ├── RewardRouteRegistrar.php │ │ ├── RewardServiceProvider.php │ │ └── Tests │ │ │ └── ListRewardsTest.php │ ├── Security │ │ ├── Actions │ │ │ ├── CheckSize.php │ │ │ ├── DecryptString.php │ │ │ └── EncryptString.php │ │ ├── Middlewares │ │ │ ├── SetRequestAccept.php │ │ │ ├── SetRequestUser.php │ │ │ └── ThrottleRequestsWithRedis.php │ │ ├── Rules │ │ │ └── ValidString.php │ │ ├── SecurityServiceProvider.php │ │ └── Watchdog.php │ ├── Service │ │ ├── Log │ │ │ ├── Listeners │ │ │ │ └── HttpClientListener.php │ │ │ ├── LogEventServiceProvider.php │ │ │ └── Middlewares │ │ │ │ └── RequestLogger.php │ │ ├── OpenAI │ │ │ ├── FakeClient.php │ │ │ ├── OpenAIServiceProvider.php │ │ │ ├── Tokenizer.php │ │ │ └── TokenizerException.php │ │ ├── Snowflake │ │ │ ├── HasSnowflakes.php │ │ │ └── SnowflakeServiceProvider.php │ │ ├── Socialite │ │ │ ├── Providers │ │ │ │ ├── GitHub.php │ │ │ │ └── Google.php │ │ │ └── SocialiteServiceProvider.php │ │ └── State │ │ │ ├── StateManager.php │ │ │ └── StateServiceProvider.php │ ├── Sms │ │ ├── Actions │ │ │ ├── IsValidPhoneNumber.php │ │ │ └── ParsePhoneNumber.php │ │ ├── Endpoints │ │ │ └── SendVerificationCode.php │ │ ├── Enums │ │ │ └── VerificationCodeScene.php │ │ ├── FakeVerificationCode.php │ │ ├── Requests │ │ │ └── SendVerificationCodeRequest.php │ │ ├── Rules │ │ │ ├── ValidPhoneNumber.php │ │ │ └── ValidVerificationCode.php │ │ ├── SmsRouteRegistrar.php │ │ ├── SmsServiceProvider.php │ │ ├── Tests │ │ │ └── SendVerificationCodeTest.php │ │ └── VerificationCode.php │ ├── Tag │ │ ├── Endpoints │ │ │ └── ListTags.php │ │ ├── Filters │ │ │ └── TagFilter.php │ │ ├── Tag.php │ │ ├── TagFactory.php │ │ ├── TagRouterRegistrar.php │ │ ├── TagServiceProvider.php │ │ └── Tests │ │ │ └── ListTagsTest.php │ └── User │ │ ├── Actions │ │ ├── GetUserRewardsTotal.php │ │ ├── GetUserUnwithdrawnRewardsTotal.php │ │ ├── RefreshUserPaidTotal.php │ │ └── RefreshUserReferrer.php │ │ ├── BelongsToCreator.php │ │ ├── Endpoints │ │ ├── ActivateUser.php │ │ ├── GetStats.php │ │ ├── GetUser.php │ │ ├── GetUserQuota.php │ │ ├── ListReferrals.php │ │ ├── ListSettings.php │ │ ├── UpdateSetting.php │ │ ├── UpdateUser.php │ │ └── UpdateUserSetting.php │ │ ├── Enums │ │ ├── SettingKey.php │ │ └── UserState.php │ │ ├── Events │ │ ├── UserActivated.php │ │ └── UserCreated.php │ │ ├── Exceptions │ │ └── InvalidStateException.php │ │ ├── Filters │ │ └── UserFilter.php │ │ ├── Listeners │ │ └── CreateUserSettings.php │ │ ├── Middlewares │ │ ├── CheckUserState.php │ │ └── RefreshUserActiveAt.php │ │ ├── Policies │ │ └── UserPolicy.php │ │ ├── Profile.php │ │ ├── Requests │ │ ├── ActivateUserRequest.php │ │ └── UpdateUserRequest.php │ │ ├── Tests │ │ ├── ActivateUserTest.php │ │ ├── GetQuotaTest.php │ │ ├── GetUserTest.php │ │ ├── ListReferralsTest.php │ │ ├── ListSettingsTest.php │ │ ├── MarkUserAsActivatedTest.php │ │ ├── UpdateUserSettingTest.php │ │ └── UpdateUserTest.php │ │ ├── User.php │ │ ├── UserEventServiceProvider.php │ │ ├── UserFactory.php │ │ ├── UserRouteRegistrar.php │ │ ├── UserServiceProvider.php │ │ └── UserSetting.php └── Providers │ ├── AppServiceProvider.php │ ├── AuthServiceProvider.php │ ├── BroadcastServiceProvider.php │ ├── EventServiceProvider.php │ └── RouteServiceProvider.php ├── artisan ├── bootstrap ├── app.php └── cache │ └── .gitignore ├── composer.json ├── composer.lock ├── config ├── app.php ├── auth.php ├── broadcasting.php ├── cache.php ├── cors.php ├── database.php ├── eloquentfilter.php ├── filesystems.php ├── hashids.php ├── hashing.php ├── like.php ├── logging.php ├── mail.php ├── morphs.php ├── openai.php ├── passport.php ├── payment.php ├── queue.php ├── quota.php ├── services.php ├── session.php ├── sms.php ├── socialite.php └── view.php ├── database ├── .gitignore ├── migrations │ ├── 2014_10_12_000000_create_users_table.php │ ├── 2014_10_12_100000_create_password_reset_tokens_table.php │ ├── 2016_06_01_000001_create_oauth_auth_codes_table.php │ ├── 2016_06_01_000002_create_oauth_access_tokens_table.php │ ├── 2016_06_01_000003_create_oauth_refresh_tokens_table.php │ ├── 2016_06_01_000004_create_oauth_clients_table.php │ ├── 2016_06_01_000005_create_oauth_personal_access_clients_table.php │ ├── 2018_12_14_000000_create_likes_table.php │ ├── 2019_08_19_000000_create_failed_jobs_table.php │ ├── 2019_12_14_000001_create_personal_access_tokens_table.php │ ├── 2023_04_05_135522_create_messages_table.php │ ├── 2023_04_05_135532_create_conversations_table.php │ ├── 2023_04_06_213947_create_quotas_table.php │ ├── 2023_04_07_151819_create_payments_table.php │ ├── 2023_04_10_225239_add_paid_at_to_payments_table.php │ ├── 2023_04_11_154234_create_user_settings_table.php │ ├── 2023_04_11_234900_add_state_to_users_table.php │ ├── 2023_04_17_170137_add_creator_id_to_messages_table.php │ ├── 2023_04_17_190002_add_tokens_count_to_quotas_table.php │ ├── 2023_04_17_190245_create_quota_statements_table.php │ ├── 2023_04_18_141745_rename_quota_statements_to_quota_usages_table.php │ ├── 2023_04_19_223226_add_paid_total_to_users_table.php │ ├── 2023_04_21_213759_rename_raws_to_raw.php │ ├── 2023_04_21_214102_create_profiles_table.php │ ├── 2023_04_22_230318_add_avatar_to_users_table.php │ ├── 2023_04_26_115133_create_gift_cards_table.php │ ├── 2023_04_28_184904_add_state_to_quotas.php │ ├── 2023_05_10_215819_create_prompts_table.php │ ├── 2023_05_20_113551_add_email_to_users_table.php │ ├── 2023_05_27_232353_create_tags_table.php │ ├── 2023_05_29_225623_add_prompt_id_to_conversations_table.php │ ├── 2023_06_06_222158_add_greeting_to_prompts_table.php │ ├── 2023_06_09_153549_create_rewards_table.php │ ├── 2023_06_14_214734_update_profiles_unique_key.php │ ├── 2023_07_03_215652_add_deleted_at_tp_payments.php │ └── 2023_07_04_130119_update_rewards_amount_to_decimal.php └── seeders │ ├── DatabaseSeeder.php │ └── PromptSeeder.php ├── docker-compose.yaml ├── docker ├── .gitignore ├── Dockerfile ├── cert │ └── ca.sh ├── config │ ├── cron.d │ │ ├── custom-schedule │ │ └── laravel-schedule │ ├── nginx │ │ ├── configs │ │ │ ├── general.conf │ │ │ ├── php_fastcgi.conf │ │ │ └── security.conf │ │ ├── nginx.conf │ │ └── www.conf │ ├── php │ │ ├── cli │ │ │ └── php.ini │ │ └── fpm │ │ │ ├── php-fpm.conf │ │ │ ├── php.ini │ │ │ └── pool.d │ │ │ └── www.conf │ └── supervisord │ │ └── worker.conf ├── readme.md └── script │ ├── start-all │ ├── start-container │ ├── start-crond │ └── start-queue ├── package.json ├── phpunit.xml ├── public ├── .htaccess ├── favicon.ico ├── index.php └── robots.txt ├── resources ├── css │ └── app.css ├── js │ ├── app.js │ └── bootstrap.js └── views │ └── welcome.blade.php ├── routes ├── api.php ├── channels.php ├── console.php └── web.php ├── storage ├── app │ ├── .gitignore │ └── public │ │ └── .gitignore ├── framework │ ├── .gitignore │ ├── cache │ │ ├── .gitignore │ │ └── data │ │ │ └── .gitignore │ ├── sessions │ │ └── .gitignore │ ├── testing │ │ └── .gitignore │ └── views │ │ └── .gitignore └── logs │ └── .gitignore ├── tests ├── CreatesApplication.php ├── Feature │ └── ExampleTest.php ├── TestCase.php └── Unit │ └── ExampleTest.php └── vite.config.js /.dockerignore: -------------------------------------------------------------------------------- 1 | docker 2 | docker-compose.yaml 3 | Dockerfile 4 | storage/logs/* 5 | -------------------------------------------------------------------------------- /.editorconfig: -------------------------------------------------------------------------------- 1 | root = true 2 | 3 | [*] 4 | charset = utf-8 5 | end_of_line = lf 6 | indent_size = 4 7 | indent_style = space 8 | insert_final_newline = true 9 | trim_trailing_whitespace = true 10 | 11 | [*.md] 12 | trim_trailing_whitespace = false 13 | 14 | [*.{yml,yaml}] 15 | indent_size = 2 16 | 17 | [docker-compose.yml] 18 | indent_size = 4 19 | -------------------------------------------------------------------------------- /.gitattributes: -------------------------------------------------------------------------------- 1 | * text=auto eol=lf 2 | 3 | *.blade.php diff=html 4 | *.css diff=css 5 | *.html diff=html 6 | *.md diff=markdown 7 | *.php diff=php 8 | 9 | /.github export-ignore 10 | CHANGELOG.md export-ignore 11 | .styleci.yml export-ignore 12 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | /.phpunit.cache 2 | /node_modules 3 | /public/build 4 | /public/hot 5 | /public/storage 6 | /storage/*.key 7 | /vendor 8 | .env 9 | .env.backup 10 | .env.production 11 | .phpunit.result.cache 12 | Homestead.json 13 | Homestead.yaml 14 | auth.json 15 | npm-debug.log 16 | yarn-error.log 17 | /.fleet 18 | /.idea 19 | /.vscode 20 | -------------------------------------------------------------------------------- /Dockerfile: -------------------------------------------------------------------------------- 1 | FROM yikeio/server-core:latest 2 | 3 | WORKDIR /data/www 4 | 5 | COPY . . 6 | 7 | RUN chown -R www-data:www-data /data/www 8 | 9 | ENTRYPOINT [ "/usr/local/bin/start-all" ] 10 | -------------------------------------------------------------------------------- /LICENSE.md: -------------------------------------------------------------------------------- 1 | The MIT License (MIT) 2 | 3 | Copyright (c) 2023 overtrue and other contributors 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /app/Console/Kernel.php: -------------------------------------------------------------------------------- 1 | command('inspire')->hourly(); 23 | 24 | $schedule->command(CheckQuota::class)->everyMinute()->withoutOverlapping(); 25 | $schedule->command(CheckPayment::class)->everyMinute()->withoutOverlapping(); 26 | } 27 | 28 | /** 29 | * Register the commands for the application. 30 | */ 31 | protected function commands(): void 32 | { 33 | $this->load(__DIR__.'/Commands'); 34 | 35 | require base_path('routes/console.php'); 36 | } 37 | } 38 | -------------------------------------------------------------------------------- /app/Http/Controllers/Controller.php: -------------------------------------------------------------------------------- 1 | expectsJson() ? null : route('login'); 16 | } 17 | } 18 | -------------------------------------------------------------------------------- /app/Http/Middleware/EncryptCookies.php: -------------------------------------------------------------------------------- 1 | 13 | */ 14 | protected $except = [ 15 | // 16 | ]; 17 | } 18 | -------------------------------------------------------------------------------- /app/Http/Middleware/PreventRequestsDuringMaintenance.php: -------------------------------------------------------------------------------- 1 | 13 | */ 14 | protected $except = [ 15 | // 16 | ]; 17 | } 18 | -------------------------------------------------------------------------------- /app/Http/Middleware/RedirectIfAuthenticated.php: -------------------------------------------------------------------------------- 1 | check()) { 24 | return redirect(RouteServiceProvider::HOME); 25 | } 26 | } 27 | 28 | return $next($request); 29 | } 30 | } 31 | -------------------------------------------------------------------------------- /app/Http/Middleware/TrimStrings.php: -------------------------------------------------------------------------------- 1 | 13 | */ 14 | protected $except = [ 15 | 'current_password', 16 | 'password', 17 | 'password_confirmation', 18 | ]; 19 | } 20 | -------------------------------------------------------------------------------- /app/Http/Middleware/TrustHosts.php: -------------------------------------------------------------------------------- 1 | 13 | */ 14 | public function hosts(): array 15 | { 16 | return [ 17 | $this->allSubdomainsOfApplicationUrl(), 18 | ]; 19 | } 20 | } 21 | -------------------------------------------------------------------------------- /app/Http/Middleware/TrustProxies.php: -------------------------------------------------------------------------------- 1 | |string|null 14 | */ 15 | protected $proxies; 16 | 17 | /** 18 | * The headers that should be used to detect proxies. 19 | * 20 | * @var int 21 | */ 22 | protected $headers = 23 | Request::HEADER_X_FORWARDED_FOR | 24 | Request::HEADER_X_FORWARDED_HOST | 25 | Request::HEADER_X_FORWARDED_PORT | 26 | Request::HEADER_X_FORWARDED_PROTO | 27 | Request::HEADER_X_FORWARDED_AWS_ELB; 28 | } 29 | -------------------------------------------------------------------------------- /app/Http/Middleware/ValidateSignature.php: -------------------------------------------------------------------------------- 1 | 13 | */ 14 | protected $except = [ 15 | // 'fbclid', 16 | // 'utm_campaign', 17 | // 'utm_content', 18 | // 'utm_medium', 19 | // 'utm_source', 20 | // 'utm_term', 21 | ]; 22 | } 23 | -------------------------------------------------------------------------------- /app/Http/Middleware/VerifyCsrfToken.php: -------------------------------------------------------------------------------- 1 | 13 | */ 14 | protected $except = [ 15 | // 16 | ]; 17 | } 18 | -------------------------------------------------------------------------------- /app/Modules/Admin/AdminServiceProvider.php: -------------------------------------------------------------------------------- 1 | all())->refresh(); 13 | } 14 | } 15 | -------------------------------------------------------------------------------- /app/Modules/Admin/Endpoints/CreatePrompt.php: -------------------------------------------------------------------------------- 1 | all())->refresh(); 13 | } 14 | } 15 | -------------------------------------------------------------------------------- /app/Modules/Admin/Endpoints/CreateTag.php: -------------------------------------------------------------------------------- 1 | all())->refresh(); 13 | } 14 | } 15 | -------------------------------------------------------------------------------- /app/Modules/Admin/Endpoints/DeleteGiftCard.php: -------------------------------------------------------------------------------- 1 | delete(); 14 | 15 | return response()->noContent(); 16 | } 17 | } 18 | -------------------------------------------------------------------------------- /app/Modules/Admin/Endpoints/DeletePrompt.php: -------------------------------------------------------------------------------- 1 | delete(); 14 | 15 | return response()->noContent(); 16 | } 17 | } 18 | -------------------------------------------------------------------------------- /app/Modules/Admin/Endpoints/DeleteTag.php: -------------------------------------------------------------------------------- 1 | delete(); 14 | 15 | return response()->noContent(); 16 | } 17 | } 18 | -------------------------------------------------------------------------------- /app/Modules/Admin/Endpoints/DeleteUser.php: -------------------------------------------------------------------------------- 1 | delete(); 14 | 15 | return response()->noContent(); 16 | } 17 | } 18 | -------------------------------------------------------------------------------- /app/Modules/Admin/Endpoints/GetUser.php: -------------------------------------------------------------------------------- 1 | user(); 13 | } 14 | } 15 | -------------------------------------------------------------------------------- /app/Modules/Admin/Endpoints/ListConversations.php: -------------------------------------------------------------------------------- 1 | latest('created_at')->filter($request->query())->paginate(15); 13 | } 14 | } 15 | -------------------------------------------------------------------------------- /app/Modules/Admin/Endpoints/ListGiftCards.php: -------------------------------------------------------------------------------- 1 | filter($request->query())->latest('created_at')->paginate(15); 13 | } 14 | } 15 | -------------------------------------------------------------------------------- /app/Modules/Admin/Endpoints/ListMessages.php: -------------------------------------------------------------------------------- 1 | filter($request->query())->latest('created_at')->paginate(15); 13 | } 14 | } 15 | -------------------------------------------------------------------------------- /app/Modules/Admin/Endpoints/ListPayments.php: -------------------------------------------------------------------------------- 1 | filter($request->query())->latest('created_at')->paginate(15); 13 | } 14 | } 15 | -------------------------------------------------------------------------------- /app/Modules/Admin/Endpoints/ListPrompts.php: -------------------------------------------------------------------------------- 1 | withCount('conversations')->latest('created_at')->filter($request->query())->paginate(15); 13 | } 14 | } 15 | -------------------------------------------------------------------------------- /app/Modules/Admin/Endpoints/ListRewards.php: -------------------------------------------------------------------------------- 1 | filter($request->query())->latest('created_at')->paginate(15); 14 | } 15 | } 16 | -------------------------------------------------------------------------------- /app/Modules/Admin/Endpoints/ListTags.php: -------------------------------------------------------------------------------- 1 | query())->paginate(15); 13 | } 14 | } 15 | -------------------------------------------------------------------------------- /app/Modules/Admin/Endpoints/ListUsers.php: -------------------------------------------------------------------------------- 1 | with(['referrer'])->latest('created_at')->filter($request->query())->paginate(15), function ($resource) { 13 | $resource->makeVisible(['phone_number', 'first_active_at', 'last_active_at', 'paid_total']); 14 | }); 15 | } 16 | } 17 | -------------------------------------------------------------------------------- /app/Modules/Admin/Endpoints/UpdateGiftCard.php: -------------------------------------------------------------------------------- 1 | update($request->all()); 13 | 14 | return $giftCard; 15 | } 16 | } 17 | -------------------------------------------------------------------------------- /app/Modules/Admin/Endpoints/UpdatePrompt.php: -------------------------------------------------------------------------------- 1 | update($request->all()); 13 | 14 | return $prompt; 15 | } 16 | } 17 | -------------------------------------------------------------------------------- /app/Modules/Admin/Endpoints/UpdateTag.php: -------------------------------------------------------------------------------- 1 | update($request->all()); 13 | 14 | return $tag; 15 | } 16 | } 17 | -------------------------------------------------------------------------------- /app/Modules/Admin/Endpoints/UpdateUser.php: -------------------------------------------------------------------------------- 1 | update($request->all()); 13 | 14 | return $user; 15 | } 16 | } 17 | -------------------------------------------------------------------------------- /app/Modules/Admin/Middlewares/MustBeAdmin.php: -------------------------------------------------------------------------------- 1 | user()->isAdmin()) { 10 | abort(403, 'You are not authorized to access this resource.'); 11 | } 12 | 13 | return $next($request); 14 | } 15 | } 16 | -------------------------------------------------------------------------------- /app/Modules/Auth/AuthServiceProvider.php: -------------------------------------------------------------------------------- 1 | app->singleton(ClientManager::class, function (Application $app) { 14 | return new ClientManager($app); 15 | }); 16 | } 17 | 18 | public function boot() 19 | { 20 | AuthRouteRegistrar::all(); 21 | 22 | Request::macro('client', function () { 23 | return app(ClientManager::class)->getClient(); 24 | }); 25 | } 26 | } 27 | -------------------------------------------------------------------------------- /app/Modules/Auth/Endpoints/CreatePersonalToken.php: -------------------------------------------------------------------------------- 1 | "string", 'type' => "string", 'expires_at' => "mixed"])] 16 | public function __invoke(Request $request): array 17 | { 18 | $this->validate($request, [ 19 | 'name' => 'required|string|max:255', 20 | ]); 21 | 22 | if ($request->user()->tokens()->where('revoked', false)->where('name', 'like', '[API]%')->count() >= 5) { 23 | abort(403, '您最多只能创建 5 个 token'); 24 | } 25 | 26 | Passport::personalAccessTokensExpireIn(now()->addYears(5)); 27 | 28 | $token = $request->user()->createToken('[API]'.$request->get('name')); 29 | 30 | return [ 31 | 'value' => $token->accessToken, 32 | 'type' => 'Bearer', 33 | 'expires_at' => $token->token->expires_at, 34 | ]; 35 | } 36 | } 37 | -------------------------------------------------------------------------------- /app/Modules/Auth/Endpoints/ListTokens.php: -------------------------------------------------------------------------------- 1 | user()->tokens() 15 | ->where('name', 'like', '[API]%') 16 | ->where('revoked', false) 17 | ->orderByDesc('created_at') 18 | ->get(); 19 | } 20 | } 21 | -------------------------------------------------------------------------------- /app/Modules/Auth/Endpoints/PurgeTokens.php: -------------------------------------------------------------------------------- 1 | user()->tokens()->get()->each(fn(Token $token) => $token->revoke()); 15 | 16 | return response()->noContent(); 17 | } 18 | } 19 | -------------------------------------------------------------------------------- /app/Modules/Auth/Endpoints/Redirect.php: -------------------------------------------------------------------------------- 1 | create($request->input('driver')); 21 | 22 | return redirect($manager->create($request->input('driver'))->withState($stateKey)->redirect()); 23 | } 24 | } 25 | -------------------------------------------------------------------------------- /app/Modules/Auth/Endpoints/RevokeToken.php: -------------------------------------------------------------------------------- 1 | user_id != $request->user()->id, 403, '非法操作'); 14 | 15 | $token->revoke(); 16 | 17 | return response()->noContent(); 18 | } 19 | } 20 | -------------------------------------------------------------------------------- /app/Modules/Auth/Requests/CreateTokenViaCodeRequest.php: -------------------------------------------------------------------------------- 1 | [ 13 | 'required', 14 | 'string', 15 | ], 16 | 'state' => [ 17 | 'required', 18 | 'string', 19 | 'size:40', 20 | ], 21 | ]; 22 | } 23 | } 24 | -------------------------------------------------------------------------------- /app/Modules/Auth/Requests/CreateTokenViaSmsRequest.php: -------------------------------------------------------------------------------- 1 | [ 16 | 'required', 17 | 'string', 18 | new ValidPhoneNumber(), 19 | ], 20 | 'sms_verification_code' => [ 21 | 'required', 22 | 'string', 23 | 'size:6', 24 | new ValidVerificationCode(VerificationCodeScene::LOGIN), 25 | ], 26 | ]; 27 | } 28 | } 29 | -------------------------------------------------------------------------------- /app/Modules/Auth/Requests/RedirectRequest.php: -------------------------------------------------------------------------------- 1 | [ 14 | 'required', 15 | 'string', 16 | Rule::in(array_keys(config('socialite'))), 17 | ], 18 | ]; 19 | } 20 | } 21 | -------------------------------------------------------------------------------- /app/Modules/Auth/Tests/CreateClientTest.php: -------------------------------------------------------------------------------- 1 | create(); 13 | 14 | $this->actingAs($user) 15 | ->postJson('/oauth/clients', [ 16 | 'name' => 'Test Client', 17 | 'redirect' => 'http://localhost', 18 | ]) 19 | ->assertSuccessful(); 20 | } 21 | } 22 | -------------------------------------------------------------------------------- /app/Modules/Auth/Tests/CreateClientTokenTest.php: -------------------------------------------------------------------------------- 1 | create(); 15 | 16 | /** @var Client $client */ 17 | $client = Client::factory()->create(['user_id' => $user->id]); 18 | 19 | $this->postJson('/oauth/token', [ 20 | 'grant_type' => 'client_credentials', 21 | 'client_id' => $client->id, 22 | 'client_secret' => $client->secret, 23 | 'scope' => '*', 24 | ]) 25 | ->assertSuccessful() 26 | ->assertJsonStructure([ 27 | 'token_type', 28 | 'expires_in', 29 | 'access_token', 30 | ]); 31 | } 32 | } 33 | -------------------------------------------------------------------------------- /app/Modules/Auth/Tests/CreateTokenViaSmsTest.php: -------------------------------------------------------------------------------- 1 | partialMock(VerificationCode::class, function (MockInterface $mock) { 15 | $mock->makePartial() 16 | ->shouldReceive('check') 17 | ->andReturn(true); 18 | }); 19 | 20 | $this->withoutMiddleware()->postJson('/api/auth/tokens:via-sms', [ 21 | 'phone_number' => '+86:18000000000', 22 | 'sms_verification_code' => Str::random(6), 23 | ]) 24 | ->assertSuccessful() 25 | ->assertJsonStructure([ 26 | 'value', 27 | 'type', 28 | 'expires_at', 29 | ]); 30 | } 31 | } 32 | -------------------------------------------------------------------------------- /app/Modules/Auth/Tests/DeleteClientTest.php: -------------------------------------------------------------------------------- 1 | create(); 15 | 16 | /** @var Client $client */ 17 | $client = Client::factory()->create(['user_id' => $user->id]); 18 | 19 | $this->actingAs($user) 20 | ->deleteJson('/oauth/clients/'.$client->id) 21 | ->assertSuccessful(); 22 | } 23 | } 24 | -------------------------------------------------------------------------------- /app/Modules/Auth/Tests/ListUserClientsTest.php: -------------------------------------------------------------------------------- 1 | create(); 15 | 16 | /** @var Client $client */ 17 | $client = Client::factory()->create(['user_id' => $user->id]); 18 | 19 | $this->actingAs($user) 20 | ->getJson('/oauth/clients') 21 | ->assertSuccessful() 22 | ->assertJsonCount(1); 23 | } 24 | } 25 | -------------------------------------------------------------------------------- /app/Modules/Chat/Actions/RefreshConversation.php: -------------------------------------------------------------------------------- 1 | first_active_at)) { 13 | $conversation->first_active_at = now(); 14 | } 15 | 16 | $conversation->last_active_at = now(); 17 | $conversation->messages_count = $conversation->messages()->count(); 18 | $conversation->tokens_count = $conversation->messages()->sum('tokens_count'); 19 | 20 | $conversation->timestamps = false; 21 | $conversation->save(); 22 | } 23 | } 24 | -------------------------------------------------------------------------------- /app/Modules/Chat/ChatServiceProvider.php: -------------------------------------------------------------------------------- 1 | $this->faker->title, 13 | 'messages_count' => random_int(0, 6000), 14 | 'tokens_count' => random_int(1000, 100000), 15 | 'first_active_at' => $this->faker->dateTimeBetween('-3 month', 'now'), 16 | 'last_active_at' => $this->faker->dateTimeBetween('-3 month', 'now'), 17 | 'created_at' => $this->faker->dateTimeBetween('-5 month', 'now'), 18 | 'updated_at' => $this->faker->dateTimeBetween('-5 month', 'now'), 19 | ]; 20 | } 21 | 22 | public function modelName(): string 23 | { 24 | return Conversation::class; 25 | } 26 | } 27 | -------------------------------------------------------------------------------- /app/Modules/Chat/ConversationPolicy.php: -------------------------------------------------------------------------------- 1 | is($conversation->creator); 12 | } 13 | 14 | public function delete(User $user, Conversation $conversation): bool 15 | { 16 | return $user->is($conversation->creator); 17 | } 18 | 19 | public function update(User $user, Conversation $conversation): bool 20 | { 21 | return $user->is($conversation->creator); 22 | } 23 | } 24 | -------------------------------------------------------------------------------- /app/Modules/Chat/Endpoints/CreateConversation.php: -------------------------------------------------------------------------------- 1 | user(); 17 | 18 | $conversation = new Conversation(); 19 | $conversation->title = $request->input('title'); 20 | $conversation->prompt_id = $request->input('prompt_id') ?: 0; 21 | $user->conversations()->save($conversation); 22 | 23 | if ($conversation->prompt_id) { 24 | $conversation->messages()->create([ 25 | 'creator_id' => $user->id, 26 | 'role' => MessageRole::ASSISTANT, 27 | 'content' => $conversation->prompt->greeting, 28 | 'tokens_count' => 0, 29 | ]); 30 | } 31 | 32 | return $conversation; 33 | } 34 | } 35 | -------------------------------------------------------------------------------- /app/Modules/Chat/Endpoints/CreateMessage.php: -------------------------------------------------------------------------------- 1 | authorize('update', $conversation); 16 | 17 | /** @var User $user */ 18 | $user = $request->user(); 19 | 20 | return tap($conversation->messages()->create([ 21 | 'creator_id' => $user->id, 22 | 'role' => MessageRole::USER->value, 23 | 'content' => $request->input('content'), 24 | 'tokens_count' => 0, 25 | ]), function () use ($conversation) { 26 | $conversation->touch(); 27 | }); 28 | } 29 | } 30 | -------------------------------------------------------------------------------- /app/Modules/Chat/Endpoints/DeleteConversation.php: -------------------------------------------------------------------------------- 1 | authorize('delete', $conversation); 14 | 15 | $conversation->delete(); 16 | 17 | return $conversation; 18 | } 19 | } 20 | -------------------------------------------------------------------------------- /app/Modules/Chat/Endpoints/GetConversation.php: -------------------------------------------------------------------------------- 1 | creator_id !== $request->user()->id, 403); 13 | 14 | $conversation->loadMissing('prompt'); 15 | 16 | return $conversation; 17 | } 18 | } 19 | -------------------------------------------------------------------------------- /app/Modules/Chat/Endpoints/ListConversationMessages.php: -------------------------------------------------------------------------------- 1 | authorize('get', $conversation); 15 | 16 | $messages = $conversation->messages() 17 | ->orderBy('id') 18 | ->filter($request->query()) 19 | ->paginate(CheckSize::run($request->query('per_page', 15))); 20 | 21 | $request->user()->attachLikeStatus($messages); 22 | 23 | return $messages; 24 | } 25 | } 26 | -------------------------------------------------------------------------------- /app/Modules/Chat/Endpoints/ListConversations.php: -------------------------------------------------------------------------------- 1 | user()->conversations() 13 | ->with('prompt') 14 | ->latest('updated_at') 15 | ->filter($request->query()) 16 | ->paginate(CheckSize::run($request->query('per_page', 15))); 17 | } 18 | } 19 | -------------------------------------------------------------------------------- /app/Modules/Chat/Endpoints/ToggleLikeMessage.php: -------------------------------------------------------------------------------- 1 | creator->is($request->user()), 403); 14 | 15 | $request->user()->toggleLike($message); 16 | 17 | return response()->noContent(); 18 | } 19 | } 20 | -------------------------------------------------------------------------------- /app/Modules/Chat/Endpoints/TruncateConversation.php: -------------------------------------------------------------------------------- 1 | authorize('update', $conversation); 19 | 20 | $conversation->messages->each->delete(); 21 | 22 | RefreshConversation::run($conversation); 23 | 24 | return response()->noContent(); 25 | } 26 | } 27 | -------------------------------------------------------------------------------- /app/Modules/Chat/Endpoints/UpdateConversation.php: -------------------------------------------------------------------------------- 1 | authorize('update', $conversation); 14 | 15 | $conversation->update($request->only(['title'])); 16 | 17 | return $conversation; 18 | } 19 | } 20 | -------------------------------------------------------------------------------- /app/Modules/Chat/Enums/MessageRole.php: -------------------------------------------------------------------------------- 1 | where('prompt_id', $promptId); 17 | } 18 | 19 | protected function getSortableFields(): array 20 | { 21 | return ['id', 'last_active_at']; 22 | } 23 | } 24 | -------------------------------------------------------------------------------- /app/Modules/Chat/Filters/MessageFilter.php: -------------------------------------------------------------------------------- 1 | MessageRole::USER, 14 | 'content' => $this->faker->text, 15 | ]; 16 | } 17 | 18 | public function modelName() 19 | { 20 | return Message::class; 21 | } 22 | } 23 | -------------------------------------------------------------------------------- /app/Modules/Chat/Requests/CreateConversationRequest.php: -------------------------------------------------------------------------------- 1 | [ 15 | 'required', 16 | 'string', 17 | 'min:1', 18 | new ValidString(), 19 | ], 20 | 'prompt_id' => [ 21 | 'nullable', 22 | Rule::exists('prompts', 'id'), 23 | ], 24 | ]; 25 | } 26 | } 27 | -------------------------------------------------------------------------------- /app/Modules/Chat/Requests/CreateMessageRequest.php: -------------------------------------------------------------------------------- 1 | [ 14 | 'required', 15 | 'string', 16 | 'between:1,15000', 17 | new ValidString(), 18 | ], 19 | ]; 20 | } 21 | } 22 | -------------------------------------------------------------------------------- /app/Modules/Chat/Requests/UpdateConversationRequest.php: -------------------------------------------------------------------------------- 1 | [ 14 | 'string', 15 | 'min:1', 16 | new ValidString(), 17 | ], 18 | ]; 19 | } 20 | } 21 | -------------------------------------------------------------------------------- /app/Modules/Chat/Tests/CreateMessageTest.php: -------------------------------------------------------------------------------- 1 | create(); 15 | 16 | /** @var Conversation $conversation */ 17 | $conversation = Conversation::factory()->create(['creator_id' => $user->id]); 18 | 19 | $this->actingAs($user) 20 | ->postJson("/api/chat/conversations/{$conversation->id}/messages", [ 21 | 'content' => 'content', 22 | ]) 23 | ->assertSuccessful(); 24 | } 25 | } 26 | -------------------------------------------------------------------------------- /app/Modules/Chat/Tests/DeleteConversationTest.php: -------------------------------------------------------------------------------- 1 | create(); 14 | 15 | $conversation = Conversation::factory()->create(['creator_id' => $user->id]); 16 | 17 | $this->actingAs($user) 18 | ->deleteJson("/api/chat/conversations/{$conversation->id}") 19 | ->assertSuccessful(); 20 | } 21 | } 22 | -------------------------------------------------------------------------------- /app/Modules/Chat/Tests/GetConversationTest.php: -------------------------------------------------------------------------------- 1 | create(); 13 | 14 | /** @var User $anotherUser */ 15 | $anotherUser = User::factory()->create(); 16 | 17 | /** @var Conversation $conversation */ 18 | $conversation1 = Conversation::factory()->create(['creator_id' => $user->id]); 19 | $conversation2 = Conversation::factory()->create(['creator_id' => $user->id]); 20 | $conversation3 = Conversation::factory()->create(['creator_id' => $anotherUser->id]); 21 | 22 | $this->actingAs($user)->getJson("/api/chat/conversations/{$conversation1->id}")->assertSuccessful(); 23 | $this->actingAs($user)->getJson("/api/chat/conversations/{$conversation2->id}")->assertSuccessful(); 24 | $this->actingAs($user)->getJson("/api/chat/conversations/{$conversation3->id}")->assertForbidden(); 25 | } 26 | } 27 | -------------------------------------------------------------------------------- /app/Modules/Chat/Tests/TruncateConversationTest.php: -------------------------------------------------------------------------------- 1 | create(); 13 | 14 | $conversation = Conversation::factory()->create(['creator_id' => $user->id]); 15 | 16 | Message::factory()->count(10)->create(['conversation_id' => $conversation->id]); 17 | 18 | $this->actingAs($user) 19 | ->getJson("/api/chat/conversations/{$conversation->id}/messages") 20 | ->assertJsonCount(10, 'data') 21 | ->assertSuccessful(); 22 | 23 | // truncate 24 | $this->actingAs($user) 25 | ->postJson("/api/chat/conversations/{$conversation->id}:truncate") 26 | ->assertNoContent(); 27 | 28 | // check 29 | $this->actingAs($user) 30 | ->getJson("/api/chat/conversations/{$conversation->id}/messages") 31 | ->assertJsonCount(0, 'data') 32 | ->assertSuccessful(); 33 | } 34 | } 35 | -------------------------------------------------------------------------------- /app/Modules/Chat/Tests/UpdateConversationTest.php: -------------------------------------------------------------------------------- 1 | create(); 14 | 15 | $conversation = Conversation::factory()->create(['creator_id' => $user->id]); 16 | 17 | $this->actingAs($user) 18 | ->putJson("/api/chat/conversations/{$conversation->id}", [ 19 | 'title' => 'New title', 20 | ]) 21 | ->assertSuccessful(); 22 | } 23 | } 24 | -------------------------------------------------------------------------------- /app/Modules/Common/Actions/ConvertStringToArray.php: -------------------------------------------------------------------------------- 1 | context = $context; 17 | } 18 | 19 | public function render(): JsonResponse 20 | { 21 | return response()->json([ 22 | 'message' => $this->message, 23 | 'context' => $this->context, 24 | ], $this->code); 25 | } 26 | } 27 | -------------------------------------------------------------------------------- /app/Modules/GiftCard/Commands/MakeGiftCard.php: -------------------------------------------------------------------------------- 1 | argument('name') ?? $defaultPlan['title']; 19 | $tokensCount = $this->option('tokens_count') ?: $defaultPlan['tokens_count']; 20 | $days = $this->option('days') ?: $defaultPlan['days']; 21 | 22 | $card = new GiftCard([ 23 | 'name' => $name, 24 | 'tokens_count' => $tokensCount, 25 | 'days' => $days, 26 | ]); 27 | 28 | $card->save(); 29 | 30 | $this->info('创建成功:'); 31 | 32 | $this->info('名称:'.$card->name); 33 | $this->info('编码:'.$card->code); 34 | $this->info('tokens:'.$card->tokens_count); 35 | $this->info('有效期:'.$card->expired_at); 36 | $this->info('有效天数:'.$card->days); 37 | } 38 | } 39 | -------------------------------------------------------------------------------- /app/Modules/GiftCard/Endpoints/ActivateGiftCard.php: -------------------------------------------------------------------------------- 1 | validate($request, [ 21 | 'code' => 'required|string|size:36', 22 | ]); 23 | 24 | /** @var User $user */ 25 | $user = $request->user(); 26 | 27 | $giftCard = GiftCard::query()->where($request->only('code'))->firstOrFail(); 28 | 29 | AssignGiftCardToUser::run($giftCard, $user); 30 | 31 | return $giftCard->refresh(); 32 | } 33 | } 34 | -------------------------------------------------------------------------------- /app/Modules/GiftCard/Filters/GiftCardFilter.php: -------------------------------------------------------------------------------- 1 | ['api', 'auth'], 13 | 'prefix' => 'api', 14 | ], function () { 15 | Route::post('/gift-cards:activate', Endpoints\ActivateGiftCard::class)->name('gift-cards.activate')->middleware('throttle:60,1'); 16 | }); 17 | } 18 | } 19 | -------------------------------------------------------------------------------- /app/Modules/GiftCard/GiftCardServiceProvider.php: -------------------------------------------------------------------------------- 1 | app->runningInConsole()) { 14 | $this->commands([ 15 | Commands\MakeGiftCard::class, 16 | ]); 17 | } 18 | } 19 | } 20 | -------------------------------------------------------------------------------- /app/Modules/GiftCard/Tests/ActivateGiftCardTest.php: -------------------------------------------------------------------------------- 1 | unused()->create([ 14 | 'name' => '测试礼品卡', 15 | ]); 16 | 17 | /** @var User $user */ 18 | $user = User::factory()->create(); 19 | 20 | $this->actingAs($user)->postJson('/api/gift-cards:activate', [ 21 | 'code' => $giftCard->code, 22 | ])->assertOk(); 23 | } 24 | } 25 | -------------------------------------------------------------------------------- /app/Modules/GiftCard/Tests/MakeGiftCardTest.php: -------------------------------------------------------------------------------- 1 | artisan('yike:make-gift-card', [ 12 | 'name' => '好友礼品卡', 13 | '--tokens_count' => 10, 14 | '--days' => 30, 15 | ])->assertExitCode(0); 16 | 17 | $this->assertDatabaseHas('gift_cards', [ 18 | 'name' => '好友礼品卡', 19 | 'tokens_count' => 10, 20 | 'days' => 30, 21 | ]); 22 | } 23 | } 24 | -------------------------------------------------------------------------------- /app/Modules/Leaderboard/Endpoints/ListLeaderboards.php: -------------------------------------------------------------------------------- 1 | where('is_admin', false) 16 | ->where('referrals_count', '>', 0) 17 | ->orderByDesc('referrals_count') 18 | ->take(100) 19 | ->get() 20 | ->transform(function (User $user) { 21 | return $user->onlySafeFields(); 22 | }); 23 | } 24 | } 25 | -------------------------------------------------------------------------------- /app/Modules/Leaderboard/LeaderboardRouteRegistrar.php: -------------------------------------------------------------------------------- 1 | ['api', 'auth'], 14 | 'prefix' => 'api', 15 | ], function () { 16 | Route::get('/leaderboards', ListLeaderboards::class)->middleware('throttle:60,1'); 17 | }); 18 | } 19 | } 20 | -------------------------------------------------------------------------------- /app/Modules/Leaderboard/LeaderboardServiceProvider.php: -------------------------------------------------------------------------------- 1 | create(); 14 | 15 | $referral = User::factory()->create(); 16 | 17 | RefreshUserReferrer::run($referral, $user); 18 | 19 | $this->actingAs($user) 20 | ->getJson('/api/leaderboards') 21 | ->assertSuccessful() 22 | ->assertJsonCount(2); 23 | } 24 | } 25 | -------------------------------------------------------------------------------- /app/Modules/Payment/Actions/CreatePaymentOrderNumber.php: -------------------------------------------------------------------------------- 1 | format('ymdHis').random_int(1000, 9999); 15 | } 16 | } 17 | -------------------------------------------------------------------------------- /app/Modules/Payment/Actions/MarkPaymentAsExpired.php: -------------------------------------------------------------------------------- 1 | state->isPending()) { 14 | return; 15 | } 16 | 17 | $payment->state = PaymentState::EXPIRED; 18 | $payment->save(); 19 | } 20 | } 21 | -------------------------------------------------------------------------------- /app/Modules/Payment/Command/CheckPayment.php: -------------------------------------------------------------------------------- 1 | where('expired_at', '<', now()) 32 | ->where('state', PaymentState::PENDING) 33 | ->update(['state' => PaymentState::EXPIRED]); 34 | } 35 | } 36 | -------------------------------------------------------------------------------- /app/Modules/Payment/Endpoints/GetPayment.php: -------------------------------------------------------------------------------- 1 | authorize('get', $payment); 14 | 15 | return $payment; 16 | } 17 | } 18 | -------------------------------------------------------------------------------- /app/Modules/Payment/Endpoints/ListPayments.php: -------------------------------------------------------------------------------- 1 | user()->payments()->filter($request->query())->get(); 13 | } 14 | } 15 | -------------------------------------------------------------------------------- /app/Modules/Payment/Enums/Gateway.php: -------------------------------------------------------------------------------- 1 | Payjs::class, 16 | }; 17 | } 18 | } 19 | -------------------------------------------------------------------------------- /app/Modules/Payment/Enums/PaymentState.php: -------------------------------------------------------------------------------- 1 | '待支付', 30 | self::PAID => '已支付', 31 | self::EXPIRED => '已过期', 32 | }; 33 | } 34 | } 35 | -------------------------------------------------------------------------------- /app/Modules/Payment/Exceptions/GatewayException.php: -------------------------------------------------------------------------------- 1 | whereIn('state', ConvertStringToArray::run($states)); 18 | } 19 | 20 | protected function getSortableFields(): array 21 | { 22 | return ['id']; 23 | } 24 | } 25 | -------------------------------------------------------------------------------- /app/Modules/Payment/GatewayManager.php: -------------------------------------------------------------------------------- 1 | resolve())($this->config[$gateway->value]); 26 | } 27 | } 28 | -------------------------------------------------------------------------------- /app/Modules/Payment/Gateways/GatewayInterface.php: -------------------------------------------------------------------------------- 1 | payment); 31 | } 32 | } 33 | -------------------------------------------------------------------------------- /app/Modules/Payment/PaymentPolicy.php: -------------------------------------------------------------------------------- 1 | is($payment->creator); 12 | } 13 | } 14 | -------------------------------------------------------------------------------- /app/Modules/Payment/PaymentRouteRegistrar.php: -------------------------------------------------------------------------------- 1 | ['api'], 17 | 'prefix' => 'api', 18 | ], function () { 19 | Route::any('/payments:process', ProcessPayment::class)->middleware('throttle:600,1'); 20 | 21 | Route::group([ 22 | 'middleware' => ['auth'], 23 | ], function () { 24 | Route::get('/payments', ListPayments::class)->middleware('throttle:120,1'); 25 | Route::post('/payments', CreatePayment::class)->middleware('throttle:60,1'); 26 | Route::get('/payments/{payment}', GetPayment::class)->middleware('throttle:120,1'); 27 | }); 28 | }); 29 | } 30 | } 31 | -------------------------------------------------------------------------------- /app/Modules/Payment/PaymentServiceProvider.php: -------------------------------------------------------------------------------- 1 | app->singleton(GatewayManager::class, function (Application $app) { 15 | return new GatewayManager(config('payment.gateways')); 16 | }); 17 | 18 | $this->app->singleton(GatewayInterface::class, function (Application $app) { 19 | return $app->make(GatewayManager::class)->get(config('payment.gateway')); 20 | }); 21 | } 22 | 23 | public function boot() 24 | { 25 | Gate::policy(Payment::class, PaymentPolicy::class); 26 | 27 | PaymentRouteRegistrar::all(); 28 | } 29 | } 30 | -------------------------------------------------------------------------------- /app/Modules/Payment/Processors/GrantQuotaProcessor.php: -------------------------------------------------------------------------------- 1 | creator, $parameters); 13 | } 14 | } 15 | -------------------------------------------------------------------------------- /app/Modules/Payment/Processors/Processor.php: -------------------------------------------------------------------------------- 1 | [ 14 | 'required', 15 | 'string', 16 | Rule::in(array_keys(config('quota.pricings', []))), 17 | ], 18 | ]; 19 | } 20 | } 21 | -------------------------------------------------------------------------------- /app/Modules/Payment/Tests/GetPaymentTest.php: -------------------------------------------------------------------------------- 1 | create(); 14 | 15 | $payment = Payment::factory()->create([ 16 | 'creator_id' => $user, 17 | 'gateway' => 'payjs', 18 | 'gateway_number' => '1234567890', 19 | ]); 20 | 21 | $this->actingAs($user) 22 | ->getJson("/api/payments/$payment->id") 23 | ->assertSuccessful(); 24 | } 25 | } 26 | -------------------------------------------------------------------------------- /app/Modules/Payment/Tests/ListPaymentsTest.php: -------------------------------------------------------------------------------- 1 | create(); 15 | 16 | Payment::factory()->create([ 17 | 'creator_id' => $user->id, 18 | 'gateway' => 'payjs', 19 | 'gateway_number' => '1234567890', 20 | ]); 21 | 22 | $this->actingAs($user) 23 | ->getJson('/api/payments') 24 | ->assertSuccessful() 25 | ->assertJsonCount(1); 26 | } 27 | } 28 | -------------------------------------------------------------------------------- /app/Modules/Prompt/Endpoints/CreatePrompt.php: -------------------------------------------------------------------------------- 1 | user()->prompts()->count() >= 100, 400, '您无法创建更多的提示'); 12 | 13 | if ($request->user()->prompts()->where('name', $request->input('name'))->exists()) { 14 | abort(400, '已经存在同名场景'); 15 | } 16 | 17 | $prompt = $request->user()->prompts()->create($request->validated()); 18 | 19 | if ($request->has('tags')) { 20 | $prompt->tags()->sync($request->input('tags')); 21 | } 22 | 23 | return $prompt; 24 | } 25 | } 26 | -------------------------------------------------------------------------------- /app/Modules/Prompt/Endpoints/DeletePrompt.php: -------------------------------------------------------------------------------- 1 | creator_id != $request->user()->id, 403, '您无权删除该场景'); 13 | 14 | $prompt->delete(); 15 | 16 | return response()->noContent(); 17 | } 18 | } 19 | -------------------------------------------------------------------------------- /app/Modules/Prompt/Endpoints/GetPrompt.php: -------------------------------------------------------------------------------- 1 | withCount('conversations') 15 | ->orderByDesc('conversations_count') 16 | ->orderByDesc('sort_order') 17 | ->take(100) 18 | ->filter($request->query()) 19 | ->simplePaginate(100); 20 | } 21 | } 22 | -------------------------------------------------------------------------------- /app/Modules/Prompt/Endpoints/ListPrompts.php: -------------------------------------------------------------------------------- 1 | withCount('conversations') 15 | ->orderByDesc('sort_order') 16 | ->orderByDesc('created_at') 17 | ->filter($request->query()) 18 | ->simplePaginate(100); 19 | } 20 | } 21 | -------------------------------------------------------------------------------- /app/Modules/Prompt/Endpoints/ListUserPrompts.php: -------------------------------------------------------------------------------- 1 | user(); 14 | 15 | return Prompt::query() 16 | ->where(function ($query) use ($user) { 17 | // 用户创建的 18 | $query->where('creator_id', $user->id) 19 | // 或者用户用过的 20 | ->orWhereHas('conversations', function ($query) use ($user) { 21 | $query->where('creator_id', $user->id); 22 | }); 23 | }) 24 | ->withCount('conversations') 25 | ->orderByDesc('conversations_count') 26 | ->orderByDesc('sort_order') 27 | ->take(100) 28 | ->filter($request->query()) 29 | ->simplePaginate(100); 30 | } 31 | } 32 | -------------------------------------------------------------------------------- /app/Modules/Prompt/Endpoints/UpdatePrompt.php: -------------------------------------------------------------------------------- 1 | creator_id != $request->user()->id, 403, '您无权修改该场景'); 13 | 14 | if ($request->user()->prompts()->whereNot('id', $prompt->id)->where('name', $request->input('name'))->exists()) { 15 | abort(400, '已经存在同名场景'); 16 | } 17 | 18 | $prompt->update($request->validated()); 19 | 20 | if ($request->has('tags')) { 21 | $prompt->tags()->sync($request->input('tags')); 22 | } 23 | 24 | return $prompt; 25 | } 26 | } 27 | -------------------------------------------------------------------------------- /app/Modules/Prompt/Filters/PromptFilter.php: -------------------------------------------------------------------------------- 1 | related('tags', function ($query) use ($ids) { 22 | return $query->whereIn('tag_id', (array) $ids); 23 | }); 24 | } 25 | } 26 | -------------------------------------------------------------------------------- /app/Modules/Prompt/PromptFactory.php: -------------------------------------------------------------------------------- 1 | $this->faker->name, 15 | 'prompt_cn' => $this->faker->paragraph, 16 | 'prompt_en' => $this->faker->paragraph, 17 | 'logo' => $this->faker->imageUrl(), 18 | 'greeting' => $this->faker->paragraph, 19 | 'description' => $this->faker->paragraph, 20 | ]; 21 | } 22 | } 23 | -------------------------------------------------------------------------------- /app/Modules/Prompt/PromptServiceProvider.php: -------------------------------------------------------------------------------- 1 | 'required|string', 13 | 'description' => 'required|string', 14 | 'logo' => 'required|string', 15 | 'prompt_cn' => 'required_without:prompt_en|string|max:1200', 16 | 'prompt_en' => 'required_without:prompt_cn|string|max:1200', 17 | 'greeting' => 'required|string|max:1200', 18 | 'tags' => 'array', 19 | 'tags.*' => 'exists:tags,id', 20 | ]; 21 | } 22 | } 23 | -------------------------------------------------------------------------------- /app/Modules/Prompt/Requests/UpdatePromptRequest.php: -------------------------------------------------------------------------------- 1 | 'string', 13 | 'description' => 'string', 14 | 'logo' => 'string', 15 | 'prompt_cn' => 'string|max:1200', 16 | 'prompt_en' => 'string|max:1200', 17 | 'greeting' => 'string|max:1200', 18 | 'tags' => 'array', 19 | 'tags.*' => 'exists:tags,id', 20 | ]; 21 | } 22 | } 23 | -------------------------------------------------------------------------------- /app/Modules/Prompt/Tests/DeletePromptTest.php: -------------------------------------------------------------------------------- 1 | create(); 12 | $user2 = User::factory()->create(); 13 | $prompt = Prompt::factory()->create([ 14 | 'creator_id' => $user->id, 15 | ]); 16 | 17 | $this->actingAs($user); 18 | 19 | $response = $this->deleteJson(route('prompts.destroy', $prompt->id)); 20 | 21 | $response->assertNoContent(); 22 | $this->assertSoftDeleted($prompt); 23 | 24 | // cannot delete other's prompt 25 | $prompt2 = Prompt::factory()->create([ 26 | 'creator_id' => $user2->id, 27 | ]); 28 | 29 | $this->deleteJson(route('prompts.destroy', $prompt2->id)) 30 | ->assertForbidden(); 31 | } 32 | } 33 | -------------------------------------------------------------------------------- /app/Modules/Quota/Actions/CreateQuotaUsage.php: -------------------------------------------------------------------------------- 1 | creator_id = $tokenizable->getCreatorId(); 15 | $usage->quota_id = $tokenizable->getQuotaId(); 16 | $usage->tokens_count = $tokenizable->getTokensCount(); 17 | $usage->tokenizable_type = $tokenizable->getTokenizableType(); 18 | $usage->tokenizable_id = $tokenizable->getTokenizableId(); 19 | $usage->save(); 20 | 21 | return $usage; 22 | } 23 | } 24 | -------------------------------------------------------------------------------- /app/Modules/Quota/Actions/GrantUserQuota.php: -------------------------------------------------------------------------------- 1 | state = QuotaState::USING; 16 | $quota->tokens_count = $parameters['tokens_count']; 17 | 18 | if (! empty($parameters['days'])) { 19 | $quota->expired_at = now()->addDays($parameters['days']); 20 | } 21 | 22 | $user->quotas()->save($quota); 23 | 24 | return $quota; 25 | } 26 | } 27 | -------------------------------------------------------------------------------- /app/Modules/Quota/BelongsToQuota.php: -------------------------------------------------------------------------------- 1 | quota_id; 12 | } 13 | 14 | public function quota(): BelongsTo 15 | { 16 | return $this->belongsTo(Quota::class, 'quota_id', 'id'); 17 | } 18 | } 19 | -------------------------------------------------------------------------------- /app/Modules/Quota/Commands/CheckQuota.php: -------------------------------------------------------------------------------- 1 | where('expired_at', '<', now()) 32 | ->where('state', QuotaState::USING) 33 | ->update(['state' => QuotaState::EXPIRED]); 34 | } 35 | } 36 | -------------------------------------------------------------------------------- /app/Modules/Quota/Endpoints/ActivateQuota.php: -------------------------------------------------------------------------------- 1 | authorize('activate', $quota); 16 | 17 | if (! $quota->state->isPending()) { 18 | abort(403, '配额状态已无法变更'); 19 | } 20 | 21 | /** @var User $user */ 22 | $user = $request->user(); 23 | 24 | if ($user->getUsingQuota()) { 25 | abort(403, '您已有正在使用的配额'); 26 | } 27 | 28 | $quota->state = QuotaState::USING; 29 | $quota->save(); 30 | 31 | return $quota; 32 | } 33 | } 34 | -------------------------------------------------------------------------------- /app/Modules/Quota/Endpoints/ListPricings.php: -------------------------------------------------------------------------------- 1 | $pricing) { 16 | $pricings[$index] = Arr::only($pricing, [ 17 | 'title', 18 | 'tokens_count', 19 | 'days', 20 | 'price', 21 | 'is_popular', 22 | ]); 23 | 24 | $pricings[$index]['pricing'] = $index; 25 | } 26 | 27 | return array_values($pricings); 28 | } 29 | } 30 | -------------------------------------------------------------------------------- /app/Modules/Quota/Endpoints/ListQuotas.php: -------------------------------------------------------------------------------- 1 | user()->quotas()->orderByDesc('id')->get(); 13 | } 14 | } 15 | -------------------------------------------------------------------------------- /app/Modules/Quota/Enums/QuotaState.php: -------------------------------------------------------------------------------- 1 | '待使用', 16 | self::USING => '使用中', 17 | self::USED => '已使用', 18 | self::EXPIRED => '已过期', 19 | }; 20 | } 21 | 22 | public function isPending(): bool 23 | { 24 | return $this === self::PENDING; 25 | } 26 | 27 | public function isUsing(): bool 28 | { 29 | return $this === self::USING; 30 | } 31 | 32 | public function isUsed(): bool 33 | { 34 | return $this === self::USED; 35 | } 36 | 37 | public function isExpired(): bool 38 | { 39 | return $this === self::EXPIRED; 40 | } 41 | } 42 | -------------------------------------------------------------------------------- /app/Modules/Quota/Exceptions/QuotaException.php: -------------------------------------------------------------------------------- 1 | 'quota_not_enough']); 15 | } 16 | } 17 | -------------------------------------------------------------------------------- /app/Modules/Quota/Filters/QuotaFilter.php: -------------------------------------------------------------------------------- 1 | whereIn('state', ConvertStringToArray::run($states)); 23 | } 24 | } 25 | -------------------------------------------------------------------------------- /app/Modules/Quota/Jobs/RefreshQuota.php: -------------------------------------------------------------------------------- 1 | quota->usages()->sum('tokens_count'); 31 | 32 | $this->quota->used_tokens_count = $usedTokensCount; 33 | $availableTokensCount = $this->quota->tokens_count - $usedTokensCount; 34 | 35 | if ($availableTokensCount <= 0) { 36 | $this->quota->state = QuotaState::USED; 37 | } 38 | 39 | $this->quota->save(); 40 | } 41 | } 42 | -------------------------------------------------------------------------------- /app/Modules/Quota/Listeners/GrantFreeQuotas.php: -------------------------------------------------------------------------------- 1 | user; 13 | 14 | $default = config('quota.defaults.chat'); 15 | 16 | GrantUserQuota::run($user, [ 17 | 'tokens_count' => $default['tokens_count'] ?? 1000, 18 | 'days' => $default['days'] ?? 30, 19 | ]); 20 | } 21 | } 22 | -------------------------------------------------------------------------------- /app/Modules/Quota/Middlewares/CheckQuota.php: -------------------------------------------------------------------------------- 1 | user(); 16 | 17 | if (! empty($user)) { 18 | $quota = $user->getUsingQuota(); 19 | 20 | if (empty($quota)) { 21 | throw QuotaException::quotaNotEnough('您没有可用的配额'); 22 | } 23 | } 24 | 25 | return $next($request); 26 | } 27 | } 28 | -------------------------------------------------------------------------------- /app/Modules/Quota/Policies/QuotaPolicy.php: -------------------------------------------------------------------------------- 1 | is($quota->user); 13 | } 14 | } 15 | -------------------------------------------------------------------------------- /app/Modules/Quota/QuotaFactory.php: -------------------------------------------------------------------------------- 1 | User::query()->inRandomOrder()->first()?->id ?? User::factory()->create()->id, 14 | 'is_available' => random_int(0, 1), 15 | 'expired_at' => $this->faker->dateTimeBetween('now', '+1 year'), 16 | 'tokens_count' => random_int(10000, 1000000), 17 | 'used_tokens_count' => random_int(0, 1000000), 18 | ]; 19 | } 20 | 21 | public function modelName() 22 | { 23 | return Quota::class; 24 | } 25 | } 26 | -------------------------------------------------------------------------------- /app/Modules/Quota/QuotaRouteRegistrar.php: -------------------------------------------------------------------------------- 1 | ['api'], 16 | 'prefix' => 'api', 17 | ], function () { 18 | Route::get('/pricings', ListPricings::class)->middleware('throttle:120,1'); 19 | 20 | Route::group([ 21 | 'middleware' => ['auth'], 22 | ], function () { 23 | Route::get('/quotas', ListQuotas::class)->middleware('throttle:120,1'); 24 | Route::post('/quotas/{quota}:activate', ActivateQuota::class)->middleware('throttle:10,1'); 25 | }); 26 | }); 27 | } 28 | } 29 | -------------------------------------------------------------------------------- /app/Modules/Quota/QuotaServiceProvider.php: -------------------------------------------------------------------------------- 1 | 'string', 27 | 'creator_id' => 'string', 28 | 'quota_id' => 'string', 29 | ]; 30 | 31 | protected static function boot() 32 | { 33 | parent::boot(); 34 | 35 | static::created(function (QuotaUsage $usage) { 36 | RefreshQuota::dispatchSync($usage->quota); 37 | }); 38 | } 39 | 40 | public function tokenizable(): BelongsTo 41 | { 42 | return $this->morphTo('tokenizable'); 43 | } 44 | } 45 | -------------------------------------------------------------------------------- /app/Modules/Quota/Tests/ListPricingsTest.php: -------------------------------------------------------------------------------- 1 | getJson('/api/pricings') 12 | ->assertSuccessful(); 13 | } 14 | } 15 | -------------------------------------------------------------------------------- /app/Modules/Quota/Tests/ListQuotasTest.php: -------------------------------------------------------------------------------- 1 | create(); 13 | 14 | $this->actingAs($user) 15 | ->getJson('/api/quotas') 16 | ->assertJsonCount(1) 17 | ->assertSuccessful(); 18 | } 19 | } 20 | -------------------------------------------------------------------------------- /app/Modules/Quota/Tokenizable.php: -------------------------------------------------------------------------------- 1 | id; 10 | } 11 | 12 | public function getTokenizableType(): string 13 | { 14 | return $this->getMorphClass(); 15 | } 16 | 17 | public function getTokensCount(): int 18 | { 19 | return $this->tokens_count ?? 0; 20 | } 21 | } 22 | -------------------------------------------------------------------------------- /app/Modules/Quota/TokenizableInterface.php: -------------------------------------------------------------------------------- 1 | amount * $rate / 100, 2); 18 | 19 | if ($amount <= 0) { 20 | return null; 21 | } 22 | 23 | /** @var \App\Modules\Reward\Reward $reward */ 24 | $reward = $user->rewards()->firstOrCreate([ 25 | 'from_user_id' => $fromUser->id, 26 | 'payment_id' => $payment->id, 27 | ], [ 28 | 'amount' => $amount, 29 | 'rate' => $rate, 30 | ]); 31 | 32 | event(new RewardCreated($reward)); 33 | 34 | return $reward; 35 | } 36 | } 37 | -------------------------------------------------------------------------------- /app/Modules/Reward/Actions/CreateRewardFromPayment.php: -------------------------------------------------------------------------------- 1 | creator->referrer) { 16 | CreateReward::run($payment->creator->referrer, $payment->creator, $payment, config('payment.reward.rate.to_referrer')); 17 | } 18 | 19 | CreateReward::run($payment->creator, $payment->creator, $payment, config('payment.reward.rate.to_self')); 20 | } 21 | } 22 | -------------------------------------------------------------------------------- /app/Modules/Reward/Endpoints/ListRewards.php: -------------------------------------------------------------------------------- 1 | with([ 14 | 'user', 'fromUser' 15 | ])->whereBelongsTo($request->user())->latest()->paginate(15); 16 | } 17 | } 18 | -------------------------------------------------------------------------------- /app/Modules/Reward/Enums/RewardState.php: -------------------------------------------------------------------------------- 1 | ['api', 'auth'], 13 | 'prefix' => 'api/', 14 | ], function () { 15 | Route::get('/rewards', Endpoints\ListRewards::class)->name('rewards.index'); 16 | }); 17 | } 18 | } 19 | -------------------------------------------------------------------------------- /app/Modules/Reward/RewardServiceProvider.php: -------------------------------------------------------------------------------- 1 | create(); 15 | $user2 = User::factory()->create(); 16 | $paymentOfUser = Payment::factory()->ofUser($user)->paid()->create([ 17 | 'amount' => 1000, 18 | ]); 19 | $paymentOfUser2 = Payment::factory()->ofUser($user2)->paid()->create([ 20 | 'amount' => 2000, 21 | ]); 22 | 23 | Reward::factory(3)->rate(10)->toUser($user)->payment($paymentOfUser)->create(); 24 | Reward::factory(2)->rate(10)->toUser($user2)->payment($paymentOfUser2)->create(); 25 | 26 | $this->actingAs($user)->getJson(route('rewards.index'))->assertSuccessful()->assertJsonCount(3, 'data'); 27 | $this->actingAs($user2)->getJson(route('rewards.index'))->assertSuccessful()->assertJsonCount(2, 'data'); 28 | } 29 | } 30 | -------------------------------------------------------------------------------- /app/Modules/Security/Actions/CheckSize.php: -------------------------------------------------------------------------------- 1 | headers->set('Accept', 'application/json'); 13 | 14 | return $next($request); 15 | } 16 | } 17 | -------------------------------------------------------------------------------- /app/Modules/Security/Middlewares/SetRequestUser.php: -------------------------------------------------------------------------------- 1 | hasValidAuthorizationHeader()) { 19 | /** @var User $user */ 20 | $user = Auth::user(); 21 | 22 | if (! empty($user)) { 23 | $request->setUserResolver(fn () => $user); 24 | } 25 | } 26 | 27 | return $next($request); 28 | } 29 | } 30 | -------------------------------------------------------------------------------- /app/Modules/Security/Middlewares/ThrottleRequestsWithRedis.php: -------------------------------------------------------------------------------- 1 | user(); 21 | 22 | $key = sha1($user?->getKey() ?? $request->ip()); 23 | 24 | return sprintf('throttle_%s_%s', $key, $this->getRouteHashValue($request->route())); 25 | } 26 | 27 | protected function getRouteHashValue(Route $route): string 28 | { 29 | return sha1(implode('|', $route->methods()).':'.$route->uri()); 30 | } 31 | } 32 | -------------------------------------------------------------------------------- /app/Modules/Security/Rules/ValidString.php: -------------------------------------------------------------------------------- 1 | app->singleton(Watchdog::class, function (Application $app) { 13 | return new Watchdog($app); 14 | }); 15 | } 16 | } 17 | -------------------------------------------------------------------------------- /app/Modules/Security/Watchdog.php: -------------------------------------------------------------------------------- 1 | request = $app->make(Request::class); 17 | } 18 | 19 | public function getUserAgent(): ?string 20 | { 21 | return $this->app->make(Agent::class)->getUserAgent(); 22 | } 23 | 24 | public function hasValidAuthorizationHeader(): bool 25 | { 26 | return ! empty(trim($this->request->bearerToken())) && Str::startsWith(trim($this->request->bearerToken()), 'eyJ'); 27 | } 28 | } 29 | -------------------------------------------------------------------------------- /app/Modules/Service/Log/LogEventServiceProvider.php: -------------------------------------------------------------------------------- 1 | [ 15 | [HttpClientListener::class, 'handleConnectionFailed'], 16 | ], 17 | RequestSending::class => [ 18 | [HttpClientListener::class, 'handleRequestSending'], 19 | ], 20 | ResponseReceived::class => [ 21 | [HttpClientListener::class, 'handleResponseReceived'], 22 | ], 23 | ]; 24 | } 25 | -------------------------------------------------------------------------------- /app/Modules/Service/OpenAI/TokenizerException.php: -------------------------------------------------------------------------------- 1 | setAttribute($model->getKeyName(), app(Snowflake::class)->id()); 17 | }); 18 | } 19 | 20 | public function getIncrementing(): bool 21 | { 22 | return false; 23 | } 24 | } 25 | -------------------------------------------------------------------------------- /app/Modules/Service/Snowflake/SnowflakeServiceProvider.php: -------------------------------------------------------------------------------- 1 | app->singleton(Snowflake::class, function (Application $app) { 18 | /** @var CacheManager $cacheManager */ 19 | $cacheManager = $app->make(CacheManager::class); 20 | 21 | return (new Snowflake()) 22 | ->setStartTimeStamp(strtotime(self::START_DATE) * 1000) 23 | ->setSequenceResolver(new LaravelSequenceResolver($cacheManager->store())); 24 | }); 25 | } 26 | 27 | public function boot(): void 28 | { 29 | } 30 | } 31 | -------------------------------------------------------------------------------- /app/Modules/Service/Socialite/Providers/Google.php: -------------------------------------------------------------------------------- 1 | endpoint = $config['endpoint'] ?? 'https://www.googleapis.com'; 14 | } 15 | 16 | protected function getTokenUrl(): string 17 | { 18 | return "{$this->endpoint}/oauth2/v4/token"; 19 | } 20 | 21 | protected function getUserByToken(string $token, ?array $query = []): array 22 | { 23 | $response = $this->getHttpClient()->get("{$this->endpoint}/userinfo/v2/me", [ 24 | 'headers' => [ 25 | 'Accept' => 'application/json', 26 | 'Authorization' => 'Bearer '.$token, 27 | ], 28 | ]); 29 | 30 | return $this->fromJsonBody($response); 31 | } 32 | } 33 | -------------------------------------------------------------------------------- /app/Modules/Service/Socialite/SocialiteServiceProvider.php: -------------------------------------------------------------------------------- 1 | app->singleton(SocialiteManager::class, function (Application $app) { 16 | $manager = new SocialiteManager(config('socialite')); 17 | 18 | $manager->extend(\Overtrue\Socialite\Providers\GitHub::NAME, function (array $config) { 19 | return new GitHub($config); 20 | }); 21 | 22 | $manager->extend(\Overtrue\Socialite\Providers\Google::NAME, function (array $config) { 23 | return new Google($config); 24 | }); 25 | 26 | return $manager; 27 | }); 28 | } 29 | } 30 | -------------------------------------------------------------------------------- /app/Modules/Service/State/StateManager.php: -------------------------------------------------------------------------------- 1 | repository->put($this->getCacheKey($stateKey), $value, $this->ttl); 21 | 22 | return $stateKey; 23 | } 24 | 25 | public function get(string $stateKey): mixed 26 | { 27 | return $this->repository->get($this->getCacheKey($stateKey)); 28 | } 29 | 30 | protected function getCacheKey(string $stateKey): string 31 | { 32 | return "state_{$stateKey}"; 33 | } 34 | 35 | public function setTtl(int $ttl): static 36 | { 37 | $this->ttl = $ttl; 38 | 39 | return $this; 40 | } 41 | } 42 | -------------------------------------------------------------------------------- /app/Modules/Service/State/StateServiceProvider.php: -------------------------------------------------------------------------------- 1 | app->bind(StateManager::class, function (Application $app) { 14 | /** @var CacheManager $cacheManager */ 15 | $cacheManager = $app->make(CacheManager::class); 16 | 17 | return new StateManager($cacheManager->store()); 18 | }); 19 | } 20 | } 21 | -------------------------------------------------------------------------------- /app/Modules/Sms/Actions/IsValidPhoneNumber.php: -------------------------------------------------------------------------------- 1 | getRegionCodeForNumber($phoneNumber); 24 | 25 | if (! in_array($region, array_keys($validRegions), true)) { 26 | return false; 27 | } 28 | 29 | return $phoneNumberToolkit->isValidNumberForRegion($phoneNumber, $region); 30 | } 31 | } 32 | -------------------------------------------------------------------------------- /app/Modules/Sms/Actions/ParsePhoneNumber.php: -------------------------------------------------------------------------------- 1 | parse($phoneNumber); 22 | } catch (NumberParseException $e) { 23 | Log::error('[SMS] - 解析手机号码失败', [ 24 | 'phone_number' => $phoneNumber, 25 | 'exception' => $e, 26 | ]); 27 | 28 | throw $e; 29 | } 30 | } 31 | } 32 | -------------------------------------------------------------------------------- /app/Modules/Sms/Endpoints/SendVerificationCode.php: -------------------------------------------------------------------------------- 1 | setPhoneNumber($request->input('phone_number')) 17 | ->setScene($request->input('scene')) 18 | ->send(); 19 | 20 | if (! $result) { 21 | abort(500, '发送失败,请稍后再试'); 22 | } 23 | 24 | return response()->noContent(); 25 | } 26 | } 27 | -------------------------------------------------------------------------------- /app/Modules/Sms/Enums/VerificationCodeScene.php: -------------------------------------------------------------------------------- 1 | repository->set($this->getCacheKey(), 666666, now()->addMinutes(30)); 10 | } 11 | } 12 | -------------------------------------------------------------------------------- /app/Modules/Sms/Requests/SendVerificationCodeRequest.php: -------------------------------------------------------------------------------- 1 | [ 16 | 'required', 17 | 'string', 18 | new ValidPhoneNumber(), 19 | ], 20 | 'scene' => [ 21 | 'required', 22 | 'string', 23 | Rule::enum(VerificationCodeScene::class), 24 | ], 25 | ]; 26 | } 27 | } 28 | -------------------------------------------------------------------------------- /app/Modules/Sms/Rules/ValidPhoneNumber.php: -------------------------------------------------------------------------------- 1 | setPhoneNumber($this->data['phone_number']) 25 | ->setScene($this->scene->value) 26 | ->check($value); 27 | 28 | if (! $result) { 29 | $fail('验证码错误'); 30 | } 31 | } 32 | 33 | public function setData(array $data): static 34 | { 35 | $this->data = $data; 36 | 37 | return $this; 38 | } 39 | } 40 | -------------------------------------------------------------------------------- /app/Modules/Sms/SmsRouteRegistrar.php: -------------------------------------------------------------------------------- 1 | ['api'], 14 | 'prefix' => 'api', 15 | ], function () { 16 | Route::post('/sms/verification-codes:send', SendVerificationCode::class)->middleware('throttle:3,1'); 17 | }); 18 | } 19 | } 20 | -------------------------------------------------------------------------------- /app/Modules/Tag/Endpoints/ListTags.php: -------------------------------------------------------------------------------- 1 | simplePaginate(50); 13 | } 14 | } 15 | -------------------------------------------------------------------------------- /app/Modules/Tag/Filters/TagFilter.php: -------------------------------------------------------------------------------- 1 | where(function ($query) use ($keywords) { 26 | $query->where('name', 'like', "%{$keywords}%"); 27 | }); 28 | } 29 | } 30 | -------------------------------------------------------------------------------- /app/Modules/Tag/TagFactory.php: -------------------------------------------------------------------------------- 1 | $this->faker->word(), 15 | 'sort_order' => random_int(0, 100), 16 | ]; 17 | } 18 | } 19 | -------------------------------------------------------------------------------- /app/Modules/Tag/TagRouterRegistrar.php: -------------------------------------------------------------------------------- 1 | ['api'], 14 | 'prefix' => 'api/', 15 | ], function () { 16 | Route::get('tags', ListTags::class)->name('tags.index')->middleware('throttle:600,1'); 17 | }); 18 | } 19 | } 20 | -------------------------------------------------------------------------------- /app/Modules/Tag/TagServiceProvider.php: -------------------------------------------------------------------------------- 1 | create(); 13 | 14 | $this->getJson(route('tags.index')) 15 | ->assertOk() 16 | ->assertJsonCount(10, 'data'); 17 | } 18 | } 19 | -------------------------------------------------------------------------------- /app/Modules/User/Actions/GetUserRewardsTotal.php: -------------------------------------------------------------------------------- 1 | id}:rewards_total", 60, function() use ($user) { 14 | return $user->rewards()->sum('amount'); 15 | }); 16 | } 17 | } 18 | -------------------------------------------------------------------------------- /app/Modules/User/Actions/GetUserUnwithdrawnRewardsTotal.php: -------------------------------------------------------------------------------- 1 | id}:rewards_total", 60, function() use ($user) { 15 | return $user->rewards()->where('state',RewardState::UNWITHDRAWN)->sum('amount'); 16 | }); 17 | } 18 | } 19 | -------------------------------------------------------------------------------- /app/Modules/User/Actions/RefreshUserPaidTotal.php: -------------------------------------------------------------------------------- 1 | payments() 14 | ->where('state', PaymentState::PAID) 15 | ->sum('amount'); 16 | 17 | $user->paid_total = $paidTotal; 18 | $user->timestamps = false; 19 | $user->save(); 20 | } 21 | } 22 | -------------------------------------------------------------------------------- /app/Modules/User/Actions/RefreshUserReferrer.php: -------------------------------------------------------------------------------- 1 | is($referrer) || $user->referrer_id) { 13 | return $user; 14 | } 15 | 16 | $user->referrer_id = $referrer->id; 17 | $user->root_referrer_id = $referrer->root_referrer_id ?: $referrer->id; 18 | 19 | if (! empty($referrer->referrer_path)) { 20 | $user->referrer_path = sprintf('%s-%s', $referrer->referrer_path, $referrer->id); 21 | } else { 22 | $user->referrer_path = $referrer->id; 23 | } 24 | 25 | $user->level = $referrer->level + 1; 26 | 27 | $user->timestamps = false; 28 | $user->save(); 29 | 30 | $referrer->referrals_count = $referrer->referrals()->count(); 31 | $referrer->timestamps = false; 32 | $referrer->save(); 33 | 34 | return $user; 35 | } 36 | } 37 | -------------------------------------------------------------------------------- /app/Modules/User/BelongsToCreator.php: -------------------------------------------------------------------------------- 1 | belongsTo(User::class, 'creator_id', 'id'); 16 | } 17 | 18 | public function getCreatorId(): int 19 | { 20 | return $this->creator_id; 21 | } 22 | } 23 | -------------------------------------------------------------------------------- /app/Modules/User/Endpoints/GetUser.php: -------------------------------------------------------------------------------- 1 | user()->makeVisible(['email', 'phone_number', 'paid_total'])->append([ 14 | 'rewards_total', 15 | 'unwithdrawn_rewards_total', 16 | ])->load(['referrer']); 17 | } 18 | } 19 | -------------------------------------------------------------------------------- /app/Modules/User/Endpoints/GetUserQuota.php: -------------------------------------------------------------------------------- 1 | user()->getUsingQuota(); 13 | 14 | if (! empty($quota)) { 15 | return $quota; 16 | } 17 | 18 | return $request->user()->quotas()->orderByDesc('id')->firstOrNew(); 19 | } 20 | } 21 | -------------------------------------------------------------------------------- /app/Modules/User/Endpoints/ListReferrals.php: -------------------------------------------------------------------------------- 1 | user()->referrals()->latest()->paginate(15); 13 | } 14 | } 15 | -------------------------------------------------------------------------------- /app/Modules/User/Endpoints/ListSettings.php: -------------------------------------------------------------------------------- 1 | user()->settings()->get(['value', 'key']); 15 | 16 | $outputs = []; 17 | 18 | foreach (SettingKey::defaults() as $key => $value) { 19 | $outputs[$key] = $value; 20 | } 21 | 22 | /** @var UserSetting $setting */ 23 | foreach ($settings as $setting) { 24 | $outputs[$setting->key->value] = $setting->value; 25 | } 26 | 27 | return $outputs; 28 | } 29 | } 30 | -------------------------------------------------------------------------------- /app/Modules/User/Endpoints/UpdateSetting.php: -------------------------------------------------------------------------------- 1 | validate($request, [ 20 | 'value' => [ 21 | ...$key->rules(), 22 | 'required', 23 | ], 24 | ]); 25 | 26 | return $request->user()->settings()->updateOrCreate([ 27 | 'key' => $key, 28 | ], [ 29 | 'value' => $request->input('value'), 30 | ]); 31 | } 32 | } 33 | -------------------------------------------------------------------------------- /app/Modules/User/Endpoints/UpdateUser.php: -------------------------------------------------------------------------------- 1 | user(); 17 | 18 | $user->update(Arr::except($request->validated(), ['referral_code'])); 19 | 20 | if ($request->has('referral_code') && ! $user->referrer_id) { 21 | $referrer = User::query() 22 | ->where('referral_code', Str::lower($request->input('referral_code'))) 23 | ->where('state', UserState::ACTIVATED) 24 | ->first(); 25 | 26 | if (! empty($referrer) && ! $referrer->is($user)) { 27 | RefreshUserReferrer::run($user, $referrer); 28 | } 29 | } 30 | 31 | return $user->refresh(); 32 | } 33 | } 34 | -------------------------------------------------------------------------------- /app/Modules/User/Endpoints/UpdateUserSetting.php: -------------------------------------------------------------------------------- 1 | validate($request, [ 20 | 'value' => [ 21 | ...$key->rules(), 22 | 'required', 23 | ], 24 | ]); 25 | 26 | return $request->user()->settings()->updateOrCreate([ 27 | 'key' => $key, 28 | ], [ 29 | 'value' => $request->input('value'), 30 | ]); 31 | } 32 | } 33 | -------------------------------------------------------------------------------- /app/Modules/User/Enums/UserState.php: -------------------------------------------------------------------------------- 1 | 'banned']); 15 | } 16 | 17 | public static function unactivated(string $message = '您的账号未激活'): static 18 | { 19 | return new static($message, 403, null, ['state' => 'unactivated']); 20 | } 21 | 22 | public static function invalid(string $message = '您的账号状态异常'): static 23 | { 24 | return new static($message, 403, null, ['state' => 'invalid']); 25 | } 26 | } 27 | -------------------------------------------------------------------------------- /app/Modules/User/Filters/UserFilter.php: -------------------------------------------------------------------------------- 1 | where(function ($query) use ($keywords) { 26 | $query->where('name', 'like', "%{$keywords}%") 27 | ->orWhere('phone_number', 'like', "%{$keywords}%") 28 | ->orWhere('email', 'like', "%{$keywords}%"); 29 | }); 30 | } 31 | } 32 | -------------------------------------------------------------------------------- /app/Modules/User/Listeners/CreateUserSettings.php: -------------------------------------------------------------------------------- 1 | user; 13 | 14 | $settings = SettingKey::defaults(); 15 | 16 | foreach ($settings as $key => $value) { 17 | $user->settings()->updateOrCreate([ 18 | 'key' => $key, 19 | ], [ 20 | 'value' => $value, 21 | ]); 22 | } 23 | } 24 | } 25 | -------------------------------------------------------------------------------- /app/Modules/User/Middlewares/RefreshUserActiveAt.php: -------------------------------------------------------------------------------- 1 | user(); 21 | 22 | if (! $user) { 23 | return; 24 | } 25 | 26 | $user->timestamps = false; 27 | 28 | if (! $user->first_active_at) { 29 | $user->first_active_at = now(); 30 | } 31 | 32 | $user->last_active_at = now(); 33 | 34 | $user->saveQuietly(); 35 | } 36 | } 37 | -------------------------------------------------------------------------------- /app/Modules/User/Policies/UserPolicy.php: -------------------------------------------------------------------------------- 1 | is($currentUser); 13 | } 14 | 15 | public function update(User $currentUser, User $user): bool 16 | { 17 | return $user->is($currentUser); 18 | } 19 | 20 | public function createClient(User $currentUser, User $user): bool 21 | { 22 | return $user->is($currentUser); 23 | } 24 | 25 | public function deleteClient(User $currentUser, User $user, Client $client): bool 26 | { 27 | return $user->is($currentUser) && $user->is($client->user); 28 | } 29 | } 30 | -------------------------------------------------------------------------------- /app/Modules/User/Requests/ActivateUserRequest.php: -------------------------------------------------------------------------------- 1 | [ 13 | 'string', 14 | 'size:6', 15 | 'required', 16 | ], 17 | ]; 18 | } 19 | } 20 | -------------------------------------------------------------------------------- /app/Modules/User/Requests/UpdateUserRequest.php: -------------------------------------------------------------------------------- 1 | 'string|max:45', 13 | 'avatar' => 'string|max:255', 14 | 'referral_code' => 'string|min:5|max:10', 15 | ]; 16 | } 17 | } 18 | -------------------------------------------------------------------------------- /app/Modules/User/Tests/ActivateUserTest.php: -------------------------------------------------------------------------------- 1 | create(); 13 | $user = User::factory()->unactivated()->create(); 14 | 15 | $this->actingAs($user) 16 | ->postJson('/api/user:activate', [ 17 | 'referral_code' => $referrer->referral_code, 18 | ]) 19 | ->assertSuccessful() 20 | ->assertJsonFragment([ 21 | 'referrer_id' => $referrer->id, 22 | 'root_referrer_id' => $referrer->id, 23 | 'referrer_path' => $referrer->id, 24 | ]); 25 | } 26 | } 27 | -------------------------------------------------------------------------------- /app/Modules/User/Tests/GetQuotaTest.php: -------------------------------------------------------------------------------- 1 | create(); 13 | 14 | $this->actingAs($user) 15 | ->getJson('/api/quota') 16 | ->assertSuccessful(); 17 | } 18 | } 19 | -------------------------------------------------------------------------------- /app/Modules/User/Tests/GetUserTest.php: -------------------------------------------------------------------------------- 1 | create(); 13 | $user = User::factory()->create([ 14 | 'referrer_id' => $referrer->id, 15 | ]); 16 | 17 | $this->actingAs($user) 18 | ->getJson('/api/user') 19 | ->assertJsonStructure([ 20 | 'id', 21 | 'name', 22 | 'email', 23 | 'phone_number', 24 | 'paid_total', 25 | 'referrer', 26 | ]) 27 | ->assertSuccessful(); 28 | } 29 | } 30 | -------------------------------------------------------------------------------- /app/Modules/User/Tests/ListReferralsTest.php: -------------------------------------------------------------------------------- 1 | create(); 14 | 15 | $referral = User::factory()->create(); 16 | 17 | RefreshUserReferrer::run($referral, $user); 18 | 19 | $this->actingAs($user) 20 | ->getJson('/api/referrals') 21 | ->assertJsonCount(1); 22 | } 23 | } 24 | -------------------------------------------------------------------------------- /app/Modules/User/Tests/ListSettingsTest.php: -------------------------------------------------------------------------------- 1 | create(); 13 | 14 | $this->actingAs($user) 15 | ->getJson('/api/settings') 16 | ->assertSuccessful(); 17 | } 18 | } 19 | -------------------------------------------------------------------------------- /app/Modules/User/Tests/MarkUserAsActivatedTest.php: -------------------------------------------------------------------------------- 1 | create(); 13 | $user = User::factory()->unactivated()->create(); 14 | 15 | $this->actingAs($user) 16 | ->postJson('/api/user:activate', [ 17 | 'referral_code' => $referrer->referral_code, 18 | ]) 19 | ->assertSuccessful() 20 | ->assertJsonFragment([ 21 | 'referrer_id' => $referrer->id, 22 | 'root_referrer_id' => $referrer->id, 23 | 'referrer_path' => $referrer->id, 24 | ]); 25 | } 26 | } 27 | -------------------------------------------------------------------------------- /app/Modules/User/Tests/UpdateUserSettingTest.php: -------------------------------------------------------------------------------- 1 | create(); 13 | 14 | $this->actingAs($user) 15 | ->putJson('/api/settings/chat_contexts_count', [ 16 | 'value' => 10, 17 | ]) 18 | ->assertSuccessful(); 19 | } 20 | } 21 | -------------------------------------------------------------------------------- /app/Modules/User/UserEventServiceProvider.php: -------------------------------------------------------------------------------- 1 | [ 15 | CreateUserSettings::class, 16 | ], 17 | UserActivated::class => [ 18 | GrantFreeQuotas::class, 19 | ], 20 | ]; 21 | } 22 | -------------------------------------------------------------------------------- /app/Modules/User/UserServiceProvider.php: -------------------------------------------------------------------------------- 1 | 14 | */ 15 | protected $policies = [ 16 | // 'App\Models\Model' => 'App\Policies\ModelPolicy', 17 | ]; 18 | 19 | /** 20 | * Register any authentication / authorization services. 21 | */ 22 | public function boot(): void 23 | { 24 | // 25 | } 26 | } 27 | -------------------------------------------------------------------------------- /app/Providers/BroadcastServiceProvider.php: -------------------------------------------------------------------------------- 1 | > 16 | */ 17 | protected $listen = [ 18 | Registered::class => [ 19 | SendEmailVerificationNotification::class, 20 | ], 21 | ]; 22 | 23 | /** 24 | * Register any events for your application. 25 | */ 26 | public function boot(): void 27 | { 28 | // 29 | } 30 | 31 | /** 32 | * Determine if events and listeners should be automatically discovered. 33 | */ 34 | public function shouldDiscoverEvents(): bool 35 | { 36 | return false; 37 | } 38 | } 39 | -------------------------------------------------------------------------------- /app/Providers/RouteServiceProvider.php: -------------------------------------------------------------------------------- 1 | routes(function () { 25 | Route::middleware('api') 26 | ->prefix('api') 27 | ->group(base_path('routes/api.php')); 28 | 29 | Route::middleware('web') 30 | ->group(base_path('routes/web.php')); 31 | }); 32 | } 33 | } 34 | -------------------------------------------------------------------------------- /bootstrap/cache/.gitignore: -------------------------------------------------------------------------------- 1 | * 2 | !.gitignore 3 | -------------------------------------------------------------------------------- /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 | -------------------------------------------------------------------------------- /config/like.php: -------------------------------------------------------------------------------- 1 | false, 8 | 9 | /* 10 | * User tables foreign key name. 11 | */ 12 | 'user_foreign_key' => 'user_id', 13 | 14 | /* 15 | * Table name for likes records. 16 | */ 17 | 'likes_table' => 'likes', 18 | 19 | /* 20 | * Model name for like record. 21 | */ 22 | 'like_model' => \Overtrue\LaravelLike\Like::class, 23 | ]; 24 | -------------------------------------------------------------------------------- /config/morphs.php: -------------------------------------------------------------------------------- 1 | 'App\\Modules\\Chat\\Message', 5 | ]; 6 | -------------------------------------------------------------------------------- /config/openai.php: -------------------------------------------------------------------------------- 1 | env('OPENAI_API_KEY'), 5 | 'endpoint' => env('OPENAI_ENDPOINT'), 6 | 'api_version' => env('OPENAI_API_VERSION'), 7 | 8 | // https://platform.openai.com/docs/models/overview 9 | // https://platform.openai.com/docs/api-reference/chat 10 | 'chat' => [ 11 | 'model' => 'gpt-35-turbo', 12 | 'presence_penalty' => 1, 13 | 'temperature' => 0.8, 14 | // 'max_tokens' => 4096, 15 | ], 16 | 17 | 'tokenizer' => [ 18 | 'endpoint' => env('OPENAI_TOKENIZER_ENDPOINT'), 19 | ], 20 | ]; 21 | -------------------------------------------------------------------------------- /config/payment.php: -------------------------------------------------------------------------------- 1 | env('PAYMENT_GATEWAY', 'payjs'), 5 | 6 | 'reward' => [ 7 | // 付款成功后的奖励比例, 单位: %, 最大 50% 8 | 'rate' => [ 9 | // 付款者 10 | 'to_self' => env('PAYMENT_REWARD_RATE_TO_SELF', 3), 11 | // 推荐人 12 | 'to_referrer' => env('PAYMENT_REWARD_RATE_TO_REFERRER', 10), 13 | ], 14 | ], 15 | 16 | 'gateways' => [ 17 | 'payjs' => [ 18 | 'merchant_id' => env('PAYJS_MERCHANT_ID'), 19 | 'secret_key' => env('PAYJS_SECRET_KEY'), 20 | 'notify_url' => env('PAYJS_NOTIFY_URL'), 21 | 'endpoint' => env('PAYJS_ENDPOINT', 'https://payjs.cn'), 22 | 23 | 'native' => [ 24 | 'default' => [ 25 | 'no_credit' => 0, 26 | ], 27 | 28 | 'ttl' => 3600, 29 | ], 30 | ], 31 | ], 32 | ]; 33 | -------------------------------------------------------------------------------- /config/services.php: -------------------------------------------------------------------------------- 1 | [ 18 | 'domain' => env('MAILGUN_DOMAIN'), 19 | 'secret' => env('MAILGUN_SECRET'), 20 | 'endpoint' => env('MAILGUN_ENDPOINT', 'api.mailgun.net'), 21 | 'scheme' => 'https', 22 | ], 23 | 24 | 'postmark' => [ 25 | 'token' => env('POSTMARK_TOKEN'), 26 | ], 27 | 28 | 'ses' => [ 29 | 'key' => env('AWS_ACCESS_KEY_ID'), 30 | 'secret' => env('AWS_SECRET_ACCESS_KEY'), 31 | 'region' => env('AWS_DEFAULT_REGION', 'us-east-1'), 32 | ], 33 | 34 | ]; 35 | -------------------------------------------------------------------------------- /config/sms.php: -------------------------------------------------------------------------------- 1 | [ 7 | 'CN' => '86', 8 | ], 9 | 10 | // HTTP 请求的超时时间(秒) 11 | 'timeout' => 5.0, 12 | 13 | // 默认发送配置 14 | 'default' => [ 15 | // 网关调用策略,默认:顺序调用 16 | 'strategy' => OrderStrategy::class, 17 | 18 | // 默认可用的发送网关 19 | 'gateways' => [ 20 | 'qcloud', 21 | 'errorlog', 22 | ], 23 | ], 24 | // 可用的网关配置 25 | 'gateways' => [ 26 | 'errorlog' => [ 27 | 'file' => storage_path('logs/sms.log'), 28 | ], 29 | 30 | 'qcloud' => [ 31 | 'sdk_app_id' => env('SMS_QCLOUD_SDK_APP_ID'), 32 | 'secret_id' => env('SMS_QCLOUD_SECRET_ID'), 33 | 'secret_key' => env('SMS_QCLOUD_SECRET_KEY'), 34 | 'sign_name' => env('SMS_QCLOUD_SIGN_NAME'), 35 | ], 36 | ], 37 | ]; 38 | -------------------------------------------------------------------------------- /config/socialite.php: -------------------------------------------------------------------------------- 1 | [ 5 | 'client_id' => env('GITHUB_CLIENT_ID'), 6 | 'client_secret' => env('GITHUB_CLIENT_SECRET'), 7 | 'redirect' => env('GITHUB_REDIRECT_URI'), 8 | 'endpoint' => env('GITHUB_ENDPOINT'), 9 | 'token_endpoint' => env('GITHUB_TOKEN_ENDPOINT'), 10 | ], 11 | 12 | 'dashboard' => [ 13 | 'provider' => 'github', 14 | 'client_id' => env('DASHBOARD_GITHUB_CLIENT_ID'), 15 | 'client_secret' => env('DASHBOARD_GITHUB_CLIENT_SECRET'), 16 | 'redirect' => env('DASHBOARD_GITHUB_REDIRECT_URI'), 17 | 'endpoint' => env('GITHUB_ENDPOINT'), 18 | 'token_endpoint' => env('GITHUB_TOKEN_ENDPOINT'), 19 | ], 20 | 21 | 'google' => [ 22 | 'client_id' => env('GOOGLE_CLIENT_ID'), 23 | 'client_secret' => env('GOOGLE_CLIENT_SECRET'), 24 | 'redirect' => env('GOOGLE_REDIRECT_URI'), 25 | 'endpoint' => env('GOOGLE_ENDPOINT'), 26 | ], 27 | ]; 28 | -------------------------------------------------------------------------------- /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 | -------------------------------------------------------------------------------- /database/.gitignore: -------------------------------------------------------------------------------- 1 | *.sqlite* 2 | -------------------------------------------------------------------------------- /database/migrations/2014_10_12_100000_create_password_reset_tokens_table.php: -------------------------------------------------------------------------------- 1 | string('email')->primary(); 16 | $table->string('token'); 17 | $table->timestamp('created_at')->nullable(); 18 | }); 19 | } 20 | 21 | /** 22 | * Reverse the migrations. 23 | */ 24 | public function down(): void 25 | { 26 | Schema::dropIfExists('password_reset_tokens'); 27 | } 28 | }; 29 | -------------------------------------------------------------------------------- /database/migrations/2016_06_01_000001_create_oauth_auth_codes_table.php: -------------------------------------------------------------------------------- 1 | string('id', 100)->primary(); 16 | $table->unsignedBigInteger('user_id')->index(); 17 | $table->uuid('client_id'); 18 | $table->text('scopes')->nullable(); 19 | $table->boolean('revoked'); 20 | $table->dateTime('expires_at')->nullable(); 21 | }); 22 | } 23 | 24 | /** 25 | * Reverse the migrations. 26 | */ 27 | public function down(): void 28 | { 29 | Schema::dropIfExists('oauth_auth_codes'); 30 | } 31 | }; 32 | -------------------------------------------------------------------------------- /database/migrations/2016_06_01_000002_create_oauth_access_tokens_table.php: -------------------------------------------------------------------------------- 1 | string('id', 100)->primary(); 16 | $table->unsignedBigInteger('user_id')->nullable()->index(); 17 | $table->uuid('client_id'); 18 | $table->string('name')->nullable(); 19 | $table->text('scopes')->nullable(); 20 | $table->boolean('revoked'); 21 | $table->timestamps(); 22 | $table->dateTime('expires_at')->nullable(); 23 | }); 24 | } 25 | 26 | /** 27 | * Reverse the migrations. 28 | */ 29 | public function down(): void 30 | { 31 | Schema::dropIfExists('oauth_access_tokens'); 32 | } 33 | }; 34 | -------------------------------------------------------------------------------- /database/migrations/2016_06_01_000003_create_oauth_refresh_tokens_table.php: -------------------------------------------------------------------------------- 1 | string('id', 100)->primary(); 16 | $table->string('access_token_id', 100)->index(); 17 | $table->boolean('revoked'); 18 | $table->dateTime('expires_at')->nullable(); 19 | }); 20 | } 21 | 22 | /** 23 | * Reverse the migrations. 24 | */ 25 | public function down(): void 26 | { 27 | Schema::dropIfExists('oauth_refresh_tokens'); 28 | } 29 | }; 30 | -------------------------------------------------------------------------------- /database/migrations/2016_06_01_000004_create_oauth_clients_table.php: -------------------------------------------------------------------------------- 1 | uuid('id')->primary(); 16 | $table->unsignedBigInteger('user_id')->nullable()->index(); 17 | $table->string('name'); 18 | $table->string('secret', 100)->nullable(); 19 | $table->string('provider')->nullable(); 20 | $table->text('redirect'); 21 | $table->boolean('personal_access_client'); 22 | $table->boolean('password_client'); 23 | $table->boolean('revoked'); 24 | $table->timestamps(); 25 | }); 26 | } 27 | 28 | /** 29 | * Reverse the migrations. 30 | */ 31 | public function down(): void 32 | { 33 | Schema::dropIfExists('oauth_clients'); 34 | } 35 | }; 36 | -------------------------------------------------------------------------------- /database/migrations/2016_06_01_000005_create_oauth_personal_access_clients_table.php: -------------------------------------------------------------------------------- 1 | bigIncrements('id'); 16 | $table->uuid('client_id'); 17 | $table->timestamps(); 18 | }); 19 | } 20 | 21 | /** 22 | * Reverse the migrations. 23 | */ 24 | public function down(): void 25 | { 26 | Schema::dropIfExists('oauth_personal_access_clients'); 27 | } 28 | }; 29 | -------------------------------------------------------------------------------- /database/migrations/2018_12_14_000000_create_likes_table.php: -------------------------------------------------------------------------------- 1 | bigIncrements('id'); 16 | $table->unsignedBigInteger(config('like.user_foreign_key'))->index()->comment('user_id'); 17 | $table->morphs('likeable'); 18 | $table->timestamps(); 19 | }); 20 | } 21 | 22 | /** 23 | * Reverse the migrations. 24 | */ 25 | public function down() 26 | { 27 | Schema::dropIfExists(config('like.likes_table')); 28 | } 29 | } 30 | -------------------------------------------------------------------------------- /database/migrations/2019_08_19_000000_create_failed_jobs_table.php: -------------------------------------------------------------------------------- 1 | id(); 16 | $table->string('uuid')->unique(); 17 | $table->text('connection'); 18 | $table->text('queue'); 19 | $table->longText('payload'); 20 | $table->longText('exception'); 21 | $table->timestamp('failed_at')->useCurrent(); 22 | }); 23 | } 24 | 25 | /** 26 | * Reverse the migrations. 27 | */ 28 | public function down(): void 29 | { 30 | Schema::dropIfExists('failed_jobs'); 31 | } 32 | }; 33 | -------------------------------------------------------------------------------- /database/migrations/2019_12_14_000001_create_personal_access_tokens_table.php: -------------------------------------------------------------------------------- 1 | id(); 16 | $table->morphs('tokenable'); 17 | $table->string('name'); 18 | $table->string('token', 64)->unique(); 19 | $table->text('abilities')->nullable(); 20 | $table->timestamp('last_used_at')->nullable(); 21 | $table->timestamp('expires_at')->nullable(); 22 | $table->timestamps(); 23 | }); 24 | } 25 | 26 | /** 27 | * Reverse the migrations. 28 | */ 29 | public function down(): void 30 | { 31 | Schema::dropIfExists('personal_access_tokens'); 32 | } 33 | }; 34 | -------------------------------------------------------------------------------- /database/migrations/2023_04_05_135522_create_messages_table.php: -------------------------------------------------------------------------------- 1 | unsignedBigInteger('id'); 16 | $table->unsignedBigInteger('conversation_id'); 17 | $table->string('role', 30); 18 | $table->text('content')->nullable(); 19 | $table->json('raws')->nullable(); 20 | $table->unsignedInteger('tokens_count')->default(0); 21 | $table->timestamps(); 22 | $table->softDeletes(); 23 | 24 | $table->primary('id'); 25 | $table->index(['conversation_id', 'deleted_at']); 26 | }); 27 | } 28 | 29 | /** 30 | * Reverse the migrations. 31 | */ 32 | public function down(): void 33 | { 34 | Schema::dropIfExists('messages'); 35 | } 36 | }; 37 | -------------------------------------------------------------------------------- /database/migrations/2023_04_06_213947_create_quotas_table.php: -------------------------------------------------------------------------------- 1 | unsignedBigInteger('id'); 16 | $table->boolean('is_available')->default(false); 17 | $table->unsignedBigInteger('user_id')->default(0); 18 | $table->string('type', 30); 19 | $table->string('meter', 30); 20 | $table->json('usage')->nullable(); 21 | $table->timestamps(); 22 | $table->timestamp('expired_at')->nullable(); 23 | 24 | $table->primary('id'); 25 | $table->index(['user_id', 'type', 'is_available']); 26 | }); 27 | } 28 | 29 | /** 30 | * Reverse the migrations. 31 | */ 32 | public function down(): void 33 | { 34 | Schema::dropIfExists('quotas'); 35 | } 36 | }; 37 | -------------------------------------------------------------------------------- /database/migrations/2023_04_10_225239_add_paid_at_to_payments_table.php: -------------------------------------------------------------------------------- 1 | timestamp('paid_at')->nullable()->comment('支付时间'); 16 | }); 17 | } 18 | 19 | /** 20 | * Reverse the migrations. 21 | */ 22 | public function down(): void 23 | { 24 | Schema::table('payments', function (Blueprint $table) { 25 | $table->dropColumn('paid_at'); 26 | }); 27 | } 28 | }; 29 | -------------------------------------------------------------------------------- /database/migrations/2023_04_11_154234_create_user_settings_table.php: -------------------------------------------------------------------------------- 1 | unsignedBigInteger('id'); 16 | $table->unsignedBigInteger('user_id'); 17 | $table->string('key'); 18 | $table->json('value')->nullable(); 19 | $table->timestamps(); 20 | 21 | $table->primary('id'); 22 | $table->unique(['user_id', 'key']); 23 | }); 24 | } 25 | 26 | /** 27 | * Reverse the migrations. 28 | */ 29 | public function down(): void 30 | { 31 | Schema::dropIfExists('user_settings'); 32 | } 33 | }; 34 | -------------------------------------------------------------------------------- /database/migrations/2023_04_11_234900_add_state_to_users_table.php: -------------------------------------------------------------------------------- 1 | string('state', 30)->default('unactivated'); 16 | }); 17 | } 18 | 19 | /** 20 | * Reverse the migrations. 21 | */ 22 | public function down(): void 23 | { 24 | Schema::table('users', function (Blueprint $table) { 25 | $table->dropColumn('state'); 26 | }); 27 | } 28 | }; 29 | -------------------------------------------------------------------------------- /database/migrations/2023_04_17_170137_add_creator_id_to_messages_table.php: -------------------------------------------------------------------------------- 1 | unsignedBigInteger('creator_id')->default(0); 16 | $table->index(['creator_id', 'deleted_at']); 17 | }); 18 | } 19 | 20 | /** 21 | * Reverse the migrations. 22 | */ 23 | public function down(): void 24 | { 25 | Schema::table('messages', function (Blueprint $table) { 26 | $table->dropColumn('creator_id'); 27 | }); 28 | } 29 | }; 30 | -------------------------------------------------------------------------------- /database/migrations/2023_04_17_190002_add_tokens_count_to_quotas_table.php: -------------------------------------------------------------------------------- 1 | unsignedBigInteger('tokens_count')->default(0); 16 | $table->unsignedBigInteger('used_tokens_count')->default(0); 17 | }); 18 | } 19 | 20 | /** 21 | * Reverse the migrations. 22 | */ 23 | public function down(): void 24 | { 25 | Schema::table('quotas', function (Blueprint $table) { 26 | $table->dropColumn('tokens_count'); 27 | $table->dropColumn('used_tokens_count'); 28 | }); 29 | } 30 | }; 31 | -------------------------------------------------------------------------------- /database/migrations/2023_04_18_141745_rename_quota_statements_to_quota_usages_table.php: -------------------------------------------------------------------------------- 1 | decimal('paid_total', 10)->default(0); 16 | }); 17 | } 18 | 19 | /** 20 | * Reverse the migrations. 21 | */ 22 | public function down(): void 23 | { 24 | Schema::table('users', function (Blueprint $table) { 25 | $table->dropColumn('paid_total'); 26 | }); 27 | } 28 | }; 29 | -------------------------------------------------------------------------------- /database/migrations/2023_04_21_213759_rename_raws_to_raw.php: -------------------------------------------------------------------------------- 1 | renameColumn('raws', 'raw'); 16 | }); 17 | 18 | Schema::table('payments', function (Blueprint $table) { 19 | $table->renameColumn('raws', 'raw'); 20 | }); 21 | } 22 | 23 | /** 24 | * Reverse the migrations. 25 | */ 26 | public function down(): void 27 | { 28 | Schema::table('messages', function (Blueprint $table) { 29 | $table->renameColumn('raw', 'raws'); 30 | }); 31 | 32 | Schema::table('payments', function (Blueprint $table) { 33 | $table->renameColumn('raw', 'raws'); 34 | }); 35 | } 36 | }; 37 | -------------------------------------------------------------------------------- /database/migrations/2023_04_22_230318_add_avatar_to_users_table.php: -------------------------------------------------------------------------------- 1 | string('avatar')->nullable(); 16 | }); 17 | } 18 | 19 | /** 20 | * Reverse the migrations. 21 | */ 22 | public function down(): void 23 | { 24 | Schema::table('users', function (Blueprint $table) { 25 | $table->dropColumn('avatar'); 26 | }); 27 | } 28 | }; 29 | -------------------------------------------------------------------------------- /database/migrations/2023_04_26_115133_create_gift_cards_table.php: -------------------------------------------------------------------------------- 1 | unsignedBigInteger('id')->primary(); 16 | $table->unsignedBigInteger('creator_id')->default(0); 17 | $table->unsignedBigInteger('user_id')->default(0); 18 | $table->string('name', 60); 19 | $table->uuid('code'); 20 | $table->timestamps(); 21 | $table->timestamp('used_at')->nullable(); 22 | $table->timestamp('expired_at')->nullable(); 23 | $table->unsignedInteger('tokens_count')->default(0); 24 | $table->unsignedInteger('days')->default(0); 25 | }); 26 | } 27 | 28 | /** 29 | * Reverse the migrations. 30 | */ 31 | public function down(): void 32 | { 33 | Schema::dropIfExists('gift_cards'); 34 | } 35 | }; 36 | -------------------------------------------------------------------------------- /database/migrations/2023_04_28_184904_add_state_to_quotas.php: -------------------------------------------------------------------------------- 1 | string('state', 30)->default('pending'); 16 | }); 17 | } 18 | 19 | /** 20 | * Reverse the migrations. 21 | */ 22 | public function down(): void 23 | { 24 | Schema::table('quotas', function (Blueprint $table) { 25 | $table->dropColumn('state'); 26 | }); 27 | } 28 | }; 29 | -------------------------------------------------------------------------------- /database/migrations/2023_05_20_113551_add_email_to_users_table.php: -------------------------------------------------------------------------------- 1 | string('email')->nullable()->after('name')->unique(); 16 | }); 17 | 18 | Schema::table('users', function (Blueprint $table) { 19 | $table->string('email')->nullable()->after('phone_number')->unique(); 20 | }); 21 | } 22 | 23 | /** 24 | * Reverse the migrations. 25 | */ 26 | public function down(): void 27 | { 28 | Schema::table('users', function (Blueprint $table) { 29 | $table->dropColumn(['email']); 30 | }); 31 | 32 | Schema::table('profiles', function (Blueprint $table) { 33 | $table->dropColumn(['email']); 34 | }); 35 | } 36 | }; 37 | -------------------------------------------------------------------------------- /database/migrations/2023_05_27_232353_create_tags_table.php: -------------------------------------------------------------------------------- 1 | id(); 16 | $table->string('name'); 17 | $table->string('icon')->nullable(); 18 | $table->unsignedInteger('sort_order')->default(0); 19 | $table->timestamps(); 20 | $table->softDeletes(); 21 | }); 22 | 23 | Schema::create('taggables', function (Blueprint $table) { 24 | $table->unsignedBigInteger('tag_id'); 25 | $table->morphs('taggable'); 26 | }); 27 | } 28 | 29 | /** 30 | * Reverse the migrations. 31 | */ 32 | public function down(): void 33 | { 34 | Schema::dropIfExists('tags'); 35 | Schema::dropIfExists('taggables'); 36 | } 37 | }; 38 | -------------------------------------------------------------------------------- /database/migrations/2023_05_29_225623_add_prompt_id_to_conversations_table.php: -------------------------------------------------------------------------------- 1 | unsignedBigInteger('prompt_id')->default(0)->after('title'); 16 | }); 17 | } 18 | 19 | /** 20 | * Reverse the migrations. 21 | */ 22 | public function down(): void 23 | { 24 | Schema::table('conversations', function (Blueprint $table) { 25 | $table->dropColumn('prompt_id'); 26 | }); 27 | } 28 | }; 29 | -------------------------------------------------------------------------------- /database/migrations/2023_06_06_222158_add_greeting_to_prompts_table.php: -------------------------------------------------------------------------------- 1 | string('greeting')->after('logo'); 16 | }); 17 | } 18 | 19 | /** 20 | * Reverse the migrations. 21 | */ 22 | public function down(): void 23 | { 24 | Schema::table('prompts', function (Blueprint $table) { 25 | $table->dropColumn('greeting'); 26 | }); 27 | } 28 | }; 29 | -------------------------------------------------------------------------------- /database/migrations/2023_06_14_214734_update_profiles_unique_key.php: -------------------------------------------------------------------------------- 1 | dropUnique(['email']); 16 | $table->unique(['email', 'platform']); 17 | }); 18 | } 19 | 20 | /** 21 | * Reverse the migrations. 22 | */ 23 | public function down(): void 24 | { 25 | Schema::table('profiles', function (Blueprint $table) { 26 | // 27 | }); 28 | } 29 | }; 30 | -------------------------------------------------------------------------------- /database/migrations/2023_07_03_215652_add_deleted_at_tp_payments.php: -------------------------------------------------------------------------------- 1 | softDeletes(); 16 | }); 17 | } 18 | 19 | /** 20 | * Reverse the migrations. 21 | */ 22 | public function down(): void 23 | { 24 | Schema::table('payments', function (Blueprint $table) { 25 | $table->dropSoftDeletes(); 26 | }); 27 | } 28 | }; 29 | -------------------------------------------------------------------------------- /database/migrations/2023_07_04_130119_update_rewards_amount_to_decimal.php: -------------------------------------------------------------------------------- 1 | decimal('amount', 10, 2)->change(); 16 | }); 17 | } 18 | 19 | /** 20 | * Reverse the migrations. 21 | */ 22 | public function down(): void 23 | { 24 | Schema::table('rewards', function (Blueprint $table) { 25 | $table->integer('amount')->change(); 26 | }); 27 | } 28 | }; 29 | -------------------------------------------------------------------------------- /database/seeders/DatabaseSeeder.php: -------------------------------------------------------------------------------- 1 | create(); 16 | 17 | // \App\Models\User::factory()->create([ 18 | // 'name' => 'Test User', 19 | // 'email' => 'test@example.com', 20 | // ]); 21 | } 22 | } 23 | -------------------------------------------------------------------------------- /database/seeders/PromptSeeder.php: -------------------------------------------------------------------------------- 1 | get('https://raw.githubusercontent.com/rockbenben/ChatGPT-Shortcut/main/src/data/prompt.json') 17 | ->json(); 18 | 19 | foreach ($prompts as $prompt) { 20 | $promptZh = $prompt['zh']; 21 | 22 | Prompt::updateOrCreate([ 23 | 'name' => $promptZh['title'], 24 | ], [ 25 | 'description' => $promptZh['remark'], 26 | 'prompt_cn' => $promptZh['description'], 27 | 'prompt_en' => $promptZh['prompt'], 28 | 'sort_order' => $promptZh['weight'] ?? 0, 29 | 'greeting' => '嗨,欢迎来到一刻,你想聊点什么?', 30 | ]); 31 | } 32 | } 33 | } 34 | -------------------------------------------------------------------------------- /docker/.gitignore: -------------------------------------------------------------------------------- 1 | .idea 2 | .DS_Store 3 | -------------------------------------------------------------------------------- /docker/cert/ca.sh: -------------------------------------------------------------------------------- 1 | if [ "$(curl -skL -w '%{http_code}' https://curl.se/ca/cacert.pem -o /data/certs/cacert.pem)" = "200" ]; then 2 | exit 0 3 | else 4 | printf 'ERROR: HTTP STATUS CODE NOT 200' 5 | exit 1 6 | fi 7 | -------------------------------------------------------------------------------- /docker/config/cron.d/custom-schedule: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/yikeio/server/8c05d8c33a31b25d33fd90560c26a052fb819f96/docker/config/cron.d/custom-schedule -------------------------------------------------------------------------------- /docker/config/cron.d/laravel-schedule: -------------------------------------------------------------------------------- 1 | * * * * * su -s /bin/bash -c "cd /data/www && php artisan schedule:run >> /data/logs/cron.log 2>&1" www-data 2 | -------------------------------------------------------------------------------- /docker/config/nginx/configs/general.conf: -------------------------------------------------------------------------------- 1 | # favicon.ico 2 | location = /favicon.ico { 3 | log_not_found off; 4 | } 5 | 6 | # robots.txt 7 | location = /robots.txt { 8 | log_not_found off; 9 | } 10 | 11 | # assets, media 12 | location ~* \.(?:css(\.map)?|js(\.map)?|jpe?g|png|gif|ico|cur|heic|webp|tiff?|mp3|m4a|aac|ogg|midi?|wav|mp4|mov|webm|mpe?g|avi|ogv|flv|wmv)$ { 13 | expires 7d; 14 | } 15 | 16 | # svg, fonts 17 | location ~* \.(?:svgz?|ttf|ttc|otf|eot|woff2?)$ { 18 | add_header Access-Control-Allow-Origin "*"; 19 | expires 7d; 20 | } 21 | 22 | # gzip 23 | gzip on; 24 | gzip_vary on; 25 | gzip_proxied any; 26 | gzip_comp_level 6; 27 | gzip_types text/plain text/css text/xml application/json application/javascript application/rss+xml application/atom+xml image/svg+xml; 28 | -------------------------------------------------------------------------------- /docker/config/nginx/configs/php_fastcgi.conf: -------------------------------------------------------------------------------- 1 | # 404 2 | try_files $fastcgi_script_name =404; 3 | 4 | # default fastcgi_params 5 | include fastcgi_params; 6 | 7 | # fastcgi settings 8 | fastcgi_index index.php; 9 | fastcgi_buffers 8 16k; 10 | fastcgi_buffer_size 32k; 11 | 12 | # fastcgi params 13 | fastcgi_param DOCUMENT_ROOT $realpath_root; 14 | fastcgi_param SCRIPT_FILENAME $realpath_root$fastcgi_script_name; 15 | -------------------------------------------------------------------------------- /docker/config/nginx/configs/security.conf: -------------------------------------------------------------------------------- 1 | # security headers 2 | add_header X-XSS-Protection "1; mode=block" always; 3 | add_header X-Content-Type-Options "nosniff" always; 4 | add_header Referrer-Policy "no-referrer-when-downgrade" always; 5 | add_header Content-Security-Policy "default-src 'self' http: https: ws: wss: data: blob: 'unsafe-inline'; frame-ancestors 'self';" always; 6 | add_header Permissions-Policy "interest-cohort=()" always; 7 | 8 | # . files 9 | location ~ /\.(?!well-known) { 10 | deny all; 11 | } 12 | -------------------------------------------------------------------------------- /docker/config/nginx/www.conf: -------------------------------------------------------------------------------- 1 | server { 2 | listen 80; 3 | listen [::]:80; 4 | server_name _; 5 | root /data/www/public/; 6 | 7 | # security 8 | include configs/security.conf; 9 | 10 | # logging 11 | access_log /data/logs/nginx/access.log json; 12 | error_log /data/logs/nginx/error.log warn; 13 | 14 | # index.php 15 | index index.php; 16 | 17 | # index.php fallback 18 | location / { 19 | try_files $uri $uri/ /index.php?$query_string; 20 | } 21 | 22 | # additional config 23 | include configs/general.conf; 24 | 25 | # handle .php 26 | location ~ \.php$ { 27 | fastcgi_pass unix:/var/run/php/php8.1-fpm.sock; 28 | include configs/php_fastcgi.conf; 29 | } 30 | } 31 | -------------------------------------------------------------------------------- /docker/config/supervisord/worker.conf: -------------------------------------------------------------------------------- 1 | [program:yike-worker-queue] 2 | process_name=%(program_name)s_%(process_num)02d 3 | command=/usr/bin/php /data/www/artisan queue:work redis --sleep=3 --tries=1 --timeout=1800 --max-time=3600 4 | autostart=true 5 | autorestart=true 6 | user=www-data 7 | numprocs=%(ENV_LARAVEL_DEFAULT_WORKER_COUNT)s 8 | redirect_stderr=true 9 | startsecs=0 10 | stopwaitsecs=360 11 | stdout_logfile_maxbytes=10MB 12 | stdout_logfile_backups=10 13 | stdout_logfile=/data/www/storage/logs/worker.log 14 | -------------------------------------------------------------------------------- /docker/readme.md: -------------------------------------------------------------------------------- 1 | ## Yike Tech 容器化部署 2 | 3 | ### 系统 4 | 5 | - Debian:stable 6 | 7 | ### 软件 8 | 9 | - Nginx 10 | - PHP 8.1 11 | - Supervisord 12 | - Cron 13 | 14 | ### 启动 15 | 16 | - start-all 单实例模式 17 | - start-container Web 容器 18 | - start-crond 定时容器 19 | - start-queue 队列容器 20 | 21 | ### 环境变量 22 | - PHP_FPM_PM_MAX_CHILDREN 23 | - LARAVEL_DEFAULT_WORKER_COUNT 24 | -------------------------------------------------------------------------------- /docker/script/start-all: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env bash 2 | 3 | set -euxo pipefail 4 | 5 | AUTO_MIGRATION=${AUTO_MIGRATION:-no} 6 | 7 | if [ "$AUTO_MIGRATION" == yes ]; then 8 | /usr/bin/php /data/www/artisan migrate --force 9 | fi 10 | 11 | /usr/bin/php /data/www/artisan optimize 12 | 13 | chown -R www-data:www-data /data/www/storage/logs 14 | 15 | cron 16 | supervisord 17 | php-fpm8.1 18 | 19 | cat /etc/cron.d/*-schedule | crontab - 20 | 21 | nginx -g 'daemon off;' 22 | -------------------------------------------------------------------------------- /docker/script/start-container: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env bash 2 | 3 | set -euxo pipefail 4 | 5 | AUTO_MIGRATION=${AUTO_MIGRATION:-no} 6 | 7 | if [ "$AUTO_MIGRATION" == yes ]; then 8 | /usr/bin/php /data/www/artisan migrate --force 9 | fi 10 | 11 | /usr/bin/php /data/www/artisan optimize 12 | 13 | chown -R www-data:www-data /data/www/storage/logs 14 | 15 | cron 16 | php-fpm8.1 17 | 18 | crontab /etc/cron.d/custom-schedule 19 | 20 | nginx -g 'daemon off;' 21 | -------------------------------------------------------------------------------- /docker/script/start-crond: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env bash 2 | 3 | set -euxo pipefail 4 | 5 | /usr/bin/php /data/www/artisan optimize 6 | 7 | chown -R www-data:www-data /data/www/storage/logs 8 | 9 | cron 10 | 11 | cat /etc/cron.d/*-schedule | crontab - 12 | 13 | tail -f /data/logs/cron.log 14 | -------------------------------------------------------------------------------- /docker/script/start-queue: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env bash 2 | 3 | set -euxo pipefail 4 | 5 | /usr/bin/php /data/www/artisan optimize 6 | 7 | chown -R www-data:www-data /data/www/storage/logs 8 | 9 | cron 10 | 11 | crontab /etc/cron.d/custom-schedule 12 | 13 | supervisord --nodaemon 14 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "private": true, 3 | "scripts": { 4 | "dev": "vite", 5 | "build": "vite build" 6 | }, 7 | "devDependencies": { 8 | "axios": "^1.1.2", 9 | "laravel-vite-plugin": "^0.7.2", 10 | "vite": "^4.0.0" 11 | } 12 | } 13 | -------------------------------------------------------------------------------- /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 | -------------------------------------------------------------------------------- /public/favicon.ico: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/yikeio/server/8c05d8c33a31b25d33fd90560c26a052fb819f96/public/favicon.ico -------------------------------------------------------------------------------- /public/robots.txt: -------------------------------------------------------------------------------- 1 | User-agent: * 2 | Disallow: 3 | -------------------------------------------------------------------------------- /resources/css/app.css: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/yikeio/server/8c05d8c33a31b25d33fd90560c26a052fb819f96/resources/css/app.css -------------------------------------------------------------------------------- /resources/js/app.js: -------------------------------------------------------------------------------- 1 | import './bootstrap'; 2 | -------------------------------------------------------------------------------- /routes/api.php: -------------------------------------------------------------------------------- 1 | id === (int) $id; 18 | }); 19 | -------------------------------------------------------------------------------- /routes/console.php: -------------------------------------------------------------------------------- 1 | comment(Inspiring::quote()); 19 | })->purpose('Display an inspiring quote'); 20 | -------------------------------------------------------------------------------- /storage/app/.gitignore: -------------------------------------------------------------------------------- 1 | * 2 | !public/ 3 | !.gitignore 4 | -------------------------------------------------------------------------------- /storage/app/public/.gitignore: -------------------------------------------------------------------------------- 1 | * 2 | !.gitignore 3 | -------------------------------------------------------------------------------- /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 | -------------------------------------------------------------------------------- /storage/framework/cache/.gitignore: -------------------------------------------------------------------------------- 1 | * 2 | !data/ 3 | !.gitignore 4 | -------------------------------------------------------------------------------- /storage/framework/cache/data/.gitignore: -------------------------------------------------------------------------------- 1 | * 2 | !.gitignore 3 | -------------------------------------------------------------------------------- /storage/framework/sessions/.gitignore: -------------------------------------------------------------------------------- 1 | * 2 | !.gitignore 3 | -------------------------------------------------------------------------------- /storage/framework/testing/.gitignore: -------------------------------------------------------------------------------- 1 | * 2 | !.gitignore 3 | -------------------------------------------------------------------------------- /storage/framework/views/.gitignore: -------------------------------------------------------------------------------- 1 | * 2 | !.gitignore 3 | -------------------------------------------------------------------------------- /storage/logs/.gitignore: -------------------------------------------------------------------------------- 1 | * 2 | !.gitignore 3 | -------------------------------------------------------------------------------- /tests/CreatesApplication.php: -------------------------------------------------------------------------------- 1 | make(Kernel::class)->bootstrap(); 18 | 19 | return $app; 20 | } 21 | } 22 | -------------------------------------------------------------------------------- /tests/Feature/ExampleTest.php: -------------------------------------------------------------------------------- 1 | get('/'); 16 | 17 | $response->assertStatus(200); 18 | } 19 | } 20 | -------------------------------------------------------------------------------- /tests/TestCase.php: -------------------------------------------------------------------------------- 1 | assertTrue(true); 15 | } 16 | } 17 | -------------------------------------------------------------------------------- /vite.config.js: -------------------------------------------------------------------------------- 1 | import { defineConfig } from 'vite'; 2 | import laravel from 'laravel-vite-plugin'; 3 | 4 | export default defineConfig({ 5 | plugins: [ 6 | laravel({ 7 | input: ['resources/css/app.css', 'resources/js/app.js'], 8 | refresh: true, 9 | }), 10 | ], 11 | }); 12 | --------------------------------------------------------------------------------