From 035fcd2615b226e58e62ce120772477ae29b20af Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bj=C3=B6rn=20Kaiser?= Date: Wed, 3 Feb 2016 21:21:30 +0000 Subject: [PATCH 01/11] Added test for proper handling of trying to call a non-existing Cloud Function --- spec/ParseAPI.spec.js | 11 +++++++++++ 1 file changed, 11 insertions(+) diff --git a/spec/ParseAPI.spec.js b/spec/ParseAPI.spec.js index 662e28ed..c75d2ce3 100644 --- a/spec/ParseAPI.spec.js +++ b/spec/ParseAPI.spec.js @@ -648,4 +648,15 @@ describe('miscellaneous', function() { }); }); + it('fails on invalid function', done => { + Parse.Cloud.run('somethingThatDoesDefinitelyNotExist').then((s) => { + fail('This should have never suceeded'); + done(); + }, (e) => { + expect(e.code).toEqual(Parse.Error.SCRIPT_FAILED); + expect(e.message).toEqual('Invalid function.'); + done(); + }); + }); + }); From ce1de0a5efb63011cf57b075ae235f587fe8fff9 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bj=C3=B6rn=20Kaiser?= Date: Wed, 17 Feb 2016 20:43:09 +0000 Subject: [PATCH 02/11] Cloud Function validation now uses the complete request instead of just the request parameters --- spec/ParseAPI.spec.js | 10 +++++----- src/functions.js | 17 ++++++++--------- 2 files changed, 13 insertions(+), 14 deletions(-) diff --git a/spec/ParseAPI.spec.js b/spec/ParseAPI.spec.js index 893b1210..52c17fbf 100644 --- a/spec/ParseAPI.spec.js +++ b/spec/ParseAPI.spec.js @@ -587,7 +587,7 @@ describe('miscellaneous', function() { done(); }); }); - + it('test cloud function query parameters', (done) => { Parse.Cloud.define('echoParams', (req, res) => { res.success(req.params); @@ -621,8 +621,8 @@ describe('miscellaneous', function() { // Register a function with validation Parse.Cloud.define('functionWithParameterValidation', (req, res) => { res.success('works'); - }, (params) => { - return params.success === 100; + }, (request) => { + return request.params.success === 100; }); Parse.Cloud.run('functionWithParameterValidation', {"success":100}).then((s) => { @@ -638,8 +638,8 @@ describe('miscellaneous', function() { // Register a function with validation Parse.Cloud.define('functionWithParameterValidationFailure', (req, res) => { res.success('noway'); - }, (params) => { - return params.success === 100; + }, (request) => { + return request.params.success === 100; }); Parse.Cloud.run('functionWithParameterValidationFailure', {"success":500}).then((s) => { diff --git a/src/functions.js b/src/functions.js index c787a814..8e88aa03 100644 --- a/src/functions.js +++ b/src/functions.js @@ -10,10 +10,15 @@ var router = new PromiseRouter(); function handleCloudFunction(req) { if (Parse.Cloud.Functions[req.params.functionName]) { - const params = Object.assign({}, req.body, req.query); - + var request = { + params: Object.assign({}, req.body, req.query), + master: req.auth && req.auth.isMaster, + user: req.auth && req.auth.user, + installationId: req.info.installationId + }; + if (Parse.Cloud.Validators[req.params.functionName]) { - var result = Parse.Cloud.Validators[req.params.functionName](params); + var result = Parse.Cloud.Validators[req.params.functionName](request); if (!result) { throw new Parse.Error(Parse.Error.SCRIPT_FAILED, 'Validation failed.'); } @@ -21,12 +26,6 @@ function handleCloudFunction(req) { return new Promise(function (resolve, reject) { var response = createResponseObject(resolve, reject); - var request = { - params: params, - master: req.auth && req.auth.isMaster, - user: req.auth && req.auth.user, - installationId: req.info.installationId - }; Parse.Cloud.Functions[req.params.functionName](request, response); }); } else { From 7cc4ef95c08ceac7fbf6575ee7308ab403f2a9a3 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?H=C3=A9ctor=20Ramos?= Date: Wed, 17 Feb 2016 14:27:32 -0800 Subject: [PATCH 03/11] Use new Parse Server logo. --- .github/parse-server-logo.png | Bin 0 -> 7573 bytes README.md | 11 +++-------- 2 files changed, 3 insertions(+), 8 deletions(-) create mode 100644 .github/parse-server-logo.png diff --git a/.github/parse-server-logo.png b/.github/parse-server-logo.png new file mode 100644 index 0000000000000000000000000000000000000000..578e5e1f3f5ba2044808b8f30cdd9c0eb33ced91 GIT binary patch literal 7573 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 literal 0 HcmV?d00001 diff --git a/README.md b/README.md index 19bf9e62..475f88fd 100644 --- a/README.md +++ b/README.md @@ -1,6 +1,6 @@ -Parse logo +![Parse Server logo](.github/parse-server-logo.png?raw=true) -## Parse Server +## Open Source Parse API Server [![Build Status](https://img.shields.io/travis/ParsePlatform/parse-server/master.svg?style=flat)](https://travis-ci.org/ParsePlatform/parse-server) [![Coverage Status](https://img.shields.io/codecov/c/github/ParsePlatform/parse-server/master.svg)](https://codecov.io/github/ParsePlatform/parse-server?branch=master) @@ -18,14 +18,9 @@ Documentation for Parse Server is available in the [wiki](https://github.com/Par If you're interested in developing for Parse Server, the [Development guide](https://github.com/ParsePlatform/parse-server/wiki/Development-Guide) will help you get set up. -### Example Project - -Check out the [parse-server-example project](https://github.com/ParsePlatform/parse-server-example) repository for an example of a Node.js application that uses the parse-server module on Express. - ### Migration Guide -Migrate your existing Parse apps to your own Parse Server. The hosted version of Parse will be fully retired on January 28th, 2017. If you are planning to migrate an app, you need to begin work as soon as possible. Learn more in the [Migration guide](https://github.com/ParsePlatform/parse-server/wiki/Migrating-an-Existing-Parse-App). - +The hosted version of Parse will be fully retired on January 28th, 2017. If you are planning to migrate an app, you need to begin work as soon as possible. Learn more in the [Migration guide](https://github.com/ParsePlatform/parse-server/wiki/Migrating-an-Existing-Parse-App). --- From d668bd984cc4508a47b0838d1a3084b52660c281 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?H=C3=A9ctor=20Ramos?= Date: Wed, 17 Feb 2016 17:25:24 -0800 Subject: [PATCH 04/11] Remove padding around logo --- .github/parse-server-logo.png | Bin 7573 -> 11622 bytes README.md | 2 -- 2 files changed, 2 deletions(-) diff --git a/.github/parse-server-logo.png b/.github/parse-server-logo.png index 578e5e1f3f5ba2044808b8f30cdd9c0eb33ced91..80fbc24344f56e09a15f288b60b19d2467602393 100644 GIT binary patch literal 11622 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 Tx4Y2aM6`m8s&u8KX~_Qqk&1Zm literal 7573 zcmb7}XH-+$+V2Ah0#Z~2lxo?EbWxh2qsXR9OCVI~y(pmuf+C`X;+7^LARslNOEGjb zbm={WNDVci2munv%f9!VJNAe7jB)OVwboq!@y!48j5*g>bFTG!_ta3Eot2Lj006M- z{Q1}f0HC)xYi~0%otTKft%qhgb z$5|Nw5LD24tYH>Bvxzfu`@=F@cze}_W6rc>r+p(ywvjdRLfZ7!nf5Q z+ODNpO?!x*#@xTPu=i4Z+@`d@n^*c)%g!8vtQtxt+AsmsgH$h90s!YE006oR0QENd z|8rdYm-HXxv&er({~h`7=>IJLm%jf<_uu*cvx>hv|HI(#l>aX5AM*e6MTt_Z;HTgk zYL{WlRu^q1ktS6Wmy=eN1t%YgOzK_%oI{mFmt4tqsVYz-1yXsyqHdVFeuhbt5{Ae~ zxjOX}NFIlSUheuZQ{bX(qx%-HhhefHpB_Lz@ib~&S#x|;5?hOaDH&!GAD`9n=3?o= zu5ipzYR7m!-RABFGel3D^DKoo8wddllOIa-OFgg~q6g6FRbKvjHU)gfl{4?`_Z_|n z%P^LGquT)tB_?vWb9Rg?H>ST>$GlZrNrF-2X{Za@0swRo!2_Zf;eSjrkFYVa8y*M166XGrpFkM*nVe?05Ds0q`W_KN%gz!L>C!(+nU7^&=$b(uiJ6&hWp%)>h_J{)GJ1u$o{Dvfgze)9ITEmF zfIU5(IJ!crW}d^t=$XegY>1FUDz~aHU$wAL@lE5Qk4T8 z=0chr^46g6XcUrKvvs7~4$8jeI{h=Lb9!blrEkhafj06;{JMRap}<<6>gx&Qn4}^y zbT_uj^t}Il{hXbHPhjM?DiaG}$pa=a%?Sk+SZ92hdBeePCvbT_OnPrHV9l%@Fc^P+ zC##rZwy*UGoDg63+WkB#Km&g;fxPnOp!7A8nH9kdhCAvH>dm)F*d@w;tN#4uBn-1*Xhafrk2o#I$u4ueqU|n*tqFy&~s% zF-FYC>-z}Q$y9N`Bs%V4WpBSosQ!$uWFfv2!&Z0OT9ACT9JC)%{^c1TohP&1f!IsO zbf^*q#_DdD5{+u$3dijb!ufEYOtP3;xt`cPB(ppD3Z}*&mB+%;MEqQ}) zxtSLm;bjid74F&)cG^~VxT>CVd?epO=-YG|Kth#_2VE`6oXux%lFB4qoG4q}PWkRO zo!a+2C-}%aGE#_sTb>csS7@a0gttGh10xczmg&79s(S~i`HQ2ZLPt67Zy*sm?zz3$ufI5nJ!*NXoC> zEG3^$xHv~!+*QTh+hl<}#s5MxJF-T4!$T`2P+m8$r`r6M4htR(pu0LwKeYCWo9=Rmy+=z~8k@wJenj=(`P3i?2S`X*tP)QTXe9%@$ls zANlT*8`jqzlywSk;r3L%^{b*r>kV*o&U72a`M{j@*_(y0rq(_;ktcd7<%53Bpt(Nw zv7IcX^>(+^{@B5K;AB5*`7?s~o$iRwnK2tTq_DC-WGrgvHW!b0BHnaKk}cbg?Ts&2 ziV5Nyh6H|qoc(r^YT=E1UtwnCAEx)PA=?AW$I_V?QR%bL#7_3NB zwxk?so%+I)Xw*I!#psAaZHhwlKZBLySSdRar#5A`OX{dkDs+a>KE^9Z{zS3SkuRjl zzGASp@nD5#6S%a7T(-F#HGU^gwcjxeHt1RC2yb8&lzA9n{OeQDrC!}R0ucQInF^o& zIZtj0^CYB09vn_e(!};1&3W_8&D&<4>h;UtMZV>Ce~E2N3eVpZ({uiqwyf12z2M*D zzv^&tC%<7;t0eUw#jm&C6`rI6< z8gaB#-`3`N#L4;tXtPHLn4itk8R}QpzB~7Bml%3;*;Vj0%JOyVo;-z@(4Xi3(nyK< zmKF{UCTGbxX?o4_IFShfC5ONTBy2f;9;T6<%>`8hxRS z*zM*t75OL1@u@|H2RE(bBewc;YwhE6<(!gUI&v*)xm@F1)9NeF)ry&4FOqAinGEu> zvE4*Q6w1~Sy}XgTOAGm^e31hWG4cEWe_NmHPJ)xn(%|~u#&sAB!Lv`T{Endb+tG=8 zPN`VD2*(yK)ABF|T6n!5-toEY&+&b3URF%a36CeA!NWz9_OMY!lZBTR`+FO&5iIvo zQPtN!7u`t1y~O_;#0#(Prpi-U#lkr8wo5^%!X@F+vKrPt>}v*UlQ=he-|(P5&9mglus3l6u>LAY-fW$;6+)$YNDA~F&f2gNiE_M$=EeEW*oDk z6HUTs3^U$))q~Qk?^F(Ji%t`_o&}HK;aN1FV-!ND%Zg}L98@qG@>ozQZjB|DewR|- z!Qm?~m=_^QVwq7Q!$?ND9ls=y4{Z!y_jlb^;0W5n8%OV|-7RJC+{-TWITYfaHQR(L zt{h6VsEIHxs}-}|Tf$_Qz(u5+RIUA#eP)|&-FuTHIPWg}cqi&!3&0FabTRf2!YTVb2eWm^Zf*R2Z9=;Dqu`xrh zcA%OQaG^3O)DycpujZqW9+?bq!<6hbVbegFkRfxvJiTfnFt3Ez($lvJWCsx)fH8-L z$>9f4=)E_XCNQr`cD>=P00}FBs6A9L$~FloBhC5;IeD=OMU=lwGm~;vNreOJD4CFS z&9m7?Z=+Ys(S+rg&fxxDsp>w8A*h7=OMYrWi)%N0?z!?X9=|Ok)>tgZny{k++{e#965#5~XDuD5OW@xP?9zD5tvaM6HAk{0vbSW5XoqYrC}!?q6- z;4hv&=kTqOayCAy54pT|vt!GxiwXWoh7rL&2Tk;#|F)VT&QG1D?5cAc>Uv8lR|wfx z9%v+X6&LgSo-Knbdy@5QZ*FSZ0q49YJoJ}S(41-PQK0D^>)&Q`iF|c!Hcmg8y1^VO zlIvkf#P+$~R+RX+XW=1A2Y$Eh4Zb5;9W^<)O_%-519?y7`45f}U_r?kV-OSVbJVxE z4BO8$3DctMF4NtQyNCCa)(MthyV2O26gg7s}aMm z(Gd~&CmfsKiFr&;E-KUKVP`1cz7NRJyZL5`*2ew9AVqh}xh_FiQ(moFX0o|3^z*8F z$Jhu6IDvdd{uR+vMO=7uwq41nn;i;y(w6Z#&8m{qJhP3LV=_wwZ8>{waDiCai&^)s zOJ@V#8_X{c)#$M>KeYF zecO3)$A!Dq$T8?sikg81&*NSQ+K_LIyur~67kSjS(ER%g#GT7)cI^Nc3a!2E{DDVo zOX+Z+(W^ODX*5gmB@vai`ldRs9f>-;M8Rr^*SeixsRVdWJ$yLN9Er*#Zm^u29k+g> zeW`jyvSq`)8H2j+lrr8WUCr{=ojn#Z!%@9Ee^pCkwT0MT-9~$^=X1=6k_<{`x340w z>q_25h?N0`uQUgLc9dtVnCTb)DsM`Wkg|v()y1bKt0iok5k4iiEVjn+RJPvDSj+Oz z5bv1KI9FE0ZYvgz>I*|ReD#6(Szo*xG)ktnlw1~5{^UPndMUHQFTw-c0jd(&BIH7w7lcm8})kZxUl>DNau8MlARWU{sqfU8>IEs zt06C$eWO@>xmIo3ef$SLB6Zx?Mpa7D5T;nclg>-UY4lCbCBKLX7obCMz7$iDOm>+* zl~8jhj-@wLuQ+_ZA{=9OTATd_VP6~fbl+=JLqQXiS%i(aqOn2yG4t9v*DFbi$xpd_ zG~S|RYngV`^kirzKE}ZZ`}^BcD5CeE`U{7EEVU7F-u6Z$2U``wZ&YM3>CeqHs4E7? z#64lUG4uQjK1+9TmUx$@CEvbt(OoBlSsOlc^V|ujXGu5$5&s4E3br=Z+tyk4Ix;?`i<<4=0=707hPLuc~?kXR!4Y49nf((eeyxQJF?&`KPWe; zaVF-gO$F(Z^~Lk#A?-LWIh%$g7p(_IdS9S zaH>xT=|zpcdN)p6K&Y;u04^RVXPrB@IbOl-ZPe_b&@UAx;qnptvELTZz)|j+Dvhy6 z0!0&}J#~M2(EXY8kcj!U*eA+ucp5TlA_PzNU0bWeCtf&cWTgcV4nC8!Q_a&e>K@cr zgypf30xAQ`eXuGP0SUM=^wK4Ikfrl_$J*2H=4f$UNzf&f$pc!UFj+10m{$T)5vbQO zu3tZHcm9jrGM2Oqr~0>?RIt*Pa&SAe*zAQ2(d7Y2y%q)v7pg1m_P$>PcnVDjHJiBb zG|)76;DUwKrOFYE&%JpuA+nLAxx2ES*x5v8@*r_60?6FEjY=v*y+H&>f`6;G&kso-58p?3`+$5_(HvXjY!=R> z93OgK2D@|3??#GP96!v1F35F#XVK#16BvW-CY3OuuC8V?F_4FKUsyjk!6ve{BG;KM zxK-AwU{BQQZq_b5YNoh9+oSxn6cg z*p=KsnF8quYouWf<$Pt)b17C8Xtt2DXhX@L%kX4_`rH1FZ-7Z*!Xlj=>CYE`)Lbd| zNGVb(bcpZk@}frb)|r(TIV+gn;-faQLi)m7AQfRrmScI(p@SjSUN`&GhS%lgv?QMz z>2+-SgX0fw_d}TFs#|OJRbX?#YthUYN|k`&LR(+E}0R zK2#3I%Po6nVHWfEt5MX}_0sGo;pq|H9za;_(Kg8VONab1krfSG#TI(54UdtD~yBvvrE}SH%SR zRoE~iv8R;#wv;Tv*yvzVO2W~i^_~jjA@)+;$bE&@9{4F~lgLxtWm)Ufj=v(A!W9)a z%1ASao}961+{U*UXLJ}Cr`KE7YOI$u4T{^o%_&4A2AsZf@M|r$dT_ntq9W>;c^;Wo-}CM7Vr45*d;fvHsdR z(04%KPHgu_yss@2+GW8J)(qvGg7%iaf`0H6%@-P=T8o_ zg9m-iIp)mnj&{{;{Y&J@(Q@Ld-Eu{} z+B~SE20ibwrT7K?Ee)r9GNyesWHl6;?z?xWKd8jLdTnLHDS^)CgK%vFl#BCT!CRd}I4&0ygpd;g{zr+W}*bP82Q0X?2p24d~KT zdh?VvX|9={8uQk&tnn(_C!!|#_~T1up`eq{AJ8?imLHj=3`p%&QZsfY6_gXK=_eQ6 z=F@}uV|**=Lszk9_}s_VwH8Mxt}9v`KB(3Wr#@>r!NDk*(?3mJv^FlB;$alwv-U>= z!cZyNH*7lQ=rahq+9TMt03^!UT;MZ3R#OoY&RSBHv3Olk`texBS)|L+9`&nuOUM)== z;21ALpSQ9B*qosWR9Vwg$!g+C+OcI)de(&l@AP=e@MKM$jRN4_9pS{)nlI{O10j(l!z$xzO?Weap~l%yg4ON zL$r#Ut4KFL=-!>NpT43fWc2y;%gbVl=~Wanz?9R%UH!;;0-u-cIvpeO4rEfSJMZD4 z=W%t417ON*aj6wb|NaJsfA{;jJhXw?Ds{syV1 zLp0dO-w05=vM37cc7vuX&315~@dDf%l&sFGYdTSLo)r)(|4ym{ioOsn4p_x@nNr$@ zZ4~>yI&p`HThpn(I3o^_cekOb)3Ot6DE@MnY@TxfI;6o6Z~(4s$~~vIAtCgUz-7M=+xWRAGj6BvGGpJLTbK3&QQg^ z{S9&UNVVQhC3*4q+6=<{`p7U7fNmo`IP!vDU8aAUC_VBX zzKlAfG&3`wYtv|oag}nfWgr!MTNagP1jqC4Gx2DYZ1eXzsIaY*g3%+P%)!5F%VjFd zVurY9I%m311AqFs6sXDg37+XPOywtMx*mLP>rir}$(&Fn38kIYB{x2l(lKFpu3RF5 zXm(hH+zz;g{pO?gzd^14$h!VF@b!O5-#^LOzcJZ=4*>taegB#6@6P`)_z%VZQ~noN gwD+XK>6ieoi^@0snosr5I4eL$%kXizrv0n`0R>9jX#fBK diff --git a/README.md b/README.md index 475f88fd..b1cfba40 100644 --- a/README.md +++ b/README.md @@ -1,7 +1,5 @@ ![Parse Server logo](.github/parse-server-logo.png?raw=true) -## Open Source Parse API Server - [![Build Status](https://img.shields.io/travis/ParsePlatform/parse-server/master.svg?style=flat)](https://travis-ci.org/ParsePlatform/parse-server) [![Coverage Status](https://img.shields.io/codecov/c/github/ParsePlatform/parse-server/master.svg)](https://codecov.io/github/ParsePlatform/parse-server?branch=master) [![npm version](https://img.shields.io/npm/v/parse-server.svg?style=flat)](https://www.npmjs.com/package/parse-server) From 61b4468dac7360568f1e292e6e9de46d79f8fb68 Mon Sep 17 00:00:00 2001 From: Drew Gross Date: Wed, 17 Feb 2016 19:00:17 -0800 Subject: [PATCH 05/11] Implement DELETE /schemas/:className --- spec/schemas.spec.js | 101 +++++++++++++++++++++++++++++++++++++++++++ src/Schema.js | 3 +- src/schemas.js | 85 ++++++++++++++++++++++++++++++++++++ 3 files changed, 188 insertions(+), 1 deletion(-) diff --git a/spec/schemas.spec.js b/spec/schemas.spec.js index fd136df4..1a6a3069 100644 --- a/spec/schemas.spec.js +++ b/spec/schemas.spec.js @@ -1,6 +1,9 @@ var Parse = require('parse/node').Parse; var request = require('request'); var dd = require('deep-diff'); +var Config = require('../src/Config'); + +var config = new Config('test'); var hasAllPODobject = () => { var obj = new Parse.Object('HasAllPOD'); @@ -633,4 +636,102 @@ describe('schemas', () => { }); }); }); + + it('requires the master key to delete schemas', done => { + request.del({ + url: 'http://localhost:8378/1/schemas/DoesntMatter', + headers: noAuthHeaders, + json: true, + }, (error, response, body) => { + expect(response.statusCode).toEqual(403); + expect(body.error).toEqual('unauthorized'); + done(); + }); + }); + + it('refuses to delete non-empty collection', done => { + var obj = hasAllPODobject(); + obj.save() + .then(() => { + request.del({ + url: 'http://localhost:8378/1/schemas/HasAllPOD', + headers: masterKeyHeaders, + json: true, + }, (error, response, body) => { + expect(response.statusCode).toEqual(400); + expect(body.code).toEqual(255); + expect(body.error).toEqual('class HasAllPOD not empty, contains 1 objects, cannot drop schema'); + done(); + }); + }); + }); + + it('fails when deleting collections with invalid class names', done => { + request.del({ + url: 'http://localhost:8378/1/schemas/_GlobalConfig', + headers: masterKeyHeaders, + json: true, + }, (error, response, body) => { + expect(response.statusCode).toEqual(400); + expect(body.code).toEqual(Parse.Error.INVALID_CLASS_NAME); + expect(body.error).toEqual('Invalid classname: _GlobalConfig, classnames can only have alphanumeric characters and _, and must start with an alpha character '); + done(); + }) + }); + + it('does not fail when deleting nonexistant collections', done => { + request.del({ + url: 'http://localhost:8378/1/schemas/Missing', + headers: masterKeyHeaders, + json: true, + }, (error, response, body) => { + expect(response.statusCode).toEqual(200); + expect(body).toEqual({}); + done(); + }); + }); + + it('deletes collections including join tables', done => { + var obj = new Parse.Object('MyClass'); + obj.set('data', 'data'); + obj.save() + .then(() => { + var obj2 = new Parse.Object('MyOtherClass'); + var relation = obj2.relation('aRelation'); + relation.add(obj); + return obj2.save(); + }) + .then(obj2 => obj2.destroy()) + .then(() => { + request.del({ + url: 'http://localhost:8378/1/schemas/MyOtherClass', + headers: masterKeyHeaders, + json: true, + }, (error, response, body) => { + expect(response.statusCode).toEqual(200); + expect(response.body).toEqual({}); + config.database.db.collection('test__Join:aRelation:MyOtherClass', { strict: true }, (err, coll) => { + //Expect Join table to be gone + expect(err).not.toEqual(null); + config.database.db.collection('test_MyOtherClass', { strict: true }, (err, coll) => { + // Expect data table to be gone + expect(err).not.toEqual(null); + request.get({ + url: 'http://localhost:8378/1/schemas/MyOtherClass', + headers: masterKeyHeaders, + json: true, + }, (error, response, body) => { + //Expect _SCHEMA entry to be gone. + expect(response.statusCode).toEqual(400); + expect(body.code).toEqual(Parse.Error.INVALID_CLASS_NAME); + expect(body.error).toEqual('class MyOtherClass does not exist'); + done(); + }); + }); + }); + }); + }, error => { + fail(error); + }); + }); }); diff --git a/src/Schema.js b/src/Schema.js index a07018bf..0d601449 100644 --- a/src/Schema.js +++ b/src/Schema.js @@ -521,7 +521,7 @@ Schema.prototype.deleteField = function(fieldName, className, database, prefix) }); } - if (schema.data[className][fieldName].startsWith('relation')) { + if (schema.data[className][fieldName].startsWith('relation<')) { //For relations, drop the _Join table return database.dropCollection(prefix + '_Join:' + fieldName + ':' + className) //Save the _SCHEMA object @@ -714,6 +714,7 @@ function getObjectType(obj) { module.exports = { load: load, classNameIsValid: classNameIsValid, + invalidClassNameMessage: invalidClassNameMessage, mongoSchemaFromFieldsAndClassName: mongoSchemaFromFieldsAndClassName, schemaAPITypeToMongoFieldType: schemaAPITypeToMongoFieldType, buildMergedSchemaObject: buildMergedSchemaObject, diff --git a/src/schemas.js b/src/schemas.js index cd8b92ec..9fb191c8 100644 --- a/src/schemas.js +++ b/src/schemas.js @@ -183,10 +183,95 @@ function modifySchema(req) { }); } +// A helper function that removes all join tables for a schema. Returns a promise. +var removeJoinTables = (database, prefix, mongoSchema) => { + return Promise.all(Object.keys(mongoSchema) + .filter(field => mongoSchema[field].startsWith('relation<')) + .map(field => { + var joinCollectionName = prefix + '_Join:' + field + ':' + mongoSchema._id; + return new Promise((resolve, reject) => { + database.dropCollection(joinCollectionName, (err, results) => { + if (err) { + reject(err); + } else { + resolve(); + } + }) + }); + }) + ); +}; + +function deleteSchema(req) { + if (!req.auth.isMaster) { + return masterKeyRequiredResponse(); + } + + if (!Schema.classNameIsValid(req.params.className)) { + return Promise.resolve({ + status: 400, + response: { + code: Parse.Error.INVALID_CLASS_NAME, + error: Schema.invalidClassNameMessage(req.params.className), + } + }); + } + + return req.config.database.collection(req.params.className) + .then(coll => new Promise((resolve, reject) => { + coll.count((err, count) => { + if (err) { + reject(err); + } else if (count > 0) { + resolve({ + status: 400, + response: { + code: 255, + error: 'class ' + req.params.className + ' not empty, contains ' + count + ' objects, cannot drop schema', + } + }); + } else { + coll.drop((err, reply) => { + if (err) { + reject(err); + } else { + // We've dropped the collection now, so delete the item from _SCHEMA + // and clear the _Join collections + req.config.database.collection('_SCHEMA') + .then(coll => new Promise((resolve, reject) => { + coll.findAndRemove({ _id: req.params.className }, [], (err, doc) => { + if (err) { + reject(err); + } else if (doc.value === null) { + //tried to delete non-existant class + resolve({ response: {}}); + } else { + removeJoinTables(req.config.database.db, req.config.database.collectionPrefix, doc.value) + .then(resolve, reject); + } + }); + })) + .then(resolve.bind(undefined, {response: {}}), reject); + } + }); + } + }); + })) + .catch(error => { + if (error.message == 'ns not found') { + // If they try to delete a non-existant class, thats fine, just let them. + return Promise.resolve({ response: {} }); + } else { + return Promise.reject(error); + } + }); +} + router.route('GET', '/schemas', getAllSchemas); router.route('GET', '/schemas/:className', getOneSchema); router.route('POST', '/schemas', createSchema); router.route('POST', '/schemas/:className', createSchema); router.route('PUT', '/schemas/:className', modifySchema); +router.route('DELETE', '/schemas/:className', deleteSchema); module.exports = router; From f271ac9ac10e9873fdec2c14a84499297572b362 Mon Sep 17 00:00:00 2001 From: jim1_lin Date: Thu, 18 Feb 2016 15:36:21 +0800 Subject: [PATCH 06/11] ExportAdapter destroy nothing will cause change password failed Changing password without any session will failed, due to clear no session with response ObjectNotFound. --- src/ExportAdapter.js | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/src/ExportAdapter.js b/src/ExportAdapter.js index 6587df79..abcc862d 100644 --- a/src/ExportAdapter.js +++ b/src/ExportAdapter.js @@ -306,7 +306,8 @@ ExportAdapter.prototype.destroy = function(className, query, options = {}) { return coll.remove(mongoWhere); }).then((resp) => { - if (resp.result.n === 0) { + //Check _Session to avoid changing password failed without any session. + if (resp.result.n === 0 && className !== "_Session") { return Promise.reject( new Parse.Error(Parse.Error.OBJECT_NOT_FOUND, 'Object not found.')); From 4e453925f7ff21a14acd67aff675da3a1fb07be0 Mon Sep 17 00:00:00 2001 From: Florent Vilmart Date: Thu, 18 Feb 2016 13:49:13 -0500 Subject: [PATCH 07/11] Minor readme change --- README.md | 3 +++ 1 file changed, 3 insertions(+) diff --git a/README.md b/README.md index b1cfba40..3ca2801f 100644 --- a/README.md +++ b/README.md @@ -105,6 +105,9 @@ For more informations about custom auth please see the examples: * databaseAdapter (unfinished) - The backing store can be changed by creating an adapter class (see `DatabaseAdapter.js`) * loggerAdapter - The default behavior/transport (File) can be changed by creating an adapter class (see [`LoggerAdapter.js`](https://github.com/ParsePlatform/parse-server/blob/master/src/Adapters/Logger/LoggerAdapter.js)) * enableAnonymousUsers - Defaults to true. Set to false to disable anonymous users. + + + --- ### Usage From a6dc76d52f0aa9b4a22cf61bd8f05a2aeb3d6011 Mon Sep 17 00:00:00 2001 From: Fosco Marotto Date: Thu, 18 Feb 2016 11:28:10 -0800 Subject: [PATCH 08/11] Updating to 2.1.1 --- CHANGELOG.md | 7 +++++++ package.json | 2 +- 2 files changed, 8 insertions(+), 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 46a6feba..e7e83371 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,5 +1,12 @@ ## Parse Server Changelog +### 2.1.1 (2/18/2016) + +* Experimental: Schemas API support for DELETE operations +* Fix: Session token issue fetching Users +* Fix: Facebook auth validation +* Fix: Invalid error when deleting missing session + ### 2.1.0 (2/17/2016) * Feature: Support for additional OAuth providers diff --git a/package.json b/package.json index a39b5ca1..1b7eb3c5 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "parse-server", - "version": "2.1.0", + "version": "2.1.1", "description": "An express module providing a Parse-compatible API server", "main": "lib/index.js", "repository": { From 5f757b0741a5933b83bbb58edc8573fdeb85a967 Mon Sep 17 00:00:00 2001 From: Patrick Pelletier Date: Thu, 18 Feb 2016 17:26:32 -0800 Subject: [PATCH 09/11] Remove default bucket in S3Adapter --- src/Adapters/Files/S3Adapter.js | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/src/Adapters/Files/S3Adapter.js b/src/Adapters/Files/S3Adapter.js index dd6c8d0d..0732fbfe 100644 --- a/src/Adapters/Files/S3Adapter.js +++ b/src/Adapters/Files/S3Adapter.js @@ -6,7 +6,6 @@ import * as AWS from 'aws-sdk'; import { FilesAdapter } from './FilesAdapter'; const DEFAULT_S3_REGION = "us-east-1"; -const DEFAULT_S3_BUCKET = "parse-files"; export class S3Adapter extends FilesAdapter { // Creates an S3 session. @@ -15,8 +14,8 @@ export class S3Adapter extends FilesAdapter { constructor( accessKey, secretKey, + bucket, { region = DEFAULT_S3_REGION, - bucket = DEFAULT_S3_BUCKET, bucketPrefix = '', directAccess = false } = {} ) { From 605f83c36a52cffd5486071986a6709a2fad6a11 Mon Sep 17 00:00:00 2001 From: Fosco Marotto Date: Thu, 18 Feb 2016 17:56:39 -0800 Subject: [PATCH 10/11] Updated readme with deploy buttons --- README.md | 77 ++++++++++++++++++++++++------------------------------- 1 file changed, 33 insertions(+), 44 deletions(-) diff --git a/README.md b/README.md index 3ca2801f..d7ccd2f0 100644 --- a/README.md +++ b/README.md @@ -10,6 +10,38 @@ Parse Server works with the Express web application framework. It can be added t Read the announcement blog post here: http://blog.parse.com/announcements/introducing-parse-server-and-the-database-migration-tool/ +## Getting Started + +[![Deploy](https://www.herokucdn.com/deploy/button.png)](https://heroku.com/deploy?template=https://github.com/parseplatform/parse-server-example) +[![Deploy to Azure](http://azuredeploy.net/deploybutton.png)](https://azuredeploy.net/?repository=https://github.com/parseplatform/parse-server-example) + + +You can create an instance of ParseServer, and mount it on a new or existing Express website: + +```js +var express = require('express'); +var ParseServer = require('parse-server').ParseServer; +var app = express(); + +// Specify the connection string for your mongodb database +// and the location to your Parse cloud code +var api = new ParseServer({ + databaseURI: 'mongodb://localhost:27017/dev', + cloud: '/home/myApp/cloud/main.js', // Provide an absolute path + appId: 'myAppId', + masterKey: '', //Add your master key here. Keep it secret! + fileKey: 'optionalFileKey', + serverURL: 'http://localhost:1337/parse' // Don't forget to change to https if needed +}); + +// Serve the Parse API on the /parse URL prefix +app.use('/parse', api); + +app.listen(1337, function() { + console.log('parse-server-example running on port 1337.'); +}); +``` + ## Documentation Documentation for Parse Server is available in the [wiki](https://github.com/ParsePlatform/parse-server/wiki) for this repository. The [Parse Server guide](https://github.com/ParsePlatform/parse-server/wiki/Parse-Server-Guide) is a good place to get started. @@ -20,10 +52,8 @@ If you're interested in developing for Parse Server, the [Development guide](htt The hosted version of Parse will be fully retired on January 28th, 2017. If you are planning to migrate an app, you need to begin work as soon as possible. Learn more in the [Migration guide](https://github.com/ParsePlatform/parse-server/wiki/Migrating-an-Existing-Parse-App). - --- - #### Basic options: * databaseURI (required) - The connection string for your database, i.e. `mongodb://user:pass@host.com/dbname` @@ -106,48 +136,8 @@ For more informations about custom auth please see the examples: * loggerAdapter - The default behavior/transport (File) can be changed by creating an adapter class (see [`LoggerAdapter.js`](https://github.com/ParsePlatform/parse-server/blob/master/src/Adapters/Logger/LoggerAdapter.js)) * enableAnonymousUsers - Defaults to true. Set to false to disable anonymous users. - - --- -### Usage - -You can create an instance of ParseServer, and mount it on a new or existing Express website: - -```js -var express = require('express'); -var ParseServer = require('parse-server').ParseServer; - -var app = express(); - -var port = process.env.PORT || 1337; - -// Specify the connection string for your mongodb database -// and the location to your Parse cloud code -var api = new ParseServer({ - databaseURI: 'mongodb://localhost:27017/dev', - cloud: '/home/myApp/cloud/main.js', // Provide an absolute path - appId: 'myAppId', - masterKey: '', //Add your master key here. Keep it secret! - fileKey: 'optionalFileKey', - serverURL: 'http://localhost:' + port + '/parse' // Don't forget to change to https if needed -}); - -// Serve the Parse API on the /parse URL prefix -app.use('/parse', api); - -// Hello world -app.get('/', function(req, res) { - res.status(200).send('Express is running here.'); -}); - -app.listen(port, function() { - console.log('parse-server-example running on port ' + port + '.'); -}); - -``` - - #### Standalone usage You can configure the Parse Server with environment variables: @@ -169,8 +159,7 @@ PARSE_SERVER_FACEBOOK_APP_IDS // string of comma separated list ``` - -Alernatively, you can use the `PARSE_SERVER_OPTIONS` environment variable set to the JSON of your configuration (see Usage). +Alternatively, you can use the `PARSE_SERVER_OPTIONS` environment variable set to the JSON of your configuration (see Usage). To start the server, just run `npm start`. From 3f6445d035736fbc708f8f83ffd47d53f4e5f317 Mon Sep 17 00:00:00 2001 From: Joseph Le Grice Date: Fri, 19 Feb 2016 16:41:31 +0000 Subject: [PATCH 11/11] remove _noBody when req.body._noBody == false --- src/middlewares.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/middlewares.js b/src/middlewares.js index 7dcf8889..38d757a9 100644 --- a/src/middlewares.js +++ b/src/middlewares.js @@ -26,7 +26,7 @@ function handleParseHeaders(req, res, next) { restAPIKey: req.get('X-Parse-REST-API-Key') }; - if (req.body && req.body._noBody) { + if (req.body) { // Unity SDK sends a _noBody key which needs to be removed. // Unclear at this point if action needs to be taken. delete req.body._noBody;