Skip to content

Instantly share code, notes, and snippets.

@naartjie
Last active April 28, 2022 16:16
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save naartjie/b9c35cb2ea2a31b72640ecf8905a45b7 to your computer and use it in GitHub Desktop.
Save naartjie/b9c35cb2ea2a31b72640ecf8905a45b7 to your computer and use it in GitHub Desktop.
(**
Pushes data from MongoDB to Oracle Micros iCare
Run it like so:
$ MONGO_URL=xxx \
MICROS_URL=xxx \
MICROS_USERNAME=xxx \
MICROS_PASSWORD=xxx \
dotnet fsi ./push_to_micros.fsx
*)
#r "nuget: MongoDB.Driver, 2.15.0"
#r "nuget: Crc32.NET, 1.2.0"
#r "nuget: FsHttp, 9.0.2"
#r "nuget: FSharp.Data, 4.2.8"
module Db =
open MongoDB.Driver
open MongoDB.Bson
open MongoDB.Bson.Serialization.Attributes
open MongoDB.Bson.Serialization.Conventions
[<BsonIgnoreExtraElements>]
type Card =
{ [<BsonElement("cardNumber")>]
Number: string }
[<BsonIgnoreExtraElements>]
type User =
{ Id: ObjectId
FirstName: string
LastName: string
Cards: System.Collections.Generic.List<Card>
[<BsonDefaultValue(null)>]
MobileNumber: string
EmailAddress: string
[<BsonDefaultValue(null)>]
BirthDay: System.Nullable<int>
[<BsonDefaultValue(null)>]
BirthMonth: System.Nullable<int>
ReceiveStarbucksEmailCommunications: bool }
let convention = CamelCaseElementNameConvention()
let pack = ConventionPack()
pack.Add convention
ConventionRegistry.Register("camelCase", pack, (fun t -> true))
let client =
System.Environment.GetEnvironmentVariable("MONGO_URL")
|> MongoClient
let db = client.GetDatabase("starbucks")
let users = db.GetCollection<User>("users")
let inline (=>) a b = a, box b
let bson x = BsonDocument(dict x)
let customersWithCards () =
printfn "fetching customers"
users
.Find(bson [ "cards.cardNumber" => bson [ "$exists" => true ] ], FindOptions(NoCursorTimeout = true))
.ToCursor()
module Micros =
open FsHttp
open FSharp.Data
open System.Text.RegularExpressions
type Envelope =
XmlProvider<"""
<S:Envelope xmlns:S="http://schemas.xmlsoap.org/soap/envelope/">
<S:Body>
<ns2:processRequestResponse xmlns:ns2="ejb.storedValue.micros.com">
<return>
&lt;?xml version="1.0" encoding="UTF-8"?>&#xd;&lt;CRMMessage language="en_US" currency="USD" isTrustedSAT="false" hostversion="1.00">&lt;RequestCode>SetCustomer&lt;/RequestCode>&lt;ResponseCode>A&lt;/ResponseCode>&lt;Row id="47756809" />&lt;/CRMMessage>&#xd;
</return>
</ns2:processRequestResponse>
</S:Body>
</S:Envelope>
""">
type Response =
XmlProvider<"""
<CRMMessage language="en_US" currency="USD" isTrustedSAT="false" hostversion="1.00">
<RequestCode>SetCustomer</RequestCode>
<ResponseCode>A</ResponseCode>
<Row id="47756809" />
</CRMMessage>
""">
let microsEndpoint =
System.Environment.GetEnvironmentVariable("MICROS_URL")
let stripOffSpecialChars str =
Regex.Replace(str, "[^\w\s\!\#\$\%\&\\\'\*\+-\/\=\?\^_\`\{\}\|\"\(\)\,\:\;\<\>\@\[\]]", "")
let s (str: string) (maxlen: int option) =
str.Trim()
|> stripOffSpecialChars
|> (fun x ->
match maxlen with
| Some maxlen when maxlen < x.Length -> x.Substring(0, maxlen)
| _ -> x)
|> System.Security.SecurityElement.Escape
let envelope (msg: string) =
let username =
System.Environment.GetEnvironmentVariable("MICROS_USERNAME")
let password =
System.Environment.GetEnvironmentVariable("MICROS_PASSWORD")
let crc =
msg
|> System.Text.Encoding.UTF8.GetBytes
|> Force.Crc32.Crc32Algorithm.Compute
let msg =
msg |> System.Security.SecurityElement.Escape
$"""
<soap:Envelope xmlns:soap="http://schemas.xmlsoap.org/soap/envelope/" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:xsd="http://www.w3.org/2001/XMLSchema">
<soap:Body>
<processRequest xmlns="ejb.storedValue.micros.com">
<in0 xmlns="">%s{msg}</in0>
<in1 xmlns="">%s{username}</in1>
<in2 xmlns="">%s{password}</in2>
<in3 xmlns="">%s{(crc.ToString("X"))}</in3>
</processRequest>
</soap:Body>
</soap:Envelope>
"""
let setCustomersMsg (customers: Db.User []) =
let row (c: Db.User) =
let id =
System.Int32.Parse(c.Id.ToString(), System.Globalization.NumberStyles.HexNumber)
$"""
<Row id="%i{id}">
<Col>%s{s c.FirstName (Some 46)}</Col>
<Col>%s{s c.LastName (Some 32)}</Col>
<Col>%s{s c.EmailAddress (Some 46)}</Col>
<Col>%s{s
(if c.MobileNumber = null then
""
else
c.MobileNumber)
(Some 25)}</Col>
<Col>%s{s
(if c.BirthDay.HasValue then
c.BirthDay.Value.ToString()
else
"")
None}</Col>
<Col>%s{s
(if c.BirthMonth.HasValue then
c.BirthMonth.Value.ToString()
else
"")
None}</Col>
<Col>%i{(if c.ReceiveStarbucksEmailCommunications then
1
else
0)}</Col>
<Col>%i{(if c.ReceiveStarbucksEmailCommunications then
1
else
0)}</Col>
</Row>
"""
$"""
<CRMMessage language="en_US" currency="USD">
<RequestCode version="1">SetCustomer</RequestCode>
<DataSet>
<DataSetColumns>
<DSColumn name="firstname"/>
<DSColumn name="lastname"/>
<DSColumn name="emailaddress"/>
<DSColumn name="mobilephonenumber"/>
<DSColumn name="birthdayofmonth"/>
<DSColumn name="birthmonth"/>
<DSColumn name="emailchnlstatus"/>
<DSColumn name="emailchnldeliverable"/>
</DataSetColumns>
<Rows>
%s{customers
|> Array.map (fun c -> row c)
|> String.concat ""}
</Rows>
</DataSet>
</CRMMessage>
"""
let updateCustomers (customers: Db.User []) =
async {
let xml = (setCustomersMsg customers) |> envelope
let! response =
http {
POST $"{microsEndpoint}"
body
ContentType "text/xml"
text xml
}
|> Request.sendAsync
let! xml = response |> Response.toTextAsync
return xml
}
module Update =
open Db
open Micros
open System.IO
open MongoDB.Driver
let updateCustomers (file: StreamWriter) (cust: User []) =
async {
let! xml = updateCustomers cust
let env =
(xml |> Envelope.Parse)
.Body
.ProcessRequestResponse
.Return
let body = env |> Response.Parse
return
match body.ResponseCode with
| "A" ->
cust
|> Seq.iter (fun c -> file.WriteLine $"{c.Cards.[0].Number}")
Ok()
| _ -> failwith $"%A{body}"
}
let updateAll () =
use users = customersWithCards ()
let alreadyDone =
File.ReadLines("./audit.txt") |> Set.ofSeq
use file =
new StreamWriter($"./audit.txt", append = true)
file.AutoFlush <- true
let mutable counter = 0
users.ToEnumerable()
|> Seq.filter (fun c -> not (alreadyDone.Contains(c.Cards.[0].Number)))
|> Seq.chunkBySize 25
|> Seq.iter (fun page ->
let _results =
updateCustomers file page
|> Async.RunSynchronously
for customer in page do
counter <- counter + 1
let accNum = customer.Cards.[0].Number
printfn $"{accNum} [%d{counter}]")
Update.updateAll ()
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment