Files
backend_fiesta/services/tenantProfile.go
2026-09-08 19:21:23 +05:30

91 lines
3.4 KiB
Go

package services
import (
"errors"
"strings"
)
// What a merchant is allowed to change about their own business.
//
// An allowlist, and it has to be one. The obvious implementation — hand the
// parsed body to GORM's `Updates` — would let anyone who can reach the endpoint
// set `approved`, `status`, `partnerid`, `partneruserid`, `moduleid`,
// `configid` or `tenanttoken` on their own record: approve themselves onto the
// platform, move themselves under another partner, or reassign their billing.
// None of those belong to the merchant, and none of them are things a UI would
// ever send, which is exactly what makes the omission easy to miss.
//
// So the fields are named here, once, and the repository writes nothing it is
// not given. Anything absent from this map is untouched rather than blanked —
// a profile form that renders four fields must not erase the twenty it did not.
//
// `tenantid` is deliberately absent too: it identifies the row, it is never a
// value to be written.
var editableTenantFields = map[string]bool{
// What a shopper sees.
"tenantname": true,
"tenantimage": true,
"tenantinfo": true,
// The kind of shop it is. Omitting it was a silent data loss: store setup
// marks Store type REQUIRED, sends it on save, and the merchant is shown a
// successful step while the answer is dropped here. It describes the
// business rather than its standing on the platform, so it belongs to the
// merchant in the way "approved" and "partnerid" do not.
"tenanttype": true,
// How to reach the business.
"primaryemail": true,
"primarycontact": true,
"companyname": true,
// The person who administers the shop — the Store admin.
//
// `tenants.firstname` — there is no lastname column, so this is the whole
// name. It was collected nowhere and writable nowhere, which is why every
// merchant read so far comes back with it empty and the store profile shows
// "—" for Store admin. It describes the business's own contact person, in
// the same way `companyname` does, and belongs to the merchant rather than
// to the platform.
"firstname": true,
// Where it is.
"address": true,
"suburb": true,
"city": true,
"state": true,
"postcode": true,
"latitude": true,
"longitude": true,
// Legal and trading terms.
"registrationno": true,
"licenseno": true,
"minorder": true,
"subcategoryid": true,
}
// TenantProfileUpdate reduces a request to the fields a merchant may set.
//
// Returns an error rather than an empty map when nothing survives: a write that
// changes nothing and reports success is indistinguishable from one that
// worked, and the caller would go on believing their licence number was saved.
func TenantProfileUpdate(fields map[string]any) (map[string]any, error) {
clean := make(map[string]any, len(fields))
for key, value := range fields {
lower := strings.ToLower(strings.TrimSpace(key))
if !editableTenantFields[lower] {
continue
}
// A blank string is "not supplied", not "erase it". The profile form
// sends every field it renders on every save, so honouring blanks would
// let a half-filled form wipe an address somebody typed last month.
if text, ok := value.(string); ok && strings.TrimSpace(text) == "" {
continue
}
clean[lower] = value
}
if len(clean) == 0 {
return nil, errors.New("nothing to update — no editable field was supplied")
}
return clean, nil
}