ghost 4.39.0 → 4.41.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (59) hide show
  1. package/Gruntfile.js +1 -1
  2. package/core/built/assets/{ghost-dark-498ff8339a89bb68c3f78f59bee4146e.css → ghost-dark-6fbe502f2bb2cde92e15b2f1a9da57a0.css} +1 -1
  3. package/core/built/assets/{ghost.min-77b93478f83b0def6ddc5a4f23ce963e.css → ghost.min-09301e5bd933cf6d24368e98a4d898a9.css} +1 -1
  4. package/core/built/assets/{ghost.min-e6559d901897066aa6a6d4145e3728ed.js → ghost.min-ee1d1d48a30dbd67513f647f360b39e3.js} +220 -225
  5. package/core/built/assets/img/themes/Headline-c5070cf549e797a6a72b87237caa1617.jpg +0 -0
  6. package/core/built/assets/{vendor.min-c39476bced9adb98ee2b292d01c7a8f4.js → vendor.min-9094db77ba3190cb10876f8e42e1d90d.js} +153 -143
  7. package/core/frontend/public/ghost.min.css +1 -1
  8. package/core/frontend/services/theme-engine/middleware/update-local-template-options.js +3 -1
  9. package/core/server/api/canary/identities.js +0 -1
  10. package/core/server/api/canary/members.js +2 -6
  11. package/core/server/api/canary/utils/serializers/input/tiers.js +17 -0
  12. package/core/server/api/canary/utils/serializers/output/actions.js +2 -2
  13. package/core/server/api/canary/utils/serializers/output/authentication.js +3 -3
  14. package/core/server/api/canary/utils/serializers/output/authors.js +3 -3
  15. package/core/server/api/canary/utils/serializers/output/email-posts.js +2 -2
  16. package/core/server/api/canary/utils/serializers/output/emails.js +3 -3
  17. package/core/server/api/canary/utils/serializers/output/images.js +2 -2
  18. package/core/server/api/canary/utils/serializers/output/integrations.js +5 -6
  19. package/core/server/api/canary/utils/serializers/output/labels.js +3 -3
  20. package/core/server/api/canary/utils/serializers/output/mappers/actions.js +7 -0
  21. package/core/server/api/canary/utils/serializers/output/mappers/emails.js +17 -0
  22. package/core/server/api/canary/utils/serializers/output/mappers/images.js +5 -0
  23. package/core/server/api/canary/utils/serializers/output/mappers/index.js +12 -0
  24. package/core/server/api/canary/utils/serializers/output/mappers/integrations.js +13 -0
  25. package/core/server/api/canary/utils/serializers/output/mappers/labels.js +4 -0
  26. package/core/server/api/canary/utils/serializers/output/mappers/pages.js +11 -0
  27. package/core/server/api/canary/utils/serializers/output/mappers/posts.js +101 -0
  28. package/core/server/api/canary/utils/serializers/output/mappers/settings.js +37 -0
  29. package/core/server/api/canary/utils/serializers/output/mappers/tags.js +11 -0
  30. package/core/server/api/canary/utils/serializers/output/mappers/users.js +12 -0
  31. package/core/server/api/canary/utils/serializers/output/members.js +2 -7
  32. package/core/server/api/canary/utils/serializers/output/pages.js +3 -3
  33. package/core/server/api/canary/utils/serializers/output/posts.js +3 -3
  34. package/core/server/api/canary/utils/serializers/output/preview.js +2 -2
  35. package/core/server/api/canary/utils/serializers/output/settings.js +2 -2
  36. package/core/server/api/canary/utils/serializers/output/tags.js +3 -3
  37. package/core/server/api/canary/utils/serializers/output/users.js +3 -3
  38. package/core/server/data/exporter/table-lists.js +1 -0
  39. package/core/server/data/migrations/versions/4.40/2022-03-07-14-37-add-members-cancel-events-table.js +8 -0
  40. package/core/server/data/migrations/versions/4.40/2022-03-15-06-40-add-offers-admin-integration-permission-roles.js +23 -0
  41. package/core/server/data/migrations/versions/4.40/2022-03-15-06-40-add-tiers-admin-integration-permission-roles.js +20 -0
  42. package/core/server/data/schema/fixtures/fixtures.json +3 -1
  43. package/core/server/data/schema/schema.js +6 -0
  44. package/core/server/models/member-cancel-event.js +28 -0
  45. package/core/server/services/mega/template.js +1 -0
  46. package/core/server/services/members/api.js +1 -0
  47. package/core/server/services/oembed.js +2 -1
  48. package/core/server/services/themes/validate.js +3 -3
  49. package/core/server/web/admin/views/default-prod.html +4 -4
  50. package/core/server/web/admin/views/default.html +4 -4
  51. package/core/server/web/api/app.js +3 -3
  52. package/core/server/web/api/canary/admin/middleware.js +2 -0
  53. package/core/shared/config/defaults.json +2 -2
  54. package/core/shared/config/overrides.json +5 -1
  55. package/core/shared/labs.js +3 -3
  56. package/package.json +23 -23
  57. package/yarn.lock +298 -595
  58. package/core/server/api/canary/utils/serializers/output/utils/mapper.js +0 -213
  59. package/core/server/frontend/ghost.min.css +0 -1
@@ -1 +1 @@
1
- /*! normalize.css v3.0.3 | MIT License | github.com/necolas/normalize.css */html{-ms-text-size-adjust:100%;-webkit-text-size-adjust:100%;font-family:sans-serif}body{margin:0}article,aside,details,figcaption,figure,footer,header,hgroup,main,menu,nav,section,summary{display:block}audio,canvas,progress,video{display:inline-block;vertical-align:baseline}audio:not([controls]){display:none;height:0}[hidden],template{display:none}a{background-color:transparent}a:active,a:hover{outline:0}abbr[title]{border-bottom:1px dotted}b,strong{font-weight:700}dfn{font-style:italic}h1{font-size:2em;margin:.67em 0}mark{background:#ff0;color:#000}small{font-size:80%}sub,sup{font-size:75%;line-height:0;position:relative;vertical-align:baseline}sup{top:-.5em}sub{bottom:-.25em}img{border:0}svg:not(:root){overflow:hidden}figure{margin:1em 40px}hr{box-sizing:content-box;height:0}pre{overflow:auto}code,kbd,pre,samp{font-family:monospace,monospace;font-size:1em}button,input,optgroup,select,textarea{color:inherit;font:inherit;margin:0}button{overflow:visible}button,select{text-transform:none}button,html input[type=button],input[type=reset],input[type=submit]{-webkit-appearance:button;cursor:pointer}button[disabled],html input[disabled]{cursor:default}button::-moz-focus-inner,input::-moz-focus-inner{border:0;padding:0}input{line-height:normal}input[type=checkbox],input[type=radio]{box-sizing:border-box;padding:0}input[type=number]::-webkit-inner-spin-button,input[type=number]::-webkit-outer-spin-button{height:auto}input[type=search]{-webkit-appearance:textfield;box-sizing:content-box}input[type=search]::-webkit-search-cancel-button,input[type=search]::-webkit-search-decoration{-webkit-appearance:none}fieldset{border:1px solid silver;margin:0 2px;padding:.35em .625em .75em}legend{border:0;padding:0}textarea{overflow:auto}optgroup{font-weight:700}table{border-collapse:collapse;border-spacing:0}td,th{padding:0}.darkgrey{color:#343f44}.midgrey{color:#738a94}.lightgrey{color:#e5eff5}.blue{color:#3eb0ef}.red{color:#f05230}.orange{color:#fecd35}.green{color:#a4d037}.darkgrey-hover:hover{color:#343f44}.midgrey-hover:hover{color:#738a94}.lightgrey-hover:hover{color:#e5eff5}.blue-hover:hover{color:#3eb0ef}.red-hover:hover{color:#f05230}.orange-hover:hover{color:#fecd35}.green-hover:hover{color:#a4d037}*,:after,:before{box-sizing:border-box}html{-webkit-tap-highlight-color:rgba(0,0,0,0);font-family:-apple-system,BlinkMacSystemFont,Segoe UI,Roboto,Oxygen,Ubuntu,Cantarell,Fira Sans,Droid Sans,Helvetica Neue,sans-serif;font-size:62.5%;letter-spacing:.2px;line-height:1.65;overflow:hidden}body,html{height:100%;width:100%}body{color:#343f44;font-size:1.4rem;overflow:auto;overflow-x:hidden}.gh-view{-ms-flex-positive:1;display:-ms-flexbox;display:flex;-ms-flex-direction:column;flex-direction:column;flex-grow:1}h1,h2{text-rendering:optimizeLegibility;color:#343f44;font-size:2.9rem;line-height:1.15em;margin:0 0 .3em;text-indent:-1px}@media (max-width:500px){h1{font-size:2.4rem}}.gh-input{-webkit-appearance:none;border:1px solid #d6e3eb;border-radius:4px;color:#4b5b62;display:block;font-size:1.6rem;font-weight:300;height:40px;line-height:1em;padding:10px 12px;transition:border-color .15s linear;-webkit-user-select:text;-moz-user-select:text;-ms-user-select:text;user-select:text;width:100%}.gh-input:focus{border-color:#b4cbda;outline:0}.gh-btn{fill:#829aa8;-webkit-font-smoothing:subpixel-antialiased;border:1px solid #d6e3eb;border-radius:5px;color:#829aa8;display:inline-block;outline:none;text-decoration:none!important;text-shadow:0 1px 0 #fff;transition:all .2s ease;-webkit-user-select:none;-moz-user-select:none;-ms-user-select:none;user-select:none}.gh-btn span{border-radius:4px;display:block;font-size:1.3rem;font-weight:400;height:33px;letter-spacing:.2px;line-height:33px;padding:0 12px;text-align:center}.gh-btn:hover{border-color:#b4cbda}.gh-btn-hover-blue:hover{border-color:#3eb0ef;color:#3eb0ef}.gh-btn-blue{fill:#fff;background:linear-gradient(#3da1d6,#2288bf);border:0;box-shadow:0 1px 0 rgba(0,0,0,.12);color:#fff;padding:1px;text-shadow:0 -1px 0 rgba(0,0,0,.1);transition:none!important}.gh-btn-blue span{background:linear-gradient(#4ab6f0,#2fa5e4 60%,#2fa5e4 90%,#38a9e5);box-shadow:inset 0 1px 0 rgba(255,255,255,.1)}.gh-btn-blue:active,.gh-btn-blue:focus{background:#1e78a9}.gh-btn-blue:active span,.gh-btn-blue:focus span{background:#29a0e0;box-shadow:none}.gh-btn-block{display:block;width:100%}.gh-input-icon{display:block;position:relative}.gh-input-icon svg{fill:color(var(--midgrey) l(15%));height:14px;left:10px;position:absolute;top:50%;transform:translateY(-7px);width:auto;z-index:2}.gh-input-icon input{padding-left:35px}.gh-app{display:-ms-flexbox;display:flex;-ms-flex-direction:column;flex-direction:column;height:100%;overflow:hidden}.gh-viewport{max-height:100%;overflow:hidden}.gh-main,.gh-viewport{-ms-flex-positive:1;display:-ms-flexbox;display:flex;flex-grow:1}.gh-main{background:#fff;overflow-y:auto;position:relative}.gh-flow{-ms-flex-positive:1;-ms-flex-direction:column;flex-direction:column;flex-grow:1;min-height:100%;overflow-y:auto}.gh-flow,.gh-flow-head{display:-ms-flexbox;display:flex}.gh-flow-head{-ms-flex-negative:0;-ms-flex-pack:justify;flex-shrink:0;justify-content:space-between;padding-bottom:20px;padding-top:4vh}.gh-flow-content-wrap{-ms-flex-positive:1;-ms-flex-negative:0;-ms-flex-pack:center;flex-grow:1;flex-shrink:0;justify-content:center;margin:0 5%;padding-bottom:8vh}.gh-flow-back,.gh-flow-content-wrap{-ms-flex-align:center;align-items:center;display:-ms-flexbox;display:flex}.gh-flow-back{border:1px solid transparent;border-radius:4px;color:#7d878a;font-weight:100;left:0;margin:0 0 0 3%;padding:2px 9px 2px 5px;position:absolute;top:0;transition:all .3s ease}.gh-flow-back svg{height:12px;line-height:14px;margin-right:4px}.gh-flow-back svg path{stroke:#7d878a;stroke-width:1.2px}.gh-flow-back:hover{border:1px solid #dae1e3}.gh-flow-back-plain{-ms-flex-align:center;align-items:center;color:#7d878a;display:-ms-flexbox;display:flex;font-weight:300;left:0;margin:0 0 0 3%;padding:2px 9px 2px 5px;position:absolute;text-decoration:none;top:0;transition:all .3s ease}.gh-flow-back-plain svg{height:12px;line-height:14px;margin-right:4px}.gh-flow-back-plain svg path{stroke:#7d878a;stroke-width:1.2px}.gh-flow-back-plain:hover{color:#15212a}.gh-flow-nav{-ms-flex:1;flex:1;position:relative}.gh-flow-content{color:#738a94;display:-ms-flexbox;display:flex;-ms-flex-direction:column;flex-direction:column;font-size:1.9rem;font-weight:100;line-height:1.5em;max-width:700px;text-align:center;width:100%}.gh-flow-content .gh-input-icon input{padding-left:35px}.gh-flow-content-unsubscribe{font-weight:300}@media (max-width:500px){.gh-flow-head-unsubscribe{padding-top:2.8vh}.gh-flow-content{font-size:4vw}.gh-flow-content-unsubscribe{font-size:1.8rem;line-height:1.6em}}.gh-flow-content header{margin:0 auto;max-width:520px}.gh-flow-content h1{font-size:4.2rem;font-weight:100}@media (max-width:600px){.gh-flow-content h1{font-size:7vw}}.gh-flow-content .gh-btn{display:block;margin:20px auto 0;max-width:400px}.gh-flow-content .form-group{margin-bottom:2.5rem}.gh-flow-content input{border:1px solid #dae1e3;font-size:1.6rem;font-weight:100;line-height:1.4em;padding:10px}.gh-flow-em{font-weight:500}.gh-signin{background:#f8fbfd;border:1px solid #dae1e3;border-radius:5px;margin:30px auto;max-width:400px;padding:40px;position:relative;text-align:left;width:100%}.gh-signin .form-group{margin-bottom:1.5rem}.gh-signin .gh-btn{margin:0}.error-content{flex-grow:1;justify-content:center;padding:8vw;user-select:text}.error-content,.error-details{align-items:center;display:flex}.error-details{margin-bottom:4rem}.error-ghost{height:115px;margin:15px}@media (max-width:630px){.error-ghost{display:none}}.error-code{color:#c5d2d9;font-size:10vw;font-weight:600;letter-spacing:-.4vw;line-height:.9em;margin:0}.error-description{border:none;color:#54666d;font-size:2.3rem;font-weight:300;line-height:1.3em;margin:0;padding:0}.error-message{align-items:center;display:flex;flex-direction:column;margin:15px}.error-message a{font-size:1.4rem;line-height:1;margin:8px 0}.error-link{background-color:transparent;color:#5ba4e5;text-decoration:none;transition:background .3s,color .3s}.error-stack{background-color:hsla(0,0%,100%,.3);margin:1rem auto;max-width:800px;padding:2rem}.error-stack-list{list-style-type:none;margin:0;padding:0}.error-stack-list li{display:block}.error-stack-list li:before{color:#bbb;content:"\21AA";display:inline-block;font-size:1.2rem;margin-right:.5rem}.error-stack-function{font-weight:700}
1
+ /*! normalize.css v3.0.3 | MIT License | github.com/necolas/normalize.css */html{-ms-text-size-adjust:100%;-webkit-text-size-adjust:100%;font-family:sans-serif}body{margin:0}article,aside,details,figcaption,figure,footer,header,hgroup,main,menu,nav,section,summary{display:block}audio,canvas,progress,video{display:inline-block;vertical-align:baseline}audio:not([controls]){display:none;height:0}[hidden],template{display:none}a{background-color:transparent}a:active,a:hover{outline:0}abbr[title]{border-bottom:1px dotted}b,strong{font-weight:700}dfn{font-style:italic}h1{font-size:2em;margin:.67em 0}mark{background:#ff0;color:#000}small{font-size:80%}sub,sup{font-size:75%;line-height:0;position:relative;vertical-align:baseline}sup{top:-.5em}sub{bottom:-.25em}img{border:0}svg:not(:root){overflow:hidden}figure{margin:1em 40px}hr{box-sizing:content-box;height:0}pre{overflow:auto}code,kbd,pre,samp{font-family:monospace,monospace;font-size:1em}button,input,optgroup,select,textarea{color:inherit;font:inherit;margin:0}button{overflow:visible}button,select{text-transform:none}button,html input[type=button],input[type=reset],input[type=submit]{-webkit-appearance:button;cursor:pointer}button[disabled],html input[disabled]{cursor:default}button::-moz-focus-inner,input::-moz-focus-inner{border:0;padding:0}input{line-height:normal}input[type=checkbox],input[type=radio]{box-sizing:border-box;padding:0}input[type=number]::-webkit-inner-spin-button,input[type=number]::-webkit-outer-spin-button{height:auto}input[type=search]{-webkit-appearance:textfield;box-sizing:content-box}input[type=search]::-webkit-search-cancel-button,input[type=search]::-webkit-search-decoration{-webkit-appearance:none}fieldset{border:1px solid silver;margin:0 2px;padding:.35em .625em .75em}legend{border:0;padding:0}textarea{overflow:auto}optgroup{font-weight:700}table{border-collapse:collapse;border-spacing:0}td,th{padding:0}.black{color:#15171a}.darkgrey{color:#394047}.midgrey{color:#7c8b9a}.lightgrey{color:#ced4d9}.blue{color:#14b8ff}.red{color:#f50b23}.orange{color:#ffb41f}.green{color:#30cf43}.darkgrey-hover:hover{color:#394047}.midgrey-hover:hover{color:#7c8b9a}.lightgrey-hover:hover{color:#ced4d9}.blue-hover:hover{color:#14b8ff}.red-hover:hover{color:#f50b23}.orange-hover:hover{color:#ffb41f}.green-hover:hover{color:#30cf43}*,:after,:before{box-sizing:border-box}html{-webkit-tap-highlight-color:rgba(0,0,0,0);font-family:-apple-system,BlinkMacSystemFont,Segoe UI,Roboto,Oxygen,Ubuntu,Cantarell,Fira Sans,Droid Sans,Helvetica Neue,sans-serif;font-size:62.5%;letter-spacing:.2px;line-height:1.65;overflow:hidden}body,html{height:100%;width:100%}body{color:#343f44;font-size:1.4rem;overflow:auto;overflow-x:hidden}.gh-view{-ms-flex-positive:1;display:-ms-flexbox;display:flex;-ms-flex-direction:column;flex-direction:column;flex-grow:1}h1,h2{text-rendering:optimizeLegibility;color:#343f44;font-size:2.9rem;line-height:1.15em;margin:0 0 .3em;text-indent:-1px}@media (max-width:500px){h1{font-size:2.4rem}}.gh-input{-webkit-appearance:none;border:1px solid #d6e3eb;border-radius:4px;color:#4b5b62;display:block;font-size:1.6rem;font-weight:300;height:40px;line-height:1em;padding:10px 12px;transition:border-color .15s linear;-webkit-user-select:text;-moz-user-select:text;-ms-user-select:text;user-select:text;width:100%}.gh-input:focus{border-color:#b4cbda;outline:0}.gh-btn{fill:#829aa8;-webkit-font-smoothing:subpixel-antialiased;border:1px solid #d6e3eb;border-radius:5px;color:#829aa8;display:inline-block;outline:none;text-decoration:none!important;transition:all .2s ease;-webkit-user-select:none;-moz-user-select:none;-ms-user-select:none;user-select:none}.gh-btn span{border-radius:4px;display:block;font-size:1.3rem;font-weight:400;height:33px;letter-spacing:.2px;line-height:33px;padding:0 12px;text-align:center}.gh-btn:hover{border-color:#b4cbda}.gh-btn-hover-blue:hover{border-color:#3eb0ef;color:#3eb0ef}.gh-btn-blue{fill:#fff;background:linear-gradient(#3da1d6,#2288bf);border:0;box-shadow:0 1px 0 rgba(0,0,0,.12);color:#fff;padding:1px;text-shadow:0 -1px 0 rgba(0,0,0,.1);transition:none!important}.gh-btn-blue span{background:linear-gradient(#4ab6f0,#2fa5e4 60%,#2fa5e4 90%,#38a9e5);box-shadow:inset 0 1px 0 hsla(0,0%,100%,.1)}.gh-btn-blue:active,.gh-btn-blue:focus{background:#1e78a9}.gh-btn-blue:active span,.gh-btn-blue:focus span{background:#29a0e0;box-shadow:none}.gh-btn-block{display:block;width:100%}.gh-input-icon{display:block;position:relative}.gh-input-icon svg{fill:color(var(--midgrey) l(15%));height:14px;left:10px;position:absolute;top:50%;transform:translateY(-7px);width:auto;z-index:2}.gh-input-icon input{padding-left:35px}.gh-app{display:-ms-flexbox;display:flex;-ms-flex-direction:column;flex-direction:column;height:100%;overflow:hidden}.gh-viewport{max-height:100%;overflow:hidden}.gh-main,.gh-viewport{-ms-flex-positive:1;display:-ms-flexbox;display:flex;flex-grow:1}.gh-main{background:#fff;overflow-y:auto;position:relative}.gh-flow{background:linear-gradient(315deg,#efefef,#fff);min-height:100%;overflow-y:auto}.gh-flow,.gh-flow-content-wrap{display:flex;flex-direction:column;flex-grow:1}.gh-flow-content-wrap{align-items:center;flex-shrink:0;justify-content:center;margin:0 24px;padding-bottom:8vh}.gh-flow-content-wrap .site-icon{border-radius:3px;height:70px;width:70px}.gh-flow-content{background:#fff;border-radius:3px;box-shadow:0 2.8px 2.2px rgba(0,0,0,.02),0 6.7px 5.3px rgba(0,0,0,.02),0 12.5px 10px rgba(0,0,0,.02),0 22.3px 17.9px rgba(0,0,0,.03),0 41.8px 33.4px rgba(0,0,0,.03),0 100px 80px rgba(0,0,0,.05);color:var(--darkgrey);display:flex;flex-direction:column;font-size:1.9rem;font-weight:300;line-height:1.5em;margin:4rem 0 6rem;max-width:520px;padding:40px;width:100%}.gh-flow-content.unsubscribe{align-items:center;justify-content:center;margin:4rem 0;max-width:560px;min-height:200px;text-align:center}@media (max-width:500px){.gh-flow-content{background:transparent;box-shadow:none;padding:0}}.gh-flow-content header{align-items:center;display:flex;flex-direction:column}.gh-flow-content h1{color:#15171a;font-size:4.1rem;font-weight:700;line-height:1.15em;margin-bottom:24px}.gh-flow-content.unsubscribe h1{font-size:3.2rem}@media (max-width:600px){.gh-flow-content h1,.gh-flow-content.unsubscribe h1{font-size:6vw}}.gh-flow-content.unsubscribe p{color:#394047;font-size:1.8rem;margin:0 0 .4em}@media (max-width:500px){.gh-flow-content.unsubscribe p{font-size:1.6rem;line-height:1.5}}.gh-flow-content .gh-btn{display:block;margin:20px auto 0;max-width:400px}.gh-flow-content .form-group{margin-bottom:2.5rem;position:relative}.gh-flow-content .form-group.error .gh-input{border-color:#f50b23;box-shadow:0 0 0 3px rgba(239,24,24,.15)}.gh-flow-content .main-error{color:#7c8b9a;font-size:1.35rem;font-weight:400;margin:0;position:absolute;right:0;text-align:center;user-select:text}.gh-flow-em{font-weight:500}.unsubscribe-footer{font-size:1.5rem;text-align:center}@media (max-width:500px){.unsubscribe-footer{font-size:1.4rem;line-height:1.4em;padding:0 24px}}.unsubscribe-footer p{color:#7c8b9a;margin:0 0 .4rem}.unsubscribe-footer a{color:#15171a;text-decoration:none}.unsubscribe-footer a:hover{text-decoration:underline}.gh-signin{margin-bottom:1.5rem}.gh-signin .gh-input,.gh-signin .gh-input:-webkit-autofill:first-line{border-radius:8px;font-size:1.8rem;height:54px;padding:12px 16px}.gh-signin .gh-input::placeholder{color:#abb4be;font-weight:400;opacity:1}.gh-signin .gh-input::-webkit-input-placeholder{color:#abb4be;font-weight:400}.gh-signin .gh-input:-ms-input-placeholder{color:#abb4be;font-weight:400}.gh-signin .gh-input::-moz-placeholder{color:#abb4be;font-weight:400;opacity:1}.gh-signin .gh-input:focus{border-color:#30cf43;box-shadow:0 0 0 3px rgba(26,170,96,.15)}.gh-signin .gh-btn{-webkit-font-smoothing:subpixel-antialiased;background:#15171a;border-radius:8px;font-weight:300;height:54px;line-height:54px;margin:0;margin-top:32px;max-width:unset;transition:all .4s ease;width:100%}.gh-signin .gh-btn span{color:#fff;font-size:1.8rem}.error-content{flex-grow:1;justify-content:center;padding:8vw;user-select:text}.error-content,.error-details{align-items:center;display:flex}.error-details{margin-bottom:4rem}.error-ghost{height:115px;margin:15px}@media (max-width:630px){.error-ghost{display:none}}.error-code{color:#c5d2d9;font-size:10vw;font-weight:600;letter-spacing:-.4vw;line-height:.9em;margin:0}.error-description{border:none;color:#54666d;font-size:2.3rem;font-weight:300;line-height:1.3em;margin:0;padding:0}.error-message{align-items:center;display:flex;flex-direction:column;margin:15px}.error-message a{font-size:1.4rem;line-height:1;margin:8px 0}.error-link{background-color:transparent;color:#5ba4e5;text-decoration:none;transition:background .3s,color .3s}.error-stack{background-color:hsla(0,0%,100%,.3);margin:1rem auto;max-width:800px;padding:2rem}.error-stack-list{list-style-type:none;margin:0;padding:0}.error-stack-list li{display:block}.error-stack-list li:before{color:#bbb;content:"\21AA";display:inline-block;font-size:1.2rem;margin-right:.5rem}.error-stack-function{font-weight:700}
@@ -33,7 +33,9 @@ function updateLocalTemplateOptions(req, res, next) {
33
33
  default_payment_card_last4: sub.default_payment_card_last4 || '****'
34
34
  });
35
35
  }),
36
- paid: req.member.status !== 'free'
36
+ paid: req.member.status !== 'free',
37
+ status: req.member.status,
38
+ products: req.member.products
37
39
  } : null;
38
40
 
39
41
  hbs.updateLocalTemplateOptions(res.locals, _.merge({}, localTemplateOptions, {
@@ -25,7 +25,6 @@ const sign = async (claims, options) => {
25
25
 
26
26
  module.exports = {
27
27
  docName: 'identities',
28
- permissions: true,
29
28
  read: {
30
29
  permissions: true,
31
30
  async query(frame) {
@@ -197,9 +197,7 @@ module.exports = {
197
197
  }
198
198
  });
199
199
  }
200
- let model = await membersService.api.members.get({id: frame.options.id}, {
201
- withRelated: ['labels', 'products', 'stripeSubscriptions', 'stripeSubscriptions.customer', 'stripeSubscriptions.stripePrice', 'stripeSubscriptions.stripePrice.stripeProduct']
202
- });
200
+ let model = await membersService.api.memberBREADService.read({id: frame.options.id});
203
201
  if (!model) {
204
202
  throw new errors.NotFoundError({
205
203
  message: tpl(messages.memberNotFound)
@@ -241,9 +239,7 @@ module.exports = {
241
239
  stripe_price_id: frame.data.stripe_price_id
242
240
  }
243
241
  });
244
- let model = await membersService.api.members.get({id: frame.options.id}, {
245
- withRelated: ['labels', 'products', 'stripeSubscriptions', 'stripeSubscriptions.customer', 'stripeSubscriptions.stripePrice', 'stripeSubscriptions.stripePrice.stripeProduct']
246
- });
242
+ let model = await membersService.api.memberBREADService.read({id: frame.options.id});
247
243
  if (!model) {
248
244
  throw new errors.NotFoundError({
249
245
  message: tpl(messages.memberNotFound)
@@ -1,3 +1,13 @@
1
+ const localUtils = require('../../index');
2
+
3
+ const forceActiveFilter = (frame) => {
4
+ if (frame.options.filter) {
5
+ frame.options.filter = `(${frame.options.filter})+active:true`;
6
+ } else {
7
+ frame.options.filter = 'active:true';
8
+ }
9
+ };
10
+
1
11
  module.exports = {
2
12
  all(_apiConfig, frame) {
3
13
  if (!frame.options.withRelated) {
@@ -18,6 +28,13 @@ module.exports = {
18
28
  });
19
29
  },
20
30
 
31
+ browse(_apiConfig, frame) {
32
+ if (localUtils.isContentAPI(frame)) {
33
+ // CASE: content api can only has active tiers
34
+ forceActiveFilter(frame);
35
+ }
36
+ },
37
+
21
38
  add(_apiConfig, frame) {
22
39
  if (frame.data.products) {
23
40
  frame.data = frame.data.products[0];
@@ -1,12 +1,12 @@
1
1
  const debug = require('@tryghost/debug')('api:canary:utils:serializers:output:actions');
2
- const mapper = require('./utils/mapper');
2
+ const mappers = require('./mappers');
3
3
 
4
4
  module.exports = {
5
5
  browse(models, apiConfig, frame) {
6
6
  debug('browse');
7
7
 
8
8
  frame.response = {
9
- actions: models.data.map(model => mapper.mapAction(model, frame)),
9
+ actions: models.data.map(model => mappers.actions(model, frame)),
10
10
  meta: models.meta
11
11
  };
12
12
  }
@@ -1,5 +1,5 @@
1
1
  const tpl = require('@tryghost/tpl');
2
- const mapper = require('./utils/mapper');
2
+ const mappers = require('./mappers');
3
3
  const debug = require('@tryghost/debug')('api:canary:utils:serializers:output:authentication');
4
4
 
5
5
  const messages = {
@@ -12,7 +12,7 @@ module.exports = {
12
12
  setup(user, apiConfig, frame) {
13
13
  frame.response = {
14
14
  users: [
15
- mapper.mapUser(user, {options: {context: {internal: true}}})
15
+ mappers.users(user, {options: {context: {internal: true}}})
16
16
  ]
17
17
  };
18
18
  },
@@ -20,7 +20,7 @@ module.exports = {
20
20
  updateSetup(user, apiConfig, frame) {
21
21
  frame.response = {
22
22
  users: [
23
- mapper.mapUser(user, {options: {context: {internal: true}}})
23
+ mappers.users(user, {options: {context: {internal: true}}})
24
24
  ]
25
25
  };
26
26
  },
@@ -1,12 +1,12 @@
1
1
  const debug = require('@tryghost/debug')('api:canary:utils:serializers:output:authors');
2
- const mapper = require('./utils/mapper');
2
+ const mappers = require('./mappers');
3
3
 
4
4
  module.exports = {
5
5
  browse(models, apiConfig, frame) {
6
6
  debug('browse');
7
7
 
8
8
  frame.response = {
9
- authors: models.data.map(model => mapper.mapUser(model, frame)),
9
+ authors: models.data.map(model => mappers.users(model, frame)),
10
10
  meta: models.meta
11
11
  };
12
12
  },
@@ -15,7 +15,7 @@ module.exports = {
15
15
  debug('read');
16
16
 
17
17
  frame.response = {
18
- authors: [mapper.mapUser(model, frame)]
18
+ authors: [mappers.users(model, frame)]
19
19
  };
20
20
  }
21
21
  };
@@ -1,4 +1,4 @@
1
- const mapper = require('./utils/mapper');
1
+ const mappers = require('./mappers');
2
2
  const gating = require('./utils/post-gating');
3
3
  const membersService = require('../../../../../services/members');
4
4
 
@@ -9,7 +9,7 @@ module.exports = {
9
9
  });
10
10
  const tiers = tiersModels.data && tiersModels.data.map(tierModel => tierModel.toJSON());
11
11
 
12
- const emailPost = await mapper.mapPost(model, frame, {tiers});
12
+ const emailPost = await mappers.posts(model, frame, {tiers});
13
13
  gating.forPost(emailPost, frame);
14
14
 
15
15
  frame.response = {
@@ -1,15 +1,15 @@
1
- const mapper = require('./utils/mapper');
1
+ const mappers = require('./mappers');
2
2
 
3
3
  module.exports = {
4
4
  read(email, apiConfig, frame) {
5
5
  frame.response = {
6
- emails: [mapper.mapEmail(email, frame)]
6
+ emails: [mappers.emails(email, frame)]
7
7
  };
8
8
  },
9
9
 
10
10
  browse(page, apiConfig, frame) {
11
11
  const data = {
12
- emails: page.data.map(model => mapper.mapEmail(model, frame)),
12
+ emails: page.data.map(model => mappers.emails(model, frame)),
13
13
  meta: page.meta
14
14
  };
15
15
 
@@ -1,5 +1,5 @@
1
1
  const debug = require('@tryghost/debug')('api:canary:utils:serializers:output:images');
2
- const mapper = require('./utils/mapper');
2
+ const mappers = require('./mappers');
3
3
 
4
4
  module.exports = {
5
5
  upload(path, apiConfig, frame) {
@@ -7,7 +7,7 @@ module.exports = {
7
7
 
8
8
  return frame.response = {
9
9
  images: [{
10
- url: mapper.mapImage(path),
10
+ url: mappers.images(path),
11
11
  // NOTE: ref field is here to have reference point on the client
12
12
  // for example when substituting existing images in the mobiledoc
13
13
  // this field would serve as an identifier to find images to replace
@@ -1,12 +1,12 @@
1
1
  const debug = require('@tryghost/debug')('api:canary:utils:serializers:output:integrations');
2
- const mapper = require('./utils/mapper');
2
+ const mappers = require('./mappers');
3
3
 
4
4
  module.exports = {
5
5
  browse({data, meta}, apiConfig, frame) {
6
6
  debug('browse');
7
7
 
8
8
  frame.response = {
9
- integrations: data.map(model => mapper.mapIntegration(model, frame)),
9
+ integrations: data.map(model => mappers.integrations(model, frame)),
10
10
  meta
11
11
  };
12
12
  },
@@ -14,22 +14,21 @@ module.exports = {
14
14
  debug('read');
15
15
 
16
16
  frame.response = {
17
- integrations: [mapper.mapIntegration(model, frame)]
17
+ integrations: [mappers.integrations(model, frame)]
18
18
  };
19
19
  },
20
20
  add(model, apiConfig, frame) {
21
21
  debug('add');
22
22
 
23
23
  frame.response = {
24
- integrations: [mapper.mapIntegration(model, frame)]
24
+ integrations: [mappers.integrations(model, frame)]
25
25
  };
26
26
  },
27
27
  edit(model, apiConfig, frame) {
28
28
  debug('edit');
29
29
 
30
30
  frame.response = {
31
- integrations: [mapper.mapIntegration(model, frame)]
31
+ integrations: [mappers.integrations(model, frame)]
32
32
  };
33
33
  }
34
34
  };
35
-
@@ -1,5 +1,5 @@
1
1
  const debug = require('@tryghost/debug')('api:canary:utils:serializers:output:labels');
2
- const mapper = require('./utils/mapper');
2
+ const mappers = require('./mappers');
3
3
 
4
4
  module.exports = {
5
5
  all(models, apiConfig, frame) {
@@ -11,7 +11,7 @@ module.exports = {
11
11
 
12
12
  if (models.meta) {
13
13
  frame.response = {
14
- labels: models.data.map(model => mapper.mapLabel(model, frame)),
14
+ labels: models.data.map(model => mappers.labels(model, frame)),
15
15
  meta: models.meta
16
16
  };
17
17
 
@@ -19,7 +19,7 @@ module.exports = {
19
19
  }
20
20
 
21
21
  frame.response = {
22
- labels: [mapper.mapLabel(models, frame)]
22
+ labels: [mappers.labels(models, frame)]
23
23
  };
24
24
  }
25
25
  };
@@ -0,0 +1,7 @@
1
+ const clean = require('../utils/clean');
2
+
3
+ module.exports = (model, frame) => {
4
+ const attrs = model.toJSON(frame.options);
5
+ clean.action(attrs);
6
+ return attrs;
7
+ };
@@ -0,0 +1,17 @@
1
+ const mega = require('../../../../../../services/mega');
2
+
3
+ module.exports = (model, frame) => {
4
+ const jsonModel = model.toJSON ? model.toJSON(frame.options) : model;
5
+
6
+ // Ensure we're not outputting unwanted replacement strings when viewing email contents
7
+ // TODO: extract this to a utility, it's duplicated in the email-preview API controller
8
+ const replacements = mega.postEmailSerializer.parseReplacements(jsonModel);
9
+ replacements.forEach((replacement) => {
10
+ jsonModel[replacement.format] = jsonModel[replacement.format].replace(
11
+ replacement.match,
12
+ replacement.fallback || ''
13
+ );
14
+ });
15
+
16
+ return jsonModel;
17
+ };
@@ -0,0 +1,5 @@
1
+ const url = require('../utils/url');
2
+
3
+ module.exports = (path) => {
4
+ return url.forImage(path);
5
+ };
@@ -0,0 +1,12 @@
1
+ module.exports = {
2
+ actions: require('./actions'),
3
+ emails: require('./emails'),
4
+ images: require('./images'),
5
+ integrations: require('./integrations'),
6
+ labels: require('./labels'),
7
+ pages: require('./pages'),
8
+ posts: require('./posts'),
9
+ settings: require('./settings'),
10
+ tags: require('./tags'),
11
+ users: require('./users')
12
+ };
@@ -0,0 +1,13 @@
1
+ module.exports = (model, frame) => {
2
+ const jsonModel = model.toJSON ? model.toJSON(frame.options) : model;
3
+
4
+ if (jsonModel.api_keys) {
5
+ jsonModel.api_keys.forEach((key) => {
6
+ if (key.type === 'admin') {
7
+ key.secret = `${key.id}:${key.secret}`;
8
+ }
9
+ });
10
+ }
11
+
12
+ return jsonModel;
13
+ };
@@ -0,0 +1,4 @@
1
+ module.exports = (model, frame) => {
2
+ const jsonModel = model.toJSON ? model.toJSON(frame.options) : model;
3
+ return jsonModel;
4
+ };
@@ -0,0 +1,11 @@
1
+ const mapPost = require('./posts');
2
+
3
+ module.exports = async (model, frame, options) => {
4
+ const jsonModel = await mapPost(model, frame, options);
5
+
6
+ delete jsonModel.email_subject;
7
+ delete jsonModel.email_recipient_filter;
8
+ delete jsonModel.email_only;
9
+
10
+ return jsonModel;
11
+ };
@@ -0,0 +1,101 @@
1
+ const _ = require('lodash');
2
+
3
+ const mapTag = require('./tags');
4
+ const mapUser = require('./users');
5
+ const mapEmail = require('./emails');
6
+
7
+ const clean = require('../utils/clean');
8
+ const date = require('../utils/date');
9
+ const extraAttrs = require('../utils/extra-attrs');
10
+ const gating = require('../utils/post-gating');
11
+ const url = require('../utils/url');
12
+
13
+ const utils = require('../../../index');
14
+
15
+ const postsMetaSchema = require('../../../../../../data/schema').tables.posts_meta;
16
+ const labsService = require('../../../../../../../shared/labs');
17
+
18
+ const getPostServiceInstance = require('../../../../../../services/posts/posts-service');
19
+ const postsService = getPostServiceInstance('canary');
20
+
21
+ module.exports = async (model, frame, options = {}) => {
22
+ const {tiers: tiersData} = options || {};
23
+ const extendedOptions = Object.assign(_.cloneDeep(frame.options), {
24
+ extraProperties: ['canonical_url']
25
+ });
26
+
27
+ const jsonModel = model.toJSON(extendedOptions);
28
+
29
+ url.forPost(model.id, jsonModel, frame);
30
+
31
+ extraAttrs.forPost(frame, model, jsonModel);
32
+
33
+ // Attach tiers to custom nql visibility filter
34
+ if (labsService.isSet('multipleProducts')
35
+ && jsonModel.visibility
36
+ ) {
37
+ if (['members', 'public'].includes(jsonModel.visibility) && jsonModel.tiers) {
38
+ jsonModel.tiers = tiersData || [];
39
+ }
40
+
41
+ if (jsonModel.visibility === 'paid' && jsonModel.tiers) {
42
+ jsonModel.tiers = tiersData ? tiersData.filter(t => t.type === 'paid') : [];
43
+ }
44
+
45
+ if (!['members', 'public', 'paid', 'tiers'].includes(jsonModel.visibility)) {
46
+ const tiers = await postsService.getProductsFromVisibilityFilter(jsonModel.visibility);
47
+
48
+ jsonModel.visibility = 'tiers';
49
+ jsonModel.tiers = tiers;
50
+ }
51
+ }
52
+
53
+ if (utils.isContentAPI(frame)) {
54
+ // Content api v2 still expects page prop
55
+ if (jsonModel.type === 'page') {
56
+ jsonModel.page = true;
57
+ }
58
+ date.forPost(jsonModel);
59
+ gating.forPost(jsonModel, frame);
60
+ }
61
+
62
+ // Transforms post/page metadata to flat structure
63
+ let metaAttrs = _.keys(_.omit(postsMetaSchema, ['id', 'post_id']));
64
+ _(metaAttrs).filter((k) => {
65
+ return (!frame.options.columns || (frame.options.columns && frame.options.columns.includes(k)));
66
+ }).each((attr) => {
67
+ // NOTE: the default of `email_only` is `false` which is why we default to `false` instead of `null`
68
+ // The undefined value is possible because `posts_meta` table is lazily created only one of the
69
+ // values is assigned.
70
+ const defaultValue = (attr === 'email_only') ? false : null;
71
+ jsonModel[attr] = _.get(jsonModel.posts_meta, attr) || defaultValue;
72
+ });
73
+ delete jsonModel.posts_meta;
74
+
75
+ clean.post(jsonModel, frame);
76
+
77
+ if (frame.options && frame.options.withRelated) {
78
+ frame.options.withRelated.forEach((relation) => {
79
+ // @NOTE: this block also decorates primary_tag/primary_author objects as they
80
+ // are being passed by reference in tags/authors. Might be refactored into more explicit call
81
+ // in the future, but is good enough for current use-case
82
+ if (relation === 'tags' && jsonModel.tags) {
83
+ jsonModel.tags = jsonModel.tags.map(tag => mapTag(tag, frame));
84
+ }
85
+
86
+ if (relation === 'authors' && jsonModel.authors) {
87
+ jsonModel.authors = jsonModel.authors.map(author => mapUser(author, frame));
88
+ }
89
+
90
+ if (relation === 'email' && jsonModel.email) {
91
+ jsonModel.email = mapEmail(jsonModel.email, frame);
92
+ }
93
+
94
+ if (relation === 'email' && _.isEmpty(jsonModel.email)) {
95
+ jsonModel.email = null;
96
+ }
97
+ });
98
+ }
99
+
100
+ return jsonModel;
101
+ };
@@ -0,0 +1,37 @@
1
+ const _ = require('lodash');
2
+
3
+ const extraAttrs = require('../utils/extra-attrs');
4
+ const url = require('../utils/url');
5
+
6
+ module.exports = (attrs, frame) => {
7
+ url.forSettings(attrs);
8
+ extraAttrs.forSettings(attrs, frame);
9
+
10
+ // NOTE: The cleanup of deprecated ghost_head/ghost_foot has to happen here
11
+ // because codeinjection_head/codeinjection_foot are assigned on a previous
12
+ // `forSettings` step. This logic can be rewritten once we get rid of deprecated
13
+ // fields completely.
14
+ if (_.isArray(attrs)) {
15
+ const keysToFilter = ['ghost_head', 'ghost_foot'];
16
+
17
+ // NOTE: to support edits of deprecated 'slack' setting artificial 'slack_url' and 'slack_username'
18
+ // were added to the request body in the input serializer. These should not be returned in response
19
+ // body unless directly requested
20
+ if (frame.original.body && frame.original.body.settings) {
21
+ const requestedEditSlackUrl = frame.original.body.settings.find(s => s.key === 'slack_url');
22
+ const requestedEditSlackUsername = frame.original.body.settings.find(s => s.key === 'slack_username');
23
+
24
+ if (!requestedEditSlackUrl) {
25
+ keysToFilter.push('slack_url');
26
+ }
27
+
28
+ if (!requestedEditSlackUsername) {
29
+ keysToFilter.push('slack_username');
30
+ }
31
+ }
32
+
33
+ attrs = _.filter(attrs, attr => !(keysToFilter.includes(attr.key)));
34
+ }
35
+
36
+ return attrs;
37
+ };
@@ -0,0 +1,11 @@
1
+ const clean = require('../utils/clean');
2
+ const url = require('../utils/url');
3
+
4
+ module.exports = (model, frame) => {
5
+ const jsonModel = model.toJSON ? model.toJSON(frame.options) : model;
6
+
7
+ url.forTag(model.id, jsonModel, frame.options);
8
+ clean.tag(jsonModel, frame);
9
+
10
+ return jsonModel;
11
+ };
@@ -0,0 +1,12 @@
1
+ const clean = require('../utils/clean');
2
+ const url = require('../utils/url');
3
+
4
+ module.exports = (model, frame) => {
5
+ const jsonModel = model.toJSON ? model.toJSON(frame.options) : model;
6
+
7
+ url.forUser(model.id, jsonModel, frame.options);
8
+
9
+ clean.author(jsonModel, frame);
10
+
11
+ return jsonModel;
12
+ };
@@ -1,7 +1,6 @@
1
1
  //@ts-check
2
2
  const debug = require('@tryghost/debug')('api:canary:utils:serializers:output:members');
3
3
  const {unparse} = require('@tryghost/members-csv');
4
- const labs = require('../../../../../../shared/labs');
5
4
 
6
5
  module.exports = {
7
6
  hasActiveStripeSubscriptions: createSerializer('hasActiveStripeSubscriptions', passthrough),
@@ -17,7 +16,6 @@ module.exports = {
17
16
  exportCSV: createSerializer('exportCSV', exportCSV),
18
17
 
19
18
  importCSV: createSerializer('importCSV', passthrough),
20
- stats: createSerializer('stats', passthrough),
21
19
  memberStats: createSerializer('memberStats', passthrough),
22
20
  mrrStats: createSerializer('mrrStats', passthrough),
23
21
  subscriberStats: createSerializer('subscriberStats', passthrough),
@@ -125,13 +123,10 @@ function serializeMember(member, options) {
125
123
  email_opened_count: json.email_opened_count,
126
124
  email_open_rate: json.email_open_rate,
127
125
  email_recipients: json.email_recipients,
128
- status: json.status
126
+ status: json.status,
127
+ last_seen_at: json.last_seen_at
129
128
  };
130
129
 
131
- if (labs.isSet('membersLastSeenFilter')) {
132
- serialized.last_seen_at = json.last_seen_at;
133
- }
134
-
135
130
  if (json.products) {
136
131
  serialized.products = json.products;
137
132
  }
@@ -1,5 +1,5 @@
1
1
  const debug = require('@tryghost/debug')('api:canary:utils:serializers:output:pages');
2
- const mapper = require('./utils/mapper');
2
+ const mappers = require('./mappers');
3
3
  const membersService = require('../../../../../services/members');
4
4
 
5
5
  module.exports = {
@@ -19,7 +19,7 @@ module.exports = {
19
19
 
20
20
  if (models.meta) {
21
21
  for (let model of models.data) {
22
- let page = await mapper.mapPage(model, frame, {tiers});
22
+ let page = await mappers.pages(model, frame, {tiers});
23
23
  pages.push(page);
24
24
  }
25
25
  frame.response = {
@@ -29,7 +29,7 @@ module.exports = {
29
29
 
30
30
  return;
31
31
  }
32
- let page = await mapper.mapPage(models, frame, {tiers});
32
+ let page = await mappers.pages(models, frame, {tiers});
33
33
  frame.response = {
34
34
  pages: [page]
35
35
  };
@@ -1,5 +1,5 @@
1
1
  const debug = require('@tryghost/debug')('api:canary:utils:serializers:output:posts');
2
- const mapper = require('./utils/mapper');
2
+ const mappers = require('./mappers');
3
3
  const membersService = require('../../../../../services/members');
4
4
 
5
5
  module.exports = {
@@ -18,7 +18,7 @@ module.exports = {
18
18
  const tiers = tiersModels.data ? tiersModels.data.map(tierModel => tierModel.toJSON()) : [];
19
19
  if (models.meta) {
20
20
  for (let model of models.data) {
21
- let post = await mapper.mapPost(model, frame, {tiers});
21
+ let post = await mappers.posts(model, frame, {tiers});
22
22
  posts.push(post);
23
23
  }
24
24
  frame.response = {
@@ -28,7 +28,7 @@ module.exports = {
28
28
 
29
29
  return;
30
30
  }
31
- let post = await mapper.mapPost(models, frame, {tiers});
31
+ let post = await mappers.posts(models, frame, {tiers});
32
32
  frame.response = {
33
33
  posts: [post]
34
34
  };