├── .env.example ├── .eslintignore ├── .eslintrc.yml ├── .gitignore ├── .jsbeautifyrc ├── .npmignore ├── .nvmrc ├── LICENSE ├── README.md ├── bin └── cli.js ├── client ├── favicon.ico └── img │ └── of-logo.svg ├── common ├── mixins │ ├── disableAllMethods.js │ ├── isPublicOrOwner.js │ └── timestamp.js └── models │ ├── artwork.js │ ├── artwork.json │ ├── channel.js │ ├── channel.json │ ├── collection.js │ ├── collection.json │ ├── frame.js │ ├── frame.json │ ├── openframe-user.js │ └── openframe-user.json ├── migrations ├── README.md └── new-data-model.js ├── package.json └── server ├── boot ├── acl.js ├── authentication.js ├── create-admin-user.js.example ├── create-sample-data.js ├── pubsub.js └── routes.js ├── component-config.json ├── config.json ├── config.local.js ├── datasources.json ├── datasources.local.js ├── lib └── auth.js ├── middleware.json ├── middleware.production.json ├── middleware ├── error-handler.js └── url-not-found-handler.js ├── model-config.json ├── model-config.local.js ├── providers.json ├── server.js └── views ├── 404.ejs ├── email-templates ├── reset-password.ejs └── verify.ejs └── error.ejs /.env.example: -------------------------------------------------------------------------------- 1 | # Expose the API Server on this port: 2 | PORT="3000" 3 | HOST="localhost" 4 | 5 | # Where is the PubSub Server? 6 | PS_PROTOCOL="http" 7 | PS_HOST="localhost" 8 | PS_PORT="3001" 9 | PS_PATH="/faye" 10 | 11 | PS_API_TOKEN="your shared token" 12 | 13 | WEBAPP_PROTOCOL="http" 14 | WEBAPP_HOST="localhost" 15 | WEBAPP_PORT=8888 16 | 17 | # Define a datasource - !! currently only tested with mongo !! 18 | LB_DB_DS_NAME=mongoDs 19 | LB_DB_DS_CONNECTOR=mongodb 20 | LB_DB_DS_DATABASE=ofsl 21 | LB_DB_DS_HOST="127.0.0.1" 22 | LB_DB_DS_PORT=27017 23 | # LB_DB_DS_USERNAME="mongoAdmin" 24 | # LB_DB_DS_PASSWORD="password" 25 | 26 | LB_EMAIL_DS_NAME=mailDs 27 | LB_EMAIL_DS_CONNECTOR=mail 28 | LB_EMAIL_DS_TYPE=SMTP 29 | LB_EMAIL_DS_HOST="smtp.somemailserver.com" 30 | LB_EMAIL_DS_PORT=465 31 | LB_EMAIL_DS_USERNAME="username" 32 | LB_EMAIL_DS_PASSWORD="password" -------------------------------------------------------------------------------- /.eslintignore: -------------------------------------------------------------------------------- 1 | node_modules/**/*.js -------------------------------------------------------------------------------- /.eslintrc.yml: -------------------------------------------------------------------------------- 1 | # see http://eslint.org/docs/rules/ for recommended rules 2 | extends: 'eslint:recommended' 3 | 4 | rules: 5 | indent: 6 | - 2 7 | - 4 8 | - SwitchCase: 1 9 | linebreak-style: [2, unix] 10 | no-console: [0] 11 | no-unused-vars: [1] 12 | # commas go last 13 | comma-style: [1, last] 14 | # variable comes before literal in conditional 15 | yoda: [1, never] 16 | # dots on same line as property 17 | dot-location: [1, property] 18 | # don't allow void operator 19 | no-void: [1] 20 | # no space before function paren 21 | space-before-function-paren: [1, never] 22 | space-before-blocks: [1, always] 23 | # prefer no space between array brackets 24 | array-bracket-spacing: [1, never] 25 | # prefer no space between object curly brackets 26 | object-curly-spacing: [1, never] 27 | # prefer no space between object prop brackets 28 | computed-property-spacing: [1, never] 29 | # prefer camelcase 30 | # camelcase: 31 | # - 1 32 | # - properties: always 33 | # prefer brackets for blocks 34 | curly: [1] 35 | no-multi-spaces: [1] 36 | space-in-parens: [1, never] 37 | eol-last: [1] 38 | no-trailing-spaces: [1] 39 | no-mixed-spaces-and-tabs: [1] 40 | # Allow but do not force dangling commas. 41 | comma-dangle: [0] 42 | # use single quotes 43 | quotes: [2, single] 44 | # use semicolons to end statements 45 | semi: [2, always] 46 | 47 | # warn if function params are reassigned 48 | no-param-reassign: 49 | - 1 50 | - props: false 51 | # enforce type equality 52 | eqeqeq: [2] 53 | 54 | no-return-assign: [2, always] 55 | no-undef-init: [1] 56 | no-lonely-if: [2] 57 | no-use-before-define: [2, nofunc] 58 | # enforce constructor functions are uppercased 59 | new-cap: 60 | - 2 61 | - newIsCap: true 62 | capIsNew: false 63 | 64 | 65 | 66 | env: 67 | # allow node env globals 68 | node: true 69 | browser: true 70 | jquery: true 71 | # allow commonjs module definition globals (e.g. require, module.exports) 72 | commonjs: true 73 | es6: true 74 | 75 | 76 | 77 | 78 | globals: 79 | _: true 80 | OF: true, 81 | Faye: true, 82 | FastClick: true 83 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | *.swp 2 | node_modules/ 3 | log/* 4 | npm-debug.log 5 | coverage/* 6 | .DS_Store 7 | .env 8 | # server/boot/create-sample-data.js 9 | server/boot/create-admin-user.js 10 | *.bak 11 | dump/* -------------------------------------------------------------------------------- /.jsbeautifyrc: -------------------------------------------------------------------------------- 1 | { 2 | "html": { 3 | "allowed_file_extensions": ["htm", "html", "xhtml", "shtml", "xml", "svg"], 4 | "brace_style": "collapse", // [collapse|expand|end-expand|none] Put braces on the same line as control statements (default), or put braces on own line (Allman / ANSI style), or just put end braces on own line, or attempt to keep them where they are 5 | "end_with_newline": false, // End output with newline 6 | "indent_char": " ", // Indentation character 7 | "indent_handlebars": false, // e.g. {{#foo}}, {{/foo}} 8 | "indent_inner_html": false, // Indent and sections 9 | "indent_scripts": "keep", // [keep|separate|normal] 10 | "indent_size": 4, // Indentation size 11 | "max_preserve_newlines": 0, // Maximum number of line breaks to be preserved in one chunk (0 disables) 12 | "preserve_newlines": true, // Whether existing line breaks before elements should be preserved (only works before elements, not inside tags or for text) 13 | "unformatted": ["a", "span", "img", "code", "pre", "sub", "sup", "em", "strong", "b", "i", "u", "strike", "big", "small", "pre", "h1", "h2", "h3", "h4", "h5", "h6"], // List of tags that should not be reformatted 14 | "wrap_line_length": 0 // Lines should wrap at next opportunity after this number of characters (0 disables) 15 | }, 16 | "css": { 17 | "allowed_file_extensions": ["css", "scss", "sass", "less"], 18 | "end_with_newline": false, // End output with newline 19 | "indent_char": " ", // Indentation character 20 | "indent_size": 4, // Indentation size 21 | "newline_between_rules": true, // Add a new line after every css rule 22 | "selector_separator": " ", 23 | "selector_separator_newline": true // Separate selectors with newline or not (e.g. "a,\nbr" or "a, br") 24 | }, 25 | "js": { 26 | "allowed_file_extensions": ["js", "json", "jshintrc", "jsbeautifyrc"], 27 | "brace_style": "collapse", // [collapse|expand|end-expand|none] Put braces on the same line as control statements (default), or put braces on own line (Allman / ANSI style), or just put end braces on own line, or attempt to keep them where they are 28 | "break_chained_methods": false, // Break chained method calls across subsequent lines 29 | "e4x": false, // Pass E4X xml literals through untouched 30 | "end_with_newline": false, // End output with newline 31 | "indent_char": " ", // Indentation character 32 | "indent_level": 0, // Initial indentation level 33 | "indent_size": 4, // Indentation size 34 | "indent_with_tabs": true, // Indent with tabs, overrides `indent_size` and `indent_char` 35 | "jslint_happy": false, // If true, then jslint-stricter mode is enforced 36 | "keep_array_indentation": false, // Preserve array indentation 37 | "keep_function_indentation": false, // Preserve function indentation 38 | "max_preserve_newlines": 0, // Maximum number of line breaks to be preserved in one chunk (0 disables) 39 | "preserve_newlines": true, // Whether existing line breaks should be preserved 40 | "space_after_anon_function": false, // Should the space before an anonymous function's parens be added, "function()" vs "function ()" 41 | "space_before_conditional": true, // Should the space before conditional statement be added, "if(true)" vs "if (true)" 42 | "space_in_empty_paren": false, // Add padding spaces within empty paren, "f()" vs "f( )" 43 | "space_in_paren": false, // Add padding spaces within paren, ie. f( a, b ) 44 | "unescape_strings": false, // Should printable characters in strings encoded in \xNN notation be unescaped, "example" vs "\x65\x78\x61\x6d\x70\x6c\x65" 45 | "wrap_line_length": 0 // Lines should wrap at next opportunity after this number of characters (0 disables) 46 | } 47 | } -------------------------------------------------------------------------------- /.npmignore: -------------------------------------------------------------------------------- 1 | .eslintignore 2 | .eslintrc.yml 3 | .jsbeautifyrc 4 | test/* -------------------------------------------------------------------------------- /.nvmrc: -------------------------------------------------------------------------------- 1 | 6 2 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | GNU AFFERO GENERAL PUBLIC LICENSE 2 | Version 3, 19 November 2007 3 | 4 | Copyright (C) 2007 Free Software Foundation, Inc. 5 | Everyone is permitted to copy and distribute verbatim copies 6 | of this license document, but changing it is not allowed. 7 | 8 | Preamble 9 | 10 | The GNU Affero General Public License is a free, copyleft license for 11 | software and other kinds of works, specifically designed to ensure 12 | cooperation with the community in the case of network server software. 13 | 14 | The licenses for most software and other practical works are designed 15 | to take away your freedom to share and change the works. By contrast, 16 | our General Public Licenses are intended to guarantee your freedom to 17 | share and change all versions of a program--to make sure it remains free 18 | software for all its users. 19 | 20 | When we speak of free software, we are referring to freedom, not 21 | price. Our General Public Licenses are designed to make sure that you 22 | have the freedom to distribute copies of free software (and charge for 23 | them if you wish), that you receive source code or can get it if you 24 | want it, that you can change the software or use pieces of it in new 25 | free programs, and that you know you can do these things. 26 | 27 | Developers that use our General Public Licenses protect your rights 28 | with two steps: (1) assert copyright on the software, and (2) offer 29 | you this License which gives you legal permission to copy, distribute 30 | and/or modify the software. 31 | 32 | A secondary benefit of defending all users' freedom is that 33 | improvements made in alternate versions of the program, if they 34 | receive widespread use, become available for other developers to 35 | incorporate. Many developers of free software are heartened and 36 | encouraged by the resulting cooperation. However, in the case of 37 | software used on network servers, this result may fail to come about. 38 | The GNU General Public License permits making a modified version and 39 | letting the public access it on a server without ever releasing its 40 | source code to the public. 41 | 42 | The GNU Affero General Public License is designed specifically to 43 | ensure that, in such cases, the modified source code becomes available 44 | to the community. It requires the operator of a network server to 45 | provide the source code of the modified version running there to the 46 | users of that server. Therefore, public use of a modified version, on 47 | a publicly accessible server, gives the public access to the source 48 | code of the modified version. 49 | 50 | An older license, called the Affero General Public License and 51 | published by Affero, was designed to accomplish similar goals. This is 52 | a different license, not a version of the Affero GPL, but Affero has 53 | released a new version of the Affero GPL which permits relicensing under 54 | this license. 55 | 56 | The precise terms and conditions for copying, distribution and 57 | modification follow. 58 | 59 | TERMS AND CONDITIONS 60 | 61 | 0. Definitions. 62 | 63 | "This License" refers to version 3 of the GNU Affero General Public License. 64 | 65 | "Copyright" also means copyright-like laws that apply to other kinds of 66 | works, such as semiconductor masks. 67 | 68 | "The Program" refers to any copyrightable work licensed under this 69 | License. Each licensee is addressed as "you". "Licensees" and 70 | "recipients" may be individuals or organizations. 71 | 72 | To "modify" a work means to copy from or adapt all or part of the work 73 | in a fashion requiring copyright permission, other than the making of an 74 | exact copy. The resulting work is called a "modified version" of the 75 | earlier work or a work "based on" the earlier work. 76 | 77 | A "covered work" means either the unmodified Program or a work based 78 | on the Program. 79 | 80 | To "propagate" a work means to do anything with it that, without 81 | permission, would make you directly or secondarily liable for 82 | infringement under applicable copyright law, except executing it on a 83 | computer or modifying a private copy. Propagation includes copying, 84 | distribution (with or without modification), making available to the 85 | public, and in some countries other activities as well. 86 | 87 | To "convey" a work means any kind of propagation that enables other 88 | parties to make or receive copies. Mere interaction with a user through 89 | a computer network, with no transfer of a copy, is not conveying. 90 | 91 | An interactive user interface displays "Appropriate Legal Notices" 92 | to the extent that it includes a convenient and prominently visible 93 | feature that (1) displays an appropriate copyright notice, and (2) 94 | tells the user that there is no warranty for the work (except to the 95 | extent that warranties are provided), that licensees may convey the 96 | work under this License, and how to view a copy of this License. If 97 | the interface presents a list of user commands or options, such as a 98 | menu, a prominent item in the list meets this criterion. 99 | 100 | 1. Source Code. 101 | 102 | The "source code" for a work means the preferred form of the work 103 | for making modifications to it. "Object code" means any non-source 104 | form of a work. 105 | 106 | A "Standard Interface" means an interface that either is an official 107 | standard defined by a recognized standards body, or, in the case of 108 | interfaces specified for a particular programming language, one that 109 | is widely used among developers working in that language. 110 | 111 | The "System Libraries" of an executable work include anything, other 112 | than the work as a whole, that (a) is included in the normal form of 113 | packaging a Major Component, but which is not part of that Major 114 | Component, and (b) serves only to enable use of the work with that 115 | Major Component, or to implement a Standard Interface for which an 116 | implementation is available to the public in source code form. A 117 | "Major Component", in this context, means a major essential component 118 | (kernel, window system, and so on) of the specific operating system 119 | (if any) on which the executable work runs, or a compiler used to 120 | produce the work, or an object code interpreter used to run it. 121 | 122 | The "Corresponding Source" for a work in object code form means all 123 | the source code needed to generate, install, and (for an executable 124 | work) run the object code and to modify the work, including scripts to 125 | control those activities. However, it does not include the work's 126 | System Libraries, or general-purpose tools or generally available free 127 | programs which are used unmodified in performing those activities but 128 | which are not part of the work. For example, Corresponding Source 129 | includes interface definition files associated with source files for 130 | the work, and the source code for shared libraries and dynamically 131 | linked subprograms that the work is specifically designed to require, 132 | such as by intimate data communication or control flow between those 133 | subprograms and other parts of the work. 134 | 135 | The Corresponding Source need not include anything that users 136 | can regenerate automatically from other parts of the Corresponding 137 | Source. 138 | 139 | The Corresponding Source for a work in source code form is that 140 | same work. 141 | 142 | 2. Basic Permissions. 143 | 144 | All rights granted under this License are granted for the term of 145 | copyright on the Program, and are irrevocable provided the stated 146 | conditions are met. This License explicitly affirms your unlimited 147 | permission to run the unmodified Program. The output from running a 148 | covered work is covered by this License only if the output, given its 149 | content, constitutes a covered work. This License acknowledges your 150 | rights of fair use or other equivalent, as provided by copyright law. 151 | 152 | You may make, run and propagate covered works that you do not 153 | convey, without conditions so long as your license otherwise remains 154 | in force. You may convey covered works to others for the sole purpose 155 | of having them make modifications exclusively for you, or provide you 156 | with facilities for running those works, provided that you comply with 157 | the terms of this License in conveying all material for which you do 158 | not control copyright. Those thus making or running the covered works 159 | for you must do so exclusively on your behalf, under your direction 160 | and control, on terms that prohibit them from making any copies of 161 | your copyrighted material outside their relationship with you. 162 | 163 | Conveying under any other circumstances is permitted solely under 164 | the conditions stated below. Sublicensing is not allowed; section 10 165 | makes it unnecessary. 166 | 167 | 3. Protecting Users' Legal Rights From Anti-Circumvention Law. 168 | 169 | No covered work shall be deemed part of an effective technological 170 | measure under any applicable law fulfilling obligations under article 171 | 11 of the WIPO copyright treaty adopted on 20 December 1996, or 172 | similar laws prohibiting or restricting circumvention of such 173 | measures. 174 | 175 | When you convey a covered work, you waive any legal power to forbid 176 | circumvention of technological measures to the extent such circumvention 177 | is effected by exercising rights under this License with respect to 178 | the covered work, and you disclaim any intention to limit operation or 179 | modification of the work as a means of enforcing, against the work's 180 | users, your or third parties' legal rights to forbid circumvention of 181 | technological measures. 182 | 183 | 4. Conveying Verbatim Copies. 184 | 185 | You may convey verbatim copies of the Program's source code as you 186 | receive it, in any medium, provided that you conspicuously and 187 | appropriately publish on each copy an appropriate copyright notice; 188 | keep intact all notices stating that this License and any 189 | non-permissive terms added in accord with section 7 apply to the code; 190 | keep intact all notices of the absence of any warranty; and give all 191 | recipients a copy of this License along with the Program. 192 | 193 | You may charge any price or no price for each copy that you convey, 194 | and you may offer support or warranty protection for a fee. 195 | 196 | 5. Conveying Modified Source Versions. 197 | 198 | You may convey a work based on the Program, or the modifications to 199 | produce it from the Program, in the form of source code under the 200 | terms of section 4, provided that you also meet all of these conditions: 201 | 202 | a) The work must carry prominent notices stating that you modified 203 | it, and giving a relevant date. 204 | 205 | b) The work must carry prominent notices stating that it is 206 | released under this License and any conditions added under section 207 | 7. This requirement modifies the requirement in section 4 to 208 | "keep intact all notices". 209 | 210 | c) You must license the entire work, as a whole, under this 211 | License to anyone who comes into possession of a copy. This 212 | License will therefore apply, along with any applicable section 7 213 | additional terms, to the whole of the work, and all its parts, 214 | regardless of how they are packaged. This License gives no 215 | permission to license the work in any other way, but it does not 216 | invalidate such permission if you have separately received it. 217 | 218 | d) If the work has interactive user interfaces, each must display 219 | Appropriate Legal Notices; however, if the Program has interactive 220 | interfaces that do not display Appropriate Legal Notices, your 221 | work need not make them do so. 222 | 223 | A compilation of a covered work with other separate and independent 224 | works, which are not by their nature extensions of the covered work, 225 | and which are not combined with it such as to form a larger program, 226 | in or on a volume of a storage or distribution medium, is called an 227 | "aggregate" if the compilation and its resulting copyright are not 228 | used to limit the access or legal rights of the compilation's users 229 | beyond what the individual works permit. Inclusion of a covered work 230 | in an aggregate does not cause this License to apply to the other 231 | parts of the aggregate. 232 | 233 | 6. Conveying Non-Source Forms. 234 | 235 | You may convey a covered work in object code form under the terms 236 | of sections 4 and 5, provided that you also convey the 237 | machine-readable Corresponding Source under the terms of this License, 238 | in one of these ways: 239 | 240 | a) Convey the object code in, or embodied in, a physical product 241 | (including a physical distribution medium), accompanied by the 242 | Corresponding Source fixed on a durable physical medium 243 | customarily used for software interchange. 244 | 245 | b) Convey the object code in, or embodied in, a physical product 246 | (including a physical distribution medium), accompanied by a 247 | written offer, valid for at least three years and valid for as 248 | long as you offer spare parts or customer support for that product 249 | model, to give anyone who possesses the object code either (1) a 250 | copy of the Corresponding Source for all the software in the 251 | product that is covered by this License, on a durable physical 252 | medium customarily used for software interchange, for a price no 253 | more than your reasonable cost of physically performing this 254 | conveying of source, or (2) access to copy the 255 | Corresponding Source from a network server at no charge. 256 | 257 | c) Convey individual copies of the object code with a copy of the 258 | written offer to provide the Corresponding Source. This 259 | alternative is allowed only occasionally and noncommercially, and 260 | only if you received the object code with such an offer, in accord 261 | with subsection 6b. 262 | 263 | d) Convey the object code by offering access from a designated 264 | place (gratis or for a charge), and offer equivalent access to the 265 | Corresponding Source in the same way through the same place at no 266 | further charge. You need not require recipients to copy the 267 | Corresponding Source along with the object code. If the place to 268 | copy the object code is a network server, the Corresponding Source 269 | may be on a different server (operated by you or a third party) 270 | that supports equivalent copying facilities, provided you maintain 271 | clear directions next to the object code saying where to find the 272 | Corresponding Source. Regardless of what server hosts the 273 | Corresponding Source, you remain obligated to ensure that it is 274 | available for as long as needed to satisfy these requirements. 275 | 276 | e) Convey the object code using peer-to-peer transmission, provided 277 | you inform other peers where the object code and Corresponding 278 | Source of the work are being offered to the general public at no 279 | charge under subsection 6d. 280 | 281 | A separable portion of the object code, whose source code is excluded 282 | from the Corresponding Source as a System Library, need not be 283 | included in conveying the object code work. 284 | 285 | A "User Product" is either (1) a "consumer product", which means any 286 | tangible personal property which is normally used for personal, family, 287 | or household purposes, or (2) anything designed or sold for incorporation 288 | into a dwelling. In determining whether a product is a consumer product, 289 | doubtful cases shall be resolved in favor of coverage. For a particular 290 | product received by a particular user, "normally used" refers to a 291 | typical or common use of that class of product, regardless of the status 292 | of the particular user or of the way in which the particular user 293 | actually uses, or expects or is expected to use, the product. A product 294 | is a consumer product regardless of whether the product has substantial 295 | commercial, industrial or non-consumer uses, unless such uses represent 296 | the only significant mode of use of the product. 297 | 298 | "Installation Information" for a User Product means any methods, 299 | procedures, authorization keys, or other information required to install 300 | and execute modified versions of a covered work in that User Product from 301 | a modified version of its Corresponding Source. The information must 302 | suffice to ensure that the continued functioning of the modified object 303 | code is in no case prevented or interfered with solely because 304 | modification has been made. 305 | 306 | If you convey an object code work under this section in, or with, or 307 | specifically for use in, a User Product, and the conveying occurs as 308 | part of a transaction in which the right of possession and use of the 309 | User Product is transferred to the recipient in perpetuity or for a 310 | fixed term (regardless of how the transaction is characterized), the 311 | Corresponding Source conveyed under this section must be accompanied 312 | by the Installation Information. But this requirement does not apply 313 | if neither you nor any third party retains the ability to install 314 | modified object code on the User Product (for example, the work has 315 | been installed in ROM). 316 | 317 | The requirement to provide Installation Information does not include a 318 | requirement to continue to provide support service, warranty, or updates 319 | for a work that has been modified or installed by the recipient, or for 320 | the User Product in which it has been modified or installed. Access to a 321 | network may be denied when the modification itself materially and 322 | adversely affects the operation of the network or violates the rules and 323 | protocols for communication across the network. 324 | 325 | Corresponding Source conveyed, and Installation Information provided, 326 | in accord with this section must be in a format that is publicly 327 | documented (and with an implementation available to the public in 328 | source code form), and must require no special password or key for 329 | unpacking, reading or copying. 330 | 331 | 7. Additional Terms. 332 | 333 | "Additional permissions" are terms that supplement the terms of this 334 | License by making exceptions from one or more of its conditions. 335 | Additional permissions that are applicable to the entire Program shall 336 | be treated as though they were included in this License, to the extent 337 | that they are valid under applicable law. If additional permissions 338 | apply only to part of the Program, that part may be used separately 339 | under those permissions, but the entire Program remains governed by 340 | this License without regard to the additional permissions. 341 | 342 | When you convey a copy of a covered work, you may at your option 343 | remove any additional permissions from that copy, or from any part of 344 | it. (Additional permissions may be written to require their own 345 | removal in certain cases when you modify the work.) You may place 346 | additional permissions on material, added by you to a covered work, 347 | for which you have or can give appropriate copyright permission. 348 | 349 | Notwithstanding any other provision of this License, for material you 350 | add to a covered work, you may (if authorized by the copyright holders of 351 | that material) supplement the terms of this License with terms: 352 | 353 | a) Disclaiming warranty or limiting liability differently from the 354 | terms of sections 15 and 16 of this License; or 355 | 356 | b) Requiring preservation of specified reasonable legal notices or 357 | author attributions in that material or in the Appropriate Legal 358 | Notices displayed by works containing it; or 359 | 360 | c) Prohibiting misrepresentation of the origin of that material, or 361 | requiring that modified versions of such material be marked in 362 | reasonable ways as different from the original version; or 363 | 364 | d) Limiting the use for publicity purposes of names of licensors or 365 | authors of the material; or 366 | 367 | e) Declining to grant rights under trademark law for use of some 368 | trade names, trademarks, or service marks; or 369 | 370 | f) Requiring indemnification of licensors and authors of that 371 | material by anyone who conveys the material (or modified versions of 372 | it) with contractual assumptions of liability to the recipient, for 373 | any liability that these contractual assumptions directly impose on 374 | those licensors and authors. 375 | 376 | All other non-permissive additional terms are considered "further 377 | restrictions" within the meaning of section 10. If the Program as you 378 | received it, or any part of it, contains a notice stating that it is 379 | governed by this License along with a term that is a further 380 | restriction, you may remove that term. If a license document contains 381 | a further restriction but permits relicensing or conveying under this 382 | License, you may add to a covered work material governed by the terms 383 | of that license document, provided that the further restriction does 384 | not survive such relicensing or conveying. 385 | 386 | If you add terms to a covered work in accord with this section, you 387 | must place, in the relevant source files, a statement of the 388 | additional terms that apply to those files, or a notice indicating 389 | where to find the applicable terms. 390 | 391 | Additional terms, permissive or non-permissive, may be stated in the 392 | form of a separately written license, or stated as exceptions; 393 | the above requirements apply either way. 394 | 395 | 8. Termination. 396 | 397 | You may not propagate or modify a covered work except as expressly 398 | provided under this License. Any attempt otherwise to propagate or 399 | modify it is void, and will automatically terminate your rights under 400 | this License (including any patent licenses granted under the third 401 | paragraph of section 11). 402 | 403 | However, if you cease all violation of this License, then your 404 | license from a particular copyright holder is reinstated (a) 405 | provisionally, unless and until the copyright holder explicitly and 406 | finally terminates your license, and (b) permanently, if the copyright 407 | holder fails to notify you of the violation by some reasonable means 408 | prior to 60 days after the cessation. 409 | 410 | Moreover, your license from a particular copyright holder is 411 | reinstated permanently if the copyright holder notifies you of the 412 | violation by some reasonable means, this is the first time you have 413 | received notice of violation of this License (for any work) from that 414 | copyright holder, and you cure the violation prior to 30 days after 415 | your receipt of the notice. 416 | 417 | Termination of your rights under this section does not terminate the 418 | licenses of parties who have received copies or rights from you under 419 | this License. If your rights have been terminated and not permanently 420 | reinstated, you do not qualify to receive new licenses for the same 421 | material under section 10. 422 | 423 | 9. Acceptance Not Required for Having Copies. 424 | 425 | You are not required to accept this License in order to receive or 426 | run a copy of the Program. Ancillary propagation of a covered work 427 | occurring solely as a consequence of using peer-to-peer transmission 428 | to receive a copy likewise does not require acceptance. However, 429 | nothing other than this License grants you permission to propagate or 430 | modify any covered work. These actions infringe copyright if you do 431 | not accept this License. Therefore, by modifying or propagating a 432 | covered work, you indicate your acceptance of this License to do so. 433 | 434 | 10. Automatic Licensing of Downstream Recipients. 435 | 436 | Each time you convey a covered work, the recipient automatically 437 | receives a license from the original licensors, to run, modify and 438 | propagate that work, subject to this License. You are not responsible 439 | for enforcing compliance by third parties with this License. 440 | 441 | An "entity transaction" is a transaction transferring control of an 442 | organization, or substantially all assets of one, or subdividing an 443 | organization, or merging organizations. If propagation of a covered 444 | work results from an entity transaction, each party to that 445 | transaction who receives a copy of the work also receives whatever 446 | licenses to the work the party's predecessor in interest had or could 447 | give under the previous paragraph, plus a right to possession of the 448 | Corresponding Source of the work from the predecessor in interest, if 449 | the predecessor has it or can get it with reasonable efforts. 450 | 451 | You may not impose any further restrictions on the exercise of the 452 | rights granted or affirmed under this License. For example, you may 453 | not impose a license fee, royalty, or other charge for exercise of 454 | rights granted under this License, and you may not initiate litigation 455 | (including a cross-claim or counterclaim in a lawsuit) alleging that 456 | any patent claim is infringed by making, using, selling, offering for 457 | sale, or importing the Program or any portion of it. 458 | 459 | 11. Patents. 460 | 461 | A "contributor" is a copyright holder who authorizes use under this 462 | License of the Program or a work on which the Program is based. The 463 | work thus licensed is called the contributor's "contributor version". 464 | 465 | A contributor's "essential patent claims" are all patent claims 466 | owned or controlled by the contributor, whether already acquired or 467 | hereafter acquired, that would be infringed by some manner, permitted 468 | by this License, of making, using, or selling its contributor version, 469 | but do not include claims that would be infringed only as a 470 | consequence of further modification of the contributor version. For 471 | purposes of this definition, "control" includes the right to grant 472 | patent sublicenses in a manner consistent with the requirements of 473 | this License. 474 | 475 | Each contributor grants you a non-exclusive, worldwide, royalty-free 476 | patent license under the contributor's essential patent claims, to 477 | make, use, sell, offer for sale, import and otherwise run, modify and 478 | propagate the contents of its contributor version. 479 | 480 | In the following three paragraphs, a "patent license" is any express 481 | agreement or commitment, however denominated, not to enforce a patent 482 | (such as an express permission to practice a patent or covenant not to 483 | sue for patent infringement). To "grant" such a patent license to a 484 | party means to make such an agreement or commitment not to enforce a 485 | patent against the party. 486 | 487 | If you convey a covered work, knowingly relying on a patent license, 488 | and the Corresponding Source of the work is not available for anyone 489 | to copy, free of charge and under the terms of this License, through a 490 | publicly available network server or other readily accessible means, 491 | then you must either (1) cause the Corresponding Source to be so 492 | available, or (2) arrange to deprive yourself of the benefit of the 493 | patent license for this particular work, or (3) arrange, in a manner 494 | consistent with the requirements of this License, to extend the patent 495 | license to downstream recipients. "Knowingly relying" means you have 496 | actual knowledge that, but for the patent license, your conveying the 497 | covered work in a country, or your recipient's use of the covered work 498 | in a country, would infringe one or more identifiable patents in that 499 | country that you have reason to believe are valid. 500 | 501 | If, pursuant to or in connection with a single transaction or 502 | arrangement, you convey, or propagate by procuring conveyance of, a 503 | covered work, and grant a patent license to some of the parties 504 | receiving the covered work authorizing them to use, propagate, modify 505 | or convey a specific copy of the covered work, then the patent license 506 | you grant is automatically extended to all recipients of the covered 507 | work and works based on it. 508 | 509 | A patent license is "discriminatory" if it does not include within 510 | the scope of its coverage, prohibits the exercise of, or is 511 | conditioned on the non-exercise of one or more of the rights that are 512 | specifically granted under this License. You may not convey a covered 513 | work if you are a party to an arrangement with a third party that is 514 | in the business of distributing software, under which you make payment 515 | to the third party based on the extent of your activity of conveying 516 | the work, and under which the third party grants, to any of the 517 | parties who would receive the covered work from you, a discriminatory 518 | patent license (a) in connection with copies of the covered work 519 | conveyed by you (or copies made from those copies), or (b) primarily 520 | for and in connection with specific products or compilations that 521 | contain the covered work, unless you entered into that arrangement, 522 | or that patent license was granted, prior to 28 March 2007. 523 | 524 | Nothing in this License shall be construed as excluding or limiting 525 | any implied license or other defenses to infringement that may 526 | otherwise be available to you under applicable patent law. 527 | 528 | 12. No Surrender of Others' Freedom. 529 | 530 | If conditions are imposed on you (whether by court order, agreement or 531 | otherwise) that contradict the conditions of this License, they do not 532 | excuse you from the conditions of this License. If you cannot convey a 533 | covered work so as to satisfy simultaneously your obligations under this 534 | License and any other pertinent obligations, then as a consequence you may 535 | not convey it at all. For example, if you agree to terms that obligate you 536 | to collect a royalty for further conveying from those to whom you convey 537 | the Program, the only way you could satisfy both those terms and this 538 | License would be to refrain entirely from conveying the Program. 539 | 540 | 13. Remote Network Interaction; Use with the GNU General Public License. 541 | 542 | Notwithstanding any other provision of this License, if you modify the 543 | Program, your modified version must prominently offer all users 544 | interacting with it remotely through a computer network (if your version 545 | supports such interaction) an opportunity to receive the Corresponding 546 | Source of your version by providing access to the Corresponding Source 547 | from a network server at no charge, through some standard or customary 548 | means of facilitating copying of software. This Corresponding Source 549 | shall include the Corresponding Source for any work covered by version 3 550 | of the GNU General Public License that is incorporated pursuant to the 551 | following paragraph. 552 | 553 | Notwithstanding any other provision of this License, you have 554 | permission to link or combine any covered work with a work licensed 555 | under version 3 of the GNU General Public License into a single 556 | combined work, and to convey the resulting work. The terms of this 557 | License will continue to apply to the part which is the covered work, 558 | but the work with which it is combined will remain governed by version 559 | 3 of the GNU General Public License. 560 | 561 | 14. Revised Versions of this License. 562 | 563 | The Free Software Foundation may publish revised and/or new versions of 564 | the GNU Affero General Public License from time to time. Such new versions 565 | will be similar in spirit to the present version, but may differ in detail to 566 | address new problems or concerns. 567 | 568 | Each version is given a distinguishing version number. If the 569 | Program specifies that a certain numbered version of the GNU Affero General 570 | Public License "or any later version" applies to it, you have the 571 | option of following the terms and conditions either of that numbered 572 | version or of any later version published by the Free Software 573 | Foundation. If the Program does not specify a version number of the 574 | GNU Affero General Public License, you may choose any version ever published 575 | by the Free Software Foundation. 576 | 577 | If the Program specifies that a proxy can decide which future 578 | versions of the GNU Affero General Public License can be used, that proxy's 579 | public statement of acceptance of a version permanently authorizes you 580 | to choose that version for the Program. 581 | 582 | Later license versions may give you additional or different 583 | permissions. However, no additional obligations are imposed on any 584 | author or copyright holder as a result of your choosing to follow a 585 | later version. 586 | 587 | 15. Disclaimer of Warranty. 588 | 589 | THERE IS NO WARRANTY FOR THE PROGRAM, TO THE EXTENT PERMITTED BY 590 | APPLICABLE LAW. EXCEPT WHEN OTHERWISE STATED IN WRITING THE COPYRIGHT 591 | HOLDERS AND/OR OTHER PARTIES PROVIDE THE PROGRAM "AS IS" WITHOUT WARRANTY 592 | OF ANY KIND, EITHER EXPRESSED OR IMPLIED, INCLUDING, BUT NOT LIMITED TO, 593 | THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR 594 | PURPOSE. THE ENTIRE RISK AS TO THE QUALITY AND PERFORMANCE OF THE PROGRAM 595 | IS WITH YOU. SHOULD THE PROGRAM PROVE DEFECTIVE, YOU ASSUME THE COST OF 596 | ALL NECESSARY SERVICING, REPAIR OR CORRECTION. 597 | 598 | 16. Limitation of Liability. 599 | 600 | IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN WRITING 601 | WILL ANY COPYRIGHT HOLDER, OR ANY OTHER PARTY WHO MODIFIES AND/OR CONVEYS 602 | THE PROGRAM AS PERMITTED ABOVE, BE LIABLE TO YOU FOR DAMAGES, INCLUDING ANY 603 | GENERAL, SPECIAL, INCIDENTAL OR CONSEQUENTIAL DAMAGES ARISING OUT OF THE 604 | USE OR INABILITY TO USE THE PROGRAM (INCLUDING BUT NOT LIMITED TO LOSS OF 605 | DATA OR DATA BEING RENDERED INACCURATE OR LOSSES SUSTAINED BY YOU OR THIRD 606 | PARTIES OR A FAILURE OF THE PROGRAM TO OPERATE WITH ANY OTHER PROGRAMS), 607 | EVEN IF SUCH HOLDER OR OTHER PARTY HAS BEEN ADVISED OF THE POSSIBILITY OF 608 | SUCH DAMAGES. 609 | 610 | 17. Interpretation of Sections 15 and 16. 611 | 612 | If the disclaimer of warranty and limitation of liability provided 613 | above cannot be given local legal effect according to their terms, 614 | reviewing courts shall apply local law that most closely approximates 615 | an absolute waiver of all civil liability in connection with the 616 | Program, unless a warranty or assumption of liability accompanies a 617 | copy of the Program in return for a fee. 618 | 619 | END OF TERMS AND CONDITIONS 620 | 621 | How to Apply These Terms to Your New Programs 622 | 623 | If you develop a new program, and you want it to be of the greatest 624 | possible use to the public, the best way to achieve this is to make it 625 | free software which everyone can redistribute and change under these terms. 626 | 627 | To do so, attach the following notices to the program. It is safest 628 | to attach them to the start of each source file to most effectively 629 | state the exclusion of warranty; and each file should have at least 630 | the "copyright" line and a pointer to where the full notice is found. 631 | 632 | 633 | Copyright (C) 634 | 635 | This program is free software: you can redistribute it and/or modify 636 | it under the terms of the GNU Affero General Public License as published by 637 | the Free Software Foundation, either version 3 of the License, or 638 | (at your option) any later version. 639 | 640 | This program is distributed in the hope that it will be useful, 641 | but WITHOUT ANY WARRANTY; without even the implied warranty of 642 | MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 643 | GNU Affero General Public License for more details. 644 | 645 | You should have received a copy of the GNU Affero General Public License 646 | along with this program. If not, see . 647 | 648 | Also add information on how to contact you by electronic and paper mail. 649 | 650 | If your software can interact with users remotely through a computer 651 | network, you should also make sure that it provides a way for users to 652 | get its source. For example, if your program is a web application, its 653 | interface could display a "Source" link that leads users to an archive 654 | of the code. There are many ways you could offer source, and different 655 | solutions will be better for different programs; see section 13 for the 656 | specific requirements. 657 | 658 | You should also get your employer (if you work as a programmer) or school, 659 | if any, to sign a "copyright disclaimer" for the program, if necessary. 660 | For more information on this, and how to apply and follow the GNU AGPL, see 661 | . -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Openframe API 2 | 3 | The Openframe API server. 4 | 5 | > Note: This repo is under active development. The API is likely to change. 6 | 7 | ### Design Notes 8 | 9 | This version of the API Server is built on [loopback](http://loopback.io/). The idea is to work towards a data model which supports the basic goals of Openframe, guided by a handful of [pilot use cases](https://github.com/OpenframeProject/Openframe-API/wiki/Pilot-Use-Cases). 10 | 11 | At present, the API provides a basic RESTful interface to the data model. It represents the **API Server (REST)** in the diagram below. The Global Event Bus lives in a separate repository, [Openframe-PubSubServer](https://github.com/OpenframeProject/Openframe-PubSubServer). 12 | 13 | ![alt tag](https://raw.githubusercontent.com/OpenframeProject/openframeproject.github.io/master/img/API%20Diagram%20v3.jpg) 14 | 15 | The block diagram above represents a proposed architecture for the Openframe platform. It will continue to evolve as development on the project progresses. 16 | 17 | 18 | ### Running an Openframe API Server 19 | 20 | This package provides a cli which can be used to start up a server. Install the package with npm, and run `openframe-apiserver` to start it. 21 | 22 | ```bash 23 | $ sudo npm install -g openframe-apiserver 24 | 25 | # start the server... 26 | $ openframe-apiserver 27 | ``` 28 | 29 | For DEBUG output, set the DEBUG env var: 30 | 31 | ```bash 32 | # output ALL debug (includes a lot due to loopback) 33 | $ DEBUG=* openframe-apiserver 34 | 35 | # output openframe-specific debug 36 | $ DEBUG=openframe:* openframe-apiserver 37 | ``` 38 | 39 | If you're not running an instance of the pubsub server separately, you can start up an instance of that concurrently by passing the `-p` parameter. 40 | 41 | ```bash 42 | # start the API server and PubSub server 43 | $ openframe-apiserver -p 44 | ``` 45 | 46 | Various configuration options can be set using a `.env` file using the `-f` flag. An example .env file might specify the port on which to expose the API server, configure the datasource, and specify a host and port on which the API server can expect to find the pubsub server. Take a look at the `.env.example` file in the project root. 47 | 48 | ``` 49 | # in .env file... 50 | PORT=1234 51 | 52 | # indicate how the API server should connect to the pubsub server 53 | PS_HOST='pubsub.openframe.io' 54 | PS_PORT=2345 55 | ``` 56 | 57 | ### Local Development 58 | 59 | The codebase was largely written following the [loopback docs'](https://docs.strongloop.com/display/public/LB/LoopBack) recommendations and examples. By default the API server will use an in-memory data store and will generate some dummy data (a few users, a few artworks). To run locally, clone the repo, install the npm dependencies, and run `npm start`. 60 | 61 | ```bash 62 | # (you'll probably fork the repo, and clone your fork) 63 | $ git clone https://github.com/OpenframeProject/Openframe-APIServer.git 64 | $ cd Openframe-APIServer 65 | $ npm install 66 | $ npm start 67 | ``` 68 | 69 | This repo includes the [pubsub server](https://github.com/OpenframeProject/Openframe-PubSubServer) as a dependency. Running `npm start` will spin up an instance of the pubsub server on port 8889. If you want to start only the API, run `npm run start-api`. Likewise, if you want to run only the pubsub server, run `npm run start-pubsub`. 70 | 71 | 72 | ### REST API Docs 73 | 74 | Loopback provides auto-generated documentation (via swagger) based on the data model definitions. After starting up the server locally, visit [localhost:8888/explorer/](http://localhost:8888/explorer/) to view the docs and test out the API. 75 | 76 | ### License 77 | 78 | The code in this project is licensed under the GNU AGPLv3, a copyleft license that promotes server-based software freedom. For me info, see the description [here](https://choosealicense.com/licenses/agpl-3.0/), or take a look at the LICENSE included in the repo. 79 | -------------------------------------------------------------------------------- /bin/cli.js: -------------------------------------------------------------------------------- 1 | #! /usr/bin/env node 2 | 3 | var program = require('commander'), 4 | debug = require('debug')('openframe:apiserver:cli'), 5 | p = require('../package.json'), 6 | version = p.version.split('.').shift(), 7 | conf = {}; 8 | 9 | program 10 | .version(version) 11 | .option('-f, --file [file]', 'Specify a .env file which includes environment vars to load.') 12 | .option('-v, --verbose', 'Output warnings.') 13 | .option('-p, --pubsub', 'Start local PubSub Server as well.') 14 | .parse(process.argv); 15 | 16 | if (program.file) { 17 | conf.path = program.file; 18 | } 19 | 20 | if (!program.verbose) { 21 | conf.silent = true; 22 | } 23 | 24 | // load env vars from a .env file 25 | require('dotenv').config(conf); 26 | 27 | if (program.pubsub) { 28 | debug('Starting pubsub server...'); 29 | require('openframe-pubsubserver').start(); 30 | } 31 | 32 | require('../server/server').start(); 33 | -------------------------------------------------------------------------------- /client/favicon.ico: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/OpenframeProject/Openframe-APIServer/f93c3220d0e4e19d71848d178b7e6878b91ec705/client/favicon.ico -------------------------------------------------------------------------------- /client/img/of-logo.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | of-logo 5 | Created with Sketch. 6 | 7 | 8 | 9 | 13 | 14 | 15 | -------------------------------------------------------------------------------- /common/mixins/disableAllMethods.js: -------------------------------------------------------------------------------- 1 | /* 2 | Openframe-APIServer is the server component of Openframe, a platform for displaying digital art. 3 | Copyright (C) 2017 Jonathan Wohl 4 | 5 | This program is free software: you can redistribute it and/or modify 6 | it under the terms of the GNU Affero General Public License as published 7 | by the Free Software Foundation, either version 3 of the License, or 8 | (at your option) any later version. 9 | 10 | This program is distributed in the hope that it will be useful, 11 | but WITHOUT ANY WARRANTY; without even the implied warranty of 12 | MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 13 | GNU Affero General Public License for more details. 14 | 15 | You should have received a copy of the GNU Affero General Public License 16 | along with this program. If not, see . 17 | */ 18 | 19 | 20 | /** 21 | * Based on @ericprieto code https://github.com/strongloop/loopback/issues/651#issuecomment-140879983 22 | * place this file into common/mixins/disableAllMethods.js 23 | * 24 | **/ 25 | 26 | module.exports = function(Model, options) { 27 | if(Model && Model.sharedClass) { 28 | var methodsToExpose = options.expose || []; 29 | var modelName = Model.sharedClass.name; 30 | var methods = Model.sharedClass.methods(); 31 | var relationMethods = []; 32 | var hiddenMethods = []; 33 | try { 34 | Object.keys(Model.definition.settings.relations).forEach(function(relation) { 35 | relationMethods.push({ name: '__findById__' + relation}); 36 | relationMethods.push({ name: '__destroyById__' + relation}); 37 | relationMethods.push({ name: '__updateById__' + relation}); 38 | relationMethods.push({ name: '__exists__' + relation}); 39 | relationMethods.push({ name: '__link__' + relation}); 40 | relationMethods.push({ name: '__get__' + relation}); 41 | relationMethods.push({ name: '__create__' + relation}); 42 | relationMethods.push({ name: '__update__' + relation}); 43 | relationMethods.push({ name: '__destroy__' + relation}); 44 | relationMethods.push({ name: '__unlink__' + relation}); 45 | relationMethods.push({ name: '__count__' + relation}); 46 | relationMethods.push({ name: '__delete__' + relation}); 47 | }); 48 | } catch(err) {} 49 | methods.concat(relationMethods).forEach(function(method) { 50 | var methodName = method.name; 51 | if(methodsToExpose.indexOf(methodName) < 0) { 52 | hiddenMethods.push(methodName); 53 | Model.disableRemoteMethodByName(methodName); 54 | } 55 | }); 56 | // if(hiddenMethods.length > 0) { 57 | // console.log('\nRemote mehtods hidden for', modelName, ':', hiddenMethods.join(', '), '\n'); 58 | // } 59 | } 60 | }; 61 | -------------------------------------------------------------------------------- /common/mixins/isPublicOrOwner.js: -------------------------------------------------------------------------------- 1 | /* 2 | Openframe-APIServer is the server component of Openframe, a platform for displaying digital art. 3 | Copyright (C) 2017 Jonathan Wohl 4 | 5 | This program is free software: you can redistribute it and/or modify 6 | it under the terms of the GNU Affero General Public License as published 7 | by the Free Software Foundation, either version 3 of the License, or 8 | (at your option) any later version. 9 | 10 | This program is distributed in the hope that it will be useful, 11 | but WITHOUT ANY WARRANTY; without even the implied warranty of 12 | MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 13 | GNU Affero General Public License for more details. 14 | 15 | You should have received a copy of the GNU Affero General Public License 16 | along with this program. If not, see . 17 | */ 18 | 19 | // 20 | // IMPORTANT NOTE 21 | // 22 | // As of Loopback 3.0, it's unclear whether or not this works. 23 | // 24 | 25 | var debug = require('debug')('openframe:isPublicOrOwner'); 26 | 27 | /** 28 | * This mixin requires a model to have 'is_public: true' or the current user 29 | * to be the object's owner in order to provide access. 30 | */ 31 | module.exports = function(Model, options) { 32 | 33 | // If the mixin specifies specific methods, add remote hooks for those only, 34 | // otherwise add global hook for all methods 35 | if (options.methods) { 36 | options.methods.forEach((method) => { 37 | Model.afterRemote(method, function(ctx, resultInstance, next) { 38 | modifyResults(ctx, resultInstance, next); 39 | }); 40 | }); 41 | // Used ONLY to find the method name -- not sure where else to look this up? It's 42 | // probably buried in loopback docs somewhere? 43 | // Model.afterRemote('**', function(ctx, resultInstance, next) { 44 | // debug('ctx.methodString', ctx.methodString); 45 | // }); 46 | } else { 47 | Model.afterRemote('**', function(ctx, resultInstance, next) { 48 | modifyResults(ctx, resultInstance, next); 49 | }); 50 | } 51 | 52 | // Modify the results to include only those which are public or owned-by current user 53 | function modifyResults(ctx, resultInstance, next) { 54 | debug('ctx.methodString', ctx.methodString); 55 | 56 | debug('ctx.req.accessToken', ctx.req.accessToken); 57 | 58 | let userId = ctx.req.accessToken && ctx.req.accessToken.userId; 59 | 60 | // if result is true, we want to reject this item 61 | function allowResult(model) { 62 | // debug('model', model.is_public, model.ownerId, userId); 63 | return (model.is_public || (userId && model.ownerId && model.ownerId.toString() === userId.toString())); 64 | } 65 | 66 | if (ctx.result) { 67 | let newResult; 68 | if (Array.isArray(resultInstance)) { 69 | debug('isArray', resultInstance.length); 70 | newResult = []; 71 | ctx.result.forEach(function(result) { 72 | if (allowResult(result)) { 73 | newResult.push(result); 74 | } else { 75 | debug('Rejecting result', result.id); 76 | } 77 | }); 78 | } else if (allowResult(ctx.result)) { 79 | newResult = ctx.result; 80 | } else { 81 | debug('Rejecting result', resultInstance.id); 82 | ctx.res.status(404).render('404'); 83 | } 84 | ctx.result = newResult; 85 | } 86 | 87 | next(); 88 | } 89 | }; 90 | -------------------------------------------------------------------------------- /common/mixins/timestamp.js: -------------------------------------------------------------------------------- 1 | /* 2 | Openframe-APIServer is the server component of Openframe, a platform for displaying digital art. 3 | Copyright (C) 2017 Jonathan Wohl 4 | 5 | This program is free software: you can redistribute it and/or modify 6 | it under the terms of the GNU Affero General Public License as published 7 | by the Free Software Foundation, either version 3 of the License, or 8 | (at your option) any later version. 9 | 10 | This program is distributed in the hope that it will be useful, 11 | but WITHOUT ANY WARRANTY; without even the implied warranty of 12 | MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 13 | GNU Affero General Public License for more details. 14 | 15 | You should have received a copy of the GNU Affero General Public License 16 | along with this program. If not, see . 17 | */ 18 | 19 | module.exports = function(Model, options) { 20 | // Model is the model class 21 | // options is an object containing the config properties from model definition 22 | Model.defineProperty('created', { 23 | type: Date, 24 | default: '$now' 25 | }); 26 | Model.defineProperty('modified', { 27 | type: Date, 28 | default: '$now' 29 | }); 30 | }; 31 | -------------------------------------------------------------------------------- /common/models/artwork.js: -------------------------------------------------------------------------------- 1 | var debug = require('debug')('openframe:model:Artwork'); 2 | 3 | module.exports = function(Artwork) { 4 | Artwork.disableRemoteMethodByName('createChangeStream'); 5 | Artwork.disableRemoteMethodByName('create'); 6 | }; 7 | -------------------------------------------------------------------------------- /common/models/artwork.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "Artwork", 3 | "plural": "artwork", 4 | "base": "PersistedModel", 5 | "idInjection": true, 6 | "options": { 7 | "validateUpsert": true 8 | }, 9 | "mixins": { 10 | "Timestamp": {}, 11 | "IsPublicOrOwner": {} 12 | }, 13 | "scopes": { 14 | "stream": { 15 | "where": { 16 | "is_public": true 17 | }, 18 | "order": "created DESC" 19 | } 20 | }, 21 | "properties": { 22 | "title": { 23 | "type": "string", 24 | "required": true 25 | }, 26 | "description": { 27 | "type": "string" 28 | }, 29 | "is_public": { 30 | "type": "boolean", 31 | "required": true, 32 | "default": false 33 | }, 34 | "url": { 35 | "type": "string", 36 | "required": true 37 | }, 38 | "thumb_url": { 39 | "type": "string" 40 | }, 41 | "author_name": { 42 | "type": "string" 43 | }, 44 | "required_extensions": { 45 | "type": "object", 46 | "required": true, 47 | "default": {} 48 | }, 49 | "format": { 50 | "type": "string", 51 | "required": true 52 | }, 53 | "options": { 54 | "type": "object", 55 | "require": false 56 | } 57 | }, 58 | "validations": [], 59 | "relations": { 60 | "owner": { 61 | "type": "belongsTo", 62 | "model": "OpenframeUser", 63 | "foreignKey": "ownerId" 64 | }, 65 | "purchased_by": { 66 | "type": "hasAndBelongsToMany", 67 | "model": "OpenframeUser" 68 | }, 69 | "liked_by": { 70 | "type": "hasAndBelongsToMany", 71 | "model": "OpenframeUser", 72 | "foreignKey": "" 73 | }, 74 | "collections": { 75 | "type": "hasAndBelongsToMany", 76 | "model": "Collection", 77 | "foreignKey": "" 78 | }, 79 | "currently_in": { 80 | "type": "hasMany", 81 | "model": "Frame", 82 | "foreignKey": "currentArtworkId" 83 | } 84 | }, 85 | "acls": [ 86 | { 87 | "accessType": "*", 88 | "principalType": "ROLE", 89 | "principalId": "$everyone", 90 | "permission": "DENY" 91 | }, 92 | { 93 | "accessType": "READ", 94 | "principalType": "ROLE", 95 | "principalId": "$everyone", 96 | "permission": "ALLOW" 97 | }, 98 | { 99 | "accessType": "WRITE", 100 | "principalType": "ROLE", 101 | "principalId": "$owner", 102 | "permission": "ALLOW" 103 | }, 104 | { 105 | "accessType": "EXECUTE", 106 | "principalType": "ROLE", 107 | "principalId": "$authenticated", 108 | "permission": "ALLOW", 109 | "property": "create" 110 | }, 111 | { 112 | "accessType": "*", 113 | "principalType": "ROLE", 114 | "principalId": "admin", 115 | "permission": "ALLOW" 116 | } 117 | ], 118 | "methods": {} 119 | } 120 | -------------------------------------------------------------------------------- /common/models/channel.js: -------------------------------------------------------------------------------- 1 | var loopback = require('loopback'); 2 | 3 | module.exports = function(Channel) { 4 | Channel.disableRemoteMethodByName('createChangeStream'); 5 | 6 | /** 7 | * Ensure that only artworks that are public or are owned by the current user can be accessed. 8 | */ 9 | // Channel.observe('access', function(ctx, next) { 10 | // var context = loopback.getCurrentContext(), 11 | // req = context && context.active ? context.active.http.req : null, 12 | // user = req ? req.user : null; 13 | 14 | // ctx.query.where = ctx.query.where || {}; 15 | 16 | // if (user) { 17 | // ctx.query.where.or = [ 18 | // {is_public: true}, 19 | // {ownerId: user.id} 20 | // ]; 21 | // } else { 22 | // ctx.query.where.is_public = true; 23 | // } 24 | 25 | // next(); 26 | // }); 27 | }; 28 | -------------------------------------------------------------------------------- /common/models/channel.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "Channel", 3 | "plural": "channels", 4 | "base": "PersistedModel", 5 | "idInjection": true, 6 | "options": { 7 | "validateUpsert": true 8 | }, 9 | "mixins": { 10 | "Timestamp": {}, 11 | "IsPublicOrOwner": {} 12 | }, 13 | "properties": { 14 | "name": { 15 | "type": "string", 16 | "required": true 17 | }, 18 | "description": { 19 | "type": "string", 20 | "required": false 21 | }, 22 | "is_public": { 23 | "type": "boolean", 24 | "required": true, 25 | "default": true 26 | }, 27 | "thumb_url": { 28 | "type": "string" 29 | } 30 | }, 31 | "validations": [], 32 | "relations": { 33 | "owner": { 34 | "type": "belongsTo", 35 | "model": "OpenframeUser", 36 | "foreignKey": "ownerId" 37 | }, 38 | "managers": { 39 | "type": "hasAndBelongsToMany", 40 | "model": "OpenframeUser", 41 | "foreignKey": "managersId", 42 | "through": "ChannelManager" 43 | }, 44 | "subscribers": { 45 | "type": "hasAndBelongsToMany", 46 | "model": "OpenframeUser", 47 | "foreignKey": "subscribersId", 48 | "through": "ChannelSubscriber" 49 | }, 50 | "current_artwork": { 51 | "type": "embedsOne", 52 | "model": "Artwork", 53 | "property": "current_artwork" 54 | } 55 | }, 56 | "acls": [ 57 | { 58 | "accessType": "*", 59 | "principalType": "ROLE", 60 | "principalId": "$everyone", 61 | "permission": "DENY" 62 | }, 63 | { 64 | "accessType": "READ", 65 | "principalType": "ROLE", 66 | "principalId": "$everyone", 67 | "permission": "ALLOW" 68 | }, 69 | { 70 | "accessType": "WRITE", 71 | "principalType": "ROLE", 72 | "principalId": "$owner", 73 | "permission": "ALLOW" 74 | }, 75 | { 76 | "accessType": "*", 77 | "principalType": "ROLE", 78 | "principalId": "admin", 79 | "permission": "ALLOW" 80 | } 81 | ], 82 | "methods": {} 83 | } 84 | -------------------------------------------------------------------------------- /common/models/collection.js: -------------------------------------------------------------------------------- 1 | module.exports = function(Collection) { 2 | Collection.disableRemoteMethodByName('disableRemoteMethodByName'); 3 | }; 4 | -------------------------------------------------------------------------------- /common/models/collection.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "Collection", 3 | "plural": "collections", 4 | "base": "PersistedModel", 5 | "idInjection": true, 6 | "options": { 7 | "validateUpsert": true 8 | }, 9 | "mixins": { 10 | "Timestamp": {} 11 | }, 12 | "properties": { 13 | "name": { 14 | "type": "string", 15 | "required": false 16 | }, 17 | "description": { 18 | "type": "string", 19 | "required": false 20 | }, 21 | "is_public": { 22 | "type": "boolean", 23 | "required": true, 24 | "default": true 25 | }, 26 | "thumb_url": { 27 | "type": "string" 28 | } 29 | }, 30 | "validations": [], 31 | "relations": { 32 | "owner": { 33 | "type": "belongsTo", 34 | "model": "OpenframeUser", 35 | "foreignKey": "ownerId" 36 | }, 37 | "current_artwork": { 38 | "type": "embedsOne", 39 | "model": "Artwork", 40 | "property": "current_artwork" 41 | }, 42 | "artwork": { 43 | "type": "hasAndBelongsToMany", 44 | "model": "Artwork" 45 | } 46 | }, 47 | "acls": [ 48 | { 49 | "accessType": "*", 50 | "principalType": "ROLE", 51 | "principalId": "$everyone", 52 | "permission": "DENY" 53 | }, 54 | { 55 | "accessType": "READ", 56 | "principalType": "ROLE", 57 | "principalId": "$everyone", 58 | "permission": "ALLOW" 59 | }, 60 | { 61 | "accessType": "EXECUTE", 62 | "principalType": "ROLE", 63 | "principalId": "$authenticated", 64 | "permission": "ALLOW", 65 | "property": "create" 66 | }, 67 | { 68 | "accessType": "WRITE", 69 | "principalType": "ROLE", 70 | "principalId": "$owner", 71 | "permission": "ALLOW" 72 | }, 73 | { 74 | "accessType": "*", 75 | "principalType": "ROLE", 76 | "principalId": "admin", 77 | "permission": "ALLOW" 78 | } 79 | ], 80 | "methods": {} 81 | } 82 | -------------------------------------------------------------------------------- /common/models/frame.js: -------------------------------------------------------------------------------- 1 | var debug = require('debug')('openframe:model:Frame'); 2 | 3 | module.exports = function(Frame) { 4 | Frame.disableRemoteMethodByName('createChangeStream'); 5 | 6 | // Remove sensitive data from Artworks being returned 7 | // in public frames 8 | // Frame.afterRemote('**', function(ctx, resultInstance, next) { 9 | // debug('ctx.methodString', ctx.methodString); 10 | 11 | // function updateResult(result) { 12 | // if (result.current_artwork) { 13 | // let newArtwork = { 14 | // title: result.current_artwork.title, 15 | // author_name: result.current_artwork.author_name 16 | // }; 17 | // if (result.current_artwork.is_public) { 18 | // newArtwork.id = result.current_artwork.id; 19 | // } 20 | // result.current_artwork(newArtwork); 21 | // // debug(result.current_artwork); 22 | // } 23 | // } 24 | // if (ctx.result) { 25 | // if (Array.isArray(resultInstance)) { 26 | // debug('isArray', resultInstance.length); 27 | // ctx.result.forEach(function(result) { 28 | // updateResult(result); 29 | // }); 30 | // } else { 31 | // updateResult(ctx.result); 32 | // } 33 | // } 34 | // next(); 35 | // }); 36 | 37 | // Never save 'current_artwork' object into DB -- it comes from relation, via currentArtworkId 38 | // TODO: I think this is a(nother) loopback bug, since with strict on we should be enforcing 39 | // properties, but since this property is the name of a relation it's allowing it to be saved (?) 40 | Frame.observe('before save', function(ctx, next) { 41 | debug('before save', typeof ctx.instance); 42 | if (ctx.instance) { 43 | ctx.instance.unsetAttribute('current_artwork'); 44 | } else { 45 | delete ctx.data.current_artwork; 46 | } 47 | debug('before save - MODIFIED', ctx.instance); 48 | next(); 49 | }); 50 | 51 | // whenever a Frame model is saved, broadcast an update event 52 | Frame.observe('after save', function(ctx, next) { 53 | if (ctx.instance && Frame.app.pubsub) { 54 | debug('Saved %s %s', ctx.Model.modelName, ctx.instance.id); 55 | if (ctx.isNewInstance) { 56 | debug('New Frame, publishing: /user/' + ctx.instance.ownerId + '/frame/new'); 57 | Frame.app.pubsub.publish('/user/' + ctx.instance.ownerId + '/frame/new', ctx.instance.id); 58 | } else { 59 | debug('Existing Frame, publishing: /frame/' + ctx.instance.id + '/db_updated'); 60 | // debug(ctx.instance); 61 | Frame.findById(ctx.instance.id, { include: 'current_artwork' }, function(err, frame) { 62 | debug(err, frame); 63 | Frame.app.pubsub.publish('/frame/' + frame.id + '/db_updated', frame); 64 | }); 65 | } 66 | } 67 | next(); 68 | }); 69 | 70 | // Ouch. Ow. Yowsers. Serisously? 71 | function removeManagers(frame, managers) { 72 | return new Promise((resolve, reject) => { 73 | frame.managers(function(err, current_managers) { 74 | debug(current_managers); 75 | if (current_managers.length) { 76 | var count = 0, 77 | total = current_managers.length; 78 | current_managers.forEach(function(cur_man) { 79 | debug(cur_man); 80 | if (managers.indexOf(cur_man.username) === -1) { 81 | debug('removing %s', cur_man.username); 82 | frame.managers.remove(cur_man, function(err) { 83 | if (err) debug(err); 84 | count++; 85 | if (count === total) { 86 | // all who are no longer present in the new list have been removed 87 | resolve(); 88 | } 89 | }); 90 | } else { 91 | count++; 92 | if (count === total) { 93 | // all who are no longer present in the new list have been removed 94 | resolve(); 95 | } 96 | } 97 | }); 98 | } else { 99 | // there are no current managers 100 | resolve(); 101 | } 102 | }); 103 | }); 104 | } 105 | 106 | // Painful painful painful -- so gross. Why loopback, why!? 107 | function addManagers(frame, managers) { 108 | var OpenframeUser = Frame.app.models.OpenframeUser; 109 | 110 | return new Promise((resolve, reject) => { 111 | OpenframeUser.find({ where: { username: { inq: managers }}}, function(err, users) { 112 | if (err) { 113 | debug(err); 114 | } 115 | var count = 0, 116 | total = users.length; 117 | if (total === 0) { 118 | // no managers found by username, return frame including current managers 119 | Frame.findById(frame.id, {include: 'managers'}, function(err, frame) { 120 | debug(err, frame); 121 | resolve(frame); 122 | }); 123 | } else { 124 | // managers found by username, add them to frame, then 125 | // return frame including current managers 126 | // XXX: Unfortunately loopback doesn't seem to provide a way to batch 127 | // update hasAndBelongsToMany relationships :/ 128 | users.forEach(function(user) { 129 | frame.managers.add(user, function(err) { 130 | count++; 131 | if (count === total) { 132 | Frame.findById(frame.id, {include: 'managers'}, function(err, frame) { 133 | debug(err, frame); 134 | resolve(frame); 135 | }); 136 | } 137 | }); 138 | }); 139 | } 140 | 141 | }); 142 | }); 143 | } 144 | 145 | // Update managers by username 146 | // 147 | // XXX: This is incredibly ugly. Loopback doesn't provide a good way to update 148 | // this type of relationship all in one go, which makes it a huge messy pain. Given 149 | // time, I may fix this. 150 | Frame.prototype.update_managers_by_username = function(managers, cb) { 151 | debug(managers); 152 | var self = this; 153 | 154 | removeManagers(self, managers) 155 | .then(function() { 156 | addManagers(self, managers) 157 | .then(function(frame) { 158 | cb(null, frame); 159 | }) 160 | .catch(debug); 161 | }).catch(debug); 162 | }; 163 | 164 | // Expose update_managers_by_username remote method 165 | Frame.remoteMethod( 166 | 'prototype.update_managers_by_username', { 167 | description: 'Add a related item by username for managers.', 168 | accepts: { 169 | arg: 'managers', 170 | type: 'array', 171 | http: { 172 | source: 'body' 173 | } 174 | }, 175 | http: { 176 | verb: 'put', 177 | path: '/managers/by_username' 178 | }, 179 | returns: { 180 | arg: 'frame', 181 | type: 'Object' 182 | } 183 | } 184 | ); 185 | 186 | 187 | /** 188 | * Update the current artwork by artwork ID 189 | * @param {String} currentArtworkId 190 | * @param {Function} callback 191 | */ 192 | Frame.prototype.update_current_artwork = function(currentArtworkId, cb) { 193 | debug('update_current_artwork', currentArtworkId); 194 | var self = this; 195 | self.updateAttribute('currentArtworkId', currentArtworkId, function(err, instance) { 196 | cb(err, instance); 197 | }); 198 | }; 199 | 200 | Frame.remoteMethod( 201 | 'prototype.update_current_artwork', { 202 | description: 'Set the current artwork for this frame', 203 | accepts: { 204 | arg: 'currentArtworkId', 205 | type: 'any', 206 | required: true, 207 | http: { 208 | source: 'path' 209 | } 210 | }, 211 | http: { 212 | verb: 'put', 213 | path: '/current_artwork/:currentArtworkId' 214 | }, 215 | returns: { 216 | arg: 'frame', 217 | type: 'Object' 218 | } 219 | } 220 | ); 221 | 222 | /** 223 | * Override toJSON in order to remove inclusion of email address for users that are 224 | * not the currently logged-in user. 225 | * 226 | * @return {Object} Plain JS Object which will be transformed to JSON for output. 227 | */ 228 | // Frame.prototype.toJSON = function() { 229 | // // TODO: this seems awfully fragile... not very clear when context is available 230 | // var ctx = loopback.getCurrentContext(), 231 | // user = ctx.get('currentUser'), 232 | // userId = user && user.id, 233 | // obj = this.toObject(false, true, false); 234 | 235 | // debug('FRAME toJSON', userId, obj); 236 | 237 | // // Remove email from managers 238 | // if (obj.managers && obj.managers.length) { 239 | // obj.managers.forEach((manager) => { 240 | // delete manager.email; 241 | // }); 242 | // } 243 | 244 | // // Remove email from owner unless it's the currently logged in user. 245 | // if (obj.owner && userId !== obj.owner.id) { 246 | // delete obj.owner.email; 247 | // } 248 | 249 | // return obj; 250 | // }; 251 | }; 252 | 253 | -------------------------------------------------------------------------------- /common/models/frame.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "Frame", 3 | "plural": "frames", 4 | "base": "PersistedModel", 5 | "idInjection": true, 6 | "strict": true, 7 | "options": { 8 | "validateUpsert": true 9 | }, 10 | "mixins": { 11 | "Timestamp": {}, 12 | "IsPublicOrOwner": { 13 | "methods": [ 14 | "find", 15 | "findOne", 16 | "findById", 17 | "prototype.__get__current_artwork" 18 | ] 19 | } 20 | }, 21 | "scope": { 22 | "order": "created", 23 | "include": { 24 | "current_artwork": true, 25 | "managers": true, 26 | "owner": true 27 | } 28 | }, 29 | "properties": { 30 | "name": { 31 | "type": "string", 32 | "required": true 33 | }, 34 | "display_name": { 35 | "type": "string" 36 | }, 37 | "description": { 38 | "type": "string" 39 | }, 40 | "connected": { 41 | "type": "boolean" 42 | }, 43 | "settings": { 44 | "type": "object", 45 | "required": true, 46 | "default": {} 47 | }, 48 | "is_public": { 49 | "type": "boolean", 50 | "required": true, 51 | "default": false 52 | }, 53 | "extensions": { 54 | "type": "object", 55 | "required": true, 56 | "default": { 57 | "openframe-image": "*", 58 | "openframe-glslviewer": "*", 59 | "openframe-website": "*", 60 | "openframe-video": "*" 61 | } 62 | }, 63 | "formats": { 64 | "type": "object", 65 | "require": true, 66 | "default": {} 67 | } 68 | }, 69 | "validations": [], 70 | "relations": { 71 | "owner": { 72 | "type": "belongsTo", 73 | "model": "OpenframeUser", 74 | "foreignKey": "ownerId" 75 | }, 76 | "managers": { 77 | "type": "hasAndBelongsToMany", 78 | "model": "OpenframeUser" 79 | }, 80 | "current_artwork": { 81 | "type": "belongsTo", 82 | "model": "Artwork", 83 | "foreignKey": "currentArtworkId" 84 | } 85 | }, 86 | "acls": [ 87 | { 88 | "accessType": "*", 89 | "principalType": "ROLE", 90 | "principalId": "$everyone", 91 | "permission": "DENY" 92 | }, 93 | { 94 | "accessType": "READ", 95 | "principalType": "ROLE", 96 | "principalId": "$everyone", 97 | "permission": "ALLOW" 98 | }, 99 | { 100 | "accessType": "WRITE", 101 | "principalType": "ROLE", 102 | "principalId": "$owner", 103 | "permission": "ALLOW" 104 | }, 105 | { 106 | "accessType": "EXECUTE", 107 | "principalType": "ROLE", 108 | "principalId": "$frameManager", 109 | "permission": "ALLOW", 110 | "property": "update_current_artwork" 111 | }, 112 | { 113 | "accessType": "EXECUTE", 114 | "principalType": "ROLE", 115 | "principalId": "$frameManager", 116 | "permission": "ALLOW", 117 | "property": "update_managers_by_username" 118 | }, 119 | { 120 | "accessType": "*", 121 | "principalType": "ROLE", 122 | "principalId": "admin", 123 | "permission": "ALLOW" 124 | } 125 | ], 126 | "methods": {} 127 | } 128 | -------------------------------------------------------------------------------- /common/models/openframe-user.js: -------------------------------------------------------------------------------- 1 | var loopback = require('loopback'), 2 | // loopbackCtx = require('loopback-context'), 3 | path = require('path'), 4 | debug = require('debug')('openframe:model:OpenframeUser'); 5 | 6 | 7 | module.exports = function(OpenframeUser) { 8 | 9 | 10 | /** 11 | * Disable specific default remote methods 12 | */ 13 | 14 | OpenframeUser.disableRemoteMethodByName('createChangeStream', true); 15 | OpenframeUser.disableRemoteMethodByName('updateAll', true); 16 | 17 | OpenframeUser.disableRemoteMethodByName('__count__accessTokens', false); 18 | OpenframeUser.disableRemoteMethodByName('__create__accessTokens', false); 19 | OpenframeUser.disableRemoteMethodByName('__delete__accessTokens', false); 20 | OpenframeUser.disableRemoteMethodByName('__destroyById__accessTokens', false); 21 | OpenframeUser.disableRemoteMethodByName('__findById__accessTokens', false); 22 | OpenframeUser.disableRemoteMethodByName('__get__accessTokens', false); 23 | OpenframeUser.disableRemoteMethodByName('__updateById__accessTokens', false); 24 | 25 | OpenframeUser.afterRemote('create', function(context, OpenframeUserInstance, next) { 26 | debug('> OpenframeUser.afterRemote triggered'); 27 | 28 | var options = { 29 | type: 'email', 30 | to: OpenframeUserInstance.email, 31 | from: 'Openframe ', 32 | subject: 'Welcome to Openframe!', 33 | template: path.resolve(__dirname, '../../server/views/email-templates/verify.ejs'), 34 | redirect: OpenframeUser.app.get('webapp_url') + '/verified', 35 | verifyHref: OpenframeUser.app.get('api_url') + 36 | OpenframeUser.http.path + 37 | OpenframeUser.sharedClass.findMethodByName('confirm').http.path + 38 | '?uid=' + OpenframeUserInstance.id + 39 | '&redirect=' + OpenframeUser.app.get('webapp_url') + 40 | '/verified' 41 | }; 42 | 43 | OpenframeUserInstance.verify(options, function(err, response) { 44 | if (err) return next(err); 45 | 46 | debug('> verification email sent:', response); 47 | 48 | context.res.send(JSON.stringify({ 49 | success: true 50 | })); 51 | }); 52 | }); 53 | 54 | 55 | OpenframeUser.on('resetPasswordRequest', function(info) { 56 | debug(info.email); // the email of the requested user 57 | debug(info.accessToken.id); // the temp access token to allow password reset 58 | var url = OpenframeUser.app.get('webapp_url') + '/reset-password/' + info.accessToken.id; 59 | var renderer = loopback.template(path.resolve(__dirname, '../../server/views/email-templates/reset-password.ejs')); 60 | var html_body = renderer({ 61 | reset_link: url 62 | }); 63 | OpenframeUser.app.models.Email.send({ 64 | to: info.email, 65 | from: 'Openframe ', 66 | subject: 'Openframe password reset', 67 | html: html_body 68 | }, function(err) { 69 | if (err) return debug('> error sending password reset email'); 70 | debug('> sending password reset email to:', info.email); 71 | }); 72 | }); 73 | 74 | 75 | /** 76 | * CUSTOM remote methods 77 | */ 78 | 79 | 80 | // Get configuration via REST endpoint 81 | OpenframeUser.config = function(cb) { 82 | var config = { 83 | pubsub_url: OpenframeUser.app.get('ps_url') 84 | }; 85 | cb(null, config); 86 | }; 87 | 88 | // Expose all_frames remote method 89 | OpenframeUser.remoteMethod( 90 | 'config', { 91 | description: 'Get some general config info from the API server.', 92 | accepts: [], 93 | http: { 94 | verb: 'get', 95 | }, 96 | returns: { 97 | arg: 'config', 98 | type: 'Object' 99 | } 100 | } 101 | ); 102 | 103 | 104 | 105 | 106 | 107 | // Get all frames, owned and managed 108 | OpenframeUser.prototype.all_frames = function(cb) { 109 | var self = this, 110 | allFrames; 111 | self.owned_frames({ 112 | include: [ 113 | 'managers', 114 | 'current_artwork', 115 | 'owner' 116 | ] 117 | }, function(err, _ownFrames) { 118 | var ownFrames = _ownFrames || []; 119 | self.managed_frames({ 120 | include: ['managers', 'current_artwork', 'owner'], 121 | where: { 122 | ownerId: { 123 | neq: self.id 124 | } 125 | } 126 | }, function(err, _manFrames) { 127 | var manFrames = _manFrames || []; 128 | allFrames = ownFrames.concat(manFrames); 129 | cb(null, allFrames); 130 | }); 131 | }); 132 | }; 133 | 134 | OpenframeUser.remoteMethod( 135 | 'prototype.all_frames', { 136 | description: 'Get all frames (owned and managed) by this user.', 137 | accepts: [], 138 | http: { 139 | verb: 'get', 140 | path: '/all_frames' 141 | }, 142 | returns: { 143 | arg: 'frames', 144 | type: 'Array' 145 | } 146 | } 147 | ); 148 | }; 149 | -------------------------------------------------------------------------------- /common/models/openframe-user.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "OpenframeUser", 3 | "plural": "users", 4 | "base": "User", 5 | "idInjection": true, 6 | "options": { 7 | "validateUpsert": true 8 | }, 9 | "mixins": { 10 | "Timestamp": {}, 11 | "IsPublicOrOwner": { 12 | "methods": [ 13 | "prototype.__get__created_artwork", 14 | "prototype.__findById__created_artwork", 15 | "prototype.__findOne__created_artwork", 16 | "prototype.__count__created_artwork", 17 | "prototype.__get__liked_artwork", 18 | "prototype.__findById__liked_artwork", 19 | "prototype.__findOne__liked_artwork", 20 | "prototype.__count__liked_artwork", 21 | "prototype.__get__purchased_artwork", 22 | "prototype.__findById__purchased_artwork", 23 | "prototype.__findOne__purchased_artwork", 24 | "prototype.__count__purchased_artwork" 25 | ] 26 | } 27 | }, 28 | "hidden": [ 29 | "emailVerified", 30 | "email", 31 | "password", 32 | "settings" 33 | ], 34 | "protected": [ 35 | "email", 36 | "created", 37 | "modified", 38 | "settings" 39 | ], 40 | "scope": { 41 | "order": "created" 42 | }, 43 | "properties": { 44 | "bio": { 45 | "type": "string" 46 | }, 47 | "website": { 48 | "type": "string" 49 | }, 50 | "twitter": { 51 | "type": "string" 52 | }, 53 | "full_name": { 54 | "type": "string" 55 | }, 56 | "settings": { 57 | "type": "object", 58 | "default": { 59 | "enable_notifications": true 60 | } 61 | } 62 | }, 63 | "validations": [], 64 | "relations": { 65 | "owned_frames": { 66 | "type": "hasMany", 67 | "model": "Frame", 68 | "foreignKey": "ownerId" 69 | }, 70 | "managed_frames": { 71 | "type": "hasAndBelongsToMany", 72 | "model": "Frame" 73 | }, 74 | "owned_channels": { 75 | "type": "hasMany", 76 | "model": "Channel", 77 | "foreignKey": "ownerId" 78 | }, 79 | "managed_channels": { 80 | "type": "hasAndBelongsToMany", 81 | "model": "Channel", 82 | "foreignKey": "managersId", 83 | "through": "ChannelManager" 84 | }, 85 | "subscribed_channels": { 86 | "type": "hasAndBelongsToMany", 87 | "model": "Channel", 88 | "foreignKey": "subscribersId", 89 | "through": "ChannelSubscriber" 90 | }, 91 | "collections": { 92 | "type": "hasMany", 93 | "model": "Collection", 94 | "foreignKey": "ownerId" 95 | }, 96 | "subscribed_collections": { 97 | "type": "hasAndBelongsToMany", 98 | "model": "Collection", 99 | "foreignKey": "subscribersId", 100 | "through": "CollectionSubscriber" 101 | }, 102 | "created_artwork": { 103 | "type": "hasMany", 104 | "model": "Artwork", 105 | "foreignKey": "ownerId" 106 | }, 107 | "purchased_artwork": { 108 | "type": "hasAndBelongsToMany", 109 | "model": "Artwork", 110 | "foreignKey": "purchasedById" 111 | }, 112 | "liked_artwork": { 113 | "type": "hasAndBelongsToMany", 114 | "model": "Artwork", 115 | "foreignKey": "likedById" 116 | }, 117 | "identities": { 118 | "type": "hasMany", 119 | "model": "OpenframeUserIdentity", 120 | "foreignKey": "userId" 121 | }, 122 | "credentials": { 123 | "type": "hasMany", 124 | "model": "OpenframeUserCredential", 125 | "foreignKey": "userId" 126 | } 127 | }, 128 | "acls": [ 129 | { 130 | "accessType": "*", 131 | "principalType": "ROLE", 132 | "principalId": "$everyone", 133 | "permission": "DENY" 134 | }, 135 | { 136 | "accessType": "READ", 137 | "principalType": "ROLE", 138 | "principalId": "$everyone", 139 | "permission": "ALLOW", 140 | "property": [ 141 | "find", 142 | "findById", 143 | "findOne", 144 | "config", 145 | "__get__created_artwork" 146 | ] 147 | }, 148 | { 149 | "accessType": "READ", 150 | "principalType": "ROLE", 151 | "principalId": "$owner", 152 | "permission": "ALLOW" 153 | }, 154 | { 155 | "accessType": "WRITE", 156 | "principalType": "ROLE", 157 | "principalId": "$owner", 158 | "permission": "ALLOW" 159 | }, 160 | { 161 | "accessType": "EXECUTE", 162 | "principalType": "ROLE", 163 | "principalId": "$owner", 164 | "permission": "ALLOW", 165 | "property": [ 166 | "all_frames" 167 | ] 168 | }, 169 | { 170 | "accessType": "*", 171 | "principalType": "ROLE", 172 | "principalId": "admin", 173 | "permission": "ALLOW" 174 | } 175 | ], 176 | "methods": {} 177 | } 178 | -------------------------------------------------------------------------------- /migrations/README.md: -------------------------------------------------------------------------------- 1 | # Migrations 2 | 3 | ### Export data 4 | `mongodump -u [user] -p [pass] --db ofsl` 5 | 6 | ### Import data 7 | 8 | `mongorestore dump -u [user] -p [pass]` 9 | 10 | ### Run migration script 11 | 12 | `> cd migrations` 13 | `> mongo` 14 | `mongo> use ofsl` 15 | `mongo> load('new-data-model.js') -------------------------------------------------------------------------------- /migrations/new-data-model.js: -------------------------------------------------------------------------------- 1 | // 2 | // FRAMES 3 | // 4 | 5 | // Rename plugins to extensions 6 | db.Frame.update({}, { $rename: { 'plugins': 'extensions'}}, {multi: 1}); 7 | 8 | // Replace embedded current artwork with a relationship 9 | db.Frame.find().snapshot().forEach( 10 | function (elem) { 11 | if (elem._current_artwork) { 12 | db.Frame.update( 13 | { 14 | _id: elem._id 15 | }, 16 | { 17 | $set: { 18 | currentArtworkId: elem._current_artwork.id 19 | }, 20 | $unset: { 21 | _current_artwork: 1, 22 | current_artwork: 1 23 | } 24 | } 25 | ); 26 | } 27 | } 28 | ); 29 | 30 | // Rename plugins to required_extensions 31 | db.Artwork.update({}, { $rename: {'plugins': 'required_extensions', 'config': 'settings'}, $unset: {aspect_mode: 1, format_other: 1, passwordConfirm: 1}}, {multi: 1}); 32 | 33 | // Use https for artworks on BOS 34 | db.Artwork.find({url: /http:\/\/thebookofshaders\.com.*/}).forEach(function(e,i) { 35 | e.url=e.url.replace("http://","https://"); 36 | db.Artwork.save(e); 37 | }); 38 | 39 | // Use https for preview images on BOS 40 | db.Artwork.find({thumb_url: /http:\/\/thebookofshaders\.com.*/}).forEach(function(e,i) { 41 | e.thumb_url=e.thumb_url.replace("http://","https://"); 42 | db.Artwork.save(e); 43 | }); 44 | 45 | // Make sure existing users are marked as 'verified' 46 | db.OpenframeUser.update({}, {$set: {emailVerified: true}}, {multi: 1}); -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "openframe-apiserver", 3 | "version": "0.4.2", 4 | "description": "The Openframe API Server.", 5 | "main": "server/server.js", 6 | "scripts": { 7 | "start": "./bin/cli.js -p", 8 | "start:live": "nodemon ./bin/cli.js -p", 9 | "start-api": "./bin/cli.js", 10 | "start-pubsub": "node ./node_modules/openframe-pubsubserver/start", 11 | "pretest": "eslint .", 12 | "patch-release": "npm version patch && npm publish && git push --follow-tags" 13 | }, 14 | "bin": { 15 | "openframe-apiserver": "./bin/cli.js" 16 | }, 17 | "author": { 18 | "name": "Jonathan Wohl", 19 | "url": "http://jonathanwohl.com" 20 | }, 21 | "contributors": [ 22 | { 23 | "name": "Jonathan Wohl", 24 | "url": "http://jonathanwohl.com" 25 | }, 26 | { 27 | "name": "Ishac Bertran", 28 | "url": "http://ishacbertran.com" 29 | } 30 | ], 31 | "license": "AGPL-3.0+", 32 | "dependencies": { 33 | "bcrypt": "^0.8.5", 34 | "bluebird": "^3.3.3", 35 | "body-parser": "^1.15.0", 36 | "bootstrap": "^3.3.6", 37 | "commander": "^2.9.0", 38 | "compression": "^1.0.3", 39 | "cors": "^2.5.2", 40 | "debug": "^2.2.0", 41 | "dotenv": "^2.0.0", 42 | "ejs": "^2.4.1", 43 | "faye": "^1.1.2", 44 | "image-zoom": "^1.1.1", 45 | "jquery": "^2.2.0", 46 | "kerberos": "0.0.18", 47 | "lodash": "^4.5.1", 48 | "loopback": "^3.0.0", 49 | "loopback-boot": "^2.6.5", 50 | "loopback-component-explorer": "^4.0.0", 51 | "loopback-connector-mongodb": "^3.0.0", 52 | "openframe-pubsubserver": "^0.1.5", 53 | "serve-favicon": "^2.0.1", 54 | "strong-error-handler": "^1.0.1", 55 | "the-big-username-blacklist": "^1.3.1", 56 | "underscore": "^1.8.3" 57 | }, 58 | "devDependencies": { 59 | "eslint": "^2.0.0", 60 | "nodemon": "^1.9.2" 61 | }, 62 | "keywords": [ 63 | "openframe" 64 | ], 65 | "repository": { 66 | "type": "git", 67 | "url": "https://github.com/OpenframeProject/Openframe-APIServer" 68 | } 69 | } 70 | -------------------------------------------------------------------------------- /server/boot/acl.js: -------------------------------------------------------------------------------- 1 | /* 2 | Openframe-APIServer is the server component of Openframe, a platform for displaying digital art. 3 | Copyright (C) 2017 Jonathan Wohl 4 | 5 | This program is free software: you can redistribute it and/or modify 6 | it under the terms of the GNU Affero General Public License as published 7 | by the Free Software Foundation, either version 3 of the License, or 8 | (at your option) any later version. 9 | 10 | This program is distributed in the hope that it will be useful, 11 | but WITHOUT ANY WARRANTY; without even the implied warranty of 12 | MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 13 | GNU Affero General Public License for more details. 14 | 15 | You should have received a copy of the GNU Affero General Public License 16 | along with this program. If not, see . 17 | */ 18 | 19 | var debug = require('debug')('openframe:acl'); 20 | 21 | /** 22 | * Add custom dynamic ACL roles 23 | * 24 | * $frameManager - current user is a manager of the frame being accessed 25 | * 26 | */ 27 | module.exports = function(app) { 28 | var Role = app.models.Role; 29 | 30 | Role.registerResolver('$frameManager', function(role, context, cb) { 31 | 32 | debug('$frameManager'); 33 | 34 | function reject(err) { 35 | debug('reject:', err); 36 | if (err) { 37 | return cb(err); 38 | } 39 | cb(null, false); 40 | } 41 | 42 | // $frameManager is only applicabale to Frame models 43 | if (context.modelName !== 'Frame') { 44 | // the target model is not a Frame 45 | return reject(); 46 | } 47 | 48 | // do not allow anonymous users 49 | var userId = context.accessToken.userId; 50 | if (!userId) { 51 | return reject(); 52 | } 53 | 54 | debug('userId', userId); 55 | 56 | // get current frame 57 | context.model.findById(context.modelId, {include:{managers: true, owner: true}}, function(err, frame) { 58 | if (err || !frame) { 59 | return reject(err); 60 | } 61 | 62 | debug('frame', frame); 63 | 64 | // if user is $owner, allow 65 | // XXX: Hack to work around $frameManager role taking precedence of $owner 66 | Role.isOwner(context.model, context.modelId, userId, function(err, owner) { 67 | if (owner) { 68 | return cb(null, true); 69 | } 70 | frame.managers.findById(userId, function(err, manager) { 71 | if (err || !manager) { 72 | return reject(err); 73 | } 74 | cb(null, true); 75 | }); 76 | }); 77 | 78 | }); 79 | }); 80 | }; 81 | 82 | -------------------------------------------------------------------------------- /server/boot/authentication.js: -------------------------------------------------------------------------------- 1 | /* 2 | Openframe-APIServer is the server component of Openframe, a platform for displaying digital art. 3 | Copyright (C) 2017 Jonathan Wohl 4 | 5 | This program is free software: you can redistribute it and/or modify 6 | it under the terms of the GNU Affero General Public License as published 7 | by the Free Software Foundation, either version 3 of the License, or 8 | (at your option) any later version. 9 | 10 | This program is distributed in the hope that it will be useful, 11 | but WITHOUT ANY WARRANTY; without even the implied warranty of 12 | MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 13 | GNU Affero General Public License for more details. 14 | 15 | You should have received a copy of the GNU Affero General Public License 16 | along with this program. If not, see . 17 | */ 18 | 19 | module.exports = function enableAuthentication(app) { 20 | // enable authentication 21 | app.enableAuth(); 22 | }; 23 | 24 | -------------------------------------------------------------------------------- /server/boot/create-admin-user.js.example: -------------------------------------------------------------------------------- 1 | /* 2 | Openframe-APIServer is the server component of Openframe, a platform for displaying digital art. 3 | Copyright (C) 2017 Jonathan Wohl 4 | 5 | This program is free software: you can redistribute it and/or modify 6 | it under the terms of the GNU Affero General Public License as published 7 | by the Free Software Foundation, either version 3 of the License, or 8 | (at your option) any later version. 9 | 10 | This program is distributed in the hope that it will be useful, 11 | but WITHOUT ANY WARRANTY; without even the implied warranty of 12 | MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 13 | GNU Affero General Public License for more details. 14 | 15 | You should have received a copy of the GNU Affero General Public License 16 | along with this program. If not, see . 17 | */ 18 | 19 | var debug = require('debug')('openframe:apiserver:admin-user'); 20 | 21 | module.exports = function(app) { 22 | var OpenframeUser = app.models.OpenframeUser, 23 | Role = app.models.Role, 24 | RoleMapping = app.models.RoleMapping; 25 | 26 | OpenframeUser.create([{ 27 | username: 'admin', 28 | email: 'admin@openframe.io', 29 | password: 'pass' 30 | }], function(err, users) { 31 | if (err) { 32 | return debug('%j', err); 33 | } 34 | 35 | // Create the admin role 36 | Role.create({ 37 | name: 'admin' 38 | }, function(err, role) { 39 | if (err) { 40 | return debug(err); 41 | } 42 | debug(role); 43 | 44 | // Make Bob an admin 45 | role.principals.create({ 46 | principalType: RoleMapping.USER, 47 | principalId: users[0].id 48 | }, function(err, principal) { 49 | if (err) { 50 | return debug(err); 51 | } 52 | debug(principal); 53 | }); 54 | }); 55 | }); 56 | }; 57 | -------------------------------------------------------------------------------- /server/boot/create-sample-data.js: -------------------------------------------------------------------------------- 1 | /* 2 | Openframe-APIServer is the server component of Openframe, a platform for displaying digital art. 3 | Copyright (C) 2017 Jonathan Wohl 4 | 5 | This program is free software: you can redistribute it and/or modify 6 | it under the terms of the GNU Affero General Public License as published 7 | by the Free Software Foundation, either version 3 of the License, or 8 | (at your option) any later version. 9 | 10 | This program is distributed in the hope that it will be useful, 11 | but WITHOUT ANY WARRANTY; without even the implied warranty of 12 | MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 13 | GNU Affero General Public License for more details. 14 | 15 | You should have received a copy of the GNU Affero General Public License 16 | along with this program. If not, see . 17 | */ 18 | 19 | var debug = require('debug')('openframe:apiserver:sample-data'); 20 | 21 | module.exports = function(app) { 22 | if (process.env.LB_DB_DS_NAME) { 23 | return; 24 | } 25 | var dataSource = 'memoryDb'; 26 | app.dataSources[dataSource].automigrate('Artwork', function(err) { 27 | if (err) throw err; 28 | 29 | // WIPE DATA ON APP START 30 | // !!! FOR TESTING ONLY !!! 31 | wipeData() 32 | .then(function(info) { 33 | debug('wiped!', info); 34 | 35 | Promise.all([ 36 | createOpenframeUsers(), 37 | createArtwork(), 38 | createFrames() 39 | ]) 40 | .then(formRelationships) 41 | .catch(function(err) { 42 | debug('ERROR!', err); 43 | }); 44 | }); 45 | 46 | 47 | 48 | function wipeData() { 49 | debug('wipeData'); 50 | var usersWipe = new Promise(function(resolve, reject) { 51 | app.models.OpenframeUser.destroyAll(function(err, info) { 52 | if (err) reject(err); 53 | resolve(info); 54 | }); 55 | }); 56 | var artworkWipe = new Promise(function(resolve, reject) { 57 | app.models.Artwork.destroyAll(function(err, info) { 58 | if (err) reject(err); 59 | resolve(info); 60 | }); 61 | }); 62 | var framesWipe = new Promise(function(resolve, reject) { 63 | app.models.Frame.destroyAll(function(err, info) { 64 | if (err) reject(err); 65 | resolve(info); 66 | }); 67 | }); 68 | 69 | return Promise.all([usersWipe, artworkWipe, framesWipe]); 70 | } 71 | 72 | function createOpenframeUsers() { 73 | return new Promise(function(resolve, reject) { 74 | app.models.OpenframeUser.create([{ 75 | username: 'slewitt', 76 | email: 'slewitt@openframe.io', 77 | password: 'asdf', 78 | full_name: 'Sol Lewitt', 79 | emailVerified: true, 80 | settings: { 81 | enable_notifications: true 82 | } 83 | }, { 84 | username: 'ppan', 85 | email: 'ppan@openframe.io', 86 | password: 'asdf', 87 | full_name: 'Peter Pan', 88 | emailVerified: true, 89 | settings: { 90 | enable_notifications: false 91 | } 92 | }, { 93 | username: 'melliot', 94 | email: 'melliot@openframe.io', 95 | password: 'asdf', 96 | full_name: 'Missy Elliot', 97 | emailVerified: true, 98 | settings: { 99 | enable_notifications: true 100 | } 101 | }, ], function(err, users) { 102 | if (err) reject(err); 103 | debug('Users created'); 104 | resolve(users); 105 | }); 106 | }); 107 | } 108 | 109 | function createFrames() { 110 | return new Promise(function(resolve, reject) { 111 | app.models.Frame.create([{ 112 | name: 'Frame A', 113 | settings: {} 114 | }, { 115 | name: 'Frame B', 116 | settings: {} 117 | }, { 118 | name: 'Frame C', 119 | settings: {} 120 | }, ], function(err, frames) { 121 | if (err) reject(err); 122 | debug('Frames created'); 123 | resolve(frames); 124 | }); 125 | }); 126 | } 127 | 128 | function createArtwork() { 129 | return new Promise(function(resolve, reject) { 130 | app.models.Artwork.create([{ 131 | title: 'Example Image', 132 | author_name: 'Ima Geofit', 133 | is_public: true, 134 | url: 'http://www.codex99.com/cartography/images/everest/everest_imhof_lg.jpg', 135 | thumb_url: 'http://www.codex99.com/cartography/images/everest/everest_imhof_lg.jpg', 136 | plugins: { 137 | 'openframe-image': 'git+https://git@github.com/OpenframeProject/Openframe-Image.git' 138 | }, 139 | format: 'openframe-image', 140 | 'ownerId': 1 141 | }, { 142 | title: 'Example Shader', 143 | author_name: 'Slim Shady', 144 | is_public: true, 145 | url: 'http://jonathanwohl.com/test.frag', 146 | thumb_url: 'http://jonathanwohl.com/shader-thumb.png', 147 | plugins: { 148 | 'openframe-glslviewer': 'git+https://git@github.com/OpenframeProject/Openframe-glslViewer.git' 149 | }, 150 | format: 'openframe-glslviewer', 151 | 'ownerId': 2 152 | }, { 153 | title: 'Example Website', 154 | author_name: 'Webster McBride', 155 | is_public: true, 156 | url: 'http://www.flyingfrying.com/', 157 | thumb_url: 'http://jonathanwohl.com/frying-egg.png', 158 | plugins: { 159 | 'openframe-website': 'git+https://git@github.com/OpenframeProject/Openframe-Website.git' 160 | }, 161 | format: 'openframe-website', 162 | 'ownerId': 3 163 | }, ], function(err, artwork) { 164 | if (err) reject(err); 165 | debug('Artworks created'); 166 | resolve(artwork); 167 | }); 168 | }); 169 | } 170 | 171 | function formRelationships(values) { 172 | debug('formRelationships', values); 173 | var users = values[0], 174 | artworks = values[1], 175 | frames = values[2]; 176 | 177 | // create a collection for each user, add all 178 | // artwork to each 179 | users.forEach(function(user) { 180 | // all users can manage frame id:2 (idx 1) 181 | user.managed_frames.add(frames[1]); 182 | 183 | 184 | user.collections.create({ 185 | name: 'Main Collection' 186 | }, function(err, collection) { 187 | debug('user.collection(): ', user.collections()); 188 | collection.artwork.add(artworks[0]); 189 | collection.artwork.add(artworks[1]); 190 | collection.artwork.add(artworks[2]); 191 | collection.current_artwork(artworks[0]); 192 | collection.save(); 193 | }); 194 | }); 195 | 196 | // add an 'owner' user to each frame 197 | frames.forEach(function(frame, idx) { 198 | // each user owns one frame 199 | frame.owner(users[idx]); 200 | frame.save(); 201 | }); 202 | } 203 | 204 | 205 | }); 206 | }; 207 | -------------------------------------------------------------------------------- /server/boot/pubsub.js: -------------------------------------------------------------------------------- 1 | /* 2 | Openframe-APIServer is the server component of Openframe, a platform for displaying digital art. 3 | Copyright (C) 2017 Jonathan Wohl 4 | 5 | This program is free software: you can redistribute it and/or modify 6 | it under the terms of the GNU Affero General Public License as published 7 | by the Free Software Foundation, either version 3 of the License, or 8 | (at your option) any later version. 9 | 10 | This program is distributed in the hope that it will be useful, 11 | but WITHOUT ANY WARRANTY; without even the implied warranty of 12 | MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 13 | GNU Affero General Public License for more details. 14 | 15 | You should have received a copy of the GNU Affero General Public License 16 | along with this program. If not, see . 17 | */ 18 | 19 | var faye = require('faye'), 20 | debug = require('debug')('openframe:apiserver:pubsub'); 21 | 22 | module.exports = function(app) { 23 | debug('instantiating pubsub module'); 24 | 25 | var ps_url = app.get('pubsub_url'), 26 | ps_token = app.get('pubsub_api_token'), 27 | clientAuth = { 28 | outgoing: function(message, callback) { 29 | debug(message); 30 | // leave non-subscribe messages alone 31 | if (message.channel !== '/meta/subscribe') { 32 | return callback(message); 33 | } 34 | 35 | // Add ext field if it's not present 36 | if (!message.ext) { 37 | message.ext = {}; 38 | } 39 | 40 | // Set the auth token 41 | message.ext.accessToken = ps_token; 42 | 43 | // Carry on and send the message to the server 44 | callback(message); 45 | } 46 | }; 47 | 48 | app.set('ps_url', ps_url); 49 | 50 | // Once the loopback app has started, connect to the PubSub server 51 | app.on('started', function() { 52 | // add a pubsub client for the API app 53 | app.pubsub = new faye.Client(ps_url); 54 | 55 | app.pubsub.addExtension(clientAuth); 56 | 57 | // handlers for pubsub connection events 58 | app.pubsub.on('transport:down', function() { 59 | // the pubsub client is offline 60 | debug('pubsub client offline'); 61 | }); 62 | 63 | // handlers for pubsub connection events 64 | app.pubsub.on('transport:up', function() { 65 | // the pubsub client is online 66 | debug('pubsub client online'); 67 | }); 68 | 69 | // listen for all /frame/connected events, update the Frame 70 | app.pubsub.subscribe('/frame/connected', function(frame_id) { 71 | // update frame status 72 | debug('frame %s connected', frame_id); 73 | app.models.Frame.findById(frame_id, function(err, frame) { 74 | if (err) { 75 | debug(err); 76 | return; 77 | } 78 | if (frame === null) { 79 | debug('Unknown frame connected.'); 80 | return; 81 | } 82 | frame.connected = true; 83 | frame.save(); 84 | }); 85 | }); 86 | 87 | // listen for all /frame/disconnected events, update the Frame 88 | app.pubsub.subscribe('/frame/disconnected', function(frame_id) { 89 | debug('frame %s disconnected', frame_id); 90 | // update frame status 91 | app.models.Frame.findById(frame_id, function(err, frame) { 92 | if (err) { 93 | debug(err); 94 | return; 95 | } 96 | if (frame === null) { 97 | debug('Unknown frame disconnected.'); 98 | return; 99 | } 100 | frame.connected = false; 101 | frame.save(); 102 | }); 103 | }); 104 | }); 105 | }; 106 | -------------------------------------------------------------------------------- /server/boot/routes.js: -------------------------------------------------------------------------------- 1 | /* 2 | Openframe-APIServer is the server component of Openframe, a platform for displaying digital art. 3 | Copyright (C) 2017 Jonathan Wohl 4 | 5 | This program is free software: you can redistribute it and/or modify 6 | it under the terms of the GNU Affero General Public License as published 7 | by the Free Software Foundation, either version 3 of the License, or 8 | (at your option) any later version. 9 | 10 | This program is distributed in the hope that it will be useful, 11 | but WITHOUT ANY WARRANTY; without even the implied warranty of 12 | MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 13 | GNU Affero General Public License for more details. 14 | 15 | You should have received a copy of the GNU Affero General Public License 16 | along with this program. If not, see . 17 | */ 18 | 19 | var debug = require('debug')('openframe:apiserver:routes'); 20 | 21 | module.exports = function(app) { 22 | 23 | /** 24 | * In dev environment, add an endpoint that lets us publish messages on the GEB 25 | * 26 | * The endpoint expects two query params, 'channel' which specifies the channel on which to publish, 27 | * and 'data', a JSON string of data to be parsed and passed along as the message payload. 28 | * 29 | * Example: 30 | * 31 | * http://localhost:8888/ps?channel=/frame/12345/connected&data={"some":"data"} 32 | */ 33 | if (app.get('env') === 'development') { 34 | app.get('/ps', function(req, res) { 35 | const { channel, data } = req.query; 36 | if (channel) { 37 | let message = data ? JSON.parse(data) : null; 38 | debug(channel, message); 39 | app.pubsub.publish(channel, message); 40 | res.send(`Published message ${JSON.stringify(message)} to ${channel}`); 41 | } else { 42 | res.send(`No message published. Please specify a 'channel' query param.`); 43 | } 44 | }); 45 | } 46 | 47 | }; 48 | 49 | -------------------------------------------------------------------------------- /server/component-config.json: -------------------------------------------------------------------------------- 1 | { 2 | "loopback-component-explorer": { 3 | "mountPath": "/explorer" 4 | } 5 | } 6 | -------------------------------------------------------------------------------- /server/config.json: -------------------------------------------------------------------------------- 1 | { 2 | "restApiRoot": "/v0", 3 | "host": "0.0.0.0", 4 | "port": 8888, 5 | "remoting": { 6 | "rest": { 7 | "normalizeHttpPath": false, 8 | "xml": false, 9 | "handleErrors": false 10 | }, 11 | "json": { 12 | "strict": false, 13 | "limit": "100kb" 14 | }, 15 | "urlencoded": { 16 | "extended": true, 17 | "limit": "100kb" 18 | }, 19 | "cors": false, 20 | "errorHandler": { 21 | "disableStackTrace": false 22 | } 23 | }, 24 | "api_url": "http://0.0.0.0:8888", 25 | "webapp_url": "http://0.0.0.0:8000", 26 | "pubsub_url": "http://0.0.0.0:8889/faye", 27 | "pubsub_api_token": "8629c101-4db7-4a0a-9bf1-33c5fc3cff23", 28 | "legacyExplorer": false, 29 | "secret_cookie": "yabbadabbadoo!" 30 | } 31 | -------------------------------------------------------------------------------- /server/config.local.js: -------------------------------------------------------------------------------- 1 | /* 2 | Openframe-APIServer is the server component of Openframe, a platform for displaying digital art. 3 | Copyright (C) 2017 Jonathan Wohl 4 | 5 | This program is free software: you can redistribute it and/or modify 6 | it under the terms of the GNU Affero General Public License as published 7 | by the Free Software Foundation, either version 3 of the License, or 8 | (at your option) any later version. 9 | 10 | This program is distributed in the hope that it will be useful, 11 | but WITHOUT ANY WARRANTY; without even the implied warranty of 12 | MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 13 | GNU Affero General Public License for more details. 14 | 15 | You should have received a copy of the GNU Affero General Public License 16 | along with this program. If not, see . 17 | */ 18 | 19 | var p = require('../package.json'), 20 | version = p.version.split('.').shift(); 21 | module.exports = { 22 | restApiRoot: '/v' + version, 23 | host: process.env.API_HOST || '0.0.0.0', 24 | port: process.env.API_PORT || 8888, 25 | 'remoting': { 26 | 'rest': { 27 | 'normalizeHttpPath': false, 28 | 'xml': false 29 | }, 30 | 'json': { 31 | 'strict': false, 32 | 'limit': '100kb' 33 | }, 34 | 'urlencoded': { 35 | 'extended': true, 36 | 'limit': '100kb' 37 | }, 38 | 'cors': false, 39 | 'errorHandler': { 40 | 'disableStackTrace': false 41 | } 42 | }, 43 | 'api_url': process.env.API_EXPOSED_URL || null, 44 | 'webapp_url': process.env.WEBAPP_EXPOSED_URL || null, 45 | 'pubsub_url': process.env.PS_EXPOSED_URL || 'http://0.0.0.0:8889/faye', 46 | 'pubsub_api_token': process.env.PS_API_TOKEN || '8629c101-4db7-4a0a-9bf1-33c5fc3cff23', 47 | 'cookieSecret': process.env.COOKIE_SECRECT || 'cce6829c-328e-4e38-aa5c-f4714ac93914', 48 | 'legacyExplorer': false 49 | }; 50 | -------------------------------------------------------------------------------- /server/datasources.json: -------------------------------------------------------------------------------- 1 | { 2 | "memoryDb": { 3 | "name": "memoryDb", 4 | "connector": "memory" 5 | } 6 | } 7 | -------------------------------------------------------------------------------- /server/datasources.local.js: -------------------------------------------------------------------------------- 1 | /* 2 | Openframe-APIServer is the server component of Openframe, a platform for displaying digital art. 3 | Copyright (C) 2017 Jonathan Wohl 4 | 5 | This program is free software: you can redistribute it and/or modify 6 | it under the terms of the GNU Affero General Public License as published 7 | by the Free Software Foundation, either version 3 of the License, or 8 | (at your option) any later version. 9 | 10 | This program is distributed in the hope that it will be useful, 11 | but WITHOUT ANY WARRANTY; without even the implied warranty of 12 | MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 13 | GNU Affero General Public License for more details. 14 | 15 | You should have received a copy of the GNU Affero General Public License 16 | along with this program. If not, see . 17 | */ 18 | 19 | 20 | var data_source_conf = {}; 21 | 22 | // if LP_DB_DS_CONNECTOR is set, assume we're using a custom db connector 23 | if (process.env.LB_DB_DS_CONNECTOR) { 24 | data_source_conf[process.env.LB_DB_DS_NAME] = { 25 | name: process.env.LB_DB_DS_NAME, 26 | connector: process.env.LB_DB_DS_CONNECTOR, 27 | database: process.env.LB_DB_DS_DATABASE, 28 | debug: process.env.LB_DB_DS_DEBUG, 29 | host: process.env.LB_DB_DS_HOST, 30 | port: process.env.LB_DB_DS_PORT, 31 | url: process.env.LB_DB_DS_URL || null, 32 | username: process.env.LB_DB_DS_USERNAME || null, 33 | password: process.env.LB_DB_DS_PASSWORD || null 34 | }; 35 | } 36 | 37 | // if LP_EMAIL_DS_CONNECTOR is set, assume we're using a custom db connector 38 | if (process.env.LB_EMAIL_DS_CONNECTOR) { 39 | data_source_conf[process.env.LB_EMAIL_DS_NAME] = { 40 | name: process.env.LB_EMAIL_DS_NAME, 41 | connector: process.env.LB_EMAIL_DS_CONNECTOR, 42 | transports: [ 43 | { 44 | type: process.env.LB_EMAIL_DS_TYPE || 'SMTP', 45 | host: process.env.LB_EMAIL_DS_HOST, 46 | // only allow secure connection 47 | secure: true, 48 | port: process.env.LB_EMAIL_DS_PORT || 465, 49 | auth: { 50 | user: process.env.LB_EMAIL_DS_USERNAME, 51 | pass: process.env.LB_EMAIL_DS_PASSWORD 52 | } 53 | } 54 | ] 55 | }; 56 | } 57 | 58 | module.exports = data_source_conf; 59 | -------------------------------------------------------------------------------- /server/lib/auth.js: -------------------------------------------------------------------------------- 1 | /* 2 | Openframe-APIServer is the server component of Openframe, a platform for displaying digital art. 3 | Copyright (C) 2017 Jonathan Wohl 4 | 5 | This program is free software: you can redistribute it and/or modify 6 | it under the terms of the GNU Affero General Public License as published 7 | by the Free Software Foundation, either version 3 of the License, or 8 | (at your option) any later version. 9 | 10 | This program is distributed in the hope that it will be useful, 11 | but WITHOUT ANY WARRANTY; without even the implied warranty of 12 | MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 13 | GNU Affero General Public License for more details. 14 | 15 | You should have received a copy of the GNU Affero General Public License 16 | along with this program. If not, see . 17 | */ 18 | 19 | var blacklist = require('the-big-username-blacklist'); 20 | 21 | module.exports = { 22 | blacklist: [ 23 | 'create-account', 24 | 'login-success', 25 | 'test', 26 | 'verified' 27 | ].concat(blacklist.list) 28 | }; 29 | -------------------------------------------------------------------------------- /server/middleware.json: -------------------------------------------------------------------------------- 1 | { 2 | "initial:before": { 3 | "loopback#favicon": { 4 | "params": "$!../client/favicon.ico" 5 | } 6 | }, 7 | "initial": { 8 | "compression": {}, 9 | "cors": { 10 | "params": { 11 | "origin": true, 12 | "credentials": true, 13 | "maxAge": 86400 14 | } 15 | } 16 | }, 17 | "session": {}, 18 | "auth": {}, 19 | "parse": {}, 20 | "routes": { 21 | "loopback#rest": { 22 | "paths": [ 23 | "${restApiRoot}" 24 | ] 25 | } 26 | }, 27 | "files": { 28 | "loopback#static": [] 29 | }, 30 | "final": { 31 | "./middleware/url-not-found-handler": {} 32 | }, 33 | "final:after": { 34 | "strong-error-handler": { 35 | "params": { 36 | "debug": true, 37 | "log": false 38 | } 39 | } 40 | } 41 | } 42 | -------------------------------------------------------------------------------- /server/middleware.production.json: -------------------------------------------------------------------------------- 1 | { 2 | "final:after": { 3 | "strong-error-handler": { 4 | "params": { 5 | "debug": false, 6 | "log": true 7 | } 8 | } 9 | } 10 | } 11 | -------------------------------------------------------------------------------- /server/middleware/error-handler.js: -------------------------------------------------------------------------------- 1 | /* 2 | Openframe-APIServer is the server component of Openframe, a platform for displaying digital art. 3 | Copyright (C) 2017 Jonathan Wohl 4 | 5 | This program is free software: you can redistribute it and/or modify 6 | it under the terms of the GNU Affero General Public License as published 7 | by the Free Software Foundation, either version 3 of the License, or 8 | (at your option) any later version. 9 | 10 | This program is distributed in the hope that it will be useful, 11 | but WITHOUT ANY WARRANTY; without even the implied warranty of 12 | MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 13 | GNU Affero General Public License for more details. 14 | 15 | You should have received a copy of the GNU Affero General Public License 16 | along with this program. If not, see . 17 | */ 18 | 19 | 'use strict'; 20 | 21 | module.exports = function () { 22 | //4XX - URLs not found 23 | return function customRaiseUrlNotFoundError(req, res, next) { 24 | // res.redirect('http://openframe.io'); 25 | res.render('error'); 26 | }; 27 | }; 28 | -------------------------------------------------------------------------------- /server/middleware/url-not-found-handler.js: -------------------------------------------------------------------------------- 1 | /* 2 | Openframe-APIServer is the server component of Openframe, a platform for displaying digital art. 3 | Copyright (C) 2017 Jonathan Wohl 4 | 5 | This program is free software: you can redistribute it and/or modify 6 | it under the terms of the GNU Affero General Public License as published 7 | by the Free Software Foundation, either version 3 of the License, or 8 | (at your option) any later version. 9 | 10 | This program is distributed in the hope that it will be useful, 11 | but WITHOUT ANY WARRANTY; without even the implied warranty of 12 | MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 13 | GNU Affero General Public License for more details. 14 | 15 | You should have received a copy of the GNU Affero General Public License 16 | along with this program. If not, see . 17 | */ 18 | 19 | 'use strict'; 20 | 21 | module.exports = function() { 22 | //4XX - URLs not found 23 | return function customRaiseUrlNotFoundError(req, res, next) { 24 | // res.redirect('http://openframe.io'); 25 | res.render('404'); 26 | }; 27 | }; 28 | -------------------------------------------------------------------------------- /server/model-config.json: -------------------------------------------------------------------------------- 1 | { 2 | "_meta": { 3 | "sources": [ 4 | "loopback/common/models", 5 | "loopback/server/models", 6 | "../common/models", 7 | "./models" 8 | ], 9 | "mixins": [ 10 | "loopback/common/mixins", 11 | "loopback/server/mixins", 12 | "../common/mixins", 13 | "./mixins" 14 | ] 15 | }, 16 | "User": { 17 | "dataSource": "memoryDb", 18 | "public": false 19 | }, 20 | "AccessToken": { 21 | "dataSource": "memoryDb", 22 | "public": false 23 | }, 24 | "ACL": { 25 | "dataSource": "memoryDb", 26 | "public": false 27 | }, 28 | "RoleMapping": { 29 | "dataSource": "memoryDb", 30 | "public": false 31 | }, 32 | "Role": { 33 | "dataSource": "memoryDb", 34 | "public": false 35 | }, 36 | "OpenframeUser": { 37 | "dataSource": "memoryDb", 38 | "public": true 39 | }, 40 | "Frame": { 41 | "dataSource": "memoryDb", 42 | "public": true 43 | }, 44 | "Artwork": { 45 | "dataSource": "memoryDb", 46 | "public": true 47 | }, 48 | "Collection": { 49 | "dataSource": "memoryDb", 50 | "public": false 51 | }, 52 | "Channel": { 53 | "dataSource": "memoryDb", 54 | "public": true 55 | } 56 | } 57 | -------------------------------------------------------------------------------- /server/model-config.local.js: -------------------------------------------------------------------------------- 1 | /* 2 | Openframe-APIServer is the server component of Openframe, a platform for displaying digital art. 3 | Copyright (C) 2017 Jonathan Wohl 4 | 5 | This program is free software: you can redistribute it and/or modify 6 | it under the terms of the GNU Affero General Public License as published 7 | by the Free Software Foundation, either version 3 of the License, or 8 | (at your option) any later version. 9 | 10 | This program is distributed in the hope that it will be useful, 11 | but WITHOUT ANY WARRANTY; without even the implied warranty of 12 | MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 13 | GNU Affero General Public License for more details. 14 | 15 | You should have received a copy of the GNU Affero General Public License 16 | along with this program. If not, see . 17 | */ 18 | 19 | module.exports = { 20 | '_meta': { 21 | 'sources': [ 22 | 'loopback/common/models', 23 | 'loopback/server/models', 24 | '../common/models', 25 | './models' 26 | ], 27 | 'mixins': [ 28 | 'loopback/common/mixins', 29 | 'loopback/server/mixins', 30 | '../common/mixins', 31 | './mixins' 32 | ] 33 | }, 34 | 'User': { 35 | 'dataSource': process.env.LB_DB_DS_NAME || 'memoryDb', 36 | 'public': false 37 | }, 38 | 'AccessToken': { 39 | 'dataSource': process.env.LB_DB_DS_NAME || 'memoryDb', 40 | 'public': false 41 | }, 42 | 'ACL': { 43 | 'dataSource': process.env.LB_DB_DS_NAME || 'memoryDb', 44 | 'public': false 45 | }, 46 | 'RoleMapping': { 47 | 'dataSource': process.env.LB_DB_DS_NAME || 'memoryDb', 48 | 'public': false 49 | }, 50 | 'Role': { 51 | 'dataSource': process.env.LB_DB_DS_NAME || 'memoryDb', 52 | 'public': false 53 | }, 54 | 'OpenframeUser': { 55 | 'dataSource': process.env.LB_DB_DS_NAME || 'memoryDb', 56 | 'public': true, 57 | 'options': { 58 | 'emailVerificationRequired': true 59 | } 60 | }, 61 | 'Frame': { 62 | 'dataSource': process.env.LB_DB_DS_NAME || 'memoryDb', 63 | 'public': true 64 | }, 65 | 'Artwork': { 66 | 'dataSource': process.env.LB_DB_DS_NAME || 'memoryDb', 67 | 'public': true 68 | }, 69 | 'Collection': { 70 | 'dataSource': process.env.LB_DB_DS_NAME || 'memoryDb', 71 | 'public': false 72 | }, 73 | 'Channel': { 74 | 'dataSource': process.env.LB_DB_DS_NAME || 'memoryDb', 75 | 'public': false 76 | }, 77 | 'Email': { 78 | 'dataSource': process.env.LB_EMAIL_DS_NAME || null 79 | } 80 | }; 81 | -------------------------------------------------------------------------------- /server/providers.json: -------------------------------------------------------------------------------- 1 | { 2 | "local": { 3 | "provider": "local", 4 | "module": "passport-local", 5 | "usernameField": "username", 6 | "passwordField": "password", 7 | "authPath": "/auth/local", 8 | "successRedirect": "/login-success", 9 | "failureRedirect": "/login", 10 | "failureFlash": true, 11 | "setAccessToken" : true, 12 | "session" : true 13 | } 14 | } 15 | 16 | -------------------------------------------------------------------------------- /server/server.js: -------------------------------------------------------------------------------- 1 | /* 2 | Openframe-APIServer is the server component of Openframe, a platform for displaying digital art. 3 | Copyright (C) 2017 Jonathan Wohl 4 | 5 | This program is free software: you can redistribute it and/or modify 6 | it under the terms of the GNU Affero General Public License as published 7 | by the Free Software Foundation, either version 3 of the License, or 8 | (at your option) any later version. 9 | 10 | This program is distributed in the hope that it will be useful, 11 | but WITHOUT ANY WARRANTY; without even the implied warranty of 12 | MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 13 | GNU Affero General Public License for more details. 14 | 15 | You should have received a copy of the GNU Affero General Public License 16 | along with this program. If not, see . 17 | */ 18 | 19 | var loopback = require('loopback'), 20 | boot = require('loopback-boot'), 21 | bodyParser = require('body-parser'), 22 | path = require('path'), 23 | debug = require('debug')('openframe:apiserver'), 24 | 25 | // EXPORT THE APP 26 | app = module.exports = loopback(); 27 | 28 | // Set some app configuration 29 | app.set('view engine', 'ejs'); // LoopBack comes with EJS out-of-box 30 | app.set('json spaces', 2); // format json responses for easier viewing 31 | app.set('views', path.resolve(__dirname, 'views')); 32 | 33 | app.use(loopback.token({ 34 | cookies: ['access_token'], 35 | headers: ['access_token'], 36 | params: ['access_token'], 37 | currentUserLiteral: 'current' 38 | })); 39 | 40 | // boot scripts mount components like REST API 41 | boot(app, __dirname); 42 | 43 | // to support JSON-encoded bodies 44 | app.middleware('parse', bodyParser.json()); 45 | // to support URL-encoded bodies 46 | app.middleware('parse', bodyParser.urlencoded({ 47 | extended: true 48 | })); 49 | 50 | // The access token is only available after boot 51 | app.middleware('auth', loopback.token({ 52 | model: app.models.AccessToken 53 | })); 54 | 55 | app.use(loopback.static(path.resolve(__dirname, '../client'))); 56 | 57 | app.start = function() { 58 | // start the web server 59 | return app.listen(function() { 60 | app.emit('started'); 61 | var baseUrl = app.get('url').replace(/\/$/, ''); 62 | debug('Web server listening at: %s', baseUrl); 63 | if (app.get('loopback-component-explorer')) { 64 | var explorerPath = app.get('loopback-component-explorer').mountPath; 65 | debug('Browse your REST API at %s%s', baseUrl, explorerPath); 66 | } 67 | }); 68 | }; 69 | 70 | // start the server if `$ node server.js` 71 | if (require.main === module) { 72 | app.start(); 73 | } 74 | -------------------------------------------------------------------------------- /server/views/404.ejs: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | Openframe API 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 38 | 39 | 40 | 41 | 42 | 43 |

4 4

44 |

It's just been one of those days.

45 |

Maybe you'll find what you're looking for over at openframe.io?

46 | 47 | 48 | 49 | 50 | -------------------------------------------------------------------------------- /server/views/email-templates/reset-password.ejs: -------------------------------------------------------------------------------- 1 | 2 |
3 |

Password reset requested

4 | 5 |

If you did not initiate this password reset, please ignore this email.

6 | 7 |

To reset your Openframe password, visit the following link and follow the instructions:

8 |

9 | 10 | Reset Password 11 | 12 |

13 |
-------------------------------------------------------------------------------- /server/views/email-templates/verify.ejs: -------------------------------------------------------------------------------- 1 | 2 |
3 |

Welcome to Openframe

4 | 5 |

You're almost ready to start displaying artwork!

6 | 7 | 8 | Please click the link below to verify your email address: 9 | 10 |

11 | Verify Email 12 |

13 | 14 |

For info and instructions on setting up a frame, please check out the Frame Setup Guide.

15 | 16 |

If you did not create an account on the Openframe website, please ignore this email.

17 |

-------------------------------------------------------------------------------- /server/views/error.ejs: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | Openframe API 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 38 | 39 | 40 | 41 | 42 | 43 |

Error

44 |

It's just been one of those days.

45 |

Maybe you'll find what you're looking for over at openframe.io?

46 | 47 | 48 | 49 | 50 | --------------------------------------------------------------------------------