From 0224a8a4dca77f135e97ab30aafd0e49964414de Mon Sep 17 00:00:00 2001 From: alimy Date: Sun, 12 Jun 2022 10:03:34 +0800 Subject: [PATCH 01/35] support visite api docs in embed docs --- README.md | 78 +- docs/assets/rapidoc-min.js | 3776 ++++++++++++++++++++++++++++++++ docs/assets/rapidoc-min.js.map | 1 + docs/embed.go | 17 + docs/index.html | 19 + docs/openapi.json | 334 +++ internal/routers/docs.go | 13 + internal/routers/docs_embed.go | 14 + internal/routers/router.go | 3 +- 9 files changed, 4221 insertions(+), 34 deletions(-) create mode 100644 docs/assets/rapidoc-min.js create mode 100644 docs/assets/rapidoc-min.js.map create mode 100644 docs/embed.go create mode 100644 docs/index.html create mode 100644 docs/openapi.json create mode 100644 internal/routers/docs.go create mode 100644 internal/routers/docs_embed.go diff --git a/README.md b/README.md index 6abdee00..fffceee5 100644 --- a/README.md +++ b/README.md @@ -111,23 +111,6 @@ PaoPao主要由以下优秀的开源项目/工具构建 ``` 提示: 如果需要内嵌web前端ui,请先构建web前端(建议设置web/.env为VITE_HOST="")。 - 5. docker构建 - ```sh - # 默认参数构建, 默认内嵌web ui并设置api host为空 - %> docker build -t your/paopao-ce:tag . - - # 内嵌web ui并且自定义API host参数 - %> docker build -t your/paopao-ce:tag --build-arg API_HOST=http://paopao.info . - - # 内嵌web ui并且使用本地web/.env中的API host - %> docker build -t your/paopao-ce:tag --build-arg USE_API_HOST=no . - - # 内嵌web ui并且使用本地编译的web/dist构建 - %> docker build -t your/paopao-ce:tag --build-arg USE_DIST=yes . - - # 只编译api server - %> docker build -t your/paopao-ce:tag --build-arg EMBED_UI=no . - ``` #### 前端 @@ -147,20 +130,6 @@ PaoPao主要由以下优秀的开源项目/工具构建 build完成后,可以在dist目录获取编译产出,配置nginx指向至该目录即可 -3. 使用Docker构建 - ```sh - %> cd web - - # 默认参数构建 - %> docker build -t your/paopao-ce:web . - - # 自定义API host 参数构建 - %> docker build -t your/paopao-ce:web --build-arg API_HOST=http://paopao.info . - - # 使用本地编译的dist构建 - %> docker build -t your/paopao-ce:web --build-arg USE_DIST=yes . - ``` - #### 桌面端 1. 进入前端目录 `web`,编辑 `.env` 文件中后端服务地址,下载依赖包 @@ -184,11 +153,48 @@ PaoPao主要由以下优秀的开源项目/工具构建 桌面端是使用[Rust](https://www.rust-lang.org/) + [tauri](https://github.com/tauri-apps/tauri)编写 的,需要安装tauri的依赖,具体参考[https://tauri.studio/v1/guides/getting-started/prerequisites](https://tauri.studio/v1/guides/getting-started/prerequisites). -### docker-compose 运行 + +### 使用Docker构建、运行 + * 后端: + ```sh + # 默认参数构建, 默认内嵌web ui并设置api host为空 + %> docker build -t your/paopao-ce:tag . + + # 内嵌web ui并且自定义API host参数 + %> docker build -t your/paopao-ce:tag --build-arg API_HOST=http://paopao.info . + + # 内嵌web ui并且使用本地web/.env中的API host + %> docker build -t your/paopao-ce:tag --build-arg USE_API_HOST=no . + + # 内嵌web ui并且使用本地编译的web/dist构建 + %> docker build -t your/paopao-ce:tag --build-arg USE_DIST=yes . + + # 只编译api server + %> docker build -t your/paopao-ce:tag --build-arg EMBED_UI=no . + + # 运行 + %> docker run -p 8008:8008 -v ${PWD}/config.yaml.sample:/app/paopao-ce/config.yaml your/paopao-ce:tag + ``` + + * 前端: + ```sh + %> cd web + + # 默认参数构建 + %> docker build -t your/paopao-ce:web . + + # 自定义API host 参数构建 + %> docker build -t your/paopao-ce:web --build-arg API_HOST=http://paopao.info . + + # 使用本地编译的dist构建 + %> docker build -t your/paopao-ce:web --build-arg USE_DIST=yes . + ``` + +### 使用 docker-compose 运行 ```sh %> git clone https://github.com/rocboss/paopao-ce.git %> docker compose up --build -# visit http://localhost:8008 +# visit http://127.0.0.1:8008 ``` 默认是使用config.yaml.sample的配置,如果需要自定义配置,请拷贝默认配置文件(比如config.yaml),修改后再同步配置到docker-compose.yaml如下: ``` @@ -212,6 +218,12 @@ PaoPao主要由以下优秀的开源项目/工具构建 .... ``` +### API 文档 +构建时将 `docs` 添加到TAGS中: +```sh +%> make run TAGS='docs' +# visit http://127.0.0.1:8008/docs +``` ### 其他说明 diff --git a/docs/assets/rapidoc-min.js b/docs/assets/rapidoc-min.js new file mode 100644 index 00000000..d370c5c2 --- /dev/null +++ b/docs/assets/rapidoc-min.js @@ -0,0 +1,3776 @@ +/*! RapiDoc 9.3.2 | Author - Mrinmoy Majumdar | License information can be found in rapidoc-min.js.LICENSE.txt */ +(()=>{var e,t,r={448:(e,t,r)=>{"use strict";const n=window.ShadowRoot&&(void 0===window.ShadyCSS||window.ShadyCSS.nativeShadow)&&"adoptedStyleSheets"in Document.prototype&&"replace"in CSSStyleSheet.prototype,a=Symbol(),o=new Map;class i{constructor(e,t){if(this._$cssResult$=!0,t!==a)throw Error("CSSResult is not constructable. Use `unsafeCSS` or `css` instead.");this.cssText=e}get styleSheet(){let e=o.get(this.cssText);return n&&void 0===e&&(o.set(this.cssText,e=new CSSStyleSheet),e.replaceSync(this.cssText)),e}toString(){return this.cssText}}const s=e=>new i("string"==typeof e?e:e+"",a),l=(e,...t)=>{const r=1===e.length?e[0]:t.reduce(((t,r,n)=>t+(e=>{if(!0===e._$cssResult$)return e.cssText;if("number"==typeof e)return e;throw Error("Value passed to 'css' function must be a 'css' function result: "+e+". Use 'unsafeCSS' to pass non-literal values, but take care to ensure page security.")})(r)+e[n+1]),e[0]);return new i(r,a)},c=n?e=>e:e=>e instanceof CSSStyleSheet?(e=>{let t="";for(const r of e.cssRules)t+=r.cssText;return s(t)})(e):e;var p;const u=window.trustedTypes,d=u?u.emptyScript:"",h=window.reactiveElementPolyfillSupport,f={toAttribute(e,t){switch(t){case Boolean:e=e?d:null;break;case Object:case Array:e=null==e?e:JSON.stringify(e)}return e},fromAttribute(e,t){let r=e;switch(t){case Boolean:r=null!==e;break;case Number:r=null===e?null:Number(e);break;case Object:case Array:try{r=JSON.parse(e)}catch(e){r=null}}return r}},m=(e,t)=>t!==e&&(t==t||e==e),y={attribute:!0,type:String,converter:f,reflect:!1,hasChanged:m};class g extends HTMLElement{constructor(){super(),this._$Et=new Map,this.isUpdatePending=!1,this.hasUpdated=!1,this._$Ei=null,this.o()}static addInitializer(e){var t;null!==(t=this.l)&&void 0!==t||(this.l=[]),this.l.push(e)}static get observedAttributes(){this.finalize();const e=[];return this.elementProperties.forEach(((t,r)=>{const n=this._$Eh(r,t);void 0!==n&&(this._$Eu.set(n,r),e.push(n))})),e}static createProperty(e,t=y){if(t.state&&(t.attribute=!1),this.finalize(),this.elementProperties.set(e,t),!t.noAccessor&&!this.prototype.hasOwnProperty(e)){const r="symbol"==typeof e?Symbol():"__"+e,n=this.getPropertyDescriptor(e,r,t);void 0!==n&&Object.defineProperty(this.prototype,e,n)}}static getPropertyDescriptor(e,t,r){return{get(){return this[t]},set(n){const a=this[e];this[t]=n,this.requestUpdate(e,a,r)},configurable:!0,enumerable:!0}}static getPropertyOptions(e){return this.elementProperties.get(e)||y}static finalize(){if(this.hasOwnProperty("finalized"))return!1;this.finalized=!0;const e=Object.getPrototypeOf(this);if(e.finalize(),this.elementProperties=new Map(e.elementProperties),this._$Eu=new Map,this.hasOwnProperty("properties")){const e=this.properties,t=[...Object.getOwnPropertyNames(e),...Object.getOwnPropertySymbols(e)];for(const r of t)this.createProperty(r,e[r])}return this.elementStyles=this.finalizeStyles(this.styles),!0}static finalizeStyles(e){const t=[];if(Array.isArray(e)){const r=new Set(e.flat(1/0).reverse());for(const e of r)t.unshift(c(e))}else void 0!==e&&t.push(c(e));return t}static _$Eh(e,t){const r=t.attribute;return!1===r?void 0:"string"==typeof r?r:"string"==typeof e?e.toLowerCase():void 0}o(){var e;this._$Ep=new Promise((e=>this.enableUpdating=e)),this._$AL=new Map,this._$Em(),this.requestUpdate(),null===(e=this.constructor.l)||void 0===e||e.forEach((e=>e(this)))}addController(e){var t,r;(null!==(t=this._$Eg)&&void 0!==t?t:this._$Eg=[]).push(e),void 0!==this.renderRoot&&this.isConnected&&(null===(r=e.hostConnected)||void 0===r||r.call(e))}removeController(e){var t;null===(t=this._$Eg)||void 0===t||t.splice(this._$Eg.indexOf(e)>>>0,1)}_$Em(){this.constructor.elementProperties.forEach(((e,t)=>{this.hasOwnProperty(t)&&(this._$Et.set(t,this[t]),delete this[t])}))}createRenderRoot(){var e;const t=null!==(e=this.shadowRoot)&&void 0!==e?e:this.attachShadow(this.constructor.shadowRootOptions);return((e,t)=>{n?e.adoptedStyleSheets=t.map((e=>e instanceof CSSStyleSheet?e:e.styleSheet)):t.forEach((t=>{const r=document.createElement("style"),n=window.litNonce;void 0!==n&&r.setAttribute("nonce",n),r.textContent=t.cssText,e.appendChild(r)}))})(t,this.constructor.elementStyles),t}connectedCallback(){var e;void 0===this.renderRoot&&(this.renderRoot=this.createRenderRoot()),this.enableUpdating(!0),null===(e=this._$Eg)||void 0===e||e.forEach((e=>{var t;return null===(t=e.hostConnected)||void 0===t?void 0:t.call(e)}))}enableUpdating(e){}disconnectedCallback(){var e;null===(e=this._$Eg)||void 0===e||e.forEach((e=>{var t;return null===(t=e.hostDisconnected)||void 0===t?void 0:t.call(e)}))}attributeChangedCallback(e,t,r){this._$AK(e,r)}_$ES(e,t,r=y){var n,a;const o=this.constructor._$Eh(e,r);if(void 0!==o&&!0===r.reflect){const i=(null!==(a=null===(n=r.converter)||void 0===n?void 0:n.toAttribute)&&void 0!==a?a:f.toAttribute)(t,r.type);this._$Ei=e,null==i?this.removeAttribute(o):this.setAttribute(o,i),this._$Ei=null}}_$AK(e,t){var r,n,a;const o=this.constructor,i=o._$Eu.get(e);if(void 0!==i&&this._$Ei!==i){const e=o.getPropertyOptions(i),s=e.converter,l=null!==(a=null!==(n=null===(r=s)||void 0===r?void 0:r.fromAttribute)&&void 0!==n?n:"function"==typeof s?s:null)&&void 0!==a?a:f.fromAttribute;this._$Ei=i,this[i]=l(t,e.type),this._$Ei=null}}requestUpdate(e,t,r){let n=!0;void 0!==e&&(((r=r||this.constructor.getPropertyOptions(e)).hasChanged||m)(this[e],t)?(this._$AL.has(e)||this._$AL.set(e,t),!0===r.reflect&&this._$Ei!==e&&(void 0===this._$EC&&(this._$EC=new Map),this._$EC.set(e,r))):n=!1),!this.isUpdatePending&&n&&(this._$Ep=this._$E_())}async _$E_(){this.isUpdatePending=!0;try{await this._$Ep}catch(e){Promise.reject(e)}const e=this.scheduleUpdate();return null!=e&&await e,!this.isUpdatePending}scheduleUpdate(){return this.performUpdate()}performUpdate(){var e;if(!this.isUpdatePending)return;this.hasUpdated,this._$Et&&(this._$Et.forEach(((e,t)=>this[t]=e)),this._$Et=void 0);let t=!1;const r=this._$AL;try{t=this.shouldUpdate(r),t?(this.willUpdate(r),null===(e=this._$Eg)||void 0===e||e.forEach((e=>{var t;return null===(t=e.hostUpdate)||void 0===t?void 0:t.call(e)})),this.update(r)):this._$EU()}catch(e){throw t=!1,this._$EU(),e}t&&this._$AE(r)}willUpdate(e){}_$AE(e){var t;null===(t=this._$Eg)||void 0===t||t.forEach((e=>{var t;return null===(t=e.hostUpdated)||void 0===t?void 0:t.call(e)})),this.hasUpdated||(this.hasUpdated=!0,this.firstUpdated(e)),this.updated(e)}_$EU(){this._$AL=new Map,this.isUpdatePending=!1}get updateComplete(){return this.getUpdateComplete()}getUpdateComplete(){return this._$Ep}shouldUpdate(e){return!0}update(e){void 0!==this._$EC&&(this._$EC.forEach(((e,t)=>this._$ES(t,this[t],e))),this._$EC=void 0),this._$EU()}updated(e){}firstUpdated(e){}}var v;g.finalized=!0,g.elementProperties=new Map,g.elementStyles=[],g.shadowRootOptions={mode:"open"},null==h||h({ReactiveElement:g}),(null!==(p=globalThis.reactiveElementVersions)&&void 0!==p?p:globalThis.reactiveElementVersions=[]).push("1.3.0");const b=globalThis.trustedTypes,x=b?b.createPolicy("lit-html",{createHTML:e=>e}):void 0,w=`lit$${(Math.random()+"").slice(9)}$`,$="?"+w,k=`<${$}>`,S=document,A=(e="")=>S.createComment(e),E=e=>null===e||"object"!=typeof e&&"function"!=typeof e,O=Array.isArray,T=e=>{var t;return O(e)||"function"==typeof(null===(t=e)||void 0===t?void 0:t[Symbol.iterator])},C=/<(?:(!--|\/[^a-zA-Z])|(\/?[a-zA-Z][^>\s]*)|(\/?$))/g,_=/-->/g,j=/>/g,I=/>|[ \n \r](?:([^\s"'>=/]+)([ \n \r]*=[ \n \r]*(?:[^ \n \r"'`<>=]|("|')|))|$)/g,P=/'/g,R=/"/g,L=/^(?:script|style|textarea|title)$/i,D=e=>(t,...r)=>({_$litType$:e,strings:t,values:r}),B=D(1),F=(D(2),Symbol.for("lit-noChange")),N=Symbol.for("lit-nothing"),z=new WeakMap,U=S.createTreeWalker(S,129,null,!1),q=(e,t)=>{const r=e.length-1,n=[];let a,o=2===t?"":"",i=C;for(let t=0;t"===l[0]?(i=null!=a?a:C,c=-1):void 0===l[1]?c=-2:(c=i.lastIndex-l[2].length,s=l[1],i=void 0===l[3]?I:'"'===l[3]?R:P):i===R||i===P?i=I:i===_||i===j?i=C:(i=I,a=void 0);const u=i===I&&e[t+1].startsWith("/>")?" ":"";o+=i===C?r+k:c>=0?(n.push(s),r.slice(0,c)+"$lit$"+r.slice(c)+w+u):r+w+(-2===c?(n.push(void 0),t):u)}const s=o+(e[r]||"")+(2===t?"":"");if(!Array.isArray(e)||!e.hasOwnProperty("raw"))throw Error("invalid template strings array");return[void 0!==x?x.createHTML(s):s,n]};class M{constructor({strings:e,_$litType$:t},r){let n;this.parts=[];let a=0,o=0;const i=e.length-1,s=this.parts,[l,c]=q(e,t);if(this.el=M.createElement(l,r),U.currentNode=this.el.content,2===t){const e=this.el.content,t=e.firstChild;t.remove(),e.append(...t.childNodes)}for(;null!==(n=U.nextNode())&&s.length0){n.textContent=b?b.emptyScript:"";for(let r=0;r2||""!==r[0]||""!==r[1]?(this._$AH=Array(r.length-1).fill(new String),this.strings=r):this._$AH=N}get tagName(){return this.element.tagName}get _$AU(){return this._$AM._$AU}_$AI(e,t=this,r,n){const a=this.strings;let o=!1;if(void 0===a)e=H(this,e,t,0),o=!E(e)||e!==this._$AH&&e!==F,o&&(this._$AH=e);else{const n=e;let i,s;for(e=a[0],i=0;i{var n,a;const o=null!==(n=null==r?void 0:r.renderBefore)&&void 0!==n?n:t;let i=o._$litPart$;if(void 0===i){const e=null!==(a=null==r?void 0:r.renderBefore)&&void 0!==a?a:null;o._$litPart$=i=new V(t.insertBefore(A(),e),e,void 0,null!=r?r:{})}return i._$AI(e),i})(t,this.renderRoot,this.renderOptions)}connectedCallback(){var e;super.connectedCallback(),null===(e=this._$Dt)||void 0===e||e.setConnected(!0)}disconnectedCallback(){var e;super.disconnectedCallback(),null===(e=this._$Dt)||void 0===e||e.setConnected(!1)}render(){return F}}ne.finalized=!0,ne._$litElement$=!0,null===(te=globalThis.litElementHydrateSupport)||void 0===te||te.call(globalThis,{LitElement:ne});const ae=globalThis.litElementPolyfillSupport;null==ae||ae({LitElement:ne});function oe(){return{baseUrl:null,breaks:!1,extensions:null,gfm:!0,headerIds:!0,headerPrefix:"",highlight:null,langPrefix:"language-",mangle:!0,pedantic:!1,renderer:null,sanitize:!1,sanitizer:null,silent:!1,smartLists:!1,smartypants:!1,tokenizer:null,walkTokens:null,xhtml:!1}}(null!==(re=globalThis.litElementVersions)&&void 0!==re?re:globalThis.litElementVersions=[]).push("3.2.0");let ie={baseUrl:null,breaks:!1,extensions:null,gfm:!0,headerIds:!0,headerPrefix:"",highlight:null,langPrefix:"language-",mangle:!0,pedantic:!1,renderer:null,sanitize:!1,sanitizer:null,silent:!1,smartLists:!1,smartypants:!1,tokenizer:null,walkTokens:null,xhtml:!1};const se=/[&<>"']/,le=/[&<>"']/g,ce=/[<>"']|&(?!#?\w+;)/,pe=/[<>"']|&(?!#?\w+;)/g,ue={"&":"&","<":"<",">":">",'"':""","'":"'"},de=e=>ue[e];function he(e,t){if(t){if(se.test(e))return e.replace(le,de)}else if(ce.test(e))return e.replace(pe,de);return e}const fe=/&(#(?:\d+)|(?:#x[0-9A-Fa-f]+)|(?:\w+));?/gi;function me(e){return e.replace(fe,((e,t)=>"colon"===(t=t.toLowerCase())?":":"#"===t.charAt(0)?"x"===t.charAt(1)?String.fromCharCode(parseInt(t.substring(2),16)):String.fromCharCode(+t.substring(1)):""))}const ye=/(^|[^\[])\^/g;function ge(e,t){e="string"==typeof e?e:e.source,t=t||"";const r={replace:(t,n)=>(n=(n=n.source||n).replace(ye,"$1"),e=e.replace(t,n),r),getRegex:()=>new RegExp(e,t)};return r}const ve=/[^\w:]/g,be=/^$|^[a-z][a-z0-9+.-]*:|^[?#]/i;function xe(e,t,r){if(e){let e;try{e=decodeURIComponent(me(r)).replace(ve,"").toLowerCase()}catch(e){return null}if(0===e.indexOf("javascript:")||0===e.indexOf("vbscript:")||0===e.indexOf("data:"))return null}t&&!be.test(r)&&(r=function(e,t){we[" "+e]||($e.test(e)?we[" "+e]=e+"/":we[" "+e]=Te(e,"/",!0));const r=-1===(e=we[" "+e]).indexOf(":");return"//"===t.substring(0,2)?r?t:e.replace(ke,"$1")+t:"/"===t.charAt(0)?r?t:e.replace(Se,"$1")+t:e+t}(t,r));try{r=encodeURI(r).replace(/%25/g,"%")}catch(e){return null}return r}const we={},$e=/^[^:]+:\/*[^/]*$/,ke=/^([^:]+:)[\s\S]*$/,Se=/^([^:]+:\/*[^/]*)[\s\S]*$/;const Ae={exec:function(){}};function Ee(e){let t,r,n=1;for(;n{let n=!1,a=t;for(;--a>=0&&"\\"===r[a];)n=!n;return n?"|":" |"})).split(/ \|/);let n=0;if(r[0].trim()||r.shift(),r.length>0&&!r[r.length-1].trim()&&r.pop(),r.length>t)r.splice(t);else for(;r.length1;)1&t&&(r+=e),t>>=1,e+=e;return r+e}function je(e,t,r,n){const a=t.href,o=t.title?he(t.title):null,i=e[1].replace(/\\([\[\]])/g,"$1");if("!"!==e[0].charAt(0)){n.state.inLink=!0;const e={type:"link",raw:r,href:a,title:o,text:i,tokens:n.inlineTokens(i,[])};return n.state.inLink=!1,e}return{type:"image",raw:r,href:a,title:o,text:he(i)}}class Ie{constructor(e){this.options=e||ie}space(e){const t=this.rules.block.newline.exec(e);if(t&&t[0].length>0)return{type:"space",raw:t[0]}}code(e){const t=this.rules.block.code.exec(e);if(t){const e=t[0].replace(/^ {1,4}/gm,"");return{type:"code",raw:t[0],codeBlockStyle:"indented",text:this.options.pedantic?e:Te(e,"\n")}}}fences(e){const t=this.rules.block.fences.exec(e);if(t){const e=t[0],r=function(e,t){const r=e.match(/^(\s+)(?:```)/);if(null===r)return t;const n=r[1];return t.split("\n").map((e=>{const t=e.match(/^\s+/);if(null===t)return e;const[r]=t;return r.length>=n.length?e.slice(n.length):e})).join("\n")}(e,t[3]||"");return{type:"code",raw:e,lang:t[2]?t[2].trim():t[2],text:r}}}heading(e){const t=this.rules.block.heading.exec(e);if(t){let e=t[2].trim();if(/#$/.test(e)){const t=Te(e,"#");this.options.pedantic?e=t.trim():t&&!/ $/.test(t)||(e=t.trim())}const r={type:"heading",raw:t[0],depth:t[1].length,text:e,tokens:[]};return this.lexer.inline(r.text,r.tokens),r}}hr(e){const t=this.rules.block.hr.exec(e);if(t)return{type:"hr",raw:t[0]}}blockquote(e){const t=this.rules.block.blockquote.exec(e);if(t){const e=t[0].replace(/^ *>[ \t]?/gm,"");return{type:"blockquote",raw:t[0],tokens:this.lexer.blockTokens(e,[]),text:e}}}list(e){let t=this.rules.block.list.exec(e);if(t){let r,n,a,o,i,s,l,c,p,u,d,h,f=t[1].trim();const m=f.length>1,y={type:"list",raw:"",ordered:m,start:m?+f.slice(0,-1):"",loose:!1,items:[]};f=m?`\\d{1,9}\\${f.slice(-1)}`:`\\${f}`,this.options.pedantic&&(f=m?f:"[*+-]");const g=new RegExp(`^( {0,3}${f})((?:[\t ][^\\n]*)?(?:\\n|$))`);for(;e&&(h=!1,t=g.exec(e))&&!this.rules.block.hr.test(e);){if(r=t[0],e=e.substring(r.length),c=t[2].split("\n",1)[0],p=e.split("\n",1)[0],this.options.pedantic?(o=2,d=c.trimLeft()):(o=t[2].search(/[^ ]/),o=o>4?1:o,d=c.slice(o),o+=t[1].length),s=!1,!c&&/^ *$/.test(p)&&(r+=p+"\n",e=e.substring(p.length+1),h=!0),!h){const t=new RegExp(`^ {0,${Math.min(3,o-1)}}(?:[*+-]|\\d{1,9}[.)])((?: [^\\n]*)?(?:\\n|$))`),n=new RegExp(`^ {0,${Math.min(3,o-1)}}((?:- *){3,}|(?:_ *){3,}|(?:\\* *){3,})(?:\\n+|$)`);for(;e&&(u=e.split("\n",1)[0],c=u,this.options.pedantic&&(c=c.replace(/^ {1,4}(?=( {4})*[^ ])/g," ")),!t.test(c))&&!n.test(e);){if(c.search(/[^ ]/)>=o||!c.trim())d+="\n"+c.slice(o);else{if(s)break;d+="\n"+c}s||c.trim()||(s=!0),r+=u+"\n",e=e.substring(u.length+1)}}y.loose||(l?y.loose=!0:/\n *\n *$/.test(r)&&(l=!0)),this.options.gfm&&(n=/^\[[ xX]\] /.exec(d),n&&(a="[ ] "!==n[0],d=d.replace(/^\[[ xX]\] +/,""))),y.items.push({type:"list_item",raw:r,task:!!n,checked:a,loose:!1,text:d}),y.raw+=r}y.items[y.items.length-1].raw=r.trimRight(),y.items[y.items.length-1].text=d.trimRight(),y.raw=y.raw.trimRight();const v=y.items.length;for(i=0;i"space"===e.type)),t=e.every((e=>{const t=e.raw.split("");let r=0;for(const e of t)if("\n"===e&&(r+=1),r>1)return!0;return!1}));!y.loose&&e.length&&t&&(y.loose=!0,y.items[i].loose=!0)}return y}}html(e){const t=this.rules.block.html.exec(e);if(t){const e={type:"html",raw:t[0],pre:!this.options.sanitizer&&("pre"===t[1]||"script"===t[1]||"style"===t[1]),text:t[0]};return this.options.sanitize&&(e.type="paragraph",e.text=this.options.sanitizer?this.options.sanitizer(t[0]):he(t[0]),e.tokens=[],this.lexer.inline(e.text,e.tokens)),e}}def(e){const t=this.rules.block.def.exec(e);if(t){t[3]&&(t[3]=t[3].substring(1,t[3].length-1));return{type:"def",tag:t[1].toLowerCase().replace(/\s+/g," "),raw:t[0],href:t[2],title:t[3]}}}table(e){const t=this.rules.block.table.exec(e);if(t){const e={type:"table",header:Oe(t[1]).map((e=>({text:e}))),align:t[2].replace(/^ *|\| *$/g,"").split(/ *\| */),rows:t[3]&&t[3].trim()?t[3].replace(/\n[ \t]*$/,"").split("\n"):[]};if(e.header.length===e.align.length){e.raw=t[0];let r,n,a,o,i=e.align.length;for(r=0;r({text:e})));for(i=e.header.length,n=0;n/i.test(t[0])&&(this.lexer.state.inLink=!1),!this.lexer.state.inRawBlock&&/^<(pre|code|kbd|script)(\s|>)/i.test(t[0])?this.lexer.state.inRawBlock=!0:this.lexer.state.inRawBlock&&/^<\/(pre|code|kbd|script)(\s|>)/i.test(t[0])&&(this.lexer.state.inRawBlock=!1),{type:this.options.sanitize?"text":"html",raw:t[0],inLink:this.lexer.state.inLink,inRawBlock:this.lexer.state.inRawBlock,text:this.options.sanitize?this.options.sanitizer?this.options.sanitizer(t[0]):he(t[0]):t[0]}}link(e){const t=this.rules.inline.link.exec(e);if(t){const e=t[2].trim();if(!this.options.pedantic&&/^$/.test(e))return;const t=Te(e.slice(0,-1),"\\");if((e.length-t.length)%2==0)return}else{const e=function(e,t){if(-1===e.indexOf(t[1]))return-1;const r=e.length;let n=0,a=0;for(;a-1){const r=(0===t[0].indexOf("!")?5:4)+t[1].length+e;t[2]=t[2].substring(0,e),t[0]=t[0].substring(0,r).trim(),t[3]=""}}let r=t[2],n="";if(this.options.pedantic){const e=/^([^'"]*[^\s])\s+(['"])(.*)\2/.exec(r);e&&(r=e[1],n=e[3])}else n=t[3]?t[3].slice(1,-1):"";return r=r.trim(),/^$/.test(e)?r.slice(1):r.slice(1,-1)),je(t,{href:r?r.replace(this.rules.inline._escapes,"$1"):r,title:n?n.replace(this.rules.inline._escapes,"$1"):n},t[0],this.lexer)}}reflink(e,t){let r;if((r=this.rules.inline.reflink.exec(e))||(r=this.rules.inline.nolink.exec(e))){let e=(r[2]||r[1]).replace(/\s+/g," ");if(e=t[e.toLowerCase()],!e||!e.href){const e=r[0].charAt(0);return{type:"text",raw:e,text:e}}return je(r,e,r[0],this.lexer)}}emStrong(e,t,r=""){let n=this.rules.inline.emStrong.lDelim.exec(e);if(!n)return;if(n[3]&&r.match(/[\p{L}\p{N}]/u))return;const a=n[1]||n[2]||"";if(!a||a&&(""===r||this.rules.inline.punctuation.exec(r))){const r=n[0].length-1;let a,o,i=r,s=0;const l="*"===n[0][0]?this.rules.inline.emStrong.rDelimAst:this.rules.inline.emStrong.rDelimUnd;for(l.lastIndex=0,t=t.slice(-1*e.length+r);null!=(n=l.exec(t));){if(a=n[1]||n[2]||n[3]||n[4]||n[5]||n[6],!a)continue;if(o=a.length,n[3]||n[4]){i+=o;continue}if((n[5]||n[6])&&r%3&&!((r+o)%3)){s+=o;continue}if(i-=o,i>0)continue;if(o=Math.min(o,o+i+s),Math.min(r,o)%2){const t=e.slice(1,r+n.index+o);return{type:"em",raw:e.slice(0,r+n.index+o+1),text:t,tokens:this.lexer.inlineTokens(t,[])}}const t=e.slice(2,r+n.index+o-1);return{type:"strong",raw:e.slice(0,r+n.index+o+1),text:t,tokens:this.lexer.inlineTokens(t,[])}}}}codespan(e){const t=this.rules.inline.code.exec(e);if(t){let e=t[2].replace(/\n/g," ");const r=/[^ ]/.test(e),n=/^ /.test(e)&&/ $/.test(e);return r&&n&&(e=e.substring(1,e.length-1)),e=he(e,!0),{type:"codespan",raw:t[0],text:e}}}br(e){const t=this.rules.inline.br.exec(e);if(t)return{type:"br",raw:t[0]}}del(e){const t=this.rules.inline.del.exec(e);if(t)return{type:"del",raw:t[0],text:t[2],tokens:this.lexer.inlineTokens(t[2],[])}}autolink(e,t){const r=this.rules.inline.autolink.exec(e);if(r){let e,n;return"@"===r[2]?(e=he(this.options.mangle?t(r[1]):r[1]),n="mailto:"+e):(e=he(r[1]),n=e),{type:"link",raw:r[0],text:e,href:n,tokens:[{type:"text",raw:e,text:e}]}}}url(e,t){let r;if(r=this.rules.inline.url.exec(e)){let e,n;if("@"===r[2])e=he(this.options.mangle?t(r[0]):r[0]),n="mailto:"+e;else{let t;do{t=r[0],r[0]=this.rules.inline._backpedal.exec(r[0])[0]}while(t!==r[0]);e=he(r[0]),n="www."===r[1]?"http://"+e:e}return{type:"link",raw:r[0],text:e,href:n,tokens:[{type:"text",raw:e,text:e}]}}}inlineText(e,t){const r=this.rules.inline.text.exec(e);if(r){let e;return e=this.lexer.state.inRawBlock?this.options.sanitize?this.options.sanitizer?this.options.sanitizer(r[0]):he(r[0]):r[0]:he(this.options.smartypants?t(r[0]):r[0]),{type:"text",raw:r[0],text:e}}}}const Pe={newline:/^(?: *(?:\n|$))+/,code:/^( {4}[^\n]+(?:\n(?: *(?:\n|$))*)?)+/,fences:/^ {0,3}(`{3,}(?=[^`\n]*\n)|~{3,})([^\n]*)\n(?:|([\s\S]*?)\n)(?: {0,3}\1[~`]* *(?=\n|$)|$)/,hr:/^ {0,3}((?:-[\t ]*){3,}|(?:_[ \t]*){3,}|(?:\*[ \t]*){3,})(?:\n+|$)/,heading:/^ {0,3}(#{1,6})(?=\s|$)(.*)(?:\n+|$)/,blockquote:/^( {0,3}> ?(paragraph|[^\n]*)(?:\n|$))+/,list:/^( {0,3}bull)([ \t][^\n]+?)?(?:\n|$)/,html:"^ {0,3}(?:<(script|pre|style|textarea)[\\s>][\\s\\S]*?(?:[^\\n]*\\n+|$)|comment[^\\n]*(\\n+|$)|<\\?[\\s\\S]*?(?:\\?>\\n*|$)|\\n*|$)|\\n*|$)|)[\\s\\S]*?(?:(?:\\n *)+\\n|$)|<(?!script|pre|style|textarea)([a-z][\\w-]*)(?:attribute)*? */?>(?=[ \\t]*(?:\\n|$))[\\s\\S]*?(?:(?:\\n *)+\\n|$)|(?=[ \\t]*(?:\\n|$))[\\s\\S]*?(?:(?:\\n *)+\\n|$))",def:/^ {0,3}\[(label)\]: *(?:\n *)?]+)>?(?:(?: +(?:\n *)?| *\n *)(title))? *(?:\n+|$)/,table:Ae,lheading:/^([^\n]+)\n {0,3}(=+|-+) *(?:\n+|$)/,_paragraph:/^([^\n]+(?:\n(?!hr|heading|lheading|blockquote|fences|list|html|table| +\n)[^\n]+)*)/,text:/^[^\n]+/,_label:/(?!\s*\])(?:\\.|[^\[\]\\])+/,_title:/(?:"(?:\\"?|[^"\\])*"|'[^'\n]*(?:\n[^'\n]+)*\n?'|\([^()]*\))/};Pe.def=ge(Pe.def).replace("label",Pe._label).replace("title",Pe._title).getRegex(),Pe.bullet=/(?:[*+-]|\d{1,9}[.)])/,Pe.listItemStart=ge(/^( *)(bull) */).replace("bull",Pe.bullet).getRegex(),Pe.list=ge(Pe.list).replace(/bull/g,Pe.bullet).replace("hr","\\n+(?=\\1?(?:(?:- *){3,}|(?:_ *){3,}|(?:\\* *){3,})(?:\\n+|$))").replace("def","\\n+(?="+Pe.def.source+")").getRegex(),Pe._tag="address|article|aside|base|basefont|blockquote|body|caption|center|col|colgroup|dd|details|dialog|dir|div|dl|dt|fieldset|figcaption|figure|footer|form|frame|frameset|h[1-6]|head|header|hr|html|iframe|legend|li|link|main|menu|menuitem|meta|nav|noframes|ol|optgroup|option|p|param|section|source|summary|table|tbody|td|tfoot|th|thead|title|tr|track|ul",Pe._comment=/|$)/,Pe.html=ge(Pe.html,"i").replace("comment",Pe._comment).replace("tag",Pe._tag).replace("attribute",/ +[a-zA-Z:_][\w.:-]*(?: *= *"[^"\n]*"| *= *'[^'\n]*'| *= *[^\s"'=<>`]+)?/).getRegex(),Pe.paragraph=ge(Pe._paragraph).replace("hr",Pe.hr).replace("heading"," {0,3}#{1,6} ").replace("|lheading","").replace("|table","").replace("blockquote"," {0,3}>").replace("fences"," {0,3}(?:`{3,}(?=[^`\\n]*\\n)|~{3,})[^\\n]*\\n").replace("list"," {0,3}(?:[*+-]|1[.)]) ").replace("html",")|<(?:script|pre|style|textarea|!--)").replace("tag",Pe._tag).getRegex(),Pe.blockquote=ge(Pe.blockquote).replace("paragraph",Pe.paragraph).getRegex(),Pe.normal=Ee({},Pe),Pe.gfm=Ee({},Pe.normal,{table:"^ *([^\\n ].*\\|.*)\\n {0,3}(?:\\| *)?(:?-+:? *(?:\\| *:?-+:? *)*)(?:\\| *)?(?:\\n((?:(?! *\\n|hr|heading|blockquote|code|fences|list|html).*(?:\\n|$))*)\\n*|$)"}),Pe.gfm.table=ge(Pe.gfm.table).replace("hr",Pe.hr).replace("heading"," {0,3}#{1,6} ").replace("blockquote"," {0,3}>").replace("code"," {4}[^\\n]").replace("fences"," {0,3}(?:`{3,}(?=[^`\\n]*\\n)|~{3,})[^\\n]*\\n").replace("list"," {0,3}(?:[*+-]|1[.)]) ").replace("html",")|<(?:script|pre|style|textarea|!--)").replace("tag",Pe._tag).getRegex(),Pe.gfm.paragraph=ge(Pe._paragraph).replace("hr",Pe.hr).replace("heading"," {0,3}#{1,6} ").replace("|lheading","").replace("table",Pe.gfm.table).replace("blockquote"," {0,3}>").replace("fences"," {0,3}(?:`{3,}(?=[^`\\n]*\\n)|~{3,})[^\\n]*\\n").replace("list"," {0,3}(?:[*+-]|1[.)]) ").replace("html",")|<(?:script|pre|style|textarea|!--)").replace("tag",Pe._tag).getRegex(),Pe.pedantic=Ee({},Pe.normal,{html:ge("^ *(?:comment *(?:\\n|\\s*$)|<(tag)[\\s\\S]+? *(?:\\n{2,}|\\s*$)|\\s]*)*?/?> *(?:\\n{2,}|\\s*$))").replace("comment",Pe._comment).replace(/tag/g,"(?!(?:a|em|strong|small|s|cite|q|dfn|abbr|data|time|code|var|samp|kbd|sub|sup|i|b|u|mark|ruby|rt|rp|bdi|bdo|span|br|wbr|ins|del|img)\\b)\\w+(?!:|[^\\w\\s@]*@)\\b").getRegex(),def:/^ *\[([^\]]+)\]: *]+)>?(?: +(["(][^\n]+[")]))? *(?:\n+|$)/,heading:/^(#{1,6})(.*)(?:\n+|$)/,fences:Ae,paragraph:ge(Pe.normal._paragraph).replace("hr",Pe.hr).replace("heading"," *#{1,6} *[^\n]").replace("lheading",Pe.lheading).replace("blockquote"," {0,3}>").replace("|fences","").replace("|list","").replace("|html","").getRegex()});const Re={escape:/^\\([!"#$%&'()*+,\-./:;<=>?@\[\]\\^_`{|}~])/,autolink:/^<(scheme:[^\s\x00-\x1f<>]*|email)>/,url:Ae,tag:"^comment|^|^<[a-zA-Z][\\w-]*(?:attribute)*?\\s*/?>|^<\\?[\\s\\S]*?\\?>|^|^",link:/^!?\[(label)\]\(\s*(href)(?:\s+(title))?\s*\)/,reflink:/^!?\[(label)\]\[(ref)\]/,nolink:/^!?\[(ref)\](?:\[\])?/,reflinkSearch:"reflink|nolink(?!\\()",emStrong:{lDelim:/^(?:\*+(?:([punct_])|[^\s*]))|^_+(?:([punct*])|([^\s_]))/,rDelimAst:/^[^_*]*?\_\_[^_*]*?\*[^_*]*?(?=\_\_)|[^*]+(?=[^*])|[punct_](\*+)(?=[\s]|$)|[^punct*_\s](\*+)(?=[punct_\s]|$)|[punct_\s](\*+)(?=[^punct*_\s])|[\s](\*+)(?=[punct_])|[punct_](\*+)(?=[punct_])|[^punct*_\s](\*+)(?=[^punct*_\s])/,rDelimUnd:/^[^_*]*?\*\*[^_*]*?\_[^_*]*?(?=\*\*)|[^_]+(?=[^_])|[punct*](\_+)(?=[\s]|$)|[^punct*_\s](\_+)(?=[punct*\s]|$)|[punct*\s](\_+)(?=[^punct*_\s])|[\s](\_+)(?=[punct*])|[punct*](\_+)(?=[punct*])/},code:/^(`+)([^`]|[^`][\s\S]*?[^`])\1(?!`)/,br:/^( {2,}|\\)\n(?!\s*$)/,del:Ae,text:/^(`+|[^`])(?:(?= {2,}\n)|[\s\S]*?(?:(?=[\\.5&&(r="x"+r.toString(16)),n+="&#"+r+";";return n}Re._punctuation="!\"#$%&'()+\\-.,/:;<=>?@\\[\\]`^{|}~",Re.punctuation=ge(Re.punctuation).replace(/punctuation/g,Re._punctuation).getRegex(),Re.blockSkip=/\[[^\]]*?\]\([^\)]*?\)|`[^`]*?`|<[^>]*?>/g,Re.escapedEmSt=/\\\*|\\_/g,Re._comment=ge(Pe._comment).replace("(?:--\x3e|$)","--\x3e").getRegex(),Re.emStrong.lDelim=ge(Re.emStrong.lDelim).replace(/punct/g,Re._punctuation).getRegex(),Re.emStrong.rDelimAst=ge(Re.emStrong.rDelimAst,"g").replace(/punct/g,Re._punctuation).getRegex(),Re.emStrong.rDelimUnd=ge(Re.emStrong.rDelimUnd,"g").replace(/punct/g,Re._punctuation).getRegex(),Re._escapes=/\\([!"#$%&'()*+,\-./:;<=>?@\[\]\\^_`{|}~])/g,Re._scheme=/[a-zA-Z][a-zA-Z0-9+.-]{1,31}/,Re._email=/[a-zA-Z0-9.!#$%&'*+/=?^_`{|}~-]+(@)[a-zA-Z0-9](?:[a-zA-Z0-9-]{0,61}[a-zA-Z0-9])?(?:\.[a-zA-Z0-9](?:[a-zA-Z0-9-]{0,61}[a-zA-Z0-9])?)+(?![-_])/,Re.autolink=ge(Re.autolink).replace("scheme",Re._scheme).replace("email",Re._email).getRegex(),Re._attribute=/\s+[a-zA-Z:_][\w.:-]*(?:\s*=\s*"[^"]*"|\s*=\s*'[^']*'|\s*=\s*[^\s"'=<>`]+)?/,Re.tag=ge(Re.tag).replace("comment",Re._comment).replace("attribute",Re._attribute).getRegex(),Re._label=/(?:\[(?:\\.|[^\[\]\\])*\]|\\.|`[^`]*`|[^\[\]\\`])*?/,Re._href=/<(?:\\.|[^\n<>\\])+>|[^\s\x00-\x1f]*/,Re._title=/"(?:\\"?|[^"\\])*"|'(?:\\'?|[^'\\])*'|\((?:\\\)?|[^)\\])*\)/,Re.link=ge(Re.link).replace("label",Re._label).replace("href",Re._href).replace("title",Re._title).getRegex(),Re.reflink=ge(Re.reflink).replace("label",Re._label).replace("ref",Pe._label).getRegex(),Re.nolink=ge(Re.nolink).replace("ref",Pe._label).getRegex(),Re.reflinkSearch=ge(Re.reflinkSearch,"g").replace("reflink",Re.reflink).replace("nolink",Re.nolink).getRegex(),Re.normal=Ee({},Re),Re.pedantic=Ee({},Re.normal,{strong:{start:/^__|\*\*/,middle:/^__(?=\S)([\s\S]*?\S)__(?!_)|^\*\*(?=\S)([\s\S]*?\S)\*\*(?!\*)/,endAst:/\*\*(?!\*)/g,endUnd:/__(?!_)/g},em:{start:/^_|\*/,middle:/^()\*(?=\S)([\s\S]*?\S)\*(?!\*)|^_(?=\S)([\s\S]*?\S)_(?!_)/,endAst:/\*(?!\*)/g,endUnd:/_(?!_)/g},link:ge(/^!?\[(label)\]\((.*?)\)/).replace("label",Re._label).getRegex(),reflink:ge(/^!?\[(label)\]\s*\[([^\]]*)\]/).replace("label",Re._label).getRegex()}),Re.gfm=Ee({},Re.normal,{escape:ge(Re.escape).replace("])","~|])").getRegex(),_extended_email:/[A-Za-z0-9._+-]+(@)[a-zA-Z0-9-_]+(?:\.[a-zA-Z0-9-_]*[a-zA-Z0-9])+(?![-_])/,url:/^((?:ftp|https?):\/\/|www\.)(?:[a-zA-Z0-9\-]+\.?)+[^\s<]*|^email/,_backpedal:/(?:[^?!.,:;*_~()&]+|\([^)]*\)|&(?![a-zA-Z0-9]+;$)|[?!.,:;*_~)]+(?!$))+/,del:/^(~~?)(?=[^\s~])([\s\S]*?[^\s~])\1(?=[^~]|$)/,text:/^([`~]+|[^`~])(?:(?= {2,}\n)|(?=[a-zA-Z0-9.!#$%&'*+\/=?_`{\|}~-]+@)|[\s\S]*?(?:(?=[\\t+" ".repeat(r.length)));e;)if(!(this.options.extensions&&this.options.extensions.block&&this.options.extensions.block.some((n=>!!(r=n.call({lexer:this},e,t))&&(e=e.substring(r.raw.length),t.push(r),!0)))))if(r=this.tokenizer.space(e))e=e.substring(r.raw.length),1===r.raw.length&&t.length>0?t[t.length-1].raw+="\n":t.push(r);else if(r=this.tokenizer.code(e))e=e.substring(r.raw.length),n=t[t.length-1],!n||"paragraph"!==n.type&&"text"!==n.type?t.push(r):(n.raw+="\n"+r.raw,n.text+="\n"+r.text,this.inlineQueue[this.inlineQueue.length-1].src=n.text);else if(r=this.tokenizer.fences(e))e=e.substring(r.raw.length),t.push(r);else if(r=this.tokenizer.heading(e))e=e.substring(r.raw.length),t.push(r);else if(r=this.tokenizer.hr(e))e=e.substring(r.raw.length),t.push(r);else if(r=this.tokenizer.blockquote(e))e=e.substring(r.raw.length),t.push(r);else if(r=this.tokenizer.list(e))e=e.substring(r.raw.length),t.push(r);else if(r=this.tokenizer.html(e))e=e.substring(r.raw.length),t.push(r);else if(r=this.tokenizer.def(e))e=e.substring(r.raw.length),n=t[t.length-1],!n||"paragraph"!==n.type&&"text"!==n.type?this.tokens.links[r.tag]||(this.tokens.links[r.tag]={href:r.href,title:r.title}):(n.raw+="\n"+r.raw,n.text+="\n"+r.raw,this.inlineQueue[this.inlineQueue.length-1].src=n.text);else if(r=this.tokenizer.table(e))e=e.substring(r.raw.length),t.push(r);else if(r=this.tokenizer.lheading(e))e=e.substring(r.raw.length),t.push(r);else{if(a=e,this.options.extensions&&this.options.extensions.startBlock){let t=1/0;const r=e.slice(1);let n;this.options.extensions.startBlock.forEach((function(e){n=e.call({lexer:this},r),"number"==typeof n&&n>=0&&(t=Math.min(t,n))})),t<1/0&&t>=0&&(a=e.substring(0,t+1))}if(this.state.top&&(r=this.tokenizer.paragraph(a)))n=t[t.length-1],o&&"paragraph"===n.type?(n.raw+="\n"+r.raw,n.text+="\n"+r.text,this.inlineQueue.pop(),this.inlineQueue[this.inlineQueue.length-1].src=n.text):t.push(r),o=a.length!==e.length,e=e.substring(r.raw.length);else if(r=this.tokenizer.text(e))e=e.substring(r.raw.length),n=t[t.length-1],n&&"text"===n.type?(n.raw+="\n"+r.raw,n.text+="\n"+r.text,this.inlineQueue.pop(),this.inlineQueue[this.inlineQueue.length-1].src=n.text):t.push(r);else if(e){const t="Infinite loop on byte: "+e.charCodeAt(0);if(this.options.silent){console.error(t);break}throw new Error(t)}}return this.state.top=!0,t}inline(e,t){this.inlineQueue.push({src:e,tokens:t})}inlineTokens(e,t=[]){let r,n,a,o,i,s,l=e;if(this.tokens.links){const e=Object.keys(this.tokens.links);if(e.length>0)for(;null!=(o=this.tokenizer.rules.inline.reflinkSearch.exec(l));)e.includes(o[0].slice(o[0].lastIndexOf("[")+1,-1))&&(l=l.slice(0,o.index)+"["+_e("a",o[0].length-2)+"]"+l.slice(this.tokenizer.rules.inline.reflinkSearch.lastIndex))}for(;null!=(o=this.tokenizer.rules.inline.blockSkip.exec(l));)l=l.slice(0,o.index)+"["+_e("a",o[0].length-2)+"]"+l.slice(this.tokenizer.rules.inline.blockSkip.lastIndex);for(;null!=(o=this.tokenizer.rules.inline.escapedEmSt.exec(l));)l=l.slice(0,o.index)+"++"+l.slice(this.tokenizer.rules.inline.escapedEmSt.lastIndex);for(;e;)if(i||(s=""),i=!1,!(this.options.extensions&&this.options.extensions.inline&&this.options.extensions.inline.some((n=>!!(r=n.call({lexer:this},e,t))&&(e=e.substring(r.raw.length),t.push(r),!0)))))if(r=this.tokenizer.escape(e))e=e.substring(r.raw.length),t.push(r);else if(r=this.tokenizer.tag(e))e=e.substring(r.raw.length),n=t[t.length-1],n&&"text"===r.type&&"text"===n.type?(n.raw+=r.raw,n.text+=r.text):t.push(r);else if(r=this.tokenizer.link(e))e=e.substring(r.raw.length),t.push(r);else if(r=this.tokenizer.reflink(e,this.tokens.links))e=e.substring(r.raw.length),n=t[t.length-1],n&&"text"===r.type&&"text"===n.type?(n.raw+=r.raw,n.text+=r.text):t.push(r);else if(r=this.tokenizer.emStrong(e,l,s))e=e.substring(r.raw.length),t.push(r);else if(r=this.tokenizer.codespan(e))e=e.substring(r.raw.length),t.push(r);else if(r=this.tokenizer.br(e))e=e.substring(r.raw.length),t.push(r);else if(r=this.tokenizer.del(e))e=e.substring(r.raw.length),t.push(r);else if(r=this.tokenizer.autolink(e,De))e=e.substring(r.raw.length),t.push(r);else if(this.state.inLink||!(r=this.tokenizer.url(e,De))){if(a=e,this.options.extensions&&this.options.extensions.startInline){let t=1/0;const r=e.slice(1);let n;this.options.extensions.startInline.forEach((function(e){n=e.call({lexer:this},r),"number"==typeof n&&n>=0&&(t=Math.min(t,n))})),t<1/0&&t>=0&&(a=e.substring(0,t+1))}if(r=this.tokenizer.inlineText(a,Le))e=e.substring(r.raw.length),"_"!==r.raw.slice(-1)&&(s=r.raw.slice(-1)),i=!0,n=t[t.length-1],n&&"text"===n.type?(n.raw+=r.raw,n.text+=r.text):t.push(r);else if(e){const t="Infinite loop on byte: "+e.charCodeAt(0);if(this.options.silent){console.error(t);break}throw new Error(t)}}else e=e.substring(r.raw.length),t.push(r);return t}}class Fe{constructor(e){this.options=e||ie}code(e,t,r){const n=(t||"").match(/\S*/)[0];if(this.options.highlight){const t=this.options.highlight(e,n);null!=t&&t!==e&&(r=!0,e=t)}return e=e.replace(/\n$/,"")+"\n",n?'
'+(r?e:he(e,!0))+"
\n":"
"+(r?e:he(e,!0))+"
\n"}blockquote(e){return`
\n${e}
\n`}html(e){return e}heading(e,t,r,n){if(this.options.headerIds){return`${e}\n`}return`${e}\n`}hr(){return this.options.xhtml?"
\n":"
\n"}list(e,t,r){const n=t?"ol":"ul";return"<"+n+(t&&1!==r?' start="'+r+'"':"")+">\n"+e+"\n"}listitem(e){return`
  • ${e}
  • \n`}checkbox(e){return" "}paragraph(e){return`

    ${e}

    \n`}table(e,t){return t&&(t=`${t}`),"\n\n"+e+"\n"+t+"
    \n"}tablerow(e){return`\n${e}\n`}tablecell(e,t){const r=t.header?"th":"td";return(t.align?`<${r} align="${t.align}">`:`<${r}>`)+e+`\n`}strong(e){return`${e}`}em(e){return`${e}`}codespan(e){return`${e}`}br(){return this.options.xhtml?"
    ":"
    "}del(e){return`${e}`}link(e,t,r){if(null===(e=xe(this.options.sanitize,this.options.baseUrl,e)))return r;let n='",n}image(e,t,r){if(null===(e=xe(this.options.sanitize,this.options.baseUrl,e)))return r;let n=`${r}":">",n}text(e){return e}}class Ne{strong(e){return e}em(e){return e}codespan(e){return e}del(e){return e}html(e){return e}text(e){return e}link(e,t,r){return""+r}image(e,t,r){return""+r}br(){return""}}class ze{constructor(){this.seen={}}serialize(e){return e.toLowerCase().trim().replace(/<[!\/a-z].*?>/gi,"").replace(/[\u2000-\u206F\u2E00-\u2E7F\\'!"#$%&()*+,./:;<=>?@[\]^`{|}~]/g,"").replace(/\s/g,"-")}getNextSafeSlug(e,t){let r=e,n=0;if(this.seen.hasOwnProperty(r)){n=this.seen[e];do{n++,r=e+"-"+n}while(this.seen.hasOwnProperty(r))}return t||(this.seen[e]=n,this.seen[r]=0),r}slug(e,t={}){const r=this.serialize(e);return this.getNextSafeSlug(r,t.dryrun)}}class Ue{constructor(e){this.options=e||ie,this.options.renderer=this.options.renderer||new Fe,this.renderer=this.options.renderer,this.renderer.options=this.options,this.textRenderer=new Ne,this.slugger=new ze}static parse(e,t){return new Ue(t).parse(e)}static parseInline(e,t){return new Ue(t).parseInline(e)}parse(e,t=!0){let r,n,a,o,i,s,l,c,p,u,d,h,f,m,y,g,v,b,x,w="";const $=e.length;for(r=0;r<$;r++)if(u=e[r],this.options.extensions&&this.options.extensions.renderers&&this.options.extensions.renderers[u.type]&&(x=this.options.extensions.renderers[u.type].call({parser:this},u),!1!==x||!["space","hr","heading","code","table","blockquote","list","html","paragraph","text"].includes(u.type)))w+=x||"";else switch(u.type){case"space":continue;case"hr":w+=this.renderer.hr();continue;case"heading":w+=this.renderer.heading(this.parseInline(u.tokens),u.depth,me(this.parseInline(u.tokens,this.textRenderer)),this.slugger);continue;case"code":w+=this.renderer.code(u.text,u.lang,u.escaped);continue;case"table":for(c="",l="",o=u.header.length,n=0;n0&&"paragraph"===y.tokens[0].type?(y.tokens[0].text=b+" "+y.tokens[0].text,y.tokens[0].tokens&&y.tokens[0].tokens.length>0&&"text"===y.tokens[0].tokens[0].type&&(y.tokens[0].tokens[0].text=b+" "+y.tokens[0].tokens[0].text)):y.tokens.unshift({type:"text",text:b}):m+=b),m+=this.parse(y.tokens,f),p+=this.renderer.listitem(m,v,g);w+=this.renderer.list(p,d,h);continue;case"html":w+=this.renderer.html(u.text);continue;case"paragraph":w+=this.renderer.paragraph(this.parseInline(u.tokens));continue;case"text":for(p=u.tokens?this.parseInline(u.tokens):u.text;r+1<$&&"text"===e[r+1].type;)u=e[++r],p+="\n"+(u.tokens?this.parseInline(u.tokens):u.text);w+=t?this.renderer.paragraph(p):p;continue;default:{const e='Token with "'+u.type+'" type was not found.';if(this.options.silent)return void console.error(e);throw new Error(e)}}return w}parseInline(e,t){t=t||this.renderer;let r,n,a,o="";const i=e.length;for(r=0;r{n(e.text,e.lang,(function(t,r){if(t)return o(t);null!=r&&r!==e.text&&(e.text=r,e.escaped=!0),i--,0===i&&o()}))}),0))})),void(0===i&&o())}try{const r=Be.lex(e,t);return t.walkTokens&&qe.walkTokens(r,t.walkTokens),Ue.parse(r,t)}catch(e){if(e.message+="\nPlease report this to https://github.com/markedjs/marked.",t.silent)return"

    An error occurred:

    "+he(e.message+"",!0)+"
    ";throw e}}qe.options=qe.setOptions=function(e){var t;return Ee(qe.defaults,e),t=qe.defaults,ie=t,qe},qe.getDefaults=oe,qe.defaults=ie,qe.use=function(...e){const t=Ee({},...e),r=qe.defaults.extensions||{renderers:{},childTokens:{}};let n;e.forEach((e=>{if(e.extensions&&(n=!0,e.extensions.forEach((e=>{if(!e.name)throw new Error("extension name required");if(e.renderer){const t=r.renderers?r.renderers[e.name]:null;r.renderers[e.name]=t?function(...r){let n=e.renderer.apply(this,r);return!1===n&&(n=t.apply(this,r)),n}:e.renderer}if(e.tokenizer){if(!e.level||"block"!==e.level&&"inline"!==e.level)throw new Error("extension level must be 'block' or 'inline'");r[e.level]?r[e.level].unshift(e.tokenizer):r[e.level]=[e.tokenizer],e.start&&("block"===e.level?r.startBlock?r.startBlock.push(e.start):r.startBlock=[e.start]:"inline"===e.level&&(r.startInline?r.startInline.push(e.start):r.startInline=[e.start]))}e.childTokens&&(r.childTokens[e.name]=e.childTokens)}))),e.renderer){const r=qe.defaults.renderer||new Fe;for(const t in e.renderer){const n=r[t];r[t]=(...a)=>{let o=e.renderer[t].apply(r,a);return!1===o&&(o=n.apply(r,a)),o}}t.renderer=r}if(e.tokenizer){const r=qe.defaults.tokenizer||new Ie;for(const t in e.tokenizer){const n=r[t];r[t]=(...a)=>{let o=e.tokenizer[t].apply(r,a);return!1===o&&(o=n.apply(r,a)),o}}t.tokenizer=r}if(e.walkTokens){const r=qe.defaults.walkTokens;t.walkTokens=function(t){e.walkTokens.call(this,t),r&&r.call(this,t)}}n&&(t.extensions=r),qe.setOptions(t)}))},qe.walkTokens=function(e,t){for(const r of e)switch(t.call(qe,r),r.type){case"table":for(const e of r.header)qe.walkTokens(e.tokens,t);for(const e of r.rows)for(const r of e)qe.walkTokens(r.tokens,t);break;case"list":qe.walkTokens(r.items,t);break;default:qe.defaults.extensions&&qe.defaults.extensions.childTokens&&qe.defaults.extensions.childTokens[r.type]?qe.defaults.extensions.childTokens[r.type].forEach((function(e){qe.walkTokens(r[e],t)})):r.tokens&&qe.walkTokens(r.tokens,t)}},qe.parseInline=function(e,t){if(null==e)throw new Error("marked.parseInline(): input parameter is undefined or null");if("string"!=typeof e)throw new Error("marked.parseInline(): input parameter is of type "+Object.prototype.toString.call(e)+", string expected");Ce(t=Ee({},qe.defaults,t||{}));try{const r=Be.lexInline(e,t);return t.walkTokens&&qe.walkTokens(r,t.walkTokens),Ue.parseInline(r,t)}catch(e){if(e.message+="\nPlease report this to https://github.com/markedjs/marked.",t.silent)return"

    An error occurred:

    "+he(e.message+"",!0)+"
    ";throw e}},qe.Parser=Ue,qe.parser=Ue.parse,qe.Renderer=Fe,qe.TextRenderer=Ne,qe.Lexer=Be,qe.lexer=Be.lex,qe.Tokenizer=Ie,qe.Slugger=ze,qe.parse=qe;qe.options,qe.setOptions,qe.use,qe.walkTokens,qe.parseInline,Ue.parse,Be.lex;var Me=r(660),He=r.n(Me);r(251),r(358),r(46),r(503),r(277),r(874),r(366),r(57),r(16);const We=l` + .hover-bg:hover{ + background: var(--bg3); + } + ::selection { + background: var(--selection-bg); + color: var(--selection-fg); + } + .regular-font{ + font-family:var(--font-regular); + } + .mono-font { + font-family:var(--font-mono); + } + .title { + font-size: calc(var(--font-size-small) + 18px); + font-weight: normal + } + .sub-title{ font-size: 20px;} + .req-res-title { + font-family: var(--font-regular); + font-size: calc(var(--font-size-small) + 4px); + font-weight:bold; + margin-bottom:8px; + text-align:left; + } + .tiny-title { + font-size:calc(var(--font-size-small) + 1px); + font-weight:bold; + } + .regular-font-size { font-size: var(--font-size-regular); } + .small-font-size { font-size: var(--font-size-small); } + .upper { text-transform: uppercase; } + .primary-text{ color: var(--primary-color); } + .bold-text { font-weight:bold; } + .gray-text { color: var(--light-fg); } + .red-text {color: var(--red)} + .blue-text {color: var(--blue)} + .multiline { + overflow: scroll; + max-height: var(--resp-area-height, 300px); + color: var(--fg3); + } + .method-fg.put { color: var(--orange); } + .method-fg.post { color: var(--green); } + .method-fg.get { color: var(--blue); } + .method-fg.delete { color: var(--red); } + .method-fg.options, + .method-fg.head, + .method-fg.patch { + color: var(--yellow); + } + + h1{ font-family:var(--font-regular); font-size:28px; padding-top: 10px; letter-spacing:normal; font-weight:normal; } + h2{ font-family:var(--font-regular); font-size:24px; padding-top: 10px; letter-spacing:normal; font-weight:normal; } + h3{ font-family:var(--font-regular); font-size:18px; padding-top: 10px; letter-spacing:normal; font-weight:normal; } + h4{ font-family:var(--font-regular); font-size:16px; padding-top: 10px; letter-spacing:normal; font-weight:normal; } + h5{ font-family:var(--font-regular); font-size:14px; padding-top: 10px; letter-spacing:normal; font-weight:normal; } + h6{ font-family:var(--font-regular); font-size:14px; padding-top: 10px; letter-spacing:normal; font-weight:normal; } + + h1,h2,h3,h4,h5,h5{ + margin-block-end: 0.2em; + } + p { margin-block-start: 0.5em; } + a { color: var(--blue); cursor:pointer; } + a.inactive-link { + color:var(--fg); + text-decoration: none; + cursor:text; + } + + code, + pre { + margin: 0px; + font-family: var(--font-mono); + font-size: calc(var(--font-size-mono) - 1px); + } + + .m-markdown, + .m-markdown-small { + display:block; + } + + .m-markdown p, + .m-markdown span { + font-size: var(--font-size-regular); + line-height:calc(var(--font-size-regular) + 8px); + } + .m-markdown li { + font-size: var(--font-size-regular); + line-height:calc(var(--font-size-regular) + 10px); + } + + .m-markdown-small p, + .m-markdown-small span, + .m-markdown-small li { + font-size: var(--font-size-small); + line-height: calc(var(--font-size-small) + 6px); + } + .m-markdown-small li { + line-height: calc(var(--font-size-small) + 8px); + } + + .m-markdown p:not(:first-child) { + margin-block-start: 24px; + } + + .m-markdown-small p:not(:first-child) { + margin-block-start: 12px; + } + .m-markdown-small p:first-child { + margin-block-start: 0; + } + + .m-markdown p, + .m-markdown-small p { + margin-block-end: 0 + } + + .m-markdown code span { + font-size:var(--font-size-mono); + } + + .m-markdown-small code, + .m-markdown code { + padding: 1px 6px; + border-radius: 2px; + color: var(--inline-code-fg); + background-color: var(--bg3); + font-size: calc(var(--font-size-mono)); + line-height: 1.2; + } + + .m-markdown-small code { + font-size: calc(var(--font-size-mono) - 1px); + } + + .m-markdown-small pre, + .m-markdown pre { + white-space: pre-wrap; + overflow-x: auto; + line-height: normal; + border-radius: 2px; + border: 1px solid var(--code-border-color); + } + + .m-markdown pre { + padding: 12px; + background-color: var(--code-bg); + color:var(--code-fg); + } + + .m-markdown-small pre { + margin-top: 4px; + padding: 2px 4px; + background-color: var(--bg3); + color: var(--fg2); + } + + .m-markdown-small pre code, + .m-markdown pre code { + border:none; + padding:0; + } + + .m-markdown pre code { + color: var(--code-fg); + background-color: var(--code-bg); + background-color: transparent; + } + + .m-markdown-small pre code { + color: var(--fg2); + background-color: var(--bg3); + } + + .m-markdown ul, + .m-markdown ol { + padding-inline-start: 30px; + } + + .m-markdown-small ul, + .m-markdown-small ol { + padding-inline-start: 20px; + } + + .m-markdown-small a, + .m-markdown a { + color:var(--blue); + } + + .m-markdown-small img, + .m-markdown img { + max-width: 100%; + } + + /* Markdown table */ + + .m-markdown-small table, + .m-markdown table { + border-spacing: 0; + margin: 10px 0; + border-collapse: separate; + border: 1px solid var(--border-color); + border-radius: var(--border-radius); + font-size: calc(var(--font-size-small) + 1px); + line-height: calc(var(--font-size-small) + 4px); + max-width: 100%; + } + + .m-markdown-small table { + font-size: var(--font-size-small); + line-height: calc(var(--font-size-small) + 2px); + margin: 8px 0; + } + + .m-markdown-small td, + .m-markdown-small th, + .m-markdown td, + .m-markdown th { + vertical-align: top; + border-top: 1px solid var(--border-color); + line-height: calc(var(--font-size-small) + 4px); + } + + .m-markdown-small tr:first-child th, + .m-markdown tr:first-child th { + border-top: 0 none; + } + + .m-markdown th, + .m-markdown td { + padding: 10px 12px; + } + + .m-markdown-small th, + .m-markdown-small td { + padding: 8px 8px; + } + + .m-markdown th, + .m-markdown-small th { + font-weight: 600; + background-color: var(--bg2); + vertical-align: middle; + } + + .m-markdown-small table code { + font-size: calc(var(--font-size-mono) - 2px); + } + + .m-markdown table code { + font-size: calc(var(--font-size-mono) - 1px); + } + + .m-markdown blockquote, + .m-markdown-small blockquote { + margin-inline-start: 0; + margin-inline-end: 0; + border-left: 3px solid var(--border-color); + padding: 6px 0 6px 6px; + } + .m-markdown hr{ + border: 1px solid var(--border-color); + } +`,Ve=l` +/* Button */ +.m-btn { + border-radius: var(--border-radius); + font-weight: 600; + display: inline-block; + padding: 6px 16px; + font-size: var(--font-size-small); + outline: 0; + line-height: 1; + text-align: center; + white-space: nowrap; + border: 2px solid var(--primary-color); + background-color:transparent; + transition: background-color 0.2s; + user-select: none; + cursor: pointer; + box-shadow: 0 1px 3px rgba(0,0,0,0.12), 0 1px 2px rgba(0,0,0,0.24); +} +.m-btn.primary { + background-color: var(--primary-color); + color: var(--primary-color-invert); +} +.m-btn.thin-border { border-width: 1px; } +.m-btn.large { padding:8px 14px; } +.m-btn.small { padding:5px 12px; } +.m-btn.tiny { padding:5px 6px; } +.m-btn.circle { border-radius: 50%; } +.m-btn:hover { + background-color: var(--primary-color); + color: var(--primary-color-invert); +} +.m-btn.nav { border: 2px solid var(--nav-accent-color); } +.m-btn.nav:hover { + background-color: var(--nav-accent-color); +} +.m-btn:disabled{ + background-color: var(--bg3); + color: var(--fg3); + border-color: var(--fg3); + cursor: not-allowed; + opacity: 0.4; +} +.toolbar-btn{ + cursor: pointer; + padding: 4px; + margin:0 2px; + font-size: var(--font-size-small); + min-width: 50px; + color: var(--primary-color-invert); + border-radius: 2px; + border: none; + background-color: var(--primary-color); +} + +input, textarea, select, button, pre { + color:var(--fg); + outline: none; + background-color: var(--input-bg); + border: 1px solid var(--border-color); + border-radius: var(--border-radius); +} +button { + font-family: var(--font-regular); +} + +/* Form Inputs */ +pre, +select, +textarea, +input[type="file"], +input[type="text"], +input[type="password"] { + font-family: var(--font-mono); + font-weight: 400; + font-size: var(--font-size-small); + transition: border .2s; + padding: 6px 5px; +} + +select { + font-family: var(--font-regular); + padding: 5px 30px 5px 5px; + background-image: url("data:image/svg+xml;charset=utf8,%3Csvg%20xmlns%3D%22http%3A%2F%2Fwww.w3.org%2F2000%2Fsvg%22%20width%3D%2212%22%20height%3D%2212%22%3E%3Cpath%20d%3D%22M10.3%203.3L6%207.6%201.7%203.3A1%201%200%2000.3%204.7l5%205a1%201%200%20001.4%200l5-5a1%201%200%2010-1.4-1.4z%22%20fill%3D%22%23777777%22%2F%3E%3C%2Fsvg%3E"); + background-position: calc(100% - 5px) center; + background-repeat: no-repeat; + background-size: 10px; + -webkit-appearance: none; + -moz-appearance: none; + appearance: none; + cursor: pointer; +} + +select:hover { + border-color: var(--primary-color); +} + +textarea::placeholder, +input[type="text"]::placeholder, +input[type="password"]::placeholder { + color: var(--placeholder-color); + opacity:1; +} + +select:focus, +textarea:focus, +input[type="text"]:focus, +input[type="password"]:focus, +textarea:active, +input[type="text"]:active, +input[type="password"]:active { + border:1px solid var(--primary-color); +} + +input[type="file"]{ + font-family: var(--font-regular); + padding:2px; + cursor:pointer; + border: 1px solid var(--primary-color); + min-height: calc(var(--font-size-small) + 18px); +} + +input[type="file"]::-webkit-file-upload-button { + font-family: var(--font-regular); + font-size: var(--font-size-small); + outline: none; + cursor:pointer; + padding: 3px 8px; + border: 1px solid var(--primary-color); + background-color: var(--primary-color); + color: var(--primary-color-invert); + border-radius: var(--border-radius);; + -webkit-appearance: none; +} + +pre, +textarea { + scrollbar-width: thin; + scrollbar-color: var(--border-color) var(--input-bg); +} + +pre::-webkit-scrollbar, +textarea::-webkit-scrollbar { + width: 8px; + height: 8px; +} + +pre::-webkit-scrollbar-track, +textarea::-webkit-scrollbar-track { + background:var(--input-bg); +} + +pre::-webkit-scrollbar-thumb, +textarea::-webkit-scrollbar-thumb { + border-radius: 2px; + background-color: var(--border-color); +} + +.link { + font-size:var(--font-size-small); + text-decoration: underline; + color:var(--blue); + font-family:var(--font-mono); + margin-bottom:2px; +} + +input[type="checkbox"]:focus{ + outline:0; +} + +/* Toggle Body */ +input[type="checkbox"] { + appearance: none; + display: inline-block; + background-color: var(--light-bg); + border: 1px solid var(--light-bg); + border-radius: 9px; + cursor: pointer; + height: 18px; + position: relative; + transition: border .25s .15s, box-shadow .25s .3s, padding .25s; + min-width: 36px; + width: 36px; + vertical-align: top; +} +/* Toggle Thumb */ +input[type="checkbox"]:after { + position: absolute; + background-color: var(--bg); + border: 1px solid var(--light-bg); + border-radius: 8px; + content: ''; + top: 0px; + left: 0px; + right: 16px; + display: block; + height: 16px; + transition: border .25s .15s, left .25s .1s, right .15s .175s; +} + +/* Toggle Body - Checked */ +input[type="checkbox"]:checked { + box-shadow: inset 0 0 0 13px var(--green); + border-color: var(--green); +} +/* Toggle Thumb - Checked*/ +input[type="checkbox"]:checked:after { + border: 1px solid var(--green); + left: 16px; + right: 1px; + transition: border .25s, left .15s .25s, right .25s .175s; +} +`,Ge=l` +.row, .col{ + display:flex; +} +.row { + align-items:center; + flex-direction: row; +} +.col { + align-items:stretch; + flex-direction: column; +} +`,Ke=l` +.m-table { + border-spacing: 0; + border-collapse: separate; + border: 1px solid var(--light-border-color); + border-radius: var(--border-radius); + margin: 0; + max-width: 100%; + direction: ltr; +} +.m-table tr:first-child td, +.m-table tr:first-child th { + border-top: 0 none; +} +.m-table td, +.m-table th { + font-size: var(--font-size-small); + line-height: calc(var(--font-size-small) + 4px); + padding: 4px 5px 4px; + vertical-align: top; +} + +.m-table.padded-12 td, +.m-table.padded-12 th { + padding: 12px; +} + +.m-table td:not([align]), +.m-table th:not([align]) { + text-align: left; +} + +.m-table th { + color: var(--fg2); + font-size: var(--font-size-small); + line-height: calc(var(--font-size-small) + 18px); + font-weight: 600; + letter-spacing: normal; + background-color: var(--bg2); + vertical-align: bottom; + border-bottom: 1px solid var(--light-border-color); +} + +.m-table > tbody > tr > td, +.m-table > tr > td { + border-top: 1px solid var(--light-border-color); + text-overflow: ellipsis; + overflow: hidden; +} +.table-title { + font-size:var(--font-size-small); + font-weight:bold; + vertical-align: middle; + margin: 12px 0 4px 0; +} +`,Je=l` +.only-large-screen { display:none; } +.endpoint-head .path{ + display: flex; + font-family:var(--font-mono); + font-size: var(--font-size-small); + align-items: center; + overflow-wrap: break-word; + word-break: break-all; +} + +.endpoint-head .descr { + font-size: var(--font-size-small); + color:var(--light-fg); + font-weight:400; + align-items: center; + overflow-wrap: break-word; + word-break: break-all; + display:none; +} + +.m-endpoint.expanded{margin-bottom:16px; } +.m-endpoint > .endpoint-head{ + border-width:1px 1px 1px 5px; + border-style:solid; + border-color:transparent; + border-top-color:var(--light-border-color); + display:flex; + padding:6px 16px; + align-items: center; + cursor: pointer; +} +.m-endpoint > .endpoint-head.put:hover, +.m-endpoint > .endpoint-head.put.expanded{ + border-color:var(--orange); + background-color:var(--light-orange); +} +.m-endpoint > .endpoint-head.post:hover, +.m-endpoint > .endpoint-head.post.expanded { + border-color:var(--green); + background-color:var(--light-green); +} +.m-endpoint > .endpoint-head.get:hover, +.m-endpoint > .endpoint-head.get.expanded { + border-color:var(--blue); + background-color:var(--light-blue); +} +.m-endpoint > .endpoint-head.delete:hover, +.m-endpoint > .endpoint-head.delete.expanded { + border-color:var(--red); + background-color:var(--light-red); +} + +.m-endpoint > .endpoint-head.head:hover, +.m-endpoint > .endpoint-head.head.expanded, +.m-endpoint > .endpoint-head.patch:hover, +.m-endpoint > .endpoint-head.patch.expanded, +.m-endpoint > .endpoint-head.options:hover, +.m-endpoint > .endpoint-head.options.expanded { + border-color:var(--yellow); + background-color:var(--light-yellow); +} + +.m-endpoint > .endpoint-head.deprecated:hover, +.m-endpoint > .endpoint-head.deprecated.expanded { + border-color:var(--border-color); + filter:opacity(0.6); +} + +.m-endpoint .endpoint-body { + flex-wrap:wrap; + padding:16px 0px 0 0px; + border-width:0px 1px 1px 5px; + border-style:solid; + box-shadow: 0px 4px 3px -3px rgba(0, 0, 0, 0.15); +} +.m-endpoint .endpoint-body.delete{ border-color:var(--red); } +.m-endpoint .endpoint-body.put{ border-color:var(--orange); } +.m-endpoint .endpoint-body.post{border-color:var(--green);} +.m-endpoint .endpoint-body.get{ border-color:var(--blue); } +.m-endpoint .endpoint-body.head, +.m-endpoint .endpoint-body.patch, +.m-endpoint .endpoint-body.options { + border-color:var(--yellow); +} + +.m-endpoint .endpoint-body.deprecated{ + border-color:var(--border-color); + filter:opacity(0.6); +} + +.endpoint-head .deprecated{ + color: var(--light-fg); + filter:opacity(0.6); +} + +.summary{ + padding:8px 8px; +} +.summary .title{ + font-size:calc(var(--font-size-regular) + 2px); + margin-bottom: 6px; + word-break: break-all; +} + +.endpoint-head .method{ + padding:2px 5px; + vertical-align: middle; + font-size:var(--font-size-small); + height: calc(var(--font-size-small) + 16px); + line-height: calc(var(--font-size-small) + 8px); + width: 60px; + border-radius: 2px; + display:inline-block; + text-align: center; + font-weight: bold; + text-transform:uppercase; + margin-right:5px; +} +.endpoint-head .method.delete{ border: 2px solid var(--red);} +.endpoint-head .method.put{ border: 2px solid var(--orange); } +.endpoint-head .method.post{ border: 2px solid var(--green); } +.endpoint-head .method.get{ border: 2px solid var(--blue); } +.endpoint-head .method.get.deprecated{ border: 2px solid var(--border-color); } +.endpoint-head .method.head, +.endpoint-head .method.patch, +.endpoint-head .method.options { + border: 2px solid var(--yellow); +} + +.req-resp-container{ + display: flex; + margin-top:16px; + align-items: stretch; + flex-wrap: wrap; + flex-direction: column; + border-top:1px solid var(--light-border-color); +} + +.view-mode-request, +api-response.view-mode { + flex:1; + min-height:100px; + padding:16px 8px; + overflow:hidden; +} +.view-mode-request { + border-width:0 0 1px 0; + border-style:dashed; +} + +.head .view-mode-request, +.patch .view-mode-request, +.options .view-mode-request { + border-color:var(--yellow); +} +.put .view-mode-request { + border-color:var(--orange); +} +.post .view-mode-request { + border-color:var(--green); +} +.get .view-mode-request { + border-color:var(--blue); +} +.delete .view-mode-request { + border-color:var(--red); +} + +@media only screen and (min-width: 1024px) { + .only-large-screen { display:block; } + .endpoint-head .path{ + font-size: var(--font-size-regular); + } + .endpoint-head .descr{ + display: flex; + } + .endpoint-head .m-markdown-small, + .descr .m-markdown-small{ + display:block; + } + .req-resp-container{ + flex-direction: var(--layout, row); + flex-wrap: nowrap; + } + api-response.view-mode { + padding:16px; + } + .view-mode-request.row-layout { + border-width:0 1px 0 0; + padding:16px; + } + .summary{ + padding:8px 16px; + } +} +`,Ye=l` +code[class*="language-"], +pre[class*="language-"] { + text-align: left; + white-space: pre; + word-spacing: normal; + word-break: normal; + word-wrap: normal; + line-height: 1.5; + tab-size: 2; + + -webkit-hyphens: none; + -moz-hyphens: none; + -ms-hyphens: none; + hyphens: none; +} + +/* Code blocks */ +pre[class*="language-"] { + padding: 1em; + margin: .5em 0; + overflow: auto; +} + +/* Inline code */ +:not(pre) > code[class*="language-"] { + white-space: normal; +} + +.token.comment, +.token.block-comment, +.token.prolog, +.token.doctype, +.token.cdata { + color: var(--light-fg) +} + +.token.punctuation { + color: var(--fg); +} + +.token.tag, +.token.attr-name, +.token.namespace, +.token.deleted { + color:var(--pink); +} + +.token.function-name { + color: var(--blue); +} + +.token.boolean, +.token.number, +.token.function { + color: var(--red); +} + +.token.property, +.token.class-name, +.token.constant, +.token.symbol { + color: var(--code-property-color); +} + +.token.selector, +.token.important, +.token.atrule, +.token.keyword, +.token.builtin { + color: var(--code-keyword-color); +} + +.token.string, +.token.char, +.token.attr-value, +.token.regex, +.token.variable { + color: var(--green); +} + +.token.operator, +.token.entity, +.token.url { + color: var(--code-operator-color); +} + +.token.important, +.token.bold { + font-weight: bold; +} +.token.italic { + font-style: italic; +} + +.token.entity { + cursor: help; +} + +.token.inserted { + color: green; +} +`,Ze=l` +.tab-panel { + border: none; +} +.tab-buttons { + height:30px; + border-bottom: 1px solid var(--light-border-color) ; + align-items: stretch; + overflow-y: hidden; + overflow-x: auto; + scrollbar-width: thin; +} +.tab-buttons::-webkit-scrollbar { + height: 1px; + background-color: var(--border-color); +} +.tab-btn { + border: none; + border-bottom: 3px solid transparent; + color: var(--light-fg); + background-color: transparent; + white-space: nowrap; + cursor:pointer; + outline:none; + font-family:var(--font-regular); + font-size:var(--font-size-small); + margin-right:16px; + padding:1px; +} +.tab-btn.active { + border-bottom: 3px solid var(--primary-color); + font-weight:bold; + color:var(--primary-color); +} + +.tab-btn:hover { + color:var(--primary-color); +} +.tab-content { + margin:-1px 0 0 0; + position:relative; + min-height: 50px; +} +`,Qe=l` +.nav-bar { + width:0; + height:100%; + overflow: hidden; + color:var(--nav-text-color); + background-color: var(--nav-bg-color); + background-blend-mode: multiply; + line-height: calc(var(--font-size-small) + 4px); + display:none; + position:relative; + flex-direction:column; + flex-wrap:nowrap; + word-break:break-word; +} +::slotted([slot=nav-logo]){ + padding:16px 16px 0 16px; +} +.nav-scroll { + overflow-x: hidden; + overflow-y: auto; + overflow-y: overlay; + scrollbar-width: thin; + scrollbar-color: var(--nav-hover-bg-color) transparent; +} + +.nav-bar-tag { + display: flex; + align-items: center; + justify-content: space-between; + flex-direction: row; +} +.nav-bar.read .nav-bar-tag-icon { + display:none; +} + +.nav-bar-tag-icon { + color: var(--nav-text-color); + font-size: 20px; +} +.nav-bar-tag-icon:hover { + color:var(--nav-hover-text-color); +} +.nav-bar.focused .nav-bar-tag-and-paths.collapsed .nav-bar-paths-under-tag { + display:none; +} +.nav-bar.focused .nav-bar-tag-and-paths.collapsed .nav-bar-tag-icon::after { + content: '⌵'; + width:16px; + height:16px; + text-align: center; + display: inline-block; + transform: rotate(-90deg); + transition: transform 0.2s ease-out 0s; +} +.nav-bar.focused .nav-bar-tag-and-paths.expanded .nav-bar-tag-icon::after { + content: '⌵'; + width:16px; + height:16px; + text-align: center; + display: inline-block; + transition: transform 0.2s ease-out 0s; +} +.nav-scroll::-webkit-scrollbar { + width: var(--scroll-bar-width, 8px); +} +.nav-scroll::-webkit-scrollbar-track { + background:transparent; +} +.nav-scroll::-webkit-scrollbar-thumb { + background-color: var(--nav-hover-bg-color); +} + +.nav-bar-tag { + font-size: var(--font-size-regular); + color: var(--nav-accent-color); + border-left:4px solid transparent; + font-weight:bold; + padding: 15px 15px 15px 10px; + text-transform: capitalize; +} + +.nav-bar-components, +.nav-bar-h1, +.nav-bar-h2, +.nav-bar-info, +.nav-bar-tag, +.nav-bar-path { + display:flex; + cursor:pointer; + border-left:4px solid transparent; +} + +.nav-bar-h1, +.nav-bar-h2, +.nav-bar-path { + font-size: calc(var(--font-size-small) + 1px); + padding: var(--nav-item-padding); +} +.nav-bar-path.small-font { + font-size: var(--font-size-small); +} + +.nav-bar-info { + font-size: var(--font-size-regular); + padding: 16px 10px; + font-weight:bold; +} +.nav-bar-section { + display: flex; + flex-direction: row; + justify-content: space-between; + font-size: var(--font-size-small); + color: var(--nav-text-color); + padding: var(--nav-item-padding); + font-weight:bold; +} +.nav-bar-section.operations { + cursor:pointer; +} +.nav-bar-section.operations:hover { + color:var(--nav-hover-text-color); + background-color:var(--nav-hover-bg-color); +} + +.nav-bar-section:first-child { + display: none; +} +.nav-bar-h2 {margin-left:12px;} + +.nav-bar-h1.active, +.nav-bar-h2.active, +.nav-bar-info.active, +.nav-bar-tag.active, +.nav-bar-path.active, +.nav-bar-section.operations.active { + border-left:4px solid var(--nav-accent-color); + color:var(--nav-hover-text-color); +} + +.nav-bar-h1:hover, +.nav-bar-h2:hover, +.nav-bar-info:hover, +.nav-bar-tag:hover, +.nav-bar-path:hover { + color:var(--nav-hover-text-color); + background-color:var(--nav-hover-bg-color); +} +`,Xe=l` +#api-info { + font-size:calc(var(--font-size-regular) - 1px);margin-top:8px + margin-left: -15px; +} + +#api-info span:before { + content: "|"; + display: inline-block; + opacity: 0.5; + width: 15px; + text-align: center; +} +#api-info span:first-child:before { + content: ""; + width: 0px; +} +`,et=l` + +`;const tt=/[\s#:?&={}]/g,rt="_rapidoc_api_key";function nt(e){return new Promise((t=>setTimeout(t,e)))}function at(e,t){const r=t.currentTarget,n=document.createElement("textarea");n.value=e,n.style.position="fixed",document.body.appendChild(n),n.focus(),n.select();try{document.execCommand("copy"),r.innerText="Copied",setTimeout((()=>{r.innerText="Copy"}),5e3)}catch(e){console.error("Unable to copy",e)}document.body.removeChild(n)}function ot(e,t,r="includes"){if("includes"===r){return`${t.method} ${t.path} ${t.summary||t.description||""} ${t.operationId||""}`.toLowerCase().includes(e.toLowerCase())}return new RegExp(e,"i").test(`${t.method} ${t.path}`)}function it(e,t=new Set){return e?(Object.keys(e).forEach((r=>{var n;if(t.add(r),e[r].properties)it(e[r].properties,t);else if(null!==(n=e[r].items)&&void 0!==n&&n.properties){var a;it(null===(a=e[r].items)||void 0===a?void 0:a.properties,t)}})),t):t}function st(e,t){if(e){const r=document.createElement("a");document.body.appendChild(r),r.style="display: none",r.href=e,r.download=t,r.click(),r.remove()}}function lt(e){if(e){const t=document.createElement("a");document.body.appendChild(t),t.style="display: none",t.href=e,t.target="_blank",t.click(),t.remove()}}var ct=r(764).Buffer;function pt(e){if(e.__esModule)return e;var t=Object.defineProperty({},"__esModule",{value:!0});return Object.keys(e).forEach((function(r){var n=Object.getOwnPropertyDescriptor(e,r);Object.defineProperty(t,r,n.get?n:{enumerable:!0,get:function(){return e[r]}})})),t}var ut=function(e){return e&&e.Math==Math&&e},dt=ut("object"==typeof globalThis&&globalThis)||ut("object"==typeof window&&window)||ut("object"==typeof self&&self)||ut("object"==typeof dt&&dt)||function(){return this}()||Function("return this")(),ht=function(e){try{return!!e()}catch(e){return!0}},ft=!ht((function(){var e=function(){}.bind();return"function"!=typeof e||e.hasOwnProperty("prototype")})),mt=ft,yt=Function.prototype,gt=yt.apply,vt=yt.call,bt="object"==typeof Reflect&&Reflect.apply||(mt?vt.bind(gt):function(){return vt.apply(gt,arguments)}),xt=ft,wt=Function.prototype,$t=wt.bind,kt=wt.call,St=xt&&$t.bind(kt,kt),At=xt?function(e){return e&&St(e)}:function(e){return e&&function(){return kt.apply(e,arguments)}},Et=function(e){return"function"==typeof e},Ot={},Tt=!ht((function(){return 7!=Object.defineProperty({},1,{get:function(){return 7}})[1]})),Ct=ft,_t=Function.prototype.call,jt=Ct?_t.bind(_t):function(){return _t.apply(_t,arguments)},It={},Pt={}.propertyIsEnumerable,Rt=Object.getOwnPropertyDescriptor,Lt=Rt&&!Pt.call({1:2},1);It.f=Lt?function(e){var t=Rt(this,e);return!!t&&t.enumerable}:Pt;var Dt,Bt,Ft=function(e,t){return{enumerable:!(1&e),configurable:!(2&e),writable:!(4&e),value:t}},Nt=At,zt=Nt({}.toString),Ut=Nt("".slice),qt=function(e){return Ut(zt(e),8,-1)},Mt=At,Ht=ht,Wt=qt,Vt=dt.Object,Gt=Mt("".split),Kt=Ht((function(){return!Vt("z").propertyIsEnumerable(0)}))?function(e){return"String"==Wt(e)?Gt(e,""):Vt(e)}:Vt,Jt=dt.TypeError,Yt=function(e){if(null==e)throw Jt("Can't call method on "+e);return e},Zt=Kt,Qt=Yt,Xt=function(e){return Zt(Qt(e))},er=Et,tr=function(e){return"object"==typeof e?null!==e:er(e)},rr={},nr=rr,ar=dt,or=Et,ir=function(e){return or(e)?e:void 0},sr=function(e,t){return arguments.length<2?ir(nr[e])||ir(ar[e]):nr[e]&&nr[e][t]||ar[e]&&ar[e][t]},lr=At({}.isPrototypeOf),cr=sr("navigator","userAgent")||"",pr=dt,ur=cr,dr=pr.process,hr=pr.Deno,fr=dr&&dr.versions||hr&&hr.version,mr=fr&&fr.v8;mr&&(Bt=(Dt=mr.split("."))[0]>0&&Dt[0]<4?1:+(Dt[0]+Dt[1])),!Bt&&ur&&(!(Dt=ur.match(/Edge\/(\d+)/))||Dt[1]>=74)&&(Dt=ur.match(/Chrome\/(\d+)/))&&(Bt=+Dt[1]);var yr=Bt,gr=yr,vr=ht,br=!!Object.getOwnPropertySymbols&&!vr((function(){var e=Symbol();return!String(e)||!(Object(e)instanceof Symbol)||!Symbol.sham&&gr&&gr<41})),xr=br&&!Symbol.sham&&"symbol"==typeof Symbol.iterator,wr=sr,$r=Et,kr=lr,Sr=xr,Ar=dt.Object,Er=Sr?function(e){return"symbol"==typeof e}:function(e){var t=wr("Symbol");return $r(t)&&kr(t.prototype,Ar(e))},Or=dt.String,Tr=function(e){try{return Or(e)}catch(e){return"Object"}},Cr=Et,_r=Tr,jr=dt.TypeError,Ir=function(e){if(Cr(e))return e;throw jr(_r(e)+" is not a function")},Pr=Ir,Rr=function(e,t){var r=e[t];return null==r?void 0:Pr(r)},Lr=jt,Dr=Et,Br=tr,Fr=dt.TypeError,Nr={exports:{}},zr=dt,Ur=Object.defineProperty,qr=function(e,t){try{Ur(zr,e,{value:t,configurable:!0,writable:!0})}catch(r){zr[e]=t}return t},Mr="__core-js_shared__",Hr=dt[Mr]||qr(Mr,{}),Wr=Hr;(Nr.exports=function(e,t){return Wr[e]||(Wr[e]=void 0!==t?t:{})})("versions",[]).push({version:"3.21.1",mode:"pure",copyright:"© 2014-2022 Denis Pushkarev (zloirock.ru)",license:"https://github.com/zloirock/core-js/blob/v3.21.1/LICENSE",source:"https://github.com/zloirock/core-js"});var Vr=Yt,Gr=dt.Object,Kr=function(e){return Gr(Vr(e))},Jr=Kr,Yr=At({}.hasOwnProperty),Zr=Object.hasOwn||function(e,t){return Yr(Jr(e),t)},Qr=At,Xr=0,en=Math.random(),tn=Qr(1..toString),rn=function(e){return"Symbol("+(void 0===e?"":e)+")_"+tn(++Xr+en,36)},nn=dt,an=Nr.exports,on=Zr,sn=rn,ln=br,cn=xr,pn=an("wks"),un=nn.Symbol,dn=un&&un.for,hn=cn?un:un&&un.withoutSetter||sn,fn=function(e){if(!on(pn,e)||!ln&&"string"!=typeof pn[e]){var t="Symbol."+e;ln&&on(un,e)?pn[e]=un[e]:pn[e]=cn&&dn?dn(t):hn(t)}return pn[e]},mn=jt,yn=tr,gn=Er,vn=Rr,bn=function(e,t){var r,n;if("string"===t&&Dr(r=e.toString)&&!Br(n=Lr(r,e)))return n;if(Dr(r=e.valueOf)&&!Br(n=Lr(r,e)))return n;if("string"!==t&&Dr(r=e.toString)&&!Br(n=Lr(r,e)))return n;throw Fr("Can't convert object to primitive value")},xn=fn,wn=dt.TypeError,$n=xn("toPrimitive"),kn=function(e,t){if(!yn(e)||gn(e))return e;var r,n=vn(e,$n);if(n){if(void 0===t&&(t="default"),r=mn(n,e,t),!yn(r)||gn(r))return r;throw wn("Can't convert object to primitive value")}return void 0===t&&(t="number"),bn(e,t)},Sn=Er,An=function(e){var t=kn(e,"string");return Sn(t)?t:t+""},En=tr,On=dt.document,Tn=En(On)&&En(On.createElement),Cn=function(e){return Tn?On.createElement(e):{}},_n=Cn,jn=!Tt&&!ht((function(){return 7!=Object.defineProperty(_n("div"),"a",{get:function(){return 7}}).a})),In=Tt,Pn=jt,Rn=It,Ln=Ft,Dn=Xt,Bn=An,Fn=Zr,Nn=jn,zn=Object.getOwnPropertyDescriptor;Ot.f=In?zn:function(e,t){if(e=Dn(e),t=Bn(t),Nn)try{return zn(e,t)}catch(e){}if(Fn(e,t))return Ln(!Pn(Rn.f,e,t),e[t])};var Un=ht,qn=Et,Mn=/#|\.prototype\./,Hn=function(e,t){var r=Vn[Wn(e)];return r==Kn||r!=Gn&&(qn(t)?Un(t):!!t)},Wn=Hn.normalize=function(e){return String(e).replace(Mn,".").toLowerCase()},Vn=Hn.data={},Gn=Hn.NATIVE="N",Kn=Hn.POLYFILL="P",Jn=Hn,Yn=Ir,Zn=ft,Qn=At(At.bind),Xn=function(e,t){return Yn(e),void 0===t?e:Zn?Qn(e,t):function(){return e.apply(t,arguments)}},ea={},ta=Tt&&ht((function(){return 42!=Object.defineProperty((function(){}),"prototype",{value:42,writable:!1}).prototype})),ra=dt,na=tr,aa=ra.String,oa=ra.TypeError,ia=function(e){if(na(e))return e;throw oa(aa(e)+" is not an object")},sa=Tt,la=jn,ca=ta,pa=ia,ua=An,da=dt.TypeError,ha=Object.defineProperty,fa=Object.getOwnPropertyDescriptor,ma="enumerable",ya="configurable",ga="writable";ea.f=sa?ca?function(e,t,r){if(pa(e),t=ua(t),pa(r),"function"==typeof e&&"prototype"===t&&"value"in r&&ga in r&&!r.writable){var n=fa(e,t);n&&n.writable&&(e[t]=r.value,r={configurable:ya in r?r.configurable:n.configurable,enumerable:ma in r?r.enumerable:n.enumerable,writable:!1})}return ha(e,t,r)}:ha:function(e,t,r){if(pa(e),t=ua(t),pa(r),la)try{return ha(e,t,r)}catch(e){}if("get"in r||"set"in r)throw da("Accessors not supported");return"value"in r&&(e[t]=r.value),e};var va=ea,ba=Ft,xa=Tt?function(e,t,r){return va.f(e,t,ba(1,r))}:function(e,t,r){return e[t]=r,e},wa=dt,$a=bt,ka=At,Sa=Et,Aa=Ot.f,Ea=Jn,Oa=rr,Ta=Xn,Ca=xa,_a=Zr,ja=function(e){var t=function(r,n,a){if(this instanceof t){switch(arguments.length){case 0:return new e;case 1:return new e(r);case 2:return new e(r,n)}return new e(r,n,a)}return $a(e,this,arguments)};return t.prototype=e.prototype,t},Ia=function(e,t){var r,n,a,o,i,s,l,c,p=e.target,u=e.global,d=e.stat,h=e.proto,f=u?wa:d?wa[p]:(wa[p]||{}).prototype,m=u?Oa:Oa[p]||Ca(Oa,p,{})[p],y=m.prototype;for(a in t)r=!Ea(u?a:p+(d?".":"#")+a,e.forced)&&f&&_a(f,a),i=m[a],r&&(s=e.noTargetGet?(c=Aa(f,a))&&c.value:f[a]),o=r&&s?s:t[a],r&&typeof i==typeof o||(l=e.bind&&r?Ta(o,wa):e.wrap&&r?ja(o):h&&Sa(o)?ka(o):o,(e.sham||o&&o.sham||i&&i.sham)&&Ca(l,"sham",!0),Ca(m,a,l),h&&(_a(Oa,n=p+"Prototype")||Ca(Oa,n,{}),Ca(Oa[n],a,o),e.real&&y&&!y[a]&&Ca(y,a,o)))},Pa=Math.ceil,Ra=Math.floor,La=function(e){var t=+e;return t!=t||0===t?0:(t>0?Ra:Pa)(t)},Da=La,Ba=Math.max,Fa=Math.min,Na=function(e,t){var r=Da(e);return r<0?Ba(r+t,0):Fa(r,t)},za=La,Ua=Math.min,qa=function(e){return e>0?Ua(za(e),9007199254740991):0},Ma=qa,Ha=function(e){return Ma(e.length)},Wa=Xt,Va=Na,Ga=Ha,Ka=function(e){return function(t,r,n){var a,o=Wa(t),i=Ga(o),s=Va(n,i);if(e&&r!=r){for(;i>s;)if((a=o[s++])!=a)return!0}else for(;i>s;s++)if((e||s in o)&&o[s]===r)return e||s||0;return!e&&-1}},Ja={includes:Ka(!0),indexOf:Ka(!1)},Ya={},Za=Zr,Qa=Xt,Xa=Ja.indexOf,eo=Ya,to=At([].push),ro=function(e,t){var r,n=Qa(e),a=0,o=[];for(r in n)!Za(eo,r)&&Za(n,r)&&to(o,r);for(;t.length>a;)Za(n,r=t[a++])&&(~Xa(o,r)||to(o,r));return o},no=["constructor","hasOwnProperty","isPrototypeOf","propertyIsEnumerable","toLocaleString","toString","valueOf"],ao=ro,oo=no,io=Object.keys||function(e){return ao(e,oo)},so=Kr,lo=io;Ia({target:"Object",stat:!0,forced:ht((function(){lo(1)}))},{keys:function(e){return lo(so(e))}});var co=rr.Object.keys,po=co,uo=qt,ho=Array.isArray||function(e){return"Array"==uo(e)},fo={};fo[fn("toStringTag")]="z";var mo="[object z]"===String(fo),yo=dt,go=mo,vo=Et,bo=qt,xo=fn("toStringTag"),wo=yo.Object,$o="Arguments"==bo(function(){return arguments}()),ko=go?bo:function(e){var t,r,n;return void 0===e?"Undefined":null===e?"Null":"string"==typeof(r=function(e,t){try{return e[t]}catch(e){}}(t=wo(e),xo))?r:$o?bo(t):"Object"==(n=bo(t))&&vo(t.callee)?"Arguments":n},So=ko,Ao=dt.String,Eo=function(e){if("Symbol"===So(e))throw TypeError("Cannot convert a Symbol value to a string");return Ao(e)},Oo={},To=Tt,Co=ta,_o=ea,jo=ia,Io=Xt,Po=io;Oo.f=To&&!Co?Object.defineProperties:function(e,t){jo(e);for(var r,n=Io(t),a=Po(t),o=a.length,i=0;o>i;)_o.f(e,r=a[i++],n[r]);return e};var Ro,Lo=sr("document","documentElement"),Do=Nr.exports,Bo=rn,Fo=Do("keys"),No=function(e){return Fo[e]||(Fo[e]=Bo(e))},zo=ia,Uo=Oo,qo=no,Mo=Ya,Ho=Lo,Wo=Cn,Vo=No("IE_PROTO"),Go=function(){},Ko=function(e){return" + + + + + + \ No newline at end of file diff --git a/docs/openapi.json b/docs/openapi.json new file mode 100644 index 00000000..cc7498e3 --- /dev/null +++ b/docs/openapi.json @@ -0,0 +1,334 @@ +{ + "openapi": "3.0.0", + "info": { + "title": "paopao-ce API", + "description": "# The paopao-ce API documentation\n\nWelcome to the paopao-ce API documentation!\n\n", + "version": "0.1.0" + }, + "servers": [ + { + "url": "https://api.paopao.info" + } + ], + "tags": [], + "paths": { + "/{bucket}": { + "post": { + "summary": "Upload Image", + "description": "Upload an image to the given bucket.\nThe `content-type` header must be provided as well\nas the `content-length` header otherwise the request will be rejected.\n\nThe uploaded file must also not exceed the given `content-length`.", + "parameters": [ + { + "name": "bucket", + "schema": { + "type": "string" + }, + "in": "path", + "description": "The bucket that the image should be uploaded.", + "required": true, + "deprecated": false + }, + { + "name": "content-length", + "schema": { + "type": "integer", + "format": "uint64" + }, + "in": "header", + "description": "The total size of the image in bytes.", + "required": true, + "deprecated": false + }, + { + "name": "format", + "schema": { + "$ref": "#/components/schemas/ImageKind" + }, + "in": "query", + "description": "The format that the uploaded image is encoded in.\n\nIf not provided, lust will guess the encoding.", + "required": false, + "deprecated": false + } + ], + "requestBody": { + "content": { + "application/octet-stream": { + "schema": { + "type": "string", + "format": "binary" + } + } + }, + "required": true + }, + "responses": { + "200": { + "description": "", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/UploadInfo" + } + } + } + }, + "404": { + "description": "Bucket not found" + }, + "400": { + "description": "The image format was incorrect or the system was\nunable to guess the format of the image." + }, + "413": { + "description": "The upload exceeds the configured maximum file size." + }, + "401": { + "description": "You are not authorized to complete this action.\n\nThis normally means the `Authorization` bearer has been left out\nof the request or is invalid." + } + } + } + }, + "/{bucket}/{image_id}": { + "get": { + "summary": "Fetch Image", + "description": "Fetch the image from the storage backend and apply and additional affects\nif required.", + "parameters": [ + { + "name": "bucket", + "schema": { + "type": "string" + }, + "in": "path", + "description": "The bucket to try fetch the image from.", + "required": true, + "deprecated": false + }, + { + "name": "image_id", + "schema": { + "type": "string", + "format": "uuid" + }, + "in": "path", + "description": "The id of the image.", + "required": true, + "deprecated": false + }, + { + "name": "format", + "schema": { + "$ref": "#/components/schemas/ImageKind" + }, + "in": "query", + "description": "The encoding format that the image should be returned as.", + "required": false, + "deprecated": false + }, + { + "name": "size", + "schema": { + "type": "string" + }, + "in": "query", + "description": "The size preset that should be used when returning the image.", + "required": false, + "deprecated": false + }, + { + "name": "width", + "schema": { + "type": "integer", + "format": "uint32" + }, + "in": "query", + "description": "A custom width to resize the returned image to.", + "required": false, + "deprecated": false + }, + { + "name": "height", + "schema": { + "type": "integer", + "format": "uint32" + }, + "in": "query", + "description": "A custom height to resize the returned image to.", + "required": false, + "deprecated": false + }, + { + "name": "accept", + "schema": { + "type": "string" + }, + "in": "header", + "description": "A set of `,` seperated content-types that could be sent as a response.\nE.g. `image/png,image/webp,image/gif`", + "required": false, + "deprecated": false + } + ], + "responses": { + "200": { + "description": "", + "content": { + "application/octet-stream": { + "schema": { + "type": "string", + "format": "binary" + } + } + }, + "headers": { + "CONTENT-TYPE": { + "required": true, + "deprecated": false, + "schema": { + "type": "string" + } + } + } + }, + "400": { + "description": "The request is invalid with the current configuration.\n\nSee the detail section for more info.", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/Detail" + } + } + } + }, + "404": { + "description": "Bucket does not exist or image does not exist.\n\nSee the detail section for more info.", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/Detail" + } + } + } + } + } + }, + "delete": { + "summary": "Delete Image", + "description": "Delete the given image.\nThis will purge all variants of the image including sizing presets and formats.\n\nImages that do not exist already will be ignored and will not return a 404.", + "parameters": [ + { + "name": "bucket", + "schema": { + "type": "string" + }, + "in": "path", + "description": "The bucket to try delete the image from.", + "required": true, + "deprecated": false + }, + { + "name": "image_id", + "schema": { + "type": "string", + "format": "uuid" + }, + "in": "path", + "description": "The image to delete try delete.", + "required": true, + "deprecated": false + } + ], + "responses": { + "200": { + "description": "" + }, + "401": { + "description": "You are not authorized to complete this action.\n\nThis normally means the `Authorization` bearer has been left out\nof the request or is invalid." + }, + "404": { + "description": "Bucket does not exist." + } + } + } + } + }, + "components": { + "schemas": { + "Detail": { + "type": "object", + "required": [ + "detail" + ], + "properties": { + "detail": { + "type": "string", + "description": "Additional information regarding the response." + } + } + }, + "ImageKind": { + "type": "string", + "enum": [ + "png", + "jpeg", + "webp", + "gif" + ] + }, + "ImageUploadInfo": { + "type": "object", + "required": [ + "sizing_id" + ], + "properties": { + "sizing_id": { + "type": "integer", + "format": "uint32", + "description": "The computed image sizing id.\n\nThis is useful for tracking files outside of lust as this is\ngenerally used for filtering within the storage systems." + } + } + }, + "UploadInfo": { + "type": "object", + "required": [ + "image_id", + "processing_time", + "io_time", + "checksum", + "images", + "bucket_id" + ], + "properties": { + "image_id": { + "type": "string", + "format": "uuid", + "description": "The generated ID for the file.\n\nThis can be used to access the file for the given bucket." + }, + "processing_time": { + "type": "number", + "format": "float", + "description": "The time spent processing the image in seconds." + }, + "io_time": { + "type": "number", + "format": "float", + "description": "The time spent uploading the image to the persistent store." + }, + "checksum": { + "type": "integer", + "format": "uint32", + "description": "The crc32 checksum of the uploaded image." + }, + "images": { + "type": "array", + "description": "The information that is specific to the image.", + "items": { + "$ref": "#/components/schemas/ImageUploadInfo" + } + }, + "bucket_id": { + "type": "integer", + "format": "uint32", + "description": "The id of the bucket the image was stored in.\n\nThis is useful for tracking files outside of lust as this is\ngenerally used for filtering within the storage systems." + } + } + } + } + } + } \ No newline at end of file diff --git a/internal/routers/docs.go b/internal/routers/docs.go new file mode 100644 index 00000000..fc054b08 --- /dev/null +++ b/internal/routers/docs.go @@ -0,0 +1,13 @@ +//go:build !docs +// +build !docs + +package routers + +import ( + "github.com/gin-gonic/gin" +) + +// registerDocs stub function for register docs asset route +func registerDocs(e *gin.Engine) { + // empty +} diff --git a/internal/routers/docs_embed.go b/internal/routers/docs_embed.go new file mode 100644 index 00000000..50fe27c4 --- /dev/null +++ b/internal/routers/docs_embed.go @@ -0,0 +1,14 @@ +//go:build docs +// +build docs + +package routers + +import ( + "github.com/gin-gonic/gin" + "github.com/rocboss/paopao-ce/docs" +) + +// registerDocs register docs asset route +func registerDocs(e *gin.Engine) { + e.StaticFS("/docs", docs.NewFileSystem()) +} diff --git a/internal/routers/router.go b/internal/routers/router.go index 9802a5c6..2a478664 100644 --- a/internal/routers/router.go +++ b/internal/routers/router.go @@ -24,8 +24,9 @@ func NewRouter() *gin.Engine { corsConfig.AddAllowHeaders("Authorization") e.Use(cors.New(corsConfig)) - // 按需注册 静态资源、LocalOSS 路由 + // 按需注册 docs、静态资源、LocalOSS 路由 { + registerDocs(e) registerStatick(e) routeLocalOSS(e) } From 239852c56e43c2cf2bff8b1e234d7b8d79bff088 Mon Sep 17 00:00:00 2001 From: alimy Date: Sun, 12 Jun 2022 10:27:40 +0800 Subject: [PATCH 02/35] remove no-need build version info --- Makefile | 3 +-- main.go | 4 +++- version | 0 3 files changed, 4 insertions(+), 3 deletions(-) delete mode 100644 version diff --git a/Makefile b/Makefile index d2090ed6..b505032b 100644 --- a/Makefile +++ b/Makefile @@ -8,12 +8,11 @@ RELEASE_DARWIN_AMD64 = $(RELEASE_ROOT)/darwin-amd64/$(TARGET) RELEASE_DARWIN_ARM64 = $(RELEASE_ROOT)/darwin-arm64/$(TARGET) RELEASE_WINDOWS_AMD64 = $(RELEASE_ROOT)/windows-amd64/$(TARGET) -BUILD_VERSION := $(shell cat version) BUILD_DATE := $(shell date +'%Y-%m-%d %H:%M:%S') SHA_SHORT := $(shell git rev-parse --short HEAD) TAGS = "" -LDFLAGS = -X "main.version=${BUILD_VERSION}" -X "main.buildDate=${BUILD_DATE}" -X "main.commitID=${SHA_SHORT}" -w -s +LDFLAGS = -X "main.buildDate=${BUILD_DATE}" -X "main.commitID=${SHA_SHORT}" -w -s all: fmt build diff --git a/main.go b/main.go index 2477b350..dbf61725 100644 --- a/main.go +++ b/main.go @@ -15,7 +15,9 @@ import ( ) var ( - version, buildDate, commitID string + version = "v0.2.0" + buildDate string + commitID string noDefaultFeatures bool features suites diff --git a/version b/version deleted file mode 100644 index e69de29b..00000000 From 2c2c5d724a35446b89099e8a240fa88c9173f00b Mon Sep 17 00:00:00 2001 From: alimy Date: Sun, 12 Jun 2022 10:38:10 +0800 Subject: [PATCH 03/35] optimize #88 add title --- docs/index.html | 2 ++ 1 file changed, 2 insertions(+) diff --git a/docs/index.html b/docs/index.html index dd3895b4..2fcd59c8 100644 --- a/docs/index.html +++ b/docs/index.html @@ -3,6 +3,8 @@ + + paopao-ce develop documents \ No newline at end of file + + + \ No newline at end of file From 54da8cdf35c7a318bd92861b24039c57498ed25d Mon Sep 17 00:00:00 2001 From: orzi! <1063614727@qq.com> Date: Wed, 15 Jun 2022 17:20:39 +0800 Subject: [PATCH 17/35] chang type --- web/src/components/compose.vue | 2 +- web/src/components/post-detail.vue | 2 +- web/src/types/NetParams.d.ts | 8 ++++---- web/src/types/NetReq.d.ts | 4 ++-- web/src/types/item.d.ts | 7 +++++-- 5 files changed, 13 insertions(+), 10 deletions(-) diff --git a/web/src/components/compose.vue b/web/src/components/compose.vue index 74811c15..0ab60c9f 100644 --- a/web/src/components/compose.vue +++ b/web/src/components/compose.vue @@ -297,7 +297,7 @@ const fileQueue = ref([]); const imageContents = ref([]); const videoContents = ref([]); const attachmentContents = ref([]); -const visitType = ref<0 | 1 | 2>(0) +const visitType = ref(0) const visibilities = [{value: 0, label: "公开"}, {value: 1, label: "私密"}, {value: 2, label: "好友可见"}] const uploadGateway = import.meta.env.VITE_HOST + '/v1/attachment'; diff --git a/web/src/components/post-detail.vue b/web/src/components/post-detail.vue index 69e8216b..c8879d45 100644 --- a/web/src/components/post-detail.vue +++ b/web/src/components/post-detail.vue @@ -235,7 +235,7 @@ const showLockModal = ref(false); const showStickModal = ref(false); const showVisibilityModal = ref(false); const loading = ref(false); -const tempVisibility = ref<0 | 1 | 2>(0); +const tempVisibility = ref(0); const emit = defineEmits<{ (e: 'reload'): void; diff --git a/web/src/types/NetParams.d.ts b/web/src/types/NetParams.d.ts index b6371564..fb507498 100644 --- a/web/src/types/NetParams.d.ts +++ b/web/src/types/NetParams.d.ts @@ -126,8 +126,8 @@ declare module NetParams { interface PostVisibilityPost { id: number, - /** 可见性 0公开 1私密 2好友可见 */ - visibility: 0 | 1 | 2 + /** 可见性:0为公开,1为私密,2为好友可见 */ + visibility: Item.VisibilityStatus } interface PostGetPostStar { @@ -164,8 +164,8 @@ declare module NetParams { users: string[], /** 附件价格 */ attachment_price: number, - /** 可见性 0公开 1私密 2好友可见 */ - visibility: 0 | 1 | 2 + /** 可见性:0为公开,1为私密,2为好友可见 */ + visibility: Item.VisibilityStatus } interface PostDeletePost { diff --git a/web/src/types/NetReq.d.ts b/web/src/types/NetReq.d.ts index ffc6c55d..bc5b4fcf 100644 --- a/web/src/types/NetReq.d.ts +++ b/web/src/types/NetReq.d.ts @@ -117,8 +117,8 @@ declare module NetReq { } interface PostVisibilityPost { - /** 可见性 0公开 1私密 2好友可见 */ - visibility_status: 0 | 1 | 2 + /** 可见性:0为公开,1为私密,2为好友可见 */ + visibility_status: Item.VisibilityStatus } interface PostGetPostStar { diff --git a/web/src/types/item.d.ts b/web/src/types/item.d.ts index bc369592..5a4a18e8 100644 --- a/web/src/types/item.d.ts +++ b/web/src/types/item.d.ts @@ -1,5 +1,8 @@ declare module Item { + /** 可见性:0为公开,1为私密,2为好友可见 */ + type VisibilityStatus = 0 | 1 | 2; + interface UserInfo { /** 用户UID */ id: number, @@ -161,8 +164,8 @@ declare module Item { contents: PostItemProps[], /** 标签列表 */ tags: { [key: string]: number } | string, - /** 可见性 0公开 1私密 2好友可见 */ - visibility: 0 | 1 | 2, + /** 可见性:0为公开,1为私密,2为好友可见 */ + visibility: VisibilityStatus, /** 是否锁定 */ is_lock: number, /** 是否置顶 */ From d33d7b4c5711e27429ff84cfc19782917a204475 Mon Sep 17 00:00:00 2001 From: orzi! <1063614727@qq.com> Date: Wed, 15 Jun 2022 19:34:42 +0800 Subject: [PATCH 18/35] type -> enum --- web/src/components/compose.vue | 21 +++++---- web/src/components/post-detail.vue | 11 ++--- web/src/main.ts | 2 +- web/src/types/NetParams.d.ts | 4 +- web/src/types/NetReq.d.ts | 2 +- web/src/types/item.d.ts | 13 +++--- web/src/utils/IEnum.ts | 69 ++++++++++++++++++++++++++++++ web/src/views/Setting.vue | 2 +- 8 files changed, 98 insertions(+), 26 deletions(-) create mode 100644 web/src/utils/IEnum.ts diff --git a/web/src/components/compose.vue b/web/src/components/compose.vue index 0ab60c9f..ca4409b6 100644 --- a/web/src/components/compose.vue +++ b/web/src/components/compose.vue @@ -273,6 +273,7 @@ import { import { createPost } from '@/api/post'; import { parsePostTag } from '@/utils/content'; import type { MentionOption, UploadFileInfo, UploadInst } from 'naive-ui'; +import { VisibilityEnum, PostItemTypeEnum } from '@/utils/IEnum'; @@ -297,8 +298,12 @@ const fileQueue = ref([]); const imageContents = ref([]); const videoContents = ref([]); const attachmentContents = ref([]); -const visitType = ref(0) -const visibilities = [{value: 0, label: "公开"}, {value: 1, label: "私密"}, {value: 2, label: "好友可见"}] +const visitType = ref(VisibilityEnum.PUBLIC); +const visibilities = [ + {value: VisibilityEnum.PUBLIC, label: "公开"} + , {value: VisibilityEnum.PRIVATE, label: "私密"} + , {value: VisibilityEnum.FRIEND, label: "好友可见"} +]; const uploadGateway = import.meta.env.VITE_HOST + '/v1/attachment'; const uploadToken = ref(); @@ -505,7 +510,7 @@ const submitPost = () => { contents.push({ content: content.value, - type: 2, // 文字 + type: PostItemTypeEnum.TEXT, // 文字 sort, }); @@ -513,7 +518,7 @@ const submitPost = () => { sort++; contents.push({ content: img.content, - type: 3, // 图片 + type: PostItemTypeEnum.IMAGEURL, // 图片 sort, }); }); @@ -521,7 +526,7 @@ const submitPost = () => { sort++; contents.push({ content: video.content, - type: 4, // 图片 + type: PostItemTypeEnum.VIDEOURL, // 视频 sort, }); }); @@ -529,7 +534,7 @@ const submitPost = () => { sort++; contents.push({ content: attachment.content, - type: 7, // 附件 + type: PostItemTypeEnum.ATTACHMENT, // 附件 sort, }); }); @@ -538,7 +543,7 @@ const submitPost = () => { sort++; contents.push({ content: link, - type: 6, // 链接 + type: PostItemTypeEnum.LINKURL, // 链接 sort, }); }); @@ -567,7 +572,7 @@ const submitPost = () => { imageContents.value = []; videoContents.value = []; attachmentContents.value = []; - visitType.value = 0; + visitType.value = VisibilityEnum.PUBLIC; }) .catch((err) => { submitting.value = false; diff --git a/web/src/components/post-detail.vue b/web/src/components/post-detail.vue index c8879d45..65a7cbec 100644 --- a/web/src/components/post-detail.vue +++ b/web/src/components/post-detail.vue @@ -26,7 +26,7 @@ 置顶 (0); +const tempVisibility = ref(VisibilityEnum.PUBLIC); const emit = defineEmits<{ (e: 'reload'): void; @@ -314,7 +315,7 @@ const adminOptions = computed(() => { }); } } - if (post.value.visibility === 0) { + if (post.value.visibility === VisibilityEnum.PUBLIC) { options.push({ label: '公开', key: 'vpublic', @@ -323,7 +324,7 @@ const adminOptions = computed(() => { , { label: '好友可见', key: 'vfriend' } ] }) - } else if (post.value.visibility === 1) { + } else if (post.value.visibility === VisibilityEnum.PRIVATE) { options.push({ label: '私密', key: 'vprivate', diff --git a/web/src/main.ts b/web/src/main.ts index 1040e04c..9385ea3a 100644 --- a/web/src/main.ts +++ b/web/src/main.ts @@ -17,4 +17,4 @@ declare global { $message: MessageApiInjection, $store: any } -} \ No newline at end of file +} diff --git a/web/src/types/NetParams.d.ts b/web/src/types/NetParams.d.ts index fb507498..854787d1 100644 --- a/web/src/types/NetParams.d.ts +++ b/web/src/types/NetParams.d.ts @@ -127,7 +127,7 @@ declare module NetParams { interface PostVisibilityPost { id: number, /** 可见性:0为公开,1为私密,2为好友可见 */ - visibility: Item.VisibilityStatus + visibility: import('@/utils/IEnum').VisibilityEnum } interface PostGetPostStar { @@ -165,7 +165,7 @@ declare module NetParams { /** 附件价格 */ attachment_price: number, /** 可见性:0为公开,1为私密,2为好友可见 */ - visibility: Item.VisibilityStatus + visibility: import('@/utils/IEnum').VisibilityEnum } interface PostDeletePost { diff --git a/web/src/types/NetReq.d.ts b/web/src/types/NetReq.d.ts index bc5b4fcf..59c3bec2 100644 --- a/web/src/types/NetReq.d.ts +++ b/web/src/types/NetReq.d.ts @@ -118,7 +118,7 @@ declare module NetReq { interface PostVisibilityPost { /** 可见性:0为公开,1为私密,2为好友可见 */ - visibility_status: Item.VisibilityStatus + visibility_status: import('@/utils/IEnum').VisibilityEnum } interface PostGetPostStar { diff --git a/web/src/types/item.d.ts b/web/src/types/item.d.ts index 5a4a18e8..818557a9 100644 --- a/web/src/types/item.d.ts +++ b/web/src/types/item.d.ts @@ -1,8 +1,5 @@ declare module Item { - /** 可见性:0为公开,1为私密,2为好友可见 */ - type VisibilityStatus = 0 | 1 | 2; - interface UserInfo { /** 用户UID */ id: number, @@ -31,7 +28,7 @@ declare module Item { /** 评论者UID */ user_id: number, /** 类别:1为标题,2为文字段落,3为图片地址,4为视频地址,5为语音地址,6为链接地址 */ - type: number, + type: import('@/utils/IEnum').CommentItemTypeEnum, /** 内容 */ content: string, /** 排序,越小越靠前 */ @@ -114,7 +111,7 @@ declare module Item { /** 内容ID */ id: number, /** 类型:1为标题,2为文字段落,3为图片地址,4为视频地址,5为语音地址,6为链接地址,7为附件资源,8为收费资源 */ - type: number, + type: import('@/utils/IEnum').PostItemTypeEnum, /** POST ID */ post_id: number, /** 内容 */ @@ -165,7 +162,7 @@ declare module Item { /** 标签列表 */ tags: { [key: string]: number } | string, /** 可见性:0为公开,1为私密,2为好友可见 */ - visibility: VisibilityStatus, + visibility: import('@/utils/IEnum').VisibilityEnum, /** 是否锁定 */ is_lock: number, /** 是否置顶 */ @@ -195,7 +192,7 @@ declare module Item { interface MessageProps { id: number, /** 类型:1为动态,2为评论,3为回复,4为私信,99为系统通知 */ - type: 1 | 2 | 3 | 4 | 99, + type: import('@/utils/IEnum').MessageTypeEnum, /** 摘要说明 */ brief: string, /** 详细内容 */ @@ -233,7 +230,7 @@ declare module Item { interface AttachmentProps { id: number, /** 类别:1为图片,2为视频,3为其他附件 */ - type: 1 | 2 | 3, + type: import('@/utils/IEnum').AttachmentTypeEnum, /** 发布者用户UID */ user_id: number, /** 发布者用户数据 */ diff --git a/web/src/utils/IEnum.ts b/web/src/utils/IEnum.ts new file mode 100644 index 00000000..0566867a --- /dev/null +++ b/web/src/utils/IEnum.ts @@ -0,0 +1,69 @@ +/** 动态内容类型枚举 */ +export enum PostItemTypeEnum { + /** 标题 */ + TITLE = 1, + /** 文字段落 */ + TEXT = 2, + /** 图片地址 */ + IMAGEURL = 3, + /** 视频地址 */ + VIDEOURL = 4, + /** 音频地址 */ + AUDIOURL = 5, + /** 链接地址 */ + LINKURL = 6, + /** 附件资源 */ + ATTACHMENT = 7, + /** 收费资源 */ + CHARGEATTACHMENT = 8 +} + +/** 回复内容类型枚举 */ +export enum CommentItemTypeEnum { + /** 标题 */ + TITLE = 1, + /** 文字段落 */ + TEXT = 2, + /** 图片地址 */ + IMAGEURL = 3, + /** 视频地址 */ + VIDEOURL = 4, + /** 音频地址 */ + AUDIOURL = 5, + /** 链接地址 */ + LINKURL = 6 +} + +/** 附件类型枚举 */ +export enum AttachmentTypeEnum { + /** 图片 */ + IMAGE = 1, + /** 视频 */ + VIDEO = 2, + /** 其他 */ + OTHER = 3 +} + +/** 消息类型枚举 */ +export enum MessageTypeEnum { + /** 动态 */ + POST = 1, + /** 评论 */ + COMMENT = 2, + /** 回复 */ + REPLY = 3, + /** 私信 */ + PRIVATELETTER = 4, + /** 系统通知 */ + SYSTEMNOTICE = 99 +} + +/** 动态可见度枚举 */ +export enum VisibilityEnum { + /** 公开 */ + PUBLIC, + /** 私密 */ + PRIVATE, + /** 好友可见 */ + FRIEND +} diff --git a/web/src/views/Setting.vue b/web/src/views/Setting.vue index 0567e6cb..6c5be9ae 100644 --- a/web/src/views/Setting.vue +++ b/web/src/views/Setting.vue @@ -102,7 +102,7 @@ From 50048d698619b9538b8839ba7ad9d7c73f172e62 Mon Sep 17 00:00:00 2001 From: orzi! <1063614727@qq.com> Date: Wed, 15 Jun 2022 19:40:04 +0800 Subject: [PATCH 19/35] fixed:type error --- web/src/components/post-detail.vue | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/web/src/components/post-detail.vue b/web/src/components/post-detail.vue index 65a7cbec..586255a1 100644 --- a/web/src/components/post-detail.vue +++ b/web/src/components/post-detail.vue @@ -26,7 +26,7 @@ 置顶 Date: Thu, 16 Jun 2022 11:32:16 +0800 Subject: [PATCH 20/35] optimize #97 user stars/collections update when post visibility changed --- config.yaml.sample | 6 ++--- internal/conf/db.go | 8 +++--- internal/conf/settting.go | 17 ++++++++++++- internal/dao/post.go | 4 +-- internal/model/post_collection.go | 33 ++++++++++++++++--------- internal/model/post_star.go | 41 ++++++++++++++++++------------- internal/routers/api/post.go | 18 +++++++++++--- internal/service/post.go | 25 +++++++++++++++++++ internal/service/user.go | 33 ++++++------------------- 9 files changed, 117 insertions(+), 68 deletions(-) diff --git a/config.yaml.sample b/config.yaml.sample index 93506fb4..524cf9df 100644 --- a/config.yaml.sample +++ b/config.yaml.sample @@ -77,8 +77,8 @@ LocalOSS: # 本地文件OSS存储配置 Bucket: paopao Domain: 127.0.0.1:8008 Database: # Database通用配置 - LogLevel: 2 - TablePrefix: p_ + LogLevel: error # 日志级别 silent|error|warn|info + TablePrefix: p_ # 表名前缀 MySQL: # MySQL数据库 Username: paopao Password: paopao @@ -88,7 +88,7 @@ MySQL: # MySQL数据库 ParseTime: True MaxIdleConns: 10 MaxOpenConns: 30 -Postgres: +Postgres: # PostgreSQL数据库 User: paopao Password: paopao DBName: paopao diff --git a/internal/conf/db.go b/internal/conf/db.go index b517b1c0..649a0627 100644 --- a/internal/conf/db.go +++ b/internal/conf/db.go @@ -23,10 +23,10 @@ func newDBEngine() (*gorm.DB, error) { newLogger := logger.New( logrus.StandardLogger(), // io writer(日志输出的目标,前缀和日志包含的内容) logger.Config{ - SlowThreshold: time.Second, // 慢 SQL 阈值 - LogLevel: databaseSetting.LogLevel, // 日志级别 - IgnoreRecordNotFoundError: true, // 忽略ErrRecordNotFound(记录未找到)错误 - Colorful: false, // 禁用彩色打印 + SlowThreshold: time.Second, // 慢 SQL 阈值 + LogLevel: databaseSetting.logLevel(), // 日志级别 + IgnoreRecordNotFoundError: true, // 忽略ErrRecordNotFound(记录未找到)错误 + Colorful: false, // 禁用彩色打印 }, ) diff --git a/internal/conf/settting.go b/internal/conf/settting.go index 7ac6548e..33cb9131 100644 --- a/internal/conf/settting.go +++ b/internal/conf/settting.go @@ -80,7 +80,7 @@ type ZincSettingS struct { type DatabaseSetingS struct { TablePrefix string - LogLevel logger.LogLevel + LogLevel string } type MySQLSettingS struct { @@ -282,3 +282,18 @@ func (s PostgresSettingS) Dsn() string { } return strings.Join(params, " ") } + +func (s *DatabaseSetingS) logLevel() logger.LogLevel { + switch strings.ToLower(s.LogLevel) { + case "silent": + return logger.Silent + case "error": + return logger.Error + case "warn": + return logger.Warn + case "info": + return logger.Info + default: + return logger.Error + } +} diff --git a/internal/dao/post.go b/internal/dao/post.go index de69205c..328de819 100644 --- a/internal/dao/post.go +++ b/internal/dao/post.go @@ -120,7 +120,7 @@ func (d *dataServant) GetUserPostStars(userID int64, offset, limit int) ([]*mode } return star.List(d.engine, &model.ConditionsT{ - "ORDER": "id DESC", + "ORDER": d.engine.NamingStrategy.TableName("PostStar") + ".id DESC", }, offset, limit) } @@ -159,7 +159,7 @@ func (d *dataServant) GetUserPostCollections(userID int64, offset, limit int) ([ } return collection.List(d.engine, &model.ConditionsT{ - "ORDER": "id DESC", + "ORDER": d.engine.NamingStrategy.TableName("PostCollection") + ".id DESC", }, offset, limit) } diff --git a/internal/model/post_collection.go b/internal/model/post_collection.go index 2c7196ea..aec5d270 100644 --- a/internal/model/post_collection.go +++ b/internal/model/post_collection.go @@ -8,22 +8,26 @@ import ( type PostCollection struct { *Model + Post *Post `json:"-"` PostID int64 `json:"post_id"` UserID int64 `json:"user_id"` } func (p *PostCollection) Get(db *gorm.DB) (*PostCollection, error) { var star PostCollection + tn := db.NamingStrategy.TableName("PostCollection") + "." + if p.Model != nil && p.ID > 0 { - db = db.Where("id = ? AND is_del = ?", p.ID, 0) + db = db.Where(tn+"id = ? AND "+tn+"is_del = ?", p.ID, 0) } if p.PostID > 0 { - db = db.Where("post_id = ?", p.PostID) + db = db.Where(tn+"post_id = ?", p.PostID) } if p.UserID > 0 { - db = db.Where("user_id = ?", p.UserID) + db = db.Where(tn+"user_id = ?", p.UserID) } + db = db.Joins("Post").Where("Post.visibility <> ?", PostVisitPrivate).Order("Post.id DESC") err := db.First(&star).Error if err != nil { return &star, err @@ -33,13 +37,13 @@ func (p *PostCollection) Get(db *gorm.DB) (*PostCollection, error) { } func (p *PostCollection) Create(db *gorm.DB) (*PostCollection, error) { - err := db.Create(&p).Error + err := db.Omit("Post").Create(&p).Error return p, err } func (p *PostCollection) Delete(db *gorm.DB) error { - return db.Model(&PostCollection{}).Where("id = ? AND is_del = ?", p.Model.ID, 0).Updates(map[string]interface{}{ + return db.Model(&PostCollection{}).Omit("Post").Where("id = ? AND is_del = ?", p.Model.ID, 0).Updates(map[string]interface{}{ "deleted_on": time.Now().Unix(), "is_del": 1, }).Error @@ -48,22 +52,25 @@ func (p *PostCollection) Delete(db *gorm.DB) error { func (p *PostCollection) List(db *gorm.DB, conditions *ConditionsT, offset, limit int) ([]*PostCollection, error) { var collections []*PostCollection var err error + tn := db.NamingStrategy.TableName("PostCollection") + "." + if offset >= 0 && limit > 0 { db = db.Offset(offset).Limit(limit) } if p.UserID > 0 { - db = db.Where("user_id = ?", p.UserID) + db = db.Where(tn+"user_id = ?", p.UserID) } for k, v := range *conditions { if k == "ORDER" { db = db.Order(v) } else { - db = db.Where(k, v) + db = db.Where(tn+k, v) } } - if err = db.Where("is_del = ?", 0).Find(&collections).Error; err != nil { + db = db.Joins("Post").Where("Post.visibility <> ?", PostVisitPrivate).Order("Post.id DESC") + if err = db.Where(tn+"is_del = ?", 0).Find(&collections).Error; err != nil { return nil, err } @@ -72,17 +79,21 @@ func (p *PostCollection) List(db *gorm.DB, conditions *ConditionsT, offset, limi func (p *PostCollection) Count(db *gorm.DB, conditions *ConditionsT) (int64, error) { var count int64 + tn := db.NamingStrategy.TableName("PostCollection") + "." + if p.PostID > 0 { - db = db.Where("post_id = ?", p.PostID) + db = db.Where(tn+"post_id = ?", p.PostID) } if p.UserID > 0 { - db = db.Where("user_id = ?", p.UserID) + db = db.Where(tn+"user_id = ?", p.UserID) } for k, v := range *conditions { if k != "ORDER" { - db = db.Where(k, v) + db = db.Where(tn+k, v) } } + + db = db.Joins("Post").Where("Post.visibility <> ?", PostVisitPrivate) if err := db.Model(p).Count(&count).Error; err != nil { return 0, err } diff --git a/internal/model/post_star.go b/internal/model/post_star.go index 3f672664..d6c712ce 100644 --- a/internal/model/post_star.go +++ b/internal/model/post_star.go @@ -8,38 +8,40 @@ import ( type PostStar struct { *Model + Post *Post `json:"-"` PostID int64 `json:"post_id"` UserID int64 `json:"user_id"` } func (p *PostStar) Get(db *gorm.DB) (*PostStar, error) { var star PostStar + tn := db.NamingStrategy.TableName("PostStar") + "." + if p.Model != nil && p.ID > 0 { - db = db.Where("id = ? AND is_del = ?", p.ID, 0) + db = db.Where(tn+"id = ? AND "+tn+"is_del = ?", p.ID, 0) } if p.PostID > 0 { - db = db.Where("post_id = ?", p.PostID) + db = db.Where(tn+"post_id = ?", p.PostID) } if p.UserID > 0 { - db = db.Where("user_id = ?", p.UserID) + db = db.Where(tn+"user_id = ?", p.UserID) } - err := db.First(&star).Error - if err != nil { - return &star, err + db = db.Joins("Post").Where("Post.visibility <> ?", PostVisitPrivate).Order("Post.id DESC") + if err := db.First(&star).Error; err != nil { + return nil, err } - return &star, nil } func (p *PostStar) Create(db *gorm.DB) (*PostStar, error) { - err := db.Create(&p).Error + err := db.Omit("Post").Create(&p).Error return p, err } func (p *PostStar) Delete(db *gorm.DB) error { - return db.Model(&PostStar{}).Where("id = ? AND is_del = ?", p.Model.ID, 0).Updates(map[string]interface{}{ + return db.Model(&PostStar{}).Omit("Post").Where("id = ? AND is_del = ?", p.Model.ID, 0).Updates(map[string]interface{}{ "deleted_on": time.Now().Unix(), "is_del": 1, }).Error @@ -48,44 +50,49 @@ func (p *PostStar) Delete(db *gorm.DB) error { func (p *PostStar) List(db *gorm.DB, conditions *ConditionsT, offset, limit int) ([]*PostStar, error) { var stars []*PostStar var err error + tn := db.NamingStrategy.TableName("PostStar") + "." + if offset >= 0 && limit > 0 { db = db.Offset(offset).Limit(limit) } if p.UserID > 0 { - db = db.Where("user_id = ?", p.UserID) + db = db.Where(tn+"user_id = ?", p.UserID) } for k, v := range *conditions { if k == "ORDER" { db = db.Order(v) } else { - db = db.Where(k, v) + db = db.Where(tn+k, v) } } - if err = db.Where("is_del = ?", 0).Find(&stars).Error; err != nil { + db = db.Joins("Post").Where("Post.visibility <> ?", PostVisitPrivate).Order("Post.id DESC") + if err = db.Find(&stars).Error; err != nil { return nil, err } - return stars, nil } func (p *PostStar) Count(db *gorm.DB, conditions *ConditionsT) (int64, error) { var count int64 + tn := db.NamingStrategy.TableName("PostStar") + "." + if p.PostID > 0 { - db = db.Where("post_id = ?", p.PostID) + db = db.Where(tn+"post_id = ?", p.PostID) } if p.UserID > 0 { - db = db.Where("user_id = ?", p.UserID) + db = db.Where(tn+"user_id = ?", p.UserID) } for k, v := range *conditions { if k != "ORDER" { - db = db.Where(k, v) + db = db.Where(tn+k, v) } } + + db = db.Joins("Post").Where("Post.visibility <> ?", PostVisitPrivate) if err := db.Model(p).Count(&count).Error; err != nil { return 0, err } - return count, nil } diff --git a/internal/routers/api/post.go b/internal/routers/api/post.go index f7d0fc1e..af352afb 100644 --- a/internal/routers/api/post.go +++ b/internal/routers/api/post.go @@ -156,11 +156,16 @@ func PostStar(c *gin.Context) { star, err := service.GetPostStar(param.ID, userID.(int64)) if err != nil { // 创建Star - service.CreatePostStar(param.ID, userID.(int64)) + _, err = service.CreatePostStar(param.ID, userID.(int64)) status = true } else { // 取消Star - service.DeletePostStar(star) + err = service.DeletePostStar(star) + } + + if err != nil { + response.ToErrorResponse(errcode.NoPermission) + return } response.ToResponse(gin.H{ @@ -204,11 +209,16 @@ func PostCollection(c *gin.Context) { collection, err := service.GetPostCollection(param.ID, userID.(int64)) if err != nil { // 创建collection - service.CreatePostCollection(param.ID, userID.(int64)) + _, err = service.CreatePostCollection(param.ID, userID.(int64)) status = true } else { // 取消Star - service.DeletePostCollection(collection) + err = service.DeletePostCollection(collection) + } + + if err != nil { + response.ToErrorResponse(errcode.NoPermission) + return } response.ToResponse(gin.H{ diff --git a/internal/service/post.go b/internal/service/post.go index 5d89dceb..3ab372a2 100644 --- a/internal/service/post.go +++ b/internal/service/post.go @@ -2,6 +2,7 @@ package service import ( "encoding/json" + "errors" "fmt" "math" "strings" @@ -269,6 +270,12 @@ func CreatePostStar(postID, userID int64) (*model.PostStar, error) { if err != nil { return nil, err } + + // 私密post不可操作 + if post.Visibility == model.PostVisitPrivate { + return nil, errors.New("no permision") + } + star, err := ds.CreatePostStar(postID, userID) if err != nil { return nil, err @@ -294,6 +301,12 @@ func DeletePostStar(star *model.PostStar) error { if err != nil { return err } + + // 私密post不可操作 + if post.Visibility == model.PostVisitPrivate { + return errors.New("no permision") + } + // 更新Post点赞数 post.UpvoteCount-- ds.UpdatePost(post) @@ -314,6 +327,12 @@ func CreatePostCollection(postID, userID int64) (*model.PostCollection, error) { if err != nil { return nil, err } + + // 私密post不可操作 + if post.Visibility == model.PostVisitPrivate { + return nil, errors.New("no permision") + } + collection, err := ds.CreatePostCollection(postID, userID) if err != nil { return nil, err @@ -339,6 +358,12 @@ func DeletePostCollection(collection *model.PostCollection) error { if err != nil { return err } + + // 私密post不可操作 + if post.Visibility == model.PostVisitPrivate { + return errors.New("no permision") + } + // 更新Post点赞数 post.CollectionCount-- ds.UpdatePost(post) diff --git a/internal/service/user.go b/internal/service/user.go index 934b8525..c4d0234e 100644 --- a/internal/service/user.go +++ b/internal/service/user.go @@ -274,21 +274,11 @@ func GetUserCollections(userID int64, offset, limit int) ([]*model.PostFormated, if err != nil { return nil, 0, err } - postIDs := []int64{} + var posts []*model.Post for _, collection := range collections { - postIDs = append(postIDs, collection.PostID) + posts = append(posts, collection.Post) } - - // 获取Posts - posts, err := ds.GetPosts(&model.ConditionsT{ - "id IN ?": postIDs, - "ORDER": "id DESC", - }, 0, 0) - if err != nil { - return nil, 0, err - } - - postsFormated, err := FormatPosts(posts) + postsFormated, err := ds.MergePosts(posts) if err != nil { return nil, 0, err } @@ -306,21 +296,12 @@ func GetUserStars(userID int64, offset, limit int) ([]*model.PostFormated, int64 if err != nil { return nil, 0, err } - postIDs := []int64{} - for _, star := range stars { - postIDs = append(postIDs, star.PostID) - } - // 获取Posts - posts, err := ds.GetPosts(&model.ConditionsT{ - "id IN ?": postIDs, - "ORDER": "id DESC", - }, 0, 0) - if err != nil { - return nil, 0, err + var posts []*model.Post + for _, star := range stars { + posts = append(posts, star.Post) } - - postsFormated, err := FormatPosts(posts) + postsFormated, err := ds.MergePosts(posts) if err != nil { return nil, 0, err } From cbb4a3d5a5e5b0b7303d039fc22dad71c1b951cf Mon Sep 17 00:00:00 2001 From: orzi! <36832622+orziz@users.noreply.github.com> Date: Thu, 16 Jun 2022 12:13:21 +0800 Subject: [PATCH 21/35] Rename item.d.ts to Item.d.ts --- web/src/types/{item.d.ts => Item.d.ts} | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) rename web/src/types/{item.d.ts => Item.d.ts} (99%) diff --git a/web/src/types/item.d.ts b/web/src/types/Item.d.ts similarity index 99% rename from web/src/types/item.d.ts rename to web/src/types/Item.d.ts index 818557a9..a4c19418 100644 --- a/web/src/types/item.d.ts +++ b/web/src/types/Item.d.ts @@ -289,4 +289,4 @@ declare module Item { created_on: number } -} \ No newline at end of file +} From e8cefc447f6a18c73ea424695d05297a28e34cee Mon Sep 17 00:00:00 2001 From: alimy Date: Fri, 17 Jun 2022 11:20:26 +0800 Subject: [PATCH 22/35] upgrade tauri to v1.0.0 --- web/package.json | 2 +- web/src-tauri/.gitignore | 1 - web/src-tauri/Cargo.lock | 3654 +++++++++++++++++++++++++++++++++ web/src-tauri/Cargo.toml | 4 +- web/src-tauri/tauri.conf.json | 2 +- web/yarn.lock | 116 +- 6 files changed, 3716 insertions(+), 63 deletions(-) create mode 100644 web/src-tauri/Cargo.lock diff --git a/web/package.json b/web/package.json index 69bc0ada..1132537b 100644 --- a/web/package.json +++ b/web/package.json @@ -30,7 +30,7 @@ "vuex": "^4.0.2" }, "devDependencies": { - "@tauri-apps/cli": "^1.0.0-rc.7", + "@tauri-apps/cli": "^1.0.0", "@types/node": "^17.0.35", "@types/qrcode": "^1.4.2", "@vitejs/plugin-vue": "^2.3.3", diff --git a/web/src-tauri/.gitignore b/web/src-tauri/.gitignore index 25279840..c1237045 100644 --- a/web/src-tauri/.gitignore +++ b/web/src-tauri/.gitignore @@ -2,4 +2,3 @@ # will have compiled files and executables /target/ WixTools -Cargo.lock diff --git a/web/src-tauri/Cargo.lock b/web/src-tauri/Cargo.lock new file mode 100644 index 00000000..6231d29a --- /dev/null +++ b/web/src-tauri/Cargo.lock @@ -0,0 +1,3654 @@ +# This file is automatically @generated by Cargo. +# It is not intended for manual editing. +version = 3 + +[[package]] +name = "adler" +version = "1.0.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f26201604c87b1e01bd3d98f8d5d9a8fcbb815e8cedb41ffccbeb4bf593a35fe" + +[[package]] +name = "adler32" +version = "1.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "aae1277d39aeec15cb388266ecc24b11c80469deae6067e17a1a7aa9e5c1f234" + +[[package]] +name = "aho-corasick" +version = "0.7.18" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1e37cfd5e7657ada45f742d6e99ca5788580b5c529dc78faf11ece6dc702656f" +dependencies = [ + "memchr", +] + +[[package]] +name = "alloc-no-stdlib" +version = "2.0.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "35ef4730490ad1c4eae5c4325b2a95f521d023e5c885853ff7aca0a6a1631db3" + +[[package]] +name = "alloc-stdlib" +version = "0.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "697ed7edc0f1711de49ce108c541623a0af97c6c60b2f6e2b65229847ac843c2" +dependencies = [ + "alloc-no-stdlib", +] + +[[package]] +name = "ansi_term" +version = "0.12.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d52a9bb7ec0cf484c551830a7ce27bd20d67eac647e1befb56b0be4ee39a55d2" +dependencies = [ + "winapi", +] + +[[package]] +name = "anyhow" +version = "1.0.57" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "08f9b8508dccb7687a1d6c4ce66b2b0ecef467c94667de27d8d7fe1f8d2a9cdc" + +[[package]] +name = "atk" +version = "0.15.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2c3d816ce6f0e2909a96830d6911c2aff044370b1ef92d7f267b43bae5addedd" +dependencies = [ + "atk-sys", + "bitflags", + "glib", + "libc", +] + +[[package]] +name = "atk-sys" +version = "0.15.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "58aeb089fb698e06db8089971c7ee317ab9644bade33383f63631437b03aafb6" +dependencies = [ + "glib-sys", + "gobject-sys", + "libc", + "system-deps 6.0.2", +] + +[[package]] +name = "attohttpc" +version = "0.19.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "262c3f7f5d61249d8c00e5546e2685cd15ebeeb1bc0f3cc5449350a1cb07319e" +dependencies = [ + "flate2", + "http", + "log", + "native-tls", + "openssl", + "serde", + "serde_json", + "serde_urlencoded", + "url", + "wildmatch", +] + +[[package]] +name = "autocfg" +version = "1.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d468802bab17cbc0cc575e9b053f41e72aa36bfa6b7f55e3529ffa43161b97fa" + +[[package]] +name = "base64" +version = "0.13.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "904dfeac50f3cdaba28fc6f57fdcddb75f49ed61346676a78c4ffe55877802fd" + +[[package]] +name = "bitflags" +version = "1.3.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bef38d45163c2f1dde094a7dfd33ccf595c92905c8f8f4fdc18d06fb1037718a" + +[[package]] +name = "block" +version = "0.1.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0d8c1fef690941d3e7788d328517591fecc684c084084702d6ff1641e993699a" + +[[package]] +name = "block-buffer" +version = "0.10.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0bf7fe51849ea569fd452f37822f606a5cabb684dc918707a0193fd4664ff324" +dependencies = [ + "generic-array", +] + +[[package]] +name = "brotli" +version = "3.3.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a1a0b1dbcc8ae29329621f8d4f0d835787c1c38bb1401979b49d13b0b305ff68" +dependencies = [ + "alloc-no-stdlib", + "alloc-stdlib", + "brotli-decompressor", +] + +[[package]] +name = "brotli-decompressor" +version = "2.3.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "59ad2d4653bf5ca36ae797b1f4bb4dbddb60ce49ca4aed8a2ce4829f60425b80" +dependencies = [ + "alloc-no-stdlib", + "alloc-stdlib", +] + +[[package]] +name = "bstr" +version = "0.2.17" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ba3569f383e8f1598449f1a423e72e99569137b47740b1da11ef19af3d5c3223" +dependencies = [ + "memchr", +] + +[[package]] +name = "bumpalo" +version = "3.10.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "37ccbd214614c6783386c1af30caf03192f17891059cecc394b4fb119e363de3" + +[[package]] +name = "bytemuck" +version = "1.9.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cdead85bdec19c194affaeeb670c0e41fe23de31459efd1c174d049269cf02cc" + +[[package]] +name = "byteorder" +version = "1.4.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "14c189c53d098945499cdfa7ecc63567cf3886b3332b312a5b4585d8d3a6a610" + +[[package]] +name = "bytes" +version = "1.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c4872d67bab6358e59559027aa3b9157c53d9358c51423c17554809a8858e0f8" + +[[package]] +name = "cairo-rs" +version = "0.15.11" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "62be3562254e90c1c6050a72aa638f6315593e98c5cdaba9017cedbabf0a5dee" +dependencies = [ + "bitflags", + "cairo-sys-rs", + "glib", + "libc", + "thiserror", +] + +[[package]] +name = "cairo-sys-rs" +version = "0.15.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3c55d429bef56ac9172d25fecb85dc8068307d17acd74b377866b7a1ef25d3c8" +dependencies = [ + "glib-sys", + "libc", + "system-deps 6.0.2", +] + +[[package]] +name = "cargo_toml" +version = "0.11.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5809dd3e6444651fd1cdd3dbec71eca438c439a0fcc8081674a14da0afe50185" +dependencies = [ + "serde", + "serde_derive", + "toml", +] + +[[package]] +name = "cc" +version = "1.0.73" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2fff2a6927b3bb87f9595d67196a70493f627687a71d87a0d692242c33f58c11" + +[[package]] +name = "cesu8" +version = "1.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6d43a04d8753f35258c91f8ec639f792891f748a1edbd759cf1dcea3382ad83c" + +[[package]] +name = "cfb" +version = "0.6.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "74f89d248799e3f15f91b70917f65381062a01bb8e222700ea0e5a7ff9785f9c" +dependencies = [ + "byteorder", + "uuid 0.8.2", +] + +[[package]] +name = "cfg-expr" +version = "0.9.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3431df59f28accaf4cb4eed4a9acc66bea3f3c3753aa6cdc2f024174ef232af7" +dependencies = [ + "smallvec", +] + +[[package]] +name = "cfg-expr" +version = "0.10.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0aacacf4d96c24b2ad6eb8ee6df040e4f27b0d0b39a5710c30091baa830485db" +dependencies = [ + "smallvec", +] + +[[package]] +name = "cfg-if" +version = "1.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "baf1de4339761588bc0619e3cbc0120ee582ebb74b53b4efbf79117bd2da40fd" + +[[package]] +name = "cocoa" +version = "0.24.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6f63902e9223530efb4e26ccd0cf55ec30d592d3b42e21a28defc42a9586e832" +dependencies = [ + "bitflags", + "block", + "cocoa-foundation", + "core-foundation", + "core-graphics", + "foreign-types", + "libc", + "objc", +] + +[[package]] +name = "cocoa-foundation" +version = "0.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7ade49b65d560ca58c403a479bb396592b155c0185eada742ee323d1d68d6318" +dependencies = [ + "bitflags", + "block", + "core-foundation", + "core-graphics-types", + "foreign-types", + "libc", + "objc", +] + +[[package]] +name = "color_quant" +version = "1.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3d7b894f5411737b7867f4827955924d7c254fc9f4d91a6aad6b097804b1018b" + +[[package]] +name = "combine" +version = "4.6.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2a604e93b79d1808327a6fca85a6f2d69de66461e7620f5a4cbf5fb4d1d7c948" +dependencies = [ + "bytes", + "memchr", +] + +[[package]] +name = "convert_case" +version = "0.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6245d59a3e82a7fc217c5828a6692dbc6dfb63a0c8c90495621f7b9d79704a0e" + +[[package]] +name = "core-foundation" +version = "0.9.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "194a7a9e6de53fa55116934067c844d9d749312f75c6f6d0980e8c252f8c2146" +dependencies = [ + "core-foundation-sys", + "libc", +] + +[[package]] +name = "core-foundation-sys" +version = "0.8.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5827cebf4670468b8772dd191856768aedcb1b0278a04f989f7766351917b9dc" + +[[package]] +name = "core-graphics" +version = "0.22.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2581bbab3b8ffc6fcbd550bf46c355135d16e9ff2a6ea032ad6b9bf1d7efe4fb" +dependencies = [ + "bitflags", + "core-foundation", + "core-graphics-types", + "foreign-types", + "libc", +] + +[[package]] +name = "core-graphics-types" +version = "0.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3a68b68b3446082644c91ac778bf50cd4104bfb002b5a6a7c44cca5a2c70788b" +dependencies = [ + "bitflags", + "core-foundation", + "foreign-types", + "libc", +] + +[[package]] +name = "cpufeatures" +version = "0.2.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "59a6001667ab124aebae2a495118e11d30984c3a653e99d86d58971708cf5e4b" +dependencies = [ + "libc", +] + +[[package]] +name = "crc32fast" +version = "1.3.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b540bd8bc810d3885c6ea91e2018302f68baba2129ab3e88f32389ee9370880d" +dependencies = [ + "cfg-if", +] + +[[package]] +name = "crossbeam-channel" +version = "0.5.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4c02a4d71819009c192cf4872265391563fd6a84c81ff2c0f2a7026ca4c1d85c" +dependencies = [ + "cfg-if", + "crossbeam-utils", +] + +[[package]] +name = "crossbeam-utils" +version = "0.8.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8ff1f980957787286a554052d03c7aee98d99cc32e09f6d45f0a814133c87978" +dependencies = [ + "cfg-if", + "once_cell", +] + +[[package]] +name = "crypto-common" +version = "0.1.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "57952ca27b5e3606ff4dd79b0020231aaf9d6aa76dc05fd30137538c50bd3ce8" +dependencies = [ + "generic-array", + "typenum", +] + +[[package]] +name = "cssparser" +version = "0.27.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "754b69d351cdc2d8ee09ae203db831e005560fc6030da058f86ad60c92a9cb0a" +dependencies = [ + "cssparser-macros", + "dtoa-short", + "itoa 0.4.8", + "matches", + "phf 0.8.0", + "proc-macro2", + "quote", + "smallvec", + "syn", +] + +[[package]] +name = "cssparser-macros" +version = "0.6.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "dfae75de57f2b2e85e8768c3ea840fd159c8f33e2b6522c7835b7abac81be16e" +dependencies = [ + "quote", + "syn", +] + +[[package]] +name = "ctor" +version = "0.1.22" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f877be4f7c9f246b183111634f75baa039715e3f46ce860677d3b19a69fb229c" +dependencies = [ + "quote", + "syn", +] + +[[package]] +name = "cty" +version = "0.2.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b365fabc795046672053e29c954733ec3b05e4be654ab130fe8f1f94d7051f35" + +[[package]] +name = "darling" +version = "0.13.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a01d95850c592940db9b8194bc39f4bc0e89dee5c4265e4b1807c34a9aba453c" +dependencies = [ + "darling_core", + "darling_macro", +] + +[[package]] +name = "darling_core" +version = "0.13.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "859d65a907b6852c9361e3185c862aae7fafd2887876799fa55f5f99dc40d610" +dependencies = [ + "fnv", + "ident_case", + "proc-macro2", + "quote", + "strsim", + "syn", +] + +[[package]] +name = "darling_macro" +version = "0.13.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9c972679f83bdf9c42bd905396b6c3588a843a17f0f16dfcfa3e2c5d57441835" +dependencies = [ + "darling_core", + "quote", + "syn", +] + +[[package]] +name = "dbus" +version = "0.9.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "de0a745c25b32caa56b82a3950f5fec7893a960f4c10ca3b02060b0c38d8c2ce" +dependencies = [ + "libc", + "libdbus-sys", + "winapi", +] + +[[package]] +name = "deflate" +version = "0.7.20" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "707b6a7b384888a70c8d2e8650b3e60170dfc6a67bb4aa67b6dfca57af4bedb4" +dependencies = [ + "adler32", + "byteorder", +] + +[[package]] +name = "deflate" +version = "1.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c86f7e25f518f4b81808a2cf1c50996a61f5c2eb394b2393bd87f2a4780a432f" +dependencies = [ + "adler32", +] + +[[package]] +name = "derive_more" +version = "0.99.17" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4fb810d30a7c1953f91334de7244731fc3f3c10d7fe163338a35b9f640960321" +dependencies = [ + "convert_case", + "proc-macro2", + "quote", + "rustc_version 0.4.0", + "syn", +] + +[[package]] +name = "digest" +version = "0.10.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f2fb860ca6fafa5552fb6d0e816a69c8e49f0908bf524e30a90d97c85892d506" +dependencies = [ + "block-buffer", + "crypto-common", +] + +[[package]] +name = "dirs-next" +version = "2.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b98cf8ebf19c3d1b223e151f99a4f9f0690dca41414773390fc824184ac833e1" +dependencies = [ + "cfg-if", + "dirs-sys-next", +] + +[[package]] +name = "dirs-sys-next" +version = "0.1.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4ebda144c4fe02d1f7ea1a7d9641b6fc6b580adcfa024ae48797ecdeb6825b4d" +dependencies = [ + "libc", + "redox_users", + "winapi", +] + +[[package]] +name = "dispatch" +version = "0.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bd0c93bb4b0c6d9b77f4435b0ae98c24d17f1c45b2ff844c6151a07256ca923b" + +[[package]] +name = "dtoa" +version = "0.4.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "56899898ce76aaf4a0f24d914c97ea6ed976d42fec6ad33fcbb0a1103e07b2b0" + +[[package]] +name = "dtoa-short" +version = "0.3.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bde03329ae10e79ede66c9ce4dc930aa8599043b0743008548680f25b91502d6" +dependencies = [ + "dtoa", +] + +[[package]] +name = "embed-resource" +version = "1.7.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ecc24ff8d764818e9ab17963b0593c535f077a513f565e75e4352d758bc4d8c0" +dependencies = [ + "cc", + "rustc_version 0.4.0", + "toml", + "vswhom", + "winreg", +] + +[[package]] +name = "embed_plist" +version = "1.2.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4ef6b89e5b37196644d8796de5268852ff179b44e96276cf4290264843743bb7" + +[[package]] +name = "fastrand" +version = "1.7.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c3fcf0cee53519c866c09b5de1f6c56ff9d647101f81c1964fa632e148896cdf" +dependencies = [ + "instant", +] + +[[package]] +name = "field-offset" +version = "0.3.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1e1c54951450cbd39f3dbcf1005ac413b49487dabf18a720ad2383eccfeffb92" +dependencies = [ + "memoffset", + "rustc_version 0.3.3", +] + +[[package]] +name = "filetime" +version = "0.2.16" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c0408e2626025178a6a7f7ffc05a25bc47103229f19c113755de7bf63816290c" +dependencies = [ + "cfg-if", + "libc", + "redox_syscall", + "winapi", +] + +[[package]] +name = "flate2" +version = "1.0.24" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f82b0f4c27ad9f8bfd1f3208d882da2b09c301bc1c828fd3a00d0216d2fbbff6" +dependencies = [ + "crc32fast", + "miniz_oxide", +] + +[[package]] +name = "fnv" +version = "1.0.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3f9eec918d3f24069decb9af1554cad7c880e2da24a9afd88aca000531ab82c1" + +[[package]] +name = "foreign-types" +version = "0.3.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f6f339eb8adc052cd2ca78910fda869aefa38d22d5cb648e6485e4d3fc06f3b1" +dependencies = [ + "foreign-types-shared", +] + +[[package]] +name = "foreign-types-shared" +version = "0.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "00b0228411908ca8685dba7fc2cdd70ec9990a6e753e89b6ac91a84c40fbaf4b" + +[[package]] +name = "form_urlencoded" +version = "1.0.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5fc25a87fa4fd2094bffb06925852034d90a17f0d1e05197d4956d3555752191" +dependencies = [ + "matches", + "percent-encoding", +] + +[[package]] +name = "futf" +version = "0.1.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "df420e2e84819663797d1ec6544b13c5be84629e7bb00dc960d6917db2987843" +dependencies = [ + "mac", + "new_debug_unreachable", +] + +[[package]] +name = "futures" +version = "0.3.21" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f73fe65f54d1e12b726f517d3e2135ca3125a437b6d998caf1962961f7172d9e" +dependencies = [ + "futures-channel", + "futures-core", + "futures-executor", + "futures-io", + "futures-sink", + "futures-task", + "futures-util", +] + +[[package]] +name = "futures-channel" +version = "0.3.21" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c3083ce4b914124575708913bca19bfe887522d6e2e6d0952943f5eac4a74010" +dependencies = [ + "futures-core", + "futures-sink", +] + +[[package]] +name = "futures-core" +version = "0.3.21" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0c09fd04b7e4073ac7156a9539b57a484a8ea920f79c7c675d05d289ab6110d3" + +[[package]] +name = "futures-executor" +version = "0.3.21" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9420b90cfa29e327d0429f19be13e7ddb68fa1cccb09d65e5706b8c7a749b8a6" +dependencies = [ + "futures-core", + "futures-task", + "futures-util", +] + +[[package]] +name = "futures-io" +version = "0.3.21" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "fc4045962a5a5e935ee2fdedaa4e08284547402885ab326734432bed5d12966b" + +[[package]] +name = "futures-lite" +version = "1.12.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7694489acd39452c77daa48516b894c153f192c3578d5a839b62c58099fcbf48" +dependencies = [ + "fastrand", + "futures-core", + "futures-io", + "memchr", + "parking", + "pin-project-lite", + "waker-fn", +] + +[[package]] +name = "futures-macro" +version = "0.3.21" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "33c1e13800337f4d4d7a316bf45a567dbcb6ffe087f16424852d97e97a91f512" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] + +[[package]] +name = "futures-sink" +version = "0.3.21" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "21163e139fa306126e6eedaf49ecdb4588f939600f0b1e770f4205ee4b7fa868" + +[[package]] +name = "futures-task" +version = "0.3.21" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "57c66a976bf5909d801bbef33416c41372779507e7a6b3a5e25e4749c58f776a" + +[[package]] +name = "futures-util" +version = "0.3.21" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d8b7abd5d659d9b90c8cba917f6ec750a74e2dc23902ef9cd4cc8c8b22e6036a" +dependencies = [ + "futures-channel", + "futures-core", + "futures-io", + "futures-macro", + "futures-sink", + "futures-task", + "memchr", + "pin-project-lite", + "pin-utils", + "slab", +] + +[[package]] +name = "fxhash" +version = "0.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c31b6d751ae2c7f11320402d34e41349dd1016f8d5d45e48c4312bc8625af50c" +dependencies = [ + "byteorder", +] + +[[package]] +name = "gdk" +version = "0.15.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a6e05c1f572ab0e1f15be94217f0dc29088c248b14f792a5ff0af0d84bcda9e8" +dependencies = [ + "bitflags", + "cairo-rs", + "gdk-pixbuf", + "gdk-sys", + "gio", + "glib", + "libc", + "pango", +] + +[[package]] +name = "gdk-pixbuf" +version = "0.15.11" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ad38dd9cc8b099cceecdf41375bb6d481b1b5a7cd5cd603e10a69a9383f8619a" +dependencies = [ + "bitflags", + "gdk-pixbuf-sys", + "gio", + "glib", + "libc", +] + +[[package]] +name = "gdk-pixbuf-sys" +version = "0.15.10" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "140b2f5378256527150350a8346dbdb08fadc13453a7a2d73aecd5fab3c402a7" +dependencies = [ + "gio-sys", + "glib-sys", + "gobject-sys", + "libc", + "system-deps 6.0.2", +] + +[[package]] +name = "gdk-sys" +version = "0.15.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "32e7a08c1e8f06f4177fb7e51a777b8c1689f743a7bc11ea91d44d2226073a88" +dependencies = [ + "cairo-sys-rs", + "gdk-pixbuf-sys", + "gio-sys", + "glib-sys", + "gobject-sys", + "libc", + "pango-sys", + "pkg-config", + "system-deps 6.0.2", +] + +[[package]] +name = "gdkx11-sys" +version = "0.15.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b4b7f8c7a84b407aa9b143877e267e848ff34106578b64d1e0a24bf550716178" +dependencies = [ + "gdk-sys", + "glib-sys", + "libc", + "system-deps 6.0.2", + "x11", +] + +[[package]] +name = "generator" +version = "0.7.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c1d9279ca822891c1a4dae06d185612cf8fc6acfe5dff37781b41297811b12ee" +dependencies = [ + "cc", + "libc", + "log", + "rustversion", + "winapi", +] + +[[package]] +name = "generic-array" +version = "0.14.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "fd48d33ec7f05fbfa152300fdad764757cbded343c1aa1cff2fbaf4134851803" +dependencies = [ + "typenum", + "version_check", +] + +[[package]] +name = "getrandom" +version = "0.1.16" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8fc3cb4d91f53b50155bdcfd23f6a4c39ae1969c2ae85982b135750cccaf5fce" +dependencies = [ + "cfg-if", + "libc", + "wasi 0.9.0+wasi-snapshot-preview1", +] + +[[package]] +name = "getrandom" +version = "0.2.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4eb1a864a501629691edf6c15a593b7a51eebaa1e8468e9ddc623de7c9b58ec6" +dependencies = [ + "cfg-if", + "libc", + "wasi 0.11.0+wasi-snapshot-preview1", +] + +[[package]] +name = "gio" +version = "0.15.11" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0f132be35e05d9662b9fa0fee3f349c6621f7782e0105917f4cc73c1bf47eceb" +dependencies = [ + "bitflags", + "futures-channel", + "futures-core", + "futures-io", + "gio-sys", + "glib", + "libc", + "once_cell", + "thiserror", +] + +[[package]] +name = "gio-sys" +version = "0.15.10" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "32157a475271e2c4a023382e9cab31c4584ee30a97da41d3c4e9fdd605abcf8d" +dependencies = [ + "glib-sys", + "gobject-sys", + "libc", + "system-deps 6.0.2", + "winapi", +] + +[[package]] +name = "glib" +version = "0.15.11" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bd124026a2fa8c33a3d17a3fe59c103f2d9fa5bd92c19e029e037736729abeab" +dependencies = [ + "bitflags", + "futures-channel", + "futures-core", + "futures-executor", + "futures-task", + "glib-macros", + "glib-sys", + "gobject-sys", + "libc", + "once_cell", + "smallvec", + "thiserror", +] + +[[package]] +name = "glib-macros" +version = "0.15.11" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "25a68131a662b04931e71891fb14aaf65ee4b44d08e8abc10f49e77418c86c64" +dependencies = [ + "anyhow", + "heck 0.4.0", + "proc-macro-crate", + "proc-macro-error", + "proc-macro2", + "quote", + "syn", +] + +[[package]] +name = "glib-sys" +version = "0.15.10" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ef4b192f8e65e9cf76cbf4ea71fa8e3be4a0e18ffe3d68b8da6836974cc5bad4" +dependencies = [ + "libc", + "system-deps 6.0.2", +] + +[[package]] +name = "glob" +version = "0.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9b919933a397b79c37e33b77bb2aa3dc8eb6e165ad809e58ff75bc7db2e34574" + +[[package]] +name = "globset" +version = "0.4.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0a1e17342619edbc21a964c2afbeb6c820c6a2560032872f397bb97ea127bd0a" +dependencies = [ + "aho-corasick", + "bstr", + "fnv", + "log", + "regex", +] + +[[package]] +name = "gobject-sys" +version = "0.15.10" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0d57ce44246becd17153bd035ab4d32cfee096a657fc01f2231c9278378d1e0a" +dependencies = [ + "glib-sys", + "libc", + "system-deps 6.0.2", +] + +[[package]] +name = "gtk" +version = "0.15.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "92e3004a2d5d6d8b5057d2b57b3712c9529b62e82c77f25c1fecde1fd5c23bd0" +dependencies = [ + "atk", + "bitflags", + "cairo-rs", + "field-offset", + "futures-channel", + "gdk", + "gdk-pixbuf", + "gio", + "glib", + "gtk-sys", + "gtk3-macros", + "libc", + "once_cell", + "pango", + "pkg-config", +] + +[[package]] +name = "gtk-sys" +version = "0.15.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d5bc2f0587cba247f60246a0ca11fe25fb733eabc3de12d1965fc07efab87c84" +dependencies = [ + "atk-sys", + "cairo-sys-rs", + "gdk-pixbuf-sys", + "gdk-sys", + "gio-sys", + "glib-sys", + "gobject-sys", + "libc", + "pango-sys", + "system-deps 6.0.2", +] + +[[package]] +name = "gtk3-macros" +version = "0.15.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "24f518afe90c23fba585b2d7697856f9e6a7bbc62f65588035e66f6afb01a2e9" +dependencies = [ + "anyhow", + "proc-macro-crate", + "proc-macro-error", + "proc-macro2", + "quote", + "syn", +] + +[[package]] +name = "heck" +version = "0.3.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6d621efb26863f0e9924c6ac577e8275e5e6b77455db64ffa6c65c904e9e132c" +dependencies = [ + "unicode-segmentation", +] + +[[package]] +name = "heck" +version = "0.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2540771e65fc8cb83cd6e8a237f70c319bd5c29f78ed1084ba5d50eeac86f7f9" + +[[package]] +name = "hermit-abi" +version = "0.1.19" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "62b467343b94ba476dcb2500d242dadbb39557df889310ac77c5d99100aaac33" +dependencies = [ + "libc", +] + +[[package]] +name = "html5ever" +version = "0.25.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e5c13fb08e5d4dfc151ee5e88bae63f7773d61852f3bdc73c9f4b9e1bde03148" +dependencies = [ + "log", + "mac", + "markup5ever", + "proc-macro2", + "quote", + "syn", +] + +[[package]] +name = "http" +version = "0.2.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "75f43d41e26995c17e71ee126451dd3941010b0514a81a9d11f3b341debc2399" +dependencies = [ + "bytes", + "fnv", + "itoa 1.0.2", +] + +[[package]] +name = "http-range" +version = "0.1.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "21dec9db110f5f872ed9699c3ecf50cf16f423502706ba5c72462e28d3157573" + +[[package]] +name = "ico" +version = "0.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6a4b3331534254a9b64095ae60d3dc2a8225a7a70229cd5888be127cdc1f6804" +dependencies = [ + "byteorder", + "png 0.11.0", +] + +[[package]] +name = "ident_case" +version = "1.0.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b9e0384b61958566e926dc50660321d12159025e767c18e043daf26b70104c39" + +[[package]] +name = "idna" +version = "0.2.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "418a0a6fab821475f634efe3ccc45c013f742efe03d853e8d3355d5cb850ecf8" +dependencies = [ + "matches", + "unicode-bidi", + "unicode-normalization", +] + +[[package]] +name = "ignore" +version = "0.4.18" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "713f1b139373f96a2e0ce3ac931cd01ee973c3c5dd7c40c0c2efe96ad2b6751d" +dependencies = [ + "crossbeam-utils", + "globset", + "lazy_static", + "log", + "memchr", + "regex", + "same-file", + "thread_local", + "walkdir", + "winapi-util", +] + +[[package]] +name = "image" +version = "0.24.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "28edd9d7bc256be2502e325ac0628bde30b7001b9b52e0abe31a1a9dc2701212" +dependencies = [ + "bytemuck", + "byteorder", + "color_quant", + "num-iter", + "num-rational", + "num-traits", +] + +[[package]] +name = "infer" +version = "0.7.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "20b2b533137b9cad970793453d4f921c2e91312a6d88b1085c07bc15fc51bb3b" +dependencies = [ + "cfb", +] + +[[package]] +name = "inflate" +version = "0.3.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f5f9f47468e9a76a6452271efadc88fe865a82be91fe75e6c0c57b87ccea59d4" +dependencies = [ + "adler32", +] + +[[package]] +name = "instant" +version = "0.1.12" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7a5bbe824c507c5da5956355e86a746d82e0e1464f65d862cc5e71da70e94b2c" +dependencies = [ + "cfg-if", +] + +[[package]] +name = "itoa" +version = "0.4.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b71991ff56294aa922b450139ee08b3bfc70982c6b2c7562771375cf73542dd4" + +[[package]] +name = "itoa" +version = "1.0.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "112c678d4050afce233f4f2852bb2eb519230b3cf12f33585275537d7e41578d" + +[[package]] +name = "javascriptcore-rs" +version = "0.16.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bf053e7843f2812ff03ef5afe34bb9c06ffee120385caad4f6b9967fcd37d41c" +dependencies = [ + "bitflags", + "glib", + "javascriptcore-rs-sys", +] + +[[package]] +name = "javascriptcore-rs-sys" +version = "0.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "905fbb87419c5cde6e3269537e4ea7d46431f3008c5d057e915ef3f115e7793c" +dependencies = [ + "glib-sys", + "gobject-sys", + "libc", + "system-deps 5.0.0", +] + +[[package]] +name = "jni" +version = "0.18.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "24967112a1e4301ca5342ea339763613a37592b8a6ce6cf2e4494537c7a42faf" +dependencies = [ + "cesu8", + "combine", + "jni-sys", + "log", + "thiserror", + "walkdir", +] + +[[package]] +name = "jni" +version = "0.19.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c6df18c2e3db7e453d3c6ac5b3e9d5182664d28788126d39b91f2d1e22b017ec" +dependencies = [ + "cesu8", + "combine", + "jni-sys", + "log", + "thiserror", + "walkdir", +] + +[[package]] +name = "jni-sys" +version = "0.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8eaf4bc02d17cbdd7ff4c7438cafcdf7fb9a4613313ad11b4f8fefe7d3fa0130" + +[[package]] +name = "js-sys" +version = "0.3.58" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c3fac17f7123a73ca62df411b1bf727ccc805daa070338fda671c86dac1bdc27" +dependencies = [ + "wasm-bindgen", +] + +[[package]] +name = "json-patch" +version = "0.2.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f995a3c8f2bc3dd52a18a583e90f9ec109c047fa1603a853e46bcda14d2e279d" +dependencies = [ + "serde", + "serde_json", + "treediff", +] + +[[package]] +name = "kuchiki" +version = "0.8.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1ea8e9c6e031377cff82ee3001dc8026cdf431ed4e2e6b51f98ab8c73484a358" +dependencies = [ + "cssparser", + "html5ever", + "matches", + "selectors", +] + +[[package]] +name = "lazy_static" +version = "1.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e2abad23fbc42b3700f2f279844dc832adb2b2eb069b2df918f455c4e18cc646" + +[[package]] +name = "libc" +version = "0.2.126" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "349d5a591cd28b49e1d1037471617a32ddcda5731b99419008085f72d5a53836" + +[[package]] +name = "libdbus-sys" +version = "0.2.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c185b5b7ad900923ef3a8ff594083d4d9b5aea80bb4f32b8342363138c0d456b" +dependencies = [ + "pkg-config", +] + +[[package]] +name = "lock_api" +version = "0.4.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "327fa5b6a6940e4699ec49a9beae1ea4845c6bab9314e4f84ac68742139d8c53" +dependencies = [ + "autocfg", + "scopeguard", +] + +[[package]] +name = "log" +version = "0.4.17" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "abb12e687cfb44aa40f41fc3978ef76448f9b6038cad6aef4259d3c095a2382e" +dependencies = [ + "cfg-if", +] + +[[package]] +name = "loom" +version = "0.5.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ff50ecb28bb86013e935fb6683ab1f6d3a20016f123c76fd4c27470076ac30f5" +dependencies = [ + "cfg-if", + "generator", + "scoped-tls", + "serde", + "serde_json", + "tracing", + "tracing-subscriber", +] + +[[package]] +name = "mac" +version = "0.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c41e0c4fef86961ac6d6f8a82609f55f31b05e4fce149ac5710e439df7619ba4" + +[[package]] +name = "mac-notification-sys" +version = "0.5.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "042f74a606175d72ca483e14e0873fe0f6c003f7af45865b17b16fdaface7203" +dependencies = [ + "cc", + "dirs-next", + "objc-foundation", + "objc_id", + "time", +] + +[[package]] +name = "malloc_buf" +version = "0.0.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "62bb907fe88d54d8d9ce32a3cceab4218ed2f6b7d35617cafe9adf84e43919cb" +dependencies = [ + "libc", +] + +[[package]] +name = "markup5ever" +version = "0.10.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a24f40fb03852d1cdd84330cddcaf98e9ec08a7b7768e952fad3b4cf048ec8fd" +dependencies = [ + "log", + "phf 0.8.0", + "phf_codegen", + "string_cache", + "string_cache_codegen", + "tendril", +] + +[[package]] +name = "matchers" +version = "0.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8263075bb86c5a1b1427b5ae862e8889656f126e9f77c484496e8b47cf5c5558" +dependencies = [ + "regex-automata", +] + +[[package]] +name = "matches" +version = "0.1.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a3e378b66a060d48947b590737b30a1be76706c8dd7b8ba0f2fe3989c68a853f" + +[[package]] +name = "memchr" +version = "2.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2dffe52ecf27772e601905b7522cb4ef790d2cc203488bbd0e2fe85fcb74566d" + +[[package]] +name = "memoffset" +version = "0.6.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5aa361d4faea93603064a027415f07bd8e1d5c88c9fbf68bf56a285428fd79ce" +dependencies = [ + "autocfg", +] + +[[package]] +name = "miniz_oxide" +version = "0.5.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6f5c75688da582b8ffc1f1799e9db273f32133c49e048f614d22ec3256773ccc" +dependencies = [ + "adler", +] + +[[package]] +name = "native-tls" +version = "0.2.10" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "fd7e2f3618557f980e0b17e8856252eee3c97fa12c54dff0ca290fb6266ca4a9" +dependencies = [ + "lazy_static", + "libc", + "log", + "openssl", + "openssl-probe", + "openssl-sys", + "schannel", + "security-framework", + "security-framework-sys", + "tempfile", +] + +[[package]] +name = "ndk" +version = "0.6.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2032c77e030ddee34a6787a64166008da93f6a352b629261d0fee232b8742dd4" +dependencies = [ + "bitflags", + "jni-sys", + "ndk-sys", + "num_enum", + "thiserror", +] + +[[package]] +name = "ndk-context" +version = "0.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "27b02d87554356db9e9a873add8782d4ea6e3e58ea071a9adb9a2e8ddb884a8b" + +[[package]] +name = "ndk-sys" +version = "0.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6e5a6ae77c8ee183dcbbba6150e2e6b9f3f4196a7666c02a715a95692ec1fa97" +dependencies = [ + "jni-sys", +] + +[[package]] +name = "new_debug_unreachable" +version = "1.0.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e4a24736216ec316047a1fc4252e27dabb04218aa4a3f37c6e7ddbf1f9782b54" + +[[package]] +name = "nodrop" +version = "0.1.14" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "72ef4a56884ca558e5ddb05a1d1e7e1bfd9a68d9ed024c21704cc98872dae1bb" + +[[package]] +name = "notify-rust" +version = "4.5.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a995a3d2834cefa389218e7a35156e8ce544bc95f836900da01ee0b26a07e9d4" +dependencies = [ + "dbus", + "mac-notification-sys", + "winrt-notification", +] + +[[package]] +name = "num-integer" +version = "0.1.45" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "225d3389fb3509a24c93f5c29eb6bde2586b98d9f016636dff58d7c6f7569cd9" +dependencies = [ + "autocfg", + "num-traits", +] + +[[package]] +name = "num-iter" +version = "0.1.43" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7d03e6c028c5dc5cac6e2dec0efda81fc887605bb3d884578bb6d6bf7514e252" +dependencies = [ + "autocfg", + "num-integer", + "num-traits", +] + +[[package]] +name = "num-rational" +version = "0.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d41702bd167c2df5520b384281bc111a4b5efcf7fbc4c9c222c815b07e0a6a6a" +dependencies = [ + "autocfg", + "num-integer", + "num-traits", +] + +[[package]] +name = "num-traits" +version = "0.2.15" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "578ede34cf02f8924ab9447f50c28075b4d3e5b269972345e7e0372b38c6cdcd" +dependencies = [ + "autocfg", +] + +[[package]] +name = "num_cpus" +version = "1.13.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "19e64526ebdee182341572e50e9ad03965aa510cd94427a4549448f285e957a1" +dependencies = [ + "hermit-abi", + "libc", +] + +[[package]] +name = "num_enum" +version = "0.5.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cf5395665662ef45796a4ff5486c5d41d29e0c09640af4c5f17fd94ee2c119c9" +dependencies = [ + "num_enum_derive", +] + +[[package]] +name = "num_enum_derive" +version = "0.5.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3b0498641e53dd6ac1a4f22547548caa6864cc4933784319cd1775271c5a46ce" +dependencies = [ + "proc-macro-crate", + "proc-macro2", + "quote", + "syn", +] + +[[package]] +name = "num_threads" +version = "0.1.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2819ce041d2ee131036f4fc9d6ae7ae125a3a40e97ba64d04fe799ad9dabbb44" +dependencies = [ + "libc", +] + +[[package]] +name = "objc" +version = "0.2.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "915b1b472bc21c53464d6c8461c9d3af805ba1ef837e1cac254428f4a77177b1" +dependencies = [ + "malloc_buf", + "objc_exception", +] + +[[package]] +name = "objc-foundation" +version = "0.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1add1b659e36c9607c7aab864a76c7a4c2760cd0cd2e120f3fb8b952c7e22bf9" +dependencies = [ + "block", + "objc", + "objc_id", +] + +[[package]] +name = "objc_exception" +version = "0.1.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ad970fb455818ad6cba4c122ad012fae53ae8b4795f86378bce65e4f6bab2ca4" +dependencies = [ + "cc", +] + +[[package]] +name = "objc_id" +version = "0.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c92d4ddb4bd7b50d730c215ff871754d0da6b2178849f8a2a2ab69712d0c073b" +dependencies = [ + "objc", +] + +[[package]] +name = "once_cell" +version = "1.12.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7709cef83f0c1f58f666e746a08b21e0085f7440fa6a29cc194d68aac97a4225" + +[[package]] +name = "open" +version = "3.0.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "360bcc8316bf6363aa3954c3ccc4de8add167b087e0259190a043c9514f910fe" +dependencies = [ + "pathdiff", + "windows-sys", +] + +[[package]] +name = "openssl" +version = "0.10.40" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "fb81a6430ac911acb25fe5ac8f1d2af1b4ea8a4fdfda0f1ee4292af2e2d8eb0e" +dependencies = [ + "bitflags", + "cfg-if", + "foreign-types", + "libc", + "once_cell", + "openssl-macros", + "openssl-sys", +] + +[[package]] +name = "openssl-macros" +version = "0.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b501e44f11665960c7e7fcf062c7d96a14ade4aa98116c004b2e37b5be7d736c" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] + +[[package]] +name = "openssl-probe" +version = "0.1.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ff011a302c396a5197692431fc1948019154afc178baf7d8e37367442a4601cf" + +[[package]] +name = "openssl-sys" +version = "0.9.74" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "835363342df5fba8354c5b453325b110ffd54044e588c539cf2f20a8014e4cb1" +dependencies = [ + "autocfg", + "cc", + "libc", + "pkg-config", + "vcpkg", +] + +[[package]] +name = "os_info" +version = "3.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0eca3ecae1481e12c3d9379ec541b238a16f0b75c9a409942daa8ec20dbfdb62" +dependencies = [ + "log", + "serde", + "winapi", +] + +[[package]] +name = "os_pipe" +version = "1.0.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2c92f2b54f081d635c77e7120862d48db8e91f7f21cef23ab1b4fe9971c59f55" +dependencies = [ + "libc", + "winapi", +] + +[[package]] +name = "pango" +version = "0.15.10" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "22e4045548659aee5313bde6c582b0d83a627b7904dd20dc2d9ef0895d414e4f" +dependencies = [ + "bitflags", + "glib", + "libc", + "once_cell", + "pango-sys", +] + +[[package]] +name = "pango-sys" +version = "0.15.10" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d2a00081cde4661982ed91d80ef437c20eacaf6aa1a5962c0279ae194662c3aa" +dependencies = [ + "glib-sys", + "gobject-sys", + "libc", + "system-deps 6.0.2", +] + +[[package]] +name = "paopao" +version = "0.1.0" +dependencies = [ + "tauri", + "tauri-build", +] + +[[package]] +name = "parking" +version = "2.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "427c3892f9e783d91cc128285287e70a59e206ca452770ece88a76f7a3eddd72" + +[[package]] +name = "parking_lot" +version = "0.11.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7d17b78036a60663b797adeaee46f5c9dfebb86948d1255007a1d6be0271ff99" +dependencies = [ + "instant", + "lock_api", + "parking_lot_core 0.8.5", +] + +[[package]] +name = "parking_lot" +version = "0.12.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3742b2c103b9f06bc9fff0a37ff4912935851bee6d36f3c02bcc755bcfec228f" +dependencies = [ + "lock_api", + "parking_lot_core 0.9.3", +] + +[[package]] +name = "parking_lot_core" +version = "0.8.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d76e8e1493bcac0d2766c42737f34458f1c8c50c0d23bcb24ea953affb273216" +dependencies = [ + "cfg-if", + "instant", + "libc", + "redox_syscall", + "smallvec", + "winapi", +] + +[[package]] +name = "parking_lot_core" +version = "0.9.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "09a279cbf25cb0757810394fbc1e359949b59e348145c643a939a525692e6929" +dependencies = [ + "cfg-if", + "libc", + "redox_syscall", + "smallvec", + "windows-sys", +] + +[[package]] +name = "paste" +version = "1.0.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0c520e05135d6e763148b6426a837e239041653ba7becd2e538c076c738025fc" + +[[package]] +name = "pathdiff" +version = "0.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8835116a5c179084a830efb3adc117ab007512b535bc1a21c991d3b32a6b44dd" + +[[package]] +name = "percent-encoding" +version = "2.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d4fd5641d01c8f18a23da7b6fe29298ff4b55afcccdf78973b24cf3175fee32e" + +[[package]] +name = "pest" +version = "2.1.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "10f4872ae94d7b90ae48754df22fd42ad52ce740b8f370b03da4835417403e53" +dependencies = [ + "ucd-trie", +] + +[[package]] +name = "phf" +version = "0.8.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3dfb61232e34fcb633f43d12c58f83c1df82962dcdfa565a4e866ffc17dafe12" +dependencies = [ + "phf_macros 0.8.0", + "phf_shared 0.8.0", + "proc-macro-hack", +] + +[[package]] +name = "phf" +version = "0.10.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "fabbf1ead8a5bcbc20f5f8b939ee3f5b0f6f281b6ad3468b84656b658b455259" +dependencies = [ + "phf_macros 0.10.0", + "phf_shared 0.10.0", + "proc-macro-hack", +] + +[[package]] +name = "phf_codegen" +version = "0.8.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cbffee61585b0411840d3ece935cce9cb6321f01c45477d30066498cd5e1a815" +dependencies = [ + "phf_generator 0.8.0", + "phf_shared 0.8.0", +] + +[[package]] +name = "phf_generator" +version = "0.8.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "17367f0cc86f2d25802b2c26ee58a7b23faeccf78a396094c13dced0d0182526" +dependencies = [ + "phf_shared 0.8.0", + "rand 0.7.3", +] + +[[package]] +name = "phf_generator" +version = "0.10.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5d5285893bb5eb82e6aaf5d59ee909a06a16737a8970984dd7746ba9283498d6" +dependencies = [ + "phf_shared 0.10.0", + "rand 0.8.5", +] + +[[package]] +name = "phf_macros" +version = "0.8.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7f6fde18ff429ffc8fe78e2bf7f8b7a5a5a6e2a8b58bc5a9ac69198bbda9189c" +dependencies = [ + "phf_generator 0.8.0", + "phf_shared 0.8.0", + "proc-macro-hack", + "proc-macro2", + "quote", + "syn", +] + +[[package]] +name = "phf_macros" +version = "0.10.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "58fdf3184dd560f160dd73922bea2d5cd6e8f064bf4b13110abd81b03697b4e0" +dependencies = [ + "phf_generator 0.10.0", + "phf_shared 0.10.0", + "proc-macro-hack", + "proc-macro2", + "quote", + "syn", +] + +[[package]] +name = "phf_shared" +version = "0.8.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c00cf8b9eafe68dde5e9eaa2cef8ee84a9336a47d566ec55ca16589633b65af7" +dependencies = [ + "siphasher", +] + +[[package]] +name = "phf_shared" +version = "0.10.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b6796ad771acdc0123d2a88dc428b5e38ef24456743ddb1744ed628f9815c096" +dependencies = [ + "siphasher", +] + +[[package]] +name = "pin-project-lite" +version = "0.2.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e0a7ae3ac2f1173085d398531c705756c94a4c56843785df85a60c1a0afac116" + +[[package]] +name = "pin-utils" +version = "0.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8b870d8c151b6f2fb93e84a13146138f05d02ed11c7e7c54f8826aaaf7c9f184" + +[[package]] +name = "pkg-config" +version = "0.3.25" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1df8c4ec4b0627e53bdf214615ad287367e482558cf84b109250b37464dc03ae" + +[[package]] +name = "png" +version = "0.11.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f0b0cabbbd20c2d7f06dbf015e06aad59b6ca3d9ed14848783e98af9aaf19925" +dependencies = [ + "bitflags", + "deflate 0.7.20", + "inflate", + "num-iter", +] + +[[package]] +name = "png" +version = "0.17.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "dc38c0ad57efb786dd57b9864e5b18bae478c00c824dc55a38bbc9da95dde3ba" +dependencies = [ + "bitflags", + "crc32fast", + "deflate 1.0.0", + "miniz_oxide", +] + +[[package]] +name = "ppv-lite86" +version = "0.2.16" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "eb9f9e6e233e5c4a35559a617bf40a4ec447db2e84c20b55a6f83167b7e57872" + +[[package]] +name = "precomputed-hash" +version = "0.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "925383efa346730478fb4838dbe9137d2a47675ad789c546d150a6e1dd4ab31c" + +[[package]] +name = "proc-macro-crate" +version = "1.1.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e17d47ce914bf4de440332250b0edd23ce48c005f59fab39d3335866b114f11a" +dependencies = [ + "thiserror", + "toml", +] + +[[package]] +name = "proc-macro-error" +version = "1.0.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "da25490ff9892aab3fcf7c36f08cfb902dd3e71ca0f9f9517bea02a73a5ce38c" +dependencies = [ + "proc-macro-error-attr", + "proc-macro2", + "quote", + "syn", + "version_check", +] + +[[package]] +name = "proc-macro-error-attr" +version = "1.0.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a1be40180e52ecc98ad80b184934baf3d0d29f979574e439af5a55274b35f869" +dependencies = [ + "proc-macro2", + "quote", + "version_check", +] + +[[package]] +name = "proc-macro-hack" +version = "0.5.19" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "dbf0c48bc1d91375ae5c3cd81e3722dff1abcf81a30960240640d223f59fe0e5" + +[[package]] +name = "proc-macro2" +version = "1.0.39" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c54b25569025b7fc9651de43004ae593a75ad88543b17178aa5e1b9c4f15f56f" +dependencies = [ + "unicode-ident", +] + +[[package]] +name = "quote" +version = "1.0.18" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a1feb54ed693b93a84e14094943b84b7c4eae204c512b7ccb95ab0c66d278ad1" +dependencies = [ + "proc-macro2", +] + +[[package]] +name = "rand" +version = "0.7.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6a6b1679d49b24bbfe0c803429aa1874472f50d9b363131f0e89fc356b544d03" +dependencies = [ + "getrandom 0.1.16", + "libc", + "rand_chacha 0.2.2", + "rand_core 0.5.1", + "rand_hc", + "rand_pcg", +] + +[[package]] +name = "rand" +version = "0.8.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "34af8d1a0e25924bc5b7c43c079c942339d8f0a8b57c39049bef581b46327404" +dependencies = [ + "libc", + "rand_chacha 0.3.1", + "rand_core 0.6.3", +] + +[[package]] +name = "rand_chacha" +version = "0.2.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f4c8ed856279c9737206bf725bf36935d8666ead7aa69b52be55af369d193402" +dependencies = [ + "ppv-lite86", + "rand_core 0.5.1", +] + +[[package]] +name = "rand_chacha" +version = "0.3.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e6c10a63a0fa32252be49d21e7709d4d4baf8d231c2dbce1eaa8141b9b127d88" +dependencies = [ + "ppv-lite86", + "rand_core 0.6.3", +] + +[[package]] +name = "rand_core" +version = "0.5.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "90bde5296fc891b0cef12a6d03ddccc162ce7b2aff54160af9338f8d40df6d19" +dependencies = [ + "getrandom 0.1.16", +] + +[[package]] +name = "rand_core" +version = "0.6.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d34f1408f55294453790c48b2f1ebbb1c5b4b7563eb1f418bcfcfdbb06ebb4e7" +dependencies = [ + "getrandom 0.2.7", +] + +[[package]] +name = "rand_hc" +version = "0.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ca3129af7b92a17112d59ad498c6f81eaf463253766b90396d39ea7a39d6613c" +dependencies = [ + "rand_core 0.5.1", +] + +[[package]] +name = "rand_pcg" +version = "0.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "16abd0c1b639e9eb4d7c50c0b8100b0d0f849be2349829c740fe8e6eb4816429" +dependencies = [ + "rand_core 0.5.1", +] + +[[package]] +name = "raw-window-handle" +version = "0.4.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b800beb9b6e7d2df1fe337c9e3d04e3af22a124460fb4c30fcc22c9117cefb41" +dependencies = [ + "cty", +] + +[[package]] +name = "redox_syscall" +version = "0.2.13" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "62f25bc4c7e55e0b0b7a1d43fb893f4fa1361d0abe38b9ce4f323c2adfe6ef42" +dependencies = [ + "bitflags", +] + +[[package]] +name = "redox_users" +version = "0.4.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b033d837a7cf162d7993aded9304e30a83213c648b6e389db233191f891e5c2b" +dependencies = [ + "getrandom 0.2.7", + "redox_syscall", + "thiserror", +] + +[[package]] +name = "regex" +version = "1.5.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d83f127d94bdbcda4c8cc2e50f6f84f4b611f69c902699ca385a39c3a75f9ff1" +dependencies = [ + "aho-corasick", + "memchr", + "regex-syntax", +] + +[[package]] +name = "regex-automata" +version = "0.1.10" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6c230d73fb8d8c1b9c0b3135c5142a8acee3a0558fb8db5cf1cb65f8d7862132" +dependencies = [ + "regex-syntax", +] + +[[package]] +name = "regex-syntax" +version = "0.6.26" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "49b3de9ec5dc0a3417da371aab17d729997c15010e7fd24ff707773a33bddb64" + +[[package]] +name = "remove_dir_all" +version = "0.5.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3acd125665422973a33ac9d3dd2df85edad0f4ae9b00dafb1a05e43a9f5ef8e7" +dependencies = [ + "winapi", +] + +[[package]] +name = "rfd" +version = "0.9.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "95281ea32d3c1ebdf84027986952e22f2bb89fa1b8b97c012be72bbc3b8e4537" +dependencies = [ + "block", + "dispatch", + "embed-resource", + "glib-sys", + "gobject-sys", + "gtk-sys", + "js-sys", + "lazy_static", + "log", + "objc", + "objc-foundation", + "objc_id", + "raw-window-handle", + "wasm-bindgen", + "wasm-bindgen-futures", + "web-sys", + "windows 0.37.0", +] + +[[package]] +name = "rustc_version" +version = "0.3.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f0dfe2087c51c460008730de8b57e6a320782fbfb312e1f4d520e6c6fae155ee" +dependencies = [ + "semver 0.11.0", +] + +[[package]] +name = "rustc_version" +version = "0.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bfa0f585226d2e68097d4f95d113b15b83a82e819ab25717ec0590d9584ef366" +dependencies = [ + "semver 1.0.10", +] + +[[package]] +name = "rustversion" +version = "1.0.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f2cc38e8fa666e2de3c4aba7edeb5ffc5246c1c2ed0e3d17e560aeeba736b23f" + +[[package]] +name = "ryu" +version = "1.0.10" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f3f6f92acf49d1b98f7a81226834412ada05458b7364277387724a237f062695" + +[[package]] +name = "same-file" +version = "1.0.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "93fc1dc3aaa9bfed95e02e6eadabb4baf7e3078b0bd1b4d7b6b0b68378900502" +dependencies = [ + "winapi-util", +] + +[[package]] +name = "schannel" +version = "0.1.20" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "88d6731146462ea25d9244b2ed5fd1d716d25c52e4d54aa4fb0f3c4e9854dbe2" +dependencies = [ + "lazy_static", + "windows-sys", +] + +[[package]] +name = "scoped-tls" +version = "1.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ea6a9290e3c9cf0f18145ef7ffa62d68ee0bf5fcd651017e586dc7fd5da448c2" + +[[package]] +name = "scopeguard" +version = "1.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d29ab0c6d3fc0ee92fe66e2d99f700eab17a8d57d1c1d3b748380fb20baa78cd" + +[[package]] +name = "security-framework" +version = "2.6.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2dc14f172faf8a0194a3aded622712b0de276821addc574fa54fc0a1167e10dc" +dependencies = [ + "bitflags", + "core-foundation", + "core-foundation-sys", + "libc", + "security-framework-sys", +] + +[[package]] +name = "security-framework-sys" +version = "2.6.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0160a13a177a45bfb43ce71c01580998474f556ad854dcbca936dd2841a5c556" +dependencies = [ + "core-foundation-sys", + "libc", +] + +[[package]] +name = "selectors" +version = "0.22.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "df320f1889ac4ba6bc0cdc9c9af7af4bd64bb927bccdf32d81140dc1f9be12fe" +dependencies = [ + "bitflags", + "cssparser", + "derive_more", + "fxhash", + "log", + "matches", + "phf 0.8.0", + "phf_codegen", + "precomputed-hash", + "servo_arc", + "smallvec", + "thin-slice", +] + +[[package]] +name = "semver" +version = "0.11.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f301af10236f6df4160f7c3f04eec6dbc70ace82d23326abad5edee88801c6b6" +dependencies = [ + "semver-parser", +] + +[[package]] +name = "semver" +version = "1.0.10" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a41d061efea015927ac527063765e73601444cdc344ba855bc7bd44578b25e1c" +dependencies = [ + "serde", +] + +[[package]] +name = "semver-parser" +version = "0.10.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "00b0bef5b7f9e0df16536d3961cfb6e84331c065b4066afb39768d0e319411f7" +dependencies = [ + "pest", +] + +[[package]] +name = "serde" +version = "1.0.137" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "61ea8d54c77f8315140a05f4c7237403bf38b72704d031543aa1d16abbf517d1" +dependencies = [ + "serde_derive", +] + +[[package]] +name = "serde_derive" +version = "1.0.137" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1f26faba0c3959972377d3b2d306ee9f71faee9714294e41bb777f83f88578be" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] + +[[package]] +name = "serde_json" +version = "1.0.81" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9b7ce2b32a1aed03c558dc61a5cd328f15aff2dbc17daad8fb8af04d2100e15c" +dependencies = [ + "itoa 1.0.2", + "ryu", + "serde", +] + +[[package]] +name = "serde_repr" +version = "0.1.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a2ad84e47328a31223de7fed7a4f5087f2d6ddfe586cf3ca25b7a165bc0a5aed" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] + +[[package]] +name = "serde_urlencoded" +version = "0.7.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d3491c14715ca2294c4d6a88f15e84739788c1d030eed8c110436aafdaa2f3fd" +dependencies = [ + "form_urlencoded", + "itoa 1.0.2", + "ryu", + "serde", +] + +[[package]] +name = "serde_with" +version = "1.14.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "678b5a069e50bf00ecd22d0cd8ddf7c236f68581b03db652061ed5eb13a312ff" +dependencies = [ + "serde", + "serde_with_macros", +] + +[[package]] +name = "serde_with_macros" +version = "1.5.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e182d6ec6f05393cc0e5ed1bf81ad6db3a8feedf8ee515ecdd369809bcce8082" +dependencies = [ + "darling", + "proc-macro2", + "quote", + "syn", +] + +[[package]] +name = "serialize-to-javascript" +version = "0.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c9823f2d3b6a81d98228151fdeaf848206a7855a7a042bbf9bf870449a66cafb" +dependencies = [ + "serde", + "serde_json", + "serialize-to-javascript-impl", +] + +[[package]] +name = "serialize-to-javascript-impl" +version = "0.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "74064874e9f6a15f04c1f3cb627902d0e6b410abbf36668afa873c61889f1763" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] + +[[package]] +name = "servo_arc" +version = "0.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d98238b800e0d1576d8b6e3de32827c2d74bee68bb97748dcf5071fb53965432" +dependencies = [ + "nodrop", + "stable_deref_trait", +] + +[[package]] +name = "sha2" +version = "0.10.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "55deaec60f81eefe3cce0dc50bda92d6d8e88f2a27df7c5033b42afeb1ed2676" +dependencies = [ + "cfg-if", + "cpufeatures", + "digest", +] + +[[package]] +name = "sharded-slab" +version = "0.1.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "900fba806f70c630b0a382d0d825e17a0f19fcd059a2ade1ff237bcddf446b31" +dependencies = [ + "lazy_static", +] + +[[package]] +name = "shared_child" +version = "1.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b0d94659ad3c2137fef23ae75b03d5241d633f8acded53d672decfa0e6e0caef" +dependencies = [ + "libc", + "winapi", +] + +[[package]] +name = "siphasher" +version = "0.3.10" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7bd3e3206899af3f8b12af284fafc038cc1dc2b41d1b89dd17297221c5d225de" + +[[package]] +name = "slab" +version = "0.4.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "eb703cfe953bccee95685111adeedb76fabe4e97549a58d16f03ea7b9367bb32" + +[[package]] +name = "smallvec" +version = "1.8.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f2dd574626839106c320a323308629dcb1acfc96e32a8cba364ddc61ac23ee83" + +[[package]] +name = "soup2" +version = "0.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b2b4d76501d8ba387cf0fefbe055c3e0a59891d09f0f995ae4e4b16f6b60f3c0" +dependencies = [ + "bitflags", + "gio", + "glib", + "libc", + "once_cell", + "soup2-sys", +] + +[[package]] +name = "soup2-sys" +version = "0.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "009ef427103fcb17f802871647a7fa6c60cbb654b4c4e4c0ac60a31c5f6dc9cf" +dependencies = [ + "bitflags", + "gio-sys", + "glib-sys", + "gobject-sys", + "libc", + "system-deps 5.0.0", +] + +[[package]] +name = "stable_deref_trait" +version = "1.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a8f112729512f8e442d81f95a8a7ddf2b7c6b8a1a6f509a95864142b30cab2d3" + +[[package]] +name = "state" +version = "0.5.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "dbe866e1e51e8260c9eed836a042a5e7f6726bb2b411dffeaa712e19c388f23b" +dependencies = [ + "loom", +] + +[[package]] +name = "string_cache" +version = "0.8.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "213494b7a2b503146286049378ce02b482200519accc31872ee8be91fa820a08" +dependencies = [ + "new_debug_unreachable", + "once_cell", + "parking_lot 0.12.1", + "phf_shared 0.10.0", + "precomputed-hash", + "serde", +] + +[[package]] +name = "string_cache_codegen" +version = "0.5.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6bb30289b722be4ff74a408c3cc27edeaad656e06cb1fe8fa9231fa59c728988" +dependencies = [ + "phf_generator 0.10.0", + "phf_shared 0.10.0", + "proc-macro2", + "quote", +] + +[[package]] +name = "strsim" +version = "0.10.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "73473c0e59e6d5812c5dfe2a064a6444949f089e20eec9a2e5506596494e4623" + +[[package]] +name = "strum" +version = "0.22.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f7ac893c7d471c8a21f31cfe213ec4f6d9afeed25537c772e08ef3f005f8729e" +dependencies = [ + "strum_macros", +] + +[[package]] +name = "strum_macros" +version = "0.22.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "339f799d8b549e3744c7ac7feb216383e4005d94bdb22561b3ab8f3b808ae9fb" +dependencies = [ + "heck 0.3.3", + "proc-macro2", + "quote", + "syn", +] + +[[package]] +name = "syn" +version = "1.0.96" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0748dd251e24453cb8717f0354206b91557e4ec8703673a4b30208f2abaf1ebf" +dependencies = [ + "proc-macro2", + "quote", + "unicode-ident", +] + +[[package]] +name = "system-deps" +version = "5.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "18db855554db7bd0e73e06cf7ba3df39f97812cb11d3f75e71c39bf45171797e" +dependencies = [ + "cfg-expr 0.9.1", + "heck 0.3.3", + "pkg-config", + "toml", + "version-compare 0.0.11", +] + +[[package]] +name = "system-deps" +version = "6.0.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a1a45a1c4c9015217e12347f2a411b57ce2c4fc543913b14b6fe40483328e709" +dependencies = [ + "cfg-expr 0.10.3", + "heck 0.4.0", + "pkg-config", + "toml", + "version-compare 0.1.0", +] + +[[package]] +name = "tao" +version = "0.11.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3bfe4c782f0543f667ee3b732d026b2f1c64af39cd52e726dec1ea1f2d8f6b80" +dependencies = [ + "bitflags", + "cairo-rs", + "cc", + "cocoa", + "core-foundation", + "core-graphics", + "crossbeam-channel", + "dispatch", + "gdk", + "gdk-pixbuf", + "gdk-sys", + "gdkx11-sys", + "gio", + "glib", + "glib-sys", + "gtk", + "image", + "instant", + "jni 0.19.0", + "lazy_static", + "libc", + "log", + "ndk", + "ndk-context", + "ndk-sys", + "objc", + "once_cell", + "parking_lot 0.11.2", + "paste", + "png 0.17.5", + "raw-window-handle", + "scopeguard", + "serde", + "tao-core-video-sys", + "unicode-segmentation", + "uuid 0.8.2", + "windows 0.37.0", + "windows-implement", + "x11-dl", +] + +[[package]] +name = "tao-core-video-sys" +version = "0.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "271450eb289cb4d8d0720c6ce70c72c8c858c93dd61fc625881616752e6b98f6" +dependencies = [ + "cfg-if", + "core-foundation-sys", + "libc", + "objc", +] + +[[package]] +name = "tar" +version = "0.4.38" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4b55807c0344e1e6c04d7c965f5289c39a8d94ae23ed5c0b57aabac549f871c6" +dependencies = [ + "filetime", + "libc", + "xattr", +] + +[[package]] +name = "tauri" +version = "1.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8e1ebb60bb8f246d5351ff9b7728fdfa7a6eba72baa722ab6021d553981caba1" +dependencies = [ + "anyhow", + "attohttpc", + "cocoa", + "dirs-next", + "embed_plist", + "flate2", + "futures", + "futures-lite", + "glib", + "glob", + "gtk", + "heck 0.4.0", + "http", + "ignore", + "notify-rust", + "objc", + "once_cell", + "open", + "os_info", + "os_pipe", + "percent-encoding", + "rand 0.8.5", + "raw-window-handle", + "regex", + "rfd", + "semver 1.0.10", + "serde", + "serde_json", + "serde_repr", + "serialize-to-javascript", + "shared_child", + "state", + "tar", + "tauri-macros", + "tauri-runtime", + "tauri-runtime-wry", + "tauri-utils", + "tempfile", + "thiserror", + "tokio", + "url", + "uuid 1.1.2", + "webkit2gtk", + "webview2-com", + "windows 0.37.0", +] + +[[package]] +name = "tauri-build" +version = "1.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e7b26eb3523e962b90012fedbfb744ca153d9be85e7981e00737e106d5323941" +dependencies = [ + "anyhow", + "cargo_toml", + "heck 0.4.0", + "semver 1.0.10", + "serde_json", + "tauri-utils", + "winres", +] + +[[package]] +name = "tauri-codegen" +version = "1.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9468c5189188c820ef605dfe4937c768cb2918e9460c8093dc4ee2cbd717b262" +dependencies = [ + "base64", + "brotli", + "ico", + "png 0.17.5", + "proc-macro2", + "quote", + "regex", + "semver 1.0.10", + "serde", + "serde_json", + "sha2", + "tauri-utils", + "thiserror", + "uuid 1.1.2", + "walkdir", +] + +[[package]] +name = "tauri-macros" +version = "1.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "40e3ffddd7a274fc7baaa260888c971a0d95d2ef403aa16600c878b8b1c00ffe" +dependencies = [ + "heck 0.4.0", + "proc-macro2", + "quote", + "syn", + "tauri-codegen", + "tauri-utils", +] + +[[package]] +name = "tauri-runtime" +version = "0.9.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "fb7dc4db360bb40584187b6cb7834da736ce4ef2ab0914e2be98014444fa9920" +dependencies = [ + "gtk", + "http", + "http-range", + "infer", + "serde", + "serde_json", + "tauri-utils", + "thiserror", + "uuid 1.1.2", + "webview2-com", + "windows 0.37.0", +] + +[[package]] +name = "tauri-runtime-wry" +version = "0.9.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c876fb3a6e7c6fe2ac466b2a6ecd83658528844b4df0914558a9bc1501b31cf3" +dependencies = [ + "cocoa", + "gtk", + "percent-encoding", + "rand 0.8.5", + "tauri-runtime", + "tauri-utils", + "uuid 1.1.2", + "webkit2gtk", + "webview2-com", + "windows 0.37.0", + "wry", +] + +[[package]] +name = "tauri-utils" +version = "1.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "727145cb55b8897fa9f2bcea4fad31dc39394703d037c9669b40f2d1c0c2d7f3" +dependencies = [ + "brotli", + "ctor", + "glob", + "heck 0.4.0", + "html5ever", + "json-patch", + "kuchiki", + "memchr", + "phf 0.10.1", + "proc-macro2", + "quote", + "semver 1.0.10", + "serde", + "serde_json", + "serde_with", + "thiserror", + "url", + "walkdir", +] + +[[package]] +name = "tempfile" +version = "3.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5cdb1ef4eaeeaddc8fbd371e5017057064af0911902ef36b39801f67cc6d79e4" +dependencies = [ + "cfg-if", + "fastrand", + "libc", + "redox_syscall", + "remove_dir_all", + "winapi", +] + +[[package]] +name = "tendril" +version = "0.4.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d24a120c5fc464a3458240ee02c299ebcb9d67b5249c8848b09d639dca8d7bb0" +dependencies = [ + "futf", + "mac", + "utf-8", +] + +[[package]] +name = "thin-slice" +version = "0.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8eaa81235c7058867fa8c0e7314f33dcce9c215f535d1913822a2b3f5e289f3c" + +[[package]] +name = "thiserror" +version = "1.0.31" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bd829fe32373d27f76265620b5309d0340cb8550f523c1dda251d6298069069a" +dependencies = [ + "thiserror-impl", +] + +[[package]] +name = "thiserror-impl" +version = "1.0.31" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0396bc89e626244658bef819e22d0cc459e795a5ebe878e6ec336d1674a8d79a" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] + +[[package]] +name = "thread_local" +version = "1.1.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5516c27b78311c50bf42c071425c560ac799b11c30b31f87e3081965fe5e0180" +dependencies = [ + "once_cell", +] + +[[package]] +name = "time" +version = "0.3.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c2702e08a7a860f005826c6815dcac101b19b5eb330c27fe4a5928fec1d20ddd" +dependencies = [ + "libc", + "num_threads", +] + +[[package]] +name = "tinyvec" +version = "1.6.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "87cc5ceb3875bb20c2890005a4e226a4651264a5c75edb2421b52861a0a0cb50" +dependencies = [ + "tinyvec_macros", +] + +[[package]] +name = "tinyvec_macros" +version = "0.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cda74da7e1a664f795bb1f8a87ec406fb89a02522cf6e50620d016add6dbbf5c" + +[[package]] +name = "tokio" +version = "1.19.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c51a52ed6686dd62c320f9b89299e9dfb46f730c7a48e635c19f21d116cb1439" +dependencies = [ + "bytes", + "memchr", + "num_cpus", + "once_cell", + "pin-project-lite", +] + +[[package]] +name = "toml" +version = "0.5.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8d82e1a7758622a465f8cee077614c73484dac5b836c02ff6a40d5d1010324d7" +dependencies = [ + "serde", +] + +[[package]] +name = "tracing" +version = "0.1.35" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a400e31aa60b9d44a52a8ee0343b5b18566b03a8321e0d321f695cf56e940160" +dependencies = [ + "cfg-if", + "pin-project-lite", + "tracing-attributes", + "tracing-core", +] + +[[package]] +name = "tracing-attributes" +version = "0.1.21" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cc6b8ad3567499f98a1db7a752b07a7c8c7c7c34c332ec00effb2b0027974b7c" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] + +[[package]] +name = "tracing-core" +version = "0.1.27" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7709595b8878a4965ce5e87ebf880a7d39c9afc6837721b21a5a816a8117d921" +dependencies = [ + "once_cell", + "valuable", +] + +[[package]] +name = "tracing-log" +version = "0.1.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "78ddad33d2d10b1ed7eb9d1f518a5674713876e97e5bb9b7345a7984fbb4f922" +dependencies = [ + "lazy_static", + "log", + "tracing-core", +] + +[[package]] +name = "tracing-subscriber" +version = "0.3.11" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4bc28f93baff38037f64e6f43d34cfa1605f27a49c34e8a04c5e78b0babf2596" +dependencies = [ + "ansi_term", + "lazy_static", + "matchers", + "regex", + "sharded-slab", + "smallvec", + "thread_local", + "tracing", + "tracing-core", + "tracing-log", +] + +[[package]] +name = "treediff" +version = "3.0.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "761e8d5ad7ce14bb82b7e61ccc0ca961005a275a060b9644a2431aa11553c2ff" +dependencies = [ + "serde_json", +] + +[[package]] +name = "typenum" +version = "1.15.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "dcf81ac59edc17cc8697ff311e8f5ef2d99fcbd9817b34cec66f90b6c3dfd987" + +[[package]] +name = "ucd-trie" +version = "0.1.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "56dee185309b50d1f11bfedef0fe6d036842e3fb77413abef29f8f8d1c5d4c1c" + +[[package]] +name = "unicode-bidi" +version = "0.3.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "099b7128301d285f79ddd55b9a83d5e6b9e97c92e0ea0daebee7263e932de992" + +[[package]] +name = "unicode-ident" +version = "1.0.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5bd2fe26506023ed7b5e1e315add59d6f584c621d037f9368fea9cfb988f368c" + +[[package]] +name = "unicode-normalization" +version = "0.1.19" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d54590932941a9e9266f0832deed84ebe1bf2e4c9e4a3554d393d18f5e854bf9" +dependencies = [ + "tinyvec", +] + +[[package]] +name = "unicode-segmentation" +version = "1.9.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7e8820f5d777f6224dc4be3632222971ac30164d4a258d595640799554ebfd99" + +[[package]] +name = "url" +version = "2.2.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a507c383b2d33b5fc35d1861e77e6b383d158b2da5e14fe51b83dfedf6fd578c" +dependencies = [ + "form_urlencoded", + "idna", + "matches", + "percent-encoding", + "serde", +] + +[[package]] +name = "utf-8" +version = "0.7.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "09cc8ee72d2a9becf2f2febe0205bbed8fc6615b7cb429ad062dc7b7ddd036a9" + +[[package]] +name = "uuid" +version = "0.8.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bc5cf98d8186244414c848017f0e2676b3fcb46807f6668a97dfe67359a3c4b7" +dependencies = [ + "getrandom 0.2.7", +] + +[[package]] +name = "uuid" +version = "1.1.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "dd6469f4314d5f1ffec476e05f17cc9a78bc7a27a6a857842170bdf8d6f98d2f" +dependencies = [ + "getrandom 0.2.7", +] + +[[package]] +name = "valuable" +version = "0.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "830b7e5d4d90034032940e4ace0d9a9a057e7a45cd94e6c007832e39edb82f6d" + +[[package]] +name = "vcpkg" +version = "0.2.15" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "accd4ea62f7bb7a82fe23066fb0957d48ef677f6eeb8215f372f52e48bb32426" + +[[package]] +name = "version-compare" +version = "0.0.11" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1c18c859eead79d8b95d09e4678566e8d70105c4e7b251f707a03df32442661b" + +[[package]] +name = "version-compare" +version = "0.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "fe88247b92c1df6b6de80ddc290f3976dbdf2f5f5d3fd049a9fb598c6dd5ca73" + +[[package]] +name = "version_check" +version = "0.9.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "49874b5167b65d7193b8aba1567f5c7d93d001cafc34600cee003eda787e483f" + +[[package]] +name = "vswhom" +version = "0.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "be979b7f07507105799e854203b470ff7c78a1639e330a58f183b5fea574608b" +dependencies = [ + "libc", + "vswhom-sys", +] + +[[package]] +name = "vswhom-sys" +version = "0.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "22025f6d8eb903ebf920ea6933b70b1e495be37e2cb4099e62c80454aaf57c39" +dependencies = [ + "cc", + "libc", +] + +[[package]] +name = "waker-fn" +version = "1.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9d5b2c62b4012a3e1eca5a7e077d13b3bf498c4073e33ccd58626607748ceeca" + +[[package]] +name = "walkdir" +version = "2.3.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "808cf2735cd4b6866113f648b791c6adc5714537bc222d9347bb203386ffda56" +dependencies = [ + "same-file", + "winapi", + "winapi-util", +] + +[[package]] +name = "wasi" +version = "0.9.0+wasi-snapshot-preview1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cccddf32554fecc6acb585f82a32a72e28b48f8c4c1883ddfeeeaa96f7d8e519" + +[[package]] +name = "wasi" +version = "0.11.0+wasi-snapshot-preview1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9c8d87e72b64a3b4db28d11ce29237c246188f4f51057d65a7eab63b7987e423" + +[[package]] +name = "wasm-bindgen" +version = "0.2.81" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7c53b543413a17a202f4be280a7e5c62a1c69345f5de525ee64f8cfdbc954994" +dependencies = [ + "cfg-if", + "wasm-bindgen-macro", +] + +[[package]] +name = "wasm-bindgen-backend" +version = "0.2.81" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5491a68ab4500fa6b4d726bd67408630c3dbe9c4fe7bda16d5c82a1fd8c7340a" +dependencies = [ + "bumpalo", + "lazy_static", + "log", + "proc-macro2", + "quote", + "syn", + "wasm-bindgen-shared", +] + +[[package]] +name = "wasm-bindgen-futures" +version = "0.4.31" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "de9a9cec1733468a8c657e57fa2413d2ae2c0129b95e87c5b72b8ace4d13f31f" +dependencies = [ + "cfg-if", + "js-sys", + "wasm-bindgen", + "web-sys", +] + +[[package]] +name = "wasm-bindgen-macro" +version = "0.2.81" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c441e177922bc58f1e12c022624b6216378e5febc2f0533e41ba443d505b80aa" +dependencies = [ + "quote", + "wasm-bindgen-macro-support", +] + +[[package]] +name = "wasm-bindgen-macro-support" +version = "0.2.81" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7d94ac45fcf608c1f45ef53e748d35660f168490c10b23704c7779ab8f5c3048" +dependencies = [ + "proc-macro2", + "quote", + "syn", + "wasm-bindgen-backend", + "wasm-bindgen-shared", +] + +[[package]] +name = "wasm-bindgen-shared" +version = "0.2.81" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6a89911bd99e5f3659ec4acf9c4d93b0a90fe4a2a11f15328472058edc5261be" + +[[package]] +name = "web-sys" +version = "0.3.58" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2fed94beee57daf8dd7d51f2b15dc2bcde92d7a72304cdf662a4371008b71b90" +dependencies = [ + "js-sys", + "wasm-bindgen", +] + +[[package]] +name = "webkit2gtk" +version = "0.18.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "29952969fb5e10fe834a52eb29ad0814ccdfd8387159b0933edf1344a1c9cdcc" +dependencies = [ + "bitflags", + "cairo-rs", + "gdk", + "gdk-sys", + "gio", + "gio-sys", + "glib", + "glib-sys", + "gobject-sys", + "gtk", + "gtk-sys", + "javascriptcore-rs", + "libc", + "once_cell", + "soup2", + "webkit2gtk-sys", +] + +[[package]] +name = "webkit2gtk-sys" +version = "0.18.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4d76ca6ecc47aeba01ec61e480139dda143796abcae6f83bcddf50d6b5b1dcf3" +dependencies = [ + "atk-sys", + "bitflags", + "cairo-sys-rs", + "gdk-pixbuf-sys", + "gdk-sys", + "gio-sys", + "glib-sys", + "gobject-sys", + "gtk-sys", + "javascriptcore-rs-sys", + "libc", + "pango-sys", + "pkg-config", + "soup2-sys", + "system-deps 6.0.2", +] + +[[package]] +name = "webview2-com" +version = "0.16.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a489a9420acabb3c2ed0434b6f71f6b56b9485ec32665a28dec1ee186d716e0f" +dependencies = [ + "webview2-com-macros", + "webview2-com-sys", + "windows 0.37.0", + "windows-implement", +] + +[[package]] +name = "webview2-com-macros" +version = "0.6.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "eaebe196c01691db62e9e4ca52c5ef1e4fd837dcae27dae3ada599b5a8fd05ac" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] + +[[package]] +name = "webview2-com-sys" +version = "0.16.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0258c53ee9adc0a4f8ba1c8c317588f7a58c7048a55b621d469ba75ab3709ca1" +dependencies = [ + "regex", + "serde", + "serde_json", + "thiserror", + "windows 0.37.0", + "windows-bindgen", +] + +[[package]] +name = "wildmatch" +version = "2.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d6c48bd20df7e4ced539c12f570f937c6b4884928a87fee70a479d72f031d4e0" + +[[package]] +name = "winapi" +version = "0.3.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5c839a674fcd7a98952e593242ea400abe93992746761e38641405d28b00f419" +dependencies = [ + "winapi-i686-pc-windows-gnu", + "winapi-x86_64-pc-windows-gnu", +] + +[[package]] +name = "winapi-i686-pc-windows-gnu" +version = "0.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ac3b87c63620426dd9b991e5ce0329eff545bccbbb34f3be09ff6fb6ab51b7b6" + +[[package]] +name = "winapi-util" +version = "0.1.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "70ec6ce85bb158151cae5e5c87f95a8e97d2c0c4b001223f33a334e3ce5de178" +dependencies = [ + "winapi", +] + +[[package]] +name = "winapi-x86_64-pc-windows-gnu" +version = "0.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "712e227841d057c1ee1cd2fb22fa7e5a5461ae8e48fa2ca79ec42cfc1931183f" + +[[package]] +name = "windows" +version = "0.24.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a9f39345ae0c8ab072c0ac7fe8a8b411636aa34f89be19ddd0d9226544f13944" +dependencies = [ + "windows_i686_gnu 0.24.0", + "windows_i686_msvc 0.24.0", + "windows_x86_64_gnu 0.24.0", + "windows_x86_64_msvc 0.24.0", +] + +[[package]] +name = "windows" +version = "0.37.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "57b543186b344cc61c85b5aab0d2e3adf4e0f99bc076eff9aa5927bcc0b8a647" +dependencies = [ + "windows-implement", + "windows_aarch64_msvc 0.37.0", + "windows_i686_gnu 0.37.0", + "windows_i686_msvc 0.37.0", + "windows_x86_64_gnu 0.37.0", + "windows_x86_64_msvc 0.37.0", +] + +[[package]] +name = "windows-bindgen" +version = "0.37.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0bed7be31ade0af08fec9b5343e9edcc005d22b1f11859b8a59b24797f5858e8" +dependencies = [ + "windows-metadata", + "windows-tokens", +] + +[[package]] +name = "windows-implement" +version = "0.37.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "67a1062e555f7d9d66fd1130ed4f7c6ec41a47529ee0850cd0e926d95b26bb14" +dependencies = [ + "syn", + "windows-tokens", +] + +[[package]] +name = "windows-metadata" +version = "0.37.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4f33f2b90a6664e369c41ab5ff262d06f048fc9685d9bf8a0e99a47750bb0463" + +[[package]] +name = "windows-sys" +version = "0.36.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ea04155a16a59f9eab786fe12a4a450e75cdb175f9e0d80da1e17db09f55b8d2" +dependencies = [ + "windows_aarch64_msvc 0.36.1", + "windows_i686_gnu 0.36.1", + "windows_i686_msvc 0.36.1", + "windows_x86_64_gnu 0.36.1", + "windows_x86_64_msvc 0.36.1", +] + +[[package]] +name = "windows-tokens" +version = "0.37.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3263d25f1170419995b78ff10c06b949e8a986c35c208dc24333c64753a87169" + +[[package]] +name = "windows_aarch64_msvc" +version = "0.36.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9bb8c3fd39ade2d67e9874ac4f3db21f0d710bee00fe7cab16949ec184eeaa47" + +[[package]] +name = "windows_aarch64_msvc" +version = "0.37.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2623277cb2d1c216ba3b578c0f3cf9cdebeddb6e66b1b218bb33596ea7769c3a" + +[[package]] +name = "windows_i686_gnu" +version = "0.24.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c0866510a3eca9aed73a077490bbbf03e5eaac4e1fd70849d89539e5830501fd" + +[[package]] +name = "windows_i686_gnu" +version = "0.36.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "180e6ccf01daf4c426b846dfc66db1fc518f074baa793aa7d9b9aaeffad6a3b6" + +[[package]] +name = "windows_i686_gnu" +version = "0.37.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d3925fd0b0b804730d44d4b6278c50f9699703ec49bcd628020f46f4ba07d9e1" + +[[package]] +name = "windows_i686_msvc" +version = "0.24.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bf0ffed56b7e9369a29078d2ab3aaeceea48eb58999d2cff3aa2494a275b95c6" + +[[package]] +name = "windows_i686_msvc" +version = "0.36.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e2e7917148b2812d1eeafaeb22a97e4813dfa60a3f8f78ebe204bcc88f12f024" + +[[package]] +name = "windows_i686_msvc" +version = "0.37.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ce907ac74fe331b524c1298683efbf598bb031bc84d5e274db2083696d07c57c" + +[[package]] +name = "windows_x86_64_gnu" +version = "0.24.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "384a173630588044205a2993b6864a2f56e5a8c1e7668c07b93ec18cf4888dc4" + +[[package]] +name = "windows_x86_64_gnu" +version = "0.36.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4dcd171b8776c41b97521e5da127a2d86ad280114807d0b2ab1e462bc764d9e1" + +[[package]] +name = "windows_x86_64_gnu" +version = "0.37.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2babfba0828f2e6b32457d5341427dcbb577ceef556273229959ac23a10af33d" + +[[package]] +name = "windows_x86_64_msvc" +version = "0.24.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9bd8f062d8ca5446358159d79a90be12c543b3a965c847c8f3eedf14b321d399" + +[[package]] +name = "windows_x86_64_msvc" +version = "0.36.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c811ca4a8c853ef420abd8592ba53ddbbac90410fab6903b3e79972a631f7680" + +[[package]] +name = "windows_x86_64_msvc" +version = "0.37.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f4dd6dc7df2d84cf7b33822ed5b86318fb1781948e9663bacd047fc9dd52259d" + +[[package]] +name = "winreg" +version = "0.10.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "80d0f4e272c85def139476380b12f9ac60926689dd2e01d4923222f40580869d" +dependencies = [ + "winapi", +] + +[[package]] +name = "winres" +version = "0.1.12" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b68db261ef59e9e52806f688020631e987592bd83619edccda9c47d42cde4f6c" +dependencies = [ + "toml", +] + +[[package]] +name = "winrt-notification" +version = "0.5.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "007a0353840b23e0c6dc73e5b962ff58ed7f6bc9ceff3ce7fe6fbad8d496edf4" +dependencies = [ + "strum", + "windows 0.24.0", + "xml-rs", +] + +[[package]] +name = "wry" +version = "0.18.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "26b1ba327c7dd4292f46bf8e6ba8e6ec2db4443b2973c9d304a359d95e0aa856" +dependencies = [ + "block", + "cocoa", + "core-graphics", + "gdk", + "gio", + "glib", + "gtk", + "http", + "jni 0.18.0", + "libc", + "log", + "objc", + "objc_id", + "once_cell", + "serde", + "serde_json", + "tao", + "thiserror", + "url", + "webkit2gtk", + "webkit2gtk-sys", + "webview2-com", + "windows 0.37.0", + "windows-implement", +] + +[[package]] +name = "x11" +version = "2.19.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6dd0565fa8bfba8c5efe02725b14dff114c866724eff2cfd44d76cea74bcd87a" +dependencies = [ + "libc", + "pkg-config", +] + +[[package]] +name = "x11-dl" +version = "2.19.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ea26926b4ce81a6f5d9d0f3a0bc401e5a37c6ae14a1bfaa8ff6099ca80038c59" +dependencies = [ + "lazy_static", + "libc", + "pkg-config", +] + +[[package]] +name = "xattr" +version = "0.2.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6d1526bbe5aaeb5eb06885f4d987bcdfa5e23187055de9b83fe00156a821fabc" +dependencies = [ + "libc", +] + +[[package]] +name = "xml-rs" +version = "0.8.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d2d7d3948613f75c98fd9328cfdcc45acc4d360655289d0a7d4ec931392200a3" diff --git a/web/src-tauri/Cargo.toml b/web/src-tauri/Cargo.toml index 940fe7e9..fc3f3b76 100644 --- a/web/src-tauri/Cargo.toml +++ b/web/src-tauri/Cargo.toml @@ -9,10 +9,10 @@ edition = "2021" rust-version = "1.57" [build-dependencies] -tauri-build = { version = "1.0.0-rc.12", features = [] } +tauri-build = { version = "1.0", features = [] } [dependencies] -tauri = { version = "1.0.0-rc.12", features = ["api-all"] } +tauri = { version = "1.0", features = ["api-all"] } [features] # by default Tauri runs in production mode diff --git a/web/src-tauri/tauri.conf.json b/web/src-tauri/tauri.conf.json index 5fde0574..934851af 100644 --- a/web/src-tauri/tauri.conf.json +++ b/web/src-tauri/tauri.conf.json @@ -60,7 +60,7 @@ }, "windows": [ { - "title": "泡泡 - 一个清新文艺的微社区", + "title": "泡泡 | 一个清新文艺的微社区", "width": 1080, "height": 800, "resizable": true, diff --git a/web/yarn.lock b/web/yarn.lock index 11d8c600..22374b50 100644 --- a/web/yarn.lock +++ b/web/yarn.lock @@ -84,65 +84,65 @@ estree-walker "^2.0.1" picomatch "^2.2.2" -"@tauri-apps/cli-darwin-arm64@1.0.0-rc.13": - version "1.0.0-rc.13" - resolved "https://registry.yarnpkg.com/@tauri-apps/cli-darwin-arm64/-/cli-darwin-arm64-1.0.0-rc.13.tgz#da73a770835ffd63a149d8becf50d3781e1c97dd" - integrity sha512-/EqOz7ASHOU98H58Ibbkg12pLG/P5oyQz8OlueaMYryajkJdmi+bHTkJ05DfbS0owAaHkRJ6f+NmoW/AnyqUbg== - -"@tauri-apps/cli-darwin-x64@1.0.0-rc.13": - version "1.0.0-rc.13" - resolved "https://registry.yarnpkg.com/@tauri-apps/cli-darwin-x64/-/cli-darwin-x64-1.0.0-rc.13.tgz#e120cc623fddca3eb9d7fea0c2de057c7440a821" - integrity sha512-bvZ0MBKFD1kc4gdVPXgwUA6tHNKj0EmlQK0Xolk6PYP9vZZeNTP1vejevW0bh2IqxC8DuqUArbG9USXwu+LFbQ== - -"@tauri-apps/cli-linux-arm-gnueabihf@1.0.0-rc.13": - version "1.0.0-rc.13" - resolved "https://registry.yarnpkg.com/@tauri-apps/cli-linux-arm-gnueabihf/-/cli-linux-arm-gnueabihf-1.0.0-rc.13.tgz#3052a59788ae57ad690d4bc09bf0756bc8808116" - integrity sha512-yODvfUkNvtYYdDTOJSDXMx9fpoEB66I2PTrYx1UKonKTEaLrQDcpw2exD/S9LPQzCYgyTuJ/kHRhG1uLdO/UUQ== - -"@tauri-apps/cli-linux-arm64-gnu@1.0.0-rc.13": - version "1.0.0-rc.13" - resolved "https://registry.yarnpkg.com/@tauri-apps/cli-linux-arm64-gnu/-/cli-linux-arm64-gnu-1.0.0-rc.13.tgz#9c4094473890c165a4fb22132229ed8212559f79" - integrity sha512-kVDJHERD8CmTeMcd2VTnD/nVCHdnNAK8a6ur3l0KTR1iF8A1AtN/sPahMQjK4f7Ar00UDjIzTw74liqakOeiZg== - -"@tauri-apps/cli-linux-arm64-musl@1.0.0-rc.13": - version "1.0.0-rc.13" - resolved "https://registry.yarnpkg.com/@tauri-apps/cli-linux-arm64-musl/-/cli-linux-arm64-musl-1.0.0-rc.13.tgz#16270a6d3b9289993b9b4d837f63dba4991d9be5" - integrity sha512-PFHz+0xKCGMqqn2TmbOSPvTRS61xJQV7srwTZjs5sHBvK536mdBnF/6V6BPEvTn5LzfRnxMu2A5X5GFkYnrZ7w== - -"@tauri-apps/cli-linux-x64-gnu@1.0.0-rc.13": - version "1.0.0-rc.13" - resolved "https://registry.yarnpkg.com/@tauri-apps/cli-linux-x64-gnu/-/cli-linux-x64-gnu-1.0.0-rc.13.tgz#d2f5031f9597300a5814dc8b4d3c59e8dc25a871" - integrity sha512-EWhTOUNHaaMM7mxp/ue+Osnzn6/o9/7qVle3MSnNI9pGQzumc/dOtBs+sWS/NPXdVEiWKET2mFMK120KJlYcQQ== - -"@tauri-apps/cli-linux-x64-musl@1.0.0-rc.13": - version "1.0.0-rc.13" - resolved "https://registry.yarnpkg.com/@tauri-apps/cli-linux-x64-musl/-/cli-linux-x64-musl-1.0.0-rc.13.tgz#9d8b02de7bd5af71c5d3d5be96ec88f3f29c8fbd" - integrity sha512-i8lsKw5iAGTAhqSQHeUCISLjhRXNrloHPoFCaSZtU0/GAPGbW/qST7u593h7cKWxRooeMwzo74ij4GhgmddClQ== - -"@tauri-apps/cli-win32-ia32-msvc@1.0.0-rc.13": - version "1.0.0-rc.13" - resolved "https://registry.yarnpkg.com/@tauri-apps/cli-win32-ia32-msvc/-/cli-win32-ia32-msvc-1.0.0-rc.13.tgz#1fcf6bed5a89af2cb30a2fe2e823ca486ded61b7" - integrity sha512-rJxSqWIQXeeT2oLzSiQyqZPgDKSGH5sK7MUr8cOCBitqy3T0COlOMX4O7hhqF3cJ/5s0aX+MuNZBzF/D0QUcxA== - -"@tauri-apps/cli-win32-x64-msvc@1.0.0-rc.13": - version "1.0.0-rc.13" - resolved "https://registry.yarnpkg.com/@tauri-apps/cli-win32-x64-msvc/-/cli-win32-x64-msvc-1.0.0-rc.13.tgz#ed2feaf3b3a120c1460cae8941443563d14840bb" - integrity sha512-ifOTrJVQoBAQUYX+EVnE4XJ/FCMHs4FQ8qxGNszqkSxrU24mmT7La6tzj77352q80KnxRa05xjjLL6GGhmzXRg== - -"@tauri-apps/cli@^1.0.0-rc.7": - version "1.0.0-rc.13" - resolved "https://registry.yarnpkg.com/@tauri-apps/cli/-/cli-1.0.0-rc.13.tgz#e58127ebe24c6cc81c3258229219056199421500" - integrity sha512-q7i45Mi1SMv5XllNoX09QS4Q/fYVFwD6piVYmqMSrKY/T5RwedQhytiVH60TxC2xk6o0akVHa7BdYiyJvXNR8A== +"@tauri-apps/cli-darwin-arm64@1.0.0": + version "1.0.0" + resolved "https://registry.yarnpkg.com/@tauri-apps/cli-darwin-arm64/-/cli-darwin-arm64-1.0.0.tgz#9ab439898b4d05a6e43df4451a42fa21e9d5eaa1" + integrity sha512-0ryomgLjdpylXypMPVXLU3PZCde3Sw5nwN4coUhBcHPBLFRb8QPet+nweVK/HiZ3mxg8WeIazvpx2s8hS0l2GQ== + +"@tauri-apps/cli-darwin-x64@1.0.0": + version "1.0.0" + resolved "https://registry.yarnpkg.com/@tauri-apps/cli-darwin-x64/-/cli-darwin-x64-1.0.0.tgz#5ffc8d444c7dc1cab14c8c743a929e448f1b1e93" + integrity sha512-oejvYUT4dEfzBi+FWMj+CMz4cZ6C2gEFHrUtKVLdTXr8Flj5UTwdB1YPGQjiOqk73LOI7cB/vXxb9DZT+Lrxgg== + +"@tauri-apps/cli-linux-arm-gnueabihf@1.0.0": + version "1.0.0" + resolved "https://registry.yarnpkg.com/@tauri-apps/cli-linux-arm-gnueabihf/-/cli-linux-arm-gnueabihf-1.0.0.tgz#c24d63b4637a0c20b9671f0284968b164482f6ee" + integrity sha512-yAu78v8TeXNx/ETS5F2G2Uw/HX+LQvZkX94zNiqFsAj7snfWI/IqSUM52OBrdh/D0EC9NCdjUJ7Vuo32uxf7tg== + +"@tauri-apps/cli-linux-arm64-gnu@1.0.0": + version "1.0.0" + resolved "https://registry.yarnpkg.com/@tauri-apps/cli-linux-arm64-gnu/-/cli-linux-arm64-gnu-1.0.0.tgz#dfb107a3d5f56dc0356126135f941261ffad10a3" + integrity sha512-YFUN/S58AN317njAynzcQ+EHhRsCDXqmp5g9Oiqmcdg1vU7fPWZivVLc1WHz+0037C7JnsX5PtKpNYewP/+Oqw== + +"@tauri-apps/cli-linux-arm64-musl@1.0.0": + version "1.0.0" + resolved "https://registry.yarnpkg.com/@tauri-apps/cli-linux-arm64-musl/-/cli-linux-arm64-musl-1.0.0.tgz#72ebfc5066c6fac82b513c20fbec6ab841b3ee05" + integrity sha512-al+TxMGoNVikEvRQfMyYE/mdjUcUNMo5brkCIAb+fL4rWQlAhAnYVzmg/rM8N4nhdXm1MOaYAagQmxr8898dNA== + +"@tauri-apps/cli-linux-x64-gnu@1.0.0": + version "1.0.0" + resolved "https://registry.yarnpkg.com/@tauri-apps/cli-linux-x64-gnu/-/cli-linux-x64-gnu-1.0.0.tgz#76f791378468a9ca2678885f0d14e05f4fd0d0c8" + integrity sha512-KQmYlYyGpn6/2kSl9QivWG6EIepm6PJd57e6IKmYwAyNhLr2XfGl1CLuocUQQgO+jprjT70HXp+MXD0tcB0+Sw== + +"@tauri-apps/cli-linux-x64-musl@1.0.0": + version "1.0.0" + resolved "https://registry.yarnpkg.com/@tauri-apps/cli-linux-x64-musl/-/cli-linux-x64-musl-1.0.0.tgz#12ccc0747c88e9c2cbdf21834b94718c8da689ca" + integrity sha512-Qpaq5lZz569Aea6jfrRchgfEJaOrfLpCRBATcF8CJFFwVKmfCUcoV+MxbCIW30Zqw5Y06njC/ffa3261AV/ZIQ== + +"@tauri-apps/cli-win32-ia32-msvc@1.0.0": + version "1.0.0" + resolved "https://registry.yarnpkg.com/@tauri-apps/cli-win32-ia32-msvc/-/cli-win32-ia32-msvc-1.0.0.tgz#5d56df6dbb62c11cae28b826619fbbad9a158399" + integrity sha512-e2DzFqEMI+s+gv14UupdI91gPxTbUJTbbfQlTHdQlOsTk4HEZTsh+ibAYBcCLAaMRW38NEsFlAUe1lQA0iRu/w== + +"@tauri-apps/cli-win32-x64-msvc@1.0.0": + version "1.0.0" + resolved "https://registry.yarnpkg.com/@tauri-apps/cli-win32-x64-msvc/-/cli-win32-x64-msvc-1.0.0.tgz#6227331d177118323cff13bc6bfb04894130c83f" + integrity sha512-lWSs90pJeQX+L31IqIzmRhwLayEeyTh7mga0AxX8G868hvdLtcXCQA/rKoFtGdVLuHAx4+M+CBF5SMYb76xGYA== + +"@tauri-apps/cli@^1.0.0": + version "1.0.0" + resolved "https://registry.yarnpkg.com/@tauri-apps/cli/-/cli-1.0.0.tgz#28f021a2db4c2087b569845d42b4fe7978ccee19" + integrity sha512-4eHnk3p0xnCXd9Zel3kLvdiiSURnN98GMFvWUAdirm5AjyOjcx8TIET/jqRYmYKE5yd+LMQqYMUfHRwA6JJUkg== optionalDependencies: - "@tauri-apps/cli-darwin-arm64" "1.0.0-rc.13" - "@tauri-apps/cli-darwin-x64" "1.0.0-rc.13" - "@tauri-apps/cli-linux-arm-gnueabihf" "1.0.0-rc.13" - "@tauri-apps/cli-linux-arm64-gnu" "1.0.0-rc.13" - "@tauri-apps/cli-linux-arm64-musl" "1.0.0-rc.13" - "@tauri-apps/cli-linux-x64-gnu" "1.0.0-rc.13" - "@tauri-apps/cli-linux-x64-musl" "1.0.0-rc.13" - "@tauri-apps/cli-win32-ia32-msvc" "1.0.0-rc.13" - "@tauri-apps/cli-win32-x64-msvc" "1.0.0-rc.13" + "@tauri-apps/cli-darwin-arm64" "1.0.0" + "@tauri-apps/cli-darwin-x64" "1.0.0" + "@tauri-apps/cli-linux-arm-gnueabihf" "1.0.0" + "@tauri-apps/cli-linux-arm64-gnu" "1.0.0" + "@tauri-apps/cli-linux-arm64-musl" "1.0.0" + "@tauri-apps/cli-linux-x64-gnu" "1.0.0" + "@tauri-apps/cli-linux-x64-musl" "1.0.0" + "@tauri-apps/cli-win32-ia32-msvc" "1.0.0" + "@tauri-apps/cli-win32-x64-msvc" "1.0.0" "@types/jest@^27.0.1": version "27.4.1" From 58171998a3ccece6e06888c079bd2fb649cfba19 Mon Sep 17 00:00:00 2001 From: alimy Date: Fri, 17 Jun 2022 12:10:27 +0800 Subject: [PATCH 23/35] desktop/tauri: optimize disable resizable transparent window and enable setfullscreen on macos --- web/src-tauri/Cargo.toml | 2 +- web/src-tauri/tauri.conf.json | 7 +++++-- 2 files changed, 6 insertions(+), 3 deletions(-) diff --git a/web/src-tauri/Cargo.toml b/web/src-tauri/Cargo.toml index fc3f3b76..460c9807 100644 --- a/web/src-tauri/Cargo.toml +++ b/web/src-tauri/Cargo.toml @@ -12,7 +12,7 @@ rust-version = "1.57" tauri-build = { version = "1.0", features = [] } [dependencies] -tauri = { version = "1.0", features = ["api-all"] } +tauri = { version = "1.0", features = ["api-all", "macos-private-api"] } [features] # by default Tauri runs in production mode diff --git a/web/src-tauri/tauri.conf.json b/web/src-tauri/tauri.conf.json index 934851af..cb0df561 100644 --- a/web/src-tauri/tauri.conf.json +++ b/web/src-tauri/tauri.conf.json @@ -10,6 +10,7 @@ "withGlobalTauri": true }, "tauri": { + "macOSPrivateApi": true, "bundle": { "active": true, "targets": "all", @@ -63,8 +64,10 @@ "title": "泡泡 | 一个清新文艺的微社区", "width": 1080, "height": 800, - "resizable": true, - "fullscreen": false + "resizable": false, + "fullscreen": false, + "transparent": true, + "decorations": true } ], "security": { From ed32dc614c36fd02f026ba10126eedc0928cef32 Mon Sep 17 00:00:00 2001 From: alimy Date: Fri, 17 Jun 2022 14:18:06 +0800 Subject: [PATCH 24/35] fixed create not expected empty string tag when create post --- internal/service/post.go | 16 ++++++++++++++-- 1 file changed, 14 insertions(+), 2 deletions(-) diff --git a/internal/service/post.go b/internal/service/post.go index 00bc494c..224cd9cc 100644 --- a/internal/service/post.go +++ b/internal/service/post.go @@ -83,12 +83,24 @@ func (p *PostContentItem) Check() error { return nil } +func tagsFrom(originTags []string) []string { + tags := make([]string, 0, len(originTags)) + for _, tag := range originTags { + // TODO: 优化tag有效性检测 + if tag = strings.TrimSpace(tag); len(tag) > 0 { + tags = append(tags, tag) + } + } + return tags +} + func CreatePost(c *gin.Context, userID int64, param PostCreationReq) (*model.Post, error) { ip := c.ClientIP() + tags := tagsFrom(param.Tags) post := &model.Post{ UserID: userID, - Tags: strings.Join(param.Tags, ","), + Tags: strings.Join(tags, ","), IP: ip, IPLoc: util.GetIPLoc(ip), AttachmentPrice: param.AttachmentPrice, @@ -99,7 +111,7 @@ func CreatePost(c *gin.Context, userID int64, param PostCreationReq) (*model.Pos } // 创建标签 - for _, t := range param.Tags { + for _, t := range tags { tag := &model.Tag{ UserID: userID, Tag: t, From be03c593380cc56a589d4039838568317943e1f4 Mon Sep 17 00:00:00 2001 From: alimy Date: Fri, 17 Jun 2022 20:43:13 +0800 Subject: [PATCH 25/35] optimize version info out logic --- Makefile | 5 ++++- internal/routers/api/home.go | 3 ++- main.go | 3 ++- pkg/debug/version.go | 25 +++++++++++++++++++++++++ 4 files changed, 33 insertions(+), 3 deletions(-) create mode 100644 pkg/debug/version.go diff --git a/Makefile b/Makefile index be109bd2..2876a0dc 100644 --- a/Makefile +++ b/Makefile @@ -13,7 +13,10 @@ BUILD_DATE := $(shell date +'%Y-%m-%d %H:%M:%S') SHA_SHORT := $(shell git rev-parse --short HEAD) TAGS = "" -LDFLAGS = -X "main.version=${BUILD_VERSION}" -X "main.buildDate=${BUILD_DATE}" -X "main.commitID=${SHA_SHORT}" -w -s +MOD_NAME = github.com/rocboss/paopao-ce +LDFLAGS = -X "${MOD_NAME}/pkg/debug.version=${BUILD_VERSION}" \ + -X "${MOD_NAME}/pkg/debug.buildDate=${BUILD_DATE}" \ + -X "${MOD_NAME}/pkg/debug.commitID=${SHA_SHORT}" -w -s all: fmt build diff --git a/internal/routers/api/home.go b/internal/routers/api/home.go index cbac6926..80489978 100644 --- a/internal/routers/api/home.go +++ b/internal/routers/api/home.go @@ -16,6 +16,7 @@ import ( "github.com/rocboss/paopao-ce/internal/service" "github.com/rocboss/paopao-ce/pkg/app" "github.com/rocboss/paopao-ce/pkg/convert" + "github.com/rocboss/paopao-ce/pkg/debug" "github.com/rocboss/paopao-ce/pkg/errcode" "github.com/rocboss/paopao-ce/pkg/util" "github.com/sirupsen/logrus" @@ -29,7 +30,7 @@ const MAX_PHONE_CAPTCHA = 10 func Version(c *gin.Context) { response := app.NewResponse(c) response.ToResponse(gin.H{ - "version": "PaoPao Service v1.0", + "BuildInfo": debug.ReadBuildInfo(), }) } diff --git a/main.go b/main.go index 17290c01..a0e0d4bc 100644 --- a/main.go +++ b/main.go @@ -11,6 +11,7 @@ import ( "github.com/rocboss/paopao-ce/internal" "github.com/rocboss/paopao-ce/internal/conf" "github.com/rocboss/paopao-ce/internal/routers" + "github.com/rocboss/paopao-ce/pkg/debug" "github.com/rocboss/paopao-ce/pkg/util" ) @@ -59,7 +60,7 @@ func main() { MaxHeaderBytes: 1 << 20, } - util.PrintHelloBanner(fmt.Sprintf("paopao %s (build:%s %s)", version, commitID, buildDate)) + util.PrintHelloBanner(debug.VersionInfo()) fmt.Fprintf(color.Output, "PaoPao service listen on %s\n", color.GreenString(fmt.Sprintf("http://%s:%s", conf.ServerSetting.HttpIp, conf.ServerSetting.HttpPort)), ) diff --git a/pkg/debug/version.go b/pkg/debug/version.go new file mode 100644 index 00000000..485e8046 --- /dev/null +++ b/pkg/debug/version.go @@ -0,0 +1,25 @@ +package debug + +import ( + "fmt" +) + +var version, commitID, buildDate string + +type BuildInfo struct { + Version string + Sum string + BuildDate string +} + +func VersionInfo() string { + return fmt.Sprintf("paopao %s (build:%s %s)", version, commitID, buildDate) +} + +func ReadBuildInfo() *BuildInfo { + return &BuildInfo{ + Version: version, + Sum: commitID, + BuildDate: buildDate, + } +} From 4aaa7323873a74832dc9ba2b6081b5dc067ba7dc Mon Sep 17 00:00:00 2001 From: alimy Date: Sun, 19 Jun 2022 18:14:24 +0800 Subject: [PATCH 26/35] add BigCacheIndex for cache index posts --- README.md | 5 +- config.yaml.sample | 7 +- go.mod | 2 + go.sum | 3 + internal/conf/conf.go | 6 + internal/conf/settting.go | 11 +- internal/core/cache.go | 1 + internal/core/version.go | 10 ++ internal/dao/cache_index_big.go | 157 ++++++++++++++++++ .../{cache_index.go => cache_index_simple.go} | 13 +- internal/dao/dao.go | 22 ++- internal/dao/post_index.go | 3 +- internal/routers/api/post.go | 3 +- internal/routers/api/user.go | 9 +- internal/routers/router.go | 6 +- internal/service/post.go | 4 +- 16 files changed, 246 insertions(+), 16 deletions(-) create mode 100644 internal/core/version.go create mode 100644 internal/dao/cache_index_big.go rename internal/dao/{cache_index.go => cache_index_simple.go} (88%) diff --git a/README.md b/README.md index 962af52d..09943391 100644 --- a/README.md +++ b/README.md @@ -274,8 +274,9 @@ Usage of release/paopao-ce: * 数据库: MySQL/Sqlite3/PostgreSQL * 对象存储: AliOSS/MinIO/LocalOSS `LocalOSS` 提供使用本地目录文件作为对象存储的功能,仅用于开发调试环境; -* 缓存: Redis/SimpleCacheIndex - `SimpleCacheIndex`提供 广场文章列表 的缓存功能; +* 缓存: Redis/SimpleCacheIndex/BigCacheIndex + `SimpleCacheIndex`提供简单的 广场推文列表 的缓存功能; + `BigCacheIndex` 使用[BigCache](https://github.com/allegro/bigcache)缓存 广场推文列表,缓存每个用户每一页,简单做到千人千面; * 搜索: Zinc * 日志: LoggerFile/LoggerZinc `LoggerFile` 使用文件写日志; diff --git a/config.yaml.sample b/config.yaml.sample index 524cf9df..38fa5a8b 100644 --- a/config.yaml.sample +++ b/config.yaml.sample @@ -13,7 +13,7 @@ Server: # 服务设置 WriteTimeout: 60 Features: Default: ["Base", "MySQL", "Option", "LocalOSS", "LoggerFile"] - Develop: ["Base", "MySQL", "Option", "Sms", "AliOSS", "LoggerZinc"] + Develop: ["Base", "MySQL", "BigCacheIndex", "Sms", "AliOSS", "LoggerZinc"] Demo: ["Base", "MySQL", "Option", "Sms", "MinIO", "LoggerZinc"] Slim: ["Base", "Sqlite3", "LocalOSS", "LoggerFile"] Base: ["Zinc", "Redis", "Alipay",] @@ -31,6 +31,11 @@ SimpleCacheIndex: # 缓存泡泡广场消息流 CheckTickDuration: 60 # 循环自检查每多少秒一次 ExpireTickDuration: 300 # 每多少秒后强制过期缓存, 设置为0禁止强制使缓存过期 ActionQPS: 100 # 添加/删除/更新Post的QPS, 默认100,范围设置[10, 10000] +BigCacheIndex: # 使用BigCache缓存泡泡广场消息流 + MaxIndexPage: 1024 # 最大缓存页数,必须是2^n, 代表最大同时缓存多少页数据 + Verbose: False # 是否打印cache操作的log + ExpireInSecond: 300 # 多少秒(>0)后强制过期缓存 + UpdateQPS: 100 # 添加/删除/更新Post的QPS, 默认100 LoggerFile: # 使用File写日志 SavePath: data/paopao-ce/logs FileName: app diff --git a/go.mod b/go.mod index de091c49..b0b7b976 100644 --- a/go.mod +++ b/go.mod @@ -3,8 +3,10 @@ module github.com/rocboss/paopao-ce go 1.16 require ( + github.com/Masterminds/semver/v3 v3.1.1 github.com/afocus/captcha v0.0.0-20191010092841-4bd1f21c8868 github.com/aliyun/aliyun-oss-go-sdk v2.2.2+incompatible + github.com/allegro/bigcache/v3 v3.0.2 github.com/baiyubin/aliyun-sts-go-sdk v0.0.0-20180326062324-cfa1a18b161f // indirect github.com/dgrijalva/jwt-go v3.2.0+incompatible github.com/disintegration/imaging v1.6.2 diff --git a/go.sum b/go.sum index 29e4614b..904a427a 100644 --- a/go.sum +++ b/go.sum @@ -82,7 +82,10 @@ github.com/alecthomas/units v0.0.0-20151022065526-2efee857e7cf/go.mod h1:ybxpYRF github.com/alecthomas/units v0.0.0-20190717042225-c3de453c63f4/go.mod h1:ybxpYRFXyAe+OPACYpWeL0wqObRcbAqCMya13uyzqw0= github.com/aliyun/aliyun-oss-go-sdk v2.2.2+incompatible h1:9gWa46nstkJ9miBReJcN8Gq34cBFbzSpQZVVT9N09TM= github.com/aliyun/aliyun-oss-go-sdk v2.2.2+incompatible/go.mod h1:T/Aws4fEfogEE9v+HPhhw+CntffsBHJ8nXQCwKr0/g8= +github.com/allegro/bigcache v1.2.1-0.20190218064605-e24eb225f156 h1:eMwmnE/GDgah4HI848JfFxHt+iPb26b4zyfspmqY0/8= github.com/allegro/bigcache v1.2.1-0.20190218064605-e24eb225f156/go.mod h1:Cb/ax3seSYIx7SuZdm2G2xzfwmv3TPSk2ucNfQESPXM= +github.com/allegro/bigcache/v3 v3.0.2 h1:AKZCw+5eAaVyNTBmI2fgyPVJhHkdWder3O9IrprcQfI= +github.com/allegro/bigcache/v3 v3.0.2/go.mod h1:aPyh7jEvrog9zAwx5N7+JUQX5dZTSGpxF1LAR4dr35I= github.com/andreyvit/diff v0.0.0-20170406064948-c7f18ee00883/go.mod h1:rCTlJbsFo29Kk6CurOXKm700vrz8f0KW0JNfpkRJY/8= github.com/antihax/optional v1.0.0/go.mod h1:uupD/76wgC+ih3iEmQUL+0Ugr19nfwCT1kdvxnR2qWY= github.com/apache/arrow/go/arrow v0.0.0-20191024131854-af6fa24be0db/go.mod h1:VTxUBvSJ3s3eHAg65PNgrsn5BtqCRPdmyXh6rAfdxN0= diff --git a/internal/conf/conf.go b/internal/conf/conf.go index 77b0c6bd..2e44f662 100644 --- a/internal/conf/conf.go +++ b/internal/conf/conf.go @@ -19,6 +19,7 @@ var ( ServerSetting *ServerSettingS AppSetting *AppSettingS SimpleCacheIndexSetting *SimpleCacheIndexSettingS + BigCacheIndexSetting *BigCacheIndexSettingS SmsJuheSetting *SmsJuheSettings AlipaySetting *AlipaySettingS ZincSetting *ZincSettingS @@ -47,6 +48,7 @@ func setupSetting(suite []string, noDefault bool) error { "App": &AppSetting, "Server": &ServerSetting, "SimpleCacheIndex": &SimpleCacheIndexSetting, + "BigCacheIndex": &BigCacheIndexSetting, "Alipay": &AlipaySetting, "SmsJuhe": &SmsJuheSetting, "LoggerFile": &loggerFileSetting, @@ -70,6 +72,10 @@ func setupSetting(suite []string, noDefault bool) error { JWTSetting.Expire *= time.Second ServerSetting.ReadTimeout *= time.Second ServerSetting.WriteTimeout *= time.Second + SimpleCacheIndexSetting.CheckTickDuration *= time.Second + SimpleCacheIndexSetting.ExpireTickDuration *= time.Second + BigCacheIndexSetting.ExpireInSecond *= time.Second + Mutex = &sync.Mutex{} return nil } diff --git a/internal/conf/settting.go b/internal/conf/settting.go index 33cb9131..182b43a5 100644 --- a/internal/conf/settting.go +++ b/internal/conf/settting.go @@ -49,11 +49,18 @@ type AppSettingS struct { type SimpleCacheIndexSettingS struct { MaxIndexSize int - CheckTickDuration int - ExpireTickDuration int + CheckTickDuration time.Duration + ExpireTickDuration time.Duration ActionQPS int } +type BigCacheIndexSettingS struct { + MaxIndexPage int + ExpireInSecond time.Duration + Verbose bool + UpdateQPS int +} + type AlipaySettingS struct { AppID string PrivateKey string diff --git a/internal/core/cache.go b/internal/core/cache.go index 07afbdb4..383f760e 100644 --- a/internal/core/cache.go +++ b/internal/core/cache.go @@ -32,6 +32,7 @@ func (a IndexActionT) String() string { // CacheIndexService cache index service interface type CacheIndexService interface { + VersionInfo IndexPostsService SendAction(active IndexActionT) } diff --git a/internal/core/version.go b/internal/core/version.go new file mode 100644 index 00000000..fd8bab2d --- /dev/null +++ b/internal/core/version.go @@ -0,0 +1,10 @@ +package core + +import ( + "github.com/Masterminds/semver/v3" +) + +type VersionInfo interface { + Name() string + Version() *semver.Version +} diff --git a/internal/dao/cache_index_big.go b/internal/dao/cache_index_big.go new file mode 100644 index 00000000..7b9615a9 --- /dev/null +++ b/internal/dao/cache_index_big.go @@ -0,0 +1,157 @@ +package dao + +import ( + "bytes" + "encoding/gob" + "fmt" + "time" + + "github.com/Masterminds/semver/v3" + "github.com/allegro/bigcache/v3" + "github.com/rocboss/paopao-ce/internal/conf" + "github.com/rocboss/paopao-ce/internal/core" + "github.com/rocboss/paopao-ce/internal/model" + "github.com/sirupsen/logrus" +) + +func newBigCacheIndexServant(getIndexPosts indexPostsFunc) *bigCacheIndexServant { + s := conf.BigCacheIndexSetting + + config := bigcache.DefaultConfig(s.ExpireInSecond) + config.Shards = s.MaxIndexPage + config.Verbose = s.Verbose + config.MaxEntrySize = 10000 + config.Logger = logrus.StandardLogger() + cache, err := bigcache.NewBigCache(config) + if err != nil { + logrus.Fatalf("initial bigCahceIndex failure by err: %v", err) + } + + cacheIndex := &bigCacheIndexServant{ + getIndexPosts: getIndexPosts, + cache: cache, + } + + // indexActionCh capacity custom configure by conf.yaml need in [10, 10000] + // or re-compile source to adjust min/max capacity + capacity := s.UpdateQPS + if capacity < 10 { + capacity = 10 + } else if capacity > 10000 { + capacity = 10000 + } + cacheIndex.indexActionCh = make(chan core.IndexActionT, capacity) + cacheIndex.cachePostsCh = make(chan *postsEntry, capacity) + + go cacheIndex.startIndexPosts() + + return cacheIndex +} + +func (s *bigCacheIndexServant) IndexPosts(userId int64, offset int, limit int) ([]*model.PostFormated, error) { + key := s.keyFrom(userId, offset, limit) + posts, err := s.getPosts(key) + if err == nil { + logrus.Debugf("get index posts from cache by key: %s userId: %d offset:%d limit:%d", key, userId, offset, limit) + return posts, nil + } + + if posts, err = s.getIndexPosts(userId, offset, limit); err != nil { + return nil, err + } + logrus.Debugf("get index posts from database by userId: %d offset:%d limit:%d", userId, offset, limit) + s.cachePosts(key, posts) + return posts, nil +} + +func (s *bigCacheIndexServant) getPosts(key string) ([]*model.PostFormated, error) { + data, err := s.cache.Get(key) + if err != nil { + logrus.Debugf("get posts by key: %s from cache err: %v", key, err) + return nil, err + } + buf := bytes.NewBuffer(data) + dec := gob.NewDecoder(buf) + var posts []*model.PostFormated + if err := dec.Decode(&posts); err != nil { + logrus.Debugf("get posts from cache in decode err: %v", err) + return nil, err + } + return posts, nil +} + +func (s *bigCacheIndexServant) cachePosts(key string, posts []*model.PostFormated) { + entry := &postsEntry{key: key, posts: posts} + select { + case s.cachePostsCh <- entry: + logrus.Debugf("send indexAction by chan of key: %s", key) + default: + go func(ch chan<- *postsEntry, entry *postsEntry) { + logrus.Debugf("send indexAction by goroutine of key: %s", key) + ch <- entry + }(s.cachePostsCh, entry) + } +} + +func (s *bigCacheIndexServant) setPosts(entry *postsEntry) { + var buf bytes.Buffer + enc := gob.NewEncoder(&buf) + if err := enc.Encode(entry.posts); err != nil { + logrus.Debugf("setPosts encode post entry err: %v", err) + return + } + if err := s.cache.Set(entry.key, buf.Bytes()); err != nil { + logrus.Debugf("setPosts set cache err: %v", err) + } + logrus.Debugf("setPosts set cache by key: %s", entry.key) +} + +func (s *bigCacheIndexServant) keyFrom(userId int64, offset int, limit int) string { + return fmt.Sprintf("index:%d:%d:%d", userId, offset, limit) +} + +func (s *bigCacheIndexServant) SendAction(act core.IndexActionT) { + select { + case s.indexActionCh <- act: + logrus.Debugf("send indexAction by chan: %s", act) + default: + go func(ch chan<- core.IndexActionT, act core.IndexActionT) { + logrus.Debugf("send indexAction by goroutine: %s", act) + ch <- act + }(s.indexActionCh, act) + } +} + +func (s *bigCacheIndexServant) startIndexPosts() { + for { + select { + case entry := <-s.cachePostsCh: + s.setPosts(entry) + case action := <-s.indexActionCh: + switch action { + // TODO: 这里列出来是因为后续可能会精细化处理每种情况 + case core.IdxActCreatePost, + core.IdxActUpdatePost, + core.IdxActDeletePost, + core.IdxActStickPost, + core.IdxActVisiblePost: + // TODO: 粗糙处理cache,后续需要针对每一种情况精细化处理 + if time.Since(s.lastCacheResetTime) > time.Minute { + s.cache.Reset() + s.lastCacheResetTime = time.Now() + logrus.Debugf("reset cache by %s", action) + } + default: + // nop + } + } + } +} + +func (s *bigCacheIndexServant) Name() string { + return "BigCacheIndex" +} + +func (s *bigCacheIndexServant) Version() *semver.Version { + return semver.MustParse("v0.1.0") +} diff --git a/internal/dao/cache_index.go b/internal/dao/cache_index_simple.go similarity index 88% rename from internal/dao/cache_index.go rename to internal/dao/cache_index_simple.go index adbd9fb2..f6c44b64 100644 --- a/internal/dao/cache_index.go +++ b/internal/dao/cache_index_simple.go @@ -4,6 +4,7 @@ import ( "errors" "time" + "github.com/Masterminds/semver/v3" "github.com/rocboss/paopao-ce/internal/conf" "github.com/rocboss/paopao-ce/internal/core" "github.com/rocboss/paopao-ce/internal/model" @@ -20,13 +21,13 @@ func newSimpleCacheIndexServant(getIndexPosts indexPostsFunc) *simpleCacheIndexS getIndexPosts: getIndexPosts, maxIndexSize: s.MaxIndexSize, indexPosts: make([]*model.PostFormated, 0), - checkTick: time.NewTicker(time.Duration(s.CheckTickDuration) * time.Second), // check whether need update index every 1 minute + checkTick: time.NewTicker(s.CheckTickDuration), // check whether need update index every 1 minute expireIndexTick: time.NewTicker(time.Second), } // force expire index every ExpireTickDuration second if s.ExpireTickDuration != 0 { - cacheIndex.expireIndexTick.Reset(time.Duration(s.CheckTickDuration) * time.Second) + cacheIndex.expireIndexTick.Reset(s.CheckTickDuration) } else { cacheIndex.expireIndexTick.Stop() } @@ -110,3 +111,11 @@ func (s *simpleCacheIndexServant) startIndexPosts() { } } } + +func (s *simpleCacheIndexServant) Name() string { + return "SimpleCacheIndex" +} + +func (s *simpleCacheIndexServant) Version() *semver.Version { + return semver.MustParse("v0.1.0") +} diff --git a/internal/dao/dao.go b/internal/dao/dao.go index fb82b71c..1c1f9886 100644 --- a/internal/dao/dao.go +++ b/internal/dao/dao.go @@ -5,6 +5,7 @@ import ( "time" "github.com/aliyun/aliyun-oss-go-sdk/oss" + "github.com/allegro/bigcache/v3" "github.com/minio/minio-go/v7" "github.com/rocboss/paopao-ce/internal/conf" "github.com/rocboss/paopao-ce/internal/core" @@ -22,6 +23,7 @@ var ( _ core.ObjectStorageService = (*localossServant)(nil) _ core.AttachmentCheckService = (*attachmentCheckServant)(nil) _ core.CacheIndexService = (*simpleCacheIndexServant)(nil) + _ core.CacheIndexService = (*bigCacheIndexServant)(nil) ) type dataServant struct { @@ -43,6 +45,18 @@ type simpleCacheIndexServant struct { expireIndexTick *time.Ticker } +type postsEntry struct { + key string + posts []*model.PostFormated +} +type bigCacheIndexServant struct { + getIndexPosts indexPostsFunc + indexActionCh chan core.IndexActionT + cachePostsCh chan *postsEntry + cache *bigcache.BigCache + lastCacheResetTime time.Time +} + type localossServant struct { savePath string domain string @@ -72,13 +86,19 @@ func NewDataService(engine *gorm.DB, zinc *zinc.ZincClient) core.DataService { } // initialize CacheIndex if needed + ds.useCacheIndex = true if conf.CfgIf("SimpleCacheIndex") { - ds.useCacheIndex = true ds.cacheIndex = newSimpleCacheIndexServant(ds.getIndexPosts) + } else if conf.CfgIf("BigCacheIndex") { + ds.cacheIndex = newBigCacheIndexServant(ds.getIndexPosts) } else { ds.useCacheIndex = false } + if ds.useCacheIndex { + logrus.Infof("use cache index service by %s for version: %s", ds.cacheIndex.Name(), ds.cacheIndex.Version()) + } + return ds } diff --git a/internal/dao/post_index.go b/internal/dao/post_index.go index 4353e938..4802f1c8 100644 --- a/internal/dao/post_index.go +++ b/internal/dao/post_index.go @@ -9,7 +9,7 @@ import ( func (d *dataServant) IndexPosts(userId int64, offset int, limit int) ([]*model.PostFormated, error) { if d.useCacheIndex { if posts, err := d.cacheIndex.IndexPosts(userId, offset, limit); err == nil { - logrus.Debugln("get index posts from cached") + logrus.Debugf("get index posts from cached by userId: %d", userId) return posts, nil } } @@ -57,6 +57,7 @@ func (d *dataServant) MergePosts(posts []*model.Post) ([]*model.PostFormated, er } // getIndexPosts _userId保留未来使用 +// TODO: 未来可能根据userId查询广场推文列表,简单做到不同用户的主页都是不同的; func (d *dataServant) getIndexPosts(_userId int64, offset int, limit int) ([]*model.PostFormated, error) { posts, err := (&model.Post{}).List(d.engine, &model.ConditionsT{ "visibility IN ?": []model.PostVisibleT{model.PostVisitPublic, model.PostVisitFriend}, diff --git a/internal/routers/api/post.go b/internal/routers/api/post.go index af352afb..0f6f62a8 100644 --- a/internal/routers/api/post.go +++ b/internal/routers/api/post.go @@ -22,9 +22,10 @@ func GetPostList(c *gin.Context) { q.Type = "tag" } + userId, _ := userIdFrom(c) if q.Query == "" && q.Type == "search" { offset, limit := app.GetPageOffset(c) - posts, err := service.GetIndexPosts(offset, limit) + posts, err := service.GetIndexPosts(userId, offset, limit) if err != nil { logrus.Errorf("service.GetPostList err: %v\n", err) response.ToErrorResponse(errcode.GetPostsFailed) diff --git a/internal/routers/api/user.go b/internal/routers/api/user.go index 9529a805..1b4e1807 100644 --- a/internal/routers/api/user.go +++ b/internal/routers/api/user.go @@ -570,6 +570,13 @@ func userFrom(c *gin.Context) (*model.User, bool) { user, ok := u.(*model.User) return user, ok } - logrus.Debugln("user not exist") return nil, false } + +func userIdFrom(c *gin.Context) (int64, bool) { + if u, exists := c.Get("UID"); exists { + uid, ok := u.(int64) + return uid, ok + } + return -1, false +} diff --git a/internal/routers/router.go b/internal/routers/router.go index 54ffb90d..80012733 100644 --- a/internal/routers/router.go +++ b/internal/routers/router.go @@ -55,9 +55,6 @@ func NewRouter() *gin.Engine { // 无鉴权路由组 noAuthApi := r.Group("/") { - // 获取广场流 - noAuthApi.GET("/posts", api.GetPostList) - // 获取动态详情 noAuthApi.GET("/post", api.GetPost) @@ -74,6 +71,9 @@ func NewRouter() *gin.Engine { // 宽松鉴权路由组 looseApi := r.Group("/").Use(middleware.JwtLoose()) { + // 获取广场流 + looseApi.GET("/posts", api.GetPostList) + // 获取用户动态列表 looseApi.GET("/user/posts", api.GetUserPosts) } diff --git a/internal/service/post.go b/internal/service/post.go index c2193eee..0f3cf28f 100644 --- a/internal/service/post.go +++ b/internal/service/post.go @@ -420,8 +420,8 @@ func GetPostContentByID(id int64) (*model.PostContent, error) { return ds.GetPostContentByID(id) } -func GetIndexPosts(offset int, limit int) ([]*model.PostFormated, error) { - return ds.IndexPosts(0, offset, limit) +func GetIndexPosts(userId int64, offset int, limit int) ([]*model.PostFormated, error) { + return ds.IndexPosts(userId, offset, limit) } func GetPostList(req *PostListReq) ([]*model.PostFormated, error) { From 61342c13a89cebff792935288d2d5a0795606864 Mon Sep 17 00:00:00 2001 From: alimy Date: Mon, 20 Jun 2022 23:59:16 +0800 Subject: [PATCH 27/35] optimize json encoding module that code from gin --- go.mod | 2 ++ go.sum | 2 ++ internal/conf/logger.go | 2 +- internal/dao/user.go | 2 +- internal/service/post.go | 2 +- pkg/json/go_json.go | 23 +++++++++++++++++++++++ pkg/json/json.go | 23 +++++++++++++++++++++++ pkg/json/jsoniter.go | 24 ++++++++++++++++++++++++ pkg/zinc/zinc.go | 2 +- 9 files changed, 78 insertions(+), 4 deletions(-) create mode 100644 pkg/json/go_json.go create mode 100644 pkg/json/json.go create mode 100644 pkg/json/jsoniter.go diff --git a/go.mod b/go.mod index b0b7b976..331a99d5 100644 --- a/go.mod +++ b/go.mod @@ -18,8 +18,10 @@ require ( github.com/go-playground/validator/v10 v10.10.1 // indirect github.com/go-redis/redis/v8 v8.11.4 github.com/go-resty/resty/v2 v2.7.0 + github.com/goccy/go-json v0.9.7 github.com/gofrs/uuid v4.0.0+incompatible github.com/google/go-cmp v0.5.7 // indirect + github.com/json-iterator/go v1.1.12 github.com/minio/minio-go/v7 v7.0.27 github.com/sirupsen/logrus v1.8.1 github.com/smartwalle/alipay/v3 v3.1.7 diff --git a/go.sum b/go.sum index 904a427a..fe11f231 100644 --- a/go.sum +++ b/go.sum @@ -273,6 +273,8 @@ github.com/go-sql-driver/mysql v1.6.0 h1:BCTh4TKNUYmOmMUcQ3IipzF5prigylS7XXjEkfC github.com/go-sql-driver/mysql v1.6.0/go.mod h1:DCzpHaOWr8IXmIStZouvnhqoel9Qv2LBy8hT2VhHyBg= github.com/go-stack/stack v1.8.0/go.mod h1:v0f6uXyyMGvRgIKkXu+yp6POWl0qKG85gN/melR3HDY= github.com/go-task/slim-sprig v0.0.0-20210107165309-348f09dbbbc0/go.mod h1:fyg7847qk6SyHyPtNmDHnmrv/HOrqktSC+C9fM+CJOE= +github.com/goccy/go-json v0.9.7 h1:IcB+Aqpx/iMHu5Yooh7jEzJk1JZ7Pjtmys2ukPr7EeM= +github.com/goccy/go-json v0.9.7/go.mod h1:6MelG93GURQebXPDq3khkgXZkazVtN9CRI+MGFi0w8I= github.com/godbus/dbus/v5 v5.0.4/go.mod h1:xhWf0FNVPg57R7Z0UbKHbJfkEywrmjJnf7w5xrFpKfA= github.com/gofrs/uuid v3.3.0+incompatible/go.mod h1:b2aQJv3Z4Fp6yNu3cdSllBxTCLRxnplIgP/c0N/04lM= github.com/gofrs/uuid v4.0.0+incompatible h1:1SD/1F5pU8p29ybwgQSwpQk+mwdRrXCYuPhW6m+TnJw= diff --git a/internal/conf/logger.go b/internal/conf/logger.go index 31724a6d..16a2b8f7 100644 --- a/internal/conf/logger.go +++ b/internal/conf/logger.go @@ -1,11 +1,11 @@ package conf import ( - "encoding/json" "fmt" "io" "time" + "github.com/rocboss/paopao-ce/pkg/json" "github.com/sirupsen/logrus" "gopkg.in/natefinch/lumberjack.v2" "gopkg.in/resty.v1" diff --git a/internal/dao/user.go b/internal/dao/user.go index ce07f5c9..7292ec1d 100644 --- a/internal/dao/user.go +++ b/internal/dao/user.go @@ -1,7 +1,6 @@ package dao import ( - "encoding/json" "errors" "fmt" "math/rand" @@ -12,6 +11,7 @@ import ( "github.com/rocboss/paopao-ce/internal/conf" "github.com/rocboss/paopao-ce/internal/model" + "github.com/rocboss/paopao-ce/pkg/json" "gopkg.in/resty.v1" ) diff --git a/internal/service/post.go b/internal/service/post.go index 0f3cf28f..e5960074 100644 --- a/internal/service/post.go +++ b/internal/service/post.go @@ -1,7 +1,6 @@ package service import ( - "encoding/json" "errors" "fmt" "math" @@ -13,6 +12,7 @@ import ( "github.com/rocboss/paopao-ce/internal/core" "github.com/rocboss/paopao-ce/internal/model" "github.com/rocboss/paopao-ce/pkg/errcode" + "github.com/rocboss/paopao-ce/pkg/json" "github.com/rocboss/paopao-ce/pkg/util" "github.com/rocboss/paopao-ce/pkg/zinc" "github.com/sirupsen/logrus" diff --git a/pkg/json/go_json.go b/pkg/json/go_json.go new file mode 100644 index 00000000..23f71726 --- /dev/null +++ b/pkg/json/go_json.go @@ -0,0 +1,23 @@ +// Copyright 2017 Bo-Yi Wu. All rights reserved. +// Use of this source code is governed by a MIT style +// license that can be found in the LICENSE file. + +//go:build go_json +// +build go_json + +package json + +import json "github.com/goccy/go-json" + +var ( + // Marshal is exported by gin/json package. + Marshal = json.Marshal + // Unmarshal is exported by gin/json package. + Unmarshal = json.Unmarshal + // MarshalIndent is exported by gin/json package. + MarshalIndent = json.MarshalIndent + // NewDecoder is exported by gin/json package. + NewDecoder = json.NewDecoder + // NewEncoder is exported by gin/json package. + NewEncoder = json.NewEncoder +) diff --git a/pkg/json/json.go b/pkg/json/json.go new file mode 100644 index 00000000..a26d7db2 --- /dev/null +++ b/pkg/json/json.go @@ -0,0 +1,23 @@ +// Copyright 2017 Bo-Yi Wu. All rights reserved. +// Use of this source code is governed by a MIT style +// license that can be found in the LICENSE file. + +//go:build !jsoniter && !go_json +// +build !jsoniter,!go_json + +package json + +import "encoding/json" + +var ( + // Marshal is exported by gin/json package. + Marshal = json.Marshal + // Unmarshal is exported by gin/json package. + Unmarshal = json.Unmarshal + // MarshalIndent is exported by gin/json package. + MarshalIndent = json.MarshalIndent + // NewDecoder is exported by gin/json package. + NewDecoder = json.NewDecoder + // NewEncoder is exported by gin/json package. + NewEncoder = json.NewEncoder +) diff --git a/pkg/json/jsoniter.go b/pkg/json/jsoniter.go new file mode 100644 index 00000000..853b1a90 --- /dev/null +++ b/pkg/json/jsoniter.go @@ -0,0 +1,24 @@ +// Copyright 2017 Bo-Yi Wu. All rights reserved. +// Use of this source code is governed by a MIT style +// license that can be found in the LICENSE file. + +//go:build jsoniter +// +build jsoniter + +package json + +import jsoniter "github.com/json-iterator/go" + +var ( + json = jsoniter.ConfigCompatibleWithStandardLibrary + // Marshal is exported by gin/json package. + Marshal = json.Marshal + // Unmarshal is exported by gin/json package. + Unmarshal = json.Unmarshal + // MarshalIndent is exported by gin/json package. + MarshalIndent = json.MarshalIndent + // NewDecoder is exported by gin/json package. + NewDecoder = json.NewDecoder + // NewEncoder is exported by gin/json package. + NewEncoder = json.NewEncoder +) diff --git a/pkg/zinc/zinc.go b/pkg/zinc/zinc.go index 8e73221d..4ea31051 100644 --- a/pkg/zinc/zinc.go +++ b/pkg/zinc/zinc.go @@ -1,7 +1,6 @@ package zinc import ( - "encoding/json" "errors" "fmt" "net/http" @@ -9,6 +8,7 @@ import ( "github.com/go-resty/resty/v2" "github.com/rocboss/paopao-ce/internal/conf" + "github.com/rocboss/paopao-ce/pkg/json" ) type ZincClient struct { From edc52e71b15efdae293f94648cf09f57fca65376 Mon Sep 17 00:00:00 2001 From: alimy Date: Tue, 21 Jun 2022 03:45:11 +0800 Subject: [PATCH 28/35] optimize search abstract service interface --- internal/core/cache.go | 1 + internal/core/core.go | 2 +- internal/core/search.go | 24 ++-- internal/core/storage.go | 2 + internal/dao/cache.go | 40 +++++++ internal/dao/dao.go | 85 +------------- internal/dao/oss.go | 54 +++++++++ internal/dao/oss_alioss.go | 9 ++ internal/dao/oss_local.go | 9 ++ internal/dao/oss_minio.go | 10 ++ internal/dao/post.go | 77 ++++++++++++ internal/dao/post_index.go | 39 ------- internal/dao/search.go | 169 +++------------------------ internal/dao/search_zinc.go | 219 +++++++++++++++++++++++++++++++++++ internal/routers/api/post.go | 6 +- internal/service/post.go | 42 +++---- internal/service/service.go | 7 +- 17 files changed, 477 insertions(+), 318 deletions(-) create mode 100644 internal/dao/cache.go create mode 100644 internal/dao/oss.go create mode 100644 internal/dao/search_zinc.go diff --git a/internal/core/cache.go b/internal/core/cache.go index 383f760e..7ea40217 100644 --- a/internal/core/cache.go +++ b/internal/core/cache.go @@ -34,5 +34,6 @@ func (a IndexActionT) String() string { type CacheIndexService interface { VersionInfo IndexPostsService + SendAction(active IndexActionT) } diff --git a/internal/core/core.go b/internal/core/core.go index adcdf537..934a77e2 100644 --- a/internal/core/core.go +++ b/internal/core/core.go @@ -7,7 +7,6 @@ import ( // DataService data service interface that process data related logic on database type DataService interface { WalletService - SearchService IndexPostsService GetComments(conditions *model.ConditionsT, offset, limit int) ([]*model.Comment, error) @@ -38,6 +37,7 @@ type DataService interface { GetPostByID(id int64) (*model.Post, error) GetPosts(conditions *model.ConditionsT, offset, limit int) ([]*model.Post, error) MergePosts(posts []*model.Post) ([]*model.PostFormated, error) + RevampPosts(posts []*model.PostFormated) ([]*model.PostFormated, error) GetPostCount(conditions *model.ConditionsT) (int64, error) UpdatePost(post *model.Post) error GetUserPostStar(postID, userID int64) (*model.PostStar, error) diff --git a/internal/core/search.go b/internal/core/search.go index 823da0d3..8bfc7253 100644 --- a/internal/core/search.go +++ b/internal/core/search.go @@ -2,7 +2,6 @@ package core import ( "github.com/rocboss/paopao-ce/internal/model" - "github.com/rocboss/paopao-ce/pkg/zinc" ) const ( @@ -12,18 +11,23 @@ const ( type SearchType string -type QueryT struct { +type QueryReq struct { Query string Visibility []model.PostVisibleT Type SearchType } -// SearchService search service interface that implement base zinc -type SearchService interface { - CreateSearchIndex(indexName string) - BulkPushDoc(data []map[string]interface{}) (bool, error) - DelDoc(indexName, id string) error - QueryAll(q *QueryT, indexName string, offset, limit int) (*zinc.QueryResultT, error) - QuerySearch(indexName, query string, offset, limit int) (*zinc.QueryResultT, error) - QueryTagSearch(indexName, query string, offset, limit int) (*zinc.QueryResultT, error) +type QueryResp struct { + Items []*model.PostFormated + Total int64 +} + +// TweetSearchService tweet search service interface +type TweetSearchService interface { + VersionInfo + + IndexName() string + AddDocuments(documents []map[string]interface{}, primaryKey ...string) (bool, error) + DeleteDocuments(identifiers []string) error + Search(q *QueryReq, offset, limit int) (*QueryResp, error) } diff --git a/internal/core/storage.go b/internal/core/storage.go index f1aa7ce7..1be1d690 100644 --- a/internal/core/storage.go +++ b/internal/core/storage.go @@ -6,6 +6,8 @@ import ( // ObjectStorageService storage service interface that implement base AliOSS、MINIO or other type ObjectStorageService interface { + VersionInfo + PutObject(objectKey string, reader io.Reader, objectSize int64, contentType string) (string, error) SignURL(objectKey string, expiredInSec int64) (string, error) ObjectURL(objetKey string) string diff --git a/internal/dao/cache.go b/internal/dao/cache.go new file mode 100644 index 00000000..143d0d27 --- /dev/null +++ b/internal/dao/cache.go @@ -0,0 +1,40 @@ +package dao + +import ( + "sync/atomic" + "time" + + "github.com/allegro/bigcache/v3" + "github.com/rocboss/paopao-ce/internal/core" + "github.com/rocboss/paopao-ce/internal/model" +) + +var ( + _ core.CacheIndexService = (*simpleCacheIndexServant)(nil) + _ core.CacheIndexService = (*bigCacheIndexServant)(nil) +) + +type postsEntry struct { + key string + posts []*model.PostFormated +} + +type indexPostsFunc func(int64, int, int) ([]*model.PostFormated, error) + +type bigCacheIndexServant struct { + getIndexPosts indexPostsFunc + indexActionCh chan core.IndexActionT + cachePostsCh chan *postsEntry + cache *bigcache.BigCache + lastCacheResetTime time.Time +} + +type simpleCacheIndexServant struct { + getIndexPosts indexPostsFunc + indexActionCh chan core.IndexActionT + indexPosts []*model.PostFormated + atomicIndex atomic.Value + maxIndexSize int + checkTick *time.Ticker + expireIndexTick *time.Ticker +} diff --git a/internal/dao/dao.go b/internal/dao/dao.go index 1c1f9886..15488018 100644 --- a/internal/dao/dao.go +++ b/internal/dao/dao.go @@ -1,15 +1,8 @@ package dao import ( - "sync/atomic" - "time" - - "github.com/aliyun/aliyun-oss-go-sdk/oss" - "github.com/allegro/bigcache/v3" - "github.com/minio/minio-go/v7" "github.com/rocboss/paopao-ce/internal/conf" "github.com/rocboss/paopao-ce/internal/core" - "github.com/rocboss/paopao-ce/internal/model" "github.com/rocboss/paopao-ce/pkg/zinc" "github.com/sirupsen/logrus" "gorm.io/gorm" @@ -17,13 +10,8 @@ import ( var ( _ core.DataService = (*dataServant)(nil) - _ core.ObjectStorageService = (*aliossServant)(nil) - _ core.ObjectStorageService = (*minioServant)(nil) - _ core.ObjectStorageService = (*s3Servant)(nil) - _ core.ObjectStorageService = (*localossServant)(nil) _ core.AttachmentCheckService = (*attachmentCheckServant)(nil) - _ core.CacheIndexService = (*simpleCacheIndexServant)(nil) - _ core.CacheIndexService = (*bigCacheIndexServant)(nil) + _ core.TweetSearchService = (*zincTweetSearchServant)(nil) ) type dataServant struct { @@ -34,55 +22,15 @@ type dataServant struct { zinc *zinc.ZincClient } -type indexPostsFunc func(int64, int, int) ([]*model.PostFormated, error) -type simpleCacheIndexServant struct { - getIndexPosts indexPostsFunc - indexActionCh chan core.IndexActionT - indexPosts []*model.PostFormated - atomicIndex atomic.Value - maxIndexSize int - checkTick *time.Ticker - expireIndexTick *time.Ticker -} - -type postsEntry struct { - key string - posts []*model.PostFormated -} -type bigCacheIndexServant struct { - getIndexPosts indexPostsFunc - indexActionCh chan core.IndexActionT - cachePostsCh chan *postsEntry - cache *bigcache.BigCache - lastCacheResetTime time.Time -} - -type localossServant struct { - savePath string - domain string -} - -type aliossServant struct { - bucket *oss.Bucket - domain string -} - -type minioServant struct { - client *minio.Client - bucket string - domain string -} - -type s3Servant = minioServant - type attachmentCheckServant struct { domain string } -func NewDataService(engine *gorm.DB, zinc *zinc.ZincClient) core.DataService { +func NewDataService() core.DataService { + client := zinc.NewClient(conf.ZincSetting) ds := &dataServant{ - engine: engine, - zinc: zinc, + engine: conf.DBEngine, + zinc: client, } // initialize CacheIndex if needed @@ -96,33 +44,12 @@ func NewDataService(engine *gorm.DB, zinc *zinc.ZincClient) core.DataService { } if ds.useCacheIndex { - logrus.Infof("use cache index service by %s for version: %s", ds.cacheIndex.Name(), ds.cacheIndex.Version()) + logrus.Infof("use %s as cache index service by version: %s", ds.cacheIndex.Name(), ds.cacheIndex.Version()) } return ds } -func NewObjectStorageService() (oss core.ObjectStorageService) { - if conf.CfgIf("AliOSS") { - oss = newAliossServent() - logrus.Infoln("use AliOSS as object storage") - } else if conf.CfgIf("MinIO") { - oss = newMinioServeant() - logrus.Infoln("use MinIO as object storage") - } else if conf.CfgIf("S3") { - oss = newS3Servent() - logrus.Infoln("use S3 as object storage") - } else if conf.CfgIf("LocalOSS") { - oss = newLocalossServent() - logrus.Infoln("use LocalOSS as object storage") - } else { - // default use AliOSS - oss = newAliossServent() - logrus.Infoln("use default AliOSS as object storage") - } - return -} - func NewAttachmentCheckerService() core.AttachmentCheckService { return &attachmentCheckServant{ domain: getOssDomain(), diff --git a/internal/dao/oss.go b/internal/dao/oss.go new file mode 100644 index 00000000..e02a7f79 --- /dev/null +++ b/internal/dao/oss.go @@ -0,0 +1,54 @@ +package dao + +import ( + "github.com/aliyun/aliyun-oss-go-sdk/oss" + "github.com/minio/minio-go/v7" + "github.com/rocboss/paopao-ce/internal/conf" + "github.com/rocboss/paopao-ce/internal/core" + "github.com/sirupsen/logrus" +) + +var ( + _ core.ObjectStorageService = (*aliossServant)(nil) + _ core.ObjectStorageService = (*minioServant)(nil) + _ core.ObjectStorageService = (*s3Servant)(nil) + _ core.ObjectStorageService = (*localossServant)(nil) +) + +type localossServant struct { + savePath string + domain string +} + +type aliossServant struct { + bucket *oss.Bucket + domain string +} + +type minioServant struct { + client *minio.Client + bucket string + domain string +} + +type s3Servant = minioServant + +func NewObjectStorageService() (oss core.ObjectStorageService) { + if conf.CfgIf("AliOSS") { + oss = newAliossServent() + } else if conf.CfgIf("MinIO") { + oss = newMinioServeant() + } else if conf.CfgIf("S3") { + oss = newS3Servent() + logrus.Infof("use S3 as object storage by version %s", oss.Version()) + return + } else if conf.CfgIf("LocalOSS") { + oss = newLocalossServent() + } else { + // default use AliOSS as object storage service + oss = newAliossServent() + logrus.Infof("use default AliOSS as object storage by version %s", oss.Version()) + } + logrus.Infof("use %s as object storage by version %s", oss.Name(), oss.Version()) + return +} diff --git a/internal/dao/oss_alioss.go b/internal/dao/oss_alioss.go index 21ae949d..641d4a54 100644 --- a/internal/dao/oss_alioss.go +++ b/internal/dao/oss_alioss.go @@ -5,6 +5,7 @@ import ( "net/url" "strings" + "github.com/Masterminds/semver/v3" "github.com/aliyun/aliyun-oss-go-sdk/oss" "github.com/rocboss/paopao-ce/internal/conf" "github.com/sirupsen/logrus" @@ -27,6 +28,14 @@ func newAliossServent() *aliossServant { } } +func (s *aliossServant) Name() string { + return "AliOSS" +} + +func (s *aliossServant) Version() *semver.Version { + return semver.MustParse("v0.1.0") +} + func (s *aliossServant) PutObject(objectKey string, reader io.Reader, objectSize int64, contentType string) (string, error) { err := s.bucket.PutObject(objectKey, reader, oss.ContentLength(objectSize), oss.ContentType(contentType)) if err != nil { diff --git a/internal/dao/oss_local.go b/internal/dao/oss_local.go index 00ed1b45..46983260 100644 --- a/internal/dao/oss_local.go +++ b/internal/dao/oss_local.go @@ -9,6 +9,7 @@ import ( "strings" "time" + "github.com/Masterminds/semver/v3" "github.com/rocboss/paopao-ce/internal/conf" "github.com/sirupsen/logrus" ) @@ -25,6 +26,14 @@ func newLocalossServent() *localossServant { } } +func (s *localossServant) Name() string { + return "LocalOSS" +} + +func (s *localossServant) Version() *semver.Version { + return semver.MustParse("v0.1.0") +} + func (s *localossServant) PutObject(objectKey string, reader io.Reader, objectSize int64, contentType string) (string, error) { saveDir := s.savePath + filepath.Dir(objectKey) err := os.MkdirAll(saveDir, 0750) diff --git a/internal/dao/oss_minio.go b/internal/dao/oss_minio.go index 053a6c10..470f03b1 100644 --- a/internal/dao/oss_minio.go +++ b/internal/dao/oss_minio.go @@ -7,6 +7,7 @@ import ( "strings" "time" + "github.com/Masterminds/semver/v3" "github.com/minio/minio-go/v7" "github.com/minio/minio-go/v7/pkg/credentials" "github.com/rocboss/paopao-ce/internal/conf" @@ -29,6 +30,14 @@ func newMinioServeant() *minioServant { } } +func (s *minioServant) Name() string { + return "MinIO" +} + +func (s *minioServant) Version() *semver.Version { + return semver.MustParse("v0.1.0") +} + func (s *minioServant) PutObject(objectKey string, reader io.Reader, objectSize int64, contentType string) (string, error) { uploadInfo, err := s.client.PutObject(context.Background(), s.bucket, objectKey, reader, objectSize, minio.PutObjectOptions{ContentType: contentType}) if err != nil { @@ -37,6 +46,7 @@ func (s *minioServant) PutObject(objectKey string, reader io.Reader, objectSize logrus.Infoln("Successfully uploaded bytes: ", uploadInfo) return s.domain + objectKey, nil } + func (s *minioServant) SignURL(objectKey string, expiredInSec int64) (string, error) { // TODO: Set request parameters for content-disposition. reqParams := make(url.Values) diff --git a/internal/dao/post.go b/internal/dao/post.go index 328de819..028d0fbc 100644 --- a/internal/dao/post.go +++ b/internal/dao/post.go @@ -207,3 +207,80 @@ func (d *dataServant) GetPostAttatchmentBill(postID, userID int64) (*model.PostA return bill.Get(d.engine) } + +// MergePosts post数据整合 +func (d *dataServant) MergePosts(posts []*model.Post) ([]*model.PostFormated, error) { + postIds := make([]int64, 0, len(posts)) + userIds := make([]int64, 0, len(posts)) + for _, post := range posts { + postIds = append(postIds, post.ID) + userIds = append(userIds, post.UserID) + } + + postContents, err := d.GetPostContentsByIDs(postIds) + if err != nil { + return nil, err + } + + users, err := d.GetUsersByIDs(userIds) + if err != nil { + return nil, err + } + + userMap := make(map[int64]*model.UserFormated, len(users)) + for _, user := range users { + userMap[user.ID] = user.Format() + } + + contentMap := make(map[int64][]*model.PostContentFormated, len(postContents)) + for _, content := range postContents { + contentMap[content.PostID] = append(contentMap[content.PostID], content.Format()) + } + + // 数据整合 + postsFormated := make([]*model.PostFormated, 0, len(posts)) + for _, post := range posts { + postFormated := post.Format() + postFormated.User = userMap[post.UserID] + postFormated.Contents = contentMap[post.ID] + postsFormated = append(postsFormated, postFormated) + } + return postsFormated, nil +} + +// RevampPosts post数据整形修复 +func (d *dataServant) RevampPosts(posts []*model.PostFormated) ([]*model.PostFormated, error) { + postIds := make([]int64, 0, len(posts)) + userIds := make([]int64, 0, len(posts)) + for _, post := range posts { + postIds = append(postIds, post.ID) + userIds = append(userIds, post.UserID) + } + + postContents, err := d.GetPostContentsByIDs(postIds) + if err != nil { + return nil, err + } + + users, err := d.GetUsersByIDs(userIds) + if err != nil { + return nil, err + } + + userMap := make(map[int64]*model.UserFormated, len(users)) + for _, user := range users { + userMap[user.ID] = user.Format() + } + + contentMap := make(map[int64][]*model.PostContentFormated, len(postContents)) + for _, content := range postContents { + contentMap[content.PostID] = append(contentMap[content.PostID], content.Format()) + } + + // 数据整合 + for _, post := range posts { + post.User = userMap[post.UserID] + post.Contents = contentMap[post.ID] + } + return posts, nil +} diff --git a/internal/dao/post_index.go b/internal/dao/post_index.go index 4802f1c8..1f35e164 100644 --- a/internal/dao/post_index.go +++ b/internal/dao/post_index.go @@ -17,45 +17,6 @@ func (d *dataServant) IndexPosts(userId int64, offset int, limit int) ([]*model. return d.getIndexPosts(userId, offset, limit) } -func (d *dataServant) MergePosts(posts []*model.Post) ([]*model.PostFormated, error) { - postIds := make([]int64, 0, len(posts)) - userIds := make([]int64, 0, len(posts)) - for _, post := range posts { - postIds = append(postIds, post.ID) - userIds = append(userIds, post.UserID) - } - - postContents, err := d.GetPostContentsByIDs(postIds) - if err != nil { - return nil, err - } - - users, err := d.GetUsersByIDs(userIds) - if err != nil { - return nil, err - } - - userMap := make(map[int64]*model.UserFormated, len(users)) - for _, user := range users { - userMap[user.ID] = user.Format() - } - - contentMap := make(map[int64][]*model.PostContentFormated, len(postContents)) - for _, content := range postContents { - contentMap[content.PostID] = append(contentMap[content.PostID], content.Format()) - } - - // 数据整合 - postsFormated := make([]*model.PostFormated, 0, len(posts)) - for _, post := range posts { - postFormated := post.Format() - postFormated.User = userMap[post.UserID] - postFormated.Contents = contentMap[post.ID] - postsFormated = append(postsFormated, postFormated) - } - return postsFormated, nil -} - // getIndexPosts _userId保留未来使用 // TODO: 未来可能根据userId查询广场推文列表,简单做到不同用户的主页都是不同的; func (d *dataServant) getIndexPosts(_userId int64, offset int, limit int) ([]*model.PostFormated, error) { diff --git a/internal/dao/search.go b/internal/dao/search.go index d30417db..992022d7 100644 --- a/internal/dao/search.go +++ b/internal/dao/search.go @@ -1,165 +1,28 @@ package dao import ( + "github.com/rocboss/paopao-ce/internal/conf" "github.com/rocboss/paopao-ce/internal/core" "github.com/rocboss/paopao-ce/pkg/zinc" + "github.com/sirupsen/logrus" ) -func (d *dataServant) CreateSearchIndex(indexName string) { - // 不存在则创建索引 - d.zinc.CreateIndex(indexName, &zinc.ZincIndexProperty{ - "id": &zinc.ZincIndexPropertyT{ - Type: "numeric", - Index: true, - Store: true, - Sortable: true, - }, - "user_id": &zinc.ZincIndexPropertyT{ - Type: "numeric", - Index: true, - Store: true, - }, - "comment_count": &zinc.ZincIndexPropertyT{ - Type: "numeric", - Index: true, - Sortable: true, - Store: true, - }, - "collection_count": &zinc.ZincIndexPropertyT{ - Type: "numeric", - Index: true, - Sortable: true, - Store: true, - }, - "upvote_count": &zinc.ZincIndexPropertyT{ - Type: "numeric", - Index: true, - Sortable: true, - Store: true, - }, - "is_top": &zinc.ZincIndexPropertyT{ - Type: "numeric", - Index: true, - Sortable: true, - Store: true, - }, - "is_essence": &zinc.ZincIndexPropertyT{ - Type: "numeric", - Index: true, - Sortable: true, - Store: true, - }, - "content": &zinc.ZincIndexPropertyT{ - Type: "text", - Index: true, - Store: true, - Aggregatable: true, - Highlightable: true, - Analyzer: "gse_search", - SearchAnalyzer: "gse_standard", - }, - "tags": &zinc.ZincIndexPropertyT{ - Type: "keyword", - Index: true, - Store: true, - }, - "ip_loc": &zinc.ZincIndexPropertyT{ - Type: "keyword", - Index: true, - Store: true, - }, - "latest_replied_on": &zinc.ZincIndexPropertyT{ - Type: "numeric", - Index: true, - Sortable: true, - Store: true, - }, - "attachment_price": &zinc.ZincIndexPropertyT{ - Type: "numeric", - Sortable: true, - Store: true, - }, - "created_on": &zinc.ZincIndexPropertyT{ - Type: "numeric", - Index: true, - Sortable: true, - Store: true, - }, - "modified_on": &zinc.ZincIndexPropertyT{ - Type: "numeric", - Index: true, - Sortable: true, - Store: true, - }, - }) - -} - -func (d *dataServant) BulkPushDoc(data []map[string]interface{}) (bool, error) { - return d.zinc.BulkPushDoc(data) -} - -func (d *dataServant) DelDoc(indexName, id string) error { - return d.zinc.DelDoc(indexName, id) -} - -func (d *dataServant) QueryAll(q *core.QueryT, indexName string, offset, limit int) (*zinc.QueryResultT, error) { - // 普通搜索 - if q.Type == core.SearchTypeDefault && q.Query != "" { - return d.QuerySearch(indexName, q.Query, offset, limit) - } - // Tag分类 - if q.Type == core.SearchTypeTag && q.Query != "" { - return d.QueryTagSearch(indexName, q.Query, offset, limit) - } - - queryMap := map[string]interface{}{ - "query": map[string]interface{}{ - "match_all": map[string]string{}, - }, - "sort": []string{"-is_top", "-latest_replied_on"}, - "from": offset, - "size": limit, - } - rsp, err := d.zinc.EsQuery(indexName, queryMap) - if err != nil { - return nil, err - } - - return rsp, err -} - -func (d *dataServant) QuerySearch(indexName, query string, offset, limit int) (*zinc.QueryResultT, error) { - rsp, err := d.zinc.EsQuery(indexName, map[string]interface{}{ - "query": map[string]interface{}{ - "match_phrase": map[string]interface{}{ - "content": query, - }, - }, - "sort": []string{"-is_top", "-latest_replied_on"}, - "from": offset, - "size": limit, - }) - if err != nil { - return nil, err - } +var ( + _ core.TweetSearchService = (*zincTweetSearchServant)(nil) +) - return rsp, err +type zincTweetSearchServant struct { + indexName string + client *zinc.ZincClient } -func (d *dataServant) QueryTagSearch(indexName, query string, offset, limit int) (*zinc.QueryResultT, error) { - rsp, err := d.zinc.ApiQuery(indexName, map[string]interface{}{ - "search_type": "querystring", - "query": map[string]interface{}{ - "term": "tags." + query + ":1", - }, - "sort_fields": []string{"-is_top", "-latest_replied_on"}, - "from": offset, - "max_results": limit, - }) - if err != nil { - return nil, err +func NewTweetSearchService() (ts core.TweetSearchService) { + if conf.CfgIf("Zinc") { + ts = newZincTweetSearchServant() + } else { + // default use Zinc as tweet search service + ts = newZincTweetSearchServant() } - - return rsp, err + logrus.Infof("use %s as tweet search serice by version %s", ts.Name(), ts.Version()) + return } diff --git a/internal/dao/search_zinc.go b/internal/dao/search_zinc.go new file mode 100644 index 00000000..3d52c2b6 --- /dev/null +++ b/internal/dao/search_zinc.go @@ -0,0 +1,219 @@ +package dao + +import ( + "github.com/Masterminds/semver/v3" + "github.com/rocboss/paopao-ce/internal/conf" + "github.com/rocboss/paopao-ce/internal/core" + "github.com/rocboss/paopao-ce/internal/model" + "github.com/rocboss/paopao-ce/pkg/json" + "github.com/rocboss/paopao-ce/pkg/zinc" +) + +func newZincTweetSearchServant() *zincTweetSearchServant { + s := conf.ZincSetting + zts := &zincTweetSearchServant{ + indexName: s.Index, + client: zinc.NewClient(s), + } + zts.createIndex() + + return zts +} + +func (s *zincTweetSearchServant) Name() string { + return "Zinc" +} + +func (s *zincTweetSearchServant) Version() *semver.Version { + return semver.MustParse("v0.1.0") +} + +func (s *zincTweetSearchServant) IndexName() string { + return s.indexName +} + +func (s *zincTweetSearchServant) AddDocuments(data []map[string]interface{}, primaryKey ...string) (bool, error) { + return s.client.BulkPushDoc(data) +} + +func (s *zincTweetSearchServant) DeleteDocuments(identifiers []string) error { + for _, id := range identifiers { + if err := s.client.DelDoc(s.indexName, id); err != nil { + return err + } + } + return nil +} + +func (s *zincTweetSearchServant) Search(q *core.QueryReq, offset, limit int) (*core.QueryResp, error) { + if q.Type == core.SearchTypeDefault && q.Query != "" { + return s.queryByContent(q, offset, limit) + } else if q.Type == core.SearchTypeTag && q.Query != "" { + return s.queryByTag(q, offset, limit) + } + return s.queryAny(offset, limit) +} + +func (s *zincTweetSearchServant) queryByContent(q *core.QueryReq, offset, limit int) (*core.QueryResp, error) { + resp, err := s.client.EsQuery(s.indexName, map[string]interface{}{ + "query": map[string]interface{}{ + "match_phrase": map[string]interface{}{ + "content": q.Query, + }, + }, + "sort": []string{"-is_top", "-latest_replied_on"}, + "from": offset, + "size": limit, + }) + if err != nil { + return nil, err + } + return s.postsFrom(resp) +} + +func (s *zincTweetSearchServant) queryByTag(q *core.QueryReq, offset, limit int) (*core.QueryResp, error) { + resp, err := s.client.ApiQuery(s.indexName, map[string]interface{}{ + "search_type": "querystring", + "query": map[string]interface{}{ + "term": "tags." + q.Query + ":1", + }, + "sort_fields": []string{"-is_top", "-latest_replied_on"}, + "from": offset, + "max_results": limit, + }) + if err != nil { + return nil, err + } + return s.postsFrom(resp) +} + +func (s *zincTweetSearchServant) queryAny(offset, limit int) (*core.QueryResp, error) { + queryMap := map[string]interface{}{ + "query": map[string]interface{}{ + "match_all": map[string]string{}, + }, + "sort": []string{"-is_top", "-latest_replied_on"}, + "from": offset, + "size": limit, + } + resp, err := s.client.EsQuery(s.indexName, queryMap) + if err != nil { + return nil, err + } + return s.postsFrom(resp) +} + +func (s *zincTweetSearchServant) postsFrom(resp *zinc.QueryResultT) (*core.QueryResp, error) { + posts := make([]*model.PostFormated, 0, len(resp.Hits.Hits)) + for _, hit := range resp.Hits.Hits { + item := &model.PostFormated{} + raw, err := json.Marshal(hit.Source) + if err != nil { + return nil, err + } + if err = json.Unmarshal(raw, item); err != nil { + return nil, err + } + posts = append(posts, item) + } + + return &core.QueryResp{ + Items: posts, + Total: resp.Hits.Total.Value, + }, nil +} + +func (s *zincTweetSearchServant) createIndex() { + // 不存在则创建索引 + s.client.CreateIndex(s.indexName, &zinc.ZincIndexProperty{ + "id": &zinc.ZincIndexPropertyT{ + Type: "numeric", + Index: true, + Store: true, + Sortable: true, + }, + "user_id": &zinc.ZincIndexPropertyT{ + Type: "numeric", + Index: true, + Store: true, + }, + "comment_count": &zinc.ZincIndexPropertyT{ + Type: "numeric", + Index: true, + Sortable: true, + Store: true, + }, + "collection_count": &zinc.ZincIndexPropertyT{ + Type: "numeric", + Index: true, + Sortable: true, + Store: true, + }, + "upvote_count": &zinc.ZincIndexPropertyT{ + Type: "numeric", + Index: true, + Sortable: true, + Store: true, + }, + "visibility": &zinc.ZincIndexPropertyT{ + Type: "numeric", + Index: true, + Sortable: true, + Store: true, + }, + "is_top": &zinc.ZincIndexPropertyT{ + Type: "numeric", + Index: true, + Sortable: true, + Store: true, + }, + "is_essence": &zinc.ZincIndexPropertyT{ + Type: "numeric", + Index: true, + Sortable: true, + Store: true, + }, + "content": &zinc.ZincIndexPropertyT{ + Type: "text", + Index: true, + Store: true, + Aggregatable: true, + Highlightable: true, + Analyzer: "gse_search", + SearchAnalyzer: "gse_standard", + }, + "tags": &zinc.ZincIndexPropertyT{ + Type: "keyword", + Index: true, + Store: true, + }, + "ip_loc": &zinc.ZincIndexPropertyT{ + Type: "keyword", + Index: true, + Store: true, + }, + "latest_replied_on": &zinc.ZincIndexPropertyT{ + Type: "numeric", + Index: true, + Sortable: true, + Store: true, + }, + "attachment_price": &zinc.ZincIndexPropertyT{ + Type: "numeric", + Sortable: true, + Store: true, + }, + "created_on": &zinc.ZincIndexPropertyT{ + Type: "numeric", + Index: true, + Sortable: true, + Store: true, + }, + "modified_on": &zinc.ZincIndexPropertyT{ + Type: "numeric", + Index: true, + Sortable: true, + Store: true, + }, + }) +} diff --git a/internal/routers/api/post.go b/internal/routers/api/post.go index 0f6f62a8..a779532b 100644 --- a/internal/routers/api/post.go +++ b/internal/routers/api/post.go @@ -14,7 +14,7 @@ import ( func GetPostList(c *gin.Context) { response := app.NewResponse(c) - q := &core.QueryT{ + q := &core.QueryReq{ Query: c.Query("query"), Type: "search", } @@ -23,8 +23,8 @@ func GetPostList(c *gin.Context) { } userId, _ := userIdFrom(c) + offset, limit := app.GetPageOffset(c) if q.Query == "" && q.Type == "search" { - offset, limit := app.GetPageOffset(c) posts, err := service.GetIndexPosts(userId, offset, limit) if err != nil { logrus.Errorf("service.GetPostList err: %v\n", err) @@ -38,7 +38,7 @@ func GetPostList(c *gin.Context) { response.ToResponseList(posts, totalRows) } else { - posts, totalRows, err := service.GetPostListFromSearch(q, (app.GetPage(c)-1)*app.GetPageSize(c), app.GetPageSize(c)) + posts, totalRows, err := service.GetPostListFromSearch(q, offset, limit) if err != nil { logrus.Errorf("service.GetPostListFromSearch err: %v\n", err) diff --git a/internal/service/post.go b/internal/service/post.go index e5960074..5a4a4d92 100644 --- a/internal/service/post.go +++ b/internal/service/post.go @@ -478,32 +478,24 @@ func GetPostCount(conditions *model.ConditionsT) (int64, error) { return ds.GetPostCount(conditions) } -func GetPostListFromSearch(q *core.QueryT, offset, limit int) ([]*model.PostFormated, int64, error) { - queryResult, err := ds.QueryAll(q, conf.ZincSetting.Index, offset, limit) +func GetPostListFromSearch(q *core.QueryReq, offset, limit int) ([]*model.PostFormated, int64, error) { + resp, err := ts.Search(q, offset, limit) if err != nil { return nil, 0, err } - - posts, err := FormatZincPost(queryResult) + posts, err := ds.RevampPosts(resp.Items) if err != nil { return nil, 0, err } - - return posts, queryResult.Hits.Total.Value, nil + return posts, resp.Total, nil } func GetPostListFromSearchByQuery(query string, offset, limit int) ([]*model.PostFormated, int64, error) { - queryResult, err := ds.QuerySearch(conf.ZincSetting.Index, query, offset, limit) - if err != nil { - return nil, 0, err - } - - posts, err := FormatZincPost(queryResult) - if err != nil { - return nil, 0, err + q := &core.QueryReq{ + Query: query, + Type: "search", } - - return posts, queryResult.Hits.Total.Value, nil + return GetPostListFromSearch(q, offset, limit) } func PushPostToSearch(post *model.Post) { @@ -512,8 +504,6 @@ func PushPostToSearch(post *model.Post) { return } - indexName := conf.ZincSetting.Index - postFormated := post.Format() postFormated.User = &model.UserFormated{ ID: post.UserID, @@ -539,7 +529,7 @@ func PushPostToSearch(post *model.Post) { data := []map[string]interface{}{} data = append(data, map[string]interface{}{ "index": map[string]interface{}{ - "_index": indexName, + "_index": ts.IndexName(), "_id": fmt.Sprintf("%d", post.ID), }, }, map[string]interface{}{ @@ -560,13 +550,11 @@ func PushPostToSearch(post *model.Post) { "modified_on": post.ModifiedOn, }) - ds.BulkPushDoc(data) + ts.AddDocuments(data) } func DeleteSearchPost(post *model.Post) error { - indexName := conf.ZincSetting.Index - - return ds.DelDoc(indexName, fmt.Sprintf("%d", post.ID)) + return ts.DeleteDocuments([]string{fmt.Sprintf("%d", post.ID)}) } func PushPostsToSearch(c *gin.Context) { @@ -579,10 +567,6 @@ func PushPostsToSearch(c *gin.Context) { pages := math.Ceil(float64(totalRows) / float64(splitNum)) nums := int(pages) - indexName := conf.ZincSetting.Index - // 创建索引 - ds.CreateSearchIndex(indexName) - for i := 0; i < nums; i++ { data := []map[string]interface{}{} @@ -605,7 +589,7 @@ func PushPostsToSearch(c *gin.Context) { data = append(data, map[string]interface{}{ "index": map[string]interface{}{ - "_index": indexName, + "_index": ts.IndexName(), "_id": fmt.Sprintf("%d", post.ID), }, }, map[string]interface{}{ @@ -628,7 +612,7 @@ func PushPostsToSearch(c *gin.Context) { } if len(data) > 0 { - ds.BulkPushDoc(data) + ts.AddDocuments(data) } } diff --git a/internal/service/service.go b/internal/service/service.go index 26682173..3ed9d261 100644 --- a/internal/service/service.go +++ b/internal/service/service.go @@ -4,19 +4,18 @@ import ( "github.com/rocboss/paopao-ce/internal/conf" "github.com/rocboss/paopao-ce/internal/core" "github.com/rocboss/paopao-ce/internal/dao" - "github.com/rocboss/paopao-ce/pkg/zinc" ) var ( ds core.DataService + ts core.TweetSearchService attachmentChecker core.AttachmentCheckService DisablePhoneVerify bool ) func Initialize() { - zincClient := zinc.NewClient(conf.ZincSetting) - ds = dao.NewDataService(conf.DBEngine, zincClient) - + ds = dao.NewDataService() + ts = dao.NewTweetSearchService() attachmentChecker = dao.NewAttachmentCheckerService() DisablePhoneVerify = !conf.CfgIf("Sms") } From 2fc1fd2f9b2ffd9fa6b717b1f20f4c822f93d4d7 Mon Sep 17 00:00:00 2001 From: alimy Date: Tue, 21 Jun 2022 14:45:57 +0800 Subject: [PATCH 29/35] optimize #118 wrap async interface for update documents to search engine --- config.yaml.sample | 12 +++--- internal/conf/conf.go | 4 ++ internal/conf/settting.go | 10 ++++- internal/core/search.go | 4 +- internal/dao/cache_index_big.go | 7 ++-- internal/dao/cache_index_simple.go | 2 +- internal/dao/dao.go | 2 +- internal/dao/oss.go | 1 + internal/dao/search.go | 36 ++++++++++++++--- internal/dao/search_bridge.go | 65 ++++++++++++++++++++++++++++++ internal/dao/search_zinc.go | 2 +- internal/routers/api/api.go | 2 +- internal/service/comment.go | 6 +-- internal/service/post.go | 19 +++++---- internal/service/service.go | 2 +- 15 files changed, 140 insertions(+), 34 deletions(-) create mode 100644 internal/dao/search_bridge.go diff --git a/config.yaml.sample b/config.yaml.sample index 38fa5a8b..b02b2ece 100644 --- a/config.yaml.sample +++ b/config.yaml.sample @@ -26,16 +26,16 @@ SmsJuhe: Alipay: AppID: PrivateKey: +CacheIndex: + MaxUpdateQPS: 100 # 最大添加/删除/更新Post的QPS, 设置范围[10, 10000], 默认100 SimpleCacheIndex: # 缓存泡泡广场消息流 MaxIndexSize: 200 # 最大缓存条数 CheckTickDuration: 60 # 循环自检查每多少秒一次 ExpireTickDuration: 300 # 每多少秒后强制过期缓存, 设置为0禁止强制使缓存过期 - ActionQPS: 100 # 添加/删除/更新Post的QPS, 默认100,范围设置[10, 10000] BigCacheIndex: # 使用BigCache缓存泡泡广场消息流 - MaxIndexPage: 1024 # 最大缓存页数,必须是2^n, 代表最大同时缓存多少页数据 - Verbose: False # 是否打印cache操作的log - ExpireInSecond: 300 # 多少秒(>0)后强制过期缓存 - UpdateQPS: 100 # 添加/删除/更新Post的QPS, 默认100 + MaxIndexPage: 1024 # 最大缓存页数,必须是2^n, 代表最大同时缓存多少页数据 + Verbose: False # 是否打印cache操作的log + ExpireInSecond: 300 # 多少秒(>0)后强制过期缓存 LoggerFile: # 使用File写日志 SavePath: data/paopao-ce/logs FileName: app @@ -51,6 +51,8 @@ JWT: # 鉴权加密 Secret: 18a6413dc4fe394c66345ebe501b2f26 Issuer: paopao-api Expire: 86400 +TweetSearch: # 推文关键字搜索相关配置 + MaxUpdateQPS: 100 # 最大添加/删除/更新Post的QPS,设置范围[10, 10000], 默认100 Zinc: # Zinc搜索配置 Host: http://zinc:4080 Index: paopao-data diff --git a/internal/conf/conf.go b/internal/conf/conf.go index 2e44f662..6f22f46f 100644 --- a/internal/conf/conf.go +++ b/internal/conf/conf.go @@ -18,10 +18,12 @@ var ( ServerSetting *ServerSettingS AppSetting *AppSettingS + CacheIndexSetting *CacheIndexSettingS SimpleCacheIndexSetting *SimpleCacheIndexSettingS BigCacheIndexSetting *BigCacheIndexSettingS SmsJuheSetting *SmsJuheSettings AlipaySetting *AlipaySettingS + TweetSearchSetting *TweetSearchS ZincSetting *ZincSettingS AliOSSSetting *AliOSSSettingS MinIOSetting *MinIOSettingS @@ -47,6 +49,7 @@ func setupSetting(suite []string, noDefault bool) error { objects := map[string]interface{}{ "App": &AppSetting, "Server": &ServerSetting, + "CacheIndex": &CacheIndexSetting, "SimpleCacheIndex": &SimpleCacheIndexSetting, "BigCacheIndex": &BigCacheIndexSetting, "Alipay": &AlipaySetting, @@ -57,6 +60,7 @@ func setupSetting(suite []string, noDefault bool) error { "MySQL": &mysqlSetting, "Postgres": &postgresSetting, "Sqlite3": &sqlite3Setting, + "TweetSearch": &TweetSearchSetting, "Zinc": &ZincSetting, "Redis": &redisSetting, "JWT": &JWTSetting, diff --git a/internal/conf/settting.go b/internal/conf/settting.go index 182b43a5..cb03e715 100644 --- a/internal/conf/settting.go +++ b/internal/conf/settting.go @@ -47,18 +47,20 @@ type AppSettingS struct { TronApiKeys []string } +type CacheIndexSettingS struct { + MaxUpdateQPS int +} + type SimpleCacheIndexSettingS struct { MaxIndexSize int CheckTickDuration time.Duration ExpireTickDuration time.Duration - ActionQPS int } type BigCacheIndexSettingS struct { MaxIndexPage int ExpireInSecond time.Duration Verbose bool - UpdateQPS int } type AlipaySettingS struct { @@ -78,6 +80,10 @@ type FeaturesSettingS struct { features map[string]string } +type TweetSearchS struct { + MaxUpdateQPS int +} + type ZincSettingS struct { Host string Index string diff --git a/internal/core/search.go b/internal/core/search.go index 8bfc7253..36583f68 100644 --- a/internal/core/search.go +++ b/internal/core/search.go @@ -22,12 +22,14 @@ type QueryResp struct { Total int64 } +type DocItems []map[string]interface{} + // TweetSearchService tweet search service interface type TweetSearchService interface { VersionInfo IndexName() string - AddDocuments(documents []map[string]interface{}, primaryKey ...string) (bool, error) + AddDocuments(documents DocItems, primaryKey ...string) (bool, error) DeleteDocuments(identifiers []string) error Search(q *QueryReq, offset, limit int) (*QueryResp, error) } diff --git a/internal/dao/cache_index_big.go b/internal/dao/cache_index_big.go index 7b9615a9..d221bdb5 100644 --- a/internal/dao/cache_index_big.go +++ b/internal/dao/cache_index_big.go @@ -34,7 +34,7 @@ func newBigCacheIndexServant(getIndexPosts indexPostsFunc) *bigCacheIndexServant // indexActionCh capacity custom configure by conf.yaml need in [10, 10000] // or re-compile source to adjust min/max capacity - capacity := s.UpdateQPS + capacity := conf.CacheIndexSetting.MaxUpdateQPS if capacity < 10 { capacity = 10 } else if capacity > 10000 { @@ -43,6 +43,7 @@ func newBigCacheIndexServant(getIndexPosts indexPostsFunc) *bigCacheIndexServant cacheIndex.indexActionCh = make(chan core.IndexActionT, capacity) cacheIndex.cachePostsCh = make(chan *postsEntry, capacity) + // 启动索引更新器 go cacheIndex.startIndexPosts() return cacheIndex @@ -84,10 +85,10 @@ func (s *bigCacheIndexServant) cachePosts(key string, posts []*model.PostFormate entry := &postsEntry{key: key, posts: posts} select { case s.cachePostsCh <- entry: - logrus.Debugf("send indexAction by chan of key: %s", key) + logrus.Debugf("cachePosts by chan of key: %s", key) default: go func(ch chan<- *postsEntry, entry *postsEntry) { - logrus.Debugf("send indexAction by goroutine of key: %s", key) + logrus.Debugf("cachePosts indexAction by goroutine of key: %s", key) ch <- entry }(s.cachePostsCh, entry) } diff --git a/internal/dao/cache_index_simple.go b/internal/dao/cache_index_simple.go index f6c44b64..a5014b7a 100644 --- a/internal/dao/cache_index_simple.go +++ b/internal/dao/cache_index_simple.go @@ -34,7 +34,7 @@ func newSimpleCacheIndexServant(getIndexPosts indexPostsFunc) *simpleCacheIndexS // indexActionCh capacity custom configure by conf.yaml need in [10, 10000] // or re-compile source to adjust min/max capacity - capacity := s.ActionQPS + capacity := conf.CacheIndexSetting.MaxUpdateQPS if capacity < 10 { capacity = 10 } else if capacity > 10000 { diff --git a/internal/dao/dao.go b/internal/dao/dao.go index 15488018..71826803 100644 --- a/internal/dao/dao.go +++ b/internal/dao/dao.go @@ -50,7 +50,7 @@ func NewDataService() core.DataService { return ds } -func NewAttachmentCheckerService() core.AttachmentCheckService { +func NewAttachmentCheckService() core.AttachmentCheckService { return &attachmentCheckServant{ domain: getOssDomain(), } diff --git a/internal/dao/oss.go b/internal/dao/oss.go index e02a7f79..a85bb8d9 100644 --- a/internal/dao/oss.go +++ b/internal/dao/oss.go @@ -48,6 +48,7 @@ func NewObjectStorageService() (oss core.ObjectStorageService) { // default use AliOSS as object storage service oss = newAliossServent() logrus.Infof("use default AliOSS as object storage by version %s", oss.Version()) + return } logrus.Infof("use %s as object storage by version %s", oss.Name(), oss.Version()) return diff --git a/internal/dao/search.go b/internal/dao/search.go index 992022d7..deda5969 100644 --- a/internal/dao/search.go +++ b/internal/dao/search.go @@ -9,20 +9,46 @@ import ( var ( _ core.TweetSearchService = (*zincTweetSearchServant)(nil) + _ core.TweetSearchService = (*bridgeTweetSearchServant)(nil) ) +type documents struct { + primaryKey []string + docItems core.DocItems + identifiers []string +} + +type bridgeTweetSearchServant struct { + ts core.TweetSearchService + updateDocsCh chan *documents +} + type zincTweetSearchServant struct { indexName string client *zinc.ZincClient } -func NewTweetSearchService() (ts core.TweetSearchService) { +func NewTweetSearchService() core.TweetSearchService { + bts := &bridgeTweetSearchServant{} + + capacity := conf.TweetSearchSetting.MaxUpdateQPS + if capacity < 10 { + capacity = 10 + } else if capacity > 10000 { + capacity = 10000 + } + bts.updateDocsCh = make(chan *documents, capacity) + if conf.CfgIf("Zinc") { - ts = newZincTweetSearchServant() + bts.ts = newZincTweetSearchServant() } else { // default use Zinc as tweet search service - ts = newZincTweetSearchServant() + bts.ts = newZincTweetSearchServant() } - logrus.Infof("use %s as tweet search serice by version %s", ts.Name(), ts.Version()) - return + logrus.Infof("use %s as tweet search serice by version %s", bts.ts.Name(), bts.ts.Version()) + + // 启动文档更新器 + go bts.startUpdateDocs() + + return bts } diff --git a/internal/dao/search_bridge.go b/internal/dao/search_bridge.go new file mode 100644 index 00000000..45766474 --- /dev/null +++ b/internal/dao/search_bridge.go @@ -0,0 +1,65 @@ +package dao + +import ( + "github.com/Masterminds/semver/v3" + "github.com/rocboss/paopao-ce/internal/core" + "github.com/sirupsen/logrus" +) + +func (s *bridgeTweetSearchServant) Name() string { + return "BridgeTweetSearch" +} + +func (s *bridgeTweetSearchServant) Version() *semver.Version { + return semver.MustParse("v0.1.0") +} + +func (s *bridgeTweetSearchServant) IndexName() string { + return s.ts.IndexName() +} + +func (s *bridgeTweetSearchServant) AddDocuments(data core.DocItems, primaryKey ...string) (bool, error) { + s.updateDocs(&documents{ + primaryKey: primaryKey, + docItems: data, + }) + return true, nil +} + +func (s *bridgeTweetSearchServant) DeleteDocuments(identifiers []string) error { + s.updateDocs(&documents{ + identifiers: identifiers, + }) + return nil +} + +func (s *bridgeTweetSearchServant) Search(q *core.QueryReq, offset, limit int) (*core.QueryResp, error) { + return s.ts.Search(q, offset, limit) +} + +func (s *bridgeTweetSearchServant) updateDocs(doc *documents) { + select { + case s.updateDocsCh <- doc: + logrus.Debugln("addDocuments send documents by chan") + default: + go func(ch chan<- *documents, item *documents) { + s.updateDocsCh <- item + logrus.Debugln("addDocuments send documents by goroutine") + }(s.updateDocsCh, doc) + } +} + +func (s *bridgeTweetSearchServant) startUpdateDocs() { + for doc := range s.updateDocsCh { + if len(doc.docItems) > 0 { + if _, err := s.ts.AddDocuments(doc.docItems, doc.primaryKey...); err != nil { + logrus.Errorf("addDocuments occurs error: %v", err) + } + } + if len(doc.identifiers) > 0 { + if err := s.ts.DeleteDocuments(doc.identifiers); err != nil { + logrus.Errorf("deleteDocuments occurs error: %s", err) + } + } + } +} diff --git a/internal/dao/search_zinc.go b/internal/dao/search_zinc.go index 3d52c2b6..6cda0503 100644 --- a/internal/dao/search_zinc.go +++ b/internal/dao/search_zinc.go @@ -32,7 +32,7 @@ func (s *zincTweetSearchServant) IndexName() string { return s.indexName } -func (s *zincTweetSearchServant) AddDocuments(data []map[string]interface{}, primaryKey ...string) (bool, error) { +func (s *zincTweetSearchServant) AddDocuments(data core.DocItems, primaryKey ...string) (bool, error) { return s.client.BulkPushDoc(data) } diff --git a/internal/routers/api/api.go b/internal/routers/api/api.go index 9049e110..26b65a4e 100644 --- a/internal/routers/api/api.go +++ b/internal/routers/api/api.go @@ -12,5 +12,5 @@ var ( func Initialize() { objectStorage = dao.NewObjectStorageService() - attachmentChecker = dao.NewAttachmentCheckerService() + attachmentChecker = dao.NewAttachmentCheckService() } diff --git a/internal/service/comment.go b/internal/service/comment.go index 4cd7fb24..8805c106 100644 --- a/internal/service/comment.go +++ b/internal/service/comment.go @@ -136,7 +136,7 @@ func CreatePostComment(ctx *gin.Context, userID int64, param CommentCreationReq) ds.UpdatePost(post) // 更新索引 - go PushPostToSearch(post) + PushPostToSearch(post) // 创建用户消息提醒 postMaster, err := ds.GetUserByID(post.UserID) @@ -251,7 +251,7 @@ func CreatePostCommentReply(ctx *gin.Context, commentID int64, content string, u ds.UpdatePost(post) // 更新索引 - go PushPostToSearch(post) + PushPostToSearch(post) // 创建用户消息提醒 commentMaster, err := ds.GetUserByID(comment.UserID) @@ -325,7 +325,7 @@ func DeletePostCommentReply(reply *model.CommentReply) error { ds.UpdatePost(post) // 更新索引 - go PushPostToSearch(post) + PushPostToSearch(post) return nil } diff --git a/internal/service/post.go b/internal/service/post.go index 5a4a4d92..2401f0ef 100644 --- a/internal/service/post.go +++ b/internal/service/post.go @@ -177,8 +177,7 @@ func CreatePost(c *gin.Context, userID int64, param PostCreationReq) (*model.Pos }) } // 推送Search - // TODO: 优化推送文章到搜索的处理机制,最好使用通道channel传递文章,可以省goroutine - go PushPostToSearch(post) + PushPostToSearch(post) } return post, nil @@ -204,7 +203,7 @@ func DeletePost(id int64) error { } // 删除索引 - go DeleteSearchPost(post) + DeleteSearchPost(post) return nil } @@ -263,11 +262,11 @@ func VisiblePost(user *model.User, postId int64, visibility model.PostVisibleT) if oldVisibility == model.PostVisitPrivate { // 从私密转为非私密需要push logrus.Debugf("visible post set to re-public to add search index: %d, visibility: %s", post.ID, visibility) - go PushPostToSearch(post) + PushPostToSearch(post) } else if visibility == model.PostVisitPrivate { // 从非私密转为私密需要删除索引 logrus.Debugf("visible post set to private to delete search index: %d, visibility: %s", post.ID, visibility) - go DeleteSearchPost(post) + DeleteSearchPost(post) } return nil } @@ -298,7 +297,7 @@ func CreatePostStar(postID, userID int64) (*model.PostStar, error) { ds.UpdatePost(post) // 更新索引 - go PushPostToSearch(post) + PushPostToSearch(post) return star, nil } @@ -324,7 +323,7 @@ func DeletePostStar(star *model.PostStar) error { ds.UpdatePost(post) // 更新索引 - go PushPostToSearch(post) + PushPostToSearch(post) return nil } @@ -355,7 +354,7 @@ func CreatePostCollection(postID, userID int64) (*model.PostCollection, error) { ds.UpdatePost(post) // 更新索引 - go PushPostToSearch(post) + PushPostToSearch(post) return collection, nil } @@ -381,7 +380,7 @@ func DeletePostCollection(collection *model.PostCollection) error { ds.UpdatePost(post) // 更新索引 - go PushPostToSearch(post) + PushPostToSearch(post) return nil } @@ -526,7 +525,7 @@ func PushPostToSearch(post *model.Post) { tagMaps[tag] = 1 } - data := []map[string]interface{}{} + data := core.DocItems{} data = append(data, map[string]interface{}{ "index": map[string]interface{}{ "_index": ts.IndexName(), diff --git a/internal/service/service.go b/internal/service/service.go index 3ed9d261..8593f754 100644 --- a/internal/service/service.go +++ b/internal/service/service.go @@ -16,6 +16,6 @@ var ( func Initialize() { ds = dao.NewDataService() ts = dao.NewTweetSearchService() - attachmentChecker = dao.NewAttachmentCheckerService() + attachmentChecker = dao.NewAttachmentCheckService() DisablePhoneVerify = !conf.CfgIf("Sms") } From 1facc41068f281ee14124c439df2e8c63ccc37a1 Mon Sep 17 00:00:00 2001 From: alimy Date: Tue, 21 Jun 2022 15:30:55 +0800 Subject: [PATCH 30/35] optimize #118 add backend worker update documents to search engine --- config.yaml.sample | 1 + internal/conf/settting.go | 2 ++ internal/dao/search.go | 11 ++++++++++- internal/dao/search_bridge.go | 18 ++++++++++++------ 4 files changed, 25 insertions(+), 7 deletions(-) diff --git a/config.yaml.sample b/config.yaml.sample index b02b2ece..dc4a81ac 100644 --- a/config.yaml.sample +++ b/config.yaml.sample @@ -53,6 +53,7 @@ JWT: # 鉴权加密 Expire: 86400 TweetSearch: # 推文关键字搜索相关配置 MaxUpdateQPS: 100 # 最大添加/删除/更新Post的QPS,设置范围[10, 10000], 默认100 + MinWorker: 10 # 最小后台更新工作者, 设置范围[5, 1000], 默认10 Zinc: # Zinc搜索配置 Host: http://zinc:4080 Index: paopao-data diff --git a/internal/conf/settting.go b/internal/conf/settting.go index cb03e715..9bceceb9 100644 --- a/internal/conf/settting.go +++ b/internal/conf/settting.go @@ -49,6 +49,7 @@ type AppSettingS struct { type CacheIndexSettingS struct { MaxUpdateQPS int + MinWorker int } type SimpleCacheIndexSettingS struct { @@ -82,6 +83,7 @@ type FeaturesSettingS struct { type TweetSearchS struct { MaxUpdateQPS int + MinWorker int } type ZincSettingS struct { diff --git a/internal/dao/search.go b/internal/dao/search.go index deda5969..0b25f4ca 100644 --- a/internal/dao/search.go +++ b/internal/dao/search.go @@ -47,8 +47,17 @@ func NewTweetSearchService() core.TweetSearchService { } logrus.Infof("use %s as tweet search serice by version %s", bts.ts.Name(), bts.ts.Version()) + numWorker := conf.TweetSearchSetting.MinWorker + if numWorker < 5 { + numWorker = 5 + } else if numWorker > 1000 { + numWorker = 1000 + } + logrus.Debugf("use %d backend worker to update documents to search engine", numWorker) // 启动文档更新器 - go bts.startUpdateDocs() + for ; numWorker > 0; numWorker-- { + go bts.startUpdateDocs() + } return bts } diff --git a/internal/dao/search_bridge.go b/internal/dao/search_bridge.go index 45766474..26b96f92 100644 --- a/internal/dao/search_bridge.go +++ b/internal/dao/search_bridge.go @@ -42,10 +42,17 @@ func (s *bridgeTweetSearchServant) updateDocs(doc *documents) { case s.updateDocsCh <- doc: logrus.Debugln("addDocuments send documents by chan") default: - go func(ch chan<- *documents, item *documents) { - s.updateDocsCh <- item - logrus.Debugln("addDocuments send documents by goroutine") - }(s.updateDocsCh, doc) + go func(item *documents) { + if len(item.docItems) > 0 { + if _, err := s.ts.AddDocuments(item.docItems, item.primaryKey...); err != nil { + logrus.Errorf("addDocuments in gorotine occurs error: %v", err) + } + } else if len(item.identifiers) > 0 { + if err := s.ts.DeleteDocuments(item.identifiers); err != nil { + logrus.Errorf("deleteDocuments in gorotine occurs error: %s", err) + } + } + }(doc) } } @@ -55,8 +62,7 @@ func (s *bridgeTweetSearchServant) startUpdateDocs() { if _, err := s.ts.AddDocuments(doc.docItems, doc.primaryKey...); err != nil { logrus.Errorf("addDocuments occurs error: %v", err) } - } - if len(doc.identifiers) > 0 { + } else if len(doc.identifiers) > 0 { if err := s.ts.DeleteDocuments(doc.identifiers); err != nil { logrus.Errorf("deleteDocuments occurs error: %s", err) } From 807d7f92b058b5f7e4559ccd194adb6cdd53af19 Mon Sep 17 00:00:00 2001 From: alimy Date: Tue, 21 Jun 2022 23:55:50 +0800 Subject: [PATCH 31/35] optimize #118 just pretty code for zinc client logic --- pkg/zinc/zinc.go | 36 +++++++++++++++--------------------- 1 file changed, 15 insertions(+), 21 deletions(-) diff --git a/pkg/zinc/zinc.go b/pkg/zinc/zinc.go index 4ea31051..63dfeeb1 100644 --- a/pkg/zinc/zinc.go +++ b/pkg/zinc/zinc.go @@ -90,9 +90,7 @@ func (c *ZincClient) CreateIndex(name string, p *ZincIndexProperty) bool { Properties: p, }, } - client := resty.New() // 创建一个restry客户端 - client.DisableWarn = true - resp, err := client.R().SetBody(data).SetBasicAuth(c.ZincUser, c.ZincPassword).Put(c.ZincHost + "/api/index") + resp, err := c.request().SetBody(data).Put(c.ZincHost + "/api/index") if err != nil || resp.StatusCode() != http.StatusOK { return false @@ -103,9 +101,7 @@ func (c *ZincClient) CreateIndex(name string, p *ZincIndexProperty) bool { // 检查索引是否存在 func (c *ZincClient) ExistIndex(name string) bool { - client := resty.New() - client.DisableWarn = true - resp, err := client.R().SetBasicAuth(c.ZincUser, c.ZincPassword).Get(c.ZincHost + "/api/index") + resp, err := c.request().Get(c.ZincHost + "/api/index") if err != nil || resp.StatusCode() != http.StatusOK { return false @@ -126,9 +122,7 @@ func (c *ZincClient) ExistIndex(name string) bool { // 新增/更新文档 func (c *ZincClient) PutDoc(name string, id int64, doc interface{}) (bool, error) { - client := resty.New() - client.DisableWarn = true - resp, err := client.R().SetBody(doc).SetBasicAuth(c.ZincUser, c.ZincPassword).Put(fmt.Sprintf("%s/api/%s/_doc/%d", c.ZincHost, name, id)) + resp, err := c.request().SetBody(doc).Put(fmt.Sprintf("%s/api/%s/_doc/%d", c.ZincHost, name, id)) if err != nil { return false, err @@ -151,9 +145,7 @@ func (c *ZincClient) BulkPushDoc(docs []map[string]interface{}) (bool, error) { } } - client := resty.New() - client.DisableWarn = true - resp, err := client.R().SetBody(dataStr).SetBasicAuth(c.ZincUser, c.ZincPassword).Post(fmt.Sprintf("%s/api/_bulk", c.ZincHost)) + resp, err := c.request().SetBody(dataStr).Post(fmt.Sprintf("%s/api/_bulk", c.ZincHost)) if err != nil { return false, err } @@ -166,9 +158,7 @@ func (c *ZincClient) BulkPushDoc(docs []map[string]interface{}) (bool, error) { } func (c *ZincClient) EsQuery(indexName string, q interface{}) (*QueryResultT, error) { - client := resty.New() - client.DisableWarn = true - resp, err := client.R().SetBody(q).SetBasicAuth(c.ZincUser, c.ZincPassword).Post(fmt.Sprintf("%s/es/%s/_search", c.ZincHost, indexName)) + resp, err := c.request().SetBody(q).Post(fmt.Sprintf("%s/es/%s/_search", c.ZincHost, indexName)) if err != nil { return nil, err } @@ -187,9 +177,7 @@ func (c *ZincClient) EsQuery(indexName string, q interface{}) (*QueryResultT, er } func (c *ZincClient) ApiQuery(indexName string, q interface{}) (*QueryResultT, error) { - client := resty.New() - client.DisableWarn = true - resp, err := client.R().SetBody(q).SetBasicAuth(c.ZincUser, c.ZincPassword).Post(fmt.Sprintf("%s/api/%s/_search", c.ZincHost, indexName)) + resp, err := c.request().SetBody(q).Post(fmt.Sprintf("%s/api/%s/_search", c.ZincHost, indexName)) if err != nil { return nil, err } @@ -208,9 +196,7 @@ func (c *ZincClient) ApiQuery(indexName string, q interface{}) (*QueryResultT, e } func (c *ZincClient) DelDoc(indexName, id string) error { - client := resty.New() - client.DisableWarn = true - resp, err := client.R().SetBasicAuth(c.ZincUser, c.ZincPassword).Delete(fmt.Sprintf("%s/api/%s/_doc/%s", c.ZincHost, indexName, id)) + resp, err := c.request().Delete(fmt.Sprintf("%s/api/%s/_doc/%s", c.ZincHost, indexName, id)) if err != nil { return err } @@ -221,3 +207,11 @@ func (c *ZincClient) DelDoc(indexName, id string) error { return nil } + +func (c *ZincClient) request() *resty.Request { + client := resty.New() + client.DisableWarn = true + client.SetBasicAuth(c.ZincUser, c.ZincPassword) + + return client.R() +} From 9c551fce4b7c84aa5847e05276d122a6de90b248 Mon Sep 17 00:00:00 2001 From: ROC Date: Wed, 22 Jun 2022 19:28:25 +0800 Subject: [PATCH 32/35] optimize docs. --- README.md | 79 +++++++++++++++++++++++++++++++++---------------------- 1 file changed, 48 insertions(+), 31 deletions(-) diff --git a/README.md b/README.md index 09943391..fc80c9c5 100644 --- a/README.md +++ b/README.md @@ -70,11 +70,12 @@ PaoPao主要由以下优秀的开源项目/工具构建 ### 安装说明 -***宝塔安装*** -我们为宝塔用户提供了超详细安装教程 [点此查看](https://www.rocs.me/archives/paopao_bt_install.html) +### 方式一. 宝塔安装 -***普通安装*** +我们为宝塔用户提供了超详细安装教程(v0.1.0版本),仅供参考,[点此查看](https://www.rocs.me/archives/paopao_bt_install.html) + +### 方式二. 手动安装 克隆代码库 @@ -154,49 +155,51 @@ PaoPao主要由以下优秀的开源项目/工具构建 的,需要安装tauri的依赖,具体参考[https://tauri.studio/v1/guides/getting-started/prerequisites](https://tauri.studio/v1/guides/getting-started/prerequisites). -### 使用Docker构建、运行 +### 方式三. 使用Docker构建、运行 * 后端: ```sh # 默认参数构建, 默认内嵌web ui并设置api host为空 - %> docker build -t your/paopao-ce:tag . + docker build -t your/paopao-ce:tag . # 内嵌web ui并且自定义API host参数 - %> docker build -t your/paopao-ce:tag --build-arg API_HOST=http://paopao.info . + docker build -t your/paopao-ce:tag --build-arg API_HOST=http://paopao.info . # 内嵌web ui并且使用本地web/.env中的API host - %> docker build -t your/paopao-ce:tag --build-arg USE_API_HOST=no . + docker build -t your/paopao-ce:tag --build-arg USE_API_HOST=no . # 内嵌web ui并且使用本地编译的web/dist构建 - %> docker build -t your/paopao-ce:tag --build-arg USE_DIST=yes . + docker build -t your/paopao-ce:tag --build-arg USE_DIST=yes . # 只编译api server - %> docker build -t your/paopao-ce:tag --build-arg EMBED_UI=no . + docker build -t your/paopao-ce:tag --build-arg EMBED_UI=no . # 运行 - %> docker run -p 8008:8008 -v ${PWD}/config.yaml.sample:/app/paopao-ce/config.yaml your/paopao-ce:tag + docker run -p 8008:8008 -v ${PWD}/config.yaml.sample:/app/paopao-ce/config.yaml your/paopao-ce:tag ``` * 前端: ```sh - %> cd web + cd web # 默认参数构建 - %> docker build -t your/paopao-ce:web . + docker build -t your/paopao-ce:web . # 自定义API host 参数构建 - %> docker build -t your/paopao-ce:web --build-arg API_HOST=http://paopao.info . + docker build -t your/paopao-ce:web --build-arg API_HOST=http://paopao.info . # 使用本地编译的dist构建 - %> docker build -t your/paopao-ce:web --build-arg USE_DIST=yes . + docker build -t your/paopao-ce:web --build-arg USE_DIST=yes . ``` -### 使用 docker-compose 运行 +### 方式四. 使用 docker-compose 运行 ```sh -%> git clone https://github.com/rocboss/paopao-ce.git -%> docker compose up --build +git clone https://github.com/rocboss/paopao-ce.git +docker compose up --build # visit paopao-ce(http://127.0.0.1:8008) and phpMysqlAdmin(http://127.0.0.1:8080) ``` + 默认是使用config.yaml.sample的配置,如果需要自定义配置,请拷贝默认配置文件(比如config.yaml),修改后再同步配置到docker-compose.yaml如下: + ``` # file: docker-compose.yaml ... @@ -217,24 +220,29 @@ PaoPao主要由以下优秀的开源项目/工具构建 - paopao-network .... ``` -***注意:默认提供的 docker-compose.yaml 仅仅用于搭建本机开发调试环境,paopao-ce/phpMysqlAdmin 默认只能本机访问,如果需要产品部署供外网访问,请自行修改配置参数或使用其他方式部署。*** + +> 注意:默认提供的 docker-compose.yaml 仅用于搭建本机开发调试环境,paopao-ce/phpMysqlAdmin 默认只能本机访问,如果需要产品部署供外网访问,请自行修改配置参数或使用其他方式部署。 ### API 文档 构建时将 `docs` 添加到TAGS中: ```sh -%> make run TAGS='docs' +make run TAGS='docs' + # visit http://127.0.0.1:8008/docs ``` -### 关于config.yaml -`config.yaml.sample` 是一份完整的配置文件模版,paopao-ce启动时会读取configs/config.yaml、./config.yaml任意一份配置文件(优先读取最先找到的文件)。 +### 配置说明 + +`config.yaml.sample` 是一份完整的配置文件模版,paopao-ce启动时会读取`./configs/config.yaml`、`./config.yaml`任意一份配置文件(优先读取最先找到的文件)。 ```sh -%> cp config.yaml.sample config.yaml -%> vi config.yaml # 修改参数 -%> paopao-ce +cp config.yaml.sample config.yaml +vim config.yaml # 修改参数 +paopao-ce ``` + 配置文件中的 `Features` 小节是声明paopao-ce运行时开启哪些功能项: + ```yaml ... @@ -250,24 +258,33 @@ Features: ... ``` -如上: Default/Develop/Demo/Slim 是不同 功能集套件(Features Suite), Base/Option 是子功能套件, Sms是关于短信验证码功能的参数选项。 -这里 `Default`套件 代表的意思是: 使用`Base/Option` 中的功能 外加 `MySQL/LocalOSS/LoggerFile`功能,也就是说开启了`Zinc/Redis/Alipay/SimpleCacheIndex/MySQL/LocalOSS/LoggerFile` 7项功能; `Develop`套件依例类推。 使用Feautures: +如上: +Default/Develop/Demo/Slim 是不同 功能集套件(Features Suite), Base/Option 是子功能套件, Sms是关于短信验证码功能的参数选项。 + +这里 `Default`套件 代表的意思是: 使用`Base/Option` 中的功能,外加 `MySQL/LocalOSS/LoggerFile`功能,也就是说开启了`Zinc/Redis/Alipay/SimpleCacheIndex/MySQL/LocalOSS/LoggerFile` 7项功能; +`Develop`套件依例类推。 + +使用Feautures: ```sh -%> release/paopao-ce --help +release/paopao-ce --help Usage of release/paopao-ce: -features value use special features -no-default-features whether use default features -%> release/paopao-ce # 默认使用 Default 功能套件 +# 默认使用 Default 功能套件 +release/paopao-ce -%> release/paopao-ce --no-default-features --features develop # 不包含 default 中的功能集,仅仅使用 develop 中声明的功能集 +# 不包含 default 中的功能集,仅仅使用 develop 中声明的功能集 +release/paopao-ce --no-default-features --features develop -%> release/paopao-ce --features sms # 使用 default 中的功能集,外加 sms 功能 +# 使用 default 中的功能集,外加 sms 功能 +release/paopao-ce --features sms -%> release/paopao-ce --no-default-features --features sqlite3,localoss,loggerfile,redis # 手动指定需要开启的功能集 +# 手动指定需要开启的功能集 +release/paopao-ce --no-default-features --features sqlite3,localoss,loggerfile,redis ``` 目前支持的功能集合: From bed9bee806bbe71133fb13e1339950932e88e0b5 Mon Sep 17 00:00:00 2001 From: alimy Date: Thu, 23 Jun 2022 18:44:16 +0800 Subject: [PATCH 33/35] support meilisearch as tweet search service --- Dockerfile | 4 +- README.md | 15 ++-- config.yaml.sample | 28 +++++-- docker-compose.yaml | 14 +++- go.mod | 2 +- go.sum | 26 ++++++- internal/conf/conf.go | 22 ++++-- internal/conf/logger.go | 96 ++++++++++++++++++------ internal/conf/settting.go | 68 ++++++++++++++++- internal/dao/search.go | 8 ++ internal/dao/search_meili.go | 139 +++++++++++++++++++++++++++++++++++ internal/dao/search_zinc.go | 16 ++++ internal/service/post.go | 28 ++----- pkg/zinc/zinc.go | 17 +++-- 14 files changed, 398 insertions(+), 85 deletions(-) create mode 100644 internal/dao/search_meili.go diff --git a/Dockerfile b/Dockerfile index d04b9afa..7378fd8b 100644 --- a/Dockerfile +++ b/Dockerfile @@ -25,8 +25,8 @@ WORKDIR /paopao-ce COPY . . COPY --from=frontend /web/dist ./web/dist ENV GOPROXY=https://goproxy.cn -RUN [ $EMBED_UI != yes ] || make build TAGS='embed' -RUN [ $EMBED_UI = yes ] || make build +RUN [ $EMBED_UI != yes ] || make build TAGS='embed jsoniter' +RUN [ $EMBED_UI = yes ] || make build TAGS='jsoniter' FROM alpine:3.16 ARG API_HOST diff --git a/README.md b/README.md index fc80c9c5..09e8dba5 100644 --- a/README.md +++ b/README.md @@ -290,14 +290,19 @@ release/paopao-ce --no-default-features --features sqlite3,localoss,loggerfile,r 目前支持的功能集合: * 数据库: MySQL/Sqlite3/PostgreSQL * 对象存储: AliOSS/MinIO/LocalOSS + `AliOSS` 阿里云对象存储服务; + `MinIO` [MinIO](https://github.com/minio/minio)对象存储服务; `LocalOSS` 提供使用本地目录文件作为对象存储的功能,仅用于开发调试环境; * 缓存: Redis/SimpleCacheIndex/BigCacheIndex - `SimpleCacheIndex`提供简单的 广场推文列表 的缓存功能; + `SimpleCacheIndex` 提供简单的 广场推文列表 的缓存功能; `BigCacheIndex` 使用[BigCache](https://github.com/allegro/bigcache)缓存 广场推文列表,缓存每个用户每一页,简单做到千人千面; -* 搜索: Zinc -* 日志: LoggerFile/LoggerZinc - `LoggerFile` 使用文件写日志; - `LoggerZinc` 使用Zinc写日志; +* 搜索: Zinc/Meili + `Zinc` 基于[Zinc](https://github.com/zinclabs/zinc)搜索引擎提供推文搜索服务(目前状态: 稳定,推荐使用); + `Meili` 基于[Meilisearch](https://github.com/meilisearch/meilisearch)搜索引擎提供推文搜索服务(目前状态: 内测阶段); +* 日志: LoggerFile/LoggerZinc/LoggerMeili + `LoggerFile` 使用文件写日志(目前状态: 稳定); + `LoggerZinc` 使用[Zinc](https://github.com/zinclabs/zinc)写日志(目前状态: 稳定,推荐使用); + `LoggerMeili` 使用[Meilisearch](https://github.com/meilisearch/meilisearch)写日志(目前状态: 调试阶段); * 支付: Alipay * 短信验证码: SmsJuhe(需要开启sms) `Sms`功能如果没有开启,任意短信验证码都可以绑定手机; diff --git a/config.yaml.sample b/config.yaml.sample index dc4a81ac..ff57d086 100644 --- a/config.yaml.sample +++ b/config.yaml.sample @@ -12,11 +12,11 @@ Server: # 服务设置 ReadTimeout: 60 WriteTimeout: 60 Features: - Default: ["Base", "MySQL", "Option", "LocalOSS", "LoggerFile"] - Develop: ["Base", "MySQL", "BigCacheIndex", "Sms", "AliOSS", "LoggerZinc"] - Demo: ["Base", "MySQL", "Option", "Sms", "MinIO", "LoggerZinc"] + Default: ["Base", "MySQL", "Option", "Zinc", "LocalOSS", "LoggerFile"] + Develop: ["Base", "MySQL", "BigCacheIndex", "Meili", "Sms", "AliOSS", "LoggerMeili"] + Demo: ["Base", "MySQL", "Option", "Zinc", "Sms", "MinIO", "LoggerZinc"] Slim: ["Base", "Sqlite3", "LocalOSS", "LoggerFile"] - Base: ["Zinc", "Redis", "Alipay",] + Base: ["Redis", "Alipay"] Option: ["SimpleCacheIndex"] Sms: "SmsJuhe" SmsJuhe: @@ -36,17 +36,23 @@ BigCacheIndex: # 使用BigCache缓存泡泡广场消息流 MaxIndexPage: 1024 # 最大缓存页数,必须是2^n, 代表最大同时缓存多少页数据 Verbose: False # 是否打印cache操作的log ExpireInSecond: 300 # 多少秒(>0)后强制过期缓存 +Logger: # 日志通用配置 + Level: debug # 日志级别 panic|fatal|error|warn|info|debug|trace LoggerFile: # 使用File写日志 SavePath: data/paopao-ce/logs FileName: app FileExt: .log - Level: info LoggerZinc: # 使用Zinc写日志 - Host: http://zinc:4080/es/_bulk + Host: zinc:4080 Index: paopao-log User: admin Password: admin - Level: info + Secure: False +LoggerMeili: # 使用Meili写日志 + Host: meili:7700 + Index: paopao-log + ApiKey: paopao-meilisearch + Secure: False JWT: # 鉴权加密 Secret: 18a6413dc4fe394c66345ebe501b2f26 Issuer: paopao-api @@ -55,10 +61,16 @@ TweetSearch: # 推文关键字搜索相关配置 MaxUpdateQPS: 100 # 最大添加/删除/更新Post的QPS,设置范围[10, 10000], 默认100 MinWorker: 10 # 最小后台更新工作者, 设置范围[5, 1000], 默认10 Zinc: # Zinc搜索配置 - Host: http://zinc:4080 + Host: zinc:4080 Index: paopao-data User: admin Password: admin + Secure: False +Meili: # Meili搜索配置 + Host: meili:7700 + Index: paopao-data + ApiKey: paopao-meilisearch + Secure: False AliOSS: # 阿里云OSS存储配置 Endpoint: AccessKeyID: diff --git a/docker-compose.yaml b/docker-compose.yaml index 3ae2b783..101ef67c 100644 --- a/docker-compose.yaml +++ b/docker-compose.yaml @@ -39,7 +39,19 @@ services: DATA_PATH: /data networks: - paopao-network - + + # meili: + # image: getmeili/meilisearch:v0.27.0 + # restart: always + # ports: + # - 7700:7700 + # volumes: + # - ./data/meili/data:/meili_data + # environment: + # - MEILI_MASTER_KEY=paopao-meilisearch + # networks: + # - paopao-network + phpmyadmin: image: phpmyadmin:5.2 depends_on: diff --git a/go.mod b/go.mod index 331a99d5..a87f9d3c 100644 --- a/go.mod +++ b/go.mod @@ -22,6 +22,7 @@ require ( github.com/gofrs/uuid v4.0.0+incompatible github.com/google/go-cmp v0.5.7 // indirect github.com/json-iterator/go v1.1.12 + github.com/meilisearch/meilisearch-go v0.19.1 github.com/minio/minio-go/v7 v7.0.27 github.com/sirupsen/logrus v1.8.1 github.com/smartwalle/alipay/v3 v3.1.7 @@ -30,7 +31,6 @@ require ( github.com/yinheli/mahonia v0.0.0-20131226213531-0eef680515cc golang.org/x/crypto v0.0.0-20220307211146-efcb8507fb70 // indirect golang.org/x/image v0.0.0-20220413100746-70e8d0d3baa9 // indirect - golang.org/x/net v0.0.0-20220127200216-cd36cc0744dd // indirect golang.org/x/sys v0.0.0-20220310020820-b874c991c1a5 // indirect google.golang.org/protobuf v1.27.1 gopkg.in/natefinch/lumberjack.v2 v2.0.0 diff --git a/go.sum b/go.sum index fe11f231..2d899cbb 100644 --- a/go.sum +++ b/go.sum @@ -87,6 +87,8 @@ github.com/allegro/bigcache v1.2.1-0.20190218064605-e24eb225f156/go.mod h1:Cb/ax github.com/allegro/bigcache/v3 v3.0.2 h1:AKZCw+5eAaVyNTBmI2fgyPVJhHkdWder3O9IrprcQfI= github.com/allegro/bigcache/v3 v3.0.2/go.mod h1:aPyh7jEvrog9zAwx5N7+JUQX5dZTSGpxF1LAR4dr35I= github.com/andreyvit/diff v0.0.0-20170406064948-c7f18ee00883/go.mod h1:rCTlJbsFo29Kk6CurOXKm700vrz8f0KW0JNfpkRJY/8= +github.com/andybalholm/brotli v1.0.4 h1:V7DdXeJtZscaqfNuAdSRuRFzuiKlHSC/Zh3zl9qY3JY= +github.com/andybalholm/brotli v1.0.4/go.mod h1:fO7iG3H7G2nSZ7m0zPUDn85XEX2GTukHGRSepvi9Eig= github.com/antihax/optional v1.0.0/go.mod h1:uupD/76wgC+ih3iEmQUL+0Ugr19nfwCT1kdvxnR2qWY= github.com/apache/arrow/go/arrow v0.0.0-20191024131854-af6fa24be0db/go.mod h1:VTxUBvSJ3s3eHAg65PNgrsn5BtqCRPdmyXh6rAfdxN0= github.com/araddon/dateparse v0.0.0-20200409225146-d820a6159ab1/go.mod h1:SLqhdZcd+dF3TEVL2RMoob5bBP5R1P1qkox+HtCBgGI= @@ -283,6 +285,8 @@ github.com/gogo/protobuf v1.1.1/go.mod h1:r8qH/GZQm5c6nD/R0oafs1akxWv10x8SbQlK7a github.com/gogo/protobuf v1.2.1/go.mod h1:hp+jE20tsWTFYpLwKvXlhS1hjn+gTNwPg2I6zVXpSg4= github.com/gogo/protobuf v1.3.1/go.mod h1:SlYgWuQ5SjCEi6WLHjHCa1yvBfUnHcTbrrZtXPKa29o= github.com/gogo/protobuf v1.3.2/go.mod h1:P1XiOD3dCwIKUDQYPy72D8LYyHL2YPYrpS2s69NZV8Q= +github.com/golang-jwt/jwt v3.2.2+incompatible h1:IfV12K8xAKAnZqdXVzCZ+TOjboZ2keLg81eXfW3O+oY= +github.com/golang-jwt/jwt v3.2.2+incompatible/go.mod h1:8pz2t5EyA70fFQQSrl6XZXzqecmYZeUEB8OUGHkxJ+I= github.com/golang/freetype v0.0.0-20170609003504-e2365dfdc4a0 h1:DACJavvAHhabrF08vX0COfcOBJRhZ8lUbR+ZWIs0Y5g= github.com/golang/freetype v0.0.0-20170609003504-e2365dfdc4a0/go.mod h1:E/TSTwGwJL78qG/PmXZO1EjYhfJinVAhrmmHX6Z8B9k= github.com/golang/geo v0.0.0-20190916061304-5b978397cfec/go.mod h1:QZ0nwyI2jOfgRAoBvP+ab5aRr7c9x7lhGEJrKvBwjWI= @@ -493,6 +497,8 @@ github.com/jinzhu/now v1.1.5/go.mod h1:d3SSVoowX0Lcu0IBviAWJpolVfI5UJVZZ7cO71lE/ github.com/jmespath/go-jmespath v0.4.0/go.mod h1:T8mJZnbsbmF+m6zOOFylbeCJqk5+pHWvzYPziyZiYoo= github.com/jmespath/go-jmespath/internal/testify v1.5.1/go.mod h1:L3OGu8Wl2/fWfCI6z80xFu9LTZmf1ZRjMHUOPmWr69U= github.com/jonboulle/clockwork v0.1.0/go.mod h1:Ii8DK3G1RaLaWxj9trq07+26W01tbo22gdxWY5EU2bo= +github.com/josharian/intern v1.0.0 h1:vlS4z54oSdjm0bgjRigI+G1HpF+tI+9rE5LLzOg8HmY= +github.com/josharian/intern v1.0.0/go.mod h1:5DoeVV0s6jJacbCEi61lwdGj/aVlrQvzHFFd8Hwg//Y= github.com/jrick/logrotate v1.0.0/go.mod h1:LNinyqDIJnpAur+b8yyulnQw/wDuN1+BYKlTRt3OuAQ= github.com/json-iterator/go v1.1.6/go.mod h1:+SdeFBvtyEkXs7REEP0seUULqWtbJapLOCVDaaPEHmU= github.com/json-iterator/go v1.1.7/go.mod h1:KdQUCv79m/52Kvf8AW2vK1V8akMuk1QjK/uOdHXbAo4= @@ -516,8 +522,9 @@ github.com/kisielk/errcheck v1.5.0/go.mod h1:pFxgyoBC7bSaBwPgfKdkLd5X25qrDl4LWUI github.com/kisielk/gotool v1.0.0/go.mod h1:XhKaO+MFFWcvkIS/tQcRk01m1F5IRFswLeQ+oQHNcck= github.com/kkdai/bstream v0.0.0-20161212061736-f391b8402d23/go.mod h1:J+Gs4SYgM6CZQHDETBtE9HaSEkGmuNXF86RwHhHUvq4= github.com/klauspost/compress v1.4.0/go.mod h1:RyIbtBH6LamlWaDj8nUwkbUhJ87Yi3uG0guNDohfE1A= -github.com/klauspost/compress v1.13.5 h1:9O69jUPDcsT9fEm74W92rZL9FQY7rCdaXVneq+yyzl4= github.com/klauspost/compress v1.13.5/go.mod h1:/3/Vjq9QcHkK5uEr5lBEmyoZ1iFhe47etQ6QUkpK6sk= +github.com/klauspost/compress v1.15.0 h1:xqfchp4whNFxn5A4XFyyYtitiWI8Hy5EW59jEwcyL6U= +github.com/klauspost/compress v1.15.0/go.mod h1:/3/Vjq9QcHkK5uEr5lBEmyoZ1iFhe47etQ6QUkpK6sk= github.com/klauspost/cpuid v0.0.0-20170728055534-ae7887de9fa5/go.mod h1:Pj4uuM528wm8OyEC2QMXAi2YiTZ96dNQPGgoMS4s3ek= github.com/klauspost/cpuid v1.2.3/go.mod h1:Pj4uuM528wm8OyEC2QMXAi2YiTZ96dNQPGgoMS4s3ek= github.com/klauspost/cpuid v1.3.1 h1:5JNjFYYQrZeKRJ0734q51WCEEn2huer72Dc7K+R/b6s= @@ -557,6 +564,8 @@ github.com/magiconair/properties v1.8.5 h1:b6kJs+EmPFMYGkow9GiUyCyOvIwYetYJ3fSaW github.com/magiconair/properties v1.8.5/go.mod h1:y3VJvCyxH9uVvJTWEGAELF3aiYNyPKd5NZ3oSwXrF60= github.com/mailru/easyjson v0.0.0-20190614124828-94de47d64c63/go.mod h1:C1wdFJiN94OJF2b5HbByQZoLdCWB1Yqtg26g4irojpc= github.com/mailru/easyjson v0.0.0-20190626092158-b2ccc519800e/go.mod h1:C1wdFJiN94OJF2b5HbByQZoLdCWB1Yqtg26g4irojpc= +github.com/mailru/easyjson v0.7.7 h1:UGYAvKxe3sBsEDzO8ZeWOSlIQfWFlxbzLZe7hwFURr0= +github.com/mailru/easyjson v0.7.7/go.mod h1:xzfreul335JAWq5oZzymOObrkdz5UnU4kGfJJLY9Nlc= github.com/matryer/moq v0.0.0-20190312154309-6cfb0558e1bd/go.mod h1:9ELz6aaclSIGnZBoaSLZ3NAl1VTufbOrXBPvtcy6WiQ= github.com/mattn/go-colorable v0.0.9/go.mod h1:9vuHe8Xs5qXnSaW/c/ABM9alt+Vo+STaOChaDxuIBZU= github.com/mattn/go-colorable v0.1.1/go.mod h1:FuOcm+DKB9mbwrcAfNl7/TZVBZ6rcnceauSikq3lYCQ= @@ -589,6 +598,8 @@ github.com/mattn/go-sqlite3 v1.14.12 h1:TJ1bhYJPV44phC+IMu1u2K/i5RriLTPe+yc68XDJ github.com/mattn/go-sqlite3 v1.14.12/go.mod h1:NyWgC/yNuGj7Q9rpYnZvas74GogHl5/Z4A/KQRfk6bU= github.com/mattn/go-tty v0.0.0-20180907095812-13ff1204f104/go.mod h1:XPvLUNfbS4fJH25nqRHfWLMa1ONC8Amw+mIA639KxkE= github.com/matttproud/golang_protobuf_extensions v1.0.1/go.mod h1:D8He9yQNgCq6Z5Ld7szi9bcBfOoFv/3dc6xSMkL2PC0= +github.com/meilisearch/meilisearch-go v0.19.1 h1:CDi7p5Ev18h0hMXaJZ/1GzSKu3lvPCsaJfrLco3bMEM= +github.com/meilisearch/meilisearch-go v0.19.1/go.mod h1:PnFFq9tELcH5mLVKCoTHRS58B3HEA8vKdBSoG6g/FCE= github.com/miekg/dns v1.1.26/go.mod h1:bPDLeHnStXmXAq1m/Ch/hvfNHr14JKNPMBo3VZKjuso= github.com/miekg/dns v1.1.41/go.mod h1:p6aan82bvRIyn+zDIv9xYNUpwa73JcSh9BKwknJysuI= github.com/minio/md5-simd v1.1.0 h1:QPfiOqlZH+Cj9teu0t9b1nTBfPbyTl16Of5MeuShdK4= @@ -748,8 +759,9 @@ github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UV github.com/stretchr/testify v1.4.0/go.mod h1:j7eGeouHqKxXV5pUuKE4zz7dFj8WfuZ+81PSLYec5m4= github.com/stretchr/testify v1.5.1/go.mod h1:5W2xD1RspED5o8YsWQXVCued0rvSQ+mT+I5cxcmMvtA= github.com/stretchr/testify v1.6.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= -github.com/stretchr/testify v1.7.0 h1:nwc3DEeHmmLAfoZucVR881uASk0Mfjw8xYJ99tb5CcY= github.com/stretchr/testify v1.7.0/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= +github.com/stretchr/testify v1.7.1 h1:5TQK59W5E3v0r2duFAb7P95B6hEeOyEnHRa8MjYSMTY= +github.com/stretchr/testify v1.7.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= github.com/subosito/gotenv v1.2.0 h1:Slr1R9HxAlEKefgq5jn9U+DnETlIUa6HfgEzj0g5d7s= github.com/subosito/gotenv v1.2.0/go.mod h1:N0PQaV/YGNqwC0u51sEeR/aUtSLEXKX9iv69rRypqCw= github.com/syndtr/goleveldb v1.0.1-0.20210819022825-2ae1ddf74ef7/go.mod h1:q4W45IWZaF22tdD+VEXcAWRA037jwmWEB5VWYORlTpc= @@ -768,9 +780,13 @@ github.com/ugorji/go/codec v1.1.7/go.mod h1:Ax+UKWsSmolVDwsd+7N3ZtXu+yMGCf907BLY github.com/ugorji/go/codec v1.2.7 h1:YPXUKf7fYbp/y8xloBqZOw2qaVggbfwMlI8WM3wZUJ0= github.com/ugorji/go/codec v1.2.7/go.mod h1:WGN1fab3R1fzQlVQTkfxVtIBhWDRqOviHU95kRgeqEY= github.com/urfave/cli/v2 v2.3.0/go.mod h1:LJmUH05zAU44vOAcrfzZQKsZbVcdbOG8rtL3/XcUArI= +github.com/valyala/bytebufferpool v1.0.0 h1:GqA5TC/0021Y/b9FG4Oi9Mr3q7XYx6KllzawFIhcdPw= github.com/valyala/bytebufferpool v1.0.0/go.mod h1:6bBcMArwyJ5K/AmCkWv1jt77kVWyCJ6HpOuEn7z0Csc= +github.com/valyala/fasthttp v1.36.0 h1:NhqfO/cB7Ajn1czkKnWkMHyPYr5nyND14ZGPk23g0/c= +github.com/valyala/fasthttp v1.36.0/go.mod h1:t/G+3rLek+CyY9bnIE+YlMRddxVAAGjhxndDB4i4C0I= github.com/valyala/fasttemplate v1.0.1/go.mod h1:UQGH1tvbgY+Nz5t2n7tXsz52dQxojPUpymEIMZ47gx8= github.com/valyala/fasttemplate v1.2.1/go.mod h1:KHLXt3tVN2HBp8eijSv/kGJopbvo7S+qRAEEKiv+SiQ= +github.com/valyala/tcplisten v1.0.0/go.mod h1:T0xQ8SeCZGxckz9qRXTfG43PvQ/mcWh7FwZEA7Ioqkc= github.com/willf/bitset v1.1.3/go.mod h1:RjeCKbqT1RxIR/KWY6phxZiaY1IyutSBfGjNPySAYV4= github.com/xiang90/probing v0.0.0-20190116061207-43a291ad63a2/go.mod h1:UETIi67q53MR2AWcXfiuqkDkRtnGDLqkBTpCHuJHxtU= github.com/xlab/treeprint v0.0.0-20180616005107-d6fb6747feb6/go.mod h1:ce1O1j6UtZfjr22oyGxGLbauSBp2YVXpARAosm7dHBg= @@ -834,6 +850,7 @@ golang.org/x/crypto v0.0.0-20210711020723-a769d52b0f97/go.mod h1:GvvjBRRGRdwPK5y golang.org/x/crypto v0.0.0-20210817164053-32db794688a5/go.mod h1:GvvjBRRGRdwPK5ydBHafDWAxML/pGHZbMvKqRZ5+Abc= golang.org/x/crypto v0.0.0-20210921155107-089bfa567519/go.mod h1:GvvjBRRGRdwPK5ydBHafDWAxML/pGHZbMvKqRZ5+Abc= golang.org/x/crypto v0.0.0-20211215153901-e495a2d5b3d3/go.mod h1:IxCIyHEi3zRg3s0A5j5BB6A9Jmi73HwBIUl50j+osU4= +golang.org/x/crypto v0.0.0-20220214200702-86341886e292/go.mod h1:IxCIyHEi3zRg3s0A5j5BB6A9Jmi73HwBIUl50j+osU4= golang.org/x/crypto v0.0.0-20220307211146-efcb8507fb70 h1:syTAU9FwmvzEoIYMqcPHOcVm4H3U5u90WsvuYgwpETU= golang.org/x/crypto v0.0.0-20220307211146-efcb8507fb70/go.mod h1:IxCIyHEi3zRg3s0A5j5BB6A9Jmi73HwBIUl50j+osU4= golang.org/x/exp v0.0.0-20180321215751-8460e604b9de/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA= @@ -931,8 +948,8 @@ golang.org/x/net v0.0.0-20210805182204-aaa1db679c0d/go.mod h1:9nx3DQGgdP8bBQD5qx golang.org/x/net v0.0.0-20210813160813-60bc85c4be6d/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y= golang.org/x/net v0.0.0-20211029224645-99673261e6eb/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y= golang.org/x/net v0.0.0-20211112202133-69e39bad7dc2/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y= -golang.org/x/net v0.0.0-20220127200216-cd36cc0744dd h1:O7DYs+zxREGLKzKoMQrtrEacpb0ZVXA5rIwylE2Xchk= -golang.org/x/net v0.0.0-20220127200216-cd36cc0744dd/go.mod h1:CfG3xpIq0wQ8r1q4Su4UZFWDARRcnwPjda9FqA0JpMk= +golang.org/x/net v0.0.0-20220225172249-27dd8689420f h1:oA4XRj0qtSt8Yo1Zms0CUlsT3KG69V2UGQWPBxujDmc= +golang.org/x/net v0.0.0-20220225172249-27dd8689420f/go.mod h1:CfG3xpIq0wQ8r1q4Su4UZFWDARRcnwPjda9FqA0JpMk= golang.org/x/oauth2 v0.0.0-20180821212333-d2e6202438be/go.mod h1:N/0e6XlmueqKjAGxoOufVs8QHGRruUQn6yWY3a++T0U= golang.org/x/oauth2 v0.0.0-20190226205417-e64efc72b421/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw= golang.org/x/oauth2 v0.0.0-20190604053449-0f29369cfe45/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw= @@ -1045,6 +1062,7 @@ golang.org/x/sys v0.0.0-20211102061401-a2f17f7b995c/go.mod h1:oPkhp1MJrh7nUepCBc golang.org/x/sys v0.0.0-20211124211545-fe61309f8881/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20211210111614-af8b64212486/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20211216021012-1d35b9e2eb4e/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.0.0-20220227234510-4e6760a101f9/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20220310020820-b874c991c1a5 h1:y/woIyUBFbpQGKS0u1aHF/40WUDnek3fPOyD08H5Vng= golang.org/x/sys v0.0.0-20220310020820-b874c991c1a5/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/term v0.0.0-20201117132131-f5c789dd3221/go.mod h1:Nr5EML6q2oocZ2LXRh80K7BxOlk5/8JxuGnuhpl+muw= diff --git a/internal/conf/conf.go b/internal/conf/conf.go index 6f22f46f..818fdb0d 100644 --- a/internal/conf/conf.go +++ b/internal/conf/conf.go @@ -7,14 +7,16 @@ import ( ) var ( - loggerFileSetting *LoggerFileSettingS - loggerZincSetting *LoggerZincSettingS - databaseSetting *DatabaseSetingS - mysqlSetting *MySQLSettingS - postgresSetting *PostgresSettingS - sqlite3Setting *Sqlite3SettingS - redisSetting *RedisSettingS - features *FeaturesSettingS + LoggerSetting *LoggerSettingS + loggerFileSetting *LoggerFileSettingS + loggerZincSetting *LoggerZincSettingS + loggerMeiliSetting *LoggerMeiliSettingS + databaseSetting *DatabaseSetingS + mysqlSetting *MySQLSettingS + postgresSetting *PostgresSettingS + sqlite3Setting *Sqlite3SettingS + redisSetting *RedisSettingS + features *FeaturesSettingS ServerSetting *ServerSettingS AppSetting *AppSettingS @@ -25,6 +27,7 @@ var ( AlipaySetting *AlipaySettingS TweetSearchSetting *TweetSearchS ZincSetting *ZincSettingS + MeiliSetting *MeiliSettingS AliOSSSetting *AliOSSSettingS MinIOSetting *MinIOSettingS S3Setting *S3SettingS @@ -54,14 +57,17 @@ func setupSetting(suite []string, noDefault bool) error { "BigCacheIndex": &BigCacheIndexSetting, "Alipay": &AlipaySetting, "SmsJuhe": &SmsJuheSetting, + "Logger": &LoggerSetting, "LoggerFile": &loggerFileSetting, "LoggerZinc": &loggerZincSetting, + "LoggerMeili": &loggerMeiliSetting, "Database": &databaseSetting, "MySQL": &mysqlSetting, "Postgres": &postgresSetting, "Sqlite3": &sqlite3Setting, "TweetSearch": &TweetSearchSetting, "Zinc": &ZincSetting, + "Meili": &MeiliSetting, "Redis": &redisSetting, "JWT": &JWTSetting, "AliOSS": &AliOSSSetting, diff --git a/internal/conf/logger.go b/internal/conf/logger.go index 16a2b8f7..7e8f787b 100644 --- a/internal/conf/logger.go +++ b/internal/conf/logger.go @@ -5,23 +5,24 @@ import ( "io" "time" + "github.com/meilisearch/meilisearch-go" "github.com/rocboss/paopao-ce/pkg/json" "github.com/sirupsen/logrus" "gopkg.in/natefinch/lumberjack.v2" "gopkg.in/resty.v1" ) -type zincLogIndex struct { - Index map[string]string `json:"index"` -} - -type zincLogData struct { +type logData struct { Time time.Time `json:"time"` Level logrus.Level `json:"level"` Message string `json:"message"` Data logrus.Fields `json:"data"` } +type zincLogIndex struct { + Index map[string]string `json:"index"` +} + type zincLogHook struct { host string index string @@ -29,6 +30,10 @@ type zincLogHook struct { password string } +type meiliLogHook struct { + index *meilisearch.Index +} + func (h *zincLogHook) Fire(entry *logrus.Entry) error { index := &zincLogIndex{ Index: map[string]string{ @@ -37,7 +42,7 @@ func (h *zincLogHook) Fire(entry *logrus.Entry) error { } indexBytes, _ := json.Marshal(index) - data := &zincLogData{ + data := &logData{ Time: entry.Time, Level: entry.Level, Message: entry.Message, @@ -63,30 +68,73 @@ func (h *zincLogHook) Levels() []logrus.Level { return logrus.AllLevels } +func (h *meiliLogHook) Fire(entry *logrus.Entry) error { + data := &logData{ + Time: entry.Time, + Level: entry.Level, + Message: entry.Message, + Data: entry.Data, + } + if _, err := h.index.AddDocuments(data); err != nil { + fmt.Println(err.Error()) + } + return nil +} + +func (h *meiliLogHook) Levels() []logrus.Level { + return logrus.AllLevels +} + +func newZincLogHook() *zincLogHook { + return &zincLogHook{ + host: loggerZincSetting.Endpoint() + "/es/_bulk", + index: loggerZincSetting.Index, + user: loggerZincSetting.User, + password: loggerZincSetting.Password, + } +} + +func newMeiliLogHook() *meiliLogHook { + client := meilisearch.NewClient(meilisearch.ClientConfig{ + Host: loggerMeiliSetting.Endpoint(), + APIKey: loggerMeiliSetting.ApiKey, + }) + + index := client.Index(loggerMeiliSetting.Index) + if _, err := index.FetchInfo(); err != nil { + logrus.Debugf("newMeiliLogHook create index because fetch index info error: %v", err) + client.CreateIndex(&meilisearch.IndexConfig{ + Uid: loggerMeiliSetting.Index, + }) + } + + return &meiliLogHook{ + index: index, + } +} + +func newFileLogger() io.Writer { + return &lumberjack.Logger{ + Filename: loggerFileSetting.SavePath + "/" + loggerFileSetting.FileName + loggerFileSetting.FileExt, + MaxSize: 600, + MaxAge: 10, + LocalTime: true, + } +} + func setupLogger() { logrus.SetFormatter(&logrus.JSONFormatter{}) + logrus.SetLevel(LoggerSetting.logLevel()) if CfgIf("LoggerFile") { - out := &lumberjack.Logger{ - Filename: loggerFileSetting.SavePath + "/" + loggerFileSetting.FileName + loggerFileSetting.FileExt, - MaxSize: 600, - MaxAge: 10, - LocalTime: true, - } + out := newFileLogger() logrus.SetOutput(out) - if level, err := logrus.ParseLevel(loggerFileSetting.Level); err == nil { - logrus.SetLevel(level) - } } else if CfgIf("LoggerZinc") { - hook := &zincLogHook{ - host: loggerZincSetting.Host, - index: loggerZincSetting.Index, - user: loggerZincSetting.User, - password: loggerZincSetting.Password, - } - if level, err := logrus.ParseLevel(loggerZincSetting.Level); err == nil { - logrus.SetLevel(level) - } + hook := newZincLogHook() + logrus.SetOutput(io.Discard) + logrus.AddHook(hook) + } else if CfgIf("LoggerMeili") { + hook := newMeiliLogHook() logrus.SetOutput(io.Discard) logrus.AddHook(hook) } diff --git a/internal/conf/settting.go b/internal/conf/settting.go index 9bceceb9..4b3f9ae3 100644 --- a/internal/conf/settting.go +++ b/internal/conf/settting.go @@ -5,6 +5,7 @@ import ( "strings" "time" + "github.com/sirupsen/logrus" "github.com/spf13/viper" "gorm.io/gorm/logger" ) @@ -13,11 +14,14 @@ type Setting struct { vp *viper.Viper } +type LoggerSettingS struct { + Level string +} + type LoggerFileSettingS struct { SavePath string FileName string FileExt string - Level string } type LoggerZincSettingS struct { @@ -25,7 +29,14 @@ type LoggerZincSettingS struct { Index string User string Password string - Level string + Secure bool +} + +type LoggerMeiliSettingS struct { + Host string + Index string + ApiKey string + Secure bool } type ServerSettingS struct { @@ -91,6 +102,14 @@ type ZincSettingS struct { Index string User string Password string + Secure bool +} + +type MeiliSettingS struct { + Host string + Index string + ApiKey string + Secure bool } type DatabaseSetingS struct { @@ -312,3 +331,48 @@ func (s *DatabaseSetingS) logLevel() logger.LogLevel { return logger.Error } } + +func (s *LoggerSettingS) logLevel() logrus.Level { + switch strings.ToLower(s.Level) { + case "panic": + return logrus.PanicLevel + case "fatal": + return logrus.FatalLevel + case "error": + return logrus.ErrorLevel + case "warn", "warning": + return logrus.WarnLevel + case "info": + return logrus.InfoLevel + case "debug": + return logrus.DebugLevel + case "trace": + return logrus.TraceLevel + default: + return logrus.ErrorLevel + } +} + +func (s *LoggerZincSettingS) Endpoint() string { + return endpoint(s.Host, s.Secure) +} + +func (s *LoggerMeiliSettingS) Endpoint() string { + return endpoint(s.Host, s.Secure) +} + +func (s *ZincSettingS) Endpoint() string { + return endpoint(s.Host, s.Secure) +} + +func (s *MeiliSettingS) Endpoint() string { + return endpoint(s.Host, s.Secure) +} + +func endpoint(host string, secure bool) string { + schema := "http" + if secure { + schema = "https" + } + return schema + "://" + host +} diff --git a/internal/dao/search.go b/internal/dao/search.go index 0b25f4ca..04791618 100644 --- a/internal/dao/search.go +++ b/internal/dao/search.go @@ -1,6 +1,7 @@ package dao import ( + "github.com/meilisearch/meilisearch-go" "github.com/rocboss/paopao-ce/internal/conf" "github.com/rocboss/paopao-ce/internal/core" "github.com/rocboss/paopao-ce/pkg/zinc" @@ -28,6 +29,11 @@ type zincTweetSearchServant struct { client *zinc.ZincClient } +type meiliTweetSearchServant struct { + client *meilisearch.Client + index *meilisearch.Index +} + func NewTweetSearchService() core.TweetSearchService { bts := &bridgeTweetSearchServant{} @@ -41,6 +47,8 @@ func NewTweetSearchService() core.TweetSearchService { if conf.CfgIf("Zinc") { bts.ts = newZincTweetSearchServant() + } else if conf.CfgIf("Meili") { + bts.ts = newMeiliTweetSearchServant() } else { // default use Zinc as tweet search service bts.ts = newZincTweetSearchServant() diff --git a/internal/dao/search_meili.go b/internal/dao/search_meili.go new file mode 100644 index 00000000..12d7335c --- /dev/null +++ b/internal/dao/search_meili.go @@ -0,0 +1,139 @@ +package dao + +import ( + "github.com/Masterminds/semver/v3" + "github.com/meilisearch/meilisearch-go" + "github.com/rocboss/paopao-ce/internal/conf" + "github.com/rocboss/paopao-ce/internal/core" + "github.com/rocboss/paopao-ce/internal/model" + "github.com/rocboss/paopao-ce/pkg/json" + "github.com/sirupsen/logrus" +) + +func newMeiliTweetSearchServant() *meiliTweetSearchServant { + s := conf.MeiliSetting + client := meilisearch.NewClient(meilisearch.ClientConfig{ + Host: s.Endpoint(), + APIKey: s.ApiKey, + }) + + if _, err := client.Index(s.Index).FetchInfo(); err != nil { + logrus.Debugf("create index because fetch index info error: %v", err) + client.CreateIndex(&meilisearch.IndexConfig{ + Uid: s.Index, + PrimaryKey: "id", + }) + searchableAttributes := []string{"content", "tags"} + sortableAttributes := []string{"is_top", "latest_replied_on"} + + index := client.Index(s.Index) + index.UpdateSearchableAttributes(&searchableAttributes) + index.UpdateSortableAttributes(&sortableAttributes) + } + + return &meiliTweetSearchServant{ + client: client, + index: client.Index(s.Index), + } +} + +func (s *meiliTweetSearchServant) Name() string { + return "Meili" +} + +func (s *meiliTweetSearchServant) Version() *semver.Version { + return semver.MustParse("v0.1.0") +} + +func (s *meiliTweetSearchServant) IndexName() string { + return s.index.UID +} + +func (s *meiliTweetSearchServant) AddDocuments(data core.DocItems, primaryKey ...string) (bool, error) { + task, err := s.index.AddDocuments(data, primaryKey...) + if err != nil { + logrus.Errorf("meiliTweetSearchServant.AddDocuments error: %v", err) + return false, err + } + logrus.Debugf("meiliTweetSearchServant.AddDocuments task: %+v", task.Details) + return true, nil +} + +func (s *meiliTweetSearchServant) DeleteDocuments(identifiers []string) error { + task, err := s.index.DeleteDocuments(identifiers) + if err != nil { + logrus.Errorf("meiliTweetSearchServant.DeleteDocuments error: %v", err) + return err + } + logrus.Debugf("meiliTweetSearchServant.DeleteDocuments task: %+v", task.Details) + return nil +} + +func (s *meiliTweetSearchServant) Search(q *core.QueryReq, offset, limit int) (*core.QueryResp, error) { + if q.Type == core.SearchTypeDefault && q.Query != "" { + return s.queryByContent(q, offset, limit) + } else if q.Type == core.SearchTypeTag && q.Query != "" { + return s.queryByTag(q, offset, limit) + } + return s.queryAny(offset, limit) +} + +func (s *meiliTweetSearchServant) queryByContent(q *core.QueryReq, offset, limit int) (*core.QueryResp, error) { + resp, err := s.index.Search(q.Query, &meilisearch.SearchRequest{ + Offset: int64(offset), + Limit: int64(limit), + AttributesToRetrieve: []string{"*"}, + Sort: []string{"is_top:desc", "latest_replied_on:desc"}, + }) + if err != nil { + return nil, err + } + return s.postsFrom(resp) +} + +func (s *meiliTweetSearchServant) queryByTag(q *core.QueryReq, offset, limit int) (*core.QueryResp, error) { + resp, err := s.index.Search("#"+q.Query, &meilisearch.SearchRequest{ + Offset: int64(offset), + Limit: int64(limit), + AttributesToRetrieve: []string{"*"}, + Sort: []string{"is_top:desc", "latest_replied_on:desc"}, + }) + if err != nil { + return nil, err + } + return s.postsFrom(resp) +} + +func (s *meiliTweetSearchServant) queryAny(offset, limit int) (*core.QueryResp, error) { + resp, err := s.index.Search("", &meilisearch.SearchRequest{ + Offset: int64(offset), + Limit: int64(limit), + Matches: true, + Sort: []string{"is_top:desc", "latest_replied_on:desc"}, + }) + if err != nil { + return nil, err + } + return s.postsFrom(resp) +} + +func (s *meiliTweetSearchServant) postsFrom(resp *meilisearch.SearchResponse) (*core.QueryResp, error) { + logrus.Debugf("resp Hits:%d NbHits:%d offset: %d limit:%d ", len(resp.Hits), resp.NbHits, resp.Offset, resp.Limit) + posts := make([]*model.PostFormated, 0, len(resp.Hits)) + for _, hit := range resp.Hits { + item := &model.PostFormated{} + raw, err := json.Marshal(hit) + if err != nil { + return nil, err + } + if err = json.Unmarshal(raw, item); err != nil { + return nil, err + } + posts = append(posts, item) + } + + return &core.QueryResp{ + Items: posts, + Total: resp.NbHits, + }, nil +} diff --git a/internal/dao/search_zinc.go b/internal/dao/search_zinc.go index 6cda0503..debae521 100644 --- a/internal/dao/search_zinc.go +++ b/internal/dao/search_zinc.go @@ -33,6 +33,22 @@ func (s *zincTweetSearchServant) IndexName() string { } func (s *zincTweetSearchServant) AddDocuments(data core.DocItems, primaryKey ...string) (bool, error) { + buf := make(core.DocItems, 0, len(data)+1) + if len(primaryKey) > 0 { + buf = append(buf, map[string]interface{}{ + "index": map[string]interface{}{ + "_index": s.indexName, + "_id": primaryKey[0], + }, + }) + } else { + buf = append(buf, map[string]interface{}{ + "index": map[string]interface{}{ + "_index": s.indexName, + }, + }) + } + buf = append(buf, data...) return s.client.BulkPushDoc(data) } diff --git a/internal/service/post.go b/internal/service/post.go index 2401f0ef..35ac6764 100644 --- a/internal/service/post.go +++ b/internal/service/post.go @@ -525,13 +525,7 @@ func PushPostToSearch(post *model.Post) { tagMaps[tag] = 1 } - data := core.DocItems{} - data = append(data, map[string]interface{}{ - "index": map[string]interface{}{ - "_index": ts.IndexName(), - "_id": fmt.Sprintf("%d", post.ID), - }, - }, map[string]interface{}{ + data := core.DocItems{{ "id": post.ID, "user_id": post.UserID, "comment_count": post.CommentCount, @@ -547,9 +541,9 @@ func PushPostToSearch(post *model.Post) { "attachment_price": post.AttachmentPrice, "created_on": post.CreatedOn, "modified_on": post.ModifiedOn, - }) + }} - ts.AddDocuments(data) + ts.AddDocuments(data, fmt.Sprintf("%d", post.ID)) } func DeleteSearchPost(post *model.Post) error { @@ -567,8 +561,6 @@ func PushPostsToSearch(c *gin.Context) { nums := int(pages) for i := 0; i < nums; i++ { - data := []map[string]interface{}{} - posts, _ := GetPostList(&PostListReq{ Conditions: &model.ConditionsT{ "visibility IN ?": []model.PostVisibleT{model.PostVisitPublic, model.PostVisitFriend}, @@ -586,12 +578,7 @@ func PushPostsToSearch(c *gin.Context) { } } - data = append(data, map[string]interface{}{ - "index": map[string]interface{}{ - "_index": ts.IndexName(), - "_id": fmt.Sprintf("%d", post.ID), - }, - }, map[string]interface{}{ + docs := core.DocItems{{ "id": post.ID, "user_id": post.User.ID, "comment_count": post.CommentCount, @@ -607,11 +594,8 @@ func PushPostsToSearch(c *gin.Context) { "attachment_price": post.AttachmentPrice, "created_on": post.CreatedOn, "modified_on": post.ModifiedOn, - }) - } - - if len(data) > 0 { - ts.AddDocuments(data) + }} + ts.AddDocuments(docs, fmt.Sprintf("%d", post.ID)) } } diff --git a/pkg/zinc/zinc.go b/pkg/zinc/zinc.go index 63dfeeb1..8d280124 100644 --- a/pkg/zinc/zinc.go +++ b/pkg/zinc/zinc.go @@ -74,7 +74,7 @@ type HitItem struct { func NewClient(conf *conf.ZincSettingS) *ZincClient { return &ZincClient{ ZincClientConfig: &ZincClientConfig{ - ZincHost: conf.Host, + ZincHost: conf.Endpoint(), ZincUser: conf.User, ZincPassword: conf.Password, }, @@ -90,7 +90,7 @@ func (c *ZincClient) CreateIndex(name string, p *ZincIndexProperty) bool { Properties: p, }, } - resp, err := c.request().SetBody(data).Put(c.ZincHost + "/api/index") + resp, err := c.request().SetBody(data).Put("/api/index") if err != nil || resp.StatusCode() != http.StatusOK { return false @@ -101,7 +101,7 @@ func (c *ZincClient) CreateIndex(name string, p *ZincIndexProperty) bool { // 检查索引是否存在 func (c *ZincClient) ExistIndex(name string) bool { - resp, err := c.request().Get(c.ZincHost + "/api/index") + resp, err := c.request().Get("/api/index") if err != nil || resp.StatusCode() != http.StatusOK { return false @@ -122,7 +122,7 @@ func (c *ZincClient) ExistIndex(name string) bool { // 新增/更新文档 func (c *ZincClient) PutDoc(name string, id int64, doc interface{}) (bool, error) { - resp, err := c.request().SetBody(doc).Put(fmt.Sprintf("%s/api/%s/_doc/%d", c.ZincHost, name, id)) + resp, err := c.request().SetBody(doc).Put(fmt.Sprintf("/api/%s/_doc/%d", name, id)) if err != nil { return false, err @@ -145,7 +145,7 @@ func (c *ZincClient) BulkPushDoc(docs []map[string]interface{}) (bool, error) { } } - resp, err := c.request().SetBody(dataStr).Post(fmt.Sprintf("%s/api/_bulk", c.ZincHost)) + resp, err := c.request().SetBody(dataStr).Post("/api/_bulk") if err != nil { return false, err } @@ -158,7 +158,7 @@ func (c *ZincClient) BulkPushDoc(docs []map[string]interface{}) (bool, error) { } func (c *ZincClient) EsQuery(indexName string, q interface{}) (*QueryResultT, error) { - resp, err := c.request().SetBody(q).Post(fmt.Sprintf("%s/es/%s/_search", c.ZincHost, indexName)) + resp, err := c.request().SetBody(q).Post(fmt.Sprintf("/es/%s/_search", indexName)) if err != nil { return nil, err } @@ -177,7 +177,7 @@ func (c *ZincClient) EsQuery(indexName string, q interface{}) (*QueryResultT, er } func (c *ZincClient) ApiQuery(indexName string, q interface{}) (*QueryResultT, error) { - resp, err := c.request().SetBody(q).Post(fmt.Sprintf("%s/api/%s/_search", c.ZincHost, indexName)) + resp, err := c.request().SetBody(q).Post(fmt.Sprintf("/api/%s/_search", indexName)) if err != nil { return nil, err } @@ -196,7 +196,7 @@ func (c *ZincClient) ApiQuery(indexName string, q interface{}) (*QueryResultT, e } func (c *ZincClient) DelDoc(indexName, id string) error { - resp, err := c.request().Delete(fmt.Sprintf("%s/api/%s/_doc/%s", c.ZincHost, indexName, id)) + resp, err := c.request().Delete(fmt.Sprintf("/api/%s/_doc/%s", indexName, id)) if err != nil { return err } @@ -211,6 +211,7 @@ func (c *ZincClient) DelDoc(indexName, id string) error { func (c *ZincClient) request() *resty.Request { client := resty.New() client.DisableWarn = true + client.SetBaseURL(c.ZincHost) client.SetBasicAuth(c.ZincUser, c.ZincPassword) return client.R() From 4a6eb41a36fa2c68274b4197c7cfa7f99a14dec2 Mon Sep 17 00:00:00 2001 From: alimy Date: Thu, 23 Jun 2022 20:09:54 +0800 Subject: [PATCH 34/35] fixed zinc add documents no effects error --- internal/dao/search_zinc.go | 2 +- internal/service/post.go | 92 +------------------------------------ 2 files changed, 2 insertions(+), 92 deletions(-) diff --git a/internal/dao/search_zinc.go b/internal/dao/search_zinc.go index debae521..dc3630a5 100644 --- a/internal/dao/search_zinc.go +++ b/internal/dao/search_zinc.go @@ -49,7 +49,7 @@ func (s *zincTweetSearchServant) AddDocuments(data core.DocItems, primaryKey ... }) } buf = append(buf, data...) - return s.client.BulkPushDoc(data) + return s.client.BulkPushDoc(buf) } func (s *zincTweetSearchServant) DeleteDocuments(identifiers []string) error { diff --git a/internal/service/post.go b/internal/service/post.go index 35ac6764..c9f21313 100644 --- a/internal/service/post.go +++ b/internal/service/post.go @@ -12,9 +12,7 @@ import ( "github.com/rocboss/paopao-ce/internal/core" "github.com/rocboss/paopao-ce/internal/model" "github.com/rocboss/paopao-ce/pkg/errcode" - "github.com/rocboss/paopao-ce/pkg/json" "github.com/rocboss/paopao-ce/pkg/util" - "github.com/rocboss/paopao-ce/pkg/zinc" "github.com/sirupsen/logrus" ) @@ -430,47 +428,7 @@ func GetPostList(req *PostListReq) ([]*model.PostFormated, error) { return nil, err } - return FormatPosts(posts) -} - -func FormatPosts(posts []*model.Post) ([]*model.PostFormated, error) { - postIds := []int64{} - userIds := []int64{} - for _, post := range posts { - postIds = append(postIds, post.ID) - userIds = append(userIds, post.UserID) - } - - postContents, err := ds.GetPostContentsByIDs(postIds) - if err != nil { - return nil, err - } - - users, err := ds.GetUsersByIDs(userIds) - if err != nil { - return nil, err - } - - // 数据整合 - postsFormated := []*model.PostFormated{} - for _, post := range posts { - postFormated := post.Format() - - for _, user := range users { - if user.ID == postFormated.UserID { - postFormated.User = user.Format() - } - } - for _, content := range postContents { - if content.PostID == post.ID { - postFormated.Contents = append(postFormated.Contents, content.Format()) - } - } - - postsFormated = append(postsFormated, postFormated) - } - - return postsFormated, nil + return ds.MergePosts(posts) } func GetPostCount(conditions *model.ConditionsT) (int64, error) { @@ -603,54 +561,6 @@ func PushPostsToSearch(c *gin.Context) { } } -func FormatZincPost(queryResult *zinc.QueryResultT) ([]*model.PostFormated, error) { - posts := []*model.PostFormated{} - for _, hit := range queryResult.Hits.Hits { - item := &model.PostFormated{} - - raw, _ := json.Marshal(hit.Source) - err := json.Unmarshal(raw, item) - if err == nil { - posts = append(posts, item) - } - } - - postIds := []int64{} - userIds := []int64{} - for _, post := range posts { - postIds = append(postIds, post.ID) - userIds = append(userIds, post.UserID) - } - postContents, err := ds.GetPostContentsByIDs(postIds) - if err != nil { - return nil, err - } - - users, err := ds.GetUsersByIDs(userIds) - if err != nil { - return nil, err - } - - // 数据整合 - for _, post := range posts { - for _, user := range users { - if user.ID == post.UserID { - post.User = user.Format() - } - } - if post.Contents == nil { - post.Contents = []*model.PostContentFormated{} - } - for _, content := range postContents { - if content.PostID == post.ID { - post.Contents = append(post.Contents, content.Format()) - } - } - } - - return posts, nil -} - func GetPostTags(param *PostTagsReq) ([]*model.TagFormated, error) { num := param.Num if num > conf.AppSetting.MaxPageSize { From 6f27d9585659aaa704d09e544b51131c31577683 Mon Sep 17 00:00:00 2001 From: alimy Date: Thu, 23 Jun 2022 22:54:51 +0800 Subject: [PATCH 35/35] make the feature of LoggerMeili work well --- README.md | 2 +- config.yaml.sample | 2 + internal/conf/logger.go | 106 ---------------------------------- internal/conf/logger_meili.go | 77 ++++++++++++++++++++++++ internal/conf/logger_zinc.go | 71 +++++++++++++++++++++++ internal/conf/settting.go | 28 +++++++-- 6 files changed, 175 insertions(+), 111 deletions(-) create mode 100644 internal/conf/logger_meili.go create mode 100644 internal/conf/logger_zinc.go diff --git a/README.md b/README.md index 09e8dba5..4bc4454e 100644 --- a/README.md +++ b/README.md @@ -302,7 +302,7 @@ release/paopao-ce --no-default-features --features sqlite3,localoss,loggerfile,r * 日志: LoggerFile/LoggerZinc/LoggerMeili `LoggerFile` 使用文件写日志(目前状态: 稳定); `LoggerZinc` 使用[Zinc](https://github.com/zinclabs/zinc)写日志(目前状态: 稳定,推荐使用); - `LoggerMeili` 使用[Meilisearch](https://github.com/meilisearch/meilisearch)写日志(目前状态: 调试阶段); + `LoggerMeili` 使用[Meilisearch](https://github.com/meilisearch/meilisearch)写日志(目前状态: 内测阶段); * 支付: Alipay * 短信验证码: SmsJuhe(需要开启sms) `Sms`功能如果没有开启,任意短信验证码都可以绑定手机; diff --git a/config.yaml.sample b/config.yaml.sample index ff57d086..8598adc3 100644 --- a/config.yaml.sample +++ b/config.yaml.sample @@ -53,6 +53,8 @@ LoggerMeili: # 使用Meili写日志 Index: paopao-log ApiKey: paopao-meilisearch Secure: False + MinWorker: 5 # 最小后台工作者, 设置范围[5, 100], 默认5 + MaxLogBuffer: 100 # 最大log缓存条数, 设置范围[10, 10000], 默认100 JWT: # 鉴权加密 Secret: 18a6413dc4fe394c66345ebe501b2f26 Issuer: paopao-api diff --git a/internal/conf/logger.go b/internal/conf/logger.go index 7e8f787b..6f978f07 100644 --- a/internal/conf/logger.go +++ b/internal/conf/logger.go @@ -1,118 +1,12 @@ package conf import ( - "fmt" "io" - "time" - "github.com/meilisearch/meilisearch-go" - "github.com/rocboss/paopao-ce/pkg/json" "github.com/sirupsen/logrus" "gopkg.in/natefinch/lumberjack.v2" - "gopkg.in/resty.v1" ) -type logData struct { - Time time.Time `json:"time"` - Level logrus.Level `json:"level"` - Message string `json:"message"` - Data logrus.Fields `json:"data"` -} - -type zincLogIndex struct { - Index map[string]string `json:"index"` -} - -type zincLogHook struct { - host string - index string - user string - password string -} - -type meiliLogHook struct { - index *meilisearch.Index -} - -func (h *zincLogHook) Fire(entry *logrus.Entry) error { - index := &zincLogIndex{ - Index: map[string]string{ - "_index": h.index, - }, - } - indexBytes, _ := json.Marshal(index) - - data := &logData{ - Time: entry.Time, - Level: entry.Level, - Message: entry.Message, - Data: entry.Data, - } - dataBytes, _ := json.Marshal(data) - - logStr := string(indexBytes) + "\n" + string(dataBytes) + "\n" - client := resty.New() - - if _, err := client.SetDisableWarn(true).R(). - SetHeader("Content-Type", "application/json"). - SetBasicAuth(h.user, h.password). - SetBody(logStr). - Post(h.host); err != nil { - fmt.Println(err.Error()) - } - - return nil -} - -func (h *zincLogHook) Levels() []logrus.Level { - return logrus.AllLevels -} - -func (h *meiliLogHook) Fire(entry *logrus.Entry) error { - data := &logData{ - Time: entry.Time, - Level: entry.Level, - Message: entry.Message, - Data: entry.Data, - } - if _, err := h.index.AddDocuments(data); err != nil { - fmt.Println(err.Error()) - } - return nil -} - -func (h *meiliLogHook) Levels() []logrus.Level { - return logrus.AllLevels -} - -func newZincLogHook() *zincLogHook { - return &zincLogHook{ - host: loggerZincSetting.Endpoint() + "/es/_bulk", - index: loggerZincSetting.Index, - user: loggerZincSetting.User, - password: loggerZincSetting.Password, - } -} - -func newMeiliLogHook() *meiliLogHook { - client := meilisearch.NewClient(meilisearch.ClientConfig{ - Host: loggerMeiliSetting.Endpoint(), - APIKey: loggerMeiliSetting.ApiKey, - }) - - index := client.Index(loggerMeiliSetting.Index) - if _, err := index.FetchInfo(); err != nil { - logrus.Debugf("newMeiliLogHook create index because fetch index info error: %v", err) - client.CreateIndex(&meilisearch.IndexConfig{ - Uid: loggerMeiliSetting.Index, - }) - } - - return &meiliLogHook{ - index: index, - } -} - func newFileLogger() io.Writer { return &lumberjack.Logger{ Filename: loggerFileSetting.SavePath + "/" + loggerFileSetting.FileName + loggerFileSetting.FileExt, diff --git a/internal/conf/logger_meili.go b/internal/conf/logger_meili.go new file mode 100644 index 00000000..51a275c4 --- /dev/null +++ b/internal/conf/logger_meili.go @@ -0,0 +1,77 @@ +package conf + +import ( + "github.com/meilisearch/meilisearch-go" + "github.com/sirupsen/logrus" +) + +type meiliLogData []map[string]interface{} + +type meiliLogHook struct { + config meilisearch.ClientConfig + idxName string + addDocsCh chan *meiliLogData +} + +func (h *meiliLogHook) Fire(entry *logrus.Entry) error { + data := meiliLogData{{ + "id": entry.Time.Unix(), + "time": entry.Time, + "level": entry.Level, + "message": entry.Message, + "data": entry.Data, + }} + + // 先尝试进log缓存,否则直接加文档 + select { + case h.addDocsCh <- &data: + default: + h.index().AddDocuments(data) + } + + return nil +} + +func (h *meiliLogHook) handleAddDocs() { + index := h.index() + for item := range h.addDocsCh { + index.AddDocuments(item) + } +} + +func (h *meiliLogHook) Levels() []logrus.Level { + return logrus.AllLevels +} + +func (h *meiliLogHook) index() *meilisearch.Index { + return meilisearch.NewClient(h.config).Index(h.idxName) +} + +func newMeiliLogHook() *meiliLogHook { + hook := &meiliLogHook{ + config: meilisearch.ClientConfig{ + Host: loggerMeiliSetting.Endpoint(), + APIKey: loggerMeiliSetting.ApiKey, + }, + idxName: loggerMeiliSetting.Index, + } + + client := meilisearch.NewClient(hook.config) + index := client.Index(hook.idxName) + if _, err := index.FetchInfo(); err != nil { + client.CreateIndex(&meilisearch.IndexConfig{ + Uid: hook.idxName, + PrimaryKey: "id", + }) + } + + // 初始化addDocsCh + hook.addDocsCh = make(chan *meiliLogData, loggerMeiliSetting.maxLogBuffer()) + + // 启动后台log工作者 + for minWork := loggerMeiliSetting.minWork(); minWork > 0; minWork-- { + go hook.handleAddDocs() + } + + return hook +} diff --git a/internal/conf/logger_zinc.go b/internal/conf/logger_zinc.go new file mode 100644 index 00000000..211270c0 --- /dev/null +++ b/internal/conf/logger_zinc.go @@ -0,0 +1,71 @@ +package conf + +import ( + "fmt" + "time" + + "github.com/rocboss/paopao-ce/pkg/json" + "github.com/sirupsen/logrus" + "gopkg.in/resty.v1" +) + +type zincLogData struct { + Time time.Time `json:"time"` + Level logrus.Level `json:"level"` + Message string `json:"message"` + Data logrus.Fields `json:"data"` +} + +type zincLogIndex struct { + Index map[string]string `json:"index"` +} + +type zincLogHook struct { + host string + index string + user string + password string +} + +func (h *zincLogHook) Fire(entry *logrus.Entry) error { + index := &zincLogIndex{ + Index: map[string]string{ + "_index": h.index, + }, + } + indexBytes, _ := json.Marshal(index) + + data := &zincLogData{ + Time: entry.Time, + Level: entry.Level, + Message: entry.Message, + Data: entry.Data, + } + dataBytes, _ := json.Marshal(data) + + logStr := string(indexBytes) + "\n" + string(dataBytes) + "\n" + client := resty.New() + + if _, err := client.SetDisableWarn(true).R(). + SetHeader("Content-Type", "application/json"). + SetBasicAuth(h.user, h.password). + SetBody(logStr). + Post(h.host); err != nil { + fmt.Println(err.Error()) + } + + return nil +} + +func (h *zincLogHook) Levels() []logrus.Level { + return logrus.AllLevels +} + +func newZincLogHook() *zincLogHook { + return &zincLogHook{ + host: loggerZincSetting.Endpoint() + "/es/_bulk", + index: loggerZincSetting.Index, + user: loggerZincSetting.User, + password: loggerZincSetting.Password, + } +} diff --git a/internal/conf/settting.go b/internal/conf/settting.go index 4b3f9ae3..25210ab5 100644 --- a/internal/conf/settting.go +++ b/internal/conf/settting.go @@ -33,10 +33,12 @@ type LoggerZincSettingS struct { } type LoggerMeiliSettingS struct { - Host string - Index string - ApiKey string - Secure bool + Host string + Index string + ApiKey string + Secure bool + MaxLogBuffer int + MinWorker int } type ServerSettingS struct { @@ -361,6 +363,24 @@ func (s *LoggerMeiliSettingS) Endpoint() string { return endpoint(s.Host, s.Secure) } +func (s *LoggerMeiliSettingS) minWork() int { + if s.MinWorker < 5 { + return 5 + } else if s.MinWorker > 100 { + return 100 + } + return s.MinWorker +} + +func (s *LoggerMeiliSettingS) maxLogBuffer() int { + if s.MaxLogBuffer < 10 { + return 10 + } else if s.MaxLogBuffer > 1000 { + return 1000 + } + return s.MaxLogBuffer +} + func (s *ZincSettingS) Endpoint() string { return endpoint(s.Host, s.Secure) }