site and webhooks packages stubbed

This commit is contained in:
2025-12-23 22:35:38 -07:00
commit cdbc08dea2
16 changed files with 527 additions and 0 deletions
+44
View File
@@ -0,0 +1,44 @@
# V2 - Attempt 2
Looking to follow the [CQRS](https://martinfowler.com/bliki/CQRS.html) pattern.
The database will follow the [event sourcing](https://martinfowler.com/eaaDev/EventSourcing.html) database pattern.
All events (or commands) will be stored in a respective event series, and all database reads will be from views that are projections, reductions, aggregations of those event series.
## Application/directory structure
- /internal
- /site: website
- /webhooks: webhooks for platform events
- /domains: packages for each domain
- /store_events: storing and events
- /platforms: ecommerce platform domains
- /tiktok: interface with tiktok
- ... etc
- ... etc
![Application structure](./diagrams/application_structure.svg)
## Architectural and Software Diagrams
### Event Sourcing Architecture
![Event sourcing architecture](./diagrams/event_sourcing.svg)
### Event Structure
![Event](./diagrams/event.svg)
### Store Event Database Tables
![Event database tables](./diagrams/event_tables.svg)
### All Diagrams
All diagrams are stored in the [diagrams](./diagrams) directory
File diff suppressed because one or more lines are too long

After

Width:  |  Height:  |  Size: 20 KiB

+49
View File
@@ -0,0 +1,49 @@
@startuml
cloud {
[Web site]
}
cloud {
[Tiktok API]
[Etsy API]
[Etc APIs]
}
node "http server" {
frame "site" {
[REST API]
[HTML Templates]
}
frame "webhooks" {
[Tiktok Hook]
[Etsy Hook]
[Etc Hooks]
}
}
node "domains" {
[store events]
[tiktok]
[etsy]
[etc]
}
[Web site] -- [REST API]
[REST API] -- [HTML Templates]
[REST API] -- [store events]
[REST API] -- [tiktok]
[REST API] -- [etsy]
[REST API] -- [etc]
[Tiktok API] -- [Tiktok Hook]
[Etsy API] -- [Etsy Hook]
[Etc APIs] -- [Etc Hooks]
[Tiktok Hook] -- [tiktok]
[Etsy Hook] -- [etsy]
[Etc Hooks] -- [etc]
@enduml
+46
View File
@@ -0,0 +1,46 @@
<?xml version="1.0" encoding="UTF-8" standalone="no"?><svg xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" contentScriptType="application/ecmascript" contentStyleType="text/css" height="390px" preserveAspectRatio="none" style="width:292px;height:390px;" version="1.1" viewBox="0 0 292 390" width="292px" zoomAndPan="magnify"><defs><filter height="300%" id="f181xfa91hfwdr" width="300%" x="-1" y="-1"><feGaussianBlur result="blurOut" stdDeviation="2.0"/><feColorMatrix in="blurOut" result="blurOut2" type="matrix" values="0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 .4 0"/><feOffset dx="4.0" dy="4.0" in="blurOut2" result="blurOut3"/><feBlend in="SourceGraphic" in2="blurOut3" mode="normal"/></filter></defs><g><!--MD5=[d841c4c2f84d4e18d352f800547f43fc]
cluster platform--><path d="M207.4101,290.7887 C211.7845,283.0528 217.0217,280.9365 223.6689,288.3495 C226.7042,281.611 233.8131,279.6104 238.5614,286.4741 C244.6464,279.7464 251.8459,281.5338 254.6263,289.9741 C261.7113,281.3619 267.2181,281.78 272.1849,291.954 C276.6125,296.2279 277.219,299.7576 271.0513,302.9381 C277.8145,306.9578 278.8998,311.0639 272.1982,316.2441 C278.8575,318.4683 279.456,322.3798 274.8287,327.2915 C280.5151,331.9934 278.5896,337.3451 272.3701,339.6852 C278.3415,342.7954 278.9048,348.3463 273.0139,352.073 C278.2453,357.2174 277.5663,362.7407 270.0414,364.9375 C266.4511,372.8768 259.3404,373.0132 254.6828,366.0366 C251.4808,375.4858 245.272,376.1696 238.8691,369.101 C233.7762,376.4902 226.1736,374.4101 222.9495,367.0798 C216.4398,375.216 210.3084,373.7899 206.2037,364.7368 C200.3841,360.9853 199.0186,356.0973 205.6657,351.8263 C200.7098,348.563 200.3242,343.885 205.1097,340.1575 C198.7812,338.0586 198.8138,332.4768 203.7132,328.9756 C197.4907,324.3591 199.5192,318.4844 205.6342,315.6457 C199.8774,311.8173 200.5234,308.2951 205.4265,304.2721 C200.5254,300.0621 200.3627,293.2409 207.4101,290.7887 " fill="#FFFFFF" style="stroke: #000000; stroke-width: 1.5;"/><text fill="#000000" font-family="sans-serif" font-size="14" font-weight="bold" lengthAdjust="spacingAndGlyphs" textLength="61" x="207.5" y="309.9659">platform</text><rect fill="#FEFECE" filter="url(#f181xfa91hfwdr)" height="36.3441" style="stroke: #A80036; stroke-width: 1.5;" width="47" x="214.5" y="320"/><text fill="#000000" font-family="sans-serif" font-size="12" lengthAdjust="spacingAndGlyphs" textLength="33" x="221.5" y="334.8281">Order</text><line style="stroke: #A80036; stroke-width: 1.5;" x1="215.5" x2="260.5" y1="340.3441" y2="340.3441"/><rect fill="#FEFECE" filter="url(#f181xfa91hfwdr)" height="36.3441" style="stroke: #A80036; stroke-width: 1.5;" width="45" x="57.5" y="8.5"/><text fill="#000000" font-family="sans-serif" font-size="12" lengthAdjust="spacingAndGlyphs" textLength="31" x="64.5" y="23.3281">Event</text><line style="stroke: #A80036; stroke-width: 1.5;" x1="58.5" x2="101.5" y1="28.8441" y2="28.8441"/><rect fill="#FEFECE" filter="url(#f181xfa91hfwdr)" height="36.3441" style="stroke: #A80036; stroke-width: 1.5;" width="92" x="34" y="104.5"/><text fill="#000000" font-family="sans-serif" font-size="12" lengthAdjust="spacingAndGlyphs" textLength="78" x="41" y="119.3281">HTTP Request</text><line style="stroke: #A80036; stroke-width: 1.5;" x1="35" x2="125" y1="124.8441" y2="124.8441"/><rect fill="#FEFECE" filter="url(#f181xfa91hfwdr)" height="36.3441" style="stroke: #A80036; stroke-width: 1.5;" width="62" x="6" y="200.5"/><text fill="#000000" font-family="sans-serif" font-size="12" lengthAdjust="spacingAndGlyphs" textLength="48" x="13" y="215.3281">Headers</text><line style="stroke: #A80036; stroke-width: 1.5;" x1="7" x2="67" y1="220.8441" y2="220.8441"/><rect fill="#FEFECE" filter="url(#f181xfa91hfwdr)" height="36.3441" style="stroke: #A80036; stroke-width: 1.5;" width="42" x="103" y="200.5"/><text fill="#000000" font-family="sans-serif" font-size="12" lengthAdjust="spacingAndGlyphs" textLength="28" x="110" y="215.3281">Body</text><line style="stroke: #A80036; stroke-width: 1.5;" x1="104" x2="144" y1="220.8441" y2="220.8441"/><rect fill="#FEFECE" filter="url(#f181xfa91hfwdr)" height="82.8241" style="stroke: #A80036; stroke-width: 1.5;" width="110" x="69" y="296.5"/><text fill="#000000" font-family="sans-serif" font-size="12" lengthAdjust="spacingAndGlyphs" textLength="96" x="76" y="311.3281">event description</text><line style="stroke: #A80036; stroke-width: 1.5;" x1="70" x2="178" y1="316.8441" y2="316.8441"/><text fill="#000000" font-family="sans-serif" font-size="10" lengthAdjust="spacingAndGlyphs" textLength="25" x="75" y="331.5341">store</text><text fill="#000000" font-family="sans-serif" font-size="10" lengthAdjust="spacingAndGlyphs" textLength="9" x="75" y="345.1541">id</text><text fill="#000000" font-family="sans-serif" font-size="10" lengthAdjust="spacingAndGlyphs" textLength="52" x="75" y="358.7741">timestamp</text><text fill="#000000" font-family="sans-serif" font-size="10" lengthAdjust="spacingAndGlyphs" textLength="26" x="75" y="372.3941">order</text><!--MD5=[2ccc1eaed605fa0e0cf1bdb00c98ccc0]
link Event to HTTPRequest--><path d="M80,44.74 C80,59.98 80,82.31 80,98.96 " fill="none" id="Event-&gt;HTTPRequest" style="stroke: #A80036; stroke-width: 1.0;"/><polygon fill="#A80036" points="80,104.37,84,95.37,80,99.37,76,95.37,80,104.37" style="stroke: #A80036; stroke-width: 1.0;"/><!--MD5=[67da588534e36a000f2f212cb4573958]
reverse link HTTPRequest to Headers--><path d="M66.53,152.95 C59.4,168.54 50.88,187.15 44.84,200.37 " fill="none" id="HTTPRequest&lt;-Headers" style="stroke: #A80036; stroke-width: 1.0;"/><polygon fill="#A80036" points="72.11,140.74,65.974,144.528,67.1106,151.649,73.2466,147.861,72.11,140.74" style="stroke: #A80036; stroke-width: 1.0;"/><!--MD5=[d58850b6fb58ba577537fc7ea6e0971f]
reverse link HTTPRequest to Body--><path d="M93.78,152.95 C101.08,168.54 109.79,187.15 115.98,200.37 " fill="none" id="HTTPRequest&lt;-Body" style="stroke: #A80036; stroke-width: 1.0;"/><polygon fill="#A80036" points="88.07,140.74,86.9859,147.8691,93.1497,151.6118,94.2338,144.4827,88.07,140.74" style="stroke: #A80036; stroke-width: 1.0;"/><!--MD5=[71037632cbcca795a013e24b2d66ab25]
reverse link Body to EventDescription--><path d="M124,249.62 C124,263.81 124,280.95 124,296.28 " fill="none" id="Body&lt;-EventDescription" style="stroke: #A80036; stroke-width: 1.0;"/><polygon fill="#A80036" points="124,236.59,120,242.59,124,248.59,128,242.59,124,236.59" style="stroke: #A80036; stroke-width: 1.0;"/><!--MD5=[cdbb0c1ca42aa84d86e03a93bd99d875]
reverse link EventDescription to Order--><path d="M199.4,338 C204.3,338 209.21,338 214.12,338 " fill="none" id="EventDescription&lt;-Order" style="stroke: #A80036; stroke-width: 1.0;"/><polygon fill="none" points="199.22,345,179.22,338,199.22,331,199.22,345" style="stroke: #A80036; stroke-width: 1.0;"/><!--MD5=[20eda4c0919a24efe2526ecd58e999de]
@startuml
object Event
object "HTTP Request" as HTTPRequest
object Headers
object Body
object "event description" as EventDescription
cloud platform {
object Order
}
Event - -> HTTPRequest
HTTPRequest *- - Headers
HTTPRequest *- - Body
Body *- - EventDescription
EventDescription : store
EventDescription : id
EventDescription : timestamp
EventDescription : order
EventDescription <|- Order
@enduml
PlantUML version 1.2020.02(Sun Mar 01 03:22:07 MST 2020)
(GPL source distribution)
Java Runtime: OpenJDK Runtime Environment
JVM: OpenJDK 64-Bit Server VM
Java Version: 21.0.9+10-Ubuntu-124.04
Operating System: Linux
Default Encoding: UTF-8
Language: en
Country: US
--></g></svg>

After

Width:  |  Height:  |  Size: 7.4 KiB

+28
View File
@@ -0,0 +1,28 @@
@startuml
object Event
object "HTTP Request" as HTTPRequest
object Headers
object Body
object "event description" as EventDescription
cloud platform {
object Order
}
Event --> HTTPRequest
HTTPRequest *-- Headers
HTTPRequest *-- Body
Body *-- EventDescription
EventDescription : store
EventDescription : id
EventDescription : timestamp
EventDescription : order
EventDescription <|- Order
@enduml
+51
View File
@@ -0,0 +1,51 @@
<?xml version="1.0" encoding="UTF-8" standalone="no"?><svg xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" contentScriptType="application/ecmascript" contentStyleType="text/css" height="331px" preserveAspectRatio="none" style="width:629px;height:331px;" version="1.1" viewBox="0 0 629 331" width="629px" zoomAndPan="magnify"><defs><filter height="300%" id="f1421eyxhylf5w" width="300%" x="-1" y="-1"><feGaussianBlur result="blurOut" stdDeviation="2.0"/><feColorMatrix in="blurOut" result="blurOut2" type="matrix" values="0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 .4 0"/><feOffset dx="4.0" dy="4.0" in="blurOut2" result="blurOut3"/><feBlend in="SourceGraphic" in2="blurOut3" mode="normal"/></filter></defs><g><!--MD5=[e8f3788ad7b97e8de68b4194558bc2a1]
cluster db--><path d="M362.5,34.5 C362.5,24.5 490,24.5 490,24.5 C490,24.5 617.5,24.5 617.5,34.5 L617.5,309 C617.5,319 490,319 490,319 C490,319 362.5,319 362.5,309 L362.5,34.5 " fill="#FFFFFF" filter="url(#f1421eyxhylf5w)" style="stroke: #000000; stroke-width: 1.5;"/><path d="M362.5,34.5 C362.5,44.5 490,44.5 490,44.5 C490,44.5 617.5,44.5 617.5,34.5 " fill="none" style="stroke: #000000; stroke-width: 1.5;"/><text fill="#000000" font-family="sans-serif" font-size="14" font-weight="bold" lengthAdjust="spacingAndGlyphs" textLength="93" x="443.5" y="61.4659">db (postgres)</text><!--MD5=[e2a75efa8a0f57c2afae8bf75386f79c]
cluster views--><rect fill="#FFFFFF" filter="url(#f1421eyxhylf5w)" height="93" style="stroke: #000000; stroke-width: 1.5;" width="207" x="386.5" y="85.5"/><text fill="#000000" font-family="sans-serif" font-size="14" font-weight="bold" lengthAdjust="spacingAndGlyphs" textLength="39" x="470.5" y="102.4659">views</text><!--MD5=[358ab9ee084784a6364725e117c35e22]
entity eventtable--><rect fill="#FEFECE" filter="url(#f1421eyxhylf5w)" height="39.0679" style="stroke: #000000; stroke-width: 1.5;" width="93" x="381" y="258"/><text fill="#000000" font-family="sans-serif" font-size="14" lengthAdjust="spacingAndGlyphs" textLength="73" x="391" y="282.9659">raw events</text><!--MD5=[d6856b040a1663a798ad7a4fdedcc4e9]
entity trigger--><ellipse cx="561.5" cy="268" fill="#FEFECE" filter="url(#f1421eyxhylf5w)" rx="12" ry="12" style="stroke: #A80036; stroke-width: 2.0;"/><polygon fill="#A80036" points="557.5,256,563.5,251,561.5,256,563.5,261,557.5,256" style="stroke: #A80036; stroke-width: 1.0;"/><text fill="#000000" font-family="sans-serif" font-size="14" lengthAdjust="spacingAndGlyphs" textLength="47" x="538" y="298.9659">trigger</text><!--MD5=[9a3c926baf05f907617b421b7de43814]
entity virtualViews--><rect fill="#FEFECE" filter="url(#f1421eyxhylf5w)" height="39.0679" style="stroke: #000000; stroke-width: 1.5;" width="63" x="403" y="123.5"/><text fill="#000000" font-family="sans-serif" font-size="14" lengthAdjust="spacingAndGlyphs" textLength="43" x="413" y="148.4659">virtual</text><!--MD5=[76cb7dc50c7f0087f20694a01d35754d]
entity materialViews--><rect fill="#FEFECE" filter="url(#f1421eyxhylf5w)" height="39.0679" style="stroke: #000000; stroke-width: 1.5;" width="76" x="501.5" y="123.5"/><text fill="#000000" font-family="sans-serif" font-size="14" lengthAdjust="spacingAndGlyphs" textLength="56" x="511.5" y="148.4659">material</text><!--MD5=[9205152578b4c4885530cd24a645560f]
entity platforms--><path d="M15.0004,252.3962 C17.9147,246.3284 23.6394,244.9637 28.0382,250.7656 C31.5671,246.5538 36.1781,246.049 38.9921,251.5702 C41.0232,244.8973 46.9531,244.3438 50.8428,249.7463 C53.4113,243.095 58.809,242.8404 62.5198,248.6783 C66.1639,241.9808 71.3092,243.5149 74.9427,248.6894 C77.4151,242.4054 81.6703,241.8911 86.3847,246.2876 C90.1319,240.7147 93.8086,240.9119 97.1528,246.6706 C100.2614,242.0476 105.8153,241.0407 108.6255,246.9942 C112.5651,240.4518 118.4868,240.0524 122.2963,247.1247 C125.976,243.1387 129.934,243.257 132.0269,248.7532 C136.347,242.1507 142.1109,243.7875 145.2468,249.977 C149.0378,244.9482 154.2668,245.8931 156.4795,251.6247 C159.9318,247.1424 165.3293,247.8174 167.8447,252.7549 C170.8599,246.6291 176.8692,246.2529 180.9339,251.5526 C188.0624,253.3067 190.8687,259.666 184.8734,265.2066 C192.4385,266.7439 195.0805,273.0861 189.3634,278.9597 C193.5784,285.0838 190.7271,290.5938 183.529,291.3388 C188.7123,296.6125 187.3826,302.5082 180.1146,304.5682 C176.8141,311.1678 172.1888,311.1065 167.6075,305.9283 C164.3979,310.6024 161.0348,311.6753 157.2106,306.5826 C153.9444,312.8946 148.5571,312.928 144.9224,306.9511 C140.2535,312.7928 135.6856,311.312 132.4473,305.4288 C129.7245,311.1787 125.4397,312.2033 121.1054,307.1152 C117.8045,312.3325 112.4506,313.2367 108.8567,307.441 C106.0404,312.1853 101.1046,311.8268 98.4812,307.2269 C94.9023,312.8611 88.1829,313.9539 84.8658,306.945 C81.853,311.5192 76.508,311.8046 73.685,306.7251 C70.4695,312.0285 66.0073,311.8158 62.3445,307.2386 C57.6569,311.8644 53.0502,311.0897 50.4689,304.9212 C47.1294,310.5761 42.5034,310.3521 39.114,304.9726 C34.666,311.1463 30.733,310.0703 26.4257,304.8163 C22.6843,311.0862 19.1749,310.1979 15.0194,305.237 C7.085,302.3704 4.7998,297.6757 10.7615,290.7304 C4.9824,289.3668 3.4157,283.8261 7.1171,279.4104 C1.9534,273.9856 2.9473,266.6986 11.414,265.4774 C7.0485,259.4079 7.355,255.1728 15.0004,252.3962 " fill="#FEFECE" style="stroke: #000000; stroke-width: 1.5;"/><text fill="#000000" font-family="sans-serif" font-size="14" lengthAdjust="spacingAndGlyphs" textLength="151" x="21" y="273.4659">ecommerce platforms:</text><text fill="#000000" font-family="sans-serif" font-size="14" lengthAdjust="spacingAndGlyphs" textLength="63" x="21" y="292.5339">A, B, C, ...</text><!--MD5=[b1a7855e6585e68a7a22c3cffb63dc0f]
entity webhook--><path d="M267,256 L267,280 M267,268 L284,268 " fill="none" filter="url(#f1421eyxhylf5w)" style="stroke: #A80036; stroke-width: 2.0;"/><ellipse cx="296" cy="268" fill="#FEFECE" filter="url(#f1421eyxhylf5w)" rx="12" ry="12" style="stroke: #A80036; stroke-width: 2.0;"/><text fill="#000000" font-family="sans-serif" font-size="14" lengthAdjust="spacingAndGlyphs" textLength="60" x="257.5" y="298.9659">webhook</text><!--MD5=[23cab040c855f106073a9554ba0a8f73]
link platforms to webhook--><path d="M187.25,277.5 C210.3,277.5 233.72,277.5 252.12,277.5 " fill="none" id="platforms-&gt;webhook" style="stroke: #A80036; stroke-width: 1.0;"/><polygon fill="#A80036" points="257.17,277.5,248.17,273.5,252.17,277.5,248.17,281.5,257.17,277.5" style="stroke: #A80036; stroke-width: 1.0;"/><text fill="#000000" font-family="sans-serif" font-size="13" lengthAdjust="spacingAndGlyphs" textLength="34" x="205.25" y="270.397">event</text><!--MD5=[83035e2f9789a9fa293c560fea234ed5]
link webhook to eventtable--><path d="M317.53,277.5 C334.5,277.5 356.17,277.5 375.78,277.5 " fill="none" id="webhook-&gt;eventtable" style="stroke: #A80036; stroke-width: 1.0;"/><polygon fill="#A80036" points="380.99,277.5,371.99,273.5,375.99,277.5,371.99,281.5,380.99,277.5" style="stroke: #A80036; stroke-width: 1.0;"/><text fill="#000000" font-family="sans-serif" font-size="13" lengthAdjust="spacingAndGlyphs" textLength="27" x="335.75" y="270.397">save</text><!--MD5=[f0230ff149e87d499b070d64117e03f8]
link eventtable to trigger--><path d="M474.37,277.5 C493.79,277.5 515.7,277.5 532.7,277.5 " fill="none" id="eventtable-&gt;trigger" style="stroke: #A80036; stroke-width: 1.0;"/><polygon fill="#A80036" points="537.8,277.5,528.8,273.5,532.8,277.5,528.8,281.5,537.8,277.5" style="stroke: #A80036; stroke-width: 1.0;"/><text fill="#000000" font-family="sans-serif" font-size="13" lengthAdjust="spacingAndGlyphs" textLength="27" x="492.5" y="270.397">read</text><!--MD5=[860cd78a7786da8322932b9ab26eba23]
reverse link materialViews to trigger--><path d="M543.46,167.84 C547.38,191.48 553.37,227.55 557.36,251.56 " fill="none" id="materialViews&lt;-trigger" style="stroke: #A80036; stroke-width: 1.0;"/><polygon fill="#A80036" points="542.61,162.7,540.1306,172.2317,543.425,167.6331,548.0236,170.9276,542.61,162.7" style="stroke: #A80036; stroke-width: 1.0;"/><text fill="#000000" font-family="sans-serif" font-size="13" lengthAdjust="spacingAndGlyphs" textLength="43" x="551.5" y="209.397">update</text><!--MD5=[2036aefff09e2d8452addd32e58e2b9b]
reverse link virtualViews to eventtable--><path d="M433.23,167.98 C431.87,193.81 429.73,234.36 428.48,257.89 " fill="none" id="virtualViews&lt;-eventtable" style="stroke: #A80036; stroke-width: 1.0;"/><polygon fill="#A80036" points="433.51,162.7,429.028,171.4699,433.239,167.6926,437.0163,171.9036,433.51,162.7" style="stroke: #A80036; stroke-width: 1.0;"/><!--MD5=[e952d7f61e81b262dbf5ac2ebc7fe5ce]
reverse link virtualViews to materialViews--><path d="M471.32,143 C481.33,143 491.34,143 501.36,143 " fill="none" id="virtualViews&lt;-materialViews" style="stroke: #A80036; stroke-width: 1.0;"/><polygon fill="#A80036" points="466.08,143,475.08,147,471.08,143,475.08,139,466.08,143" style="stroke: #A80036; stroke-width: 1.0;"/><!--MD5=[7e09ac5c3e227e26930fb5d40726a924]
@startuml
cloud "ecommerce platforms:\n A, B, C, ..." as platforms
boundary webhook
database db as "db (postgres)" {
rectangle "raw events" as eventtable
control trigger
rectangle views {
rectangle virtual as virtualViews
rectangle material as materialViews
}
}
platforms -> webhook : event
webhook -> eventtable : save
eventtable -> trigger : read
trigger -up-> materialViews : update
eventtable -up-> virtualViews
materialViews -left-> virtualViews
@enduml
PlantUML version 1.2020.02(Sun Mar 01 03:22:07 MST 2020)
(GPL source distribution)
Java Runtime: OpenJDK Runtime Environment
JVM: OpenJDK 64-Bit Server VM
Java Version: 21.0.9+10-Ubuntu-124.04
Operating System: Linux
Default Encoding: UTF-8
Language: en
Country: US
--></g></svg>

After

Width:  |  Height:  |  Size: 9.5 KiB

+25
View File
@@ -0,0 +1,25 @@
@startuml
cloud "ecommerce platforms:\n A, B, C, ..." as platforms
boundary webhook
database db as "db (postgres)" {
rectangle "raw events" as eventtable
control trigger
rectangle views {
rectangle virtual as virtualViews
rectangle material as materialViews
}
}
platforms -> webhook : event
webhook -> eventtable : save
eventtable -> trigger : read
trigger -up-> materialViews : update
eventtable -up-> virtualViews
materialViews -left-> virtualViews
@enduml
File diff suppressed because one or more lines are too long

After

Width:  |  Height:  |  Size: 11 KiB

+56
View File
@@ -0,0 +1,56 @@
@startuml
object store_events
store_events : platform
store_events : store_id
store_events : event_id
store_events : event_timestamp
store_events : tiktok_store_id
store_events : tiktok_event_id
store_events : <platform B>_store_id
store_events : <platform B>_event_id
store_events : <platform C>_store_id
store_events : <platform C>_event_id
store_events : <platform ..>_store_id
store_events : <platform ..>_event_id
store_events : raw payload?
note right of store_events::"tiktok_event_id"
reference tiktok_store_event_details
end note
note right of store_events::"<platform B>_event_id"
reference <platform B>_store_event_details
end note
note right of store_events::"<platform C>_event_id"
reference <platform C>_store_event_details
end note
note right of store_events::"<platform ..>_event_id"
only one reference can exist at a time
(eg event CANNOT be both from tiktok AND etsy)
end note
object tiktok_store_event_details
tiktok_store_event_details : store_id
tiktok_store_event_details : event_id
tiktok_store_event_details : event_timestamp
tiktok_store_event_details : [attribute: value ...]
object "<platform B>_store_event_details" as platform_b_specific_store_event_details
platform_b_specific_store_event_details : store_id
platform_b_specific_store_event_details : event_id
platform_b_specific_store_event_details : event_timestamp
platform_b_specific_store_event_details : [attribute: value ...]
object "<platform C>_store_event_details" as platform_c_specific_store_event_details
platform_c_specific_store_event_details : store_id
platform_c_specific_store_event_details : event_id
platform_c_specific_store_event_details : event_timestamp
platform_c_specific_store_event_details : [attribute: value ...]
store_events <|-- tiktok_store_event_details
store_events <|-- platform_b_specific_store_event_details
store_events <|-- platform_c_specific_store_event_details
@enduml
+3
View File
@@ -0,0 +1,3 @@
module ruben/inventory2
go 1.24.2
+26
View File
@@ -0,0 +1,26 @@
package site
import (
"net/http"
)
func NewSiteHandler() http.Handler {
mux := http.NewServeMux()
mux.HandleFunc("/", func(w http.ResponseWriter, r *http.Request) {
w.Header().Set("Content-Type", "text/html")
w.Write([]byte(`
<DOCTYPE html>
<html>
<head>
<title>TEST</title>
</head>
<body>
TEST
</body>
</html>
`))
})
return mux
}
+17
View File
@@ -0,0 +1,17 @@
package etsy
import (
"fmt"
"net/http"
)
func NewWebhookHandler() http.Handler {
mux := http.NewServeMux()
mux.HandleFunc("/", func(w http.ResponseWriter, r *http.Request) {
w.Header().Set("Content-Type", "text/plain")
w.Write([]byte(fmt.Sprintf("received etsy webhook call: %s %s\n", r.Method, r.URL)))
})
return mux
}
+17
View File
@@ -0,0 +1,17 @@
package tiktok
import (
"fmt"
"net/http"
)
func NewWebhookHandler() http.Handler {
mux := http.NewServeMux()
mux.HandleFunc("/", func(w http.ResponseWriter, r *http.Request) {
w.Header().Set("Content-Type", "text/plain")
w.Write([]byte(fmt.Sprintf("received tiktok webhook call: %s %s\n", r.Method, r.URL)))
})
return mux
}
+19
View File
@@ -0,0 +1,19 @@
package webhooks
import (
"net/http"
"ruben/inventory2/internal/webhooks/etsy"
"ruben/inventory2/internal/webhooks/tiktok"
"ruben/inventory2/internal/webhooks/wix"
)
func New() http.Handler {
wh := http.NewServeMux()
wh.Handle("/etsy/", http.StripPrefix("/etsy", etsy.NewWebhookHandler()))
wh.Handle("/tiktok/", http.StripPrefix("/tiktok", tiktok.NewWebhookHandler()))
wh.Handle("/wix/", http.StripPrefix("/wix", wix.NewWebhookHandler()))
return wh
}
+17
View File
@@ -0,0 +1,17 @@
package wix
import (
"fmt"
"net/http"
)
func NewWebhookHandler() http.Handler {
mux := http.NewServeMux()
mux.HandleFunc("/", func(w http.ResponseWriter, r *http.Request) {
w.Header().Set("Content-Type", "text/plain")
w.Write([]byte(fmt.Sprintf("received wix webhook call: %s %s\n", r.Method, r.URL)))
})
return mux
}
+127
View File
@@ -0,0 +1,127 @@
package main
import (
"context"
"errors"
"fmt"
"net/http"
"os"
"os/signal"
"syscall"
"time"
"ruben/inventory2/internal/site"
"ruben/inventory2/internal/webhooks"
)
func main() {
if err := runApp(context.Background()); err != nil {
panic(err)
}
fmt.Println("application shutdown")
}
func runApp(ctx context.Context) error {
ctx, shutdown := context.WithCancel(ctx)
defer shutdown()
// start http server
srvErrCh := runServer(ctx)
// wait for interrupt signal or unrecoverable failure, then shutdown
osSignalCh := make(chan os.Signal, 1)
signal.Notify(osSignalCh, syscall.SIGINT, syscall.SIGTERM)
var serverAlreadyShutdown bool
select {
case s := <-osSignalCh:
fmt.Println("application received shutdown signal:", s)
fmt.Println("shutting down")
case err := <-srvErrCh:
serverAlreadyShutdown = true
fmt.Println("server shutdown unexpectedly")
if err != nil {
fmt.Println("server encountered error:", err)
}
}
shutdown()
// capture application errors that occurred during or caused shutdown
var errs []error
if !serverAlreadyShutdown {
if err := <-srvErrCh; err != nil {
errs = append(errs, fmt.Errorf("server experienced an error: %w", err))
}
fmt.Println("server shut down")
}
return errors.Join(errs...)
}
func buildHTTPHandler() http.Handler {
mux := http.NewServeMux()
mux.Handle("/webhooks/", http.StripPrefix("/webhooks", webhooks.New()))
mux.Handle("/site/", http.StripPrefix("/site", site.NewSiteHandler()))
return mux
}
func runServer(ctx context.Context) <-chan error {
srv := &http.Server{
Addr: ":9000", // local
Handler: buildHTTPHandler(),
}
ctx, cancel := context.WithCancel(ctx)
alreadyShutdownCh := make(chan struct{}, 1)
runningErrCh := make(chan error, 1)
go func() {
defer close(runningErrCh)
defer cancel()
defer close(alreadyShutdownCh)
fmt.Println("server running on 9000...")
if err := srv.ListenAndServe(); err != nil {
if !errors.Is(err, http.ErrServerClosed) {
runningErrCh <- fmt.Errorf("server experienced error: %w", err)
}
}
}()
shutdownErrCh := make(chan error, 1)
go func() {
defer close(shutdownErrCh)
select {
case <-alreadyShutdownCh:
return
case <-ctx.Done():
}
shutdownCtx, cancelShutdown := context.WithTimeout(context.Background(), 60*time.Second)
defer cancelShutdown()
if err := srv.Shutdown(shutdownCtx); err != nil {
shutdownErrCh <- fmt.Errorf("error occurred attempting to shutdown server: %w", err)
}
}()
errCh := make(chan error, 1)
go func() {
defer close(errCh)
err1 := <-runningErrCh
err2 := <-shutdownErrCh
if err := errors.Join(err1, err2); err != nil {
errCh <- err
}
}()
return errCh
}