Shivamyadav wrote a reply+100 XP
10h ago
Yeah, its a japan's website.
We are using the AWS to stream the video via the .ts and .mu38 and added the signed URL with the minimum 20 sec time period before the url expired.
Shivamyadav started a new conversation+100 XP
17h ago
The Workflow User pays on clients website and redirects to a watch page on the Client's website.
User clicks a "Watch Video" button.
The button triggers an API call to Our Main Website.
Our backend saves the user's ID/Email to our database, requests an embed link from UIshare, and returns it to the client's frontend.
The Problem If our backend returns a standard, static URL, users can inspect the page source, copy the video link, and share it with unauthorized people who can then watch it for free.
What I Need Help With How do I implement a secure architecture to prevent this? Specifically:
Short-Lived Signed URLs: How to generate an embed link that expires in 30 seconds (preventing sharing), but allows the video to keep playing uninterrupted for the legitimate user once started?
IP/Domain Binding: How to restrict the video player so it only plays if accessed from the buyer's IP address and our approved domain?
Concurrent Session Blocks: Best practices for tracking active streams on our backend to stop a user from opening the video in multiple tabs or devices simultaneously.
Shivamyadav wrote a reply+100 XP
4d ago
I have tried using the browser performance api and it's working fine now.
It's a client's requirements for the all types of the data transferred to be charged from the users based on the particular organization according to the aws.
So I have created a cron job to get the usd price list accordingly to the AWS price tier based on the GB, TB and store all the browsers client side data transfer to the seperate log table but my concern was how long and when should I store the data transferred form the browser to the db like hitting the api endpoint.
Shivamyadav started a new conversation+100 XP
4d ago
I'm tracking actual data transferred to users (videos, images, JS, CSS, fonts, etc.) and need to store usage statistics per learner/client.
What's the recommended approach to avoid losing usage data when users:
- Refresh or close the tab
- Navigate away
- Lose internet connectivity
- Experience browser crashes
Would periodic syncing + navigator.sendBeacon() + local storage retries be sufficient, or is there a better pattern?
Shivamyadav wrote a reply+100 XP
1w ago
Yeah, thanks I will look for the cue package.
As I have many configs files and constants files with huge number of keys and also I don't want to pass them from controller every time I need to use the key and accept those as a prop and use it. It will be like overwork and headaches.
Shivamyadav started a new conversation+100 XP
1w ago
I’m working with Laravel + Inertia + Vue 3 and I’m struggling with how to properly expose backend data like config values, constants, and translations to the frontend in a scalable way.
Using HandleInertiaRequests::share() for everything feels wrong once the app grows. The payload gets bigger, everything is sent on every request, and it turns into a dumping ground for global data. Accessing it in Vue via usePage() also becomes messy.
Shivamyadav wrote a reply+100 XP
1w ago
Yeah, but the issue is how can I get those details from the Js on each page load ?
Shivamyadav started a new conversation+100 XP
1w ago
I'm trying to track the actual amount of data transferred when a user loads a page, including:
- Images
- Videos
- CSS
- JavaScript
- Fonts
- Assets served from S3/CloudFront
I tried measuring the Laravel response size, but that only includes the HTML/JSON returned by Laravel and not the assets loaded by the browser.
I also tried using the browser Performance API (performance.getEntriesByType('resource')), but many external/CDN resources report a transfer size of 0.
What is the recommended way to accurately track the total data transferred for a user/page load? Is there a reliable approach for capturing all asset downloads, including files served from CloudFront and S3?
Shivamyadav wrote a reply+100 XP
2w ago
My rollup options form the vite config file
import { defineConfig } from 'vite';
import laravel from 'laravel-vite-plugin';
import path from 'path';
export default defineConfig({
plugins: [
laravel({
input: [
'resources/assets/desktop/css/sass/app.scss',
],
refresh: true,
}),
],
server: {
host: '0.0.0.0', // Allow access from local network / custom domains
port: 5176,
cors: {
origin: [
'http://manual5.uishare.local',
'http://127.0.0.1',
'http://localhost',
/^https?:\/\/.*\.uishare\.local(:\d+)?$/, // Allow all subdomains of uishare.local
],
credentials: true,
},
hmr: {
host: 'manual5.uishare.local',
port: 5176,
},
},
build: {
rollupOptions: {
input: {
'head-scripts': 'resources/js/head-scripts.js',
'vendor': 'resources/js/vendor.js',
'app': 'resources/js/app.js',
'embed': 'resources/js/embed.js',
'site': 'resources/js/site.js',
},
output: {
format: 'iife',
entryFileNames: 'desktop/js/[name].js',
chunkFileNames: 'desktop/js/[name].js',
assetFileNames: 'desktop/[ext]/[name].[ext]',
},
},
},
resolve: {
alias: {
'@': path.resolve(__dirname, 'resources'),
},
},
});
Even I am getting the errors like :
siteApp is not defined $(...).select2 is not a function
$(...).slick is not a function
$ jquery and sometimes angular
Shivamyadav wrote a reply+100 XP
2w ago
Thanks for the suggestion man, I will give it a try and let you know if this was able to setup or not.
Shivamyadav liked a comment+100 XP
2w ago
The issue here is that Vite treats assets as ES modules by default, whereas your current legacy setup relies on strict, sequential concatenation (mix.babel) where jQuery, Angular, and all those plugins register themselves globally on the window object. If Vite processes them, they run in their own module scopes, breaking dependencies like angular or siteApp because jQuery plugins can't find jQuery, and Angular plugins can't find angular.
Since you have a massive list of legacy jQuery and AngularJS v1/v2 vendors, trying to import them properly via ES modules will be an endless rabbit hole of configuration headaches.
The cleanest solution is to let Vite handle your modern code (like your Sass/CSS), but use Vite's build.rollupOptions to keep bundling these legacy assets into flat files, while forcing them to remain global scripts.
Shivamyadav started a new conversation+100 XP
2w ago
I have setup the vite and removed the mix and scss is loading and working with the laravel but the angular js version 2 is breaking. How can i setup the order as it is and avoid any siteApp issue form the angular itself this is how it was setup form the webpack.
let mix = require('laravel-mix');
mix
.sass('resources/assets/desktop/sass/app.scss', 'public/desktop/css/app.css')
.styles([
'resources/assets/desktop/css/vendor/jquery-ui.min.css',
'resources/assets/desktop/css/vendor/colorbox.css',
'resources/assets/desktop/css/vendor/croppie.min.css',
'resources/assets/desktop/css/vendor/slick.css',
'resources/assets/desktop/css/vendor/animate.css',
'resources/assets/desktop/css/vendor/aria-accordion.css',
'resources/assets/desktop/css/vendor/sm-core-css.css',
'resources/assets/desktop/css/vendor/fSelect.css',
'resources/assets/desktop/css/vendor/css-menu.css',
'resources/assets/desktop/css/vendor/easy-responsive-tabs.css',
'resources/assets/desktop/css/vendor/select2.min.css',
'resources/assets/desktop/css/vendor/jquery.fileupload-ui.css',
'resources/assets/desktop/css/vendor/jquery.fileupload.css',
'resources/assets/desktop/css/vendor/selectric.css',
'resources/assets/desktop/css/vendor/draganddrop.css',
'resources/assets/desktop/css/vendor/jquery.datetimepicker.css',
'resources/assets/desktop/css/vendor/jquery-editable.css',
'resources/assets/desktop/css/vendor/daterangepicker.css',
'resources/assets/desktop/css/vendor/font-awesome.min.css',
'resources/assets/desktop/css/vendor/yakuhanjp-noto.min.css',
'resources/assets/desktop/css/vendor/video-js.min.css', // VideoJS Player Library
'resources/assets/desktop/css/vendor/videojs-vr.css', // VideoJS VR Plugin
], 'public/desktop/css/vendor.css')
.babel([
'resources/assets/desktop/js/vendor/modernizr.custom.js',
'resources/assets/desktop/js/vendor/jwplayer.min.js',
'resources/assets/desktop/js/vendor/chart.min.js',
'resources/assets/desktop/js/vendor/chartjs-plugin-datalabels.min.js'
], 'public/desktop/js/head-scripts.js')
.babel([
'resources/assets/desktop/js/vendor/jquery.js',
'resources/assets/desktop/js/vendor/jquery-migrate.min.js',
'resources/assets/desktop/js/vendor/moment.min.js',
'resources/assets/desktop/js/vendor/moment-timezone.min.js',
'resources/assets/desktop/js/vendor/lodash.min.js',
'resources/assets/desktop/js/vendor/angular.js',
'resources/assets/desktop/js/vendor/jquery-ui.min.js',
'resources/assets/desktop/js/vendor/jquery.ui.widget.js',
'resources/assets/desktop/js/vendor/angular-resource.js',
'resources/assets/desktop/js/vendor/angular-sanitize.min.js',
'resources/assets/desktop/js/vendor/slick.min.js',
'resources/assets/desktop/js/vendor/colorbox.js',
'resources/assets/desktop/js/vendor/jquery.dlmenu.js',
'resources/assets/desktop/js/vendor/aria-accordion.js',
'resources/assets/desktop/js/vendor/jquery.custom-file-input.js',
'resources/assets/desktop/js/vendor/jquery.smartmenus.js',
'resources/assets/desktop/js/vendor/fSelect.js',
'resources/assets/desktop/js/vendor/easyResponsiveTabs.js',
'resources/assets/desktop/js/vendor/select2.min.js',
'resources/assets/desktop/js/vendor/ja.js',
'resources/assets/desktop/js/vendor/select2.multi-checkboxes.js',
'resources/assets/desktop/js/vendor/jquery.multi-select.js',
'resources/assets/desktop/js/vendor/croppie.min.js',
'resources/assets/desktop/js/vendor/load-image.all.min.js',
'resources/assets/desktop/js/vendor/jquery.blueimp-gallery.min.js',
'resources/assets/desktop/js/vendor/canvas-to-blob.min.js',
'resources/assets/desktop/js/vendor/jquery.iframe-transport.js',
'resources/assets/desktop/js/vendor/jquery.fileupload.js',
'resources/assets/desktop/js/vendor/jquery.fileupload-process.js',
'resources/assets/desktop/js/vendor/jquery.fileupload-image.js',
'resources/assets/desktop/js/vendor/jquery.fileupload-audio.js',
'resources/assets/desktop/js/vendor/jquery.fileupload-video.js',
'resources/assets/desktop/js/vendor/jquery.fileupload-validate.js',
'resources/assets/desktop/js/vendor/jquery.fileupload-angular.js',
'resources/assets/desktop/js/vendor/jquery.matchHeight-min.js',
'resources/assets/desktop/js/vendor/exif-js.js',
'resources/assets/desktop/js/vendor/draganddrop.js',
'resources/assets/desktop/js/vendor/angular-chart.js',
'resources/assets/desktop/js/vendor/jquery.datetimepicker.full.js',
'resources/assets/desktop/js/vendor/jquery-editable-poshytip.min.js',
'resources/assets/desktop/js/vendor/jscolor.js',
'resources/assets/desktop/js/vendor/daterangepicker.min.js',
'resources/assets/desktop/js/vendor/clipboard.min.js',
'node_modules/bootstrap/dist/js/bootstrap.bundle.min.js',
], 'public/desktop/js/vendor.js')
.babel([
'resources/assets/desktop/js/site/app/main.js',
'resources/assets/desktop/js/site/app/controllers/*.js',
'resources/assets/desktop/js/site/app/directives/*.js',
'resources/assets/desktop/js/site/app/services/*.js',
'resources/assets/desktop/js/site/app/filters/*.js'
], 'public/desktop/js/app.js')
.babel([
'resources/assets/desktop/js/vendor/jquery.js',
'resources/assets/desktop/js/site/embed/main.js',
], 'public/desktop/js/embed.js')
.babel([
'resources/assets/desktop/js/vendor/jquery.selectric.js',
'resources/assets/desktop/js/site/site.js',
'resources/assets/desktop/js/site/access/main.js',
'resources/assets/desktop/js/site/access-management/main.js',
'resources/assets/desktop/js/site/access-security/main.js',
'resources/assets/desktop/js/site/account-subscription/main.js',
'resources/assets/desktop/js/site/badge/main.js',
'resources/assets/desktop/js/site/base/header-menu.js',
'resources/assets/desktop/js/site/blocked-domain/main.js',
'resources/assets/desktop/js/site/card-payment/main.js',
'resources/assets/desktop/js/site/category/main.js',
'resources/assets/desktop/js/site/certificate/main.js',
'resources/assets/desktop/js/site/coupon/main.js',
'resources/assets/desktop/js/site/course/main.js',
'resources/assets/desktop/js/site/course-management/main.js',
'resources/assets/desktop/js/site/coverpage/main.js',
'resources/assets/desktop/js/site/dashboard/main.js',
'resources/assets/desktop/js/site/division/main.js',
'resources/assets/desktop/js/site/division/open.js',
'resources/assets/desktop/js/site/division-management/main.js',
'resources/assets/desktop/js/site/document/main.js',
'resources/assets/desktop/js/site/emailer/main.js',
'resources/assets/desktop/js/site/event/main.js',
'resources/assets/desktop/js/site/general/main.js',
'resources/assets/desktop/js/site/geographical/main.js',
'resources/assets/desktop/js/site/goto-meeting-account/main.js',
'resources/assets/desktop/js/site/home/main.js',
'resources/assets/desktop/js/site/language/main.js',
'resources/assets/desktop/js/site/login/main.js',
'resources/assets/desktop/js/site/message/main.js',
'resources/assets/desktop/js/site/multimedia/main.js',
'resources/assets/desktop/js/site/notification/main.js',
'resources/assets/desktop/js/site/package-management/main.js',
'resources/assets/desktop/js/site/profile/main.js',
'resources/assets/desktop/js/site/register/main.js',
'resources/assets/desktop/js/site/resume-payment/main.js',
'resources/assets/desktop/js/site/setting/main.js',
'resources/assets/desktop/js/site/setup-card/main.js',
'resources/assets/desktop/js/site/test/main.js',
'resources/assets/desktop/js/site/test-management/main.js',
'resources/assets/desktop/js/site/transaction/main.js',
'resources/assets/desktop/js/site/user/main.js',
'resources/assets/desktop/js/site/user-management/main.js',
], 'public/desktop/js/site.js')
.babel([
'public/desktop/js/app.js',
'public/desktop/js/site.js',
], 'public/desktop/js/platform.js')
.options({
processCssUrls : false
});
Shivamyadav wrote a reply+100 XP
4w ago
As there are many more designs in the Admin panel and I have seen only the admin panel users and assign tasks to the users and the modal for the assignees listings etc stuff..
I am a really big fan of everything Jeffrey's do for the laracasts and his designs too.
Shivamyadav started a new conversation+100 XP
4w ago
I want to get the some inspiration and idea form the laracasts admin panel.
I have seen it form the Jeffrey's video .
@JeffreyWay If possible can I get the public link to see the designs?
Shivamyadav started a new conversation+100 XP
4w ago
Any way to pass the browser's session storage data to the middleware without the query params?
As I am working with the multiple test active session prevention for the multiple tabs, I could not get the active tab ID to the midddeware?
Any idea how can i get or pass, as query params is the option but user can manually copy and use it.
I am using the reverb to prevent it and send the echo channel to listen it handle it.
Shivamyadav wrote a reply+100 XP
1mo ago
This a actually a solid way to do this.
As my client do not want to increase the payment cost for the pusher so he denies for this.
Any proper alternative solution for this, could you suggest me.
Updated requirements are:
- A user can have only ONE active test session at a time for a given test.
- Same Session Continuity (Allowed).
- Prevent Multi-Tab / Multi-Device Access.
- How to handle for the scenario when the browser session expires and device shutdown or user did not finished the test and returned back etc stuffs.
Shivamyadav wrote a reply+100 XP
1mo ago
This a actually a solid way to do this.
As my client do not want to increase the payment cost for the pusher so he denies for this.
Any proper alternative solution for this, could you suggest me.
Updated requirements are:
- A user can have only ONE active test session at a time for a given test.
- Same Session Continuity (Allowed).
- Prevent Multi-Tab / Multi-Device Access.
- How to handle for the scenario when the browser session expires and device shutdown or user did not finished the test and returned back etc stuffs.
Shivamyadav wrote a reply+100 XP
1mo ago
Thank you for the suggestion 😊.
I will look at this approach and let you know, if I can get it .
Shivamyadav liked a comment+100 XP
1mo ago
Thanks, ChatGPT.
Shivamyadav wrote a reply+100 XP
1mo ago
Shivamyadav started a new conversation+100 XP
1mo ago
I am building an online test/exam system in Laravel where users access URLs like:
http://manual5.uishare.local/test/{id}/attempt
I want to prevent a user from:
Opening the same test in multiple tabs
If a test is already active, I want to show a message like: "This test is already active in another session/tab."
My concerns:
- Reliability
- Preventing race conditions
- Handling browser crashes/tab closes
- Supporting scalability
- Avoiding false locks
- Security against bypassing frontend checks
Shivamyadav wrote a reply+100 XP
1mo ago
Yeah, there's no any test written for a single piece of code..
Shivamyadav wrote a reply+100 XP
1mo ago
Function to create the user
public function create($data) {
/** Default Variables **/
$allowImageProcessing = false;
$organizationID = session()->exists('org.id') ? session('org.id') : (auth()->check() ? auth()->user()->organization_id : null);
$organizationUsagePlanID = auth()->check() ? auth()->user()->organization()->value('learner_plan_id') : session('org.learner_plan_id');
$distributedMarketplaceFlag = $this->settingRepository->findByKey('distributed_marketplace', $organizationID);
$isDistributedMarketplace = (! is_null($distributedMarketplaceFlag) && $distributedMarketplaceFlag == 1);
$isExceptionClient = in_array(session('org.sub_domain'), config('constants.csv_upload.exception_clients'));
$isMarketplace = $this->organizationRepository->isMarketplace($organizationID);
$isWhiteLabel = $this->organizationRepository->isWhiteLabel($organizationID);
$newUserPlan = null;
$setEmpIDAsPassword = $this->settingRepository->findByKey('set_employee_id_as_password', $organizationID);
$notificationByEmailFlag = $this->settingRepository->findByKey('all_notifications_to_email', $organizationID);
$setInviteFlag = false; // INFO: Allows to Send the Invite Email Twice
$storageUsed = 0;
/** Default Variables **/
/** Additional Metadata **/
$data['user']['organization_id'] = $organizationID;
if ($isExceptionClient || in_array(session('org.sub_domain'), config('clients.smasterkoushin.subdomains'))) { // INFO: User Activation (for Limited Clients)
$data['user']['is_active'] = 1; // Exception: Activate without Verification
} elseif (! array_key_exists('is_active', $data['user'])) {
$data['user']['is_active'] = 0; // INFO: Assign Inactive Status and User will Activate Post Password Reset
}
if (! is_null($setEmpIDAsPassword) && ($setEmpIDAsPassword == 1) && array_key_exists('emp_id', $data['user']) && strlen($data['user']['emp_id']) > 0) { // INFO: Flag to Force Assign Employee ID as Password
$data['user']['skip_reset_password'] = 1; // INFO: Skips Password Reset Step from Invite Email SSO Link
$data['user']['password'] = Hash::make($data['user']['emp_id']); // INFO: Override Password (if exists)
}
/** Additional Metadata **/
/** Upload Image (if exists) **/
if (array_key_exists('profile_image', $data['user'])) {
$allowImageProcessing = true; // Image Processing Needed
/** Upload Profile Image **/
$data['user']['profile_image'] = $this->s3Service->uploadFile('user-profile-image', $data['user']['profile_image']);
/** Upload Profile Image **/
/** Calculate Storage Used **/
$signedURL = $this->cloudFrontService->getSignedURL($data['user']['profile_image'], config('time.5_minutes'));
$fileHeaders = get_headers($signedURL, true);
$storageUsed += $this->storageService->convertBytesToKB($fileHeaders['Content-Length']);
/** Calculate Storage Used **/
} else {
if (in_array(session('org.sub_domain'), config('clients.chronos.subdomains'))) {
$data['user']['profile_image'] = env('S3_DM_CF_URL').'/cat.jpg';
} elseif (in_array(session('org.sub_domain'), config('clients.fujikura.subdomains'))) {
$data['user']['profile_image'] = env('S3_DM_CF_URL').'/white.png';
}
}
/** Upload Image (if exists) **/
/** Upgrade Learner Plan (if learner capacity exceeds and no plan is scheduled) **/
$totalUsers = ($this->getCountByRole() + 1);
if (($organizationUsagePlanID == config('constants.plans.users.small_team')) && ($totalUsers > config('constants.plans.users.small_team_user_limit'))) {
$newUserPlan = $this->organizationRepository->getNextUserPlan();
}
/** Upgrade Learner Plan (if learner capacity exceeds and no plan is scheduled) **/
/** Upgrade GTM Users Plan **/
if ($isWhiteLabel == false) { // INFO: Only for Organization Client
$this->manageGTMSubscription($totalUsers);
}
/** Upgrade GTM Users Plan **/
/** Assign Notification Email (as per setting flag) **/
if (! array_key_exists('allow_notification_email', $data['user'])) {
$data['user']['allow_notification_email'] = (! is_null($notificationByEmailFlag) && ($notificationByEmailFlag == 1)); // INFO: If the Flag is Set then Notification Emails will be Sent.
}
/** Assign Notification Email (as per setting flag) **/
/** Create User **/
$modelResource = $this->userModel->create($data['user']);
/** Create User **/
if ($modelResource->exists) {
/** [START] Corporate Level User Creation **/
if (in_array(session('org.sub_domain'), config('clients.jisha.subdomains')) && (array_key_exists('corporate', $data['user']) && $data['user']['corporate'] == true)) {
/** Assigning Manager Role **/
$data['user']['role_id'] = config('constants.roles.manager');
$modelResource->role_id = config('constants.roles.manager');
$modelResource->save();
/** Assigning Manager Role **/
/** Fetching/Creating Parent Division (if not exists) **/
$corporateDivisionResource = $this->divisionRepository->findOrCreate($organizationID, config('clients.jisha.parent_division_title'));
/** Fetching/Creating Parent Division (if not exists) **/
/** Creating Sub-Division and User Mapping **/
$divisionData['division']['parent_id'] = $corporateDivisionResource->id;
$divisionData['division']['title'] = $data['user']['company_name'];
$divisionData['division']['role_id'] = $data['user']['role_id'];
$divisionData['division']['user_id'] = $modelResource->id;
$this->divisionRepository->create($divisionData);
/** Creating Sub-Division and User Mapping **/
/** [START] Process Corporate Subscription **/
event(new HandleCorporateSubscription($data['user']['corporate_plan_id'], $modelResource->id));
/** [END] Process Corporate Subscription **/
}
/** Create Expert (if Role is Admin/Manager) **/
if (in_array($data['user']['role_id'], [config('constants.roles.admin'), config('constants.roles.manager')])) {
/** Get B2B Expert's Commission **/
$expertCommissionID = $this->commissionModel->where('rate', 0.95)->value('id');
/** Get B2B Expert's Commission **/
$expertStatus = 1;
if ($isDistributedMarketplace && $data['user']['role_id'] == config('constants.roles.manager')) { // INFO: Distributed Marketplace Client and Manager
$expertStatus = array_key_exists('registration_status', $data['user']) ? $data['user']['registration_status'] : config('constants.user.status.screening'); // CRITICAL: For Distributed Marketplace Block Expert from Editing Anything (-2: Screening = Limited Access, -1: Approval 1 = Limited Access & 1: Approval 2 = Full Access)
}
/** Set Stripe Connect Account ID **/
$stripeAccountID = null;
if ($isMarketplace && ! $isDistributedMarketplace) { // INFO: Marketplace Admin and Manager (Non Distributed Marketplace)
$stripeAccountKey = $this->settingRepository->findByKey('stripe_connect_account_id', $organizationID);
$stripeAccountID = ($stripeAccountKey ?? null);
}
/** Set Stripe Connect Account ID **/
$data['expert'] = [
'profile_type' => config('constants.user.profile_type.individual'),
'company_name' => $modelResource->company_name,
'telephone' => $modelResource->mobile,
'portal_id' => config('constants.portal.b2b'),
'organization_id' => $organizationID,
'stripe_account_id' => $stripeAccountID,
'course_commission_id' => $expertCommissionID,
'seminar_commission_id' => $expertCommissionID,
'ctl_name' => $modelResource->full_name,
'ctl_address_1' => $modelResource->address_1,
'ctl_address_2' => $modelResource->address_2,
'website_url' => $modelResource->website,
'is_active' => $expertStatus,
];
$modelResource->is_expert = 1;
$modelResource->expert()->create($data['expert']);
/** [START] Notification to Admin and Partner on Approval Process **/
if ($isDistributedMarketplace && $data['user']['role_id'] == config('constants.roles.manager')) {
/** Fetching/Creating Parent Division (if not exists) **/
$isGoGetterz = in_array(session('org.sub_domain'), config('clients.gogetterz.subdomains'));
if ($isGoGetterz) {
$parentDivisionTitle = config('clients.gogetterz.parent_division_title');
} else {
$parentDivisionTitle = 'Top';
}
$parentDivisionResource = $this->divisionRepository->findOrCreate($organizationID, $parentDivisionTitle);
/** Fetching/Creating Parent Division (if not exists) **/
/** Creating Sub-Division and User Mapping **/
$divisionData['division']['parent_id'] = $parentDivisionResource->id;
$divisionData['division']['title'] = $modelResource->username;
$divisionData['division']['role_id'] = $data['user']['role_id'];
$divisionData['division']['user_id'] = $modelResource->id;
$this->divisionRepository->create($divisionData);
/** Creating Sub-Division and User Mapping **/
/** [START] Send Screening Notification to Admins **/
if ($expertStatus == config('constants.user.status.screening')) {
/** Alert Variables **/
$alertData['data_set'] = [
config('constants.notify_admins.category.user') => $modelResource->id, // INFO: User : User ID
];
$alertData['organization_id'] = $organizationID;
$alertData['origin'] = config('constants.notify_admins.origin.distributed_marketplace_manager_register');
/** Alert Variables **/
/** Notify Admins **/
event(new NotifyOrganizationAdmins($alertData));
/** Notify Admins **/
}
/** [END] Send Screening Notification to Admins **/
/** Send Email Notification to Partner **/
if (in_array($expertStatus, [config('constants.user.status.approval_1'), config('constants.user.status.approval_2')])) { // INFO: -1: Approval - 1, 1: Approval - 2
$this->handleApprovalState($expertStatus, $modelResource);
}
/** Send Email Notification to Partner **/
/** Replicate Company Details **/
event(new ReplicateCompanyDetails($modelResource->id));
/** Replicate Company Details **/
}
/** [END] Notification to Admin and Partner on Approval Process **/
}
/** Create Expert (if Role is Admin/Manager) **/
/** Create Stripe Customer **/
$stripeResponse = app()->make(StripeCustomer::class)->createFromEmail($data['user']['email']);
$modelResource->stripe_customer_id = $stripeResponse['data']['id'];
$modelResource->save();
/** Create Stripe Customer **/
/** [Optional] Mapping Handling **/
$divisionExists = (array_key_exists('divisions', $data['user']) && (! empty($data['user']['divisions'])));
/** [Optional] Mapping Handling **/
/** Assign Division **/
if ($divisionExists) {
foreach ($data['user']['divisions'] as $divisionID) {
$data['user-division'] = [
'department_id' => $divisionID,
'organization_id' => $data['user']['organization_id'],
];
$modelResource->divisions()->updateOrCreate($data['user-division'], $data['user-division']);
}
}
/** Assign Division **/
/** Assign Permission Matrix **/
$permissionStatus = $this->updatePermission($modelResource);
if ($permissionStatus == false) {
return config('errors.permission.failed');
}
/** Assign Permission Matrix **/
/** Upgrade User Plan (learner capacity exceeded) **/
if (! is_null($newUserPlan)) {
$activationStatus = $this->upgradeUserPlan($organizationID, $newUserPlan);
if (! $activationStatus) {
return config('errors.subscription.failed');
}
}
/** Upgrade User Plan (learner capacity exceeded) **/
/** Update Storage Ledger **/
if ($storageUsed > 0) {
$data['storage-ledger'] = [
'organization_id' => $organizationID,
'learner_id' => auth()->check() ? auth()->id() : $modelResource->id,
'notes' => 'Added Profile Image.',
'debit' => $storageUsed,
];
$modelResource->storageLogs()->updateOrCreate([
'mod_ref_type' => 'b2b-learner',
'mod_ref_id' => $modelResource->id,
], $data['storage-ledger']);
}
/** Update Storage Ledger **/
/** Initiate Image Processor **/
if ($allowImageProcessing) {
event(new ImageProcessor($modelResource->id, 'b2b_learner_profile_image'));
}
/** Initiate Image Processor **/
/** Account Subscription (only for marketplace member user) **/
$organizationType = $modelResource->organization()->value('type');
$accountSubscriptionStatus = $this->accountSubscriptionService->isActive($organizationType);
if ($accountSubscriptionStatus &&
(in_array($modelResource->role_id, [config('constants.roles.member'), config('constants.roles.manager')]) ||
(in_array(session('org.sub_domain'), config('clients.jisha.subdomains')) && (array_key_exists('corporate', $data['user']) && $data['user']['corporate'] == true))
)
) { // INFO: Account Subscription is Active! - Current User is a Member/Manager OR Corporate Sign-Up
/** Mark Current Member as Super User **/
$modelResource->is_superuser = 1; // Assign Super User to only Member Role
$modelResource->save();
/** Mark Current Member as Super User **/
if (auth()->check() && (in_array(session('org.sub_domain'), config('clients.jisha.subdomains'))) && $divisionExists) {
/** [START] Process Corporate Subscription **/
if ($modelResource->role_id == config('constants.roles.member')) { // INFO: Added User is Member - Assign to Free Access Pool
$this->userSubscriptionRepository->assignFreeAccess($modelResource->id);
} elseif ($modelResource->role_id == config('constants.roles.manager')) { // INFO: Added User is Manager - More than One-Manager under Division then Assign to Free Access Pool
foreach ($data['user']['divisions'] as $divisionID) { // INFO: Loop Through Divisions
$totalManagers = $this->divisionUserModel->leftJoin('b2b_learners', 'b2b_department_learners.learner_id', '=', 'b2b_learners.id')
->where('b2b_learners.role_id', config('constants.roles.manager'))
->where('department_id', $divisionID)
->where('learner_id', '<>', $modelResource->id)
->whereNull('b2b_department_learners.deleted_at')
->whereNull('b2b_learners.deleted_at')
->count();
if ($totalManagers > 0) { // INFO: One-Manager Already Exists under this Division
$this->userSubscriptionRepository->assignFreeAccess($modelResource->id);
break; // INFO: Skip after Manager is Assigned in Free Access Pool
}
}
}
/** [END] Process Corporate Subscription **/
}
}
/** Account Subscription (only for marketplace member user) **/
/** Send Invite Email **/
if ($data['user']['send_email']) {
$skipResetPassword = array_key_exists('skip_reset_password', $data['user']);
$withDelay = (array_key_exists('invite_email_with_delay', $data['user']) ? $data['user']['invite_email_with_delay'] : true);
$this->sendInviteEmail($modelResource, $setInviteFlag, $skipResetPassword, $withDelay);
}
/** Send Invite Email **/
/** [START] Attach Custom Fields to User **/
if (array_key_exists('custom_fields', $data['user']) && count($data['user']['custom_fields'])) {
$this->attachCustomFields($modelResource->id, $data['user']['custom_fields']);
}
/** [END] Attach Custom Fields to User **/
/** [START] Client #250 > 102 Subdomains Related Expiration Logic **/
if (($organizationID >= 299 && $organizationID <= 400) && ($modelResource->role_id != config('constants.roles.admin'))) {
$expirationDate = date('Y-m-t 23:59:59', strtotime('+1 month', time())); // Last Day of Next Month
app()->make(UserExpirationLog::class)->create([
'organization_id' => $organizationID,
'learner_id' => $modelResource->id,
'expiration_at' => $expirationDate,
]);
}
/** [END] Client #250 > 102 Subdomains Related Expiration Logic **/
/** [START] Entity Allocation by Division **/
$newDivisions = $modelResource->divisions()->pluck('department_id')->toArray();
event(new EntityDivisionAllocation($modelResource->id, [], $newDivisions));
/** [END] Entity Allocation by Division **/
/** [START] Assign Manager's User Limit **/
if ($modelResource->role_id == config('constants.roles.manager') && array_key_exists('user_limit', $data['user']) && $data['user']['user_limit'] > 0) { // INFO: Role Switched from Manager to non-Manager (i.e. Admin/Member)
$settingData = [
'type' => "manager_{$modelResource->id}_user_limit",
'value' => $data['user']['user_limit'],
];
$this->settingRepository->upsert($organizationID, $settingData);
}
/** [END] Assign Manager's User Limit **/
}
return $modelResource;
}
Shivamyadav wrote a reply+100 XP
1mo ago
You can have a look at this a shorter function somewhere 400 of lines.
public function create($data) {
/** Default Variables **/
$allowImageProcessing = false;
$organizationID = session()->exists('org.id') ? session('org.id') : (auth()->check() ? auth()->user()->organization_id : null);
$organizationUsagePlanID = auth()->check() ? auth()->user()->organization()->value('learner_plan_id') : session('org.learner_plan_id');
$distributedMarketplaceFlag = $this->settingRepository->findByKey('distributed_marketplace', $organizationID);
$isDistributedMarketplace = (! is_null($distributedMarketplaceFlag) && $distributedMarketplaceFlag == 1);
$isExceptionClient = in_array(session('org.sub_domain'), config('constants.csv_upload.exception_clients'));
$isMarketplace = $this->organizationRepository->isMarketplace($organizationID);
$isWhiteLabel = $this->organizationRepository->isWhiteLabel($organizationID);
$newUserPlan = null;
$setEmpIDAsPassword = $this->settingRepository->findByKey('set_employee_id_as_password', $organizationID);
$notificationByEmailFlag = $this->settingRepository->findByKey('all_notifications_to_email', $organizationID);
$setInviteFlag = false; // INFO: Allows to Send the Invite Email Twice
$storageUsed = 0;
/** Default Variables **/
/** Additional Metadata **/
$data['user']['organization_id'] = $organizationID;
if ($isExceptionClient || in_array(session('org.sub_domain'), config('clients.smasterkoushin.subdomains'))) { // INFO: User Activation (for Limited Clients)
$data['user']['is_active'] = 1; // Exception: Activate without Verification
} elseif (! array_key_exists('is_active', $data['user'])) {
$data['user']['is_active'] = 0; // INFO: Assign Inactive Status and User will Activate Post Password Reset
}
if (! is_null($setEmpIDAsPassword) && ($setEmpIDAsPassword == 1) && array_key_exists('emp_id', $data['user']) && strlen($data['user']['emp_id']) > 0) { // INFO: Flag to Force Assign Employee ID as Password
$data['user']['skip_reset_password'] = 1; // INFO: Skips Password Reset Step from Invite Email SSO Link
$data['user']['password'] = Hash::make($data['user']['emp_id']); // INFO: Override Password (if exists)
}
/** Additional Metadata **/
/** Upload Image (if exists) **/
if (array_key_exists('profile_image', $data['user'])) {
$allowImageProcessing = true; // Image Processing Needed
/** Upload Profile Image **/
$data['user']['profile_image'] = $this->s3Service->uploadFile('user-profile-image', $data['user']['profile_image']);
/** Upload Profile Image **/
/** Calculate Storage Used **/
$signedURL = $this->cloudFrontService->getSignedURL($data['user']['profile_image'], config('time.5_minutes'));
$fileHeaders = get_headers($signedURL, true);
$storageUsed += $this->storageService->convertBytesToKB($fileHeaders['Content-Length']);
/** Calculate Storage Used **/
} else {
if (in_array(session('org.sub_domain'), config('clients.chronos.subdomains'))) {
$data['user']['profile_image'] = env('S3_DM_CF_URL').'/cat.jpg';
} elseif (in_array(session('org.sub_domain'), config('clients.fujikura.subdomains'))) {
$data['user']['profile_image'] = env('S3_DM_CF_URL').'/white.png';
}
}
/** Upload Image (if exists) **/
/** Upgrade Learner Plan (if learner capacity exceeds and no plan is scheduled) **/
$totalUsers = ($this->getCountByRole() + 1);
if (($organizationUsagePlanID == config('constants.plans.users.small_team')) && ($totalUsers > config('constants.plans.users.small_team_user_limit'))) {
$newUserPlan = $this->organizationRepository->getNextUserPlan();
}
/** Upgrade Learner Plan (if learner capacity exceeds and no plan is scheduled) **/
/** Upgrade GTM Users Plan **/
if ($isWhiteLabel == false) { // INFO: Only for Organization Client
$this->manageGTMSubscription($totalUsers);
}
/** Upgrade GTM Users Plan **/
/** Assign Notification Email (as per setting flag) **/
if (! array_key_exists('allow_notification_email', $data['user'])) {
$data['user']['allow_notification_email'] = (! is_null($notificationByEmailFlag) && ($notificationByEmailFlag == 1)); // INFO: If the Flag is Set then Notification Emails will be Sent.
}
/** Assign Notification Email (as per setting flag) **/
/** Create User **/
$modelResource = $this->userModel->create($data['user']);
/** Create User **/
if ($modelResource->exists) {
/** [START] Corporate Level User Creation **/
if (in_array(session('org.sub_domain'), config('clients.jisha.subdomains')) && (array_key_exists('corporate', $data['user']) && $data['user']['corporate'] == true)) {
/** Assigning Manager Role **/
$data['user']['role_id'] = config('constants.roles.manager');
$modelResource->role_id = config('constants.roles.manager');
$modelResource->save();
/** Assigning Manager Role **/
/** Fetching/Creating Parent Division (if not exists) **/
$corporateDivisionResource = $this->divisionRepository->findOrCreate($organizationID, config('clients.jisha.parent_division_title'));
/** Fetching/Creating Parent Division (if not exists) **/
/** Creating Sub-Division and User Mapping **/
$divisionData['division']['parent_id'] = $corporateDivisionResource->id;
$divisionData['division']['title'] = $data['user']['company_name'];
$divisionData['division']['role_id'] = $data['user']['role_id'];
$divisionData['division']['user_id'] = $modelResource->id;
$this->divisionRepository->create($divisionData);
/** Creating Sub-Division and User Mapping **/
/** [START] Process Corporate Subscription **/
event(new HandleCorporateSubscription($data['user']['corporate_plan_id'], $modelResource->id));
/** [END] Process Corporate Subscription **/
}
/** Create Expert (if Role is Admin/Manager) **/
if (in_array($data['user']['role_id'], [config('constants.roles.admin'), config('constants.roles.manager')])) {
/** Get B2B Expert's Commission **/
$expertCommissionID = $this->commissionModel->where('rate', 0.95)->value('id');
/** Get B2B Expert's Commission **/
$expertStatus = 1;
if ($isDistributedMarketplace && $data['user']['role_id'] == config('constants.roles.manager')) { // INFO: Distributed Marketplace Client and Manager
$expertStatus = array_key_exists('registration_status', $data['user']) ? $data['user']['registration_status'] : config('constants.user.status.screening'); // CRITICAL: For Distributed Marketplace Block Expert from Editing Anything (-2: Screening = Limited Access, -1: Approval 1 = Limited Access & 1: Approval 2 = Full Access)
}
/** Set Stripe Connect Account ID **/
$stripeAccountID = null;
if ($isMarketplace && ! $isDistributedMarketplace) { // INFO: Marketplace Admin and Manager (Non Distributed Marketplace)
$stripeAccountKey = $this->settingRepository->findByKey('stripe_connect_account_id', $organizationID);
$stripeAccountID = ($stripeAccountKey ?? null);
}
/** Set Stripe Connect Account ID **/
$data['expert'] = [
'profile_type' => config('constants.user.profile_type.individual'),
'company_name' => $modelResource->company_name,
'telephone' => $modelResource->mobile,
'portal_id' => config('constants.portal.b2b'),
'organization_id' => $organizationID,
'stripe_account_id' => $stripeAccountID,
'course_commission_id' => $expertCommissionID,
'seminar_commission_id' => $expertCommissionID,
'ctl_name' => $modelResource->full_name,
'ctl_address_1' => $modelResource->address_1,
'ctl_address_2' => $modelResource->address_2,
'website_url' => $modelResource->website,
'is_active' => $expertStatus,
];
$modelResource->is_expert = 1;
$modelResource->expert()->create($data['expert']);
/** [START] Notification to Admin and Partner on Approval Process **/
if ($isDistributedMarketplace && $data['user']['role_id'] == config('constants.roles.manager')) {
/** Fetching/Creating Parent Division (if not exists) **/
$isGoGetterz = in_array(session('org.sub_domain'), config('clients.gogetterz.subdomains'));
if ($isGoGetterz) {
$parentDivisionTitle = config('clients.gogetterz.parent_division_title');
} else {
$parentDivisionTitle = 'Top';
}
$parentDivisionResource = $this->divisionRepository->findOrCreate($organizationID, $parentDivisionTitle);
/** Fetching/Creating Parent Division (if not exists) **/
/** Creating Sub-Division and User Mapping **/
$divisionData['division']['parent_id'] = $parentDivisionResource->id;
$divisionData['division']['title'] = $modelResource->username;
$divisionData['division']['role_id'] = $data['user']['role_id'];
$divisionData['division']['user_id'] = $modelResource->id;
$this->divisionRepository->create($divisionData);
/** Creating Sub-Division and User Mapping **/
/** [START] Send Screening Notification to Admins **/
if ($expertStatus == config('constants.user.status.screening')) {
/** Alert Variables **/
$alertData['data_set'] = [
config('constants.notify_admins.category.user') => $modelResource->id, // INFO: User : User ID
];
$alertData['organization_id'] = $organizationID;
$alertData['origin'] = config('constants.notify_admins.origin.distributed_marketplace_manager_register');
/** Alert Variables **/
/** Notify Admins **/
event(new NotifyOrganizationAdmins($alertData));
/** Notify Admins **/
}
/** [END] Send Screening Notification to Admins **/
/** Send Email Notification to Partner **/
if (in_array($expertStatus, [config('constants.user.status.approval_1'), config('constants.user.status.approval_2')])) { // INFO: -1: Approval - 1, 1: Approval - 2
$this->handleApprovalState($expertStatus, $modelResource);
}
/** Send Email Notification to Partner **/
/** Replicate Company Details **/
event(new ReplicateCompanyDetails($modelResource->id));
/** Replicate Company Details **/
}
/** [END] Notification to Admin and Partner on Approval Process **/
}
/** Create Expert (if Role is Admin/Manager) **/
/** Create Stripe Customer **/
$stripeResponse = app()->make(StripeCustomer::class)->createFromEmail($data['user']['email']);
$modelResource->stripe_customer_id = $stripeResponse['data']['id'];
$modelResource->save();
/** Create Stripe Customer **/
/** [Optional] Mapping Handling **/
$divisionExists = (array_key_exists('divisions', $data['user']) && (! empty($data['user']['divisions'])));
/** [Optional] Mapping Handling **/
/** Assign Division **/
if ($divisionExists) {
foreach ($data['user']['divisions'] as $divisionID) {
$data['user-division'] = [
'department_id' => $divisionID,
'organization_id' => $data['user']['organization_id'],
];
$modelResource->divisions()->updateOrCreate($data['user-division'], $data['user-division']);
}
}
/** Assign Division **/
/** Assign Permission Matrix **/
$permissionStatus = $this->updatePermission($modelResource);
if ($permissionStatus == false) {
return config('errors.permission.failed');
}
/** Assign Permission Matrix **/
/** Upgrade User Plan (learner capacity exceeded) **/
if (! is_null($newUserPlan)) {
$activationStatus = $this->upgradeUserPlan($organizationID, $newUserPlan);
if (! $activationStatus) {
return config('errors.subscription.failed');
}
}
/** Upgrade User Plan (learner capacity exceeded) **/
/** Update Storage Ledger **/
if ($storageUsed > 0) {
$data['storage-ledger'] = [
'organization_id' => $organizationID,
'learner_id' => auth()->check() ? auth()->id() : $modelResource->id,
'notes' => 'Added Profile Image.',
'debit' => $storageUsed,
];
$modelResource->storageLogs()->updateOrCreate([
'mod_ref_type' => 'b2b-learner',
'mod_ref_id' => $modelResource->id,
], $data['storage-ledger']);
}
/** Update Storage Ledger **/
/** Initiate Image Processor **/
if ($allowImageProcessing) {
event(new ImageProcessor($modelResource->id, 'b2b_learner_profile_image'));
}
/** Initiate Image Processor **/
/** Account Subscription (only for marketplace member user) **/
$organizationType = $modelResource->organization()->value('type');
$accountSubscriptionStatus = $this->accountSubscriptionService->isActive($organizationType);
if ($accountSubscriptionStatus &&
(in_array($modelResource->role_id, [config('constants.roles.member'), config('constants.roles.manager')]) ||
(in_array(session('org.sub_domain'), config('clients.jisha.subdomains')) && (array_key_exists('corporate', $data['user']) && $data['user']['corporate'] == true))
)
) { // INFO: Account Subscription is Active! - Current User is a Member/Manager OR Corporate Sign-Up
/** Mark Current Member as Super User **/
$modelResource->is_superuser = 1; // Assign Super User to only Member Role
$modelResource->save();
/** Mark Current Member as Super User **/
if (auth()->check() && (in_array(session('org.sub_domain'), config('clients.jisha.subdomains'))) && $divisionExists) {
/** [START] Process Corporate Subscription **/
if ($modelResource->role_id == config('constants.roles.member')) { // INFO: Added User is Member - Assign to Free Access Pool
$this->userSubscriptionRepository->assignFreeAccess($modelResource->id);
} elseif ($modelResource->role_id == config('constants.roles.manager')) { // INFO: Added User is Manager - More than One-Manager under Division then Assign to Free Access Pool
foreach ($data['user']['divisions'] as $divisionID) { // INFO: Loop Through Divisions
$totalManagers = $this->divisionUserModel->leftJoin('b2b_learners', 'b2b_department_learners.learner_id', '=', 'b2b_learners.id')
->where('b2b_learners.role_id', config('constants.roles.manager'))
->where('department_id', $divisionID)
->where('learner_id', '<>', $modelResource->id)
->whereNull('b2b_department_learners.deleted_at')
->whereNull('b2b_learners.deleted_at')
->count();
if ($totalManagers > 0) { // INFO: One-Manager Already Exists under this Division
$this->userSubscriptionRepository->assignFreeAccess($modelResource->id);
break; // INFO: Skip after Manager is Assigned in Free Access Pool
}
}
}
/** [END] Process Corporate Subscription **/
}
}
/** Account Subscription (only for marketplace member user) **/
/** Send Invite Email **/
if ($data['user']['send_email']) {
$skipResetPassword = array_key_exists('skip_reset_password', $data['user']);
$withDelay = (array_key_exists('invite_email_with_delay', $data['user']) ? $data['user']['invite_email_with_delay'] : true);
$this->sendInviteEmail($modelResource, $setInviteFlag, $skipResetPassword, $withDelay);
}
/** Send Invite Email **/
/** [START] Attach Custom Fields to User **/
if (array_key_exists('custom_fields', $data['user']) && count($data['user']['custom_fields'])) {
$this->attachCustomFields($modelResource->id, $data['user']['custom_fields']);
}
/** [END] Attach Custom Fields to User **/
/** [START] Client #250 > 102 Subdomains Related Expiration Logic **/
if (($organizationID >= 299 && $organizationID <= 400) && ($modelResource->role_id != config('constants.roles.admin'))) {
$expirationDate = date('Y-m-t 23:59:59', strtotime('+1 month', time())); // Last Day of Next Month
app()->make(UserExpirationLog::class)->create([
'organization_id' => $organizationID,
'learner_id' => $modelResource->id,
'expiration_at' => $expirationDate,
]);
}
/** [END] Client #250 > 102 Subdomains Related Expiration Logic **/
/** [START] Entity Allocation by Division **/
$newDivisions = $modelResource->divisions()->pluck('department_id')->toArray();
event(new EntityDivisionAllocation($modelResource->id, [], $newDivisions));
/** [END] Entity Allocation by Division **/
/** [START] Assign Manager's User Limit **/
if ($modelResource->role_id == config('constants.roles.manager') && array_key_exists('user_limit', $data['user']) && $data['user']['user_limit'] > 0) { // INFO: Role Switched from Manager to non-Manager (i.e. Admin/Member)
$settingData = [
'type' => "manager_{$modelResource->id}_user_limit",
'value' => $data['user']['user_limit'],
];
$this->settingRepository->upsert($organizationID, $settingData);
}
/** [END] Assign Manager's User Limit **/
}
return $modelResource;
}
Shivamyadav liked a comment+100 XP
1mo ago
@shivamyadav I’ve worked on probably hundreds of Laravel projects over more than 10 years now, and at companies of all sizes (from SMEs to Fortune 500s). The projects that fell apart and became unmaintainable messes were the ones where developers decided to be “clever” and create their own folder structure, or do “modules”, “domains”, or whatever.
Seriously, stick to Laravel’s default directory structure:
- Put interfaces in an app/Contracts directory, which follows the framework’s convention.
- Use managers for things that can have multiple providers (i.e. payment gateways). Think how Laravel uses managers for components you interact with (cache, queue, etc) where the code doesn’t change if you swap providers (i.e. from Redis to database). Try to strive for the same in your application where you’re relying on interfaces and managers rather than a specific provider.
- If you have different “areas” in your application, then you can sub-namespace controllers (i.e.
App\Http\Controllers\Admin). - For interactions with third-party services, I create a dedicated namespace in an app/Services directory (i.e.
App\Services\Mux). I then treat this folder as a package, so it may have its own Artisan commands, Eloquent models, controllers, routes, etc with a service provider to register those resources.
It Just Works™ and scales, and your Artisan make:* commands still work without having to install horrible “glue” packages. And with the rise in AI-assisted development, sticking to the default directory structure will only help that given that’s what LLMs will be trained on.
Shivamyadav wrote a reply+100 XP
1mo ago
Before React, you should be able to:
- Use map() without thinking
- Update objects with spread operator
- Fetch API data using async/await
- Understand closures at a basic level
- Write clean arrow functions
Here is the link some starting lessons are free - https://laracasts.com/series/react-from-scratch
Shivamyadav started a new conversation+100 XP
1mo ago
In Laravel projects, what folder structure patterns have you found most effective for keeping interfaces (contracts) and their implementations maintainable as the codebase grows?
Specifically, how do you balance:
- feature-based organization vs layer-based organization
- discoverability vs separation of concerns
Any examples from production applications would be helpful.
Shivamyadav wrote a reply+100 XP
1mo ago
Check form the network tab from where it is coming?
Shivamyadav wrote a reply+100 XP
1mo ago
Somewhere in your code you have injected that particular font..
Browser does not load the font by default.
Look for this or only just search the font name on your project. I'm sure it's there .
@import url('https://fonts.googleapis.com/css2?family=Figtree&display=swap');
Shivamyadav wrote a reply+100 XP
2mos ago
I think you missed this part to adding your form element
Your form is missing enctype="multipart/form-data"
<form method="POST" action="{{ route('store') }}" enctype="multipart/form-data">
Shivamyadav wrote a reply+100 XP
2mos ago
Sorry, I was kind a little bit in hurry to finish it somehow at that time. So, I missed it to implement it.
According to your advice. I will implement it on Saturday, as today I am working on some other cases.
Thanks, for your valuable time and advice.
Shivamyadav liked a comment+100 XP
2mos ago
@shivamyadav I see you didn’t take my advice (that I’m sure I mentioned a couple of times when you’ve posted about this Google Drive-related project) of moving your Google Client creation logic to a service provider, so that classes then using that client (controllers, etc) just need to type-hint it and it will be automatically built and resolved:
public function register(): void
{
$this->app->singleton(Client::class, function (Application $app) {
return new Client([
'client_id' => $app['config']['services.google.client_id'],
'client_secret' => $app['config']['services.google.client_secret'],
'scopes' => 'https://www.googleapis.com/auth/drive',
'redirect_uri' => $app['config']['services.google.redirect_uri'],
'prompt' => 'consent',
'access_type' => 'offline',
]);
});
}
For refreshing access tokens, I have a scheduled task that fires daily to refresh any soon-to-expire access tokens. But again, I’m sure I’ve mentioned this before:
Schedule::command('access-token:refresh')->daily();
If you find you’re unable to refresh an access token (because it’s been revoked by the owner), then you can delete it, and send a notification to the owner telling them they need to re-connect their Google account.
Shivamyadav wrote a reply+100 XP
2mos ago
Thanks for the reply ☺️.
Actually this was the issue it was silently truncating the data and it get's only stored a allowed bytes of charcters to the table.
Yeah, I have already fixed and moved the constructor logic out and created a method authenticate and using it simply
Shivamyadav liked a comment+100 XP
2mos ago
Your logic is actually fine. The problem is almost certainly your database migration.
Google access tokens are frequently longer than 255 characters. If you used $table->string('access_token') in your migration, Laravel defaults to a VARCHAR(255) and is silently truncating your token when saving it to the database.
Here is exactly why your test behaved that way:
When you omit the created key, the Google client assumes the token is expired and forces a refresh. It then uses the full, fresh token directly from memory, which is why it works. However, when you include created, the client checks the timestamp, sees it hasn't been an hour yet, and sends the truncated (corrupted) token from the database to Google. Google rejects it, triggering the 401 Unauthorized.
Change your columns to text in your database migration:
$table->text('access_token');
$table->text('refresh_token');
Clear out your oauth_credentials table and re-authenticate to store a fresh, full-length token.
Avoid putting heavy external API calls inside a __construct(). If Google's API goes down, times out, or rate-limits you, your entire class fails to instantiate and it can crash Laravel's service container resolution. Move the token initialization to a dedicated connect() or boot() method that you call when you actually need to interact with the Drive API.
Shivamyadav wrote a reply+100 XP
2mos ago
Not started just asked S better version of my question to get better help from people 🙏😔
Shivamyadav wrote a reply+100 XP
2mos ago
Yeah, from gpt.
I told him that this is what I was thinking is there any security vulnerabilities that can occurs and write a thread post to get the feedback from the professional and senior to the it field.
Shivamyadav started a new conversation+100 XP
2mos ago
I’m building a lightweight Laravel-based attendance system where users can check in/out only when they are physically present within a strict 10–20 meter radius of the workplace using live GPS validation.
- ⚙️ Core Features Implemented 2.📍 Real-time geofencing (strict 10–20m radius enforcement)
- 🎯 GPS accuracy filtering (reject low-accuracy readings)
- ⏱️ Live check-in only (no delayed or backdated submissions)
- 🚫 Mock location detection (Android-based checks)
- 📱 Device tracking (basic fingerprinting for consistency)
- 🤳 Selfie Verification Approach (No AI)
To keep the system lightweight and privacy-friendly, I’m intentionally avoiding AI/ML solutions.
Current approach:
- Capture a selfie during check-in
- Compare it with a previously stored reference image
- Use pixel-level comparison techniques, such as:
- Image difference
- Hashing (e.g., perceptual hash)
- Similarity scoring
❓ Feedback I’m Looking For
I’d really appreciate insights from experienced developers on the following:
- Reliability How reliable is pixel-based image comparison in real-world usage?
- Real-world Variations How should I handle: 1. lighting differences 2. face angle changes 3. different camera quality
- Alternatives (Without AI) Are there any better non-AI approaches for basic face verification?
- Feasibility Is this approach fundamentally flawed for identity verification?
- Improvements Would capturing multiple selfies (2–3 per check-in) improve accuracy or just add noise? 🎯 Goal
The aim is to build a system that is:
- ✅ Simple to integrate
- ⚡ Lightweight (no heavy dependencies)
- 🔒 Privacy-friendly (no AI processing)
- 🛡️ Reasonably resistant to misuse
🙏 Closing
Any suggestions, critiques, or alternative approaches would be greatly appreciated. Looking forward to learning from your experience!
Shivamyadav wrote a reply+100 XP
2mos ago
Try this as the route ziggy package does not provide the reactive data on page navigation. You need to use the vue's reactivity with the route of the ziggy like this :
<script setup>
import { useSlots, h, computed } from 'vue'
import { usePage } from '@inertiajs/vue3'
const slots = useSlots()
const page = usePage()
// reactive current URL
const currentUrl = computed(() => page.url)
const renderFunc = () => {
const children = slots.collapseChild?.() || []
return children.map(child => {
return h(
"li",
{
class: {
active: child.props?.href === currentUrl.value
}
},
child
)
})
}
</script>
<template>
<ul>
<component :is="renderFunc" />
</ul>
</template>
Shivamyadav started a new conversation+100 XP
3mos ago
I have a GoogleDriveService to mange the work for the drive related.
Earlier it was setup by me to work like get the new refresh access token for each request and it was working fine.
Yesterday my senior suggested me to use this way: 1. To add the logic only get the new access token if it is expired. 2. I have added this logic but now I am getting 401 (Unauthorized) error.
My constructor code
public function __construct()
{
$client = new \Google\Client();
$client->setClientId(config('google.drive.client_id'));
$client->setClientSecret(config('google.drive.client_secret'));
$client->setRedirectUri(config('google.drive.redirect_uri'));
$client->addScope(\Google\Service\Drive::DRIVE);
$client->setAccessType('offline');
$client->setPrompt('consent');
// INFO: Get the oauth credentials as an array
$accessToken = OauthCredentials::first()->toArray();
// dd($accessToken);
Log::info('Db token', $accessToken);
if (!isset($accessToken['access_token'])) {
throw new \Exception('Failed to fetch access token. Check your refresh token!');
}
// INFO: Set the access token with all the details
$client->setAccessToken([
'access_token' => $accessToken['access_token'],
'refresh_token' => $accessToken['refresh_token'],
'token_type' => $accessToken['token_type'],
'expires_in' => (int) $accessToken['expires_in'],
'created' => (int) $accessToken['token_created_at'], // Unix timestamp
]);
// dump($client->isAccessTokenExpired());
/** Chech if the token is expired or not and refresh if needed */
if ($client->isAccessTokenExpired()) {
Log::info("Access token expired. Refreshing...");
// INFO: Use the db refresh token to fetch the new token
$newToken = $client->fetchAccessTokenWithRefreshToken($accessToken['refresh_token']);
// INFO: Set the refresh token if the new token doesn't have it
if (!isset($newToken['refresh_token'])) {
$newToken['refresh_token'] = $accessToken['refresh_token'];
}
/** Store the credentials and token related meta data to the db */
OauthCredentials::updateOrCreate(
['id' => 1],
[
'refresh_token' => $newToken['refresh_token'],
'access_token' => $newToken['access_token'],
'token_type' => $newToken['token_type'] ?? 'Bearer',
'expires_in' => $newToken['expires_in'],
'token_created_at' => $newToken['created'],
]
);
/** Store the credentials and token related meta data to the db */
Log::info('New token', $client->getAccessToken());
Log::info("New access token generated : ", $newToken);
// Set new token
$client->setAccessToken($newToken);
Log::info("New access token generated");
}
$this->drive = new \Google\Service\Drive($client);
$this->parentFolderId = config('google.drive.parent_folder_id');
}
As soon i removed this below line form the top (first) set access token
'created' => (int) $accessToken['token_created_at'], // Unix timestamp
It works but now each time it fetches the new access token for it.
Main error i am getting
"error": {
"code": 401,
"message": "Request had invalid authentication credentials. Expected OAuth 2 access token, login cookie or other valid authentication credential. See https://developers.google.com/identity/sign-in/web/devconsole-project.",
"errors": [
{
"message": "Invalid Credentials",
"domain": "global",
"reason": "authError",
"location": "Authorization",
"locationType": "header"
}
],
"status": "UNAUTHENTICATED"
}
}
Shivamyadav started a new conversation+100 XP
3mos ago
For the first time when I click to my sidebar notifications button it does two things:
- Navigate to the notifications page.
- Clears the notification badges for the new emails.
And then I tried some filter to the notifications and when I am done with the filtering and click on the notifications button again it works again for step 1 and step 2 but the issue arrives here in the form of the not letting the search and other inputs gets cleared but the data is at the initial state without applied any filter and url is also clean.
I have tried with the preserveState to be false for the below function markNewMailsRead() but did not worked.
Is there something I am missing or any solution for this issue.
My sidebar link component
<SidebarLink @click="markNewMailsRead" :href="route('notifications.index')"
:active="isActiveLink('notifications.index')">
<div class="flex items-center justify-between w-full">
<!-- Left -->
<div class="flex items-center gap-4">
<Bell class="size-5" />
<span>Notifications</span>
</div>
<!-- Badge -->
<div v-if="totalNewMails" class="relative">
<!-- Pulse ring -->
<span class="absolute inset-0 rounded-full bg-primary animate-ping"></span>
<!-- Count -->
<span class="relative flex items-center justify-center
w-6 h-6 p-1
rounded-full bg-primary
text-xs font-bold text-black">
{{ totalNewMails > 99 ? '99+' : totalNewMails }}
</span>
</div>
</div>
</SidebarLink>
// INFO: New Mail mark as read ( to just make the live new count 0)
const markNewMailsRead = () => {
router.post(route('notifications.new-mails.read'), {}, {
preserveScroll: true,
preserveState: true,
});
};
Shivamyadav wrote a reply+100 XP
3mos ago
Thanks for the info. I did not know about the env direct helper issue in the code.
I have already setup the oauth and using and it works fine now.
Shivamyadav liked a comment+100 XP
3mos ago
@shivamyadav As @ian_h says, you should not be using the env helper in your code. The Laravel docs also tell you not to do this.
Instead, you should be binding that class in the service container where the configuration values are passed to your constructor:
public function register(): void
{
$this->app->singleton(GoogleDriveService::class, function () {
return new GoogleDriveService(
clientId: $this->app['config']['services.google.drive.client_id'],
clientSecret: $this->app['config']['services.google.drive.client_secret'],
redirectUri: $this->app['config']['services.google.drive.redirect'],
);
});
}
You can then just type-hint the service class, and it will be resolved (and configured) by the service container:
class SomeController extends Controller
{
protected GoogleDriveService $googleDrive;
public function __construct(GoogleDriveService $googleDrive)
{
$this->googleDrive = $googleDrive;
}
}
As for the error, it’s telling you what the problem is: service accounts don’t have a storage quote, so you can’t use service accounts for storage-related operations like you’re trying to do. You need to use an OAuth approach so you’re authenticating as a user, who will have a storage quota. You can use Socialite to create (and refresh) Google access tokens.
Shivamyadav wrote a reply+100 XP
3mos ago
With the doc and little bit help of ai.
Shivamyadav liked a comment+100 XP
3mos ago
Also, I'd highly recommend not using env() in the code, rather Config::get() as the former will cause you issues when you cache the config in production.
Shivamyadav wrote a reply+100 XP
3mos ago
<?php
namespace App\Services;
use Google\Client;
use Google\Service\Drive;
use Google\Service\Drive\DriveFile;
class GoogleDriveService
{
protected Drive $drive;
protected string $parentFolderId;
public function __construct()
{
$client = new Client();
$client->setClientId(env('GOOGLE_DRIVE_CLIENT_ID'));
$client->setClientSecret(env('GOOGLE_DRIVE_CLIENT_SECRET'));
$client->setRedirectUri(env('GOOGLE_DRIVE_REDIRECT_URI'));
$client->addScope(Drive::DRIVE);
$client->refreshToken(env('GOOGLE_DRIVE_REFRESH_TOKEN'));
$this->drive = new Drive($client);
// Use parent folder from config
$this->parentFolderId = config('drive.parent_folder_id');
}
/**
* Upload a file into a subfolder based on the config mapping
*/
public function uploadByPosition(string $filePath, string $fileName, string $position): array
{
// Get folder name from config mapping, default to position if not mapped
$folderMap = config('drive.position_folders', []);
$folderName = $folderMap[$position] ?? $position;
$subFolderId = $this->getOrCreateSubFolder($folderName);
$fileMetadata = new DriveFile([
'name' => $fileName,
'parents' => [$subFolderId]
]);
$content = file_get_contents($filePath);
$file = $this->drive->files->create($fileMetadata, [
'data' => $content,
'mimeType' => mime_content_type($filePath),
'uploadType' => 'multipart',
'supportsAllDrives' => true,
]);
return [
'subfolder' => $folderName,
'file_id' => $file->id,
'preview_url' => $this->getFileUrl($file->id)
];
}
/**
* Get or create a subfolder under parent
*/
protected function getOrCreateSubFolder(string $folderName): string
{
$response = $this->drive->files->listFiles([
'q' => "name='{$folderName}' and mimeType='application/vnd.google-apps.folder' and '{$this->parentFolderId}' in parents and trashed=false",
'spaces' => 'drive',
'fields' => 'files(id, name)',
'supportsAllDrives' => true,
]);
if (count($response->files) > 0) {
return $response->files[0]->id;
}
// Folder not found → create it
$folderMetadata = new DriveFile([
'name' => $folderName,
'mimeType' => 'application/vnd.google-apps.folder',
'parents' => [$this->parentFolderId],
]);
$folder = $this->drive->files->create($folderMetadata, [
'fields' => 'id',
'supportsAllDrives' => true,
]);
return $folder->id;
}
/**
* Generate a preview URL for Google Drive
*/
public function getFileUrl(string $fileId): string
{
return "https://drive.google.com/file/d/{$fileId}/view";
}
}
Shivamyadav started a new conversation+100 XP
3mos ago
Currently I am using the service account and setup the testing route with the credentials and getting this error:
Route::get('/drive-test', function () {
$service = new \App\Services\GoogleDriveService();
$fileId = $service->upload(
storage_path('1770402265.shivam-resume (1).pdf'),
'test.pdf'
);
return $service->getFileUrl($fileId);
});
Google \ Service \ Exception (403)
{ "error": { "code": 403, "message": "Service Accounts do not have storage quota. Leverage shared drives (https://developers.google.com/workspace/drive/api/guides/about-shareddrives), or use OAuth delegation (http://support.google.com/a/answer/7281227) instead.", "errors": [ { "message": "Service Accounts do not have storage quota. Leverage shared drives (https://developers.google.com/workspace/drive/api/guides/about-shareddrives), or use OAuth delegation (http://support.google.com/a/answer/7281227) instead.", "domain": "usageLimits", "reason": "storageQuotaExceeded" } ] } }
Shivamyadav wrote a reply+100 XP
3mos ago
After reading your reply. I thought yeah this is the way I can achieve it.
Also I can check the every pagination data listing with that source of truth and mark the checkbox for every visiting page to checked the box to provide the feedback to the user and got the some help from the AI to unselected paginations data listing could be added to the another source of truth like. excluded_records which will hold the ID's which does not required to be deleted.
Shivamyadav liked a comment+100 XP
3mos ago
Since not all the records are loaded - there's no way to know which records to select, you'd need to send a key in order to remove all, for example, selected_all=true
Shivamyadav started a new conversation+100 XP
3mos ago
I have created a notification listing with the pagination but I want to delete all the notifications or only the selected one to delete it.
- I was using the checkbox for each record and the top header select all the records form the listing and it selects all the records but only from which ever page I select the header checkbox to select all the listing.
- When I go to the 2nd page it's record listings was not selected how to get the all the listing selected within the pagination too?
Shivamyadav wrote a reply+100 XP
4mos ago
Thanks snapey. This weekend I will try it to upgrade it with Laravel 12.