Google Drive (documents)
Every file the system keeps — passports, visas, ID, issued e-tickets and
vouchers, group documents, voucher attachments, TDS certificates, incident
documents, enquiry and visa-application files, request attachments, payment
receipts, chat attachments — is kept in the company Shared Drive
Alhuda-Shared-Drive on Google Workspace (alhudatravels.in), and nowhere
else. Nothing is kept in Supabase Storage
(ACC-074).
No file on the drive is shared with anyone
(ACC-073).
Who can open a file
| Who | How |
|---|---|
| Staff | Inside the ERP. Every screen that lists a document has an Open button; the file opens in a viewer on the page (an image, or a PDF in a frame, with Download). The drive-file function checks the viewer holds that record's view permission and hands out a link that lives ten minutes. No Google account is needed. |
| A traveller or partner | Inside the app or the portal, through drive-file, for their own documents only (TRV-011). |
| Shared Drive members | Can also open the drive itself ("Open in Google Drive" in the viewer). Membership is for administering the drive — tidying folders, restoring from the trash — not for everyday reading. |
| Anyone else | Nobody. There is no "anyone with the link" sharing. |
How files get there
Only edge functions write to the drive, with a Google Cloud service
account that is a member of the Shared Drive (Content manager). A service
account has no storage of its own, so the folder must be a Shared Drive, not a
folder in someone's My Drive. Every call carries supportsAllDrives=true.
| Function | Stores | Checks |
|---|---|---|
upload-customer-doc |
a customer's documents | customers.edit / customers.create, or the traveller's own record |
upload-partner-doc |
a partner's registration documents | the partner's own login; or staff holding partners.edit, who send the agency's id (PTR-087) |
upload-employee-doc |
an employee's documents | the person, or admin.users.edit |
drive-upload |
group, voucher, TDS, incident, visa and ticket documents; a customer's request attachments and payment receipts | the permission of that screen (ACC-074) |
lead-intake |
files sent with a website enquiry | the captcha, then 5 enquiries with files per hour per connection |
visa-intake |
files sent with a website visa application | the captcha and the form's rate limits |
chat-upload |
chat attachments | a staff login |
issue-document |
issued e-ticket sheets and hotel vouchers; payment receipts | tickets.view or hotels.view, with bookings.view; a receipt: finance.view or bookings.view, or the booking's customer or partner (FIN-045) |
A PDF or a photo (JPEG, PNG, WebP, HEIC), 10 MB at most; a website form, 20 MB in all.
Folders
Under the Shared Drive's root:
Customers/<name (id)>/ customer documents
Customers/<name (id)>/Requests/ a customer's request attachments
Partners/<agency (id)>/ partner registration documents
Employees/<name (id)>/ employee documents
Visa/<case>/ visa documents uploaded by staff
Visa/Intake/<name (request)>/ files sent with a website visa application
Tickets/<ticket>/ ticket documents
Issued/<booking (id)>/ issued e-tickets and hotel vouchers
Groups/<group code (id)>/ group documents
Finance/Vouchers/<voucher (id)>/ voucher attachments
Finance/TDS/<deduction (id)>/ Form 16A certificates
Finance/Receipts/<name (id)>/ receipts a customer uploaded with a payment
Enquiries/<name (lead)>/ files sent with a website enquiry
Incidents/<incident (id)>/ incident documents
Chat/<month>/ chat attachments
The functions make the folders the first time they need them. Every file of the
registry kinds is also recorded in the DriveFile table with its kind and
record; drive-file reads the kind from there, never from the caller.
Settings
| Where | Name | Value |
|---|---|---|
| Supabase secrets | GOOGLE_DRIVE_CLIENT_EMAIL, GOOGLE_DRIVE_PRIVATE_KEY |
From the service account's JSON key |
| Supabase secrets | GOOGLE_DRIVE_FOLDER_ID |
The Shared Drive's ID (0APyir2YXbXjdUk9PVA) |
| Supabase secrets | DRIVE_LINK_SECRET |
A long random string. Signs the ten-minute links drive-file hands out. When it is not set the service role key signs them; set it so the links can be revoked on their own (see below). |
| Supabase secrets | TURNSTILE_SECRET_KEY |
Already set for the website forms. Without it the website refuses files (a form without files still goes through). |
| Cloudflare Pages | — | Nothing. VITE_GOOGLE_CLIENT_ID and VITE_GOOGLE_DRIVE_FOLDER_ID are no longer used: delete them from the project's environment variables. The browser never talks to Google. |
drive-file and drive-upload are deployed with verify_jwt = false
(supabase/config.toml): the link a viewer opens carries no session, and both
functions check the session themselves.
Setting it up
- Shared Drive. In Drive: New → Shared drive. Add as members only the people who administer the drive.
- Workspace sharing. Admin console → Apps → Google Workspace → Drive and Docs
→ Sharing settings: sharing outside the domain On (the service account is an
outside account), warn ticked, guest accounts off, and "visible to anyone
with the link" off. Access checker: recipients only, or
alhudatravels.in. - Service account. Google Cloud → enable the Google Drive API → IAM & Admin → Service accounts → create → Keys → Add key → JSON. If key creation is blocked, set the organisation policy Disable service account key creation to Not enforced for the project. Add the service account's email to the Shared Drive as Content manager.
- Secrets. Set the three Google secrets from the JSON file and
DRIVE_LINK_SECRET(openssl rand -base64 48) — never paste a key into chat or a ticket — then deploy the functions below. - Check. On a test customer, upload a document and open it from the customer's screen as a member of staff who is not a Shared Drive member; confirm in Drive → the file → Share that it is shared with nobody.
Deploying
After a change to any of them, deploy:
for f in drive-file drive-upload upload-customer-doc upload-partner-doc upload-employee-doc \
lead-intake visa-intake chat-upload issue-document; do
supabase functions deploy "$f" --project-ref <ref>
done
signed-url was removed (it signed any bucket path); delete it from the
project: supabase functions delete signed-url --project-ref <ref>.
Rotating
- The service account key. Create a new JSON key, set the two secrets again, deploy the functions above, then delete the old key in Google Cloud.
- The link secret. Set a new
DRIVE_LINK_SECRET. Every link already handed out stops working at once; people simply open the file again.
Supabase Storage
Not used. The buckets on production (erp-storage-bucket, incident-documents,
traveller-documents) are no longer written or read. Deleting them is the
owner's decision; nothing in the system depends on them.
Not built
- A file removed from a record stays on the drive: a service account cannot delete from a Shared Drive, only move a file to the trash, and the ERP does not do even that on removal. Tidy the folders by hand.
- Removing a person from the Shared Drive when their ERP login is deactivated is done by hand.