backend requirements onthe xustomer app
This commit is contained in:
95
controllers/cxIdentifiers.go
Normal file
95
controllers/cxIdentifiers.go
Normal file
@@ -0,0 +1,95 @@
|
||||
package controllers
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"math/rand"
|
||||
"time"
|
||||
|
||||
"doormile/db"
|
||||
"doormile/utils"
|
||||
)
|
||||
|
||||
// Human-facing identifiers.
|
||||
//
|
||||
// A pickup booking is DM-######; an order is DMX########. Both columns are
|
||||
// UNIQUE, and both used to be minted from four random bytes plus a truncated
|
||||
// unix second. Random short ids collide long before the id space runs out, and
|
||||
// a collision here is not a retry — it is a failed booking at the moment the
|
||||
// customer taps Confirm. So both come off a Postgres sequence, which is the
|
||||
// only generator in this system that can promise uniqueness.
|
||||
//
|
||||
// The sequences have no MAXVALUE and no CYCLE (migrations/migrate.go): past
|
||||
// 999999 the reference simply grows a digit rather than wrapping onto an id
|
||||
// that already exists. Rows written before this change keep their old
|
||||
// DM-BK-/DM-TRK- strings; nothing anywhere parses either format, so the two
|
||||
// coexist and no backfill is needed.
|
||||
|
||||
// nextSequenceValue draws the next value from a Postgres sequence.
|
||||
func nextSequenceValue(sequence string) (int64, bool) {
|
||||
if db.DB == nil {
|
||||
return 0, false
|
||||
}
|
||||
var n int64
|
||||
if err := db.DB.Raw(fmt.Sprintf("SELECT nextval('%s')", sequence)).Scan(&n).Error; err != nil {
|
||||
utils.Warn("identifier sequence unavailable, falling back", "sequence", sequence, "error", err)
|
||||
return 0, false
|
||||
}
|
||||
return n, true
|
||||
}
|
||||
|
||||
// fallbackNumber is used only when the sequence cannot be read — a database
|
||||
// that is unreachable, or a deployment where the migration has not run yet. It
|
||||
// keeps the shape of the identifier (so the client and the console never see a
|
||||
// second format) and takes its entropy from the clock plus a random tail,
|
||||
// which makes a collision vanishingly unlikely for the short window this path
|
||||
// is ever live. It is a degradation, not a design: the sequence is the
|
||||
// guarantee.
|
||||
func fallbackNumber(digits int) int64 {
|
||||
span := int64(1)
|
||||
for i := 0; i < digits; i++ {
|
||||
span *= 10
|
||||
}
|
||||
base := time.Now().UnixNano() % span
|
||||
jitter := rand.Int63n(1000)
|
||||
n := (base + jitter) % span
|
||||
// Never return a value that would render with fewer digits than the format
|
||||
// promises — DM-000042 reads as a broken reference, not a short one.
|
||||
if n < span/10 {
|
||||
n += span / 10
|
||||
}
|
||||
return n
|
||||
}
|
||||
|
||||
// generateBookingNo mints a pickup booking reference: DM-482913.
|
||||
//
|
||||
// The sequence guarantees uniqueness; cxScrambledBooking scatters it so
|
||||
// consecutive bookings do not get adjacent references. See
|
||||
// cxIdentifierScramble.go for why the scattering is a keyed permutation rather
|
||||
// than arithmetic.
|
||||
//
|
||||
// Past 900,000 bookings the fixed-width range is exhausted and the reference
|
||||
// grows a digit instead of wrapping onto one already issued. Uniqueness is
|
||||
// never traded for appearance.
|
||||
func generateBookingNo() string {
|
||||
if n, ok := nextSequenceValue("cx_booking_reference_seq"); ok {
|
||||
if scrambled, inRange := cxScrambledBooking(n); inRange {
|
||||
return fmt.Sprintf("DM-%06d", scrambled)
|
||||
}
|
||||
return fmt.Sprintf("DM-%06d", n)
|
||||
}
|
||||
return fmt.Sprintf("DM-%06d", fallbackNumber(6))
|
||||
}
|
||||
|
||||
// generateTrackingNo mints an order's tracking number: DMX10482913. One per
|
||||
// destination, minted when the miler completes the pickup — never at booking
|
||||
// time, because until the parcels are actually collected there is no order to
|
||||
// track.
|
||||
func generateTrackingNo() string {
|
||||
if n, ok := nextSequenceValue("cx_tracking_seq"); ok {
|
||||
if scrambled, inRange := cxScrambledTracking(n); inRange {
|
||||
return fmt.Sprintf("DMX%08d", scrambled)
|
||||
}
|
||||
return fmt.Sprintf("DMX%08d", n)
|
||||
}
|
||||
return fmt.Sprintf("DMX%08d", fallbackNumber(8))
|
||||
}
|
||||
Reference in New Issue
Block a user