Last active
April 28, 2022 16:16
-
-
Save naartjie/b9c35cb2ea2a31b72640ecf8905a45b7 to your computer and use it in GitHub Desktop.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
(** | |
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> | |
<?xml version="1.0" encoding="UTF-8"?>
<CRMMessage language="en_US" currency="USD" isTrustedSAT="false" hostversion="1.00"><RequestCode>SetCustomer</RequestCode><ResponseCode>A</ResponseCode><Row id="47756809" /></CRMMessage>
 | |
</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