From bc96133c4074e3e98400d8441e763b1e2c66efda Mon Sep 17 00:00:00 2001 From: HenryShan <zshan2@illinois.edu> Date: Mon, 15 Mar 2021 16:08:01 +0800 Subject: [PATCH] assignment-2.2 ver2.0 CSS style added, creating form input --- DataBase/mongoDB.py | 3 - RegularExpressionParser/Parser.py | 2 + Server/SimpleServer.py | 36 +++++-- Tests/ServerTests.py | 4 +- web/app/src/App.js | 134 ++++++++++++++++++++------ web/app/src/material/sign_correct.png | Bin 0 -> 8706 bytes web/app/src/material/sign_error.png | Bin 0 -> 9611 bytes 7 files changed, 138 insertions(+), 41 deletions(-) create mode 100644 web/app/src/material/sign_correct.png create mode 100644 web/app/src/material/sign_error.png diff --git a/DataBase/mongoDB.py b/DataBase/mongoDB.py index 1c794be..ef437fc 100644 --- a/DataBase/mongoDB.py +++ b/DataBase/mongoDB.py @@ -98,9 +98,6 @@ def get_documents_json(opt, identifier): for item in data: item.pop("_id") file[typeName].append(item) - print("===============================================") - print(file) - print("===============================================") return json.dumps(file) diff --git a/RegularExpressionParser/Parser.py b/RegularExpressionParser/Parser.py index 8df3f22..abb8e02 100644 --- a/RegularExpressionParser/Parser.py +++ b/RegularExpressionParser/Parser.py @@ -77,6 +77,8 @@ def parse_query_to_json(pair): else: return {elements[0].split(".")[1]: {"$regex": elements[1], "$options": "i"}} else: + if len(elements[0].split(".")) != 2: + return {"wrong": "True"} if re.search(">", pair): return {elements[0].split(".")[1]: {"$gt": float(elements[1])}} elif re.search("<", pair): diff --git a/Server/SimpleServer.py b/Server/SimpleServer.py index fa1ed3b..bdc8d5c 100644 --- a/Server/SimpleServer.py +++ b/Server/SimpleServer.py @@ -7,8 +7,10 @@ from flask import request from flask import abort from flask import jsonify from urllib.parse import urlparse, parse_qs +from flask_cors import CORS app = Flask(__name__) +cors = CORS(app) # app.config["DEBUG"] = True @@ -26,22 +28,33 @@ def data_base(collection): url_parsed = urlparse(request.url) qs_parsed = parse_qs(url_parsed.query) if qs_parsed == {}: - return jsonify(DataBase.get_documents_json(0, {})) - return jsonify(search_document(["book.id:" + qs_parsed["id"][0]])) + return DataBase.get_documents_json(0, {}) + result = search_document(["book.id:" + qs_parsed["id"][0]]) + if result == {"wrong": "True"}: + abort(400, "Bad Request") + else: + return result elif collection == "author": url_parsed = urlparse(request.url) qs_parsed = parse_qs(url_parsed.query) if qs_parsed == {}: - return jsonify(DataBase.get_documents_json(1, {})) - return jsonify(search_document(["author.id:" + qs_parsed["id"][0]])) + return DataBase.get_documents_json(1, {}) + result = search_document(["author.id:" + qs_parsed["id"][0]]) + if result == {"wrong": "True"}: + abort(400, "Bad Request") + else: + return result elif collection == "search": url_parsed = urlparse(request.url) query = Parser.parse_url_to_query(url_parsed.query) qs_parsed = query.replace("q=", "") result = search_document(qs_parsed.split("&")) - return result + if result == {"wrong": "True"}: + abort(400, "Bad Request") + else: + return result else: - abort(404) + abort(400, "Bad Request") elif request.method == "PUT": if request.headers["Content-Type"] != "application/json": abort(415) @@ -51,7 +64,7 @@ def data_base(collection): elif collection == "author": opt = 1 else: - abort(400) + abort(400, "Bad Request") DataBase.update_dicts(opt, request.args.to_dict(), json_update_info) return "200: PUT succeeded" elif request.method == "POST": @@ -74,7 +87,7 @@ def data_base(collection): Scrape.scrape_api(url, max_book, max_author) return "200: new data has been added to database" else: - abort(400) + abort(400, "Bad Request") return "200: POST succeeded" elif request.method == "DELETE": identifier = request.args.to_dict() @@ -93,6 +106,8 @@ def search_document(identifiers): """ function used to find one or several document in database """ if len(identifiers) == 1: json_idt = Parser.parse_query_to_json(identifiers[0]) + if json_idt == {"wrong": "True"}: + return {"wrong": "True"} print(json_idt) if re.search("^book.*", identifiers[0]): return DataBase.get_documents_json(0, json_idt) @@ -113,6 +128,8 @@ def search_document(identifiers): opt = 1 json_idt1 = Parser.parse_query_to_json(identifiers[0]) json_idt2 = Parser.parse_query_to_json(identifiers[2]) + if json_idt1 == {"wrong": "True"} or json_idt2 == {"wrong": "True"}: + return {"wrong": "True"} if identifiers[1] == "AND": exp = {"$and": [json_idt1, json_idt2]} elif identifiers[1] == "OR": @@ -124,7 +141,8 @@ def search_document(identifiers): print(exp) return DataBase.get_documents_json(opt, exp) else: - return "Error, unknown identifiers" + print("Error, unknown identifiers") + return {} if __name__ == "__main__": diff --git a/Tests/ServerTests.py b/Tests/ServerTests.py index f069b7c..e0aff99 100644 --- a/Tests/ServerTests.py +++ b/Tests/ServerTests.py @@ -38,13 +38,13 @@ class DataBaseTests(unittest.TestCase): url = "http://127.0.0.1:5000/api/book?id=12345678" res = requests.get(url) self.assertEqual(res.status_code, 200) - self.assertEqual(res.json(), "{'books': []}") + self.assertEqual(res.json(), {'books': []}) def test_invalid_get_wrong_collection_name(self): db.insert_document(self.test_data1, 0) url = "http://127.0.0.1:5000/api/bookssss?id=38746485" res = requests.get(url) - self.assertEqual(res.status_code, 404) + self.assertEqual(res.status_code, 400) def test_valid_put(self): db.insert_document(self.test_data1, 0) diff --git a/web/app/src/App.js b/web/app/src/App.js index de608eb..a219b6e 100644 --- a/web/app/src/App.js +++ b/web/app/src/App.js @@ -25,59 +25,139 @@ //export default App; import React, { useState } from 'react'; -import FourButtons from './Components/FourButtons' +import FourButtons from './Components/FourButtons'; +import { Dialog } from 'react-overlay-pack'; +import CorrectSign from './material/sign_correct.png'; +import ErrorSign from './material/sign_error.png'; + function App() { const { useState } = React; const [queryStr, setQueryStr] = useState('') - const [jsonValue, setJsonValue] = useState('') + const [sign, setSign] = useState(CorrectSign) const [text, setText] = useState('This area shows the result of requests..') + const [formState, setFormState] = useState('hide') + const [dialogState, setDialogState] = useState(false) const axios = require('axios').default; + var center = {display: 'flex', justifyContent: 'center', alignItems: 'center'} + + function get() { + axios.get('http://127.0.0.1:5000/api/' + queryStr) + .then(function (response) { + // handle success + setSign(CorrectSign) + showDialog() + document.getElementById('dialog').value = response.status + ":\n" + response.statusText + document.getElementById('board').value = JSON.stringify(response.data) + }) + .catch(function (error) { + // handle error + setSign(ErrorSign) + showDialog() + document.getElementById('dialog').value = error + }) + } + function put() { + changeFormState() + } function mesg() { - alert('Hey!') + document.getElementById('board').value = 'Hello there!' + } + function changeFormState() { + if (formState === 'hide') { + setFormState('show') + } else { + setFormState('hide') + } + } + function showDialog() { + setDialogState(true) } return ( <div> + {dialogState === true && + <Dialog + show={dialogState} + onOutsideClick={() => setDialogState(false)}> + <div style={{marginTop:'20%'}}> + <div style={{ + marginLeft: '35%' + }}> + <img src={sign}/> + </div> + <div> + <textarea id='dialog' + readOnly={true} + rows="3" + cols="38" + style={{ + border:'2px solid silver', + backgroundColor: 'white', + fontSize: '200%', + margin: '50px' + }}> + {text} + </textarea> + </div> + </div> + </Dialog>} <form name='mainform'> - <h1> Welcome to the home page of GoodReads Crawler! </h1> - <h3> Please input your query string:</h3> - <input - id='queryString' - type='text' - placeholder='example: book?id=12345678' - size='40' - value={queryStr} - onChange={(e) => setQueryStr(e.target.value)} - /> - <h3> Please input your json parameters (only effective for POST and PUT):</h3> - <input - id='jsonValue' - type='text' - placeholder='example: {"rating_counr": 1000000}' - size='40' - value={jsonValue} - onChange={(e) => setJsonValue(e.target.value)} - /> + <h1 style={center}> + Welcome to the home page of GoodReads Crawler! </h1> + <h3 style={center}> Please input your query string:</h3> + <div style={center}> + <input + id='queryString' + type='text' + placeholder='example: book?id=12345678' + size='40' + value={queryStr} + onChange={(e) => setQueryStr(e.target.value)} + /> + </div> + <h3 style={center}> Please input your data for uploading (only effective for POST and PUT):</h3> + {formState === 'hide' && + <div style={center}> + <button style={{width:60}} onClick={changeFormState}> + book + </button> + <button style={{marginLeft:10, width:60}} onClick={changeFormState}> + author + </button> + </div>} + {formState === 'show' && + <form style={center}> + <table border="1" style="width:100%;"> + <tr> + <td>Cell 1</td> + <input/> + </tr> + <tr> + <td>Cell 3</td> + <input/> + </tr> + </table> + </form>} </form> - <div style={{marginTop:20}}> + <div style={{marginTop:20, display: 'flex', justifyContent: 'center', alignItems: 'center'}}> <FourButtons backColor='blue' borderColor='RoyalBlue' leftMargin={0} text='GET' - func={mesg} /> + func={get} /> <FourButtons backColor='black' borderColor='gray' leftMargin={20} text='PUT' - func={mesg} /> + func={put} /> <FourButtons backColor='green' borderColor='seagreen' leftMargin={20} text='POST' - func={mesg} /> + func={showDialog} /> <FourButtons backColor='red' borderColor='indianred' @@ -85,7 +165,7 @@ function App() { text='DELETE' func={mesg} /> </div> - <div style={{marginTop:20}}> + <div style={{marginTop:20, display: 'flex', justifyContent: 'center', alignItems: 'center'}}> <textarea id="board" name="returnData" rows="6" cols="50" value={text} readOnly={true}> </textarea> </div> diff --git a/web/app/src/material/sign_correct.png b/web/app/src/material/sign_correct.png new file mode 100644 index 0000000000000000000000000000000000000000..f4c62915fc0b2c590da99790c4369a3e73f75cfe GIT binary patch literal 8706 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 literal 0 HcmV?d00001 diff --git a/web/app/src/material/sign_error.png b/web/app/src/material/sign_error.png new file mode 100644 index 0000000000000000000000000000000000000000..52c8cb30694f970a86ed6baf14741e1a03183ca1 GIT binary patch literal 9611 zcmV;6C3M<}P)<h;3K|Lk000e1NJLTq0040S003|Z1^@s6DI%YB00001b5ch_0Itp) z=>Px#1ZP1_K>z@;j|==^1poj532;bRa{vGi!vFvd!vV){sAK>DB_K&eK~#8N?Okh- zROOjI*<30hFiov8Fs05gh^b1{Sh~i5Sc&DI2wSBPTvJ}L3{YSJsS^CNmBcEEj48ns zq!6i+N>Upm7o|-26PV31!kUOx3%H6GoMw8k=fW@-E;G~9J#%`W=l#C-^y!|{_Z}{? zZ~1zf=|11(ectPLzH^RqsZcVR{0jJ=z+VS{({<g6I9@&Ay4T_JOz<sn*PWek-TPvv zI~NXjL?1qP`0%4Wf7<@o>9y+~KehPjpLWh$_-yN)_ij0H>m3K0Lo?c1ohgG6Co~dq zYTS-#Hz!+ey(1a<{=II;+=U}2m#-Y`dikZczBjiYIDho$mUw6T8h2>;$zj+1H}=84 zhR|os^nD%2V#oANJ4TN6D|Yx-Hc=2(Am4zHuEOzZ@bwVhw;{+`_}hKMeFG0c(2Kj* zZ(P$cZ{fQ3X*X>KIIlH3&hJ_r=l88n)$b!t^&i2zTb=3yt<JR1!F?^E+W3i3og1mD zb6cI7B)NIwRF|OQcX6E87j>M^(Z&Is`_>orv;S~v@$y$rz4}^1?9-Mt!@cJhqyL8p zs_Vu%mTz-x9HSlUH^5gen4Gv300l7tLYV@-0l?h}q0JpQ9(lZL%WKa>e)3oYtZEAc z{d%i2ZVy3fo>=`qt&{3LY^|z0)>2jXF^(Tcs%k$0V9m{;`e=Ca3<4O7hU!PaE`e+b z)g|%wq=E_{N5T`UV=Yy+eP}C+HUNChF(}7jg37*HzI(^(oj+Z+rSGjh4YBTyXE46U z!E-rAj+JBP*zFvy956XiJ7GU~Gih~+&b9@8Z|+#p^T3l0;R#c>wL0V9hPAvCnN)X} zAhk}aZE3Bl?SRmG@cB$+QtesrxfIu(2N;9+O&mat2w1ejs=B0~_3!)*f6H@tF58o~ zrH|T{wov^i7Swmzr{45-*V5J7diL#WfK{%5qAkF<X`OVpR|c4zn4O@2cg_c1|B)jL zI-Xv(3IbdY;C4WmO>l1?;NM|b6hZ5?APqpkF#}R<!f@Si1{1uj04nSE_P1o5TbnST z*2dUZI8=WQ7TgO9?SKV`BhI)F0B959>kh=>^|79gRRU(*ofUIqUv4n%gqRGZS)PD5 zZ{e!2<7_|@va_vf`a!tPPY}=AnkUcbZL6y9ZwpP2F}C>tQ=xl6<>DX!$w9vb``aFd z%S~V`ak^%VtDnkrqN=Vf;!ODjV?Ky@y0d%j3mf7c(N&mJkY!2&WW{iK5+*?F`zj_i z4o4;fjCr%p{{7H`o(C3L@IpHw)PoQdz^iGGR!u*HI5!v#O=nUv49*HzCNSl4P#<6q zR9Y#=MpquKs&6-A9)FN?=>GZ7H@GKH8p||b-eBj<xqp?iuwuD<2!j^aiAhWXUmH&* zZ%;%!?r&edd?mnx+ngfcNvx}?Yo}mv9L%t|G=ZwHFBE6?4;C<u+cjX~*v>h8CSXq4 zd3wXf^{zX-vfp*@k0;}|bMBM@WkH!3>0IJOjRTAIolTylCf=BE6Zf3@&HAU{k{Y13 z+cEKM;9Yd!vT^3Pz!+;fn6%6Y-R!MD*lrRa_bFKBJ(K}t;#s8?)ul<)G;o^r^&dVw zzwPe1t5MT!4Nsif2sgyC;IaWPlQYK&_P|6UBw(^sfwyUFUAAItyt8dJfSeC`+y<@z zv%dTq_|kK))G!QCVjl{1Sf6{(saIZm3K`vtsK<7pUOR*gGs5J66CMljSPPPazVoyG z4kbk3_l0ogxWPWltm_SjW<(;+_(LC7&)jw9-G4L;CX<Vh#oh%O)q*Ec*}<2Fd!<HM z0IxzEpr+lH=<T21`sl-}+h~20XEZ`HKgO}s)NxY7jf#1hLMn#+;1L)W^?s1_d;Por zJpEiM`{w}WoFoB4W_up)wbNSV-ZN`A{uW8yGRWs%lq27wvg7l`cdvc`!PjAsv(LZR zv<O<b4n^{=6QSBen2cB-;$fim1qr5Pwo7ZRPIVd|cdqjNTYpni5fGLAa{`N1GLvne zdQ)%ngsEWw`3}O$Yq9RmH39DR;$*L*08A$5^zGiU1c7}M1hf|uJ4Q`x4%LhGaS6;n z(M%y^vI;n>paqceN%V(fNUW^&`}Zb0=RKH={N%CZsm06Fn13VldoF~VHd5ycu)g*) z`z;i3{~W=?kiigFf)FoHqM!HMr`@y*^>M>6-7A!yvb!ii`T^us27I2=^}AP>LfbZD z!24l&;assVbK*fvS4hF}C~gr#rbVF-`JP~Oy}Ulz)7Y5oKYBEo=xj?S6C)T(9>PyX z`udaZ7blbb@4u7md1HIBYt3__0De%lUj<Lb;2U2q_Rke8OM5nyh<k;H+I#-Ukxc+{ zNiv?CbHRaRC+v)!vU7Z%)4hJ<(kKCRYMPLBwwTNl8fao)QQ#?yWr^E^m7!ZC2+8hW z{5*N~-S-S&?nrU~okI}9L-t6Ja+3XCf2-%RJp$YJ)}G|a<tvlOq&+|hq>LwEoVnOH z_YnY>XsfCpgfeg$FC1~E>>v2+-!>xzEu|N`U_i2QcH#%HYxQ#iU`thPQ}g5*Ep#2C zc|Pk4nqWRw0aBo}Hd+}$BgK;mVG15!m_G_Q_U}1}pz$|6lBG?aE9dinu6$2LA)s`@ zNYG>~LF3HFDg(<*dWJ;FothSuT}{1jZr?l%AklFekXGcCB{V!L26jK+ATG3da(xpA z1t5z9(3fD)T~YhRUBaz(y}B(K>*+KWLn2_Funoy~__z0)LMW{b*BpFpW`4)EX`x+< zekO`VmT|%|c~qCHZZ{jNJSZEHkz6sUZ9y5*)ZN&)8Qm;lVhGtX*<sdN`SJserpLfn zw=HckF;HA+u@<N<icCQ;-tkEAJnI4#1yF@atQsDGW1z(fn2euLGBS4yq}+abVid$G z7s^P1Y@Sd}i`;Kx<f0*W=r(l43*)RKE9X|x*gsJ_s3p{vzTJD49CyZhF|ZUDO2Fi^ zWSJom4lR%IX#{3Yx|jfZ1P{P*=$J?ZQ?g+38r4O`OqCC1MVVRG8g6yQ?M-yFHMj|P ze#~{h1zAr*SCv`WVIyE_1&h<$dK()T!R2j=R@Ea5Ee$}jxKA|Se)l}dE-5HYFvJ4X z?TLG{Zto(HD3TqM#81h9=_y>q%pGElVwnMR%*HAw$_%m_f)XIvnR1Nf2lx7*j=UGL zu0<!joK=3Jg9g4vtpMMc=!nioXX~JSdr|j=5d)cp7Ue=UhER*U*RB%*33!4Q!4lLi zJz`8YAHe1_$IS$&=Yc0hA$5L1bI)Xz7iGtVkZ5Rn=V#7U?{~~y_?qj+m%Hx3-H>_t zi4IOyH+n6vM0C!5aJ9XJbj;L&QbrVPfhK;KERPu{g`lqLl5m|xlIQvXaQB8y0;HGp z6yRP}Zpi9+0s1@_1klET)6cJar5}k7<UZRjWtNuc;3RRQXy6(&c>C$+fAtisXDf>L zLjbQ6gFqo)oC}p0f})x+MMxAnrXMap0(ewDEq$n%R8q}agJh7&lpQy7g(uf10Hh>3 zDB1R({qOfS4Y}?Ukbmik4o>jgX#ZdL&j$c6o(R=7A_hieQ<1Nb4;tJk=4uFPE-f20 z?ujN}rWk6x^n=OxZafYWwjenKumy4iKuV&+wN@C)-ozIj>s&W+KQQ1f0f~<mp8SL# ziyMV0Upv^_eosqP-I{P{#x7)0AET2#8wc|OsPT{5$@NPDrpdwO=st53HPOtAo~G^A z9}1A33<Bh%tg-}4qJwjo=zMhY(MR3@1}lLDw|j8kPGQ!_AK4#Y4VjA@<po_{kznve zCoy)G4?tmCJ`h07?l{TIH3sG*GpB@?@R;@3(TnRr><3cz@l@5dw>i$i?tS}SM#-@d zAbMqog(^GYM@(tCEA0ljhWjyyhE``<BQntluSlSi!rdq)0BsT_O)DEfUq%o)H}Nz$ zDv;6oy9xr4ta9Z=18x`#PpJO1ed^6`#ohRtMDMwK=ox`a?j#3aYBDngC(b%G_raB) zIpeoOGmg>3TV(5Cb^z%*ysQiWvLL4G)GU<5<CoP6Jr85!WO|HDEidMBuaqr!_#r;V za8757;~YA@cHP!N*L{kf5l!Y!a`1&(;mp%-yzyhCyBoL{6pgooy=>Iqsh>R%0U(cz z4H^TlI8v(wF>7UJt^j(99>8iO;9*H1kw^lE#*r^tx$!D@l7l~bRyYvvo&`6sDm<}z zCrq2$=g-*df?2JQtQyBve_k@;B)nvX+Ia;L=gknJUWnH|31q$`hn$i#m*u#cQ+2Fs z$(rqXlN|iPv%=op#tKhxQ?FQIVabeW8JglEHU}^9k5*_?@GFf7qNl|8%o0lyodDTo zvIP>^YTck<;EX>w_*u((Bstuitk#%{jrd1eEc9C;*G98hp<k=$DwvIO35o+&4fJ9} z31ooJJeSK}W4o3r3dlrE#%;JHCA9f~w1Lm8ky$URTg_#aDsRf3%bO8r+$UXYez^k} zt#IA=ooJM?k+%sLYs3fc7nO{(ckkW>QEsm<&<e%+;6}MhL08H+=viS|M4*fyjR0e# zGTbpCwhe)^Pg;;FW4V2D>}-dN<{BSG@dIS4Y=Io%jsV^R60NFlVr)z#+<9o~df>%O z^($(HMD*&o>)zQuXYL9BvO{AbYQvndP=hoF#`Qx|B_^%sv#m@XOBC6@A2_;09#egn zNN7W2B(rv12vDocl&i?p%AMB8*oaK^gYLEKwhf!*xbJ{jH-1$>B%;u|>(3uPJP(C^ z0})3fZK46@hL)^WNb92&vK|um;~Q)_q(X}wZ;|Yd8q8#go1DrLW*(BO66kMW?|+&7 zJYRJ904&dqt2GWC{mATKfDChiNT%$gq59!w-j(G{Inj3O5C0tN?tB)y#qAu`Xzm-5 z&=AofAba}R=bkw3T(u2xkoN}mW{ZV>rX|t}Iq9;53-AYqdPVc80IaWgt&vy!**=-^ zgcFuaSXLlBuNaaf1MZRnCG+28h5@cDu@4b2*LvkxBl~;^CdXB-aw_Q#0X2jm*^;gN zM`Y_`RqZK^>F?e9_WdT|x<7Vf?o2c_fgpYzAYS9T1G77)eutNu{tN(5phoG($+9{V z)HvCc6xCvdBAgNv7!rj;00|7Y5&AbL`L$JC5WeHuBohS!QB=R0Y~U1imE5}MDT?+1 zmU+t3p%FGMh$PBW##~8M8r{^aAt{J0h~iQu$#g=Wb}e1A11@!i8+X5lrmlAAO2M@~ z`}X}ST<NcIB8q!nYD!%z%xqYh6XnFYD~u!pEtMb>Dgp&ckVZ`m+Dbu^Re5+-UEN*b zWRfFI{S`p=k=dvAB?_ed!QC2bEDuDsK?^UW<gh?;aXf~9w?&+){e#^dFTfgq1njOO zh{lyJ`q>i*oZG^o8UM>G6$QCcUN?h92zZ!NIqsC_GTxch{~FKw)G9=fwqO2Rf#hy( zYPIP*mtftr--sJU2r#bG#C3lk!d7wgGr{KGSGH5mTsanwgCT<;j_Nmq6$fI19Hj^2 zINbZzU$y|dhrlxl;xz-^9kWi{ddD*m%G+pAU1^{dvO^WBObQvPOsGr{NlYZzX1+<K zu?12sB-0zD1dajP#ESqjdC{^cu-yQbapCuL<7E@Z!f}}efc)1UEs(=-spsfY+n!$b zI<Q;px<j`R#Oux<J$hfOGp+#^cmO8bk;|3(EreP`D-bdYUNR8?Dn!vBk}(d(WxCc; zF5T9A0%REgk~uC~E-lC?M(M=!IcH|wjJZMm`PqWVnsI<vl;GO;xNd9>cOMeOncXjK zd<X^pmgdPbjsTpzS*g*;YSAJ^PlZ$to%vO1AetR#X3;&f1d-1Gl4GOL2%t$QioYw0 zzh}H=8)Xy5LxrQ_WyazlPT|4bakZTo#{u4$1N`O^#9L1Nbot`r&iL2y<)>n<IkHln zI5m=5M53Vlf-8l1KG$3e(aKDwRhbxRHuR>F4-=~iqFNx8#CVb7tdPv}IPY^G{ljFo zC6kHI1d&Hga70P0r_1bHL8O7<`(DiBNY@`;-3t63b=)%pv%{0?*WibH!V{{we`Wwb z&RO6aAylVaX$6Rrk!x=ySYjGuT$;5KI10*hp=GwMbLSFZxk@W!<D%U2fw4)Vm<2?# z1#-A~!qoE-XWH@BM;_h<5T9{kM?bm`rrB_!s`h}GX=pm*VOCcv3@v6MUXh7-WLA0D zZXtm_7t>uzvSZ5+1+WzB^D|v3<2J`x?%d^k>*DD-Xo>3LbAiYeG<+W9<ImuE-T{cO zIM9LzF~Q9^@i4TYm$zW$1Cc^vs$E%fWkM$HE)PWGDMC`NaN)otF}aKCyv#9u|K8LB z1Ko6)q(Jh>#7r$Quzzq0m)bby(ub1p^F&A60jFpE#z!O0xNRq@>OL};opP_zkZJlA z3GSJM3DNb@y@=(YFE8F@j3F0$+Dyx{F*cWQa<PfEne}MpFNJX_QLHgw49W^3SPFm0 zsPD|X??s)~Wh<YGI930FfXQozIg=WX(HR$}D@zazwc7%zg*b&MmJ~FOF=owrd4p>z zSsEMCOH@JQ@H4T2OBEpT`#6gJL8pDryk{d$&F<Dob<Meoe2?k3ZE;<0Tu39EL6USy zj6iZ?rCL$hY%L?Tw3ktXR**EQD`c($qEiD7jhtHj+=vqmP2XaQ{JJ)9-px>&)Kq#x zqU8Wm({2UTzcdy&p0Q#Zm(A)-{yUJ%B^rg!Gh1S#OnkWYpAX>ee&9*hX?3ay;OAT~ zWx(VQa)TI>yJm$d5Ztp7K~;-njPw`i`6gK^r($Wyt2ZwUL6G>5ibG2*WPWCx#-JQz zI8h6rw+$mVh~wp2n28US5AZ2686d4vEm9H;JNUq{mC2^<)LLvs!$nRqR&HW&VRKVC zFaviGbMii^H;n<JE_#pYB;iig#j+oM))-lcrk2ENfp(!lg**0S`jKt_LE{Qw1(D-* znjMGW6$T)f1+TfUer!SX14!0b$<l^n;8-}3a+4lUAz*>Bh^`hmhM{6K84!8PLot@f zxs&Nm1-wE*q$MyN9!mg6lf4#b1?va0B5XCf|5U_uaFNJt56ORhF^!>oPO-w^?<qtR z6E7UGKmSUJE|wQH;Q%(PIU!lpzL-QM<wCeD85S`o{+{mi|1I%yx5Q#&B0;1Tj5UbT zK4PMY7Y0OHBLU<XI2MfynPV)5J%MG$MBave!Q!HciDo})aoH(9mzAB4X^G*>PAN=( z;a@IrS(#RV@`6i>lAV^hqpff%Gdl$+CDn;z4x+|CAAt1#0$_pKS`urWh%)2C#eJhJ z>^E>s0jwZOorpmfy<&5E=^6pi068|qB*wpDV-gTs><5jj0?fAcss)c1uHZM8$T=43 zWfVkNu_=D-wAyz;xN`2sbDfxw)`Wf+8aS4*WXzLCy?!W}@fZ>nrsTELQm<lE{GJFK z17+`u&xMX@49v#_67x;D*0BSU+cgWF>!_<3rCm>~uK4vtb)T64Ud-!>#<kk(iDMii zS+*78afr>R>xp`0A3rGg%Dxu-l_arhSfdvMtyUHjC;Y52gNc~`@5Q#IF_KxJUfHjx z2eMSwmHo^;HV|5odu%F|p;m5>jWEDt<|Z0$lOh2m=(mM1$Al7qVMJqgxg>4aw1Q(* zh0PWdQ@CX8vGMPA!xu&EcB^D!!{VACvfXk)q>}jycX|zRRbrW?mnZZ<`aFViU8@4f zBge@xb8G}K7l?to-MoFL&a^$;9{#1-cM8E6LC3^J6G#zM&^ZM%$Hlgp;0Orik_D0R zp9-S$&IKZP^uANR{8)E@Bf0q$7FkeoLt)cZkvNcYC&ly2S=2VMYrJQHA|WYRKsMG= zSX{CzCB~9@9_LMElO(JhlA)PR$o?JRLH&{~fUrb!>vwb74sh3<dFtht9)bY2z!HxX za)+>V294*v7#bf0L@TpWN$<r7>N786JOq$+UAjVz&U)2#6&oWtZ(0n?f%HB~ASf1P z$E>%Iais959Rl-7Q*tjx!rqJF-4(^Sh#%y<D~ilcjY~CmMJa&gOpNF1T~S3_3IODo zD3DP_xofPD?duAVy4GU*A&49wEwMZh&1)Oz?&-~_7(xB{*@EcZ6&0<To_1H%HHeG$ z{={i-`)>8l3L+=36Y>_r%LI|61#d35GK#GQ@-k9j)=2LlZk22@X$8_`t%WB##siuA z6U&`z`u&Od$6!Gm26*Q_bH;z#xp>*@2!(uwyxgB??i619>=UdPQ7aa8r!ZG<C_HL; zITMp}F)>ZG*}k|hYc2lI9s@vGthQOGTy-E;K1v@GK{P*5*Q!b*a|uu$RahRWw7U3T z&RkWQxL}em7LpYQVge)UK|DO%^QY}w++p4+9FseR#|_wf_A$s`^HE^*x%phGGAG93 zyu3*nVjKQ@_Whu#Fh$^uA)LIu+E85I_UedDcCz)SeR8!%{tkuF3&$qwEu%h0|7=4O zn#_JP*@Ec1XMf86{!=Y406TNfKH9QR&v0jdQFm8SPtPDzgXzvFkW4Emh6se#UTIIy z@YPOaPMF`(VrhZWZ!sMMJn09gXu_knnM;2eb0c`ndihynp(u^G=+cR~L?H>51yHO| zR@9v8Pf<;zKRpBgEBQG#6e^Dt09{i#BuR&Tj*VC(1jfRj6J`4X9GDYk7ORT`3P_<u z(AkGcfXK!oDOBAaK^MzD4Y2k(Hn#38w!dto&#^IHC3{S?@~0KTGiE%;2CfuM$#ZPH zCrZT*ADf3CH2}n>=Fp6So+zcRhSn(BiT^}EWlmHzp2B4k!Yqy!!(}fK<QNg%Gy9`b zvOit;7V9g6jM5k-8jB}N1wPxYh3}Bh^PE!!(P&fprebkb<&aHMv^_sp#1yg++gE{; zI1ja4(|2j4ZBQX9{%p6*r!4mG{m+7*vgij<1_HN0%MnW<2<4K*9laoHp0X%{95W1& ztZ>}um;G=a%IalGB~<{u<fH&*%GA#m#QaZLv>^JPck0UXPGN;R(Mitdostzia}0T_ zICL9k;3=ULN2VZ71I-g+8JS@oQt(V?mH}+>;|JkXa~{X*N_|O>rPe1h%aEtC%A2xB zaqm6vR1z9^Of;DOyi*eXG3v>~XsoCw57Q!<4CqR~ajEtNUSd^O4iP*75c8%i_}LWp z`AZ721yQb86!hd_68<38c+U3>@K$G9QO^M9;FyXti)2!unV`|^D~tSbl=J5^z$uqp z)~B43bCJ&gC*hBytg)D<@pFI*B#UImM^5C51QL!%JP=5h_#j?&qml>)$d9Z5f&-t% zKj;qTe&rCp@M5Fu#%J}+e{v<ve|vMN{ur#1Z)wZBrBO7Ebr0NzycYxUL;$pjj!di) zNzWxlv^XlOxKJX9|M*$1k=j~Nl{`pp%2vMeUJSkw2pOro7Xz`du=iq+@TK>Ln%L2g zyf@T@Lo*`CkcxUk4GVR7Lk-AG4&=-JzI24CQDN2daDjkJb-9XpKd0nc8%HME*F3TM zW4;*Eb%)ouiNrm?B>x*~Nce$RBbbPLuiPu`T~Wp&^<7aKBXqJ~Hqy|+Tz~+2?}~yf z3ba7-FpY&z_d*P9?Af>P#f0n52Y5GvSr_JiR}=|9^xoRWcNB5^Ml@8v3!Rnq);3xo zCrA*5zf@$(9x5qw0RqTOopKNvWXQ(PJ^&>fh4ObsoGFJQ4?Vhd2yPTj-VQvj!7;ZR z%_RJIOx~ZEa;5eNFjKMQ@**>4vp~Sl^ezO@xX{8kdr^kXlV^;;)5QTeveBAjG@5v4 z+iJ+aa5tJsI6;$~ioMB{DK<4!FCP&+b+eZxS@6WW@VJ)`hv1bLUim0I4suKWht)SE zB2M*L7)ls&+IQyNe>A`o7Vkzg2`7%hJB1l1Yb5Pf?Y&%Gt0pl7lk3JTBUoP1eda17 zlvQp3PTnbeqN?`facBHH-Rn30b}*S-1i4q{o%=XB@a^8z7%m6d_?Q<+PRztdbN!6* zLSBsk0Z<s8YqGH=or3;$fr(i)C^b&wMVP>3zTKNZ2FbwBDlf{e1zt^(Mki$7?mfKB zbqDT(%u9bMw4cy`m#tDW_?~?LunC$|)_eBJEJ1k9kgGc+ld)1**1cXT7776AGSzDk zNHQ;8oA_teLxbiR#40DsjNZ(|!O`#82bcGHc0ZvZb*nYpUDdaH&k_`&o0})sH<b#c zH))9<A-PCOttJ^T_38N+nQYwiCtob!xsX9`qV7|?OvW68Smi@mQD$7ygm((JI^*^x zI@%iCggZaxd-W{1bT^twXpB1mt^!|&`ONA1-K$FpBmgW4NS(CC4q7FF<>I+{o?1$1 z7i;lc5-w|bTKkzR@=RWg^>O?>7GjAi7s^OvMVUD@EzORz|NN07o6ygqA$O=UaWIJ3 zAUF-k?&li>NW{h_4hF}9ffNuCA31q`7J=Bsa|(!w0>Ly^X$49mo)%LD*#nzY2;ROn zOsw<8T<`o;L)?|dyQ67&92>_;@O0cfs#0cSl?Qi9wD7gQ1dz(_jBo00Y}|})me9*V zwv~y4*$54f2J#pG?g1ICsxK=>`k9FWHJ<xhn91t43<BnrCDzpm;j{PmtYofHn&0@! z16sO0Uw^~?xQmaqt%4Uc&U~ygusjUe3|SXS3vB9rbNl9D*IkN^x!vMA$`+q169=;q zI!FT&BAwIq@_OT1of>hiGDuT?cnbpLph;$9wNwI0A#!^nvsu~q!q^F+Yob9WAPRCZ z#B-z_w8#3Dz)^8%Z3ev3pA+Dl`v~u$;1zmUE{pjvw;c}r^>3SDeM@N!egMIhb)!MV zPS_bc9S|T9BVXON6xPESiLA9QZ0blZ2*`Cv&nn58lMQ0On^jgo>B^Ya)8*1inP>>G zw5XI9%u00ATv_3H%rGU+$vUULsm)xtl|JowG<Fxl{<(rB{&77NN0bpY#{lSF8v~cX z>@PfkO6-iCvU3iC+SIpu#}fGXO{^m^nPZrEC*RF4?$xO&3P=S|KkL!63dw3yLT9Sa zy{M8k!Ub}zmdsejqqs#dzo%83Hd5z{)$?zby?%;hogZQ!kHd(6L(CPR+$YiB`yW=% z+{GJV03eHb#=wgMpwcKHF{tzJH7yE<W~_@ip<P^}19%Y#q_;UVqhzZrn8p1X7o1{( zt!%+k4r-%B_Rk4Cf_aPkY@d2lFG{g6ywp1^<YV2PtOL26b{m*U%*6vx$q!_|8Psiw z-v0Tmk3PH_TD}$5*a(4r%oPbPoQqYOSXogp)pBUPnmPKdN&oHtoqksi`{w}WoS3{I z`>Z=pPsAb~0Pa1rcH?j3ZhRT)z<XnE>|2l-7f+alj%^ZC3|SyKsH-u^8=-CYoO<Q8 zry|a{7sC^)cSS?>hu~7gDlz#$t0ZR%sSqY0et>fE7=cH|Txt{eZcz4!@~&%V{2{Kw za4{Rn6W!-syzd3aNstrkz_BSK${`MlgX18<*CI~d-hcS;{I<L2u0}e&l{*Abh8#1y zw`!2VRu;?)!I|R(d*Fpb^=CM@=84tE;Ds7nm#x^!%Si$5v$88Ol_m}jELM3od6q8Y zMs6*jRU$4nATDj^omZInCzyD<DMv1SFf+#m_E}!-87OCa#F_R9z&jYd^#|K~_U&un zzL-ICJ@7`z<kEAW`3Qsctdctf@cH&cwB!Ev<;zzhMy%&{NC+Ssn4@AQnZmqKoY}W! zjtOH+frn$e%)uFdkeBIBZ`iorjSsKHobTtHF@IjrokUqsCPq4!5n<4tRpLlZyn#E+ z&;I?;f}RH!tpb=EV3|8<nR4%kQ_~);ntq0vtkhP#&PKsylqbr5p;v+f`WQhh_W@TM z$L~DLGh-%r<9Bwg{^bVu<jGZbb1(OXa%-VyeUyb2%jHR!0IiZ+&bf<j3V0^LM7^{i z^3WrzS_I4~JGsZz2&TG?2@q2Be*8NYo?P$GXgxqPK`58CUE7BM*A|){gHjFfsUk>< zdZ2J^bemAXgHgxX*}e9K4e^fXD$HpC=ft^jj-0EV^W|rK*$9)NRf>xR8!%%jFgu=J zwhF+(&4zX$F=#?2`vEUJ!xB0myk7kK90Xn519ppGZI5l+5`hmm=rd(PO$%KnGVQ}q zxcwc|Zr*-+?Yi}`o{m*WS{Ar*cNQ{oKX~Fg!I!u7Wg||^PA~<gwM^8P3;N#Nv7+aJ zCmX^Orf!3qdYju7Aj~5W;%7+Ri+jLsfvoMYO}5Pv!K|><eE1pMC6bjrDB52?y=2Xn z?#9L!TsOYL=Qgh#@G^-LwG;M(c?0-P@Z5ppk;l8Xy!K4wCyzBWhw8UNkh{3*0ayDu z0QR;`a(RpOp<EvFU7#iVfg1I9C-qu(1rf<syw$1dgS8#!HNKYb{>vXvE?e<Z@9w`m zJJ{X9Cspv3=s$wOeb0{d$^<WyIB_cgX0oiIctP}fxTkO17&CXc@7yEjjvRUB^oC7a zI_54s&_3<vHVAtV!X5!b_`1B7zND53ngVA&n`Jn;$%StuaH>b5^|J>%7cYx;ZP|7p zcD!Zta9{rljO77)mEjgJt&Z8|KZ7~eO8`8_`9HES@BDkB|EK@}002ovPDHLkV1m$5 BL$Ck< literal 0 HcmV?d00001 -- GitLab