diff --git a/404.html b/404.html new file mode 100644 index 00000000..56442525 --- /dev/null +++ b/404.html @@ -0,0 +1,15 @@ + + + + + + + Hitrix - golang framework + + + + +

404

There's nothing here.
Take me home
+ + + diff --git a/assets/css/styles.1ec23e2d.css b/assets/css/styles.1ec23e2d.css new file mode 100644 index 00000000..a719d59b --- /dev/null +++ b/assets/css/styles.1ec23e2d.css @@ -0,0 +1,10 @@ +.icon.outbound{position:relative;display:inline-block;color:#aaa;vertical-align:middle;top:-1px} +:root{--c-brand:#3eaf7c;--c-brand-light:#4abf8a;--c-bg:#ffffff;--c-bg-light:#f3f4f5;--c-bg-lighter:#eeeeee;--c-bg-navbar:var(--c-bg);--c-bg-sidebar:var(--c-bg);--c-bg-arrow:#cccccc;--c-text:#2c3e50;--c-text-accent:var(--c-brand);--c-text-light:#3a5169;--c-text-lighter:#4e6e8e;--c-text-lightest:#6a8bad;--c-text-quote:#999999;--c-border:#eaecef;--c-border-dark:#dfe2e5;--c-tip:#42b983;--c-tip-bg:var(--c-bg-light);--c-tip-title:var(--c-text);--c-tip-text:var(--c-text);--c-tip-text-accent:var(--c-text-accent);--c-warning:#e7c000;--c-warning-bg:#fffae3;--c-warning-title:#ad9000;--c-warning-text:#746000;--c-warning-text-accent:var(--c-text);--c-danger:#cc0000;--c-danger-bg:#ffe0e0;--c-danger-title:#990000;--c-danger-text:#660000;--c-danger-text-accent:var(--c-text);--c-details-bg:#eeeeee;--c-badge-tip:var(--c-tip);--c-badge-warning:var(--c-warning);--c-badge-danger:var(--c-danger);--t-color:0.3s ease;--t-transform:0.3s ease;--code-bg-color:#282c34;--code-hl-bg-color:rgba(0, 0, 0, 0.66);--code-ln-color:#9e9e9e;--code-ln-wrapper-width:3.5rem;--font-family:-apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, Oxygen, Ubuntu, Cantarell, "Fira Sans", "Droid Sans", "Helvetica Neue", sans-serif;--font-family-code:Consolas, Monaco, "Andale Mono", "Ubuntu Mono", monospace;--navbar-height:3.6rem;--navbar-padding-v:0.7rem;--navbar-padding-h:1.5rem;--sidebar-width:20rem;--sidebar-width-mobile:calc(var(--sidebar-width) * 0.82);--content-width:740px;--homepage-width:960px}.back-to-top{--back-to-top-color:var(--c-brand);--back-to-top-color-hover:var(--c-brand-light)}.DocSearch{--docsearch-primary-color:var(--c-brand);--docsearch-text-color:var(--c-text);--docsearch-highlight-color:var(--c-brand);--docsearch-muted-color:var(--c-text-quote);--docsearch-container-background:rgba(9, 10, 17, 0.8);--docsearch-modal-background:var(--c-bg-light);--docsearch-searchbox-background:var(--c-bg-lighter);--docsearch-searchbox-focus-background:var(--c-bg);--docsearch-searchbox-shadow:inset 0 0 0 2px var(--c-brand);--docsearch-hit-color:var(--c-text-light);--docsearch-hit-active-color:var(--c-bg);--docsearch-hit-background:var(--c-bg);--docsearch-hit-shadow:0 1px 3px 0 var(--c-border-dark);--docsearch-footer-background:var(--c-bg)}.medium-zoom-overlay{--medium-zoom-bg-color:var(--c-bg)}#nprogress{--nprogress-color:var(--c-brand)}.pwa-popup{--pwa-popup-text-color:var(--c-text);--pwa-popup-bg-color:var(--c-bg);--pwa-popup-border-color:var(--c-brand);--pwa-popup-shadow:0 4px 16px var(--c-brand);--pwa-popup-btn-text-color:var(--c-bg);--pwa-popup-btn-bg-color:var(--c-brand);--pwa-popup-btn-hover-bg-color:var(--c-brand-light)}.search-box{--search-bg-color:var(--c-bg);--search-accent-color:var(--c-brand);--search-text-color:var(--c-text);--search-border-color:var(--c-border);--search-item-text-color:var(--c-text-lighter);--search-item-focus-bg-color:var(--c-bg-light)}html.dark{--c-brand:#3aa675;--c-brand-light:#349469;--c-bg:#22272e;--c-bg-light:#2b313a;--c-bg-lighter:#262c34;--c-text:#adbac7;--c-text-light:#96a7b7;--c-text-lighter:#8b9eb0;--c-text-lightest:#8094a8;--c-border:#3e4c5a;--c-border-dark:#34404c;--c-tip:#318a62;--c-warning:#ceab00;--c-warning-bg:#7e755b;--c-warning-title:#ceac03;--c-warning-text:#362e00;--c-danger:#940000;--c-danger-bg:#806161;--c-danger-title:#610000;--c-danger-text:#3a0000;--c-details-bg:#323843;--code-hl-bg-color:#363b46;color-scheme:dark}html.dark .DocSearch{--docsearch-logo-color:var(--c-text);--docsearch-modal-shadow:inset 1px 1px 0 0 #2c2e40, 0 3px 8px 0 #000309;--docsearch-key-shadow:inset 0 -2px 0 0 #282d55, inset 0 0 1px 1px #51577d, 0 2px 2px 0 rgba(3, 4, 9, 0.3);--docsearch-key-gradient:linear-gradient(-225deg, #444950, #1c1e21);--docsearch-footer-shadow:inset 0 1px 0 0 rgba(73, 76, 106, 0.5), 0 -4px 8px 0 rgba(0, 0, 0, 0.2)}body,html{padding:0;margin:0;background-color:var(--c-bg);transition:background-color var(--t-color)}body{font-family:var(--font-family);-webkit-font-smoothing:antialiased;-moz-osx-font-smoothing:grayscale;font-size:16px;color:var(--c-text)}a,p a code{color:var(--c-text-accent)}a{font-weight:500;overflow-wrap:break-word}p a code{font-weight:400}code,kbd{font-family:var(--font-family-code)}kbd{background:var(--c-bg-lighter);border:solid .15rem var(--c-border-dark);border-bottom:solid .25rem var(--c-border-dark);border-radius:.15rem;padding:0 .15em}code{color:var(--c-text-lighter);padding:.25rem .5rem;font-size:.85em;background-color:var(--c-bg-light);border-radius:3px;overflow-wrap:break-word;transition:background-color var(--t-color),color var(--t-color)}blockquote{color:var(--c-text-quote);border-left:.2rem solid var(--c-border-dark);margin:1rem 0;padding:.25rem 0 .25rem 1rem}blockquote>p,code{margin:0}ol,ul{padding-left:1.2em}strong{font-weight:600}h1,h2,h3,h4,h5,h6{font-weight:600;line-height:1.25}h1:hover .header-anchor,h2:hover .header-anchor,h3:hover .header-anchor,h4:hover .header-anchor,h5:hover .header-anchor,h6:hover .header-anchor{opacity:1}h1{font-size:2.2rem}h2{font-size:1.65rem;padding-bottom:.3rem;border-bottom:1px solid var(--c-border);transition:border-color var(--t-color)}h3{font-size:1.35rem}h4{font-size:1.15rem}h5{font-size:1.05rem}blockquote,h6{font-size:1rem}a.header-anchor{font-size:.85em;float:left;margin-left:-.87em;padding-right:.23em;margin-top:.125em;opacity:0}a,a.header-anchor:hover{text-decoration:none}a.header-anchor:focus-visible{opacity:1}ol,p,ul{line-height:1.7}hr{border:0;border-top:1px solid var(--c-border)}table{border-collapse:collapse;margin:1rem 0;display:block;overflow-x:auto}tr{border-top:1px solid var(--c-border-dark)}tr:nth-child(2n){background-color:var(--c-bg-light)}td,th{border:1px solid var(--c-border-dark);padding:.6em 1em}.arrow,.badge{display:inline-block}.arrow{width:0;height:0}.arrow.down,.arrow.up{border-left:4px solid transparent;border-right:4px solid transparent}.arrow.up{border-bottom:6px solid var(--c-bg-arrow)}.arrow.down{border-top:6px solid var(--c-bg-arrow)}.arrow.left,.arrow.right{border-top:4px solid transparent;border-bottom:4px solid transparent}.arrow.right{border-left:6px solid var(--c-bg-arrow)}.arrow.left{border-right:6px solid var(--c-bg-arrow)}.badge{font-size:14px;height:18px;line-height:18px;border-radius:3px;padding:0 6px;color:var(--c-bg);vertical-align:top;transition:color var(--t-color),background-color var(--t-color)}.badge.tip{background-color:var(--c-badge-tip)}.badge.warning{background-color:var(--c-badge-warning)}.badge.danger{background-color:var(--c-badge-danger)}.badge+.badge{margin-left:5px}code[class*=language-],pre[class*=language-]{color:#ccc;background:0 0;font-family:var(--font-family-code);font-size:1em;text-align:left;white-space:pre;word-spacing:normal;word-break:normal;word-wrap:normal;line-height:1.5;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-hyphens:none;-ms-hyphens:none;hyphens:none}pre[class*=language-]{padding:1em;margin:.5em 0;overflow:auto}:not(pre)>code[class*=language-],pre[class*=language-]{background:#2d2d2d}:not(pre)>code[class*=language-]{padding:.1em;border-radius:.3em;white-space:normal}.token.block-comment,.token.cdata,.token.comment,.token.doctype,.token.prolog{color:#999}.token.punctuation{color:#ccc}.token.attr-name,.token.deleted,.token.namespace,.token.tag{color:#ec5975}.token.function-name{color:#6196cc}.token.boolean,.token.function,.token.number{color:#f08d49}.token.class-name,.token.constant,.token.property,.token.symbol{color:#f8c555}.token.atrule,.token.builtin,.token.important,.token.keyword,.token.selector{color:#cc99cd}.token.attr-value,.token.char,.token.regex,.token.string,.token.variable{color:#7ec699}.token.entity,.token.operator,.token.url{color:#67cdcc}.token.bold,.token.important{font-weight:700}.token.italic{font-style:italic}.token.entity{cursor:help}.token.inserted{color:#3eaf7c}.theme-default-content pre,.theme-default-content pre[class*=language-]{line-height:1.4;padding:1.25rem 1.5rem;margin:.85rem 0;border-radius:6px;overflow:auto}.theme-default-content pre code,.theme-default-content pre[class*=language-] code{color:#fff;padding:0;background-color:transparent;border-radius:0;-webkit-font-smoothing:auto;-moz-osx-font-smoothing:auto}.theme-default-content .line-number{font-family:var(--font-family-code)}div[class*=language-]{position:relative;background-color:var(--code-bg-color);border-radius:6px}div[class*=language-]::before{position:absolute;z-index:3;top:.8em;right:1em;font-size:.75rem;color:var(--code-ln-color)}div[class*=language-] pre,div[class*=language-] pre[class*=language-]{background:0 0!important;position:relative;z-index:1}div[class*=language-] .highlight-lines{-webkit-user-select:none;-moz-user-select:none;-ms-user-select:none;user-select:none;padding-top:1.3rem;position:absolute;top:0;left:0;width:100%;line-height:1.4}div[class*=language-] .highlight-lines .highlight-line{background-color:var(--code-hl-bg-color)}div[class*=language-]:not(.line-numbers-mode) .line-numbers{display:none}div[class*=language-].line-numbers-mode .highlight-lines .highlight-line{position:relative}div[class*=language-].line-numbers-mode .highlight-lines .highlight-line::before{content:" ";position:absolute;z-index:2;left:0;top:0;display:block;width:var(--code-ln-wrapper-width);height:100%}div[class*=language-].line-numbers-mode pre{margin-left:var(--code-ln-wrapper-width);padding-left:1rem;vertical-align:middle}div[class*=language-].line-numbers-mode .line-numbers{position:absolute;top:0;width:var(--code-ln-wrapper-width);text-align:center;color:var(--code-ln-color);padding-top:1.25rem;line-height:1.4}div[class*=language-].line-numbers-mode .line-numbers .line-number,div[class*=language-].line-numbers-mode .line-numbers br{-webkit-user-select:none;-moz-user-select:none;-ms-user-select:none;user-select:none}div[class*=language-].line-numbers-mode .line-numbers .line-number{position:relative;z-index:3;font-size:.85em}div[class*=language-].line-numbers-mode::after{content:"";position:absolute;top:0;left:0;width:var(--code-ln-wrapper-width);height:100%;border-radius:6px 0 0 6px;border-right:1px solid var(--code-hl-bg-color)}div[class*=language-].ext-c:before{content:"c"}div[class*=language-].ext-cpp:before{content:"cpp"}div[class*=language-].ext-cs:before{content:"cs"}div[class*=language-].ext-css:before{content:"css"}div[class*=language-].ext-dart:before{content:"dart"}div[class*=language-].ext-docker:before{content:"docker"}div[class*=language-].ext-fs:before{content:"fs"}div[class*=language-].ext-go:before{content:"go"}div[class*=language-].ext-html:before{content:"html"}div[class*=language-].ext-java:before{content:"java"}div[class*=language-].ext-js:before{content:"js"}div[class*=language-].ext-json:before{content:"json"}div[class*=language-].ext-kt:before{content:"kt"}div[class*=language-].ext-less:before{content:"less"}div[class*=language-].ext-makefile:before{content:"makefile"}div[class*=language-].ext-md:before{content:"md"}div[class*=language-].ext-php:before{content:"php"}div[class*=language-].ext-py:before{content:"py"}div[class*=language-].ext-rb:before{content:"rb"}div[class*=language-].ext-rs:before{content:"rs"}div[class*=language-].ext-sass:before{content:"sass"}div[class*=language-].ext-scss:before{content:"scss"}div[class*=language-].ext-sh:before{content:"sh"}div[class*=language-].ext-styl:before{content:"styl"}div[class*=language-].ext-ts:before{content:"ts"}div[class*=language-].ext-toml:before{content:"toml"}div[class*=language-].ext-vue:before{content:"vue"}div[class*=language-].ext-yml:before{content:"yml"}@media (max-width:419px){.theme-default-content div[class*=language-]{margin:.85rem -1.5rem;border-radius:0}}.code-group__nav{margin-top:.85rem;margin-bottom:calc(-1.7rem - 6px);padding-bottom:calc(1.7rem - 6px);padding-left:10px;padding-top:10px;border-top-left-radius:6px;border-top-right-radius:6px;background-color:var(--code-bg-color)}.code-group__ul{margin:auto 0;padding-left:0;display:inline-flex;list-style:none}.code-group__nav-tab{border:0;padding:5px;cursor:pointer;background-color:transparent;font-size:.85em;line-height:1.4;color:rgba(255,255,255,.9);font-weight:600}.code-group__nav-tab:focus{outline:0}.code-group__nav-tab:focus-visible{outline:1px solid rgba(255,255,255,.9)}.code-group__nav-tab-active{border-bottom:var(--c-brand) 1px solid}@media (max-width:419px){.code-group__nav{margin-left:-1.5rem;margin-right:-1.5rem;border-radius:0}}.code-group-item,.dropdown-wrapper .nav-dropdown .dropdown-item .dropdown-subtitle>a.router-link-active::after{display:none}.code-group-item__active{display:block}.code-group-item>pre{background-color:orange}.custom-container{transition:color var(--t-color),border-color var(--t-color),background-color var(--t-color)}.custom-container .custom-container-title{font-weight:600;margin-bottom:-.4rem}.custom-container.danger,.custom-container.tip,.custom-container.warning{padding:.1rem 1.5rem;border-left-width:.5rem;border-left-style:solid;margin:1rem 0}.custom-container.tip{border-color:var(--c-tip);background-color:var(--c-tip-bg);color:var(--c-tip-text)}.custom-container.tip .custom-container-title{color:var(--c-tip-title)}.custom-container.tip a{color:var(--c-tip-text-accent)}.custom-container.warning{border-color:var(--c-warning);background-color:var(--c-warning-bg);color:var(--c-warning-text)}.custom-container.warning .custom-container-title{color:var(--c-warning-title)}.custom-container.warning a{color:var(--c-warning-text-accent)}.custom-container.danger{border-color:var(--c-danger);background-color:var(--c-danger-bg);color:var(--c-danger-text)}.custom-container.danger .custom-container-title{color:var(--c-danger-title)}.custom-container.danger a{color:var(--c-danger-text-accent)}.custom-container.details{display:block;position:relative;border-radius:2px;margin:1.6em 0;padding:1.6em;background-color:var(--c-details-bg)}.custom-container.details h4{margin-top:0}.custom-container.details figure:last-child,.custom-container.details p:last-child{margin-bottom:0;padding-bottom:0}.custom-container.details summary{outline:0;cursor:pointer}.dropdown-wrapper{cursor:pointer}.dropdown-wrapper .dropdown-title,.dropdown-wrapper .mobile-dropdown-title{display:block;font-size:.9rem;font-family:inherit;cursor:inherit;padding:inherit;line-height:1.4rem;background:0 0;border:0;font-weight:500;color:var(--c-text)}.dropdown-wrapper .mobile-dropdown-title{display:none;font-weight:600;font-size:inherit}.dropdown-wrapper .dropdown-title:hover,.dropdown-wrapper .mobile-dropdown-title:hover{border-color:transparent}.dropdown-wrapper .dropdown-title .arrow,.dropdown-wrapper .mobile-dropdown-title .arrow{vertical-align:middle;margin-top:-1px;margin-left:.4rem}.dropdown-wrapper .mobile-dropdown-title:hover{color:var(--c-text-accent)}.dropdown-wrapper .nav-dropdown .dropdown-item{color:inherit;line-height:1.7rem}.dropdown-wrapper .nav-dropdown .dropdown-item .dropdown-subtitle{margin:.45rem 0 0;border-top:1px solid var(--c-border);padding:1rem 0 .45rem;font-size:.9rem}.dropdown-wrapper .nav-dropdown .dropdown-item .dropdown-subtitle>span{padding:0 1.5rem 0 1.25rem}.dropdown-wrapper .nav-dropdown .dropdown-item .dropdown-subtitle>a{font-weight:inherit}.dropdown-wrapper .nav-dropdown .dropdown-item .dropdown-subitem-wrapper{padding:0;list-style:none}.dropdown-wrapper .nav-dropdown .dropdown-item .dropdown-subitem-wrapper .dropdown-subitem{font-size:.9em}.dropdown-wrapper .nav-dropdown .dropdown-item a{display:block;line-height:1.7rem;position:relative;border-bottom:none;font-weight:400;margin-bottom:0;padding:0 1.5rem 0 1.25rem}.dropdown-wrapper .nav-dropdown .dropdown-item a.router-link-active,.dropdown-wrapper .nav-dropdown .dropdown-item a:hover,.navbar-links a.router-link-active,.navbar-links a:hover{color:var(--c-text-accent)}.dropdown-wrapper .nav-dropdown .dropdown-item a.router-link-active::after{content:"";width:0;height:0;border-left:5px solid var(--c-text-accent);border-top:3px solid transparent;border-bottom:3px solid transparent;position:absolute;top:calc(50% - 2px);left:9px}.dropdown-wrapper .nav-dropdown .dropdown-item:first-child .dropdown-subtitle{margin-top:0;padding-top:0;border-top:0}@media (max-width:719px){.dropdown-wrapper.open .dropdown-title,.dropdown-wrapper.open .mobile-dropdown-title{margin-bottom:.5rem}.dropdown-wrapper .dropdown-title{display:none}.dropdown-wrapper .mobile-dropdown-title{display:block}.dropdown-wrapper .nav-dropdown{transition:height .1s ease-out;overflow:hidden}.dropdown-wrapper .nav-dropdown .dropdown-item .dropdown-subtitle{border-top:0;margin-top:0;padding-top:0;padding-bottom:0;font-size:15px;line-height:2rem}.dropdown-wrapper .nav-dropdown .dropdown-item>a{font-size:15px;line-height:2rem}.dropdown-wrapper .nav-dropdown .dropdown-item .dropdown-subitem{font-size:14px;padding-left:1rem}}@media (min-width:720px){.dropdown-wrapper{height:1.8rem}.dropdown-wrapper.open .nav-dropdown,.dropdown-wrapper:hover .nav-dropdown{display:block!important}.dropdown-wrapper.open:blur{display:none}.dropdown-wrapper .nav-dropdown{display:none;height:auto!important;box-sizing:border-box;max-height:calc(100vh - 2.7rem);overflow-y:auto;position:absolute;top:100%;right:0;background-color:var(--c-bg-navbar);padding:.6rem 0;border:1px solid var(--c-border);border-bottom-color:var(--c-border-dark);text-align:left;border-radius:.25rem;white-space:nowrap;margin:0}}.dropdown-enter-from,.dropdown-leave-to{height:0!important}.home{padding:var(--navbar-height) 2rem 0;max-width:var(--homepage-width);margin:0 auto;display:block}.home .hero{text-align:center}.home .hero img{max-width:100%;max-height:280px;display:block;margin:3rem auto 1.5rem}.home .hero h1{font-size:3rem}.home .hero .actions,.home .hero .description,.home .hero h1{margin:1.8rem auto}.home .hero .description{max-width:35rem;font-size:1.6rem;line-height:1.3;color:var(--c-text-lightest)}.home .hero .action-button{display:inline-block;font-size:1.2rem;padding:.8rem 1.6rem;border-width:2px;border-style:solid;border-radius:4px;transition:background-color var(--t-color);box-sizing:border-box}.home .hero .action-button:not(:first-child){margin-left:1.5rem}.home .hero .action-button.primary{color:var(--c-bg);background-color:var(--c-brand);border-color:var(--c-brand)}.home .hero .action-button.primary:hover{background-color:var(--c-brand-light)}.home .hero .action-button.secondary{color:var(--c-brand);background-color:var(--c-bg);border-color:var(--c-brand)}.home .hero .action-button.secondary:hover{color:var(--c-bg);background-color:var(--c-brand-light)}.home .features{border-top:1px solid var(--c-border);transition:border-color var(--t-color);padding:1.2rem 0;margin-top:2.5rem;display:flex;flex-wrap:wrap;align-items:flex-start;align-content:stretch;justify-content:space-between}.home .feature{flex-grow:1;flex-basis:30%;max-width:30%}.home .feature h2{font-size:1.4rem;font-weight:500;border-bottom:none;padding-bottom:0;color:var(--c-text-light)}.home .feature p,.home .footer{color:var(--c-text-lighter)}.home .footer{padding:2.5rem;border-top:1px solid var(--c-border);text-align:center;transition:border-color var(--t-color)}@media (max-width:719px){.home .features{flex-direction:column}.home .feature{max-width:100%;padding:0 2.5rem}}@media (max-width:419px){.home{padding-left:1.5rem;padding-right:1.5rem}.home .hero img{max-height:210px;margin:2rem auto 1.2rem}.home .hero h1{font-size:2rem}.home .hero .actions,.home .hero .description,.home .hero h1{margin:1.2rem auto}.home .hero .description{font-size:1.2rem}.home .hero .action-button{font-size:1rem;padding:.6rem 1.2rem}.home .feature h2{font-size:1.25rem}}.theme-default-content:not(.custom){max-width:var(--content-width);margin:0 auto;padding:2rem 2.5rem}@media (max-width:959px){.theme-default-content:not(.custom){padding:2rem}}@media (max-width:419px){.theme-default-content:not(.custom){padding:1.5rem}}.page{padding-top:var(--navbar-height);padding-left:var(--sidebar-width)}.navbar,.sidebar{position:fixed;left:0;box-sizing:border-box}.navbar{z-index:20;top:0;right:0;height:var(--navbar-height);border-bottom:1px solid var(--c-border);background-color:var(--c-bg-navbar);transition:background-color var(--t-color),border-color var(--t-color)}.sidebar{font-size:16px;width:var(--sidebar-width);z-index:10;margin:0;top:var(--navbar-height);bottom:0;border-right:1px solid var(--c-border);overflow-y:auto;scrollbar-width:thin;scrollbar-color:var(--c-brand) var(--c-border);background-color:var(--c-bg-sidebar);transition:transform var(--t-transform),background-color var(--t-color),border-color var(--t-color)}.sidebar::-webkit-scrollbar{width:7px}.sidebar::-webkit-scrollbar-track{background-color:var(--c-border)}.sidebar::-webkit-scrollbar-thumb{background-color:var(--c-brand)}.sidebar-mask{position:fixed;z-index:9;top:0;left:0;width:100vw;height:100vh;display:none}.theme-container.sidebar-open .sidebar-mask{display:block}.theme-container.sidebar-open .navbar>.toggle-sidebar-button .icon span:nth-child(1){transform:rotate(45deg) translate3d(5.5px,5.5px,0)}.theme-container.sidebar-open .navbar>.toggle-sidebar-button .icon span:nth-child(2){transform:scale3d(0,1,1)}.theme-container.sidebar-open .navbar>.toggle-sidebar-button .icon span:nth-child(3){transform:rotate(-45deg) translate3d(6px,-6px,0)}.theme-container.sidebar-open .navbar>.toggle-sidebar-button .icon span:nth-child(1),.theme-container.sidebar-open .navbar>.toggle-sidebar-button .icon span:nth-child(3){transform-origin:center}.theme-container.no-navbar .theme-default-content:not(.custom)>h1,.theme-container.no-navbar h2,.theme-container.no-navbar h3,.theme-container.no-navbar h4,.theme-container.no-navbar h5,.theme-container.no-navbar h6{margin-top:1.5rem;padding-top:0}.theme-container.no-navbar .page{padding-top:0}.theme-container.no-navbar .sidebar{top:0}@media (min-width:720px){.theme-container.no-sidebar .sidebar{display:none}.theme-container.no-sidebar .page{padding-left:0}}.theme-default-content:not(.custom)>h1,.theme-default-content:not(.custom)>h2,.theme-default-content:not(.custom)>h3,.theme-default-content:not(.custom)>h4,.theme-default-content:not(.custom)>h5,.theme-default-content:not(.custom)>h6{margin-top:calc(.5rem - var(--navbar-height));padding-top:calc(1rem + var(--navbar-height));margin-bottom:0}.theme-default-content:not(.custom)>h1:first-child,.theme-default-content:not(.custom)>h2:first-child,.theme-default-content:not(.custom)>h3:first-child,.theme-default-content:not(.custom)>h4:first-child,.theme-default-content:not(.custom)>h5:first-child,.theme-default-content:not(.custom)>h6:first-child{margin-bottom:1rem}.theme-default-content:not(.custom)>h1:first-child+.custom-container,.theme-default-content:not(.custom)>h1:first-child+p,.theme-default-content:not(.custom)>h1:first-child+pre,.theme-default-content:not(.custom)>h2:first-child+.custom-container,.theme-default-content:not(.custom)>h2:first-child+p,.theme-default-content:not(.custom)>h2:first-child+pre,.theme-default-content:not(.custom)>h3:first-child+.custom-container,.theme-default-content:not(.custom)>h3:first-child+p,.theme-default-content:not(.custom)>h3:first-child+pre,.theme-default-content:not(.custom)>h4:first-child+.custom-container,.theme-default-content:not(.custom)>h4:first-child+p,.theme-default-content:not(.custom)>h4:first-child+pre,.theme-default-content:not(.custom)>h5:first-child+.custom-container,.theme-default-content:not(.custom)>h5:first-child+p,.theme-default-content:not(.custom)>h5:first-child+pre,.theme-default-content:not(.custom)>h6:first-child+.custom-container,.theme-default-content:not(.custom)>h6:first-child+p,.theme-default-content:not(.custom)>h6:first-child+pre{margin-top:2rem}.theme-default-content:not(.custom){padding-top:0}.theme-default-content:not(.custom) a:hover{text-decoration:underline}.theme-default-content:not(.custom) img{max-width:100%}.theme-default-content.custom{padding:0;margin:0}.theme-default-content.custom img{max-width:100%}@media (max-width:959px){.sidebar{font-size:15px;width:var(--sidebar-width-mobile)}.page{padding-left:var(--sidebar-width-mobile)}}@media (max-width:719px){.sidebar{top:0;padding-top:var(--navbar-height);transform:translateX(-100%)}.page{padding-left:0}.theme-container.sidebar-open .sidebar{transform:translateX(0)}.theme-container.no-navbar .sidebar{padding-top:0}}@media (max-width:419px){h1{font-size:1.9rem}}.navbar{--navbar-line-height:calc( var(--navbar-height) - 2 * var(--navbar-padding-v) );padding:var(--navbar-padding-v) var(--navbar-padding-h);line-height:var(--navbar-line-height)}.navbar .logo{height:var(--navbar-line-height);margin-right:var(--navbar-padding-v);vertical-align:top}.navbar .site-name{font-size:1.3rem;font-weight:600;color:var(--c-text);position:relative}.navbar .navbar-links-wrapper{display:flex;position:absolute;box-sizing:border-box;top:var(--navbar-padding-v);right:var(--navbar-padding-h);height:var(--navbar-line-height);padding-left:var(--navbar-padding-h);white-space:nowrap;font-size:.9rem}.navbar .navbar-links-wrapper .search-box{flex:0 0 auto;vertical-align:top}@media (max-width:719px){.navbar{padding-left:4rem}.navbar .can-hide{display:none}.navbar .site-name{width:calc(100vw - 9.4rem);overflow:hidden;white-space:nowrap;text-overflow:ellipsis}}.navbar-links,.navbar-links a{display:inline-block}.navbar-links a{line-height:1.4rem;color:inherit}.navbar-links .navbar-links-item{position:relative;display:inline-block;margin-left:1.5rem;line-height:var(--navbar-line-height)}.navbar-links .navbar-links-item:first-child{margin-left:0}@media (max-width:719px){.navbar-links .navbar-links-item{margin-left:0}}@media (min-width:719px){.navbar-links a.router-link-active,.navbar-links a:hover{color:var(--c-text)}.navbar-links-item>a:not(.external).router-link-active,.navbar-links-item>a:not(.external):hover{margin-bottom:-2px;border-bottom:2px solid var(--c-text-accent)}}.toggle-sidebar-button{position:absolute;top:.6rem;left:1rem;display:none;padding:.6rem;cursor:pointer}.toggle-sidebar-button .icon{display:flex;flex-direction:column;justify-content:center;align-items:center;width:1.25rem;height:1.25rem;cursor:inherit}.toggle-sidebar-button .icon span{display:inline-block;width:100%;height:2px;border-radius:2px;background-color:var(--c-text);transition:transform var(--t-transform)}.toggle-sidebar-button .icon span:nth-child(2){margin:6px 0}@media screen and (max-width:719px){.toggle-sidebar-button{display:block}}.toggle-dark-button{display:flex;margin:auto;margin-left:1rem;border:0;background:0 0;color:var(--c-text);opacity:.8;cursor:pointer}.toggle-dark-button:hover{opacity:1}.toggle-dark-button .icon{width:1.25rem;height:1.25rem}.page-meta,.page-nav{max-width:var(--content-width);margin:0 auto;padding:2rem 2.5rem}@media (max-width:959px){.page-meta,.page-nav{padding:2rem}}@media (max-width:419px){.page-meta,.page-nav{padding:1.5rem}}.page{padding-bottom:2rem;display:block}.page-meta{padding-top:1rem;padding-bottom:1rem;overflow:auto}.page-meta .meta-item{cursor:default;margin-top:.8rem}.page-meta .meta-item .meta-item-label{font-weight:500;color:var(--c-text-lighter)}.page-meta .meta-item .meta-item-info{font-weight:400;color:var(--c-text-quote)}.page-meta .edit-link{display:inline-block;margin-right:.25rem}.page-meta .last-updated{float:right}@media (max-width:719px){.page-meta .last-updated{font-size:.8em;float:none}.page-meta .contributors{font-size:.8em}}.page-nav{padding-top:1rem;padding-bottom:0}.page-nav .inner{min-height:2rem;margin-top:0;border-top:1px solid var(--c-border);transition:border-color var(--t-color);padding-top:1rem;overflow:auto}.page-nav .next{float:right}.sidebar ul{padding:0;margin:0;list-style-type:none}.sidebar a{display:inline-block}.sidebar .navbar-links{display:none;border-bottom:1px solid var(--c-border);transition:border-color var(--t-color);padding:.5rem 0 .75rem}.sidebar .navbar-links a{font-weight:600}.sidebar .navbar-links .navbar-links-item{display:block;line-height:1.25rem;font-size:1.1em;padding:.5rem 0 .5rem 1.5rem}.sidebar .sidebar-links{padding:1.5rem 0}.sidebar .sidebar-links>li:not(:first-child),.sidebar-links>.sidebar-item:not(.sidebar-heading):not(:first-child){margin-top:.75rem}.sidebar .sidebar-links .sidebar-sub-items{padding-left:1rem;font-size:.95em}@media (max-width:719px){.sidebar .navbar-links{display:block}.sidebar .navbar-links .dropdown-wrapper .nav-dropdown .dropdown-item a.router-link-active::after{top:calc(1rem - 2px)}.sidebar .sidebar-links{padding:1rem 0}}.sidebar-heading{color:var(--c-text);transition:color .15s ease;font-size:1.1em;font-weight:700;padding:.35rem 1.5rem .35rem 1.25rem;width:100%;box-sizing:border-box;margin:0;border-left:.25rem solid transparent}.sidebar-heading .arrow{position:relative;top:-.12em;left:.5em}.sidebar-item:not(.sidebar-heading){font-size:1em;font-weight:400;display:inline-block;color:var(--c-text);border-left:.25rem solid transparent;margin:0;padding:.35rem 1rem .35rem 2rem;line-height:1.4;width:100%;box-sizing:border-box}.sidebar-sub-items .sidebar-item:not(.sidebar-heading){padding:.25rem 1rem .25rem 1.75rem}.sidebar-item{cursor:default}a.sidebar-item{cursor:pointer}a.sidebar-item.active,a.sidebar-item:hover{color:var(--c-text-accent)}a.sidebar-item.active{font-weight:600;border-left-color:var(--c-text-accent)}a.sidebar-item.sidebar-heading.active{font-weight:700;border-left-color:transparent}.sidebar-sub-items a.sidebar-item.active{font-weight:500;border-left-color:transparent}.sr-only{position:absolute;width:1px;height:1px;padding:0;margin:-1px;overflow:hidden;clip:rect(0,0,0,0);white-space:nowrap;border-width:0;-webkit-user-select:none;-moz-user-select:none;-ms-user-select:none;user-select:none}.table-of-contents .badge{vertical-align:middle}.fade-slide-y-enter-active{transition:all .3s ease}.fade-slide-y-leave-active{transition:all .3s cubic-bezier(1,.5,.8,1)}.fade-slide-y-enter-from,.fade-slide-y-leave-to{transform:translateY(10px);opacity:0} +:root{--search-bg-color:#ffffff;--search-accent-color:#3eaf7c;--search-text-color:#2c3e50;--search-border-color:#eaecef;--search-item-text-color:#5d81a5;--search-item-focus-bg-color:#f3f4f5;--search-input-width:8rem;--search-result-width:20rem} +.search-box{display:inline-block;position:relative;margin-left:1rem}.search-box input{cursor:text;width:var(--search-input-width);height:2rem;color:var(--search-text-color);display:inline-block;border:1px solid var(--search-border-color);border-radius:2rem;font-size:.9rem;line-height:2rem;padding:0 .5rem 0 2rem;outline:0;transition:all ease .3s;background:var(--search-bg-color) url(/hitrix/assets/img/search.b017a09f.svg) .6rem .5rem no-repeat;background-size:1rem}.search-box input:focus{cursor:auto;border-color:var(--search-accent-color)}.search-box .suggestions{background:var(--search-bg-color);width:var(--search-result-width);position:absolute;top:2rem;right:0;border:1px solid var(--search-border-color);border-radius:6px;padding:.4rem;list-style-type:none}.search-box .suggestion{line-height:1.4;padding:.4rem .6rem;border-radius:4px;cursor:pointer}.search-box .suggestion.focus{background-color:var(--search-item-focus-bg-color)}.search-box .suggestion.focus a{color:var(--search-accent-color)}.search-box .suggestion a{white-space:normal;color:var(--search-item-text-color)}.search-box .suggestion .page-title{font-weight:600}.search-box .suggestion .page-header{font-size:.9em;margin-left:.25em}@media (max-width:720px){.search-box input{cursor:pointer;width:0;border-color:transparent;position:relative}.search-box input:focus{cursor:text;left:0;width:10rem}}@media (max-width:420px){.search-box input:focus{width:8rem}.search-box .suggestions{width:calc(100vw - 4rem);right:-.5rem}} +:root{--medium-zoom-z-index:100;--medium-zoom-bg-color:#ffffff;--medium-zoom-opacity:1} +.medium-zoom-overlay{background-color:var(--medium-zoom-bg-color)!important;z-index:var(--medium-zoom-z-index)}.medium-zoom-overlay~img{z-index:calc(var(--medium-zoom-z-index) + 1)}.medium-zoom--opened .medium-zoom-overlay{opacity:var(--medium-zoom-opacity)} +:root{--back-to-top-z-index:5;--back-to-top-color:#3eaf7c;--back-to-top-color-hover:#71cda3} +.back-to-top{cursor:pointer;position:fixed;bottom:2rem;right:2.5rem;width:2rem;height:1.2rem;background-color:var(--back-to-top-color);-webkit-mask:url(/hitrix/assets/img/back-to-top.8b37f773.svg) no-repeat;mask:url(/hitrix/assets/img/back-to-top.8b37f773.svg) no-repeat;z-index:var(--back-to-top-z-index)}.back-to-top:hover{background-color:var(--back-to-top-color-hover)}@media (max-width:959px){.back-to-top{display:none}}.back-to-top-enter-active,.back-to-top-leave-active{transition:opacity .3s}.back-to-top-enter-from,.back-to-top-leave-to{opacity:0} +:root{--nprogress-color:#29d;--nprogress-z-index:1031} +#nprogress{pointer-events:none}#nprogress .bar{background:var(--nprogress-color);position:fixed;z-index:var(--nprogress-z-index);top:0;left:0;width:100%;height:2px}#nprogress .peg{display:block;position:absolute;right:0;width:100px;height:100%;box-shadow:0 0 10px var(--nprogress-color),0 0 5px var(--nprogress-color);opacity:1;transform:rotate(3deg) translate(0,-4px)} diff --git a/assets/img/back-to-top.8b37f773.svg b/assets/img/back-to-top.8b37f773.svg new file mode 100644 index 00000000..83236781 --- /dev/null +++ b/assets/img/back-to-top.8b37f773.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/assets/img/search.b017a09f.svg b/assets/img/search.b017a09f.svg new file mode 100644 index 00000000..03d83913 --- /dev/null +++ b/assets/img/search.b017a09f.svg @@ -0,0 +1 @@ + diff --git a/assets/js/3293.43d80d8f.js b/assets/js/3293.43d80d8f.js new file mode 100644 index 00000000..dd848acb --- /dev/null +++ b/assets/js/3293.43d80d8f.js @@ -0,0 +1 @@ +"use strict";(self.webpackChunk=self.webpackChunk||[]).push([[3293],{3293:(e,l,t)=>{t.r(l),t.d(l,{default:()=>_e});var a=t(6252),n=t(3577),i=t(2262),u=t(9963),s=t(7621),r=t(2119),o=t(480);const c=["href","rel","target","aria-label"],v=(0,a.aZ)({inheritAttrs:!1}),d=(0,a.aZ)({...v,props:{item:{type:Object,required:!0}},setup:function(e){const l=e,t=(0,r.yj)(),u=(0,s.WF)(),{item:v}=(0,i.BK)(l),d=(0,i.Fl)((()=>(0,o.ak)(v.value.link))),p=(0,i.Fl)((()=>(0,o.B2)(v.value.link)||(0,o.R5)(v.value.link))),h=(0,i.Fl)((()=>{if(!p.value)return v.value.target?v.value.target:d.value?"_blank":void 0})),g=(0,i.Fl)((()=>"_blank"===h.value)),m=(0,i.Fl)((()=>!d.value&&!p.value&&!g.value)),k=(0,i.Fl)((()=>{if(!p.value)return v.value.rel?v.value.rel:g.value?"noopener noreferrer":void 0})),b=(0,i.Fl)((()=>v.value.ariaLabel||v.value.text)),w=(0,i.Fl)((()=>{const e=Object.keys(u.value.locales);return e.length?!e.some((e=>e===v.value.link)):"/"!==v.value.link})),f=(0,i.Fl)((()=>!!w.value&&t.path.startsWith(v.value.link))),U=(0,i.Fl)((()=>!!m.value&&(v.value.activeMatch?new RegExp(v.value.activeMatch).test(t.path):f.value)));return(e,l)=>{const t=(0,a.up)("RouterLink"),u=(0,a.up)("OutboundLink");return(0,i.SU)(m)?((0,a.wg)(),(0,a.j4)(t,(0,a.dG)({key:0,class:["nav-link",{"router-link-active":(0,i.SU)(U)}],to:(0,i.SU)(v).link,"aria-label":(0,i.SU)(b)},e.$attrs),{default:(0,a.w5)((()=>[(0,a.WI)(e.$slots,"before"),(0,a.Uk)(" "+(0,n.zw)((0,i.SU)(v).text)+" ",1),(0,a.WI)(e.$slots,"after")])),_:3},16,["class","to","aria-label"])):((0,a.wg)(),(0,a.iD)("a",(0,a.dG)({key:1,class:"nav-link external",href:(0,i.SU)(v).link,rel:(0,i.SU)(k),target:(0,i.SU)(h),"aria-label":(0,i.SU)(b)},e.$attrs),[(0,a.WI)(e.$slots,"before"),(0,a.Uk)(" "+(0,n.zw)((0,i.SU)(v).text)+" ",1),(0,i.SU)(g)?((0,a.wg)(),(0,a.j4)(u,{key:0})):(0,a.kq)("",!0),(0,a.WI)(e.$slots,"after")],16,c))}}}),p=["aria-labelledby"],h={class:"hero"},g=["src","alt"],m={key:1,id:"main-title"},k={key:2,class:"description"},b={key:3,class:"actions"},w={key:0,class:"features"},f={class:"theme-default-content custom"},U=["innerHTML"],S=["textContent"],y=(0,a.aZ)({setup(e){const l=(0,s.I2)(),t=(0,s.I5)(),u=(0,i.Fl)((()=>l.value.heroImage?(0,s.pJ)(l.value.heroImage):null)),r=(0,i.Fl)((()=>null===l.value.heroText?null:l.value.heroText||t.value.title||"Hello")),c=(0,i.Fl)((()=>l.value.heroAlt||r.value||"hero")),v=(0,i.Fl)((()=>null===l.value.tagline?null:l.value.tagline||t.value.description||"Welcome to your VuePress site")),y=(0,i.Fl)((()=>(0,o.kJ)(l.value.actions)?l.value.actions.map((({text:e,link:l,type:t="primary"})=>({text:e,link:l,type:t}))):[])),D=(0,i.Fl)((()=>(0,o.kJ)(l.value.features)?l.value.features:[])),F=(0,i.Fl)((()=>l.value.footer)),_=(0,i.Fl)((()=>l.value.footerHtml));return(e,l)=>{const t=(0,a.up)("Content");return(0,a.wg)(),(0,a.iD)("main",{class:"home","aria-labelledby":(0,i.SU)(r)?"main-title":void 0},[(0,a._)("header",h,[(0,i.SU)(u)?((0,a.wg)(),(0,a.iD)("img",{key:0,src:(0,i.SU)(u),alt:(0,i.SU)(c)},null,8,g)):(0,a.kq)("",!0),(0,i.SU)(r)?((0,a.wg)(),(0,a.iD)("h1",m,(0,n.zw)((0,i.SU)(r)),1)):(0,a.kq)("",!0),(0,i.SU)(v)?((0,a.wg)(),(0,a.iD)("p",k,(0,n.zw)((0,i.SU)(v)),1)):(0,a.kq)("",!0),(0,i.SU)(y).length?((0,a.wg)(),(0,a.iD)("p",b,[((0,a.wg)(!0),(0,a.iD)(a.HY,null,(0,a.Ko)((0,i.SU)(y),(e=>((0,a.wg)(),(0,a.j4)(d,{key:e.text,class:(0,n.C_)(["action-button",[e.type]]),item:e},null,8,["class","item"])))),128))])):(0,a.kq)("",!0)]),(0,i.SU)(D).length?((0,a.wg)(),(0,a.iD)("div",w,[((0,a.wg)(!0),(0,a.iD)(a.HY,null,(0,a.Ko)((0,i.SU)(D),(e=>((0,a.wg)(),(0,a.iD)("div",{key:e.title,class:"feature"},[(0,a._)("h2",null,(0,n.zw)(e.title),1),(0,a._)("p",null,(0,n.zw)(e.details),1)])))),128))])):(0,a.kq)("",!0),(0,a._)("div",f,[(0,a.Wm)(t)]),(0,i.SU)(F)?((0,a.wg)(),(0,a.iD)(a.HY,{key:1},[(0,i.SU)(_)?((0,a.wg)(),(0,a.iD)("div",{key:0,class:"footer",innerHTML:(0,i.SU)(F)},null,8,U)):((0,a.wg)(),(0,a.iD)("div",{key:1,class:"footer",textContent:(0,n.zw)((0,i.SU)(F))},null,8,S))],64)):(0,a.kq)("",!0)],8,p)}}});var D=t(2791);const F=e=>!(0,o.ak)(e)||/github\.com/.test(e)?"GitHub":/bitbucket\.org/.test(e)?"Bitbucket":/gitlab\.com/.test(e)?"GitLab":/gitee\.com/.test(e)?"Gitee":null,_={GitHub:":repo/edit/:branch/:path",GitLab:":repo/-/edit/:branch/:path",Gitee:":repo/edit/:branch/:path",Bitbucket:":repo/src/:branch/:path?mode=edit&spa=0&at=:branch&fileviewer=file-view-default"},W=(0,a.aZ)({setup(e){const l=e=>{e.style.height=e.scrollHeight+"px"},t=e=>{e.style.height=""};return(e,n)=>((0,a.wg)(),(0,a.j4)(u.uT,{name:"dropdown",onEnter:l,onAfterEnter:t,onBeforeLeave:l},{default:(0,a.w5)((()=>[(0,a.WI)(e.$slots,"default")])),_:3}))}}),x=["aria-label"],I={class:"title"},$=(0,a._)("span",{class:"arrow down"},null,-1),C=["aria-label"],L={class:"title"},z={class:"nav-dropdown"},H={class:"dropdown-subtitle"},j={key:1},q={class:"dropdown-subitem-wrapper"},M=(0,a.aZ)({props:{item:{type:Object,required:!0}},setup(e){const l=e,{item:t}=(0,i.BK)(l),s=(0,i.Fl)((()=>t.value.ariaLabel||t.value.text)),o=(0,i.iH)(!1),c=(0,r.yj)();(0,a.YP)((()=>c.path),(()=>{o.value=!1}));const v=e=>{const l=0===e.detail;o.value=!!l&&!o.value},p=(e,l)=>l[l.length-1]===e;return(e,l)=>((0,a.wg)(),(0,a.iD)("div",{class:(0,n.C_)(["dropdown-wrapper",{open:o.value}])},[(0,a._)("button",{class:"dropdown-title",type:"button","aria-label":(0,i.SU)(s),onClick:v},[(0,a._)("span",I,(0,n.zw)((0,i.SU)(t).text),1),$],8,x),(0,a._)("button",{class:"mobile-dropdown-title",type:"button","aria-label":(0,i.SU)(s),onClick:l[0]||(l[0]=e=>o.value=!o.value)},[(0,a._)("span",L,(0,n.zw)((0,i.SU)(t).text),1),(0,a._)("span",{class:(0,n.C_)(["arrow",o.value?"down":"right"])},null,2)],8,C),(0,a.Wm)(W,null,{default:(0,a.w5)((()=>[(0,a.wy)((0,a._)("ul",z,[((0,a.wg)(!0),(0,a.iD)(a.HY,null,(0,a.Ko)((0,i.SU)(t).children,((e,l)=>((0,a.wg)(),(0,a.iD)("li",{key:e.link||l,class:"dropdown-item"},[e.children?((0,a.wg)(),(0,a.iD)(a.HY,{key:0},[(0,a._)("h4",H,[e.link?((0,a.wg)(),(0,a.j4)(d,{key:0,item:e,onFocusout:l=>p(e,(0,i.SU)(t).children)&&0===e.children.length&&(o.value=!1)},null,8,["item","onFocusout"])):((0,a.wg)(),(0,a.iD)("span",j,(0,n.zw)(e.text),1))]),(0,a._)("ul",q,[((0,a.wg)(!0),(0,a.iD)(a.HY,null,(0,a.Ko)(e.children,(l=>((0,a.wg)(),(0,a.iD)("li",{key:l.link,class:"dropdown-subitem"},[(0,a.Wm)(d,{item:l,onFocusout:a=>p(l,e.children)&&p(e,(0,i.SU)(t).children)&&(o.value=!1)},null,8,["item","onFocusout"])])))),128))])],64)):((0,a.wg)(),(0,a.j4)(d,{key:1,item:e,onFocusout:l=>p(e,(0,i.SU)(t).children)&&(o.value=!1)},null,8,["item","onFocusout"]))])))),128))],512),[[u.F8,o.value]])])),_:1})],2))}}),T={key:0,class:"navbar-links"},B=(0,a.aZ)({setup(e){const l=e=>(0,o.HD)(e)?(0,D.sC)(e):e.children?{...e,children:e.children.map(l)}:e,t=(()=>{const e=(0,D.X6)();return(0,i.Fl)((()=>(e.value.navbar||[]).map(l)))})(),n=(()=>{const e=(0,r.tv)(),l=(0,s.I)(),t=(0,s.I5)(),a=(0,D.X6)();return(0,i.Fl)((()=>{var n,i;const u=Object.keys(t.value.locales);if(u.length<2)return[];const s=e.currentRoute.value.path,r=e.currentRoute.value.fullPath;return[{text:null!=(n=a.value.selectLanguageText)?n:"unkown language",ariaLabel:null!=(i=a.value.selectLanguageAriaLabel)?i:"unkown language",children:u.map((n=>{var i,u,o,c,v,d;const p=null!=(u=null==(i=t.value.locales)?void 0:i[n])?u:{},h=null!=(c=null==(o=a.value.locales)?void 0:o[n])?c:{},g=`${p.lang}`,m=null!=(v=h.selectLanguageName)?v:g;let k;if(g===t.value.lang)k=r;else{const t=s.replace(l.value,n);k=e.getRoutes().some((e=>e.path===t))?t:null!=(d=h.home)?d:n}return{text:m,link:k}}))}]}))})(),u=(()=>{const e=(0,D.X6)(),l=(0,i.Fl)((()=>e.value.repo)),t=(0,i.Fl)((()=>l.value?F(l.value):null)),a=(0,i.Fl)((()=>l.value&&!(0,o.ak)(l.value)?`https://github.com/${l.value}`:l.value)),n=(0,i.Fl)((()=>a.value?e.value.repoLabel?e.value.repoLabel:null===t.value?"Source":t.value:null));return(0,i.Fl)((()=>a.value&&n.value?[{text:n.value,link:a.value}]:[]))})(),c=(0,i.Fl)((()=>[...t.value,...n.value,...u.value]));return(e,l)=>(0,i.SU)(c).length?((0,a.wg)(),(0,a.iD)("nav",T,[((0,a.wg)(!0),(0,a.iD)(a.HY,null,(0,a.Ko)((0,i.SU)(c),(e=>((0,a.wg)(),(0,a.iD)("div",{key:e.text,class:"navbar-links-item"},[e.children?((0,a.wg)(),(0,a.j4)(M,{key:0,item:e},null,8,["item"])):((0,a.wg)(),(0,a.j4)(d,{key:1,item:e},null,8,["item"]))])))),128))])):(0,a.kq)("",!0)}}),R=["title"],Y={class:"icon",focusable:"false",viewBox:"0 0 32 32"},Z=[(0,a.uE)('',9)],X={class:"icon",focusable:"false",viewBox:"0 0 32 32"},P=[(0,a._)("path",{d:"M13.502 5.414a15.075 15.075 0 0 0 11.594 18.194a11.113 11.113 0 0 1-7.975 3.39c-.138 0-.278.005-.418 0a11.094 11.094 0 0 1-3.2-21.584M14.98 3a1.002 1.002 0 0 0-.175.016a13.096 13.096 0 0 0 1.825 25.981c.164.006.328 0 .49 0a13.072 13.072 0 0 0 10.703-5.555a1.01 1.01 0 0 0-.783-1.565A13.08 13.08 0 0 1 15.89 4.38A1.015 1.015 0 0 0 14.98 3z",fill:"currentColor"},null,-1)],E=(0,a.aZ)({setup(e){const l=(0,D.X6)(),t=(0,D.vs)(),n=()=>{t.value=!t.value};return(e,s)=>((0,a.wg)(),(0,a.iD)("button",{class:"toggle-dark-button",title:(0,i.SU)(l).toggleDarkMode,onClick:n},[(0,a.wy)(((0,a.wg)(),(0,a.iD)("svg",Y,Z,512)),[[u.F8,!(0,i.SU)(t)]]),(0,a.wy)(((0,a.wg)(),(0,a.iD)("svg",X,P,512)),[[u.F8,(0,i.SU)(t)]])],8,R))}}),K=["title"],V=[(0,a._)("div",{class:"icon","aria-hidden":"true"},[(0,a._)("span"),(0,a._)("span"),(0,a._)("span")],-1)],G=(0,a.aZ)({emits:["toggle"],setup(e){const l=(0,D.X6)();return(e,t)=>((0,a.wg)(),(0,a.iD)("div",{class:"toggle-sidebar-button",title:(0,i.SU)(l).toggleSidebar,"aria-expanded":"false",role:"button",tabindex:"0",onClick:t[0]||(t[0]=l=>e.$emit("toggle"))},V,8,K))}}),N=["src","alt"],A=(0,a.aZ)({emits:["toggle-sidebar"],setup(e){const l=(0,s.I)(),t=(0,s.I5)(),u=(0,D.X6)(),r=(0,D.vs)(),o=(0,i.iH)(null),c=(0,i.iH)(null),v=(0,i.Fl)((()=>u.value.home||l.value)),d=(0,i.Fl)((()=>r.value&&void 0!==u.value.logoDark?u.value.logoDark:u.value.logo)),p=(0,i.Fl)((()=>t.value.title)),h=(0,i.iH)(0),g=(0,i.Fl)((()=>h.value?{maxWidth:h.value+"px"}:{})),m=(0,i.Fl)((()=>u.value.darkMode));function k(e,l){var t,a,n;const i=null==(n=null==(a=null==(t=null==e?void 0:e.ownerDocument)?void 0:t.defaultView)?void 0:a.getComputedStyle(e,null))?void 0:n[l],u=Number.parseInt(i,10);return Number.isNaN(u)?0:u}return(0,a.bv)((()=>{const e=k(o.value,"paddingLeft")+k(o.value,"paddingRight"),l=()=>{var l;window.innerWidth<=719?h.value=0:h.value=o.value.offsetWidth-e-((null==(l=c.value)?void 0:l.offsetWidth)||0)};l(),window.addEventListener("resize",l,!1),window.addEventListener("orientationchange",l,!1)})),(e,l)=>{const t=(0,a.up)("RouterLink"),u=(0,a.up)("NavbarSearch");return(0,a.wg)(),(0,a.iD)("header",{ref:(e,l)=>{l.navbar=e,o.value=e},class:"navbar"},[(0,a.Wm)(G,{onToggle:l[0]||(l[0]=l=>e.$emit("toggle-sidebar"))}),(0,a._)("span",{ref:(e,l)=>{l.siteBrand=e,c.value=e}},[(0,a.Wm)(t,{to:(0,i.SU)(v)},{default:(0,a.w5)((()=>[(0,i.SU)(d)?((0,a.wg)(),(0,a.iD)("img",{key:0,class:"logo",src:(0,i.SU)(s.pJ)((0,i.SU)(d)),alt:(0,i.SU)(p)},null,8,N)):(0,a.kq)("",!0),(0,i.SU)(p)?((0,a.wg)(),(0,a.iD)("span",{key:1,class:(0,n.C_)(["site-name",{"can-hide":(0,i.SU)(d)}])},(0,n.zw)((0,i.SU)(p)),3)):(0,a.kq)("",!0)])),_:1},8,["to"])],512),(0,a._)("div",{class:"navbar-links-wrapper",style:(0,n.j5)((0,i.SU)(g))},[(0,a.WI)(e.$slots,"before"),(0,a.Wm)(B,{class:"can-hide"}),(0,a.WI)(e.$slots,"after"),(0,i.SU)(m)?((0,a.wg)(),(0,a.j4)(E,{key:0})):(0,a.kq)("",!0),(0,a.Wm)(u)],4)],512)}}}),O={class:"page-meta"},J={key:0,class:"meta-item edit-link"},Q={key:1,class:"meta-item last-updated"},ee={class:"meta-item-label"},le={class:"meta-item-info"},te={key:2,class:"meta-item contributors"},ae={class:"meta-item-label"},ne={class:"meta-item-info"},ie=["title"],ue=(0,a.Uk)(", "),se=(0,a.aZ)({setup(e){const l=(0,D.X6)(),t=(()=>{const e=(0,D.X6)(),l=(0,s.Vi)(),t=(0,s.I2)();return(0,i.Fl)((()=>{var a,n;if(null!=(n=null!=(a=t.value.editLink)?a:e.value.editLink)&&!n)return null;const{repo:i,docsRepo:u=i,docsBranch:s="main",docsDir:r="",editLinkText:c}=e.value;if(!u)return null;const v=(({docsRepo:e,docsBranch:l,docsDir:t,filePathRelative:a,editLinkPattern:n})=>{const i=F(e);let u;return n?u=n:null!==i&&(u=_[i]),u?u.replace(/:repo/,(0,o.ak)(e)?e:`https://github.com/${e}`).replace(/:branch/,l).replace(/:path/,(0,o.FY)(`${(0,o.U1)(t)}/${a}`)):null})({docsRepo:u,docsBranch:s,docsDir:r,filePathRelative:l.value.filePathRelative,editLinkPattern:e.value.editLinkPattern});return v?{text:null!=c?c:"Edit this page",link:v}:null}))})(),u=(()=>{const e=(0,s.I5)(),l=(0,D.X6)(),t=(0,s.Vi)(),a=(0,s.I2)();return(0,i.Fl)((()=>{var n,i,u,s;return(null==(i=null!=(n=a.value.lastUpdated)?n:l.value.lastUpdated)||i)&&(null==(u=t.value.git)?void 0:u.updatedTime)?new Date(null==(s=t.value.git)?void 0:s.updatedTime).toLocaleString(e.value.lang):null}))})(),r=(()=>{const e=(0,D.X6)(),l=(0,s.Vi)(),t=(0,s.I2)();return(0,i.Fl)((()=>{var a,n,i,u;return null!=(n=null!=(a=t.value.contributors)?a:e.value.contributors)&&!n||null==(u=null==(i=l.value.git)?void 0:i.contributors)?null:u}))})();return(e,s)=>((0,a.wg)(),(0,a.iD)("footer",O,[(0,i.SU)(t)?((0,a.wg)(),(0,a.iD)("div",J,[(0,a.Wm)(d,{class:"meta-item-label",item:(0,i.SU)(t)},null,8,["item"])])):(0,a.kq)("",!0),(0,i.SU)(u)?((0,a.wg)(),(0,a.iD)("div",Q,[(0,a._)("span",ee,(0,n.zw)((0,i.SU)(l).lastUpdatedText)+": ",1),(0,a._)("span",le,(0,n.zw)((0,i.SU)(u)),1)])):(0,a.kq)("",!0),(0,i.SU)(r)&&(0,i.SU)(r).length?((0,a.wg)(),(0,a.iD)("div",te,[(0,a._)("span",ae,(0,n.zw)((0,i.SU)(l).contributorsText)+": ",1),(0,a._)("span",ne,[((0,a.wg)(!0),(0,a.iD)(a.HY,null,(0,a.Ko)((0,i.SU)(r),((e,l)=>((0,a.wg)(),(0,a.iD)(a.HY,{key:l},[(0,a._)("span",{class:"contributor",title:`email: ${e.email}`},(0,n.zw)(e.name),9,ie),l!==(0,i.SU)(r).length-1?((0,a.wg)(),(0,a.iD)(a.HY,{key:0},[ue],64)):(0,a.kq)("",!0)],64)))),128))])])):(0,a.kq)("",!0)]))}}),re={key:0,class:"page-nav"},oe={class:"inner"},ce={key:0,class:"prev"},ve=(0,a.Uk)(" ← "),de={key:1,class:"next"},pe=(0,a.Uk)(" → "),he=(0,a.aZ)({setup(e){const l=e=>!1===e?null:(0,o.HD)(e)?(0,D.sC)(e):!!(0,o.PO)(e)&&e,t=(e,l,a)=>{const n=e.findIndex((e=>e.link===l));if(-1!==n){const l=e[n+a];return(null==l?void 0:l.link)?l:null}for(const n of e)if(n.children){const e=t(n.children,l,a);if(e)return e}return null},n=(0,s.I2)(),u=(0,D.VU)(),c=(0,r.yj)(),v=(0,i.Fl)((()=>{const e=l(n.value.prev);return!1!==e?e:t(u.value,c.path,-1)})),p=(0,i.Fl)((()=>{const e=l(n.value.next);return!1!==e?e:t(u.value,c.path,1)}));return(e,l)=>(0,i.SU)(v)||(0,i.SU)(p)?((0,a.wg)(),(0,a.iD)("nav",re,[(0,a._)("p",oe,[(0,i.SU)(v)?((0,a.wg)(),(0,a.iD)("span",ce,[ve,(0,a.Wm)(d,{item:(0,i.SU)(v)},null,8,["item"])])):(0,a.kq)("",!0),(0,i.SU)(p)?((0,a.wg)(),(0,a.iD)("span",de,[(0,a.Wm)(d,{item:(0,i.SU)(p)},null,8,["item"]),pe])):(0,a.kq)("",!0)])])):(0,a.kq)("",!0)}}),ge={class:"page"},me={class:"theme-default-content"},ke=(0,a.aZ)({setup:e=>(e,l)=>{const t=(0,a.up)("Content");return(0,a.wg)(),(0,a.iD)("main",ge,[(0,a.WI)(e.$slots,"top"),(0,a._)("div",me,[(0,a.Wm)(t)]),(0,a.Wm)(se),(0,a.Wm)(he),(0,a.WI)(e.$slots,"bottom")])}}),be=e=>decodeURI(e).replace(/#.*$/,"").replace(/(index)?\.(md|html)$/,""),we=(e,l)=>!!((e,l)=>void 0!==l&&(e.hash===l||be(e.path)===be(l)))(e,l.link)||!!l.children&&l.children.some((l=>we(e,l))),fe=(e,l)=>e.link?(0,a.h)(d,{...l,item:e}):(0,a.h)("p",l,e.text),Ue=(e,l)=>{var t;return(null===(t=e.children)||void 0===t?void 0:t.length)?(0,a.h)("ul",{class:{"sidebar-sub-items":l>0}},e.children.map((e=>(0,a.h)("li",(0,a.h)(Se,{item:e,depth:l+1}))))):null},Se=({item:e,depth:l=0})=>{const t=(0,r.yj)(),a=we(t,e);return[fe(e,{class:{"sidebar-heading":0===l,"sidebar-item":!0,active:a}}),Ue(e,l)]};Se.displayName="SidebarChild",Se.props={item:{type:Object,required:!0},depth:{type:Number,required:!1}};const ye={class:"sidebar"},De={class:"sidebar-links"},Fe=(0,a.aZ)({setup(e){const l=(0,D.VU)();return(e,t)=>((0,a.wg)(),(0,a.iD)("aside",ye,[(0,a.Wm)(B),(0,a.WI)(e.$slots,"top"),(0,a._)("ul",De,[((0,a.wg)(!0),(0,a.iD)(a.HY,null,(0,a.Ko)((0,i.SU)(l),(e=>((0,a.wg)(),(0,a.j4)((0,i.SU)(Se),{key:e.link||e.text,item:e},null,8,["item"])))),128))]),(0,a.WI)(e.$slots,"bottom")]))}}),_e=(0,a.aZ)({setup(e){const l=(0,s.Vi)(),t=(0,s.I2)(),o=(0,D.X6)(),c=(0,i.Fl)((()=>!1!==t.value.navbar&&!1!==o.value.navbar)),v=(0,D.VU)(),d=(0,i.iH)(!1),p=e=>{d.value="boolean"==typeof e?e:!d.value},h={x:0,y:0},g=e=>{h.x=e.changedTouches[0].clientX,h.y=e.changedTouches[0].clientY},m=e=>{const l=e.changedTouches[0].clientX-h.x,t=e.changedTouches[0].clientY-h.y;Math.abs(l)>Math.abs(t)&&Math.abs(l)>40&&(l>0&&h.x<=80?p(!0):p(!1))},k=(0,i.Fl)((()=>[{"no-navbar":!c.value,"no-sidebar":!v.value.length,"sidebar-open":d.value},t.value.pageClass]));let b;(0,a.bv)((()=>{const e=(0,r.tv)();b=e.afterEach((()=>{p(!1)}))})),(0,a.Ah)((()=>{b()}));const w=(0,D.P$)(),f=w.resolve,U=w.pending;return(e,s)=>((0,a.wg)(),(0,a.iD)("div",{class:(0,n.C_)(["theme-container",(0,i.SU)(k)]),onTouchstart:g,onTouchend:m},[(0,a.WI)(e.$slots,"navbar",{},(()=>[(0,i.SU)(c)?((0,a.wg)(),(0,a.j4)(A,{key:0,onToggleSidebar:p},{before:(0,a.w5)((()=>[(0,a.WI)(e.$slots,"navbar-before")])),after:(0,a.w5)((()=>[(0,a.WI)(e.$slots,"navbar-after")])),_:3})):(0,a.kq)("",!0)])),(0,a._)("div",{class:"sidebar-mask",onClick:s[0]||(s[0]=e=>p(!1))}),(0,a.WI)(e.$slots,"sidebar",{},(()=>[(0,a.Wm)(Fe,null,{top:(0,a.w5)((()=>[(0,a.WI)(e.$slots,"sidebar-top")])),bottom:(0,a.w5)((()=>[(0,a.WI)(e.$slots,"sidebar-bottom")])),_:3})])),(0,a.WI)(e.$slots,"page",{},(()=>[(0,i.SU)(t).home?((0,a.wg)(),(0,a.j4)(y,{key:0})):((0,a.wg)(),(0,a.j4)(u.uT,{key:1,name:"fade-slide-y",mode:"out-in",onBeforeEnter:(0,i.SU)(f),onBeforeLeave:(0,i.SU)(U)},{default:(0,a.w5)((()=>[(0,a.Wm)(ke,{key:(0,i.SU)(l).path},{top:(0,a.w5)((()=>[(0,a.WI)(e.$slots,"page-top")])),bottom:(0,a.w5)((()=>[(0,a.WI)(e.$slots,"page-bottom")])),_:3})])),_:3},8,["onBeforeEnter","onBeforeLeave"]))]))],34))}})}}]); \ No newline at end of file diff --git a/assets/js/8491.d9869bda.js b/assets/js/8491.d9869bda.js new file mode 100644 index 00000000..3203213a --- /dev/null +++ b/assets/js/8491.d9869bda.js @@ -0,0 +1 @@ +"use strict";(self.webpackChunk=self.webpackChunk||[]).push([[8491],{8491:(e,t,l)=>{l.r(t),l.d(t,{default:()=>d});var u=l(6252),n=l(3577),o=l(2262),a=l(7621),c=l(2791);const s={class:"theme-container"},r={class:"theme-default-content"},h=(0,u._)("h1",null,"404",-1),d=(0,u.aZ)({setup(e){var t,l,d;const k=(0,a.I)(),v=(0,c.X6)(),m=null!=(t=v.value.notFound)?t:["Not Found"],f=null!=(l=v.value.home)?l:k.value,i=null!=(d=v.value.backToHome)?d:"Back to home";return(e,t)=>{const l=(0,u.up)("RouterLink");return(0,u.wg)(),(0,u.iD)("div",s,[(0,u._)("div",r,[h,(0,u._)("blockquote",null,(0,n.zw)(m[Math.floor(Math.random()*m.length)]),1),(0,u.Wm)(l,{to:(0,o.SU)(f)},{default:(0,u.w5)((()=>[(0,u.Uk)((0,n.zw)((0,o.SU)(i)),1)])),_:1},8,["to"])])])}}})}}]); \ No newline at end of file diff --git a/assets/js/8567.307c9fba.js b/assets/js/8567.307c9fba.js new file mode 100644 index 00000000..acfc1e5e --- /dev/null +++ b/assets/js/8567.307c9fba.js @@ -0,0 +1,2 @@ +/*! For license information please see 8567.307c9fba.js.LICENSE.txt */ +(self.webpackChunk=self.webpackChunk||[]).push([[8567],{2262:(e,t,n)=>{"use strict";n.d(t,{Bj:()=>l,qq:()=>b,Fl:()=>Ne,nZ:()=>i,X3:()=>Oe,PG:()=>be,dq:()=>je,Xl:()=>ke,EB:()=>a,Jd:()=>k,WL:()=>Fe,qj:()=>me,OT:()=>ge,iH:()=>Pe,lk:()=>E,Um:()=>ve,XI:()=>Ae,IU:()=>_e,BK:()=>ze,j:()=>S,X$:()=>j,SU:()=>Te});var o=n(3577);let r;const s=[];class l{constructor(e=!1){this.active=!0,this.effects=[],this.cleanups=[],!e&&r&&(this.parent=r,this.index=(r.scopes||(r.scopes=[])).push(this)-1)}run(e){if(this.active)try{return this.on(),e()}finally{this.off()}}on(){this.active&&(s.push(this),r=this)}off(){this.active&&(s.pop(),r=s[s.length-1])}stop(e){if(this.active){if(this.effects.forEach((e=>e.stop())),this.cleanups.forEach((e=>e())),this.scopes&&this.scopes.forEach((e=>e.stop(!0))),this.parent&&!e){const e=this.parent.scopes.pop();e&&e!==this&&(this.parent.scopes[this.index]=e,e.index=this.index)}this.active=!1}}}function i(){return r}function a(e){r&&r.cleanups.push(e)}const c=e=>{const t=new Set(e);return t.w=0,t.n=0,t},u=e=>(e.w&h)>0,d=e=>(e.n&h)>0,p=new WeakMap;let f=0,h=1;const m=[];let v;const g=Symbol(""),y=Symbol("");class b{constructor(e,t=null,n){this.fn=e,this.scheduler=t,this.active=!0,this.deps=[],function(e,t){(t=t||r)&&t.active&&t.effects.push(e)}(this,n)}run(){if(!this.active)return this.fn();if(!m.includes(this))try{return m.push(v=this),_.push(O),O=!0,h=1<<++f,f<=30?(({deps:e})=>{if(e.length)for(let t=0;t{const{deps:t}=e;if(t.length){let n=0;for(let o=0;o0?m[e-1]:void 0}}stop(){this.active&&(w(this),this.onStop&&this.onStop(),this.active=!1)}}function w(e){const{deps:t}=e;if(t.length){for(let n=0;n{("length"===t||t>=r)&&a.push(e)}));else switch(void 0!==n&&a.push(i.get(n)),t){case"add":(0,o.kJ)(e)?(0,o.S0)(n)&&a.push(i.get("length")):(a.push(i.get(g)),(0,o._N)(e)&&a.push(i.get(y)));break;case"delete":(0,o.kJ)(e)||(a.push(i.get(g)),(0,o._N)(e)&&a.push(i.get(y)));break;case"set":(0,o._N)(e)&&a.push(i.get(g))}if(1===a.length)a[0]&&P(a[0]);else{const e=[];for(const t of a)t&&e.push(...t);P(c(e))}}function P(e,t){for(const t of(0,o.kJ)(e)?e:[...e])(t!==v||t.allowRecurse)&&(t.scheduler?t.scheduler():t.run())}const A=(0,o.fY)("__proto__,__v_isRef,__isVue"),I=new Set(Object.getOwnPropertyNames(Symbol).map((e=>Symbol[e])).filter(o.yk)),R=H(),T=H(!1,!0),L=H(!0),F=z();function z(){const e={};return["includes","indexOf","lastIndexOf"].forEach((t=>{e[t]=function(...e){const n=_e(this);for(let e=0,t=this.length;e{e[t]=function(...e){k();const n=_e(this)[t].apply(this,e);return E(),n}})),e}function H(e=!1,t=!1){return function(n,r,s){if("__v_isReactive"===r)return!e;if("__v_isReadonly"===r)return e;if("__v_raw"===r&&s===(e?t?he:fe:t?pe:de).get(n))return n;const l=(0,o.kJ)(n);if(!e&&l&&(0,o.RI)(F,r))return Reflect.get(F,r,s);const i=Reflect.get(n,r,s);return((0,o.yk)(r)?I.has(r):A(r))?i:(e||S(n,0,r),t?i:je(i)?l&&(0,o.S0)(r)?i:i.value:(0,o.Kn)(i)?e?ge(i):me(i):i)}}const $=N(),M=N(!0);function N(e=!1){return function(t,n,r,s){let l=t[n];if(!e&&(r=_e(r),l=_e(l),!(0,o.kJ)(t)&&je(l)&&!je(r)))return l.value=r,!0;const i=(0,o.kJ)(t)&&(0,o.S0)(n)?Number(n)!0,deleteProperty:(e,t)=>!0},B=(0,o.l7)({},U,{get:T,set:M}),J=e=>e,q=e=>Reflect.getPrototypeOf(e);function V(e,t,n=!1,o=!1){const r=_e(e=e.__v_raw),s=_e(t);t!==s&&!n&&S(r,0,t),!n&&S(r,0,s);const{has:l}=q(r),i=o?J:n?Se:Ee;return l.call(r,t)?i(e.get(t)):l.call(r,s)?i(e.get(s)):void(e!==r&&e.get(t))}function W(e,t=!1){const n=this.__v_raw,o=_e(n),r=_e(e);return e!==r&&!t&&S(o,0,e),!t&&S(o,0,r),e===r?n.has(e):n.has(e)||n.has(r)}function G(e,t=!1){return e=e.__v_raw,!t&&S(_e(e),0,g),Reflect.get(e,"size",e)}function K(e){e=_e(e);const t=_e(this);return q(t).has.call(t,e)||(t.add(e),j(t,"add",e,e)),this}function Z(e,t){t=_e(t);const n=_e(this),{has:r,get:s}=q(n);let l=r.call(n,e);l||(e=_e(e),l=r.call(n,e));const i=s.call(n,e);return n.set(e,t),l?(0,o.aU)(t,i)&&j(n,"set",e,t):j(n,"add",e,t),this}function Y(e){const t=_e(this),{has:n,get:o}=q(t);let r=n.call(t,e);r||(e=_e(e),r=n.call(t,e)),o&&o.call(t,e);const s=t.delete(e);return r&&j(t,"delete",e,void 0),s}function X(){const e=_e(this),t=0!==e.size,n=e.clear();return t&&j(e,"clear",void 0,void 0),n}function Q(e,t){return function(n,o){const r=this,s=r.__v_raw,l=_e(s),i=t?J:e?Se:Ee;return!e&&S(l,0,g),s.forEach(((e,t)=>n.call(o,i(e),i(t),r)))}}function ee(e,t,n){return function(...r){const s=this.__v_raw,l=_e(s),i=(0,o._N)(l),a="entries"===e||e===Symbol.iterator&&i,c="keys"===e&&i,u=s[e](...r),d=n?J:t?Se:Ee;return!t&&S(l,0,c?y:g),{next(){const{value:e,done:t}=u.next();return t?{value:e,done:t}:{value:a?[d(e[0]),d(e[1])]:d(e),done:t}},[Symbol.iterator](){return this}}}}function te(e){return function(...t){return"delete"!==e&&this}}function ne(){const e={get(e){return V(this,e)},get size(){return G(this)},has:W,add:K,set:Z,delete:Y,clear:X,forEach:Q(!1,!1)},t={get(e){return V(this,e,!1,!0)},get size(){return G(this)},has:W,add:K,set:Z,delete:Y,clear:X,forEach:Q(!1,!0)},n={get(e){return V(this,e,!0)},get size(){return G(this,!0)},has(e){return W.call(this,e,!0)},add:te("add"),set:te("set"),delete:te("delete"),clear:te("clear"),forEach:Q(!0,!1)},o={get(e){return V(this,e,!0,!0)},get size(){return G(this,!0)},has(e){return W.call(this,e,!0)},add:te("add"),set:te("set"),delete:te("delete"),clear:te("clear"),forEach:Q(!0,!0)};return["keys","values","entries",Symbol.iterator].forEach((r=>{e[r]=ee(r,!1,!1),n[r]=ee(r,!0,!1),t[r]=ee(r,!1,!0),o[r]=ee(r,!0,!0)})),[e,n,t,o]}const[oe,re,se,le]=ne();function ie(e,t){const n=t?e?le:se:e?re:oe;return(t,r,s)=>"__v_isReactive"===r?!e:"__v_isReadonly"===r?e:"__v_raw"===r?t:Reflect.get((0,o.RI)(n,r)&&r in t?n:t,r,s)}const ae={get:ie(!1,!1)},ce={get:ie(!1,!0)},ue={get:ie(!0,!1)},de=new WeakMap,pe=new WeakMap,fe=new WeakMap,he=new WeakMap;function me(e){return e&&e.__v_isReadonly?e:ye(e,!1,U,ae,de)}function ve(e){return ye(e,!1,B,ce,pe)}function ge(e){return ye(e,!0,D,ue,fe)}function ye(e,t,n,r,s){if(!(0,o.Kn)(e))return e;if(e.__v_raw&&(!t||!e.__v_isReactive))return e;const l=s.get(e);if(l)return l;const i=(a=e).__v_skip||!Object.isExtensible(a)?0:function(e){switch(e){case"Object":case"Array":return 1;case"Map":case"Set":case"WeakMap":case"WeakSet":return 2;default:return 0}}((0,o.W7)(a));var a;if(0===i)return e;const c=new Proxy(e,2===i?r:n);return s.set(e,c),c}function be(e){return we(e)?be(e.__v_raw):!(!e||!e.__v_isReactive)}function we(e){return!(!e||!e.__v_isReadonly)}function Oe(e){return be(e)||we(e)}function _e(e){const t=e&&e.__v_raw;return t?_e(t):e}function ke(e){return(0,o.Nj)(e,"__v_skip",!0),e}const Ee=e=>(0,o.Kn)(e)?me(e):e,Se=e=>(0,o.Kn)(e)?ge(e):e;function Ce(e){C()&&((e=_e(e)).dep||(e.dep=c()),x(e.dep))}function xe(e,t){(e=_e(e)).dep&&P(e.dep)}function je(e){return Boolean(e&&!0===e.__v_isRef)}function Pe(e){return Ie(e,!1)}function Ae(e){return Ie(e,!0)}function Ie(e,t){return je(e)?e:new Re(e,t)}class Re{constructor(e,t){this._shallow=t,this.dep=void 0,this.__v_isRef=!0,this._rawValue=t?e:_e(e),this._value=t?e:Ee(e)}get value(){return Ce(this),this._value}set value(e){e=this._shallow?e:_e(e),(0,o.aU)(e,this._rawValue)&&(this._rawValue=e,this._value=this._shallow?e:Ee(e),xe(this))}}function Te(e){return je(e)?e.value:e}const Le={get:(e,t,n)=>Te(Reflect.get(e,t,n)),set:(e,t,n,o)=>{const r=e[t];return je(r)&&!je(n)?(r.value=n,!0):Reflect.set(e,t,n,o)}};function Fe(e){return be(e)?e:new Proxy(e,Le)}function ze(e){const t=(0,o.kJ)(e)?new Array(e.length):{};for(const n in e)t[n]=$e(e,n);return t}class He{constructor(e,t){this._object=e,this._key=t,this.__v_isRef=!0}get value(){return this._object[this._key]}set value(e){this._object[this._key]=e}}function $e(e,t){const n=e[t];return je(n)?n:new He(e,t)}class Me{constructor(e,t,n){this._setter=t,this.dep=void 0,this._dirty=!0,this.__v_isRef=!0,this.effect=new b(e,(()=>{this._dirty||(this._dirty=!0,xe(this))})),this.__v_isReadonly=n}get value(){const e=_e(this);return Ce(e),e._dirty&&(e._dirty=!1,e._value=e.effect.run()),e._value}set value(e){this._setter(e)}}function Ne(e,t){let n,r;const s=(0,o.mf)(e);return s?(n=e,r=o.dG):(n=e.get,r=e.set),new Me(n,r,s||!r)}Promise.resolve()},6252:(e,t,n)=>{"use strict";n.d(t,{nZ:()=>o.nZ,EB:()=>o.EB,iH:()=>o.iH,SU:()=>o.SU,P$:()=>O,HY:()=>Fe,$d:()=>Ct,j4:()=>Ve,kq:()=>ot,iD:()=>qe,_:()=>Xe,Eo:()=>xe,uE:()=>nt,Uk:()=>tt,Wm:()=>Qe,RC:()=>A,aZ:()=>j,FN:()=>gt,Q6:()=>x,h:()=>tn,f3:()=>y,dG:()=>it,Y3:()=>Ut,Jd:()=>B,bv:()=>N,Ah:()=>J,ic:()=>D,wg:()=>Ue,JJ:()=>g,Ko:()=>at,WI:()=>ct,up:()=>Re,U2:()=>k,nK:()=>C,Y8:()=>b,YP:()=>Zt,w5:()=>d,wy:()=>ge});var o=n(2262),r=n(3577);function s(e,t,...n){const o=e.vnode.props||r.kT;let s=n;const l=t.startsWith("update:"),i=l&&t.slice(7);if(i&&i in o){const e=`${"modelValue"===i?"model":i}Modifiers`,{number:t,trim:l}=o[e]||r.kT;l?s=n.map((e=>e.trim())):t&&(s=n.map(r.He))}let a,c=o[a=(0,r.hR)(t)]||o[a=(0,r.hR)((0,r._A)(t))];!c&&l&&(c=o[a=(0,r.hR)((0,r.rs)(t))]),c&&Ct(c,e,6,s);const u=o[a+"Once"];if(u){if(e.emitted){if(e.emitted[a])return}else e.emitted={};e.emitted[a]=!0,Ct(u,e,6,s)}}function l(e,t,n=!1){const o=t.emitsCache,s=o.get(e);if(void 0!==s)return s;const i=e.emits;let a={},c=!1;if(!(0,r.mf)(e)){const o=e=>{const n=l(e,t,!0);n&&(c=!0,(0,r.l7)(a,n))};!n&&t.mixins.length&&t.mixins.forEach(o),e.extends&&o(e.extends),e.mixins&&e.mixins.forEach(o)}return i||c?((0,r.kJ)(i)?i.forEach((e=>a[e]=null)):(0,r.l7)(a,i),o.set(e,a),a):(o.set(e,null),null)}function i(e,t){return!(!e||!(0,r.F7)(t))&&(t=t.slice(2).replace(/Once$/,""),(0,r.RI)(e,t[0].toLowerCase()+t.slice(1))||(0,r.RI)(e,(0,r.rs)(t))||(0,r.RI)(e,t))}new Set,new Map;let a=null,c=null;function u(e){const t=a;return a=e,c=e&&e.type.__scopeId||null,t}function d(e,t=a,n){if(!t)return e;if(e._n)return e;const o=(...n)=>{o._d&&Be(-1);const r=u(t),s=e(...n);return u(r),o._d&&Be(1),s};return o._n=!0,o._c=!0,o._d=!0,o}function p(e){const{type:t,vnode:n,proxy:o,withProxy:s,props:l,propsOptions:[i],slots:a,attrs:c,emit:d,render:p,renderCache:m,data:v,setupState:g,ctx:y,inheritAttrs:b}=e;let w,O;const _=u(e);try{if(4&n.shapeFlag){const e=s||o;w=rt(p.call(e,e,m,l,g,v,y)),O=c}else{const e=t;w=rt(e.length>1?e(l,{attrs:c,slots:a,emit:d}):e(l,null)),O=t.props?c:f(c)}}catch(t){Me.length=0,xt(t,e,1),w=Qe(He)}let k=w;if(O&&!1!==b){const e=Object.keys(O),{shapeFlag:t}=k;e.length&&7&t&&(i&&e.some(r.tR)&&(O=h(O,i)),k=et(k,O))}return n.dirs&&(k.dirs=k.dirs?k.dirs.concat(n.dirs):n.dirs),n.transition&&(k.transition=n.transition),w=k,u(_),w}const f=e=>{let t;for(const n in e)("class"===n||"style"===n||(0,r.F7)(n))&&((t||(t={}))[n]=e[n]);return t},h=(e,t)=>{const n={};for(const o in e)(0,r.tR)(o)&&o.slice(9)in t||(n[o]=e[o]);return n};function m(e,t,n){const o=Object.keys(t);if(o.length!==Object.keys(e).length)return!0;for(let r=0;r1)return n&&(0,r.mf)(t)?t.call(o.proxy):t}}function b(){const e={isMounted:!1,isLeaving:!1,isUnmounting:!1,leavingVNodes:new Map};return N((()=>{e.isMounted=!0})),B((()=>{e.isUnmounting=!0})),e}const w=[Function,Array],O={name:"BaseTransition",props:{mode:String,appear:Boolean,persisted:Boolean,onBeforeEnter:w,onEnter:w,onAfterEnter:w,onEnterCancelled:w,onBeforeLeave:w,onLeave:w,onAfterLeave:w,onLeaveCancelled:w,onBeforeAppear:w,onAppear:w,onAfterAppear:w,onAppearCancelled:w},setup(e,{slots:t}){const n=gt(),r=b();let s;return()=>{const l=t.default&&x(t.default(),!0);if(!l||!l.length)return;const i=(0,o.IU)(e),{mode:a}=i,c=l[0];if(r.isLeaving)return E(c);const u=S(c);if(!u)return E(c);const d=k(u,i,r,n);C(u,d);const p=n.subTree,f=p&&S(p);let h=!1;const{getTransitionKey:m}=u.type;if(m){const e=m();void 0===s?s=e:e!==s&&(s=e,h=!0)}if(f&&f.type!==He&&(!Ge(u,f)||h)){const e=k(f,i,r,n);if(C(f,e),"out-in"===a)return r.isLeaving=!0,e.afterLeave=()=>{r.isLeaving=!1,n.update()},E(c);"in-out"===a&&u.type!==He&&(e.delayLeave=(e,t,n)=>{_(r,f)[String(f.key)]=f,e._leaveCb=()=>{t(),e._leaveCb=void 0,delete d.delayedLeave},d.delayedLeave=n})}return c}}};function _(e,t){const{leavingVNodes:n}=e;let o=n.get(t.type);return o||(o=Object.create(null),n.set(t.type,o)),o}function k(e,t,n,o){const{appear:r,mode:s,persisted:l=!1,onBeforeEnter:i,onEnter:a,onAfterEnter:c,onEnterCancelled:u,onBeforeLeave:d,onLeave:p,onAfterLeave:f,onLeaveCancelled:h,onBeforeAppear:m,onAppear:v,onAfterAppear:g,onAppearCancelled:y}=t,b=String(e.key),w=_(n,e),O=(e,t)=>{e&&Ct(e,o,9,t)},E={mode:s,persisted:l,beforeEnter(t){let o=i;if(!n.isMounted){if(!r)return;o=m||i}t._leaveCb&&t._leaveCb(!0);const s=w[b];s&&Ge(e,s)&&s.el._leaveCb&&s.el._leaveCb(),O(o,[t])},enter(e){let t=a,o=c,s=u;if(!n.isMounted){if(!r)return;t=v||a,o=g||c,s=y||u}let l=!1;const i=e._enterCb=t=>{l||(l=!0,O(t?s:o,[e]),E.delayedLeave&&E.delayedLeave(),e._enterCb=void 0)};t?(t(e,i),t.length<=1&&i()):i()},leave(t,o){const r=String(e.key);if(t._enterCb&&t._enterCb(!0),n.isUnmounting)return o();O(d,[t]);let s=!1;const l=t._leaveCb=n=>{s||(s=!0,o(),O(n?h:f,[t]),t._leaveCb=void 0,w[r]===e&&delete w[r])};w[r]=e,p?(p(t,l),p.length<=1&&l()):l()},clone:e=>k(e,t,n,o)};return E}function E(e){if(R(e))return(e=et(e)).children=null,e}function S(e){return R(e)?e.children?e.children[0]:void 0:e}function C(e,t){6&e.shapeFlag&&e.component?C(e.component.subTree,t):128&e.shapeFlag?(e.ssContent.transition=t.clone(e.ssContent),e.ssFallback.transition=t.clone(e.ssFallback)):e.transition=t}function x(e,t=!1){let n=[],o=0;for(let r=0;r1)for(let e=0;e!!e.type.__asyncLoader;function A(e){(0,r.mf)(e)&&(e={loader:e});const{loader:t,loadingComponent:n,errorComponent:s,delay:l=200,timeout:i,suspensible:a=!0,onError:c}=e;let u,d=null,p=0;const f=()=>{let e;return d||(e=d=t().catch((e=>{if(e=e instanceof Error?e:new Error(String(e)),c)return new Promise(((t,n)=>{c(e,(()=>t((p++,d=null,f()))),(()=>n(e)),p+1)}));throw e})).then((t=>e!==d&&d?d:(t&&(t.__esModule||"Module"===t[Symbol.toStringTag])&&(t=t.default),u=t,t))))};return j({name:"AsyncComponentWrapper",__asyncLoader:f,get __asyncResolved(){return u},setup(){const e=vt;if(u)return()=>I(u,e);const t=t=>{d=null,xt(t,e,13,!s)};if(a&&e.suspense||Ot)return f().then((t=>()=>I(t,e))).catch((e=>(t(e),()=>s?Qe(s,{error:e}):null)));const r=(0,o.iH)(!1),c=(0,o.iH)(),p=(0,o.iH)(!!l);return l&&setTimeout((()=>{p.value=!1}),l),null!=i&&setTimeout((()=>{if(!r.value&&!c.value){const e=new Error(`Async component timed out after ${i}ms.`);t(e),c.value=e}}),i),f().then((()=>{r.value=!0,e.parent&&R(e.parent.vnode)&&Dt(e.parent.update)})).catch((e=>{t(e),c.value=e})),()=>r.value&&u?I(u,e):c.value&&s?Qe(s,{error:c.value}):n&&!p.value?Qe(n):void 0}})}function I(e,{vnode:{ref:t,props:n,children:o}}){const r=Qe(e,n,o);return r.ref=t,r}const R=e=>e.type.__isKeepAlive;function T(e,t){F(e,"a",t)}function L(e,t){F(e,"da",t)}function F(e,t,n=vt){const o=e.__wdc||(e.__wdc=()=>{let t=n;for(;t;){if(t.isDeactivated)return;t=t.parent}e()});if(H(t,o,n),n){let e=n.parent;for(;e&&e.parent;)R(e.parent.vnode)&&z(o,t,n,e),e=e.parent}}function z(e,t,n,o){const s=H(t,e,o,!0);J((()=>{(0,r.Od)(o[t],s)}),n)}function H(e,t,n=vt,r=!1){if(n){const s=n[e]||(n[e]=[]),l=t.__weh||(t.__weh=(...r)=>{if(n.isUnmounted)return;(0,o.Jd)(),yt(n);const s=Ct(t,n,e,r);return bt(),(0,o.lk)(),s});return r?s.unshift(l):s.push(l),l}}RegExp,RegExp;const $=e=>(t,n=vt)=>(!Ot||"sp"===e)&&H(e,t,n),M=$("bm"),N=$("m"),U=$("bu"),D=$("u"),B=$("bum"),J=$("um"),q=$("sp"),V=$("rtg"),W=$("rtc");function G(e,t=vt){H("ec",e,t)}let K=!0;function Z(e,t,n){Ct((0,r.kJ)(e)?e.map((e=>e.bind(t.proxy))):e.bind(t.proxy),t,n)}function Y(e,t,n,o){const s=o.includes(".")?Qt(n,o):()=>n[o];if((0,r.HD)(e)){const n=t[e];(0,r.mf)(n)&&Zt(s,n)}else if((0,r.mf)(e))Zt(s,e.bind(n));else if((0,r.Kn)(e))if((0,r.kJ)(e))e.forEach((e=>Y(e,t,n,o)));else{const o=(0,r.mf)(e.handler)?e.handler.bind(n):t[e.handler];(0,r.mf)(o)&&Zt(s,o,e)}}function X(e){const t=e.type,{mixins:n,extends:o}=t,{mixins:r,optionsCache:s,config:{optionMergeStrategies:l}}=e.appContext,i=s.get(t);let a;return i?a=i:r.length||n||o?(a={},r.length&&r.forEach((e=>Q(a,e,l,!0))),Q(a,t,l)):a=t,s.set(t,a),a}function Q(e,t,n,o=!1){const{mixins:r,extends:s}=t;s&&Q(e,s,n,!0),r&&r.forEach((t=>Q(e,t,n,!0)));for(const r in t)if(o&&"expose"===r);else{const o=ee[r]||n&&n[r];e[r]=o?o(e[r],t[r]):t[r]}return e}const ee={data:te,props:re,emits:re,methods:re,computed:re,beforeCreate:oe,created:oe,beforeMount:oe,mounted:oe,beforeUpdate:oe,updated:oe,beforeDestroy:oe,beforeUnmount:oe,destroyed:oe,unmounted:oe,activated:oe,deactivated:oe,errorCaptured:oe,serverPrefetch:oe,components:re,directives:re,watch:function(e,t){if(!e)return t;if(!t)return e;const n=(0,r.l7)(Object.create(null),e);for(const o in t)n[o]=oe(e[o],t[o]);return n},provide:te,inject:function(e,t){return re(ne(e),ne(t))}};function te(e,t){return t?e?function(){return(0,r.l7)((0,r.mf)(e)?e.call(this,this):e,(0,r.mf)(t)?t.call(this,this):t)}:t:e}function ne(e){if((0,r.kJ)(e)){const t={};for(let n=0;n{c=!0;const[n,o]=ie(e,t,!0);(0,r.l7)(i,n),o&&a.push(...o)};!n&&t.mixins.length&&t.mixins.forEach(o),e.extends&&o(e.extends),e.mixins&&e.mixins.forEach(o)}if(!l&&!c)return o.set(e,r.Z6),r.Z6;if((0,r.kJ)(l))for(let e=0;e-1,o[1]=n<0||e-1||(0,r.RI)(o,"default"))&&a.push(t)}}}const u=[i,a];return o.set(e,u),u}function ae(e){return"$"!==e[0]}function ce(e){const t=e&&e.toString().match(/^\s*function (\w+)/);return t?t[1]:null===e?"null":""}function ue(e,t){return ce(e)===ce(t)}function de(e,t){return(0,r.kJ)(t)?t.findIndex((t=>ue(t,e))):(0,r.mf)(t)&&ue(t,e)?0:-1}const pe=e=>"_"===e[0]||"$stable"===e,fe=e=>(0,r.kJ)(e)?e.map(rt):[rt(e)],he=(e,t,n)=>{const o=d(((...e)=>fe(t(...e))),n);return o._c=!1,o},me=(e,t,n)=>{const o=e._ctx;for(const n in e){if(pe(n))continue;const s=e[n];if((0,r.mf)(s))t[n]=he(0,s,o);else if(null!=s){const e=fe(s);t[n]=()=>e}}},ve=(e,t)=>{const n=fe(t);e.slots.default=()=>n};function ge(e,t){if(null===a)return e;const n=a.proxy,o=e.dirs||(e.dirs=[]);for(let e=0;e(l.has(e)||(e&&(0,r.mf)(e.install)?(l.add(e),e.install(a,...t)):(0,r.mf)(e)&&(l.add(e),e(a,...t))),a),mixin:e=>(s.mixins.includes(e)||s.mixins.push(e),a),component:(e,t)=>t?(s.components[e]=t,a):s.components[e],directive:(e,t)=>t?(s.directives[e]=t,a):s.directives[e],mount(r,l,c){if(!i){const u=Qe(n,o);return u.appContext=s,l&&t?t(u,r):e(u,r,c),i=!0,a._container=r,r.__vue_app__=a,Et(u.component)||u.component.proxy}},unmount(){i&&(e(null,a._container),delete a._container.__vue_app__)},provide:(e,t)=>(s.provides[e]=t,a)};return a}}let _e=!1;const ke=e=>/svg/.test(e.namespaceURI)&&"foreignObject"!==e.tagName,Ee=e=>8===e.nodeType;function Se(e){const{mt:t,p:n,o:{patchProp:o,nextSibling:s,parentNode:l,remove:i,insert:a,createComment:c}}=e,u=(n,o,r,i,a,c=!1)=>{const v=Ee(n)&&"["===n.data,g=()=>h(n,o,r,i,a,v),{type:y,ref:b,shapeFlag:w}=o,O=n.nodeType;o.el=n;let _=null;switch(y){case ze:3!==O?_=g():(n.data!==o.children&&(_e=!0,n.data=o.children),_=s(n));break;case He:_=8!==O||v?g():s(n);break;case $e:if(1===O){_=n;const e=!o.children.length;for(let t=0;t{a=a||!!t.dynamicChildren;const{type:c,props:u,patchFlag:d,shapeFlag:f,dirs:h}=t,m="input"===c&&h||"option"===c;if(m||-1!==d){if(h&&ye(t,null,n,"created"),u)if(m||!a||48&d)for(const t in u)(m&&t.endsWith("value")||(0,r.F7)(t)&&!(0,r.Gg)(t))&&o(e,t,null,u[t],!1,void 0,n);else u.onClick&&o(e,"onClick",null,u.onClick,!1,void 0,n);let c;if((c=u&&u.onVnodeBeforeMount)&&Pe(c,n,t),h&&ye(t,null,n,"beforeMount"),((c=u&&u.onVnodeMounted)||h)&&v((()=>{c&&Pe(c,n,t),h&&ye(t,null,n,"mounted")}),s),16&f&&(!u||!u.innerHTML&&!u.textContent)){let o=p(e.firstChild,t,e,n,s,l,a);for(;o;){_e=!0;const e=o;o=o.nextSibling,i(e)}}else 8&f&&e.textContent!==t.children&&(_e=!0,e.textContent=t.children)}return e.nextSibling},p=(e,t,o,r,s,l,i)=>{i=i||!!t.dynamicChildren;const a=t.children,c=a.length;for(let t=0;t{const{slotScopeIds:u}=t;u&&(r=r?r.concat(u):u);const d=l(e),f=p(s(e),t,d,n,o,r,i);return f&&Ee(f)&&"]"===f.data?s(t.anchor=f):(_e=!0,a(t.anchor=c("]"),d,f),f)},h=(e,t,o,r,a,c)=>{if(_e=!0,t.el=null,c){const t=m(e);for(;;){const n=s(e);if(!n||n===t)break;i(n)}}const u=s(e),d=l(e);return i(e),n(null,t,d,u,o,r,ke(d),a),u},m=e=>{let t=0;for(;e;)if((e=s(e))&&Ee(e)&&("["===e.data&&t++,"]"===e.data)){if(0===t)return s(e);t--}return e};return[(e,t)=>{if(!t.hasChildNodes())return n(null,e,t),void Vt();_e=!1,u(t.firstChild,e,null,null,null),Vt(),_e&&console.error("Hydration completed but contains mismatches.")},u]}const Ce=v;function xe(e){return function(e,t){(0,r.E9)().__VUE__=!0;const{insert:n,remove:a,patchProp:c,createElement:u,createText:d,createComment:f,setText:h,setElementText:v,parentNode:g,nextSibling:y,setScopeId:b=r.dG,cloneNode:w,insertStaticContent:O}=e,_=(e,t,n,o=null,r=null,s=null,l=!1,i=null,a=!!t.dynamicChildren)=>{if(e===t)return;e&&!Ge(e,t)&&(o=Y(e),V(e,r,s,!0),e=null),-2===t.patchFlag&&(a=!1,t.dynamicChildren=null);const{type:c,ref:u,shapeFlag:d}=t;switch(c){case ze:k(e,t,n,o);break;case He:E(e,t,n,o);break;case $e:null==e&&S(t,n,o,l);break;case Fe:z(e,t,n,o,r,s,l,i,a);break;default:1&d?x(e,t,n,o,r,s,l,i,a):6&d?H(e,t,n,o,r,s,l,i,a):(64&d||128&d)&&c.process(e,t,n,o,r,s,l,i,a,Q)}null!=u&&r&&je(u,e&&e.ref,s,t||e,!t)},k=(e,t,o,r)=>{if(null==e)n(t.el=d(t.children),o,r);else{const n=t.el=e.el;t.children!==e.children&&h(n,t.children)}},E=(e,t,o,r)=>{null==e?n(t.el=f(t.children||""),o,r):t.el=e.el},S=(e,t,n,o)=>{[e.el,e.anchor]=O(e.children,t,n,o)},C=({el:e,anchor:t})=>{let n;for(;e&&e!==t;)n=y(e),a(e),e=n;a(t)},x=(e,t,n,o,r,s,l,i,a)=>{l=l||"svg"===t.type,null==e?j(t,n,o,r,s,l,i,a):T(e,t,r,s,l,i,a)},j=(e,t,o,s,l,i,a,d)=>{let p,f;const{type:h,props:m,shapeFlag:g,transition:y,patchFlag:b,dirs:O}=e;if(e.el&&void 0!==w&&-1===b)p=e.el=w(e.el);else{if(p=e.el=u(e.type,i,m&&m.is,m),8&g?v(p,e.children):16&g&&I(e.children,p,null,s,l,i&&"foreignObject"!==h,a,d),O&&ye(e,null,s,"created"),m){for(const t in m)"value"===t||(0,r.Gg)(t)||c(p,t,null,m[t],i,e.children,s,l,Z);"value"in m&&c(p,"value",null,m.value),(f=m.onVnodeBeforeMount)&&Pe(f,s,e)}A(p,e,e.scopeId,a,s)}O&&ye(e,null,s,"beforeMount");const _=(!l||l&&!l.pendingBranch)&&y&&!y.persisted;_&&y.beforeEnter(p),n(p,t,o),((f=m&&m.onVnodeMounted)||_||O)&&Ce((()=>{f&&Pe(f,s,e),_&&y.enter(p),O&&ye(e,null,s,"mounted")}),l)},A=(e,t,n,o,r)=>{if(n&&b(e,n),o)for(let t=0;t{for(let c=a;c{const a=t.el=e.el;let{patchFlag:u,dynamicChildren:d,dirs:p}=t;u|=16&e.patchFlag;const f=e.props||r.kT,h=t.props||r.kT;let m;(m=h.onVnodeBeforeUpdate)&&Pe(m,n,t,e),p&&ye(t,e,n,"beforeUpdate");const g=s&&"foreignObject"!==t.type;if(d?L(e.dynamicChildren,d,a,n,o,g,l):i||D(e,t,a,null,n,o,g,l,!1),u>0){if(16&u)F(a,t,f,h,n,o,s);else if(2&u&&f.class!==h.class&&c(a,"class",null,h.class,s),4&u&&c(a,"style",f.style,h.style,s),8&u){const r=t.dynamicProps;for(let t=0;t{m&&Pe(m,n,t,e),p&&ye(t,e,n,"updated")}),o)},L=(e,t,n,o,r,s,l)=>{for(let i=0;i{if(n!==o){for(const a in o){if((0,r.Gg)(a))continue;const u=o[a],d=n[a];u!==d&&"value"!==a&&c(e,a,d,u,i,t.children,s,l,Z)}if(n!==r.kT)for(const a in n)(0,r.Gg)(a)||a in o||c(e,a,n[a],null,i,t.children,s,l,Z);"value"in o&&c(e,"value",n.value,o.value)}},z=(e,t,o,r,s,l,i,a,c)=>{const u=t.el=e?e.el:d(""),p=t.anchor=e?e.anchor:d("");let{patchFlag:f,dynamicChildren:h,slotScopeIds:m}=t;m&&(a=a?a.concat(m):m),null==e?(n(u,o,r),n(p,o,r),I(t.children,o,p,s,l,i,a,c)):f>0&&64&f&&h&&e.dynamicChildren?(L(e.dynamicChildren,h,o,s,l,i,a),(null!=t.key||s&&t===s.subTree)&&Ae(e,t,!0)):D(e,t,o,p,s,l,i,a,c)},H=(e,t,n,o,r,s,l,i,a)=>{t.slotScopeIds=i,null==e?512&t.shapeFlag?r.ctx.activate(t,n,o,l,a):$(t,n,o,r,s,l,a):M(e,t,a)},$=(e,t,n,i,a,c,u)=>{const d=e.component=function(e,t,n){const i=e.type,a=(t?t.appContext:e.appContext)||ht,c={uid:mt++,vnode:e,type:i,parent:t,appContext:a,root:null,next:null,subTree:null,update:null,scope:new o.Bj(!0),render:null,proxy:null,exposed:null,exposeProxy:null,withProxy:null,provides:t?t.provides:Object.create(a.provides),accessCache:null,renderCache:[],components:null,directives:null,propsOptions:ie(i,a),emitsOptions:l(i,a),emit:null,emitted:null,propsDefaults:r.kT,inheritAttrs:i.inheritAttrs,ctx:r.kT,data:r.kT,props:r.kT,attrs:r.kT,slots:r.kT,refs:r.kT,setupState:r.kT,setupContext:null,suspense:n,suspenseId:n?n.pendingId:0,asyncDep:null,asyncResolved:!1,isMounted:!1,isUnmounted:!1,isDeactivated:!1,bc:null,c:null,bm:null,m:null,bu:null,u:null,um:null,bum:null,da:null,a:null,rtg:null,rtc:null,ec:null,sp:null};return c.ctx={_:c},c.root=t?t.root:c,c.emit=s.bind(null,c),e.ce&&e.ce(c),c}(e,i,a);if(R(e)&&(d.ctx.renderer=Q),function(e,t=!1){Ot=t;const{props:n,children:s}=e.vnode,l=wt(e);!function(e,t,n,s=!1){const l={},i={};(0,r.Nj)(i,Ke,1),e.propsDefaults=Object.create(null),se(e,t,l,i);for(const t in e.propsOptions[0])t in l||(l[t]=void 0);n?e.props=s?l:(0,o.Um)(l):e.type.props?e.props=l:e.props=i,e.attrs=i}(e,n,l,t),((e,t)=>{if(32&e.vnode.shapeFlag){const n=t._;n?(e.slots=(0,o.IU)(t),(0,r.Nj)(t,"_",n)):me(t,e.slots={})}else e.slots={},t&&ve(e,t);(0,r.Nj)(e.slots,Ke,1)})(e,s);const i=l?function(e,t){const n=e.type;e.accessCache=Object.create(null),e.proxy=(0,o.Xl)(new Proxy(e.ctx,ft));const{setup:s}=n;if(s){const n=e.setupContext=s.length>1?function(e){const t=t=>{e.exposed=t||{}};let n;return{get attrs(){return n||(n=function(e){return new Proxy(e.attrs,{get:(t,n)=>((0,o.j)(e,"get","$attrs"),t[n])})}(e))},slots:e.slots,emit:e.emit,expose:t}}(e):null;yt(e),(0,o.Jd)();const l=St(s,e,0,[e.props,n]);if((0,o.lk)(),bt(),(0,r.tI)(l)){if(l.then(bt,bt),t)return l.then((n=>{_t(e,n,t)})).catch((t=>{xt(t,e,0)}));e.asyncDep=l}else _t(e,l,t)}else kt(e,t)}(e,t):void 0;Ot=!1}(d),d.asyncDep){if(a&&a.registerDep(d,N),!e.el){const e=d.subTree=Qe(He);E(null,e,t,n)}}else N(d,e,t,n,a,c,u)},M=(e,t,n)=>{const o=t.component=e.component;if(function(e,t,n){const{props:o,children:r,component:s}=e,{props:l,children:a,patchFlag:c}=t,u=s.emitsOptions;if(t.dirs||t.transition)return!0;if(!(n&&c>=0))return!(!r&&!a||a&&a.$stable)||o!==l&&(o?!l||m(o,l,u):!!l);if(1024&c)return!0;if(16&c)return o?m(o,l,u):!!l;if(8&c){const e=t.dynamicProps;for(let t=0;tIt&&At.splice(t,1)}(o.update),o.update()}else t.component=e.component,t.el=e.el,o.vnode=t},N=(e,t,n,s,l,i,a)=>{const c=new o.qq((()=>{if(e.isMounted){let t,{next:n,bu:o,u:s,parent:u,vnode:d}=e,f=n;c.allowRecurse=!1,n?(n.el=d.el,U(e,n,a)):n=d,o&&(0,r.ir)(o),(t=n.props&&n.props.onVnodeBeforeUpdate)&&Pe(t,u,n,d),c.allowRecurse=!0;const h=p(e),m=e.subTree;e.subTree=h,_(m,h,g(m.el),Y(m),e,l,i),n.el=h.el,null===f&&function({vnode:e,parent:t},n){for(;t&&t.subTree===e;)(e=t.vnode).el=n,t=t.parent}(e,h.el),s&&Ce(s,l),(t=n.props&&n.props.onVnodeUpdated)&&Ce((()=>Pe(t,u,n,d)),l)}else{let o;const{el:a,props:u}=t,{bm:d,m:f,parent:h}=e,m=P(t);if(c.allowRecurse=!1,d&&(0,r.ir)(d),!m&&(o=u&&u.onVnodeBeforeMount)&&Pe(o,h,t),c.allowRecurse=!0,a&&te){const n=()=>{e.subTree=p(e),te(a,e.subTree,e,l,null)};m?t.type.__asyncLoader().then((()=>!e.isUnmounted&&n())):n()}else{const o=e.subTree=p(e);_(null,o,n,s,e,l,i),t.el=o.el}if(f&&Ce(f,l),!m&&(o=u&&u.onVnodeMounted)){const e=t;Ce((()=>Pe(o,h,e)),l)}256&t.shapeFlag&&e.a&&Ce(e.a,l),e.isMounted=!0,t=n=s=null}}),(()=>Dt(e.update)),e.scope),u=e.update=c.run.bind(c);u.id=e.uid,c.allowRecurse=u.allowRecurse=!0,u()},U=(e,t,n)=>{t.component=e;const s=e.vnode.props;e.vnode=t,e.next=null,function(e,t,n,s){const{props:l,attrs:i,vnode:{patchFlag:a}}=e,c=(0,o.IU)(l),[u]=e.propsOptions;let d=!1;if(!(s||a>0)||16&a){let o;se(e,t,l,i)&&(d=!0);for(const s in c)t&&((0,r.RI)(t,s)||(o=(0,r.rs)(s))!==s&&(0,r.RI)(t,o))||(u?!n||void 0===n[s]&&void 0===n[o]||(l[s]=le(u,c,s,void 0,e,!0)):delete l[s]);if(i!==c)for(const e in i)t&&(0,r.RI)(t,e)||(delete i[e],d=!0)}else if(8&a){const n=e.vnode.dynamicProps;for(let o=0;o{const{vnode:o,slots:s}=e;let l=!0,i=r.kT;if(32&o.shapeFlag){const e=t._;e?n&&1===e?l=!1:((0,r.l7)(s,t),n||1!==e||delete s._):(l=!t.$stable,me(t,s)),i=t}else t&&(ve(e,t),i={default:1});if(l)for(const e in s)pe(e)||e in i||delete s[e]})(e,t.children,n),(0,o.Jd)(),qt(void 0,e.update),(0,o.lk)()},D=(e,t,n,o,r,s,l,i,a=!1)=>{const c=e&&e.children,u=e?e.shapeFlag:0,d=t.children,{patchFlag:p,shapeFlag:f}=t;if(p>0){if(128&p)return void J(c,d,n,o,r,s,l,i,a);if(256&p)return void B(c,d,n,o,r,s,l,i,a)}8&f?(16&u&&Z(c,r,s),d!==c&&v(n,d)):16&u?16&f?J(c,d,n,o,r,s,l,i,a):Z(c,r,s,!0):(8&u&&v(n,""),16&f&&I(d,n,o,r,s,l,i,a))},B=(e,t,n,o,s,l,i,a,c)=>{e=e||r.Z6,t=t||r.Z6;const u=e.length,d=t.length,p=Math.min(u,d);let f;for(f=0;fd?Z(e,s,l,!0,!1,p):I(t,n,o,s,l,i,a,c,p)},J=(e,t,n,o,s,l,i,a,c)=>{let u=0;const d=t.length;let p=e.length-1,f=d-1;for(;u<=p&&u<=f;){const o=e[u],r=t[u]=c?st(t[u]):rt(t[u]);if(!Ge(o,r))break;_(o,r,n,null,s,l,i,a,c),u++}for(;u<=p&&u<=f;){const o=e[p],r=t[f]=c?st(t[f]):rt(t[f]);if(!Ge(o,r))break;_(o,r,n,null,s,l,i,a,c),p--,f--}if(u>p){if(u<=f){const e=f+1,r=ef)for(;u<=p;)V(e[u],s,l,!0),u++;else{const h=u,m=u,v=new Map;for(u=m;u<=f;u++){const e=t[u]=c?st(t[u]):rt(t[u]);null!=e.key&&v.set(e.key,u)}let g,y=0;const b=f-m+1;let w=!1,O=0;const k=new Array(b);for(u=0;u=b){V(o,s,l,!0);continue}let r;if(null!=o.key)r=v.get(o.key);else for(g=m;g<=f;g++)if(0===k[g-m]&&Ge(o,t[g])){r=g;break}void 0===r?V(o,s,l,!0):(k[r-m]=u+1,r>=O?O=r:w=!0,_(o,t[r],n,null,s,l,i,a,c),y++)}const E=w?function(e){const t=e.slice(),n=[0];let o,r,s,l,i;const a=e.length;for(o=0;o>1,e[n[i]]0&&(t[o]=n[s-1]),n[s]=o)}}for(s=n.length,l=n[s-1];s-- >0;)n[s]=l,l=t[l];return n}(k):r.Z6;for(g=E.length-1,u=b-1;u>=0;u--){const e=m+u,r=t[e],p=e+1{const{el:l,type:i,transition:a,children:c,shapeFlag:u}=e;if(6&u)q(e.component.subTree,t,o,r);else if(128&u)e.suspense.move(t,o,r);else if(64&u)i.move(e,t,o,Q);else if(i!==Fe)if(i!==$e)if(2!==r&&1&u&&a)if(0===r)a.beforeEnter(l),n(l,t,o),Ce((()=>a.enter(l)),s);else{const{leave:e,delayLeave:r,afterLeave:s}=a,i=()=>n(l,t,o),c=()=>{e(l,(()=>{i(),s&&s()}))};r?r(l,i,c):c()}else n(l,t,o);else(({el:e,anchor:t},o,r)=>{let s;for(;e&&e!==t;)s=y(e),n(e,o,r),e=s;n(t,o,r)})(e,t,o);else{n(l,t,o);for(let e=0;e{const{type:s,props:l,ref:i,children:a,dynamicChildren:c,shapeFlag:u,patchFlag:d,dirs:p}=e;if(null!=i&&je(i,null,n,e,!0),256&u)return void t.ctx.deactivate(e);const f=1&u&&p,h=!P(e);let m;if(h&&(m=l&&l.onVnodeBeforeUnmount)&&Pe(m,t,e),6&u)K(e.component,n,o);else{if(128&u)return void e.suspense.unmount(n,o);f&&ye(e,null,t,"beforeUnmount"),64&u?e.type.remove(e,t,n,r,Q,o):c&&(s!==Fe||d>0&&64&d)?Z(c,t,n,!1,!0):(s===Fe&&384&d||!r&&16&u)&&Z(a,t,n),o&&W(e)}(h&&(m=l&&l.onVnodeUnmounted)||f)&&Ce((()=>{m&&Pe(m,t,e),f&&ye(e,null,t,"unmounted")}),n)},W=e=>{const{type:t,el:n,anchor:o,transition:r}=e;if(t===Fe)return void G(n,o);if(t===$e)return void C(e);const s=()=>{a(n),r&&!r.persisted&&r.afterLeave&&r.afterLeave()};if(1&e.shapeFlag&&r&&!r.persisted){const{leave:t,delayLeave:o}=r,l=()=>t(n,s);o?o(e.el,s,l):l()}else s()},G=(e,t)=>{let n;for(;e!==t;)n=y(e),a(e),e=n;a(t)},K=(e,t,n)=>{const{bum:o,scope:s,update:l,subTree:i,um:a}=e;o&&(0,r.ir)(o),s.stop(),l&&(l.active=!1,V(i,e,t,n)),a&&Ce(a,t),Ce((()=>{e.isUnmounted=!0}),t),t&&t.pendingBranch&&!t.isUnmounted&&e.asyncDep&&!e.asyncResolved&&e.suspenseId===t.pendingId&&(t.deps--,0===t.deps&&t.resolve())},Z=(e,t,n,o=!1,r=!1,s=0)=>{for(let l=s;l6&e.shapeFlag?Y(e.component.subTree):128&e.shapeFlag?e.suspense.next():y(e.anchor||e.el),X=(e,t,n)=>{null==e?t._vnode&&V(t._vnode,null,null,!0):_(t._vnode||null,e,t,null,null,null,n),Vt(),t._vnode=e},Q={p:_,um:V,m:q,r:W,mt:$,mc:I,pc:D,pbc:L,n:Y,o:e};let ee,te;return t&&([ee,te]=t(Q)),{render:X,hydrate:ee,createApp:Oe(X,ee)}}(e,Se)}function je(e,t,n,s,l=!1){if((0,r.kJ)(e))return void e.forEach(((e,o)=>je(e,t&&((0,r.kJ)(t)?t[o]:t),n,s,l)));if(P(s)&&!l)return;const i=4&s.shapeFlag?Et(s.component)||s.component.proxy:s.el,a=l?null:i,{i:c,r:u}=e,d=t&&t.r,p=c.refs===r.kT?c.refs={}:c.refs,f=c.setupState;if(null!=d&&d!==u&&((0,r.HD)(d)?(p[d]=null,(0,r.RI)(f,d)&&(f[d]=null)):(0,o.dq)(d)&&(d.value=null)),(0,r.HD)(u)){const e=()=>{p[u]=a,(0,r.RI)(f,u)&&(f[u]=a)};a?(e.id=-1,Ce(e,n)):e()}else if((0,o.dq)(u)){const e=()=>{u.value=a};a?(e.id=-1,Ce(e,n)):e()}else(0,r.mf)(u)&&St(u,c,12,[a,p])}function Pe(e,t,n,o=null){Ct(e,t,7,[n,o])}function Ae(e,t,n=!1){const o=e.children,s=t.children;if((0,r.kJ)(o)&&(0,r.kJ)(s))for(let e=0;e0?Ne||r.Z6:null,Me.pop(),Ne=Me[Me.length-1]||null,De>0&&Ne&&Ne.push(e),e}function qe(e,t,n,o,r,s){return Je(Xe(e,t,n,o,r,s,!0))}function Ve(e,t,n,o,r){return Je(Qe(e,t,n,o,r,!0))}function We(e){return!!e&&!0===e.__v_isVNode}function Ge(e,t){return e.type===t.type&&e.key===t.key}const Ke="__vInternal",Ze=({key:e})=>null!=e?e:null,Ye=({ref:e})=>null!=e?(0,r.HD)(e)||(0,o.dq)(e)||(0,r.mf)(e)?{i:a,r:e}:e:null;function Xe(e,t=null,n=null,o=0,s=null,l=(e===Fe?0:1),i=!1,a=!1){const u={__v_isVNode:!0,__v_skip:!0,type:e,props:t,key:t&&Ze(t),ref:t&&Ye(t),scopeId:c,slotScopeIds:null,children:n,component:null,suspense:null,ssContent:null,ssFallback:null,dirs:null,transition:null,el:null,anchor:null,target:null,targetAnchor:null,staticCount:0,shapeFlag:l,patchFlag:o,dynamicProps:s,dynamicChildren:null,appContext:null};return a?(lt(u,n),128&l&&e.normalize(u)):n&&(u.shapeFlag|=(0,r.HD)(n)?8:16),De>0&&!i&&Ne&&(u.patchFlag>0||6&l)&&32!==u.patchFlag&&Ne.push(u),u}const Qe=function(e,t=null,n=null,s=0,l=null,i=!1){if(e&&e!==Te||(e=He),We(e)){const o=et(e,t,!0);return n&<(o,n),o}if(a=e,(0,r.mf)(a)&&"__vccOpts"in a&&(e=e.__vccOpts),t){t=function(e){return e?(0,o.X3)(e)||Ke in e?(0,r.l7)({},e):e:null}(t);let{class:e,style:n}=t;e&&!(0,r.HD)(e)&&(t.class=(0,r.C_)(e)),(0,r.Kn)(n)&&((0,o.X3)(n)&&!(0,r.kJ)(n)&&(n=(0,r.l7)({},n)),t.style=(0,r.j5)(n))}var a;return Xe(e,t,n,s,l,(0,r.HD)(e)?1:(e=>e.__isSuspense)(e)?128:(e=>e.__isTeleport)(e)?64:(0,r.Kn)(e)?4:(0,r.mf)(e)?2:0,i,!0)};function et(e,t,n=!1){const{props:o,ref:s,patchFlag:l,children:i}=e,a=t?it(o||{},t):o;return{__v_isVNode:!0,__v_skip:!0,type:e.type,props:a,key:a&&Ze(a),ref:t&&t.ref?n&&s?(0,r.kJ)(s)?s.concat(Ye(t)):[s,Ye(t)]:Ye(t):s,scopeId:e.scopeId,slotScopeIds:e.slotScopeIds,children:i,target:e.target,targetAnchor:e.targetAnchor,staticCount:e.staticCount,shapeFlag:e.shapeFlag,patchFlag:t&&e.type!==Fe?-1===l?16:16|l:l,dynamicProps:e.dynamicProps,dynamicChildren:e.dynamicChildren,appContext:e.appContext,dirs:e.dirs,transition:e.transition,component:e.component,suspense:e.suspense,ssContent:e.ssContent&&et(e.ssContent),ssFallback:e.ssFallback&&et(e.ssFallback),el:e.el,anchor:e.anchor}}function tt(e=" ",t=0){return Qe(ze,null,e,t)}function nt(e,t){const n=Qe($e,null,e);return n.staticCount=t,n}function ot(e="",t=!1){return t?(Ue(),Ve(He,null,e)):Qe(He,null,e)}function rt(e){return null==e||"boolean"==typeof e?Qe(He):(0,r.kJ)(e)?Qe(Fe,null,e.slice()):"object"==typeof e?st(e):Qe(ze,null,String(e))}function st(e){return null===e.el||e.memo?e:et(e)}function lt(e,t){let n=0;const{shapeFlag:o}=e;if(null==t)t=null;else if((0,r.kJ)(t))n=16;else if("object"==typeof t){if(65&o){const n=t.default;return void(n&&(n._c&&(n._d=!1),lt(e,n()),n._c&&(n._d=!0)))}{n=32;const o=t._;o||Ke in t?3===o&&a&&(1===a.slots._?t._=1:(t._=2,e.patchFlag|=1024)):t._ctx=a}}else(0,r.mf)(t)?(t={default:t,_ctx:a},n=32):(t=String(t),64&o?(n=16,t=[tt(t)]):n=8);e.children=t,e.shapeFlag|=n}function it(...e){const t={};for(let n=0;nt(e,n,void 0,l&&l[n])));else{const n=Object.keys(e);s=new Array(n.length);for(let o=0,r=n.length;o!We(e)||e.type!==He&&!(e.type===Fe&&!ut(e.children))))?e:null}const dt=e=>e?wt(e)?Et(e)||e.proxy:dt(e.parent):null,pt=(0,r.l7)(Object.create(null),{$:e=>e,$el:e=>e.vnode.el,$data:e=>e.data,$props:e=>e.props,$attrs:e=>e.attrs,$slots:e=>e.slots,$refs:e=>e.refs,$parent:e=>dt(e.parent),$root:e=>dt(e.root),$emit:e=>e.emit,$options:e=>X(e),$forceUpdate:e=>()=>Dt(e.update),$nextTick:e=>Ut.bind(e.proxy),$watch:e=>Xt.bind(e)}),ft={get({_:e},t){const{ctx:n,setupState:s,data:l,props:i,accessCache:a,type:c,appContext:u}=e;let d;if("$"!==t[0]){const o=a[t];if(void 0!==o)switch(o){case 0:return s[t];case 1:return l[t];case 3:return n[t];case 2:return i[t]}else{if(s!==r.kT&&(0,r.RI)(s,t))return a[t]=0,s[t];if(l!==r.kT&&(0,r.RI)(l,t))return a[t]=1,l[t];if((d=e.propsOptions[0])&&(0,r.RI)(d,t))return a[t]=2,i[t];if(n!==r.kT&&(0,r.RI)(n,t))return a[t]=3,n[t];K&&(a[t]=4)}}const p=pt[t];let f,h;return p?("$attrs"===t&&(0,o.j)(e,"get",t),p(e)):(f=c.__cssModules)&&(f=f[t])?f:n!==r.kT&&(0,r.RI)(n,t)?(a[t]=3,n[t]):(h=u.config.globalProperties,(0,r.RI)(h,t)?h[t]:void 0)},set({_:e},t,n){const{data:o,setupState:s,ctx:l}=e;if(s!==r.kT&&(0,r.RI)(s,t))s[t]=n;else if(o!==r.kT&&(0,r.RI)(o,t))o[t]=n;else if((0,r.RI)(e.props,t))return!1;return!("$"===t[0]&&t.slice(1)in e||(l[t]=n,0))},has({_:{data:e,setupState:t,accessCache:n,ctx:o,appContext:s,propsOptions:l}},i){let a;return void 0!==n[i]||e!==r.kT&&(0,r.RI)(e,i)||t!==r.kT&&(0,r.RI)(t,i)||(a=l[0])&&(0,r.RI)(a,i)||(0,r.RI)(o,i)||(0,r.RI)(pt,i)||(0,r.RI)(s.config.globalProperties,i)}},ht=be();let mt=0;let vt=null;const gt=()=>vt||a,yt=e=>{vt=e,e.scope.on()},bt=()=>{vt&&vt.scope.off(),vt=null};function wt(e){return 4&e.vnode.shapeFlag}let Ot=!1;function _t(e,t,n){(0,r.mf)(t)?e.type.__ssrInlineRender?e.ssrRender=t:e.render=t:(0,r.Kn)(t)&&(e.setupState=(0,o.WL)(t)),kt(e,n)}function kt(e,t,n){const s=e.type;e.render||(e.render=s.render||r.dG),yt(e),(0,o.Jd)(),function(e){const t=X(e),n=e.proxy,s=e.ctx;K=!1,t.beforeCreate&&Z(t.beforeCreate,e,"bc");const{data:l,computed:i,methods:a,watch:c,provide:u,inject:d,created:p,beforeMount:f,mounted:h,beforeUpdate:m,updated:v,activated:b,deactivated:w,beforeDestroy:O,beforeUnmount:_,destroyed:k,unmounted:E,render:S,renderTracked:C,renderTriggered:x,errorCaptured:j,serverPrefetch:P,expose:A,inheritAttrs:I,components:R,directives:F,filters:z}=t;if(d&&function(e,t,n=r.dG,s=!1){(0,r.kJ)(e)&&(e=ne(e));for(const n in e){const l=e[n];let i;i=(0,r.Kn)(l)?"default"in l?y(l.from||n,l.default,!0):y(l.from||n):y(l),(0,o.dq)(i)&&s?Object.defineProperty(t,n,{enumerable:!0,configurable:!0,get:()=>i.value,set:e=>i.value=e}):t[n]=i}}(d,s,null,e.appContext.config.unwrapInjectedRef),a)for(const e in a){const t=a[e];(0,r.mf)(t)&&(s[e]=t.bind(n))}if(l){const t=l.call(n,n);(0,r.Kn)(t)&&(e.data=(0,o.qj)(t))}if(K=!0,i)for(const e in i){const t=i[e],l=(0,r.mf)(t)?t.bind(n,n):(0,r.mf)(t.get)?t.get.bind(n,n):r.dG,a=!(0,r.mf)(t)&&(0,r.mf)(t.set)?t.set.bind(n):r.dG,c=(0,o.Fl)({get:l,set:a});Object.defineProperty(s,e,{enumerable:!0,configurable:!0,get:()=>c.value,set:e=>c.value=e})}if(c)for(const e in c)Y(c[e],s,n,e);if(u){const e=(0,r.mf)(u)?u.call(n):u;Reflect.ownKeys(e).forEach((t=>{g(t,e[t])}))}function H(e,t){(0,r.kJ)(t)?t.forEach((t=>e(t.bind(n)))):t&&e(t.bind(n))}if(p&&Z(p,e,"c"),H(M,f),H(N,h),H(U,m),H(D,v),H(T,b),H(L,w),H(G,j),H(W,C),H(V,x),H(B,_),H(J,E),H(q,P),(0,r.kJ)(A))if(A.length){const t=e.exposed||(e.exposed={});A.forEach((e=>{Object.defineProperty(t,e,{get:()=>n[e],set:t=>n[e]=t})}))}else e.exposed||(e.exposed={});S&&e.render===r.dG&&(e.render=S),null!=I&&(e.inheritAttrs=I),R&&(e.components=R),F&&(e.directives=F)}(e),(0,o.lk)(),bt()}function Et(e){if(e.exposed)return e.exposeProxy||(e.exposeProxy=new Proxy((0,o.WL)((0,o.Xl)(e.exposed)),{get:(t,n)=>n in t?t[n]:n in pt?pt[n](e):void 0}))}function St(e,t,n,o){let r;try{r=o?e(...o):e()}catch(e){xt(e,t,n)}return r}function Ct(e,t,n,o){if((0,r.mf)(e)){const s=St(e,t,n,o);return s&&(0,r.tI)(s)&&s.catch((e=>{xt(e,t,n)})),s}const s=[];for(let r=0;r>>1;Wt(At[o])Wt(e)-Wt(t))),Ht=0;Htnull==e.id?1/0:e.id;function Gt(e){Pt=!1,jt=!0,qt(e),At.sort(((e,t)=>Wt(e)-Wt(t))),r.dG;try{for(It=0;Ite.value,p=!!e._shallow):(0,o.PG)(e)?(u=()=>e,s=!0):(0,r.kJ)(e)?(f=!0,p=e.some(o.PG),u=()=>e.map((e=>(0,o.dq)(e)?e.value:(0,o.PG)(e)?en(e):(0,r.mf)(e)?St(e,c,2):void 0))):u=(0,r.mf)(e)?t?()=>St(e,c,2):()=>{if(!c||!c.isUnmounted)return d&&d(),Ct(e,c,3,[h])}:r.dG,t&&s){const e=u;u=()=>en(e())}let h=e=>{d=y.onStop=()=>{St(e,c,4)}};if(Ot)return h=r.dG,t?n&&Ct(t,c,3,[u(),f?[]:void 0,h]):u(),r.dG;let m=f?[]:Kt;const v=()=>{if(y.active)if(t){const e=y.run();(s||p||(f?e.some(((e,t)=>(0,r.aU)(e,m[t]))):(0,r.aU)(e,m)))&&(d&&d(),Ct(t,c,3,[e,m===Kt?void 0:m,h]),m=e)}else y.run()};let g;v.allowRecurse=!!t,g="sync"===l?v:"post"===l?()=>Ce(v,c&&c.suspense):()=>{!c||c.isMounted?function(e){Jt(e,Tt,Rt,Lt)}(v):v()};const y=new o.qq(u,g);return t?n?v():m=y.run():"post"===l?Ce(y.run.bind(y),c&&c.suspense):y.run(),()=>{y.stop(),c&&c.scope&&(0,r.Od)(c.scope.effects,y)}}function Xt(e,t,n){const o=this.proxy,s=(0,r.HD)(e)?e.includes(".")?Qt(o,e):()=>o[e]:e.bind(o,o);let l;(0,r.mf)(t)?l=t:(l=t.handler,n=t);const i=vt;yt(this);const a=Yt(s,l.bind(o),n);return i?yt(i):bt(),a}function Qt(e,t){const n=t.split(".");return()=>{let t=e;for(let e=0;e{en(e,t)}));else if((0,r.PO)(e))for(const n in e)en(e[n],t);return e}function tn(e,t,n){const o=arguments.length;return 2===o?(0,r.Kn)(t)&&!(0,r.kJ)(t)?We(t)?Qe(e,null,[t]):Qe(e,t):Qe(e,null,t):(o>3?n=Array.prototype.slice.call(arguments,2):3===o&&We(n)&&(n=[n]),Qe(e,t,n))}Symbol("");const nn="3.2.19"},9963:(e,t,n)=>{"use strict";n.d(t,{nZ:()=>r.nZ,EB:()=>r.EB,iH:()=>r.iH,SU:()=>r.SU,YP:()=>r.YP,uT:()=>_,vr:()=>M,F8:()=>L});var o=n(3577),r=n(6252);n(2262);const s="undefined"!=typeof document?document:null,l=new Map,i={insert:(e,t,n)=>{t.insertBefore(e,n||null)},remove:e=>{const t=e.parentNode;t&&t.removeChild(e)},createElement:(e,t,n,o)=>{const r=t?s.createElementNS("http://www.w3.org/2000/svg",e):s.createElement(e,n?{is:n}:void 0);return"select"===e&&o&&null!=o.multiple&&r.setAttribute("multiple",o.multiple),r},createText:e=>s.createTextNode(e),createComment:e=>s.createComment(e),setText:(e,t)=>{e.nodeValue=t},setElementText:(e,t)=>{e.textContent=t},parentNode:e=>e.parentNode,nextSibling:e=>e.nextSibling,querySelector:e=>s.querySelector(e),setScopeId(e,t){e.setAttribute(t,"")},cloneNode(e){const t=e.cloneNode(!0);return"_value"in e&&(t._value=e._value),t},insertStaticContent(e,t,n,o){const r=n?n.previousSibling:t.lastChild;let i=l.get(e);if(!i){const t=s.createElement("template");if(t.innerHTML=o?`${e}`:e,i=t.content,o){const e=i.firstChild;for(;e.firstChild;)i.appendChild(e.firstChild);i.removeChild(e)}l.set(e,i)}return t.insertBefore(i.cloneNode(!0),n),[r?r.nextSibling:t.firstChild,n?n.previousSibling:t.lastChild]}},a=/\s*!important$/;function c(e,t,n){if((0,o.kJ)(n))n.forEach((n=>c(e,t,n)));else if(t.startsWith("--"))e.setProperty(t,n);else{const r=function(e,t){const n=d[t];if(n)return n;let r=(0,o._A)(t);if("filter"!==r&&r in e)return d[t]=r;r=(0,o.kC)(r);for(let n=0;ndocument.createEvent("Event").timeStamp&&(f=()=>performance.now());const e=navigator.userAgent.match(/firefox\/(\d+)/i);h=!!(e&&Number(e[1])<=53)}let m=0;const v=Promise.resolve(),g=()=>{m=0};const y=/(?:Once|Passive|Capture)$/,b=/^on[a-z]/;"undefined"!=typeof HTMLElement&&HTMLElement;const w="transition",O="animation",_=(e,{slots:t})=>(0,r.h)(r.P$,function(e){const t={};for(const n in e)n in k||(t[n]=e[n]);if(!1===e.css)return t;const{name:n="v",type:r,duration:s,enterFromClass:l=`${n}-enter-from`,enterActiveClass:i=`${n}-enter-active`,enterToClass:a=`${n}-enter-to`,appearFromClass:c=l,appearActiveClass:u=i,appearToClass:d=a,leaveFromClass:p=`${n}-leave-from`,leaveActiveClass:f=`${n}-leave-active`,leaveToClass:h=`${n}-leave-to`}=e,m=function(e){if(null==e)return null;if((0,o.Kn)(e))return[C(e.enter),C(e.leave)];{const t=C(e);return[t,t]}}(s),v=m&&m[0],g=m&&m[1],{onBeforeEnter:y,onEnter:b,onEnterCancelled:w,onLeave:O,onLeaveCancelled:_,onBeforeAppear:A=y,onAppear:R=b,onAppearCancelled:T=w}=t,L=(e,t,n)=>{j(e,t?d:a),j(e,t?u:i),n&&n()},F=(e,t)=>{j(e,h),j(e,f),t&&t()},z=e=>(t,n)=>{const o=e?R:b,s=()=>L(t,e,n);E(o,[t,s]),P((()=>{j(t,e?c:l),x(t,e?d:a),S(o)||I(t,r,v,s)}))};return(0,o.l7)(t,{onBeforeEnter(e){E(y,[e]),x(e,l),x(e,i)},onBeforeAppear(e){E(A,[e]),x(e,c),x(e,u)},onEnter:z(!1),onAppear:z(!0),onLeave(e,t){const n=()=>F(e,t);x(e,p),document.body.offsetHeight,x(e,f),P((()=>{j(e,p),x(e,h),S(O)||I(e,r,g,n)})),E(O,[e,n])},onEnterCancelled(e){L(e,!1),E(w,[e])},onAppearCancelled(e){L(e,!0),E(T,[e])},onLeaveCancelled(e){F(e),E(_,[e])}})}(e),t);_.displayName="Transition";const k={name:String,type:String,css:{type:Boolean,default:!0},duration:[String,Number,Object],enterFromClass:String,enterActiveClass:String,enterToClass:String,appearFromClass:String,appearActiveClass:String,appearToClass:String,leaveFromClass:String,leaveActiveClass:String,leaveToClass:String},E=(_.props=(0,o.l7)({},r.P$.props,k),(e,t=[])=>{(0,o.kJ)(e)?e.forEach((e=>e(...t))):e&&e(...t)}),S=e=>!!e&&((0,o.kJ)(e)?e.some((e=>e.length>1)):e.length>1);function C(e){return(0,o.He)(e)}function x(e,t){t.split(/\s+/).forEach((t=>t&&e.classList.add(t))),(e._vtc||(e._vtc=new Set)).add(t)}function j(e,t){t.split(/\s+/).forEach((t=>t&&e.classList.remove(t)));const{_vtc:n}=e;n&&(n.delete(t),n.size||(e._vtc=void 0))}function P(e){requestAnimationFrame((()=>{requestAnimationFrame(e)}))}let A=0;function I(e,t,n,o){const r=e._endId=++A,s=()=>{r===e._endId&&o()};if(n)return setTimeout(s,n);const{type:l,timeout:i,propCount:a}=function(e,t){const n=window.getComputedStyle(e),o=e=>(n[e]||"").split(", "),r=o("transitionDelay"),s=o("transitionDuration"),l=R(r,s),i=o("animationDelay"),a=o("animationDuration"),c=R(i,a);let u=null,d=0,p=0;return t===w?l>0&&(u=w,d=l,p=s.length):t===O?c>0&&(u=O,d=c,p=a.length):(d=Math.max(l,c),u=d>0?l>c?w:O:null,p=u?u===w?s.length:a.length:0),{type:u,timeout:d,propCount:p,hasTransform:u===w&&/\b(transform|all)(,|$)/.test(n.transitionProperty)}}(e,t);if(!l)return o();const c=l+"end";let u=0;const d=()=>{e.removeEventListener(c,p),s()},p=t=>{t.target===e&&++u>=a&&d()};setTimeout((()=>{uT(t)+T(e[n]))))}function T(e){return 1e3*Number(e.slice(0,-1).replace(",","."))}new WeakMap,new WeakMap;const L={beforeMount(e,{value:t},{transition:n}){e._vod="none"===e.style.display?"":e.style.display,n&&t?n.beforeEnter(e):F(e,t)},mounted(e,{value:t},{transition:n}){n&&t&&n.enter(e)},updated(e,{value:t,oldValue:n},{transition:o}){!t!=!n&&(o?t?(o.beforeEnter(e),F(e,!0),o.enter(e)):o.leave(e,(()=>{F(e,!1)})):F(e,t))},beforeUnmount(e,{value:t}){F(e,t)}};function F(e,t){e.style.display=t?e._vod:"none"}const z=(0,o.l7)({patchProp:(e,t,n,s,l=!1,i,a,u,d)=>{"class"===t?function(e,t,n){const o=e._vtc;o&&(t=(t?[t,...o]:[...o]).join(" ")),null==t?e.removeAttribute("class"):n?e.setAttribute("class",t):e.className=t}(e,s,l):"style"===t?function(e,t,n){const r=e.style,s=r.display;if(n)if((0,o.HD)(n))t!==n&&(r.cssText=n);else{for(const e in n)c(r,e,n[e]);if(t&&!(0,o.HD)(t))for(const e in t)null==n[e]&&c(r,e,"")}else e.removeAttribute("style");"_vod"in e&&(r.display=s)}(e,n,s):(0,o.F7)(t)?(0,o.tR)(t)||function(e,t,n,s,l=null){const i=e._vei||(e._vei={}),a=i[t];if(s&&a)a.value=s;else{const[n,c]=function(e){let t;if(y.test(e)){let n;for(t={};n=e.match(y);)e=e.slice(0,e.length-n[0].length),t[n[0].toLowerCase()]=!0}return[(0,o.rs)(e.slice(2)),t]}(t);if(s){const a=i[t]=function(e,t){const n=e=>{const s=e.timeStamp||f();(h||s>=n.attached-1)&&(0,r.$d)(function(e,t){if((0,o.kJ)(t)){const n=e.stopImmediatePropagation;return e.stopImmediatePropagation=()=>{n.call(e),e._stopped=!0},t.map((e=>t=>!t._stopped&&e(t)))}return t}(e,n.value),t,5,[e])};return n.value=e,n.attached=m||(v.then(g),m=f()),n}(s,l);!function(e,t,n,o){e.addEventListener(t,n,o)}(e,n,a,c)}else a&&(function(e,t,n,o){e.removeEventListener(t,n,o)}(e,n,a,c),i[t]=void 0)}}(e,t,0,s,a):("."===t[0]?(t=t.slice(1),1):"^"===t[0]?(t=t.slice(1),0):function(e,t,n,r){return r?"innerHTML"===t||"textContent"===t||!!(t in e&&b.test(t)&&(0,o.mf)(n)):"spellcheck"!==t&&"draggable"!==t&&("form"!==t&&(("list"!==t||"INPUT"!==e.tagName)&&(("type"!==t||"TEXTAREA"!==e.tagName)&&((!b.test(t)||!(0,o.HD)(n))&&t in e))))}(e,t,s,l))?function(e,t,n,r,s,l,i){if("innerHTML"===t||"textContent"===t)return r&&i(r,s,l),void(e[t]=null==n?"":n);if("value"===t&&"PROGRESS"!==e.tagName){e._value=n;const o=null==n?"":n;return e.value!==o&&(e.value=o),void(null==n&&e.removeAttribute(t))}if(""===n||null==n){const r=typeof e[t];if("boolean"===r)return void(e[t]=(0,o.yA)(n));if(null==n&&"string"===r)return e[t]="",void e.removeAttribute(t);if("number"===r){try{e[t]=0}catch(e){}return void e.removeAttribute(t)}}try{e[t]=n}catch(e){}}(e,t,s,i,a,u,d):("true-value"===t?e._trueValue=s:"false-value"===t&&(e._falseValue=s),function(e,t,n,r,s){if(r&&t.startsWith("xlink:"))null==n?e.removeAttributeNS(p,t.slice(6,t.length)):e.setAttributeNS(p,t,n);else{const r=(0,o.Pq)(t);null==n||r&&!(0,o.yA)(n)?e.removeAttribute(t):e.setAttribute(t,r?"":n)}}(e,t,s,l))}},i);let H,$=!1;const M=(...e)=>{const t=(H=$?H:(0,r.Eo)(z),$=!0,H).createApp(...e),{mount:n}=t;return t.mount=e=>{const t=function(e){if((0,o.HD)(e))return document.querySelector(e);return e}(e);if(t)return n(t,!0,t instanceof SVGElement)},t}},3577:(e,t,n)=>{"use strict";function o(e,t){const n=Object.create(null),o=e.split(",");for(let e=0;e!!n[e.toLowerCase()]:e=>!!n[e]}n.d(t,{Z6:()=>g,kT:()=>v,NO:()=>b,dG:()=>y,_A:()=>J,kC:()=>W,Nj:()=>Y,l7:()=>k,E9:()=>ee,aU:()=>K,RI:()=>C,rs:()=>V,yA:()=>l,ir:()=>Z,kJ:()=>x,mf:()=>I,e1:()=>r,S0:()=>N,_N:()=>j,tR:()=>_,Kn:()=>L,F7:()=>O,PO:()=>M,tI:()=>F,Gg:()=>U,DM:()=>P,Pq:()=>s,HD:()=>R,yk:()=>T,WV:()=>p,hq:()=>f,fY:()=>o,C_:()=>d,j5:()=>i,Od:()=>E,zw:()=>h,hR:()=>G,He:()=>X,W7:()=>$});const r=o("Infinity,undefined,NaN,isFinite,isNaN,parseFloat,parseInt,decodeURI,decodeURIComponent,encodeURI,encodeURIComponent,Math,Number,Date,Array,Object,Boolean,String,RegExp,Map,Set,JSON,Intl,BigInt"),s=o("itemscope,allowfullscreen,formnovalidate,ismap,nomodule,novalidate,readonly");function l(e){return!!e||""===e}function i(e){if(x(e)){const t={};for(let n=0;n{if(e){const n=e.split(c);n.length>1&&(t[n[0].trim()]=n[1].trim())}})),t}function d(e){let t="";if(R(e))t=e;else if(x(e))for(let n=0;np(e,t)))}const h=e=>null==e?"":x(e)||L(e)&&(e.toString===z||!I(e.toString))?JSON.stringify(e,m,2):String(e),m=(e,t)=>t&&t.__v_isRef?m(e,t.value):j(t)?{[`Map(${t.size})`]:[...t.entries()].reduce(((e,[t,n])=>(e[`${t} =>`]=n,e)),{})}:P(t)?{[`Set(${t.size})`]:[...t.values()]}:!L(t)||x(t)||M(t)?t:String(t),v={},g=[],y=()=>{},b=()=>!1,w=/^on[^a-z]/,O=e=>w.test(e),_=e=>e.startsWith("onUpdate:"),k=Object.assign,E=(e,t)=>{const n=e.indexOf(t);n>-1&&e.splice(n,1)},S=Object.prototype.hasOwnProperty,C=(e,t)=>S.call(e,t),x=Array.isArray,j=e=>"[object Map]"===H(e),P=e=>"[object Set]"===H(e),A=e=>e instanceof Date,I=e=>"function"==typeof e,R=e=>"string"==typeof e,T=e=>"symbol"==typeof e,L=e=>null!==e&&"object"==typeof e,F=e=>L(e)&&I(e.then)&&I(e.catch),z=Object.prototype.toString,H=e=>z.call(e),$=e=>H(e).slice(8,-1),M=e=>"[object Object]"===H(e),N=e=>R(e)&&"NaN"!==e&&"-"!==e[0]&&""+parseInt(e,10)===e,U=o(",key,ref,onVnodeBeforeMount,onVnodeMounted,onVnodeBeforeUpdate,onVnodeUpdated,onVnodeBeforeUnmount,onVnodeUnmounted"),D=e=>{const t=Object.create(null);return n=>t[n]||(t[n]=e(n))},B=/-(\w)/g,J=D((e=>e.replace(B,((e,t)=>t?t.toUpperCase():"")))),q=/\B([A-Z])/g,V=D((e=>e.replace(q,"-$1").toLowerCase())),W=D((e=>e.charAt(0).toUpperCase()+e.slice(1))),G=D((e=>e?`on${W(e)}`:"")),K=(e,t)=>!Object.is(e,t),Z=(e,t)=>{for(let n=0;n{Object.defineProperty(e,t,{configurable:!0,enumerable:!1,value:n})},X=e=>{const t=parseFloat(e);return isNaN(t)?e:t};let Q;const ee=()=>Q||(Q="undefined"!=typeof globalThis?globalThis:"undefined"!=typeof self?self:"undefined"!=typeof window?window:"undefined"!=typeof global?global:{})},5698:(e,t,n)=>{"use strict";var o=n(3131),r=n(9947),s=n(4611),l=n(6056),i=n(4634),a=n(480),c=n(9963),u=n(6252),d=n(2119),p=n(4546),f=n(2262),h=n(3447),m=n(704);const v=c.vr,g=d.PO;(async()=>{const e=v({name:"VuepressApp",setup(){(0,p.BK)();for(const e of s.l)e();return()=>[(0,u.h)(d.MA),...r.p.map((e=>(0,u.h)(e)))]}}),t=(0,d.p7)({history:g((0,a.U1)(p.HM.value.base)),routes:i.g,scrollBehavior:(e,t,n)=>n||(e.hash?{el:e.hash}:{top:0})});t.beforeResolve((async(e,t)=>{var n;e.path===t.path&&t!==d.AJ||([p.Xp.value]=await Promise.all([(0,p.C4)(e.name),null===(n=l.b[e.name])||void 0===n?void 0:n.__asyncLoader()]))})),((e,t)=>{const n=(0,f.Fl)((()=>(0,p.S)(p.HM.value.locales,t.currentRoute.value.path))),o=(0,f.Fl)((()=>(0,p.kY)(p.HM.value,n.value))),r=(0,f.Fl)((()=>(0,p.hN)(p.Xp.value))),s=(0,f.Fl)((()=>(0,p.lp)(p.Xp.value,o.value))),l=(0,f.Fl)((()=>(0,p.nl)(s.value,r.value,o.value))),i=(0,f.Fl)((()=>(0,p.Vo)(p.Xp.value)));e.provide(p.C3,n),e.provide(p.AE,o),e.provide(p.PY,r),e.provide(p.et,s),e.provide(p.VV,l),e.provide(p.b5,i),Object.defineProperties(e.config.globalProperties,{$frontmatter:{get:()=>r.value},$headTitle:{get:()=>s.value},$lang:{get:()=>i.value},$page:{get:()=>p.Xp.value},$routeLocale:{get:()=>n.value},$site:{get:()=>p.HM.value},$siteLocale:{get:()=>o.value},$withBase:{get:()=>h.pJ}})})(e,t),(e=>{e.component("ClientOnly",m.qx),e.component("Content",m.VY),e.component("OutboundLink",m.MS)})(e);for(const n of o.g)await n({app:e,router:t,siteData:p.HM});return e.use(t),{app:e,router:t}})().then((({app:e,router:t})=>{t.isReady().then((()=>{e.mount("#app")}))}))},4802:(e,t,n)=>{"use strict";n.d(t,{Y:()=>a});var o=n(4150),r=n(480),s=n(6252),l=n(2262),i=n(4546);const a=(0,s.aZ)({name:"Vuepress",setup(){const e=(0,i.Vi)(),t=(0,l.Fl)((()=>{let t;if(e.value.path){const n=e.value.frontmatter.layout;t=(0,r.HD)(n)?n:"Layout"}else t="404";return o.Z[t]||(0,s.up)(t,!1)}));return()=>(0,s.h)(t.value)}})},704:(e,t,n)=>{"use strict";n.d(t,{qx:()=>s,VY:()=>a,MS:()=>u});var o=n(6252),r=n(2262);const s=(0,o.aZ)({setup(e,t){const n=(0,r.iH)(!1);return(0,o.bv)((()=>{n.value=!0})),()=>{var e,o;return n.value?null===(o=(e=t.slots).default)||void 0===o?void 0:o.call(e):null}}});var l=n(6056),i=n(4546);const a=e=>{let t;t=e.pageKey?e.pageKey:(0,i.Vi)().value.key;const n=l.b[t];return n?(0,o.h)(n):(0,o.h)("div","404 Not Found")};a.displayName="Content",a.props={pageKey:{type:String,required:!1}};const c=(0,o.h)("svg",{class:"icon outbound",xmlns:"http://www.w3.org/2000/svg","aria-hidden":"true",focusable:"false",x:"0px",y:"0px",viewBox:"0 0 100 100",width:"15",height:"15"},[(0,o.h)("path",{fill:"currentColor",d:"M18.8,85.1h56l0,0c2.2,0,4-1.8,4-4v-32h-8v28h-48v-48h28v-8h-32l0,0c-2.2,0-4,1.8-4,4v56C14.8,83.3,16.6,85.1,18.8,85.1z"}),(0,o.h)("polygon",{fill:"currentColor",points:"45.7,48.7 51.3,54.3 77.2,28.5 77.2,37.2 85.2,37.2 85.2,14.9 62.8,14.9 62.8,22.9 71.5,22.9"})]),u=(e,{slots:t})=>{var n;return(0,o.h)("span",[c,null===(n=t.default)||void 0===n?void 0:n.call(t)])};u.displayName="OutboundLink",n(4802)},4546:(e,t,n)=>{"use strict";n.d(t,{Xp:()=>i,PY:()=>d,VV:()=>m,et:()=>g,b5:()=>b,C4:()=>c,hN:()=>f,nl:()=>v,lp:()=>y,Vo:()=>w,S:()=>k,kY:()=>P,C3:()=>O,BK:()=>R,HM:()=>S,AE:()=>x,Vi:()=>a,I2:()=>p,I:()=>_,WF:()=>C,I5:()=>j});var o=n(2262),r=n(9706);const s=(0,o.iH)(r.T),l=(0,o.OT)({key:"",path:"",title:"",lang:"",frontmatter:{},excerpt:"",headers:[]}),i=(0,o.iH)(l),a=()=>i,c=async e=>{const t=s.value[e];if(!t)return l;const n=await t();return null!=n?n:l};var u=n(6252);const d=Symbol(""),p=()=>{const e=(0,u.f3)(d);if(!e)throw new Error("usePageFrontmatter() is called without provider.");return e},f=e=>e.frontmatter;var h=n(480);const m=Symbol(""),v=(e,t,n)=>{const o=(0,h.HD)(t.description)?t.description:n.description,r=[...(0,h.kJ)(t.head)?t.head:[],...n.head,["title",{},e],["meta",{name:"description",content:o}]];return(0,h.H7)(r)},g=Symbol(""),y=(e,t)=>`${e.title?`${e.title} | `:""}${t.title}`,b=Symbol(""),w=e=>e.lang||"en",O=Symbol(""),_=()=>{const e=(0,u.f3)(O);if(!e)throw new Error("useRouteLocale() is called without provider.");return e},k=(e,t)=>(0,h.gb)(e,t);var E=n(5220);const S=(0,o.iH)(E.H),C=()=>S,x=Symbol(""),j=()=>{const e=(0,u.f3)(x);if(!e)throw new Error("useSiteLocaleData() is called without provider.");return e},P=(e,t)=>({...e,...e.locales[t]});var A=n(2119);const I=Symbol(""),R=()=>{const e=(0,A.yj)(),t=(()=>{const e=(0,u.f3)(m);if(!e)throw new Error("usePageHead() is called without provider.");return e})(),n=(()=>{const e=(0,u.f3)(b);if(!e)throw new Error("usePageLang() is called without provider.");return e})(),r=(0,o.iH)([]),s=()=>{document.documentElement.lang=n.value,r.value.forEach((e=>{e.parentNode===document.head&&document.head.removeChild(e)})),r.value.splice(0,r.value.length),t.value.forEach((e=>{const t=L(e);null!==t&&(document.head.appendChild(t),r.value.push(t))}))};(0,u.JJ)(I,s),(0,u.bv)((()=>{t.value.forEach((e=>{const t=T(e);t&&r.value.push(t)})),s(),(0,u.YP)((()=>e.path),(()=>s()))}))},T=([e,t,n=""])=>{const o=`head > ${e}${Object.entries(t).map((([e,t])=>(0,h.HD)(t)?`[${e}="${t}"]`:!0===t?`[${e}]`:"")).join("")}`;return Array.from(document.querySelectorAll(o)).find((e=>e.innerText===n))||null},L=([e,t,n])=>{if(!(0,h.HD)(e))return null;const o=document.createElement(e);return(0,h.PO)(t)&&Object.entries(t).forEach((([e,t])=>{(0,h.HD)(t)?o.setAttribute(e,t):!0===t&&o.setAttribute(e,"")})),(0,h.HD)(n)&&o.appendChild(document.createTextNode(n)),o}},7621:(e,t,n)=>{"use strict";n.d(t,{MS:()=>o.MS,C3:()=>r.C3,Vi:()=>r.Vi,I2:()=>r.I2,I:()=>r.I,WF:()=>r.WF,I5:()=>r.I5,vW:()=>s.vW,F2:()=>s.F2,pJ:()=>s.pJ}),n(5698);var o=n(704),r=n(4546),s=n(3447)},3447:(e,t,n)=>{"use strict";n.d(t,{vW:()=>o,F2:()=>r,pJ:()=>i});const o=e=>e,r=e=>e;var s=n(480),l=n(4546);const i=e=>(0,s.ak)(e)?e:`${(0,l.WF)().value.base}${(0,s.FY)(e)}`},1263:(e,t,n)=>{"use strict";n.d(t,{Z:()=>a});var o=n(7621),r=n(2938),s=n(6252),l=n(2119);const i=async(e,...t)=>{const{scrollBehavior:n}=e.options;e.options.scrollBehavior=void 0,await e.replace(...t).finally((()=>e.options.scrollBehavior=n))},a=(0,o.F2)((()=>{(({headerLinkSelector:e,headerAnchorSelector:t,delay:n,offset:a=5})=>{const c=(0,l.tv)(),u=(0,o.Vi)(),d=(0,r.D)((()=>(()=>{var n,o,r,s;const l=Array.from(document.querySelectorAll(e)),u=Array.from(document.querySelectorAll(t)).filter((e=>l.some((t=>t.hash===e.hash)))),d=Math.max(window.pageYOffset,document.documentElement.scrollTop,document.body.scrollTop),p=window.innerHeight+d,f=Math.max(document.documentElement.scrollHeight,document.body.scrollHeight),h=Math.abs(f-p)=(null!==(o=null===(n=t.parentElement)||void 0===n?void 0:n.offsetTop)&&void 0!==o?o:0)-a,m=!l||d<(null!==(s=null===(r=l.parentElement)||void 0===r?void 0:r.offsetTop)&&void 0!==s?s:0)-a;if(!(p||f&&m))continue;const v=decodeURIComponent(c.currentRoute.value.hash),g=decodeURIComponent(t.hash);if(v===g)return;if(h)for(let t=e+1;t{d(),window.addEventListener("scroll",d)})),(0,s.Jd)((()=>{window.removeEventListener("scroll",d)})),(0,s.YP)((()=>u.value.path),d)})({headerLinkSelector:"a.sidebar-item",headerAnchorSelector:".header-anchor",delay:200,offset:5})}))},3051:(e,t,n)=>{"use strict";n.d(t,{Z:()=>c});var o=n(2938),r=n(6252),s=n(2262),l=n(9963);const i=()=>window.pageYOffset||document.documentElement.scrollTop||document.body.scrollTop||0,a=()=>window.scrollTo({top:0,behavior:"smooth"}),c=(0,r.aZ)({name:"BackToTop",setup(){const e=(0,s.iH)(0),t=(0,s.Fl)((()=>e.value>300));(0,r.bv)((()=>{e.value=i(),window.addEventListener("scroll",(0,o.D)((()=>{e.value=i()}),100))}));const n=(0,r.h)("div",{class:"back-to-top",onClick:a});return()=>(0,r.h)(l.uT,{name:"back-to-top"},{default:()=>t.value?n:null})}})},6971:(e,t,n)=>{"use strict";n.d(t,{Z:()=>m});var o=n(7621),r=Object.assign||function(e){for(var t=1;t1&&void 0!==arguments[1]?arguments[1]:{},o=window.Promise||function(e){function t(){}e(t,t)},s=function(e){var t=e.target;t!==R?-1!==C.indexOf(t)&&_({target:t}):O()},p=function(){if(!j&&I.original){var e=window.pageYOffset||document.documentElement.scrollTop||document.body.scrollTop||0;Math.abs(P-e)>A.scrollOffset&&setTimeout(O,150)}},f=function(e){var t=e.key||e.keyCode;"Escape"!==t&&"Esc"!==t&&27!==t||O()},h=function(){var e=arguments.length>0&&void 0!==arguments[0]?arguments[0]:{},t=e;if(e.background&&(R.style.background=e.background),e.container&&e.container instanceof Object&&(t.container=r({},A.container,e.container)),e.template){var n=l(e.template)?e.template:document.querySelector(e.template);t.template=n}return A=r({},A,t),C.forEach((function(e){e.dispatchEvent(d("medium-zoom:update",{detail:{zoom:T}}))})),T},m=function(){var t=arguments.length>0&&void 0!==arguments[0]?arguments[0]:{};return e(r({},A,t))},v=function(){for(var e=arguments.length,t=Array(e),n=0;n0?t.reduce((function(e,t){return[].concat(e,a(t))}),[]):C;return o.forEach((function(e){e.classList.remove("medium-zoom-image"),e.dispatchEvent(d("medium-zoom:detach",{detail:{zoom:T}}))})),C=C.filter((function(e){return-1===o.indexOf(e)})),T},y=function(e,t){var n=arguments.length>2&&void 0!==arguments[2]?arguments[2]:{};return C.forEach((function(o){o.addEventListener("medium-zoom:"+e,t,n)})),x.push({type:"medium-zoom:"+e,listener:t,options:n}),T},b=function(e,t){var n=arguments.length>2&&void 0!==arguments[2]?arguments[2]:{};return C.forEach((function(o){o.removeEventListener("medium-zoom:"+e,t,n)})),x=x.filter((function(n){return!(n.type==="medium-zoom:"+e&&n.listener.toString()===t.toString())})),T},w=function(){var e=arguments.length>0&&void 0!==arguments[0]?arguments[0]:{},t=e.target,n=function(){var e={width:document.documentElement.clientWidth,height:document.documentElement.clientHeight,left:0,top:0,right:0,bottom:0},t=void 0,n=void 0;if(A.container)if(A.container instanceof Object)t=(e=r({},e,A.container)).width-e.left-e.right-2*A.margin,n=e.height-e.top-e.bottom-2*A.margin;else{var o=(l(A.container)?A.container:document.querySelector(A.container)).getBoundingClientRect(),s=o.width,a=o.height,c=o.left,u=o.top;e=r({},e,{width:s,height:a,left:c,top:u})}t=t||e.width-2*A.margin,n=n||e.height-2*A.margin;var d=I.zoomedHd||I.original,p=i(d)?t:d.naturalWidth||t,f=i(d)?n:d.naturalHeight||n,h=d.getBoundingClientRect(),m=h.top,v=h.left,g=h.width,y=h.height,b=Math.min(p,t)/g,w=Math.min(f,n)/y,O=Math.min(b,w),_="scale("+O+") translate3d("+((t-g)/2-v+A.margin+e.left)/O+"px, "+((n-y)/2-m+A.margin+e.top)/O+"px, 0)";I.zoomed.style.transform=_,I.zoomedHd&&(I.zoomedHd.style.transform=_)};return new o((function(e){if(t&&-1===C.indexOf(t))e(T);else if(I.zoomed)e(T);else{if(t)I.original=t;else{if(!(C.length>0))return void e(T);var o=C;I.original=o[0]}if(I.original.dispatchEvent(d("medium-zoom:open",{detail:{zoom:T}})),P=window.pageYOffset||document.documentElement.scrollTop||document.body.scrollTop||0,j=!0,I.zoomed=u(I.original),document.body.appendChild(R),A.template){var r=l(A.template)?A.template:document.querySelector(A.template);I.template=document.createElement("div"),I.template.appendChild(r.content.cloneNode(!0)),document.body.appendChild(I.template)}if(document.body.appendChild(I.zoomed),window.requestAnimationFrame((function(){document.body.classList.add("medium-zoom--opened")})),I.original.classList.add("medium-zoom-image--hidden"),I.zoomed.classList.add("medium-zoom-image--opened"),I.zoomed.addEventListener("click",O),I.zoomed.addEventListener("transitionend",(function t(){j=!1,I.zoomed.removeEventListener("transitionend",t),I.original.dispatchEvent(d("medium-zoom:opened",{detail:{zoom:T}})),e(T)})),I.original.getAttribute("data-zoom-src")){I.zoomedHd=I.zoomed.cloneNode(),I.zoomedHd.removeAttribute("srcset"),I.zoomedHd.removeAttribute("sizes"),I.zoomedHd.src=I.zoomed.getAttribute("data-zoom-src"),I.zoomedHd.onerror=function(){clearInterval(s),console.warn("Unable to reach the zoom image target "+I.zoomedHd.src),I.zoomedHd=null,n()};var s=setInterval((function(){I.zoomedHd.complete&&(clearInterval(s),I.zoomedHd.classList.add("medium-zoom-image--opened"),I.zoomedHd.addEventListener("click",O),document.body.appendChild(I.zoomedHd),n())}),10)}else if(I.original.hasAttribute("srcset")){I.zoomedHd=I.zoomed.cloneNode(),I.zoomedHd.removeAttribute("sizes"),I.zoomedHd.removeAttribute("loading");var i=I.zoomedHd.addEventListener("load",(function(){I.zoomedHd.removeEventListener("load",i),I.zoomedHd.classList.add("medium-zoom-image--opened"),I.zoomedHd.addEventListener("click",O),document.body.appendChild(I.zoomedHd),n()}))}else n()}}))},O=function(){return new o((function(e){!j&&I.original?(j=!0,document.body.classList.remove("medium-zoom--opened"),I.zoomed.style.transform="",I.zoomedHd&&(I.zoomedHd.style.transform=""),I.template&&(I.template.style.transition="opacity 150ms",I.template.style.opacity=0),I.original.dispatchEvent(d("medium-zoom:close",{detail:{zoom:T}})),I.zoomed.addEventListener("transitionend",(function t(){I.original.classList.remove("medium-zoom-image--hidden"),document.body.removeChild(I.zoomed),I.zoomedHd&&document.body.removeChild(I.zoomedHd),document.body.removeChild(R),I.zoomed.classList.remove("medium-zoom-image--opened"),I.template&&document.body.removeChild(I.template),j=!1,I.zoomed.removeEventListener("transitionend",t),I.original.dispatchEvent(d("medium-zoom:closed",{detail:{zoom:T}})),I.original=null,I.zoomed=null,I.zoomedHd=null,I.template=null,e(T)}))):e(T)}))},_=function(){var e=arguments.length>0&&void 0!==arguments[0]?arguments[0]:{},t=e.target;return I.original?O():w({target:t})},k=function(){return A},E=function(){return C},S=function(){return I.original},C=[],x=[],j=!1,P=0,A=n,I={original:null,zoomed:null,zoomedHd:null,template:null};"[object Object]"===Object.prototype.toString.call(t)?A=t:(t||"string"==typeof t)&&v(t),A=r({margin:0,background:"#fff",scrollOffset:40,container:null,template:null},A);var R=c(A.background);document.addEventListener("click",s),document.addEventListener("keyup",f),document.addEventListener("scroll",p),window.addEventListener("resize",O);var T={open:w,close:O,toggle:_,update:h,clone:m,attach:v,detach:g,on:y,off:b,getOptions:k,getImages:E,getZoomedImage:S};return T},f=Symbol("mediumZoom"),h={},m=(0,o.vW)((({app:e,router:t})=>{const n=p(h);n.refresh=(e=":not(a) > img")=>{n.detach(),n.attach(e)},e.provide(f,n),t.afterEach((()=>{setTimeout((()=>n.refresh()),500)}))}))},6243:(e,t,n)=>{"use strict";n.d(t,{Z:()=>i});var o=n(7621),r=n(4865),s=n(6252),l=n(2119);const i=(0,o.F2)((()=>{(0,s.bv)((()=>{const e=(0,l.tv)(),t=new Set;t.add(e.currentRoute.value.path),r.configure({showSpinner:!1}),e.beforeEach((e=>{t.has(e.path)||r.start()})),e.afterEach((e=>{t.add(e.path),r.done()}))}))}))},1843:(e,t,n)=>{"use strict";n.d(t,{Z:()=>m});var o=n(7621),r=n(6252),s=n(2262),l=n(2119),i=n(5472);const a=(0,s.iH)(i.D),c=/[^\x00-\x7F]/,u=e=>e.replace(/[-/\\^$*+?.()|[\]{}]/g,"\\$&"),d=(e,t)=>{const n=t.join(" "),o=e.split(/\s+/g).map((e=>e.trim())).filter((e=>!!e));if(c.test(e))return o.some((e=>n.toLowerCase().indexOf(e)>-1));const r=e.endsWith(" ");return new RegExp(o.map(((e,t)=>o.length!==t+1||r?`(?=.*\\b${u(e)}\\b)`:`(?=.*\\b${u(e)})`)).join("")+".+","gi").test(n)},p=(0,r.aZ)({name:"SearchBox",props:{locales:{type:Object,required:!1,default:()=>({})},hotKeys:{type:Array,required:!1,default:()=>[]},maxSuggestions:{type:Number,required:!1,default:5}},setup(e){const{locales:t,hotKeys:n,maxSuggestions:i}=(0,s.BK)(e),c=(0,l.tv)(),u=(0,o.I)(),p=a,f=(0,s.iH)(null),h=(0,s.iH)(!1),m=(0,s.iH)(""),v=(0,s.Fl)((()=>{var e;return null!==(e=t.value[u.value])&&void 0!==e?e:{}})),g=(({searchIndex:e,routeLocale:t,query:n,maxSuggestions:o})=>{const r=(0,s.Fl)((()=>e.value.filter((e=>e.pathLocale===t.value))));return(0,s.Fl)((()=>{const e=n.value.trim().toLowerCase();if(!e)return[];const t=[],s=(n,r)=>{d(e,[r.title])&&t.push({link:`${n.path}#${r.slug}`,title:n.title,header:r.title});for(const e of r.children){if(t.length>=o.value)return;s(n,e)}};for(const n of r.value){if(t.length>=o.value)break;if(d(e,[n.title,...n.extraFields]))t.push({link:n.path,title:n.title});else for(const e of n.headers){if(t.length>=o.value)break;s(n,e)}}return t}))})({searchIndex:p,routeLocale:u,query:m,maxSuggestions:i}),{focusIndex:y,focusNext:b,focusPrev:w}=(e=>{const t=(0,s.iH)(0);return{focusIndex:t,focusNext:()=>{t.value{t.value>0?t.value-=1:t.value=e.value.length-1}}})(g);(({input:e,hotKeys:t})=>{const n=n=>{e.value&&0!==t.value.length&&n.target===document.body&&t.value.includes(n.key)&&(e.value.focus(),n.preventDefault())};(0,r.bv)((()=>{document.addEventListener("keydown",n)})),(0,r.Jd)((()=>{document.removeEventListener("keydown",n)}))})({input:f,hotKeys:n});const O=(0,s.Fl)((()=>h.value&&!!g.value.length)),_=e=>{if(!O.value)return;const t=g.value[e];t&&c.push(t.link).then((()=>{m.value="",y.value=0}))};return()=>(0,r.h)("form",{class:"search-box",role:"search"},[(0,r.h)("input",{ref:f,type:"search",placeholder:v.value.placeholder,autocomplete:"off",spellcheck:!1,value:m.value,onFocus:()=>h.value=!0,onBlur:()=>h.value=!1,onInput:e=>m.value=e.target.value,onKeydown:e=>{switch(e.key){case"ArrowUp":O.value&&w();break;case"ArrowDown":O.value&&b();break;case"Enter":e.preventDefault(),_(y.value)}}}),O.value&&(0,r.h)("ul",{class:"suggestions",onMouseleave:()=>y.value=-1},g.value.map((({link:e,title:t,header:n},o)=>(0,r.h)("li",{class:["suggestion",{focus:y.value===o}],onMouseenter:()=>y.value=o,onMousedown:()=>_(o)},(0,r.h)("a",{href:e,onClick:e=>e.preventDefault()},[(0,r.h)("span",{class:"page-title"},t),n&&(0,r.h)("span",{class:"page-header"},`> ${n}`)])))))])}}),f={"/":{placeholder:"Search..."}},h=["s","/"],m=(0,o.vW)((({app:e})=>{e.component("SearchBox",(e=>(0,r.h)(p,{locales:f,hotKeys:h,maxSuggestions:10,...e})))}))},1598:(e,t,n)=>{"use strict";n.d(t,{Z:()=>l});var o=n(7621),r=n(2262),s=n(3197);const l=(0,o.vW)((({app:e})=>{const t=(0,s.BV)(),n=e._context.provides[o.C3],l=(0,r.Fl)((()=>(0,s.g$)(t.value,n.value)));e.provide(s.ZS,l),Object.defineProperties(e.config.globalProperties,{$theme:{get:()=>t.value},$themeLocale:{get:()=>l.value}})}))},3197:(e,t,n)=>{"use strict";n.d(t,{g$:()=>c,ZS:()=>i,BV:()=>s,X6:()=>a});var o=n(2232);const r=(0,n(2262).iH)(o.f),s=()=>r;var l=n(6252);const i=Symbol(""),a=()=>{const e=(0,l.f3)(i);if(!e)throw new Error("useThemeLocaleData() is called without provider.");return e},c=(e,t)=>{var n;return{...e,...null===(n=e.locales)||void 0===n?void 0:n[t]}}},480:(e,t,n)=>{"use strict";n.d(t,{H7:()=>r,kJ:()=>o.kJ,mf:()=>o.mf,ak:()=>s,B2:()=>l,R5:()=>i,PO:()=>a,HD:()=>o.HD,U1:()=>c,FY:()=>u,gb:()=>d});var o=n(3577);const r=e=>{const t=new Set,n=[];return e.forEach((e=>{const o=(([e,t,n])=>"meta"===e&&t.name?`${e}.${t.name}`:["title","base"].includes(e)?e:"template"===e&&t.id?`${e}.${t.id}`:JSON.stringify([e,t,n]))(e);t.has(o)||(t.add(o),n.push(e))})),n},s=e=>/^(https?:)?\/\//.test(e),l=e=>/^mailto:/.test(e),i=e=>/^tel:/.test(e),a=e=>"[object Object]"===Object.prototype.toString.call(e),c=e=>e.replace(/\/$/,""),u=e=>e.replace(/^\//,""),d=(e,t)=>{const n=Object.keys(e).sort(((e,t)=>{const n=t.split("/").length-e.split("/").length;return 0!==n?n:t.length-e.length}));for(const e of n)if(t.startsWith(e))return e;return"/"}},2009:(e,t,n)=>{"use strict";n.d(t,{Z:()=>m});var o=n(7621),r=n(6252),s=n(3577);const l=(0,r.aZ)({props:{type:{type:String,required:!1,default:"tip"},text:{type:String,required:!1,default:""},vertical:{type:String,required:!1,default:void 0}},setup:e=>(t,n)=>((0,r.wg)(),(0,r.iD)("span",{class:(0,s.C_)(["badge",e.type]),style:(0,s.j5)({verticalAlign:e.vertical})},[(0,r.WI)(t.$slots,"default",{},(()=>[(0,r.Uk)((0,s.zw)(e.text),1)]))],6))});var i=n(2262);const a=(0,r.aZ)({name:"CodeGroup",setup(e,{slots:t}){const n=(0,i.iH)(-1),o=(0,i.iH)([]);return()=>{var e;const s=((null===(e=t.default)||void 0===e?void 0:e.call(t))||[]).filter((e=>"CodeGroupItem"===e.type.name)).map((e=>(null===e.props&&(e.props={}),e)));return 0===s.length?null:(n.value<0||n.value>s.length-1?(n.value=s.findIndex((e=>""===e.props.active||!0===e.props.active)),-1===n.value&&(n.value=0)):s.forEach(((e,t)=>{e.props.active=t===n.value})),(0,r.h)("div",{class:"code-group"},[(0,r.h)("div",{class:"code-group__nav"},(0,r.h)("ul",{class:"code-group__ul"},s.map(((e,t)=>{const s=t===n.value;return(0,r.h)("li",{class:"code-group__li"},(0,r.h)("button",{ref:e=>{e&&(o.value[t]=e)},class:{"code-group__nav-tab":!0,"code-group__nav-tab-active":s},ariaPressed:s,ariaExpanded:s,onClick:()=>n.value=t,onKeydown:e=>((e,t)=>{" "===e.key||"Enter"===e.key?(e.preventDefault(),n.value=t):"ArrowRight"===e.key?(e.preventDefault(),((e=n.value)=>{e{n.value=e>0?e-1:o.value.length-1,o.value[n.value].focus()})(t))})(e,t)},e.props.title))})))),s]))}}}),c=["aria-selected"],u=(0,r.aZ)({name:"CodeGroupItem"}),d=(0,r.aZ)({...u,props:{title:{type:String,required:!0},active:{type:Boolean,required:!1,default:!1}},setup:function(e){return(t,n)=>((0,r.wg)(),(0,r.iD)("div",{class:(0,s.C_)(["code-group-item",{"code-group-item__active":e.active}]),"aria-selected":e.active},[(0,r.WI)(t.$slots,"default")],10,c))}});var p=n(2791);const f={class:"sr-only"},h=(0,r.aZ)({setup(e){const t=(0,p.X6)();return(e,n)=>((0,r.wg)(),(0,r.j4)((0,i.SU)(o.MS),null,{default:(0,r.w5)((()=>[(0,r._)("span",f,(0,s.zw)((0,i.SU)(t).openInNewWindow),1)])),_:1}))}}),m=(0,o.vW)((({app:e,router:t})=>{e.component("Badge",l),e.component("CodeGroup",a),e.component("CodeGroupItem",d),delete e._context.components.OutboundLink,e.component("OutboundLink",h),e.component("NavbarSearch",(()=>{const t=e.component("Docsearch")||e.component("SearchBox");return t?(0,r.h)(t):null}));const n=t.options.scrollBehavior;t.options.scrollBehavior=async(...e)=>(await(0,p.P$)().wait(),n(...e))}))},8866:(e,t,n)=>{"use strict";n.d(t,{Z:()=>s});var o=n(7621),r=n(2791);const s=(0,o.F2)((()=>{(0,r.OX)(),(0,r.fR)()}))},2791:(e,t,n)=>{"use strict";n.d(t,{OX:()=>O,fR:()=>L,vs:()=>w,sC:()=>C,P$:()=>A,VU:()=>T,X6:()=>U});var o=n(9963);function r(e){return!!(0,o.nZ)()&&((0,o.EB)(e),!0)}const s="undefined"!=typeof window,l=(Object.prototype.toString,()=>{});const i=e=>e();var a=Object.getOwnPropertySymbols,c=Object.prototype.hasOwnProperty,u=Object.prototype.propertyIsEnumerable;Object.defineProperty,Object.defineProperties,Object.getOwnPropertyDescriptors,Object.getOwnPropertySymbols,Object.prototype.hasOwnProperty,Object.prototype.propertyIsEnumerable,Object.defineProperty,Object.defineProperties,Object.getOwnPropertyDescriptors,Object.getOwnPropertySymbols,Object.prototype.hasOwnProperty,Object.prototype.propertyIsEnumerable,Object.defineProperty,Object.getOwnPropertySymbols,Object.prototype.hasOwnProperty,Object.prototype.propertyIsEnumerable,Object.defineProperty,Object.defineProperties,Object.getOwnPropertyDescriptors,Object.getOwnPropertySymbols,Object.prototype.hasOwnProperty,Object.prototype.propertyIsEnumerable,Object.defineProperty,Object.defineProperties,Object.getOwnPropertyDescriptors,Object.getOwnPropertySymbols,Object.prototype.hasOwnProperty,Object.prototype.propertyIsEnumerable,Object.defineProperty,Object.getOwnPropertySymbols,Object.prototype.hasOwnProperty,Object.prototype.propertyIsEnumerable,Object.defineProperty,Object.getOwnPropertySymbols,Object.prototype.hasOwnProperty,Object.prototype.propertyIsEnumerable,Object.getOwnPropertySymbols,Object.prototype.hasOwnProperty,Object.prototype.propertyIsEnumerable;const d=s?window:void 0;s&&window.document,s&&window.navigator,Object.defineProperty,Object.defineProperties,Object.getOwnPropertyDescriptors,Object.getOwnPropertySymbols,Object.prototype.hasOwnProperty,Object.prototype.propertyIsEnumerable;const p={boolean:{read:e=>"true"===e,write:e=>String(e)},object:{read:e=>JSON.parse(e),write:e=>JSON.stringify(e)},number:{read:e=>Number.parseFloat(e),write:e=>String(e)},any:{read:e=>e,write:e=>String(e)},string:{read:e=>e,write:e=>String(e)}};function f(e,t,n=(e=>null==(e=d)?void 0:e.localStorage)(),s={}){var f;const{flush:h="pre",deep:m=!0,listenToStorageChanges:v=!0,window:g=d,eventFilter:y,onError:b=(e=>{console.error(e)})}=s,w=(0,o.SU)(t),O=null==w?"any":"boolean"==typeof w?"boolean":"string"==typeof w?"string":"object"==typeof w||Array.isArray(w)?"object":Number.isNaN(w)?"any":"number",_=(0,o.iH)(t),k=null!=(f=s.serializer)?f:p[O];function E(t){if(n&&(!t||t.key===e))try{const o=t?t.newValue:n.getItem(e);null==o?(_.value=w,null!==w&&n.setItem(e,k.write(w))):_.value=k.read(o)}catch(e){b(e)}}return E(),g&&v&&function(...e){let t,n,s,i;if("string"==typeof e[0]?([n,s,i]=e,t=d):[t,n,s,i]=e,!t)return l;let a=l;const c=(0,o.YP)((()=>(0,o.SU)(t)),(e=>{a(),e&&(e.addEventListener(n,s,i),a=()=>{e.removeEventListener(n,s,i),a=l})}),{immediate:!0,flush:"post"}),u=()=>{c(),a()};r(u)}(g,"storage",E),n&&function(e,t,n={}){const r=n,{eventFilter:s=i}=r,l=((e,t)=>{var n={};for(var o in e)c.call(e,o)&&t.indexOf(o)<0&&(n[o]=e[o]);if(null!=e&&a)for(var o of a(e))t.indexOf(o)<0&&u.call(e,o)&&(n[o]=e[o]);return n})(r,["eventFilter"]);return(0,o.YP)(e,(d=s,p=t,function(...e){d((()=>p.apply(this,e)),{fn:p,thisArg:this,args:e})}),l);var d,p}(_,(()=>{try{null==_.value?n.removeItem(e):n.setItem(e,k.write(_.value))}catch(e){b(e)}}),{flush:h,deep:m,eventFilter:y}),_}function h(e){return function(e,t={}){const{window:n=d}=t;if(!n)return(0,o.iH)(!1);const s=n.matchMedia(e),l=(0,o.iH)(s.matches),i=e=>{l.value=e.matches};return"addEventListener"in s?s.addEventListener("change",i):s.addListener(i),r((()=>{"removeEventListener"in s?s.removeEventListener("change",i):s.removeListener(i)})),l}("(prefers-color-scheme: dark)",e)}var m,v;Object.defineProperty,Object.defineProperties,Object.getOwnPropertyDescriptors,Object.getOwnPropertySymbols,Object.prototype.hasOwnProperty,Object.prototype.propertyIsEnumerable,Object.defineProperty,Object.defineProperties,Object.getOwnPropertyDescriptors,Object.getOwnPropertySymbols,Object.prototype.hasOwnProperty,Object.prototype.propertyIsEnumerable,Object.defineProperty,Object.defineProperties,Object.getOwnPropertyDescriptors,Object.getOwnPropertySymbols,Object.prototype.hasOwnProperty,Object.prototype.propertyIsEnumerable,Object.getOwnPropertySymbols,Object.prototype.hasOwnProperty,Object.prototype.propertyIsEnumerable,new Map,Object.defineProperty,Object.defineProperties,Object.getOwnPropertyDescriptors,Object.getOwnPropertySymbols,Object.prototype.hasOwnProperty,Object.prototype.propertyIsEnumerable,Object.defineProperty,Object.getOwnPropertySymbols,Object.prototype.hasOwnProperty,Object.prototype.propertyIsEnumerable,Object.getOwnPropertySymbols,Object.prototype.hasOwnProperty,Object.prototype.propertyIsEnumerable,Object.defineProperty,Object.getOwnPropertySymbols,Object.prototype.hasOwnProperty,Object.prototype.propertyIsEnumerable,Object.defineProperty,Object.defineProperties,Object.getOwnPropertyDescriptors,Object.getOwnPropertySymbols,Object.prototype.hasOwnProperty,Object.prototype.propertyIsEnumerable,(v=m||(m={})).UP="UP",v.RIGHT="RIGHT",v.DOWN="DOWN",v.LEFT="LEFT",v.NONE="NONE",Object.defineProperty,Object.getOwnPropertySymbols,Object.prototype.hasOwnProperty,Object.prototype.propertyIsEnumerable,Object.defineProperty,Object.defineProperties,Object.getOwnPropertyDescriptors,Object.getOwnPropertySymbols,Object.prototype.hasOwnProperty,Object.prototype.propertyIsEnumerable,Object.defineProperty,Object.getOwnPropertySymbols,Object.prototype.hasOwnProperty,Object.prototype.propertyIsEnumerable,Object.defineProperty,Object.getOwnPropertySymbols,Object.prototype.hasOwnProperty,Object.prototype.propertyIsEnumerable;var g=n(6252),y=n(2262);const b=Symbol(""),w=()=>{const e=(0,g.f3)(b);if(!e)throw new Error("useDarkMode() is called without provider.");return e},O=()=>{const e=U(),t=h(),n=f("vuepress-color-scheme","auto"),o=(0,y.Fl)({get:()=>!!e.value.darkMode&&("auto"===n.value?t.value:"dark"===n.value),set(e){e===t.value?n.value="auto":n.value=e?"dark":"light"}});(0,g.JJ)(b,o),_(o)},_=e=>{const t=(t=e.value)=>{const n=null===window||void 0===window?void 0:window.document.querySelector("html");null==n||n.classList.toggle("dark",t)};(0,g.bv)((()=>{(0,g.YP)(e,t,{immediate:!0})})),(0,g.Ah)((()=>t()))};var k=n(480),E=n(2119);const S=(...e)=>{const t=(0,E.tv)().resolve(...e),n=t.matched[t.matched.length-1];if(!(null==n?void 0:n.redirect))return t;const{redirect:o}=n,r=(0,k.mf)(o)?o(t):o,s=(0,k.HD)(r)?{path:r}:r;return S({hash:t.hash,query:t.query,params:t.params,...s})},C=e=>{const t=S(e);return{text:t.meta.title||e,link:"404"===t.name?e:t.fullPath}};let x=null,j=null;const P={wait:()=>x,pending:()=>{x=new Promise((e=>j=e))},resolve:()=>{null==j||j(),x=null,j=null}},A=()=>P;var I=n(7621);const R=Symbol("sidebarItems"),T=()=>{const e=(0,g.f3)(R);if(!e)throw new Error("useSidebarItems() is called without provider.");return e},L=()=>{const e=U(),t=(0,I.I2)(),n=(0,y.Fl)((()=>F(t.value,e.value)));(0,g.JJ)(R,n)},F=(e,t)=>{var n,o,r,s;const l=null!==(o=null!==(n=e.sidebar)&&void 0!==n?n:t.sidebar)&&void 0!==o?o:"auto",i=null!==(s=null!==(r=e.sidebarDepth)&&void 0!==r?r:t.sidebarDepth)&&void 0!==s?s:2;return e.home||!1===l?[]:"auto"===l?H(i):(0,k.kJ)(l)?$(l,i):(0,k.PO)(l)?M(l,i):[]},z=(e,t)=>t>0?e.map((e=>((e,t)=>({text:e.title,link:`#${e.slug}`,children:z(e.children,t)}))(e,t-1))):[],H=e=>{const t=(0,I.Vi)();return[{text:t.value.title,children:z(t.value.headers,e)}]},$=(e,t)=>{const n=(0,E.yj)(),o=(0,I.Vi)(),r=e=>{var s;let l;if(l=(0,k.HD)(e)?C(e):e,l.children)return{...l,children:l.children.map((e=>r(e)))};if(l.link===n.path){const e=1===(null===(s=o.value.headers[0])||void 0===s?void 0:s.level)?o.value.headers[0].children:o.value.headers;return{...l,children:z(e,t)}}return l};return e.map((e=>r(e)))},M=(e,t)=>{var n;const o=(0,E.yj)(),r=null!==(n=e[(0,k.gb)(e,o.path)])&&void 0!==n?n:[];return $(r,t)};var N=n(3197);const U=()=>(0,N.X6)()},4865:function(e,t,n){var o,r;o=function(){var e,t,n={version:"0.2.0"},o=n.settings={minimum:.08,easing:"ease",positionUsing:"",speed:200,trickle:!0,trickleRate:.02,trickleSpeed:800,showSpinner:!0,barSelector:'[role="bar"]',spinnerSelector:'[role="spinner"]',parent:"body",template:'
'};function r(e,t,n){return en?n:e}function s(e){return 100*(-1+e)}n.configure=function(e){var t,n;for(t in e)void 0!==(n=e[t])&&e.hasOwnProperty(t)&&(o[t]=n);return this},n.status=null,n.set=function(e){var t=n.isStarted();e=r(e,o.minimum,1),n.status=1===e?null:e;var a=n.render(!t),c=a.querySelector(o.barSelector),u=o.speed,d=o.easing;return a.offsetWidth,l((function(t){""===o.positionUsing&&(o.positionUsing=n.getPositioningCSS()),i(c,function(e,t,n){var r;return(r="translate3d"===o.positionUsing?{transform:"translate3d("+s(e)+"%,0,0)"}:"translate"===o.positionUsing?{transform:"translate("+s(e)+"%,0)"}:{"margin-left":s(e)+"%"}).transition="all "+t+"ms "+n,r}(e,u,d)),1===e?(i(a,{transition:"none",opacity:1}),a.offsetWidth,setTimeout((function(){i(a,{transition:"all "+u+"ms linear",opacity:0}),setTimeout((function(){n.remove(),t()}),u)}),u)):setTimeout(t,u)})),this},n.isStarted=function(){return"number"==typeof n.status},n.start=function(){n.status||n.set(0);var e=function(){setTimeout((function(){n.status&&(n.trickle(),e())}),o.trickleSpeed)};return o.trickle&&e(),this},n.done=function(e){return e||n.status?n.inc(.3+.5*Math.random()).set(1):this},n.inc=function(e){var t=n.status;return t?("number"!=typeof e&&(e=(1-t)*r(Math.random()*t,.1,.95)),t=r(t+e,0,.994),n.set(t)):n.start()},n.trickle=function(){return n.inc(Math.random()*o.trickleRate)},e=0,t=0,n.promise=function(o){return o&&"resolved"!==o.state()?(0===t&&n.start(),e++,t++,o.always((function(){0==--t?(e=0,n.done()):n.set((e-t)/e)})),this):this},n.render=function(e){if(n.isRendered())return document.getElementById("nprogress");c(document.documentElement,"nprogress-busy");var t=document.createElement("div");t.id="nprogress",t.innerHTML=o.template;var r,l=t.querySelector(o.barSelector),a=e?"-100":s(n.status||0),u=document.querySelector(o.parent);return i(l,{transition:"all 0 linear",transform:"translate3d("+a+"%,0,0)"}),o.showSpinner||(r=t.querySelector(o.spinnerSelector))&&p(r),u!=document.body&&c(u,"nprogress-custom-parent"),u.appendChild(t),t},n.remove=function(){u(document.documentElement,"nprogress-busy"),u(document.querySelector(o.parent),"nprogress-custom-parent");var e=document.getElementById("nprogress");e&&p(e)},n.isRendered=function(){return!!document.getElementById("nprogress")},n.getPositioningCSS=function(){var e=document.body.style,t="WebkitTransform"in e?"Webkit":"MozTransform"in e?"Moz":"msTransform"in e?"ms":"OTransform"in e?"O":"";return t+"Perspective"in e?"translate3d":t+"Transform"in e?"translate":"margin"};var l=function(){var e=[];function t(){var n=e.shift();n&&n(t)}return function(n){e.push(n),1==e.length&&t()}}(),i=function(){var e=["Webkit","O","Moz","ms"],t={};function n(n){return n=n.replace(/^-ms-/,"ms-").replace(/-([\da-z])/gi,(function(e,t){return t.toUpperCase()})),t[n]||(t[n]=function(t){var n=document.body.style;if(t in n)return t;for(var o,r=e.length,s=t.charAt(0).toUpperCase()+t.slice(1);r--;)if((o=e[r]+s)in n)return o;return t}(n))}function o(e,t,o){t=n(t),e.style[t]=o}return function(e,t){var n,r,s=arguments;if(2==s.length)for(n in t)void 0!==(r=t[n])&&t.hasOwnProperty(n)&&o(e,n,r);else o(e,s[1],s[2])}}();function a(e,t){return("string"==typeof e?e:d(e)).indexOf(" "+t+" ")>=0}function c(e,t){var n=d(e),o=n+t;a(n,t)||(e.className=o.substring(1))}function u(e,t){var n,o=d(e);a(e,t)&&(n=o.replace(" "+t+" "," "),e.className=n.substring(1,n.length-1))}function d(e){return(" "+(e.className||"")+" ").replace(/\s+/gi," ")}function p(e){e&&e.parentNode&&e.parentNode.removeChild(e)}return n},void 0===(r=o.call(t,n,t,e))||(e.exports=r)},2938:(e,t,n)=>{"use strict";function o(e,t,n){var o,r,s;void 0===t&&(t=50),void 0===n&&(n={});var l=null!=(o=n.isImmediate)&&o,i=null!=(r=n.callback)&&r,a=n.maxWait,c=Date.now(),u=[];function d(){if(void 0!==a){var e=Date.now()-c;if(e+t>=a)return a-e}return t}var p=function(){var t=[].slice.call(arguments),n=this;return new Promise((function(o,r){var a=l&&void 0===s;if(void 0!==s&&clearTimeout(s),s=setTimeout((function(){if(s=void 0,c=Date.now(),!l){var o=e.apply(n,t);i&&i(o),u.forEach((function(e){return(0,e.resolve)(o)})),u=[]}}),d()),a){var p=e.apply(n,t);return i&&i(p),o(p)}u.push({resolve:o,reject:r})}))};return p.cancel=function(e){void 0!==s&&clearTimeout(s),u.forEach((function(t){return(0,t.reject)(e)})),u=[]},p}n.d(t,{D:()=>o})},2119:(e,t,n)=>{"use strict";n.d(t,{MA:()=>xe,AJ:()=>F,p7:()=>je,PO:()=>T,yj:()=>Ie,tv:()=>Ae});var o=n(6252),r=n(2262);const s="function"==typeof Symbol&&"symbol"==typeof Symbol.toStringTag,l=e=>s?Symbol(e):"_vr_"+e,i=l("rvlm"),a=l("rvd"),c=l("r"),u=l("rl"),d=l("rvl"),p="undefined"!=typeof window,f=Object.assign;function h(e,t){const n={};for(const o in t){const r=t[o];n[o]=Array.isArray(r)?r.map(e):e(r)}return n}const m=()=>{},v=/\/$/;function g(e,t,n="/"){let o,r={},s="",l="";const i=t.indexOf("?"),a=t.indexOf("#",i>-1?i:0);return i>-1&&(o=t.slice(0,i),s=t.slice(i+1,a>-1?a:t.length),r=e(s)),a>-1&&(o=o||t.slice(0,a),l=t.slice(a,t.length)),o=function(e,t){if(e.startsWith("/"))return e;if(!e)return t;const n=t.split("/"),o=e.split("/");let r,s,l=n.length-1;for(r=0;re===t[n])):1===e.length&&e[0]===t}var k,E;!function(e){e.pop="pop",e.push="push"}(k||(k={})),function(e){e.back="back",e.forward="forward",e.unknown=""}(E||(E={}));const S=/^[^#]+#/;function C(e,t){return e.replace(S,"#")+t}const x=()=>({left:window.pageXOffset,top:window.pageYOffset});function j(e,t){return(history.state?history.state.position-t:-1)+e}const P=new Map;let A=()=>location.protocol+"//"+location.host;function I(e,t){const{pathname:n,search:o,hash:r}=t,s=e.indexOf("#");if(s>-1){let t=r.includes(e.slice(s))?e.slice(s).length:1,n=r.slice(t);return"/"!==n[0]&&(n="/"+n),y(n,"")}return y(n,e)+o+r}function R(e,t,n,o=!1,r=!1){return{back:e,current:t,forward:n,replaced:o,position:window.history.length,scroll:r?x():null}}function T(e){const t=function(e){const{history:t,location:n}=window,o={value:I(e,n)},r={value:t.state};function s(o,s,l){const i=e.indexOf("#"),a=i>-1?(n.host&&document.querySelector("base")?e:e.slice(i))+o:A()+e+o;try{t[l?"replaceState":"pushState"](s,"",a),r.value=s}catch(e){console.error(e),n[l?"replace":"assign"](a)}}return r.value||s(o.value,{back:null,current:o.value,forward:null,position:t.length-1,replaced:!0,scroll:null},!0),{location:o,state:r,push:function(e,n){const l=f({},r.value,t.state,{forward:e,scroll:x()});s(l.current,l,!0),s(e,f({},R(o.value,e,null),{position:l.position+1},n),!1),o.value=e},replace:function(e,n){s(e,f({},t.state,R(r.value.back,e,r.value.forward,!0),n,{position:r.value.position}),!0),o.value=e}}}(e=function(e){if(!e)if(p){const t=document.querySelector("base");e=(e=t&&t.getAttribute("href")||"/").replace(/^\w+:\/\/[^\/]+/,"")}else e="/";return"/"!==e[0]&&"#"!==e[0]&&(e="/"+e),e.replace(v,"")}(e)),n=function(e,t,n,o){let r=[],s=[],l=null;const i=({state:s})=>{const i=I(e,location),a=n.value,c=t.value;let u=0;if(s){if(n.value=i,t.value=s,l&&l===a)return void(l=null);u=c?s.position-c.position:0}else o(i);r.forEach((e=>{e(n.value,a,{delta:u,type:k.pop,direction:u?u>0?E.forward:E.back:E.unknown})}))};function a(){const{history:e}=window;e.state&&e.replaceState(f({},e.state,{scroll:x()}),"")}return window.addEventListener("popstate",i),window.addEventListener("beforeunload",a),{pauseListeners:function(){l=n.value},listen:function(e){r.push(e);const t=()=>{const t=r.indexOf(e);t>-1&&r.splice(t,1)};return s.push(t),t},destroy:function(){for(const e of s)e();s=[],window.removeEventListener("popstate",i),window.removeEventListener("beforeunload",a)}}}(e,t.state,t.location,t.replace),o=f({location:"",base:e,go:function(e,t=!0){t||n.pauseListeners(),history.go(e)},createHref:C.bind(null,e)},t,n);return Object.defineProperty(o,"location",{enumerable:!0,get:()=>t.location.value}),Object.defineProperty(o,"state",{enumerable:!0,get:()=>t.state.value}),o}function L(e){return"string"==typeof e||"symbol"==typeof e}const F={path:"/",name:void 0,params:{},query:{},hash:"",fullPath:"/",matched:[],meta:{},redirectedFrom:void 0},z=l("nf");var H;function $(e,t){return f(new Error,{type:e,[z]:!0},t)}function M(e,t){return e instanceof Error&&z in e&&(null==t||!!(e.type&t))}!function(e){e[e.aborted=4]="aborted",e[e.cancelled=8]="cancelled",e[e.duplicated=16]="duplicated"}(H||(H={}));const N="[^/]+?",U={sensitive:!1,strict:!1,start:!0,end:!0},D=/[.+*?^${}()[\]/\\]/g;function B(e,t){let n=0;for(;nt.length?1===t.length&&80===t[0]?1:-1:0}function J(e,t){let n=0;const o=e.score,r=t.score;for(;n1&&("*"===i||"+"===i)&&t(`A repeatable param (${c}) must be alone in its segment. eg: '/:ids+.`),s.push({type:1,value:c,regexp:u,repeatable:"*"===i||"+"===i,optional:"*"===i||"?"===i})):t("Invalid state to consume buffer"),c="")}function p(){c+=i}for(;af(e,t.meta)),{})}function Y(e,t){const n={};for(const o in e)n[o]=o in t?t[o]:e[o];return n}const X=/#/g,Q=/&/g,ee=/\//g,te=/=/g,ne=/\?/g,oe=/\+/g,re=/%5B/g,se=/%5D/g,le=/%5E/g,ie=/%60/g,ae=/%7B/g,ce=/%7C/g,ue=/%7D/g,de=/%20/g;function pe(e){return encodeURI(""+e).replace(ce,"|").replace(re,"[").replace(se,"]")}function fe(e){return pe(e).replace(oe,"%2B").replace(de,"+").replace(X,"%23").replace(Q,"%26").replace(ie,"`").replace(ae,"{").replace(ue,"}").replace(le,"^")}function he(e){return null==e?"":function(e){return pe(e).replace(X,"%23").replace(ne,"%3F")}(e).replace(ee,"%2F")}function me(e){try{return decodeURIComponent(""+e)}catch(e){}return""+e}function ve(e){const t={};if(""===e||"?"===e)return t;const n=("?"===e[0]?e.slice(1):e).split("&");for(let e=0;ee&&fe(e))):[o&&fe(o)]).forEach((e=>{void 0!==e&&(t+=(t.length?"&":"")+n,null!=e&&(t+="="+e))})):void 0!==o&&(t+=(t.length?"&":"")+n)}return t}function ye(e){const t={};for(const n in e){const o=e[n];void 0!==o&&(t[n]=Array.isArray(o)?o.map((e=>null==e?null:""+e)):null==o?o:""+o)}return t}function be(){let e=[];return{add:function(t){return e.push(t),()=>{const n=e.indexOf(t);n>-1&&e.splice(n,1)}},list:()=>e,reset:function(){e=[]}}}function we(e,t,n,o,r){const s=o&&(o.enterCallbacks[r]=o.enterCallbacks[r]||[]);return()=>new Promise(((l,i)=>{const a=e=>{var a;!1===e?i($(4,{from:n,to:t})):e instanceof Error?i(e):"string"==typeof(a=e)||a&&"object"==typeof a?i($(2,{from:t,to:e})):(s&&o.enterCallbacks[r]===s&&"function"==typeof e&&s.push(e),l())},c=e.call(o&&o.instances[r],t,n,a);let u=Promise.resolve(c);e.length<3&&(u=u.then(a)),u.catch((e=>i(e)))}))}function Oe(e,t,n,o){const r=[];for(const i of e)for(const e in i.components){let a=i.components[e];if("beforeRouteEnter"===t||i.instances[e])if("object"==typeof(l=a)||"displayName"in l||"props"in l||"__vccOpts"in l){const s=(a.__vccOpts||a)[t];s&&r.push(we(s,n,o,i,e))}else{let l=a();r.push((()=>l.then((r=>{if(!r)return Promise.reject(new Error(`Couldn't resolve component "${e}" at "${i.path}"`));const l=(a=r).__esModule||s&&"Module"===a[Symbol.toStringTag]?r.default:r;var a;i.components[e]=l;const c=(l.__vccOpts||l)[t];return c&&we(c,n,o,i,e)()}))))}}var l;return r}function _e(e){const t=(0,o.f3)(c),n=(0,o.f3)(u),s=(0,r.Fl)((()=>t.resolve((0,r.SU)(e.to)))),l=(0,r.Fl)((()=>{const{matched:e}=s.value,{length:t}=e,o=e[t-1],r=n.matched;if(!o||!r.length)return-1;const l=r.findIndex(b.bind(null,o));if(l>-1)return l;const i=Ee(e[t-2]);return t>1&&Ee(o)===i&&r[r.length-1].path!==i?r.findIndex(b.bind(null,e[t-2])):l})),i=(0,r.Fl)((()=>l.value>-1&&function(e,t){for(const n in t){const o=t[n],r=e[n];if("string"==typeof o){if(o!==r)return!1}else if(!Array.isArray(r)||r.length!==o.length||o.some(((e,t)=>e!==r[t])))return!1}return!0}(n.params,s.value.params))),a=(0,r.Fl)((()=>l.value>-1&&l.value===n.matched.length-1&&w(n.params,s.value.params)));return{route:s,href:(0,r.Fl)((()=>s.value.href)),isActive:i,isExactActive:a,navigate:function(n={}){return function(e){if(!(e.metaKey||e.altKey||e.ctrlKey||e.shiftKey||e.defaultPrevented||void 0!==e.button&&0!==e.button)){if(e.currentTarget&&e.currentTarget.getAttribute){const t=e.currentTarget.getAttribute("target");if(/\b_blank\b/i.test(t))return}return e.preventDefault&&e.preventDefault(),!0}}(n)?t[(0,r.SU)(e.replace)?"replace":"push"]((0,r.SU)(e.to)).catch(m):Promise.resolve()}}}const ke=(0,o.aZ)({name:"RouterLink",props:{to:{type:[String,Object],required:!0},replace:Boolean,activeClass:String,exactActiveClass:String,custom:Boolean,ariaCurrentValue:{type:String,default:"page"}},useLink:_e,setup(e,{slots:t}){const n=(0,r.qj)(_e(e)),{options:s}=(0,o.f3)(c),l=(0,r.Fl)((()=>({[Se(e.activeClass,s.linkActiveClass,"router-link-active")]:n.isActive,[Se(e.exactActiveClass,s.linkExactActiveClass,"router-link-exact-active")]:n.isExactActive})));return()=>{const r=t.default&&t.default(n);return e.custom?r:(0,o.h)("a",{"aria-current":n.isExactActive?e.ariaCurrentValue:null,href:n.href,onClick:n.navigate,class:l.value},r)}}});function Ee(e){return e?e.aliasOf?e.aliasOf.path:e.path:""}const Se=(e,t,n)=>null!=e?e:null!=t?t:n;function Ce(e,t){if(!e)return null;const n=e(t);return 1===n.length?n[0]:n}const xe=(0,o.aZ)({name:"RouterView",inheritAttrs:!1,props:{name:{type:String,default:"default"},route:Object},setup(e,{attrs:t,slots:n}){const s=(0,o.f3)(d),l=(0,r.Fl)((()=>e.route||s.value)),c=(0,o.f3)(a,0),u=(0,r.Fl)((()=>l.value.matched[c]));(0,o.JJ)(a,c+1),(0,o.JJ)(i,u),(0,o.JJ)(d,l);const p=(0,r.iH)();return(0,o.YP)((()=>[p.value,u.value,e.name]),(([e,t,n],[o,r,s])=>{t&&(t.instances[n]=e,r&&r!==t&&e&&e===o&&(t.leaveGuards.size||(t.leaveGuards=r.leaveGuards),t.updateGuards.size||(t.updateGuards=r.updateGuards))),!e||!t||r&&b(t,r)&&o||(t.enterCallbacks[n]||[]).forEach((t=>t(e)))}),{flush:"post"}),()=>{const r=l.value,s=u.value,i=s&&s.components[e.name],a=e.name;if(!i)return Ce(n.default,{Component:i,route:r});const c=s.props[e.name],d=c?!0===c?r.params:"function"==typeof c?c(r):c:null,h=(0,o.h)(i,f({},d,t,{onVnodeUnmounted:e=>{e.component.isUnmounted&&(s.instances[a]=null)},ref:p}));return Ce(n.default,{Component:h,route:r})||h}}});function je(e){const t=function(e,t){const n=[],o=new Map;function r(e,n,o){const i=!o,a=function(e){return{path:e.path,redirect:e.redirect,name:e.name,meta:e.meta||{},aliasOf:void 0,beforeEnter:e.beforeEnter,props:G(e),children:e.children||[],instances:{},leaveGuards:new Set,updateGuards:new Set,enterCallbacks:{},components:"components"in e?e.components||{}:{default:e.component}}}(e);a.aliasOf=o&&o.record;const c=Y(t,e),u=[a];if("alias"in e){const t="string"==typeof e.alias?[e.alias]:e.alias;for(const e of t)u.push(f({},a,{components:o?o.record.components:a.components,path:e,aliasOf:o?o.record:a}))}let d,p;for(const t of u){const{path:u}=t;if(n&&"/"!==u[0]){const e=n.record.path,o="/"===e[e.length-1]?"":"/";t.path=n.record.path+(u&&o+u)}if(d=W(t,n,c),o?o.alias.push(d):(p=p||d,p!==d&&p.alias.push(d),i&&e.name&&!K(d)&&s(e.name)),"children"in a){const e=a.children;for(let t=0;t{s(p)}:m}function s(e){if(L(e)){const t=o.get(e);t&&(o.delete(e),n.splice(n.indexOf(t),1),t.children.forEach(s),t.alias.forEach(s))}else{const t=n.indexOf(e);t>-1&&(n.splice(t,1),e.record.name&&o.delete(e.record.name),e.children.forEach(s),e.alias.forEach(s))}}function l(e){let t=0;for(;t=0;)t++;n.splice(t,0,e),e.record.name&&!K(e)&&o.set(e.record.name,e)}return t=Y({strict:!1,end:!0,sensitive:!1},t),e.forEach((e=>r(e))),{addRoute:r,resolve:function(e,t){let r,s,l,i={};if("name"in e&&e.name){if(r=o.get(e.name),!r)throw $(1,{location:e});l=r.record.name,i=f(function(e,t){const n={};for(const o of t)o in e&&(n[o]=e[o]);return n}(t.params,r.keys.filter((e=>!e.optional)).map((e=>e.name))),e.params),s=r.stringify(i)}else if("path"in e)s=e.path,r=n.find((e=>e.re.test(s))),r&&(i=r.parse(s),l=r.record.name);else{if(r=t.name?o.get(t.name):n.find((e=>e.re.test(t.path))),!r)throw $(1,{location:e,currentLocation:t});l=r.record.name,i=f({},t.params,e.params),s=r.stringify(i)}const a=[];let c=r;for(;c;)a.unshift(c.record),c=c.parent;return{name:l,path:s,params:i,matched:a,meta:Z(a)}},removeRoute:s,getRoutes:function(){return n},getRecordMatcher:function(e){return o.get(e)}}}(e.routes,e),n=e.parseQuery||ve,s=e.stringifyQuery||ge,l=e.history,i=be(),a=be(),v=be(),y=(0,r.XI)(F);let O=F;p&&e.scrollBehavior&&"scrollRestoration"in history&&(history.scrollRestoration="manual");const _=h.bind(null,(e=>""+e)),E=h.bind(null,he),S=h.bind(null,me);function C(e,o){if(o=f({},o||y.value),"string"==typeof e){const r=g(n,e,o.path),s=t.resolve({path:r.path},o),i=l.createHref(r.fullPath);return f(r,s,{params:S(s.params),hash:me(r.hash),redirectedFrom:void 0,href:i})}let r;if("path"in e)r=f({},e,{path:g(n,e.path,o.path).path});else{const t=f({},e.params);for(const e in t)null==t[e]&&delete t[e];r=f({},e,{params:E(e.params)}),o.params=E(o.params)}const i=t.resolve(r,o),a=e.hash||"";i.params=_(S(i.params));const c=function(e,t){const n=t.query?e(t.query):"";return t.path+(n&&"?")+n+(t.hash||"")}(s,f({},e,{hash:(u=a,pe(u).replace(ae,"{").replace(ue,"}").replace(le,"^")),path:i.path}));var u;const d=l.createHref(c);return f({fullPath:c,hash:a,query:s===ge?ye(e.query):e.query||{}},i,{redirectedFrom:void 0,href:d})}function A(e){return"string"==typeof e?g(n,e,y.value.path):f({},e)}function I(e,t){if(O!==e)return $(8,{from:t,to:e})}function R(e){return z(e)}function T(e){const t=e.matched[e.matched.length-1];if(t&&t.redirect){const{redirect:n}=t;let o="function"==typeof n?n(e):n;return"string"==typeof o&&(o=o.includes("?")||o.includes("#")?o=A(o):{path:o},o.params={}),f({query:e.query,hash:e.hash,params:e.params},o)}}function z(e,t){const n=O=C(e),o=y.value,r=e.state,l=e.force,i=!0===e.replace,a=T(n);if(a)return z(f(A(a),{state:r,force:l,replace:i}),t||n);const c=n;let u;return c.redirectedFrom=t,!l&&function(e,t,n){const o=t.matched.length-1,r=n.matched.length-1;return o>-1&&o===r&&b(t.matched[o],n.matched[r])&&w(t.params,n.params)&&e(t.query)===e(n.query)&&t.hash===n.hash}(s,o,n)&&(u=$(16,{to:c,from:o}),te(o,o,!0,!1)),(u?Promise.resolve(u):N(c,o)).catch((e=>M(e)?e:Q(e,c,o))).then((e=>{if(e){if(M(e,2))return z(f(A(e.to),{state:r,force:l,replace:i}),t||c)}else e=D(c,o,!0,i,r);return U(c,o,e),e}))}function H(e,t){const n=I(e,t);return n?Promise.reject(n):Promise.resolve()}function N(e,t){let n;const[o,r,s]=function(e,t){const n=[],o=[],r=[],s=Math.max(t.matched.length,e.matched.length);for(let l=0;lb(e,s)))?o.push(s):n.push(s));const i=e.matched[l];i&&(t.matched.find((e=>b(e,i)))||r.push(i))}return[n,o,r]}(e,t);n=Oe(o.reverse(),"beforeRouteLeave",e,t);for(const r of o)r.leaveGuards.forEach((o=>{n.push(we(o,e,t))}));const l=H.bind(null,e,t);return n.push(l),Pe(n).then((()=>{n=[];for(const o of i.list())n.push(we(o,e,t));return n.push(l),Pe(n)})).then((()=>{n=Oe(r,"beforeRouteUpdate",e,t);for(const o of r)o.updateGuards.forEach((o=>{n.push(we(o,e,t))}));return n.push(l),Pe(n)})).then((()=>{n=[];for(const o of e.matched)if(o.beforeEnter&&!t.matched.includes(o))if(Array.isArray(o.beforeEnter))for(const r of o.beforeEnter)n.push(we(r,e,t));else n.push(we(o.beforeEnter,e,t));return n.push(l),Pe(n)})).then((()=>(e.matched.forEach((e=>e.enterCallbacks={})),n=Oe(s,"beforeRouteEnter",e,t),n.push(l),Pe(n)))).then((()=>{n=[];for(const o of a.list())n.push(we(o,e,t));return n.push(l),Pe(n)})).catch((e=>M(e,8)?e:Promise.reject(e)))}function U(e,t,n){for(const o of v.list())o(e,t,n)}function D(e,t,n,o,r){const s=I(e,t);if(s)return s;const i=t===F,a=p?history.state:{};n&&(o||i?l.replace(e.fullPath,f({scroll:i&&a&&a.scroll},r)):l.push(e.fullPath,r)),y.value=e,te(e,t,n,i),ee()}let B;let q,V=be(),X=be();function Q(e,t,n){ee(e);const o=X.list();return o.length?o.forEach((o=>o(e,t,n))):console.error(e),Promise.reject(e)}function ee(e){q||(q=!0,B=l.listen(((e,t,n)=>{const o=C(e),r=T(o);if(r)return void z(f(r,{replace:!0}),o).catch(m);O=o;const s=y.value;var i,a;p&&(i=j(s.fullPath,n.delta),a=x(),P.set(i,a)),N(o,s).catch((e=>M(e,12)?e:M(e,2)?(z(e.to,o).then((e=>{M(e,20)&&!n.delta&&n.type===k.pop&&l.go(-1,!1)})).catch(m),Promise.reject()):(n.delta&&l.go(-n.delta,!1),Q(e,o,s)))).then((e=>{(e=e||D(o,s,!1))&&(n.delta?l.go(-n.delta,!1):n.type===k.pop&&M(e,20)&&l.go(-1,!1)),U(o,s,e)})).catch(m)})),V.list().forEach((([t,n])=>e?n(e):t())),V.reset())}function te(t,n,r,s){const{scrollBehavior:l}=e;if(!p||!l)return Promise.resolve();const i=!r&&function(e){const t=P.get(e);return P.delete(e),t}(j(t.fullPath,0))||(s||!r)&&history.state&&history.state.scroll||null;return(0,o.Y3)().then((()=>l(t,n,i))).then((e=>e&&function(e){let t;if("el"in e){const n=e.el,o="string"==typeof n&&n.startsWith("#"),r="string"==typeof n?o?document.getElementById(n.slice(1)):document.querySelector(n):n;if(!r)return;t=function(e,t){const n=document.documentElement.getBoundingClientRect(),o=e.getBoundingClientRect();return{behavior:t.behavior,left:o.left-n.left-(t.left||0),top:o.top-n.top-(t.top||0)}}(r,e)}else t=e;"scrollBehavior"in document.documentElement.style?window.scrollTo(t):window.scrollTo(null!=t.left?t.left:window.pageXOffset,null!=t.top?t.top:window.pageYOffset)}(e))).catch((e=>Q(e,t,n)))}const ne=e=>l.go(e);let oe;const re=new Set,se={currentRoute:y,addRoute:function(e,n){let o,r;return L(e)?(o=t.getRecordMatcher(e),r=n):r=e,t.addRoute(r,o)},removeRoute:function(e){const n=t.getRecordMatcher(e);n&&t.removeRoute(n)},hasRoute:function(e){return!!t.getRecordMatcher(e)},getRoutes:function(){return t.getRoutes().map((e=>e.record))},resolve:C,options:e,push:R,replace:function(e){return R(f(A(e),{replace:!0}))},go:ne,back:()=>ne(-1),forward:()=>ne(1),beforeEach:i.add,beforeResolve:a.add,afterEach:v.add,onError:X.add,isReady:function(){return q&&y.value!==F?Promise.resolve():new Promise(((e,t)=>{V.add([e,t])}))},install(e){e.component("RouterLink",ke),e.component("RouterView",xe),e.config.globalProperties.$router=this,Object.defineProperty(e.config.globalProperties,"$route",{enumerable:!0,get:()=>(0,r.SU)(y)}),p&&!oe&&y.value===F&&(oe=!0,R(l.location).catch((e=>{})));const t={};for(const e in F)t[e]=(0,r.Fl)((()=>y.value[e]));e.provide(c,this),e.provide(u,(0,r.qj)(t)),e.provide(d,y);const n=e.unmount;re.add(e),e.unmount=function(){re.delete(e),re.size<1&&(O=F,B&&B(),y.value=F,oe=!1,q=!1),n()}}};return se}function Pe(e){return e.reduce(((e,t)=>e.then((()=>t()))),Promise.resolve())}function Ae(){return(0,o.f3)(c)}function Ie(){return(0,o.f3)(u)}}}]); \ No newline at end of file diff --git a/assets/js/8567.307c9fba.js.LICENSE.txt b/assets/js/8567.307c9fba.js.LICENSE.txt new file mode 100644 index 00000000..bab1a5d3 --- /dev/null +++ b/assets/js/8567.307c9fba.js.LICENSE.txt @@ -0,0 +1,8 @@ +/* NProgress, (c) 2013, 2014 Rico Sta. Cruz - http://ricostacruz.com/nprogress + * @license MIT */ + +/*! + * vue-router v4.0.11 + * (c) 2021 Eduardo San Martin Morote + * @license MIT + */ diff --git a/assets/js/app.b3be4a57.js b/assets/js/app.b3be4a57.js new file mode 100644 index 00000000..c0c04694 --- /dev/null +++ b/assets/js/app.b3be4a57.js @@ -0,0 +1 @@ +"use strict";(self.webpackChunk=self.webpackChunk||[]).push([[2143],{3131:(e,t,i)=>{i.d(t,{g:()=>l});var a=i(2009),s=i(1598),d=i(1843),r=i(6971);const l=[a.Z,s.Z,d.Z,r.Z]},9947:(e,t,i)=>{i.d(t,{p:()=>a});const a=[i(3051).Z]},4611:(e,t,i)=>{i.d(t,{l:()=>r});var a=i(8866),s=i(1263),d=i(6243);const r=[a.Z,s.Z,d.Z]},4150:(e,t,i)=>{i.d(t,{Z:()=>s});var a=i(6252);const s={404:(0,a.RC)((()=>i.e(8491).then(i.bind(i,8491)))),Layout:(0,a.RC)((()=>i.e(3293).then(i.bind(i,3293))))}},6056:(e,t,i)=>{i.d(t,{b:()=>s});var a=i(6252);const s={"v-8daa1a0e":(0,a.RC)((()=>i.e(2509).then(i.bind(i,1878)))),"v-98064128":(0,a.RC)((()=>i.e(5126).then(i.bind(i,851)))),"v-da6e2b5e":(0,a.RC)((()=>i.e(5154).then(i.bind(i,5e3)))),"v-cb968696":(0,a.RC)((()=>i.e(8881).then(i.bind(i,9647)))),"v-1c2050b9":(0,a.RC)((()=>i.e(9313).then(i.bind(i,8660)))),"v-3b4e8b58":(0,a.RC)((()=>i.e(2347).then(i.bind(i,9827)))),"v-17b1b5ba":(0,a.RC)((()=>i.e(880).then(i.bind(i,489)))),"v-3c612605":(0,a.RC)((()=>i.e(3426).then(i.bind(i,6497)))),"v-df4716ce":(0,a.RC)((()=>i.e(9135).then(i.bind(i,9674)))),"v-b53ae1f0":(0,a.RC)((()=>i.e(842).then(i.bind(i,2544)))),"v-94709e56":(0,a.RC)((()=>i.e(1308).then(i.bind(i,3137)))),"v-63c19e21":(0,a.RC)((()=>i.e(5874).then(i.bind(i,1267)))),"v-6787fb4c":(0,a.RC)((()=>i.e(6835).then(i.bind(i,235)))),"v-0142f26d":(0,a.RC)((()=>i.e(3656).then(i.bind(i,1727)))),"v-e8c14dd6":(0,a.RC)((()=>i.e(178).then(i.bind(i,9364)))),"v-56016842":(0,a.RC)((()=>i.e(9524).then(i.bind(i,3551)))),"v-f9cbeb06":(0,a.RC)((()=>i.e(3797).then(i.bind(i,4943)))),"v-0206991c":(0,a.RC)((()=>i.e(286).then(i.bind(i,8897)))),"v-7cbce74e":(0,a.RC)((()=>i.e(4779).then(i.bind(i,4932)))),"v-494d9f70":(0,a.RC)((()=>i.e(8331).then(i.bind(i,2444)))),"v-4fcd777f":(0,a.RC)((()=>i.e(9459).then(i.bind(i,4871)))),"v-54e2d192":(0,a.RC)((()=>i.e(2255).then(i.bind(i,5400)))),"v-39e0a9d6":(0,a.RC)((()=>i.e(3995).then(i.bind(i,9436)))),"v-07f01b20":(0,a.RC)((()=>i.e(6306).then(i.bind(i,6752)))),"v-63bef79a":(0,a.RC)((()=>i.e(1571).then(i.bind(i,5143)))),"v-b83148a8":(0,a.RC)((()=>i.e(795).then(i.bind(i,5479)))),"v-bc0867e6":(0,a.RC)((()=>i.e(4785).then(i.bind(i,682)))),"v-7199ca13":(0,a.RC)((()=>i.e(71).then(i.bind(i,7550)))),"v-549f42a4":(0,a.RC)((()=>i.e(2493).then(i.bind(i,4702)))),"v-f8b83d42":(0,a.RC)((()=>i.e(1119).then(i.bind(i,767)))),"v-06986a13":(0,a.RC)((()=>i.e(1324).then(i.bind(i,5194)))),"v-4fe4c9ba":(0,a.RC)((()=>i.e(4114).then(i.bind(i,6644)))),"v-45d60cd4":(0,a.RC)((()=>i.e(4788).then(i.bind(i,9536)))),"v-2073f07c":(0,a.RC)((()=>i.e(2471).then(i.bind(i,2534)))),"v-ee5df3ee":(0,a.RC)((()=>i.e(7152).then(i.bind(i,2378)))),"v-b9b8c0d2":(0,a.RC)((()=>i.e(7698).then(i.bind(i,7809)))),"v-1229168e":(0,a.RC)((()=>i.e(838).then(i.bind(i,6444)))),"v-28c3d309":(0,a.RC)((()=>i.e(7672).then(i.bind(i,990)))),"v-317eb255":(0,a.RC)((()=>i.e(5811).then(i.bind(i,7523)))),"v-9d15fa06":(0,a.RC)((()=>i.e(8893).then(i.bind(i,4681)))),"v-68303986":(0,a.RC)((()=>i.e(4918).then(i.bind(i,161)))),"v-92baae66":(0,a.RC)((()=>i.e(2288).then(i.bind(i,2050)))),"v-55888b62":(0,a.RC)((()=>i.e(4420).then(i.bind(i,9136)))),"v-05036bb3":(0,a.RC)((()=>i.e(1644).then(i.bind(i,4005)))),"v-05d68719":(0,a.RC)((()=>i.e(702).then(i.bind(i,9574)))),"v-36afa754":(0,a.RC)((()=>i.e(8813).then(i.bind(i,2883)))),"v-7d2151d6":(0,a.RC)((()=>i.e(7523).then(i.bind(i,2576)))),"v-d11abda4":(0,a.RC)((()=>i.e(1495).then(i.bind(i,9689)))),"v-40d785a4":(0,a.RC)((()=>i.e(4891).then(i.bind(i,6633)))),"v-375b0a45":(0,a.RC)((()=>i.e(9263).then(i.bind(i,1994)))),"v-60346367":(0,a.RC)((()=>i.e(2775).then(i.bind(i,4705)))),"v-3a9e6ecc":(0,a.RC)((()=>i.e(1608).then(i.bind(i,3787)))),"v-720c0f26":(0,a.RC)((()=>i.e(469).then(i.bind(i,4303)))),"v-55c4d0d7":(0,a.RC)((()=>i.e(8705).then(i.bind(i,5104)))),"v-3706649a":(0,a.RC)((()=>i.e(88).then(i.bind(i,8109))))}},9706:(e,t,i)=>{i.d(t,{T:()=>a});const a={"v-8daa1a0e":()=>i.e(2509).then(i.bind(i,6464)).then((({data:e})=>e)),"v-98064128":()=>i.e(5126).then(i.bind(i,3884)).then((({data:e})=>e)),"v-da6e2b5e":()=>i.e(5154).then(i.bind(i,6299)).then((({data:e})=>e)),"v-cb968696":()=>i.e(8881).then(i.bind(i,5419)).then((({data:e})=>e)),"v-1c2050b9":()=>i.e(9313).then(i.bind(i,1629)).then((({data:e})=>e)),"v-3b4e8b58":()=>i.e(2347).then(i.bind(i,7362)).then((({data:e})=>e)),"v-17b1b5ba":()=>i.e(880).then(i.bind(i,7369)).then((({data:e})=>e)),"v-3c612605":()=>i.e(3426).then(i.bind(i,7893)).then((({data:e})=>e)),"v-df4716ce":()=>i.e(9135).then(i.bind(i,6754)).then((({data:e})=>e)),"v-b53ae1f0":()=>i.e(842).then(i.bind(i,5977)).then((({data:e})=>e)),"v-94709e56":()=>i.e(1308).then(i.bind(i,9796)).then((({data:e})=>e)),"v-63c19e21":()=>i.e(5874).then(i.bind(i,2807)).then((({data:e})=>e)),"v-6787fb4c":()=>i.e(6835).then(i.bind(i,5273)).then((({data:e})=>e)),"v-0142f26d":()=>i.e(3656).then(i.bind(i,3046)).then((({data:e})=>e)),"v-e8c14dd6":()=>i.e(178).then(i.bind(i,431)).then((({data:e})=>e)),"v-56016842":()=>i.e(9524).then(i.bind(i,602)).then((({data:e})=>e)),"v-f9cbeb06":()=>i.e(3797).then(i.bind(i,1274)).then((({data:e})=>e)),"v-0206991c":()=>i.e(286).then(i.bind(i,344)).then((({data:e})=>e)),"v-7cbce74e":()=>i.e(4779).then(i.bind(i,733)).then((({data:e})=>e)),"v-494d9f70":()=>i.e(8331).then(i.bind(i,4976)).then((({data:e})=>e)),"v-4fcd777f":()=>i.e(9459).then(i.bind(i,9869)).then((({data:e})=>e)),"v-54e2d192":()=>i.e(2255).then(i.bind(i,1872)).then((({data:e})=>e)),"v-39e0a9d6":()=>i.e(3995).then(i.bind(i,5010)).then((({data:e})=>e)),"v-07f01b20":()=>i.e(6306).then(i.bind(i,6572)).then((({data:e})=>e)),"v-63bef79a":()=>i.e(1571).then(i.bind(i,3059)).then((({data:e})=>e)),"v-b83148a8":()=>i.e(795).then(i.bind(i,3981)).then((({data:e})=>e)),"v-bc0867e6":()=>i.e(4785).then(i.bind(i,2889)).then((({data:e})=>e)),"v-7199ca13":()=>i.e(71).then(i.bind(i,9055)).then((({data:e})=>e)),"v-549f42a4":()=>i.e(2493).then(i.bind(i,3091)).then((({data:e})=>e)),"v-f8b83d42":()=>i.e(1119).then(i.bind(i,355)).then((({data:e})=>e)),"v-06986a13":()=>i.e(1324).then(i.bind(i,3596)).then((({data:e})=>e)),"v-4fe4c9ba":()=>i.e(4114).then(i.bind(i,381)).then((({data:e})=>e)),"v-45d60cd4":()=>i.e(4788).then(i.bind(i,1475)).then((({data:e})=>e)),"v-2073f07c":()=>i.e(2471).then(i.bind(i,3826)).then((({data:e})=>e)),"v-ee5df3ee":()=>i.e(7152).then(i.bind(i,1866)).then((({data:e})=>e)),"v-b9b8c0d2":()=>i.e(7698).then(i.bind(i,6807)).then((({data:e})=>e)),"v-1229168e":()=>i.e(838).then(i.bind(i,184)).then((({data:e})=>e)),"v-28c3d309":()=>i.e(7672).then(i.bind(i,6114)).then((({data:e})=>e)),"v-317eb255":()=>i.e(5811).then(i.bind(i,6032)).then((({data:e})=>e)),"v-9d15fa06":()=>i.e(8893).then(i.bind(i,9026)).then((({data:e})=>e)),"v-68303986":()=>i.e(4918).then(i.bind(i,1108)).then((({data:e})=>e)),"v-92baae66":()=>i.e(2288).then(i.bind(i,6873)).then((({data:e})=>e)),"v-55888b62":()=>i.e(4420).then(i.bind(i,6510)).then((({data:e})=>e)),"v-05036bb3":()=>i.e(1644).then(i.bind(i,1828)).then((({data:e})=>e)),"v-05d68719":()=>i.e(702).then(i.bind(i,1135)).then((({data:e})=>e)),"v-36afa754":()=>i.e(8813).then(i.bind(i,7689)).then((({data:e})=>e)),"v-7d2151d6":()=>i.e(7523).then(i.bind(i,7172)).then((({data:e})=>e)),"v-d11abda4":()=>i.e(1495).then(i.bind(i,4058)).then((({data:e})=>e)),"v-40d785a4":()=>i.e(4891).then(i.bind(i,8301)).then((({data:e})=>e)),"v-375b0a45":()=>i.e(9263).then(i.bind(i,9922)).then((({data:e})=>e)),"v-60346367":()=>i.e(2775).then(i.bind(i,5262)).then((({data:e})=>e)),"v-3a9e6ecc":()=>i.e(1608).then(i.bind(i,272)).then((({data:e})=>e)),"v-720c0f26":()=>i.e(469).then(i.bind(i,4520)).then((({data:e})=>e)),"v-55c4d0d7":()=>i.e(8705).then(i.bind(i,9620)).then((({data:e})=>e)),"v-3706649a":()=>i.e(88).then(i.bind(i,1801)).then((({data:e})=>e))}},4634:(e,t,i)=>{i.d(t,{g:()=>s});var a=i(4802);const s=[["v-8daa1a0e","/","Introduction",["/index.html","/README.md"]],["v-98064128","/roadmap/","Roadmap",["/roadmap/index.html","/roadmap/README.md"]],["v-da6e2b5e","/rules/","Documentation",["/rules/index.html","/rules/README.md"]],["v-cb968696","/guide/features/acl.html","ACL",["/guide/features/acl","/guide/features/acl.md"]],["v-1c2050b9","/guide/features/consumer_runners.html","Consumer runners",["/guide/features/consumer_runners","/guide/features/consumer_runners.md"]],["v-3b4e8b58","/guide/features/flags.html","Flags",["/guide/features/flags","/guide/features/flags.md"]],["v-17b1b5ba","/guide/features/goroutine.html","Goroutine",["/guide/features/goroutine","/guide/features/goroutine.md"]],["v-3c612605","/guide/features/helper.html","Helpers",["/guide/features/helper","/guide/features/helper.md"]],["v-df4716ce","/guide/features/pagination.html","Pagination",["/guide/features/pagination","/guide/features/pagination.md"]],["v-b53ae1f0","/guide/features/script.html","Running scripts",["/guide/features/script","/guide/features/script.md"]],["v-94709e56","/guide/features/seeder.html","Database Seeding",["/guide/features/seeder","/guide/features/seeder.md"]],["v-63c19e21","/guide/features/test.html","Tests",["/guide/features/test","/guide/features/test.md"]],["v-6787fb4c","/guide/features/upload_files.html","Upload files",["/guide/features/upload_files","/guide/features/upload_files.md"]],["v-0142f26d","/guide/features/validator.html","Validator",["/guide/features/validator","/guide/features/validator.md"]],["v-e8c14dd6","/guide/graphql/dataloaders.html","Dataloaders",["/guide/graphql/dataloaders","/guide/graphql/dataloaders.md"]],["v-56016842","/guide/services/api_logger.html","API logger service",["/guide/services/api_logger","/guide/services/api_logger.md"]],["v-f9cbeb06","/guide/services/app.html","App",["/guide/services/app","/guide/services/app.md"]],["v-0206991c","/guide/services/authentication.html","Authentication Service",["/guide/services/authentication","/guide/services/authentication.md"]],["v-7cbce74e","/guide/services/checkout.html","Checkout.com API",["/guide/services/checkout","/guide/services/checkout.md"]],["v-494d9f70","/guide/services/clock.html","Clock service",["/guide/services/clock","/guide/services/clock.md"]],["v-4fcd777f","/guide/services/clockwork.html","ClockWork",["/guide/services/clockwork","/guide/services/clockwork.md"]],["v-54e2d192","/guide/services/config.html","Config",["/guide/services/config","/guide/services/config.md"]],["v-39e0a9d6","/guide/services/crud.html","CRUD",["/guide/services/crud","/guide/services/crud.md"]],["v-07f01b20","/guide/services/ddos.html","DDOS Protection",["/guide/services/ddos","/guide/services/ddos.md"]],["v-63bef79a","/guide/services/dynamic_link.html","Dynamic link service",["/guide/services/dynamic_link","/guide/services/dynamic_link.md"]],["v-b83148a8","/guide/services/elorus.html","Elorus.com API",["/guide/services/elorus","/guide/services/elorus.md"]],["v-bc0867e6","/guide/services/error_logger.html","Error Logger",["/guide/services/error_logger","/guide/services/error_logger.md"]],["v-7199ca13","/guide/services/exporter.html","Exporter service",["/guide/services/exporter","/guide/services/exporter.md"]],["v-549f42a4","/guide/services/fcm.html","Firebase cloud messaging (FCM) service",["/guide/services/fcm","/guide/services/fcm.md"]],["v-f8b83d42","/guide/services/feature_flag.html","Feature flag service",["/guide/services/feature_flag","/guide/services/feature_flag.md"]],["v-06986a13","/guide/services/file_extractor.html","File extractor service",["/guide/services/file_extractor","/guide/services/file_extractor.md"]],["v-4fe4c9ba","/guide/services/geocoding.html","Geocoding",["/guide/services/geocoding","/guide/services/geocoding.md"]],["v-45d60cd4","/guide/services/google_analytics.html","Google Analytics",["/guide/services/google_analytics","/guide/services/google_analytics.md"]],["v-2073f07c","/guide/services/gql.html","Gql",["/guide/services/gql","/guide/services/gql.md"]],["v-ee5df3ee","/guide/services/html2pdf.html","HTML2PDF service",["/guide/services/html2pdf","/guide/services/html2pdf.md"]],["v-b9b8c0d2","/guide/services/jwt.html","JWT",["/guide/services/jwt","/guide/services/jwt.md"]],["v-1229168e","/guide/services/kubernetes.html","Kubernetes",["/guide/services/kubernetes","/guide/services/kubernetes.md"]],["v-28c3d309","/guide/services/license_plate_recognizer.html","Licence plate recognizer",["/guide/services/license_plate_recognizer","/guide/services/license_plate_recognizer.md"]],["v-317eb255","/guide/services/localizer.html","Localizer service",["/guide/services/localizer","/guide/services/localizer.md"]],["v-9d15fa06","/guide/services/mail.html","Mail Mailjet service",["/guide/services/mail","/guide/services/mail.md"]],["v-68303986","/guide/services/orm_engine.html","ORM Engine",["/guide/services/orm_engine","/guide/services/orm_engine.md"]],["v-92baae66","/guide/services/orm_engine_context.html","ORM Engine Context",["/guide/services/orm_engine_context","/guide/services/orm_engine_context.md"]],["v-55888b62","/guide/services/oss.html","Object Storage Service",["/guide/services/oss","/guide/services/oss.md"]],["v-05036bb3","/guide/services/otp.html","OTP service",["/guide/services/otp","/guide/services/otp.md"]],["v-05d68719","/guide/services/password.html","Password",["/guide/services/password","/guide/services/password.md"]],["v-36afa754","/guide/services/request_logger.html","Request Logger service",["/guide/services/request_logger","/guide/services/request_logger.md"]],["v-7d2151d6","/guide/services/sentry.html","Sentry service",["/guide/services/sentry","/guide/services/sentry.md"]],["v-d11abda4","/guide/services/setting.html","Setting service",["/guide/services/setting","/guide/services/setting.md"]],["v-40d785a4","/guide/services/slack.html","Slack",["/guide/services/slack","/guide/services/slack.md"]],["v-375b0a45","/guide/services/sms.html","SMS Service",["/guide/services/sms","/guide/services/sms.md"]],["v-60346367","/guide/services/stripe.html","Stripe",["/guide/services/stripe","/guide/services/stripe.md"]],["v-3a9e6ecc","/guide/services/template.html","Template",["/guide/services/template","/guide/services/template.md"]],["v-720c0f26","/guide/services/uploader.html","Uploader",["/guide/services/uploader","/guide/services/uploader.md"]],["v-55c4d0d7","/guide/services/websocket.html","WebSocket",["/guide/services/websocket","/guide/services/websocket.md"]],["v-3706649a","/404.html","",["/404"]]].reduce(((e,[t,i,s,d])=>(e.push({name:t,path:i,component:a.Y,meta:{title:s}},...d.map((e=>({path:e,redirect:i})))),e)),[{name:"404",path:"/:catchAll(.*)",component:a.Y}])},5472:(e,t,i)=>{i.d(t,{D:()=>a});const a=[{title:"Introduction",headers:[{level:2,title:"Installation",slug:"installation",children:[]},{level:2,title:"Quick start",slug:"quick-start",children:[{level:3,title:"Register Dev Panel",slug:"register-dev-panel",children:[]},{level:3,title:"DI services",slug:"di-services",children:[]},{level:3,title:"Setting mode",slug:"setting-mode",children:[]},{level:3,title:"Environment variables",slug:"environment-variables",children:[]}]}],path:"/",pathLocale:"/",extraFields:[]},{title:"Roadmap",headers:[{level:2,title:"Planned in next release",slug:"planned-in-next-release",children:[]},{level:2,title:"Planned in near feature",slug:"planned-in-near-feature",children:[]}],path:"/roadmap/",pathLocale:"/",extraFields:[]},{title:"Documentation",headers:[{level:2,title:"Localhost tools",slug:"localhost-tools",children:[{level:3,title:"force-alters",slug:"force-alters",children:[]}]},{level:2,title:"Domains",slug:"domains",children:[]},{level:2,title:"Crons (scripts)",slug:"crons-scripts",children:[]},{level:2,title:"Naming conventions and rules",slug:"naming-conventions-and-rules",children:[]},{level:2,title:"Teamwork rules",slug:"teamwork-rules",children:[]}],path:"/rules/",pathLocale:"/",extraFields:[]},{title:"ACL",headers:[],path:"/guide/features/acl.html",pathLocale:"/",extraFields:[]},{title:"Consumer runners",headers:[{level:3,title:"ConsumerRunner - non scalable",slug:"consumerrunner-non-scalable",children:[]},{level:3,title:"ScalableConsumerRunner - scalable",slug:"scalableconsumerrunner-scalable",children:[]}],path:"/guide/features/consumer_runners.html",pathLocale:"/",extraFields:[]},{title:"Flags",headers:[{level:2,title:"Pre deploy",slug:"pre-deploy",children:[]},{level:2,title:"Force alters",slug:"force-alters",children:[]}],path:"/guide/features/flags.html",pathLocale:"/",extraFields:[]},{title:"Goroutine",headers:[],path:"/guide/features/goroutine.html",pathLocale:"/",extraFields:[]},{title:"Helpers",headers:[],path:"/guide/features/helper.html",pathLocale:"/",extraFields:[]},{title:"Pagination",headers:[],path:"/guide/features/pagination.html",pathLocale:"/",extraFields:[]},{title:"Running scripts",headers:[],path:"/guide/features/script.html",pathLocale:"/",extraFields:[]},{title:"Database Seeding",headers:[],path:"/guide/features/seeder.html",pathLocale:"/",extraFields:[]},{title:"Tests",headers:[],path:"/guide/features/test.html",pathLocale:"/",extraFields:[]},{title:"Upload files",headers:[],path:"/guide/features/upload_files.html",pathLocale:"/",extraFields:[]},{title:"Validator",headers:[],path:"/guide/features/validator.html",pathLocale:"/",extraFields:[]},{title:"Dataloaders",headers:[{level:2,title:"How to create a dataloader?",slug:"how-to-create-a-dataloader",children:[]},{level:2,title:"How to use a dataloader?",slug:"how-to-use-a-dataloader",children:[]}],path:"/guide/graphql/dataloaders.html",pathLocale:"/",extraFields:[]},{title:"API logger service",headers:[],path:"/guide/services/api_logger.html",pathLocale:"/",extraFields:[]},{title:"App",headers:[],path:"/guide/services/app.html",pathLocale:"/",extraFields:[]},{title:"Authentication Service",headers:[],path:"/guide/services/authentication.html",pathLocale:"/",extraFields:[]},{title:"Checkout.com API",headers:[],path:"/guide/services/checkout.html",pathLocale:"/",extraFields:[]},{title:"Clock service",headers:[],path:"/guide/services/clock.html",pathLocale:"/",extraFields:[]},{title:"ClockWork",headers:[],path:"/guide/services/clockwork.html",pathLocale:"/",extraFields:[]},{title:"Config",headers:[{level:2,title:"Environment variables in config file",slug:"environment-variables-in-config-file",children:[]}],path:"/guide/services/config.html",pathLocale:"/",extraFields:[]},{title:"CRUD",headers:[{level:3,title:"Search vs Filter",slug:"search-vs-filter",children:[]},{level:3,title:"defining columns",slug:"defining-columns",children:[]},{level:3,title:"Use Select fields with dependency",slug:"use-select-fields-with-dependency",children:[]},{level:3,title:"Use CRUD with our export service",slug:"use-crud-with-our-export-service",children:[]}],path:"/guide/services/crud.html",pathLocale:"/",extraFields:[]},{title:"DDOS Protection",headers:[],path:"/guide/services/ddos.html",pathLocale:"/",extraFields:[]},{title:"Dynamic link service",headers:[],path:"/guide/services/dynamic_link.html",pathLocale:"/",extraFields:[]},{title:"Elorus.com API",headers:[],path:"/guide/services/elorus.html",pathLocale:"/",extraFields:[]},{title:"Error Logger",headers:[],path:"/guide/services/error_logger.html",pathLocale:"/",extraFields:[]},{title:"Exporter service",headers:[],path:"/guide/services/exporter.html",pathLocale:"/",extraFields:[]},{title:"Firebase cloud messaging (FCM) service",headers:[],path:"/guide/services/fcm.html",pathLocale:"/",extraFields:[]},{title:"Feature flag service",headers:[],path:"/guide/services/feature_flag.html",pathLocale:"/",extraFields:[]},{title:"File extractor service",headers:[],path:"/guide/services/file_extractor.html",pathLocale:"/",extraFields:[]},{title:"Geocoding",headers:[],path:"/guide/services/geocoding.html",pathLocale:"/",extraFields:[]},{title:"Google Analytics",headers:[],path:"/guide/services/google_analytics.html",pathLocale:"/",extraFields:[]},{title:"Gql",headers:[],path:"/guide/services/gql.html",pathLocale:"/",extraFields:[]},{title:"HTML2PDF service",headers:[],path:"/guide/services/html2pdf.html",pathLocale:"/",extraFields:[]},{title:"JWT",headers:[],path:"/guide/services/jwt.html",pathLocale:"/",extraFields:[]},{title:"Kubernetes",headers:[],path:"/guide/services/kubernetes.html",pathLocale:"/",extraFields:[]},{title:"Licence plate recognizer",headers:[],path:"/guide/services/license_plate_recognizer.html",pathLocale:"/",extraFields:[]},{title:"Localizer service",headers:[],path:"/guide/services/localizer.html",pathLocale:"/",extraFields:[]},{title:"Mail Mailjet service",headers:[],path:"/guide/services/mail.html",pathLocale:"/",extraFields:[]},{title:"ORM Engine",headers:[],path:"/guide/services/orm_engine.html",pathLocale:"/",extraFields:[]},{title:"ORM Engine Context",headers:[],path:"/guide/services/orm_engine_context.html",pathLocale:"/",extraFields:[]},{title:"Object Storage Service",headers:[],path:"/guide/services/oss.html",pathLocale:"/",extraFields:[]},{title:"OTP service",headers:[{level:2,title:"Retry feature:",slug:"retry-feature",children:[]},{level:2,title:"Use case",slug:"use-case",children:[]}],path:"/guide/services/otp.html",pathLocale:"/",extraFields:[]},{title:"Password",headers:[],path:"/guide/services/password.html",pathLocale:"/",extraFields:[]},{title:"Request Logger service",headers:[],path:"/guide/services/request_logger.html",pathLocale:"/",extraFields:[]},{title:"Sentry service",headers:[],path:"/guide/services/sentry.html",pathLocale:"/",extraFields:[]},{title:"Setting service",headers:[],path:"/guide/services/setting.html",pathLocale:"/",extraFields:[]},{title:"Slack",headers:[],path:"/guide/services/slack.html",pathLocale:"/",extraFields:[]},{title:"SMS Service",headers:[],path:"/guide/services/sms.html",pathLocale:"/",extraFields:[]},{title:"Stripe",headers:[],path:"/guide/services/stripe.html",pathLocale:"/",extraFields:[]},{title:"Template",headers:[],path:"/guide/services/template.html",pathLocale:"/",extraFields:[]},{title:"Uploader",headers:[],path:"/guide/services/uploader.html",pathLocale:"/",extraFields:[]},{title:"WebSocket",headers:[],path:"/guide/services/websocket.html",pathLocale:"/",extraFields:[]},{title:"",headers:[],path:"/404.html",pathLocale:"/",extraFields:[]}]},5220:(e,t,i)=>{i.d(t,{H:()=>a});const a={base:"/hitrix/",lang:"en-US",title:"Hitrix - golang framework",description:"Golang Framework for high traffic applications. Designed for speed up development time.",head:[["link",{rel:"shortcut icon",href:"logo-favicon.png"}],["meta",{name:"theme-color",content:"#D7A318"}],["meta",{name:"apple-mobile-web-app-capable",content:"yes"}],["meta",{name:"apple-mobile-web-app-status-bar-style",content:"black"}]],locales:{}}},2232:(e,t,i)=>{i.d(t,{f:()=>a});const a={repo:"https://github.com/coretrix/hitrix",docsRepo:"https://github.com/coretrix/hitrix",logo:"/logo-favicon-90x90.png",editLinks:!0,docsDir:"docs/docs",editLinkText:"",lastUpdated:!0,smoothScroll:!0,algolia:{apiKey:"21fc128e1fae00977c070d0f30e7b0cd",indexName:"vuepress",appId:"WMOLNCX6UH",algoliaOptions:{facetFilters:["tags:v1"]}},navbar:[{text:"Guide",link:"/"},{text:"Roadmap",link:"/roadmap/"},{text:"CoreTrix Rules",link:"/rules/"}],sidebar:[{text:"Guide",children:[{text:"Introduction",collapsible:!1,link:"/"},{text:"Services",children:[{text:"App",link:"/guide/services/app"},{text:"Config",link:"/guide/services/config"},{text:"ORM Engine",link:"/guide/services/orm_engine"},{text:"ORM Engine Context",link:"/guide/services/orm_engine_context"},{text:"OSS",link:"/guide/services/oss"},{text:"API Logger",link:"/guide/services/api_logger"},{text:"Authentication",link:"/guide/services/authentication"},{text:"Clock",link:"/guide/services/clock"},{text:"Checkout",link:"/guide/services/checkout"},{text:"CRUD",link:"/guide/services/crud"},{text:"DDOS",link:"/guide/services/ddos"},{text:"Dynamic link",link:"/guide/services/dynamic_link"},{text:"Elorus - Invoice manager",link:"/guide/services/elorus"},{text:"Error logger",link:"/guide/services/error_logger"},{text:"Firebase cloud messaging",link:"/guide/services/fcm"},{text:"File extractor",link:"/guide/services/file_extractor"},{text:"Localizer",link:"/guide/services/localizer"},{text:"Google Analytics",link:"/guide/services/google_analytics"},{text:"Transactional email sender",link:"/guide/services/mail"},{text:"JWT",link:"/guide/services/jwt"},{text:"OTP",link:"/guide/services/otp"},{text:"Password",link:"/guide/services/password"},{text:"HTML2PDF",link:"/guide/services/html2pdf"},{text:"Slack",link:"/guide/services/slack"},{text:"SMS",link:"/guide/services/sms"},{text:"Setting",link:"/guide/services/setting"},{text:"Stripe",link:"/guide/services/stripe"},{text:"Uploader",link:"/guide/services/uploader"},{text:"WebSocket",link:"/guide/services/websocket"},{text:"Exporter",link:"/guide/services/exporter"},{text:"Feature flags",link:"/guide/services/feature_flag"},{text:"ClockWork",link:"/guide/services/clockwork"},{text:"Kubernetes",link:"/guide/services/kubernetes"},{text:"Template",link:"/guide/services/template"},{text:"Gql",link:"/guide/services/gql"},{text:"Request Logger",link:"/guide/services/request_logger"}]},{text:"Features",children:[{text:"Flags",link:"/guide/features/flags"},{text:"Background scripts",link:"/guide/features/script"},{text:"Seeder",link:"/guide/features/seeder"},{text:"Validator",link:"/guide/features/validator"},{text:"Pagination",link:"/guide/features/pagination"},{text:"Integration test",link:"/guide/features/test"},{text:"Helper",link:"/guide/features/helper"},{text:"Goroutine",link:"/guide/features/goroutine"},{text:"Upload files",link:"/guide/features/upload_files"}]},{text:"GraphQL",children:[{text:"Dataloaders",link:"/guide/graphql/dataloaders"}]}]}],locales:{"/":{selectLanguageName:"English"}},darkMode:!0,selectLanguageText:"Languages",selectLanguageAriaLabel:"Select language",sidebarDepth:2,editLink:!0,lastUpdatedText:"Last Updated",contributors:!0,contributorsText:"Contributors",notFound:["There's nothing here.","How did we get here?","That's a Four-Oh-Four.","Looks like we've got some broken links."],backToHome:"Take me home",openInNewWindow:"open in new window",toggleDarkMode:"toggle dark mode",toggleSidebar:"toggle sidebar"}}},e=>{e.O(0,[2512,8567],(()=>(5698,e(e.s=5698)))),e.O()}]); \ No newline at end of file diff --git a/assets/js/runtime~app.7229c1cf.js b/assets/js/runtime~app.7229c1cf.js new file mode 100644 index 00000000..c33c7532 --- /dev/null +++ b/assets/js/runtime~app.7229c1cf.js @@ -0,0 +1 @@ +(()=>{"use strict";var e,a,r={},d={};function v(e){var a=d[e];if(void 0!==a)return a.exports;var c=d[e]={exports:{}};return r[e].call(c.exports,c,c.exports,v),c.exports}v.m=r,e=[],v.O=(a,r,d,c)=>{if(!r){var t=1/0;for(n=0;n=c)&&Object.keys(v.O).every((e=>v.O[e](r[b])))?r.splice(b--,1):(f=!1,c0&&e[n-1][2]>c;n--)e[n]=e[n-1];e[n]=[r,d,c]},v.d=(e,a)=>{for(var r in a)v.o(a,r)&&!v.o(e,r)&&Object.defineProperty(e,r,{enumerable:!0,get:a[r]})},v.f={},v.e=e=>Promise.all(Object.keys(v.f).reduce(((a,r)=>(v.f[r](e,a),a)),[])),v.u=e=>"assets/js/"+({71:"v-7199ca13",88:"v-3706649a",178:"v-e8c14dd6",286:"v-0206991c",469:"v-720c0f26",702:"v-05d68719",795:"v-b83148a8",838:"v-1229168e",842:"v-b53ae1f0",880:"v-17b1b5ba",1119:"v-f8b83d42",1308:"v-94709e56",1324:"v-06986a13",1495:"v-d11abda4",1571:"v-63bef79a",1608:"v-3a9e6ecc",1644:"v-05036bb3",2255:"v-54e2d192",2288:"v-92baae66",2347:"v-3b4e8b58",2471:"v-2073f07c",2493:"v-549f42a4",2509:"v-8daa1a0e",2775:"v-60346367",3426:"v-3c612605",3656:"v-0142f26d",3797:"v-f9cbeb06",3995:"v-39e0a9d6",4114:"v-4fe4c9ba",4420:"v-55888b62",4779:"v-7cbce74e",4785:"v-bc0867e6",4788:"v-45d60cd4",4891:"v-40d785a4",4918:"v-68303986",5126:"v-98064128",5154:"v-da6e2b5e",5811:"v-317eb255",5874:"v-63c19e21",6306:"v-07f01b20",6835:"v-6787fb4c",7152:"v-ee5df3ee",7523:"v-7d2151d6",7672:"v-28c3d309",7698:"v-b9b8c0d2",8331:"v-494d9f70",8705:"v-55c4d0d7",8813:"v-36afa754",8881:"v-cb968696",8893:"v-9d15fa06",9135:"v-df4716ce",9263:"v-375b0a45",9313:"v-1c2050b9",9459:"v-4fcd777f",9524:"v-56016842"}[e]||e)+"."+{71:"394b0308",88:"a81808dd",178:"163b3d24",286:"a8af3b50",469:"92e015a0",702:"cc6091d2",795:"77493078",838:"7fddad3c",842:"3fd59914",880:"9712a74a",1119:"6c3e2a39",1308:"cc8e4ec4",1324:"bf9ddaf3",1495:"b8b22d35",1571:"6a3c557e",1608:"0bb25cd2",1644:"e29841b7",2255:"85b8ddf1",2288:"600b3911",2347:"76bea257",2471:"f9b76b3a",2493:"e30474a7",2509:"3e229fe7",2775:"66c4dbc2",3293:"43d80d8f",3426:"f865f2b0",3656:"408083d1",3797:"51708ef5",3995:"89bf367b",4114:"6108bea2",4420:"7ef3c924",4779:"e148fb1a",4785:"ccc8387c",4788:"9fd7f3d6",4891:"6651a1cb",4918:"c6b80122",5126:"a44cd149",5154:"19354693",5811:"13a38855",5874:"87a3ab13",6306:"5dd30e3f",6835:"04042151",7152:"e465b6dc",7523:"e7d1a198",7672:"77c074f0",7698:"2376f019",8331:"68d7268f",8491:"d9869bda",8705:"7e271966",8813:"763b36f3",8881:"67f1f322",8893:"88d80137",9135:"0053e281",9263:"c9170900",9313:"de81a832",9459:"b22e4bec",9524:"c0df6684"}[e]+".js",v.miniCssF=e=>"assets/css/styles.1ec23e2d.css",v.o=(e,a)=>Object.prototype.hasOwnProperty.call(e,a),a={},v.l=(e,r,d,c)=>{if(a[e])a[e].push(r);else{var t,f;if(void 0!==d)for(var b=document.getElementsByTagName("script"),o=0;o{t.onerror=t.onload=null,clearTimeout(s);var v=a[e];if(delete a[e],t.parentNode&&t.parentNode.removeChild(t),v&&v.forEach((e=>e(d))),r)return r(d)},s=setTimeout(i.bind(null,void 0,{type:"timeout",target:t}),12e4);t.onerror=i.bind(null,t.onerror),t.onload=i.bind(null,t.onload),f&&document.head.appendChild(t)}},v.r=e=>{"undefined"!=typeof Symbol&&Symbol.toStringTag&&Object.defineProperty(e,Symbol.toStringTag,{value:"Module"}),Object.defineProperty(e,"__esModule",{value:!0})},v.p="/hitrix/",(()=>{var e={523:0,2512:0};v.f.j=(a,r)=>{var d=v.o(e,a)?e[a]:void 0;if(0!==d)if(d)r.push(d[2]);else if(/^(2512|523)$/.test(a))e[a]=0;else{var c=new Promise(((r,v)=>d=e[a]=[r,v]));r.push(d[2]=c);var t=v.p+v.u(a),f=new Error;v.l(t,(r=>{if(v.o(e,a)&&(0!==(d=e[a])&&(e[a]=void 0),d)){var c=r&&("load"===r.type?"missing":r.type),t=r&&r.target&&r.target.src;f.message="Loading chunk "+a+" failed.\n("+c+": "+t+")",f.name="ChunkLoadError",f.type=c,f.request=t,d[1](f)}}),"chunk-"+a,a)}},v.O.j=a=>0===e[a];var a=(a,r)=>{var d,c,[t,f,b]=r,o=0;if(t.some((a=>0!==e[a]))){for(d in f)v.o(f,d)&&(v.m[d]=f[d]);if(b)var n=b(v)}for(a&&a(r);o{s.r(a),s.d(a,{data:()=>t});const t={key:"v-0142f26d",path:"/guide/features/validator.html",title:"Validator",lang:"en-US",frontmatter:{},excerpt:"",headers:[],filePathRelative:"guide/features/validator.md",git:{updatedTime:1666683363e3,contributors:[{name:"Anton",email:"a.shumansky@gmail.com",commits:3}]}}},1727:(n,a,s)=>{s.r(a),s.d(a,{default:()=>p});const t=(0,s(6252).uE)('

Validator

We support 2 types of validators. One of them is related to graphql, the other one is related to rest.

Graphql validator

There are 2 steps that needs to be executed if you want to use this kind of validator

  1. Add directive @validate(rules: String!) on INPUT_FIELD_DEFINITION into your schema.graphqls file

  2. Call ValidateDirective into your main.go file

config := generated.Config{Resolvers: &graph.Resolver{}, Directives: generated.DirectiveRoot{Validate: hitrix.ValidateDirective()} }\n\ns.RunServer(4001, generated.NewExecutableSchema(config), func(ginEngine *gin.Engine) {\n    commonMiddleware.Cors(ginEngine)\n    middleware.Router(ginEngine)\n})\n
1
2
3
4
5
6

After that you can define the validation rules in that way:

input ApplePurchaseRequest {\n  ForceEmail: Boolean!\n  Name: String\n  Email: String @validate(rules: "email") #for rules param you can use everything supported by https://github.com/go-playground/validator validate.Var(value, rules)\n  AppleReceipt: String!\n}\n
1
2
3
4
5
6

To handle the errors you need to call function hitrix.Validate(ctx, nil) in your resolver

func (r *mutationResolver) RegisterTransactions(ctx context.Context, applePurchaseRequest model.ApplePurchaseRequest) (*model.RegisterTransactionsResponse, error) {\n    if !hitrix.Validate(ctx, nil) {\n        return nil, nil\n    }\n    // your logic here...\n}\n
1
2
3
4
5
6

The function hitrix.Validate(ctx, nil) as second param accept callback where you can define your custom validation related to business logic

REST validator

You should define tags for every field

type RequestDTOMerchantSave struct {\n\tStoreID         string `conform:"trim" binding:"required,min=1,max=30"`\n\tStoreBio        string `conform:"trim" binding:"omitempty,min=5,max=1000"`\n\tAvatarFileID    *uint64\n\tContactPhone    *ContactPhone    `binding:"omitempty"`\n\tContactWhatsapp *ContactWhatsapp `binding:"omitempty"`\n\tContactWeb      string           `binding:"omitempty,url"`\n\tContactTelegram *ContactTelegram `binding:"omitempty"`\n\tContactEmail    string           `conform:"trim" binding:"omitempty,email"`\n}\n
1
2
3
4
5
6
7
8
9
10

Using binding you can define all rules needed for the particular validation Using conform you can trim the value before validation to be applied

Validation notes
RepatriationAfterTyreBlockInMinutes int               `binding:"numeric,gte=0"`\n

If you want to support 0 value you should not put required tag for the fields because the validator thinks that zero is not a value

',18),e={},p=(0,s(3744).Z)(e,[["render",function(n,a){return t}]])},3744:(n,a)=>{a.Z=(n,a)=>{for(const[s,t]of a)n[s]=t;return n}}}]); \ No newline at end of file diff --git a/assets/js/v-0206991c.a8af3b50.js b/assets/js/v-0206991c.a8af3b50.js new file mode 100644 index 00000000..60147e0d --- /dev/null +++ b/assets/js/v-0206991c.a8af3b50.js @@ -0,0 +1 @@ +"use strict";(self.webpackChunk=self.webpackChunk||[]).push([[286],{344:(n,s,a)=>{a.r(s),a.d(s,{data:()=>e});const e={key:"v-0206991c",path:"/guide/services/authentication.html",title:"Authentication Service",lang:"en-US",frontmatter:{},excerpt:"",headers:[],filePathRelative:"guide/services/authentication.md",git:{updatedTime:1689160831e3,contributors:[{name:"Iliyan",email:"iliyan.motovski@coretrix.com",commits:3},{name:"Anton",email:"a.shumansky@gmail.com",commits:1},{name:"alhasaniq",email:"alhasan.nasiry@gmail.com",commits:1}]}}},8897:(n,s,a)=>{a.r(s),a.d(s,{default:()=>o});const e=(0,a(6252).uE)('

Authentication Service

This service is used to making the life easy by doing the whole authentication life cycle using JWT token.

Register the service into your main.go file:

registry.ServiceProviderAuthentication(),\n
1

Access the service:

service.DI().Authentication()\n
1
Dependencies :

JWTService

PasswordService

ClockService

GeneratorService

GoogleService # optional , when you need to support google login

FacebookService # optional , when you need to support facebook login

AppleService # optional , when you need to support apple login

func Authenticate(ormService *beeorm.Engine, uniqueValue string, password string, entity AuthProviderEntity) (accessToken string, refreshToken string, err error) {}\nfunc VerifyAccessToken(ormService *beeorm.Engine, accessToken string, entity beeorm.Entity) error {}\nfunc VerifySocialLogin(ctx context.Context, source, token string, isAndroid bool)\nfunc RefreshToken(ormService *beeorm.Engine, refreshToken string) (newAccessToken string, newRefreshToken string, err error) {}\nfunc LogoutCurrentSession(ormService *beeorm.Engine, accessKey string){}\nfunc LogoutAllSessions(ormService *beeorm.Engine, id uint64)\nfunc AuthenticateOTP(ormService *beeorm.Engine, phone string, entity OTPProviderEntity) (accessToken string, refreshToken string, err error){}\n
1
2
3
4
5
6
7
  1. The Authenticate function will take an uniqueValue such as Email or Mobile, a plain password, and generates accessToken and refreshToken. You will also need to pass your entity as third argument, and it will give you the specific user entity related to provided access token The entity should implement the AuthProviderEntity interface :
       type AuthProviderEntity interface {\n    beeorm.Entity\n    GetUniqueFieldName() string\n    GetPassword() string\n   }\n
    1
    2
    3
    4
    5
    The example of such entity is as follows:
    type UserEntity struct {\n    beeorm.ORM  `orm:"table=users;redisCache;redisSearch=search_pool"`\n    ID       uint64 `orm:"searchable;sortable"`\n    Email    string `orm:"required;unique=Email;searchable"`\n    Password string `orm:"required"`\n}\n\nfunc (user *UserEntity) GetUniqueFieldName() string {\n    return "Email"\n}\n\nfunc (user *UserEntity) GetPassword() string {\nreturn user.Password\n}\n
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
  2. The VerifyAccessToken will get the AccessToken, process the validation and expiration, and fill the entity param with the authenticated user entity in case of successful authentication.
  3. The RefreshToken method will generate a new token pair for given user
  4. The LogoutCurrentSession you can logout the user current session , you need to pass it the accessKey that is the jwt identifier jti the exists in both access and refresh token.
  5. The LogoutAllSessions you can logout the user from all sessions , you need to pass it the id (user id).
  6. You need to have a authentication key in your config file for this service to work. secret key under authentication is mandatory but other options are optional:
  7. The service can also support OTP if you want your service to support otp you should have support_otp key set to true under authentication
  8. The service also needs redis to store its sessions so you need to identify the redis storage name in config , the key is auth_redis under authentication
authentication:\n  secret: "a-deep-dark-secret" #mandatory, secret to be used for JWT\n  access_token_ttl: 86400 # optional, in seconds, default to 1day\n  refresh_token_ttl: 31536000 #optional, in seconds, default to 1year\n  auth_redis: default #optional , default is the default redis\n  otp_ttl: 120 #optional ,set it when you want to use otp, It is the ttl of otp code , default is 60 seconds\n  otp_length: 5 #optional, set if you want to customize the length of otp (i.e. Email OTP)\n
1
2
3
4
5
6
7
',17),t={},o=(0,a(3744).Z)(t,[["render",function(n,s){return e}]])},3744:(n,s)=>{s.Z=(n,s)=>{for(const[a,e]of s)n[a]=e;return n}}}]); \ No newline at end of file diff --git a/assets/js/v-05036bb3.e29841b7.js b/assets/js/v-05036bb3.e29841b7.js new file mode 100644 index 00000000..7b6c35f3 --- /dev/null +++ b/assets/js/v-05036bb3.e29841b7.js @@ -0,0 +1 @@ +"use strict";(self.webpackChunk=self.webpackChunk||[]).push([[1644],{1828:(n,s,a)=>{a.r(s),a.d(s,{data:()=>e});const e={key:"v-05036bb3",path:"/guide/services/otp.html",title:"OTP service",lang:"en-US",frontmatter:{},excerpt:"",headers:[{level:2,title:"Retry feature:",slug:"retry-feature",children:[]},{level:2,title:"Use case",slug:"use-case",children:[]}],filePathRelative:"guide/services/otp.md",git:{updatedTime:1652968637e3,contributors:[{name:"Iliyan",email:"iliyan.motovski@coretrix.com",commits:3},{name:"Yavor",email:"qvor.vasilev@gmail.com",commits:1},{name:"majid mohsenifar",email:"majid.mohsenifar@coretrix.com",commits:1}]}}},4005:(n,s,a)=>{a.r(s),a.d(s,{default:()=>t});const e=(0,a(6252).uE)('

OTP service

If you want to authenticate your user using OTP you may need to use OTP service. This service can send the code using SMS or even call and verifying it later.

Register the service into your main.go file:

registry.ServiceProviderOTP()\n
1

Supported OTP providers:

  1. Twilio
  2. Sinch
  3. Mada

Now it is possible to provide phone number prefixes for each OTP provider.

For example if I provide the following setting Twilio;Mada:+35987,+35988, this means if phone numer starts with either of +35987 or +35988, then we will use Mada, for all others numbers we will use Twilio.

You can register OTP providers in 2 ways:

  1. Provide setting in DB for key: otp_sms_provider with value either of Twilio or Sinch or Mada. You can pass all providers as well separated by semicolon - Twilio;Mada:+35987,+35988;Sinch.
  2. Call registry.ServiceProviderOTP(otp.SMSOTPProviderTwilio, otp.SMSOTPProviderSinch) with 1 or more parameters for force provider.

Retry feature:

You can set up the service to retry failed OTP send attempts. In order to do this, you need to add in the config:

sms:\n  retry: true\n  max_retries: 20\n
1
2
3

For retry feature you also need to start in your app this consumer:

    // add this if you want to use send OTP retry feature\n    s.RunBackgroundProcess(func(b *hitrix.BackgroundProcessor) {\n\t    go b.RunScript(&scripts.RetryOTPConsumer{})\n    })\n
1
2
3
4

Retry feature uses exponential backoff to retry OTP requests, starting from 0.5 seconds. If max_retries is reached, the consumer will drop the OTP request and mark it unsendable in DB.

Access the service:

service.DI().OTP()\n
1

Use case

You can send OTP to user phone using SMS or call like this:

package auth\nimport (\n    "context"\n    "service"\n    "github.com/coretrix/hitrix/service/component/otp"\n)\n\nfunc SendOTP(){\n    ormService := service.DI().OrmEngineForContext(context.Background())\n    OTPService := service.DI().OTP()\n\n    // add this if you want to use send OTP retry feature\n    s.RunBackgroundProcess(func(b *hitrix.BackgroundProcessor) {\n    \tgo b.RunScript(&scripts.RetryOTPConsumer{})\n    })\n\n\t//SMS\n    code, err := OTPService.SendSMS(ormService, &otp.Phone{\n        Number: "+123456789",\n    })\n\n    //call\n    code, err := OTPService.Call(ormService, &otp.Phone{\n        Number: "+123456789",\n    })\n}\n
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26

Then you can verify OTP like this:

package auth\nimport (\n    "context"\n    "service"\n    "github.com/coretrix/hitrix/service/component/otp"\n)\n\nfunc Verify(){\n    ormService := service.DI().OrmEngineForContext(context.Background())\n    OTPService := service.DI().OTP()\n    code:="1234" //the code user entered\n\n    otpRequestValid, otpCodeValid, err := OTPService.Verify(\n    ormService,\n    &otp.Phone{Number: "+123456789"},\n    code,\n    )\n}\n
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
',23),p={},t=(0,a(3744).Z)(p,[["render",function(n,s){return e}]])},3744:(n,s)=>{s.Z=(n,s)=>{for(const[a,e]of s)n[a]=e;return n}}}]); \ No newline at end of file diff --git a/assets/js/v-05d68719.cc6091d2.js b/assets/js/v-05d68719.cc6091d2.js new file mode 100644 index 00000000..299b172f --- /dev/null +++ b/assets/js/v-05d68719.cc6091d2.js @@ -0,0 +1 @@ +"use strict";(self.webpackChunk=self.webpackChunk||[]).push([[702],{1135:(s,a,n)=>{n.r(a),n.d(a,{data:()=>e});const e={key:"v-05d68719",path:"/guide/services/password.html",title:"Password",lang:"en-US",frontmatter:{},excerpt:"",headers:[],filePathRelative:"guide/services/password.md",git:{updatedTime:1685967966e3,contributors:[{name:"Anton",email:"a.shumansky@gmail.com",commits:1},{name:"Krasimir Ivanov",email:"krasimir.ivanov@coretrix.com",commits:1}]}}},9574:(s,a,n)=>{n.r(a),n.d(a,{default:()=>o});const e=(0,n(6252).uE)('

Password

This service it can be used to hash and verify hashed passwords.

Register the service into your main.go file:

 registry.ServiceProviderPassword(password.NewSimpleManager)\n
1

Access the service:

service.DI().Password()\n
1
',6),t={},o=(0,n(3744).Z)(t,[["render",function(s,a){return e}]])},3744:(s,a)=>{a.Z=(s,a)=>{for(const[n,e]of a)s[n]=e;return s}}}]); \ No newline at end of file diff --git a/assets/js/v-06986a13.bf9ddaf3.js b/assets/js/v-06986a13.bf9ddaf3.js new file mode 100644 index 00000000..9d27384a --- /dev/null +++ b/assets/js/v-06986a13.bf9ddaf3.js @@ -0,0 +1 @@ +"use strict";(self.webpackChunk=self.webpackChunk||[]).push([[1324],{3596:(n,s,a)=>{a.r(s),a.d(s,{data:()=>e});const e={key:"v-06986a13",path:"/guide/services/file_extractor.html",title:"File extractor service",lang:"en-US",frontmatter:{},excerpt:"",headers:[],filePathRelative:"guide/services/file_extractor.md",git:{updatedTime:1634291321e3,contributors:[{name:"Anton",email:"a.shumansky@gmail.com",commits:1}]}}},5194:(n,s,a)=>{a.r(s),a.d(s,{default:()=>p});const e=(0,a(6252).uE)('

File extractor service

File extractor provides you a simple function to search in a path recursively and find terms based on a regular expression.

Register the service into your main.go file:

registry.ServiceProviderExtractor(),\n
1

Access the service:

service.DI().FileExtractorService()\n
1

Extract phrase (errors in this example):

errorTerms, err := extractService.Extract(fileextractor.ExtractParams{\n  SearchPath: "./",\n  Excludes:   []string{},\n  Expression: `errors.New[(]*\\("([^)]*)"\\)`,\n})\nif err != nil {\n  // handle error\n}\n
1
2
3
4
5
6
7
8
',8),t={},p=(0,a(3744).Z)(t,[["render",function(n,s){return e}]])},3744:(n,s)=>{s.Z=(n,s)=>{for(const[a,e]of s)n[a]=e;return n}}}]); \ No newline at end of file diff --git a/assets/js/v-07f01b20.5dd30e3f.js b/assets/js/v-07f01b20.5dd30e3f.js new file mode 100644 index 00000000..36ecdf35 --- /dev/null +++ b/assets/js/v-07f01b20.5dd30e3f.js @@ -0,0 +1 @@ +"use strict";(self.webpackChunk=self.webpackChunk||[]).push([[6306],{6572:(n,e,s)=>{s.r(e),s.d(e,{data:()=>a});const a={key:"v-07f01b20",path:"/guide/services/ddos.html",title:"DDOS Protection",lang:"en-US",frontmatter:{},excerpt:"",headers:[],filePathRelative:"guide/services/ddos.md",git:{updatedTime:1634291321e3,contributors:[{name:"Anton",email:"a.shumansky@gmail.com",commits:1}]}}},6752:(n,e,s)=>{s.r(e),s.d(e,{default:()=>o});const a=(0,s(6252).uE)('

DDOS Protection

This service contains DDOS protection features

Register the service into your main.go file:

registry.ServiceProviderDDOS()\n
1

Access the service:

service.DI().DDOS()\n
1

You can protect for example login endpoint from many attempts by using method ProtectManyAttempts

',7),t={},o=(0,s(3744).Z)(t,[["render",function(n,e){return a}]])},3744:(n,e)=>{e.Z=(n,e)=>{for(const[s,a]of e)n[s]=a;return n}}}]); \ No newline at end of file diff --git a/assets/js/v-1229168e.7fddad3c.js b/assets/js/v-1229168e.7fddad3c.js new file mode 100644 index 00000000..7e67bc40 --- /dev/null +++ b/assets/js/v-1229168e.7fddad3c.js @@ -0,0 +1 @@ +"use strict";(self.webpackChunk=self.webpackChunk||[]).push([[838],{184:(n,s,a)=>{a.r(s),a.d(s,{data:()=>e});const e={key:"v-1229168e",path:"/guide/services/kubernetes.html",title:"Kubernetes",lang:"en-US",frontmatter:{},excerpt:"",headers:[],filePathRelative:"guide/services/kubernetes.md",git:{updatedTime:1656076836e3,contributors:[{name:"Masih Yeganeh",email:"goodboy.php@gmail.com",commits:1}]}}},6444:(n,s,a)=>{a.r(s),a.d(s,{default:()=>d});var e=a(6252);const t=(0,e.uE)('

Kubernetes

This service is used for calling Kubernetes APIs.
Currently, this service can be used to add other domains to Kubernetes Ingresses and handling certification generation with cert-manager.

To use this, register the service into your main.go file first:

registry.ServiceProviderKubernetes()\n
1

and you should put your credentials and other configs in your config file

kubernetes:\n  environment: "dev"\n  config_file: "/path/to/kubeconfig"\n
1
2
3

the config_file can be one of these:

',7),o=(0,e._)("li",null,"absolute path to config file",-1),p=(0,e._)("li",null,"relative path to config file - then address of config directory will be prepended to it",-1),c=(0,e.Uk)("can be omitted completely - then Kubernetes in-cluster config of "),i={href:"https://kubernetes.io/docs/admin/authentication/#service-account-tokens",target:"_blank",rel:"noopener noreferrer"},u=(0,e.Uk)("Service Account Token"),l=(0,e.Uk)(" will be used"),r=(0,e.uE)('

Access the service:

service.DI().Kubernetes()\n
1

Some functions this service provide are:

\tGetIngressDomains(ctx context.Context) ([]string, error)\n\tAddIngress(ctx context.Context, domain, secretName, serviceName, servicePortName string, annotations map[string]string) error\n   \tRemoveIngress(ctx context.Context, domain string) error\n   \tIsCertificateProvisioned(ctx context.Context, secretName string) (bool, error)\n
1
2
3
4
',4),k={},d=(0,a(3744).Z)(k,[["render",function(n,s){const a=(0,e.up)("OutboundLink");return(0,e.wg)(),(0,e.iD)(e.HY,null,[t,(0,e._)("ul",null,[o,p,(0,e._)("li",null,[c,(0,e._)("a",i,[u,(0,e.Wm)(a)]),l])]),r],64)}]])},3744:(n,s)=>{s.Z=(n,s)=>{for(const[a,e]of s)n[a]=e;return n}}}]); \ No newline at end of file diff --git a/assets/js/v-17b1b5ba.9712a74a.js b/assets/js/v-17b1b5ba.9712a74a.js new file mode 100644 index 00000000..ae7bc224 --- /dev/null +++ b/assets/js/v-17b1b5ba.9712a74a.js @@ -0,0 +1 @@ +"use strict";(self.webpackChunk=self.webpackChunk||[]).push([[880],{7369:(n,a,s)=>{s.r(a),s.d(a,{data:()=>e});const e={key:"v-17b1b5ba",path:"/guide/features/goroutine.html",title:"Goroutine",lang:"en-US",frontmatter:{},excerpt:"",headers:[],filePathRelative:"guide/features/goroutine.md",git:{updatedTime:163482546e4,contributors:[{name:"majid mohsenifar",email:"majid.mohsenifar@coretrix.com",commits:1}]}}},489:(n,a,s)=>{s.r(a),s.d(a,{default:()=>o});const e=(0,s(6252).uE)('

Goroutine

Go provides simple solution for concurrency using go keyword, but in any case that goroutine panics you have to handle that yourself. Hitrix provides Goroutine function that does this automatically for you and you don't have to be worry about your goroutine panic. Hitrix Goroutine function would recover the panic for you and log the error. You can use that like this:

package mypackage\n\nimport "github.com/coretrix/hitrix"\n\nfunc Myfunc(){\n  hitrix.Goroutine(someFunc())\n}\n
1
2
3
4
5
6
7

Hitrix also provides another function named GoroutineWithRestart. If you have a function that must be run all the time you can use this function. In any case of panics it would log the error and automatically start the function again. You can use this function like this:

package mypackage\n\nimport "github.com/coretrix/hitrix"\n\nfunc Myfunc(){\n  hitrix.GoroutineWithRestart(someFunc())\n}\n
1
2
3
4
5
6
7
',5),t={},o=(0,s(3744).Z)(t,[["render",function(n,a){return e}]])},3744:(n,a)=>{a.Z=(n,a)=>{for(const[s,e]of a)n[s]=e;return n}}}]); \ No newline at end of file diff --git a/assets/js/v-1c2050b9.de81a832.js b/assets/js/v-1c2050b9.de81a832.js new file mode 100644 index 00000000..422d1836 --- /dev/null +++ b/assets/js/v-1c2050b9.de81a832.js @@ -0,0 +1 @@ +"use strict";(self.webpackChunk=self.webpackChunk||[]).push([[9313],{1629:(e,n,s)=>{s.r(n),s.d(n,{data:()=>r});const r={key:"v-1c2050b9",path:"/guide/features/consumer_runners.html",title:"Consumer runners",lang:"en-US",frontmatter:{},excerpt:"",headers:[{level:3,title:"ConsumerRunner - non scalable",slug:"consumerrunner-non-scalable",children:[]},{level:3,title:"ScalableConsumerRunner - scalable",slug:"scalableconsumerrunner-scalable",children:[]}],filePathRelative:"guide/features/consumer_runners.md",git:{updatedTime:1651228845e3,contributors:[{name:"Iliyan",email:"iliyan.motovski@coretrix.com",commits:1}]}}},8660:(e,n,s)=>{s.r(n),s.d(n,{default:()=>l});const r=(0,s(6252).uE)('

Consumer runners

Consumer runners enable you to quickly spin up BeeORM queue consumers easily. There are 2 types of consumer.

  • scalable
  • non scalable

ConsumerRunner - non scalable

Use queue.NewConsumerRunner(ctx) to make consumers which are not required to be able to scale.

This consumer works with following 4 interfaces:

  • ConsumerOne (consumes items one by one)
  • ConsumerMany (consumes items in batches)
  • ConsumerOneByModulo (consumes items one by one using modulo)
  • ConsumerManyByModulo (consumes items in batches using modulo)

ScalableConsumerRunner - scalable

Use queue.NewScalableConsumerRunner(ctx, persistent redis pool) to make consumers which are required to be able to scale.

This consumer works with following 2 interfaces:

  • ConsumerOne (consumes items one by one)
  • ConsumerMany (consumes items in batches)
',11),u={},l=(0,s(3744).Z)(u,[["render",function(e,n){return r}]])},3744:(e,n)=>{n.Z=(e,n)=>{for(const[s,r]of n)e[s]=r;return e}}}]); \ No newline at end of file diff --git a/assets/js/v-2073f07c.f9b76b3a.js b/assets/js/v-2073f07c.f9b76b3a.js new file mode 100644 index 00000000..469ee1dc --- /dev/null +++ b/assets/js/v-2073f07c.f9b76b3a.js @@ -0,0 +1 @@ +"use strict";(self.webpackChunk=self.webpackChunk||[]).push([[2471],{3826:(e,n,s)=>{s.r(n),s.d(n,{data:()=>a});const a={key:"v-2073f07c",path:"/guide/services/gql.html",title:"Gql",lang:"en-US",frontmatter:{},excerpt:"",headers:[],filePathRelative:"guide/services/gql.md",git:{updatedTime:1643743243e3,contributors:[{name:"Anton",email:"a.shumansky@gmail.com",commits:1}]}}},2534:(e,n,s)=>{s.r(n),s.d(n,{default:()=>c});const a=(0,s(6252).uE)('

Gql

This service can be used to return GQL errors and translate them if localize service is registered

Register the service into your main.go file:

registry.ServiceProviderGql()\n
1

Access the service:

service.DI().Gql()\n
1

The methods that can be used are GraphqlErr and GraphqlErrPath

',7),t={},c=(0,s(3744).Z)(t,[["render",function(e,n){return a}]])},3744:(e,n)=>{n.Z=(e,n)=>{for(const[s,a]of n)e[s]=a;return e}}}]); \ No newline at end of file diff --git a/assets/js/v-28c3d309.77c074f0.js b/assets/js/v-28c3d309.77c074f0.js new file mode 100644 index 00000000..bf72dc41 --- /dev/null +++ b/assets/js/v-28c3d309.77c074f0.js @@ -0,0 +1 @@ +"use strict";(self.webpackChunk=self.webpackChunk||[]).push([[7672],{6114:(e,n,a)=>{a.r(n),a.d(n,{data:()=>s});const s={key:"v-28c3d309",path:"/guide/services/license_plate_recognizer.html",title:"Licence plate recognizer",lang:"en-US",frontmatter:{},excerpt:"",headers:[],filePathRelative:"guide/services/license_plate_recognizer.md",git:{updatedTime:167353016e4,contributors:[{name:"Anton",email:"a.shumansky@gmail.com",commits:1}]}}},990:(e,n,a)=>{a.r(n),a.d(n,{default:()=>t});const s=(0,a(6252).uE)('

Licence plate recognizer

This service allow us to upload base64, and it will return all car license plates on this image.

You should put your api key from platerecognizer.com in config:

platerecognizer:\n  api_key: some_key\n
1
2

Access the service:

service.DI().LicencePlateRecognizer()\n
1
',6),c={},t=(0,a(3744).Z)(c,[["render",function(e,n){return s}]])},3744:(e,n)=>{n.Z=(e,n)=>{for(const[a,s]of n)e[a]=s;return e}}}]); \ No newline at end of file diff --git a/assets/js/v-317eb255.13a38855.js b/assets/js/v-317eb255.13a38855.js new file mode 100644 index 00000000..e1f922fa --- /dev/null +++ b/assets/js/v-317eb255.13a38855.js @@ -0,0 +1 @@ +"use strict";(self.webpackChunk=self.webpackChunk||[]).push([[5811],{6032:(n,s,a)=>{a.r(s),a.d(s,{data:()=>e});const e={key:"v-317eb255",path:"/guide/services/localizer.html",title:"Localizer service",lang:"en-US",frontmatter:{},excerpt:"",headers:[],filePathRelative:"guide/services/localizer.md",git:{updatedTime:1640273751e3,contributors:[{name:"Anton",email:"a.shumansky@gmail.com",commits:2}]}}},7523:(n,s,a)=>{a.r(s),a.d(s,{default:()=>k});var e=a(6252);const p=(0,e._)("h1",{id:"localizer-service",tabindex:"-1"},[(0,e._)("a",{class:"header-anchor",href:"#localizer-service","aria-hidden":"true"},"#"),(0,e.Uk)(" Localizer service")],-1),t=(0,e._)("p",null,"Localizer provides you a simple translation service that can pull and push translation pairs from local (file) and external sources (online services).",-1),o=(0,e.Uk)("Currently localizer supports only "),l={href:"https://poeditor.com",target:"_blank",rel:"noopener noreferrer"},c=(0,e.Uk)("POEditor"),r=(0,e.Uk)(" online source."),u=(0,e.uE)('

Localizer using a bucket key to separate and manage translation parts of your app.

First you need these in your app config:

translation:\n  poeditor:\n    api_key: ENV[POEDITOR_API_KEY]\n    project_id: ENV[POEDITOR_PROJECT_ID]\n    language: ENV[POEDITOR_LANGUAGE]\n
1
2
3
4
5

Register the service into your main.go file:

registry.ServiceProviderLocalizer("") //for param you can provide env var as key(not as value!) in case you want to have sub folder in locale folder \n
1

Access the service:

service.DI().LocalizerService()\n
1

Loading translation pairs from map:

bucketKey := "greet-service"\nappend := false // append or replace?\npairs := map[string]string{\n  "app_name": "My App Name",\n  "loading_text": "Loading ...",\n}\nlocalizerService.LoadBucketFromMap(\n  bucketKey, \n  pairs, \n  append,\n)\n
1
2
3
4
5
6
7
8
9
10
11

Using Localize() function to translate a key:

appName, err := localizerService.Localize(bucketKey, "app_name")\nif err !nil {\n  // handle error\n}\n
1
2
3
4

Loading translation pairs from local file:

localizerService.LoadBucketFromFile(\n  bucketKey,\n  "locales/greet.en.json",\n  append,\n)\n
1
2
3
4
5

Pull the translations from external source:

err := localizerService.PullBucketFromSource(bucketKey, append)\nif err != nil {\n  log.Fatal(err)\n}\n
1
2
3
4

Push translations to external source:

err := localizerService.PushBucketToSource(bucketKey)\nif err != nil {\n  // handle error\n}\n
1
2
3
4
',17),i={},k=(0,a(3744).Z)(i,[["render",function(n,s){const a=(0,e.up)("OutboundLink");return(0,e.wg)(),(0,e.iD)(e.HY,null,[p,t,(0,e._)("p",null,[o,(0,e._)("a",l,[c,(0,e.Wm)(a)]),r]),u],64)}]])},3744:(n,s)=>{s.Z=(n,s)=>{for(const[a,e]of s)n[a]=e;return n}}}]); \ No newline at end of file diff --git a/assets/js/v-36afa754.763b36f3.js b/assets/js/v-36afa754.763b36f3.js new file mode 100644 index 00000000..2f28a90b --- /dev/null +++ b/assets/js/v-36afa754.763b36f3.js @@ -0,0 +1 @@ +"use strict";(self.webpackChunk=self.webpackChunk||[]).push([[8813],{7689:(n,s,a)=>{a.r(s),a.d(s,{data:()=>e});const e={key:"v-36afa754",path:"/guide/services/request_logger.html",title:"Request Logger service",lang:"en-US",frontmatter:{},excerpt:"",headers:[],filePathRelative:"guide/services/request_logger.md",git:{updatedTime:169088319e4,contributors:[{name:"Anton",email:"a.shumansky@gmail.com",commits:2}]}}},2883:(n,s,a)=>{a.r(s),a.d(s,{default:()=>o});const e=(0,a(6252).uE)('

Request Logger service

This service is used for logging all upcoming and outgoing requests

Register the service into your main.go file:

registry.ServiceProviderRequestLogger()\n
1

and you should register the entity RequestLoggerEntity into the ORM

Access the service:

service.DI().RequestLogger()\n
1

The functions this service provide are:

\tLogRequest(ormService *beeorm.Engine, appName, url string, request *http.Request, contentType string) *entity.RequestLoggerEntity\n    LogResponse(ormService *beeorm.Engine, requestLoggerEntity *entity.RequestLoggerEntity, responseBody []byte, status int)\n
1
2

They can be used to log any outgoing requests you send

Also you are able to enable middleware which will log all incoming requests

middleware.RequestLogger(ginEngine, func(context *gin.Context, requestEntity *entity.RequestLoggerEntity) {\n\t\t\tuserService := ioc.GetUserService()\n\t\t\tsession, hasSession := userService.GetSession(context.Request.Context())\n\n\t\t\tif hasSession && session.User != nil {\n\t\t\t\trequestEntity.UserID = session.User.ID\n\t\t\t}\n\t\t})\n
1
2
3
4
5
6
7
8

The second parameter (anonymous function) is called extender and it is used to save extra param to request_logger table like logged user id

If you want to use this middleware please do not forget to register the entity RequestLoggerEntity

We created a Cleaner that will remove all rows in request_logger table older than 30 days by default. This will prevent your database to be fulfilled with logs If you want to change ttl to other value you can do it from your config file like on the example bellow:

request_logger:\n  ttl_in_days: 5\n
1
2

To enable it please put this code into your single-instance-cron

    b := &hitrix.BackgroundProcessor{Server: s}\n    b.RunAsyncRequestLoggerCleaner()\n
1
2

Using our Dev Panel you will be able easily to see all requests and search trough them

',19),t={},o=(0,a(3744).Z)(t,[["render",function(n,s){return e}]])},3744:(n,s)=>{s.Z=(n,s)=>{for(const[a,e]of s)n[a]=e;return n}}}]); \ No newline at end of file diff --git a/assets/js/v-3706649a.a81808dd.js b/assets/js/v-3706649a.a81808dd.js new file mode 100644 index 00000000..f261aa3b --- /dev/null +++ b/assets/js/v-3706649a.a81808dd.js @@ -0,0 +1 @@ +"use strict";(self.webpackChunk=self.webpackChunk||[]).push([[88],{1801:(t,e,n)=>{n.r(e),n.d(e,{data:()=>r});const r={key:"v-3706649a",path:"/404.html",title:"",lang:"en-US",frontmatter:{layout:"404"},excerpt:"",headers:[],filePathRelative:null,git:{}}},8109:(t,e,n)=>{n.r(e),n.d(e,{default:()=>a});const r={},a=(0,n(3744).Z)(r,[["render",function(t,e){return null}]])},3744:(t,e)=>{e.Z=(t,e)=>{for(const[n,r]of e)t[n]=r;return t}}}]); \ No newline at end of file diff --git a/assets/js/v-375b0a45.c9170900.js b/assets/js/v-375b0a45.c9170900.js new file mode 100644 index 00000000..456764bc --- /dev/null +++ b/assets/js/v-375b0a45.c9170900.js @@ -0,0 +1 @@ +"use strict";(self.webpackChunk=self.webpackChunk||[]).push([[9263],{9922:(n,s,a)=>{a.r(s),a.d(s,{data:()=>e});const e={key:"v-375b0a45",path:"/guide/services/sms.html",title:"SMS Service",lang:"en-US",frontmatter:{},excerpt:"",headers:[],filePathRelative:"guide/services/sms.md",git:{updatedTime:168554389e4,contributors:[{name:"Anton",email:"a.shumansky@gmail.com",commits:5},{name:"Iliyan",email:"iliyan.motovski@coretrix.com",commits:1}]}}},1994:(n,s,a)=>{a.r(s),a.d(s,{default:()=>t});const e=(0,a(6252).uE)('

SMS Service

This service is capable of sending sms messages using different providers . We supports next providers twilio sinch kavenegar link mobility mobica

Register the service into your main.go file:

registry.ServiceProviderSMS(sms.NewTwilioProvider, sms.NewSinchProvider)\n
1

Access the service:

service.DI().SMS()\n
1
Dependencies :

ClockService, ORMConfigService

Every request is saved in SmsTrackerEntity. So you will be able to track every sms

We support defining primary and secondary providers. If primary fails we try to send a message with the secondary provider.

We can set default providers and providers per message as well

Default providers are set at ServiceProviderSMS() Providers per message are set at SendMessage()

The method SendMessage used to send simple message

type Message struct {\n\tText     string\n\tNumber   string\n\tProvider *Provider\n}\n
1
2
3
4
5
configs
sms:\n  sandbox_mode: false\n  twilio:\n    sid: ENV[SMS_TWILIO_SID]\n    token: ENV[SMS_TWILIO_TOKEN]\n    from_number: ENV[SMS_TWILIO_FROM_NUMBER]\n  kavenegar:\n    api_key: ENV[SMS_KAVENEGAR_API_KEY]\n    sender: ENV[SMS_KAVENEGAR_SENDER]\n  sinch:\n    app_id: ENV[SMS_SINCH_APP_ID]\n    app_secret: ENV[SMS_SINCH_APP_SECRET]\n    msg_url: ENV[SMS_SINCH_MSG_URL]\n    from_number: ENV[SMS_SINCH_FROM_NUMBER]\n  mobica:\n    email: ENV[SMS_MOBICA_EMAIL]\n    password: ENV[SMS_MOBICA_PASSWORD]\n    route: ENV[SMS_MOBICA_ROUTE]\n    from: ENV[SMS_MOBICA_FROM]\n    endpoint: ENV[SMS_MOBICA_ENDPOINT]\n  link_mobility:\n    service: ENV[SMS_LINK_MOBILITY_SERVICE]\n    key: ENV[SMS_LINK_MOBILITY_KEY]\n    secret: ENV[SMS_LINK_MOBILITY_SECRET]\n    endpoint: ENV[SMS_LINK_MOBILITY_ENDPOINT]\n    shortcode: ENV[SMS_LINK_MOBILITY_SHORTCODE]\n
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26

If you set sandbox_mode=true we won't send real sms to the customer

',17),p={},t=(0,a(3744).Z)(p,[["render",function(n,s){return e}]])},3744:(n,s)=>{s.Z=(n,s)=>{for(const[a,e]of s)n[a]=e;return n}}}]); \ No newline at end of file diff --git a/assets/js/v-39e0a9d6.89bf367b.js b/assets/js/v-39e0a9d6.89bf367b.js new file mode 100644 index 00000000..3ea8bf4f --- /dev/null +++ b/assets/js/v-39e0a9d6.89bf367b.js @@ -0,0 +1 @@ +"use strict";(self.webpackChunk=self.webpackChunk||[]).push([[3995],{5010:(n,s,a)=>{a.r(s),a.d(s,{data:()=>t});const t={key:"v-39e0a9d6",path:"/guide/services/crud.html",title:"CRUD",lang:"en-US",frontmatter:{},excerpt:"",headers:[{level:3,title:"Search vs Filter",slug:"search-vs-filter",children:[]},{level:3,title:"defining columns",slug:"defining-columns",children:[]},{level:3,title:"Use Select fields with dependency",slug:"use-select-fields-with-dependency",children:[]},{level:3,title:"Use CRUD with our export service",slug:"use-crud-with-our-export-service",children:[]}],filePathRelative:"guide/services/crud.md",git:{updatedTime:1696505147e3,contributors:[{name:"Iman Daneshi",email:"emandaneshikohan@gmail.com",commits:2},{name:"Anton",email:"a.shumansky@gmail.com",commits:1},{name:"h-khodadadeh",email:"khodadadeh@coretrix.com",commits:1}]}}},9436:(n,s,a)=>{a.r(s),a.d(s,{default:()=>e});const t=(0,a(6252).uE)('

CRUD

This service it gives you ability to build gql query and apply different query parameters to the query that should be used in listing pages

Register the service into your main.go file:

registry.ServiceProviderCrud(),\n
1

Access the service:

service.DI().Crud()\n
1

Search vs Filter

Search is used on strings while filtering can be used on wide range of data types. One important note to remember is that if your column is searchable it can't be filterable.

defining columns

First you need to define what columns you're going to have and which of them will be Searchable, Sortable or Filterable(user for enum values). Using this configuration you also define the supported filters that can be applied.

The second step is in your controller(handler) to call few methods from that service that will build the right query for you based on the request. Crud service supports mysql query builder and redis-search query builder.

Example of the way you can use it:

//defining columns.\nfunc columns() []crud.Column {\n    return []crud.Column{\n            {\n                Key:            "ID",\n                Type:           crud.NumberType,\n                Label:          "ID",\n                Searchable:     false,\n                Sortable:       true,\n                Visible:        true,\n                Filterable:     true,\n                FilterValidMap: nil,\n            },\n            {\n                Key:            "Name",\n                Type:           crud.StringType,\n                Label:          "Name",\n                Searchable:     true,\n                Sortable:       false,\n                Visible:        true,\n                Filterable:     false,\n                FilterValidMap: nil,\n            }\n        }\n}\n
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
//listing request using by gql\ntype ListRequest struct {\n    Page     *int                   `json:"Page"`\n    PageSize *int                   `json:"PageSize"`\n    Filter   map[string]interface{} `json:"Filter"`\n    Search   map[string]interface{} `json:"Search"`\n    SearchOr map[string]interface{} `json:"SearchOR"`\n    Sort     map[string]interface{} `json:"Sort"`\n}\n
1
2
3
4
5
6
7
8
9

and at the end your method where you return the response:

cols := columns()\n\ncrudService := service.DI().CrudService()\n\nsearchParams := crudService.ExtractListParams(cols, crud.ListRequestConvertorFromGQL(userListRequest))\nquery := crudService.GenerateListRedisSearchQuery(searchParams)\n\normService := ioc.GetOrmEngineFromContext(ctx)\nvar userEntities []*entity.UserEntity\n\normService.RedisSearch(&userEntities, query, beeorm.NewPager(searchParams.Page, searchParams.PageSize))\n\nuserEntityList := make([]*model.User, len(userEntities))\n\nfor i, userEntity := range userEntities {\n    userEntityList[i] = populate.UserAdmin(userEntity)\n}\n\nreturn &model.UserList{\n    Rows:    userEntityList,\n    Total:   len(userEntityList),\n    Columns: crud.ColumnConvertorToGQL(cols),\n    }, nil\n
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23

Use Select fields with dependency

Imagine you have these fields with such options, on is the country and for the city filter to work user must select the country first and then select the proper city that exists in the list of that country otherwise this filter will be ignored.

Take london as an example, if we select United States as country and London as city, then city filter will be ignored, so make sure this is also handled in your front-end.

Note: If you don't set FilterDependencyField or DataMapStringStringKeyStringValue this filter will be ignored!

func columns() []crud.Column {\n    return []crud.Column{\n    \t\t{\n\t\t\tKey:                    "Country",\n\t\t\tFilterType:             hitrixCrud.SelectTypeStringString,\n\t\t\tLabel:                  "Country",\n\t\t\tSearchable:             true,\n\t\t\tSortable:               false,\n\t\t\tVisible:                true,\n\t\t\tTranslationDataEnabled: true,\n\t\t\tDataStringKeyStringValue: []*hitrixCrud.StringKeyStringValue{\n\t\t\t\t{Key: "bg", Label: "Bulgaria"},\n\t\t\t\t{Key: "us", Label: "United States"},\n\t\t\t\t{Key: "uk", Label: "United Kingdom"},\n\t\t\t},\n\t\t},\n\t\t{\n\t\t\tKey:                   "City",\n\t\t\tFilterType:            hitrixCrud.SelectTypeStringString,\n\t\t\tLabel:                 "City",\n\t\t\tSearchable:            true,\n\t\t\tSortable:              false,\n\t\t\tVisible:               true,\n\t\t\tFilterDependencyField: "Country",\n\t\t\tDataMapStringStringKeyStringValue: map[string][]*hitrixCrud.StringKeyStringValue{\n\t\t\t\t"bg": {\n\t\t\t\t\t{Key: "sofia", Label: "Sofia"},\n\t\t\t\t\t{Key: "plovdiv", Label: "Plovdiv"},\n\t\t\t\t},\n\t\t\t\t"us": {\n\t\t\t\t\t{Key: "new_york", Label: "New York"},\n\t\t\t\t\t{Key: "los_angeles", Label: "Los Angeles"},\n\t\t\t\t},\n\t\t\t\t"uk": {\n\t\t\t\t\t{Key: "london", Label: "London"},\n\t\t\t\t},\n\t\t\t},\n\t\t},\n\t}\n}\n
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40

Use CRUD with our export service

You can mix our crud service with our exporter service to add a quick and painless exporting system to your project

In order to do that you can follow these steps

1 - Making your currently list view function private and creating another function for passing to gin router

for example here we renamed "List" to "list" and we created a new "ListRequest" function and we're passing it to gin router instead of "List"

\nfunc Columns() []hitrixCrud.Column {\n    return []hitrixCrud.Column{\n        {\n            Key:        "ID",\n            Label:      "ID",\n            Searchable: false,\n            Sortable:   true,\n            Visible:    true,\n        },\n        {\n            Key:        "Name",\n            FilterType: hitrixCrud.InputTypeString,\n            Label:      "Name",\n            Searchable: true,\n            Sortable:   false,\n            Visible:    true,\n        },\n    }\n}\n\ntype ListRow struct {\n    ID        uint64\n    Name      string\n}\n\n\nfunc ListRequest(ctx context.Context, request *hitrixCrud.ListRequest) (*city.ResponseDTOList, error) {\n\t// You should pass this function to the gin router\n\treturn list(service.DI().OrmEngineForContext(ctx), request)\n}\n\nfunc list(ormService *beeorm.Engine, request *hitrixCrud.ListRequest) (*city.ResponseDTOList, error) {\n\t\n\t// this is the function that handles the getting and making the payload fot the crud that is going to be used\n\t// for both list (endpoint) and exporting\n\t\n\tcols := Columns()\n\tcrudService := service.DI().Crud()\n\n\tsearchParams := crudService.ExtractListParams(cols, request)\n\tquery := crudService.GenerateListRedisSearchQuery(searchParams)\n\n\tvar cityEntities []*entity.CityEntity\n\t\n\t// your logic for getting entities from database with the query generated from crud\n\n\trows := make([]ListRow, len(cityEntities))\n\n\t// you logic for creating rows for your crud\n\n\treturn &ResponseDTOList{\n\t\tRows:        rows,\n\t\tTotal:       int(total),\n\t\tColumns:     cols,\n\t\tPageContext: getPageContext(ormService),\n\t}, nil\n}\n
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58

2 - Creating a new handler for exporting the data obtained from "list" function

func ListExport(ormService *beeorm.Engine, request *hitrixCrud.ListRequest, _ uint64, _ map[string]string) (error, []string, [][]interface{}) {\n\t\n\t// This function handles the data for exporting\n\t\n\texportColumns := make([]string, 0) // excel or csv Columns for passing to our exporter service\n\tallExportData := make([][]interface{}, 0) // data for passing to our exporter service\n\n\tpager := beeorm.NewPager(1, 1000)\n\n\tfor {\n\t\t\n\t\t// loop for getting all the data out of the list function, bypassing the pagination\n\t\t\n\t\trequest.Page = &pager.CurrentPage\n\t\trequest.PageSize = &pager.PageSize\n\n\t\tres, err := list(ormService, request)\n\n\t\tif err != nil {\n\t\t\treturn err, nil, nil\n\t\t}\n\t\t\n\t\t// GetExporterDataCrud converts any given rows which in example is []city.ListRow to\n\t\t// exporter service columns and data as types of []string, [][]interface, exactly the data you need for passing to our\n\t\t// exporting system\n\t\t\n\t\tcolumns, exportData := hitrixCrud.GetExporterDataCrud(Columns(), res.Rows)\n\n\t\tfor _, exportDataRow := range exportData {\n\t\t\tallExportData = append(allExportData, exportDataRow)\n\t\t}\n\n\t\texportColumns = columns\n\n\t\tif len(res.Rows) < pager.PageSize {\n\t\t\tbreak\n\t\t}\n\n\t\tpager.IncrementPage()\n\t}\n\n\treturn nil, exportColumns, allExportData\n}\n
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43

3 - Creating the config and passing it to the service

we pass the ListExport function that we created above to the config

const (\n    CityExportID                = "cities"\n)\n\nvar RegisteredExportConfigs = []crud.ExportConfig{\n\t{\n\t\tHandler:          cityView.ListExport,\n\t\tID:               CityExportID,\n\t\tAllowedExtraArgs: nil,\n\t\tResource:         "city",\n\t\tPermissions:      []string{"read"},\n\t},\n}\n\nRegisterDIGlobalService(\n    ...\n    hitrixRegistry.ServiceProviderCrud(RegisteredExportConfigs),\n    ...\n)\n\n
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20

4 - Getting export ready data from crud

Imagine we have these two cities in our database

[\n  {\n    "ID": 1,\n    "Name": "Sofia"\n  },\n  {\n    "ID": 2,\n    "Name": "New York"\n  }\n]\n
1
2
3
4
5
6
7
8
9
10

We use the crud method to get config/handler and export our data

handler, exists := crudService.GetExportHandler(CityExportID) // getting the handler\n\nerr, columns, data := handler(\n    ormService,\n    &hitrixCrud.ListRequest{},\n    1, // user requesting the export\n    nil, \n)\n\nprint(columns)    // would print []string{"ID", "Name"}\nprint(data) // would print [][]interface{}{[]interface{"1", "Sofia"}, []interface{"2", "New York"}}\n\n
1
2
3
4
5
6
7
8
9
10
11
12

and you can easily pass this data to our exporter service, for example:

excelBytes, err := exporterService.XLSXExportToByte("cities", columns, data)\n
1

and if you like to check the permissions you can get the whole config like this

config, exists := crudService.GetExportConfig(CityExportID) // getting the config\n
1
',41),p={},e=(0,a(3744).Z)(p,[["render",function(n,s){return t}]])},3744:(n,s)=>{s.Z=(n,s)=>{for(const[a,t]of s)n[a]=t;return n}}}]); \ No newline at end of file diff --git a/assets/js/v-3a9e6ecc.0bb25cd2.js b/assets/js/v-3a9e6ecc.0bb25cd2.js new file mode 100644 index 00000000..a6102bad --- /dev/null +++ b/assets/js/v-3a9e6ecc.0bb25cd2.js @@ -0,0 +1 @@ +"use strict";(self.webpackChunk=self.webpackChunk||[]).push([[1608],{272:(e,n,a)=>{a.r(n),a.d(n,{data:()=>s});const s={key:"v-3a9e6ecc",path:"/guide/services/template.html",title:"Template",lang:"en-US",frontmatter:{},excerpt:"",headers:[],filePathRelative:"guide/services/template.md",git:{updatedTime:1643303756e3,contributors:[{name:"Anton",email:"a.shumansky@gmail.com",commits:1}]}}},3787:(e,n,a)=>{a.r(n),a.d(n,{default:()=>c});const s=(0,a(6252).uE)('

Template

This service can be used to render templates from your html files and also from mandrill templates

Register the service into your main.go file:

registry.ServiceProviderTemplate()\n
1

Access the service:

service.DI().Template()\n
1
',6),t={},c=(0,a(3744).Z)(t,[["render",function(e,n){return s}]])},3744:(e,n)=>{n.Z=(e,n)=>{for(const[a,s]of n)e[a]=s;return e}}}]); \ No newline at end of file diff --git a/assets/js/v-3b4e8b58.76bea257.js b/assets/js/v-3b4e8b58.76bea257.js new file mode 100644 index 00000000..902e7236 --- /dev/null +++ b/assets/js/v-3b4e8b58.76bea257.js @@ -0,0 +1 @@ +"use strict";(self.webpackChunk=self.webpackChunk||[]).push([[2347],{7362:(e,a,r)=>{r.r(a),r.d(a,{data:()=>t});const t={key:"v-3b4e8b58",path:"/guide/features/flags.html",title:"Flags",lang:"en-US",frontmatter:{},excerpt:"",headers:[{level:2,title:"Pre deploy",slug:"pre-deploy",children:[]},{level:2,title:"Force alters",slug:"force-alters",children:[]}],filePathRelative:"guide/features/flags.md",git:{updatedTime:1664453698e3,contributors:[{name:"Anton",email:"a.shumansky@gmail.com",commits:2},{name:"Iliyan",email:"iliyan.motovski@coretrix.com",commits:1}]}}},9827:(e,a,r)=>{r.r(a),r.d(a,{default:()=>o});const t=(0,r(6252).uE)('

Flags

Pre deploy

If you run your binary with argument -pre-deploy the program will check for alters and if there is no alters it will exit with code 0 but if there is an alters it will exit with code 1.

Force alters

If you run your binary with argument -force-alters the program will check for DB and RediSearch alters and it will execute them(only in local and qa mode). You can use this command on localhost if you use make file:

You can use this feature during the deployment process check if you need to execute the alters before you deploy it

',6),l={},o=(0,r(3744).Z)(l,[["render",function(e,a){return t}]])},3744:(e,a)=>{a.Z=(e,a)=>{for(const[r,t]of a)e[r]=t;return e}}}]); \ No newline at end of file diff --git a/assets/js/v-3c612605.f865f2b0.js b/assets/js/v-3c612605.f865f2b0.js new file mode 100644 index 00000000..f5cf3862 --- /dev/null +++ b/assets/js/v-3c612605.f865f2b0.js @@ -0,0 +1 @@ +"use strict";(self.webpackChunk=self.webpackChunk||[]).push([[3426],{7893:(e,r,l)=>{l.r(r),l.d(r,{data:()=>a});const a={key:"v-3c612605",path:"/guide/features/helper.html",title:"Helpers",lang:"en-US",frontmatter:{},excerpt:"",headers:[],filePathRelative:"guide/features/helper.md",git:{updatedTime:1681919644e3,contributors:[{name:"Anton",email:"a.shumansky@gmail.com",commits:2}]}}},6497:(e,r,l)=>{l.r(r),l.d(r,{default:()=>o});var a=l(6252);const t=(0,a._)("h1",{id:"helpers",tabindex:"-1"},[(0,a._)("a",{class:"header-anchor",href:"#helpers","aria-hidden":"true"},"#"),(0,a.Uk)(" Helpers")],-1),n=(0,a._)("p",null,[(0,a.Uk)("We have different type of helpers that will provide you simple universal functions that will save your time. Please review folder "),(0,a._)("code",null,"pkg/helpers"),(0,a.Uk)(". We have helpers like: "),(0,a._)("code",null,"array, auth, call, file, graphql, mysql, price, time, validator geo"),(0,a.Uk)(" and so on")],-1),i=(0,a._)("p",null,"Only way to be aware with them is to spend some time check the code and inform yourself what can be helpful for you from our helpers",-1),s={},o=(0,l(3744).Z)(s,[["render",function(e,r){return(0,a.wg)(),(0,a.iD)(a.HY,null,[t,n,i],64)}]])},3744:(e,r)=>{r.Z=(e,r)=>{for(const[l,a]of r)e[l]=a;return e}}}]); \ No newline at end of file diff --git a/assets/js/v-40d785a4.6651a1cb.js b/assets/js/v-40d785a4.6651a1cb.js new file mode 100644 index 00000000..9c8610fe --- /dev/null +++ b/assets/js/v-40d785a4.6651a1cb.js @@ -0,0 +1 @@ +"use strict";(self.webpackChunk=self.webpackChunk||[]).push([[4891],{8301:(s,n,a)=>{a.r(n),a.d(n,{data:()=>e});const e={key:"v-40d785a4",path:"/guide/services/slack.html",title:"Slack",lang:"en-US",frontmatter:{},excerpt:"",headers:[],filePathRelative:"guide/services/slack.md",git:{updatedTime:1643016226e3,contributors:[{name:"Anton",email:"a.shumansky@gmail.com",commits:1},{name:"Moein Nemati",email:"moeinroid@gmail.com",commits:1}]}}},6633:(s,n,a)=>{a.r(n),a.d(n,{default:()=>o});const e=(0,a(6252).uE)('

Slack

Gives you ability to send Slack messages using slack bot. You can define multiple Slack bots. Also, it's used to send messages if you use our ErrorLogger service using errors bot. The config that needs to be set in hitrix.yaml is:

slack:\n    error_channel: "test" #optional, used by ErrorLogger\n    dev_panel_url: "test" #optional, used by ErrorLogger\n    bot_tokens:\n      errors: "your token"\n      another_bot: "second token"\n
1
2
3
4
5
6

NOTE: bot_tokens.errors is a must-have when using ErrorLogger service.

Register the service into your main.go file:

registry.ServiceProviderSlack()\n
1

Access the service:

service.DI().Slack()\n
1
',8),t={},o=(0,a(3744).Z)(t,[["render",function(s,n){return e}]])},3744:(s,n)=>{n.Z=(s,n)=>{for(const[a,e]of n)s[a]=e;return s}}}]); \ No newline at end of file diff --git a/assets/js/v-45d60cd4.9fd7f3d6.js b/assets/js/v-45d60cd4.9fd7f3d6.js new file mode 100644 index 00000000..2886ab17 --- /dev/null +++ b/assets/js/v-45d60cd4.9fd7f3d6.js @@ -0,0 +1 @@ +"use strict";(self.webpackChunk=self.webpackChunk||[]).push([[4788],{1475:(n,a,s)=>{s.r(a),s.d(a,{data:()=>e});const e={key:"v-45d60cd4",path:"/guide/services/google_analytics.html",title:"Google Analytics",lang:"en-US",frontmatter:{},excerpt:"",headers:[],filePathRelative:"guide/services/google_analytics.md",git:{updatedTime:1654162597e3,contributors:[{name:"Masih Yeganeh",email:"goodboy.php@gmail.com",commits:1}]}}},9536:(n,a,s)=>{s.r(a),s.d(a,{default:()=>t});const e=(0,s(6252).uE)('

Google Analytics

This service is used for querying from Google Analytics

Register the service into your main.go. You need to provide function that init the analytics type (UA or GA4)

    registry.ServiceProviderGoogleAnalytics(googleanalytics.NewGA4)\n
1

Then you need to download client configuration (credentials file) from your panel and put it in configs folder. After that, you should put your ID of your GA property and config file name in config yaml file

Example:

google_analytics:\n  config_file_name: Name-s0m3r4nd0mv4lu3.json\n  property_id: 123456789\n
1
2
3

Access the service:

service.DI().GoogleAnalytics().GetProvider(googleanalytics.GA4)\n
1
',9),o={},t=(0,s(3744).Z)(o,[["render",function(n,a){return e}]])},3744:(n,a)=>{a.Z=(n,a)=>{for(const[s,e]of a)n[s]=e;return n}}}]); \ No newline at end of file diff --git a/assets/js/v-494d9f70.68d7268f.js b/assets/js/v-494d9f70.68d7268f.js new file mode 100644 index 00000000..c8918edc --- /dev/null +++ b/assets/js/v-494d9f70.68d7268f.js @@ -0,0 +1 @@ +"use strict";(self.webpackChunk=self.webpackChunk||[]).push([[8331],{4976:(e,s,n)=>{n.r(s),n.d(s,{data:()=>a});const a={key:"v-494d9f70",path:"/guide/services/clock.html",title:"Clock service",lang:"en-US",frontmatter:{},excerpt:"",headers:[],filePathRelative:"guide/services/clock.md",git:{updatedTime:1634291321e3,contributors:[{name:"Anton",email:"a.shumansky@gmail.com",commits:1}]}}},2444:(e,s,n)=>{n.r(s),n.d(s,{default:()=>c});const a=(0,n(6252).uE)('

Clock service

This service is used for time operations. It is better to use it everywhere instead of time.Now() because it can be mocked and you can set whatever time you want in your tests

Register the service into your main.go file:

registry.ServiceClock(),\n
1

Access the service:

service.DI().Clock()\n
1

The methods that this service provide are: Now() and NowPointer()

',7),t={},c=(0,n(3744).Z)(t,[["render",function(e,s){return a}]])},3744:(e,s)=>{s.Z=(e,s)=>{for(const[n,a]of s)e[n]=a;return e}}}]); \ No newline at end of file diff --git a/assets/js/v-4fcd777f.b22e4bec.js b/assets/js/v-4fcd777f.b22e4bec.js new file mode 100644 index 00000000..141d4953 --- /dev/null +++ b/assets/js/v-4fcd777f.b22e4bec.js @@ -0,0 +1 @@ +"use strict";(self.webpackChunk=self.webpackChunk||[]).push([[9459],{9869:(n,s,a)=>{a.r(s),a.d(s,{data:()=>e});const e={key:"v-4fcd777f",path:"/guide/services/clockwork.html",title:"ClockWork",lang:"en-US",frontmatter:{},excerpt:"",headers:[],filePathRelative:"guide/services/clockwork.md",git:{updatedTime:1644306561e3,contributors:[{name:"Anton",email:"a.shumansky@gmail.com",commits:3}]}}},4871:(n,s,a)=>{a.r(s),a.d(s,{default:()=>h});var e=a(6252);const o=(0,e._)("h1",{id:"clockwork",tabindex:"-1"},[(0,e._)("a",{class:"header-anchor",href:"#clockwork","aria-hidden":"true"},"#"),(0,e.Uk)(" ClockWork")],-1),t=(0,e._)("p",null,"This service provides you information about all queries executed by ORM and performance metrics for every api request. Also gives you ability to set your own log data. All the information it will be visible into your chrome inspector.",-1),l=(0,e.Uk)("It requires installation of Clockwork Chrome extension "),p={href:"https://chrome.google.com/webstore/detail/clockwork/dmggabnehkmmfmdffgajcflpdjlnoemp",target:"_blank",rel:"noopener noreferrer"},c=(0,e.Uk)("ClockWork extension"),r=(0,e.Uk)("You need to add special header to activate this feature. My recommendation is to install also this extension "),i={href:"https://chrome.google.com/webstore/detail/modheader/idgpnmonknjnojddfkpgkljpfnnfcklj",target:"_blank",rel:"noopener noreferrer"},u=(0,e.Uk)("ModHeader extension"),k=(0,e.Uk)(" You should set header "),d=(0,e._)("code",null,"CoreTrix",-1),g=(0,e.Uk)(" with value equal to the password you will set bellow in your yaml file"),m=(0,e.uE)('
clockwork:\n    password: "your password here"\n\n
1
2
3

Register the service into your main.go file as context service:

registry.ServiceProviderClockWorkForContext()\n
1

Access the service:

service.DI().ClockWorkForContext(ctx).GetLoggerDataSource()\n
1

There are 2 steps that also needs to be done:

  1. To add this middleware
\thitrixMiddleware "github.com/coretrix/hitrix/pkg/middleware"\n\t\n\t...\n\t\n\thitrixMiddleware.Clockwork(ginEngine)\n
1
2
3
4
5
  1. To add special route
\thitrixController "github.com/coretrix/hitrix/pkg/controller"\n\n    ...\n\n\tvar clockwork *hitrixController.ClockworkController\n\tginEngine.GET("/__clockwork/:id", clockwork.GetIndexAction)\n
1
2
3
4
5
6

You also are able to send your own data to clockwork and use it as a logger to debug easily. You can do it in that way anywhere in your code:

\tservice.DI().ClockWorkForContext(ctx).GetLoggerDataSource().LogDebugString("key", "test")\n
1
',12),b={},h=(0,a(3744).Z)(b,[["render",function(n,s){const a=(0,e.up)("OutboundLink");return(0,e.wg)(),(0,e.iD)(e.HY,null,[o,t,(0,e._)("p",null,[l,(0,e._)("a",p,[c,(0,e.Wm)(a)])]),(0,e._)("p",null,[r,(0,e._)("a",i,[u,(0,e.Wm)(a)]),k,d,g]),m],64)}]])},3744:(n,s)=>{s.Z=(n,s)=>{for(const[a,e]of s)n[a]=e;return n}}}]); \ No newline at end of file diff --git a/assets/js/v-4fe4c9ba.6108bea2.js b/assets/js/v-4fe4c9ba.6108bea2.js new file mode 100644 index 00000000..a3d32994 --- /dev/null +++ b/assets/js/v-4fe4c9ba.6108bea2.js @@ -0,0 +1 @@ +"use strict";(self.webpackChunk=self.webpackChunk||[]).push([[4114],{381:(n,s,a)=>{a.r(s),a.d(s,{data:()=>e});const e={key:"v-4fe4c9ba",path:"/guide/services/geocoding.html",title:"Geocoding",lang:"en-US",frontmatter:{},excerpt:"",headers:[],filePathRelative:"guide/services/geocoding.md",git:{updatedTime:1689281472e3,contributors:[{name:"Anton",email:"a.shumansky@gmail.com",commits:2},{name:"Krasimir Ivanov",email:"krasimir.ivanov@coretrix.com",commits:1}]}}},6644:(n,s,a)=>{a.r(s),a.d(s,{default:()=>p});const e=(0,a(6252).uE)('

Geocoding

This service is used for geocoding and reverse geocoding. It supports multiple providers. For now only Google Maps provider is implemented.

You should put your api key from Google Maps in config:

geocoding:\n  use_caching: true\n  cache_ttl_min_days: 5\n  cache_ttl_max_days: 10\n  google_maps:\n    api_key: some_key\n
1
2
3
4
5
6

Note 1: if you decide to use caching functionality, you need to run script ClearExpiredGeocodingCache in your project. This script will delete expired cache.

Note 2: if you decide to use caching functionality, lat/lng are cut (not rounded) to 5 decimal place

Access the service:

service.DI().Geocoding()\n
1

The service exposes 3 methods that you can use:

type IGeocoding interface {\n\tGeocode(ctx context.Context, ormService *beeorm.Engine, address string, language Language) (*Address, error)\n\tReverseGeocode(ctx context.Context, ormService *beeorm.Engine, latLng *LatLng, language Language) (*Address, error)\n    CutCoordinates(float float64, precision int) (float64, error)\n}\n
1
2
3
4
5
',10),t={},p=(0,a(3744).Z)(t,[["render",function(n,s){return e}]])},3744:(n,s)=>{s.Z=(n,s)=>{for(const[a,e]of s)n[a]=e;return n}}}]); \ No newline at end of file diff --git a/assets/js/v-549f42a4.e30474a7.js b/assets/js/v-549f42a4.e30474a7.js new file mode 100644 index 00000000..cf9c359c --- /dev/null +++ b/assets/js/v-549f42a4.e30474a7.js @@ -0,0 +1 @@ +"use strict";(self.webpackChunk=self.webpackChunk||[]).push([[2493],{3091:(e,s,n)=>{n.r(s),n.d(s,{data:()=>a});const a={key:"v-549f42a4",path:"/guide/services/fcm.html",title:"Firebase cloud messaging (FCM) service",lang:"en-US",frontmatter:{},excerpt:"",headers:[],filePathRelative:"guide/services/fcm.md",git:{updatedTime:1634291321e3,contributors:[{name:"Anton",email:"a.shumansky@gmail.com",commits:1}]}}},4702:(e,s,n)=>{n.r(s),n.d(s,{default:()=>t});const a=(0,n(6252).uE)('

Firebase cloud messaging (FCM) service

This service is used for sending different types of push notifications

Register the service into your main.go file:

registry.ServiceProviderFCM(),\n
1

Config sample:

expose FIREBASE_CONFIG="path/to/service-account-file.json"

',6),i={},t=(0,n(3744).Z)(i,[["render",function(e,s){return a}]])},3744:(e,s)=>{s.Z=(e,s)=>{for(const[n,a]of s)e[n]=a;return e}}}]); \ No newline at end of file diff --git a/assets/js/v-54e2d192.85b8ddf1.js b/assets/js/v-54e2d192.85b8ddf1.js new file mode 100644 index 00000000..df510761 --- /dev/null +++ b/assets/js/v-54e2d192.85b8ddf1.js @@ -0,0 +1 @@ +"use strict";(self.webpackChunk=self.webpackChunk||[]).push([[2255],{1872:(n,e,a)=>{a.r(e),a.d(e,{data:()=>s});const s={key:"v-54e2d192",path:"/guide/services/config.html",title:"Config",lang:"en-US",frontmatter:{},excerpt:"",headers:[{level:2,title:"Environment variables in config file",slug:"environment-variables-in-config-file",children:[]}],filePathRelative:"guide/services/config.md",git:{updatedTime:1634291321e3,contributors:[{name:"Anton",email:"a.shumansky@gmail.com",commits:1}]}}},5400:(n,e,a)=>{a.r(e),a.d(e,{default:()=>t});const s=(0,a(6252).uE)('

Config

This service provides you access to your config file. We support only YAML file.

Register the service into your main.go file:

 registry.ServiceProviderConfigDirectory("../config")\n
1

you should provide the folder where are your config files.

The folder structure should look like that:

config\n - app-name\n    - config.yaml\n - hitrix.yaml #optional config where you can define some settings related to built-in services like slack service\n
1
2
3
4

Access the service:

service.DI().Config()\n
1

Environment variables in config file

Its good practice to keep your secrets like database credentials and so on out of the repository. Our advice is to keep them like environment variables and call them into config.yaml file For example your config can looks like this:

orm:\n  default:\n    mysql: ENV[DEFAULT_MYSQL]\n    redis: ENV[DEFAULT_REDIS]\n    locker: default\n    local_cache: 1000\n
1
2
3
4
5
6

where DEFAULT_MYSQL and DEFAULT_REDIS are env variables and our framework will automatically replace ENV[DEFAULT_MYSQL] and ENV[DEFAULT_REDIS] with the right values

If you want to define array of values you should split them by ; and they will be presented into the yaml file in that way:

cors:\n    - test1\n    - test2\n
1
2
3

If you want to enable the debug for orm you can add this tag orm_debug: true on the main level of your config

Also we check if there is .env.XXX file in main config folder where XXX is the value of the APP_MODE. If there is for example .env.local we are reading those env variables and merge them with config.yaml how we presented above

',17),o={},t=(0,a(3744).Z)(o,[["render",function(n,e){return s}]])},3744:(n,e)=>{e.Z=(n,e)=>{for(const[a,s]of e)n[a]=s;return n}}}]); \ No newline at end of file diff --git a/assets/js/v-55888b62.7ef3c924.js b/assets/js/v-55888b62.7ef3c924.js new file mode 100644 index 00000000..41f727bf --- /dev/null +++ b/assets/js/v-55888b62.7ef3c924.js @@ -0,0 +1 @@ +"use strict";(self.webpackChunk=self.webpackChunk||[]).push([[4420],{6510:(n,s,a)=>{a.r(s),a.d(s,{data:()=>e});const e={key:"v-55888b62",path:"/guide/services/oss.html",title:"Object Storage Service",lang:"en-US",frontmatter:{},excerpt:"",headers:[],filePathRelative:"guide/services/oss.md",git:{updatedTime:167145534e4,contributors:[{name:"Krasimir Ivanov",email:"krasimir.ivanov@coretrix.com",commits:4}]}}},9136:(n,s,a)=>{a.r(s),a.d(s,{default:()=>p});const e=(0,a(6252).uE)('

Object Storage Service

This service is used for storing files into any amazon s3 or google cloud compatible services

Register the service into your main.go. You need to provide function that init the provider and list of buckets

registry.ServiceProviderOSS(oss.NewAmazonOSS, oss.Namespaces{"products": oss.BucketPublic})\n
1

and you should register the entity OSSBucketCounterEntity into the ORM

Also, you should put your credentials and other configs in config/hitrix.yml

S3 example:

oss:\n  amazon_s3:\n    endpoint: "https://somestorage.com" # set to "" if you're using https://s3.amazonaws.com\n    access_key_id: ENV[S3_ACCESS_KEY_ID]\n    secret_access_key: ENV[S3_SECRET_ACCESS_KEY_ID]\n    disable_ssl: false\n    region: us-east-1\n  buckets:\n    public: # config for public bucket\n     name: bucket-name # bucket name\n     cdn_url: "https://somesite.com/{{.StorageKey}}/" # Available variables is: .StorageKey (Namespace is part of StorageKey)\n    private: # config for private bucket\n     name: bucket-name-private # bucket name\n     local: "http://127.0.0.1/{{.StorageKey}}" # Will output "http://127.0.0.1/product/1.jpeg"\n
1
2
3
4
5
6
7
8
9
10
11
12
13
14

Google example:

In your config folder you should put the .oss.json config file that you have from google Your config file should looks like that:

{\n  "type": "...",\n  "project_id": "...",\n  "private_key_id": "...",\n  "private_key": "...",\n  "client_email": "...",\n  "client_id": "...",\n  "auth_uri": "...",\n  "token_uri": "...",\n  "auth_provider_x509_cert_url": "...",\n  "client_x509_cert_url": "..."\n}\n
1
2
3
4
5
6
7
8
9
10
11
12

The last thing you need to set in domain that gonna be used for the static files. You can setup the domain in hitrix.yaml config file like this:

oss: \n  domain: myapp.com\n  google:\n    anyvar: 1\n  buckets:\n    public: # config for public bucket\n      name: bucket-name # bucket name\n      cdn_url: "https://somesite.com/{{.StorageKey}}/" # Available variables are: .Namespace, .CounterID, and, .StorageKey\n    private: # config for private bucket\n      name: bucket-name-private # bucket name\n      local: "http://127.0.0.1/{{.Namespace}}/{{.StorageKey}}/{{.CounterID}}" # Will output "http://127.0.0.1/product/1.jpeg/1"\n
1
2
3
4
5
6
7
8
9
10
11

Access the service:

service.DI().OSService()\n
1
',15),t={},p=(0,a(3744).Z)(t,[["render",function(n,s){return e}]])},3744:(n,s)=>{s.Z=(n,s)=>{for(const[a,e]of s)n[a]=e;return n}}}]); \ No newline at end of file diff --git a/assets/js/v-55c4d0d7.7e271966.js b/assets/js/v-55c4d0d7.7e271966.js new file mode 100644 index 00000000..52850798 --- /dev/null +++ b/assets/js/v-55c4d0d7.7e271966.js @@ -0,0 +1 @@ +"use strict";(self.webpackChunk=self.webpackChunk||[]).push([[8705],{9620:(n,s,a)=>{a.r(s),a.d(s,{data:()=>t});const t={key:"v-55c4d0d7",path:"/guide/services/websocket.html",title:"WebSocket",lang:"en-US",frontmatter:{},excerpt:"",headers:[],filePathRelative:"guide/services/websocket.md",git:{updatedTime:1634917363e3,contributors:[{name:"Anton",email:"a.shumansky@gmail.com",commits:1},{name:"Krasimir Ivanov",email:"krasimir.ivanov@coretrix.com",commits:1}]}}},5104:(n,s,a)=>{a.r(s),a.d(s,{default:()=>p});const t=(0,a(6252).uE)('

WebSocket

This service add support of websockets. It manage the connections and provide you easy way to read and write messages

Register the service into your main.go file:

registry.ServiceProviderSocketRegistry(registerHandler, unregisterHandler func(s *socket.Socket))\n
1

Access the service:

service.DI().SocketRegistry()\n
1

To be able to handle new connections you should create your own route and create a handler for it. Your handler should looks like that:

type WebsocketController struct {\n}\n\nfunc (controller *WebsocketController) InitConnection(c *gin.Context) {\n\tws, err := upgrader.Upgrade(c.Writer, c.Request, nil)\n\tif err != nil {\n\t\tpanic(err)\n\t}\n\n\tsocketRegistryService, has := service.DI().SocketRegistry()\n\tif !has {\n\t\tpanic("Socket Registry is not registered")\n\t}\n\n\terrorLoggerService := service.DI().ErrorLogger()\n\n\tconnection := &socket.Connection{Send: make(chan []byte, 256), Ws: ws}\n\tsocketHolder := &socket.Socket{\n\t\tErrorLogger: errorLoggerService,\n\t\tConnection:  connection,\n\t\tID:          "unique connection hash based on userID, deviceID and timestamp",\n\t\tNamespace:   model.DefaultNamespace,\n\t}\n\n\tsocketRegistryService.Register <- socketHolder\n\n\tgo socketHolder.WritePump()\n\tgo socketHolder.ReadPump(socketRegistryService, func(rawData []byte) {\n\t\ts, _ := socketRegistryService.Sockets.Load(socketHolder.ID)\n\t\t\n        dto := &DTOMessage{}\n        err = json.Unmarshal(rawData, dto)\n        if err != nil {\n            errorLoggerService.LogError(err)\n            retrun\n        }\n        //handle business logic here\n        s.(*socket.Socket).Emit(dto)\n\t})\n}\n\n
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41

This handler initializes the new coming connections and have 2 go routines - one for writing messages and the second one for reading messages If you want to send message you should use socketRegistryService.Emit

If you want to read coming messages you should do it in the function we are passing as second parameter of ReadPump method

If you want to select certain connection you can do it by the ID and this method

s, err := socketRegistryService.Sockets.Load(ID)\n
1

Also websocket service provide you hooks for registering new connections and for unregistering already existing connections. You can define those handlers when you register the service based on namespace of socket.

',13),e={},p=(0,a(3744).Z)(e,[["render",function(n,s){return t}]])},3744:(n,s)=>{s.Z=(n,s)=>{for(const[a,t]of s)n[a]=t;return n}}}]); \ No newline at end of file diff --git a/assets/js/v-56016842.c0df6684.js b/assets/js/v-56016842.c0df6684.js new file mode 100644 index 00000000..79b2b553 --- /dev/null +++ b/assets/js/v-56016842.c0df6684.js @@ -0,0 +1 @@ +"use strict";(self.webpackChunk=self.webpackChunk||[]).push([[9524],{602:(n,s,a)=>{a.r(s),a.d(s,{data:()=>e});const e={key:"v-56016842",path:"/guide/services/api_logger.html",title:"API logger service",lang:"en-US",frontmatter:{},excerpt:"",headers:[],filePathRelative:"guide/services/api_logger.md",git:{updatedTime:1634291321e3,contributors:[{name:"Anton",email:"a.shumansky@gmail.com",commits:1}]}}},3551:(n,s,a)=>{a.r(s),a.d(s,{default:()=>o});const e=(0,a(6252).uE)('

API logger service

This service is used to track every api request and response. For example it can be used in any other service as SMS service, Stripe service and so on. Using it you will have a history of all requests and responses and it will help you even in case you need to debug something.

Register the service into your main.go file:

registry.APILogger(&entity.APILogEntity{}),\n
1

Access the service:

service.DI().APILogger()\n
1

All the data it will be saved into the APILogEntity entity.

The methods that this service provide are:

type APILogger interface {\n\tLogStart(logType string, request interface{})\n\tLogError(message string, response interface{})\n\tLogSuccess(response interface{})\n}\n
1
2
3
4
5

You should call LogStart before you send request to the api

You should call LogError in case api return you error

You should call LogSuccess in case api return you success

',12),t={},o=(0,a(3744).Z)(t,[["render",function(n,s){return e}]])},3744:(n,s)=>{s.Z=(n,s)=>{for(const[a,e]of s)n[a]=e;return n}}}]); \ No newline at end of file diff --git a/assets/js/v-60346367.66c4dbc2.js b/assets/js/v-60346367.66c4dbc2.js new file mode 100644 index 00000000..7bbb71af --- /dev/null +++ b/assets/js/v-60346367.66c4dbc2.js @@ -0,0 +1 @@ +"use strict";(self.webpackChunk=self.webpackChunk||[]).push([[2775],{5262:(n,s,a)=>{a.r(s),a.d(s,{data:()=>e});const e={key:"v-60346367",path:"/guide/services/stripe.html",title:"Stripe",lang:"en-US",frontmatter:{},excerpt:"",headers:[],filePathRelative:"guide/services/stripe.md",git:{updatedTime:1634291321e3,contributors:[{name:"Anton",email:"a.shumansky@gmail.com",commits:1}]}}},4705:(n,s,a)=>{a.r(s),a.d(s,{default:()=>p});const e=(0,a(6252).uE)('

Stripe

Stripe payment integration

Register the service into your main.go file:

registry.ServiceProviderStripe(),\n
1

Access the service:

service.DI().Stripe()\n
1

Config sample:

stripe:\n  key: "api_key"\n  webhook_secrets: # map of your webhook secrets\n    checkout: "key"\n
1
2
3
4
',8),t={},p=(0,a(3744).Z)(t,[["render",function(n,s){return e}]])},3744:(n,s)=>{s.Z=(n,s)=>{for(const[a,e]of s)n[a]=e;return n}}}]); \ No newline at end of file diff --git a/assets/js/v-63bef79a.6a3c557e.js b/assets/js/v-63bef79a.6a3c557e.js new file mode 100644 index 00000000..7047aa41 --- /dev/null +++ b/assets/js/v-63bef79a.6a3c557e.js @@ -0,0 +1 @@ +"use strict";(self.webpackChunk=self.webpackChunk||[]).push([[1571],{3059:(n,s,a)=>{a.r(s),a.d(s,{data:()=>e});const e={key:"v-63bef79a",path:"/guide/services/dynamic_link.html",title:"Dynamic link service",lang:"en-US",frontmatter:{},excerpt:"",headers:[],filePathRelative:"guide/services/dynamic_link.md",git:{updatedTime:1634291321e3,contributors:[{name:"Anton",email:"a.shumansky@gmail.com",commits:1}]}}},5143:(n,s,a)=>{a.r(s),a.d(s,{default:()=>t});const e=(0,a(6252).uE)('

Dynamic link service

This service is used for generating dynamic links, at this moment only Firebase is supported

Register the service into your main.go file:

registry.ServiceProviderDynamicLink(),\n
1

Access the service:

service.DI().DynamicLink()\n
1

Config sample:

 api_key: string # required\n dynamic_link_info: # required\n   domain_uri_prefix: string # required\n   link: string # required\n   android_info: # optional\n     package_name: string # optional\n     fallback_link: string # optional\n     min_package_version_code: string # optional\n   ios_info: # optional\n     bundle_id: string # optional\n     fallback_link: string # optional\n     custom_scheme: string # optional\n     ipad_fallback_link: string # optional\n     ipad_bundle_id: string # optional\n     app_store_id: string # optional\n   navigation_info: # optional\n     enable_forced_redirect: boolean # required\n   analytics_info: # optional\n     google_play_analytics: # optional\n       utm_source: string # optional\n       utm_medium: string # optional\n       utm_campaign: string # optional\n       utm_term: string # optional\n       utm_content: string # optional\n       gcl_id: string # optional\n     itunes_connect_analytics: # optional\n       at: string # optional\n       ct: string # optional\n       mt: string # optional\n       pt: string # optional\n   social_meta_tag_info: # optional\n     social_title: string # optional\n     social_description: string # optional\n     social_image_link: string # optional\n suffix: # optional\n   option: string # required, values: "SHORT" or "UNGUESSABLE"\n
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
',8),p={},t=(0,a(3744).Z)(p,[["render",function(n,s){return e}]])},3744:(n,s)=>{s.Z=(n,s)=>{for(const[a,e]of s)n[a]=e;return n}}}]); \ No newline at end of file diff --git a/assets/js/v-63c19e21.87a3ab13.js b/assets/js/v-63c19e21.87a3ab13.js new file mode 100644 index 00000000..bba71d91 --- /dev/null +++ b/assets/js/v-63c19e21.87a3ab13.js @@ -0,0 +1 @@ +"use strict";(self.webpackChunk=self.webpackChunk||[]).push([[5874],{2807:(n,s,a)=>{a.r(s),a.d(s,{data:()=>t});const t={key:"v-63c19e21",path:"/guide/features/test.html",title:"Tests",lang:"en-US",frontmatter:{},excerpt:"",headers:[],filePathRelative:"guide/features/test.md",git:{updatedTime:1634291321e3,contributors:[{name:"Anton",email:"a.shumansky@gmail.com",commits:1}]}}},1267:(n,s,a)=>{a.r(s),a.d(s,{default:()=>e});const t=(0,a(6252).uE)('

Tests

Hitrix provide you test abstract layer that can be used to simulate requests to your graphql api

In your code you can create similar function that makes new instance of your app

func createContextMyApp(t *testing.T, projectName string, resolvers graphql.ExecutableSchema) *test.Ctx {\n\tdefaultServices := []*service.Definition{\n\t\tregistry.ServiceProviderConfigDirectory("../example/config"),\n\t\tregistry.ServiceProviderOrmRegistry(entity.Init),\n\t\tregistry.ServiceProviderOrmEngine(),\n\t\t//your services here\n\t}\n\n\treturn test.CreateContext(t,\n\t\tprojectName,\n\t\tresolvers,\n\t\tfunc(ginEngine *gin.Engine) { middleware.Router(ginEngine) },\n\t\tdefaultServices,\n\t)\n}\n\n
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16

After that you can call queries or mutations

func TestProcessApplePurchaseWithEmail(t *testing.T) {\n\ttype queryRegisterTransactions struct {\n\t\tRegisterTransactionsResponse *model.RegisterTransactionsResponse `graphql:"RegisterTransactions(applePurchaseRequest: $applePurchaseRequest)"`\n\t}\n\n\tvariables := map[string]interface{}{\n\t\t"applePurchaseRequest": model.ApplePurchaseRequest{\n\t\t\tForceEmail:   false,\n\t\t},\n\t}\n\n\tfakeMail := &mailMock.Sender{}\n\tfakeMail.On("SendTemplate", "hymn@abv.bg").Return(nil)\n\n\tgot := &queryRegisterTransactions{}\n\tprojectName, resolver := tests.GetWebAPIResolver()\n\tctx := tests.CreateContextWebAPI(t, projectName, resolver, &tests.IoCMocks{MailService: fakeMail})\n\n\terr := ctx.HandleMutation(got, variables)\n\tassert.Nil(t, err)\n\n\t//...\n\tfakeMail.AssertExpectations(t)\n}\n
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24

Hitrix supports parallel tests In case you want to execute parallel tests you need to set PARALLEL_TESTS=true env var in your IDE config and be sure you don't have set -p 1 in Go tool arguments In case you want to disable parallel tests remove PARALLEL_TESTS or set it to false and set in Go tool arguments value -p 1

',7),p={},e=(0,a(3744).Z)(p,[["render",function(n,s){return t}]])},3744:(n,s)=>{s.Z=(n,s)=>{for(const[a,t]of s)n[a]=t;return n}}}]); \ No newline at end of file diff --git a/assets/js/v-6787fb4c.04042151.js b/assets/js/v-6787fb4c.04042151.js new file mode 100644 index 00000000..a1d62eb0 --- /dev/null +++ b/assets/js/v-6787fb4c.04042151.js @@ -0,0 +1 @@ +"use strict";(self.webpackChunk=self.webpackChunk||[]).push([[6835],{5273:(n,s,a)=>{a.r(s),a.d(s,{data:()=>e});const e={key:"v-6787fb4c",path:"/guide/features/upload_files.html",title:"Upload files",lang:"en-US",frontmatter:{},excerpt:"",headers:[],filePathRelative:"guide/features/upload_files.md",git:{updatedTime:1667058369e3,contributors:[{name:"Anton",email:"a.shumansky@gmail.com",commits:1}]}}},235:(n,s,a)=>{a.r(s),a.d(s,{default:()=>p});const e=(0,a(6252).uE)('

Upload files

Hitrix allow us to upload files using OSS service and assign them to FileEntity. Whenever we want to reassign them to the right entity we need to do something like that in separate endpoint

    //...\n\tfileEntity := &entity.FileEntity{}\n\tfound := ormService.LoadByID(fileID, fileEntity)\n\n\tif !found {\n\t\treturn fmt.Errorf("file with FileID %v not found", *fileID)\n\t}\n\n\tif fileEntity.Namespace != oss.NamespaceUserAvatar.String() {\n\t\treturn goErrors.New("wrong file category")\n\t}\n\n\tuserEntity.Avatar = fileEntity.File\n    //...\n
1
2
3
4
5
6
7
8
9
10
11
12
13
14

If you want to enable this feature you should call middleware.FileRouter(ginEngine) This will add /v1/file/upload/ endpoint where the customers can upload their files

',4),t={},p=(0,a(3744).Z)(t,[["render",function(n,s){return e}]])},3744:(n,s)=>{s.Z=(n,s)=>{for(const[a,e]of s)n[a]=e;return n}}}]); \ No newline at end of file diff --git a/assets/js/v-68303986.c6b80122.js b/assets/js/v-68303986.c6b80122.js new file mode 100644 index 00000000..202572fc --- /dev/null +++ b/assets/js/v-68303986.c6b80122.js @@ -0,0 +1 @@ +"use strict";(self.webpackChunk=self.webpackChunk||[]).push([[4918],{1108:(n,e,s)=>{s.r(e),s.d(e,{data:()=>a});const a={key:"v-68303986",path:"/guide/services/orm_engine.html",title:"ORM Engine",lang:"en-US",frontmatter:{},excerpt:"",headers:[],filePathRelative:"guide/services/orm_engine.md",git:{updatedTime:1634291321e3,contributors:[{name:"Anton",email:"a.shumansky@gmail.com",commits:1}]}}},161:(n,e,s)=>{s.r(e),s.d(e,{default:()=>i});const a=(0,s(6252).uE)('

ORM Engine

Used to access ORM in background scripts. It is one instance for the whole script

Register the service into your main.go file:

registry.ServiceProviderOrmEngine()\n
1

Access the service:

service.DI().ORMEngine()\n
1

Never use that service in API. It is not thread safe!

',7),t={},i=(0,s(3744).Z)(t,[["render",function(n,e){return a}]])},3744:(n,e)=>{e.Z=(n,e)=>{for(const[s,a]of e)n[s]=a;return n}}}]); \ No newline at end of file diff --git a/assets/js/v-7199ca13.394b0308.js b/assets/js/v-7199ca13.394b0308.js new file mode 100644 index 00000000..510abd24 --- /dev/null +++ b/assets/js/v-7199ca13.394b0308.js @@ -0,0 +1 @@ +"use strict";(self.webpackChunk=self.webpackChunk||[]).push([[71],{9055:(n,s,a)=>{a.r(s),a.d(s,{data:()=>e});const e={key:"v-7199ca13",path:"/guide/services/exporter.html",title:"Exporter service",lang:"en-US",frontmatter:{},excerpt:"",headers:[],filePathRelative:"guide/services/exporter.md",git:{updatedTime:1634906075e3,contributors:[{name:"Saman Shahroudi",email:"shahroudi.dev@gmail.com",commits:1}]}}},7550:(n,s,a)=>{a.r(s),a.d(s,{default:()=>p});const e=(0,a(6252).uE)('

Exporter service

This service is able to export business data to various file formats. Currently, we support 2 file formats: XLSX and CSV.

Register the service into your main.go file:

registry.ServiceProviderExporter()\n
1

Access the service:

service.DI().Exporter()\n
1

Input data should be filled in as follows:

sheet := "sheet 1"\nheaders := []string{"Header 1", "Header 2"}\n\nrows := make([][]interface{}, 0)\n\nvar firstRow []interface{}\nfirstRow = append(firstRow, "cell 1", "cell 2")\nrows = append(rows, firstRow)\n\nvar secondRow []interface{}\nsecondRow = append(secondRow, "cell 1", "cell 2")\nrows = append(rows, secondRow)\n
1
2
3
4
5
6
7
8
9
10
11
12

Use XLSXExportToByte() function to convert raw data to Excel file and return it as a byte slice:

xlsxBytes, err := exporterService.XLSXExportToByte(sheet, headers, rows)\n
1

Use XLSXExportToFile() function for converting raw data to Excel file and save it in the given path:

err := exporterService.XLSXExportToFile(sheet, headers, rows, filePath)\n
1

Use CSVExportToByte() function to convert raw data to CSV file and return it as a byte slice:

csvBytes, err := exporterService.CSVExportToByte(headers, rows)\n
1

Using CSVExportToFile() function for converting raw data to CSV file and save it in the given path:

err := exporterService.XLSXExportToFile(headers, rows, filePath)\n
1
',16),t={},p=(0,a(3744).Z)(t,[["render",function(n,s){return e}]])},3744:(n,s)=>{s.Z=(n,s)=>{for(const[a,e]of s)n[a]=e;return n}}}]); \ No newline at end of file diff --git a/assets/js/v-720c0f26.92e015a0.js b/assets/js/v-720c0f26.92e015a0.js new file mode 100644 index 00000000..48cd6560 --- /dev/null +++ b/assets/js/v-720c0f26.92e015a0.js @@ -0,0 +1 @@ +"use strict";(self.webpackChunk=self.webpackChunk||[]).push([[469],{4520:(n,s,a)=>{a.r(s),a.d(s,{data:()=>e});const e={key:"v-720c0f26",path:"/guide/services/uploader.html",title:"Uploader",lang:"en-US",frontmatter:{},excerpt:"",headers:[],filePathRelative:"guide/services/uploader.md",git:{updatedTime:1634291321e3,contributors:[{name:"Anton",email:"a.shumansky@gmail.com",commits:1}]}}},4303:(n,s,a)=>{a.r(s),a.d(s,{default:()=>o});const e=(0,a(6252).uE)('

Uploader

This service uses TUS protocol to enable fast resumable and multi-part upload of big files. It provides an easy interface for plug-in whatever data store and locker you want to implement. Currently, Amazon S3 data store and Redis locker are implemented. For Amazon data store to work, you need to register Amazon S3 service before this one, also for Redis locker to work, you need to register orm service background before this one.

Register the service into your main.go file:

registry.ServiceProviderUploader(tusd.Config{...}, datastore.GetAmazonS3Store, locker.GetRedisLocker)\n
1

Access the service:

service.DI().Uploader()\n
1

Hitrix also provides REST uploader controller which you can register all handler methods in your router:

var uploaderController *hitrixController.UploaderController\nuploaderGroup := ginEngine.Group("/files/")\nuploaderGroup.Use(middleware.AuthorizeWithHeaderStrict())\n{\n\tuploaderGroup.POST("", uploaderController.PostFileAction)\n\tuploaderGroup.HEAD(":id", uploaderController.HeadFile)\n\tuploaderGroup.PATCH(":id", uploaderController.PatchFile)\n\tuploaderGroup.GET(":id", uploaderController.GetFileAction)\n\tuploaderGroup.DELETE(":id", uploaderController.DeleteFile)\n}\n
1
2
3
4
5
6
7
8
9
10

Also you need bucket name in config:

uploader:\n  bucket: media\n
1
2
',10),t={},o=(0,a(3744).Z)(t,[["render",function(n,s){return e}]])},3744:(n,s)=>{s.Z=(n,s)=>{for(const[a,e]of s)n[a]=e;return n}}}]); \ No newline at end of file diff --git a/assets/js/v-7cbce74e.e148fb1a.js b/assets/js/v-7cbce74e.e148fb1a.js new file mode 100644 index 00000000..c03b4980 --- /dev/null +++ b/assets/js/v-7cbce74e.e148fb1a.js @@ -0,0 +1 @@ +"use strict";(self.webpackChunk=self.webpackChunk||[]).push([[4779],{733:(n,s,a)=>{a.r(s),a.d(s,{data:()=>e});const e={key:"v-7cbce74e",path:"/guide/services/checkout.html",title:"Checkout.com API",lang:"en-US",frontmatter:{},excerpt:"",headers:[],filePathRelative:"guide/services/checkout.md",git:{updatedTime:1635507123e3,contributors:[{name:"Iman Daneshi",email:"emandaneshikohan@gmail.com",commits:2},{name:"Saman Shahroudi",email:"shahroudi.dev@gmail.com",commits:1}]}}},4932:(n,s,a)=>{a.r(s),a.d(s,{default:()=>p});const e=(0,a(6252).uE)('

Checkout.com API

This service is used to create a payment, check webhook key, request a refund and manage user cards for checkout.com payment API

Register the service into your main.go file:

hitrixRegistry.ServiceProviderCheckout()\n
1

And you should put your credentials and other configs in config/hitrix.yml

checkout:\n  secret_key: secret\n  public_key: public\n  currency: USD\n  webhook_keys:\n    main: somekey\n
1
2
3
4
5
6

Access the service:

checkoutService := service.DI().Checkout()\n
1

Using the service:

// Request a payment\ncheckoutService.RequestPayment(\n    payments.IDSource{\n        Type: "id",\n        ID:  "sometoken",\n    },\n    100,\n    "USD",\n    "Order-1000",\n    &payments.Customer{Email: "email@email.com"},\n    map[string]string{"OrderId": "Order-1000"}\n)\n      \n// Request a refund\ncheckoutService.RequestRefunds(1000, "PaymentId", "Order-1000", map[string]string{"OrderId": "Order-1000", "RefundsID": "Order-1000"})\n      \n// Validating incoming webhook\ncheckoutService.CheckWebhookKey("main", "value of Authorization header")\n\n// Get user cards\ncheckoutService.GetCustomerInstruments("cus_someid")\n\n// Delete user card\ncheckoutService.DeleteCustomerInstrument("src_someid")\n
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
',10),t={},p=(0,a(3744).Z)(t,[["render",function(n,s){return e}]])},3744:(n,s)=>{s.Z=(n,s)=>{for(const[a,e]of s)n[a]=e;return n}}}]); \ No newline at end of file diff --git a/assets/js/v-7d2151d6.e7d1a198.js b/assets/js/v-7d2151d6.e7d1a198.js new file mode 100644 index 00000000..5f0c4d89 --- /dev/null +++ b/assets/js/v-7d2151d6.e7d1a198.js @@ -0,0 +1 @@ +"use strict";(self.webpackChunk=self.webpackChunk||[]).push([[7523],{7172:(n,s,a)=>{a.r(s),a.d(s,{data:()=>e});const e={key:"v-7d2151d6",path:"/guide/services/sentry.html",title:"Sentry service",lang:"en-US",frontmatter:{},excerpt:"",headers:[],filePathRelative:"guide/services/sentry.md",git:{updatedTime:1660820102e3,contributors:[{name:"Iliyan",email:"iliyan.motovski@coretrix.com",commits:1}]}}},2576:(n,s,a)=>{a.r(s),a.d(s,{default:()=>o});const e=(0,a(6252).uE)('

Sentry service

This service allow you to use sentry for logging events and performance tracking.

Register the service into your main.go file:

registry.ServiceProviderSentry(tracesSampleRate *float64)\n
1

Access the service:

service.DI().Sentry()\n
1

The methods that this service provide are:

type ISentry interface {\n    CaptureMessage(message string)\n    Flush(timeout time.Duration)\n    StartSpan(ctx context.Context, operation string, options ...sentry.SpanOption) *sentry.Span\n}\n
1
2
3
4
5

You should call CaptureMessage when you want to send event to sentry

You should call Flush in your main file with defer, with 2 second timeout

You should call StartSpan when you want to start performance monitor

',11),t={},o=(0,a(3744).Z)(t,[["render",function(n,s){return e}]])},3744:(n,s)=>{s.Z=(n,s)=>{for(const[a,e]of s)n[a]=e;return n}}}]); \ No newline at end of file diff --git a/assets/js/v-8daa1a0e.3e229fe7.js b/assets/js/v-8daa1a0e.3e229fe7.js new file mode 100644 index 00000000..77d99cb9 --- /dev/null +++ b/assets/js/v-8daa1a0e.3e229fe7.js @@ -0,0 +1 @@ +"use strict";(self.webpackChunk=self.webpackChunk||[]).push([[2509],{6464:(n,s,a)=>{a.r(s),a.d(s,{data:()=>e});const e={key:"v-8daa1a0e",path:"/",title:"Introduction",lang:"en-US",frontmatter:{},excerpt:"",headers:[{level:2,title:"Installation",slug:"installation",children:[]},{level:2,title:"Quick start",slug:"quick-start",children:[{level:3,title:"Register Dev Panel",slug:"register-dev-panel",children:[]},{level:3,title:"DI services",slug:"di-services",children:[]},{level:3,title:"Setting mode",slug:"setting-mode",children:[]},{level:3,title:"Environment variables",slug:"environment-variables",children:[]}]}],filePathRelative:"README.md",git:{updatedTime:1685967966e3,contributors:[{name:"Anton",email:"a.shumansky@gmail.com",commits:3},{name:"Krasimir Ivanov",email:"krasimir.ivanov@coretrix.com",commits:1}]}}},1878:(n,s,a)=>{a.r(s),a.d(s,{default:()=>N});var e=a(6252);const t=(0,e._)("h1",{id:"introduction",tabindex:"-1"},[(0,e._)("a",{class:"header-anchor",href:"#introduction","aria-hidden":"true"},"#"),(0,e.Uk)(" Introduction")],-1),p=(0,e.Uk)("Hitrix is a web framework written in Go (Golang) and support Graphql and REST api. It is based on top of "),o={href:"https://gqlgen.com/%5D",target:"_blank",rel:"noopener noreferrer"},c=(0,e.Uk)("Gqlgen"),i=(0,e.Uk)(" and "),l={href:"https://github.com/gin-gonic/gin",target:"_blank",rel:"noopener noreferrer"},r=(0,e.Uk)("Gin Framework"),u=(0,e._)("h4",{id:"why-to-choose-hitrix",tabindex:"-1"},[(0,e._)("a",{class:"header-anchor",href:"#why-to-choose-hitrix","aria-hidden":"true"},"#"),(0,e.Uk)(" Why to choose Hitrix?")],-1),k=(0,e._)("p",null,"Hitrix is combination between high performance and speed of development. There are many build-in features and tools that save development time. Also this framework helps you from the day zero to start creating new features for your project. You don't need to spend time thinking about error log, db layer, caching, DI, structure, background jobs and so on. The only thing you need to do is to use Hitrix and deliver fast to the business",-1),d=(0,e._)("p",null,"Built-in features:",-1),b=(0,e.Uk)("It supports all features of "),m={href:"https://gqlgen.com/%5D",target:"_blank",rel:"noopener noreferrer"},g=(0,e.Uk)("Gqlgen"),h=(0,e.Uk)(" and "),v={href:"https://github.com/gin-gonic/gin",target:"_blank",rel:"noopener noreferrer"},f=(0,e.Uk)("Gin Framework"),y=(0,e.Uk)("Integrated with "),w={href:"https://github.com/latolukasz/beeorm",target:"_blank",rel:"noopener noreferrer"},q=(0,e.Uk)("ORM"),x=(0,e.Uk)("Follows "),_={href:"https://en.wikipedia.org/wiki/Dependency_injection",target:"_blank",rel:"noopener noreferrer"},I=(0,e.Uk)("Dependency injection"),D=(0,e.Uk)(" pattern"),S=(0,e._)("li",null,"Provides many DI services that makes your life easier. You can read more about them in our documentation",-1),R=(0,e.Uk)("Provides "),P={href:"https://github.com/coretrix/dev-frontend",target:"_blank",rel:"noopener noreferrer"},E=(0,e.Uk)("Dev panel"),G=(0,e.Uk)(" where you can monitor and manage your application(monitoring, error log, db alters redis status and so on)"),U=(0,e._)("li",null,[(0,e.Uk)("Other Features "),(0,e._)("ul",null,[(0,e._)("li",null,"Database Seeding"),(0,e._)("li",null,"Helpers"),(0,e._)("li",null,"Background scripts"),(0,e._)("li",null,"Validators"),(0,e._)("li",null,"Integration tests"),(0,e._)("li",null,"and so on...")])],-1),M=(0,e.uE)('

Installation

go get -u github.com/coretrix/hitrix\n
1

Quick start

  1. Run next command into your project's main folder and the graph structure will be created
go run github.com/99designs/gqlgen init\n
1
  1. Create cmd folder into your project and file called main.go

Put the next code into the file:

package main\n\nimport (\n\t"github.com/coretrix/hitrix"\n\t"github.com/gin-gonic/gin"\n\t\n\t"your-project/graph" //path you your graph\n\t"your-project/graph/generated" //path you your graph generated folder\n)\n\nfunc main() {\n\ts, deferFunc := hitrix.New(\n\t\t"app-name", "your secret",\n\t).Build()\n    defer deferFunc()\n\ts.RunServer(9999, generated.NewExecutableSchema(generated.Config{Resolvers: &graph.Resolver{}}),  func(ginEngine *gin.Engine) {\n\t\t//here you can register all your middlewares\n\t})\n}\n\n
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20

You are able register DI services in your main.go file in that way:

package main\n\nimport (\n\t"github.com/coretrix/hitrix"\n\t"github.com/coretrix/hitrix/service/registry"\n\t"your-project/entity"\n\t"your-project/graph"\n\t"your-project/graph/generated"\n\t"github.com/coretrix/hitrix/pkg/middleware"\n\t"github.com/gin-gonic/gin"\n\t"github.com/coretrix/hitrix/service/component/app"\n)\n\nfunc main() {\n\ts, deferFunc := hitrix.New(\n\t\t"app-name", "your secret",\n\t).RegisterDIGlobalService(\n\t\tregistry.ServiceProviderErrorLogger(), //register redis error logger\n\t\tregistry.ServiceProviderConfigDirectory("../config"), //register config service. As param you should point to the folder of your config file\n\t\tregistry.ServiceProviderOrmRegistry(entity.Init), //register our ORM and pass function where we set some configurations \n\t\tregistry.ServiceProviderOrmEngine(), //register our ORM engine for background processes\n\t\tregistry.ServiceProviderJWT(), //register JWT DI service\n\t\tregistry.ServiceProviderPassword(password.NewSimpleManager), //register pasword DI service\n\t).RegisterDIRequestService(\n\t\tregistry.ServiceProviderOrmEngineForContext(false), //register our ORM engine per context used in foreground processes \n\t).RegisterRedisPools(&app.RedisPools{Persistent: "your pool here"}).\n    Build()\n    defer deferFunc()\n\n\ts.RunServer(9999, generated.NewExecutableSchema(generated.Config{Resolvers: &graph.Resolver{}}),  func(ginEngine *gin.Engine) {\n\t\tmiddleware.Cors(ginEngine)\n\t})\n}\n\n
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34

Now I will explain the main.go file line by line

  1. We create New instance of Hitrix and pass app name and a secret that is used from our security services

  2. We register some DI services

    2.1. Global DI service for error logger. It will be used for error handler as well in case of panic If you register Slack error logger also it will send messages to slack channel

    2.2. Global DI service that loads config file

    2.3. Global DI service that initialize our ORM registry

    2.4. Global DI ORM engine used in background processes

    2.6. Global DI JWT service used by dev panel

    2.7. Global DI Password service used by dev-panel

    2.8. Request DI ORM engine used in foreground processes

  3. We register redis pools. Those pools are used by different services as authentication service, dev panel and so on

  4. We run the server on port 9999, pass graphql resolver and as third param we pass all middlewares we need.
    As you can see in our example we register only Cors middleware

',12),O={id:"register-dev-panel",tabindex:"-1"},W=(0,e._)("a",{class:"header-anchor",href:"#register-dev-panel","aria-hidden":"true"},"#",-1),C=(0,e.Uk)(" Register "),A={href:"https://github.com/coretrix/dev-frontend",target:"_blank",rel:"noopener noreferrer"},F=(0,e.Uk)("Dev Panel"),T=(0,e.uE)('

If you want to use our dev panel and to be able to manage alters, error log, redis monitoring, redis stream and so on you should execute next steps:

Create DevPanelUserEntity

package entity\n\nimport (\n\t"github.com/latolukasz/beeorm"\n)\n\ntype DevPanelUserEntity struct {\n\tbeeorm.ORM   `orm:"table=admin_users;redisCache"`\n\tID        uint64\n\tEmail     string `orm:"unique=Email"`\n\tPassword  string\n\n\tUserEmailIndex *beeorm.CachedQuery `queryOne:":Email = ?"`\n}\n\nfunc (e *DevPanelUserEntity) GetUsername() string {\n\treturn e.Email\n}\n\nfunc (e *DevPanelUserEntity) GetPassword() string {\n\treturn e.Password\n}\n\n
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23

After that you should register it to the entity.Init function

package entity\n\nimport "github.com/latolukasz/beeorm"\n\nfunc Init(registry *beeorm.Registry) {\n\tregistry.RegisterEntity(\n\t\t&DevPanelUserEntity{},\n\t)\n}\n\n
1
2
3
4
5
6
7
8
9
10

Please execute this alter into your database

\ncreate table dev_panel_users\n(\n    ID       bigint unsigned auto_increment primary key,\n    Email    varchar(255) null,\n    Password varchar(255) null,\n    constraint Email unique (Email)\n) charset = utf8mb4;\n\n\n
1
2
3
4
5
6
7
8
9
10

After that you can make GET request to http://localhost:9999/dev/create-dev-panel-user/?username=contact@coretrix.com&password=coretrix This will generate sql query that should be executed into your database to create new user for dev panel

Register dev panel when you make new instance of hitrix framework in your main.go file

s, deferFunc := hitrix.New(\n\t\t"app-name", "your secret",\n\t).RegisterDIGlobalService(\n\t\tregistry.ServiceProviderErrorLogger(), //register redis error logger\n\t\t//...\n\t).\n    RegisterDevPanel(&entity.DevPanelUserEntity{}, middleware.Router). //register our dev-panel and pass the entity where we save admin users, the router and the third param is used for the redis stream pool if its used\n    Build()\n
1
2
3
4
5
6
7
8

DI services

We have two types of DI services - Global and Request services Global services are singletons created once for the whole application Request services are singletons created once per request

Calling DI services

If you want to access the registered DI services you can do in in that way:

service.DI().App() //access the app\nservice.DI().Config() //access config\nservice.DI().OrmEngine() //access global orm engine\nservice.DI().OrmEngineForContext() //access reqeust orm engine\nservice.DI().JWT() //access JWT\nservice.DI().Password() //access JWT\n//...and so on\n
1
2
3
4
5
6
7

Register new DI global service

\nfunc ServiceProviderMyService() *ServiceProvider {\n\treturn &ServiceProvider{\n\t\tName:   "my_service",\n\t\tBuild: func(ctn di.Container) (interface{}, error) {\n\t\t\treturn &yourService{}, nil\n\t\t},\n\t}\n}\n\n
1
2
3
4
5
6
7
8
9
10

And you have to register ServiceProviderMyService() in your main.go file

Now you can access this service in your code using:

import (\n    "github.com/coretrix/hitrix"\n)\n\nfunc SomeResolver(ctx context.Context) {\n\n    service.HasService("my_service") // return true\n    \n    // return error if Build function returned error\n    myService, has, err := service.GetServiceSafe("my_service") \n    // will panic if Build function returns error\n    myService, has := service.GetServiceOptional("my_service") \n    // will panic if service is not registered or Build function returned errors\n    myService := service.GetServiceRequired("my_service") \n\n    // if you registered service with field "Global" set to false (request service)\n\n    myContextService, has, err := hitrix.GetServiceForRequestSafe(ctx).Get("my_service_request")\n    myContextService, has := hitrix.GetServiceForRequestOptional(ctx).Get("my_service_request") \n    myContextService := hitrix.GetServiceForRequestRequired(ctx).Get("my_service_request") \n}\n\n
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22

It's a good practice to define one object to return all available services:

package my_package\nimport (\n    "github.com/coretrix/hitrix"\n)\n\n\n\nfunc MyService() MyService {\n    return service.GetServiceRequired("service_key").(*MyService)\n}\n\n\n
1
2
3
4
5
6
7
8
9
10
11
12

Setting mode

APP_MODE environment variable

You can define hitrix mode using special environment variable "APP_MODE".

Hitrix provides by default four modes:

  • hitrix.ModeLocal - local
    • should be used on local development machine (developer laptop)
    • errors and stack trace is printed directly to system console
    • log level is set to Debug level
    • log is formatted using human friendly console text formatter
    • Gin Framework is running in GinDebug mode
  • hitrix.ModeTest - test
    • should be used when you run your application tests
  • hitrix.ModeDev - dev
    • should be used on your dev server
  • hitrix.ModeDemo - demo
    • should be used on your demo server
  • hitrix.ModeProd - prod
    • errors and stack trace is printed only using Log
    • log level is set to Warn level
    • log is formatted using json formatter

Mode is just a string. You can define any name you want. Remember that every mode that you create follows hitrix.ModeProd rules explained above.

In code you can easly check current mode using one of these methods:

service.DI().App().Mode()\nservice.DI().App().IsInLocalMode()\nservice.DI().App().IsInProdMode()\nservice.DI().App().IsInMode("my_mode")\n
1
2
3
4

Environment variables

APP_FOLDER environment variable

There are another important environment variable called environment You can set path to your app folder for your demo, prod or any other environment

',33),j={},N=(0,a(3744).Z)(j,[["render",function(n,s){const a=(0,e.up)("OutboundLink");return(0,e.wg)(),(0,e.iD)(e.HY,null,[t,(0,e._)("p",null,[p,(0,e._)("a",o,[c,(0,e.Wm)(a)]),i,(0,e._)("a",l,[r,(0,e.Wm)(a)])]),u,k,d,(0,e._)("ul",null,[(0,e._)("li",null,[b,(0,e._)("a",m,[g,(0,e.Wm)(a)]),h,(0,e._)("a",v,[f,(0,e.Wm)(a)])]),(0,e._)("li",null,[y,(0,e._)("a",w,[q,(0,e.Wm)(a)])]),(0,e._)("li",null,[x,(0,e._)("a",_,[I,(0,e.Wm)(a)]),D]),S,(0,e._)("li",null,[R,(0,e._)("a",P,[E,(0,e.Wm)(a)]),G]),U]),M,(0,e._)("h3",O,[W,C,(0,e._)("a",A,[F,(0,e.Wm)(a)])]),T],64)}]])},3744:(n,s)=>{s.Z=(n,s)=>{for(const[a,e]of s)n[a]=e;return n}}}]); \ No newline at end of file diff --git a/assets/js/v-92baae66.600b3911.js b/assets/js/v-92baae66.600b3911.js new file mode 100644 index 00000000..50223b40 --- /dev/null +++ b/assets/js/v-92baae66.600b3911.js @@ -0,0 +1 @@ +"use strict";(self.webpackChunk=self.webpackChunk||[]).push([[2288],{6873:(n,e,s)=>{s.r(e),s.d(e,{data:()=>a});const a={key:"v-92baae66",path:"/guide/services/orm_engine_context.html",title:"ORM Engine Context",lang:"en-US",frontmatter:{},excerpt:"",headers:[],filePathRelative:"guide/services/orm_engine_context.md",git:{updatedTime:1642747259e3,contributors:[{name:"Anton",email:"a.shumansky@gmail.com",commits:2}]}}},2050:(n,e,s)=>{s.r(e),s.d(e,{default:()=>o});const a=(0,s(6252).uE)('

ORM Engine Context

Used to access ORM in foreground scripts like API. It is one instance per every request

Register the service into your main.go file as context service:

registry.ServiceProviderOrmEngineForContext()\n
1

Access the service:

service.DI().ORMEngineForContext()\n
1
',6),t={},o=(0,s(3744).Z)(t,[["render",function(n,e){return a}]])},3744:(n,e)=>{e.Z=(n,e)=>{for(const[s,a]of e)n[s]=a;return n}}}]); \ No newline at end of file diff --git a/assets/js/v-94709e56.cc8e4ec4.js b/assets/js/v-94709e56.cc8e4ec4.js new file mode 100644 index 00000000..c815e790 --- /dev/null +++ b/assets/js/v-94709e56.cc8e4ec4.js @@ -0,0 +1 @@ +"use strict";(self.webpackChunk=self.webpackChunk||[]).push([[1308],{9796:(n,s,a)=>{a.r(s),a.d(s,{data:()=>e});const e={key:"v-94709e56",path:"/guide/features/seeder.html",title:"Database Seeding",lang:"en-US",frontmatter:{},excerpt:"",headers:[],filePathRelative:"guide/features/seeder.md",git:{updatedTime:1641372375e3,contributors:[{name:"Anton",email:"a.shumansky@gmail.com",commits:2}]}}},3137:(n,s,a)=>{a.r(s),a.d(s,{default:()=>S});var e=a(6252);const p=(0,e._)("h1",{id:"database-seeding",tabindex:"-1"},[(0,e._)("a",{class:"header-anchor",href:"#database-seeding","aria-hidden":"true"},"#"),(0,e.Uk)(" Database Seeding")],-1),t=(0,e._)("p",null,"Hitrix supports seeds that can be used to populate database or apply changes on it.",-1),o=(0,e.Uk)("To Seed your database ("),c={href:"https://en.wikipedia.org/wiki/Database_seeding",target:"_blank",rel:"noopener noreferrer"},u=(0,e.Uk)("wiki"),i=(0,e.Uk)("), you can use the seeding feature that is implemented as script ("),l=(0,e._)("code",null,"DBSeedScript",-1),r=(0,e.Uk)("). This "),k=(0,e._)("code",null,"DBSeedScript",-1),d=(0,e.Uk)(" needs to be provided a "),b=(0,e._)("code",null,"Seeds map[string][]Seed",-1),m=(0,e.Uk)(" field, Where the string key is the identifier of the project that is used. This measn you can have different project in one code base The Script can be implemented in your app by making a type that satisfies the "),g=(0,e._)("code",null,"Seed",-1),h=(0,e.Uk)(" interface:"),f=(0,e.uE)('
type Seed interface {\n    Execute(*beeorm.Engine)\n    Environments() []string\n    Name() string\n}\n
1
2
3
4
5

Example:

users_seed.go

type UsersSeed struct{}\n\nfunc (seed *UsersSeed) Name() string {\n    return "UsersSeed"\n}\n\nfunc (seed *UsersSeed) Environments() []string {\n    return []string{app.ModeTest, app.ModeLocal, app.ModeDev, app.ModeDemo, app.ModeProd}\n}\n\nfunc (seed *UsersSeed) Execute(ormService *beeorm.Engine) {\n\t// TODO insert a new user entity to the db\n}\n
1
2
3
4
5
6
7
8
9
10
11
12
13

And after your server definition (such as the one in server.go), you could use it to run the seeds just like you would run any script (explained earlier in the scripts seciton) as follows:

// Server Definition\ns,_ := hitrix.New()\n\n...\n\ns.RunBackgroundProcess(func(b *hitrix.BackgroundProcessor) {\n  // seed database\n  go b.RunScript(&hitrixScripts.DBSeedScript{\n    SeedsPerProject: map[string][]hitrixScripts.Seed{\n        "project_name": {\n            &script.UserProfileAttributesSeed{},\n        },\n    })\n\n  // ... other scripts\n})\n
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16

This seed will only run if no seeds has ever been ran with that name (in db table seeder, no row with 'name'='UsersSeed' )

',7),y={},S=(0,a(3744).Z)(y,[["render",function(n,s){const a=(0,e.up)("OutboundLink");return(0,e.wg)(),(0,e.iD)(e.HY,null,[p,t,(0,e._)("p",null,[o,(0,e._)("a",c,[u,(0,e.Wm)(a)]),i,l,r,k,d,b,m,g,h]),f],64)}]])},3744:(n,s)=>{s.Z=(n,s)=>{for(const[a,e]of s)n[a]=e;return n}}}]); \ No newline at end of file diff --git a/assets/js/v-98064128.a44cd149.js b/assets/js/v-98064128.a44cd149.js new file mode 100644 index 00000000..5f823f9f --- /dev/null +++ b/assets/js/v-98064128.a44cd149.js @@ -0,0 +1 @@ +"use strict";(self.webpackChunk=self.webpackChunk||[]).push([[5126],{3884:(e,a,n)=>{n.r(a),n.d(a,{data:()=>r});const r={key:"v-98064128",path:"/roadmap/",title:"Roadmap",lang:"en-US",frontmatter:{},excerpt:"",headers:[{level:2,title:"Planned in next release",slug:"planned-in-next-release",children:[]},{level:2,title:"Planned in near feature",slug:"planned-in-near-feature",children:[]}],filePathRelative:"roadmap/README.md",git:{updatedTime:1634560433e3,contributors:[{name:"Anton",email:"a.shumansky@gmail.com",commits:2}]}}},851:(e,a,n)=>{n.r(a),n.d(a,{default:()=>i});const r=(0,n(6252).uE)('

Roadmap

Planned in next release

  • Refactor Authentication service
  • Fix JWT token issue

Planned in near feature

  • Add access log to dev-panel using kubernetes api
',5),t={},i=(0,n(3744).Z)(t,[["render",function(e,a){return r}]])},3744:(e,a)=>{a.Z=(e,a)=>{for(const[n,r]of a)e[n]=r;return e}}}]); \ No newline at end of file diff --git a/assets/js/v-9d15fa06.88d80137.js b/assets/js/v-9d15fa06.88d80137.js new file mode 100644 index 00000000..6427a732 --- /dev/null +++ b/assets/js/v-9d15fa06.88d80137.js @@ -0,0 +1 @@ +"use strict";(self.webpackChunk=self.webpackChunk||[]).push([[8893],{9026:(n,s,a)=>{a.r(s),a.d(s,{data:()=>e});const e={key:"v-9d15fa06",path:"/guide/services/mail.html",title:"Mail Mailjet service",lang:"en-US",frontmatter:{},excerpt:"",headers:[],filePathRelative:"guide/services/mail.md",git:{updatedTime:1689945408e3,contributors:[{name:"Anton",email:"a.shumansky@gmail.com",commits:3}]}}},4681:(n,s,a)=>{a.r(s),a.d(s,{default:()=>p});const e=(0,a(6252).uE)('

Mail Mailjet service

This service is used for sending transactional emails using providers like Mailjet or Mandrill

Register the service into your main.go file:

registry.ServiceProviderMail(mail.NewMailjet)\n
1

and you should register the entity MailTrackerEntity into the ORM Also, you should put your credentials and other configs in your config file

mail:\n  mailjet:\n    api_key_public: ...\n    api_key_private: ...\n    default_from_email: test@coretrix.tv\n    from_name: coretrix.com\n    sandbox_mode: false\n    whitelisted_emails:\n      - "@coretrix.com"\n  mandrill:\n    api_key: ...\n    default_from_email: test@coretrix.tv\n    from_name: coretrix.com\n
1
2
3
4
5
6
7
8
9
10
11
12
13

If you set sandbox_mode=true we won't send real email to the customer.

BUT we allow sandbox_mode to be disabled only for specific emails or domains. That's why we created config param called whitelisted_emails All emails or domains defined in this config param will be sent to receiver even if sandbox is enabled.

The idea is to be able to test the platform and in the same time to be sure that real emails are not sent to customers.

Access the service:

service.DI().Mail()\n
1

Some functions this service provide are:

    GetTemplateKeyFromConfig(templateName string) (string, error)\n    SendTemplate(ormService *beeorm.Engine, message *Message) error\n    SendTemplateWithAttachments(ormService *beeorm.Engine, message *MessageAttachment) error\n    GetTemplateHTMLCode(templateName string) (string, error)\n
1
2
3
4
',13),t={},p=(0,a(3744).Z)(t,[["render",function(n,s){return e}]])},3744:(n,s)=>{s.Z=(n,s)=>{for(const[a,e]of s)n[a]=e;return n}}}]); \ No newline at end of file diff --git a/assets/js/v-b53ae1f0.3fd59914.js b/assets/js/v-b53ae1f0.3fd59914.js new file mode 100644 index 00000000..d271fe78 --- /dev/null +++ b/assets/js/v-b53ae1f0.3fd59914.js @@ -0,0 +1 @@ +"use strict";(self.webpackChunk=self.webpackChunk||[]).push([[842],{5977:(n,s,a)=>{a.r(s),a.d(s,{data:()=>p});const p={key:"v-b53ae1f0",path:"/guide/features/script.html",title:"Running scripts",lang:"en-US",frontmatter:{},excerpt:"",headers:[],filePathRelative:"guide/features/script.md",git:{updatedTime:1634291321e3,contributors:[{name:"Anton",email:"a.shumansky@gmail.com",commits:1}]}}},2544:(n,s,a)=>{a.r(s),a.d(s,{default:()=>e});const p=(0,a(6252).uE)('

Running scripts

This feature is used to run background scripts(cron jobs).

First you need to define script definition that implements hitrix.Script interface:

\ntype TestScript struct {}\n\nfunc (script *TestScript) Code() string {\n    return "test-script"\n}\n\nfunc (script *TestScript) Unique() bool {\n    // if true you can't run more than one script at the same time\n    return false\n}\n\nfunc (script *TestScript) Description() string {\n    return "script description"\n}\n\nfunc (script *TestScript) Run(ctx context.Context, exit hitrix.Exit) {\n    // put logic here\n\tif shouldExitWithCode2 {\n        exit.Error()\t// you can exit script and specify exit code\n    }\n}\n\n
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23

Methods above are required. Optionally you can also implement these interfaces:

\n// hitrix.ScriptInfinity interface\nfunc (script *TestScript) Infinity() bool {\n    // run script and use blocking operation in cases you run all your code in goroutines\n    return true\n}\n\n// hitrix.ScriptInterval interface\nfunc (script *TestScript) Interval() time.Duration {                                                    \n    // run script every minute\n    return time.Minute \n}\n\n// hitrix.ScriptIntervalOptional interface\nfunc (script *TestScript) IntervalActive() bool {                                                    \n    // only run first day of month\n    return time.Now().Day() == 1\n}\n\n// hitrix.ScriptIntermediate interface\nfunc (script *TestScript) IsIntermediate() bool {                                                    \n    // script is intermediate, for example is listening for data in chain\n    return true\n}\n\n// hitrix.ScriptOptional interface\nfunc (script *TestScript) Active() bool {                                                    \n    // this script is visible only in local mode\n    return DIC().App().IsInLocalMode()\n}\n\n
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31

Once you defined script you can run it using RunScript method:

package main\nimport "github.com/coretrix/hitrix"\n\nfunc main() {\n\th := hitrix.New("app_name", "your secret").Build()\n\th.RunBackgroundProcess(func(b *hitrix.BackgroundProcessor) {\n\t\tb.RunScript(&TestScript)\n\t})\n}\n
1
2
3
4
5
6
7
8
9

You can also register script as dynamic script and run it using program flag:

package main\nimport "github.com/coretrix/hitrix"\n\nfunc main() {\n\t\n    hitrix.New("app_name", "your secret").RegisterDIService(\n        &registry.ServiceProvider{\n            Name:   "my-script",\n            \n            Script: true, // you need to set true here\n            Build: func(ctn di.Container) (interface{}, error) {\n                return &TestScript{}, nil\n            },\n        },\n    ).Build()\n}\n
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16

You can see all available script by using special flag -list-scripts:

./app -list-scripts\n
1

To run script:

./app -run-script my-script\n
1
',14),t={},e=(0,a(3744).Z)(t,[["render",function(n,s){return p}]])},3744:(n,s)=>{s.Z=(n,s)=>{for(const[a,p]of s)n[a]=p;return n}}}]); \ No newline at end of file diff --git a/assets/js/v-b83148a8.77493078.js b/assets/js/v-b83148a8.77493078.js new file mode 100644 index 00000000..39d97fa3 --- /dev/null +++ b/assets/js/v-b83148a8.77493078.js @@ -0,0 +1 @@ +"use strict";(self.webpackChunk=self.webpackChunk||[]).push([[795],{3981:(n,s,a)=>{a.r(s),a.d(s,{data:()=>t});const t={key:"v-b83148a8",path:"/guide/services/elorus.html",title:"Elorus.com API",lang:"en-US",frontmatter:{},excerpt:"",headers:[],filePathRelative:"guide/services/elorus.md",git:{updatedTime:1646129473e3,contributors:[{name:"mahya.ghadiri",email:"mahya.ghadiri@snapp.cab",commits:2}]}}},5479:(n,s,a)=>{a.r(s),a.d(s,{default:()=>e});const t=(0,a(6252).uE)('

Elorus.com API

This service is used to access Elorus platform, for creating and managing invoices

Register the service into your main.go file:

hitrixRegistry.ServiceProviderElorus()\n
1

And you should put your credentials and other configs in config/hitrix.yml

elorus:\n  url: https://api.elorus.com/v1.1\n  token: secret\n  organization_id: secret\n
1
2
3
4

Access the service:

elorusService := service.DI().Elorus()\n
1

Using the service:

// Request to create contact\nelorusService.CreateContact(\n    elorus.CreateContactRequest{\n        FirstName: "name",\n        Active:  true,\n        Company:"company",\n        VatNumber:"BG108"\n        Email: []struct {   \n        \tEmail   string `json:"email"`\n            Primary bool   `json:"primary"`\n        }{{\n            Email:  "email@email.com",\n            Primary: true,\n        }},\n        Phones: []struct {\n            Number  string `json:"number"`\n            Primary bool   `json:"primary"`\n        }{{\n            Number:  "0869586598",\n            Primary: true,\n        }},        \n    },\n)\n      \n// Request to create invoice\nelorusService.CreateInvoice(\n\telorus.CreateInvoiceRequest{\n        Date:              time.Now().Format("2006-01-02"),\n        Client:            contactId,\n        ClientDisplayName: "name",\n        ClientEmail:       "email@email.com",\n        ClientVatNumber:   "BG108",\n        Number:            "0",\n        Items: []struct {\n            Title                        string   `json:"title"`\n            Description                  string   `json:"description"`\n            Quantity                     string   `json:"quantity"`\n            UnitMeasure                  int      `json:"unit_measure"`\n            UnitValue                    string   `json:"unit_value"`\n            Taxes                        []string `json:"Taxes"`\n            UnitTotal                    string   `json:"unit_total"`\n        }{{\n            Title:  "title",\n            Quantity: "5",\n\t\t\tUnitValue: "800",\n            UnitMeasure: 1,\n            UnitTotal: "500",\n            Taxes:       []string{"2416104549958812800"},\n        }},\n    }\n)     \n\n// Request to get invoice list\nelorusService.GetInvoiceList(\n\telorus.GetInvoiceListRequest{\n        Client:            contactId,\n        Page: "1",\n        PageSize:"10",\n    }\n)\n\n// Request to get invoice list\nelorusService.DownloadInvoice(\n\telorus.DownloadInvoiceRequest{\n        ID:            "id",\n    }\n)\n
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
',10),p={},e=(0,a(3744).Z)(p,[["render",function(n,s){return t}]])},3744:(n,s)=>{s.Z=(n,s)=>{for(const[a,t]of s)n[a]=t;return n}}}]); \ No newline at end of file diff --git a/assets/js/v-b9b8c0d2.2376f019.js b/assets/js/v-b9b8c0d2.2376f019.js new file mode 100644 index 00000000..4044cee0 --- /dev/null +++ b/assets/js/v-b9b8c0d2.2376f019.js @@ -0,0 +1 @@ +"use strict";(self.webpackChunk=self.webpackChunk||[]).push([[7698],{6807:(n,e,s)=>{s.r(e),s.d(e,{data:()=>a});const a={key:"v-b9b8c0d2",path:"/guide/services/jwt.html",title:"JWT",lang:"en-US",frontmatter:{},excerpt:"",headers:[],filePathRelative:"guide/services/jwt.md",git:{updatedTime:1634291321e3,contributors:[{name:"Anton",email:"a.shumansky@gmail.com",commits:1}]}}},7809:(n,e,s)=>{s.r(e),s.d(e,{default:()=>c});const a=(0,s(6252).uE)('

JWT

You can use that service to encode and decode JWT tokens

Register the service into your main.go file:

registry.ServiceProviderJWT()\n
1

Access the service:

service.DI().JWT()\n
1
',6),t={},c=(0,s(3744).Z)(t,[["render",function(n,e){return a}]])},3744:(n,e)=>{e.Z=(n,e)=>{for(const[s,a]of e)n[s]=a;return n}}}]); \ No newline at end of file diff --git a/assets/js/v-bc0867e6.ccc8387c.js b/assets/js/v-bc0867e6.ccc8387c.js new file mode 100644 index 00000000..1e71912b --- /dev/null +++ b/assets/js/v-bc0867e6.ccc8387c.js @@ -0,0 +1 @@ +"use strict";(self.webpackChunk=self.webpackChunk||[]).push([[4785],{2889:(n,s,e)=>{e.r(s),e.d(s,{data:()=>a});const a={key:"v-bc0867e6",path:"/guide/services/error_logger.html",title:"Error Logger",lang:"en-US",frontmatter:{},excerpt:"",headers:[],filePathRelative:"guide/services/error_logger.md",git:{updatedTime:1634291321e3,contributors:[{name:"Anton",email:"a.shumansky@gmail.com",commits:1}]}}},682:(n,s,e)=>{e.r(s),e.d(s,{default:()=>r});const a=(0,e(6252).uE)('

Error Logger

Used to save unhandled errors in error log. Hitrix use recovery function to handle those errors. If you setup Slack service you also going to receive notifications in your slack

Register the service into your main.go file:

registry.ServiceProviderErrorLogger()\n
1

Access the service:

service.DI().ErrorLogger()\n
1

It can be used to save custom errors as well:

        errorLoggerService := ioc.GetErrorLoggerService()\n\t\t\n\t\terrorLoggerService.LogErrorWithRequest(c, err) //if you provide context we will save request body as well\n\t\terrorLoggerService.LogError(err)\n
1
2
3
4
',8),o={},r=(0,e(3744).Z)(o,[["render",function(n,s){return a}]])},3744:(n,s)=>{s.Z=(n,s)=>{for(const[e,a]of s)n[e]=a;return n}}}]); \ No newline at end of file diff --git a/assets/js/v-cb968696.67f1f322.js b/assets/js/v-cb968696.67f1f322.js new file mode 100644 index 00000000..8e8e336c --- /dev/null +++ b/assets/js/v-cb968696.67f1f322.js @@ -0,0 +1 @@ +"use strict";(self.webpackChunk=self.webpackChunk||[]).push([[8881],{5419:(n,s,a)=>{a.r(s),a.d(s,{data:()=>t});const t={key:"v-cb968696",path:"/guide/features/acl.html",title:"ACL",lang:"en-US",frontmatter:{},excerpt:"",headers:[],filePathRelative:"guide/features/acl.md",git:{updatedTime:1671652413e3,contributors:[{name:"Anton",email:"a.shumansky@gmail.com",commits:1}]}}},9647:(n,s,a)=>{a.r(s),a.d(s,{default:()=>e});const t=(0,a(6252).uE)('

ACL

You can use ACL feature by including 4 hitrix entities in you orm init:

type RoleEntity struct {\n    beeorm.ORM `orm:"table=roles;redisCache;redisSearch=search_pool"`\n    ID         uint64    `orm:"sortable"`\n    Name       string    `orm:"required;searchable;unique=Name_FakeDelete:1"`\n    CreatedAt  time.Time `orm:"time=true"`\n    FakeDelete bool      `orm:"unique=Name_FakeDelete:2"`\n}\n\ntype ResourceEntity struct {\n    beeorm.ORM `orm:"table=resources;redisCache;redisSearch=search_pool"`\n    ID         uint64    `orm:"searchable"`\n    Name       string    `orm:"required;searchable;unique=Name_FakeDelete:1"`\n    CreatedAt  time.Time `orm:"time=true"`\n    FakeDelete bool      `orm:"unique=Name_FakeDelete:2"`\n}\n\ntype PermissionEntity struct {\n    beeorm.ORM `orm:"table=permissions;redisCache;redisSearch=search_pool"`\n    ID         uint64          `orm:"searchable;sortable"`\n    ResourceID *ResourceEntity `orm:"required;searchable;unique=ResourceID_Name_FakeDelete:1"`\n    Name       string          `orm:"required;searchable;unique=ResourceID_Name_FakeDelete:2"`\n    CreatedAt  time.Time       `orm:"time=true"`\n    FakeDelete bool            `orm:"unique=ResourceID_Name_FakeDelete:3"`\n}\n\ntype PrivilegeEntity struct {\n    beeorm.ORM    `orm:"table=privileges;redisCache;redisSearch=search_pool"`\n    ID            uint64\n    RoleID        *RoleEntity         `orm:"required;searchable;unique=RoleID_ResourceID_FakeDelete:1"`\n    ResourceID    *ResourceEntity     `orm:"required;searchable;unique=RoleID_ResourceID_FakeDelete:2"`\n    PermissionIDs []*PermissionEntity `orm:"required;searchable"`\n    CreatedAt     time.Time           `orm:"time=true"`\n    FakeDelete    bool                `orm:"unique=RoleID_ResourceID_FakeDelete:3"`\n}\n
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34

After you do this, in you local (to the specific project) user entity, you can include foreign key to role entity like this:

\ntype UserEntity struct {\n    beeorm.ORM `orm:"table=users;log=log_db_pool;redisCache;redisSearch=search_pool"`\n    ID         uint64\n    RoleID     *hitrixEntity.RoleEntity `orm:"required"`\n}\n
1
2
3
4
5
6

Now you are ready to use the ACL feature by using these endpoints:

var aclController *hitrixController.ACLController\n{\n\tv1Group.GET("/acl/resources/", aclController.ListResourcesAction)\n\tv1Group.GET("/acl/role/:ID/", aclController.GetRoleAction)\n\tv1Group.POST("/acl/roles/", aclController.ListRolesAction)\n\tv1Group.POST("/acl/role/", aclController.CreateRoleAction)\n\tv1Group.PUT("/acl/role/:ID/", aclController.UpdateRoleAction)\n\tv1Group.DELETE("/acl/role/:ID/", aclController.DeleteRoleAction)\n\tv1Group.POST("/acl/assign-role/", aclController.PostAssignRoleToUserAction(func() beeorm.Entity {\n\t\treturn &entity.AdminUserEntity{}\n\t}))\n}\n
1
2
3
4
5
6
7
8
9
10
11
12

These endpoints allow you to configure different combinations of roles, resources and permissions. After you configure your roles and permissions, you can start using the ACL by using middleware:

func ACL(resource string, permissions ...string) gin.HandlerFunc {\n\treturn func(c *gin.Context) {\n\t\tadminUserEntity, ok := ioc.GetAdminUserService().GetSession(c.Request.Context())\n\t\tif !ok {\n\t\t\tc.AbortWithStatus(http.StatusUnauthorized)\n\n\t\t\treturn\n\t\t}\n\n\t\tormService := service.DI().OrmEngineForContext(c.Request.Context())\n\n\t\tif !acl.ACL(ormService, adminUserEntity.User.RoleID, resource, permissions...) {\n\t\t\tc.AbortWithStatus(http.StatusUnauthorized)\n\n\t\t\treturn\n\t\t}\n\n\t\tc.Next()\n\t}\n}\n\n
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21

For example if you want to make the roles and permissions endpoints protected by ACL, you can modify the router to look like this:

var aclController *hitrixController.ACLController\naclGroup := v1Group.Group("/acl/").Use(authMiddleware.AuthorizeWithHeaderStrict())\n{\n\taclGroup.GET("resources/", authMiddleware.ACL(constant.ResourceResource, constant.PermissionView), aclController.ListResourcesAction)\n\taclGroup.GET("role/:ID/", authMiddleware.ACL(constant.ResourceRole, constant.PermissionView), aclController.GetRoleAction)\n\taclGroup.POST("roles/", authMiddleware.ACL(constant.ResourceRole, constant.PermissionView), aclController.ListRolesAction)\n\taclGroup.POST("role/", authMiddleware.ACL(constant.ResourceRole, constant.PermissionModify), aclController.CreateRoleAction)\n\taclGroup.PUT("role/:ID/", authMiddleware.ACL(constant.ResourceRole, constant.PermissionModify), aclController.UpdateRoleAction)\n\taclGroup.DELETE("role/:ID/", authMiddleware.ACL(constant.ResourceRole, constant.PermissionModify), aclController.DeleteRoleAction)\n\taclGroup.POST("assign-role/", authMiddleware.ACL(constant.ResourceAdminUser, constant.PermissionAssignRole), aclController.PostAssignRoleToUserAction(func() beeorm.Entity {\n\t\treturn &entity.AdminUserEntity{}\n\t}))\n}\n
1
2
3
4
5
6
7
8
9
10
11
12
13

You can see how this middleware is used on each endpoint, and it checks if the logged user role has the required privileges that are specified on each endpoint.

',12),p={},e=(0,a(3744).Z)(p,[["render",function(n,s){return t}]])},3744:(n,s)=>{s.Z=(n,s)=>{for(const[a,t]of s)n[a]=t;return n}}}]); \ No newline at end of file diff --git a/assets/js/v-d11abda4.b8b22d35.js b/assets/js/v-d11abda4.b8b22d35.js new file mode 100644 index 00000000..67ea4dcb --- /dev/null +++ b/assets/js/v-d11abda4.b8b22d35.js @@ -0,0 +1 @@ +"use strict";(self.webpackChunk=self.webpackChunk||[]).push([[1495],{4058:(n,s,a)=>{a.r(s),a.d(s,{data:()=>e});const e={key:"v-d11abda4",path:"/guide/services/setting.html",title:"Setting service",lang:"en-US",frontmatter:{},excerpt:"",headers:[],filePathRelative:"guide/services/setting.md",git:{updatedTime:1646901485e3,contributors:[{name:"h-khodadadeh",email:"khodadadeh@coretrix.com",commits:2},{name:"Saman Shahroudi",email:"saman.shahroudi@coretrix.com",commits:1}]}}},9689:(n,s,a)=>{a.r(s),a.d(s,{default:()=>p});const e=(0,a(6252).uE)('

Setting service

If your application requires configurations that might change or predefined, you need to use setting service. You should save your settings in SettingsEntity, then use this service to fetch it.

Register the service into your main.go file:

registry.ServiceProviderSetting()\n
1

Access the service:

service.DI().Setting()\n
1

Use case

Imagine you need to restrict access to login page after certain number of failed login attempts. You can simply store this value in SettingsEntity and fetch it using this service:

package save \nimport (\n "service"\n)\n\nfunc SaveConfig(){\n    ormService := service.DI().ORMEngine()\n\tormService.Flush(&entity.SettingsEntity{\n\t\tKey:       "user.login.threshold",\n\t\tValue:     "3",\n\t\tValueType: entity.SettingsValueTypeAll.SettingsValueTypeNumber,\n\t})\n}\n
1
2
3
4
5
6
7
8
9
10
11
12
13

Then later in your login package, you can retrieve this value and use it:

package login\nimport (\n    "errors"\n    "service"\n)\n\nfunc Login(currentCount uint64) error {\n    ormService := service.DI().ORMEngine()\n    allowed, found := service.DI().Setting().GetUint64(ormService, "user.login.threshold")\n    if found && currentCount> allowed{\n        return errors.New("too many login attempt")\n    }  \n    return nil  \n}\n
1
2
3
4
5
6
7
8
9
10
11
12
13
14
',11),t={},p=(0,a(3744).Z)(t,[["render",function(n,s){return e}]])},3744:(n,s)=>{s.Z=(n,s)=>{for(const[a,e]of s)n[a]=e;return n}}}]); \ No newline at end of file diff --git a/assets/js/v-da6e2b5e.19354693.js b/assets/js/v-da6e2b5e.19354693.js new file mode 100644 index 00000000..3c94b5a7 --- /dev/null +++ b/assets/js/v-da6e2b5e.19354693.js @@ -0,0 +1 @@ +"use strict";(self.webpackChunk=self.webpackChunk||[]).push([[5154],{6299:(n,a,s)=>{s.r(a),s.d(a,{data:()=>e});const e={key:"v-da6e2b5e",path:"/rules/",title:"Documentation",lang:"en-US",frontmatter:{},excerpt:"",headers:[{level:2,title:"Localhost tools",slug:"localhost-tools",children:[{level:3,title:"force-alters",slug:"force-alters",children:[]}]},{level:2,title:"Domains",slug:"domains",children:[]},{level:2,title:"Crons (scripts)",slug:"crons-scripts",children:[]},{level:2,title:"Naming conventions and rules",slug:"naming-conventions-and-rules",children:[]},{level:2,title:"Teamwork rules",slug:"teamwork-rules",children:[]}],filePathRelative:"rules/README.md",git:{updatedTime:1657298404e3,contributors:[{name:"Anton",email:"a.shumansky@gmail.com",commits:5},{name:"Krasimir Ivanov",email:"krasimir.ivanov@coretrix.com",commits:1}]}}},5e3:(n,a,s)=>{s.r(a),s.d(a,{default:()=>o});const e=(0,s(6252).uE)('

Documentation

Localhost tools

force-alters

If you run your binary with argument --force-alters the program will check for DB and RediSearch alters and it will execute them(only in local mode).

make web-api param=--force-alters

Domains

Naming convention for our backend domains are:

[binary name].[env].[project].[domain]

For prod we are skipping [env] For example for our binary called web-api the domain will be web-api.dev.lys.domain.com or web-api.demo.lys.domain.com

Naming convention for our frontend domains are:

[binary name without suffix api].[env].[project].[domain]

For prod we are skipping [env] For example for web.dev.lys.domain.com or web.demo.lys.domain.com

Naming convention for our REST endpoints domains are:

[noun]/[noun].../[action]/?params

Always use - as separator. Endpoint name is not tied to package name.

POST - CREATE entity actions, SEARCH entity actions

PATCH - UPDATE entity actions

GET - GET entity actions

DELETE - DELETE entity actions

PUT - not used

Examples:

GET /profile/payment-info/cards/get/ - gets cards information

PATCH /profile/payment-info/cards/update/ - updates card information

POST /profile/payment-info/cards/create/ - creates card

DELETE /profile/payment-info/cards/delete/ - deletes card

Crons (scripts)

What is the differences between single-instance-cron and multi-instance-cron

  • single-instance-cron is for crons that cannot scale. Imagine you read something from db every 10min and you update something. If you have more than one instance it's gonna conflict. That's why we gonna create only one pod for it
  • multi-instance-cron for crons that can scale. Imagine you read from queue. You can have as much consumers as you want. That`s why we gonna have more pod instances for it

Naming conventions and rules

  1. If you have variable that contains one or more entities you should add a suffix to the variable Entity/Entities For example productEntity or productEntities
  2. Try to avoid append() Example:
package main\n\nfunc main()  {\n\tattributeEntities := make([]*struct{ID int}, 10)\n\tvar someMap = make([]int, len(attributeEntities))\n\tfor i, attributeEntity := range attributeEntities {\n\t\tsomeMap[i] = attributeEntity.ID //here we avoid map because we set the len\n\t}\n}\n\n
1
2
3
4
5
6
7
8
9
10
  1. If you implement communication with external API or something general that can be valid for every other project like Authentication for example, you can implement it as service in Hitrix.
  2. When declaring a variable, use inferred variable declaration syntax:
package main\n\nfunc main() {\n    //Acceptable\n    _ = &OrderEntity{}\n\n    //Not acceptable\n    var orderEntity OrderEntity\n    someFunc(&orderEntity)\n}\n\ntype OrderEntity struct{\n}\nfunc someFunc(entity *OrderEntity)  {\n \n}\n\n
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
  1. For declaring slices, please use make to declare your variable:
package main\n\nfunc main() {\n    //Acceptable\n    _ = make([]*OrderEntity, 0)\n\n    //Not acceptable\n    var _ []*OrderEntity\n}\ntype OrderEntity struct{\n}\n
1
2
3
4
5
6
7
8
9
10
11
  1. When instantiating graphql objects, use methods that are defined in populate package. If there is no method for your object, please declare one for your object.
  2. Don't use time.Now() . Use ioc.GetClockService().Now() instead. If you need pointer, use ioc.GetClockService().NowPointer().
  3. All entity files should have _entity.go suffix, the entity itself should end with Entity.
  4. In yaml config files we should set env vars only for values that going to be different in different environments(dev/demo/prod) If they are the same we should not use env var, but we can set the value into the yaml file
  5. Custom redis indexes can be re-indexed using dev panel but dirty queues needs extra effort from developer side.
  • You need to extend the slice into DevPanelController->GetActionListAction slice dirty This step will add new menu in dev panel dashboard.
  • Be sure that you added GET url for in into your router For example ginEngine.GET("/dev/mark-as-dirty-price-changed/", devPanel.GetMarkAsDirtyPriceChanged)
  • Your action should look like that
package controller\n\ntype DevPanelController struct{\n}\nfunc (controller *DevPanelController) GetMarkAsDirtyPriceChanged(c *gin.Context) {\n\tormService := service.DI().OrmEngineForContext(c.Request.Context())\n\n\tproducer := producers.PriceChangedDirtyAllProducer{}\n\n\terr := producer.Produce(ormService)\n\n\tif err != nil {\n\t\tresponse.ErrorResponseGlobal(c, err, nil)\n\t\treturn\n\t}\n\n\tc.JSON(200, gin.H{})\n}\n
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18

And your processor should look like that:

package model \ntype PriceChangedDirtyAllProducer struct {\n}\n\nfunc (p *PriceChangedDirtyAllProducer) Produce(ormService *beeorm.Engine) error {\n\tvariantEntity := entity.VariantEntity{}\n\twhere := beeorm.NewWhere("1 ORDER BY ID ASC")\n\tpager := &beeorm.Pager{CurrentPage: 1, PageSize: 1000}\n\thasMoreToIndex := true\n\tfor hasMoreToIndex {\n\t\tids := ormService.SearchIDs(where, pager, &variantEntity)\n\n\t\tif len(ids) == 0 {\n\t\t\tbreak\n\t\t}\n\n\t\tif len(ids) < pager.PageSize {\n\t\t\thasMoreToIndex = false\n\t\t}\n\n\t\tormService.MarkDirty(&entity.VariantEntity{}, redisstream.StreamOrmDirtyPriceChanged, ids...)\n\n\t\tpager.IncrementPage()\n\t}\n\n\treturn nil\n}\n
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27

Teamwork rules

We have defined rules that backend and frontend developers should follow to keep good communication and deliver the feature like a one team

  1. Read and discuss the epic with the business person
  2. Backend and frontend developers together should go through design and define endpoints and request/response structure. Whenever they are ready they should post it as a comment into the backend ticket to be visible that both agreed on it
  3. Start implementing the feature
  4. Before completing the task they should do following things:
    • When backend developer is done and all tests pass he should test every endpoint by himself using swagger on dev environment before complete his ticket
    • When frontend developer is done he should deploy on dev and test the feature very well before complete his ticket
  5. When everything works on dev frontend developer is responsible to talk to backend developer and together to deploy on demo and go through the flow and verify if it works
  6. Mark the task as completed and inform the business person.
',43),t={},o=(0,s(3744).Z)(t,[["render",function(n,a){return e}]])},3744:(n,a)=>{a.Z=(n,a)=>{for(const[s,e]of a)n[s]=e;return n}}}]); \ No newline at end of file diff --git a/assets/js/v-df4716ce.0053e281.js b/assets/js/v-df4716ce.0053e281.js new file mode 100644 index 00000000..57ee475b --- /dev/null +++ b/assets/js/v-df4716ce.0053e281.js @@ -0,0 +1 @@ +"use strict";(self.webpackChunk=self.webpackChunk||[]).push([[9135],{6754:(n,s,a)=>{a.r(s),a.d(s,{data:()=>e});const e={key:"v-df4716ce",path:"/guide/features/pagination.html",title:"Pagination",lang:"en-US",frontmatter:{},excerpt:"",headers:[],filePathRelative:"guide/features/pagination.md",git:{updatedTime:1634291321e3,contributors:[{name:"Anton",email:"a.shumansky@gmail.com",commits:1}]}}},9674:(n,s,a)=>{a.r(s),a.d(s,{default:()=>p});const e=(0,a(6252).uE)('

Pagination

You can use:

package helper\n\ntype URLQueryPager struct {\n\t// example = ?current_page=1&page_size=25\n\tCurrentPage int `binding:"min=1" form:"current_page"`\n\tPageSize    int `binding:"min=1" form:"page_size"`\n}\n
1
2
3
4
5
6
7

in your code that needs pagination like:

package mypackage\n\nimport "github.com/coretrix/hitrix/pkg/helper"\n\ntype SomeURLQuery struct {\n\thelper.URLQueryPager\n\tOtherField1 string `form:"other_field_1"`\n\tOtherField2 int `form:"other_field_2"`\n}\n
1
2
3
4
5
6
7
8
9
',5),t={},p=(0,a(3744).Z)(t,[["render",function(n,s){return e}]])},3744:(n,s)=>{s.Z=(n,s)=>{for(const[a,e]of s)n[a]=e;return n}}}]); \ No newline at end of file diff --git a/assets/js/v-e8c14dd6.163b3d24.js b/assets/js/v-e8c14dd6.163b3d24.js new file mode 100644 index 00000000..c9861940 --- /dev/null +++ b/assets/js/v-e8c14dd6.163b3d24.js @@ -0,0 +1 @@ +"use strict";(self.webpackChunk=self.webpackChunk||[]).push([[178],{431:(n,a,s)=>{s.r(a),s.d(a,{data:()=>e});const e={key:"v-e8c14dd6",path:"/guide/graphql/dataloaders.html",title:"Dataloaders",lang:"en-US",frontmatter:{},excerpt:"",headers:[{level:2,title:"How to create a dataloader?",slug:"how-to-create-a-dataloader",children:[]},{level:2,title:"How to use a dataloader?",slug:"how-to-use-a-dataloader",children:[]}],filePathRelative:"guide/graphql/dataloaders.md",git:{updatedTime:1644595642e3,contributors:[{name:"Krasimir Ivanov",email:"krasimir.ivanov@coretrix.com",commits:4}]}}},9364:(n,a,s)=>{s.r(a),s.d(a,{default:()=>p});const e=(0,s(6252).uE)('

Dataloaders

What are GraphQL Dataloaders? Please take a look here https://gqlgen.com/reference/dataloaders/

How to create a dataloader?

  1. Create a folder "dataloaders" inside graphql folder
  2. Create file dataloaders.go
package dataloaders\n\n//go:generate dataloaden LoaderName uint64 []*/path/entity.SomeModel/Entity\n
1
2
3
  1. Generate dataloaders
go generate ./api/<binary>/graphql/dataloaders/...\n
1
  1. Implement loaders

go generate will create gen.go file that contains loaders skeleton.

func NewLoaders(ctx context.Context) *Loaders {\n    return &Loaders{\n        VariantsByProductID: LoaderNameLoader{\n        maxBatch: 1000,\n        wait:     5 * time.Millisecond,\n        fetch: func(ids []uint64) ([][]*entity.SomeModel, []error) {\n                //Some code here\n\t\t\t\treturn someModelSlice, errors\n\t\t\t},\n\t\t}\n\t}\n}\n
1
2
3
4
5
6
7
8
9
10
11
12
  1. Create middleware and attach Dataloaders to context
  • Create file dataloader.go
func DataLoaders(ginEngine *gin.Engine) {\n\tginEngine.Use(func(c *gin.Context) {\n\t\tctx := c.Request.Context()\n\t\tctx = context.WithValue(ctx, key, NewLoaders(ctx))\n\t\tc.Request = c.Request.WithContext(ctx)\n\t})\n}\n
1
2
3
4
5
6
7
  • Add DataLoaders middleware to Gin
  1. Add Retriever to resolvers
// This file will not be regenerated automatically.\n//go:generate go run github.com/99designs/gqlgen\n// It serves as dependency injection for your app, add any dependencies you require here.\n\ntype Resolver struct {\n    DataLoaders dataloaders.Retriever\n}\n
1
2
3
4
5
6
7
  1. git add & git commit

How to use a dataloader?

  1. Accessing Dataloaders - dataloaders can be accessed using Retreive function in resolvers.
func (r *someResolver) SomeMethod(ctx context.Context, obj *model.SomeModel) ([]*model.SomeOtherModel, error) {\n\tif !hitrix.Validate(ctx, nil) {\n\t\treturn nil, nil\n\t}\n\n\tsomeOtherModel, err := r.DataLoaders.Retrieve(ctx).VariantsByProductID.Load(obj.ID)\n\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\n\treturn someOtherModel, nil\n}\n
1
2
3
4
5
6
7
8
9
10
11
12
13
',20),t={},p=(0,s(3744).Z)(t,[["render",function(n,a){return e}]])},3744:(n,a)=>{a.Z=(n,a)=>{for(const[s,e]of a)n[s]=e;return n}}}]); \ No newline at end of file diff --git a/assets/js/v-ee5df3ee.e465b6dc.js b/assets/js/v-ee5df3ee.e465b6dc.js new file mode 100644 index 00000000..ef10111d --- /dev/null +++ b/assets/js/v-ee5df3ee.e465b6dc.js @@ -0,0 +1 @@ +"use strict";(self.webpackChunk=self.webpackChunk||[]).push([[7152],{1866:(e,s,n)=>{n.r(s),n.d(s,{data:()=>a});const a={key:"v-ee5df3ee",path:"/guide/services/html2pdf.html",title:"HTML2PDF service",lang:"en-US",frontmatter:{},excerpt:"",headers:[],filePathRelative:"guide/services/html2pdf.md",git:{updatedTime:1659017697e3,contributors:[{name:"Krasimir Ivanov",email:"krasimir.ivanov@coretrix.com",commits:1}]}}},2378:(e,s,n)=>{n.r(s),n.d(s,{default:()=>c});const a=(0,n(6252).uE)('

HTML2PDF service

HTML2PDF service provides a generating pdf function from html code using Chrome headless.

First you need these in your app config:

chrome_headless:\n  web_socket_url: ENV[CHROME_HEADLESS_WEB_SOCKET_URL]\n
1
2

Register the service into your main.go file:

registry.ServiceProviderHTML2PDF()\n
1

Access the service:

service.DI().HTML2PDFService()\n
1

Using HtmlToPdf() function to generate PDF from html:

pdfBytes := html2pdfService.HtmlToPdf("<html><p>Hi!</p></html>")\n
1

Recommended docker file for Chrome headless:

https://hub.docker.com/r/chromedp/headless-shell/\n
1
',12),t={},c=(0,n(3744).Z)(t,[["render",function(e,s){return a}]])},3744:(e,s)=>{s.Z=(e,s)=>{for(const[n,a]of s)e[n]=a;return e}}}]); \ No newline at end of file diff --git a/assets/js/v-f8b83d42.6c3e2a39.js b/assets/js/v-f8b83d42.6c3e2a39.js new file mode 100644 index 00000000..8170e0ea --- /dev/null +++ b/assets/js/v-f8b83d42.6c3e2a39.js @@ -0,0 +1 @@ +"use strict";(self.webpackChunk=self.webpackChunk||[]).push([[1119],{355:(n,s,a)=>{a.r(s),a.d(s,{data:()=>t});const t={key:"v-f8b83d42",path:"/guide/services/feature_flag.html",title:"Feature flag service",lang:"en-US",frontmatter:{},excerpt:"",headers:[],filePathRelative:"guide/services/feature_flag.md",git:{updatedTime:1636464285e3,contributors:[{name:"Anton",email:"a.shumansky@gmail.com",commits:4}]}}},767:(n,s,a)=>{a.r(s),a.d(s,{default:()=>p});const t=(0,a(6252).uE)('

Feature flag service

This service provides you ability to enable and disable different features into your platform

Register the service into your main.go file:

registry.ServiceProviderFeatureFlag()\n
1

Access the service:

service.DI().FeatureFlag()\n
1

Please have in your mind that you need to register FeatureFlagEntity

At start of the service hitrix is syncing the feature_flag table with the registered features. Automatically make inserts and updates if it's needed. The service never remove from the table!

Also you are able to activate and deactivate the features using our dev panel

Use case

In case you want to enable/disable the whole resolver you can do it in that way

package graph\nimport (\n\t"errors"\n\t"service"\n)\n\nfunc (r *attributeResolver) Values(ctx context.Context, obj *model.Attribute) ([]*model.AttributeValue, error) {\n\tormService := service.DI().ORMEngineFromContext(ctx)\n\terr := service.DI().FeatureFlag().FailIfIsNotActive(ormService, "bundle")\n    if err != nil {\n\t\treturn nil, err\n    }\n\t\n\treturn attribute.ValuesWeb(ctx, obj)\n}\n
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15

In case you want to chek if feature is enabled/disabled somewhere in your logic you can use this method

package login\nimport (\n    "errors"\n    "service"\n)\n\nfunc Login(ctx context.Context) error {\n\tormService := service.DI().ORMEngineFromContext(ctx)\n\tisActiveBundle := service.DI().FeatureFlag().IsActive(ormService, "bundle")\n    if isActiveBundle{\n       //your logic here\n    }\n\t//your logic here\n\n\treturn nil\n}\n
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16

There are 2 methods that will help you to run all cron jobs that are related to the feature flag

for _, featureScript := range service.DI().FeatureFlag().GetScriptsSingleInstance(service.DI().OrmEngine()) {\n\t\t\tgo b.RunScript(featureScript)\n\t\t}\n
1
2
3

and

for _, featureScript := range service.DI().FeatureFlag().GetScriptsMultiInstance(service.DI().OrmEngine()) {\n\t\t\tgo b.RunScript(featureScript)\n\t\t}\n
1
2
3
',18),e={},p=(0,a(3744).Z)(e,[["render",function(n,s){return t}]])},3744:(n,s)=>{s.Z=(n,s)=>{for(const[a,t]of s)n[a]=t;return n}}}]); \ No newline at end of file diff --git a/assets/js/v-f9cbeb06.51708ef5.js b/assets/js/v-f9cbeb06.51708ef5.js new file mode 100644 index 00000000..9aff5c47 --- /dev/null +++ b/assets/js/v-f9cbeb06.51708ef5.js @@ -0,0 +1 @@ +"use strict";(self.webpackChunk=self.webpackChunk||[]).push([[3797],{1274:(e,n,a)=>{a.r(n),a.d(n,{data:()=>s});const s={key:"v-f9cbeb06",path:"/guide/services/app.html",title:"App",lang:"en-US",frontmatter:{},excerpt:"",headers:[],filePathRelative:"guide/services/app.md",git:{updatedTime:1634291321e3,contributors:[{name:"Anton",email:"a.shumansky@gmail.com",commits:1}]}}},4943:(e,n,a)=>{a.r(n),a.d(n,{default:()=>p});const s=(0,a(6252).uE)('

App

This service provide information about the application like MODE, NAME and so on.

It is auto registered so you don't need to register it

Access the service:

service.DI().App()\n
1
',5),t={},p=(0,a(3744).Z)(t,[["render",function(e,n){return s}]])},3744:(e,n)=>{n.Z=(e,n)=>{for(const[a,s]of n)e[a]=s;return e}}}]); \ No newline at end of file diff --git a/guide/features/acl.html b/guide/features/acl.html new file mode 100644 index 00000000..f3efde41 --- /dev/null +++ b/guide/features/acl.html @@ -0,0 +1,101 @@ + + + + + + + ACL | Hitrix - golang framework + + + + +

ACL

You can use ACL feature by including 4 hitrix entities in you orm init:

type RoleEntity struct {
+    beeorm.ORM `orm:"table=roles;redisCache;redisSearch=search_pool"`
+    ID         uint64    `orm:"sortable"`
+    Name       string    `orm:"required;searchable;unique=Name_FakeDelete:1"`
+    CreatedAt  time.Time `orm:"time=true"`
+    FakeDelete bool      `orm:"unique=Name_FakeDelete:2"`
+}
+
+type ResourceEntity struct {
+    beeorm.ORM `orm:"table=resources;redisCache;redisSearch=search_pool"`
+    ID         uint64    `orm:"searchable"`
+    Name       string    `orm:"required;searchable;unique=Name_FakeDelete:1"`
+    CreatedAt  time.Time `orm:"time=true"`
+    FakeDelete bool      `orm:"unique=Name_FakeDelete:2"`
+}
+
+type PermissionEntity struct {
+    beeorm.ORM `orm:"table=permissions;redisCache;redisSearch=search_pool"`
+    ID         uint64          `orm:"searchable;sortable"`
+    ResourceID *ResourceEntity `orm:"required;searchable;unique=ResourceID_Name_FakeDelete:1"`
+    Name       string          `orm:"required;searchable;unique=ResourceID_Name_FakeDelete:2"`
+    CreatedAt  time.Time       `orm:"time=true"`
+    FakeDelete bool            `orm:"unique=ResourceID_Name_FakeDelete:3"`
+}
+
+type PrivilegeEntity struct {
+    beeorm.ORM    `orm:"table=privileges;redisCache;redisSearch=search_pool"`
+    ID            uint64
+    RoleID        *RoleEntity         `orm:"required;searchable;unique=RoleID_ResourceID_FakeDelete:1"`
+    ResourceID    *ResourceEntity     `orm:"required;searchable;unique=RoleID_ResourceID_FakeDelete:2"`
+    PermissionIDs []*PermissionEntity `orm:"required;searchable"`
+    CreatedAt     time.Time           `orm:"time=true"`
+    FakeDelete    bool                `orm:"unique=RoleID_ResourceID_FakeDelete:3"`
+}
+
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34

After you do this, in you local (to the specific project) user entity, you can include foreign key to role entity like this:


+type UserEntity struct {
+    beeorm.ORM `orm:"table=users;log=log_db_pool;redisCache;redisSearch=search_pool"`
+    ID         uint64
+    RoleID     *hitrixEntity.RoleEntity `orm:"required"`
+}
+
1
2
3
4
5
6

Now you are ready to use the ACL feature by using these endpoints:

var aclController *hitrixController.ACLController
+{
+	v1Group.GET("/acl/resources/", aclController.ListResourcesAction)
+	v1Group.GET("/acl/role/:ID/", aclController.GetRoleAction)
+	v1Group.POST("/acl/roles/", aclController.ListRolesAction)
+	v1Group.POST("/acl/role/", aclController.CreateRoleAction)
+	v1Group.PUT("/acl/role/:ID/", aclController.UpdateRoleAction)
+	v1Group.DELETE("/acl/role/:ID/", aclController.DeleteRoleAction)
+	v1Group.POST("/acl/assign-role/", aclController.PostAssignRoleToUserAction(func() beeorm.Entity {
+		return &entity.AdminUserEntity{}
+	}))
+}
+
1
2
3
4
5
6
7
8
9
10
11
12

These endpoints allow you to configure different combinations of roles, resources and permissions. After you configure your roles and permissions, you can start using the ACL by using middleware:

func ACL(resource string, permissions ...string) gin.HandlerFunc {
+	return func(c *gin.Context) {
+		adminUserEntity, ok := ioc.GetAdminUserService().GetSession(c.Request.Context())
+		if !ok {
+			c.AbortWithStatus(http.StatusUnauthorized)
+
+			return
+		}
+
+		ormService := service.DI().OrmEngineForContext(c.Request.Context())
+
+		if !acl.ACL(ormService, adminUserEntity.User.RoleID, resource, permissions...) {
+			c.AbortWithStatus(http.StatusUnauthorized)
+
+			return
+		}
+
+		c.Next()
+	}
+}
+
+
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21

For example if you want to make the roles and permissions endpoints protected by ACL, you can modify the router to look like this:

var aclController *hitrixController.ACLController
+aclGroup := v1Group.Group("/acl/").Use(authMiddleware.AuthorizeWithHeaderStrict())
+{
+	aclGroup.GET("resources/", authMiddleware.ACL(constant.ResourceResource, constant.PermissionView), aclController.ListResourcesAction)
+	aclGroup.GET("role/:ID/", authMiddleware.ACL(constant.ResourceRole, constant.PermissionView), aclController.GetRoleAction)
+	aclGroup.POST("roles/", authMiddleware.ACL(constant.ResourceRole, constant.PermissionView), aclController.ListRolesAction)
+	aclGroup.POST("role/", authMiddleware.ACL(constant.ResourceRole, constant.PermissionModify), aclController.CreateRoleAction)
+	aclGroup.PUT("role/:ID/", authMiddleware.ACL(constant.ResourceRole, constant.PermissionModify), aclController.UpdateRoleAction)
+	aclGroup.DELETE("role/:ID/", authMiddleware.ACL(constant.ResourceRole, constant.PermissionModify), aclController.DeleteRoleAction)
+	aclGroup.POST("assign-role/", authMiddleware.ACL(constant.ResourceAdminUser, constant.PermissionAssignRole), aclController.PostAssignRoleToUserAction(func() beeorm.Entity {
+		return &entity.AdminUserEntity{}
+	}))
+}
+
1
2
3
4
5
6
7
8
9
10
11
12
13

You can see how this middleware is used on each endpoint, and it checks if the logged user role has the required privileges that are specified on each endpoint.

+ + + diff --git a/guide/features/consumer_runners.html b/guide/features/consumer_runners.html new file mode 100644 index 00000000..a20d3bfd --- /dev/null +++ b/guide/features/consumer_runners.html @@ -0,0 +1,15 @@ + + + + + + + Consumer runners | Hitrix - golang framework + + + + +

Consumer runners

Consumer runners enable you to quickly spin up BeeORM queue consumers easily. There are 2 types of consumer.

  • scalable
  • non scalable

ConsumerRunner - non scalable

Use queue.NewConsumerRunner(ctx) to make consumers which are not required to be able to scale.

This consumer works with following 4 interfaces:

  • ConsumerOne (consumes items one by one)
  • ConsumerMany (consumes items in batches)
  • ConsumerOneByModulo (consumes items one by one using modulo)
  • ConsumerManyByModulo (consumes items in batches using modulo)

ScalableConsumerRunner - scalable

Use queue.NewScalableConsumerRunner(ctx, persistent redis pool) to make consumers which are required to be able to scale.

This consumer works with following 2 interfaces:

  • ConsumerOne (consumes items one by one)
  • ConsumerMany (consumes items in batches)
+ + + diff --git a/guide/features/flags.html b/guide/features/flags.html new file mode 100644 index 00000000..f41e7428 --- /dev/null +++ b/guide/features/flags.html @@ -0,0 +1,15 @@ + + + + + + + Flags | Hitrix - golang framework + + + + +

Flags

Pre deploy

If you run your binary with argument -pre-deploy the program will check for alters and if there is no alters it will exit with code 0 but if there is an alters it will exit with code 1.

Force alters

If you run your binary with argument -force-alters the program will check for DB and RediSearch alters and it will execute them(only in local and qa mode). You can use this command on localhost if you use make file:

You can use this feature during the deployment process check if you need to execute the alters before you deploy it

+ + + diff --git a/guide/features/goroutine.html b/guide/features/goroutine.html new file mode 100644 index 00000000..76fa963e --- /dev/null +++ b/guide/features/goroutine.html @@ -0,0 +1,29 @@ + + + + + + + Goroutine | Hitrix - golang framework + + + + +

Goroutine

Go provides simple solution for concurrency using go keyword, but in any case that goroutine panics you have to handle that yourself. Hitrix provides Goroutine function that does this automatically for you and you don't have to be worry about your goroutine panic. Hitrix Goroutine function would recover the panic for you and log the error. You can use that like this:

package mypackage
+
+import "github.com/coretrix/hitrix"
+
+func Myfunc(){
+  hitrix.Goroutine(someFunc())
+}
+
1
2
3
4
5
6
7

Hitrix also provides another function named GoroutineWithRestart. If you have a function that must be run all the time you can use this function. In any case of panics it would log the error and automatically start the function again. You can use this function like this:

package mypackage
+
+import "github.com/coretrix/hitrix"
+
+func Myfunc(){
+  hitrix.GoroutineWithRestart(someFunc())
+}
+
1
2
3
4
5
6
7
+ + + diff --git a/guide/features/helper.html b/guide/features/helper.html new file mode 100644 index 00000000..28e7f708 --- /dev/null +++ b/guide/features/helper.html @@ -0,0 +1,15 @@ + + + + + + + Helpers | Hitrix - golang framework + + + + + + + + diff --git a/guide/features/pagination.html b/guide/features/pagination.html new file mode 100644 index 00000000..e7b46c18 --- /dev/null +++ b/guide/features/pagination.html @@ -0,0 +1,31 @@ + + + + + + + Pagination | Hitrix - golang framework + + + + +

Pagination

You can use:

package helper
+
+type URLQueryPager struct {
+	// example = ?current_page=1&page_size=25
+	CurrentPage int `binding:"min=1" form:"current_page"`
+	PageSize    int `binding:"min=1" form:"page_size"`
+}
+
1
2
3
4
5
6
7

in your code that needs pagination like:

package mypackage
+
+import "github.com/coretrix/hitrix/pkg/helper"
+
+type SomeURLQuery struct {
+	helper.URLQueryPager
+	OtherField1 string `form:"other_field_1"`
+	OtherField2 int `form:"other_field_2"`
+}
+
1
2
3
4
5
6
7
8
9
+ + + diff --git a/guide/features/script.html b/guide/features/script.html new file mode 100644 index 00000000..441ac12a --- /dev/null +++ b/guide/features/script.html @@ -0,0 +1,96 @@ + + + + + + + Running scripts | Hitrix - golang framework + + + + +

Running scripts

This feature is used to run background scripts(cron jobs).

First you need to define script definition that implements hitrix.Script interface:


+type TestScript struct {}
+
+func (script *TestScript) Code() string {
+    return "test-script"
+}
+
+func (script *TestScript) Unique() bool {
+    // if true you can't run more than one script at the same time
+    return false
+}
+
+func (script *TestScript) Description() string {
+    return "script description"
+}
+
+func (script *TestScript) Run(ctx context.Context, exit hitrix.Exit) {
+    // put logic here
+	if shouldExitWithCode2 {
+        exit.Error()	// you can exit script and specify exit code
+    }
+}
+
+
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23

Methods above are required. Optionally you can also implement these interfaces:


+// hitrix.ScriptInfinity interface
+func (script *TestScript) Infinity() bool {
+    // run script and use blocking operation in cases you run all your code in goroutines
+    return true
+}
+
+// hitrix.ScriptInterval interface
+func (script *TestScript) Interval() time.Duration {                                                    
+    // run script every minute
+    return time.Minute 
+}
+
+// hitrix.ScriptIntervalOptional interface
+func (script *TestScript) IntervalActive() bool {                                                    
+    // only run first day of month
+    return time.Now().Day() == 1
+}
+
+// hitrix.ScriptIntermediate interface
+func (script *TestScript) IsIntermediate() bool {                                                    
+    // script is intermediate, for example is listening for data in chain
+    return true
+}
+
+// hitrix.ScriptOptional interface
+func (script *TestScript) Active() bool {                                                    
+    // this script is visible only in local mode
+    return DIC().App().IsInLocalMode()
+}
+
+
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31

Once you defined script you can run it using RunScript method:

package main
+import "github.com/coretrix/hitrix"
+
+func main() {
+	h := hitrix.New("app_name", "your secret").Build()
+	h.RunBackgroundProcess(func(b *hitrix.BackgroundProcessor) {
+		b.RunScript(&TestScript)
+	})
+}
+
1
2
3
4
5
6
7
8
9

You can also register script as dynamic script and run it using program flag:

package main
+import "github.com/coretrix/hitrix"
+
+func main() {
+	
+    hitrix.New("app_name", "your secret").RegisterDIService(
+        &registry.ServiceProvider{
+            Name:   "my-script",
+            
+            Script: true, // you need to set true here
+            Build: func(ctn di.Container) (interface{}, error) {
+                return &TestScript{}, nil
+            },
+        },
+    ).Build()
+}
+
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16

You can see all available script by using special flag -list-scripts:

./app -list-scripts
+
1

To run script:

./app -run-script my-script
+
1
+ + + diff --git a/guide/features/seeder.html b/guide/features/seeder.html new file mode 100644 index 00000000..eb3a7fc5 --- /dev/null +++ b/guide/features/seeder.html @@ -0,0 +1,49 @@ + + + + + + + Database Seeding | Hitrix - golang framework + + + + +

Database Seeding

Hitrix supports seeds that can be used to populate database or apply changes on it.

To Seed your database (wikiopen in new window), you can use the seeding feature that is implemented as script (DBSeedScript). This DBSeedScript needs to be provided a Seeds map[string][]Seed field, Where the string key is the identifier of the project that is used. This measn you can have different project in one code base The Script can be implemented in your app by making a type that satisfies the Seed interface:

type Seed interface {
+    Execute(*beeorm.Engine)
+    Environments() []string
+    Name() string
+}
+
1
2
3
4
5

Example:

users_seed.go

type UsersSeed struct{}
+
+func (seed *UsersSeed) Name() string {
+    return "UsersSeed"
+}
+
+func (seed *UsersSeed) Environments() []string {
+    return []string{app.ModeTest, app.ModeLocal, app.ModeDev, app.ModeDemo, app.ModeProd}
+}
+
+func (seed *UsersSeed) Execute(ormService *beeorm.Engine) {
+	// TODO insert a new user entity to the db
+}
+
1
2
3
4
5
6
7
8
9
10
11
12
13

And after your server definition (such as the one in server.go), you could use it to run the seeds just like you would run any script (explained earlier in the scripts seciton) as follows:

// Server Definition
+s,_ := hitrix.New()
+
+...
+
+s.RunBackgroundProcess(func(b *hitrix.BackgroundProcessor) {
+  // seed database
+  go b.RunScript(&hitrixScripts.DBSeedScript{
+    SeedsPerProject: map[string][]hitrixScripts.Seed{
+        "project_name": {
+            &script.UserProfileAttributesSeed{},
+        },
+    })
+
+  // ... other scripts
+})
+
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16

This seed will only run if no seeds has ever been ran with that name (in db table seeder, no row with 'name'='UsersSeed' )

+ + + diff --git a/guide/features/test.html b/guide/features/test.html new file mode 100644 index 00000000..ad0a3630 --- /dev/null +++ b/guide/features/test.html @@ -0,0 +1,55 @@ + + + + + + + Tests | Hitrix - golang framework + + + + +

Tests

Hitrix provide you test abstract layer that can be used to simulate requests to your graphql api

In your code you can create similar function that makes new instance of your app

func createContextMyApp(t *testing.T, projectName string, resolvers graphql.ExecutableSchema) *test.Ctx {
+	defaultServices := []*service.Definition{
+		registry.ServiceProviderConfigDirectory("../example/config"),
+		registry.ServiceProviderOrmRegistry(entity.Init),
+		registry.ServiceProviderOrmEngine(),
+		//your services here
+	}
+
+	return test.CreateContext(t,
+		projectName,
+		resolvers,
+		func(ginEngine *gin.Engine) { middleware.Router(ginEngine) },
+		defaultServices,
+	)
+}
+
+
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16

After that you can call queries or mutations

func TestProcessApplePurchaseWithEmail(t *testing.T) {
+	type queryRegisterTransactions struct {
+		RegisterTransactionsResponse *model.RegisterTransactionsResponse `graphql:"RegisterTransactions(applePurchaseRequest: $applePurchaseRequest)"`
+	}
+
+	variables := map[string]interface{}{
+		"applePurchaseRequest": model.ApplePurchaseRequest{
+			ForceEmail:   false,
+		},
+	}
+
+	fakeMail := &mailMock.Sender{}
+	fakeMail.On("SendTemplate", "hymn@abv.bg").Return(nil)
+
+	got := &queryRegisterTransactions{}
+	projectName, resolver := tests.GetWebAPIResolver()
+	ctx := tests.CreateContextWebAPI(t, projectName, resolver, &tests.IoCMocks{MailService: fakeMail})
+
+	err := ctx.HandleMutation(got, variables)
+	assert.Nil(t, err)
+
+	//...
+	fakeMail.AssertExpectations(t)
+}
+
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24

Hitrix supports parallel tests In case you want to execute parallel tests you need to set PARALLEL_TESTS=true env var in your IDE config and be sure you don't have set -p 1 in Go tool arguments In case you want to disable parallel tests remove PARALLEL_TESTS or set it to false and set in Go tool arguments value -p 1

+ + + diff --git a/guide/features/upload_files.html b/guide/features/upload_files.html new file mode 100644 index 00000000..8f19eb85 --- /dev/null +++ b/guide/features/upload_files.html @@ -0,0 +1,29 @@ + + + + + + + Upload files | Hitrix - golang framework + + + + +

Upload files

Hitrix allow us to upload files using OSS service and assign them to FileEntity. Whenever we want to reassign them to the right entity we need to do something like that in separate endpoint

    //...
+	fileEntity := &entity.FileEntity{}
+	found := ormService.LoadByID(fileID, fileEntity)
+
+	if !found {
+		return fmt.Errorf("file with FileID %v not found", *fileID)
+	}
+
+	if fileEntity.Namespace != oss.NamespaceUserAvatar.String() {
+		return goErrors.New("wrong file category")
+	}
+
+	userEntity.Avatar = fileEntity.File
+    //...
+
1
2
3
4
5
6
7
8
9
10
11
12
13
14

If you want to enable this feature you should call middleware.FileRouter(ginEngine) This will add /v1/file/upload/ endpoint where the customers can upload their files

+ + + diff --git a/guide/features/validator.html b/guide/features/validator.html new file mode 100644 index 00000000..8c45b22f --- /dev/null +++ b/guide/features/validator.html @@ -0,0 +1,44 @@ + + + + + + + Validator | Hitrix - golang framework + + + + +

Validator

We support 2 types of validators. One of them is related to graphql, the other one is related to rest.

Graphql validator

There are 2 steps that needs to be executed if you want to use this kind of validator

  1. Add directive @validate(rules: String!) on INPUT_FIELD_DEFINITION into your schema.graphqls file

  2. Call ValidateDirective into your main.go file

config := generated.Config{Resolvers: &graph.Resolver{}, Directives: generated.DirectiveRoot{Validate: hitrix.ValidateDirective()} }
+
+s.RunServer(4001, generated.NewExecutableSchema(config), func(ginEngine *gin.Engine) {
+    commonMiddleware.Cors(ginEngine)
+    middleware.Router(ginEngine)
+})
+
1
2
3
4
5
6

After that you can define the validation rules in that way:

input ApplePurchaseRequest {
+  ForceEmail: Boolean!
+  Name: String
+  Email: String @validate(rules: "email") #for rules param you can use everything supported by https://github.com/go-playground/validator validate.Var(value, rules)
+  AppleReceipt: String!
+}
+
1
2
3
4
5
6

To handle the errors you need to call function hitrix.Validate(ctx, nil) in your resolver

func (r *mutationResolver) RegisterTransactions(ctx context.Context, applePurchaseRequest model.ApplePurchaseRequest) (*model.RegisterTransactionsResponse, error) {
+    if !hitrix.Validate(ctx, nil) {
+        return nil, nil
+    }
+    // your logic here...
+}
+
1
2
3
4
5
6

The function hitrix.Validate(ctx, nil) as second param accept callback where you can define your custom validation related to business logic

REST validator

You should define tags for every field

type RequestDTOMerchantSave struct {
+	StoreID         string `conform:"trim" binding:"required,min=1,max=30"`
+	StoreBio        string `conform:"trim" binding:"omitempty,min=5,max=1000"`
+	AvatarFileID    *uint64
+	ContactPhone    *ContactPhone    `binding:"omitempty"`
+	ContactWhatsapp *ContactWhatsapp `binding:"omitempty"`
+	ContactWeb      string           `binding:"omitempty,url"`
+	ContactTelegram *ContactTelegram `binding:"omitempty"`
+	ContactEmail    string           `conform:"trim" binding:"omitempty,email"`
+}
+
1
2
3
4
5
6
7
8
9
10

Using binding you can define all rules needed for the particular validation Using conform you can trim the value before validation to be applied

Validation notes
RepatriationAfterTyreBlockInMinutes int               `binding:"numeric,gte=0"`
+

If you want to support 0 value you should not put required tag for the fields because the validator thinks that zero is not a value

+ + + diff --git a/guide/graphql/dataloaders.html b/guide/graphql/dataloaders.html new file mode 100644 index 00000000..23248397 --- /dev/null +++ b/guide/graphql/dataloaders.html @@ -0,0 +1,58 @@ + + + + + + + Dataloaders | Hitrix - golang framework + + + + +

Dataloaders

What are GraphQL Dataloaders? Please take a look here https://gqlgen.com/reference/dataloaders/

How to create a dataloader?

  1. Create a folder "dataloaders" inside graphql folder
  2. Create file dataloaders.go
package dataloaders
+
+//go:generate dataloaden LoaderName uint64 []*/path/entity.SomeModel/Entity
+
1
2
3
  1. Generate dataloaders
go generate ./api/<binary>/graphql/dataloaders/...
+
1
  1. Implement loaders

go generate will create gen.go file that contains loaders skeleton.

func NewLoaders(ctx context.Context) *Loaders {
+    return &Loaders{
+        VariantsByProductID: LoaderNameLoader{
+        maxBatch: 1000,
+        wait:     5 * time.Millisecond,
+        fetch: func(ids []uint64) ([][]*entity.SomeModel, []error) {
+                //Some code here
+				return someModelSlice, errors
+			},
+		}
+	}
+}
+
1
2
3
4
5
6
7
8
9
10
11
12
  1. Create middleware and attach Dataloaders to context
  • Create file dataloader.go
func DataLoaders(ginEngine *gin.Engine) {
+	ginEngine.Use(func(c *gin.Context) {
+		ctx := c.Request.Context()
+		ctx = context.WithValue(ctx, key, NewLoaders(ctx))
+		c.Request = c.Request.WithContext(ctx)
+	})
+}
+
1
2
3
4
5
6
7
  • Add DataLoaders middleware to Gin
  1. Add Retriever to resolvers
// This file will not be regenerated automatically.
+//go:generate go run github.com/99designs/gqlgen
+// It serves as dependency injection for your app, add any dependencies you require here.
+
+type Resolver struct {
+    DataLoaders dataloaders.Retriever
+}
+
1
2
3
4
5
6
7
  1. git add & git commit

How to use a dataloader?

  1. Accessing Dataloaders - dataloaders can be accessed using Retreive function in resolvers.
func (r *someResolver) SomeMethod(ctx context.Context, obj *model.SomeModel) ([]*model.SomeOtherModel, error) {
+	if !hitrix.Validate(ctx, nil) {
+		return nil, nil
+	}
+
+	someOtherModel, err := r.DataLoaders.Retrieve(ctx).VariantsByProductID.Load(obj.ID)
+
+	if err != nil {
+		return nil, err
+	}
+
+	return someOtherModel, nil
+}
+
1
2
3
4
5
6
7
8
9
10
11
12
13
+ + + diff --git a/guide/services/api_logger.html b/guide/services/api_logger.html new file mode 100644 index 00000000..32efb10c --- /dev/null +++ b/guide/services/api_logger.html @@ -0,0 +1,22 @@ + + + + + + + API logger service | Hitrix - golang framework + + + + +

API logger service

This service is used to track every api request and response. For example it can be used in any other service as SMS service, Stripe service and so on. Using it you will have a history of all requests and responses and it will help you even in case you need to debug something.

Register the service into your main.go file:

registry.APILogger(&entity.APILogEntity{}),
+
1

Access the service:

service.DI().APILogger()
+
1

All the data it will be saved into the APILogEntity entity.

The methods that this service provide are:

type APILogger interface {
+	LogStart(logType string, request interface{})
+	LogError(message string, response interface{})
+	LogSuccess(response interface{})
+}
+
1
2
3
4
5

You should call LogStart before you send request to the api

You should call LogError in case api return you error

You should call LogSuccess in case api return you success

+ + + diff --git a/guide/services/app.html b/guide/services/app.html new file mode 100644 index 00000000..2d2a5fc3 --- /dev/null +++ b/guide/services/app.html @@ -0,0 +1,16 @@ + + + + + + + App | Hitrix - golang framework + + + + + + + + diff --git a/guide/services/authentication.html b/guide/services/authentication.html new file mode 100644 index 00000000..d60b30ab --- /dev/null +++ b/guide/services/authentication.html @@ -0,0 +1,50 @@ + + + + + + + Authentication Service | Hitrix - golang framework + + + + +

Authentication Service

This service is used to making the life easy by doing the whole authentication life cycle using JWT token.

Register the service into your main.go file:

registry.ServiceProviderAuthentication(),
+
1

Access the service:

service.DI().Authentication()
+
1
Dependencies :

JWTService

PasswordService

ClockService

GeneratorService

GoogleService # optional , when you need to support google login

FacebookService # optional , when you need to support facebook login

AppleService # optional , when you need to support apple login

func Authenticate(ormService *beeorm.Engine, uniqueValue string, password string, entity AuthProviderEntity) (accessToken string, refreshToken string, err error) {}
+func VerifyAccessToken(ormService *beeorm.Engine, accessToken string, entity beeorm.Entity) error {}
+func VerifySocialLogin(ctx context.Context, source, token string, isAndroid bool)
+func RefreshToken(ormService *beeorm.Engine, refreshToken string) (newAccessToken string, newRefreshToken string, err error) {}
+func LogoutCurrentSession(ormService *beeorm.Engine, accessKey string){}
+func LogoutAllSessions(ormService *beeorm.Engine, id uint64)
+func AuthenticateOTP(ormService *beeorm.Engine, phone string, entity OTPProviderEntity) (accessToken string, refreshToken string, err error){}
+
1
2
3
4
5
6
7
  1. The Authenticate function will take an uniqueValue such as Email or Mobile, a plain password, and generates accessToken and refreshToken. You will also need to pass your entity as third argument, and it will give you the specific user entity related to provided access token The entity should implement the AuthProviderEntity interface :
       type AuthProviderEntity interface {
    +    beeorm.Entity
    +    GetUniqueFieldName() string
    +    GetPassword() string
    +   }
    +
    1
    2
    3
    4
    5
    The example of such entity is as follows:
    type UserEntity struct {
    +    beeorm.ORM  `orm:"table=users;redisCache;redisSearch=search_pool"`
    +    ID       uint64 `orm:"searchable;sortable"`
    +    Email    string `orm:"required;unique=Email;searchable"`
    +    Password string `orm:"required"`
    +}
    +
    +func (user *UserEntity) GetUniqueFieldName() string {
    +    return "Email"
    +}
    +
    +func (user *UserEntity) GetPassword() string {
    +return user.Password
    +}
    +
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
  2. The VerifyAccessToken will get the AccessToken, process the validation and expiration, and fill the entity param with the authenticated user entity in case of successful authentication.
  3. The RefreshToken method will generate a new token pair for given user
  4. The LogoutCurrentSession you can logout the user current session , you need to pass it the accessKey that is the jwt identifier jti the exists in both access and refresh token.
  5. The LogoutAllSessions you can logout the user from all sessions , you need to pass it the id (user id).
  6. You need to have a authentication key in your config file for this service to work. secret key under authentication is mandatory but other options are optional:
  7. The service can also support OTP if you want your service to support otp you should have support_otp key set to true under authentication
  8. The service also needs redis to store its sessions so you need to identify the redis storage name in config , the key is auth_redis under authentication
authentication:
+  secret: "a-deep-dark-secret" #mandatory, secret to be used for JWT
+  access_token_ttl: 86400 # optional, in seconds, default to 1day
+  refresh_token_ttl: 31536000 #optional, in seconds, default to 1year
+  auth_redis: default #optional , default is the default redis
+  otp_ttl: 120 #optional ,set it when you want to use otp, It is the ttl of otp code , default is 60 seconds
+  otp_length: 5 #optional, set if you want to customize the length of otp (i.e. Email OTP)
+
1
2
3
4
5
6
7
+ + + diff --git a/guide/services/checkout.html b/guide/services/checkout.html new file mode 100644 index 00000000..319848a2 --- /dev/null +++ b/guide/services/checkout.html @@ -0,0 +1,47 @@ + + + + + + + Checkout.com API | Hitrix - golang framework + + + + +

Checkout.com API

This service is used to create a payment, check webhook key, request a refund and manage user cards for checkout.com payment API

Register the service into your main.go file:

hitrixRegistry.ServiceProviderCheckout()
+
1

And you should put your credentials and other configs in config/hitrix.yml

checkout:
+  secret_key: secret
+  public_key: public
+  currency: USD
+  webhook_keys:
+    main: somekey
+
1
2
3
4
5
6

Access the service:

checkoutService := service.DI().Checkout()
+
1

Using the service:

// Request a payment
+checkoutService.RequestPayment(
+    payments.IDSource{
+        Type: "id",
+        ID:  "sometoken",
+    },
+    100,
+    "USD",
+    "Order-1000",
+    &payments.Customer{Email: "email@email.com"},
+    map[string]string{"OrderId": "Order-1000"}
+)
+      
+// Request a refund
+checkoutService.RequestRefunds(1000, "PaymentId", "Order-1000", map[string]string{"OrderId": "Order-1000", "RefundsID": "Order-1000"})
+      
+// Validating incoming webhook
+checkoutService.CheckWebhookKey("main", "value of Authorization header")
+
+// Get user cards
+checkoutService.GetCustomerInstruments("cus_someid")
+
+// Delete user card
+checkoutService.DeleteCustomerInstrument("src_someid")
+
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
Last Updated: 10/29/2021, 11:32:03 AM
Contributors: Iman Daneshi, Saman Shahroudi
+ + + diff --git a/guide/services/clock.html b/guide/services/clock.html new file mode 100644 index 00000000..a9b90c86 --- /dev/null +++ b/guide/services/clock.html @@ -0,0 +1,17 @@ + + + + + + + Clock service | Hitrix - golang framework + + + + + + + + diff --git a/guide/services/clockwork.html b/guide/services/clockwork.html new file mode 100644 index 00000000..bcd8de50 --- /dev/null +++ b/guide/services/clockwork.html @@ -0,0 +1,32 @@ + + + + + + + ClockWork | Hitrix - golang framework + + + + +

ClockWork

This service provides you information about all queries executed by ORM and performance metrics for every api request. Also gives you ability to set your own log data. All the information it will be visible into your chrome inspector.

It requires installation of Clockwork Chrome extension ClockWork extensionopen in new window

You need to add special header to activate this feature. My recommendation is to install also this extension ModHeader extensionopen in new window You should set header CoreTrix with value equal to the password you will set bellow in your yaml file

clockwork:
+    password: "your password here"
+
+
1
2
3

Register the service into your main.go file as context service:

registry.ServiceProviderClockWorkForContext()
+
1

Access the service:

service.DI().ClockWorkForContext(ctx).GetLoggerDataSource()
+
1

There are 2 steps that also needs to be done:

  1. To add this middleware
	hitrixMiddleware "github.com/coretrix/hitrix/pkg/middleware"
+	
+	...
+	
+	hitrixMiddleware.Clockwork(ginEngine)
+
1
2
3
4
5
  1. To add special route
	hitrixController "github.com/coretrix/hitrix/pkg/controller"
+
+    ...
+
+	var clockwork *hitrixController.ClockworkController
+	ginEngine.GET("/__clockwork/:id", clockwork.GetIndexAction)
+
1
2
3
4
5
6

You also are able to send your own data to clockwork and use it as a logger to debug easily. You can do it in that way anywhere in your code:

	service.DI().ClockWorkForContext(ctx).GetLoggerDataSource().LogDebugString("key", "test")
+
1
+ + + diff --git a/guide/services/config.html b/guide/services/config.html new file mode 100644 index 00000000..9c45cd1a --- /dev/null +++ b/guide/services/config.html @@ -0,0 +1,30 @@ + + + + + + + Config | Hitrix - golang framework + + + + +

Config

This service provides you access to your config file. We support only YAML file.

Register the service into your main.go file:

 registry.ServiceProviderConfigDirectory("../config")
+
1

you should provide the folder where are your config files.

The folder structure should look like that:

config
+ - app-name
+    - config.yaml
+ - hitrix.yaml #optional config where you can define some settings related to built-in services like slack service
+
1
2
3
4

Access the service:

service.DI().Config()
+
1

Environment variables in config file

Its good practice to keep your secrets like database credentials and so on out of the repository. Our advice is to keep them like environment variables and call them into config.yaml file For example your config can looks like this:

orm:
+  default:
+    mysql: ENV[DEFAULT_MYSQL]
+    redis: ENV[DEFAULT_REDIS]
+    locker: default
+    local_cache: 1000
+
1
2
3
4
5
6

where DEFAULT_MYSQL and DEFAULT_REDIS are env variables and our framework will automatically replace ENV[DEFAULT_MYSQL] and ENV[DEFAULT_REDIS] with the right values

If you want to define array of values you should split them by ; and they will be presented into the yaml file in that way:

cors:
+    - test1
+    - test2
+
1
2
3

If you want to enable the debug for orm you can add this tag orm_debug: true on the main level of your config

Also we check if there is .env.XXX file in main config folder where XXX is the value of the APP_MODE. If there is for example .env.local we are reading those env variables and merge them with config.yaml how we presented above

+ + + diff --git a/guide/services/crud.html b/guide/services/crud.html new file mode 100644 index 00000000..6e22cda5 --- /dev/null +++ b/guide/services/crud.html @@ -0,0 +1,259 @@ + + + + + + + CRUD | Hitrix - golang framework + + + + +

CRUD

This service it gives you ability to build gql query and apply different query parameters to the query that should be used in listing pages

Register the service into your main.go file:

registry.ServiceProviderCrud(),
+
1

Access the service:

service.DI().Crud()
+
1

Search vs Filter

Search is used on strings while filtering can be used on wide range of data types. One important note to remember is that if your column is searchable it can't be filterable.

defining columns

First you need to define what columns you're going to have and which of them will be Searchable, Sortable or Filterable(user for enum values). Using this configuration you also define the supported filters that can be applied.

The second step is in your controller(handler) to call few methods from that service that will build the right query for you based on the request. Crud service supports mysql query builder and redis-search query builder.

Example of the way you can use it:

//defining columns.
+func columns() []crud.Column {
+    return []crud.Column{
+            {
+                Key:            "ID",
+                Type:           crud.NumberType,
+                Label:          "ID",
+                Searchable:     false,
+                Sortable:       true,
+                Visible:        true,
+                Filterable:     true,
+                FilterValidMap: nil,
+            },
+            {
+                Key:            "Name",
+                Type:           crud.StringType,
+                Label:          "Name",
+                Searchable:     true,
+                Sortable:       false,
+                Visible:        true,
+                Filterable:     false,
+                FilterValidMap: nil,
+            }
+        }
+}
+
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
//listing request using by gql
+type ListRequest struct {
+    Page     *int                   `json:"Page"`
+    PageSize *int                   `json:"PageSize"`
+    Filter   map[string]interface{} `json:"Filter"`
+    Search   map[string]interface{} `json:"Search"`
+    SearchOr map[string]interface{} `json:"SearchOR"`
+    Sort     map[string]interface{} `json:"Sort"`
+}
+
1
2
3
4
5
6
7
8
9

and at the end your method where you return the response:

cols := columns()
+
+crudService := service.DI().CrudService()
+
+searchParams := crudService.ExtractListParams(cols, crud.ListRequestConvertorFromGQL(userListRequest))
+query := crudService.GenerateListRedisSearchQuery(searchParams)
+
+ormService := ioc.GetOrmEngineFromContext(ctx)
+var userEntities []*entity.UserEntity
+
+ormService.RedisSearch(&userEntities, query, beeorm.NewPager(searchParams.Page, searchParams.PageSize))
+
+userEntityList := make([]*model.User, len(userEntities))
+
+for i, userEntity := range userEntities {
+    userEntityList[i] = populate.UserAdmin(userEntity)
+}
+
+return &model.UserList{
+    Rows:    userEntityList,
+    Total:   len(userEntityList),
+    Columns: crud.ColumnConvertorToGQL(cols),
+    }, nil
+
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23

Use Select fields with dependency

Imagine you have these fields with such options, on is the country and for the city filter to work user must select the country first and then select the proper city that exists in the list of that country otherwise this filter will be ignored.

Take london as an example, if we select United States as country and London as city, then city filter will be ignored, so make sure this is also handled in your front-end.

Note: If you don't set FilterDependencyField or DataMapStringStringKeyStringValue this filter will be ignored!

func columns() []crud.Column {
+    return []crud.Column{
+    		{
+			Key:                    "Country",
+			FilterType:             hitrixCrud.SelectTypeStringString,
+			Label:                  "Country",
+			Searchable:             true,
+			Sortable:               false,
+			Visible:                true,
+			TranslationDataEnabled: true,
+			DataStringKeyStringValue: []*hitrixCrud.StringKeyStringValue{
+				{Key: "bg", Label: "Bulgaria"},
+				{Key: "us", Label: "United States"},
+				{Key: "uk", Label: "United Kingdom"},
+			},
+		},
+		{
+			Key:                   "City",
+			FilterType:            hitrixCrud.SelectTypeStringString,
+			Label:                 "City",
+			Searchable:            true,
+			Sortable:              false,
+			Visible:               true,
+			FilterDependencyField: "Country",
+			DataMapStringStringKeyStringValue: map[string][]*hitrixCrud.StringKeyStringValue{
+				"bg": {
+					{Key: "sofia", Label: "Sofia"},
+					{Key: "plovdiv", Label: "Plovdiv"},
+				},
+				"us": {
+					{Key: "new_york", Label: "New York"},
+					{Key: "los_angeles", Label: "Los Angeles"},
+				},
+				"uk": {
+					{Key: "london", Label: "London"},
+				},
+			},
+		},
+	}
+}
+
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40

Use CRUD with our export service

You can mix our crud service with our exporter service to add a quick and painless exporting system to your project

In order to do that you can follow these steps

1 - Making your currently list view function private and creating another function for passing to gin router

for example here we renamed "List" to "list" and we created a new "ListRequest" function and we're passing it to gin router instead of "List"


+func Columns() []hitrixCrud.Column {
+    return []hitrixCrud.Column{
+        {
+            Key:        "ID",
+            Label:      "ID",
+            Searchable: false,
+            Sortable:   true,
+            Visible:    true,
+        },
+        {
+            Key:        "Name",
+            FilterType: hitrixCrud.InputTypeString,
+            Label:      "Name",
+            Searchable: true,
+            Sortable:   false,
+            Visible:    true,
+        },
+    }
+}
+
+type ListRow struct {
+    ID        uint64
+    Name      string
+}
+
+
+func ListRequest(ctx context.Context, request *hitrixCrud.ListRequest) (*city.ResponseDTOList, error) {
+	// You should pass this function to the gin router
+	return list(service.DI().OrmEngineForContext(ctx), request)
+}
+
+func list(ormService *beeorm.Engine, request *hitrixCrud.ListRequest) (*city.ResponseDTOList, error) {
+	
+	// this is the function that handles the getting and making the payload fot the crud that is going to be used
+	// for both list (endpoint) and exporting
+	
+	cols := Columns()
+	crudService := service.DI().Crud()
+
+	searchParams := crudService.ExtractListParams(cols, request)
+	query := crudService.GenerateListRedisSearchQuery(searchParams)
+
+	var cityEntities []*entity.CityEntity
+	
+	// your logic for getting entities from database with the query generated from crud
+
+	rows := make([]ListRow, len(cityEntities))
+
+	// you logic for creating rows for your crud
+
+	return &ResponseDTOList{
+		Rows:        rows,
+		Total:       int(total),
+		Columns:     cols,
+		PageContext: getPageContext(ormService),
+	}, nil
+}
+
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58

2 - Creating a new handler for exporting the data obtained from "list" function

func ListExport(ormService *beeorm.Engine, request *hitrixCrud.ListRequest, _ uint64, _ map[string]string) (error, []string, [][]interface{}) {
+	
+	// This function handles the data for exporting
+	
+	exportColumns := make([]string, 0) // excel or csv Columns for passing to our exporter service
+	allExportData := make([][]interface{}, 0) // data for passing to our exporter service
+
+	pager := beeorm.NewPager(1, 1000)
+
+	for {
+		
+		// loop for getting all the data out of the list function, bypassing the pagination
+		
+		request.Page = &pager.CurrentPage
+		request.PageSize = &pager.PageSize
+
+		res, err := list(ormService, request)
+
+		if err != nil {
+			return err, nil, nil
+		}
+		
+		// GetExporterDataCrud converts any given rows which in example is []city.ListRow to
+		// exporter service columns and data as types of []string, [][]interface, exactly the data you need for passing to our
+		// exporting system
+		
+		columns, exportData := hitrixCrud.GetExporterDataCrud(Columns(), res.Rows)
+
+		for _, exportDataRow := range exportData {
+			allExportData = append(allExportData, exportDataRow)
+		}
+
+		exportColumns = columns
+
+		if len(res.Rows) < pager.PageSize {
+			break
+		}
+
+		pager.IncrementPage()
+	}
+
+	return nil, exportColumns, allExportData
+}
+
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43

3 - Creating the config and passing it to the service

we pass the ListExport function that we created above to the config

const (
+    CityExportID                = "cities"
+)
+
+var RegisteredExportConfigs = []crud.ExportConfig{
+	{
+		Handler:          cityView.ListExport,
+		ID:               CityExportID,
+		AllowedExtraArgs: nil,
+		Resource:         "city",
+		Permissions:      []string{"read"},
+	},
+}
+
+RegisterDIGlobalService(
+    ...
+    hitrixRegistry.ServiceProviderCrud(RegisteredExportConfigs),
+    ...
+)
+
+
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20

4 - Getting export ready data from crud

Imagine we have these two cities in our database

[
+  {
+    "ID": 1,
+    "Name": "Sofia"
+  },
+  {
+    "ID": 2,
+    "Name": "New York"
+  }
+]
+
1
2
3
4
5
6
7
8
9
10

We use the crud method to get config/handler and export our data

handler, exists := crudService.GetExportHandler(CityExportID) // getting the handler
+
+err, columns, data := handler(
+    ormService,
+    &hitrixCrud.ListRequest{},
+    1, // user requesting the export
+    nil, 
+)
+
+print(columns)    // would print []string{"ID", "Name"}
+print(data) // would print [][]interface{}{[]interface{"1", "Sofia"}, []interface{"2", "New York"}}
+
+
1
2
3
4
5
6
7
8
9
10
11
12

and you can easily pass this data to our exporter service, for example:

excelBytes, err := exporterService.XLSXExportToByte("cities", columns, data)
+
1

and if you like to check the permissions you can get the whole config like this

config, exists := crudService.GetExportConfig(CityExportID) // getting the config
+
1
Last Updated: 10/5/2023, 11:25:47 AM
Contributors: Iman Daneshi, Anton, h-khodadadeh
+ + + diff --git a/guide/services/ddos.html b/guide/services/ddos.html new file mode 100644 index 00000000..406952c6 --- /dev/null +++ b/guide/services/ddos.html @@ -0,0 +1,17 @@ + + + + + + + DDOS Protection | Hitrix - golang framework + + + + + + + + diff --git a/guide/services/dynamic_link.html b/guide/services/dynamic_link.html new file mode 100644 index 00000000..a2603976 --- /dev/null +++ b/guide/services/dynamic_link.html @@ -0,0 +1,53 @@ + + + + + + + Dynamic link service | Hitrix - golang framework + + + + +

Dynamic link service

This service is used for generating dynamic links, at this moment only Firebase is supported

Register the service into your main.go file:

registry.ServiceProviderDynamicLink(),
+
1

Access the service:

service.DI().DynamicLink()
+
1

Config sample:

 api_key: string # required
+ dynamic_link_info: # required
+   domain_uri_prefix: string # required
+   link: string # required
+   android_info: # optional
+     package_name: string # optional
+     fallback_link: string # optional
+     min_package_version_code: string # optional
+   ios_info: # optional
+     bundle_id: string # optional
+     fallback_link: string # optional
+     custom_scheme: string # optional
+     ipad_fallback_link: string # optional
+     ipad_bundle_id: string # optional
+     app_store_id: string # optional
+   navigation_info: # optional
+     enable_forced_redirect: boolean # required
+   analytics_info: # optional
+     google_play_analytics: # optional
+       utm_source: string # optional
+       utm_medium: string # optional
+       utm_campaign: string # optional
+       utm_term: string # optional
+       utm_content: string # optional
+       gcl_id: string # optional
+     itunes_connect_analytics: # optional
+       at: string # optional
+       ct: string # optional
+       mt: string # optional
+       pt: string # optional
+   social_meta_tag_info: # optional
+     social_title: string # optional
+     social_description: string # optional
+     social_image_link: string # optional
+ suffix: # optional
+   option: string # required, values: "SHORT" or "UNGUESSABLE"
+
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
+ + + diff --git a/guide/services/elorus.html b/guide/services/elorus.html new file mode 100644 index 00000000..d675b0e5 --- /dev/null +++ b/guide/services/elorus.html @@ -0,0 +1,88 @@ + + + + + + + Elorus.com API | Hitrix - golang framework + + + + +

Elorus.com API

This service is used to access Elorus platform, for creating and managing invoices

Register the service into your main.go file:

hitrixRegistry.ServiceProviderElorus()
+
1

And you should put your credentials and other configs in config/hitrix.yml

elorus:
+  url: https://api.elorus.com/v1.1
+  token: secret
+  organization_id: secret
+
1
2
3
4

Access the service:

elorusService := service.DI().Elorus()
+
1

Using the service:

// Request to create contact
+elorusService.CreateContact(
+    elorus.CreateContactRequest{
+        FirstName: "name",
+        Active:  true,
+        Company:"company",
+        VatNumber:"BG108"
+        Email: []struct {   
+        	Email   string `json:"email"`
+            Primary bool   `json:"primary"`
+        }{{
+            Email:  "email@email.com",
+            Primary: true,
+        }},
+        Phones: []struct {
+            Number  string `json:"number"`
+            Primary bool   `json:"primary"`
+        }{{
+            Number:  "0869586598",
+            Primary: true,
+        }},        
+    },
+)
+      
+// Request to create invoice
+elorusService.CreateInvoice(
+	elorus.CreateInvoiceRequest{
+        Date:              time.Now().Format("2006-01-02"),
+        Client:            contactId,
+        ClientDisplayName: "name",
+        ClientEmail:       "email@email.com",
+        ClientVatNumber:   "BG108",
+        Number:            "0",
+        Items: []struct {
+            Title                        string   `json:"title"`
+            Description                  string   `json:"description"`
+            Quantity                     string   `json:"quantity"`
+            UnitMeasure                  int      `json:"unit_measure"`
+            UnitValue                    string   `json:"unit_value"`
+            Taxes                        []string `json:"Taxes"`
+            UnitTotal                    string   `json:"unit_total"`
+        }{{
+            Title:  "title",
+            Quantity: "5",
+			UnitValue: "800",
+            UnitMeasure: 1,
+            UnitTotal: "500",
+            Taxes:       []string{"2416104549958812800"},
+        }},
+    }
+)     
+
+// Request to get invoice list
+elorusService.GetInvoiceList(
+	elorus.GetInvoiceListRequest{
+        Client:            contactId,
+        Page: "1",
+        PageSize:"10",
+    }
+)
+
+// Request to get invoice list
+elorusService.DownloadInvoice(
+	elorus.DownloadInvoiceRequest{
+        ID:            "id",
+    }
+)
+
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
+ + + diff --git a/guide/services/error_logger.html b/guide/services/error_logger.html new file mode 100644 index 00000000..a3f6d485 --- /dev/null +++ b/guide/services/error_logger.html @@ -0,0 +1,21 @@ + + + + + + + Error Logger | Hitrix - golang framework + + + + +

Error Logger

Used to save unhandled errors in error log. Hitrix use recovery function to handle those errors. If you setup Slack service you also going to receive notifications in your slack

Register the service into your main.go file:

registry.ServiceProviderErrorLogger()
+
1

Access the service:

service.DI().ErrorLogger()
+
1

It can be used to save custom errors as well:

        errorLoggerService := ioc.GetErrorLoggerService()
+		
+		errorLoggerService.LogErrorWithRequest(c, err) //if you provide context we will save request body as well
+		errorLoggerService.LogError(err)
+
1
2
3
4
+ + + diff --git a/guide/services/exporter.html b/guide/services/exporter.html new file mode 100644 index 00000000..2a6946b4 --- /dev/null +++ b/guide/services/exporter.html @@ -0,0 +1,33 @@ + + + + + + + Exporter service | Hitrix - golang framework + + + + +

Exporter service

This service is able to export business data to various file formats. Currently, we support 2 file formats: XLSX and CSV.

Register the service into your main.go file:

registry.ServiceProviderExporter()
+
1

Access the service:

service.DI().Exporter()
+
1

Input data should be filled in as follows:

sheet := "sheet 1"
+headers := []string{"Header 1", "Header 2"}
+
+rows := make([][]interface{}, 0)
+
+var firstRow []interface{}
+firstRow = append(firstRow, "cell 1", "cell 2")
+rows = append(rows, firstRow)
+
+var secondRow []interface{}
+secondRow = append(secondRow, "cell 1", "cell 2")
+rows = append(rows, secondRow)
+
1
2
3
4
5
6
7
8
9
10
11
12

Use XLSXExportToByte() function to convert raw data to Excel file and return it as a byte slice:

xlsxBytes, err := exporterService.XLSXExportToByte(sheet, headers, rows)
+
1

Use XLSXExportToFile() function for converting raw data to Excel file and save it in the given path:

err := exporterService.XLSXExportToFile(sheet, headers, rows, filePath)
+
1

Use CSVExportToByte() function to convert raw data to CSV file and return it as a byte slice:

csvBytes, err := exporterService.CSVExportToByte(headers, rows)
+
1

Using CSVExportToFile() function for converting raw data to CSV file and save it in the given path:

err := exporterService.XLSXExportToFile(headers, rows, filePath)
+
1
+ + + diff --git a/guide/services/fcm.html b/guide/services/fcm.html new file mode 100644 index 00000000..825daf0e --- /dev/null +++ b/guide/services/fcm.html @@ -0,0 +1,16 @@ + + + + + + + Firebase cloud messaging (FCM) service | Hitrix - golang framework + + + + + + + + diff --git a/guide/services/feature_flag.html b/guide/services/feature_flag.html new file mode 100644 index 00000000..cb316148 --- /dev/null +++ b/guide/services/feature_flag.html @@ -0,0 +1,54 @@ + + + + + + + Feature flag service | Hitrix - golang framework + + + + +

Feature flag service

This service provides you ability to enable and disable different features into your platform

Register the service into your main.go file:

registry.ServiceProviderFeatureFlag()
+
1

Access the service:

service.DI().FeatureFlag()
+
1

Please have in your mind that you need to register FeatureFlagEntity

At start of the service hitrix is syncing the feature_flag table with the registered features. Automatically make inserts and updates if it's needed. The service never remove from the table!

Also you are able to activate and deactivate the features using our dev panel

Use case

In case you want to enable/disable the whole resolver you can do it in that way

package graph
+import (
+	"errors"
+	"service"
+)
+
+func (r *attributeResolver) Values(ctx context.Context, obj *model.Attribute) ([]*model.AttributeValue, error) {
+	ormService := service.DI().ORMEngineFromContext(ctx)
+	err := service.DI().FeatureFlag().FailIfIsNotActive(ormService, "bundle")
+    if err != nil {
+		return nil, err
+    }
+	
+	return attribute.ValuesWeb(ctx, obj)
+}
+
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15

In case you want to chek if feature is enabled/disabled somewhere in your logic you can use this method

package login
+import (
+    "errors"
+    "service"
+)
+
+func Login(ctx context.Context) error {
+	ormService := service.DI().ORMEngineFromContext(ctx)
+	isActiveBundle := service.DI().FeatureFlag().IsActive(ormService, "bundle")
+    if isActiveBundle{
+       //your logic here
+    }
+	//your logic here
+
+	return nil
+}
+
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16

There are 2 methods that will help you to run all cron jobs that are related to the feature flag

for _, featureScript := range service.DI().FeatureFlag().GetScriptsSingleInstance(service.DI().OrmEngine()) {
+			go b.RunScript(featureScript)
+		}
+
1
2
3

and

for _, featureScript := range service.DI().FeatureFlag().GetScriptsMultiInstance(service.DI().OrmEngine()) {
+			go b.RunScript(featureScript)
+		}
+
1
2
3
+ + + diff --git a/guide/services/file_extractor.html b/guide/services/file_extractor.html new file mode 100644 index 00000000..b2584baa --- /dev/null +++ b/guide/services/file_extractor.html @@ -0,0 +1,25 @@ + + + + + + + File extractor service | Hitrix - golang framework + + + + +

File extractor service

File extractor provides you a simple function to search in a path recursively and find terms based on a regular expression.

Register the service into your main.go file:

registry.ServiceProviderExtractor(),
+
1

Access the service:

service.DI().FileExtractorService()
+
1

Extract phrase (errors in this example):

errorTerms, err := extractService.Extract(fileextractor.ExtractParams{
+  SearchPath: "./",
+  Excludes:   []string{},
+  Expression: `errors.New[(]*\("([^)]*)"\)`,
+})
+if err != nil {
+  // handle error
+}
+
1
2
3
4
5
6
7
8
+ + + diff --git a/guide/services/geocoding.html b/guide/services/geocoding.html new file mode 100644 index 00000000..30900574 --- /dev/null +++ b/guide/services/geocoding.html @@ -0,0 +1,27 @@ + + + + + + + Geocoding | Hitrix - golang framework + + + + +

Geocoding

This service is used for geocoding and reverse geocoding. It supports multiple providers. For now only Google Maps provider is implemented.

You should put your api key from Google Maps in config:

geocoding:
+  use_caching: true
+  cache_ttl_min_days: 5
+  cache_ttl_max_days: 10
+  google_maps:
+    api_key: some_key
+
1
2
3
4
5
6

Note 1: if you decide to use caching functionality, you need to run script ClearExpiredGeocodingCache in your project. This script will delete expired cache.

Note 2: if you decide to use caching functionality, lat/lng are cut (not rounded) to 5 decimal place

Access the service:

service.DI().Geocoding()
+
1

The service exposes 3 methods that you can use:

type IGeocoding interface {
+	Geocode(ctx context.Context, ormService *beeorm.Engine, address string, language Language) (*Address, error)
+	ReverseGeocode(ctx context.Context, ormService *beeorm.Engine, latLng *LatLng, language Language) (*Address, error)
+    CutCoordinates(float float64, precision int) (float64, error)
+}
+
1
2
3
4
5
+ + + diff --git a/guide/services/google_analytics.html b/guide/services/google_analytics.html new file mode 100644 index 00000000..f0abd292 --- /dev/null +++ b/guide/services/google_analytics.html @@ -0,0 +1,20 @@ + + + + + + + Google Analytics | Hitrix - golang framework + + + + +

Google Analytics

This service is used for querying from Google Analytics

Register the service into your main.go. You need to provide function that init the analytics type (UA or GA4)

    registry.ServiceProviderGoogleAnalytics(googleanalytics.NewGA4)
+
1

Then you need to download client configuration (credentials file) from your panel and put it in configs folder. After that, you should put your ID of your GA property and config file name in config yaml file

Example:

google_analytics:
+  config_file_name: Name-s0m3r4nd0mv4lu3.json
+  property_id: 123456789
+
1
2
3

Access the service:

service.DI().GoogleAnalytics().GetProvider(googleanalytics.GA4)
+
1
+ + + diff --git a/guide/services/gql.html b/guide/services/gql.html new file mode 100644 index 00000000..e20ed213 --- /dev/null +++ b/guide/services/gql.html @@ -0,0 +1,17 @@ + + + + + + + Gql | Hitrix - golang framework + + + + + + + + diff --git a/guide/services/html2pdf.html b/guide/services/html2pdf.html new file mode 100644 index 00000000..6a24a8d7 --- /dev/null +++ b/guide/services/html2pdf.html @@ -0,0 +1,21 @@ + + + + + + + HTML2PDF service | Hitrix - golang framework + + + + +

HTML2PDF service

HTML2PDF service provides a generating pdf function from html code using Chrome headless.

First you need these in your app config:

chrome_headless:
+  web_socket_url: ENV[CHROME_HEADLESS_WEB_SOCKET_URL]
+
1
2

Register the service into your main.go file:

registry.ServiceProviderHTML2PDF()
+
1

Access the service:

service.DI().HTML2PDFService()
+
1

Using HtmlToPdf() function to generate PDF from html:

pdfBytes := html2pdfService.HtmlToPdf("<html><p>Hi!</p></html>")
+
1

Recommended docker file for Chrome headless:

https://hub.docker.com/r/chromedp/headless-shell/
+
1
+ + + diff --git a/guide/services/jwt.html b/guide/services/jwt.html new file mode 100644 index 00000000..c71261e2 --- /dev/null +++ b/guide/services/jwt.html @@ -0,0 +1,17 @@ + + + + + + + JWT | Hitrix - golang framework + + + + + + + + diff --git a/guide/services/kubernetes.html b/guide/services/kubernetes.html new file mode 100644 index 00000000..2401bc81 --- /dev/null +++ b/guide/services/kubernetes.html @@ -0,0 +1,24 @@ + + + + + + + Kubernetes | Hitrix - golang framework + + + + +

Kubernetes

This service is used for calling Kubernetes APIs.
Currently, this service can be used to add other domains to Kubernetes Ingresses and handling certification generation with cert-manager.

To use this, register the service into your main.go file first:

registry.ServiceProviderKubernetes()
+
1

and you should put your credentials and other configs in your config file

kubernetes:
+  environment: "dev"
+  config_file: "/path/to/kubeconfig"
+
1
2
3

the config_file can be one of these:

  • absolute path to config file
  • relative path to config file - then address of config directory will be prepended to it
  • can be omitted completely - then Kubernetes in-cluster config of Service Account Tokenopen in new window will be used

Access the service:

service.DI().Kubernetes()
+
1

Some functions this service provide are:

	GetIngressDomains(ctx context.Context) ([]string, error)
+	AddIngress(ctx context.Context, domain, secretName, serviceName, servicePortName string, annotations map[string]string) error
+   	RemoveIngress(ctx context.Context, domain string) error
+   	IsCertificateProvisioned(ctx context.Context, secretName string) (bool, error)
+
1
2
3
4
+ + + diff --git a/guide/services/license_plate_recognizer.html b/guide/services/license_plate_recognizer.html new file mode 100644 index 00000000..b188c589 --- /dev/null +++ b/guide/services/license_plate_recognizer.html @@ -0,0 +1,18 @@ + + + + + + + Licence plate recognizer | Hitrix - golang framework + + + + + + + + diff --git a/guide/services/localizer.html b/guide/services/localizer.html new file mode 100644 index 00000000..13afe214 --- /dev/null +++ b/guide/services/localizer.html @@ -0,0 +1,50 @@ + + + + + + + Localizer service | Hitrix - golang framework + + + + +

Localizer service

Localizer provides you a simple translation service that can pull and push translation pairs from local (file) and external sources (online services).

Currently localizer supports only POEditoropen in new window online source.

Localizer using a bucket key to separate and manage translation parts of your app.

First you need these in your app config:

translation:
+  poeditor:
+    api_key: ENV[POEDITOR_API_KEY]
+    project_id: ENV[POEDITOR_PROJECT_ID]
+    language: ENV[POEDITOR_LANGUAGE]
+
1
2
3
4
5

Register the service into your main.go file:

registry.ServiceProviderLocalizer("") //for param you can provide env var as key(not as value!) in case you want to have sub folder in locale folder 
+
1

Access the service:

service.DI().LocalizerService()
+
1

Loading translation pairs from map:

bucketKey := "greet-service"
+append := false // append or replace?
+pairs := map[string]string{
+  "app_name": "My App Name",
+  "loading_text": "Loading ...",
+}
+localizerService.LoadBucketFromMap(
+  bucketKey, 
+  pairs, 
+  append,
+)
+
1
2
3
4
5
6
7
8
9
10
11

Using Localize() function to translate a key:

appName, err := localizerService.Localize(bucketKey, "app_name")
+if err !nil {
+  // handle error
+}
+
1
2
3
4

Loading translation pairs from local file:

localizerService.LoadBucketFromFile(
+  bucketKey,
+  "locales/greet.en.json",
+  append,
+)
+
1
2
3
4
5

Pull the translations from external source:

err := localizerService.PullBucketFromSource(bucketKey, append)
+if err != nil {
+  log.Fatal(err)
+}
+
1
2
3
4

Push translations to external source:

err := localizerService.PushBucketToSource(bucketKey)
+if err != nil {
+  // handle error
+}
+
1
2
3
4
+ + + diff --git a/guide/services/mail.html b/guide/services/mail.html new file mode 100644 index 00000000..e0c8c87a --- /dev/null +++ b/guide/services/mail.html @@ -0,0 +1,34 @@ + + + + + + + Mail Mailjet service | Hitrix - golang framework + + + + +

Mail Mailjet service

This service is used for sending transactional emails using providers like Mailjet or Mandrill

Register the service into your main.go file:

registry.ServiceProviderMail(mail.NewMailjet)
+
1

and you should register the entity MailTrackerEntity into the ORM Also, you should put your credentials and other configs in your config file

mail:
+  mailjet:
+    api_key_public: ...
+    api_key_private: ...
+    default_from_email: test@coretrix.tv
+    from_name: coretrix.com
+    sandbox_mode: false
+    whitelisted_emails:
+      - "@coretrix.com"
+  mandrill:
+    api_key: ...
+    default_from_email: test@coretrix.tv
+    from_name: coretrix.com
+
1
2
3
4
5
6
7
8
9
10
11
12
13

If you set sandbox_mode=true we won't send real email to the customer.

BUT we allow sandbox_mode to be disabled only for specific emails or domains. That's why we created config param called whitelisted_emails All emails or domains defined in this config param will be sent to receiver even if sandbox is enabled.

The idea is to be able to test the platform and in the same time to be sure that real emails are not sent to customers.

Access the service:

service.DI().Mail()
+
1

Some functions this service provide are:

    GetTemplateKeyFromConfig(templateName string) (string, error)
+    SendTemplate(ormService *beeorm.Engine, message *Message) error
+    SendTemplateWithAttachments(ormService *beeorm.Engine, message *MessageAttachment) error
+    GetTemplateHTMLCode(templateName string) (string, error)
+
1
2
3
4
+ + + diff --git a/guide/services/orm_engine.html b/guide/services/orm_engine.html new file mode 100644 index 00000000..f55e2b27 --- /dev/null +++ b/guide/services/orm_engine.html @@ -0,0 +1,17 @@ + + + + + + + ORM Engine | Hitrix - golang framework + + + + + + + + diff --git a/guide/services/orm_engine_context.html b/guide/services/orm_engine_context.html new file mode 100644 index 00000000..0bad5a63 --- /dev/null +++ b/guide/services/orm_engine_context.html @@ -0,0 +1,17 @@ + + + + + + + ORM Engine Context | Hitrix - golang framework + + + + + + + + diff --git a/guide/services/oss.html b/guide/services/oss.html new file mode 100644 index 00000000..acb5a257 --- /dev/null +++ b/guide/services/oss.html @@ -0,0 +1,54 @@ + + + + + + + Object Storage Service | Hitrix - golang framework + + + + +

Object Storage Service

This service is used for storing files into any amazon s3 or google cloud compatible services

Register the service into your main.go. You need to provide function that init the provider and list of buckets

registry.ServiceProviderOSS(oss.NewAmazonOSS, oss.Namespaces{"products": oss.BucketPublic})
+
1

and you should register the entity OSSBucketCounterEntity into the ORM

Also, you should put your credentials and other configs in config/hitrix.yml

S3 example:

oss:
+  amazon_s3:
+    endpoint: "https://somestorage.com" # set to "" if you're using https://s3.amazonaws.com
+    access_key_id: ENV[S3_ACCESS_KEY_ID]
+    secret_access_key: ENV[S3_SECRET_ACCESS_KEY_ID]
+    disable_ssl: false
+    region: us-east-1
+  buckets:
+    public: # config for public bucket
+     name: bucket-name # bucket name
+     cdn_url: "https://somesite.com/{{.StorageKey}}/" # Available variables is: .StorageKey (Namespace is part of StorageKey)
+    private: # config for private bucket
+     name: bucket-name-private # bucket name
+     local: "http://127.0.0.1/{{.StorageKey}}" # Will output "http://127.0.0.1/product/1.jpeg"
+
1
2
3
4
5
6
7
8
9
10
11
12
13
14

Google example:

In your config folder you should put the .oss.json config file that you have from google Your config file should looks like that:

{
+  "type": "...",
+  "project_id": "...",
+  "private_key_id": "...",
+  "private_key": "...",
+  "client_email": "...",
+  "client_id": "...",
+  "auth_uri": "...",
+  "token_uri": "...",
+  "auth_provider_x509_cert_url": "...",
+  "client_x509_cert_url": "..."
+}
+
1
2
3
4
5
6
7
8
9
10
11
12

The last thing you need to set in domain that gonna be used for the static files. You can setup the domain in hitrix.yaml config file like this:

oss: 
+  domain: myapp.com
+  google:
+    anyvar: 1
+  buckets:
+    public: # config for public bucket
+      name: bucket-name # bucket name
+      cdn_url: "https://somesite.com/{{.StorageKey}}/" # Available variables are: .Namespace, .CounterID, and, .StorageKey
+    private: # config for private bucket
+      name: bucket-name-private # bucket name
+      local: "http://127.0.0.1/{{.Namespace}}/{{.StorageKey}}/{{.CounterID}}" # Will output "http://127.0.0.1/product/1.jpeg/1"
+
1
2
3
4
5
6
7
8
9
10
11

Access the service:

service.DI().OSService()
+
1
+ + + diff --git a/guide/services/otp.html b/guide/services/otp.html new file mode 100644 index 00000000..43e36022 --- /dev/null +++ b/guide/services/otp.html @@ -0,0 +1,68 @@ + + + + + + + OTP service | Hitrix - golang framework + + + + +

OTP service

If you want to authenticate your user using OTP you may need to use OTP service. This service can send the code using SMS or even call and verifying it later.

Register the service into your main.go file:

registry.ServiceProviderOTP()
+
1

Supported OTP providers:

  1. Twilio
  2. Sinch
  3. Mada

Now it is possible to provide phone number prefixes for each OTP provider.

For example if I provide the following setting Twilio;Mada:+35987,+35988, this means if phone numer starts with either of +35987 or +35988, then we will use Mada, for all others numbers we will use Twilio.

You can register OTP providers in 2 ways:

  1. Provide setting in DB for key: otp_sms_provider with value either of Twilio or Sinch or Mada. You can pass all providers as well separated by semicolon - Twilio;Mada:+35987,+35988;Sinch.
  2. Call registry.ServiceProviderOTP(otp.SMSOTPProviderTwilio, otp.SMSOTPProviderSinch) with 1 or more parameters for force provider.

Retry feature:

You can set up the service to retry failed OTP send attempts. In order to do this, you need to add in the config:

sms:
+  retry: true
+  max_retries: 20
+
1
2
3

For retry feature you also need to start in your app this consumer:

    // add this if you want to use send OTP retry feature
+    s.RunBackgroundProcess(func(b *hitrix.BackgroundProcessor) {
+	    go b.RunScript(&scripts.RetryOTPConsumer{})
+    })
+
1
2
3
4

Retry feature uses exponential backoff to retry OTP requests, starting from 0.5 seconds. If max_retries is reached, the consumer will drop the OTP request and mark it unsendable in DB.

Access the service:

service.DI().OTP()
+
1

Use case

You can send OTP to user phone using SMS or call like this:

package auth
+import (
+    "context"
+    "service"
+    "github.com/coretrix/hitrix/service/component/otp"
+)
+
+func SendOTP(){
+    ormService := service.DI().OrmEngineForContext(context.Background())
+    OTPService := service.DI().OTP()
+
+    // add this if you want to use send OTP retry feature
+    s.RunBackgroundProcess(func(b *hitrix.BackgroundProcessor) {
+    	go b.RunScript(&scripts.RetryOTPConsumer{})
+    })
+
+	//SMS
+    code, err := OTPService.SendSMS(ormService, &otp.Phone{
+        Number: "+123456789",
+    })
+
+    //call
+    code, err := OTPService.Call(ormService, &otp.Phone{
+        Number: "+123456789",
+    })
+}
+
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26

Then you can verify OTP like this:

package auth
+import (
+    "context"
+    "service"
+    "github.com/coretrix/hitrix/service/component/otp"
+)
+
+func Verify(){
+    ormService := service.DI().OrmEngineForContext(context.Background())
+    OTPService := service.DI().OTP()
+    code:="1234" //the code user entered
+
+    otpRequestValid, otpCodeValid, err := OTPService.Verify(
+    ormService,
+    &otp.Phone{Number: "+123456789"},
+    code,
+    )
+}
+
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
Last Updated: 5/19/2022, 1:57:17 PM
Contributors: Iliyan, Yavor, majid mohsenifar
+ + + diff --git a/guide/services/password.html b/guide/services/password.html new file mode 100644 index 00000000..c6e79fba --- /dev/null +++ b/guide/services/password.html @@ -0,0 +1,17 @@ + + + + + + + Password | Hitrix - golang framework + + + + + + + + diff --git a/guide/services/request_logger.html b/guide/services/request_logger.html new file mode 100644 index 00000000..9fef5957 --- /dev/null +++ b/guide/services/request_logger.html @@ -0,0 +1,31 @@ + + + + + + + Request Logger service | Hitrix - golang framework + + + + +

Request Logger service

This service is used for logging all upcoming and outgoing requests

Register the service into your main.go file:

registry.ServiceProviderRequestLogger()
+
1

and you should register the entity RequestLoggerEntity into the ORM

Access the service:

service.DI().RequestLogger()
+
1

The functions this service provide are:

	LogRequest(ormService *beeorm.Engine, appName, url string, request *http.Request, contentType string) *entity.RequestLoggerEntity
+    LogResponse(ormService *beeorm.Engine, requestLoggerEntity *entity.RequestLoggerEntity, responseBody []byte, status int)
+
1
2

They can be used to log any outgoing requests you send

Also you are able to enable middleware which will log all incoming requests

middleware.RequestLogger(ginEngine, func(context *gin.Context, requestEntity *entity.RequestLoggerEntity) {
+			userService := ioc.GetUserService()
+			session, hasSession := userService.GetSession(context.Request.Context())
+
+			if hasSession && session.User != nil {
+				requestEntity.UserID = session.User.ID
+			}
+		})
+
1
2
3
4
5
6
7
8

The second parameter (anonymous function) is called extender and it is used to save extra param to request_logger table like logged user id

If you want to use this middleware please do not forget to register the entity RequestLoggerEntity

We created a Cleaner that will remove all rows in request_logger table older than 30 days by default. This will prevent your database to be fulfilled with logs If you want to change ttl to other value you can do it from your config file like on the example bellow:

request_logger:
+  ttl_in_days: 5
+
1
2

To enable it please put this code into your single-instance-cron

    b := &hitrix.BackgroundProcessor{Server: s}
+    b.RunAsyncRequestLoggerCleaner()
+
1
2

Using our Dev Panel you will be able easily to see all requests and search trough them

+ + + diff --git a/guide/services/sentry.html b/guide/services/sentry.html new file mode 100644 index 00000000..83817ad8 --- /dev/null +++ b/guide/services/sentry.html @@ -0,0 +1,22 @@ + + + + + + + Sentry service | Hitrix - golang framework + + + + +

Sentry service

This service allow you to use sentry for logging events and performance tracking.

Register the service into your main.go file:

registry.ServiceProviderSentry(tracesSampleRate *float64)
+
1

Access the service:

service.DI().Sentry()
+
1

The methods that this service provide are:

type ISentry interface {
+    CaptureMessage(message string)
+    Flush(timeout time.Duration)
+    StartSpan(ctx context.Context, operation string, options ...sentry.SpanOption) *sentry.Span
+}
+
1
2
3
4
5

You should call CaptureMessage when you want to send event to sentry

You should call Flush in your main file with defer, with 2 second timeout

You should call StartSpan when you want to start performance monitor

+ + + diff --git a/guide/services/setting.html b/guide/services/setting.html new file mode 100644 index 00000000..29e57353 --- /dev/null +++ b/guide/services/setting.html @@ -0,0 +1,44 @@ + + + + + + + Setting service | Hitrix - golang framework + + + + +

Setting service

If your application requires configurations that might change or predefined, you need to use setting service. You should save your settings in SettingsEntity, then use this service to fetch it.

Register the service into your main.go file:

registry.ServiceProviderSetting()
+
1

Access the service:

service.DI().Setting()
+
1

Use case

Imagine you need to restrict access to login page after certain number of failed login attempts. You can simply store this value in SettingsEntity and fetch it using this service:

package save 
+import (
+ "service"
+)
+
+func SaveConfig(){
+    ormService := service.DI().ORMEngine()
+	ormService.Flush(&entity.SettingsEntity{
+		Key:       "user.login.threshold",
+		Value:     "3",
+		ValueType: entity.SettingsValueTypeAll.SettingsValueTypeNumber,
+	})
+}
+
1
2
3
4
5
6
7
8
9
10
11
12
13

Then later in your login package, you can retrieve this value and use it:

package login
+import (
+    "errors"
+    "service"
+)
+
+func Login(currentCount uint64) error {
+    ormService := service.DI().ORMEngine()
+    allowed, found := service.DI().Setting().GetUint64(ormService, "user.login.threshold")
+    if found && currentCount> allowed{
+        return errors.New("too many login attempt")
+    }  
+    return nil  
+}
+
1
2
3
4
5
6
7
8
9
10
11
12
13
14
Last Updated: 3/10/2022, 8:38:05 AM
Contributors: h-khodadadeh, Saman Shahroudi
+ + + diff --git a/guide/services/slack.html b/guide/services/slack.html new file mode 100644 index 00000000..f8ed53f4 --- /dev/null +++ b/guide/services/slack.html @@ -0,0 +1,23 @@ + + + + + + + Slack | Hitrix - golang framework + + + + +

Slack

Gives you ability to send Slack messages using slack bot. You can define multiple Slack bots. Also, it's used to send messages if you use our ErrorLogger service using errors bot. The config that needs to be set in hitrix.yaml is:

slack:
+    error_channel: "test" #optional, used by ErrorLogger
+    dev_panel_url: "test" #optional, used by ErrorLogger
+    bot_tokens:
+      errors: "your token"
+      another_bot: "second token"
+
1
2
3
4
5
6

NOTE: bot_tokens.errors is a must-have when using ErrorLogger service.

Register the service into your main.go file:

registry.ServiceProviderSlack()
+
1

Access the service:

service.DI().Slack()
+
1
+ + + diff --git a/guide/services/sms.html b/guide/services/sms.html new file mode 100644 index 00000000..4f885800 --- /dev/null +++ b/guide/services/sms.html @@ -0,0 +1,48 @@ + + + + + + + SMS Service | Hitrix - golang framework + + + + +

SMS Service

This service is capable of sending sms messages using different providers . We supports next providers twilio sinch kavenegar link mobility mobica

Register the service into your main.go file:

registry.ServiceProviderSMS(sms.NewTwilioProvider, sms.NewSinchProvider)
+
1

Access the service:

service.DI().SMS()
+
1
Dependencies :

ClockService, ORMConfigService

Every request is saved in SmsTrackerEntity. So you will be able to track every sms

We support defining primary and secondary providers. If primary fails we try to send a message with the secondary provider.

We can set default providers and providers per message as well

Default providers are set at ServiceProviderSMS() Providers per message are set at SendMessage()

The method SendMessage used to send simple message

type Message struct {
+	Text     string
+	Number   string
+	Provider *Provider
+}
+
1
2
3
4
5
configs
sms:
+  sandbox_mode: false
+  twilio:
+    sid: ENV[SMS_TWILIO_SID]
+    token: ENV[SMS_TWILIO_TOKEN]
+    from_number: ENV[SMS_TWILIO_FROM_NUMBER]
+  kavenegar:
+    api_key: ENV[SMS_KAVENEGAR_API_KEY]
+    sender: ENV[SMS_KAVENEGAR_SENDER]
+  sinch:
+    app_id: ENV[SMS_SINCH_APP_ID]
+    app_secret: ENV[SMS_SINCH_APP_SECRET]
+    msg_url: ENV[SMS_SINCH_MSG_URL]
+    from_number: ENV[SMS_SINCH_FROM_NUMBER]
+  mobica:
+    email: ENV[SMS_MOBICA_EMAIL]
+    password: ENV[SMS_MOBICA_PASSWORD]
+    route: ENV[SMS_MOBICA_ROUTE]
+    from: ENV[SMS_MOBICA_FROM]
+    endpoint: ENV[SMS_MOBICA_ENDPOINT]
+  link_mobility:
+    service: ENV[SMS_LINK_MOBILITY_SERVICE]
+    key: ENV[SMS_LINK_MOBILITY_KEY]
+    secret: ENV[SMS_LINK_MOBILITY_SECRET]
+    endpoint: ENV[SMS_LINK_MOBILITY_ENDPOINT]
+    shortcode: ENV[SMS_LINK_MOBILITY_SHORTCODE]
+
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26

If you set sandbox_mode=true we won't send real sms to the customer

+ + + diff --git a/guide/services/stripe.html b/guide/services/stripe.html new file mode 100644 index 00000000..50fb019b --- /dev/null +++ b/guide/services/stripe.html @@ -0,0 +1,21 @@ + + + + + + + Stripe | Hitrix - golang framework + + + + + + + + diff --git a/guide/services/template.html b/guide/services/template.html new file mode 100644 index 00000000..9e04e03c --- /dev/null +++ b/guide/services/template.html @@ -0,0 +1,17 @@ + + + + + + + Template | Hitrix - golang framework + + + + + + + + diff --git a/guide/services/uploader.html b/guide/services/uploader.html new file mode 100644 index 00000000..3f9691ed --- /dev/null +++ b/guide/services/uploader.html @@ -0,0 +1,29 @@ + + + + + + + Uploader | Hitrix - golang framework + + + + +

Uploader

This service uses TUS protocol to enable fast resumable and multi-part upload of big files. It provides an easy interface for plug-in whatever data store and locker you want to implement. Currently, Amazon S3 data store and Redis locker are implemented. For Amazon data store to work, you need to register Amazon S3 service before this one, also for Redis locker to work, you need to register orm service background before this one.

Register the service into your main.go file:

registry.ServiceProviderUploader(tusd.Config{...}, datastore.GetAmazonS3Store, locker.GetRedisLocker)
+
1

Access the service:

service.DI().Uploader()
+
1

Hitrix also provides REST uploader controller which you can register all handler methods in your router:

var uploaderController *hitrixController.UploaderController
+uploaderGroup := ginEngine.Group("/files/")
+uploaderGroup.Use(middleware.AuthorizeWithHeaderStrict())
+{
+	uploaderGroup.POST("", uploaderController.PostFileAction)
+	uploaderGroup.HEAD(":id", uploaderController.HeadFile)
+	uploaderGroup.PATCH(":id", uploaderController.PatchFile)
+	uploaderGroup.GET(":id", uploaderController.GetFileAction)
+	uploaderGroup.DELETE(":id", uploaderController.DeleteFile)
+}
+
1
2
3
4
5
6
7
8
9
10

Also you need bucket name in config:

uploader:
+  bucket: media
+
1
2
+ + + diff --git a/guide/services/websocket.html b/guide/services/websocket.html new file mode 100644 index 00000000..b55065b4 --- /dev/null +++ b/guide/services/websocket.html @@ -0,0 +1,59 @@ + + + + + + + WebSocket | Hitrix - golang framework + + + + +

WebSocket

This service add support of websockets. It manage the connections and provide you easy way to read and write messages

Register the service into your main.go file:

registry.ServiceProviderSocketRegistry(registerHandler, unregisterHandler func(s *socket.Socket))
+
1

Access the service:

service.DI().SocketRegistry()
+
1

To be able to handle new connections you should create your own route and create a handler for it. Your handler should looks like that:

type WebsocketController struct {
+}
+
+func (controller *WebsocketController) InitConnection(c *gin.Context) {
+	ws, err := upgrader.Upgrade(c.Writer, c.Request, nil)
+	if err != nil {
+		panic(err)
+	}
+
+	socketRegistryService, has := service.DI().SocketRegistry()
+	if !has {
+		panic("Socket Registry is not registered")
+	}
+
+	errorLoggerService := service.DI().ErrorLogger()
+
+	connection := &socket.Connection{Send: make(chan []byte, 256), Ws: ws}
+	socketHolder := &socket.Socket{
+		ErrorLogger: errorLoggerService,
+		Connection:  connection,
+		ID:          "unique connection hash based on userID, deviceID and timestamp",
+		Namespace:   model.DefaultNamespace,
+	}
+
+	socketRegistryService.Register <- socketHolder
+
+	go socketHolder.WritePump()
+	go socketHolder.ReadPump(socketRegistryService, func(rawData []byte) {
+		s, _ := socketRegistryService.Sockets.Load(socketHolder.ID)
+		
+        dto := &DTOMessage{}
+        err = json.Unmarshal(rawData, dto)
+        if err != nil {
+            errorLoggerService.LogError(err)
+            retrun
+        }
+        //handle business logic here
+        s.(*socket.Socket).Emit(dto)
+	})
+}
+
+
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41

This handler initializes the new coming connections and have 2 go routines - one for writing messages and the second one for reading messages If you want to send message you should use socketRegistryService.Emit

If you want to read coming messages you should do it in the function we are passing as second parameter of ReadPump method

If you want to select certain connection you can do it by the ID and this method

s, err := socketRegistryService.Sockets.Load(ID)
+
1

Also websocket service provide you hooks for registering new connections and for unregistering already existing connections. You can define those handlers when you register the service based on namespace of socket.

+ + + diff --git a/index.html b/index.html new file mode 100644 index 00000000..8a3143c4 --- /dev/null +++ b/index.html @@ -0,0 +1,177 @@ + + + + + + + Introduction | Hitrix - golang framework + + + + +

Introduction

Hitrix is a web framework written in Go (Golang) and support Graphql and REST api. It is based on top of Gqlgenopen in new window and Gin Frameworkopen in new window

Why to choose Hitrix?

Hitrix is combination between high performance and speed of development. There are many build-in features and tools that save development time. Also this framework helps you from the day zero to start creating new features for your project. You don't need to spend time thinking about error log, db layer, caching, DI, structure, background jobs and so on. The only thing you need to do is to use Hitrix and deliver fast to the business

Built-in features:

Installation

go get -u github.com/coretrix/hitrix
+
1

Quick start

  1. Run next command into your project's main folder and the graph structure will be created
go run github.com/99designs/gqlgen init
+
1
  1. Create cmd folder into your project and file called main.go

Put the next code into the file:

package main
+
+import (
+	"github.com/coretrix/hitrix"
+	"github.com/gin-gonic/gin"
+	
+	"your-project/graph" //path you your graph
+	"your-project/graph/generated" //path you your graph generated folder
+)
+
+func main() {
+	s, deferFunc := hitrix.New(
+		"app-name", "your secret",
+	).Build()
+    defer deferFunc()
+	s.RunServer(9999, generated.NewExecutableSchema(generated.Config{Resolvers: &graph.Resolver{}}),  func(ginEngine *gin.Engine) {
+		//here you can register all your middlewares
+	})
+}
+
+
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20

You are able register DI services in your main.go file in that way:

package main
+
+import (
+	"github.com/coretrix/hitrix"
+	"github.com/coretrix/hitrix/service/registry"
+	"your-project/entity"
+	"your-project/graph"
+	"your-project/graph/generated"
+	"github.com/coretrix/hitrix/pkg/middleware"
+	"github.com/gin-gonic/gin"
+	"github.com/coretrix/hitrix/service/component/app"
+)
+
+func main() {
+	s, deferFunc := hitrix.New(
+		"app-name", "your secret",
+	).RegisterDIGlobalService(
+		registry.ServiceProviderErrorLogger(), //register redis error logger
+		registry.ServiceProviderConfigDirectory("../config"), //register config service. As param you should point to the folder of your config file
+		registry.ServiceProviderOrmRegistry(entity.Init), //register our ORM and pass function where we set some configurations 
+		registry.ServiceProviderOrmEngine(), //register our ORM engine for background processes
+		registry.ServiceProviderJWT(), //register JWT DI service
+		registry.ServiceProviderPassword(password.NewSimpleManager), //register pasword DI service
+	).RegisterDIRequestService(
+		registry.ServiceProviderOrmEngineForContext(false), //register our ORM engine per context used in foreground processes 
+	).RegisterRedisPools(&app.RedisPools{Persistent: "your pool here"}).
+    Build()
+    defer deferFunc()
+
+	s.RunServer(9999, generated.NewExecutableSchema(generated.Config{Resolvers: &graph.Resolver{}}),  func(ginEngine *gin.Engine) {
+		middleware.Cors(ginEngine)
+	})
+}
+
+
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34

Now I will explain the main.go file line by line

  1. We create New instance of Hitrix and pass app name and a secret that is used from our security services

  2. We register some DI services

    2.1. Global DI service for error logger. It will be used for error handler as well in case of panic If you register Slack error logger also it will send messages to slack channel

    2.2. Global DI service that loads config file

    2.3. Global DI service that initialize our ORM registry

    2.4. Global DI ORM engine used in background processes

    2.6. Global DI JWT service used by dev panel

    2.7. Global DI Password service used by dev-panel

    2.8. Request DI ORM engine used in foreground processes

  3. We register redis pools. Those pools are used by different services as authentication service, dev panel and so on

  4. We run the server on port 9999, pass graphql resolver and as third param we pass all middlewares we need.
    As you can see in our example we register only Cors middleware

Register Dev Panelopen in new window

If you want to use our dev panel and to be able to manage alters, error log, redis monitoring, redis stream and so on you should execute next steps:

Create DevPanelUserEntity

package entity
+
+import (
+	"github.com/latolukasz/beeorm"
+)
+
+type DevPanelUserEntity struct {
+	beeorm.ORM   `orm:"table=admin_users;redisCache"`
+	ID        uint64
+	Email     string `orm:"unique=Email"`
+	Password  string
+
+	UserEmailIndex *beeorm.CachedQuery `queryOne:":Email = ?"`
+}
+
+func (e *DevPanelUserEntity) GetUsername() string {
+	return e.Email
+}
+
+func (e *DevPanelUserEntity) GetPassword() string {
+	return e.Password
+}
+
+
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23

After that you should register it to the entity.Init function

package entity
+
+import "github.com/latolukasz/beeorm"
+
+func Init(registry *beeorm.Registry) {
+	registry.RegisterEntity(
+		&DevPanelUserEntity{},
+	)
+}
+
+
1
2
3
4
5
6
7
8
9
10

Please execute this alter into your database


+create table dev_panel_users
+(
+    ID       bigint unsigned auto_increment primary key,
+    Email    varchar(255) null,
+    Password varchar(255) null,
+    constraint Email unique (Email)
+) charset = utf8mb4;
+
+
+
1
2
3
4
5
6
7
8
9
10

After that you can make GET request to http://localhost:9999/dev/create-dev-panel-user/?username=contact@coretrix.com&password=coretrix This will generate sql query that should be executed into your database to create new user for dev panel

Register dev panel when you make new instance of hitrix framework in your main.go file

s, deferFunc := hitrix.New(
+		"app-name", "your secret",
+	).RegisterDIGlobalService(
+		registry.ServiceProviderErrorLogger(), //register redis error logger
+		//...
+	).
+    RegisterDevPanel(&entity.DevPanelUserEntity{}, middleware.Router). //register our dev-panel and pass the entity where we save admin users, the router and the third param is used for the redis stream pool if its used
+    Build()
+
1
2
3
4
5
6
7
8

DI services

We have two types of DI services - Global and Request services Global services are singletons created once for the whole application Request services are singletons created once per request

Calling DI services

If you want to access the registered DI services you can do in in that way:

service.DI().App() //access the app
+service.DI().Config() //access config
+service.DI().OrmEngine() //access global orm engine
+service.DI().OrmEngineForContext() //access reqeust orm engine
+service.DI().JWT() //access JWT
+service.DI().Password() //access JWT
+//...and so on
+
1
2
3
4
5
6
7

Register new DI global service


+func ServiceProviderMyService() *ServiceProvider {
+	return &ServiceProvider{
+		Name:   "my_service",
+		Build: func(ctn di.Container) (interface{}, error) {
+			return &yourService{}, nil
+		},
+	}
+}
+
+
1
2
3
4
5
6
7
8
9
10

And you have to register ServiceProviderMyService() in your main.go file

Now you can access this service in your code using:

import (
+    "github.com/coretrix/hitrix"
+)
+
+func SomeResolver(ctx context.Context) {
+
+    service.HasService("my_service") // return true
+    
+    // return error if Build function returned error
+    myService, has, err := service.GetServiceSafe("my_service") 
+    // will panic if Build function returns error
+    myService, has := service.GetServiceOptional("my_service") 
+    // will panic if service is not registered or Build function returned errors
+    myService := service.GetServiceRequired("my_service") 
+
+    // if you registered service with field "Global" set to false (request service)
+
+    myContextService, has, err := hitrix.GetServiceForRequestSafe(ctx).Get("my_service_request")
+    myContextService, has := hitrix.GetServiceForRequestOptional(ctx).Get("my_service_request") 
+    myContextService := hitrix.GetServiceForRequestRequired(ctx).Get("my_service_request") 
+}
+
+
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22

It's a good practice to define one object to return all available services:

package my_package
+import (
+    "github.com/coretrix/hitrix"
+)
+
+
+
+func MyService() MyService {
+    return service.GetServiceRequired("service_key").(*MyService)
+}
+
+
+
1
2
3
4
5
6
7
8
9
10
11
12

Setting mode

APP_MODE environment variable

You can define hitrix mode using special environment variable "APP_MODE".

Hitrix provides by default four modes:

  • hitrix.ModeLocal - local
    • should be used on local development machine (developer laptop)
    • errors and stack trace is printed directly to system console
    • log level is set to Debug level
    • log is formatted using human friendly console text formatter
    • Gin Framework is running in GinDebug mode
  • hitrix.ModeTest - test
    • should be used when you run your application tests
  • hitrix.ModeDev - dev
    • should be used on your dev server
  • hitrix.ModeDemo - demo
    • should be used on your demo server
  • hitrix.ModeProd - prod
    • errors and stack trace is printed only using Log
    • log level is set to Warn level
    • log is formatted using json formatter

Mode is just a string. You can define any name you want. Remember that every mode that you create follows hitrix.ModeProd rules explained above.

In code you can easly check current mode using one of these methods:

service.DI().App().Mode()
+service.DI().App().IsInLocalMode()
+service.DI().App().IsInProdMode()
+service.DI().App().IsInMode("my_mode")
+
1
2
3
4

Environment variables

APP_FOLDER environment variable

There are another important environment variable called environment You can set path to your app folder for your demo, prod or any other environment

+ + + diff --git a/logo-favicon-90x90.png b/logo-favicon-90x90.png new file mode 100644 index 00000000..0a30ff43 Binary files /dev/null and b/logo-favicon-90x90.png differ diff --git a/logo-favicon.png b/logo-favicon.png new file mode 100644 index 00000000..0a30ff43 Binary files /dev/null and b/logo-favicon.png differ diff --git a/roadmap/index.html b/roadmap/index.html new file mode 100644 index 00000000..79418201 --- /dev/null +++ b/roadmap/index.html @@ -0,0 +1,15 @@ + + + + + + + Roadmap | Hitrix - golang framework + + + + + + + + diff --git a/rules/index.html b/rules/index.html new file mode 100644 index 00000000..4b6bdf69 --- /dev/null +++ b/rules/index.html @@ -0,0 +1,98 @@ + + + + + + + Documentation | Hitrix - golang framework + + + + +

Documentation

Localhost tools

force-alters

If you run your binary with argument --force-alters the program will check for DB and RediSearch alters and it will execute them(only in local mode).

make web-api param=--force-alters

Domains

Naming convention for our backend domains are:

[binary name].[env].[project].[domain]

For prod we are skipping [env] For example for our binary called web-api the domain will be web-api.dev.lys.domain.com or web-api.demo.lys.domain.com

Naming convention for our frontend domains are:

[binary name without suffix api].[env].[project].[domain]

For prod we are skipping [env] For example for web.dev.lys.domain.com or web.demo.lys.domain.com

Naming convention for our REST endpoints domains are:

[noun]/[noun].../[action]/?params

Always use - as separator. Endpoint name is not tied to package name.

POST - CREATE entity actions, SEARCH entity actions

PATCH - UPDATE entity actions

GET - GET entity actions

DELETE - DELETE entity actions

PUT - not used

Examples:

GET /profile/payment-info/cards/get/ - gets cards information

PATCH /profile/payment-info/cards/update/ - updates card information

POST /profile/payment-info/cards/create/ - creates card

DELETE /profile/payment-info/cards/delete/ - deletes card

Crons (scripts)

What is the differences between single-instance-cron and multi-instance-cron

  • single-instance-cron is for crons that cannot scale. Imagine you read something from db every 10min and you update something. If you have more than one instance it's gonna conflict. That's why we gonna create only one pod for it
  • multi-instance-cron for crons that can scale. Imagine you read from queue. You can have as much consumers as you want. That`s why we gonna have more pod instances for it

Naming conventions and rules

  1. If you have variable that contains one or more entities you should add a suffix to the variable Entity/Entities For example productEntity or productEntities
  2. Try to avoid append() Example:
package main
+
+func main()  {
+	attributeEntities := make([]*struct{ID int}, 10)
+	var someMap = make([]int, len(attributeEntities))
+	for i, attributeEntity := range attributeEntities {
+		someMap[i] = attributeEntity.ID //here we avoid map because we set the len
+	}
+}
+
+
1
2
3
4
5
6
7
8
9
10
  1. If you implement communication with external API or something general that can be valid for every other project like Authentication for example, you can implement it as service in Hitrix.
  2. When declaring a variable, use inferred variable declaration syntax:
package main
+
+func main() {
+    //Acceptable
+    _ = &OrderEntity{}
+
+    //Not acceptable
+    var orderEntity OrderEntity
+    someFunc(&orderEntity)
+}
+
+type OrderEntity struct{
+}
+func someFunc(entity *OrderEntity)  {
+ 
+}
+
+
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
  1. For declaring slices, please use make to declare your variable:
package main
+
+func main() {
+    //Acceptable
+    _ = make([]*OrderEntity, 0)
+
+    //Not acceptable
+    var _ []*OrderEntity
+}
+type OrderEntity struct{
+}
+
1
2
3
4
5
6
7
8
9
10
11
  1. When instantiating graphql objects, use methods that are defined in populate package. If there is no method for your object, please declare one for your object.
  2. Don't use time.Now() . Use ioc.GetClockService().Now() instead. If you need pointer, use ioc.GetClockService().NowPointer().
  3. All entity files should have _entity.go suffix, the entity itself should end with Entity.
  4. In yaml config files we should set env vars only for values that going to be different in different environments(dev/demo/prod) If they are the same we should not use env var, but we can set the value into the yaml file
  5. Custom redis indexes can be re-indexed using dev panel but dirty queues needs extra effort from developer side.
  • You need to extend the slice into DevPanelController->GetActionListAction slice dirty This step will add new menu in dev panel dashboard.
  • Be sure that you added GET url for in into your router For example ginEngine.GET("/dev/mark-as-dirty-price-changed/", devPanel.GetMarkAsDirtyPriceChanged)
  • Your action should look like that
package controller
+
+type DevPanelController struct{
+}
+func (controller *DevPanelController) GetMarkAsDirtyPriceChanged(c *gin.Context) {
+	ormService := service.DI().OrmEngineForContext(c.Request.Context())
+
+	producer := producers.PriceChangedDirtyAllProducer{}
+
+	err := producer.Produce(ormService)
+
+	if err != nil {
+		response.ErrorResponseGlobal(c, err, nil)
+		return
+	}
+
+	c.JSON(200, gin.H{})
+}
+
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18

And your processor should look like that:

package model 
+type PriceChangedDirtyAllProducer struct {
+}
+
+func (p *PriceChangedDirtyAllProducer) Produce(ormService *beeorm.Engine) error {
+	variantEntity := entity.VariantEntity{}
+	where := beeorm.NewWhere("1 ORDER BY ID ASC")
+	pager := &beeorm.Pager{CurrentPage: 1, PageSize: 1000}
+	hasMoreToIndex := true
+	for hasMoreToIndex {
+		ids := ormService.SearchIDs(where, pager, &variantEntity)
+
+		if len(ids) == 0 {
+			break
+		}
+
+		if len(ids) < pager.PageSize {
+			hasMoreToIndex = false
+		}
+
+		ormService.MarkDirty(&entity.VariantEntity{}, redisstream.StreamOrmDirtyPriceChanged, ids...)
+
+		pager.IncrementPage()
+	}
+
+	return nil
+}
+
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27

Teamwork rules

We have defined rules that backend and frontend developers should follow to keep good communication and deliver the feature like a one team

  1. Read and discuss the epic with the business person
  2. Backend and frontend developers together should go through design and define endpoints and request/response structure. Whenever they are ready they should post it as a comment into the backend ticket to be visible that both agreed on it
  3. Start implementing the feature
  4. Before completing the task they should do following things:
    • When backend developer is done and all tests pass he should test every endpoint by himself using swagger on dev environment before complete his ticket
    • When frontend developer is done he should deploy on dev and test the feature very well before complete his ticket
  5. When everything works on dev frontend developer is responsible to talk to backend developer and together to deploy on demo and go through the flow and verify if it works
  6. Mark the task as completed and inform the business person.
+ + +